OSDN Git Service

MacGui: Initial implementation of new queue sync code which allows multi-instance...
[handbrake-jp/handbrake-jp-git.git] / macosx / Controller.m
index ce3e70a..66d7674 100644 (file)
@@ -4,6 +4,7 @@
    Homepage: <http://handbrake.fr/>.
    It may be used under the terms of the GNU General Public License. */
 
+#include <dlfcn.h>
 #import "Controller.h"
 #import "HBOutputPanelController.h"
 #import "HBPreferencesController.h"
@@ -82,7 +83,12 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
     fPreferencesController = [[HBPreferencesController alloc] init];
     /* 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"]]];
-    [self writeToActivityLog: "%s", [versionStringFull UTF8String]];    
+    [self writeToActivityLog: "%s", [versionStringFull UTF8String]];
+    
+    /* Get the PID number for this hb instance, used in multi instance encoding */
+    pidNum = [self getThisHBInstancePID];
+    /* Report this pid to the activity log */
+    [self writeToActivityLog: "Pid for this instance:%d", pidNum];
     
     return self;
 }
@@ -121,12 +127,19 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
     [fPresetsOutlineView setAutosaveExpandedItems:YES];
     
     dockIconProgress = 0;
-
+    
+    /* Init QueueFile .plist */
+    [self loadQueueFile];
+    
     /* Call UpdateUI every 1/2 sec */
+    
     [[NSRunLoop currentRunLoop] addTimer:[NSTimer
-                                          scheduledTimerWithTimeInterval:0.5 target:self
-                                          selector:@selector(updateUI:) userInfo:nil repeats:YES]
-                                 forMode:NSDefaultRunLoopMode];
+                                          scheduledTimerWithTimeInterval:0.5 
+                                          target:self
+                                          selector:@selector(updateUI:) 
+                                          userInfo:nil repeats:YES]
+                                          forMode:NSDefaultRunLoopMode];
+                             
 
     // Open debug output window now if it was visible when HB was closed
     if ([[NSUserDefaults standardUserDefaults] boolForKey:@"OutputPanelIsOpen"])
@@ -216,29 +229,42 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
             [self browseSources:(id)fOpenSourceTitleMMenu];
         }
     }
+    currentQueueEncodeNameString = @"";
 }
 
+#pragma mark -
+#pragma mark Multiple Instances
 - (int) hbInstances
 {
     /* check to see if another instance of HandBrake.app is running */
     NSArray *runningAppDictionaries = [[NSWorkspace sharedWorkspace] launchedApplications];
-    NSDictionary *aDictionary;
+    NSDictionary *runningAppsDictionary;
     int hbInstances = 0;
-    for (aDictionary in runningAppDictionaries)
+    for (runningAppsDictionary in runningAppDictionaries)
        {
-        if ([[aDictionary valueForKey:@"NSApplicationName"] isEqualToString:@"HandBrake"])
+        if ([[runningAppsDictionary valueForKey:@"NSApplicationName"] isEqualToString:@"HandBrake"])
                {
             hbInstances++;
                }
        }
     return hbInstances;
 }
+/* Gets the Process Identifer (PID) for this instance and returns it as an integer */
+- (int) getThisHBInstancePID
+{
+    /* Get the PID of this HB instance */
+    int hbInstancePID = [[NSRunningApplication currentApplication] processIdentifier];
+    return hbInstancePID;
+}
+
+#pragma mark -
 
 - (void) didDimissReloadQueue: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo
 {
     if (returnCode == NSAlertOtherReturn)
     {
         [self clearQueueAllItems];
+        
         /* We show whichever open source window specified in LaunchSourceBehavior preference key */
         if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"LaunchSourceBehavior"] isEqualToString: @"Open Source"])
         {
@@ -301,7 +327,7 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
 
 - (void)applicationWillTerminate:(NSNotification *)aNotification
 {
-    
+    [currentQueueEncodeNameString release];
     [browsedSourceDisplayName release];
     [outputPanel release];
        [fQueueController release];
@@ -309,8 +335,10 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
     [fPictureController release];
     [fApplicationIcon release];
 
-       hb_close(&fHandle);
+    hb_close(&fHandle);
     hb_close(&fQueueEncodeLibhb);
+    hb_global_close();
+
 }
 
 
@@ -333,9 +361,6 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
     
     /* Init UserPresets .plist */
        [self loadPresets];
-    
-    /* Init QueueFile .plist */
-    [self loadQueueFile];
        
     fRipIndicatorShown = NO;  // initially out of view in the nib
     
@@ -487,8 +512,6 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
        [self getDefaultPresets:nil];
        /* lets initialize the current successful scancount here to 0 */
        currentSuccessfulScanCount = 0;
-    
-    
 }
 
 - (void) enableUI: (bool) b
@@ -512,7 +535,8 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
         fQueueStatus,fPresetsAdd,fPresetsDelete,fSrcAngleLabel,fSrcAnglePopUp,
                fCreateChapterMarkers,fVidTurboPassCheck,fDstMp4LargeFileCheck,fSubForcedCheck,fPresetsOutlineView,
         fAudDrcLabel,fDstMp4HttpOptFileCheck,fDstMp4iPodFileCheck,fVidQualityRFField,fVidQualityRFLabel,
-        fEncodeStartStopPopUp,fSrcTimeStartEncodingField,fSrcTimeEndEncodingField,fSrcFrameStartEncodingField,fSrcFrameEndEncodingField};
+        fEncodeStartStopPopUp,fSrcTimeStartEncodingField,fSrcTimeEndEncodingField,fSrcFrameStartEncodingField,
+        fSrcFrameEndEncodingField, fLoadChaptersButton, fSaveChaptersButton, fFrameratePfrCheck};
     
     for( unsigned i = 0;
         i < sizeof( controls ) / sizeof( NSControl * ); i++ )
@@ -754,10 +778,10 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
                        break;
         }
 #undef p
-
+            
             
 #define p s.param.working
-        
+            
         case HB_STATE_SEARCHING:
                {
             NSMutableString * string;
@@ -804,7 +828,7 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
             break;  
         }
             
-
+            
         case HB_STATE_WORKING:
         {
             NSMutableString * string;
@@ -836,8 +860,12 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
             }
             
             [fStatusField setStringValue: string];
-            /* Set the status string in fQueueController as well */
-            [fQueueController setQueueStatusString: string];
+            /* Set the status string in fQueueController as well but add currentQueueEncodeNameString to delineate
+             * which encode is being worked on by this instance in a multiple instance situation
+             */
+            NSString * queueStatusString = [NSString stringWithFormat:@"%@ -> %@",string,currentQueueEncodeNameString];
+            [fQueueController setQueueStatusString:queueStatusString];
+            
             /* Update slider */
             CGFloat progress_total = ( p.progress + p.job_cur - 1 ) / p.job_count;
             [fRipIndicator setIndeterminate: NO];
@@ -858,14 +886,14 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
                 fRipIndicatorShown = YES;
                 
             }
-
+            
             /* Update dock icon */
             if( dockIconProgress < 100.0 * progress_total )
             {
                 [self UpdateDockIcon: progress_total];
                 dockIconProgress += 5;
             }
-
+            
             break;
         }
 #undef p
@@ -942,7 +970,7 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
                 [self sendToMetaX:pathOfFinishedEncode];
                 
                 /* since we have successfully completed an encode, we increment the queue counter */
-                [self incrementQueueItemDone:nil]; 
+                [self incrementQueueItemDone:currentQueueEncodeIndex]; 
                 
                 /* all end of queue actions below need to be done after all queue encodes have finished 
                  * and there are no pending jobs left to process
@@ -982,16 +1010,23 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
                         returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
                         [scriptObject release];
                     }
-                    
                 }
-                
-                
             }
             
             break;
         }
     }
     
+    /* Since we can use multiple instance off of the same queue file it is imperative that we keep the QueueFileArray updated off of the QueueFile.plist
+     * so we go ahead and do it in this existing timer as opposed to using a new one */
+    
+    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];
+    
 }
 
 /* We use this to write messages to stderr from the macgui which show up in the activity window and log*/
