Video processing implemented.

This commit is contained in:
Alexander Shabarshov 2026-05-25 12:34:36 +01:00
parent 9cdf611ec8
commit af363ebb9a
18 changed files with 268 additions and 45 deletions

View File

@ -8,7 +8,9 @@ namespace Splitter_UI;
public partial class App : Application public partial class App : Application
{ {
private readonly ServiceProvider _provider; private readonly ServiceProvider _provider = null!;
public App() { }
public App(ServiceProvider provider) public App(ServiceProvider provider)
{ {

View File

@ -0,0 +1,15 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace Splitter_UI.Converters;
public sealed class BoolInvertConverter : IValueConverter
{
public static readonly BoolInvertConverter Instance = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is bool b ? !b : value;
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is bool b ? !b : value;
}

View File

@ -1,6 +1,5 @@
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Media; using Avalonia.Media;
using System;
using System.Globalization; using System.Globalization;
namespace Splitter_UI.Converters; namespace Splitter_UI.Converters;

View File

@ -30,6 +30,7 @@ internal sealed class Program
services.AddTransient<PreviewPaneViewModel>(); services.AddTransient<PreviewPaneViewModel>();
services.AddTransient<InspectorPaneViewModel>(); services.AddTransient<InspectorPaneViewModel>();
services.AddSingleton<StatusBarViewModel>(); services.AddSingleton<StatusBarViewModel>();
services.AddSingleton<ProgressViewModel>();
services.AddSingleton<LogPaneViewModel>(logPaveVM); services.AddSingleton<LogPaneViewModel>(logPaveVM);
services.AddSingleton<ILogService>(logPaveVM); services.AddSingleton<ILogService>(logPaveVM);
@ -48,6 +49,7 @@ internal sealed class Program
}; };
}); });
services.AddSingleton<ILogger, GlobalLogger>(); services.AddSingleton<ILogger, GlobalLogger>();
services.AddSingleton<IJobProcessor, JobProcessor>();
// Domain services (your pipeline) // Domain services (your pipeline)
services.AddTransient<IFileProbeService, FileProbeService>(); services.AddTransient<IFileProbeService, FileProbeService>();

View File

@ -1,16 +1,20 @@
namespace Splitter_UI.Services; namespace Splitter_UI.Services;
internal class GlobalLogger(ILogService _logService, StatusBarViewModel _statusBar) : ILogger internal class GlobalLogger(ILogService _logService, StatusBarViewModel _statusBar, ProgressViewModel _progress) : ILogger
{ {
public void ClearProgress(string name, int progressLine) public void ClearProgress(string name, int progressLine)
{ {
if (progressLine == 0) if (progressLine == 0)
_statusBar.Percent = 0; _statusBar.Percent = 0;
else
_progress.ClearProgress(name, progressLine-1);
} }
public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed) public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed)
{ {
if (progressLine == 0) if (progressLine == 0)
_statusBar.Percent = progress; _statusBar.Percent = progress;
else
_progress.DrawProgress(name, progressLine - 1, progress, eta, speed);
} }
public void Log(string prefix, ConsoleColor color, string msg) public void Log(string prefix, ConsoleColor color, string msg)

View File

@ -39,4 +39,21 @@ public partial class FileListViewModel : ObservableObject
Selected = Files.LastOrDefault(); Selected = Files.LastOrDefault();
} }
internal void DeleteSelected()
{
if (SelectedFiles.Any())
{
var toDelete = SelectedFiles.ToList();
foreach (var item in toDelete)
Files.Remove(item);
}
else if ( Selected != null)
{
var sel = Selected;
Files.Remove(sel);
}
Selected = Files.LastOrDefault();
}
} }

View File

