emby-toolbox/EmbyToolbox/ViewModels/AddFilesSettingsViewModel.cs
2026-05-16 20:28:49 +05:00

358 lines
14 KiB
C#

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using EmbyToolbox.Models;
using EmbyToolbox.Services;
namespace EmbyToolbox.ViewModels;
public sealed class AddFilesSettingsViewModel : INotifyPropertyChanged
{
private readonly IReadOnlyList<ConversionProfilePresetRow> _profiles;
private readonly Action<AddFilesOptions> _onAdd;
private readonly Action _onCancel;
private readonly ProfileOverrideBuilder _builder = new();
private ConversionProfileSettingsEntry _sourceProfile = new();
private ConversionProfilePresetRow? _selectedProfile;
private string _container = string.Empty;
private string _video = string.Empty;
private string _pixelFormat = string.Empty;
private string _resolution = string.Empty;
private string _fps = string.Empty;
private string _videoBitrateMode = VideoBitratePolicy.Auto;
private string _videoBitrateCustomMbps = string.Empty;
private string _audio = string.Empty;
private string _audioBitrate = string.Empty;
private bool _subtitles;
private bool _externalTracks;
private bool _externalSubtitles;
private bool _fonts;
private bool _removeForeignTracks;
private bool _disableSubtitleDefault;
private string _validationMessage = string.Empty;
private bool _isValid;
public AddFilesSettingsViewModel(
IReadOnlyList<ConversionProfilePresetRow> profiles,
ConversionFormOptions formOptions,
AddFilesSettingsState? previousState,
string fallbackProfileName,
bool fallbackDisableSubtitleDefault,
bool fallbackRemoveForeignTracks,
Action<AddFilesOptions> onAdd,
Action onCancel)
{
_profiles = profiles;
_onAdd = onAdd;
_onCancel = onCancel;
Profiles = new ObservableCollection<ConversionProfilePresetRow>(profiles);
ContainerOptions = formOptions.ContainerOptions;
VideoCodecOptions = formOptions.VideoCodecOptions;
PixelFormatOptions = formOptions.PixelFormatOptions;
ResolutionOptions = formOptions.ResolutionOptions;
FpsOptions = formOptions.FpsOptions;
AudioCodecOptions = formOptions.AudioCodecOptions;
AudioBitrateOptions = formOptions.AudioBitrateKbps;
VideoBitrateOptions = VideoBitratePolicy.UiOptions;
YesNoOptions = ["Да", "Нет"];
AddCommand = new RelayCommand(ExecuteAdd, CanAdd);
CancelCommand = new RelayCommand(() => _onCancel());
var profileName = string.IsNullOrWhiteSpace(previousState?.ProfileName)
? fallbackProfileName
: previousState!.ProfileName;
_selectedProfile = Profiles.FirstOrDefault(p => p.Profile.Equals(profileName, StringComparison.OrdinalIgnoreCase))
?? Profiles.FirstOrDefault(p => p.Profile.Equals("Emby", StringComparison.OrdinalIgnoreCase))
?? Profiles.FirstOrDefault();
if (_selectedProfile is not null)
{
LoadProfile(_selectedProfile.ToSettingsEntry(), resetUserValues: true);
}
if (previousState is not null)
{
ApplyState(previousState);
}
else
{
_disableSubtitleDefault = fallbackDisableSubtitleDefault;
_removeForeignTracks = fallbackRemoveForeignTracks;
}
Validate();
}
public ObservableCollection<ConversionProfilePresetRow> Profiles { get; }
public IReadOnlyList<string> ContainerOptions { get; }
public IReadOnlyList<string> VideoCodecOptions { get; }
public IReadOnlyList<string> PixelFormatOptions { get; }
public IReadOnlyList<string> ResolutionOptions { get; }
public IReadOnlyList<string> FpsOptions { get; }
public IReadOnlyList<string> AudioCodecOptions { get; }
public IReadOnlyList<string> AudioBitrateOptions { get; }
public IReadOnlyList<string> VideoBitrateOptions { get; }
public IReadOnlyList<string> YesNoOptions { get; }
public RelayCommand AddCommand { get; }
public RelayCommand CancelCommand { get; }
public ConversionProfilePresetRow? SelectedProfile
{
get => _selectedProfile;
set
{
if (ReferenceEquals(_selectedProfile, value))
{
return;
}
_selectedProfile = value;
OnPropertyChanged();
if (value is not null)
{
LoadProfile(value.ToSettingsEntry(), resetUserValues: true);
}
}
}
public string Container { get => _container; set => SetField(ref _container, value); }
public string Video { get => _video; set => SetField(ref _video, value); }
public string PixelFormat { get => _pixelFormat; set => SetField(ref _pixelFormat, value); }
public string Resolution { get => _resolution; set => SetField(ref _resolution, value); }
public string Fps { get => _fps; set => SetField(ref _fps, value); }
public string VideoBitrateMode
{
get => _videoBitrateMode;
set
{
if (SetField(ref _videoBitrateMode, string.IsNullOrWhiteSpace(value) ? VideoBitratePolicy.Auto : value))
{
OnPropertyChanged(nameof(IsVideoBitrateCustomVisible));
}
}
}
public string VideoBitrateCustomMbps { get => _videoBitrateCustomMbps; set => SetField(ref _videoBitrateCustomMbps, value); }
public string Audio { get => _audio; set => SetField(ref _audio, value); }
public string AudioBitrate { get => _audioBitrate; set => SetField(ref _audioBitrate, value); }
public bool Subtitles { get => _subtitles; set => SetField(ref _subtitles, value); }
public bool ExternalTracks { get => _externalTracks; set => SetField(ref _externalTracks, value); }
public bool ExternalSubtitles { get => _externalSubtitles; set => SetField(ref _externalSubtitles, value); }
public bool Fonts { get => _fonts; set => SetField(ref _fonts, value); }
public bool RemoveForeignTracks { get => _removeForeignTracks; set => SetField(ref _removeForeignTracks, value); }
public bool DisableSubtitleDefault { get => _disableSubtitleDefault; set => SetField(ref _disableSubtitleDefault, value); }
public bool IsVideoBitrateCustomVisible => string.Equals(VideoBitrateMode, VideoBitratePolicy.Custom, StringComparison.Ordinal);
public bool HasValidationMessage => !string.IsNullOrWhiteSpace(ValidationMessage);
public string ValidationMessage
{
get => _validationMessage;
private set
{
if (_validationMessage == value)
{
return;
}
_validationMessage = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasValidationMessage));
}
}
public AddFilesSettingsState CaptureState() =>
new AddFilesSettingsState
{
ProfileName = SelectedProfile?.Profile ?? "Emby",
ChangedFields = BuildEffectiveSettings().ChangedFields.ToArray(),
Container = Container,
Video = Video,
PixelFormat = PixelFormat,
Resolution = Resolution,
Fps = Fps,
VideoBitrateMode = VideoBitrateMode,
VideoBitrateCustomMbps = VideoBitrateCustomMbps,
Audio = Audio,
AudioBitrate = AudioBitrate,
Subtitles = Subtitles,
ExternalTracks = ExternalTracks,
ExternalSubtitles = ExternalSubtitles,
Fonts = Fonts,
RemoveForeignTracks = RemoveForeignTracks,
DisableSubtitleDefault = DisableSubtitleDefault
};
private void LoadProfile(ConversionProfileSettingsEntry profile, bool resetUserValues)
{
_sourceProfile = ProfileOverrideBuilder.Clone(profile);
if (!resetUserValues)
{
return;
}
Container = profile.Container;
Video = profile.Video;
PixelFormat = profile.PixelFormat;
Resolution = profile.Resolution;
Fps = profile.Fps;
VideoBitrateMode = VideoBitratePolicy.NormalizeMode(profile.VideoBitrateMode);
VideoBitrateCustomMbps = profile.VideoBitrateMbps?.ToString("0.###", CultureInfo.InvariantCulture) ?? string.Empty;
Audio = profile.Audio;
AudioBitrate = profile.Bitrate;
Subtitles = IsYes(profile.Subtitles);
ExternalTracks = IsYes(profile.ExternalTracks);
ExternalSubtitles = IsYes(profile.ExternalSubtitles);
Fonts = IsYes(profile.Fonts);
}
private void ApplyState(AddFilesSettingsState state)
{
var changed = state.ChangedFields.ToHashSet(StringComparer.Ordinal);
if (changed.Contains("Container")) Container = state.Container;
if (changed.Contains("Video")) Video = state.Video;
if (changed.Contains("PixelFormat")) PixelFormat = state.PixelFormat;
if (changed.Contains("Resolution")) Resolution = state.Resolution;
if (changed.Contains("Fps")) Fps = state.Fps;
if (changed.Contains("VideoBitrateMode")) VideoBitrateMode = state.VideoBitrateMode;
if (changed.Contains("VideoBitrateMbps")) VideoBitrateCustomMbps = state.VideoBitrateCustomMbps;
if (changed.Contains("Audio")) Audio = state.Audio;
if (changed.Contains("Bitrate")) AudioBitrate = state.AudioBitrate;
if (changed.Contains("Subtitles")) Subtitles = state.Subtitles;
if (changed.Contains("ExternalTracks")) ExternalTracks = state.ExternalTracks;
if (changed.Contains("ExternalSubtitles")) ExternalSubtitles = state.ExternalSubtitles;
if (changed.Contains("Fonts")) Fonts = state.Fonts;
RemoveForeignTracks = state.RemoveForeignTracks;
DisableSubtitleDefault = state.DisableSubtitleDefault;
}
private bool CanAdd() => _isValid;
private void ExecuteAdd()
{
if (!Validate())
{
return;
}
var effective = BuildEffectiveSettings();
_onAdd(
new AddFilesOptions
{
Profile = effective.SourceProfileName,
DisableSubtitleDefault = DisableSubtitleDefault,
RemoveForeignAudioAndSubtitles = RemoveForeignTracks,
EffectiveSettings = effective
});
}
private EffectiveProfileSettings BuildEffectiveSettings()
{
var form = new ConversionProfileSettingsEntry
{
Profile = SelectedProfile?.Profile ?? "Emby",
Container = Container,
Video = Video,
PixelFormat = PixelFormat,
Resolution = Resolution,
Fps = Fps,
Audio = Audio,
Bitrate = AudioBitrate,
VideoBitrateMode = VideoBitrateMode,
VideoBitrateMbps = IsVideoBitrateCustomVisible && TryParseCustomVideoBitrate(VideoBitrateCustomMbps, out var mbps) ? mbps : null,
Subtitles = ToYesNo(Subtitles),
ExternalTracks = ToYesNo(ExternalTracks),
ExternalSubtitles = ToYesNo(ExternalSubtitles),
Fonts = ToYesNo(Fonts)
};
return _builder.Build(_sourceProfile, form, SelectedProfile?.Profile ?? "Emby");
}
private bool Validate()
{
if (SelectedProfile is null)
{
ValidationMessage = "Выберите профиль.";
_isValid = false;
AddCommand.RaiseCanExecuteChanged();
return false;
}
if (IsVideoBitrateCustomVisible && !TryParseCustomVideoBitrate(VideoBitrateCustomMbps, out _))
{
ValidationMessage = "Видеобитрейт должен быть числом больше 0.";
_isValid = false;
AddCommand.RaiseCanExecuteChanged();
return false;
}
ValidationMessage = string.Empty;
_isValid = true;
AddCommand.RaiseCanExecuteChanged();
return true;
}
private bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
Validate();
return true;
}
private static bool TryParseCustomVideoBitrate(string? raw, out double mbps)
{
mbps = 0;
if (string.IsNullOrWhiteSpace(raw))
{
return false;
}
return double.TryParse(raw.Trim().Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out mbps)
&& mbps > 0;
}
private static bool IsYes(string? value) =>
value is not null
&& (value.Equals("Да", StringComparison.OrdinalIgnoreCase)
|| value.Equals("Yes", StringComparison.OrdinalIgnoreCase)
|| value.Equals("true", StringComparison.OrdinalIgnoreCase));
private static string ToYesNo(bool value) => value ? "Да" : "Нет";
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class AddFilesSettingsState
{
public string ProfileName { get; init; } = "Emby";
public IReadOnlyList<string> ChangedFields { get; init; } = [];
public string Container { get; init; } = string.Empty;
public string Video { get; init; } = string.Empty;
public string PixelFormat { get; init; } = string.Empty;
public string Resolution { get; init; } = string.Empty;
public string Fps { get; init; } = string.Empty;
public string VideoBitrateMode { get; init; } = VideoBitratePolicy.Auto;
public string VideoBitrateCustomMbps { get; init; } = string.Empty;
public string Audio { get; init; } = string.Empty;
public string AudioBitrate { get; init; } = string.Empty;
public bool Subtitles { get; init; }
public bool ExternalTracks { get; init; }
public bool ExternalSubtitles { get; init; }
public bool Fonts { get; init; }
public bool RemoveForeignTracks { get; init; }
public bool DisableSubtitleDefault { get; init; }
}