/// <summary> /// Extract the Audio and Video Streams from the file using Windows DirectShow Graph Filter and then remux using TSMuxer and FFMPEG. /// Currently only support extracting 1 audio stream, so a good option for DVRMS (backup for WTV) /// </summary> /// <returns>True if successful in Remuxing to TS</returns> private bool DirectShowExtractAndRemuxStreams() { if (_jobStatus.Cancelled) { _jobLog.WriteEntry("Job cancelled, skipping DirectShow Streams Extraction", Log.LogEntryType.Error); return false; } switch (Util.FilePaths.CleanExt(_RecordingFile)) { case ".wtv": case ".dvr-ms": case ".tivo": break; default: _jobLog.WriteEntry(this, "DirectShow Streams remuxing only supported for WTV, DVR-MS and TIVO files", Log.LogEntryType.Error); return false; } try { _jobLog.WriteEntry("Extracting with Graph....", Log.LogEntryType.Debug); _extract = new ExtractWithGraph(_RecordingFile, _destinationPath, ExtractWithGraph.ExtractMediaType.Audio | ExtractWithGraph.ExtractMediaType.Video, _jobStatus, _jobLog); // Extract only audio and video streams, we don't need subtitle } catch (Exception e) { try { if (_extract != null) _extract.Dispose(); _jobLog.WriteEntry(this, Localise.GetPhrase("Error Extracting with Graph....") + "\r\nError : " + e.ToString(), Log.LogEntryType.Error, true); } catch (Exception e1) { _jobLog.WriteEntry(this, Localise.GetPhrase("Error disposing objects after graph failure....") + "\r\nError : " + e1.ToString(), Log.LogEntryType.Error, true); } return false; } try { _jobLog.WriteEntry("Building with Graph....", Log.LogEntryType.Debug); _extract.BuildGraph(); // TODO: Currently this only returns 1 audio stream and doesn't extract other audio streams } catch (Exception e) { try { _jobLog.WriteEntry(this, ("Error building graph.\r\nError : " + e.ToString()), Log.LogEntryType.Error); _extract.DeleteParts(); _extract.Dispose(); } catch (Exception e1) { _jobLog.WriteEntry(this, Localise.GetPhrase("Error disposing objects after graph failure....") + "\r\nError : " + e1.ToString(), Log.LogEntryType.Error, true); } return false; } // Atleast 1 audio or 1 video channel should be there if (String.IsNullOrWhiteSpace(_extract.VideoPart) && (_extract.AudioParts.Count < 1)) { try { _jobLog.WriteEntry(this, "Graph invalid number of video/audio streams", Log.LogEntryType.Error); _extract.DeleteParts(); _extract.Dispose(); } catch (Exception e1) { _jobLog.WriteEntry(this, "Invalid Graph audio/video streams disposing.\r\nError : " + e1.ToString(), Log.LogEntryType.Error); } return false; } try { _jobLog.WriteEntry("Running with Graph....", Log.LogEntryType.Debug); _extract.RunGraph(); } catch (Exception e) { try { _jobLog.WriteEntry(this, ("Error running graph.\r\nError : " + e.ToString()), Log.LogEntryType.Error); _extract.DeleteParts(); _extract.Dispose(); } catch (Exception e1) { _jobLog.WriteEntry(this, "Error disposing graph.\r\nError : " + e1.ToString(), Log.LogEntryType.Error); } return false; } // Did not abort if ((!_extract.SuccessfulExtraction)) { try { _jobLog.WriteEntry(this, "Error extracting streams using Graph", Log.LogEntryType.Error); _extract.DeleteParts(); _extract.Dispose(); } catch (Exception e1) { _jobLog.WriteEntry(this, "Error extracting streams disposing.\r\nError : " + e1.ToString(), Log.LogEntryType.Error); } return false; } // Dispose the Graph object try { _jobLog.WriteEntry("Disposing Graph....", Log.LogEntryType.Debug); _extract.Dispose(); } catch (Exception e) { _jobLog.WriteEntry(this, "Error disposing graph.\r\nError : " + e.ToString(), Log.LogEntryType.Error); // We can still continue here } // Put the streams back together in TS format // Check to see if we can use FFmpegParts to do the job _jobLog.WriteEntry(this, Localise.GetPhrase("Muxing streams with TsMuxer"), Log.LogEntryType.Information); _jobStatus.CurrentAction = Localise.GetPhrase("Muxing streams"); if (!RemuxRawTSMuxer(_RecordingFile, RemuxedFile, _extract, _jobStatus, _jobLog) || !RemuxedFileOK()) { _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); _jobStatus.CurrentAction = Localise.GetPhrase("Fallback muxing streams"); if (!RemuxRawPartsFFmpeg(_RecordingFile, RemuxedFile, _extract, _jobStatus, _jobLog) || !RemuxedFileOK()) { _extract.DeleteParts(); // Clean up _jobLog.WriteEntry(this, "Fallback Remux Streams FFMpegParts failed", Log.LogEntryType.Error); return false; } } _extract.DeleteParts(); // Clean up return true; }
/// <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); } }