Improve crop editor and copy publish output to dist

Add resize handles, zoom, view brightness, and a multi-frame still
strip. Keep the crop box on mouse-up, and copy AmiReel.exe to dist.
This commit is contained in:
2026-08-15 22:59:20 +02:00
parent 323324d713
commit e635b708a2
10 changed files with 801 additions and 96 deletions
+56
View File
@@ -160,6 +160,62 @@ public sealed class ShortsPipeline
await _runner.RunAsync(tools.Ffmpeg, BuildExtractFrameArguments(inputFile, start, outputPath), log, null, token);
}
/// <summary>Writes several raw source frames across the clip so the crop editor can switch stills.</summary>
public async Task<IReadOnlyList<string>> ExtractSourceFramesAsync(
string inputFile,
string start,
IReadOnlyList<double> offsetsSeconds,
string ffmpegPath,
string ffprobePath,
string outputDirectory,
Action<string> log,
CancellationToken token)
{
if (string.IsNullOrWhiteSpace(inputFile) || !File.Exists(inputFile))
throw new FileNotFoundException("Select a valid source video for the Short.");
if (offsetsSeconds.Count == 0)
throw new ArgumentException("At least one preview offset is required.");
Directory.CreateDirectory(outputDirectory);
ToolPaths tools = await ToolExtractor.ResolveAsync(ffmpegPath, ffprobePath, token);
List<string> paths = [];
for (int index = 0; index < offsetsSeconds.Count; index++)
{
string seek = AddTimestampOffset(start, offsetsSeconds[index]);
string path = Path.Combine(outputDirectory, $"frame-{index:00}.png");
log($"Extracting crop preview {index + 1} of {offsetsSeconds.Count} ({seek})...");
await _runner.RunAsync(tools.Ffmpeg, BuildExtractFrameArguments(inputFile, seek, path), log, null, token);
paths.Add(path);
}
return paths;
}
internal const int CropPreviewFrameCount = 6;
internal static IReadOnlyList<double> CropPreviewOffsets(double durationSeconds, int count = CropPreviewFrameCount)
{
if (count <= 1 || durationSeconds <= 0)
return [0];
double last = Math.Max(0, durationSeconds - 0.15);
if (last <= 0)
return [0];
double[] offsets = new double[count];
for (int index = 0; index < count; index++)
offsets[index] = Math.Round(last * index / (count - 1), 3, MidpointRounding.AwayFromZero);
return offsets;
}
internal static string FormatPreviewOffsetLabel(double seconds)
{
TimeSpan value = TimeSpan.FromSeconds(Math.Max(0, seconds));
return value.TotalHours >= 1
? value.ToString(@"h\:mm\:ss", CultureInfo.InvariantCulture)
: value.ToString(@"m\:ss", CultureInfo.InvariantCulture);
}
internal static List<string> BuildExtractFrameArguments(string inputFile, string start, string outputPath) =>
[
"-hide_banner",