emby-toolbox/EmbyToolbox/Services/ConversionQueueSetupPersistence.cs
Emby Toolbox 6264b487fe Initial commit: Emby Toolbox (conversion scroll fix, bulk Del for tracks).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:33:47 +05:00

282 lines
10 KiB
C#

using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using EmbyToolbox.Models;
namespace EmbyToolbox.Services;
/// <summary>Сохранение и загрузка очереди конвертации (.conv_setup, JSON).</summary>
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<ConversionQueueSetupRoot>(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<ConversionProfileSettingsEntry> Profiles { get; set; } = [];
public List<ConversionQueueTaskPersistModel> Tasks { get; set; } = [];
}
public sealed class ConversionFormOptionsSnapshot
{
public List<string> ContainerOptions { get; set; } = [];
public List<string> VideoCodecOptions { get; set; } = [];
public List<string> PixelFormatOptions { get; set; } = [];
public List<string> ResolutionOptions { get; set; } = [];
public List<string> FpsOptions { get; set; } = [];
public List<string> AudioBitrateKbps { get; set; } = [];
public List<string> 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<SidecarFilePersistModel>? Sidecars { get; set; }
public List<ExternalAudioFilePersistModel>? 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<ExternalAudioStreamPersistModel> 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<TrackOverrideEntryPersistModel> 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
};
}