Automatic crop size calculation. GravitateTo is in sync. Defaults appied at start.

This commit is contained in:
Alexander Shabarshov 2026-05-25 09:11:18 +01:00
parent c6ca4fcbb6
commit 2dc7b050c8
36 changed files with 82 additions and 100 deletions

View File

@ -7,12 +7,15 @@ public sealed class ActionToIconConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value switch
{
"crop" => "\uf125", // FA7 crop
"rotate" => "\uf2f1", // FA7 rotate
_ => null
};
if (value == null)
return null;
var p = System.Convert.ToInt32(value);
return p == 0
? "\uf125" // FA7 crop
: "\uf2f1" // FA7 rotate
;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)

View File

@ -1,6 +1,4 @@
using Avalonia;
namespace Splitter_UI.Models;
namespace Splitter_UI.Models;
public class PreviewData
{

View File

@ -1,8 +1,6 @@
using Avalonia;
using Avalonia.Media;
using Microsoft.Extensions.DependencyInjection;
using splitter.algo;
using splitter.tui;
namespace Splitter_UI;

View File

@ -1,6 +1,4 @@
using NcnnDotNet.Layers;
using OpenCvSharp;
using splitter.tui;
using OpenCvSharp.Dnn;
namespace Splitter_UI.Services;
@ -15,19 +13,63 @@ public sealed class AutoDecisionService(IThumbnailService _thumbnails, IFileProb
{
try
{
job.GravitateTo = new(0.5f, 0.5f);
job.OverrideTargetDuration = 58.0;
job.Mask = "[NAME]_seg[NN].[EXT]";
job.OutputFolder = Path.Combine(Path.GetDirectoryName(job.InputFile)!, "splitter");
job.Probe = await _fileProbe.ProbeAsync(job.InputFile);
job.Thumbnail = await _thumbnails.CreateThumbnailAsync(job.InputFile, job.Probe, rotateDegree: job.Rotate);
var sampler = new VideoRotationSampler(null);
job.Rotate = await sampler.DetectRotationAsync(job.InputFile, job.Probe.Duration);
job.SuggestedAction = job.Rotate == 0 ? "crop" : "rotate";
if (job.SuggestedAction == "crop")
if (job.Probe.Width > job.Probe.Height)
{
job.Detect = "body";
job.Rotate = 0;
CalculateCrop(job);
}
else
{
var sampler = new VideoRotationSampler(null);
job.Rotate = await sampler.DetectRotationAsync(job.InputFile, job.Probe.Duration);
job.Detect = job.Rotate == 0 ? null : "body";
}
}
catch (Exception ex)
{
_log.LogError($"Error creating thumbnail for {Path.GetFileName(job.InputFile)}: {ex.Message}");
}
}
private static void CalculateCrop(JobViewModel job)
{
var targetAR = (float)CommandLine.DefaultW / CommandLine.DefaultH;
var pixelAspect = job.Probe!.Sar.X / job.Probe.Sar.Y;
float srcW = job.Probe.Width * pixelAspect;
float srcH = job.Probe.Height;
var srcAR = srcW / srcH;
float cropH = srcH;
float cropW = cropH * targetAR;
if (cropW > srcW)
{
cropW = srcW;
cropH = cropW / targetAR;
}
float x = (srcW - cropW) * 0.5f;
float y = (srcH - cropH) * 0.5f;
float invPixelAspect = 1f / pixelAspect;
float cropW_px = cropW * invPixelAspect;
float cropH_px = cropH;
float x_px = x * invPixelAspect;
float y_px = y;
job.CropText = $"{(int)MathF.Round(cropW_px)},{(int)MathF.Round(cropH_px)}";
}
}

View File

@ -1,7 +1,6 @@
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Media.Imaging;
using OpenCvSharp;
namespace Splitter_UI.Services;

View File

@ -1,7 +1,4 @@
using OpenCvSharp;
using splitter.algo;
namespace Splitter_UI.Services;
namespace Splitter_UI.Services;
internal class DummyDetector : IObjectDetector
{

View File

@ -1,6 +1,4 @@
using splitter.probe;
namespace Splitter_UI.Services;
namespace Splitter_UI.Services;
public sealed class FileProbeService : IFileProbeService
{

View File

@ -1,6 +1,4 @@
using splitter.tui;
namespace Splitter_UI.Services;
namespace Splitter_UI.Services;
internal class GlobalLogger(ILogService _logService) : ILogger
{

View File

@ -1,6 +1,4 @@
using splitter.probe;
namespace Splitter_UI.Services;
namespace Splitter_UI.Services;
public interface IFileProbeService
{

View File

@ -1,5 +1,4 @@
using Avalonia.Media.Imaging;
using splitter.probe;
namespace Splitter_UI.Services;

View File

@ -1,7 +1,4 @@
using OpenCvSharp;
using splitter.algo;
namespace Splitter_UI.Services;
namespace Splitter_UI.Services;
public class SingleThreadedDetector<T>(IObjectDetector _detector) : IObjectDetector
where T : IObjectDetector

View File

@ -2,7 +2,6 @@
using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using splitter.probe;
namespace Splitter_UI.Services;

View File

@ -16,11 +16,6 @@ public partial class InspectorPaneViewModel : ObservableObject
"face", "body", "none"
];
public List<int> RotationAngles =>
[
0, 90, 180, 270
];
[RelayCommand]
private void ApplyOverrides()
{

View File

@ -5,9 +5,6 @@ using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using splitter.algo;
using splitter.probe;
using splitter.tui;
namespace Splitter_UI.ViewModels;
@ -19,7 +16,6 @@ public partial class JobViewModel : ObservableObject
[ObservableProperty] private PreviewData? _preview = new(null, [], null, new(0.5f, 0.5f));
[ObservableProperty] private ProgressInfo? _progress;
[ObservableProperty] private Bitmap? _thumbnail;
[ObservableProperty] private string _suggestedAction = "";
[ObservableProperty] private double _sliderLiveValue;
[ObservableProperty] private double _positionSeconds;
@ -217,6 +213,13 @@ public partial class JobViewModel : ObservableObject
entry.PropertyChanged += OnParameterChanged;
}
PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(Probe))
{
OnPropertyChanged(nameof(DurationSeconds));
}
};
ParametersList.CollectionChanged += OnParametersCollectionChanged;
StepForwardCommand = new RelayCommand(StepForward);

View File

@ -1,6 +1,5 @@
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using splitter.algo;
namespace Splitter_UI.ViewModels;

View File

@ -82,7 +82,7 @@
VerticalAlignment="Center"/>
<TextBlock FontFamily="{StaticResource FontAwesome}"
Text="{Binding SuggestedAction, Converter={StaticResource ActionToIconConverter}}"
Text="{Binding Rotate, Converter={StaticResource ActionToIconConverter}}"
FontSize="12"
HorizontalAlignment="Right"
Foreground="LimeGreen"/>

View File

@ -1,5 +1,3 @@
using Avalonia.Controls;
namespace Splitter_UI.Views;
public partial class MainWindow : Avalonia.Controls.Window

View File

@ -4,7 +4,6 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Threading;
using splitter.algo;
namespace Splitter_UI.Views;

View File

@ -14,7 +14,7 @@
Preview="{Binding Preview}"
Sar="{Binding Sar}"
RotateAngle="{Binding Rotate}"
GravitateTo="{Binding GravitateTo}"/>
GravitateTo="{Binding GravitateTo, Mode=TwoWay}"/>
<Grid Grid.Row="1"
ColumnDefinitions="Auto,*,Auto"

View File

@ -1,5 +1,4 @@
using System.Globalization;
using splitter.algo;
using splitter.util;
namespace splitter;

View File

@ -1,7 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using splitter.algo;
using splitter.tui;
namespace splitter;

View File

@ -1,5 +1,4 @@
using System.Globalization;
using splitter.algo;
namespace splitter;

View File

@ -1,9 +1,6 @@
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using OpenCvSharp;
using splitter.algo;
using splitter.tui;
namespace splitter;

View File

@ -1,6 +1,4 @@
using OpenCvSharp;
namespace splitter.algo;
namespace splitter.algo;
public enum TrackState
{

View File

@ -1,6 +1,4 @@
using OpenCvSharp;
namespace splitter.algo;
namespace splitter.algo;
public interface IObjectDetector : IDisposable
{

View File

@ -1,6 +1,4 @@
using System.Runtime.InteropServices;
using OpenCvSharp;
using splitter.tui;
using UltraFaceDotNet;
namespace splitter.algo;

View File

@ -1,8 +1,6 @@
using System.Runtime.CompilerServices;
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using OpenCvSharp;
using splitter.tui;
namespace splitter.algo;

View File

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace splitter.probe;

View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using static splitter.probe.ProbeVideo;
namespace splitter.probe;
namespace splitter.probe;
public sealed class FfprobeResult
{

View File

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace splitter.probe;

View File

@ -1,6 +1,4 @@
using OpenCvSharp;
namespace splitter.probe;
namespace splitter.probe;
public sealed class FrameRotationDetector
{

View File

@ -2,7 +2,6 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using splitter.algo;
namespace splitter.probe;

View File

@ -1,7 +1,4 @@
using OpenCvSharp;
using splitter.algo;
namespace splitter.probe;
namespace splitter.probe;
public record VideoInfo(
double Duration,

View File

@ -1,5 +1,4 @@
using OpenCvSharp;
using System.Diagnostics;
using System.Diagnostics;
namespace splitter.probe;

View File

@ -2,9 +2,6 @@ using System.Collections.Concurrent;
using System.Diagnostics;
using Spectre.Console;
using splitter;
using splitter.algo;
using splitter.probe;
using splitter.tui;
static partial class Program
{

View File

@ -1,6 +1,4 @@
using splitter;
using splitter.algo;
using splitter.probe;
public record SingleTask(
SingleJob Job,