public void RunGraph() { DateTime lastTick = DateTime.Now; int hangPeriod = MCEBuddyConf.GlobalMCEConfig.GeneralOptions.hangTimeout; long lastVideoSize = 0, lastAudioSize = 0, lastSubtitleSize = 0; long totalPartsSize = 0, sourceSize = 0; bool AbortError = false; int hr = 0; IMediaControl mediaControl = (IMediaControl)_fg; IMediaEvent mediaEvent = (IMediaEvent)_fg; hr = mediaControl.Run(); checkHR(hr); // Change the priority temporarily (need to reset it back after Dumping Streams) ProcessPriorityClass lastPriority = GlobalDefs.Priority; // Set it up Process.GetCurrentProcess().PriorityClass = GlobalDefs.Priority; // Set the CPU Priority IOPriority.SetPriority(GlobalDefs.IOPriority); // First set the CPU priority // Get filesize of source file sourceSize = Util.FileIO.FileSize(_SourceFile); // Sanity checking if (sourceSize <= 0) { _jobLog.WriteEntry(this, "Unable to get source file size, disabling infinite loop checking.", Log.LogEntryType.Warning); hangPeriod = 0; } bool stop = false, isSuspended = false; while (!stop) { System.Threading.Thread.Sleep(100); if (_jobStatus.Cancelled) { // Received a shutdown command external to the filter extraction _jobLog.WriteEntry(this, "Stream extraction cancelled, aborting graph.", Log.LogEntryType.Warning); stop = true; AbortError = true; mediaControl.Stop(); break; } if (isSuspended) { lastTick = DateTime.Now; // Since during suspension there will be no output it shouldn't terminate the process } if (!isSuspended && GlobalDefs.Pause) // Check if process has to be suspended (if not already) { _jobLog.WriteEntry(this, "Stream extraction paused", Log.LogEntryType.Information); mediaControl.Pause(); isSuspended = true; } if (isSuspended && !GlobalDefs.Pause) // Check if we need to resume the process { _jobLog.WriteEntry(this, "Stream extraction resumed", Log.LogEntryType.Information); isSuspended = false; mediaControl.Run(); } if (lastPriority != GlobalDefs.Priority) // Check if the priority was changed and if so update it { _jobLog.WriteEntry(this, "Stream extraction priority changed", Log.LogEntryType.Information); lastPriority = GlobalDefs.Priority; Process.GetCurrentProcess().PriorityClass = GlobalDefs.Priority; // Set the CPU Priority IOPriority.SetPriority(GlobalDefs.IOPriority); // First set the CPU priority } EventCode ev; IntPtr p1, p2; if (mediaEvent.GetEvent(out ev, out p1, out p2, 0) == 0) { if (ev == EventCode.Complete) { mediaControl.Stop(); stop = true; } else if (ev == EventCode.ErrorAbort || ev == EventCode.UserAbort || ev == EventCode.StErrStopped || ev == EventCode.ErrorAbortEx) { mediaControl.Stop(); stop = true; //AbortError = true; - some partial/corrupted files are errored out, we'll handle extraction errors later } mediaEvent.FreeEventParams(ev, p1, p2); } // Sanity checking to prevent infinite loop for extraction (sometimes steams extracts infinitely) // Check if the filesize exceed the initial file size and if so abort the operation if (sourceSize > 0) { totalPartsSize = 0; // Video file check if ((_extractMediaType & ExtractMediaType.Video) != 0) { long videoSize = Util.FileIO.FileSize(_VideoPart); if (videoSize < 0) { _jobLog.WriteEntry(this, "Unable to get extracted video stream file size for infinite loop detection.", Log.LogEntryType.Warning); } else if (videoSize > (sourceSize * INFINITE_LOOP_CHECK_THRESHOLD)) { _jobLog.WriteEntry(this, "Extracted video stream is greater than " + INFINITE_LOOP_CHECK_THRESHOLD.ToString(CultureInfo.InvariantCulture) + " times the source file size " + (sourceSize / 1024).ToString("N", CultureInfo.InvariantCulture) + " [KB].\r\nExtraction likely hung, terminating streams extraction.", Log.LogEntryType.Error); stop = true; AbortError = true; mediaControl.Stop(); break; } if (hangPeriod > 0) { if (videoSize > lastVideoSize) // If we have progress { lastTick = DateTime.Now; } } totalPartsSize += videoSize; lastVideoSize = videoSize; } // Audio file check if ((_extractMediaType & ExtractMediaType.Audio) != 0) { foreach (string audioPart in _AudioParts) { long audioSize = Util.FileIO.FileSize(audioPart); if (audioSize < 0) { _jobLog.WriteEntry(this, "Unable to get extracted audio stream file size for infinite loop detection.", Log.LogEntryType.Warning); } else if (audioSize > (sourceSize * INFINITE_LOOP_CHECK_THRESHOLD)) { _jobLog.WriteEntry(this, "Extracted audio stream is greater than " + INFINITE_LOOP_CHECK_THRESHOLD.ToString(CultureInfo.InvariantCulture) + " times the source file size " + (sourceSize / 1024).ToString("N", CultureInfo.InvariantCulture) + " [KB].\r\nExtraction likely hung, terminating streams extraction.", Log.LogEntryType.Error); stop = true; AbortError = true; mediaControl.Stop(); break; } if (hangPeriod > 0) { if (audioSize > lastAudioSize) // If we have progress { lastTick = DateTime.Now; } } totalPartsSize += audioSize; lastAudioSize = audioSize; } } // Subtitle file check if ((_extractMediaType & ExtractMediaType.Subtitle) != 0) { foreach (string subtitlePart in _SubtitleParts) { long subtitleSize = Util.FileIO.FileSize(subtitlePart); if (subtitleSize < 0) { _jobLog.WriteEntry(this, "Unable to get extracted subtitle stream file size for infinite loop detection.", Log.LogEntryType.Warning); } else if (subtitleSize > (sourceSize * INFINITE_LOOP_CHECK_THRESHOLD)) { _jobLog.WriteEntry(this, "Extracted subtitle stream is greater than " + INFINITE_LOOP_CHECK_THRESHOLD.ToString(CultureInfo.InvariantCulture) + " times the source file size " + (sourceSize / 1024).ToString("N", CultureInfo.InvariantCulture) + " [KB].\r\nExtraction likely hung, terminating streams extraction.", Log.LogEntryType.Error); stop = true; AbortError = true; mediaControl.Stop(); break; } if (hangPeriod > 0) { if (subtitleSize > lastSubtitleSize) // If we have progress { lastTick = DateTime.Now; } } totalPartsSize += subtitleSize; lastSubtitleSize = subtitleSize; } } if (totalPartsSize < 0) { totalPartsSize = 0; // Incase we get -ve numbers } _jobStatus.PercentageComplete = (((float)totalPartsSize / (float)sourceSize) > 1 ? 100 : ((float)totalPartsSize / (float)sourceSize) * 100); // Calculate % complete from size estimation (since no recoding is happening) and cap at 100% _jobLog.WriteEntry(this, "Percentage complete : " + _jobStatus.PercentageComplete.ToString("0.00", CultureInfo.InvariantCulture) + " %", Log.LogEntryType.Debug); // Write to file } // Check if we have reached the end of the file or runs out of disk space, sometime windows just loops endlessly without any incremental output // TODO: Should we treat this as an error or normal processing if ((hangPeriod > 0) && (DateTime.Now > lastTick.AddSeconds(hangPeriod))) { _jobLog.WriteEntry("No response from stream extraction for " + hangPeriod + " seconds, process likely finished, continuing.", Log.LogEntryType.Warning); // Don't treat as an error for now stop = true; // AbortError = true; // Don't treat as an error for now mediaControl.Stop(); break; } } // Reset Priority to Normal IOPriority.SetPriority(GlobalDefs.EngineIOPriority); // Set CPU priority after restoring the scheduling priority Process.GetCurrentProcess().PriorityClass = GlobalDefs.EnginePriority; // Set the CPU Priority back to Above Normal (engine always runs above normal) List <string> parts = _AudioParts.Concat(_SubtitleParts).ToList(); // Create a list of all subtitle, audio parts if (!String.IsNullOrWhiteSpace(_VideoPart)) { parts.Add(_VideoPart); // add video part } _jobLog.WriteEntry(this, "Source " + _SourceFile + " filesize [KB] : " + (sourceSize / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); // Write to file foreach (string part in parts) { _jobLog.WriteEntry(this, part + " extracted filesize [KB] : " + (FileIO.FileSize(part) / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); // Write to file } _jobLog.WriteEntry(this, "Total extracted parts size [KB] : " + (totalPartsSize / 1024).ToString("N", CultureInfo.InvariantCulture), Log.LogEntryType.Debug); // Write to file if (!AbortError) { _SuccessfulExtraction = true; } }
public virtual void Run() { //_success = false; //initial state to be defined by each inheriting class depending on it's handlers, we only update error conditions in this routine Process Proc = null; DateTime ExecutionStartTime; // Last thing to check, for custom commands, this will log an error and exit if (!File.Exists(_ApplicationPath)) { _jobStatus.ErrorMsg = "Application File Not Found"; _jobLog.WriteEntry(this, _jobStatus.ErrorMsg + " : " + _ApplicationPath, Log.LogEntryType.Error); _jobStatus.PercentageComplete = 0; _success = false; return; } if (_Parameters.Length > 8192) { _jobLog.WriteEntry(this, Localise.GetPhrase("Warning - command parameters exceeding 8192 characters. This may fail on some systems such as Windows XP"), Log.LogEntryType.Warning); } // Reset any progress counters _jobStatus.PercentageComplete = 0; _jobStatus.ETA = Localise.GetPhrase("Working") + "..."; //some processes like ReMuxSupp don't update perc, put a default message // Check if job has been cancelled, needed for some long running functions called Base if (_jobStatus.Cancelled) { _jobStatus.ErrorMsg = "Job cancelled, killing process"; _jobLog.WriteEntry(this, _jobStatus.ErrorMsg, Log.LogEntryType.Error); _success = false; //process has been terminated return; } try { if (_HangPeriod > 0) { _LastTick = DateTime.Now; } _jobLog.WriteEntry(this, "Launching process " + _ApplicationPath, Log.LogEntryType.Debug); _jobLog.WriteEntry(this, "Process arguments " + _Parameters, Log.LogEntryType.Debug); if (GlobalDefs.IsEngineRunningAsService) // these params only make sense when running as a service { _jobLog.WriteEntry(this, "UI Session Admin Process : " + _uiAdminSessionProcess.ToString(), Log.LogEntryType.Debug); } //Start the process ExecutionStartTime = DateTime.Now; // Check if we need to run a UISession process - It can only be run from a service, if we are running from a non service console, we don't need this // Apps like handbrake with hardware accelleration cannot run from a non UI session in windows (even in Windows 8 there are limitations with ffmpeg and OpenCL from Session 0) due to unavailability of hardware (OpenCL) API's in NonUI sessions mode // These apps needs to be started in a UI session separately to run // Check if we are running as a Service (Session 0 non UI Interactive) and if the user forcing use of UI Session 1 bool checkUICompliance = (GlobalDefs.IsEngineRunningAsService && _uiAdminSessionProcess); if (checkUICompliance) { _jobLog.WriteEntry(this, "Starting process as a UISession process with Admin privileges. This requires atleast 1 user to be logged into the system (remote desktop or locally)", Log.LogEntryType.Debug); uint procId = AppProcess.StartAppWithAdminPrivilegesFromNonUISession(_ApplicationPath, _Parameters, true, OutputHandler, _showWindow, _jobLog); if (procId == 0) { _jobLog.WriteEntry(this, "Unable to create UI Session process with Admin Privileges from NonUI Session. Is any user logged on?", Log.LogEntryType.Warning); _jobLog.WriteEntry(this, "Retrying process creation as a NonUI Session process with Admin privileges", Log.LogEntryType.Warning); _jobLog.WriteEntry(this, "Some functions like hardware encoding may not work in this mode", Log.LogEntryType.Warning); Proc = null; } else { Proc = Process.GetProcessById((int)procId); // Get the process identifier } } // Create the process if it hasn't already been created if (Proc == null) { //Set up the process Proc = new Process(); Proc.StartInfo.FileName = _ApplicationPath; Proc.StartInfo.Arguments = _Parameters; if (_showWindow) { Proc.StartInfo.CreateNoWindow = false; // for custom apps we create a window Proc.StartInfo.WindowStyle = ProcessWindowStyle.Normal; } else { Proc.StartInfo.CreateNoWindow = true; Proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; } Proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(_ApplicationPath); Proc.StartInfo.RedirectStandardOutput = true; Proc.StartInfo.RedirectStandardError = true; Proc.StartInfo.UseShellExecute = false; // always false else error handlers break Proc.OutputDataReceived += new DataReceivedEventHandler(OutputHandler); Proc.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler); Proc.Start(); // First start the process else we get an exception Proc.BeginOutputReadLine(); Proc.BeginErrorReadLine(); } } catch (Exception Ex) { _jobStatus.ErrorMsg = "Unable to start process"; _jobLog.WriteEntry(this, _jobStatus.ErrorMsg + "\r\n" + Ex.Message, Log.LogEntryType.Error); _success = false; //process did not start return; // Exit now or we get an exception } // NOTE: Do all this after starting the process outside the catch block. Sometimes the process exists quickly (e.g. when process priority is IDLE) before this code runs and hence this code throws an exception try { _jobLog.WriteEntry(this, "Setting process priority to " + GlobalDefs.Priority.ToString(), Log.LogEntryType.Debug); Proc.PriorityClass = GlobalDefs.Priority; // Set the CPU Priority IOPriority.SetPriority(Proc.Handle, GlobalDefs.IOPriority); // Set the IO Priority if (MCEBuddyConf.GlobalMCEConfig.GeneralOptions.CPUAffinity != (IntPtr)0) { _jobLog.WriteEntry(this, "Setting CPU affinity to -> " + MCEBuddyConf.GlobalMCEConfig.GeneralOptions.CPUAffinity.ToString("d"), Log.LogEntryType.Debug); Proc.ProcessorAffinity = MCEBuddyConf.GlobalMCEConfig.GeneralOptions.CPUAffinity; } } catch (Exception e) { _jobLog.WriteEntry(this, "Error trying process priority or setting CPU affinity to -> " + MCEBuddyConf.GlobalMCEConfig.GeneralOptions.CPUAffinity.ToString("d") + "\n" + e.ToString(), Log.LogEntryType.Warning); } //Wait for an end while ((!Proc.HasExited)) { try { if (!_ignoreSuspend) // for some processes like ffmpegMediaInfo initiated by UI, we cannot block since it will hang the application { if (!isSuspended && GlobalDefs.Pause) // Check if process has to be suspended (if not already) { _jobLog.WriteEntry(this, "Suspending process", Log.LogEntryType.Information); IOPriority.SuspendProcess(Proc.Id); _jobLog.Flush(); // Flush the pending writes isSuspended = true; } if (isSuspended && !GlobalDefs.Pause) // Check if we need to resume the process { _jobLog.WriteEntry(this, "Resuming process", Log.LogEntryType.Information); isSuspended = false; _percentageHistory.Clear(); // Lose the history and start from here IOPriority.ResumeProcess(Proc.Id); } } if (Proc.PriorityClass != GlobalDefs.Priority) // Check if the priority was changed and if so update it { _jobLog.WriteEntry(this, "Process priority changed to " + GlobalDefs.Priority.ToString(), Log.LogEntryType.Information); Proc.PriorityClass = GlobalDefs.Priority; IOPriority.SetPriority(Proc.Handle, GlobalDefs.IOPriority); // First set the CPU priority } } catch { } //incase process exits in the background - not an issue, just avoid a crash if (_jobStatus.Cancelled) // if job has been cancelled kill the process { try { Proc.Kill(); _jobStatus.ErrorMsg = "Job cancelled, killing process"; _jobLog.WriteEntry(_jobStatus.ErrorMsg, Log.LogEntryType.Error); _success = false; //process has been terminated } catch (Exception Ex) { _jobStatus.ErrorMsg = "Job cancelled, unable to kill process"; _jobLog.WriteEntry(_jobStatus.ErrorMsg, Log.LogEntryType.Error); _jobLog.WriteEntry(Ex.Message, Log.LogEntryType.Warning); _success = false; //process has been terminated } break; } if (_unrecoverableError) { _jobLog.WriteEntry("Unrecoverable error encountered. Process likely hung, killing it", Log.LogEntryType.Error); _success = false; break; } if (isSuspended) { _LastTick = DateTime.Now; // Since during suspension there will be no output it shouldn't terminate the process } if ((_HangPeriod > 0) && (DateTime.Now > _LastTick.AddSeconds(_HangPeriod))) { _jobLog.WriteEntry("No response from process for " + _HangPeriod + " seconds, process likely hung - killing it", Log.LogEntryType.Error); _success = false; break; } System.Threading.Thread.Sleep(100); // sleep and check again } if (!_jobStatus.Cancelled) { System.Threading.Thread.Sleep(2000); //Allow the last set of messages to be written } if (!Proc.HasExited) // If we broke out of the loop, somethign bad happened { try { Proc.Kill(); _jobStatus.ErrorMsg = "Process hung, killing process"; _jobLog.WriteEntry(_jobStatus.ErrorMsg, Log.LogEntryType.Error); _success = false; //process has been terminated } catch (Exception Ex) { _jobStatus.ErrorMsg = "Unable to terminate process"; _jobLog.WriteEntry(_jobStatus.ErrorMsg + "\r\n" + Ex.Message, Log.LogEntryType.Error); _success = false; //process has been terminated } } else { // Sometimes for some reason the process exits without the output redirect async handlers completing causing failures // MSDN http://msdn.microsoft.com/en-us/library/fb4aw7b8(v=vs.110).aspx recommends using WaitForExit(x) followed by WaitForExit() to flush all output async handlers // TODO: Aysnc output is broken in .NET even with WaitForExit(), see http://alabaxblog.info/2013/06/redirectstandardoutput-beginoutputreadline-pattern-broken/ and http://stackoverflow.com/questions/9533070/how-to-read-to-end-process-output-asynchronously-in-c try { while (!Proc.WaitForExit(10)) { ; // Wait for it to return True } Proc.WaitForExit(); // Now wait for it to flush the buffers } catch { } _exitCode = Proc.ExitCode; // Store the exit code _jobLog.WriteEntry("Process exited with code " + _exitCode.ToString(), Log.LogEntryType.Debug); } _ExecutionTime = DateTime.Now - ExecutionStartTime; //Close out try { Proc.Close(); Proc.Dispose(); } catch (Exception Ex) { _jobLog.WriteEntry("Unable to cleanly close process" + "\r\n" + Ex.Message, Log.LogEntryType.Error); } Proc = null; if (!_jobStatus.Cancelled) { System.Threading.Thread.Sleep(100); //Allow for the process to be flush any pending messages } }