using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Mime; using System.Reflection; using System.Text; using System.Threading.Tasks; using TagLib; using File = System.IO.File; // ReSharper disable InconsistentNaming namespace BatchProcess { internal class Program { private static void Main(string[] args) { Console.WriteLine("Batch Processor"); Console.WriteLine(); var skipPause = false; if (args.Length > 0 && args[0].ToLower() == "--no-pause") { skipPause = true; args = args.Skip(1).ToArray(); } try { RealMain(args); } catch (Exception e) { Console.WriteLine($"ERROR:{e}"); } Console.WriteLine(); if (skipPause) return; Console.Write("Press ENTER to exit..."); Console.ReadLine(); } private const string OP_DIR_EMB_COVER_MP3 = "embcover_mp3_jpg_dir"; private const string OP_DIR_EMB_COVER_M4A = "embcover_m4a_jpg_dir"; private const string OP_EMB_COVER = "embcover"; private const string OP_COPY = "copy"; private const string OP_EMB_COVER_IF_NOT_EMBEDDED = "embcover_if_not_embedded"; private const string OP_EMB_COVER_IF_NOT_EMBEDDED_COVER_DOT_JPG = "embcover_if_not_embedded_cover_jpg"; private const string OP_EMB_LRC = "emblrc"; private const string OP_CONVERT_FLAC_TO_AAC = "convert_flac_to_aac"; private const string OP_CONVERT_LIB_FLAC_TO_AAC = "convert_lib_flac_to_aac"; private const string OP_CONVERT_LIB_FLAC_TO_AAC_IN_DIR = "convert_lib_flac_to_aac_in_dir"; private static void RealMain(string[] args) { if (0 == args.Length) { Console.WriteLine($"Usage: {Path.GetFileName(AppDomain.CurrentDomain.FriendlyName)} [args...]"); Console.WriteLine($"Prefix ops:"); Console.WriteLine($" * --no-pause"); Console.WriteLine($" Disable enter key to exit"); Console.WriteLine($"Available ops:"); Console.WriteLine($" * {OP_COPY} "); Console.WriteLine($" Copy tags from source to target (replace)"); Console.WriteLine($" Note: Only standard tags and front cover"); Console.WriteLine($" * {OP_EMB_LRC} "); Console.WriteLine($" Embed lyrics into file(utf8)"); Console.WriteLine($" * {OP_EMB_COVER}|{OP_EMB_COVER_IF_NOT_EMBEDDED} [outputPath] [mime-type]"); Console.WriteLine($" Embed cover into file, default mine-type is {MediaTypeNames.Image.Jpeg}"); Console.WriteLine($" * {OP_EMB_COVER_IF_NOT_EMBEDDED_COVER_DOT_JPG} "); Console.WriteLine($" Embed cover into file if not embedded, same folder cover.jpg"); Console.WriteLine($" * {OP_DIR_EMB_COVER_MP3} "); Console.WriteLine($" Lookup dir, embed same file name jpg/jpeg into mp3"); Console.WriteLine($" Embed abc.jpg or abc.jpeg into abc.mp3 and save a copy to output dir."); Console.WriteLine($" * {OP_DIR_EMB_COVER_M4A} "); Console.WriteLine($" Lookup dir, embed same file name jpg/jpeg into m4a"); Console.WriteLine($" Embed abc.jpg or abc.jpeg into abc.m4a and save a copy to output dir."); Console.WriteLine($" * {OP_CONVERT_FLAC_TO_AAC} [q value]"); Console.WriteLine($" Convert to aac and copy tags to output, default q is 1.0"); Console.WriteLine($" * {OP_CONVERT_LIB_FLAC_TO_AAC} [q value]"); Console.WriteLine($" Convert to aac and copy tags to output, default q is 1.0"); Console.WriteLine($" inputDir/Album/Track.flac -> outputDir/Album/Track.m4a"); Console.WriteLine($" Also copy inputDir/Album/cover.jpg to outDir/Album/cover.jpg if found"); Console.WriteLine($" * {OP_CONVERT_LIB_FLAC_TO_AAC_IN_DIR} [q value]"); Console.WriteLine($" Convert to aac and copy tags to output, default q is 1.0"); Console.WriteLine($" libDir/Album/Track.flac -> libDir/Album/AAC_Q1.00/Track.m4a"); Console.WriteLine($""); Console.WriteLine($" + More on demand"); return; } var op = args[0]; Console.WriteLine($"OP:{op}"); var argsToOp = args.Skip(1).ToArray(); switch (op.ToLower()) { default: throw new ArgumentException($"No recognizable op:{op}"); case OP_DIR_EMB_COVER_MP3: EmbJpegCoverDir(argsToOp, "*.mp3"); break; case OP_DIR_EMB_COVER_M4A: EmbJpegCoverDir(argsToOp, "*.m4a"); break; case OP_EMB_COVER: EmbCover(argsToOp, true); break; case OP_EMB_COVER_IF_NOT_EMBEDDED: EmbCover(argsToOp, false); break; case OP_EMB_LRC: EmbLrc(argsToOp); break; case OP_COPY: CopyTag(argsToOp); break; case OP_EMB_COVER_IF_NOT_EMBEDDED_COVER_DOT_JPG: if (argsToOp.Length != 1) throw new ArgumentException("args need 1"); // ReSharper disable once AssignNullToNotNullAttribute var argsMod = new[] { argsToOp[0], Path.Combine(Path.GetDirectoryName(argsToOp[0]), "cover.jpg") }; EmbCover(argsMod, false); break; case OP_CONVERT_FLAC_TO_AAC: ConvertFlacToAac(argsToOp); break; case OP_CONVERT_LIB_FLAC_TO_AAC: ConvertLibFlacToAac(argsToOp); break; case OP_CONVERT_LIB_FLAC_TO_AAC_IN_DIR: ConvertLibFlacToAacInDir(argsToOp); break; } } // impl private static void ConvertLibFlacToAacInDir(string[] args) { if (args.Length < 1) throw new ArgumentException("args least need 1"); var libDir = args[0]; if (false == Directory.Exists(libDir)) throw new DirectoryNotFoundException($"lib dir `{libDir}' not found."); var q = args.Length > 1 && float.TryParse(args[1], out var tq) && tq is >= 0 and <= 1 ? tq : 1.0; foreach (var albDir in Directory.GetDirectories(libDir)) { var outAlbDir = Path.Combine(albDir, $"AAC_Q{q:N2}"); if (false == Directory.Exists(outAlbDir)) { Console.WriteLine($"Create dir:{outAlbDir}"); Directory.CreateDirectory(outAlbDir); } foreach (var flacPath in Directory.GetFiles(albDir, "*.flac")) { var outM4APath = Path.Combine(outAlbDir, Path.ChangeExtension(Path.GetFileName(flacPath), ".m4a")); Console.WriteLine( ConvertFlacToAac(new[] {flacPath, outM4APath, $"{q}"}) ? $"Convert OK. -> {outM4APath}" : $"Exist, Skipped. {outM4APath}" ); } } } private static void ConvertLibFlacToAac(string[] args) { if (args.Length < 2) throw new ArgumentException("args least need 2"); var inDir = args[0]; var outDir = args[1]; if (false == Directory.Exists(inDir)) throw new DirectoryNotFoundException($"input dir `{inDir}' not found."); if (false == Directory.Exists(outDir)) throw new DirectoryNotFoundException($"output dir `{inDir}' not found."); var q = args.Length > 2 && float.TryParse(args[2], out var tq) && tq is >= 0 and <= 1 ? tq : 1.0; foreach (var albDir in Directory.GetDirectories(inDir)) { var albDirName = Path.GetFileName(albDir); var outAlbDir = Path.Combine(outDir, albDirName); if (false == Directory.Exists(outAlbDir)) { Console.WriteLine($"Create dir:{outAlbDir}"); Directory.CreateDirectory(outAlbDir); } var coverPath = Path.Combine(albDir, "cover.jpg"); var destCoverPath = Path.Combine(outAlbDir, "cover.jpg"); if (File.Exists(coverPath) && false == File.Exists(destCoverPath)) { Console.WriteLine($"Copy {coverPath}"); Console.WriteLine($" -> {destCoverPath} "); File.Copy(coverPath, destCoverPath); } foreach (var flacPath in Directory.GetFiles(albDir, "*.flac")) { var outM4APath = Path.Combine(outAlbDir, Path.ChangeExtension(Path.GetFileName(flacPath), ".m4a")); if (ConvertFlacToAac(new[] { flacPath, outM4APath, $"{q}" })) { Console.WriteLine($"Convert OK. -> {outM4APath}"); } else { Console.WriteLine($"Exist, Skipped. {outM4APath}"); } } } } private static bool ConvertFlacToAac(string[] args) { if (args.Length < 2) throw new ArgumentException("args least need 2"); var pathIn = args[0]; var pathOutput = args[1]; var q = args.Length > 2 && float.TryParse(args[2], out var tq) && tq is >= 0 and <= 1 ? tq : 1.0; if (false == File.Exists(pathIn)) throw new FileNotFoundException($"sourcePath `{pathIn}' not found."); if (File.Exists(pathOutput)) return false; if (false == Directory.Exists(Path.GetDirectoryName(pathOutput))) throw new DirectoryNotFoundException($"targetPath dir not found."); Console.WriteLine($"Processing {pathIn}..."); var flacProcess = new Process { StartInfo = { FileName = "FLAC", Arguments = $"-s -c -d \"{pathIn}\"", RedirectStandardOutput = true, UseShellExecute = false, } }; var aacProcess = new Process { StartInfo = { FileName = "NeroAacEnc", Arguments = $"-if - -q {q} -of \"{pathOutput}\"", RedirectStandardInput = true, UseShellExecute = false, } }; flacProcess.Start(); aacProcess.Start(); var pipe = Task.Factory.StartNew(() => { try { flacProcess.StandardOutput.BaseStream.CopyTo(aacProcess.StandardInput.BaseStream, 1024 * 512); } catch { //EAT ERROR } try { aacProcess.StandardInput.BaseStream.Close(); } catch { //EAT ERROR } }); Task.WaitAll(pipe); flacProcess.WaitForExit(); aacProcess.WaitForExit(); CopyTag(args); return true; } private static void CopyTag(string[] args) { if (args.Length < 2) throw new ArgumentException("args need 2"); var pathIn = args[0]; var pathOutput = args[1]; if (false == File.Exists(pathIn)) throw new FileNotFoundException($"sourcePath `{pathIn}' not found."); if (false == Directory.Exists(Path.GetDirectoryName(pathOutput))) throw new DirectoryNotFoundException($"targetPath dir not found."); if (false == File.Exists(pathOutput)) throw new FileNotFoundException($"targetPath `{pathOutput}' not found."); using var src = TagLib.File.Create(pathIn); var dst = TagLib.File.Create(pathOutput); dst.Tag.Clear(); dst.Save(); dst.Dispose(); dst = TagLib.File.Create(pathOutput); src.Tag.CopyTo(dst.Tag, true); dst.Tag.Pictures = src.Tag.Pictures; src.Dispose(); dst.Save(); dst.Dispose(); Console.WriteLine("Tag Copied."); } private static void EmbCover(string[] args, bool overwrite) { if (args.Length < 2) throw new ArgumentException("args least need 2"); var pathIn = args[0]; var pathCover = args[1]; var pathOutput = args.Length > 2 ? args[2] : args[0]; var mimeType = args.Length > 3 ? args[4] : MediaTypeNames.Image.Jpeg; if (false == File.Exists(pathIn)) throw new FileNotFoundException($"inputPath `{pathIn}' not found."); if (false == File.Exists(pathCover)) throw new FileNotFoundException($"coverPath `{pathCover}' not found."); Console.WriteLine($"Processing {pathIn}..."); Console.WriteLine($"Comver image:{pathCover}"); if (pathIn != pathOutput) File.Copy(pathIn, pathOutput, true); if (EmbedCoverIntoFile(pathOutput, pathCover, mimeType, overwrite)) { // 12345678901234 Console.WriteLine($"Saved to: {pathOutput}"); } else { Console.WriteLine("Skipped."); } } private static void EmbLrc(string[] args) { if (args.Length < 3) throw new ArgumentException("args need 2"); var pathIn = args[0]; var pathLrc = args[1]; var pathOutput = args[2]; if (false == File.Exists(pathIn)) throw new FileNotFoundException($"inputPath `{pathIn}' not found."); if (false == File.Exists(pathLrc)) throw new FileNotFoundException($"coverPath `{pathLrc}' not found."); Console.WriteLine($"Processing {pathIn}..."); Console.WriteLine($"Comver image:{pathLrc}"); if (pathIn != pathOutput) File.Copy(pathIn, pathOutput); EmbedLrcIntoFile(pathOutput, pathLrc); // 12345678901234 Console.WriteLine($"Saved to: {pathOutput}"); } private static void EmbJpegCoverDir(string[] args, string pattern) { if (args.Length != 2) throw new ArgumentException("args need 2"); var dirIn = args[0]; var dirOut = args[1]; if (false == Directory.Exists(dirIn)) throw new DirectoryNotFoundException($"inputDir `{dirIn}' not found."); if (false == Directory.Exists(dirOut)) throw new DirectoryNotFoundException($"outputDir `{dirOut} not found.'"); Console.WriteLine($"Enter directory:{dirIn}"); var files = Directory.GetFiles(dirIn, pattern); if (0 == files.Length) { Console.WriteLine($"Not result for {pattern}"); } else { foreach (var file in files) { // 12345678901234 Console.WriteLine($"Processing {file}..."); var imgFilePath = Path.ChangeExtension(file, ".jpg"); if (false == File.Exists(imgFilePath)) imgFilePath = Path.ChangeExtension(file, ".jpeg"); if (false == File.Exists(imgFilePath)) { Console.WriteLine("Cover image file not found, Skipped. ***"); continue; } // 12345678901234 Console.WriteLine($"Comver image:{imgFilePath}"); var destFileName = Path.Combine(dirOut, Path.GetFileName(file)); File.Copy(file, destFileName); EmbedCoverIntoFile(destFileName, imgFilePath, MediaTypeNames.Image.Jpeg); // 12345678901234 Console.WriteLine($"Saved to: {destFileName}"); } } Console.WriteLine($"Leave directory:{dirIn}"); } private static bool EmbedCoverIntoFile(string filePath, string coverPath, string mimeType, bool overwrite = true) { using var mediaFile = TagLib.File.Create(filePath); if (!overwrite) { if (mediaFile.Tag.Pictures.Any(p => p.Type == PictureType.FrontCover)) return false; } var pic = new Picture //REF: stackoverflow.com/a/30285220/2430943 { Type = PictureType.FrontCover, Description = "Cover", MimeType = mimeType, Data = File.ReadAllBytes(coverPath) }; mediaFile.Tag.Pictures = new IPicture[] { pic }; mediaFile.Save(); return true; } private static void EmbedLrcIntoFile(string filePath, string lrcPath) { var mediaFile = TagLib.File.Create(filePath); mediaFile.Tag.Lyrics = File.ReadAllText(lrcPath, Encoding.UTF8); mediaFile.Save(); } } }