2021-02-02 12:56:48 +01:00
using Flowframes.Data ;
using Flowframes.IO ;
using Flowframes.Main ;
using Flowframes.MiscUtils ;
using System ;
using System.Collections.Generic ;
using System.Drawing ;
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using static Flowframes . AvProcess ;
using Utils = Flowframes . Media . FFmpegUtils ;
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-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-02-23 22:26:13 +01:00
string args = $" -loglevel panic -i {inputFile.Wrap()} -map 0:{track.streamIndex} -vn -c:a copy {outPath.Wrap()}" ;
2021-02-02 12:56:48 +01:00
await RunFfmpeg ( args , LogMode . Hidden ) ;
2021-02-23 22:26:13 +01:00
if ( File . Exists ( outPath ) & & IOUtils . GetFilesize ( outPath ) < 512 )
2021-02-02 12:56:48 +01:00
{
2021-02-23 22:26:13 +01:00
Logger . Log ( $"Failed to extract audio stream #{track.streamIndex} losslessly! Trying to re-encode." ) ;
File . Delete ( outPath ) ;
outPath = Path . ChangeExtension ( outPath , Utils . GetAudioExtForContainer ( Path . GetExtension ( inputFile ) ) ) ;
args = $" -loglevel panic -i {inputFile.Wrap()} -vn {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {outPath.Wrap()}" ;
2021-02-16 11:27:00 +01:00
await RunFfmpeg ( args , LogMode . Hidden , TaskType . ExtractOther , true ) ;
2021-02-04 21:03:59 +01:00
2021-02-23 22:26:13 +01:00
if ( File . Exists ( outPath ) & & 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 > ( ) ;
string args = $"-i {inputFile.Wrap()}" ;
Logger . Log ( "GetAudioTracks()" , true , false , "ffmpeg" ) ;
string [ ] outputLines = ( await GetFfmpegOutputAsync ( args ) ) . SplitIntoLines ( ) ;
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 ;
int streamIndex = line . Remove ( "Stream #0:" ) . Split ( ':' ) [ 0 ] . GetInt ( ) ;
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-02 12:56:48 +01:00
2021-02-04 21:03:59 +01:00
foreach ( SubtitleTrack subTrack in subtitleTracks )
{
2021-02-24 10:21:47 +01:00
string outPath = Path . Combine ( outFolder , $"{subTrack.streamIndex}_{subTrack.lang}_{subTrack.encoding}.srt" ) ;
2021-02-23 22:26:13 +01:00
string args = $" -loglevel error -sub_charenc {subTrack.encoding} -i {inputFile.Wrap()} -map 0:{subTrack.streamIndex} {outPath.Wrap()}" ;
2021-02-04 21:03:59 +01:00
await RunFfmpeg ( args , LogMode . Hidden ) ;
if ( subtitleTracks . Count > 4 ) Program . mainForm . SetProgress ( FormatUtils . RatioInt ( counter , subtitleTracks . Count ) ) ;
Logger . Log ( $"[FFCmds] Extracted subtitle track {subTrack.streamIndex} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})" , true , false , "ffmpeg" ) ;
counter + + ;
}
if ( subtitleTracks . Count > 0 )
2021-02-08 21:11:25 +01:00
{
Logger . Log ( $"Extracted {subtitleTracks.Count} 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 > ( ) ;
2021-02-02 12:56:48 +01:00
string args = $"-i {inputFile.Wrap()}" ;
2021-02-04 21:03:59 +01:00
Logger . Log ( "GetSubtitleTracks()" , true , false , "ffmpeg" ) ;
2021-02-02 12:56:48 +01:00
string [ ] outputLines = ( await GetFfmpegOutputAsync ( args ) ) . 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-02-24 09:59:18 +01:00
public static async Task MergeAudioAndSubs ( string inputFile , string tempFolder ) // https://superuser.com/a/277667
2021-02-02 12:56:48 +01:00
{
2021-02-23 23:55:03 +01:00
// Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true);
2021-02-02 12:56:48 +01:00
string containerExt = Path . GetExtension ( inputFile ) ;
2021-02-23 23:55:03 +01:00
string tempPath = Path . Combine ( tempFolder , $"vid{containerExt}" ) ;
string outPath = Path . Combine ( tempFolder , $"muxed{containerExt}" ) ;
2021-02-02 12:56:48 +01:00
File . Move ( inputFile , tempPath ) ;
string inName = Path . GetFileName ( tempPath ) ;
string outName = Path . GetFileName ( outPath ) ;
2021-02-23 23:55:03 +01:00
string [ ] audioTracks = IOUtils . GetFilesSorted ( tempFolder , false , "*_audio.*" ) ; // Find audio files
string [ ] subTracks = IOUtils . GetFilesSorted ( tempFolder , false , "*.srt" ) ; // Find subtitle files
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
bool audio = Config . GetBool ( "keepSubs" ) ;
2021-02-02 12:56:48 +01:00
bool subs = Utils . ContainerSupportsSubs ( containerExt , false ) & & Config . GetBool ( "keepSubs" ) ;
2021-02-23 23:55:03 +01:00
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-24 09:59:18 +01:00
trackMapArgs + = $" -map {inputIndex}" ; // Map input file
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-02-23 23:55:03 +01:00
bool allAudioCodecsSupported = true ;
2021-02-02 12:56:48 +01:00
2021-02-23 23:55:03 +01:00
foreach ( string audioTrack in audioTracks )
if ( ! Utils . ContainerSupportsAudioFormat ( Interpolate . current . outMode , Path . GetExtension ( audioTrack ) ) )
allAudioCodecsSupported = false ;
2021-02-02 12:56:48 +01:00
2021-02-23 23:55:03 +01:00
if ( ! allAudioCodecsSupported )
Logger . Log ( "Warning: Input audio format(s) not fully support in output container. Audio transfer will not be lossless." , false , 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 ) ;
string audioArgs = allAudioCodecsSupported ? "-c:a copy" : Utils . GetAudioFallbackArgs ( Path . GetExtension ( inputFile ) ) ;
2021-02-02 12:56:48 +01:00
2021-02-24 09:59:18 +01:00
string args = $" -i {inName} {trackInputArgs} -map 0:v {trackMapArgs} -c:v copy {audioArgs} {subArgs} {trackMetaArgs} -shortest {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 ) ;
File . Move ( outPath , inputFile ) ;
}
else
{
File . Move ( tempPath , inputFile ) ;
}
}
}
}