using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using EmbyToolbox.Models; namespace EmbyToolbox.Services; /// Сохранение и загрузка очереди конвертации (.conv_setup, JSON). public static class ConversionQueueSetupPersistence { public const string FileExtension = ".conv_setup"; private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } }; public static string GetQueueSetupsDirectory() { var dir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "EmbyToolbox", "QueueSetups"); Directory.CreateDirectory(dir); return dir; } public static string AllocateAutoSavePath() { var stamp = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss", System.Globalization.CultureInfo.InvariantCulture); return Path.Combine(GetQueueSetupsDirectory(), $"conversion-setup-{stamp}{FileExtension}"); } public static void SaveToPath(string path, ConversionQueueSetupRoot document) { var dir = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(dir)) { Directory.CreateDirectory(dir); } var json = JsonSerializer.Serialize(document, JsonOptions); File.WriteAllText(path, json, System.Text.UTF8Encoding.UTF8); } public static ConversionQueueSetupRoot LoadFromPath(string path) { var json = File.ReadAllText(path, System.Text.UTF8Encoding.UTF8); var doc = JsonSerializer.Deserialize(json, JsonOptions) ?? throw new InvalidDataException("Пустой или некорректный .conv_setup"); return doc; } } public sealed class ConversionQueueSetupRoot { public int SchemaVersion { get; set; } = 1; public DateTime SavedAtUtc { get; set; } public string? DefaultQueueProfile { get; set; } public bool CopyPreviousTrackSettings { get; set; } public bool DisableSubtitleDefault { get; set; } public ConversionFormOptionsSnapshot FormOptions { get; set; } = new(); public List Profiles { get; set; } = []; public List Tasks { get; set; } = []; } public sealed class ConversionFormOptionsSnapshot { public List ContainerOptions { get; set; } = []; public List VideoCodecOptions { get; set; } = []; public List PixelFormatOptions { get; set; } = []; public List ResolutionOptions { get; set; } = []; public List FpsOptions { get; set; } = []; public List AudioBitrateKbps { get; set; } = []; public List VideoBitrateModeOptions { get; set; } = []; } public sealed class ConversionQueueTaskPersistModel { public string FullPath { get; set; } = ""; public string? SnapshotScopeBatchRoot { get; set; } public int OrderNumber { get; set; } public string Profile { get; set; } = "Emby"; public string PlanSummary { get; set; } = ""; public string Status { get; set; } = ConversionQueueStatus.Pending; public int Progress { get; set; } public bool IsManuallyEdited { get; set; } public bool IsProcessed { get; set; } public bool ProcessedInCurrentRun { get; set; } public string? LastRunId { get; set; } public string? ErrorMessage { get; set; } public string? ErrorDetails { get; set; } public int FileSizeMb { get; set; } public bool HasFfprobeAudioSummary { get; set; } public int FfprobeAudioCount { get; set; } public int? FfprobeAudioSizeMb { get; set; } public bool FfprobeAudioSizePartial { get; set; } public MediaAnalysisResult? MediaAnalysis { get; set; } public List? Sidecars { get; set; } public List? ExternalAudioFiles { get; set; } public ConversionTaskOverridePersistModel? Overrides { get; set; } } public sealed class SidecarFilePersistModel { public string FullPath { get; set; } = ""; public bool IsAudio { get; set; } public bool IsSubtitle { get; set; } public bool IsFont { get; set; } public static SidecarFilePersistModel From(SidecarFile s) => new() { FullPath = s.FullPath, IsAudio = s.IsAudio, IsSubtitle = s.IsSubtitle, IsFont = s.IsFont }; public SidecarFile ToModel() => new(FullPath, IsAudio, IsSubtitle, IsFont); } public sealed class ExternalAudioStreamPersistModel { public string FileFullPath { get; set; } = ""; public int StreamOrdinal { get; set; } public string CodecName { get; set; } = "?"; public string? TitleFromProbe { get; set; } public int? Channels { get; set; } public int? SampleRateHz { get; set; } public long? BitRateBps { get; set; } public static ExternalAudioStreamPersistModel From(ExternalAudioStream s) => new() { FileFullPath = s.FileFullPath, StreamOrdinal = s.StreamOrdinal, CodecName = s.CodecName, TitleFromProbe = s.TitleFromProbe, Channels = s.Channels, SampleRateHz = s.SampleRateHz, BitRateBps = s.BitRateBps }; public ExternalAudioStream ToModel() => new() { FileFullPath = FileFullPath, StreamOrdinal = StreamOrdinal, CodecName = CodecName, TitleFromProbe = TitleFromProbe, Channels = Channels, SampleRateHz = SampleRateHz, BitRateBps = BitRateBps }; } public sealed class ExternalAudioFilePersistModel { public string FullPath { get; set; } = ""; public List Streams { get; set; } = []; public static ExternalAudioFilePersistModel From(ExternalAudioFile f) => new() { FullPath = f.FullPath, Streams = f.Streams.Select(ExternalAudioStreamPersistModel.From).ToList() }; public ExternalAudioFile ToModel() => new(FullPath, Streams.Select(s => s.ToModel()).ToList()); } public sealed class ConversionTaskOverridePersistModel { public string TargetContainer { get; set; } = ""; public string TargetVideo { get; set; } = ""; public string TargetPixelFormat { get; set; } = ""; public string TargetResolution { get; set; } = ""; public string TargetFps { get; set; } = ""; public string TargetAudioBitrate { get; set; } = "256 kbps"; public string TargetVideoBitrateMode { get; set; } = VideoBitratePolicy.Auto; public double? TargetVideoBitrateMbps { get; set; } public List TrackOverrides { get; set; } = []; public static ConversionTaskOverridePersistModel From(ConversionTaskOverride o) { var m = new ConversionTaskOverridePersistModel { TargetContainer = o.TargetContainer, TargetVideo = o.TargetVideo, TargetPixelFormat = o.TargetPixelFormat, TargetResolution = o.TargetResolution, TargetFps = o.TargetFps, TargetAudioBitrate = o.TargetAudioBitrate, TargetVideoBitrateMode = o.TargetVideoBitrateMode, TargetVideoBitrateMbps = o.TargetVideoBitrateMbps }; foreach (var t in o.TrackOverrides) { m.TrackOverrides.Add(TrackOverrideEntryPersistModel.From(t)); } return m; } public void ApplyTo(ConversionTaskOverride target) { target.TargetContainer = TargetContainer; target.TargetVideo = TargetVideo; target.TargetPixelFormat = TargetPixelFormat; target.TargetResolution = TargetResolution; target.TargetFps = TargetFps; target.TargetAudioBitrate = TargetAudioBitrate; target.TargetVideoBitrateMode = TargetVideoBitrateMode; target.TargetVideoBitrateMbps = TargetVideoBitrateMbps; target.TrackOverrides.Clear(); foreach (var t in TrackOverrides) { target.TrackOverrides.Add(t.ToEntry()); } } } public sealed class TrackOverrideEntryPersistModel { public int StreamIndex { get; set; } public string? ExternalPath { get; set; } public SourceKind Source { get; set; } public MediaStreamKind StreamKind { get; set; } public TrackActionKind Action { get; set; } public bool? Default { get; set; } public string? Language { get; set; } public string? Title { get; set; } public string? AudioBitrateKbps { get; set; } public int ExternalAudioStreamOrdinal { get; set; } public string? ExternalStreamCodec { get; set; } public string? ExternalStreamDetails { get; set; } public int SameFileExternalAudioStreamCount { get; set; } = 1; public string? ExternalFfprobeTitle { get; set; } public static TrackOverrideEntryPersistModel From(TrackOverrideEntry t) => new() { StreamIndex = t.StreamIndex, ExternalPath = t.ExternalPath, Source = t.Source, StreamKind = t.StreamKind, Action = t.Action, Default = t.Default, Language = t.Language, Title = t.Title, AudioBitrateKbps = t.AudioBitrateKbps, ExternalAudioStreamOrdinal = t.ExternalAudioStreamOrdinal, ExternalStreamCodec = t.ExternalStreamCodec, ExternalStreamDetails = t.ExternalStreamDetails, SameFileExternalAudioStreamCount = t.SameFileExternalAudioStreamCount, ExternalFfprobeTitle = t.ExternalFfprobeTitle }; public TrackOverrideEntry ToEntry() => new() { StreamIndex = StreamIndex, ExternalPath = ExternalPath, Source = Source, StreamKind = StreamKind, Action = Action, Default = Default, Language = Language, Title = Title, AudioBitrateKbps = AudioBitrateKbps, ExternalAudioStreamOrdinal = ExternalAudioStreamOrdinal, ExternalStreamCodec = ExternalStreamCodec, ExternalStreamDetails = ExternalStreamDetails, SameFileExternalAudioStreamCount = SameFileExternalAudioStreamCount, ExternalFfprobeTitle = ExternalFfprobeTitle }; }