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 <kode54@gmail.com>
CQTexperiment
Christopher Snowhill 2022-02-10 15:29:13 -08:00
parent 5ff1f95481
commit 39dcb88728
5 changed files with 116 additions and 18 deletions

View File

@ -66,6 +66,8 @@
8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; }; 8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; };
839366671815923C006DD712 /* CogPluginMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogPluginMulti.h */; }; 839366671815923C006DD712 /* CogPluginMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogPluginMulti.h */; };
839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; }; 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 */; }; 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, ); }; }; 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 */; }; 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 = "<group>"; }; 8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = "<group>"; }; 839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = "<group>"; };
839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; }; 839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; };
8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = "<group>"; };
8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = "<group>"; };
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; }; 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8DC2EF5B0486A6940098B216 /* CogAudio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CogAudio.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioContainer.h; sourceTree = "<group>"; };
@ -333,6 +337,8 @@
17D21CDC0B8BE5B400D1EBDE /* Utils */ = { 17D21CDC0B8BE5B400D1EBDE /* Utils */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8399CF2A27B5D1D4008751F1 /* NSDictionary+Merge.h */,
8399CF2B27B5D1D4008751F1 /* NSDictionary+Merge.m */,
8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */, 8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */,
8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */, 8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */,
8384912618080FF100E7332D /* Logging.h */, 8384912618080FF100E7332D /* Logging.h */,
@ -430,6 +436,7 @@
17D21CA10B8BE4BA00D1EBDE /* BufferChain.h in Headers */, 17D21CA10B8BE4BA00D1EBDE /* BufferChain.h in Headers */,
17D21CA50B8BE4BA00D1EBDE /* InputNode.h in Headers */, 17D21CA50B8BE4BA00D1EBDE /* InputNode.h in Headers */,
17D21CA70B8BE4BA00D1EBDE /* Node.h in Headers */, 17D21CA70B8BE4BA00D1EBDE /* Node.h in Headers */,
8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */,
17D21CA90B8BE4BA00D1EBDE /* OutputNode.h in Headers */, 17D21CA90B8BE4BA00D1EBDE /* OutputNode.h in Headers */,
17D21CC50B8BE4BA00D1EBDE /* OutputCoreAudio.h in Headers */, 17D21CC50B8BE4BA00D1EBDE /* OutputCoreAudio.h in Headers */,
834FD4F427AFA2150063BC83 /* Downmix.h in Headers */, 834FD4F427AFA2150063BC83 /* Downmix.h in Headers */,
@ -536,6 +543,7 @@
835EDD7B279FE23A001EDCCE /* HeadphoneFilter.m in Sources */, 835EDD7B279FE23A001EDCCE /* HeadphoneFilter.m in Sources */,
17D21CA20B8BE4BA00D1EBDE /* BufferChain.m in Sources */, 17D21CA20B8BE4BA00D1EBDE /* BufferChain.m in Sources */,
17D21CA60B8BE4BA00D1EBDE /* InputNode.m in Sources */, 17D21CA60B8BE4BA00D1EBDE /* InputNode.m in Sources */,
8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */,
17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */, 17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */,
17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */, 17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */,
834FD4F527AFA2150063BC83 /* Downmix.m in Sources */, 834FD4F527AFA2150063BC83 /* Downmix.m in Sources */,

View File

@ -6,6 +6,8 @@
#import "NSFileHandle+CreateFile.h" #import "NSFileHandle+CreateFile.h"
#import "NSDictionary+Merge.h"
@implementation PluginController @implementation PluginController
@synthesize sources; @synthesize sources;
@ -535,10 +537,11 @@ static PluginController *sharedPluginController = nil;
} }
NSDictionary *properties = [decoder properties]; NSDictionary *properties = [decoder properties];
NSDictionary *metadata = [decoder metadata];
[decoder close]; [decoder close];
return properties; return [NSDictionary dictionaryByMerging:properties with:metadata];
} }
} }

View File

@ -371,7 +371,7 @@
@dynamic albumArt; @dynamic albumArt;
- (NSImage *)albumArt { - (NSImage *)albumArt {
if(!albumArtInternal) return nil; if(!albumArtInternal || ![albumArtInternal length]) return nil;
NSString *imageCacheTag = [NSString stringWithFormat:@"%@-%@-%@-%@", album, artist, genre, year]; NSString *imageCacheTag = [NSString stringWithFormat:@"%@-%@-%@-%@", album, artist, genre, year];
NSImage *image = [NSImage imageNamed:imageCacheTag]; NSImage *image = [NSImage imageNamed:imageCacheTag];

View File

@ -47,10 +47,22 @@
BOOL endOfAudio; BOOL endOfAudio;
int metadataIndex; int metadataIndex;
NSString *genre;
NSString *artist; NSString *artist;
NSString *title; NSString *albumartist;
NSString *album; 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; NSDictionary *id3Metadata;
} }

View File

