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
148
149
150
151
152
153
154
155
156
157
158
|
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Logging;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.MediaEncoding.Encoder
{
public class ImageEncoder
{
private readonly string _ffmpegPath;
private readonly ILogger _logger;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(5, 5);
public ImageEncoder(string ffmpegPath, ILogger logger)
{
_ffmpegPath = ffmpegPath;
_logger = logger;
}
public async Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
{
ValidateInput(options);
var process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = _ffmpegPath,
Arguments = GetArguments(options),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
await ResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
process.Start();
var memoryStream = new MemoryStream();
#pragma warning disable 4014
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
process.StandardOutput.BaseStream.CopyToAsync(memoryStream);
#pragma warning restore 4014
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
process.BeginErrorReadLine();
var ranToCompletion = process.WaitForExit(5000);
if (!ranToCompletion)
{
try
{
_logger.Info("Killing ffmpeg process");
process.Kill();
process.WaitForExit(1000);
}
catch (Exception ex)
{
_logger.ErrorException("Error killing process", ex);
}
}
ResourcePool.Release();
var exitCode = ranToCompletion ? process.ExitCode : -1;
process.Dispose();
if (exitCode == -1 || memoryStream.Length == 0)
{
memoryStream.Dispose();
var msg = string.Format("ffmpeg image encoding failed for {0}", options.InputPath);
_logger.Error(msg);
throw new ApplicationException(msg);
}
memoryStream.Position = 0;
return memoryStream;
}
private string GetArguments(ImageEncodingOptions options)
{
var vfScale = GetFilterGraph(options);
var outputFormat = GetOutputFormat(options);
return string.Format("-i file:\"{0}\" {1} -f {2}",
options.InputPath,
vfScale,
outputFormat);
}
private string GetFilterGraph(ImageEncodingOptions options)
{
if (!options.Width.HasValue &&
!options.Height.HasValue &&
!options.MaxHeight.HasValue &&
!options.MaxWidth.HasValue)
{
return string.Empty;
}
var widthScale = "-1";
var heightScale = "-1";
if (options.MaxWidth.HasValue)
{
widthScale = "min(iw," + options.MaxWidth.Value.ToString(_usCulture) + ")";
}
else if (options.Width.HasValue)
{
widthScale = options.Width.Value.ToString(_usCulture);
}
if (options.MaxHeight.HasValue)
{
heightScale = "min(ih," + options.MaxHeight.Value.ToString(_usCulture) + ")";
}
else if (options.Height.HasValue)
{
heightScale = options.Height.Value.ToString(_usCulture);
}
var scaleMethod = "lanczos";
return string.Format("-vf scale=\"{0}:{1}\" -sws_flags {2}",
widthScale,
heightScale,
scaleMethod);
}
private string GetOutputFormat(ImageEncodingOptions options)
{
return options.Format;
}
private void ValidateInput(ImageEncodingOptions options)
{
}
}
}
|