/* HBAudioTrackPreset.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 "HBAudioTrackPreset.h" #import "HBCodingUtilities.h" #include "handbrake/handbrake.h" #define DEFAULT_SAMPLERATE 48000 @interface HBAudioTrackPreset () @property (nonatomic, readwrite) int container; @property (nonatomic, readwrite) int selectedEncoder; @end @implementation HBAudioTrackPreset - (instancetype)init { self = [super init]; if (self) { // defaults settings _encoder = HB_ACODEC_CA_AAC; _selectedEncoder = HB_ACODEC_INVALID; _container = HB_MUX_MKV; _sampleRate = 0; _bitRate = 160; _mixdown = HB_AMIXDOWN_DOLBYPLII; } return self; } - (instancetype)initWithContainer:(int)container { self = [self init]; if (self) { _container = container; } return self; } - (void)containerChanged:(int)container { // Do things here } #pragma mark - Setters override - (void)setEncoder:(int)encoder { if (encoder != _encoder) { [[self.undo prepareWithInvocationTarget:self] setEncoder:_encoder]; } _encoder = encoder; if (!(self.undo.isUndoing || self.undo.isRedoing)) { [self validateFallbackEncoder]; } } - (void)setFallbackEncoder:(int)fallbackEncoder { if (fallbackEncoder != _fallbackEncoder) { [[self.undo prepareWithInvocationTarget:self] setFallbackEncoder:_fallbackEncoder]; } _fallbackEncoder = fallbackEncoder; if (!(self.undo.isUndoing || self.undo.isRedoing)) { [self validateFallbackEncoder]; } } - (void)setSelectedEncoder:(int)fallbackEncoder { if (fallbackEncoder != _selectedEncoder) { [[self.undo prepareWithInvocationTarget:self] setFallbackEncoder:_fallbackEncoder]; } _selectedEncoder = fallbackEncoder; if (!(self.undo.isUndoing || self.undo.isRedoing)) { [self validateMixdown]; [self validateSamplerate]; [self validateBitrate]; } } - (void)setMixdown:(int)mixdown { if (mixdown == HB_AMIXDOWN_NONE) { mixdown = hb_mixdown_get_default(self.selectedEncoder, 0); } if (mixdown != _mixdown) { [[self.undo prepareWithInvocationTarget:self] setMixdown:_mixdown]; } _mixdown = mixdown; if (!(self.undo.isUndoing || self.undo.isRedoing)) { [self validateBitrate]; } } - (void)setSampleRate:(int)sampleRate { if (sampleRate != _sampleRate) { [[self.undo prepareWithInvocationTarget:self] setSampleRate:_sampleRate]; } _sampleRate = sampleRate; if (!(self.undo.isUndoing || self.undo.isRedoing)) { [self validateBitrate]; } } - (void)setBitRate:(int)bitRate { if (bitRate != _bitRate) { [[self.undo prepareWithInvocationTarget:self] setBitRate:_bitRate]; } _bitRate = bitRate; } #pragma mark - #pragma mark Validation /** If the encoder is a passthru, return its fallback if available to make possible to set the fallback settings. */ - (void)validateFallbackEncoder { if (_encoder & HB_ACODEC_PASS_FLAG) { int fallbackEncoder = hb_audio_encoder_get_fallback_for_passthru(_encoder); self.selectedEncoder = (fallbackEncoder != HB_ACODEC_INVALID) ? fallbackEncoder : self.fallbackEncoder; } else { self.selectedEncoder = self.encoder; } } - (void)validateMixdown { if (!hb_mixdown_has_codec_support(self.mixdown, self.selectedEncoder)) { self.mixdown = hb_mixdown_get_default(self.selectedEncoder, 0); } } - (void)validateSamplerate { if (self.selectedEncoder & HB_ACODEC_PASS_FLAG) { self.sampleRate = 0; // Auto (same as source) } else if (self.sampleRate) { self.sampleRate = hb_audio_samplerate_find_closest(self.sampleRate, self.selectedEncoder); } } - (void)validateBitrate { if (self.selectedEncoder & HB_ACODEC_PASS_FLAG) { self.bitRate = -1; } else if (self.bitRate == -1) // switching from passthru { self.bitRate = hb_audio_bitrate_get_default(self.selectedEncoder, self.sampleRate ? self.sampleRate : DEFAULT_SAMPLERATE, self.mixdown); } else { self.bitRate = hb_audio_bitrate_get_best(self.selectedEncoder, self.bitRate, self.sampleRate ? self.sampleRate : DEFAULT_SAMPLERATE, self.mixdown); } } - (BOOL)isAutoPassthruEnabledWithNoFallback { return (self.encoder == HB_ACODEC_AUTO_PASS && self.fallbackEncoder == HB_ACODEC_NONE); } - (BOOL)mixdownEnabled { BOOL retval = YES; if (self.mixdown == HB_AMIXDOWN_NONE || self.isAutoPassthruEnabledWithNoFallback) { // "None" mixdown (passthru) retval = NO; } return retval; } - (BOOL)bitrateEnabled { BOOL retval = YES; int myCodecDefaultBitrate = hb_audio_bitrate_get_default(self.selectedEncoder, 0, 0); if (myCodecDefaultBitrate < 0 || self.isAutoPassthruEnabledWithNoFallback) { retval = NO; } return retval; } - (BOOL)passThruDisabled { BOOL retval = YES; if (self.selectedEncoder & HB_ACODEC_PASS_FLAG || self.isAutoPassthruEnabledWithNoFallback) { retval = NO; } return retval; } - (void)setGain:(double)gain { if (gain != _gain) { [[self.undo prepareWithInvocationTarget:self] setGain:_gain]; } _gain = gain; } // Because we have indicated that the binding for the gain validates immediately we can implement the // key value binding method to ensure the gain stays in our accepted range. - (BOOL)validateGain:(id *)ioValue error:(NSError * __autoreleasing *)outError { BOOL retval = YES; if (nil != *ioValue) { if ([*ioValue intValue] < -20) { *ioValue = @(-20); } else if ([*ioValue intValue] > 20) { *ioValue = @20; } } return retval; } - (void)setDrc:(double)drc { if (drc != _drc) { [[self.undo prepareWithInvocationTarget:self] setDrc:_drc]; } _drc = drc; } #pragma mark - Options - (NSArray *)encoders { NSMutableArray *encoders = [[NSMutableArray alloc] init]; for (const hb_encoder_t *audio_encoder = hb_audio_encoder_get_next(NULL); audio_encoder != NULL; audio_encoder = hb_audio_encoder_get_next(audio_encoder)) { if (audio_encoder->codec != HB_ACODEC_NONE) { [encoders addObject:@(audio_encoder->name)]; } } return encoders; } - (NSArray *)mixdowns { NSMutableArray *mixdowns = [[NSMutableArray alloc] init]; for (const hb_mixdown_t *mixdown = hb_mixdown_get_next(NULL); mixdown != NULL; mixdown = hb_mixdown_get_next(mixdown)) { if (hb_mixdown_has_codec_support(mixdown->amixdown, self.selectedEncoder)) { [mixdowns addObject:@(mixdown->name)]; } } return mixdowns; } - (NSArray *)sampleRates { NSMutableArray *sampleRates = [[NSMutableArray alloc] init]; [sampleRates addObject:@"Auto"]; for (const hb_rate_t *audio_samplerate = hb_audio_samplerate_get_next(NULL); audio_samplerate != NULL; audio_samplerate = hb_audio_samplerate_get_next(audio_samplerate)) { int rate = audio_samplerate->rate; if (rate == hb_audio_samplerate_find_closest(rate, self.selectedEncoder)) { [sampleRates addObject:@(audio_samplerate->name)]; } } return sampleRates; } - (NSArray *)bitRates { int minBitRate = 0; int maxBitRate = 0; hb_audio_bitrate_get_limits(self.selectedEncoder, self.sampleRate, self.mixdown, &minBitRate, &maxBitRate); NSMutableArray *bitRates = [[NSMutableArray alloc] init]; for (const hb_rate_t *audio_bitrate = hb_audio_bitrate_get_next(NULL); audio_bitrate != NULL; audio_bitrate = hb_audio_bitrate_get_next(audio_bitrate)) { if (audio_bitrate->rate >= minBitRate && audio_bitrate->rate <= maxBitRate) { [bitRates addObject:@(audio_bitrate->name)]; } } return bitRates; } + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *retval = nil; // Tell KVO to reload the *enabled keyPaths // after a change to encoder. if ([key isEqualToString:@"bitrateEnabled"] || [key isEqualToString:@"passThruDisabled"] || [key isEqualToString:@"mixdownEnabled"]) { retval = [NSSet setWithObjects:@"selectedEncoder", @"encoder", @"fallbackEncoder", @"mixdown", @"sampleRate", nil]; } else if ([key isEqualToString:@"mixdowns"]) { retval = [NSSet setWithObjects:@"selectedEncoder", @"encoder", @"fallbackEncoder", nil]; } else if ([key isEqualToString:@"sampleRates"]) { retval = [NSSet setWithObjects:@"selectedEncoder", @"encoder", @"fallbackEncoder", @"mixdown", nil]; } else if ([key isEqualToString:@"bitRates"]) { retval = [NSSet setWithObjects:@"selectedEncoder", @"encoder", @"fallbackEncoder", @"mixdown", @"sampleRate", nil]; } else { retval = [NSSet set]; } return retval; } - (void)setNilValueForKey:(NSString *)key { if ([key isEqualToString:@"drc"] || [key isEqualToString:@"gain"]) { [self setValue:@0 forKey:key]; } } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { HBAudioTrackPreset *copy = [[[self class] alloc] init]; if (copy) { copy->_encoder = _encoder; copy->_fallbackEncoder = _fallbackEncoder; copy->_selectedEncoder = _selectedEncoder; copy->_mixdown = _mixdown; copy->_sampleRate = _sampleRate; copy->_bitRate = _bitRate; copy->_gain = _gain; copy->_drc = _drc; copy->_container = _container; } return copy; } #pragma mark - NSCoding + (BOOL)supportsSecureCoding { return YES; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeInt:1 forKey:@"HBAudioTrackPresetVersion"]; encodeInt(_encoder); encodeInt(_fallbackEncoder); encodeInt(_mixdown); encodeInt(_sampleRate); encodeInt(_bitRate); encodeDouble(_gain); encodeDouble(_drc); encodeInt(_container); } - (instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; decodeInt(_encoder); if (_encoder < 0) { goto fail; } decodeInt(_fallbackEncoder); if (_fallbackEncoder < 0) { goto fail; } decodeInt(_mixdown); if (_mixdown < 0) { goto fail; } decodeInt(_sampleRate); if (_sampleRate < 0) { goto fail; } decodeInt(_bitRate); if (_bitRate < 0) { goto fail; } decodeDouble(_gain); decodeDouble(_drc); decodeInt(_container); if (_container != HB_MUX_MP4 && _container != HB_MUX_MKV && _container != HB_MUX_WEBM) { goto fail; } [self validateFallbackEncoder]; return self; fail: return nil; } @end