mirror of
https://github.com/unclshura/splitter.git
synced 2026-06-22 00:22:01 +00:00
246 lines
7.2 KiB
C#
246 lines
7.2 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using Avalonia.Media.Imaging;
|
|
using Avalonia.Threading;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
namespace Splitter_UI.ViewModels;
|
|
|
|
public partial class JobViewModel : ObservableObject
|
|
{
|
|
public SingleJob Job { get; }
|
|
public VideoInfo? Probe { get; set; }
|
|
[ObservableProperty]
|
|
private PreviewData? _preview = new(null, [], null);
|
|
public ProgressInfo? Progress { get; set; }
|
|
|
|
[ObservableProperty]
|
|
private Bitmap? _thumbnail;
|
|
|
|
[ObservableProperty]
|
|
private string _suggestedAction = "";
|
|
|
|
// This updates continuously
|
|
[ObservableProperty]
|
|
private double _sliderLiveValue;
|
|
|
|
// This updates only on release
|
|
[ObservableProperty]
|
|
private double _positionSeconds;
|
|
|
|
public double DurationSeconds => Probe?.Duration ?? 0;
|
|
|
|
public IRelayCommand StepForwardCommand { get; }
|
|
public IRelayCommand StepBackwardCommand { get; }
|
|
|
|
private readonly IThumbnailService _thumbnails;
|
|
private readonly IFileProbeService _fileProbe;
|
|
private readonly DispatcherTimer _debounceTimer;
|
|
|
|
public string FileName => Path.GetFileName(Job.InputFile);
|
|
|
|
public string TextDesc => Probe != null
|
|
? $"{Probe.Width}x{Probe.Height}, {TimeSpan.FromSeconds(Probe.Duration).ToString(@"hh\:mm\:ss")}), FPS: {Probe.Fps:F2}, Bitrate: {Probe.Bitrate/1024/1024:F2} MB/s"
|
|
: "";
|
|
|
|
public override string ToString() => $"{FileName} - {TextDesc}";
|
|
|
|
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();
|
|
Task.Run(CreatePreview);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
StepForwardCommand = new RelayCommand(StepForward);
|
|
StepBackwardCommand = new RelayCommand(StepBackward);
|
|
|
|
_debounceTimer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromSeconds(1)
|
|
};
|
|
_debounceTimer.Tick += DebounceTimerTick;
|
|
|
|
_ = Task.Run( LoadThumbnailAsync );
|
|
}
|
|
|
|
private async Task LoadThumbnailAsync()
|
|
{
|
|
Probe = await _fileProbe.ProbeAsync(Job);
|
|
Thumbnail = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe, rotateDegree: Job.Rotate);
|
|
SuggestedAction = Probe.Rotation == 0 ? "crop" : "rotate";
|
|
|
|
await CreatePreview();
|
|
}
|
|
|
|
private async Task CreatePreview()
|
|
{
|
|
if ( Probe == null)
|
|
return;
|
|
try
|
|
{
|
|
var frame = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe, TimeSpan.FromSeconds(PositionSeconds), Probe.Width, Probe.Height, Job.Rotate);
|
|
Preview = new PreviewData(frame, [], null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void StepForward()
|
|
{
|
|
if (DurationSeconds <= 0)
|
|
return;
|
|
|
|
var step = DurationSeconds * 0.1; // 10% of total duration
|
|
|
|
SliderLiveValue = Math.Min(DurationSeconds, SliderLiveValue + step);
|
|
// trigger seek in your playback pipeline here
|
|
}
|
|
|
|
private void StepBackward()
|
|
{
|
|
if (DurationSeconds <= 0)
|
|
return;
|
|
|
|
var step = DurationSeconds * 0.1; // 10% of total duration
|
|
|
|
SliderLiveValue = Math.Max(0, SliderLiveValue - step);
|
|
// trigger seek in your playback pipeline here
|
|
}
|
|
|
|
partial void OnSliderLiveValueChanged(double value)
|
|
{
|
|
// Restart debounce timer on every slider update
|
|
_debounceTimer.Stop();
|
|
_debounceTimer.Start();
|
|
}
|
|
|
|
private void DebounceTimerTick(object? sender, EventArgs e)
|
|
{
|
|
_debounceTimer.Stop();
|
|
|
|
// Commit the final value
|
|
PositionSeconds = SliderLiveValue;
|
|
}
|
|
|
|
partial void OnPositionSecondsChanged(double value)
|
|
{
|
|
Task.Run(CreatePreview);
|
|
}
|
|
}
|