using System.Collections; using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows; using EmbyToolbox.Models; using EmbyToolbox.Services; namespace EmbyToolbox.ViewModels; public sealed class BulkFileConversionSettingsViewModel : INotifyPropertyChanged, ITrackPlanPreviewHost { private readonly ConversionQueueItem _representative; private readonly ConversionTaskOverride _draft; private readonly Action> _onApply; private readonly Action _onClose; public BulkFileConversionSettingsViewModel( ConversionQueueItem representative, IReadOnlyList targets, ConversionFormOptions formOptions, Action> onApply, Action onClose) { _representative = representative; _draft = representative.TaskOverride.Clone(); _onApply = onApply; _onClose = onClose; FormOptions = formOptions; TargetRowNumbersText = string.Join(", ", targets.OrderBy(i => i.OrderNumber).Select(i => i.OrderNumber)); FilesCount = targets.Count; BuildRows(); ValidateDefaultConflicts(); SaveCommand = new RelayCommand(ExecuteSave); CancelCommand = new RelayCommand(() => _onClose()); OnSelectionChangedCommand = new RelayCommand(OnSelectionChanged); SetSelectedTracksRemoveCommand = new RelayCommand(SetSelectedTracksRemove, CanSetSelectedTracksRemove); } public ConversionFormOptions FormOptions { get; } public ObservableCollection TrackRows { get; } = new(); public ObservableCollection SelectedTracks { get; } = new(); public RelayCommand SaveCommand { get; } public RelayCommand CancelCommand { get; } public RelayCommand OnSelectionChangedCommand { get; } public RelayCommand SetSelectedTracksRemoveCommand { get; } public int FilesCount { get; } public string TargetRowNumbersText { get; } public event PropertyChangedEventHandler? PropertyChanged; public void RecalculatePlanPreview() { // No per-file plan preview in bulk dialog. } public void OnTrackDefaultEnabled(TrackSettingsRowViewModel row) { if (row.DataModel.StreamKind is not (MediaStreamKind.Audio or MediaStreamKind.Subtitle)) { return; } foreach (var r in TrackRows) { if (ReferenceEquals(r, row)) { continue; } if (r.DataModel.StreamKind == row.DataModel.StreamKind) { r.Default = false; } } } public void ValidateDefaultConflicts() { var audioDefaults = TrackRows .Where(r => r.DataModel.StreamKind == MediaStreamKind.Audio && r.Action != TrackActionKind.Remove && r.Default is true) .ToList(); var subDefaults = TrackRows .Where(r => r.DataModel.StreamKind == MediaStreamKind.Subtitle && r.Action != TrackActionKind.Remove && r.Default is true) .ToList(); foreach (var r in TrackRows) { r.HasDefaultConflict = false; } if (audioDefaults.Count > 1) { foreach (var r in audioDefaults) { r.HasDefaultConflict = true; } } if (subDefaults.Count > 1) { foreach (var r in subDefaults) { r.HasDefaultConflict = true; } } } private void OnSelectionChanged(object? parameter) { SelectedTracks.Clear(); if (parameter is IList list) { foreach (var r in list.OfType()) { SelectedTracks.Add(r); } } SetSelectedTracksRemoveCommand.RaiseCanExecuteChanged(); } private bool CanSetSelectedTracksRemove(object? parameter) { if (parameter is not IList list || list.Count == 0) { return false; } return list.OfType().Any(r => r.ValidActions.Contains(TrackActionKind.Remove)); } private void SetSelectedTracksRemove(object? parameter) { if (parameter is not IList list || list.Count == 0) { return; } var rows = list.OfType().ToList(); var changed = false; foreach (var row in rows) { if (!row.ValidActions.Contains(TrackActionKind.Remove)) { continue; } row.Action = TrackActionKind.Remove; changed = true; } if (changed) { ValidateDefaultConflicts(); SetSelectedTracksRemoveCommand.RaiseCanExecuteChanged(); } } private void ExecuteSave() { ValidateDefaultConflicts(); var hasAudioConflict = TrackRows.Any(r => r.HasDefaultConflict && r.DataModel.StreamKind == MediaStreamKind.Audio); var hasSubConflict = TrackRows.Any(r => r.HasDefaultConflict && r.DataModel.StreamKind == MediaStreamKind.Subtitle); if (hasAudioConflict || hasSubConflict) { MessageBox.Show( "Ошибка: для Audio и Subtitle может быть только одна дорожка Default (исключая Remove).", "Валидация", MessageBoxButton.OK, MessageBoxImage.Warning); return; } _onApply(_draft.TrackOverrides.Select(t => t.Clone()).ToList()); _onClose(); } private void BuildRows() { TrackRows.Clear(); var media = _representative.MediaAnalysis; var idx = 1; foreach (var t in _draft.TrackOverrides) { MediaStreamInfo? em = t.Source == SourceKind.Embedded && media is not null ? media.AllStreams.FirstOrDefault(s => s.Index == t.StreamIndex) : null; TrackRows.Add(new TrackSettingsRowViewModel(this, t, idx, em, _draft.TargetContainer)); idx++; } } private void OnPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }