From 39dcb8872845efab6fc1a134c2c440e8eba95085 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 10 Feb 2022 15:29:13 -0800 Subject: [PATCH] FFmpeg input: Support reading metadata Where TagLib is not being employed, use FFmpeg to read tags where possible. This allows reading tags from files like IFF. It reads it through properties, otherwise allowing tag readers to function like usual. Signed-off-by: Christopher Snowhill --- Audio/CogAudio.xcodeproj/project.pbxproj | 8 ++ Audio/PluginController.m | 5 +- Playlist/PlaylistEntry.m | 2 +- Plugins/FFMPEG/FFMPEGDecoder.h | 16 +++- Plugins/FFMPEG/FFMPEGDecoder.m | 103 ++++++++++++++++++++--- 5 files changed, 116 insertions(+), 18 deletions(-) diff --git a/Audio/CogAudio.xcodeproj/project.pbxproj b/Audio/CogAudio.xcodeproj/project.pbxproj index 68fca5c55..f5eaef1b8 100644 --- a/Audio/CogAudio.xcodeproj/project.pbxproj +++ b/Audio/CogAudio.xcodeproj/project.pbxproj @@ -66,6 +66,8 @@ 8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; }; 839366671815923C006DD712 /* CogPluginMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogPluginMulti.h */; }; 839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; }; + 8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */ = {isa = PBXBuildFile; fileRef = 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */; }; + 8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */; }; 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; 8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E8D3D2E0CBAEE6E00135C1B /* AudioContainer.m */; }; @@ -160,6 +162,8 @@ 8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = ""; }; 839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = ""; }; 839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = ""; }; + 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = ""; }; + 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = ""; }; 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8DC2EF5B0486A6940098B216 /* CogAudio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CogAudio.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioContainer.h; sourceTree = ""; }; @@ -333,6 +337,8 @@ 17D21CDC0B8BE5B400D1EBDE /* Utils */ = { isa = PBXGroup; children = ( + 8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */, + 8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */, 8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */, 8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */, 8384912618080FF100E7332D /* Logging.h */, @@ -430,6 +436,7 @@ 17D21CA10B8BE4BA00D1EBDE /* BufferChain.h in Headers */, 17D21CA50B8BE4BA00D1EBDE /* InputNode.h in Headers */, 17D21CA70B8BE4BA00D1EBDE /* Node.h in Headers */, + 8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */, 17D21CA90B8BE4BA00D1EBDE /* OutputNode.h in Headers */, 17D21CC50B8BE4BA00D1EBDE /* OutputCoreAudio.h in Headers */, 834FD4F427AFA2150063BC83 /* Downmix.h in Headers */, @@ -536,6 +543,7 @@ 835EDD7B279FE23A001EDCCE /* HeadphoneFilter.m in Sources */, 17D21CA20B8BE4BA00D1EBDE /* BufferChain.m in Sources */, 17D21CA60B8BE4BA00D1EBDE /* InputNode.m in Sources */, + 8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */, 17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */, 17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */, 834FD4F527AFA2150063BC83 /* Downmix.m in Sources */, diff --git a/Audio/PluginController.m b/Audio/PluginController.m index 175e4ad0d..c43fde8c9 100644 --- a/Audio/PluginController.m +++ b/Audio/PluginController.m @@ -6,6 +6,8 @@ #import "NSFileHandle+CreateFile.h" +#import "NSDictionary+Merge.h" + @implementation PluginController @synthesize sources; @@ -535,10 +537,11 @@ static PluginController *sharedPluginController = nil; } NSDictionary *properties = [decoder properties]; + NSDictionary *metadata = [decoder metadata]; [decoder close]; - return properties; + return [NSDictionary dictionaryByMerging:properties with:metadata]; } } diff --git a/Playlist/PlaylistEntry.m b/Playlist/PlaylistEntry.m index 317da72bb..68ebc33fe 100644 --- a/Playlist/PlaylistEntry.m +++ b/Playlist/PlaylistEntry.m @@ -371,7 +371,7 @@ @dynamic albumArt; - (NSImage *)albumArt { - if(!albumArtInternal) return nil; + if(!albumArtInternal || ![albumArtInternal length]) return nil; NSString *imageCacheTag = [NSString stringWithFormat:@"%@-%@-%@-%@", album, artist, genre, year]; NSImage *image = [NSImage imageNamed:imageCacheTag]; diff --git a/Plugins/FFMPEG/FFMPEGDecoder.h b/Plugins/FFMPEG/FFMPEGDecoder.h index ec9210381..a817c4f61 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.h +++ b/Plugins/FFMPEG/FFMPEGDecoder.h @@ -47,10 +47,22 @@ BOOL endOfAudio; int metadataIndex; - NSString *genre; NSString *artist; - NSString *title; + NSString *albumartist; NSString *album; + NSString *title; + NSString *genre; + NSNumber *year; + NSNumber *track; + NSNumber *disc; + float replayGainAlbumGain; + float replayGainAlbumPeak; + float replayGainTrackGain; + float replayGainTrackPeak; + + int attachedPicIndex; + NSData *albumArt; + NSDictionary *id3Metadata; } diff --git a/Plugins/FFMPEG/FFMPEGDecoder.m b/Plugins/FFMPEG/FFMPEGDecoder.m index 8f9a5bf21..072bf34b9 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.m +++ b/Plugins/FFMPEG/FFMPEGDecoder.m @@ -157,6 +157,7 @@ static uint8_t reverse_bits[0x100]; streamIndex = -1; metadataIndex = -1; + attachedPicIndex = -1; AVCodecParameters *codecPar; for(i = 0; i < formatCtx->nb_streams; i++) { @@ -167,6 +168,8 @@ static uint8_t reverse_bits[0x100]; streamIndex = i; } else if(codecPar->codec_id == AV_CODEC_ID_TIMED_ID3) { metadataIndex = i; + } else if(stream->disposition & AV_DISPOSITION_ATTACHED_PIC) { + attachedPicIndex = i; } else { stream->discard = AVDISCARD_ALL; } @@ -445,10 +448,19 @@ static uint8_t reverse_bits[0x100]; seekable = [s seekable]; - genre = @""; - album = @""; artist = @""; + albumartist = @""; + album = @""; title = @""; + genre = @""; + year = @(0); + track = @(0); + disc = @(0); + replayGainAlbumGain = 0.0; + replayGainAlbumPeak = 0.0; + replayGainTrackGain = 0.0; + replayGainTrackPeak = 0.0; + albumArt = [NSData data]; id3Metadata = @{}; [self updateMetadata]; @@ -495,13 +507,19 @@ static uint8_t reverse_bits[0x100]; } - (void)updateMetadata { - if([source seekable]) return; - const AVDictionaryEntry *tag = NULL; - NSString *_genre = genre; - NSString *_album = album; NSString *_artist = artist; + NSString *_albumartist = albumartist; + NSString *_album = album; NSString *_title = title; + NSString *_genre = genre; + NSNumber *_year = year; + NSNumber *_track = track; + NSNumber *_disc = disc; + float _replayGainAlbumGain = replayGainAlbumGain; + float _replayGainAlbumPeak = replayGainAlbumPeak; + float _replayGainTrackGain = replayGainTrackGain; + float _replayGainTrackPeak = replayGainTrackPeak; if(formatCtx->metadata) { while((tag = av_dict_get(formatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { if(!strcasecmp(tag->key, "streamtitle")) { @@ -517,10 +535,35 @@ static uint8_t reverse_bits[0x100]; _album = [NSString stringWithUTF8String:tag->value]; } else if(!strcasecmp(tag->key, "icy-genre")) { _genre = [NSString stringWithUTF8String:tag->value]; + } else if(!strcasecmp(tag->key, "album")) { + _album = [NSString stringWithUTF8String:tag->value]; + } else if(!strcasecmp(tag->key, "album_artist")) { + _albumartist = [NSString stringWithUTF8String:tag->value]; } else if(!strcasecmp(tag->key, "artist")) { _artist = [NSString stringWithUTF8String:tag->value]; } else if(!strcasecmp(tag->key, "title")) { _title = [NSString stringWithUTF8String:tag->value]; + } else if(!strcasecmp(tag->key, "date")) { + NSString *dateString = [NSString stringWithUTF8String:tag->value]; + _year = @([dateString intValue]); + } else if(!strcasecmp(tag->key, "track")) { + NSString *trackString = [NSString stringWithUTF8String:tag->value]; + _track = @([trackString intValue]); + } else if(!strcasecmp(tag->key, "disc")) { + NSString *discString = [NSString stringWithUTF8String:tag->value]; + _disc = @([discString intValue]); + } else if(!strcasecmp(tag->key, "replaygain_album_gain")) { + NSString *rgValue = [NSString stringWithUTF8String:tag->value]; + _replayGainAlbumGain = [rgValue floatValue]; + } else if(!strcasecmp(tag->key, "replaygain_album_peak")) { + NSString *rgValue = [NSString stringWithUTF8String:tag->value]; + _replayGainAlbumPeak = [rgValue floatValue]; + } else if(!strcasecmp(tag->key, "replaygain_track_gain")) { + NSString *rgValue = [NSString stringWithUTF8String:tag->value]; + _replayGainTrackGain = [rgValue floatValue]; + } else if(!strcasecmp(tag->key, "replaygain_track_peak")) { + NSString *rgValue = [NSString stringWithUTF8String:tag->value]; + _replayGainTrackPeak = [rgValue floatValue]; } } } @@ -537,16 +580,34 @@ static uint8_t reverse_bits[0x100]; } } - if(![_genre isEqual:genre] || + if(![_artist isEqual:artist] || + ![_albumartist isEqual:albumartist] || ![_album isEqual:album] || - ![_artist isEqual:artist] || - ![_title isEqual:title]) { - genre = _genre; - album = _album; + ![_title isEqual:title] || + ![_genre isEqual:genre] || + ![_year isEqual:year] || + ![_track isEqual:track] || + ![_disc isEqual:disc] || + _replayGainAlbumGain != replayGainAlbumGain || + _replayGainAlbumPeak != replayGainAlbumPeak || + _replayGainTrackGain != replayGainTrackGain || + _replayGainTrackPeak != replayGainTrackPeak) { artist = _artist; + albumartist = _albumartist; + album = _album; title = _title; - [self willChangeValueForKey:@"metadata"]; - [self didChangeValueForKey:@"metadata"]; + genre = _genre; + year = _year; + track = _track; + disc = _disc; + replayGainAlbumGain = _replayGainAlbumGain; + replayGainAlbumPeak = _replayGainAlbumPeak; + replayGainTrackGain = _replayGainTrackGain; + replayGainTrackPeak = _replayGainTrackPeak; + if(![source seekable]) { + [self willChangeValueForKey:@"metadata"]; + [self didChangeValueForKey:@"metadata"]; + } } } @@ -563,6 +624,17 @@ static uint8_t reverse_bits[0x100]; } } +- (void)updateArtwork { + NSData *_albumArt = [NSData dataWithBytes:lastReadPacket->data length:lastReadPacket->size]; + if(![_albumArt isEqual:albumArt]) { + albumArt = _albumArt; + if(![source seekable]) { + [self willChangeValueForKey:@"metadata"]; + [self didChangeValueForKey:@"metadata"]; + } + } +} + - (int)readAudio:(void *)buf frames:(UInt32)frames { if(totalFrames && framesRead >= totalFrames) return 0; @@ -611,6 +683,9 @@ static uint8_t reverse_bits[0x100]; if(lastReadPacket->stream_index == metadataIndex) { [self updateID3Metadata]; continue; + } else if(lastReadPacket->stream_index == attachedPicIndex) { + [self updateArtwork]; + continue; } if(lastReadPacket->stream_index != streamIndex) @@ -832,7 +907,7 @@ static uint8_t reverse_bits[0x100]; } - (NSDictionary *)metadata { - return [NSDictionary dictionaryByMerging:@{ @"genre": genre, @"album": album, @"artist": artist, @"title": title } with:id3Metadata]; + return [NSDictionary dictionaryByMerging:@{ @"artist": artist, @"albumartist": albumartist, @"album": album, @"title": title, @"genre": genre, @"year": year, @"track": track, @"disc": disc, @"replayGainAlbumGain": @(replayGainAlbumGain), @"replayGainAlbumPeak": @(replayGainAlbumPeak), @"replayGainTrackGain": @(replayGainTrackGain), @"replayGainTrackPeak": @(replayGainTrackPeak), @"albumArt": albumArt } with:id3Metadata]; } + (NSArray *)fileTypes {