} // ----------------------------------------- /// <summary> /// Convert from bin/cue to encoded audio/cue /// </summary> /// <param name="_Input">Input file, must be `.cue`</param> /// <param name="_Output">Output folder, If null, it will be same as input file folder</param> /// <param name="_Audio">Audio Quality to encode the audio tracks with.</param> /// <param name="_Title">Title of the CD</param> /// <param name="onComplete">Completed (completeStatus,final Size)</param> /// <returns></returns> public static bool startJob_ConvertCue(string _Input, string _Output, Tuple <int, int> _Audio, string _Title, Action <bool, int> onComplete) { if (LOCKED) { ERROR = "Engine is working"; return(false); } if (!FFMPEG_OK) { ERROR = "FFmpeg is not set"; return(false); } LOCKED = true; var par = new CrushParams(); // CovertCue shares params with Crush job par.inputFile = _Input; par.outputDir = _Output; par.audioQuality = _Audio; par.cdTitle = _Title; var j = new JobConvertCue(par); j.MAX_CONCURRENT = MAX_TASKS; j.onComplete = (s) => { LOCKED = false; ERROR = j.ERROR[1]; onComplete(s, 0); // Disregard final filesize }; j.onJobStatus = jobStatusHandler; // For status and progress updates j.start(); return(true); } // -----------------------------------------
}// ----------------------------------------- // - public override void start() { CrushParams p = jobData; LOG.line(); LOG.log("=== CONVERTING A CD with the following parameters :"); LOG.log("- Input : {0}", p.inputFile); LOG.log("- Output Dir : {0}", p.outputDir); LOG.log("- Temp Dir : {0}", p.tempDir); LOG.log("- CD Title : {0}", p.cdTitle); LOG.log("- Audio Quality : {0}", CDCRUSH.getAudioQualityString(p.audioQuality)); base.start(); }// -----------------------------------------
} // ----------------------------------------- /// <summary> /// Compress a CD to output folder /// </summary> /// <param name="_Input">Input file, must be `.cue`</param> /// <param name="_Output">Output folder, If null, it will be same as input file folder</param> /// <param name="_Audio">Audio Quality to encode the audio tracks with.</param> /// <param name="_Cover">Cover Image to store in the archive</param> /// <param name="_Title">Title of the CD</param> /// <param name="onComplete">Completed (completeStatus,CrushedSize)</param> /// <returns></returns> public static bool startJob_CrushCD(string _Input, string _Output, Tuple <int, int> _Audio, string _Cover, string _Title, int compressionLevel, Action <bool, int, CueReader> onComplete) { if (LOCKED) { ERROR = "Engine is working"; return(false); } if (!FFMPEG_OK) { ERROR = "FFmpeg is not set"; return(false); } LOCKED = true; // Set the running parameters for the Crush (compress) job var par = new CrushParams { inputFile = _Input, outputDir = _Output, audioQuality = _Audio, cover = _Cover, cdTitle = _Title, compressionLevel = compressionLevel, expectedTracks = HACK_CD_TRACKS }; // Create the job and set it up var j = new JobCrush(par); j.MAX_CONCURRENT = MAX_TASKS; j.onComplete = (s) => { LOCKED = false; ERROR = j.ERROR[1]; // Note: job.jobData is a general use object that was set up in the job // I can read it and get things that I want from it if (s) { onComplete(s, j.jobData.crushedSize, j.jobData.cd); // Hack, send CDINFO and SIZE as well } else { onComplete(s, 0, null); } }; j.onJobStatus = jobStatusHandler; // For status and progress updates j.start(); return(true); } // -----------------------------------------
} // ----------------------------------------- /// <summary> /// Compress a CD to output folder /// </summary> /// <param name="_Input">Input file, must be `.cue`</param> /// <param name="_Output">Output folder, If null, it will be same as input file folder</param> /// <param name="_Audio">Audio Quality to encode the audio tracks with.</param> /// <param name="_Cover">Cover Image to store in the archive</param> /// <param name="_Title">Title of the CD</param> /// <param name="onComplete">Completed (completeStatus,MD5,CrushedSize)</param> /// <returns></returns> public static bool crushCD(string _Input, string _Output, int _Audio, string _Cover, string _Title, Action <bool, string, int> onComplete) { // NOTE : JOB checks for input file if (LOCKED) { ERROR = "Engine is working"; return(false); } if (!FFMPEG_OK) { ERROR = "FFmpeg is not set"; return(false); } LOCKED = true; var par = new CrushParams(); par.inputFile = _Input; par.outputDir = _Output; par.audioQuality = _Audio; par.cover = _Cover; par.cdTitle = _Title; var j = new JobCrush(par); j.MAX_CONCURRENT = MAX_TASKS; j.onComplete = (s) => { LOCKED = false; ERROR = j.ERROR[1]; if (s) { CueReader cd = (CueReader)j.jobData.cd; onComplete(s, cd.getFirstDataTrackMD5(), j.jobData.crushedSize); // Hack, send CDINFO and SIZE as well } else { onComplete(s, "", 0); } }; j.onJobStatus = jobStatusHandler; // For status and progress updates j.start(); return(true); } // -----------------------------------------
} // ----------------------------------------- /// <summary> /// Starts either a CRUSH or CONVERT Job /// - Crush will encode all tracks and then create a .CRUSHED archive that can be restored lates /// - Convert will just encode all audio tracks and create new .CUE/.BIN/.ENCODED AUDIO FILES /// - [CONVERT] can create files in a folder or inside an Archive /// /// </summary> /// <param name="_Mode">0:Crush, 1:Convert, 2:Convert + ARCHIVE</param> /// <param name="_Input">Must be a valid CUE file </param> /// <param name="_Output">Output Directory - IF EMPTY will be same dir as INPUT</param> /// <param name="_Audio">Audio Settings TUPLE. Check `AudioMaster`</param> /// <param name="_ArchSet">Archive Settings Index. Check `ArchiveMaster` -1 for no archive</param> /// <param name="_Title">Name of the CD</param> /// <param name="_Cover">Path of Cover Image to store in the archive - CAN BE EMPTY</param> /// <param name="onComplete">Complete Calback (completeStatus,jobData object)</param> /// <returns>Preliminary Success</returns> public static bool startJob_Convert_Crush( int _Mode, string _Input, string _Output, Tuple <string, int> _Audio, int _ArchSet, string _Title, string _Cover, Action <bool, CrushParams> onComplete) { if (LOCKED) { ERROR = "Engine is working"; return(false); } if (!FFMPEG_OK) { ERROR = "FFmpeg is not set"; return(false); } LOCKED = true; // Set the running parameters for the job var par = new CrushParams { inputFile = _Input, outputDir = _Output, audioQuality = _Audio, cover = _Cover, cdTitle = _Title, archiveSettingsInd = _ArchSet, expectedTracks = HACK_CD_TRACKS, mode = _Mode }; var job = new JobCrush(par); job.MAX_CONCURRENT = MAX_TASKS; job.onJobStatus = jobStatusHandler; // For status and progress updates, FORM sets this. job.onComplete = (s) => { LOCKED = false; ERROR = job.ERROR[1]; onComplete(s, job.jobData); }; job.start(); return(true); }
}// ----------------------------------------- /// <summary> /// Called on FAIL / COMPLETE / PROGRAM EXIT /// Clean up temporary files /// </summary> protected override void kill() { base.kill(); if (CDCRUSH.FLAG_KEEP_TEMP) { return; } // - Cleanup CrushParams p = jobData; if (p.tempDir != p.outputDir) // NOTE: This is always a subdir of the master temp dir { try { Directory.Delete(p.tempDir, true); } catch (IOException) { // do nothing } } // -- }// -----------------------------------------
} // ----------------------------------------- /// <summary> /// Convert from bin/cue to encoded audio/cue /// </summary> /// <param name="_Input">Input file, must be `.cue`</param> /// <param name="_Output">Output folder, If null, it will be same as input file folder</param> /// <param name="_Audio">Audio Quality to encode the audio tracks with.</param> /// <param name="_Title">Title of the CD</param> /// <param name="onComplete">Completed (completeStatus,final Size)</param> /// <returns></returns> public static bool startJob_ConvertCue(string _Input, string _Output, Tuple <int, int> _Audio, string _Title, Action <bool, int, CueReader> onComplete) { if (LOCKED) { ERROR = "Engine is working"; return(false); } if (!FFMPEG_OK) { ERROR = "FFmpeg is not set"; return(false); } LOCKED = true; var par = new CrushParams { inputFile = _Input, outputDir = _Output, audioQuality = _Audio, cdTitle = _Title, expectedTracks = HACK_CD_TRACKS }; // CovertCue shares params with Crush job var j = new JobConvertCue(par); j.MAX_CONCURRENT = MAX_TASKS; j.onComplete = (s) => { LOCKED = false; ERROR = j.ERROR[1]; onComplete(s, 0, j.jobData.cd); // Disregard final filesize, because it's not an archive }; j.onJobStatus = jobStatusHandler; // For status and progress updates j.start(); return(true); } // -----------------------------------------
// -- public JobCrush(CrushParams p) : base("Compress CD") { // Check for input files // :: -------------------- if (!CDCRUSH.check_file_(p.inputFile, ".cue")) { fail(msg: CDCRUSH.ERROR); return; } if (string.IsNullOrEmpty(p.outputDir)) { p.outputDir = Path.GetDirectoryName(p.inputFile); } if (!FileTools.createDirectory(p.outputDir)) { fail(msg: "Can't create Output Dir " + p.outputDir); return; } p.tempDir = Path.Combine(CDCRUSH.TEMP_FOLDER, Guid.NewGuid().ToString().Substring(0, 12)); if (!FileTools.createDirectory(p.tempDir)) { fail(msg: "Can't create TEMP dir"); return; } // IMPORTANT!! sharedData gets set by value, NOT A POINTER, do not make changes to p after this jobData = p; // -- // - Read the CUE file :: add(new CTask((t) => { var cd = new CueReader(); jobData.cd = cd; if (!cd.load(p.inputFile)) { t.fail(msg: cd.ERROR); return; } // Post CD CUE load :: // In case user named the CD, otherwise it's going to be the same if (!string.IsNullOrWhiteSpace(p.cdTitle)) { cd.CD_TITLE = FileTools.sanitizeFilename(p.cdTitle); } // Real quality to string name cd.CD_AUDIO_QUALITY = CDCRUSH.getAudioQualityString(p.audioQuality); // This flag notes that all files will go to the TEMP folder jobData.workFromTemp = !cd.MULTIFILE; // Generate the final arc name now that I have the CD TITLE jobData.finalArcPath = Path.Combine(p.outputDir, cd.CD_TITLE + ".arc"); t.complete(); }, "Reading", true)); // - Cut tracks // --------------------------- add(new TaskCutTrackFiles()); // - Compress tracks // --------------------- add(new CTask((t) => { CueReader cd = jobData.cd; foreach (CueTrack tr in cd.tracks) { addNextAsync(new TaskCompressTrack(tr)); } //-- t.complete(); }, "Preparing")); // Create Archive // Add all tracks to the final archive // --------------------- add(new CTask((t) => { CueReader cd = jobData.cd; // -- Get list of files:: System.Collections.ArrayList files = new System.Collections.ArrayList(); foreach (var tr in cd.tracks) { files.Add(tr.workingFile); // Working file is valid, was set earlier } // Compress all the track files var arc = new FreeArc(CDCRUSH.TOOLS_PATH); t.handleCliReport(arc); arc.compress((string[])files.ToArray(typeof(string)), jobData.finalArcPath, p.compressionLevel); t.killExtra = () => arc.kill(); }, "Compressing")); // - Create CD SETTINGS and push it to the final archive // ( I am appending these files so that they can be quickly loaded later ) // -------------------- add(new CTask((t) => { CueReader cd = jobData.cd; #if DEBUG cd.debugInfo(); #endif string path_settings = Path.Combine(p.tempDir, CDCRUSH.CDCRUSH_SETTINGS); if (!cd.saveJson(path_settings)) { t.fail(msg: cd.ERROR); return; } // - Cover Image Set? string path_cover; if (p.cover != null) { path_cover = Path.Combine(p.tempDir, CDCRUSH.CDCRUSH_COVER); File.Copy(p.cover, path_cover); } else { path_cover = null; } // - Append the file(s) var arc = new FreeArc(CDCRUSH.TOOLS_PATH); t.handleCliReport(arc); arc.appendFiles(new string[] { path_settings, path_cover }, jobData.finalArcPath); t.killExtra = () => arc.kill(); }, "Finalizing")); // - Get post data add(new CTask((t) => { var finfo = new FileInfo(jobData.finalArcPath); jobData.crushedSize = (int)finfo.Length; t.complete(); }, "Finalizing")); // -- COMPLETE -- }// -----------------------------------------
}// ----------------------------------------- // -- public override void start() { base.start(); p = (CrushParams)jobData; // Working file is already set and points to either TEMP or INPUT folder INPUT = track.workingFile; // NOTE: OUTPUT path is generated later with the setupfiles() function // Before compressing the tracks, get and store the MD5 of the track using (var md5 = System.Security.Cryptography.MD5.Create()) { using (var str = File.OpenRead(INPUT)) { track.md5 = BitConverter.ToString(md5.ComputeHash(str)).Replace("-", "").ToLower(); } } // -- if (track.isData) { var ecm = new EcmTools(CDCRUSH.TOOLS_PATH); setupHandlers(ecm); // New filename that is going to be generated: setupFiles(".bin.ecm"); ecm.ecm(INPUT, OUTPUT); // old .bin file from wherever it was to temp/bin.ecm } else // AUDIO TRACK : { // Get Audio Data. (codecID, codecQuality) Tuple <string, int> audioQ = p.audioQuality; // New filename that is going to be generated: setupFiles(AudioMaster.getCodecExt(audioQ.Item1)); // I need ffmpeg for both occations var ffmp = new FFmpeg(CDCRUSH.FFMPEG_PATH); setupHandlers(ffmp); if (audioQ.Item1 == "TAK") { var tak = new Tak(CDCRUSH.TOOLS_PATH); // This will make FFMPEG read the PCM file, convert it to WAV on the fly // and feed it to TAK, which will convert and save it. ffmp.convertPCMStreamToWavStream((ffmpegIn, ffmpegOut) => { var sourceFile = File.OpenRead(INPUT); tak.encodeFromStream(OUTPUT, (takIn) => { ffmpegOut.CopyTo(takIn); takIn.Close(); }); sourceFile.CopyTo(ffmpegIn); // Feed PCM to FFMPEG ffmpegIn.Close(); }); } else { // It must be FFMPEG ffmp.encodePCM(audioQ.Item1, audioQ.Item2, INPUT, OUTPUT); } } //- end if (track.isData) }// -----------------------------------------
// -- public JobConvertCue(CrushParams p) : base("Convert CD") { // Check for input files // :: -------------------- if (!CDCRUSH.check_file_(p.inputFile, ".cue")) { fail(msg: CDCRUSH.ERROR); return; } if (string.IsNullOrEmpty(p.outputDir)) { p.outputDir = Path.GetDirectoryName(p.inputFile); } // : NEW : // : ALWAYS Create a subfolder to avoid overwriting the source files p.outputDir = CDCRUSH.checkCreateUniqueOutput(p.outputDir, p.cdTitle + CDCRUSH.RESTORED_FOLDER_SUFFIX); if (p.outputDir == null) { fail("Output Dir Error " + p.outputDir); return; } // - p.tempDir = Path.Combine(CDCRUSH.TEMP_FOLDER, Guid.NewGuid().ToString().Substring(0, 12)); if (!FileTools.createDirectory(p.tempDir)) { fail(msg: "Can't create TEMP dir"); return; } // Useful to know. p.flag_convert_only = true; // IMPORTANT!! sharedData gets set by value, NOT A POINTER, do not make changes to p after this jobData = p; hack_setExpectedProgTracks(p.expectedTracks + 2); // -- // - Read the CUE file :: add(new CTask((t) => { var cd = new CueReader(); jobData.cd = cd; if (!cd.load(p.inputFile)) { t.fail(msg: cd.ERROR); return; } // -- if (cd.tracks.Count == 1) { t.fail(msg: "No point in converting. No audio tracks on the cd."); return; } // Meaning the tracks are going to be extracted in the temp folder jobData.flag_sourceTracksOnTemp = (!cd.MULTIFILE && cd.tracks.Count > 1); // In case user named the CD, otherwise it's going to be the same if (!string.IsNullOrWhiteSpace(p.cdTitle)) { cd.CD_TITLE = FileTools.sanitizeFilename(p.cdTitle); } // Real quality to string name cd.CD_AUDIO_QUALITY = CDCRUSH.getAudioQualityString(p.audioQuality); t.complete(); }, "-Reading")); // - Cut tracks // --------------------------- add(new TaskCutTrackFiles()); // - Compress tracks // --------------------- add(new CTask((t) => { // Only encode the audio tracks foreach (CueTrack tr in (jobData.cd as CueReader).tracks) { if (!tr.isData) { addNextAsync(new TaskCompressTrack(tr)); } } t.complete(); }, "-Preparing")); // - Create new CUE file // -------------------- add(new CTask((t) => { CueReader cd = jobData.cd; // DEV: So far : // track.trackFile is UNSET. cd.saveCue needs it to be set. // track.workingFile points to a valid file, some might be in TEMP folder and some in input folder (data tracks) int stepProgress = (int)Math.Round(100.0f / cd.tracks.Count); // -- Move files to output folder foreach (var track in cd.tracks) { if (!cd.MULTIFILE) { // Fix the index times to start with 00:00:00 track.setNewTimesReset(); } string ext = Path.GetExtension(track.workingFile); track.trackFile = $"{cd.CD_TITLE} (track {track.trackNo}){ext}"; // Data track was not cut or encoded. // It's in the input folder, don't move it if (track.isData && cd.MULTIFILE) { FileTools.tryCopy(track.workingFile, Path.Combine(p.outputDir, track.trackFile)); } else { // TaskCompress already put the audio files on the output folder // But it's no big deal calling it again // This is for the data tracks that are on the temp folder FileTools.tryMove(track.workingFile, Path.Combine(p.outputDir, track.trackFile)); } t.PROGRESS += stepProgress; } //-- Create the new CUE file if (!cd.saveCUE(Path.Combine(p.outputDir, cd.CD_TITLE + ".cue"))) { t.fail(cd.ERROR); return; } t.complete(); }, "Finalizing")); // -- COMPLETE -- }// -----------------------------------------
// -- public JobCrush(CrushParams par) : base("Compress CD") { p = par; // Hack to fix progress hack_setExpectedProgTracks(p.expectedTracks + 3); // - Read CUE and some init // --------------------------- add(new CTask((t) => { var cd = new cd.CDInfos(); p.cd = cd; try{ cd.cueLoad(p.inputFile); }catch (haxe.lang.HaxeException e) { t.fail(msg: e.Message); return; } // Meaning the tracks are going to be CUT in the temp folder, so they are safe to be removed p.flag_sourceTracksOnTemp = (!cd.MULTIFILE && cd.tracks.length > 1); // In case user named the CD, otherwise it's going to be the same if (!string.IsNullOrWhiteSpace(p.cdTitle)) { cd.CD_TITLE = FileTools.sanitizeFilename(p.cdTitle); } // Real quality to string name cd.CD_AUDIO_QUALITY = AudioMaster.getCodecSettingsInfo(p.audioQuality); if (p.mode != 1) // Convert only does not require an archive { // Generate the final ARCHIVE path now that I have the CD TITLE p.finalArcPath = Path.Combine(p.outputDir, cd.CD_TITLE + p.archiver_ext); // Try to create a new archive in case it exists? while (File.Exists(p.finalArcPath)) { LOG.log("{0} already exists, adding (_) until unique", p.finalArcPath); // S is entire path without (.ext) string S = p.finalArcPath.Substring(0, p.finalArcPath.Length - p.archiver_ext.Length); p.finalArcPath = S + "_" + p.archiver_ext; } LOG.log("- Destination Archive : {0}", p.finalArcPath); } if (p.mode == 1) { // : ALWAYS Create a subfolder (when converting) to avoid overwriting the source files p.outputDir = CDCRUSH.checkCreateUniqueOutput(p.outputDir, p.cdTitle + CDCRUSH.RESTORED_FOLDER_SUFFIX); if (p.outputDir == null) { fail("Output Dir Error " + p.outputDir); return; } } jobData = p; // Some TASKS read jobData t.complete(); }, "-Reading", "Reading CUE data and preparing")); // - Cut tracks // --------------------------- add(new TaskCutTrackFiles()); // - Encode tracks // --------------------- add(new CTask((t) => { for (int i = 0; i < p.cd.tracks.length; i++) { cd.CDTrack tr = p.cd.tracks[i] as cd.CDTrack; // Do not encode DATA TRACKS to ECM when converting. if (p.mode > 0 && tr.isData) { continue; } addNextAsync(new TaskCompressTrack(tr)); } t.complete(); }, "-Preparing", "Preparing to compress tracks")); // - Prepare Tracks on CONVERT modes // - Needed for the new .CUE to be created // - if CONVERT MODE, move all files to output if (p.mode > 0) { add(new CTask((t) => { // DEV: So far : // track.trackFile is UNSET. cd.saveCue needs it to be set. // track.workingFile points to a valid file, some might be in TEMP folder and some in input folder (data tracks) int stepProgress = (int)Math.Round(100.0f / p.cd.tracks.length); // -- Move files to output folder for (int i = 0; i < p.cd.tracks.length; i++) { cd.CDTrack track = p.cd.tracks[i] as cd.CDTrack; if (!p.cd.MULTIFILE) { // Fix the index times to start with 00:00:00 track.rewriteIndexes_forMultiFile(); } string ext = Path.GetExtension(track.workingFile); // This tells what the files should be named in the `.cue` file: track.trackFile = $"{p.cd.CD_TITLE} (track {track.trackNo}){ext}"; // Data track was not cut or encoded. // It's in the input folder, don't move it if (track.isData && (p.cd.MULTIFILE || p.cd.tracks.length == 1)) { if (p.mode == 1) { FileTools.tryCopy(track.workingFile, Path.Combine(p.outputDir, track.trackFile)); track.workingFile = Path.Combine(p.outputDir, track.trackFile); } else { // I need to copy all files to TEMP, so that they can be renamed and archived FileTools.tryCopy(track.workingFile, Path.Combine(p.tempDir, track.trackFile)); track.workingFile = Path.Combine(p.tempDir, track.trackFile); } } else // encoded file that is on TEMP or OUTPUT { if (p.mode == 1) { // TaskCompress already put the audio files on the output folder // But it's no big deal calling it again // This is for the data tracks that are on the temp folder FileTools.tryMove(track.workingFile, Path.Combine(p.outputDir, track.trackFile)); track.workingFile = Path.Combine(p.outputDir, track.trackFile); } else { // Track that has been encoded and is on TEMP // It is currently named as "track_xx.xx" so rename it FileTools.tryMove(track.workingFile, Path.Combine(p.tempDir, track.trackFile)); track.workingFile = Path.Combine(p.tempDir, track.trackFile); } } t.PROGRESS += stepProgress; } // -- end processing tracks if (p.mode == 1) { p.new_cue_path = Path.Combine(p.outputDir, p.cd.CD_TITLE + ".cue"); } else { p.new_cue_path = Path.Combine(p.tempDir, p.cd.CD_TITLE + ".cue"); } //. Create the new CUE file try{ p.cd.cueSave( p.new_cue_path, new haxe.root.Array <object>(new [] { "CDCRUSH (dotNet) version : " + CDCRUSH.PROGRAM_VERSION, CDCRUSH.LINK_SOURCE })); }catch (haxe.lang.HaxeException e) { t.fail(msg: e.Message); return; } t.complete(); }, "Converting")); } // - Create an Archive // Add all tracks to the final archive // --------------------- if (p.mode != 1) { add(new CTask((t) => { // -- Get list of files to compress // . Tracks System.Collections.ArrayList files = new System.Collections.ArrayList(); for (var i = 0; i < p.cd.tracks.length; i++) { files.Add((p.cd.tracks[i] as cd.CDTrack).workingFile); // Working file is valid, was set earlier } if (p.mode == 0) // Only on CDCRUSH add cover and json data { // . Settings string path_settings = Path.Combine(p.tempDir, CDCRUSH.CDCRUSH_SETTINGS); try{ p.cd.jsonSave(path_settings); files.Add(path_settings); }catch (haxe.lang.HaxeException e) { t.fail(msg: e.Message); return; } // . Cover Image string path_cover; if (p.cover != null) { path_cover = Path.Combine(p.tempDir, CDCRUSH.CDCRUSH_COVER); File.Copy(p.cover, path_cover); files.Add(path_cover); } else { path_cover = null; } } else { // It must be CONVERT + ARCHIVE files.Add(p.new_cue_path); } // -. Compress whatever files are on var arc = ArchiveMaster.getArchiver(p.finalArcPath); string arcStr = ArchiveMaster.getCompressionSettings(p.archiveSettingsInd).Item2; arc.compress((string[])files.ToArray(typeof(string)), jobData.finalArcPath, -1, arcStr); arc.onProgress = (pr) => t.PROGRESS = pr; arc.onComplete = (s) => { if (s) { // NOTE: This var is autowritten whenever a compress operation is complete p.crushedSize = (int)arc.COMPRESSED_SIZE; // IMPORTANT to write to jobdata, because it is not a pointer and this needs to be read externally jobData = p; t.complete(); } else { fail(arc.ERROR); } }; t.killExtra = () => arc.kill(); }, "Compressing", "Compressing everything into an archive")); } // -- COMPLETE -- add(new CTask((t) => { LOG.log("== Detailed CD INFOS =="); LOG.log(p.cd.getDetailedInfo()); t.complete(); }, "-complete")); }// -----------------------------------------
}// ----------------------------------------- // -- public override void start() { base.start(); p = (CrushParams)jobData; // Working file is already set and points to either TEMP or INPUT folder sourceTrackFile = track.workingFile; // Before compressing the tracks, get and store the MD5 of the track using (var md5 = System.Security.Cryptography.MD5.Create()) { using (var str = File.OpenRead(sourceTrackFile)) { track.md5 = BitConverter.ToString(md5.ComputeHash(str)).Replace("-", "").ToLower(); } } // -- if (track.isData) { var ecm = new EcmTools(CDCRUSH.TOOLS_PATH); ecm.onProgress = handleProgress; ecm.onComplete = (s) => { if (s) { deleteOldFile(); complete(); } else { fail(msg: ecm.ERROR); } }; // In case the task ends abruptly killExtra = () => ecm.kill(); // New filename that is going to be generated: setupFiles(".bin.ecm"); ecm.ecm(sourceTrackFile, track.workingFile); // old .bin file from wherever it was to temp/bin.ecm } else // AUDIO TRACK : { var ffmp = new FFmpeg(CDCRUSH.FFMPEG_PATH); ffmp.onProgress = handleProgress; ffmp.onComplete = (s) => { if (s) { deleteOldFile(); complete(); } else { fail(msg: ffmp.ERROR); } }; // In case the task ends abruptly killExtra = () => ffmp.kill(); // Cast for easy coding Tuple <int, int> audioQ = jobData.audioQuality; // NOTE: I know this redundant, but it works : switch (audioQ.Item1) { case 0: // FLAC setupFiles(".flac"); ffmp.audioPCMToFlac(sourceTrackFile, track.workingFile); break; case 1: // VORBIS setupFiles(".ogg"); ffmp.audioPCMToOggVorbis(sourceTrackFile, audioQ.Item2, track.workingFile); break; case 2: // OPUS setupFiles(".ogg"); // Opus needs an actual bitrate, not an index ffmp.audioPCMToOggOpus(sourceTrackFile, FFmpeg.OPUS_QUALITY[audioQ.Item2], track.workingFile); break; case 3: // MP3 setupFiles(".mp3"); ffmp.audioPCMToMP3(sourceTrackFile, audioQ.Item2, track.workingFile); break; } //- end switch } //- end if (track.isData) }// -----------------------------------------
// -- public JobConvertCue(CrushParams p) : base("Convert CD") { // Check for input files // :: -------------------- if (!CDCRUSH.check_file_(p.inputFile, ".cue")) { fail(msg: CDCRUSH.ERROR); return; } if (string.IsNullOrEmpty(p.outputDir)) { p.outputDir = Path.GetDirectoryName(p.inputFile); } // : NEW : // : ALWAYS Create a subfolder to avoid overwriting the source files try { p.outputDir = Path.Combine(p.outputDir, p.cdTitle + FOLDER_SUFFIX); } catch (ArgumentException) { fail("Output Dir Error " + p.outputDir); return; } if (!FileTools.createDirectory(p.outputDir)) { fail(msg: "Can't create Output Dir " + p.outputDir); return; } p.tempDir = Path.Combine(CDCRUSH.TEMP_FOLDER, Guid.NewGuid().ToString().Substring(0, 12)); if (!FileTools.createDirectory(p.tempDir)) { fail(msg: "Can't create TEMP dir"); return; } // IMPORTANT!! sharedData gets set by value, NOT A POINTER, do not make changes to p after this jobData = p; // -- // - Read the CUE file :: add(new CTask((t) => { var cd = new CueReader(); jobData.cd = cd; if (!cd.load(p.inputFile)) { t.fail(msg: cd.ERROR); return; } // Post CD CUE load :: // In case user named the CD, otherwise it's going to be the same if (!string.IsNullOrWhiteSpace(p.cdTitle)) { cd.CD_TITLE = FileTools.sanitizeFilename(p.cdTitle); } // Real quality to string name cd.CD_AUDIO_QUALITY = CDCRUSH.getAudioQualityString(p.audioQuality); // This flag notes that all files will go to the TEMP folder jobData.workFromTemp = !cd.MULTIFILE; t.complete(); }, "Reading", true)); // - Cut tracks // --------------------------- add(new TaskCutTrackFiles()); // - Compress tracks // --------------------- add(new CTask((t) => { // Only encode the audio tracks foreach (CueTrack tr in (jobData.cd as CueReader).tracks) { if (!tr.isData) { addNextAsync(new TaskCompressTrack(tr)); } } t.complete(); }, "Preparing")); // - Create new CUE file // -------------------- add(new CTask((t) => { CueReader cd = jobData.cd; // DEV: So far : // track.trackFile is UNSET. cd.saveCue needs it to be set. // track.workingFile points to a valid file, some might be in TEMP folder and some in input folder (data tracks) // -- Move files to output folder foreach (var track in cd.tracks) { if (!cd.MULTIFILE) { // Fix the index times to start with 00:00:00 track.setNewTimesReset(); } string ext = Path.GetExtension(track.workingFile); track.trackFile = $"{cd.CD_TITLE} (track {track.trackNo}){ext}"; // Data track was not cut or encoded. // It's in the input folder, don't move it if (track.isData && cd.MULTIFILE) { FileTools.tryCopy(track.workingFile, Path.Combine(p.outputDir, track.trackFile)); } else { FileTools.tryMove(track.workingFile, Path.Combine(p.outputDir, track.trackFile)); } } //-- Create the new CUE file if (!cd.saveCUE(Path.Combine(p.outputDir, cd.CD_TITLE + ".cue"))) { t.fail(cd.ERROR); return; } t.complete(); }, "Finalizing")); // -- COMPLETE -- }// -----------------------------------------
}// ----------------------------------------- // -- public override void start() { base.start(); p = (CrushParams)jobData; // Working file is already set and points to either TEMP or INPUT folder trackFile = track.workingFile; // Before compressing the tracks, get and store the MD5 of the track using (var md5 = System.Security.Cryptography.MD5.Create()) { using (var str = File.OpenRead(trackFile)) { track.md5 = BitConverter.ToString(md5.ComputeHash(str)).Replace("-", "").ToLower(); } } // -- if (track.isData) { var ecm = new EcmTools(CDCRUSH.TOOLS_PATH); ecm.onComplete = (s) => { if (s) { deleteOldFile(); complete(); } else { fail(msg: ecm.ERROR); } }; // In case the task ends abruptly killExtra = () => ecm.kill(); // New filename that is going to be generated: track.storedFileName = track.getTrackName() + ".bin.ecm"; track.workingFile = Path.Combine(jobData.tempDir, track.storedFileName); ecm.ecm(trackFile, track.workingFile); // old .bin file from wherever it was to temp/bin.ecm } else // AUDIO TRACK : { var ffmp = new FFmpeg(CDCRUSH.FFMPEG_PATH); ffmp.onComplete = (s) => { if (s) { deleteOldFile(); complete(); } else { fail(msg: ffmp.ERROR); } }; // In case the task ends abruptly killExtra = () => ffmp.kill(); // Cast for easy coding Tuple <int, int> audioQ = jobData.audioQuality; switch (audioQ.Item1) { case 0: // FLAC track.storedFileName = track.getTrackName() + ".flac"; track.workingFile = Path.Combine(jobData.tempDir, track.storedFileName); ffmp.audioPCMToFlac(trackFile, track.workingFile); break; case 1: // VORBIS track.storedFileName = track.getTrackName() + ".ogg"; track.workingFile = Path.Combine(jobData.tempDir, track.storedFileName); ffmp.audioPCMToOggVorbis(trackFile, audioQ.Item2, track.workingFile); break; case 2: // OPUS track.storedFileName = track.getTrackName() + ".ogg"; track.workingFile = Path.Combine(jobData.tempDir, track.storedFileName); ffmp.audioPCMToOggOpus(trackFile, CDCRUSH.OPUS_QUALITY[audioQ.Item2], track.workingFile); break; } //- end switch } //- end if (track.isData) }// -----------------------------------------