protected override void FinalSanityCheck() { // Check if we don't have a video stream and remove the video mapping if (_videoFile.VideoStream == -1) // Check if we have a video stream { _jobLog.WriteEntry(this, "No Video stream detected, removing support for video stream", Log.LogEntryType.Warning); _cmdParams = _cmdParams.Replace("-map 0:v", ""); // Replace the map video command with nothing } else { _cmdParams = _cmdParams.Replace("-map 0:v", "-map 0:" + _videoFile.VideoStream.ToString(CultureInfo.InvariantCulture)); // Replace the map video command with the actual video stream (some TS files contain multiple video streams) } // Audio stream can be -1 if no language is selected do we don't check for it. if (_videoFile.FFMPEGStreamInfo.AudioTracks < 1) { _jobLog.WriteEntry(this, "No Audio stream detected, removing support for audio stream", Log.LogEntryType.Warning); _cmdParams = _cmdParams.Replace("-map 0:a", ""); // Replace the map audio command with nothing } // Check if we need to burn in subtitles, this is done in the VERY end because this filter cannot be replaced since it contains : that will break the MCEBuddy video manipulator functions (only works once) // Special characters \ : ' need to escaped for the filter (in that order) // Then you need to re-escapte the / ' characters for ffmpeg to parse it (in that order) // Refer to FFMPEG Ticket #3334, order if VERY important, first escape the \, then escape the :, then escape the ', then reescape the \ and finally reescape ' /* Comment from Stefano Sabatini from ffmpeg users forum on "escaping hell" in ffmpeg filters * the SRT filepath is * * D:\MCEBuddy\MCEBuddy 2.x\MCEBuddy.ServiceCMD\bin\x86\Debug\working0\HD Small'.srt * * So this string contains : which is special according to the filter * description syntax, and the \ and ' special escaping characters. * * So, first level escaping: * D\:\\MCEBuddy\\MCEBuddy 2.x\\MCEBuddy.ServiceCMD\\bin\\x86\\Debug\\working0\\HD Small\'.srt * * Now you embed the filter description string in the filtergraph * description, so you add another escaping level, and you need to escape * the special \ and ' characters. One way of doing this is as: * * subtitles=D\\:\\\\MCEBuddy\\\\MCEBuddy 2.x\\\\MCEBuddy.ServiceCMD\\\\bin\\\\x86\\\\Debug\\\\working0\\\\HD Small\\\'.srt * * Alternatively you use quoting: * subtitles='D\:\\MCEBuddy\\MCEBuddy 2.x\\MCEBuddy.ServiceCMD\\bin\\x86\\Debug\\working0\\HD Small'\'.srt */ if (!String.IsNullOrWhiteSpace(_srtFile) && File.Exists(_srtFile) && (ParameterValue("-vcodec") != "copy")) // does not work with copy codec { ParameterReplaceOrInsertVideoFilter("subtitles", "=" + FilePaths.FixSpaces(_srtFile.Replace(@"\", @"\\").Replace(@":", @"\:").Replace(@"'", @"\'").Replace(@"\", @"\\").Replace(@"'", @"\'"))); _subtitleBurned = true; } }
private bool MKVRemux() { _jobStatus.ErrorMsg = ""; _jobStatus.PercentageComplete = 100; //all good to start with _jobStatus.ETA = ""; Util.FileIO.TryFileDelete(RemuxedTempFile); string parameters = "--clusters-in-meta-seek -o " + FilePaths.FixSpaces(RemuxedTempFile) + " --compression -1:none " + FilePaths.FixSpaces(_originalFile); MKVMerge mkvMerge = new MKVMerge(parameters, _jobStatus, _jobLog); mkvMerge.Run(); if (!mkvMerge.Success) { _jobLog.WriteEntry(this, Localise.GetPhrase("MKVMerge failed"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = Localise.GetPhrase("MKVMerge failed"); return(false); } _jobLog.WriteEntry(this, Localise.GetPhrase("MKVMerge remux moving file"), Log.LogEntryType.Information); return(ReplaceTempRemuxed()); }
private bool MP4BoxRemux() { _jobStatus.ErrorMsg = ""; _jobStatus.PercentageComplete = 100; //all good to start with _jobStatus.ETA = ""; Util.FileIO.TryFileDelete(RemuxedTempFile); string Parameters = " -keep-sys -keep-all"; //Check for Null FPS (bug with MediaInfo for some .TS files) if (_fps <= 0) { _jobLog.WriteEntry(this, Localise.GetPhrase("Mp4BoxRemuxAVI FPS 0 reported by video file - non compliant video file, skipping adding to parameter"), Log.LogEntryType.Warning); Parameters += " -add " + FilePaths.FixSpaces(_originalFile) + " -new " + FilePaths.FixSpaces(RemuxedTempFile); } else { Parameters += " -fps " + _fps.ToString(System.Globalization.CultureInfo.InvariantCulture) + " -add " + FilePaths.FixSpaces(_originalFile) + " -new " + FilePaths.FixSpaces(RemuxedTempFile); } MP4Box mp4Box = new MP4Box(Parameters, _jobStatus, _jobLog); mp4Box.Run(); if (!mp4Box.Success || _jobStatus.PercentageComplete < GlobalDefs.ACCEPTABLE_COMPLETION) { _jobLog.WriteEntry(this, Localise.GetPhrase("MP4BoxRemux failed"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = Localise.GetPhrase("MP4BoxRemux failed"); return(false); } _jobLog.WriteEntry(this, Localise.GetPhrase("MP4Box remux moving file"), Log.LogEntryType.Information); return(ReplaceTempRemuxed()); }
/// <summary> /// Extracts subtitles from a video file into a SRT format (with same name) and cleans it up. /// It will overwrite any existing SRT files /// If there are multiple subtitles it extracts them into multiple files with incremental names. /// </summary> /// <param name="sourceFile">Path to video file</param> /// <param name="offset">Offset of the subtitles during extraction</param> /// <param name="overWrite">True to overwrite existing SRT files, false to create new ones with incremental names</param> /// <param name="languageExtractList">List of 3 digit language codes to extract (blank to extract all, unnamed languages will always be extracted)</param> /// <returns>True if successful</returns> public bool ExtractSubtitles(string sourceFile, string workingPath, int startTrim, int endTrim, double offset, bool overWrite, List <string> languageExtractList) { _jobLog.WriteEntry(this, ("Extracting Subtitles from " + sourceFile + " into SRT file"), Log.LogEntryType.Information); _jobLog.WriteEntry(this, "Source File : " + sourceFile, Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Working Path " + workingPath, Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Start Trim : " + startTrim.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Stop Trim : " + endTrim.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Offset : " + offset.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); if (String.IsNullOrEmpty(sourceFile)) { return(true); // nothing to do } if (!File.Exists(sourceFile)) { _jobLog.WriteEntry(this, ("File does not exist " + sourceFile), Log.LogEntryType.Warning); return(true); //nothing to process } FFmpegMediaInfo mediaInfo = new FFmpegMediaInfo(sourceFile, _jobStatus, _jobLog); if (!mediaInfo.Success || mediaInfo.ParseError) { _jobLog.WriteEntry(this, ("Error reading subtitle info from file"), Log.LogEntryType.Error); return(false); } _jobLog.WriteEntry(this, "Found " + mediaInfo.SubtitleTracks.ToString() + " Subtitle tracks, extract only the first matching track", Log.LogEntryType.Debug); bool extractedSubtitle = false; for (int i = 0; i < mediaInfo.SubtitleTracks; i++) { if (extractedSubtitle) // Only extract and use one subtitle (sometimes chapter tracks are misidentified as subtitle tracks) { continue; } // Build the command line string parameters = ""; string outputSRTFile = ""; // Using Serviio subtitle filename format (filename.srt or filename_language.srt or filename_uniquenumber.srt) // Check for language comparison if required if (languageExtractList != null) { if (languageExtractList.Count > 0) // If list is empty, we extract all { _jobLog.WriteEntry(this, "Subtitle language extraction list -> " + String.Join(",", languageExtractList.ToArray()), Log.LogEntryType.Debug); if (!String.IsNullOrWhiteSpace(mediaInfo.MediaInfo.SubtitleInfo[i].Language)) // check if we have a language defined for this track { if (!languageExtractList.Contains(mediaInfo.MediaInfo.SubtitleInfo[i].Language)) // This language is not in the list of extraction { _jobLog.WriteEntry(this, "Skipping subtitle extraction since subtitle language >" + mediaInfo.MediaInfo.SubtitleInfo[i].Language + "< is NOT in the subtitle language list", Log.LogEntryType.Warning); continue; // Skip this subtitle track } } else { _jobLog.WriteEntry(this, "Extracting subtitle since there is no language defined for track", Log.LogEntryType.Debug); } } } // Check for existing SRT files if (overWrite) { outputSRTFile = Path.Combine(workingPath, Path.GetFileNameWithoutExtension(sourceFile)) + (i > 0 ? (String.IsNullOrWhiteSpace(mediaInfo.MediaInfo.SubtitleInfo[i].Language) ? "_" + i.ToString() : "_" + mediaInfo.MediaInfo.SubtitleInfo[i].Language) : "") + ".srt"; // First file user default name, then try to name with language first, if not give a unique number parameters += " -y"; } else // Create a unique SRT file name { int existingSRTCount = 0; outputSRTFile = Path.Combine(workingPath, Path.GetFileNameWithoutExtension(sourceFile)) + ".srt"; // Try default name while (File.Exists(outputSRTFile)) { _jobLog.WriteEntry(this, "Subtitle file " + outputSRTFile + " exists, creating a new unique SRT filename", Log.LogEntryType.Debug); outputSRTFile = Path.Combine(workingPath, Path.GetFileNameWithoutExtension(sourceFile)) + (String.IsNullOrWhiteSpace(mediaInfo.MediaInfo.SubtitleInfo[i].Language) ? "_" + existingSRTCount.ToString() : "_" + mediaInfo.MediaInfo.SubtitleInfo[i].Language + (existingSRTCount > 0 ? existingSRTCount.ToString() : "")) + ".srt"; // Create a unique SRT filename, try with language first, if not give a unique identifier, avoid a loop existingSRTCount++; } } // Build ffmpeg command line parameters += " -i " + FilePaths.FixSpaces(sourceFile); parameters += " -an -vn"; parameters += " -map 0:" + mediaInfo.MediaInfo.SubtitleInfo[i].Stream.ToString(); // Subtitle steam no parameters += " -scodec copy -copyinkf -f srt " + FilePaths.FixSpaces(outputSRTFile); // Now extract it _jobLog.WriteEntry(this, "Extracting Subtitle " + (i + 1).ToString() + " with language >" + mediaInfo.MediaInfo.SubtitleInfo[i].Language + "< to " + outputSRTFile, Log.LogEntryType.Debug); FFmpeg ffmpeg = new FFmpeg(parameters, _jobStatus, _jobLog); ffmpeg.Run(); if (!ffmpeg.Success) { FileIO.TryFileDelete(outputSRTFile); // Delete partial file // Backup, try using MP4Box instead to extract it _jobLog.WriteEntry(this, ("FFMPEG failed to extract subtitles into SRT file, retrying using MP4Box"), Log.LogEntryType.Warning); parameters = "-srt " + (mediaInfo.MediaInfo.SubtitleInfo[i].Stream + 1).ToString() + " " + FilePaths.FixSpaces(sourceFile); // MP4Box create an output file called <input>_<track>_text.srt // Check if the output srt exists and then rename it if it does string tempSrtOutput = FilePaths.GetFullPathWithoutExtension(sourceFile) + "_" + (mediaInfo.MediaInfo.SubtitleInfo[i].Stream + 1).ToString() + "_text.srt"; bool tempSrtExists = false; if (File.Exists(tempSrtOutput)) // Save the output srt filename if it exists { try { FileIO.MoveAndInheritPermissions(tempSrtOutput, tempSrtOutput + ".tmp"); } catch (Exception e) { _jobLog.WriteEntry(this, ("Error extracting subtitles into SRT file.\r\n" + e.ToString()), Log.LogEntryType.Error); return(false); } tempSrtExists = true; } // Extract the subtitle MP4Box mp4Box = new MP4Box(parameters, _jobStatus, _jobLog); mp4Box.Run(); if (!mp4Box.Success) { _jobLog.WriteEntry(this, ("Error extracting subtitles into SRT file"), Log.LogEntryType.Error); FileIO.TryFileDelete(tempSrtOutput); // Delete partial file if (tempSrtExists) { RestoreSavedSrt(tempSrtOutput); } return(false); } if (FileIO.FileSize(tempSrtOutput) <= 0) // MP4Box always return success even if nothing is extracted, so check if has been extracted { _jobLog.WriteEntry(this, "No or empty Subtitle file " + tempSrtOutput + " extracted by MP4Box, skipping", Log.LogEntryType.Debug); FileIO.TryFileDelete(tempSrtOutput); // Delete empty file } else { // Rename the temp output SRT to the expected name try { FileIO.MoveAndInheritPermissions(tempSrtOutput, outputSRTFile); } catch (Exception e) { _jobLog.WriteEntry(this, ("Error extracting subtitles into SRT file.\r\n" + e.ToString()), Log.LogEntryType.Error); FileIO.TryFileDelete(tempSrtOutput); // Delete partial file if (tempSrtExists) { RestoreSavedSrt(tempSrtOutput); } return(false); } } // Restore temp SRT file if it exists if (tempSrtExists) { RestoreSavedSrt(tempSrtOutput); } } if (FileIO.FileSize(outputSRTFile) <= 0) // Check for empty files { _jobLog.WriteEntry(this, "Empty Subtitle file " + outputSRTFile + " extracted, deleting it", Log.LogEntryType.Warning); FileIO.TryFileDelete(outputSRTFile); // Delete empty file } else { // Trim the SRT file if required if (startTrim > 0 || endTrim > 0) { // Get the length of the video, needed to calculate end point float Duration = 0; Duration = VideoParams.VideoDuration(sourceFile); if (Duration <= 0) { FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(sourceFile, _jobStatus, _jobLog); if (ffmpegStreamInfo.Success && !ffmpegStreamInfo.ParseError) { // Converted file should contain only 1 audio stream Duration = ffmpegStreamInfo.MediaInfo.VideoInfo.Duration; _jobLog.WriteEntry(this, ("Video duration") + " : " + Duration.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Information); if (Duration == 0) { _jobLog.WriteEntry(this, ("Video duration 0"), Log.LogEntryType.Error); return(false); } } else { _jobLog.WriteEntry(this, ("Cannot read video duration"), Log.LogEntryType.Error); return(false); } } // Trim the subtitle if (!TrimSubtitle(outputSRTFile, workingPath, startTrim, endTrim, Duration, 0)) { _jobLog.WriteEntry(this, ("Error trimming SRT file"), Log.LogEntryType.Error); return(false); } } // Clean it up and offset the SRT if required if (!SRTValidateAndClean(outputSRTFile, offset)) { _jobLog.WriteEntry(this, ("Cannot clean and set offset for SRT file"), Log.LogEntryType.Error); return(false); } // Check for empty file if (Util.FileIO.FileSize(outputSRTFile) <= 0) { FileIO.TryFileDelete(outputSRTFile); // Delete the empty file _jobLog.WriteEntry(this, ("No valid SRT file found"), Log.LogEntryType.Warning); continue; // check for the next subtitle track } _extractedSRTFile = outputSRTFile; // Save it _jobLog.WriteEntry(this, "Extracted Subtitle file " + outputSRTFile + ", size [KB] " + (FileIO.FileSize(outputSRTFile) / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); extractedSubtitle = true; // We have success } } return(true); }
private bool MP4BoxRemuxAvi() { _jobStatus.ErrorMsg = ""; _jobStatus.PercentageComplete = 100; //all good to start with _jobStatus.ETA = ""; string fileNameBase = Path.Combine(_workingPath, Path.GetFileNameWithoutExtension(_originalFile)); string audioStream = fileNameBase + "_audio.raw"; string newAudioStream = fileNameBase + "_audio.aac"; string videoStream = fileNameBase + "_video.h264"; Util.FileIO.TryFileDelete(RemuxedTempFile); // Video string Parameters = " -keep-sys -aviraw video -out " + FilePaths.FixSpaces(videoStream) + " " + FilePaths.FixSpaces(_originalFile); _jobStatus.CurrentAction = Localise.GetPhrase("Remuxing to") + " " + _remuxTo.ToLower() + " " + Localise.GetPhrase("Part") + " 1"; MP4Box mp4Box = new MP4Box(Parameters, _jobStatus, _jobLog); mp4Box.Run(); if (!mp4Box.Success || _jobStatus.PercentageComplete < GlobalDefs.ACCEPTABLE_COMPLETION) // check for completion of job { _jobStatus.ErrorMsg = "MP4Box Remux Video AVI failed"; _jobLog.WriteEntry(this, _jobStatus.ErrorMsg + " at " + _jobStatus.PercentageComplete.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Error); return(false); } // Audio Parameters = " -keep-all -keep-sys -aviraw audio -out " + FilePaths.FixSpaces(audioStream) + " " + FilePaths.FixSpaces(_originalFile); _jobStatus.CurrentAction = Localise.GetPhrase("Remuxing to") + " " + _remuxTo.ToLower() + " " + Localise.GetPhrase("Part") + " 2"; mp4Box = new MP4Box(Parameters, _jobStatus, _jobLog); mp4Box.Run(); if (!mp4Box.Success || _jobStatus.PercentageComplete < GlobalDefs.ACCEPTABLE_COMPLETION) //check for completion of job { _jobStatus.ErrorMsg = "MP4Box Remux Audio AVI failed"; _jobLog.WriteEntry(this, _jobStatus.ErrorMsg + " at " + _jobStatus.PercentageComplete.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Error); return(false); } // Check if streams are extracted if ((File.Exists(audioStream)) && (File.Exists(videoStream))) { _jobLog.WriteEntry(this, Localise.GetPhrase("MP4Box remux avi moving file"), Log.LogEntryType.Information); try { Util.FileIO.TryFileDelete(newAudioStream); FileIO.MoveAndInheritPermissions(audioStream, newAudioStream); } catch (Exception e) { _jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move remuxed stream") + " " + audioStream + " to " + newAudioStream + "\r\nError : " + e.ToString(), Log.LogEntryType.Error); _jobStatus.PercentageComplete = 0; _jobStatus.ErrorMsg = "Unable to move muxed stream"; return(false); } string mergeParameters = " -keep-sys -keep-all"; //Check for Null FPS (bug with MediaInfo for some .TS files) if (_fps <= 0) { _jobLog.WriteEntry(this, Localise.GetPhrase("Mp4BoxRemuxAVI FPS 0 reported by video file - non compliant video file, skipping adding to parameter"), Log.LogEntryType.Warning); mergeParameters += " -add " + FilePaths.FixSpaces(videoStream) + " -add " + FilePaths.FixSpaces(newAudioStream) + " -new " + FilePaths.FixSpaces(RemuxedTempFile); } else { mergeParameters += " -fps " + _fps.ToString(System.Globalization.CultureInfo.InvariantCulture) + " -add " + FilePaths.FixSpaces(videoStream) + " -add " + FilePaths.FixSpaces(newAudioStream) + " -new " + FilePaths.FixSpaces(RemuxedTempFile); } _jobStatus.CurrentAction = Localise.GetPhrase("Remuxing to") + " " + _remuxTo.ToLower() + " " + Localise.GetPhrase("Part") + " 3"; mp4Box = new MP4Box(mergeParameters, _jobStatus, _jobLog); mp4Box.Run(); if (!mp4Box.Success || _jobStatus.PercentageComplete < GlobalDefs.ACCEPTABLE_COMPLETION) // check for completion { _jobStatus.ErrorMsg = "Mp4Box Remux Merger AVI with FPS conversion failed"; _jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); return(false); } Util.FileIO.TryFileDelete(videoStream); Util.FileIO.TryFileDelete(newAudioStream); _jobLog.WriteEntry(this, Localise.GetPhrase("MP4Box remux AVI trying to move remuxed file"), Log.LogEntryType.Information); return(ReplaceTempRemuxed()); } else { _jobLog.WriteEntry(this, Localise.GetPhrase("MP4Box Remux AVI of") + " " + _originalFile + " " + Localise.GetPhrase("failed. Extracted video and audio streams not found."), Log.LogEntryType.Error); _jobStatus.PercentageComplete = 0; _jobStatus.ErrorMsg = "Remux failed, extracted video streams not found"; return(false); } }
/// <summary> /// Remuxes the converted file to the specified extension/format using FFMPEG. /// (Optionally) If a new Audio Stream file is specified, the audio from the new file is taken and video from the original file /// </summary> /// <param name="newAudioStream">(Optional) New Audio stream to use</param> /// <returns>True is successful</returns> public bool FfmpegRemux(string newAudioStream = "") { _jobStatus.ErrorMsg = ""; _jobStatus.PercentageComplete = 100; //all good to start with _jobStatus.ETA = ""; Util.FileIO.TryFileDelete(RemuxedTempFile); FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(_originalFile, _jobStatus, _jobLog); if (!ffmpegStreamInfo.Success || ffmpegStreamInfo.ParseError) { _jobStatus.ErrorMsg = "Unable to read video information"; _jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); _jobStatus.PercentageComplete = 0; return(false); } // Input original string ffmpegParams = "-y -i " + FilePaths.FixSpaces(_originalFile); // Add New Audio stream if (!String.IsNullOrEmpty(newAudioStream)) { // Take audio stream from new audio file ffmpegParams += " -i " + FilePaths.FixSpaces(newAudioStream) + " -map 1:a -acodec copy"; // Video from the original file if (ffmpegStreamInfo.MediaInfo.VideoInfo.Stream != -1) { ffmpegParams += " -map 0:" + ffmpegStreamInfo.MediaInfo.VideoInfo.Stream.ToString() + " -vcodec copy"; // Fix for FFMPEG WTV MJPEG ticket #2227 } else { ffmpegParams += " -vn"; } } else { // Check for audio tracks if (ffmpegStreamInfo.AudioTracks > 0) { ffmpegParams += " -map 0:a -acodec copy"; } else { ffmpegParams += " -an"; } // Check for video tracks if (ffmpegStreamInfo.MediaInfo.VideoInfo.Stream != -1) { ffmpegParams += " -map 0:" + ffmpegStreamInfo.MediaInfo.VideoInfo.Stream.ToString() + " -vcodec copy"; // Fix for FFMPEG WTV MJPEG ticket #2227 } else { ffmpegParams += " -vn"; } } ffmpegParams += " " + FilePaths.FixSpaces(RemuxedTempFile); if (!FFmpeg.FFMpegExecuteAndHandleErrors(ffmpegParams, _jobStatus, _jobLog, FilePaths.FixSpaces(RemuxedTempFile))) // Let this function handle error conditions since it's just a simple execute { _jobStatus.ErrorMsg = Localise.GetPhrase("ffmpeg remux failed"); _jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); return(false); } _jobLog.WriteEntry(this, Localise.GetPhrase("FFMPEG ReMux moving file"), Log.LogEntryType.Information); return(ReplaceTempRemuxed()); }