mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-22 00:22:01 +00:00
121 lines
3.2 KiB
C#
121 lines
3.2 KiB
C#
using System.Diagnostics;
|
|
using Avalonia;
|
|
using Avalonia.Media.Imaging;
|
|
using Avalonia.Platform;
|
|
|
|
namespace Splitter_UI.Services;
|
|
|
|
public sealed class ThumbnailService : IThumbnailService
|
|
{
|
|
private readonly int _thumbWidth = 160;
|
|
private readonly int _thumbHeight = 90;
|
|
|
|
// Reusable buffer for BGR24 → 3 bytes per pixel
|
|
private readonly byte[] _bgrBuffer;
|
|
private readonly byte[] _bgraBuffer;
|
|
|
|
public ThumbnailService()
|
|
{
|
|
_bgrBuffer = new byte[_thumbWidth * _thumbHeight * 3];
|
|
_bgraBuffer = new byte[_thumbWidth * _thumbHeight * 4];
|
|
}
|
|
|
|
public async Task<Bitmap?> CreateThumbnailAsync(string file, VideoInfo probe)
|
|
{
|
|
// Decode a single frame using ffmpeg → raw BGR24 into _bgrBuffer
|
|
bool ok = await DecodeFrameAsync(file);
|
|
if (!ok)
|
|
return null;
|
|
|
|
// Convert BGR24 → BGRA32
|
|
ConvertBgrToBgra(_bgrBuffer, _bgraBuffer, _thumbWidth, _thumbHeight);
|
|
|
|
// Create Avalonia Bitmap
|
|
return CreateBitmap(_bgraBuffer, _thumbWidth, _thumbHeight);
|
|
}
|
|
|
|
private async Task<bool> DecodeFrameAsync(string file)
|
|
{
|
|
// ffmpeg command: decode one frame, resize, output raw BGR24
|
|
var args =
|
|
$"-ss 0 -t 0.1 -i \"{file}\" " +
|
|
"-an -sn " +
|
|
$"-vf \"scale={_thumbWidth}:{_thumbHeight}:force_original_aspect_ratio=decrease," +
|
|
$"pad={_thumbWidth}:{_thumbHeight}:(ow-iw)/2:(oh-ih)/2,format=bgr24\" " +
|
|
"-f rawvideo -";
|
|
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = "ffmpeg",
|
|
Arguments = args,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
var p = new Process { StartInfo = psi };
|
|
p.Start();
|
|
|
|
int needed = _bgrBuffer.Length;
|
|
int read = 0;
|
|
|
|
using var stdout = p.StandardOutput.BaseStream;
|
|
|
|
while (read < needed)
|
|
{
|
|
int r = await stdout.ReadAsync(_bgrBuffer, read, needed - read);
|
|
if (r == 0)
|
|
{
|
|
TryKill(p);
|
|
return false;
|
|
}
|
|
read += r;
|
|
}
|
|
|
|
TryKill(p);
|
|
return true;
|
|
}
|
|
|
|
private static void TryKill(Process p)
|
|
{
|
|
try { p.Kill(); } catch { }
|
|
}
|
|
|
|
private static void ConvertBgrToBgra(byte[] bgr, byte[] bgra, int width, int height)
|
|
{
|
|
int si = 0;
|
|
int di = 0;
|
|
|
|
int totalPixels = width * height;
|
|
|
|
for (int i = 0; i < totalPixels; i++)
|
|
{
|
|
bgra[di + 0] = bgr[si + 0]; // B
|
|
bgra[di + 1] = bgr[si + 1]; // G
|
|
bgra[di + 2] = bgr[si + 2]; // R
|
|
bgra[di + 3] = 255; // A
|
|
|
|
si += 3;
|
|
di += 4;
|
|
}
|
|
}
|
|
|
|
private static unsafe Bitmap CreateBitmap(byte[] bgra, int width, int height)
|
|
{
|
|
int stride = width * 4;
|
|
|
|
fixed (byte* p = bgra)
|
|
{
|
|
return new Bitmap(
|
|
PixelFormat.Bgra8888,
|
|
AlphaFormat.Premul,
|
|
(nint)p,
|
|
new PixelSize(width, height),
|
|
new Vector(96, 96),
|
|
stride);
|
|
}
|
|
}
|
|
|
|
}
|