/* $Id: Controller.mm,v 1.79 2005/11/04 19:41:32 titer Exp $
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#include
#import "Controller.h"
#import "HBOutputPanelController.h"
#import "HBPreferencesController.h"
#import "HBDVDDetector.h"
#import "HBPresetsManager.h"
#import "HBPreset.h"
#import "HBPreviewController.h"
#import "HBDockTile.h"
#import "HBUtilities.h"
#import "HBPresetsViewController.h"
#import "HBAddPresetController.h"
#import "HBAudioDefaults.h"
#import "HBSubtitlesDefaults.h"
#import "HBCore.h"
NSString *HBContainerChangedNotification = @"HBContainerChangedNotification";
NSString *keyContainerTag = @"keyContainerTag";
NSString *HBTitleChangedNotification = @"HBTitleChangedNotification";
NSString *keyTitleTag = @"keyTitleTag";
// DockTile update freqency in total percent increment
#define dockTileUpdateFrequency 0.1f
@interface HBController ()
// The current selected preset.
@property (nonatomic, retain) HBPreset *selectedPreset;
@property (nonatomic) BOOL customPreset;
/**
* The HBCore used for scanning.
*/
@property (nonatomic, retain) HBCore *core;
/**
* The HBCore used for encoding.
*/
@property (nonatomic, retain) HBCore *queueCore;
@end
/*******************************
* HBController implementation *
*******************************/
@implementation HBController
- (instancetype)init
{
self = [super init];
if (self)
{
// Register the defaults preferences
[HBPreferencesController registerUserDefaults];
/* Check for and create the App Support Preview directory if necessary */
NSString *previewDirectory = [[HBUtilities appSupportPath] stringByAppendingPathComponent:@"Previews"];
if (![[NSFileManager defaultManager] fileExistsAtPath:previewDirectory])
{
[[NSFileManager defaultManager] createDirectoryAtPath:previewDirectory
withIntermediateDirectories:YES
attributes:nil
error:NULL];
}
// Inits the controllers
outputPanel = [[HBOutputPanelController alloc] init];
fPictureController = [[HBPictureController alloc] init];
fQueueController = [[HBQueueController alloc] init];
// we init the HBPresetsManager class
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];
[dockTile updateDockIcon:-1.0 withETA:@""];
// Lets report the HandBrake version number here to the activity log and text log file
NSString *versionStringFull = [[NSString stringWithFormat:@"Handbrake Version: %@",
[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]]
stringByAppendingString:[NSString stringWithFormat: @" (%@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]]];
[HBUtilities writeToActivityLog: "%s", [versionStringFull UTF8String]];
// Optional dvd nav UseDvdNav
[HBCore setDVDNav:[[[NSUserDefaults standardUserDefaults] objectForKey:@"UseDvdNav"] boolValue]];
int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue];
// Init libhb
_core = [[HBCore alloc] initWithLoggingLevel:loggingLevel];
// Init a separate instance of libhb for user scanning and setting up jobs
_queueCore = [[HBCore alloc] initWithLoggingLevel:loggingLevel];
// Registers the observers to the cores notifications.
[self registerScanCoreNotifications];
[self registerQueueCoreNotifications];
// Set the Growl Delegate
[GrowlApplicationBridge setGrowlDelegate: self];
[fPictureController setDelegate:self];
[fPictureController setHandle:self.core.hb_handle];
[fQueueController setHandle:self.queueCore.hb_handle];
[fQueueController setHBController:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(autoSetM4vExtension:) name:HBMixdownChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateMp4Checkboxes:) name:HBVideoEncoderChangedNotification object:nil];
dockIconProgress = 0;
}
return self;
}
- (void) applicationDidFinishLaunching: (NSNotification *) notification
{
/* 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. */
if (hbInstanceNum == 1)
{
NSString *PreviewDirectory = [[HBUtilities appSupportPath] stringByAppendingPathComponent:@"Previews"];
NSError *error;
NSArray *files = [ [NSFileManager defaultManager] contentsOfDirectoryAtPath: PreviewDirectory error: &error ];
for( NSString *file in files )
{
if( ![file isEqual: @"."] && ![file isEqual: @".."] )
{
[ [NSFileManager defaultManager] removeItemAtPath: [ PreviewDirectory stringByAppendingPathComponent: file ] error: &error ];
if( error )
{
//an error occurred
[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];
// Open queue window now if it was visible when HB was closed
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"QueueWindowIsOpen"])
[self showQueueWindow:nil];
[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)
{
/* 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
*/
if (hbInstanceNum > 1)
{
NSAlert *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)
{
NSString *alertTitle;
if (fWorkingCount > 0)
{
alertTitle = [NSString stringWithFormat:
NSLocalizedString(@"HandBrake Has Detected %d Previously Encoding Item(s) and %d Pending Item(s) In Your Queue.", @""),
fWorkingCount,fPendingCount];
}
else
{
alertTitle = [NSString stringWithFormat:
NSLocalizedString(@"HandBrake Has Detected %d Pending Item(s) In Your Queue.", @""),
fPendingCount];
}
NSAlert *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];
if (self.core.state != HBStateScanning && !titleLoaded)
{
// 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
{
if (self.core.state != HBStateScanning && !titleLoaded)
{
// 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];
}
}
}
currentQueueEncodeNameString = @"";
}
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
{
[self openFile:filenames.firstObject];
[NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
}
- (void)openFile:(NSString *)filePath
{
if (self.core.state != HBStateScanning)
{
[browsedSourceDisplayName release];
browsedSourceDisplayName = [filePath.lastPathComponent retain];
[self performScan:filePath scanTitleNum:0];
}
}
#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
// We only want filenames (and so folders too)
- (NSDragOperation)draggingEntered:(id )sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType])
{
NSArray *paths = [pboard propertyListForType:NSFilenamesPboardType];
return paths.count == 1 ? NSDragOperationGeneric : NSDragOperationNone;
}
return NSDragOperationNone;
}
// This method is doing the job after the drag & drop operation has been validated by [self draggingEntered] and OSX
- (BOOL)performDragOperation:(id )sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType])
{
NSArray *paths = [pboard propertyListForType:NSFilenamesPboardType];
[self openFile:paths.firstObject];
}
return YES;
}
#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)
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"Are you sure you want to quit HandBrake?", nil)];
[alert setInformativeText:NSLocalizedString(@"If you quit HandBrake your current encode will be reloaded into your queue at next launch. Do you want to quit anyway?", nil)];
[alert addButtonWithTitle:NSLocalizedString(@"Quit", nil)];
[alert addButtonWithTitle:NSLocalizedString(@"Don't Quit", nil)];
[alert setAlertStyle:NSCriticalAlertStyle];
NSInteger result = [alert runModal];
[alert release];
if (result == NSAlertFirstButtonReturn)
{
return NSTerminateNow;
}
else
{
return NSTerminateCancel;
}
}
// Warn if items still in the queue
else if (fPendingCount > 0)
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"Are you sure you want to quit HandBrake?", nil)];
[alert setInformativeText:NSLocalizedString(@"There are pending encodes in your queue. Do you want to quit anyway?",nil)];
[alert addButtonWithTitle:NSLocalizedString(@"Quit", nil)];
[alert addButtonWithTitle:NSLocalizedString(@"Don't Quit", nil)];
[alert setAlertStyle:NSCriticalAlertStyle];
NSInteger result = [alert runModal];
[alert release];
if (result == NSAlertFirstButtonReturn)
{
return NSTerminateNow;
}
else
{
return NSTerminateCancel;
}
}
return NSTerminateNow;
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
[presetManager savePresets];
[presetManager release];
[self closeQueueFSEvent];
[currentQueueEncodeNameString release];
[browsedSourceDisplayName release];
[outputPanel release];
[fQueueController release];
[fPreviewController release];
[fPictureController release];
[dockTile release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.core = nil;
self.queueCore = nil;
[HBCore closeGlobal];
}
- (void) awakeFromNib
{
/* For 64 bit builds, the threaded animation in the progress
* indicators conflicts with the animation in the advanced tab
* for reasons not completely clear. jbrjake found a note in the
* 10.5 dev notes regarding this possiblility. It was also noted
* that unless specified, setUsesThreadedAnimation defaults to true.
* So, at least for now we set the indicator animation to NO for
* both the scan and regular progress indicators for both 32 and 64 bit
* as it test out fine on both and there is no reason our progress indicators
* should require their own thread.
*/
[fScanIndicator setUsesThreadedAnimation:NO];
[fRipIndicator setUsesThreadedAnimation:NO];
// Presets initialization
[self checkBuiltInsForUpdates];
[self buildPresetsMenu];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(buildPresetsMenu) name:HBPresetsChangedNotification object:nil];
[fPresetDrawer setDelegate:self];
NSSize drawerSize = NSSizeFromString([[NSUserDefaults standardUserDefaults]
stringForKey:@"HBDrawerSize"]);
if (drawerSize.width)
{
[fPresetDrawer setContentSize: drawerSize];
}
/* Show/Dont Show Presets drawer upon launch based
on user preference DefaultPresetsDrawerShow */
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBDefaultPresetsDrawerShow"])
{
[fPresetDrawer open:self];
}
/* Setup the start / stop popup */
[fEncodeStartStopPopUp removeAllItems];
[fEncodeStartStopPopUp addItemWithTitle: @"Chapters"];
[fEncodeStartStopPopUp addItemWithTitle: @"Seconds"];
[fEncodeStartStopPopUp addItemWithTitle: @"Frames"];
// Align the start / stop widgets with the chapter popups
NSPoint startPoint = [fSrcChapterStartPopUp frame].origin;
startPoint.y += 2;
NSPoint endPoint = [fSrcChapterEndPopUp frame].origin;
endPoint.y += 2;
[fSrcTimeStartEncodingField setFrameOrigin:startPoint];
[fSrcTimeEndEncodingField setFrameOrigin:endPoint];
[fSrcFrameStartEncodingField setFrameOrigin:startPoint];
[fSrcFrameEndEncodingField setFrameOrigin:endPoint];
/* Destination box*/
NSMenuItem *menuItem;
[fDstFormatPopUp removeAllItems];
for (const hb_container_t *container = hb_container_get_next(NULL);
container != NULL;
container = hb_container_get_next(container))
{
NSString *title = nil;
if (container->format & HB_MUX_MASK_MP4)
{
title = @"MP4 File";
}
else if (container->format & HB_MUX_MASK_MKV)
{
title = @"MKV File";
}
else
{
title = [NSString stringWithUTF8String:container->name];
}
menuItem = [[fDstFormatPopUp menu] addItemWithTitle:title
action:nil
keyEquivalent:@""];
[menuItem setTag:container->format];
}
// select the first container
[fDstFormatPopUp selectItemAtIndex:0];
[self formatPopUpChanged:nil];
[fDstFile2Field setStringValue:[NSString
stringWithFormat:@"%@/Desktop/Movie.mp4",
NSHomeDirectory()]];
/* Set Auto Crop to On at launch */
[fPictureController setAutoCrop:YES];
/* Bottom */
[fStatusField setStringValue: @""];
/* Register HBController's Window as a receiver for files/folders drag & drop operations */
[fWindow registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
// Set up the preset drawer
fPresetsView = [[HBPresetsViewController alloc] initWithPresetManager:presetManager];
[fPresetDrawer setContentView:[fPresetsView view]];
fPresetsView.delegate = self;
[[fPresetDrawer contentView] setAutoresizingMask:( NSViewWidthSizable | NSViewHeightSizable )];
// Set up the chapters title view
fChapterTitlesController = [[HBChapterTitlesController alloc] init];
[fChaptersTitlesTab setView:[fChapterTitlesController view]];
// setup the subtitles view
fSubtitlesViewController = [[HBSubtitlesController alloc] init];
[fSubtitlesTab setView:[fSubtitlesViewController view]];
// setup the audio controller
fAudioController = [[HBAudioController alloc] init];
[fAudioTab setView:[fAudioController view]];
// setup the advanced view controller
fAdvancedOptions = [[HBAdvancedController alloc] init];
[fAdvancedTab setView:[fAdvancedOptions view]];
// setup the video view controller
fVideoController = [[HBVideoController alloc] init];
fVideoController.fAdvancedOptions = fAdvancedOptions;
fVideoController.fHBController = self;
[fVideoTab setView:[fVideoController view]];
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
forKeyPath:@"values.HBShowAdvancedTab"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:NULL];
[fWindow recalculateKeyViewLoop];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == NULL)
{
if ([keyPath isEqualToString:@"values.HBShowAdvancedTab"])
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBShowAdvancedTab"])
{
if (![[fMainTabView tabViewItems] containsObject:fAdvancedTab])
{
[fMainTabView insertTabViewItem:fAdvancedTab atIndex:3];
[fAdvancedTab release];
}
}
else
{
[fAdvancedTab retain];
[fMainTabView removeTabViewItem:fAdvancedTab];
}
}
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void) enableUI: (BOOL) b
{
NSControl * controls[] =
{
fSrcTitleField, fSrcTitlePopUp,
fSrcChapterStartPopUp, fSrcChapterToField,
fSrcChapterEndPopUp, fSrcDuration1Field, fSrcDuration2Field,
fDstFormatField, fDstFormatPopUp, fDstFile1Field, fDstFile2Field,
fDstBrowseButton, fSrcAngleLabel, fSrcAnglePopUp,
fDstMp4HttpOptFileCheck, fDstMp4iPodFileCheck,
fEncodeStartStopPopUp, fSrcTimeStartEncodingField,
fSrcTimeEndEncodingField, fSrcFrameStartEncodingField,
fSrcFrameEndEncodingField,
};
for (unsigned i = 0; i < (sizeof(controls) / sizeof(NSControl*)); i++)
{
if ([[controls[i] className] isEqualToString: @"NSTextField"])
{
NSTextField *tf = (NSTextField*)controls[i];
if (![tf isBezeled])
{
[tf setTextColor: (b ?
[NSColor controlTextColor] :
[NSColor disabledControlTextColor])];
continue;
}
}
[controls[i] setEnabled: b];
}
[fPresetsView setUIEnabled:b];
[fVideoController setUIEnabled:b];
[fAudioController setUIEnabled:b];
[fSubtitlesViewController setUIEnabled:b];
[fChapterTitlesController setUIEnabled: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:nil];
[[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)
{
if (QueueFileArray[currentQueueEncodeIndex][@"SubtitleList"] &&
[[QueueFileArray[currentQueueEncodeIndex][@"SubtitleList"] 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)
{
NSString *pathOfFinishedEncode;
// Get the output file name for the finished encode
pathOfFinishedEncode = [[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"DestinationPath"];
// 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:pathOfFinishedEncode];
}
// Send to tagger
[self sendToMetaX:pathOfFinishedEncode];
// since we have successfully completed an encode, we increment the queue counter
[self incrementQueueItemDone:currentQueueEncodeIndex];
}
}];
}
#pragma mark -
#pragma mark Toolbar
- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
{
SEL action = toolbarItem.action;
if (self.core)
{
if (self.core.state == HBStateScanning)
{
if (action == @selector(browseSources:))
{
[toolbarItem setImage: [NSImage imageNamed: @"stopencode"]];
[toolbarItem setLabel: @"Cancel Scan"];
[toolbarItem setPaletteLabel: @"Cancel Scanning"];
[toolbarItem setToolTip: @"Cancel Scanning Source"];
return YES;
}
if (action == @selector(Rip:) || action == @selector(addToQueue:))
return NO;
}
else
{
if (action == @selector(browseSources:))
{
[toolbarItem setImage: [NSImage imageNamed: @"source"]];
[toolbarItem setLabel: @"Source"];
[toolbarItem setPaletteLabel: @"Source"];
[toolbarItem setToolTip: @"Choose Video Source"];
return YES;
}
}
HBState queueState = self.queueCore.state;
if (queueState == HBStateWorking || queueState == HBStateSearching || queueState == HBStateMuxing)
{
if (action == @selector(Rip:))
{
[toolbarItem setImage: [NSImage imageNamed: @"stopencode"]];
[toolbarItem setLabel: @"Stop"];
[toolbarItem setPaletteLabel: @"Stop"];
[toolbarItem setToolTip: @"Stop Encoding"];
return YES;
}
if (action == @selector(Pause:))
{
[toolbarItem setImage: [NSImage imageNamed: @"pauseencode"]];
[toolbarItem setLabel: @"Pause"];
[toolbarItem setPaletteLabel: @"Pause Encoding"];
[toolbarItem setToolTip: @"Pause Encoding"];
return YES;
}
if (SuccessfulScan)
{
if (action == @selector(addToQueue:))
return YES;
if (action == @selector(showPicturePanel:))
return YES;
if (action == @selector(showPreviewWindow:))
return YES;
}
}
else if (queueState == HBStatePaused)
{
if (action == @selector(Pause:))
{
[toolbarItem setImage: [NSImage imageNamed: @"encode"]];
[toolbarItem setLabel: @"Resume"];
[toolbarItem setPaletteLabel: @"Resume Encoding"];
[toolbarItem setToolTip: @"Resume Encoding"];
return YES;
}
if (action == @selector(Rip:))
return YES;
if (action == @selector(addToQueue:))
return YES;
if (action == @selector(showPicturePanel:))
return YES;
if (action == @selector(showPreviewWindow:))
return YES;
}
else if (queueState == HBStateScanning)
{
return NO;
}
else if (queueState == HBStateWorkDone || queueState == HBStateScanDone || SuccessfulScan)
{
if (action == @selector(Rip:))
{
[toolbarItem setImage: [NSImage imageNamed: @"encode"]];
if (hb_count(self.core.hb_handle) > 0)
[toolbarItem setLabel: @"Start Queue"];
else
[toolbarItem setLabel: @"Start"];
[toolbarItem setPaletteLabel: @"Start Encoding"];
[toolbarItem setToolTip: @"Start Encoding"];
return YES;
}
if (action == @selector(addToQueue:))
return YES;
if (action == @selector(showPicturePanel:))
return YES;
if (action == @selector(showPreviewWindow:))
return YES;
}
}
/* If there are any pending queue items, make sure the start/stop button is active */
if (action == @selector(Rip:) && fPendingCount > 0)
return YES;
if (action == @selector(showQueueWindow:))
return YES;
if (action == @selector(toggleDrawer:))
return YES;
if (action == @selector(browseSources:))
return YES;
if (action == @selector(showDebugOutputPanel:))
return YES;
return NO;
}
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
SEL action = [menuItem action];
if (self.queueCore)
{
HBState queueState = self.queueCore.state;
if (action == @selector(addToQueue:) || action == @selector(addAllTitlesToQueue:) || action == @selector(showPicturePanel:) || action == @selector(showAddPresetPanel:))
return SuccessfulScan && [fWindow attachedSheet] == nil;
if (action == @selector(selectDefaultPreset:))
return [fWindow attachedSheet] == nil;
if (action == @selector(Pause:))
{
if (queueState == HBStateWorking)
{
if(![[menuItem title] isEqualToString:@"Pause Encoding"])
[menuItem setTitle:@"Pause Encoding"];
return YES;
}
else if (queueState == HBStatePaused)
{
if(![[menuItem title] isEqualToString:@"Resume Encoding"])
[menuItem setTitle:@"Resume Encoding"];
return YES;
}
else
return NO;
}
if (action == @selector(Rip:))
{
if (queueState == HBStateWorking || queueState == HBStateMuxing || queueState == HBStatePaused)
{
if(![[menuItem title] isEqualToString:@"Stop Encoding"])
[menuItem setTitle:@"Stop Encoding"];
return YES;
}
else if (SuccessfulScan)
{
if(![[menuItem title] isEqualToString:@"Start Encoding"])
[menuItem setTitle:@"Start Encoding"];
return [fWindow attachedSheet] == nil;
}
else
return NO;
}
if (action == @selector(browseSources:))
{
if (self.core.state == HBStateScanning)
return NO;
else
return [fWindow attachedSheet] == nil;
}
if (action == @selector(selectPresetFromMenu:))
{
if (!self.customPreset && [menuItem.representedObject isEqualTo:self.selectedPreset])
{
[menuItem setState:NSOnState];
}
else
{
[menuItem setState:NSOffState];
}
}
}
return YES;
}
#pragma mark -
#pragma mark Encode Done Actions
// register a test notification and make
// it enabled by default
#define SERVICE_NAME @"Encode Done"
- (NSDictionary *)registrationDictionaryForGrowl
{
NSDictionary *registrationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithObjects:SERVICE_NAME,nil], GROWL_NOTIFICATIONS_ALL,
[NSArray arrayWithObjects:SERVICE_NAME,nil], GROWL_NOTIFICATIONS_DEFAULT,
nil];
return registrationDictionary;
}
-(void)showGrowlDoneNotification:(NSString *) filePath
{
/* This end of encode action is called as each encode rolls off of the queue */
/* Setup the Growl stuff */
NSString * finishedEncode = filePath;
/* strip off the path to just show the file name */
finishedEncode = [finishedEncode lastPathComponent];
NSString * growlMssg = [NSString stringWithFormat: @"your HandBrake encode %@ is done!",finishedEncode];
[GrowlApplicationBridge
notifyWithTitle:@"Put down that cocktail…"
description:growlMssg
notificationName:SERVICE_NAME
iconData:nil
priority:0
isSticky:1
clickContext:nil];
}
-(void)sendToMetaX:(NSString *) filePath
{
/* 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 \"", filePath, @"\")"]];
[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
/*Opens the source browse window, called from Open Source widgets */
- (IBAction)browseSources:(id)sender
{
if (self.core.state == HBStateScanning)
{
[self.core cancelScan];
return;
}
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setCanChooseFiles:YES];
[panel setCanChooseDirectories:YES];
NSURL *sourceDirectory;
if ([[NSUserDefaults standardUserDefaults] URLForKey:@"LastSourceDirectoryURL"])
{
sourceDirectory = [[NSUserDefaults standardUserDefaults] URLForKey:@"LastSourceDirectoryURL"];
}
else
{
sourceDirectory = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop"];
}
/* we open up the browse sources sheet here and call for browseSourcesDone after the sheet is closed
* to evaluate whether we want to specify a title, we pass the sender in the contextInfo variable
*/
[panel setDirectoryURL:sourceDirectory];
[panel beginSheetModalForWindow:fWindow completionHandler:
^(NSInteger result) {
[self browseSourcesDone:panel returnCode:(int)result contextInfo:sender];
}];
}
- (void) browseSourcesDone: (NSOpenPanel *) sheet
returnCode: (int) returnCode contextInfo: (void *) contextInfo
{
/* we convert the sender content of contextInfo back into a variable called sender
* mostly just for consistency for evaluation later
*/
id sender = (id)contextInfo;
/* User selected a file to open */
if( returnCode == NSOKButton )
{
// We started a new scan, so set SuccessfulScan to no for now.
SuccessfulScan = NO;
/* Free display name allocated previously by this code */
[browsedSourceDisplayName release];
NSURL *scanURL = [[sheet URLs] objectAtIndex: 0];
/* we set the last searched source directory in the prefs here */
NSURL *sourceDirectory = [scanURL URLByDeletingLastPathComponent];
[[NSUserDefaults standardUserDefaults] setURL:sourceDirectory forKey:@"LastSourceDirectoryURL"];
/* we order out sheet, which is the browse window as we need to open
* the title selection sheet right away
*/
[sheet orderOut: self];
if (sender == fOpenSourceTitleMMenu || [[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
{
/* We put the chosen source path in the source display text field for the
* source title selection sheet in which the user specifies the specific title to be
* scanned as well as the short source name in fSrcDsplyNameTitleScan just for display
* purposes in the title panel
*/
/* Full Path */
[fScanSrcTitlePathField setStringValue:[scanURL path]];
NSString *displayTitlescanSourceName;
if ([[scanURL lastPathComponent] isEqualToString: @"VIDEO_TS"])
{
/* If VIDEO_TS Folder is chosen, choose its parent folder for the source display name
we have to use the title->path value so we get the proper name of the volume if a physical dvd is the source*/
displayTitlescanSourceName = [[scanURL URLByDeletingLastPathComponent] lastPathComponent];
}
else
{
/* if not the VIDEO_TS Folder, we can assume the chosen folder is the source name */
displayTitlescanSourceName = [scanURL lastPathComponent];
}
/* we set the source display name in the title selection dialogue */
[fSrcDsplyNameTitleScan setStringValue:displayTitlescanSourceName];
/* we set the attempted scans display name for main window to displayTitlescanSourceName*/
browsedSourceDisplayName = [displayTitlescanSourceName retain];
/* We show the actual sheet where the user specifies the title to be scanned
* as we are going to do a title specific scan
*/
[self showSourceTitleScanPanel:nil];
}
else
{
/* We are just doing a standard full source scan, so we specify "0" to libhb */
NSURL *url = [[sheet URLs] objectAtIndex: 0];
/* We check to see if the chosen file at path is a package */
if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:[url path]])
{
[HBUtilities writeToActivityLog: "trying to open a package at: %s", [[url path] UTF8String]];
/* We check to see if this is an .eyetv package */
if ([[url pathExtension] isEqualToString: @"eyetv"])
{
[HBUtilities writeToActivityLog:"trying to open eyetv package"];
/* We're looking at an EyeTV package - try to open its enclosed
.mpg media file */
browsedSourceDisplayName = [[[url URLByDeletingPathExtension] lastPathComponent] retain];
NSString *mpgname;
NSUInteger n = [[[url path] stringByAppendingString: @"/"]
completePathIntoString: &mpgname caseSensitive: YES
matchesIntoArray: nil
filterTypes: [NSArray arrayWithObject: @"mpg"]];
if (n > 0)
{
/* Found an mpeg inside the eyetv package, make it our scan path
and call performScan on the enclosed mpeg */
[HBUtilities writeToActivityLog:"found mpeg in eyetv package"];
[self performScan:mpgname scanTitleNum:0];
}
else
{
/* We did not find an mpeg file in our package, so we do not call performScan */
[HBUtilities writeToActivityLog:"no valid mpeg in eyetv package"];
}
}
/* We check to see if this is a .dvdmedia package */
else if ([[url pathExtension] isEqualToString: @"dvdmedia"])
{
/* path IS a package - but dvdmedia packages can be treaded like normal directories */
browsedSourceDisplayName = [[[url URLByDeletingPathExtension] lastPathComponent] retain];
[HBUtilities writeToActivityLog:"trying to open dvdmedia package"];
[self performScan:[url path] scanTitleNum:0];
}
else
{
/* The package is not an eyetv package, try to open it anyway */
browsedSourceDisplayName = [[url lastPathComponent] retain];
[HBUtilities writeToActivityLog:"not a known to package"];
[self performScan:[url path] scanTitleNum:0];
}
}
else // path is not a package, so we treat it as a dvd parent folder or VIDEO_TS folder
{
/* path is not a package, so we call perform scan directly on our file */
if ([[url lastPathComponent] isEqualToString: @"VIDEO_TS"])
{
[HBUtilities writeToActivityLog:"trying to open video_ts folder (video_ts folder chosen)"];
/* If VIDEO_TS Folder is chosen, choose its parent folder for the source display name*/
browsedSourceDisplayName = [[[url URLByDeletingLastPathComponent] lastPathComponent] retain];
}
else
{
[HBUtilities writeToActivityLog:"trying to open video_ts folder (parent directory chosen)"];
/* if not the VIDEO_TS Folder, we can assume the chosen folder is the source name */
/* make sure we remove any path extension as this can also be an '.mpg' file */
browsedSourceDisplayName = [[url lastPathComponent] retain];
}
applyQueueToScan = NO;
[self performScan:[url path] scanTitleNum:0];
}
}
}
}
/* Here we open the title selection sheet where we can specify an exact title to be scanned */
- (IBAction) showSourceTitleScanPanel: (id) sender
{
/* We default the title number to be scanned to "0" which results in a full source scan, unless the
* user changes it
*/
[fScanSrcTitleNumField setStringValue: @"0"];
/* Show the panel */
[NSApp beginSheet:fScanSrcTitlePanel modalForWindow:fWindow modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
}
- (IBAction) closeSourceTitleScanPanel: (id) sender
{
[NSApp endSheet: fScanSrcTitlePanel];
[fScanSrcTitlePanel orderOut: self];
if(sender == fScanSrcTitleOpenButton)
{
/* We setup the scan status in the main window to indicate a source title scan */
[fSrcDVD2Field setStringValue: @"Opening a new source title…"];
[fScanIndicator setHidden: NO];
[fScanHorizontalLine setHidden: YES];
[fScanIndicator setIndeterminate: YES];
[fScanIndicator startAnimation: nil];
/* We use the performScan method to actually perform the specified scan passing the path and the title
* to be scanned
*/
applyQueueToScan = NO;
[self performScan:[fScanSrcTitlePathField stringValue] scanTitleNum:[fScanSrcTitleNumField intValue]];
}
}
/* Here we actually tell hb_scan to perform the source scan, using the path to source and title number*/
- (void)performScan:(NSString *)scanPath scanTitleNum:(NSInteger)scanTitleNum
{
// Save the current settings
if (titleLoaded) {
self.selectedPreset = [self createPresetFromCurrentSettings];
titleLoaded = NO;
}
// Notify anyone interested (audio/subtitles/chapters controller) that there's no title
fTitle = NULL;
[fPictureController setTitle:NULL];
[[NSNotificationCenter defaultCenter] postNotification:
[NSNotification notificationWithName: HBTitleChangedNotification
object: self
userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
[NSData dataWithBytesNoCopy: &fTitle length: sizeof(fTitle) freeWhenDone: NO], keyTitleTag,
nil]]];
[self enableUI: NO];
NSError *outError = NULL;
NSURL *fileURL = [NSURL fileURLWithPath:scanPath];
BOOL suppressWarning = [[NSUserDefaults standardUserDefaults] boolForKey:@"suppresslibdvdcss"];
// Check if we can scan the source and if there is any warning.
BOOL canScan = [self.core canScan:fileURL error:&outError];
// Notify the user that we don't support removal of copy proteciton.
if (canScan && [outError code] == 101 && !suppressWarning)
{
// Only show the user this warning once. They may be using a solution we don't know about. Notifying them each time is annoying.
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"suppresslibdvdcss"];
// Compatible libdvdcss not found
[HBUtilities writeToActivityLog: "libdvdcss.2.dylib not found for decrypting physical dvd"];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Please note that HandBrake does not support the removal of copy-protection from DVD Discs. You can if you wish install libdvdcss or any other 3rd party software for this function."];
[alert setInformativeText:@"Videolan.org provides libdvdcss if you are not currently using another solution."];
[alert addButtonWithTitle:@"Get libdvdcss.pkg"];
[alert addButtonWithTitle:@"Cancel Scan"];
[alert addButtonWithTitle:@"Attempt Scan Anyway"];
[NSApp requestUserAttention:NSCriticalRequest];
NSInteger status = [alert runModal];
[alert release];
if (status == NSAlertFirstButtonReturn)
{
/* User chose to go download vlc (as they rightfully should) so we send them to the vlc site */
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://download.videolan.org/libdvdcss/1.2.12/macosx/"]];
canScan = NO;
}
else if (status == NSAlertSecondButtonReturn)
{
/* User chose to cancel the scan */
[HBUtilities writeToActivityLog: "Cannot open physical dvd, scan cancelled"];
canScan = NO;
}
else
{
/* User chose to override our warning and scan the physical dvd anyway, at their own peril. on an encrypted dvd this produces massive log files and fails */
[HBUtilities writeToActivityLog:"User overrode copy-protection warning - trying to open physical dvd without decryption"];
}
}
if (canScan)
{
int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
int min_title_duration_seconds = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MinTitleScanSeconds"] intValue];
[self.core scan:fileURL
titleNum:scanTitleNum
previewsNum:hb_num_previews minTitleDuration:min_title_duration_seconds];
}
}
- (IBAction) showNewScan:(id)sender
{
hb_title_set_t * title_set;
hb_title_t * title = NULL;
int feature_title=0; // Used to store the main feature title
title_set = hb_get_title_set(self.core.hb_handle);
if( !hb_list_count( title_set->list_title ) )
{
/* We display a message if a valid dvd source was not chosen */
[fSrcDVD2Field setStringValue: @"No Valid Source Found"];
SuccessfulScan = NO;
// Notify PictureController that there's no title
[fPictureController setTitle:NULL];
// Notify anyone interested (video/audio/subtitles/chapters controller) that there's no title
[[NSNotificationCenter defaultCenter] postNotification:
[NSNotification notificationWithName: HBTitleChangedNotification
object: self
userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
[NSData dataWithBytesNoCopy: &fTitle length: sizeof(fTitle) freeWhenDone: NO], keyTitleTag,
nil]]];
}
else
{
if (applyQueueToScan == YES)
{
/* 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 (applyQueueToScan == NO)
{
[HBUtilities writeToActivityLog: "showNewScan: This is a new source item scan"];
}
else
{
[HBUtilities writeToActivityLog: "showNewScan: cannot grok scan status"];
}
[[fWindow toolbar] validateVisibleItems];
[fSrcTitlePopUp removeAllItems];
for( int i = 0; i < hb_list_count( title_set->list_title ); i++ )
{
title = (hb_title_t *) hb_list_item( title_set->list_title, i );
currentSource = [NSString stringWithUTF8String: title->name];
/*Set DVD Name at top of window with the browsedSourceDisplayName grokked right before -performScan */
if (!browsedSourceDisplayName)
{
browsedSourceDisplayName = @"NoNameDetected";
}
[fSrcDVD2Field setStringValue:browsedSourceDisplayName];
// use the correct extension based on the container
int videoContainer = (int)[[fDstFormatPopUp selectedItem] tag];
const char *ext = hb_container_get_default_extension(videoContainer);
/* If its a queue rescan for edit, get the queue item output path */
/* if not, its a new source scan. */
/* Check to see if the last destination has been set,use if so, if not, use Desktop */
if (applyQueueToScan == YES)
{
[fDstFile2Field setStringValue: [NSString stringWithFormat:@"%@", [[QueueFileArray objectAtIndex:fqueueEditRescanItemNum] objectForKey:@"DestinationPath"]]];
}
else if ([[NSUserDefaults standardUserDefaults] stringForKey:@"LastDestinationDirectory"])
{
[fDstFile2Field setStringValue: [NSString stringWithFormat:
@"%@/%@.%s", [[NSUserDefaults standardUserDefaults] stringForKey:@"LastDestinationDirectory"],[browsedSourceDisplayName stringByDeletingPathExtension],ext]];
}
else
{
[fDstFile2Field setStringValue: [NSString stringWithFormat:
@"%@/Desktop/%@.%s", NSHomeDirectory(),[browsedSourceDisplayName stringByDeletingPathExtension],ext]];
}
// set m4v extension if necessary - do not override user-specified .mp4 extension
if ((videoContainer & HB_MUX_MASK_MP4) && (applyQueueToScan != YES))
{
[self autoSetM4vExtension:sender];
}
/* See if this is the main feature according to libhb */
if (title->index == title_set->feature)
{
feature_title = i;
}
if( title->type == HB_BD_TYPE )
{
[fSrcTitlePopUp addItemWithTitle: [NSString
stringWithFormat: @"%@ %d (%05d.MPLS) - %02dh%02dm%02ds",
currentSource, title->index, title->playlist,
title->hours, title->minutes, title->seconds]];
}
else
{
[fSrcTitlePopUp addItemWithTitle: [NSString
stringWithFormat: @"%@ %d - %02dh%02dm%02ds",
currentSource, title->index,
title->hours, title->minutes, title->seconds]];
}
}
/* if we are a stream, select the first title */
if (title && (title->type == HB_STREAM_TYPE || title->type == HB_FF_STREAM_TYPE))
{
[fSrcTitlePopUp selectItemAtIndex: 0];
}
else
{
/* if not then select the main feature title */
[fSrcTitlePopUp selectItemAtIndex: feature_title];
}
SuccessfulScan = YES;
[self titlePopUpChanged:nil];
titleLoaded = YES;
[self encodeStartStopPopUpChanged:nil];
[self enableUI: YES];
// Open preview window now if it was visible when HB was closed
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"PreviewWindowIsOpen"])
[self showPreviewWindow:nil];
// Open picture sizing window now if it was visible when HB was closed
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"PictureSizeWindowIsOpen"])
[self showPicturePanel:nil];
if (applyQueueToScan == YES)
{
/* we are a rescan of an existing queue item and need to apply the queued settings to the scan */
[HBUtilities writeToActivityLog: "showNewScan: calling applyQueueSettingsToMainWindow"];
[self applyQueueSettingsToMainWindow:nil];
}
}
}
#pragma mark -
#pragma mark New Output Destination
- (IBAction)browseFile:(id)sender
{
// Open a panel to let the user choose and update the text field
NSSavePanel * panel = [NSSavePanel savePanel];
// We get the current file name and path from the destination field here
NSString *destinationDirectory = fDstFile2Field.stringValue.stringByDeletingLastPathComponent;
[panel setDirectoryURL:[NSURL fileURLWithPath:destinationDirectory]];
[panel setNameFieldStringValue:[[fDstFile2Field stringValue] lastPathComponent]];
[panel beginSheetModalForWindow:fWindow completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
fDstFile2Field.stringValue = panel.URL.path;
// Save this path to the prefs so that on next browse destination window it opens there
NSString *destinationDirectory = [[fDstFile2Field stringValue] stringByDeletingLastPathComponent];
[[NSUserDefaults standardUserDefaults] setObject:destinationDirectory forKey:@"LastDestinationDirectory"];
}
}];
}
#pragma mark -
#pragma mark Main Window Control
- (IBAction) openMainWindow: (id) sender
{
[fWindow makeKeyAndOrderFront:nil];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
{
if( !flag ) {
[fWindow makeKeyAndOrderFront:nil];
}
return YES;
}
- (NSSize) drawerWillResizeContents:(NSDrawer *) drawer toSize:(NSSize) contentSize {
[[NSUserDefaults standardUserDefaults] setObject:NSStringFromSize( contentSize ) forKey:@"HBDrawerSize"];
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[])
{
HBController *hb = (HBController *)clientCallBackInfo;
[hb reloadQueue];
}
- (void)initQueueFSEvent
{
/* 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
);
CFRelease(pathsToWatch);
/* Create the stream before calling this. */
FSEventStreamScheduleWithRunLoop(QueueStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
FSEventStreamStart(QueueStream);
}
- (void)closeQueueFSEvent
{
FSEventStreamStop(QueueStream);
FSEventStreamInvalidate(QueueStream);
FSEventStreamRelease(QueueStream);
}
- (void)loadQueueFile
{
/* 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.plist"] 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];
}
QueueFileArray = [[NSMutableArray alloc] initWithContentsOfFile:QueueFile];
/* 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];
}
}
}
- (void)reloadQueue
{
[HBUtilities writeToActivityLog:"Queue reloaded"];
NSMutableArray * tempQueueArray = [[NSMutableArray alloc] initWithContentsOfFile:QueueFile];
[QueueFileArray setArray:tempQueueArray];
[tempQueueArray release];
/* Send Fresh QueueFileArray to fQueueController to update queue window */
[fQueueController setQueueArray: QueueFileArray];
[self getQueueStats];
}
- (void)addQueueFileItem
{
[QueueFileArray addObject:[self createQueueFileItem]];
[self saveQueueFileItem];
}
- (void) removeQueueFileItem:(NSUInteger) queueItemToRemove
{
[QueueFileArray removeObjectAtIndex:queueItemToRemove];
[self saveQueueFileItem];
}
- (void)saveQueueFileItem
{
[QueueFileArray writeToFile:QueueFile atomically:YES];
[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;
/* We use a number system to set the encode status of the queue item
* in controller.mm
* 0 == already encoded
* 1 == is being encoded
* 2 == is yet to be encoded
* 3 == cancelled
*/
int i = 0;
for (NSDictionary *thisQueueDict in QueueFileArray)
{
if ([[thisQueueDict objectForKey:@"Status"] intValue] == 1) // being encoded
{
fWorkingCount++;
/* check to see if we are the instance doing this encoding */
if ([thisQueueDict objectForKey:@"EncodingPID"] && [[thisQueueDict objectForKey:@"EncodingPID"] intValue] == pidNum)
{
currentQueueEncodeIndex = i;
}
}
if ([[thisQueueDict objectForKey:@"Status"] intValue] == 2) // pending
{
fPendingCount++;
}
i++;
}
/* 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];
}
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 (id tempObject in QueueFileArray)
{
NSDictionary *thisQueueDict = tempObject;
if ([[thisQueueDict objectForKey:@"Status"] intValue] == 2 && nextPendingFound == NO) // pending
{
nextPendingFound = YES;
nextPendingIndex = [QueueFileArray indexOfObject: tempObject];
[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
{
NSMutableArray *tempArray;
tempArray = [NSMutableArray array];
/* we look here to see if the preset is we move on to the next one */
for (id tempObject in QueueFileArray)
{
/* We want to keep any queue item that is pending or was previously being encoded */
if ([[tempObject objectForKey:@"Status"] intValue] == 1 || [[tempObject objectForKey:@"Status"] intValue] == 2)
{
/* If the queue item is marked as "encoding" (1)
* then change its status back to pending (2) which effectively
* puts it back into the queue to be encoded
*/
if ([[tempObject objectForKey:@"Status"] intValue] == 1)
{
[tempObject setObject:[NSNumber numberWithInt: 2] forKey:@"Status"];
}
[tempArray addObject:tempObject];
}
}
[QueueFileArray setArray:tempArray];
[self saveQueueFileItem];
}
/* 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;
tempArray = [NSMutableArray array];
/* we look here to see if the preset is we move on to the next one */
for (id tempObject 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 ([[tempObject objectForKey:@"Status"] intValue] == 0 || [[tempObject objectForKey:@"Status"] intValue] == 3)
{
[tempArray addObject:tempObject];
}
}
[QueueFileArray removeObjectsInArray:tempArray];
[self saveQueueFileItem];
}
/* This method will clear the queue of all encodes. effectively creating an empty queue */
- (void) clearQueueAllItems
{
NSMutableArray *tempArray;
tempArray = [NSMutableArray array];
/* we look here to see if the preset is we move on to the next one */
for (id tempObject in QueueFileArray)
{
[tempArray addObject:tempObject];
}
[QueueFileArray removeObjectsInArray:tempArray];
[self saveQueueFileItem];
}
/* This method will duplicate prepareJob however into the
* queue .plist instead of into the job structure so it can
* be recalled later */
- (NSDictionary *)createQueueFileItem
{
NSMutableDictionary *queueFileJob = [[NSMutableDictionary alloc] init];
hb_list_t * list = hb_get_titles(self.core.hb_handle);
hb_title_t * title = (hb_title_t *) hb_list_item( list,
(int)[fSrcTitlePopUp indexOfSelectedItem] );
hb_job_t * job = title->job;
/* We use a number system to set the encode status of the queue item
* 0 == already encoded
* 1 == is being encoded
* 2 == is yet to be encoded
* 3 == cancelled
*/
[queueFileJob setObject:[NSNumber numberWithInt:2] forKey:@"Status"];
/* Source and Destination Information */
[queueFileJob setObject:[NSString stringWithUTF8String: title->path] forKey:@"SourcePath"];
[queueFileJob setObject:[[fSrcDVD2Field stringValue] lastPathComponent] forKey:@"SourceName"];
[queueFileJob setObject:[NSNumber numberWithInt:title->index] forKey:@"TitleNumber"];
[queueFileJob setObject:[NSNumber numberWithInteger:[fSrcAnglePopUp indexOfSelectedItem] + 1] forKey:@"TitleAngle"];
/* Determine and set a variable to tell hb what start and stop times to use (chapters, seconds or frames) */
if( [fEncodeStartStopPopUp indexOfSelectedItem] == 0 )
{
[queueFileJob setObject:[NSNumber numberWithInt:0] forKey:@"fEncodeStartStop"];
}
else if ([fEncodeStartStopPopUp indexOfSelectedItem] == 1)
{
[queueFileJob setObject:[NSNumber numberWithInt:1] forKey:@"fEncodeStartStop"];
}
else if ([fEncodeStartStopPopUp indexOfSelectedItem] == 2)
{
[queueFileJob setObject:[NSNumber numberWithInt:2] forKey:@"fEncodeStartStop"];
}
/* Chapter encode info */
[queueFileJob setObject:[NSNumber numberWithInteger:[fSrcChapterStartPopUp indexOfSelectedItem] + 1] forKey:@"ChapterStart"];
[queueFileJob setObject:[NSNumber numberWithInteger:[fSrcChapterEndPopUp indexOfSelectedItem] + 1] forKey:@"ChapterEnd"];
/* Time (pts) encode info */
[queueFileJob setObject:[NSNumber numberWithInt:[fSrcTimeStartEncodingField intValue]] forKey:@"StartSeconds"];
[queueFileJob setObject:[NSNumber numberWithInt:[fSrcTimeEndEncodingField intValue] - [fSrcTimeStartEncodingField intValue]] forKey:@"StopSeconds"];
/* Frame number encode info */
[queueFileJob setObject:[NSNumber numberWithInt:[fSrcFrameStartEncodingField intValue]] forKey:@"StartFrame"];
[queueFileJob setObject:[NSNumber numberWithInt:[fSrcFrameEndEncodingField intValue] - [fSrcFrameStartEncodingField intValue]] forKey:@"StopFrame"];
/* The number of seek points equals the number of seconds announced in the title as that is our current granularity */
int title_duration_seconds = (title->hours * 3600) + (title->minutes * 60) + (title->seconds);
[queueFileJob setObject:[NSNumber numberWithInt:title_duration_seconds] forKey:@"SourceTotalSeconds"];
[queueFileJob setObject:[fDstFile2Field stringValue] forKey:@"DestinationPath"];
/* Lets get the preset info if there is any */
[queueFileJob setObject:[fPresetSelectedDisplay stringValue] forKey:@"PresetName"];
[queueFileJob setObject:[fDstFormatPopUp titleOfSelectedItem] forKey:@"FileFormat"];
/* Chapter Markers*/
/* If we are encoding by chapters and we have only one chapter or a title without chapters, set chapter markers to off.
Leave them on if we're doing point to point encoding, as libhb supports chapters when doing p2p. */
if ([fEncodeStartStopPopUp indexOfSelectedItem] == 0 &&
[fSrcChapterStartPopUp indexOfSelectedItem] == [fSrcChapterEndPopUp indexOfSelectedItem])
{
[queueFileJob setObject:[NSNumber numberWithInt:0] forKey:@"ChapterMarkers"];
}
else
{
[queueFileJob setObject:@(fChapterTitlesController.createChapterMarkers) forKey:@"ChapterMarkers"];
}
/* We need to get the list of chapter names to put into an array and store
* in our queue, so they can be reapplied in prepareJob when this queue
* item comes up if Chapter Markers is set to on.
*/
[queueFileJob setObject:fChapterTitlesController.chapterTitlesArray forKey:@"ChapterNames"];
/* Mux mp4 with http optimization */
[queueFileJob setObject:[NSNumber numberWithInteger:[fDstMp4HttpOptFileCheck state]] forKey:@"Mp4HttpOptimize"];
/* Add iPod uuid atom */
[queueFileJob setObject:[NSNumber numberWithInteger:[fDstMp4iPodFileCheck state]] forKey:@"Mp4iPodCompatible"];
/* Codecs */
/* Video encoder */
[fVideoController prepareVideoForQueueFileJob:queueFileJob];
/* Picture Sizing */
[queueFileJob setObject:[NSNumber numberWithInt:fTitle->job->width] forKey:@"PictureWidth"];
[queueFileJob setObject:[NSNumber numberWithInt:fTitle->job->height] forKey:@"PictureHeight"];
[queueFileJob setObject:[NSNumber numberWithInt:fTitle->job->anamorphic.keep_display_aspect] forKey:@"PictureKeepRatio"];
[queueFileJob setObject:[NSNumber numberWithInt:fTitle->job->anamorphic.mode] forKey:@"PicturePAR"];
[queueFileJob setObject:[NSNumber numberWithInt:fTitle->job->modulus] forKey:@"PictureModulus"];
[queueFileJob setObject:[NSNumber numberWithInt:fTitle->job->par.num] forKey:@"PicturePARPixelWidth"];
[queueFileJob setObject:[NSNumber numberWithInt:fTitle->job->par.den] forKey:@"PicturePARPixelHeight"];
/* Text summaries of various settings */
[queueFileJob setObject:[NSString stringWithString:[self pictureSettingsSummary]]
forKey:@"PictureSettingsSummary"];
[queueFileJob setObject:fPictureController.filters.summary
forKey:@"PictureFiltersSummary"];
[queueFileJob setObject:[NSString stringWithString:[self muxerOptionsSummary]]
forKey:@"MuxerOptionsSummary"];
/* Set crop settings here */
[queueFileJob setObject:[NSNumber numberWithInt:[fPictureController autoCrop]] forKey:@"PictureAutoCrop"];
[queueFileJob setObject:[NSNumber numberWithInt:job->crop[0]] forKey:@"PictureTopCrop"];
[queueFileJob setObject:[NSNumber numberWithInt:job->crop[1]] forKey:@"PictureBottomCrop"];
[queueFileJob setObject:[NSNumber numberWithInt:job->crop[2]] forKey:@"PictureLeftCrop"];
[queueFileJob setObject:[NSNumber numberWithInt:job->crop[3]] forKey:@"PictureRightCrop"];
/* Picture Filters */
HBFilters *filters = fPictureController.filters;
queueFileJob[@"PictureDetelecine"] = @(filters.detelecine);
queueFileJob[@"PictureDetelecineCustom"] = filters.detelecineCustomString;
queueFileJob[@"PictureDecombDeinterlace"] = @(filters.useDecomb);
queueFileJob[@"PictureDecomb"] = @(filters.decomb);
queueFileJob[@"PictureDecombCustom"] = filters.decombCustomString;
queueFileJob[@"PictureDeinterlace"] = @(filters.deinterlace);
queueFileJob[@"PictureDeinterlaceCustom"] = filters.deinterlaceCustomString;
queueFileJob[@"PictureDenoise"] = filters.denoise;
queueFileJob[@"PictureDenoisePreset"] = filters.denoisePreset;
queueFileJob[@"PictureDenoiseTune"] = filters.denoiseTune;
queueFileJob[@"PictureDenoiseCustom"] = filters.denoiseCustomString;
queueFileJob[@"PictureDeblock"] = [NSString stringWithFormat:@"%ld",(long)filters.deblock];
queueFileJob[@"VideoGrayScale"] = [NSString stringWithFormat:@"%ld",(long)filters.grayscale];
/* Audio Defaults */
NSMutableDictionary *audioDefaults = [NSMutableDictionary dictionary];
[fAudioController.settings prepareAudioDefaultsForPreset:audioDefaults];
queueFileJob[@"AudioDefaults"] = audioDefaults;
/* Audio */
NSMutableArray *audioArray = [[NSMutableArray alloc] initWithArray:[fAudioController audioTracks] copyItems:YES];
[queueFileJob setObject:audioArray forKey:@"AudioList"];
[audioArray release];
/* Subtitles Defaults */
NSMutableDictionary *subtitlesDefaults = [NSMutableDictionary dictionary];
[fSubtitlesViewController.settings prepareSubtitlesDefaultsForPreset:subtitlesDefaults];
queueFileJob[@"SubtitlesDefaults"] = subtitlesDefaults;
/* Subtitles */
NSMutableArray *subtitlesArray = [[NSMutableArray alloc] initWithArray:[fSubtitlesViewController subtitles] copyItems:YES];
[queueFileJob setObject:subtitlesArray forKey:@"SubtitleList"];
[subtitlesArray release];
/* Now we go ahead and set the "job->values in the plist for passing right to fQueueEncodeLibhb */
[queueFileJob setObject:[NSNumber numberWithInteger:[[fDstFormatPopUp selectedItem] tag]] forKey:@"JobFileFormatMux"];
/* Codecs */
/* Framerate */
[queueFileJob setObject:[NSNumber numberWithInt:title->vrate.num] forKey:@"JobVrate"];
[queueFileJob setObject:[NSNumber numberWithInt:title->vrate.den] forKey:@"JobVrateBase"];
/* we need to auto relase the queueFileJob and return it */
[queueFileJob autorelease];
return queueFileJob;
}
/* 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)*/
[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] setObject:[NSNumber numberWithInt:0] forKey:@"Status"];
/* 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 */
[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] setObject:[NSNumber numberWithInt:1] forKey:@"Status"];
[HBUtilities writeToActivityLog: "incrementQueueItemDone new pending items found: %d", currentQueueEncodeIndex];
[self saveQueueFileItem];
/* now we can go ahead and scan the new pending queue item */
[self performNewQueueScan:[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"SourcePath"] scanTitleNum:[[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"TitleNumber"]intValue]];
}
else
{
[HBUtilities writeToActivityLog: "incrementQueueItemDone there are no more pending encodes"];
/* Done encoding, allow system sleep for the encode handle */
/*
* Since there are no more items to encode, go to queueCompletedAlerts
* for user specified alerts after queue completed
*/
[self queueCompletedAlerts];
}
}
/* Here we actually tell hb_scan to perform the source scan, using the path to source and title number*/
- (void) performNewQueueScan:(NSString *) scanPath scanTitleNum: (NSInteger) scanTitleNum
{
/* Tell HB to output a new activity log file for this encode */
[outputPanel startEncodeLog:[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"DestinationPath"]];
/* We now flag the queue item as being owned by this instance of HB using the PID */
[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] setObject:[NSNumber numberWithInt:pidNum] forKey:@"EncodingPID"];
/* Get the currentQueueEncodeNameString from the queue item to display in the status field */
currentQueueEncodeNameString = [[[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"DestinationPath"] 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];
}
/* This assumes that we have re-scanned and loaded up a new queue item to send to libhb as fQueueEncodeLibhb */
- (void) processNewQueueEncode
{
hb_list_t * list = hb_get_titles( self.queueCore.hb_handle );
hb_title_t * title = (hb_title_t *) hb_list_item( list,0 ); // is always zero since now its a single title scan
hb_job_t * job = title->job;
if( !hb_list_count( list ) )
{
[HBUtilities writeToActivityLog: "processNewQueueEncode WARNING nothing found in the title list"];
}
NSMutableDictionary * queueToApply = [QueueFileArray objectAtIndex:currentQueueEncodeIndex];
[HBUtilities writeToActivityLog: "Preset: %s", [[queueToApply objectForKey:@"PresetName"] UTF8String]];
[HBUtilities writeToActivityLog: "processNewQueueEncode number of passes expected is: %d", ([[queueToApply objectForKey:@"VideoTwoPass"] intValue] + 1)];
hb_job_set_file(job, [[queueToApply objectForKey:@"DestinationPath"] UTF8String]);
[self prepareJob];
/*
* If scanning we need to do some extra setup of the job.
*/
if (job->indepth_scan == 1)
{
char *encoder_preset_tmp = job->encoder_preset != NULL ? strdup(job->encoder_preset) : NULL;
char *encoder_tune_tmp = job->encoder_tune != NULL ? strdup(job->encoder_tune) : NULL;
char *encoder_options_tmp = job->encoder_options != NULL ? strdup(job->encoder_options) : NULL;
char *encoder_profile_tmp = job->encoder_profile != NULL ? strdup(job->encoder_profile) : NULL;
char *encoder_level_tmp = job->encoder_level != NULL ? strdup(job->encoder_level) : NULL;
/*
* When subtitle scan is enabled do a fast pre-scan job
* which will determine which subtitles to enable, if any.
*/
hb_job_set_encoder_preset (job, NULL);
hb_job_set_encoder_tune (job, NULL);
hb_job_set_encoder_options(job, NULL);
hb_job_set_encoder_profile(job, NULL);
hb_job_set_encoder_level (job, NULL);
job->pass = -1;
hb_add(self.queueCore.hb_handle, job);
/*
* reset the advanced settings
*/
hb_job_set_encoder_preset (job, encoder_preset_tmp);
hb_job_set_encoder_tune (job, encoder_tune_tmp);
hb_job_set_encoder_options(job, encoder_options_tmp);
hb_job_set_encoder_profile(job, encoder_profile_tmp);
hb_job_set_encoder_level (job, encoder_level_tmp);
free(encoder_preset_tmp);
free(encoder_tune_tmp);
free(encoder_options_tmp);
free(encoder_profile_tmp);
free(encoder_level_tmp);
}
if ([[queueToApply objectForKey:@"VideoTwoPass"] intValue] == 1)
{
job->indepth_scan = 0;
job->pass = 1;
hb_add(self.queueCore.hb_handle, job);
job->pass = 2;
hb_add(self.queueCore.hb_handle, job);
}
else
{
job->indepth_scan = 0;
job->pass = 0;
hb_add(self.queueCore.hb_handle, job);
}
NSString *destinationDirectory = [[queueToApply objectForKey:@"DestinationPath"] stringByDeletingLastPathComponent];
[[NSUserDefaults standardUserDefaults] setObject:destinationDirectory forKey:@"LastDestinationDirectory"];
/* Lets mark our new encode as 1 or "Encoding" */
[queueToApply setObject:[NSNumber numberWithInt:1] forKey:@"Status"];
[self saveQueueFileItem];
/* libhb makes a copy of the job. So we need to free any resource
* that were allocated in construction of the job. This empties
* the audio, subtitle, and filter lists */
hb_job_reset(job);
/* We should be all setup so let 'er rip */
[self doRip];
}
#pragma mark -
#pragma mark Queue Item Editing
/* Rescans the chosen queue item back into the main window */
- (void)rescanQueueItemToMainWindow:(NSString *) scanPath scanTitleNum: (NSUInteger) scanTitleNum selectedQueueItem: (NSUInteger) selectedQueueItem
{
fqueueEditRescanItemNum = selectedQueueItem;
[HBUtilities writeToActivityLog: "rescanQueueItemToMainWindow: Re-scanning queue item at index:%d",fqueueEditRescanItemNum];
applyQueueToScan = YES;
/* Make sure we release the display name before reassigning it */
[browsedSourceDisplayName release];
/* Set the browsedSourceDisplayName for showNewScan */
browsedSourceDisplayName = [[[QueueFileArray objectAtIndex:fqueueEditRescanItemNum] objectForKey:@"SourceName"] retain];
[self performScan:scanPath scanTitleNum:scanTitleNum];
}
/* We use this method after a queue item rescan for edit.
* it largely mirrors -selectPreset in terms of structure.
* Assumes that a queue item has been reloaded into the main window.
*/
- (IBAction)applyQueueSettingsToMainWindow:(id)sender
{
NSMutableDictionary * queueToApply = [QueueFileArray objectAtIndex:fqueueEditRescanItemNum];
hb_job_t * job = fTitle->job;
if (queueToApply)
{
[HBUtilities writeToActivityLog: "applyQueueSettingsToMainWindow: queue item found"];
}
/* Set title number and chapters */
/* since the queue only scans a single title, its already been selected in showNewScan
so do not try to reset it here. However if we do decide to do full source scans on
a queue edit rescan, we would need it. So leaving in for now but commenting out. */
//[fSrcTitlePopUp selectItemAtIndex: [[queueToApply objectForKey:@"TitleNumber"] intValue] - 1];
[fSrcChapterStartPopUp selectItemAtIndex: [[queueToApply objectForKey:@"ChapterStart"] intValue] - 1];
[fSrcChapterEndPopUp selectItemAtIndex: [[queueToApply objectForKey:@"ChapterEnd"] intValue] - 1];
/* File Format */
[fDstFormatPopUp selectItemWithTag:[queueToApply[@"JobFileFormatMux"] integerValue]];
[self formatPopUpChanged:nil];
/* Chapter Markers*/
fChapterTitlesController.createChapterMarkers = [[queueToApply objectForKey:@"ChapterMarkers"] boolValue];
[fChapterTitlesController addChaptersFromQueue:[queueToApply objectForKey:@"ChapterNames"]];
/* Mux mp4 with http optimization */
[fDstMp4HttpOptFileCheck setState:[[queueToApply objectForKey:@"Mp4HttpOptimize"] intValue]];
/* Set the state of ipod compatible with Mp4iPodCompatible. Only for x264*/
[fDstMp4iPodFileCheck setState:[[queueToApply objectForKey:@"Mp4iPodCompatible"] intValue]];
/* video encoder */
[fVideoController applyVideoSettingsFromQueue:queueToApply];
/* Audio Defaults */
[fAudioController.settings applySettingsFromPreset:queueToApply[@"AudioDefaults"]];
/* Audio */
/* Now lets add our new tracks to the audio list here */
[fAudioController addTracksFromQueue:[queueToApply objectForKey:@"AudioList"]];
/* Subtitles Defaults */
[fSubtitlesViewController.settings applySettingsFromPreset:queueToApply[@"SubtitlesDefaults"]];
/* Subtitles */
[fSubtitlesViewController addTracksFromQueue:[queueToApply objectForKey:@"SubtitleList"]];
/* Picture Settings */
/* If Cropping is set to custom, then recall all four crop values from
when the preset was created and apply them */
if ([[queueToApply objectForKey:@"PictureAutoCrop"] intValue] == 0)
{
[fPictureController setAutoCrop:NO];
/* Here we use the custom crop values saved at the time the preset was saved */
job->crop[0] = [[queueToApply objectForKey:@"PictureTopCrop"] intValue];
job->crop[1] = [[queueToApply objectForKey:@"PictureBottomCrop"] intValue];
job->crop[2] = [[queueToApply objectForKey:@"PictureLeftCrop"] intValue];
job->crop[3] = [[queueToApply objectForKey:@"PictureRightCrop"] intValue];
}
else /* if auto crop has been saved in preset, set to auto and use post scan auto crop */
{
[fPictureController setAutoCrop:YES];
/* Here we use the auto crop values determined right after scan */
job->crop[0] = fTitle->crop[0];
job->crop[1] = fTitle->crop[1];
job->crop[2] = fTitle->crop[2];
job->crop[3] = fTitle->crop[3];
}
job->anamorphic.mode = [[queueToApply objectForKey:@"PicturePAR"] intValue];
job->modulus = [[queueToApply objectForKey:@"PictureModulus"] intValue];
job->maxWidth = job->maxHeight = 0;
job->anamorphic.keep_display_aspect = [[queueToApply objectForKey:@"PictureKeepRatio"] intValue];
job->width = [[queueToApply objectForKey:@"PictureWidth"] intValue];
job->height = [[queueToApply objectForKey:@"PictureHeight"] intValue];
/* Filters */
HBFilters *filters = [fPictureController filters];
/* We only allow *either* Decomb or Deinterlace. So check for the PictureDecombDeinterlace key. */
filters.useDecomb = YES;
filters.decomb = 0;
filters.deinterlace = 0;
if ([queueToApply[@"PictureDecombDeinterlace"] intValue] == 1)
{
/* we are using decomb */
/* Decomb */
if ([queueToApply[@"PictureDecomb"] intValue] > 0)
{
filters.decomb = [queueToApply[@"PictureDecomb"] intValue];
/* if we are using "Custom" in the decomb setting, also set the custom string*/
if ([queueToApply[@"PictureDecomb"] intValue] == 1)
{
filters.decombCustomString = queueToApply[@"PictureDecombCustom"];
}
}
}
else
{
/* We are using Deinterlace */
/* Deinterlace */
if ([queueToApply[@"PictureDeinterlace"] intValue] > 0)
{
filters.useDecomb = NO;
filters.deinterlace = [queueToApply[@"PictureDeinterlace"] intValue];
/* if we are using "Custom" in the deinterlace setting, also set the custom string*/
if ([queueToApply[@"PictureDeinterlace"] intValue] == 1)
{
filters.deinterlaceCustomString = queueToApply[@"PictureDeinterlaceCustom"];
}
}
}
/* Detelecine */
if ([queueToApply[@"PictureDetelecine"] intValue] > 0)
{
filters.detelecine = [queueToApply[@"PictureDetelecine"] intValue];
/* if we are using "Custom" in the detelecine setting, also set the custom string*/
if ([queueToApply[@"PictureDetelecine"] intValue] == 1)
{
filters.detelecineCustomString = queueToApply[@"PictureDetelecineCustom"];
}
}
else
{
filters.detelecine = 0;
}
/* Denoise */
filters.denoise = queueToApply[@"PictureDenoise"];
filters.denoisePreset = queueToApply[@"PictureDenoisePreset"];
filters.denoiseTune = queueToApply[@"PictureDenoiseTune"];
filters.denoiseCustomString = queueToApply[@"PictureDenoiseCustom"];
/* Deblock */
if ([queueToApply[@"PictureDeblock"] intValue] == 1)
{
/* if its a one, then its the old on/off deblock, set on to 5*/
filters.deblock = 5;
}
else
{
/* use the settings intValue */
filters.deblock = [queueToApply[@"PictureDeblock"] intValue];
}
filters.grayscale = [queueToApply[@"VideoGrayScale"] boolValue];
/* we call SetTitle: in fPictureController so we get an instant update in the Picture Settings window */
[fPictureController setTitle:fTitle];
[self pictureSettingsDidChange];
[fPresetSelectedDisplay setStringValue:queueToApply[@"PresetName"]];
[fPresetsView deselect];
/* Now that source is loaded and settings applied, delete the queue item from the queue */
[HBUtilities writeToActivityLog: "applyQueueSettingsToMainWindow: deleting queue item:%d",fqueueEditRescanItemNum];
[self removeQueueFileItem:fqueueEditRescanItemNum];
}
#pragma mark -
#pragma mark Live Preview
/* Note,this is much like prepareJob, but directly sets the job vars so Picture Preview
* can encode to its temp preview directory and playback. This is *not* used for any actual user
* encodes
*/
- (void) prepareJobForPreview
{
hb_list_t * list = hb_get_titles(self.core.hb_handle);
hb_title_t * title = (hb_title_t *) hb_list_item( list,
(int)[fSrcTitlePopUp indexOfSelectedItem] );
hb_job_t * job = title->job;
/* set job->angle for libdvdnav */
job->angle = (int)[fSrcAnglePopUp indexOfSelectedItem] + 1;
/* Chapter selection */
job->chapter_start = (int)[fSrcChapterStartPopUp indexOfSelectedItem] + 1;
job->chapter_end = (int)[fSrcChapterEndPopUp indexOfSelectedItem] + 1;
/* Format (Muxer) and Video Encoder */
job->mux = (int)[[fDstFormatPopUp selectedItem] tag];
/* Video Encoder */
[fVideoController prepareVideoForJobPreview:job andTitle:title];
/* Subtitle settings */
BOOL one_burned = NO;
int i = 0;
for (id subtitleDict in fSubtitlesViewController.subtitles)
{
int subtitle = [subtitleDict[keySubTrackIndex] intValue];
int force = [subtitleDict[keySubTrackForced] intValue];
int burned = [subtitleDict[keySubTrackBurned] intValue];
int def = [subtitleDict[keySubTrackDefault] intValue];
// if i is 0, then we are in the first item of the subtitles which we need to
// check for the "Foreign Audio Search" which would be keySubTrackIndex of -1
/* if we are on the first track and using "Foreign Audio Search" */
if (i == 0 && subtitle == -1)
{
[HBUtilities writeToActivityLog: "Foreign Language Search: %d", 1];
job->indepth_scan = 1;
if (burned != 1)
{
job->select_subtitle_config.dest = PASSTHRUSUB;
}
else
{
job->select_subtitle_config.dest = RENDERSUB;
}
job->select_subtitle_config.force = force;
job->select_subtitle_config.default_track = def;
}
else
{
/* if we are getting the subtitles from an external srt file */
if ([subtitleDict[keySubTrackType] intValue] == SRTSUB)
{
hb_subtitle_config_t sub_config;
sub_config.offset = [subtitleDict[keySubTrackSrtOffset] intValue];
/* we need to srncpy file name and codeset */
strncpy(sub_config.src_filename, [subtitleDict[keySubTrackSrtFilePath] UTF8String], 255);
sub_config.src_filename[255] = 0;
strncpy(sub_config.src_codeset, [subtitleDict[keySubTrackSrtCharCode] UTF8String], 39);
sub_config.src_codeset[39] = 0;
if( !burned && hb_subtitle_can_pass( SRTSUB, job->mux ) )
{
sub_config.dest = PASSTHRUSUB;
}
else if( hb_subtitle_can_burn( SRTSUB ) )
{
// Only allow one subtitle to be burned into the video
if( one_burned )
continue;
one_burned = TRUE;
sub_config.dest = RENDERSUB;
}
sub_config.force = 0;
sub_config.default_track = def;
hb_srt_add( job, &sub_config, [subtitleDict[keySubTrackLanguageIsoCode] UTF8String]);
continue;
}
/* We are setting a source subtitle so access the source subtitle info */
hb_subtitle_t * subt = (hb_subtitle_t *) hb_list_item( title->list_subtitle, subtitle );
if( subt != NULL )
{
hb_subtitle_config_t sub_config = subt->config;
if( !burned && hb_subtitle_can_pass( subt->source, job->mux ) )
{
sub_config.dest = PASSTHRUSUB;
}
else if( hb_subtitle_can_burn( subt->source ) )
{
// Only allow one subtitle to be burned into the video
if( one_burned )
continue;
one_burned = TRUE;
sub_config.dest = RENDERSUB;
}
sub_config.force = force;
sub_config.default_track = def;
hb_subtitle_add( job, &sub_config, subtitle );
}
}
i++;
}
if( one_burned )
{
hb_filter_object_t *filter = hb_filter_init( HB_FILTER_RENDER_SUB );
hb_add_filter( job, filter, [[NSString stringWithFormat:@"%d:%d:%d:%d",
job->crop[0], job->crop[1],
job->crop[2], job->crop[3]] UTF8String] );
}
/* Auto Passthru */
job->acodec_copy_mask = 0;
if (fAudioController.settings.allowAACPassthru)
{
job->acodec_copy_mask |= HB_ACODEC_FFAAC;
}
if (fAudioController.settings.allowAC3Passthru)
{
job->acodec_copy_mask |= HB_ACODEC_AC3;
}
if (fAudioController.settings.allowDTSHDPassthru)
{
job->acodec_copy_mask |= HB_ACODEC_DCA_HD;
}
if (fAudioController.settings.allowDTSPassthru)
{
job->acodec_copy_mask |= HB_ACODEC_DCA;
}
if (fAudioController.settings.allowMP3Passthru)
{
job->acodec_copy_mask |= HB_ACODEC_MP3;
}
job->acodec_fallback = fAudioController.settings.encoderFallback;
// First clear out any audio tracks in the job currently
int audiotrack_count = hb_list_count(job->list_audio);
for (i = 0; i < audiotrack_count; i++)
{
hb_audio_t *temp_audio = (hb_audio_t *) hb_list_item(job->list_audio, 0);
hb_list_rem(job->list_audio, temp_audio);
}
/* Audio tracks and mixdowns */
for (NSDictionary *audioDict in fAudioController.audioTracks)
{
hb_audio_config_t *audio = (hb_audio_config_t *)calloc(1, sizeof(*audio));
hb_audio_config_init(audio);
audio->in.track = [audioDict[@"Track"] intValue];
/* We go ahead and assign values to our audio->out. */
audio->out.track = audio->in.track;
audio->out.codec = [audioDict[@"JobEncoder"] intValue];
audio->out.compression_level = hb_audio_compression_get_default(audio->out.codec);
audio->out.mixdown = [audioDict[@"JobMixdown"] intValue];
audio->out.normalize_mix_level = 0;
audio->out.bitrate = [audioDict[@"JobBitrate"] intValue];
audio->out.samplerate = [audioDict[@"JobSamplerate"] intValue];
audio->out.dynamic_range_compression = [audioDict[@"TrackDRCSlider"] doubleValue];
audio->out.gain = [audioDict[@"TrackGainSlider"] doubleValue];
audio->out.dither_method = hb_audio_dither_get_default();
hb_audio_add(job, audio);
free(audio);
}
/* Filters */
/* Though Grayscale is not really a filter, per se
* we put it here since its in the filters panel
*/
if ([fPictureController.filters grayscale])
{
job->grayscale = 1;
}
else
{
job->grayscale = 0;
}
/* Now lets call the filters if applicable.
* The order of the filters is critical
*/
/* Detelecine */
HBFilters *filters = [fPictureController filters];
if (filters.detelecine == 1)
{
hb_filter_object_t *filter = hb_filter_init(HB_FILTER_DETELECINE);
/* use a custom detelecine string */
hb_add_filter(job, filter, [filters.detelecineCustomString UTF8String]);
}
else if (filters.detelecine == 2)
{
/* Default */
hb_filter_object_t *filter = hb_filter_init(HB_FILTER_DETELECINE);
hb_add_filter(job, filter, NULL);
}
if (filters.useDecomb == YES)
{
/* Decomb */
hb_filter_object_t *filter = hb_filter_init(HB_FILTER_DECOMB);
if (filters.decomb == 1)
{
/* use a custom decomb string */
hb_add_filter(job, filter, [filters.decombCustomString UTF8String]);
}
else if (filters.decomb == 2)
{
/* use libhb defaults */
hb_add_filter(job, filter, NULL);
}
else if (filters.decomb == 3)
{
/* use old defaults (decomb fast) */
hb_add_filter(job, filter, "7:2:6:9:1:80");
}
else if (filters.decomb == 4)
{
/* decomb 3 with bobbing enabled */
hb_add_filter(job, filter, "455");
}
}
else
{
/* Deinterlace */
hb_filter_object_t *filter = hb_filter_init(HB_FILTER_DEINTERLACE);
if (filters.deinterlace == 1)
{
/* we add the custom string if present */
hb_add_filter(job, filter, [filters.deinterlaceCustomString UTF8String]);
}
else if (filters.deinterlace == 2)
{
/* Run old deinterlacer fd by default */
hb_add_filter(job, filter, "0");
}
else if (filters.deinterlace == 3)
{
/* Yadif mode 0 (without spatial deinterlacing) */
hb_add_filter(job, filter, "1");
}
else if (filters.deinterlace == 4)
{
/* Yadif (with spatial deinterlacing) */
hb_add_filter(job, filter, "3");
}
else if (filters.deinterlace == 5)
{
/* Yadif (with spatial deinterlacing and bobbing) */
hb_add_filter(job, filter, "15");
}
}
/* Denoise */
if (![filters.denoise isEqualToString:@"off"])
{
int filter_id = HB_FILTER_HQDN3D;
if ([filters.denoise isEqualToString:@"nlmeans"])
filter_id = HB_FILTER_NLMEANS;
if ([filters.denoisePreset isEqualToString:@"none"])
{
const char *filter_str;
filter_str = [filters.denoiseCustomString UTF8String];
hb_filter_object_t *filter = hb_filter_init(filter_id);
hb_add_filter(job, filter, filter_str);
}
else
{
const char *filter_str, *preset, *tune;
preset = [filters.denoisePreset UTF8String];
tune = [filters.denoiseTune UTF8String];
filter_str = hb_generate_filter_settings(filter_id, preset, tune);
hb_filter_object_t *filter = hb_filter_init(filter_id);
hb_add_filter(job, filter, filter_str);
}
}
/* Deblock (uses pp7 default) */
/* NOTE: even though there is a valid deblock setting of 0 for the filter, for
* the macgui's purposes a value of 0 actually means to not even use the filter
* current hb_filter_deblock.settings valid ranges are from 5 - 15
*/
if (filters.deblock != 0)
{
hb_filter_object_t *filter = hb_filter_init( HB_FILTER_DEBLOCK );
NSString *deblockStringValue = [NSString stringWithFormat: @"%ld",(long)filters.deblock];
hb_add_filter( job, filter, [deblockStringValue UTF8String] );
}
/* Add Crop/Scale filter */
hb_filter_object_t *filter = hb_filter_init( HB_FILTER_CROP_SCALE );
hb_add_filter( job, filter, [[NSString stringWithFormat:@"%d:%d:%d:%d:%d:%d",
job->width, job->height,
job->crop[0], job->crop[1],
job->crop[2], job->crop[3]] UTF8String] );
}
#pragma mark -
#pragma mark Job Handling
- (void) prepareJob
{
NSDictionary * queueToApply = [QueueFileArray objectAtIndex:currentQueueEncodeIndex];
hb_list_t * list = hb_get_titles(self.queueCore.hb_handle);
hb_title_t * title = (hb_title_t *) hb_list_item( list,0 ); // is always zero since now its a single title scan
hb_job_t * job = title->job;
hb_audio_config_t * audio;
hb_filter_object_t * filter;
/* Title Angle for dvdnav */
job->angle = [[queueToApply objectForKey:@"TitleAngle"] intValue];
if([[queueToApply objectForKey:@"fEncodeStartStop"] intValue] == 0)
{
/* Chapter selection */
[HBUtilities writeToActivityLog: "Start / Stop set to chapters"];
job->chapter_start = [[queueToApply objectForKey:@"ChapterStart"] intValue];
job->chapter_end = [[queueToApply objectForKey:@"ChapterEnd"] intValue];
}
else if ([[queueToApply objectForKey:@"fEncodeStartStop"] intValue] == 1)
{
/* we are pts based start / stop */
[HBUtilities writeToActivityLog: "Start / Stop set to seconds…"];
/* Point A to Point B. Time to time in seconds.*/
/* get the start seconds from the start seconds field */
int start_seconds = [[queueToApply objectForKey:@"StartSeconds"] intValue];
job->pts_to_start = start_seconds * 90000LL;
/* Stop seconds is actually the duration of encode, so subtract the end seconds from the start seconds */
int stop_seconds = [[queueToApply objectForKey:@"StopSeconds"] intValue];
job->pts_to_stop = stop_seconds * 90000LL;
}
else if ([[queueToApply objectForKey:@"fEncodeStartStop"] intValue] == 2)
{
/* we are frame based start / stop */
[HBUtilities writeToActivityLog: "Start / Stop set to frames…"];
/* Point A to Point B. Frame to frame */
/* get the start frame from the start frame field */
int start_frame = [[queueToApply objectForKey:@"StartFrame"] intValue];
job->frame_to_start = start_frame;
/* get the frame to stop on from the end frame field */
int stop_frame = [[queueToApply objectForKey:@"StopFrame"] intValue];
job->frame_to_stop = stop_frame;
}
/* Format (Muxer) and Video Encoder */
job->mux = [[queueToApply objectForKey:@"JobFileFormatMux"] intValue];
job->vcodec = [[queueToApply objectForKey:@"JobVideoEncoderVcodec"] intValue];
/* We set http optimized mp4 here */
if( [[queueToApply objectForKey:@"Mp4HttpOptimize"] intValue] == 1 )
{
job->mp4_optimize = 1;
}
else
{
job->mp4_optimize = 0;
}
/* We set the chapter marker extraction here based on the format being
mpeg4 or mkv and the checkbox being checked */
if ([[queueToApply objectForKey:@"ChapterMarkers"] intValue] == 1)
{
job->chapter_markers = 1;
/* now lets get our saved chapter names out the array in the queue file
* and insert them back into the title chapter list. We have it here,
* because unless we are inserting chapter markers there is no need to
* spend the overhead of iterating through the chapter names array imo
* Also, note that if for some reason we don't apply chapter names, the
* chapters just come out 001, 002, etc. etc.
*/
NSArray *chapterNamesArray = [queueToApply objectForKey:@"ChapterNames"];
int i = 0;
for (id tempObject in chapterNamesArray)
{
hb_chapter_t *chapter = (hb_chapter_t *) hb_list_item( job->list_chapter, i );
if( chapter != NULL )
{
hb_chapter_set_title( chapter, [tempObject UTF8String] );
}
i++;
}
}
else
{
job->chapter_markers = 0;
}
if (job->vcodec == HB_VCODEC_X264 || job->vcodec == HB_VCODEC_X265)
{
/* iPod 5G atom */
job->ipod_atom = ([[queueToApply objectForKey:@"Mp4iPodCompatible"]
intValue] == 1);
/* set fastfirstpass if 2-pass and Turbo are enabled */
if ([[queueToApply objectForKey:@"VideoTwoPass"] intValue] == 1)
{
job->fastfirstpass = ([[queueToApply objectForKey:@"VideoTurboTwoPass"]
intValue] == 1);
}
/* advanced x264 options */
NSString *tmpString;
// translate zero-length strings to NULL for libhb
const char *encoder_preset = NULL;
const char *encoder_tune = NULL;
const char *encoder_options = NULL;
const char *encoder_profile = NULL;
const char *encoder_level = NULL;
if ([[queueToApply objectForKey:@"x264UseAdvancedOptions"] intValue])
{
// we are using the advanced panel
if ([(tmpString = [queueToApply objectForKey:@"x264Option"]) length])
{
encoder_options = [tmpString UTF8String];
}
}
else
{
// we are using the x264 preset system
if ([(tmpString = [queueToApply objectForKey:@"VideoTune"]) length])
{
encoder_tune = [tmpString UTF8String];
}
if ([(tmpString = [queueToApply objectForKey:@"VideoOptionExtra"]) length])
{
encoder_options = [tmpString UTF8String];
}
if ([(tmpString = [queueToApply objectForKey:@"VideoProfile"]) length])
{
encoder_profile = [tmpString UTF8String];
}
if ([(tmpString = [queueToApply objectForKey:@"VideoLevel"]) length])
{
encoder_level = [tmpString UTF8String];
}
encoder_preset = [[queueToApply objectForKey:@"VideoPreset"] UTF8String];
}
hb_job_set_encoder_preset (job, encoder_preset);
hb_job_set_encoder_tune (job, encoder_tune);
hb_job_set_encoder_options(job, encoder_options);
hb_job_set_encoder_profile(job, encoder_profile);
hb_job_set_encoder_level (job, encoder_level);
}
else if (job->vcodec & HB_VCODEC_FFMPEG_MASK)
{
hb_job_set_encoder_options(job,
[[queueToApply objectForKey:@"VideoOptionExtra"]
UTF8String]);
}
/* Picture Size Settings */
job->width = [[queueToApply objectForKey:@"PictureWidth"] intValue];
job->height = [[queueToApply objectForKey:@"PictureHeight"] intValue];
job->anamorphic.keep_display_aspect = [[queueToApply objectForKey:@"PictureKeepRatio"] intValue];
job->anamorphic.mode = [[queueToApply objectForKey:@"PicturePAR"] intValue];
job->modulus = [[queueToApply objectForKey:@"PictureModulus"] intValue];
job->par.num = [[queueToApply objectForKey:@"PicturePARPixelWidth"] intValue];
job->par.den = [[queueToApply objectForKey:@"PicturePARPixelHeight"] intValue];
/* Here we use the crop values saved at the time the preset was saved */
job->crop[0] = [[queueToApply objectForKey:@"PictureTopCrop"] intValue];
job->crop[1] = [[queueToApply objectForKey:@"PictureBottomCrop"] intValue];
job->crop[2] = [[queueToApply objectForKey:@"PictureLeftCrop"] intValue];
job->crop[3] = [[queueToApply objectForKey:@"PictureRightCrop"] intValue];
/* Video settings */
/* Framerate */
int fps_mode, fps_num, fps_den;
if ([[queueToApply objectForKey:@"JobIndexVideoFramerate"] intValue] > 0)
{
/* a specific framerate has been chosen */
fps_num = 27000000;
fps_den = (int)[[queueToApply objectForKey:@"JobIndexVideoFramerate"] intValue];
if ([[queueToApply objectForKey:@"VideoFramerateMode"] isEqualToString:@"cfr"])
{
// CFR
fps_mode = 1;
}
else
{
// PFR
fps_mode = 2;
}
}
else
{
/* same as source */
fps_num = [[queueToApply objectForKey:@"JobVrate"] intValue];
fps_den = [[queueToApply objectForKey:@"JobVrateBase"] intValue];
if ([[queueToApply objectForKey:@"VideoFramerateMode"] isEqualToString:@"cfr"])
{
// CFR
fps_mode = 1;
}
else
{
// VFR
fps_mode = 0;
}
}
if ( [[queueToApply objectForKey:@"VideoQualityType"] intValue] != 2 )
{
job->vquality = -1.0;
job->vbitrate = [[queueToApply objectForKey:@"VideoAvgBitrate"] intValue];
}
if ( [[queueToApply objectForKey:@"VideoQualityType"] intValue] == 2 )
{
job->vquality = [[queueToApply objectForKey:@"VideoQualitySlider"] floatValue];
job->vbitrate = 0;
}
job->grayscale = [[queueToApply objectForKey:@"VideoGrayScale"] intValue];
// Map the settings in the dictionaries for the SubtitleList array to match title->list_subtitle
BOOL one_burned = NO;
int i = 0;
NSArray *subtitles = [queueToApply objectForKey:@"SubtitleList"];
for (id subtitleDict in subtitles)
{
int subtitle = [subtitleDict[keySubTrackIndex] intValue];
int force = [subtitleDict[keySubTrackForced] intValue];
int burned = [subtitleDict[keySubTrackBurned] intValue];
int def = [subtitleDict[keySubTrackDefault] intValue];
// if i is 0, then we are in the first item of the subtitles which we need to
// check for the "Foreign Audio Search" which would be keySubTrackIndex of -1
// if we are on the first track and using "Foreign Audio Search"
if (i == 0 && subtitle == -1)
{
[HBUtilities writeToActivityLog: "Foreign Language Search: %d", 1];
job->indepth_scan = 1;
if (burned != 1)
{
job->select_subtitle_config.dest = PASSTHRUSUB;
}
else
{
job->select_subtitle_config.dest = RENDERSUB;
}
job->select_subtitle_config.force = force;
job->select_subtitle_config.default_track = def;
}
else
{
// if we are getting the subtitles from an external srt file
if ([subtitleDict[keySubTrackType] intValue] == SRTSUB)
{
hb_subtitle_config_t sub_config;
sub_config.offset = [subtitleDict[keySubTrackSrtOffset] intValue];
// we need to srncpy file name and codeset
strncpy(sub_config.src_filename, [subtitleDict[keySubTrackSrtFilePath] UTF8String], 255);
sub_config.src_filename[255] = 0;
strncpy(sub_config.src_codeset, [subtitleDict[keySubTrackSrtCharCode] UTF8String], 39);
sub_config.src_codeset[39] = 0;
if( !burned && hb_subtitle_can_pass( SRTSUB, job->mux ) )
{
sub_config.dest = PASSTHRUSUB;
}
else if( hb_subtitle_can_burn( SRTSUB ) )
{
// Only allow one subtitle to be burned into the video
if( one_burned )
continue;
one_burned = TRUE;
sub_config.dest = RENDERSUB;
}
sub_config.force = 0;
sub_config.default_track = def;
hb_srt_add( job, &sub_config, [subtitleDict[keySubTrackLanguageIsoCode] UTF8String]);
continue;
}
/* We are setting a source subtitle so access the source subtitle info */
hb_subtitle_t * subt = (hb_subtitle_t *) hb_list_item( title->list_subtitle, subtitle );
if( subt != NULL )
{
hb_subtitle_config_t sub_config = subt->config;
if( !burned && hb_subtitle_can_pass( subt->source, job->mux ) )
{
sub_config.dest = PASSTHRUSUB;
}
else if( hb_subtitle_can_burn( subt->source ) )
{
// Only allow one subtitle to be burned into the video
if( one_burned )
continue;
one_burned = TRUE;
sub_config.dest = RENDERSUB;
}
sub_config.force = force;
sub_config.default_track = def;
hb_subtitle_add( job, &sub_config, subtitle );
}
}
i++;
}
if( one_burned )
{
filter = hb_filter_init( HB_FILTER_RENDER_SUB );
hb_add_filter( job, filter, [[NSString stringWithFormat:@"%d:%d:%d:%d",
job->crop[0], job->crop[1],
job->crop[2], job->crop[3]] UTF8String] );
}
/* Auto Defaults */
NSDictionary *audioDefaults = queueToApply[@"AudioDefaults"];
job->acodec_copy_mask = 0;
if ([audioDefaults[@"AudioAllowAACPass"] boolValue])
{
job->acodec_copy_mask |= HB_ACODEC_FFAAC;
}
if ([audioDefaults[@"AudioAllowAC3Pass"] boolValue])
{
job->acodec_copy_mask |= HB_ACODEC_AC3;
}
if ([audioDefaults[@"AudioAllowDTSHDPass"] boolValue])
{
job->acodec_copy_mask |= HB_ACODEC_DCA_HD;
}
if ([audioDefaults[@"AudioAllowDTSPass"] boolValue])
{
job->acodec_copy_mask |= HB_ACODEC_DCA;
}
if ([audioDefaults[@"AudioAllowMP3Pass"] boolValue])
{
job->acodec_copy_mask |= HB_ACODEC_MP3;
}
job->acodec_fallback = hb_audio_encoder_get_from_name([audioDefaults[@"AudioEncoderFallback"] UTF8String]);
/* Audio tracks and mixdowns */
/* Now lets add our new tracks to the audio list here */
for (NSDictionary *audioDict in [queueToApply objectForKey:@"AudioList"])
{
audio = (hb_audio_config_t *)calloc(1, sizeof(*audio));
hb_audio_config_init(audio);
audio->in.track = [audioDict[@"Track"] intValue];
/* We go ahead and assign values to our audio->out. */
audio->out.track = audio->in.track;
audio->out.codec = [audioDict[@"JobEncoder"] intValue];
audio->out.compression_level = hb_audio_compression_get_default(audio->out.codec);
audio->out.mixdown = [audioDict[@"JobMixdown"] intValue];
audio->out.normalize_mix_level = 0;
audio->out.bitrate = [audioDict[@"JobBitrate"] intValue];
audio->out.samplerate = [audioDict[@"JobSamplerate"] intValue];
audio->out.dynamic_range_compression = [audioDict[@"TrackDRCSlider"] doubleValue];
audio->out.gain = [audioDict[@"TrackGainSlider"] doubleValue];
audio->out.dither_method = hb_audio_dither_get_default();
hb_audio_add(job, audio);
free(audio);
}
/* Now lets call the filters if applicable.
* The order of the filters is critical
*/
/* Detelecine */
filter = hb_filter_init( HB_FILTER_DETELECINE );
if ([[queueToApply objectForKey:@"PictureDetelecine"] intValue] == 1)
{
/* use a custom detelecine string */
hb_add_filter( job, filter, [[queueToApply objectForKey:@"PictureDetelecineCustom"] UTF8String] );
}
else if ([[queueToApply objectForKey:@"PictureDetelecine"] intValue] == 2)
{
/* Use libhb's default values */
hb_add_filter( job, filter, NULL );
}
if ([[queueToApply objectForKey:@"PictureDecombDeinterlace"] intValue] == 1)
{
/* Decomb */
filter = hb_filter_init( HB_FILTER_DECOMB );
if ([[queueToApply objectForKey:@"PictureDecomb"] intValue] == 1)
{
/* use a custom decomb string */
hb_add_filter( job, filter, [[queueToApply objectForKey:@"PictureDecombCustom"] UTF8String] );
}
else if ([[queueToApply objectForKey:@"PictureDecomb"] intValue] == 2)
{
/* use libhb defaults */
hb_add_filter( job, filter, NULL );
}
else if ([[queueToApply objectForKey:@"PictureDecomb"] intValue] == 3)
{
/* use old defaults (decomb fast) */
hb_add_filter( job, filter, "7:2:6:9:1:80" );
}
else if ([[queueToApply objectForKey:@"PictureDecomb"] intValue] == 4)
{
/* decomb 3 with bobbing enabled */
hb_add_filter( job, filter, "455" );
}
}
else
{
/* Deinterlace */
filter = hb_filter_init( HB_FILTER_DEINTERLACE );
if ([[queueToApply objectForKey:@"PictureDeinterlace"] intValue] == 1)
{
/* we add the custom string if present */
hb_add_filter( job, filter, [[queueToApply objectForKey:@"PictureDeinterlaceCustom"] UTF8String] );
}
else if ([[queueToApply objectForKey:@"PictureDeinterlace"] intValue] == 2)
{
/* Run old deinterlacer fd by default */
hb_add_filter( job, filter, "0" );
}
else if ([[queueToApply objectForKey:@"PictureDeinterlace"] intValue] == 3)
{
/* Yadif mode 0 (without spatial deinterlacing) */
hb_add_filter( job, filter, "1" );
}
else if ([[queueToApply objectForKey:@"PictureDeinterlace"] intValue] == 4)
{
/* Yadif (with spatial deinterlacing) */
hb_add_filter( job, filter, "3" );
}
else if ([[queueToApply objectForKey:@"PictureDeinterlace"] intValue] == 5)
{
/* Yadif (with spatial deinterlacing and bobbing) */
hb_add_filter( job, filter, "15" );
}
}
/* Denoise */
if (![queueToApply[@"PictureDenoise"] isEqualToString:@"off"])
{
int filter_id = HB_FILTER_HQDN3D;
if ([queueToApply[@"PictureDenoise"] isEqualToString:@"nlmeans"])
filter_id = HB_FILTER_NLMEANS;
if ([queueToApply[@"PictureDenoisePreset"] isEqualToString:@"none"])
{
const char *filter_str;
filter_str = [queueToApply[@"PictureDenoiseCustom"] UTF8String];
filter = hb_filter_init(filter_id);
hb_add_filter(job, filter, filter_str);
}
else
{
const char *filter_str, *preset, *tune;
preset = [queueToApply[@"PictureDenoisePreset"] UTF8String];
tune = [queueToApply[@"PictureDenoiseTune"] UTF8String];
filter_str = hb_generate_filter_settings(filter_id, preset, tune);
filter = hb_filter_init(filter_id);
hb_add_filter(job, filter, filter_str);
}
}
/* Deblock (uses pp7 default) */
/* NOTE: even though there is a valid deblock setting of 0 for the filter, for
* the macgui's purposes a value of 0 actually means to not even use the filter
* current hb_filter_deblock.settings valid ranges are from 5 - 15
*/
filter = hb_filter_init( HB_FILTER_DEBLOCK );
if ([[queueToApply objectForKey:@"PictureDeblock"] intValue] != 0)
{
hb_add_filter( job, filter, [[queueToApply objectForKey:@"PictureDeblock"] UTF8String] );
}
/* Add Crop/Scale filter */
filter = hb_filter_init( HB_FILTER_CROP_SCALE );
hb_add_filter( job, filter, [[NSString stringWithFormat:@"%d:%d:%d:%d:%d:%d",
job->width, job->height,
job->crop[0], job->crop[1],
job->crop[2], job->crop[3]] UTF8String] );
/* Add framerate shaping filter */
filter = hb_filter_init(HB_FILTER_VFR);
hb_add_filter(job, filter, [[NSString stringWithFormat:@"%d:%d:%d",
fps_mode, fps_num, fps_den] UTF8String]);
[HBUtilities writeToActivityLog: "prepareJob exiting"];
}
/* addToQueue: puts up an alert before ultimately calling doAddToQueue
*/
- (IBAction) addToQueue: (id) sender
{
/* We get the destination directory from the destination field here */
NSString *destinationDirectory = [[fDstFile2Field stringValue] stringByDeletingLastPathComponent];
/* We check for a valid destination here */
if ([[NSFileManager defaultManager] fileExistsAtPath:destinationDirectory] == 0)
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Warning!"];
[alert setInformativeText:@"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: [fDstFile2Field stringValue]])
{
fileExists = YES;
}
/* We now run through the queue and make sure we are not overwriting an exisiting queue item */
for (id tempObject in QueueFileArray)
{
NSDictionary *thisQueueDict = tempObject;
if ([[thisQueueDict objectForKey:@"DestinationPath"] isEqualToString: [fDstFile2Field stringValue]])
{
fileExistsInQueue = YES;
}
}
if(fileExists == YES)
{
NSAlert *alert = [NSAlert alertWithMessageText:@"File already exists."
defaultButton:@"Cancel"
alternateButton:@"Overwrite"
otherButton:nil
informativeTextWithFormat:@"Do you want to overwrite %@?", [fDstFile2Field stringValue]];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector( overwriteAddToQueueAlertDone:returnCode:contextInfo: ) contextInfo:NULL];
}
else if (fileExistsInQueue == YES)
{
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 %@?", [fDstFile2Field stringValue]];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector( overwriteAddToQueueAlertDone:returnCode:contextInfo: ) contextInfo:NULL];
}
else
{
[self doAddToQueue];
}
}
/* 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
{
if( returnCode == NSAlertAlternateReturn )
[self doAddToQueue];
}
- (void) doAddToQueue
{
[self addQueueFileItem ];
}
/* Rip: 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)
{
[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)
{
currentQueueEncodeIndex = [self getNextPendingQueueIndex];
/* here lets start the queue with the first pending item */
[self performNewQueueScan:[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"SourcePath"] scanTitleNum:[[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"TitleNumber"]intValue]];
return;
}
// Before adding jobs to the queue, check for a valid destination.
NSString *destinationDirectory = [[fDstFile2Field stringValue] 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 runModal];
[alert release];
return;
}
/* We check for duplicate name here */
if( [[NSFileManager defaultManager] fileExistsAtPath:[fDstFile2Field stringValue]] )
{
NSAlert *alert = [NSAlert alertWithMessageText:@"File already exists."
defaultButton:@"Cancel"
alternateButton:@"Overwrite"
otherButton:nil
informativeTextWithFormat:@"Do you want to overwrite %@?", [fDstFile2Field stringValue]];
[alert setAlertStyle:NSCriticalAlertStyle];
[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];
[self performNewQueueScan:[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"SourcePath"] scanTitleNum:[[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"TitleNumber"]intValue]];
}
}
/* 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];
}
NSString *destinationDirectory = [[fDstFile2Field stringValue] stringByDeletingLastPathComponent];
[[NSUserDefaults standardUserDefaults] setObject:destinationDirectory forKey:@"LastDestinationDirectory"];
currentQueueEncodeIndex = [self getNextPendingQueueIndex];
[self performNewQueueScan:[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"SourcePath"] scanTitleNum:[[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"TitleNumber"]intValue]];
}
}
- (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];
}
}
- (void) doRip
{
/* Let libhb do the job */
[self.queueCore start];
/* set the fEncodeState State */
fEncodeState = 1;
}
//------------------------------------------------------------------------------------
// 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
{
if (!fQueueController) return;
/*
* No need to allow system sleep here as we'll either call Cancel:
* (which will take care of it) or resume right away
*/
[self.queueCore pause];
// Which window to attach the sheet to?
NSWindow * docWindow;
if ([sender respondsToSelector: @selector(window)])
{
docWindow = [sender window];
}
else
{
docWindow = fWindow;
}
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
{
/* 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
}
}
//------------------------------------------------------------------------------------
// Cancels and deletes the current job and stops libhb from processing the remaining
// encodes.
//------------------------------------------------------------------------------------
- (void) doCancelCurrentJob
{
// Stop the current job. hb_stop will only cancel the current pass and then set
// its state to HB_STATE_WORKDONE. It also does this asynchronously. So when we
// see the state has changed to HB_STATE_WORKDONE (in updateUI), we'll delete the
// remaining passes of the job and then start the queue back up if there are any
// remaining jobs.
[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 objectAtIndex:currentQueueEncodeIndex] setObject:[NSNumber numberWithInt:3] forKey:@"Status"];
// 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 Status = 1 ( being encoded ) so another instance can not come along and try to scan it while we are scanning */
[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] setObject:[NSNumber numberWithInt:1] forKey:@"Status"];
[HBUtilities writeToActivityLog: "incrementQueueItemDone new pending items found: %d", currentQueueEncodeIndex];
[self saveQueueFileItem];
/* now we can go ahead and scan the new pending queue item */
[self performNewQueueScan:[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"SourcePath"] scanTitleNum:[[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"TitleNumber"]intValue]];
}
else
{
[HBUtilities writeToActivityLog: "incrementQueueItemDone there are no more pending encodes"];
}
}
- (void) doCancelCurrentJobAndStop
{
[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 objectAtIndex:currentQueueEncodeIndex] setObject:[NSNumber numberWithInt:3] forKey:@"Status"];
// 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)
{
[self.queueCore resume];
}
else
{
[self.queueCore pause];
}
}
#pragma mark -
#pragma mark Batch Queue Titles Methods
- (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]];
[alert setAlertStyle:NSCriticalAlertStyle];
[alert beginSheetModalForWindow:fWindow modalDelegate:self didEndSelector:@selector( addAllTitlesToQueueAlertDone:returnCode:contextInfo: ) contextInfo:NULL];
}
- (void) addAllTitlesToQueueAlertDone: (NSWindow *) sheet
returnCode: (int) returnCode contextInfo: (void *) contextInfo
{
if( returnCode == NSAlertAlternateReturn )
[self 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++ )
{
[fSrcTitlePopUp selectItemAtIndex:i];
/* Now call titlePopUpChanged to load it up */
[self titlePopUpChanged:nil];
/* now add the title to the queue */
[self addToQueue:nil];
}
/* Now that we are done, reselect the previously selected title.*/
[fSrcTitlePopUp selectItemAtIndex: currentlySelectedTitle];
/* Now call titlePopUpChanged to load it up */
[self titlePopUpChanged:nil];
}
#pragma mark -
#pragma mark GUI Controls Changed Methods
- (void)updateFileName
{
if (!SuccessfulScan)
{
return;
}
hb_list_t *list = hb_get_titles(self.core.hb_handle);
hb_title_t *title = (hb_title_t *)
hb_list_item(list, (int)[fSrcTitlePopUp indexOfSelectedItem]);
NSString *sourceName = nil;
// Get the file name
if ((title->type == HB_STREAM_TYPE || title->type == HB_FF_STREAM_TYPE) &&
hb_list_count( list ) > 1 )
{
sourceName = @(title->name);
}
else
{
sourceName = [browsedSourceDisplayName stringByDeletingPathExtension];
}
// Generate a new file name
NSString *fileName = [HBUtilities automaticNameForSource:sourceName
title:title->index
chapters:NSMakeRange([fSrcChapterStartPopUp indexOfSelectedItem] + 1, [fSrcChapterEndPopUp indexOfSelectedItem] + 1)
quality:fVideoController.qualityType ? fVideoController.selectedQuality : 0
bitrate:!fVideoController.qualityType ? fVideoController.selectedBitrate : 0
videoCodec:fVideoController.codec];
// Swap the old one with the new one
[fDstFile2Field setStringValue: [NSString stringWithFormat:@"%@/%@.%@",
[[fDstFile2Field stringValue] stringByDeletingLastPathComponent],
fileName,
[[fDstFile2Field stringValue] pathExtension]]];
}
- (IBAction) titlePopUpChanged: (id) sender
{
hb_list_t * list = hb_get_titles( self.core.hb_handle );
hb_title_t * title = (hb_title_t*)
hb_list_item( list, (int)[fSrcTitlePopUp indexOfSelectedItem] );
// If there is already a title load, save the current settings to a preset
if (titleLoaded)
{
self.selectedPreset = [self createPresetFromCurrentSettings];
}
/* If we are a stream type and a batch scan, grok the output file name from title->name upon title change */
if ((title->type == HB_STREAM_TYPE || title->type == HB_FF_STREAM_TYPE) &&
hb_list_count( list ) > 1 )
{
/* we set the default name according to the new title->name */
[fDstFile2Field setStringValue: [NSString stringWithFormat:
@"%@/%@.%@", [[fDstFile2Field stringValue] stringByDeletingLastPathComponent],
[NSString stringWithUTF8String: title->name],
[[fDstFile2Field stringValue] pathExtension]]];
/* Change the source to read out the parent folder also */
[fSrcDVD2Field setStringValue:[NSString stringWithFormat:@"%@/%@", browsedSourceDisplayName,[NSString stringWithUTF8String: title->name]]];
}
/* For point a to point b pts encoding, set the start and end fields to 0 and the title duration in seconds respectively */
int duration = (title->hours * 3600) + (title->minutes * 60) + (title->seconds);
[fSrcTimeStartEncodingField setStringValue: [NSString stringWithFormat: @"%d", 0]];
[fSrcTimeEndEncodingField setStringValue: [NSString stringWithFormat: @"%d", duration]];
/* For point a to point b frame encoding, set the start and end fields to 0 and the title duration * announced fps in seconds respectively */
[fSrcFrameStartEncodingField setStringValue: [NSString stringWithFormat: @"%d", 1]];
//[fSrcFrameEndEncodingField setStringValue: [NSString stringWithFormat: @"%d", ((title->hours * 3600) + (title->minutes * 60) + (title->seconds)) * 24]];
[fSrcFrameEndEncodingField setStringValue: [NSString stringWithFormat: @"%d", duration * (title->vrate.num / title->vrate.den)]];
/* Update encode start / stop variables */
/* Update chapter popups */
[fSrcChapterStartPopUp removeAllItems];
[fSrcChapterEndPopUp removeAllItems];
for( int i = 0; i < hb_list_count( title->list_chapter ); i++ )
{
[fSrcChapterStartPopUp addItemWithTitle: [NSString
stringWithFormat: @"%d", i + 1]];
[fSrcChapterEndPopUp addItemWithTitle: [NSString
stringWithFormat: @"%d", i + 1]];
}
[fSrcChapterStartPopUp selectItemAtIndex: 0];
[fSrcChapterEndPopUp selectItemAtIndex:
hb_list_count( title->list_chapter ) - 1];
[self chapterPopUpChanged:nil];
[fSrcAnglePopUp removeAllItems];
for( int i = 0; i < title->angle_count; i++ )
{
[fSrcAnglePopUp addItemWithTitle: [NSString stringWithFormat: @"%d", i + 1]];
}
[fSrcAnglePopUp selectItemAtIndex: 0];
/* Start Get and set the initial pic size for display */
fTitle = title;
/* Set Auto Crop to on upon selecting a new title */
[fPictureController setAutoCrop:YES];
/* Update the others views */
[[NSNotificationCenter defaultCenter] postNotification:
[NSNotification notificationWithName: HBTitleChangedNotification
object: self
userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
[NSData dataWithBytesNoCopy: &fTitle length: sizeof(fTitle) freeWhenDone: NO], keyTitleTag,
nil]]];
/* If Auto Naming is on. We create an output filename of dvd name - title number */
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DefaultAutoNaming"])
{
[self updateFileName];
}
/* apply the current preset */
[self applyPreset:self.selectedPreset];
}
- (IBAction) encodeStartStopPopUpChanged: (id) sender;
{
if( [fEncodeStartStopPopUp isEnabled] )
{
/* We are chapters */
if( [fEncodeStartStopPopUp indexOfSelectedItem] == 0 )
{
[fSrcChapterStartPopUp setHidden: NO];
[fSrcChapterEndPopUp setHidden: NO];
[fSrcTimeStartEncodingField setHidden: YES];
[fSrcTimeEndEncodingField setHidden: YES];
[fSrcFrameStartEncodingField setHidden: YES];
[fSrcFrameEndEncodingField setHidden: YES];
[self chapterPopUpChanged:nil];
}
/* We are time based (seconds) */
else if ([fEncodeStartStopPopUp indexOfSelectedItem] == 1)
{
[fSrcChapterStartPopUp setHidden: YES];
[fSrcChapterEndPopUp setHidden: YES];
[fSrcTimeStartEncodingField setHidden: NO];
[fSrcTimeEndEncodingField setHidden: NO];
[fSrcFrameStartEncodingField setHidden: YES];
[fSrcFrameEndEncodingField setHidden: YES];
[self startEndSecValueChanged:nil];
}
/* We are frame based */
else if ([fEncodeStartStopPopUp indexOfSelectedItem] == 2)
{
[fSrcChapterStartPopUp setHidden: YES];
[fSrcChapterEndPopUp setHidden: YES];
[fSrcTimeStartEncodingField setHidden: YES];
[fSrcTimeEndEncodingField setHidden: YES];
[fSrcFrameStartEncodingField setHidden: NO];
[fSrcFrameEndEncodingField setHidden: NO];
[self startEndFrameValueChanged:nil];
}
}
}
- (IBAction) chapterPopUpChanged: (id) sender
{
/* If start chapter popup is greater than end chapter popup,
we set the end chapter popup to the same as start chapter popup */
if ([fSrcChapterStartPopUp indexOfSelectedItem] > [fSrcChapterEndPopUp indexOfSelectedItem])
{
[fSrcChapterEndPopUp selectItemAtIndex: [fSrcChapterStartPopUp indexOfSelectedItem]];
}
hb_list_t * list = hb_get_titles( self.core.hb_handle);
hb_title_t * title = (hb_title_t *)
hb_list_item( list, (int)[fSrcTitlePopUp indexOfSelectedItem] );
hb_chapter_t * chapter;
int64_t duration = 0;
for( NSInteger i = [fSrcChapterStartPopUp indexOfSelectedItem];
i <= [fSrcChapterEndPopUp indexOfSelectedItem]; i++ )
{
chapter = (hb_chapter_t *) hb_list_item( title->list_chapter, (int)i );
duration += chapter->duration;
}
duration /= 90000; /* pts -> seconds */
[fSrcDuration2Field setStringValue: [NSString stringWithFormat:
@"%02lld:%02lld:%02lld", duration / 3600, ( duration / 60 ) % 60,
duration % 60]];
/* We're changing the chapter range - we may need to flip the m4v/mp4 extension */
if ([[fDstFormatPopUp selectedItem] tag] & HB_MUX_MASK_MP4)
{
[self autoSetM4vExtension:sender];
}
/* If Auto Naming is on it might need to be update if it includes the chapters range */
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DefaultAutoNaming"])
{
[self updateFileName];
}
}
- (IBAction) startEndSecValueChanged: (id) sender
{
int duration = [fSrcTimeEndEncodingField intValue] - [fSrcTimeStartEncodingField intValue];
[fSrcDuration2Field setStringValue: [NSString stringWithFormat:
@"%02d:%02d:%02d", duration / 3600, ( duration / 60 ) % 60,
duration % 60]];
}
- (IBAction) startEndFrameValueChanged: (id) sender
{
hb_list_t * list = hb_get_titles(self.core.hb_handle);
hb_title_t * title = (hb_title_t*)
hb_list_item( list, (int)[fSrcTitlePopUp indexOfSelectedItem] );
int duration = ([fSrcFrameEndEncodingField intValue] - [fSrcFrameStartEncodingField intValue]) / (title->vrate.num / title->vrate.den);
[fSrcDuration2Field setStringValue: [NSString stringWithFormat:
@"%02d:%02d:%02d", duration / 3600, ( duration / 60 ) % 60,
duration % 60]];
}
- (IBAction) formatPopUpChanged: (id) sender
{
NSString *string = [fDstFile2Field stringValue];
int videoContainer = (int)[[fDstFormatPopUp selectedItem] tag];
const char *ext = NULL;
// enable chapter markers and hide muxer-specific options
[fDstMp4HttpOptFileCheck setHidden:YES];
[fDstMp4iPodFileCheck setHidden:YES];
switch (videoContainer)
{
case HB_MUX_AV_MP4:
[fDstMp4HttpOptFileCheck setHidden:NO];
[fDstMp4iPodFileCheck setHidden:NO];
break;
default:
break;
}
// set the file extension
ext = hb_container_get_default_extension(videoContainer);
[fDstFile2Field setStringValue:[NSString stringWithFormat:@"%@.%s",
[string stringByDeletingPathExtension],
ext]];
if (videoContainer & HB_MUX_MASK_MP4)
{
[self autoSetM4vExtension:sender];
}
/* post a notification for any interested observers to indicate that our video container has changed */
[[NSNotificationCenter defaultCenter] postNotification:
[NSNotification notificationWithName:HBContainerChangedNotification
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:videoContainer], keyContainerTag,
nil]]];
[self customSettingUsed:sender];
}
- (void) autoSetM4vExtension:(NSNotification *)notification
{
if (!([[fDstFormatPopUp selectedItem] tag] & HB_MUX_MASK_MP4))
return;
NSString * extension = @"mp4";
BOOL anyCodecAC3 = [fAudioController anyCodecMatches: HB_ACODEC_AC3] || [fAudioController anyCodecMatches: HB_ACODEC_AC3_PASS];
/* Chapter markers are enabled if the checkbox is ticked and we are doing p2p or we have > 1 chapter */
BOOL chapterMarkers = (fChapterTitlesController.createChapterMarkers) &&
([fEncodeStartStopPopUp indexOfSelectedItem] != 0 ||
[fSrcChapterStartPopUp indexOfSelectedItem] < [fSrcChapterEndPopUp indexOfSelectedItem]);
if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"DefaultMpegExtension"] isEqualToString: @".m4v"] ||
((YES == anyCodecAC3 || YES == chapterMarkers) &&
[[[NSUserDefaults standardUserDefaults] objectForKey:@"DefaultMpegExtension"] isEqualToString: @"Auto"] ))
{
extension = @"m4v";
}
if( [extension isEqualTo: [[fDstFile2Field stringValue] pathExtension]] )
return;
else
[fDstFile2Field setStringValue: [NSString stringWithFormat:@"%@.%@",
[[fDstFile2Field stringValue] stringByDeletingPathExtension], extension]];
}
- (void)updateMp4Checkboxes:(NSNotification *)notification
{
if (fVideoController.codec != HB_VCODEC_X264)
{
/* We set the iPod atom checkbox to disabled and uncheck it as its only for x264 in the mp4
* container. Format is taken care of in formatPopUpChanged method by hiding and unchecking
* anything other than MP4. */
[fDstMp4iPodFileCheck setEnabled: NO];
[fDstMp4iPodFileCheck setState: NSOffState];
}
else
{
[fDstMp4iPodFileCheck setEnabled: YES];
}
}
/* Method to determine if we should change the UI
To reflect whether or not a Preset is being used or if
the user is using "Custom" settings by determining the sender*/
- (IBAction) customSettingUsed: (id) sender
{
if ([sender stringValue])
{
/* Deselect the currently selected Preset if there is one*/
[fPresetsView deselect];
/* Change UI to show "Custom" settings are being used */
[fPresetSelectedDisplay setStringValue: @"Custom"];
self.customPreset = YES;
}
/* If Auto Naming is on it might need to be update if it includes the quality token */
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DefaultAutoNaming"])
{
[self updateFileName];
}
}
#pragma mark -
#pragma mark - Picture
/**
* Registers changes made in the Picture Settings Window.
*/
- (void)pictureSettingsDidChange
{
// align picture settings and video filters in the UI using tabs
fVideoController.pictureSettingsField = [self pictureSettingsSummary];
fVideoController.pictureFiltersField = fPictureController.filters.summary;
/* Store storage resolution for unparse */
if (fTitle)
{
fVideoController.fPresetsWidthForUnparse = fTitle->job->width;
fVideoController.fPresetsHeightForUnparse = fTitle->job->height;
// width or height may have changed, unparse
[fVideoController x264PresetsChangedDisplayExpandedOptions:nil];
}
}
#pragma mark -
#pragma mark - Text Summaries
- (NSString*) pictureSettingsSummary
{
NSMutableString *summary = [NSMutableString stringWithString:@""];
if (fPictureController && fTitle && fTitle->job)
{
[summary appendString:[fPictureController pictureSizeInfoString]];
if (fTitle->job->anamorphic.mode != HB_ANAMORPHIC_STRICT)
{
// anamorphic is not Strict, show the modulus
[summary appendFormat:@", Modulus: %d", fTitle->job->modulus];
}
[summary appendFormat:@", Crop: %s %d/%d/%d/%d",
[fPictureController autoCrop] ? "Auto" : "Custom",
fTitle->job->crop[0], fTitle->job->crop[1],
fTitle->job->crop[2], fTitle->job->crop[3]];
}
return [NSString stringWithString:summary];
}
- (NSString*) muxerOptionsSummary
{
NSMutableString *summary = [NSMutableString stringWithString:@""];
if ([fDstMp4HttpOptFileCheck isHidden] == NO &&
[fDstMp4HttpOptFileCheck isEnabled] == YES &&
[fDstMp4HttpOptFileCheck state] == NSOnState)
{
[summary appendString:@" - Web optimized"];
}
if ([fDstMp4iPodFileCheck isHidden] == NO &&
[fDstMp4iPodFileCheck isEnabled] == YES &&
[fDstMp4iPodFileCheck state] == NSOnState)
{
[summary appendString:@" - iPod 5G support"];
}
if ([summary hasPrefix:@" - "])
{
[summary deleteCharactersInRange:NSMakeRange(0, 3)];
}
return [NSString stringWithString:summary];
}
#pragma mark -
#pragma mark Open New Windows
- (IBAction) openHomepage: (id) sender
{
[[NSWorkspace sharedWorkspace] openURL: [NSURL
URLWithString:@"http://handbrake.fr/"]];
}
- (IBAction) openForums: (id) sender
{
[[NSWorkspace sharedWorkspace] openURL: [NSURL
URLWithString:@"http://forum.handbrake.fr/"]];
}
- (IBAction) openUserGuide: (id) sender
{
[[NSWorkspace sharedWorkspace] openURL: [NSURL
URLWithString:@"http://trac.handbrake.fr/wiki/HandBrakeGuide"]];
}
/**
* Shows debug output window.
*/
- (IBAction)showDebugOutputPanel:(id)sender
{
[outputPanel showOutputPanel:sender];
}
/**
* Shows preferences window.
*/
- (IBAction) showPreferencesWindow: (id) sender
{
if (fPreferencesController == nil)
{
fPreferencesController = [[HBPreferencesController alloc] init];
}
NSWindow *window = [fPreferencesController window];
if (![window isVisible])
[window center];
[window makeKeyAndOrderFront: nil];
}
/**
* Shows queue window.
*/
- (IBAction) showQueueWindow:(id)sender
{
[fQueueController showQueueWindow:sender];
}
- (IBAction) toggleDrawer:(id)sender
{
if ([fPresetDrawer state] == NSDrawerClosedState)
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"HBDefaultPresetsDrawerShow"];
}
else
{
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"HBDefaultPresetsDrawerShow"];
}
[fPresetDrawer toggle:self];
}
/**
* Shows Picture Settings Window.
*/
- (IBAction) showPicturePanel: (id) sender
{
[fPictureController showPictureWindow:sender];
}
- (IBAction) showPreviewWindow: (id) sender
{
[fPictureController showPreviewWindow:sender];
}
#pragma mark - Preset Methods
- (void)applyPreset:(HBPreset *)preset
{
self.selectedPreset = preset;
self.customPreset = NO;
if (preset != nil && SuccessfulScan)
{
hb_job_t * job = fTitle->job;
NSDictionary *chosenPreset = preset.content;
[fPresetSelectedDisplay setStringValue:[chosenPreset objectForKey:@"PresetName"]];
if ([[chosenPreset objectForKey:@"Default"] intValue] == 1)
{
[fPresetSelectedDisplay setStringValue:[NSString stringWithFormat:@"%@ (Default)", [chosenPreset objectForKey:@"PresetName"]]];
}
else
{
[fPresetSelectedDisplay setStringValue:[chosenPreset objectForKey:@"PresetName"]];
}
/* File Format */
/* map legacy container names via libhb */
int format = hb_container_get_from_name(hb_container_sanitize_name([chosenPreset[@"FileFormat"] UTF8String]));
[fDstFormatPopUp selectItemWithTag:format];
[self formatPopUpChanged:nil];
/* Chapter Markers*/
fChapterTitlesController.createChapterMarkers = [[chosenPreset objectForKey:@"ChapterMarkers"] boolValue];
/* check to see if we have only one chapter */
[self chapterPopUpChanged:nil];
/* Mux mp4 with http optimization */
[fDstMp4HttpOptFileCheck setState:[[chosenPreset objectForKey:@"Mp4HttpOptimize"] intValue]];
/* Video encoder */
[fVideoController applySettingsFromPreset:chosenPreset];
/* Lets run through the following functions to get variables set there */
/* Set the state of ipod compatible with Mp4iPodCompatible. Only for x264*/
[fDstMp4iPodFileCheck setState:[[chosenPreset objectForKey:@"Mp4iPodCompatible"] intValue]];
/* Audio */
[fAudioController applySettingsFromPreset: chosenPreset];
/*Subtitles*/
[fSubtitlesViewController applySettingsFromPreset:chosenPreset];
/* Picture Settings */
/* Note: objectForKey:@"UsesPictureSettings" refers to picture size, which encompasses:
* height, width, keep ar, anamorphic and crop settings.
* picture filters are handled separately below.
*/
int maxWidth = fTitle->geometry.width - job->crop[2] - job->crop[3];
int maxHeight = fTitle->geometry.height - job->crop[0] - job->crop[1];
job->maxWidth = job->maxHeight = 0;
/* Check to see if the objectForKey:@"UsesPictureSettings is greater than 0, as 0 means use picture sizing "None"
* ( 2 is use max for source and 1 is use exact size when the preset was created ) and the
* preset completely ignores any picture sizing values in the preset.
*/
if ([[chosenPreset objectForKey:@"UsesPictureSettings"] intValue] > 0)
{
/* If Cropping is set to custom, then recall all four crop values from
when the preset was created and apply them */
if ([[chosenPreset objectForKey:@"PictureAutoCrop"] intValue] == 0)
{
[fPictureController setAutoCrop:NO];
/* Here we use the custom crop values saved at the time the preset was saved */
job->crop[0] = [[chosenPreset objectForKey:@"PictureTopCrop"] intValue];
job->crop[1] = [[chosenPreset objectForKey:@"PictureBottomCrop"] intValue];
job->crop[2] = [[chosenPreset objectForKey:@"PictureLeftCrop"] intValue];
job->crop[3] = [[chosenPreset objectForKey:@"PictureRightCrop"] intValue];
}
else /* if auto crop has been saved in preset, set to auto and use post scan auto crop */
{
[fPictureController setAutoCrop:YES];
/* Here we use the auto crop values determined right after scan */
job->crop[0] = fTitle->crop[0];
job->crop[1] = fTitle->crop[1];
job->crop[2] = fTitle->crop[2];
job->crop[3] = fTitle->crop[3];
}
/* crop may have changed, reset maxWidth/maxHeight */
maxWidth = fTitle->geometry.width - job->crop[2] - job->crop[3];
maxHeight = fTitle->geometry.height - job->crop[0] - job->crop[1];
/* Set modulus */
if ([chosenPreset objectForKey:@"PictureModulus"])
{
job->modulus = [[chosenPreset objectForKey:@"PictureModulus"] intValue];
}
else
{
job->modulus = 16;
}
/*
* Assume max picture settings initially
*/
job->anamorphic.mode = [[chosenPreset objectForKey:@"PicturePAR"] intValue];
job->width = fTitle->geometry.width - job->crop[2] - job->crop[3];
job->height = fTitle->geometry.height - job->crop[0] - job->crop[1];
job->anamorphic.keep_display_aspect = [[chosenPreset objectForKey:@"PictureKeepRatio"] intValue];
/* Check to see if the objectForKey:@"UsesPictureSettings" is 2,
* which means "Use max. picture size for source"
* If not 2 it must be 1 here which means "Use the picture
* size specified in the preset"
*/
if ([[chosenPreset objectForKey:@"UsesPictureSettings"] intValue] != 2 &&
[[chosenPreset objectForKey:@"UsesMaxPictureSettings"] intValue] != 1)
{
/*
* if the preset specifies neither max. width nor height
* (both are 0), use the max. picture size
*
* if the specified non-zero dimensions exceed those of the
* source, also use the max. picture size (no upscaling)
*/
if ([[chosenPreset objectForKey:@"PictureWidth"] intValue] > 0)
{
job->maxWidth = [[chosenPreset objectForKey:@"PictureWidth"] intValue];
}
if ([[chosenPreset objectForKey:@"PictureHeight"] intValue] > 0)
{
job->maxHeight = [[chosenPreset objectForKey:@"PictureHeight"] intValue];
}
}
}
/* Modulus added to maxWidth/maxHeight to allow a small amount of
* upscaling to the next mod boundary. This does not apply to
* explicit limits set for device compatibility. It only applies
* when limiting to cropped title dimensions.
*/
maxWidth += job->modulus - 1;
maxHeight += job->modulus - 1;
if (job->maxWidth == 0 || job->maxWidth > maxWidth)
job->maxWidth = maxWidth;
if (job->maxHeight == 0 || job->maxHeight > maxHeight)
job->maxHeight = maxHeight;
hb_geometry_t srcGeo, resultGeo;
hb_geometry_settings_t uiGeo;
srcGeo.width = fTitle->geometry.width;
srcGeo.height = fTitle->geometry.height;
srcGeo.par = fTitle->geometry.par;
uiGeo.mode = job->anamorphic.mode;
uiGeo.keep = !!job->anamorphic.keep_display_aspect * HB_KEEP_DISPLAY_ASPECT;
uiGeo.itu_par = 0;
uiGeo.modulus = job->modulus;
memcpy(uiGeo.crop, job->crop, sizeof(int[4]));
uiGeo.geometry.width = job->width;
uiGeo.geometry.height = job->height;
uiGeo.geometry.par = job->par;
uiGeo.maxWidth = job->maxWidth;
uiGeo.maxHeight = job->maxHeight;
hb_set_anamorphic_size2(&srcGeo, &uiGeo, &resultGeo);
job->width = resultGeo.width;
job->height = resultGeo.height;
job->par = resultGeo.par;
/* we call SetTitle: in fPictureController so we get an instant update in the Picture Settings window */
[fPictureController setTitle:fTitle];
[fPictureController.filters applySettingsFromPreset:chosenPreset];
[self pictureSettingsDidChange];
}
}
#pragma mark - Presets View Controller Delegate
- (void)selectionDidChange
{
[self applyPreset:fPresetsView.selectedPreset];
}
#pragma mark -
#pragma mark Manage Presets
- (void) checkBuiltInsForUpdates
{
/* if we have built in presets to update, then do so AlertBuiltInPresetUpdate*/
if ([presetManager checkBuiltInsForUpdates])
{
if( [[NSUserDefaults standardUserDefaults] boolForKey:@"AlertBuiltInPresetUpdate"] == YES)
{
/* Show an alert window that built in presets will be updated */
/*On Screen Notification*/
[NSApp requestUserAttention:NSCriticalRequest];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"HandBrake has determined your built in presets are out of date…"];
[alert setInformativeText:@"HandBrake will now update your built-in presets."];
[alert runModal];
[alert release];
}
/* when alert is dismissed, go ahead and update the built in presets */
[presetManager generateBuiltInPresets];
}
}
- (IBAction)showAddPresetPanel:(id)sender
{
/* Show the add panel */
HBAddPresetController *addPresetController = [[HBAddPresetController alloc] initWithPreset:[self createPresetFromCurrentSettings]
videoSize:NSMakeSize(fTitle->job->width, fTitle->job->height)];
[NSApp beginSheet:addPresetController.window modalForWindow:fWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:addPresetController];
}
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
HBAddPresetController *addPresetController = (HBAddPresetController *)contextInfo;
if (returnCode == NSModalResponseContinue)
{
[presetManager addPreset:addPresetController.preset];
}
[addPresetController release];
}
- (HBPreset *)createPresetFromCurrentSettings
{
NSMutableDictionary *preset = [NSMutableDictionary dictionary];
NSDictionary *currentPreset = self.selectedPreset.content;
preset[@"PresetBuildNumber"] = [NSString stringWithFormat: @"%d", [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] intValue]];
preset[@"PresetName"] = fPresetSelectedDisplay.stringValue;
preset[@"Folder"] = @NO;
// Set whether or not this is a user preset or factory 0 is factory, 1 is user
preset[@"Type"] = @1;
preset[@"Default"] = @0;
// Get the whether or not to apply pic Size and Cropping (includes Anamorphic)
preset[@"UsesPictureSettings"] = currentPreset[@"UsesPictureSettings"];
// Get whether or not to use the current Picture Filter settings for the preset
preset[@"UsesPictureFilters"] = currentPreset[@"UsesPictureFilters"];
preset[@"PresetDescription"] = currentPreset[@"PresetDescription"];
preset[@"FileFormat"] = fDstFormatPopUp.titleOfSelectedItem;
preset[@"ChapterMarkers"] = @(fChapterTitlesController.createChapterMarkers);
// Mux mp4 with http optimization
preset[@"Mp4HttpOptimize"] = @(fDstMp4HttpOptFileCheck.state);
// Add iPod uuid atom
preset[@"Mp4iPodCompatible"] = @(fDstMp4iPodFileCheck.state);
// Video encoder
[fVideoController prepareVideoForPreset:preset];
// Picture Sizing
preset[@"PictureWidth"] = currentPreset[@"PictureWidth"];
preset[@"PictureHeight"] = currentPreset[@"PictureHeight"];
hb_job_t *job = fTitle->job;
preset[@"PictureKeepRatio"] = @(job->anamorphic.keep_display_aspect);
preset[@"PicturePAR"] = @(job->anamorphic.mode);
preset[@"PictureModulus"] = @(job->modulus);
// Set crop settings
preset[@"PictureAutoCrop"] = @(fPictureController.autoCrop);
preset[@"PictureTopCrop"] = @(job->crop[0]);
preset[@"PictureBottomCrop"] = @(job->crop[1]);
preset[@"PictureLeftCrop"] = @(job->crop[2]);
preset[@"PictureRightCrop"] = @(job->crop[3]);
// Picture Filters
[fPictureController.filters prepareFiltersForPreset:preset];
// Audio
[fAudioController.settings prepareAudioDefaultsForPreset:preset];
// Subtitles
[fSubtitlesViewController.settings prepareSubtitlesDefaultsForPreset:preset];
return [[[HBPreset alloc] initWithName:preset[@"PresetName"] content:preset builtIn:NO] autorelease];
}
#pragma mark -
#pragma mark Import Export Preset(s)
- (IBAction) browseExportPresetFile: (id) sender
{
/* Open a panel to let the user choose where and how to save the export file */
NSSavePanel *panel = [NSSavePanel savePanel];
/* We get the current file name and path from the destination field here */
NSString *defaultExportDirectory = [NSString stringWithFormat: @"%@/Desktop/", NSHomeDirectory()];
[panel setDirectoryURL:[NSURL fileURLWithPath:defaultExportDirectory]];
[panel setNameFieldStringValue:@"HB_Export.plist"];
[panel beginSheetModalForWindow:fWindow completionHandler:^(NSInteger result) {
if( result == NSOKButton )
{
NSURL *exportPresetsFile = [panel URL];
NSURL *presetExportDirectory = [exportPresetsFile URLByDeletingLastPathComponent];
[[NSUserDefaults standardUserDefaults] setURL:presetExportDirectory forKey:@"LastPresetExportDirectoryURL"];
/* We check for the presets.plist */
if ([[NSFileManager defaultManager] fileExistsAtPath:[exportPresetsFile path]] == 0)
{
[[NSFileManager defaultManager] createFileAtPath:[exportPresetsFile path] contents:nil attributes:nil];
}
NSMutableArray *presetsToExport = [[[NSMutableArray alloc] initWithContentsOfURL:exportPresetsFile] autorelease];
if (presetsToExport == nil)
{
presetsToExport = [[NSMutableArray alloc] init];
/* now get and add selected presets to export */
}
if (fPresetsView.selectedPreset != nil)
{
[presetsToExport addObject:[fPresetsView.selectedPreset content]];
[presetsToExport writeToURL:exportPresetsFile atomically:YES];
}
}
}];
}
- (IBAction)browseImportPresetFile:(id)sender
{
NSOpenPanel *panel;
panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setCanChooseFiles:YES];
[panel setCanChooseDirectories:NO];
[panel setAllowedFileTypes:@[@"plist", @"xml"]];
NSURL *sourceDirectory;
if ([[NSUserDefaults standardUserDefaults] URLForKey:@"LastPresetImportDirectoryURL"])
{
sourceDirectory = [[NSUserDefaults standardUserDefaults] URLForKey:@"LastPresetImportDirectoryURL"];
}
else
{
sourceDirectory = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop"];
}
/* we open up the browse sources sheet here and call for browseSourcesDone after the sheet is closed
* to evaluate whether we want to specify a title, we pass the sender in the contextInfo variable
*/
/* set this for allowed file types, not sure if we should allow xml or not */
[panel setDirectoryURL:sourceDirectory];
[panel beginSheetModalForWindow:fWindow completionHandler:^(NSInteger result)
{
NSURL *importPresetsFile = [panel URL];
NSURL *importPresetsDirectory = nil;//[importPresetsFile URLByDeletingLastPathComponent];
[[NSUserDefaults standardUserDefaults] setURL:importPresetsDirectory forKey:@"LastPresetImportDirectoryURL"];
/* NOTE: here we need to do some sanity checking to verify we do not hose up our presets file */
NSMutableArray *presetsToImport = [[NSMutableArray alloc] initWithContentsOfURL:importPresetsFile];
/* iterate though the new array of presets to import and add them to our presets array */
for (NSMutableDictionary *tempObject in presetsToImport)
{
/* make any changes to the incoming preset we see fit */
/* make sure the incoming preset is not tagged as default */
[tempObject setObject:[NSNumber numberWithInt:0] forKey:@"Default"];
/* prepend "(imported) to the name of the incoming preset for clarification since it can be changed */
NSString *prependedName = [@"(import) " stringByAppendingString:[tempObject objectForKey:@"PresetName"]] ;
[tempObject setObject:prependedName forKey:@"PresetName"];
/* actually add the new preset to our presets array */
[presetManager addPresetFromDictionary:tempObject];
}
[presetsToImport autorelease];
}];
}
#pragma mark -
#pragma mark Preset Menu
- (IBAction)selectDefaultPreset:(id)sender
{
[self applyPreset:presetManager.defaultPreset];
[fPresetsView setSelection:_selectedPreset];
}
- (IBAction)insertFolder:(id)sender
{
[fPresetsView insertFolder:sender];
}
- (IBAction)selectPresetFromMenu:(id)sender
{
// Retrieve the preset stored in the NSMenuItem
HBPreset *preset = [sender representedObject];
[self applyPreset:preset];
[fPresetsView setSelection:preset];
}
/**
* Adds the presets list to the menu.
*/
- (void)buildPresetsMenu
{
// First we remove all the preset menu items
// inserted previosly
NSArray *menuItems = [presetsMenu.itemArray copy];
for (NSMenuItem *item in menuItems)
{
if (item.tag != -1)
{
[presetsMenu removeItem:item];
}
}
[menuItems release];
__block NSUInteger i = 0;
__block BOOL builtInEnded = NO;
[presetManager.root enumerateObjectsUsingBlock:^(id obj, NSIndexPath *idx, BOOL *stop)
{
if (idx.length)
{
NSMenuItem *item = [[NSMenuItem alloc] init];
item.title = [obj name];
item.tag = i++;
// Set an action only to the actual presets,
// not on the folders.
if ([obj isLeaf])
{
item.action = @selector(selectPresetFromMenu:);
item.representedObject = obj;
}
// Make the default preset font bold.
if ([obj isDefault])
{
NSAttributedString *newTitle = [[NSAttributedString alloc] initWithString:[obj name]
attributes:@{NSFontAttributeName: [NSFont boldSystemFontOfSize:14]}];
[item setAttributedTitle:newTitle];
[newTitle release];
}
// Add a separator line after the last builtIn preset
if ([obj isBuiltIn] == NO && builtInEnded == NO)
{
[presetsMenu addItem:[NSMenuItem separatorItem]];
builtInEnded = YES;
}
item.indentationLevel = idx.length - 1;
[presetsMenu addItem:item];
[item release];
}
}];
}
/**
* We use this method to recreate new, updated factory presets
*/
- (IBAction)addFactoryPresets:(id)sender
{
[presetManager generateBuiltInPresets];
}
@end