/* $Id: Controller.mm,v 1.78 2005/11/04 13:09:41 titer Exp $ This file is part of the HandBrake source code. Homepage: . It may be used under the terms of the GNU General Public License. */ #include "Controller.h" #define _(a) NSLocalizedString(a,NULL) static int FormatSettings[3][4] = { { HB_MUX_MP4 | HB_VCODEC_FFMPEG | HB_ACODEC_FAAC, HB_MUX_MP4 | HB_VCODEC_X264 | HB_ACODEC_FAAC, 0, 0 }, { HB_MUX_AVI | HB_VCODEC_FFMPEG | HB_ACODEC_LAME, HB_MUX_AVI | HB_VCODEC_FFMPEG | HB_ACODEC_AC3, HB_MUX_AVI | HB_VCODEC_X264 | HB_ACODEC_LAME, HB_MUX_AVI | HB_VCODEC_X264 | HB_ACODEC_AC3 }, { HB_MUX_OGM | HB_VCODEC_FFMPEG | HB_ACODEC_VORBIS, HB_MUX_OGM | HB_VCODEC_FFMPEG | HB_ACODEC_LAME, 0, 0 } }; /******************************* * HBController implementation * *******************************/ @implementation HBController - init { self = [super init]; fHandle = NULL; return self; } - (void) applicationDidFinishLaunching: (NSNotification *) notification { int build; char * version; /* Init libhb */ fHandle = hb_init( HB_DEBUG_ALL, [[NSUserDefaults standardUserDefaults] boolForKey:@"CheckForUpdates"] ); /* Init others controllers */ [fScanController SetHandle: fHandle]; [fPictureController SetHandle: fHandle]; [fQueueController SetHandle: fHandle]; /* Call UpdateUI every 2/10 sec */ [[NSRunLoop currentRunLoop] addTimer: [NSTimer scheduledTimerWithTimeInterval: 0.2 target: self selector: @selector( UpdateUI: ) userInfo: NULL repeats: FALSE] forMode: NSModalPanelRunLoopMode]; if( ( build = hb_check_update( fHandle, &version ) ) > -1 ) { /* Update available - tell the user */ NSBeginInformationalAlertSheet( _( @"Update is available" ), _( @"Go get it!" ), _( @"Discard" ), NULL, fWindow, self, @selector( UpdateAlertDone:returnCode:contextInfo: ), NULL, NULL, [NSString stringWithFormat: _( @"HandBrake %s (build %d) is now available for download." ), version, build] ); return; } /* Show scan panel ASAP */ [self performSelectorOnMainThread: @selector(ShowScanPanel:) withObject: NULL waitUntilDone: NO]; } - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) app { if( [[fRipButton title] isEqualToString: _( @"Cancel" )] ) { [self Cancel: NULL]; return NSTerminateCancel; } /* Clean up */ hb_close( &fHandle ); return NSTerminateNow; } - (void) awakeFromNib { [fWindow center]; [self TranslateStrings]; /* Destination box */ [fDstFormatPopUp removeAllItems]; [fDstFormatPopUp addItemWithTitle: _( @"MP4 file" )]; [fDstFormatPopUp addItemWithTitle: _( @"AVI file" )]; [fDstFormatPopUp addItemWithTitle: _( @"OGM file" )]; [fDstFormatPopUp selectItemAtIndex: 0]; [self FormatPopUpChanged: NULL]; [fDstFile2Field setStringValue: [NSString stringWithFormat: @"%@/Desktop/Movie.mp4", NSHomeDirectory()]]; /* Video encoder */ [fVidEncoderPopUp removeAllItems]; [fVidEncoderPopUp addItemWithTitle: @"FFmpeg"]; [fVidEncoderPopUp addItemWithTitle: @"XviD"]; /* Video quality */ [fVidTargetSizeField setIntValue: 700]; [fVidBitrateField setIntValue: 1000]; [fVidQualityMatrix selectCell: fVidBitrateCell]; [self VideoMatrixChanged: NULL]; /* Video framerate */ [fVidRatePopUp removeAllItems]; [fVidRatePopUp addItemWithTitle: _( @"Same as source" )]; for( int i = 0; i < hb_video_rates_count; i++ ) { [fVidRatePopUp addItemWithTitle: [NSString stringWithCString: hb_video_rates[i].string]]; } [fVidRatePopUp selectItemAtIndex: 0]; /* Audio bitrate */ [fAudBitratePopUp removeAllItems]; for( int i = 0; i < hb_audio_bitrates_count; i++ ) { [fAudBitratePopUp addItemWithTitle: [NSString stringWithCString: hb_audio_bitrates[i].string]]; } [fAudBitratePopUp selectItemAtIndex: hb_audio_bitrates_default]; /* Audio samplerate */ [fAudRatePopUp removeAllItems]; for( int i = 0; i < hb_audio_rates_count; i++ ) { [fAudRatePopUp addItemWithTitle: [NSString stringWithCString: hb_audio_rates[i].string]]; } [fAudRatePopUp selectItemAtIndex: hb_audio_rates_default]; /* Bottom */ [fStatusField setStringValue: @""]; [self EnableUI: NO]; [fPauseButton setEnabled: NO]; [fRipButton setEnabled: NO]; } - (void) TranslateStrings { [fSrcDVD1Field setStringValue: _( @"DVD:" )]; [fSrcTitleField setStringValue: _( @"Title:" )]; [fSrcChapterField setStringValue: _( @"Chapters:" )]; [fSrcChapterToField setStringValue: _( @"to" )]; [fSrcDuration1Field setStringValue: _( @"Duration:" )]; [fDstFormatField setStringValue: _( @"File format:" )]; [fDstCodecsField setStringValue: _( @"Codecs:" )]; [fDstFile1Field setStringValue: _( @"File:" )]; [fDstBrowseButton setTitle: _( @"Browse" )]; [fVidRateField setStringValue: _( @"Framerate (fps):" )]; [fVidEncoderField setStringValue: _( @"Encoder:" )]; [fVidQualityField setStringValue: _( @"Quality:" )]; } /*********************************************************************** * 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; 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] = 0x000000FF; /* Black */ } } for( i = row_start + 2; i < row_end - 2; i++ ) { pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] ); pen[0] = 0x000000FF; pen[1] = 0x000000FF; for( j = 2; j < (int) [bmp size].width - 2; j++ ) { if( j < 2 + (int) ( ( [bmp size].width - 4.0 ) * progress ) ) { pen[j] = 0xFF0000FF; /* Red */ } else { pen[j] = 0xFFFFFFFF; /* White */ } } pen[j] = 0x000000FF; pen[j+1] = 0x000000FF; } 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] = 0x000000FF; /* Black */ } } /* Now update the dock icon */ tiff = [bmp TIFFRepresentationUsingCompression: NSTIFFCompressionNone factor: 1.0]; icon = [[NSImage alloc] initWithData: tiff]; [NSApp setApplicationIconImage: icon]; [icon release]; } - (void) UpdateUI: (NSTimer *) timer { hb_state_t s; hb_get_state( fHandle, &s ); switch( s.state ) { case HB_STATE_IDLE: /* If the scan panel is currently showing, recheck for DVD drives. The clean way would be to use NSWorkspaceDidMountNotification, but for some reason the notifications don't work when having the sheet attached */ if( [fWindow attachedSheet] == fScanPanel ) { [fScanController DetectDrives: NULL]; } break; case HB_STATE_SCANNING: [fScanController UpdateUI: &s]; break; #define p s.param.scandone case HB_STATE_SCANDONE: { hb_list_t * list; hb_title_t * title; [fScanController UpdateUI: &s]; list = hb_get_titles( fHandle ); if( !hb_list_count( list ) ) { break; } [fSrcTitlePopUp removeAllItems]; for( int i = 0; i < hb_list_count( list ); i++ ) { title = (hb_title_t *) hb_list_item( list, i ); [fSrcDVD2Field setStringValue: [NSString stringWithUTF8String: title->dvd]]; [fSrcTitlePopUp addItemWithTitle: [NSString stringWithFormat: @"%d - %02dh%02dm%02ds", title->index, title->hours, title->minutes, title->seconds]]; } [self TitlePopUpChanged: NULL]; [self EnableUI: YES]; [fPauseButton setEnabled: NO]; [fRipButton setEnabled: YES]; break; } #undef p #define p s.param.working case HB_STATE_WORKING: { float progress_total; NSMutableString * string; /* Update text field */ string = [NSMutableString stringWithFormat: _( @"Encoding: task %d of %d, %.2f %%" ), p.job_cur, p.job_count, 100.0 * p.progress]; if( p.seconds > -1 ) { [string appendFormat: _( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)" ), p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds]; } [fStatusField setStringValue: string]; /* Update slider */ progress_total = ( p.progress + p.job_cur - 1 ) / p.job_count; [fRipIndicator setDoubleValue: 100.0 * progress_total]; /* Update dock icon */ [self UpdateDockIcon: progress_total]; [fPauseButton setEnabled: YES]; [fPauseButton setTitle: _( @"Pause" )]; [fRipButton setEnabled: YES]; [fRipButton setTitle: _( @"Cancel" )]; break; } #undef p case HB_STATE_PAUSED: [fStatusField setStringValue: _( @"Paused" )]; [fPauseButton setEnabled: YES]; [fPauseButton setTitle: _( @"Resume" )]; [fRipButton setEnabled: YES]; [fRipButton setTitle: _( @"Cancel" )]; break; case HB_STATE_WORKDONE: { [self EnableUI: YES]; [fStatusField setStringValue: _( @"Done." )]; [fRipIndicator setDoubleValue: 0.0]; [fRipButton setTitle: _( @"Rip" )]; /* Restore dock icon */ [self UpdateDockIcon: -1.0]; [fPauseButton setEnabled: NO]; [fPauseButton setTitle: _( @"Pause" )]; [fRipButton setEnabled: YES]; [fRipButton setTitle: _( @"Rip" )]; /* FIXME */ hb_job_t * job; while( ( job = hb_job( fHandle, 0 ) ) ) { hb_rem( fHandle, job ); } break; } } /* FIXME: we should only do that when necessary */ if( [fQueueCheck state] == NSOnState ) { int count = hb_count( fHandle ); if( count ) { [fQueueCheck setTitle: [NSString stringWithFormat: @"Enable queue (%d task%s in queue)", count, ( count > 1 ) ? "s" : ""]]; } else { [fQueueCheck setTitle: @"Enable queue (no task in queue)"]; } } [[NSRunLoop currentRunLoop] addTimer: [NSTimer scheduledTimerWithTimeInterval: 0.2 target: self selector: @selector( UpdateUI: ) userInfo: NULL repeats: FALSE] forMode: NSModalPanelRunLoopMode]; } - (void) EnableUI: (bool) b { NSControl * controls[] = { fSrcDVD1Field, fSrcDVD2Field, fSrcTitleField, fSrcTitlePopUp, fSrcChapterField, fSrcChapterStartPopUp, fSrcChapterToField, fSrcChapterEndPopUp, fSrcDuration1Field, fSrcDuration2Field, fDstFormatField, fDstFormatPopUp, fDstCodecsField, fDstCodecsPopUp, fDstFile1Field, fDstFile2Field, fDstBrowseButton, fVidRateField, fVidRatePopUp, fVidEncoderField, fVidEncoderPopUp, fVidQualityField, fVidQualityMatrix, fVidGrayscaleCheck, fSubField, fSubPopUp, fAudLang1Field, fAudLang1PopUp, fAudLang2Field, fAudLang2PopUp, fAudRateField, fAudRatePopUp, fAudBitrateField, fAudBitratePopUp, fPictureButton }; for( unsigned i = 0; i < sizeof( controls ) / sizeof( NSControl * ); i++ ) { if( [[controls[i] className] isEqualToString: @"NSTextField"] ) { NSTextField * tf = (NSTextField *) controls[i]; if( ![tf isBezeled] ) { [tf setTextColor: b ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]]; continue; } } [controls[i] setEnabled: b]; } [self VideoMatrixChanged: NULL]; } - (IBAction) ShowScanPanel: (id) sender { [NSApp beginSheet: fScanPanel modalForWindow: fWindow modalDelegate: NULL didEndSelector: NULL contextInfo: NULL]; [NSApp runModalForWindow: fScanPanel]; [NSApp endSheet: fScanPanel]; [fScanPanel orderOut: self]; } - (BOOL) windowShouldClose: (id) sender { /* Stop the application when the user closes the window */ [NSApp terminate: self]; return YES; } - (IBAction) VideoMatrixChanged: (id) sender; { bool target, bitrate, quality; target = bitrate = quality = false; if( [fVidQualityMatrix isEnabled] ) { switch( [fVidQualityMatrix selectedRow] ) { case 0: target = true; break; case 1: bitrate = true; break; case 2: quality = true; break; } } [fVidTargetSizeField setEnabled: target]; [fVidBitrateField setEnabled: bitrate]; [fVidQualitySlider setEnabled: quality]; [fVidTwoPassCheck setEnabled: !quality && [fVidQualityMatrix isEnabled]]; if( quality ) { [fVidTwoPassCheck setState: NSOffState]; } [self QualitySliderChanged: sender]; [self CalculateBitrate: sender]; } - (IBAction) QualitySliderChanged: (id) sender { [fVidConstantCell setTitle: [NSString stringWithFormat: _( @"Constant quality: %.0f %%" ), 100.0 * [fVidQualitySlider floatValue]]]; } - (IBAction) BrowseFile: (id) sender { /* Open a panel to let the user choose and update the text field */ NSSavePanel * panel = [NSSavePanel savePanel]; [panel beginSheetForDirectory: NULL file: NULL modalForWindow: fWindow modalDelegate: self didEndSelector: @selector( BrowseFileDone:returnCode:contextInfo: ) contextInfo: NULL]; } - (void) BrowseFileDone: (NSSavePanel *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo { if( returnCode == NSOKButton ) { [fDstFile2Field setStringValue: [sheet filename]]; [self FormatPopUpChanged: NULL]; } } - (IBAction) ShowPicturePanel: (id) sender { hb_list_t * list = hb_get_titles( fHandle ); hb_title_t * title = (hb_title_t *) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); /* Resize the panel */ NSSize newSize; newSize.width = 246 + title->width; newSize.height = 80 + title->height; [fPicturePanel setContentSize: newSize]; [fPictureController SetTitle: title]; [NSApp beginSheet: fPicturePanel modalForWindow: fWindow modalDelegate: NULL didEndSelector: NULL contextInfo: NULL]; [NSApp runModalForWindow: fPicturePanel]; [NSApp endSheet: fPicturePanel]; [fPicturePanel orderOut: self]; } - (IBAction) ShowQueuePanel: (id) sender { /* Update the OutlineView */ [fQueueController Update: sender]; /* Show the panel */ [NSApp beginSheet: fQueuePanel modalForWindow: fWindow modalDelegate: NULL didEndSelector: NULL contextInfo: NULL]; [NSApp runModalForWindow: fQueuePanel]; [NSApp endSheet: fQueuePanel]; [fQueuePanel orderOut: self]; } - (void) PrepareJob { hb_list_t * list = hb_get_titles( fHandle ); hb_title_t * title = (hb_title_t *) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); hb_job_t * job = title->job; /* Chapter selection */ job->chapter_start = [fSrcChapterStartPopUp indexOfSelectedItem] + 1; job->chapter_end = [fSrcChapterEndPopUp indexOfSelectedItem] + 1; /* Format and codecs */ int format = [fDstFormatPopUp indexOfSelectedItem]; int codecs = [fDstCodecsPopUp indexOfSelectedItem]; job->mux = FormatSettings[format][codecs] & HB_MUX_MASK; job->vcodec = FormatSettings[format][codecs] & HB_VCODEC_MASK; job->acodec = FormatSettings[format][codecs] & HB_ACODEC_MASK; if( ( job->vcodec & HB_VCODEC_FFMPEG ) && [fVidEncoderPopUp indexOfSelectedItem] > 0 ) { job->vcodec = HB_VCODEC_XVID; } if( job->vcodec & HB_VCODEC_X264 ) { job->h264_13 = [fVidEncoderPopUp indexOfSelectedItem]; } /* Video settings */ if( [fVidRatePopUp indexOfSelectedItem] > 0 ) { job->vrate = 27000000; job->vrate_base = hb_video_rates[[fVidRatePopUp indexOfSelectedItem]-1].rate; } else { job->vrate = title->rate; job->vrate_base = title->rate_base; } switch( [fVidQualityMatrix selectedRow] ) { case 0: /* Target size. Bitrate should already have been calculated and displayed in fVidBitrateField, so let's just use it */ case 1: job->vquality = -1.0; job->vbitrate = [fVidBitrateField intValue]; break; case 2: job->vquality = [fVidQualitySlider floatValue]; job->vbitrate = 0; break; } job->grayscale = ( [fVidGrayscaleCheck state] == NSOnState ); /* Subtitle settings */ job->subtitle = [fSubPopUp indexOfSelectedItem] - 1; /* Audio tracks */ job->audios[0] = [fAudLang1PopUp indexOfSelectedItem] - 1; job->audios[1] = [fAudLang2PopUp indexOfSelectedItem] - 1; job->audios[2] = -1; /* Audio settings */ job->arate = hb_audio_rates[[fAudRatePopUp indexOfSelectedItem]].rate; job->abitrate = hb_audio_bitrates[[fAudBitratePopUp indexOfSelectedItem]].rate; } - (IBAction) EnableQueue: (id) sender { bool e = ( [fQueueCheck state] == NSOnState ); [fQueueAddButton setHidden: !e]; [fQueueShowButton setHidden: !e]; [fRipButton setTitle: e ? @"Start" : @"Rip"]; } - (IBAction) AddToQueue: (id) sender { hb_list_t * list = hb_get_titles( fHandle ); hb_title_t * title = (hb_title_t *) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); hb_job_t * job = title->job; [self PrepareJob]; /* Destination file */ job->file = strdup( [[fDstFile2Field stringValue] UTF8String] ); if( [fVidTwoPassCheck state] == NSOnState ) { job->pass = 1; hb_add( fHandle, job ); job->pass = 2; hb_add( fHandle, job ); } else { job->pass = 0; hb_add( fHandle, job ); } } - (IBAction) Rip: (id) sender { /* Rip or Cancel ? */ if( [[fRipButton title] isEqualToString: _( @"Cancel" )] ) { [self Cancel: sender]; return; } if( [fQueueCheck state] == NSOffState ) { [self AddToQueue: sender]; } if( [[NSFileManager defaultManager] fileExistsAtPath: [fDstFile2Field stringValue]] ) { NSBeginCriticalAlertSheet( _( @"File already exists" ), _( @"Cancel" ), _( @"Overwrite" ), NULL, fWindow, self, @selector( OverwriteAlertDone:returnCode:contextInfo: ), NULL, NULL, [NSString stringWithFormat: _( @"Do you want to overwrite %@?" ), [fDstFile2Field stringValue]] ); return; } [self _Rip]; } - (void) OverwriteAlertDone: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo { if( returnCode == NSAlertAlternateReturn ) { [self _Rip]; } } - (void) UpdateAlertDone: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo { if( returnCode == NSAlertAlternateReturn ) { /* Show scan panel */ [self performSelectorOnMainThread: @selector(ShowScanPanel:) withObject: NULL waitUntilDone: NO]; return; } /* Go to HandBrake homepage and exit */ [self OpenHomepage: NULL]; [NSApp terminate: self]; } - (void) _Rip { /* Let libhb do the job */ hb_start( fHandle ); /* Disable interface */ [self EnableUI: NO]; [fPauseButton setEnabled: NO]; [fRipButton setEnabled: NO]; } - (IBAction) Cancel: (id) sender { NSBeginCriticalAlertSheet( _( @"Cancel - Are you sure?" ), _( @"Keep working" ), _( @"Cancel encoding" ), NULL, fWindow, self, @selector( _Cancel:returnCode:contextInfo: ), NULL, NULL, _( @"Encoding won't be recoverable." ) ); } - (void) _Cancel: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo { if( returnCode == NSAlertAlternateReturn ) { hb_stop( fHandle ); [fPauseButton setEnabled: NO]; [fRipButton setEnabled: NO]; } } - (IBAction) Pause: (id) sender { [fPauseButton setEnabled: NO]; [fRipButton setEnabled: NO]; if( [[fPauseButton title] isEqualToString: _( @"Resume" )] ) { hb_resume( fHandle ); } else { hb_pause( fHandle ); } } - (IBAction) TitlePopUpChanged: (id) sender { hb_list_t * list = hb_get_titles( fHandle ); hb_title_t * title = (hb_title_t*) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); /* Update chapter popups */ [fSrcChapterStartPopUp removeAllItems]; [fSrcChapterEndPopUp removeAllItems]; for( int i = 0; i < hb_list_count( title->list_chapter ); i++ ) { [fSrcChapterStartPopUp addItemWithTitle: [NSString stringWithFormat: @"%d", i + 1]]; [fSrcChapterEndPopUp addItemWithTitle: [NSString stringWithFormat: @"%d", i + 1]]; } [fSrcChapterStartPopUp selectItemAtIndex: 0]; [fSrcChapterEndPopUp selectItemAtIndex: hb_list_count( title->list_chapter ) - 1]; [self ChapterPopUpChanged: NULL]; /* Update subtitle popups */ hb_subtitle_t * subtitle; [fSubPopUp removeAllItems]; [fSubPopUp addItemWithTitle: @"None"]; for( int i = 0; i < hb_list_count( title->list_subtitle ); i++ ) { subtitle = (hb_subtitle_t *) hb_list_item( title->list_subtitle, i ); /* We cannot use NSPopUpButton's addItemWithTitle because it checks for duplicate entries */ [[fSubPopUp menu] addItemWithTitle: [NSString stringWithCString: subtitle->lang] action: NULL keyEquivalent: @""]; } [fSubPopUp selectItemAtIndex: 0]; /* Update lang popups */ hb_audio_t * audio; [fAudLang1PopUp removeAllItems]; [fAudLang2PopUp removeAllItems]; [fAudLang1PopUp addItemWithTitle: _( @"None" )]; [fAudLang2PopUp addItemWithTitle: _( @"None" )]; for( int i = 0; i < hb_list_count( title->list_audio ); i++ ) { audio = (hb_audio_t *) hb_list_item( title->list_audio, i ); [[fAudLang1PopUp menu] addItemWithTitle: [NSString stringWithCString: audio->lang] action: NULL keyEquivalent: @""]; [[fAudLang2PopUp menu] addItemWithTitle: [NSString stringWithCString: audio->lang] action: NULL keyEquivalent: @""]; } [fAudLang1PopUp selectItemAtIndex: 1]; [fAudLang2PopUp selectItemAtIndex: 0]; } - (IBAction) ChapterPopUpChanged: (id) sender { hb_list_t * list = hb_get_titles( fHandle ); hb_title_t * title = (hb_title_t *) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); hb_chapter_t * chapter; int64_t duration = 0; for( int i = [fSrcChapterStartPopUp indexOfSelectedItem]; i <= [fSrcChapterEndPopUp indexOfSelectedItem]; i++ ) { chapter = (hb_chapter_t *) hb_list_item( title->list_chapter, i ); duration += chapter->duration; } duration /= 90000; /* pts -> seconds */ [fSrcDuration2Field setStringValue: [NSString stringWithFormat: @"%02lld:%02lld:%02lld", duration / 3600, ( duration / 60 ) % 60, duration % 60]]; [self CalculateBitrate: sender]; } - (IBAction) FormatPopUpChanged: (id) sender { NSString * string = [fDstFile2Field stringValue]; int format = [fDstFormatPopUp indexOfSelectedItem]; char * ext = NULL; /* Update the codecs popup */ [fDstCodecsPopUp removeAllItems]; switch( format ) { case 0: ext = "mp4"; [fDstCodecsPopUp addItemWithTitle: _( @"MPEG-4 Video / AAC Audio" )]; [fDstCodecsPopUp addItemWithTitle: _( @"AVC/H.264 Video / AAC Audio" )]; break; case 1: ext = "avi"; [fDstCodecsPopUp addItemWithTitle: _( @"MPEG-4 Video / MP3 Audio" )]; [fDstCodecsPopUp addItemWithTitle: _( @"MPEG-4 Video / AC-3 Audio" )]; [fDstCodecsPopUp addItemWithTitle: _( @"AVC/H.264 Video / MP3 Audio" )]; [fDstCodecsPopUp addItemWithTitle: _( @"AVC/H.264 Video / AC-3 Audio" )]; break; case 2: ext = "ogm"; [fDstCodecsPopUp addItemWithTitle: _( @"MPEG-4 Video / Vorbis Audio" )]; [fDstCodecsPopUp addItemWithTitle: _( @"MPEG-4 Video / MP3 Audio" )]; break; } [self CodecsPopUpChanged: NULL]; /* Add/replace to the correct extension */ if( [string characterAtIndex: [string length] - 4] == '.' ) { [fDstFile2Field setStringValue: [NSString stringWithFormat: @"%@.%s", [string substringToIndex: [string length] - 4], ext]]; } else { [fDstFile2Field setStringValue: [NSString stringWithFormat: @"%@.%s", string, ext]]; } } - (IBAction) CodecsPopUpChanged: (id) sender { int format = [fDstFormatPopUp indexOfSelectedItem]; int codecs = [fDstCodecsPopUp indexOfSelectedItem]; /* Update the encoder popup if necessary */ if( ( FormatSettings[format][codecs] & HB_VCODEC_X264 ) && [fVidEncoderPopUp numberOfItems] > 1 ) { /* MPEG-4 -> H.264 */ [fVidEncoderPopUp removeAllItems]; [fVidEncoderPopUp addItemWithTitle: @"x264 (Main profile)"]; [fVidEncoderPopUp addItemWithTitle: @"x264 (Baseline profile)"]; } else if( ( FormatSettings[format][codecs] & HB_VCODEC_FFMPEG ) && [fVidEncoderPopUp numberOfItems] < 2 ) { /* H.264 -> MPEG-4 */ [fVidEncoderPopUp removeAllItems]; [fVidEncoderPopUp addItemWithTitle: @"FFmpeg"]; [fVidEncoderPopUp addItemWithTitle: @"XviD"]; [fVidEncoderPopUp selectItemAtIndex: 0]; } if( FormatSettings[format][codecs] & HB_ACODEC_AC3 ) { /* AC-3 pass-through: disable samplerate and bitrate */ [fAudRatePopUp setEnabled: NO]; [fAudBitratePopUp setEnabled: NO]; } else { [fAudRatePopUp setEnabled: YES]; [fAudBitratePopUp setEnabled: YES]; } [self CalculateBitrate: sender]; } - (IBAction) CalculateBitrate: (id) sender { if( !fHandle || [fVidQualityMatrix selectedRow] != 0 ) { return; } hb_list_t * list = hb_get_titles( fHandle ); hb_title_t * title = (hb_title_t *) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); hb_job_t * job = title->job; [self PrepareJob]; [fVidBitrateField setIntValue: hb_calc_bitrate( job, [fVidTargetSizeField intValue] )]; } - (void) controlTextDidBeginEditing: (NSNotification *) notification { [self CalculateBitrate: NULL]; } - (void) controlTextDidEndEditing: (NSNotification *) notification { [self CalculateBitrate: NULL]; } - (void) controlTextDidChange: (NSNotification *) notification { [self CalculateBitrate: NULL]; } - (IBAction) OpenHomepage: (id) sender { [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:@"http://handbrake.m0k.org/"]]; } - (IBAction) OpenForums: (id) sender { [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:@"http://handbrake.m0k.org/forum/"]]; } @end