using System.Globalization; using System.IO; using System.Text; using System.Text.RegularExpressions; using AmiReel.Models; namespace AmiReel.Services; /// /// 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-fx-v4.sh so end users /// don't need bash/WSL — the FFmpeg filtergraphs below are copied from that script. /// public sealed class ShortsPipeline { internal const string DefaultSourceCropFilter = "crop='min(iw,ih*4/3)':'min(ih,iw*3/4)':'(iw-ow)/2':'(ih-oh)/2'"; private static readonly Regex CropSpecRegex = new( @"crop=(\d+):(\d+):(\d+):(\d+)", RegexOptions.CultureInvariant); private readonly ProcessRunner _runner = new(); public async Task RenderAsync(ShortSettings settings, IProgress progress, Action 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); double fadeIn = Math.Clamp(settings.SourceFadeInSeconds, 0, duration); string baseFilter = await ResolveBaseFilterAsync(tools, settings, log, token); string filterComplex = BuildFilterComplex( ApplySourceFade(baseFilter, fadeIn), fontEsc: EscapeFfmpegPath(settings.FontFile), hookEsc: EscapeFfmpegPath(hookFile), titleEsc: EscapeFfmpegPath(titleFile), metaEsc: EscapeFfmpegPath(metaFile), websiteEsc: EscapeFfmpegPath(websiteFile), hookEnd, infoStart, ctaStart); List 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]); if (settings.Style == ShortStyle.Video) args.AddRange(["-stream_loop", "-1", "-i", settings.BackgroundVideo]); 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", "-t", F(duration), "-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 { } } } /// Looks for a common bold system font to pre-fill the Shorts font field with. 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."); if (s.Style == ShortStyle.Video && (string.IsNullOrWhiteSpace(s.BackgroundVideo) || !File.Exists(s.BackgroundVideo))) throw new FileNotFoundException("The video style requires a background video."); if (s.VideoBlur < 0) throw new ArgumentException("Background video blur must be zero or greater."); if (s.VideoOpacity is < 0 or > 1) throw new ArgumentException("Background video opacity must be between 0.0 and 1.0."); if (s.CropLeft < 0 || s.CropRight < 0 || s.CropTop < 0 || s.CropBottom < 0) throw new ArgumentException("Manual crop values must be non-negative."); if (s.SourceMarginX < 0 || s.SourceMarginX >= 540) throw new ArgumentException("Source side margin must be between 0 and 539 pixels."); if (s.SourceFadeInSeconds < 0) throw new ArgumentException("Source fade-in must be zero or greater."); } internal static string BuildMeta(ShortSettings s) { List 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 ApplySourceFade(string baseFilter, double fadeInSeconds) { if (fadeInSeconds <= 0) return baseFilter; return $"[0:v]format=rgba,fade=t=in:st=0:d={F(fadeInSeconds)}:alpha=1[srcfade];" + baseFilter.Replace("[0:v]", "[srcfade]", StringComparison.Ordinal); } internal static int SourceTargetWidth(int sourceMarginX) => 1080 - (sourceMarginX * 2); internal static string BuildSourceScaleFilter(int sourceMarginX) => $"scale={SourceTargetWidth(sourceMarginX)}:-2:flags=lanczos"; internal static string BuildManualCropFilter(int left, int right, int top, int bottom) => $"crop=iw-{left}-{right}:ih-{top}-{bottom}:{left}:{top}"; internal static string BuildVideoBaseFilter(string cropFilter, string scaleFilter, double blur, double opacity) { string blurFilter = blur > 0 ? $",gblur=sigma={F(blur)}" : ""; return "color=c=black:s=1080x1920:r=50[black];" + $"[1:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1{blurFilter},format=rgba,colorchannelmixer=aa={F(opacity)}[bgvideo];" + "[black][bgvideo]overlay=x=0:y=0:format=auto[bg];" + $"[0:v]{cropFilter},{scaleFilter},setsar=1[fg];" + "[bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base]"; } internal static string? SelectMostCommonCropSpec(string ffmpegOutput) { Dictionary counts = new(StringComparer.Ordinal); foreach (Match match in CropSpecRegex.Matches(ffmpegOutput)) { string spec = match.Value; counts[spec] = counts.GetValueOrDefault(spec) + 1; } return counts.Count == 0 ? null : counts.OrderByDescending(pair => pair.Value).ThenBy(pair => pair.Key, StringComparer.Ordinal).First().Key; } internal static bool TryParseCropSpec(string spec, out int width, out int height, out int x, out int y) { Match match = CropSpecRegex.Match(spec); if (!match.Success) { width = height = x = y = 0; return false; } width = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); height = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); x = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture); y = int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture); return true; } internal static bool TryBuildAutoHorizontalCrop(int detectedWidth, int detectedX, out int width, out int x) { int inset = (int)(detectedWidth * 0.005 / 2) * 2; width = ((detectedWidth - (2 * inset)) / 2) * 2; x = ((detectedX + inset) / 2) * 2; if (width < 16) return false; return true; } private async Task ResolveBaseFilterAsync( ToolPaths tools, ShortSettings settings, Action log, CancellationToken token) { if (settings.Style != ShortStyle.Video) return GetBaseFilter(settings.Style); string cropFilter = DefaultSourceCropFilter; string scaleFilter = BuildSourceScaleFilter(settings.SourceMarginX); bool hasManualCrop = settings.CropLeft > 0 || settings.CropRight > 0 || settings.CropTop > 0 || settings.CropBottom > 0; if (hasManualCrop) { cropFilter = BuildManualCropFilter(settings.CropLeft, settings.CropRight, settings.CropTop, settings.CropBottom); log($"Source crop: manual L={settings.CropLeft} R={settings.CropRight} T={settings.CropTop} B={settings.CropBottom}"); } else if (settings.SourceCropMode == ShortSourceCropMode.Auto) { string? detected = await DetectHorizontalCropAsync(tools, settings, log, token); if (detected is not null) { cropFilter = detected; } else { log("Warning: could not auto-detect left/right source black borders; using centered 4:3 crop."); } } else { log("Source crop: centered 4:3"); } return BuildVideoBaseFilter(cropFilter, scaleFilter, settings.VideoBlur, settings.VideoOpacity); } private async Task DetectHorizontalCropAsync( ToolPaths tools, ShortSettings settings, Action log, CancellationToken token) { double detectDuration = Math.Clamp(settings.DurationSeconds < 4 ? settings.DurationSeconds : 4, 0.5, 4); List args = [ "-hide_banner", "-ss", settings.Start, "-t", F(detectDuration), "-i", settings.InputFile, "-an", "-vf", "crop=iw:trunc(ih*0.70/2)*2:0:trunc(ih*0.15/2)*2,cropdetect=limit=28:round=2:reset=0", "-f", "null", "-", ]; string output = await _runner.RunAsync(tools.Ffmpeg, args, log, null, token, allowFailure: true); string? spec = SelectMostCommonCropSpec(output); if (spec is null || !TryParseCropSpec(spec, out int width, out _, out int x, out _)) return null; if (!TryBuildAutoHorizontalCrop(width, x, out int cropWidth, out int cropX)) return null; log($"Source crop: w={cropWidth}, x={cropX} (auto left/right black crop)"); return $"crop={cropWidth}:ih:{cropX}:0"; } 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=4[canvas_src][fg_src][top_src][bottom_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=0x010203:t=fill[canvas]; [fg_src]scale=940:760:force_original_aspect_ratio=decrease,setsar=1[fg]; [top_src]scale=1400:470:flags=lanczos,vflip,eq=brightness=-0.48:contrast=0.92:saturation=0.68,gblur=sigma=7,setsar=1,format=rgba,geq=r='r(X,Y)':g='g(X,Y)':b='b(X,Y)':a='255*0.62*(Y/H)'[top]; [bottom_src]scale=1400:470:flags=lanczos,vflip,eq=brightness=-0.50:contrast=0.90:saturation=0.64,gblur=sigma=7,setsar=1,format=rgba,geq=r='r(X,Y)':g='g(X,Y)':b='b(X,Y)':a='255*0.58*(1-Y/H)'[bottom]; [canvas][top]overlay=x=(W-w)/2:y=250:format=auto[tmp1]; [tmp1][bottom]overlay=x=(W-w)/2:y=1435:format=auto[tmp2]; [tmp2][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] """, ShortStyle.Video => BuildVideoBaseFilter( DefaultSourceCropFilter, BuildSourceScaleFilter(40), blur: 0, opacity: 1), _ => throw new ArgumentOutOfRangeException(nameof(style), style, "Unknown short style."), }; }