Merge pull request #236 from alex6dj/feature/support-subtitle-hard-burnning

Support subtitle hard burnning
This commit is contained in:
Malte Rosenbjerg 2021-08-06 08:09:33 +02:00 committed by GitHub
commit 01cb994922
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 193 additions and 0 deletions

View file

@ -317,6 +317,27 @@ public void Builder_BuildString_DrawtextFilter_Alt()
str);
}
[TestMethod]
public void Builder_BuildString_SubtitleHardBurnFilter()
{
var str = FFMpegArguments
.FromFileInput("input.mp4")
.OutputToFile("output.mp4", false, opt => opt
.WithVideoFilters(filterOptions => filterOptions
.HardBurnSubtitle(SubtitleHardBurnOptions
.Create(subtitlePath: "sample.srt")
.SetCharacterEncoding("UTF-8")
.SetOriginalSize(1366,768)
.SetSubtitleIndex(0)
.WithStyle(StyleOptions.Create()
.WithParameter("FontName", "DejaVu Serif")
.WithParameter("PrimaryColour", "&HAA00FF00")))))
.Arguments;
Assert.AreEqual("-i \"input.mp4\" -vf \"subtitles=sample.srt:charenc=UTF-8:original_size=1366x768:stream_index=0:force_style='FontName=DejaVu Serif\\,PrimaryColour=&HAA00FF00'\" \"output.mp4\"",
str);
}
[TestMethod]
public void Builder_BuildString_StartNumber()
{

View file

@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace FFMpegCore.Extend
{
internal static class KeyValuePairExtensions
{
/// <summary>
/// Concat the two members of a <see cref="KeyValuePair{TKey,TValue}" />
/// </summary>
/// <param name="pair">Input object</param>
/// <param name="enclose">
/// If true encloses the value part between quotes if contains an space character. If false use the
/// value unmodified
/// </param>
/// <returns>The formatted string</returns>
public static string FormatArgumentPair(this KeyValuePair<string, string> pair, bool enclose)
{
var key = pair.Key;
var value = enclose ? pair.Value.EncloseIfContainsSpace() : pair.Value;
return $"{key}={value}";
}
}
}

View file

@ -0,0 +1,15 @@
namespace FFMpegCore.Extend
{
internal static class StringExtensions
{
/// <summary>
/// Enclose string between quotes if contains an space character
/// </summary>
/// <param name="input">The input</param>
/// <returns>The enclosed string</returns>
public static string EncloseIfContainsSpace(this string input)
{
return input.Contains(" ") ? $"'{input}'" : input;
}
}
}

View file

@ -0,0 +1,132 @@
using FFMpegCore.Extend;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace FFMpegCore.Arguments
{
public class SubtitleHardBurnArgument : IVideoFilterArgument
{
private readonly SubtitleHardBurnOptions _subtitleHardBurnOptions;
public SubtitleHardBurnArgument(SubtitleHardBurnOptions subtitleHardBurnOptions)
{
_subtitleHardBurnOptions = subtitleHardBurnOptions;
}
public string Key => "subtitles";
public string Value => _subtitleHardBurnOptions.TextInternal;
}
public class SubtitleHardBurnOptions
{
private readonly string _subtitle;
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>();
/// <summary>
/// Create a new <see cref="SubtitleHardBurnOptions"/> using a provided subtitle file or a video file
/// containing one.
/// </summary>
/// <param name="subtitlePath"></param>
/// <returns></returns>
/// <remarks>Only support .srt and .ass files, and subrip and ssa subtitle streams</remarks>
public static SubtitleHardBurnOptions Create(string subtitlePath)
{
return new SubtitleHardBurnOptions(subtitlePath);
}
private SubtitleHardBurnOptions(string subtitle)
{
_subtitle = subtitle;
}
/// <summary>
/// Specify the size of the original video, the video for which the ASS file was composed.
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public SubtitleHardBurnOptions SetOriginalSize(int width, int height)
{
return WithParameter("original_size", $"{width}x{height}");
}
/// <summary>
/// Specify the size of the original video, the video for which the ASS file was composed.
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
public SubtitleHardBurnOptions SetOriginalSize(Size size)
{
return SetOriginalSize(size.Width, size.Height);
}
/// <summary>
/// Set subtitles stream index.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
/// <remarks>
/// Used when the provided subtitle is an stream of a video file (ex. .mkv) with multiple subtitles.
/// Represent the index of the subtitle not the stream, them the first subtitle index is 0 and second is 1
/// </remarks>
public SubtitleHardBurnOptions SetSubtitleIndex(int index)
{
return WithParameter("stream_index", index.ToString());
}
/// <summary>
/// Set subtitles input character encoding. Only useful if not UTF-8
/// </summary>
/// <param name="encode">Charset encoding</param>
/// <returns></returns>
public SubtitleHardBurnOptions SetCharacterEncoding(string encode)
{
return WithParameter("charenc", encode);
}
/// <summary>
/// Override default style or script info parameters of the subtitles
/// </summary>
/// <param name="styleOptions"></param>
/// <returns></returns>
public SubtitleHardBurnOptions WithStyle(StyleOptions styleOptions)
{
return WithParameter("force_style", styleOptions.TextInternal);
}
public SubtitleHardBurnOptions WithParameter(string key, string value)
{
Parameters.Add(key, value);
return this;
}
internal string TextInternal => string.Join(":", new[] { _subtitle.EncloseIfContainsSpace() }.Concat(Parameters.Select(parameter => parameter.FormatArgumentPair(enclose: true))));
}
public class StyleOptions
{
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>();
public static StyleOptions Create()
{
return new StyleOptions();
}
/// <summary>
/// Used to override default style or script info parameters of the subtitles. It accepts ASS style format
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public StyleOptions WithParameter(string key, string value)
{
Parameters.Add(key, value);
return this;
}
internal string TextInternal => string.Join(",", Parameters.Select(parameter => parameter.FormatArgumentPair(enclose: false)));
}
}

View file

@ -50,6 +50,7 @@ public class VideoFilterOptions
public VideoFilterOptions Transpose(Transposition transposition) => WithArgument(new TransposeArgument(transposition));
public VideoFilterOptions Mirror(Mirroring mirroring) => WithArgument(new SetMirroringArgument(mirroring));
public VideoFilterOptions DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions));
public VideoFilterOptions HardBurnSubtitle(SubtitleHardBurnOptions subtitleHardBurnOptions) => WithArgument(new SubtitleHardBurnArgument(subtitleHardBurnOptions));
private VideoFilterOptions WithArgument(IVideoFilterArgument argument)
{