@ -16,6 +16,13 @@ public partial class InspectorPaneViewModel : ObservableObject
"face", "body", "none" "face", "body", "none"
]; ];
[RelayCommand]
private void TransformAll()
{
_ = _main.Start();
}
[RelayCommand] [RelayCommand]
private void ApplyOverrides() private void ApplyOverrides()
{ {
@ -43,12 +50,16 @@ public partial class InspectorPaneViewModel : ObservableObject
public IRelayCommand RotateLeftCommand { get; } public IRelayCommand RotateLeftCommand { get; }
public IRelayCommand RotateRightCommand { get; } public IRelayCommand RotateRightCommand { get; }
private MainViewModel _main = null!;
public InspectorPaneViewModel() public InspectorPaneViewModel()
{ {
RotateLeftCommand = new RelayCommand(() => AdjustRotation(-90)); RotateLeftCommand = new RelayCommand(() => AdjustRotation(-90));
RotateRightCommand = new RelayCommand(() => AdjustRotation(+90)); RotateRightCommand = new RelayCommand(() => AdjustRotation(+90));
} }
public void SetMain(MainViewModel main) => _main = main;
private void AdjustRotation(int delta) private void AdjustRotation(int delta)
{ {
if ( Selected == null) if ( Selected == null)

View File

@ -12,6 +12,8 @@ public partial class JobViewModel : ObservableObject
{ {
private SingleJob Job { get; } private SingleJob Job { get; }
public SingleJob GetJob() => Job;
[ObservableProperty] private VideoInfo? _probe; [ObservableProperty] private VideoInfo? _probe;
[ObservableProperty] private PreviewData? _preview = new(null, [], null, new(0.5f, 0.5f)); [ObservableProperty] private PreviewData? _preview = new(null, [], null, new(0.5f, 0.5f));
[ObservableProperty] private Bitmap? _thumbnail; [ObservableProperty] private Bitmap? _thumbnail;
@ -366,4 +368,5 @@ public partial class JobViewModel : ObservableObject
{ {
Task.Run(CreatePreview); Task.Run(CreatePreview);
} }
} }

View File

@ -1,4 +1,4 @@
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.ComponentModel;
namespace Splitter_UI.ViewModels; namespace Splitter_UI.ViewModels;
@ -9,41 +9,74 @@ public partial class MainViewModel : ViewModelBase
public InspectorPaneViewModel Inspector { get; } public InspectorPaneViewModel Inspector { get; }
public StatusBarViewModel StatusBar { get; } public StatusBarViewModel StatusBar { get; }
public LogPaneViewModel LogPane { get; } public LogPaneViewModel LogPane { get; }
public ProgressViewModel Progress { get; }
private IJobProcessor _processor = null!;
[ObservableProperty] private bool _transformMode = false;
private ILogger _logger;
public MainViewModel( public MainViewModel(
IFileJobFactory fileJobFactory, FileListViewModel fileListVM,
IAutoDecisionService autoDecisionService,
PreviewPaneViewModel ppVM, PreviewPaneViewModel ppVM,
InspectorPaneViewModel iVM, InspectorPaneViewModel iVM,
LogPaneViewModel lpVM, LogPaneViewModel lpVM,
StatusBarViewModel sbVM StatusBarViewModel sbVM,
ProgressViewModel pVM,
IJobProcessor processor,
ILogger logger
) )
{ {
FileList = new FileListViewModel(fileJobFactory, autoDecisionService); FileList = fileListVM;
Preview = ppVM; Preview = ppVM;
Inspector = iVM; Inspector = iVM;
LogPane = lpVM; LogPane = lpVM;
StatusBar = sbVM; StatusBar = sbVM;
// Wire selection → preview + inspector Progress = pVM;
_processor = processor;
_logger = logger;
// Wire selection -> preview + inspector
FileList.SelectedFileChanged += file => FileList.SelectedFileChanged += file =>
{ {
Preview.Selected = file; Preview.Selected = file;
Inspector.Selected = file; Inspector.Selected = file;
}; };
Inspector.SetMain(this);
Inspector.Files = FileList.Files; Inspector.Files = FileList.Files;
} }
[RelayCommand] public async Task Start()
private void Start() {
try
{ {
StatusBar.StatusText = "Processing…"; StatusBar.StatusText = "Processing…";
// call IProcessingService here StatusBar.Percent = 0;
TransformMode = true;
var files = FileList.Files.ToList();
var jobs = new List<SingleTask>();
foreach (var file in files)
{
var fileJobs = await _processor.GenerateJobs(file.GetJob(), false);
jobs.AddRange(fileJobs);
} }
[RelayCommand] await _processor.ProcessJobs(jobs, false);
private void Stop() }
catch (Exception ex)
{ {
StatusBar.StatusText = "Stopped"; // Handle exception
StatusBar.StatusText = "Error occurred…";
_logger.LogError($"Error: {ex.Message}");
}
finally
{
StatusBar.StatusText = "Ready…";
StatusBar.Percent = 0;
TransformMode = false;
} }
} }
}

View File

@ -0,0 +1,43 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace Splitter_UI.ViewModels;
public record ProgressInfo(string Name, int ProgressLine, double Progress, TimeSpan Eta, double Speed);
public partial class ProgressViewModel : ObservableObject
{
[ObservableProperty] private int _numberOfProcesses = 0;
public ObservableCollection<ProgressInfo> Processes { get; } = [];
private Lock _lock = new();
public void ClearProgress(string name, int progressLine)
{
lock (_lock)
{
if (progressLine < 0 || progressLine > Processes.Count)
return;
NumberOfProcesses -= 1;
Processes[progressLine] = new ProgressInfo("", progressLine, 0, TimeSpan.Zero, 0);
}
}
public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed)
{
lock (_lock)
{
if (progressLine < 0)
return;
while (Processes.Count <= progressLine)
{
Processes.Add(new ProgressInfo("", Processes.Count, 0, TimeSpan.Zero, 0));
}
if (Processes[progressLine].Name == "")
NumberOfProcesses += 1;
Processes[progressLine] = new ProgressInfo(name, progressLine, progress, eta, speed);
}
}
}

View File

@ -10,6 +10,4 @@ public partial class StatusBarViewModel : ObservableObject
[ObservableProperty] [ObservableProperty]
private double _percent; private double _percent;
[ObservableProperty]
private string _threadInfo = "Threads: 0/0";
} }

