cog/Plugins/CueSheet/CueSheet.m

315 lines
8.7 KiB
Objective-C

//
// CueSheet.m
// CueSheet
//
// Created by Zaphod Beeblebrox on 10/8/07.
// Copyright 2007 __MyCompanyName__. All rights reserved.
//
#import "CueSheet.h"
#import "CueSheetTrack.h"
#import "Plugin.h"
#import "Logging.h"
#import "SandboxBroker.h"
@implementation CueSheet
+ (id)cueSheetWithFile:(NSString *)filename {
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:@"://"];
if(protocolRange.location != NSNotFound) {
return [NSURL URLWithString:path];
}
NSMutableString *unixPath = [path mutableCopy];
// Get the fragment
NSString *fragment = @"";
NSScanner *scanner = [NSScanner scannerWithString:unixPath];
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"#1234567890"];
while(![scanner isAtEnd]) {
NSString *possibleFragment;
[scanner scanUpToString:@"#" intoString:nil];
if([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) {
fragment = possibleFragment;
[unixPath deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])];
break;
}
}
if(![unixPath hasPrefix:@"/"]) {
// Only relative paths would have windows backslashes.
[unixPath replaceOccurrencesOfString:@"\\" withString:@"/" options:0 range:NSMakeRange(0, [unixPath length])];
NSString *basePath = [[[baseFilename stringByStandardizingPath] stringByDeletingLastPathComponent] stringByAppendingString:@"/"];
[unixPath insertString:basePath atIndex:0];
}
// Append the fragment
NSURL *url = [NSURL URLWithString:[[[NSURL fileURLWithPath:unixPath] absoluteString] stringByAppendingString:fragment]];
return url;
}
- (void)parseFile:(NSString *)filename {
NSStringEncoding encoding;
NSError *error = nil;
id sandboxBrokerClass = NSClassFromString(@"SandboxBroker");
const void *sbHandle = [[sandboxBrokerClass sharedSandboxBroker] beginFolderAccess:[NSURL fileURLWithPath:filename]];
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:NSWindowsCP1251StringEncoding error:&error];
}
if(error) {
error = nil;
contents = [NSString stringWithContentsOfFile:filename encoding:NSISOLatin1StringEncoding error:&error];
}
[[sandboxBrokerClass sharedSandboxBroker] endFolderAccess:sbHandle];
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;
NSString *path = nil;
NSString *artist = nil;
NSString *album = nil;
NSString *title = nil;
NSString *genre = nil;
NSString *year = nil;
float albumGain = 0.0f;
float albumPeak = 0.0f;
float trackGain = 0.0f;
float trackPeak = 0.0f;
BOOL trackAdded = NO;
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSScanner *scanner = nil;
for(NSString *line in [contents componentsSeparatedByString:@"\n"]) {
scanner = [[NSScanner alloc] initWithString:line];
NSString *command;
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&command]) {
continue;
}
// FILE "filename.shn" WAVE
if([command isEqualToString:@"FILE"]) {
trackAdded = NO;
if(![scanner scanString:@"\"" intoString:nil]) {
continue;
}
// Read in the path
if(![scanner scanUpToString:@"\"" intoString:&path]) {
continue;
}
}
// TRACK 01 AUDIO
else if([command isEqualToString:@"TRACK"]) {
trackAdded = NO;
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&track]) {
continue;
}
NSString *type = nil;
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&type] || ![type isEqualToString:@"AUDIO"]) {
continue;
}
}
// INDEX 01 00:00:10
// Note that time is written in Minutes:Seconds:Frames, where frames are 1/75 of a second
else if([command isEqualToString:@"INDEX"]) {
if(trackAdded) {
continue;
}
if(!path) {
continue;
}
NSString *index = nil;
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&index] || [index intValue] != 1) {
continue;
}
[scanner scanCharactersFromSet:whitespace intoString:nil];
NSString *time = nil;
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&time]) {
continue;
}
NSArray *msf = [time componentsSeparatedByString:@":"];
size_t count = [msf count];
if(count != 1 && count != 3) {
continue;
}
double seconds;
BOOL timeInSamples = NO;
if(count == 1) {
seconds = [[msf objectAtIndex:0] floatValue];
timeInSamples = YES;
} else
seconds = (60 * [[msf objectAtIndex:0] intValue]) + [[msf objectAtIndex:1] intValue] + ([[msf objectAtIndex:2] floatValue] / 75);
if(track == nil) {
track = @"01";
}
// Need to add basePath, and convert to URL
[entries addObject:
[CueSheetTrack trackWithURL:[self urlForPath:path relativeTo:filename]
track:track
time:seconds
timeInSamples:timeInSamples
artist:artist
album:album
title:title
genre:genre
year:year
albumGain:albumGain
albumPeak:albumPeak
trackGain:trackGain
trackPeak:trackPeak]];
trackAdded = YES;
} else if([command isEqualToString:@"PERFORMER"]) {
if(![scanner scanString:@"\"" intoString:nil]) {
continue;
}
// Read in the path
if(![scanner scanUpToString:@"\"" intoString:&artist]) {
continue;
}
} else if([command isEqualToString:@"TITLE"]) {
NSString *titleDest;
if(![scanner scanString:@"\"" intoString:nil]) {
continue;
}
// Read in the path
if(![scanner scanUpToString:@"\"" intoString:&titleDest]) {
continue;
}
if(!path) // Have not come across a file yet.
album = titleDest;
else
title = titleDest;
} else if([command isEqualToString:@"REM"]) // Additional metadata sometimes stored in comments
{
NSString *type;
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&type]) {
continue;
}
if([type isEqualToString:@"GENRE"]) {
// DLog(@"GENRE!");
if([scanner scanString:@"\"" intoString:nil]) {
// DLog(@"QUOTED");
if(![scanner scanUpToString:@"\"" intoString:&genre]) {
DLog(@"FAILED TO SCAN");
continue;
}
} else {
// DLog(@"UNQUOTED");
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&genre]) {
continue;
}
}
} else if([type isEqualToString:@"DATE"]) {
// DLog(@"DATE!");
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&year]) {
continue;
}
} else if([type hasPrefix:@"REPLAYGAIN_"]) {
NSString *rgTag = nil;
if(![scanner scanUpToCharactersFromSet:whitespace intoString:&rgTag])
continue;
if([type hasPrefix:@"REPLAYGAIN_ALBUM_"]) {
if([type hasSuffix:@"GAIN"])
albumGain = [rgTag floatValue];
else if([type hasSuffix:@"PEAK"])
albumPeak = [rgTag floatValue];
} else if([type hasPrefix:@"REPLAYGAIN_TRACK_"]) {
if([type hasSuffix:@"GAIN"])
trackGain = [rgTag floatValue];
else if([type hasSuffix:@"PEAK"])
trackPeak = [rgTag floatValue];
}
}
}
}
tracks = [entries copy];
}
- (id)initWithFile:(NSString *)filename {
self = [super init];
if(self) {
[self parseFile:filename];
}
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;
}
- (CueSheetTrack *)track:(NSString *)fragment {
for(CueSheetTrack *t in tracks) {
if([[t track] isEqualToString:fragment]) {
return t;
}
}
return nil;
}
@end