Implemented basic embedded CueSheet support
parent
6315377eaf
commit
d24a01a637
|
@ -14,5 +14,6 @@
|
|||
}
|
||||
|
||||
+ (id<CogDecoder>)audioDecoderForSource:(id <CogSource>)source;
|
||||
+ (id<CogDecoder>)audioDecoderForSource:(id <CogSource>)source skipCue:(BOOL)skip;
|
||||
|
||||
@end
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
|
||||
+ (id<CogDecoder>) audioDecoderForSource:(id <CogSource>)source
|
||||
{
|
||||
return [[PluginController sharedPluginController] audioDecoderForSource:source];
|
||||
return [[PluginController sharedPluginController] audioDecoderForSource:source skipCue:NO];
|
||||
}
|
||||
|
||||
+ (id<CogDecoder>) audioDecoderForSource:(id <CogSource>)source skipCue:(BOOL)skip
|
||||
{
|
||||
return [[PluginController sharedPluginController] audioDecoderForSource:source skipCue:skip];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -14,5 +14,6 @@
|
|||
}
|
||||
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url;
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip;
|
||||
|
||||
@end
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -88,9 +88,9 @@
|
|||
|
||||
- (id<CogSource>) audioSourceForURL:(NSURL *)url;
|
||||
- (NSArray *) urlsForContainerURL:(NSURL *)url;
|
||||
- (NSDictionary *) metadataForURL:(NSURL *)url;
|
||||
- (NSDictionary *) metadataForURL:(NSURL *)url skipCue:(BOOL)skip;
|
||||
- (NSDictionary *) propertiesForURL:(NSURL *)url;
|
||||
- (id<CogDecoder>) audioDecoderForSource:(id<CogSource>)source;
|
||||
- (id<CogDecoder>) audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip;
|
||||
|
||||
- (int) putMetadataInURL:(NSURL *)url;
|
||||
@end
|
||||
|
|
|
@ -296,13 +296,25 @@ static PluginController *sharedPluginController = nil;
|
|||
}
|
||||
|
||||
//Note: Source is assumed to already be opened.
|
||||
- (id<CogDecoder>) audioDecoderForSource:(id <CogSource>)source
|
||||
- (id<CogDecoder>) audioDecoderForSource:(id <CogSource>)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<CogDecoder> decoder = [self audioDecoderForSource:source];
|
||||
id<CogDecoder> decoder = [self audioDecoderForSource:source skipCue:NO];
|
||||
if (![decoder open:source])
|
||||
{
|
||||
return nil;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<UserTextIdentificationFrame *>(*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<UserTextIdentificationFrame *>(*it)->description() == "cuesheet") {
|
||||
frame = static_cast<UserTextIdentificationFrame*>(*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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
@synthesize year;
|
||||
@synthesize track;
|
||||
|
||||
@synthesize cuesheet;
|
||||
|
||||
@synthesize totalFrames;
|
||||
@synthesize bitrate;
|
||||
@synthesize channels;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]]]];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue