123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- using System;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using System.Net.Mime;
- 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 const string OP_CONVERT_LIB_SUBSET_TO_MP4_IN_DIR = "convert_lib_subset_to_mp4_in_dir";
- private const string OP_SCAN_LIB_NO_COVER = "scan_lib_no_cover";
- private static void RealMain(string[] args)
- {
- if (0 == args.Length)
- {
- Console.WriteLine($"Usage: {Path.GetFileName(AppDomain.CurrentDomain.FriendlyName)} <op> [args...]");
- Console.WriteLine($"Prefix ops:");
- Console.WriteLine($" * --no-pause");
- Console.WriteLine($" Disable enter key to exit");
- Console.WriteLine($"Available ops:");
- Console.WriteLine($" * {OP_COPY} <sourcePath> <targetPath>");
- Console.WriteLine($" Copy tags from source to target (replace)");
- Console.WriteLine($" Note: Only standard tags and front cover");
- Console.WriteLine($" * {OP_EMB_LRC} <inputPath> <lrcPath> <outputPath>");
- Console.WriteLine($" Embed lyrics into file(utf8)");
- Console.WriteLine($" * {OP_EMB_COVER}|{OP_EMB_COVER_IF_NOT_EMBEDDED} <inputPath> <coverPath> [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} <inputPath>");
- Console.WriteLine($" Embed cover into file if not embedded, same folder cover.jpg");
- Console.WriteLine($" * {OP_DIR_EMB_COVER_MP3} <inputDir> <outputDir>");
- 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} <inputDir> <outputDir>");
- 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} <inputPath> <outputPath> [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} <inputDir> <outputDir> [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} <libDir> [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($" * {OP_CONVERT_LIB_SUBSET_TO_MP4_IN_DIR} <libDir> [subset]");
- Console.WriteLine($" Convert to mp4(static cover) and copy tags to output, default subset is AAC_Q1.00");
- Console.WriteLine($" libDir/Album/subset/Track.m4a -> libDir/Album/subset_MP4/Track.mp4");
- Console.WriteLine($" * {OP_SCAN_LIB_NO_COVER} <libDir> [pattern] [...]");
- Console.WriteLine($" Scan files in lib find no cover, pattern default *.flac");
- 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;
- case OP_CONVERT_LIB_SUBSET_TO_MP4_IN_DIR: ConvertLibSubsetToMp4InDir(argsToOp); break;
- case OP_SCAN_LIB_NO_COVER: ScanLibNoCover(argsToOp); break;
- }
- }
- // impl
- private static void ScanLibNoCover(string[] args)
- {
- if (args.Length < 1) throw new ArgumentException("args least need 1");
- var libDir = args[0];
- var patterns = args.Length > 1
- ? args.Skip(1).ToArray()
- : new[] { "*.flac" };
- if (false == Directory.Exists(libDir)) throw new DirectoryNotFoundException($"lib dir `{libDir}' not found.");
- foreach (var discDir in Directory.GetDirectories(libDir))
- {
- var trackFiles = Microsoft.VisualBasic.FileIO.FileSystem.GetFiles(discDir, Microsoft.VisualBasic.FileIO.SearchOption.SearchTopLevelOnly, patterns);
- foreach (var file in trackFiles)
- {
- using var t = TagLib.File.Create(file);
- var hasCover = t.Tag.Pictures.Any(p => p.Type == PictureType.FrontCover);
- if (!hasCover) Console.WriteLine(file);
- }
- }
- }
- private static void ConvertLibSubsetToMp4InDir(string[] args)
- {
- if (args.Length < 1) throw new ArgumentException("args least need 1");
- var libDir = args[0]; var subset = args.Length > 1 ? args[1] : "AAC_Q1.00";
- if (false == Directory.Exists(libDir)) throw new DirectoryNotFoundException($"lib dir `{libDir}' not found.");
- foreach (var discDir in Directory.GetDirectories(libDir))
- {
- var subsetDir = Path.Combine(discDir, subset); if (false == Directory.Exists(subsetDir)) continue;
- foreach (var track in Directory.GetFiles(subsetDir))
- {
- var outDir = Path.Combine(discDir, subset + "_MP4");
- var outFile = Path.Combine(outDir, Path.GetFileNameWithoutExtension(track) + ".MP4");
- if (File.Exists(outFile)) { Console.WriteLine($"SKIP: exist. `{outFile}'"); continue; }
- byte[] coverBytes;
- try
- {
- using var tag = TagLib.File.Create(track);
- coverBytes = tag.Tag.Pictures.Where(p => p.Type == PictureType.FrontCover).Select(p => p.Data.ToArray()).FirstOrDefault();
- }
- catch (Exception e) { Console.WriteLine($"ERR: {e.Message} in `{track}'"); continue; }
- if (coverBytes == null) { Console.WriteLine($"WARN: skip. no cover in `{track}'"); continue; }
- var tmp = Path.GetTempFileName();
- File.WriteAllBytes(tmp, coverBytes);
- if (Directory.Exists(outDir) == false) { Console.WriteLine($"Create dir `{outDir}'"); Directory.CreateDirectory(outDir); }
- Console.WriteLine($"PROCESS: `{track}'");
- var ffMpegArgs = new string[]
- {
- "-n","-hide_banner","-stats","-v","warning",
- "-framerate","60",
- "-loop","1","-i",tmp,
- "-i",track,
- "-map_metadata","-1","-map_chapters","-1",
- "-shortest",
- "-vf","scale=-1:480,pad=ceil(iw/2)*2:ceil(ih/2)*2",
- "-c:v","h264","-pix_fmt","yuv420p",
- "-preset","placebo",
- "-crf","28","-bf","1250","-g","1250","-keyint_min","250",
- "-c:a","copy",
- outFile
- };
- var process = new Process
- {
- StartInfo =
- {
- FileName = "ffmpeg",
- CreateNoWindow = false,
- UseShellExecute = false,
- }
- };
- foreach (var item in ffMpegArgs) process.StartInfo.ArgumentList.Add(item);
- process.Start();
- process.PriorityClass = ProcessPriorityClass.BelowNormal;
- process.WaitForExit();
- File.Delete(tmp);//delete temp cover file
- if (process.ExitCode == 0) CopyTag(new[] { track, outFile }, false);
- }
- }
- }
- 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}");
- foreach (var flacPath in Directory.GetFiles(albDir, "*.flac"))
- {
- if (false == Directory.Exists(outAlbDir))
- {
- Console.WriteLine($"Create dir:{outAlbDir}");
- Directory.CreateDirectory(outAlbDir);
- }
- 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, bool cover = true)
- {
- 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);
- if (cover) 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();
- }
- }
- }
|