/* HBJob+UIAdditions.m $ This file is part of the HandBrake source code. Homepage: . It may be used under the terms of the GNU General Public License. */ #import "HBJob+UIAdditions.h" #import "HBAttributedStringAdditions.h" #import "HBTitle.h" #import "HBJob.h" #import "HBAudioTrack.h" #import "HBAudioDefaults.h" #import "HBSubtitlesTrack.h" #import "HBPicture+UIAdditions.h" #import "HBFilters+UIAdditions.h" #include "hb.h" // Text Styles static NSMutableParagraphStyle *ps; static NSDictionary *detailAttr; static NSDictionary *detailBoldAttr; static NSDictionary *titleAttr; static NSDictionary *shortHeightAttr; @implementation HBJob (UIAdditions) - (BOOL)mp4OptionsEnabled { return ((self.container & HB_MUX_MASK_MP4) != 0); } + (NSSet *)keyPathsForValuesAffectingMp4OptionsEnabled { return [NSSet setWithObjects:@"container", nil]; } - (BOOL)mp4iPodCompatibleEnabled { return ((self.container & HB_MUX_MASK_MP4) != 0) && (self.video.encoder & HB_VCODEC_H264_MASK); } + (NSSet *)keyPathsForValuesAffectingMp4iPodCompatibleEnabled { return [NSSet setWithObjects:@"container", @"video.encoder", nil]; } - (NSArray *)angles { NSMutableArray *angles = [NSMutableArray array]; for (int i = 1; i <= self.title.angles; i++) { [angles addObject:[NSString stringWithFormat: @"%d", i]]; } return angles; } - (NSArray *)containers { NSMutableArray *containers = [NSMutableArray array]; for (const hb_container_t *container = hb_container_get_next(NULL); container != NULL; container = hb_container_get_next(container)) { NSString *title = nil; if (container->format & HB_MUX_MASK_MP4) { title = NSLocalizedString(@"MP4 File", @""); } else if (container->format & HB_MUX_MASK_MKV) { title = NSLocalizedString(@"MKV File", @""); } else { title = @(container->name); } [containers addObject:title]; } return [containers copy]; } - (void)initStyles { if (!ps) { // Attributes ps = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; [ps setHeadIndent: 40.0]; [ps setParagraphSpacing: 1.0]; [ps setTabStops:@[]]; // clear all tabs [ps addTabStop: [[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0]]; detailAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]], NSParagraphStyleAttributeName: ps}; detailBoldAttr = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]], NSParagraphStyleAttributeName: ps}; titleAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]], NSParagraphStyleAttributeName: ps}; shortHeightAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:2.0]}; } } - (NSAttributedString *)attributedDescription { // Below should be put into a separate method but I am way too f'ing lazy right now NSMutableAttributedString *finalString = [[NSMutableAttributedString alloc] initWithString: @""]; [self initStyles]; @autoreleasepool { // First line, we should strip the destination path and just show the file name and add the title num and chapters (if any) NSString *summaryInfo; NSString *titleString = [NSString stringWithFormat:@"Title %d", self.titleIdx]; NSString *startStopString = @""; if (self.range.type == HBRangeTypeChapters) { // Start Stop is chapters startStopString = (self.range.chapterStart == self.range.chapterStop) ? [NSString stringWithFormat:@"Chapter %d", self.range.chapterStart + 1] : [NSString stringWithFormat:@"Chapters %d through %d", self.range.chapterStart + 1, self.range.chapterStop + 1]; } else if (self.range.type == HBRangeTypeSeconds) { // Start Stop is seconds startStopString = [NSString stringWithFormat:@"Seconds %d through %d", self.range.secondsStart, self.range.secondsStop]; } else if (self.range.type == HBRangeTypeFrames) { // Start Stop is Frames startStopString = [NSString stringWithFormat:@"Frames %d through %d", self.range.frameStart, self.range.frameStop]; } NSString *passesString = @""; // check to see if our first subtitle track is Foreign Language Search, in which case there is an in depth scan if (self.subtitles.tracks.firstObject.sourceTrackIdx == 1) { passesString = [passesString stringByAppendingString:@"1 Foreign Language Search Pass - "]; } if (self.video.qualityType == 1 || self.video.twoPass == NO) { passesString = [passesString stringByAppendingString:@"1 Video Pass"]; } else { if (self.video.turboTwoPass == YES) { passesString = [passesString stringByAppendingString:@"2 Video Passes First Turbo"]; } else { passesString = [passesString stringByAppendingString:@"2 Video Passes"]; } } [finalString appendString:[NSString stringWithFormat:@"%@", self.description] withAttributes:titleAttr]; // lets add the output file name to the title string here NSString *outputFilenameString = self.destURL.lastPathComponent; summaryInfo = [NSString stringWithFormat: @" (%@, %@, %@) -> %@", titleString, startStopString, passesString, outputFilenameString]; [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttr]; // Insert a short-in-height line to put some white space after the title [finalString appendString:@"\n" withAttributes:shortHeightAttr]; // End of Title Stuff // Second Line (Preset Name) [finalString appendString: @"Preset: " withAttributes:detailBoldAttr]; [finalString appendString:[NSString stringWithFormat:@"%@\n", self.presetName] withAttributes:detailAttr]; // Third Line (Format Summary) NSString *audioCodecSummary = @""; // This seems to be set by the last track we have available... // Lets also get our audio track detail since we are going through the logic for use later NSMutableArray *audioDetails = [NSMutableArray array]; BOOL autoPassthruPresent = NO; for (HBAudioTrack *audioTrack in self.audio.tracks) { if (audioTrack.isEnabled) { const char *codecName = hb_audio_encoder_get_name(audioTrack.encoder); const char *mixdownName = hb_mixdown_get_name(audioTrack.mixdown); const char *sampleRateName = audioTrack.sampleRate ? hb_audio_samplerate_get_name(audioTrack.sampleRate) : "Auto"; audioCodecSummary = [NSString stringWithFormat: @"%@", @(codecName)]; NSString *detailString = [NSString stringWithFormat: @"%@ Encoder: %@, Mixdown: %@, SampleRate: %@ khz, Bitrate: %d kbps, DRC: %@, Gain: %@", self.audio.sourceTracks[audioTrack.sourceTrackIdx][keyAudioTrackName], @(codecName), @(mixdownName), @(sampleRateName), audioTrack.bitRate, (0.0 < audioTrack.drc) ? @(audioTrack.drc) : NSLocalizedString(@"Off", nil), (0.0 != audioTrack.gain) ? @(audioTrack.gain) : NSLocalizedString(@"Off", nil) ]; [audioDetails addObject: detailString]; // check if we have an Auto Passthru output track if ([@(codecName) isEqualToString: @"Auto Passthru"]) { autoPassthruPresent = YES; } } } NSString *jobFormatInfo; if (self.chaptersEnabled) jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video %@ Audio, Chapter Markers\n", @(hb_container_get_name(self.container)), @(hb_video_encoder_get_name(self.video.encoder)), audioCodecSummary]; else jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video %@ Audio\n", @(hb_container_get_name(self.container)), @(hb_video_encoder_get_name(self.video.encoder)), audioCodecSummary]; [finalString appendString: @"Format: " withAttributes:detailBoldAttr]; [finalString appendString: jobFormatInfo withAttributes:detailAttr]; // Optional String for muxer options NSMutableString *containerOptions = [NSMutableString stringWithString:@""]; if ((self.container & HB_MUX_MASK_MP4) && self.mp4HttpOptimize) { [containerOptions appendString:@" - Web optimized"]; } if ((self.container & HB_MUX_MASK_MP4) && self.mp4iPodCompatible) { [containerOptions appendString:@" - iPod 5G support"]; } if ([containerOptions hasPrefix:@" - "]) { [containerOptions deleteCharactersInRange:NSMakeRange(0, 3)]; } if (containerOptions.length) { [finalString appendString:@"Container Options: " withAttributes:detailBoldAttr]; [finalString appendString:containerOptions withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; } // Fourth Line (Destination Path) [finalString appendString: @"Destination: " withAttributes:detailBoldAttr]; [finalString appendString: self.destURL.path withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; // Fifth Line Picture Details NSString *pictureInfo = [NSString stringWithFormat:@"%@", self.picture.summary]; if (self.picture.keepDisplayAspect) { pictureInfo = [pictureInfo stringByAppendingString:@" Keep Aspect Ratio"]; } [finalString appendString:@"Picture: " withAttributes:detailBoldAttr]; [finalString appendString:pictureInfo withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; /* Optional String for Picture Filters */ if (self.filters.summary.length) { NSString *pictureFilters = [NSString stringWithFormat:@"%@", self.filters.summary]; [finalString appendString:@"Filters: " withAttributes:detailBoldAttr]; [finalString appendString:pictureFilters withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; } // Sixth Line Video Details NSString * videoInfo = [NSString stringWithFormat:@"Encoder: %@", @(hb_video_encoder_get_name(self.video.encoder))]; // for framerate look to see if we are using vfr detelecine if (self.video.frameRate == 0) { if (self.video.frameRateMode == 0) { // we are using same as source with vfr detelecine videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (Variable Frame Rate)", videoInfo]; } else { // we are using a variable framerate without dropping frames videoInfo = [NSString stringWithFormat:@"%@ Framerate: Same as source (Constant Frame Rate)", videoInfo]; } } else { // we have a specified, constant framerate if (self.video.frameRateMode == 0) { videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (Peak Frame Rate)", videoInfo, @(hb_video_framerate_get_name(self.video.frameRate))]; } else { videoInfo = [NSString stringWithFormat:@"%@ Framerate: %@ (Constant Frame Rate)", videoInfo, @(hb_video_framerate_get_name(self.video.frameRate))]; } } if (self.video.qualityType == 0) // ABR { videoInfo = [NSString stringWithFormat:@"%@ Bitrate: %d(kbps)", videoInfo, self.video.avgBitrate]; } else // CRF { videoInfo = [NSString stringWithFormat:@"%@ Constant Quality: %.2f", videoInfo ,self.video.quality]; } [finalString appendString: @"Video: " withAttributes:detailBoldAttr]; [finalString appendString: videoInfo withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; if (hb_video_encoder_get_presets(self.video.encoder) != NULL) { // we are using x264/x265 NSString *encoderPresetInfo = @""; if (self.video.advancedOptions) { // we are using the old advanced panel if (self.video.videoOptionExtra.length) { encoderPresetInfo = [encoderPresetInfo stringByAppendingString:self.video.videoOptionExtra]; } else { encoderPresetInfo = [encoderPresetInfo stringByAppendingString:@"default settings"]; } } else { // we are using the x264 system encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@"Preset: %@", self.video.preset]]; if (self.video.tune.length || self.video.fastDecode) { encoderPresetInfo = [encoderPresetInfo stringByAppendingString:@" - Tune: "]; if (self.video.tune.length) { encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@"%@", self.video.tune]]; if (self.video.fastDecode) { encoderPresetInfo = [encoderPresetInfo stringByAppendingString:@","]; } } if (self.video.fastDecode) { encoderPresetInfo = [encoderPresetInfo stringByAppendingString:@"fastdecode"]; } } if (self.video.videoOptionExtra.length) { encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Options: %@", self.video.videoOptionExtra]]; } if (self.video.profile.length) { encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Profile: %@", self.video.profile]]; } if (self.video.level.length) { encoderPresetInfo = [encoderPresetInfo stringByAppendingString: [NSString stringWithFormat:@" - Level: %@", self.video.level]]; } } [finalString appendString: @"Video Options: " withAttributes:detailBoldAttr]; [finalString appendString: encoderPresetInfo withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; } else { // we are using libavcodec NSString *lavcInfo = @""; if (self.video.videoOptionExtra.length) { lavcInfo = [lavcInfo stringByAppendingString:self.video.videoOptionExtra]; } else { lavcInfo = [lavcInfo stringByAppendingString: @"default settings"]; } [finalString appendString: @"Encoder Options: " withAttributes:detailBoldAttr]; [finalString appendString: lavcInfo withAttributes:detailAttr]; [finalString appendString:@"\n" withAttributes:detailAttr]; } // Seventh Line Audio Details for (NSString *anAudioDetail in audioDetails) { if (anAudioDetail.length) { [finalString appendString: [NSString stringWithFormat: @"Audio: "] withAttributes: detailBoldAttr]; [finalString appendString: anAudioDetail withAttributes: detailAttr]; [finalString appendString: @"\n" withAttributes: detailAttr]; } } // Eigth Line Auto Passthru Details // only print Auto Passthru settings if we have an Auro Passthru output track if (autoPassthruPresent == YES) { NSString *autoPassthruFallback = @"", *autoPassthruCodecs = @""; HBAudioDefaults *audioDefaults = self.audio.defaults; autoPassthruFallback = [autoPassthruFallback stringByAppendingString:@(hb_audio_encoder_get_name(audioDefaults.encoderFallback))]; if (audioDefaults.allowAACPassthru) { autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"AAC"]; } if (audioDefaults.allowAC3Passthru) { if (autoPassthruCodecs.length) { autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; } autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"AC3"]; } if (audioDefaults.allowDTSHDPassthru) { if (autoPassthruCodecs.length) { autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; } autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"DTS-HD"]; } if (audioDefaults.allowDTSPassthru) { if (autoPassthruCodecs.length) { autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; } autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"DTS"]; } if (audioDefaults.allowMP3Passthru) { if (autoPassthruCodecs.length) { autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@", "]; } autoPassthruCodecs = [autoPassthruCodecs stringByAppendingString:@"MP3"]; } [finalString appendString: @"Auto Passthru Codecs: " withAttributes: detailBoldAttr]; if (autoPassthruCodecs.length) { [finalString appendString: autoPassthruCodecs withAttributes: detailAttr]; } else { [finalString appendString: @"None" withAttributes: detailAttr]; } [finalString appendString: @"\n" withAttributes: detailAttr]; [finalString appendString: @"Auto Passthru Fallback: " withAttributes: detailBoldAttr]; [finalString appendString: autoPassthruFallback withAttributes: detailAttr]; [finalString appendString: @"\n" withAttributes: detailAttr]; } // Ninth Line Subtitle Details for (HBSubtitlesTrack *track in self.subtitles.tracks) { // Ignore the none track. if (track.isEnabled) { // remember that index 0 of Subtitles can contain "Foreign Audio Search [finalString appendString: @"Subtitle: " withAttributes:detailBoldAttr]; [finalString appendString: self.subtitles.sourceTracks[track.sourceTrackIdx][@"keySubTrackName"] withAttributes:detailAttr]; if (track.forcedOnly) { [finalString appendString: @" - Forced Only" withAttributes:detailAttr]; } if (track.burnedIn) { [finalString appendString: @" - Burned In" withAttributes:detailAttr]; } if (track.def) { [finalString appendString: @" - Default" withAttributes:detailAttr]; } [finalString appendString:@"\n" withAttributes:detailAttr]; } } } [finalString deleteCharactersInRange:NSMakeRange(finalString.length - 1, 1)]; return finalString; } @end @implementation HBContainerTransformer + (Class)transformedValueClass { return [NSString class]; } - (id)transformedValue:(id)value { int container = [value intValue]; if (container & HB_MUX_MASK_MP4) { return NSLocalizedString(@"MP4 File", @""); } else if (container & HB_MUX_MASK_MKV) { return NSLocalizedString(@"MKV File", @""); } else { const char *name = hb_container_get_name(container); if (name) { return @(name); } else { return nil; } } } + (BOOL)allowsReverseTransformation { return YES; } - (id)reverseTransformedValue:(id)value { if ([value isEqualToString:NSLocalizedString(@"MP4 File", @"")]) { return @(HB_MUX_AV_MP4); } else if ([value isEqualToString:NSLocalizedString(@"MKV File", @"")]) { return @(HB_MUX_AV_MKV); } return @(hb_container_get_from_name([value UTF8String])); } @end @implementation HBURLTransformer + (Class)transformedValueClass { return [NSString class]; } - (id)transformedValue:(id)value { if (value) return [value path]; else return nil; } + (BOOL)allowsReverseTransformation { return YES; } - (id)reverseTransformedValue:(id)value { if (value) { return [NSURL fileURLWithPath:value]; } return nil; } @end