From a133cf9622ad299ea3636e265a6bfcea0b254c3c Mon Sep 17 00:00:00 2001 From: spaceXrace <92670772+spaceXrace@users.noreply.github.com> Date: Wed, 13 May 2026 12:29:31 +0200 Subject: [PATCH] Add folder transcoding --- bin/omarchy-transcode | 100 +++++++++++++++++- .../nautilus-python/extensions/transcode.py | 77 +++++++++----- migrations/1778424449.sh | 3 + 3 files changed, 150 insertions(+), 30 deletions(-) create mode 100644 migrations/1778424449.sh diff --git a/bin/omarchy-transcode b/bin/omarchy-transcode index a8b3d8ad18..4ab01c47d1 100755 --- a/bin/omarchy-transcode +++ b/bin/omarchy-transcode @@ -15,6 +15,7 @@ Usage: With no input, pick a picture or video with Walker from ~/Pictures and ~/Videos, or only from --path when provided. Then pick the output format and resolution. +When input is a directory, transcode matching direct child files only. Options: --path path Limit interactive fuzzy file selection to this path @@ -43,6 +44,17 @@ media_type() { esac } +media_type_for_format() { + case "$1" in + jpg | png) echo picture ;; + mp4 | gif) echo video ;; + *) + echo "Invalid format: $1" >&2 + return 1 + ;; + esac +} + output_path() { local input="$1" local format="$2" @@ -120,6 +132,67 @@ transcode_video() { esac } +transcode_file() { + local input="$1" format="$2" resolution="$3" + local type output + + type=$(media_type "$input") + output=$(output_path "$input" "$format" "$resolution") + + if [[ $type == "video" ]]; then + transcode_video "$input" "$format" "$resolution" "$output" + else + transcode_picture "$input" "$format" "$resolution" "$output" + fi + + printf '%s\n' "$output" +} + +transcode_directory() { + local input="$1" format="$2" resolution="$3" + local target_type path type output count=0 skipped=0 + + target_type=$(media_type_for_format "$format") + + shopt -s nullglob + for path in "$input"/*; do + [[ -f $path ]] || continue + + if ! type=$(media_type "$path" 2>/dev/null); then + (( skipped += 1 )) + continue + fi + + if [[ $type != "$target_type" ]]; then + (( skipped += 1 )) + continue + fi + + output=$(output_path "$path" "$format" "$resolution") + if [[ $type == "video" ]]; then + omarchy-notification-send -g  "Transcoding video…" "$(basename -- "$path") to $format ($resolution)" + transcode_video "$path" "$format" "$resolution" "$output" + else + transcode_picture "$path" "$format" "$resolution" "$output" + fi + + (( count += 1 )) + printf 'Transcoded %s -> %s\n' "$path" "$output" + done + shopt -u nullglob + + if (( count == 0 )); then + echo "No supported $target_type files found in: $input" >&2 + return 1 + fi + + omarchy-notification-send "Transcoded $count item(s) to $resolution $format" "Saved in $(basename -- "$input")" + + if (( skipped > 0 )); then + printf 'Skipped %d unsupported or mismatched item(s).\n' "$skipped" + fi +} + copy_to_clipboard() { local output="$1" local uri @@ -170,7 +243,26 @@ main() { fi [[ -n $input ]] || return 1 - [[ -f $input ]] || { echo "File not found: $input" >&2; return 1; } + [[ -f $input || -d $input ]] || { echo "File or directory not found: $input" >&2; return 1; } + + if [[ -d $input ]]; then + if [[ -z $format ]]; then + format=$(omarchy-menu-select "Select format" jpg png mp4 gif) + fi + + type=$(media_type_for_format "$format") + + if [[ -z $resolution ]]; then + if [[ $type == "picture" ]]; then + resolution=$(omarchy-menu-select "Select resolution" high medium low) + else + resolution=$(omarchy-menu-select "Select resolution" 4k 1080p 720p) + fi + fi + + transcode_directory "$input" "$format" "$resolution" + return + fi type=$(media_type "$input") @@ -190,15 +282,13 @@ main() { fi fi - output=$(output_path "$input" "$format" "$resolution") - if [[ $type == "video" ]]; then omarchy-notification-send -g  "Transcoding video…" "$(basename -- "$input") to $format ($resolution)" - transcode_video "$input" "$format" "$resolution" "$output" + output=$(transcode_file "$input" "$format" "$resolution") copy_to_clipboard "$output" omarchy-notification-send -g  "Transcoded to $resolution $format" "Saved and copied to clipboard." else - transcode_picture "$input" "$format" "$resolution" "$output" + output=$(transcode_file "$input" "$format" "$resolution") copy_to_clipboard "$output" omarchy-notification-send -g  "Transcoded to $resolution $format" "Saved and copied to clipboard." fi diff --git a/default/nautilus-python/extensions/transcode.py b/default/nautilus-python/extensions/transcode.py index be1dfe4bf6..552be381b3 100644 --- a/default/nautilus-python/extensions/transcode.py +++ b/default/nautilus-python/extensions/transcode.py @@ -8,14 +8,49 @@ from gi.repository import GObject, Gio, Nautilus -SUPPORTED_MIME_PREFIXES = ("image/", "video/") -SUPPORTED_EXTENSIONS = { - ".jpg", ".jpeg", ".png", ".webp", ".gif", ".heic", ".avif", - ".mp4", ".mov", ".m4v", ".mkv", ".webm", ".avi", -} +PICTURE_EXTENSIONS = (".jpg", ".jpeg", ".png", ".webp", ".gif", ".heic", ".avif") +VIDEO_EXTENSIONS = (".mp4", ".mov", ".m4v", ".mkv", ".webm", ".avi") class TranscodeAction(GObject.GObject, Nautilus.MenuProvider): + def _media_type(self, file): + mime = file.get_mime_type() or "" + if mime.startswith("image/"): + return "picture" + if mime.startswith("video/"): + return "video" + + location = file.get_location() + if not location: + return None + path = (location.get_path() or "").lower() + if path.endswith(PICTURE_EXTENSIONS): + return "picture" + if path.endswith(VIDEO_EXTENSIONS): + return "video" + return None + + def _batch_command(self, binary, media_type, paths): + if media_type == "picture": + format_options = "jpg png" + resolution_options = "high medium low" + else: + format_options = "mp4 gif" + resolution_options = "4k 1080p 720p" + + commands = [ + f"format=$(omarchy-menu-select 'Select {media_type} format' {format_options}) || exit 1", + f"resolution=$(omarchy-menu-select 'Select {media_type} resolution' {resolution_options}) || exit 1", + ] + + for path in paths: + commands.append( + f"echo {shlex.quote(f'Transcoding {path}')} && " + f"{shlex.join([binary, path])} \"$format\" \"$resolution\" || true" + ) + + return "; ".join(commands) + def _launch_transcode(self, paths): wrapper = shutil.which("omarchy-launch-floating-terminal-with-presentation") binary = shutil.which("omarchy-transcode") @@ -23,28 +58,19 @@ def _launch_transcode(self, paths): return if len(paths) == 1: - cmd = shlex.join([binary, paths[0]]) + cmd = shlex.join([binary, paths[0][1]]) else: - cmd = "; ".join( - f"echo {shlex.quote(f'Transcoding {path}')} && " - f"{shlex.join([binary, path])} || true" - for path in paths - ) + picture_paths = [path for media_type, path in paths if media_type == "picture"] + video_paths = [path for media_type, path in paths if media_type == "video"] + commands = [] + if picture_paths: + commands.append(self._batch_command(binary, "picture", picture_paths)) + if video_paths: + commands.append(self._batch_command(binary, "video", video_paths)) + cmd = "; ".join(commands) Gio.Subprocess.new([wrapper, cmd], Gio.SubprocessFlags.NONE) - def _is_supported(self, file): - mime = file.get_mime_type() or "" - if mime.startswith(SUPPORTED_MIME_PREFIXES): - return True - - location = file.get_location() - if not location: - return False - path = location.get_path() or "" - lower = path.lower() - return any(lower.endswith(ext) for ext in SUPPORTED_EXTENSIONS) - def _selected_paths(self, files): paths = [] seen = set() @@ -52,7 +78,8 @@ def _selected_paths(self, files): for file in files: if file.is_directory(): continue - if not self._is_supported(file): + media_type = self._media_type(file) + if not media_type: continue location = file.get_location() if not location: @@ -60,7 +87,7 @@ def _selected_paths(self, files): path = location.get_path() if path and path not in seen: seen.add(path) - paths.append(path) + paths.append((media_type, path)) return paths def _make_item(self, paths): diff --git a/migrations/1778424449.sh b/migrations/1778424449.sh new file mode 100644 index 0000000000..56f7fbd84a --- /dev/null +++ b/migrations/1778424449.sh @@ -0,0 +1,3 @@ +echo "Refresh Nautilus Transcode context menu" + +source "$OMARCHY_PATH/install/config/nautilus-python.sh"