View File

@ -5,7 +5,9 @@
xmlns:views="clr-namespace:Splitter_UI.Views" xmlns:views="clr-namespace:Splitter_UI.Views"
xmlns:conv="clr-namespace:Splitter_UI.Converters" xmlns:conv="clr-namespace:Splitter_UI.Converters"
x:Class="Splitter_UI.Views.FileListView" x:Class="Splitter_UI.Views.FileListView"
x:DataType="vm:FileListViewModel"> x:DataType="vm:FileListViewModel"
KeyDown="OnKeyDown"
Focusable="True">
<UserControl.Resources> <UserControl.Resources>
<conv:ZeroToBoolConverter x:Key="ZeroToBoolConverter"/> <conv:ZeroToBoolConverter x:Key="ZeroToBoolConverter"/>

View File

@ -21,6 +21,14 @@ public partial class FileListView : UserControl
InitializeComponent(); InitializeComponent();
} }
private void OnKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
if (DataContext is FileListViewModel vm)
vm.DeleteSelected();
}
}
private void OnDragEnter(object? sender, DragEventArgs e) private void OnDragEnter(object? sender, DragEventArgs e)
{ {
IsDragActive = true; IsDragActive = true;

View File

@ -147,10 +147,21 @@ x:DataType="vm:InspectorPaneViewModel">
<TextBox Text="{Binding Selected.PassthroughText}" Width="260"/> <TextBox Text="{Binding Selected.PassthroughText}" Width="260"/>
</StackPanel> </StackPanel>
<Button Content="Apply to Selected" <StackPanel Orientation="Horizontal"
Command="{Binding ApplyOverridesCommand}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,10,0,0"/> Spacing="8"
Margin="0,10,0,0">
<Button Content="Apply to Selected"
Command="{Binding ApplyOverridesCommand}"/>
<Button Content="Transform all"
Background="#AA0000"
Foreground="White"
Command="{Binding TransformAllCommand}"/>
</StackPanel>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@ -3,12 +3,18 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:Splitter_UI.Views" xmlns:views="clr-namespace:Splitter_UI.Views"
xmlns:vm="clr-namespace:Splitter_UI.ViewModels" xmlns:vm="clr-namespace:Splitter_UI.ViewModels"
xmlns:conv="clr-namespace:Splitter_UI.Converters"
x:Class="Splitter_UI.Views.MainWindow" x:Class="Splitter_UI.Views.MainWindow"
x:DataType="vm:MainViewModel" x:DataType="vm:MainViewModel"
x:Name="Root"
Width="1400" Width="1400"
Height="950" Height="950"
Title="Splitter UI"> Title="Splitter UI">
<Window.Resources>
<conv:BoolInvertConverter x:Key="BoolInvertConverter"/>
</Window.Resources>
<DockPanel> <DockPanel>
<!-- Status Bar --> <!-- Status Bar -->
@ -19,8 +25,9 @@
<views:LogPane DockPanel.Dock="Bottom" Height="150" <views:LogPane DockPanel.Dock="Bottom" Height="150"
DataContext="{Binding LogPane}" /> DataContext="{Binding LogPane}" />
<Grid>
<!-- Main Content --> <!-- Main Content -->
<Grid ColumnDefinitions="2*,3*,430"> <Grid ColumnDefinitions="2*,3*,430" IsVisible="{Binding TransformMode, Converter={StaticResource BoolInvertConverter}}">
<!-- File List --> <!-- File List -->
<views:FileListView Grid.Column="0" <views:FileListView Grid.Column="0"
@ -36,6 +43,12 @@
</Grid> </Grid>
<!-- Progress view (replaces entire grid) -->
<views:ProgressView
DataContext="{Binding Progress}"
IsVisible="{Binding #Root.DataContext.TransformMode}"/>
</Grid>
</DockPanel> </DockPanel>
</Window> </Window>

View File

@ -0,0 +1,50 @@
<UserControl
x:Class="Splitter_UI.Views.ProgressView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Splitter_UI.ViewModels"
x:DataType="vm:ProgressViewModel">
<Border Background="#111" Padding="8">
<ItemsControl ItemsSource="{Binding Processes}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:ProgressInfo">
<Grid ColumnDefinitions="2*,3*,Auto,Auto"
Margin="0,2">
<!-- Name -->
<TextBlock Grid.Column="0"
Text="{Binding Name}"
VerticalAlignment="Center"
FontSize="12"/>
<!-- Progress bar -->
<ProgressBar Grid.Column="1"
Height="12"
Minimum="0"
Maximum="1"
Value="{Binding Progress}"
Margin="8,0"/>
<!-- ETA -->
<TextBlock Grid.Column="2"
Width="70"
Text="{Binding Eta, StringFormat={}{0:hh\\:mm\\:ss}}"
VerticalAlignment="Center"
Margin="12,0"
FontSize="12"/>
<!-- Speed -->
<TextBlock Grid.Column="3"
Width="70"
Text="{Binding Speed, StringFormat={}{0:0.00}}"
VerticalAlignment="Center"
Margin="12,0"
FontSize="12"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</UserControl>

View File

@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace Splitter_UI.Views;
public partial class ProgressView : UserControl
{
public ProgressView()
{
InitializeComponent();
}
}

View File

@ -12,13 +12,13 @@
Text="{Binding StatusText}" /> Text="{Binding StatusText}" />
<ProgressBar Grid.Column="1" <ProgressBar Grid.Column="1"
Width="200" Height="16" Width="200"
Height="16"
Minimum="0"
Maximum="1"
VerticalAlignment="Center" VerticalAlignment="Center"
Value="{Binding Percent}" /> Value="{Binding Percent}" />
<TextBlock Grid.Column="2"
VerticalAlignment="Center"
Text="{Binding ThreadInfo}" />
</Grid> </Grid>
</Border> </Border>
</UserControl> </UserControl>