/// <summary> /// Convert audio or video to mp3 and display progress dialog. /// </summary> private bool convertToMp3(string file, string stream, string progressText, DialogProgress dialogProgress, DateTime entireClipStartTime, DateTime entireClipEndTime, string tempMp3Filename) { bool status = true; DateTime entireClipDuration = UtilsSubs.getDurationTime(entireClipStartTime, entireClipEndTime); DialogProgress.updateProgressInvoke(dialogProgress, progressText); // Enable detail mode in progress dialog DialogProgress.enableDetailInvoke(dialogProgress, true); // Set the duration of the clip in the progress dialog (for detail mode) DialogProgress.setDuration(dialogProgress, entireClipDuration); // Rip the audio to a temporary mp3 file UtilsAudio.ripAudioFromVideo(file, stream, entireClipStartTime, entireClipEndTime, Settings.Instance.AudioClips.Bitrate, tempMp3Filename, dialogProgress); DialogProgress.enableDetailInvoke(dialogProgress, false); FileInfo fileInfo = new FileInfo(tempMp3Filename); // Error if the temporary mp3 file doesn't exist or is zero bytes if (!File.Exists(tempMp3Filename) || fileInfo.Length == 0) { status = false; } return(status); }
/// <summary> /// Gets called when all subtitle processing is finished (or cancelled). /// </summary> private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (dialogProgress != null) { dialogProgress.Hide(); dialogProgress = null; } if (e.Error != null) { UtilsMsg.showErrMsg(e.Error.Message); return; } if (e.Cancelled) { MessageBox.Show("Action cancelled.", UtilsAssembly.Title, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } TimeSpan workerTotalTime = DateTime.Now - workerStartTime; WorkerVars workerVars = e.Result as WorkerVars; string srsFormat = getSrsFormatList(); string endMessage = $"Processing completed in {workerTotalTime.TotalMinutes:0.00} minutes.\n\n{srsFormat}"; UtilsMsg.showInfoMsg(endMessage); }
/// <summary> /// Called when the audio extraction thread completes. /// </summary> private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (dialogProgress != null) { dialogProgress.Hide(); dialogProgress = null; } if (e.Error != null) { UtilsMsg.showErrMsg(e.Error.Message); return; } if (e.Cancelled) { MessageBox.Show("Action cancelled.", UtilsAssembly.Title, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } TimeSpan workerTotalTime = DateTime.Now - this.workerStartTime; WorkerVars workerVars = e.Result as WorkerVars; string endMessage = String.Format("Audio extraction completed in {0:0.00} minutes.", workerTotalTime.TotalMinutes); UtilsMsg.showInfoMsg(endMessage); }
/// <summary> /// Gets called when all preview's subtitle processing is finished (or cancelled). /// </summary> private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (dialogProgress != null) { dialogProgress.Hide(); dialogProgress = null; } if (e.Error != null) { UtilsMsg.showErrMsg(e.Error.Message); return; } if (e.Cancelled) { return; } WorkerVars workerVars = e.Result as WorkerVars; previewWorkerVars = workerVars; updateStats(); populateLinesListView(this.comboBoxEpisode.SelectedIndex); Logger.Instance.info("Preview: COMPLETED"); }
/// <summary> /// Set the onlyNeededForContext flag for lines that are inactive but needed for context purposes. /// Call inactivateLines() before calling this routine. /// </summary> public List <List <InfoCombined> > markLinesOnlyNeededForContext(WorkerVars workerVars, DialogProgress dialogProgress) { int totalLines = 0; int progessCount = 0; foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { totalLines += combArray.Count; } // For each episode foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { // For each line in episode for (int lineIdx = 0; lineIdx < combArray.Count; lineIdx++) { InfoCombined comb = combArray[lineIdx]; progessCount++; if (comb.Active) { // Leading for (int i = Math.Max(0, lineIdx - Settings.Instance.ContextLeadingCount); i < lineIdx; i++) { if (!combArray[i].Active) { combArray[i].OnlyNeededForContext = true; } } // Trailing for (int i = Math.Min(combArray.Count - 1, lineIdx + 1); i < Math.Min(combArray.Count - 1, lineIdx + Settings.Instance.ContextTrailingCount + 1); i++) { if (!combArray[i].Active) { combArray[i].OnlyNeededForContext = true; } } } string progressText = $"Find lines needed for context: {Convert.ToInt32(progessCount * (100.0 / totalLines))}%"; int progress = Convert.ToInt32(progessCount * (100.0 / totalLines)); DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); if (dialogProgress.Cancel) { return(null); } } } return(workerVars.CombinedAll); }
/// <summary> /// Generate snapshots for all episodes. /// </summary> public bool genSnapshots(WorkerVars workerVars, DialogProgress dialogProgress) { int progessCount = 0; int episodeCount = 0; int totalEpisodes = workerVars.CombinedAll.Count; int totalLines = UtilsSubs.getTotalLineCount(workerVars.CombinedAll); DateTime lastTime = UtilsSubs.getLastTime(workerVars.CombinedAll); UtilsName name = new UtilsName(Settings.Instance.DeckName, totalEpisodes, totalLines, lastTime, Settings.Instance.VideoClips.Size.Width, Settings.Instance.VideoClips.Size.Height); // For each episode foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { episodeCount++; // For each line in episode, generate a snapshot foreach (InfoCombined t in combArray) { progessCount++; string progressText = $"Generating snapshot: {progessCount.ToString()} of {totalLines.ToString()}"; int progress = Convert.ToInt32(progessCount * (100.0 / totalLines)); // Update the progress dialog DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); InfoCombined comb = t; DateTime startTime = comb.Subs1.StartTime; DateTime endTime = comb.Subs1.EndTime; DateTime midTime = UtilsSubs.getMidpointTime(startTime, endTime); string videoFileName = Settings.Instance.VideoClips.Files[episodeCount - 1]; // Create output filename string nameStr = name.createName(ConstantSettings.SnapshotFilenameFormat, (int)episodeCount + Settings.Instance.EpisodeStartNumber - 1, progessCount, startTime, endTime, comb.Subs1.Text, comb.Subs2.Text); string outFile = $"{workerVars.MediaDir}{Path.DirectorySeparatorChar}{nameStr}"; // {2} // Generate snapshot UtilsSnapshot.takeSnapshotFromVideo(videoFileName, midTime, Settings.Instance.Snapshots.Size, Settings.Instance.Snapshots.Crop, outFile); // Did the user press the cancel button? if (dialogProgress.Cancel) { return(false); } } } return(true); }
public static void setDuration(DialogProgress dialogProgress, DateTime duration) { // Wait for thread to become avaiable if (dialogProgress.IsHandleCreated) { dialogProgress.Invoke((MethodInvoker) delegate() { dialogProgress.Duration = duration; }); } }
public static void enableDetailInvoke(DialogProgress dialogProgress, bool enabled) { // Wait for thread to become avaiable if (dialogProgress.IsHandleCreated) { dialogProgress.Invoke((MethodInvoker) delegate() { dialogProgress.DetailedProgress = enabled; }); } }
public static void updateProgressInvoke(DialogProgress dialogProgress, string text) { // Wait for thread to become avaiable if (dialogProgress.IsHandleCreated) { dialogProgress.Invoke((MethodInvoker) delegate() { dialogProgress.updateProgress(text); }); } }
public static void updateDetailedProgressInvoke(DialogProgress dialogProgress, int progress, InfoFFmpegProgress ffmpegProgress) { // Wait for thread to become avaiable if (dialogProgress.IsHandleCreated) { dialogProgress.Invoke((MethodInvoker) delegate() { dialogProgress.updateDetailedProgress(progress, ffmpegProgress); }); } }
public static void nextStepInvoke(DialogProgress dialogProgress, int step, string stepName) { // Wait for thread to become avaiable if (dialogProgress.IsHandleCreated) { dialogProgress.Invoke((MethodInvoker) delegate() { dialogProgress.StepName = stepName; dialogProgress.StepsCurrent = step; }); } }
public static DataReceivedEventHandler getFFmpegOutputHandler(DialogProgress dialogProgress) { DataReceivedEventHandler outHandler = null; // Wait for thread to become avaiable if (dialogProgress.IsHandleCreated) { dialogProgress.Invoke((MethodInvoker) delegate() { outHandler = dialogProgress.FFmpegOutputHandler; }); } return(outHandler); }
public static bool getCancelInvoke(DialogProgress dialogProgress) { bool cancelState = false; // Wait for thread to become avaiable if (dialogProgress.IsHandleCreated) { dialogProgress.Invoke((MethodInvoker) delegate() { cancelState = dialogProgress.Cancel; }); } return(cancelState); }
/// <summary> /// Start the processing in a seperate thread. /// If combinedAll is not null, use it rather then generating a new combinedAll. /// </summary> public void start(List <List <InfoCombined> > combinedAll) { // Create directory stucture try { createOutputDirStructure(); } catch { UtilsMsg.showErrMsg("Cannot write to output directory. \nTry checking the directory's permissions."); return; } Logger.Instance.info("SubsProcessor.start"); Logger.Instance.writeSettingsToLog(); // Start the worker thread try { WorkerVars workerVars = new WorkerVars(combinedAll, getMediaDir(Settings.Instance.OutputDir, Settings.Instance.DeckName), WorkerVars.SubsProcessingType.Normal); // Create a background thread BackgroundWorker bw = new BackgroundWorker(); bw.DoWork += new DoWorkEventHandler(bw_DoWork); bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); // Create a progress dialog on the UI thread dialogProgress = new DialogProgress(); currentStep = 0; dialogProgress.StepsTotal = determineNumSteps(combinedAll); workerStartTime = DateTime.Now; bw.RunWorkerAsync(workerVars); // Lock up the UI with this modal progress form dialogProgress.ShowDialog(); dialogProgress = null; } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong before processing could start.\n" + e1); return; } }
public void ffmpegOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { if (!String.IsNullOrEmpty(outLine.Data)) { InfoFFmpegProgress ffmpegProgress = new InfoFFmpegProgress(); bool parseSuccess = ffmpegProgress.parseFFmpegProgress(outLine.Data); if (parseSuccess) { int progress = (int)((ffmpegProgress.Time.TimeOfDay.TotalMilliseconds / Math.Max(1, Duration.TimeOfDay.TotalMilliseconds)) * 100); DialogProgress.updateDetailedProgressInvoke(this, progress, ffmpegProgress); // Debug //TextWriter writer = new StreamWriter("ffmpeg_output.txt", true, Encoding.UTF8); //writer.WriteLine(outLine.Data); //writer.WriteLine(String.Format("Progress: {0:00}", progress)); //writer.Close(); } } }
/// <summary> /// Extract the audio from the media. /// </summary> private void buttonExtract_Click(object sender, EventArgs e) { errorProvider1.Clear(); if (validateForm()) { updateSettings(); Logger.Instance.info("Extract Audio From Media: GO!"); Logger.Instance.writeSettingsToLog(); // Start the worker thread try { WorkerVars workerVars = new WorkerVars(null, Settings.Instance.OutputDir, WorkerVars.SubsProcessingType.Normal); // Create a background thread BackgroundWorker bw = new BackgroundWorker(); bw.DoWork += new DoWorkEventHandler(splitAudio); bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); // Create a progress dialog on the UI thread dialogProgress = new DialogProgress(); this.workerStartTime = DateTime.Now; bw.RunWorkerAsync(workerVars); // Lock up the UI with this modal progress form dialogProgress.ShowDialog(); dialogProgress = null; } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong before processing could start.\n" + e1); return; } } }
/// <summary> /// Called when the Dueling Subtitles creation thread completes. /// </summary> private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (dialogProgress != null) { dialogProgress.Hide(); dialogProgress = null; } if (e.Error != null) { UtilsMsg.showErrMsg(e.Error.Message); return; } if (e.Cancelled) { MessageBox.Show("Action cancelled.", UtilsAssembly.Title, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } WorkerVars workerVars = e.Result as WorkerVars; UtilsMsg.showInfoMsg("Dueling subtitles have been created successfully."); }
/// <summary> /// Pair lines from Subs1 to Subs2. /// /// Subs2srs takes a two-pass approach when it combines Subs1 and Subs2. The first pass matches a line from /// Subs1 to the closest line in Subs2. Closest here meaning the difference in start times. This will work fine /// assuming that both Subs1 and Subs2 have the exact same number of lines and that lines are similarly timed. /// Naturally, this is almost never the case. The result being that some lines will be skipped and others repeated. /// The second pass adds back in any lines that were skipped by combining them with others lines and doesn't allow /// the same line to be repeated. /// /// This is used by the Main Dialog, Preview, Dueling Subtitles tools, and Extract Audio from Media Tool. /// </summary> public List <List <InfoCombined> > combineAllSubs(WorkerVars workerVars, DialogProgress dialogProgress) { List <List <InfoCombined> > combinedAll = new List <List <InfoCombined> >(); int totalEpisodes = Settings.Instance.Subs[0].Files.Length; // For each episode for (int epIdx = 0; epIdx < totalEpisodes; epIdx++) { SubsParser subs1Parser = null; SubsParser subs2Parser = null; List <InfoLine> subs1LineInfos = null; List <InfoLine> subs2LineInfos = null; string curSub1File = Settings.Instance.Subs[0].Files[epIdx]; string curSub2File = ""; bool subs1ContainsVobsubs = UtilsSubs.filePatternContainsVobsubs(Settings.Instance.Subs[0].FilePattern); bool subs2ContainsVobsubs = UtilsSubs.filePatternContainsVobsubs(Settings.Instance.Subs[1].FilePattern); string progressText = $"Processing subtitles for episode {epIdx + 1} of {totalEpisodes}"; if (subs1ContainsVobsubs || subs2ContainsVobsubs) { progressText += "\nNote: Vobsubs (.idx/.sub) can take a few moments to process."; } int progress = Convert.ToInt32((epIdx + 1) * (100.0 / totalEpisodes)); DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); int streamSubs1 = 0; int streamSubs2 = 0; // In the subtitles are in Vobsub format, get the stream to use if (subs1ContainsVobsubs) { streamSubs1 = Convert.ToInt32(Settings.Instance.Subs[0].VobsubStream.Num); } if (subs2ContainsVobsubs) { streamSubs2 = Convert.ToInt32(Settings.Instance.Subs[1].VobsubStream.Num); } // Parse Subs1 subs1Parser = UtilsSubs.getSubtitleParserType(workerVars, curSub1File, streamSubs1, epIdx + Settings.Instance.EpisodeStartNumber, 1, Encoding.GetEncoding(Settings.Instance.Subs[0].Encoding)); Logger.Instance.writeFileToLog(curSub1File, Encoding.GetEncoding(Settings.Instance.Subs[0].Encoding)); subs1LineInfos = subs1Parser.parse(); if (subs1LineInfos == null) { return(null); } subs1LineInfos = removeIncorrectlyTimedLines(subs1LineInfos); if (Settings.Instance.Subs[0].JoinSentencesEnabled && !subs1ContainsVobsubs) { subs1LineInfos = combinePartialLinesIntoSentence(subs1LineInfos, Settings.Instance.Subs[0].JoinSentencesCharList); } // Apply Subs1 time shift if (Settings.Instance.TimeShiftEnabled) { foreach (InfoLine line in subs1LineInfos) { line.StartTime = UtilsSubs.shiftTiming(line.StartTime, Settings.Instance.Subs[0].TimeShift); line.EndTime = UtilsSubs.shiftTiming(line.EndTime, Settings.Instance.Subs[0].TimeShift); } } // Parse Subs2 (if needed) if (Settings.Instance.Subs[1].Files.Length != 0) { curSub2File = Settings.Instance.Subs[1].Files[epIdx]; subs2Parser = UtilsSubs.getSubtitleParserType(workerVars, curSub2File, streamSubs2, epIdx + Settings.Instance.EpisodeStartNumber, 2, Encoding.GetEncoding(Settings.Instance.Subs[1].Encoding)); Logger.Instance.writeFileToLog(curSub2File, Encoding.GetEncoding(Settings.Instance.Subs[1].Encoding)); subs2LineInfos = subs2Parser.parse(); if (subs2LineInfos == null) { return(null); } subs2LineInfos = removeIncorrectlyTimedLines(subs2LineInfos); if (Settings.Instance.Subs[1].JoinSentencesEnabled && !subs2ContainsVobsubs) { subs2LineInfos = combinePartialLinesIntoSentence(subs2LineInfos, Settings.Instance.Subs[1].JoinSentencesCharList); } // Apply Subs2 time shift if (Settings.Instance.TimeShiftEnabled) { foreach (InfoLine line in subs2LineInfos) { line.StartTime = UtilsSubs.shiftTiming(line.StartTime, Settings.Instance.Subs[1].TimeShift); line.EndTime = UtilsSubs.shiftTiming(line.EndTime, Settings.Instance.Subs[1].TimeShift); } } } else { subs2LineInfos = subs1LineInfos; } // Pass 1: Match each Subs1 to the closest Subs2 List <InfoCombined> combinedSubs = pass1CombineSubs(subs1LineInfos, subs2LineInfos); if (dialogProgress.Cancel) { return(null); } // Pass 2: Fix mismatches present after Pass 1. if (Settings.Instance.Subs[1].Files.Length != 0) { combinedSubs = pass2FixMismatches(combinedSubs, subs2LineInfos); if (dialogProgress.Cancel) { return(null); } } // Adjust the timings based on user preference foreach (InfoCombined comb in combinedSubs) { // Use Subs2 timings? if (Settings.Instance.Subs[1].TimingsEnabled) { // Important Note: // The rest of the software uses subs1 for timing, so just cram subs2 timings into subs1 comb.Subs1.StartTime = comb.Subs2.StartTime; comb.Subs1.EndTime = comb.Subs2.EndTime; } } if (dialogProgress.Cancel) { return(null); } // Add this episode's paired lines combinedAll.Add(combinedSubs); } return(combinedAll); }
/// <summary> /// Generate the preview in seperate thread. /// </summary> private void generatePreview() { // Fire event to tell MainForm to update the settings if (GeneretePreview != null) { GeneretePreview(this, EventArgs.Empty); } populateEpisodeComboBox(); string tempPreviewDir = Path.GetTempPath() + ConstantSettings.TempPreviewDirName; if (Directory.Exists(tempPreviewDir)) { try { Directory.Delete(tempPreviewDir, true); } catch { //UtilsMsg.showErrMsg("Unable to delete the temporary directory at:\n" + tempPreviewDir); } } // Create the temporary directory try { Directory.CreateDirectory(tempPreviewDir); } catch { UtilsMsg.showErrMsg("Cannot write to " + tempPreviewDir + "\nTry checking the directory's permissions."); return; } Logger.Instance.info("Preview: GO!"); Logger.Instance.writeSettingsToLog(); // Start the worker thread try { WorkerVars workerVars = new WorkerVars(null, tempPreviewDir, WorkerVars.SubsProcessingType.Preview); // Create a background thread BackgroundWorker bw = new BackgroundWorker(); bw.DoWork += new DoWorkEventHandler(bw_DoWork); bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); // Create a progress dialog on the UI thread dialogProgress = new DialogProgress(); bw.RunWorkerAsync(workerVars); // Lock up the UI with this modal progress form dialogProgress.ShowDialog(); dialogProgress = null; } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong before preview could be generated.\n" + e1); return; } }
/// <summary> /// Generate the SRS import file. /// </summary> public bool genSrs(WorkerVars workerVars, DialogProgress dialogProgress) { int episodeIdx = -1; int totalEpisodes = workerVars.CombinedAll.Count; int lineIdx = -1; int totalLines = UtilsSubs.getTotalLineCount(workerVars.CombinedAll); lastTime = UtilsSubs.getLastTime(workerVars.CombinedAll); name = new UtilsName(Settings.Instance.DeckName, totalEpisodes, totalLines, lastTime, Settings.Instance.VideoClips.Size.Width, Settings.Instance.VideoClips.Size.Height); string nameStr = name.createName(ConstantSettings.SrsFilenameFormat, 0, 0, new DateTime(), new DateTime(), "", ""); // Create filename // Example: <outdir>\Toki_wo_Kakeru_Shoujo.tsv string srsFilename = $"{Settings.Instance.OutputDir}{Path.DirectorySeparatorChar}{nameStr}"; TextWriter srsWriter = new StreamWriter(srsFilename, false, Encoding.UTF8); // For each episode foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { episodeIdx++; lineIdx = -1; // For each line in episode, process foreach (InfoCombined comb in combArray) { progessCount++; lineIdx++; mainComb = comb; // Skip lines that are only needed for context if (mainComb.OnlyNeededForContext) { continue; } string srsLine = ""; // Format the current import file line srsLine = formatAll(workerVars.CombinedAll, episodeIdx, lineIdx, FormatType.Normal); // Add leading context lines if (Settings.Instance.ContextLeadingCount > 0) { for (int i = Settings.Instance.ContextLeadingCount; i > 0; i--) { int prevLineIdx = lineIdx - i; if (prevLineIdx >= 0) { if (isLineInContextRange(combArray, lineIdx, prevLineIdx)) { srsLine += formatAll(workerVars.CombinedAll, episodeIdx, prevLineIdx, FormatType.Leading); } else { srsLine += formatContextPlaceholder(FormatType.Leading); } } else { srsLine += formatContextPlaceholder(FormatType.Leading); } } } // Add trailing context lines if (Settings.Instance.ContextTrailingCount > 0) { for (int i = 1; i <= Settings.Instance.ContextTrailingCount; i++) { int nextLineIdx = lineIdx + i; if (nextLineIdx < combArray.Count) { if (isLineInContextRange(combArray, lineIdx, nextLineIdx)) { srsLine += formatAll(workerVars.CombinedAll, episodeIdx, nextLineIdx, FormatType.Trailing); } else { srsLine += formatContextPlaceholder(FormatType.Trailing); } } else { srsLine += formatContextPlaceholder(FormatType.Trailing); } } } // Write line to file srsWriter.WriteLine(srsLine); string progressText = $"Generating SRS (ex. Anki) import file: line {progessCount.ToString()} of {totalLines.ToString()}"; int progress = Convert.ToInt32(progessCount * (100.0 / totalLines)); DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); // Did the user press the cancel button? if (dialogProgress.Cancel) { srsWriter.Close(); return(false); } } } srsWriter.Close(); return(true); }
/// <summary> /// Call ffmpeg with provided arguments and update the progress dialog. /// No windows will popup. If Cancel is pressed in the progress dialog, the process will be killed. /// </summary> static public void startFFmpegProgress(string ffmpegAudioProgArgs, DialogProgress dialogProgress) { Process ffmpegProcess = new Process(); bool tryAgain = true; bool useShellExecute = false; bool createNoWindow = true; // Try several different ways of calling ffmpeg because of the dreaded // "System.ComponentModel.Win32Exception: The system cannot find the drive specified" exception // Try relative path from subs2srs.exe try { ffmpegProcess.StartInfo.FileName = ConstantSettings.PathFFmpegExe; ffmpegProcess.StartInfo.Arguments = ffmpegAudioProgArgs; ffmpegProcess.StartInfo.UseShellExecute = useShellExecute; ffmpegProcess.StartInfo.CreateNoWindow = createNoWindow; ffmpegProcess.StartInfo.RedirectStandardError = true; ffmpegProcess.ErrorDataReceived += new DataReceivedEventHandler(DialogProgress.getFFmpegOutputHandler(dialogProgress)); ffmpegProcess.Start(); ffmpegProcess.BeginErrorReadLine(); // Loop until process has exited while (!ffmpegProcess.HasExited) { Thread.Sleep(100); // If the Cancel button was pressed if (DialogProgress.getCancelInvoke(dialogProgress)) { try { ffmpegProcess.Kill(); } catch { // Don't care } break; } } tryAgain = false; } catch { try { ffmpegProcess.Kill(); } catch { // Dont care } tryAgain = true; } // Try absolute path to ffmpeg.exe if (tryAgain) { ffmpegProcess = new Process(); try { ffmpegProcess.StartInfo.FileName = ConstantSettings.PathFFmpegFullExe; ffmpegProcess.StartInfo.Arguments = ffmpegAudioProgArgs; ffmpegProcess.StartInfo.UseShellExecute = useShellExecute; ffmpegProcess.StartInfo.CreateNoWindow = createNoWindow; ffmpegProcess.StartInfo.RedirectStandardError = true; ffmpegProcess.ErrorDataReceived += new DataReceivedEventHandler(DialogProgress.getFFmpegOutputHandler(dialogProgress)); ffmpegProcess.Start(); ffmpegProcess.BeginErrorReadLine(); // Loop until process has exited while (!ffmpegProcess.HasExited) { Thread.Sleep(100); // If the Cancel button was pressed if (DialogProgress.getCancelInvoke(dialogProgress)) { try { ffmpegProcess.Kill(); } catch { // Don't care } break; } } tryAgain = false; } catch (Exception) { try { ffmpegProcess.Kill(); } catch { // Dont care } tryAgain = true; } } // Try setting PATH to include the absolute path of ffmpeg.exe if (tryAgain) { try { string oldPath = Environment.GetEnvironmentVariable("Path"); string ffmpegDir = Path.GetDirectoryName(ConstantSettings.PathFFmpegFullExe); if (!oldPath.Contains(ffmpegDir)) { string newPath = oldPath + ";" + ffmpegDir; Environment.SetEnvironmentVariable("Path", newPath); } ffmpegProcess = new Process(); ffmpegProcess.StartInfo.FileName = ConstantSettings.ExeFFmpeg; ffmpegProcess.StartInfo.Arguments = ffmpegAudioProgArgs; ffmpegProcess.StartInfo.UseShellExecute = useShellExecute; ffmpegProcess.StartInfo.CreateNoWindow = createNoWindow; ffmpegProcess.StartInfo.RedirectStandardError = true; ffmpegProcess.ErrorDataReceived += new DataReceivedEventHandler(DialogProgress.getFFmpegOutputHandler(dialogProgress)); ffmpegProcess.Start(); ffmpegProcess.BeginErrorReadLine(); // Loop until process has exited while (!ffmpegProcess.HasExited) { Thread.Sleep(100); // If the Cancel button was pressed if (DialogProgress.getCancelInvoke(dialogProgress)) { try { ffmpegProcess.Kill(); } catch { // Don't care } break; } } } catch { // Don't care } } }
/// <summary> /// Rip (and re-encode) a portion of the audio from a video file. /// </summary> public static void ripAudioFromVideo(string inFile, string stream, DateTime startTime, DateTime endTime, int bitrate, string outFile, DialogProgress dialogProgress) { string audioBitrateArg = UtilsVideo.formatAudioBitrateArg(bitrate); string audioMapArg = UtilsVideo.formatAudioMapArg(stream); string timeArg = UtilsVideo.formatStartTimeAndDurationArg(startTime, endTime); string ffmpegAudioProgArgs = ""; // Example format: // -vn -y -i "G:\Temp\inputs.mkv" -ac 2 -map 0:1 -ss 00:03:32.420 -t 00:02:03.650 -b:a 128k -threads 0 "output.mp3" ffmpegAudioProgArgs = $"-vn -y -i \"{inFile}\" -ac 2 {audioMapArg} {timeArg} {audioBitrateArg} -threads 0 \"{outFile}\""; // {4} if (dialogProgress == null) { UtilsCommon.startFFmpeg(ffmpegAudioProgArgs, false, true); } else { UtilsCommon.startFFmpegProgress(ffmpegAudioProgArgs, dialogProgress); } }
/// <summary> /// Rip (and re-encode) a portion of the audio from a video file. /// </summary> public static void ripAudioFromVideo(string inFile, string stream, DateTime startTime, DateTime endTime, int bitrate, string outFile, DialogProgress dialogProgress) { string audioBitrateArg = UtilsVideo.formatAudioBitrateArg(bitrate); string audioMapArg = UtilsVideo.formatAudioMapArg(stream); string timeArg = UtilsVideo.formatStartTimeAndDurationArg(startTime, endTime); string ffmpegAudioProgArgs = ""; // Example format: // -vn -y -i "G:\Temp\inputs.mkv" -ac 2 -map 0:1 -ss 00:03:32.420 -t 00:02:03.650 -b:a 128k -threads 0 "output.mp3" ffmpegAudioProgArgs = String.Format("-vn -y -i \"{0}\" -ac 2 {1} {2} {3} -threads 0 \"{4}\"", // Video file inFile, // {0} // Mapping audioMapArg, // {1} // Time span timeArg, // {2} // Bitrate audioBitrateArg, // {3} // Output file name outFile); // {4} if (dialogProgress == null) { UtilsCommon.startFFmpeg(ffmpegAudioProgArgs, false, true); } else { UtilsCommon.startFFmpegProgress(ffmpegAudioProgArgs, dialogProgress); } }
/// <summary> /// Performs the work in the processing thread. /// </summary> private void bw_DoWork(object sender, DoWorkEventArgs e) { WorkerVars workerVars = e.Argument as WorkerVars; List <List <InfoCombined> > combinedAll = new List <List <InfoCombined> >(); WorkerSubs subsWorker = new WorkerSubs(); int totalLines = 0; bool needToGenerateCombinedAll = workerVars.CombinedAll == null; // Only generate a combinedAll if one if not provided if (needToGenerateCombinedAll) { // Parse and combine the subtitles try { DialogProgress.nextStepInvoke(dialogProgress, ++currentStep, "Combine subs"); combinedAll = subsWorker.combineAllSubs(workerVars, dialogProgress); if (combinedAll != null) { workerVars.CombinedAll = combinedAll; } else { e.Cancel = true; return; } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong before processing could start.\n" + e1); e.Cancel = true; return; } foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { totalLines += combArray.Count; } if (totalLines == 0) { UtilsMsg.showErrMsg( "No lines of dialog could be parsed from the subtitle files.\nPlease check that they are valid."); e.Cancel = true; return; } // Inactivate lines try { DialogProgress.nextStepInvoke(dialogProgress, ++currentStep, "Inactivate lines"); combinedAll = subsWorker.inactivateLines(workerVars, dialogProgress); if (combinedAll != null) { workerVars.CombinedAll = combinedAll; } else { e.Cancel = true; return; } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong while setting active lines.\n" + e1); e.Cancel = true; return; } } // Find context lines if (Settings.Instance.ContextLeadingCount > 0 || Settings.Instance.ContextTrailingCount > 0) { try { DialogProgress.nextStepInvoke(dialogProgress, ++currentStep, "Find context lines"); combinedAll = subsWorker.markLinesOnlyNeededForContext(workerVars, dialogProgress); if (combinedAll != null) { workerVars.CombinedAll = combinedAll; } else { e.Cancel = true; return; } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong while finding context lines.\n" + e1); e.Cancel = true; return; } } // Remove Inactive lines (unless they are needed for context) try { DialogProgress.nextStepInvoke(dialogProgress, ++currentStep, "Remove inactive lines"); combinedAll = subsWorker.removeInactiveLines(workerVars, dialogProgress, true); if (combinedAll != null) { workerVars.CombinedAll = combinedAll; } else { e.Cancel = true; return; } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong while removing inactive lines.\n" + e1); e.Cancel = true; return; } totalLines = 0; foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { totalLines += combArray.Count; } if (totalLines == 0) { UtilsMsg.showErrMsg( "No lines will be processed. Please check your settings to make\nsure that you are not mistakenly pruning too many lines."); e.Cancel = true; return; } try { // Move vobsubs from preview dir to .media dir if (!needToGenerateCombinedAll) { if (!subsWorker.copyVobsubsFromPreviewDirToMediaDir(workerVars, dialogProgress)) { e.Cancel = true; return; } } } catch { } // Generate SRS import file try { DialogProgress.nextStepInvoke(dialogProgress, ++currentStep, "Generate import file"); WorkerSrs srsWorker = new WorkerSrs(); if (!srsWorker.genSrs(workerVars, dialogProgress)) { e.Cancel = true; return; } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong while generating the SRS import file.\n" + e1); e.Cancel = true; return; } List <List <InfoCombined> > combinedAllWithContext = ObjectCopier.Clone <List <List <InfoCombined> > >(workerVars.CombinedAll); // Generate audio clips try { if (Settings.Instance.AudioClips.Enabled) { DialogProgress.nextStepInvoke(dialogProgress, ++currentStep, "Generate audio clips"); if (Settings.Instance.ContextLeadingCount > 0 && Settings.Instance.ContextLeadingIncludeAudioClips || Settings.Instance.ContextTrailingCount > 0 && Settings.Instance.ContextTrailingIncludeAudioClips) { workerVars.CombinedAll = combinedAllWithContext; } else { workerVars.CombinedAll = subsWorker.removeContextOnlyLines(combinedAllWithContext); } WorkerAudio audioWorker = new WorkerAudio(); if (!audioWorker.genAudioClip(workerVars, dialogProgress)) { e.Cancel = true; return; } } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong while generating the audio clips.\n" + e1); e.Cancel = true; return; } // Generate Snapshots try { if (Settings.Instance.Snapshots.Enabled) { DialogProgress.nextStepInvoke(dialogProgress, ++currentStep, "Generate snapshots"); if (Settings.Instance.ContextLeadingCount > 0 && Settings.Instance.ContextLeadingIncludeSnapshots || Settings.Instance.ContextTrailingCount > 0 && Settings.Instance.ContextTrailingIncludeSnapshots) { workerVars.CombinedAll = combinedAllWithContext; } else { workerVars.CombinedAll = subsWorker.removeContextOnlyLines(combinedAllWithContext); } WorkerSnapshot snapshotWorker = new WorkerSnapshot(); if (!snapshotWorker.genSnapshots(workerVars, dialogProgress)) { e.Cancel = true; return; } } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong while generating snapshots.\n" + e1); e.Cancel = true; return; } // Generate video clips try { if (Settings.Instance.VideoClips.Enabled) { DialogProgress.nextStepInvoke(dialogProgress, ++currentStep, "Generate video clips"); if (Settings.Instance.ContextLeadingCount > 0 && Settings.Instance.ContextLeadingIncludeVideoClips || Settings.Instance.ContextTrailingCount > 0 && Settings.Instance.ContextTrailingIncludeVideoClips) { workerVars.CombinedAll = combinedAllWithContext; } else { workerVars.CombinedAll = subsWorker.removeContextOnlyLines(combinedAllWithContext); } WorkerVideo videoWorker = new WorkerVideo(); if (!videoWorker.genVideoClip(workerVars, dialogProgress)) { e.Cancel = true; return; } } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong while generating the video clips.\n" + e1); e.Cancel = true; return; } e.Result = workerVars; }
/// <summary> /// Performs the work of the audio extraction thread. /// </summary> private void splitAudio(object sender, DoWorkEventArgs e) { WorkerVars workerVars = e.Argument as WorkerVars; List <List <InfoCombined> > combinedAll = new List <List <InfoCombined> >(); WorkerSubs subsWorker = new WorkerSubs(); if (groupBoxCheckLyrics.Checked) { // Parse and combine the subtitles try { combinedAll = subsWorker.combineAllSubs(workerVars, dialogProgress); if (combinedAll != null) { workerVars.CombinedAll = combinedAll; } else { e.Cancel = true; return; } } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong before processing could start.\n" + e1); e.Cancel = true; return; } } DateTime mediaStartime = new DateTime(); DateTime mediaEndtime = new DateTime(); DateTime mediaDuration = new DateTime(); int episode = 0; DialogProgress.updateProgressInvoke(dialogProgress, 0, "Starting..."); foreach (string file in mediaFiles) { episode++; try { mediaEndtime = UtilsVideo.getVideoLength(file); } catch (Exception e1) { UtilsMsg.showErrMsg("Something went wrong while determining duration of the media:\n" + e1); return; } if (useSpan) { mediaStartime = spanStart; // If the span end time if not greater than the actual duration of the media if (spanEnd < mediaEndtime) { mediaEndtime = spanEnd; } } UtilsName name = new UtilsName( deckName, mediaFiles.Length, // Total number of episodes 1, // Total number of lines (Note: not filled out here) mediaEndtime, // Last time 0, 0 // Width and height (Note: not filled out anywhere) ); mediaDuration = UtilsSubs.getDurationTime(mediaStartime, mediaEndtime); string progressText = String.Format("Processing audio from media file {0} of {1}", episode, mediaFiles.Length); int progress = Convert.ToInt32((episode - 1) * (100.0 / mediaFiles.Length)); DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); // Enable detail mode in progress dialog DialogProgress.enableDetailInvoke(dialogProgress, true); // Set the duration of the clip in the progress dialog (for detail mode) DialogProgress.setDuration(dialogProgress, mediaDuration); string tempMp3Filename = Path.GetTempPath() + ConstantSettings.TempAudioFilename; UtilsAudio.ripAudioFromVideo(mediaFiles[episode - 1], audioStream.Num, mediaStartime, mediaEndtime, bitrate, tempMp3Filename, dialogProgress); DialogProgress.enableDetailInvoke(dialogProgress, false); if (dialogProgress.Cancel) { e.Cancel = true; File.Delete(tempMp3Filename); return; } int numClips = 1; if (!isSingleFile) { numClips = (int)Math.Ceiling((mediaDuration.TimeOfDay.TotalMilliseconds / (clipLength.TimeOfDay.TotalSeconds * 1000.0))); } for (int clipIdx = 0; clipIdx < numClips; clipIdx++) { progressText = String.Format("Splitting segment {0} of {1} from media file {2} of {3}", clipIdx + 1, numClips, episode, mediaFiles.Length); progress = Convert.ToInt32((episode - 1) * (100.0 / mediaFiles.Length)); DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); if (dialogProgress.Cancel) { e.Cancel = true; File.Delete(tempMp3Filename); return; } // The start and end times used for processing DateTime startTime = new DateTime(); DateTime endTime = new DateTime(); if (isSingleFile) { endTime = mediaDuration; } else { startTime = startTime.AddSeconds((double)(clipLength.TimeOfDay.TotalSeconds * clipIdx)); endTime = endTime.AddSeconds((double)(clipLength.TimeOfDay.TotalSeconds * (clipIdx + 1))); if (endTime.TimeOfDay.TotalMilliseconds >= mediaDuration.TimeOfDay.TotalMilliseconds) { endTime = mediaDuration; } } // The start and end times that will be displayed DateTime startTimeName = startTime.AddMilliseconds(mediaStartime.TimeOfDay.TotalMilliseconds); DateTime endTimeName = endTime.AddMilliseconds(mediaStartime.TimeOfDay.TotalMilliseconds); // Fill in the total number of lines with the total number of clips name.TotalNumLines = numClips; string nameStr = name.createName(ConstantSettings.ExtractMediaAudioFilenameFormat, episode + episodeStartNumber - 1, clipIdx + 1, startTimeName, endTimeName, "", ""); string outName = String.Format("{0}{1}{2}", outputDir, // {0} Path.DirectorySeparatorChar, // {1} nameStr); // {2} UtilsAudio.cutAudio(tempMp3Filename, startTime, endTime, outName); nameStr = name.createName(ConstantSettings.AudioId3Artist, episode + episodeStartNumber - 1, clipIdx + 1, startTimeName, endTimeName, "", ""); string tagArtist = String.Format("{0}", nameStr); // {0} nameStr = name.createName(ConstantSettings.AudioId3Album, episode + episodeStartNumber - 1, clipIdx + 1, startTimeName, endTimeName, "", ""); string tagAlbum = String.Format("{0}", nameStr); // {0} nameStr = name.createName(ConstantSettings.AudioId3Title, episode + episodeStartNumber - 1, clipIdx + 1, startTimeName, endTimeName, "", ""); string tagTitle = String.Format("{0}", nameStr); // {0} nameStr = name.createName(ConstantSettings.AudioId3Genre, episode + episodeStartNumber - 1, clipIdx + 1, startTimeName, endTimeName, "", ""); string tagGenre = String.Format("{0}", nameStr); // {0} string tagLyrics = ""; if (groupBoxCheckLyrics.Checked) { int totalLyricsLines = 0; int curLyricsNum = 1; // Precount the number of lyrics lines foreach (InfoCombined comb in combinedAll[episode - 1]) { if (comb.Subs1.StartTime.TimeOfDay.TotalMilliseconds >= startTimeName.TimeOfDay.TotalMilliseconds && comb.Subs1.StartTime.TimeOfDay.TotalMilliseconds <= endTimeName.TimeOfDay.TotalMilliseconds) { totalLyricsLines++; } } // Fill in the total number of lyrics lines name.TotalNumLines = curLyricsNum; // Foreach comb in the current episode, if the comb lies within the // current clip, add it to the lryics tag foreach (InfoCombined comb in combinedAll[episode - 1]) { if (comb.Subs1.StartTime.TimeOfDay.TotalMilliseconds >= startTimeName.TimeOfDay.TotalMilliseconds && comb.Subs1.StartTime.TimeOfDay.TotalMilliseconds <= endTimeName.TimeOfDay.TotalMilliseconds) { tagLyrics += formatLyricsPair(comb, name, startTimeName, episode + episodeStartNumber - 1, curLyricsNum) + "\r\n"; curLyricsNum++; } } } UtilsAudio.tagAudio(outName, tagArtist, tagAlbum, tagTitle, tagGenre, tagLyrics, clipIdx + 1, numClips); } } return; }
/// <summary> /// Generate Audio clips for all episodes. /// </summary> public bool genAudioClip(WorkerVars workerVars, DialogProgress dialogProgress) { int progessCount = 0; int episodeCount = 0; int totalEpisodes = workerVars.CombinedAll.Count; int curEpisodeCount = 0; int totalLines = UtilsSubs.getTotalLineCount(workerVars.CombinedAll); DateTime lastTime = UtilsSubs.getLastTime(workerVars.CombinedAll); UtilsName name = new UtilsName(Settings.Instance.DeckName, totalEpisodes, totalLines, lastTime, Settings.Instance.VideoClips.Size.Width, Settings.Instance.VideoClips.Size.Height); DialogProgress.updateProgressInvoke(dialogProgress, 0, "Creating audio clips."); // For each episode foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { episodeCount++; // It is possible for all lines in an episode to be set to inactive if (combArray.Count == 0) { // Skip this episode continue; } // Is the audio input an mp3 file? bool inputFileIsMp3 = Settings.Instance.AudioClips.Files.Length > 0 && Path.GetExtension(Settings.Instance.AudioClips.Files[episodeCount - 1]) .ToLower() == ".mp3"; DateTime entireClipStartTime = combArray[0].Subs1.StartTime; DateTime entireClipEndTime = combArray[combArray.Count - 1].Subs1.EndTime; string tempMp3Filename = Path.GetTempPath() + ConstantSettings.TempAudioFilename; // Apply pad to entire clip timings (if requested) if (Settings.Instance.AudioClips.PadEnabled) { entireClipStartTime = UtilsSubs.applyTimePad(entireClipStartTime, -Settings.Instance.AudioClips.PadStart); entireClipEndTime = UtilsSubs.applyTimePad(entireClipEndTime, Settings.Instance.AudioClips.PadEnd); } // Do we need to extract the audio from the video file? if (Settings.Instance.AudioClips.UseAudioFromVideo) { string progressText = $"Extracting audio from video file {episodeCount} of {totalEpisodes}"; // {1} bool success = convertToMp3( Settings.Instance.VideoClips.Files[episodeCount - 1], Settings.Instance.VideoClips.AudioStream.Num, progressText, dialogProgress, entireClipStartTime, entireClipEndTime, tempMp3Filename); if (!success) { UtilsMsg.showErrMsg("Failed to extract the audio from the video.\n" + "Make sure that the video does not have any DRM restrictions."); return(false); } } // If the reencode option is set or the input audio is not an mp3, reencode to mp3 else if (ConstantSettings.ReencodeBeforeSplittingAudio || !inputFileIsMp3) { string progressText = $"Reencoding audio file {episodeCount} of {totalEpisodes}"; bool success = convertToMp3( Settings.Instance.AudioClips.Files[episodeCount - 1], "0", progressText, dialogProgress, entireClipStartTime, entireClipEndTime, tempMp3Filename); if (!success) { UtilsMsg.showErrMsg("Failed to reencode the audio file.\n" + "Make sure that the audio file does not have any DRM restrictions."); return(false); } } curEpisodeCount = 0; // Reset // For each line in episode, generate an audio clip foreach (InfoCombined comb in combArray) { progessCount++; curEpisodeCount++; int progress = Convert.ToInt32(progessCount * (100.0 / totalLines)); string progressText = $"Generating audio clip: {progessCount.ToString()} of {totalLines.ToString()}"; // Update the progress dialog DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); // Did the user press the cancel button? if (dialogProgress.Cancel) { File.Delete(tempMp3Filename); return(false); } DateTime startTime = comb.Subs1.StartTime; DateTime endTime = comb.Subs1.EndTime; DateTime filenameStartTime = comb.Subs1.StartTime; DateTime filenameEndTime = comb.Subs1.EndTime; string fileToCut = ""; if (Settings.Instance.AudioClips.UseAudioFromVideo || ConstantSettings.ReencodeBeforeSplittingAudio || !inputFileIsMp3) { startTime = UtilsSubs.shiftTiming(startTime, -(int)entireClipStartTime.TimeOfDay.TotalMilliseconds); endTime = UtilsSubs.shiftTiming(endTime, -(int)entireClipStartTime.TimeOfDay.TotalMilliseconds); fileToCut = tempMp3Filename; } else { fileToCut = Settings.Instance.AudioClips.Files[episodeCount - 1]; } // Apply pad (if requested) if (Settings.Instance.AudioClips.PadEnabled) { startTime = UtilsSubs.applyTimePad(startTime, -Settings.Instance.AudioClips.PadStart); endTime = UtilsSubs.applyTimePad(endTime, Settings.Instance.AudioClips.PadEnd); filenameStartTime = UtilsSubs.applyTimePad(comb.Subs1.StartTime, -Settings.Instance.AudioClips.PadStart); filenameEndTime = UtilsSubs.applyTimePad(comb.Subs1.EndTime, Settings.Instance.AudioClips.PadEnd); } string lyricSubs2 = ""; // Set the Subs2 lyric if it exists if (Settings.Instance.Subs[1].Files.Length != 0) { lyricSubs2 = comb.Subs2.Text.Trim(); } // Create output filename string nameStr = name.createName(ConstantSettings.AudioFilenameFormat, (int)episodeCount + Settings.Instance.EpisodeStartNumber - 1, progessCount, filenameStartTime, filenameEndTime, comb.Subs1.Text, lyricSubs2); string outName = $"{workerVars.MediaDir}{Path.DirectorySeparatorChar}{nameStr}"; // {2} // Create audio clip UtilsAudio.cutAudio(fileToCut, startTime, endTime, outName); // Tag the audio clip tagAudio(name, outName, episodeCount, curEpisodeCount, progessCount, combArray.Count, filenameStartTime, filenameEndTime, comb.Subs1.Text, lyricSubs2); } File.Delete(tempMp3Filename); } // Normalize all mp3 files in the media directory if (Settings.Instance.AudioClips.Normalize) { DialogProgress.updateProgressInvoke(dialogProgress, -1, "Normalizing audio..."); UtilsAudio.normalizeAudio(workerVars.MediaDir); } return(true); }
/// <summary> /// Generate video clips for all episodes. /// </summary> public bool genVideoClip(WorkerVars workerVars, DialogProgress dialogProgress) { int progessCount = 0; int episodeCount = 0; int totalEpisodes = workerVars.CombinedAll.Count; int totalLines = UtilsSubs.getTotalLineCount(workerVars.CombinedAll); DateTime lastTime = UtilsSubs.getLastTime(workerVars.CombinedAll); UtilsName name = new UtilsName(Settings.Instance.DeckName, totalEpisodes, totalLines, lastTime, Settings.Instance.VideoClips.Size.Width, Settings.Instance.VideoClips.Size.Height); DialogProgress.updateProgressInvoke(dialogProgress, 0, "Creating video clips."); // For each episode foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { episodeCount++; // It is possible for all lines in an episode to be set to inactive if (combArray.Count == 0) { // Skip this episode continue; } string progressText = String.Format("Converting video file {0} of {1}", episodeCount, totalEpisodes); DialogProgress.updateProgressInvoke(dialogProgress, progressText); DateTime entireClipStartTime = combArray[0].Subs1.StartTime; DateTime entireClipEndTime = combArray[combArray.Count - 1].Subs1.EndTime; // Apply pad to entire clip timings (if requested) if (Settings.Instance.VideoClips.PadEnabled) { entireClipStartTime = UtilsSubs.applyTimePad(entireClipStartTime, -Settings.Instance.VideoClips.PadStart); entireClipEndTime = UtilsSubs.applyTimePad(entireClipEndTime, Settings.Instance.VideoClips.PadEnd); } // Enable detail mode in progress dialog DialogProgress.enableDetailInvoke(dialogProgress, true); // Set the duration of the clip in the progress dialog (for detail mode) DateTime entireClipDuration = UtilsSubs.getDurationTime(entireClipStartTime, entireClipEndTime); DialogProgress.setDuration(dialogProgress, entireClipDuration); string tempVideoFilename = Path.GetTempPath() + ConstantSettings.TempVideoFilename; string videoExtension = ".avi"; // Convert entire video (from first line to last line) based on user settings (size, crop, bitrate, etc). // It will be cut into smaller video clips later. if (Settings.Instance.VideoClips.IPodSupport) { videoExtension = ".mp4"; tempVideoFilename += videoExtension; UtilsVideo.convertVideo(Settings.Instance.VideoClips.Files[episodeCount - 1], Settings.Instance.VideoClips.AudioStream.Num, entireClipStartTime, entireClipEndTime, Settings.Instance.VideoClips.Size, Settings.Instance.VideoClips.Crop, Settings.Instance.VideoClips.BitrateVideo, Settings.Instance.VideoClips.BitrateAudio, UtilsVideo.VideoCodec.h264, UtilsVideo.AudioCodec.AAC, UtilsVideo.Profilex264.IPod640, UtilsVideo.Presetx264.SuperFast, tempVideoFilename, dialogProgress); } else { tempVideoFilename += videoExtension; UtilsVideo.convertVideo(Settings.Instance.VideoClips.Files[episodeCount - 1], Settings.Instance.VideoClips.AudioStream.Num, entireClipStartTime, entireClipEndTime, Settings.Instance.VideoClips.Size, Settings.Instance.VideoClips.Crop, Settings.Instance.VideoClips.BitrateVideo, Settings.Instance.VideoClips.BitrateAudio, UtilsVideo.VideoCodec.MPEG4, UtilsVideo.AudioCodec.MP3, UtilsVideo.Profilex264.None, UtilsVideo.Presetx264.None, tempVideoFilename, dialogProgress); } DialogProgress.enableDetailInvoke(dialogProgress, false); // Generate a video clip for each line of the episode foreach (InfoCombined comb in combArray) { progessCount++; progressText = string.Format("Generating video clip: {0} of {1}", progessCount.ToString(), totalLines.ToString()); int progress = Convert.ToInt32(progessCount * (100.0 / totalLines)); // Update the progress dialog DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); // Did the user press the cancel button? if (dialogProgress.Cancel) { File.Delete(tempVideoFilename); return(false); } // Adjust timing to sync with the start of the converted video (which starts at the episode's first line of dialog) DateTime startTime = UtilsSubs.shiftTiming(comb.Subs1.StartTime, -((int)entireClipStartTime.TimeOfDay.TotalMilliseconds)); DateTime endTime = UtilsSubs.shiftTiming(comb.Subs1.EndTime, -((int)entireClipStartTime.TimeOfDay.TotalMilliseconds)); // Times used in the filename DateTime filenameStartTime = comb.Subs1.StartTime; DateTime filenameEndTime = comb.Subs1.EndTime; // Apply pad (if requested) if (Settings.Instance.VideoClips.PadEnabled) { startTime = UtilsSubs.applyTimePad(startTime, -Settings.Instance.VideoClips.PadStart); endTime = UtilsSubs.applyTimePad(endTime, Settings.Instance.VideoClips.PadEnd); filenameStartTime = UtilsSubs.applyTimePad(comb.Subs1.StartTime, -Settings.Instance.VideoClips.PadStart); filenameEndTime = UtilsSubs.applyTimePad(comb.Subs1.EndTime, Settings.Instance.VideoClips.PadEnd); } // Create output filename string nameStr = name.createName(ConstantSettings.VideoFilenameFormat, (int)episodeCount + Settings.Instance.EpisodeStartNumber - 1, progessCount, filenameStartTime, filenameEndTime, comb.Subs1.Text, comb.Subs2.Text); string outFile = string.Format("{0}{1}{2}{3}", workerVars.MediaDir, // {0} Path.DirectorySeparatorChar, // {1} nameStr, // {2} videoExtension); // {3} // Cut video clip for current line UtilsVideo.cutVideo(tempVideoFilename, startTime, endTime, outFile); } File.Delete(tempVideoFilename); } return(true); }
/// <summary> /// Get a list with the inactive lines removed from each episode. /// </summary> public List <List <InfoCombined> > removeInactiveLines(WorkerVars workerVars, DialogProgress dialogProgress, bool dontRemoveContextLines) { int totalLines = 0; int progessCount = 0; List <List <InfoCombined> > activeLines = new List <List <InfoCombined> >(); bool subs1ContainsVobsubs = UtilsSubs.filePatternContainsVobsubs(Settings.Instance.Subs[0].FilePattern); bool subs2ContainsVobsubs = UtilsSubs.filePatternContainsVobsubs(Settings.Instance.Subs[1].FilePattern); foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { totalLines += combArray.Count; } // For each episode foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { List <InfoCombined> currentEpisodeActiveLines = new List <InfoCombined>(); // For each line in episode foreach (InfoCombined comb in combArray) { progessCount++; if (comb.Active || dontRemoveContextLines && comb.OnlyNeededForContext) { currentEpisodeActiveLines.Add(comb); } else { if (subs1ContainsVobsubs) { try { // Multiple vobsub images can be shown in a single line, so extract each image and delete it List <string> vobsubImages = UtilsSubs.extractVobsubFilesFromText(comb.Subs1.Text); foreach (string vobsubImage in vobsubImages) { File.Delete(workerVars.MediaDir + Path.DirectorySeparatorChar + vobsubImage); } } catch { // Don't care } } if (subs2ContainsVobsubs) { try { // Multiple vobsub image can be shown in a single line, so extract each image and delete it List <string> vobsubImages = UtilsSubs.extractVobsubFilesFromText(comb.Subs2.Text); foreach (string vobsubImage in vobsubImages) { File.Delete(workerVars.MediaDir + Path.DirectorySeparatorChar + vobsubImage); } } catch { // Don't care } } } string progressText = $"Remove inactive lines: {Convert.ToInt32(progessCount * (100.0 / totalLines))}%"; int progress = Convert.ToInt32(progessCount * (100.0 / totalLines)); DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); if (dialogProgress.Cancel) { return(null); } } activeLines.Add(currentEpisodeActiveLines); } return(activeLines); }
/// <summary> /// Copy the Vobsub image files from the temporary preview directory to the media directory. /// </summary> public bool copyVobsubsFromPreviewDirToMediaDir(WorkerVars workerVars, DialogProgress dialogProgress) { int totalLines = 0; int progessCount = 0; bool subs1ContainsVobsubs = UtilsSubs.filePatternContainsVobsubs(Settings.Instance.Subs[0].FilePattern); bool subs2ContainsVobsubs = UtilsSubs.filePatternContainsVobsubs(Settings.Instance.Subs[1].FilePattern); string tempPreviewDir = Path.GetTempPath() + ConstantSettings.TempPreviewDirName; foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { totalLines += combArray.Count; } // For each episode foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { // For each line in episode foreach (InfoCombined comb in combArray) { progessCount++; if (subs1ContainsVobsubs) { try { // Multiple vobsub image can be shown in a single line, so copy each of them List <string> vobsubImages = UtilsSubs.extractVobsubFilesFromText(comb.Subs1.Text); foreach (string vobsubImage in vobsubImages) { File.Copy(tempPreviewDir + Path.DirectorySeparatorChar + vobsubImage, workerVars.MediaDir + Path.DirectorySeparatorChar + vobsubImage); } } catch { // Don't care } } if (subs2ContainsVobsubs) { try { // Multiple vobsub image can be shown in a single line, so copy each of them List <string> vobsubImages = UtilsSubs.extractVobsubFilesFromText(comb.Subs2.Text); foreach (string vobsubImage in vobsubImages) { File.Copy(tempPreviewDir + Path.DirectorySeparatorChar + vobsubImage, workerVars.MediaDir + Path.DirectorySeparatorChar + vobsubImage); } } catch { // Don't care } } string progressText = $"Copying vobsubs to .media directory: {Convert.ToInt32(progessCount * (100.0 / totalLines))}%"; int progress = Convert.ToInt32(progessCount * (100.0 / totalLines)); DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); if (dialogProgress.Cancel) { return(false); } } } return(true); }
/// <summary> /// Inactivate lines from episodes based on include/exclude lists, duplicate lines, etc. /// Does not remove lines, only modifies their active flag. /// </summary> public List <List <InfoCombined> > inactivateLines(WorkerVars workerVars, DialogProgress dialogProgress) { int totalLines = 0; int progessCount = 0; foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { totalLines += combArray.Count; } bool checkIncludesExludes = Settings.Instance.Subs[0].IncludedWords.Length != 0 || Settings.Instance.Subs[1].IncludedWords.Length != 0 || Settings.Instance.Subs[0].ExcludedWords.Length != 0 || Settings.Instance.Subs[1].ExcludedWords.Length != 0; Hashtable allLinesSoFarSubs1 = new Hashtable(); Hashtable allLinesSoFarSubs2 = new Hashtable(); bool removeDupsSubs1 = Settings.Instance.Subs[0].ExcludeDuplicateLinesEnabled; bool removeDupsSubs2 = Settings.Instance.Subs[1].ExcludeDuplicateLinesEnabled && Settings.Instance.Subs[1].FilePattern != ""; bool checkDups = removeDupsSubs1 || removeDupsSubs2; // For each episode foreach (List <InfoCombined> combArray in workerVars.CombinedAll) { List <InfoCombined> compArrayRemoved = new List <InfoCombined>(); // For each line in episode foreach (InfoCombined comb in combArray) { bool isExcluded = false; progessCount++; // Don't process if subtitles are vobsub bool isSubs1Vobsub = UtilsSubs.filePatternContainsVobsubs(Settings.Instance.Subs[0].FilePattern); bool isSubs2Vobsub = UtilsSubs.filePatternContainsVobsubs(Settings.Instance.Subs[1].FilePattern); // Remove lines not in the span if (Settings.Instance.SpanEnabled && !isExcluded) { DateTime spanStart = Settings.Instance.SpanStart; DateTime spanEnd = Settings.Instance.SpanEnd; if (comb.Subs1.StartTime < spanStart || comb.Subs1.StartTime > spanEnd) { isExcluded = true; } } bool passedIgnoreShorterThanTime = true; // Make sure that line is at least the min amount of milliseconds as specified by user if (Settings.Instance.Subs[0].ExcludeShorterThanTimeEnabled && Settings.Instance.Subs[0].TimingsEnabled) { passedIgnoreShorterThanTime = Math.Abs((int)comb.Subs1.EndTime.TimeOfDay.TotalMilliseconds - (int)comb.Subs1.StartTime.TimeOfDay.TotalMilliseconds) >= Settings.Instance.Subs[0].ExcludeShorterThanTime; } else if (Settings.Instance.Subs[1].ExcludeShorterThanTimeEnabled && Settings.Instance.Subs[1].TimingsEnabled) { // Note: using Subs1 here is not a mistake because Subs2 timings will be placed into Subs1 in combineSubs(). passedIgnoreShorterThanTime = Math.Abs((int)comb.Subs1.EndTime.TimeOfDay.TotalMilliseconds - (int)comb.Subs1.StartTime.TimeOfDay.TotalMilliseconds) >= Settings.Instance.Subs[1].ExcludeShorterThanTime; } if (!passedIgnoreShorterThanTime && !isExcluded) { isExcluded = true; } bool passedIgnoreLongerThanTime = true; // Make sure that line is at least the min amount of milliseconds as specified by user if (Settings.Instance.Subs[0].ExcludeLongerThanTimeEnabled && Settings.Instance.Subs[0].TimingsEnabled) { passedIgnoreLongerThanTime = Math.Abs((int)comb.Subs1.EndTime.TimeOfDay.TotalMilliseconds - (int)comb.Subs1.StartTime.TimeOfDay.TotalMilliseconds) <= Settings.Instance.Subs[0].ExcludeLongerThanTime; } else if (Settings.Instance.Subs[1].ExcludeLongerThanTimeEnabled && Settings.Instance.Subs[1].TimingsEnabled) { // Note: using Subs1 here is not a mistake because Subs2 timings will be placed into Subs1 in combineSubs(). passedIgnoreLongerThanTime = Math.Abs((int)comb.Subs1.EndTime.TimeOfDay.TotalMilliseconds - (int)comb.Subs1.StartTime.TimeOfDay.TotalMilliseconds) <= Settings.Instance.Subs[1].ExcludeLongerThanTime; } if (!passedIgnoreLongerThanTime && !isExcluded) { isExcluded = true; } // Make sure that line is at least the min character length as specified by user bool passedIgnoreFewerTestSubs1 = isSubs1Vobsub || !Settings.Instance.Subs[0].ExcludeFewerEnabled || Settings.Instance.Subs[0].ExcludeFewerEnabled && comb.Subs1.Text.Length >= Settings.Instance.Subs[0].ExcludeFewerCount; bool passedIgnoreFewerTestSubs2 = isSubs2Vobsub || !Settings.Instance.Subs[1].ExcludeFewerEnabled || Settings.Instance.Subs[1].ExcludeFewerEnabled && comb.Subs2.Text.Length >= Settings.Instance.Subs[1].ExcludeFewerCount; if ((!passedIgnoreFewerTestSubs1 || !passedIgnoreFewerTestSubs2) && !isExcluded) { isExcluded = true; } // Remove based on actors if (Settings.Instance.ActorList.Count > 0 && !isExcluded) { bool actorFound = false; foreach (string actor in Settings.Instance.ActorList) { if (Settings.Instance.Subs[0].ActorsEnabled) { if (comb.Subs1.Actor.Trim().ToLower() == actor.Trim().ToLower()) { actorFound = true; break; } } else { if (comb.Subs2.Actor.Trim().ToLower() == actor.Trim().ToLower()) { actorFound = true; break; } } } isExcluded = !actorFound; } // Remove based on include/exclude lists if (checkIncludesExludes && !isExcluded) { if (!isSubs1Vobsub && Settings.Instance.Subs[0].ExcludedWords.Length != 0) { foreach (string word in Settings.Instance.Subs[0].ExcludedWords) { if (comb.Subs1.Text.ToLower().Contains(word.ToLower())) { isExcluded = true; } } } if (!isSubs2Vobsub && Settings.Instance.Subs[1].ExcludedWords.Length != 0 && !isExcluded) { foreach (string word in Settings.Instance.Subs[1].ExcludedWords) { if (comb.Subs2.Text.ToLower().Contains(word.ToLower())) { isExcluded = true; } } } if (!isSubs1Vobsub && Settings.Instance.Subs[0].IncludedWords.Length != 0 && !isExcluded) { bool wordFound = false; foreach (string word in Settings.Instance.Subs[0].IncludedWords) { if (comb.Subs1.Text.ToLower().Contains(word.ToLower())) { wordFound = true; break; } } isExcluded = !wordFound; } if (!isSubs2Vobsub && Settings.Instance.Subs[1].IncludedWords.Length != 0 && !isExcluded) { bool wordFound = false; foreach (string word in Settings.Instance.Subs[1].IncludedWords) { if (comb.Subs2.Text.ToLower().Contains(word.ToLower())) { wordFound = true; break; } } isExcluded = !wordFound; } } // Remove Duplicates if (checkDups && !isExcluded) { if (!isSubs1Vobsub && removeDupsSubs1) { if (allLinesSoFarSubs1.Contains(comb.Subs1.Text.ToLower())) { isExcluded = true; } else { allLinesSoFarSubs1.Add(comb.Subs1.Text.ToLower(), comb); } } if (!isSubs2Vobsub && removeDupsSubs2) { if (allLinesSoFarSubs2.Contains(comb.Subs2.Text.ToLower())) { isExcluded = true; } else { allLinesSoFarSubs2.Add(comb.Subs2.Text.ToLower(), comb); } } } // Remove lines without a kanji if (Settings.Instance.LangaugeSpecific.KanjiLinesOnly && !isExcluded) { if (!isSubs1Vobsub && !UtilsLang.containsIdeograph(comb.Subs1.Text) && !isSubs2Vobsub && !UtilsLang.containsIdeograph(comb.Subs2.Text)) { isExcluded = true; } } // Unset the active flag if (isExcluded) { comb.Active = false; } string progressText = $"Setting active lines based on user specified rules: {Convert.ToInt32(progessCount * (100.0 / totalLines))}%"; int progress = Convert.ToInt32(progessCount * (100.0 / totalLines)); DialogProgress.updateProgressInvoke(dialogProgress, progress, progressText); if (dialogProgress.Cancel) { return(null); } } } return(workerVars.CombinedAll); }