2021-02-02 12:56:48 +01:00
using Flowframes.Data ;
using Flowframes.IO ;
using Flowframes.Main ;
using Flowframes.MiscUtils ;
2021-03-03 16:00:48 +01:00
using Flowframes.UI ;
2021-02-02 12:56:48 +01:00
using System ;
using System.Collections.Generic ;
using System.Drawing ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using static Flowframes . AvProcess ;
using Utils = Flowframes . Media . FFmpegUtils ;
2021-03-08 19:54:58 +01:00
using I = Flowframes . Interpolate ;
2021-02-02 12:56:48 +01:00
namespace Flowframes.Media
{
partial class FfmpegAudioAndMetadata : FfmpegCommands
{
2021-02-24 09:59:18 +01:00
#region Audio
public static async Task ExtractAudioTracks ( string inputFile , string outFolder , bool showMsg = true )
2021-02-02 12:56:48 +01:00
{
2021-02-24 09:59:18 +01:00
string msg = "Extracting audio from video..." ;
if ( showMsg ) Logger . Log ( msg ) ;
2021-02-23 22:26:13 +01:00
List < AudioTrack > audioTracks = await GetAudioTracks ( inputFile ) ;
int counter = 1 ;
foreach ( AudioTrack track in audioTracks )
2021-02-02 12:56:48 +01:00
{
2021-03-24 14:40:01 +01:00
if ( I . canceled ) break ;
2021-03-01 22:40:04 +01:00
2021-02-23 22:26:13 +01:00
string audioExt = Utils . GetAudioExt ( inputFile , track . streamIndex ) ;
2021-02-24 10:21:47 +01:00
string outPath = Path . Combine ( outFolder , $"{track.streamIndex}_{track.metadata}_audio.{audioExt}" ) ;
2021-03-02 15:06:44 +01:00
string [ ] trim = FfmpegExtract . GetTrimArgs ( ) ;
string args = $"{trim[0]} -i {inputFile.Wrap()} {trim[1]} -map 0:{track.streamIndex} -vn -c:a copy {outPath.Wrap()}" ;
await RunFfmpeg ( args , LogMode . Hidden , "panic" ) ;
2021-02-23 22:26:13 +01:00
2021-03-24 14:40:01 +01:00
if ( IOUtils . GetFilesize ( outPath ) < 512 )
2021-02-02 12:56:48 +01:00
{
2021-03-24 14:40:01 +01:00
// Logger.Log($"Failed to extract audio stream #{track.streamIndex} losslessly! Trying to re-encode.");
2021-02-23 22:26:13 +01:00
File . Delete ( outPath ) ;
outPath = Path . ChangeExtension ( outPath , Utils . GetAudioExtForContainer ( Path . GetExtension ( inputFile ) ) ) ;
2021-03-24 14:40:01 +01:00
args = $"{trim[0]} -i {inputFile.Wrap()} {trim[1]} -vn {Utils.GetAudioFallbackArgs(I.current.outMode)} {outPath.Wrap()}" ;
2021-03-02 15:06:44 +01:00
await RunFfmpeg ( args , LogMode . Hidden , "panic" , TaskType . ExtractOther , true ) ;
2021-02-04 21:03:59 +01:00
2021-03-24 14:40:01 +01:00
if ( IOUtils . GetFilesize ( outPath ) < 512 )
2021-02-04 21:03:59 +01:00
{
2021-02-23 22:26:13 +01:00
Logger . Log ( $"Failed to extract audio stream #{track.streamIndex}, even with re-encoding. Will be missing from output." ) ;
IOUtils . TryDeleteIfExists ( outPath ) ;
2021-02-04 21:03:59 +01:00
return ;
}
2021-02-23 22:26:13 +01:00
Logger . Log ( $"Audio stream #{track.streamIndex} has been re-encoded as it can't be extracted losslessly. This may decrease the quality slightly." , false , true ) ;
2021-02-04 21:03:59 +01:00
}
2021-02-23 22:26:13 +01:00
if ( audioTracks . Count > 1 ) Program . mainForm . SetProgress ( FormatUtils . RatioInt ( counter , audioTracks . Count ) ) ;
Logger . Log ( $"[FFCmds] Extracted audio track {track.streamIndex} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})" , true , false , "ffmpeg" ) ;
counter + + ;
2021-02-04 21:03:59 +01:00
}
2021-02-23 22:26:13 +01:00
}
public static async Task < List < AudioTrack > > GetAudioTracks ( string inputFile )
{
List < AudioTrack > audioTracks = new List < AudioTrack > ( ) ;
Logger . Log ( "GetAudioTracks()" , true , false , "ffmpeg" ) ;
2021-05-06 12:08:03 +02:00
string [ ] outputLines = ( await GetVideoInfoCached . GetFfmpegInfoAsync ( inputFile ) ) . SplitIntoLines ( ) ;
2021-02-23 22:26:13 +01:00
for ( int i = 0 ; i < outputLines . Length ; i + + )
2021-02-04 21:03:59 +01:00
{
2021-02-23 22:26:13 +01:00
string line = outputLines [ i ] ;
if ( ! line . Contains ( " Audio: " ) ) continue ;
2021-03-24 14:40:01 +01:00
int streamIndex = line . Remove ( "Stream #0:" ) . Split ( ':' ) [ 0 ] . Split ( '[' ) [ 0 ] . GetInt ( ) ;
2021-02-23 22:26:13 +01:00
2021-02-24 10:21:47 +01:00
string meta = "" ;
2021-02-23 22:26:13 +01:00
string codec = "" ;
2021-02-24 10:21:47 +01:00
if ( ( i + 2 < outputLines . Length ) & & outputLines [ i + 1 ] . Contains ( "Metadata:" ) & & ( outputLines [ i + 2 ] . Contains ( "title" ) | | outputLines [ i + 2 ] . Contains ( "lang" ) ) )
meta = outputLines [ i + 2 ] . Replace ( ":" , "=" ) . Remove ( " " ) ;
2021-02-23 22:26:13 +01:00
2021-02-24 10:21:47 +01:00
Logger . Log ( $"Found audio stream #{streamIndex} {meta}" , true , false , "ffmpeg" ) ;
audioTracks . Add ( new AudioTrack ( streamIndex , meta , codec ) ) ;
2021-02-02 12:56:48 +01:00
}
2021-02-23 22:26:13 +01:00
return audioTracks ;
2021-02-02 12:56:48 +01:00
}
2021-02-24 09:59:18 +01:00
#endregion
2021-02-23 22:26:13 +01:00
#region Subtitles
2021-02-04 21:03:59 +01:00
public static async Task ExtractSubtitles ( string inputFile , string outFolder , Interpolate . OutMode outMode , bool showMsg = true )
2021-02-02 12:56:48 +01:00
{
2021-02-04 21:03:59 +01:00
try
2021-02-02 12:56:48 +01:00
{
2021-02-04 21:03:59 +01:00
string msg = "Extracting subtitles from video..." ;
if ( showMsg ) Logger . Log ( msg ) ;
List < SubtitleTrack > subtitleTracks = await GetSubtitleTracks ( inputFile ) ;
int counter = 1 ;
2021-02-25 19:21:11 +01:00
int extractedSuccessfully = 0 ;
2021-02-02 12:56:48 +01:00
2021-02-04 21:03:59 +01:00
foreach ( SubtitleTrack subTrack in subtitleTracks )
{
2021-03-01 22:40:04 +01:00
if ( Interpolate . canceled ) break ;
2021-02-24 10:21:47 +01:00
string outPath = Path . Combine ( outFolder , $"{subTrack.streamIndex}_{subTrack.lang}_{subTrack.encoding}.srt" ) ;
2021-03-02 15:06:44 +01:00
string [ ] trim = FfmpegExtract . GetTrimArgs ( ) ;
string args = $"-sub_charenc {subTrack.encoding} {trim[0]} -i {inputFile.Wrap()} {trim[1]} -map 0:{subTrack.streamIndex} {outPath.Wrap()}" ;
await RunFfmpeg ( args , LogMode . Hidden , "error" ) ;
2021-02-04 21:03:59 +01:00
if ( subtitleTracks . Count > 4 ) Program . mainForm . SetProgress ( FormatUtils . RatioInt ( counter , subtitleTracks . Count ) ) ;
counter + + ;
2021-02-25 19:21:11 +01:00
if ( IOUtils . GetFilesize ( outPath ) > = 32 )
{
Logger . Log ( $"[FFCmds] Extracted subtitle track {subTrack.streamIndex} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})" , true , false , "ffmpeg" ) ;
extractedSuccessfully + + ;
}
else
{
IOUtils . TryDeleteIfExists ( outPath ) ; // Delete if encode was not successful
}
2021-02-04 21:03:59 +01:00
}
2021-02-25 19:21:11 +01:00
if ( extractedSuccessfully > 0 )
2021-02-08 21:11:25 +01:00
{
2021-02-25 19:21:11 +01:00
Logger . Log ( $"Extracted {extractedSuccessfully} subtitle tracks from the input video." , false , Logger . GetLastLine ( ) . Contains ( msg ) ) ;
2021-02-04 21:03:59 +01:00
Utils . ContainerSupportsSubs ( Utils . GetExt ( outMode ) , true ) ;
2021-02-08 21:11:25 +01:00
}
2021-02-04 21:03:59 +01:00
}
catch ( Exception e )
2021-02-02 12:56:48 +01:00
{
2021-02-04 21:03:59 +01:00
Logger . Log ( "Error extracting subtitles: " + e . Message ) ;
2021-02-02 12:56:48 +01:00
}
2021-02-04 21:03:59 +01:00
Program . mainForm . SetProgress ( 0 ) ;
2021-02-02 12:56:48 +01:00
}
2021-02-04 21:03:59 +01:00
public static async Task < List < SubtitleTrack > > GetSubtitleTracks ( string inputFile )
2021-02-02 12:56:48 +01:00
{
2021-02-04 21:03:59 +01:00
List < SubtitleTrack > subtitleTracks = new List < SubtitleTrack > ( ) ;
Logger . Log ( "GetSubtitleTracks()" , true , false , "ffmpeg" ) ;
2021-05-06 12:08:03 +02:00
string [ ] outputLines = ( await GetVideoInfoCached . GetFfmpegInfoAsync ( inputFile ) ) . SplitIntoLines ( ) ;
2021-02-04 21:03:59 +01:00
for ( int i = 0 ; i < outputLines . Length ; i + + )
2021-02-02 12:56:48 +01:00
{
2021-02-04 21:03:59 +01:00
string line = outputLines [ i ] ;
if ( ! line . Contains ( " Subtitle: " ) ) continue ;
2021-02-23 23:55:03 +01:00
int streamIndex = line . Remove ( "Stream #0:" ) . Split ( ':' ) [ 0 ] . GetInt ( ) ;
2021-02-24 10:21:47 +01:00
string lang = "" ;
2021-02-04 21:03:59 +01:00
string subEnc = "UTF-8" ;
if ( line . Contains ( "(" ) & & line . Contains ( "): Subtitle: " ) ) // Lang code in stream name, like "Stream #0:2(eng): Subtitle: ..."
2021-02-02 12:56:48 +01:00
lang = line . Split ( '(' ) [ 1 ] . Split ( ')' ) [ 0 ] ;
2021-02-04 21:03:59 +01:00
if ( ( i + 2 < outputLines . Length ) & & outputLines [ i + 1 ] . Contains ( "Metadata:" ) & & outputLines [ i + 2 ] . Contains ( "SUB_CHARENC" ) ) // Subtitle encoding is in metadata!
subEnc = outputLines [ i + 2 ] . Remove ( "SUB_CHARENC" ) . Remove ( ":" ) . TrimWhitespaces ( ) ;
2021-02-23 23:55:03 +01:00
Logger . Log ( $"Found subtitle track #{streamIndex} with language '{lang}' and encoding '{subEnc}'" , true , false , "ffmpeg" ) ;
subtitleTracks . Add ( new SubtitleTrack ( streamIndex , lang , subEnc ) ) ;
2021-02-02 12:56:48 +01:00
}
2021-02-04 21:03:59 +01:00
return subtitleTracks ;
2021-02-02 12:56:48 +01:00
}
2021-02-23 22:26:13 +01:00
#endregion
2021-03-24 14:40:01 +01:00
#region Mux From Input
2021-03-03 16:00:48 +01:00
public static async Task MergeStreamsFromInput ( string inputVideo , string interpVideo , string tempFolder )
2021-02-02 12:56:48 +01:00
{
2021-03-09 15:55:50 +01:00
if ( ! File . Exists ( inputVideo ) & & ! I . current . inputIsFrames )
2021-03-03 22:30:00 +01:00
{
Logger . Log ( "Warning: Input video file not found, can't copy audio/subtitle streams to output video!" ) ;
return ;
}
2021-03-03 16:00:48 +01:00
string containerExt = Path . GetExtension ( interpVideo ) ;
2021-02-23 23:55:03 +01:00
string tempPath = Path . Combine ( tempFolder , $"vid{containerExt}" ) ;
string outPath = Path . Combine ( tempFolder , $"muxed{containerExt}" ) ;
2021-03-03 16:00:48 +01:00
File . Move ( interpVideo , tempPath ) ;
string inName = Path . GetFileName ( tempPath ) ;
string outName = Path . GetFileName ( outPath ) ;
string subArgs = "-c:s " + Utils . GetSubCodecForContainer ( containerExt ) ;
2021-03-24 14:40:01 +01:00
bool audioCompat = Utils . ContainerSupportsAllAudioFormats ( I . current . outMode , GetAudioCodecs ( inputVideo ) ) ;
2021-03-08 19:54:58 +01:00
string audioArgs = audioCompat ? "" : Utils . GetAudioFallbackArgs ( I . current . outMode ) ;
2021-03-03 16:00:48 +01:00
2021-03-24 14:40:01 +01:00
if ( ! audioCompat )
Logger . Log ( "Warning: Input audio format(s) not fully supported in output container - Will re-encode." , true , false , "ffmpeg" ) ;
2021-04-05 16:52:18 +02:00
bool audio = Config . GetBool ( "keepAudio" ) ;
bool subs = Config . GetBool ( "keepSubs" ) ;
bool meta = Config . GetBool ( "keepMeta" ) ;
2021-03-13 14:11:06 +01:00
2021-04-05 16:52:18 +02:00
if ( ! audio )
2021-03-03 16:12:49 +01:00
audioArgs = "-an" ;
2021-04-05 16:52:18 +02:00
if ( ! subs | | ( subs & & ! Utils . ContainerSupportsSubs ( containerExt ) ) )
2021-03-03 16:12:49 +01:00
subArgs = "-sn" ;
2021-04-05 16:52:18 +02:00
bool isMkv = I . current . outMode = = I . OutMode . VidMkv ;
string mkvFix = isMkv ? "-max_interleave_delta 0" : "" ; // https://reddit.com/r/ffmpeg/comments/efddfs/starting_new_cluster_due_to_timestamp/
2021-04-07 19:03:01 +02:00
string metaArg = ( isMkv & & meta ) ? "-map 1:t?" : "" ; // https://reddit.com/r/ffmpeg/comments/fw4jnh/how_to_make_ffmpeg_keep_attached_images_in_mkv_as/
2021-03-08 19:54:58 +01:00
2021-03-03 16:00:48 +01:00
if ( QuickSettingsTab . trimEnabled )
{
string otherStreamsName = $"otherStreams{containerExt}" ;
string [ ] trim = FfmpegExtract . GetTrimArgs ( ) ;
2021-04-09 23:50:51 +02:00
string args1 = $"{trim[0]} -i {inputVideo.Wrap()} {trim[1]} -map 0 -map -0:v -map -0:d -c copy {audioArgs} {subArgs} {otherStreamsName}" ; // Extract trimmed
2021-03-03 16:00:48 +01:00
await RunFfmpeg ( args1 , tempFolder , LogMode . Hidden ) ;
2021-04-05 16:52:18 +02:00
string args2 = $"-i {inName} -i {otherStreamsName} -map 0:v:0 -map 1:a:? -map 1:s:? {metaArg} -c copy {audioArgs} {subArgs} {mkvFix} {outName}" ; // Merge interp + trimmed original
2021-03-03 16:00:48 +01:00
await RunFfmpeg ( args2 , tempFolder , LogMode . Hidden ) ;
IOUtils . TryDeleteIfExists ( Path . Combine ( tempFolder , otherStreamsName ) ) ;
}
2021-03-03 16:12:49 +01:00
else // If trimming is disabled we can pull the streams directly from the input file
2021-03-03 16:00:48 +01:00
{
2021-04-05 16:52:18 +02:00
string args = $"-i {inName} -i {inputVideo.Wrap()} -map 0:v:0 -map 1:a:? -map 1:s:? {metaArg} -c copy {audioArgs} {subArgs} {mkvFix} {outName}" ;
2021-03-03 16:00:48 +01:00
await RunFfmpeg ( args , tempFolder , LogMode . Hidden ) ;
}
if ( File . Exists ( outPath ) & & IOUtils . GetFilesize ( outPath ) > 512 )
{
File . Delete ( tempPath ) ;
File . Move ( outPath , interpVideo ) ;
}
else
{
File . Move ( tempPath , interpVideo ) ; // Muxing failed, move unmuxed video file back
}
}
2021-03-24 14:40:01 +01:00
#endregion
#region Mux From Extracted Streams
2021-03-03 16:00:48 +01:00
public static async Task MergeAudioAndSubs ( string interpVideo , string tempFolder ) // https://superuser.com/a/277667
{
string containerExt = Path . GetExtension ( interpVideo ) ;
string tempPath = Path . Combine ( tempFolder , $"vid{containerExt}" ) ;
string outPath = Path . Combine ( tempFolder , $"muxed{containerExt}" ) ;
File . Move ( interpVideo , tempPath ) ;
2021-02-02 12:56:48 +01:00
string inName = Path . GetFileName ( tempPath ) ;
string outName = Path . GetFileName ( outPath ) ;
2021-02-24 17:57:30 +01:00
bool audio = Config . GetBool ( "keepAudio" ) ;
2021-03-02 15:06:44 +01:00
bool subs = Config . GetBool ( "keepSubs" ) & & Utils . ContainerSupportsSubs ( containerExt , false ) ;
2021-02-24 17:57:30 +01:00
string [ ] audioTracks = audio ? IOUtils . GetFilesSorted ( tempFolder , false , "*_audio.*" ) : new string [ 0 ] ; // Find audio files
string [ ] subTracks = subs ? IOUtils . GetFilesSorted ( tempFolder , false , "*.srt" ) : new string [ 0 ] ; // Find subtitle files
2021-02-23 23:55:03 +01:00
Dictionary < int , string > trackFiles = new Dictionary < int , string > ( ) ; // Dict holding all track files with their index
foreach ( string audioTrack in audioTracks ) // Loop through audio streams to add them to the dict
trackFiles [ Path . GetFileNameWithoutExtension ( audioTrack ) . Split ( '_' ) [ 0 ] . GetInt ( ) ] = audioTrack ; // Add file, dict key is stream index
foreach ( string subTrack in subTracks ) // Loop through subtitle streams to add them to the dict
trackFiles [ Path . GetFileNameWithoutExtension ( subTrack ) . Split ( '_' ) [ 0 ] . GetInt ( ) ] = subTrack ; // Add file, dict key is stream index
string trackInputArgs = "" ;
string trackMapArgs = "" ;
string trackMetaArgs = "" ;
SortedDictionary < int , string > sortedTrackFiles = new SortedDictionary < int , string > ( trackFiles ) ;
2021-02-24 09:59:18 +01:00
int inputIndex = 1 ; // Input index (= output stream index) - Start at 1 since 0 is the video stream
2021-02-23 23:55:03 +01:00
foreach ( KeyValuePair < int , string > track in sortedTrackFiles )
2021-02-02 12:56:48 +01:00
{
2021-02-23 23:55:03 +01:00
int streamIndex = track . Key ;
string trackFile = track . Value ;
trackInputArgs + = $" -i {Path.GetFileName(trackFile)}" ; // Input filename
2021-02-28 11:51:36 +01:00
if ( Path . GetFileNameWithoutExtension ( trackFile ) . Contains ( "_audio" ) )
trackMapArgs + = $" -map {inputIndex}:a" ; // Map input file (audio track)
else
trackMapArgs + = $" -map {inputIndex}:s" ; // Map input file (subtitle track)
2021-02-24 10:21:47 +01:00
string meta = Path . GetFileNameWithoutExtension ( trackFile ) . Split ( '_' ) [ 1 ] ;
if ( ! string . IsNullOrWhiteSpace ( meta ) )
{
if ( Path . GetFileNameWithoutExtension ( trackFile ) . Contains ( "_audio" ) )
trackMetaArgs + = $" -metadata:s:{inputIndex} {meta}" ; // Metadata
else
trackMetaArgs + = $" -metadata:s:{inputIndex} language={meta}" ; // Language
}
2021-02-24 09:59:18 +01:00
inputIndex + + ;
2021-02-02 12:56:48 +01:00
}
2021-03-24 14:40:01 +01:00
bool audioCompat = true ;
2021-02-02 12:56:48 +01:00
2021-02-23 23:55:03 +01:00
foreach ( string audioTrack in audioTracks )
2021-03-08 19:54:58 +01:00
if ( ! Utils . ContainerSupportsAudioFormat ( I . current . outMode , Path . GetExtension ( audioTrack ) ) )
2021-03-24 14:40:01 +01:00
audioCompat = false ;
2021-02-02 12:56:48 +01:00
2021-03-24 14:40:01 +01:00
if ( ! audioCompat )
Logger . Log ( "Warning: Input audio format(s) not fully supported in output container - Will re-encode." , true , false , "ffmpeg" ) ;
2021-02-02 12:56:48 +01:00
2021-02-23 23:55:03 +01:00
string subArgs = "-c:s " + Utils . GetSubCodecForContainer ( containerExt ) ;
2021-03-24 14:40:01 +01:00
string audioArgs = audioCompat ? "-c:a copy" : Utils . GetAudioFallbackArgs ( I . current . outMode ) ;
2021-02-02 12:56:48 +01:00
2021-03-02 15:06:44 +01:00
string args = $" -i {inName} {trackInputArgs} -map 0:v {trackMapArgs} -c:v copy {audioArgs} {subArgs} {trackMetaArgs} {outName}" ;
2021-02-02 12:56:48 +01:00
2021-02-23 23:55:03 +01:00
await RunFfmpeg ( args , tempFolder , LogMode . Hidden ) ;
2021-02-02 12:56:48 +01:00
2021-02-23 23:55:03 +01:00
// if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024)
// {
// Logger.Log("Failed to merge audio losslessly! Trying to re-encode.", false, false, "ffmpeg");
//
// args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
// $"{trackInputArgs} -map 0:v -map 1:a {trackMapArgs} -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} -c:s {subCodec} {trackMetaArgs} -shortest {outName}";
//
// await RunFfmpeg(args, tempFolder, LogMode.Hidden);
//
// if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024)
// {
// Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.", false, false, "ffmpeg");
// IOUtils.TryMove(tempPath, inputFile); // Move temp file back
// IOUtils.TryDeleteIfExists(tempPath);
// return;
// }
//
// string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper();
// Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt.Remove(".").ToUpper()}). This may decrease the quality slightly.", false, true, "ffmpeg");
// }
2021-02-02 12:56:48 +01:00
if ( File . Exists ( outPath ) & & IOUtils . GetFilesize ( outPath ) > 512 )
{
File . Delete ( tempPath ) ;
2021-03-03 16:00:48 +01:00
File . Move ( outPath , interpVideo ) ;
2021-02-02 12:56:48 +01:00
}
else
{
2021-03-03 16:00:48 +01:00
File . Move ( tempPath , interpVideo ) ;
2021-02-02 12:56:48 +01:00
}
}
2021-03-24 14:40:01 +01:00
#endregion
2021-02-02 12:56:48 +01:00
}
}