|
@@ -6,38 +6,102 @@ namespace Webmify {
|
|
|
public string input_file { get; private set; }
|
|
public string input_file { get; private set; }
|
|
|
public string output_dir { get; private set; }
|
|
public string output_dir { get; private set; }
|
|
|
public EncodingProfile profile { get; private set; }
|
|
public EncodingProfile profile { get; private set; }
|
|
|
|
|
+ public double duration { get; private set; }
|
|
|
|
|
|
|
|
- public signal void progress_changed(double fraction_change, bool completed);
|
|
|
|
|
|
|
+ public signal void progress_changed(double fraction, bool completed);
|
|
|
|
|
|
|
|
- public Encoder(File input, string output_dir, EncodingProfile profile) {
|
|
|
|
|
|
|
+ public Encoder(File input, string output_dir, EncodingProfile profile, double duration) {
|
|
|
input_file = input.get_path();
|
|
input_file = input.get_path();
|
|
|
this.output_dir = output_dir;
|
|
this.output_dir = output_dir;
|
|
|
this.profile = profile;
|
|
this.profile = profile;
|
|
|
|
|
+ this.duration = duration;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public void encode() throws Error {
|
|
public void encode() throws Error {
|
|
|
progress_changed(0.0, false);
|
|
progress_changed(0.0, false);
|
|
|
var output_file = get_output_file().get_path();
|
|
var output_file = get_output_file().get_path();
|
|
|
var first_command = profile.get_first_pass_command(input_file, output_file);
|
|
var first_command = profile.get_first_pass_command(input_file, output_file);
|
|
|
- if(first_command.length != 0) {
|
|
|
|
|
- var first_pass = new Subprocess.newv(first_command, GLib.SubprocessFlags.NONE);
|
|
|
|
|
- first_pass.wait();
|
|
|
|
|
- if(first_pass.get_exit_status() != 0) {
|
|
|
|
|
- throw new Error.literal(Quark.from_string("first-pass-failed"), 19, "Failed to analyse video");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ var second_command = profile.get_second_pass_command(input_file, output_file);
|
|
|
|
|
+
|
|
|
|
|
+ bool has_first = first_command.length > 0;
|
|
|
|
|
+ bool has_second = second_command.length > 0;
|
|
|
|
|
+ double second_start = has_first ? 0.5 : 0.0;
|
|
|
|
|
+
|
|
|
|
|
+ if(has_first) {
|
|
|
|
|
+ run_pass(first_command, 0.0, 0.5, "Failed to analyse video");
|
|
|
|
|
+ }
|
|
|
|
|
+ if(has_second) {
|
|
|
|
|
+ run_pass(second_command, second_start, 1.0, "Failed to encode video");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- progress_changed(0.5, false);
|
|
|
|
|
- var second_command = profile.get_second_pass_command(input_file, output_file);
|
|
|
|
|
- if(second_command.length != 0) {
|
|
|
|
|
- var second_pass = new Subprocess.newv(second_command, GLib.SubprocessFlags.NONE);
|
|
|
|
|
- second_pass.wait();
|
|
|
|
|
- if(second_pass.get_exit_status() != 0) {
|
|
|
|
|
- throw new Error.literal(Quark.from_string("second-pass-failed"), 19, "Failed to encode video");
|
|
|
|
|
|
|
+ progress_changed(1.0, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void run_pass(string[] command, double start, double end, string phase_label) throws Error {
|
|
|
|
|
+ var full_command = with_muted_output(command);
|
|
|
|
|
+ var proc = new Subprocess.newv(full_command,
|
|
|
|
|
+ GLib.SubprocessFlags.STDOUT_PIPE | GLib.SubprocessFlags.STDERR_PIPE);
|
|
|
|
|
+
|
|
|
|
|
+ var stderr_text = new StringBuilder();
|
|
|
|
|
+ var stderr_thread = new Thread<void>.try("ffmpeg-stderr", () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ var stderr_stream = new DataInputStream(proc.get_stderr_pipe());
|
|
|
|
|
+ string? line;
|
|
|
|
|
+ while((line = stderr_stream.read_line_utf8()) != null) {
|
|
|
|
|
+ stderr_text.append(line);
|
|
|
|
|
+ stderr_text.append_c('\n');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch(Error e) {
|
|
|
}
|
|
}
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ var stdout_stream = new DataInputStream(proc.get_stdout_pipe());
|
|
|
|
|
+ string? line;
|
|
|
|
|
+ while((line = stdout_stream.read_line_utf8()) != null) {
|
|
|
|
|
+ if(line.has_prefix("out_time_us=")) {
|
|
|
|
|
+ int64 out_time_us = int64.parse(line.substring("out_time_us=".length));
|
|
|
|
|
+ if(out_time_us < 0) {
|
|
|
|
|
+ out_time_us = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ double pass_fraction = duration > 0
|
|
|
|
|
+ ? ((double)out_time_us / 1000000.0) / duration
|
|
|
|
|
+ : 0.0;
|
|
|
|
|
+ pass_fraction = pass_fraction.clamp(0.0, 1.0);
|
|
|
|
|
+ double overall = (start + (end - start) * pass_fraction).clamp(0.0, 1.0);
|
|
|
|
|
+ progress_changed(overall, false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch(Error e) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ proc.wait();
|
|
|
|
|
+ if(stderr_thread != null) {
|
|
|
|
|
+ stderr_thread.join();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- progress_changed(0.5, true);
|
|
|
|
|
|
|
+ int exit_status = proc.get_exit_status();
|
|
|
|
|
+ if(exit_status != 0) {
|
|
|
|
|
+ string detail = stderr_text.str.strip();
|
|
|
|
|
+ string message = detail.length > 0
|
|
|
|
|
+ ? @"$phase_label: $detail"
|
|
|
|
|
+ : @"$phase_label (exit status $exit_status)";
|
|
|
|
|
+ throw new Error.literal(Quark.from_string("ffmpeg-failed"), exit_status, message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static string[] with_muted_output(string[] command) {
|
|
|
|
|
+ var result = new string[]{};
|
|
|
|
|
+ result += command[0];
|
|
|
|
|
+ result += "-hide_banner";
|
|
|
|
|
+ result += "-loglevel";
|
|
|
|
|
+ result += "error";
|
|
|
|
|
+ result += "-progress";
|
|
|
|
|
+ result += "pipe:1";
|
|
|
|
|
+ for(int i = 1; i < command.length; i++) {
|
|
|
|
|
+ result += command[i];
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public File get_output_file() {
|
|
public File get_output_file() {
|