57 lines
2.4 KiB
C#
57 lines
2.4 KiB
C#
using System.Text.RegularExpressions;
|
|
using VideoDownloader.Core.Config;
|
|
using VideoDownloader.Core.Platform;
|
|
|
|
namespace VideoDownloader.Server;
|
|
|
|
/// <summary>
|
|
/// M2 라이브러리 메타데이터 — ffmpeg 기반 썸네일(.thumb.jpg)·해상도(.meta.json) 사이드카 캐시.
|
|
/// IProcessRunner 주입(테스트는 Fake, 호스트는 DesktopProcessRunner).
|
|
/// </summary>
|
|
public sealed partial class Thumbnailer
|
|
{
|
|
private readonly IProcessRunner _runner;
|
|
public Thumbnailer(IProcessRunner runner) => _runner = runner;
|
|
|
|
/// <summary>1초 지점 프레임 썸네일(320px 폭 JPEG). 실패 시 null. 캐시 히트 시 프로세스 없음.</summary>
|
|
public async Task<byte[]?> GetThumbnailAsync(string ffmpeg, string mediaPath, CancellationToken ct = default)
|
|
{
|
|
var cache = mediaPath + ".thumb.jpg";
|
|
try
|
|
{
|
|
if (File.Exists(cache)) return await File.ReadAllBytesAsync(cache, ct);
|
|
var res = await _runner.RunAsync(new ProcessSpec(ffmpeg, new[]
|
|
{
|
|
"-y", "-hide_banner", "-loglevel", "error", "-ss", "1", "-i", mediaPath,
|
|
"-frames:v", "1", "-vf", "scale=320:-2", cache,
|
|
}), null, ct);
|
|
if (res.ExitCode != 0 || !File.Exists(cache)) return null;
|
|
return await File.ReadAllBytesAsync(cache, ct);
|
|
}
|
|
catch { return null; }
|
|
}
|
|
|
|
/// <summary>해상도("1920x1080"). ffmpeg -i stderr 의 Stream 라인에서 추출, .meta.json 캐시.</summary>
|
|
public async Task<string?> GetResolutionAsync(string ffmpeg, string mediaPath, CancellationToken ct = default)
|
|
{
|
|
var cache = mediaPath + ".meta.json";
|
|
try
|
|
{
|
|
if (File.Exists(cache))
|
|
{
|
|
var cached = await File.ReadAllTextAsync(cache, ct);
|
|
return cached.Length >= 3 ? cached.Trim('"') : null;
|
|
}
|
|
var res = await _runner.RunAsync(new ProcessSpec(ffmpeg, new[]
|
|
{ "-hide_banner", "-i", mediaPath }), null, ct);
|
|
var m = ResolutionRegex().Match(res.Stderr);
|
|
var value = m.Success ? m.Groups[1].Value : null;
|
|
await File.WriteAllTextAsync(cache, value == null ? "-" : $"\"{value}\"", ct);
|
|
return value;
|
|
}
|
|
catch { return null; }
|
|
}
|
|
|
|
[GeneratedRegex(@"Video:.*?,\s(\d{2,5}x\d{2,5})")]
|
|
private static partial Regex ResolutionRegex();
|
|
}
|