From 9b1bd51bd3f0c2d84e6a3e79fd13dc500f9ca5a8 Mon Sep 17 00:00:00 2001 From: l4kr Date: Sat, 4 Jul 2026 00:58:28 +0200 Subject: [PATCH] Add chapters display, gray out fixed tracks, spacebar preview --- MainWindow.xaml | 9 ++++++++- MainWindow.xaml.cs | 27 +++++++++++++++++++++++++++ Models/MkvModels.cs | 9 +++++++++ Models/TrackItem.cs | 14 ++++++++++++++ Services/MkvService.cs | 18 +++++++++++++++++- 5 files changed, 75 insertions(+), 2 deletions(-) diff --git a/MainWindow.xaml b/MainWindow.xaml index 50bbb9f..fb2983c 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -233,6 +233,12 @@ + + + + + + @@ -259,13 +265,14 @@ + Binding="{Binding IdDisplay}" Width="45" IsReadOnly="True"/> private void FileQueueList_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) { + if (e.Key == System.Windows.Input.Key.Space) + { + OpenSelectedFileInDefaultPlayer(); + e.Handled = true; + return; + } + if (e.Key != System.Windows.Input.Key.Delete) return; var selected = FileQueueList.SelectedItems.Cast().ToList(); @@ -371,6 +379,25 @@ public partial class MainWindow : FluentWindow e.Handled = true; } + /// + /// Opens the currently-selected queue item with whatever application + /// Windows has associated with .mkv files (typically the default video + /// player), so the user can quickly preview a file without leaving the app. + /// + private void OpenSelectedFileInDefaultPlayer() + { + if (FileQueueList.SelectedItem is not QueuedFileItem item) return; + + try + { + Process.Start(new ProcessStartInfo(item.FilePath) { UseShellExecute = true }); + } + catch (Exception ex) + { + StatusText.Text = $"Couldn't open {item.FileName}: {ex.Message}"; + } + } + private void ClearQueueButton_Click(object sender, RoutedEventArgs e) { foreach (var item in _fileQueue) diff --git a/Models/MkvModels.cs b/Models/MkvModels.cs index bbe2158..45bfbf1 100644 --- a/Models/MkvModels.cs +++ b/Models/MkvModels.cs @@ -13,6 +13,15 @@ public class MkvIdentifyResult [JsonPropertyName("tracks")] public List Tracks { get; set; } = new(); + + [JsonPropertyName("chapters")] + public List Chapters { get; set; } = new(); +} + +public class MkvChapters +{ + [JsonPropertyName("num_entries")] + public int NumEntries { get; set; } } public class MkvTrack diff --git a/Models/TrackItem.cs b/Models/TrackItem.cs index d6c66f8..e317acc 100644 --- a/Models/TrackItem.cs +++ b/Models/TrackItem.cs @@ -44,6 +44,20 @@ public class TrackItem : INotifyPropertyChanged /// public string DisplayName => Type == "subtitles" ? $"{Codec}/{Name}" : Name; + /// + /// Video is always kept, and chapters (a synthetic pseudo-track, not a + /// real mkvmerge track) are always kept too — neither is user-selectable, + /// so their Copy checkbox is shown checked but disabled. + /// + public bool IsEditable => Type is "audio" or "subtitles"; + + /// + /// Chapters aren't a real track and have no track ID; show a dash + /// instead of a meaningless number. + /// + public string IdDisplay => Type == "chapters" ? "—" : Id.ToString(); + + public bool Copy { get => _copy; diff --git a/Services/MkvService.cs b/Services/MkvService.cs index 6dc1238..52a0a1e 100644 --- a/Services/MkvService.cs +++ b/Services/MkvService.cs @@ -110,7 +110,7 @@ public class MkvService MkvTrack? preferredAudio = eligibleAudio.FirstOrDefault(t => t.Properties?.AudioChannels == 2) ?? eligibleAudio.FirstOrDefault(); - return info.Tracks.Select(t => + var items = info.Tracks.Select(t => { bool copy = t.Type switch { @@ -136,6 +136,22 @@ public class MkvService Copy = copy, }; }).ToList(); + + int chapterCount = info.Chapters.Sum(c => c.NumEntries); + if (chapterCount > 0) + { + items.Add(new TrackItem + { + Id = -1, + Type = "chapters", + Codec = string.Empty, + Language = string.Empty, + Name = $"Chapters ({chapterCount})", + Copy = true, + }); + } + + return items; } ///