.
This commit is contained in:
18
auto_add_sub_folder.desktop
Executable file
18
auto_add_sub_folder.desktop
Executable file
@@ -0,0 +1,18 @@
|
||||
[Desktop Entry]
|
||||
Comment[de_DE]=
|
||||
Comment=
|
||||
Exec=sh -e -c 'exec "$(dirname "$0")/bin/auto_add_sub_folder.sh"' %k
|
||||
GenericName[de_DE]=
|
||||
GenericName=
|
||||
Icon=mkvmerge
|
||||
MimeType=
|
||||
Name[de_DE]=auto_add_sub_folder
|
||||
Name=auto_add_sub_folder
|
||||
Path=
|
||||
PrefersNonDefaultGPU=false
|
||||
StartupNotify=true
|
||||
Terminal=true
|
||||
TerminalOptions=
|
||||
Type=Application
|
||||
X-KDE-SubstituteUID=false
|
||||
X-KDE-Username=
|
||||
430
bin/auto_add_sub_folder.sh
Executable file
430
bin/auto_add_sub_folder.sh
Executable file
@@ -0,0 +1,430 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
############################################
|
||||
# auto_add_sub_folder.sh
|
||||
#
|
||||
# Reads input_folder from ../settings.ini (relative to this script in bin/)
|
||||
# - If CLI args are provided, they override input_folder.
|
||||
#
|
||||
# Behavior:
|
||||
# - Recursively finds *.mkv under root(s) (case-insensitive)
|
||||
# - Robust sample exclusion (case-insensitive):
|
||||
# * filename is "sample.mkv"
|
||||
# * OR filename contains token "sample" (basename-sample.mkv, basename.Sample.mkv, basename_sample.mkv, etc.)
|
||||
# * OR ANY directory component equals "sample" (sample/, SAMPLE/, SaMPle/)
|
||||
#
|
||||
# - For each MKV: finds a sibling subtitle folder (case-insensitive) via candidates list:
|
||||
# Subs, Sub, Subtitles, Subtitle
|
||||
# then muxes ALL .idx/.sub pairs found inside.
|
||||
#
|
||||
# - Does NOT override language (VobSub language usually lives in the .idx)
|
||||
# - Sets track-name (heuristic) + forced flag based on idx filename only
|
||||
#
|
||||
# mkvmerge:
|
||||
# - auto-detect native OR flatpak (org.bunkus.mkvtoolnix-gui etc.)
|
||||
#
|
||||
# Safety:
|
||||
# - Writes output to temp file then replaces original
|
||||
# - Keeps .bak by default (disable via --no-bak)
|
||||
############################################
|
||||
|
||||
# ───────────────────────── colors ─────────────────────────
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;92m'
|
||||
BLUE='\033[0;94m'
|
||||
WHITE_ON_GRAY='\033[0;37;100m'
|
||||
BLACK_ON_WHITE='\033[0;30;47m'
|
||||
WHITE_ON_RED='\033[0;37;41m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
DRY_RUN=0
|
||||
KEEP_BAK=1
|
||||
VERBOSE=1
|
||||
|
||||
# Robust subtitle folder detection (case-insensitive)
|
||||
SUBDIR_CANDIDATES=("Subs" "Sub" "Subtitles" "Subtitle")
|
||||
|
||||
MKVMERGE_CMD=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
auto_add_sub_folder.sh [options] [root] [root2 ...]
|
||||
|
||||
If no root is given:
|
||||
- reads [pathes] input_folder from ../settings.ini
|
||||
|
||||
Options:
|
||||
-n, --dry-run Print commands only, do not execute
|
||||
--no-bak Do not keep .bak of original MKV
|
||||
-q, --quiet Less logging
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
log() { (( VERBOSE == 1 )) && printf '[%s] %s\n' "$(date +'%F %T')" "$*"; }
|
||||
dbg() { (( VERBOSE == 1 )) && printf ' %s\n' "$*"; }
|
||||
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# ---------------- INI parsing (minimal, safe for values with spaces) ----------------
|
||||
# Reads first matching key in a section:
|
||||
# [section]
|
||||
# key=value
|
||||
ini_get() {
|
||||
local ini_file="$1"
|
||||
local section="$2"
|
||||
local key="$3"
|
||||
|
||||
awk -v sec="[$section]" -v key="$key" '
|
||||
function ltrim(s){ sub(/^[ \t\r\n]+/, "", s); return s }
|
||||
function rtrim(s){ sub(/[ \t\r\n]+$/, "", s); return s }
|
||||
function trim(s){ return rtrim(ltrim(s)) }
|
||||
BEGIN{ insec=0 }
|
||||
{
|
||||
line=$0
|
||||
# strip BOM if present
|
||||
sub(/^\xef\xbb\xbf/, "", line)
|
||||
|
||||
# ignore comments (full-line)
|
||||
if (line ~ /^[ \t]*#/) next
|
||||
if (line ~ /^[ \t]*;/) next
|
||||
|
||||
# section header
|
||||
if (match(line, /^[ \t]*\[[^]]+\][ \t]*$/)) {
|
||||
insec = (trim(line) == sec)
|
||||
next
|
||||
}
|
||||
|
||||
if (!insec) next
|
||||
|
||||
# key=value, keep everything after first '=' (may contain spaces)
|
||||
if (match(line, /^[ \t]*[^=]+=/)) {
|
||||
split(line, a, "=")
|
||||
k = trim(a[1])
|
||||
if (k != key) next
|
||||
|
||||
pos = index(line, "=")
|
||||
v = substr(line, pos+1)
|
||||
v = trim(v)
|
||||
|
||||
# remove optional surrounding quotes
|
||||
if (v ~ /^".*"$/) { sub(/^"/, "", v); sub(/"$/, "", v) }
|
||||
print v
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "$ini_file"
|
||||
}
|
||||
|
||||
# ---------------- mkvmerge detection ----------------
|
||||
detect_mkvmerge() {
|
||||
log "Detecting mkvmerge…"
|
||||
if command -v mkvmerge >/dev/null 2>&1; then
|
||||
MKVMERGE_CMD=(mkvmerge)
|
||||
log "Using native mkvmerge: $(command -v mkvmerge)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v flatpak >/dev/null 2>&1; then
|
||||
local candidates=(
|
||||
"org.bunkus.mkvtoolnix-gui"
|
||||
"org.bunkus.mkvtoolnix.MKVToolNix"
|
||||
"org.bunkus.mkvtoolnix"
|
||||
)
|
||||
|
||||
local appid="" c=""
|
||||
for c in "${candidates[@]}"; do
|
||||
if flatpak info "$c" >/dev/null 2>&1; then
|
||||
appid="$c"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$appid" ]]; then
|
||||
appid="$(flatpak list --app --columns=application 2>/dev/null | grep -i 'mkvtoolnix' | head -n 1 || true)"
|
||||
fi
|
||||
|
||||
if [[ -n "$appid" ]]; then
|
||||
MKVMERGE_CMD=(flatpak run --command=mkvmerge "$appid")
|
||||
log "Using mkvmerge via Flatpak: $appid"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
die "Neither native mkvmerge nor a MKVToolNix Flatpak installation was found."
|
||||
}
|
||||
|
||||
# ---------------- sample exclusion ----------------
|
||||
# Returns 0 if path should be excluded as sample; otherwise 1.
|
||||
is_sample_mkv() {
|
||||
local f="$1"
|
||||
local base bn lc
|
||||
base="$(basename "$f")"
|
||||
bn="${base%.*}"
|
||||
lc="${bn,,}"
|
||||
|
||||
# 1) exactly "sample"
|
||||
if [[ "$lc" == "sample" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 2) token "sample" in basename (separators: dot, underscore, hyphen, space)
|
||||
if [[ "$lc" =~ (^|[._\ \-])sample([._\ \-]|$) ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 3) any directory component equals "sample" (case-insensitive)
|
||||
local d seg
|
||||
d="$(dirname "$f")"
|
||||
while [[ "$d" != "/" && -n "$d" ]]; do
|
||||
seg="$(basename "$d")"
|
||||
if [[ "${seg,,}" == "sample" ]]; then
|
||||
return 0
|
||||
fi
|
||||
d="$(dirname "$d")"
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------- subtitle folder finder (case-insensitive candidates) ----------------
|
||||
find_subs_dir() {
|
||||
local mkv="$1"
|
||||
local dir cand hit
|
||||
dir="$(dirname "$mkv")"
|
||||
|
||||
for cand in "${SUBDIR_CANDIDATES[@]}"; do
|
||||
hit="$(find "$dir" -maxdepth 1 -mindepth 1 -type d -iname "$cand" -print -quit 2>/dev/null || true)"
|
||||
if [[ -n "$hit" ]]; then
|
||||
echo "$hit"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------- subtitle meta (no language override) ----------------
|
||||
# Echoes: track_name|forcedFlag
|
||||
infer_meta_for_idx() {
|
||||
local idx="$1"
|
||||
local stem lc forced name
|
||||
|
||||
stem="$(basename "$idx" .idx)"
|
||||
lc="${stem,,}"
|
||||
|
||||
forced="no"
|
||||
[[ "$lc" == *forced* ]] && forced="yes"
|
||||
|
||||
# Track name heuristic (display only, not language)
|
||||
if [[ "$lc" =~ (^|[._\ \-])eng([._\ \-]|$) ]] || [[ "$lc" == *english* ]]; then
|
||||
name="English"
|
||||
elif [[ "$lc" =~ (^|[._\ \-])de([._\ \-]|$) ]] || [[ "$lc" == *german* ]] || [[ "$lc" =~ (^|[._\ \-])ger([._\ \-]|$) ]] || [[ "$lc" == *deu* ]]; then
|
||||
name="Deutsch"
|
||||
else
|
||||
name="$stem"
|
||||
fi
|
||||
|
||||
[[ "$forced" == "yes" ]] && name+=" (Forced)"
|
||||
echo "${name}|${forced}"
|
||||
}
|
||||
|
||||
# ---------------- mux one mkv ----------------
|
||||
mux_one_mkv() {
|
||||
local mkv="$1"
|
||||
local subs
|
||||
|
||||
log "MKV found: $mkv"
|
||||
|
||||
subs="$(find_subs_dir "$mkv" || true)"
|
||||
dbg "Subs dir : ${subs:-<not found>}"
|
||||
|
||||
if [[ -z "${subs:-}" ]]; then
|
||||
dbg "No subtitles folder found (tried: ${SUBDIR_CANDIDATES[*]}) -> skip"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local -a idxs=()
|
||||
local f=""
|
||||
while IFS= read -r -d '' f; do
|
||||
idxs+=("$f")
|
||||
done < <(find "$subs" -maxdepth 1 -type f -iname "*.idx" -print0)
|
||||
|
||||
if [[ ${#idxs[@]} -eq 0 ]]; then
|
||||
dbg "Subtitle folder exists but no .idx files -> skip"
|
||||
return 0
|
||||
fi
|
||||
|
||||
dbg "IDX files: ${#idxs[@]}"
|
||||
for f in "${idxs[@]}"; do dbg " - $(basename "$f")"; done
|
||||
|
||||
local out_tmp="${mkv%.*}.with-subs.tmp.mkv"
|
||||
local bak="${mkv}.bak"
|
||||
|
||||
local -a cmd=()
|
||||
cmd=("${MKVMERGE_CMD[@]}" -o "$out_tmp" "$mkv")
|
||||
|
||||
local added=0
|
||||
local idx=""
|
||||
for idx in "${idxs[@]}"; do
|
||||
local sub="${idx%.*}.sub"
|
||||
if [[ ! -f "$sub" ]]; then
|
||||
dbg "Skip idx (missing paired .sub): $(basename "$idx")"
|
||||
continue
|
||||
fi
|
||||
|
||||
local meta name forcedFlag
|
||||
meta="$(infer_meta_for_idx "$idx")"
|
||||
IFS='|' read -r name forcedFlag <<<"$meta"
|
||||
|
||||
dbg "Add VobSub: $(basename "$idx") -> name='$name', forced=$forcedFlag"
|
||||
|
||||
# IMPORTANT: do NOT override language; keep what is in the .idx
|
||||
cmd+=( --track-name 0:"$name" )
|
||||
if [[ "$forcedFlag" == "yes" ]]; then
|
||||
cmd+=( --forced-track 0:yes --default-track 0:no )
|
||||
else
|
||||
cmd+=( --forced-track 0:no --default-track 0:no )
|
||||
fi
|
||||
cmd+=( "$idx" )
|
||||
|
||||
((++added))
|
||||
done
|
||||
|
||||
if [[ $added -eq 0 ]]; then
|
||||
dbg "No usable subtitle sets (.idx + .sub) -> nothing to mux"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Muxing $added subtitle input(s) into: $(basename "$mkv")"
|
||||
dbg "Temp out : $out_tmp"
|
||||
dbg "Command : ${cmd[*]}"
|
||||
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
log "Dry-run: not executing."
|
||||
return 0
|
||||
fi
|
||||
|
||||
"${cmd[@]}"
|
||||
|
||||
[[ -f "$out_tmp" ]] || die "mkvmerge finished but temp output file not found: $out_tmp"
|
||||
|
||||
if [[ $KEEP_BAK -eq 1 ]]; then
|
||||
mv -f -- "$mkv" "$bak"
|
||||
mv -f -- "$out_tmp" "$mkv"
|
||||
log "Done. Backup kept: $(basename "$bak")"
|
||||
else
|
||||
mv -f -- "$out_tmp" "$mkv"
|
||||
log "Done. Original replaced (no .bak)."
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------- scan root ----------------
|
||||
process_root() {
|
||||
local root="$1"
|
||||
[[ -e "$root" ]] || { log "Root not found: $root"; return 0; }
|
||||
|
||||
log "Scanning root: $root"
|
||||
|
||||
local -a mkvs=()
|
||||
local f=""
|
||||
|
||||
while IFS= read -r -d '' f; do
|
||||
if is_sample_mkv "$f"; then
|
||||
dbg "Skip MKV (sample rule): $f"
|
||||
continue
|
||||
fi
|
||||
mkvs+=("$f")
|
||||
done < <(find "$root" -type f -iname "*.mkv" -print0)
|
||||
|
||||
log "Found MKVs: ${#mkvs[@]} under $root"
|
||||
if [[ ${#mkvs[@]} -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local processed=0
|
||||
local mkv=""
|
||||
for mkv in "${mkvs[@]}"; do
|
||||
mux_one_mkv "$mkv"
|
||||
((++processed))
|
||||
done
|
||||
|
||||
log "Finished root: $root (processed $processed MKV(s))"
|
||||
}
|
||||
|
||||
# ---------------- main ----------------
|
||||
main() {
|
||||
ROOTS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-n|--dry-run) DRY_RUN=1; shift ;;
|
||||
--no-bak) KEEP_BAK=0; shift ;;
|
||||
-q|--quiet) VERBOSE=0; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--) shift; break ;;
|
||||
-*) die "Unknown option: $1" ;;
|
||||
*) ROOTS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local script_dir base_dir ini_file
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
base_dir="$(cd "$script_dir/.." && pwd)"
|
||||
ini_file="$base_dir/settings.ini"
|
||||
|
||||
# If no CLI roots: read input_folder from settings.ini
|
||||
if [[ ${#ROOTS[@]} -eq 0 ]]; then
|
||||
if [[ ! -f "$ini_file" ]]; then
|
||||
echo
|
||||
echo -e "${WHITE_ON_RED} Settingsfile nicht gefunden: $ini_file ${NC}"
|
||||
echo
|
||||
read -n 1 -s -r -p "Press any key to exit"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local input_folder
|
||||
input_folder="$(ini_get "$ini_file" "pathes" "input_folder" || true)"
|
||||
|
||||
if [[ -z "${input_folder:-}" ]]; then
|
||||
echo
|
||||
echo -e "${WHITE_ON_RED} input_folder fehlt in settings.ini ([pathes]) ${NC}"
|
||||
echo
|
||||
read -n 1 -s -r -p "Press any key to exit"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ROOTS=("$input_folder")
|
||||
fi
|
||||
|
||||
detect_mkvmerge
|
||||
|
||||
echo
|
||||
echo "────────────────────────────────────────────────────────────────"
|
||||
echo -e "${BLACK_ON_WHITE} Auto Add Sub Folder (VobSub) ${NC}"
|
||||
echo "────────────────────────────────────────────────────────────────"
|
||||
echo " "
|
||||
echo -e "${WHITE_ON_GRAY} Input Folder ${NC} ${ROOTS[*]}"
|
||||
echo -e "${WHITE_ON_GRAY} Subs Folders ${NC} ${SUBDIR_CANDIDATES[*]}"
|
||||
echo -e "${WHITE_ON_GRAY} MKVMerge ${NC} ${MKVMERGE_CMD[*]}"
|
||||
echo -e "${WHITE_ON_GRAY} Dry Run ${NC} ${DRY_RUN}"
|
||||
echo -e "${WHITE_ON_GRAY} Keep Backup ${NC} ${KEEP_BAK}"
|
||||
echo " "
|
||||
echo "────────────────────────────────────────────────────────────────"
|
||||
echo
|
||||
|
||||
local r=""
|
||||
for r in "${ROOTS[@]}"; do
|
||||
process_root "$r"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "────────────────────────────────────────────────────────────────"
|
||||
echo -e "${GREEN}✔ Finished${NC}"
|
||||
echo
|
||||
read -n 1 -s -r -p "Press any key to exit"
|
||||
exit 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -139,12 +139,14 @@ main() {
|
||||
|
||||
done < <(
|
||||
find "$input_folder" \
|
||||
-type d -regex "^$escaped_output_dir$" -prune -false -o \
|
||||
-type d -regex "^$escaped_output_dir/.*" -prune -false -o \
|
||||
-type f \( \
|
||||
-iname "*.mkv" -o -iname "*.mp4" -o -iname "*.avi" -o \
|
||||
-iname "*.ts" -o -iname "*.vob" -o -iname "*.mpeg" -o -iname "*.mov" \
|
||||
\) -print
|
||||
-type d -regex "^$escaped_output_dir$" -prune -false -o \
|
||||
-type d -regex "^$escaped_output_dir/.*" -prune -false -o \
|
||||
-type f \( \
|
||||
-iname "*.mkv" -o -iname "*.mp4" -o -iname "*.avi" -o \
|
||||
-iname "*.ts" -o -iname "*.vob" -o -iname "*.mpeg" -o -iname "*.mov" \
|
||||
\) \
|
||||
! -iname "*sample.*" \
|
||||
-print
|
||||
)
|
||||
|
||||
if [ "$found_any" = false ]; then
|
||||
|
||||
Reference in New Issue
Block a user