Skip to content

Commit

Permalink
Add chapters support for command-line client
Browse files Browse the repository at this point in the history
- Two keys -e and -E for treating each mp3 file as a chapter
- Chapter name template accepts three escape sequences:
    %a - author (from source file)
    %t - title (from source file)
    %N - chapter number
- Refactoring: AudioBinder now operates on AudioFile instead
    of filenames
- AudioFile tries to fetch file meta data on initialization
  • Loading branch information
gonzoua committed Jun 13, 2010
1 parent 18d9f2c commit 649db7d
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 78 deletions.
13 changes: 7 additions & 6 deletions AudioBinder.h
Expand Up @@ -29,18 +29,19 @@

#include <AudioToolbox/AudioFormat.h>
#include <AudioToolbox/ExtendedAudioFile.h>
#import "AudioFile.h"

#define DEFAULT_SAMPLE_RATE 44100.f

@protocol AudioBinderDelegate

-(void) updateStatus: (NSString *)filename handled:(UInt64)handledFrames total:(UInt64)totalFrames;
-(void) conversionStart: (NSString*)filename
-(void) updateStatus: (AudioFile*)file handled:(UInt64)handledFrames total:(UInt64)totalFrames;
-(void) conversionStart: (AudioFile*)file
format: (AudioStreamBasicDescription*)format
formatDescription: (NSString*)description
length: (UInt64)frames;
-(BOOL) continueFailedConversion:(NSString*)filename reason:(NSString*)reason;
-(void) conversionFinished: (NSString*)filename;
-(BOOL) continueFailedConversion:(AudioFile*)file reason:(NSString*)reason;
-(void) conversionFinished: (AudioFile*)file duration: (UInt32)milliseconds;
-(void) audiobookReady: (NSString*)filename duration: (UInt32)seconds;
-(void) audiobookFailed: (NSString*)filename reason: (NSString*)reason;

Expand All @@ -65,12 +66,12 @@
-(id) init;
-(void) reset;
-(void) setDelegate: (id <AudioBinderDelegate>)delegate;
-(void) addInputFile: (NSString*)fileName;
-(void) addInputFile: (AudioFile*)file;
-(void) setOutputFile: (NSString*)outFileName;
-(BOOL) convert;
-(BOOL) openOutFile;
-(void) closeOutFile;
-(BOOL) convertOneFile: (NSString*)inFileName reason: (NSString**)reason;
-(BOOL) convertOneFile: (AudioFile*)inFile reason: (NSString**)reason;
-(void) cancel;

@end
26 changes: 15 additions & 11 deletions AudioBinder.m
Expand Up @@ -136,9 +136,9 @@ -(void)setDelegate: (id<AudioBinderDelegate>)delegate
_delegate = delegate;
}

-(void)addInputFile: (NSString*)fileName
-(void)addInputFile: (AudioFile*)file
{
[_inFiles addObject: fileName];
[_inFiles addObject: file];
}

-(void)setOutputFile: (NSString*)outFileName
Expand Down Expand Up @@ -167,7 +167,7 @@ -(BOOL)convert
return NO;
}

for (NSString* inFile in _inFiles)
for (AudioFile* inFile in _inFiles)
{
NSString *reason;
if ([self convertOneFile:inFile reason:&reason] == NO)
Expand Down Expand Up @@ -251,7 +251,7 @@ -(BOOL)openOutFile

status = ExtAudioFileCreateNew(&dirFSRef,
(CFStringRef)[_outFileName lastPathComponent],
kAudioFileM4AType, &outputFormat,
kAudioFileMPEG4Type, &outputFormat,
NULL, &_outAudioFile);

if (status != noErr)
Expand All @@ -271,7 +271,7 @@ -(void)closeOutFile
_outAudioFile = nil;
}

-(BOOL) convertOneFile: (NSString *)inFileName reason: (NSString**)reason
-(BOOL) convertOneFile: (AudioFile *)inFile reason: (NSString**)reason
{
// Get description
NSString *fileFormat;
Expand All @@ -292,16 +292,16 @@ -(BOOL) convertOneFile: (NSString *)inFileName reason: (NSString**)reason
@try {
// open audio file
status = FSPathMakeRef(
(const UInt8 *)[inFileName fileSystemRepresentation],
(const UInt8 *)[inFile.filePath fileSystemRepresentation],
&ref, &isDirectory);
if (status != noErr)
[NSException raise:@"ConvertException"
format:@"Failed to make reference for file %@: %@",
inFileName, stringForOSStatus(status)];
inFile.filePath, stringForOSStatus(status)];

if (isDirectory)
[NSException raise:@"ConvertException"
format:@"Error: %@ is directory", inFileName];
format:@"Error: %@ is directory", inFile.filePath];

status = ExtAudioFileOpen(&ref, &inAudioFile);
if (status != noErr)
Expand Down Expand Up @@ -339,7 +339,7 @@ -(BOOL) convertOneFile: (NSString *)inFileName reason: (NSString**)reason
format:@"failed to get input file length: %@",
stringForOSStatus(status)];

