Browse Source

Update dependencies, improve output

Billy Barrow 1 month ago
parent
commit
d609938aa7
3 changed files with 119 additions and 46 deletions
  1. 38 29
      src/Application.vala
  2. 80 16
      src/Encoder.vala
  3. 1 1
      src/meson.build

+ 38 - 29
src/Application.vala

@@ -43,35 +43,44 @@ namespace Webmify {
 
             // Cooldown to allow for file to fully copy
             Thread.usleep(30000000);
-            
-            var video_file = File.new_for_path(path);
-            var video_info = new VideoInfo(video_file);
-            video_info.read_info();
-            print(@"[Webmify] Read metadata for \"$filename\"\n");
-
-            var profiles = construct_profiles();
-            var profile = profiles.first(p => p.suitable_for(video_info));
-
-            profile.setup_for(video_info);
-            var encoder = new Encoder(video_file, work_dir, profile);
-            encoder.progress_changed.connect((frac, done) => {
-                if(!done) {
-                    print(@"[Webmify] Encode \"$filename\": $(frac*100)%\n");
-                }
-                else {
-                    print(@"[Webmify] Encode \"$filename\": complete!\n");
-                }
-            });
-
-            print(@"[Webmify] Begin encoding \"$filename\" as VP9/WebM using profile $(profile.version_label())\n");
-            encoder.encode();
-
-            var output_path = encoder.get_output_file();
-            print(@"[Webmify] Copy \"$(Path.get_basename(output_path.get_path()))\" to output directory\n");
-            output_path.copy(File.new_build_filename(output_dir, Path.get_basename(output_path.get_path())), FileCopyFlags.ALL_METADATA);
-            output_path.delete();
-            video_file.delete();
-            print(@"[Webmify] Conversion complete: $filename -> $(Path.get_basename(output_path.get_path()))\n");
+
+            try {
+                var video_file = File.new_for_path(path);
+                var video_info = new VideoInfo(video_file);
+                video_info.read_info();
+                print(@"[Webmify] Read metadata for \"$filename\"\n");
+
+                var profiles = construct_profiles();
+                var profile = profiles.first(p => p.suitable_for(video_info));
+
+                profile.setup_for(video_info);
+                var encoder = new Encoder(video_file, work_dir, profile, video_info.duration);
+
+                int last_percent = -1;
+                encoder.progress_changed.connect((frac, done) => {
+                    if(done) {
+                        print(@"[Webmify] Encode \"$filename\": complete!\n");
+                    } else {
+                        int percent = (int)(frac * 100);
+                        if(percent != last_percent) {
+                            last_percent = percent;
+                            print(@"[Webmify] Encode \"$filename\": $percent%\n");
+                        }
+                    }
+                });
+
+                print(@"[Webmify] Begin encoding \"$filename\" as VP9/WebM using profile $(profile.version_label())\n");
+                encoder.encode();
+
+                var output_path = encoder.get_output_file();
+                print(@"[Webmify] Copy \"$(Path.get_basename(output_path.get_path()))\" to output directory\n");
+                output_path.copy(File.new_build_filename(output_dir, Path.get_basename(output_path.get_path())), FileCopyFlags.ALL_METADATA);
+                output_path.delete();
+                video_file.delete();
+                print(@"[Webmify] Conversion complete: $filename -> $(Path.get_basename(output_path.get_path()))\n");
+            } catch(Error e) {
+                printerr(@"[Webmify] Error processing \"$filename\": $(e.message)\n");
+            }
         }, workers);
 
         assert_not_reached();

+ 80 - 16
src/Encoder.vala

@@ -6,38 +6,102 @@ namespace Webmify {
         public string input_file { get; private set; }
         public string output_dir { 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();
             this.output_dir = output_dir;
             this.profile = profile;
+            this.duration = duration;
         }
 
         public void encode() throws Error {
             progress_changed(0.0, false);
             var output_file = get_output_file().get_path();
             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() {

+ 1 - 1
src/meson.build

@@ -12,7 +12,7 @@ dependencies = [
     dependency('json-glib-1.0'),
     dependency('gio-2.0'),
     dependency('gee-0.8'),
-    dependency('invercargill'),
+    dependency('invercargill-1'),
 ]
 
 deps = dependencies