diff --git a/.gitignore b/.gitignore
index b5a6689..1452a03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ dist/
src/recoder.egg-info/
*.gresource
+*.compiled
diff --git a/dev-run.sh b/dev-run.sh
new file mode 100755
index 0000000..8c2d40e
--- /dev/null
+++ b/dev-run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# Compile resources
+glib-compile-resources src/resources/resources.xml \
+ --target=src/recoder/resources.gresource \
+ --sourcedir=src/resources
+
+# Compile GSettings schemas (using correct path)
+glib-compile-schemas src/resources
+
+# Run the app with environment variables
+GSETTINGS_SCHEMA_DIR=$(pwd)/src/resources \
+PYTHONPATH=$(pwd)/src \
+python -m recoder.app
diff --git a/src/recoder/app.py b/src/recoder/app.py
index a1bc48a..f464675 100755
--- a/src/recoder/app.py
+++ b/src/recoder/app.py
@@ -18,7 +18,7 @@ def load_resources():
def main():
load_resources()
- from recoder.recoder_window import RecoderWindow # delayed import
+ from recoder.window import RecoderWindow # delayed import
class RecoderApp(Adw.Application):
def __init__(self):
diff --git a/src/recoder/preferences.py b/src/recoder/preferences.py
new file mode 100644
index 0000000..01a685e
--- /dev/null
+++ b/src/recoder/preferences.py
@@ -0,0 +1,58 @@
+import gi
+gi.require_version("Gtk", "4.0")
+gi.require_version("Adw", "1")
+from gi.repository import Gtk, Gio, Adw
+import re
+
+@Gtk.Template(resource_path="/net/jeena/recoder/preferences.ui")
+class RecoderPreferences(Adw.PreferencesWindow):
+ __gtype_name__ = "RecoderPreferences"
+
+ output_folder_entry = Gtk.Template.Child()
+
+ def __init__(self):
+ super().__init__()
+
+ self.settings = Gio.Settings.new("net.jeena.recoder.preferences")
+
+ current_value = self.settings.get_string("output-folder-template")
+ self.output_folder_entry.set_text(current_value)
+
+ self.output_folder_entry.connect("changed", self.on_output_folder_changed)
+ self.settings.connect("changed::output-folder-template", self.on_setting_changed)
+
+ def validate_template(self, text):
+ allowed_pattern = r'^[\w\s\-./~${}]+$'
+ if not re.match(allowed_pattern, text):
+ return False
+
+ if text.count("{{") != text.count("}}"):
+ return False
+
+ var_pattern = r'\{\{([a-zA-Z0-9_]+)\}\}'
+ for var in re.findall(r'\{\{.*?\}\}', text):
+ if not re.match(var_pattern, var):
+ return False
+
+ if '//' in text.replace('file://', ''):
+ return False
+
+ return True
+
+ def on_output_folder_changed(self, entry):
+ text = entry.get_text()
+
+ if self.validate_template(text):
+ self.settings.set_string("output-folder-template", text)
+ entry.remove_css_class("error")
+ else:
+ entry.add_css_class("error")
+
+
+
+
+ def on_setting_changed(self, settings, key):
+ if key == "output-folder-template":
+ new_val = settings.get_string(key)
+ if self.output_folder_entry.get_text() != new_val:
+ self.output_folder_entry.set_text(new_val)
diff --git a/src/recoder/recoder_window.py b/src/recoder/window.py
similarity index 78%
rename from src/recoder/recoder_window.py
rename to src/recoder/window.py
index abcdd6b..52ee2b7 100644
--- a/src/recoder/recoder_window.py
+++ b/src/recoder/window.py
@@ -14,9 +14,10 @@ from recoder.utils import extract_video_files, notify_done, play_complete_sound
from recoder.file_entry_row import FileEntryRow
from recoder.drop_handler import DropHandler
from recoder.app_state import AppState, AppStateManager, UIStateManager
+from recoder.preferences import RecoderPreferences
-@Gtk.Template(resource_path="/net/jeena/recoder/recoder_window.ui")
+@Gtk.Template(resource_path="/net/jeena/recoder/window.ui")
class RecoderWindow(Adw.ApplicationWindow):
__gtype_name__ = "RecoderWindow"
@@ -28,9 +29,18 @@ class RecoderWindow(Adw.ApplicationWindow):
btn_cancel = Gtk.Template.Child()
progress_bar = Gtk.Template.Child()
folder_label = Gtk.Template.Child()
+ btn_preferences = Gtk.Template.Child()
def __init__(self, application):
super().__init__(application=application)
+
+ self.state_settings = Gio.Settings.new("net.jeena.recoder.state")
+
+ # Bind window size and state to your window properties
+ self.state_settings.bind("width", self, "default-width", Gio.SettingsBindFlags.DEFAULT)
+ self.state_settings.bind("height", self, "default-height", Gio.SettingsBindFlags.DEFAULT)
+ self.state_settings.bind("is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT)
+ self.state_settings.bind("is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT)
self.file_items_to_process = []
self.current_folder_name = None
@@ -44,6 +54,7 @@ class RecoderWindow(Adw.ApplicationWindow):
self.btn_transcode.connect("clicked", self.on_transcode_clicked)
self.btn_cancel.connect("clicked", self.on_cancel_clicked)
+ self.btn_preferences.connect("clicked", self.on_preferences_clicked)
self.app_state_manager.state = AppState.IDLE
@@ -57,10 +68,24 @@ class RecoderWindow(Adw.ApplicationWindow):
Notify.init("Recoder")
- def process_drop_value(self, value):
+ self.preferences_window = None
- # value could be a list of Gio.File or a single Gio.File
- # Assuming it's a list:
+ def on_preferences_clicked(self, button):
+ if self.preferences_window is None:
+ self.preferences_window = RecoderPreferences()
+ self.preferences_window.set_transient_for(self)
+ self.preferences_window.set_modal(True)
+ self.preferences_window.connect("close-request", self.on_preferences_window_close)
+
+ self.preferences_window.present()
+
+ def on_preferences_window_close(self, window):
+ window.hide()
+ # Don't destroy, just hide
+ return True # stops further handlers, prevents default destruction
+
+
+ def process_drop_value(self, value):
folder_file = None
if isinstance(value, list) and len(value) > 0:
folder_file = value[0]
@@ -68,7 +93,6 @@ class RecoderWindow(Adw.ApplicationWindow):
folder_file = value
if folder_file:
- # Set the current folder name for UI
self.current_folder_name = folder_file.get_basename()
file_items = extract_video_files(value)
@@ -107,8 +131,6 @@ class RecoderWindow(Adw.ApplicationWindow):
if not self.file_items_to_process:
return
- # no need to remove drop_controller here
-
self.transcoder = Transcoder(self.file_items_to_process)
self.transcoder.connect("notify::batch-progress", self.on_transcoder_progress)
self.transcoder.connect("notify::batch-status", self.on_transcoder_status)
diff --git a/src/resources/net.jeena.recoder.gschema.xml b/src/resources/net.jeena.recoder.gschema.xml
new file mode 100644
index 0000000..e2ab4b2
--- /dev/null
+++ b/src/resources/net.jeena.recoder.gschema.xml
@@ -0,0 +1,36 @@
+
+
+
+ '{{source_folder_name}}-transcoded'
+ Template for output folder
+
+ Relative or absolute path template for transcoded files.
+ Supports {{source_folder_name}} as a variable.
+
+
+
+
+
+
+ 600
+ Window width
+ Last saved width of the main window.
+
+
+ 350
+ Window height
+ Last saved height of the main window.
+
+
+ false
+ Window maximized state
+ Whether the main window was maximized last time.
+
+
+ false
+ Window fullscreen state
+ Whether the main window was fullscreen last time.
+
+
+
+
diff --git a/src/resources/preferences.ui b/src/resources/preferences.ui
new file mode 100644
index 0000000..0215a49
--- /dev/null
+++ b/src/resources/preferences.ui
@@ -0,0 +1,52 @@
+
+
+
+ Preferences
+ 600
+ 400
+
+
+
+
+
+
diff --git a/src/resources/resources.xml b/src/resources/resources.xml
index 973c271..dad3728 100644
--- a/src/resources/resources.xml
+++ b/src/resources/resources.xml
@@ -1,6 +1,7 @@
- recoder_window.ui
+ window.ui
+ preferences.ui
file_entry_row.ui
style.css
diff --git a/src/resources/recoder_window.ui b/src/resources/window.ui
similarity index 70%
rename from src/resources/recoder_window.ui
rename to src/resources/window.ui
index 29de453..5ec4e0d 100644
--- a/src/resources/recoder_window.ui
+++ b/src/resources/window.ui
@@ -12,25 +12,31 @@
@@ -75,6 +81,7 @@
+
true