mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-22 00:22:01 +00:00
UI for SingleJob added.
This commit is contained in:
parent
ad418e18a9
commit
e18d043b78
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme/>
|
<FluentTheme/>
|
||||||
|
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
|
|
||||||
</Application>
|
</Application>
|
||||||
15
Splitter-UI/Models/ParameterEntry.cs
Normal file
15
Splitter-UI/Models/ParameterEntry.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,9 +9,9 @@ public sealed class FileJobFactory : IFileJobFactory
|
|||||||
_services = services;
|
_services = services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileJobViewModel Create(SingleJob job)
|
public JobViewModel Create(SingleJob job)
|
||||||
{
|
{
|
||||||
// Resolve a fresh VM + fresh services
|
// Resolve a fresh VM + fresh services
|
||||||
return ActivatorUtilities.CreateInstance<FileJobViewModel>(_services, job);
|
return ActivatorUtilities.CreateInstance<JobViewModel>(_services, job);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,5 +6,5 @@ namespace Splitter_UI.Services;
|
|||||||
|
|
||||||
public interface IFileJobFactory
|
public interface IFileJobFactory
|
||||||
{
|
{
|
||||||
FileJobViewModel Create(SingleJob job);
|
JobViewModel Create(SingleJob job);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="12.0.3" />
|
<PackageReference Include="Avalonia" Version="12.0.3" />
|
||||||
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="12.0.0" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="12.0.3" />
|
<PackageReference Include="Avalonia.Desktop" Version="12.0.3" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.3" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.3" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.3" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.3" />
|
||||||
|
|||||||
@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,20 +7,20 @@ namespace Splitter_UI.ViewModels;
|
|||||||
public partial class FileListViewModel : ObservableObject
|
public partial class FileListViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
private readonly IFileJobFactory _factory;
|
private readonly IFileJobFactory _factory;
|
||||||
public ObservableCollection<FileJobViewModel> Files { get; } = [];
|
public ObservableCollection<JobViewModel> Files { get; } = [];
|
||||||
public ObservableCollection<FileJobViewModel> SelectedFiles { get; } = [];
|
public ObservableCollection<JobViewModel> SelectedFiles { get; } = [];
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private FileJobViewModel? _selected;
|
private JobViewModel? _selected;
|
||||||
|
|
||||||
public event Action<FileJobViewModel?>? SelectedFileChanged;
|
public event Action<JobViewModel?>? SelectedFileChanged;
|
||||||
|
|
||||||
public FileListViewModel(IFileJobFactory factory)
|
public FileListViewModel(IFileJobFactory factory)
|
||||||
{
|
{
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedChanged(FileJobViewModel? value)
|
partial void OnSelectedChanged(JobViewModel? value)
|
||||||
=> SelectedFileChanged?.Invoke(value);
|
=> SelectedFileChanged?.Invoke(value);
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|||||||
@ -8,13 +8,18 @@ namespace Splitter_UI.ViewModels;
|
|||||||
public partial class InspectorPaneViewModel : ObservableObject
|
public partial class InspectorPaneViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private FileJobViewModel? _selected;
|
private JobViewModel? _selected;
|
||||||
|
|
||||||
public List<string> DetectModes =>
|
public List<string> DetectModes =>
|
||||||
[
|
[
|
||||||
"face", "body", "none"
|
"face", "body", "none"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public List<int> RotationAngles =>
|
||||||
|
[
|
||||||
|
0, 90, 180, 270
|
||||||
|
];
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void ApplyOverrides()
|
private void ApplyOverrides()
|
||||||
{
|
{
|
||||||
@ -22,4 +27,26 @@ public partial class InspectorPaneViewModel : ObservableObject
|
|||||||
return;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
156
Splitter-UI/ViewModels/JobViewModel.cs
Normal file
156
Splitter-UI/ViewModels/JobViewModel.cs
Normal file
@ -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<ParameterEntry> 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<string>()
|
||||||
|
: 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,7 +5,7 @@ namespace Splitter_UI.ViewModels;
|
|||||||
public partial class PreviewPaneViewModel : ObservableObject
|
public partial class PreviewPaneViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private FileJobViewModel? _selected;
|
private JobViewModel? _selected;
|
||||||
|
|
||||||
public PreviewPaneViewModel()
|
public PreviewPaneViewModel()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -51,7 +51,7 @@
|
|||||||
</ListBox.Styles>
|
</ListBox.Styles>
|
||||||
|
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:FileJobViewModel">
|
<DataTemplate x:DataType="vm:JobViewModel">
|
||||||
<Border x:Name="ItemRoot"
|
<Border x:Name="ItemRoot"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
|
|||||||
@ -1,42 +1,156 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="Splitter_UI.Views.InspectorPane"
|
x:Class="Splitter_UI.Views.InspectorPane"
|
||||||
xmlns:vm="clr-namespace:Splitter_UI.ViewModels"
|
xmlns:vm="clr-namespace:Splitter_UI.ViewModels"
|
||||||
x:DataType="vm:InspectorPaneViewModel">
|
x:DataType="vm:InspectorPaneViewModel">
|
||||||
|
|
||||||
<Border Background="#252525" Padding="12">
|
<Border Background="#252525" Padding="12">
|
||||||
<StackPanel Spacing="8">
|
<ScrollViewer>
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
|
||||||
<TextBlock Text="Parameters" FontSize="18" Margin="0,0,0,10"/>
|
<TextBlock Text="Parameters" FontSize="10" Margin="0,0,0,10" FontWeight="Bold"/>
|
||||||
|
|
||||||
|
<!-- InputFile -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="{Binding Selected.FileName}" Width="360" Margin="0,0,0,10" FontStyle="Italic"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Rotate -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="Rotate" Width="120"/>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
|
|
||||||
|
<Button Width="24" Height="24"
|
||||||
|
Padding="0"
|
||||||
|
Command="{Binding RotateLeftCommand}">
|
||||||
|
<TextBlock FontFamily="{StaticResource FontAwesome}"
|
||||||
|
Text=""
|
||||||
|
FontSize="12"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,-1,0,0"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Width="24" Height="24"
|
||||||
|
Padding="0"
|
||||||
|
Command="{Binding RotateRightCommand}">
|
||||||
|
<TextBlock FontFamily="{StaticResource FontAwesome}"
|
||||||
|
Text=""
|
||||||
|
FontSize="12"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,-1,0,0"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Angle display -->
|
||||||
|
<TextBlock Text="{Binding Selected.Rotate}"
|
||||||
|
Width="40"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Mask -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="Mask" Width="120"/>
|
||||||
|
<TextBox Text="{Binding Selected.Job.Mask}" Width="260"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- OutputFolder -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="Output Folder" Width="120"/>
|
||||||
|
<TextBox Text="{Binding Selected.Job.OutputFolder}" Width="260"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Crop -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="Crop (w,h)" Width="120"/>
|
||||||
|
<TextBox Text="{Binding Selected.CropText}" Width="160"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- GravitateTo -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="GravitateTo" Width="120"/>
|
||||||
|
<TextBox Text="{Binding Selected.GravitateText}" Width="160"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Detect -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="Detect" Width="120"/>
|
||||||
|
<ComboBox ItemsSource="{Binding DetectModes}"
|
||||||
|
SelectedItem="{Binding Selected.Job.Detect}"
|
||||||
|
Width="160"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- OverrideTargetDuration -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="Target Duration" Width="120"/>
|
||||||
|
<NumericUpDown Value="{Binding Selected.Job.OverrideTargetDuration}" Width="120"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- RotateAuto -->
|
||||||
|
<CheckBox Content="Rotate Auto"
|
||||||
|
IsChecked="{Binding Selected.Job.RotateAuto}"/>
|
||||||
|
|
||||||
|
<!-- ForceFixed -->
|
||||||
|
<CheckBox Content="Force Fixed Duration"
|
||||||
|
IsChecked="{Binding Selected.Job.ForceFixed}"/>
|
||||||
|
|
||||||
|
<!-- SingleThreaded -->
|
||||||
|
<CheckBox Content="Single Threaded"
|
||||||
|
IsChecked="{Binding Selected.Job.SingleThreaded}"/>
|
||||||
|
|
||||||
|
<!-- Debug -->
|
||||||
|
<CheckBox Content="Debug Mode"
|
||||||
|
IsChecked="{Binding Selected.Job.Debug}"/>
|
||||||
|
|
||||||
|
<!-- Parameters dictionary -->
|
||||||
|
<TextBlock Text="Advanced Parameters" FontSize="10" Margin="0,10,0,0" FontWeight="Bold"/>
|
||||||
|
|
||||||
|
<DataGrid ItemsSource="{Binding Selected.ParametersList}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
HeadersVisibility="Column"
|
||||||
|
Height="160">
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
|
||||||
|
<DataGridTextColumn Header="Key"
|
||||||
|
Binding="{Binding Key}"
|
||||||
|
Width="*"/>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn Header="Value" Width="2*">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Value}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
|
||||||
|
<DataGridTemplateColumn.CellEditingTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellEditingTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Passthrough -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock Text="Passthrough" Width="120"/>
|
||||||
|
<TextBox Text="{Binding Selected.PassthroughText}" Width="260"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Content="Apply to Selected"
|
||||||
|
Command="{Binding ApplyOverridesCommand}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0,10,0,0"/>
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<TextBlock Text="Rotate:" Width="100"/>
|
|
||||||
<NumericUpDown Value="{Binding Selected.Job.Rotate}" Width="120"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<!--
|
</ScrollViewer>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<TextBlock Text="Crop:" Width="100"/>
|
|
||||||
<TextBox Text="{Binding Selected.CropText}" Width="200"/>
|
|
||||||
</StackPanel>
|
|
||||||
-->
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<TextBlock Text="Detect:" Width="100"/>
|
|
||||||
<ComboBox ItemsSource="{Binding DetectModes}"
|
|
||||||
SelectedItem="{Binding Selected.Job.Detect}"
|
|
||||||
Width="200"/>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<CheckBox Content="Force Fixed Duration"
|
|
||||||
IsChecked="{Binding Selected.Job.ForceFixed}"/>
|
|
||||||
|
|
||||||
<CheckBox Content="Debug Mode"
|
|
||||||
IsChecked="{Binding Selected.Job.Debug}"/>
|
|
||||||
|
|
||||||
<Button Content="Apply to Selected"
|
|
||||||
Command="{Binding ApplyOverridesCommand}"
|
|
||||||
Margin="0,10,0,0"/>
|
|
||||||
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -4,21 +4,93 @@ namespace splitter;
|
|||||||
|
|
||||||
public class SingleJob
|
public class SingleJob
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// File path of the input video. This is required for each job and should be
|
||||||
|
/// set to a valid video file path. The splitter will read this file, analyze it,
|
||||||
|
/// and split it into segments based on the specified parameters.
|
||||||
|
/// The output segments will be saved in the OutputFolder with names
|
||||||
|
/// derived from this input file and the Mask pattern if provided.
|
||||||
|
/// </summary>
|
||||||
public string InputFile { get; set; } = null!;
|
public string InputFile { get; set; } = null!;
|
||||||
|
/// <summary>
|
||||||
|
/// Output folder where the split segments will be saved. This should be set
|
||||||
|
/// to a valid directory path.
|
||||||
|
/// </summary>
|
||||||
public string OutputFolder { get; set; } = null!;
|
public string OutputFolder { get; set; } = null!;
|
||||||
|
/// <summary>
|
||||||
|
/// Crop parameters. Width and height for cropping the video. If set, the
|
||||||
|
/// splitter will crop the video to the specified dimensions while tracking the subject.
|
||||||
|
/// </summary>
|
||||||
public (int width, int height)? Crop { get; set; }
|
public (int width, int height)? Crop { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The fallback point to gravitate towards when tracking the subject. Coordinates are normalized (0.0 to 1.0).
|
||||||
|
/// By default , the splitter gravitates towards the center of the frame (0.5, 0.5).
|
||||||
|
/// Setting this allows you to bias the tracking towards a specific area of the frame,
|
||||||
|
/// such as left-center (0.2, 0.5) or top-right (0.8, 0.2). This can be useful for
|
||||||
|
/// videos where the subject tends to be off-center or for creative framing choices.
|
||||||
|
/// </summary>
|
||||||
public Point2f? GravitateTo { get; set; }
|
public Point2f? GravitateTo { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Destination file mask.
|
||||||
|
/// </summary>
|
||||||
public string? Mask { get; set; }
|
public string? Mask { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Instead of producing the output, just generate debug frames with tracking
|
||||||
|
/// overlay to visually verify that the tracking is working correctly.
|
||||||
|
/// </summary>
|
||||||
public bool Debug { get; set; }
|
public bool Debug { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Type of detector to use for tracking. Supported values are: face (UltraFace),
|
||||||
|
/// body (YoloOnnx, default), none (no tracking, just a center point).
|
||||||
|
/// </summary>
|
||||||
public string? Detect { get; set; }
|
public string? Detect { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Set starget segments length explicitly. By default, the splitter calculates segment
|
||||||
|
/// lengths to be equal and not exceed 58 seconds.
|
||||||
|
/// </summary>
|
||||||
public double? OverrideTargetDuration { get; set; }
|
public double? OverrideTargetDuration { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Parameters to pass thru to ffmpeg. These are specified after "--" in the command
|
||||||
|
/// line and are passed directly to the ffmpeg command line for each segment.
|
||||||
|
/// </summary>
|
||||||
public string[] Passthrough { get; set; } = [];
|
public string[] Passthrough { get; set; } = [];
|
||||||
|
/// <summary>
|
||||||
|
/// Debugging parameter. Instead of text UI putput lines in plain text.
|
||||||
|
/// This is useful when the output is being piped to a file or another program,
|
||||||
|
/// or when the user prefers a simpler log format without progress bars and dynamic updates.
|
||||||
|
/// </summary>
|
||||||
public bool PlainText { get; set; }
|
public bool PlainText { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Debugging parameter. Just show estimated segments length, count, and other info
|
||||||
|
/// without actually performing the splitting.
|
||||||
|
/// </summary>
|
||||||
public bool EstimateOnly { get; set; }
|
public bool EstimateOnly { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Do not adapt segment length. When set, the splitter will use the exact
|
||||||
|
/// segment duration specified by --duration for all segments except possibly
|
||||||
|
/// the last one, which may be shorter.
|
||||||
|
/// </summary>
|
||||||
public bool ForceFixed { get; set; }
|
public bool ForceFixed { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Use single thread for operations. When set, the splitter will not run
|
||||||
|
/// multiple ffmpeg processes in parallel.
|
||||||
|
/// </summary>
|
||||||
public bool SingleThreaded { get; set; }
|
public bool SingleThreaded { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Rotation angle: 90, 180, or 270 degrees. This is useful for videos that
|
||||||
|
/// have incorrect orientation metadata.
|
||||||
|
/// </summary>
|
||||||
public int? Rotate { get; set; }
|
public int? Rotate { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Autodetect if rotation is needed. Not very reliable but can work for some videos.
|
||||||
|
/// Uses edge orientation statistics to determine if the video is rotated and
|
||||||
|
/// applies the appropriate rotation if needed.
|
||||||
|
/// </summary>
|
||||||
public bool RotateAuto { get; set; }
|
public bool RotateAuto { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Override internal parameters. This allows you to set custom parameters for the
|
||||||
|
/// object detector or rotation detector.
|
||||||
|
/// </summary>
|
||||||
public Dictionary<string, string> Parameters { get; set; } = [];
|
public Dictionary<string, string> Parameters { get; set; } = [];
|
||||||
|
|
||||||
public void Override<T>(ref T member, string name)
|
public void Override<T>(ref T member, string name)
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
<BuildNumber>0</BuildNumber>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- DEBUG CONFIGURATION -->
|
<!-- DEBUG CONFIGURATION -->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user