Implemented basic embedded CueSheet support

CQTexperiment
Christopher Snowhill 2022-01-14 16:46:35 -08:00
parent 6315377eaf
commit d24a01a637
36 changed files with 482 additions and 86 deletions

View File

@ -14,5 +14,6 @@
}
+ (id<CogDecoder>)audioDecoderForSource:(id <CogSource>)source;
+ (id<CogDecoder>)audioDecoderForSource:(id <CogSource>)source skipCue:(BOOL)skip;
@end

View File

@ -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

View File

@ -14,5 +14,6 @@
}
+ (NSDictionary *)metadataForURL:(NSURL *)url;
+ (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip;
@end

View File

@ -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];
}
}

View File

@ -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])

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -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)
{
}

View File

@ -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);

View File

@ -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)
{
}

View File

@ -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.
*/

View File

@ -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)
{

View File

@ -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;

View File

@ -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)
{
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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)
{
}

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -35,6 +35,8 @@
@synthesize year;
@synthesize track;
@synthesize cuesheet;
@synthesize totalFrames;
@synthesize bitrate;
@synthesize channels;

View File

@ -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");

View File

@ -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;

View File

@ -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;

View File

@ -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]]]];

View File

@ -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;

View File

@ -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;

View File

@ -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",

View File

@ -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.