[_delegate conversionStart: inFileName
[_delegate conversionStart: inFile
format: &format
formatDescription: fileFormat
length: framesTotal];
Expand Down Expand Up @@ -422,6 +422,7 @@ -(BOOL) convertOneFile: (NSString *)inFileName reason: (NSString**)reason
bufferList.mBuffers[0].mData = audioBuffer;
bufferList.mBuffers[0].mDataByteSize = AUDIO_BUFFER_SIZE;

SInt64 prevPos = _outFileLength;
do {

framesToRead =
Expand All @@ -444,7 +445,7 @@ -(BOOL) convertOneFile: (NSString *)inFileName reason: (NSString**)reason

framesConverted += framesToRead;

[_delegate updateStatus:inFileName
[_delegate updateStatus:inFile
handled:framesConverted
total:framesTotal];

Expand All @@ -459,7 +460,10 @@ -(BOOL) convertOneFile: (NSString *)inFileName reason: (NSString**)reason

} while(framesToRead > 0);

[_delegate conversionFinished:inFileName];

UInt32 duration = (_outFileLength - prevPos)*1000/_sampleRate;
[_delegate conversionFinished:inFile
duration:duration];
}
@catch (NSException *e) {
*reason = [e reason];
Expand Down
8 changes: 8 additions & 0 deletions AudioBookBinder.xcodeproj/project.pbxproj
Expand Up @@ -38,6 +38,8 @@
9F86C0D21023DAC000E65B00 /* MP4File.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F86C0D11023DAC000E65B00 /* MP4File.m */; };
9F95914211658710001C2433 /* iAudiobook256.icns in Resources */ = {isa = PBXBuildFile; fileRef = 9F95914111658710001C2433 /* iAudiobook256.icns */; };
9FA52D9211C2F100003FD879 /* AudioFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F6FA4A8111E56090047728F /* AudioFile.m */; };
9FA52D9F11C2FFCE003FD879 /* Chapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FA52D9E11C2FFCE003FD879 /* Chapter.m */; };
9FA52DA011C2FFCE003FD879 /* Chapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FA52D9E11C2FFCE003FD879 /* Chapter.m */; };
9FAB146D11619F9F00A2F36A /* ExpandedPathToIconTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FAB146A11619F9F00A2F36A /* ExpandedPathToIconTransformer.m */; };
9FAB146E11619F9F00A2F36A /* ExpandedPathToPathTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FAB146C11619F9F00A2F36A /* ExpandedPathToPathTransformer.m */; };
9FAB14821161A51400A2F36A /* PrefsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FAB14811161A51400A2F36A /* PrefsController.m */; };
Expand Down Expand Up @@ -108,6 +110,8 @@
9F86C0D11023DAC000E65B00 /* MP4File.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MP4File.m; sourceTree = "<group>"; };
9F95914111658710001C2433 /* iAudiobook256.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = iAudiobook256.icns; sourceTree = "<group>"; };
9F9C32D711A750A600F3FBC4 /* Russian */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Russian; path = Russian.lproj/MainMenu.xib; sourceTree = "<group>"; };
9FA52D9D11C2FFCE003FD879 /* Chapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Chapter.h; sourceTree = "<group>"; };
9FA52D9E11C2FFCE003FD879 /* Chapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Chapter.m; sourceTree = "<group>"; };
9FAB146911619F9F00A2F36A /* ExpandedPathToIconTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExpandedPathToIconTransformer.h; sourceTree = "<group>"; };
9FAB146A11619F9F00A2F36A /* ExpandedPathToIconTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExpandedPathToIconTransformer.m; sourceTree = "<group>"; };
9FAB146B11619F9F00A2F36A /* ExpandedPathToPathTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExpandedPathToPathTransformer.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -200,6 +204,8 @@
9F62872B1194D502000D3A05 /* CoverImageView.m */,
9FB4DE0511C05C0E007B3DDE /* MetaEditor.h */,
9FB4DE0611C05C0E007B3DDE /* MetaEditor.mm */,
9FA52D9D11C2FFCE003FD879 /* Chapter.h */,
9FA52D9E11C2FFCE003FD879 /* Chapter.m */,
);
name = Source;
sourceTree = "<group>";
Expand Down Expand Up @@ -349,6 +355,7 @@
9FBCBF46102A6A2C0067AE9F /* ConsoleDelegate.m in Sources */,
9FB4DE0811C05C0E007B3DDE /* MetaEditor.mm in Sources */,
9FA52D9211C2F100003FD879 /* AudioFile.m in Sources */,
9FA52DA011C2FFCE003FD879 /* Chapter.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -370,6 +377,7 @@
9FAB14821161A51400A2F36A /* PrefsController.m in Sources */,
9F62872C1194D502000D3A05 /* CoverImageView.m in Sources */,
9FB4DE0711C05C0E007B3DDE /* MetaEditor.mm in Sources */,
9FA52D9F11C2FFCE003FD879 /* Chapter.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
9 changes: 7 additions & 2 deletions AudioFile.h
Expand Up @@ -13,16 +13,21 @@
NSString *filePath;
NSString *name;
NSInteger duration;
NSString *artist, *title;
BOOL valid;
}

@property(readwrite, copy) NSString *filePath;
@property(readwrite, copy) NSString *name;
@property(readwrite, assign) NSInteger duration;
@property(readwrite, assign) BOOL valid;
@property(readwrite, copy) NSString *artist;
@property(readwrite, copy) NSString *title;


- (id) initWithPath:(NSString*)path;
- (void) dealloc;
- (BOOL) isValid;

// private function
- (void) updateDuration;
- (void) updateInfo;
@end
81 changes: 51 additions & 30 deletions AudioFile.m
Expand Up @@ -19,50 +19,71 @@ - (id) initWithPath:(NSString*)path
self.filePath = path;
self.name = [path lastPathComponent];
self.duration = -1;
[self updateDuration];
self.valid = NO;
[self updateInfo];
}

return self;
}

- (BOOL) isValid
{
if (self.duration >= 0)
return TRUE;

return FALSE;
}

- (void) dealloc
{
[filePath release];
[name release];
self.filePath = nil;
self.name = nil;
self.artist = nil;
self.title = nil;
[super dealloc];
}

@synthesize filePath, name, duration;
@synthesize filePath, name, duration, valid, artist, title;

- (void) updateDuration
- (void) updateInfo
{
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSString *extension = [[self.filePath pathExtension] lowercaseString];
if ([ws filenameExtension:extension isValidForType:@"public.audio"])
{
AudioFileID audioFile;
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
(CFStringRef)self.filePath,
kCFURLPOSIXPathStyle, FALSE);
if (AudioFileOpenURL(url, 0x01, 0, &audioFile) == noErr)
{
UInt32 len = sizeof(NSTimeInterval);
NSTimeInterval dur;
if (AudioFileGetProperty(audioFile, kAudioFilePropertyEstimatedDuration, &len, &dur) == noErr)
self.duration = dur;
AudioFileClose(audioFile);
// NSString *extension = [[self.filePath pathExtension] lowercaseString];
OSStatus status;
AudioFileID audioFile;
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
(CFStringRef)self.filePath,
kCFURLPOSIXPathStyle, FALSE);
if (AudioFileOpenURL(url, 0x01, 0, &audioFile) == noErr) {
UInt32 len = sizeof(NSTimeInterval);
NSTimeInterval dur;
if (AudioFileGetProperty(audioFile, kAudioFilePropertyEstimatedDuration, &len, &dur) == noErr)
self.duration = dur*1000;

UInt32 writable = 0, size;
status = AudioFileGetPropertyInfo(audioFile,
kAudioFilePropertyInfoDictionary, &size, &writable);

if ( status == noErr ) {
CFDictionaryRef info = NULL;
status = AudioFileGetProperty(audioFile,
kAudioFilePropertyInfoDictionary, &size, &info);
if ( status == noErr ) {
NSDictionary *properties = (NSDictionary *)info;
// NSLog(@"file properties: %@", properties);
NSString *s = [NSString stringWithUTF8String:
[[properties objectForKey:@"artist"] UTF8String]];
if (s)
self.artist = s;
else
self.artist = @"";


s = [NSString stringWithUTF8String:
[[properties objectForKey:@"title"] UTF8String]];
if (s)
self.title = s;
else
self.title = @"";
}
}
CFRelease(url);
self.valid = YES;
AudioFileClose(audioFile);
}
NSLog(@"updateDuration: %d", self.duration);

CFRelease(url);
}


@end
9 changes: 4 additions & 5 deletions ConsoleDelegate.h
Expand Up @@ -41,12 +41,11 @@
-(void) setSkipErrors:(BOOL)skip;

// AudioBinderDelegate methods
-(void) updateStatus: (NSString *)filename handled:(UInt64)handledFrames total:(UInt64)totalFrames;
-(void) conversionStart: (NSString*)filename format: (AudioStreamBasicDescription*)asbd formatDescription: (NSString*)description length: (UInt64)frames;
-(BOOL) continueFailedConversion:(NSString*)filename reason:(NSString*)reason;
-(void) conversionFinished: (NSString*)filename;
-(void) updateStatus: (AudioFile*)file handled:(UInt64)handledFrames total:(UInt64)totalFrames;
-(void) conversionStart: (AudioFile*)file format: (AudioStreamBasicDescription*)asbd formatDescription: (NSString*)description length: (UInt64)frames;
-(BOOL) continueFailedConversion:(AudioFile*)file reason:(NSString*)reason;
-(void) conversionFinished: (AudioFile*)file duration:(UInt32)duration;
-(void) audiobookReady: (NSString*)filename duration: (UInt32)seconds;
-(void) audiobookFailed: (NSString*)filename reason: (NSString*)reason;


@end

0 comments on commit 649db7d

Please sign in to comment.