Teach the file manager to send and to shrink
Right-click a file to send it to your phone, or a video to make it smaller. macOS has both behind the Share sheet and Quick Actions; Windows has "Send to"; a stock Linux file manager has neither, and the usual answer for the second one is a web uploader or an ffmpeg incantation looked up again every time. Neither adds machinery. Sending reuses panama-kdeconnect, the same helper the Home & Phone page and quick settings already drive, so there is one way to talk to a phone rather than two. The entry appears only when a phone is actually reachable: an item that is present and fails is worse than one that is absent, because the absence explains itself. The transcoder's two rules are both about not losing work. It never writes to its input, and it never writes over an earlier output -- a second run produces -2 rather than eating the first result. Verified against a real encode: 1920x1080 became 854x480, with an even width because H.264 rejects an odd one at the very end of a long encode, which is the worst possible moment to find out. Menus decide by mime type rather than extension, act on one file at a time, and refuse anything that is not a local path. nautilus-python turned out to be declared already; it now says it carries Panama's own extensions too.
This commit is contained in:
Executable
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Make a video smaller, or turn a picture into another format.
|
||||
#
|
||||
# panama-transcode video <file> 1080p|720p|480p
|
||||
# panama-transcode image <file> jpg|png|webp
|
||||
#
|
||||
# The two things people actually right-click a media file to do. macOS has
|
||||
# Quick Actions for both; Linux file managers have neither, and the usual
|
||||
# answer is a web uploader or a forgotten ffmpeg incantation.
|
||||
#
|
||||
# Two rules, both about not losing work:
|
||||
#
|
||||
# * The input is never written to. Output goes beside it with a suffix.
|
||||
# * An existing output is never overwritten. The suffix gains a number
|
||||
# rather than replacing something somebody made earlier.
|
||||
#
|
||||
# ffmpeg does the work and is already a declared dependency; this only decides
|
||||
# the arguments, which is the part worth writing down once.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
err() { printf 'panama-transcode: %s\n' "$*" >&2; }
|
||||
|
||||
command -v ffmpeg >/dev/null 2>&1 || { err 'ffmpeg is not installed'; exit 1; }
|
||||
|
||||
# A path that does not exist yet, beside the input.
|
||||
free_path() {
|
||||
local dir="$1" stem="$2" suffix="$3" ext="$4"
|
||||
local candidate="$dir/$stem-$suffix.$ext"
|
||||
local counter=2
|
||||
while [[ -e "$candidate" ]]; do
|
||||
candidate="$dir/$stem-$suffix-$counter.$ext"
|
||||
counter=$(( counter + 1 ))
|
||||
done
|
||||
printf '%s' "$candidate"
|
||||
}
|
||||
|
||||
notify() {
|
||||
command -v notify-send >/dev/null 2>&1 || return 0
|
||||
notify-send --icon="${3:-video-x-generic}" "$1" "$2" 2>/dev/null || true
|
||||
}
|
||||
|
||||
cmd_video() {
|
||||
local input="${1:-}" preset="${2:-1080p}"
|
||||
[[ -f "$input" ]] || { err 'that file does not exist'; return 2; }
|
||||
|
||||
local height
|
||||
case "$preset" in
|
||||
1080p) height=1080 ;;
|
||||
720p) height=720 ;;
|
||||
480p) height=480 ;;
|
||||
*) err "unknown size: $preset"; return 2 ;;
|
||||
esac
|
||||
|
||||
local dir stem output
|
||||
dir="$(dirname "$input")"
|
||||
stem="$(basename "${input%.*}")"
|
||||
output="$(free_path "$dir" "$stem" "$preset" mp4)"
|
||||
|
||||
notify "Transcoding" "$(basename "$input") → $preset" video-x-generic
|
||||
# -2 rather than -1 on width: H.264 needs even dimensions, and an odd one
|
||||
# fails at the very end of a long encode.
|
||||
if ffmpeg -nostdin -loglevel error -i "$input" \
|
||||
-vf "scale=-2:'min($height,ih)'" \
|
||||
-c:v libx264 -crf 23 -preset medium \
|
||||
-c:a aac -b:a 128k \
|
||||
"$output" </dev/null; then
|
||||
notify "Transcoded" "$(basename "$output")" video-x-generic
|
||||
printf '%s\n' "$output"
|
||||
else
|
||||
rm -f "$output"
|
||||
notify "Transcode failed" "$(basename "$input")" dialog-error-symbolic
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_image() {
|
||||
local input="${1:-}" format="${2:-jpg}"
|
||||
[[ -f "$input" ]] || { err 'that file does not exist'; return 2; }
|
||||
|
||||
case "$format" in
|
||||
jpg|png|webp) ;;
|
||||
*) err "unknown format: $format"; return 2 ;;
|
||||
esac
|
||||
|
||||
local dir stem output
|
||||
dir="$(dirname "$input")"
|
||||
stem="$(basename "${input%.*}")"
|
||||
output="$(free_path "$dir" "$stem" converted "$format")"
|
||||
|
||||
local -a quality=()
|
||||
[[ "$format" == "jpg" ]] && quality=(-q:v 3)
|
||||
[[ "$format" == "webp" ]] && quality=(-quality 82)
|
||||
|
||||
if ffmpeg -nostdin -loglevel error -i "$input" "${quality[@]}" "$output" </dev/null; then
|
||||
notify "Converted" "$(basename "$output")" image-x-generic
|
||||
printf '%s\n' "$output"
|
||||
else
|
||||
rm -f "$output"
|
||||
notify "Conversion failed" "$(basename "$input")" dialog-error-symbolic
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
video) shift; cmd_video "$@" ;;
|
||||
image) shift; cmd_image "$@" ;;
|
||||
-h|--help|"")
|
||||
cat <<'USAGE'
|
||||
usage: panama-transcode video <file> [1080p|720p|480p]
|
||||
panama-transcode image <file> [jpg|png|webp]
|
||||
|
||||
Writes beside the input, never over it, and never over an existing output.
|
||||
USAGE
|
||||
;;
|
||||
*) err "unknown command: $1"; exit 2 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user