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"); } } }