3 This file is part of the HandBrake source code.
4 Homepage: <http://handbrake.fr/>.
5 It may be used under the terms of the GNU General Public License. */
9 #import "HBCodingUtilities.h"
12 NSString * const HBVideoChangedNotification = @"HBVideoChangedNotification";
16 @property (nonatomic, readwrite) double qualityMinValue;
17 @property (nonatomic, readwrite) double qualityMaxValue;
19 @property (nonatomic, readwrite) NSUInteger mediumPresetIndex;
21 @property (nonatomic, readwrite, getter=areNotificationsEnabled) BOOL notificationsEnabled;
25 @implementation HBVideo
27 - (instancetype)initWithJob:(HBJob *)job;
31 _encoder = HB_VCODEC_X264;
34 _qualityMaxValue = 51.0f;
42 [self updateQualityBounds];
44 _notificationsEnabled = YES;
49 - (void)postChangedNotification
51 if (self.areNotificationsEnabled)
53 [[NSNotificationCenter defaultCenter] postNotification: [NSNotification notificationWithName:HBVideoChangedNotification
59 #pragma mark - Setters
62 * Updates the min/max quality values
64 - (void)updateQualityBounds
66 // Get the current slider maxValue to check for a change in slider scale
67 // later so that we can choose a new similar value on the new slider scale
68 double previousMaxValue = self.qualityMaxValue;
69 double previousPercentOfSliderScale = (self.quality / (self.qualityMaxValue - self.qualityMinValue + 1));
72 float minValue, maxValue, granularity;
73 hb_video_quality_get_limits(self.encoder,
74 &minValue, &maxValue, &granularity, &direction);
76 self.qualityMinValue = minValue;
77 self.qualityMaxValue = maxValue;
79 // check to see if we have changed slider scales
80 if (previousMaxValue != maxValue)
82 // if so, convert the old setting to the new scale as close as possible
83 // based on percentages
84 self.quality = floor((maxValue - minValue + 1.) * (previousPercentOfSliderScale));
88 - (void)setEncoder:(int)encoder
91 [self updateQualityBounds];
92 [self validatePresetsSettings];
93 [self validateAdvancedOptions];
95 [self postChangedNotification];
98 - (void)setQualityType:(int)qualityType
100 _qualityType = qualityType;
101 [self postChangedNotification];
104 - (void)setAvgBitrate:(int)avgBitrate
106 _avgBitrate = avgBitrate;
107 [self postChangedNotification];
110 - (void)setQuality:(double)quality
113 [self postChangedNotification];
116 - (void)setFrameRate:(int)frameRate
118 _frameRate = frameRate;
119 [self postChangedNotification];
122 - (void)setFrameRateMode:(int)frameRateMode
124 _frameRateMode = frameRateMode;
125 [self postChangedNotification];
128 - (void)setTwoPass:(BOOL)twoPass
131 [self postChangedNotification];
134 - (void)setTurboTwoPass:(BOOL)turboTwoPass
136 _turboTwoPass = turboTwoPass;
137 [self postChangedNotification];
140 - (void)containerChanged
142 BOOL encoderSupported = NO;
144 for (const hb_encoder_t *video_encoder = hb_video_encoder_get_next(NULL);
145 video_encoder != NULL;
146 video_encoder = hb_video_encoder_get_next(video_encoder))
148 if (video_encoder->muxers & self.job.container)
150 if (video_encoder->codec == self.encoder)
152 encoderSupported = YES;
157 if (!encoderSupported)
159 self.encoder = HB_VCODEC_X264;
163 - (void)setPreset:(NSString *)preset
165 _preset = [preset copy];
166 [self postChangedNotification];
169 - (void)setTune:(NSString *)tune
171 if (![tune isEqualToString:@"none"])
180 [self postChangedNotification];
183 - (void)setProfile:(NSString *)profile
185 _profile = [profile copy];
186 [self postChangedNotification];
189 - (void)setLevel:(NSString *)level
191 _level = [level copy];
192 [self postChangedNotification];
195 - (void)setVideoOptionExtra:(NSString *)videoOptionExtra
197 if (videoOptionExtra != nil)
199 _videoOptionExtra = [videoOptionExtra copy];
203 _videoOptionExtra = @"";
205 [self postChangedNotification];
208 - (void)setFastDecode:(BOOL)fastDecode
210 _fastDecode = fastDecode;
211 [self postChangedNotification];
214 - (void)validatePresetsSettings
216 NSArray *presets = self.presets;
217 if (presets.count && ![presets containsObject:self.preset]) {
218 self.preset = presets[self.mediumPresetIndex];
221 NSArray *tunes = self.tunes;
222 if (tunes.count && ![tunes containsObject:self.tune]) {
223 self.tune = tunes.firstObject;
226 NSArray *profiles = self.profiles;
227 if (profiles.count && ![profiles containsObject:self.profile]) {
228 self.profile = profiles.firstObject;
231 NSArray *levels = self.levels;
232 if (levels.count && ![levels containsObject:self.level]) {
233 self.level = levels.firstObject;
237 - (void)validateAdvancedOptions
239 if (self.encoder != HB_VCODEC_H264_MASK)
241 self.advancedOptions = NO;
245 + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
249 // Tell KVO to reload the presets settings
250 // after a change to the encoder.
251 if ([key isEqualToString:@"presets"] ||
252 [key isEqualToString:@"tunes"] ||
253 [key isEqualToString:@"profiles"] ||
254 [key isEqualToString:@"levels"])
256 retval = [NSSet setWithObjects:@"encoder", nil];
259 // Tell KVO to reload the x264 unparse string
260 // after values changes.
261 if ([key isEqualToString:@"unparseOptions"])
263 retval = [NSSet setWithObjects:@"encoder", @"preset", @"tune", @"profile", @"level",
264 @"videoOptionExtra", @"fastDecode", @"job.picture.width", @"job.picture.height", nil];
267 if ([key isEqualToString:@"encoders"])
269 retval = [NSSet setWithObjects:@"job.container", nil];
272 if ([key isEqualToString:@"fastDecodeSupported"] ||
273 [key isEqualToString:@"turboTwoPassSupported"])
275 retval = [NSSet setWithObjects:@"encoder", nil];
281 - (void)setNilValueForKey:(NSString *)key
283 [self setValue:@0 forKey:key];
290 NSMutableArray *temp = [NSMutableArray array];
292 const char * const *presets = hb_video_encoder_get_presets(self.encoder);
293 for (int i = 0; presets != NULL && presets[i] != NULL; i++)
295 [temp addObject:@(presets[i])];
296 if (!strcasecmp(presets[i], "medium"))
298 self.mediumPresetIndex = i;
307 NSMutableArray *temp = [NSMutableArray array];
309 [temp addObject:@"none"];
311 const char * const *tunes = hb_video_encoder_get_tunes(self.encoder);
313 for (int i = 0; tunes != NULL && tunes[i] != NULL; i++)
315 // we filter out "fastdecode" as we have a dedicated checkbox for it
316 if (strcasecmp(tunes[i], "fastdecode") != 0)
318 [temp addObject:@(tunes[i])];
325 - (NSArray *)profiles
327 NSMutableArray *temp = [NSMutableArray array];
329 const char * const *profiles = hb_video_encoder_get_profiles(self.encoder);
330 for (int i = 0; profiles != NULL && profiles[i] != NULL; i++)
332 [temp addObject:@(profiles[i])];
336 [temp addObject:@"auto"];
344 NSMutableArray *temp = [NSMutableArray array];
346 const char * const *levels = hb_video_encoder_get_levels(self.encoder);
347 for (int i = 0; levels != NULL && levels[i] != NULL; i++)
349 [temp addObject:@(levels[i])];
353 [temp addObject:@"auto"];
359 #pragma mark - NSCopying
361 - (instancetype)copyWithZone:(NSZone *)zone
363 HBVideo *copy = [[[self class] alloc] init];
367 copy->_encoder = _encoder;
369 copy->_qualityType = _qualityType;
370 copy->_avgBitrate = _avgBitrate;
371 copy->_quality = _quality;
373 copy->_qualityMinValue = _qualityMinValue;
374 copy->_qualityMaxValue = _qualityMaxValue;
376 copy->_frameRate = _frameRate;
377 copy->_frameRateMode = _frameRateMode;
379 copy->_twoPass = _twoPass;
380 copy->_turboTwoPass = _turboTwoPass;
382 copy->_advancedOptions = _advancedOptions;
383 copy->_preset = [_preset copy];
384 copy->_tune = [_tune copy];
385 copy->_profile = [_profile copy];
386 copy->_level = [_level copy];
387 copy->_videoOptionExtra = [_videoOptionExtra copy];
388 copy->_fastDecode = _fastDecode;
390 copy->_notificationsEnabled = _notificationsEnabled;
396 #pragma mark - NSCoding
398 + (BOOL)supportsSecureCoding
403 - (void)encodeWithCoder:(NSCoder *)coder
405 [coder encodeInt:1 forKey:@"HBVideoVersion"];
409 encodeInt(_qualityType);
410 encodeInt(_avgBitrate);
411 encodeDouble(_quality);
413 encodeDouble(_qualityMinValue);
414 encodeDouble(_qualityMaxValue);
416 encodeInt(_frameRate);
417 encodeInt(_frameRateMode);
419 encodeBool(_twoPass);
420 encodeBool(_turboTwoPass);
422 encodeBool(_advancedOptions);
423 encodeObject(_preset);
425 encodeObject(_profile);
426 encodeObject(_level);
428 encodeObject(_videoOptionExtra);
430 encodeBool(_fastDecode);
433 - (instancetype)initWithCoder:(NSCoder *)decoder
439 decodeInt(_qualityType);
440 decodeInt(_avgBitrate);
441 decodeDouble(_quality);
443 decodeDouble(_qualityMinValue);
444 decodeDouble(_qualityMaxValue);
446 decodeInt(_frameRate);
447 decodeInt(_frameRateMode);
449 decodeBool(_twoPass);
450 decodeBool(_turboTwoPass);
452 decodeBool(_advancedOptions);
453 decodeObject(_preset, NSString);
454 decodeObject(_tune, NSString);
455 decodeObject(_profile, NSString);
456 decodeObject(_level, NSString);
458 decodeObject(_videoOptionExtra, NSString);
460 decodeBool(_fastDecode);
462 _notificationsEnabled = YES;
467 #pragma mark - Various conversion methods from dict/preset/queue/etc
470 * Returns a string minus the fastdecode string.
472 - (NSString *)stripFastDecodeFromString:(NSString *)tune
474 // filter out fastdecode
475 tune = [tune stringByReplacingOccurrencesOfString:@"," withString:@""];
476 tune = [tune stringByReplacingOccurrencesOfString:@"fastdecode" withString:@""];
482 * Retuns the tune string plus the fastdecode option (if enabled)
484 - (NSString *)completeTune
486 NSMutableString *string = [[NSMutableString alloc] init];
490 [string appendString:self.tune];
497 [string appendString:@","];
500 [string appendString:@"fastdecode"];
506 - (void)applyPreset:(NSDictionary *)preset
508 self.notificationsEnabled = NO;
510 // map legacy encoder names via libhb.
511 self.encoder = hb_video_encoder_get_from_name([preset[@"VideoEncoder"] UTF8String]);
513 if (hb_video_encoder_get_presets(self.encoder) != NULL)
515 if (self.encoder == HB_VCODEC_X264 && [preset[@"x264UseAdvancedOptions"] boolValue])
517 // preset does not use the x264 preset system, reset the widgets.
518 self.preset = @"medium";
520 self.profile = @"auto";
521 self.level = @"auto";
522 self.fastDecode = NO;
524 self.videoOptionExtra = preset[@"VideoOptionExtra"];
525 self.advancedOptions = YES;
529 // x264UseAdvancedOptions is set to 0 (disabled),
530 // so we use the new preset system and
531 // disable the advanced panel
532 self.advancedOptions = NO;
534 self.preset = preset[@"VideoPreset"];
535 self.tune = preset[@"VideoTune"];
536 self.videoOptionExtra = preset[@"VideoOptionExtra"];
537 self.profile = preset[@"VideoProfile"];
538 self.level = preset[@"VideoLevel"];
540 if ([self.tune rangeOfString:@"fastdecode"].location != NSNotFound)
542 self.fastDecode = YES;;
546 self.fastDecode = NO;
549 self.tune = [self stripFastDecodeFromString:self.tune];
554 self.videoOptionExtra = preset[@"VideoOptionExtra"];
557 int qualityType = [preset[@"VideoQualityType"] intValue] - 1;
558 /* Note since the removal of Target Size encoding, the possible values for VideoQuality type are 0 - 1.
559 * Therefore any preset that uses the old 2 for Constant Quality would now use 1 since there is one less index
560 * for the fVidQualityMatrix. It should also be noted that any preset that used the deprecated Target Size
561 * setting of 0 would set us to 0 or ABR since ABR is now tagged 0. Fortunately this does not affect any built-in
562 * presets since they all use Constant Quality or Average Bitrate.*/
563 if (qualityType == -1)
567 self.qualityType = qualityType;
569 self.avgBitrate = [preset[@"VideoAvgBitrate"] intValue];
570 self.quality = [preset[@"VideoQualitySlider"] floatValue];
573 if ([preset[@"VideoFramerate"] isEqualToString:@"auto"])
575 // Now set the Video Frame Rate Mode to either vfr or cfr according to the preset.
576 if (!preset[@"VideoFramerateMode"] ||
577 [preset[@"VideoFramerateMode"] isEqualToString:@"vfr"])
579 self.frameRateMode = 0; // we want vfr
583 self.frameRateMode = 1; // we want cfr
588 // Now set the Video Frame Rate Mode to either pfr or cfr according to the preset.
589 if ([preset[@"VideoFramerateMode"] isEqualToString:@"pfr"] ||
590 [preset[@"VideoFrameratePFR"] intValue] == 1)
592 self.frameRateMode = 0; // we want pfr
596 self.frameRateMode = 1; // we want cfr
599 // map legacy names via libhb.
600 int intValue = hb_video_framerate_get_from_name([preset[@"VideoFramerate"] UTF8String]);
605 self.frameRate = intValue;
608 self.twoPass = [preset[@"VideoTwoPass"] boolValue];
610 // Turbo 1st pass for 2 Pass Encoding.
611 self.turboTwoPass = [preset[@"VideoTurboTwoPass"] boolValue];
613 self.notificationsEnabled = YES;
616 - (void)writeToPreset:(NSMutableDictionary *)preset
618 preset[@"VideoEncoder"] = @(hb_video_encoder_get_short_name(self.encoder));
620 if (hb_video_encoder_get_presets(self.encoder) != NULL)
622 preset[@"VideoPreset"] = self.preset;
623 preset[@"VideoTune"] = [self completeTune];
624 preset[@"VideoOptionExtra"] = self.videoOptionExtra;
625 preset[@"VideoProfile"] = self.profile;
626 preset[@"VideoLevel"] = self.level;
628 // x264 Options, this will either be advanced panel or the video tabs x264 presets panel with modded option string
629 if (self.advancedOptions)
631 // use the old advanced panel.
632 preset[@"x264UseAdvancedOptions"] = @YES;
636 preset[@"x264UseAdvancedOptions"] = @NO;
641 // FFmpeg (lavc) Option String
642 preset[@"VideoOptionExtra"] = self.videoOptionExtra;
645 /* though there are actually only 0 - 1 types available in the ui we need to map to the old 0 - 2
646 * set of indexes from when we had 0 == Target , 1 == Abr and 2 == Constant Quality for presets
647 * to take care of any legacy presets. */
648 preset[@"VideoQualityType"] = @(self.qualityType + 1);
649 preset[@"VideoAvgBitrate"] = @(self.avgBitrate);
650 preset[@"VideoQualitySlider"] = @(self.quality);
652 /* Video framerate and framerate mode */
653 if (self.frameRateMode == 1)
655 preset[@"VideoFramerateMode"] = @"cfr";
657 if (self.frameRate == 0) // Same as source is selected
659 preset[@"VideoFramerate"] = @"auto";
661 if (self.frameRateMode == 0)
663 preset[@"VideoFramerateMode"] = @"vfr";
666 else // translate the rate (selected item's tag) to the official libhb name
668 preset[@"VideoFramerate"] = [NSString stringWithFormat:@"%s",
669 hb_video_framerate_get_name((int)self.frameRate)];
671 if (self.frameRateMode == 0)
673 preset[@"VideoFramerateMode"] = @"pfr";
677 preset[@"VideoTwoPass"] = @(self.twoPass);
678 preset[@"VideoTurboTwoPass"] = @(self.turboTwoPass);