-
Notifications
You must be signed in to change notification settings - Fork 1
/
FrameEx.cs
378 lines (364 loc) · 17 KB
/
FrameEx.cs
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
using System;
using System.IO;
using System.Windows.Forms;
using System.Diagnostics;
using MediaInfo;
namespace FrameEx
{
public partial class FrameEx : Form
{
string ffmpegPath;
string inputVideoPath = "";
string outputFolder = "";
string duration = "";
string videoName = "";
string outputFormat = "jpg";
string outputPath = "";
double totalDurationInSeconds;
int extractionSeconds;
double fps;
double numberOfFramesForExtraction;
private Process extractionProc = null;
public FrameEx()
{
InitializeComponent();
// Select jpg as default output format
formatPicker.SelectedIndex = 0;
// Subscribe to the FormClosing event
this.FormClosing += FrameExFormClosing;
// Set path to ffmpeg-a
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
ffmpegPath = Path.Combine(baseDirectory, "ffmpeg.exe");
}
private void BrowseVideoBtn_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "Video Files|*.mp4;*.m4v;*.mov;*.avi;*.mkv;*.wmv|All Files|*.*";
openFileDialog.Title = "Select a Video File";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
// Save path
inputVideoPath = openFileDialog.FileName;
selectVideoTxtbox.Text = inputVideoPath;
// For output filename formating
videoName = Path.GetFileNameWithoutExtension(inputVideoPath);
// Get video duration using ffmpeg
Process getVideoDuration = new Process();
getVideoDuration.StartInfo.FileName = ffmpegPath;
getVideoDuration.StartInfo.Arguments = $"-i \"{inputVideoPath}\"";
getVideoDuration.StartInfo.UseShellExecute = false;
getVideoDuration.StartInfo.RedirectStandardError = true;
getVideoDuration.StartInfo.CreateNoWindow = true;
getVideoDuration.Start();
string getVideoDurationOutput = getVideoDuration.StandardError.ReadToEnd();
getVideoDuration.WaitForExit();
// Parse the FFmpeg output to extract the video duration
if (!string.IsNullOrEmpty(getVideoDurationOutput))
{
int index = getVideoDurationOutput.IndexOf("Duration: ");
if (index >= 0)
{
string durationString = getVideoDurationOutput.Substring(index + 10, 12);
duration = durationString.Replace(",", "");
}
}
// Convert the duration string to a TimeSpan
if (TimeSpan.TryParse(duration, out TimeSpan videoDuration))
{
// FFmpeg rounds down video length if there's 0 in the first decimal place when extracting frames
string totalDurationInSecondsTest = videoDuration.TotalSeconds.ToString(); // Convert the number to a string
int decimalPointIndexDur = totalDurationInSecondsTest.IndexOf('.');
if (decimalPointIndexDur != -1 && decimalPointIndexDur + 1 < totalDurationInSecondsTest.Length && totalDurationInSecondsTest[decimalPointIndexDur + 1] == '0')
{
totalDurationInSeconds = Math.Floor(videoDuration.TotalSeconds);
}
else
{
totalDurationInSeconds = videoDuration.TotalSeconds;
}
// Set trackBar size
trackBar.Maximum = (int)totalDurationInSeconds;
// Duration of a video is set as last frames initial location, set Last frame maximum, restart First frame value
lastFrameTimestampUpDown.Maximum = (decimal)totalDurationInSeconds;
lastFrameTimestampUpDown.Value = lastFrameTimestampUpDown.Maximum;
lastFrameTimestampUpDown.Value = lastFrameTimestampUpDown.Maximum;
firstFrameTimestampUpDown.Value = 0;
}
// Get video fps
var media = new MediaInfoWrapper(inputVideoPath);
fps = media.Framerate;
// FFmpeg rounds down FPS if there's 0 on the first decimal place and rounds up for every other case when extracting frames
string fpsTest = fps.ToString(); // Convert the number to a string
int decimalPointIndexFps = fpsTest.IndexOf('.');
if (decimalPointIndexFps != -1 && decimalPointIndexFps + 1 < fpsTest.Length && fpsTest[decimalPointIndexFps + 1] == '0') {
fps = Math.Floor(fps);
}
else {
fps = Math.Ceiling(fps);
}
// Enable Buttons for frame searching
EnableButtonForFrameSeaching();
// Enable Extract button if you already have outputPath selected
if (outputFolder != "")
{
extractBtn.Enabled = true;
// Create outputPath with videoName
outputPath = Path.Combine(outputFolder, videoName + "-%d." + outputFormat);
// Enable format picker
formatPicker.Enabled = true;
}
}
}
}
private void BrowseFolderBtn_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog())
{
folderBrowserDialog.Description = "Select output Folder";
folderBrowserDialog.SelectedPath = Directory.GetCurrentDirectory();
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
outputFolder = folderBrowserDialog.SelectedPath;
outputFolderTxtbox.Text = outputFolder;
// Enable Extract button if you already have inputVideo selected
if (inputVideoPath != "")
{
extractBtn.Enabled = true;
// Create outputPath with videoName
outputPath = Path.Combine(outputFolder, videoName + "-%d." + outputFormat);
// Enable format picker
formatPicker.Enabled = true;
}
}
}
}
private void ExtractBtn_Click(object sender, EventArgs e)
{
// Calculate last frame timestamp
extractionSeconds = (int)lastFrameTimestampUpDown.Value - (int)firstFrameTimestampUpDown.Value;
// Calculate number of frames for extraction
numberOfFramesForExtraction = Math.Ceiling(extractionSeconds * fps);
Console.WriteLine($"Number of frames for extraction: {numberOfFramesForExtraction}");
// Convert the numeric value to a TimeSpan
TimeSpan extractionTime = TimeSpan.FromSeconds(extractionSeconds);
// Format the TimeSpan as "hh:mm:ss"
string extractionFormattedTime = string.Format("{0:D2}:{1:D2}:{2:D2}", extractionTime.Hours, extractionTime.Minutes, extractionTime.Seconds);
// Build the FFmpeg command
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = ffmpegPath,
Arguments = $"-ss \"{firstFrameTimestamp.Text}\" -i \"{inputVideoPath}\" -t \"{extractionFormattedTime}\" -q:v 1 \"{outputPath}", //-q:v specifies the quality level of the output image, which ranges from 1 (highest quality) to 31 (lowest quality)
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardError = true
};
extractionProc = System.Diagnostics.Process.Start(startInfo);
extractionProc.EnableRaisingEvents = true; // Enables the Exited event
extractionProc.ErrorDataReceived += ExtractionProcErrorDataReceived;
extractionProc.BeginErrorReadLine();
// UI UPDATE
extractBtn.Enabled = false;
selectVideoBtn.Enabled = false;
outputFolderBtn.Enabled = false;
trackBar.Enabled = false;
radioButton1.Enabled = false;
radioButton2.Enabled = false;
stopBtn.Enabled = true;
progressBar.Value = 0;
// Disable Buttons for frame searching
firstFrameTimestampUpDown.Enabled = false;
lastFrameTimestampUpDown.Enabled = false;
startVideoTimestampBtn.Enabled = false;
endVideoTimestampBtn.Enabled = false;
progressLabel.Text = "In progress...";
// Handle the Exited event to know when FFmpeg has finished
extractionProc.Exited += (exitSender, exitE) =>
{
// Check the ExitCode to determine the result of the process
int exitCode = extractionProc.ExitCode;
if (exitCode == 0)
{
progressBar.Invoke((MethodInvoker)delegate
{
progressBar.Value = 100;
progressLabel.Text = "Done";
stopBtn.Enabled = false;
extractBtn.Enabled = true;
selectVideoBtn.Enabled = true;
outputFolderBtn.Enabled = true;
// enable Buttons for frame searching
EnableButtonForFrameSeaching();
});
MessageBox.Show("Conversion complete!"); // User feedback
}
else
{
Console.WriteLine($"FFmpeg process exited with error code: {exitCode}");
}
// Dispose of the process
extractionProc.Dispose();
};
}
void ExtractionProcErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null && e.Data.StartsWith("frame="))
{
//extract from ffmpeg last extracted frame
string input = e.Data.Split('=')[1].Trim();
string trimInput = input.Replace(" fps", "");
int lastFrame = Int32.Parse(trimInput);
// progress bar update
progressBar.Invoke((MethodInvoker)delegate {
double progressBarVal = lastFrame / numberOfFramesForExtraction * 100;
if (progressBarVal < 100) progressBar.Value = (int)progressBarVal;
});
}
}
private void StopBtn_Click(object sender, EventArgs e)
{
DialogResult r = MessageBox.Show("Are you sure?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (r == DialogResult.Yes)
{
extractionProc.Kill();
// restart UI
progressLabel.Text = "";
progressBar.Value = 0;
stopBtn.Enabled = false;
extractBtn.Enabled = true;
selectVideoBtn.Enabled = true;
outputFolderBtn.Enabled = true;
// enable Buttons for frame searching
EnableButtonForFrameSeaching();
MessageBox.Show("Conversion aborted!"); }
}
private void FrameExFormClosing(object sender, EventArgs e)
{
if (extractionProc != null)
{
try
{
// kill process if running at exit
Process[] processes = Process.GetProcessesByName(extractionProc.ProcessName);
if (processes.Length > 0) extractionProc.Kill();
}
catch (Exception ex)
{
// Handle any exceptions gracefully
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
}
private void FirstFrameTimestampUpDown_ValueChanged(object sender, EventArgs e)
{
// Set maximum limit
firstFrameTimestampUpDown.Maximum = lastFrameTimestampUpDown.Value - 1;
lastFrameTimestampUpDown.Minimum = firstFrameTimestampUpDown.Value + 1;
// Get the numeric value from the NumericUpDown control
int seconds = (int)firstFrameTimestampUpDown.Value;
// Convert the numeric value to a TimeSpan
TimeSpan time = TimeSpan.FromSeconds(seconds);
// Format the TimeSpan as "hh:mm:ss"
string formattedTime = string.Format("{0:D2}:{1:D2}:{2:D2}", time.Hours, time.Minutes, time.Seconds);
// Update the display
firstFrameTimestamp.Text = formattedTime;
// Update trackBar
if (radioButton1.Checked == true)
{
trackBar.Value = seconds;
}
}
private void LastFrameTimestampUpDown_ValueChanged(object sender, EventArgs e)
{
// Set minimum limit
lastFrameTimestampUpDown.Minimum = firstFrameTimestampUpDown.Value + 1;
firstFrameTimestampUpDown.Maximum = lastFrameTimestampUpDown.Value - 1;
// Get the numeric value from the NumericUpDown control
int seconds = (int)lastFrameTimestampUpDown.Value;
// Convert the numeric value to a TimeSpan
TimeSpan time = TimeSpan.FromSeconds(seconds);
// Format the TimeSpan as "hh:mm:ss"
string formattedTime = string.Format("{0:D2}:{1:D2}:{2:D2}", time.Hours, time.Minutes, time.Seconds);
// Update the display
lastFrameTimestamp.Text = formattedTime;
// Update trackBar
if (radioButton2.Checked == true)
{
trackBar.Value = seconds;
}
}
// restart 1st frame btn
private void StartVideoTimestampBtn_Click(object sender, EventArgs e)
{
firstFrameTimestampUpDown.Value = 0;
firstFrameTimestampUpDown.DownButton();
}
// restart last frame btn
private void EndVideoTimestampBtn_Click(object sender, EventArgs e)
{
lastFrameTimestampUpDown.Value = (decimal)totalDurationInSeconds;
lastFrameTimestampUpDown.UpButton();
}
private void formatPicker_SelectedIndexChanged(object sender, EventArgs e)
{
if (formatPicker.SelectedItem == null)
{
return;
}
outputFormat = formatPicker.SelectedItem.ToString();
// Create outputPath with videoName
outputPath = Path.Combine(outputFolder, videoName + "-%d." + outputFormat);
}
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
// move trackBar possition
trackBar.Value = (int)firstFrameTimestampUpDown.Value;
}
private void radioButton2_CheckedChanged(object sender, EventArgs e)
{
// move trackBar possition
trackBar.Value = (int)lastFrameTimestampUpDown.Value;
}
private void trackBar_Scroll(object sender, EventArgs e)
{
// move firstFrameTimestamp
if (radioButton1.Checked == true)
{
// maximum limmit
if (trackBar.Value > (int)firstFrameTimestampUpDown.Maximum)
{
trackBar.Value = (int)firstFrameTimestampUpDown.Maximum;
}
else
{
firstFrameTimestampUpDown.Value = trackBar.Value;
}
}
// move lastFrameTimestamp
else
{
// minimum limmit
if (trackBar.Value < (int)lastFrameTimestampUpDown.Minimum)
{
trackBar.Value = (int)lastFrameTimestampUpDown.Minimum;
}
else
{
lastFrameTimestampUpDown.Value = trackBar.Value;
}
}
}
private void EnableButtonForFrameSeaching()
{
firstFrameTimestampUpDown.Enabled = true;
lastFrameTimestampUpDown.Enabled = true;
startVideoTimestampBtn.Enabled = true;
endVideoTimestampBtn.Enabled = true;
trackBar.Enabled = true;
radioButton1.Enabled = true;
radioButton2.Enabled = true;
}
}
}