/* 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