Restructure app and move into files
This commit is contained in:
parent
32a4e78d1b
commit
86bf8750de
13 changed files with 205 additions and 186 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1 +1,7 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
pkg/
|
||||||
|
*.tar.zst
|
||||||
|
*.tar.xz
|
15
PKGBUILD
15
PKGBUILD
|
@ -1,15 +0,0 @@
|
||||||
pkgname=recoder
|
|
||||||
pkgver=1.0
|
|
||||||
pkgrel=1
|
|
||||||
pkgdesc="GTK4/libadwaita Video Recoder for DaVinci Resolve"
|
|
||||||
arch=('any')
|
|
||||||
url="https://example.com"
|
|
||||||
license=('MIT')
|
|
||||||
depends=('python' 'python-gobject' 'gtk4' 'libadwaita')
|
|
||||||
source=('recoder.py' 'recoder.desktop')
|
|
||||||
sha256sums=('SKIP' 'SKIP')
|
|
||||||
|
|
||||||
package() {
|
|
||||||
install -Dm755 "recoder.py" "$pkgdir/usr/bin/recoder"
|
|
||||||
install -Dm644 "recoder.desktop" "$pkgdir/usr/share/applications/recoder.desktop"
|
|
||||||
}
|
|
10
Pipfile
10
Pipfile
|
@ -1,10 +0,0 @@
|
||||||
[[source]]
|
|
||||||
name = "pypi"
|
|
||||||
url = "https://pypi.org/simple"
|
|
||||||
verify_ssl = true
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
PyGObject = "*"
|
|
||||||
|
|
||||||
[requires]
|
|
||||||
python_version = "3.10"
|
|
0
README.md
Normal file
0
README.md
Normal file
25
packaging/PKGBUILD
Normal file
25
packaging/PKGBUILD
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Maintainer: Jeena <your-email@example.com>
|
||||||
|
pkgname=recoder
|
||||||
|
pkgver=1.0.0
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="A GTK4 video transcoding GUI application"
|
||||||
|
arch=('x86_64' 'aarch64')
|
||||||
|
url="https://github.com/yourusername/recoder"
|
||||||
|
license=('GPL3')
|
||||||
|
depends=('gtk4' 'libadwaita' 'gobject-introspection' 'python' 'python-gobject' 'pulseaudio' 'ffmpeg')
|
||||||
|
optdepends=('libcanberra: play system notification sounds')
|
||||||
|
makedepends=('python-setuptools')
|
||||||
|
source=()
|
||||||
|
noextract=()
|
||||||
|
sha256sums=()
|
||||||
|
|
||||||
|
package() {
|
||||||
|
install -dm755 "$pkgdir/usr/bin"
|
||||||
|
install -m755 ../src/app.py "$pkgdir/usr/bin/recoder"
|
||||||
|
|
||||||
|
install -dm755 "$pkgdir/usr/lib/recoder"
|
||||||
|
cp -r ../src/* "$pkgdir/usr/lib/recoder/"
|
||||||
|
|
||||||
|
install -Dm644 ../data/recoder.desktop "$pkgdir/usr/share/applications/recoder.desktop"
|
||||||
|
install -Dm644 ../data/icons/recoder.png "$pkgdir/usr/share/icons/hicolor/256x256/apps/recoder.png"
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=Recoder
|
|
||||||
Exec=recoder
|
|
||||||
Icon=video-x-generic
|
|
||||||
Terminal=false
|
|
||||||
Categories=AudioVideo;Video;GTK;
|
|
||||||
StartupNotify=true
|
|
9
resources/recoder.desktop
Normal file
9
resources/recoder.desktop
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Recoder
|
||||||
|
Comment=GTK4 Video Transcoding GUI Application
|
||||||
|
Exec=recoder
|
||||||
|
Icon=recoder
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=AudioVideo;Video;Utility;
|
||||||
|
StartupNotify=true
|
28
src/app.py
Normal file
28
src/app.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import sys
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version('Gtk', '4.0')
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
|
||||||
|
from gi.repository import Adw, Gio
|
||||||
|
from ui import RecoderWindow
|
||||||
|
|
||||||
|
Adw.init()
|
||||||
|
|
||||||
|
class RecoderApp(Adw.Application):
|
||||||
|
def __init__(self):
|
||||||
|
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(application=self)
|
||||||
|
self.window.present()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = RecoderApp()
|
||||||
|
return app.run(sys.argv)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
16
src/config.py
Normal file
16
src/config.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
|
CONFIG_PATH = Path(GLib.get_user_config_dir()) / "recoder" / "config.json"
|
||||||
|
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def save_config(config):
|
||||||
|
with open(CONFIG_PATH, "w") as f:
|
||||||
|
json.dump(config, f)
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
if CONFIG_PATH.exists():
|
||||||
|
with open(CONFIG_PATH) as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {}
|
57
src/transcoder_worker.py
Normal file
57
src/transcoder_worker.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from gi.repository import GLib
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def transcode_file(input_path, output_dir):
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
basename = os.path.basename(input_path)
|
||||||
|
output_path = os.path.join(output_dir, basename)
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg", "-y", "-i", input_path,
|
||||||
|
"-c:v", "libx264", "-preset", "fast",
|
||||||
|
"-c:a", "aac", output_path
|
||||||
|
]
|
||||||
|
|
||||||
|
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
return result.returncode == 0, output_path
|
||||||
|
|
||||||
|
class TranscoderWorker:
|
||||||
|
def __init__(self, files, progress_callback=None, done_callback=None):
|
||||||
|
self.files = files
|
||||||
|
self.progress_callback = progress_callback
|
||||||
|
self.done_callback = done_callback
|
||||||
|
self.is_processing = False
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
if self.is_processing:
|
||||||
|
return
|
||||||
|
self.is_processing = True
|
||||||
|
thread = threading.Thread(target=self._process_files)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _process_files(self):
|
||||||
|
total = len(self.files)
|
||||||
|
for idx, filepath in enumerate(self.files, start=1):
|
||||||
|
basename = os.path.basename(filepath)
|
||||||
|
self._update_progress(f"Processing {basename} ({idx}/{total})...", idx / total)
|
||||||
|
|
||||||
|
success, output_path = transcode_file(filepath, os.path.join(os.path.dirname(filepath), "transcoded"))
|
||||||
|
if not success:
|
||||||
|
self._update_progress(f"Error transcoding {basename}", idx / total)
|
||||||
|
else:
|
||||||
|
self._update_progress(f"Finished {basename}", idx / total)
|
||||||
|
|
||||||
|
self.is_processing = False
|
||||||
|
self._update_progress("All done!", 1.0)
|
||||||
|
self._notify_done()
|
||||||
|
|
||||||
|
def _update_progress(self, text, fraction):
|
||||||
|
if self.progress_callback:
|
||||||
|
GLib.idle_add(self.progress_callback, text, fraction)
|
||||||
|
|
||||||
|
def _notify_done(self):
|
||||||
|
if self.done_callback:
|
||||||
|
GLib.idle_add(self.done_callback)
|
201
recoder.py → src/ui.py
Executable file → Normal file
201
recoder.py → src/ui.py
Executable file → Normal file
|
@ -1,35 +1,14 @@
|
||||||
#!/usr/bin/env python3
|
import os
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
gi.require_version('Gtk', '4.0')
|
gi.require_version('Gtk', '4.0')
|
||||||
gi.require_version('Adw', '1')
|
gi.require_version('Adw', '1')
|
||||||
from gi.repository import Gtk, Adw, Gio, Gdk, GLib, GObject
|
from gi.repository import Gtk, Adw, Gio, Gdk, GLib
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
import os
|
from config import load_config, save_config
|
||||||
import sys
|
from transcoder_worker import TranscoderWorker
|
||||||
import subprocess
|
|
||||||
import threading
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from models import FileItem, FileStatus
|
|
||||||
from transcoder import transcode_file
|
|
||||||
|
|
||||||
Adw.init()
|
|
||||||
|
|
||||||
CONFIG_PATH = Path(GLib.get_user_config_dir()) / "recoder" / "config.json"
|
|
||||||
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
def save_config(config):
|
|
||||||
with open(CONFIG_PATH, "w") as f:
|
|
||||||
json.dump(config, f)
|
|
||||||
|
|
||||||
def load_config():
|
|
||||||
if CONFIG_PATH.exists():
|
|
||||||
with open(CONFIG_PATH) as f:
|
|
||||||
return json.load(f)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
class RecoderWindow(Adw.ApplicationWindow):
|
class RecoderWindow(Adw.ApplicationWindow):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -38,35 +17,53 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
self.set_default_size(700, 400)
|
self.set_default_size(700, 400)
|
||||||
|
|
||||||
self.config = load_config()
|
self.config = load_config()
|
||||||
self.current_dir = self.config.get("last_directory", str(Path.home()))
|
self.current_dir = self.config.get("last_directory", str(os.path.expanduser("~")))
|
||||||
|
|
||||||
# Main Box
|
self.files_to_process = []
|
||||||
|
self.transcoder = None
|
||||||
|
|
||||||
|
# UI setup
|
||||||
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||||
|
|
||||||
# Create the header bar
|
|
||||||
self.header_bar = Adw.HeaderBar()
|
self.header_bar = Adw.HeaderBar()
|
||||||
|
|
||||||
# Create the toolbar view and set the header bar
|
|
||||||
self.toolbar_view = Adw.ToolbarView()
|
self.toolbar_view = Adw.ToolbarView()
|
||||||
self.toolbar_view.add_top_bar(self.header_bar)
|
self.toolbar_view.add_top_bar(self.header_bar)
|
||||||
self.toolbar_view.set_content(self.vbox)
|
self.toolbar_view.set_content(self.vbox)
|
||||||
|
|
||||||
# Set the toolbar view as the window content
|
|
||||||
self.set_content(self.toolbar_view)
|
self.set_content(self.toolbar_view)
|
||||||
|
|
||||||
# TextView to list files
|
|
||||||
self.textview = Gtk.TextView()
|
self.textview = Gtk.TextView()
|
||||||
self.textview.set_editable(False)
|
self.textview.set_editable(False)
|
||||||
self.textbuffer = self.textview.get_buffer()
|
self.textbuffer = self.textview.get_buffer()
|
||||||
|
|
||||||
# Create a DropTarget for URI list (files)
|
|
||||||
self.drop_target = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY)
|
self.drop_target = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY)
|
||||||
self.drop_target.connect("enter", self.on_drop_enter)
|
self.drop_target.connect("enter", self.on_drop_enter)
|
||||||
self.drop_target.connect("leave", self.on_drop_leave)
|
self.drop_target.connect("leave", self.on_drop_leave)
|
||||||
self.drop_target.connect("drop", self.on_drop)
|
self.drop_target.connect("drop", self.on_drop)
|
||||||
self.textview.add_controller(self.drop_target)
|
self.textview.add_controller(self.drop_target)
|
||||||
|
|
||||||
# CSS for highlight
|
# CSS
|
||||||
|
self._setup_css()
|
||||||
|
|
||||||
|
self.drop_hint = Gtk.Label(label="📂 Drop files here to get started")
|
||||||
|
self.drop_hint.add_css_class("dim-label")
|
||||||
|
self._setup_dim_label_css()
|
||||||
|
|
||||||
|
overlay = Gtk.Overlay()
|
||||||
|
overlay.set_child(self.textview)
|
||||||
|
overlay.add_overlay(self.drop_hint)
|
||||||
|
self.drop_hint.set_halign(Gtk.Align.CENTER)
|
||||||
|
self.drop_hint.set_valign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
|
self.scrolled = Gtk.ScrolledWindow()
|
||||||
|
self.scrolled.set_child(overlay)
|
||||||
|
self.vbox.append(self.scrolled)
|
||||||
|
self.scrolled.set_vexpand(True)
|
||||||
|
self.scrolled.set_hexpand(True)
|
||||||
|
|
||||||
|
self.progress = Gtk.ProgressBar()
|
||||||
|
self.vbox.append(self.progress)
|
||||||
|
self.progress.set_hexpand(True)
|
||||||
|
|
||||||
|
def _setup_css(self):
|
||||||
css = b"""
|
css = b"""
|
||||||
textview.drop-highlight {
|
textview.drop-highlight {
|
||||||
background-color: @theme_selected_bg_color;
|
background-color: @theme_selected_bg_color;
|
||||||
|
@ -81,11 +78,7 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create label overlay
|
def _setup_dim_label_css(self):
|
||||||
self.drop_hint = Gtk.Label(label="📂 Drop files here to get started")
|
|
||||||
self.drop_hint.add_css_class("dim-label")
|
|
||||||
|
|
||||||
# Optional: use CSS to style it
|
|
||||||
css = b"""
|
css = b"""
|
||||||
.dim-label {
|
.dim-label {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
@ -100,47 +93,19 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Overlay
|
|
||||||
overlay = Gtk.Overlay()
|
|
||||||
overlay.set_child(self.textview)
|
|
||||||
overlay.add_overlay(self.drop_hint)
|
|
||||||
self.drop_hint.set_halign(Gtk.Align.CENTER)
|
|
||||||
self.drop_hint.set_valign(Gtk.Align.CENTER)
|
|
||||||
|
|
||||||
# Scroll container for textview
|
|
||||||
self.scrolled = Gtk.ScrolledWindow()
|
|
||||||
self.scrolled.set_child(overlay)
|
|
||||||
self.vbox.append(self.scrolled)
|
|
||||||
self.scrolled.set_vexpand(True)
|
|
||||||
self.scrolled.set_hexpand(True)
|
|
||||||
|
|
||||||
# Progress bar
|
|
||||||
self.progress = Gtk.ProgressBar()
|
|
||||||
self.vbox.append(self.progress)
|
|
||||||
self.progress.set_hexpand(True)
|
|
||||||
|
|
||||||
self.files_to_process = []
|
|
||||||
self.is_processing = False
|
|
||||||
|
|
||||||
|
|
||||||
def load_files_from_directory(self, directory):
|
|
||||||
self.files_to_process = []
|
|
||||||
self.textbuffer.set_text("")
|
|
||||||
for entry in os.scandir(directory):
|
|
||||||
if entry.is_file() and entry.name.lower().endswith((".mp4", ".mov", ".mkv", ".avi")):
|
|
||||||
self.files_to_process.append(entry.path)
|
|
||||||
self.textbuffer.insert_at_cursor(f"{entry.name}\n")
|
|
||||||
if self.files_to_process:
|
|
||||||
self.start_transcoding()
|
|
||||||
|
|
||||||
def on_drop_enter(self, drop_target, x, y):
|
def on_drop_enter(self, drop_target, x, y):
|
||||||
self.textview.add_css_class("drop-highlight")
|
self.textview.add_css_class("drop-highlight")
|
||||||
|
return True
|
||||||
|
|
||||||
def on_drop_leave(self, drop_target):
|
def on_drop_leave(self, drop_target):
|
||||||
self.textview.remove_css_class("drop-highlight")
|
self.textview.remove_css_class("drop-highlight")
|
||||||
|
return True
|
||||||
|
|
||||||
def on_drop(self, drop_target, value, x, y):
|
def on_drop(self, drop_target, value, x, y):
|
||||||
# value is a Gio.File or a list of Gio.Files
|
GLib.idle_add(partial(self.process_drop_value, value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
def process_drop_value(self, value):
|
||||||
if isinstance(value, Gio.File):
|
if isinstance(value, Gio.File):
|
||||||
uris = [value]
|
uris = [value]
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
|
@ -152,7 +117,6 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
for file in uris:
|
for file in uris:
|
||||||
path = file.get_path()
|
path = file.get_path()
|
||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
# Add all supported video files from the dropped folder
|
|
||||||
for entry in os.scandir(path):
|
for entry in os.scandir(path):
|
||||||
if entry.is_file() and entry.name.lower().endswith((".mp4", ".mov", ".mkv", ".avi")):
|
if entry.is_file() and entry.name.lower().endswith((".mp4", ".mov", ".mkv", ".avi")):
|
||||||
paths.append(entry.path)
|
paths.append(entry.path)
|
||||||
|
@ -163,75 +127,38 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.textview.remove_css_class("drop-highlight")
|
self.textview.remove_css_class("drop-highlight")
|
||||||
self.drop_hint.set_visible(False) # Hide hint
|
self.drop_hint.set_visible(False)
|
||||||
|
|
||||||
self.files_to_process = paths
|
self.files_to_process = paths
|
||||||
self.textbuffer.set_text("\n".join(os.path.basename(p) for p in paths) + "\n")
|
self.textbuffer.set_text("\n".join(os.path.basename(p) for p in paths) + "\n")
|
||||||
self.start_transcoding()
|
self.start_transcoding()
|
||||||
return True
|
return False
|
||||||
|
|
||||||
def start_transcoding(self):
|
def start_transcoding(self):
|
||||||
if self.is_processing:
|
if self.transcoder and self.transcoder.is_processing:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.is_processing = True
|
|
||||||
self.progress.set_fraction(0.0)
|
self.progress.set_fraction(0.0)
|
||||||
self.progress.set_text("Starting transcoding...")
|
self.progress.set_text("Starting transcoding...")
|
||||||
|
|
||||||
thread = threading.Thread(target=self.transcode_files)
|
self.transcoder = TranscoderWorker(
|
||||||
thread.daemon = True
|
self.files_to_process,
|
||||||
thread.start()
|
progress_callback=self.update_progress,
|
||||||
|
done_callback=self.notify_done,
|
||||||
|
)
|
||||||
|
self.transcoder.start()
|
||||||
|
|
||||||
def transcode_files(self):
|
def update_progress(self, text, fraction):
|
||||||
total = len(self.files_to_process)
|
self.progress.set_show_text(True)
|
||||||
|
self.progress.set_text(text)
|
||||||
for idx, filepath in enumerate(self.files_to_process, start=1):
|
self.progress.set_fraction(fraction)
|
||||||
basename = os.path.basename(filepath)
|
|
||||||
self.update_progress_text(f"Processing {basename} ({idx}/{total})...")
|
|
||||||
success, output_path = transcode_file(filepath, os.path.join(os.path.dirname(filepath), "transcoded"))
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
self.update_progress_text(f"Error transcoding {basename}")
|
|
||||||
else:
|
|
||||||
self.update_progress_text(f"Finished {basename}")
|
|
||||||
|
|
||||||
self.update_progress_fraction(idx / total)
|
|
||||||
|
|
||||||
self.is_processing = False
|
|
||||||
self.update_progress_text("All done!")
|
|
||||||
self.progress.set_fraction(1.0)
|
|
||||||
self.notify_done()
|
|
||||||
|
|
||||||
|
|
||||||
def update_progress_text(self, text):
|
|
||||||
GLib.idle_add(self.progress.set_show_text, True)
|
|
||||||
GLib.idle_add(self.progress.set_text, text)
|
|
||||||
|
|
||||||
def update_progress_fraction(self, fraction):
|
|
||||||
GLib.idle_add(self.progress.set_fraction, fraction)
|
|
||||||
|
|
||||||
def notify_done(self):
|
def notify_done(self):
|
||||||
# Visual cue - change progress bar color briefly
|
self.progress.set_show_text(True)
|
||||||
GLib.idle_add(self.progress.set_show_text, True)
|
self.progress.set_text("Transcoding Complete!")
|
||||||
GLib.idle_add(self.progress.set_text, "Transcoding Complete!")
|
self.play_complete_sound()
|
||||||
# Audio cue
|
|
||||||
subprocess.Popen(["paplay", "/usr/share/sounds/freedesktop/stereo/complete.oga"])
|
|
||||||
|
|
||||||
class RecoderApp(Adw.Application):
|
|
||||||
def __init__(self):
|
|
||||||
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(application=self)
|
|
||||||
self.window.present()
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = RecoderApp()
|
|
||||||
return app.run(sys.argv)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
|
|
||||||
|
def play_complete_sound(self):
|
||||||
|
if shutil.which("canberra-gtk-play"):
|
||||||
|
subprocess.Popen(["canberra-gtk-play", "--id", "complete"])
|
||||||
|
else:
|
||||||
|
print("canberra-gtk-play not found.")
|
|
@ -1,16 +0,0 @@
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def transcode_file(input_path, output_dir):
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
basename = os.path.basename(input_path)
|
|
||||||
output_path = os.path.join(output_dir, basename)
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
"ffmpeg", "-y", "-i", input_path,
|
|
||||||
"-c:v", "libx264", "-preset", "fast",
|
|
||||||
"-c:a", "aac", output_path
|
|
||||||
]
|
|
||||||
|
|
||||||
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
return result.returncode == 0, output_path
|
|
Loading…
Add table
Add a link
Reference in a new issue