From e18d043b78aae5d54649648545203c38516955aa Mon Sep 17 00:00:00 2001 From: unclshura Date: Fri, 22 May 2026 08:58:09 +0100 Subject: [PATCH] UI for SingleJob added. --- Splitter-UI/App.axaml | 1 + Splitter-UI/Models/ParameterEntry.cs | 15 ++ Splitter-UI/Services/FileJobFactory.cs | 4 +- Splitter-UI/Services/IFileJobFactory.cs | 2 +- Splitter-UI/Splitter-UI.csproj | 1 + Splitter-UI/ViewModels/FileJobViewModel.cs | 42 ---- Splitter-UI/ViewModels/FileListViewModel.cs | 10 +- .../ViewModels/InspectorPaneViewModel.cs | 29 ++- Splitter-UI/ViewModels/JobViewModel.cs | 156 +++++++++++++++ .../ViewModels/PreviewPaneViewModel.cs | 2 +- Splitter-UI/Views/FileListView.axaml | 2 +- Splitter-UI/Views/InspectorPane.axaml | 182 ++++++++++++++---- splitter-cli/CommandLine.cs | 72 +++++++ splitter-cli/splitter.csproj | 1 + 14 files changed, 432 insertions(+), 87 deletions(-) create mode 100644 Splitter-UI/Models/ParameterEntry.cs delete mode 100644 Splitter-UI/ViewModels/FileJobViewModel.cs create mode 100644 Splitter-UI/ViewModels/JobViewModel.cs diff --git a/Splitter-UI/App.axaml b/Splitter-UI/App.axaml index 11ae681..78cddfc 100644 --- a/Splitter-UI/App.axaml +++ b/Splitter-UI/App.axaml @@ -17,6 +17,7 @@ + \ No newline at end of file diff --git a/Splitter-UI/Models/ParameterEntry.cs b/Splitter-UI/Models/ParameterEntry.cs new file mode 100644 index 0000000..d8a7f4e --- /dev/null +++ b/Splitter-UI/Models/ParameterEntry.cs @@ -0,0 +1,15 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Splitter_UI.Models; + +public partial class ParameterEntry : ObservableObject +{ + public string Key { get; } + [ObservableProperty] private string _value; + + public ParameterEntry(string key, string value) + { + Key = key; + Value = value; + } +} diff --git a/Splitter-UI/Services/FileJobFactory.cs b/Splitter-UI/Services/FileJobFactory.cs index 921a07c..421fc3d 100644 --- a/Splitter-UI/Services/FileJobFactory.cs +++ b/Splitter-UI/Services/FileJobFactory.cs @@ -9,9 +9,9 @@ public sealed class FileJobFactory : IFileJobFactory _services = services; } - public FileJobViewModel Create(SingleJob job) + public JobViewModel Create(SingleJob job) { // Resolve a fresh VM + fresh services - return ActivatorUtilities.CreateInstance(_services, job); + return ActivatorUtilities.CreateInstance(_services, job); } } diff --git a/Splitter-UI/Services/IFileJobFactory.cs b/Splitter-UI/Services/IFileJobFactory.cs index 883cdb3..58c4a0b 100644 --- a/Splitter-UI/Services/IFileJobFactory.cs +++ b/Splitter-UI/Services/IFileJobFactory.cs @@ -6,5 +6,5 @@ namespace Splitter_UI.Services; public interface IFileJobFactory { - FileJobViewModel Create(SingleJob job); + JobViewModel Create(SingleJob job); } diff --git a/Splitter-UI/Splitter-UI.csproj b/Splitter-UI/Splitter-UI.csproj index 60aaf87..56135ad 100644 --- a/Splitter-UI/Splitter-UI.csproj +++ b/Splitter-UI/Splitter-UI.csproj @@ -18,6 +18,7 @@ + diff --git a/Splitter-UI/ViewModels/FileJobViewModel.cs b/Splitter-UI/ViewModels/FileJobViewModel.cs deleted file mode 100644 index 7b23c7c..0000000 --- a/Splitter-UI/ViewModels/FileJobViewModel.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Avalonia.Media.Imaging; -using CommunityToolkit.Mvvm.ComponentModel; - -namespace Splitter_UI.ViewModels; - -public partial class FileJobViewModel : ObservableObject -{ - public SingleJob Job { get; } - public VideoInfo? Probe { get; set; } - public PreviewData? Preview { get; set; } - public ProgressInfo? Progress { get; set; } - - [ObservableProperty] - private Bitmap? _thumbnail; - - public string FileName { get; set; } - - - [ObservableProperty] - private string _suggestedAction = ""; - - private readonly IThumbnailService _thumbnails; - private readonly IFileProbeService _fileProbe; - - public FileJobViewModel(SingleJob job, IThumbnailService thumbnails, IFileProbeService fileProbe) - { - Job = job; - _thumbnails = thumbnails; - _fileProbe = fileProbe; - - FileName = Path.GetFileName(job.InputFile); - - _ = Task.Run( LoadThumbnailAsync ); - } - - private async Task LoadThumbnailAsync() - { - Probe = await _fileProbe.ProbeAsync(Job); - Thumbnail = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe); - SuggestedAction = Probe.Rotation == 0 ? "crop" : "rotate"; - } -} diff --git a/Splitter-UI/ViewModels/FileListViewModel.cs b/Splitter-UI/ViewModels/FileListViewModel.cs index c27b3a5..5256e98 100644 --- a/Splitter-UI/ViewModels/FileListViewModel.cs +++ b/Splitter-UI/ViewModels/FileListViewModel.cs @@ -7,20 +7,20 @@ namespace Splitter_UI.ViewModels; public partial class FileListViewModel : ObservableObject { private readonly IFileJobFactory _factory; - public ObservableCollection Files { get; } = []; - public ObservableCollection SelectedFiles { get; } = []; + public ObservableCollection Files { get; } = []; + public ObservableCollection SelectedFiles { get; } = []; [ObservableProperty] - private FileJobViewModel? _selected; + private JobViewModel? _selected; - public event Action? SelectedFileChanged; + public event Action? SelectedFileChanged; public FileListViewModel(IFileJobFactory factory) { _factory = factory; } - partial void OnSelectedChanged(FileJobViewModel? value) + partial void OnSelectedChanged(JobViewModel? value) => SelectedFileChanged?.Invoke(value); [RelayCommand] diff --git a/Splitter-UI/ViewModels/InspectorPaneViewModel.cs b/Splitter-UI/ViewModels/InspectorPaneViewModel.cs index f8270cf..22506a5 100644 --- a/Splitter-UI/ViewModels/InspectorPaneViewModel.cs +++ b/Splitter-UI/ViewModels/InspectorPaneViewModel.cs @@ -8,13 +8,18 @@ namespace Splitter_UI.ViewModels; public partial class InspectorPaneViewModel : ObservableObject { [ObservableProperty] - private FileJobViewModel? _selected; + private JobViewModel? _selected; public List DetectModes => [ "face", "body", "none" ]; + public List RotationAngles => + [ + 0, 90, 180, 270 + ]; + [RelayCommand] private void ApplyOverrides() { @@ -22,4 +27,26 @@ public partial class InspectorPaneViewModel : ObservableObject return; } + + public IRelayCommand RotateLeftCommand { get; } + public IRelayCommand RotateRightCommand { get; } + + public InspectorPaneViewModel() + { + RotateLeftCommand = new RelayCommand(() => AdjustRotation(-90)); + RotateRightCommand = new RelayCommand(() => AdjustRotation(+90)); + } + + private void AdjustRotation(int delta) + { + if (Selected?.Job == null) + return; + + var r = Selected.Job.Rotate ?? 0; + r = (r + delta) % 360; + if (r < 0) r += 360; + + Selected.Rotate = r; + } + } diff --git a/Splitter-UI/ViewModels/JobViewModel.cs b/Splitter-UI/ViewModels/JobViewModel.cs new file mode 100644 index 0000000..5b92c3f --- /dev/null +++ b/Splitter-UI/ViewModels/JobViewModel.cs @@ -0,0 +1,156 @@ +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using Avalonia.Media.Imaging; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Splitter_UI.ViewModels; + +public partial class JobViewModel : ObservableObject +{ + public SingleJob Job { get; } + public VideoInfo? Probe { get; set; } + public PreviewData? Preview { get; set; } + public ProgressInfo? Progress { get; set; } + + [ObservableProperty] + private Bitmap? _thumbnail; + + [ObservableProperty] + private string _suggestedAction = ""; + + private readonly IThumbnailService _thumbnails; + private readonly IFileProbeService _fileProbe; + + public string FileName => Path.GetFileName(Job.InputFile); + + public ObservableCollection ParametersList { get; } + = new(); + + public string CropText + { + get => Job.Crop is { } c ? $"{c.width},{c.height}" : ""; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + Job.Crop = null; + } + else + { + var parts = value.Split(','); + if (parts.Length == 2 && + int.TryParse(parts[0], out var w) && + int.TryParse(parts[1], out var h)) + Job.Crop = (w, h); + } + OnPropertyChanged(); + } + } + + public string GravitateText + { + get => Job.GravitateTo is { } p ? $"{p.X:F3},{p.Y:F3}" : ""; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + Job.GravitateTo = null; + } + else + { + var parts = value.Split(','); + if (parts.Length == 2 && + float.TryParse(parts[0], out var x) && + float.TryParse(parts[1], out var y)) + Job.GravitateTo = new Point2f(x, y); + } + OnPropertyChanged(); + } + } + + public string PassthroughText + { + get => string.Join(' ', Job.Passthrough); + set + { + Job.Passthrough = string.IsNullOrWhiteSpace(value) + ? Array.Empty() + : value.Split(' ', StringSplitOptions.RemoveEmptyEntries); + OnPropertyChanged(); + } + } + + public int? Rotate + { + get => Job.Rotate; + set + { + Job.Rotate = value; + OnPropertyChanged(); + } + } + + public JobViewModel(SingleJob job, IThumbnailService thumbnails, IFileProbeService fileProbe) + { + Job = job; + _thumbnails = thumbnails; + _fileProbe = fileProbe; + + ParametersList.Add(new ParameterEntry("DropoutToleranceFrames", "")); + ParametersList.Add(new ParameterEntry("EmaFactor", "")); + ParametersList.Add(new ParameterEntry("CameraEasing", "")); + ParametersList.Add(new ParameterEntry("LostFreezeFrames", "")); + ParametersList.Add(new ParameterEntry("RotationDetectorSampleCount", "")); + ParametersList.Add(new ParameterEntry("RotationDetectorSampleLength", "")); + ParametersList.Add(new ParameterEntry("RotationDetectorFrameWidth", "")); + ParametersList.Add(new ParameterEntry("RotationDetectorFrameHeight", "")); + + foreach (var entry in ParametersList) + { + entry.PropertyChanged += OnParameterChanged; + } + + ParametersList.CollectionChanged += OnParametersCollectionChanged; + + + _ = Task.Run( LoadThumbnailAsync ); + } + + private async Task LoadThumbnailAsync() + { + Probe = await _fileProbe.ProbeAsync(Job); + Thumbnail = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe); + SuggestedAction = Probe.Rotation == 0 ? "crop" : "rotate"; + } + + private void OnParameterChanged(object? sender, PropertyChangedEventArgs e) + { + if (sender is ParameterEntry p && e.PropertyName == nameof(ParameterEntry.Value)) + { + Job.Parameters[p.Key] = p.Value; + } + } + + private void OnParametersCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (ParameterEntry p in e.NewItems) + { + Job.Parameters[p.Key] = p.Value; + p.PropertyChanged += OnParameterChanged; + } + } + + if (e.OldItems != null) + { + foreach (ParameterEntry p in e.OldItems) + { + Job.Parameters.Remove(p.Key); + p.PropertyChanged -= OnParameterChanged; + } + } + } + +} diff --git a/Splitter-UI/ViewModels/PreviewPaneViewModel.cs b/Splitter-UI/ViewModels/PreviewPaneViewModel.cs index 464cc5b..e29d1b4 100644 --- a/Splitter-UI/ViewModels/PreviewPaneViewModel.cs +++ b/Splitter-UI/ViewModels/PreviewPaneViewModel.cs @@ -5,7 +5,7 @@ namespace Splitter_UI.ViewModels; public partial class PreviewPaneViewModel : ObservableObject { [ObservableProperty] - private FileJobViewModel? _selected; + private JobViewModel? _selected; public PreviewPaneViewModel() { diff --git a/Splitter-UI/Views/FileListView.axaml b/Splitter-UI/Views/FileListView.axaml index bcd7b89..a43ed8a 100644 --- a/Splitter-UI/Views/FileListView.axaml +++ b/Splitter-UI/Views/FileListView.axaml @@ -51,7 +51,7 @@ - + +xmlns="https://github.com/avaloniaui" +xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" +x:Class="Splitter_UI.Views.InspectorPane" +xmlns:vm="clr-namespace:Splitter_UI.ViewModels" +x:DataType="vm:InspectorPaneViewModel"> - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +