/* HBAudio.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 "HBAudio.h" #import "HBJob.h" #import "HBJob+HBJobConversion.h" #import "HBTitle.h" #import "HBAudioTrack.h" #import "HBAudioTrackPreset.h" #import "HBAudioDefaults.h" #import "HBCodingUtilities.h" #import "HBLocalizationUtilities.h" #import "HBJob+Private.h" #include "hb.h" #define NONE_TRACK_INDEX 0 NSString *HBAudioEncoderChangedNotification = @"HBAudioEncoderChangedNotification"; @interface HBAudio () @property (nonatomic, readwrite, weak) HBJob *job; @property (nonatomic, readwrite) int container; @end @implementation HBAudio - (instancetype)initWithJob:(HBJob *)job { self = [super init]; if (self) { _job = job; _container = HB_MUX_MP4; _tracks = [[NSMutableArray alloc] init]; _defaults = [[HBAudioDefaults alloc] init]; // Add the none track to the source array NSMutableArray *sourceTracks = [job.title.audioTracks mutableCopy]; HBTitleAudioTrack *none = [[HBTitleAudioTrack alloc] initWithDisplayName:HBKitLocalizedString(@"None", @"HBAudio -> none track name")]; [sourceTracks insertObject:none atIndex:0]; _sourceTracks = [sourceTracks copy]; } return self; } #pragma mark - Data Source - (HBTitleAudioTrack *)sourceTrackAtIndex:(NSUInteger)idx { return self.sourceTracks[idx]; } - (NSArray *)sourceTracksArray { NSMutableArray *sourceNames = [NSMutableArray array]; for (HBTitleAudioTrack *track in self.sourceTracks) { [sourceNames addObject:track.displayName]; } return sourceNames; } #pragma mark - Delegate - (void)track:(HBAudioTrack *)track didChangeSourceFrom:(NSUInteger)oldSourceIdx { // If the source was changed to None, remove the track if (track.sourceTrackIdx == NONE_TRACK_INDEX) { NSUInteger idx = [self.tracks indexOfObject:track]; [self removeObjectFromTracksAtIndex:idx]; } // Else add a new None track else if (oldSourceIdx == NONE_TRACK_INDEX) { [self addNoneTrack]; } } - (void)addNoneTrack { HBAudioTrack *track = [self trackFromSourceTrackIndex:NONE_TRACK_INDEX]; [self addTrack:track]; } #pragma mark - Public methods - (void)addAllTracks { [self removeTracksAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.tracks.count)]]; // Add the remaining tracks for (NSUInteger idx = 1; idx < self.sourceTracksArray.count; idx++) { [self addTrack:[self trackFromSourceTrackIndex:idx]]; } [self addNoneTrack]; } - (void)removeAll { [self removeTracksAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.tracks.count)]]; [self addNoneTrack]; } - (void)reloadDefaults { [self addDefaultTracksFromJobSettings:self.job.jobDict]; } - (void)setContainer:(int)container { _container = container; if (!(self.undo.isUndoing || self.undo.isRedoing)) { for (HBAudioTrack *track in self.tracks) { track.container = container; } // Update the Auto Passthru Fallback Codec Popup // lets get the tag of the currently selected item first so we might reset it later [self.defaults validateEncoderFallbackForVideoContainer:container]; } } - (void)setUndo:(NSUndoManager *)undo { _undo = undo; for (HBAudioTrack *track in self.tracks) { track.undo = undo; } self.defaults.undo = undo; } - (void)setDefaults:(HBAudioDefaults *)defaults { if (defaults != _defaults) { [[self.undo prepareWithInvocationTarget:self] setDefaults:_defaults]; } _defaults = defaults; _defaults.undo = self.undo; } /** * Convenience method to add a track to tracks array. * * @param track the track to add. */ - (void)addTrack:(HBAudioTrack *)newTrack { [self insertObject:newTrack inTracksAtIndex:[self countOfTracks]]; } /** * Creates a new track dictionary from a source track. * * @param index the index of the source track in the sourceTracks array */ - (HBAudioTrack *)trackFromSourceTrackIndex:(NSInteger)index { HBAudioTrack *track = [[HBAudioTrack alloc] initWithTrackIdx:index container:self.container dataSource:self delegate:self]; track.undo = self.undo; return track; } #pragma mark - Defaults - (void)addDefaultTracksFromJobSettings:(NSDictionary *)settings { NSMutableArray *tracks = [NSMutableArray array]; NSArray *> *settingsTracks = settings[@"Audio"][@"AudioList"]; // Reinitialize the configured list of audio tracks [self removeTracksAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.tracks.count)]]; // Add the tracks for (NSDictionary *trackDict in settingsTracks) { HBAudioTrack *track = [self trackFromSourceTrackIndex:[trackDict[@"Track"] unsignedIntegerValue] + 1]; track.drc = [trackDict[@"DRC"] doubleValue]; track.gain = [trackDict[@"Gain"] doubleValue]; track.mixdown = hb_mixdown_get_from_name([trackDict[@"Mixdown"] UTF8String]); track.sampleRate = [trackDict[@"Samplerate"] intValue] == -1 ? 0 : [trackDict[@"Samplerate"] intValue]; track.bitRate = [trackDict[@"Bitrate"] intValue]; track.encoder = hb_audio_encoder_get_from_name([trackDict[@"Encoder"] UTF8String]); [tracks addObject:track]; } [self insertTracks:tracks atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, tracks.count)]]; // Add an None item [self addNoneTrack]; } - (BOOL)anyCodecMatches:(int)codec { for (HBAudioTrack *track in self.tracks) { if (track.isEnabled && codec == track.encoder) { return YES; } } return NO; } #pragma mark - #pragma mark Notification Handling - (void)encoderChanged { [[NSNotificationCenter defaultCenter] postNotificationName:HBAudioEncoderChangedNotification object:self]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { HBAudio *copy = [[[self class] alloc] init]; if (copy) { copy->_container = _container; copy->_sourceTracks = [_sourceTracks copy]; copy->_tracks = [[NSMutableArray alloc] init]; for (HBAudioTrack *track in _tracks) { HBAudioTrack *trackCopy = [track copy]; [copy->_tracks addObject:trackCopy]; trackCopy.dataSource = copy; trackCopy.delegate = copy; } copy->_defaults = [_defaults copy]; } return copy; } #pragma mark - NSCoding + (BOOL)supportsSecureCoding { return YES; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeInt:2 forKey:@"HBAudioVersion"]; encodeInt(_container); encodeObject(_sourceTracks); encodeObject(_tracks); encodeObject(_defaults); } - (instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; decodeInt(_container); if (_container != HB_MUX_MP4 && _container != HB_MUX_MKV && _container != HB_MUX_WEBM) { goto fail; } decodeCollectionOfObjectsOrFail(_sourceTracks, NSArray, HBTitleAudioTrack); if (_sourceTracks.count < 1) { goto fail; } decodeCollectionOfObjectsOrFail(_tracks, NSMutableArray, HBAudioTrack); for (HBAudioTrack *track in _tracks) { track.dataSource = self; track.delegate = self; } decodeObjectOrFail(_defaults, HBAudioDefaults); return self; fail: return nil; } #pragma mark - Presets - (void)writeToPreset:(HBMutablePreset *)preset { [self.defaults writeToPreset:preset]; } - (void)applyPreset:(HBPreset *)preset jobSettings:(NSDictionary *)settings { [self.defaults applyPreset:preset jobSettings:settings]; [self addDefaultTracksFromJobSettings:settings]; } #pragma mark - #pragma mark KVC - (NSUInteger)countOfTracks { return self.tracks.count; } - (HBAudioTrack *)objectInTracksAtIndex:(NSUInteger)index { return self.tracks[index]; } - (void)insertObject:(HBAudioTrack *)track inTracksAtIndex:(NSUInteger)index { [[self.undo prepareWithInvocationTarget:self] removeObjectFromTracksAtIndex:index]; [self.tracks insertObject:track atIndex:index]; } - (void)insertTracks:(NSArray *)array atIndexes:(NSIndexSet *)indexes { [[self.undo prepareWithInvocationTarget:self] removeTracksAtIndexes:indexes]; [self.tracks insertObjects:array atIndexes:indexes]; } - (void)removeObjectFromTracksAtIndex:(NSUInteger)index { HBAudioTrack *track = self.tracks[index]; [[self.undo prepareWithInvocationTarget:self] insertObject:track inTracksAtIndex:index]; [self.tracks removeObjectAtIndex:index]; } - (void)removeTracksAtIndexes:(NSIndexSet *)indexes { NSArray *tracks = [self.tracks objectsAtIndexes:indexes]; [[self.undo prepareWithInvocationTarget:self] insertTracks:tracks atIndexes:indexes]; [self.tracks removeObjectsAtIndexes:indexes]; } @end