58e11ba4e3
New "Video | Shorts" tab strip on the main page. The existing Video tab and its render pipeline are unchanged; Shorts is a new, independent workflow sharing the same render-progress/log/output controls. - Services/ShortsPipeline.cs: C# port of amigadb-short.sh rather than shelling out to bash — end users on plain Windows don't have bash/WSL, and shipping this as a native part of the app keeps distribution self-contained. All 19 visual style filtergraphs (brand, pixel, mirror, crop, workbench, blur, crt, copper, stars, grid, tiles, scan, starfield, plasma, rasterbars, vhs, monitor, split, spectrum) are copied verbatim from the bash heredocs. Per explicit decision, kept parity with the script's actual behavior where the brand-style HOOK_BOX_Y/INFO_BOX_Y/font-size variables are computed but never actually used in the final filter (dead code in the original) rather than "fixing" it and changing rendered output. Reuses ToolExtractor/MediaProbe/ProcessRunner exactly as RenderPipeline does, so FFmpeg resolution and NVENC fallback behave identically. - Models/ShortSettings.cs: job parameters + ShortStyle enum. - AppSettings gains sticky Shorts defaults (style, hook, website, font, background image, CRF, preset) alongside the existing Video defaults. - MainPage: Video/Shorts tab buttons toggle two content panels; the background-image field auto-hides for non-brand styles; font field defaults to the first bold system font found (Segoe UI Bold, Segoe UI Semibold, Arial Bold, Calibri Bold); blank output path is auto-derived from the input filename and style, matching the script's default. - 13 new unit tests (escaping order, meta-line joining, validation, every style producing a non-empty filter, the drawtext trailer's timing gates). Full run is 72/72 passing. - Fixed a bug caught during manual testing: MainPage.Resources["PrimaryButtonStyle"] looked in the Page's own (empty) resource dictionary instead of Application.Current.Resources, throwing on every tab switch. Verified end-to-end: rendered a real Short from an existing video through the actual running app (not just unit tests) and inspected extracted frames — hook/title/meta/website text overlays appear and disappear at the correct timestamps with correct styling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
387 lines
24 KiB
C#
387 lines
24 KiB
C#
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text;
|
|
using AmiReel.Models;
|
|
|
|
namespace AmiReel.Services;
|
|
|
|
/// <summary>
|
|
/// Renders a vertical (1080x1920, 50 FPS) YouTube Short from an existing video, with a chosen
|
|
/// visual style and title-card text overlays. A C# port of amigadb-short.sh so end users don't
|
|
/// need bash/WSL — the FFmpeg filtergraphs below are copied verbatim from that script.
|
|
/// </summary>
|
|
public sealed class ShortsPipeline
|
|
{
|
|
private readonly ProcessRunner _runner = new();
|
|
|
|
public async Task RenderAsync(ShortSettings settings, IProgress<RenderProgress> progress, Action<string> log, CancellationToken token)
|
|
{
|
|
Validate(settings);
|
|
ToolPaths tools = await ToolExtractor.ResolveAsync(settings.FfmpegPath, settings.FfprobePath, token);
|
|
|
|
if (settings.Style == ShortStyle.Spectrum)
|
|
{
|
|
MediaProbe probe = new(_runner, log);
|
|
bool hasAudio = await probe.HasAudioAsync(tools, settings.InputFile, token);
|
|
if (!hasAudio)
|
|
throw new InvalidOperationException("The spectrum style requires an audio stream in the source video.");
|
|
}
|
|
|
|
string? outputDirectory = Path.GetDirectoryName(Path.GetFullPath(settings.OutputPath));
|
|
if (!string.IsNullOrEmpty(outputDirectory))
|
|
Directory.CreateDirectory(outputDirectory);
|
|
|
|
string workDir = Path.Combine(Path.GetTempPath(), "AmiReel", "shorts", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(workDir);
|
|
|
|
try
|
|
{
|
|
string hookFile = WriteTextFile(workDir, "hook.txt", settings.Hook);
|
|
string titleFile = WriteTextFile(workDir, "title.txt", settings.Title);
|
|
string metaFile = WriteTextFile(workDir, "meta.txt", BuildMeta(settings));
|
|
string websiteFile = WriteTextFile(workDir, "website.txt", settings.Website);
|
|
|
|
double duration = settings.DurationSeconds;
|
|
double infoStart = Math.Max(0, duration - 7);
|
|
double ctaStart = Math.Max(0, duration - 3.5);
|
|
double hookEnd = Math.Min(2.7, duration);
|
|
|
|
string filterComplex = BuildFilterComplex(
|
|
GetBaseFilter(settings.Style),
|
|
fontEsc: EscapeFfmpegPath(settings.FontFile),
|
|
hookEsc: EscapeFfmpegPath(hookFile),
|
|
titleEsc: EscapeFfmpegPath(titleFile),
|
|
metaEsc: EscapeFfmpegPath(metaFile),
|
|
websiteEsc: EscapeFfmpegPath(websiteFile),
|
|
hookEnd, infoStart, ctaStart);
|
|
|
|
List<string> args = ["-hide_banner", "-y", "-ss", settings.Start, "-t", F(duration), "-i", settings.InputFile];
|
|
if (settings.Style == ShortStyle.Brand)
|
|
args.AddRange(["-loop", "1", "-i", settings.BackgroundImage]);
|
|
|
|
args.AddRange([
|
|
"-filter_complex", filterComplex,
|
|
"-map", "[vout]",
|
|
"-map", "0:a?",
|
|
"-c:v", "libx264",
|
|
"-preset", settings.Preset,
|
|
"-crf", settings.Crf.ToString(CultureInfo.InvariantCulture),
|
|
"-profile:v", "high",
|
|
"-level:v", "4.2",
|
|
"-pix_fmt", "yuv420p",
|
|
"-c:a", "aac",
|
|
"-b:a", "320k",
|
|
"-ar", "48000",
|
|
"-movflags", "+faststart",
|
|
"-shortest",
|
|
settings.OutputPath,
|
|
]);
|
|
|
|
progress.Report(new(2, "Rendering short", $"Applying the '{settings.Style}' style"));
|
|
TimeSpan totalSpan = TimeSpan.FromSeconds(duration);
|
|
await _runner.RunAsync(tools.Ffmpeg, args, log, null, token, outputHandler: output =>
|
|
{
|
|
if (output.Time is not { } time) return;
|
|
double percent = Math.Min(98, 2 + time.TotalSeconds / duration * 96);
|
|
progress.Report(new(percent, "Rendering short", $"{time:hh\\:mm\\:ss} / {totalSpan:hh\\:mm\\:ss}"));
|
|
});
|
|
|
|
progress.Report(new(100, "Complete", settings.OutputPath));
|
|
}
|
|
finally
|
|
{
|
|
try { Directory.Delete(workDir, true); } catch { }
|
|
}
|
|
}
|
|
|
|
/// <summary>Looks for a common bold system font to pre-fill the Shorts font field with.</summary>
|
|
public static string? FindDefaultFont()
|
|
{
|
|
string fontsDir = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
|
|
foreach (string name in new[] { "segoeuib.ttf", "seguisb.ttf", "arialbd.ttf", "calibrib.ttf" })
|
|
{
|
|
string candidate = Path.Combine(fontsDir, name);
|
|
if (File.Exists(candidate)) return candidate;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static void Validate(ShortSettings s)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(s.InputFile) || !File.Exists(s.InputFile))
|
|
throw new FileNotFoundException("Select a valid source video for the Short.");
|
|
if (string.IsNullOrWhiteSpace(s.Title))
|
|
throw new ArgumentException("Enter a title.");
|
|
if (string.IsNullOrWhiteSpace(s.OutputPath))
|
|
throw new ArgumentException("Select an output file.");
|
|
if (s.DurationSeconds <= 0)
|
|
throw new ArgumentException("Duration must be greater than zero.");
|
|
if (string.IsNullOrWhiteSpace(s.FontFile) || !File.Exists(s.FontFile))
|
|
throw new FileNotFoundException("Select a bold TrueType/OpenType font file.");
|
|
if (s.Style == ShortStyle.Brand && (string.IsNullOrWhiteSpace(s.BackgroundImage) || !File.Exists(s.BackgroundImage)))
|
|
throw new FileNotFoundException("The brand style requires a background image.");
|
|
}
|
|
|
|
internal static string BuildMeta(ShortSettings s)
|
|
{
|
|
List<string> parts = [];
|
|
if (!string.IsNullOrWhiteSpace(s.Group)) parts.Add(s.Group.Trim());
|
|
if (!string.IsNullOrWhiteSpace(s.Year)) parts.Add(s.Year.Trim());
|
|
if (!string.IsNullOrWhiteSpace(s.Type)) parts.Add(s.Type.Trim());
|
|
return string.Join(" · ", parts);
|
|
}
|
|
|
|
internal static string EscapeFfmpegPath(string value) =>
|
|
value.Replace("\\", "\\\\").Replace(":", "\\:").Replace("'", "\\'");
|
|
|
|
private static string WriteTextFile(string directory, string fileName, string content)
|
|
{
|
|
string path = Path.Combine(directory, fileName);
|
|
File.WriteAllText(path, content + "\n", new UTF8Encoding(false));
|
|
return path;
|
|
}
|
|
|
|
internal static string BuildFilterComplex(
|
|
string baseFilter, string fontEsc, string hookEsc, string titleEsc, string metaEsc, string websiteEsc,
|
|
double hookEnd, double infoStart, double ctaStart)
|
|
{
|
|
string hookEndStr = F(hookEnd);
|
|
string infoStartStr = F(infoStart);
|
|
string ctaStartStr = F(ctaStart);
|
|
|
|
return baseFilter + ";" +
|
|
"[base]" +
|
|
$"drawbox=x=0:y=0:w=1080:h=245:color=black@0.50:t=fill:enable='between(t,0,{hookEndStr})'," +
|
|
$"drawtext=fontfile='{fontEsc}':textfile='{hookEsc}':fontcolor=white:fontsize=70:line_spacing=10:x=(w-text_w)/2:y=70:fix_bounds=true:shadowcolor=black@0.75:shadowx=4:shadowy=4:enable='between(t,0,{hookEndStr})'," +
|
|
$"drawbox=x=0:y=1490:w=1080:h=430:color=black@0.62:t=fill:enable='gte(t,{infoStartStr})'," +
|
|
$"drawtext=fontfile='{fontEsc}':textfile='{titleEsc}':fontcolor=white:fontsize=62:line_spacing=8:x=(w-text_w)/2:y=1560:fix_bounds=true:shadowcolor=black@0.80:shadowx=4:shadowy=4:enable='gte(t,{infoStartStr})'," +
|
|
$"drawtext=fontfile='{fontEsc}':textfile='{metaEsc}':fontcolor=white@0.92:fontsize=42:x=(w-text_w)/2:y=1655:fix_bounds=true:shadowcolor=black@0.80:shadowx=3:shadowy=3:enable='gte(t,{infoStartStr})'," +
|
|
$"drawtext=fontfile='{fontEsc}':textfile='{websiteEsc}':fontcolor=0x55c8ff:fontsize=52:x=(w-text_w)/2:y=1780:fix_bounds=true:shadowcolor=black@0.85:shadowx=4:shadowy=4:enable='gte(t,{ctaStartStr})'," +
|
|
"fps=50,format=yuv420p[vout]";
|
|
}
|
|
|
|
private static string F(double value) => value.ToString("0.###", CultureInfo.InvariantCulture);
|
|
|
|
internal static string GetBaseFilter(ShortStyle style) => style switch
|
|
{
|
|
ShortStyle.Brand => """
|
|
[1:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1[bg];
|
|
[0:v]scale=920:700:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0x07111f,setsar=1,eq=contrast=1.02:saturation=1.02[fg];
|
|
[bg]drawbox=x=65:y=505:w=950:h=730:color=black@0.22:t=fill[bg2];
|
|
[bg2][fg]overlay=x=(W-w)/2:y=520:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Pixel => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=270:480:force_original_aspect_ratio=increase,crop=270:480,scale=1080:1920:flags=neighbor,eq=brightness=-0.36:saturation=1.18,gblur=sigma=2,setsar=1[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0x55c8ff,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto,
|
|
drawbox=x=0:y='300+20*sin(t*1.70)':w=1080:h=8:color=0x55c8ff@0.72:t=fill,
|
|
drawbox=x=0:y='1610+18*sin(t*1.35)':w=1080:h=8:color=0xff7020@0.72:t=fill[base]
|
|
""",
|
|
|
|
ShortStyle.Mirror => """
|
|
[0:v]split=3[fg_src][top_src][bottom_src];
|
|
[top_src]scale=1080:620:force_original_aspect_ratio=increase,crop=1080:620,vflip,eq=brightness=-0.34:saturation=0.82,gblur=sigma=9,setsar=1[top];
|
|
[bottom_src]scale=1080:620:force_original_aspect_ratio=increase,crop=1080:620,vflip,eq=brightness=-0.34:saturation=0.82,gblur=sigma=9,setsar=1[bottom];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+12:ih+12:6:6:color=white@0.70,setsar=1[fg];
|
|
[top]pad=1080:1920:0:0:color=0x050616[canvas];
|
|
[canvas][bottom]overlay=x=0:y=1300[tmp];
|
|
[tmp][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Crop => """
|
|
[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,eq=brightness=-0.04:saturation=1.05[base]
|
|
""",
|
|
|
|
ShortStyle.Workbench => """
|
|
[0:v]split=2[canvas_src][screen_src];
|
|
[canvas_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,
|
|
drawbox=x=0:y=0:w=1080:h=1920:color=0x090c20:t=fill,
|
|
drawbox=x=55:y=410:w=970:h=930:color=0xc4c8d0:t=fill,
|
|
drawbox=x=70:y=425:w=940:h=900:color=0x1b4f9c:t=fill,
|
|
drawbox=x=70:y=425:w=940:h=74:color=0xf28c28:t=fill,
|
|
drawbox=x=88:y=445:w=34:h=34:color=0x111111:t=fill[window];
|
|
[screen_src]scale=960:760:force_original_aspect_ratio=decrease,pad=iw+10:ih+10:5:5:color=0xffffff,setsar=1[screen];
|
|
[window][screen]overlay=x=(W-w)/2:y=525:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Blur => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=35,eq=brightness=-0.22:saturation=0.85,setsar=1[bg];
|
|
[fg_src]scale=1080:900:force_original_aspect_ratio=decrease,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Crt => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=28,eq=brightness=-0.45:saturation=0.55,setsar=1[bg];
|
|
[fg_src]scale=980:800:force_original_aspect_ratio=decrease,lenscorrection=k1=-0.060:k2=0.020,vignette=angle=PI/5,eq=contrast=1.08:saturation=1.10,setsar=1,
|
|
drawgrid=width=iw:height=4:thickness=1:color=black@0.18[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto,
|
|
drawbox=x=50:y='220+mod(t*340,1480)':w=980:h=4:color=white@0.08:t=fill[base]
|
|
""",
|
|
|
|
ShortStyle.Copper => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,eq=brightness=-0.50:saturation=0.80,gblur=sigma=18,setsar=1,
|
|
drawbox=x=0:y=0:w=1080:h=1920:color=0x061a5f@0.55:t=fill,
|
|
drawbox=x=0:y='220+20*sin(t*1.00)':w=1080:h=140:color=0x123fcb@0.28:t=fill,
|
|
drawbox=x=0:y='420+24*sin(t*1.20)':w=1080:h=100:color=0x2b8cff@0.22:t=fill,
|
|
drawbox=x=0:y='660+16*sin(t*1.50)':w=1080:h=120:color=0x00b8ff@0.18:t=fill,
|
|
drawbox=x=0:y='1110+22*sin(t*1.10)':w=1080:h=150:color=0xff7a1f@0.20:t=fill,
|
|
drawbox=x=0:y='1360+18*sin(t*1.30)':w=1080:h=110:color=0xffc120@0.18:t=fill,
|
|
drawbox=x=0:y='1660+15*sin(t*1.70)':w=1080:h=130:color=0x58d4ff@0.18:t=fill[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0xffffff,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Stars => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=30,eq=brightness=-0.72:saturation=0.28,setsar=1,
|
|
drawbox=x=0:y=0:w=1080:h=1920:color=0x05081b@0.75:t=fill,
|
|
drawbox=x='mod(100+80*t,1080)':y='mod(80+210*t,1920)':w=3:h=3:color=white@0.95:t=fill,
|
|
drawbox=x='mod(880-55*t,1080)':y='mod(220+170*t,1920)':w=2:h=2:color=white@0.85:t=fill,
|
|
drawbox=x='mod(300+120*t,1080)':y='mod(520+140*t,1920)':w=3:h=3:color=0x9ad8ff@0.90:t=fill,
|
|
drawbox=x='mod(980-75*t,1080)':y='mod(710+160*t,1920)':w=2:h=2:color=white@0.80:t=fill,
|
|
drawbox=x='mod(420+65*t,1080)':y='mod(1040+200*t,1920)':w=4:h=4:color=0xffd46b@0.85:t=fill,
|
|
drawbox=x='mod(750-90*t,1080)':y='mod(1280+110*t,1920)':w=2:h=2:color=white@0.88:t=fill,
|
|
drawbox=x='mod(210+75*t,1080)':y='mod(1440+180*t,1920)':w=3:h=3:color=0x9ad8ff@0.90:t=fill,
|
|
drawbox=x='mod(1030-60*t,1080)':y='mod(1680+140*t,1920)':w=2:h=2:color=white@0.82:t=fill,
|
|
drawbox=x='mod(560+95*t,1080)':y='mod(260+260*t,1920)':w=2:h=2:color=white@0.92:t=fill,
|
|
drawbox=x='mod(60+110*t,1080)':y='mod(860+120*t,1920)':w=3:h=3:color=0xffd46b@0.82:t=fill[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+12:ih+12:6:6:color=0x55c8ff,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Grid => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=24,eq=brightness=-0.62:saturation=0.36,setsar=1,
|
|
drawbox=x=0:y=0:w=1080:h=1920:color=0x090b17@0.72:t=fill,
|
|
drawgrid=width=90:height=90:thickness=2:color=0x39d2ff@0.18,
|
|
drawbox=x=0:y=0:w=1080:h=1120:color=0x050814@0.80:t=fill,
|
|
drawbox=x=0:y=1120:w=1080:h=4:color=0xff54d9@0.70:t=fill[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0xff54d9,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Tiles => """
|
|
[0:v]split=5[fg_src][a][b][c][d];
|
|
[a]scale=540:960:force_original_aspect_ratio=increase,crop=540:960,eq=brightness=-0.45:saturation=0.68,gblur=sigma=4,setsar=1[ta];
|
|
[b]scale=540:960:force_original_aspect_ratio=increase,crop=540:960,hflip,eq=brightness=-0.45:saturation=0.68,gblur=sigma=4,setsar=1[tb];
|
|
[c]scale=540:960:force_original_aspect_ratio=increase,crop=540:960,vflip,eq=brightness=-0.45:saturation=0.68,gblur=sigma=4,setsar=1[tc];
|
|
[d]scale=540:960:force_original_aspect_ratio=increase,crop=540:960,hflip,vflip,eq=brightness=-0.45:saturation=0.68,gblur=sigma=4,setsar=1[td];
|
|
[ta][tb]hstack=inputs=2[top];
|
|
[tc][td]hstack=inputs=2[bottom];
|
|
[top][bottom]vstack=inputs=2[bg];
|
|
[fg_src]scale=980:800:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0xffffff,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Scan => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,scale=360:640:flags=neighbor,scale=1080:1920:flags=neighbor,eq=brightness=-0.44:saturation=1.05,gblur=sigma=2,setsar=1[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0x55c8ff,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto,
|
|
drawbox=x=40:y='220+mod(t*380,1480)':w=1000:h=8:color=white@0.14:t=fill,
|
|
drawbox=x=0:y='280+20*sin(t*1.40)':w=1080:h=6:color=0x55c8ff@0.45:t=fill,
|
|
drawbox=x=0:y='1640+15*sin(t*1.20)':w=1080:h=6:color=0xff7020@0.45:t=fill[base]
|
|
""",
|
|
|
|
ShortStyle.Starfield => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=32,eq=brightness=-0.78:saturation=0.22,setsar=1,
|
|
drawbox=x=0:y=0:w=1080:h=1920:color=0x020511@0.82:t=fill,
|
|
drawbox=x='mod(120+75*t,1080)':y='mod(80+520*t,1920)':w=3:h=18:color=white@0.92:t=fill,
|
|
drawbox=x='mod(970-55*t,1080)':y='mod(250+430*t,1920)':w=2:h=14:color=0xa4dcff@0.85:t=fill,
|
|
drawbox=x='mod(370+95*t,1080)':y='mod(430+600*t,1920)':w=4:h=24:color=white@0.90:t=fill,
|
|
drawbox=x='mod(810-80*t,1080)':y='mod(640+470*t,1920)':w=2:h=17:color=0xffdc83@0.85:t=fill,
|
|
drawbox=x='mod(230+65*t,1080)':y='mod(900+540*t,1920)':w=3:h=20:color=white@0.88:t=fill,
|
|
drawbox=x='mod(1020-100*t,1080)':y='mod(1110+650*t,1920)':w=4:h=26:color=0x8fd5ff@0.90:t=fill,
|
|
drawbox=x='mod(560+85*t,1080)':y='mod(1320+490*t,1920)':w=2:h=16:color=white@0.86:t=fill,
|
|
drawbox=x='mod(70+110*t,1080)':y='mod(1510+590*t,1920)':w=3:h=22:color=0xffdc83@0.82:t=fill,
|
|
drawbox=x='mod(720-60*t,1080)':y='mod(1720+450*t,1920)':w=2:h=15:color=white@0.88:t=fill,
|
|
drawbox=x='mod(440+105*t,1080)':y='mod(180+700*t,1920)':w=3:h=28:color=0xa4dcff@0.88:t=fill,
|
|
drawbox=x='mod(900-72*t,1080)':y='mod(780+560*t,1920)':w=2:h=18:color=white@0.84:t=fill,
|
|
drawbox=x='mod(310+90*t,1080)':y='mod(1240+620*t,1920)':w=4:h=24:color=white@0.90:t=fill[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0x70cfff,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Plasma => """
|
|
[0:v]split=2[dim_src][fg_src];
|
|
nullsrc=size=1080x1920:rate=50,format=rgb24,
|
|
geq=r='128+105*sin(X/68+T*1.9)+22*sin(Y/91-T*1.1)':g='128+96*sin(Y/75+T*1.5)+26*sin((X+Y)/120+T*1.3)':b='128+110*sin((X-Y)/82-T*1.7)',
|
|
gblur=sigma=18,eq=brightness=-0.16:saturation=1.22[plasma];
|
|
[dim_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=26,eq=brightness=-0.70:saturation=0.35,setsar=1[dim];
|
|
[plasma][dim]blend=all_mode=overlay:all_opacity=0.35[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0xffffff,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Rasterbars => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=30,eq=brightness=-0.70:saturation=0.35,setsar=1,
|
|
drawbox=x=0:y=0:w=1080:h=1920:color=0x05061b@0.80:t=fill,
|
|
drawbox=x=0:y='250+90*sin(t*1.25)':w=1080:h=30:color=0xff3b7f@0.78:t=fill,
|
|
drawbox=x=0:y='300+90*sin(t*1.25)':w=1080:h=24:color=0xff7b32@0.72:t=fill,
|
|
drawbox=x=0:y='345+90*sin(t*1.25)':w=1080:h=18:color=0xffd12b@0.66:t=fill,
|
|
drawbox=x=0:y='1510+85*sin(t*1.10+1.2)':w=1080:h=18:color=0x55e6ff@0.68:t=fill,
|
|
drawbox=x=0:y='1550+85*sin(t*1.10+1.2)':w=1080:h=24:color=0x4b8cff@0.72:t=fill,
|
|
drawbox=x=0:y='1600+85*sin(t*1.10+1.2)':w=1080:h=30:color=0x8b55ff@0.76:t=fill[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0xffffff,setsar=1[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Vhs => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=26,eq=brightness=-0.44:saturation=0.55,noise=alls=10:allf=t+u,setsar=1[bg];
|
|
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,chromashift=cbh=2:crh=-2,noise=alls=5:allf=t+u,eq=contrast=1.08:saturation=0.92,vignette=angle=PI/4.5,setsar=1,
|
|
drawgrid=width=iw:height=5:thickness=1:color=black@0.13[fg];
|
|
[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto,
|
|
drawbox=x=0:y='300+mod(t*510,1320)':w=1080:h=10:color=white@0.08:t=fill,
|
|
drawbox=x=0:y='420+mod(t*320,1080)':w=1080:h=3:color=0x7bd7ff@0.10:t=fill[base]
|
|
""",
|
|
|
|
ShortStyle.Monitor => """
|
|
[0:v]split=2[bg_src][screen_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=34,eq=brightness=-0.68:saturation=0.32,setsar=1,
|
|
drawbox=x=0:y=0:w=1080:h=1920:color=0x11131a@0.78:t=fill,
|
|
drawbox=x=42:y=365:w=996:h=1000:color=0xaaa69a:t=fill,
|
|
drawbox=x=68:y=392:w=944:h=944:color=0x2a2926:t=fill,
|
|
drawbox=x=105:y=438:w=870:h=790:color=0x050505:t=fill,
|
|
drawbox=x=792:y=1260:w=44:h=18:color=0x6aff73:t=fill,
|
|
drawbox=x=852:y=1253:w=30:h=30:color=0x232323:t=fill,
|
|
drawbox=x=910:y=1253:w=30:h=30:color=0x232323:t=fill,
|
|
drawbox=x=380:y=1365:w=320:h=75:color=0x8f8b80:t=fill,
|
|
drawbox=x=290:y=1438:w=500:h=36:color=0x77736b:t=fill[monitor];
|
|
[screen_src]scale=840:750:force_original_aspect_ratio=decrease,lenscorrection=k1=-0.045:k2=0.012,vignette=angle=PI/5,eq=contrast=1.06:saturation=1.06,setsar=1[screen];
|
|
[monitor][screen]overlay=x=(W-w)/2:y=458:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Split => """
|
|
[0:v]split=4[bg_src][main_src][top_src][bottom_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=34,eq=brightness=-0.55:saturation=0.55,setsar=1[bg];
|
|
[top_src]scale=1080:520:force_original_aspect_ratio=increase,crop=1080:360:y='(ih-360)/2',eq=brightness=-0.28:saturation=0.88,gblur=sigma=2,setsar=1[top];
|
|
[bottom_src]scale=1080:520:force_original_aspect_ratio=increase,crop=1080:360:y='(ih-360)/2',hflip,eq=brightness=-0.32:saturation=0.80,gblur=sigma=2,setsar=1[bottom];
|
|
[main_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0xffffff,setsar=1[main];
|
|
[bg][top]overlay=x=0:y=275[tmp1];
|
|
[tmp1][bottom]overlay=x=0:y=1285[tmp2];
|
|
[tmp2][main]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]
|
|
""",
|
|
|
|
ShortStyle.Spectrum => """
|
|
[0:v]split=2[bg_src][fg_src];
|
|
[bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=30,eq=brightness=-0.62:saturation=0.40,setsar=1,
|
|
drawbox=x=0:y=0:w=1080:h=1920:color=0x040717@0.68:t=fill[bg];
|
|
[fg_src]scale=1000:780:force_original_aspect_ratio=decrease,pad=iw+12:ih+12:6:6:color=0x55c8ff,setsar=1[fg];
|
|
[0:a]aformat=sample_fmts=fltp:channel_layouts=stereo,showspectrum=s=1000x250:mode=combined:color=rainbow:scale=sqrt:fscale=log:slide=scroll:win_func=hann:fps=50:opacity=0.85,format=rgba[spec];
|
|
[bg][spec]overlay=x=(W-w)/2:y=1360:format=auto[tmp];
|
|
[tmp][fg]overlay=x=(W-w)/2:y=480:format=auto[base]
|
|
""",
|
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(style), style, "Unknown short style."),
|
|
};
|
|
}
|