diff --git a/Base.lproj/InfoInspector.xib b/Base.lproj/InfoInspector.xib index a1527beae..eeeb2727c 100644 --- a/Base.lproj/InfoInspector.xib +++ b/Base.lproj/InfoInspector.xib @@ -1,8 +1,8 @@ - + - + @@ -15,16 +15,16 @@ - + - + - + - + @@ -33,7 +33,7 @@ - + @@ -42,7 +42,7 @@ - + @@ -51,7 +51,7 @@ - + @@ -60,16 +60,16 @@ - + - + - + @@ -78,7 +78,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -96,7 +96,7 @@ - + @@ -105,7 +105,7 @@ - + @@ -114,7 +114,7 @@ - + @@ -123,7 +123,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -147,7 +147,7 @@ - + @@ -159,7 +159,7 @@ - + @@ -171,7 +171,7 @@ - + @@ -183,7 +183,7 @@ - + @@ -191,11 +191,11 @@ - + - + @@ -207,7 +207,7 @@ - + @@ -223,7 +223,7 @@ - + @@ -235,7 +235,7 @@ - + @@ -247,7 +247,7 @@ - + @@ -259,7 +259,7 @@ - + @@ -268,7 +268,7 @@ - + @@ -280,7 +280,7 @@ - + @@ -289,7 +289,7 @@ - + @@ -301,7 +301,7 @@ - + @@ -310,7 +310,7 @@ - + @@ -322,7 +322,7 @@ - + @@ -331,7 +331,7 @@ - + @@ -344,7 +344,7 @@ - + @@ -353,7 +353,7 @@ - + @@ -365,7 +365,7 @@ - + @@ -374,7 +374,7 @@ - + @@ -386,7 +386,7 @@ - + @@ -394,8 +394,17 @@ + + + + + + + + + - + @@ -407,6 +416,19 @@ + + + + + + + + + + + + + diff --git a/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents b/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents index 96a65fb5c..0664176a8 100644 --- a/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents +++ b/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -15,10 +15,7 @@ - - - @@ -30,19 +27,18 @@ - - + + - @@ -55,12 +51,10 @@ - - @@ -70,7 +64,7 @@ - + \ No newline at end of file diff --git a/Playlist/PlaylistEntry.h b/Playlist/PlaylistEntry.h index 02e210fc7..110370abc 100644 --- a/Playlist/PlaylistEntry.h +++ b/Playlist/PlaylistEntry.h @@ -74,6 +74,25 @@ @property(nonatomic, readonly) float rating; +@property(nonatomic) NSString *_Nullable album; +@property(nonatomic) NSString *_Nullable albumartist; +@property(nonatomic) NSString *_Nullable artist; +@property(nonatomic) NSString *_Nullable rawTitle; +@property(nonatomic) NSString *_Nullable genre; +@property(nonatomic) int32_t disc; +@property(nonatomic) int32_t track; +@property(nonatomic) int32_t year; + +@property(nonatomic) NSString *_Nullable date; + +@property(nonatomic) NSString *_Nullable comment; + +@property(nonatomic) NSDictionary *_Nullable metadataBlob; + +- (NSString *_Nullable)readAllValuesAsString:(NSString *_Nonnull)tagName; +- (void)setValue:(NSString *_Nonnull)tagName fromString:(NSString *_Nullable)value; +- (void)addValue:(NSString *_Nonnull)tagName fromString:(NSString *_Nonnull)value; + - (void)setMetadata:(NSDictionary *_Nonnull)metadata; @end diff --git a/Playlist/PlaylistEntry.m b/Playlist/PlaylistEntry.m index fa0ad0b31..ae8f38b65 100644 --- a/Playlist/PlaylistEntry.m +++ b/Playlist/PlaylistEntry.m @@ -16,11 +16,28 @@ #import "SHA256Digest.h" #import "SecondsFormatter.h" +#import + extern NSPersistentContainer *kPersistentContainer; extern NSMutableDictionary *kArtworkDictionary; +static NSMutableDictionary *kMetadataBlobCache = nil; +static NSMutableDictionary *kMetadataCache = nil; + +static void *kCompressionScratchBuffer = NULL; +static void *kDecompressionScratchBuffer = NULL; + @implementation PlaylistEntry (Extension) ++ (void)initialize { + if(!kMetadataBlobCache) { + kMetadataBlobCache = [[NSMutableDictionary alloc] init]; + } + if(!kMetadataCache) { + kMetadataCache = [[NSMutableDictionary alloc] init]; + } +} + // The following read-only keys depend on the values of other properties + (NSSet *)keyPathsForValuesAffectingUrl { @@ -503,8 +520,72 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path) { self.error = YES; self.errorMessage = NSLocalizedStringFromTableInBundle(@"ErrorMetadata", nil, [NSBundle bundleForClass:[self class]], @""); } else { + NSMutableDictionary *metaDict = [[NSMutableDictionary alloc] init]; self.volume = 1; - [self setValuesForKeysWithDictionary:metadata]; + for(NSString *key in metadata) { + NSString *lowerKey = [key lowercaseString]; + id valueObj = [metadata objectForKey:key]; + NSArray *values = nil; + NSString *firstValue = nil; + NSData *dataValue = nil; + if([valueObj isKindOfClass:[NSArray class]]) { + values = (NSArray *)valueObj; + if([values count]) { + firstValue = values[0]; + } + } else if([valueObj isKindOfClass:[NSString class]]) { + firstValue = (NSString *)valueObj; + values = @[firstValue]; + } else if([valueObj isKindOfClass:[NSNumber class]]) { + NSNumber *numberValue = (NSNumber *)valueObj; + firstValue = [numberValue stringValue]; + values = @[firstValue]; + } else if([valueObj isKindOfClass:[NSData class]]) { + dataValue = (NSData *)valueObj; + } + if([lowerKey isEqualToString:@"bitrate"]) { + self.bitrate = [firstValue intValue]; + } else if([lowerKey isEqualToString:@"bitspersample"]) { + self.bitsPerSample = [firstValue intValue]; + } else if([lowerKey isEqualToString:@"channelconfig"]) { + self.channelConfig = [firstValue intValue]; + } else if([lowerKey isEqualToString:@"channels"]) { + self.channels = [firstValue intValue]; + } else if([lowerKey isEqualToString:@"codec"]) { + self.codec = firstValue; + } else if([lowerKey isEqualToString:@"cuesheet"]) { + self.cuesheet = firstValue; + } else if([lowerKey isEqualToString:@"encoding"]) { + self.encoding = firstValue; + } else if([lowerKey isEqualToString:@"endian"]) { + self.endian = firstValue; + } else if([lowerKey isEqualToString:@"floatingpoint"]) { + self.floatingPoint = [firstValue boolValue]; + } else if([lowerKey isEqualToString:@"samplerate"]) { + self.sampleRate = [firstValue floatValue]; + } else if([lowerKey isEqualToString:@"seekable"]) { + self.seekable = [firstValue boolValue]; + } else if([lowerKey isEqualToString:@"totalframes"]) { + self.totalFrames = [firstValue integerValue]; + } else if([lowerKey isEqualToString:@"unsigned"]) { + self.unSigned = [firstValue boolValue]; + } else if([lowerKey isEqualToString:@"replaygain_album_gain"]) { + self.replayGainAlbumGain = [firstValue floatValue]; + } else if([lowerKey isEqualToString:@"replaygain_album_peak"]) { + self.replayGainAlbumPeak = [firstValue floatValue]; + } else if([lowerKey isEqualToString:@"replaygain_track_gain"]) { + self.replayGainTrackGain = [firstValue floatValue]; + } else if([lowerKey isEqualToString:@"replaygain_track_peak"]) { + self.replayGainTrackPeak = [firstValue floatValue]; + } else if([lowerKey isEqualToString:@"volume"]) { + self.volume = [firstValue floatValue]; + } else if([lowerKey isEqualToString:@"albumart"]) { + self.albumArt = dataValue; + } else { + [metaDict setObject:values forKey:lowerKey]; + } + } + self.metadataBlob = metaDict; } [self setMetadataLoaded:YES]; @@ -574,4 +655,318 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path) { } } +@dynamic album; +- (NSString *)album { + return [self readAllValuesAsString:@"album"]; +} + +- (void)setAlbum:(NSString *)album { + [self setValue:@"album" fromString:album]; +} + +@dynamic albumartist; +- (NSString *)albumartist { + NSString *value = [self readAllValuesAsString:@"albumartist"]; + if(!value) { + value = [self readAllValuesAsString:@"album artist"]; + } + if(!value) { + value = [self readAllValuesAsString:@"album_artist"]; + } + return value; +} + +- (void)setAlbumartist:(NSString *)albumartist { + [self setValue:@"albumartist" fromString:albumartist]; + [self setValue:@"album artist" fromString:nil]; + [self setValue:@"album_artist" fromString:nil]; +} + +@dynamic artist; +- (NSString *)artist { + return [self readAllValuesAsString:@"artist"]; +} + +- (void)setArtist:(NSString *)artist { + [self setValue:@"artist" fromString:artist]; +} + +@dynamic rawTitle; +- (NSString *)rawTitle { + return [self readAllValuesAsString:@"title"]; +} + +- (void)setRawTitle:(NSString *)rawTitle { + [self setValue:@"title" fromString:rawTitle]; +} + +@dynamic genre; +- (NSString *)genre { + return [self readAllValuesAsString:@"genre"]; +} + +- (void)setGenre:(NSString *)genre { + [self setValue:@"genre" fromString:genre]; +} + +@dynamic disc; +- (int32_t)disc { + NSString *value = [self readAllValuesAsString:@"discnumber"]; + if(!value) { + value = [self readAllValuesAsString:@"discnum"]; + } + if(!value) { + value = [self readAllValuesAsString:@"disc"]; + } + if(value) { + return [value intValue]; + } else { + return 0; + } +} + +- (void)setDisc:(int32_t)disc { + [self setValue:@"discnumber" fromString:[NSString stringWithFormat:@"%u", disc]]; + [self setValue:@"discnum" fromString:nil]; + [self setValue:@"disc" fromString:nil]; +} + +@dynamic track; +- (int32_t)track { + NSString *value = [self readAllValuesAsString:@"tracknumber"]; + if(!value) { + value = [self readAllValuesAsString:@"tracknum"]; + } + if(!value) { + value = [self readAllValuesAsString:@"track"]; + } + if(value) { + return [value intValue]; + } else { + return 0; + } +} + +@dynamic year; +- (int32_t)year { + NSString *value = [self readAllValuesAsString:@"date"]; + if(!value) { + value = [self readAllValuesAsString:@"recording_date"]; + } + if(!value) { + value = [self readAllValuesAsString:@"year"]; + } + if(value) { + return [value intValue]; + } else { + return 0; + } +} + +- (void)setYear:(int32_t)year { + NSString *svalue = [NSString stringWithFormat:@"%u", year]; + [self setValue:@"year" fromString:svalue]; + [self setValue:@"date" fromString:nil]; + [self setValue:@"recording_date" fromString:nil]; +} + +@dynamic date; +- (NSString *)date { + NSString *value = [self readAllValuesAsString:@"date"]; + if(!value) { + value = [self readAllValuesAsString:@"recording_date"]; + } + if(!value) { + value = [self readAllValuesAsString:@"year"]; + } + return value; +} + +- (void)setDate:(NSString *)date { + [self setValue:@"date" fromString:date]; + [self setValue:@"recording_date" fromString:nil]; + [self setValue:@"year" fromString:nil]; +} + +@dynamic comment; +- (NSString *)comment { + return [self readAllValuesAsString:@"comment"]; +} + +- (void)setComment:(NSString *)comment { + [self setValue:@"comment" fromString:comment]; +} + +- (NSString *_Nullable)readAllValuesAsString:(NSString *_Nonnull)tagName { + NSMutableDictionary *dict = [kMetadataCache objectForKey:self.urlString]; + if(dict) { + NSString *value = [dict objectForKey:tagName]; + if(value) { + return value; + } + } + + id metaObj = self.metadataBlob; + + if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) { + NSDictionary *metaDict = (NSDictionary *)metaObj; + + NSArray *values = [metaDict objectForKey:tagName]; + + if(values) { + NSString *value = [values componentsJoinedByString:@", "]; + if(!dict) { + dict = [[NSMutableDictionary alloc] init]; + [kMetadataCache setObject:dict forKey:self.urlString]; + } + [dict setObject:value forKey:tagName]; + return value; + } + } + + return nil; +} + +- (void)deleteAllValues { + self.metadataBlob = nil; + [kMetadataCache removeObjectForKey:self.urlString]; +} + +- (void)deleteValue:(NSString *_Nonnull)tagName { + id metaObj = self.metadataBlob; + + if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) { + NSDictionary *metaDict = (NSDictionary *)metaObj; + NSMutableDictionary *metaDictCopy = [metaDict mutableCopy]; + + [metaDictCopy removeObjectForKey:tagName]; + + self.metadataBlob = [NSDictionary dictionaryWithDictionary:metaDictCopy]; + } +} + +- (void)setValue:(NSString *_Nonnull)tagName fromString:(NSString *_Nullable)value { + if(!value) { + [self deleteValue:tagName]; + return; + } + + NSArray *values = [value componentsSeparatedByString:@", "]; + + id metaObj = self.metadataBlob; + + if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) { + NSDictionary *metaDict = (NSDictionary *)metaObj; + NSMutableDictionary *metaDictCopy = [metaDict mutableCopy]; + + [metaDictCopy setObject:values forKey:tagName]; + + self.metadataBlob = [NSDictionary dictionaryWithDictionary:metaDictCopy]; + } +} + +- (void)addValue:(NSString *_Nonnull)tagName fromString:(NSString *_Nonnull)value { + NSMutableDictionary *dict = [kMetadataCache objectForKey:self.urlString]; + if(dict) { + [dict removeObjectForKey:tagName]; + } + + id metaObj = self.metadataBlob; + + if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) { + NSDictionary *metaDict = (NSDictionary *)metaObj; + NSMutableDictionary *metaDictCopy = [metaDict mutableCopy]; + + NSArray *values = [metaDictCopy objectForKey:tagName]; + NSMutableArray *valuesCopy; + if(values) { + valuesCopy = [values mutableCopy]; + } else { + valuesCopy = [[NSMutableArray alloc] init]; + } + [valuesCopy addObject:value]; + values = [NSArray arrayWithArray:valuesCopy]; + [metaDictCopy setObject:values forKey:tagName]; + + self.metadataBlob = [NSDictionary dictionaryWithDictionary:metaDictCopy]; + } +} + +- (NSDictionary *)metadataBlob { + { + NSDictionary *blob = [kMetadataBlobCache objectForKey:self.urlString]; + if(blob) { + return blob; + } + } + + if(self.metadataCompressed == nil || self.metadataDecompressedSize == 0) { + return @{}; + } + + if(!kDecompressionScratchBuffer) { + size_t scratchSize = compression_decode_scratch_buffer_size(COMPRESSION_ZLIB); + kDecompressionScratchBuffer = malloc(scratchSize); + } + + void *decompressionBuffer = malloc(self.metadataDecompressedSize); + + size_t decodedBytes = compression_decode_buffer(decompressionBuffer, self.metadataDecompressedSize, [self.metadataCompressed bytes], [self.metadataCompressed length], kDecompressionScratchBuffer, COMPRESSION_ZLIB); + + NSData *decodedData = [NSData dataWithBytes:decompressionBuffer length:decodedBytes]; + + free(decompressionBuffer); + + NSSet *allowed = [NSSet setWithArray:@[[NSDictionary class], [NSMutableDictionary class], [NSArray class], [NSMutableArray class], [NSString class]]]; + NSError *error = nil; + NSDictionary *dict; + dict = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowed + fromData:decodedData + error:&error]; + + if(!dict) { + dict = @{}; + } + + [kMetadataBlobCache setObject:dict forKey:self.urlString]; + + return dict; +} + +- (void)setMetadataBlob:(NSMutableDictionary *)metadataBlob { + [kMetadataCache removeObjectForKey:self.urlString]; + + if(metadataBlob == nil) { + self.metadataCompressed = nil; + self.metadataDecompressedSize = 0; + [kMetadataBlobCache removeObjectForKey:self.urlString]; + return; + } + + [kMetadataBlobCache setObject:metadataBlob forKey:self.urlString]; + + NSError *error = nil; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:metadataBlob + requiringSecureCoding:YES + error:&error]; + + if(!kCompressionScratchBuffer) { + size_t scratchBufferSize = compression_encode_scratch_buffer_size(COMPRESSION_ZLIB); + kCompressionScratchBuffer = malloc(scratchBufferSize); + } + + size_t fullSize = [data length]; + size_t compressedSize = fullSize * 3 / 2 + 256; + void *compressedBuffer = malloc(compressedSize); + + size_t compressedOutSize = compression_encode_buffer(compressedBuffer, compressedSize, [data bytes], fullSize, kCompressionScratchBuffer, COMPRESSION_ZLIB); + + NSData *compressData = [NSData dataWithBytes:compressedBuffer length:compressedOutSize]; + + free(compressedBuffer); + + self.metadataCompressed = compressData; + self.metadataDecompressedSize = fullSize; +} + @end diff --git a/Playlist/PlaylistLoader.m b/Playlist/PlaylistLoader.m index 677fe35ce..48374f7d6 100644 --- a/Playlist/PlaylistLoader.m +++ b/Playlist/PlaylistLoader.m @@ -861,6 +861,8 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path); } - (BOOL)addDataStore { + BOOL dataMigrated = [[NSUserDefaults standardUserDefaults] boolForKey:@"metadataMigrated"]; + NSPersistentContainer *pc = playlistController.persistentContainer; if(pc) { NSManagedObjectContext *moc = pc.viewContext; @@ -902,10 +904,17 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path); ++index; } [resultsCopy removeObjectsAtIndexes:pruneSet]; - if([pruneSet count]) { + + if(!dataMigrated) { + for(PlaylistEntry *pe in resultsCopy) { + pe.metadataLoaded = NO; + } + } + + if([pruneSet count] || !dataMigrated) { [playlistController commitPersistentStore]; } - + results = [NSArray arrayWithArray:resultsCopy]; { @@ -917,6 +926,8 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path); [playlistController readQueueFromDataStore]; [playlistController readShuffleListFromDataStore]; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"metadataMigrated"]; + return YES; } return NO; diff --git a/Plugins/CueSheet/CueSheetMetadataReader.m b/Plugins/CueSheet/CueSheetMetadataReader.m index e38074f73..3f7f0bde6 100644 --- a/Plugins/CueSheet/CueSheetMetadataReader.m +++ b/Plugins/CueSheet/CueSheetMetadataReader.m @@ -85,10 +85,10 @@ if([[track track] intValue]) [cuesheetMetadata setValue:@([[track track] intValue]) forKey:@"track"]; if([track genre]) [cuesheetMetadata setValue:[track genre] forKey:@"genre"]; if([[track year] intValue]) [cuesheetMetadata setValue:@([[track year] intValue]) forKey:@"year"]; - if([track albumGain]) [cuesheetMetadata setValue:@([track albumGain]) forKey:@"replayGainAlbumGain"]; - if([track albumPeak]) [cuesheetMetadata setValue:@([track albumPeak]) forKey:@"replayGainAlbumPeak"]; - if([track trackGain]) [cuesheetMetadata setValue:@([track trackGain]) forKey:@"replayGainTrackGain"]; - if([track trackPeak]) [cuesheetMetadata setValue:@([track trackPeak]) forKey:@"replayGainTrackPeak"]; + if([track albumGain]) [cuesheetMetadata setValue:@([track albumGain]) forKey:@"replaygain_album_gain"]; + if([track albumPeak]) [cuesheetMetadata setValue:@([track albumPeak]) forKey:@"replaygain_album_peak"]; + if([track trackGain]) [cuesheetMetadata setValue:@([track trackGain]) forKey:@"replaygain_track_gain"]; + if([track trackPeak]) [cuesheetMetadata setValue:@([track trackPeak]) forKey:@"replaygain_track_peak"]; return [NSDictionary dictionaryWithDictionary:cuesheetMetadata]; } diff --git a/Plugins/FFMPEG/FFMPEGDecoder.h b/Plugins/FFMPEG/FFMPEGDecoder.h index e999a4d10..cd546d50e 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.h +++ b/Plugins/FFMPEG/FFMPEGDecoder.h @@ -60,23 +60,13 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence); BOOL endOfAudio; int metadataIndex; - NSString *artist; - NSString *albumartist; - NSString *album; - NSString *title; - NSString *genre; - NSNumber *year; - NSNumber *track; - NSNumber *disc; - float replayGainAlbumGain; - float replayGainAlbumPeak; - float replayGainTrackGain; - float replayGainTrackPeak; - float volumeScale; int attachedPicIndex; + NSData *albumArt; + NSDictionary *metaDict; + NSDictionary *id3Metadata; } diff --git a/Plugins/FFMPEG/FFMPEGDecoder.m b/Plugins/FFMPEG/FFMPEGDecoder.m index af62db24a..463e1ba1e 100644 --- a/Plugins/FFMPEG/FFMPEGDecoder.m +++ b/Plugins/FFMPEG/FFMPEGDecoder.m @@ -465,21 +465,9 @@ static uint8_t reverse_bits[0x100]; seekedToStart = !seekable; - artist = @""; - albumartist = @""; - album = @""; - title = @""; - genre = @""; - year = @(0); - track = @(0); - disc = @(0); - replayGainAlbumGain = 0.0; - replayGainAlbumPeak = 0.0; - replayGainTrackGain = 0.0; - replayGainTrackPeak = 0.0; - volumeScale = 1.0; + id3Metadata = [[NSDictionary alloc] init]; + metaDict = [NSDictionary dictionary]; albumArt = [NSData data]; - id3Metadata = @{}; metadataUpdated = NO; [self updateMetadata]; @@ -534,21 +522,18 @@ static uint8_t reverse_bits[0x100]; [self close]; } +static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) { + NSMutableArray *array = [dict valueForKey:tag]; + if(!array) { + array = [[NSMutableArray alloc] init]; + [dict setObject:array forKey:tag]; + } + [array addObject:value]; +} + - (void)updateMetadata { + NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init]; const AVDictionaryEntry *tag = NULL; - 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; - float _volumeScale = volumeScale; for(size_t i = 0; i < 2; ++i) { AVDictionary *metadata; if(i == 0) { @@ -567,62 +552,45 @@ static uint8_t reverse_bits[0x100]; if(!strcasecmp(tag->key, "streamtitle")) { NSString *artistTitle = guess_encoding_of_string(tag->value); NSArray *splitValues = [artistTitle componentsSeparatedByString:@" - "]; - _artist = @""; - _title = [splitValues objectAtIndex:0]; + NSString *_artist = @""; + NSString *_title = [splitValues objectAtIndex:0]; if([splitValues count] > 1) { _artist = _title; _title = [splitValues objectAtIndex:1]; + setDictionary(_metaDict, @"artist", _artist); + setDictionary(_metaDict, @"title", _title); + } else { + setDictionary(_metaDict, @"title", _title); } } else if(!strcasecmp(tag->key, "icy-url")) { - _album = guess_encoding_of_string(tag->value); - } else if(!strcasecmp(tag->key, "icy-genre") || - !strcasecmp(tag->key, "genre")) { - _genre = guess_encoding_of_string(tag->value); - } else if(!strcasecmp(tag->key, "album")) { - _album = guess_encoding_of_string(tag->value); - } else if(!strcasecmp(tag->key, "album_artist")) { - _albumartist = guess_encoding_of_string(tag->value); - } else if(!strcasecmp(tag->key, "artist")) { - _artist = guess_encoding_of_string(tag->value); + setDictionary(_metaDict, @"album", guess_encoding_of_string(tag->value)); + } else if(!strcasecmp(tag->key, "icy-genre")) { + setDictionary(_metaDict, @"genre", guess_encoding_of_string(tag->value)); } else if(!strcasecmp(tag->key, "title")) { NSString *_tag = guess_encoding_of_string(tag->value); if(i == 0 && formatCtx->nb_chapters > 1) { - _album = _tag; + setDictionary(_metaDict, @"album", _tag); } else { - _title = _tag; + setDictionary(_metaDict, @"title", _tag); } - } else if(!strcasecmp(tag->key, "date") || - !strcasecmp(tag->key, "date_recorded")) { - NSString *dateString = guess_encoding_of_string(tag->value); - _year = @([dateString intValue]); - } else if(!strcasecmp(tag->key, "track")) { - NSString *trackString = guess_encoding_of_string(tag->value); - _track = @([trackString intValue]); - } else if(!strcasecmp(tag->key, "disc")) { - NSString *discString = guess_encoding_of_string(tag->value); - _disc = @([discString intValue]); - } else if(!strcasecmp(tag->key, "replaygain_album_gain")) { - NSString *rgValue = guess_encoding_of_string(tag->value); - _replayGainAlbumGain = [rgValue floatValue]; - } else if(!strcasecmp(tag->key, "replaygain_album_peak")) { - NSString *rgValue = guess_encoding_of_string(tag->value); - _replayGainAlbumPeak = [rgValue floatValue]; - } else if(!strcasecmp(tag->key, "replaygain_track_gain")) { - NSString *rgValue = guess_encoding_of_string(tag->value); - _replayGainTrackGain = [rgValue floatValue]; - } else if(!strcasecmp(tag->key, "replaygain_track_peak")) { - NSString *rgValue = guess_encoding_of_string(tag->value); - _replayGainTrackPeak = [rgValue floatValue]; + } else if(!strcasecmp(tag->key, "date_recorded")) { + setDictionary(_metaDict, @"date", guess_encoding_of_string(tag->value)); } else if(!strcasecmp(tag->key, "replaygain_gain")) { // global or chapter gain - NSString *rgValue = guess_encoding_of_string(tag->value); - if(i == 0) _replayGainAlbumGain = [rgValue floatValue]; - else _replayGainTrackGain = [rgValue floatValue]; + NSString *tagName; + if(i == 0) + tagName = @"replaygain_album_gain"; + else + tagName = @"replaygain_track_gain"; + setDictionary(_metaDict, tagName, guess_encoding_of_string(tag->value)); } else if(!strcasecmp(tag->key, "replaygain_peak")) { // global or chapter peak - NSString *rgValue = guess_encoding_of_string(tag->value); - if(i == 0) _replayGainAlbumPeak = [rgValue floatValue]; - else _replayGainTrackPeak = [rgValue floatValue]; + NSString *tagName; + if(i == 0) + tagName = @"replaygain_album_peak"; + else + tagName = @"replaygain_track_peak"; + setDictionary(_metaDict, tagName, guess_encoding_of_string(tag->value)); } else if(!strcasecmp(tag->key, "iTunNORM")) { NSString *tagString = guess_encoding_of_string(tag->value); NSArray *tag = [tagString componentsSeparatedByString:@" "]; @@ -642,8 +610,11 @@ static uint8_t reverse_bits[0x100]; float volume1 = -log10((double)(hexvalue1) / 1000) * 10; float volume2 = -log10((double)(hexvalue2) / 1000) * 10; float volumeToUse = MIN(volume1, volume2); - _volumeScale = pow(10, volumeToUse / 20); + NSNumber *_volumeScale = @(pow(10, volumeToUse / 20)); + setDictionary(_metaDict, @"volume", [_volumeScale stringValue]); } + } else { + setDictionary(_metaDict, guess_encoding_of_string(tag->key), guess_encoding_of_string(tag->value)); } } } @@ -653,39 +624,28 @@ static uint8_t reverse_bits[0x100]; HTTPSource *httpSource = (HTTPSource *)source; if([httpSource hasMetadata]) { NSDictionary *metadata = [httpSource metadata]; - _genre = [metadata valueForKey:@"genre"]; - _album = [metadata valueForKey:@"album"]; - _artist = [metadata valueForKey:@"artist"]; - _title = [metadata valueForKey:@"title"]; + NSString *_genre = [metadata valueForKey:@"genre"]; + NSString *_album = [metadata valueForKey:@"album"]; + NSString *_artist = [metadata valueForKey:@"artist"]; + NSString *_title = [metadata valueForKey:@"title"]; + + if(_genre && [_genre length]) { + [_metaDict setObject:@[_genre] forKey:@"genre"]; + } + if(_album && [_album length]) { + [_metaDict setObject:@[_album] forKey:@"album"]; + } + if(_artist && [_artist length]) { + [_metaDict setObject:@[_artist] forKey:@"artist"]; + } + if(_title && [_title length]) { + [_metaDict setObject:@[_title] forKey:@"title"]; + } } } - if(![_artist isEqual:artist] || - ![_albumartist isEqual:albumartist] || - ![_album isEqual:album] || - ![_title isEqual:title] || - ![_genre isEqual:genre] || - ![_year isEqual:year] || - ![_track isEqual:track] || - ![_disc isEqual:disc] || - _replayGainAlbumGain != replayGainAlbumGain || - _replayGainAlbumPeak != replayGainAlbumPeak || - _replayGainTrackGain != replayGainTrackGain || - _replayGainTrackPeak != replayGainTrackPeak || - _volumeScale != volumeScale) { - artist = _artist; - albumartist = _albumartist; - album = _album; - title = _title; - genre = _genre; - year = _year; - track = _track; - disc = _disc; - replayGainAlbumGain = _replayGainAlbumGain; - replayGainAlbumPeak = _replayGainAlbumPeak; - replayGainTrackGain = _replayGainTrackGain; - replayGainTrackPeak = _replayGainTrackPeak; - volumeScale = _volumeScale; + if(![_metaDict isEqualToDictionary:metaDict]) { + metaDict = _metaDict; if(![source seekable]) { [self willChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"]; @@ -700,7 +660,7 @@ static uint8_t reverse_bits[0x100]; Class tagReader = NSClassFromString(@"TagLibID3v2Reader"); if(tagReader && [tagReader respondsToSelector:@selector(metadataForTag:)]) { NSDictionary *_id3Metadata = [tagReader metadataForTag:tag]; - if(![_id3Metadata isEqualTo:id3Metadata]) { + if(![_id3Metadata isEqualToDictionary:id3Metadata]) { id3Metadata = _id3Metadata; [self willChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"]; @@ -710,7 +670,7 @@ static uint8_t reverse_bits[0x100]; - (void)updateArtwork { NSData *_albumArt = [NSData dataWithBytes:lastReadPacket->data length:lastReadPacket->size]; - if(![_albumArt isEqual:albumArt]) { + if(![_albumArt isEqualToData:albumArt]) { albumArt = _albumArt; if(![source seekable]) { [self willChangeValueForKey:@"metadata"]; @@ -1010,7 +970,10 @@ static uint8_t reverse_bits[0x100]; } - (NSDictionary *)metadata { - 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), @"volume": @(volumeScale), @"albumArt": albumArt } with:id3Metadata]; + NSDictionary *dict1 = @{ @"albumArt": albumArt }; + NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict]; + NSDictionary *dict3 = [dict2 dictionaryByMergingWith:id3Metadata]; + return dict3; } + (NSArray *)fileTypes { diff --git a/Plugins/Flac/Flac.xcodeproj/project.pbxproj b/Plugins/Flac/Flac.xcodeproj/project.pbxproj index ff5b0866f..6bf15ba6f 100644 --- a/Plugins/Flac/Flac.xcodeproj/project.pbxproj +++ b/Plugins/Flac/Flac.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 17C93F080B8FF67A008627D6 /* FlacDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C93F040B8FF67A008627D6 /* FlacDecoder.m */; }; + 8301C147287805F500651A6E /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8301C146287805F500651A6E /* NSDictionary+Merge.m */; }; 836EF0DA27BB970B00BF35B2 /* libFLAC.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0D927BB970B00BF35B2 /* libFLAC.8.dylib */; }; 83AA660B27B7DAE40098D4B8 /* cuesheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 83AA660A27B7DAE40098D4B8 /* cuesheet.m */; }; 8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */; }; @@ -33,6 +34,8 @@ 17C93F030B8FF67A008627D6 /* FlacDecoder.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FlacDecoder.h; sourceTree = ""; }; 17C93F040B8FF67A008627D6 /* FlacDecoder.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FlacDecoder.m; sourceTree = ""; }; 32DBCF630370AF2F00C91783 /* Flac_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Flac_Prefix.pch; sourceTree = ""; }; + 8301C145287805F500651A6E /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = ""; }; + 8301C146287805F500651A6E /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = ""; }; 8356BD1927B3CCBB0074E50C /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../HTTPSource/HTTPSource.h; sourceTree = ""; }; 836EF0D927BB970B00BF35B2 /* libFLAC.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libFLAC.8.dylib; path = ../../ThirdParty/flac/lib/libFLAC.8.dylib; sourceTree = ""; }; 83747C4A2862DCF40021245F /* Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; @@ -89,6 +92,8 @@ 08FB77AFFE84173DC02AAC07 /* Classes */ = { isa = PBXGroup; children = ( + 8301C145287805F500651A6E /* NSDictionary+Merge.h */, + 8301C146287805F500651A6E /* NSDictionary+Merge.m */, 83AA660A27B7DAE40098D4B8 /* cuesheet.m */, 8356BD1927B3CCBB0074E50C /* HTTPSource.h */, 8384912D180816C900E7332D /* Logging.h */, @@ -211,6 +216,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8301C147287805F500651A6E /* NSDictionary+Merge.m in Sources */, 17C93F080B8FF67A008627D6 /* FlacDecoder.m in Sources */, 83AA660B27B7DAE40098D4B8 /* cuesheet.m in Sources */, ); diff --git a/Plugins/Flac/FlacDecoder.h b/Plugins/Flac/FlacDecoder.h index 09038fa17..417ce98ca 100644 --- a/Plugins/Flac/FlacDecoder.h +++ b/Plugins/Flac/FlacDecoder.h @@ -36,18 +36,8 @@ BOOL streamOpened; BOOL abortFlag; - NSString *artist; - NSString *albumartist; - NSString *album; - NSString *title; - NSString *genre; - NSNumber *year; - NSNumber *track; - NSNumber *disc; - float replayGainAlbumGain; - float replayGainAlbumPeak; - float replayGainTrackGain; - float replayGainTrackPeak; + NSDictionary *metaDict; + NSDictionary *icyMetaDict; NSData *albumArt; diff --git a/Plugins/Flac/FlacDecoder.m b/Plugins/Flac/FlacDecoder.m index 221534823..876682d9e 100644 --- a/Plugins/Flac/FlacDecoder.m +++ b/Plugins/Flac/FlacDecoder.m @@ -12,6 +12,8 @@ #import "HTTPSource.h" +#import "NSDictionary+Merge.h" + extern void grabbag__cuesheet_emit(NSString **out, const FLAC__StreamMetadata *cuesheet, const char *file_reference); @implementation FlacDecoder @@ -178,6 +180,15 @@ FLAC__StreamDecoderWriteStatus WriteCallback(const FLAC__StreamDecoder *decoder, return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } +static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) { + NSMutableArray *array = [dict valueForKey:tag]; + if(!array) { + array = [[NSMutableArray alloc] init]; + [dict setObject:array forKey:tag]; + } + [array addObject:value]; +} + // This callback is only called for STREAMINFO blocks void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) { // Some flacs observed in the wild have multiple STREAMINFO metadata blocks, @@ -223,18 +234,7 @@ void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMeta } if(metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { - NSString *_artist = flacDecoder->artist; - NSString *_albumartist = flacDecoder->albumartist; - NSString *_album = flacDecoder->album; - NSString *_title = flacDecoder->title; - NSString *_genre = flacDecoder->genre; - NSNumber *_year = flacDecoder->year; - NSNumber *_track = flacDecoder->track; - NSNumber *_disc = flacDecoder->disc; - float _replayGainAlbumGain = flacDecoder->replayGainAlbumGain; - float _replayGainAlbumPeak = flacDecoder->replayGainAlbumPeak; - float _replayGainTrackGain = flacDecoder->replayGainTrackGain; - float _replayGainTrackPeak = flacDecoder->replayGainTrackPeak; + NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init]; NSString *_cuesheet = flacDecoder->cuesheet; const FLAC__StreamMetadata_VorbisComment *vorbis_comment = &metadata->data.vorbis_comment; for(int i = 0; i < vorbis_comment->num_comments; ++i) { @@ -246,74 +246,23 @@ void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMeta free(_name); free(_value); name = [name lowercaseString]; - if([name isEqualToString:@"artist"]) { - _artist = value; - } else if([name isEqualToString:@"albumartist"]) { - _albumartist = value; - } else if([name isEqualToString:@"album"]) { - _album = value; - } else if([name isEqualToString:@"title"]) { - _title = value; - } else if([name isEqualToString:@"genre"]) { - _genre = value; - } else if([name isEqualToString:@"cuesheet"]) { + if([name isEqualToString:@"cuesheet"]) { _cuesheet = value; flacDecoder->cuesheetFound = YES; - } else if([name isEqualToString:@"date"] || - [name isEqualToString:@"year"]) { - _year = @([value intValue]); - } else if([name isEqualToString:@"tracknumber"] || - [name isEqualToString:@"tracknum"] || - [name isEqualToString:@"track"]) { - _track = @([value intValue]); - } else if([name isEqualToString:@"discnumber"] || - [name isEqualToString:@"discnum"] || - [name isEqualToString:@"disc"]) { - _disc = @([value intValue]); - } else if([name isEqualToString:@"replaygain_album_gain"]) { - _replayGainAlbumGain = [value floatValue]; - } else if([name isEqualToString:@"replaygain_album_peak"]) { - _replayGainAlbumPeak = [value floatValue]; - } else if([name isEqualToString:@"replaygain_track_gain"]) { - _replayGainTrackGain = [value floatValue]; - } else if([name isEqualToString:@"replaygain_track_peak"]) { - _replayGainTrackPeak = [value floatValue]; } else if([name isEqualToString:@"waveformatextensible_channel_mask"]) { if([value hasPrefix:@"0x"]) { char *end; const char *_value = [value UTF8String] + 2; flacDecoder->channelConfig = (uint32_t)strtoul(_value, &end, 16); } + } else { + setDictionary(_metaDict, name, value); } } } - if(![_artist isEqual:flacDecoder->artist] || - ![_albumartist isEqual:flacDecoder->albumartist] || - ![_album isEqual:flacDecoder->album] || - ![_title isEqual:flacDecoder->title] || - ![_genre isEqual:flacDecoder->genre] || - ![_cuesheet isEqual:flacDecoder->cuesheet] || - ![_year isEqual:flacDecoder->year] || - ![_track isEqual:flacDecoder->track] || - ![_disc isEqual:flacDecoder->disc] || - _replayGainAlbumGain != flacDecoder->replayGainAlbumGain || - _replayGainAlbumPeak != flacDecoder->replayGainAlbumPeak || - _replayGainTrackGain != flacDecoder->replayGainTrackGain || - _replayGainTrackPeak != flacDecoder->replayGainTrackPeak) { - flacDecoder->artist = _artist; - flacDecoder->albumartist = _albumartist; - flacDecoder->album = _album; - flacDecoder->title = _title; - flacDecoder->genre = _genre; - flacDecoder->cuesheet = _cuesheet; - flacDecoder->year = _year; - flacDecoder->track = _track; - flacDecoder->disc = _disc; - flacDecoder->replayGainAlbumGain = _replayGainAlbumGain; - flacDecoder->replayGainAlbumPeak = _replayGainAlbumPeak; - flacDecoder->replayGainTrackGain = _replayGainTrackGain; - flacDecoder->replayGainTrackPeak = _replayGainTrackPeak; + if(![_metaDict isEqualToDictionary:flacDecoder->metaDict]) { + flacDecoder->metaDict = _metaDict; if(![flacDecoder->source seekable]) { [flacDecoder willChangeValueForKey:@"metadata"]; @@ -348,18 +297,8 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS isOggFlac = YES; } - artist = @""; - albumartist = @""; - album = @""; - title = @""; - genre = @""; - year = @(0); - track = @(0); - disc = @(0); - replayGainAlbumGain = 0.0; - replayGainAlbumPeak = 0.0; - replayGainTrackGain = 0.0; - replayGainTrackPeak = 0.0; + metaDict = [NSDictionary dictionary]; + icyMetaDict = [NSDictionary dictionary]; albumArt = [NSData data]; cuesheetFound = NO; cuesheet = @""; @@ -461,20 +400,28 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS Class sourceClass = [source class]; if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) { HTTPSource *httpSource = (HTTPSource *)source; + NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init]; if([httpSource hasMetadata]) { NSDictionary *metadata = [httpSource metadata]; NSString *_genre = [metadata valueForKey:@"genre"]; NSString *_album = [metadata valueForKey:@"album"]; NSString *_artist = [metadata valueForKey:@"artist"]; NSString *_title = [metadata valueForKey:@"title"]; - if(![_genre isEqualToString:genre] || - ![_album isEqualToString:album] || - ![_artist isEqualToString:artist] || - ![_title isEqualToString:title]) { - genre = _genre; - album = _album; - artist = _artist; - title = _title; + + if(_genre && [_genre length]) { + setDictionary(_icyMetaDict, @"genre", _genre); + } + if(_album && [_album length]) { + setDictionary(_icyMetaDict, @"album", _album); + } + if(_artist && [_artist length]) { + setDictionary(_icyMetaDict, @"artist", _artist); + } + if(_title && [_title length]) { + setDictionary(_icyMetaDict, @"title", _title); + } + if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) { + icyMetaDict = _icyMetaDict; [self willChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"]; } @@ -557,7 +504,10 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS } - (NSDictionary *)metadata { - return @{ @"artist": artist, @"albumartist": albumartist, @"album": album, @"title": title, @"genre": genre, @"year": year, @"track": track, @"disc": disc, @"replayGainAlbumGain": @(replayGainAlbumGain), @"replayGainAlbumPeak": @(replayGainAlbumPeak), @"replayGainTrackGain": @(replayGainTrackGain), @"replayGainTrackPeak": @(replayGainTrackPeak), @"cuesheet": cuesheet, @"albumArt": albumArt }; + NSDictionary *dict1 = @{ @"albumArt": albumArt, @"cuesheet": cuesheet }; + NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict]; + NSDictionary *dict3 = [dict2 dictionaryByMergingWith:icyMetaDict]; + return dict3; } + (NSArray *)fileTypes { diff --git a/Plugins/HighlyComplete/HighlyComplete/HCDecoder.h b/Plugins/HighlyComplete/HighlyComplete/HCDecoder.h index 1ae1497ca..ce01303ec 100644 --- a/Plugins/HighlyComplete/HighlyComplete/HCDecoder.h +++ b/Plugins/HighlyComplete/HighlyComplete/HCDecoder.h @@ -24,12 +24,6 @@ int tagLengthMs; int tagFadeMs; - float replayGainAlbumGain; - float replayGainAlbumPeak; - float replayGainTrackGain; - float replayGainTrackPeak; - float volume; - int type; int sampleRate; diff --git a/Plugins/HighlyComplete/HighlyComplete/HCDecoder.mm b/Plugins/HighlyComplete/HighlyComplete/HCDecoder.mm index a81555e1a..432a219a2 100644 --- a/Plugins/HighlyComplete/HighlyComplete/HCDecoder.mm +++ b/Plugins/HighlyComplete/HighlyComplete/HCDecoder.mm @@ -220,6 +220,20 @@ static int parse_time_crap(NSString *value) { return totalSeconds; } +static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) { + NSMutableArray *array = [dict valueForKey:tag]; + if(!array) { + array = [[NSMutableArray alloc] init]; + [dict setObject:array forKey:tag]; + } + if([array count]) { + NSString *existing = array[0]; + array[0] = [existing stringByAppendingFormat:@"\r\n%@", value]; + } else { + [array addObject:value]; + } +} + static int psf_info_meta(void *context, const char *name, const char *value) { struct psf_info_meta_state *state = (struct psf_info_meta_state *)context; @@ -232,46 +246,18 @@ static int psf_info_meta(void *context, const char *name, const char *value) { if([taglc isEqualToString:@"game"]) { taglc = @"album"; - } else if([taglc isEqualToString:@"date"]) { - taglc = @"year"; - } else if([taglc isEqualToString:@"album artist"]) { - taglc = @"albumartist"; - } else if([taglc isEqualToString:@"tracknumber"]) { - taglc = @"track"; - } else if([taglc isEqualToString:@"discnumber"]) { - taglc = @"disc"; } - if([taglc hasPrefix:@"replaygain_"]) { - if([taglc hasPrefix:@"replaygain_album_"]) { - if([taglc hasSuffix:@"gain"]) - state->albumGain = [svalue floatValue]; - else if([taglc hasSuffix:@"peak"]) - state->albumPeak = [svalue floatValue]; - } else if([taglc hasPrefix:@"replaygain_track_"]) { - if([taglc hasSuffix:@"gain"]) - state->trackGain = [svalue floatValue]; - else if([taglc hasSuffix:@"peak"]) - state->trackPeak = [svalue floatValue]; - } - } else if([taglc isEqualToString:@"volume"]) { - state->volume = [svalue floatValue]; - } else if([taglc isEqualToString:@"length"]) { + if([taglc isEqualToString:@"length"]) { state->tag_length_ms = parse_time_crap(svalue); + setDictionary(state->info, @"psf_length", svalue); } else if([taglc isEqualToString:@"fade"]) { state->tag_fade_ms = parse_time_crap(svalue); + setDictionary(state->info, @"psf_fade", svalue); } else if([taglc isEqualToString:@"utf8"]) { state->utf8 = true; - } else if([taglc isEqualToString:@"title"] || - [taglc isEqualToString:@"albumartist"] || - [taglc isEqualToString:@"artist"] || - [taglc isEqualToString:@"album"] || - [taglc isEqualToString:@"genre"]) { - [state->info setObject:svalue forKey:taglc]; - } else if([taglc isEqualToString:@"year"] || - [taglc isEqualToString:@"track"] || - [taglc isEqualToString:@"disc"]) { - [state->info setObject:@([svalue intValue]) forKey:taglc]; + } else { + setDictionary(state->info, taglc, svalue); } return 0; @@ -1218,12 +1204,6 @@ static int usf_info(void *context, const char *name, const char *value) { tagFadeMs = (int)ceil(defaultFade * 1000.0); } - replayGainAlbumGain = info.albumGain; - replayGainAlbumPeak = info.albumPeak; - replayGainTrackGain = info.trackGain; - replayGainTrackPeak = info.trackPeak; - volume = info.volume; - metadataList = info.info; framesLength = [self retrieveFrameCount:tagLengthMs]; @@ -1573,11 +1553,6 @@ static int usf_info(void *context, const char *name, const char *value) { @"totalFrames": @(totalFrames), @"bitrate": @(0), @"seekable": @(YES), - @"replayGainAlbumGain": @(replayGainAlbumGain), - @"replayGainAlbumPeak": @(replayGainAlbumPeak), - @"replayGainTrackGain": @(replayGainTrackGain), - @"replayGainTrackPeak": @(replayGainTrackPeak), - @"volume": @(volume), @"codec": codec, @"endian": @"host", @"encoding": @"synthesized" }; diff --git a/Plugins/MIDI/MIDI/MIDIMetadataReader.mm b/Plugins/MIDI/MIDI/MIDIMetadataReader.mm index 6d3f317bf..6fde95f49 100644 --- a/Plugins/MIDI/MIDI/MIDIMetadataReader.mm +++ b/Plugins/MIDI/MIDI/MIDIMetadataReader.mm @@ -26,6 +26,15 @@ return 1.0f; } +static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) { + NSMutableArray *array = [dict valueForKey:tag]; + if(!array) { + array = [[NSMutableArray alloc] init]; + [dict setObject:array forKey:tag]; + } + [array addObject:value]; +} + + (NSDictionary *)metadataForURL:(NSURL *)url { id audioSourceClass = NSClassFromString(@"AudioSource"); id source = [audioSourceClass audioSourceForURL:url]; @@ -62,9 +71,7 @@ midi_meta_data_item item; bool remap_display_name = !metadata.get_item("title", item); - NSArray *allowedKeys = @[@"title", @"artist", @"albumartist", @"album", @"genre"]; - - NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:10]; + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; for(size_t i = 0; i < metadata.get_count(); ++i) { const midi_meta_data_item &item = metadata[i]; @@ -72,11 +79,7 @@ if(![name isEqualToString:@"type"]) { if(remap_display_name && [name isEqualToString:@"display_name"]) name = @"title"; - if([allowedKeys containsObject:name]) { - [dict setObject:guess_encoding_of_string(item.m_value.c_str()) forKey:name]; - } else if([name isEqualToString:@"year"]) { - [dict setObject:@([guess_encoding_of_string(item.m_value.c_str()) intValue]) forKey:name]; - } + setDictionary(dict, name, guess_encoding_of_string(item.m_value.c_str())); } } diff --git a/Plugins/OpenMPT/OpenMPT/OMPTMetadataReader.mm b/Plugins/OpenMPT/OpenMPT/OMPTMetadataReader.mm index 66205ec41..b5922805e 100644 --- a/Plugins/OpenMPT/OpenMPT/OMPTMetadataReader.mm +++ b/Plugins/OpenMPT/OpenMPT/OMPTMetadataReader.mm @@ -27,6 +27,15 @@ return 1.0f; } +static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) { + NSMutableArray *array = [dict valueForKey:tag]; + if(!array) { + array = [[NSMutableArray alloc] init]; + [dict setObject:array forKey:tag]; + } + [array addObject:value]; +} + + (NSDictionary *)metadataForURL:(NSURL *)url { id audioSourceClass = NSClassFromString(@"AudioSource"); id source = [audioSourceClass audioSourceForURL:url]; @@ -55,41 +64,25 @@ std::map ctls; openmpt::module *mod = new openmpt::module(data, std::clog, ctls); - NSString *title = nil; - NSString *artist = nil; - // NSString * comment = nil; - NSString *date = nil; - NSString *type = nil; + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; std::vector keys = mod->get_metadata_keys(); for(std::vector::iterator key = keys.begin(); key != keys.end(); ++key) { - if(*key == "title") - title = guess_encoding_of_string(mod->get_metadata(*key).c_str()); - else if(*key == "artist") - artist = guess_encoding_of_string(mod->get_metadata(*key).c_str()); - /*else if ( *key == "message" ) - comment = guess_encoding_of_string(mod->get_metadata( *key ).c_str());*/ - else if(*key == "date") - date = guess_encoding_of_string(mod->get_metadata(*key).c_str()); - else if(*key == "type_long") - type = guess_encoding_of_string(mod->get_metadata(*key).c_str()); + NSString *tag = guess_encoding_of_string((*key).c_str()); + NSString *value = guess_encoding_of_string(mod->get_metadata(*key).c_str()); + if(*key == "type") + continue; + else if(*key == "type_long") { + setDictionary(dict, @"codec", value); + } else { + setDictionary(dict, tag, value); + } } delete mod; - if(title == nil) - title = @""; - if(artist == nil) - artist = @""; - /*if (comment == nil) - comment = @"";*/ - if(date == nil) - date = @""; - if(type == nil) - type = @""; - - return @{ @"title": title, @"artist": artist, /*@"comment": comment,*/ @"year": @([date intValue]), @"codec": type }; + return dict; } catch(std::exception & /*e*/) { return 0; } diff --git a/Plugins/Opus/Opus/OpusDecoder.h b/Plugins/Opus/Opus/OpusDecoder.h index b983d84dc..a74b29657 100644 --- a/Plugins/Opus/Opus/OpusDecoder.h +++ b/Plugins/Opus/Opus/OpusDecoder.h @@ -23,23 +23,13 @@ int channels; long totalFrames; - NSString *artist; - NSString *albumartist; - NSString *album; - NSString *title; - NSString *genre; - NSNumber *year; - NSNumber *track; - NSNumber *disc; - float replayGainAlbumGain; - float replayGainTrackGain; - - NSString *icygenre; - NSString *icyalbum; - NSString *icyartist; - NSString *icytitle; + NSDictionary *metaDict; + NSDictionary *icyMetaDict; NSData *albumArt; + + float replayGainAlbumGain; + float replayGainTrackGain; } @end diff --git a/Plugins/Opus/Opus/OpusDecoder.m b/Plugins/Opus/Opus/OpusDecoder.m index fc09fbbd7..d625f9524 100644 --- a/Plugins/Opus/Opus/OpusDecoder.m +++ b/Plugins/Opus/Opus/OpusDecoder.m @@ -16,6 +16,8 @@ #import "NSDictionary+Merge.h" +#import + @implementation OpusFile static const int MAXCHANNELS = 8; @@ -121,113 +123,60 @@ opus_int64 sourceTell(void *_stream) { [self willChangeValueForKey:@"properties"]; [self didChangeValueForKey:@"properties"]; - artist = @""; - albumartist = @""; - album = @""; - title = @""; - genre = @""; - icygenre = @""; - icyalbum = @""; - icyartist = @""; - icytitle = @""; - year = @(0); - track = @(0); - disc = @(0); + metaDict = [NSDictionary dictionary]; + icyMetaDict = [NSDictionary dictionary]; albumArt = [NSData data]; + [self updateMetadata]; return YES; } -- (NSString *)parseTag:(NSString *)tag fromTags:(const OpusTags *)tags { - NSMutableArray *tagStrings = [[NSMutableArray alloc] init]; - - int tagCount = opus_tags_query_count(tags, [tag UTF8String]); - - for(int i = 0; i < tagCount; ++i) { - const char *value = opus_tags_query(tags, [tag UTF8String], i); - [tagStrings addObject:guess_encoding_of_string(value)]; +static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) { + NSMutableArray *array = [dict valueForKey:tag]; + if(!array) { + array = [[NSMutableArray alloc] init]; + [dict setObject:array forKey:tag]; } - - return [tagStrings componentsJoinedByString:@", "]; + [array addObject:value]; } - (void)updateMetadata { - const OpusTags *tags = op_tags(opusRef, -1); + const struct OpusTags *tags = op_tags(opusRef, -1); + NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init]; + NSData *_albumArt = albumArt; if(tags) { - NSString *_artist = [self parseTag:@"artist" fromTags:tags]; - NSString *_albumartist = [self parseTag:@"albumartist" fromTags:tags]; - NSString *_album = [self parseTag:@"album" fromTags:tags]; - NSString *_title = [self parseTag:@"title" fromTags:tags]; - NSString *_genre = [self parseTag:@"genre" fromTags:tags]; + for(int i = 0; i < tags->comments; ++i) { + FLAC__StreamMetadata_VorbisComment_Entry entry = { .entry = (FLAC__byte *)tags->user_comments[i], .length = tags->comment_lengths[i] }; + char *name, *value; + if(FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(entry, &name, &value)) { + NSString *tagName = guess_encoding_of_string(name); + NSString *tagValue = guess_encoding_of_string(value); + free(name); + free(value); - NSString *_yearDate = [self parseTag:@"date" fromTags:tags]; - NSString *_yearYear = [self parseTag:@"year" fromTags:tags]; + tagName = [tagName lowercaseString]; - NSNumber *_year = @(0); - if([_yearDate length]) - _year = @([_yearDate intValue]); - else if([_yearYear length]) - _year = @([_yearYear intValue]); - - NSString *_trackNumber = [self parseTag:@"tracknumber" fromTags:tags]; - NSString *_trackNum = [self parseTag:@"tracknum" fromTags:tags]; - NSString *_trackTrack = [self parseTag:@"track" fromTags:tags]; - - NSNumber *_track = @(0); - if([_trackNumber length]) - _track = @([_trackNumber intValue]); - else if([_trackNum length]) - _track = @([_trackNum intValue]); - else if([_trackTrack length]) - _track = @([_trackTrack intValue]); - - NSString *_discNumber = [self parseTag:@"discnumber" fromTags:tags]; - NSString *_discNum = [self parseTag:@"discnum" fromTags:tags]; - NSString *_discDisc = [self parseTag:@"disc" fromTags:tags]; - - NSNumber *_disc = @(0); - if([_discNumber length]) - _disc = @([_discNumber intValue]); - else if([_discNum length]) - _disc = @([_discNum intValue]); - else if([_discDisc length]) - _disc = @([_discDisc intValue]); - - NSData *_albumArt = [NSData data]; - - size_t count = opus_tags_query_count(tags, "METADATA_BLOCK_PICTURE"); - if(count) { - const char *pictureTag = opus_tags_query(tags, "METADATA_BLOCK_PICTURE", 0); - OpusPictureTag _pic = { 0 }; - if(opus_picture_tag_parse(&_pic, pictureTag) >= 0) { - if(_pic.format == OP_PIC_FORMAT_PNG || - _pic.format == OP_PIC_FORMAT_JPEG || - _pic.format == OP_PIC_FORMAT_GIF) { - _albumArt = [NSData dataWithBytes:_pic.data length:_pic.data_length]; + if([tagName isEqualToString:@"metadata_block_picture"]) { + OpusPictureTag _pic = { 0 }; + if(opus_picture_tag_parse(&_pic, [tagValue UTF8String]) >= 0) { + if(_pic.format == OP_PIC_FORMAT_PNG || + _pic.format == OP_PIC_FORMAT_JPEG || + _pic.format == OP_PIC_FORMAT_GIF) { + _albumArt = [NSData dataWithBytes:_pic.data length:_pic.data_length]; + } + opus_picture_tag_clear(&_pic); + } + } else { + setDictionary(_metaDict, tagName, tagValue); } - opus_picture_tag_clear(&_pic); } } - if(![_artist isEqual:artist] || - ![_albumartist isEqual:albumartist] || - ![_album isEqual:album] || - ![_title isEqual:title] || - ![_genre isEqual:genre] || - ![_year isEqual:year] || - ![_track isEqual:year] || - ![_disc isEqual:disc] || - ![_albumArt isEqual:albumArt]) { - artist = _artist; - albumartist = _albumartist; - album = _album; - title = _title; - genre = _genre; - year = _year; - track = _track; - disc = _disc; + if(![_albumArt isEqualToData:albumArt] || + ![_metaDict isEqualToDictionary:metaDict]) { + metaDict = _metaDict; albumArt = _albumArt; [self willChangeValueForKey:@"metadata"]; @@ -239,31 +188,35 @@ opus_int64 sourceTell(void *_stream) { - (void)updateIcyMetadata { if([source seekable]) return; - NSString *_genre = icygenre; - NSString *_album = icyalbum; - NSString *_artist = icyartist; - NSString *_title = icytitle; + NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init]; Class sourceClass = [source class]; if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) { HTTPSource *httpSource = (HTTPSource *)source; if([httpSource hasMetadata]) { NSDictionary *metadata = [httpSource metadata]; - _genre = [metadata valueForKey:@"genre"]; - _album = [metadata valueForKey:@"album"]; - _artist = [metadata valueForKey:@"artist"]; - _title = [metadata valueForKey:@"title"]; + NSString *_genre = [metadata valueForKey:@"genre"]; + NSString *_album = [metadata valueForKey:@"album"]; + NSString *_artist = [metadata valueForKey:@"artist"]; + NSString *_title = [metadata valueForKey:@"title"]; + + if(_genre && [_genre length]) { + setDictionary(_icyMetaDict, @"genre", _genre); + } + if(_album && [_album length]) { + setDictionary(_icyMetaDict, @"album", _album); + } + if(_artist && [_artist length]) { + setDictionary(_icyMetaDict, @"artist", _artist); + } + if(_title && [_title length]) { + setDictionary(_icyMetaDict, @"title", _title); + } } } - if(![_genre isEqual:icygenre] || - ![_album isEqual:icyalbum] || - ![_artist isEqual:icyartist] || - ![_title isEqual:icytitle]) { - icygenre = _genre; - icyalbum = _album; - icyartist = _artist; - icytitle = _title; + if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) { + icyMetaDict = _icyMetaDict; [self willChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"]; } @@ -338,15 +291,18 @@ opus_int64 sourceTell(void *_stream) { @"totalFrames": @(totalFrames), @"bitrate": @(bitrate), @"seekable": @([source seekable] && seekable), - @"replayGainAlbumGain": @(replayGainAlbumGain), - @"replayGainTrackGain": @(replayGainTrackGain), + @"replaygain_album_gain": @(replayGainAlbumGain), + @"replaygain_track_gain": @(replayGainTrackGain), @"codec": @"Opus", @"endian": @"host", @"encoding": @"lossy" }; } - (NSDictionary *)metadata { - return [@{ @"artist": artist, @"albumartist": albumartist, @"album": album, @"title": title, @"genre": genre, @"year": year, @"track": track, @"disc": disc, @"albumArt": albumArt } dictionaryByMergingWith:@{ @"genre": icygenre, @"album": icyalbum, @"artist": icyartist, @"title": icytitle }]; + NSDictionary *dict1 = @{ @"albumArt": albumArt }; + NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict]; + NSDictionary *dict3 = [dict2 dictionaryByMergingWith:icyMetaDict]; + return dict3; } + (NSArray *)fileTypes { diff --git a/Plugins/Opus/OpusPlugin.xcodeproj/project.pbxproj b/Plugins/Opus/OpusPlugin.xcodeproj/project.pbxproj index e0fcdb202..14c5d0bec 100644 --- a/Plugins/Opus/OpusPlugin.xcodeproj/project.pbxproj +++ b/Plugins/Opus/OpusPlugin.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8301C14928780C1A00651A6E /* libFLAC.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8301C14828780C1A00651A6E /* libFLAC.8.dylib */; }; 83186316285CEC91001422CC /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83186315285CEC91001422CC /* NSDictionary+Merge.m */; }; 836EF0CF27BB952F00BF35B2 /* libopusfile.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0CE27BB952F00BF35B2 /* libopusfile.0.dylib */; }; 8375B04017FFEA400092A79F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8375B03F17FFEA400092A79F /* Cocoa.framework */; }; @@ -27,6 +28,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 8301C14828780C1A00651A6E /* libFLAC.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libFLAC.8.dylib; path = ../../ThirdParty/flac/lib/libFLAC.8.dylib; sourceTree = ""; }; 83186314285CEC91001422CC /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../../Utils/NSDictionary+Merge.h"; sourceTree = ""; }; 83186315285CEC91001422CC /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../../Utils/NSDictionary+Merge.m"; sourceTree = ""; }; 833F68411CDBCABC00AFB9F0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -53,6 +55,7 @@ buildActionMask = 2147483647; files = ( 8375B04017FFEA400092A79F /* Cocoa.framework in Frameworks */, + 8301C14928780C1A00651A6E /* libFLAC.8.dylib in Frameworks */, 836EF0CF27BB952F00BF35B2 /* libopusfile.0.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -90,6 +93,7 @@ 8375B03E17FFEA400092A79F /* Frameworks */ = { isa = PBXGroup; children = ( + 8301C14828780C1A00651A6E /* libFLAC.8.dylib */, 836EF0CE27BB952F00BF35B2 /* libopusfile.0.dylib */, 8375B03F17FFEA400092A79F /* Cocoa.framework */, 8375B04117FFEA400092A79F /* Other Frameworks */, @@ -340,10 +344,14 @@ ../../ThirdParty/opusfile/include, ../../ThirdParty/opus/include, ../../ThirdParty/ogg/include, + ../../ThirdParty/flac/include, ); INFOPLIST_FILE = "Opus/OpusPlugin-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; - LIBRARY_SEARCH_PATHS = ../../ThirdParty/opusfile/lib; + LIBRARY_SEARCH_PATHS = ( + ../../ThirdParty/opusfile/lib, + ../../ThirdParty/flac/lib, + ); PRODUCT_BUNDLE_IDENTIFIER = net.kode54.opus; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -364,10 +372,14 @@ ../../ThirdParty/opusfile/include, ../../ThirdParty/opus/include, ../../ThirdParty/ogg/include, + ../../ThirdParty/flac/include, ); INFOPLIST_FILE = "Opus/OpusPlugin-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; - LIBRARY_SEARCH_PATHS = ../../ThirdParty/opusfile/lib; + LIBRARY_SEARCH_PATHS = ( + ../../ThirdParty/opusfile/lib, + ../../ThirdParty/flac/lib, + ); PRODUCT_BUNDLE_IDENTIFIER = net.kode54.opus; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; diff --git a/Plugins/TagLib/TagLibID3v2Reader.mm b/Plugins/TagLib/TagLibID3v2Reader.mm index 51f3f5a20..daaacfbdd 100644 --- a/Plugins/TagLib/TagLibID3v2Reader.mm +++ b/Plugins/TagLib/TagLibID3v2Reader.mm @@ -87,10 +87,10 @@ rgAlbumPeak = tag->rgAlbumPeak(); rgTrackGain = tag->rgTrackGain(); rgTrackPeak = tag->rgTrackPeak(); - [dict setObject:@(rgAlbumGain) forKey:@"replayGainAlbumGain"]; - [dict setObject:@(rgAlbumPeak) forKey:@"replayGainAlbumPeak"]; - [dict setObject:@(rgTrackGain) forKey:@"replayGainTrackGain"]; - [dict setObject:@(rgTrackPeak) forKey:@"replayGainTrackPeak"]; + [dict setObject:@(rgAlbumGain) forKey:@"replaygain_album_gain"]; + [dict setObject:@(rgAlbumPeak) forKey:@"replaygain_album_peak"]; + [dict setObject:@(rgTrackGain) forKey:@"replaygain_track_gain"]; + [dict setObject:@(rgTrackPeak) forKey:@"replaygain_track_peak"]; soundcheck = tag->soundcheck(); if(!soundcheck.isEmpty()) { @@ -128,6 +128,9 @@ if(!cuesheet.isEmpty()) [dict setObject:[NSString stringWithUTF8String:cuesheet.toCString(true)] forKey:@"cuesheet"]; + if(!comment.isEmpty()) + [dict setObject:[NSString stringWithUTF8String:comment.toCString(true)] forKey:@"comment"]; + // Try to load the image. NSData *image = nil; diff --git a/Plugins/TagLib/TagLibMetadataReader.m b/Plugins/TagLib/TagLibMetadataReader.m index 9cf9c407b..7c665227a 100644 --- a/Plugins/TagLib/TagLibMetadataReader.m +++ b/Plugins/TagLib/TagLibMetadataReader.m @@ -101,10 +101,10 @@ rgAlbumPeak = tag->rgAlbumPeak(); rgTrackGain = tag->rgTrackGain(); rgTrackPeak = tag->rgTrackPeak(); - [dict setObject:@(rgAlbumGain) forKey:@"replayGainAlbumGain"]; - [dict setObject:@(rgAlbumPeak) forKey:@"replayGainAlbumPeak"]; - [dict setObject:@(rgTrackGain) forKey:@"replayGainTrackGain"]; - [dict setObject:@(rgTrackPeak) forKey:@"replayGainTrackPeak"]; + [dict setObject:@(rgAlbumGain) forKey:@"replaygain_album_gain"]; + [dict setObject:@(rgAlbumPeak) forKey:@"replaygain_album_peak"]; + [dict setObject:@(rgTrackGain) forKey:@"replaygain_track_gain"]; + [dict setObject:@(rgTrackPeak) forKey:@"replaygain_track_peak"]; soundcheck = tag->soundcheck(); if(!soundcheck.isEmpty()) { @@ -141,6 +141,9 @@ if(!cuesheet.isEmpty()) [dict setObject:[NSString stringWithUTF8String:cuesheet.toCString(true)] forKey:@"cuesheet"]; + + if(!comment.isEmpty()) + [dict setObject:[NSString stringWithUTF8String:comment.toCString(true)] forKey:@"comment"]; } // Try to load the image. diff --git a/Plugins/Vorbis/VorbisDecoder.h b/Plugins/Vorbis/VorbisDecoder.h index 3c5806e71..61c271ea0 100644 --- a/Plugins/Vorbis/VorbisDecoder.h +++ b/Plugins/Vorbis/VorbisDecoder.h @@ -32,23 +32,8 @@ float frequency; long totalFrames; - NSString *artist; - NSString *albumartist; - NSString *album; - NSString *title; - NSString *genre; - NSNumber *year; - NSNumber *track; - NSNumber *disc; - float replayGainAlbumGain; - float replayGainAlbumPeak; - float replayGainTrackGain; - float replayGainTrackPeak; - - NSString *icygenre; - NSString *icyalbum; - NSString *icyartist; - NSString *icytitle; + NSDictionary *metaDict; + NSDictionary *icyMetaDict; NSData *albumArt; } diff --git a/Plugins/Vorbis/VorbisDecoder.m b/Plugins/Vorbis/VorbisDecoder.m index 30302c795..410ed5641 100644 --- a/Plugins/Vorbis/VorbisDecoder.m +++ b/Plugins/Vorbis/VorbisDecoder.m @@ -16,6 +16,8 @@ #import "NSDictionary+Merge.h" +#import + @implementation VorbisDecoder static const int MAXCHANNELS = 8; @@ -101,111 +103,58 @@ long sourceTell(void *datasource) { [self willChangeValueForKey:@"properties"]; [self didChangeValueForKey:@"properties"]; - artist = @""; - albumartist = @""; - album = @""; - title = @""; - genre = @""; - icygenre = @""; - icyalbum = @""; - icyartist = @""; - icytitle = @""; - year = @(0); - track = @(0); - disc = @(0); + metaDict = [NSDictionary dictionary]; + icyMetaDict = [NSDictionary dictionary]; albumArt = [NSData data]; + [self updateMetadata]; return YES; } -- (NSString *)parseTag:(NSString *)tag fromTags:(vorbis_comment *)tags { - NSMutableArray *tagStrings = [[NSMutableArray alloc] init]; - - int tagCount = vorbis_comment_query_count(tags, [tag UTF8String]); - - for(int i = 0; i < tagCount; ++i) { - const char *value = vorbis_comment_query(tags, [tag UTF8String], i); - [tagStrings addObject:guess_encoding_of_string(value)]; +static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) { + NSMutableArray *array = [dict valueForKey:tag]; + if(!array) { + array = [[NSMutableArray alloc] init]; + [dict setObject:array forKey:tag]; } - - return [tagStrings componentsJoinedByString:@", "]; + [array addObject:value]; } - (void)updateMetadata { vorbis_comment *tags = ov_comment(&vorbisRef, -1); + NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init]; + NSData *_albumArt = albumArt; if(tags) { - NSString *_artist = [self parseTag:@"artist" fromTags:tags]; - NSString *_albumartist = [self parseTag:@"albumartist" fromTags:tags]; - NSString *_album = [self parseTag:@"album" fromTags:tags]; - NSString *_title = [self parseTag:@"title" fromTags:tags]; - NSString *_genre = [self parseTag:@"genre" fromTags:tags]; + for(int i = 0; i < tags->comments; ++i) { + FLAC__StreamMetadata_VorbisComment_Entry entry = { .entry = (FLAC__byte *)tags->user_comments[i], .length = tags->comment_lengths[i] }; + char *name, *value; + if(FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(entry, &name, &value)) { + NSString *tagName = guess_encoding_of_string(name); + NSString *tagValue = guess_encoding_of_string(value); + free(name); + free(value); - NSString *_yearDate = [self parseTag:@"date" fromTags:tags]; - NSString *_yearYear = [self parseTag:@"year" fromTags:tags]; + tagName = [tagName lowercaseString]; - NSNumber *_year = @(0); - if([_yearDate length]) - _year = @([_yearDate intValue]); - else if([_yearYear length]) - _year = @([_yearYear intValue]); - - NSString *_trackNumber = [self parseTag:@"tracknumber" fromTags:tags]; - NSString *_trackNum = [self parseTag:@"tracknum" fromTags:tags]; - NSString *_trackTrack = [self parseTag:@"track" fromTags:tags]; - - NSNumber *_track = @(0); - if([_trackNumber length]) - _track = @([_trackNumber intValue]); - else if([_trackNum length]) - _track = @([_trackNum intValue]); - else if([_trackTrack length]) - _track = @([_trackTrack intValue]); - - NSString *_discNumber = [self parseTag:@"discnumber" fromTags:tags]; - NSString *_discNum = [self parseTag:@"discnum" fromTags:tags]; - NSString *_discDisc = [self parseTag:@"disc" fromTags:tags]; - - NSNumber *_disc = @(0); - if([_discNumber length]) - _disc = @([_discNumber intValue]); - else if([_discNum length]) - _disc = @([_discNum intValue]); - else if([_discDisc length]) - _disc = @([_discDisc intValue]); - - NSData *_albumArt = [NSData data]; - - size_t count = vorbis_comment_query_count(tags, "METADATA_BLOCK_PICTURE"); - if(count) { - const char *pictureTag = vorbis_comment_query(tags, "METADATA_BLOCK_PICTURE", 0); - flac_picture_t *picture = flac_picture_parse_from_base64(pictureTag); - if(picture) { - if(picture->binary && picture->binary_length) { - _albumArt = [NSData dataWithBytes:picture->binary length:picture->binary_length]; + if([tagName isEqualToString:@"metadata_block_picture"]) { + flac_picture_t *picture = flac_picture_parse_from_base64([tagValue UTF8String]); + if(picture) { + if(picture->binary && picture->binary_length) { + _albumArt = [NSData dataWithBytes:picture->binary length:picture->binary_length]; + } + flac_picture_free(picture); + } + } else { + setDictionary(_metaDict, tagName, tagValue); } - flac_picture_free(picture); } } - if(![_artist isEqual:artist] || - ![_albumartist isEqual:albumartist] || - ![_album isEqual:album] || - ![_title isEqual:title] || - ![_genre isEqual:genre] || - ![_year isEqual:year] || - ![_track isEqual:year] || - ![_disc isEqual:disc] || - ![_albumArt isEqual:albumArt]) { - artist = _artist; - albumartist = _albumartist; - album = _album; - title = _title; - genre = _genre; - year = _year; - track = _track; - disc = _disc; + if(![_albumArt isEqualToData:albumArt] || + ![_metaDict isEqualToDictionary:metaDict]) { + metaDict = _metaDict; albumArt = _albumArt; [self willChangeValueForKey:@"metadata"]; @@ -217,31 +166,35 @@ long sourceTell(void *datasource) { - (void)updateIcyMetadata { if([source seekable]) return; - NSString *_genre = icygenre; - NSString *_album = icyalbum; - NSString *_artist = icyartist; - NSString *_title = icytitle; + NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init]; Class sourceClass = [source class]; if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) { HTTPSource *httpSource = (HTTPSource *)source; if([httpSource hasMetadata]) { NSDictionary *metadata = [httpSource metadata]; - _genre = [metadata valueForKey:@"genre"]; - _album = [metadata valueForKey:@"album"]; - _artist = [metadata valueForKey:@"artist"]; - _title = [metadata valueForKey:@"title"]; + NSString *_genre = [metadata valueForKey:@"genre"]; + NSString *_album = [metadata valueForKey:@"album"]; + NSString *_artist = [metadata valueForKey:@"artist"]; + NSString *_title = [metadata valueForKey:@"title"]; + + if(_genre && [_genre length]) { + setDictionary(_icyMetaDict, @"genre", _genre); + } + if(_album && [_album length]) { + setDictionary(_icyMetaDict, @"album", _album); + } + if(_artist && [_artist length]) { + setDictionary(_icyMetaDict, @"artist", _artist); + } + if(_title && [_title length]) { + setDictionary(_icyMetaDict, @"title", _title); + } } } - if(![_genre isEqual:icygenre] || - ![_album isEqual:icyalbum] || - ![_artist isEqual:icyartist] || - ![_title isEqual:icytitle]) { - icygenre = _genre; - icyalbum = _album; - icyartist = _artist; - icytitle = _title; + if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) { + icyMetaDict = _icyMetaDict; [self willChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"]; } @@ -325,7 +278,10 @@ long sourceTell(void *datasource) { } - (NSDictionary *)metadata { - return [@{ @"artist": artist, @"albumartist": albumartist, @"album": album, @"title": title, @"genre": genre, @"year": year, @"track": track, @"disc": disc, @"albumArt": albumArt } dictionaryByMergingWith:@{ @"genre": icygenre, @"album": icyalbum, @"artist": icyartist, @"title": icytitle }]; + NSDictionary *dict1 = @{ @"albumArt": albumArt }; + NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict]; + NSDictionary *dict3 = [dict2 dictionaryByMergingWith:icyMetaDict]; + return dict3; } + (NSArray *)fileTypes { diff --git a/Plugins/Vorbis/VorbisPlugin.xcodeproj/project.pbxproj b/Plugins/Vorbis/VorbisPlugin.xcodeproj/project.pbxproj index 734274b1f..8a425bcc4 100644 --- a/Plugins/Vorbis/VorbisPlugin.xcodeproj/project.pbxproj +++ b/Plugins/Vorbis/VorbisPlugin.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 17C93D360B8FDA66008627D6 /* VorbisDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C93D340B8FDA66008627D6 /* VorbisDecoder.m */; }; + 8301C14B287810F300651A6E /* libFLAC.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8301C14A287810F300651A6E /* libFLAC.8.dylib */; }; 83186313285CEBD2001422CC /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83186312285CEBD2001422CC /* NSDictionary+Merge.m */; }; 836EF0D627BB969D00BF35B2 /* libvorbisfile.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0D427BB969D00BF35B2 /* libvorbisfile.3.dylib */; }; 836EF0DF27BB987000BF35B2 /* libvorbis.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0DE27BB987000BF35B2 /* libvorbis.0.dylib */; }; @@ -24,6 +25,7 @@ 17C93D330B8FDA66008627D6 /* VorbisDecoder.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VorbisDecoder.h; sourceTree = ""; }; 17C93D340B8FDA66008627D6 /* VorbisDecoder.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = VorbisDecoder.m; sourceTree = ""; }; 32DBCF630370AF2F00C91783 /* VorbisPlugin_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VorbisPlugin_Prefix.pch; sourceTree = ""; }; + 8301C14A287810F300651A6E /* libFLAC.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libFLAC.8.dylib; path = ../../ThirdParty/flac/lib/libFLAC.8.dylib; sourceTree = ""; }; 83186311285CEBD2001422CC /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = ""; }; 83186312285CEBD2001422CC /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = ""; }; 8356BD1C27B46A2D0074E50C /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../HTTPSource/HTTPSource.h; sourceTree = ""; }; @@ -45,6 +47,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 8301C14B287810F300651A6E /* libFLAC.8.dylib in Frameworks */, 8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */, 836EF0DF27BB987000BF35B2 /* libvorbis.0.dylib in Frameworks */, 836EF0D627BB969D00BF35B2 /* libvorbisfile.3.dylib in Frameworks */, @@ -103,6 +106,7 @@ 1058C7ACFEA557BF11CA2CBB /* Linked Frameworks */ = { isa = PBXGroup; children = ( + 8301C14A287810F300651A6E /* libFLAC.8.dylib */, 836EF0DE27BB987000BF35B2 /* libvorbis.0.dylib */, 836EF0D427BB969D00BF35B2 /* libvorbisfile.3.dylib */, 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */, @@ -295,10 +299,14 @@ "$(inherited)", ../../ThirdParty/vorbis/include, ../../ThirdParty/ogg/include, + ../../ThirdParty/flac/include, ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Library/Bundles"; - LIBRARY_SEARCH_PATHS = ../../ThirdParty/vorbis/lib; + LIBRARY_SEARCH_PATHS = ( + ../../ThirdParty/vorbis/lib, + ../../ThirdParty/flac/lib, + ); PRODUCT_BUNDLE_IDENTIFIER = org.cogx.vorbis; PRODUCT_NAME = VorbisPlugin; SDKROOT = macosx; @@ -324,10 +332,14 @@ "$(inherited)", ../../ThirdParty/vorbis/include, ../../ThirdParty/ogg/include, + ../../ThirdParty/flac/include, ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Library/Bundles"; - LIBRARY_SEARCH_PATHS = ../../ThirdParty/vorbis/lib; + LIBRARY_SEARCH_PATHS = ( + ../../ThirdParty/vorbis/lib, + ../../ThirdParty/flac/lib, + ); PRODUCT_BUNDLE_IDENTIFIER = org.cogx.vorbis; PRODUCT_NAME = VorbisPlugin; SDKROOT = macosx; diff --git a/Plugins/vgmstream/vgmstream/VGMDecoder.m b/Plugins/vgmstream/vgmstream/VGMDecoder.m index 78ba9ac25..5f49855c4 100644 --- a/Plugins/vgmstream/vgmstream/VGMDecoder.m +++ b/Plugins/vgmstream/vgmstream/VGMDecoder.m @@ -157,10 +157,10 @@ static NSString *get_description_tag(const char *description, const char *tag, c @"floatingPoint": @(NO), @"channels": @(channels), @"seekable": @(YES), - @"replayGainAlbumGain": rgAlbumGain, - @"replayGainAlbumPeak": rgAlbumPeak, - @"replayGainTrackGain": rgTrackGain, - @"replayGainTrackPeak": rgTrackPeak, + @"replaygain_album_gain": rgAlbumGain, + @"replaygain_album_peak": rgAlbumPeak, + @"replaygain_track_gain": rgTrackGain, + @"replaygain_track_peak": rgTrackPeak, @"codec": codec, @"endian": @"host", @"encoding": @"lossy/lossless" }; diff --git a/Utils/RedundantPlaylistDataStore.m b/Utils/RedundantPlaylistDataStore.m index 904ba6264..e1df1a300 100644 --- a/Utils/RedundantPlaylistDataStore.m +++ b/Utils/RedundantPlaylistDataStore.m @@ -51,6 +51,17 @@ } } +- (NSArray *)coalesceArray:(NSArray *)input { + if(input == nil) return input; + + NSMutableArray *array = [[NSMutableArray alloc] init]; + for(NSString *string in input) { + [array addObject:[self coalesceString:string]]; + } + + return [NSArray arrayWithArray:array]; +} + - (NSDictionary *)coalesceEntryInfo:(NSDictionary *)entryInfo { if(entryInfo == nil) return entryInfo; @@ -63,6 +74,9 @@ } else if([obj isKindOfClass:[NSData class]]) { NSData *dataObj = (NSData *)obj; [ret setObject:[self coalesceArt:dataObj] forKey:key]; + } else if([obj isKindOfClass:[NSArray class]]) { + NSArray *arrayObj = (NSArray *)obj; + [ret setObject:[self coalesceArray:arrayObj] forKey:key]; } else { [ret setObject:obj forKey:key]; } diff --git a/en.lproj/InfoInspector.strings b/en.lproj/InfoInspector.strings index 48f90b4af..0a67f6400 100644 --- a/en.lproj/InfoInspector.strings +++ b/en.lproj/InfoInspector.strings @@ -14,9 +14,6 @@ /* Class = "NSTextFieldCell"; title = "Length:"; ObjectID = "16"; */ "16.title" = "Length:"; -/* Class = "NSTextFieldCell"; title = "Year:"; ObjectID = "18"; */ -"18.title" = "Year:"; - /* Class = "NSTextFieldCell"; title = "Genre:"; ObjectID = "20"; */ "20.title" = "Genre:"; @@ -79,3 +76,9 @@ /* Class = "NSTextFieldCell"; title = "Codec:"; ObjectID = "QPg-Mb-Urn"; */ "QPg-Mb-Urn.title" = "Codec:"; + +/* Class = "NSTextFieldCell"; title = "Date:"; ObjectID = "17"; */ +"18.title" = "Date:"; + +/* Class = "NSTextFieldCell"; title = "Comment:"; ObjectID = "cd3-Qt-hCm"; */ +"Ule-N3-dKW.title" = "Comment:"; diff --git a/es.lproj/InfoInspector.strings b/es.lproj/InfoInspector.strings index 5e772b625..1d476ea7f 100644 --- a/es.lproj/InfoInspector.strings +++ b/es.lproj/InfoInspector.strings @@ -16,9 +16,6 @@ /* Class = "NSTextFieldCell"; title = "Length:"; ObjectID = "16"; */ "16.title" = "Duración:"; -/* Class = "NSTextFieldCell"; title = "Year:"; ObjectID = "18"; */ -"18.title" = "Año:"; - /* Class = "NSTextFieldCell"; title = "Genre:"; ObjectID = "20"; */ "20.title" = "Género:"; @@ -109,3 +106,8 @@ /* Class = "NSTextFieldCell"; title = "N/A"; ObjectID = "Yby-OU-cqP"; */ "Yby-OU-cqP.title" = "n/d"; +/* Class = "NSTextFieldCell"; title = "Date:"; ObjectID = "17"; */ +"18.title" = "Date:"; + +/* Class = "NSTextFieldCell"; title = "Comment:"; ObjectID = "cd3-Qt-hCm"; */ +"Ule-N3-dKW.title" = "Comment:";