WinGui: Fix another instance of the Caliburn vs Json.net sillyness where objects...
[HandBrake.git] / macosx / HBJob+HBJobConversion.m
blob1b9f39fd05adcd414e37514fe3a74f1df0cbd173
1 /*  HBJob+HBJobConversion.m $
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. */
7 #import "HBJob+HBJobConversion.h"
9 #import "HBAudioDefaults.h"
10 #import "HBAudioTrack.h"
12 #import "HBTitlePrivate.h"
14 @implementation HBJob (HBJobConversion)
16 /**
17  *  Prepares a hb_job_t
18  */
19 - (hb_job_t *)hb_job
21     NSAssert(self.title, @"HBJob: calling hb_job without a valid title loaded");
22     NSAssert(self.destURL, @"HBJob: calling hb_job without a valid destination");
24     hb_title_t *title = self.title.hb_title;
25     hb_job_t *job = hb_job_init(title);
27     hb_job_set_file(job, self.destURL.path.fileSystemRepresentation);
29     // Title Angle for dvdnav
30     job->angle = self.angle;
32     if (self.range.type == HBRangeTypeChapters)
33     {
34         // Chapter selection
35         job->chapter_start = self.range.chapterStart + 1;
36         job->chapter_end   = self.range.chapterStop + 1;
37     }
38     else if (self.range.type == HBRangeTypeSeconds)
39     {
40         // we are pts based start / stop
41         // Point A to Point B. Time to time in seconds.
42         // get the start seconds from the start seconds field
43         int start_seconds = self.range.secondsStart;
44         job->pts_to_start = start_seconds * 90000LL;
45         // Stop seconds is actually the duration of encode, so subtract the end seconds from the start seconds
46         int stop_seconds = self.range.secondsStop;
47         job->pts_to_stop = (stop_seconds - start_seconds) * 90000LL;
48     }
49     else if (self.range.type == HBRangeTypeFrames)
50     {
51         // we are frame based start / stop
52         //Point A to Point B. Frame to frame
53         // get the start frame from the start frame field
54         int start_frame = self.range.frameStart;
55         job->frame_to_start = start_frame;
56         // get the frame to stop on from the end frame field
57         int stop_frame = self.range.frameStop;
58         job->frame_to_stop = stop_frame - start_frame;
59     }
60     else if (self.range.type == HBRangePreviewIndex)
61     {
62         job->start_at_preview = self.range.previewIndex;
63         job->seek_points = self.range.previewsCount;
64         job->pts_to_stop = self.range.ptsToStop;
65     }
67     // Format (Muxer) and Video Encoder
68     job->mux = self.container;
69     job->vcodec = self.video.encoder;
71     // We set http optimized mp4 here
72     job->mp4_optimize = self.mp4HttpOptimize;
74     // We set the chapter marker extraction here based on the format being
75     // mpeg4 or mkv and the checkbox being checked.
76     if (self.chaptersEnabled)
77     {
78         job->chapter_markers = 1;
80         // now lets get our saved chapter names out the array in the queue file
81         // and insert them back into the title chapter list. We have it here,
82         // because unless we are inserting chapter markers there is no need to
83         // spend the overhead of iterating through the chapter names array imo
84         // Also, note that if for some reason we don't apply chapter names, the
85         // chapters just come out 001, 002, etc. etc.
86         int i = 0;
87         for (NSString *name in self.chapterTitles)
88         {
89             hb_chapter_t *chapter = (hb_chapter_t *) hb_list_item(job->list_chapter, i);
90             if (chapter != NULL)
91             {
92                 hb_chapter_set_title(chapter, name.UTF8String);
93             }
94             i++;
95         }
96     }
97     else
98     {
99         job->chapter_markers = 0;
100     }
102     if (job->vcodec & HB_VCODEC_H264_MASK)
103     {
104         // iPod 5G atom
105         job->ipod_atom = self.mp4iPodCompatible;
106     }
108     if (self.video.twoPass && (self.video.encoder == HB_VCODEC_X264 ||
109                                self.video.encoder == HB_VCODEC_X265))
110     {
111         job->fastfirstpass = self.video.turboTwoPass;
112     }
113     job->twopass = self.video.twoPass;
115     if (hb_video_encoder_get_presets(self.video.encoder) != NULL)
116     {
117         // advanced x264/x265 options
118         NSString   *tmpString;
119         // translate zero-length strings to NULL for libhb
120         const char *encoder_preset  = NULL;
121         const char *encoder_tune    = NULL;
122         const char *encoder_options = NULL;
123         const char *encoder_profile = NULL;
124         const char *encoder_level   = NULL;
125         if (self.video.advancedOptions)
126         {
127             // we are using the advanced panel
128             if ([(tmpString = self.video.videoOptionExtra) length])
129             {
130                 encoder_options = tmpString.UTF8String;
131             }
132         }
133         else
134         {
135             // we are using the x264/x265 preset system
136             if ([(tmpString = self.video.completeTune) length])
137             {
138                 encoder_tune = [tmpString UTF8String];
139             }
140             if ([(tmpString = self.video.videoOptionExtra) length])
141             {
142                 encoder_options = [tmpString UTF8String];
143             }
144             if ([(tmpString = self.video.profile) length])
145             {
146                 encoder_profile = [tmpString UTF8String];
147             }
148             if ([(tmpString = self.video.level) length])
149             {
150                 encoder_level = [tmpString UTF8String];
151             }
152             encoder_preset = self.video.preset.UTF8String;
153         }
154         hb_job_set_encoder_preset (job, encoder_preset);
155         hb_job_set_encoder_tune   (job, encoder_tune);
156         hb_job_set_encoder_options(job, encoder_options);
157         hb_job_set_encoder_profile(job, encoder_profile);
158         hb_job_set_encoder_level  (job, encoder_level);
159     }
160     else if (job->vcodec & HB_VCODEC_FFMPEG_MASK)
161     {
162         hb_job_set_encoder_options(job, self.video.videoOptionExtra.UTF8String);
163     }
165     // Picture Size Settings
166     job->par.num = self.picture.parWidth;
167     job->par.den = self.picture.parHeight;
169     // Video settings
170     // Framerate
171     int fps_mode, fps_num, fps_den;
172     if (self.video.frameRate > 0)
173     {
174         // a specific framerate has been chosen
175         fps_num = 27000000;
176         fps_den = self.video.frameRate;
177         if (self.video.frameRateMode == 1)
178         {
179             // CFR
180             fps_mode = 1;
181         }
182         else
183         {
184             // PFR
185             fps_mode = 2;
186         }
187     }
188     else
189     {
190         // same as source
191         fps_num = title->vrate.num;
192         fps_den = title->vrate.den;
193         if (self.video.frameRateMode == 1)
194         {
195             // CFR
196             fps_mode = 1;
197         }
198         else
199         {
200             // VFR
201             fps_mode = 0;
202         }
203     }
205     switch (self.video.qualityType)
206     {
207         case 0:
208             // ABR
209             job->vquality = -1.0;
210             job->vbitrate = self.video.avgBitrate;
211             break;
212         case 1:
213             // Constant Quality
214             job->vquality = self.video.quality;
215             job->vbitrate = 0;
216             break;
217     }
219     job->grayscale = self.filters.grayscale;
221     // Map the settings in the dictionaries for the SubtitleList array to match title->list_subtitle
222     BOOL one_burned = NO;
223     for (NSDictionary *subtitleDict in self.subtitles.tracks)
224     {
225         int subtitle = [subtitleDict[keySubTrackIndex] intValue];
226         BOOL force = [subtitleDict[keySubTrackForced] boolValue];
227         BOOL burned = [subtitleDict[keySubTrackBurned] boolValue];
228         BOOL def = [subtitleDict[keySubTrackDefault] boolValue];
230         // Skip the "None" track.
231         if (subtitle == -2)
232         {
233             continue;
234         }
236         // we need to check for the "Foreign Audio Search" which would be keySubTrackIndex of -1
237         if (subtitle == -1)
238         {
239             job->indepth_scan = 1;
241             if (burned != 1)
242             {
243                 job->select_subtitle_config.dest = PASSTHRUSUB;
244             }
245             else
246             {
247                 job->select_subtitle_config.dest = RENDERSUB;
248             }
250             job->select_subtitle_config.force = force;
251             job->select_subtitle_config.default_track = def;
252         }
253         else
254         {
255             // if we are getting the subtitles from an external srt file
256             if ([subtitleDict[keySubTrackType] intValue] == SRTSUB)
257             {
258                 hb_subtitle_config_t sub_config;
260                 sub_config.offset = [subtitleDict[keySubTrackSrtOffset] intValue];
262                 // we need to strncpy file name and codeset
263                 strncpy(sub_config.src_filename, [subtitleDict[keySubTrackSrtFilePath] UTF8String], 255);
264                 sub_config.src_filename[255] = 0;
265                 strncpy(sub_config.src_codeset, [subtitleDict[keySubTrackSrtCharCode] UTF8String], 39);
266                 sub_config.src_codeset[39] = 0;
268                 if (!burned && hb_subtitle_can_pass(SRTSUB, job->mux))
269                 {
270                     sub_config.dest = PASSTHRUSUB;
271                 }
272                 else if (hb_subtitle_can_burn(SRTSUB))
273                 {
274                     // Only allow one subtitle to be burned into the video
275                     if (one_burned)
276                         continue;
277                     one_burned = YES;
278                     sub_config.dest = RENDERSUB;
279                 }
281                 sub_config.force = 0;
282                 sub_config.default_track = def;
283                 hb_srt_add( job, &sub_config, [subtitleDict[keySubTrackLanguageIsoCode] UTF8String]);
284                 continue;
285             }
287             // We are setting a source subtitle so access the source subtitle info
288             hb_subtitle_t * subt = (hb_subtitle_t *) hb_list_item(title->list_subtitle, subtitle);
290             if (subt != NULL)
291             {
292                 hb_subtitle_config_t sub_config = subt->config;
294                 if (!burned && hb_subtitle_can_pass(subt->source, job->mux))
295                 {
296                     sub_config.dest = PASSTHRUSUB;
297                 }
298                 else if (hb_subtitle_can_burn(subt->source))
299                 {
300                     // Only allow one subtitle to be burned into the video
301                     if (one_burned)
302                         continue;
303                     one_burned = YES;
304                     sub_config.dest = RENDERSUB;
305                 }
307                 sub_config.force = force;
308                 sub_config.default_track = def;
309                 hb_subtitle_add(job, &sub_config, subtitle);
310             }
311         }
312     }
314     if (one_burned)
315     {
316         hb_filter_object_t *filter = hb_filter_init( HB_FILTER_RENDER_SUB );
317         hb_add_filter(job, filter, [NSString stringWithFormat:@"%d:%d:%d:%d",
318                                       self.picture.cropTop, self.picture.cropBottom,
319                                       self.picture.cropLeft, self.picture.cropRight].UTF8String);
320     }
322     // Audio Defaults
323     job->acodec_copy_mask = 0;
325     HBAudioDefaults *audioDefaults = self.audio.defaults;
327     if (audioDefaults.allowAACPassthru)
328     {
329         job->acodec_copy_mask |= HB_ACODEC_AAC_PASS;
330     }
331     if (audioDefaults.allowAC3Passthru)
332     {
333         job->acodec_copy_mask |= HB_ACODEC_AC3_PASS;
334     }
335     if (audioDefaults.allowEAC3Passthru)
336     {
337         job->acodec_copy_mask |= HB_ACODEC_EAC3_PASS;
338     }
339     if (audioDefaults.allowDTSHDPassthru)
340     {
341         job->acodec_copy_mask |= HB_ACODEC_DCA_HD_PASS;
342     }
343     if (audioDefaults.allowDTSPassthru)
344     {
345         job->acodec_copy_mask |= HB_ACODEC_DCA_PASS;
346     }
347     if (audioDefaults.allowMP3Passthru)
348     {
349         job->acodec_copy_mask |= HB_ACODEC_MP3_PASS;
350     }
351     if (audioDefaults.allowTrueHDPassthru)
352     {
353         job->acodec_copy_mask |= HB_ACODEC_TRUEHD_PASS;
354     }
355     if (audioDefaults.allowFLACPassthru)
356     {
357         job->acodec_copy_mask |= HB_ACODEC_FLAC_PASS;
358     }
360     job->acodec_fallback = audioDefaults.encoderFallback;
362     // Audio tracks and mixdowns
363     // Now lets add our new tracks to the audio list here
364     for (HBAudioTrack *audioTrack in self.audio.tracks)
365     {
366         if (audioTrack.enabled)
367         {
368             hb_audio_config_t *audio = (hb_audio_config_t *)calloc(1, sizeof(*audio));
369             hb_audio_config_init(audio);
371             NSNumber *sampleRateToUse = ([audioTrack.sampleRate[keyAudioSamplerate] intValue] == 0 ?
372                                          audioTrack.track[keyAudioInputSampleRate] :
373                                          audioTrack.sampleRate[keyAudioSamplerate]);
375             audio->in.track            = [audioTrack.track[keyAudioTrackIndex] intValue] -1;
377             // We go ahead and assign values to our audio->out.<properties>
378             audio->out.track                     = audio->in.track;
379             audio->out.codec                     = [audioTrack.codec[keyAudioCodec] intValue];
380             audio->out.compression_level         = hb_audio_compression_get_default(audio->out.codec);
381             audio->out.mixdown                   = [audioTrack.mixdown[keyAudioMixdown] intValue];
382             audio->out.normalize_mix_level       = 0;
383             audio->out.bitrate                   = [audioTrack.bitRate[keyAudioBitrate] intValue];
384             audio->out.samplerate                = [sampleRateToUse intValue];
385             audio->out.dither_method             = hb_audio_dither_get_default();
387             // output is not passthru so apply gain
388             if (!([[audioTrack codec][keyAudioCodec] intValue] & HB_ACODEC_PASS_FLAG))
389             {
390                 audio->out.gain = [audioTrack.gain doubleValue];
391             }
392             else
393             {
394                 // output is passthru - the Gain dial is disabled so don't apply its value
395                 audio->out.gain = 0;
396             }
398             if (hb_audio_can_apply_drc([audioTrack.track[keyAudioInputCodec] intValue],
399                                        [audioTrack.track[keyAudioInputCodecParam] intValue],
400                                        [audioTrack.codec[keyAudioCodec] intValue]))
401             {
402                 audio->out.dynamic_range_compression = [audioTrack.drc doubleValue];
403             }
404             else
405             {
406                 // source isn't AC3 or output is passthru - the DRC dial is disabled so don't apply its value
407                 audio->out.dynamic_range_compression = 0;
408             }
410             hb_audio_add(job, audio);
411             free(audio);
412         }
413     }
415     // Now lets call the filters if applicable.
416     // The order of the filters is critical
418     // Detelecine
419     hb_filter_object_t *filter;
420     if (![self.filters.detelecine isEqualToString:@"off"])
421     {
422         int filter_id = HB_FILTER_DETELECINE;
423         const char *filter_str = hb_generate_filter_settings(filter_id,
424                                                              self.filters.detelecine.UTF8String,
425                                                              self.filters.detelecineCustomString.UTF8String);
426         filter = hb_filter_init(filter_id);
427         hb_add_filter(job, filter, filter_str);
428     }
430     if (self.filters.useDecomb && ![self.filters.decomb isEqualToString:@"off"])
431     {
432         // Decomb
433         int filter_id = HB_FILTER_DECOMB;
434         const char *filter_str = hb_generate_filter_settings(filter_id,
435                                                              self.filters.decomb.UTF8String,
436                                                              self.filters.decombCustomString.UTF8String);
437         filter = hb_filter_init(filter_id);
438         hb_add_filter(job, filter, filter_str);
439     }
440     else if (!self.filters.useDecomb && ![self.filters.deinterlace isEqualToString:@"off"])
441     {
442         // Deinterlace
443         int filter_id = HB_FILTER_DEINTERLACE;
444         const char *filter_str = hb_generate_filter_settings(filter_id,
445                                                              self.filters.deinterlace.UTF8String,
446                                                              self.filters.deinterlaceCustomString.UTF8String);
447         filter = hb_filter_init(filter_id);
448         hb_add_filter(job, filter, filter_str);
449     }
451     // Denoise
452     if (![self.filters.denoise isEqualToString:@"off"])
453     {
454         int filter_id = HB_FILTER_HQDN3D;
455         if ([self.filters.denoise isEqualToString:@"nlmeans"])
456             filter_id = HB_FILTER_NLMEANS;
458         if ([self.filters.denoisePreset isEqualToString:@"custom"])
459         {
460             const char *filter_str;
461             filter_str = self.filters.denoiseCustomString.UTF8String;
462             filter = hb_filter_init(filter_id);
463             hb_add_filter(job, filter, filter_str);
464         }
465         else
466         {
467             const char *filter_str, *preset, *tune;
468             preset = self.filters.denoisePreset.UTF8String;
469             tune = self.filters.denoiseTune.UTF8String;
470             filter_str = hb_generate_filter_settings(filter_id, preset, tune);
471             filter = hb_filter_init(filter_id);
472             hb_add_filter(job, filter, filter_str);
473         }
474     }
476     // Deblock (uses pp7 default)
477     if (self.filters.deblock)
478     {
479         filter = hb_filter_init(HB_FILTER_DEBLOCK);
480         hb_add_filter(job, filter, [NSString stringWithFormat:@"%d", self.filters.deblock].UTF8String);
481     }
483     // Add Crop/Scale filter
484     filter = hb_filter_init(HB_FILTER_CROP_SCALE);
485     hb_add_filter( job, filter, [NSString stringWithFormat:@"%d:%d:%d:%d:%d:%d",
486                                  self.picture.width, self.picture.height,
487                                  self.picture.cropTop, self.picture.cropBottom,
488                                  self.picture.cropLeft, self.picture.cropRight].UTF8String);
489     
490     // Add framerate shaping filter
491     filter = hb_filter_init(HB_FILTER_VFR);
492     hb_add_filter(job, filter, [[NSString stringWithFormat:@"%d:%d:%d",
493                                  fps_mode, fps_num, fps_den] UTF8String]);
494     
495     return job;
498 @end