282 lines
10 KiB
C#
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
|
|
};
|
|
}
|