1 // -------------------------------------------------------------------------------------------------------------------- 2 // <copyright file="EncodeTaskFactory.cs" company="HandBrake Project (http://handbrake.fr)"> 3 // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. 4 // </copyright> 5 // <summary> 6 // The encode factory. 7 // </summary> 8 // -------------------------------------------------------------------------------------------------------------------- 9 10 namespace HandBrakeWPF.Services.Encode.Factories 11 { 12 using System.Collections.Generic; 13 using System.Globalization; 14 using System.Linq; 15 using System.Text.Json; 16 17 using HandBrake.Interop.Interop; 18 using HandBrake.Interop.Interop.HbLib; 19 using HandBrake.Interop.Interop.Interfaces.Model; 20 using HandBrake.Interop.Interop.Interfaces.Model.Encoders; 21 using HandBrake.Interop.Interop.Json.Encode; 22 using HandBrake.Interop.Interop.Json.Shared; 23 24 using HandBrakeWPF.Helpers; 25 using HandBrakeWPF.Model.Filters; 26 using HandBrakeWPF.Services.Interfaces; 27 using HandBrakeWPF.Utilities; 28 29 using AudioEncoder = Model.Models.AudioEncoder; 30 using AudioEncoderRateType = Model.Models.AudioEncoderRateType; 31 using AudioTrack = Model.Models.AudioTrack; 32 using ChapterMarker = Model.Models.ChapterMarker; 33 using EncodeTask = Model.EncodeTask; 34 using FramerateMode = Model.Models.FramerateMode; 35 using OutputFormat = Model.Models.OutputFormat; 36 using PointToPointMode = Model.Models.PointToPointMode; 37 using Subtitle = HandBrake.Interop.Interop.Json.Encode.Subtitles; 38 using SubtitleTrack = Model.Models.SubtitleTrack; 39 using Validate = Helpers.Validate; 40 using VideoEncoder = HandBrakeWPF.Model.Video.VideoEncoder; 41 using VideoEncodeRateType = HandBrakeWPF.Model.Video.VideoEncodeRateType; 42 43 /// <summary> 44 /// This factory takes the internal EncodeJob object and turns it into a set of JSON models 45 /// that can be deserialized by libhb. 46 /// </summary> 47 internal class EncodeTaskFactory 48 { 49 private readonly IUserSettingService userSettingService; 50 EncodeTaskFactory(IUserSettingService userSettingService)51 public EncodeTaskFactory(IUserSettingService userSettingService) 52 { 53 this.userSettingService = userSettingService; 54 } 55 Create(EncodeTask job, HBConfiguration configuration)56 internal JsonEncodeObject Create(EncodeTask job, HBConfiguration configuration) 57 { 58 JsonEncodeObject encode = new JsonEncodeObject 59 { 60 SequenceID = 0, 61 Audio = CreateAudio(job), 62 Destination = CreateDestination(job), 63 Filters = CreateFilters(job), 64 PAR = CreatePAR(job), 65 Metadata = CreateMetadata(job), 66 Source = CreateSource(job, configuration), 67 Subtitle = CreateSubtitle(job), 68 Video = CreateVideo(job, configuration) 69 }; 70 71 return encode; 72 } 73 CreateSource(EncodeTask job, HBConfiguration configuration)74 private Source CreateSource(EncodeTask job, HBConfiguration configuration) 75 { 76 Range range = new Range(); 77 switch (job.PointToPointMode) 78 { 79 case PointToPointMode.Chapters: 80 range.Type = "chapter"; 81 range.Start = job.StartPoint; 82 range.End = job.EndPoint; 83 break; 84 case PointToPointMode.Seconds: 85 range.Type = "time"; 86 range.Start = job.StartPoint * 90000; 87 range.End = job.EndPoint * 90000; 88 break; 89 case PointToPointMode.Frames: 90 range.Type = "frame"; 91 range.Start = job.StartPoint; 92 range.End = job.EndPoint; 93 break; 94 case PointToPointMode.Preview: 95 range.Type = "preview"; 96 range.Start = job.PreviewEncodeStartAt; 97 range.SeekPoints = configuration.PreviewScanCount; 98 range.End = job.PreviewEncodeDuration * 90000; 99 break; 100 } 101 102 Source source = new Source 103 { 104 Title = job.Title, 105 Range = range, 106 Angle = job.Angle, 107 Path = job.Source, 108 }; 109 return source; 110 } 111 CreateDestination(EncodeTask job)112 private Destination CreateDestination(EncodeTask job) 113 { 114 Destination destination = new Destination 115 { 116 File = job.Destination, 117 Mp4Options = new Mp4Options 118 { 119 IpodAtom = VideoEncoderHelpers.IsH264(job.VideoEncoder) ? job.IPod5GSupport : false, 120 Mp4Optimize = job.OptimizeMP4 121 }, 122 ChapterMarkers = job.IncludeChapterMarkers, 123 AlignAVStart = job.AlignAVStart, 124 Mux = EnumHelper<OutputFormat>.GetShortName(job.OutputFormat), 125 ChapterList = new List<Chapter>() 126 }; 127 128 if (job.IncludeChapterMarkers) 129 { 130 foreach (ChapterMarker item in job.ChapterNames) 131 { 132 Chapter chapter = new Chapter { Name = item.ChapterName }; 133 destination.ChapterList.Add(chapter); 134 } 135 } 136 137 return destination; 138 } 139 CreatePAR(EncodeTask job)140 private PAR CreatePAR(EncodeTask job) 141 { 142 return new PAR { Num = job.PixelAspectX, Den = job.PixelAspectY }; 143 } 144 CreateSubtitle(EncodeTask job)145 private Subtitle CreateSubtitle(EncodeTask job) 146 { 147 Subtitles subtitle = new Subtitles 148 { 149 Search = 150 new SubtitleSearch 151 { 152 Enable = false, 153 Default = false, 154 Burn = false, 155 Forced = false 156 }, 157 SubtitleList = new List<HandBrake.Interop.Interop.Json.Encode.SubtitleTrack>() 158 }; 159 160 foreach (SubtitleTrack item in job.SubtitleTracks) 161 { 162 if (!item.IsSrtSubtitle) 163 { 164 // Handle Foreign Audio Search 165 if (item.SourceTrack.TrackNumber == 0) 166 { 167 subtitle.Search.Enable = true; 168 subtitle.Search.Burn = item.Burned; 169 subtitle.Search.Default = item.Default; 170 subtitle.Search.Forced = item.Forced; 171 } 172 else 173 { 174 HandBrake.Interop.Interop.Json.Encode.SubtitleTrack track = new HandBrake.Interop.Interop.Json.Encode.SubtitleTrack 175 { 176 Burn = item.Burned, 177 Default = item.Default, 178 Forced = item.Forced, 179 ID = item.SourceTrack.TrackNumber, 180 Track = (item.SourceTrack.TrackNumber - 1), 181 Name = item.Name 182 }; 183 184 subtitle.SubtitleList.Add(track); 185 } 186 } 187 else 188 { 189 HandBrake.Interop.Interop.Json.Encode.SubtitleTrack track = new HandBrake.Interop.Interop.Json.Encode.SubtitleTrack 190 { 191 Track = -1, // Indicates SRT 192 Default = item.Default, 193 Offset = item.SrtOffset, 194 Burn = item.Burned, 195 Name = item.Name, 196 Import = 197 new SubImport 198 { 199 Format = item.SrtPath.EndsWith("srt") ? "SRT" : "SSA", 200 Filename = item.SrtPath, 201 Codeset = item.SrtCharCode, 202 Language = item.SrtLangCode 203 } 204 }; 205 206 subtitle.SubtitleList.Add(track); 207 } 208 } 209 210 return subtitle; 211 } 212 CreateVideo(EncodeTask job, HBConfiguration configuration)213 private Video CreateVideo(EncodeTask job, HBConfiguration configuration) 214 { 215 Video video = new Video(); 216 217 HBVideoEncoder videoEncoder = HandBrakeEncoderHelpers.VideoEncoders.FirstOrDefault(e => e.ShortName == EnumHelper<VideoEncoder>.GetShortName(job.VideoEncoder)); 218 Validate.NotNull(videoEncoder, "Video encoder " + job.VideoEncoder + " not recognized."); 219 if (videoEncoder != null) 220 { 221 video.Encoder = videoEncoder.ShortName; 222 } 223 224 video.Level = job.VideoLevel?.ShortName; 225 video.Preset = job.VideoPreset?.ShortName; 226 video.Profile = job.VideoProfile?.ShortName; 227 228 if (job.VideoTunes != null && job.VideoTunes.Count > 0) 229 { 230 foreach (var item in job.VideoTunes) 231 { 232 video.Tune += string.IsNullOrEmpty(video.Tune) ? item.ShortName : "," + item.ShortName; 233 } 234 } 235 236 if (job.VideoEncodeRateType == VideoEncodeRateType.ConstantQuality) 237 { 238 video.Quality = (decimal?)job.Quality; 239 } 240 241 if (job.VideoEncodeRateType == VideoEncodeRateType.AverageBitrate) 242 { 243 video.Bitrate = job.VideoBitrate; 244 video.TwoPass = job.TwoPass; 245 video.Turbo = job.TurboFirstPass; 246 } 247 248 video.QSV.Decode = HandBrakeHardwareEncoderHelper.IsQsvAvailable && configuration.EnableQuickSyncDecoding; 249 250 // The use of the QSV decoder is configurable for non QSV encoders. 251 if (video.QSV.Decode && job.VideoEncoder != VideoEncoder.QuickSync && job.VideoEncoder != VideoEncoder.QuickSyncH265 && job.VideoEncoder != VideoEncoder.QuickSyncH26510b) 252 { 253 video.QSV.Decode = configuration.UseQSVDecodeForNonQSVEnc; 254 } 255 256 video.Options = job.ExtraAdvancedArguments; 257 258 if (HandBrakeHardwareEncoderHelper.IsQsvAvailable && (HandBrakeHardwareEncoderHelper.QsvHardwareGeneration > 6) && (job.VideoEncoder == VideoEncoder.QuickSync || job.VideoEncoder == VideoEncoder.QuickSyncH265 || job.VideoEncoder == VideoEncoder.QuickSyncH26510b)) 259 { 260 if (configuration.EnableQsvLowPower && !video.Options.Contains("lowpower")) 261 { 262 video.Options = string.IsNullOrEmpty(video.Options) ? "lowpower=1" : string.Concat(video.Options, ":lowpower=1"); 263 } 264 else if(!configuration.EnableQsvLowPower && !video.Options.Contains("lowpower")) 265 { 266 video.Options = string.IsNullOrEmpty(video.Options) ? "lowpower=0" : string.Concat(video.Options, ":lowpower=0"); 267 } 268 } 269 270 return video; 271 } 272 CreateAudio(EncodeTask job)273 private Audio CreateAudio(EncodeTask job) 274 { 275 Audio audio = new Audio(); 276 277 List<string> copyMaskList = new List<string>(); 278 if (job.AudioPassthruOptions.AudioAllowAACPass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.AacPassthru)); 279 if (job.AudioPassthruOptions.AudioAllowAC3Pass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.Ac3Passthrough)); 280 if (job.AudioPassthruOptions.AudioAllowDTSHDPass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.DtsHDPassthrough)); 281 if (job.AudioPassthruOptions.AudioAllowDTSPass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.DtsPassthrough)); 282 if (job.AudioPassthruOptions.AudioAllowEAC3Pass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.EAc3Passthrough)); 283 if (job.AudioPassthruOptions.AudioAllowFlacPass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.FlacPassthru)); 284 if (job.AudioPassthruOptions.AudioAllowMP3Pass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.Mp3Passthru)); 285 if (job.AudioPassthruOptions.AudioAllowTrueHDPass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.TrueHDPassthrough)); 286 if (job.AudioPassthruOptions.AudioAllowMP2Pass) copyMaskList.Add(EnumHelper<AudioEncoder>.GetShortName(AudioEncoder.Mp2Passthru)); 287 288 audio.CopyMask = copyMaskList.ToArray(); 289 290 HBAudioEncoder audioEncoder = HandBrakeEncoderHelpers.GetAudioEncoder(EnumHelper<AudioEncoder>.GetShortName(job.AudioPassthruOptions.AudioEncoderFallback)); 291 audio.FallbackEncoder = audioEncoder?.ShortName; 292 293 Validate.NotNull(audio.FallbackEncoder, string.Format("Unrecognized audio encoder: {0} \n", job.AudioPassthruOptions.AudioEncoderFallback)); 294 295 audio.AudioList = new List<HandBrake.Interop.Interop.Json.Encode.AudioTrack>(); 296 foreach (AudioTrack item in job.AudioTracks) 297 { 298 HBAudioEncoder encoder = HandBrakeEncoderHelpers.GetAudioEncoder(EnumHelper<AudioEncoder>.GetShortName(item.Encoder)); 299 Validate.NotNull(encoder, "Unrecognized audio encoder:" + item.Encoder); 300 301 if (item.IsPassthru && (item.ScannedTrack.Codec & encoder.Id) == 0) 302 { 303 // We have an unsupported passthru. Rather than let libhb drop the track, switch it to the fallback. 304 encoder = HandBrakeEncoderHelpers.GetAudioEncoder(EnumHelper<AudioEncoder>.GetShortName(job.AudioPassthruOptions.AudioEncoderFallback)); 305 } 306 307 HBMixdown mixdown = HandBrakeEncoderHelpers.GetMixdown(item.MixDown); 308 309 HBRate sampleRate = HandBrakeEncoderHelpers.AudioSampleRates.FirstOrDefault(s => s.Name == item.SampleRate.ToString(CultureInfo.InvariantCulture)); 310 311 HandBrake.Interop.Interop.Json.Encode.AudioTrack audioTrack = new HandBrake.Interop.Interop.Json.Encode.AudioTrack 312 { 313 Track = (item.Track.HasValue ? item.Track.Value : 0) - 1, 314 DRC = item.DRC, 315 Encoder = encoder.ShortName, 316 Gain = item.Gain, 317 Mixdown = mixdown != null ? mixdown.Id : -1, 318 NormalizeMixLevel = false, 319 Samplerate = sampleRate != null ? sampleRate.Rate : 0, 320 Name = !string.IsNullOrEmpty(item.TrackName) ? item.TrackName : null, 321 }; 322 323 if (!item.IsPassthru) 324 { 325 if (item.EncoderRateType == AudioEncoderRateType.Quality) 326 { 327 audioTrack.Quality = item.Quality; 328 } 329 330 if (item.EncoderRateType == AudioEncoderRateType.Bitrate) 331 { 332 audioTrack.Bitrate = item.Bitrate; 333 } 334 } 335 336 audio.AudioList.Add(audioTrack); 337 } 338 339 return audio; 340 } 341 CreateFilters(EncodeTask job)342 private Filters CreateFilters(EncodeTask job) 343 { 344 Filters filter = new Filters 345 { 346 FilterList = new List<Filter>(), 347 }; 348 349 // Note, order is important. 350 351 // Detelecine 352 if (job.Detelecine != Detelecine.Off) 353 { 354 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_DETELECINE, null, null, job.CustomDetelecine); 355 if (!string.IsNullOrEmpty(unparsedJson)) 356 { 357 JsonDocument settings = JsonDocument.Parse(unparsedJson); 358 359 Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_DETELECINE, Settings = settings }; 360 filter.FilterList.Add(filterItem); 361 } 362 } 363 364 // Deinterlace 365 if (job.DeinterlaceFilter == DeinterlaceFilter.Yadif) 366 { 367 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_DEINTERLACE, job.DeinterlacePreset?.ShortName, null, job.CustomDeinterlaceSettings); 368 if (!string.IsNullOrEmpty(unparsedJson)) 369 { 370 JsonDocument root = JsonDocument.Parse(unparsedJson); 371 372 Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_DEINTERLACE, Settings = root }; 373 filter.FilterList.Add(filterItem); 374 } 375 } 376 377 // Decomb 378 if (job.DeinterlaceFilter == DeinterlaceFilter.Decomb) 379 { 380 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_DECOMB, job.DeinterlacePreset?.ShortName, null, job.CustomDeinterlaceSettings); 381 if (!string.IsNullOrEmpty(unparsedJson)) 382 { 383 JsonDocument settings = JsonDocument.Parse(unparsedJson); 384 385 Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_DECOMB, Settings = settings }; 386 filter.FilterList.Add(filterItem); 387 } 388 } 389 390 if (job.DeinterlaceFilter == DeinterlaceFilter.Decomb || job.DeinterlaceFilter == DeinterlaceFilter.Yadif) 391 { 392 if (job.CombDetect != CombDetect.Off) 393 { 394 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_COMB_DETECT, EnumHelper<CombDetect>.GetShortName(job.CombDetect), null, job.CustomCombDetect); 395 if (!string.IsNullOrEmpty(unparsedJson)) 396 { 397 JsonDocument settings = JsonDocument.Parse(unparsedJson); 398 399 Filter filterItem = new Filter 400 { 401 ID = (int)hb_filter_ids.HB_FILTER_COMB_DETECT, 402 Settings = settings 403 }; 404 filter.FilterList.Add(filterItem); 405 } 406 } 407 } 408 409 // Denoise 410 if (job.Denoise != Denoise.Off) 411 { 412 hb_filter_ids id = job.Denoise == Denoise.hqdn3d 413 ? hb_filter_ids.HB_FILTER_HQDN3D 414 : hb_filter_ids.HB_FILTER_NLMEANS; 415 416 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)id, job.DenoisePreset.ToString().ToLower().Replace(" ", string.Empty), job.DenoiseTune.ToString().ToLower().Replace(" ", string.Empty), job.CustomDenoise); 417 418 if (!string.IsNullOrEmpty(unparsedJson)) 419 { 420 JsonDocument settings = JsonDocument.Parse(unparsedJson); 421 422 Filter filterItem = new Filter { ID = (int)id, Settings = settings }; 423 filter.FilterList.Add(filterItem); 424 } 425 } 426 427 // Sharpen 428 if (job.Sharpen != Sharpen.Off) 429 { 430 hb_filter_ids id = job.Sharpen == Sharpen.LapSharp 431 ? hb_filter_ids.HB_FILTER_LAPSHARP 432 : hb_filter_ids.HB_FILTER_UNSHARP; 433 434 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)id, job.SharpenPreset.Key, job.SharpenTune.Key, job.SharpenCustom); 435 436 if (!string.IsNullOrEmpty(unparsedJson)) 437 { 438 JsonDocument settings = JsonDocument.Parse(unparsedJson); 439 440 Filter filterItem = new Filter { ID = (int)id, Settings = settings }; 441 filter.FilterList.Add(filterItem); 442 } 443 } 444 445 // Deblock 446 if (job.DeblockPreset != null && job.DeblockPreset.Key != "off") 447 { 448 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_DEBLOCK, job.DeblockPreset.Key, job.DeblockTune.Key, job.CustomDeblock); 449 if (!string.IsNullOrEmpty(unparsedJson)) 450 { 451 JsonDocument settings = JsonDocument.Parse(unparsedJson); 452 453 Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_DEBLOCK, Settings = settings }; 454 filter.FilterList.Add(filterItem); 455 } 456 } 457 458 // CropScale Filter 459 string cropSettings = string.Format("width={0}:height={1}:crop-top={2}:crop-bottom={3}:crop-left={4}:crop-right={5}", job.Width, job.Height, job.Cropping.Top, job.Cropping.Bottom, job.Cropping.Left, job.Cropping.Right); 460 string unparsedCropSettingsJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_CROP_SCALE, null, null, cropSettings); 461 if (!string.IsNullOrEmpty(unparsedCropSettingsJson)) 462 { 463 JsonDocument cropSettingsJson = JsonDocument.Parse(unparsedCropSettingsJson); 464 465 Filter cropScale = new Filter 466 { 467 ID = (int)hb_filter_ids.HB_FILTER_CROP_SCALE, 468 Settings = cropSettingsJson 469 }; 470 filter.FilterList.Add(cropScale); 471 } 472 473 // Padding Filter 474 if (job.Padding.Enabled) 475 { 476 // Calculate the new Width / Height 477 int? width = job.Width; 478 int? height = job.Height; 479 if (job.Padding.Enabled) 480 { 481 width = width + job.Padding.W; 482 height = height + job.Padding.H; 483 } 484 485 // Setup the filter. 486 string padSettings = string.Format("width={0}:height={1}:color={2}:x={3}:y={4}", width, height, job.Padding.Color, job.Padding.X, job.Padding.Y); 487 string unparsedPadSettingsJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_PAD, null, null, padSettings); 488 if (!string.IsNullOrEmpty(unparsedPadSettingsJson)) 489 { 490 JsonDocument PadSettingsJson = JsonDocument.Parse(unparsedPadSettingsJson); 491 492 Filter padding = new Filter 493 { 494 ID = (int)hb_filter_ids.HB_FILTER_PAD, 495 Settings = PadSettingsJson 496 }; 497 filter.FilterList.Add(padding); 498 } 499 } 500 501 // Colourspace 502 if (job.Colourspace != null && job.Colourspace.Key != "off") 503 { 504 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_COLORSPACE, job.Colourspace.Key, null, job.CustomColourspace); 505 if (!string.IsNullOrEmpty(unparsedJson)) 506 { 507 JsonDocument settings = JsonDocument.Parse(unparsedJson); 508 509 Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_COLORSPACE, Settings = settings }; 510 filter.FilterList.Add(filterItem); 511 } 512 } 513 514 if (job.ChromaSmooth != null && job.ChromaSmooth.Key != "off") 515 { 516 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_CHROMA_SMOOTH, job.ChromaSmooth.Key, job.ChromaSmoothTune?.Key, job.CustomChromaSmooth); 517 if (!string.IsNullOrEmpty(unparsedJson)) 518 { 519 JsonDocument settings = JsonDocument.Parse(unparsedJson); 520 521 Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_CHROMA_SMOOTH, Settings = settings }; 522 filter.FilterList.Add(filterItem); 523 } 524 } 525 526 527 // Grayscale 528 if (job.Grayscale) 529 { 530 Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_GRAYSCALE, Settings = null }; 531 filter.FilterList.Add(filterItem); 532 } 533 534 // Rotate 535 if (job.Rotation != 0 || job.FlipVideo) 536 { 537 string rotateSettings = string.Format("angle={0}:hflip={1}", job.Rotation, job.FlipVideo ? "1" : "0"); 538 string unparsedJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_ROTATE, null, null, rotateSettings); 539 if (!string.IsNullOrEmpty(unparsedJson)) 540 { 541 JsonDocument settings = JsonDocument.Parse(unparsedJson); 542 543 Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_ROTATE, Settings = settings }; 544 filter.FilterList.Add(filterItem); 545 } 546 } 547 548 // Framerate shaping filter 549 int fm = job.FramerateMode == FramerateMode.CFR ? 1 : job.FramerateMode == FramerateMode.PFR ? 2 : 0; 550 int? num = null, den = null; 551 if (job.Framerate != null) 552 { 553 int vrate = HandBrakeUnitConversionHelpers.GetFramerateFromName(job.Framerate.Value.ToString(CultureInfo.InvariantCulture)); 554 555 if (vrate > 0) 556 { 557 num = 27000000; 558 den = vrate; 559 } 560 } 561 562 string framerateString = num.HasValue ? string.Format("mode={0}:rate={1}/{2}", fm, num, den) : string.Format("mode={0}", fm); // filter_cfr, filter_vrate.num, filter_vrate.den 563 string unparsedFramerateJson = HandBrakeFilterHelpers.GenerateFilterSettingJson((int)hb_filter_ids.HB_FILTER_VFR, null, null, framerateString); 564 if (!string.IsNullOrEmpty(unparsedFramerateJson)) 565 { 566 JsonDocument framerateSettings = JsonDocument.Parse(unparsedFramerateJson); 567 568 Filter framerateShaper = new Filter 569 { 570 ID = (int)hb_filter_ids.HB_FILTER_VFR, 571 Settings = framerateSettings 572 }; 573 filter.FilterList.Add(framerateShaper); 574 } 575 576 return filter; 577 } 578 CreateMetadata(EncodeTask job)579 private Metadata CreateMetadata(EncodeTask job) 580 { 581 if (job.MetaData != null && job.MetaData.PassthruMetadataEnabled) 582 { 583 Metadata metaData = new Metadata 584 { 585 Artist = job.MetaData.Artist, 586 Album = job.MetaData.Album, 587 AlbumArtist = job.MetaData.AlbumArtist, 588 Comment = job.MetaData.Comment, 589 Composer = job.MetaData.Composer, 590 Description = job.MetaData.Description, 591 Genre = job.MetaData.Genre, 592 LongDescription = job.MetaData.LongDescription, 593 Name = job.MetaData.Name, 594 ReleaseDate = job.MetaData.ReleaseDate 595 }; 596 return metaData; 597 } 598 599 return new Metadata(); // Empty Metadata will not pass through to the destination. 600 } 601 } 602 } 603