cog/Plugins/CueSheet/CueSheetDecoder.m

335 lines
7.9 KiB
Matlab
Raw Normal View History

2007-10-09 02:25:40 +00:00
//
// CueSheetDecoder.m
// CueSheet
//
// Created by Zaphod Beeblebrox on 10/8/07.
// Copyright 2007 __MyCompanyName__. All rights reserved.
//
#import "CueSheetDecoder.h"
#import "CueSheet.h"
#import "CueSheetContainer.h"
#import "CueSheetTrack.h"
2007-10-09 02:25:40 +00:00
#import "Logging.h"
2007-10-09 02:25:40 +00:00
@implementation CueSheetDecoder
+ (NSArray *)fileTypes {
return [CueSheetContainer fileTypes];
2007-10-09 02:25:40 +00:00
}
+ (NSArray *)mimeTypes {
2007-10-14 18:56:23 +00:00
return [CueSheetContainer mimeTypes];
2007-10-14 18:39:58 +00:00
}
+ (float)priority {
return 16.0f;
}
+ (NSArray *)fileTypeAssociations {
return @[
@[@"CUE Sheet File", @"cue.icns", @"cue"]
];
}
- (NSDictionary *)properties {
NSMutableDictionary *properties = [[decoder properties] mutableCopy];
// Need to alter length
if(!noFragment)
[properties setObject:[NSNumber numberWithLong:(trackEnd - trackStart)] forKey:@"totalFrames"];
return [NSDictionary dictionaryWithDictionary:properties];
}
- (NSDictionary *)metadata {
if(decoder != nil)
return [decoder metadata];
else
return @{};
2007-10-09 02:25:40 +00:00
}
- (BOOL)open:(id<CogSource>)s {
if(![[s url] isFileURL]) {
return NO;
}
NSURL *url = [s url];
sourceURL = url;
embedded = NO;
cuesheet = nil;
NSDictionary *fileMetadata;
noFragment = NO;
observersAdded = NO;
NSString *ext = [url pathExtension];
if([ext caseInsensitiveCompare:@"cue"] != NSOrderedSame) {
// Embedded cuesheet check
fileMetadata = [NSClassFromString(@"AudioMetadataReader") metadataForURL:url skipCue:YES];
source = s;
decoder = [NSClassFromString(@"AudioDecoder") audioDecoderForSource:source skipCue:YES];
if(![decoder open:source]) {
ALog(@"Could not open cuesheet decoder");
return NO;
}
NSDictionary *alsoMetadata = [decoder metadata];
NSString *sheet = [fileMetadata objectForKey:@"cuesheet"];
if(!sheet || ![sheet length]) sheet = [alsoMetadata 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]];
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.
if(!embedded) {
source = [NSClassFromString(@"AudioSource") audioSourceForURL:[track url]];
if(![source open:[track 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;
}
}
CueSheetTrack *nextTrack = nil;
if(i + 1 < [tracks count]) {
nextTrack = [tracks objectAtIndex:i + 1];
}
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;
double _trackStart = [track time];
if(![track timeInSamples]) _trackStart *= sampleRate;
trackStart = _trackStart;
if(nextTrack && (embedded || ([[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]]))) {
double _trackEnd = [nextTrack time];
if(![nextTrack timeInSamples]) _trackEnd *= sampleRate;
trackEnd = _trackEnd;
} else {
trackEnd = [[properties objectForKey:@"totalFrames"] doubleValue];
}
[self seek:0];
// Note: Should register for observations of the decoder
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
return YES;
}
}
} else {
// Fix for embedded cuesheet handler parsing non-embedded files,
// or files that are already in the playlist without a fragment
source = [NSClassFromString(@"AudioSource") audioSourceForURL:url];
if(![source open:url]) {
ALog(@"Could not open cuesheet source");
return NO;
}
decoder = [NSClassFromString(@"AudioDecoder") audioDecoderForSource:source skipCue:YES];
[self registerObservers];
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;
}
- (void)registerObservers {
DLog(@"REGISTERING OBSERVERS");
[decoder addObserver:self
forKeyPath:@"properties"
options:(NSKeyValueObservingOptionNew)
context:NULL];
[decoder addObserver:self
forKeyPath:@"metadata"
options:(NSKeyValueObservingOptionNew)
context:NULL];
observersAdded = YES;
}
- (void)removeObservers {
if(observersAdded) {
[decoder removeObserver:self forKeyPath:@"properties"];
[decoder removeObserver:self forKeyPath:@"metadata"];
observersAdded = NO;
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
[self willChangeValueForKey:keyPath];
[self didChangeValueForKey:keyPath];
}
- (void)close {
if(decoder) {
[self removeObservers];
[decoder close];
decoder = nil;
}
source = nil;
cuesheet = nil;
track = nil;
}
- (void)dealloc {
[self close];
}
- (BOOL)setTrack:(NSURL *)url {
// handling the file directly
if(noFragment)
return NO;
BOOL pathsAreFiles = NO;
if([url isFileURL] && [sourceURL isFileURL]) {
pathsAreFiles = YES;
}
// Same file, just next track...this may be unnecessary since frame-based decoding is done now...
if(embedded || ([[sourceURL path] isEqualToString:[url path]] && (pathsAreFiles || [[sourceURL host] isEqualToString:[url host]]))) {
NSArray *tracks = [cuesheet tracks];
int i;
for(i = 0; i < [tracks count]; i++) {
if([[[tracks objectAtIndex:i] track] isEqualToString:[url fragment]]) {
CueSheetTrack *_track = [tracks objectAtIndex:i];
if(![[_track url] isEqualTo:[track url]])
return NO;
track = _track;
float sampleRate = [[[decoder properties] objectForKey:@"sampleRate"] floatValue];
double _trackStart = [track time];
if(![track timeInSamples]) _trackStart *= sampleRate;
trackStart = _trackStart;
CueSheetTrack *nextTrack = nil;
if(i + 1 < [tracks count]) {
nextTrack = [tracks objectAtIndex:i + 1];
}
if(nextTrack && (embedded || [[[nextTrack url] absoluteString] isEqualToString:[[track url] absoluteString]])) {
double _trackEnd = [nextTrack time];
if(![nextTrack timeInSamples]) _trackEnd *= sampleRate;
trackEnd = _trackEnd;
} else {
trackEnd = [[[decoder properties] objectForKey:@"totalFrames"] longValue];
}
[self seek:0];
DLog(@"CHANGING TRACK!");
return YES;
}
}
}
return NO;
2007-10-09 02:25:40 +00:00
}
- (long)seek:(long)frame {
if(!noFragment && frame > trackEnd - trackStart) {
// need a better way of returning fail.
return -1;
2007-10-09 02:25:40 +00:00
}
frame += trackStart;
framePosition = [decoder seek:frame];
return framePosition - trackStart;
2007-10-09 02:25:40 +00:00
}
- (int)readAudio:(void *)buf frames:(UInt32)frames {
if(!noFragment && framePosition + frames > trackEnd) {
2017-09-18 02:21:48 +00:00
frames = (UInt32)(trackEnd - framePosition);
2007-10-09 02:25:40 +00:00
}
if(!frames) {
DLog(@"Returning 0");
return 0;
}
int n = [decoder readAudio:buf frames:frames];
framePosition += n;
return n;
2007-10-09 02:25:40 +00:00
}
- (BOOL)isSilence {
if([decoder respondsToSelector:@selector(isSilence)])
return [decoder isSilence];
return NO;
}
2007-10-09 02:25:40 +00:00
@end