/// <summary> /// Converts an image sequence to a video. /// </summary> /// <param name="output">Output video file.</param> /// <param name="frameRate">FPS</param> /// <param name="images">Image sequence collection</param> /// <returns>Output video information.</returns> public VideoInfo JoinImageSequence(FileInfo output, double frameRate = 30, params ImageInfo[] images) { var temporaryImageFiles = images.Select((image, index) => { FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName)); var destinationPath = image.FullName.Replace(image.Name, $"{index.ToString().PadLeft(9, '0')}{image.Extension}"); File.Copy(image.FullName, destinationPath); return(destinationPath); }).ToList(); var firstImage = images.First(); var args = ArgumentsStringifier.FrameRate(frameRate) + ArgumentsStringifier.Size(new Size(firstImage.Width, firstImage.Height)) + ArgumentsStringifier.StartNumber(0) + ArgumentsStringifier.Input($"{firstImage.Directory}\\%09d.png") + ArgumentsStringifier.FrameOutputCount(images.Length) + ArgumentsStringifier.Video(VideoCodec.LibX264) + ArgumentsStringifier.Output(output); try { if (!RunProcess(args, output)) { throw new FFMpegException(FFMpegExceptionType.Operation, "Could not join the provided image sequence."); } return(new VideoInfo(output)); } finally { Cleanup(temporaryImageFiles); } }
/// <summary> /// Saves audio from a specific video file to disk. /// </summary> /// <param name="source">Source video file.</param> /// <param name="output">Output audio file.</param> /// <returns>Success state.</returns> public FileInfo ExtractAudio(VideoInfo source, FileInfo output) { FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output); FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp3); var args = ArgumentsStringifier.Input(source) + ArgumentsStringifier.Disable(Channel.Video) + ArgumentsStringifier.Output(output); if (!RunProcess(args, output)) { throw new FFMpegException(FFMpegExceptionType.Operation, "Could not extract the audio from the requested video."); } output.Refresh(); return(output); }
/// <summary> /// Strips a video file of audio. /// </summary> /// <param name="source">Source video file.</param> /// <param name="output">Output video file.</param> /// <returns></returns> public VideoInfo Mute(VideoInfo source, FileInfo output) { FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output); FFMpegHelper.ConversionSizeExceptionCheck(source); FFMpegHelper.ExtensionExceptionCheck(output, source.Extension); var args = ArgumentsStringifier.Input(source) + ArgumentsStringifier.Copy() + ArgumentsStringifier.Disable(Channel.Audio) + ArgumentsStringifier.Output(output); if (!RunProcess(args, output)) { throw new FFMpegException(FFMpegExceptionType.Operation, "Could not mute the requested video."); } return(new VideoInfo(output)); }
/// <summary> /// Records M3U8 streams to the specified output. /// </summary> /// <param name="uri">URI to pointing towards stream.</param> /// <param name="output">Output file</param> /// <returns>Success state.</returns> public VideoInfo SaveM3U8Stream(Uri uri, FileInfo output) { FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4); if (uri.Scheme == "http" || uri.Scheme == "https") { var args = ArgumentsStringifier.Input(uri) + ArgumentsStringifier.Output(output); if (!RunProcess(args, output)) { throw new FFMpegException(FFMpegExceptionType.Operation, $"Saving the ${uri.AbsoluteUri} stream failed."); } return(new VideoInfo(output)); } throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream."); }
/// <summary> /// Adds audio to a video file. /// </summary> /// <param name="source">Source video file.</param> /// <param name="audio">Source audio file.</param> /// <param name="output">Output video file.</param> /// <param name="stopAtShortest">Indicates if the encoding should stop at the shortest input file.</param> /// <returns>Success state</returns> public VideoInfo ReplaceAudio(VideoInfo source, FileInfo audio, FileInfo output, bool stopAtShortest = false) { FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output); FFMpegHelper.InputsExistExceptionCheck(audio); FFMpegHelper.ConversionSizeExceptionCheck(source); FFMpegHelper.ExtensionExceptionCheck(output, source.Extension); var args = ArgumentsStringifier.Input(source) + ArgumentsStringifier.Input(audio) + ArgumentsStringifier.Copy(Channel.Video) + ArgumentsStringifier.Audio(AudioCodec.Aac, AudioQuality.Hd) + ArgumentsStringifier.FinalizeAtShortestInput(stopAtShortest) + ArgumentsStringifier.Output(output); if (!RunProcess(args, output)) { throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio."); } return(new VideoInfo(output)); }
/// <summary> /// Adds a poster image to an audio file. /// </summary> /// <param name="image">Source image file.</param> /// <param name="audio">Source audio file.</param> /// <param name="output">Output video file.</param> /// <returns></returns> public VideoInfo PosterWithAudio(FileInfo image, FileInfo audio, FileInfo output) { FFMpegHelper.InputsExistExceptionCheck(image, audio); FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4); FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName)); var args = ArgumentsStringifier.Loop(1) + ArgumentsStringifier.Input(image) + ArgumentsStringifier.Input(audio) + ArgumentsStringifier.Video(VideoCodec.LibX264, 2400) + ArgumentsStringifier.Audio(AudioCodec.Aac, AudioQuality.Normal) + ArgumentsStringifier.FinalizeAtShortestInput(true) + ArgumentsStringifier.Output(output); if (!RunProcess(args, output)) { throw new FFMpegException(FFMpegExceptionType.Operation, "An error occured while adding the audio file to the image."); } return(new VideoInfo(output)); }
/// <summary> /// Saves a 'png' thumbnail from the input video. /// </summary> /// <param name="source">Source video file.</param> /// <param name="output">Output video file</param> /// <param name="captureTime">Seek position where the thumbnail should be taken.</param> /// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param> /// <returns>Bitmap with the requested snapshot.</returns> public Bitmap Snapshot(VideoInfo source, FileInfo output, Size?size = null, TimeSpan?captureTime = null) { if (captureTime == null) { captureTime = TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3); } if (output.Extension.ToLower() != FileExtension.Png) { output = new FileInfo(output.FullName.Replace(output.Extension, FileExtension.Png)); } if (size == null || (size.Value.Height == 0 && size.Value.Width == 0)) { size = new Size(source.Width, source.Height); } if (size.Value.Width != size.Value.Height) { if (size.Value.Width == 0) { var ratio = source.Width / (double)size.Value.Width; size = new Size((int)(source.Width * ratio), (int)(source.Height * ratio)); } if (size.Value.Height == 0) { var ratio = source.Height / (double)size.Value.Height; size = new Size((int)(source.Width * ratio), (int)(source.Height * ratio)); } } FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output); var thumbArgs = ArgumentsStringifier.Input(source) + ArgumentsStringifier.Video(VideoCodec.Png) + ArgumentsStringifier.FrameOutputCount(1) + ArgumentsStringifier.Seek(captureTime) + ArgumentsStringifier.Size(size) + ArgumentsStringifier.Output(output); if (!RunProcess(thumbArgs, output)) { throw new OperationCanceledException("Could not take snapshot!"); } output.Refresh(); Bitmap result; using (var bmp = (Bitmap)Image.FromFile(output.FullName)) { using (var ms = new MemoryStream()) { bmp.Save(ms, ImageFormat.Png); result = new Bitmap(ms); } } if (output.Exists) { output.Delete(); } return(result); }
/// <summary> /// Convert a video do a different format. /// </summary> /// <param name="source">Input video source.</param> /// <param name="output">Output information.</param> /// <param name="type">Target conversion video type.</param> /// <param name="speed">Conversion target speed/quality (faster speed = lower quality).</param> /// <param name="size">Video size.</param> /// <param name="audioQuality">Conversion target audio quality.</param> /// <param name="multithreaded">Is encoding multithreaded.</param> /// <returns>Output video information.</returns> public VideoInfo Convert( VideoInfo source, FileInfo output, VideoType type = VideoType.Mp4, Speed speed = Speed.SuperFast, VideoSize size = VideoSize.Original, AudioQuality audioQuality = AudioQuality.Normal, bool multithreaded = false) { FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output); FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForType(type)); FFMpegHelper.ConversionSizeExceptionCheck(source); string args = ""; var scale = VideoSize.Original == size ? 1 : (double)source.Height / (int)size; var outputSize = new Size( (int)(source.Width / scale), (int)(source.Height / scale) ); if (outputSize.Width % 2 != 0) { outputSize.Width += 1; } switch (type) { case VideoType.Mp4: args = ArgumentsStringifier.Input(source) + ArgumentsStringifier.Threads(multithreaded) + ArgumentsStringifier.Scale(outputSize) + ArgumentsStringifier.Video(VideoCodec.LibX264, 2400) + ArgumentsStringifier.Speed(speed) + ArgumentsStringifier.Audio(AudioCodec.Aac, audioQuality) + ArgumentsStringifier.Output(output); break; case VideoType.Ogv: args = ArgumentsStringifier.Input(source) + ArgumentsStringifier.Threads(multithreaded) + ArgumentsStringifier.Scale(outputSize) + ArgumentsStringifier.Video(VideoCodec.LibTheora, 2400) + ArgumentsStringifier.Speed(16) + ArgumentsStringifier.Audio(AudioCodec.LibVorbis, audioQuality) + ArgumentsStringifier.Output(output); break; case VideoType.Ts: args = ArgumentsStringifier.Input(source) + ArgumentsStringifier.Copy() + ArgumentsStringifier.BitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB) + ArgumentsStringifier.ForceFormat(VideoCodec.MpegTs) + ArgumentsStringifier.Output(output); break; } if (!RunProcess(args, output)) { throw new FFMpegException(FFMpegExceptionType.Conversion, $"The video could not be converted to {Enum.GetName(typeof(VideoType), type)}"); } return(new VideoInfo(output)); }