Add overall progress, notifications, and UI polish

- Unify audio/subtitle selection: single-select, auto-default,
  cross-file propagation by exact name
- Show file size in queue, group divider and reordered columns in
  track table, drop accent highlight on row selection
- Add cumulative progress bar (files finished, MB copied) to the
  stripping window
- Add Windows toast notifications on stripping completion/cancel/error
- Restart the app after settings are changed; Cancel no longer
  clears the queue, only closes the window
- Add ConfigureAwait(false) throughout MkvService so concurrent
  strips don't starve the UI thread
- Hardcode scan/strip concurrency (10/5) instead of exposing it in
  Settings
This commit is contained in:
l4kr
2026-07-04 02:35:26 +02:00
parent 9b1bd51bd3
commit 05c496c10b
12 changed files with 272 additions and 68 deletions
+6
View File
@@ -14,6 +14,12 @@ public class JobProgressItem : INotifyPropertyChanged
public string FileName { get; set; } = string.Empty;
/// <summary>
/// Size of the source file in bytes, used as this job's weight when
/// computing overall cumulative progress across all queued files.
/// </summary>
public long FileSizeBytes { get; set; }
public int Percent
{
get => _percent;
+25
View File
@@ -32,6 +32,7 @@ public class QueuedFileItem : INotifyPropertyChanged
public string FilePath { get; }
public string FileName => Path.GetFileName(FilePath);
public string FileSizeDisplay { get; }
public ObservableCollection<TrackItem> Tracks { get; } = new();
public string Status
@@ -76,9 +77,33 @@ public class QueuedFileItem : INotifyPropertyChanged
public QueuedFileItem(string filePath)
{
FilePath = filePath;
FileSizeDisplay = FormatFileSize(filePath);
Tracks.CollectionChanged += OnTracksChanged;
}
private static string FormatFileSize(string filePath)
{
try
{
long bytes = new FileInfo(filePath).Length;
string[] units = { "B", "KB", "MB", "GB", "TB" };
double size = bytes;
int unitIndex = 0;
while (size >= 1024 && unitIndex < units.Length - 1)
{
size /= 1024;
unitIndex++;
}
return unitIndex == 0
? $"{size:0} {units[unitIndex]}"
: $"{size:0.#} {units[unitIndex]}";
}
catch
{
return string.Empty;
}
}
private void OnTracksChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
+18 -11
View File
@@ -19,12 +19,21 @@ public class TrackItem : INotifyPropertyChanged
public string Name { get; init; } = string.Empty;
public bool ForcedDisplay { get; init; }
/// <summary>
/// True for the first track of each type group (video/audio/subtitles/
/// chapters) in the track table, computed once when the list is built.
/// Used to draw a divider line above the row in the UI.
/// </summary>
public bool IsGroupStart { get; set; }
private bool _defaultTrack;
/// <summary>
/// Whether this track should be flagged as the default track of its type
/// when stripped. For subtitles this is kept in sync with <see cref="Copy"/>
/// (selecting a subtitle also makes it the default one).
/// when stripped. For audio and subtitles this is kept in sync with
/// <see cref="Copy"/> (selecting a track also makes it the default one
/// of its type).
/// </summary>
public bool DefaultTrack
{
@@ -38,11 +47,9 @@ public class TrackItem : INotifyPropertyChanged
}
/// <summary>
/// Text shown in the track table's Name column. Subtitle tracks are shown
/// as "Codec/Name" so tracks that share a display name but use a different
/// codec are easy to tell apart.
/// Text shown in the track table's Name column.
/// </summary>
public string DisplayName => Type == "subtitles" ? $"{Codec}/{Name}" : Name;
public string DisplayName => Name;
/// <summary>
/// Video is always kept, and chapters (a synthetic pseudo-track, not a
@@ -70,12 +77,12 @@ public class TrackItem : INotifyPropertyChanged
}
/// <summary>
/// Key used to match subtitle tracks with the same identity across different
/// queued files when propagating a subtitle selection. Subtitles are matched
/// on "Codec/Name" so tracks with the same name but a different codec are
/// treated as distinct.
/// Key used to match a track's identity across different queued files
/// when propagating an audio/subtitle selection. Matched on "Codec/Name"
/// so tracks with the same name but a different codec are treated as
/// distinct, even though only the name is shown in the table.
/// </summary>
public string PropagationKey => DisplayName;
public string PropagationKey => $"{Codec}/{Name}";
public event PropertyChangedEventHandler? PropertyChanged;