OSDN Git Service

Don't drop subtitles when crossing PTS discontinuities by using buffer sequence numbe...
[handbrake-jp/handbrake-jp-git.git] / macosx / ExpressController.m
1 #import "ExpressController.h"
2 #import "DriveDetector.h"
3
4 #define INSERT_STRING @"Insert a DVD"
5
6 @interface ExpressController (Private)
7
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;
13
14 - (void) convertShow;
15 - (void) convertEnable: (BOOL) b;
16 - (void) convertTimer: (NSTimer *) timer;
17
18 @end
19
20 @implementation ExpressController
21
22 /***********************************************************************
23  * Application delegate methods
24  **********************************************************************/
25 - (void) awakeFromNib
26 {
27     NSEnumerator * enumerator;
28
29     /* Show the "Open DVD" interface */
30     fDriveDetector = [[DriveDetector alloc] initWithCallback: self
31         selector: @selector( openUpdateDrives: )];
32     [fDriveDetector run];
33     [self openEnable: YES];
34     [fWindow setContentSize: [fOpenView frame].size];
35     [fWindow setContentView: fOpenView];
36     [fWindow center];
37     [fWindow makeKeyAndOrderFront: nil];
38
39     /* NSTableView initializations */
40      NSButtonCell * buttonCell;
41      NSTableColumn * tableColumn;
42      enumerator = [[fConvertTableView tableColumns] objectEnumerator];
43      while( ( tableColumn = [enumerator nextObject] ) )
44      {
45          [tableColumn setEditable: NO];
46      }
47      tableColumn = [fConvertTableView tableColumnWithIdentifier: @"Check"];
48      buttonCell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
49     [buttonCell setEditable: YES];
50     [buttonCell setButtonType: NSSwitchButton];
51     [tableColumn setDataCell: buttonCell];
52
53     /* Preferences */
54     fConvertFolderString = [@"~/Movies" stringByExpandingTildeInPath];
55     [fConvertFolderString retain];
56 }
57
58 - (void) applicationWillFinishLaunching: (NSNotification *) n
59 {
60     fHandle = hb_init_express( HB_DEBUG_NONE, 0 );
61     fList   = hb_get_titles( fHandle );
62 }
63
64 - (void) applicationWillTerminate: (NSNotification *) n
65 {
66     hb_close( &fHandle );
67 }
68
69 /***********************************************************************
70  * Tableview datasource methods
71  **********************************************************************/
72 - (int) numberOfRowsInTableView: (NSTableView *) t
73 {
74     if( !fHandle )
75         return 0;
76
77     return hb_list_count( fList );
78 }
79
80 - (id) tableView:(NSTableView *) t objectValueForTableColumn:
81     (NSTableColumn *) col row: (int) row
82 {
83     if( [[col identifier] isEqualToString: @"Check"] )
84     {
85         return [fConvertCheckArray objectAtIndex: row];
86     }
87     else
88     {
89         hb_title_t * title = hb_list_item( fList, row );
90         if( [[col identifier] isEqualToString: @"Title"] )
91         {
92             return [@"Title " stringByAppendingFormat: @"%d",
93                     title->index];
94         }
95         else if( [[col identifier] isEqualToString: @"Duration"] )
96         {
97             if( title->hours > 0 )
98             {
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": ""];
103             }
104             else if( title->minutes > 0 )
105             {
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": ""];
110             }
111             else
112             {
113                 return [NSString stringWithFormat: @"%d seconds",
114                         title->seconds];
115             }
116         }
117     }
118     return nil;
119 }
120
121 - (void) tableView: (NSTableView *) t setObjectValue: (id) object
122     forTableColumn: (NSTableColumn *) col row: (int) row
123 {
124     if( [[col identifier] isEqualToString: @"Check"] )
125     {
126         [fConvertCheckArray replaceObjectAtIndex: row withObject: object];
127     }
128 }
129
130 /***********************************************************************
131  * User events methods
132  **********************************************************************/
133 - (void) openShow: (id) sender
134 {
135     NSRect frame  = [fWindow frame];
136     float  offset = [fConvertView frame].size.height -
137                     [fOpenView frame].size.height;
138
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];
144
145     [fDriveDetector run];
146 }
147
148 - (void) openMatrixChanged: (id) sender
149 {
150     [self openEnable: YES];
151     if( [fOpenMatrix selectedRow] )
152     {
153         [self openBrowse: self];
154     }
155 }
156
157 - (void) openBrowse: (id) sender
158 {
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: )
166         contextInfo: nil];                                                      
167 }
168
169 - (void) openGo: (id) sender
170 {
171     [self openEnable: NO];
172     [fOpenIndicator setIndeterminate: YES];
173     [fOpenIndicator startAnimation: nil];
174     [fOpenProgressField setStringValue: @"Opening..."];
175     [fDriveDetector stop];
176
177     if( [fOpenMatrix selectedRow] )
178     {
179         hb_scan( fHandle, [fOpenFolderString UTF8String], 0 );
180     }
181     else
182     {
183         hb_scan( fHandle, [[fDrives objectForKey: [fOpenPopUp
184                  titleOfSelectedItem]] UTF8String], 0 );
185     }
186
187     NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0
188         target: self selector: @selector( openTimer: ) userInfo: nil
189         repeats: YES];
190 }
191
192 - (void) convertGo: (id) sender
193 {
194     int i, j;
195
196     for( i = 0; i < hb_list_count( fList ); i++ )
197     {
198         if( ![[fConvertCheckArray objectAtIndex: i] boolValue] )
199             continue;
200
201         hb_title_t * title = hb_list_item( fList, i );
202         hb_job_t   * job   = title->job;
203
204         int pixels = 307200;
205                 int aspect = title->aspect;
206                 if( [fConvertAspectPopUp indexOfSelectedItem] == 1)
207                 {
208             aspect = 4 * HB_ASPECT_BASE / 3;
209                 }
210
211                 int maxwidth = 640;
212                 job->vbitrate = 1000;
213                 if( [fConvertMaxWidthPopUp indexOfSelectedItem] == 1)
214                 {
215             maxwidth = 320;
216                         job->vbitrate = 500;
217                 }
218                 job->deinterlace = 1;
219                 
220                 do
221                 {
222                         hb_set_size( job, aspect, pixels );
223                         pixels -= 10;
224                 } while(job->width > maxwidth);
225                 
226         if( [fConvertFormatPopUp indexOfSelectedItem] == 0 )
227         {
228             /* iPod / H.264 */
229             job->mux      = HB_MUX_IPOD;
230             job->vcodec   = HB_VCODEC_X264;
231                         job->h264_level = 30;
232         }
233         else if( [fConvertFormatPopUp indexOfSelectedItem] == 1 )
234         {
235             /* iPod / MPEG-4 */
236             job->mux      = HB_MUX_MP4;
237             job->vcodec   = HB_VCODEC_FFMPEG;
238         }
239         else
240         {
241             /* PSP / MPEG-4 */
242             job->mux        = HB_MUX_PSP;
243             job->vrate      = 27000000;
244             job->vrate_base = 900900;   /* 29.97 fps */
245             job->vcodec     = HB_VCODEC_FFMPEG;
246             job->vbitrate   = 600;
247             pixels          = 76800;
248             job->arate      = 24000;
249             job->abitrate   = 96;
250             aspect          = 16 * HB_ASPECT_BASE / 9;
251
252                         if( [fConvertAspectPopUp indexOfSelectedItem] )
253                         {
254                                 aspect = -1;
255                         }
256
257                         hb_set_size( job, aspect, pixels );
258         }
259
260         job->vquality = -1.0;
261
262         const char * lang;
263
264         /* Audio selection */
265         hb_audio_t * audio;
266         lang = [[fConvertAudioPopUp titleOfSelectedItem] UTF8String];
267         job->audios[0] = -1;
268         for( j = 0; j < hb_list_count( title->list_audio ); j++ )
269         {
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 ) )
273             {
274                 job->audios[0] = j;
275                 break;
276             }
277         }
278         if( job->audios[0] == -1 )
279         {
280             /* If the language isn't available in this title, choose
281                the first track */
282             job->audios[0] = 0;
283         }
284         job->audios[1] = -1;
285
286         job->audio_mixdowns[0] = HB_AMIXDOWN_DOLBYPLII;
287         
288         /* Subtitle selection */
289         hb_subtitle_t * subtitle;
290         lang = [[fConvertSubtitlePopUp titleOfSelectedItem] UTF8String];
291         job->subtitle = -1;
292         for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
293         {
294             /* Choose the first track that matches the language */
295             subtitle = hb_list_item( title->list_subtitle, j );
296             if( !strcmp( lang, subtitle->lang ) )
297             {
298                 job->subtitle = j;
299                 break;
300             }
301         }
302         
303         job->file = strdup( [[NSString stringWithFormat:                 
304                 @"%@/%s - Title %d.m4v", fConvertFolderString,      
305                 title->name, title->index] UTF8String] );
306         hb_add( fHandle, job );
307     }
308
309     hb_start( fHandle );
310
311     NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0
312         target: self selector: @selector( convertTimer: ) userInfo: nil
313         repeats: YES];
314
315     [self convertEnable: NO];
316 }
317
318 - (void) convertCancel: (id) sender
319 {
320     hb_stop( fHandle );
321     [self convertEnable: YES];
322 }
323
324 @end
325
326 /***********************************************************************
327  * Private methods
328  **********************************************************************/
329
330 @implementation ExpressController (Private)
331
332 - (void) openUpdateDrives: (NSDictionary *) drives
333 {
334     if( fDrives )
335     {
336         [fDrives release];
337     }
338     fDrives = [[NSDictionary alloc] initWithDictionary: drives];
339
340     NSString * device;
341     NSEnumerator * enumerator = [fDrives keyEnumerator];
342     [fOpenPopUp removeAllItems];
343     while( ( device = [enumerator nextObject] ) )
344     {
345         [fOpenPopUp addItemWithTitle: device];
346     }
347
348     if( ![fOpenPopUp numberOfItems] )
349     {
350         [fOpenPopUp addItemWithTitle: INSERT_STRING];
351     }
352     [fOpenPopUp selectItemAtIndex: 0];
353     if( [fOpenMatrix isEnabled] )
354     {
355         [self openEnable: YES];
356     }
357 }
358
359 - (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
360     returnCode contextInfo: (void *) contextInfo
361 {
362     if( returnCode != NSOKButton )
363         return;
364
365     if( fOpenFolderString )
366         [fOpenFolderString release];
367     fOpenFolderString = [[[sheet filenames] objectAtIndex: 0] retain];
368     [fOpenFolderField setStringValue: [fOpenFolderString lastPathComponent]];
369     [self openGo: self];
370 }
371
372 - (void) openEnable: (BOOL) b
373 {
374     [fOpenMatrix       setEnabled: b];
375     [fOpenPopUp        setEnabled: b];
376     [fOpenFolderField  setEnabled: b];
377     [fOpenBrowseButton setEnabled: b];
378     [fOpenGoButton     setEnabled: b];
379
380     if( b )
381     {
382         if( [fOpenMatrix selectedRow] )
383         {
384             [fOpenPopUp setEnabled: NO];
385         }
386         else
387         {
388             [fOpenFolderField  setEnabled: NO];
389             [fOpenBrowseButton setEnabled: NO];
390             if( [[fOpenPopUp titleOfSelectedItem]
391                     isEqualToString: INSERT_STRING] )
392             {
393                 [fOpenGoButton setEnabled: NO];
394             }
395         }
396     }
397 }
398
399 - (void) openTimer: (NSTimer *) timer
400 {
401     hb_state_t s;
402     hb_get_state( fHandle, &s );
403     switch( s.state )
404     {
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]];
413             break;
414 #undef p
415
416         case HB_STATE_SCANDONE:
417             [timer invalidate];
418
419             [fOpenIndicator setIndeterminate: NO];
420             [fOpenIndicator setDoubleValue: 0.0];
421             [self openEnable: YES];
422
423             if( hb_list_count( fList ) )
424             {
425                 [self convertShow];
426             }
427             else
428             {
429                 [fDriveDetector run];
430             }
431             break;
432
433         default:
434             break;
435     }
436 }
437
438 - (void) convertShow
439 {
440     int i, j;
441
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++ )
448     {
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 )]];
453
454         /* Update audio popup */
455         hb_audio_t * audio;
456         for( j = 0; j < hb_list_count( title->list_audio ); j++ )
457         {
458             audio = hb_list_item( title->list_audio, j );
459             [fConvertAudioPopUp addItemWithTitle:
460                 [NSString stringWithUTF8String: audio->lang_simple]];
461         }
462                 [fConvertAudioPopUp selectItemWithTitle: @"English"];
463
464         /* Update subtitle popup */
465         hb_subtitle_t * subtitle;
466         for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
467         {
468             subtitle = hb_list_item( title->list_subtitle, j );
469             [fConvertSubtitlePopUp addItemWithTitle:
470                 [NSString stringWithUTF8String: subtitle->lang]];
471         }
472     }
473     [fConvertTableView reloadData];
474
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];
483
484     /* Folder popup */
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:
490         NSMakeSize(16,16)];
491     [image16 lockFocus];
492     [[NSGraphicsContext currentContext]
493         setImageInterpolation: NSImageInterpolationHigh];
494     [image32 drawInRect: NSMakeRect(0,0,16,16)
495         fromRect: NSMakeRect(0,0,32,32) operation: NSCompositeCopy
496         fraction: 1.0];
497     [image16 unlockFocus];                                                      
498     [item setImage: image16];
499     [image16 release];
500
501     [self convertEnable: YES];
502 }
503
504 - (void) convertEnable: (BOOL) b
505 {
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];
514     if( b )
515     {
516         [fConvertGoButton setTitle: @"Convert"];
517         [fConvertGoButton setAction: @selector(convertGo:)];
518     }
519     else
520     {
521         [fConvertGoButton setTitle: @"Cancel"];
522         [fConvertGoButton setAction: @selector(convertCancel:)];
523     }
524 }
525
526 /***********************************************************************
527 * UpdateDockIcon
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
532 * icon.
533 **********************************************************************/
534 - (void) UpdateDockIcon: (float) progress
535 {
536     NSImage * icon;
537     NSData * tiff;
538     NSBitmapImageRep * bmp;
539     uint32_t * pen;
540     uint32_t black = htonl( 0x000000FF );
541     uint32_t red   = htonl( 0xFF0000FF );
542     uint32_t white = htonl( 0xFFFFFFFF );
543     int row_start, row_end;
544     int i, j;
545         
546     /* Get application original icon */
547     icon = [NSImage imageNamed: @"NSApplicationIcon"];
548         
549     if( progress < 0.0 || progress > 1.0 )
550     {
551         [NSApp setApplicationIconImage: icon];
552         return;
553     }
554         
555     /* Get it in a raw bitmap form */
556     tiff = [icon TIFFRepresentationUsingCompression:
557                                            NSTIFFCompressionNone factor: 1.0];
558     bmp = [NSBitmapImageRep imageRepWithData: tiff];
559     
560     /* Draw the progression bar */
561     /* It's pretty simple (ugly?) now, but I'm no designer */
562         
563     row_start = 3 * (int) [bmp size].height / 4;
564     row_end   = 7 * (int) [bmp size].height / 8;
565         
566     for( i = row_start; i < row_start + 2; i++ )
567     {
568         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
569         for( j = 0; j < (int) [bmp size].width; j++ )
570         {
571             pen[j] = black;
572         }
573     }
574     for( i = row_start + 2; i < row_end - 2; i++ )
575     {
576         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
577         pen[0] = black;
578         pen[1] = black;
579         for( j = 2; j < (int) [bmp size].width - 2; j++ )
580         {
581             if( j < 2 + (int) ( ( [bmp size].width - 4.0 ) * progress ) )
582             {
583                 pen[j] = red;
584             }
585             else
586             {
587                 pen[j] = white;
588             }
589         }
590         pen[j]   = black;
591         pen[j+1] = black;
592     }
593     for( i = row_end - 2; i < row_end; i++ )
594     {
595         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
596         for( j = 0; j < (int) [bmp size].width; j++ )
597         {
598             pen[j] = black;
599         }
600     }
601         
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];
607     [icon release];
608 }
609
610 - (void) convertTimer: (NSTimer *) timer
611 {
612     hb_state_t s;
613     hb_get_state( fHandle, &s );
614     switch( s.state )
615     {
616 #define p s.param.working
617         case HB_STATE_WORKING:
618         {
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];
623             if( p.seconds > -1 )
624             {
625                 [string appendFormat: @" (%.1f fps, ", p.rate_avg];
626                 if( p.hours > 0 )
627                 {
628                     [string appendFormat: @"%d hour%s %d min%s",
629                         p.hours, p.hours == 1 ? "" : "s",
630                         p.minutes, p.minutes == 1 ? "" : "s"];
631                 }
632                 else if( p.minutes > 0 )
633                 {
634                     [string appendFormat: @"%d min%s %d sec%s",
635                         p.minutes, p.minutes == 1 ? "" : "s",
636                         p.seconds, p.seconds == 1 ? "" : "s"];
637                 }
638                 else
639                 {
640                     [string appendFormat: @"%d second%s",
641                         p.seconds, p.seconds == 1 ? "" : "s"];
642                 }
643                 [string appendString: @" left)"];
644             }
645             [fConvertInfoString setStringValue: string];
646             [fConvertIndicator setIndeterminate: NO];
647             [fConvertIndicator setDoubleValue: 100.0 * progress_total];
648             [self UpdateDockIcon: progress_total];
649                         break;
650         }
651 #undef p
652
653 #define p s.param.muxing
654         case HB_STATE_MUXING:
655         {
656             NSMutableString * string = [NSMutableString
657                 stringWithFormat: @"Muxing..."];
658             [fConvertInfoString setStringValue: string];
659             [fConvertIndicator setIndeterminate: YES];
660             [fConvertIndicator startAnimation: nil];
661             [self UpdateDockIcon: 1.0];
662             break;
663         }
664 #undef p
665
666         case HB_STATE_WORKDONE:
667                 {
668                         [timer invalidate];
669             [fConvertIndicator setIndeterminate: NO];
670             [fConvertIndicator setDoubleValue: 0.0];
671             [self UpdateDockIcon: -1.0];
672             [self convertEnable: YES];
673                         
674 #define p s.param.workdone
675                         switch(p.error)
676                         {
677                                 case HB_ERROR_NONE:
678                                         [fConvertInfoString setStringValue: @"Done."];
679                                         break;
680                                 case HB_ERROR_CANCELED:
681                                         [fConvertInfoString setStringValue: @"Canceled."];
682                                         break;
683                                 case HB_ERROR_UNKNOWN:
684                                         [fConvertInfoString setStringValue: @"Unknown Error."];
685                                         break;
686                         }
687 #undef p
688
689                         hb_job_t * job;
690             while( ( job = hb_job( fHandle, 0 ) ) )
691             {
692                 hb_rem( fHandle, job );
693             }
694                         break;
695                 }
696         default:
697             break;
698     }
699 }
700
701 @end