OdessaGUIProject.Workers.SaveWorker.SpliceVideo C# (CSharp) Method

SpliceVideo() private method

Splices out a new highlight video and saves it to the OutputDirectory
private SpliceVideo ( string outputFilePath, bool isReencoding = false ) : FileInfo
outputFilePath string
isReencoding bool Refers to whether we should re-encode. Needed because some formats need to be re-encoded.
return System.IO.FileInfo
        internal FileInfo SpliceVideo(string outputFilePath, bool isReencoding = false)
        {
            if (HideOutputFile)
                Logger.Info(Id + ": Called with isReencoding=" + isReencoding);
            else
                Logger.Info(Id + ": Called with outputFilePath=" + outputFilePath + " and isReencoding=" + isReencoding);

            if (MainModel.IsAuthorized() == false)
            {
                Logger.Error("Highlight Hunter Engine Not Authorized!");
                ErrorMessage = "Corrupt installation";
                PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                return null;
            }

            if (Math.Abs(HighlightObject.InputFileObject.FramesPerSecond - 0) < Double.Epsilon)
            {
                Logger.Error("FramesPerSecond equals zero!");
                ErrorMessage = "Could not retrieve frame rate of video.";
                PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                return null;
            }

            if (HighlightObject.InputFileObject.Bitrate == 0)
            {
                Logger.Error("Bitrate equals zero!");
                ErrorMessage = "Could not retrieve bitrate of video.";
                PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                return null;
            }

            var isWatermarked = false;

            #if !TEST

            #region Look up activation status

            if (activationState == Protection.ActivationState.Unlicensed ||
                activationState == Protection.ActivationState.TrialExpired)
                isWatermarked = true;

            #endregion Look up activation status
            #else
            isWatermarked = ForceWatermark;
            #endif

            var start = HighlightObject.StartTime;

            var end = HighlightObject.EndTime;

            var duration = end - start;

            #if !TEST
            if (activationState == Protection.ActivationState.Activated || activationState == Protection.ActivationState.Trial)
            {
                string ssAsString = start.Hours.ToString("00", CultureInfo.InvariantCulture) + ":" + start.Minutes.ToString("00", CultureInfo.InvariantCulture) + ":" +
                                start.Seconds.ToString("00", CultureInfo.InvariantCulture);
                string endAsString = end.Hours.ToString("00", CultureInfo.InvariantCulture) + ":" + end.Minutes.ToString("00", CultureInfo.InvariantCulture) + ":" +
                                     end.Seconds.ToString("00", CultureInfo.InvariantCulture);

                Logger.Info("Splicing from " + ssAsString + " to " + endAsString);
            }
            #endif

            if (!HideOutputFile)
                Logger.Info("Writing to: " + outputFilePath);

            // set working together to temp outputPath so the OdessaWatermark works
            var splicingProcess = new Process
                                      {
                                          StartInfo =
                                              {
                                                  FileName = MainModel.GetPathToFFmpeg(),
                                                  WorkingDirectory = Path.GetTempPath(),
                                              }
                                      };

            splicingProcess.StartInfo.Arguments =
                "-ss " + start.TotalSeconds.ToString(CultureInfo.InvariantCulture) + " " +
                "-t " + duration.TotalSeconds.ToString(CultureInfo.InvariantCulture) + " " +
                "-y " + // -y overwrites output
                "-i \"" + HighlightObject.InputFileObject.SourceFileInfo.FullName + "\" " +
                "-threads 3 " +
                "-acodec copy " +
                "-copyts " + // Copy timestamps from input to output. (helps with audio syncing)
                "-copytb -1 "; // Specify how to set the encoder timebase when stream copying. mode is an integer numeric value, and can assume one of the following values: -1 Try to make the choice automatically, in order to generate a sane output.

            ReportProgress(1);

            string watermarkPath = Path.Combine(Path.GetTempPath(), "OdessaWatermark" + Id + ".png"); // Include Id because we may be saving multiple files at the same time
            if (isWatermarked && OutputFormat != SaveWorker.OutputFormats.Facebook)
            {
                #region Save watermark to disk

                try
                {
                    SaveWatermarkToDisk(HighlightObject.InputFileObject.VideoWidth, HighlightObject.InputFileObject.VideoHeight, watermarkPath);
                }
                catch (Exception ex)
                {
                    // the user might have done something to prevent the watermark from being saved
                    Logger.Error("Exception while saving watermark to disk: " + ex);

                    #region delete temporary watermark file on disk

                    if (File.Exists(watermarkPath))
                    {
                        try
                        {
                            File.Delete(watermarkPath);
                        }
                        catch
                        {
                            Debug.Assert(true, "Couldn't delete watermark outputPath at " + watermarkPath);
                        }
                    }

                    #endregion delete temporary watermark file on disk

                    ErrorMessage = "Corrupt installation.";
                    PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                    return null;
                }
                if (File.Exists(watermarkPath) == false)
                {
                    Logger.Error("Watermark was not saved to disk!");
                    ErrorMessage = "Corrupt installation.";
                    PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                    return null;
                }

                #endregion Save watermark to disk

                splicingProcess.StartInfo.Arguments +=
                    "-vf \"movie=OdessaWatermark" + Id + ".png [watermark]; [in][watermark]overlay=0:0 [out]\" ";
            }

            ReportProgress(2);

            #region Figure out -vcodec, -b, and -r param

            var needsSpecificBitrateAndFPS = true;

            var frameRate = HighlightObject.InputFileObject.FramesPerSecond;
            switch (OutputFormat)
            {
                case OutputFormats.Original:
                    if (!isWatermarked && !isReencoding)
                    {
                        needsSpecificBitrateAndFPS = false;
                        splicingProcess.StartInfo.Arguments += "-vcodec copy ";
                    }
                    else
                    {
                        needsSpecificBitrateAndFPS = true;
                        // don't need to specify vcodec because ffmpeg will use extension of file
                    }
                    break;

                case OutputFormats.Facebook:
                    needsSpecificBitrateAndFPS = true;
                    // according to https://www.facebook.com/help/?faq=124738474272230, we should keep:

                    // encode to H.264
                    // ffmpeg doesn't have an LGPL h.264 encoder so we'll encode to MPEG4 part 2 instead
                    // TODO: use GPL h.264 encoder instead?
                    splicingProcess.StartInfo.Arguments += "-vcodec libx264 ";

                    // FPS to 30 or under
                    if (frameRate > 30)
                        frameRate = 30;

                    // keep max dimension to 1280
                    var videoHeight = HighlightObject.InputFileObject.VideoHeight;
                    var videoWidth = HighlightObject.InputFileObject.VideoWidth;
                    var isResized = false;
                    if (videoWidth > 1280)
                    {
                        videoHeight = (int)(videoHeight * 1280.0 / videoWidth);
                        videoWidth = 1280;
                        isResized = true;
                    }
                    if (videoHeight > 1280)
                    {
                        videoWidth = (int)(videoWidth * 1280.0 / videoHeight);
                        videoHeight = 1280;
                        isResized = true;
                    }

                    // make sure height and width are always divisible by 2
                    if (videoWidth % 2 > 0)
                        videoWidth -= 1;
                    if (videoHeight % 2 > 0)
                        videoHeight -= 1;

                    if (isResized)
                        splicingProcess.StartInfo.Arguments += "-s " + videoWidth.ToString(CultureInfo.InvariantCulture) + "x" + videoHeight.ToString(CultureInfo.InvariantCulture) + " ";
                    break;

                case OutputFormats.ProRes:
                    needsSpecificBitrateAndFPS = false;
                    splicingProcess.StartInfo.Arguments += "-vcodec prores ";
                    break;
            }

            if (needsSpecificBitrateAndFPS)
                splicingProcess.StartInfo.Arguments +=
                    "-r " + frameRate.ToString("0.00", CultureInfo.InvariantCulture) +
                    " -b:v " + HighlightObject.InputFileObject.Bitrate.ToString("0", CultureInfo.InvariantCulture) + " ";

            #endregion Figure out -vcodec, -b, and -r param

            #region Verify validity of outputFilePath

            if (OutputFormat == OutputFormats.ProRes)
            { // make sure it has .MOV extension
                if (Path.GetExtension(outputFilePath).ToUpperInvariant() != ".MOV")
                    outputFilePath += ".MOV";
            }

            #endregion

            splicingProcess.StartInfo.Arguments += "\"" + outputFilePath + "\"";
            splicingProcess.StartInfo.UseShellExecute = false;
            splicingProcess.StartInfo.ErrorDialog = false;

            splicingProcess.StartInfo.RedirectStandardError = true;
            splicingProcess.StartInfo.CreateNoWindow = true;
            splicingProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

            splicingProcess.ErrorDataReceived += SplicingProcessOutputDataReceived;

            FfmpegArguments = splicingProcess.StartInfo.Arguments;

            if (CheckForCancelledWorker()) return null;

            if (!isWatermarked && !HideOutputFile)
                Logger.Info("Calling ffmpeg with arguments: " + splicingProcess.StartInfo.FileName + " " +
                    splicingProcess.StartInfo.Arguments);

            #if DEBUG
            Logger.Info("Calling ffmpeg with arguments: " + splicingProcess.StartInfo.FileName + " " +
                splicingProcess.StartInfo.Arguments);
            #endif

            try
            {
                splicingProcess.Start();
                //SplicingProcess.PriorityClass = ProcessPriorityClass.BelowNormal;

                ReportProgress(3);

                splicingProcess.BeginErrorReadLine();
                while (true)
                {
                    if (CheckForCancelledWorker())
                    {
                        splicingProcess.Kill();
                        break;
                    }

                    if (splicingProcess.WaitForExit(100))
                    {
                        break;
                    }
                }

                ReportProgress(100);

                Logger.Info("Ffmpeg finished");

            }
            catch (Exception ex)
            {
                Logger.Error("Exception running ffmpeg process! " + ex);
                ErrorMessage = "Could not start splicing process. " + ex;
                PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                return null;
            }

            #region delete temporary watermark file on disk

            if (File.Exists(watermarkPath))
            {
                try
                {
                    File.Delete(watermarkPath);
                }
                catch
                {
                    Debug.Assert(true, "Couldn't delete watermark outputPath at " + watermarkPath);
                }
            }

            #endregion delete temporary watermark file on disk

            if (CheckForCancelledWorker()) return null;

            if (File.Exists(outputFilePath))
            {
                try
                {
                    var outputFileInfo = new FileInfo(outputFilePath);

                    // see if it's a valid output file. if the file is under 1mb, something is wrong.
                    // this may not be the best way to detect a bad output file. a better way might be to read ffmpeg output
                    if (IsValidVideo(outputFileInfo))
                    {
                        PublishWorkerResult = PublishWorkerResults.Success;
                        return outputFileInfo;
                    }
                    else
                    {
                        if (!isReencoding)
                        {
                            Logger.Info("Output file doesn't seem valid. Let's try reencoding.");
                            outputFileInfo = SpliceVideo(outputFilePath, isReencoding: true);
                            if (!IsValidVideo(outputFileInfo))
                            {
                                Logger.Info("Even re-encoding didn't work. Something else is wrong!");
                                PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                                ErrorMessage = "Error while saving video";
                                return null;
                            }
                            else
                            {   // PublishWorkerResult would be set by second SpliceVideo call
                                return outputFileInfo;
                            }
                        }
                        else
                        { // we have no other ways to do this
                            Logger.Error("Reencoding failed as well");
                            PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                            ErrorMessage = "Error while saving video";
                            return null;
                        }
                    }
                }
                catch (Exception ex)
                {
                    if (!HideOutputFile)
                        Logger.Error("Exception returning OutputFilePath of " + outputFilePath + ": " + ex);

                    ErrorMessage = "Could not fetch OutputFilePath";
                    PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                    return null;
                }
            }
            else
            {
                Logger.Error("Output file not found");
                ErrorMessage = "Could not fetch OutputFilePath";
                PublishWorkerResult = PublishWorkerResults.UnableToSplice;
                return null;
            }
        }