Kaynağa Gözat

Video editor polish

Billy Barrow 2 yıl önce
ebeveyn
işleme
4e17e5967b

+ 52 - 13
src/Editor.vala

@@ -21,11 +21,15 @@ namespace Publicate {
         private Button save_tab_button;
         private Button remove_file_button;
 
+        private SaveProgress progress_window;
+
         private Gee.HashMap<string, Editors.EditorWidget> open_editors = new Gee.HashMap<string, Editors.EditorWidget>();
 
         public PpubEditor(ViewerWindow win) {
             window = win;
 
+            progress_window = new SaveProgress(window);
+
             orientation = Orientation.VERTICAL;
             header = new Adw.HeaderBar();
             append(header);
@@ -120,10 +124,14 @@ namespace Publicate {
                 var editor = new Editors.PlainTextEditor(window, tab_view);
                 add_editor(editor, asset);
             }
-            if(asset.mimetype == "application/x-ppub-metadata") {
+            else if(asset.mimetype == "application/x-ppub-metadata") {
                 var editor = new Editors.MetadataEditor(window, tab_view);
                 add_editor(editor, asset);
             }
+            if(asset.mimetype == "application/x-ppvm") {
+                var editor = new Editors.VideoManifestEditor(window, tab_view);
+                add_editor(editor, asset);
+            }
 
         }
 
@@ -164,22 +172,53 @@ namespace Publicate {
         }
 
         private async void save(Invercargill.Enumerable<Savable> to_save) {
+            progress_window.display_progress("Saving…", 0.0);
+            yield do_save(to_save);
+            progress_window.complete();
+        }
 
-            var builder = new Ppub.Builder();
-            foreach (var item in to_save) {
-                item.save_asset(builder);
-            }
-
-            var temp_file = File.new_for_path(publication_file.get_path() + ".tmp");
-            var stream = yield temp_file.replace_async(null, false, FileCreateFlags.REPLACE_DESTINATION, Priority.DEFAULT);
+        private async void do_save(Invercargill.Enumerable<Savable> to_save) {
 
-            builder.write(stream);
-            yield stream.close_async(Priority.DEFAULT, null);
+            SourceFunc callback = do_save.callback;
             
-            yield temp_file.move_async(publication_file, FileCopyFlags.OVERWRITE, Priority.DEFAULT, null, null);
-            yield load_ppub(publication_file);
+            ThreadFunc<bool> run = () => {
+                var items = to_save.count();
+                var count = 0;
+                var builder = new Ppub.Builder();
+                foreach (var item in to_save) {
+                    count++;
+                    item.save_asset(builder);
+                    Idle.add(() => {
+                        progress_window.display_progress("Processing Assets…", (double)count / (double)items);
+                        return false;
+                    });
+                }
+    
+                Idle.add(() => {
+                    progress_window.display_progress("Writing PPUB…", 1.0);
+                    return false;
+                });
+
+                var temp_file = File.new_for_path(publication_file.get_path() + ".tmp");
+                var stream = temp_file.replace(null, false, FileCreateFlags.REPLACE_DESTINATION);
+    
+                builder.write(stream);
+                stream.close();
+                
+                temp_file.move(publication_file, FileCopyFlags.OVERWRITE);
+
+                Idle.add(() => {
+                    load_ppub.begin(publication_file, () => {
+                        select_file_from_tab();
+                        callback();
+                    });
+                    return false;
+                });
+                return true;
+            };
+            new Thread<bool>("saver thread", run);
+            yield;
 
-            select_file_from_tab();
         }
 
         public async void save_tab() {

+ 1 - 1
src/Editors/MarkdownEditor.vala

@@ -119,7 +119,7 @@ namespace Publicate.Editors {
             set_tab_name(false);
         }
 
-        public override async void save_asset(Ppub.Builder builder) {
+        public void save_asset(Ppub.Builder builder) {
             var article_data = get_text().to_utf8();
             var article_stream = new MemoryInputStream.from_data((uint8[])article_data);
             var compression_info = new Ppub.CompressionInfo(article_stream);

+ 1 - 1
src/Editors/MetadataEditor.vala

@@ -142,7 +142,7 @@ namespace Publicate.Editors {
             return stripped;
         }
 
-        public async void save_asset (Ppub.Builder builder) {
+        public void save_asset (Ppub.Builder builder) {
             metadata.title = process_string(title.text);
 
             var email = author_email.text.chomp().chug();

+ 1 - 1
src/Editors/PlainTextEditor.vala

@@ -69,7 +69,7 @@ namespace Publicate.Editors {
             set_tab_name(false);
         }
 
-        public override async void save_asset(Ppub.Builder builder) {
+        public override void save_asset(Ppub.Builder builder) {
             var article_data = get_text().to_utf8();
             var article_stream = new MemoryInputStream.from_data((uint8[])article_data);
             var compression_info = new Ppub.CompressionInfo(article_stream);

+ 167 - 0
src/Editors/VideoManifestEditor.vala

@@ -0,0 +1,167 @@
+using Gtk;
+using Adw;
+using GtkCommonMark;
+using Invercargill;
+
+namespace Publicate.Editors {
+
+    public class VideoManifestEditor : Box, EditorWidget, Savable {
+
+        private Ppub.VideoManifest manifest;
+        private Ppub.Asset asset;
+        private TabPage page;
+
+        private FileChooserRow master;
+        private EntryRow title;
+        private EntryRow author;
+        private FileChooserRow poster;
+        private FileChooserRow description_file;
+
+        private FileChooserRow licence;
+        private EntryRow copyright;
+
+        private ViewerWindow window;
+
+        private bool unsaved = false;
+        public bool has_unsaved_changes { get{
+            return unsaved;
+        } }
+
+
+        public VideoManifestEditor(ViewerWindow win, TabView tab_view) {
+            window = win;
+
+            var scroll_window = new ScrolledWindow ();
+            scroll_window.hexpand = true;
+            scroll_window.vexpand = true;
+            var box = new Box(Orientation.VERTICAL, 0);
+            scroll_window.child = box;
+            append(scroll_window);
+
+
+            var group = new PreferencesGroup();
+            group.margin_bottom = 18;
+            group.margin_end = 18;
+            group.margin_start = 18;
+            group.margin_top = 18;
+            group.title = "Publication Details";
+            group.description = "";
+
+            master = new FileChooserRow (window);
+            master.title = "Video Master";
+            master.asset_selected.connect (something_changed);
+            group.add(master);
+
+            title = new EntryRow ();
+            title.title = "Title";
+            title.changed.connect (something_changed);
+            group.add(title);
+
+            author = new EntryRow ();
+            author.title = "Author";
+            author.changed.connect (something_changed);
+            group.add(author);
+
+            poster = new FileChooserRow (window);
+            poster.title = "Thumbnail";
+            poster.asset_selected.connect (something_changed);
+            group.add(poster);
+
+            description_file = new FileChooserRow (window);
+            description_file.title = "Description";
+            description_file.asset_selected.connect (something_changed);
+            group.add(description_file);
+
+            box.append(group);
+
+            var group2 = new PreferencesGroup();
+            group2.margin_bottom = 18;
+            group2.margin_end = 18;
+            group2.margin_start = 18;
+            group2.margin_top = 18;
+            group2.title = "Licence and Copyright";
+            group2.description = "";
+
+            copyright = new EntryRow ();
+            copyright.title = "Copyright Notice";
+            copyright.changed.connect (something_changed);
+            group2.add(copyright);
+
+            licence = new FileChooserRow (window);
+            licence.title = "Licence";
+            licence.asset_selected.connect (something_changed);
+            group2.add(licence);
+
+            box.append(group2);
+
+            this.page = tab_view.add_page (this, null);
+        }
+
+        public async void load_asset (Ppub.Publication publication, Ppub.Asset asset) {
+            this.asset = asset;
+
+            var stream = publication.read_asset (asset.name);
+            manifest = new Ppub.VideoManifest.from_stream(stream);
+
+            poster.set_assets(publication.assets.where(a => a.mimetype.has_prefix ("image/")));
+            licence.set_assets(publication.assets.where(a => a.mimetype.has_prefix ("text/")));
+            master.set_assets(publication.assets.where(a => a.mimetype.has_prefix("video/")));
+            description_file.set_assets(publication.assets.where(a => a.mimetype.has_prefix("text/")));
+
+            master.set_selected_item (manifest.master ?? "");
+            title.text = manifest.title ?? "";
+            author.text = manifest.author ?? "";
+            poster.set_selected_item (manifest.poster ?? "");
+            description_file.set_selected_item (manifest.description_file_name ?? "");
+
+            copyright.text = manifest.copyright ?? "";
+            licence.set_selected_item (manifest.licence ?? "");
+
+            set_changed(false);
+        }
+
+        public string asset_name { get {
+            return asset.name;
+        } }
+
+        public Adw.TabPage tab_page { get {
+            return page;
+        }}
+
+        private string? process_string(string str) {
+            var stripped = str.chomp().chug();
+            if(stripped == "") {
+                return null;
+            }
+            return stripped;
+        }
+
+        public void save_asset (Ppub.Builder builder) {
+            manifest.title = process_string(title.text);
+            manifest.author = process_string(author.text);
+
+            manifest.poster = process_string (poster.selected_asset);
+            manifest.description_file_name = process_string(description_file.selected_asset);
+            manifest.copyright = process_string(copyright.text);
+            manifest.licence = process_string (licence.selected_asset);
+
+            var stream = new MemoryInputStream.from_data((uint8[])manifest.to_string().to_utf8());
+            var compression_info = new Ppub.CompressionInfo(stream);
+            stream.seek(0, SeekType.SET);
+
+            builder.add_asset(asset.name, asset.mimetype, stream, Invercargill.empty<string>(), compression_info);
+            set_changed(false);
+        }
+
+        private void set_changed(bool changed) {
+            this.page.title = changed ? @"⏺  $(manifest.title) Video Manifest" : @"$(manifest.title) Video Manifest";
+            unsaved = changed;
+        }
+
+        private void something_changed() {
+            set_changed(true);
+        }
+    }
+
+
+}

+ 3 - 3
src/Savable.vala

@@ -1,7 +1,7 @@
 
 namespace Publicate {
     public interface Savable : Object {
-        public abstract async void save_asset(Ppub.Builder builder) throws Error;
+        public abstract void save_asset(Ppub.Builder builder) throws Error;
     }
 
     public class SavableAsset : Object, Savable {
@@ -14,7 +14,7 @@ namespace Publicate {
             this.asset = asset;
         }
 
-        public async void save_asset(Ppub.Builder builder) throws Error {
+        public void save_asset(Ppub.Builder builder) throws Error {
             var stream = publication.read_asset(asset.name);
             var compression = new Ppub.CompressionInfo(stream);
             stream = publication.read_asset(asset.name);
@@ -37,7 +37,7 @@ namespace Publicate {
             this.compression = compression;
         }
 
-        public async void save_asset(Ppub.Builder builder) throws Error {
+        public void save_asset(Ppub.Builder builder) throws Error {
             builder.add_asset(name, mimetype, stream, Invercargill.empty<string>(), compression);
         }
     }

+ 53 - 0
src/SaveProgress.vala

@@ -0,0 +1,53 @@
+using Gtk;
+using Adw;
+
+namespace Publicate {
+
+
+    public class SaveProgress : Adw.Window {
+
+        private ProgressBar progress_bar;
+        private Label label; 
+        private ViewerWindow window;
+
+        public SaveProgress(ViewerWindow window) {
+            this.window = window;
+
+            var hbox = new Box(Orientation.HORIZONTAL, 8);
+            hbox.margin_bottom = 18;
+            hbox.margin_top = 18;
+            hbox.margin_start = 18;
+            hbox.margin_end = 18;
+
+            this.resizable = false;
+
+            var vbox = new Box(Orientation.VERTICAL, 8);
+
+            hbox.append (vbox);
+            content = hbox;
+
+            label = new Label("Please Wait…");
+            progress_bar = new ProgressBar ();
+
+            vbox.append (label);
+            vbox.append(progress_bar);
+
+        }
+
+        public void display_progress(string text, double fraction) {
+            if(!visible) {
+                this.modal = true;
+                this.transient_for = window;
+                present();
+            }
+            progress_bar.fraction = fraction;
+            label.label = text;
+        }
+
+        public void complete() {
+            hide();
+        }
+
+    }
+
+}

+ 2 - 0
src/Video/VideoProcessor.vala

@@ -12,6 +12,8 @@ namespace Publicate.Video {
 
         public VideoProcessor() {
 
+            this.resizable = false;
+
             var hbox = new Box(Orientation.HORIZONTAL, 8);
             hbox.margin_bottom = 18;
             hbox.margin_top = 18;

+ 89 - 13
src/Wizards/Video.vala

@@ -9,6 +9,7 @@ namespace Publicate.Wizards {
         private EntryRow title;
         private EntryRow author;
         private EntryRow author_email;
+        private ThumbnailChooserRow thumbnail_file;
 
         private ViewerWindow window;
 
@@ -31,6 +32,10 @@ namespace Publicate.Wizards {
             video_file.title = "Video";
             group.add(video_file);
 
+            thumbnail_file = new ThumbnailChooserRow(window);
+            thumbnail_file.title = "Thumbnail";
+            group.add(thumbnail_file);
+
             title = new EntryRow ();
             title.title = "Title";
             group.add(title);
@@ -95,12 +100,18 @@ namespace Publicate.Wizards {
             var processor = new Video.VideoProcessor();
             var result = yield processor.process_video(video_file.selected_file.get_path(), window);
 
-            var builder = new Ppub.Builder();
-            builder.add_metadata(metadata);
-
             result.manifest.author = metadata.author;
             result.manifest.title = metadata.title;
             result.manifest.description_file_name = "video_description.md";
+            result.manifest.master = result.video_files.first_or_default()?.get_basename();
+
+            if(thumbnail_file.selected_file != null) {
+                result.manifest.poster = thumbnail_file.selected_file.get_basename();
+                metadata.poster = thumbnail_file.selected_file.get_basename();
+            }
+
+            var builder = new Ppub.Builder();
+            builder.add_metadata(metadata);
 
             var manifest_data = result.manifest.to_string();
             print(manifest_data);
@@ -117,17 +128,14 @@ namespace Publicate.Wizards {
 
             builder.add_asset("video_description.md", "text/markdown", description_stream, Invercargill.empty<string>(), compression_info);
 
+            if(thumbnail_file.selected_file != null) {
+                yield add_file(builder, thumbnail_file.selected_file);
+                print("Added file\n");
+            }
+
             foreach (var result_file in result.video_files) {
-                var stream = yield result_file.read_async(Priority.DEFAULT, null);
-                var compression = new Ppub.CompressionInfo(stream, false);
-                stream.seek(0, SeekType.SET, null);
-                var sample = new uint8[2048];
-                size_t sample_size;
-                yield stream.read_all_async(sample, Priority.DEFAULT, null, out sample_size);
-                stream.seek(0, SeekType.SET, null);
-    
-                var mimetype = Ppub.guess_mimetype(result_file.get_basename(), sample);
-                builder.add_asset(result_file.get_basename(), mimetype, stream, Invercargill.empty<string>(), compression);
+                yield add_file(builder, result_file);
+                print("Added video file\n");
             }
 
             var output_stream = file.replace(null, false, FileCreateFlags.REPLACE_DESTINATION);
@@ -146,6 +154,19 @@ namespace Publicate.Wizards {
             video_file.reset();
         }
 
+        private static async void add_file(Ppub.Builder builder, File file) throws Error {
+            var stream = yield file.read_async(Priority.DEFAULT, null);
+            var compression = new Ppub.CompressionInfo(stream, false);
+            stream.seek(0, SeekType.SET, null);
+            var sample = new uint8[2048];
+            size_t sample_size;
+            yield stream.read_all_async(sample, Priority.DEFAULT, null, out sample_size);
+            stream.seek(0, SeekType.SET, null);
+
+            var mimetype = Ppub.guess_mimetype(file.get_basename(), sample);
+            builder.add_asset(file.get_basename(), mimetype, stream, Invercargill.empty<string>(), compression);
+        }
+
     }
 
     private class VideoChooserRow : ActionRow {
@@ -215,4 +236,59 @@ namespace Publicate.Wizards {
 
     }
 
+    private class ThumbnailChooserRow : ActionRow {
+
+        private ViewerWindow toplevel;
+
+        public File? selected_file {get; set;}
+
+        public ThumbnailChooserRow(ViewerWindow window) {
+            toplevel = window;
+            reset();
+
+            var button = new Button.from_icon_name("document-open-symbolic");
+            button.clicked.connect(() => open_video.begin());
+            button.margin_bottom = 8;
+            button.margin_top = 8;
+            button.add_css_class("flat");
+            add_suffix(button);
+
+            activatable_widget = button;
+            activatable = true;
+        }
+
+        public void reset() {
+            selected_file = null;
+            subtitle = "Not selected";
+        }
+
+        public async void open_video() throws Error {
+
+            var dialog = new FileDialog();
+            var filters = new GLib.ListStore(Type.OBJECT);
+            var video_filter = new FileFilter();
+            var all_filter = new FileFilter();
+            filters.append(video_filter);
+            filters.append(all_filter);
+            video_filter.add_mime_type("image/jpeg");
+            video_filter.add_mime_type("image/png");
+            video_filter.name = "Image Files";
+            all_filter.add_pattern("*");
+            all_filter.name = "All Files";
+
+            dialog.filters = filters;
+            dialog.set_initial_file(selected_file);
+            var file = yield dialog.open(toplevel, null);
+
+            if(file == null) {
+                return;
+            }
+            
+            selected_file = file;
+            subtitle = selected_file.get_basename();
+        }
+
+
+    }
+
 }

+ 2 - 0
src/meson.build

@@ -8,6 +8,7 @@ sources += files('ZoomSpinButton.vala')
 sources += files('StartupMenu.vala')
 sources += files('FileExplorer.vala')
 sources += files('Editor.vala')
+sources += files('SaveProgress.vala')
 sources += files('Savable.vala')
 sources += files('FileChooser.vala')
 sources += files('FileCreationPopover.vala')
@@ -15,6 +16,7 @@ sources += files('Editors/EditorWidget.vala')
 sources += files('Editors/MarkdownEditor.vala')
 sources += files('Editors/PlainTextEditor.vala')
 sources += files('Editors/MetadataEditor.vala')
+sources += files('Editors/VideoManifestEditor.vala')
 sources += files('Editors/UnsupportedEditor.vala')
 sources += files('Wizards/Wizard.vala')
 sources += files('Wizards/Standard.vala')