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 ;
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 ( ) ;
}
public static async Task < List < Stream > > GetStreams ( string path , bool progressBar , int streamCount , Fraction defaultFps , bool countFrames )
{
List < Stream > streamList = new List < Stream > ( ) ;
try
{
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 ) ) ;
Fraction fps = path . IsConcatFile ( ) ? defaultFps : await IoUtils . GetVideoFramerate ( path ) ;
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 ) ;
if ( codec . ToLower ( ) = = "dts" & & profile ! = "unknown" ) codec = profile ;
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 ;
}
public enum Codec { H264 , H265 , H264Nvenc , H265Nvenc , Av1 , Vp9 , ProRes , AviRaw , Gif }
2021-08-23 16:50:18 +02:00
public static Codec GetCodec ( Interpolate . OutMode mode )
{
if ( mode = = Interpolate . OutMode . VidMp4 | | mode = = Interpolate . OutMode . VidMkv )
{
int mp4MkvEnc = Config . GetInt ( Config . Key . mp4Enc ) ;
if ( mp4MkvEnc = = 0 ) return Codec . H264 ;
if ( mp4MkvEnc = = 1 ) return Codec . H265 ;
if ( mp4MkvEnc = = 2 ) return Codec . H264Nvenc ;
if ( mp4MkvEnc = = 3 ) return Codec . H265Nvenc ;
if ( mp4MkvEnc = = 4 ) return Codec . Av1 ;
}
if ( mode = = Interpolate . OutMode . VidWebm )
return Codec . Vp9 ;
if ( mode = = Interpolate . OutMode . VidProRes )
return Codec . ProRes ;
if ( mode = = Interpolate . OutMode . VidAvi )
return Codec . AviRaw ;
2022-06-06 07:03:27 +02:00
if ( mode = = Interpolate . OutMode . VidGif )
return Codec . Gif ;
2021-08-23 16:50:18 +02:00
return Codec . H264 ;
}
public static string GetEnc ( Codec codec )
{
switch ( codec )
{
case Codec . H264 : return "libx264" ;
case Codec . H265 : return "libx265" ;
case Codec . H264Nvenc : return "h264_nvenc" ;
case Codec . H265Nvenc : return "hevc_nvenc" ;
case Codec . Av1 : return "libsvtav1" ;
case Codec . Vp9 : return "libvpx-vp9" ;
case Codec . ProRes : return "prores_ks" ;
case Codec . AviRaw : return Config . Get ( Config . Key . aviCodec ) ;
2022-06-06 07:03:27 +02:00
case Codec . Gif : return "gif" ;
2021-08-23 16:50:18 +02:00
}
2022-05-31 22:17:22 +02:00
2021-08-23 16:50:18 +02:00
return "libx264" ;
}
2022-06-06 07:03:27 +02:00
public static string [ ] GetEncArgs ( Codec codec , 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
{
2021-10-27 11:04:18 +02:00
int keyint = 10 ;
2021-08-23 16:50:18 +02:00
2021-11-24 13:48:23 +01:00
if ( codec = = Codec . H264 )
2021-08-23 16:50:18 +02:00
{
string preset = Config . Get ( Config . Key . ffEncPreset ) . ToLower ( ) . Remove ( " " ) ;
2021-10-27 11:04:18 +02:00
string g = GetKeyIntArg ( fps , keyint ) ;
2021-11-24 13:48:23 +01:00
return new string [ ] { $"-c:v {GetEnc(codec)} -crf {Config.GetInt(Config.Key.h264Crf)} -preset {preset} {g} -pix_fmt {GetPixFmt()}" } ;
2021-08-23 16:50:18 +02:00
}
if ( codec = = Codec . H265 )
{
string preset = Config . Get ( Config . Key . ffEncPreset ) . ToLower ( ) . Remove ( " " ) ;
int crf = Config . GetInt ( Config . Key . h265Crf ) ;
2021-10-27 11:04:18 +02:00
string g = GetKeyIntArg ( fps , keyint ) ;
2021-11-24 13:48:23 +01:00
return new string [ ] { $"-c:v {GetEnc(codec)} {(crf > 0 ? $" - crf { crf } " : " - x265 - params lossless = 1 ")} -preset {preset} {g} -pix_fmt {GetPixFmt()}" } ;
2021-08-23 16:50:18 +02:00
}
if ( codec = = Codec . H264Nvenc )
{
int cq = ( Config . GetInt ( Config . Key . h264Crf ) * 1.1f ) . RoundToInt ( ) ;
2021-11-24 13:48:23 +01:00
return new string [ ] { $"-c:v {GetEnc(codec)} -b:v 0 {(cq > 0 ? $" - cq { cq } - preset p7 " : " - preset lossless ")} -pix_fmt {GetPixFmt()}" } ;
2021-08-23 16:50:18 +02:00
}
if ( codec = = Codec . H265Nvenc )
{
int cq = ( Config . GetInt ( Config . Key . h265Crf ) * 1.1f ) . RoundToInt ( ) ;
2021-11-24 13:48:23 +01:00
return new string [ ] { $"-c:v {GetEnc(codec)} -b:v 0 {(cq > 0 ? $" - cq { cq } - preset p7 " : " - preset lossless ")} -pix_fmt {GetPixFmt()}" } ;
2021-08-23 16:50:18 +02:00
}
if ( codec = = Codec . Av1 )
{
2021-10-25 15:30:47 +02:00
int cq = Config . GetInt ( Config . Key . av1Crf ) ;
2021-10-27 11:04:18 +02:00
string g = GetKeyIntArg ( fps , keyint ) ;
2022-07-04 10:14:54 +02:00
return new string [ ] { $"-c:v {GetEnc(codec)} -b:v 0 -qp {cq} {GetSvtAv1Speed()} {g} -svtav1-params enable-overlays=0:enable-tf=0:scd=0 -pix_fmt {GetPixFmt()}" } ;
2021-08-23 16:50:18 +02:00
}
if ( codec = = Codec . Vp9 )
{
int crf = Config . GetInt ( Config . Key . vp9Crf ) ;
2021-11-24 13:48:23 +01:00
string qualityStr = ( crf > 0 ) ? $"-crf {crf}" : "-lossless 1" ;
2021-10-27 11:04:18 +02:00
string g = GetKeyIntArg ( fps , keyint ) ;
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
{
return new string [ ] { $"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pix_fmt {GetPixFmt()}" } ;
}
else
{
return new string [ ] {
$"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pass 1 -pix_fmt {GetPixFmt()} -an" ,
$"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pass 2 -pix_fmt {GetPixFmt()}"
} ;
}
2021-08-23 16:50:18 +02:00
}
2021-11-24 13:48:23 +01:00
if ( codec = = Codec . ProRes )
2021-08-23 16:50:18 +02:00
{
2021-11-24 13:48:23 +01:00
return new string [ ] { $"-c:v {GetEnc(codec)} -profile:v {Config.GetInt(Config.Key.proResProfile)} -pix_fmt {GetPixFmt()}" } ;
2021-08-23 16:50:18 +02:00
}
if ( codec = = Codec . AviRaw )
{
2021-11-24 13:48:23 +01:00
return new string [ ] { $"-c:v {GetEnc(codec)} -pix_fmt {Config.Get(Config.Key.aviColors)}" } ;
2021-08-23 16:50:18 +02:00
}
2022-06-06 07:03:27 +02:00
if ( codec = = Codec . Gif )
{
return new string [ ] { $"-c:v {GetEnc(codec)} -gifflags -offsetting" } ;
}
2021-11-24 13:48:23 +01:00
return new string [ 0 ] ;
2021-08-23 16:50:18 +02:00
}
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 ) ;
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 " )
{
2021-11-24 21:08:29 +01:00
int keyInt = ( fps * intervalSeconds ) . RoundToInt ( ) . Clamp ( 20 , 300 ) ;
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
{
string preset = Config . Get ( Config . Key . ffEncPreset ) . ToLower ( ) . Remove ( " " ) ;
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 ( )
{
string preset = Config . Get ( Config . Key . ffEncPreset ) . ToLower ( ) . Remove ( " " ) ;
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}" ;
}
2021-11-24 13:48:23 +01:00
static string GetPixFmt ( )
2021-08-23 16:50:18 +02:00
{
switch ( Config . GetInt ( Config . Key . pixFmt ) )
{
case 0 : return "yuv420p" ;
case 1 : return "yuv444p" ;
case 2 : return "yuv420p10le" ;
case 3 : return "yuv444p10le" ;
}
return "yuv420p" ;
}
2021-11-24 13:48:23 +01:00
public static bool ContainerSupportsAllAudioFormats ( Interpolate . OutMode outMode , 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 )
{
if ( ! ContainerSupportsAudioFormat ( outMode , format ) )
return false ;
}
return true ;
}
2021-11-24 13:48:23 +01:00
public static bool ContainerSupportsAudioFormat ( Interpolate . OutMode outMode , 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" } ;
string [ ] formatsProres = new string [ ] { "m4a" , "ac3" , "dts" , "wav" } ;
string [ ] formatsAvi = new string [ ] { "m4a" , "ac3" , "dts" } ;
switch ( outMode )
{
case Interpolate . OutMode . VidMp4 : supported = formatsMp4 . Contains ( alias ) ; break ;
case Interpolate . OutMode . VidMkv : supported = formatsMkv . Contains ( alias ) ; break ;
case Interpolate . OutMode . VidWebm : supported = formatsWebm . Contains ( alias ) ; break ;
case Interpolate . OutMode . VidProRes : supported = formatsProres . Contains ( alias ) ; break ;
case Interpolate . OutMode . VidAvi : supported = formatsAvi . Contains ( alias ) ; break ;
}
Logger . Log ( $"Checking if {outMode} supports audio format '{format}' ({alias}): {supported}" , true , false , "ffmpeg" ) ;
return supported ;
}
public static string GetExt ( Interpolate . OutMode outMode , bool dot = true )
{
string ext = dot ? "." : "" ;
switch ( outMode )
{
case Interpolate . OutMode . VidMp4 : ext + = "mp4" ; break ;
case Interpolate . OutMode . VidMkv : ext + = "mkv" ; break ;
case Interpolate . OutMode . VidWebm : ext + = "webm" ; break ;
case Interpolate . OutMode . VidProRes : ext + = "mov" ; break ;
case Interpolate . OutMode . VidAvi : ext + = "avi" ; break ;
case Interpolate . OutMode . VidGif : ext + = "gif" ; break ;
}
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" ;
}
2021-11-24 13:48:23 +01:00
public static async Task < string > GetAudioFallbackArgs ( string videoPath , Interpolate . OutMode outMode , 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
2021-09-13 22:42:33 +02:00
if ( outMode = = Interpolate . OutMode . VidMkv | | outMode = = Interpolate . OutMode . VidWebm | | ( outMode = = Interpolate . OutMode . VidMp4 & & 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 ( ) ) ;
if ( validExtensions = = null )
validExtensions = new List < string > ( ) ;
validExtensions = validExtensions . Select ( x = > x . Remove ( "." ) . ToLower ( ) ) . ToList ( ) ; // Ignore "." in extensions
2021-08-23 16:50:18 +02:00
string concatFileContent = "" ;
string [ ] files = IoUtils . GetFilesSorted ( inputFilesDir ) ;
2022-07-20 18:10:31 +02:00
int fileCount = 0 ;
2021-08-23 16:50:18 +02:00
2022-07-20 18:10:31 +02:00
foreach ( string file in files . Where ( x = > validExtensions . Contains ( Path . GetExtension ( x ) . Replace ( "." , "" ) . ToLower ( ) ) ) )
2021-08-23 16:50:18 +02:00
{
2022-07-20 18:10:31 +02:00
fileCount + + ;
2021-08-23 16:50:18 +02:00
concatFileContent + = $"file '{file.Replace(@" \ ", " / ")}'\n" ;
}
File . WriteAllText ( outputPath , concatFileContent ) ;
2022-07-20 18:10:31 +02:00
return fileCount ;
}
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
}
}
}