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"
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++ )
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)
/* 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)
[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"];
[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"];
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"];
}
}
/* 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
{
[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
{
{
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
*/
/* Detelecine */
+ hb_filter_detelecine.settings = NULL;
if ([fPictureController detelecine] == 1)
{
/* use a custom detelecine string */
{
/* Decomb */
/* we add the custom string if present */
+ hb_filter_decomb.settings = NULL;
if ([fPictureController decomb] == 1)
{
/* use a custom decomb string */
/* 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
{
[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
{
{
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
* The order of the filters is critical
*/
/* Detelecine */
+ hb_filter_detelecine.settings = NULL;
if ([[queueToApply objectForKey:@"PictureDetelecine"] intValue] == 1)
{
/* use a custom detelecine string */
{
/* Decomb */
/* we add the custom string if present */
+ hb_filter_decomb.settings = NULL;
if ([[queueToApply objectForKey:@"PictureDecomb"] intValue] == 1)
{
/* use a custom decomb string */
- (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];
if (audio != NULL)
{
- /* find out if our selected output audio codec supports mono and / or 6ch */
- /* 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 && 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 */
/* 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]
{
[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]];
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;
+ }
}
{
[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"];
}
+#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