diff --git a/src/recoder/app.py b/src/recoder/app.py
index 557332a..a1bc48a 100755
--- a/src/recoder/app.py
+++ b/src/recoder/app.py
@@ -1,6 +1,5 @@
#!/usr/bin/env python3
import sys
-import os
import gi
gi.require_version('Gtk', '4.0')
@@ -19,18 +18,20 @@ def load_resources():
def main():
load_resources()
- from recoder.ui import RecoderWindow # delayed import
+ from recoder.recoder_window import RecoderWindow # delayed import
class RecoderApp(Adw.Application):
def __init__(self):
- super().__init__(application_id="net.jeena.Recoder",
- flags=Gio.ApplicationFlags.FLAGS_NONE)
+ super().__init__(
+ application_id="net.jeena.Recoder",
+ flags=Gio.ApplicationFlags.FLAGS_NONE
+ )
self.window = None
def do_activate(self):
if not self.window:
self.window = RecoderWindow(self)
- self.window.window.present()
+ self.window.present()
app = RecoderApp()
return app.run(sys.argv)
diff --git a/src/recoder/file_entry_row.py b/src/recoder/file_entry_row.py
new file mode 100644
index 0000000..91267e2
--- /dev/null
+++ b/src/recoder/file_entry_row.py
@@ -0,0 +1,48 @@
+import gi
+gi.require_version("Gtk", "4.0")
+from gi.repository import Gtk
+
+from recoder.models import FileStatus
+
+ICONS = {
+ FileStatus.WAITING: "media-playback-pause-symbolic",
+ FileStatus.PROCESSING: "view-refresh-symbolic",
+ FileStatus.DONE: "task-complete-symbolic",
+ FileStatus.ERROR: "dialog-error-symbolic",
+}
+
+LABELS = {
+ FileStatus.WAITING: "Waiting",
+ FileStatus.DONE: "Done",
+ FileStatus.ERROR: "Error",
+}
+
+@Gtk.Template(resource_path="/net/jeena/recoder/file_entry_row.ui")
+class FileEntryRow(Gtk.ListBoxRow):
+ __gtype_name__ = "FileEntryRow"
+
+ icon = Gtk.Template.Child()
+ label = Gtk.Template.Child()
+ progress_label = Gtk.Template.Child()
+ level_bar = Gtk.Template.Child()
+
+ def __init__(self, item):
+ super().__init__()
+ self.item = item
+ self.item.connect("notify::status", self.update_display)
+ self.item.connect("notify::progress", self.update_display)
+ self.update_display()
+
+ def update_display(self, *args):
+ basename = self.item.file.get_basename()
+ self.label.set_text(basename)
+
+ icon_name = ICONS.get(self.item.status, "object-select-symbolic")
+ self.icon.set_from_icon_name(icon_name)
+
+ if self.item.status == FileStatus.PROCESSING:
+ self.progress_label.set_text(f"{self.item.progress}%")
+ self.level_bar.set_value(self.item.progress)
+ else:
+ self.progress_label.set_text(LABELS.get(self.item.status, ""))
+ self.level_bar.set_value(100 if self.item.status == FileStatus.DONE else 0)
diff --git a/src/recoder/recoder_window.py b/src/recoder/recoder_window.py
new file mode 100644
index 0000000..4dffa69
--- /dev/null
+++ b/src/recoder/recoder_window.py
@@ -0,0 +1,118 @@
+import gi
+gi.require_version('Gtk', '4.0')
+gi.require_version('Adw', '1')
+gi.require_version('Notify', '0.7')
+
+from gi.repository import Gtk, Gdk, Gio, Adw, GLib, Notify
+from functools import partial
+
+from recoder.transcoder import Transcoder
+from recoder.utils import extract_video_files, notify_done, play_complete_sound
+from recoder.file_entry_row import FileEntryRow
+
+@Gtk.Template(resource_path="/net/jeena/recoder/recoder_window.ui")
+class RecoderWindow(Adw.ApplicationWindow):
+ __gtype_name__ = "RecoderWindow"
+
+ overlay = Gtk.Template.Child()
+ progress_bar = Gtk.Template.Child()
+ drop_hint = Gtk.Template.Child()
+ listbox = Gtk.Template.Child()
+ scrolled_window = Gtk.Template.Child()
+
+ def __init__(self, application):
+ super().__init__(application=application)
+
+ self.file_items_to_process = []
+ self.transcoder = None
+ self.file_rows = {}
+
+ self.drop_target = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY)
+ self.drop_target.connect("drop", self.on_drop)
+ self.drop_target.connect("enter", self.on_drop_enter)
+ self.drop_target.connect("leave", self.on_drop_leave)
+ self.add_controller(self.drop_target)
+
+ css_provider = Gtk.CssProvider()
+ css_provider.load_from_resource("/net/jeena/recoder/style.css")
+ Gtk.StyleContext.add_provider_for_display(
+ Gdk.Display.get_default(),
+ css_provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
+ )
+
+ Notify.init("Recoder")
+
+ def on_drop_enter(self, drop_target, x, y):
+ self.overlay.add_css_class("drop-highlight")
+ return True
+
+ def on_drop_leave(self, drop_target):
+ self.overlay.remove_css_class("drop-highlight")
+ return True
+
+ def on_drop(self, drop_target, value, x, y):
+ GLib.idle_add(partial(self.process_drop_value, value))
+ self.overlay.remove_overlay(self.drop_hint)
+ self.progress_bar.set_visible(True)
+ self.progress_bar.set_fraction(0.0)
+ self.drop_hint.set_visible(False)
+ return value
+
+ def process_drop_value(self, value):
+ file_items = extract_video_files(value)
+ if not file_items:
+ return False
+
+ self.clear_listbox()
+ self.file_rows.clear()
+
+ for file_item in file_items:
+ row = FileEntryRow(file_item)
+ self.listbox.append(row)
+ self.file_rows[file_item] = row
+
+ self.file_items_to_process = file_items
+ self.start_transcoding()
+ return False
+
+ def clear_listbox(self):
+ child = self.listbox.get_first_child()
+ while child:
+ next_child = child.get_next_sibling()
+ self.listbox.remove(child)
+ child = next_child
+
+ def start_transcoding(self):
+ if self.transcoder and self.transcoder.is_processing:
+ return
+
+ self.remove_controller(self.drop_target)
+
+ self.progress_bar.set_fraction(0.0)
+ self.progress_bar.set_text("Starting transcoding...")
+
+ self.transcoder = Transcoder(
+ self.file_items_to_process,
+ progress_callback=self.update_progress,
+ done_callback=self._done_callback,
+ file_done_callback=self.mark_file_done,
+ )
+ self.transcoder.start()
+
+ def update_progress(self, text, fraction):
+ self.progress_bar.set_show_text(True)
+ self.progress_bar.set_text(text)
+ self.progress_bar.set_fraction(fraction)
+
+ def mark_file_done(self, filepath):
+ if filepath in self.file_rows:
+ row = self.file_rows[filepath]
+ GLib.idle_add(row.mark_done)
+
+ def _done_callback(self):
+ self.progress_bar.set_show_text(True)
+ self.progress_bar.set_text("Transcoding Complete!")
+ play_complete_sound()
+ notify_done("Recoder", "Transcoding finished!")
+ self.add_controller(self.drop_target)
diff --git a/src/recoder/ui.py b/src/recoder/ui.py
deleted file mode 100644
index d566844..0000000
--- a/src/recoder/ui.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import os
-import gi
-gi.require_version('Gtk', '4.0')
-gi.require_version('Notify', '0.7')
-
-from gi.repository import Gtk, Gdk, Gio, GLib, Notify
-from functools import partial
-
-from recoder.transcoder import Transcoder
-from recoder.utils import extract_video_files, notify_done, play_complete_sound
-from recoder.models import FileStatus, FileItem
-
-class FileEntryRow(Gtk.ListBoxRow):
- def __init__(self, item):
- super().__init__()
- self.item = item
-
- self.icon = Gtk.Image.new_from_icon_name("object-select-symbolic")
- self.label = Gtk.Label(xalign=0, hexpand=True)
- self.progress_label = Gtk.Label(xalign=1)
-
- hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
- hbox.append(self.icon)
- hbox.append(self.label)
- hbox.append(self.progress_label)
- self.set_child(hbox)
-
- self.item.connect("notify::status", self.on_status_changed)
- self.item.connect("notify::progress", self.on_progress_changed)
-
- self.update_display()
-
- def on_status_changed(self, *_):
- self.update_display()
-
- def on_progress_changed(self, *_):
- self.update_display()
-
- def update_display(self):
- basename = self.item.file.get_basename()
- self.label.set_text(basename)
-
- match self.item.status:
- case FileStatus.WAITING:
- self.icon.set_from_icon_name("media-playback-pause-symbolic")
- self.progress_label.set_text("Waiting")
- case FileStatus.PROCESSING:
- self.icon.set_from_icon_name("view-refresh-symbolic")
- self.progress_label.set_text(f"{self.item.progress}%")
- case FileStatus.DONE:
- self.icon.set_from_icon_name("task-complete-symbolic")
- self.progress_label.set_text("Done")
- case FileStatus.ERROR:
- self.icon.set_from_icon_name("dialog-error-symbolic")
- self.progress_label.set_text("Error")
-
-
-class RecoderWindow:
- def __init__(self, application, **kwargs):
- self.file_items_to_process = []
- self.transcoder = None
- self.file_rows = {}
-
- builder = Gtk.Builder()
- builder.add_from_resource("/net/jeena/recoder/recoder.ui")
-
- self.window = builder.get_object("main_window")
- self.window.set_application(application)
-
- self.overlay = builder.get_object("overlay")
- self.progress = builder.get_object("progress_bar")
- self.drop_hint = builder.get_object("drop_hint")
- self.listbox = builder.get_object("listbox")
- self.scrolled_window = builder.get_object("scrolled_window")
-
- self.drop_target = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY)
- self.drop_target.connect("drop", self.on_drop)
- self.drop_target.connect("enter", self.on_drop_enter)
- self.drop_target.connect("leave", self.on_drop_leave)
- self.window.add_controller(self.drop_target)
-
- css_provider = Gtk.CssProvider()
- css_provider.load_from_resource("/net/jeena/recoder/style.css")
- Gtk.StyleContext.add_provider_for_display(
- Gdk.Display.get_default(),
- css_provider,
- Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
- )
-
- Notify.init("Recoder")
-
- def on_drop_enter(self, drop_target, x, y):
- self.overlay.add_css_class("drop-highlight")
- return True
-
- def on_drop_leave(self, drop_target):
- self.overlay.remove_css_class("drop-highlight")
- return True
-
- def on_drop(self, drop_target, value, x, y):
- GLib.idle_add(partial(self.process_drop_value, value))
- self.overlay.remove_overlay(self.drop_hint)
- self.progress.set_visible(True)
- self.progress.set_fraction(0.0)
- self.drop_hint.set_visible(False)
- return value
-
- def process_drop_value(self, value):
- file_items = extract_video_files(value)
- if not file_items:
- return False
-
- # Clear previous rows
- self.clear_listbox()
- self.file_rows.clear()
-
- for file_item in file_items:
- row = FileEntryRow(file_item)
- self.listbox.append(row)
- self.file_rows[file_item] = row
-
- self.file_items_to_process = file_items
- self.start_transcoding()
- return False
-
- def clear_listbox(self):
- child = self.listbox.get_first_child()
- while child:
- next_child = child.get_next_sibling()
- self.listbox.remove(child)
- child = next_child
-
- def start_transcoding(self):
- if self.transcoder and self.transcoder.is_processing:
- return
-
- self.window.remove_controller(self.drop_target)
-
- self.progress.set_fraction(0.0)
- self.progress.set_text("Starting transcoding...")
-
- self.transcoder = Transcoder(
- self.file_items_to_process,
- progress_callback=self.update_progress,
- done_callback=self._done_callback,
- file_done_callback=self.mark_file_done,
- )
- self.transcoder.start()
-
- def update_progress(self, text, fraction):
- self.progress.set_show_text(True)
- self.progress.set_text(text)
- self.progress.set_fraction(fraction)
-
- def mark_file_done(self, filepath):
- if filepath in self.file_rows:
- row = self.file_rows[filepath]
- GLib.idle_add(row.mark_done)
-
- def _done_callback(self):
- self.progress.set_show_text(True)
- self.progress.set_text("Transcoding Complete!")
- play_complete_sound()
- notify_done("Recoder", "Transcoding finished!")
- self.window.add_controller(self.drop_target)
diff --git a/src/resources/file_entry_row.ui b/src/resources/file_entry_row.ui
new file mode 100644
index 0000000..fbdd5c8
--- /dev/null
+++ b/src/resources/file_entry_row.ui
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
diff --git a/src/resources/recoder.ui b/src/resources/recoder_window.ui
similarity index 85%
rename from src/resources/recoder.ui
rename to src/resources/recoder_window.ui
index c54c8ec..d84dee0 100644
--- a/src/resources/recoder.ui
+++ b/src/resources/recoder_window.ui
@@ -3,38 +3,48 @@
-
+
Recoder
700
400
-
+
+
+
vertical
6
true
+
true
true
+
true
true
-
+
+
none
true
+ True
+
+
📂 Drop files here to get started
@@ -47,6 +57,7 @@
+
true
@@ -56,6 +67,6 @@
-
-
+
+
diff --git a/src/resources/resources.xml b/src/resources/resources.xml
index 40892f6..973c271 100644
--- a/src/resources/resources.xml
+++ b/src/resources/resources.xml
@@ -1,6 +1,7 @@
- recoder.ui
+ recoder_window.ui
+ file_entry_row.ui
style.css
diff --git a/src/resources/style.css b/src/resources/style.css
index d6d1f4a..5d995f3 100644
--- a/src/resources/style.css
+++ b/src/resources/style.css
@@ -7,7 +7,3 @@
font-size: 16px;
color: @theme_unfocused_fg_color;
}
-
-.debug-row {
- background: red;
-}
\ No newline at end of file