public bool Trim(string sourceVideo, string workingPath, int startTrim, int endTrim) { _jobLog.WriteEntry(this, "Start Trim : " + startTrim.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Stop Trim : " + endTrim.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Video File : " + sourceVideo, Log.LogEntryType.Debug); if ((startTrim == 0) && (endTrim == 0)) { _trimmedVideo = sourceVideo; // It's the same file return true; // nothing to do here } string tempFile = Path.Combine(workingPath, Path.GetFileNameWithoutExtension(sourceVideo) + "-temp" + Util.FilePaths.CleanExt(sourceVideo)); // Get the length of the video, needed to calculate end point float Duration; Duration = VideoParams.VideoDuration(sourceVideo); FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(sourceVideo, _jobStatus, _jobLog); if (!ffmpegStreamInfo.Success || ffmpegStreamInfo.ParseError) { _jobLog.WriteEntry(this, "Cannot read video info", Log.LogEntryType.Error); return false; } if (Duration <= 0) { // Converted file should contain only 1 audio stream Duration = ffmpegStreamInfo.MediaInfo.VideoInfo.Duration; _jobLog.WriteEntry(this, "Video duration : " + Duration.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Information); if (Duration == 0) { _jobLog.WriteEntry(this, "Video duration 0", Log.LogEntryType.Error); return false; } } // dont' use threads here since we are copying video to improve stability string ffmpegParams = "-y"; ffmpegParams += " -i " + Util.FilePaths.FixSpaces(sourceVideo); // While setting the start trim before the input file (FAST seek) can speed up seeking and (http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg) FAST seek (since accurate seek cuts on a NON KeyFrame which causes Audio Sync Issues) // Due to ffmpeg ticket #3252 we need to use ACCURATE seek (trim after input file) to avoid PTS<DTS error if (startTrim != 0) { if (startTrim < Duration) ffmpegParams += " -ss " + startTrim.ToString(System.Globalization.CultureInfo.InvariantCulture); else { _jobLog.WriteEntry(this, "Start trim (" + startTrim.ToString() + ") greater than file duration (" + Duration.ToString(System.Globalization.CultureInfo.InvariantCulture) + "). Skipping start trimming.", Log.LogEntryType.Warning); startTrim = 0; // Skip it } } // Set the end trim (calculate from reducing from video length) if (endTrim != 0) { // FFMPEG can specify duration of encoding, i.e. encoding_duration = stopTime - startTime // startTime = startTrim, stopTime = video_duration - endTrim int encDuration = (((int)Duration) - endTrim) - (startTrim); // by default _startTrim is 0 if (encDuration > 0) ffmpegParams += " -t " + encDuration.ToString(System.Globalization.CultureInfo.InvariantCulture); else { _jobLog.WriteEntry(this, "End trim (" + endTrim.ToString() + ") + Start trim (" + startTrim.ToString() + ") greater than file duration (" + Duration.ToString(System.Globalization.CultureInfo.InvariantCulture) + "). Skipping end trimming.", Log.LogEntryType.Warning); endTrim = 0; } } // Sanity check once more if ((startTrim == 0) && (endTrim == 0)) { _jobLog.WriteEntry(this, "Start trim and end trim skipped. Skipping trimming.", Log.LogEntryType.Warning); _trimmedVideo = sourceVideo; // It's the same file return true; // nothing to do here } // Check for audio channels if (ffmpegStreamInfo.AudioTracks > 0) ffmpegParams += " -map 0:a -acodec copy"; else ffmpegParams += " -an"; // Check for video stream 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"; //Output file ffmpegParams += " " + Util.FilePaths.FixSpaces(tempFile); if (!FFmpeg.FFMpegExecuteAndHandleErrors(ffmpegParams, _jobStatus, _jobLog, Util.FilePaths.FixSpaces(tempFile))) //check of file is created, outputhandler reports success (Percentage not requires since Success is more accurate) { _jobLog.WriteEntry("Failed to trim video at " + _jobStatus.PercentageComplete.ToString(System.Globalization.CultureInfo.InvariantCulture) + "%", Log.LogEntryType.Error); return false; } // Replace the file if (File.Exists(tempFile)) { _jobLog.WriteEntry(this, "TrimVideo trying to replace file Source : " + sourceVideo + " Temp : " + tempFile, Log.LogEntryType.Debug); // If the original uncut file is in the working temp directory, then just replace it if (Path.GetDirectoryName(sourceVideo).ToLower() == workingPath.ToLower()) Util.FileIO.TryFileReplace(sourceVideo, tempFile); else // If the original uncut video is not in the working temp directory, then just rename the tempFile with the original name and keep in the temp working directory (don't mangle original video file) { FileIO.TryFileDelete(Path.Combine(workingPath, Path.GetFileName(sourceVideo))); // Just in case it exists File.Move(tempFile, Path.Combine(workingPath, Path.GetFileName(sourceVideo))); } _trimmedVideo = Path.Combine(workingPath, Path.GetFileName(sourceVideo)); // The final cut video always lies in the working directory } else { _jobLog.WriteEntry(this, "TrimVideo cannot find temp file " + tempFile, Log.LogEntryType.Error); _jobStatus.PercentageComplete = 0; return false; } return true; // All good here }
private bool FixAudioDelay() { string encoderParams; _jobStatus.PercentageComplete = 100; //all good to start with _jobStatus.ETA = ""; if (_videoFile.AudioDelaySet || _toolAudioDelay == 0) return true; //It's already been done (probably by mencoder) or been requested to skip // Check if the converted file has Audio AND Video streams (if one is missing, then skip this step) FFmpegMediaInfo ffmpegInfo = new FFmpegMediaInfo(_convertedFile, _jobStatus, _jobLog); if (!ffmpegInfo.Success || ffmpegInfo.ParseError) { _jobStatus.PercentageComplete = 0; // if the file wasn't completely converted the percentage will be low so no worries _jobStatus.ErrorMsg = "Fix AudioSync getting mediainfo Failed for " + _convertedFile; _jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); return false; } if ((ffmpegInfo.MediaInfo.VideoInfo.Stream == -1) || (ffmpegInfo.AudioTracks < 1)) { _jobLog.WriteEntry(this, "Fix audiosync, No video or no audio track detected - skipping audio sync", Log.LogEntryType.Warning); return true; } double audioDelay = _toolAudioDelay; if (audioDelay != 0) { _jobLog.WriteEntry(this, ("Fixing Audio Delay, Detected :") + " " + _videoFile.AudioDelay.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", Manual Adj : " + _toolAudioDelay.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Information); string ext = FilePaths.CleanExt(_convertedFile); string fixedFile = Path.Combine(_workingPath, Path.GetFileNameWithoutExtension(_convertedFile) + "_AVFIX" + FilePaths.CleanExt(_convertedFile)); FileIO.TryFileDelete(fixedFile); _jobStatus.CurrentAction = Localise.GetPhrase("Correcting audio delay"); switch (ext) { case ".wmv": _jobLog.WriteEntry(this, ("Using ASFBin to correct audio sync for extension ") + ext, Log.LogEntryType.Debug); encoderParams = " -i " + Util.FilePaths.FixSpaces(_convertedFile) + " -adelay " + audioDelay.ToString(System.Globalization.CultureInfo.InvariantCulture) + " -o " + Util.FilePaths.FixSpaces(fixedFile) + " -y"; ASFBin asfBin = new ASFBin(encoderParams, _jobStatus, _jobLog); asfBin.Run(); if (!asfBin.Success || (FileIO.FileSize(fixedFile) <= 0)) { _jobStatus.ErrorMsg = "Fixing Audio Delay for WMV failed"; _jobLog.WriteEntry(this, ("Fixing Audio Delay for WMV failed"), Log.LogEntryType.Error); _jobStatus.PercentageComplete = 0; return false; } break; case ".avi": _jobLog.WriteEntry(this, ("Using Mencoder to correct audio sync for extension ") + ext, Log.LogEntryType.Debug); encoderParams = Util.FilePaths.FixSpaces(_convertedFile) + " -oac copy -ovc copy -ni -delay " + (-1 * audioDelay).ToString(System.Globalization.CultureInfo.InvariantCulture) + " -o " + Util.FilePaths.FixSpaces(fixedFile.ToString(System.Globalization.CultureInfo.InvariantCulture)); // avoid using threads since we are copying to increase stability _jobLog.WriteEntry(this, "Fixing Audio Delay using MEncoder with Parameters: " + encoderParams, Log.LogEntryType.Debug); Mencoder mencoderAVI = new Mencoder(encoderParams, _jobStatus, _jobLog, false); mencoderAVI.Run(); if (!mencoderAVI.Success) // something failed or was incomplete, do not check for % completion as Mencoder looks fro success criteria { _jobStatus.PercentageComplete = 0; _jobStatus.ErrorMsg = ("Fix AudioSync failed for") + " " + ext; _jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); return false; } break; default: _jobLog.WriteEntry(this, ("Using FFMPEG to correct audio sync for extension ") + ext, Log.LogEntryType.Debug); if (audioDelay > 0) // Map same file as 2 inputs, shift and take the audio in one and take the video from the other { encoderParams = "-y -i " + Util.FilePaths.FixSpaces(_convertedFile) + " -ss " + audioDelay.ToString(System.Globalization.CultureInfo.InvariantCulture) +" -i " + Util.FilePaths.FixSpaces(_convertedFile) + " -map 1:v -map 0:a -acodec copy -vcodec copy"; _jobLog.WriteEntry(this, "Fixing +ve Audio Delay using FFMPEG", Log.LogEntryType.Debug); } // if audio is behind the video skip seconds from the 2nd input file and remap to ouput (keeping the audio shift positive) else { encoderParams = "-y -ss " + (audioDelay * -1).ToString(System.Globalization.CultureInfo.InvariantCulture) + " -i " + Util.FilePaths.FixSpaces(_convertedFile) + " -i " + Util.FilePaths.FixSpaces(_convertedFile) + " -map 1:v -map 0:a -acodec copy -vcodec copy"; _jobLog.WriteEntry(this, "Fixing -ve Audio Delay using FFMPEG", Log.LogEntryType.Debug); } encoderParams += " " + Util.FilePaths.FixSpaces(fixedFile); if (!FFmpeg.FFMpegExecuteAndHandleErrors(encoderParams, _jobStatus, _jobLog, Util.FilePaths.FixSpaces(fixedFile))) // Do not check for % completion since FFMPEG doesn't always report a % for this routine for some reason { _jobStatus.PercentageComplete = 0; // if the file wasn't completely converted the percentage will be low so no worries _jobStatus.ErrorMsg = "Fix AudioSync Failed for " + ext; _jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); return false; } break; } try { _jobLog.WriteEntry(this, ("Fix Audio Delay trying to move fixed file"), Log.LogEntryType.Information); FileIO.TryFileDelete(_convertedFile); File.Move(fixedFile, _convertedFile); } catch (Exception e) { _jobLog.WriteEntry(this, ("Unable to move audio sync corrected file") + " " + fixedFile + "\r\nError : " + e.ToString(), Log.LogEntryType.Error); _jobStatus.ErrorMsg = "Unable to move audio sync file"; _jobStatus.PercentageComplete = 0; return false; } _jobLog.WriteEntry(this, ("Finished Audio Delay Correction, file size [KB]") + " " + (FileIO.FileSize(_convertedFile) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } else _jobLog.WriteEntry(this, ("Fix Audio Delay, net correction 0, skipping correction"), Log.LogEntryType.Information); return true; }
/// <summary> /// Updates the Video file properties structure, check for crop, audio and video information /// </summary> /// <param name="skipCropDetect">True to skip detecting cropping parameters</param> /// <param name="detectInterlace">Extracts the video intelacing type by analyzing it in depth</param> /// <param name="videoFileName">Path to Original Source Video</param> /// <param name="remuxedFileName">Path to Remuxed video, else null or empty string</param> /// <param name="edlFile">Path to EDL file else null or empty string</param> /// <param name="audioLanguage">Audio Language</param> /// <param name="jobStatus">JobStatus</param> /// <param name="jobLog">JobLog</param> public void UpdateVideoInfo(bool skipCropDetect, bool detectInterlace, string videoFileName, string remuxedFileName, string edlFile, string audioLanguage, JobStatus jobStatus, Log jobLog) { ResetParameters(); // Reset VideoInfo parameters _jobStatus = jobStatus; _jobLog = jobLog; _EDLFile = edlFile; _requestedAudioLanguage = audioLanguage; _originalFileName = videoFileName; _remuxedFileName = remuxedFileName; Ini ini = new Ini(GlobalDefs.ProfileFile); _skipCropDetect = skipCropDetect; _detectInterlacing = detectInterlace; _jobLog.WriteEntry(this, "Reading MediaInfo from " + SourceVideo, Log.LogEntryType.Information); _videoCodec = VideoParams.VideoFormat(SourceVideo); jobLog.WriteEntry(this, "Video Codec : " + _videoCodec, Log.LogEntryType.Debug); if (String.IsNullOrWhiteSpace(_videoCodec)) _error = true; _audioCodec = VideoParams.AudioFormat(SourceVideo); jobLog.WriteEntry(this, "Audio Codec : " + _audioCodec, Log.LogEntryType.Debug); if (String.IsNullOrWhiteSpace(_audioCodec)) _error = true; _fps = VideoParams.FPS(SourceVideo); jobLog.WriteEntry(this, "Video FPS : " + _fps.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); if (_fps <= 0) _error = true; _width = VideoParams.VideoWidth(SourceVideo); jobLog.WriteEntry(this, "Video Width : " + _width.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); if (_width <= 0) _error = true; _height = VideoParams.VideoHeight(SourceVideo); jobLog.WriteEntry(this, "Video Height : " + _height.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); if (_height <= 0) _error = true; _duration = VideoParams.VideoDuration(SourceVideo); jobLog.WriteEntry(this, "Video Duration : " + _duration.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); if (_duration <= 0) _error = true; _audioDelay = VideoParams.AudioDelay(SourceVideo); jobLog.WriteEntry(this, "Audio Delay : " + _audioDelay.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _scanType = VideoParams.VideoScanType(SourceVideo); jobLog.WriteEntry(this, "Video Scan Type : " + _scanType.ToString(), Log.LogEntryType.Debug); if (_scanType == ScanType.Unknown && _detectInterlacing) // If we couldn't find it lets try ffmpeg - avoid unnecessary cycles if not required { jobLog.WriteEntry(this, "Scan type unknown, trying with FFMPEGMediaInfo", Log.LogEntryType.Debug); _ffmpegStreamInfo = new FFmpegMediaInfo(SourceVideo, _jobStatus, _jobLog, 0, 0, _ignoreSuspend); // Run interlace detection with defaults if (_ffmpegStreamInfo.Success && !_ffmpegStreamInfo.ParseError) { // Now calcuate whether it's Interlaced or Progressive based on the Multi Frame Interlaced Detection Results long totalInterlaced = _ffmpegStreamInfo.MFInterlaceDetectionResults.BFF + _ffmpegStreamInfo.MFInterlaceDetectionResults.TFF; long totalProgressive = _ffmpegStreamInfo.MFInterlaceDetectionResults.Progressive; long totalUndetermined = _ffmpegStreamInfo.MFInterlaceDetectionResults.Undetermined; // TODO: What to do with this? if (totalInterlaced == 0 && totalProgressive == 0) // Boundary conditions _scanType = ScanType.Unknown; else if (totalInterlaced == 0) // Avoid divide by zero exception _scanType = ScanType.Progressive; else { double PtoIRatio = totalProgressive / totalInterlaced; // See below comment // Refer to this, how to tell if the video is interlaced or telecine // http://forum.videohelp.com/threads/295007-Interlaced-or-telecined-how-to-tell?p=1797771&viewfull=1#post1797771 // It is a statistical ratio, telecine has approx split of 3 progressive and 2 interlaced (i.e. ratio of about 1.5 progressive to interlaced) // TODO: We need to revisit the logic below for telecine, interlaced or progressive detection (check idet filter for updates ffmpeg ticket #3073) if ((totalProgressive == 0) && (totalProgressive == 0)) // Unknown - could not find _scanType = ScanType.Unknown; else if ((PtoIRatio > TELECINE_LOW_P_TO_I_RATIO) && (PtoIRatio < TELECINE_HIGH_P_TO_I_RATIO)) // Let us keep a band to measure telecine ratio, see comment above _scanType = ScanType.Telecine; else if (PtoIRatio <= TELECINE_LOW_P_TO_I_RATIO) // We play safe, more interlaced than progressive _scanType = ScanType.Interlaced; else if (PtoIRatio >= TELECINE_HIGH_P_TO_I_RATIO) // Progressive has the clear lead _scanType = ScanType.Progressive; else _scanType = ScanType.Unknown; // No idea where we are } jobLog.WriteEntry(this, "FFMPEG Video Scan Type : " + _scanType.ToString(), Log.LogEntryType.Debug); } else jobLog.WriteEntry(this, "Error reading scan type from FFMPEGMediaInfo", Log.LogEntryType.Warning); } // We don't get AudioChannel information here as it interfers with FFMPEG /*mi.Option("Inform", "Audio; %Channels%"); int.TryParse(mi.Inform(), out _audioChannels); jobLog.WriteEntry(this, "Audio Channels : " + _audioChannels.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug);*/ // Supplement with extracting Video and Audio information (sometimes MediaInfo fails) using FFMPEG and selected the Audio Language specified _jobLog.WriteEntry(this, "Supplementing Media information using FFMPEG", Log.LogEntryType.Information); _ffmpegStreamInfo = new FFmpegMediaInfo(SourceVideo, _jobStatus, _jobLog, _ignoreSuspend); // this may be called from the UI request if (_ffmpegStreamInfo.Success && !_ffmpegStreamInfo.ParseError) { // Store the video information (there's only 1 video per file) _width = _ffmpegStreamInfo.MediaInfo.VideoInfo.Width; _height = _ffmpegStreamInfo.MediaInfo.VideoInfo.Height; if ((_fps <= 0) || ((_fps > _ffmpegStreamInfo.MediaInfo.VideoInfo.FPS) && (_ffmpegStreamInfo.MediaInfo.VideoInfo.FPS > 0))) // Check _fps, sometimes MediaInfo get it below 0 or too high (most times it's reliable) _fps = _ffmpegStreamInfo.MediaInfo.VideoInfo.FPS; else _ffmpegStreamInfo.MediaInfo.VideoInfo.FPS = _fps; // Store the value from MediaInfo, more reliable _duration = _ffmpegStreamInfo.MediaInfo.VideoInfo.Duration; _videoCodec = _ffmpegStreamInfo.MediaInfo.VideoInfo.VideoCodec; _videoStream = _ffmpegStreamInfo.MediaInfo.VideoInfo.Stream; _videoPID = _ffmpegStreamInfo.MediaInfo.VideoInfo.PID; // video PID // Default Check if all audio streams have same codec, if so populate the field for later use during reading profiles for (int i = 0; i < _ffmpegStreamInfo.AudioTracks; i++) { if (i == 0) _audioCodec = _ffmpegStreamInfo.MediaInfo.AudioInfo[i].AudioCodec; // baseline the codec name else if (_audioCodec != _ffmpegStreamInfo.MediaInfo.AudioInfo[i].AudioCodec) { _audioCodec = ""; // All codecs are not the same, reset it and let the encoder figure it out break; // we're done here } } // Default check if all audio streams have same channels, if so populate the field for later use during reading profiles for (int i = 0; i < _ffmpegStreamInfo.AudioTracks; i++) { if (i == 0) _audioChannels = _ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels; // baseline the channels else if (_audioChannels != _ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels) { _audioChannels = 0; // All channels are not the same, reset it and let the encoder figure it out break; // we're done here } } // Audio parameters - find the best Audio channel for the selected language or best audio track if there are imparired tracks otherwise by default the encoder will select the best audio channel (encoders do not do a good job of ignoring imparired tracks) bool selectedTrack = false; if ((!String.IsNullOrEmpty(_requestedAudioLanguage) || (_ffmpegStreamInfo.ImpariedAudioTrackCount > 0)) && (_ffmpegStreamInfo.AudioTracks > 1)) // More than 1 audio track to choose from and either we have a language match request or a presence of an imparied channel (likely no audio) { for (int i = 0; i < _ffmpegStreamInfo.AudioTracks; i++) { bool processTrack = false; // By default we don't need to process // Language selection check, if the user has picked a specific language code, look for it // If we find a match, we look the one with the highest number of channels in it if (!String.IsNullOrEmpty(_requestedAudioLanguage)) { if ((_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Language.ToLower() == _requestedAudioLanguage) && (_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels > 0)) { if (selectedTrack) { if (!( // take into account impaired tracks (since impaired tracks typically have no audio) ((_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels > _audioChannels) && !_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Impaired) || // PREFERENCE to non-imparied Audio tracks with the most channels ((_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels > _audioChannels) && _selectedAudioImpaired) || // PREFERENCE to Audio tracks with most channels if currently selected track is impaired (!_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Impaired && _selectedAudioImpaired) // PREFER non impaired audio over currently selected impaired )) continue; // we have found a lang match, now we are looking for more channels only now } processTrack = true; // All conditions met, we need to process this track } } else if (_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels > 0)// we have a imparied audio track, select the non impaired track with the highest number of tracks or bitrate or frequency { if (selectedTrack) { if (!( // take into account impaired tracks (since impaired tracks typically have no audio) ((_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels > _audioChannels) && !_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Impaired) || // PREFERENCE to non-imparied Audio tracks with the most channels ((_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels > _audioChannels) && _selectedAudioImpaired) || // PREFERENCE to Audio tracks with most channels if currently selected track is impaired (!_ffmpegStreamInfo.MediaInfo.AudioInfo[i].Impaired && _selectedAudioImpaired) // PREFER non impaired audio over currently selected impaired )) continue; // we have found a lang match, now we are looking for more channels only now } processTrack = true; // All conditions met, we need to process this track } if (processTrack) // We need to process this track { _audioChannels = _ffmpegStreamInfo.MediaInfo.AudioInfo[i].Channels; _audioStream = _ffmpegStreamInfo.MediaInfo.AudioInfo[i].Stream; // store the stream number for the selected audio channel _audioCodec = _ffmpegStreamInfo.MediaInfo.AudioInfo[i].AudioCodec; _audioPID = _ffmpegStreamInfo.MediaInfo.AudioInfo[i].PID; // Audio PID _audioTrack = i; // Store the audio track number we selected _audioLanguage = _ffmpegStreamInfo.MediaInfo.AudioInfo[i].Language.ToLower(); // this is what we selected _selectedAudioImpaired = _ffmpegStreamInfo.MediaInfo.AudioInfo[i].Impaired; // Is this an imparied audio track? selectedTrack = true; // We found a suitable track if (!String.IsNullOrEmpty(_requestedAudioLanguage)) _jobLog.WriteEntry(this, "Found Audio Language match for language" + " " + _requestedAudioLanguage.ToUpper() + ", " + "Audio Stream" + " " + _audioStream.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Audio Track") + " " + _audioTrack.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Channels") + " " + _audioChannels.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Codec") + " " + _audioCodec + " Impaired " + _selectedAudioImpaired.ToString(), Log.LogEntryType.Debug); else _jobLog.WriteEntry(this, "Compensating for audio impaired tracks, selected track with language" + " " + _requestedAudioLanguage.ToUpper() + ", " + "Audio Stream" + " " + _audioStream.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Audio Track") + " " + _audioTrack.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Channels") + " " + _audioChannels.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Codec") + " " + _audioCodec + " Impaired " + _selectedAudioImpaired.ToString(), Log.LogEntryType.Debug); } } if (!selectedTrack) _jobLog.WriteEntry(this, ("Could not find a match for selected Audio Language Code") + " " + _requestedAudioLanguage + ", letting encoder choose best audio language", Log.LogEntryType.Warning); else _jobLog.WriteEntry(this, ("Selected Audio Stream") + " " + _audioStream.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Audio Track") + " " + _audioTrack.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Channels") + " " + _audioChannels.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Codec") + " " + _audioCodec + ", Impaired " + _selectedAudioImpaired.ToString(), Log.LogEntryType.Debug); } else if (_ffmpegStreamInfo.AudioTracks == 1) // We have just one audio track, then populate the information otherwise the encoding operations will have a hard time determining audio information { if (_ffmpegStreamInfo.MediaInfo.AudioInfo[0].Channels > 0) { _audioChannels = _ffmpegStreamInfo.MediaInfo.AudioInfo[0].Channels; _audioStream = _ffmpegStreamInfo.MediaInfo.AudioInfo[0].Stream; // store the stream number for the selected audio channel _audioCodec = _ffmpegStreamInfo.MediaInfo.AudioInfo[0].AudioCodec; _audioPID = _ffmpegStreamInfo.MediaInfo.AudioInfo[0].PID; // Audio PID _audioTrack = 0; // Store the audio track number we selected _audioLanguage = _ffmpegStreamInfo.MediaInfo.AudioInfo[0].Language.ToLower(); // this is what we selected _selectedAudioImpaired = _ffmpegStreamInfo.MediaInfo.AudioInfo[0].Impaired; // Is this an imparied audio track? _jobLog.WriteEntry(this, "Only one audio track present, " + ("Audio Stream") + " " + _audioStream.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Audio Track") + " " + _audioTrack.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Channels") + " " + _audioChannels.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Codec") + " " + _audioCodec + " Impaired " + _selectedAudioImpaired.ToString(), Log.LogEntryType.Debug); } } else { _jobLog.WriteEntry(this, ("Audio Stream") + " " + _audioStream.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Audio Track") + " " + _audioTrack.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Channels") + " " + _audioChannels.ToString(System.Globalization.CultureInfo.InvariantCulture) + ", " + ("Codec") + " " + _audioCodec + ", Impaired " + _selectedAudioImpaired.ToString(), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "No audio language selected, letting encoder choose best audio language", Log.LogEntryType.Warning); } _error = false; // all good now } else _error = true; if (_error) { _jobLog.WriteEntry(this, ("Unable to read media information using FFMPEG or MediaInfo"), Log.LogEntryType.Error); return; } // Get the video properties for the original video _jobLog.WriteEntry(this, "Reading Original File Media information", Log.LogEntryType.Information); _originalFileFFmpegStreamInfo = new FFmpegMediaInfo(_originalFileName, _jobStatus, _jobLog, _ignoreSuspend); if (!_originalFileFFmpegStreamInfo.Success || _originalFileFFmpegStreamInfo.ParseError) _jobLog.WriteEntry(this, ("Unable to read media information using FFMPEG"), Log.LogEntryType.Warning); if (_skipCropDetect) _jobLog.WriteEntry(this, "Skipping crop information", Log.LogEntryType.Information); else UpdateCropInfo(jobLog); }
public bool Extract(string sourceFile, string workingPath, string ccOptions, int startTrim, int endTrim, double ccOffset) { _jobLog.WriteEntry(this, Localise.GetPhrase("Extracting Closed Captions as 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, "CC Options : " + ccOptions, Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Start Trim : " + startTrim.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Stop Trim : " + endTrim.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Offset : " + ccOffset.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); if (String.IsNullOrWhiteSpace(ccOptions)) return true; // nothing to do, accidentally called // Output SRT file has to be working directory, will be copied to output afterwards string tmpExtractedSRTFile = Path.Combine(workingPath, Path.GetFileNameWithoutExtension(sourceFile)) + ".srt"; string ccExtractorParams = ""; string field = ""; string channel = ""; if (ccOptions.ToLower() != "default") // let ccextrator choose defaults if specified { // CCOptions are encoded as field,channel field = ccOptions.Split(',')[0]; channel = ccOptions.Split(',')[1]; } if (field == "1" || field == "2") ccExtractorParams += " -" + field; // Field is -1 or -2 (we don't support -12 since it creates 2 SRT files and there's no way to handle that) if (channel == "2") ccExtractorParams += " -cc2"; // By default is Channel 1, there is no parameter for it // Adjust for any offset required during extraction (opposite direction, so -ve) if (ccOffset != 0) ccExtractorParams += " -delay " + (-ccOffset * 1000).ToString(System.Globalization.CultureInfo.InvariantCulture); // ccOffset is in seconds while -delay required milliseconds // 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, Localise.GetPhrase("Video duration") + " : " + Duration.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Information); if (Duration == 0) { _jobLog.WriteEntry(this, Localise.GetPhrase("Video duration 0"), Log.LogEntryType.Error); return false; } } else { _jobLog.WriteEntry(this, Localise.GetPhrase("Cannot read video duration"), Log.LogEntryType.Error); return false; } } // Set the start trim time if (startTrim != 0) ccExtractorParams += " -startat " + TimeSpan.FromSeconds((double)startTrim).ToString(); // Set the end trim time if (endTrim != 0) { // startTime = startTrim, stopTime = video_duration - endTrim int encDuration = (((int)Duration) - endTrim) - (startTrim); // by default _startTrim is 0 ccExtractorParams += " -endat " + TimeSpan.FromSeconds((double)encDuration).ToString(); } // Set the input file ccExtractorParams += " " + Util.FilePaths.FixSpaces(sourceFile); // set output file ccExtractorParams += " -o " + Util.FilePaths.FixSpaces(tmpExtractedSRTFile); // Run the command CCExtractor ccExtractor = new CCExtractor(ccExtractorParams, _jobStatus, _jobLog); ccExtractor.Run(); if (!ccExtractor.Success) // check for termination/success { _jobLog.WriteEntry("CCExtractor failed. Disabling detection , retrying using TS format", Log.LogEntryType.Warning); // TODO: Right format to pick (TS/ES/PS etc) - doing TS for now // Sometimes it doesn't detect the format correctly so try to force it (TS) ccExtractorParams += " -ts"; ccExtractor = new CCExtractor(ccExtractorParams, _jobStatus, _jobLog); ccExtractor.Run(); if (!ccExtractor.Success) // check for termination/success { _jobLog.WriteEntry("CCExtractor failed to extract closed captions", Log.LogEntryType.Error); return false; } } // Check if we have a format identification error (sometimes ccextractor misidentifies files) if ((Util.FileIO.FileSize(tmpExtractedSRTFile) <= 0) && ccExtractor.FormatReadingError) { FileIO.TryFileDelete(tmpExtractedSRTFile); // Delete the empty file _jobLog.WriteEntry(this, "No SRT file found and format error detected. CCExtractor may not have identified the format correctly, forcing TS format and retrying extraction.", Log.LogEntryType.Warning); ccExtractorParams += " -in=ts"; // force TS format and retry ccExtractor = new CCExtractor(ccExtractorParams, _jobStatus, _jobLog); ccExtractor.Run(); if (!ccExtractor.Success) // check for termination/success { _jobLog.WriteEntry(Localise.GetPhrase("Retrying: CCExtractor failed to extract closed captions"), Log.LogEntryType.Error); return false; } } // Check for empty file if (Util.FileIO.FileSize(tmpExtractedSRTFile) <= 0) { FileIO.TryFileDelete(tmpExtractedSRTFile); // Delete the empty file _jobLog.WriteEntry(this, Localise.GetPhrase("No valid SRT file found"), Log.LogEntryType.Warning); return true; // no error } _extractedSRTFile = tmpExtractedSRTFile; // We got the file _jobLog.WriteEntry(this, Localise.GetPhrase("Extracted closed captions to") + " " + _extractedSRTFile, Log.LogEntryType.Information); return true; }
/// <summary> /// Remux with adjustments the extracted raw stream parts individually using ffmpeg /// </summary> /// <param name="originalFile">Path to original source video</param> /// <param name="remuxedFile">Path to output remuxed file</param> /// <param name="extractGraphResults">Extract graph object which has extracts the stream into raw parts</param> /// <returns>Success or Failure</returns> public static bool RemuxRawPartsFFmpeg(string originalFile, string remuxedFile, ExtractWithGraph extractGraphResults, JobStatus jobStatus, Log jobLog) { float audioDelay = 0, fps = 0; string vcodec = ""; if (extractGraphResults == null || String.IsNullOrWhiteSpace(originalFile) || String.IsNullOrWhiteSpace(remuxedFile)) return false; // Atleast 1 audio or video stream if (String.IsNullOrWhiteSpace(extractGraphResults.VideoPart) && (extractGraphResults.AudioParts.Count < 1)) return false; Util.FileIO.TryFileDelete(remuxedFile); // Start clean // Audio Delay on the original file if (extractGraphResults.AudioParts.Count > 0) { audioDelay = VideoParams.AudioDelay(originalFile); jobLog.WriteEntry("Audio Delay : " + audioDelay.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } // FPS and codec of the video if (!String.IsNullOrWhiteSpace(extractGraphResults.VideoPart)) { fps = VideoParams.FPS(extractGraphResults.VideoPart); vcodec = VideoParams.VideoFormat(extractGraphResults.VideoPart).ToLower(); FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(extractGraphResults.VideoPart, jobStatus, jobLog); if (String.IsNullOrWhiteSpace(vcodec) || (fps <= 0) || ((fps > ffmpegStreamInfo.MediaInfo.VideoInfo.FPS) && (ffmpegStreamInfo.MediaInfo.VideoInfo.FPS > 0))) { jobLog.WriteEntry("MediaInfo reading FPS/Vcodec failed, Reading FFMPEG info from " + extractGraphResults.VideoPart, Log.LogEntryType.Debug); if (ffmpegStreamInfo.Success) // Don't check for parse error since this is raw video stream it will always give an parse error for N/A duration { if ((fps <= 0) || ((fps > ffmpegStreamInfo.MediaInfo.VideoInfo.FPS) && (ffmpegStreamInfo.MediaInfo.VideoInfo.FPS > 0))) // MediaInfo did not succeed or got wrong value fps = ffmpegStreamInfo.MediaInfo.VideoInfo.FPS; // Get the FPS if (String.IsNullOrWhiteSpace(vcodec)) vcodec = ffmpegStreamInfo.MediaInfo.VideoInfo.VideoCodec; // Get video codecc } if (fps <= 0) jobLog.WriteEntry("Unable to read FPS from " + originalFile + ", assuming no video stream", Log.LogEntryType.Warning); if (String.IsNullOrWhiteSpace(vcodec)) jobLog.WriteEntry("No Video Codec detected in raw stream, assuming no video stream", Log.LogEntryType.Warning); } jobLog.WriteEntry("Video Codec detected : " + vcodec + ", FPS : " + fps.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); // TODO: Check what vcodec is returned is by MediaInfo.dll and do we really need to do this? This is last recourse /*if ((_mpeg2Codecs.Any(s => s.Contains(vcodec.ToLower()))) && (!_allowH264CopyRemuxing)) // Accept H264 direct remuxing only if allowed { _jobLog.WriteEntry("FFMpegParts incompatible settings, non mpeg2video video detected and H264CopyRemuxing is not enabled", Log.LogEntryType.Error); return false; }*/ } string FFmpegParams = ""; string ffmpegMapParams = ""; // Keep track of the tracks added since we need to map them int ffmpegMapCount = 0; // Generate the timestamps, framerate and specify input format since it's raw video and ffmpeg won't recognize it FFmpegParams += "-fflags +genpts -y"; if (fps > 0) // Check if we have a video stream FFmpegParams += " -r " + fps.ToString(CultureInfo.InvariantCulture); // Check for valid video stream otherwise just audio if (!String.IsNullOrWhiteSpace(vcodec) && fps > 0) { if (audioDelay == 0) jobLog.WriteEntry("Skipping Audio Delay correction, cannnot read", Log.LogEntryType.Warning); if (audioDelay > 0) // positive is used on video track FFmpegParams += " -itsoffset " + audioDelay.ToString(CultureInfo.InvariantCulture); // Define input video stream type if ((vcodec == "avc") || (vcodec == "h264")) FFmpegParams += " -f h264"; else FFmpegParams += " -f mpegvideo"; FFmpegParams += " -i " + Util.FilePaths.FixSpaces(extractGraphResults.VideoPart); // Video ffmpegMapParams += " -map " + ffmpegMapCount.ToString() + ":v"; ffmpegMapCount++; } // Insert the audio streams foreach (string audioPart in extractGraphResults.AudioParts) { if (!String.IsNullOrWhiteSpace(vcodec) && fps > 0) if (audioDelay < 0) // Negative is used on audio tracks FFmpegParams += " -itsoffset " + (-1 * audioDelay).ToString(CultureInfo.InvariantCulture); FFmpegParams += " -i " + Util.FilePaths.FixSpaces(audioPart); // Audio streams ffmpegMapParams += " -map " + ffmpegMapCount.ToString() + ":a"; ffmpegMapCount++; } // Copy all Streams to output file FFmpegParams += ffmpegMapParams; // Audio if (extractGraphResults.AudioParts.Count > 0) FFmpegParams += " -acodec copy"; else FFmpegParams += " -an"; // Video if (!String.IsNullOrWhiteSpace(vcodec) && fps > 0) FFmpegParams += " -vcodec copy"; else FFmpegParams += " -vn"; // Output file FFmpegParams += " -f mpegts " + Util.FilePaths.FixSpaces(remuxedFile); if (!FFmpeg.FFMpegExecuteAndHandleErrors(FFmpegParams, jobStatus, jobLog, Util.FilePaths.FixSpaces(remuxedFile))) // process was terminated or failed { jobLog.WriteEntry("FFmpeg Remux Parts failed", Log.LogEntryType.Error); Util.FileIO.TryFileDelete(remuxedFile); return false; } return (FileIO.FileSize(remuxedFile) <= 0 ? false : true); // check if the file exists }
/// <summary> /// Put the extracting raw streams back together in TS format using TsMuxer /// </summary> /// <param name="originalFile">Path to original source video</param> /// <param name="remuxedFile">Path to output remuxed file</param> /// <param name="extractGraphResults">Extract graph object which has extracts the stream into raw parts</param> /// <returns>True if successful</returns> public static bool RemuxRawTSMuxer(string originalFile, string remuxedFile, ExtractWithGraph extractGraphResults, JobStatus jobStatus, Log jobLog) { float audioDelay = 0, fps = 0; string vcodec = ""; if (extractGraphResults == null || String.IsNullOrWhiteSpace(originalFile) || String.IsNullOrWhiteSpace(remuxedFile)) return false; // Atleast 1 audio or video stream if (String.IsNullOrWhiteSpace(extractGraphResults.VideoPart) && (extractGraphResults.AudioParts.Count < 1)) return false; Util.FileIO.TryFileDelete(remuxedFile); // Start clean // Audio Delay on the original file if (extractGraphResults.AudioParts.Count > 0) { audioDelay = VideoParams.AudioDelay(originalFile); jobLog.WriteEntry("Audio Delay : " + audioDelay.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } // fps and codec of video if (!String.IsNullOrWhiteSpace(extractGraphResults.VideoPart)) { fps = VideoParams.FPS(extractGraphResults.VideoPart); vcodec = VideoParams.VideoFormat(extractGraphResults.VideoPart).ToLower(); FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(extractGraphResults.VideoPart, jobStatus, jobLog); if (String.IsNullOrWhiteSpace(vcodec) || (fps <= 0) || ((fps > ffmpegStreamInfo.MediaInfo.VideoInfo.FPS) && (ffmpegStreamInfo.MediaInfo.VideoInfo.FPS > 0))) { jobLog.WriteEntry("MediaInfo reading FPS/Vcodec failed, Reading FFMPEG info from " + extractGraphResults.VideoPart, Log.LogEntryType.Debug); if (ffmpegStreamInfo.Success) // Don't check for parse error since this is raw video stream it will always give an parse error for N/A duration { if ((fps <= 0) || ((fps > ffmpegStreamInfo.MediaInfo.VideoInfo.FPS) && (ffmpegStreamInfo.MediaInfo.VideoInfo.FPS > 0))) // MediaInfo did not succeed or got wrong value fps = ffmpegStreamInfo.MediaInfo.VideoInfo.FPS; // Get the FPS if (String.IsNullOrWhiteSpace(vcodec)) vcodec = ffmpegStreamInfo.MediaInfo.VideoInfo.VideoCodec; // Get video codecc } if (fps <= 0) jobLog.WriteEntry("Unable to read FPS from " + originalFile + ", assuming no video stream", Log.LogEntryType.Warning); if (String.IsNullOrWhiteSpace(vcodec)) jobLog.WriteEntry("No Video Codec detected in raw stream, assuming no video stream", Log.LogEntryType.Warning); } jobLog.WriteEntry("Video Codec detected : " + vcodec + ", FPS : " + fps.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } // Setup the TSMuxer - http://forum.doom9.org/archive/index.php/t-142559.html string MetaFile = MCEBuddy.Util.FilePaths.GetFullPathWithoutExtension(remuxedFile) + ".meta"; string MetaFileContents = "MUXOPT --no-pcr-on-video-pid --new-audio-pes --vbr --vbv-len=500\r\n"; if (String.IsNullOrWhiteSpace(vcodec) || fps <= 0) jobLog.WriteEntry("No video stream detected, skipping video" + fps.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); // Nothing to do, no video stream else if ((vcodec == "avc") || (vcodec == "h264")) { /*if (!_allowH264CopyRemuxing) { _jobStatus.ErrorMsg = "TsMuxer incompatible settings, H264 video detected"; _jobLog.WriteEntry(_jobStatus.ErrorMsg, Log.LogEntryType.Error); return false; }*/ // There is no other recourse so mux it MetaFileContents += "V_MPEG4/ISO/AVC, " + Util.FilePaths.FixSpaces(extractGraphResults.VideoPart) + ", fps=" + fps.ToString(CultureInfo.InvariantCulture) + ", insertSEI, contSPS\r\n"; } else MetaFileContents += "V_MPEG-2, " + Util.FilePaths.FixSpaces(extractGraphResults.VideoPart) + ", fps=" + fps.ToString(CultureInfo.InvariantCulture) + "\r\n"; // Setup the Audio streams foreach (string AudioPart in extractGraphResults.AudioParts) { string acodec = VideoParams.AudioFormat(AudioPart).ToLower(); if (String.IsNullOrWhiteSpace(acodec)) { jobLog.WriteEntry("No Audio Codec detected in raw stream", Log.LogEntryType.Error); return false; } jobLog.WriteEntry("Audio Stream Codec detected : " + acodec, Log.LogEntryType.Debug); if (acodec.Contains("ac3") || acodec.Contains("ac-3")) MetaFileContents += "A_AC3"; else if (acodec.Contains("aac")) MetaFileContents += "A_AAC"; else if (acodec.Contains("dts")) MetaFileContents += "A_DTS"; else MetaFileContents += "A_MP3"; MetaFileContents += ", " + Util.FilePaths.FixSpaces(AudioPart); if (audioDelay == 0) jobLog.WriteEntry("Skipping Audio Delay correction, cannnot read", Log.LogEntryType.Warning); else MetaFileContents += ", timeshift=" + (-1 * audioDelay).ToString(CultureInfo.InvariantCulture) + "s"; MetaFileContents += "\r\n"; } try { jobLog.WriteEntry("Writing TsMuxer Meta commands : \r\n" + MetaFileContents + "\r\nTo : " + MetaFile, Log.LogEntryType.Debug); System.IO.File.WriteAllText(MetaFile, MetaFileContents); } catch (Exception e) { jobLog.WriteEntry(Localise.GetPhrase("ReMuxTsMuxer failed to write Metafile...") + "\r\nError : " + e.ToString(), Log.LogEntryType.Error); return false; } AppWrapper.TSMuxer tsmuxer = new TSMuxer(Util.FilePaths.FixSpaces(MetaFile) + " " + Util.FilePaths.FixSpaces(remuxedFile), jobStatus, jobLog); tsmuxer.Run(); Util.FileIO.TryFileDelete(MetaFile); if (!tsmuxer.Success) // process was terminated or failed { jobLog.WriteEntry("ReMuxTsMuxer failed", Log.LogEntryType.Error); Util.FileIO.TryFileDelete(remuxedFile); return false; } return (FileIO.FileSize(remuxedFile) <= 0 ? false : true); // check if the file exists }
/// <summary> /// Checks if the specified file has any zero channel audio tracks or no audio tracks (except original file) /// </summary> /// <param name="file">Full path to file to check</param> /// <returns>True if there are any zero channel audio tracks or No Audio Tracks (except Original file) or failure to read the audio streams</returns> private bool CheckForNoneOrZeroChannelAudioTrack(string file, JobStatus jobStatus, Log jobLog) { jobLog.WriteEntry("Checking for 0 Channel Audio Tracks in " + file, Log.LogEntryType.Debug); // Check for 0 channel audio stream in Remuxed file FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(file, jobStatus, jobLog); if (ffmpegStreamInfo.Success && !ffmpegStreamInfo.ParseError) { if (ffmpegStreamInfo.ZeroChannelAudioTrackCount > 0) { jobLog.WriteEntry("Found 0 channel audio track in file " + file, Log.LogEntryType.Warning); return true; } if (String.Compare(file, _RecordingFile, true) != 0) // We don't check original file for no audio tracks (it is allowed to have no audio tracks) { if (ffmpegStreamInfo.AudioTracks == 0 && _RecordingFileMediaInfo.AudioTracks != 0 && (_RecordingFileMediaInfo.AudioTracks > _RecordingFileMediaInfo.ImpariedAudioTrackCount)) // The original file itself may have no audio tracks or should have atleast 1 non imparired audio track { jobLog.WriteEntry("Found No audio tracks in remuxed file", Log.LogEntryType.Warning); return true; } } } else { jobLog.WriteEntry("Unable to read FFMPEG MediaInfo to verify audio streams in file " + file, Log.LogEntryType.Error); return true; } return false; // good }
public RemuxMCERecording(ConversionJobOptions cjo, JobStatus jobStatus, Log jobLog) { _jobStatus = jobStatus; _jobLog = jobLog; _RecordingFile = cjo.sourceVideo; _destinationPath = cjo.workingPath; _requestedAudioLanguage = cjo.audioLanguage; _tivoMAKKey = cjo.tivoMAKKey; if (Util.FilePaths.CleanExt(_RecordingFile) == ".ts") // Handle TS files difference since they will have the same namess _RemuxedFile = Path.Combine(_destinationPath, Path.GetFileNameWithoutExtension(_RecordingFile) + "-REMUXED.ts"); else _RemuxedFile = Path.Combine(_destinationPath, Path.GetFileNameWithoutExtension(_RecordingFile) + ".ts"); // Read various profile parameters Ini configProfileIni = new Ini(GlobalDefs.ProfileFile); _useRemuxsupp = configProfileIni.ReadBoolean(cjo.profile, "UseWTVRemuxsupp", false); // Some videos fail with FFMPEG remuxing and Mencoder encoding (use remuxsupp for remuxing there) _jobLog.WriteEntry(this, "Force Remuxsupp (UseWTVRemuxsupp) : " + _useRemuxsupp.ToString(), Log.LogEntryType.Debug); _forceWTVStreamsRemuxing = configProfileIni.ReadBoolean(cjo.profile, "ForceWTVStreamsRemuxing", false); // Use Streams remuxing for DVRMS and WTV files _jobLog.WriteEntry(this, "Force Streams Remuxing (ForceWTVStreamsRemuxing) : " + _forceWTVStreamsRemuxing.ToString(), Log.LogEntryType.Debug); _allowH264CopyRemuxing = configProfileIni.ReadBoolean(cjo.profile, "AllowH264CopyRemuxing", true); // Allow H.264 files to be remuxed into TS without recoding to MPEG2 _jobLog.WriteEntry(this, "Allow H264 Copy Remuxing (AllowH264CopyRemuxing) (default: true) : " + _allowH264CopyRemuxing.ToString(), Log.LogEntryType.Debug); _allowAllCopyRemuxing = configProfileIni.ReadBoolean(cjo.profile, "AllowAllCopyRemuxing", false); // Allow any video codec to be remuxed into TS without recoding to MPEG2 _jobLog.WriteEntry(this, "Allow All Video codec formats Copy Remuxing (AllowAllCopyRemuxing) (default: false) : " + _allowAllCopyRemuxing.ToString(), Log.LogEntryType.Debug); // Get the media info for the recording file once for the entire operation to reuse _jobLog.WriteEntry(this, "Reading Recording file " + _RecordingFile + " media information", Log.LogEntryType.Debug); _RecordingFileMediaInfo = new FFmpegMediaInfo(_RecordingFile, _jobStatus, _jobLog); // Check for donator version of Comskip Comskip checkComskip = new Comskip(MCEBuddyConf.GlobalMCEConfig.GeneralOptions.comskipPath, _jobLog); // Check if we are using a mpeg4 video and allowing h264 video codec for commercial skipping purposes if (_allowH264CopyRemuxing) { if (_mpeg4Codecs.Any(s => s.Contains(_RecordingFileMediaInfo.MediaInfo.VideoInfo.VideoCodec.ToLower()))) { if (cjo.commercialRemoval == CommercialRemovalOptions.Comskip) { if (checkComskip.IsDonator) _jobLog.WriteEntry(this, "AllowH264CopyRemuxing will run fast for commercial detection, using donator version of Comskip", Log.LogEntryType.Information); else _jobLog.WriteEntry(this, "AllowH264CopyRemuxing is SLOW with the bundled Comskip. Use ShowAnalyzer or Comskip Donator version (http://www.comskip.org) to speed up commercial detection. Codec detected -> " + _RecordingFileMediaInfo.MediaInfo.VideoInfo.VideoCodec, Log.LogEntryType.Warning); } } } // Check if we are using an unsupported codec and copying to TS format if (_allowAllCopyRemuxing) if (!_supportedCodecs.Any(s => s.Contains(_RecordingFileMediaInfo.MediaInfo.VideoInfo.VideoCodec.ToLower()))) // Check if we using any of the default supported codecs _jobLog.WriteEntry(this, "AllowAllCopyRemuxing is enabled and an unsupported codec in the source video is detected. Some underlying programs may not work with this codec. Codec detected -> " + _RecordingFileMediaInfo.MediaInfo.VideoInfo.VideoCodec, Log.LogEntryType.Warning); }
/// <summary> /// Main conversion routine /// </summary> public void Convert() { _jobStatus.ErrorMsg = ""; //start clean _jobStatus.SuccessfulConversion = false; //no successful conversion yet _jobStatus.Completed = false; Log jobLog = CreateLog(_conversionOptions.sourceVideo); //Debug, dump all the conversion parameter before starting to help with debugging jobLog.WriteEntry("Starting conversion - DEBUG MESSAGES", Log.LogEntryType.Debug); jobLog.WriteEntry("Windows OS Version -> " + Util.OSVersion.TrueOSVersion.ToString() + " (" + Util.OSVersion.GetOSVersion().ToString() + ", " + Util.OSVersion.GetOSProductType() + ")", Log.LogEntryType.Information); jobLog.WriteEntry("Windows Platform -> " + (Environment.Is64BitOperatingSystem ? "64 Bit" : "32 Bit"), Log.LogEntryType.Information); jobLog.WriteEntry("MCEBuddy Build Platform -> " + ((IntPtr.Size == 4) ? "32 Bit" : "64 Bit"), Log.LogEntryType.Information); string currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); jobLog.WriteEntry("MCEBuddy Build Version : " + currentVersion, Log.LogEntryType.Information); jobLog.WriteEntry("MCEBuddy Build Date : " + File.GetLastWriteTime(System.Reflection.Assembly.GetExecutingAssembly().Location).ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Information); jobLog.WriteEntry("MCEBuddy Running as Service : " + GlobalDefs.IsEngineRunningAsService.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Information); jobLog.WriteEntry(MCEBuddyConf.GlobalMCEConfig.GeneralOptions.ToString(), Log.LogEntryType.Debug); jobLog.WriteEntry(_conversionOptions.ToString(), Log.LogEntryType.Debug); jobLog.WriteEntry("Max Concurrent Jobs -> " + _maxConcurrentJobs.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); jobLog.WriteEntry("Commercial Skip Cut (profile + task) -> " + _commercialSkipCut.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); jobLog.WriteEntry("Auto DeInterlacing (profile + task) -> " + _autoDeinterlace.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); jobLog.WriteEntry("Pre-Conversion Commercial Remover -> " + _preConversionCommercialRemover.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); jobLog.WriteEntry("Copy LOG File -> " + _copyLOGFile.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); jobLog.WriteEntry("Free Space Check -> " + _spaceCheck.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); jobLog.WriteEntry("Subtitle Cut Segment Incremental Offset -> " + _subtitleSegmentOffset.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); jobLog.WriteEntry("Locale Language -> " + Localise.ThreeLetterISO().ToUpper(), Log.LogEntryType.Debug); try { RegistryKey installed_versions = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP"); string[] version_names = installed_versions.GetSubKeyNames(); //version names start with 'v', eg, 'v3.5' which needs to be trimmed off before conversion string Framework = version_names[version_names.Length - 1].Remove(0, 1); string SP = (string)installed_versions.OpenSubKey(version_names[version_names.Length - 1]).GetValue("SP", 0).ToString(); jobLog.WriteEntry(".NET Framework Version -> " + Framework + ", Service Pack -> " + SP, Log.LogEntryType.Debug); } catch { jobLog.WriteEntry("Cannot get .NET Framework version from Registry", Log.LogEntryType.Warning); } try // the entire conversion process, incase of an abort signal { jobLog.WriteEntry(this, "Current System language is " + CultureInfo.CurrentCulture.DisplayName + " (" + CultureInfo.CurrentCulture.ThreeLetterISOLanguageName.ToString() + ")", Log.LogEntryType.Information); jobLog.WriteEntry(this, "Converting file -> " + _conversionOptions.sourceVideo, Log.LogEntryType.Information); // Create and clear the contents of the working folder Util.FilePaths.CreateDir(_conversionOptions.workingPath); Util.FileIO.ClearFolder(_conversionOptions.workingPath); if (!Directory.Exists(_conversionOptions.workingPath)) // Check if it exists, possible the folder isn't accesible or user put a bogus value here { jobLog.WriteEntry(this, "Unable to create temp directory " + _conversionOptions.workingPath + ". Conversion will fail.", Log.LogEntryType.Error); _jobStatus.ErrorMsg = "Unable to create temp directory"; Cleanup(jobLog); return; // serious problem } // Print the vesion of FFMPEG or FFProbe being used (since FFProbe runs in silent mode) and dump file information if (!_jobStatus.Cancelled) FFmpegMediaInfo.DumpFileInformation(OriginalFileName, _jobStatus, jobLog); // Dump the media information for debugging purposes // Run the pre metadata custom command from the user if configured if (!_jobStatus.Cancelled) { LogStatus(Localise.GetPhrase("Running custom commands"), jobLog); string tmpSrt, tmpEdl; tmpEdl = (File.Exists(Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".edl") ? Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".edl" : ""); tmpSrt = (File.Exists(Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".srt") ? Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".srt" : ""); CustomCommand customCommand = new CustomCommand("PreMetaCustomCommand", _conversionOptions.profile, _conversionOptions.taskName, _conversionOptions.workingPath, "", _convertedFile, OriginalFileName, _remuxedVideo, tmpEdl, tmpSrt, null, _jobStatus, jobLog); // EDl and SRT file may lie with the source video if (!customCommand.Run()) { jobLog.WriteEntry(this, ("Pre metadata Custom command failed to run, critical failure"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = ("Pre metadata Custom command failed to run, critical failure"); Cleanup(jobLog); return; // serious problem } // Check if the source file has been tampered with if (!File.Exists(_conversionOptions.sourceVideo)) { jobLog.WriteEntry(this, ("PreMeta Source file has been renamed or deleted by custom command") + " -> " + _conversionOptions.sourceVideo, Log.LogEntryType.Error); _jobStatus.ErrorMsg = ("PreMeta Source file has been renamed or deleted by custom command"); Cleanup(jobLog); return; // serious problem } else jobLog.WriteEntry(this, ("Finished pre metadata custom command, source file size [KB]") + " " + (Util.FileIO.FileSize(_conversionOptions.sourceVideo) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } // Get and process Metadata if (!_jobStatus.Cancelled) { // Download/extract the video metadata first LogStatus(Localise.GetPhrase("Getting show information and banner from Internet sources"), jobLog); _metaData = new VideoMetaData(_conversionOptions, _jobStatus, jobLog); _metaData.Extract(); // Check metadata for Copy Protection if (_metaData.MetaData.CopyProtected && !_conversionOptions.renameOnly) // If it's copy protected fail the conversion here with the right message and status, if we are renaming only then skip the check { if (_conversionOptions.ignoreCopyProtection) // If we are asked to ignore copy protection, we will just continue jobLog.WriteEntry(this, ("ERROR: VIDEO IS COPYPROTECTED. CONVERSION WILL FAIL, BUT TRYING ANYWAYS."), Log.LogEntryType.Warning); else { jobLog.WriteEntry(this, ("ERROR: VIDEO IS COPYPROTECTED. CONVERSION WILL FAIL, STOPPING CONVERSION"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = ("ERROR: VIDEO IS COPYPROTECTED. CONVERSION WILL FAIL"); Cleanup(jobLog); return; } } } // DO THIS HERE and NOT in in the queue manager because you need ALL METADATA before you can Generate Filename to compare with history file. // By now you have downloaded additional metadata which can be used to generate the filename // Now that we have metadata, figure out if we need to process the file // Check if the destination file exists (after calculating destination name and path) and check if are required to reprocess files that exist if (!_jobStatus.Cancelled) { // Check if we need to check for destination file reprocessing if (_conversionOptions.skipReprocessing) { jobLog.WriteEntry(this, "Checking for destination file skip reprocessing", Log.LogEntryType.Information); string destinationFile = GetDestinationFilename(_conversionOptions, _metaData, OriginalFileName, jobLog); // Check if we need to skip reprocessing this file bool skipReprocessing = false; if (File.Exists(destinationFile)) // Check if the destination file exists { jobLog.WriteEntry(this, "Destination file " + destinationFile + " EXISTS, skipping conversion - SUCCESSFUL processing", Log.LogEntryType.Warning); skipReprocessing = true; } if (!skipReprocessing && _conversionOptions.checkReprocessingHistory) // If we aren't already skipping reprocessig (save disk IO/CPU), do we need to check the history file for reprocessing? { if (QueueManager.DoesConvertedFileExistCheckHistory(destinationFile)) { jobLog.WriteEntry(this, "Destination file " + destinationFile + " found converted in HISTORY, skipping re-conversion - SUCCESSFUL processing", Log.LogEntryType.Warning); skipReprocessing = true; } } // Check if the destination file exists if (skipReprocessing) { // SPECIAL CONDITION, THIS IS A SUCCESSFUL EXIT - Let exit here since we don't need to process it _convertedFile = destinationFile; // Update the link to the converted file as it will be required for post processing _jobStatus.ErrorMsg = ""; // all done, no error here, skipping processing but successful _jobStatus.SuccessfulConversion = _jobStatus.SuccessfulSkipConversion = true; // now the original file can be deleted if required Cleanup(jobLog); return; } else { jobLog.WriteEntry(this, "Destination file " + destinationFile + " does not exist, continuing with conversion", Log.LogEntryType.Information); } } } // Run the pre remuxing custom command from the user if configured if (!_jobStatus.Cancelled) { LogStatus(Localise.GetPhrase("Running custom commands"), jobLog); string tmpSrt, tmpEdl; tmpEdl = (File.Exists(Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".edl") ? Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".edl" : ""); tmpSrt = (File.Exists(Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".srt") ? Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".srt" : ""); CustomCommand customCommand = new CustomCommand("PreCustomCommand", _conversionOptions.profile, _conversionOptions.taskName, _conversionOptions.workingPath, Path.GetDirectoryName(GetDestinationFilename(_conversionOptions, _metaData, OriginalFileName, jobLog)), _convertedFile, OriginalFileName, _remuxedVideo, tmpEdl, tmpSrt, _metaData.MetaData, _jobStatus, jobLog); // EDL and SRT file may lie with the source video if (!customCommand.Run()) { jobLog.WriteEntry(this, ("Pre remuxing Custom command failed to run, critical failure"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = ("Pre remuxing Custom command failed to run, critical failure"); Cleanup(jobLog); return; // serious problem } // Check if the source file has been tampered with if (!File.Exists(WorkingVideo)) { jobLog.WriteEntry(this, ("Pre remuxing Source file has been renamed or deleted by custom command") + " -> " + _conversionOptions.sourceVideo, Log.LogEntryType.Error); _jobStatus.ErrorMsg = ("Pre rexmuing Source file has been renamed or deleted by custom command"); Cleanup(jobLog); return; // serious problem } else jobLog.WriteEntry(this, ("Finished pre remuxing custom command, source file size [KB]") + " " + (Util.FileIO.FileSize(_conversionOptions.sourceVideo) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } //Check for available disk space LogStatus(Localise.GetPhrase("Checking for disk space"), jobLog); if (!SufficientSpace(jobLog)) { jobLog.WriteEntry(this, ("Insufficient disk space"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = "Insufficient disk space"; Cleanup(jobLog); // close the joblog and clean up return; } // Check for small test files);) if (Util.FileIO.FileSize(_conversionOptions.sourceVideo) < 100000000) { jobLog.WriteEntry(this, ("This is a small video file less than 100MB. Some actions such as advertisement removal will not occur"), Log.LogEntryType.Warning); _conversionOptions.commercialRemoval = CommercialRemovalOptions.None; } // Copy support files over if (!_jobStatus.Cancelled) { // If the SRT File exists, copy it jobLog.WriteEntry(this, ("Checking for SRT File"), Log.LogEntryType.Information); { string srtFile; if (Text.ContainsUnicode(OriginalFileName) && !_conversionOptions.renameOnly) // Unicode test, not all underlying apps and functions currently support UNICODE filenames, so use a ASCII name for now srtFile = GlobalDefs.UNICODE_TEMPNAME + ".srt"; else srtFile = Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".srt"; // SRT file along with original source video if (File.Exists(srtFile) && (Util.FileIO.FileSize(srtFile) > 0)) // Exist and non empty { string srtDest = Path.Combine(_conversionOptions.workingPath, Path.GetFileName(srtFile)); try { File.Copy(srtFile, srtDest); _deleteSRTFile = true; _srtFile = srtDest; jobLog.WriteEntry(this, ("Found existing SRT file and saved it"), Log.LogEntryType.Information); } catch (Exception e) { jobLog.WriteEntry(this, ("Found existing SRT file but unable to save it") + "\r\nError : " + e.ToString(), Log.LogEntryType.Warning); } } } //If the EDL/EDLP file exists, copy it to working directory jobLog.WriteEntry(this, Localise.GetPhrase("Checking for EDL File"), Log.LogEntryType.Information); { string edlFile; if (Text.ContainsUnicode(OriginalFileName) && !_conversionOptions.renameOnly) // Unicode test, not all underlying apps and functions currently support UNICODE filenames, so use a ASCII name for now edlFile = GlobalDefs.UNICODE_TEMPNAME + ".edl"; else edlFile = Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".edl"; if (File.Exists(edlFile)) // EDL File { string edlDest = Path.Combine(_conversionOptions.workingPath, Path.GetFileName(edlFile)); try { File.Copy(edlFile, edlDest); _saveEDLFile = true; jobLog.WriteEntry(this, ("Found existing EDL file and saved it"), Log.LogEntryType.Information); } catch (Exception e) { jobLog.WriteEntry(this, ("Found existing EDL file but unable to save it") + "\r\nError : " + e.ToString(), Log.LogEntryType.Warning); } } } // Check for EDLP file jobLog.WriteEntry(this, ("Checking for EDLP File"), Log.LogEntryType.Information); { string edlpFile; if (Text.ContainsUnicode(OriginalFileName) && !_conversionOptions.renameOnly) // Unicode test, not all underlying apps and functions currently support UNICODE filenames, so use a ASCII name for now edlpFile = GlobalDefs.UNICODE_TEMPNAME + ".edlp"; else edlpFile = Util.FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".edlp"; if (File.Exists(edlpFile)) // EDLP file { string edlpDest = Path.Combine(_conversionOptions.workingPath, Path.GetFileName(edlpFile)); try { File.Copy(edlpFile, edlpDest); _saveEDLFile = true; jobLog.WriteEntry(this, ("Found existing EDLP file and saved it"), Log.LogEntryType.Information); } catch (Exception e) { jobLog.WriteEntry(this, ("Found existing EDLP file but unable to save it") + "\r\nError : " + e.ToString(), Log.LogEntryType.Warning); } } } } if (!_jobStatus.Cancelled) { // DO this last before starting the analysis/conversion process after copying SRT, EDL, NFO etc files // If Commerical removal is set for TS files copy them to the temp directory (During Commercial removal, files (except TS) are remuxed later into their own temp working directories) // We need exclusive access to each copy of the file in their respective temp working directories otherwise commercial skipping gets messed up when we have multiple simultaneous tasks for the same file (they all share/overwrite the same EDL file) which casuses a failure // Check if the TS file (or any file if we are skipping remuxing) has any Zero Channel Audio tracks, in which case it needs to be remuxed to remove/compensate for them, remuxing will overwrite the original file if it's a TS file, so make a local copy string sourceExt = FilePaths.CleanExt(_conversionOptions.sourceVideo); if ((sourceExt == ".ts") || _conversionOptions.skipRemuxing) { FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(_conversionOptions.sourceVideo, _jobStatus, jobLog); if (ffmpegStreamInfo.Success && !ffmpegStreamInfo.ParseError) { if ((ffmpegStreamInfo.ZeroChannelAudioTrackCount > 0)) // TODO: Do we need to check for Imparied Audio tracks here when not Remuxing? (imparied audio tracks are sometimes empty - can they cause the encoders to fail?) _zeroChannelAudio = true; } else { jobLog.WriteEntry(this, ("Unable to read FFMPEG MediaInfo to verify remuxsupp audio streams"), Log.LogEntryType.Warning); } } // Make a local copy only if we are renaming, has zero channel audio or skipping remuxing for non-TS or is a TS file with some conditions met (like commercial removal, etc where the original file may be modified or locked and accessed simultaneously) // If the sourcefile has a Unicode name, always copy it since underlying programs cannot handle unicode names and then rename the local copy if ((Text.ContainsUnicode(_conversionOptions.sourceVideo) && !_conversionOptions.renameOnly) || (!_conversionOptions.skipCopyBackup && (_zeroChannelAudio || _conversionOptions.renameOnly || (((sourceExt == ".ts") || ((sourceExt != ".ts") && _conversionOptions.skipRemuxing)) && (_conversionOptions.commercialRemoval != CommercialRemovalOptions.None))))) { if (_conversionOptions.skipCopyBackup) jobLog.WriteEntry(this, "DANGER: Skip copying original files (skip original backup) is enabled. This could lead to conversion failures or unpredictable outcomes. Please DISABLE this option unless absolutely necessary.", Log.LogEntryType.Warning); string newSource; if (Text.ContainsUnicode(_conversionOptions.sourceVideo) && !_conversionOptions.renameOnly) // Unicode test, not all underlying apps and functions currently support UNICODE filenames, so use a ASCII name for now newSource = Path.Combine(_conversionOptions.workingPath, GlobalDefs.UNICODE_TEMPNAME + Util.FilePaths.CleanExt(_conversionOptions.sourceVideo)); else newSource = Path.Combine(_conversionOptions.workingPath, Path.GetFileName(_conversionOptions.sourceVideo)); LogStatus(Localise.GetPhrase("Copying source file to working directory"), jobLog); try { jobLog.WriteEntry(this, ("Copying source video to working directory") + " Source:" + _conversionOptions.sourceVideo + ", Target:" + newSource, Log.LogEntryType.Information); File.Copy(_conversionOptions.sourceVideo, newSource, true); // Copy the file, overwrite if required _conversionOptions.sourceVideo = newSource; // replace the source file that we will work on going forward on success } catch (Exception e) { jobLog.WriteEntry(this, ("Unable to copy source video to working directory") + " Source:" + _conversionOptions.sourceVideo + ", Target:" + newSource + " Error : " + e.ToString(), Log.LogEntryType.Error); _jobStatus.ErrorMsg = ("Unable to copy source video to working directory"); Cleanup(jobLog); return; } } } // If we are ONLY renaming NOT using free Comskip AND WITHOUT extracting subtitles, we skip the video processing, removal etc (i.e. no Remuxing) // If detecting commercials while only renaming, Showanalyzer and donator comskip support all formats, so no remuxing required if (!(_conversionOptions.renameOnly && !(_conversionOptions.commercialRemoval == CommercialRemovalOptions.Comskip && !(new Comskip(MCEBuddyConf.GlobalMCEConfig.GeneralOptions.comskipPath, jobLog).IsDonator)) && String.IsNullOrEmpty(_conversionOptions.extractCC))) { if (!_jobStatus.Cancelled) { if (_conversionOptions.skipRemuxing) jobLog.WriteEntry(this, "SKIPPING REMUXING, this may lead to conversion failure since all underlying apps may not support all file formats.\r\nWTV commercial detection is only supported by donator version of Comskip (http://www.kaashoek.com/comskip/).", Log.LogEntryType.Warning); // Remux media center recordings or any video if commercials are being removed to TS (except TS) and we are not asked to skip remuxing // Unless TS tracks have zero channel audio in them, then remux them to remove the zero audio channel tracks, otherwise future functions fails (like trim) - NOTE while 0 TS channel audio is remuxed, it ends up replacing the original file string sourceExt = FilePaths.CleanExt(_conversionOptions.sourceVideo); if (((sourceExt != ".ts") && !_conversionOptions.skipRemuxing) || _zeroChannelAudio) { LogStatus(Localise.GetPhrase("Remuxing recording"), jobLog); RemuxMediaCenter.RemuxMCERecording remux = new RemuxMCERecording(_conversionOptions, _jobStatus, jobLog); bool res = remux.Remux(); jobLog.WriteEntry(this, ("Remuxing: Percentage Complete") + " " + _jobStatus.PercentageComplete.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); if (!res) { // Remux failed _jobStatus.ErrorMsg = ("Remux failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } WorkingVideo = remux.RemuxedFile; _workingVideoRecoded = remux.VideoStreamRecoded; // Was the original video recoded? We lost CC data in remued file _initialEDLSkipSeconds = (_saveEDLFile ? remux.SkipInitialSeconds : 0); // If we are using a saved EDL File, we need to compensate for any seconds that were trimmed while remuxing to compensate for EDL later jobLog.WriteEntry(this, "Was remuxed video recoded : " + _workingVideoRecoded.ToString(System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); jobLog.WriteEntry(this, "Remuxed video file : " + WorkingVideo, Log.LogEntryType.Debug); jobLog.WriteEntry(this, Localise.GetPhrase("Finished Remuxing, file size [KB]") + " " + (Util.FileIO.FileSize(WorkingVideo) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } } } // If we are ONLY renaming, we skip the video processing, trimming, removal etc if (!_conversionOptions.renameOnly) { // If we are trimming the video, try to do it here, if it fails then try to do it during the conversion if (!_jobStatus.Cancelled) { LogStatus(Localise.GetPhrase("Trimming video recording"), jobLog); TrimVideo trimVideo = new TrimVideo(_conversionOptions.profile, _jobStatus, jobLog); if (!trimVideo.Trim(WorkingVideo, _conversionOptions.workingPath, _conversionOptions.startTrim, _conversionOptions.endTrim)) { // Trimming failed - just log the error here, we'll try to pick it up during conversion jobLog.WriteEntry(this, "Trimming failed, will retry during conversion", Log.LogEntryType.Warning); } else { jobLog.WriteEntry(this, "Trimming successful, setting trim parameters to 0 to avoid retrimming", Log.LogEntryType.Debug); _conversionOptions.startTrim = _conversionOptions.endTrim = 0; // Indicate that trimming is complete so we don't redo it later WorkingVideo = trimVideo.TrimmedVideo; // Update the working video path with the new trimmed video } } } // Do commercial detection if there is not metadata copy protection since this can be run for rename only option also if (!_jobStatus.Cancelled && (!_metaData.MetaData.CopyProtected || _conversionOptions.ignoreCopyProtection)) { // Get the Video duration float duration = VideoParams.VideoDuration(WorkingVideo); if (duration <= 0) { FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(WorkingVideo, _jobStatus, jobLog); if (!ffmpegStreamInfo.Success || ffmpegStreamInfo.ParseError) { // Getting Video Duration failed _jobStatus.ErrorMsg = Localise.GetPhrase("Unable to get Video Duration"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } duration = ffmpegStreamInfo.MediaInfo.VideoInfo.Duration; } // If we are using ShowAnalyzer, do it here jobLog.WriteEntry(this, "Checking for ShowAnalyzer", Log.LogEntryType.Information); if (_conversionOptions.commercialRemoval == CommercialRemovalOptions.ShowAnalyzer) { LogStatus(Localise.GetPhrase("ShowAnalyzer advertisement scan"), jobLog); _commercialScan = new Scanner(_conversionOptions, WorkingVideo, true, duration, _jobStatus, jobLog); if (!_commercialScan.Scan()) { _jobStatus.ErrorMsg = Localise.GetPhrase("ShowAnalyzer failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } } // If we are using comskip, do it here jobLog.WriteEntry(this, "Checking for Comskip", Log.LogEntryType.Information); if (_conversionOptions.commercialRemoval == CommercialRemovalOptions.Comskip) { LogStatus(Localise.GetPhrase("Comskip advertisement scan"), jobLog); _commercialScan = new Scanner(_conversionOptions, WorkingVideo, false, duration, _jobStatus, jobLog); if (!_commercialScan.Scan()) { _jobStatus.ErrorMsg = "Comskip failed"; jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); Cleanup(jobLog); return; } } // We extract the closed captions after the commerical scanning, trimming and video info is complete // We check if the SRT file alread exists and needs to be adjusted for EDL commercials // If not we check if commerical removal is enabled, if so we need to create a temp file which we will cut to compensate for EDL and THEN extract the CC (this helps keep the audio in sync with the CC due to cutting on non KeyFrames issues) // If no commercial removal we just extract the CC from the remuxed file (TS) if (!_jobStatus.Cancelled) { // CC Extractor only works on TS files or if WTV/DVRMS, we can extract the streams and remux into TS file if (!String.IsNullOrEmpty(_conversionOptions.extractCC) && ((FilePaths.CleanExt(WorkingVideo) == ".ts") || (_conversionOptions.skipRemuxing && ((FilePaths.CleanExt(WorkingVideo) == ".wtv") || (FilePaths.CleanExt(WorkingVideo) == ".dvr-ms"))))) { LogStatus(Localise.GetPhrase("Extracting closed captions"), jobLog); // Setup closed captions for extraction _cc = new ClosedCaptions(_conversionOptions.profile, _jobStatus, jobLog); if (!(String.IsNullOrWhiteSpace(_srtFile))) // We already have a SRT file to work with { jobLog.WriteEntry(this, "Found saved SRT file -> " + _srtFile, Log.LogEntryType.Debug); // We need to validate the clean up the SRT file LogStatus(Localise.GetPhrase("Validating closed captions"), jobLog); if (!_cc.SRTValidateAndClean(_srtFile)) { // Validating CC failed _jobStatus.ErrorMsg = Localise.GetPhrase("Validating closed captions failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } if ((_commercialScan != null) && (!_commercialSkipCut)) // Incase we asked not to cut the video, just create the EDL file, let us not cut the SRT files also { if (_commercialScan.CommercialsFound) // We just adjust the SRT file with the EDL file { LogStatus(Localise.GetPhrase("Trimming closed captions"), jobLog); if (!_cc.EDLTrim(_commercialScan.EDLFile, _srtFile, _conversionOptions.ccOffset, _subtitleSegmentOffset)) { // Trimming CC failed _jobStatus.ErrorMsg = Localise.GetPhrase("Trimming closed captions failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } } } } else // We need to create a SRT file { string tempCCFile = ""; tempCCFile = WorkingVideo; // If there are no commercials to remove, we can work directly on the remuxed file // If we are skipping remuxing for wtv/dvrms files then we need to extract the streams data // If the original video was recoded we have lost the CC data, try to extract the original video stream if ((_workingVideoRecoded || _conversionOptions.skipRemuxing) && ((Util.FilePaths.CleanExt(_conversionOptions.sourceVideo) == ".wtv") || (Util.FilePaths.CleanExt(_conversionOptions.sourceVideo) == ".dvr-ms"))) { ExtractWithGraph extractVideo = null; try { LogStatus(Localise.GetPhrase("Extracting streams"), jobLog); jobLog.WriteEntry("Creating graph to extract streams", Log.LogEntryType.Debug); extractVideo = new ExtractWithGraph(_conversionOptions.sourceVideo, _conversionOptions.workingPath, ExtractWithGraph.ExtractMediaType.Video, _jobStatus, jobLog); // We only need video stream to extract CCData using ccExtractor jobLog.WriteEntry("Building graph", Log.LogEntryType.Debug); extractVideo.BuildGraph(); jobLog.WriteEntry("Extracting streams", Log.LogEntryType.Debug); extractVideo.RunGraph(); if ((!extractVideo.SuccessfulExtraction) || (String.IsNullOrWhiteSpace(extractVideo.VideoPart))) // Successful extraction and atleast one video stream { jobLog.WriteEntry(this, "Error extracting streams using Graph", Log.LogEntryType.Warning); throw new Exception("Graph extraction unsuccessful"); // It'll clean up automatically } // Dispose the Graph object try { jobLog.WriteEntry("Disposing Graph....", Log.LogEntryType.Debug); extractVideo.Dispose(); } catch (Exception e) { jobLog.WriteEntry(this, "Error disposing graph.\r\nError : " + e.ToString(), Log.LogEntryType.Warning); // We can still continue } // We need to put the stream parts back together in a TS format so that ccExtractor can work on it // temp file name to be used by ccExtractor string ccStreamsFile = Path.Combine(_conversionOptions.workingPath, Path.GetFileNameWithoutExtension(WorkingVideo) + "-ccExtract.ts"); // Put the streams back together in TS format jobLog.WriteEntry(this, Localise.GetPhrase("Muxing streams with TsMuxer"), Log.LogEntryType.Information); LogStatus(Localise.GetPhrase("Muxing streams"), jobLog); if (!RemuxMCERecording.RemuxRawTSMuxer(_conversionOptions.sourceVideo, ccStreamsFile, extractVideo, _jobStatus, jobLog)) { jobLog.WriteEntry(this, "TsMuxer Streams Muxing failed", Log.LogEntryType.Error); // Otherwise try tsMuxer jobLog.WriteEntry(this, Localise.GetPhrase("Fallback muxing streams with FFMpegParts"), Log.LogEntryType.Information); LogStatus(Localise.GetPhrase("Fallback muxing streams"), jobLog); if (!RemuxMCERecording.RemuxRawPartsFFmpeg(_conversionOptions.sourceVideo, ccStreamsFile, extractVideo, _jobStatus, jobLog)) { extractVideo.DeleteParts(); // Clean up FileIO.TryFileDelete(ccStreamsFile); // Clean up jobLog.WriteEntry(this, "Fallback Remux Streams FFMpegParts failed", Log.LogEntryType.Warning); } } extractVideo.DeleteParts(); // Clean up, we have the file now // All good we should have a video file now if (FileIO.FileSize(ccStreamsFile) > 0) // We have a good file tempCCFile = ccStreamsFile; // This is what we will work with now else { FileIO.TryFileDelete(ccStreamsFile); // Clean up jobLog.WriteEntry("Extracted video stream unsuccessful, continuing without closed captions", Log.LogEntryType.Warning); } } catch (Exception e) { try { jobLog.WriteEntry(this, ("Unable to extract video using DirectShow Graph to get closed captions.\r\nError : " + e.ToString()), Log.LogEntryType.Warning); if (extractVideo != null) { extractVideo.DeleteParts(); extractVideo.Dispose(); } } catch (Exception e1) { jobLog.WriteEntry(this, "Error disposing directshow graph.\r\nError : " + e1.ToString(), Log.LogEntryType.Warning); } // We couldn't extract the video - continue with Working Video - we can still use the remuxed TS file to extract CC possibly } } // DONT NEED TO ACTUALLY CUT THE VIDEO, RATHER JUST EXTRACT AND ADJUST, GIVES GREATER DEGREE OF CONTROL ON SYNC ISSUES WHEN THE ACTUAL VIDEO IS CUT /*if ((_commercialScan != null) && (!commercialSkipCut)) // If we are not asked to skip cutting commercials (we don't need to adjust CC), for commerical cutting we need to create a special EDL adjusted file before extracting of CC { jobLog.WriteEntry(this, "Checking if commercials were found", Log.LogEntryType.Information); if (_commercialScan.CommercialsFound) // If there are commericals we need to create a special EDL cut file to work with before extracting CC { // Copy the remuxed file to a temp file which we will then cut using the EDL file and then extract the closed captions // This helps keep the closed captions in sync with the cut video tempCCFile = Path.Combine(_conversionOptions.workingPath, Path.GetFileNameWithoutExtension(WorkingVideo) + "-CCTemp" + Path.GetExtension(SourceVideo)); try { // Create a temp file for CC EDL cutting and extracting jobLog.WriteEntry(this, "Creating temporary remuxed file for extracting CC and adjusting for commercials", Log.LogEntryType.Debug); File.Copy(SourceVideo, tempCCFile); } catch (Exception e) { // Creating temp CC file failed _jobStatus.ErrorMsg = Localise.GetPhrase("Creating temporary file for CC extracting file failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg + "\r\nError : " + e.ToString(), Log.LogEntryType.Error); Cleanup(jobLog); return; } // Now adjust the file for commercial removal jobLog.WriteEntry(this, "Removing commercials from temp file before extracting CC", Log.LogEntryType.Debug); Remover edlCCAdjust = new Remover(_conversionOptions.profile, tempCCFile, _commercialScan.EDLFile, ref _videoFile, _jobStatus, jobLog); // Pass the original or remuxed video here edlCCAdjust.StripCommercials(); _videoFile.AdsRemoved = false; // Reset it, since we didn't really remove the ad's, just on temp file for extracting CC's if (_jobStatus.PercentageComplete == 0) // for Commercial Stripping failure, this numbers is set to 0 { // Adjusting EDL CC failed _jobStatus.ErrorMsg = Localise.GetPhrase("Removing commercials from temp file for CC extracting failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } } }*/ // Trimming is already complete so we just need to extract _deleteSRTFile = true; // We are extracting a SRT file, delete it incase it's along with the original file LogStatus(Localise.GetPhrase("Extracting closed captions"), jobLog); if (!_cc.Extract(tempCCFile, _conversionOptions.workingPath, _conversionOptions.extractCC, 0, 0, _conversionOptions.ccOffset)) { // Extracting CC failed _jobStatus.ErrorMsg = Localise.GetPhrase("Extracting closed captions failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } // We need to validate the clean up the SRT file LogStatus(Localise.GetPhrase("Validating closed captions"), jobLog); if (!_cc.SRTValidateAndClean(_cc.SRTFile)) { // Validating CC failed _jobStatus.ErrorMsg = Localise.GetPhrase("Validating closed captions failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } if (!String.IsNullOrWhiteSpace(_cc.SRTFile)) // If we have a valid SRT file { if (String.Compare(tempCCFile, WorkingVideo, true) != 0) // If we created a temp file, lets get rid of it now Util.FileIO.TryFileDelete(tempCCFile); _srtFile = _cc.SRTFile; // Save the SRT file location jobLog.WriteEntry(this, "SRT file -> " + _srtFile, Log.LogEntryType.Debug); } } } } } // If we are ONLY renaming, we skip the video processing, trimming, removal etc if (!_conversionOptions.renameOnly) { // Get the video properties if (!_jobStatus.Cancelled) { // Create the Video File object and get the container + stream information LogStatus(Localise.GetPhrase("Analyzing video information"), jobLog); _videoFile = new VideoInfo(true, false, _conversionOptions.sourceVideo, _remuxedVideo, "", _conversionOptions.audioLanguage, _jobStatus, jobLog); // Skip cropping information for now, we'll get that later before conversion if (_videoFile.Error) { _jobStatus.ErrorMsg = "Analyzing video information failed"; jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); Cleanup(jobLog); return; } } // Run the pre commercial removal custom command from the user if configured if (!_jobStatus.Cancelled) { LogStatus(Localise.GetPhrase("Running custom commands"), jobLog); CustomCommand customCommand = new CustomCommand("PreCommercialRemovalCustomCommand", _conversionOptions.profile, _conversionOptions.taskName, _conversionOptions.workingPath, Path.GetDirectoryName(GetDestinationFilename(_conversionOptions, _metaData, OriginalFileName, jobLog)), _convertedFile, OriginalFileName, _remuxedVideo, (_commercialScan == null ? "" : _commercialScan.EDLFile), _srtFile, _metaData.MetaData, _jobStatus, jobLog); // EDL and SRT file may lie with the source video if (!customCommand.Run()) { jobLog.WriteEntry(this, Localise.GetPhrase("Pre commercial removal Custom command failed to run, critical failure"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = Localise.GetPhrase("Pre commercial removal Custom command failed to run, critical failure"); Cleanup(jobLog); return; // serious problem } // Check if the working file has been tampered with if (!File.Exists(WorkingVideo)) { jobLog.WriteEntry(this, Localise.GetPhrase("Pre commercial removal working file has been renamed or deleted by custom command") + " -> " + WorkingVideo, Log.LogEntryType.Error); _jobStatus.ErrorMsg = Localise.GetPhrase("Pre commercial removal working file has been renamed or deleted by custom command"); Cleanup(jobLog); return; // serious problem } else jobLog.WriteEntry(this, Localise.GetPhrase("Finished pre commercial removal custom command, source file size [KB]") + " " + (Util.FileIO.FileSize(_conversionOptions.sourceVideo) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } // Remove the commercials incase they aren't supported format for after conversion for commercial removal if (!_jobStatus.Cancelled) { if (_commercialScan != null) { // Check if the final conversion extension has a format that's supported by the commercial remover, else remove the commercials here itself during the TS file stage // Check if the profile dicates to remove commercials before the actual conversion if (_preConversionCommercialRemover || (!Remover.IsSupportedExtension(Transcode.Convert.GetConversionExtension(_conversionOptions), _conversionOptions.profile))) { jobLog.WriteEntry(this, "Final format is not a supported format for removing commercials, PRE-Removing commercials for Ext -> " + Transcode.Convert.GetConversionExtension(_conversionOptions), Log.LogEntryType.Information); jobLog.WriteEntry(this, "Checking if commercials were found", Log.LogEntryType.Information); if ((_commercialScan.CommercialsFound) && (!_videoFile.AdsRemoved)) //commercials might be stripped { if (!_commercialSkipCut) { LogStatus(Localise.GetPhrase("Removing commercials"), jobLog); _commercialRemover = new Remover(_conversionOptions.profile, WorkingVideo, _conversionOptions.workingPath, _commercialScan.EDLFile, _initialEDLSkipSeconds, _videoFile, _jobStatus, jobLog); _commercialRemover.StripCommercials(true); // we need to select the language while stripping the TS file else we lose the language information //We dont' check for % completion here since some files are very short and % isn't reliable if (_videoFile.AdsRemoved) // for Commercial Stripping success, this is true { WorkingVideo = _commercialRemover.CommercialFreeVideo; // Since we have a new commercial free video, this will be our new source (remuxed) video jobLog.WriteEntry(this, Localise.GetPhrase("Finished removing commercials, file size [KB]") + " " + (Util.FileIO.FileSize(WorkingVideo) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); // Cut the SRT file also if it exists if (!String.IsNullOrWhiteSpace(_srtFile)) { LogStatus(Localise.GetPhrase("Trimming closed captions"), jobLog); if (!_cc.EDLTrim(_commercialScan.EDLFile, _srtFile, 0, _subtitleSegmentOffset)) // Offset is already compensated for while extraction { // Trimming CC failed _jobStatus.ErrorMsg = Localise.GetPhrase("Trimming closed captions failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } } // After removing commercials it's possible that the Audio/Video streams properties have changed - this is due to MEncoder and cutting TS, it only keeps 1 audio stream, so rescan // Create the Video File object and get the container + stream information jobLog.WriteEntry(this, "ReAnalyzing video information post commercial removal before video conversion", Log.LogEntryType.Information); LogStatus(Localise.GetPhrase("Analyzing video information"), jobLog); // While updating we don't need to pass EDL file anymore since the ad's have been removed and no cropping information here _videoFile.UpdateVideoInfo(true, false, _conversionOptions.sourceVideo, _remuxedVideo, "", _conversionOptions.audioLanguage, _jobStatus, jobLog); if (_videoFile.Error) { _jobStatus.ErrorMsg = "Analyzing video information failed"; jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); Cleanup(jobLog); return; } } else jobLog.WriteEntry(this, "Not able to remove commercials, will try again after conversion using unsupported format cutter", Log.LogEntryType.Warning); } else jobLog.WriteEntry(this, Localise.GetPhrase("Skipping commercial cutting, preserving EDL file"), Log.LogEntryType.Information); } else jobLog.WriteEntry(this, Localise.GetPhrase("Commercials not found or cutting already completed"), Log.LogEntryType.Information); } } } // Get the updated video information if (!_jobStatus.Cancelled) { // Get information before/after removing commercials if required // After removing commercials it's possible that the Audio/Video streams properties have changed - this is due to MEncoder and cutting TS, it only keeps 1 audio stream, so rescan // Create the Video File object and get the container + stream information LogStatus(Localise.GetPhrase("Analyzing video information"), jobLog); // Get the updated video information, skip cropping for now (it will be handled during the conversion) _videoFile.UpdateVideoInfo(true, _autoDeinterlace, _conversionOptions.sourceVideo, _remuxedVideo, (_videoFile.AdsRemoved ? "" : (_commercialScan != null ? _commercialScan.EDLFile : "")), _conversionOptions.audioLanguage, _jobStatus, jobLog); // Check if ad's have not been removed, if we have scanned for commercial pass along the EDL file to speed up the crop detect if (_videoFile.Error) { _jobStatus.ErrorMsg = "Analyzing video information failed"; jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); Cleanup(jobLog); return; } } // Convert the video if (!_jobStatus.Cancelled) { // Convert the file Transcode.Convert convertFile = new Transcode.Convert(_jobStatus, jobLog); LogStatus(Localise.GetPhrase("Converting"), jobLog); bool res = convertFile.Run(_conversionOptions, _videoFile, _commercialScan, _srtFile); // if we're using MEncoder, then we will complete the commercial stripping here itself if (!res) { _jobStatus.ErrorMsg = Localise.GetPhrase("Conversion failed"); jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); // Conversion failed Cleanup(jobLog); return; } // If we burned the subtitles into the file, then delete the SRT file since it's no longer required if (convertFile.SubtitleBurned) { jobLog.WriteEntry(this, "Subtitles were burned into the video while converting, deleting the SRT file", Log.LogEntryType.Information); FileIO.TryFileDelete(_srtFile); // Delete the SRT _srtFile = ""; // Point to nothing } _convertedFile = convertFile.ConvertedFile; jobLog.WriteEntry(this, "Converted File : " + _convertedFile, Log.LogEntryType.Information); jobLog.WriteEntry(this, Localise.GetPhrase("Finished conversion, file size [KB]") + " " + (Util.FileIO.FileSize(_convertedFile) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } // Remove the commercials incase they weren't removed earlier if (!_jobStatus.Cancelled) { if (_commercialScan != null) { jobLog.WriteEntry(this, "Checking if commercials were found", Log.LogEntryType.Information); if ((_commercialScan.CommercialsFound) && (!_videoFile.AdsRemoved)) //commercials might be stripped during conversion or before conversion { if (!_commercialSkipCut) { LogStatus(Localise.GetPhrase("Removing commercials"), jobLog); _commercialRemover = new Remover(_conversionOptions.profile, _convertedFile, _conversionOptions.workingPath, _commercialScan.EDLFile, _initialEDLSkipSeconds, _videoFile, _jobStatus, jobLog); _commercialRemover.StripCommercials(); //We dont' check for % completion here since some files are very short and % isn't reliable if (!_videoFile.AdsRemoved) // Commercial Stripping failure { _jobStatus.ErrorMsg = Localise.GetPhrase("Removing commercials failed"); jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); Cleanup(jobLog); return; } _convertedFile = _commercialRemover.CommercialFreeVideo; jobLog.WriteEntry(this, Localise.GetPhrase("Finished removing commercials, file size [KB]") + " " + (Util.FileIO.FileSize(_convertedFile) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); // Cut the SRT file also if it exists (since commercials not were not cut earlier, the SRT wasn't cut earlier either if (!String.IsNullOrWhiteSpace(_srtFile)) { LogStatus(Localise.GetPhrase("Trimming closed captions"), jobLog); if (!_cc.EDLTrim(_commercialScan.EDLFile, _srtFile, 0, _subtitleSegmentOffset)) // Offset is already compensated for while extraction { // Trimming CC failed _jobStatus.ErrorMsg = Localise.GetPhrase("Trimming closed captions failed"); jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); Cleanup(jobLog); return; } } } else jobLog.WriteEntry(this, Localise.GetPhrase("Skipping commercial cutting, preserving EDL file"), Log.LogEntryType.Information); } else jobLog.WriteEntry(this, Localise.GetPhrase("Commercials not found or cutting already completed"), Log.LogEntryType.Information); } } // Add the subtitles and chapters to the container if asked if (!_jobStatus.Cancelled) { if (_conversionOptions.embedSubtitlesChapters) { string chapFile = ""; // Nero Chapter file string xmlChapFile = ""; // iTunes Chapter file // Convert the EDL file to Chapters (EDLToChapter will adjust chapters based on whether the video is cut with the EDL file or not, i.e. CommercialSkipCut) if (_commercialScan != null) { EDL edl = new EDL(_conversionOptions.profile, _convertedFile, _videoFile.Duration, _commercialScan.EDLFile, _initialEDLSkipSeconds, _jobStatus, jobLog); if (edl.ConvertEDLToChapters(!_commercialSkipCut)) { chapFile = edl.CHAPFile; // Get the Nero chapter file xmlChapFile = edl.XMLCHAPFile; // Get the iTunes chapter file } } LogStatus(Localise.GetPhrase("Adding subtitles and chapters to file"), jobLog); if (!_metaData.AddSubtitlesAndChaptersToFile(_srtFile, chapFile, xmlChapFile, _convertedFile)) { _jobStatus.ErrorMsg = Localise.GetPhrase("Adding subtitles and chapters failed"); jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); Cleanup(jobLog); return; } jobLog.WriteEntry(this, Localise.GetPhrase("Finished adding subtitles and chapters to file, file size [KB]") + " " + (Util.FileIO.FileSize(_convertedFile) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } } // This point onward there is not ETA or % so set ETA to working _jobStatus.ETA = "Working..."; // Write the meta data if (!_jobStatus.Cancelled) { if (_conversionOptions.writeMetadata) { LogStatus(Localise.GetPhrase("Writing show information"), jobLog); _metaData.WriteTags(_convertedFile); // we can ignore failure of writing meta data, not critical jobLog.WriteEntry(this, Localise.GetPhrase("Finished writing tags, file size [KB]") + " " + (Util.FileIO.FileSize(_convertedFile) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } } } else // If we are ONLY RENAMING, then we are working direcly on the original file (ignore the remuxed video which was used to extract EDL, XML, NFO and SRT files) { WorkingVideo = ""; // Disregard the remuxed file completely, we are working on the original video // Get the video properties if (!_jobStatus.Cancelled) { // Create the Video File object and get the container + stream information LogStatus(Localise.GetPhrase("Analyzing video information"), jobLog); _videoFile = new VideoInfo(true, false, _conversionOptions.sourceVideo, "", "", "", _jobStatus, jobLog); // No work done here just get basic properties // Dont' need to worrk about errors/failues here since this is only used if we are extracting MC information } _jobStatus.ETA = "Working..."; _convertedFile = _conversionOptions.sourceVideo; // We are working directly on the source file here (copied) } // Create the XML file with Source video information for WTV and DVRMS file (XBMC compliant NFO file, http://wiki.xbmc.org/index.php?title=Import_-_Export_Library) if (!_jobStatus.Cancelled) { if (_conversionOptions.extractXML) _metaData.WriteXBMCXMLTags(OriginalFileName, WorkingVideo, _conversionOptions.workingPath, _videoFile); } // Processing complete, now rename the file based upon meta data string subDestinationPath = ""; if (!_jobStatus.Cancelled) { // Before renaming get the MediaInformation once to dump into the Log File for debugging purposes. FFmpegMediaInfo.DumpFileInformation(_convertedFile, _jobStatus, jobLog); LogStatus(Localise.GetPhrase("Renaming file using show information"), jobLog); // Check if we are using a Unicode Temp file name and restore it before renaming it if (Text.ContainsUnicode(OriginalFileName) && !_conversionOptions.renameOnly) // Unicode test, not all underlying apps and functions currently support UNICODE filenames, so use a ASCII name for now { try { string orgConvFile = Path.Combine(_conversionOptions.workingPath, Path.GetFileNameWithoutExtension(OriginalFileName) + FilePaths.CleanExt(_convertedFile)); jobLog.WriteEntry(this, "Restoring temporary Unicode file to original filename -> " + orgConvFile, Log.LogEntryType.Debug); FileIO.TryFileDelete(orgConvFile); File.Move(_convertedFile, orgConvFile); // Restore original filename _convertedFile = orgConvFile; // Restore the name } catch (Exception e) { jobLog.WriteEntry(this, "Unable to rename temporary Unicode file to original filename.\r\nError -> " + e.ToString(), Log.LogEntryType.Warning); } } RenameConvertedFile(out subDestinationPath, jobLog); // Rename the file } // Now run the post conversion custom command on the final file before it is moved if (!_jobStatus.Cancelled) { LogStatus(Localise.GetPhrase("Running custom commands"), jobLog); CustomCommand customCommand = new CustomCommand("CustomCommand", _conversionOptions.profile, _conversionOptions.taskName, _conversionOptions.workingPath, Path.GetDirectoryName(GetDestinationFilename(_conversionOptions, _metaData, OriginalFileName, jobLog)), _convertedFile, OriginalFileName, _remuxedVideo, (_commercialScan == null ? "" : _commercialScan.EDLFile), _srtFile, _metaData.MetaData, _jobStatus, jobLog); if (!customCommand.Run()) { jobLog.WriteEntry(this, Localise.GetPhrase("Custom command failed to run, critical failure"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = Localise.GetPhrase("Custom command failed to run, critical failure"); Cleanup(jobLog); return; // serious problem } // Check if the converted file has been tampered with if (!File.Exists(_convertedFile)) { jobLog.WriteEntry(this, Localise.GetPhrase("Converted file has been renamed or deleted by custom command") + " -> " + _convertedFile, Log.LogEntryType.Error); _jobStatus.ErrorMsg = Localise.GetPhrase("Converted file has been renamed or deleted by custom command"); Cleanup(jobLog); return; // serious problem } else jobLog.WriteEntry(this, Localise.GetPhrase("Finished custom command, file size [KB]") + " " + (Util.FileIO.FileSize(_convertedFile) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } // Finally move the file if (!_jobStatus.Cancelled) { LogStatus(Localise.GetPhrase("Moving converted file to destination"), jobLog); if (!MoveConvertedFile(subDestinationPath, jobLog)) { _jobStatus.ErrorMsg = Localise.GetPhrase("Moving converted file to destination failed"); // don't set this unless you want to indicate failure up the chain and kill the conversion process jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); Cleanup(jobLog); return; } } // After moving, now add the destination file to the iTunes/WMP library if required if (!_jobStatus.Cancelled) { if (_conversionOptions.addToiTunes) { LogStatus(Localise.GetPhrase("Adding file to the iTunes library"), jobLog); VideoMetaData.AddFileToiTunesLibrary(_convertedFile, jobLog); } if (_conversionOptions.addToWMP) { LogStatus(Localise.GetPhrase("Adding file to the WMP library"), jobLog); VideoMetaData.AddFileToWMPLibrary(_convertedFile, jobLog); } } // Finally - Move the remaining files file if (!_jobStatus.Cancelled) { // XML FILE (generated by Comskip or any other program) string xmlFile = Path.Combine(_conversionOptions.workingPath, (Path.GetFileNameWithoutExtension(WorkingVideo) + ".xml")); // XML file created by 3rd Party in temp working directory try { if (File.Exists(xmlFile)) { if (String.Compare(xmlFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".xml"), true) != 0) // Don't delete if they are the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, Localise.GetPhrase("Found XML file, moving to destination") + " XML:" + xmlFile + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".xml")); File.Move(xmlFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".xml")); //rename to match destination file } } } catch (Exception e) // Not critial to be unable to move SRT File { jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move XML file to destination") + " XML:" + xmlFile + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } // SRT FILE try { if (File.Exists(_srtFile)) { if (String.Compare(_srtFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".srt"), true) != 0) // Don't delete if they are the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, Localise.GetPhrase("Found SRT file, moving to destination") + " SRT:" + _srtFile + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".srt")); File.Move(_srtFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".srt")); //rename to match destination file } } } catch (Exception e) // Not critial to be unable to move SRT File { jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move SRT file to destination") + " SRT:" + _srtFile + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } // EDL FILE if (_commercialScan != null) { try { if (File.Exists(_commercialScan.EDLFile) && _commercialSkipCut) // if we are asked to keep EDL file, we copy it out to output { if (String.Compare(_commercialScan.EDLFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edl"), true) != 0) // don't delete the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, Localise.GetPhrase("Found EDL file, request to move to destination") + " EDL:" + _commercialScan.EDLFile + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edl")); File.Move(_commercialScan.EDLFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edl")); } } } catch (Exception e) { jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move EDL file to destination") + " EDL:" + _commercialScan.EDLFile + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } } else if (_saveEDLFile) // no commercial scan but we still found an EDL file with the source, we copy it to the output { // EDL File string edlFile = Path.Combine(_conversionOptions.workingPath, (Path.GetFileNameWithoutExtension(WorkingVideo) + ".edl")); // Saved EDL file try { if (File.Exists(edlFile)) { if (String.Compare(edlFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edl"), true) != 0) // Don't delete if they are the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, Localise.GetPhrase("Found EDL file, moving to destination") + " EDL:" + edlFile + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edl")); File.Move(edlFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edl")); //rename to match destination file } } } catch (Exception e) // Not critial to be unable to move EDL File { jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move EDL file to destination") + " EDL:" + edlFile + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } //EDLP File string edlpFile = Path.Combine(_conversionOptions.workingPath, (Path.GetFileNameWithoutExtension(WorkingVideo) + ".edlp")); // Saved EDLP file try { if (File.Exists(edlpFile)) { if (String.Compare(edlpFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edlp"), true) != 0) // Don't delete if they are the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, Localise.GetPhrase("Found EDLP file, moving to destination") + " EDLP:" + edlpFile + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edlp")); File.Move(edlpFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edlp")); //rename to match destination file } } } catch (Exception e) // Not critial to be unable to move EDL File { jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move EDLP file to destination") + " EDLP:" + edlpFile + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } } // NFO FILE string nfoFile = Path.Combine(_conversionOptions.workingPath, Path.GetFileNameWithoutExtension(WorkingVideo) + ".nfo"); // Path\FileName.nfo try { if (File.Exists(nfoFile)) { if (String.Compare(nfoFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".nfo"), true) != 0) // Don't delete if they are the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, Localise.GetPhrase("Found NFO file, moving to destination") + " NFO:" + nfoFile + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".nfo")); File.Move(nfoFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".nfo")); //rename to match destination file } } } catch (Exception e) // Not critial to be unable to move NFO File { jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move NFO file to destination") + " NFO:" + nfoFile + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } // COVER ART if (_conversionOptions.extractXML) // Only if asked to save the information { string coverArt = _metaData.MetaData.BannerFile; // Path to cover art try { if (File.Exists(coverArt)) { if (String.Compare(coverArt, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + FilePaths.CleanExt(coverArt)), true) != 0) // Don't delete if they are the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, ("Found Cover Art file, copying to destination") + " CoverArt:" + coverArt + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + FilePaths.CleanExt(coverArt))); File.Copy(coverArt, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + FilePaths.CleanExt(coverArt))); // Copy (NOT move since it may be used by other conversions and we don't keep downloading cover art) and rename to match destination file } } } catch (Exception e) // Not critial to be unable to move Cover Art File { jobLog.WriteEntry(this, ("Unable to copy Cover Art file to destination") + " CoverArt:" + coverArt + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } } // LOG FILE if (_copyLOGFile) { string logFile = Path.Combine(_conversionOptions.workingPath, Path.GetFileNameWithoutExtension(WorkingVideo) + ".log"); // Path\FileName.log try { if (File.Exists(logFile)) { if (String.Compare(logFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".log"), true) != 0) // Don't delete if they are the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, Localise.GetPhrase("Found LOG file, moving to destination") + " LOG:" + logFile + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".log")); File.Move(logFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".log")); //rename to match destination file } } } catch (Exception e) // Not critial to be unable to move LOG File { jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move LOG file to destination") + " LOG:" + logFile + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } } // PROPERTIES FILE if (_copyPropertiesFile) { string propertiesFile = FilePaths.GetFullPathWithoutExtension(OriginalFileName) + ".properties"; // The properties file lies with the original file try { if (File.Exists(propertiesFile)) { if (String.Compare(propertiesFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".properties"), true) != 0) // Don't delete if they are the same file, e.g. TS to TS in same directory { jobLog.WriteEntry(this, Localise.GetPhrase("Found SageTV Properties file, moving to destination") + " PROPERTIES:" + propertiesFile + " Destination:" + Path.GetDirectoryName(_convertedFile), Log.LogEntryType.Information); FileIO.TryFileDelete((Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".properties")); File.Move(propertiesFile, (Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".properties")); //rename to match destination file } } } catch (Exception e) // Not critial to be unable to move LOG File { jobLog.WriteEntry(this, Localise.GetPhrase("Unable to move LOG file to destination") + " PROPERTIES:" + propertiesFile + " Destination:" + Path.GetDirectoryName(_convertedFile) + " Error -> " + e.ToString(), Log.LogEntryType.Warning); } } // Last things - Run the end of conversion post rename and move custom command from the user if configured if (!_jobStatus.Cancelled) { LogStatus(Localise.GetPhrase("Running custom commands"), jobLog); string tmpSrt, tmpEdl; tmpEdl = (File.Exists(Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edl") ? Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".edl" : ""); tmpSrt = (File.Exists(Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".srt") ? Util.FilePaths.GetFullPathWithoutExtension(_convertedFile) + ".srt" : ""); CustomCommand customCommand = new CustomCommand("PostCustomCommand", _conversionOptions.profile, _conversionOptions.taskName, _conversionOptions.workingPath, Path.GetDirectoryName(GetDestinationFilename(_conversionOptions, _metaData, OriginalFileName, jobLog)), _convertedFile, OriginalFileName, _remuxedVideo, tmpEdl, tmpSrt, _metaData.MetaData, _jobStatus, jobLog); if (!customCommand.Run()) { jobLog.WriteEntry(this, Localise.GetPhrase("End of Conversion Custom command failed to run, critical failure"), Log.LogEntryType.Error); _jobStatus.ErrorMsg = Localise.GetPhrase("End of Conversion Custom command failed to run, critical failure"); Cleanup(jobLog); return; // serious problem } // Check if the converted file has been tampered with if (!File.Exists(_convertedFile)) { // WE WILL NOTE THIS AS AN SERIOUS ERROR BUT IT ISN'T A DEALBREAKER SINCE WE ARE ALL DONE HERE AND THE FILE HAS BEEN MOVED. jobLog.WriteEntry(this, Localise.GetPhrase("Destination file has been renamed or deleted by post custom command") + " -> " + _convertedFile, Log.LogEntryType.Error); } else jobLog.WriteEntry(this, Localise.GetPhrase("Finished end of conversion custom command, file size [KB]") + " " + (Util.FileIO.FileSize(_convertedFile) / 1024).ToString("N", System.Globalization.CultureInfo.InvariantCulture), Log.LogEntryType.Debug); } _jobStatus.ErrorMsg = ""; // all done, we are in the clear, success _jobStatus.SuccessfulConversion = true; //now the original file can be deleted if required LogStatus(Localise.GetPhrase("Success - All done!"), jobLog); } Cleanup(jobLog); // all done, clean it up, close jobLog and mark it inactive } catch(ThreadAbortException) { //incase the conversion thread is stopped due to the GUI stopping/cancelling, then release the logfile locks _jobStatus.ErrorMsg = "Conversion thread aborted, conversion cancelled"; jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); Cleanup(jobLog); } catch (Exception e) { // Unhandled error, clean up and log error _jobStatus.ErrorMsg = "Unhanded error during conversion, conversion cancelled"; jobLog.WriteEntry(this, _jobStatus.ErrorMsg + "\r\n" + e.ToString(), Log.LogEntryType.Error); Cleanup(jobLog); } }
private void ScanWithComskip() { string parameters = ""; if(_convOptions.comskipIni != "") // Check for custom Ini file { if(File.Exists(_convOptions.comskipIni)) parameters += "--ini=" + Util.FilePaths.FixSpaces(_convOptions.comskipIni); else _jobLog.WriteEntry(this, Localise.GetPhrase("Custom Comskip INI file does not exist, Skipping custom INI"), Log.LogEntryType.Warning); } parameters += " --output=" + Util.FilePaths.FixSpaces(_convOptions.workingPath); // Redirect all files to working folder (save issues with skipping copying original files and simulatenous scanning on same file) parameters += " " + Util.FilePaths.FixSpaces(_videoFileName); float Duration = 0; Duration = VideoParams.VideoDuration(_videoFileName); if (Duration <= 0) { FFmpegMediaInfo mediaInfo = new FFmpegMediaInfo(_videoFileName, _jobStatus, _jobLog); if (!mediaInfo.Success || mediaInfo.ParseError) _jobLog.WriteEntry(this, Localise.GetPhrase("Cannot read video duration"), Log.LogEntryType.Error); if (mediaInfo.MediaInfo.VideoInfo.Duration == 0) _jobLog.WriteEntry(this, Localise.GetPhrase("Video duration 0"), Log.LogEntryType.Warning); else Duration = mediaInfo.MediaInfo.VideoInfo.Duration; } // Check for custom version of Comskip path Comskip comskip = new Comskip(_customComskipPath, parameters, Duration, _jobStatus, _jobLog); // Use custom comskip or fallback to default comskip.Run(); if (!comskip.Success || !(File.Exists(WorkingEDLFilePath) || File.Exists(WorkingEDLPFilePath))) // Check if the EDL/EDLP file exists (here it is in working path), % does not always work { _jobLog.WriteEntry(this, "Comskip failed or no output EDL/EDLP file found", Log.LogEntryType.Warning); _jobStatus.PercentageComplete = 0; } }
/// <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(); }
/// <summary> /// Removes commercials from the converted file using the EDL file and updates the RemoveAds flag if successful /// </summary> /// <param name="filterAudioLanguage">True if you want to keep only the identified audio language while cutting TS files (before conversion since VideoFile will be used to determine audio and video track properties). This is useful when commercial remove is done before video conversion since audio language information is lost.</param> public void StripCommercials(bool remuxedVideoFilterAudioLanguage = false) { _jobStatus.PercentageComplete = 100; //all good to start with _jobStatus.ETA = ""; if (!File.Exists(_uncutVideo)) { _jobLog.WriteEntry(this, Localise.GetPhrase("File not found") + " " + _uncutVideo, Log.LogEntryType.Error); _jobStatus.PercentageComplete = 0; _jobStatus.ErrorMsg = "Strip Commercials file not found"; return; } if (!File.Exists(EDLFile)) { _jobLog.WriteEntry(this, Localise.GetPhrase("EDL file does not exist - no commercials found") + " " + EDLFile, Log.LogEntryType.Information); _jobStatus.ErrorMsg = "Strip commercial EDL file not found"; _jobStatus.PercentageComplete = 0; return; } if (remuxedVideoFilterAudioLanguage) // If we are being called preconversion then the media info is the same as the remuxed file _uncutVideoMediaInfo = _remuxedVideoFileInfo.FFMPEGStreamInfo; else // otherwise lets get the media info for the conversted file { _uncutVideoMediaInfo = new FFmpegMediaInfo(_uncutVideo, _jobStatus, _jobLog); if (!_uncutVideoMediaInfo.Success || _uncutVideoMediaInfo.ParseError) { _jobStatus.PercentageComplete = 0; // if the file wasn't completely converted the percentage will be low so no worries _jobStatus.ErrorMsg = "Commerical remover, getting video mediainfo failed for " + _uncutVideo; _jobLog.WriteEntry(this, (_jobStatus.ErrorMsg), Log.LogEntryType.Error); return; } } _jobLog.WriteEntry(this, Localise.GetPhrase("Commercial Remove: Before removal file size [KB] ") + (Util.FileIO.FileSize(_uncutVideo)/1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); ; if (_universalCommercialRemover) { _jobLog.WriteEntry(this, Localise.GetPhrase("Forcing use of Universal Commercial Remover for") + " " + _ext, Log.LogEntryType.Warning); CutFFMPEG(remuxedVideoFilterAudioLanguage); } else { switch (_ext) { case ".ts": _jobLog.WriteEntry(this, Localise.GetPhrase("Removing commercials using CutTS") + " ext -> " + _ext, Log.LogEntryType.Information); CutTS(remuxedVideoFilterAudioLanguage); // TODO: Mencoder based TS cutting is broken, generated unusable files and also EDL cutting does not work /*if (0 == _jobStatus.PercentageComplete) // Try backup { _jobLog.WriteEntry(this, Localise.GetPhrase("CutTS failed, trying to remove commercials using CutMencoder") + " ext -> " + _ext, Log.LogEntryType.Information); _jobStatus.PercentageComplete = 100; //Reset _jobStatus.ErrorMsg = ""; //reset error message since we are trying again CutMencoder(filterTSAudioLanguage); }*/ break; case ".wmv": _jobLog.WriteEntry(this, Localise.GetPhrase("Removing commercials using CutWMV") + " ext -> " + _ext, Log.LogEntryType.Information); CutWMV(); break; case ".mpg": case ".avi": _jobLog.WriteEntry(this, Localise.GetPhrase("Removing commercials using CutMencoder") + " ext -> " + _ext, Log.LogEntryType.Information); CutMencoder(); break; case ".m4v": case ".mp4": if (_cutMP4Alternative) { _jobLog.WriteEntry(this, Localise.GetPhrase("Removing commercials using CutMP4Alternate") + " ext -> " + _ext, Log.LogEntryType.Information); CutMP4Alternate(); } else { _jobLog.WriteEntry(this, Localise.GetPhrase("Removing commercials using CutMP4") + " ext -> " + _ext, Log.LogEntryType.Information); CutMP4(); if (0 == _jobStatus.PercentageComplete) // CutMP4 can fail for some .TS files that can't be read by MediaInfo, do a backup try { _jobStatus.ErrorMsg = ""; //reset error message since we are trying again _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4 failed, trying backup CutMP4Alternate to remove commercials"), Log.LogEntryType.Warning); _jobStatus.PercentageComplete = 100; //Reset CutMP4Alternate(); } } break; case ".mkv": { _jobLog.WriteEntry(this, Localise.GetPhrase("Removing commercials using CutMKV"), Log.LogEntryType.Information); CutMKV(); break; } default: { _jobLog.WriteEntry(this, Localise.GetPhrase("Unsupported extension for removing commercials ") + " " + _ext + ", trying default FFMPEG cutter", Log.LogEntryType.Warning); CutFFMPEG(remuxedVideoFilterAudioLanguage); break; } } // If all fails, try one last chance with CutFFMPEG if (0 == _jobStatus.PercentageComplete) // Try backup { _jobLog.WriteEntry(this, Localise.GetPhrase("Cutting failed, trying to remove commercials using CutFFMPEG") + " ext -> " + _ext, Log.LogEntryType.Information); _jobStatus.PercentageComplete = 100; //Reset _jobStatus.ErrorMsg = ""; //reset error message since we are trying again CutFFMPEG(remuxedVideoFilterAudioLanguage); } } if ((_jobStatus.PercentageComplete != 0) && !String.IsNullOrWhiteSpace(_cutVideo)) _remuxedVideoFileInfo.AdsRemoved = true; // We have removed the Ad's else _remuxedVideoFileInfo.AdsRemoved = false; _jobLog.WriteEntry(this, Localise.GetPhrase("Commercial Remove: After removal file size [KB] ") + (Util.FileIO.FileSize(_cutVideo) / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); ; _jobLog.WriteEntry(this, Localise.GetPhrase("Commercial Remove: Percentage Complete") + " " + _jobStatus.PercentageComplete.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Debug); }
/// <summary> /// Backup remove commercials from MP4 using MEncoder, not the best, limited in many ways /// </summary> private void CutMP4Alternate() { string tempFile = CutFileName(_uncutVideo, 0, 0); // TODO: Need to add support for non AAC Audio Codecs // Refer to: http://forum.videohelp.com/threads/337215-Mencoder-not-copying-aac-to-mp4?p=2140000 // and http://www.mplayerhq.hu/DOCS/codecs-status.html#ac // Do not use -hr-edl-seek as it doesn't work well with -ovc copy // lavdopts supports a max of 8 threads // avoid using threads, stablity - only copying here string mencoderParams = Util.FilePaths.FixSpaces(_uncutVideo) + " -of lavf -ovc copy -oac copy"; //Set fafmttag based on the type of audio codec of converted file (currently supported aac, ac3, eac3) _jobLog.WriteEntry(this, Localise.GetPhrase("Trying to reading converted file Audio information"), Log.LogEntryType.Information); string audioCodec = ""; FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(_uncutVideo, _jobStatus, _jobLog); if (ffmpegStreamInfo.Success && !ffmpegStreamInfo.ParseError) { // Converted file should contain only 1 audio stream audioCodec = ffmpegStreamInfo.MediaInfo.AudioInfo[0].AudioCodec; _jobLog.WriteEntry(this, Localise.GetPhrase("Found AudioCodec") + " " + audioCodec, Log.LogEntryType.Information); if (String.IsNullOrEmpty(audioCodec)) _jobLog.WriteEntry(this, Localise.GetPhrase("Audio codec information is blank, will try to continue without it"), Log.LogEntryType.Warning); } else _jobLog.WriteEntry(this, Localise.GetPhrase("Cannot read Audio codec information, will try to continue without it"), Log.LogEntryType.Warning); switch (audioCodec) { case "aac": _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate found AAC Audio Codec"), Log.LogEntryType.Information); mencoderParams += @" -fafmttag 0x706D"; break; case "ac3": case "ac-3": _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate found AC3 Audio Codec"), Log.LogEntryType.Information); mencoderParams += @" -fafmttag 0x2000"; break; case "e-ac-3": _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate found E-AC3 Audio Codec"), Log.LogEntryType.Information); mencoderParams += @" -fafmttag 0x33434145"; break; case "mp3": _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate found mp3 Audio Codec"), Log.LogEntryType.Information); mencoderParams += @" -fafmttag 0x55"; break; case "flac": _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate found flac Audio Codec"), Log.LogEntryType.Information); mencoderParams += @" -fafmttag 0xF1AC"; break; case "mp1": case "mp2": _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate found mp2 Audio Codec"), Log.LogEntryType.Information); mencoderParams += @" -fafmttag 0x50"; break; default: _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate could not identify Audio Codec, DEFAULTING TO AAC - please check video file and audio encoder type"), Log.LogEntryType.Warning); mencoderParams += @" -fafmttag 0x706D"; break; } mencoderParams += " -edl " + Util.FilePaths.FixSpaces(EDLFile) + " -o " + Util.FilePaths.FixSpaces(tempFile); _jobStatus.CurrentAction = Localise.GetPhrase("Merging commercial free segments into new video"); _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate: Merging commercial free segments into new video"), Log.LogEntryType.Information); Mencoder mencoder = new Mencoder(mencoderParams, _jobStatus, _jobLog, false); mencoder.Run(); if (!mencoder.Success || (Util.FileIO.FileSize(tempFile) <= 0)) // do not check for % success here since sometimes it does not show complete number { _jobStatus.ErrorMsg = "CutMP4Alternate Commercial cutting failed"; _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate Commercial cutting failed"), Log.LogEntryType.Error); _jobStatus.PercentageComplete = 0; return; } _jobLog.WriteEntry(this, Localise.GetPhrase("CutMP4Alternate trying to replace file") + " Output : " + _uncutVideo + " Temp : " + tempFile, Log.LogEntryType.Debug); RenameAndMoveFile(tempFile); }
/// <summary> /// Remove commercials from TS files using FFMPEG, AVIDemux and FFMPEG as backup /// </summary> /// <param name="remuxedVideoFilterAudioLanguage">True if you want to use the user selected language to filter out while cutting</param> private void CutTS(bool remuxedVideoFilterAudioLanguage) { bool selectedTSAudioSuccess = false; // have we successfully filtered the audio tracks List<KeyValuePair<float, float>> keepList = new List<KeyValuePair<float, float>>(); long totalSegmentSize = 0; // Read the EDL file and convert into cut structure if (!ParseEDLFile(ref keepList)) return; //Do the cuts and pick them up as we go int cutNumber = 0; List<string> filesFound = new List<string>(); foreach (KeyValuePair<float, float> keep in keepList) { _jobStatus.CurrentAction = Localise.GetPhrase("Cutting commercials from video - segment") + " " + cutNumber.ToString(CultureInfo.InvariantCulture); _jobLog.WriteEntry(this, Localise.GetPhrase("CutTS: Cutting commercials from video - segment ") + cutNumber.ToString(CultureInfo.InvariantCulture), Log.LogEntryType.Information); // Create the splits - reset success each time string cutFileName = CutFileName(_uncutVideo, keep.Key, keep.Value); string parameters = " -y -threads 0"; // http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg - We use only FAST seek (since accurate seek cuts on a NON KeyFrame which causes Audio Sync Issues) parameters += " -ss " + keep.Key.ToString(CultureInfo.InvariantCulture); parameters += " -i " + Util.FilePaths.FixSpaces(_uncutVideo); // how much to Cut parameters += " -t " + (keep.Value - keep.Key).ToString(CultureInfo.InvariantCulture); // If we are requested to filter out the selected audio language, check for it's existance and filter it if (remuxedVideoFilterAudioLanguage && (_remuxedVideoFileInfo.FFMPEGStreamInfo.AudioTracks > 1)) { if (_remuxedVideoFileInfo.AudioStream == -1) { _jobLog.WriteEntry("Cannot get audio stream selection details, copying all audio streams", Log.LogEntryType.Warning); // Check for video track if (_uncutVideoMediaInfo.MediaInfo.VideoInfo.Stream != -1) parameters += " -map 0:v -vcodec copy"; else parameters += " -vn"; // Audio tracks parameters += " -map 0:a -acodec copy"; } else { _jobLog.WriteEntry("Selecting Audio Language " + _remuxedVideoFileInfo.AudioLanguage + " Audio Stream " + _remuxedVideoFileInfo.AudioStream.ToString(), Log.LogEntryType.Debug); // Check for video track if (_uncutVideoMediaInfo.MediaInfo.VideoInfo.Stream != -1) parameters += " -map 0:v -vcodec copy"; else parameters += " -vn"; // Audio track parameters += " -map 0:" + _remuxedVideoFileInfo.AudioStream.ToString() + " -acodec copy"; // Select the Audiotrack we had isolated earlier selectedTSAudioSuccess = true; } } else { // Check for video track if (_uncutVideoMediaInfo.MediaInfo.VideoInfo.Stream != -1) parameters += " -map 0:v -vcodec copy"; else parameters += " -vn"; // Check for Audio tracks if (_uncutVideoMediaInfo.AudioTracks > 0) parameters += " -map 0:a -acodec copy"; else parameters += " -an"; } // Stream copy parameters += " " + Util.FilePaths.FixSpaces(cutFileName); if (!FFmpeg.FFMpegExecuteAndHandleErrors(parameters, _jobStatus, _jobLog, Util.FilePaths.FixSpaces(cutFileName), false)) // Don't check for % here since Comskip has a bug that gives EDL cut segments past the end of the file || _jobStatus.PercentageComplete < GlobObjects.ACCEPTABLE_COMPLETION) // each run resets this number or an error in the process { filesFound.Add(cutFileName); // add the last file created (which may be partial) CleanupCutFiles(filesFound); //Clean up cut files _jobStatus.PercentageComplete = 0; _jobStatus.ErrorMsg = "CutTS splitting video segments failed"; _jobLog.WriteEntry(this, Localise.GetPhrase("CutTS splitting video segments failed"), Log.LogEntryType.Error); return; } filesFound.Add(cutFileName); // to be deleted later cutNumber++; totalSegmentSize += Util.FileIO.FileSize(cutFileName); // Calculate the total size of all segments for validation later } if (cutNumber < 1) { _jobLog.WriteEntry(this, Localise.GetPhrase("No commercials to remove from ") + EDLFile, Log.LogEntryType.Information); _jobStatus.PercentageComplete = 100; //Set to success since sometime mp4box doesn't set to 100 if there no/incomplete pieces to strip return; } // Build the AVIDemux Merge Params cutNumber = 0; string aviDemuxMergeParams = ""; foreach (string file in filesFound) { if (cutNumber++ == 0) aviDemuxMergeParams += Util.FilePaths.FixSpaces(file); else aviDemuxMergeParams += " --append " + Util.FilePaths.FixSpaces(file); } // Save the file as a TS file string tempFile = CutFileName(_uncutVideo, 0, 0); aviDemuxMergeParams += " --video-codec copy --audio-codec copy --output-format ffts --save " + Util.FilePaths.FixSpaces(tempFile); // Build the FFMPEG Merge Params // Create a text file with the list of all the files which need to be concatenated // http://ffmpeg.org/trac/ffmpeg/wiki/How%20to%20concatenate%20(join,%20merge)%20media%20files string concatFileList = ""; string concatFileName = Path.Combine(_workingPath, Path.GetFileNameWithoutExtension(_uncutVideo) + "_ConcatList.txt"); foreach (string file in filesFound) concatFileList += "file " + "'" + file.Replace("'", @"'\''") + "'" + "\r\n"; // File paths are enclosed in single quotes, compensate for ' with '\'' in the filename System.IO.File.WriteAllText(concatFileName, concatFileList); // write the text file cutNumber = 0; // Merge the files using FFMPEG as a backup string ffmpegMergeParams = "-y -f concat -i " + Util.FilePaths.FixSpaces(concatFileName); // Check for video track if (_uncutVideoMediaInfo.MediaInfo.VideoInfo.Stream != -1) ffmpegMergeParams += " -map v -vcodec copy"; else ffmpegMergeParams += " -vn"; // Check for Audio tracks if (_uncutVideoMediaInfo.AudioTracks > 0) ffmpegMergeParams += " -map a -acodec copy"; else ffmpegMergeParams += " -an"; // Avoid negative timestamp issues https://trac.ffmpeg.org/wiki/Seeking%20with%20FFmpeg ffmpegMergeParams += " -avoid_negative_ts 1"; ffmpegMergeParams += " " + Util.FilePaths.FixSpaces(tempFile); // Output file _jobStatus.CurrentAction = Localise.GetPhrase("Merging commercial free segments into new video"); _jobLog.WriteEntry(this, "CutTS: Merging commercial free segments into new video", Log.LogEntryType.Information); // Check if the converted file has more than 1 audio track, AVIDemux does not support merging files with > 1 audio track (it drops them randomly) bool useFFMPEG = false; bool retVal = false; FFmpegMediaInfo ffmpegStreamInfo = new FFmpegMediaInfo(_uncutVideo, _jobStatus, _jobLog); if (!ffmpegStreamInfo.Success || ffmpegStreamInfo.ParseError) { CleanupCutFiles(filesFound); //Clean up cut files Util.FileIO.TryFileDelete(concatFileName); _jobStatus.PercentageComplete = 0; _jobStatus.ErrorMsg = "CutTS unable to get video info"; _jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); return; } AVIDemux aviDemux = new AVIDemux(aviDemuxMergeParams, _jobStatus, _jobLog); // If we have more than one audio track we should use FFMPEG instead of AVIDemux if (_useFFMPEGMerge) { _jobLog.WriteEntry(this, "Forcing FFMPEG instead of AVIDemux to merge tracks. There may be issues with the video at the merged segments", Log.LogEntryType.Warning); useFFMPEG = true; retVal = FFmpeg.FFMpegExecuteAndHandleErrors(ffmpegMergeParams, _jobStatus, _jobLog, Util.FilePaths.FixSpaces(tempFile)); } else if (_useAVIDemuxMerge) { _jobLog.WriteEntry(this, "Forcing AVIDemux to merge tracks. Sometimes merging may hang", Log.LogEntryType.Warning); useFFMPEG = false; aviDemux.Run(); retVal = aviDemux.Success; } else if (ffmpegStreamInfo.AudioTracks <= 1 || selectedTSAudioSuccess) // If the source file has 1 audio track or if we have filtered the audio track while cutting the segements { _jobLog.WriteEntry(this, "Single audio track detected in converted file, using AVIDemux to merge tracks.", Log.LogEntryType.Debug); useFFMPEG = false; aviDemux.Run(); // Run only if we have 1 or less audio tracks retVal = aviDemux.Success; } else { _jobLog.WriteEntry(this, "More than 1 audio track detected in converted file, using FFMPEG instead of AVIDemux to merge tracks. There may be issues with the video at the merged segments", Log.LogEntryType.Warning); useFFMPEG = true; retVal = FFmpeg.FFMpegExecuteAndHandleErrors(ffmpegMergeParams, _jobStatus, _jobLog, Util.FilePaths.FixSpaces(tempFile)); } _jobLog.WriteEntry(this, "CutTS : Merged Segements size [KB] " + (Util.FileIO.FileSize(tempFile) / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "CutTS : Total Expected Segements size [KB] " + (totalSegmentSize / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); // If we didn't succeed or filesize wasn't as expected then try backup merging if (!retVal || (Util.FileIO.FileSize(tempFile) < (GlobalDefs.MINIMUM_MERGED_FILE_THRESHOLD * totalSegmentSize)) || (totalSegmentSize <= 0)) // Check final merged file size to double verify, sometime AVIDemux skips merging segments if there is an error (like video width for segment is different due to commercials) { _jobLog.WriteEntry(this, "CutTS: Merging video segments failed, retrying with backup", Log.LogEntryType.Warning); _jobStatus.CurrentAction = Localise.GetPhrase("Merging commercial free segments into new video"); _jobLog.WriteEntry(this, "CutTS: Merging commercial free segments into new video", Log.LogEntryType.Information); // If we used ffmpeg earlier, then use AVIDemux else use ffmpeg for the backup if (useFFMPEG) { _jobLog.WriteEntry(this, "Backup merging, forcing AVIDemux to merge tracks. Sometimes merging may hang", Log.LogEntryType.Warning); aviDemux.Run(); retVal = aviDemux.Success; } else // use ffmpeg since we used avidemux earlier { _jobLog.WriteEntry(this, "Backup merging, forcing FFMPEG instead of AVIDemux to merge tracks. There may be issues with the video at the merged segments", Log.LogEntryType.Warning); retVal = FFmpeg.FFMpegExecuteAndHandleErrors(ffmpegMergeParams, _jobStatus, _jobLog, Util.FilePaths.FixSpaces(tempFile)); } _jobLog.WriteEntry(this, "CutTS : Backup merged Segements size [KB] " + (Util.FileIO.FileSize(tempFile) / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "CutTS : Total Expected Segements size [KB] " + (totalSegmentSize / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); if (!retVal || (Util.FileIO.FileSize(tempFile) < (GlobalDefs.MINIMUM_MERGED_FILE_THRESHOLD * totalSegmentSize)) || (totalSegmentSize <= 0)) // Check final merged file size to double verify, sometime AVIDemux skips merging segments if there is an error (like video width for segment is different due to commercials) { CleanupCutFiles(filesFound); //Clean up cut files Util.FileIO.TryFileDelete(concatFileName); _jobStatus.PercentageComplete = 0; _jobStatus.ErrorMsg = "CutTS merging video segments failed"; _jobLog.WriteEntry(this, "CutTS merging video segments failed", Log.LogEntryType.Error); return; } } // Clean up Util.FileIO.TryFileDelete(concatFileName); // Move the files _jobLog.WriteEntry(this, Localise.GetPhrase("CutTS trying to replace file") + " Output : " + _uncutVideo + " Temp : " + tempFile, Log.LogEntryType.Debug); RenameAndMoveFile(tempFile); //Clean up cut files CleanupCutFiles(filesFound); }
public MediaInfo GetFileInfo(string videoFilePath) { if (String.IsNullOrWhiteSpace(videoFilePath)) { Log.AppLog.WriteEntry(this, "Empty filepath passed to get file info", Log.LogEntryType.Error, true); return null; } // Get the properties of this source video JobStatus jobStatus = new JobStatus(); // Get the FPS from MediaInfo, more reliable then FFMPEG but it doesn't always work float FPS = VideoParams.FPS(videoFilePath); FFmpegMediaInfo videoInfo = new FFmpegMediaInfo(videoFilePath, jobStatus, Log.AppLog, true); // cannot suspend during a UI request else it hangs if (videoInfo.Success && !videoInfo.ParseError) { if ((FPS > 0) && (FPS <= videoInfo.MediaInfo.VideoInfo.FPS)) // Check _fps, sometimes MediaInfo get it below 0 or too high (most times it's reliable) videoInfo.MediaInfo.VideoInfo.FPS = FPS; // update it } else { Log.AppLog.WriteEntry("Error trying to get Audio Video information. Unable to Read Media File -> " + videoFilePath, Log.LogEntryType.Error, true); } return videoInfo.MediaInfo; }