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 {