diff options
-rw-r--r-- | macosx/Controller.h | 85 | ||||
-rw-r--r-- | macosx/Controller.m | 1668 | ||||
-rw-r--r-- | macosx/English.lproj/MainMenu.xib | 14 | ||||
-rw-r--r-- | macosx/English.lproj/Queue.xib | 17 | ||||
-rw-r--r-- | macosx/HBAttributedStringAdditions.h | 16 | ||||
-rw-r--r-- | macosx/HBAttributedStringAdditions.m | 20 | ||||
-rw-r--r-- | macosx/HBCore.h | 41 | ||||
-rw-r--r-- | macosx/HBCore.m | 155 | ||||
-rw-r--r-- | macosx/HBDistributedArray.h | 36 | ||||
-rw-r--r-- | macosx/HBDistributedArray.m | 328 | ||||
-rw-r--r-- | macosx/HBJob+UIAdditions.h | 2 | ||||
-rw-r--r-- | macosx/HBJob+UIAdditions.m | 406 | ||||
-rw-r--r-- | macosx/HBJob.h | 2 | ||||
-rw-r--r-- | macosx/HBJob.m | 10 | ||||
-rw-r--r-- | macosx/HBPreviewGenerator.m | 94 | ||||
-rw-r--r-- | macosx/HBQueueController.h | 29 | ||||
-rw-r--r-- | macosx/HBQueueController.mm | 1716 | ||||
-rw-r--r-- | macosx/HandBrake.xcodeproj/project.pbxproj | 18 | ||||
-rw-r--r-- | macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [DEBUG].xcscheme | 8 | ||||
-rw-r--r-- | macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [RELEASE].xcscheme | 8 |
20 files changed, 2278 insertions, 2395 deletions
diff --git a/macosx/Controller.h b/macosx/Controller.h index 87d305bb3..499051225 100644 --- a/macosx/Controller.h +++ b/macosx/Controller.h @@ -5,7 +5,6 @@ It may be used under the terms of the GNU General Public License. */ #import <Cocoa/Cocoa.h> -#import <Growl/Growl.h> @class HBQueueController; @@ -24,9 +23,11 @@ @class HBPresetsManager; @class HBDockTile; -@interface HBController : NSObject <NSApplicationDelegate, NSDrawerDelegate, GrowlApplicationBridgeDelegate> +@class HBJob; + +@interface HBController : NSObject <NSApplicationDelegate, NSDrawerDelegate> { - IBOutlet NSWindow * fWindow; + IBOutlet NSWindow *fWindow; IBOutlet NSTabView *fMainTabView; @@ -124,37 +125,16 @@ IBOutlet NSProgressIndicator * fRipIndicator; BOOL fRipIndicatorShown; - /* Queue File variables */ - FSEventStreamRef QueueStream; - NSString * QueueFile; - NSMutableArray * QueueFileArray; - NSInteger currentQueueEncodeIndex; // Used to track the currently encoding queueu item - /* User Preset variables here */ HBPresetsManager * presetManager; HBPresetsViewController * fPresetsView; IBOutlet NSMenu * presetsMenu; IBOutlet NSDrawer * fPresetDrawer; - - /* Queue variables */ - int hbInstanceNum; //stores the number of HandBrake instances currently running - int fPendingCount; // Number of various kinds of job groups in fJobGroups. - int fWorkingCount; - - pid_t pidNum; // The pid number for this instance - NSString * currentQueueEncodeNameString; - - /* integer to set to determine the previous state - of encode 0==idle, 1==encoding, 2==cancelled*/ - int fEncodeState; - - /* Dock progress variables */ - double dockIconProgress; - - HBDockTile *dockTile; } +@property (nonatomic, readonly) NSWindow *window; + - (IBAction) browseSources: (id) sender; - (IBAction) showSourceTitleScanPanel: (id) sender; - (IBAction) closeSourceTitleScanPanel: (id) sender; @@ -174,47 +154,22 @@ - (void)pictureSettingsDidChange; - (IBAction) openMainWindow: (id) sender; -/* Add All titles to the queue */ -- (IBAction) addAllTitlesToQueue: (id) sender; -- (void) addAllTitlesToQueueAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo; -- (void) doAddAllTitlesToQueue; - -/* Queue File Stuff */ -- (void) initQueueFSEvent; -- (void) closeQueueFSEvent; -- (void) loadQueueFile; -- (void) reloadQueue; -- (void)saveQueueFileItem; -- (void) incrementQueueItemDone:(NSInteger) queueItemDoneIndexNum; -- (void) performNewQueueScan:(NSString *) scanPath scanTitleNum: (NSInteger) scanTitleNum; -- (void) processNewQueueEncode; -- (void) clearQueueEncodedItems; -/* Queue Editing */ -- (void)rescanQueueItemToMainWindow:(NSUInteger) selectedQueueItem; - -- (void) removeQueueFileItem:(NSUInteger) queueItemToRemove; -- (void) clearQueueAllItems; -- (void)moveObjectsInQueueArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex; -- (void)getQueueStats; -- (void)setQueueEncodingItemsAsPending; -- (IBAction) addToQueue: (id) sender; -- (void) overwriteAddToQueueAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo; -- (void) doAddToQueue; - -- (IBAction) showQueueWindow:(id)sender; +// Queue +- (IBAction)addToQueue:(id)sender; +- (IBAction)addAllTitlesToQueue:(id)sender; + +- (void)rescanJobToMainWindow:(HBJob *)queueItem; +- (void)setQueueState:(NSString *)info; +- (void)setQueueInfo:(NSString *)info progress:(double)progress hidden:(BOOL)hidden; + +- (IBAction)showQueueWindow:(id)sender; - (IBAction)showPreferencesWindow:(id)sender; -- (IBAction) Rip: (id) sender; -- (void) overWriteAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo; +- (IBAction)rip:(id)sender; -- (IBAction) Cancel: (id) sender; -- (void) doCancelCurrentJob; -- (void) doCancelCurrentJobAndStop; -- (IBAction) Pause: (id) sender; +- (IBAction)cancel:(id)sender; +- (IBAction)pause:(id)sender; - (IBAction) openHomepage: (id) sender; - (IBAction) openForums: (id) sender; @@ -231,8 +186,4 @@ - (IBAction)addFactoryPresets:(id)sender; - (IBAction)showDebugOutputPanel:(id)sender; -- (void) remindUserOfSleepOrShutdown; - -- (int) hbInstances; - @end diff --git a/macosx/Controller.m b/macosx/Controller.m index bc57b3518..4023d1300 100644 --- a/macosx/Controller.m +++ b/macosx/Controller.m @@ -27,39 +27,26 @@ #import "HBPresetsViewController.h" #import "HBAddPresetController.h" -#import "HBPicture+UIAdditions.h" -#import "HBFilters+UIAdditions.h" - #import "HBCore.h" #import "HBJob.h" -// DockTile update freqency in total percent increment -#define dockTileUpdateFrequency 0.1f - @interface HBController () <HBPresetsViewControllerDelegate, HBPreviewControllerDelegate, HBPictureControllerDelegate> @property (nonatomic, copy) NSString *browsedSourceDisplayName; -// The current job. +/// The current job. @property (nonatomic, retain) HBJob *job; -// The job to be applied from the queue. +/// The job to be applied from the queue. @property (nonatomic, retain) HBJob *jobFromQueue; -// The current selected preset. +/// The current selected preset. @property (nonatomic, retain) HBPreset *selectedPreset; @property (nonatomic) BOOL customPreset; -/** - * The HBCore used for scanning. - */ +/// The HBCore used for scanning. @property (nonatomic, retain) HBCore *core; -/** - * The HBCore used for encoding. - */ -@property (nonatomic, retain) HBCore *queueCore; - @end @implementation HBController @@ -77,22 +64,14 @@ fPictureController = [[HBPictureController alloc] init]; fPreviewController = [[HBPreviewController alloc] initWithDelegate:self]; fQueueController = [[HBQueueController alloc] init]; + fQueueController.controller = self; + fQueueController.outputPanel = outputPanel; // we init the HBPresetsManager NSURL *presetsURL = [NSURL fileURLWithPath:[[HBUtilities appSupportPath] stringByAppendingPathComponent:@"UserPresets.plist"]]; presetManager = [[HBPresetsManager alloc] initWithURL:presetsURL]; _selectedPreset = [presetManager.defaultPreset retain]; - // Workaround to avoid a bug in Snow Leopard - // we can switch back to [[NSApplication sharedApplication] applicationIconImage] - // when we won't support it anymore. - NSImage *appIcon = [NSImage imageNamed:@"HandBrake"]; - [appIcon setSize:NSMakeSize(1024, 1024)]; - - // Load the dockTile and instiante initial text fields - dockTile = [[HBDockTile alloc] initWithDockTile:[[NSApplication sharedApplication] dockTile] - image:appIcon]; - // Lets report the HandBrake version number here to the activity log and text log file NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; NSString *versionStringFull = [NSString stringWithFormat:@"Handbrake Version: %@ (%@)", infoDict[@"CFBundleShortVersionString"], infoDict[@"CFBundleVersion"]]; @@ -106,23 +85,11 @@ _core = [[HBCore alloc] initWithLoggingLevel:loggingLevel]; _core.name = @"ScanCore"; - // Init a separate instance of libhb for user scanning and setting up jobs - _queueCore = [[HBCore alloc] initWithLoggingLevel:loggingLevel]; - _queueCore.name = @"QueueCore"; - - // Registers the observers to the cores notifications. - [self registerScanCoreNotifications]; - [self registerQueueCoreNotifications]; - - // Set the Growl Delegate - [GrowlApplicationBridge setGrowlDelegate: self]; - [fPictureController setDelegate:self]; - [fPreviewController setCore:self.core]; - [fQueueController setCore:self.queueCore]; - [fQueueController setHBController:self]; + // Set the Growl Delegate + [GrowlApplicationBridge setGrowlDelegate:fQueueController]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(autoSetM4vExtension:) name:HBMixdownChangedNotification object:nil]; } @@ -132,6 +99,8 @@ - (void) applicationDidFinishLaunching: (NSNotification *) notification { + [self enableUI:NO]; + // Checks for presets updates [self checkBuiltInsForUpdates]; @@ -142,40 +111,33 @@ [fPresetDrawer open:self]; } - /* Init QueueFile .plist */ - [self loadQueueFile]; - [self initQueueFSEvent]; - /* Run hbInstances to get any info on other instances as well as set the - * pid number for this instance in the case of multi-instance encoding. */ - hbInstanceNum = [self hbInstances]; - - /* If we are a single instance it is safe to clean up the previews if there are any - * left over. This is a bit of a kludge but will prevent a build up of old instance - * live preview cruft. No danger of removing an active preview directory since they - * are created later in HBPreviewController if they don't exist at the moment a live - * preview encode is initiated. */ + // Get the number of HandBrake instances currently running + NSUInteger hbInstanceNum = [NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]].count; + + // If we are a single instance it is safe to clean up the previews if there are any + // left over. This is a bit of a kludge but will prevent a build up of old instance + // live preview cruft. No danger of removing an active preview directory since they + // are created later in HBPreviewController if they don't exist at the moment a live + // preview encode is initiated. if (hbInstanceNum == 1) { - NSString *PreviewDirectory = [[HBUtilities appSupportPath] stringByAppendingPathComponent:@"Previews"]; - NSError *error; - NSArray *files = [ [NSFileManager defaultManager] contentsOfDirectoryAtPath: PreviewDirectory error: &error ]; - for( NSString *file in files ) + NSString *previewDirectory = [[HBUtilities appSupportPath] stringByAppendingPathComponent:@"Previews"]; + NSError *error = nil; + NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:previewDirectory error:&error]; + for (NSString *file in files) { - if( ![file isEqual: @"."] && ![file isEqual: @".."] ) + if (![file isEqual:@"."] && ![file isEqual:@".."]) { - [ [NSFileManager defaultManager] removeItemAtPath: [ PreviewDirectory stringByAppendingPathComponent: file ] error: &error ]; - if( error ) + BOOL result = [[NSFileManager defaultManager] removeItemAtPath:[previewDirectory stringByAppendingPathComponent:file] error:&error]; + if (result == NO && error) { //an error occurred - [HBUtilities writeToActivityLog: "Could not remove existing preview at : %s",[file UTF8String] ]; + [HBUtilities writeToActivityLog: "Could not remove existing preview at : %s", file.UTF8String]; } } } - } - [self enableUI: NO]; - // Open debug output window now if it was visible when HB was closed if ([[NSUserDefaults standardUserDefaults] boolForKey:@"OutputPanelIsOpen"]) [self showDebugOutputPanel:nil]; @@ -186,70 +148,53 @@ [self openMainWindow:nil]; - /* Now we re-check the queue array to see if there are - * any remaining encodes to be done in it and ask the - * user if they want to reload the queue */ - if ([QueueFileArray count] > 0) + // Now we re-check the queue array to see if there are + // any remaining encodes to be done in it and ask the + // user if they want to reload the queue */ + if (fQueueController.count) { - /* run getQueueStats to see whats in the queue file */ - [self getQueueStats]; - /* this results in these values - * fPendingCount = 0; - * fWorkingCount = 0; - */ - - /* On Screen Notification - * We check to see if there is already another instance of hb running. - * Note: hbInstances == 1 means we are the only instance of HandBrake.app - */ + // On Screen Notification + // We check to see if there is already another instance of hb running. + // Note: hbInstances == 1 means we are the only instance of HandBrake.app + NSAlert *alert = nil; if (hbInstanceNum > 1) { - NSAlert *alert = [[NSAlert alloc] init]; + alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"There is already an instance of HandBrake running.", @"")]; [alert setInformativeText:NSLocalizedString(@"HandBrake will now load up the existing queue.", nil)]; [alert addButtonWithTitle:NSLocalizedString(@"Reload Queue", nil)]; - [alert beginSheetModalForWindow:fWindow - modalDelegate:self - didEndSelector:@selector(didDimissReloadQueue:returnCode:contextInfo:) - contextInfo:nil]; - [alert release]; } else { - if (fWorkingCount > 0 || fPendingCount > 0) + if (fQueueController.workingItemsCount > 0 || fQueueController.pendingItemsCount > 0) { NSString *alertTitle; - if (fWorkingCount > 0) + if (fQueueController.workingItemsCount > 0) { alertTitle = [NSString stringWithFormat: NSLocalizedString(@"HandBrake Has Detected %d Previously Encoding Item(s) and %d Pending Item(s) In Your Queue.", @""), - fWorkingCount,fPendingCount]; + fQueueController.workingItemsCount, fQueueController.pendingItemsCount]; } else { alertTitle = [NSString stringWithFormat: NSLocalizedString(@"HandBrake Has Detected %d Pending Item(s) In Your Queue.", @""), - fPendingCount]; + fQueueController.pendingItemsCount]; } - NSAlert *alert = [[NSAlert alloc] init]; + alert = [[NSAlert alloc] init]; [alert setMessageText:alertTitle]; [alert setInformativeText:NSLocalizedString(@"Do you want to reload them ?", nil)]; [alert addButtonWithTitle:NSLocalizedString(@"Reload Queue", nil)]; [alert addButtonWithTitle:NSLocalizedString(@"Empty Queue", nil)]; [alert setAlertStyle:NSCriticalAlertStyle]; - [alert beginSheetModalForWindow:fWindow - modalDelegate:self - didEndSelector:@selector(didDimissReloadQueue:returnCode:contextInfo:) - contextInfo:nil]; - [alert release]; } else { - /* Since we addressed any pending or previously encoding items above, we go ahead and make sure - * the queue is empty of any finished items or cancelled items */ - [self clearQueueAllItems]; + // Since we addressed any pending or previously encoding items above, we go ahead and make sure + // the queue is empty of any finished items or cancelled items. + [fQueueController removeAllJobs]; if (self.core.state != HBStateScanning && !self.job) { @@ -265,7 +210,40 @@ } } } - + } + + if (alert) + { + NSModalResponse response = [alert runModal]; + + if (response == NSAlertSecondButtonReturn) + { + [HBUtilities writeToActivityLog: "didDimissReloadQueue NSAlertSecondButtonReturn Chosen"]; + [fQueueController removeAllJobs]; + + // We show whichever open source window specified in LaunchSourceBehavior preference key + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"LaunchSourceBehavior"] isEqualToString: @"Open Source"]) + { + [self browseSources:nil]; + } + + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"LaunchSourceBehavior"] isEqualToString: @"Open Source (Title Specific)"]) + { + [self browseSources:(id)fOpenSourceTitleMMenu]; + } + } + else + { + [HBUtilities writeToActivityLog: "didDimissReloadQueue NSAlertFirstButtonReturn Chosen"]; + if (hbInstanceNum == 1) + { + [fQueueController setEncodingJobsAsPending]; + } + + [self showQueueWindow:nil]; + } + + [alert release]; } } else @@ -284,7 +262,6 @@ } } } - currentQueueEncodeNameString = @""; } - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames @@ -333,49 +310,6 @@ } #pragma mark - -#pragma mark Multiple Instances - -/* hbInstances checks to see if other instances of HB are running and also sets the pid for this instance for multi-instance queue encoding */ - - /* Note for now since we are in early phases of multi-instance I have put in quite a bit of logging. Can be removed as we see fit. */ -- (int) hbInstances -{ - /* check to see if another instance of HandBrake.app is running */ - NSArray *runningInstances = [NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]]; - NSRunningApplication *runningInstance; - - NSRunningApplication *thisInstance = [NSRunningApplication currentApplication]; - NSString *thisInstanceAppPath = [[NSBundle mainBundle] bundlePath]; - [HBUtilities writeToActivityLog: "hbInstances path to this instance: %s", [thisInstanceAppPath UTF8String]]; - - int hbInstances = 0; - NSString *runningInstanceAppPath; - pid_t runningInstancePidNum; - - for (runningInstance in runningInstances) - { - /*Report the path to each active instances app path */ - runningInstancePidNum = [runningInstance processIdentifier]; - runningInstanceAppPath = [[runningInstance bundleURL] path]; - [HBUtilities writeToActivityLog: "hbInstance found instance pidnum: %d at path: %s", runningInstancePidNum, [runningInstanceAppPath UTF8String]]; - /* see if this is us*/ - if ([runningInstance isEqual: thisInstance]) - { - /* If so this is our pidnum */ - [HBUtilities writeToActivityLog: "hbInstance MATCH FOUND, our pidnum is: %d", runningInstancePidNum]; - /* Get the PID number for this hb instance, used in multi instance encoding */ - pidNum = runningInstancePidNum; - /* Report this pid to the activity log */ - [HBUtilities writeToActivityLog: "Pid for this instance: %d", pidNum]; - /* Tell fQueueController what our pidNum is */ - [fQueueController setPidNum:pidNum]; - } - hbInstances++; - } - return hbInstances; -} - -#pragma mark - #pragma mark Drag & drop handling // This method is used by OSX to know what kind of files can be drag & drop on the NSWindow @@ -409,42 +343,9 @@ #pragma mark - -- (void) didDimissReloadQueue: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo -{ - - [HBUtilities writeToActivityLog: "didDimissReloadQueue number of hb instances:%d", hbInstanceNum]; - if (returnCode == NSAlertSecondButtonReturn) - { - [HBUtilities writeToActivityLog: "didDimissReloadQueue NSAlertSecondButtonReturn Chosen"]; - [self clearQueueAllItems]; - - /* We show whichever open source window specified in LaunchSourceBehavior preference key */ - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"LaunchSourceBehavior"] isEqualToString: @"Open Source"]) - { - [self browseSources:nil]; - } - - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"LaunchSourceBehavior"] isEqualToString: @"Open Source (Title Specific)"]) - { - [self browseSources:(id)fOpenSourceTitleMMenu]; - } - } - else - { - [HBUtilities writeToActivityLog: "didDimissReloadQueue NSAlertFirstButtonReturn Chosen"]; - if (hbInstanceNum == 1) - { - - [self setQueueEncodingItemsAsPending]; - } - [self reloadQueue]; - [self showQueueWindow:NULL]; - } -} - - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) app { - if (self.queueCore.state != HBStateIdle) + if (fQueueController.core.state != HBStateIdle) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"Are you sure you want to quit HandBrake?", nil)]; @@ -467,7 +368,7 @@ } // Warn if items still in the queue - else if (fPendingCount > 0) + else if (fQueueController.pendingItemsCount > 0) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"Are you sure you want to quit HandBrake?", nil)]; @@ -495,23 +396,20 @@ [presetManager savePresets]; [presetManager release]; - [self closeQueueFSEvent]; - [currentQueueEncodeNameString release]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [outputPanel release]; [fQueueController release]; + [fQueueController release]; [fPreviewController release]; [fPictureController release]; - [dockTile release]; - - [[NSNotificationCenter defaultCenter] removeObserver:self]; self.core = nil; - self.queueCore = nil; self.browsedSourceDisplayName = nil; + [HBCore closeGlobal]; } - - (void) awakeFromNib { /* For 64 bit builds, the threaded animation in the progress @@ -525,6 +423,8 @@ * should require their own thread. */ + _window = fWindow; + [fScanIndicator setUsesThreadedAnimation:NO]; [fRipIndicator setUsesThreadedAnimation:NO]; @@ -655,294 +555,6 @@ fPresetsView.enabled = b; } -/** - * Registers the observers to the scan core notifications - */ -- (void)registerScanCoreNotifications -{ - NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreScanningNotification object:self.core queue:mainQueue usingBlock:^(NSNotification *note) { - hb_state_t s = *(self.core.hb_state); - #define p s.param.scanning - if (p.preview_cur) - { - fSrcDVD2Field.stringValue = [NSString stringWithFormat: - NSLocalizedString( @"Scanning title %d of %d, preview %d…", @"" ), - p.title_cur, p.title_count, - p.preview_cur]; - } - else - { - fSrcDVD2Field.stringValue = [NSString stringWithFormat: - NSLocalizedString( @"Scanning title %d of %d…", @"" ), - p.title_cur, p.title_count]; - } - fScanIndicator.hidden = NO; - fScanHorizontalLine.hidden = YES; - fScanIndicator.doubleValue = 100.0 * p.progress; - #undef p - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreScanDoneNotification object:self.core queue:mainQueue usingBlock:^(NSNotification *note) { - fScanHorizontalLine.hidden = NO; - fScanIndicator.hidden = YES; - fScanIndicator.indeterminate = NO; - fScanIndicator.doubleValue = 0.0; - - [HBUtilities writeToActivityLog:"ScanDone state received from fHandle"]; - [self showNewScan]; - [[fWindow toolbar] validateVisibleItems]; - }]; -} - -- (void)registerQueueCoreNotifications -{ - NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreScanningNotification object:self.queueCore queue:mainQueue usingBlock:^(NSNotification *note) { - hb_state_t s = *(self.queueCore.hb_state); - #define p s.param.scanning - NSString *scan_status; - if (p.preview_cur) - { - scan_status = [NSString stringWithFormat: - NSLocalizedString( @"Queue Scanning title %d of %d, preview %d…", @"" ), - p.title_cur, p.title_count, p.preview_cur]; - } - else - { - scan_status = [NSString stringWithFormat: - NSLocalizedString( @"Queue Scanning title %d of %d…", @"" ), - p.title_cur, p.title_count]; - } - fStatusField.stringValue = scan_status; - - // Set the status string in fQueueController as well - [fQueueController setQueueStatusString: scan_status]; - #undef p - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreScanDoneNotification object:self.queueCore queue:mainQueue usingBlock:^(NSNotification *note) { - [HBUtilities writeToActivityLog:"ScanDone state received from fQueueEncodeLibhb"]; - [self processNewQueueEncode]; - [[fWindow toolbar] validateVisibleItems]; - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreSearchingNotification object:self.queueCore queue:mainQueue usingBlock:^(NSNotification *note) { - hb_state_t s = *(self.queueCore.hb_state); - #define p s.param.working - NSMutableString *string = [NSMutableString stringWithFormat: - NSLocalizedString(@"Searching for start point… : %.2f %%", @""), - 100.0 * p.progress]; - - if (p.seconds > -1) - { - [string appendFormat:NSLocalizedString(@" (ETA %02dh%02dm%02ds)", @"" ), p.hours, p.minutes, p.seconds]; - } - - fStatusField.stringValue = string; - // Set the status string in fQueueController as well - [fQueueController setQueueStatusString: string]; - - // Update slider - CGFloat progress_total = (p.progress + p.job_cur - 1) / p.job_count; - fRipIndicator.indeterminate = NO; - fRipIndicator.doubleValue = 100.0 * progress_total; - - // If progress bar hasn't been revealed at the bottom of the window, do - // that now. This code used to be in doRip. I moved it to here to handle - // the case where hb_start is called by HBQueueController and not from - // HBController. - if (!fRipIndicatorShown) - { - NSRect frame = [fWindow frame]; - if (frame.size.width <= 591) - frame.size.width = 591; - frame.size.height += 36; - frame.origin.y -= 36; - [fWindow setFrame:frame display:YES animate:YES]; - fRipIndicatorShown = YES; - } - #undef p - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreWorkingNotification object:self.queueCore queue:mainQueue usingBlock:^(NSNotification *note) { - hb_state_t s = *(self.queueCore.hb_state); - #define p s.param.working - // Update text field - NSString *pass_desc; - if (p.job_cur == 1 && p.job_count > 1) - { - HBJob *queueJob = QueueFileArray[currentQueueEncodeIndex]; - if (queueJob.subtitles.tracks.count && - [queueJob.subtitles.tracks.firstObject[keySubTrackIndex] intValue] == -1) - { - pass_desc = NSLocalizedString(@"(subtitle scan)", @""); - } - else - { - pass_desc = @""; - } - } - else - { - pass_desc = @""; - } - - NSMutableString *string; - if (pass_desc.length) - { - string = [NSMutableString stringWithFormat: - NSLocalizedString(@"Encoding: %@ \nPass %d %@ of %d, %.2f %%", @""), - currentQueueEncodeNameString, - p.job_cur, pass_desc, p.job_count, 100.0 * p.progress]; - } - else - { - string = [NSMutableString stringWithFormat: - NSLocalizedString(@"Encoding: %@ \nPass %d of %d, %.2f %%", @""), - currentQueueEncodeNameString, - p.job_cur, p.job_count, 100.0 * p.progress]; - } - - if (p.seconds > -1) - { - if (p.rate_cur > 0.0) - { - [string appendFormat: - NSLocalizedString(@" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @""), - p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds]; - } - else - { - [string appendFormat: - NSLocalizedString(@" (ETA %02dh%02dm%02ds)", @""), - p.hours, p.minutes, p.seconds]; - } - } - - fStatusField.stringValue = string; - [fQueueController setQueueStatusString:string]; - - // Update slider - CGFloat progress_total = (p.progress + p.job_cur - 1) / p.job_count; - fRipIndicator.indeterminate = NO; - fRipIndicator.doubleValue = 100.0 * progress_total; - - // If progress bar hasn't been revealed at the bottom of the window, do - // that now. This code used to be in doRip. I moved it to here to handle - // the case where hb_start is called by HBQueueController and not from - // HBController. - if (!fRipIndicatorShown) - { - NSRect frame = [fWindow frame]; - if (frame.size.width <= 591) - frame.size.width = 591; - frame.size.height += 36; - frame.origin.y -= 36; - [fWindow setFrame:frame display:YES animate:YES]; - fRipIndicatorShown = YES; - } - - /* Update dock icon */ - if (dockIconProgress < 100.0 * progress_total) - { - // ETA format is [XX]X:XX:XX when ETA is greater than one hour - // [X]X:XX when ETA is greater than 0 (minutes or seconds) - // When these conditions doesn't applied (eg. when ETA is undefined) - // we show just a tilde (~) - - NSString *etaStr = @""; - if (p.hours > 0) - etaStr = [NSString stringWithFormat:@"%d:%02d:%02d", p.hours, p.minutes, p.seconds]; - else if (p.minutes > 0 || p.seconds > 0) - etaStr = [NSString stringWithFormat:@"%d:%02d", p.minutes, p.seconds]; - else - etaStr = @"~"; - - [dockTile updateDockIcon:progress_total withETA:etaStr]; - - dockIconProgress += dockTileUpdateFrequency; - } - #undef p - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreMuxingNotification object:self.queueCore queue:mainQueue usingBlock:^(NSNotification *note) { - // Update text field - fStatusField.stringValue = NSLocalizedString(@"Muxing…", @""); - // Set the status string in fQueueController as well - [fQueueController setQueueStatusString:NSLocalizedString(@"Muxing…", @"")]; - // Update slider - fRipIndicator.indeterminate = YES; - [fRipIndicator startAnimation: nil]; - - // Update dock icon - [dockTile updateDockIcon:1.0 withETA:@""]; - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCorePausedNotification object:self.queueCore queue:mainQueue usingBlock:^(NSNotification *note) { - NSString *paused = NSLocalizedString(@"Paused", @""); - fStatusField.stringValue = paused; - [fQueueController setQueueStatusString:paused]; - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreWorkDoneNotification object:self.queueCore queue:mainQueue usingBlock:^(NSNotification *note) { - fStatusField.stringValue = NSLocalizedString(@"Encode Finished.", @""); - // Set the status string in fQueueController as well - [fQueueController setQueueStatusString:NSLocalizedString(@"Encode Finished.", @"")]; - fRipIndicator.indeterminate = NO; - fRipIndicator.doubleValue = 0.0; - [fRipIndicator setIndeterminate: NO]; - - [[fWindow toolbar] validateVisibleItems]; - - // Restore dock icon - [dockTile updateDockIcon:-1.0 withETA:@""]; - dockIconProgress = 0; - - if (fRipIndicatorShown) - { - NSRect frame = [fWindow frame]; - if( frame.size.width <= 591 ) - frame.size.width = 591; - frame.size.height += -36; - frame.origin.y -= -36; - [fWindow setFrame:frame display:YES animate:YES]; - fRipIndicatorShown = NO; - } - // Since we are done with this encode, tell output to stop writing to the - // individual encode log. - [outputPanel endEncodeLog]; - - // Check to see if the encode state has not been cancelled - // to determine if we should check for encode done notifications. - if (fEncodeState != 2) - { - // Get the output file name for the finished encode - HBJob *finishedJob = QueueFileArray[currentQueueEncodeIndex]; - - // Both the Growl Alert and Sending to tagger can be done as encodes roll off the queue - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Growl Notification"] || - [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window And Growl"]) - { - // If Play System Alert has been selected in Preferences - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"AlertWhenDoneSound"] == YES) - { - NSBeep(); - } - [self showGrowlDoneNotification:finishedJob.destURL]; - } - - // Send to tagger - [self sendToExternalApp:finishedJob.destURL]; - - // since we have successfully completed an encode, we increment the queue counter - [self incrementQueueItemDone:currentQueueEncodeIndex]; - } - }]; -} - #pragma mark - #pragma mark Toolbar @@ -961,7 +573,7 @@ return YES; } - if (action == @selector(Rip:) || action == @selector(addToQueue:)) + if (action == @selector(rip:) || action == @selector(addToQueue:)) return NO; } else @@ -976,11 +588,11 @@ } } - HBState queueState = self.queueCore.state; + HBState queueState = fQueueController.core.state; if (queueState == HBStateScanning || queueState == HBStateWorking || queueState == HBStateSearching || queueState == HBStateMuxing) { - if (action == @selector(Rip:)) + if (action == @selector(rip:)) { [toolbarItem setImage: [NSImage imageNamed: @"stopencode"]]; [toolbarItem setLabel: @"Stop"]; @@ -988,7 +600,7 @@ [toolbarItem setToolTip: @"Stop Encoding"]; return YES; } - if (action == @selector(Pause:)) + if (action == @selector(pause:)) { [toolbarItem setImage: [NSImage imageNamed: @"pauseencode"]]; [toolbarItem setLabel: @"Pause"]; @@ -999,7 +611,7 @@ } else if (queueState == HBStatePaused) { - if (action == @selector(Pause:)) + if (action == @selector(pause:)) { [toolbarItem setImage: [NSImage imageNamed: @"encode"]]; [toolbarItem setLabel: @"Resume"]; @@ -1007,15 +619,15 @@ [toolbarItem setToolTip: @"Resume Encoding"]; return YES; } - if (action == @selector(Rip:)) + if (action == @selector(rip:)) return YES; } else { - if (action == @selector(Rip:)) + if (action == @selector(rip:)) { [toolbarItem setImage: [NSImage imageNamed: @"encode"]]; - if (fPendingCount > 0) + if (fQueueController.pendingItemsCount > 0) [toolbarItem setLabel: @"Start Queue"]; else [toolbarItem setLabel: @"Start"]; @@ -1044,7 +656,7 @@ } // If there are any pending queue items, make sure the start/stop button is active. - if (action == @selector(Rip:) && (fPendingCount > 0 || self.job)) + if (action == @selector(rip:) && (fQueueController.pendingItemsCount > 0 || self.job)) return YES; if (action == @selector(showQueueWindow:)) return YES; @@ -1061,7 +673,7 @@ - (BOOL) validateMenuItem: (NSMenuItem *) menuItem { SEL action = [menuItem action]; - HBState queueState = self.queueCore.state; + HBState queueState = fQueueController.core.state; if (action == @selector(addToQueue:) || action == @selector(addAllTitlesToQueue:) || action == @selector(showPicturePanel:) || action == @selector(showAddPresetPanel:)) return self.job && [fWindow attachedSheet] == nil; @@ -1069,7 +681,7 @@ if (action == @selector(selectDefaultPreset:)) return [fWindow attachedSheet] == nil; - if (action == @selector(Pause:)) + if (action == @selector(pause:)) { if (queueState == HBStateWorking) { @@ -1086,7 +698,7 @@ else return NO; } - if (action == @selector(Rip:)) + if (action == @selector(rip:)) { if (queueState == HBStateWorking || queueState == HBStateMuxing || queueState == HBStatePaused) { @@ -1126,94 +738,6 @@ } #pragma mark - -#pragma mark Encode Done Actions - -#define SERVICE_NAME @"Encode Done" - -/** - * Register a test notification and make - * it enabled by default - */ -- (NSDictionary *)registrationDictionaryForGrowl -{ - return @{GROWL_NOTIFICATIONS_ALL: @[SERVICE_NAME], - GROWL_NOTIFICATIONS_DEFAULT: @[SERVICE_NAME]}; -} - -- (void)showGrowlDoneNotification:(NSURL *)fileURL -{ - // This end of encode action is called as each encode rolls off of the queue - // Setup the Growl stuff - NSString *growlMssg = [NSString stringWithFormat:@"your HandBrake encode %@ is done!", fileURL.lastPathComponent]; - [GrowlApplicationBridge notifyWithTitle:@"Put down that cocktail…" - description:growlMssg - notificationName:SERVICE_NAME - iconData:nil - priority:0 - isSticky:1 - clickContext:nil]; -} - -- (void)sendToExternalApp:(NSURL *)fileURL -{ - // This end of encode action is called as each encode rolls off of the queue - if([[NSUserDefaults standardUserDefaults] boolForKey: @"sendToMetaX"] == YES) - { - NSString *sendToApp = [[NSUserDefaults standardUserDefaults] objectForKey:@"SendCompletedEncodeToApp"]; - if (![sendToApp isEqualToString:@"None"]) - { - [HBUtilities writeToActivityLog: "trying to send encode to: %s", [sendToApp UTF8String]]; - NSAppleScript *myScript = [[NSAppleScript alloc] initWithSource: [NSString stringWithFormat: @"%@%@%@%@%@", @"tell application \"",sendToApp,@"\" to open (POSIX file \"", fileURL.path, @"\")"]]; - [myScript executeAndReturnError: nil]; - [myScript release]; - } - } -} - -- (void) queueCompletedAlerts -{ - /* If Play System Alert has been selected in Preferences */ - if( [[NSUserDefaults standardUserDefaults] boolForKey:@"AlertWhenDoneSound"] == YES ) - { - NSBeep(); - } - - /* If Alert Window or Window and Growl has been selected */ - if( [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window"] || - [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window And Growl"] ) - { - /*On Screen Notification*/ - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:@"Put down that cocktail…"]; - [alert setInformativeText:@"Your HandBrake queue is done!"]; - [NSApp requestUserAttention:NSCriticalRequest]; - [alert runModal]; - [alert release]; - } - - /* If sleep has been selected */ - if( [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Put Computer To Sleep"] ) - { - /* Sleep */ - NSDictionary *errorDict; - NSAppleScript *scriptObject = [[NSAppleScript alloc] initWithSource: - @"tell application \"Finder\" to sleep"]; - [scriptObject executeAndReturnError: &errorDict]; - [scriptObject release]; - } - /* If Shutdown has been selected */ - if( [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Shut Down Computer"] ) - { - /* Shut Down */ - NSDictionary* errorDict; - NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource: - @"tell application \"Finder\" to shut down"]; - [scriptObject executeAndReturnError: &errorDict]; - [scriptObject release]; - } -} - -#pragma mark - #pragma mark Get New Source /** @@ -1450,75 +974,102 @@ int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue]; int min_title_duration_seconds = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MinTitleScanSeconds"] intValue]; - [self.core scan:scanURL - titleNum:scanTitleNum - previewsNum:hb_num_previews minTitleDuration:min_title_duration_seconds]; + [self.core scanURL:scanURL + titleIndex:scanTitleNum + previews:hb_num_previews minDuration:min_title_duration_seconds + progressHandler:^(HBState state, hb_state_t hb_state) + { + #define p hb_state.param.scanning + if (p.preview_cur) + { + fSrcDVD2Field.stringValue = [NSString stringWithFormat: + NSLocalizedString( @"Scanning title %d of %d, preview %d…", @"" ), + p.title_cur, p.title_count, + p.preview_cur]; + } + else + { + fSrcDVD2Field.stringValue = [NSString stringWithFormat: + NSLocalizedString( @"Scanning title %d of %d…", @"" ), + p.title_cur, p.title_count]; + } + fScanIndicator.hidden = NO; + fScanHorizontalLine.hidden = YES; + fScanIndicator.doubleValue = 100.0 * p.progress; + #undef p + } + completationHandler:^(BOOL success) + { + fScanHorizontalLine.hidden = NO; + fScanIndicator.hidden = YES; + fScanIndicator.indeterminate = NO; + fScanIndicator.doubleValue = 0.0; + + if (success) + { + [self showNewScan]; + } + else + { + // We display a message if a valid source was not chosen + fSrcDVD2Field.stringValue = NSLocalizedString(@"No Valid Source Found", @""); + } + [fWindow.toolbar validateVisibleItems]; + }]; } } - (void)showNewScan { - if (!self.core.titles.count) + if (self.jobFromQueue) { - // We display a message if a valid source was not chosen - fSrcDVD2Field.stringValue = @"No Valid Source Found"; + // we are a rescan of an existing queue item and need to apply the queued settings to the scan + [HBUtilities writeToActivityLog: "showNewScan: This is a queued item rescan"]; } else { - if (self.jobFromQueue) - { - // we are a rescan of an existing queue item and need to apply the queued settings to the scan - [HBUtilities writeToActivityLog: "showNewScan: This is a queued item rescan"]; - } - else - { - [HBUtilities writeToActivityLog: "showNewScan: This is a new source item scan"]; - } - - [fSrcTitlePopUp removeAllItems]; + [HBUtilities writeToActivityLog: "showNewScan: This is a new source item scan"]; + } - for (HBTitle *title in self.core.titles) - { - // Set Source Name at top of window with the browsedSourceDisplayName grokked right before -performScan - if (!self.browsedSourceDisplayName) - { - self.browsedSourceDisplayName = @"NoNameDetected"; - } - fSrcDVD2Field.stringValue = self.browsedSourceDisplayName; + [fSrcTitlePopUp removeAllItems]; - [fSrcTitlePopUp addItemWithTitle:title.description]; + for (HBTitle *title in self.core.titles) + { + // Set Source Name at top of window with the browsedSourceDisplayName grokked right before -performScan + fSrcDVD2Field.stringValue = self.browsedSourceDisplayName; - // See if this is the main feature according - if (title.isFeatured) - { - [fSrcTitlePopUp selectItemWithTitle:title.description]; - } - } + [fSrcTitlePopUp addItemWithTitle:title.description]; - // Select the first item is nothing is selected - if (!fSrcTitlePopUp.selectedItem) + // See if this is the main feature according + if (title.isFeatured) { - [fSrcTitlePopUp selectItemAtIndex:0]; + [fSrcTitlePopUp selectItemWithTitle:title.description]; } + } - // Updates the main window ui - [self enableUI:YES]; - [self titlePopUpChanged:nil]; + // Select the first item is nothing is selected + if (!fSrcTitlePopUp.selectedItem) + { + [fSrcTitlePopUp selectItemAtIndex:0]; + } - // Open preview window now if it was visible when HB was closed - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"PreviewWindowIsOpen"]) - [self showPreviewWindow:nil]; + // Updates the main window ui + [self enableUI:YES]; + [self titlePopUpChanged:nil]; - // Open picture sizing window now if it was visible when HB was closed - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"PictureSizeWindowIsOpen"]) - [self showPicturePanel:nil]; + // Open preview window now if it was visible when HB was closed + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"PreviewWindowIsOpen"]) + [self showPreviewWindow:nil]; - if (self.jobFromQueue) - { - [fPresetsView deselect]; + // Open picture sizing window now if it was visible when HB was closed + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"PictureSizeWindowIsOpen"]) + [self showPicturePanel:nil]; - self.jobFromQueue = nil; - } + if (self.jobFromQueue) + { + [fPresetsView deselect]; + + self.jobFromQueue = nil; } } @@ -1565,438 +1116,83 @@ return contentSize; } -#pragma mark - -#pragma mark Queue File - -static void queueFSEventStreamCallback( - ConstFSEventStreamRef streamRef, - void *clientCallBackInfo, - size_t numEvents, - void *eventPaths, - const FSEventStreamEventFlags eventFlags[], - const FSEventStreamEventId eventIds[]) -{ - if (numEvents >= 1) - { - // Reload the queue only if one of the following events happened. - FSEventStreamEventFlags flags = eventFlags[0]; - if (flags & (kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRemoved | kFSEventStreamEventFlagItemRenamed | kFSEventStreamEventFlagItemModified)) - { - HBController *hb = (HBController *)clientCallBackInfo; - [hb reloadQueue]; - } - } -} +#pragma mark - Queue Item Editing -- (void)initQueueFSEvent +/** + * Rescans the chosen queue item back into the main window + */ +- (void)rescanJobToMainWindow:(HBJob *)queueItem { - /* Define variables and create a CFArray object containing - CFString objects containing paths to watch. - */ - CFStringRef mypath = (CFStringRef) [[HBUtilities appSupportPath] stringByAppendingPathComponent:@"Queue"]; - CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL); - - FSEventStreamContext callbackCtx; - callbackCtx.version = 0; - callbackCtx.info = self; - callbackCtx.retain = NULL; - callbackCtx.release = NULL; - callbackCtx.copyDescription = NULL; - - CFAbsoluteTime latency = 0.5; /* Latency in seconds */ - - /* Create the stream, passing in a callback */ - QueueStream = FSEventStreamCreate(NULL, - &queueFSEventStreamCallback, - &callbackCtx, - pathsToWatch, - kFSEventStreamEventIdSinceNow, - latency, - kFSEventStreamCreateFlagIgnoreSelf | kFSEventStreamCreateFlagMarkSelf - ); - - CFRelease(pathsToWatch); + // Set the browsedSourceDisplayName for showNewScan + self.jobFromQueue = queueItem; + self.browsedSourceDisplayName = self.jobFromQueue.fileURL.lastPathComponent; - /* Create the stream before calling this. */ - FSEventStreamScheduleWithRunLoop(QueueStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - FSEventStreamStart(QueueStream); + [self performScan:self.jobFromQueue.fileURL scanTitleNum:self.jobFromQueue.titleIdx]; } -- (void)closeQueueFSEvent -{ - FSEventStreamStop(QueueStream); - FSEventStreamInvalidate(QueueStream); - FSEventStreamRelease(QueueStream); -} +#pragma mark - Queue -- (void)loadQueueFile +- (void)setQueueState:(NSString *)info { - // We declare the default NSFileManager into fileManager - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *appSupportPath = [HBUtilities appSupportPath]; - - // We define the location of the user presets file - QueueFile = [[appSupportPath stringByAppendingPathComponent:@"Queue/Queue.hbqueue"] retain]; - - // We check for the Queue.plist - if (![fileManager fileExistsAtPath:QueueFile]) - { - if (![fileManager fileExistsAtPath:[appSupportPath stringByAppendingPathComponent:@"Queue"]]) - { - [fileManager createDirectoryAtPath:[appSupportPath stringByAppendingPathComponent:@"Queue"] withIntermediateDirectories:YES attributes:nil error:NULL]; - } - [fileManager createFileAtPath:QueueFile contents:nil attributes:nil]; - } - - @try - { - QueueFileArray = [[NSKeyedUnarchiver unarchiveObjectWithFile:QueueFile] retain]; - } - @catch (NSException *exception) - { - [HBUtilities writeToActivityLog:"failed to read the queue to disk"]; - } - - // lets check to see if there is anything in the queue file .plist - if (QueueFileArray == nil) - { - // if not, then lets initialize an empty array - QueueFileArray = [[NSMutableArray alloc] init]; - } - else - { - // ONLY clear out encoded items if we are single instance - if (hbInstanceNum == 1) - { - [self clearQueueEncodedItems]; - } - } + fQueueStatus.stringValue = info; } -- (void)reloadQueue -{ - [HBUtilities writeToActivityLog:"Queue reloaded"]; +#define WINDOW_HEIGHT 591 +#define WINDOW_HEIGHT_OFFSET 36 - NSMutableArray *tempQueueArray = nil;; - @try - { - tempQueueArray = [NSKeyedUnarchiver unarchiveObjectWithFile:QueueFile]; - } - @catch (NSException *exception) - { - tempQueueArray = nil; - [HBUtilities writeToActivityLog:"failed to read the queue to disk"]; - } - - [QueueFileArray setArray:tempQueueArray]; - // Send Fresh QueueFileArray to fQueueController to update queue window - [fQueueController setQueueArray:QueueFileArray]; - [self getQueueStats]; -} - -- (void)addQueueFileItem +- (void)setQueueInfo:(NSString *)info progress:(double)progress hidden:(BOOL)hidden { - [QueueFileArray addObject:[[self.job copy] autorelease]]; - [self saveQueueFileItem]; -} - -- (void)removeQueueFileItem:(NSUInteger)queueItemToRemove -{ - [QueueFileArray removeObjectAtIndex:queueItemToRemove]; - [self saveQueueFileItem]; -} + fStatusField.stringValue = info; + fRipIndicator.doubleValue = progress; -- (void)saveQueueFileItem -{ - if (![NSKeyedArchiver archiveRootObject:QueueFileArray toFile:QueueFile]) + if (hidden) { - [HBUtilities writeToActivityLog:"failed to write the queue to disk"]; - } - [fQueueController setQueueArray: QueueFileArray]; - [self getQueueStats]; -} - -/** - * Updates the queue status label on the main window. - */ -- (void)getQueueStats -{ - // lets get the stats on the status of the queue array - fPendingCount = 0; - fWorkingCount = 0; - - int i = 0; - for (HBJob *job in QueueFileArray) - { - if (job.state == HBJobStateWorking) // being encoded - { - fWorkingCount++; - // check to see if we are the instance doing this encoding - if (job.pidId == pidNum) - { - currentQueueEncodeIndex = i; - } - } - if (job.state == HBJobStateReady) // pending + if (fRipIndicatorShown) { - fPendingCount++; - } - i++; - } + NSRect frame = fWindow.frame; + if (frame.size.width <= WINDOW_HEIGHT) + frame.size.width = WINDOW_HEIGHT; + frame.size.height += -WINDOW_HEIGHT_OFFSET; + frame.origin.y -= -WINDOW_HEIGHT_OFFSET; + [fWindow setFrame:frame display:YES animate:YES]; + fRipIndicatorShown = NO; - // Set the queue status field in the main window - NSString *string; - if (fPendingCount == 0) - { - string = NSLocalizedString( @"No encode pending", @""); - } - else if (fPendingCount == 1) - { - string = [NSString stringWithFormat: NSLocalizedString( @"%d encode pending", @"" ), fPendingCount]; + // Refresh the toolbar buttons + [fWindow.toolbar validateVisibleItems]; + } } else { - string = [NSString stringWithFormat: NSLocalizedString( @"%d encodes pending", @"" ), fPendingCount]; - } - - [fQueueStatus setStringValue:string]; -} - -/** - * Used to get the next pending queue item index and return it if found - */ -- (NSInteger)getNextPendingQueueIndex -{ - // initialize nextPendingIndex to -1, this value tells incrementQueueItemDone that there are no pending items in the queue - NSInteger nextPendingIndex = -1; - BOOL nextPendingFound = NO; - for (HBJob *job in QueueFileArray) - { - if (job.state == HBJobStateReady && nextPendingFound == NO) // pending - { - nextPendingFound = YES; - nextPendingIndex = [QueueFileArray indexOfObject:job]; - [HBUtilities writeToActivityLog: "getNextPendingQueueIndex next pending encode index is:%d", nextPendingIndex]; - } - } - return nextPendingIndex; -} - -/** - * This method will set any item marked as encoding back to pending - * currently used right after a queue reload - */ -- (void) setQueueEncodingItemsAsPending -{ - for (HBJob *job in QueueFileArray) - { - // We want to keep any queue item that is pending or was previously being encoded - if (job.state == HBJobStateWorking || job.state == HBJobStateReady) + // If progress bar hasn't been revealed at the bottom of the window, do + // that now. + if (!fRipIndicatorShown) { - // If the queue item is marked as "working" - // then change its status back to ready which effectively - // puts it back into the queue to be encoded - if (job.state == HBJobStateWorking) - { - job.state = HBJobStateReady; - } - } - } - - [self saveQueueFileItem]; -} + NSRect frame = fWindow.frame; + if (frame.size.width <= WINDOW_HEIGHT) + frame.size.width = WINDOW_HEIGHT; + frame.size.height += WINDOW_HEIGHT_OFFSET; + frame.origin.y -= WINDOW_HEIGHT_OFFSET; + [fWindow setFrame:frame display:YES animate:YES]; + fRipIndicatorShown = YES; -/** - * This method will clear the queue of any encodes that are not still pending - * this includes both successfully completed encodes as well as cancelled encodes - */ -- (void) clearQueueEncodedItems -{ - NSMutableArray *tempArray = [NSMutableArray array]; - for (HBJob *job in QueueFileArray) - { - /* If the queue item is either completed (0) or cancelled (3) from the - * last session, then we put it in tempArray to be deleted from QueueFileArray. - * NOTE: this means we retain pending (2) and also an item that is marked as - * still encoding (1). If the queue has an item that is still marked as encoding - * from a previous session, we can conlude that HB was either shutdown, or crashed - * during the encodes so we keep it and tell the user in the "Load Queue Alert" - */ - if (job.state == HBJobStateCompleted || job.state == HBJobStateCanceled) - { - [tempArray addObject:job]; + // Refresh the toolbar buttons + [fWindow.toolbar validateVisibleItems]; } } - - [QueueFileArray removeObjectsInArray:tempArray]; - [self saveQueueFileItem]; -} - -/** - * This method will clear the queue of all encodes. effectively creating an empty queue - */ -- (void) clearQueueAllItems -{ - NSMutableArray *tempArray = [NSMutableArray array]; - // we look here to see if the preset is we move on to the next one - for (HBJob *job in QueueFileArray) - { - [tempArray addObject:job]; - } - - [QueueFileArray removeObjectsInArray:tempArray]; - [self saveQueueFileItem]; -} - -/** - * this is actually called from the queue controller to modify the queue array and return it back to the queue controller - */ -- (void)moveObjectsInQueueArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex -{ - NSUInteger index = [indexSet lastIndex]; - NSUInteger aboveInsertIndexCount = 0; - - NSUInteger removeIndex; - - if (index >= insertIndex) - { - removeIndex = index + aboveInsertIndexCount; - aboveInsertIndexCount++; - } - else - { - removeIndex = index; - insertIndex--; - } - - id object = [[QueueFileArray objectAtIndex:removeIndex] retain]; - [QueueFileArray removeObjectAtIndex:removeIndex]; - [QueueFileArray insertObject:object atIndex:insertIndex]; - [object release]; - - // We save all of the Queue data here - // and it also gets sent back to the queue controller - [self saveQueueFileItem]; - } -#pragma mark - -#pragma mark Queue Job Processing - -- (void)incrementQueueItemDone:(NSInteger)queueItemDoneIndexNum -{ - // Mark the encode just finished as done (status 0) - HBJob *queueJob = QueueFileArray[currentQueueEncodeIndex]; - queueJob.state = HBJobStateCompleted; - - // We save all of the Queue data here - [self saveQueueFileItem]; - - /* Since we have now marked a queue item as done - * we can go ahead and increment currentQueueEncodeIndex - * so that if there is anything left in the queue we can - * go ahead and move to the next item if we want to */ - NSInteger queueItems = [QueueFileArray count]; - /* Check to see if there are any more pending items in the queue */ - NSInteger newQueueItemIndex = [self getNextPendingQueueIndex]; - /* If we still have more pending items in our queue, lets go to the next one */ - if (newQueueItemIndex >= 0 && newQueueItemIndex < queueItems) - { - // Set our currentQueueEncodeIndex now to the newly found Pending encode as we own it - currentQueueEncodeIndex = newQueueItemIndex; - // now we mark the queue item as Status = 1 ( being encoded ) so another instance can not come along and try to scan it while we are scanning - queueJob = QueueFileArray[currentQueueEncodeIndex]; - queueJob.state = HBJobStateWorking; - [HBUtilities writeToActivityLog: "incrementQueueItemDone new pending items found: %d", currentQueueEncodeIndex]; - [self saveQueueFileItem]; - queueJob = QueueFileArray[currentQueueEncodeIndex]; - // now we can go ahead and scan the new pending queue item - [self performNewQueueScan:queueJob.fileURL.path scanTitleNum:queueJob.titleIdx]; - } - else - { - [HBUtilities writeToActivityLog: "incrementQueueItemDone there are no more pending encodes"]; - // Since there are no more items to encode, go to queueCompletedAlerts - // for user specified alerts after queue completed - [self queueCompletedAlerts]; - } -} +#pragma mark - Job Handling /** - * Here we actually tell hb_scan to perform the source scan, using the path to source and title number + * Actually adds a job to the queue */ -- (void) performNewQueueScan:(NSString *) scanPath scanTitleNum: (NSInteger) scanTitleNum +- (void)doAddToQueue { - HBJob *queueJob = QueueFileArray[currentQueueEncodeIndex]; - // Tell HB to output a new activity log file for this encode - [outputPanel startEncodeLog:queueJob.destURL]; - - // We now flag the queue item as being owned by this instance of HB using the PID - queueJob.pidId = pidNum; - // Get the currentQueueEncodeNameString from the queue item to display in the status field */ - [currentQueueEncodeNameString autorelease]; - currentQueueEncodeNameString = [[queueJob.destURL.path lastPathComponent] retain]; - // We save all of the Queue data here - [self saveQueueFileItem]; - - // Only scan 10 previews before an encode - additional previews are - // only useful for autocrop and static previews, which are already taken care of at this point - NSURL *fileURL = [NSURL fileURLWithPath:scanPath]; - [self.queueCore scan:fileURL titleNum:scanTitleNum previewsNum:10 minTitleDuration:0]; + [fQueueController addJob:[[self.job copy] autorelease]]; } /** - * This assumes that we have re-scanned and loaded up a new queue item to send to libhb as fQueueEncodeLibhb - */ -- (void)processNewQueueEncode -{ - HBJob *queueJob = QueueFileArray[currentQueueEncodeIndex]; - - if (self.queueCore.titles.count) - { - // Reset the title in the job. - queueJob.title = self.queueCore.titles[0]; - - // We should be all setup so let 'er rip - [self.queueCore encodeJob:queueJob]; - fEncodeState = 1; - - // Lets mark our new encode as 1 or "Encoding" - queueJob.state = HBJobStateWorking; - - // We are done using the title, remove it from the job - queueJob.title = nil; - [self saveQueueFileItem]; - } - else - { - [HBUtilities writeToActivityLog: "processNewQueueEncode WARNING nothing found in the title list"]; - } -} - -#pragma mark - -#pragma mark Queue Item Editing - -/* Rescans the chosen queue item back into the main window */ -- (void)rescanQueueItemToMainWindow:(NSUInteger)selectedQueueItem -{ - [HBUtilities writeToActivityLog:"rescanQueueItemToMainWindow: Re-scanning queue item at index:%d", selectedQueueItem]; - - // Set the browsedSourceDisplayName for showNewScan - self.jobFromQueue = QueueFileArray[selectedQueueItem]; - self.browsedSourceDisplayName = self.jobFromQueue.fileURL.lastPathComponent; - - [self performScan:self.jobFromQueue.fileURL scanTitleNum:self.jobFromQueue.titleIdx]; - - // Now that source is loaded and settings applied, delete the queue item from the queue - [HBUtilities writeToActivityLog: "applyQueueSettingsToMainWindow: deleting queue item:%d", selectedQueueItem]; - [self removeQueueFileItem:selectedQueueItem]; -} - -#pragma mark - -#pragma mark Job Handling - -/* addToQueue: puts up an alert before ultimately calling doAddToQueue + * Puts up an alert before ultimately calling doAddToQueue */ - (IBAction)addToQueue:(id)sender { @@ -2006,56 +1202,38 @@ static void queueFSEventStreamCallback( if ([[NSFileManager defaultManager] fileExistsAtPath:destinationDirectory] == 0) { NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:@"Warning!"]; - [alert setInformativeText:@"This is not a valid destination directory!"]; + [alert setMessageText:NSLocalizedString(@"Warning!", @"")]; + [alert setInformativeText:NSLocalizedString(@"This is not a valid destination directory!", @"")]; [alert runModal]; [alert release]; return; } - - BOOL fileExists; - fileExists = NO; - - BOOL fileExistsInQueue; - fileExistsInQueue = NO; - - // We check for and existing file here - if([[NSFileManager defaultManager] fileExistsAtPath:self.job.destURL.path]) - { - fileExists = YES; - } - - // We now run through the queue and make sure we are not overwriting an exisiting queue item - for (HBJob *job in QueueFileArray) - { - if ([job.destURL isEqualTo:self.job.destURL]) - { - fileExistsInQueue = YES; - } - } - if (fileExists == YES) + if ([[NSFileManager defaultManager] fileExistsAtPath:self.job.destURL.path]) { - NSAlert *alert = [NSAlert alertWithMessageText:@"File already exists." - defaultButton:@"Cancel" - alternateButton:@"Overwrite" - otherButton:nil - informativeTextWithFormat:@"Do you want to overwrite %@?", self.job.destURL.path]; + // File exist, warn user + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"File already exists.", @"")]; + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Do you want to overwrite %@?", @""), self.job.destURL.path]]; + [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"")]; + [alert addButtonWithTitle:NSLocalizedString(@"Overwrite", @"")]; [alert setAlertStyle:NSCriticalAlertStyle]; - [alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector( overwriteAddToQueueAlertDone:returnCode:contextInfo: ) contextInfo:NULL]; - + [alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector(overwriteAddToQueueAlertDone:returnCode:contextInfo:) contextInfo:NULL]; + [alert release]; } - else if (fileExistsInQueue == YES) + else if ([fQueueController jobExistAtURL:self.job.destURL]) { - NSAlert *alert = [NSAlert alertWithMessageText:@"There is already a queue item for this destination." - defaultButton:@"Cancel" - alternateButton:@"Overwrite" - otherButton:nil - informativeTextWithFormat:@"Do you want to overwrite %@?", self.job.destURL.path]; + // File exist in queue, warn user + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"There is already a queue item for this destination.", @"")]; + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Do you want to overwrite %@?", @""), self.job.destURL.path]]; + [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"")]; + [alert addButtonWithTitle:NSLocalizedString(@"Overwrite", @"")]; [alert setAlertStyle:NSCriticalAlertStyle]; - [alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector( overwriteAddToQueueAlertDone:returnCode:contextInfo: ) contextInfo:NULL]; + [alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector(overwriteAddToQueueAlertDone:returnCode:contextInfo:) contextInfo:NULL]; + [alert release]; } else { @@ -2063,312 +1241,159 @@ static void queueFSEventStreamCallback( } } -/* overwriteAddToQueueAlertDone: called from the alert posted by addToQueue that asks - the user if they want to overwrite an exiting movie file. -*/ -- (void) overwriteAddToQueueAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo +/** + * Called from the alert posted by addToQueue + * that asks the user if they want to overwrite an exiting movie file. + */ +- (void)overwriteAddToQueueAlertDone:(NSAlert *)alert + returnCode:(NSInteger)returnCode + contextInfo:(void *)contextInfo { - if( returnCode == NSAlertAlternateReturn ) + if (returnCode == NSAlertSecondButtonReturn) + { [self doAddToQueue]; + } } -- (void) doAddToQueue +- (void)doRip { - [self addQueueFileItem]; + // if there are no jobs in the queue, then add this one to the queue and rip + // otherwise, just rip the queue + if (fQueueController.pendingItemsCount == 0) + { + [self doAddToQueue]; + } + + [fQueueController rip:self]; } -/* Rip: puts up an alert before ultimately calling doRip -*/ -- (IBAction) Rip: (id) sender +/** + * Puts up an alert before ultimately calling doRip + */ +- (IBAction)rip:(id)sender { - [HBUtilities writeToActivityLog: "Rip: Pending queue count is %d", fPendingCount]; - /* Rip or Cancel ? */ - if (self.queueCore.state == HBStateWorking || self.queueCore.state == HBStatePaused) + // Rip or Cancel ? + if (fQueueController.core.state == HBStateWorking || fQueueController.core.state == HBStatePaused) { - [self Cancel: sender]; + [self cancel:sender]; return; } - - // We check to see if we need to warn the user that the computer will go to sleep - // or shut down when encoding is finished - [self remindUserOfSleepOrShutdown]; - + // If there are pending jobs in the queue, then this is a rip the queue - if (fPendingCount > 0) + if (fQueueController.pendingItemsCount > 0) { - currentQueueEncodeIndex = [self getNextPendingQueueIndex]; - // here lets start the queue with the first pending item - HBJob *queueJob = QueueFileArray[currentQueueEncodeIndex]; - [self performNewQueueScan:queueJob.fileURL.path scanTitleNum:queueJob.titleIdx]; - + [fQueueController rip:self]; return; } - + // Before adding jobs to the queue, check for a valid destination. NSString *destinationDirectory = self.job.destURL.path.stringByDeletingLastPathComponent; if ([[NSFileManager defaultManager] fileExistsAtPath:destinationDirectory] == 0) { NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:@"Warning!"]; - [alert setInformativeText:@"This is not a valid destination directory!"]; + [alert setMessageText:NSLocalizedString(@"Warning!", @"")]; + [alert setInformativeText:NSLocalizedString(@"This is not a valid destination directory!", @"")]; [alert runModal]; [alert release]; return; } - + // We check for duplicate name here - if( [[NSFileManager defaultManager] fileExistsAtPath:self.job.destURL.path] ) + if ([[NSFileManager defaultManager] fileExistsAtPath:self.job.destURL.path]) { - NSAlert *alert = [NSAlert alertWithMessageText:@"File already exists." - defaultButton:@"Cancel" - alternateButton:@"Overwrite" - otherButton:nil - informativeTextWithFormat:@"Do you want to overwrite %@?", self.job.destURL.path]; + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"Warning!", @"")]; + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Do you want to overwrite %@?", @""), self.job.destURL.path]]; + [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"")]; + [alert addButtonWithTitle:NSLocalizedString(@"Overwrite", @"")]; [alert setAlertStyle:NSCriticalAlertStyle]; - [alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector( overWriteAlertDone:returnCode:contextInfo: ) contextInfo:NULL]; + [alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector(overWriteAlertDone:returnCode:contextInfo:) contextInfo:NULL]; // overWriteAlertDone: will be called when the alert is dismissed. It will call doRip. - } - else - { - /* if there are no pending jobs in the queue, then add this one to the queue and rip - otherwise, just rip the queue */ - if(fPendingCount == 0) - { - [self doAddToQueue]; - } - - // go right to processing the new queue encode - currentQueueEncodeIndex = [self getNextPendingQueueIndex]; - HBJob *queueJob = QueueFileArray[currentQueueEncodeIndex]; - [self performNewQueueScan:queueJob.fileURL.path scanTitleNum:queueJob.titleIdx]; - } -} - -/* overWriteAlertDone: called from the alert posted by Rip: that asks the user if they - want to overwrite an exiting movie file. -*/ -- (void) overWriteAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo -{ - if( returnCode == NSAlertAlternateReturn ) - { - // if there are no jobs in the queue, then add this one to the queue and rip - // otherwise, just rip the queue - if (fPendingCount == 0) - { - [self doAddToQueue]; - } - - [[NSUserDefaults standardUserDefaults] setURL:self.job.destURL.URLByDeletingLastPathComponent - forKey:@"HBLastDestinationDirectory"]; - currentQueueEncodeIndex = [self getNextPendingQueueIndex]; - HBJob *queueJob = QueueFileArray[currentQueueEncodeIndex]; - [self performNewQueueScan:queueJob.fileURL.path scanTitleNum:queueJob.titleIdx]; - } -} - -- (void) remindUserOfSleepOrShutdown -{ - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Put Computer To Sleep"]) - { - /*Warn that computer will sleep after encoding*/ - NSBeep(); - [NSApp requestUserAttention:NSCriticalRequest]; - - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:@"The computer will sleep after encoding is done."]; - [alert setInformativeText:@"You have selected to sleep the computer after encoding. To turn off sleeping, go to the HandBrake preferences."]; - [alert addButtonWithTitle:@"OK"]; - [alert addButtonWithTitle:@"Preferences…"]; - - NSInteger reminduser = [alert runModal]; [alert release]; - if (reminduser == NSAlertSecondButtonReturn) - { - [self showPreferencesWindow:nil]; - } - } - else if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Shut Down Computer"]) - { - /*Warn that computer will shut down after encoding*/ - NSBeep(); - [NSApp requestUserAttention:NSCriticalRequest]; - - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:@"The computer will shut down after encoding is done."]; - [alert setInformativeText:@"You have selected to shut down the computer after encoding. To turn off shut down, go to the HandBrake preferences."]; - [alert addButtonWithTitle:@"OK"]; - [alert addButtonWithTitle:@"Preferences…"]; - - NSInteger reminduser = [alert runModal]; - if (reminduser == NSAlertSecondButtonReturn) - { - [self showPreferencesWindow:nil]; - } - [alert release]; - } - -} - -//------------------------------------------------------------------------------------ -// Displays an alert asking user if the want to cancel encoding of current job. -// Cancel: returns immediately after posting the alert. Later, when the user -// acknowledges the alert, doCancelCurrentJob is called. -//------------------------------------------------------------------------------------ -- (IBAction)Cancel: (id)sender -{ - [self.queueCore pause]; - - // Which window to attach the sheet to? - NSWindow * docWindow; - if ([sender respondsToSelector: @selector(window)]) - { - docWindow = [sender window]; } else { - docWindow = fWindow; + [self doRip]; } - - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:NSLocalizedString(@"You are currently encoding. What would you like to do ?", nil)]; - [alert setInformativeText:NSLocalizedString(@"Your encode will be cancelled if you don't continue encoding.", nil)]; - [alert addButtonWithTitle:NSLocalizedString(@"Continue Encoding", nil)]; - [alert addButtonWithTitle:NSLocalizedString(@"Cancel Current and Stop", nil)]; - [alert addButtonWithTitle:NSLocalizedString(@"Cancel Current and Continue", nil)]; - [alert setAlertStyle:NSCriticalAlertStyle]; - [alert beginSheetModalForWindow:docWindow - modalDelegate:self - didEndSelector:@selector(didDimissCancel:returnCode:contextInfo:) - contextInfo:nil]; - [alert release]; } -- (void) didDimissCancel: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo +/** + * overWriteAlertDone: called from the alert posted by Rip: that asks the user if they + * want to overwrite an exiting movie file. + */ +- (void)overWriteAlertDone:(NSAlert *)alert + returnCode:(NSInteger)returnCode + contextInfo:(void *)contextInfo { - /* No need to prevent system sleep here as we didn't allow it in Cancel: */ - [self.queueCore resume]; - if (returnCode == NSAlertSecondButtonReturn) { - [self doCancelCurrentJobAndStop]; - } - else if (returnCode == NSAlertThirdButtonReturn) - { - [self doCancelCurrentJob]; // <- this also stops libhb + [self doRip]; } } -//------------------------------------------------------------------------------------ -// Cancels and deletes the current job and stops libhb from processing the remaining -// encodes. -//------------------------------------------------------------------------------------ -- (void) doCancelCurrentJob +/** + * Displays an alert asking user if the want to cancel encoding of current job. + * Cancel: returns immediately after posting the alert. Later, when the user + * acknowledges the alert, doCancelCurrentJob is called. + */ +- (IBAction)cancel:(id)sender { - // Stop the current job. - [self.queueCore stop]; - - fEncodeState = 2; // don't alert at end of processing since this was a cancel - - // now that we've stopped the currently encoding job, lets mark it as cancelled - [QueueFileArray[currentQueueEncodeIndex] setState:HBJobStateCanceled]; - // and as always, save it in Queue.plist - /* We save all of the Queue data here */ - [self saveQueueFileItem]; - - // and see if there are more items left in our queue - NSInteger queueItems = [QueueFileArray count]; - /* If we still have more items in our queue, lets go to the next one */ - /* Check to see if there are any more pending items in the queue */ - NSInteger newQueueItemIndex = [self getNextPendingQueueIndex]; - /* If we still have more pending items in our queue, lets go to the next one */ - if (newQueueItemIndex >= 0 && newQueueItemIndex < queueItems) - { - /*Set our currentQueueEncodeIndex now to the newly found Pending encode as we own it */ - currentQueueEncodeIndex = newQueueItemIndex; - /* now we mark the queue item as worling so another instance can not come along and try to scan it while we are scanning */ - [QueueFileArray[currentQueueEncodeIndex] setState:HBJobStateWorking]; - [HBUtilities writeToActivityLog: "incrementQueueItemDone new pending items found: %d", currentQueueEncodeIndex]; - [self saveQueueFileItem]; - - HBJob *queueJob = QueueFileArray[currentQueueEncodeIndex]; - // now we can go ahead and scan the new pending queue item - [self performNewQueueScan:queueJob.fileURL.path scanTitleNum:queueJob.titleIdx]; - } - else - { - [HBUtilities writeToActivityLog: "incrementQueueItemDone there are no more pending encodes"]; - } + [fQueueController cancel:self]; } -- (void) doCancelCurrentJobAndStop +- (IBAction)pause:(id)sender { - [self.queueCore stop]; - - fEncodeState = 2; // don't alert at end of processing since this was a cancel - - // now that we've stopped the currently encoding job, lets mark it as cancelled - [QueueFileArray[currentQueueEncodeIndex] setState:HBJobStateCanceled]; - // and as always, save it in Queue.plist - /* We save all of the Queue data here */ - [self saveQueueFileItem]; - // so now lets move to - currentQueueEncodeIndex++ ; - [HBUtilities writeToActivityLog: "cancelling current job and stopping the queue"]; -} -- (IBAction) Pause: (id) sender -{ - if (self.queueCore.state == HBStatePaused) + if (fQueueController.core.state == HBStatePaused) { - [self.queueCore resume]; + [fQueueController.core resume]; } else { - [self.queueCore pause]; + [fQueueController.core pause]; } } #pragma mark - #pragma mark Batch Queue Titles Methods -- (IBAction) addAllTitlesToQueue: (id) sender + +- (IBAction)addAllTitlesToQueue:(id)sender { - NSAlert *alert = [NSAlert alertWithMessageText:@"You are about to add ALL titles to the queue!" - defaultButton:@"Cancel" - alternateButton:@"Yes, I want to add all titles to the queue" - otherButton:nil - informativeTextWithFormat:@"Current preset will be applied to all %ld titles. Are you sure you want to do this?", (long)[fSrcTitlePopUp numberOfItems]]; + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"You are about to add ALL titles to the queue!", @"")]; + [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Current preset will be applied to all %ld titles. Are you sure you want to do this?", @""), self.core.titles.count]]; + [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"")]; + [alert addButtonWithTitle:NSLocalizedString(@"Yes, I want to add all titles to the queue", @"")]; [alert setAlertStyle:NSCriticalAlertStyle]; - [alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector( addAllTitlesToQueueAlertDone:returnCode:contextInfo: ) contextInfo:NULL]; + [alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector(addAllTitlesToQueueAlertDone:returnCode:contextInfo:) contextInfo:NULL]; + [alert release]; } -- (void) addAllTitlesToQueueAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo +- (void)addAllTitlesToQueueAlertDone:(NSAlert *)alert + returnCode:(NSInteger)returnCode + contextInfo:(void *)contextInfo { - if( returnCode == NSAlertAlternateReturn ) + if (returnCode == NSAlertSecondButtonReturn) + { [self doAddAllTitlesToQueue]; + } } -- (void) doAddAllTitlesToQueue +- (void)doAddAllTitlesToQueue { - // first get the currently selected index so we can choose it again after cycling through the available titles. - NSInteger currentlySelectedTitle = [fSrcTitlePopUp indexOfSelectedItem]; - - /* For each title in the fSrcTitlePopUp, select it */ - for (int i = 0; i < [fSrcTitlePopUp numberOfItems]; i++) + NSMutableArray *jobs = [[NSMutableArray alloc] init]; + + for (HBTitle *title in self.core.titles) { - [fSrcTitlePopUp selectItemAtIndex:i]; - // Now call titlePopUpChanged to load it up - [self titlePopUpChanged:nil]; - // now add the title to the queue - [self addToQueue:nil]; + HBJob *job = [[HBJob alloc] initWithTitle:title andPreset:self.selectedPreset]; + job.destURL = [self destURLForJob:job]; + [jobs addObject:job]; + [job release]; } - // Now that we are done, reselect the previously selected title. - [fSrcTitlePopUp selectItemAtIndex: currentlySelectedTitle]; - // Now call titlePopUpChanged to load it up - [self titlePopUpChanged:nil]; + + [fQueueController addJobsFromArray:jobs]; + [jobs release]; } #pragma mark - @@ -2376,11 +1401,6 @@ static void queueFSEventStreamCallback( - (void)updateFileName { - if (!self.job) - { - return; - } - HBTitle *title = self.job.title; // Generate a new file name @@ -2396,6 +1416,24 @@ static void queueFSEventStreamCallback( [NSString stringWithFormat:@"%@.%@", fileName, self.job.destURL.pathExtension]]; } +- (NSURL *)destURLForJob:(HBJob *)job +{ + // Check to see if the last destination has been set,use if so, if not, use Desktop + NSURL *destURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"HBLastDestinationDirectory"]; + if (!destURL || ![[NSFileManager defaultManager] fileExistsAtPath:destURL.path]) + { + destURL = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) firstObject] + isDirectory:YES]; + } + + destURL = [destURL URLByAppendingPathComponent:job.title.name]; + // use the correct extension based on the container + const char *ext = hb_container_get_default_extension(self.job.container); + destURL = [destURL URLByAppendingPathExtension:@(ext)]; + + return destURL; +} + - (IBAction) titlePopUpChanged: (id) sender { // If there is already a title load, save the current settings to a preset @@ -2415,21 +1453,7 @@ static void queueFSEventStreamCallback( else { self.job = [[[HBJob alloc] initWithTitle:title andPreset:self.selectedPreset] autorelease]; - - // Check to see if the last destination has been set,use if so, if not, use Desktop - NSURL *destURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"HBLastDestinationDirectory"]; - if (!destURL || ![[NSFileManager defaultManager] fileExistsAtPath:destURL.path]) - { - destURL = [NSURL fileURLWithPath:[NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) firstObject] - isDirectory:YES]; - } - - destURL = [destURL URLByAppendingPathComponent:title.name]; - // use the correct extension based on the container - const char *ext = hb_container_get_default_extension(self.job.container); - destURL = [destURL URLByAppendingPathExtension:@(ext)]; - - self.job.destURL = destURL; + self.job.destURL = [self destURLForJob:self.job]; // set m4v extension if necessary - do not override user-specified .mp4 extension if (self.job.container & HB_MUX_MASK_MP4) @@ -2595,10 +1619,9 @@ static void queueFSEventStreamCallback( */ - (IBAction) showQueueWindow:(id)sender { - [fQueueController showQueueWindow:sender]; + [fQueueController showWindow:sender]; } - - (IBAction) toggleDrawer:(id)sender { if ([fPresetDrawer state] == NSDrawerClosedState) @@ -2616,7 +1639,6 @@ static void queueFSEventStreamCallback( /** * Shows Picture Settings Window. */ - - (IBAction) showPicturePanel: (id) sender { [fPictureController showPictureWindow]; diff --git a/macosx/English.lproj/MainMenu.xib b/macosx/English.lproj/MainMenu.xib index 13e98f79d..2aa8f63da 100644 --- a/macosx/English.lproj/MainMenu.xib +++ b/macosx/English.lproj/MainMenu.xib @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14C81f" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6724" systemVersion="14C99d" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> <dependencies> <deployment version="1060" identifier="macosx"/> <development version="5100" identifier="xcode"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6724"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> @@ -415,12 +415,12 @@ <font key="font" metaFont="smallSystem"/> </buttonCell> <connections> + <binding destination="240" name="value" keyPath="self.job.mp4iPodCompatible" id="GWA-QG-Bfu"/> <binding destination="240" name="hidden" keyPath="self.job.mp4iPodCompatibleEnabled" id="eGS-R2-zSV"> <dictionary key="options"> <string key="NSValueTransformerName">NSNegateBoolean</string> </dictionary> </binding> - <binding destination="240" name="value" keyPath="self.job.mp4iPodCompatible" id="GWA-QG-Bfu"/> </connections> </button> <textField verticalHuggingPriority="750" id="5505"> @@ -491,12 +491,12 @@ </toolbarItem> <toolbarItem implicitItemIdentifier="4CAB4280-DF01-4BDC-B23B-98BD6E6190F4" label="Start" paletteLabel="Start Encoding" toolTip="Start Encoding" tag="-1" image="encode" id="byg-kj-sEM"> <connections> - <action selector="Rip:" target="240" id="0B6-IB-70f"/> + <action selector="rip:" target="240" id="0Db-56-2aY"/> </connections> </toolbarItem> <toolbarItem implicitItemIdentifier="E4C5E251-DD8D-4288-AC80-AF8E654B7A32" label="Pause" paletteLabel="Pause Encoding" toolTip="Pause Encoding" tag="-1" image="pauseencode" id="wTQ-KF-5KW"> <connections> - <action selector="Pause:" target="240" id="mIG-m6-Stn"/> + <action selector="pause:" target="240" id="OjT-ck-YX1"/> </connections> </toolbarItem> <toolbarItem implicitItemIdentifier="69E83092-D0D1-48A5-BF46-99EFB3EC630A" label="Add To Queue" paletteLabel="Add To Queue" tag="-1" image="addqueue" id="DZZ-Fe-wjw"> @@ -647,12 +647,12 @@ </menuItem> <menuItem title="Start Encoding" keyEquivalent="s" id="2444"> <connections> - <action selector="Rip:" target="240" id="2448"/> + <action selector="rip:" target="240" id="kGM-Ym-khx"/> </connections> </menuItem> <menuItem title="Pause Encoding" keyEquivalent="p" id="2494"> <connections> - <action selector="Pause:" target="240" id="2496"/> + <action selector="pause:" target="240" id="RAM-7s-91U"/> </connections> </menuItem> </items> diff --git a/macosx/English.lproj/Queue.xib b/macosx/English.lproj/Queue.xib index 116eebee8..297e3c964 100644 --- a/macosx/English.lproj/Queue.xib +++ b/macosx/English.lproj/Queue.xib @@ -1,21 +1,21 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5056" systemVersion="13F14" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6724" systemVersion="14C99d" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> <dependencies> <deployment version="1060" identifier="macosx"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6724"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="HBQueueController"> <connections> - <outlet property="fOutlineView" destination="2597" id="2601"/> - <outlet property="fProgressTextField" destination="2646" id="2648"/> - <outlet property="fQueueCountField" destination="2511" id="2564"/> + <outlet property="countTextField" destination="2511" id="7vs-Ty-tNx"/> + <outlet property="outlineView" destination="2597" id="dPQ-wg-8cy"/> + <outlet property="progressTextField" destination="2646" id="E60-Gv-b2q"/> <outlet property="window" destination="2576" id="2645"/> </connections> </customObject> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> - <customObject id="-3" userLabel="Application"/> - <window title="Queue - HandBrake" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="QueueWindow" animationBehavior="default" id="2576" userLabel="Window"> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <window title="Queue" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="QueueWindow" animationBehavior="default" id="2576" userLabel="Window"> <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" unifiedTitleAndToolbar="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="893" y="128" width="574" height="423"/> @@ -33,7 +33,7 @@ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <outlineView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" indentationPerLevel="16" autoresizesOutlineColumn="YES" outlineTableColumn="2624" id="2597" customClass="HBQueueOutlineView"> - <rect key="frame" x="0.0" y="0.0" width="532" height="336"/> + <rect key="frame" x="0.0" y="0.0" width="532" height="19"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> @@ -137,6 +137,7 @@ <connections> <outlet property="delegate" destination="-2" id="2579"/> </connections> + <point key="canvasLocation" x="383" y="524.5"/> </window> <menu id="2649" userLabel="ContextMenu"> <items> diff --git a/macosx/HBAttributedStringAdditions.h b/macosx/HBAttributedStringAdditions.h new file mode 100644 index 000000000..bcc26e839 --- /dev/null +++ b/macosx/HBAttributedStringAdditions.h @@ -0,0 +1,16 @@ +// +// HBAttributedStringAdditions.h +// HandBrake +// +// Created by Damiano Galassi on 16/01/15. +// +// + +#import <Foundation/Foundation.h> + +@interface NSMutableAttributedString (HBAttributedStringAdditions) + +- (void)appendString:(NSString *)aString withAttributes:(NSDictionary *)aDictionary; + +@end + diff --git a/macosx/HBAttributedStringAdditions.m b/macosx/HBAttributedStringAdditions.m new file mode 100644 index 000000000..fc685482d --- /dev/null +++ b/macosx/HBAttributedStringAdditions.m @@ -0,0 +1,20 @@ +// +// HBAttributedStringAdditions.m +// HandBrake +// +// Created by Damiano Galassi on 16/01/15. +// +// + +#import "HBAttributedStringAdditions.h" + +@implementation NSMutableAttributedString (HBAttributedStringAdditions) + +- (void)appendString:(NSString *)aString withAttributes:(NSDictionary *)aDictionary +{ + NSAttributedString *s = [[[NSAttributedString alloc] initWithString:aString + attributes:aDictionary] autorelease]; + [self appendAttributedString:s]; +} + +@end
\ No newline at end of file diff --git a/macosx/HBCore.h b/macosx/HBCore.h index f78c26c55..66fd40d72 100644 --- a/macosx/HBCore.h +++ b/macosx/HBCore.h @@ -22,19 +22,12 @@ typedef NS_ENUM(NSUInteger, HBState) { HBStateSearching = HB_STATE_SEARCHING ///< HB is searching }; -// These constants specify various status notifications sent by HBCore -extern NSString *HBCoreScanningNotification; -extern NSString *HBCoreScanDoneNotification; -extern NSString *HBCoreSearchingNotification; -extern NSString *HBCoreWorkingNotification; -extern NSString *HBCorePausedNotification; -extern NSString *HBCoreWorkDoneNotification; -extern NSString *HBCoreMuxingNotification; +typedef void (^HBCoreProgressHandler)(HBState state, hb_state_t hb_state); +typedef void (^HBCoreCompletationHandler)(BOOL success); /** * HBCore is an Objective-C interface to the low-level HandBrake library. - * HBCore monitors state changes of libhb and provides notifications via - * NSNotificationCenter to any object who needs them. It can also be used + * HBCore monitors state changes of libhb. It can also be used * to implement properties that can be directly bound to elements of the gui. */ @interface HBCore : NSObject @@ -68,7 +61,7 @@ extern NSString *HBCoreMuxingNotification; @property (nonatomic, readonly) HBState state; /** - * Pointer to a hb_state_s struct containing state information of libhb. + * Pointer to a hb_state_s struct containing the detailed state information of libhb. */ @property (nonatomic, readonly) hb_state_t *hb_state; @@ -78,12 +71,12 @@ extern NSString *HBCoreMuxingNotification; @property (nonatomic, readonly) hb_handle_t *hb_handle; /** - * The name of the core, used in for debugging purpose. + * The name of the core, used for debugging purpose. */ @property (nonatomic, copy) NSString *name; /** - * Determines whether the scan operation can scan a particural URL or whether an additional decription lib is needed.. + * Determines whether the scan operation can scan a particural URL or whether an additional decryption lib is needed. * * @param url the URL of the input file. * @param error an error containing additional info. @@ -100,7 +93,12 @@ extern NSString *HBCoreMuxingNotification; * @param previewsNum the number of previews image to generate. * @param minTitleDuration the minimum duration of the wanted titles in seconds. */ -- (void)scan:(NSURL *)url titleNum:(NSUInteger)titleNum previewsNum:(NSUInteger)previewsNum minTitleDuration:(NSUInteger)minTitleDuration; +- (void)scanURL:(NSURL *)url + titleIndex:(NSUInteger)titleNum + previews:(NSUInteger)previewsNum + minDuration:(NSUInteger)minTitleDuration +progressHandler:(HBCoreProgressHandler)progressHandler +completationHandler:(HBCoreCompletationHandler)completationHandler; /** * Cancels the scan execution. @@ -117,19 +115,20 @@ extern NSString *HBCoreMuxingNotification; * * @param job the job to encode. */ -- (void)encodeJob:(HBJob *)job; +- (void)encodeJob:(HBJob *)job progressHandler:(HBCoreProgressHandler)progressHandler completationHandler:(HBCoreCompletationHandler)completationHandler; /** - * Starts the libhb encoding session. - * - * This method must be called after all jobs have been added. + * Stops encoding session and releases resources. */ -- (void)start; +- (void)cancelEncode; /** - * Stops encoding session and releases resources. + * Starts the libhb encoding session. + * + * This method must be called after all jobs have been added. + * deprecated, will be removed soon. */ -- (void)stop; +- (void)startProgressHandler:(HBCoreProgressHandler)progressHandler completationHandler:(HBCoreCompletationHandler)completationHandler; /** * Pauses the encoding session. diff --git a/macosx/HBCore.m b/macosx/HBCore.m index 111f5dc1e..05871bbbc 100644 --- a/macosx/HBCore.m +++ b/macosx/HBCore.m @@ -12,29 +12,6 @@ #include <dlfcn.h> -// These constants specify various status notifications sent by HBCore - -/// Notification sent to update status while scanning. Matches HB_STATE_SCANNING constant in libhb. -NSString *HBCoreScanningNotification = @"HBCoreScanningNotification"; - -/// Notification sent after scanning is complete. Matches HB_STATE_SCANDONE constant in libhb. -NSString *HBCoreScanDoneNotification = @"HBCoreScanDoneNotification"; - -/// Notification sent to update status while searching. Matches HB_STATE_SEARCHING constant in libhb. -NSString *HBCoreSearchingNotification = @"HBCoreSearchingNotification"; - -/// Notification sent to update status while encoding. Matches HB_STATE_WORKING constant in libhb. -NSString *HBCoreWorkingNotification = @"HBCoreWorkingNotification"; - -/// Notification sent when encoding is paused. Matches HB_STATE_PAUSED constant in libhb. -NSString *HBCorePausedNotification = @"HBCorePausedNotification"; - -/// Notification sent after encoding is complete. Matches HB_STATE_WORKDONE constant in libhb. -NSString *HBCoreWorkDoneNotification = @"HBCoreWorkDoneNotification"; - -/// Notification sent to update status while muxing. Matches HB_STATE_MUXING constant in libhb. -NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; - /** * Private methods of HBCore. */ @@ -49,6 +26,15 @@ NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; /// Current scanned titles. @property (nonatomic, readwrite, retain) NSArray *titles; +/// Progress handler. +@property (nonatomic, readwrite, copy) HBCoreProgressHandler progressHandler; + +/// Completation handler. +@property (nonatomic, readwrite, copy) HBCoreCompletationHandler completationHandler; + +/// User cancelled. +@property (nonatomic, readwrite, getter=isCancelled) BOOL cancelled; + - (void)stateUpdateTimer:(NSTimer *)timer; @end @@ -161,8 +147,12 @@ NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; return YES; } -- (void)scan:(NSURL *)url titleNum:(NSUInteger)titleNum previewsNum:(NSUInteger)previewsNum minTitleDuration:(NSUInteger)minTitleDuration; +- (void)scanURL:(NSURL *)url titleIndex:(NSUInteger)titleNum previews:(NSUInteger)previewsNum minDuration:(NSUInteger)minTitleDuration progressHandler:(HBCoreProgressHandler)progressHandler completationHandler:(HBCoreCompletationHandler)completationHandler { + // Copy the progress/completation blocks + self.progressHandler = progressHandler; + self.completationHandler = completationHandler; + // Start the timer to handle libhb state changes [self startUpdateTimerWithInterval:0.2]; @@ -207,7 +197,7 @@ NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; /** * Creates an array of lightweight HBTitles instances. */ -- (void)scanDone +- (BOOL)scanDone { hb_title_set_t *title_set = hb_get_title_set(_hb_handle); NSMutableArray *titles = [NSMutableArray array]; @@ -221,6 +211,8 @@ NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; self.titles = [[titles copy] autorelease]; [HBUtilities writeToActivityLog:"%s scan done", self.name.UTF8String]; + + return self.titles.count; } - (void)cancelScan @@ -232,23 +224,25 @@ NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; #pragma mark - Encodes -- (void)encodeJob:(HBJob *)job +- (void)encodeJob:(HBJob *)job progressHandler:(HBCoreProgressHandler)progressHandler completationHandler:(HBCoreCompletationHandler)completationHandler; { + // Add the job to libhb hb_job_t *hb_job = job.hb_job; - - [HBUtilities writeToActivityLog: "processNewQueueEncode number of passes expected is: %d", (job.video.twoPass + 1)]; hb_job_set_file(hb_job, job.destURL.path.fileSystemRepresentation); - hb_add(self.hb_handle, hb_job); // Free the job hb_job_close(&hb_job); - [self start]; + [self startProgressHandler:progressHandler completationHandler:completationHandler]; } -- (void)start +- (void)startProgressHandler:(HBCoreProgressHandler)progressHandler completationHandler:(HBCoreCompletationHandler)completationHandler; { + // Copy the progress/completation blocks + self.progressHandler = progressHandler; + self.completationHandler = completationHandler; + // Start the timer to handle libhb state changes [self startUpdateTimerWithInterval:0.5]; @@ -263,20 +257,34 @@ NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; [HBUtilities writeToActivityLog:"%s work started", self.name.UTF8String]; } -- (void)workDone +- (BOOL)workDone { // HB_STATE_WORKDONE happpens as a result of libhb finishing all its jobs // or someone calling hb_stop. In the latter case, hb_stop does not clear // out the remaining passes/jobs in the queue. We'll do that here. hb_job_t *job; while ((job = hb_job(_hb_handle, 0))) + { hb_rem(_hb_handle, job); + } [HBUtilities writeToActivityLog:"%s work done", self.name.UTF8String]; + + if (self.isCancelled) + { + self.cancelled = NO; + return NO; + } + else + { + return YES; + } } -- (void)stop +- (void)cancelEncode { + self.cancelled = YES; + hb_stop(_hb_handle); hb_system_sleep_allow(_hb_handle); @@ -334,19 +342,15 @@ NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; switch (stateValue) { case HB_STATE_WORKING: - return @selector(handleHBStateWorking); case HB_STATE_SCANNING: - return @selector(handleHBStateScanning); case HB_STATE_MUXING: - return @selector(handleHBStateMuxing); case HB_STATE_PAUSED: - return @selector(handleHBStatePaused); case HB_STATE_SEARCHING: - return @selector(handleHBStateSearching); + return @selector(handleProgress); case HB_STATE_SCANDONE: - return @selector(handleHBStateScanDone); + return @selector(handleScanCompletation); case HB_STATE_WORKDONE: - return @selector(handleHBStateWorkDone); + return @selector(handleWorkCompletation); default: NSAssert1(NO, @"[HBCore selectorForState:] unknown state %lu", stateValue); return NULL; @@ -397,68 +401,51 @@ NSString *HBCoreMuxingNotification = @"HBCoreMuxingNotification"; #pragma mark - Notifications /** - * Processes HBStateScanning state information. Current implementation just - * sends HBCoreScanningNotification. + * Processes HBStateSearching state information. Current implementation just + * sends HBCoreSearchingNotification. */ -- (void)handleHBStateScanning +- (void)handleProgress { - [[NSNotificationCenter defaultCenter] postNotificationName:HBCoreScanningNotification object:self]; + if (self.progressHandler) + { + self.progressHandler(self.state, *(self.hb_state)); + } } /** * Processes HBStateScanDone state information. Current implementation just * sends HBCoreScanDoneNotification. */ -- (void)handleHBStateScanDone -{ - [self scanDone]; - [[NSNotificationCenter defaultCenter] postNotificationName:HBCoreScanDoneNotification object:self]; -} - -/** - * Processes HBStateWorking state information. Current implementation just - * sends HBCoreWorkingNotification. - */ -- (void)handleHBStateWorking +- (void)handleScanCompletation { - [[NSNotificationCenter defaultCenter] postNotificationName:HBCoreWorkingNotification object:self]; -} + BOOL success = [self scanDone]; -/** - * Processes HBStatePaused state information. Current implementation just - * sends HBCorePausedNotification. - */ -- (void)handleHBStatePaused -{ - [[NSNotificationCenter defaultCenter] postNotificationName:HBCorePausedNotification object:self]; + if (self.completationHandler) + { + HBCoreCompletationHandler completationHandler = [self.completationHandler retain]; + self.progressHandler = nil; + self.completationHandler = nil; + completationHandler(success); + [completationHandler release]; + } } /** * Processes HBStateWorkDone state information. Current implementation just * sends HBCoreWorkDoneNotification. */ -- (void)handleHBStateWorkDone +- (void)handleWorkCompletation { - [self workDone]; - [[NSNotificationCenter defaultCenter] postNotificationName:HBCoreWorkDoneNotification object:self]; -} + BOOL success = [self workDone]; -/** - * Processes HBStateMuxing state information. Current implementation just - * sends HBCoreMuxingNotification. - */ -- (void)handleHBStateMuxing -{ - [[NSNotificationCenter defaultCenter] postNotificationName:HBCoreMuxingNotification object:self]; -} - -/** - * Processes HBStateSearching state information. Current implementation just - * sends HBCoreSearchingNotification. - */ -- (void)handleHBStateSearching -{ - [[NSNotificationCenter defaultCenter] postNotificationName:HBCoreSearchingNotification object:self]; + if (self.completationHandler) + { + HBCoreCompletationHandler completationHandler = [self.completationHandler retain]; + self.progressHandler = nil; + self.completationHandler = nil; + completationHandler(success); + [completationHandler release]; + } } @end diff --git a/macosx/HBDistributedArray.h b/macosx/HBDistributedArray.h new file mode 100644 index 000000000..4163fca17 --- /dev/null +++ b/macosx/HBDistributedArray.h @@ -0,0 +1,36 @@ +/* HBDistributedArray.h $ + + This file is part of the HandBrake source code. + Homepage: <http://handbrake.fr/>. + It may be used under the terms of the GNU General Public License. */ + +#import <Cocoa/Cocoa.h> + +extern NSString *HBDistributedArrayChanged; + +/** + * HBDistributedArray + * a mutable array that share its content between processes. + * post a HBDistributedArrayChanged when the content is changed + * by another process. + * + * Use beginTransaction and commit to wrap atomic changes to the array. + * + * It is safe to keep a reference to an array object. + */ +@interface HBDistributedArray : NSMutableArray + +- (instancetype)initWithURL:(NSURL *)fileURL; + +/** + * Begin a transaction on the array + */ +- (void)beginTransaction; + +/** + * Commit the changes and notify + * the observers about the changes. + */ +- (void)commit; + +@end diff --git a/macosx/HBDistributedArray.m b/macosx/HBDistributedArray.m new file mode 100644 index 000000000..c8b07ddcf --- /dev/null +++ b/macosx/HBDistributedArray.m @@ -0,0 +1,328 @@ +/* HBDistributedArray.m $ + + This file is part of the HandBrake source code. + Homepage: <http://handbrake.fr/>. + It may be used under the terms of the GNU General Public License. */ + +#import "HBDistributedArray.h" + +#include <semaphore.h> + +/** + * HBProxyArrayObject wraps an object inside a proxy + * to make it possible to keep a reference to an array + * object even if the underlying has been swapped + */ + +@interface HBProxyArrayObject : NSProxy + +- (instancetype)initWithObject:(id)object; + +@property (nonatomic, retain) id representedObject; +@property (nonatomic, readonly) NSString *uuid; + +@end + +@implementation HBProxyArrayObject + +- (instancetype)initWithObject:(id)object +{ + _representedObject = [object retain]; + + return self; +} + +- (void)dealloc +{ + [_representedObject release]; + [super dealloc]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector +{ + return [self.representedObject methodSignatureForSelector:selector]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + [invocation invokeWithTarget:self.representedObject]; +} + +- (NSString *)uuid +{ + return [self.representedObject uuid]; +} + +@end + +NSString *HBDistributedArrayChanged = @"HBDistributedArrayChanged"; +NSString *HBDistributedArraWrittenToDisk = @"HBDistributedArraWrittenToDisk"; + +@interface HBDistributedArray () + +@property (nonatomic, readonly) NSMutableArray *array; +@property (nonatomic, readonly) NSURL *fileURL; +@property (nonatomic, readwrite) NSTimeInterval modifiedTime; + +@property (nonatomic, readonly) sem_t *mutex; + +@end + +@implementation HBDistributedArray + +- (instancetype)initWithURL:(NSURL *)fileURL +{ + self = [super init]; + if (self) + { + _fileURL = [fileURL copy]; + _array = [[NSMutableArray alloc] init]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:_fileURL.path]) + { + if (![fileManager fileExistsAtPath:_fileURL.URLByDeletingLastPathComponent.path]) + { + [fileManager createDirectoryAtPath:_fileURL.URLByDeletingLastPathComponent.path withIntermediateDirectories:YES attributes:nil error:NULL]; + } + [fileManager createFileAtPath:_fileURL.path contents:nil attributes:nil]; + } + + NSArray *runningInstances = [NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]]; + const char *name = [NSString stringWithFormat:@"/%@.hblock", _fileURL.lastPathComponent].UTF8String; + + // Unlink the semaphore if we are the only + // instance running, this fixes the case where + // HB crashed while the sem is locked. + if (runningInstances.count == 1) + { + sem_unlink(name); + } + + // Use a named semaphore as a mutex for now + // it can cause a deadlock if an instance + // crashed while it has the lock on the semaphore. + _mutex = sem_open(name, O_CREAT, 0777, 1); + if (_mutex == SEM_FAILED) { + NSLog(@"%s: %d\n", "Error in creating semaphore: ", errno); + } + + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:HBDistributedArraWrittenToDisk object:nil]; + + // Load the array from disk + [self lock]; + [self reload]; + [self unlock]; + } + + return self; +} + +- (void)dealloc +{ + [self lock]; + [self synchronize]; + [self unlock]; + + [_fileURL release]; + _fileURL = nil; + [_array release]; + _array = nil; + + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; + + sem_close(_mutex); + + [super dealloc]; +} + +- (void)lock +{ + sem_wait(self.mutex); +} + +- (void)unlock +{ + sem_post(self.mutex); +} + +- (void)beginTransaction +{ + [self lock]; + // We got the lock, need to check if + // someone else modified the file + // while we were locked, because we + // could have not received the notification yet + NSDate *date = nil; + [self.fileURL getResourceValue:&date forKey:NSURLAttributeModificationDateKey error:nil]; + if (date.timeIntervalSinceReferenceDate > self.modifiedTime) + { + // File was modified while we waited on the lock + // reload it + [self reload]; + } +} + +- (void)commit +{ + [self synchronize]; + [self unlock]; +} + +- (void)postNotification +{ + [[NSNotificationCenter defaultCenter] postNotificationName:HBDistributedArrayChanged object:self]; +} + +/** + * Handle the distributed notification + */ +- (void)handleNotification:(NSNotification *)notification +{ + if (!([notification.object integerValue] == getpid())) + { + if ([notification.userInfo[@"path"] isEqualToString:self.fileURL.path]) + { + [self lock]; + [self reload]; + [self unlock]; + } + } +} + +/** + * Reload the array from disk + */ +- (void)reload +{ + NSMutableArray *temp = nil;; + @try + { + temp = [NSKeyedUnarchiver unarchiveObjectWithFile:self.fileURL.path]; + } + @catch (NSException *exception) + { + temp = nil; + } + + // Swap the proxy objects representation with the new + // one read from disk + NSMutableArray *proxyArray = [NSMutableArray array]; + for (id anObject in temp) + { + NSString *uuid = [anObject uuid]; + + HBProxyArrayObject *proxy = nil; + for (HBProxyArrayObject *temp in self.array) + { + if ([[temp uuid] isEqualToString:uuid]) + { + temp.representedObject = anObject; + proxy = temp; + break; + } + } + + if (proxy) + { + [proxyArray addObject:proxy]; + } + else + { + [proxyArray addObject:[self wrapObjectIfNeeded:anObject]]; + } + } + + [self setArray:proxyArray]; + [self postNotification]; + + // Update the time, so we can avoid reloaded the file from disk later. + self.modifiedTime = [NSDate timeIntervalSinceReferenceDate]; +} + +/** + * Writes the changes to disk + */ +- (void)synchronize +{ + NSMutableArray *temp = [NSMutableArray array]; + + // Unwrap the array objects and save them to disk + for (HBProxyArrayObject *proxy in self) + { + [temp addObject:proxy.representedObject]; + } + + if (![NSKeyedArchiver archiveRootObject:temp toFile:self.fileURL.path]) + { + NSLog(@"failed to write the queue to disk"); + } + + // Send a distributed notification. + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:HBDistributedArraWrittenToDisk + object:[NSString stringWithFormat:@"%d", getpid()] + userInfo:@{@"path": self.fileURL.path} + deliverImmediately:YES]; + + // Update the time, so we can avoid reloaded the file from disk later. + self.modifiedTime = [NSDate timeIntervalSinceReferenceDate]; +} + +/** + * Wraps an object inside a HBObjectProxy instance + * if it's not already wrapped. + * + * @param anObject the object to wrap + * + * @return a wrapped object + */ +- (id)wrapObjectIfNeeded:(id)anObject +{ + if ([[anObject class] isEqual:[HBProxyArrayObject class]]) + { + return anObject; + } + else + { + return [[[HBProxyArrayObject alloc] initWithObject:anObject] autorelease]; + } +} + +#pragma mark - Methods needed to subclass NSMutableArray + +- (void)insertObject:(id)anObject atIndex:(NSUInteger)index +{ + [self.array insertObject:[self wrapObjectIfNeeded:anObject] atIndex:index]; +} + +- (void)removeObjectAtIndex:(NSUInteger)index +{ + [self.array removeObjectAtIndex:index]; +} + +- (void)addObject:(id)anObject +{ + [self.array addObject:[self wrapObjectIfNeeded:anObject]]; +} + +- (void)removeLastObject +{ + [self.array removeLastObject]; +} + +- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject +{ + [self.array replaceObjectAtIndex:index withObject:[self wrapObjectIfNeeded:anObject]]; +} + +- (NSUInteger)count +{ + return [self.array count]; +} + +- (id)objectAtIndex:(NSUInteger)index +{ + return [self.array objectAtIndex:index]; +} + +@end diff --git a/macosx/HBJob+UIAdditions.h b/macosx/HBJob+UIAdditions.h index a8adc4f25..0ef1d05be 100644 --- a/macosx/HBJob+UIAdditions.h +++ b/macosx/HBJob+UIAdditions.h @@ -14,6 +14,8 @@ @property (nonatomic, readonly) NSArray *angles; +@property (nonatomic, readonly) NSAttributedString *attributedDescription; + @end @interface HBContainerTransformer : NSValueTransformer diff --git a/macosx/HBJob+UIAdditions.m b/macosx/HBJob+UIAdditions.m index 432478a42..1f941e5df 100644 --- a/macosx/HBJob+UIAdditions.m +++ b/macosx/HBJob+UIAdditions.m @@ -5,8 +5,24 @@ It may be used under the terms of the GNU General Public License. */ #import "HBJob+UIAdditions.h" + +#import "HBAttributedStringAdditions.h" +#import "HBJob.h" +#import "HBAudioTrack.h" +#import "HBAudioDefaults.h" + +#import "HBPicture+UIAdditions.h" +#import "HBFilters+UIAdditions.h" + #include "hb.h" +// Text Styles +static NSMutableParagraphStyle *ps; +static NSDictionary *detailAttr; +static NSDictionary *detailBoldAttr; +static NSDictionary *titleAttr; +static NSDictionary *shortHeightAttr; + @implementation HBJob (UIAdditions) - (BOOL)mp4OptionsEnabled @@ -56,6 +72,396 @@ return [[containers copy] autorelease]; } +- (void)initStyles +{ + if (!ps) + { + // Attributes + ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain]; + [ps setHeadIndent: 40.0]; + [ps setParagraphSpacing: 1.0]; + [ps setTabStops:@[]]; // clear all tabs + [ps addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]]; + + detailAttr = [@{NSFontAttributeName: [NSFont systemFontOfSize:10.0], + NSParagraphStyleAttributeName: ps} retain]; + + detailBoldAttr = [@{NSFontAttributeName: [NSFont boldSystemFontOfSize:10.0], + NSParagraphStyleAttributeName: ps} retain]; + + titleAttr = [@{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]], + NSParagraphStyleAttributeName: ps} retain]; + + shortHeightAttr = [@{NSFontAttributeName: [NSFont systemFontOfSize:2.0]} retain]; + } +} + +- (NSAttributedString *)attributedDescription +{ + // Below should be put into a separate method but I am way too f'ing lazy right now + NSMutableAttributedString *finalString = [[NSMutableAttributedString alloc] initWithString: @""]; + + [self initStyles]; + + @autoreleasepool + { + // First line, we should strip the destination path and just show the file name and add the title num and chapters (if any) + NSString *summaryInfo; + + NSString *titleString = [NSString stringWithFormat:@"Title %d", self.titleIdx]; + + NSString *startStopString = @""; + if (self.range.type == HBRangeTypeChapters) + { + // Start Stop is chapters + startStopString = (self.range.chapterStart == self.range.chapterStop) ? + [NSString stringWithFormat:@"Chapter %d", self.range.chapterStart] : + [NSString stringWithFormat:@"Chapters %d through %d", self.range.chapterStart, self.range.chapterStop]; + } + else if (self.range.type == HBRangeTypeSeconds) + { + // Start Stop is seconds + startStopString = [NSString stringWithFormat:@"Seconds %d through %d", self.range.secondsStart, self.range.secondsStop]; + } + else if (self.range.type == HBRangeTypeFrames) + { + // Start Stop is Frames + startStopString = [NSString stringWithFormat:@"Frames %d through %d", self.range.frameStart, self.range.frameStop]; + } + NSString *passesString = @""; + // check to see if our first subtitle track is Foreign Language Search, in which case there is an in depth scan + if (self.subtitles.tracks.count && [self.subtitles.tracks[0][@"keySubTrackIndex"] intValue] == -1) + { + passesString = [passesString stringByAppendingString:@"1 Foreign Language Search Pass - "]; + } + if (self.video.qualityType == 1 || self.video.twoPass == NO) + { + passesString = [passesString stringByAppendingString:@"1 Video Pass"]; + } + else + { + if (self.video.turboTwoPass == YES) + { + passesString = [passesString stringByAppendingString:@"2 Video Passes First Turbo"]; + } + else + { + passesString = [passesString stringByAppendingString:@"2 Video Passes"]; + } + } + + [finalString appendString:[NSString stringWithFormat:@"%@", self.fileURL.path.lastPathComponent] withAttributes:titleAttr]; + + // lets add the output file name to the title string here + NSString *outputFilenameString = self.destURL.lastPathComponent; + + summaryInfo = [NSString stringWithFormat: @" (%@, %@, %@) -> %@", titleString, startStopString, passesString, outputFilenameString]; + + [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr]; + + // Insert a short-in-height line to put some white space after the title + [finalString appendString:@"\n" withAttributes:shortHeightAttr]; + // End of Title Stuff + + // Second Line (Preset Name) + [finalString appendString: @"Preset: " withAttributes:detailBoldAttr]; + [finalString appendString:[NSString stringWithFormat:@"%@\n", self.presetName] withAttributes:detailAttr]; + + // Third Line (Format Summary) + NSString *audioCodecSummary = @""; // This seems to be set by the last track we have available... + // Lets also get our audio track detail since we are going through the logic for use later + + NSMutableArray *audioDetails = [NSMutableArray array]; + BOOL autoPassthruPresent = NO; + + for (HBAudioTrack *audioTrack in self.audio.tracks) + { + if (audioTrack.enabled) + { + audioCodecSummary = [NSString stringWithFormat: @"%@", audioTrack.codec[keyAudioCodecName]]; + NSNumber *drc = audioTrack.drc; + NSNumber *gain = audioTrack.gain; + NSString *detailString = [NSString stringWithFormat: @"%@ Encoder: %@ Mixdown: %@ SampleRate: %@(khz) Bitrate: %@(kbps), DRC: %@, Gain: %@", + audioTrack.track[keyAudioTrackName], + audioTrack.codec[keyAudioCodecName], + audioTrack.mixdown[keyAudioMixdownName], + audioTrack.sampleRate[keyAudioSampleRateName], + audioTrack.bitRate[keyAudioBitrateName], + (0.0 < [drc floatValue]) ? (NSObject *)drc : (NSObject *)@"Off", + (0.0 != [gain floatValue]) ? (NSObject *)gain : (NSObject *)@"Off" + ]; + [audioDetails addObject: detailString]; + // check if we have an Auto Passthru output track + if ([audioTrack.codec[keyAudioCodecName] isEqualToString: @"Auto Passthru"]) + { + autoPassthruPresent = YES; + } + } + } + + NSString *jobFormatInfo; + if (self.chaptersEnabled) + jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video %@ Audio, Chapter Markers\n", + @(hb_container_get_name(self.container)), @(hb_video_encoder_get_name(self.video.encoder)), audioCodecSummary]; + else + jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video %@ Audio\n", + @(hb_container_get_name(self.container)), @(hb_video_encoder_get_name(self.video.encoder)), audioCodecSummary]; + + [finalString appendString: @"Format: " withAttributes:detailBoldAttr]; + [finalString appendString: jobFormatInfo withAttributes:detailAttr]; + + // Optional String for muxer options + NSMutableString *containerOptions = [NSMutableString stringWithString:@""]; + if ((self.container & HB_MUX_MASK_MP4) && self.mp4HttpOptimize) + { + [containerOptions appendString:@" - Web optimized"]; + } + if ((self.container & HB_MUX_MASK_MP4) && self.mp4iPodCompatible) + { + [containerOptions appendString:@" - iPod 5G support"]; + } + if ([containerOptions hasPrefix:@" - "]) + { + [containerOptions deleteCharactersInRange:NSMakeRange(0, 3)]; + } + if (containerOptions.length) + { + [finalString appendString:@"Container Options: " withAttributes:detailBoldAttr]; + [finalString appendString:containerOptions withAttributes:detailAttr]; + [finalString appendString:@"\n" withAttributes:detailAttr]; + } + + // Fourth Line (Destination Path) + [finalString appendString: @"Destination: " withAttributes:detailBoldAttr]; + [finalString appendString: self.destURL.path withAttributes:detailAttr]; + [finalString appendString:@"\n" withAttributes:detailAttr]; + + + // Fifth Line Picture Details + NSString *pictureInfo = [NSString stringWithFormat:@"%@", self.picture.summary]; + if (self.picture.keepDisplayAspect) + { + pictureInfo = [pictureInfo stringByAppendingString:@" Keep Aspect Ratio"]; + } + [finalString appendString:@"Picture: " withAttributes:detailBoldAttr]; + [finalString appendString:pictureInfo withAttributes:detailAttr]; + [finalString appendString:@"\n" withAttributes:detailAttr]; + + /* Optional String for Picture Filters */ + if (self.filters.summary.length) + { + NSString *pictureFilters = [NSString stringWithFormat:@"%@", self.filters.summary]; + [finalString appendString:@"Filters: " withAttributes:detailBoldAttr]; + [finalString appendString:pictureFilters withAttributes:detailAttr]; + [finalString appendString:@"\n" withAttributes:detailAttr]; + } + + // Sixth Line Video Details + NSString * videoInfo = [NSString stringWithFormat:@"Encoder: %@", @(hb_video_encoder_get_name(self.video.encoder))]; + + // for framerate look to see if we are using vfr detelecine + if (self.video.frameRate == 0) + { + if (self.video.frameRateMode == 0) + { + // we are using same as source with vfr detelecine + videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (Variable Frame Rate)", videoInfo]; + } + else + { + // we are using a variable framerate without dropping frames + videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (Constant Frame Rate)", videoInfo]; + } + } + else + { + // we have a specified, constant framerate + if (self.video.frameRateMode == 0) + { + videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (Peak Frame Rate)", videoInfo, @(hb_video_framerate_get_name(self.video.frameRate))]; + } + else + { + videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (Constant Frame Rate)", videoInfo, @(hb_video_framerate_get_name(self.video.frameRate))]; + } + } + + + if (self.video.qualityType == 0) // ABR + { + videoInfo = [NSString stringWithFormat:@"%@ Bitrate: %d(kbps)", videoInfo, self.video.avgBitrate]; + } + else // CRF + { + videoInfo = [NSString stringWithFormat:@"%@ Constant Quality: %.2f", videoInfo ,self.video.quality]; + } + + [finalString appendString: @"Video: " withAttributes:detailBoldAttr]; + [finalString appendString: videoInfo withAttributes:detailAttr]; + [finalString appendString:@"\n" withAttributes:detailAttr]; + + + if (self.video.encoder == HB_VCODEC_X264 || self.video.encoder == HB_VCODEC_X265) + { + // we are using x264/x265 + NSString *encoderPresetInfo = @""; + if (self.video.advancedOptions) + { + // we are using the old advanced panel + if (self.video.videoOptionExtra.length) + { + encoderPresetInfo = [encoderPresetInfo stringByAppendingString:self.video.videoOptionExtra]; + } + else + { + encoderPresetInfo = [encoderPresetInfo stringByAppendingString:@"default settings"]; + } + } + else + { + // we are using the x264 system + encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@"Preset: %@", self.video.preset]]; + if (self.video.tune.length) + { + encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Tune: %@", self.video.tune]]; + } + if (self.video.videoOptionExtra.length) + { + encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Options: %@", self.video.videoOptionExtra]]; + } + if (self.video.profile.length) + { + encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Profile: %@", self.video.profile]]; + } + if (self.video.level.length) + { + encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Level: %@", self.video.level]]; + } + } + [finalString appendString: @"Encoder Options: " withAttributes:detailBoldAttr]; + [finalString appendString: encoderPresetInfo withAttributes:detailAttr]; + [finalString appendString:@"\n" withAttributes:detailAttr]; + } + else + { + // we are using libavcodec + NSString *lavcInfo = @""; + if (self.video.videoOptionExtra.length) + { + lavcInfo = [lavcInfo stringByAppendingString:self.video.videoOptionExtra]; + } + else + { + lavcInfo = [lavcInfo stringByAppendingString: @"default settings"]; + } + [finalString appendString: @"Encoder Options: " withAttributes:detailBoldAttr]; + [finalString appendString: lavcInfo withAttributes:detailAttr]; + [finalString appendString:@"\n" withAttributes:detailAttr]; + } + + + // Seventh Line Audio Details + int audioDetailCount = 0; + for (NSString *anAudioDetail in audioDetails) { + audioDetailCount++; + if (anAudioDetail.length) { + [finalString appendString: [NSString stringWithFormat: @"Audio Track %d ", audioDetailCount] withAttributes: detailBoldAttr]; + [finalString appendString: anAudioDetail withAttributes: detailAttr]; + [finalString appendString: @"\n" withAttributes: detailAttr]; + } + } + + // Eigth Line Auto Passthru Details + // only print Auto Passthru settings if we have an Auro Passthru output track + if (autoPassthruPresent == YES) + { + NSString *autoPassthruFallback = @"", *autoPassthruCodecs = @""; + HBAudioDefaults *audioDefaults = self.audio.defaults; + autoPassthruFallback = [autoPassthruFallback stringByAppendingString:@(hb_audio_encoder_get_name(audioDefaults.encoderFallback))]; + if (audioDefaults.allowAACPassthru) + { + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"AAC"]; + } + if (audioDefaults.allowAC3Passthru) + { + if (autoPassthruCodecs.length) + { + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; + } + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"AC3"]; + } + if (audioDefaults.allowDTSHDPassthru) + { + if (autoPassthruCodecs.length) + { + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; + } + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"DTS-HD"]; + } + if (audioDefaults.allowDTSPassthru) + { + if (autoPassthruCodecs.length) + { + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; + } + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"DTS"]; + } + if (audioDefaults.allowMP3Passthru) + { + if (autoPassthruCodecs.length) + { + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; + } + autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"MP3"]; + } + [finalString appendString: @"Auto Passthru Codecs: " withAttributes: detailBoldAttr]; + if (autoPassthruCodecs.length) + { + [finalString appendString: autoPassthruCodecs withAttributes: detailAttr]; + } + else + { + [finalString appendString: @"None" withAttributes: detailAttr]; + } + [finalString appendString: @"\n" withAttributes: detailAttr]; + [finalString appendString: @"Auto Passthru Fallback: " withAttributes: detailBoldAttr]; + [finalString appendString: autoPassthruFallback withAttributes: detailAttr]; + [finalString appendString: @"\n" withAttributes: detailAttr]; + } + + // Ninth Line Subtitle Details + int i = 0; + for (NSDictionary *track in self.subtitles.tracks) + { + // Ignore the none track. + if (i == self.subtitles.tracks.count - 1) + { + continue; + } + + /* remember that index 0 of Subtitles can contain "Foreign Audio Search*/ + [finalString appendString: @"Subtitle: " withAttributes:detailBoldAttr]; + [finalString appendString: track[@"keySubTrackName"] withAttributes:detailAttr]; + if ([track[@"keySubTrackForced"] intValue] == 1) + { + [finalString appendString: @" - Forced Only" withAttributes:detailAttr]; + } + if ([track[@"keySubTrackBurned"] intValue] == 1) + { + [finalString appendString: @" - Burned In" withAttributes:detailAttr]; + } + if ([track[@"keySubTrackDefault"] intValue] == 1) + { + [finalString appendString: @" - Default" withAttributes:detailAttr]; + } + [finalString appendString:@"\n" withAttributes:detailAttr]; + i++; + } + } + + return [finalString autorelease]; +} + @end @implementation HBContainerTransformer diff --git a/macosx/HBJob.h b/macosx/HBJob.h index d7e4f14c7..26a004f95 100644 --- a/macosx/HBJob.h +++ b/macosx/HBJob.h @@ -49,7 +49,7 @@ typedef NS_ENUM(NSUInteger, HBJobState){ @property (nonatomic, readwrite, assign) HBTitle *title; @property (nonatomic, readonly) int titleIdx; -@property (nonatomic, readwrite) int pidId; +@property (nonatomic, readonly) NSString *uuid; /** * The file URL of the source. diff --git a/macosx/HBJob.m b/macosx/HBJob.m index d23bb6039..c55e99fa8 100644 --- a/macosx/HBJob.m +++ b/macosx/HBJob.m @@ -44,6 +44,8 @@ NSString *HBContainerChangedNotification = @"HBContainerChangedNotificatio _chapterTitles = [title.chapters mutableCopy]; + _uuid = [[[NSUUID UUID] UUIDString] retain]; + [self applyPreset:preset]; } @@ -142,6 +144,8 @@ NSString *HBContainerChangedNotification = @"HBContainerChangedNotificatio [_chapterTitles release]; + [_uuid release]; + [super dealloc]; } @@ -156,7 +160,7 @@ NSString *HBContainerChangedNotification = @"HBContainerChangedNotificatio copy->_state = HBJobStateReady; copy->_presetName = [_presetName copy]; copy->_titleIdx = _titleIdx; - copy->_pidId = _pidId; + copy->_uuid = [[[NSUUID UUID] UUIDString] retain]; copy->_fileURL = [_fileURL copy]; copy->_destURL = [_destURL copy]; @@ -192,7 +196,7 @@ NSString *HBContainerChangedNotification = @"HBContainerChangedNotificatio encodeInt(_state); encodeObject(_presetName); encodeInt(_titleIdx); - encodeInt(_pidId); + encodeObject(_uuid); encodeObject(_fileURL); encodeObject(_destURL); @@ -221,7 +225,7 @@ NSString *HBContainerChangedNotification = @"HBContainerChangedNotificatio decodeInt(_state); decodeObject(_presetName); decodeInt(_titleIdx); - decodeInt(_pidId); + decodeObject(_uuid); decodeObject(_fileURL); decodeObject(_destURL); diff --git a/macosx/HBPreviewGenerator.m b/macosx/HBPreviewGenerator.m index 51c0fd125..88ad2c320 100644 --- a/macosx/HBPreviewGenerator.m +++ b/macosx/HBPreviewGenerator.m @@ -27,8 +27,6 @@ typedef enum EncodeState : NSUInteger { @property (nonatomic, readonly) HBJob *job; @property (nonatomic) HBCore *core; -@property (nonatomic, getter=isCancelled) BOOL cancelled; - @property (nonatomic, retain) NSURL *fileURL; @@ -243,64 +241,43 @@ typedef enum EncodeState : NSUInteger { int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue]; self.core = [[[HBCore alloc] initWithLoggingLevel:loggingLevel] autorelease]; self.core.name = @"PreviewCore"; - [self registerCoreNotifications]; // lets go ahead and send it off to libhb hb_add(self.core.hb_handle, job); hb_job_close(&job); // start the actual encode - [self.core start]; - - return YES; -} - -/** - * Cancels the encoding process - */ -- (void) cancel -{ - if (self.core.state == HBStateWorking || self.core.state == HBStatePaused) - { - [self.core stop]; - self.cancelled = YES; - } -} - -/** - * Registers for notifications from HBCore. - */ -- (void) registerCoreNotifications -{ - NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreWorkingNotification object:self.core queue:mainQueue usingBlock:^(NSNotification *note) { - hb_state_t s = *(self.core.hb_state); - - NSMutableString *info = [NSMutableString stringWithFormat: @"Encoding preview: %.2f %%", 100.0 * s.param.working.progress]; + [self.core startProgressHandler:^(HBState state, hb_state_t hb_state) { + switch (state) { + case HBStateWorking: + { + NSMutableString *info = [NSMutableString stringWithFormat: @"Encoding preview: %.2f %%", 100.0 * hb_state.param.working.progress]; + + if (hb_state.param.working.seconds > -1) + { + [info appendFormat:@" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", + hb_state.param.working.rate_cur, hb_state.param.working.rate_avg, hb_state.param.working.hours, + hb_state.param.working.minutes, hb_state.param.working.seconds]; + } + + double progress = 100.0 * hb_state.param.working.progress; + + [self.delegate updateProgress:progress info:info]; + break; + } + case HBStateMuxing: + [self.delegate updateProgress:100.0 info:@"Muxing Preview…"]; + break; - if (s.param.working.seconds > -1) - { - [info appendFormat:@" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", - s.param.working.rate_cur, s.param.working.rate_avg, s.param.working.hours, - s.param.working.minutes, s.param.working.seconds]; + default: + break; } - - double progress = 100.0 * s.param.working.progress; - - [self.delegate updateProgress:progress info:info]; - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreMuxingNotification object:self.core queue:mainQueue usingBlock:^(NSNotification *note) { - [self.delegate updateProgress:100.0 info:@"Muxing Preview…"]; - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:HBCoreWorkDoneNotification object:self.core queue:mainQueue usingBlock:^(NSNotification *note) { - [self.core stop]; + } + completationHandler:^(BOOL success) { self.core = nil; - /* Encode done, call the delegate and close libhb handle */ - if (!self.isCancelled) + // Encode done, call the delegate and close libhb handle + if (success) { [self.delegate didCreateMovieAtURL:self.fileURL]; } @@ -308,18 +285,27 @@ typedef enum EncodeState : NSUInteger { { [self.delegate didCancelMovieCreation]; } + }]; - self.cancelled = NO; + return YES; +} - [[NSNotificationCenter defaultCenter] removeObserver:self]; - }]; +/** + * Cancels the encoding process + */ +- (void) cancel +{ + if (self.core.state == HBStateWorking || self.core.state == HBStatePaused) + { + [self.core cancelEncode]; + } } #pragma mark - - (void) dealloc { - [self.core stop]; + [self.core cancelEncode]; [_core release]; _core = nil; diff --git a/macosx/HBQueueController.h b/macosx/HBQueueController.h index 41f76698b..87d114df5 100644 --- a/macosx/HBQueueController.h +++ b/macosx/HBQueueController.h @@ -5,19 +5,34 @@ It may be used under the terms of the GNU General Public License. */ #import <Cocoa/Cocoa.h> +#import <Growl/Growl.h> @class HBController; +@class HBOutputPanelController; @class HBCore; +@class HBJob; -@interface HBQueueController : NSWindowController <NSToolbarDelegate, NSWindowDelegate> +@interface HBQueueController : NSWindowController <NSToolbarDelegate, NSWindowDelegate, GrowlApplicationBridgeDelegate> -- (void)setPidNum: (int)myPidnum; -- (void)setCore: (HBCore *)core; -- (void)setHBController: (HBController *)controller; +/// The HBCore used for encoding. +@property (nonatomic, readonly) HBCore *core; -- (void)setQueueArray: (NSMutableArray *)QueueFileArray; -- (void)setQueueStatusString: (NSString *)statusString; +@property (nonatomic, assign) HBController *controller; +@property (nonatomic, assign) HBOutputPanelController *outputPanel; -- (IBAction)showQueueWindow: (id)sender; +@property (nonatomic, readonly) NSUInteger count; +@property (nonatomic, readonly) NSUInteger pendingItemsCount; +@property (nonatomic, readonly) NSUInteger workingItemsCount; + +- (void)addJob:(HBJob *)item; +- (void)addJobsFromArray:(NSArray *)items; + +- (BOOL)jobExistAtURL:(NSURL *)url; + +- (void)removeAllJobs; +- (void)setEncodingJobsAsPending; + +- (IBAction)rip:(id)sender; +- (IBAction)cancel:(id)sender; @end diff --git a/macosx/HBQueueController.mm b/macosx/HBQueueController.mm index 542b2ba33..9ca4137bf 100644 --- a/macosx/HBQueueController.mm +++ b/macosx/HBQueueController.mm @@ -1,3 +1,4 @@ + /* HBQueueController This file is part of the HandBrake source code. @@ -5,92 +6,53 @@ It may be used under the terms of the GNU General Public License. */ #import "HBQueueController.h" + #import "HBCore.h" #import "Controller.h" +#import "HBOutputPanelController.h" #import "HBQueueOutlineView.h" -#import "HBImageAndTextCell.h" #import "HBUtilities.h" #import "HBJob.h" -#import "HBAudioDefaults.h" -#import "HBAudioTrack.h" +#import "HBJob+UIAdditions.h" -#import "HBPicture+UIAdditions.h" -#import "HBFilters+UIAdditions.h" +#import "HBDistributedArray.h" -#define HB_ROW_HEIGHT_TITLE_ONLY 17.0 +#import "HBDockTile.h" // Pasteboard type for or drag operations -#define DragDropSimplePboardType @"HBQueueCustomOutlineViewPboardType" +#define DragDropSimplePboardType @"HBQueueCustomOutlineViewPboardType" -#pragma mark - +// DockTile update freqency in total percent increment +#define dockTileUpdateFrequency 0.1f -//------------------------------------------------------------------------------------ -// NSMutableAttributedString (HBAdditions) -//------------------------------------------------------------------------------------ - -@interface NSMutableAttributedString (HBAdditions) -- (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary; -@end - -@implementation NSMutableAttributedString (HBAdditions) -- (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary -{ - NSAttributedString *s = [[[NSAttributedString alloc] initWithString:aString - attributes:aDictionary] autorelease]; - [self appendAttributedString:s]; -} -@end - -#pragma mark - +#define HB_ROW_HEIGHT_TITLE_ONLY 17.0 @interface HBQueueController () <HBQueueOutlineViewDelegate> -{ - HBController *fHBController; // reference to HBController - NSMutableArray *fJobGroups; // mirror image of the queue array from controller.mm - - int pidNum; // Records the PID number from HBController for this instance - int fEncodingQueueItem; // corresponds to the index of fJobGroups encoding item - int fPendingCount; // Number of various kinds of job groups in fJobGroups. - int fWorkingCount; - NSMutableIndexSet *fSavedExpandedItems; // used by save/restoreOutlineViewState to preserve which items are expanded - NSMutableIndexSet *fSavedSelectedItems; // used by save/restoreOutlineViewState to preserve which items are selected - NSMutableDictionary *descriptions; +@property (nonatomic, readonly) HBDockTile *dockTile; +@property (nonatomic, readwrite) double dockIconProgress; - NSTimer *fAnimationTimer; // animates the icon of the current job in the queue outline view - int fAnimationIndex; // used to generate name of image used to animate the current job in the queue outline view +@property (assign) IBOutlet NSTextField *progressTextField; +@property (assign) IBOutlet NSTextField *countTextField; +@property (assign) IBOutlet HBQueueOutlineView *outlineView; - IBOutlet NSTextField *fProgressTextField; +@property (nonatomic, readonly) NSMutableDictionary *descriptions; - IBOutlet HBQueueOutlineView *fOutlineView; - IBOutlet NSTextField *fQueueCountField; - NSArray *fDraggedNodes; +@property (nonatomic, readonly) HBDistributedArray *jobs; +@property (nonatomic, retain) HBJob *currentJob; - // Text Styles - NSMutableParagraphStyle *ps; - NSDictionary *detailAttr; - NSDictionary *detailBoldAttr; - NSDictionary *titleAttr; - NSDictionary *shortHeightAttr; -} - -@property (nonatomic, readonly) HBCore *queueCore; +@property (nonatomic, readwrite) NSUInteger pendingItemsCount; +@property (nonatomic, readwrite) NSUInteger workingItemsCount; -/* control encodes in the window */ -- (IBAction)removeSelectedQueueItem: (id)sender; -- (IBAction)revealSelectedQueueItem: (id)sender; -- (IBAction)editSelectedQueueItem: (id)sender; +@property (nonatomic, retain) NSArray *dragNodesArray; @end @implementation HBQueueController -//------------------------------------------------------------------------------------ -// init -//------------------------------------------------------------------------------------ -- (id)init +- (instancetype)init { if (self = [super initWithWindowNibName:@"Queue"]) { @@ -101,267 +63,743 @@ // If/when we switch to using bindings, this can probably go away. [self window]; - // Our defaults - [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"QueueWindowIsOpen": @"NO"}]; + _descriptions = [[NSMutableDictionary alloc] init]; + + // Workaround to avoid a bug in Snow Leopard + // we can switch back to [[NSApplication sharedApplication] applicationIconImage] + // when we won't support it anymore. + NSImage *appIcon = [NSImage imageNamed:@"HandBrake"]; + [appIcon setSize:NSMakeSize(1024, 1024)]; + + // Load the dockTile and instiante initial text fields + _dockTile = [[HBDockTile alloc] initWithDockTile:[[NSApplication sharedApplication] dockTile] + image:appIcon]; - fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain]; - descriptions = [[NSMutableDictionary alloc] init]; + int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue]; - [self initStyles]; + // Init a separate instance of libhb for the queue + _core = [[HBCore alloc] initWithLoggingLevel:loggingLevel]; + _core.name = @"QueueCore"; + + [self loadQueueFile]; } return self; } -- (void)setQueueArray:(NSMutableArray *)QueueFileArray +- (void)dealloc +{ + // clear the delegate so that windowWillClose is not attempted + if ([[self window] delegate] == self) + [[self window] setDelegate:nil]; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [_core release]; + [_jobs release]; + [_currentJob release]; + + [_dockTile release]; + [_descriptions release]; + [_dragNodesArray release]; + + [super dealloc]; +} + +- (void)windowDidLoad +{ + // lets setup our queue list outline view for drag and drop here + [self.outlineView registerForDraggedTypes:@[DragDropSimplePboardType]]; + [self.outlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; + [self.outlineView setVerticalMotionCanBeginDrag:YES]; + + // Don't allow autoresizing of main column, else the "delete" column will get + // pushed out of view. + [self.outlineView setAutoresizesOutlineColumn: NO]; +} + +- (void)windowWillClose:(NSNotification *)aNotification +{ + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"]; +} + +/** + * Displays and brings the queue window to the front + */ +- (IBAction)showWindow:(id)sender +{ + [super showWindow:sender]; + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"]; +} + +#pragma mark Toolbar + +- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem +{ + SEL action = theItem.action; + HBState s = self.core.state; + + if (action == @selector(toggleStartCancel:)) + { + if ((s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing)) + { + theItem.image = [NSImage imageNamed:@"stopencode"]; + theItem.label = NSLocalizedString(@"Stop", @""); + theItem.toolTip = NSLocalizedString(@"Stop Encoding", @""); + return YES; + } + else + { + theItem.image = [NSImage imageNamed:@"encode"]; + theItem.label = NSLocalizedString(@"Start", @""); + theItem.toolTip = NSLocalizedString(@"Start Encoding", @""); + return (self.pendingItemsCount > 0); + } + } + + if (action == @selector(togglePauseResume:)) + { + if (s == HBStatePaused) + { + theItem.image = [NSImage imageNamed:@"encode"]; + theItem.label = NSLocalizedString(@"Resume", @""); + theItem.toolTip = NSLocalizedString(@"Resume Encoding", @""); + return YES; + } + else + { + theItem.image = [NSImage imageNamed:@"pauseencode"]; + theItem.label = NSLocalizedString(@"Pause", @""); + theItem.toolTip = NSLocalizedString(@"Pause Encoding", @""); + return (s == HBStateWorking || s == HBStateMuxing); + } + } + + return NO; +} + +#pragma mark - +#pragma mark Queue File + +- (void)reloadQueue { - [fJobGroups setArray:QueueFileArray]; - [descriptions removeAllObjects]; + [self getQueueStats]; + [self.outlineView reloadData]; +} - [fOutlineView reloadData]; +- (void)loadQueueFile +{ + NSURL *queueURL = [NSURL fileURLWithPath:[[HBUtilities appSupportPath] stringByAppendingPathComponent:@"Queue/Queue.hbqueue"]]; + _jobs = [[HBDistributedArray alloc] initWithURL:queueURL]; + + [self reloadQueue]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadQueue) name:HBDistributedArrayChanged object:_jobs]; +} + +- (void)addJob:(HBJob *)item +{ + [self.jobs beginTransaction]; + [self.jobs addObject:item]; + [self.jobs commit]; + + [self reloadQueue]; +} +- (void)addJobsFromArray:(NSArray *)items; +{ + [self.jobs beginTransaction]; + [self.jobs addObjectsFromArray:items]; + [self.jobs commit]; + + [self reloadQueue]; +} + +- (BOOL)jobExistAtURL:(NSURL *)url +{ + for (HBJob *item in self.jobs) + { + if ([item.destURL isEqualTo:url]) + { + return YES; + } + } + return NO; +} + +- (void)removeQueueItemAtIndex:(NSUInteger)index +{ + [self.jobs beginTransaction]; + if (self.jobs.count > index) + { + [self.jobs removeObjectAtIndex:index]; + } + [self.jobs commit]; + + [self reloadQueue]; +} + +/** + * Updates the queue status label. + */ +- (void)getQueueStats +{ // lets get the stats on the status of the queue array - - fPendingCount = 0; - fWorkingCount = 0; - - int i = 0; - for (HBJob *job in fJobGroups) - { - if (job.state == HBJobStateWorking) // being encoded - { - fWorkingCount++; - // we have an encoding job so, lets start the animation timer - if (job.pidId == pidNum) - { - fEncodingQueueItem = i; - } - } + NSUInteger pendingCount = 0; + NSUInteger workingCount = 0; + + for (HBJob *job in self.jobs) + { + if (job.state == HBJobStateWorking) // being encoded + { + workingCount++; + } if (job.state == HBJobStateReady) // pending { - fPendingCount++; - } - i++; - } + pendingCount++; + } + } - // Set the queue status field in the queue window - NSMutableString *string; - if (fPendingCount == 0) + NSString *string; + if (pendingCount == 0) { - string = [NSMutableString stringWithFormat: NSLocalizedString( @"No encode pending", @"" )]; + string = NSLocalizedString(@"No encode pending", @""); } - else if (fPendingCount == 1) + else if (pendingCount == 1) { - string = [NSMutableString stringWithFormat: NSLocalizedString( @"%d encode pending", @"" ), fPendingCount]; + string = [NSString stringWithFormat: NSLocalizedString(@"%d encode pending", @""), pendingCount]; } else { - string = [NSMutableString stringWithFormat: NSLocalizedString( @"%d encodes pending", @"" ), fPendingCount]; + string = [NSString stringWithFormat: NSLocalizedString(@"%d encodes pending", @""), pendingCount]; } - [fQueueCountField setStringValue:string]; - + + self.countTextField.stringValue = string; + [self.controller setQueueState:string]; + + self.pendingItemsCount = pendingCount; + self.workingItemsCount = workingCount; } -/* This method sets the status string in the queue window - * and is called from Controller.mm (fHBController) - * instead of running another timer here polling libhb - * for encoding status - */ -- (void)setQueueStatusString:(NSString *)statusString +- (NSUInteger)count { - [fProgressTextField setStringValue:statusString]; + return self.jobs.count; } -//------------------------------------------------------------------------------------ -// dealloc -//------------------------------------------------------------------------------------ -- (void)dealloc +/** + * Used to get the next pending queue item and return it if found + */ +- (HBJob *)getNextPendingQueueItem { - // clear the delegate so that windowWillClose is not attempted - if( [[self window] delegate] == self ) - [[self window] setDelegate:nil]; + for (HBJob *job in self.jobs) + { + if (job.state == HBJobStateReady) + { + return job; + } + } + return nil; +} - [fJobGroups release]; +/** + * This method will set any item marked as encoding back to pending + * currently used right after a queue reload + */ +- (void)setEncodingJobsAsPending +{ + [self.jobs beginTransaction]; + for (HBJob *job in self.jobs) + { + // We want to keep any queue item that is pending or was previously being encoded + if (job.state == HBJobStateWorking) + { + job.state = HBJobStateReady; + } + } + [self.jobs commit]; - [fSavedExpandedItems release]; - [fSavedSelectedItems release]; + [self reloadQueue]; +} - [ps release]; - [detailAttr release]; - [detailBoldAttr release]; - [titleAttr release]; - [shortHeightAttr release]; +/** + * This method will clear the queue of any encodes that are not still pending + * this includes both successfully completed encodes as well as cancelled encodes + */ +- (void)clearEncodedJobs +{ + [self.jobs beginTransaction]; + NSMutableArray *encodedJobs = [NSMutableArray array]; + for (HBJob *job in self.jobs) + { + if (job.state == HBJobStateCompleted || job.state == HBJobStateCanceled) + { + [encodedJobs addObject:job]; + } + } - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self.jobs removeObjectsInArray:encodedJobs]; + [self.jobs commit]; - [super dealloc]; + [self reloadQueue]; } -//------------------------------------------------------------------------------------ -// Receive HB handle -//------------------------------------------------------------------------------------ -- (void)setCore: (HBCore *)core +/** + * This method will clear the queue of all encodes. effectively creating an empty queue + */ +- (void)removeAllJobs { - _queueCore = core; -} + [self.jobs beginTransaction]; + [self.jobs removeAllObjects]; + [self.jobs commit]; -//------------------------------------------------------------------------------------ -// Receive HBController -//------------------------------------------------------------------------------------ -- (void)setHBController: (HBController *)controller -{ - fHBController = controller; + [self reloadQueue]; } -- (void)setPidNum: (int)myPidnum +/** + * this is actually called from the queue controller to modify the queue array and return it back to the queue controller + */ +- (void)moveObjectsInQueueArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex { - pidNum = myPidnum; - [HBUtilities writeToActivityLog: "HBQueueController : My Pidnum is %d", pidNum]; + [self.jobs beginTransaction]; + + NSUInteger index = [indexSet lastIndex]; + NSUInteger aboveInsertIndexCount = 0; + + NSUInteger removeIndex; + + if (index >= insertIndex) + { + removeIndex = index + aboveInsertIndexCount; + aboveInsertIndexCount++; + } + else + { + removeIndex = index; + insertIndex--; + } + + id object = [self.jobs[removeIndex] retain]; + [self.jobs removeObjectAtIndex:removeIndex]; + [self.jobs insertObject:object atIndex:insertIndex]; + [object release]; + + // We save all of the Queue data here + // and it also gets sent back to the queue controller + [self.jobs commit]; + [self reloadQueue]; } #pragma mark - +#pragma mark Queue Job Processing -//------------------------------------------------------------------------------------ -// Displays and brings the queue window to the front -//------------------------------------------------------------------------------------ -- (IBAction) showQueueWindow: (id)sender +/** + * Starts the queue + */ +- (void)encodeNextQueueItem { - [self showWindow:sender]; - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"]; - [self startAnimatingCurrentWorkingEncodeInQueue]; + // Check to see if there are any more pending items in the queue + HBJob *nextJob = [self getNextPendingQueueItem]; + + // If we still have more pending items in our queue, lets go to the next one + if (nextJob) + { + self.currentJob = nextJob; + // now we mark the queue item as working so another instance can not come along and try to scan it while we are scanning + self.currentJob.state = HBJobStateWorking; + + // now we can go ahead and scan the new pending queue item + [self performScan:self.currentJob.fileURL titleIdx:self.currentJob.titleIdx]; + } + else + { + self.currentJob = nil; + + [HBUtilities writeToActivityLog:"Queue Done, there are no more pending encodes"]; + + // Since there are no more items to encode, go to queueCompletedAlerts + // for user specified alerts after queue completed + [self queueCompletedAlerts]; + } } -//------------------------------------------------------------------------------------ -// windowDidLoad -//------------------------------------------------------------------------------------ -- (void)windowDidLoad +- (void)encodeCompleted { - /* lets setup our queue list outline view for drag and drop here */ - [fOutlineView registerForDraggedTypes: @[DragDropSimplePboardType] ]; - [fOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; - [fOutlineView setVerticalMotionCanBeginDrag: YES]; + // Since we are done with this encode, tell output to stop writing to the + // individual encode log. + [self.outputPanel endEncodeLog]; - // Don't allow autoresizing of main column, else the "delete" column will get - // pushed out of view. - [fOutlineView setAutoresizesOutlineColumn: NO]; + // Check to see if the encode state has not been cancelled + // to determine if we should check for encode done notifications. + if (self.currentJob.state != HBJobStateCanceled) + { + // Both the Growl Alert and Sending to tagger can be done as encodes roll off the queue + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Growl Notification"] || + [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window And Growl"]) + { + // If Play System Alert has been selected in Preferences + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"AlertWhenDoneSound"] == YES) + { + NSBeep(); + } + [self showGrowlDoneNotification:self.currentJob.destURL]; + } + + // Mark the encode just finished as done + self.currentJob.state = HBJobStateCompleted; + + // Send to tagger + [self sendToExternalApp:self.currentJob.destURL]; + } + + self.currentJob = nil; + + // since we have successfully completed an encode, we go to the next + [self encodeNextQueueItem]; + + [self.window.toolbar validateVisibleItems]; + [self reloadQueue]; } -//------------------------------------------------------------------------------------ -// windowWillClose -//------------------------------------------------------------------------------------ -- (void)windowWillClose:(NSNotification *)aNotification +/** + * Here we actually tell hb_scan to perform the source scan, using the path to source and title number + */ +- (void)performScan:(NSURL *)scanURL titleIdx:(NSInteger)index { - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"QueueWindowIsOpen"]; - [self stopAnimatingCurrentJobGroupInQueue]; + // Tell HB to output a new activity log file for this encode + [self.outputPanel startEncodeLog:self.currentJob.destURL]; + + // Only scan 10 previews before an encode - additional previews are + // only useful for autocrop and static previews, which are already taken care of at this point + [self.core scanURL:scanURL + titleIndex:index + previews:10 + minDuration:0 + progressHandler:^(HBState state, hb_state_t hb_state) { + NSMutableString *status = [NSMutableString stringWithFormat: + NSLocalizedString( @"Queue Scanning title %d of %d…", @"" ), + hb_state.param.scanning.title_cur, hb_state.param.scanning.title_count]; + if (hb_state.param.scanning.preview_cur) + { + [status appendFormat:@", preview %d…", hb_state.param.scanning.preview_cur]; + } + + self.progressTextField.stringValue = status; + [self.controller setQueueInfo:status progress:0 hidden:NO]; + } + completationHandler:^(BOOL success) { + if (success) + { + [self doEncodeQueueItem]; + } + else + { + [self.jobs beginTransaction]; + + self.currentJob.state = HBJobStateCanceled; + [self encodeCompleted]; + + [self.jobs commit]; + [self reloadQueue]; + } + + [self.window.toolbar validateVisibleItems]; + }]; } -#pragma mark Toolbar +/** + * This assumes that we have re-scanned and loaded up a new queue item to send to libhb + */ +- (void)doEncodeQueueItem +{ + // Reset the title in the job. + self.currentJob.title = self.core.titles[0]; + + // We should be all setup so let 'er rip + [self.core encodeJob:self.currentJob + progressHandler:^(HBState state, hb_state_t hb_state) { + NSMutableString *string = nil; + CGFloat progress = 0; + switch (state) + { + case HBStateSearching: + { + #define p hb_state.param.working + string = [NSMutableString stringWithFormat: + NSLocalizedString(@"Searching for start point… : %.2f %%", @""), + 100.0 * p.progress]; + + if (p.seconds > -1) + { + [string appendFormat:NSLocalizedString(@" (ETA %02dh%02dm%02ds)", @"" ), p.hours, p.minutes, p.seconds]; + } + #undef p + + break; + } + case HBStateWorking: + { + #define p hb_state.param.working + NSString *pass_desc = @""; + if (p.job_cur == 1 && p.job_count > 1) + { + if ([self.currentJob.subtitles.tracks.firstObject[keySubTrackIndex] intValue] == -1) + { + pass_desc = NSLocalizedString(@"(subtitle scan)", @""); + } + } + + if (pass_desc.length) + { + string = [NSMutableString stringWithFormat: + NSLocalizedString(@"Encoding: %@ \nPass %d %@ of %d, %.2f %%", @""), + self.currentJob.destURL.lastPathComponent, + p.job_cur, pass_desc, p.job_count, 100.0 * p.progress]; + } + else + { + string = [NSMutableString stringWithFormat: + NSLocalizedString(@"Encoding: %@ \nPass %d of %d, %.2f %%", @""), + self.currentJob.destURL.lastPathComponent, + p.job_cur, p.job_count, 100.0 * p.progress]; + } + + if (p.seconds > -1) + { + if (p.rate_cur > 0.0) + { + [string appendFormat: + NSLocalizedString(@" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @""), + p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds]; + } + else + { + [string appendFormat: + NSLocalizedString(@" (ETA %02dh%02dm%02ds)", @""), + p.hours, p.minutes, p.seconds]; + } + } + + progress = (p.progress + p.job_cur - 1) / p.job_count; + + // Update dock icon + if (self.dockIconProgress < 100.0 * progress) + { + // ETA format is [XX]X:XX:XX when ETA is greater than one hour + // [X]X:XX when ETA is greater than 0 (minutes or seconds) + // When these conditions doesn't applied (eg. when ETA is undefined) + // we show just a tilde (~) + + NSString *etaStr = @""; + if (p.hours > 0) + etaStr = [NSString stringWithFormat:@"%d:%02d:%02d", p.hours, p.minutes, p.seconds]; + else if (p.minutes > 0 || p.seconds > 0) + etaStr = [NSString stringWithFormat:@"%d:%02d", p.minutes, p.seconds]; + else + etaStr = @"~"; + + [self.dockTile updateDockIcon:progress withETA:etaStr]; + + self.dockIconProgress += dockTileUpdateFrequency; + } + #undef p + + break; + } + case HBStateMuxing: + { + string = [NSMutableString stringWithString:NSLocalizedString(@"Muxing…", @"")]; + + // Update dock icon + [self.dockTile updateDockIcon:1.0 withETA:@""]; + + break; + } + case HBStatePaused: + { + string = [NSMutableString stringWithString:NSLocalizedString(@"Paused", @"")]; + break; + } + default: + break; + } + + // Update text field + self.progressTextField.stringValue = string; + [self.controller setQueueInfo:string progress:progress * 100.0 hidden:NO]; + } + completationHandler:^(BOOL success) { + NSString *info = NSLocalizedString(@"Encode Finished.", @""); + + self.progressTextField.stringValue = info; + [self.controller setQueueInfo:info progress:100.0 hidden:YES]; + + // Restore dock icon + [self.dockTile updateDockIcon:-1.0 withETA:@""]; + self.dockIconProgress = 0; + + [self.jobs beginTransaction]; + + [self encodeCompleted]; + + [self.jobs commit]; + [self reloadQueue]; + }]; + + // We are done using the title, remove it from the job + self.currentJob.title = nil; +} + +/** + * Cancels and deletes the current job and starts processing the next in queue. + */ +- (void)doCancelCurrentJob +{ + [self.jobs beginTransaction]; + + self.currentJob.state = HBJobStateCanceled; + [self.core cancelEncode]; + + [self.jobs commit]; + [self reloadQueue]; +} -//------------------------------------------------------------------------------------ -// validateToolbarItem: -//------------------------------------------------------------------------------------ -- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem +/** + * Cancels and deletes the current job and stops libhb from processing the remaining encodes. + */ +- (void)doCancelCurrentJobAndStop { - // Optional method: This message is sent to us since we are the target of some - // toolbar item actions. + [self.jobs beginTransaction]; - if (!self.queueCore) return NO; + self.currentJob.state = HBJobStateCanceled; - BOOL enable = NO; - HBState s = self.queueCore.state; + [self.core cancelEncode]; - if ([[toolbarItem itemIdentifier] isEqualToString:@"HBQueueStartCancelToolbarIdentifier"]) - { - if ((s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing)) - { - enable = YES; - [toolbarItem setImage:[NSImage imageNamed: @"stopencode"]]; - [toolbarItem setLabel: @"Stop"]; - [toolbarItem setToolTip: @"Stop Encoding"]; - } + [self.jobs commit]; + [self reloadQueue]; +} - else if (fPendingCount > 0) - { - enable = YES; - [toolbarItem setImage:[NSImage imageNamed: @"encode"]]; - [toolbarItem setLabel: @"Start"]; - [toolbarItem setToolTip: @"Start Encoding"]; - } +#pragma mark - Encode Done Actions - else +#define SERVICE_NAME @"Encode Done" + +/** + * Register a test notification and make + * it enabled by default + */ +- (NSDictionary *)registrationDictionaryForGrowl +{ + return @{GROWL_NOTIFICATIONS_ALL: @[SERVICE_NAME], + GROWL_NOTIFICATIONS_DEFAULT: @[SERVICE_NAME]}; +} + +- (void)showGrowlDoneNotification:(NSURL *)fileURL +{ + // This end of encode action is called as each encode rolls off of the queue + // Setup the Growl stuff + NSString *growlMssg = [NSString stringWithFormat:@"your HandBrake encode %@ is done!", fileURL.lastPathComponent]; + [GrowlApplicationBridge notifyWithTitle:@"Put down that cocktail…" + description:growlMssg + notificationName:SERVICE_NAME + iconData:nil + priority:0 + isSticky:1 + clickContext:nil]; +} + +- (void)sendToExternalApp:(NSURL *)fileURL +{ + // This end of encode action is called as each encode rolls off of the queue + if([[NSUserDefaults standardUserDefaults] boolForKey: @"sendToMetaX"] == YES) + { + NSString *sendToApp = [[NSUserDefaults standardUserDefaults] objectForKey:@"SendCompletedEncodeToApp"]; + if (![sendToApp isEqualToString:@"None"]) { - enable = NO; - [toolbarItem setImage:[NSImage imageNamed: @"encode"]]; - [toolbarItem setLabel: @"Start"]; - [toolbarItem setToolTip: @"Start Encoding"]; + [HBUtilities writeToActivityLog: "trying to send encode to: %s", [sendToApp UTF8String]]; + NSAppleScript *myScript = [[NSAppleScript alloc] initWithSource: [NSString stringWithFormat: @"%@%@%@%@%@", @"tell application \"",sendToApp,@"\" to open (POSIX file \"", fileURL.path, @"\")"]]; + [myScript executeAndReturnError: nil]; + [myScript release]; } } +} - if ([[toolbarItem itemIdentifier] isEqualToString:@"HBQueuePauseResumeToolbarIdentifier"]) +- (void)queueCompletedAlerts +{ + // If Play System Alert has been selected in Preferences + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"AlertWhenDoneSound"] == YES) { - if (s == HBStatePaused) - { - enable = YES; - [toolbarItem setImage:[NSImage imageNamed: @"encode"]]; - [toolbarItem setLabel: @"Resume"]; - [toolbarItem setToolTip: @"Resume Encoding"]; - } + NSBeep(); + } - else if ((s == HBStateWorking) || (s == HBStateMuxing)) - { - enable = YES; - [toolbarItem setImage:[NSImage imageNamed: @"pauseencode"]]; - [toolbarItem setLabel: @"Pause"]; - [toolbarItem setToolTip: @"Pause Encoding"]; - } - else - { - enable = NO; - [toolbarItem setImage:[NSImage imageNamed: @"pauseencode"]]; - [toolbarItem setLabel: @"Pause"]; - [toolbarItem setToolTip: @"Pause Encoding"]; - } + // If Alert Window or Window and Growl has been selected + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window"] || + [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window And Growl"]) + { + // On Screen Notification + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"Put down that cocktail…", @"")]; + [alert setInformativeText:NSLocalizedString(@"Your HandBrake queue is done!", @"")]; + [NSApp requestUserAttention:NSCriticalRequest]; + [alert runModal]; + [alert release]; } - return enable; + // If sleep has been selected + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Put Computer To Sleep"]) + { + // Sleep + NSDictionary *errorDict; + NSAppleScript *scriptObject = [[NSAppleScript alloc] initWithSource: + @"tell application \"Finder\" to sleep"]; + [scriptObject executeAndReturnError: &errorDict]; + [scriptObject release]; + } + // If Shutdown has been selected + if( [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Shut Down Computer"] ) + { + // Shut Down + NSDictionary *errorDict; + NSAppleScript *scriptObject = [[NSAppleScript alloc] initWithSource:@"tell application \"Finder\" to shut down"]; + [scriptObject executeAndReturnError: &errorDict]; + [scriptObject release]; + } } -#pragma mark - - - -#pragma mark Queue Item Controls +#pragma mark - Queue Item Controls - (void)HB_deleteSelectionFromTableView:(NSTableView *)tableView { [self removeSelectedQueueItem:tableView]; } -//------------------------------------------------------------------------------------ -// Delete encodes from the queue window and accompanying array -// Also handling first cancelling the encode if in fact its currently encoding. -//------------------------------------------------------------------------------------ -- (IBAction)removeSelectedQueueItem: (id)sender +/** + * Delete encodes from the queue window and accompanying array + * Also handling first cancelling the encode if in fact its currently encoding. + */ +- (IBAction)removeSelectedQueueItem:(id)sender { - NSIndexSet *targetedRow = [fOutlineView targetedRowIndexes]; + NSIndexSet *targetedRow = [self.outlineView targetedRowIndexes]; NSUInteger row = [targetedRow firstIndex]; if (row == NSNotFound) + { return; - /* if this is a currently encoding job, we need to be sure to alert the user, - * to let them decide to cancel it first, then if they do, we can come back and - * remove it */ - - if ([fJobGroups[row] state] == HBJobStateWorking) + } + + // if this is a currently encoding job, we need to be sure to alert the user, + // to let them decide to cancel it first, then if they do, we can come back and + // remove it + if (self.jobs[row] == self.currentJob) { - /* We pause the encode here so that it doesn't finish right after and then - * screw up the sync while the window is open - */ - [fHBController Pause:NULL]; - NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It ?", nil)]; + // We pause the encode here so that it doesn't finish right after and then + // screw up the sync while the window is open + [self togglePauseResume:nil]; + + NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It?", nil)]; + // Which window to attach the sheet to? - NSWindow *docWindow = nil; + NSWindow *targetWindow = nil; if ([sender respondsToSelector: @selector(window)]) { - docWindow = [sender window]; + targetWindow = [sender window]; } NSAlert *alert = [[NSAlert alloc] init]; @@ -371,56 +809,55 @@ [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Delete", nil)]; [alert setAlertStyle:NSCriticalAlertStyle]; - [alert beginSheetModalForWindow:docWindow + [alert beginSheetModalForWindow:targetWindow modalDelegate:self didEndSelector:@selector(didDimissCancelCurrentJob:returnCode:contextInfo:) - contextInfo:nil]; + contextInfo:self.jobs[row]]; [alert release]; } - else - { + else if ([self.jobs[row] state] != HBJobStateWorking) + { // since we are not a currently encoding item, we can just be removed - [fHBController removeQueueFileItem:row]; + [self removeQueueItemAtIndex:row]; } } -- (void) didDimissCancelCurrentJob: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo +- (void)didDimissCancelCurrentJob:(NSAlert *)alert + returnCode:(NSInteger)returnCode + contextInfo:(void *)contextInfo { - /* We resume encoding and perform the appropriate actions - * Note: Pause: is a toggle type method based on hb's current - * state, if it paused, it will resume encoding and vice versa. - * In this case, we are paused from the calling window, so calling - * [fHBController Pause:NULL]; Again will resume encoding - */ - [fHBController Pause:NULL]; + // We resume encoding and perform the appropriate actions + // Note: Pause: is a toggle type method based on hb's current + // state, if it paused, it will resume encoding and vice versa. + // In this case, we are paused from the calling window, so calling + // [self togglePauseResume:nil]; Again will resume encoding + [self togglePauseResume:nil]; + if (returnCode == NSAlertSecondButtonReturn) { - /* We need to save the currently encoding item number first */ - int encodingItemToRemove = fEncodingQueueItem; - /* Since we are encoding, we need to let fHBController Cancel this job - * upon which it will move to the next one if there is one - */ - [fHBController doCancelCurrentJob]; - /* Now, we can go ahead and remove the job we just cancelled since - * we have its item number from above - */ - [fHBController removeQueueFileItem:encodingItemToRemove]; + // We need to save the currently encoding item number first + NSInteger index = [self.jobs indexOfObject:self.currentJob]; + // Since we are encoding, we need to let fHBController Cancel this job + // upon which it will move to the next one if there is one + [self doCancelCurrentJob]; + // Now, we can go ahead and remove the job we just cancelled since + // we have its item number from above + [self removeQueueItemAtIndex:index]; } - } -//------------------------------------------------------------------------------------ -// Show the finished encode in the finder -//------------------------------------------------------------------------------------ +/** + * Show the finished encode in the finder + */ - (IBAction)revealSelectedQueueItem: (id)sender { - NSIndexSet *targetedRow = [fOutlineView targetedRowIndexes]; + NSIndexSet *targetedRow = [self.outlineView targetedRowIndexes]; NSInteger row = [targetedRow firstIndex]; if (row != NSNotFound) { while (row != NSNotFound) { - HBJob *queueItemToOpen = [fOutlineView itemAtRow:row]; + HBJob *queueItemToOpen = [self.outlineView itemAtRow:row]; [[NSWorkspace sharedWorkspace] selectFile:queueItemToOpen.destURL.path inFileViewerRootedAtPath:nil]; row = [targetedRow indexGreaterThanIndex: row]; @@ -428,65 +865,177 @@ } } -//------------------------------------------------------------------------------------ -// Starts or cancels the processing of jobs depending on the current state -//------------------------------------------------------------------------------------ -- (IBAction)toggleStartCancel: (id)sender +- (void)remindUserOfSleepOrShutdown { - if (!self.queueCore) return; + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString:@"Put Computer To Sleep"]) + { + // Warn that computer will sleep after encoding + NSBeep(); + [NSApp requestUserAttention:NSCriticalRequest]; - HBState s = self.queueCore.state; - if ((s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing)) + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"The computer will sleep after encoding is done.", @"")]; + [alert setInformativeText:NSLocalizedString(@"You have selected to sleep the computer after encoding. To turn off sleeping, go to the HandBrake preferences.", @"")]; + [alert addButtonWithTitle:NSLocalizedString(@"OK", @"")]; + [alert addButtonWithTitle:NSLocalizedString(@"Preferences…",@"")]; + + NSInteger response = [alert runModal]; + if (response == NSAlertSecondButtonReturn) + { + [self.controller showPreferencesWindow:nil]; + } + [alert release]; + } + else if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString:@"Shut Down Computer"]) { - [fHBController Cancel: self]; + // Warn that computer will shut down after encoding + NSBeep(); + [NSApp requestUserAttention:NSCriticalRequest]; + + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"The computer will shut down after encoding is done.", @"")]; + [alert setInformativeText:NSLocalizedString(@"You have selected to shut down the computer after encoding. To turn off shut down, go to the HandBrake preferences.", @"")]; + [alert addButtonWithTitle:NSLocalizedString(@"OK", @"")]; + [alert addButtonWithTitle:NSLocalizedString(@"Preferences…", @"")]; + + NSInteger response = [alert runModal]; + if (response == NSAlertSecondButtonReturn) + { + [self.controller showPreferencesWindow:nil]; + } + [alert release]; } - else if (fPendingCount > 0) +} + +/** + * Rip: puts up an alert before ultimately calling doRip + */ +- (IBAction)rip:(id)sender +{ + // Rip or Cancel ? + if (self.core.state == HBStateWorking || self.core.state == HBStatePaused) { - [fHBController Rip: NULL]; + [self cancel:sender]; } + // If there are pending jobs in the queue, then this is a rip the queue + else if (self.pendingItemsCount > 0) + { + // We check to see if we need to warn the user that the computer will go to sleep + // or shut down when encoding is finished + [self remindUserOfSleepOrShutdown]; + + [self.jobs beginTransaction]; + + [self encodeNextQueueItem]; + + [self.jobs commit]; + [self reloadQueue]; + } +} + +/** + * Displays an alert asking user if the want to cancel encoding of current job. + * Cancel: returns immediately after posting the alert. Later, when the user + * acknowledges the alert, doCancelCurrentJob is called. + */ +- (IBAction)cancel:(id)sender +{ + [self.core pause]; + + // Which window to attach the sheet to? + NSWindow *window; + if ([sender respondsToSelector:@selector(window)]) + { + window = [sender window]; + } + else + { + window = self.window; + } + + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"You are currently encoding. What would you like to do ?", nil)]; + [alert setInformativeText:NSLocalizedString(@"Your encode will be cancelled if you don't continue encoding.", nil)]; + [alert addButtonWithTitle:NSLocalizedString(@"Continue Encoding", nil)]; + [alert addButtonWithTitle:NSLocalizedString(@"Cancel Current and Stop", nil)]; + [alert addButtonWithTitle:NSLocalizedString(@"Cancel Current and Continue", nil)]; + [alert setAlertStyle:NSCriticalAlertStyle]; + + [alert beginSheetModalForWindow:window + modalDelegate:self + didEndSelector:@selector(didDimissCancel:returnCode:contextInfo:) + contextInfo:nil]; + [alert release]; } -//------------------------------------------------------------------------------------ -// Toggles the pause/resume state of libhb -//------------------------------------------------------------------------------------ -- (IBAction)togglePauseResume: (id)sender +- (void)didDimissCancel:(NSAlert *)alert + returnCode:(NSInteger)returnCode + contextInfo:(void *)contextInfo { - if (!self.queueCore) return; + [self.core resume]; - HBState s = self.queueCore.state; + if (returnCode == NSAlertSecondButtonReturn) + { + [self doCancelCurrentJobAndStop]; // <- this also stops libhb + } + else if (returnCode == NSAlertThirdButtonReturn) + { + [self doCancelCurrentJob]; + } +} + +/** + * Starts or cancels the processing of jobs depending on the current state + */ +- (IBAction)toggleStartCancel:(id)sender +{ + HBState s = self.core.state; + if ((s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing)) + { + [self cancel:self]; + } + else if (self.pendingItemsCount > 0) + { + [self rip:self]; + } +} + +/** + * Toggles the pause/resume state of libhb + */ +- (IBAction)togglePauseResume:(id)sender +{ + HBState s = self.core.state; if (s == HBStatePaused) { - [self.queueCore resume]; - [self startAnimatingCurrentWorkingEncodeInQueue]; + [self.core resume]; } - else if ((s == HBStateWorking) || (s == HBStateMuxing)) + else if (s == HBStateWorking || s == HBStateMuxing) { - [self.queueCore pause]; - [self stopAnimatingCurrentJobGroupInQueue]; + [self.core pause]; } } -//------------------------------------------------------------------------------------ -// Send the selected queue item back to the main window for rescan and possible edit. -//------------------------------------------------------------------------------------ -- (IBAction)editSelectedQueueItem: (id)sender +/** + * Send the selected queue item back to the main window for rescan and possible edit. + */ +- (IBAction)editSelectedQueueItem:(id)sender { - NSInteger row = [fOutlineView clickedRow]; + NSInteger row = [self.outlineView clickedRow]; if (row == NSNotFound) { return; } - /* if this is a currently encoding job, we need to be sure to alert the user, - * to let them decide to cancel it first, then if they do, we can come back and - * remove it */ - - HBJob *job = fJobGroups[row]; + // if this is a currently encoding job, we need to be sure to alert the user, + // to let them decide to cancel it first, then if they do, we can come back and + // remove it + HBJob *job = self.jobs[row]; if (job.state == HBJobStateWorking) { // We pause the encode here so that it doesn't finish right after and then // screw up the sync while the window is open - [fHBController Pause:NULL]; + [self togglePauseResume:nil]; NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It ?", nil)]; // Which window to attach the sheet to? NSWindow *docWindow = nil; @@ -505,115 +1054,29 @@ [alert beginSheetModalForWindow:docWindow modalDelegate:self didEndSelector:@selector(didDimissCancelCurrentJob:returnCode:contextInfo:) - contextInfo:nil]; + contextInfo:job]; [alert release]; } else { - /* since we are not a currently encoding item, we can just be cancelled */ - [fHBController rescanQueueItemToMainWindow:row]; - } -} - - -#pragma mark - -#pragma mark Animate Encoding Item - -//------------------------------------------------------------------------------------ -// Starts animating the job icon of the currently processing job in the queue outline -// view. -//------------------------------------------------------------------------------------ -- (void) startAnimatingCurrentWorkingEncodeInQueue -{ - if (!fAnimationTimer) - fAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0/12.0 // 1/12 because there are 6 images in the animation cycle - target:self - selector:@selector(animateWorkingEncodeInQueue:) - userInfo:nil - repeats:YES] retain]; -} - -//------------------------------------------------------------------------------------ -// If a job is currently processing, its job icon in the queue outline view is -// animated to its next state. -//------------------------------------------------------------------------------------ -- (void) animateWorkingEncodeInQueue:(NSTimer*)theTimer -{ - if (fWorkingCount > 0) - { - fAnimationIndex++; - fAnimationIndex %= 6; // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below. - [self animateWorkingEncodeIconInQueue]; - } -} + // since we are not a currently encoding item, we can just be cancelled + HBJob *item = [[[self.jobs[row] representedObject] copy] autorelease]; + [self.controller rescanJobToMainWindow:item]; -/* We need to make sure we denote only working encodes even for multiple instances */ -- (void) animateWorkingEncodeIconInQueue -{ - NSInteger row = fEncodingQueueItem; /// need to set to fEncodingQueueItem - NSInteger col = [fOutlineView columnWithIdentifier: @"icon"]; - if (row != -1 && col != -1) - { - NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row]; - [fOutlineView setNeedsDisplayInRect: frame]; + // Now that source is loaded and settings applied, delete the queue item from the queue + [self removeQueueItemAtIndex:row]; } } -//------------------------------------------------------------------------------------ -// Stops animating the job icon of the currently processing job in the queue outline -// view. -//------------------------------------------------------------------------------------ -- (void) stopAnimatingCurrentJobGroupInQueue -{ - if (fAnimationTimer && [fAnimationTimer isValid]) - { - [fAnimationTimer invalidate]; - [fAnimationTimer release]; - fAnimationTimer = nil; - } -} - - -#pragma mark - - -- (void)moveObjectsInArray:(NSMutableArray *)array fromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex -{ - NSUInteger index = [indexSet lastIndex]; - NSUInteger aboveInsertIndexCount = 0; - - while (index != NSNotFound) - { - NSUInteger removeIndex; - - if (index >= insertIndex) - { - removeIndex = index + aboveInsertIndexCount; - aboveInsertIndexCount++; - } - else - { - removeIndex = index; - insertIndex--; - } - - id object = [array[removeIndex] retain]; - [array removeObjectAtIndex:removeIndex]; - [array insertObject:object atIndex:insertIndex]; - [object release]; - - index = [indexSet indexLessThanIndex:index]; - } -} - - #pragma mark - #pragma mark NSOutlineView delegate - - (id)outlineView:(NSOutlineView *)fOutlineView child:(NSInteger)index ofItem:(id)item { if (item == nil) - return fJobGroups[index]; + { + return self.jobs[index]; + } // We are only one level deep, so we can't be asked about children NSAssert (NO, @"HBQueueController outlineView:child:ofItem: can't handle nested items."); @@ -639,23 +1102,27 @@ // Our outline view has no levels, so number of children will be zero for all // top-level items. if (item == nil) - return [fJobGroups count]; + { + return [self.jobs count]; + } else + { return 0; + } } - (void)outlineViewItemDidCollapse:(NSNotification *)notification { id item = [notification userInfo][@"NSObject"]; - NSInteger row = [fOutlineView rowForItem:item]; - [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]]; + NSInteger row = [self.outlineView rowForItem:item]; + [self.outlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]]; } - (void)outlineViewItemDidExpand:(NSNotification *)notification { id item = [notification userInfo][@"NSObject"]; - NSInteger row = [fOutlineView rowForItem:item]; - [fOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]]; + NSInteger row = [self.outlineView rowForItem:item]; + [self.outlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]]; } - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item @@ -685,403 +1152,24 @@ } } -- (void)initStyles -{ - // Attributes - ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain]; - [ps setHeadIndent: 40.0]; - [ps setParagraphSpacing: 1.0]; - [ps setTabStops:@[]]; // clear all tabs - [ps addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]]; - - detailAttr = [@{NSFontAttributeName: [NSFont systemFontOfSize:10.0], - NSParagraphStyleAttributeName: ps} retain]; - - detailBoldAttr = [@{NSFontAttributeName: [NSFont boldSystemFontOfSize:10.0], - NSParagraphStyleAttributeName: ps} retain]; - - titleAttr = [@{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]], - NSParagraphStyleAttributeName: ps} retain]; - - shortHeightAttr = [@{NSFontAttributeName: [NSFont systemFontOfSize:2.0]} retain]; -} - - (id)outlineView:(NSOutlineView *)fOutlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { - if ([[tableColumn identifier] isEqualToString:@"desc"]) + if ([tableColumn.identifier isEqualToString:@"desc"]) { HBJob *job = item; - if ([descriptions objectForKey:@(job.hash)]) - { - return [descriptions objectForKey:@(job.hash)]; - } - - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - /* Below should be put into a separate method but I am way too f'ing lazy right now */ - NSMutableAttributedString *finalString = [[NSMutableAttributedString alloc] initWithString: @""]; - - /* First line, we should strip the destination path and just show the file name and add the title num and chapters (if any) */ - NSString *summaryInfo; - - NSString *titleString = [NSString stringWithFormat:@"Title %d", job.titleIdx]; - - NSString *startStopString = @""; - if (job.range.type == HBRangeTypeChapters) - { - // Start Stop is chapters - startStopString = (job.range.chapterStart == job.range.chapterStop) ? - [NSString stringWithFormat:@"Chapter %d", job.range.chapterStart] : - [NSString stringWithFormat:@"Chapters %d through %d", job.range.chapterStart, job.range.chapterStop]; - } - else if (job.range.type == HBRangeTypeSeconds) + if (self.descriptions[@(job.hash)]) { - // Start Stop is seconds - startStopString = [NSString stringWithFormat:@"Seconds %d through %d", job.range.secondsStart, job.range.secondsStop]; - } - else if (job.range.type == HBRangeTypeFrames) - { - // Start Stop is Frames - startStopString = [NSString stringWithFormat:@"Frames %d through %d", job.range.frameStart, job.range.frameStop]; - } - NSString *passesString = @""; - // check to see if our first subtitle track is Foreign Language Search, in which case there is an in depth scan - if (job.subtitles.tracks.count && [job.subtitles.tracks[0][@"keySubTrackIndex"] intValue] == -1) - { - passesString = [passesString stringByAppendingString:@"1 Foreign Language Search Pass - "]; - } - if (job.video.qualityType == 1 || job.video.twoPass == NO) - { - passesString = [passesString stringByAppendingString:@"1 Video Pass"]; - } - else - { - if (job.video.turboTwoPass == YES) - { - passesString = [passesString stringByAppendingString:@"2 Video Passes First Turbo"]; - } - else - { - passesString = [passesString stringByAppendingString:@"2 Video Passes"]; - } + return self.descriptions[@(job.hash)]; } - [finalString appendString:[NSString stringWithFormat:@"%@", job.fileURL.path.lastPathComponent] withAttributes:titleAttr]; + NSAttributedString *finalString = job.attributedDescription; + self.descriptions[@(job.hash)] = finalString;; - /* lets add the output file name to the title string here */ - NSString *outputFilenameString = job.destURL.lastPathComponent; - - summaryInfo = [NSString stringWithFormat: @" (%@, %@, %@) -> %@", titleString, startStopString, passesString, outputFilenameString]; - - [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr]; - - // Insert a short-in-height line to put some white space after the title - [finalString appendString:@"\n" withAttributes:shortHeightAttr]; - // End of Title Stuff - - // Second Line (Preset Name) - [finalString appendString: @"Preset: " withAttributes:detailBoldAttr]; - [finalString appendString:[NSString stringWithFormat:@"%@\n", job.presetName] withAttributes:detailAttr]; - - // Third Line (Format Summary) - NSString *audioCodecSummary = @""; // This seems to be set by the last track we have available... - // Lets also get our audio track detail since we are going through the logic for use later - - NSMutableArray *audioDetails = [NSMutableArray array]; - BOOL autoPassthruPresent = NO; - - for (HBAudioTrack *audioTrack in job.audio.tracks) - { - if (audioTrack.enabled) - { - audioCodecSummary = [NSString stringWithFormat: @"%@", audioTrack.codec[keyAudioCodecName]]; - NSNumber *drc = audioTrack.drc; - NSNumber *gain = audioTrack.gain; - NSString *detailString = [NSString stringWithFormat: @"%@ Encoder: %@ Mixdown: %@ SampleRate: %@(khz) Bitrate: %@(kbps), DRC: %@, Gain: %@", - audioTrack.track[keyAudioTrackName], - audioTrack.codec[keyAudioCodecName], - audioTrack.mixdown[keyAudioMixdownName], - audioTrack.sampleRate[keyAudioSampleRateName], - audioTrack.bitRate[keyAudioBitrateName], - (0.0 < [drc floatValue]) ? (NSObject *)drc : (NSObject *)@"Off", - (0.0 != [gain floatValue]) ? (NSObject *)gain : (NSObject *)@"Off" - ]; - [audioDetails addObject: detailString]; - // check if we have an Auto Passthru output track - if ([audioTrack.codec[keyAudioCodecName] isEqualToString: @"Auto Passthru"]) - { - autoPassthruPresent = YES; - } - } - } - - NSString *jobFormatInfo; - if (job.chaptersEnabled) - jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video %@ Audio, Chapter Markers\n", - @(hb_container_get_name(job.container)), @(hb_video_encoder_get_name(job.video.encoder)), audioCodecSummary]; - else - jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video %@ Audio\n", - @(hb_container_get_name(job.container)), @(hb_video_encoder_get_name(job.video.encoder)), audioCodecSummary]; - - [finalString appendString: @"Format: " withAttributes:detailBoldAttr]; - [finalString appendString: jobFormatInfo withAttributes:detailAttr]; - - // Optional String for muxer options - NSMutableString *containerOptions = [NSMutableString stringWithString:@""]; - if ((job.container & HB_MUX_MASK_MP4) && job.mp4HttpOptimize) - { - [containerOptions appendString:@" - Web optimized"]; - } - if ((job.container & HB_MUX_MASK_MP4) && job.mp4iPodCompatible) - { - [containerOptions appendString:@" - iPod 5G support"]; - } - if ([containerOptions hasPrefix:@" - "]) - { - [containerOptions deleteCharactersInRange:NSMakeRange(0, 3)]; - } - if (containerOptions.length) - { - [finalString appendString:@"Container Options: " withAttributes:detailBoldAttr]; - [finalString appendString:containerOptions withAttributes:detailAttr]; - [finalString appendString:@"\n" withAttributes:detailAttr]; - } - - // Fourth Line (Destination Path) - [finalString appendString: @"Destination: " withAttributes:detailBoldAttr]; - [finalString appendString: job.destURL.path withAttributes:detailAttr]; - [finalString appendString:@"\n" withAttributes:detailAttr]; - - - // Fifth Line Picture Details - NSString *pictureInfo = [NSString stringWithFormat:@"%@", job.picture.summary]; - if (job.picture.keepDisplayAspect) - { - pictureInfo = [pictureInfo stringByAppendingString:@" Keep Aspect Ratio"]; - } - [finalString appendString:@"Picture: " withAttributes:detailBoldAttr]; - [finalString appendString:pictureInfo withAttributes:detailAttr]; - [finalString appendString:@"\n" withAttributes:detailAttr]; - - /* Optional String for Picture Filters */ - if (job.filters.summary.length) - { - NSString *pictureFilters = [NSString stringWithFormat:@"%@", job.filters.summary]; - [finalString appendString:@"Filters: " withAttributes:detailBoldAttr]; - [finalString appendString:pictureFilters withAttributes:detailAttr]; - [finalString appendString:@"\n" withAttributes:detailAttr]; - } - - // Sixth Line Video Details - NSString * videoInfo = [NSString stringWithFormat:@"Encoder: %@", @(hb_video_encoder_get_name(job.video.encoder))]; - - // for framerate look to see if we are using vfr detelecine - if (job.video.frameRate == 0) - { - if (job.video.frameRateMode == 0) - { - // we are using same as source with vfr detelecine - videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (Variable Frame Rate)", videoInfo]; - } - else - { - // we are using a variable framerate without dropping frames - videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (Constant Frame Rate)", videoInfo]; - } - } - else - { - // we have a specified, constant framerate - if (job.video.frameRateMode == 0) - { - videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (Peak Frame Rate)", videoInfo, @(hb_video_framerate_get_name(job.video.frameRate))]; - } - else - { - videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (Constant Frame Rate)", videoInfo, @(hb_video_framerate_get_name(job.video.frameRate))]; - } - } - - - if (job.video.qualityType == 0) // ABR - { - videoInfo = [NSString stringWithFormat:@"%@ Bitrate: %d(kbps)", videoInfo, job.video.avgBitrate]; - } - else // CRF - { - videoInfo = [NSString stringWithFormat:@"%@ Constant Quality: %.2f", videoInfo ,job.video.quality]; - } - - [finalString appendString: @"Video: " withAttributes:detailBoldAttr]; - [finalString appendString: videoInfo withAttributes:detailAttr]; - [finalString appendString:@"\n" withAttributes:detailAttr]; - - - if (job.video.encoder == HB_VCODEC_X264 || job.video.encoder == HB_VCODEC_X265) - { - // we are using x264/x265 - NSString *encoderPresetInfo = @""; - if (job.video.advancedOptions) - { - // we are using the old advanced panel - if (job.video.videoOptionExtra.length) - { - encoderPresetInfo = [encoderPresetInfo stringByAppendingString:job.video.videoOptionExtra]; - } - else - { - encoderPresetInfo = [encoderPresetInfo stringByAppendingString:@"default settings"]; - } - } - else - { - // we are using the x264 system - encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@"Preset: %@", job.video.preset]]; - if (job.video.tune.length) - { - encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Tune: %@", job.video.tune]]; - } - if (job.video.videoOptionExtra.length) - { - encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Options: %@", job.video.videoOptionExtra]]; - } - if (job.video.profile.length) - { - encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Profile: %@", job.video.profile]]; - } - if (job.video.level.length) - { - encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Level: %@", job.video.level]]; - } - } - [finalString appendString: @"Encoder Options: " withAttributes:detailBoldAttr]; - [finalString appendString: encoderPresetInfo withAttributes:detailAttr]; - [finalString appendString:@"\n" withAttributes:detailAttr]; - } - else - { - // we are using libavcodec - NSString *lavcInfo = @""; - if (job.video.videoOptionExtra.length) - { - lavcInfo = [lavcInfo stringByAppendingString:job.video.videoOptionExtra]; - } - else - { - lavcInfo = [lavcInfo stringByAppendingString: @"default settings"]; - } - [finalString appendString: @"Encoder Options: " withAttributes:detailBoldAttr]; - [finalString appendString: lavcInfo withAttributes:detailAttr]; - [finalString appendString:@"\n" withAttributes:detailAttr]; - } - - - // Seventh Line Audio Details - int audioDetailCount = 0; - for (NSString *anAudioDetail in audioDetails) { - audioDetailCount++; - if (anAudioDetail.length) { - [finalString appendString: [NSString stringWithFormat: @"Audio Track %d ", audioDetailCount] withAttributes: detailBoldAttr]; - [finalString appendString: anAudioDetail withAttributes: detailAttr]; - [finalString appendString: @"\n" withAttributes: detailAttr]; - } - } - - // Eigth Line Auto Passthru Details - // only print Auto Passthru settings if we have an Auro Passthru output track - if (autoPassthruPresent == YES) - { - NSString *autoPassthruFallback = @"", *autoPassthruCodecs = @""; - HBAudioDefaults *audioDefaults = job.audio.defaults; - autoPassthruFallback = [autoPassthruFallback stringByAppendingString:@(hb_audio_encoder_get_name(audioDefaults.encoderFallback))]; - if (audioDefaults.allowAACPassthru) - { - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"AAC"]; - } - if (audioDefaults.allowAC3Passthru) - { - if (autoPassthruCodecs.length) - { - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; - } - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"AC3"]; - } - if (audioDefaults.allowDTSHDPassthru) - { - if (autoPassthruCodecs.length) - { - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; - } - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"DTS-HD"]; - } - if (audioDefaults.allowDTSPassthru) - { - if (autoPassthruCodecs.length) - { - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; - } - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"DTS"]; - } - if (audioDefaults.allowMP3Passthru) - { - if (autoPassthruCodecs.length) - { - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; - } - autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"MP3"]; - } - [finalString appendString: @"Auto Passthru Codecs: " withAttributes: detailBoldAttr]; - if (autoPassthruCodecs.length) - { - [finalString appendString: autoPassthruCodecs withAttributes: detailAttr]; - } - else - { - [finalString appendString: @"None" withAttributes: detailAttr]; - } - [finalString appendString: @"\n" withAttributes: detailAttr]; - [finalString appendString: @"Auto Passthru Fallback: " withAttributes: detailBoldAttr]; - [finalString appendString: autoPassthruFallback withAttributes: detailAttr]; - [finalString appendString: @"\n" withAttributes: detailAttr]; - } - - // Ninth Line Subtitle Details - int i = 0; - for (NSDictionary *track in job.subtitles.tracks) - { - // Ignore the none track. - if (i == job.subtitles.tracks.count - 1) - { - continue; - } - - /* remember that index 0 of Subtitles can contain "Foreign Audio Search*/ - [finalString appendString: @"Subtitle: " withAttributes:detailBoldAttr]; - [finalString appendString: track[@"keySubTrackName"] withAttributes:detailAttr]; - if ([track[@"keySubTrackForced"] intValue] == 1) - { - [finalString appendString: @" - Forced Only" withAttributes:detailAttr]; - } - if ([track[@"keySubTrackBurned"] intValue] == 1) - { - [finalString appendString: @" - Burned In" withAttributes:detailAttr]; - } - if ([track[@"keySubTrackDefault"] intValue] == 1) - { - [finalString appendString: @" - Default" withAttributes:detailAttr]; - } - [finalString appendString:@"\n" withAttributes:detailAttr]; - i++; - } - - [pool release]; - - [descriptions setObject:finalString forKey:@(job.hash)]; - - return [finalString autorelease]; + return finalString; } - else if ([[tableColumn identifier] isEqualToString:@"icon"]) + else if ([tableColumn.identifier isEqualToString:@"icon"]) { HBJob *job = item; if (job.state == HBJobStateCompleted) @@ -1090,7 +1178,7 @@ } else if (job.state == HBJobStateWorking) { - return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]]; + return [NSImage imageNamed:@"EncodeWorking0"]; } else if (job.state == HBJobStateCanceled) { @@ -1106,10 +1194,13 @@ return @""; } } -/* This method inserts the proper action icons into the far right of the queue window */ + +/** + * This method inserts the proper action icons into the far right of the queue window + */ - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item { - if ([[tableColumn identifier] isEqualToString:@"desc"]) + if ([tableColumn.identifier isEqualToString:@"desc"]) { // nb: The "desc" column is currently an HBImageAndTextCell. However, we are longer // using the image portion of the cell so we could switch back to a regular NSTextFieldCell. @@ -1117,7 +1208,7 @@ // Set the image here since the value returned from outlineView:objectValueForTableColumn: didn't specify the image part [cell setImage:nil]; } - else if ([[tableColumn identifier] isEqualToString:@"action"]) + else if ([tableColumn.identifier isEqualToString:@"action"]) { [cell setEnabled: YES]; BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView); @@ -1152,43 +1243,40 @@ { // By default, the disclosure image gets centered vertically in the cell. We want // always at the top. - if ([outlineView isItemExpanded: item]) + if ([outlineView isItemExpanded:item]) + { [cell setImagePosition: NSImageAbove]; + } else + { [cell setImagePosition: NSImageOnly]; + } } #pragma mark - #pragma mark NSOutlineView delegate (dragging related) -//------------------------------------------------------------------------------------ -// NSTableView delegate -//------------------------------------------------------------------------------------ - - - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard { // Dragging is only allowed of the pending items. - if ([items[0] state] != HBJobStateReady) // 2 is pending + if ([items[0] state] != HBJobStateReady) { return NO; } - + // Don't retain since this is just holding temporaral drag information, and it is //only used during a drag! We could put this in the pboard actually. - fDraggedNodes = items; - + self.dragNodesArray = items; + // Provide data for our custom type, and simple NSStrings. [pboard declareTypes:@[DragDropSimplePboardType] owner:self]; - + // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!. [pboard setData:[NSData data] forType:DragDropSimplePboardType]; - + return YES; } - -/* This method is used to validate the drops. */ - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index { // Don't allow dropping ONTO an item since they can't really contain any children. @@ -1201,20 +1289,19 @@ // Don't allow dropping INTO an item since they can't really contain any children. if (item != nil) { - index = [fOutlineView rowForItem: item] + 1; + index = [self.outlineView rowForItem: item] + 1; item = nil; } - - // NOTE: Should we allow dropping a pending job *above* the - // finished or already encoded jobs ? + // We do not let the user drop a pending job before or *above* // already finished or currently encoding jobs. - if (index <= fEncodingQueueItem) + NSInteger encodingIndex = [self.jobs indexOfObject:self.currentJob]; + if (index <= encodingIndex) { return NSDragOperationNone; - index = MAX (index, fEncodingQueueItem); + index = MAX (index, encodingIndex); } - + [outlineView setDropItem:item dropChildIndex:index]; return NSDragOperationGeneric; } @@ -1223,14 +1310,17 @@ { NSMutableIndexSet *moveItems = [NSMutableIndexSet indexSet]; - for (id obj in fDraggedNodes) - [moveItems addIndex:[fJobGroups indexOfObject:obj]]; + for (id obj in self.dragNodesArray) + { + [moveItems addIndex:[self.jobs indexOfObject:obj]]; + } // Successful drop, we use moveObjectsInQueueArray:... in fHBController // to properly rearrange the queue array, save it to plist and then send it back here. // since Controller.mm is handling all queue array manipulation. - // We *could do this here, but I think we are better served keeping that code together. - [fHBController moveObjectsInQueueArray:fJobGroups fromIndexes:moveItems toIndex: index]; + // We could do this here, but I think we are better served keeping that code together. + [self moveObjectsInQueueArray:self.jobs fromIndexes:moveItems toIndex: index]; + return YES; } diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj index e840a408a..30476e490 100644 --- a/macosx/HandBrake.xcodeproj/project.pbxproj +++ b/macosx/HandBrake.xcodeproj/project.pbxproj @@ -153,6 +153,7 @@ A9935213196F38A70069C6B7 /* ChaptersTitles.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9935211196F38A70069C6B7 /* ChaptersTitles.xib */; }; A9AA447A1970664A00D7DEFC /* HBUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = A9AA44791970664A00D7DEFC /* HBUtilities.m */; }; A9BB0F2719A0ECE40079F1C1 /* HBHUDButtonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A9BB0F2619A0ECE40079F1C1 /* HBHUDButtonCell.m */; }; + A9BC24C91A69293E007DC41A /* HBAttributedStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A9BC24C81A69293E007DC41A /* HBAttributedStringAdditions.m */; }; A9C0DB85197E7B0000DF55B3 /* SubtitlesDefaults.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9C0DB83197E7B0000DF55B3 /* SubtitlesDefaults.xib */; }; A9C9F88919A733FE00DC8923 /* HBHUDView.m in Sources */ = {isa = PBXBuildFile; fileRef = A9C9F88819A733FE00DC8923 /* HBHUDView.m */; }; A9CF25F11990D62C0023F727 /* Presets.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9CF25EF1990D62C0023F727 /* Presets.xib */; }; @@ -173,6 +174,7 @@ A9E1468316BC2AD800C307BC /* prev-p.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A9E1467F16BC2AD800C307BC /* prev-p.pdf */; }; A9E2FD271A21BC4A000E8D3F /* HBAddPresetController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E2FD251A21BC4A000E8D3F /* HBAddPresetController.m */; }; A9E2FD2B1A21BC6F000E8D3F /* AddPreset.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9E2FD291A21BC6F000E8D3F /* AddPreset.xib */; }; + A9E66D701A67A2A8007B641D /* HBDistributedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E66D6F1A67A2A8007B641D /* HBDistributedArray.m */; }; A9EA43681A2210C400785E95 /* HBQueueOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = A9EA43671A2210C400785E95 /* HBQueueOutlineView.m */; }; A9F2EB6F196F12C800066546 /* Audio.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9F2EB6D196F12C800066546 /* Audio.xib */; }; A9F472891976B7F30009EC65 /* HBSubtitlesDefaultsController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9F472871976B7F30009EC65 /* HBSubtitlesDefaultsController.m */; }; @@ -419,6 +421,8 @@ A9B34D74197696FE00871B7D /* DiskArbitration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskArbitration.framework; path = System/Library/Frameworks/DiskArbitration.framework; sourceTree = SDKROOT; }; A9BB0F2519A0ECE40079F1C1 /* HBHUDButtonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBHUDButtonCell.h; sourceTree = "<group>"; }; A9BB0F2619A0ECE40079F1C1 /* HBHUDButtonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBHUDButtonCell.m; sourceTree = "<group>"; }; + A9BC24C71A69293E007DC41A /* HBAttributedStringAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAttributedStringAdditions.h; sourceTree = "<group>"; }; + A9BC24C81A69293E007DC41A /* HBAttributedStringAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAttributedStringAdditions.m; sourceTree = "<group>"; }; A9C0DB84197E7B0000DF55B3 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = SubtitlesDefaults.xib; sourceTree = "<group>"; }; A9C9F88719A733FE00DC8923 /* HBHUDView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBHUDView.h; sourceTree = "<group>"; }; A9C9F88819A733FE00DC8923 /* HBHUDView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBHUDView.m; sourceTree = "<group>"; }; @@ -449,6 +453,8 @@ A9E2FD241A21BC4A000E8D3F /* HBAddPresetController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAddPresetController.h; sourceTree = "<group>"; }; A9E2FD251A21BC4A000E8D3F /* HBAddPresetController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAddPresetController.m; sourceTree = "<group>"; }; A9E2FD2A1A21BC6F000E8D3F /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = AddPreset.xib; sourceTree = "<group>"; }; + A9E66D6E1A67A2A8007B641D /* HBDistributedArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBDistributedArray.h; sourceTree = "<group>"; }; + A9E66D6F1A67A2A8007B641D /* HBDistributedArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBDistributedArray.m; sourceTree = "<group>"; }; A9EA43661A2210C400785E95 /* HBQueueOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBQueueOutlineView.h; sourceTree = "<group>"; }; A9EA43671A2210C400785E95 /* HBQueueOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBQueueOutlineView.m; sourceTree = "<group>"; }; A9F2EB6E196F12C800066546 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Audio.xib; sourceTree = "<group>"; }; @@ -678,6 +684,8 @@ A9AA44791970664A00D7DEFC /* HBUtilities.m */, 273F209D14ADBE670021BE6D /* HBOutputRedirect.h */, 273F209E14ADBE670021BE6D /* HBOutputRedirect.m */, + A9E66D6E1A67A2A8007B641D /* HBDistributedArray.h */, + A9E66D6F1A67A2A8007B641D /* HBDistributedArray.m */, A98C29C21977B10600AF5DED /* HBLanguagesSelection.h */, A98C29C31977B10600AF5DED /* HBLanguagesSelection.m */, A9B34D711976844500871B7D /* UI Views */, @@ -993,6 +1001,8 @@ A9B34D711976844500871B7D /* UI Views */ = { isa = PBXGroup; children = ( + A9BC24C71A69293E007DC41A /* HBAttributedStringAdditions.h */, + A9BC24C81A69293E007DC41A /* HBAttributedStringAdditions.m */, A9EA43661A2210C400785E95 /* HBQueueOutlineView.h */, A9EA43671A2210C400785E95 /* HBQueueOutlineView.m */, 46AB433315F98A2B009C0961 /* DockTextField.h */, @@ -1081,7 +1091,7 @@ 273F1FE014AD9DA40021BE6D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0620; }; buildConfigurationList = 273F1FE314AD9DA40021BE6D /* Build configuration list for PBXProject "HandBrake" */; compatibilityVersion = "Xcode 3.2"; @@ -1217,6 +1227,7 @@ A9DEC8771A23C88D00C79B48 /* HBVideo.m in Sources */, A9523937199A6AAE00588AEF /* HBFilters.m in Sources */, A9AA447A1970664A00D7DEFC /* HBUtilities.m in Sources */, + A9BC24C91A69293E007DC41A /* HBAttributedStringAdditions.m in Sources */, 273F20AC14ADBE670021BE6D /* Controller.m in Sources */, 273F20AD14ADBE670021BE6D /* HBAdvancedController.m in Sources */, 273F20AE14ADBE670021BE6D /* HBAudioTrack.m in Sources */, @@ -1231,6 +1242,7 @@ A93FD4751A62ABE800A6AC43 /* HBAudio.m in Sources */, A971281F1A2C75180088C076 /* HBTitle.m in Sources */, 273F20B514ADBE670021BE6D /* HBPreferencesController.m in Sources */, + A9E66D701A67A2A8007B641D /* HBDistributedArray.m in Sources */, A9DC6C52196F04F6002AE6B4 /* HBSubtitlesController.m in Sources */, A9F472891976B7F30009EC65 /* HBSubtitlesDefaultsController.m in Sources */, A9CF25F41990D64E0023F727 /* HBPreset.m in Sources */, @@ -1437,6 +1449,8 @@ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_SHADOW = NO; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; @@ -1477,6 +1491,8 @@ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_SHADOW = NO; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; diff --git a/macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [DEBUG].xcscheme b/macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [DEBUG].xcscheme index 3ec110ffa..a4579d8bc 100644 --- a/macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [DEBUG].xcscheme +++ b/macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [DEBUG].xcscheme @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <Scheme - LastUpgradeVersion = "0610" + LastUpgradeVersion = "0620" version = "1.3"> <BuildAction parallelizeBuildables = "YES" @@ -62,7 +62,8 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" allowLocationSimulation = "YES"> - <BuildableProductRunnable> + <BuildableProductRunnable + runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "273F203814ADBC200021BE6D" @@ -80,7 +81,8 @@ useCustomWorkingDirectory = "NO" buildConfiguration = "debug" debugDocumentVersioning = "YES"> - <BuildableProductRunnable> + <BuildableProductRunnable + runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "273F203814ADBC200021BE6D" diff --git a/macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [RELEASE].xcscheme b/macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [RELEASE].xcscheme index 59c647285..d51e11b37 100644 --- a/macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [RELEASE].xcscheme +++ b/macosx/HandBrake.xcodeproj/xcshareddata/xcschemes/HandBrake [RELEASE].xcscheme @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <Scheme - LastUpgradeVersion = "0610" + LastUpgradeVersion = "0620" version = "1.3"> <BuildAction parallelizeBuildables = "YES" @@ -62,7 +62,8 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" allowLocationSimulation = "YES"> - <BuildableProductRunnable> + <BuildableProductRunnable + runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "273F203814ADBC200021BE6D" @@ -80,7 +81,8 @@ useCustomWorkingDirectory = "NO" buildConfiguration = "release" debugDocumentVersioning = "YES"> - <BuildableProductRunnable> + <BuildableProductRunnable + runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "273F203814ADBC200021BE6D" |