#import "ExpressController.h"
+#import "DriveDetector.h"
+
+#define INSERT_STRING @"Insert a DVD"
@interface ExpressController (Private)
+- (void) openUpdateDrives: (NSDictionary *) drives;
- (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
returnCode contextInfo: (void *) contextInfo;
- (void) openEnable: (BOOL) b;
- (void) openTimer: (NSTimer *) timer;
- (void) convertShow;
+- (void) convertEnable: (BOOL) b;
- (void) convertTimer: (NSTimer *) timer;
@end
**********************************************************************/
- (void) awakeFromNib
{
+ NSEnumerator * enumerator;
+
/* Show the "Open DVD" interface */
+ fDriveDetector = [[DriveDetector alloc] initWithCallback: self
+ selector: @selector( openUpdateDrives: )];
+ [fDriveDetector run];
[self openEnable: YES];
[fWindow setContentSize: [fOpenView frame].size];
[fWindow setContentView: fOpenView];
[fWindow makeKeyAndOrderFront: nil];
/* NSTableView initializations */
- NSTableColumn * tableColumn = [fConvertTableView
- tableColumnWithIdentifier: @"Check"];
- NSButtonCell * buttonCell = [[[NSButtonCell alloc]
- initTextCell: @""] autorelease];
+ NSButtonCell * buttonCell;
+ NSTableColumn * tableColumn;
+ enumerator = [[fConvertTableView tableColumns] objectEnumerator];
+ while( ( tableColumn = [enumerator nextObject] ) )
+ {
+ [tableColumn setEditable: NO];
+ }
+ tableColumn = [fConvertTableView tableColumnWithIdentifier: @"Check"];
+ buttonCell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
[buttonCell setEditable: YES];
[buttonCell setButtonType: NSSwitchButton];
[tableColumn setDataCell: buttonCell];
- (void) applicationWillFinishLaunching: (NSNotification *) n
{
- fHandle = hb_init( HB_DEBUG_NONE, 0 );
+ fHandle = hb_init_express( HB_DEBUG_NONE, 0 );
fList = hb_get_titles( fHandle );
}
}
/***********************************************************************
- * Tableview delegate methods
+ * Tableview datasource methods
**********************************************************************/
- (int) numberOfRowsInTableView: (NSTableView *) t
{
return [@"Title " stringByAppendingFormat: @"%d",
title->index];
}
- else if( [[col identifier] isEqualToString: @"Length"] )
+ else if( [[col identifier] isEqualToString: @"Duration"] )
{
if( title->hours > 0 )
{
[fWindow setContentView: fEmptyView];
[fWindow setFrame: frame display: YES animate: YES];
[fWindow setContentView: fOpenView];
+
+ [fDriveDetector run];
}
- (void) openMatrixChanged: (id) sender
{
[self openEnable: YES];
+ if( [fOpenMatrix selectedRow] )
+ {
+ [self openBrowse: self];
+ }
}
- (void) openBrowse: (id) sender
[self openEnable: NO];
[fOpenIndicator setIndeterminate: YES];
[fOpenIndicator startAnimation: nil];
+ [fOpenProgressField setStringValue: @"Opening..."];
+ [fDriveDetector stop];
- hb_scan( fHandle, [fOpenFolderString UTF8String], 0 );
+ if( [fOpenMatrix selectedRow] )
+ {
+ hb_scan( fHandle, [fOpenFolderString UTF8String], 0 );
+ }
+ else
+ {
+ hb_scan( fHandle, [[fDrives objectForKey: [fOpenPopUp
+ titleOfSelectedItem]] UTF8String], 0 );
+ }
- NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 0.5
+ NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self selector: @selector( openTimer: ) userInfo: nil
repeats: YES];
}
- (void) convertGo: (id) sender
{
- int i;
+ int i, j;
for( i = 0; i < hb_list_count( fList ); i++ )
{
hb_title_t * title = hb_list_item( fList, i );
hb_job_t * job = title->job;
- job->width = 320;
- for( ;; )
+ int pixels = 307200;
+ int aspect = title->aspect;
+ if( [fConvertAspectPopUp indexOfSelectedItem] == 1)
+ {
+ aspect = 4 * HB_ASPECT_BASE / 3;
+ }
+
+ int maxwidth = 640;
+ job->vbitrate = 1000;
+ if( [fConvertMaxWidthPopUp indexOfSelectedItem] == 1)
+ {
+ maxwidth = 320;
+ job->vbitrate = 500;
+ }
+ job->deinterlace = 1;
+
+ do
+ {
+ hb_set_size( job, aspect, pixels );
+ pixels -= 10;
+ } while(job->width > maxwidth);
+
+ if( [fConvertFormatPopUp indexOfSelectedItem] == 0 )
{
- /* XXX */
- hb_fix_aspect( job, HB_KEEP_WIDTH );
- if( job->height == 240 )
+ /* iPod / H.264 */
+ job->mux = HB_MUX_IPOD;
+ job->vcodec = HB_VCODEC_X264;
+ job->h264_level = 30;
+ }
+ else if( [fConvertFormatPopUp indexOfSelectedItem] == 1 )
+ {
+ /* iPod / MPEG-4 */
+ job->mux = HB_MUX_MP4;
+ job->vcodec = HB_VCODEC_FFMPEG;
+ }
+ else
+ {
+ /* PSP / MPEG-4 */
+ job->mux = HB_MUX_PSP;
+ job->vrate = 27000000;
+ job->vrate_base = 900900; /* 29.97 fps */
+ job->vcodec = HB_VCODEC_FFMPEG;
+ job->vbitrate = 600;
+ pixels = 76800;
+ job->arate = 24000;
+ job->abitrate = 96;
+ aspect = 16 * HB_ASPECT_BASE / 9;
+
+ if( [fConvertAspectPopUp indexOfSelectedItem] )
+ {
+ aspect = -1;
+ }
+
+ hb_set_size( job, aspect, pixels );
+ }
+
+ job->vquality = -1.0;
+
+ const char * lang;
+
+ /* Audio selection */
+ hb_audio_t * audio;
+ lang = [[fConvertAudioPopUp titleOfSelectedItem] UTF8String];
+ job->audios[0] = -1;
+ for( j = 0; j < hb_list_count( title->list_audio ); j++ )
+ {
+ /* Choose the first track that matches the language */
+ audio = hb_list_item( title->list_audio, j );
+ if( !strcmp( lang, audio->lang_simple ) )
{
+ job->audios[0] = j;
break;
}
- else if( job->height < 240 )
- {
- job->crop[2] += 2;
- job->crop[3] += 2;
- }
- else
+ }
+ if( job->audios[0] == -1 )
+ {
+ /* If the language isn't available in this title, choose
+ the first track */
+ job->audios[0] = 0;
+ }
+ job->audios[1] = -1;
+
+ job->audio_mixdowns[0] = HB_AMIXDOWN_DOLBYPLII;
+
+ /* Subtitle selection */
+ hb_subtitle_t * subtitle;
+ lang = [[fConvertSubtitlePopUp titleOfSelectedItem] UTF8String];
+ job->subtitle = -1;
+ for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
+ {
+ /* Choose the first track that matches the language */
+ subtitle = hb_list_item( title->list_subtitle, j );
+ if( !strcmp( lang, subtitle->lang ) )
{
- job->crop[0] += 2;
- job->crop[1] += 2;
+ job->subtitle = j;
+ break;
}
}
- job->vquality = -1.0;
- job->vbitrate = 600;
- job->vcodec = HB_VCODEC_X264;
- job->h264_13 = 1;
- job->file = strdup( [[NSString stringWithFormat:
- @"%@/%p - Title %d.mp4", fConvertFolderString, self,
- title->index] UTF8String] );
+
+ job->file = strdup( [[NSString stringWithFormat:
+ @"%@/%s - Title %d.m4v", fConvertFolderString,
+ title->name, title->index] UTF8String] );
hb_add( fHandle, job );
}
hb_start( fHandle );
- NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 0.5
+ NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self selector: @selector( convertTimer: ) userInfo: nil
repeats: YES];
+
+ [self convertEnable: NO];
+}
+
+- (void) convertCancel: (id) sender
+{
+ hb_stop( fHandle );
+ [self convertEnable: YES];
}
@end
@implementation ExpressController (Private)
+- (void) openUpdateDrives: (NSDictionary *) drives
+{
+ if( fDrives )
+ {
+ [fDrives release];
+ }
+ fDrives = [[NSDictionary alloc] initWithDictionary: drives];
+
+ NSString * device;
+ NSEnumerator * enumerator = [fDrives keyEnumerator];
+ [fOpenPopUp removeAllItems];
+ while( ( device = [enumerator nextObject] ) )
+ {
+ [fOpenPopUp addItemWithTitle: device];
+ }
+
+ if( ![fOpenPopUp numberOfItems] )
+ {
+ [fOpenPopUp addItemWithTitle: INSERT_STRING];
+ }
+ [fOpenPopUp selectItemAtIndex: 0];
+ if( [fOpenMatrix isEnabled] )
+ {
+ [self openEnable: YES];
+ }
+}
+
- (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
returnCode contextInfo: (void *) contextInfo
{
[fOpenFolderString release];
fOpenFolderString = [[[sheet filenames] objectAtIndex: 0] retain];
[fOpenFolderField setStringValue: [fOpenFolderString lastPathComponent]];
+ [self openGo: self];
}
- (void) openEnable: (BOOL) b
{
[fOpenFolderField setEnabled: NO];
[fOpenBrowseButton setEnabled: NO];
+ if( [[fOpenPopUp titleOfSelectedItem]
+ isEqualToString: INSERT_STRING] )
+ {
+ [fOpenGoButton setEnabled: NO];
+ }
}
}
}
{
[self convertShow];
}
+ else
+ {
+ [fDriveDetector run];
+ }
break;
default:
- (void) convertShow
{
- int i;
+ int i, j;
fConvertCheckArray = [[NSMutableArray alloc] initWithCapacity:
hb_list_count( fList )];
+ [fConvertAudioPopUp removeAllItems];
+ [fConvertSubtitlePopUp removeAllItems];
+ [fConvertSubtitlePopUp addItemWithTitle: @"None"];
for( i = 0; i < hb_list_count( fList ); i++ )
{
/* Default is to convert titles longer than 30 minutes. */
hb_title_t * title = hb_list_item( fList, i );
[fConvertCheckArray addObject: [NSNumber numberWithBool:
( 60 * title->hours + title->minutes > 30 )]];
+
+ /* Update audio popup */
+ hb_audio_t * audio;
+ for( j = 0; j < hb_list_count( title->list_audio ); j++ )
+ {
+ audio = hb_list_item( title->list_audio, j );
+ [fConvertAudioPopUp addItemWithTitle:
+ [NSString stringWithUTF8String: audio->lang_simple]];
+ }
+ [fConvertAudioPopUp selectItemWithTitle: @"English"];
+
+ /* Update subtitle popup */
+ hb_subtitle_t * subtitle;
+ for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
+ {
+ subtitle = hb_list_item( title->list_subtitle, j );
+ [fConvertSubtitlePopUp addItemWithTitle:
+ [NSString stringWithUTF8String: subtitle->lang]];
+ }
}
[fConvertTableView reloadData];
[image16 unlockFocus];
[item setImage: image16];
[image16 release];
+
+ [self convertEnable: YES];
+}
+
+- (void) convertEnable: (BOOL) b
+{
+ [fConvertTableView setEnabled: b];
+ [fConvertFolderPopUp setEnabled: b];
+ [fConvertFormatPopUp setEnabled: b];
+ [fConvertAspectPopUp setEnabled: b];
+ [fConvertMaxWidthPopUp setEnabled: b];
+ [fConvertAudioPopUp setEnabled: b];
+ [fConvertSubtitlePopUp setEnabled: b];
+ [fConvertOpenButton setEnabled: b];
+ if( b )
+ {
+ [fConvertGoButton setTitle: @"Convert"];
+ [fConvertGoButton setAction: @selector(convertGo:)];
+ }
+ else
+ {
+ [fConvertGoButton setTitle: @"Cancel"];
+ [fConvertGoButton setAction: @selector(convertCancel:)];
+ }
+}
+
+/***********************************************************************
+* UpdateDockIcon
+***********************************************************************
+* Shows a progression bar on the dock icon, filled according to
+* 'progress' (0.0 <= progress <= 1.0).
+* Called with progress < 0.0 or progress > 1.0, restores the original
+* icon.
+**********************************************************************/
+- (void) UpdateDockIcon: (float) progress
+{
+ NSImage * icon;
+ NSData * tiff;
+ NSBitmapImageRep * bmp;
+ uint32_t * pen;
+ uint32_t black = htonl( 0x000000FF );
+ uint32_t red = htonl( 0xFF0000FF );
+ uint32_t white = htonl( 0xFFFFFFFF );
+ int row_start, row_end;
+ int i, j;
+
+ /* Get application original icon */
+ icon = [NSImage imageNamed: @"NSApplicationIcon"];
+
+ if( progress < 0.0 || progress > 1.0 )
+ {
+ [NSApp setApplicationIconImage: icon];
+ return;
+ }
+
+ /* Get it in a raw bitmap form */
+ tiff = [icon TIFFRepresentationUsingCompression:
+ NSTIFFCompressionNone factor: 1.0];
+ bmp = [NSBitmapImageRep imageRepWithData: tiff];
+
+ /* Draw the progression bar */
+ /* It's pretty simple (ugly?) now, but I'm no designer */
+
+ row_start = 3 * (int) [bmp size].height / 4;
+ row_end = 7 * (int) [bmp size].height / 8;
+
+ for( i = row_start; i < row_start + 2; i++ )
+ {
+ pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
+ for( j = 0; j < (int) [bmp size].width; j++ )
+ {
+ pen[j] = black;
+ }
+ }
+ for( i = row_start + 2; i < row_end - 2; i++ )
+ {
+ pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
+ pen[0] = black;
+ pen[1] = black;
+ for( j = 2; j < (int) [bmp size].width - 2; j++ )
+ {
+ if( j < 2 + (int) ( ( [bmp size].width - 4.0 ) * progress ) )
+ {
+ pen[j] = red;
+ }
+ else
+ {
+ pen[j] = white;
+ }
+ }
+ pen[j] = black;
+ pen[j+1] = black;
+ }
+ for( i = row_end - 2; i < row_end; i++ )
+ {
+ pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
+ for( j = 0; j < (int) [bmp size].width; j++ )
+ {
+ pen[j] = black;
+ }
+ }
+
+ /* Now update the dock icon */
+ tiff = [bmp TIFFRepresentationUsingCompression:
+ NSTIFFCompressionNone factor: 1.0];
+ icon = [[NSImage alloc] initWithData: tiff];
+ [NSApp setApplicationIconImage: icon];
+ [icon release];
}
- (void) convertTimer: (NSTimer *) timer
{
#define p s.param.working
case HB_STATE_WORKING:
+ {
+ float progress_total = ( p.progress + p.job_cur - 1 ) / p.job_count;
+ NSMutableString * string = [NSMutableString
+ stringWithFormat: @"Converting: %.1f %%",
+ 100.0 * progress_total];
+ if( p.seconds > -1 )
+ {
+ [string appendFormat: @" (%.1f fps, ", p.rate_avg];
+ if( p.hours > 0 )
+ {
+ [string appendFormat: @"%d hour%s %d min%s",
+ p.hours, p.hours == 1 ? "" : "s",
+ p.minutes, p.minutes == 1 ? "" : "s"];
+ }
+ else if( p.minutes > 0 )
+ {
+ [string appendFormat: @"%d min%s %d sec%s",
+ p.minutes, p.minutes == 1 ? "" : "s",
+ p.seconds, p.seconds == 1 ? "" : "s"];
+ }
+ else
+ {
+ [string appendFormat: @"%d second%s",
+ p.seconds, p.seconds == 1 ? "" : "s"];
+ }
+ [string appendString: @" left)"];
+ }
+ [fConvertInfoString setStringValue: string];
[fConvertIndicator setIndeterminate: NO];
- [fConvertIndicator setDoubleValue: 100.0 * p.progress];
+ [fConvertIndicator setDoubleValue: 100.0 * progress_total];
+ [self UpdateDockIcon: progress_total];
+ break;
+ }
+#undef p
+
+#define p s.param.muxing
+ case HB_STATE_MUXING:
+ {
+ NSMutableString * string = [NSMutableString
+ stringWithFormat: @"Muxing..."];
+ [fConvertInfoString setStringValue: string];
+ [fConvertIndicator setIndeterminate: YES];
+ [fConvertIndicator startAnimation: nil];
+ [self UpdateDockIcon: 1.0];
break;
+ }
#undef p
case HB_STATE_WORKDONE:
- [timer invalidate];
+ {
+ [timer invalidate];
[fConvertIndicator setIndeterminate: NO];
[fConvertIndicator setDoubleValue: 0.0];
- break;
+ [self UpdateDockIcon: -1.0];
+ [self convertEnable: YES];
+
+#define p s.param.workdone
+ switch(p.error)
+ {
+ case HB_ERROR_NONE:
+ [fConvertInfoString setStringValue: @"Done."];
+ break;
+ case HB_ERROR_CANCELED:
+ [fConvertInfoString setStringValue: @"Canceled."];
+ break;
+ case HB_ERROR_UNKNOWN:
+ [fConvertInfoString setStringValue: @"Unknown Error."];
+ break;
+ }
+#undef p
+ hb_job_t * job;
+ while( ( job = hb_job( fHandle, 0 ) ) )
+ {
+ hb_rem( fHandle, job );
+ }
+ break;
+ }
default:
break;
}