1 #import "ExpressController.h"
3 #define INSERT_STRING @"Insert a DVD"
4 #define TOOLBAR_START @"TOOLBAR_START"
5 #define TOOLBAR_PAUSE @"TOOLBAR_PAUSE"
6 #define TOOLBAR_OPEN @"TOOLBAR_OPEN"
7 #define TOOLBAR_ADVANCED @"TOOLBAR_ADVANCED"
9 #define p fState->param
11 @interface ExpressController (Private)
13 - (void) openUpdateDrives: (NSDictionary *) drives;
14 - (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
15 returnCode contextInfo: (void *) contextInfo;
16 - (void) openEnable: (BOOL) b;
18 - (id) updatePopUpIcon: (id) value;
20 - (void) convertEnable: (BOOL) b;
24 @implementation ExpressController
26 /***********************************************************************
27 * Application delegate methods
28 **********************************************************************/
31 NSEnumerator * enumerator;
33 /* NSToolbar initializations */
34 fToolbar = [[NSToolbar alloc] initWithIdentifier: @"InstantHandBrake Toolbar"];
35 [fToolbar setDelegate: self];
36 [fToolbar setAllowsUserCustomization: YES];
37 [fToolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
38 [fToolbar setVisible:NO];
39 [fWindow setShowsToolbarButton:NO];
40 [fWindow setToolbar: fToolbar];
42 /* Show the "Open DVD" interface */
43 fDriveDetector = [[DriveDetector alloc] initWithCallback: self
44 selector: @selector( openUpdateDrives: )];
46 [self openEnable: YES];
47 [fWindow setContentSize: [fOpenView frame].size];
48 [fWindow setContentView: fOpenView];
50 [fWindow makeKeyAndOrderFront: nil];
52 /* NSTableView initializations */
53 NSButtonCell * buttonCell;
54 NSTableColumn * tableColumn;
55 enumerator = [[fConvertTableView tableColumns] objectEnumerator];
56 while( ( tableColumn = [enumerator nextObject] ) )
58 [tableColumn setEditable: NO];
60 tableColumn = [fConvertTableView tableColumnWithIdentifier: @"Check"];
61 buttonCell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
62 [buttonCell setEditable: YES];
63 [buttonCell setButtonType: NSSwitchButton];
64 [tableColumn setDataCell: buttonCell];
67 fConvertFolderString = [NSString stringWithFormat:@"%@/Desktop", NSHomeDirectory()];
68 [fConvertFolderString retain];
71 - (void) applicationWillFinishLaunching: (NSNotification *) n
73 fCore = [[HBCore alloc] init];
74 [fCore openInDebugMode:NO checkForUpdates:NO];
75 fHandle = [fCore hb_handle];
76 fState = [fCore hb_state];
77 fList = hb_get_titles( fHandle );
79 [[NSNotificationCenter defaultCenter] addObserver:self
80 selector:@selector(scanningSource:)
81 name:@"HBCoreScanningNotification" object:nil];
83 [[NSNotificationCenter defaultCenter] addObserver:self
84 selector:@selector(scanDone:)
85 name:@"HBCoreScanDoneNotification" object:nil];
87 [[NSNotificationCenter defaultCenter] addObserver:self
88 selector:@selector(muxing:)
89 name:@"HBCoreMuxingNotification" object:nil];
91 [[NSNotificationCenter defaultCenter] addObserver:self
92 selector:@selector(working:)
93 name:@"HBCoreWorkingNotification" object:nil];
95 [[NSNotificationCenter defaultCenter] addObserver:self
96 selector:@selector(workDone:)
97 name:@"HBCoreWorkDoneNotification" object:nil];
100 - (void) applicationWillTerminate: (NSNotification *) n
105 - (NSToolbarItem *) toolbar: (NSToolbar *) toolbar itemForItemIdentifier: (NSString *) ident
106 willBeInsertedIntoToolbar: (BOOL) flag
108 NSToolbarItem * item;
109 item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
111 if ([ident isEqualToString: TOOLBAR_START])
113 [item setLabel: NSLocalizedString(@"Convert", "Convert")];
114 [item setPaletteLabel: NSLocalizedString(@"Convert/Cancel", @"Convert/Cancel")];
115 [item setImage: [NSImage imageNamed: @"Play"]];
116 [item setTarget: self];
117 [item setAction: @selector(convertGo:)];
119 else if ([ident isEqualToString: TOOLBAR_PAUSE])
121 [item setLabel: NSLocalizedString(@"Pause", "Pause")];
122 [item setPaletteLabel: NSLocalizedString(@"Pause/Resume", @"Pause/Resume")];
123 [item setImage: [NSImage imageNamed: @"Pause"]];
124 [item setTarget: self];
125 [item setAction: @selector(pauseGo:)];
127 else if ([ident isEqualToString: TOOLBAR_OPEN])
129 [item setLabel: NSLocalizedString(@"Open...", "Open...")];
130 [item setPaletteLabel: NSLocalizedString(@"Open Another Source", "Open Another Source")];
131 [item setImage: [NSImage imageNamed: @"Open"]];
132 [item setTarget: self];
133 [item setAction: @selector(openShow:)];
144 - (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
146 return [NSArray arrayWithObjects: TOOLBAR_START, TOOLBAR_PAUSE,
147 NSToolbarFlexibleSpaceItemIdentifier, TOOLBAR_OPEN, nil];
150 - (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
152 return [NSArray arrayWithObjects: TOOLBAR_START, TOOLBAR_PAUSE,
153 TOOLBAR_OPEN, NSToolbarFlexibleSpaceItemIdentifier, nil];
156 /***********************************************************************
157 * Tableview datasource methods
158 **********************************************************************/
159 - (int) numberOfRowsInTableView: (NSTableView *) t
164 return hb_list_count( fList );
167 - (id) tableView:(NSTableView *) t objectValueForTableColumn:
168 (NSTableColumn *) col row: (int) row
170 if( [[col identifier] isEqualToString: @"Check"] )
172 return [fConvertCheckArray objectAtIndex: row];
176 hb_title_t * title = hb_list_item( fList, row );
177 if( [[col identifier] isEqualToString: @"Title"] )
179 return [@"Title " stringByAppendingFormat: @"%d",
182 else if( [[col identifier] isEqualToString: @"Duration"] )
184 if( title->hours > 0 )
186 return [NSString stringWithFormat:
187 @"%d hour%s %d min%s", title->hours,
188 title->hours > 1 ? "s" : "", title->minutes,
189 title->minutes > 1 ? "s": ""];
191 else if( title->minutes > 0 )
193 return [NSString stringWithFormat:
194 @"%d min%s %d sec%s", title->minutes,
195 title->minutes > 1 ? "s" : "", title->seconds,
196 title->seconds > 1 ? "s": ""];
200 return [NSString stringWithFormat: @"%d seconds",
208 - (void) tableView: (NSTableView *) t setObjectValue: (id) object
209 forTableColumn: (NSTableColumn *) col row: (int) row
211 if( [[col identifier] isEqualToString: @"Check"] )
213 [fConvertCheckArray replaceObjectAtIndex: row withObject: object];
217 /***********************************************************************
218 * User events methods
219 **********************************************************************/
220 - (void) openShow: (id) sender
222 NSRect frame = [fWindow frame];
223 float offset = [fConvertView frame].size.height -
224 [fOpenView frame].size.height;
226 frame.origin.y += offset;
227 frame.size.height -= offset;
228 [fWindow setContentView: fEmptyView];
229 [fWindow setFrame: frame display: YES animate: YES];
230 [fToolbar setVisible:NO];
231 [fOpenProgressField setStringValue: @""];
232 [fWindow setContentView: fOpenView];
234 [fDriveDetector run];
237 - (void) openMatrixChanged: (id) sender
239 [self openEnable: YES];
240 if( [fOpenMatrix selectedRow] )
242 [self openBrowse: self];
246 - (void) openBrowse: (id) sender
248 NSOpenPanel * panel = [NSOpenPanel openPanel];
249 [panel setAllowsMultipleSelection: NO];
250 [panel setCanChooseFiles: YES];
251 [panel setCanChooseDirectories: YES ];
252 [panel beginSheetForDirectory: nil file: nil types: nil
253 modalForWindow: fWindow modalDelegate: self
254 didEndSelector: @selector( openBrowseDidEnd:returnCode:contextInfo: )
258 - (void) openGo: (id) sender
260 [self openEnable: NO];
261 [fOpenIndicator setIndeterminate: YES];
262 [fOpenIndicator startAnimation: nil];
263 [fOpenProgressField setStringValue: @"Opening..."];
264 [fDriveDetector stop];
266 if( [fOpenMatrix selectedRow] )
268 hb_scan( fHandle, [fOpenFolderString UTF8String], 0 );
272 hb_scan( fHandle, [[fDrives objectForKey: [fOpenPopUp
273 titleOfSelectedItem]] UTF8String], 0 );
277 - (void) selectFolderSheetShow: (id) sender
279 NSOpenPanel * panel = [NSOpenPanel openPanel];
281 [panel setPrompt: NSLocalizedString(@"Select", "Convert -> Save panel prompt")];
282 [panel setAllowsMultipleSelection: NO];
283 [panel setCanChooseFiles: NO];
284 [panel setCanChooseDirectories: YES];
285 [panel setCanCreateDirectories: YES];
287 [panel beginSheetForDirectory: nil file: nil types: nil
288 modalForWindow: fWindow modalDelegate: self didEndSelector:
289 @selector(selectFolderSheetClosed:returnCode:contextInfo:) contextInfo: nil];
292 - (void) selectFolderSheetClosed: (NSOpenPanel *) sheet returnCode: (int)
293 returnCode contextInfo: (void *) contextInfo
295 if( returnCode != NSOKButton )
298 if( fConvertFolderString )
299 [fConvertFolderString release];
300 fConvertFolderString = [[[sheet filenames] objectAtIndex: 0] retain];
301 [[fConvertFolderPopUp itemAtIndex: 0] setTitle: [fConvertFolderString lastPathComponent]];
302 [fConvertFolderPopUp selectItemAtIndex:0];
304 NSMenuItem * item = [fConvertFolderPopUp itemAtIndex: 0];
305 [item setImage: [self updatePopUpIcon:fConvertFolderString]];
309 - (void) convertGo: (id) sender
313 for( i = 0; i < hb_list_count( fList ); i++ )
315 if( ![[fConvertCheckArray objectAtIndex: i] boolValue] )
318 hb_title_t * title = hb_list_item( fList, i );
319 hb_job_t * job = title->job;
322 int aspect = title->aspect;
323 if( [fConvertAspectPopUp indexOfSelectedItem] == 1)
325 aspect = 4 * HB_ASPECT_BASE / 3;
329 job->vbitrate = 1000;
330 if( [fConvertMaxWidthPopUp indexOfSelectedItem] == 1)
335 job->deinterlace = 1;
339 hb_set_size( job, aspect, pixels );
341 } while(job->width > maxwidth);
343 if( [fConvertFormatPopUp indexOfSelectedItem] == 0 )
346 job->mux = HB_MUX_IPOD;
347 job->vcodec = HB_VCODEC_X264;
348 job->h264_level = 30;
349 job->x264opts = "bframes=0:cabac=0:ref=1:vbv-maxrate=1500:vbv-bufsize=2000:analyse=all:me=umh:subq=6:no-fast-pskip=1";
351 else if( [fConvertFormatPopUp indexOfSelectedItem] == 1 )
354 job->mux = HB_MUX_PSP;
355 job->vrate = 27000000;
356 job->vrate_base = 900900; /* 29.97 fps */
357 job->vcodec = HB_VCODEC_FFMPEG;
362 aspect = 16 * HB_ASPECT_BASE / 9;
364 if( [fConvertAspectPopUp indexOfSelectedItem] )
369 hb_set_size( job, aspect, pixels );
373 job->mux = HB_MUX_MP4;
374 job->vcodec = HB_VCODEC_X264;
375 job->x264opts = "bframes=3:ref=1:subq=5:me=umh:no-fast-pskip=1:trellis=1:cabac=0";
377 job->vbitrate = 1500;
380 job->vquality = -1.0;
384 /* Audio selection */
386 lang = [[fConvertAudioPopUp titleOfSelectedItem] UTF8String];
388 for( j = 0; j < hb_list_count( title->list_audio ); j++ )
390 /* Choose the first track that matches the language */
391 audio = hb_list_item( title->list_audio, j );
392 if( !strcmp( lang, audio->lang_simple ) )
398 if( job->audios[0] == -1 )
400 /* If the language isn't available in this title, choose
406 job->audio_mixdowns[0] = HB_AMIXDOWN_DOLBYPLII;
408 /* Subtitle selection */
409 hb_subtitle_t * subtitle;
410 lang = [[fConvertSubtitlePopUp titleOfSelectedItem] UTF8String];
412 for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
414 /* Choose the first track that matches the language */
415 subtitle = hb_list_item( title->list_subtitle, j );
416 if( !strcmp( lang, subtitle->lang ) )
423 job->file = strdup( [[NSString stringWithFormat:
424 @"%@/%s - Title %d.m4v", fConvertFolderString,
425 title->name, title->index] UTF8String] );
426 hb_add( fHandle, job );
431 [self convertEnable: NO];
434 - (void) convertCancel: (id) sender
437 [self convertEnable: YES];
442 /***********************************************************************
444 **********************************************************************/
446 @implementation ExpressController (Private)
448 - (void) openUpdateDrives: (NSDictionary *) drives
454 fDrives = [[NSDictionary alloc] initWithDictionary: drives];
457 NSEnumerator * enumerator = [fDrives keyEnumerator];
458 [fOpenPopUp removeAllItems];
459 while( ( device = [enumerator nextObject] ) )
461 [fOpenPopUp addItemWithTitle: device];
464 if( ![fOpenPopUp numberOfItems] )
466 [fOpenPopUp addItemWithTitle: INSERT_STRING];
468 [fOpenPopUp selectItemAtIndex: 0];
469 if( [fOpenMatrix isEnabled] )
471 [self openEnable: YES];
475 - (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
476 returnCode contextInfo: (void *) contextInfo
478 if( returnCode != NSOKButton )
481 if( fOpenFolderString )
482 [fOpenFolderString release];
483 fOpenFolderString = [[[sheet filenames] objectAtIndex: 0] retain];
484 [fOpenFolderField setStringValue: [fOpenFolderString lastPathComponent]];
488 - (BOOL)validateToolbarItem: (NSToolbarItem *) toolbarItem
490 NSString * ident = [toolbarItem itemIdentifier];
492 if ([ident isEqualToString: TOOLBAR_START] && [HBStateWorking isEqualToString:[fCore state]])
494 [toolbarItem setAction: @selector(convertCancel:)];
495 [toolbarItem setLabel:NSLocalizedString(@"Cancel", @"Cancel")];
498 else if ([ident isEqualToString: TOOLBAR_START] && [HBStateWorkDone isEqualToString:[fCore state]])
500 [toolbarItem setAction: @selector(convertGo:)];
501 [toolbarItem setLabel:NSLocalizedString(@"Convert", @"Convert")];
504 else if ([ident isEqualToString: TOOLBAR_OPEN] && [HBStateWorking isEqualToString:[fCore state]])
512 - (void) openEnable: (BOOL) b
514 [fOpenMatrix setEnabled: b];
515 [fOpenPopUp setEnabled: b];
516 [fOpenFolderField setEnabled: b];
517 [fOpenBrowseButton setEnabled: b];
518 [fOpenGoButton setEnabled: b];
522 if( [fOpenMatrix selectedRow] )
524 [fOpenPopUp setEnabled: NO];
528 [fOpenFolderField setEnabled: NO];
529 [fOpenBrowseButton setEnabled: NO];
530 if( [[fOpenPopUp titleOfSelectedItem]
531 isEqualToString: INSERT_STRING] )
533 [fOpenGoButton setEnabled: NO];
539 - (void) scanningSource: (NSNotification *) n
541 [fOpenIndicator setIndeterminate: NO];
542 [fOpenIndicator setDoubleValue: 100.0 *
543 ( (float) p.scanning.title_cur - 0.5 ) / p.scanning.title_count];
544 [fOpenProgressField setStringValue: [NSString
545 stringWithFormat: @"Scanning title %d of %d...",
546 p.scanning.title_cur, p.scanning.title_count]];
549 - (void) scanDone: (NSNotification *) n
551 [fOpenIndicator setIndeterminate: NO];
552 [fOpenIndicator setDoubleValue: 0.0];
554 [self openEnable: YES];
556 if( hb_list_count( fList ) )
562 [fDriveDetector run];
563 [fOpenProgressField setStringValue: NSLocalizedString(@"No Title Found...",@"No Title Found...")];
571 fConvertCheckArray = [[NSMutableArray alloc] initWithCapacity:
572 hb_list_count( fList )];
573 [fConvertAudioPopUp removeAllItems];
574 [fConvertSubtitlePopUp removeAllItems];
575 [fConvertSubtitlePopUp addItemWithTitle: @"None"];
576 for( i = 0; i < hb_list_count( fList ); i++ )
578 /* Default is to convert titles longer than 30 minutes. */
579 hb_title_t * title = hb_list_item( fList, i );
580 [fConvertCheckArray addObject: [NSNumber numberWithBool:
581 ( 60 * title->hours + title->minutes > 30 )]];
583 /* Update audio popup */
585 for( j = 0; j < hb_list_count( title->list_audio ); j++ )
587 audio = hb_list_item( title->list_audio, j );
588 [fConvertAudioPopUp addItemWithTitle:
589 [NSString stringWithUTF8String: audio->lang_simple]];
591 [fConvertAudioPopUp selectItemWithTitle: @"English"];
593 if ( [fConvertAudioPopUp selectedItem] == nil )
594 [fConvertAudioPopUp selectItemWithTitle: @"Unknown"];
596 /* Update subtitle popup */
597 hb_subtitle_t * subtitle;
598 for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
600 subtitle = hb_list_item( title->list_subtitle, j );
601 [fConvertSubtitlePopUp addItemWithTitle:
602 [NSString stringWithUTF8String: subtitle->lang]];
605 [fConvertTableView reloadData];
607 NSRect frame = [fWindow frame];
608 float offset = [fConvertView frame].size.height -
609 [fOpenView frame].size.height;
610 float hoffset = [fConvertView frame].size.width -
611 [fOpenView frame].size.width;
612 frame.origin.y -= offset;
613 frame.size.height += offset;
614 frame.size.width += hoffset;
615 [fWindow setContentView: fEmptyView];
616 [fWindow setFrame: frame display: YES animate: YES];
617 [fToolbar setVisible:YES];
618 [fWindow setContentView: fConvertView];
620 NSMenuItem * item = [fConvertFolderPopUp itemAtIndex: 0];
621 [item setTitle: [fConvertFolderString lastPathComponent]];
622 [item setImage: [self updatePopUpIcon:fConvertFolderString]];
624 [self convertEnable: YES];
627 - (void) convertEnable: (BOOL) b
629 [fConvertTableView setEnabled: b];
630 [fConvertFolderPopUp setEnabled: b];
631 [fConvertFormatPopUp setEnabled: b];
632 [fConvertAspectPopUp setEnabled: b];
633 [fConvertMaxWidthPopUp setEnabled: b];
634 [fConvertAudioPopUp setEnabled: b];
635 [fConvertSubtitlePopUp setEnabled: b];
638 /***********************************************************************
640 ***********************************************************************
641 * Shows a progression bar on the dock icon, filled according to
642 * 'progress' (0.0 <= progress <= 1.0).
643 * Called with progress < 0.0 or progress > 1.0, restores the original
645 **********************************************************************/
646 - (void) UpdateDockIcon: (float) progress
650 NSBitmapImageRep * bmp;
652 uint32_t black = htonl( 0x000000FF );
653 uint32_t red = htonl( 0xFF0000FF );
654 uint32_t white = htonl( 0xFFFFFFFF );
655 int row_start, row_end;
658 /* Get application original icon */
659 icon = [NSImage imageNamed: @"NSApplicationIcon"];
661 if( progress < 0.0 || progress > 1.0 )
663 [NSApp setApplicationIconImage: icon];
667 /* Get it in a raw bitmap form */
668 tiff = [icon TIFFRepresentationUsingCompression:
669 NSTIFFCompressionNone factor: 1.0];
670 bmp = [NSBitmapImageRep imageRepWithData: tiff];
672 /* Draw the progression bar */
673 /* It's pretty simple (ugly?) now, but I'm no designer */
675 row_start = 3 * (int) [bmp size].height / 4;
676 row_end = 7 * (int) [bmp size].height / 8;
678 for( i = row_start; i < row_start + 2; i++ )
680 pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
681 for( j = 0; j < (int) [bmp size].width; j++ )
686 for( i = row_start + 2; i < row_end - 2; i++ )
688 pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
691 for( j = 2; j < (int) [bmp size].width - 2; j++ )
693 if( j < 2 + (int) ( ( [bmp size].width - 4.0 ) * progress ) )
705 for( i = row_end - 2; i < row_end; i++ )
707 pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
708 for( j = 0; j < (int) [bmp size].width; j++ )
714 /* Now update the dock icon */
715 tiff = [bmp TIFFRepresentationUsingCompression:
716 NSTIFFCompressionNone factor: 1.0];
717 icon = [[NSImage alloc] initWithData: tiff];
718 [NSApp setApplicationIconImage: icon];
722 - (id) updatePopUpIcon: (id) value
729 icon = [[NSWorkspace sharedWorkspace] iconForFile: value];
731 [icon setScalesWhenResized: YES];
732 [icon setSize: NSMakeSize(16.0, 16.0)];
737 - (void) working: (NSNotification *) n
739 float progress_total = ( p.working.progress + p.working.job_cur - 1 ) / p.working.job_count;
740 NSMutableString * string = [NSMutableString stringWithFormat: @"Converting: %.1f %%", 100.0 * progress_total];
742 if( p.working.seconds > -1 )
744 [string appendFormat: @" (%.1f fps, ", p.working.rate_avg];
745 if( p.working.hours > 0 )
747 [string appendFormat: @"%d hour%s %d min%s",
748 p.working.hours, p.working.hours == 1 ? "" : "s",
749 p.working.minutes, p.working.minutes == 1 ? "" : "s"];
751 else if( p.working.minutes > 0 )
753 [string appendFormat: @"%d min%s %d sec%s",
754 p.working.minutes, p.working.minutes == 1 ? "" : "s",
755 p.working.seconds, p.working.seconds == 1 ? "" : "s"];
759 [string appendFormat: @"%d second%s",
760 p.working.seconds, p.working.seconds == 1 ? "" : "s"];
762 [string appendString: @" left)"];
765 [fConvertInfoString setStringValue: string];
766 [fConvertIndicator setIndeterminate: NO];
767 [fConvertIndicator setDoubleValue: 100.0 * progress_total];
768 [self UpdateDockIcon: progress_total];
771 - (void) muxing: (NSNotification *) n
773 [fConvertInfoString setStringValue: NSLocalizedString(@"Muxing...",@"Muxing...")];
774 [fConvertIndicator setIndeterminate: YES];
775 [fConvertIndicator startAnimation: nil];
776 [self UpdateDockIcon: 1.0];
779 - (void) workDone: (NSNotification *) n
781 [fConvertIndicator setIndeterminate: NO];
782 [fConvertIndicator setDoubleValue: 0.0];
783 [self UpdateDockIcon: -1.0];
784 [self convertEnable: YES];
786 [fConvertInfoString setStringValue: NSLocalizedString(@"Done.",@"Done.")];
788 [fCore removeAllJobs];