From 7febed2d49cd53a0915b2c3275a6d6cc65d3d927 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Tue, 25 Oct 2022 23:50:35 +0800 Subject: [PATCH 01/15] Support set audio and video encode format when merge into mkv --- src/Mergers/FFMPEG.cs | 9 ++++++ src/Mergers/Merger.cs | 2 ++ src/Program.cs | 66 +++++++++++++++++++++++++++++-------------- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/Mergers/FFMPEG.cs b/src/Mergers/FFMPEG.cs index 5e7b067..5b037e0 100644 --- a/src/Mergers/FFMPEG.cs +++ b/src/Mergers/FFMPEG.cs @@ -107,5 +107,14 @@ public void Merge() Process process = Process.Start(_ffmpeg, _command); process.WaitForExit(); } + + public void Merge(string audioFormat, string videoFormat) + { + _command += string.Join(" ", _inputOptions) + string.Join(" ", _mapOptions) + string.Join(" ", _metadataOptions); + _command += $" -c:a \"{(string.IsNullOrWhiteSpace(audioFormat) ? "copy" : audioFormat)}\" -c:v \"{(string.IsNullOrWhiteSpace(videoFormat) ? "copy" : videoFormat)}\" \"{_output}\""; + //Console.WriteLine(_ffmpeg + _command); + Process process = Process.Start(_ffmpeg, _command); + process.WaitForExit(); + } } } diff --git a/src/Mergers/Merger.cs b/src/Mergers/Merger.cs index 494fa2f..a9cc8b6 100644 --- a/src/Mergers/Merger.cs +++ b/src/Mergers/Merger.cs @@ -13,5 +13,7 @@ internal interface Merger void Merge(); + void Merge(string audioFormat, string videoFormat) { } + } } diff --git a/src/Program.cs b/src/Program.cs index b827144..2490816 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -21,7 +21,7 @@ internal sealed class Program [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Retrieves the configuration file")] private static Task<int> Main(string[] args) { - + // Loading config file // TODO: A LA MANO ? IConfiguration config = new ConfigurationBuilder() @@ -86,9 +86,18 @@ private static Task<int> Main(string[] args) mkvEngineOption.SetDefaultValue("internal"); mkvEngineOption.AddAlias("-e"); + var audioFormatOption = new Option<string>( + name: "--audio-format", + description: "Audio encode format in MKV file, the original is PCM."); + audioFormatOption.AddAlias("-af"); + + var videoFormatOption = new Option<string>( + name: "--video-format", + description: "Video encode format in MKV file, the original is VP9."); + videoFormatOption.AddAlias("-vf"); var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact."); - + rootCommand.AddGlobalOption(outputFolderOption); rootCommand.AddGlobalOption(noCleanupOption); @@ -99,15 +108,19 @@ private static Task<int> Main(string[] args) key2Option, subsOption, mergeOption, - mkvEngineOption + mkvEngineOption, + audioFormatOption, + videoFormatOption, }; var batchDemuxCommand = new Command("batchDemux", "Tries to demux all .usm files in the specified folder") - { + { usmFolderArg, subsOption, mergeOption, - mkvEngineOption + mkvEngineOption, + audioFormatOption, + videoFormatOption, }; //var hcaDecrypt = new Command(); @@ -123,37 +136,37 @@ private static Task<int> Main(string[] args) // Command Handlers - demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => + demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup, string audioFormat, string videoFormat) => { - await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup); - }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); + await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup, audioFormat, videoFormat); + }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption, audioFormatOption, videoFormatOption); - batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) => + batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup, string audioFormat, string videoFormat) => { var timer = System.Diagnostics.Stopwatch.StartNew(); - await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup); + await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup, audioFormat, videoFormat); timer.Stop(); Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed"); }, - usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); + usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption, audioFormatOption, videoFormatOption); convertHcaCommand.SetHandler(async (FileSystemInfo input, DirectoryInfo? output, bool noCleanup) => { await ConvertHcaCommand(input, output /*, noCleanup*/); - }, + }, hcaInputArg, outputFolderOption, noCleanupOption); return Task.FromResult(rootCommand.InvokeAsync(args).Result); } - private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) + private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup, string audioFormat, string videoFormat) { if (file == null) throw new ArgumentNullException("No file provided."); if (!file.Exists) throw new ArgumentException("File {0} does not exist.", file.Name); if (!file.Name.EndsWith(".usm")) throw new ArgumentException($"File {file.Name} provided isn't a .usm file."); - if (key1!=null && key2!= null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid."); + if (key1 != null && key2 != null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid."); string outputArg = output == null ? file.Directory.FullName : ((output.Exists) ? output.FullName : throw new ArgumentException("Output directory is invalid.")); @@ -163,12 +176,12 @@ private static async Task DemuxUsmCommand(FileInfo file, string key1, string key Demuxer.Demux(file.FullName, key1Arg, key2Arg, outputArg); if (merge) { - MergeFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName), engine, subs); + MergeFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName), engine, subs, audioFormat, videoFormat); if (!noCleanup) CleanFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName)); } } - private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) + private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup, string audioFormat, string videoFormat) { if (inputDir is not { Exists: true }) throw new DirectoryNotFoundException("Input directory is invalid."); string outputArg = (outputDir == null) @@ -180,7 +193,7 @@ private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInf Demuxer.Demux(f, Array.Empty<byte>(), Array.Empty<byte>(), outputArg); if (!merge) continue; - MergeFiles(outputArg, Path.GetFileNameWithoutExtension(f), engine, subs); + MergeFiles(outputArg, Path.GetFileNameWithoutExtension(f), engine, subs, audioFormat, videoFormat); if (!noCleanup) CleanFiles(outputArg, Path.GetFileNameWithoutExtension(f)); } } @@ -213,9 +226,13 @@ private static async Task ConvertHcaCommand(FileSystemInfo input, DirectoryInfo? } } - private static void MergeFiles(string outputPath, string basename, string engine, bool subs) + private static void MergeFiles(string outputPath, string basename, string engine, bool subs, string audioFormat, string videoFormat) { Merger merger; + if (!string.IsNullOrWhiteSpace(audioFormat) || !string.IsNullOrWhiteSpace(videoFormat)) + { + engine = "ffmpeg"; + } switch (engine) { case "internal": @@ -233,14 +250,14 @@ private static void MergeFiles(string outputPath, string basename, string engine case "ffmpeg": Console.WriteLine("Merging using ffmpeg."); merger = File.Exists( - Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "") + Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "") ? new FFMPEG(Path.Combine(outputPath, basename + ".mkv"), settings.FfmpegPath) : new FFMPEG(Path.Combine(outputPath, basename + ".mkv")); merger.AddVideoTrack(Path.Combine(outputPath, basename + ".ivf")); break; default: throw new ArgumentException("Not implemented"); } - + foreach (string f in Directory.EnumerateFiles(outputPath, $"{basename}_*.wav")) { if (!int.TryParse(Path.GetFileNameWithoutExtension(f)[^1..], out int language)) // Extracting language number from filename @@ -301,7 +318,14 @@ private static void MergeFiles(string outputPath, string basename, string engine else Console.WriteLine($"No subtitles found for cutscene {basename}"); } // Merging the file - merger.Merge(); + if (!string.IsNullOrWhiteSpace(audioFormat) || !string.IsNullOrWhiteSpace(videoFormat)) + { + merger.Merge(audioFormat, videoFormat); + } + else + { + merger.Merge(); + } } private static void CleanFiles(string outputPath, string basename) From deccf3a189f94345f8e2c1ba54008e1fddd8dd84 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Wed, 26 Oct 2022 21:29:32 +0800 Subject: [PATCH 02/15] Improve endian convert method --- src/Utils/Utils.cs | 93 +++++----------------------------------------- 1 file changed, 9 insertions(+), 84 deletions(-) diff --git a/src/Utils/Utils.cs b/src/Utils/Utils.cs index 1df5823..91235b5 100644 --- a/src/Utils/Utils.cs +++ b/src/Utils/Utils.cs @@ -1,114 +1,38 @@ +using System.Net; + namespace GICutscenes.Utils { internal class Tools { public static ushort Bswap(ushort v) { - if (!BitConverter.IsLittleEndian) return v; - ushort r = (ushort)(v & 0xFF); - r <<= 8; - v >>= 8; - r |= (ushort)(v & 0xFF); - return r; + return (ushort)IPAddress.HostToNetworkOrder((short)v); } public static short Bswap(short v) { - if (!BitConverter.IsLittleEndian) return v; - short r = (short)(v & 0xFF); - r <<= 8; - v >>= 8; - r |= (short)(v & 0xFF); - return r; + return IPAddress.HostToNetworkOrder(v); } public static int Bswap(int v) { - if (!BitConverter.IsLittleEndian) return v; - int r = v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - return r; + return IPAddress.HostToNetworkOrder(v); } public static uint Bswap(uint v) { - if (!BitConverter.IsLittleEndian) return v; - uint r = v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - return r; + return (uint)IPAddress.HostToNetworkOrder((int)v); } public static long Bswap(long v) { - if (!BitConverter.IsLittleEndian) return v; - long r = v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - return r; + return IPAddress.HostToNetworkOrder(v); } public static ulong Bswap(ulong v) { - if (!BitConverter.IsLittleEndian) return v; - ulong r = v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - r <<= 8; - v >>= 8; - r |= v & 0xFF; - return r; + return (ulong)IPAddress.HostToNetworkOrder((long)v); } public static float Bswap(float v) @@ -117,6 +41,7 @@ public static float Bswap(float v) uint i = Bswap(BitConverter.SingleToUInt32Bits(v)); return BitConverter.UInt32BitsToSingle(i); } + public static uint Ceil2(uint a, uint b) { return (uint)(b > 0 ? a / b + (a % b != 0 ? 1 : 0) : 0); } } } From 98102817da2303b20f77f3c4a8c213103ce49798 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Wed, 26 Oct 2022 22:08:39 +0800 Subject: [PATCH 03/15] Use absolute path --- src/Demuxer.cs | 7 ++++--- src/Program.cs | 18 +++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Demuxer.cs b/src/Demuxer.cs index e6751ca..b5d7913 100644 --- a/src/Demuxer.cs +++ b/src/Demuxer.cs @@ -45,9 +45,10 @@ private static ulong EncryptionKeyInFilename(string filename) private static (ulong, bool) EncryptionKeyInBLK(string videoFilename) { - if (!File.Exists("versions.json")) throw new FileNotFoundException("File versions.json couldn't be found in the folder of the tool."); + var versionsFilePath = Path.Combine(AppContext.BaseDirectory, "versions.json"); + if (!File.Exists(versionsFilePath)) throw new FileNotFoundException("File versions.json couldn't be found in the folder of the tool."); videoFilename = Path.GetFileNameWithoutExtension(videoFilename); - string jsonString = File.ReadAllText("versions.json"); + string jsonString = File.ReadAllText(versionsFilePath); VersionList? versions = JsonSerializer.Deserialize<VersionList>(jsonString, VersionJson.Default.VersionList); if (versions?.list == null) throw new JsonException("Json content from versions.json is invalid or couldn't be parsed..."); Version? v = Array.Find(versions.list, x => (x.videos != null && x.videos.Contains(videoFilename)) || (x.videoGroups != null && Array.Exists(x.videoGroups, y => y.videos != null && y.videos.Contains(videoFilename)))); @@ -56,7 +57,7 @@ private static (ulong, bool) EncryptionKeyInBLK(string videoFilename) if (v.videoGroups != null) { key = Array.Find(v.videoGroups, y => y.videos != null && y.videos.Contains(videoFilename))?.key ?? throw new KeyNotFoundException("Unable to find the second key in versions.json for " + videoFilename); - } + } return (key, v.encAudio ?? false); } diff --git a/src/Program.cs b/src/Program.cs index b827144..613066f 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -21,11 +21,11 @@ internal sealed class Program [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Retrieves the configuration file")] private static Task<int> Main(string[] args) { - + // Loading config file // TODO: A LA MANO ? IConfiguration config = new ConfigurationBuilder() - .AddJsonFile("appsettings.json") + .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json")) .AddEnvironmentVariables() .Build(); @@ -88,7 +88,7 @@ private static Task<int> Main(string[] args) var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact."); - + rootCommand.AddGlobalOption(outputFolderOption); rootCommand.AddGlobalOption(noCleanupOption); @@ -103,7 +103,7 @@ private static Task<int> Main(string[] args) }; var batchDemuxCommand = new Command("batchDemux", "Tries to demux all .usm files in the specified folder") - { + { usmFolderArg, subsOption, mergeOption, @@ -123,7 +123,7 @@ private static Task<int> Main(string[] args) // Command Handlers - demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => + demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => { await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup); }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); @@ -141,7 +141,7 @@ private static Task<int> Main(string[] args) convertHcaCommand.SetHandler(async (FileSystemInfo input, DirectoryInfo? output, bool noCleanup) => { await ConvertHcaCommand(input, output /*, noCleanup*/); - }, + }, hcaInputArg, outputFolderOption, noCleanupOption); return Task.FromResult(rootCommand.InvokeAsync(args).Result); @@ -153,7 +153,7 @@ private static async Task DemuxUsmCommand(FileInfo file, string key1, string key if (!file.Exists) throw new ArgumentException("File {0} does not exist.", file.Name); if (!file.Name.EndsWith(".usm")) throw new ArgumentException($"File {file.Name} provided isn't a .usm file."); - if (key1!=null && key2!= null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid."); + if (key1 != null && key2 != null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid."); string outputArg = output == null ? file.Directory.FullName : ((output.Exists) ? output.FullName : throw new ArgumentException("Output directory is invalid.")); @@ -233,14 +233,14 @@ private static void MergeFiles(string outputPath, string basename, string engine case "ffmpeg": Console.WriteLine("Merging using ffmpeg."); merger = File.Exists( - Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "") + Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "") ? new FFMPEG(Path.Combine(outputPath, basename + ".mkv"), settings.FfmpegPath) : new FFMPEG(Path.Combine(outputPath, basename + ".mkv")); merger.AddVideoTrack(Path.Combine(outputPath, basename + ".ivf")); break; default: throw new ArgumentException("Not implemented"); } - + foreach (string f in Directory.EnumerateFiles(outputPath, $"{basename}_*.wav")) { if (!int.TryParse(Path.GetFileNameWithoutExtension(f)[^1..], out int language)) // Extracting language number from filename From d3a30d0877adbf4c6423af857eaeaff5e099d76c Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Wed, 26 Oct 2022 22:54:26 +0800 Subject: [PATCH 04/15] Add command to reset appsettings.json --- src/Program.cs | 52 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 613066f..ba82e1e 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -4,6 +4,7 @@ using GICutscenes.Mergers; using GICutscenes.Mergers.GIMKV; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; namespace GICutscenes { @@ -16,21 +17,12 @@ internal sealed class Settings } internal sealed class Program { - public static Settings settings; + public static Settings? settings; [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Retrieves the configuration file")] private static Task<int> Main(string[] args) { - // Loading config file - // TODO: A LA MANO ? - IConfiguration config = new ConfigurationBuilder() - .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json")) - .AddEnvironmentVariables() - .Build(); - - settings = config.GetRequiredSection(nameof(Settings)).Get<Settings>(); - // CLI Options var demuxFileOption = new Argument<FileInfo?>( name: "Input file", @@ -92,6 +84,8 @@ private static Task<int> Main(string[] args) rootCommand.AddGlobalOption(outputFolderOption); rootCommand.AddGlobalOption(noCleanupOption); + var resetCommand = new Command("reset", "Reset 'appsettings.json' file to default."); + var demuxUsmCommand = new Command("demuxUsm", "Demuxes a specified .usm file to a specified folder") { demuxFileOption, @@ -117,20 +111,33 @@ private static Task<int> Main(string[] args) hcaInputArg }; + rootCommand.AddCommand(resetCommand); rootCommand.AddCommand(demuxUsmCommand); rootCommand.AddCommand(batchDemuxCommand); rootCommand.AddCommand(convertHcaCommand); + resetCommand.SetHandler(() => + { + var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } }; + var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), json); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("'appsettings.json' has reset to default."); + Console.ResetColor(); + }); + // Command Handlers demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => { + ReadSetting(); await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup); }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) => { + ReadSetting(); var timer = System.Diagnostics.Stopwatch.StartNew(); await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup); timer.Stop(); @@ -147,6 +154,31 @@ private static Task<int> Main(string[] args) return Task.FromResult(rootCommand.InvokeAsync(args).Result); } + + [RequiresUnreferencedCode("Calls Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>()")] + private static void ReadSetting() + { + if (settings is null) + { + // Loading config file + // TODO: A LA MANO ? + IConfiguration config = new ConfigurationBuilder() + .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json")) + .AddEnvironmentVariables() + .Build(); + + settings = config?.GetSection(nameof(Settings)).Get<Settings>(); + if (settings is null) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine("File 'appsettings.json' has error section, use command 'reset' to reset it to default."); + Console.ResetColor(); + Environment.Exit(1); + } + } + } + + private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) { if (file == null) throw new ArgumentNullException("No file provided."); From 7a77d34fa089e9812351aae2fb8f66b0a173a084 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Wed, 26 Oct 2022 22:58:44 +0800 Subject: [PATCH 05/15] null reference warning --- src/Program.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index ba82e1e..a7a0705 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -129,10 +129,10 @@ private static Task<int> Main(string[] args) // Command Handlers demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => - { + { ReadSetting(); - await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup); - }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); + await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup); + }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) => @@ -181,13 +181,13 @@ private static void ReadSetting() private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) { - if (file == null) throw new ArgumentNullException("No file provided."); + if (file == null) throw new ArgumentNullException(nameof(file), "No file provided."); if (!file.Exists) throw new ArgumentException("File {0} does not exist.", file.Name); if (!file.Name.EndsWith(".usm")) throw new ArgumentException($"File {file.Name} provided isn't a .usm file."); if (key1 != null && key2 != null && (key1.Length != 8 || key2.Length != 8)) throw new ArgumentException("Keys are invalid."); string outputArg = output == null - ? file.Directory.FullName + ? file.Directory!.FullName : ((output.Exists) ? output.FullName : throw new ArgumentException("Output directory is invalid.")); Console.WriteLine($"Output folder : {outputArg}"); byte[] key1Arg = Convert.FromHexString(key1 ?? ""); @@ -258,15 +258,15 @@ private static void MergeFiles(string outputPath, string basename, string engine case "mkvmerge": Console.WriteLine("Merging using mkvmerge."); merger = File.Exists( - Path.GetFileNameWithoutExtension(settings.MkvMergePath) == "mkvmerge" ? settings.MkvMergePath : "") - ? new Mkvmerge(Path.Combine(outputPath, basename + ".mkv"), settings.MkvMergePath) : new Mkvmerge(Path.Combine(outputPath, basename + ".mkv")); + Path.GetFileNameWithoutExtension(settings?.MkvMergePath)?.ToLower() == "mkvmerge" ? settings?.MkvMergePath : "") + ? new Mkvmerge(Path.Combine(outputPath, basename + ".mkv"), settings!.MkvMergePath!) : new Mkvmerge(Path.Combine(outputPath, basename + ".mkv")); merger.AddVideoTrack(Path.Combine(outputPath, basename + ".ivf")); break; case "ffmpeg": Console.WriteLine("Merging using ffmpeg."); merger = File.Exists( - Path.GetFileNameWithoutExtension(settings.FfmpegPath) == "ffmpeg" ? settings.FfmpegPath : "") - ? new FFMPEG(Path.Combine(outputPath, basename + ".mkv"), settings.FfmpegPath) : new FFMPEG(Path.Combine(outputPath, basename + ".mkv")); + Path.GetFileNameWithoutExtension(settings?.FfmpegPath)?.ToLower() == "ffmpeg" ? settings?.FfmpegPath : "") + ? new FFMPEG(Path.Combine(outputPath, basename + ".mkv"), settings!.FfmpegPath!) : new FFMPEG(Path.Combine(outputPath, basename + ".mkv")); merger.AddVideoTrack(Path.Combine(outputPath, basename + ".ivf")); break; default: @@ -282,7 +282,7 @@ private static void MergeFiles(string outputPath, string basename, string engine if (subs) { - string subsFolder = settings.SubsFolder ?? throw new ArgumentException("Configuration value is not set for the key SubsFolder."); + string subsFolder = settings?.SubsFolder ?? throw new ArgumentException("Configuration value is not set for the key SubsFolder."); subsFolder = Path.GetFullPath(subsFolder); if (!Directory.Exists(subsFolder)) throw new ArgumentException( From 734f3978f806db21236b6f638f7f933e20e04b2c Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Wed, 26 Oct 2022 23:12:10 +0800 Subject: [PATCH 06/15] Change command order --- src/Program.cs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index a7a0705..c646092 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -84,8 +84,6 @@ private static Task<int> Main(string[] args) rootCommand.AddGlobalOption(outputFolderOption); rootCommand.AddGlobalOption(noCleanupOption); - var resetCommand = new Command("reset", "Reset 'appsettings.json' file to default."); - var demuxUsmCommand = new Command("demuxUsm", "Demuxes a specified .usm file to a specified folder") { demuxFileOption, @@ -111,20 +109,12 @@ private static Task<int> Main(string[] args) hcaInputArg }; - rootCommand.AddCommand(resetCommand); + var resetCommand = new Command("reset", "Reset 'appsettings.json' file to default."); + rootCommand.AddCommand(demuxUsmCommand); rootCommand.AddCommand(batchDemuxCommand); rootCommand.AddCommand(convertHcaCommand); - - resetCommand.SetHandler(() => - { - var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } }; - var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true }); - File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), json); - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("'appsettings.json' has reset to default."); - Console.ResetColor(); - }); + rootCommand.AddCommand(resetCommand); // Command Handlers @@ -142,14 +132,22 @@ private static Task<int> Main(string[] args) await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup); timer.Stop(); Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed"); - }, - usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); + }, usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); convertHcaCommand.SetHandler(async (FileSystemInfo input, DirectoryInfo? output, bool noCleanup) => { await ConvertHcaCommand(input, output /*, noCleanup*/); - }, - hcaInputArg, outputFolderOption, noCleanupOption); + }, hcaInputArg, outputFolderOption, noCleanupOption); + + resetCommand.SetHandler(() => + { + var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } }; + var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), json); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("'appsettings.json' has reset to default."); + Console.ResetColor(); + }); return Task.FromResult(rootCommand.InvokeAsync(args).Result); } From de8c090e8ac3114d15582eef297d5225de2251c4 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Thu, 27 Oct 2022 09:08:36 +0800 Subject: [PATCH 07/15] Handle exception --- src/Program.cs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index c646092..29f327a 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Configuration; -using System.CommandLine; -using GICutscenes.FileTypes; +using GICutscenes.FileTypes; using GICutscenes.Mergers; using GICutscenes.Mergers.GIMKV; +using Microsoft.Extensions.Configuration; +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Parsing; using System.Diagnostics.CodeAnalysis; using System.Text.Json; @@ -20,7 +22,7 @@ internal sealed class Program public static Settings? settings; [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Retrieves the configuration file")] - private static Task<int> Main(string[] args) + private static int Main(string[] args) { // CLI Options @@ -78,11 +80,17 @@ private static Task<int> Main(string[] args) mkvEngineOption.SetDefaultValue("internal"); mkvEngineOption.AddAlias("-e"); + var stackTraceOption = new Option<bool>( + name: "--stack-trace", + description: "Show stack trace when throw exception."); + stackTraceOption.AddAlias("-st"); + var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact."); rootCommand.AddGlobalOption(outputFolderOption); rootCommand.AddGlobalOption(noCleanupOption); + rootCommand.AddGlobalOption(stackTraceOption); var demuxUsmCommand = new Command("demuxUsm", "Demuxes a specified .usm file to a specified folder") { @@ -149,7 +157,19 @@ private static Task<int> Main(string[] args) Console.ResetColor(); }); - return Task.FromResult(rootCommand.InvokeAsync(args).Result); + return new CommandLineBuilder(rootCommand).UseDefaults().UseExceptionHandler((ex, context) => + { + Console.ForegroundColor = ConsoleColor.Red; + if (context.ParseResult.GetValueForOption(stackTraceOption)) + { + Console.Error.WriteLine(ex); + } + else + { + Console.Error.WriteLine($"{ex.GetType()}: {ex.Message}"); + } + Console.ResetColor(); + }).Build().Invoke(args); } From 4ece977c711d97c1a047b6bd805cbe71994f1ead Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Thu, 27 Oct 2022 09:21:34 +0800 Subject: [PATCH 08/15] Change async Task to void --- src/Program.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 29f327a..8c4a17c 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -126,25 +126,25 @@ private static int Main(string[] args) // Command Handlers - demuxUsmCommand.SetHandler(async (FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => + demuxUsmCommand.SetHandler((FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => { ReadSetting(); - await DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup); + DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup); }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); - batchDemuxCommand.SetHandler(async (DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) => + batchDemuxCommand.SetHandler((DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) => { ReadSetting(); var timer = System.Diagnostics.Stopwatch.StartNew(); - await BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup); + BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup); timer.Stop(); Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed"); }, usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); - convertHcaCommand.SetHandler(async (FileSystemInfo input, DirectoryInfo? output, bool noCleanup) => + convertHcaCommand.SetHandler((FileSystemInfo input, DirectoryInfo? output, bool noCleanup) => { - await ConvertHcaCommand(input, output /*, noCleanup*/); + ConvertHcaCommand(input, output /*, noCleanup*/); }, hcaInputArg, outputFolderOption, noCleanupOption); resetCommand.SetHandler(() => @@ -197,7 +197,7 @@ private static void ReadSetting() } - private static async Task DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) + private static void DemuxUsmCommand(FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) { if (file == null) throw new ArgumentNullException(nameof(file), "No file provided."); if (!file.Exists) throw new ArgumentException("File {0} does not exist.", file.Name); @@ -218,7 +218,7 @@ private static async Task DemuxUsmCommand(FileInfo file, string key1, string key } } - private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) + private static void BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) { if (inputDir is not { Exists: true }) throw new DirectoryNotFoundException("Input directory is invalid."); string outputArg = (outputDir == null) @@ -235,7 +235,7 @@ private static async Task BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInf } } - private static async Task ConvertHcaCommand(FileSystemInfo input, DirectoryInfo? output) + private static void ConvertHcaCommand(FileSystemInfo input, DirectoryInfo? output) { if (!input.Exists) throw new ArgumentException("No file or directory given."); string outputArg = (output == null) From ce8e10e1bc06ce14cd04ec58e91facac92fb914f Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Thu, 27 Oct 2022 10:00:17 +0800 Subject: [PATCH 09/15] Add update command --- src/Program.cs | 92 +++++++++++++++++++++++++++++++--- src/Utils/GithubRelease.cs | 100 +++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 8 deletions(-) create mode 100644 src/Utils/GithubRelease.cs diff --git a/src/Program.cs b/src/Program.cs index 8c4a17c..f7422f9 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,11 +1,14 @@ using GICutscenes.FileTypes; using GICutscenes.Mergers; using GICutscenes.Mergers.GIMKV; +using GICutscenes.Utils; using Microsoft.Extensions.Configuration; using System.CommandLine; using System.CommandLine.Builder; using System.CommandLine.Parsing; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Net; using System.Text.Json; namespace GICutscenes @@ -80,17 +83,22 @@ private static int Main(string[] args) mkvEngineOption.SetDefaultValue("internal"); mkvEngineOption.AddAlias("-e"); + var notOpenBrowserOption = new Option<bool>( + name: "-n", + description: "Do not open browser if there's new version."); + var stackTraceOption = new Option<bool>( name: "--stack-trace", description: "Show stack trace when throw exception."); stackTraceOption.AddAlias("-st"); + var proxyOption = new Option<string>( + name: "--proxy", + description: "Specifies a proxy server for the request."); + proxyOption.AddAlias("-p"); - var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact."); - rootCommand.AddGlobalOption(outputFolderOption); - rootCommand.AddGlobalOption(noCleanupOption); - rootCommand.AddGlobalOption(stackTraceOption); + var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact."); var demuxUsmCommand = new Command("demuxUsm", "Demuxes a specified .usm file to a specified folder") { @@ -99,7 +107,9 @@ private static int Main(string[] args) key2Option, subsOption, mergeOption, - mkvEngineOption + mkvEngineOption, + outputFolderOption, + noCleanupOption, }; var batchDemuxCommand = new Command("batchDemux", "Tries to demux all .usm files in the specified folder") @@ -107,14 +117,23 @@ private static int Main(string[] args) usmFolderArg, subsOption, mergeOption, - mkvEngineOption + mkvEngineOption, + outputFolderOption, + noCleanupOption, }; //var hcaDecrypt = new Command(); var convertHcaCommand = new Command("convertHca", "Converts input .hca files into .wav files") { - hcaInputArg + hcaInputArg, + outputFolderOption, + }; + + var updateCommand = new Command("update", "Update for usm secret key and GICutscenes.") + { + notOpenBrowserOption, + proxyOption, }; var resetCommand = new Command("reset", "Reset 'appsettings.json' file to default."); @@ -122,6 +141,7 @@ private static int Main(string[] args) rootCommand.AddCommand(demuxUsmCommand); rootCommand.AddCommand(batchDemuxCommand); rootCommand.AddCommand(convertHcaCommand); + rootCommand.AddCommand(updateCommand); rootCommand.AddCommand(resetCommand); @@ -136,7 +156,7 @@ private static int Main(string[] args) batchDemuxCommand.SetHandler((DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) => { ReadSetting(); - var timer = System.Diagnostics.Stopwatch.StartNew(); + var timer = Stopwatch.StartNew(); BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup); timer.Stop(); Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed"); @@ -147,6 +167,11 @@ private static int Main(string[] args) ConvertHcaCommand(input, output /*, noCleanup*/); }, hcaInputArg, outputFolderOption, noCleanupOption); + updateCommand.SetHandler(async (bool notOpenBrowser, string proxy) => + { + await UpdateAsync(notOpenBrowser, proxy); + }, notOpenBrowserOption, proxyOption); + resetCommand.SetHandler(() => { var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } }; @@ -363,5 +388,56 @@ private static void CleanFiles(string outputPath, string basename) foreach (string f in Directory.EnumerateFiles(outputPath, $"{basename}_*.hca")) File.Delete(f); foreach (string f in Directory.EnumerateFiles(outputPath, $"{basename}_*.wav")) File.Delete(f); } + + [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Deserialize<TValue>(String, JsonSerializerOptions)")] + private static async Task UpdateAsync(bool notOpenBroswer, string? proxy) + { + var webProxy = new WebProxy(proxy); + var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All, Proxy = webProxy }); + client.DefaultRequestHeaders.Add("User-Agent", "GICutscenes"); + + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine("Update 'versions.json'..."); + Console.ResetColor(); + var versionsString = await client.GetStringAsync("https://raw.githubusercontent.com/ToaHartor/GI-cutscenes/main/versions.json"); + await File.WriteAllTextAsync(Path.Combine(AppContext.BaseDirectory, "versions.json"), versionsString); + Console.WriteLine("'versions.json' has updated to the latest version."); + + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine("Check update for GICutscenes..."); + Console.ResetColor(); + var releaseString = await client.GetStringAsync("https://api.github.com/repos/ToaHartor/GI-cutscenes/releases/latest"); + var release = JsonSerializer.Deserialize<GithubRelease>(releaseString!); + var currentVersion = typeof(Program).Assembly.GetName().Version; + if (System.Version.TryParse(release?.TagName?[1..], out var latestVersion)) + { + if (latestVersion > currentVersion) + { + + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine($"Latest version is '{release?.TagName}', GICutscenes needs to update."); + Console.ResetColor(); + if (!notOpenBroswer) + { + if (!string.IsNullOrWhiteSpace(release?.HtmlUrl)) + { + // What happens on macOS or Linux? + Process.Start(new ProcessStartInfo(release.HtmlUrl) { UseShellExecute = true }); + } + } + } + else + { + Console.WriteLine("GICutscenes is already the latest version."); + } + } + else + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine($"Cannot compare version, current version is '{currentVersion}', latest version is '{release?.TagName}'."); + Console.ResetColor(); + } + } + } } diff --git a/src/Utils/GithubRelease.cs b/src/Utils/GithubRelease.cs new file mode 100644 index 0000000..5d5b960 --- /dev/null +++ b/src/Utils/GithubRelease.cs @@ -0,0 +1,100 @@ +using System.Text.Json.Serialization; + +namespace GICutscenes.Utils; + +internal class GithubRelease +{ + + [JsonPropertyName("url")] + public string Url { get; set; } + + [JsonPropertyName("assets_url")] + public string AssetsUrl { get; set; } + + [JsonPropertyName("upload_url")] + public string UploadUrl { get; set; } + + [JsonPropertyName("html_url")] + public string HtmlUrl { get; set; } + + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("node_id")] + public string NodeId { get; set; } + + [JsonPropertyName("tag_name")] + public string TagName { get; set; } + + [JsonPropertyName("target_commitish")] + public string TargetCommitish { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("draft")] + public bool Draft { get; set; } + + [JsonPropertyName("prerelease")] + public bool Prerelease { get; set; } + + [JsonPropertyName("created_at")] + public DateTime CreatedAt { get; set; } + + [JsonPropertyName("published_at")] + public DateTime PublishedAt { get; set; } + + [JsonPropertyName("assets")] + public List<GithubAsset> Assets { get; set; } + + [JsonPropertyName("tarball_url")] + public string TarballUrl { get; set; } + + [JsonPropertyName("zipball_url")] + public string ZipballUrl { get; set; } + + [JsonPropertyName("body")] + public string Body { get; set; } + +} + + +public class GithubAsset +{ + [JsonPropertyName("url")] + public string Url { get; set; } + + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("node_id")] + public string NodeId { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("label")] + public string Label { get; set; } + + [JsonPropertyName("content_type")] + public string ContentType { get; set; } + + [JsonPropertyName("state")] + public string State { get; set; } + + [JsonPropertyName("size")] + public int Size { get; set; } + + [JsonPropertyName("download_count")] + public int DownloadCount { get; set; } + + [JsonPropertyName("created_at")] + public DateTime CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime UpdatedAt { get; set; } + + [JsonPropertyName("browser_download_url")] + public string BrowserDownloadUrl { get; set; } +} + From f73b38bc960c89dead9411c86946e88c769ca9a8 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Thu, 27 Oct 2022 15:21:59 +0800 Subject: [PATCH 10/15] Change options order --- src/Program.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index f7422f9..3dcacd6 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -84,18 +84,20 @@ private static int Main(string[] args) mkvEngineOption.AddAlias("-e"); var notOpenBrowserOption = new Option<bool>( - name: "-n", + name: "--no-browser", description: "Do not open browser if there's new version."); + notOpenBrowserOption.AddAlias("-nb"); + + var proxyOption = new Option<string>( + name: "--proxy", + description: "Specifies a proxy server for the request."); + proxyOption.AddAlias("-p"); var stackTraceOption = new Option<bool>( name: "--stack-trace", description: "Show stack trace when throw exception."); stackTraceOption.AddAlias("-st"); - var proxyOption = new Option<string>( - name: "--proxy", - description: "Specifies a proxy server for the request."); - proxyOption.AddAlias("-p"); var rootCommand = new RootCommand("A command line program playing with the cutscenes files (USM) from Genshin Impact."); @@ -416,6 +418,7 @@ private static async Task UpdateAsync(bool notOpenBroswer, string? proxy) Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine($"Latest version is '{release?.TagName}', GICutscenes needs to update."); + Console.WriteLine($"Release page: {release?.HtmlUrl}"); Console.ResetColor(); if (!notOpenBroswer) { From c055daafdef1e9416b94f6c754a593551595181f Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Thu, 27 Oct 2022 15:25:00 +0800 Subject: [PATCH 11/15] Subtitles style --- src/FileTypes/ASS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FileTypes/ASS.cs b/src/FileTypes/ASS.cs index 31bcbea..8752668 100644 --- a/src/FileTypes/ASS.cs +++ b/src/FileTypes/ASS.cs @@ -80,7 +80,7 @@ public string ConvertToAss() { [V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding -Style: Default,{_fontname},18,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,20,1 +Style: Default,{_fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1 [Events] Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text" + Environment.NewLine; From 70c2a4d1a0a57edc90ad0e93ae25ae7f48086bb6 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Thu, 27 Oct 2022 15:58:07 +0800 Subject: [PATCH 12/15] Create output folder if not exitsts --- src/Program.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 3dcacd6..bdaaa45 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -43,7 +43,7 @@ private static int Main(string[] args) var outputFolderOption = new Option<DirectoryInfo?>( name: "--output", - description: "Output folder." + description: "Output folder, default is './output'." ); outputFolderOption.AddAlias("-o"); @@ -151,6 +151,8 @@ private static int Main(string[] args) demuxUsmCommand.SetHandler((FileInfo file, string key1, string key2, DirectoryInfo? output, string engine, bool merge, bool subs, bool noCleanup) => { ReadSetting(); + output ??= new DirectoryInfo("./output"); + output.Create(); DemuxUsmCommand(file, key1, key2, output, engine, merge, subs, noCleanup); }, demuxFileOption, key1Option, key2Option, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); @@ -158,16 +160,20 @@ private static int Main(string[] args) batchDemuxCommand.SetHandler((DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) => { ReadSetting(); + outputDir ??= new DirectoryInfo("./output"); + outputDir.Create(); var timer = Stopwatch.StartNew(); BatchDemuxCommand(inputDir, outputDir, engine, merge, subs, noCleanup); timer.Stop(); Console.WriteLine($"{timer.ElapsedMilliseconds}ms elapsed"); }, usmFolderArg, outputFolderOption, mkvEngineOption, mergeOption, subsOption, noCleanupOption); - convertHcaCommand.SetHandler((FileSystemInfo input, DirectoryInfo? output, bool noCleanup) => + convertHcaCommand.SetHandler((FileSystemInfo input, DirectoryInfo? output) => { - ConvertHcaCommand(input, output /*, noCleanup*/); - }, hcaInputArg, outputFolderOption, noCleanupOption); + output ??= new DirectoryInfo("./output"); + output.Create(); + ConvertHcaCommand(input, output); + }, hcaInputArg, outputFolderOption); updateCommand.SetHandler(async (bool notOpenBrowser, string proxy) => { From dbca55fb18337c180ea343d287dd915a4bb9dfb3 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Sat, 29 Oct 2022 23:09:03 +0800 Subject: [PATCH 13/15] jsDelivr --- src/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Program.cs b/src/Program.cs index bdaaa45..22c52e4 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -407,7 +407,7 @@ private static async Task UpdateAsync(bool notOpenBroswer, string? proxy) Console.ForegroundColor = ConsoleColor.DarkGray; Console.WriteLine("Update 'versions.json'..."); Console.ResetColor(); - var versionsString = await client.GetStringAsync("https://raw.githubusercontent.com/ToaHartor/GI-cutscenes/main/versions.json"); + var versionsString = await client.GetStringAsync("https://cdn.jsdelivr.net/gh/ToaHartor/GI-cutscenes@main/versions.json"); await File.WriteAllTextAsync(Path.Combine(AppContext.BaseDirectory, "versions.json"), versionsString); Console.WriteLine("'versions.json' has updated to the latest version."); From 46e9f773ea54fe403519266c924227e56d42f6be Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Mon, 31 Oct 2022 19:03:33 +0800 Subject: [PATCH 14/15] Custom subtitle style --- appsettings.json | 3 +- src/FileTypes/ASS.cs | 123 ++++++++++++++++++++++++----------------- src/GICutscenes.csproj | 87 +++++++++++++++-------------- src/Program.cs | 25 +++++++-- 4 files changed, 138 insertions(+), 100 deletions(-) diff --git a/appsettings.json b/appsettings.json index 52388d7..9f028f5 100644 --- a/appsettings.json +++ b/appsettings.json @@ -2,6 +2,7 @@ "Settings": { "MkvMergePath": "", "FfmpegPath": "", - "SubsFolder": "./GenshinData/Subtitle" + "SubsFolder": "./GenshinData/Subtitle", + "SubtiteStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1" } } \ No newline at end of file diff --git a/src/FileTypes/ASS.cs b/src/FileTypes/ASS.cs index 8752668..80bbbf0 100644 --- a/src/FileTypes/ASS.cs +++ b/src/FileTypes/ASS.cs @@ -1,10 +1,11 @@ -using System.Text.RegularExpressions; +using System.Text; +using System.Text.RegularExpressions; namespace GICutscenes.FileTypes { internal class ASS { - public static readonly string[] SubsExtensions = {".ass", ".srt", ".txt"}; + public static readonly string[] SubsExtensions = { ".ass", ".srt", ".txt" }; private readonly string _srt; private readonly string _fontname; private readonly List<string> _dialogLines; @@ -67,34 +68,52 @@ public void ParseSrt() } } - public string ConvertToAss() { - - string filename = Path.ChangeExtension(_srt, ".ass"); - string header = - @$"[Script Info] -; This is an Advanced Sub Station Alpha v4+ script. -ScriptType: v4.00+ -Collisions: Normal -ScaledBorderAndShadow: yes -PlayDepth: 0 - -[V4+ Styles] -Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding -Style: Default,{_fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1 + public string ConvertToAss(string outputPath) + { + var sb = new StringBuilder(); + sb.AppendLine(""" + [Script Info] + ; This is an Advanced Sub Station Alpha v4+ script. + ScriptType: v4.00+ + Collisions: Normal + ScaledBorderAndShadow: yes + PlayDepth: 0 + + [V4+ Styles] + Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding + """); + if (string.IsNullOrWhiteSpace(Program.settings?.SubtiteStyle)) + { + sb.AppendLine($"Style: Default,{_fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1"); + } + else + { + sb.AppendLine(Program.settings?.SubtiteStyle.Replace("{fontname}", _fontname)); + } + sb.AppendLine(""" + + [Events] + Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text + """); -[Events] -Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text" + Environment.NewLine; - File.WriteAllText(filename, header); - string content = string.Join(Environment.NewLine, _dialogLines); - // Correcting styles - content = Regex.Replace(content, @"<([ubi])>", @"{\${1}1}"); - content = Regex.Replace(content, @"</([ubi])>", @"{\${1}0}"); - content = Regex.Replace(content, @"<font\s+color=""?#(\w{2})(\w{2})(\w{2})""?>", @"{\c&H$3$2$1&}"); - content = Regex.Replace(content, @"</font>", ""); + foreach (var dialogLine in _dialogLines) + { + if (!string.IsNullOrWhiteSpace(dialogLine)) + { + string content = dialogLine; + content = Regex.Replace(content, @"<([ubi])>", @"{\${1}1}"); + content = Regex.Replace(content, @"</([ubi])>", @"{\${1}0}"); + content = Regex.Replace(content, @"<font\s+color=""?#(\w{2})(\w{2})(\w{2})""?>", @"{\c&H$3$2$1&}"); + content = Regex.Replace(content, @"</font>", ""); + sb.AppendLine(content); + } + } - File.AppendAllText(filename, content); + var filename = Path.Combine(outputPath, "Subs", Path.GetFileNameWithoutExtension(_srt) + ".ass"); + Directory.CreateDirectory(Path.Combine(outputPath, "Subs")); + File.WriteAllText(filename, sb.ToString()); Console.WriteLine($"{_srt} converted to ASS"); - File.Delete(_srt); // Can be commented if you don't want to delete the original + //File.Delete(_srt); // Can be commented if you don't want to delete the original return filename; } @@ -122,30 +141,30 @@ public string ConvertToAss() { return null; } - public static void ConvertAllSrt(string subsFolder) - { - string? file = null; - //file = "ID/Cs_Inazuma_EQ4002207_ShikishogunRecalling_Boy_ID.txt"; - if (file == null) - { - foreach (string langDir in Directory.EnumerateDirectories(subsFolder)) - { - foreach (string srtFile in Directory.GetFiles(langDir, "*.txt")) - { - ASS newAss = new(srtFile, Path.GetDirectoryName(langDir) ?? "unk"); - newAss.ParseSrt(); - newAss.ConvertToAss(); - } - } - } - else - { - ASS newAss = new(Path.Combine(subsFolder, file), Path.GetDirectoryName(file) ?? "unk"); - newAss.ParseSrt(); - newAss.ConvertToAss(); - } - - Environment.Exit(0); - } + //public static void ConvertAllSrt(string subsFolder) + //{ + // string? file = null; + // //file = "ID/Cs_Inazuma_EQ4002207_ShikishogunRecalling_Boy_ID.txt"; + // if (file == null) + // { + // foreach (string langDir in Directory.EnumerateDirectories(subsFolder)) + // { + // foreach (string srtFile in Directory.GetFiles(langDir, "*.txt")) + // { + // ASS newAss = new(srtFile, Path.GetDirectoryName(langDir) ?? "unk"); + // newAss.ParseSrt(); + // newAss.ConvertToAss(); + // } + // } + // } + // else + // { + // ASS newAss = new(Path.Combine(subsFolder, file), Path.GetDirectoryName(file) ?? "unk"); + // newAss.ParseSrt(); + // newAss.ConvertToAss(); + // } + + // Environment.Exit(0); + //} } } diff --git a/src/GICutscenes.csproj b/src/GICutscenes.csproj index 9843993..1180d44 100644 --- a/src/GICutscenes.csproj +++ b/src/GICutscenes.csproj @@ -1,46 +1,47 @@ <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <Version>0.4.1</Version> - <OutputType>Exe</OutputType> - <TargetFramework>net6.0</TargetFramework> - <!--<RuntimeIdentifier>win-x64</RuntimeIdentifier>--> - <PublishTrimmed>true</PublishTrimmed> - <Authors>ToaHartor</Authors> - <RootNamespace>GICutscenes</RootNamespace> - <Description>A command line program playing with the cutscenes files (USM) from Genshin Impact.</Description> - <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> - <RepositoryUrl>https://github.com/ToaHartor/GI-cutscenes</RepositoryUrl> + <PropertyGroup> + <Version>0.4.1</Version> + <OutputType>Exe</OutputType> + <TargetFramework>net6.0</TargetFramework> + <!--<RuntimeIdentifier>win-x64</RuntimeIdentifier>--> + <PublishTrimmed>true</PublishTrimmed> + <Authors>ToaHartor</Authors> + <RootNamespace>GICutscenes</RootNamespace> + <Description>A command line program playing with the cutscenes files (USM) from Genshin Impact.</Description> + <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> + <RepositoryUrl>https://github.com/ToaHartor/GI-cutscenes</RepositoryUrl> - <Nullable>enable</Nullable> - <ImplicitUsings>enable</ImplicitUsings> - <SatelliteResourceLanguages>en</SatelliteResourceLanguages> - <DebugType>embedded</DebugType> - <PackageReadmeFile>README.md</PackageReadmeFile> - <RepositoryType>git</RepositoryType> - <AnalysisLevel>latest-all</AnalysisLevel> - <EnableTrimAnalyzer>true</EnableTrimAnalyzer> - </PropertyGroup> + <Nullable>enable</Nullable> + <LangVersion>preview</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <SatelliteResourceLanguages>en</SatelliteResourceLanguages> + <DebugType>embedded</DebugType> + <PackageReadmeFile>README.md</PackageReadmeFile> + <RepositoryType>git</RepositoryType> + <AnalysisLevel>latest-all</AnalysisLevel> + <EnableTrimAnalyzer>true</EnableTrimAnalyzer> + </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> - <Optimize>False</Optimize> - <DebugType>full</DebugType> - </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <Optimize>False</Optimize> + <DebugType>full</DebugType> + </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> - <DebugType>full</DebugType> - <PublishSingleFile>true</PublishSingleFile> - <TieredCompilation>true</TieredCompilation> - <PublishReadyToRun>true</PublishReadyToRun> - <!--<RunAOTCompilation>true</RunAOTCompilation>--> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0-preview.5.22301.12" /> - <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0-preview.5.22301.12" /> - <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0-preview.5.22301.12" /> - <PackageReference Include="System.CommandLine" Version="2.0.0-beta3.22114.1" /> - </ItemGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> + <DebugType>full</DebugType> + <PublishSingleFile>true</PublishSingleFile> + <TieredCompilation>true</TieredCompilation> + <PublishReadyToRun>true</PublishReadyToRun> + <!--<RunAOTCompilation>true</RunAOTCompilation>--> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0-preview.5.22301.12" /> + <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0-preview.5.22301.12" /> + <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0-preview.5.22301.12" /> + <PackageReference Include="System.CommandLine" Version="2.0.0-beta3.22114.1" /> + </ItemGroup> <ItemGroup> <Content Include="../appsettings.json"> @@ -55,10 +56,10 @@ </ItemGroup> <ItemGroup> - <None Include="..\.editorconfig" Link=".editorconfig" /> - <None Include="..\README.md"> - <Pack>True</Pack> - <PackagePath>\</PackagePath> - </None> + <None Include="..\.editorconfig" Link=".editorconfig" /> + <None Include="..\README.md"> + <Pack>True</Pack> + <PackagePath>\</PackagePath> + </None> </ItemGroup> </Project> diff --git a/src/Program.cs b/src/Program.cs index 22c52e4..a2e8408 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -18,6 +18,7 @@ internal sealed class Settings public string? MkvMergePath { get; set; } public string? SubsFolder { get; set; } public string? FfmpegPath { get; set; } + public string? SubtiteStyle { get; set; } } internal sealed class Program @@ -182,9 +183,17 @@ private static int Main(string[] args) resetCommand.SetHandler(() => { - var obj = new { Settings = new Settings { FfmpegPath = "", MkvMergePath = "", SubsFolder = "" } }; - var json = JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true }); - File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), json); + const string str = """ + { + "Settings": { + "MkvMergePath": "", + "FfmpegPath": "", + "SubsFolder": "", + "SubtiteStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1" + } + } + """; + File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), str); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("'appsettings.json' has reset to default."); Console.ResetColor(); @@ -249,6 +258,10 @@ private static void DemuxUsmCommand(FileInfo file, string key1, string key2, Dir MergeFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName), engine, subs); if (!noCleanup) CleanFiles(outputArg, Path.GetFileNameWithoutExtension(file.FullName)); } + if (!noCleanup && Directory.Exists(Path.Combine(outputArg, "Subs"))) + { + Directory.Delete(Path.Combine(outputArg, "Subs"), true); + } } private static void BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? outputDir, string engine, bool merge, bool subs, bool noCleanup) @@ -266,6 +279,10 @@ private static void BatchDemuxCommand(DirectoryInfo inputDir, DirectoryInfo? out MergeFiles(outputArg, Path.GetFileNameWithoutExtension(f), engine, subs); if (!noCleanup) CleanFiles(outputArg, Path.GetFileNameWithoutExtension(f)); } + if (!noCleanup && Directory.Exists(Path.Combine(outputArg, "Subs"))) + { + Directory.Delete(Path.Combine(outputArg, "Subs"), true); + } } private static void ConvertHcaCommand(FileSystemInfo input, DirectoryInfo? output) @@ -367,7 +384,7 @@ private static void MergeFiles(string outputPath, string basename, string engine if (!sub.IsAss()) { sub.ParseSrt(); - subFile = sub.ConvertToAss(); + subFile = sub.ConvertToAss(outputPath); } merger.AddSubtitlesTrack(subFile, lang); From 90192945fef2ec0f16bbd910fecd207c12041634 Mon Sep 17 00:00:00 2001 From: Scighost <scighost@outlook.com> Date: Mon, 31 Oct 2022 19:30:34 +0800 Subject: [PATCH 15/15] Fix spelling error --- appsettings.json | 2 +- src/FileTypes/ASS.cs | 4 ++-- src/Program.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/appsettings.json b/appsettings.json index 9f028f5..74ff011 100644 --- a/appsettings.json +++ b/appsettings.json @@ -3,6 +3,6 @@ "MkvMergePath": "", "FfmpegPath": "", "SubsFolder": "./GenshinData/Subtitle", - "SubtiteStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1" + "SubsStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1" } } \ No newline at end of file diff --git a/src/FileTypes/ASS.cs b/src/FileTypes/ASS.cs index 80bbbf0..bf532dc 100644 --- a/src/FileTypes/ASS.cs +++ b/src/FileTypes/ASS.cs @@ -82,13 +82,13 @@ [Script Info] [V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding """); - if (string.IsNullOrWhiteSpace(Program.settings?.SubtiteStyle)) + if (string.IsNullOrWhiteSpace(Program.settings?.SubsStyle)) { sb.AppendLine($"Style: Default,{_fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1"); } else { - sb.AppendLine(Program.settings?.SubtiteStyle.Replace("{fontname}", _fontname)); + sb.AppendLine(Program.settings?.SubsStyle.Replace("{fontname}", _fontname)); } sb.AppendLine(""" diff --git a/src/Program.cs b/src/Program.cs index 18cc964..0ca8744 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -18,7 +18,7 @@ internal sealed class Settings public string? MkvMergePath { get; set; } public string? SubsFolder { get; set; } public string? FfmpegPath { get; set; } - public string? SubtiteStyle { get; set; } + public string? SubsStyle { get; set; } } internal sealed class Program @@ -203,7 +203,7 @@ private static int Main(string[] args) "MkvMergePath": "", "FfmpegPath": "", "SubsFolder": "", - "SubtiteStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1" + "SubsStyle": "Style: Default,{fontname},12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100.0,100.0,0.0,0.0,1,0,0.5,2,10,10,14,1" } } """;