forked from Tyrrrz/YoutubeExplode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFFmpeg.cs
More file actions
147 lines (132 loc) · 4.85 KB
/
FFmpeg.cs
File metadata and controls
147 lines (132 loc) · 4.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CliWrap;
using CliWrap.Exceptions;
using YoutubeExplode.Converter.Utils.Extensions;
namespace YoutubeExplode.Converter;
// Ideally this should use named pipes and stream through stdout.
// However, named pipes aren't well supported on non-Windows OS and
// stdout streaming only works with some specific formats.
internal partial class FFmpeg
{
private readonly string _filePath;
public FFmpeg(string filePath) => _filePath = filePath;
public async ValueTask ExecuteAsync(
string arguments,
IProgress<double>? progress,
CancellationToken cancellationToken = default
)
{
var stdErrBuffer = new StringBuilder();
var stdErrPipe = PipeTarget.Merge(
// Collect error output in case of failure
PipeTarget.ToStringBuilder(stdErrBuffer),
// Collect progress output if requested
progress?.Pipe(CreateProgressRouter) ?? PipeTarget.Null
);
try
{
await Cli.Wrap(_filePath)
.WithArguments(arguments)
.WithStandardErrorPipe(stdErrPipe)
.ExecuteAsync(cancellationToken);
}
catch (CommandExecutionException ex)
{
throw new InvalidOperationException(
$"""
FFmpeg command-line tool failed with an error.
Standard error:
{stdErrBuffer}
""",
ex
);
}
}
}
internal partial class FFmpeg
{
public static string GetFilePath() =>
// Try to find FFmpeg in the probe directory
Directory
.EnumerateFiles(
AppDomain.CurrentDomain.BaseDirectory ?? Directory.GetCurrentDirectory()
)
.FirstOrDefault(
f =>
string.Equals(
Path.GetFileNameWithoutExtension(f),
"ffmpeg",
StringComparison.OrdinalIgnoreCase
)
)
// Otherwise fallback to just "ffmpeg" and hope it's on the PATH
?? "ffmpeg";
private static PipeTarget CreateProgressRouter(IProgress<double> progress)
{
var totalDuration = default(TimeSpan?);
return PipeTarget.ToDelegate(line =>
{
// Extract total stream duration
if (totalDuration is null)
{
// Need to extract all components separately because TimeSpan cannot directly
// parse a time string that is greater than 24 hours.
var totalDurationMatch = Regex.Match(line, @"Duration:\s(\d+):(\d+):(\d+\.\d+)");
if (totalDurationMatch.Success)
{
var hours = int.Parse(
totalDurationMatch.Groups[1].Value,
CultureInfo.InvariantCulture
);
var minutes = int.Parse(
totalDurationMatch.Groups[2].Value,
CultureInfo.InvariantCulture
);
var seconds = double.Parse(
totalDurationMatch.Groups[3].Value,
CultureInfo.InvariantCulture
);
totalDuration =
TimeSpan.FromHours(hours)
+ TimeSpan.FromMinutes(minutes)
+ TimeSpan.FromSeconds(seconds);
}
}
if (totalDuration is null || totalDuration == TimeSpan.Zero)
return;
// Extract processed stream duration
var processedDurationMatch = Regex.Match(line, @"time=(\d+):(\d+):(\d+\.\d+)");
if (processedDurationMatch.Success)
{
var hours = int.Parse(
processedDurationMatch.Groups[1].Value,
CultureInfo.InvariantCulture
);
var minutes = int.Parse(
processedDurationMatch.Groups[2].Value,
CultureInfo.InvariantCulture
);
var seconds = double.Parse(
processedDurationMatch.Groups[3].Value,
CultureInfo.InvariantCulture
);
var processedDuration =
TimeSpan.FromHours(hours)
+ TimeSpan.FromMinutes(minutes)
+ TimeSpan.FromSeconds(seconds);
progress.Report(
(
processedDuration.TotalMilliseconds / totalDuration.Value.TotalMilliseconds
).Clamp(0, 1)
);
}
});
}
}