using McMaster.Extensions.CommandLineUtils; using Newtonsoft.Json; using System; using System.ComponentModel.DataAnnotations; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Reflection; using System.Text; using VGMToolbox.format; namespace UsmToolkit { [Command("UsmToolkit")] [VersionOptionFromMember("--version", MemberName = nameof(GetVersion))] [Subcommand(typeof(ExtractCommand), typeof(GetDependenciesCommand))] class Program { static int Main(string[] args) { try { return CommandLineApplication.Execute(args); } catch (FileNotFoundException e) { Console.WriteLine($"The file {e.FileName} cannot be found. The program will now exit."); return 2; } catch (Exception e) { Console.WriteLine($"FATAL ERROR: {e.Message}\n{e.StackTrace}"); return -1; } } protected int OnExecute(CommandLineApplication app) { app.ShowHelp(); return 1; } private static string GetVersion() => typeof(Program).Assembly.GetCustomAttribute().InformationalVersion; private class ExtractCommand { private class JoinConfig { public string VideoParameter { get; set; } public string AudioParameter { get; set; } public string OutputFormat { get; set; } } [Required] [FileOrDirectoryExists] [Argument(0, Description = "File or folder containing usm files")] public string InputPath { get; set; } [Option(CommandOptionType.NoValue, Description = "Join files after extraction.", ShortName = "j", LongName = "join")] public bool Join { get; set; } [Option(CommandOptionType.SingleValue, Description = "Specify output directory.", ShortName = "o", LongName = "output-dir")] public string OutputDir { get; set; } [Option(CommandOptionType.NoValue, Description = "Remove temporary m2v and audio after joining.", ShortName = "c", LongName = "clean")] public bool CleanTempFiles { get; set; } protected int OnExecute(CommandLineApplication app) { FileAttributes attr = File.GetAttributes(InputPath); if (attr.HasFlag(FileAttributes.Directory)) { foreach (var file in Directory.GetFiles(InputPath, "*.usm")) { Convert(file); } } else Convert(InputPath); return 0; } private void Convert(string fileName) { Console.WriteLine($"File: {fileName}"); var usmStream = new CriUsmStream(fileName); Console.WriteLine("Demuxing..."); usmStream.DemultiplexStreams(new MpegStream.DemuxOptionsStruct() { AddHeader = false, AddPlaybackHacks = false, ExtractAudio = true, ExtractVideo = true, SplitAudioStreams = false }); if (Join) { if (!string.IsNullOrEmpty(OutputDir) && !Directory.Exists(OutputDir)) Directory.CreateDirectory(OutputDir); JoinOutputFile(usmStream); } } private void JoinOutputFile(CriUsmStream usmStream) { if (!File.Exists("config.json")) { Console.WriteLine("ERROR: config.json not found!"); return; } var audioFormat = usmStream.FinalAudioExtension; var pureFileName = Path.GetFileNameWithoutExtension(usmStream.FilePath); if (audioFormat == ".adx") { //ffmpeg can not handle .adx from 0.2 for whatever reason //need vgmstream to format that to wav if (!Directory.Exists("vgmstream")) { Console.WriteLine("ERROR: vgmstream folder not found!"); return; } Console.WriteLine("adx audio detected, convert to wav..."); Helpers.ExecuteProcess("vgmstream/test.exe", $"\"{Path.ChangeExtension(usmStream.FilePath, usmStream.FinalAudioExtension)}\" -o \"{Path.ChangeExtension(usmStream.FilePath, "wav")}\""); usmStream.FinalAudioExtension = ".wav"; } Helpers.ExecuteProcess("ffmpeg", CreateFFmpegParameters(usmStream, pureFileName)); if (CleanTempFiles) { Console.WriteLine($"Cleaning up temporary files from {pureFileName}"); File.Delete(Path.ChangeExtension(usmStream.FilePath, "wav")); File.Delete(Path.ChangeExtension(usmStream.FilePath, "adx")); File.Delete(Path.ChangeExtension(usmStream.FilePath, "hca")); File.Delete(Path.ChangeExtension(usmStream.FilePath, "m2v")); } } private string CreateFFmpegParameters(CriUsmStream usmStream, string pureFileName) { JoinConfig conf = JsonConvert.DeserializeObject(File.ReadAllText("config.json")); StringBuilder sb = new StringBuilder(); sb.Append($"-i \"{Path.ChangeExtension(usmStream.FilePath, usmStream.FileExtensionVideo)}\" "); if (usmStream.HasAudio) sb.Append($"-i \"{Path.ChangeExtension(usmStream.FilePath, usmStream.FinalAudioExtension)}\" "); sb.Append($"{conf.VideoParameter} "); if (usmStream.HasAudio) sb.Append($"{conf.AudioParameter} "); sb.Append($"\"{Path.Combine(OutputDir ?? string.Empty, $"{pureFileName}.{conf.OutputFormat}")}\""); return sb.ToString(); } } private class GetDependenciesCommand { private class DepsConfig { public string Vgmstream { get; set; } public string FFmpeg { get; set; } } protected int OnExecute(CommandLineApplication app) { DepsConfig conf = JsonConvert.DeserializeObject(File.ReadAllText("deps.json")); WebClient client = new WebClient(); Console.WriteLine($"Downloading ffmpeg from {conf.FFmpeg}"); client.DownloadFile(conf.FFmpeg, "ffmpeg.zip"); Console.WriteLine($"Extracting ffmpeg..."); using (ZipArchive archive = ZipFile.OpenRead("ffmpeg.zip")) { var ent = archive.Entries.FirstOrDefault(x => x.Name == "ffmpeg.exe"); if (ent != null) { ent.ExtractToFile("ffmpeg.exe", true); } } File.Delete("ffmpeg.zip"); Console.WriteLine($"Downloading vgmstream from {conf.Vgmstream}"); client.DownloadFile(conf.Vgmstream, "vgmstream.zip"); Console.WriteLine("Extracting vgmstream..."); ZipFile.ExtractToDirectory("vgmstream.zip", "vgmstream", true); File.Delete("vgmstream.zip"); return 0; } } } }