1 #import "ExpressController.h"
2 #import "DriveDetector.h"
4 #define INSERT_STRING @"Insert a DVD"
6 @interface ExpressController (Private)
8 - (void) openUpdateDrives: (NSDictionary *) drives;
9 - (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
10 returnCode contextInfo: (void *) contextInfo;
11 - (void) openEnable: (BOOL) b;
12 - (void) openTimer: (NSTimer *) timer;
15 - (void) convertEnable: (BOOL) b;
16 - (void) convertTimer: (NSTimer *) timer;
20 @implementation ExpressController
22 /***********************************************************************
23 * Application delegate methods
24 **********************************************************************/
27 NSEnumerator * enumerator;
29 /* Show the "Open DVD" interface */
30 fDriveDetector = [[DriveDetector alloc] initWithCallback: self
31 selector: @selector( openUpdateDrives: )];
33 [self openEnable: YES];
34 [fWindow setContentSize: [fOpenView frame].size];
35 [fWindow setContentView: fOpenView];
37 [fWindow makeKeyAndOrderFront: nil];
39 /* NSTableView initializations */
40 NSButtonCell * buttonCell;
41 NSTableColumn * tableColumn;
42 enumerator = [[fConvertTableView tableColumns] objectEnumerator];
43 while( ( tableColumn = [enumerator nextObject] ) )
45 [tableColumn setEditable: NO];
47 tableColumn = [fConvertTableView tableColumnWithIdentifier: @"Check"];
48 buttonCell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
49 [buttonCell setEditable: YES];
50 [buttonCell setButtonType: NSSwitchButton];
51 [tableColumn setDataCell: buttonCell];
54 fConvertFolderString = [@"~/Movies" stringByExpandingTildeInPath];
55 [fConvertFolderString retain];
58 - (void) applicationWillFinishLaunching: (NSNotification *) n
60 fHandle = hb_init_express( HB_DEBUG_NONE, 0 );
61 fList = hb_get_titles( fHandle );
64 - (void) applicationWillTerminate: (NSNotification *) n
69 /***********************************************************************
70 * Tableview datasource methods
71 **********************************************************************/
72 - (int) numberOfRowsInTableView: (NSTableView *) t
77 return hb_list_count( fList );
80 - (id) tableView:(NSTableView *) t objectValueForTableColumn:
81 (NSTableColumn *) col row: (int) row
83 if( [[col identifier] isEqualToString: @"Check"] )
85 return [fConvertCheckArray objectAtIndex: row];
89 hb_title_t * title = hb_list_item( fList, row );
90 if( [[col identifier] isEqualToString: @"Title"] )
92 return [@"Title " stringByAppendingFormat: @"%d",
95 else if( [[col identifier] isEqualToString: @"Duration"] )
97 if( title->hours > 0 )
99 return [NSString stringWithFormat:
100 @"%d hour%s %d min%s", title->hours,
101 title->hours > 1 ? "s" : "", title->minutes,
102 title->minutes > 1 ? "s": ""];
104 else if( title->minutes > 0 )
106 return [NSString stringWithFormat:
107 @"%d min%s %d sec%s", title->minutes,
108 title->minutes > 1 ? "s" : "", title->seconds,
109 title->seconds > 1 ? "s": ""];
113 return [NSString stringWithFormat: @"%d seconds",
121 - (void) tableView: (NSTableView *) t setObjectValue: (id) object
122 forTableColumn: (NSTableColumn *) col row: (int) row
124 if( [[col identifier] isEqualToString: @"Check"] )
126 [fConvertCheckArray replaceObjectAtIndex: row withObject: object];
130 /***********************************************************************
131 * User events methods
132 **********************************************************************/
133 - (void) openShow: (id) sender
135 NSRect frame = [fWindow frame];
136 float offset = [fConvertView frame].size.height -
137 [fOpenView frame].size.height;
139 frame.origin.y += offset;
140 frame.size.height -= offset;
141 [fWindow setContentView: fEmptyView];
142 [fWindow setFrame: frame display: YES animate: YES];
143 [fWindow setContentView: fOpenView];
145 [fDriveDetector run];
148 - (void) openMatrixChanged: (id) sender
150 [self openEnable: YES];
151 if( [fOpenMatrix selectedRow] )
153 [self openBrowse: self];
157 - (void) openBrowse: (id) sender
159 NSOpenPanel * panel = [NSOpenPanel openPanel];
160 [panel setAllowsMultipleSelection: NO];
161 [panel setCanChooseFiles: YES];
162 [panel setCanChooseDirectories: YES ];
163 [panel beginSheetForDirectory: nil file: nil types: nil
164 modalForWindow: fWindow modalDelegate: self
165 didEndSelector: @selector( openBrowseDidEnd:returnCode:contextInfo: )
169 - (void) openGo: (id) sender
171 [self openEnable: NO];
172 [fOpenIndicator setIndeterminate: YES];
173 [fOpenIndicator startAnimation: nil];
174 [fOpenProgressField setStringValue: @"Opening..."];
175 [fDriveDetector stop];
177 if( [fOpenMatrix selectedRow] )
179 hb_scan( fHandle, [fOpenFolderString UTF8String], 0 );
183 hb_scan( fHandle, [[fDrives objectForKey: [fOpenPopUp
184 titleOfSelectedItem]] UTF8String], 0 );
187 NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0
188 target: self selector: @selector( openTimer: ) userInfo: nil
192 - (void) convertGo: (id) sender
196 for( i = 0; i < hb_list_count( fList ); i++ )
198 if( ![[fConvertCheckArray objectAtIndex: i] boolValue] )
201 hb_title_t * title = hb_list_item( fList, i );
202 hb_job_t * job = title->job;
205 int aspect = title->aspect;
206 if( [fConvertAspectPopUp indexOfSelectedItem] == 1)
208 aspect = 4 * HB_ASPECT_BASE / 3;
212 job->vbitrate = 1000;
213 if( [fConvertMaxWidthPopUp indexOfSelectedItem] == 1)
218 job->deinterlace = 1;
222 hb_set_size( job, aspect, pixels );
224 } while(job->width > maxwidth);
226 if( [fConvertFormatPopUp indexOfSelectedItem] == 0 )
229 job->mux = HB_MUX_IPOD;
230 job->vcodec = HB_VCODEC_X264;
231 job->h264_level = 30;
233 else if( [fConvertFormatPopUp indexOfSelectedItem] == 1 )
236 job->mux = HB_MUX_MP4;
237 job->vcodec = HB_VCODEC_FFMPEG;
242 job->mux = HB_MUX_PSP;
243 job->vrate = 27000000;
244 job->vrate_base = 900900; /* 29.97 fps */
245 job->vcodec = HB_VCODEC_FFMPEG;
250 aspect = 16 * HB_ASPECT_BASE / 9;
252 if( [fConvertAspectPopUp indexOfSelectedItem] )
257 hb_set_size( job, aspect, pixels );
260 job->vquality = -1.0;
264 /* Audio selection */
266 lang = [[fConvertAudioPopUp titleOfSelectedItem] UTF8String];
268 for( j = 0; j < hb_list_count( title->list_audio ); j++ )
270 /* Choose the first track that matches the language */
271 audio = hb_list_item( title->list_audio, j );
272 if( !strcmp( lang, audio->lang_simple ) )
278 if( job->audios[0] == -1 )
280 /* If the language isn't available in this title, choose
286 job->audio_mixdowns[0] = HB_AMIXDOWN_DOLBYPLII;
288 /* Subtitle selection */
289 hb_subtitle_t * subtitle;
290 lang = [[fConvertSubtitlePopUp titleOfSelectedItem] UTF8String];
292 for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
294 /* Choose the first track that matches the language */
295 subtitle = hb_list_item( title->list_subtitle, j );
296 if( !strcmp( lang, subtitle->lang ) )
303 job->file = strdup( [[NSString stringWithFormat:
304 @"%@/%s - Title %d.m4v", fConvertFolderString,
305 title->name, title->index] UTF8String] );
306 hb_add( fHandle, job );
311 NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0
312 target: self selector: @selector( convertTimer: ) userInfo: nil
315 [self convertEnable: NO];
318 - (void) convertCancel: (id) sender
321 [self convertEnable: YES];
326 /***********************************************************************
328 **********************************************************************/
330 @implementation ExpressController (Private)
332 - (void) openUpdateDrives: (NSDictionary *) drives
338 fDrives = [[NSDictionary alloc] initWithDictionary: drives];
341 NSEnumerator * enumerator = [fDrives keyEnumerator];
342 [fOpenPopUp removeAllItems];
343 while( ( device = [enumerator nextObject] ) )
345 [fOpenPopUp addItemWithTitle: device];
348 if( ![fOpenPopUp numberOfItems] )
350 [fOpenPopUp addItemWithTitle: INSERT_STRING];
352 [fOpenPopUp selectItemAtIndex: 0];
353 if( [fOpenMatrix isEnabled] )
355 [self openEnable: YES];
359 - (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
360 returnCode contextInfo: (void *) contextInfo
362 if( returnCode != NSOKButton )
365 if( fOpenFolderString )
366 [fOpenFolderString release];
367 fOpenFolderString = [[[sheet filenames] objectAtIndex: 0] retain];
368 [fOpenFolderField setStringValue: [fOpenFolderString lastPathComponent]];
372 - (void) openEnable: (BOOL) b
374 [fOpenMatrix setEnabled: b];
375 [fOpenPopUp setEnabled: b];
376 [fOpenFolderField setEnabled: b];
377 [fOpenBrowseButton setEnabled: b];
378 [fOpenGoButton setEnabled: b];
382 if( [fOpenMatrix selectedRow] )
384 [fOpenPopUp setEnabled: NO];
388 [fOpenFolderField setEnabled: NO];
389 [fOpenBrowseButton setEnabled: NO];
390 if( [[fOpenPopUp titleOfSelectedItem]
391 isEqualToString: INSERT_STRING] )
393 [fOpenGoButton setEnabled: NO];
399 - (void) openTimer: (NSTimer *) timer
402 hb_get_state( fHandle, &s );
405 #define p s.param.scanning
406 case HB_STATE_SCANNING:
407 [fOpenIndicator setIndeterminate: NO];
408 [fOpenIndicator setDoubleValue: 100.0 *
409 ( (float) p.title_cur - 0.5 ) / p.title_count];
410 [fOpenProgressField setStringValue: [NSString
411 stringWithFormat: @"Scanning title %d of %d...",
412 p.title_cur, p.title_count]];
416 case HB_STATE_SCANDONE:
419 [fOpenIndicator setIndeterminate: NO];
420 [fOpenIndicator setDoubleValue: 0.0];
421 [self openEnable: YES];
423 if( hb_list_count( fList ) )
429 [fDriveDetector run];
442 fConvertCheckArray = [[NSMutableArray alloc] initWithCapacity:
443 hb_list_count( fList )];
444 [fConvertAudioPopUp removeAllItems];
445 [fConvertSubtitlePopUp removeAllItems];
446 [fConvertSubtitlePopUp addItemWithTitle: @"None"];
447 for( i = 0; i < hb_list_count( fList ); i++ )
449 /* Default is to convert titles longer than 30 minutes. */
450 hb_title_t * title = hb_list_item( fList, i );
451 [fConvertCheckArray addObject: [NSNumber numberWithBool:
452 ( 60 * title->hours + title->minutes > 30 )]];
454 /* Update audio popup */
456 for( j = 0; j < hb_list_count( title->list_audio ); j++ )
458 audio = hb_list_item( title->list_audio, j );
459 [fConvertAudioPopUp addItemWithTitle:
460 [NSString stringWithUTF8String: audio->lang_simple]];
462 [fConvertAudioPopUp selectItemWithTitle: @"English"];
464 /* Update subtitle popup */
465 hb_subtitle_t * subtitle;
466 for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
468 subtitle = hb_list_item( title->list_subtitle, j );
469 [fConvertSubtitlePopUp addItemWithTitle:
470 [NSString stringWithUTF8String: subtitle->lang]];
473 [fConvertTableView reloadData];
475 NSRect frame = [fWindow frame];
476 float offset = [fConvertView frame].size.height -
477 [fOpenView frame].size.height;
478 frame.origin.y -= offset;
479 frame.size.height += offset;
480 [fWindow setContentView: fEmptyView];
481 [fWindow setFrame: frame display: YES animate: YES];
482 [fWindow setContentView: fConvertView];
485 NSMenuItem * item = [fConvertFolderPopUp itemAtIndex: 0];
486 [item setTitle: [fConvertFolderString lastPathComponent]];
487 NSImage * image32 = [[NSWorkspace sharedWorkspace] iconForFile:
488 fConvertFolderString];
489 NSImage * image16 = [[NSImage alloc] initWithSize:
492 [[NSGraphicsContext currentContext]
493 setImageInterpolation: NSImageInterpolationHigh];
494 [image32 drawInRect: NSMakeRect(0,0,16,16)
495 fromRect: NSMakeRect(0,0,32,32) operation: NSCompositeCopy
497 [image16 unlockFocus];
498 [item setImage: image16];
501 [self convertEnable: YES];
504 - (void) convertEnable: (BOOL) b
506 [fConvertTableView setEnabled: b];
507 [fConvertFolderPopUp setEnabled: b];
508 [fConvertFormatPopUp setEnabled: b];
509 [fConvertAspectPopUp setEnabled: b];
510 [fConvertMaxWidthPopUp setEnabled: b];
511 [fConvertAudioPopUp setEnabled: b];
512 [fConvertSubtitlePopUp setEnabled: b];
513 [fConvertOpenButton setEnabled: b];
516 [fConvertGoButton setTitle: @"Convert"];
517 [fConvertGoButton setAction: @selector(convertGo:)];
521 [fConvertGoButton setTitle: @"Cancel"];
522 [fConvertGoButton setAction: @selector(convertCancel:)];
526 /***********************************************************************
528 ***********************************************************************
529 * Shows a progression bar on the dock icon, filled according to
530 * 'progress' (0.0 <= progress <= 1.0).
531 * Called with progress < 0.0 or progress > 1.0, restores the original
533 **********************************************************************/
534 - (void) UpdateDockIcon: (float) progress
538 NSBitmapImageRep * bmp;
540 uint32_t black = htonl( 0x000000FF );
541 uint32_t red = htonl( 0xFF0000FF );
542 uint32_t white = htonl( 0xFFFFFFFF );
543 int row_start, row_end;
546 /* Get application original icon */
547 icon = [NSImage imageNamed: @"NSApplicationIcon"];
549 if( progress < 0.0 || progress > 1.0 )
551 [NSApp setApplicationIconImage: icon];
555 /* Get it in a raw bitmap form */
556 tiff = [icon TIFFRepresentationUsingCompression:
557 NSTIFFCompressionNone factor: 1.0];
558 bmp = [NSBitmapImageRep imageRepWithData: tiff];
560 /* Draw the progression bar */
561 /* It's pretty simple (ugly?) now, but I'm no designer */
563 row_start = 3 * (int) [bmp size].height / 4;
564 row_end = 7 * (int) [bmp size].height / 8;
566 for( i = row_start; i < row_start + 2; i++ )
568 pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
569 for( j = 0; j < (int) [bmp size].width; j++ )
574 for( i = row_start + 2; i < row_end - 2; i++ )
576 pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
579 for( j = 2; j < (int) [bmp size].width - 2; j++ )
581 if( j < 2 + (int) ( ( [bmp size].width - 4.0 ) * progress ) )
593 for( i = row_end - 2; i < row_end; i++ )
595 pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
596 for( j = 0; j < (int) [bmp size].width; j++ )
602 /* Now update the dock icon */
603 tiff = [bmp TIFFRepresentationUsingCompression:
604 NSTIFFCompressionNone factor: 1.0];
605 icon = [[NSImage alloc] initWithData: tiff];
606 [NSApp setApplicationIconImage: icon];
610 - (void) convertTimer: (NSTimer *) timer
613 hb_get_state( fHandle, &s );
616 #define p s.param.working
617 case HB_STATE_WORKING:
619 float progress_total = ( p.progress + p.job_cur - 1 ) / p.job_count;
620 NSMutableString * string = [NSMutableString
621 stringWithFormat: @"Converting: %.1f %%",
622 100.0 * progress_total];
625 [string appendFormat: @" (%.1f fps, ", p.rate_avg];
628 [string appendFormat: @"%d hour%s %d min%s",
629 p.hours, p.hours == 1 ? "" : "s",
630 p.minutes, p.minutes == 1 ? "" : "s"];
632 else if( p.minutes > 0 )
634 [string appendFormat: @"%d min%s %d sec%s",
635 p.minutes, p.minutes == 1 ? "" : "s",
636 p.seconds, p.seconds == 1 ? "" : "s"];
640 [string appendFormat: @"%d second%s",
641 p.seconds, p.seconds == 1 ? "" : "s"];
643 [string appendString: @" left)"];
645 [fConvertInfoString setStringValue: string];
646 [fConvertIndicator setIndeterminate: NO];
647 [fConvertIndicator setDoubleValue: 100.0 * progress_total];
648 [self UpdateDockIcon: progress_total];
653 #define p s.param.muxing
654 case HB_STATE_MUXING:
656 NSMutableString * string = [NSMutableString
657 stringWithFormat: @"Muxing..."];
658 [fConvertInfoString setStringValue: string];
659 [fConvertIndicator setIndeterminate: YES];
660 [fConvertIndicator startAnimation: nil];
661 [self UpdateDockIcon: 1.0];
666 case HB_STATE_WORKDONE:
669 [fConvertIndicator setIndeterminate: NO];
670 [fConvertIndicator setDoubleValue: 0.0];
671 [self UpdateDockIcon: -1.0];
672 [self convertEnable: YES];
674 #define p s.param.workdone
678 [fConvertInfoString setStringValue: @"Done."];
680 case HB_ERROR_CANCELED:
681 [fConvertInfoString setStringValue: @"Canceled."];
683 case HB_ERROR_UNKNOWN:
684 [fConvertInfoString setStringValue: @"Unknown Error."];
690 while( ( job = hb_job( fHandle, 0 ) ) )
692 hb_rem( fHandle, job );