/// <summary> /// Convert video encoding speed to H264 preset. /// </summary> /// <param name="encodingSpeed">The encoding speed.</param> /// <returns>The H264 preset.</returns> private string H264EncodingSpeedToPreset(VideoEncodingSpeed encodingSpeed) { switch (encodingSpeed) { case VideoEncodingSpeed.UltraFast: return("ultrafast"); case VideoEncodingSpeed.SuperFast: return("superfast"); case VideoEncodingSpeed.VeryFast: return("veryfast"); case VideoEncodingSpeed.Faster: return("faster"); case VideoEncodingSpeed.Fast: return("fast"); case VideoEncodingSpeed.Medium: return("medium"); case VideoEncodingSpeed.Slow: return("slow"); case VideoEncodingSpeed.Slower: return("slower"); case VideoEncodingSpeed.VerySlow: return("veryslow"); } throw new Exception("Unknown H264 encoding speed."); }
protected virtual void FillFFMpegArgumentsList() { // This option are necessary to be able to read metadata on Windows. src: http://jonhall.info/how_to/create_id3_tags_using_ffmpeg const string mp3MetadataArgs = "-id3v2_version 3 -write_id3v1 1"; // AAC have no standard tag system, use ApeV2 (that are compatible). src: http://eolindel.free.fr/foobar/tags.shtml const string aacMetadataArgs = "-write_apetag 1"; switch (this.ConversionPreset.OutputType) { case OutputType.Aac: { string channelArgs = ConversionJob_FFMPEG.ComputeAudioChannelArgs(this.ConversionPreset); // https://trac.ffmpeg.org/wiki/Encode/AAC int audioEncodingBitrate = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.AudioBitrate); string encoderArgs = $"-c:a aac -q:a {this.AACBitrateToQualityIndex(audioEncodingBitrate)} {channelArgs} {aacMetadataArgs}"; string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Avi: { // https://trac.ffmpeg.org/wiki/Encode/MPEG-4 int videoEncodingQuality = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.VideoQuality); int audioEncodingBitrate = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.AudioBitrate); string transformArgs = ConversionJob_FFMPEG.ComputeTransformArgs(this.ConversionPreset); string audioArgs = "-an"; if (this.ConversionPreset.GetSettingsValue <bool>(ConversionPreset.ConversionSettingKeys.EnableAudio)) { audioArgs = string.Format("-c:a libmp3lame -qscale:a {0}", this.MP3VBRBitrateToQualityIndex(audioEncodingBitrate)); } // Compute final arguments. string videoFilteringArgs = ConversionJob_FFMPEG.Encapsulate("-vf", transformArgs); string encoderArgs = $"-c:v mpeg4 -vtag xvid -qscale:v {this.MPEG4QualityToQualityIndex(videoEncodingQuality)} {audioArgs} {videoFilteringArgs} {mp3MetadataArgs}"; string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Flac: { string channelArgs = ConversionJob_FFMPEG.ComputeAudioChannelArgs(this.ConversionPreset); // http://taer-naguur.blogspot.fr/2013/11/flac-audio-encoding-with-ffmpeg.html string encoderArgs = $"-compression_level 12 {channelArgs}"; string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Gif: { // http://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html string fileName = Path.GetFileName(this.InputFilePath); string tempPath = Path.GetTempPath(); string paletteFilePath = PathHelpers.GenerateUniquePath(tempPath + fileName + " - palette.png"); string transformArgs = ConversionJob_FFMPEG.ComputeTransformArgs(this.ConversionPreset); // fps. int framesPerSecond = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.VideoFramesPerSecond); if (!string.IsNullOrEmpty(transformArgs)) { transformArgs += ","; } transformArgs += string.Format("fps={0}", framesPerSecond); // Generate palette. string encoderArgs = string.Format("-vf \"{0},palettegen\"", transformArgs); string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, paletteFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass("Indexing colors", arguments, paletteFilePath)); // Create gif. encoderArgs = string.Format("-i \"{0}\" -lavfi \"{1},paletteuse\"", paletteFilePath, transformArgs); arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Ico: { string encoderArgs = string.Empty; string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Jpg: { int encodingQuality = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.ImageQuality); float scaleFactor = this.ConversionPreset.GetSettingsValue <float>(ConversionPreset.ConversionSettingKeys.ImageScale); string scaleArgs = string.Empty; if (Math.Abs(scaleFactor - 1f) >= 0.005f) { scaleArgs = string.Format("-vf scale=iw*{0}:ih*{0}", scaleFactor.ToString("#.##", CultureInfo.InvariantCulture)); } string encoderArgs = string.Format("-q:v {0} {1}", this.JPGQualityToQualityIndex(encodingQuality), scaleArgs); string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Mp3: { string channelArgs = ConversionJob_FFMPEG.ComputeAudioChannelArgs(this.ConversionPreset); string encoderArgs = string.Empty; EncodingMode encodingMode = this.ConversionPreset.GetSettingsValue <EncodingMode>(ConversionPreset.ConversionSettingKeys.AudioEncodingMode); int encodingQuality = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.AudioBitrate); switch (encodingMode) { case EncodingMode.Mp3VBR: encoderArgs = $"-codec:a libmp3lame -q:a {this.MP3VBRBitrateToQualityIndex(encodingQuality)} {channelArgs} {mp3MetadataArgs}"; break; case EncodingMode.Mp3CBR: encoderArgs = $"-codec:a libmp3lame -b:a {encodingQuality}k {channelArgs} {mp3MetadataArgs}"; break; default: break; } string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Mkv: case OutputType.Mp4: { // https://trac.ffmpeg.org/wiki/Encode/H.264 // https://trac.ffmpeg.org/wiki/Encode/AAC int videoEncodingQuality = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.VideoQuality); VideoEncodingSpeed videoEncodingSpeed = this.ConversionPreset.GetSettingsValue <VideoEncodingSpeed>(ConversionPreset.ConversionSettingKeys.VideoEncodingSpeed); int audioEncodingBitrate = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.AudioBitrate); string transformArgs = ConversionJob_FFMPEG.ComputeTransformArgs(this.ConversionPreset); string videoFilteringArgs = ConversionJob_FFMPEG.Encapsulate("-vf", transformArgs); string audioArgs = "-an"; if (this.ConversionPreset.GetSettingsValue <bool>(ConversionPreset.ConversionSettingKeys.EnableAudio)) { audioArgs = $"-c:a aac -qscale:a {this.AACBitrateToQualityIndex(audioEncodingBitrate)}"; } string encoderArgs = string.Format( "-c:v libx264 -preset {0} -crf {1} {2} {3}", this.H264EncodingSpeedToPreset(videoEncodingSpeed), this.H264QualityToCRF(videoEncodingQuality), audioArgs, videoFilteringArgs); string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Ogg: { string channelArgs = ConversionJob_FFMPEG.ComputeAudioChannelArgs(this.ConversionPreset); int encodingQuality = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.AudioBitrate); string encoderArgs = $"-vn -codec:a libvorbis -qscale:a {this.OGGVBRBitrateToQualityIndex(encodingQuality)} {channelArgs}"; string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Ogv: { // https://trac.ffmpeg.org/wiki/TheoraVorbisEncodingGuide int videoEncodingQuality = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.VideoQuality); int audioEncodingBitrate = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.AudioBitrate); string transformArgs = ConversionJob_FFMPEG.ComputeTransformArgs(this.ConversionPreset); string videoFilteringArgs = ConversionJob_FFMPEG.Encapsulate("-vf", transformArgs); string audioArgs = "-an"; if (this.ConversionPreset.GetSettingsValue <bool>(ConversionPreset.ConversionSettingKeys.EnableAudio)) { audioArgs = $"-codec:a libvorbis -qscale:a {this.OGGVBRBitrateToQualityIndex(audioEncodingBitrate)}"; } string encoderArgs = $"-codec:v libtheora -qscale:v {this.OGVTheoraQualityToQualityIndex(videoEncodingQuality)} {audioArgs} {videoFilteringArgs}"; string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Png: { float scaleFactor = this.ConversionPreset.GetSettingsValue <float>(ConversionPreset.ConversionSettingKeys.ImageScale); string scaleArgs = string.Empty; if (Math.Abs(scaleFactor - 1f) >= 0.005f) { scaleArgs = string.Format("-vf scale=iw*{0}:ih*{0}", scaleFactor.ToString("#.##", CultureInfo.InvariantCulture)); } // http://www.howtogeek.com/203979/is-the-png-format-lossless-since-it-has-a-compression-parameter/ string encoderArgs = string.Format("-compression_level 100 {0}", scaleArgs); string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Wav: { string channelArgs = ConversionJob_FFMPEG.ComputeAudioChannelArgs(this.ConversionPreset); EncodingMode encodingMode = this.ConversionPreset.GetSettingsValue <EncodingMode>(ConversionPreset.ConversionSettingKeys.AudioEncodingMode); string encoderArgs = $"-acodec {this.WAVEncodingToCodecArgument(encodingMode)} {channelArgs}"; string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; case OutputType.Webm: { // https://trac.ffmpeg.org/wiki/Encode/VP9 int videoEncodingQuality = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.VideoQuality); int audioEncodingQuality = this.ConversionPreset.GetSettingsValue <int>(ConversionPreset.ConversionSettingKeys.AudioBitrate); string encodingArgs = string.Empty; if (videoEncodingQuality == 63) { // Replace maximum quality settings by lossless compression. encodingArgs = $"-lossless 1"; } else { encodingArgs = $"-crf {this.WebmQualityToCRF(videoEncodingQuality)} -b:v 0"; } string transformArgs = ConversionJob_FFMPEG.ComputeTransformArgs(this.ConversionPreset); string videoFilteringArgs = ConversionJob_FFMPEG.Encapsulate("-vf", transformArgs); string audioArgs = "-an"; if (this.ConversionPreset.GetSettingsValue <bool>(ConversionPreset.ConversionSettingKeys.EnableAudio)) { audioArgs = string.Format("-c:a libvorbis -qscale:a {0}", this.OGGVBRBitrateToQualityIndex(audioEncodingQuality)); } string encoderArgs = string.Format( "-c:v libvpx-vp9 {0} {1} {2}", encodingArgs, audioArgs, videoFilteringArgs); string arguments = string.Format("-n -stats -i \"{0}\" {2} \"{1}\"", this.InputFilePath, this.OutputFilePath, encoderArgs); this.ffmpegArgumentStringByPass.Add(new FFMpegPass(arguments)); } break; default: throw new NotImplementedException("Converter not implemented for output file type " + this.ConversionPreset.OutputType); } if (this.ffmpegArgumentStringByPass.Count == 0) { throw new Exception("No ffmpeg arguments generated."); } for (int index = 0; index < this.ffmpegArgumentStringByPass.Count; index++) { if (string.IsNullOrEmpty(this.ffmpegArgumentStringByPass[index].Arguments)) { throw new Exception("Invalid ffmpeg process arguments."); } } }