Add effective profile editor for queued files
This commit is contained in:
parent
6cf86d41f5
commit
2e6c26178f
@ -5,4 +5,5 @@ public sealed class AddFilesOptions
|
||||
public string Profile { get; init; } = "Emby";
|
||||
public bool DisableSubtitleDefault { get; init; }
|
||||
public bool RemoveForeignAudioAndSubtitles { get; init; }
|
||||
public EffectiveProfileSettings EffectiveSettings { get; init; } = new();
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ public sealed class ConversionQueueItem : INotifyPropertyChanged
|
||||
private MediaAnalysisResult? _mediaAnalysis;
|
||||
private IReadOnlyList<SidecarFile> _sidecars = System.Array.Empty<SidecarFile>();
|
||||
private IReadOnlyList<ExternalAudioFile> _externalAudioFiles = System.Array.Empty<ExternalAudioFile>();
|
||||
private EffectiveProfileSettings? _effectiveProfileSettings;
|
||||
private ConversionPlan? _lastPlan;
|
||||
private bool _isProcessed;
|
||||
private bool _processedInCurrentRun;
|
||||
@ -133,6 +134,21 @@ public sealed class ConversionQueueItem : INotifyPropertyChanged
|
||||
|
||||
public ConversionTaskOverride TaskOverride { get; } = new();
|
||||
|
||||
public EffectiveProfileSettings? EffectiveProfileSettings
|
||||
{
|
||||
get => _effectiveProfileSettings;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(_effectiveProfileSettings, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_effectiveProfileSettings = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public ConversionPlan? LastPlan
|
||||
{
|
||||
get => _lastPlan;
|
||||
@ -484,6 +500,7 @@ public sealed class ConversionQueueItem : INotifyPropertyChanged
|
||||
}
|
||||
|
||||
_profile = value;
|
||||
EffectiveProfileSettings = null;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
12
EmbyToolbox/Models/EffectiveProfileSettings.cs
Normal file
12
EmbyToolbox/Models/EffectiveProfileSettings.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using EmbyToolbox.Services;
|
||||
|
||||
namespace EmbyToolbox.Models;
|
||||
|
||||
public sealed class EffectiveProfileSettings
|
||||
{
|
||||
public string SourceProfileName { get; init; } = "Emby";
|
||||
public ConversionProfileSettingsEntry Profile { get; init; } = new();
|
||||
public IReadOnlyList<string> ChangedFields { get; init; } = [];
|
||||
|
||||
public bool HasOverrides => ChangedFields.Count > 0;
|
||||
}
|
||||
@ -103,7 +103,9 @@ public sealed class ConversionExecutionService
|
||||
item.ErrorDetails = null;
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var profile = resolveProfile(item.Profile) ?? ConversionProfileMapping.EmbyFallback;
|
||||
var profile = item.EffectiveProfileSettings?.Profile
|
||||
?? resolveProfile(item.Profile)
|
||||
?? ConversionProfileMapping.EmbyFallback;
|
||||
targetContainer = ResolveTargetContainer(item, profile);
|
||||
finalPath = BuildFinalPath(item.FullPath, targetContainer);
|
||||
tempOut = Path.Combine(tempRoot, $"{Path.GetFileNameWithoutExtension(item.FileName)}.__processing__{GetOutputExtension(targetContainer)}");
|
||||
|
||||
65
EmbyToolbox/Services/ProfileOverrideBuilder.cs
Normal file
65
EmbyToolbox/Services/ProfileOverrideBuilder.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using EmbyToolbox.Models;
|
||||
|
||||
namespace EmbyToolbox.Services;
|
||||
|
||||
public sealed class ProfileOverrideBuilder
|
||||
{
|
||||
public EffectiveProfileSettings Build(
|
||||
ConversionProfileSettingsEntry source,
|
||||
ConversionProfileSettingsEntry formValues,
|
||||
string sourceProfileName)
|
||||
{
|
||||
var changed = new List<string>();
|
||||
AddIfChanged(changed, "Container", source.Container, formValues.Container);
|
||||
AddIfChanged(changed, "Video", source.Video, formValues.Video);
|
||||
AddIfChanged(changed, "PixelFormat", source.PixelFormat, formValues.PixelFormat);
|
||||
AddIfChanged(changed, "Resolution", source.Resolution, formValues.Resolution);
|
||||
AddIfChanged(changed, "Fps", source.Fps, formValues.Fps);
|
||||
AddIfChanged(changed, "Audio", source.Audio, formValues.Audio);
|
||||
AddIfChanged(changed, "Bitrate", source.Bitrate, formValues.Bitrate);
|
||||
AddIfChanged(changed, "VideoBitrateMode", source.VideoBitrateMode, formValues.VideoBitrateMode);
|
||||
if (source.VideoBitrateMbps != formValues.VideoBitrateMbps)
|
||||
{
|
||||
changed.Add("VideoBitrateMbps");
|
||||
}
|
||||
|
||||
AddIfChanged(changed, "Subtitles", source.Subtitles, formValues.Subtitles);
|
||||
AddIfChanged(changed, "ExternalTracks", source.ExternalTracks, formValues.ExternalTracks);
|
||||
AddIfChanged(changed, "ExternalSubtitles", source.ExternalSubtitles, formValues.ExternalSubtitles);
|
||||
AddIfChanged(changed, "Fonts", source.Fonts, formValues.Fonts);
|
||||
|
||||
return new EffectiveProfileSettings
|
||||
{
|
||||
SourceProfileName = sourceProfileName,
|
||||
Profile = Clone(formValues, sourceProfileName),
|
||||
ChangedFields = changed
|
||||
};
|
||||
}
|
||||
|
||||
public static ConversionProfileSettingsEntry Clone(ConversionProfileSettingsEntry source, string? profileName = null) =>
|
||||
new()
|
||||
{
|
||||
Profile = string.IsNullOrWhiteSpace(profileName) ? source.Profile : profileName!,
|
||||
Container = source.Container,
|
||||
Video = source.Video,
|
||||
PixelFormat = source.PixelFormat,
|
||||
Resolution = source.Resolution,
|
||||
Fps = source.Fps,
|
||||
Audio = source.Audio,
|
||||
Bitrate = source.Bitrate,
|
||||
VideoBitrateMode = source.VideoBitrateMode,
|
||||
VideoBitrateMbps = source.VideoBitrateMbps,
|
||||
Subtitles = source.Subtitles,
|
||||
ExternalTracks = source.ExternalTracks,
|
||||
ExternalSubtitles = source.ExternalSubtitles,
|
||||
Fonts = source.Fonts
|
||||
};
|
||||
|
||||
private static void AddIfChanged(List<string> changed, string name, string? source, string? value)
|
||||
{
|
||||
if (!string.Equals(source?.Trim(), value?.Trim(), StringComparison.Ordinal))
|
||||
{
|
||||
changed.Add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,7 +102,7 @@ public sealed class QueueAnalysisService
|
||||
await uiInvoke(
|
||||
() =>
|
||||
{
|
||||
var profile = _profile.GetProfile(item.Profile) ?? ConversionProfileMapping.EmbyFallback;
|
||||
var profile = ResolveEffectiveProfile(item);
|
||||
item.TaskOverride.TrackOverrides.Clear();
|
||||
TrackOverrideSeeder.EnsureDefaults(
|
||||
item.TaskOverride,
|
||||
@ -297,7 +297,7 @@ public sealed class QueueAnalysisService
|
||||
|
||||
var audio = FfprobeAudioInfoParser.TryParse(result.Json) ?? new FfprobeAudioInfo(0, null, true);
|
||||
var side = discovery.Sidecars;
|
||||
var profile = _profile.GetProfile(item.Profile) ?? ConversionProfileMapping.EmbyFallback;
|
||||
var profile = ResolveEffectiveProfile(item);
|
||||
// При повторном анализе sidecar-набор может измениться (добавили/удалили внешние файлы).
|
||||
// Пересобираем список дорожек, чтобы не держать устаревшие external entries.
|
||||
item.TaskOverride.TrackOverrides.Clear();
|
||||
@ -419,6 +419,11 @@ public sealed class QueueAnalysisService
|
||||
public int ErrorCount;
|
||||
}
|
||||
|
||||
private ConversionProfileSettingsEntry ResolveEffectiveProfile(ConversionQueueItem item) =>
|
||||
item.EffectiveProfileSettings?.Profile
|
||||
?? _profile.GetProfile(item.Profile)
|
||||
?? ConversionProfileMapping.EmbyFallback;
|
||||
|
||||
private static bool IsForeignLanguageForAutoRemove(string? language)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(language))
|
||||
|
||||
@ -15,6 +15,7 @@ public static class VideoBitratePolicy
|
||||
Auto,
|
||||
Source,
|
||||
"2 Mbps",
|
||||
"3 Mbps",
|
||||
"4 Mbps",
|
||||
"6 Mbps",
|
||||
"8 Mbps",
|
||||
|
||||
@ -1,102 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using EmbyToolbox.Models;
|
||||
|
||||
namespace EmbyToolbox.ViewModels;
|
||||
|
||||
public sealed class AddFilesOptionsViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private readonly Action<AddFilesOptions> _onAdd;
|
||||
private readonly Action _onCancel;
|
||||
|
||||
private bool _disableSubtitleDefault;
|
||||
private bool _removeForeignAudioAndSubtitles;
|
||||
private ConversionProfilePresetRow? _selectedProfile;
|
||||
|
||||
public AddFilesOptionsViewModel(
|
||||
IReadOnlyList<ConversionProfilePresetRow> profiles,
|
||||
string selectedProfileName,
|
||||
bool disableSubtitleDefault,
|
||||
bool removeForeignAudioAndSubtitles,
|
||||
Action<AddFilesOptions> onAdd,
|
||||
Action onCancel)
|
||||
{
|
||||
_onAdd = onAdd;
|
||||
_onCancel = onCancel;
|
||||
_disableSubtitleDefault = disableSubtitleDefault;
|
||||
_removeForeignAudioAndSubtitles = removeForeignAudioAndSubtitles;
|
||||
Profiles = new ObservableCollection<ConversionProfilePresetRow>(profiles);
|
||||
_selectedProfile = Profiles.FirstOrDefault(p => p.Profile.Equals(selectedProfileName, StringComparison.OrdinalIgnoreCase))
|
||||
?? Profiles.FirstOrDefault(p => p.Profile.Equals("Emby", StringComparison.OrdinalIgnoreCase))
|
||||
?? Profiles.FirstOrDefault();
|
||||
AddCommand = new RelayCommand(ExecuteAdd);
|
||||
CancelCommand = new RelayCommand(() => _onCancel());
|
||||
}
|
||||
|
||||
public bool DisableSubtitleDefault
|
||||
{
|
||||
get => _disableSubtitleDefault;
|
||||
set
|
||||
{
|
||||
if (_disableSubtitleDefault == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disableSubtitleDefault = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveForeignAudioAndSubtitles
|
||||
{
|
||||
get => _removeForeignAudioAndSubtitles;
|
||||
set
|
||||
{
|
||||
if (_removeForeignAudioAndSubtitles == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_removeForeignAudioAndSubtitles = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<ConversionProfilePresetRow> Profiles { get; }
|
||||
|
||||
public ConversionProfilePresetRow? SelectedProfile
|
||||
{
|
||||
get => _selectedProfile;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(_selectedProfile, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedProfile = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand AddCommand { get; }
|
||||
public RelayCommand CancelCommand { get; }
|
||||
|
||||
private void ExecuteAdd()
|
||||
{
|
||||
_onAdd(
|
||||
new AddFilesOptions
|
||||
{
|
||||
Profile = string.IsNullOrWhiteSpace(_selectedProfile?.Profile) ? "Emby" : _selectedProfile.Profile,
|
||||
DisableSubtitleDefault = _disableSubtitleDefault,
|
||||
RemoveForeignAudioAndSubtitles = _removeForeignAudioAndSubtitles
|
||||
});
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string? name = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
357
EmbyToolbox/ViewModels/AddFilesSettingsViewModel.cs
Normal file
357
EmbyToolbox/ViewModels/AddFilesSettingsViewModel.cs
Normal file
@ -0,0 +1,357 @@
|
||||
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; }
|
||||
}
|
||||
@ -11,7 +11,8 @@ public sealed class ConversionFormOptions
|
||||
public List<string> PixelFormatOptions { get; } = ["yuv420p", "yuv420p10le", "yuv422p", "yuv444p"];
|
||||
public List<string> ResolutionOptions { get; } = ["Без изменений", "Максимум 2160p", "Максимум 1440p", "Максимум 1080p", "Максимум 720p"];
|
||||
public List<string> FpsOptions { get; } = ["Без изменений", "Максимум 60", "Максимум 30", "Максимум 25", "Максимум 24"];
|
||||
public List<string> AudioBitrateKbps { get; } = ["128 kbps", "192 kbps", "256 kbps", "320 kbps"];
|
||||
public List<string> AudioCodecOptions { get; } = ["AAC", "AC3", "EAC3", "Opus", "MP3", "FLAC", "Copy"];
|
||||
public List<string> AudioBitrateKbps { get; } = ["96 kbps", "128 kbps", "160 kbps", "192 kbps", "256 kbps", "320 kbps"];
|
||||
public List<string> VideoBitrateModeOptions { get; } = VideoBitratePolicy.UiOptions.ToList();
|
||||
|
||||
internal void RestoreListsFromSerialized(IReadOnlyList<string>? containers,
|
||||
|
||||
@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
@ -44,6 +45,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
private bool _copyPreviousTrackSettings;
|
||||
private bool _disableSubtitleDefault;
|
||||
private bool _removeForeignTracksByDefault;
|
||||
private AddFilesSettingsState? _lastAddFilesSettingsState;
|
||||
private ConversionProfilePresetRow? _selectedDefaultProfile;
|
||||
private bool _isQueueDropHighlight;
|
||||
private bool _isExecutionRunning;
|
||||
@ -611,7 +613,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
return;
|
||||
}
|
||||
|
||||
var prof = _profile.GetProfile(item.Profile) ?? ConversionProfileMapping.EmbyFallback;
|
||||
var prof = ResolveEffectiveProfile(item);
|
||||
TrackOverrideSeeder.SyncTargetFieldsFromProfile(item.TaskOverride, prof);
|
||||
var plan = _planService.Build(item.MediaAnalysis, item.Sidecars, prof, item.TaskOverride, item.ExternalAudioFiles);
|
||||
item.SetPlan(plan);
|
||||
@ -630,7 +632,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
&& (string.Equals(item.Status, ConversionQueueStatus.Pending, StringComparison.Ordinal)
|
||||
|| string.Equals(item.Status, ConversionQueueStatus.Ready, StringComparison.Ordinal)))
|
||||
{
|
||||
var prof = _profile.GetProfile(item.Profile) ?? ConversionProfileMapping.EmbyFallback;
|
||||
var prof = ResolveEffectiveProfile(item);
|
||||
// Keep per-file manual overrides intact; only untouched tasks follow updated profile targets.
|
||||
if (!item.IsManuallyEdited)
|
||||
{
|
||||
@ -890,7 +892,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
var affected = 0;
|
||||
foreach (var item in analysis.MajorityItems)
|
||||
{
|
||||
var prof = _profile.GetProfile(item.Profile) ?? ConversionProfileMapping.EmbyFallback;
|
||||
var prof = ResolveEffectiveProfile(item);
|
||||
var plan = _planService.Build(item.MediaAnalysis!, item.Sidecars, prof, item.TaskOverride, item.ExternalAudioFiles);
|
||||
item.IsManuallyEdited = true;
|
||||
item.SetPlan(plan);
|
||||
@ -1378,7 +1380,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
t.FfprobeAudioSizeMb,
|
||||
t.FfprobeAudioSizePartial);
|
||||
|
||||
var prof = _profile.GetProfile(item.Profile) ?? ConversionProfileMapping.EmbyFallback;
|
||||
var prof = ResolveEffectiveProfile(item);
|
||||
var plan = _planService.Build(item.MediaAnalysis!, sidecars, prof, item.TaskOverride, ext);
|
||||
item.SetPlan(plan);
|
||||
item.Status = NormalizeLoadedExecutionStatus(t.Status);
|
||||
@ -1556,6 +1558,15 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
}
|
||||
|
||||
var profile = string.IsNullOrWhiteSpace(addOptions.Profile) ? "Emby" : addOptions.Profile.Trim();
|
||||
var effectiveSettings = addOptions.EffectiveSettings.Profile.Profile.Length > 0
|
||||
? addOptions.EffectiveSettings
|
||||
: new EffectiveProfileSettings
|
||||
{
|
||||
SourceProfileName = profile,
|
||||
Profile = _profile.GetProfile(profile) ?? ConversionProfileMapping.EmbyFallback,
|
||||
ChangedFields = []
|
||||
};
|
||||
LogAddFilesEffectiveSettings(effectiveSettings, addOptions);
|
||||
var added = 0;
|
||||
var dups = 0;
|
||||
var newBatch = new List<ConversionQueueItem>();
|
||||
@ -1600,8 +1611,10 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
Status = ConversionQueueStatus.Analyzing,
|
||||
Progress = 0,
|
||||
Profile = profile,
|
||||
EffectiveProfileSettings = effectiveSettings,
|
||||
PlanSummary = "Анализ…"
|
||||
};
|
||||
TrackOverrideSeeder.SyncTargetFieldsFromProfile(item.TaskOverride, effectiveSettings.Profile);
|
||||
QueueTasks.Add(item);
|
||||
newBatch.Add(item);
|
||||
added++;
|
||||
@ -1624,6 +1637,27 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
private string CurrentProfileNameForNewTasks() =>
|
||||
string.IsNullOrWhiteSpace(_defaultQueueProfile) ? "Emby" : _defaultQueueProfile;
|
||||
|
||||
private ConversionProfileSettingsEntry ResolveEffectiveProfile(ConversionQueueItem item) =>
|
||||
item.EffectiveProfileSettings?.Profile
|
||||
?? _profile.GetProfile(item.Profile)
|
||||
?? ConversionProfileMapping.EmbyFallback;
|
||||
|
||||
private void LogAddFilesEffectiveSettings(EffectiveProfileSettings effectiveSettings, AddFilesOptions addOptions)
|
||||
{
|
||||
var p = effectiveSettings.Profile;
|
||||
var changed = effectiveSettings.ChangedFields.Count == 0
|
||||
? "none"
|
||||
: string.Join(", ", effectiveSettings.ChangedFields);
|
||||
_logging.Info(
|
||||
"AddFiles effective settings:" + Environment.NewLine
|
||||
+ $"Profile={effectiveSettings.SourceProfileName}" + Environment.NewLine
|
||||
+ $"Overrides={changed}" + Environment.NewLine
|
||||
+ $"Container={p.Container}; Video={p.Video}; PixelFormat={p.PixelFormat}; Resolution={p.Resolution}; FPS={p.Fps}; VideoBitrate={p.VideoBitrateMode}; VideoBitrateMbps={p.VideoBitrateMbps?.ToString("0.###", CultureInfo.InvariantCulture) ?? "-"}" + Environment.NewLine
|
||||
+ $"Audio={p.Audio}; AudioBitrate={p.Bitrate}; Subtitles={p.Subtitles}; ExternalAudio={p.ExternalTracks}; ExternalSubtitles={p.ExternalSubtitles}; ExternalFonts={p.Fonts}" + Environment.NewLine
|
||||
+ $"ForeignTracks={(addOptions.RemoveForeignAudioAndSubtitles ? "Remove" : "Keep")}; DisableSubtitleDefault={addOptions.DisableSubtitleDefault}",
|
||||
"conversion.queue");
|
||||
}
|
||||
|
||||
private void RenumberQueue()
|
||||
{
|
||||
for (var i = 0; i < QueueTasks.Count; i++)
|
||||
@ -1769,8 +1803,10 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
};
|
||||
|
||||
AddFilesOptions? selected = null;
|
||||
var vm = new AddFilesOptionsViewModel(
|
||||
var vm = new AddFilesSettingsViewModel(
|
||||
_presetRowsForSetup(),
|
||||
FormOptions,
|
||||
_lastAddFilesSettingsState,
|
||||
CurrentProfileNameForNewTasks(),
|
||||
DisableSubtitleDefault,
|
||||
RemoveForeignTracksByDefault,
|
||||
@ -1780,6 +1816,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
_defaultQueueProfile = options.Profile;
|
||||
DisableSubtitleDefault = options.DisableSubtitleDefault;
|
||||
RemoveForeignTracksByDefault = options.RemoveForeignAudioAndSubtitles;
|
||||
_lastAddFilesSettingsState = ((AddFilesSettingsViewModel)dialog.DataContext).CaptureState();
|
||||
SyncDefaultProfileFromList(_presetRowsForSetup());
|
||||
dialog.DialogResult = true;
|
||||
dialog.Close();
|
||||
@ -2151,7 +2188,7 @@ public sealed class ConversionViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
var profile = _profile.GetProfile(item.Profile) ?? ConversionProfileMapping.EmbyFallback;
|
||||
var profile = ResolveEffectiveProfile(item);
|
||||
var plan = _planService.Build(item.MediaAnalysis, item.Sidecars, profile, item.TaskOverride, item.ExternalAudioFiles);
|
||||
item.SetPlan(plan);
|
||||
}
|
||||
|
||||
@ -1,96 +1,182 @@
|
||||
<Window x:Class="EmbyToolbox.Views.AddFilesOptionsDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:EmbyToolbox.Converters"
|
||||
Title="Параметры добавления"
|
||||
Width="520"
|
||||
Height="230"
|
||||
ResizeMode="NoResize"
|
||||
Width="620"
|
||||
Height="430"
|
||||
MinWidth="600"
|
||||
MinHeight="390"
|
||||
ResizeMode="CanResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource Ui.Brush.Surface}">
|
||||
<Grid Margin="14">
|
||||
<Window.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||
<Style x:Key="AddSettingsLabel" TargetType="TextBlock" BasedOn="{StaticResource UiTextCaption}">
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Margin" Value="0,0,6,3" />
|
||||
</Style>
|
||||
<Style x:Key="AddSettingsCombo" TargetType="ComboBox" BasedOn="{StaticResource UiCombo}">
|
||||
<Setter Property="Height" Value="26" />
|
||||
<Setter Property="MinHeight" Value="26" />
|
||||
<Setter Property="Margin" Value="0,0,8,6" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style x:Key="AddSettingsTextBox" TargetType="TextBox" BasedOn="{StaticResource UiTextInput}">
|
||||
<Setter Property="Height" Value="26" />
|
||||
<Setter Property="MinHeight" Value="26" />
|
||||
<Setter Property="Margin" Value="0,0,8,6" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style x:Key="AddSettingsGroup" TargetType="GroupBox">
|
||||
<Setter Property="Padding" Value="6,4,6,4" />
|
||||
<Setter Property="Margin" Value="0,0,0,6" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Ui.Brush.BorderSubtle}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="16" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid Grid.Row="0" Margin="0,0,0,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="220" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="Профиль"
|
||||
Style="{StaticResource UiTextCaption}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,12,0" />
|
||||
<TextBlock Grid.Column="0" Text="Профиль:" Style="{StaticResource AddSettingsLabel}" Margin="0,0,8,0" />
|
||||
<ComboBox Grid.Column="1"
|
||||
Height="32"
|
||||
Style="{StaticResource UiCombo}"
|
||||
Style="{StaticResource AddSettingsCombo}"
|
||||
Margin="0"
|
||||
ItemsSource="{Binding Profiles}"
|
||||
SelectedItem="{Binding SelectedProfile, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Profile, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Text="{Binding Profile}" TextTrimming="CharacterEllipsis" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2"
|
||||
Height="32"
|
||||
VerticalAlignment="Top">
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel>
|
||||
<GroupBox Header="Видео" Style="{StaticResource AddSettingsGroup}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="18" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="24" />
|
||||
<ColumnDefinition Width="18" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<CheckBox Grid.Column="0"
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="0"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding DisableSubtitleDefault, Mode=TwoWay}" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="Отключать субтитры"
|
||||
VerticalAlignment="Center"
|
||||
Margin="10,0,0,0"
|
||||
FontSize="12" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Контейнер" Style="{StaticResource AddSettingsLabel}" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="Видео codec" Style="{StaticResource AddSettingsLabel}" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Text="Pixel format" Style="{StaticResource AddSettingsLabel}" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="0" Style="{StaticResource AddSettingsCombo}" ItemsSource="{Binding ContainerOptions}" SelectedItem="{Binding Container, Mode=TwoWay}" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" Style="{StaticResource AddSettingsCombo}" ItemsSource="{Binding VideoCodecOptions}" SelectedItem="{Binding Video, Mode=TwoWay}" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="2" Style="{StaticResource AddSettingsCombo}" ItemsSource="{Binding PixelFormatOptions}" SelectedItem="{Binding PixelFormat, Mode=TwoWay}" />
|
||||
|
||||
<CheckBox Grid.Column="3"
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="0"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding RemoveForeignAudioAndSubtitles, Mode=TwoWay}" />
|
||||
<TextBlock Grid.Column="4"
|
||||
Text="Удалять иностранные дорожки"
|
||||
VerticalAlignment="Center"
|
||||
Margin="10,0,0,0"
|
||||
FontSize="12" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Resolution" Style="{StaticResource AddSettingsLabel}" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="FPS" Style="{StaticResource AddSettingsLabel}" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="2" Text="Видеобитрейт" Style="{StaticResource AddSettingsLabel}" />
|
||||
<ComboBox Grid.Row="3" Grid.Column="0" Style="{StaticResource AddSettingsCombo}" ItemsSource="{Binding ResolutionOptions}" SelectedItem="{Binding Resolution, Mode=TwoWay}" />
|
||||
<ComboBox Grid.Row="3" Grid.Column="1" Style="{StaticResource AddSettingsCombo}" ItemsSource="{Binding FpsOptions}" SelectedItem="{Binding Fps, Mode=TwoWay}" />
|
||||
<Grid Grid.Row="3" Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ComboBox Grid.Row="0" Style="{StaticResource AddSettingsCombo}" ItemsSource="{Binding VideoBitrateOptions}" SelectedItem="{Binding VideoBitrateMode, Mode=TwoWay}" />
|
||||
<Grid Grid.Row="1" Visibility="{Binding IsVideoBitrateCustomVisible, Converter={StaticResource BoolToVis}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="90" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="Видеобитрейт (Mbps)"
|
||||
Style="{StaticResource AddSettingsLabel}" />
|
||||
<TextBox Grid.Column="1"
|
||||
Style="{StaticResource AddSettingsTextBox}"
|
||||
Text="{Binding VideoBitrateCustomMbps, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
|
||||
<Button MinWidth="100"
|
||||
<GroupBox Header="Аудио" Style="{StaticResource AddSettingsGroup}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Audio codec" Style="{StaticResource AddSettingsLabel}" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="Audio bitrate" Style="{StaticResource AddSettingsLabel}" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="0" Style="{StaticResource AddSettingsCombo}" ItemsSource="{Binding AudioCodecOptions}" SelectedItem="{Binding Audio, Mode=TwoWay}" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" Style="{StaticResource AddSettingsCombo}" ItemsSource="{Binding AudioBitrateOptions}" SelectedItem="{Binding AudioBitrate, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="8" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<GroupBox Grid.Column="0" Header="Внешние дорожки" Style="{StaticResource AddSettingsGroup}">
|
||||
<WrapPanel>
|
||||
<CheckBox Content="Внешние аудио" IsChecked="{Binding ExternalTracks, Mode=TwoWay}" Margin="0,0,14,4" />
|
||||
<CheckBox Content="Внешние субтитры" IsChecked="{Binding ExternalSubtitles, Mode=TwoWay}" Margin="0,0,14,4" />
|
||||
<CheckBox Content="Внешние шрифты" IsChecked="{Binding Fonts, Mode=TwoWay}" Margin="0,0,14,4" />
|
||||
</WrapPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Grid.Column="2" Header="Прочие настройки" Style="{StaticResource AddSettingsGroup}">
|
||||
<WrapPanel>
|
||||
<CheckBox Content="Субтитры" IsChecked="{Binding Subtitles, Mode=TwoWay}" Margin="0,0,14,4" />
|
||||
<CheckBox Content="Удалять иностранные дорожки" IsChecked="{Binding RemoveForeignTracks, Mode=TwoWay}" Margin="0,0,14,4" />
|
||||
<CheckBox Content="Отключать субтитры" IsChecked="{Binding DisableSubtitleDefault, Mode=TwoWay}" Margin="0,0,14,4" />
|
||||
</WrapPanel>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid Grid.Row="2" Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding ValidationMessage}"
|
||||
Foreground="{DynamicResource Ui.Brush.ErrorText}"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding HasValidationMessage, Converter={StaticResource BoolToVis}}" />
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Button MinWidth="96"
|
||||
Margin="0,0,8,0"
|
||||
Style="{StaticResource UiButtonPrimary}"
|
||||
Foreground="White"
|
||||
Content="Добавить"
|
||||
IsDefault="True"
|
||||
Command="{Binding AddCommand}" />
|
||||
<Button MinWidth="100"
|
||||
<Button MinWidth="96"
|
||||
Style="{StaticResource UiButtonSecondary}"
|
||||
Content="Отмена"
|
||||
IsCancel="True"
|
||||
Command="{Binding CancelCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user