196 lines
6.2 KiB
C#
196 lines
6.2 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.UI.Xaml;
|
|
using Microsoft.UI.Xaml.Controls;
|
|
using Windows.Media.Core;
|
|
using Windows.Media.Playback;
|
|
|
|
namespace ClipForge
|
|
{
|
|
public sealed partial class TrimmerWindow : Window
|
|
{
|
|
private string _clipPath;
|
|
private TimeSpan _duration;
|
|
private TimeSpan _inPoint;
|
|
private TimeSpan _outPoint;
|
|
private bool _isPlaying;
|
|
private bool _scrubbing;
|
|
private MediaPlayer _mediaPlayer;
|
|
private DispatcherTimer _timer;
|
|
|
|
public TrimmerWindow(string clipPath)
|
|
{
|
|
this.InitializeComponent();
|
|
_clipPath = clipPath;
|
|
|
|
this.AppWindow.Resize(new Windows.Graphics.SizeInt32(800, 560));
|
|
this.AppWindow.SetPresenter(
|
|
Microsoft.UI.Windowing.AppWindowPresenterKind.Overlapped);
|
|
|
|
LoadVideo();
|
|
}
|
|
|
|
private void LoadVideo()
|
|
{
|
|
_mediaPlayer = new MediaPlayer();
|
|
_mediaPlayer.Source = MediaSource.CreateFromUri(
|
|
new Uri(_clipPath));
|
|
_mediaPlayer.MediaOpened += OnMediaOpened;
|
|
|
|
MediaPlayer.SetMediaPlayer(_mediaPlayer);
|
|
|
|
// Timer to update scrubber position
|
|
_timer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(250)
|
|
};
|
|
_timer.Tick += OnTimerTick;
|
|
_timer.Start();
|
|
}
|
|
|
|
private void OnMediaOpened(MediaPlayer sender, object args)
|
|
{
|
|
_duration = sender.NaturalDuration;
|
|
_outPoint = _duration;
|
|
|
|
DispatcherQueue.TryEnqueue(() =>
|
|
{
|
|
DurationLabel.Text = FormatTime(_duration);
|
|
OutPointLabel.Text = FormatTime(_duration);
|
|
ScrubberSlider.Maximum = _duration.TotalSeconds;
|
|
});
|
|
}
|
|
|
|
private void OnTimerTick(object? sender, object e)
|
|
{
|
|
if (_scrubbing || !_isPlaying) return;
|
|
ScrubberSlider.Value = _mediaPlayer.Position.TotalSeconds;
|
|
}
|
|
|
|
private void ScrubberSlider_ValueChanged(object sender,
|
|
Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
|
|
{
|
|
if (_mediaPlayer == null) return;
|
|
_scrubbing = true;
|
|
_mediaPlayer.Position = TimeSpan.FromSeconds(e.NewValue);
|
|
_scrubbing = false;
|
|
}
|
|
|
|
private void PlayButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_isPlaying)
|
|
{
|
|
_mediaPlayer.Pause();
|
|
PlayButton.Content = new TextBlock
|
|
{
|
|
Text = "\uE768",
|
|
FontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Segoe MDL2 Assets"),
|
|
FontSize = 14
|
|
};
|
|
_isPlaying = false;
|
|
}
|
|
else
|
|
{
|
|
_mediaPlayer.Play();
|
|
PlayButton.Content = new TextBlock
|
|
{
|
|
Text = "\uE769",
|
|
FontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Segoe MDL2 Assets"),
|
|
FontSize = 14
|
|
};
|
|
_isPlaying = true;
|
|
}
|
|
}
|
|
|
|
private void SetInPoint_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_inPoint = _mediaPlayer.Position;
|
|
InPointLabel.Text = FormatTime(_inPoint);
|
|
}
|
|
|
|
private void SetOutPoint_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_outPoint = _mediaPlayer.Position;
|
|
OutPointLabel.Text = FormatTime(_outPoint);
|
|
}
|
|
|
|
private async void ExportTrim_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (_inPoint >= _outPoint)
|
|
{
|
|
var dialog = new ContentDialog
|
|
{
|
|
Title = "Invalid Range",
|
|
Content = "In point must be before out point.",
|
|
CloseButtonText = "OK",
|
|
XamlRoot = this.Content.XamlRoot
|
|
};
|
|
await dialog.ShowAsync();
|
|
return;
|
|
}
|
|
|
|
_mediaPlayer.Pause();
|
|
_isPlaying = false;
|
|
|
|
var outputDir = Path.GetDirectoryName(_clipPath)!;
|
|
var fileName = Path.GetFileNameWithoutExtension(_clipPath);
|
|
var outputPath = Path.Combine(outputDir,
|
|
$"{fileName}_trimmed_{DateTime.Now:HHmmss}.mp4");
|
|
|
|
await TrimWithFfmpegAsync(_clipPath, outputPath,
|
|
_inPoint, _outPoint);
|
|
|
|
var successDialog = new ContentDialog
|
|
{
|
|
Title = "Export Complete",
|
|
Content = $"Saved as {Path.GetFileName(outputPath)}",
|
|
CloseButtonText = "OK",
|
|
XamlRoot = this.Content.XamlRoot
|
|
};
|
|
await successDialog.ShowAsync();
|
|
this.Close();
|
|
}
|
|
|
|
private async Task TrimWithFfmpegAsync(
|
|
string input, string output,
|
|
TimeSpan inPoint, TimeSpan outPoint)
|
|
{
|
|
var ffmpegPath = Path.Combine(AppContext.BaseDirectory, "ffmpeg.exe");
|
|
if (!File.Exists(ffmpegPath)) return;
|
|
|
|
var start = inPoint.ToString(@"hh\:mm\:ss\.fff");
|
|
var duration = (outPoint - inPoint).ToString(@"hh\:mm\:ss\.fff");
|
|
|
|
var args = $"-ss {start} -i \"{input}\" -t {duration} -c copy \"{output}\"";
|
|
|
|
var process = new System.Diagnostics.Process
|
|
{
|
|
StartInfo = new System.Diagnostics.ProcessStartInfo
|
|
{
|
|
FileName = ffmpegPath,
|
|
Arguments = args,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
process.Start();
|
|
await Task.Run(() => process.WaitForExit());
|
|
}
|
|
|
|
private void Cancel_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
_mediaPlayer?.Pause();
|
|
_timer?.Stop();
|
|
this.Close();
|
|
}
|
|
|
|
private static string FormatTime(TimeSpan t)
|
|
{
|
|
return t.TotalHours >= 1
|
|
? t.ToString(@"h\:mm\:ss")
|
|
: t.ToString(@"m\:ss");
|
|
}
|
|
}
|
|
} |