You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
399 lines
11 KiB
399 lines
11 KiB
using UnityEngine; |
|
using FFmpeg; |
|
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Text; |
|
|
|
/// <summary> |
|
/// Entry point for FFmpeg |
|
/// </summary> |
|
public static class FFmpegCommands |
|
{ |
|
//Do not call this |
|
static FFmpegWrapper w; |
|
//Call this ------------| |
|
// | |
|
// \|/ |
|
static FFmpegWrapper Wrapper |
|
{ |
|
get |
|
{ |
|
if (w == null) |
|
{ |
|
w = MonoBehaviour.FindObjectOfType<FFmpegWrapper>(); |
|
if (w == null) |
|
Debug.LogException(new Exception("Place a FFmpeg.prefab in the scene")); |
|
} |
|
return w; |
|
} |
|
} |
|
//Data (instructions) |
|
public const char SEPARATOR = ' '; |
|
public const char QUOTE = '\''; |
|
public const char DOUBLE_QUOTE = '\"'; |
|
public const string VERSION_INSTRUCTION = "-version"; |
|
public const string REWRITE_INSTRUCTION = "-y"; |
|
public const string INPUT_INSTRUCTION = "-i"; |
|
public const string INDEX_PREFIX_INSTRUCTION = "%"; |
|
public const string INDEX_SUFIX_INSTRUCTION = "d"; |
|
public const string RESIZE_INSTRUCTION = "-r"; |
|
public const string SS_INSTRUCTION = "-ss"; |
|
public const string CODEC_INSTRUCTION = "-codec"; |
|
public const string C_CODEC_INSTRUCTION = "-c"; |
|
public const string COPY_INSTRUCTION = "copy"; |
|
public const string TIME_INSTRUCTION = "-t"; |
|
public const string CODEC_VIDEO_INSTRUCTION = "-c:v"; |
|
public const string CODEC_AUDIO_INSTRUCTION = "-c:a"; |
|
public const string LIB_X264_INSTRUCTION = "libx264"; |
|
public const string CONSTANT_RATE_FACTOR_INSTRUCTION = "-crf"; |
|
public const string FILE_FORMAT_INPUT_INSTRUCTION = "-f"; |
|
public const string CONCAT_INSTRUCTION = "concat"; |
|
public const string SAFE_INSTRUCTION = "-safe"; |
|
public const string ZERO_INSTRUCTION = "0"; |
|
public const string FILTER_COMPLEX_INSTRUCTION = "-filter_complex"; |
|
public const string MAP_INSTRUCTION = "-map"; |
|
public const string PRESET_INSTRUCTION = "-preset"; |
|
public const string VIDEO_INSTRUCTION = "[v]"; |
|
public const string AUDIO_INSTRUCTION = "[a]"; |
|
public const string ULTRASAFE_INSTRUCTION = "ultrafast"; |
|
public const string VIDEO_FORMAT = "[{0}:v:0] "; |
|
public const string AUDIO_FORMAT = "[{0}:a:0] "; |
|
public const string CONCAT_FORMAT = "{0}=n={1}:v=1:a=1"; |
|
public const string FIRST_INPUT_VIDEO_CHANNEL = "0:v"; |
|
public const string SECOND_INPUT_AUDIO_CHANNEL = "1:a"; |
|
public const string SHORTEST_INSTRUCTION = "-shortest"; |
|
public const string PIXEL_FORMAT = "-pix_fmt"; |
|
public const string YUV_420P = "yuv420p"; |
|
|
|
//------------------------------ |
|
|
|
public static void GetVersion() |
|
{ |
|
Wrapper.Execute(new string[] { VERSION_INSTRUCTION }); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void Convert(BaseData config) |
|
{ |
|
//-y -i .../input.mp4 .../output.mp3 |
|
string[] command = |
|
{ |
|
REWRITE_INSTRUCTION, |
|
INPUT_INSTRUCTION, |
|
config.inputPath, |
|
config.outputPath |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void Trim(TrimData config) |
|
{ |
|
//-y -i .../input.mp4 -ss 00:00:50.0 -codec copy -t 20 .../output.mp4 |
|
string[] command = |
|
{ |
|
REWRITE_INSTRUCTION, |
|
INPUT_INSTRUCTION, |
|
config.inputPath, |
|
SS_INSTRUCTION, |
|
config.fromTime, |
|
CODEC_INSTRUCTION, |
|
COPY_INSTRUCTION, |
|
TIME_INSTRUCTION, |
|
config.durationSec.ToString(), |
|
config.outputPath |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void Decode(DecodeEncodeData config) |
|
{ |
|
//-y -i .../video.mp4 -r 30 .../image%1d.jpg .../track.mp3 |
|
string[] command = |
|
{ |
|
REWRITE_INSTRUCTION, |
|
INPUT_INSTRUCTION, |
|
config.inputPath, |
|
RESIZE_INSTRUCTION, |
|
config.fps.ToString(), |
|
config.outputPath, |
|
config.soundPath |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void Encode(DecodeEncodeData config) |
|
{ |
|
//-y -i .../image%1d.jpg -r 30 -i .../track.mp3 -pix_fmt yuv420p .../video.mp4 |
|
string[] command = |
|
{ |
|
REWRITE_INSTRUCTION, |
|
INPUT_INSTRUCTION, |
|
config.inputPath, |
|
RESIZE_INSTRUCTION, |
|
config.fps.ToString(), |
|
INPUT_INSTRUCTION, |
|
config.soundPath, |
|
PIXEL_FORMAT, |
|
YUV_420P, |
|
config.outputPath |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void Compress(CompressionData config) |
|
{ |
|
//-i .../input.mp4 -c:v libx264 -crf 23 .../output.mp4 -y |
|
string[] command = |
|
{ |
|
INPUT_INSTRUCTION, |
|
config.inputPath, |
|
CODEC_VIDEO_INSTRUCTION, |
|
LIB_X264_INSTRUCTION, |
|
CONSTANT_RATE_FACTOR_INSTRUCTION, |
|
config.crf.ToString(), |
|
config.outputPath, |
|
REWRITE_INSTRUCTION |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void AppendFast(AppendData config) |
|
{ |
|
//-f concat -safe 0 -i .../mylist.txt -c copy .../output.mp4 -y |
|
string[] command = |
|
{ |
|
FILE_FORMAT_INPUT_INSTRUCTION, |
|
CONCAT_INSTRUCTION, |
|
SAFE_INSTRUCTION, |
|
ZERO_INSTRUCTION, |
|
INPUT_INSTRUCTION, |
|
GetInputsFile(config.inputPaths), |
|
C_CODEC_INSTRUCTION, |
|
COPY_INSTRUCTION, |
|
config.outputPath, |
|
REWRITE_INSTRUCTION |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
static string GetInputsFile(List<string> inputPaths) |
|
{ |
|
string inputFolder = Path.GetDirectoryName( |
|
//Remove standalone style |
|
inputPaths[0].Replace("\"", string.Empty)); |
|
|
|
StringBuilder fileBuffer = new StringBuilder(); |
|
fileBuffer.Append("# File with input videos\n"); |
|
foreach(string ip in inputPaths) |
|
{ |
|
//Relative to input folder |
|
string inputPath = ip.Remove(0, inputFolder.Length); |
|
//Remove standalone style |
|
inputPath = ip.Replace("\"", string.Empty); |
|
|
|
fileBuffer.Append("file '" + inputPath + "'\n"); |
|
} |
|
|
|
string filePath = Path.Combine(inputFolder, "AppendInputFiles.txt"); |
|
using (FileStream fileStream = File.Create(filePath)) |
|
{ |
|
byte[] buffer = |
|
new UTF8Encoding(true).GetBytes(fileBuffer.ToString()); |
|
fileStream.Write(buffer, 0, buffer.Length); |
|
} |
|
#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_EDITOR |
|
filePath = DOUBLE_QUOTE + filePath + DOUBLE_QUOTE; |
|
#endif |
|
Debug.Log("FilePath: " + filePath); |
|
return filePath; |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void AppendFull(AppendData config) |
|
{ |
|
//ffmpeg - i .../input1.mp4 - i .../input2.webm \ |
|
//-filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] concat=n=2:v=1:a=1 [v] [a]" \ |
|
//-map "[v]" - map "[a]" < encoding options > .../output.mkv -y |
|
List<string> cmd = new List<string>(); |
|
StringBuilder filter = new StringBuilder(); |
|
#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_EDITOR |
|
filter.Append(DOUBLE_QUOTE); |
|
#endif |
|
for (int i = 0; i < config.inputPaths.Count; ++i) |
|
{ |
|
cmd.Add(INPUT_INSTRUCTION); |
|
cmd.Add(config.inputPaths[i]); |
|
|
|
filter.Append(string.Format(VIDEO_FORMAT, i)).Append(string.Format(AUDIO_FORMAT, i)); |
|
} |
|
|
|
filter. |
|
Append(string.Format(CONCAT_FORMAT, CONCAT_INSTRUCTION, config.inputPaths.Count)). |
|
Append(SEPARATOR). |
|
Append(VIDEO_INSTRUCTION). |
|
Append(SEPARATOR). |
|
Append(AUDIO_INSTRUCTION); |
|
#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_EDITOR |
|
filter.Append(DOUBLE_QUOTE); |
|
#endif |
|
|
|
cmd.Add(FILTER_COMPLEX_INSTRUCTION); |
|
cmd.Add(filter.ToString()); |
|
cmd.Add(MAP_INSTRUCTION); |
|
cmd.Add(VIDEO_INSTRUCTION); |
|
cmd.Add(MAP_INSTRUCTION); |
|
cmd.Add(AUDIO_INSTRUCTION); |
|
cmd.Add(PRESET_INSTRUCTION); |
|
cmd.Add(ULTRASAFE_INSTRUCTION); |
|
cmd.Add(config.outputPath); |
|
cmd.Add(REWRITE_INSTRUCTION); |
|
|
|
string[] command = cmd.ToArray(); |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void AddSoundFast(SoundData config) |
|
{ |
|
//aac is compatible with mp4 - no need full re-encoding. |
|
//-y -i .../input.mp4 -i .../audio.aac -c copy -map 0:v -map 1:a -shortest .../output.mp4 |
|
string[] command = |
|
{ |
|
REWRITE_INSTRUCTION, |
|
INPUT_INSTRUCTION, |
|
config.inputPath, |
|
INPUT_INSTRUCTION, |
|
config.soundPath, |
|
C_CODEC_INSTRUCTION, |
|
COPY_INSTRUCTION, |
|
MAP_INSTRUCTION, |
|
FIRST_INPUT_VIDEO_CHANNEL, |
|
MAP_INSTRUCTION, |
|
SECOND_INPUT_AUDIO_CHANNEL, |
|
SHORTEST_INSTRUCTION, |
|
config.outputPath |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void AddSoundFull(SoundData config) |
|
{ |
|
//mp3 is does not compatible with mp4. Full re-encoding handles this. |
|
//-y -i .../audio.mp3 -i .../input.mp4 .../output.mp4 |
|
string[] command = |
|
{ |
|
REWRITE_INSTRUCTION, |
|
INPUT_INSTRUCTION, |
|
config.soundPath, |
|
INPUT_INSTRUCTION, |
|
config.inputPath, |
|
config.outputPath |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void Watermark(WatermarkData config) |
|
{ |
|
//-y -i .../watermark.png -i .../input.mp4 -filter_complex \ |
|
//"[0:v]scale=iw*0.25:ih*0.25 [ovrl], [1:v][ovrl]overlay=x=(main_w-overlay_w)*0.95:y=(main_h-overlay_h)*0.05" \ |
|
//FFmpegUnityBindDemo.mp4 |
|
StringBuilder filter = new StringBuilder(); |
|
#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_EDITOR |
|
filter.Append(DOUBLE_QUOTE); |
|
#endif |
|
filter. |
|
Append("[0:v]scale=iw*"). |
|
Append(config.imageScale). |
|
Append(":ih*"). |
|
Append(config.imageScale). |
|
Append(" [ovrl], [1:v][ovrl]overlay=x=(main_w-overlay_w)*"). |
|
Append(config.xPosNormal). |
|
Append(":y=(main_h-overlay_h)*"). |
|
Append(config.yPosNormal); |
|
#if UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_EDITOR |
|
filter.Append(DOUBLE_QUOTE); |
|
#endif |
|
|
|
string[] command = |
|
{ |
|
REWRITE_INSTRUCTION, |
|
INPUT_INSTRUCTION, |
|
config.imagePath, |
|
INPUT_INSTRUCTION, |
|
config.inputPath, |
|
FILTER_COMPLEX_INSTRUCTION, |
|
filter.ToString(), |
|
config.outputPath |
|
}; |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
public static void DirectInput(string input) |
|
{ |
|
string[] command = input.Split(' '); |
|
|
|
DebugCommand(command); |
|
|
|
Wrapper.Execute(command); |
|
} |
|
|
|
//------------------------------ |
|
|
|
static void DebugCommand(string[] command) |
|
{ |
|
StringBuilder debugCommand = new StringBuilder(); |
|
foreach (string instruction in command) |
|
debugCommand.Append(instruction + " "); |
|
Debug.Log(debugCommand.ToString()); |
|
} |
|
} |