From d24a01a6370e14f10f35cce6eac355d48ea5f959 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Fri, 14 Jan 2022 16:46:35 -0800 Subject: [PATCH] Implemented basic embedded CueSheet support --- Audio/AudioDecoder.h | 1 + Audio/AudioDecoder.m | 7 +- Audio/AudioMetadataReader.h | 1 + Audio/AudioMetadataReader.m | 9 +- Audio/AudioPlayer.m | 42 ++++- Audio/Plugin.h | 4 +- Audio/PluginController.m | 30 +++- .../TagLib/taglib/taglib/ape/apetag.cpp | 11 ++ Frameworks/TagLib/taglib/taglib/ape/apetag.h | 2 + .../TagLib/taglib/taglib/asf/asftag.cpp | 9 + Frameworks/TagLib/taglib/taglib/asf/asftag.h | 4 + .../TagLib/taglib/taglib/mod/modtag.cpp | 9 + Frameworks/TagLib/taglib/taglib/mod/modtag.h | 10 ++ .../TagLib/taglib/taglib/mp4/mp4tag.cpp | 11 ++ Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h | 18 +- .../taglib/taglib/mpeg/id3v1/id3v1tag.cpp | 9 + .../taglib/taglib/mpeg/id3v1/id3v1tag.h | 2 + .../taglib/taglib/mpeg/id3v2/id3v2tag.cpp | 46 ++++++ .../taglib/taglib/mpeg/id3v2/id3v2tag.h | 4 + .../TagLib/taglib/taglib/ogg/xiphcomment.cpp | 15 ++ .../TagLib/taglib/taglib/ogg/xiphcomment.h | 2 + .../TagLib/taglib/taglib/riff/wav/infotag.cpp | 9 + .../TagLib/taglib/taglib/riff/wav/infotag.h | 2 + Frameworks/TagLib/taglib/taglib/tag.h | 12 ++ Frameworks/TagLib/taglib/taglib/tagunion.cpp | 10 ++ Frameworks/TagLib/taglib/taglib/tagunion.h | 2 + Playlist/PlaylistEntry.h | 4 + Playlist/PlaylistEntry.m | 2 + Plugins/APL/APLDecoder.m | 2 +- Plugins/CueSheet/CueSheet.h | 3 + Plugins/CueSheet/CueSheet.m | 51 ++++-- Plugins/CueSheet/CueSheetContainer.m | 30 +++- Plugins/CueSheet/CueSheetDecoder.h | 4 + Plugins/CueSheet/CueSheetDecoder.m | 155 +++++++++++++----- Plugins/CueSheet/CueSheetMetadataReader.m | 31 +++- Plugins/TagLib/TagLibMetadataReader.m | 5 + 36 files changed, 482 insertions(+), 86 deletions(-) diff --git a/Audio/AudioDecoder.h b/Audio/AudioDecoder.h index aadda898a..02d2707f3 100644 --- a/Audio/AudioDecoder.h +++ b/Audio/AudioDecoder.h @@ -14,5 +14,6 @@ } + (id)audioDecoderForSource:(id )source; ++ (id)audioDecoderForSource:(id )source skipCue:(BOOL)skip; @end diff --git a/Audio/AudioDecoder.m b/Audio/AudioDecoder.m index a0aa0ce3b..eb5e095b1 100644 --- a/Audio/AudioDecoder.m +++ b/Audio/AudioDecoder.m @@ -14,7 +14,12 @@ + (id) audioDecoderForSource:(id )source { - return [[PluginController sharedPluginController] audioDecoderForSource:source]; + return [[PluginController sharedPluginController] audioDecoderForSource:source skipCue:NO]; +} + ++ (id) audioDecoderForSource:(id )source skipCue:(BOOL)skip +{ + return [[PluginController sharedPluginController] audioDecoderForSource:source skipCue:skip]; } @end diff --git a/Audio/AudioMetadataReader.h b/Audio/AudioMetadataReader.h index 20784f3c9..fa09372f9 100644 --- a/Audio/AudioMetadataReader.h +++ b/Audio/AudioMetadataReader.h @@ -14,5 +14,6 @@ } + (NSDictionary *)metadataForURL:(NSURL *)url; ++ (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip; @end diff --git a/Audio/AudioMetadataReader.m b/Audio/AudioMetadataReader.m index ed42d83b4..00e7d093a 100644 --- a/Audio/AudioMetadataReader.m +++ b/Audio/AudioMetadataReader.m @@ -14,7 +14,14 @@ + (NSDictionary *)metadataForURL:(NSURL *)url { @autoreleasepool { - return [[PluginController sharedPluginController] metadataForURL:url]; + return [[PluginController sharedPluginController] metadataForURL:url skipCue:NO]; + } +} + ++ (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip +{ + @autoreleasepool { + return [[PluginController sharedPluginController] metadataForURL:url skipCue:skip]; } } diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 7fcf90c53..1bc89a479 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -347,11 +347,49 @@ lastChain = bufferChain; } - if ([[nextStream scheme] isEqualToString:[[lastChain streamURL] scheme]] + BOOL pathsEqual = NO; + + if ([nextStream isFileURL] && [[lastChain streamURL] isFileURL]) + { + NSMutableString *unixPathNext = [[nextStream path] mutableCopy]; + NSMutableString *unixPathPrev = [[[lastChain streamURL] path] mutableCopy]; + + //Get the fragment + NSString *fragmentNext = @""; + NSScanner *scanner = [NSScanner scannerWithString:unixPathNext]; + NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"#1234567890"]; + while (![scanner isAtEnd]) { + NSString *possibleFragment; + [scanner scanUpToString:@"#" intoString:nil]; + + if ([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) { + fragmentNext = possibleFragment; + [unixPathNext deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])]; + break; + } + } + + NSString *fragmentPrev = @""; + scanner = [NSScanner scannerWithString:unixPathPrev]; + while (![scanner isAtEnd]) { + NSString *possibleFragment; + [scanner scanUpToString:@"#" intoString:nil]; + + if ([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) { + fragmentPrev = possibleFragment; + [unixPathPrev deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])]; + } + } + + if ([unixPathNext isEqualToString:unixPathPrev]) + pathsEqual = YES; + } + + if (pathsEqual || ([[nextStream scheme] isEqualToString:[[lastChain streamURL] scheme]] && (([nextStream host] == nil && [[lastChain streamURL] host] == nil) || [[nextStream host] isEqualToString:[[lastChain streamURL] host]]) - && [[nextStream path] isEqualToString:[[lastChain streamURL] path]]) + && [[nextStream path] isEqualToString:[[lastChain streamURL] path]])) { if ([lastChain setTrack:nextStream] && [newChain openWithInput:[lastChain inputNode] withOutputFormat:[output format] withRGInfo:nextStreamRGInfo]) diff --git a/Audio/Plugin.h b/Audio/Plugin.h index 1139da8cf..a4e003251 100644 --- a/Audio/Plugin.h +++ b/Audio/Plugin.h @@ -88,9 +88,9 @@ - (id) audioSourceForURL:(NSURL *)url; - (NSArray *) urlsForContainerURL:(NSURL *)url; -- (NSDictionary *) metadataForURL:(NSURL *)url; +- (NSDictionary *) metadataForURL:(NSURL *)url skipCue:(BOOL)skip; - (NSDictionary *) propertiesForURL:(NSURL *)url; -- (id) audioDecoderForSource:(id)source; +- (id) audioDecoderForSource:(id)source skipCue:(BOOL)skip; - (int) putMetadataInURL:(NSURL *)url; @end diff --git a/Audio/PluginController.m b/Audio/PluginController.m index 543f03d5c..96460eafb 100644 --- a/Audio/PluginController.m +++ b/Audio/PluginController.m @@ -296,13 +296,25 @@ static PluginController *sharedPluginController = nil; } //Note: Source is assumed to already be opened. -- (id) audioDecoderForSource:(id )source +- (id) audioDecoderForSource:(id )source skipCue:(BOOL)skip { NSString *ext = [[source url] pathExtension]; NSArray *decoders = [decodersByExtension objectForKey:[ext lowercaseString]]; NSString *classString; if (decoders) { if ( [decoders count] > 1 ) { + if (skip) + { + NSMutableArray * _decoders = [decoders mutableCopy]; + for (int i = 0; i < [_decoders count];) + { + if ([[_decoders objectAtIndex:i] isEqualToString:@"CueSheetDecoder"]) + [_decoders removeObjectAtIndex:i]; + else + ++i; + } + return [[CogDecoderMulti alloc] initWithDecoders:_decoders]; + } return [[CogDecoderMulti alloc] initWithDecoders:decoders]; } else { @@ -329,7 +341,7 @@ static PluginController *sharedPluginController = nil; return [[decoder alloc] init]; } -- (NSDictionary *)metadataForURL:(NSURL *)url +- (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip { NSString * urlScheme = [url scheme]; if ([urlScheme isEqualToString:@"http"] || @@ -341,6 +353,18 @@ static PluginController *sharedPluginController = nil; NSString *classString; if (readers) { if ( [readers count] > 1 ) { + if (skip) + { + NSMutableArray *_readers = [readers mutableCopy]; + for (int i = 0; i < [_readers count];) + { + if ([[_readers objectAtIndex:i] isEqualToString:@"CueSheetMetadataReader"]) + [_readers removeObjectAtIndex:i]; + else + ++i; + } + return [CogMetadataReaderMulti metadataForURL:url readers:_readers]; + } return [CogMetadataReaderMulti metadataForURL:url readers:readers]; } else { @@ -410,7 +434,7 @@ static PluginController *sharedPluginController = nil; } { - id decoder = [self audioDecoderForSource:source]; + id decoder = [self audioDecoderForSource:source skipCue:NO]; if (![decoder open:source]) { return nil; diff --git a/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp b/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp index bd9ece44b..e10b5fabb 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp +++ b/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp @@ -174,6 +174,13 @@ unsigned int APE::Tag::track() const return d->itemListMap["TRACK"].toString().toInt(); } +String APE::Tag::cuesheet() const +{ + if(d->itemListMap["CUESHEET"].isEmpty()) + return String(); + return d->itemListMap["CUESHEET"].toString(); +} + float APE::Tag::rgAlbumGain() const { if (d->itemListMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty()) @@ -248,6 +255,10 @@ void APE::Tag::setTrack(unsigned int i) addValue("TRACK", String::number(i), true); } +void APE::Tag::setCuesheet(const String &) +{ +} + void APE::Tag::setRGAlbumGain(float f) { if (f == 0) diff --git a/Frameworks/TagLib/taglib/taglib/ape/apetag.h b/Frameworks/TagLib/taglib/taglib/ape/apetag.h index 76f0a365c..f86d7eb1c 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apetag.h +++ b/Frameworks/TagLib/taglib/taglib/ape/apetag.h @@ -95,6 +95,7 @@ namespace TagLib { virtual String genre() const; virtual unsigned int year() const; virtual unsigned int track() const; + virtual String cuesheet() const; virtual float rgAlbumGain() const; virtual float rgAlbumPeak() const; virtual float rgTrackGain() const; @@ -108,6 +109,7 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(unsigned int i); virtual void setTrack(unsigned int i); + virtual void setCuesheet(const String &s); virtual void setRGAlbumGain(float f); virtual void setRGAlbumPeak(float f); virtual void setRGTrackGain(float f); diff --git a/Frameworks/TagLib/taglib/taglib/asf/asftag.cpp b/Frameworks/TagLib/taglib/taglib/asf/asftag.cpp index cd3fc1379..95d15f9a7 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asftag.cpp +++ b/Frameworks/TagLib/taglib/taglib/asf/asftag.cpp @@ -115,6 +115,11 @@ String ASF::Tag::genre() const return String(); } +String ASF::Tag::cuesheet() const +{ + return String(); +} + float ASF::Tag::rgAlbumGain() const { return 0; @@ -184,6 +189,10 @@ void ASF::Tag::setTrack(unsigned int value) setAttribute("WM/TrackNumber", String::number(value)); } +void ASF::Tag::setCuesheet(const String &) +{ +} + void ASF::Tag::setRGAlbumGain(float) { } diff --git a/Frameworks/TagLib/taglib/taglib/asf/asftag.h b/Frameworks/TagLib/taglib/taglib/asf/asftag.h index fe48f287e..cfd00cd3b 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asftag.h +++ b/Frameworks/TagLib/taglib/taglib/asf/asftag.h @@ -103,6 +103,8 @@ namespace TagLib { */ virtual unsigned int track() const; + virtual String cuesheet() const; + virtual float rgAlbumGain() const; virtual float rgAlbumPeak() const; virtual float rgTrackGain() const; @@ -158,6 +160,8 @@ namespace TagLib { * Sets the track to \a i. If \a s is 0 then this value will be cleared. */ virtual void setTrack(unsigned int i); + + virtual void setCuesheet(const String &s); virtual void setRGAlbumGain(float f); virtual void setRGAlbumPeak(float f); diff --git a/Frameworks/TagLib/taglib/taglib/mod/modtag.cpp b/Frameworks/TagLib/taglib/taglib/mod/modtag.cpp index 705b36a79..1055955f0 100644 --- a/Frameworks/TagLib/taglib/taglib/mod/modtag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mod/modtag.cpp @@ -94,6 +94,11 @@ unsigned int Mod::Tag::track() const return 0; } +String Mod::Tag::cuesheet() const +{ + return String(); +} + float Mod::Tag::rgAlbumGain() const { return 0; @@ -153,6 +158,10 @@ void Mod::Tag::setTrack(unsigned int) { } +void Mod::Tag::setCuesheet(const String &) +{ +} + void Mod::Tag::setRGAlbumGain(float) { } diff --git a/Frameworks/TagLib/taglib/taglib/mod/modtag.h b/Frameworks/TagLib/taglib/taglib/mod/modtag.h index 21d07b4fb..d24dd8d4c 100644 --- a/Frameworks/TagLib/taglib/taglib/mod/modtag.h +++ b/Frameworks/TagLib/taglib/taglib/mod/modtag.h @@ -93,6 +93,11 @@ namespace TagLib { * Not supported by module files. Therefore always returns 0. */ virtual unsigned int track() const; + + /*! + * Not supported by module files. Therefore always returns empty. + */ + virtual String cuesheet() const; /*! * Not supported by module files. Therefore always returns 0. @@ -182,6 +187,11 @@ namespace TagLib { */ virtual void setTrack(unsigned int track); + /*! + * Not supported by module files and therefore ignored. + */ + virtual void setCuesheet(const String &); + /*! * Not supported by module files and therefore ignored. */ diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.cpp b/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.cpp index 8c5d15e87..9bba688ac 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.cpp @@ -780,6 +780,12 @@ MP4::Tag::track() const return 0; } +String +MP4::Tag::cuesheet() const +{ + return String(); +} + float MP4::Tag::rgAlbumGain() const { @@ -872,6 +878,11 @@ MP4::Tag::setTrack(unsigned int value) } } +void +MP4::Tag::setCuesheet(const String &) +{ +} + void MP4::Tag::setRGAlbumGain(float f) { diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h b/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h index be5d21b22..e2dbd9f14 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h @@ -61,10 +61,11 @@ namespace TagLib { virtual String genre() const; virtual unsigned int year() const; virtual unsigned int track() const; - float rgAlbumGain() const; - float rgAlbumPeak() const; - float rgTrackGain() const; - float rgTrackPeak() const; + virtual String cuesheet() const; + virtual float rgAlbumGain() const; + virtual float rgAlbumPeak() const; + virtual float rgTrackGain() const; + virtual float rgTrackPeak() const; virtual void setTitle(const String &value); virtual void setAlbumArtist(const String &value); @@ -74,10 +75,11 @@ namespace TagLib { virtual void setGenre(const String &value); virtual void setYear(unsigned int value); virtual void setTrack(unsigned int value); - void setRGAlbumGain(float); - void setRGAlbumPeak(float); - void setRGTrackGain(float); - void setRGTrackPeak(float); + virtual void setCuesheet(const String &value); + virtual void setRGAlbumGain(float); + virtual void setRGAlbumPeak(float); + virtual void setRGTrackGain(float); + virtual void setRGTrackPeak(float); virtual bool isEmpty() const; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp index 9bfd7d8d3..f91b4df0c 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp @@ -167,6 +167,11 @@ unsigned int ID3v1::Tag::track() const return d->track; } +String ID3v1::Tag::cuesheet() const +{ + return String(); +} + float ID3v1::Tag::rgAlbumGain() const { return 0; @@ -226,6 +231,10 @@ void ID3v1::Tag::setTrack(unsigned int i) d->track = i < 256 ? i : 0; } +void ID3v1::Tag::setCuesheet(const String &) +{ +} + void ID3v1::Tag::setRGAlbumGain(float) { } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h index e963f5902..f35765f4f 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h @@ -143,6 +143,7 @@ namespace TagLib { virtual String genre() const; virtual unsigned int year() const; virtual unsigned int track() const; + virtual String cuesheet() const; virtual float rgAlbumGain() const; virtual float rgAlbumPeak() const; virtual float rgTrackGain() const; @@ -156,6 +157,7 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(unsigned int i); virtual void setTrack(unsigned int i); + virtual void setCuesheet(const String &s); virtual void setRGAlbumGain(float f); virtual void setRGAlbumPeak(float f); virtual void setRGTrackGain(float f); diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp index c6b936ecc..47c431002 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp @@ -245,6 +245,26 @@ unsigned int ID3v2::Tag::track() const return 0; } +String ID3v2::Tag::cuesheet() const +{ + const FrameList &list = d->frameListMap["TXXX"]; + if (!list.isEmpty()) { + for (FrameList::ConstIterator it = list.begin(); it != list.end(); ++it) { + UserTextIdentificationFrame const* frame = static_cast(*it); + if (!frame->description().isNull() && frame->description() == "cuesheet") { + // Remove description + StringList l = frame->fieldList(); + for(StringList::Iterator it = l.begin(); it != l.end(); ++it) { + l.erase(it); + break; + } + return l.toString(); + } + } + } + return String(); +} + float ID3v2::Tag::rg(const String &type) const { const FrameList &list = d->frameListMap["TXXX"]; @@ -376,6 +396,32 @@ void ID3v2::Tag::setTrack(unsigned int i) setTextFrame("TRCK", String::number(i)); } +void ID3v2::Tag::setCuesheet(const String &s) +{ + bool createdFrame = false; + UserTextIdentificationFrame * frame = NULL; + FrameList &list = d->frameListMap["TXXX"]; + for (FrameList::Iterator it = list.begin(); it != list.end(); ++it) { + if (static_cast(*it)->description() == "cuesheet") { + frame = static_cast(*it); + break; + } + } + if (s.isEmpty()) { + if (frame) + removeFrame(frame); + return; + } + if (frame == NULL) { + frame = new UserTextIdentificationFrame; + frame->setDescription("cuesheet"); + createdFrame = true; + } + frame->setText(s); + if (createdFrame) + addFrame(frame); +} + void ID3v2::Tag::setRG(const String &type, float f, bool peak) { bool createdFrame = false; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h index 1cc95687a..1fd96073d 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h @@ -164,6 +164,8 @@ namespace TagLib { virtual String genre() const; virtual unsigned int year() const; virtual unsigned int track() const; + + virtual String cuesheet() const; float rg(const String &type) const; virtual float rgAlbumGain() const; @@ -179,6 +181,8 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(unsigned int i); virtual void setTrack(unsigned int i); + + virtual void setCuesheet(const String &s); void setRG(const String &type, float f, bool peak); virtual void setRGAlbumGain(float f); diff --git a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp index b8c70ef17..7e0f2fbfd 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp @@ -148,6 +148,13 @@ unsigned int Ogg::XiphComment::track() const return 0; } +String Ogg::XiphComment::cuesheet() const +{ + if(d->fieldListMap["CUESHEET"].isEmpty()) + return String(); + return d->fieldListMap["CUESHEET"].front(); +} + float Ogg::XiphComment::rgAlbumGain() const { if(d->fieldListMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty()) @@ -231,6 +238,14 @@ void Ogg::XiphComment::setTrack(unsigned int i) addField("TRACKNUMBER", String::number(i)); } +void Ogg::XiphComment::setCuesheet(const String &s) +{ + if (s.isEmpty()) + removeField("CUESHEET"); + else + addField("CUESHEET", s); +} + void Ogg::XiphComment::setRGAlbumGain(float f) { if (f == 0) diff --git a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h index 6a583a39b..50906f473 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h @@ -88,6 +88,7 @@ namespace TagLib { virtual String genre() const; virtual unsigned int year() const; virtual unsigned int track() const; + virtual String cuesheet() const; virtual float rgAlbumGain() const; virtual float rgAlbumPeak() const; virtual float rgTrackGain() const; @@ -101,6 +102,7 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(unsigned int i); virtual void setTrack(unsigned int i); + virtual void setCuesheet(const String &s); virtual void setRGAlbumGain(float f); virtual void setRGAlbumPeak(float f); virtual void setRGTrackGain(float f); diff --git a/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.cpp b/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.cpp index 8bdf8ef9c..65420b1a0 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.cpp +++ b/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.cpp @@ -128,6 +128,11 @@ unsigned int RIFF::Info::Tag::track() const return fieldText("IPRT").toInt(); } +String RIFF::Info::Tag::cuesheet() const +{ + return String(); +} + float RIFF::Info::Tag::rgAlbumGain() const { return 0; @@ -193,6 +198,10 @@ void RIFF::Info::Tag::setTrack(unsigned int i) d->fieldListMap.erase("IPRT"); } +void RIFF::Info::Tag::setCuesheet(const String &) +{ +} + void RIFF::Info::Tag::setRGAlbumGain(float) { } diff --git a/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.h b/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.h index 1d9ef8ef9..d3a91a951 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.h +++ b/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.h @@ -110,6 +110,7 @@ namespace TagLib { virtual String genre() const; virtual unsigned int year() const; virtual unsigned int track() const; + virtual String cuesheet() const; virtual float rgAlbumGain() const; virtual float rgAlbumPeak() const; virtual float rgTrackGain() const; @@ -123,6 +124,7 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(unsigned int i); virtual void setTrack(unsigned int i); + virtual void setCuesheet(const String &s); virtual void setRGAlbumGain(float f); virtual void setRGAlbumPeak(float f); virtual void setRGTrackGain(float f); diff --git a/Frameworks/TagLib/taglib/taglib/tag.h b/Frameworks/TagLib/taglib/taglib/tag.h index fd5422dcd..2bcfbadc0 100644 --- a/Frameworks/TagLib/taglib/taglib/tag.h +++ b/Frameworks/TagLib/taglib/taglib/tag.h @@ -125,6 +125,12 @@ namespace TagLib { */ virtual unsigned int track() const = 0; + /*! + * Returns the embedded cuesheet; if there is no cuesheet set, this + * will return an empty string. + */ + virtual String cuesheet() const = 0; + /*! * Returns the ReplayGain album gain; if there is no gain level set, this * will return 0. @@ -198,6 +204,12 @@ namespace TagLib { */ virtual void setTrack(unsigned int i) = 0; + /*! + * Sets the embedded cuesheet to \a s. If \a s is empty then this + * value will be cleared. + */ + virtual void setCuesheet(const String &s) = 0; + /*! * Sets the ReplayGain album gain to \a f. If \a f is 0 then this value will * be cleared. diff --git a/Frameworks/TagLib/taglib/taglib/tagunion.cpp b/Frameworks/TagLib/taglib/taglib/tagunion.cpp index 213192cf5..11171ef65 100644 --- a/Frameworks/TagLib/taglib/taglib/tagunion.cpp +++ b/Frameworks/TagLib/taglib/taglib/tagunion.cpp @@ -213,6 +213,11 @@ unsigned int TagUnion::track() const numberUnion(track); } +String TagUnion::cuesheet() const +{ + stringUnion(cuesheet); +} + float TagUnion::rgAlbumGain() const { floatUnion(rgAlbumGain); @@ -273,6 +278,11 @@ void TagUnion::setTrack(unsigned int i) setUnion(Track, i); } +void TagUnion::setCuesheet(const String &s) +{ + setUnion(Cuesheet, s); +} + void TagUnion::setRGAlbumGain(float f) { setUnion(RGAlbumGain, f); diff --git a/Frameworks/TagLib/taglib/taglib/tagunion.h b/Frameworks/TagLib/taglib/taglib/tagunion.h index 30514430b..c5317b74d 100644 --- a/Frameworks/TagLib/taglib/taglib/tagunion.h +++ b/Frameworks/TagLib/taglib/taglib/tagunion.h @@ -67,6 +67,7 @@ namespace TagLib { virtual String genre() const; virtual unsigned int year() const; virtual unsigned int track() const; + virtual String cuesheet() const; virtual float rgAlbumGain() const; virtual float rgAlbumPeak() const; virtual float rgTrackGain() const; @@ -80,6 +81,7 @@ namespace TagLib { virtual void setGenre(const String &s); virtual void setYear(unsigned int i); virtual void setTrack(unsigned int i); + virtual void setCuesheet(const String &s); virtual void setRGAlbumGain(float f); virtual void setRGAlbumPeak(float f); virtual void setRGTrackGain(float f); diff --git a/Playlist/PlaylistEntry.h b/Playlist/PlaylistEntry.h index 57ef38446..f8b3c92de 100644 --- a/Playlist/PlaylistEntry.h +++ b/Playlist/PlaylistEntry.h @@ -34,6 +34,8 @@ NSNumber *year; NSNumber *track; + NSString *cuesheet; + NSData *albumArtInternal; float replayGainAlbumGain; @@ -109,6 +111,8 @@ @property(retain) NSNumber *year; @property(retain) NSNumber *track; +@property(retain) NSString *cuesheet; + @property(retain, readonly) NSImage *albumArt; @property(retain) NSData *albumArtInternal; diff --git a/Playlist/PlaylistEntry.m b/Playlist/PlaylistEntry.m index 743a342ab..edbfc60f2 100644 --- a/Playlist/PlaylistEntry.m +++ b/Playlist/PlaylistEntry.m @@ -35,6 +35,8 @@ @synthesize year; @synthesize track; +@synthesize cuesheet; + @synthesize totalFrames; @synthesize bitrate; @synthesize channels; diff --git a/Plugins/APL/APLDecoder.m b/Plugins/APL/APLDecoder.m index d7fbbb122..aa3346842 100644 --- a/Plugins/APL/APLDecoder.m +++ b/Plugins/APL/APLDecoder.m @@ -42,7 +42,7 @@ ALog(@"Could not open source for file '%@' referenced in apl", [apl file]); return NO; } - decoder = [NSClassFromString(@"AudioDecoder") audioDecoderForSource:source]; + decoder = [NSClassFromString(@"AudioDecoder") audioDecoderForSource:source skipCue:YES]; if (![decoder open:source]) { ALog(@"Could not open decoder for source for apl"); diff --git a/Plugins/CueSheet/CueSheet.h b/Plugins/CueSheet/CueSheet.h index 44d562b9d..295c26504 100644 --- a/Plugins/CueSheet/CueSheet.h +++ b/Plugins/CueSheet/CueSheet.h @@ -15,10 +15,13 @@ } + (id)cueSheetWithFile: (NSString *)filename; ++ (id)cueSheetWithString: (NSString *)cuesheet withFilename:(NSString *)filename; - (id)initWithFile:(NSString *)filename; +- (id)initWithString:(NSString *)cuesheet withFilename:(NSString *)filename; - (void)parseFile:(NSString *)filename; +- (void)parseString:(NSString *)contents withFilename:(NSString *)filename; - (NSArray *)tracks; diff --git a/Plugins/CueSheet/CueSheet.m b/Plugins/CueSheet/CueSheet.m index 9a37cd612..d29182d27 100644 --- a/Plugins/CueSheet/CueSheet.m +++ b/Plugins/CueSheet/CueSheet.m @@ -20,6 +20,11 @@ return [[CueSheet alloc] initWithFile:filename]; } ++ (id)cueSheetWithString:(NSString *)cuesheet withFilename:(NSString *)filename +{ + return [[CueSheet alloc] initWithString:cuesheet withFilename:filename]; +} + - (NSURL *)urlForPath:(NSString *)path relativeTo:(NSString *)baseFilename { NSRange protocolRange = [path rangeOfString:@"://"]; @@ -60,35 +65,37 @@ return url; } - - - - (void)parseFile:(NSString *)filename { - NSStringEncoding encoding; - NSError *error = nil; - NSString *contents = [NSString stringWithContentsOfFile:filename usedEncoding:&encoding error:&error]; + NSStringEncoding encoding; + NSError *error = nil; + NSString *contents = [NSString stringWithContentsOfFile:filename usedEncoding:&encoding error:&error]; if (error) { error = nil; contents = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:&error]; } - if (error) { - error = nil; - contents = [NSString stringWithContentsOfFile:filename encoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000) error:&error]; - } + if (error) { + error = nil; + contents = [NSString stringWithContentsOfFile:filename encoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000) error:&error]; + } if (error) { error = nil; contents = [NSString stringWithContentsOfFile:filename encoding:NSWindowsCP1251StringEncoding error:&error]; - } + } if (error) { error = nil; contents = [NSString stringWithContentsOfFile:filename encoding:NSISOLatin1StringEncoding error:&error]; - } - if (error || !contents) { - ALog(@"Could not open file...%@ %@ %@", filename, contents, error); - return; - } - + } + if (error || !contents) { + ALog(@"Could not open file...%@ %@ %@", filename, contents, error); + return; + } + + [self parseString:contents withFilename:filename]; +} + +- (void)parseString:(NSString *)contents withFilename:(NSString *)filename +{ NSMutableArray *entries = [[NSMutableArray alloc] init]; NSString *track = nil; @@ -262,6 +269,16 @@ return self; } +- (id)initWithString:(NSString *)cuesheet withFilename:(NSString *)filename +{ + self = [super init]; + if (self) { + [self parseString:cuesheet withFilename:filename]; + } + + return self; +} + - (NSArray *)tracks { return tracks; diff --git a/Plugins/CueSheet/CueSheetContainer.m b/Plugins/CueSheet/CueSheetContainer.m index 14e19bbd0..e12ef4ed7 100644 --- a/Plugins/CueSheet/CueSheetContainer.m +++ b/Plugins/CueSheet/CueSheetContainer.m @@ -11,11 +11,13 @@ #import "CueSheet.h" #import "CueSheetTrack.h" +#import "AudioMetadataReader.h" + @implementation CueSheetContainer + (NSArray *)fileTypes { - return [NSArray arrayWithObject:@"cue"]; + return [NSArray arrayWithObjects:@"cue", @"ogg", @"opus", @"flac", @"wv", @"mp3", nil]; } + (NSArray *)mimeTypes @@ -25,7 +27,7 @@ + (float)priority { - return 1.0f; + return 16.0f; } + (NSArray *)urlsForContainerURL:(NSURL *)url @@ -40,8 +42,28 @@ } NSMutableArray *tracks = [NSMutableArray array]; - - CueSheet *cuesheet = [CueSheet cueSheetWithFile:[url path]]; + + BOOL embedded = NO; + CueSheet *cuesheet = nil; + NSDictionary * fileMetadata; + + NSString *ext = [url pathExtension]; + if ([ext caseInsensitiveCompare:@"cue"] != NSOrderedSame) + { + // Embedded cuesheet check + fileMetadata = [NSClassFromString(@"AudioMetadataReader") metadataForURL:url skipCue:YES]; + NSString * sheet = [fileMetadata objectForKey:@"cuesheet"]; + if ([sheet length]) + { + cuesheet = [CueSheet cueSheetWithString:sheet withFilename:[url path]]; + } + embedded = YES; + } + else + cuesheet = [CueSheet cueSheetWithFile:[url path]]; + + if (!cuesheet) + return embedded ? [NSMutableArray arrayWithObject:url] : tracks; for (CueSheetTrack *track in [cuesheet tracks]) { [tracks addObject:[NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:@"#%@", [track track]]]]; diff --git a/Plugins/CueSheet/CueSheetDecoder.h b/Plugins/CueSheet/CueSheetDecoder.h index 00b1c7702..efa5df530 100644 --- a/Plugins/CueSheet/CueSheetDecoder.h +++ b/Plugins/CueSheet/CueSheetDecoder.h @@ -23,6 +23,10 @@ long trackStart; //Starting frame of track. long trackEnd; //Frames until end of track. + + BOOL embedded; + BOOL noFragment; + NSURL *baseURL; CueSheet *cuesheet; CueSheetTrack *track; diff --git a/Plugins/CueSheet/CueSheetDecoder.m b/Plugins/CueSheet/CueSheetDecoder.m index 18b42ecad..1c721ae1a 100644 --- a/Plugins/CueSheet/CueSheetDecoder.m +++ b/Plugins/CueSheet/CueSheetDecoder.m @@ -49,60 +49,122 @@ NSURL *url = [s url]; - cuesheet = [CueSheet cueSheetWithFile:[url path]]; + embedded = NO; + cuesheet = nil; + NSDictionary * fileMetadata; + + noFragment = NO; + + NSString *ext = [url pathExtension]; + if ([ext caseInsensitiveCompare:@"cue"] != NSOrderedSame) + { + // Embedded cuesheet check + fileMetadata = [NSClassFromString(@"AudioMetadataReader") metadataForURL:url skipCue:YES]; + NSString * sheet = [fileMetadata objectForKey:@"cuesheet"]; + if ([sheet length]) + { + cuesheet = [CueSheet cueSheetWithString:sheet withFilename:[url path]]; + embedded = YES; + } + + baseURL = url; + + NSString *fragment = [url fragment]; + if (!fragment || [fragment isEqualToString:@""]) + noFragment = YES; + } + else + cuesheet = [CueSheet cueSheetWithFile:[url path]]; - NSArray *tracks = [cuesheet tracks]; - int i; - for (i = 0; i < [tracks count]; i++) - { - if ([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]){ - track = [tracks objectAtIndex:i]; + if (!noFragment) + { + NSArray *tracks = [cuesheet tracks]; + int i; + for (i = 0; i < [tracks count]; i++) + { + if ([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]){ + track = [tracks objectAtIndex:i]; - //Kind of a hackish way of accessing outside classes. - source = [NSClassFromString(@"AudioSource") audioSourceForURL:[track url]]; + NSURL *trackUrl = (embedded) ? baseURL : [track url]; - if (![source open:[track url]]) { - ALog(@"Could not open cuesheet source"); - return NO; - } + //Kind of a hackish way of accessing outside classes. + source = [NSClassFromString(@"AudioSource") audioSourceForURL:trackUrl]; - decoder = [NSClassFromString(@"AudioDecoder") audioDecoderForSource:source]; + if (![source open:url]) { + ALog(@"Could not open cuesheet source"); + return NO; + } - if (![decoder open:source]) { - ALog(@"Could not open cuesheet decoder"); - return NO; - } + decoder = [NSClassFromString(@"AudioDecoder") audioDecoderForSource:source skipCue:YES]; - CueSheetTrack *nextTrack = nil; - if (i + 1 < [tracks count]) { - nextTrack = [tracks objectAtIndex:i + 1]; - } + if (![decoder open:source]) { + ALog(@"Could not open cuesheet decoder"); + return NO; + } - NSDictionary *properties = [decoder properties]; - int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue]; - int channels = [[properties objectForKey:@"channels"] intValue]; - float sampleRate = [[properties objectForKey:@"sampleRate"] floatValue]; + CueSheetTrack *nextTrack = nil; + if (i + 1 < [tracks count]) { + nextTrack = [tracks objectAtIndex:i + 1]; + } - bytesPerFrame = (bitsPerSample/8) * channels; + NSDictionary *properties = [decoder properties]; + int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue]; + int channels = [[properties objectForKey:@"channels"] intValue]; + float sampleRate = [[properties objectForKey:@"sampleRate"] floatValue]; + + bytesPerFrame = (bitsPerSample/8) * channels; - trackStart = [track time] * sampleRate; + trackStart = [track time] * sampleRate; - if (nextTrack && [[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]]) { - trackEnd = [nextTrack time] * sampleRate; - } - else { - trackEnd = [[properties objectForKey:@"totalFrames"] doubleValue]; - } + if (nextTrack && (embedded || ([[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]]))) { + trackEnd = [nextTrack time] * sampleRate; + } + else { + trackEnd = [[properties objectForKey:@"totalFrames"] doubleValue]; + } - [self seek: 0]; + [self seek: 0]; - //Note: Should register for observations of the decoder - [self willChangeValueForKey:@"properties"]; - [self didChangeValueForKey:@"properties"]; + //Note: Should register for observations of the decoder + [self willChangeValueForKey:@"properties"]; + [self didChangeValueForKey:@"properties"]; - return YES; - } - } + return YES; + } + } + } + else { + NSURL *trackUrl = (embedded) ? baseURL : [track url]; + + //Kind of a hackish way of accessing outside classes. + source = [NSClassFromString(@"AudioSource") audioSourceForURL:trackUrl]; + + if (![source open:url]) { + ALog(@"Could not open cuesheet source"); + return NO; + } + + decoder = [NSClassFromString(@"AudioDecoder") audioDecoderForSource:source skipCue:YES]; + + if (![decoder open:source]) { + ALog(@"Could not open cuesheet decoder"); + return NO; + } + + NSDictionary *properties = [decoder properties]; + int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue]; + int channels = [[properties objectForKey:@"channels"] intValue]; + + bytesPerFrame = (bitsPerSample/8) * channels; + + trackStart = 0; + + trackEnd = [[properties objectForKey:@"totalFrames"] doubleValue]; + + [self seek: 0]; + + return YES; + } return NO; } @@ -124,8 +186,12 @@ - (BOOL)setTrack:(NSURL *)url { + // handling the file directly + if (noFragment) + return NO; + //Same file, just next track...this may be unnecessary since frame-based decoding is done now... - if ([[[track url] path] isEqualToString:[url path]] && [[[track url] host] isEqualToString:[url host]] && [[url fragment] intValue] == [[track track] intValue] + 1) { + if (embedded || ([[[track url] path] isEqualToString:[url path]] && [[[track url] host] isEqualToString:[url host]] && [[url fragment] intValue] == [[track track] intValue] + 1)) { NSArray *tracks = [cuesheet tracks]; int i; @@ -142,12 +208,15 @@ nextTrack = [tracks objectAtIndex:i + 1]; } - if (nextTrack && [[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]]) { + if (nextTrack && (embedded || [[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]])) { trackEnd = [nextTrack time] * sampleRate; } else { trackEnd = [[[decoder properties] objectForKey:@"totalFrames"] longValue]; } + + if (embedded) + [self seek:0]; DLog(@"CHANGING TRACK!"); return YES; diff --git a/Plugins/CueSheet/CueSheetMetadataReader.m b/Plugins/CueSheet/CueSheetMetadataReader.m index 651811acc..1694a1ad1 100644 --- a/Plugins/CueSheet/CueSheetMetadataReader.m +++ b/Plugins/CueSheet/CueSheetMetadataReader.m @@ -29,7 +29,7 @@ + (float)priority { - return 1.0f; + return 16.0f; } + (NSDictionary *)metadataForURL:(NSURL *)url @@ -37,8 +37,30 @@ if (![url isFileURL]) { return nil; } - - CueSheet *cuesheet = [CueSheet cueSheetWithFile:[url path]]; + + BOOL embedded = NO; + CueSheet *cuesheet = nil; + NSDictionary * fileMetadata; + + Class audioMetadataReader = NSClassFromString(@"AudioMetadataReader"); + + NSString *ext = [url pathExtension]; + if ([ext caseInsensitiveCompare:@"cue"] != NSOrderedSame) + { + // Embedded cuesheet check + fileMetadata = [audioMetadataReader metadataForURL:url skipCue:YES]; + NSString * sheet = [fileMetadata objectForKey:@"cuesheet"]; + if ([sheet length]) + { + cuesheet = [CueSheet cueSheetWithString:sheet withFilename:[url path]]; + embedded = YES; + } + } + else + cuesheet = [CueSheet cueSheetWithFile:[url path]]; + + if (!cuesheet) + return nil; NSArray *tracks = [cuesheet tracks]; for (CueSheetTrack *track in tracks) @@ -46,7 +68,8 @@ if ([[url fragment] isEqualToString:[track track]]) { // Class supplied by CogAudio, which is guaranteed to be present - NSDictionary * fileMetadata = [NSClassFromString(@"AudioMetadataReader") metadataForURL:[track url]]; + if (!embedded) + fileMetadata = [audioMetadataReader metadataForURL:[track url] skipCue:YES]; NSDictionary * cuesheetMetadata = [NSDictionary dictionaryWithObjectsAndKeys: [track artist], @"artist", [track album], @"album", diff --git a/Plugins/TagLib/TagLibMetadataReader.m b/Plugins/TagLib/TagLibMetadataReader.m index 676392629..bdb548528 100644 --- a/Plugins/TagLib/TagLibMetadataReader.m +++ b/Plugins/TagLib/TagLibMetadataReader.m @@ -74,6 +74,7 @@ TagLib::String artist, albumartist, title, album, genre, comment; int year, track; float rgAlbumGain, rgAlbumPeak, rgTrackGain, rgTrackPeak; + TagLib::String cuesheet; artist = tag->artist(); albumartist = tag->albumartist(); @@ -81,6 +82,7 @@ album = tag->album(); genre = tag->genre(); comment = tag->comment(); + cuesheet = tag->cuesheet(); year = tag->year(); [dict setObject:[NSNumber numberWithInt:year] forKey:@"year"]; @@ -111,6 +113,9 @@ if (!genre.isEmpty()) [dict setObject:[NSString stringWithUTF8String:genre.toCString(true)] forKey:@"genre"]; + + if (!cuesheet.isEmpty()) + [dict setObject:[NSString stringWithUTF8String:cuesheet.toCString(true)] forKey:@"cuesheet"]; } // Try to load the image.