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;
}
}