mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-24 12:19:27 +01:00
249 lines
12 KiB
C#
249 lines
12 KiB
C#
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
|
|
{
|
|
public static async Task ExtractAudioTracks(string inputFile, string outFolder)
|
|
{
|
|
List<AudioTrack> audioTracks = await GetAudioTracks(inputFile);
|
|
int counter = 1;
|
|
|
|
foreach (AudioTrack track in audioTracks)
|
|
{
|
|
string audioExt = Utils.GetAudioExt(inputFile, track.streamIndex);
|
|
string outPath = Path.Combine(outFolder, $"{track.streamIndex}_{track.title}_audio.{audioExt}");
|
|
string args = $" -loglevel panic -i {inputFile.Wrap()} -map 0:{track.streamIndex} -vn -c:a copy {outPath.Wrap()}";
|
|
await RunFfmpeg(args, LogMode.Hidden);
|
|
|
|
if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 512)
|
|
{
|
|
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()}";
|
|
await RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractOther, true);
|
|
|
|
if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 512)
|
|
{
|
|
Logger.Log($"Failed to extract audio stream #{track.streamIndex}, even with re-encoding. Will be missing from output.");
|
|
IOUtils.TryDeleteIfExists(outPath);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
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++)
|
|
{
|
|
string line = outputLines[i];
|
|
if (!line.Contains(" Audio: ")) continue;
|
|
|
|
int streamIndex = line.Remove("Stream #0:").Split(':')[0].GetInt();
|
|
|
|
string title = "";
|
|
string codec = "";
|
|
|
|
if ((i + 2 < outputLines.Length) && outputLines[i + 1].Contains("Metadata:") && outputLines[i + 2].Contains("title"))
|
|
title = outputLines[i + 2].Remove("title").Remove(":").TrimWhitespaces();
|
|
|
|
Logger.Log($"Found audio stream #{streamIndex} {title}", true, false, "ffmpeg");
|
|
audioTracks.Add(new AudioTrack(streamIndex, title, codec));
|
|
}
|
|
|
|
return audioTracks;
|
|
}
|
|
|
|
#region Subtitles
|
|
|
|
public static async Task ExtractSubtitles(string inputFile, string outFolder, Interpolate.OutMode outMode,bool showMsg = true)
|
|
{
|
|
try
|
|
{
|
|
string msg = "Extracting subtitles from video...";
|
|
if (showMsg) Logger.Log(msg);
|
|
|
|
List<SubtitleTrack> subtitleTracks = await GetSubtitleTracks(inputFile);
|
|
int counter = 1;
|
|
|
|
foreach (SubtitleTrack subTrack in subtitleTracks)
|
|
{
|
|
string outPath = Path.Combine(outFolder, $"{subTrack.streamIndex}_{subTrack.langFriendly}_{subTrack.encoding}.srt");
|
|
string args = $" -loglevel error -sub_charenc {subTrack.encoding} -i {inputFile.Wrap()} -map 0:{subTrack.streamIndex} {outPath.Wrap()}";
|
|
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)
|
|
{
|
|
Logger.Log($"Extracted {subtitleTracks.Count} subtitle tracks from the input video.", false, Logger.GetLastLine().Contains(msg));
|
|
Utils.ContainerSupportsSubs(Utils.GetExt(outMode), true);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Log("Error extracting subtitles: " + e.Message);
|
|
}
|
|
|
|
Program.mainForm.SetProgress(0);
|
|
}
|
|
|
|
public static async Task<List<SubtitleTrack>> GetSubtitleTracks(string inputFile)
|
|
{
|
|
List<SubtitleTrack> subtitleTracks = new List<SubtitleTrack>();
|
|
string args = $"-i {inputFile.Wrap()}";
|
|
Logger.Log("GetSubtitleTracks()", true, false, "ffmpeg");
|
|
string[] outputLines = (await GetFfmpegOutputAsync(args)).SplitIntoLines();
|
|
|
|
for (int i = 0; i < outputLines.Length; i++)
|
|
{
|
|
string line = outputLines[i];
|
|
if (!line.Contains(" Subtitle: ")) continue;
|
|
|
|
int streamIndex = line.Remove("Stream #0:").Split(':')[0].GetInt();
|
|
|
|
string lang = "unknown";
|
|
string subEnc = "UTF-8";
|
|
|
|
if (line.Contains("(") && line.Contains("): Subtitle: ")) // Lang code in stream name, like "Stream #0:2(eng): Subtitle: ..."
|
|
lang = line.Split('(')[1].Split(')')[0];
|
|
|
|
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();
|
|
|
|
Logger.Log($"Found subtitle track #{streamIndex} with language '{lang}' and encoding '{subEnc}'", true, false, "ffmpeg");
|
|
subtitleTracks.Add(new SubtitleTrack(streamIndex, lang, subEnc));
|
|
}
|
|
|
|
return subtitleTracks;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public static async Task MergeAudioAndSubs(string inputFile, string tempFolder, int looptimes = -1) // https://superuser.com/a/277667
|
|
{
|
|
// Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true);
|
|
string containerExt = Path.GetExtension(inputFile);
|
|
string tempPath = Path.Combine(tempFolder, $"vid{containerExt}");
|
|
string outPath = Path.Combine(tempFolder, $"muxed{containerExt}");
|
|
File.Move(inputFile, tempPath);
|
|
string inName = Path.GetFileName(tempPath);
|
|
// string audioName = Path.GetFileName(audioPath);
|
|
string outName = Path.GetFileName(outPath);
|
|
|
|
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");
|
|
bool subs = Utils.ContainerSupportsSubs(containerExt, false) && Config.GetBool("keepSubs");
|
|
|
|
string trackInputArgs = "";
|
|
string trackMapArgs = "";
|
|
string trackMetaArgs = "";
|
|
|
|
// for (int subTrack = 0; subTrack < subTracks.Length; subTrack++)
|
|
// {
|
|
// trackInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}"; // Input filename
|
|
// trackMapArgs += $" -map {Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('_')[0].GetInt()}"; // Stream index
|
|
// trackMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('_')[1]}"; // Language
|
|
// }
|
|
|
|
SortedDictionary<int, string> sortedTrackFiles = new SortedDictionary<int, string>(trackFiles);
|
|
|
|
foreach (KeyValuePair<int, string> track in sortedTrackFiles)
|
|
{
|
|
int streamIndex = track.Key;
|
|
string trackFile = track.Value;
|
|
|
|
trackInputArgs += $" -i {Path.GetFileName(trackFile)}"; // Input filename
|
|
trackMapArgs += $" -map {Path.GetFileNameWithoutExtension(trackFile).Split('_')[0].GetInt()}"; // Set stream index
|
|
trackMetaArgs += $" -metadata:s:{streamIndex} title={Path.GetFileNameWithoutExtension(trackFile).Split('_')[1]}"; // Language or title
|
|
}
|
|
|
|
bool allAudioCodecsSupported = true;
|
|
|
|
foreach (string audioTrack in audioTracks)
|
|
if (!Utils.ContainerSupportsAudioFormat(Interpolate.current.outMode, Path.GetExtension(audioTrack)))
|
|
allAudioCodecsSupported = false;
|
|
|
|
if(!allAudioCodecsSupported)
|
|
Logger.Log("Warning: Input audio format(s) not fully support in output container. Audio transfer will not be lossless.", false, false, "ffmpeg");
|
|
|
|
string subArgs = "-c:s " + Utils.GetSubCodecForContainer(containerExt);
|
|
string audioArgs = allAudioCodecsSupported ? "-c:a copy" : Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile));
|
|
|
|
string args = $" -i {inName} -stream_loop {looptimes}" +
|
|
$"{trackInputArgs} -map 0:v {trackMapArgs} -c:v copy {audioArgs} {subArgs} {trackMetaArgs} -shortest {outName}";
|
|
|
|
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
|
|
|
|
|
|
// 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");
|
|
// }
|
|
|
|
if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) > 512)
|
|
{
|
|
File.Delete(tempPath);
|
|
File.Move(outPath, inputFile);
|
|
}
|
|
else
|
|
{
|
|
File.Move(tempPath, inputFile);
|
|
}
|
|
}
|
|
}
|
|
}
|