Add docs and Help link + Toasts

This commit is contained in:
Jeena 2025-06-08 21:48:52 +09:00
parent 6c4f43a947
commit aa21483bd1
10 changed files with 225 additions and 118 deletions

74
docs/HELP.md Normal file
View file

@ -0,0 +1,74 @@
<p align="center">
<img src="../src/resources/net.jeena.Recoder.svg" width="120" height="120" alt="Recoder logo">
</p>
# Recoder — Help Guide
Recoder is a minimal, user-friendly tool for batch video transcoding. This quick guide walks you through using the app.
---
## 🚀 Getting Started
When you open Recoder, youll see a prompt inviting you to drop a video file or folder:
![Initial View](screenshot-1.png)
### 📂 Dropping Files or Folders
- You can drop **one video file** or **one folder** containing video files onto the app.
- The folder can have subdirectories, but Recoder will **not** process files recursively. Only files in the dropped folder itself will be processed.
- Non-video files will be ignored.
### 🔧 Preparing to Transcode
After you drop a folder into Recoder, it will list all the video files it found:
![Folder Loaded](./screenshot-2.png)
- A blue **Transcode** button appears once the files are ready to process.
- The **Clear icon** is always available — click it to cancel everything and reset the app if you're done or need to start over.
- The **menu button** gives access to Preferences and Help.
---
## 🎬 Transcoding
Click the Transcode button to start processing. While transcoding:
![Transcoding in Progress](./screenshot-3.png)
- The blue **Transcode** button is replaced by a **Pause** button, allowing you to temporarily stop the process.
- If paused, the button changes to **Resume**, so you can continue when you're ready.
- The **Clear button** can also be used during transcoding to cancel the process entirely and clear the current session.
By default:
- Transcoded files are saved into the same directory as the source, inside a subfolder named `{{source_folder_name}}-transcoded`.
- File names remain the same as the originals but with a `.mov` extension.
---
### ⚙️ Preferences
In Preferences, you can customize the single **output folder path** where transcoded files will be saved. This path controls both the folders location and name. You can use:
- `{{source_folder_name}}` to reuse the original folder name
- Relative paths like `../done/`
- Absolute paths like `/mnt/Export/`
- `~` to refer to your home directory
- Simple names like `output` to create a folder inside the source folder
- Any combination of the above, e.g. `../{{source_folder_name}}-dnxhd`
![Preferences](screenshot-4.png)
---
## 💡 Notes
- Make sure you have enough free space on your drive because both the original and transcoded files are kept, and transcoded files may be larger.
---
If you need more help, check the [GitHub repository](https://github.com/jeena/recoder) or open an issue.

BIN
docs/screenshot-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/screenshot-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/screenshot-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/screenshot-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -55,6 +55,11 @@ def main():
self.add_action(preferences_action) self.add_action(preferences_action)
self.set_accels_for_action("app.preferences", ["<Primary>comma"]) self.set_accels_for_action("app.preferences", ["<Primary>comma"])
help_action = Gio.SimpleAction.new("help", None)
help_action.connect("activate", self.on_help_activated)
self.add_action(help_action)
self.set_accels_for_action("app.help", ["F1"])
about_action = Gio.SimpleAction.new("about", None) about_action = Gio.SimpleAction.new("about", None)
about_action.connect("activate", self.on_about_activate) about_action.connect("activate", self.on_about_activate)
self.add_action(about_action) self.add_action(about_action)
@ -89,6 +94,9 @@ def main():
def on_preferences_close(self, window): def on_preferences_close(self, window):
window.set_visible(False) window.set_visible(False)
if window.prefs_changed:
window.prefs_changed = False
self.window.toast_overlay.add_toast(Adw.Toast.new("Preferences saved"))
# Don't destroy, just hide # Don't destroy, just hide
return True # stops further handlers, prevents default destruction return True # stops further handlers, prevents default destruction
@ -96,6 +104,16 @@ def main():
self.quit() self.quit()
return False # allow default handler to proceed return False # allow default handler to proceed
def on_help_activated(self, action, param):
uri = "https://github.com/jeena/recoder/blob/master/docs/HELP.md"
try:
Gio.AppInfo.launch_default_for_uri(uri, None)
self.window.toast_overlay.add_toast(Adw.Toast.new("Opening help in browser…"))
except GLib.Error as e:
self.window.toast_overlay.add_toast(Adw.Toast.new(f"Failed to open help: {e.message}"))
app = RecoderApp() app = RecoderApp()
return app.run(sys.argv) return app.run(sys.argv)

View file

@ -13,6 +13,7 @@ class RecoderPreferences(Adw.PreferencesWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.prefs_changed = False
self.settings = Gio.Settings.new("net.jeena.recoder.preferences") self.settings = Gio.Settings.new("net.jeena.recoder.preferences")
current_value = self.settings.get_string("output-folder-template") current_value = self.settings.get_string("output-folder-template")
@ -44,13 +45,11 @@ class RecoderPreferences(Adw.PreferencesWindow):
if self.validate_template(text): if self.validate_template(text):
self.settings.set_string("output-folder-template", text) self.settings.set_string("output-folder-template", text)
self.prefs_changed = True
entry.remove_css_class("error") entry.remove_css_class("error")
else: else:
entry.add_css_class("error") entry.add_css_class("error")
def on_setting_changed(self, settings, key): def on_setting_changed(self, settings, key):
if key == "output-folder-template": if key == "output-folder-template":
new_val = settings.get_string(key) new_val = settings.get_string(key)

View file

@ -22,6 +22,7 @@ from recoder.app import APP_NAME
class RecoderWindow(Adw.ApplicationWindow): class RecoderWindow(Adw.ApplicationWindow):
__gtype_name__ = "RecoderWindow" __gtype_name__ = "RecoderWindow"
toast_overlay = Gtk.Template.Child()
overlay = Gtk.Template.Child() overlay = Gtk.Template.Child()
drop_hint = Gtk.Template.Child() drop_hint = Gtk.Template.Child()
listbox = Gtk.Template.Child() listbox = Gtk.Template.Child()
@ -96,6 +97,10 @@ class RecoderWindow(Adw.ApplicationWindow):
self.file_items_to_process = file_items self.file_items_to_process = file_items
self.app_state_manager.state = AppState.FILES_LOADED self.app_state_manager.state = AppState.FILES_LOADED
count = len(self.file_items_to_process)
toast = Adw.Toast.new(f"{count} video file{'s' if count != 1 else ''} added")
self.toast_overlay.add_toast(toast)
return False return False
def clear_listbox(self): def clear_listbox(self):
@ -122,26 +127,30 @@ class RecoderWindow(Adw.ApplicationWindow):
self.transcoder.connect("notify::batch-progress", self.on_transcoder_progress) self.transcoder.connect("notify::batch-progress", self.on_transcoder_progress)
self.transcoder.connect("notify::batch-status", self.on_transcoder_status) self.transcoder.connect("notify::batch-status", self.on_transcoder_status)
self.transcoder.start() self.transcoder.start()
self.app_state_manager.state = AppState.TRANSCODING self.app_state_manager.state = AppState.TRANSCODING
self.toast_overlay.add_toast(Adw.Toast.new("Starting transcoding"))
def pause_transcoding(self): def pause_transcoding(self):
if self.transcoder: if self.transcoder:
self.transcoder.pause() self.transcoder.pause()
self.is_paused = True self.is_paused = True
self.app_state_manager.state = AppState.PAUSED self.app_state_manager.state = AppState.PAUSED
self.toast_overlay.add_toast(Adw.Toast.new("Transcoding paused"))
def resume_transcoding(self): def resume_transcoding(self):
if self.transcoder: if self.transcoder:
self.transcoder.resume() self.transcoder.resume()
self.is_paused = False self.is_paused = False
self.app_state_manager.state = AppState.TRANSCODING self.app_state_manager.state = AppState.TRANSCODING
self.toast_overlay.add_toast(Adw.Toast.new("Resuming transcoding"))
def on_clear_clicked(self, button): def on_clear_clicked(self, button):
if self.transcoder and self.transcoder.is_processing: if self.transcoder and self.transcoder.is_processing:
self.transcoder.stop() self.transcoder.stop()
self.transcoder = None self.transcoder = None
self.clear_listbox()
self.app_state_manager.state = AppState.STOPPED self.app_state_manager.state = AppState.STOPPED
self.toast_overlay.add_toast(Adw.Toast.new("File list cleared"))
def on_transcoder_progress(self, transcoder, param): def on_transcoder_progress(self, transcoder, param):
self.progress_bar.set_fraction(transcoder.batch_progress / 100.0) self.progress_bar.set_fraction(transcoder.batch_progress / 100.0)
@ -150,6 +159,7 @@ class RecoderWindow(Adw.ApplicationWindow):
if transcoder.batch_status == BatchStatus.DONE: if transcoder.batch_status == BatchStatus.DONE:
play_complete_sound() play_complete_sound()
notify_done(APP_NAME, "Transcoding finished!") notify_done(APP_NAME, "Transcoding finished!")
self.toast_overlay.add_toast(Adw.Toast.new("Transcoding finished!"))
self.app_state_manager.state = AppState.DONE self.app_state_manager.state = AppState.DONE
elif transcoder.batch_status == BatchStatus.STOPPED: elif transcoder.batch_status == BatchStatus.STOPPED:
@ -157,4 +167,5 @@ class RecoderWindow(Adw.ApplicationWindow):
elif transcoder.batch_status == BatchStatus.ERROR: elif transcoder.batch_status == BatchStatus.ERROR:
notify_done(APP_NAME, "An error occurred during transcoding.") notify_done(APP_NAME, "An error occurred during transcoding.")
self.toast_overlay.add_toast(Adw.Toast.new("An error occurred during transcoding"))
self.app_state_manager.state = AppState.ERROR self.app_state_manager.state = AppState.ERROR

View file

@ -4,6 +4,5 @@
<file>preferences.ui</file> <file>preferences.ui</file>
<file>file_entry_row.ui</file> <file>file_entry_row.ui</file>
<file>style.css</file> <file>style.css</file>
<file alias="net.jeena.Recoder.svg">../resources/net.jeena.Recoder.svg</file>
</gresource> </gresource>
</gresources> </gresources>

View file

@ -8,6 +8,10 @@
<attribute name="label">Preferences</attribute> <attribute name="label">Preferences</attribute>
<attribute name="action">app.preferences</attribute> <attribute name="action">app.preferences</attribute>
</item> </item>
<item>
<attribute name="label">Help</attribute>
<attribute name="action">app.help</attribute>
</item>
<item> <item>
<attribute name="label">About Recoder</attribute> <attribute name="label">About Recoder</attribute>
<attribute name="action">app.about</attribute> <attribute name="action">app.about</attribute>
@ -23,6 +27,8 @@
<property name="default-width">700</property> <property name="default-width">700</property>
<property name="default-height">400</property> <property name="default-height">400</property>
<child>
<object class="AdwToastOverlay" id="toast_overlay">
<child> <child>
<object class="AdwToolbarView" id="toolbar_view"> <object class="AdwToolbarView" id="toolbar_view">
<child type="top"> <child type="top">
@ -133,7 +139,6 @@
</style> </style>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
@ -149,7 +154,6 @@
</child> </child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
@ -163,5 +167,7 @@
</property> </property>
</object> </object>
</child> </child>
</object>
</child>
</template> </template>
</interface> </interface>