@@ -1612,31 +1647,20 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
     
     if( [detector isVideoDVD] )
     {
-        int hb_arch;
-#if defined( __LP64__ )
-        /* we are 64 bit */
-        hb_arch = 64;
-#else
-        /* we are 32 bit */
-        hb_arch = 32;
-#endif 
-        
-        
         // The chosen path was actually on a DVD, so use the raw block
         // device path instead.
         path = [detector devicePath];
         [self writeToActivityLog: "trying to open a physical dvd at: %s", [scanPath UTF8String]];
         
         /* lets check for vlc here to make sure we have a dylib available to use for decrypting */
-        NSString *vlcPath = @"/Applications/VLC.app/Contents/MacOS/lib/libdvdcss.2.dylib";
-        NSFileManager * fileManager = [NSFileManager defaultManager];
-           if ([fileManager fileExistsAtPath:vlcPath] == 0) 
-           {
-            /*vlc not found in /Applications so we set the bool to cancel scanning to 1 */
+        void *dvdcss = dlopen("libdvdcss.2.dylib", RTLD_LAZY);
+        if (dvdcss == NULL) 
+        {
+            /*compatible vlc not found, so we set the bool to cancel scanning to 1 */
             cancelScanDecrypt = 1;
             [self writeToActivityLog: "VLC app not found for decrypting physical dvd"];
             int status;
-            status = NSRunAlertPanel(@"HandBrake could not find VLC or your VLC is out of date.",@"Please download and install VLC media player in your /Applications folder if you wish to read encrypted DVDs.", @"Get VLC", @"Cancel Scan", @"Attempt Scan Anyway");
+            status = NSRunAlertPanel(@"HandBrake could not find VLC or your VLC is incompatible (Note: 32 bit vlc is not compatible with 64 bit HandBrake and vice-versa).",@"Please download and install VLC media player if you wish to read encrypted DVDs.", @"Get VLC", @"Cancel Scan", @"Attempt Scan Anyway");
             [NSApp requestUserAttention:NSCriticalRequest];
             
             if (status == NSAlertDefaultReturn)
@@ -1662,111 +1686,8 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
             /* VLC was found in /Applications so all is well, we can carry on using vlc's libdvdcss.dylib for decrypting if needed */
             [self writeToActivityLog: "VLC app found for decrypting physical dvd"];
             vlcFound = 1;
+            dlclose(dvdcss);
         }
-        /* test for architecture of the vlc app */
-        NSArray *vlc_architecturesArray = [[NSBundle bundleWithPath:@"/Applications/VLC.app"] executableArchitectures];
-        BOOL vlcIntel32bit = NO;
-        BOOL vlcIntel64bit = NO;
-        BOOL vlcPPC32bit = NO;
-        BOOL vlcPPC64bit = NO;
-        /* check the available architectures for vlc and note accordingly */
-        NSEnumerator *enumerator = [vlc_architecturesArray objectEnumerator];
-        id tempObject;
-        while (tempObject = [enumerator nextObject])
-        {
-            
-            if ([tempObject intValue] == NSBundleExecutableArchitectureI386)
-            {
-                vlcIntel32bit = YES;   
-            }
-            if ([tempObject intValue] == NSBundleExecutableArchitectureX86_64)
-            {
-                vlcIntel64bit = YES;   
-            }
-            if ([tempObject intValue] == NSBundleExecutableArchitecturePPC)
-            {
-                vlcPPC32bit = YES;   
-            }
-            if ([tempObject intValue] == NSBundleExecutableArchitecturePPC64)
-            {
-                vlcPPC64bit = YES;   
-            }
-            
-        }
-        /* Write vlc architecture findings to activity window */
-        if (vlcIntel32bit)
-        {
-            [self writeToActivityLog: " 32-Bit VLC app found for decrypting physical dvd"];
-        }
-        if (vlcIntel64bit)
-        {
-            [self writeToActivityLog: " 64-Bit VLC app found for decrypting physical dvd"];
-        }
-        
-        
-        
-        if (vlcFound && hb_arch == 64 && !vlcIntel64bit && cancelScanDecrypt != 1)
-        {
-            
-            /* we are 64 bit */
-            
-            /* Appropriate VLC not found, so cancel */
-            cancelScanDecrypt = 1;
-            [self writeToActivityLog: "This version of HandBrake is 64 bit, 64 bit version of vlc not found, scan cancelled"];
-            /*On Screen Notification*/
-            int status;
-            NSBeep();
-            status = NSRunAlertPanel(@"This version of HandBrake is 64 bit, VLC found but not 64 bit!",@"", @"Cancel Scan", @"Attempt Scan Anyway", @"Get 64 bit VLC", nil);
-            [NSApp requestUserAttention:NSCriticalRequest];
-            
-            if (status == NSAlertDefaultReturn)
-            {
-                /* User chose to cancel the scan */
-                [self writeToActivityLog: "cannot open physical dvd VLC found but not 64 bit, scan cancelled"];
-                cancelScanDecrypt = 1;
-            }
-            else if (status == NSAlertAlternateReturn)
-            {
-                [self writeToActivityLog: "user overrode 64-bit warning trying to open physical dvd without proper decryption"];
-                cancelScanDecrypt = 0;
-            }
-            else
-            {
-                /* User chose to go download vlc (as they rightfully should) so we send them to the vlc site */
-                [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.videolan.org/vlc/download-macosx.html"]];
-            }
-            
-        }    
-        else if (vlcFound && hb_arch == 32 && !vlcIntel32bit && cancelScanDecrypt != 1)
-        {
-            /* we are 32 bit */
-            /* Appropriate VLC not found, so cancel */
-            cancelScanDecrypt = 1;
-            [self writeToActivityLog: "This version of HandBrake is 32 bit, 32 bit version of vlc not found, scan cancelled"];
-            /*On Screen Notification*/
-            int status;
-            NSBeep();
-            status = NSRunAlertPanel(@"This version of HandBrake is 32 bit, VLC found but not 32 bit!",@"", @"Cancel Scan", @"Attempt Scan Anyway", @"Get 32 bit VLC", nil);
-            [NSApp requestUserAttention:NSCriticalRequest];
-            
-            if (status == NSAlertDefaultReturn)
-            {
-                /* User chose to cancel the scan */
-                [self writeToActivityLog: "cannot open physical dvd VLC found but not 32 bit, scan cancelled"];
-                cancelScanDecrypt = 1;
-            }
-            else if (status == NSAlertAlternateReturn)
-            {
-                [self writeToActivityLog: "user overrode 32-bit warning trying to open physical dvd without proper decryption"];
-                cancelScanDecrypt = 0;
-            }
-            else
-            {
-                /* User chose to go download vlc (as they rightfully should) so we send them to the vlc site */
-                [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.videolan.org/vlc/download-macosx.html"]];
-            }
-            
-        } 
     }
     
     if (cancelScanDecrypt == 0)
@@ -1801,9 +1722,7 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
 {
     hb_list_t  * list;
        hb_title_t * title;
-       int indxpri=0;    // Used to search the longuest title (default in combobox)
-       int longuestpri=0; // Used to search the longuest title (default in combobox)
-    
+       int feature_title=0; // Used to store the main feature title
 
         list = hb_get_titles( fHandle );
         
@@ -1877,11 +1796,10 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
                                                      @"%@/Desktop/%@.mp4", NSHomeDirectory(),[browsedSourceDisplayName stringByDeletingPathExtension]]];
                 }
                 
-                
-                if (longuestpri < title->hours*60*60 + title->minutes *60 + title->seconds)
+                /* See if this is the main feature according to libhb */
+                if (title->index == title->job->feature)
                 {
-                    longuestpri=title->hours*60*60 + title->minutes *60 + title->seconds;
-                    indxpri=i;
+                    feature_title = i;
                 }
                 
                 [fSrcTitlePopUp addItemWithTitle: [NSString
@@ -1897,8 +1815,8 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
             }
             else
             {
-                /* if not then select the longest title (dvd) */
-                [fSrcTitlePopUp selectItemAtIndex: indxpri];
+                /* if not then select the main feature title */
+                [fSrcTitlePopUp selectItemAtIndex: feature_title];
             }
             [self titlePopUpChanged:nil];
             
@@ -2000,53 +1918,38 @@ static NSString *        ChooseSourceIdentifier             = @"Choose Source It
        /*We define the location of the user presets file */
     QueueFile = @"~/Library/Application Support/HandBrake/Queue.plist";
        QueueFile = [[QueueFile stringByExpandingTildeInPath]retain];
-    /* We check for the presets.plist */
+    /* We check for the Queue.plist */
        if ([fileManager fileExistsAtPath:QueueFile] == 0)
        {
                [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 (nil == QueueFileArray)
        {
         /* if not, then lets initialize an empty array */
                QueueFileArray = [[NSMutableArray alloc] init];
-        
-     /* Initialize our curQueueEncodeIndex to 0
-     * so we can use it to track which queue
-     * item is to be used to track our encodes */
-     /* NOTE: this should be changed if and when we
-      * are able to get the last unfinished encode
-      * in the case of a crash or shutdown */
-    
-       }
+    }
     else
     {
-    [self clearQueueEncodedItems];
+        /* ONLY clear out encoded items if we are single instance */
+        if ([self hbInstances] == 1)
+        {
+            [self clearQueueEncodedItems];
+        }
     }
-    currentQueueEncodeIndex = 0;
 }
 
 - (void)addQueueFileItem
 {
-        [QueueFileArray addObject:[self createQueueFileItem]];
-        [self saveQueueFileItem];
-
+    [QueueFileArray addObject:[self createQueueFileItem]];
+    [self saveQueueFileItem];
+    
 }
 
 - (void) removeQueueFileItem:(int) queueItemToRemove
 {
-   
-   /* Find out if the item we are removing is a cancelled (3) or a finished (0) item*/
-   if ([[[QueueFileArray objectAtIndex:queueItemToRemove] objectForKey:@"Status"] intValue] == 3 || [[[QueueFileArray objectAtIndex:queueItemToRemove] objectForKey:@"Status"] intValue] == 0)
-    {
-    /* Since we are removing a cancelled or finished item, WE need to decrement the currentQueueEncodeIndex
-     * by one to keep in sync with the queue array
-     */
-    currentQueueEncodeIndex--;
-    [self writeToActivityLog: "removeQueueFileItem: Removing a cancelled/finished encode, decrement currentQueueEncodeIndex to %d", currentQueueEncodeIndex];
-    }
     [QueueFileArray removeObjectAtIndex:queueItemToRemove];
     [self saveQueueFileItem];
 
@@ -2090,7 +1993,13 @@ fWorkingCount = 0;
                if ([[thisQueueDict objectForKey:@"Status"] intValue] == 1) // being encoded
                {
                        fWorkingCount++;
-            fEncodingQueueItem = i;    
+            fEncodingQueueItem = i;
+            /* 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         
         {
@@ -2116,6 +2025,30 @@ fWorkingCount = 0;
     [fQueueStatus setStringValue:string];
 }
 
+/* Used to get the next pending queue item index and return it if found */
+- (int)getNextPendingQueueIndex
+{
+    /* initialize nextPendingIndex to -1, this value tells incrementQueueItemDone that there are no pending items in the queue */
+    int nextPendingIndex = -1;
+       BOOL nextPendingFound = NO;
+    NSEnumerator *enumerator = [QueueFileArray objectEnumerator];
+       id tempObject;
+    int i = 0;
+       while (tempObject = [enumerator nextObject])
+       {
+               NSDictionary *thisQueueDict = tempObject;
+        if ([[thisQueueDict objectForKey:@"Status"] intValue] == 2 && nextPendingFound == NO) // pending               
+        {
+                       nextPendingFound = YES;
+            nextPendingIndex = [QueueFileArray indexOfObject: tempObject];
+            [self writeToActivityLog: "getNextPendingQueueIndex next pending encod index is:%d", nextPendingIndex];
+               }
+        i++;
+       }
+    return nextPendingIndex;
+}
+
+
 /* This method will set any item marked as encoding back to pending
  * currently used right after a queue reload
  */
@@ -2242,7 +2175,7 @@ fWorkingCount = 0;
     
     
     /* 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);
+    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"];
@@ -2300,6 +2233,7 @@ fWorkingCount = 0;
        [queueFileJob setObject:[NSNumber numberWithFloat:[fVidQualityRFField floatValue]] forKey:@"VideoQualitySlider"];
     /* Framerate */
     [queueFileJob setObject:[fVidRatePopUp titleOfSelectedItem] forKey:@"VideoFramerate"];
+    [queueFileJob setObject:[NSNumber numberWithInt:[fFrameratePfrCheck state]] forKey:@"VideoFrameratePFR"];
     
        /* 2 Pass Encoding */
        [queueFileJob setObject:[NSNumber numberWithInt:[fVidTwoPassCheck state]] forKey:@"VideoTwoPass"];
@@ -2441,7 +2375,6 @@ fWorkingCount = 0;
     /*Audio*/
     if ([fAudLang1PopUp indexOfSelectedItem] > 0)
     {
-        //[queueFileJob setObject:[fAudTrack1CodecPopUp indexOfSelectedItem] forKey:@"JobAudio1Encoder"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack1CodecPopUp selectedItem] tag]] forKey:@"JobAudio1Encoder"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack1MixPopUp selectedItem] tag]] forKey:@"JobAudio1Mixdown"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack1RatePopUp selectedItem] tag]] forKey:@"JobAudio1Samplerate"];
@@ -2449,7 +2382,6 @@ fWorkingCount = 0;
      }
     if ([fAudLang2PopUp indexOfSelectedItem] > 0)
     {
-        //[queueFileJob setObject:[fAudTrack1CodecPopUp indexOfSelectedItem] forKey:@"JobAudio2Encoder"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack2CodecPopUp selectedItem] tag]] forKey:@"JobAudio2Encoder"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack2MixPopUp selectedItem] tag]] forKey:@"JobAudio2Mixdown"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack2RatePopUp selectedItem] tag]] forKey:@"JobAudio2Samplerate"];
@@ -2457,7 +2389,6 @@ fWorkingCount = 0;
     }
     if ([fAudLang3PopUp indexOfSelectedItem] > 0)
     {
-        //[queueFileJob setObject:[fAudTrack1CodecPopUp indexOfSelectedItem] forKey:@"JobAudio3Encoder"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack3CodecPopUp selectedItem] tag]] forKey:@"JobAudio3Encoder"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack3MixPopUp selectedItem] tag]] forKey:@"JobAudio3Mixdown"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack3RatePopUp selectedItem] tag]] forKey:@"JobAudio3Samplerate"];
@@ -2465,14 +2396,12 @@ fWorkingCount = 0;
     }
     if ([fAudLang4PopUp indexOfSelectedItem] > 0)
     {
-        //[queueFileJob setObject:[fAudTrack1CodecPopUp indexOfSelectedItem] forKey:@"JobAudio4Encoder"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack4CodecPopUp selectedItem] tag]] forKey:@"JobAudio4Encoder"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack4MixPopUp selectedItem] tag]] forKey:@"JobAudio4Mixdown"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack4RatePopUp selectedItem] tag]] forKey:@"JobAudio4Samplerate"];
         [queueFileJob setObject:[NSNumber numberWithInt:[[fAudTrack4BitratePopUp selectedItem] tag]] forKey:@"JobAudio4Bitrate"];
     }
 
     /* we need to auto relase the queueFileJob and return it */
     [queueFileJob autorelease];
     return queueFileJob;
@@ -2518,46 +2447,56 @@ fWorkingCount = 0;
 
 - (void) incrementQueueItemDone:(int) queueItemDoneIndexNum
 {
-    int i = currentQueueEncodeIndex;
-    [[QueueFileArray objectAtIndex:i] setObject:[NSNumber numberWithInt:0] forKey:@"Status"];
+    /* 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];
-       /* We Reload the New Table data for presets */
-    //[fPresetsOutlineView reloadData];
 
     /* 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 */
-    currentQueueEncodeIndex++ ;
-    [self writeToActivityLog: "incrementQueueItemDone currentQueueEncodeIndex is incremented to: %d", currentQueueEncodeIndex];
     int queueItems = [QueueFileArray count];
-    /* If we still have more items in our queue, lets go to the next one */
-    if (currentQueueEncodeIndex < queueItems)
-    {
-    [self writeToActivityLog: "incrementQueueItemDone currentQueueEncodeIndex is incremented to: %d", currentQueueEncodeIndex];
-    [self performNewQueueScan:[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"SourcePath"] scanTitleNum:[[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"TitleNumber"]intValue]];
+    /* Check to see if there are any more pending items in the queue */
+    int 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"];
+        [self 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
     {
-        [self writeToActivityLog: "incrementQueueItemDone the %d item queue is complete", currentQueueEncodeIndex - 1];
+        [self writeToActivityLog: "incrementQueueItemDone there are no more pending encodes"];
     }
 }
 
 /* Here we actually tell hb_scan to perform the source scan, using the path to source and title number*/
 - (void) performNewQueueScan:(NSString *) scanPath scanTitleNum: (int) scanTitleNum
 {
-   /* Tell HB to output a new activity log file for this encode */
+    /* 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];
     
-     /* use a bool to determine whether or not we can decrypt using vlc */
+    /* use a bool to determine whether or not we can decrypt using vlc */
     BOOL cancelScanDecrypt = 0;
     /* set the bool so that showNewScan knows to apply the appropriate queue
-    * settings as this is a queue rescan
-    */
-    //applyQueueToScan = YES;
+     * settings as this is a queue rescan
+     */
     NSString *path = scanPath;
     HBDVDDetector *detector = [HBDVDDetector detectorForPath:path];
     
@@ -2569,10 +2508,9 @@ fWorkingCount = 0;
         [self writeToActivityLog: "trying to open a physical dvd at: %s", [scanPath UTF8String]];
 
         /* lets check for vlc here to make sure we have a dylib available to use for decrypting */
-        NSString *vlcPath = @"/Applications/VLC.app";
-        NSFileManager * fileManager = [NSFileManager defaultManager];
-           if ([fileManager fileExistsAtPath:vlcPath] == 0) 
-           {
+        void *dvdcss = dlopen("libdvdcss.2.dylib", RTLD_LAZY);
+        if (dvdcss == NULL) 
+        {
             /*vlc not found in /Applications so we set the bool to cancel scanning to 1 */
             cancelScanDecrypt = 1;
             [self writeToActivityLog: "VLC app not found for decrypting physical dvd"];
@@ -2601,6 +2539,7 @@ fWorkingCount = 0;
         else
         {
             /* VLC was found in /Applications so all is well, we can carry on using vlc's libdvdcss.dylib for decrypting if needed */
+            dlclose(dvdcss);
             [self writeToActivityLog: "VLC app found for decrypting physical dvd"];
         }
     }
@@ -2620,8 +2559,7 @@ fWorkingCount = 0;
             [self writeToActivityLog: "scanning specifically for title: %d", scanTitleNum];
         }
         
-        [self writeToActivityLog: "performNewQueueScan currentQueueEncodeIndex is: %d", currentQueueEncodeIndex];
-        /* We use our advance pref to determine how many previews to scan */
+         /* We use our advance pref to determine how many previews to scan */
         int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
         hb_scan( fQueueEncodeLibhb, [path UTF8String], scanTitleNum, hb_num_previews, 0 );
     }
@@ -2643,7 +2581,6 @@ fWorkingCount = 0;
     [self writeToActivityLog: "Preset: %s", [[queueToApply objectForKey:@"PresetName"] UTF8String]];
     [self writeToActivityLog: "processNewQueueEncode number of passes expected is: %d", ([[queueToApply objectForKey:@"VideoTwoPass"] intValue] + 1)];
     job->file = [[queueToApply objectForKey:@"DestinationPath"] UTF8String];
-    //[self writeToActivityLog: "processNewQueueEncode sending to prepareJob"];
     [self prepareJob];
     
     /*
@@ -2718,7 +2655,6 @@ fWorkingCount = 0;
         free(subtitle);
     }
     
-    
     /* We should be all setup so let 'er rip */   
     [self doRip];
 }
@@ -2815,7 +2751,7 @@ fWorkingCount = 0;
     }
     
     [self videoMatrixChanged:nil];
-    [self writeToActivityLog: "applyQueueSettingsToMainWindow: video matrix changed"];    
+        
     /* Video framerate */
     /* For video preset video framerate, we want to make sure that Same as source does not conflict with the
      detected framerate in the fVidRatePopUp so we use index 0*/
@@ -2919,7 +2855,6 @@ fWorkingCount = 0;
         [self audioTrackPopUpChanged: fAudLang4PopUp];
     }
     
-    [self writeToActivityLog: "applyQueueSettingsToMainWindow: audio set up"];
     /*Subtitles*/
     /* Crashy crashy right now, working on it */
     [fSubtitlesDelegate setNewSubtitles:[queueToApply objectForKey:@"SubtitleList"]];
@@ -2979,9 +2914,6 @@ fWorkingCount = 0;
     job->anamorphic.mode = [[queueToApply objectForKey:@"PicturePAR"]  intValue];
     job->modulus = [[queueToApply objectForKey:@"PictureModulus"]  intValue];
     
-    [self writeToActivityLog: "applyQueueSettingsToMainWindow: picture sizing set up"];
-    
-    
     /* Filters */
     
     /* We only allow *either* Decomb or Deinterlace. So check for the PictureDecombDeinterlace key.
@@ -3079,10 +3011,9 @@ fWorkingCount = 0;
     [fPictureController SetTitle:fTitle];
     [self calculatePictureSizing:nil];
     
-    [self writeToActivityLog: "applyQueueSettingsToMainWindow: picture filters set up"];
     /* somehow we need to figure out a way to tie the queue item to a preset if it used one */
     //[queueFileJob setObject:[fPresetSelectedDisplay stringValue] forKey:@"PresetName"];
-    //    [queueFileJob setObject:[NSNumber numberWithInt:[fPresetsOutlineView selectedRow]] forKey:@"PresetIndexNum"];
+    //[queueFileJob setObject:[NSNumber numberWithInt:[fPresetsOutlineView selectedRow]] forKey:@"PresetIndexNum"];
     if ([queueToApply objectForKey:@"PresetIndexNum"]) // This item used a preset so insert that info
        {
                /* Deselect the currently selected Preset if there is one*/
@@ -3165,7 +3096,14 @@ fWorkingCount = 0;
         /* We are not same as source so we set job->cfr to 1 
          * to enable constant frame rate since user has specified
          * a specific framerate*/
-        job->cfr = 1;
+        if ([fFrameratePfrCheck state] == 1)
+        {
+            job->cfr = 2;
+        }
+        else
+        {
+            job->cfr = 1;
+        }
     }
     else
     {
@@ -3241,23 +3179,18 @@ bool one_burned = FALSE;
                 [self writeToActivityLog: "Foreign Language Search: %d", 1];
                 
                 job->indepth_scan = 1;
-                if (burned == 1 || job->mux != HB_MUX_MP4)
+                
+                if (burned != 1)
                 {
-                    if (burned != 1 && job->mux == HB_MUX_MKV)
-                    {
-                        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;
-                    
+                    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
             {
@@ -3305,17 +3238,10 @@ bool one_burned = FALSE;
                 {
                     hb_subtitle_config_t sub_config = subt->config;
                     
-                    if (!burned && job->mux == HB_MUX_MKV && 
-                        subt->format == PICTURESUB)
+                    if ( !burned && subt->format == PICTURESUB )
                     {
                         sub_config.dest = PASSTHRUSUB;
                     }
-                    else if (!burned && job->mux == HB_MUX_MP4 && 
-                             subt->format == PICTURESUB)
-                    {
-                        // Skip any non-burned vobsubs when output is mp4
-                        continue;
-                    }
                     else if ( burned && subt->format == PICTURESUB )
                     {
                         // Only allow one subtitle to be burned into the video
@@ -3442,6 +3368,7 @@ bool one_burned = FALSE;
     */
     
        /* Detelecine */
+    hb_filter_detelecine.settings = NULL;
     if ([fPictureController detelecine] == 1)
     {
         /* use a custom detelecine string */
@@ -3460,6 +3387,7 @@ bool one_burned = FALSE;
     {
         /* Decomb */
         /* we add the custom string if present */
+        hb_filter_decomb.settings = NULL;
         if ([fPictureController decomb] == 1)
         {
             /* use a custom decomb string */
@@ -3570,51 +3498,28 @@ bool one_burned = FALSE;
         /* we are pts based start / stop */
         [self writeToActivityLog: "Start / Stop set to seconds ..."];
         
-        /* Point A to Point B. Since we cannot get frame accurate start times, attempt to glean a semi-accurate start time based on a percentage of the
-         * scanned title time as per live preview, while in some cases inaccurate its the best I can do with what I have barring a pre-scan index afaik.
-         */
-        /* Attempt to bastardize the live preview code to get a roughly 1 second accurate point a to point b encode ... */
+        /* 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->start_at_preview = start_seconds;
-        /* The number of seek points equals the number of seconds announced in the title as that is our current granularity */
-        //job->seek_points = [[queueToApply objectForKey:@"SourceTotalSeconds"] 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;
-
-        /* A bunch of verbose activity log messages to check on what should be expected */
-        [self writeToActivityLog: "point a to b should start at: %d seconds", start_seconds];
-        [self writeToActivityLog: "point a to b should start at (hh:mm:ss): %d:%d:%d", start_seconds / 3600, ( start_seconds / 60 ) % 60,start_seconds % 60];
-        [self writeToActivityLog: "point a to b duration: %d seconds", stop_seconds];
-        [self writeToActivityLog: "point a to b duration (hh:mm:ss): %d:%d:%d", stop_seconds / 3600, ( stop_seconds / 60 ) % 60,stop_seconds % 60];
-        [self writeToActivityLog: "point a to b should end at: %d seconds", start_seconds + stop_seconds];
-        [self writeToActivityLog: "point a to b should end at (hh:mm:ss): %d:%d:%d", (start_seconds + stop_seconds) / 3600, ( (start_seconds + stop_seconds) / 60 ) % 60,(start_seconds + stop_seconds) % 60];
+        
     }
     else if ([[queueToApply objectForKey:@"fEncodeStartStop"] intValue] == 2)
     {
         /* we are frame based start / stop */
         [self writeToActivityLog: "Start / Stop set to frames ..."];
         
-        /* Point A to Point B. Since we cannot get frame accurate start times, attempt to glean a semi-accurate start time based on a percentage of the
-         * scanned title time as per live preview, while in some cases inaccurate its the best I can do with what I have barring a pre-scan index afaik.
-         */
-        /* Attempt to bastardize the live preview code to get a roughly 1 second accurate point a to point b encode ... */
-        /* get the start seconds from the start seconds field */
+        /* 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->start_at_preview = start_seconds;
-        /* The number of seek points equals the number of seconds announced in the title as that is our current granularity */
-        //job->seek_points = [[queueToApply objectForKey:@"SourceTotalSeconds"] intValue];
         job->frame_to_start = start_frame;
-        /* Stop seconds is actually the duration of encode, so subtract the end seconds from the start seconds */
+        /* get the frame to stop on from the end frame field */
         int stop_frame = [[queueToApply objectForKey:@"StopFrame"] intValue];
         job->frame_to_stop = stop_frame;
-
-        /* A bunch of verbose activity log messages to check on what should be expected */
-        [self writeToActivityLog: "point a to b should start at frame %d", start_frame];
-        [self writeToActivityLog: "point a to b duration: %d frames", stop_frame];
-        [self writeToActivityLog: "point a to b should end at frame %d", start_frame + stop_frame];
+        
     }
 
        
@@ -3755,7 +3660,15 @@ bool one_burned = FALSE;
         /* We are not same as source so we set job->cfr to 1 
          * to enable constant frame rate since user has specified
          * a specific framerate*/
-        job->cfr = 1;
+        
+        if ([[queueToApply objectForKey:@"VideoFrameratePFR"] intValue] == 1)
+        {
+            job->cfr = 2;
+        }
+        else
+        {
+            job->cfr = 1;
+        }
     }
     else
     {
@@ -3837,22 +3750,18 @@ bool one_burned = FALSE;
                 [self writeToActivityLog: "Foreign Language Search: %d", 1];
                 
                 job->indepth_scan = 1;
-                if (burned == 1 || job->mux != HB_MUX_MP4)
+                
+                if (burned != 1)
                 {
-                    if (burned != 1 && job->mux == HB_MUX_MKV)
-                    {
-                        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;
+                    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
             {
@@ -3901,17 +3810,10 @@ bool one_burned = FALSE;
                 {
                     hb_subtitle_config_t sub_config = subt->config;
                     
-                    if (!burned && job->mux == HB_MUX_MKV && 
-                        subt->format == PICTURESUB)
+                    if ( !burned && subt->format == PICTURESUB )
                     {
                         sub_config.dest = PASSTHRUSUB;
                     }
-                    else if (!burned && job->mux == HB_MUX_MP4 && 
-                             subt->format == PICTURESUB)
-                    {
-                        // Skip any non-burned vobsubs when output is mp4
-                        continue;
-                    }
                     else if ( burned && subt->format == PICTURESUB )
                     {
                         // Only allow one subtitle to be burned into the video
@@ -4018,6 +3920,7 @@ bool one_burned = FALSE;
      * The order of the filters is critical
      */
     /* Detelecine */
+    hb_filter_detelecine.settings = NULL;
     if ([[queueToApply objectForKey:@"PictureDetelecine"] intValue] == 1)
     {
         /* use a custom detelecine string */
@@ -4034,6 +3937,7 @@ bool one_burned = FALSE;
     {
         /* Decomb */
         /* we add the custom string if present */
+        hb_filter_decomb.settings = NULL;
         if ([[queueToApply objectForKey:@"PictureDecomb"] intValue] == 1)
         {
             /* use a custom decomb string */
@@ -4118,7 +4022,7 @@ bool one_burned = FALSE;
 
 
 /* addToQueue: puts up an alert before ultimately calling doAddToQueue
-*/
+ */
 - (IBAction) addToQueue: (id) sender
 {
        /* We get the destination directory from the destination field here */
@@ -4168,7 +4072,7 @@ bool one_burned = FALSE;
     }
     else if (fileExistsInQueue == YES)
     {
-    NSBeginCriticalAlertSheet( NSLocalizedString( @"There is already a queue item for this destination.", @"" ),
+        NSBeginCriticalAlertSheet( NSLocalizedString( @"There is already a queue item for this destination.", @"" ),
                                   NSLocalizedString( @"Cancel", @"" ), NSLocalizedString( @"Overwrite", @"" ), nil, fWindow, self,
                                   @selector( overwriteAddToQueueAlertDone:returnCode:contextInfo: ),
                                   NULL, NULL, [NSString stringWithFormat:
@@ -4220,6 +4124,7 @@ bool one_burned = FALSE;
     // 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]]; 
         
@@ -4257,6 +4162,7 @@ bool one_burned = FALSE;
         }
         
         /* 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]]; 
         
     }
@@ -4394,23 +4300,29 @@ bool one_burned = FALSE;
     // and as always, save it in the queue .plist...
     /* We save all of the Queue data here */
     [self saveQueueFileItem];
-    // so now lets move to 
-    currentQueueEncodeIndex++ ;
+    
     // ... and see if there are more items left in our queue
     int queueItems = [QueueFileArray count];
     /* If we still have more items in our queue, lets go to the next one */
-    if (currentQueueEncodeIndex < queueItems)
-    {
-    [self writeToActivityLog: "doCancelCurrentJob currentQueueEncodeIndex is incremented to: %d", currentQueueEncodeIndex];
-    [self writeToActivityLog: "doCancelCurrentJob moving to the next job"];
-    
-    [self performNewQueueScan:[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"SourcePath"] scanTitleNum:[[[QueueFileArray objectAtIndex:currentQueueEncodeIndex] objectForKey:@"TitleNumber"]intValue]];
+    /* Check to see if there are any more pending items in the queue */
+    int 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"];
+        [self 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
     {
-        [self writeToActivityLog: "doCancelCurrentJob the item queue is complete"];
+        [self writeToActivityLog: "incrementQueueItemDone there are no more pending encodes"];
     }
-
 }
 
 - (void) doCancelCurrentJobAndStop
@@ -4926,6 +4838,17 @@ the user is using "Custom" settings by determining the sender*/
 
 - (IBAction ) videoFrameRateChanged: (id) sender
 {
+    /* Hide and set the PFR Checkbox to OFF if we are set to Same as Source */
+    if ([fVidRatePopUp indexOfSelectedItem] == 0)
+    {
+        [fFrameratePfrCheck setHidden:YES];
+        [fFrameratePfrCheck setState:0];
+    }
+    else
+    {
+        [fFrameratePfrCheck setHidden:NO];
+    }
+    
     /* We call method method to calculatePictureSizing to error check detelecine*/
     [self calculatePictureSizing: sender];
 
@@ -5360,6 +5283,13 @@ the user is using "Custom" settings by determining the sender*/
     /* We will first verify that a lower track number has been selected before enabling each track
      * for example, make sure a track is selected for track 1 before enabling track 2, etc.
      */
+    
+    /* If the source has no audio then disable audio track 1 */
+    if (hb_list_count( fTitle->list_audio ) == 0)
+    {
+        [fAudLang1PopUp selectItemAtIndex:0];
+    }
+     
     if ([fAudLang1PopUp indexOfSelectedItem] == 0)
     {
         [fAudLang2PopUp setEnabled: NO];
@@ -5515,28 +5445,34 @@ the user is using "Custom" settings by determining the sender*/
     /* e.g. to find the first French track, pass in an NSString * of "Francais" */
     /* e.g. to find the first English 5.1 AC3 track, pass in an NSString * of "English (AC3) (5.1 ch)" */
     /* if no matching track is found, then selectIndexIfNotFound is used to choose which track to select instead */
-
-       if (searchPrefixString)
-       {
-
-        for( int i = 0; i < [sender numberOfItems]; i++ )
+    if (hb_list_count( fTitle->list_audio ) != 0)
+    {
+        if (searchPrefixString)
         {
-            /* Try to find the desired search string */
-            if ([[[sender itemAtIndex: i] title] hasPrefix:searchPrefixString])
+            
+            for( int i = 0; i < [sender numberOfItems]; i++ )
             {
-                [sender selectItemAtIndex: i];
-                return;
+                /* Try to find the desired search string */
+                if ([[[sender itemAtIndex: i] title] hasPrefix:searchPrefixString])
+                {
+                    [sender selectItemAtIndex: i];
+                    return;
+                }
             }
+            /* couldn't find the string, so select the requested "search string not found" item */
+            /* index of 0 means select the "none" item */
+            /* index of 1 means select the first audio track */
+            [sender selectItemAtIndex: selectIndexIfNotFound];
         }
-        /* couldn't find the string, so select the requested "search string not found" item */
-        /* index of 0 means select the "none" item */
-        /* index of 1 means select the first audio track */
-        [sender selectItemAtIndex: selectIndexIfNotFound];
-       }
+        else
+        {
+            /* if no search string is provided, then select the selectIndexIfNotFound item */
+            [sender selectItemAtIndex: selectIndexIfNotFound];
+        }
+    }
     else
     {
-        /* if no search string is provided, then select the selectIndexIfNotFound item */
-        [sender selectItemAtIndex: selectIndexIfNotFound];
+        [sender selectItemAtIndex: 0];
     }
 
 }
@@ -5585,6 +5521,9 @@ the user is using "Custom" settings by determining the sender*/
                 // FAAC
                 menuItem = [[audiocodecPopUp menu] addItemWithTitle:@"AAC (faac)" action: NULL keyEquivalent: @""];
                 [menuItem setTag: HB_ACODEC_FAAC];
+                // MP3
+                menuItem = [[audiocodecPopUp menu] addItemWithTitle:@"MP3 (lame)" action: NULL keyEquivalent: @""];
+                [menuItem setTag: HB_ACODEC_LAME];
                 // AC3 Passthru
                 menuItem = [[audiocodecPopUp menu] addItemWithTitle:@"AC3 Passthru" action: NULL keyEquivalent: @""];
                 [menuItem setTag: HB_ACODEC_AC3];
@@ -5631,6 +5570,12 @@ the user is using "Custom" settings by determining the sender*/
     
     /* make sure we have a selected title before continuing */
     if (fTitle == NULL) return;
+    /* make sure we have a source audio track before continuing */
+    if (hb_list_count( fTitle->list_audio ) == 0)
+    {
+        [sender selectItemAtIndex:0];
+        return;
+    }
     /* if the sender is the lanaguage popup and there is nothing in the codec popup, lets call
     * audioAddAudioTrackCodecs on the codec popup to populate it properly before moving on
     */
@@ -5728,17 +5673,8 @@ the user is using "Custom" settings by determining the sender*/
         if (audio != NULL)
         {
 
-            /* find out if our selected output audio codec supports mono and / or 6ch */
-            /* we also check for an input codec of AC3 or DCA,
-             as they are the only libraries able to do the mixdown to mono / conversion to 6-ch */
-            /* audioCodecsSupportMono and audioCodecsSupport6Ch are the same for now,
-             but this may change in the future, so they are separated for flexibility */
-            int audioCodecsSupportMono =
-                    (audio->in.codec & (HB_ACODEC_AC3|HB_ACODEC_DCA)) &&
-                    (acodec != HB_ACODEC_LAME);
-            int audioCodecsSupport6Ch =
-                    (audio->in.codec & (HB_ACODEC_AC3|HB_ACODEC_DCA)) &&
-                    (acodec != HB_ACODEC_LAME);
+            /* find out if our selected output audio codec supports 6ch */
+            int audioCodecsSupport6Ch = (audio->in.codec && acodec != HB_ACODEC_LAME);
             
             /* check for AC-3 passthru */
             if (audio->in.codec == HB_ACODEC_AC3 && acodec == HB_ACODEC_AC3)
@@ -5770,21 +5706,17 @@ the user is using "Custom" settings by determining the sender*/
                 /* get the input channel layout without any lfe channels */
                 int layout = audio->in.channel_layout & HB_INPUT_CH_LAYOUT_DISCRETE_NO_LFE_MASK;
                 
-                /* do we want to add a mono option? */
-                if (audioCodecsSupportMono == 1)
-                {
-                    NSMenuItem *menuItem = [[mixdownPopUp menu] addItemWithTitle:
-                                            [NSString stringWithUTF8String: hb_audio_mixdowns[0].human_readable_name]
-                                                                          action: NULL keyEquivalent: @""];
-                    [menuItem setTag: hb_audio_mixdowns[0].amixdown];
-                    if (minMixdownUsed == 0) minMixdownUsed = hb_audio_mixdowns[0].amixdown;
-                    maxMixdownUsed = MAX(maxMixdownUsed, hb_audio_mixdowns[0].amixdown);
-                }
+                /* add a mono option */
+                NSMenuItem *menuItem = [[mixdownPopUp menu] addItemWithTitle:
+                                        [NSString stringWithUTF8String: hb_audio_mixdowns[0].human_readable_name]
+                                                                      action: NULL keyEquivalent: @""];
+                [menuItem setTag: hb_audio_mixdowns[0].amixdown];
+                if (minMixdownUsed == 0) minMixdownUsed = hb_audio_mixdowns[0].amixdown;
+                maxMixdownUsed = MAX(maxMixdownUsed, hb_audio_mixdowns[0].amixdown);
                 
                 /* do we want to add a stereo option? */
-                /* offer stereo if we have a mono source and non-mono-supporting codecs, as otherwise we won't have a mixdown at all */
-                /* also offer stereo if we have a stereo-or-better source */
-                if ((layout == HB_INPUT_CH_LAYOUT_MONO && audioCodecsSupportMono == 0) || layout >= HB_INPUT_CH_LAYOUT_STEREO)
+                /* offer stereo if we have a stereo-or-better source */
+                if (layout >= HB_INPUT_CH_LAYOUT_STEREO)
                 {
                     NSMenuItem *menuItem = [[mixdownPopUp menu] addItemWithTitle:
                                             [NSString stringWithUTF8String: hb_audio_mixdowns[1].human_readable_name]
@@ -5975,17 +5907,16 @@ the user is using "Custom" settings by determining the sender*/
             {
                 /* FAAC has a minimum of 192 kbps for 6-channel discrete */
                 minbitrate = 192;
-                /* If either mixdown popup includes 6-channel discrete, then allow up to 448 kbps */
-                maxbitrate = 448;
+                /* If either mixdown popup includes 6-channel discrete, then allow up to 768 kbps */
+                maxbitrate = 768;
                 break;
             }
             else
             {
                 /* FAAC is happy using our min bitrate of 32 kbps for stereo or mono */
                 minbitrate = 32;
-                /* FAAC won't honour anything more than 160 for stereo, so let's not offer it */
                 /* note: haven't dealt with mono separately here, FAAC will just use the max it can */
-                maxbitrate = 160;
+                maxbitrate = 320;
                 break;
             }
 
@@ -6037,7 +5968,7 @@ the user is using "Custom" settings by determining the sender*/
     }
     
     /* make sure we have a selected title before continuing */
-    if (fTitle == NULL) return;
+    if (fTitle == NULL || hb_list_count( fTitle->list_audio ) == 0) return;
     /* get the audio so we can find out what input rates are*/
     hb_audio_config_t * audio;
     audio = (hb_audio_config_t *) hb_list_audio_config_item( fTitle->list_audio, [audiotrackPopUp indexOfSelectedItem] - 1 );
@@ -6710,7 +6641,9 @@ return YES;
         {
             [fVidRatePopUp selectItemWithTitle:[chosenPreset objectForKey:@"VideoFramerate"]];
         }
-        
+        /* Set PFR */
+        [fFrameratePfrCheck setState:[[chosenPreset objectForKey:@"VideoFrameratePFR"] intValue]];
+        [self videoFrameRateChanged:nil];
         
         /* 2 Pass Encoding */
         [fVidTwoPassCheck setState:[[chosenPreset objectForKey:@"VideoTwoPass"] intValue]];
@@ -6725,6 +6658,7 @@ return YES;
         {
             
             /* pointer to this track's mixdown, codec, sample rate and bitrate NSPopUpButton's */
+            NSPopUpButton * trackLangPreviousPopUp = nil;
             NSPopUpButton * trackLangPopUp = nil;
             NSPopUpButton * mixdownPopUp = nil;
             NSPopUpButton * audiocodecPopUp = nil;
@@ -6751,6 +6685,7 @@ return YES;
                 }
                 if( i == 2 )
                 {
+                    trackLangPreviousPopUp = fAudLang1PopUp;
                     trackLangPopUp = fAudLang2PopUp;
                     mixdownPopUp = fAudTrack2MixPopUp;
                     audiocodecPopUp = fAudTrack2CodecPopUp;
@@ -6760,6 +6695,7 @@ return YES;
                 }
                 if( i == 3 )
                 {
+                    trackLangPreviousPopUp = fAudLang2PopUp;
                     trackLangPopUp = fAudLang3PopUp;
                     mixdownPopUp = fAudTrack3MixPopUp;
                     audiocodecPopUp = fAudTrack3CodecPopUp;
@@ -6769,6 +6705,7 @@ return YES;
                 }
                 if( i == 4 )
                 {
+                    trackLangPreviousPopUp = fAudLang3PopUp;
                     trackLangPopUp = fAudLang4PopUp;
                     mixdownPopUp = fAudTrack4MixPopUp;
                     audiocodecPopUp = fAudTrack4CodecPopUp;
@@ -6780,7 +6717,16 @@ return YES;
                 
                 if ([trackLangPopUp indexOfSelectedItem] == 0)
                 {
-                    [trackLangPopUp selectItemAtIndex: 1];
+                    if (i ==1)
+                    {
+                        [trackLangPopUp selectItemAtIndex: 1];
+                    }
+                    else
+                    {
+                        /* if we are greater than track 1, select
+                         * the same track as the previous track */
+                        [trackLangPopUp selectItemAtIndex: [trackLangPreviousPopUp indexOfSelectedItem]];
+                    }
                 }
                 [self audioTrackPopUpChanged: trackLangPopUp];
                 [audiocodecPopUp selectItemWithTitle:[tempObject objectForKey:@"AudioEncoder"]];
@@ -7110,14 +7056,36 @@ return YES;
                 job->keep_ratio = [[chosenPreset objectForKey:@"PictureKeepRatio"]  intValue];
                 if (job->keep_ratio == 1)
                 {
+                    int height = fTitle->height;
+
+                    if ( job->height && job->height < fTitle->height )
+                        height = job->height;
+
                     hb_fix_aspect( job, HB_KEEP_WIDTH );
-                    if( job->height > fTitle->height )
+                    // Make sure the resulting height is less than
+                    // the title height and less than the height
+                    // requested in the preset.
+                    if( job->height > height )
                     {
-                        job->height = fTitle->height;
+                        job->height = height;
                         hb_fix_aspect( job, HB_KEEP_HEIGHT );
                     }
                 }
                 job->anamorphic.mode = [[chosenPreset objectForKey:@"PicturePAR"]  intValue];
+                if ( job->anamorphic.mode > 0 )
+                {
+                    int w, h, par_w, par_h;
+
+                    job->anamorphic.par_width = fTitle->pixel_aspect_width;
+                    job->anamorphic.par_height = fTitle->pixel_aspect_height;
+                    job->maxWidth = job->width;
+                    job->maxHeight = job->height;
+                    hb_set_anamorphic_size( job, &w, &h, &par_w, &par_h );
+                    job->maxWidth = 0;
+                    job->maxHeight = 0;
+                    job->width = w;
+                    job->height = h;
+                }
                 
             }
             
@@ -7292,6 +7260,18 @@ return YES;
 }
 
 
+- (IBAction) addPresetPicDropdownChanged: (id) sender
+{
+    if ([fPresetNewPicSettingsPopUp indexOfSelectedItem] == 1)
+    {
+        [fPresetNewPicWidthHeightBox setHidden:NO];  
+    }
+    else
+    {
+        [fPresetNewPicWidthHeightBox setHidden:YES];
+    }
+}
+
 - (IBAction) showAddPresetPanel: (id) sender
 {
     /* Deselect the currently selected Preset if there is one*/
@@ -7300,7 +7280,7 @@ return YES;
     /* Populate the preset picture settings popup here */
     [fPresetNewPicSettingsPopUp removeAllItems];
     [fPresetNewPicSettingsPopUp addItemWithTitle:@"None"];
-    [fPresetNewPicSettingsPopUp addItemWithTitle:@"Current"];
+    [fPresetNewPicSettingsPopUp addItemWithTitle:@"Custom"];
     [fPresetNewPicSettingsPopUp addItemWithTitle:@"Source Maximum (post source scan)"];
     [fPresetNewPicSettingsPopUp selectItemAtIndex: 0]; 
     /* Uncheck the preset use filters checkbox */
@@ -7310,6 +7290,12 @@ return YES;
     /* Erase info from the input fields*/
        [fPresetNewName setStringValue: @""];
        [fPresetNewDesc setStringValue: @""];
+    
+    /* Initialize custom height and width settings to current values */
+    
+       [fPresetNewPicWidth setStringValue: [NSString stringWithFormat:@"%d",fTitle->job->width]];
+       [fPresetNewPicHeight setStringValue: [NSString stringWithFormat:@"%d",fTitle->job->height]];
+    [self addPresetPicDropdownChanged:nil];
        /* Show the panel */
        [NSApp beginSheet:fAddPresetPanel modalForWindow:fWindow modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
 }
@@ -7434,6 +7420,7 @@ return YES;
         {
             [preset setObject:[fVidRatePopUp titleOfSelectedItem] forKey:@"VideoFramerate"];
         }
+        [preset setObject:[NSNumber numberWithInt:[fFrameratePfrCheck state]] forKey:@"VideoFrameratePFR"];
         
         /* 2 Pass Encoding */
         [preset setObject:[NSNumber numberWithInt:[fVidTwoPassCheck state]] forKey:@"VideoTwoPass"];
@@ -7441,11 +7428,11 @@ return YES;
         [preset setObject:[NSNumber numberWithInt:[fVidTurboPassCheck state]] forKey:@"VideoTurboTwoPass"];
         /*Picture Settings*/
         hb_job_t * job = fTitle->job;
+        
         /* Picture Sizing */
-        /* Use Max Picture settings for whatever the dvd is.*/
         [preset setObject:[NSNumber numberWithInt:0] forKey:@"UsesMaxPictureSettings"];
-        [preset setObject:[NSNumber numberWithInt:fTitle->job->width] forKey:@"PictureWidth"];
-        [preset setObject:[NSNumber numberWithInt:fTitle->job->height] forKey:@"PictureHeight"];
+        [preset setObject:[NSNumber numberWithInt:[fPresetNewPicWidth intValue]] forKey:@"PictureWidth"];
+        [preset setObject:[NSNumber numberWithInt:[fPresetNewPicHeight intValue]] forKey:@"PictureHeight"];
         [preset setObject:[NSNumber numberWithInt:fTitle->job->keep_ratio] forKey:@"PictureKeepRatio"];
         [preset setObject:[NSNumber numberWithInt:fTitle->job->anamorphic.mode] forKey:@"PicturePAR"];
         [preset setObject:[NSNumber numberWithInt:fTitle->job->modulus] forKey:@"PictureModulus"];
@@ -8006,6 +7993,154 @@ return YES;
     
 }
 
+#pragma mark -
+#pragma mark Chapter Files Import / Export
+
+- (IBAction) browseForChapterFile: (id) sender
+{
+       /* Open a panel to let the user choose the file */
+       NSOpenPanel * panel = [NSOpenPanel openPanel];
+       /* We get the current file name and path from the destination field here */
+       [panel beginSheetForDirectory: [NSString stringWithFormat:@"%@/",
+                                    [[NSUserDefaults standardUserDefaults] stringForKey:@"LastDestinationDirectory"]]
+                             file: NULL
+                            types: [NSArray arrayWithObjects:@"csv",nil]
+                   modalForWindow: fWindow modalDelegate: self
+                   didEndSelector: @selector( browseForChapterFileDone:returnCode:contextInfo: )
+                      contextInfo: NULL];
+}
+
+- (void) browseForChapterFileDone: (NSOpenPanel *) sheet
+    returnCode: (int) returnCode contextInfo: (void *) contextInfo
+{
+    NSArray *chaptersArray; /* temp array for chapters */
+       NSMutableArray *chaptersMutableArray; /* temp array for chapters */
+    NSString *chapterName;     /* temp string from file */
+    int chapters, i;
+    
+    if( returnCode == NSOKButton )  /* if they click OK */
+    {  
+        chapterName = [[NSString alloc] initWithContentsOfFile:[sheet filename] encoding:NSUTF8StringEncoding error:NULL];
+        chaptersArray = [chapterName componentsSeparatedByString:@"\n"];
+        chaptersMutableArray= [chaptersArray mutableCopy];
+               chapters = [fChapterTitlesDelegate numberOfRowsInTableView:fChapterTable];
+        if ([chaptersMutableArray count] > 0)
+        { 
+        /* if last item is empty remove it */
+            if ([[chaptersMutableArray objectAtIndex:[chaptersArray count]-1] length] == 0)
+            {
+                [chaptersMutableArray removeLastObject];
+            }
+        }
+        /* if chapters in table is not equal to array count */
+        if ((unsigned int) chapters != [chaptersMutableArray count])
+        {
+            [sheet close];
+            [[NSAlert alertWithMessageText:NSLocalizedString(@"Unable to load chapter file", @"Unable to load chapter file")
+                             defaultButton:NSLocalizedString(@"OK", @"OK")
+                           alternateButton:NULL 
+                               otherButton:NULL
+                 informativeTextWithFormat:NSLocalizedString(@"%d chapters expected, %d chapters found in %@", @"%d chapters expected, %d chapters found in %@"), 
+              chapters, [chaptersMutableArray count], [[sheet filename] lastPathComponent]] runModal];
+            return;
+        }
+               /* otherwise, go ahead and populate table with array */
+               for (i=0; i<chapters; i++)
+        {
+         
+            if([[chaptersMutableArray objectAtIndex:i] length] > 5)
+            { 
+                /* avoid a segfault */
+                /* Get the Range.location of the first comma in the line and then put everything after that into chapterTitle */
+                NSRange firstCommaRange = [[chaptersMutableArray objectAtIndex:i] rangeOfString:@","];
+                NSString *chapterTitle = [[chaptersMutableArray objectAtIndex:i] substringFromIndex:firstCommaRange.location + 1];
+                /* Since we store our chapterTitle commas as "\," for the cli, we now need to remove the escaping "\" from the title */
+                chapterTitle = [chapterTitle stringByReplacingOccurrencesOfString:@"\\," withString:@","];
+                [fChapterTitlesDelegate tableView:fChapterTable 
+                                   setObjectValue:chapterTitle
+                                   forTableColumn:fChapterTableNameColumn
+                                              row:i];
+            }
+            else 
+            {
+                [sheet close];
+                [[NSAlert alertWithMessageText:NSLocalizedString(@"Unable to load chapter file", @"Unable to load chapter file")
+                                 defaultButton:NSLocalizedString(@"OK", @"OK")
+                               alternateButton:NULL 
+                                   otherButton:NULL
+                     informativeTextWithFormat:NSLocalizedString(@"%@ was not formatted as expected.", @"%@ was not formatted as expected."), [[sheet filename] lastPathComponent]] runModal];   
+                [fChapterTable reloadData];
+                return;
+            }
+        }
+        [fChapterTable reloadData];
+    }
+}
+
+- (IBAction) browseForChapterFileSave: (id) sender
+{
+    NSSavePanel *panel = [NSSavePanel savePanel];
+    /* Open a panel to let the user save to a file */
+    [panel setAllowedFileTypes:[NSArray arrayWithObjects:@"csv",nil]];
+    [panel beginSheetForDirectory: [[fDstFile2Field stringValue] stringByDeletingLastPathComponent] 
+                             file: [[[[fDstFile2Field stringValue] lastPathComponent] stringByDeletingPathExtension] 
+                                     stringByAppendingString:@"-chapters.csv"]
+                   modalForWindow: fWindow 
+                    modalDelegate: self
+                   didEndSelector: @selector( browseForChapterFileSaveDone:returnCode:contextInfo: )
+                      contextInfo: NULL];
+}
+
+- (void) browseForChapterFileSaveDone: (NSSavePanel *) sheet
+    returnCode: (int) returnCode contextInfo: (void *) contextInfo
+{
+    NSString *chapterName;      /* pointer for string for later file-writing */
+    NSString *chapterTitle;
+    NSError *saveError = [[NSError alloc] init];
+    int chapters, i;    /* ints for the number of chapters in the table and the loop */
+    
+    if( returnCode == NSOKButton )   /* if they clicked OK */
+    {  
+        chapters = [fChapterTitlesDelegate numberOfRowsInTableView:fChapterTable];
+        chapterName = [NSString string];
+        for (i=0; i<chapters; i++)
+        {
+            /* put each chapter title from the table into the array */
+            if (i<9)
+            { /* if i is from 0 to 8 (chapters 1 to 9) add two leading zeros */
+                chapterName = [chapterName stringByAppendingFormat:@"00%d,",i+1];
+            }
+            else if (i<99)
+            { /* if i is from 9 to 98 (chapters 10 to 99) add one leading zero */
+                chapterName = [chapterName stringByAppendingFormat:@"0%d,",i+1];
+            }
+            else if (i<999)
+            { /* in case i is from 99 to 998 (chapters 100 to 999) no leading zeros */
+                chapterName = [chapterName stringByAppendingFormat:@"%d,",i+1];
+            }
+            
+            chapterTitle = [fChapterTitlesDelegate tableView:fChapterTable objectValueForTableColumn:fChapterTableNameColumn row:i];
+            /* escape any commas in the chapter name with "\," */
+            chapterTitle = [chapterTitle stringByReplacingOccurrencesOfString:@"," withString:@"\\,"];
+            chapterName = [chapterName stringByAppendingString:chapterTitle];
+            if (i+1 != chapters)
+            { /* if not the last chapter */
+                chapterName = [chapterName stringByAppendingString:@ "\n"];
+            }
+
+            
+        }
+        /* try to write it to where the user wanted */
+        if (![chapterName writeToFile:[sheet filename] 
+                           atomically:NO 
+                             encoding:NSUTF8StringEncoding 
+                                error:&saveError])
+        {
+            [sheet close];
+            [[NSAlert alertWithError:saveError] runModal];
+        }
+    }
+}
 
 @end