feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization

This commit is contained in:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,81 @@
using System;
using System.Text;
namespace Paca.Core;
/// <summary>
/// Configuration for tiled thumbnail sprite sheet generation.
/// </summary>
public sealed record SpriteSheetConfig(
int Columns,
int Rows,
int ThumbWidth,
int ThumbHeight,
int IntervalSec)
{
public int TotalTilesPerSheet => Columns * Rows;
public int SheetWidth => Columns * ThumbWidth;
public int SheetHeight => Rows * ThumbHeight;
}
/// <summary>
/// Generates ffmpeg tile filter pipelines and WebVTT sprite coordinate mappings for scrubbing preview.
/// </summary>
public static class VideoSpriteSheetGenerator
{
/// <summary>
/// Builds ffmpeg CLI command line for rendering tiled thumbnail sprite sheet JPEG.
/// </summary>
public static string BuildFfmpegSpriteCommandLine(
string inputVideoPath,
string outputJpgPath,
SpriteSheetConfig config)
{
if (config.IntervalSec <= 0) throw new ArgumentOutOfRangeException(nameof(config.IntervalSec), "Interval must be positive.");
if (config.Columns <= 0 || config.Rows <= 0) throw new ArgumentOutOfRangeException(nameof(config), "Columns and Rows must be positive.");
return $"-i \"{inputVideoPath}\" -vf \"fps=1/{config.IntervalSec},scale={config.ThumbWidth}:{config.ThumbHeight},tile={config.Columns}x{config.Rows}\" -frames:v 1 \"{outputJpgPath}\" -y";
}
/// <summary>
/// Generates standard WebVTT subtitle/scrubber specification with #xywh coordinates.
/// </summary>
public static string GenerateVttMapping(
string spriteFileName,
double totalDurationSec,
SpriteSheetConfig config)
{
if (config.IntervalSec <= 0) throw new ArgumentOutOfRangeException(nameof(config.IntervalSec), "Interval must be positive.");
if (config.Columns <= 0 || config.Rows <= 0) throw new ArgumentOutOfRangeException(nameof(config), "Columns and Rows must be positive.");
var sb = new StringBuilder();
sb.AppendLine("WEBVTT");
sb.AppendLine();
var tileIndex = 0;
for (double start = 0; start < totalDurationSec; start += config.IntervalSec)
{
var end = Math.Min(start + config.IntervalSec, totalDurationSec);
var startTimeStr = FormatVttTime(start);
var endTimeStr = FormatVttTime(end);
var col = tileIndex % config.Columns;
var row = (tileIndex / config.Columns) % config.Rows;
var x = col * config.ThumbWidth;
var y = row * config.ThumbHeight;
sb.AppendLine($"{startTimeStr} --> {endTimeStr}");
sb.AppendLine($"{spriteFileName}#xywh={x},{y},{config.ThumbWidth},{config.ThumbHeight}");
sb.AppendLine();
tileIndex++;
}
return sb.ToString();
}
private static string FormatVttTime(double seconds)
{
var ts = TimeSpan.FromSeconds(seconds);
return $"{(int)ts.TotalHours:D2}:{ts.Minutes:D2}:{ts.Seconds:D2}.{ts.Milliseconds:D3}";
}
}