Add project files.
This commit is contained in:
196
ClipForge/TrimmerWindow.xaml.cs
Normal file
196
ClipForge/TrimmerWindow.xaml.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user