2022-07-20 18:10:31 +02:00
using Flowframes.Data ;
using Flowframes.Data.Streams ;
using Flowframes.IO ;
using Flowframes.MiscUtils ;
2021-08-23 16:50:18 +02:00
using System ;
using System.Collections.Generic ;
2021-10-25 15:30:47 +02:00
using System.Drawing ;
2021-08-23 16:50:18 +02:00
using System.IO ;
using System.Linq ;
2021-09-14 18:28:10 +02:00
using System.Threading.Tasks ;
2023-01-15 17:23:49 +01:00
using static Flowframes . Data . Enums . Encoding ;
2022-07-20 18:10:31 +02:00
using static Flowframes . Media . GetVideoInfo ;
using Stream = Flowframes . Data . Streams . Stream ;
2021-08-23 16:50:18 +02:00
namespace Flowframes.Media
{
class FfmpegUtils
{
2022-07-20 18:10:31 +02:00
private readonly static FfprobeMode showStreams = FfprobeMode . ShowStreams ;
private readonly static FfprobeMode showFormat = FfprobeMode . ShowFormat ;
public static async Task < int > GetStreamCount ( string path )
{
Logger . Log ( $"GetStreamCount({path})" , true ) ;
string output = await GetFfmpegInfoAsync ( path , "Stream #0:" ) ;
if ( string . IsNullOrWhiteSpace ( output . Trim ( ) ) )
return 0 ;
return output . SplitIntoLines ( ) . Where ( x = > x . MatchesWildcard ( "*Stream #0:*: *: *" ) ) . Count ( ) ;
}
2023-02-15 12:27:36 +01:00
public static async Task < List < Stream > > GetStreams ( string path , bool progressBar , int streamCount , Fraction ? defaultFps , bool countFrames )
2022-07-20 18:10:31 +02:00
{
List < Stream > streamList = new List < Stream > ( ) ;
try
{
2023-02-15 12:27:36 +01:00
if ( defaultFps = = null )
defaultFps = new Fraction ( 30 , 1 ) ;
2022-07-20 18:10:31 +02:00
string output = await GetFfmpegInfoAsync ( path , "Stream #0:" ) ;
string [ ] streams = output . SplitIntoLines ( ) . Where ( x = > x . MatchesWildcard ( "*Stream #0:*: *: *" ) ) . ToArray ( ) ;
foreach ( string streamStr in streams )
{
try
{
int idx = streamStr . Split ( ':' ) [ 1 ] . Split ( '[' ) [ 0 ] . Split ( '(' ) [ 0 ] . GetInt ( ) ;
bool def = await GetFfprobeInfoAsync ( path , showStreams , "DISPOSITION:default" , idx ) = = "1" ;
if ( progressBar )
Program . mainForm . SetProgress ( FormatUtils . RatioInt ( idx + 1 , streamCount ) ) ;
if ( streamStr . Contains ( ": Video:" ) )
{
string lang = await GetFfprobeInfoAsync ( path , showStreams , "TAG:language" , idx ) ;
string title = await GetFfprobeInfoAsync ( path , showStreams , "TAG:title" , idx ) ;
string codec = await GetFfprobeInfoAsync ( path , showStreams , "codec_name" , idx ) ;
string codecLong = await GetFfprobeInfoAsync ( path , showStreams , "codec_long_name" , idx ) ;
string pixFmt = ( await GetFfprobeInfoAsync ( path , showStreams , "pix_fmt" , idx ) ) . ToUpper ( ) ;
int kbits = ( await GetFfprobeInfoAsync ( path , showStreams , "bit_rate" , idx ) ) . GetInt ( ) / 1024 ;
Size res = await GetMediaResolutionCached . GetSizeAsync ( path ) ;
Size sar = SizeFromString ( await GetFfprobeInfoAsync ( path , showStreams , "sample_aspect_ratio" , idx ) ) ;
Size dar = SizeFromString ( await GetFfprobeInfoAsync ( path , showStreams , "display_aspect_ratio" , idx ) ) ;
2023-02-15 12:27:36 +01:00
Fraction fps = path . IsConcatFile ( ) ? ( Fraction ) defaultFps : await IoUtils . GetVideoFramerate ( path ) ;
2022-07-20 18:10:31 +02:00
int frameCount = countFrames ? await GetFrameCountCached . GetFrameCountAsync ( path ) : 0 ;
VideoStream vStream = new VideoStream ( lang , title , codec , codecLong , pixFmt , kbits , res , sar , dar , fps , frameCount ) ;
vStream . Index = idx ;
vStream . IsDefault = def ;
Logger . Log ( $"Added video stream: {vStream}" , true ) ;
streamList . Add ( vStream ) ;
continue ;
}
if ( streamStr . Contains ( ": Audio:" ) )
{
string lang = await GetFfprobeInfoAsync ( path , showStreams , "TAG:language" , idx ) ;
string title = await GetFfprobeInfoAsync ( path , showStreams , "TAG:title" , idx ) ;
string codec = await GetFfprobeInfoAsync ( path , showStreams , "codec_name" , idx ) ;
string profile = await GetFfprobeInfoAsync ( path , showStreams , "profile" , idx ) ;
2022-10-14 09:00:47 +02:00
if ( codec . ToLowerInvariant ( ) = = "dts" & & profile ! = "unknown" ) codec = profile ;
2022-07-20 18:10:31 +02:00
string codecLong = await GetFfprobeInfoAsync ( path , showStreams , "codec_long_name" , idx ) ;
int kbits = ( await GetFfprobeInfoAsync ( path , showStreams , "bit_rate" , idx ) ) . GetInt ( ) / 1024 ;
int sampleRate = ( await GetFfprobeInfoAsync ( path , showStreams , "sample_rate" , idx ) ) . GetInt ( ) ;
int channels = ( await GetFfprobeInfoAsync ( path , showStreams , "channels" , idx ) ) . GetInt ( ) ;
string layout = ( await GetFfprobeInfoAsync ( path , showStreams , "channel_layout" , idx ) ) ;
AudioStream aStream = new AudioStream ( lang , title , codec , codecLong , kbits , sampleRate , channels , layout ) ;
aStream . Index = idx ;
aStream . IsDefault = def ;
Logger . Log ( $"Added audio stream: {aStream}" , true ) ;
streamList . Add ( aStream ) ;
continue ;
}
if ( streamStr . Contains ( ": Subtitle:" ) )
{
string lang = await GetFfprobeInfoAsync ( path , showStreams , "TAG:language" , idx ) ;
string title = await GetFfprobeInfoAsync ( path , showStreams , "TAG:title" , idx ) ;
string codec = await GetFfprobeInfoAsync ( path , showStreams , "codec_name" , idx ) ;
string codecLong = await GetFfprobeInfoAsync ( path , showStreams , "codec_long_name" , idx ) ;
bool bitmap = await IsSubtitleBitmapBased ( path , idx , codec ) ;
SubtitleStream sStream = new SubtitleStream ( lang , title , codec , codecLong , bitmap ) ;
sStream . Index = idx ;
sStream . IsDefault = def ;
Logger . Log ( $"Added subtitle stream: {sStream}" , true ) ;
streamList . Add ( sStream ) ;
continue ;
}
if ( streamStr . Contains ( ": Data:" ) )
{
string codec = await GetFfprobeInfoAsync ( path , showStreams , "codec_name" , idx ) ;
string codecLong = await GetFfprobeInfoAsync ( path , showStreams , "codec_long_name" , idx ) ;
DataStream dStream = new DataStream ( codec , codecLong ) ;
dStream . Index = idx ;
dStream . IsDefault = def ;
Logger . Log ( $"Added data stream: {dStream}" , true ) ;
streamList . Add ( dStream ) ;
continue ;
}
if ( streamStr . Contains ( ": Attachment:" ) )
{
string codec = await GetFfprobeInfoAsync ( path , showStreams , "codec_name" , idx ) ;
string codecLong = await GetFfprobeInfoAsync ( path , showStreams , "codec_long_name" , idx ) ;
string filename = await GetFfprobeInfoAsync ( path , showStreams , "TAG:filename" , idx ) ;
string mimeType = await GetFfprobeInfoAsync ( path , showStreams , "TAG:mimetype" , idx ) ;
AttachmentStream aStream = new AttachmentStream ( codec , codecLong , filename , mimeType ) ;
aStream . Index = idx ;
aStream . IsDefault = def ;
Logger . Log ( $"Added attachment stream: {aStream}" , true ) ;
streamList . Add ( aStream ) ;
continue ;
}
Logger . Log ( $"Unknown stream (not vid/aud/sub/dat/att): {streamStr}" , true ) ;
Stream stream = new Stream { Codec = "Unknown" , CodecLong = "Unknown" , Index = idx , IsDefault = def , Type = Stream . StreamType . Unknown } ;
streamList . Add ( stream ) ;
}
catch ( Exception e )
{
Logger . Log ( $"Error scanning stream: {e.Message}\n{e.StackTrace}" ) ;
}
}
}
catch ( Exception e )
{
Logger . Log ( $"GetStreams Exception: {e.Message}\n{e.StackTrace}" , true ) ;
}
Logger . Log ( $"Video Streams: {string.Join(" , ", streamList.Where(x => x.Type == Stream.StreamType.Video).Select(x => string.IsNullOrWhiteSpace(x.Title) ? " No Title " : x.Title))}" , true ) ;
Logger . Log ( $"Audio Streams: {string.Join(" , ", streamList.Where(x => x.Type == Stream.StreamType.Audio).Select(x => string.IsNullOrWhiteSpace(x.Title) ? " No Title " : x.Title))}" , true ) ;
Logger . Log ( $"Subtitle Streams: {string.Join(" , ", streamList.Where(x => x.Type == Stream.StreamType.Subtitle).Select(x => string.IsNullOrWhiteSpace(x.Title) ? " No Title " : x.Title))}" , true ) ;
if ( progressBar )
Program . mainForm . SetProgress ( 0 ) ;
return streamList ;
}
public static async Task < bool > IsSubtitleBitmapBased ( string path , int streamIndex , string codec = "" )
{
if ( codec = = "ssa" | | codec = = "ass" | | codec = = "mov_text" | | codec = = "srt" | | codec = = "subrip" | | codec = = "text" | | codec = = "webvtt" )
return false ;
if ( codec = = "dvdsub" | | codec = = "dvd_subtitle" | | codec = = "pgssub" | | codec = = "hdmv_pgs_subtitle" | | codec . StartsWith ( "dvb_" ) )
return true ;
// If codec was not listed above, manually check if it's compatible by trying to encode it:
//string ffmpegCheck = await GetFfmpegOutputAsync(path, $"-map 0:{streamIndex} -c:s srt -t 0 -f null -");
//return ffmpegCheck.Contains($"encoding currently only possible from text to text or bitmap to bitmap");
2021-08-23 16:50:18 +02:00
2022-07-20 18:10:31 +02:00
return false ;
}
2023-01-31 11:41:39 +01:00
public static string [ ] GetEncArgs ( OutputSettings settings , Size res , float fps , bool realtime = false ) // Array contains as many entries as there are encoding passes. If "realtime" is true, force single pass.
2021-08-23 16:50:18 +02:00
{
2023-01-18 14:55:38 +01:00
Encoder enc = settings . Encoder ;
2023-01-15 17:23:49 +01:00
int keyint = 10 ;
var args = new List < string > ( ) ;
EncoderInfoVideo info = OutputUtils . GetEncoderInfoVideo ( enc ) ;
2023-01-31 11:41:39 +01:00
PixelFormat pixFmt = settings . PixelFormat ;
if ( settings . Format = = Enums . Output . Format . Realtime )
pixFmt = PixelFormat . Yuv444P ;
if ( pixFmt = = ( PixelFormat ) ( - 1 ) ) // No pixel format set in GUI
pixFmt = info . PixelFormatDefault ! = ( PixelFormat ) ( - 1 ) ? info . PixelFormatDefault : info . PixelFormats . First ( ) ; // Set default or fallback to first in list
2023-01-15 17:23:49 +01:00
args . Add ( $"-c:v {info.Name}" ) ;
2021-08-23 16:50:18 +02:00
2023-01-15 17:23:49 +01:00
if ( enc = = Encoder . X264 | | enc = = Encoder . X265 | | enc = = Encoder . SvtAv1 | | enc = = Encoder . VpxVp9 | | enc = = Encoder . Nvenc264 | | enc = = Encoder . Nvenc265 | | enc = = Encoder . NvencAv1 )
args . Add ( GetKeyIntArg ( fps , keyint ) ) ;
2022-06-06 07:03:27 +02:00
2023-01-15 17:23:49 +01:00
if ( pixFmt ! = ( PixelFormat ) ( - 1 ) )
args . Add ( $"-pix_fmt {pixFmt.ToString().Lower()}" ) ;
2021-08-23 16:50:18 +02:00
2023-01-15 17:23:49 +01:00
if ( enc = = Encoder . X264 )
2021-08-23 16:50:18 +02:00
{
2023-01-15 17:23:49 +01:00
string preset = Config . Get ( Config . Key . ffEncPreset ) . ToLowerInvariant ( ) . Remove ( " " ) ; // TODO: Replace this ugly stuff with enums
2023-02-05 21:31:48 +01:00
int crf = GetCrf ( settings ) ;
2023-01-18 14:55:38 +01:00
args . Add ( $"-crf {crf} -preset {preset}" ) ;
2021-08-23 16:50:18 +02:00
}
2022-05-31 22:17:22 +02:00
2023-01-15 17:23:49 +01:00
if ( enc = = Encoder . X265 )
2021-08-23 16:50:18 +02:00
{
2023-01-15 17:23:49 +01:00
string preset = Config . Get ( Config . Key . ffEncPreset ) . ToLowerInvariant ( ) . Remove ( " " ) ; // TODO: Replace this ugly stuff with enums
2023-02-05 21:31:48 +01:00
int crf = GetCrf ( settings ) ;
2023-01-15 17:23:49 +01:00
args . Add ( $"{(crf > 0 ? $" - crf { crf } " : " - x265 - params lossless = 1 ")} -preset {preset}" ) ;
2021-08-23 16:50:18 +02:00
}
2023-01-15 17:23:49 +01:00
if ( enc = = Encoder . SvtAv1 )
2021-08-23 16:50:18 +02:00
{
2023-02-05 21:31:48 +01:00
int crf = GetCrf ( settings ) ;
2023-01-18 14:55:38 +01:00
args . Add ( $"-crf {crf} {GetSvtAv1Speed()} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0" ) ;
2021-08-23 16:50:18 +02:00
}
2023-01-15 17:23:49 +01:00
if ( enc = = Encoder . VpxVp9 )
2021-08-23 16:50:18 +02:00
{
2023-02-05 21:31:48 +01:00
int crf = GetCrf ( settings ) ;
2021-11-24 13:48:23 +01:00
string qualityStr = ( crf > 0 ) ? $"-crf {crf}" : "-lossless 1" ;
2021-11-24 21:08:29 +01:00
string t = GetTilingArgs ( res , "-tile-columns " , "-tile-rows " ) ;
2022-06-06 07:03:27 +02:00
if ( realtime ) // Force 1-pass
{
2023-01-15 17:23:49 +01:00
args . Add ( $"-b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1" ) ;
2022-06-06 07:03:27 +02:00
}
else
{
2023-01-15 17:23:49 +01:00
return new string [ ] {
$"{string.Join(" ", args)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 -pass 1 -an" ,
$"{string.Join(" ", args)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 -pass 2"
2022-06-06 07:03:27 +02:00
} ;
}
2021-08-23 16:50:18 +02:00
}
2023-01-15 17:23:49 +01:00
if ( enc = = Encoder . Nvenc264 )
{
2023-02-05 21:31:48 +01:00
int crf = GetCrf ( settings ) ;
2023-01-18 14:55:38 +01:00
args . Add ( $"-b:v 0 {(crf > 0 ? $" - cq { crf } - preset p7 " : " - preset lossless ")}" ) ;
2023-01-15 17:23:49 +01:00
}
if ( enc = = Encoder . Nvenc265 )
{
2023-02-05 21:31:48 +01:00
int crf = GetCrf ( settings ) ;
2023-01-18 14:55:38 +01:00
args . Add ( $"-b:v 0 {(crf > 0 ? $" - cq { crf } - preset p7 " : " - preset lossless ")}" ) ;
2023-01-15 17:23:49 +01:00
}
if ( enc = = Encoder . NvencAv1 )
2021-08-23 16:50:18 +02:00
{
2023-02-05 21:31:48 +01:00
int crf = GetCrf ( settings ) ;
2023-01-18 14:55:38 +01:00
args . Add ( $"-b:v 0 -preset p7 {(crf > 0 ? $" - cq { crf } " : " - tune lossless ")}" ) ; // Lossless not supported as of Jan 2023!!
2021-08-23 16:50:18 +02:00
}
2023-01-15 17:23:49 +01:00
if ( enc = = Encoder . ProResKs )
2021-08-23 16:50:18 +02:00
{
2023-02-20 19:30:23 +01:00
var profile = ParseUtils . GetEnum < Quality . ProResProfile > ( settings . Quality , true , Strings . VideoQuality ) ;
args . Add ( $"-profile:v {OutputUtils.ProresProfiles[profile]}" ) ;
2021-08-23 16:50:18 +02:00
}
2023-01-15 17:23:49 +01:00
if ( enc = = Encoder . Gif )
2022-06-06 07:03:27 +02:00
{
2023-01-15 17:23:49 +01:00
args . Add ( "-gifflags -offsetting" ) ;
2022-06-06 07:03:27 +02:00
}
2023-02-20 19:30:23 +01:00
if ( enc = = Encoder . Jpeg )
{
var qualityLevel = ParseUtils . GetEnum < Quality . JpegWebm > ( settings . Quality , true , Strings . VideoQuality ) ;
args . Add ( $"-q:v {OutputUtils.JpegQuality[qualityLevel]}" ) ;
}
if ( enc = = Encoder . Webp )
{
var qualityLevel = ParseUtils . GetEnum < Quality . JpegWebm > ( settings . Quality , true , Strings . VideoQuality ) ;
args . Add ( $"-q:v {OutputUtils.WebpQuality[qualityLevel]}" ) ;
}
2023-01-15 17:23:49 +01:00
return new string [ ] { string . Join ( " " , args ) } ;
2021-08-23 16:50:18 +02:00
}
2023-02-05 21:31:48 +01:00
private static int GetCrf ( OutputSettings settings )
{
if ( settings . CustomQuality . NotEmpty ( ) )
return settings . CustomQuality . GetInt ( ) ;
else
return OutputUtils . GetCrf ( ParseUtils . GetEnum < Quality . Common > ( settings . Quality , true , Strings . VideoQuality ) , settings . Encoder ) ;
}
2021-10-25 15:30:47 +02:00
public static string GetTilingArgs ( Size resolution , string colArg , string rowArg )
{
int cols = 0 ;
if ( resolution . Width > = 1920 ) cols = 1 ;
if ( resolution . Width > = 3840 ) cols = 2 ;
if ( resolution . Width > = 7680 ) cols = 3 ;
int rows = 0 ;
2021-11-24 21:08:29 +01:00
if ( resolution . Height > = 1600 ) rows = 1 ;
if ( resolution . Height > = 3200 ) rows = 2 ;
if ( resolution . Height > = 6400 ) rows = 3 ;
2021-10-25 15:30:47 +02:00
2021-11-24 21:08:29 +01:00
Logger . Log ( $"GetTilingArgs: Video resolution is {resolution.Width}x{resolution.Height} - Using 2^{cols} columns, 2^{rows} rows (=> {Math.Pow(2, cols)}x{Math.Pow(2, rows)} = {Math.Pow(2, cols) * Math.Pow(2, rows)} Tiles)" , true ) ;
2023-01-15 17:23:49 +01:00
return $"{(cols > 0 ? colArg + cols : "")} {(rows > 0 ? rowArg + rows : "")}" ;
2021-10-25 15:30:47 +02:00
}
2021-10-27 11:04:18 +02:00
public static string GetKeyIntArg ( float fps , int intervalSeconds , string arg = "-g " )
{
2023-01-15 17:23:49 +01:00
int keyInt = ( fps * intervalSeconds ) . RoundToInt ( ) . Clamp ( 30 , 600 ) ;
2021-10-27 11:04:18 +02:00
return $"{arg}{keyInt}" ;
}
2021-11-24 13:48:23 +01:00
static string GetVp9Speed ( )
2021-08-23 16:50:18 +02:00
{
2022-10-14 09:00:47 +02:00
string preset = Config . Get ( Config . Key . ffEncPreset ) . ToLowerInvariant ( ) . Remove ( " " ) ;
2021-08-23 16:50:18 +02:00
string arg = "" ;
if ( preset = = "veryslow" ) arg = "0" ;
if ( preset = = "slower" ) arg = "1" ;
if ( preset = = "slow" ) arg = "2" ;
if ( preset = = "medium" ) arg = "3" ;
if ( preset = = "fast" ) arg = "4" ;
if ( preset = = "faster" ) arg = "5" ;
if ( preset = = "veryfast" ) arg = "4 -deadline realtime" ;
return $"-cpu-used {arg}" ;
}
static string GetSvtAv1Speed ( )
{
2022-10-14 09:00:47 +02:00
string preset = Config . Get ( Config . Key . ffEncPreset ) . ToLowerInvariant ( ) . Remove ( " " ) ;
2021-08-23 16:50:18 +02:00
string arg = "8" ;
2022-04-18 04:30:37 +02:00
if ( preset = = "veryslow" ) arg = "3" ;
if ( preset = = "slower" ) arg = "4" ;
if ( preset = = "slow" ) arg = "5" ;
if ( preset = = "medium" ) arg = "6" ;
if ( preset = = "fast" ) arg = "7" ;
if ( preset = = "faster" ) arg = "8" ;
if ( preset = = "veryfast" ) arg = "9" ;
2021-08-23 16:50:18 +02:00
return $"-preset {arg}" ;
}
2023-01-15 17:23:49 +01:00
public static bool ContainerSupportsAllAudioFormats ( Enums . Output . Format outFormat , List < string > codecs )
2021-08-23 16:50:18 +02:00
{
2021-11-24 13:48:23 +01:00
if ( codecs . Count < 1 )
2021-08-23 16:50:18 +02:00
Logger . Log ( $"Warning: ContainerSupportsAllAudioFormats() was called, but codec list has {codecs.Count} entries." , true , false , "ffmpeg" ) ;
foreach ( string format in codecs )
{
2023-01-15 17:23:49 +01:00
if ( ! ContainerSupportsAudioFormat ( outFormat , format ) )
2021-08-23 16:50:18 +02:00
return false ;
}
return true ;
}
2023-01-15 17:23:49 +01:00
public static bool ContainerSupportsAudioFormat ( Enums . Output . Format outFormat , string format )
2021-08-23 16:50:18 +02:00
{
bool supported = false ;
string alias = GetAudioExt ( format ) ;
2021-09-27 13:03:23 +02:00
string [ ] formatsMp4 = new string [ ] { "m4a" , "mp3" , "ac3" , "dts" } ;
string [ ] formatsMkv = new string [ ] { "m4a" , "mp3" , "ac3" , "dts" , "ogg" , "mp2" , "wav" , "wma" } ;
2021-08-23 16:50:18 +02:00
string [ ] formatsWebm = new string [ ] { "ogg" } ;
2023-01-15 17:23:49 +01:00
string [ ] formatsMov = new string [ ] { "m4a" , "ac3" , "dts" , "wav" } ;
2021-08-23 16:50:18 +02:00
string [ ] formatsAvi = new string [ ] { "m4a" , "ac3" , "dts" } ;
2023-01-15 17:23:49 +01:00
switch ( outFormat )
2021-08-23 16:50:18 +02:00
{
2023-01-15 17:23:49 +01:00
case Enums . Output . Format . Mp4 : supported = formatsMp4 . Contains ( alias ) ; break ;
case Enums . Output . Format . Mkv : supported = formatsMkv . Contains ( alias ) ; break ;
case Enums . Output . Format . Webm : supported = formatsWebm . Contains ( alias ) ; break ;
case Enums . Output . Format . Mov : supported = formatsMov . Contains ( alias ) ; break ;
case Enums . Output . Format . Avi : supported = formatsAvi . Contains ( alias ) ; break ;
2021-08-23 16:50:18 +02:00
}
2023-01-15 17:23:49 +01:00
Logger . Log ( $"Checking if {outFormat} supports audio format '{format}' ({alias}): {supported}" , true , false , "ffmpeg" ) ;
2021-08-23 16:50:18 +02:00
return supported ;
}
2023-01-18 14:55:38 +01:00
public static string GetExt ( OutputSettings settings , bool dot = true )
2021-08-23 16:50:18 +02:00
{
string ext = dot ? "." : "" ;
2023-01-15 17:23:49 +01:00
EncoderInfoVideo info = settings . Encoder . GetInfo ( ) ;
2021-08-23 16:50:18 +02:00
2023-01-15 17:23:49 +01:00
if ( string . IsNullOrWhiteSpace ( info . OverideExtension ) )
ext + = settings . Format . ToString ( ) . Lower ( ) ;
else
ext + = info . OverideExtension ;
2021-08-23 16:50:18 +02:00
return ext ;
}
2021-11-24 13:48:23 +01:00
public static string GetAudioExt ( string codec )
2021-08-23 16:50:18 +02:00
{
if ( codec . StartsWith ( "pcm_" ) )
return "wav" ;
switch ( codec )
{
case "vorbis" : return "ogg" ;
case "opus" : return "ogg" ;
case "mp2" : return "mp2" ;
case "mp3" : return "mp3" ;
case "aac" : return "m4a" ;
case "ac3" : return "ac3" ;
case "eac3" : return "ac3" ;
case "dts" : return "dts" ;
case "alac" : return "wav" ;
case "flac" : return "wav" ;
case "wmav1" : return "wma" ;
case "wmav2" : return "wma" ;
}
return "unsupported" ;
}
2023-01-15 17:23:49 +01:00
public static async Task < string > GetAudioFallbackArgs ( string videoPath , Enums . Output . Format outFormat , float itsScale )
2021-08-23 16:50:18 +02:00
{
2021-09-13 22:42:33 +02:00
bool opusMp4 = Config . GetBool ( Config . Key . allowOpusInMp4 ) ;
2021-09-14 18:28:10 +02:00
int opusBr = Config . GetInt ( Config . Key . opusBitrate , 128 ) ;
int aacBr = Config . GetInt ( Config . Key . aacBitrate , 160 ) ;
2021-10-20 20:28:21 +02:00
int ac = ( await GetVideoInfo . GetFfprobeInfoAsync ( videoPath , GetVideoInfo . FfprobeMode . ShowStreams , "channels" , 0 ) ) . GetInt ( ) ;
string af = GetAudioFilters ( itsScale ) ;
2021-08-23 16:50:18 +02:00
2023-01-15 17:23:49 +01:00
if ( outFormat = = Enums . Output . Format . Mkv | | outFormat = = Enums . Output . Format . Webm | | ( outFormat = = Enums . Output . Format . Mp4 & & opusMp4 ) )
2021-10-20 20:28:21 +02:00
return $"-c:a libopus -b:a {(ac > 4 ? $" { opusBr * 2 } " : $" { opusBr } ")}k -ac {(ac > 0 ? $" { ac } " : " 2 ")} {af}" ; // Double bitrate if 5ch or more, ignore ac if <= 0
2021-09-13 22:42:33 +02:00
else
2021-10-20 20:28:21 +02:00
return $"-c:a aac -b:a {(ac > 4 ? $" { aacBr * 2 } " : $" { aacBr } ")}k -aac_coder twoloop -ac {(ac > 0 ? $" { ac } " : " 2 ")} {af}" ;
}
private static string GetAudioFilters ( float itsScale )
{
if ( itsScale = = 0 | | itsScale = = 1 )
return "" ;
2021-11-24 13:48:23 +01:00
if ( itsScale > 4 )
2021-10-20 20:30:27 +02:00
return $"-af atempo=0.5,atempo=0.5,atempo={((1f / itsScale) * 4).ToStringDot()}" ;
2021-10-20 20:28:21 +02:00
else if ( itsScale > 2 )
return $"-af atempo=0.5,atempo={((1f / itsScale) * 2).ToStringDot()}" ;
else
return $"-af atempo={(1f / itsScale).ToStringDot()}" ;
2021-08-23 16:50:18 +02:00
}
public static string GetSubCodecForContainer ( string containerExt )
{
2021-11-24 13:48:23 +01:00
containerExt = containerExt . Remove ( "." ) ;
2021-08-23 16:50:18 +02:00
2022-06-06 07:03:27 +02:00
if ( containerExt = = "mp4" | | containerExt = = "mov" ) return "mov_text" ;
2021-08-23 16:50:18 +02:00
if ( containerExt = = "webm" ) return "webvtt" ;
2021-09-13 22:44:16 +02:00
return "copy" ; // Default: Copy subs
2021-08-23 16:50:18 +02:00
}
public static bool ContainerSupportsSubs ( string containerExt , bool showWarningIfNotSupported = true )
{
containerExt = containerExt . Remove ( "." ) ;
bool supported = ( containerExt = = "mp4" | | containerExt = = "mkv" | | containerExt = = "webm" | | containerExt = = "mov" ) ;
Logger . Log ( $"Subtitles {(supported ? " are supported " : " not supported ")} by {containerExt.ToUpper()}" , true ) ;
if ( showWarningIfNotSupported & & Config . GetBool ( Config . Key . keepSubs ) & & ! supported )
2022-06-06 07:03:27 +02:00
Logger . Log ( $"Warning: {containerExt.ToUpper()} exports do not include subtitles." ) ;
2021-11-24 13:48:23 +01:00
2021-08-23 16:50:18 +02:00
return supported ;
}
2022-07-20 18:10:31 +02:00
public static int CreateConcatFile ( string inputFilesDir , string outputPath , List < string > validExtensions = null )
2021-08-23 16:50:18 +02:00
{
2022-07-20 18:10:31 +02:00
if ( IoUtils . GetAmountOfFiles ( inputFilesDir , false ) < 1 )
return 0 ;
Directory . CreateDirectory ( outputPath . GetParentDir ( ) ) ;
2023-02-15 12:27:36 +01:00
validExtensions = validExtensions ? ? new List < string > ( ) ;
validExtensions = validExtensions . Select ( x = > x . Remove ( "." ) . Lower ( ) ) . ToList ( ) ; // Ignore "." in extensions
var validFiles = IoUtils . GetFilesSorted ( inputFilesDir ) . Where ( f = > validExtensions . Contains ( Path . GetExtension ( f ) . Replace ( "." , "" ) . Lower ( ) ) ) ;
string fileContent = string . Join ( Environment . NewLine , validFiles . Select ( f = > $"file '{f.Replace(@" \ ", " / ")}'" ) ) ;
2022-11-14 10:13:41 +01:00
IoUtils . TryDeleteIfExists ( outputPath ) ;
2023-02-15 12:27:36 +01:00
File . WriteAllText ( outputPath , fileContent ) ;
2021-08-23 16:50:18 +02:00
2023-02-15 12:27:36 +01:00
return validFiles . Count ( ) ;
2022-07-20 18:10:31 +02:00
}
public static Size SizeFromString ( string str , char delimiter = ':' )
{
try
{
string [ ] nums = str . Remove ( " " ) . Trim ( ) . Split ( delimiter ) ;
return new Size ( nums [ 0 ] . GetInt ( ) , nums [ 1 ] . GetInt ( ) ) ;
}
catch
{
return new Size ( ) ;
}
2021-08-23 16:50:18 +02:00
}
}
}