public bool Run(ConversionJobOptions conversionOptions, VideoInfo videoFile, Scanner commercialScan, string srtFile) { bool converted = false; Ini ini = new Ini(GlobalDefs.ProfileFile); // Dump the entire profile for debugging purposes (incase users have customized it) _jobLog.WriteEntry("Profile being used : " + conversionOptions.profile + ".\r\nProfile entries ->", Log.LogEntryType.Debug); SortedList<string, string> profileEntries = ini.GetSectionKeyValuePairs(conversionOptions.profile); foreach (string key in profileEntries.Keys) { _jobLog.WriteEntry(key + "=" + profileEntries[key], Log.LogEntryType.Debug); } string[] order = GetProfileEncoderOrder(conversionOptions.profile); foreach (string encoder in order) { switch (encoder.Trim()) { case "copy": { _jobLog.WriteEntry(this, Localise.GetPhrase("Using special case COPY for converter"), Log.LogEntryType.Information); // Special case, no real encoder, just ignore any recoding and assume the output = input file ConvertWithCopy convertWithCopy = new ConvertWithCopy(conversionOptions, "copy", videoFile, _jobStatus, _jobLog, commercialScan); if (!convertWithCopy.Unsupported) { _jobLog.WriteEntry(this, Localise.GetPhrase("Converting with COPY"), Log.LogEntryType.Information); bool ret = convertWithCopy.Convert(); if (ret) { converted = true; _convertedFile = convertWithCopy.ConvertedFile; videoFile.ConversionTool = "copy"; } else { _jobLog.WriteEntry(this, Localise.GetPhrase("COPY did not convert successfully, using fallback if configured"), Log.LogEntryType.Error); } } break; } case "mencoder": { ConvertWithMencoder convertWithMencoder = new ConvertWithMencoder(conversionOptions, "mencoder", videoFile, _jobStatus, _jobLog, commercialScan); if (!convertWithMencoder.Unsupported) { _jobLog.WriteEntry(this, Localise.GetPhrase("Converting with MEncoder"), Log.LogEntryType.Information); bool ret = convertWithMencoder.Convert(); if (ret) { converted = true; _convertedFile = convertWithMencoder.ConvertedFile; videoFile.ConversionTool = "mencoder"; } else { _jobLog.WriteEntry(this, Localise.GetPhrase("MEncoder did not convert successfully, using fallback if configured"), Log.LogEntryType.Error); } } else _jobLog.WriteEntry(this, Localise.GetPhrase("Unsupported MEncoder file formats"), Log.LogEntryType.Error); break; } case "handbrake": { ConvertWithHandbrake convertWithHandbrake = new ConvertWithHandbrake(conversionOptions, "handbrake", videoFile, _jobStatus, _jobLog, commercialScan); if (!convertWithHandbrake.Unsupported) { _jobLog.WriteEntry(this, Localise.GetPhrase("Converting with Handbrake"), Log.LogEntryType.Information); bool ret = convertWithHandbrake.Convert(); if (ret) { converted = true; _convertedFile = convertWithHandbrake.ConvertedFile; videoFile.ConversionTool = "handbrake"; } else { _jobLog.WriteEntry(this, Localise.GetPhrase("Handbrake did not convert successfully, using fallback if configured"), Log.LogEntryType.Error); } } else _jobLog.WriteEntry(this, Localise.GetPhrase("Unsupported Handbrake file formats"), Log.LogEntryType.Error); break; } case "ffmpeg": { ConvertWithFfmpeg convertWithFfmpeg = new ConvertWithFfmpeg(conversionOptions, "ffmpeg", videoFile, _jobStatus, _jobLog, commercialScan, srtFile); if (!convertWithFfmpeg.Unsupported) { _jobLog.WriteEntry(this, Localise.GetPhrase("Converting with FFMpeg"), Log.LogEntryType.Information); bool ret = convertWithFfmpeg.Convert(); if (ret) { converted = true; _convertedFile = convertWithFfmpeg.ConvertedFile; _subtitleBurned = convertWithFfmpeg.SubtitleBurned; // Right now only ffmpeg supports subtitle burning videoFile.ConversionTool = "ffmpeg"; } else { _jobLog.WriteEntry(this, Localise.GetPhrase("FFMpeg did not convert successfully, using fallback if configured"), Log.LogEntryType.Error); } } else _jobLog.WriteEntry(this, Localise.GetPhrase("Unsupported FFMpeg file formats"), Log.LogEntryType.Error); break; } default: { _jobLog.WriteEntry(Localise.GetPhrase("Unsupported converter"), Log.LogEntryType.Error); break; } } if (converted || _jobStatus.Cancelled) break; } if (!converted) _jobLog.WriteEntry(this, Localise.GetPhrase("Unable to convert file") + " " + Path.GetFileName(videoFile.SourceVideo) + " " + Localise.GetPhrase("using profile") + " " + conversionOptions.profile, Log.LogEntryType.Error); else { _jobLog.WriteEntry(this, Localise.GetPhrase("Successfully converted file") + " " + Path.GetFileName(videoFile.SourceVideo) + " " + Localise.GetPhrase("using profile") + " " + conversionOptions.profile, Log.LogEntryType.Debug); //Reset the error message incase there was a fallback conversion the suceeded _jobStatus.ErrorMsg = ""; } return converted; }
/// <summary> /// Check the History if the file has been converted (check output/converted filename and path). /// This function is thread safe /// </summary> /// <param name="convertedFile">Output Filename and path to check</param> /// <returns>True of the output filename and path exists in the history file</returns> public static bool DoesConvertedFileExistCheckHistory(string convertedFile) { // TODO: A very very large history file can cause the computer to "hang" and have high CPU utilization, how does one handle this situation? try { // Check if the file has been converted in the past Ini historyIni = new Ini(GlobalDefs.HistoryFile); List<string> fileNames = historyIni.GetSectionNames(); foreach (string filePath in fileNames) { if (filePath.ToLower() == convertedFile.ToLower()) // Check if the converted file exists in the History list, ignore case if (historyIni.GetSectionKeyValuePairs(filePath)["Status"] == "OutputFromConversion") // Double check that this file is an output from the conversion return true; } } catch (Exception e) { Log.AppLog.WriteEntry("Unable to check History file entries", Log.LogEntryType.Error, true); Log.AppLog.WriteEntry("Error -> " + e.ToString(), Log.LogEntryType.Error, true); } return false; }
/// <summary> /// Scans the Monitored directories and Manual Queue for new files to process /// Applies the filter tests /// Processes conversion task jobs for the files and adds them to the queue /// This function takes a lock on the Queue while modifying the queue and is thread safe /// </summary> public void ScanForFiles() { // Search the specific directories foreach (MonitorJobOptions monitorTask in MCEBuddyConf.GlobalMCEConfig.AllMonitorTasks) { IEnumerable<string> foundFiles = null; try { // Directory.EnumerateFiles throws an exception if it comes across a protected/unaccesible directory and the ENTIRE list is empty // Instead we need to handle exceptions, skip the protected file/directory and continue walking down the rest foundFiles = FilePaths.GetDirectoryFiles(monitorTask.searchPath, "*", (monitorTask.monitorSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)).OrderBy(File.GetLastWriteTime); // We sort the files by last modified time when scanning and adding (oldest to newest) if (foundFiles != null && foundFiles.Count() > 0) // Check for no files (could be due to security access/protection errors { foreach (string foundFile in foundFiles) { if (GlobalDefs.Shutdown) // Check for a shutdown command, this can be an intensive loop return; // Exit - we're done here Monitor.Enter(_monitorTaskFilterMismatchFiles); // Make this thread safe if (_monitorTaskFilterMismatchFiles.ContainsKey(foundFile)) // Check if this file has been processed by this monitor task and doesn't have a filter match, if so skip it (the filters do not change until the engine is stopped, settings changed, engine restarted which will create a new queue) { if (_monitorTaskFilterMismatchFiles[foundFile].Contains(monitorTask.taskName)) { Monitor.Exit(_monitorTaskFilterMismatchFiles); continue; } } Monitor.Exit(_monitorTaskFilterMismatchFiles); Monitor.Enter(_archivedFiles); if (_archivedFiles.Contains(foundFile)) // Check if the file has been marked as an archive file if so skip it { Monitor.Exit(_archivedFiles); continue; } Monitor.Exit(_archivedFiles); // First check if this file is in the MCEBuddyArchive directory (which contains converted files, if so skip them) if (Path.GetDirectoryName(foundFile).ToLower().Contains((string.IsNullOrEmpty(MCEBuddyConf.GlobalMCEConfig.GeneralOptions.archivePath) ? GlobalDefs.MCEBUDDY_ARCHIVE.ToLower() : MCEBuddyConf.GlobalMCEConfig.GeneralOptions.archivePath.ToLower()))) { Monitor.Enter(_archivedFiles); _archivedFiles.Add(foundFile); // add to the list Monitor.Exit(_archivedFiles); Log.AppLog.WriteEntry(this, "File " + foundFile + " has been converted and archived, skipping", Log.LogEntryType.Debug); continue; } // 1st Level Pattern Check - Found a file, filename pattern match from monitor task if (Util.Text.WildcardRegexPatternMatch(Path.GetFileName(foundFile), monitorTask.searchPattern)) // Check pattern match for Monitor Locations filter { // Take a lock here for EACH file before processing and modifying queue // This is done per file and not for all files so that we don't lock up the engine interfaces which need the same lock to respond to GUI queries // CheckAndAddFile is very intensive time consuming process for each file as it extracts metadata and compares filters // Also EnumerateFiles is a very intensive process for very large nested and remote directories which will lock up the thread if the lock is taken Monitor.Enter(Queue); // Take a lock on the queue before modifying the queue CheckAndAddFile(foundFile, monitorTask, false); // Check history, monitor and conversion task filters and creates conversion job for file Monitor.Exit(Queue); // Release the lock on the queue after modifying the queue } else // File type mismatch, log and keep track of them for each monitor task processed { Monitor.Enter(_monitorTaskFilterMismatchFiles); // Make it thread safe if (!_monitorTaskFilterMismatchFiles.ContainsKey(foundFile)) /// Check if this file does not have a key, then create one _monitorTaskFilterMismatchFiles.Add(foundFile, new List<string>()); // Make a new key for the file _monitorTaskFilterMismatchFiles[foundFile].Add(monitorTask.taskName); // Add this task for the file as a filter mismatch Monitor.Exit(_monitorTaskFilterMismatchFiles); Log.AppLog.WriteEntry(this, "File " + Path.GetFileName(foundFile) + " did not match wildcard" + " " + monitorTask.searchPattern + " for monitor task " + monitorTask.taskName, Log.LogEntryType.Debug); } } } else Log.AppLog.WriteEntry("No accessible files founds in location " + monitorTask.searchPath + " for monitor task " + monitorTask.taskName, Log.LogEntryType.Information); } catch (Exception ex) { Log.AppLog.WriteEntry("Unable to search for files in location " + monitorTask.searchPath + " for monitor task " + monitorTask.taskName + "\r\nERROR : " + ex.Message, Log.LogEntryType.Warning); foundFiles = null; try { Monitor.Exit(Queue); } // Release queue lock catch { } try { Monitor.Exit(_monitorTaskFilterMismatchFiles); } // Release monitor mismatch list lock catch { } try { Monitor.Exit(_archivedFiles); } // Release archived list lock catch { } } } // Read the manual queue - manual selections are always first Ini iniQueue = new Ini(GlobalDefs.ManualQueueFile); SortedList<string, string> manualQueue = iniQueue.GetSectionKeyValuePairs("ManualQueue"); foreach (KeyValuePair<string, string> manualFile in manualQueue) { string filePath = manualFile.Value; // Due to INI restriction only the Value can hold special characters like ];= which maybe contained in the filename, hence the Value is used to capture the "TRUE" filename (Key and Section have restrictions) if (GlobalDefs.Shutdown) // Check for a shutdown command, this can be an intensive loop return; // Exit - we're done here // Check for valid entry if (String.IsNullOrWhiteSpace(filePath)) continue; string destinationPath = Path.GetDirectoryName(filePath); if (String.IsNullOrWhiteSpace(destinationPath)) // check for a null directory name here (happens with some root level network paths) { iniQueue.DeleteSection(filePath); // Remove the key from the manual file continue; } // Connect network drives if needed for each manual entry GeneralOptions go = MCEBuddyConf.GlobalMCEConfig.GeneralOptions; if (Util.Net.IsUNCPath(destinationPath)) { if (!String.IsNullOrWhiteSpace(go.userName)) { ConnectNet(destinationPath, go.domainName, go.userName, go.password); } else { Log.AppLog.WriteEntry("No network authentication username found, defaulting to Guest authentication", Log.LogEntryType.Warning); ConnectNet(destinationPath, "", GlobalDefs.DEFAULT_NETWORK_USERNAME, ""); } } if (Directory.Exists(filePath)) // After we are connected, check if the target is a directory (accidentally happens), we don't process it { Log.AppLog.WriteEntry("Manually selected file " + filePath + " is a directory, skipping", Log.LogEntryType.Warning); iniQueue.DeleteSection(filePath); // Remove the key from the manual file continue; } if (!File.Exists(filePath)) // After we are connected, check if the file actually exists { Log.AppLog.WriteEntry("Manually selected file " + filePath + " does not exist, skipping", Log.LogEntryType.Warning); iniQueue.DeleteSection(filePath); // Remove the key from the manual file continue; } Log.AppLog.WriteEntry("Manually selected file " + filePath + " added to queue", Log.LogEntryType.Debug); // Take a lock here for EACH file before processing and modifying queue // This is done per file and not for all files so that we don't lock up the engine interfaces which need the same lock to respond to GUI queries // CheckAndAddFile is very intensive time consuming process for each file as it extracts metadata and compares filters try { Monitor.Enter(Queue); // Take a lock on the queue before modifying the queue CheckAndAddFile(filePath, null, true); Monitor.Exit(Queue); // Release the lock on the queue after modifying the queue } catch (Exception ex) { Log.AppLog.WriteEntry("Add manual files terminated.\r\nERROR : " + ex.Message, Log.LogEntryType.Warning); try { Monitor.Exit(Queue); } // Release queue lock catch { } } } }
public Dictionary<string, SortedList<string, string>> GetConversionHistory() { Ini historyIni = new Ini(GlobalDefs.HistoryFile); Dictionary<string, SortedList<string, string>> retVal = new Dictionary<string,SortedList<string,string>>(); try { List<string> fileNames = historyIni.GetSectionNames(); foreach (string filePath in fileNames) { try { SortedList<string, string> entries = historyIni.GetSectionKeyValuePairs(filePath); retVal.Add(filePath, entries); // Add the file and the entries for the file } catch (Exception e1) { Log.AppLog.WriteEntry(this, "Error processing history file section entry -> " + filePath + "\r\nError -> " + e1.ToString(), Log.LogEntryType.Error, true); } } } catch (Exception e) { Log.AppLog.WriteEntry(this, "Unable to get History file entries.\r\nError -> " + e.ToString(), Log.LogEntryType.Error, true); } return retVal; }