|
1 | 1 | using System.Collections.ObjectModel; |
| 2 | +using System.Diagnostics; |
2 | 3 | using System.Windows; |
3 | 4 | using CommunityToolkit.Mvvm.ComponentModel; |
4 | 5 | using CommunityToolkit.Mvvm.Input; |
@@ -26,6 +27,7 @@ public SRSCreatorViewModel(ISrsCreationService srsService, IFileDialogService fi |
26 | 27 | _settingsService = settingsService; |
27 | 28 |
|
28 | 29 | _sRSService.Progress += OnProgress; |
| 30 | + _sRSService.ScanProgress += OnScanProgress; |
29 | 31 |
|
30 | 32 | AppSettings settings = _settingsService.Load(); |
31 | 33 |
|
@@ -108,11 +110,18 @@ public SRSCreatorViewModel(ISrsCreationService srsService, IFileDialogService fi |
108 | 110 | [ObservableProperty] |
109 | 111 | private bool _iSOProcessing; |
110 | 112 |
|
| 113 | + private Stopwatch? _scanStopwatch; |
| 114 | + private bool _scanModalActive; |
| 115 | + |
111 | 116 | // Output |
112 | 117 | [ObservableProperty] |
113 | 118 | [NotifyCanExecuteChangedFor(nameof(CreateSRSCommand))] |
114 | 119 | private string _outputPath = string.Empty; |
115 | 120 |
|
| 121 | + // Optional main file for match-offset verification (mirrors pyrescene -c) |
| 122 | + [ObservableProperty] |
| 123 | + private string _mainFilePath = string.Empty; |
| 124 | + |
116 | 125 | // Options |
117 | 126 | [ObservableProperty] |
118 | 127 | private string _appName = string.Empty; |
@@ -178,6 +187,21 @@ private async Task BrowseInputAsync() |
178 | 187 | } |
179 | 188 | } |
180 | 189 |
|
| 190 | + [RelayCommand] |
| 191 | + private async Task BrowseMainFileAsync() |
| 192 | + { |
| 193 | + string? path = await _fileDialog.OpenFileAsync("Select Main File (Full Movie)", |
| 194 | + FileDialogFilters.MediaFiles); |
| 195 | + |
| 196 | + if (path is not null) |
| 197 | + { |
| 198 | + MainFilePath = path; |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + [RelayCommand] |
| 203 | + private void ClearMainFile() => MainFilePath = string.Empty; |
| 204 | + |
181 | 205 | [RelayCommand] |
182 | 206 | private async Task BrowseOutputAsync() |
183 | 207 | { |
@@ -219,7 +243,8 @@ private async Task CreateSRSAsync() |
219 | 243 | { |
220 | 244 | var options = new SRSCreationOptions |
221 | 245 | { |
222 | | - AppName = string.IsNullOrWhiteSpace(AppName) ? FormatUtilities.GetDefaultAppName() : AppName |
| 246 | + AppName = string.IsNullOrWhiteSpace(AppName) ? FormatUtilities.GetDefaultAppName() : AppName, |
| 247 | + MainFilePath = string.IsNullOrWhiteSpace(MainFilePath) ? null : MainFilePath |
223 | 248 | }; |
224 | 249 |
|
225 | 250 | Log("Starting SRS creation..."); |
@@ -274,9 +299,30 @@ await ISOMediaExtractor.ExtractFileAsync( |
274 | 299 | Log($"Input: {samplePath}"); |
275 | 300 | Log($"Output: {OutputPath}"); |
276 | 301 |
|
| 302 | + // Show progress modal during profiling (can take many seconds on large samples) |
| 303 | + ISOProgressHeading = "Profiling Sample"; |
| 304 | + ISOCurrentFileText = Path.GetFileName(samplePath); |
| 305 | + ISOFileCountText = "Reading sample structure and computing CRC..."; |
| 306 | + ISOOverallPercent = 0; |
| 307 | + ISOCurrentPercent = 0; |
| 308 | + ISOCurrentSizeText = string.Empty; |
| 309 | + ISOProcessedText = string.Empty; |
| 310 | + ISORemainingText = string.Empty; |
| 311 | + ISOSpeedText = string.Empty; |
| 312 | + ISOEtaText = string.Empty; |
| 313 | + _scanStopwatch = Stopwatch.StartNew(); |
| 314 | + _scanModalActive = true; |
| 315 | + ISOProcessing = true; |
| 316 | + |
| 317 | + // Yield to let the dispatcher open the modal before heavy work starts |
| 318 | + await Task.Yield(); |
| 319 | + |
277 | 320 | SRSCreationResult result = await _sRSService.CreateAsync( |
278 | 321 | OutputPath, samplePath, options, _cts.Token); |
279 | 322 |
|
| 323 | + _scanModalActive = false; |
| 324 | + ISOProcessing = false; |
| 325 | + |
280 | 326 | if (result.Success) |
281 | 327 | { |
282 | 328 | ProgressPercent = 100; |
@@ -311,6 +357,7 @@ await ISOMediaExtractor.ExtractFileAsync( |
311 | 357 | } |
312 | 358 | finally |
313 | 359 | { |
| 360 | + _scanModalActive = false; |
314 | 361 | ISOProcessing = false; |
315 | 362 | IsCreating = false; |
316 | 363 | _cts?.Dispose(); |
@@ -359,9 +406,72 @@ private void OnProgress(object? _, SRSCreationProgressEventArgs e) |
359 | 406 | { |
360 | 407 | ProgressMessage = e.Message; |
361 | 408 | Log(e.Message); |
| 409 | + |
| 410 | + if (!_scanModalActive) |
| 411 | + { |
| 412 | + return; |
| 413 | + } |
| 414 | + |
| 415 | + // Transition the modal as we move from profiling -> verifying -> writing -> complete |
| 416 | + if (e.Message.StartsWith("Verifying sample against main file", StringComparison.OrdinalIgnoreCase)) |
| 417 | + { |
| 418 | + ISOProgressHeading = "Verifying Against Main File"; |
| 419 | + ISOCurrentFileText = "Searching for track signatures in main file..."; |
| 420 | + ISOOverallPercent = 0; |
| 421 | + ISOCurrentPercent = 0; |
| 422 | + _scanStopwatch?.Restart(); |
| 423 | + } |
| 424 | + else if (e.Message.StartsWith("Writing SRS", StringComparison.OrdinalIgnoreCase)) |
| 425 | + { |
| 426 | + ISOProgressHeading = "Writing SRS"; |
| 427 | + ISOCurrentFileText = "Writing SRS file..."; |
| 428 | + ISOOverallPercent = 100; |
| 429 | + ISOCurrentPercent = 100; |
| 430 | + } |
| 431 | + }); |
| 432 | + } |
| 433 | + |
| 434 | + private void OnScanProgress(object? _, SRSScanProgressEventArgs e) |
| 435 | + { |
| 436 | + Application.Current.Dispatcher.BeginInvoke(() => |
| 437 | + { |
| 438 | + if (!_scanModalActive) |
| 439 | + { |
| 440 | + return; |
| 441 | + } |
| 442 | + |
| 443 | + ISOOverallPercent = e.Percent; |
| 444 | + ISOCurrentPercent = e.Percent; |
| 445 | + ISOCurrentFileText = e.Phase; |
| 446 | + UpdateScanStats(e.BytesScanned, e.BytesTotal); |
362 | 447 | }); |
363 | 448 | } |
364 | 449 |
|
| 450 | + private void UpdateScanStats(long processed, long total) |
| 451 | + { |
| 452 | + if (total <= 0 || _scanStopwatch is null) |
| 453 | + { |
| 454 | + return; |
| 455 | + } |
| 456 | + |
| 457 | + double elapsed = _scanStopwatch.Elapsed.TotalSeconds; |
| 458 | + ISOProcessedText = $"{FormatUtilities.FormatSize(processed)} / {FormatUtilities.FormatSize(total)}"; |
| 459 | + |
| 460 | + long remaining = total - processed; |
| 461 | + ISORemainingText = FormatUtilities.FormatSize(remaining); |
| 462 | + |
| 463 | + if (elapsed > 0.5 && processed > 0) |
| 464 | + { |
| 465 | + double bytesPerSec = processed / elapsed; |
| 466 | + ISOSpeedText = $"{FormatUtilities.FormatSize((long)bytesPerSec)}/s"; |
| 467 | + |
| 468 | + double secondsRemaining = remaining / bytesPerSec; |
| 469 | + ISOEtaText = secondsRemaining < 60 |
| 470 | + ? $"{secondsRemaining:F0}s" |
| 471 | + : $"{(int)(secondsRemaining / 60)}m {(int)(secondsRemaining % 60)}s"; |
| 472 | + } |
| 473 | + } |
| 474 | + |
365 | 475 | private void Log(string message) => AppendLogEntry(LogEntries, message); |
366 | 476 |
|
367 | 477 | #region ISO Support |
|
0 commit comments