@ -157,6 +157,7 @@ static uint8_t reverse_bits[0x100];
streamIndex = -1; streamIndex = -1;
metadataIndex = -1; metadataIndex = -1;
attachedPicIndex = -1;
AVCodecParameters *codecPar; AVCodecParameters *codecPar;
for(i = 0; i < formatCtx->nb_streams; i++) { for(i = 0; i < formatCtx->nb_streams; i++) {
@ -167,6 +168,8 @@ static uint8_t reverse_bits[0x100];
streamIndex = i; streamIndex = i;
} else if(codecPar->codec_id == AV_CODEC_ID_TIMED_ID3) { } else if(codecPar->codec_id == AV_CODEC_ID_TIMED_ID3) {
metadataIndex = i; metadataIndex = i;
} else if(stream->disposition & AV_DISPOSITION_ATTACHED_PIC) {
attachedPicIndex = i;
} else { } else {
stream->discard = AVDISCARD_ALL; stream->discard = AVDISCARD_ALL;
} }
@ -445,10 +448,19 @@ static uint8_t reverse_bits[0x100];
seekable = [s seekable]; seekable = [s seekable];
genre = @"";
album = @"";
artist = @""; artist = @"";
albumartist = @"";
album = @"";
title = @""; title = @"";
genre = @"";
year = @(0);
track = @(0);
disc = @(0);
replayGainAlbumGain = 0.0;
replayGainAlbumPeak = 0.0;
replayGainTrackGain = 0.0;
replayGainTrackPeak = 0.0;
albumArt = [NSData data];
id3Metadata = @{}; id3Metadata = @{};
[self updateMetadata]; [self updateMetadata];
@ -495,13 +507,19 @@ static uint8_t reverse_bits[0x100];
} }
- (void)updateMetadata { - (void)updateMetadata {
if([source seekable]) return;
const AVDictionaryEntry *tag = NULL; const AVDictionaryEntry *tag = NULL;
NSString *_genre = genre;
NSString *_album = album;
NSString *_artist = artist; NSString *_artist = artist;
NSString *_albumartist = albumartist;
NSString *_album = album;
NSString *_title = title; 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) { if(formatCtx->metadata) {
while((tag = av_dict_get(formatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { while((tag = av_dict_get(formatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
if(!strcasecmp(tag->key, "streamtitle")) { if(!strcasecmp(tag->key, "streamtitle")) {
@ -517,10 +535,35 @@ static uint8_t reverse_bits[0x100];
_album = [NSString stringWithUTF8String:tag->value]; _album = [NSString stringWithUTF8String:tag->value];
} else if(!strcasecmp(tag->key, "icy-genre")) { } else if(!strcasecmp(tag->key, "icy-genre")) {
_genre = [NSString stringWithUTF8String:tag->value]; _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")) { } else if(!strcasecmp(tag->key, "artist")) {
_artist = [NSString stringWithUTF8String:tag->value]; _artist = [NSString stringWithUTF8String:tag->value];
} else if(!strcasecmp(tag->key, "title")) { } else if(!strcasecmp(tag->key, "title")) {
_title = [NSString stringWithUTF8String:tag->value]; _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,17 +580,35 @@ static uint8_t reverse_bits[0x100];
} }
} }
if(![_genre isEqual:genre] || if(![_artist isEqual:artist] ||
![_albumartist isEqual:albumartist] ||
![_album isEqual:album] || ![_album isEqual:album] ||
![_artist isEqual:artist] || ![_title isEqual:title] ||
![_title isEqual:title]) { ![_genre isEqual:genre] ||
genre = _genre; ![_year isEqual:year] ||
album = _album; ![_track isEqual:track] ||
![_disc isEqual:disc] ||
_replayGainAlbumGain != replayGainAlbumGain ||
_replayGainAlbumPeak != replayGainAlbumPeak ||
_replayGainTrackGain != replayGainTrackGain ||
_replayGainTrackPeak != replayGainTrackPeak) {
artist = _artist; artist = _artist;
albumartist = _albumartist;
album = _album;
title = _title; title = _title;
genre = _genre;
year = _year;
track = _track;
disc = _disc;
replayGainAlbumGain = _replayGainAlbumGain;
replayGainAlbumPeak = _replayGainAlbumPeak;
replayGainTrackGain = _replayGainTrackGain;
replayGainTrackPeak = _replayGainTrackPeak;
if(![source seekable]) {
[self willChangeValueForKey:@"metadata"]; [self willChangeValueForKey:@"metadata"];
[self didChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"];
} }
}
} }
- (void)updateID3Metadata { - (void)updateID3Metadata {
@ -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 { - (int)readAudio:(void *)buf frames:(UInt32)frames {
if(totalFrames && framesRead >= totalFrames) if(totalFrames && framesRead >= totalFrames)
return 0; return 0;
@ -611,6 +683,9 @@ static uint8_t reverse_bits[0x100];
if(lastReadPacket->stream_index == metadataIndex) { if(lastReadPacket->stream_index == metadataIndex) {
[self updateID3Metadata]; [self updateID3Metadata];
continue; continue;
} else if(lastReadPacket->stream_index == attachedPicIndex) {
[self updateArtwork];
continue;
} }
if(lastReadPacket->stream_index != streamIndex) if(lastReadPacket->stream_index != streamIndex)
@ -832,7 +907,7 @@ static uint8_t reverse_bits[0x100];
} }
- (NSDictionary *)metadata { - (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 { + (NSArray *)fileTypes {