diff --git a/go_backend/ape_tags.go b/go_backend/ape_tags.go index fe112f97..c654e160 100644 --- a/go_backend/ape_tags.go +++ b/go_backend/ape_tags.go @@ -453,7 +453,7 @@ func apeKeysFromFields(fields map[string]string) map[string]struct{} { "artist": "ARTIST", "album": "ALBUM", "album_artist": "ALBUM ARTIST", - "date": "YEAR", + "date": "DATE", "genre": "GENRE", "track_number": "TRACK", "disc_number": "DISC", @@ -475,7 +475,7 @@ func apeKeysFromFields(fields map[string]string) map[string]struct{} { } } // Some fields have reader aliases that must also be cleared when the - // canonical key is updated (e.g. "Year" writer ↔ DATE/YEAR reader, + // canonical key is updated (e.g. DATE writer ↔ DATE/YEAR reader, // DISC ↔ DISCNUMBER, TRACK ↔ TRACKNUMBER, "ALBUM ARTIST" ↔ ALBUMARTIST, // LABEL ↔ PUBLISHER, LYRICS ↔ UNSYNCEDLYRICS). if _, present := fields["date"]; present { diff --git a/go_backend/library_scan.go b/go_backend/library_scan.go index dc818ddd..c8ec9f54 100644 --- a/go_backend/library_scan.go +++ b/go_backend/library_scan.go @@ -31,6 +31,8 @@ type LibraryScanResult struct { SampleRate int `json:"sampleRate,omitempty"` Bitrate int `json:"bitrate,omitempty"` // kbps, for lossy formats (MP3, Opus, Vorbis) Genre string `json:"genre,omitempty"` + Label string `json:"label,omitempty"` + Copyright string `json:"copyright,omitempty"` Format string `json:"format,omitempty"` MetadataFromFilename bool `json:"metadataFromFilename,omitempty"` } @@ -368,6 +370,8 @@ func scanFLACFile(filePath string, result *LibraryScanResult, displayNameHint st result.DiscNumber = metadata.DiscNumber result.ReleaseDate = metadata.Date result.Genre = metadata.Genre + result.Label = metadata.Label + result.Copyright = metadata.Copyright quality, err := GetAudioQuality(filePath) if err == nil { @@ -403,6 +407,8 @@ func scanM4AFile(filePath string, result *LibraryScanResult, displayNameHint str result.ReleaseDate = metadata.Year } result.Genre = metadata.Genre + result.Label = metadata.Label + result.Copyright = metadata.Copyright } quality, err := GetM4AQuality(filePath) @@ -435,6 +441,8 @@ func scanMP3File(filePath string, result *LibraryScanResult, displayNameHint str result.ReleaseDate = metadata.Year } result.ISRC = metadata.ISRC + result.Label = metadata.Label + result.Copyright = metadata.Copyright quality, err := GetMP3Quality(filePath) if err == nil { @@ -467,6 +475,8 @@ func scanOggFile(filePath string, result *LibraryScanResult, displayNameHint str result.DiscNumber = metadata.DiscNumber result.Genre = metadata.Genre result.ReleaseDate = metadata.Date + result.Label = metadata.Label + result.Copyright = metadata.Copyright quality, err := GetOggQuality(filePath) if err == nil { @@ -508,6 +518,8 @@ func scanAPEFile(filePath string, result *LibraryScanResult, displayNameHint str } else { result.ReleaseDate = metadata.Year } + result.Label = metadata.Label + result.Copyright = metadata.Copyright applyDefaultLibraryMetadata(filePath, displayNameHint, result) diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index 02334b2e..bb8f8882 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -252,7 +252,7 @@ class _TrackMetadataScreenState extends ConsumerState { } if (mounted && exists && - !_isLocalItem && + !_isCueVirtualTrack && !_hasLoadedResolvedAudioMetadata) { unawaited(_refreshResolvedAudioMetadataFromFile()); } @@ -291,8 +291,9 @@ class _TrackMetadataScreenState extends ConsumerState { } Future _refreshResolvedAudioMetadataFromFile() async { - if (_isLocalItem || - _downloadItem == null || + if ((_isLocalItem && _localLibraryItem == null) || + (!_isLocalItem && _downloadItem == null) || + _isCueVirtualTrack || _hasLoadedResolvedAudioMetadata) { return; } @@ -338,9 +339,10 @@ class _TrackMetadataScreenState extends ConsumerState { (copyright == null || copyright!.isEmpty); final shouldPersistResolvedAudioMetadata = - resolvedBitDepth != null || - resolvedSampleRate != null || - (isPlaceholderQualityLabel(_quality) && resolvedQuality != null); + !_isLocalItem && + (resolvedBitDepth != null || + resolvedSampleRate != null || + (isPlaceholderQualityLabel(_quality) && resolvedQuality != null)); if ((resolvedBitDepth != null || resolvedSampleRate != null || @@ -517,10 +519,10 @@ class _TrackMetadataScreenState extends ConsumerState { (_isLocalItem ? _localLibraryItem!.genre : _downloadItem!.genre); String? get label => _editedMetadata?['label']?.toString() ?? - (_isLocalItem ? null : _downloadItem!.label); + (_isLocalItem ? _localLibraryItem!.label : _downloadItem!.label); String? get copyright => _editedMetadata?['copyright']?.toString() ?? - (_isLocalItem ? null : _downloadItem!.copyright); + (_isLocalItem ? _localLibraryItem!.copyright : _downloadItem!.copyright); int? get duration => _readPositiveInt(_editedMetadata?['duration']) ?? (_isLocalItem ? _localLibraryItem!.duration : _downloadItem!.duration); diff --git a/lib/services/ffmpeg_service.dart b/lib/services/ffmpeg_service.dart index 91c29ccb..ec2f05ce 100644 --- a/lib/services/ffmpeg_service.dart +++ b/lib/services/ffmpeg_service.dart @@ -1760,9 +1760,13 @@ class FFmpegService { if (value != '0') vorbis['DISCNUMBER'] = value; break; case 'DATE': - case 'YEAR': vorbis['DATE'] = value; break; + case 'YEAR': + if (!vorbis.containsKey('DATE') || vorbis['DATE']!.isEmpty) { + vorbis['DATE'] = value; + } + break; case 'GENRE': vorbis['GENRE'] = value; break; @@ -1921,9 +1925,13 @@ class FFmpegService { m4aMap['disc'] = value; break; case 'DATE': - case 'YEAR': m4aMap['date'] = value; break; + case 'YEAR': + if (!m4aMap.containsKey('date') || m4aMap['date']!.isEmpty) { + m4aMap['date'] = value; + } + break; case 'GENRE': m4aMap['genre'] = value; break; @@ -2004,9 +2012,13 @@ class FFmpegService { } break; case 'DATE': - case 'YEAR': id3Map['date'] = value; break; + case 'YEAR': + if (!id3Map.containsKey('date') || id3Map['date']!.isEmpty) { + id3Map['date'] = value; + } + break; case 'ISRC': id3Map['TSRC'] = value; break; diff --git a/lib/services/library_database.dart b/lib/services/library_database.dart index b3cb306b..c09694f6 100644 --- a/lib/services/library_database.dart +++ b/lib/services/library_database.dart @@ -27,6 +27,8 @@ class LocalLibraryItem { final int? sampleRate; final int? bitrate; // kbps, for lossy formats (mp3, opus, ogg) final String? genre; + final String? label; + final String? copyright; final String? format; // flac, mp3, opus, m4a const LocalLibraryItem({ @@ -48,6 +50,8 @@ class LocalLibraryItem { this.sampleRate, this.bitrate, this.genre, + this.label, + this.copyright, this.format, }); @@ -70,6 +74,8 @@ class LocalLibraryItem { 'sampleRate': sampleRate, 'bitrate': bitrate, 'genre': genre, + 'label': label, + 'copyright': copyright, 'format': format, }; @@ -93,6 +99,8 @@ class LocalLibraryItem { sampleRate: json['sampleRate'] as int?, bitrate: (json['bitrate'] as num?)?.toInt(), genre: json['genre'] as String?, + label: json['label'] as String?, + copyright: json['copyright'] as String?, format: json['format'] as String?, ); @@ -122,7 +130,7 @@ class LibraryDatabase { return await openDatabase( path, - version: 4, + version: 5, onConfigure: (db) async { await db.rawQuery('PRAGMA journal_mode = WAL'); await db.execute('PRAGMA synchronous = NORMAL'); @@ -155,6 +163,8 @@ class LibraryDatabase { sample_rate INTEGER, bitrate INTEGER, genre TEXT, + label TEXT, + copyright TEXT, format TEXT ) '''); @@ -190,6 +200,12 @@ class LibraryDatabase { await db.execute('ALTER TABLE library ADD COLUMN bitrate INTEGER'); _log.i('Added bitrate column for lossy format quality'); } + + if (oldVersion < 5) { + await db.execute('ALTER TABLE library ADD COLUMN label TEXT'); + await db.execute('ALTER TABLE library ADD COLUMN copyright TEXT'); + _log.i('Added label/copyright columns'); + } } Map _jsonToDbRow(Map json) { @@ -212,6 +228,8 @@ class LibraryDatabase { 'sample_rate': json['sampleRate'], 'bitrate': json['bitrate'], 'genre': json['genre'], + 'label': json['label'], + 'copyright': json['copyright'], 'format': json['format'], }; } @@ -236,6 +254,8 @@ class LibraryDatabase { 'sampleRate': row['sample_rate'], 'bitrate': row['bitrate'], 'genre': row['genre'], + 'label': row['label'], + 'copyright': row['copyright'], 'format': row['format'], }; }