first commit

This commit is contained in:
2025-10-09 10:55:52 +02:00
commit 6be8201511
20 changed files with 2132 additions and 0 deletions

BIN
._wunf_upd_prefix.sh Executable file

Binary file not shown.

47
checkmp4.sh Executable file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env bash
#
# checkmp4.sh <directory>
#
# Verifies all .mp4 files in the given directory ie checks if they are broken or not.
# Prints "OK" or "Not OK" for each file.
# Exit code is 0 if all files are OK, 1 if any are broken.
dir="$1"
if [ -z "$dir" ]; then
echo "Usage: $0 <directory>"
exit 2
fi
if [ ! -d "$dir" ]; then
echo "Not a directory: $dir"
exit 2
fi
bad=0
# loop through mp4 files (non-recursive)
# use `find "$dir" -type f -iname "*.mp4"` if you want recursive
shopt -s nullglob
for file in "$dir"/*.mp4 "$dir"/*.MP4; do
[ -e "$file" ] || continue
# --- Test 1: metadata sanity check ---
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration \
-of csv=p=0 "$file" > /dev/null 2>&1
probe_status=$?
# --- Test 2: deep read/decode ---
ffmpeg -v error -xerror -i "$file" -f null - -nostats > /dev/null 2>&1
decode_status=$?
if [ $probe_status -eq 0 ] && [ $decode_status -eq 0 ]; then
echo "OK $file"
else
echo "Not OK $file"
bad=1
fi
done
shopt -u nullglob
exit $bad

42
clean_nfo.sh Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail
DRYRUN=false
DIR="."
# Argumenthantering
for arg in "$@"; do
case "$arg" in
--dry-run) DRYRUN=true ;;
*) DIR="$arg" ;;
esac
done
# Kontroll att katalogen finns
if [ ! -d "$DIR" ]; then
echo "Fel: katalogen '$DIR' finns inte"
exit 1
fi
echo "Söker efter .nfo-filer i: $DIR"
$DRYRUN && echo ">>> DRY-RUN: inga filer tas bort <<<"
# Gå igenom alla .nfo-filer
find "$DIR" -type f -iname '*.nfo' -print0 |
while IFS= read -r -d '' nfo; do
base="${nfo%.*}"
# Matcha motsvarande .mp4 (case-insensitive)
shopt -s nullglob nocaseglob
mp4s=( "$base".mp4 )
shopt -u nocaseglob
if (( ${#mp4s[@]} == 0 )); then
if $DRYRUN; then
echo "[dry-run] would delete: $nfo"
else
echo "Deleting: $nfo"
rm -v -- "$nfo"
fi
fi
done

127
compare_advanced.sh Executable file
View File

@ -0,0 +1,127 @@
#!/usr/bin/env bash
# compare_advanced.sh file1.mp4 file2.mp4
set -euo pipefail
f1="${1:-}"; f2="${2:-}"
if [[ -z "$f1" || -z "$f2" ]]; then
echo "Usage: $0 <file1> <file2>" >&2
exit 2
fi
for f in "$f1" "$f2"; do
[[ -f "$f" ]] || { echo "Not found: $f" >&2; exit 2; }
done
have_signature_filter() {
ffmpeg -hide_banner -filters 2>/dev/null | grep -qE ' V[.*] signature '
}
# Pretty print quick summary (reuse from simple approach)
summary() {
local f="$1"
local size dur v w h fps
size=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f")
dur=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$f" || echo "")
v=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate -of csv=p=0 "$f" | head -n1)
IFS=',' read -r vcodec w h rfr <<<"$v"
if [[ "$rfr" == */* ]]; then
fps=$(awk -v r="$rfr" 'BEGIN{split(r,a,"/"); if(a[2]==0) print 0; else printf "%.3f", a[1]/a[2]}')
else
fps=$(awk -v r="$rfr" 'BEGIN{printf "%.3f", r+0}')
fi
printf " Size: %s bytes | Duration: %.3fs | Video: %s, %sx%s @ %.3f fps\n" "$size" "${dur:-0}" "$vcodec" "$w" "$h" "$fps"
}
if have_signature_filter; then
# Use ffmpeg's signature filter (robust against re-encode/resize)
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
sig1="$tmpdir/1.sig"
sig2="$tmpdir/2.sig"
log="$tmpdir/compare.log"
echo "Computing video fingerprints (signature filter)..."
ffmpeg -v error -i "$f1" -vf "signature=format=xml:filename=$sig1" -f null - </dev/null
ffmpeg -v error -i "$f2" -vf "signature=format=xml:filename=$sig2" -f null - </dev/null
echo "Comparing fingerprints..."
# The signature filter can compare two signature files. Different FFmpeg builds print slightly different lines.
# We run a compare pass and parse a similarity metric from stderr.
# Try two plausible invocations; accept the first that succeeds.
if ffmpeg -v info -i "$sig1" -i "$sig2" -filter_complex "signature=compare=1" -f null - 2>"$log"; then
:
elif ffmpeg -v info -i "$sig2" -i "$sig1" -filter_complex "signature=compare=1" -f null - 2>>"$log"; then
:
fi
# Extract a rough similarity ratio (fallback to match count if ratio not printed)
ratio=$(grep -Eo 'similarity[^0-9]*([0-9]+(\.[0-9]+)?)' "$log" | tail -n1 | awk '{print $NF}' || true)
matches=$(grep -Eo 'matches[^0-9]*([0-9]+)' "$log" | awk '{print $NF}' | tail -n1 || true)
echo
echo "Quick summaries:"
echo "File 1: $f1"; summary "$f1"
echo "File 2: $f2"; summary "$f2"
echo
if [[ -n "${ratio:-}" ]]; then
echo "Similarity (signature): ${ratio}"
verdict=$(awk -v r="$ratio" 'BEGIN{ if(r+0 >= 0.80) print "Very likely same content"; else if(r+0 >= 0.60) print "Possibly same with edits"; else print "Likely different"; }')
elif [[ -n "${matches:-}" ]]; then
echo "Matching frames (signature): ${matches}"
verdict=$(awk -v m="$matches" 'BEGIN{ if(m+0 >= 50) print "Very likely same content"; else if(m+0 >= 10) print "Possibly same"; else print "Likely different"; }')
else
echo "Could not parse a similarity score from FFmpeg output; check log at: $log"
verdict="Inconclusive (check logs)"
fi
echo "Verdict: $verdict"
exit 0
fi
# ---- Fallback heuristic (no signature filter) ----
# Sample 1 frame every 10s, normalize to small grayscale, hash each frame, compare overlap.
echo "Signature filter not available; using frame-sampling heuristic..."
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
extract_hashes() {
local f="$1" base="$2"
# One frame every 10s, small grayscale to be robust to resizes/re-encodes
ffmpeg -v error -i "$f" -vf "fps=1/10,scale=160:90,format=gray" -f image2pipe -vcodec png - \
| sha256sum | awk '{print $1}' > "$tmpdir/${base}.hashes"
# Note: sha256sum on the whole pipe will hash the full stream; we want per-frame hashes.
# If system sha256sum collapses the stream to one hash, fall back to numbered files:
if [[ ! -s "$tmpdir/${base}.hashes" || $(wc -l < "$tmpdir/${base}.hashes") -le 1 ]]; then
rm -f "$tmpdir/${base}"_*.png
ffmpeg -v error -i "$f" -vf "fps=1/10,scale=160:90,format=gray" "$tmpdir/${base}_%05d.png"
( cd "$tmpdir" && for p in ${base}_*.png; do sha256sum "$p" | awk '{print $1}'; done ) > "$tmpdir/${base}.hashes"
rm -f "$tmpdir/${base}"_*.png
fi
}
extract_hashes "$f1" A
extract_hashes "$f2" B
A="$tmpdir/A.hashes"; B="$tmpdir/B.hashes"
countA=$(wc -l < "$A"); countB=$(wc -l < "$B")
common=$(comm -12 <(sort "$A") <(sort "$B") | wc -l)
jaccard=$(awk -v c="$common" -v a="$countA" -v b="$countB" 'BEGIN{u=a+b-c; if(u==0) print 0; else printf "%.3f", c/u}')
echo
echo "Quick summaries:"
echo "File 1: $f1"; summary "$f1"
echo "File 2: $f2"; summary "$f2"
echo
echo "Frame-sampling similarity:"
echo " Frames hashed: file1=$countA, file2=$countB"
echo " Common hashes: $common"
echo " Jaccard index: $jaccard"
verdict=$(awk -v j="$jaccard" 'BEGIN{
if(j+0 >= 0.80) print "Very likely same content";
else if(j+0 >= 0.50) print "Possibly same content (edits/recodes)";
else print "Likely different";
}')
echo "Verdict: $verdict"

87
compare_simple.sh Executable file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env bash
# compare_simple.sh file1.mp4 file2.mp4
set -euo pipefail
f1="${1:-}"; f2="${2:-}"
if [[ -z "$f1" || -z "$f2" ]]; then
echo "Usage: $0 <file1> <file2>" >&2
exit 2
fi
for f in "$f1" "$f2"; do
[[ -f "$f" ]] || { echo "Not found: $f" >&2; exit 2; }
done
probe() {
local f="$1"
# format props
local size dur br
size=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f")
dur=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$f" || echo "")
br=$(ffprobe -v error -show_entries format=bit_rate -of default=nw=1:nk=1 "$f" || echo "")
# first video stream
local vcodec w h fps
vcodec=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=nw=1:nk=1 "$f" || echo "")
w=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=nw=1:nk=1 "$f" || echo "")
h=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=nw=1:nk=1 "$f" || echo "")
# r_frame_rate like 24000/1001 → convert to float
local rfr
rfr=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=nw=1:nk=1 "$f" || echo "")
if [[ "$rfr" == */* ]]; then
fps=$(awk -v r="$rfr" 'BEGIN{split(r,a,"/"); if(a[2]==0) print 0; else printf "%.3f", a[1]/a[2]}')
else
fps=$(awk -v r="$rfr" 'BEGIN{printf "%.3f", r+0}')
fi
# audio summary (codec:channels@rate, semicolon-separated)
local ainfo
ainfo=$(ffprobe -v error -select_streams a -show_entries stream=codec_name,channels,sample_rate -of csv=p=0 "$f" \
| awk -F, '{printf "%s:%s@%s", $1, $2, $3; if (NR!=0) printf "\n"}' \
| paste -sd';' - || true)
echo "$size|$dur|$br|$vcodec|${w}x${h}|$fps|$ainfo"
}
read -r s1 d1 br1 vc1 res1 fps1 a1 <<<"$(probe "$f1" | tr '|' ' ')"
read -r s2 d2 br2 vc2 res2 fps2 a2 <<<"$(probe "$f2" | tr '|' ' ')"
# helpers
absdiff() { awk -v a="$1" -v b="$2" 'BEGIN{d=a-b; if(d<0)d=-d; print d}'; }
pctdiff() { awk -v a="$1" -v b="$2" 'BEGIN{if(a==0&&b==0){print 0;exit} m=(a+b)/2; if(m==0){print 100;exit} d=a-b; if(d<0)d=-d; printf "%.2f", (d/m)*100 }'; }
dur_diff=$(absdiff "${d1:-0}" "${d2:-0}")
fps_diff=$(absdiff "${fps1:-0}" "${fps2:-0}")
size_pct=$(pctdiff "${s1:-0}" "${s2:-0}")
printf "File 1: %s\n" "$f1"
printf " Size: %s bytes\n Duration: %.3fs\n Video: %s, %s, %.3f fps\n" "$s1" "${d1:-0}" "${vc1:-?}" "${res1:-?}" "${fps1:-0}"
printf " Audio: %s\n" "${a1:-none}"
printf "\n"
printf "File 2: %s\n" "$f2"
printf " Size: %s bytes\n Duration: %.3fs\n Video: %s, %s, %.3f fps\n" "$s2" "${d2:-0}" "${vc2:-?}" "${res2:-?}" "${fps2:-0}"
printf " Audio: %s\n" "${a2:-none}"
printf "\n"
echo "Differences:"
printf " Size delta: %s%%\n" "$size_pct"
printf " Duration delta: %.3fs\n" "$dur_diff"
printf " FPS delta: %.3f\n" "$fps_diff"
printf " Resolution match: %s\n" "$([[ "$res1" == "$res2" ]] && echo yes || echo no)"
printf " Video codec match: %s\n" "$([[ "$vc1" == "$vc2" ]] && echo yes || echo no)"
printf " Audio tracks match: %s\n" "$([[ "$a1" == "$a2" ]] && echo yes || echo no)"
echo
# verdict heuristics
likely="Different"
# allow small tolerance on duration/fps
dur_ok=$(awk -v d="$dur_diff" 'BEGIN{print (d<=1.0)?"1":"0"}')
fps_ok=$(awk -v d="$fps_diff" 'BEGIN{print (d<=0.5)?"1":"0"}')
if [[ "$res1" == "$res2" && "$dur_ok" == "1" && "$fps_ok" == "1" ]]; then
likely="Likely same content (possibly different encode)"
fi
if [[ "$vc1" == "$vc2" && "$a1" == "$a2" && "$size_pct" == "0.00" ]]; then
likely="Identical encode/container"
fi
echo "Verdict: $likely"

122
compare_snapshot.sh Executable file
View File

@ -0,0 +1,122 @@
#!/usr/bin/env bash
# compare_snapshot.sh (robust v2)
# Compare two MP4s by grabbing a snapshot at a given time and measuring SSIM/PSNR.
#
# Usage:
# ./compare_snapshot.sh <file1.mp4> <file2.mp4> [time_seconds] [scale] [--verbose]
# Examples:
# ./compare_snapshot.sh A.mp4 B.mp4
# ./compare_snapshot.sh A.mp4 B.mp4 12 320:-1 --verbose
#
# Exit codes: 0 = ran ok (doesn't imply "very close"), 2 = usage error, 3 = ffmpeg failure
set -u # keep -e/-o pipefail off to avoid silent exits from grep/awk mismatches
f1="${1:-}"; f2="${2:-}"
t="${3:-12}"
scale="${4:-320:-1}"
verbose=0
[[ "${5:-}" == "--verbose" ]] && verbose=1
if [[ -z "${f1}" || -z "${f2}" ]]; then
echo "Usage: $0 <file1.mp4> <file2.mp4> [time_seconds] [scale] [--verbose]" >&2
exit 2
fi
for f in "$f1" "$f2"; do
[[ -f "$f" ]] || { echo "Not found: $f" >&2; exit 2; }
done
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
shot1="$tmpdir/shot1.png"
shot2="$tmpdir/shot2.png"
log="$tmpdir/metrics.log"
# Helper for verbose prints
vprint() { [[ $verbose -eq 1 ]] && echo "[DBG]" "$@" >&2; }
# 1) Extract frames (accurate seek: -ss after -i)
# Use -y to avoid “file exists” issues, and format to a stable pixel format.
cmd1=(ffmpeg -hide_banner -v error -y -i "$f1" -ss "$t" -frames:v 1 -vf "scale=$scale,format=yuv420p" "$shot1")
cmd2=(ffmpeg -hide_banner -v error -y -i "$f2" -ss "$t" -frames:v 1 -vf "scale=$scale,format=yuv420p" "$shot2")
vprint "${cmd1[@]}"
"${cmd1[@]}" || { echo "Failed to extract snapshot from $f1 at ${t}s." >&2; exit 3; }
vprint "${cmd2[@]}"
"${cmd2[@]}" || { echo "Failed to extract snapshot from $f2 at ${t}s." >&2; exit 3; }
# Sanity check: files exist and non-empty
if [[ ! -s "$shot1" || ! -s "$shot2" ]]; then
echo "Snapshot extraction produced empty files. Try a different time or scale." >&2
[[ $verbose -eq 1 ]] && ls -l "$tmpdir" >&2
exit 3
fi
# 2) Compare with SSIM (+ PSNR). Different ffmpeg builds print slightly different lines.
# We send stderr to $log and parse multiple possible formats.
cmp_cmd=(ffmpeg -hide_banner -v info -i "$shot1" -i "$shot2" -lavfi "ssim;[0:v][1:v]psnr" -f null -)
vprint "${cmp_cmd[@]}"
"${cmp_cmd[@]}" > /dev/null 2> "$log" || true
# Try to parse SSIM "All:" first, fallback to Y channel, then average/mean lines.
ssim_all="$(grep -Eo 'All:[0-9]+(\.[0-9]+)?' "$log" | head -n1 | cut -d: -f2 || true)"
[[ -z "$ssim_all" ]] && ssim_all="$(grep -Eo 'SSIM [^ ]* All:[0-9]+(\.[0-9]+)?' "$log" | awk -F'All:' '{print $2}' | head -n1 || true)"
[[ -z "$ssim_all" ]] && ssim_all="$(grep -Eo 'SSIM Y:[0-9]+(\.[0-9]+)?' "$log" | head -n1 | cut -d: -f2 || true)"
# Parse PSNR average if present (may appear as "average:xx.xx" or "avg:xx.xx").
psnr_all="$(grep -Eo 'average:[0-9]+(\.[0-9]+)?' "$log" | head -n1 | cut -d: -f2 || true)"
[[ -z "$psnr_all" ]] && psnr_all="$(grep -Eo 'avg:[0-9]+(\.[0-9]+)?' "$log" | head -n1 | cut -d: -f2 || true)"
# 3) Print summaries of the source files (optional but handy)
summarize() {
local f="$1"
local dur vinfo fps w h vcodec rfr
dur=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$f" 2>/dev/null || echo "")
vinfo=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate -of csv=p=0 "$f" 2>/dev/null | head -n1)
IFS=',' read -r vcodec w h rfr <<<"$vinfo"
if [[ "$rfr" == */* ]]; then
fps=$(awk -v r="$rfr" 'BEGIN{split(r,a,"/"); if(a[2]==0) print 0; else printf "%.3f", a[1]/a[2]}')
else
fps=$(awk -v r="$rfr" 'BEGIN{printf "%.3f", r+0}')
fi
printf " Duration: %.3fs | Video: %s, %sx%s @ %s fps\n" "${dur:-0}" "${vcodec:-?}" "${w:-?}" "${h:-?}" "${fps:-?}"
}
echo "Snapshot comparison at t=${t}s (scaled to ${scale}):"
echo "File 1: $f1"; summarize "$f1"
echo "File 2: $f2"; summarize "$f2"
echo
# 4) Handle cases where parsing failed
if [[ -z "$ssim_all" && -z "$psnr_all" ]]; then
echo "Could not parse SSIM/PSNR from ffmpeg output."
[[ $verbose -eq 1 ]] && { echo "--- ffmpeg log ---"; cat "$log"; echo "-------------------"; }
echo "Tips: try a different time (e.g. 5 or 30), or a different scale (e.g. 640:-1)."
exit 0
fi
# 5) Report metrics and give a verdict
printf "Metrics:\n"
[[ -n "$ssim_all" ]] && printf " SSIM (All): %s (1.00 = identical)\n" "$ssim_all"
[[ -n "$psnr_all" ]] && printf " PSNR avg: %s dB\n" "$psnr_all"
echo
# Verdict from SSIM (fallback to PSNR if SSIM missing)
verdict="Different"
if [[ -n "$ssim_all" ]]; then
verdict=$(awk -v s="$ssim_all" 'BEGIN{
if (s+0 >= 0.97) print "Very close (near-identical)";
else if (s+0 >= 0.90) print "Close";
else print "Different";
}')
elif [[ -n "$psnr_all" ]]; then
verdict=$(awk -v p="$psnr_all" 'BEGIN{
if (p+0 >= 40) print "Very close (near-identical)";
else if (p+0 >= 30) print "Close";
else print "Different";
}')
fi
echo "Verdict: $verdict"
# Optional: show where log is if verbose
[[ $verbose -eq 1 ]] && { echo "(Log at: $log)"; }

183
count_words.sh Executable file
View File

@ -0,0 +1,183 @@
#!/usr/bin/env bash
set -uo pipefail
# count_words.sh — Räknar underscore-separerade ord i filnamn
# Version: 1.3.0
VERSION="1.3.0"
# Usage:
# ./count_words.sh [katalog] [--to-file] [--ext "mp4,avi,mkv"] [--version]
#
# Exempel:
# ./count_words.sh .
# ./count_words.sh /path --to-file
# ./count_words.sh /path --ext "mp4,avi" --to-file
# ./count_words.sh --version
DIR="."
TO_FILE=false
OUTFILE="reserved__words.txt"
EXTS=("mp4") # default
print_help() {
cat <<EOF
count_words.sh v$VERSION
Räknar ord (underscore-separerade) i filnamn, case-insensitive.
Ignorerar filändelser; filtrerar på angivna ändelser (default: mp4).
Ignorerar prefix som [WUNF] eller [WUNF_001] i filnamn.
Usage:
$0 [dir] [--to-file] [--ext "mp4,avi,mkv"] [--version]
Flaggor:
--to-file Skriv endast orden (kommaseparerat) till $OUTFILE (append utan dubbletter)
--ext "a,b,c" Tillåtna filändelser (kommaseparerad lista), t.ex. "mp4,avi"
--version Skriv ut version och avsluta
-h, --help Visa denna hjälp
EOF
}
# Om första argumentet är en katalog, använd den
if [[ $# -gt 0 && -d "${1:-}" ]]; then
DIR="$1"
shift
fi
# Arg-parsning
while [[ $# -gt 0 ]]; do
case "$1" in
--to-file)
TO_FILE=true; shift ;;
--ext)
shift
IFS=',' read -r -a EXTS <<< "${1:-}"
shift || true
;;
--version)
echo "$VERSION"
exit 0
;;
-h|--help)
print_help; exit 0 ;;
*)
echo "Okänt argument: $1" >&2
print_help
exit 1
;;
esac
done
# Bygg lookup för tillåtna ändelser (lowercase)
declare -A ALLOWED_EXT=()
for e in "${EXTS[@]}"; do
[[ -n "$e" ]] || continue
ALLOWED_EXT["${e,,}"]=1
done
declare -A counts=()
shopt -s nullglob
for f in "$DIR"/*; do
[[ -f "$f" ]] || continue
name=$(basename "$f")
# Filtrera på tillåtna ändelser
ext="${name##*.}"
if [[ "$name" == "$ext" ]]; then
continue # ingen ändelse
fi
ext_lc="${ext,,}"
[[ ${ALLOWED_EXT[$ext_lc]+_} ]] || continue
# Ta bort ändelsen
base="${name%.*}"
# TA BORT WUNF-PREFIX: [WUNF] eller [WUNF_ddd] var som helst i namnet
# (behåll allt annat)
base_noprefix=$(sed -E 's/\[WUNF(_[0-9]{1,3})?\]//g' <<< "$base")
# base_noprefix=$(sed -E 's/\[WUNF(_[0-9]{3})?\]//g' <<< "$base")
# Splitta på '_' och räkna (case-insensitive)
oldIFS=$IFS
IFS='_'
read -r -a parts <<< "$base_noprefix"
IFS=$oldIFS
for word in "${parts[@]}"; do
# Trimma whitespace
w="$word"
w="${w#"${w%%[![:space:]]*}"}" # trim left
w="${w%"${w##*[![:space:]]}"}" # trim right
[[ -n "$w" ]] || continue
# Normalisera till lowercase
w_lc="${w,,}"
# Initiera och öka
: "${counts[$w_lc]:=0}"
(( counts[$w_lc]++ ))
done
done
# Samla ord med förekomst > 2
results=()
for w in "${!counts[@]}"; do
if (( counts[$w] > 2 )); then
results+=("${counts[$w]} $w")
fi
done
# Ingen träff?
if [[ ${#results[@]} -eq 0 ]]; then
if $TO_FILE; then
if [[ -f "$OUTFILE" ]]; then
echo "Inga nya ord (>2) hittades. Filen lämnades oförändrad. (v$VERSION)"
else
: > "$OUTFILE"
echo "Inga ord (>2) hittades. Tom fil skapad: $OUTFILE (v$VERSION)"
fi
else
echo "Inga ord förekom mer än två gånger. (v$VERSION)"
fi
exit 0
fi
# Sortera: primärt på antal (fallande), sekundärt på ord (stigande)
sorted_lines=$(printf "%s\n" "${results[@]}" | sort -nrk1,1 -k2,2)
if $TO_FILE; then
# Nya ord (lowercase redan), en per rad
new_words=$(printf "%s\n" "$sorted_lines" | awk '{print $2}')
# Läs befintliga ord från OUTFILE (om den finns) och bygg union utan dubbletter
declare -A uniq=()
if [[ -f "$OUTFILE" ]]; then
# Tolka både kommatecken och whitespace som separerare
while IFS= read -r tok; do
w="$tok"
# trim
w="${w#"${w%%[![:space:]]*}"}"
w="${w%"${w##*[![:space:]]}"}"
[[ -n "$w" ]] || continue
uniq["${w,,}"]=1
done < <(tr -s ',[:space:]' '\n' < "$OUTFILE")
fi
# Lägg till nya ord
while IFS= read -r nw; do
[[ -n "$nw" ]] || continue
uniq["$nw"]=1
done <<< "$new_words"
# Bygg kommaseparerad sträng (alfabetiskt för determinism)
all_words=$(printf "%s\n" "${!uniq[@]}" | sort -u | paste -sd, -)
# Skriv tillbaka hela unionen (utan dubbletter)
echo "$all_words" > "$OUTFILE"
echo "Resultat uppdaterat i $OUTFILE (v$VERSION)"
else
echo "Ord som förekommit mer än två gånger (sorterat högst->lägst) — v$VERSION:"
while read -r count word; do
echo "$word ($count ggr)"
done <<< "$sorted_lines"
fi

182
count_words.sh.save Executable file
View File

@ -0,0 +1,182 @@
#!/usr/bin/env bash
set -uo pipefail
# count_words.sh — Räknar underscore-separerade ord i filnamn
# Version: 1.3.0
VERSION="1.3.1"
# Usage:
# ./count_words.sh [katalog] [--to-file] [--ext "mp4,avi,mkv"] [--version]
#
# Exempel:
# ./count_words.sh .
# ./count_words.sh /path --to-file
# ./count_words.sh /path --ext "mp4,avi" --to-file
# ./count_words.sh --version
DIR="."
TO_FILE=false
OUTFILE="reserved__words.txt"
EXTS=("mp4") # default
print_help() {
cat <<EOF
count_words.sh v$VERSION
Räknar ord (underscore-separerade) i filnamn, case-insensitive.
Ignorerar filändelser; filtrerar på angivna ändelser (default: mp4).
Ignorerar prefix som [WUNF] eller [WUNF_001] i filnamn.
Usage:
$0 [dir] [--to-file] [--ext "mp4,avi,mkv"] [--version]
Flaggor:
--to-file Skriv endast orden (kommaseparerat) till $OUTFILE (append utan dubbletter)
--ext "a,b,c" Tillåtna filändelser (kommaseparerad lista), t.ex. "mp4,avi"
--version Skriv ut version och avsluta
-h, --help Visa denna hjälp
EOF
}
# Om första argumentet är en katalog, använd den
if [[ $# -gt 0 && -d "${1:-}" ]]; then
DIR="$1"
shift
fi
# Arg-parsning
while [[ $# -gt 0 ]]; do
case "$1" in
--to-file)
TO_FILE=true; shift ;;
--ext)
shift
IFS=',' read -r -a EXTS <<< "${1:-}"
shift || true
;;
--version)
echo "$VERSION"
exit 0
;;
-h|--help)
print_help; exit 0 ;;
*)
echo "Okänt argument: $1" >&2
print_help
exit 1
;;
esac
done
# Bygg lookup för tillåtna ändelser (lowercase)
declare -A ALLOWED_EXT=()
for e in "${EXTS[@]}"; do
[[ -n "$e" ]] || continue
ALLOWED_EXT["${e,,}"]=1
done
declare -A counts=()
shopt -s nullglob
for f in "$DIR"/*; do
[[ -f "$f" ]] || continue
name=$(basename "$f")
# Filtrera på tillåtna ändelser
ext="${name##*.}"
if [[ "$name" == "$ext" ]]; then
continue # ingen ändelse
fi
ext_lc="${ext,,}"
[[ ${ALLOWED_EXT[$ext_lc]+_} ]] || continue
# Ta bort ändelsen
base="${name%.*}"
# TA BORT WUNF-PREFIX: [WUNF] eller [WUNF_ddd] var som helst i namnet
# (behåll allt annat)
base_noprefix=$(sed -E 's/\[WUNF(_[0-9]{3})?\]//g' <<< "$base")
# Splitta på '_' och räkna (case-insensitive)
oldIFS=$IFS
IFS='_'
read -r -a parts <<< "$base_noprefix"
IFS=$oldIFS
for word in "${parts[@]}"; do
# Trimma whitespace
w="$word"
w="${w#"${w%%[![:space:]]*}"}" # trim left
w="${w%"${w##*[![:space:]]}"}" # trim right
[[ -n "$w" ]] || continue
# Normalisera till lowercase
w_lc="${w,,}"
# Initiera och öka
: "${counts[$w_lc]:=0}"
(( counts[$w_lc]++ ))
done
done
# Samla ord med förekomst > 2
results=()
for w in "${!counts[@]}"; do
if (( counts[$w] > 2 )); then
results+=("${counts[$w]} $w")
fi
done
# Ingen träff?
if [[ ${#results[@]} -eq 0 ]]; then
if $TO_FILE; then
if [[ -f "$OUTFILE" ]]; then
echo "Inga nya ord (>2) hittades. Filen lämnades oförändrad. (v$VERSION)"
else
: > "$OUTFILE"
echo "Inga ord (>2) hittades. Tom fil skapad: $OUTFILE (v$VERSION)"
fi
else
echo "Inga ord förekom mer än två gånger. (v$VERSION)"
fi
exit 0
fi
# Sortera: primärt på antal (fallande), sekundärt på ord (stigande)
sorted_lines=$(printf "%s\n" "${results[@]}" | sort -nrk1,1 -k2,2)
if $TO_FILE; then
# Nya ord (lowercase redan), en per rad
new_words=$(printf "%s\n" "$sorted_lines" | awk '{print $2}')
# Läs befintliga ord från OUTFILE (om den finns) och bygg union utan dubbletter
declare -A uniq=()
if [[ -f "$OUTFILE" ]]; then
# Tolka både kommatecken och whitespace som separerare
while IFS= read -r tok; do
w="$tok"
# trim
w="${w#"${w%%[![:space:]]*}"}"
w="${w%"${w##*[![:space:]]}"}"
[[ -n "$w" ]] || continue
uniq["${w,,}"]=1
done < <(tr -s ',[:space:]' '\n' < "$OUTFILE")
fi
# Lägg till nya ord
while IFS= read -r nw; do
[[ -n "$nw" ]] || continue
uniq["$nw"]=1
done <<< "$new_words"
# Bygg kommaseparerad sträng (alfabetiskt för determinism)
all_words=$(printf "%s\n" "${!uniq[@]}" | sort -u | paste -sd, -)
# Skriv tillbaka hela unionen (utan dubbletter)
echo "$all_words" > "$OUTFILE"
echo "Resultat uppdaterat i $OUTFILE (v$VERSION)"
else
echo "Ord som förekommit mer än två gånger (sorterat högst->lägst) — v$VERSION:"
while read -r count word; do
echo "$word ($count ggr)"
done <<< "$sorted_lines"
fi

90
detect_wunf.sh Executable file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env bash
# detect_pink_intro_hsv.sh <file.mp4> [duration_s] [fps] [hue_min] [hue_max] [sat_min] [val_min] [min_ratio]
#
# Detect a magenta/pink intro by averaging frames to 1x1 and classifying in HSV.
#
# Defaults:
# duration_s = 1.0
# fps = 6
# hue_min = 285 # degrees, magenta band start
# hue_max = 330 # degrees, magenta band end
# sat_min = 0.35 # require some saturation
# val_min = 0.35 # avoid very dark frames
# min_ratio = 0.6 # % of sampled frames that must be pink-ish
#
# Exit codes: 0 detected, 1 not detected, 2 usage error
set -euo pipefail
file="${1:-}"
duration="${2:-1.0}"
sample_fps="${3:-6}"
HMIN="${4:-285}"
HMAX="${5:-330}"
SMIN="${6:-0.35}"
VMIN="${7:-0.35}"
MIN_RATIO="${8:-0.6}"
if [[ -z "$file" ]]; then
echo "Usage: $0 <file.mp4> [duration_s] [fps] [hue_min] [hue_max] [sat_min] [val_min] [min_ratio]" >&2
exit 2
fi
[[ -f "$file" ]] || { echo "File not found: $file" >&2; exit 2; }
# Stream 1x1 RGB for sampled frames; each frame = 3 bytes. Parse with od+awk.
summary="$(
LC_ALL=C ffmpeg -hide_banner -v error \
-i "$file" -t "$duration" \
-vf "fps=${sample_fps},scale=1:1:flags=area,format=rgb24" \
-f rawvideo - 2>/dev/null \
| od -An -tu1 -w3 \
| awk -v HMIN="$HMIN" -v HMAX="$HMAX" -v SMIN="$SMIN" -v VMIN="$VMIN" '
function max(a,b){return a>b?a:b}
function min(a,b){return a<b?a:b}
# Convert RGB(0..255) to HSV; return H in degrees [0,360), S,V in [0,1]
function rgb2hsv(R,G,B, r,g,b,M,m,d,H,S,V) {
r=R/255.0; g=G/255.0; b=B/255.0;
M=r; if(g>M) M=g; if(b>M) M=b;
m=r; if(g<m) m=g; if(b<m) m=b;
d=M-m; V=M;
if (M==0) { S=0 } else { S=(d==0?0:d/M) }
if (d==0) { H=0 }
else if (M==r) { H = 60.0 * fmod(((g-b)/d), 6) }
else if (M==g) { H = 60.0 * (((b-r)/d) + 2) }
else { H = 60.0 * (((r-g)/d) + 4) }
if (H < 0) H += 360.0
hsv[1]=H; hsv[2]=S; hsv[3]=V
}
function fmod(a,b){ return a - int(a/b)*b }
BEGIN{ total=0; pink=0; }
NF==3 {
R=$1; G=$2; B=$3;
total++;
rgb2hsv(R,G,B);
H=hsv[1]; S=hsv[2]; V=hsv[3];
# classify: hue in magenta band AND sufficiently saturated/bright
if (H>=HMIN && H<=HMAX && S>=SMIN && V>=VMIN) pink++;
# save last metrics to show a couple of examples (optional)
if (total<=10) printf "DBG frame%02d: RGB=(%3d,%3d,%3d) HSV=(%6.1f°, %4.2f, %4.2f)\n", total,R,G,B,H,S,V > "/dev/stderr"
}
END {
if (total==0) { printf "TOTAL=0 PINK=0 RATIO=0\n"; exit; }
printf "TOTAL=%d PINK=%d RATIO=%.3f\n", total, pink, pink/total;
}'
)"
if ! printf "%s" "$summary" | grep -q 'TOTAL='; then
echo "No frames parsed (clip too short or pipeline issue)."
exit 1
fi
eval "$summary" # sets TOTAL,PINK,RATIO
echo "File: $file"
echo "Analyzed: first ${duration}s @ ${sample_fps} fps → frames: ${TOTAL}"
echo "Pink-ish frames (hue in ${HMIN}-${HMAX}°, S>=${SMIN}, V>=${VMIN}): ${PINK}"
echo "Ratio: ${RATIO} (threshold: ${MIN_RATIO})"
awk -v r="$RATIO" -v thr="$MIN_RATIO" 'BEGIN{ exit !(r+0 >= thr+0) }' \
&& { echo "DETECTED: Pink intro present"; exit 0; }
echo "Not detected"; exit 1

106
mvi.sh Executable file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Användning:
mvi KÄLLA MÅL
mvi KÄLLA1 KÄLLA2 ... KATALOG
Flyttar som 'mv', men om en målfil redan finns så läggs ett index in
före filändelsen, t.ex. "fil.txt" -> "fil.1.txt", "fil.2.txt", ...
Exempel:
mvi bild.jpg bilder/ # bilder/bild.jpg eller bilder/bild.1.jpg
mvi .env prod/ # prod/.env eller prod/.env.1
mvi projekt/ arkiv/ # arkiv/projekt eller arkiv/projekt.1
EOF
}
# Ta reda på om sista argumentet är en katalog (målet när flera källor)
is_dir() {
local p=$1
[[ -d "$p" ]]
}
# Skapa ett indexerat fil-/katalognamn om target redan finns.
# Regler:
# - index (".N") injiceras före sista filändelsen
# - för dolda filer utan ändelse (t.ex. ".env"): ".env.1"
# - för flera punkter (t.ex. "fil.tar.gz"): "fil.tar.1.gz"
indexed_path() {
local target="$1"
local dir base name ext n candidate
dir=$(dirname -- "$target")
name=$(basename -- "$target")
# Dela upp name i bas + ev. ext (sista punkten räknas som extension-separator)
if [[ "$name" = .* && "$name" != *.* ]]; then
# Dold fil utan extension (".env")
base="$name"
ext=""
elif [[ "$name" = *.* ]]; then
base="${name%.*}"
ext=".${name##*.}"
else
base="$name"
ext=""
fi
n=1
while :; do
candidate="${dir}/${base}.${n}${ext}"
[[ ! -e "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
n=$((n+1))
done
}
# Flytta en enskild källa till ett givet mål (fil eller katalog)
move_one() {
local src="$1"
local dst="$2"
local target
if [[ -d "$dst" ]]; then
target="${dst%/}/$(basename -- "$src")"
else
target="$dst"
fi
if [[ -e "$target" ]]; then
target=$(indexed_path "$target")
fi
# Skapa målkatalog om den inte finns (som mv gör med -t dir om den finns)
mkdir -p -- "$(dirname -- "$target")"
mv -- "$src" "$target"
}
# --- Huvudprogram ---
if [[ $# -lt 2 ]]; then
usage
exit 1
fi
# Sista arg är destination
dest="${@: -1}"
sources=("${@:1:$(($#-1))}")
# Om flera källor: dest måste vara katalog
if [[ ${#sources[@]} -gt 1 ]]; then
if ! is_dir "$dest"; then
echo "Fel: Flera källor kräver att destinationen är en katalog." >&2
exit 2
fi
fi
# Iterera över alla källor
for src in "${sources[@]}"; do
if [[ ! -e "$src" ]]; then
echo "Varning: källan finns inte: $src" >&2
continue
fi
move_one "$src" "$dest"
done

266
rename_and_filter.sh Executable file
View File

@ -0,0 +1,266 @@
#!/usr/bin/env bash
# =============================================================================
# rename_and_filter.sh
#
# Version: 1.3.1
# Last updated: 2025-10-04
#
# Summary
# -------
# Given a directory, a comma-separated file of reserved words, and an optional
# file extension filter:
# 1) Removes any filename tokens (separated by "_") that match reserved words
# (case-insensitive, exact token matches).
# - EXTRA: If a reserved word appears as the last token (e.g., "..._good"),
# it is removed even if it's only delimited on the left side.
# 2) Replaces one or more spaces with a single underscore "_".
# 3) Collapses multiple "_" into a single "_", and trims leading/trailing "_".
#
# Notes:
# - No files are deleted; only renaming occurs.
# - Processes files only in the given directory (non-recursive).
# - Collision handling: if target name exists, suffix "_N" before the extension.
# - Reserved words match only against the basename tokens (extension excluded).
#
# Extension filter:
# - Default: mp4
# - "*" : all files
# - "jpg" : only .jpg (with or without leading dot accepted, e.g., "jpg" or ".jpg")
#
# Logging:
# RENAME: "old_name.ext" => "new_name.ext"
# NOCHANGE (dry-run): "file.ext" (nothing to do)
#
# Usage
# -----
# ./rename_and_filter.sh [--dry-run] <directory> <reserved_words.csv> [extension]
#
# Examples
# --------
# # Dry-run on mp4 files (default):
# ./rename_and_filter.sh --dry-run ./videos ./reserved_words.csv
#
# # All files:
# ./rename_and_filter.sh ./media ./reserved_words.csv "*"
#
# # Only jpg:
# ./rename_and_filter.sh ./pictures ./reserved_words.csv jpg
#
# Exit codes
# ----------
# 0 Success
# 1 Invalid arguments / missing files
# 2 Requires bash >= 4
# =============================================================================
set -euo pipefail
# Requires bash 4 for associative arrays
if [ "${BASH_VERSINFO:-0}" -lt 4 ]; then
echo "This script requires bash >= 4." >&2
exit 2
fi
DRY_RUN=0
print_help() {
sed -n '2,160p' "$0" | sed 's/^# \{0,1\}//'
}
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
rename_file() {
local src="$1"
local dst="$2"
# Guard: identical path, nothing to do
if [[ "$src" == "$dst" ]]; then
if (( DRY_RUN )); then
log "NOCHANGE (dry-run): \"$(basename "$src")\" (nothing to do)"
fi
return 0
fi
local base ext candidate n
base="${dst##*/}"
ext=""
if [[ "$base" == *.* ]]; then
ext=".${base##*.}"
base="${base%.*}"
fi
candidate="$dst"
n=1
# Avoid collisions (unless it's the same file, already handled above)
while [[ -e "$candidate" && "$src" != "$candidate" ]]; do
candidate="$(dirname "$dst")/${base}_$n$ext"
((n++))
done
if (( DRY_RUN )); then
log "RENAME: \"$(basename "$src")\" => \"$(basename "$candidate")\""
else
mv -v -- "$src" "$candidate" >/dev/null
log "RENAME: \"$(basename "$src")\" => \"$(basename "$candidate")\""
fi
}
# --- Argument parsing ---
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
print_help
exit 0
fi
if [[ "${1:-}" == "-n" || "${1:-}" == "--dry-run" ]]; then
DRY_RUN=1
shift
fi
if [[ $# -lt 2 || $# -gt 3 ]]; then
echo "Error: Provide <directory> <reserved_words.csv> [extension]. Use --help for info." >&2
exit 1
fi
DIR="$1"
WORD_FILE="$2"
EXT="${3:-mp4}"
# Normalize EXT: strip leading dot if present
if [[ "$EXT" == .* ]]; then
EXT="${EXT#.}"
fi
if [[ ! -d "$DIR" ]]; then
echo "Error: Directory does not exist: $DIR" >&2
exit 1
fi
if [[ ! -f "$WORD_FILE" ]]; then
echo "Error: Reserved words file does not exist: $WORD_FILE" >&2
exit 1
fi
# --- Load reserved words into an associative set ---
declare -A RESERVED=()
# Allow comma and newlines as separators
# shellcheck disable=SC2002
mapfile -t _tokens < <(cat "$WORD_FILE" | tr ',\r\n' '\n')
for raw in "${_tokens[@]}"; do
# trim whitespace
w="${raw#"${raw%%[![:space:]]*}"}" # ltrim
w="${w%"${w##*[![:space:]]}"}" # rtrim
[[ -z "$w" ]] && continue
RESERVED["${w,,}"]=1
done
log "Processing directory: $DIR (dry-run=$DRY_RUN, extension=${EXT})"
log "Reserved words loaded: ${#RESERVED[@]}"
# --- Normalize (rules 2 & 3) ---
normalize_name() {
local name="$1"
# Replace one-or-more spaces with single underscore
name="$(printf '%s' "$name" | sed -E 's/[[:space:]]+/_/g')"
# Collapse multiple underscores
name="$(printf '%s' "$name" | sed -E 's/_+/_/g')"
# Trim leading/trailing underscores from the basename (keep extension)
local base ext
base="$name"
ext=""
if [[ "$base" == *.* ]]; then
ext=".${base##*.}"
base="${base%.*}"
fi
base="${base##_}"
base="${base%%_}"
printf '%s%s' "$base" "$ext"
}
# --- Remove reserved tokens from basename (plus end-token rule) ---
strip_reserved() {
local name="$1"
local base ext token lw
local -a parts new_parts=()
base="$(basename "$name")"
ext=""
if [[ "$base" == *.* ]]; then
ext=".${base##*.}"
base="${base%.*}"
fi
# Pre-normalize for tokenization
local norm_base
norm_base="$(printf '%s' "$base" | sed -E 's/[[:space:]]+/_/g' | sed -E 's/_+/_/g')"
IFS='_' read -r -a parts <<< "$norm_base"
for token in "${parts[@]}"; do
[[ -z "$token" ]] && continue
lw="${token,,}"
if [[ -n "${RESERVED[$lw]:-}" ]]; then
continue
fi
new_parts+=("$token")
done
local new_base
if (( ${#new_parts[@]} == 0 )); then
new_base=""
else
new_base="$(IFS=_; echo "${new_parts[*]}")"
fi
# Extra end-token rule (defensive): if last token is reserved, drop it
if [[ -n "$new_base" ]]; then
local last last_lc
last="${new_base##*_}"
last_lc="${last,,}"
if [[ -n "${RESERVED[$last_lc]:-}" ]]; then
if [[ "$new_base" == *_* ]]; then
new_base="${new_base%_*}"
else
new_base=""
fi
fi
fi
# Fallback if everything disappeared
if [[ -z "$new_base" ]]; then
new_base="untitled"
fi
printf '%s%s' "$new_base" "$ext"
}
# --- File pattern based on EXT ---
shopt -s nullglob
pattern="*"
if [[ "$EXT" != "*" ]]; then
pattern="*.$EXT"
fi
# --- Main loop ---
for path in "$DIR"/$pattern; do
[[ -f "$path" ]] || continue
orig_basename="$(basename "$path")"
# 1) remove reserved tokens from basename
stripped="$(strip_reserved "$orig_basename")"
# 2 & 3) normalize (spaces -> "_", collapse "_", trim)
new_name="$(normalize_name "$stripped")"
# NEW: If nothing changed, do not attempt to rename at all
if [[ "$new_name" == "$orig_basename" ]]; then
if (( DRY_RUN )); then
log "NOCHANGE (dry-run): \"${orig_basename}\" (nothing to do)"
fi
continue
fi
target="$(dirname "$path")/$new_name"
rename_file "$path" "$target"
done
log "Done."

40
rename_space.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
# Standardvärden
DIR="."
DRY_RUN=false
# Läs argument
for arg in "$@"; do
case "$arg" in
--dry-run)
DRY_RUN=true
;;
*)
DIR="$arg"
;;
esac
done
# Gå igenom alla filer i katalogen rekursivt
find "$DIR" -depth -type f | while read -r file; do
dir=$(dirname "$file")
base=$(basename "$file")
# Byt ut mellanslag mot underscore
newbase=$(echo "$base" | tr ' ' '_')
# Byt ut flera underscores mot en enda
newbase=$(echo "$newbase" | sed 's/_\+/_/g')
# Om namnet har ändrats
if [ "$base" != "$newbase" ]; then
newpath="$dir/$newbase"
if [ "$DRY_RUN" = true ]; then
echo "[Dry-run] $file -> $newpath"
else
echo "Renaming: $file -> $newpath"
mv -i "$file" "$newpath"
fi
fi
done

94
run_wunf.sh Executable file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -euo pipefail
# Hitta scriptets egen katalog
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Standardvärden
DIR="." # <-- katalogen man står i
PREFIX="[WUNF]_"
CSV="wunf_scenes.csv"
EXT="mp4"
LOG_DIR="${HOME}/log"
LOG_FILE="${LOG_DIR}/run_wunf.log"
SCAN_SCRIPT="${SCRIPT_DIR}/scan_and_prefix_pink.sh"
UPDATE_SCRIPT="${SCRIPT_DIR}/wunf_upd_prefix.sh"
# Skapa loggmapp om den inte finns
mkdir -p "$LOG_DIR"
# Starta loggning: både stdout och stderr till loggfil + terminal
exec > >(tee -a "$LOG_FILE") 2>&1
echo "============================================================"
echo " Körning startad: $(date)"
echo " Script: $0"
echo " Loggfil: $LOG_FILE"
echo "============================================================"
usage() {
cat <<EOF
Usage: $(basename "$0") [options]
Kör scan + prefix och uppdatera därefter prefix i CSV-listan.
Options:
-d, --dir <path> Katalog att skanna (default: .)
-p, --prefix <str> Prefix att sätta (default: [WUNF]_)
-c, --csv <file> CSV-fil att använda (default: wunf_scenes.csv)
-e, --ext <ext> Filändelse att matcha (default: mp4)
-h, --help Visa denna hjälp
Exempel:
$(basename "$0") --dir . --prefix "[WUNF]_"
EOF
}
# --- Parse args ---
while [[ $# -gt 0 ]]; do
case "$1" in
-d|--dir) DIR="${2:-}"; shift 2 ;;
-p|--prefix) PREFIX="${2:-}"; shift 2 ;;
-c|--csv) CSV="${2:-}"; shift 2 ;;
-e|--ext) EXT="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Okänd flagga: $1"; usage; exit 1 ;;
esac
done
# --- Kontroller ---
[[ -d "$DIR" ]] || { echo "Fel: katalogen finns ej: $DIR" >&2; exit 1; }
[[ -x "$SCAN_SCRIPT" ]] || { echo "Fel: saknar körbart $SCAN_SCRIPT" >&2; exit 1; }
[[ -x "$UPDATE_SCRIPT" ]] || { echo "Fel: saknar körbart $UPDATE_SCRIPT" >&2; exit 1; }
# --- Cleanup-hantering ---
LIST_FILE="$(mktemp -t wunf_filenames.XXXXXX.txt)"
cleanup() {
rm -f "$LIST_FILE"
echo "Tillfällig fil raderad: $LIST_FILE"
echo "Körning avslutad: $(date)"
echo "============================================================"
}
trap cleanup EXIT INT TERM
echo "==> Steg 1: Scannar och sätter prefix (${PREFIX}) i ${DIR}"
"$SCAN_SCRIPT" "$DIR" --prefix "$PREFIX"
echo "==> Steg 2: Skapar fil-lista för filer med prefix (${PREFIX}) och .${EXT}"
escaped_prefix_for_glob="$(printf '%s' "${PREFIX}" | sed -e 's/\[/\\[/g' -e 's/]/\\]/g')"
pattern="${escaped_prefix_for_glob}*.${EXT}"
pushd "$DIR" > /dev/null
# shellcheck disable=SC2086
ls -1d --quoting-style=literal $pattern > "$LIST_FILE" 2>/dev/null || true
popd > /dev/null
if [[ ! -s "$LIST_FILE" ]]; then
echo "Inga filer matchade '${PREFIX}*.${EXT}' i ${DIR}." >&2
exit 2
fi
echo "==> Hittade $(wc -l < "$LIST_FILE" | tr -d ' ') filer. Kör uppdateringsscriptet…"
"$UPDATE_SCRIPT" --list "$LIST_FILE" "$CSV"
echo "==> Klart!"

34
sanitycheck.sh Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
#
# sanitycheck.sh <file.mp4>
#
# Returns: "OK" if file looks good, "Not OK" otherwise.
file="$1"
if [ -z "$file" ]; then
echo "Usage: $0 <file.mp4>"
exit 2
fi
if [ ! -f "$file" ]; then
echo "Not OK (file not found)"
exit 1
fi
# --- Test 1: metadata sanity check ---
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration \
-of csv=p=0 "$file" > /dev/null 2>&1
probe_status=$?
# --- Test 2: deep read/decode ---
ffmpeg -v error -xerror -i "$file" -f null - -nostats > /dev/null 2>&1
decode_status=$?
if [ $probe_status -eq 0 ] && [ $decode_status -eq 0 ]; then
echo "OK"
exit 0
else
echo "Not OK"
exit 1
fi

30
scan_and_prefix_pink.md Normal file
View File

@ -0,0 +1,30 @@
Examples
Basic (non-recursive):
chmod +x scan_and_prefix_pink.sh
./scan_and_prefix_pink.sh /path/to/videos
Recursive + dry-run + custom prefix:
./scan_and_prefix_pink.sh /path/to/videos --recursive --dry-run --prefix INTRO_
Stricter detection (wider window, brighter requirement, higher ratio):
./scan_and_prefix_pink.sh /path/to/videos \
--duration 1.2 --fps 6 --hue-min 280 --hue-max 335 \
--sat-min 0.40 --val-min 0.45 --min-ratio 0.7
Notes
Collision-safe: if the target name exists, the script appends .1, .2, … to the new filename.
Idempotent: already-prefixed files are skipped.
Output: one line per file with a clear status:
RENAMED, NOT_MATCH, SKIPPED, or ERROR (with a short reason).
You can tweak the HSV thresholds if your intro varies (e.g., broader hue band 270340 or lower sat-min for paler pink).

152
scan_and_prefix_pink.sh Executable file
View File

@ -0,0 +1,152 @@
#!/usr/bin/env bash
# scan_and_prefix_pink.sh
#
# Scan a directory for MP4 files, detect a pink/magenta intro,
# and prefix matching files (unless already prefixed).
#
# Options:
# --dry-run, --recursive/-r, --prefix PREFIX, etc. (same as before)
set -euo pipefail
# ---- Defaults ----
DRY_RUN=0
RECURSIVE=0
PREFIX="PINK_"
DURATION="1.0"
FPS="6"
HUE_MIN="285"
HUE_MAX="330"
SAT_MIN="0.35"
VAL_MIN="0.35"
MIN_RATIO="0.6"
# ---- Parse args ----
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <directory> [--dry-run] [--recursive|-r] [--prefix PREFIX] ..." >&2
exit 2
fi
DIR="$1"; shift || true
[[ -d "$DIR" ]] || { echo "Not a directory: $DIR" >&2; exit 2; }
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--recursive|-r) RECURSIVE=1; shift ;;
--prefix) PREFIX="${2:-}"; shift 2 ;;
--duration) DURATION="${2:-}"; shift 2 ;;
--fps) FPS="${2:-}"; shift 2 ;;
--hue-min) HUE_MIN="${2:-}"; shift 2 ;;
--hue-max) HUE_MAX="${2:-}"; shift 2 ;;
--sat-min) SAT_MIN="${2:-}"; shift 2 ;;
--val-min) VAL_MIN="${2:-}"; shift 2 ;;
--min-ratio) MIN_RATIO="${2:-}"; shift 2 ;;
--help|-h)
grep -E '^# ' "$0" | sed 's/^# //'; exit 0 ;;
*) echo "Unknown option: $1" >&2; exit 2 ;;
esac
done
# ---- Helpers ----
print_result() { # $1=status $2=file $3=detail
printf "%-10s %s%s\n" "$1" "$2" "${3:+ ($3)}"
}
safe_rename() { # $1=path $2=prefix
local path="$1" pre="$2"
local dir base target
dir=$(dirname -- "$path")
base=$(basename -- "$path")
target="${dir}/${pre}${base}"
# Avoid collisions
if [[ -e "$target" ]]; then
local n=1
while [[ -e "${target}.${n}" ]]; do n=$((n+1)); done
target="${target}.${n}"
fi
if [[ $DRY_RUN -eq 1 ]]; then
print_result "RENAMED" "$path" "dry-run → $(basename -- "$target")"
else
if mv -- "$path" "$target"; then
print_result "RENAMED" "$path" "$(basename -- "$target")"
else
print_result "ERROR" "$path" "rename failed"
fi
fi
}
detect_pink() { # $1=file -> echoes "total pink ratio"
local file="$1"
LC_ALL=C ffmpeg -hide_banner -v error \
-i "$file" -t "$DURATION" \
-vf "fps=${FPS},scale=1:1:flags=area,format=rgb24" \
-f rawvideo - 2>/dev/null \
| od -An -tu1 -w3 \
| awk -v HMIN="$HUE_MIN" -v HMAX="$HUE_MAX" -v SMIN="$SAT_MIN" -v VMIN="$VAL_MIN" '
function fmod(a,b){ return a - int(a/b)*b }
function rgb2hsv(R,G,B, r,g,b,M,m,d,H,S,V) {
r=R/255.0; g=G/255.0; b=B/255.0;
M=r; if(g>M) M=g; if(b>M) M=b;
m=r; if(g<m) m=g; if(b<m) m=b;
d=M-m; V=M;
if (M==0) S=0; else S=(d==0?0:d/M);
if (d==0) H=0;
else if (M==r) H = 60.0 * fmod(((g-b)/d), 6);
else if (M==g) H = 60.0 * (((b-r)/d) + 2);
else H = 60.0 * (((r-g)/d) + 4);
if (H < 0) H += 360.0;
hsv[1]=H; hsv[2]=S; hsv[3]=V;
}
BEGIN{ total=0; pink=0; }
NF==3 {
R=$1; G=$2; B=$3; total++;
rgb2hsv(R,G,B); H=hsv[1]; S=hsv[2]; V=hsv[3];
if (H>=HMIN && H<=HMAX && S>=SMIN && V>=VMIN) pink++;
}
END {
if (total==0) { print "0 0 0"; exit; }
printf "%d %d %.3f\n", total, pink, pink/total;
}'
}
is_mp4() { case "$1" in *.mp4|*.MP4) return 0;; *) return 1;; esac; }
# ---- File list ----
if [[ $RECURSIVE -eq 1 ]]; then
mapfile -t FILES < <(find "$DIR" -type f \( -iname '*.mp4' \))
else
mapfile -t FILES < <(ls "$DIR"/*.mp4 "$DIR"/*.MP4 2>/dev/null || true)
fi
# ---- Process ----
if [[ ${#FILES[@]} -eq 0 ]]; then
echo "No MP4 files found in: $DIR"
exit 0
fi
for f in "${FILES[@]}"; do
[[ -f "$f" ]] || { print_result "SKIPPED" "$f" "not a file"; continue; }
is_mp4 "$f" || { print_result "SKIPPED" "$f" "not mp4"; continue; }
base=$(basename -- "$f")
if [[ "$base" == "$PREFIX"* ]]; then
print_result "SKIPPED" "$f" "already prefixed"
continue
fi
read -r total pink ratio < <(detect_pink "$f")
if [[ "$total" -eq 0 ]]; then
print_result "ERROR" "$f" "no frames parsed"
continue
fi
awk -v r="$ratio" -v thr="$MIN_RATIO" 'BEGIN{exit !(r>=thr)}' && {
safe_rename "$f" "$PREFIX"
continue
}
print_result "NOT_MATCH" "$f" "ratio=${ratio} (<${MIN_RATIO})"
done

28
scrapWUNFIdx.sh Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
OUTFILE="${1:-wunf_scenes.csv}"
# Töm (eller skapa) utfilen
: > "$OUTFILE"
for page in $(seq 1 18); do
echo "Hämtar sida $page..." >&2
curl -sL "https://www.wakeupnfuck.com/scene?page=${page}" |
perl -0777 -ne '
# Läs hela sidan som en sträng (-0777) och matcha varje informationsblock
while (m{
<div\ class="informations">\s*
.*?<h3>\s*([^<]+)\s*</h3>\s*
.*?<p\ class="sub">\s*([^<]+)\s*</p>\s*
.*?<p\ class="timer">\s*([^<]+)\s*</p>
}gxis) {
my ($code, $name, $dur) = ($1, $2, $3);
for ($code, $name, $dur) { s/^\s+|\s+$//g } # trim
# Skriv i önskat format: Namn;Kod;Tid
print "$name;$code;$dur\n";
}
' >> "$OUTFILE"
done
echo "Klart. Skrev $(wc -l < "$OUTFILE") rader till $OUTFILE" >&2

1
thumbnail.sh Normal file
View File

@ -0,0 +1 @@
ffmpeg -i WoodManCastingX_17_08_25_Ninel_Mojado_XXX_1080p__hdporn__ghost__dailyvids__0dayporn__internallink__V.mp4 -ss 00:00:07.000 -frames:v 1 out.png

346
videocmp_select.sh Executable file
View File

@ -0,0 +1,346 @@
#!/usr/bin/env bash
# videocmp_select.sh
#
# Modes:
# 1) Pair mode: compare A and B
# ./videocmp_select.sh A.mp4 B.mp4 [options]
#
# 2) Directory scan mode: group by prefix before first "WoodmanCastingX" (case-insensitive)
# ./videocmp_select.sh --scan-dir [DIR] [--recursive] [options]
# (DIR defaults to "." if omitted)
#
# Pipeline:
# 1) Validate files (ffprobe fields + ffmpeg deep decode)
# 2) Confirm same movie via snapshot SSIM @ --snapshot-time (default 12s)
# 3) Optional: run external compare impl (simple/advanced) for logging
# 4) Pick preferred: prefer --prefer-height (default 720), then longer duration, then larger file
# 5) Act on loser: --action print|move|delete (with --dry-run)
#
# Common options:
# --snapshot-time SEC (default: 12)
# --snapshot-scale WxH (default: 320:-1)
# --snapshot-ssim THRESH (default: 0.97)
# --impl simple|advanced (default: simple) # logs only
# --impl-simple PATH (default: ./compare_simple.sh)
# --impl-advanced PATH (default: ./compare_advanced.sh)
# --impl-optional (default) warn if impl missing
# --impl-required error if chosen impl missing
# --prefer-height N (default: 720)
# --duration-eps SEC (default: 0.0)
# --action print|move|delete (default: print)
# --trash-dir PATH (default: $HOME/.video_trash)
# --dry-run
# --verbose
#
# Directory-scan options:
# --scan-dir [DIR] Enable directory mode (DIR optional; default ".")
# --recursive, -r Recurse into subfolders
# --delimiter WORD (default: WoodmanCastingX) case-insensitive
#
# Exit codes:
# 0 success | 1 differ/broken | 2 usage | 3 missing dependency
set -u
# -------- defaults --------
SNAP_T="12"
SNAP_SCALE="320:-1"
SNAP_SSIM="0.97"
IMPL="simple"
IMPL_SIMPLE="./compare_simple.sh"
IMPL_ADV="./compare_advanced.sh"
IMPL_REQUIRED=0
PREF_HEIGHT=720
DURATION_EPS=0.0
ACTION="print"
TRASH_DIR="${HOME}/.video_trash"
DRY=0
VERBOSE=0
SCAN_DIR=""
RECURSIVE=0
DELIM="WoodmanCastingX"
# -------- helpers --------
need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1" >&2; exit 3; }; }
v() { [[ $VERBOSE -eq 1 ]] && echo "[LOG]" "$@" >&2; }
die() { echo "[ERR]" "$@" >&2; exit 1; }
need ffmpeg; need ffprobe; need awk; need grep; need stat; need sed; need tr; need find
# -------- array-based option parser --------
ARGS=("$@")
REM_ARR=()
i=0
while (( i < ${#ARGS[@]} )); do
arg="${ARGS[i]}"
case "$arg" in
--snapshot-time) SNAP_T="${ARGS[i+1]:-}"; i=$((i+2));;
--snapshot-scale) SNAP_SCALE="${ARGS[i+1]:-}"; i=$((i+2));;
--snapshot-ssim) SNAP_SSIM="${ARGS[i+1]:-}"; i=$((i+2));;
--impl) IMPL="${ARGS[i+1]:-}"; i=$((i+2));;
--impl-simple) IMPL_SIMPLE="${ARGS[i+1]:-}"; i=$((i+2));;
--impl-advanced) IMPL_ADV="${ARGS[i+1]:-}"; i=$((i+2));;
--impl-optional) IMPL_REQUIRED=0; i=$((i+1));;
--impl-required) IMPL_REQUIRED=1; i=$((i+1));;
--prefer-height) PREF_HEIGHT="${ARGS[i+1]:-}"; i=$((i+2));;
--duration-eps) DURATION_EPS="${ARGS[i+1]:-}"; i=$((i+2));;
--action) ACTION="${ARGS[i+1]:-}"; i=$((i+2));;
--trash-dir) TRASH_DIR="${ARGS[i+1]:-}"; i=$((i+2));;
--dry-run) DRY=1; i=$((i+1));;
--verbose) VERBOSE=1; i=$((i+1));;
--scan-dir)
# optional arg: use next token unless it looks like another option
next="${ARGS[i+1]:-}"
if [[ -n "$next" && "$next" != --* ]]; then
SCAN_DIR="$next"; i=$((i+2))
else
SCAN_DIR="."; i=$((i+1))
fi
;;
--recursive|-r) RECURSIVE=1; i=$((i+1));;
--delimiter) DELIM="${ARGS[i+1]:-}"; i=$((i+2));;
--help|-h)
grep -E '^# ' "$0" | sed 's/^# //'
exit 0 ;;
*)
# leave positional for pair mode
REM_ARR+=("$arg"); i=$((i+1));;
esac
done
v "[DBG] Options: SCAN_DIR='${SCAN_DIR:-}' RECURSIVE=$RECURSIVE DELIM='$DELIM' PREF_HEIGHT=$PREF_HEIGHT SNAP_T=$SNAP_T"
# -------- core functions --------
probe_meta() { # file -> "w h dur codec size"
local f="$1" size w h dur vcodec
size=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f")
w=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=nw=1:nk=1 "$f" || echo 0)
h=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=nw=1:nk=1 "$f" || echo 0)
dur=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$f" || echo 0)
vcodec=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=nw=1:nk=1 "$f" || echo "?")
echo "$w $h $dur $vcodec $size"
}
check_ok() { # file -> 0 ok / 1 bad
local f="$1" w h dur
w=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=nw=1:nk=1 "$f" 2>/dev/null || echo "")
h=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=nw=1:nk=1 "$f" 2>/dev/null || echo "")
dur=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$f" 2>/dev/null || echo "")
if [[ -z "$w" || -z "$h" || "$w" = "N/A" || "$h" = "N/A" || "$w" -eq 0 || "$h" -eq 0 ]]; then
echo "BROKEN(ffprobe: no valid video stream): $f" >&2; return 1; fi
if [[ -n "$dur" && "$dur" != "N/A" ]]; then
awk -v d="$dur" 'BEGIN{exit !(d+0>0)}' || { echo "BROKEN(ffprobe: nonpositive duration): $f" >&2; return 1; }
fi
if ! ffmpeg -v error -xerror -i "$f" -f null - -nostats >/dev/null 2>&1; then
echo "BROKEN(ffmpeg decode): $f" >&2; return 1; fi
return 0
}
snapshot_compare_ssim() { # f1 f2 time scale -> "ssim" (empty if fail)
local f1="$1" f2="$2" t="$3" sc="$4"
local tmpd s1 s2 log ssim
tmpd="$(mktemp -d)"; s1="$tmpd/1.png"; s2="$tmpd/2.png"; log="$tmpd/cmp.log"
ffmpeg -hide_banner -v error -y -i "$f1" -ss "$t" -frames:v 1 -vf "scale=$sc,format=yuv420p" "$s1" || true
ffmpeg -hide_banner -v error -y -i "$f2" -ss "$t" -frames:v 1 -vf "scale=$sc,format=yuv420p" "$s2" || true
if [[ ! -s "$s1" || ! -s "$s2" ]]; then rm -rf "$tmpd"; echo ""; return 0; fi
ffmpeg -hide_banner -v info -i "$s1" -i "$s2" -lavfi "ssim" -f null - > /dev/null 2> "$log" || true
ssim="$(grep -Eo 'All:[0-9]+(\.[0-9]+)?' "$log" | head -n1 | cut -d: -f2)"
[[ -z "$ssim" ]] && ssim="$(grep -Eo 'SSIM [^ ]* All:[0-9]+(\.[0-9]+)?' "$log" | awk -F'All:' '{print $2}' | head -n1)"
[[ -z "$ssim" ]] && ssim="$(grep -Eo 'SSIM Y:[0-9]+(\.[0-9]+)?' "$log" | head -n1 | cut -d: -f2)"
rm -rf "$tmpd"; echo "$ssim"
}
run_impl() { # impl, A, B
local which="$1" f1="$2" f2="$3" path=""
[[ "$which" == "advanced" ]] && path="$IMPL_ADV" || path="$IMPL_SIMPLE"
if [[ ! -x "$path" ]]; then
if [[ $IMPL_REQUIRED -eq 1 ]]; then
die "Requested --impl=$which but script not found/executable at $path"
else
echo "[impl:$which] not found ($path) — skipping" >&2; return 0
fi
fi
echo "[impl:$which] $path \"$f1\" \"$f2\"" >&2
"$path" "$f1" "$f2" 2>&1 | sed -n '1,12p' >&2
}
score_file() { # file -> "tier720 dur size"
local f="$1"
read -r W H DUR VC SIZE <<<"$(probe_meta "$f")"
local tier=1; [[ "$H" -eq "$PREF_HEIGHT" ]] && tier=0
echo "$tier $DUR $SIZE"
}
pick_winner() { # A B -> "KEEP|DROP|why"
local a="$1" b="$2"
read -r aTier aDur aSize <<<"$(score_file "$a")"
read -r bTier bDur bSize <<<"$(score_file "$b")"
v "Quality scores: A[tier=$aTier dur=$aDur size=$aSize] B[tier=$bTier dur=$bDur size=$bSize]"
if (( aTier < bTier )); then echo "$a|$b|prefer ${PREF_HEIGHT}p (A)"; return 0; fi
if (( bTier < aTier )); then echo "$b|$a|prefer ${PREF_HEIGHT}p (B)"; return 0; fi
awk -v A="$aDur" -v B="$bDur" -v eps="$DURATION_EPS" 'BEGIN{
if ((A-B) > eps) print "A"; else if ((B-A) > eps) print "B"; else print "TIE";
}' | {
read who
if [[ "$who" == "A" ]]; then echo "$a|$b|longer duration (A)"; return 0; fi
if [[ "$who" == "B" ]]; then echo "$b|$a|longer duration (B)"; return 0; fi
if (( aSize > bSize )); then echo "$a|$b|larger file size (A)"; else
if (( bSize > aSize )); then echo "$b|$a|larger file size (B)"; else
echo "$a|$b|tie-break (keep A)"; fi; fi
}
}
act_on_loser() { # loser keep
local loser="$1" keeper="$2"
case "$ACTION" in
print)
echo "[ACTION] Keep: $keeper"
echo "[ACTION] Drop: $loser"
;;
move)
mkdir -p -- "$TRASH_DIR"
if [[ $DRY -eq 1 ]]; then
echo "[ACTION] dry-run: mv \"$loser\" \"$TRASH_DIR/\""
else
mv -- "$loser" "$TRASH_DIR/" && echo "[ACTION] moved to trash: $loser -> $TRASH_DIR/"
fi
echo "[ACTION] kept: $keeper"
;;
delete)
if [[ $DRY -eq 1 ]]; then
echo "[ACTION] dry-run: rm \"$loser\""
else
rm -- "$loser" && echo "[ACTION] deleted: $loser"
fi
echo "[ACTION] kept: $keeper"
;;
*) echo "[WARN] Unknown --action='$ACTION' → printing only."; echo "[ACTION] Keep: $keeper ; Drop: $loser" ;;
esac
}
same_movie_or_skip() { # A B -> 0 if same (SSIM>=thr), else 1
local a="$1" b="$2" ssim
echo "== Snapshot compare @${SNAP_T}s: ==" >&2
ssim="$(snapshot_compare_ssim "$a" "$b" "$SNAP_T" "$SNAP_SCALE")"
if [[ -z "$ssim" ]]; then
echo "[WARN] Could not compute SSIM for: $a vs $b" >&2; return 1; fi
printf "[INFO] SSIM(All) %s vs %s → %s\n" "$(basename -- "$a")" "$(basename -- "$b")" "$ssim" >&2
awk -v s="$ssim" -v thr="$SNAP_SSIM" 'BEGIN{exit !(s+0 >= thr+0)}'
}
# ----- pair comparison driver -----
compare_pair() { # A B
local A="$1" B="$2"
echo "== Step 1: Validating files =="; okA=0; okB=0
check_ok "$A" && okA=1; check_ok "$B" && okB=1
if (( okA==0 || okB==0 )); then echo "[FAIL] One or both files broken. A_ok=$okA B_ok=$okB" >&2; exit 1; fi
echo "[OK] Both files decoded cleanly."
echo; echo "== Step 2: Snapshot compare ==";
if ! same_movie_or_skip "$A" "$B"; then
echo "[FAIL] Files are not the same movie (SSIM < ${SNAP_SSIM})." >&2; exit 1
fi
echo "[OK] Same movie."
echo; echo "== Step 3: External compare ($IMPL) =="; run_impl "$IMPL" "$A" "$B"
echo; echo "== Step 4: Quality selection (prefer ${PREF_HEIGHT}p) ==";
read -r keep drop why <<<"$(pick_winner "$A" "$B" | tr '|' ' ')"
echo "[DECISION] Keep: $keep"; echo "[DECISION] Drop: $drop"; echo "[REASON] $why"
echo; echo "== Step 5: Action =="; act_on_loser "$drop" "$keep"
echo; echo "== Summary =="; echo "Kept: $keep"; echo "Dropped: $drop"
[[ "$ACTION" == "move" ]] && echo "(Moved loser to: $TRASH_DIR)"
[[ "$ACTION" == "delete" ]] && echo "(Loser was deleted)"
[[ $DRY -eq 1 ]] && echo "(Dry-run only; no changes made)"
}
# ----- directory scan helpers/drivers -----
scan_and_group() {
if [[ $RECURSIVE -eq 1 ]]; then
mapfile -t FILES < <(find "$SCAN_DIR" -type f \( -iname '*.mp4' \))
else
mapfile -t FILES < <(ls "$SCAN_DIR"/*.mp4 "$SCAN_DIR"/*.MP4 2>/dev/null || true)
fi
}
extract_key() { # filename -> group key before first DELIM (CI), original case
local name="$1" low delimlow key
low="$(echo -n "$name" | tr '[:upper:]' '[:lower:]')"
delimlow="$(echo -n "$DELIM" | tr '[:upper:]' '[:lower:]')"
[[ "$low" == *"$delimlow"* ]] || { echo ""; return 0; }
key="${low%%"$delimlow"*}"
local cutlen=${#key}
echo "${name:0:$cutlen}"
}
process_group() { # files...
local files=("$@") n=${#files[@]}
if (( n < 2 )); then v "Group <2 files → skip"; return 0; fi
echo; echo "=== Group (${n} files): prefix before '${DELIM}' ==="
echo "Files:"; for f in "${files[@]}"; do echo " - $f"; done
local best="${files[0]}"
if ! check_ok "$best"; then echo "[WARN] Skipping broken file: $best"; return 0; fi
for ((i=1;i<n;i++)); do
local cand="${files[i]}"
if ! check_ok "$cand"; then echo "[WARN] Skipping broken file: $cand"; continue; fi
echo; echo "--- Compare: $(basename -- "$best") vs $(basename -- "$cand") ---"
if ! same_movie_or_skip "$best" "$cand"; then
echo "[SKIP] Snapshot says NOT same movie; leaving both in place."; continue; fi
run_impl "$IMPL" "$best" "$cand"
read -r keep drop why <<<"$(pick_winner "$best" "$cand" | tr '|' ' ')"
echo "[DECISION] Keep: $keep"; echo "[DECISION] Drop: $drop"; echo "[REASON] $why"
act_on_loser "$drop" "$keep"
best="$keep"
done
echo; echo "=== Group result: kept $(basename -- "$best") ==="
}
dir_mode() {
[[ -n "${SCAN_DIR:-}" ]] || SCAN_DIR="."
[[ -d "$SCAN_DIR" ]] || die "Not a directory: $SCAN_DIR"
echo ">> Directory scan mode on: $SCAN_DIR (recursive=$RECURSIVE, delimiter='$DELIM')"
scan_and_group
if [[ ${#FILES[@]} -eq 0 ]]; then echo "No MP4 files found."; exit 0; fi
declare -A groups
for f in "${FILES[@]}"; do
base="$(basename -- "$f")"
key="$(extract_key "$base")"
[[ -z "$key" ]] && { v "No delimiter in: $base → skip grouping"; continue; }
groups["$key"]+=$'\n'"$f"
done
if [[ ${#groups[@]} -eq 0 ]]; then echo "No files with delimiter '$DELIM' found."; exit 0; fi
for k in "${!groups[@]}"; do
IFS=$'\n' read -r -d '' -a grpfiles < <(printf "%s" "${groups[$k]}" | sed '/^$/d' && printf '\0')
process_group "${grpfiles[@]}"
done
echo; echo ">> Directory scan complete."
}
# -------- dispatch --------
if [[ -n "${SCAN_DIR:-}" ]]; then
dir_mode
exit 0
fi
# Pair mode
if [[ ${#REM_ARR[@]} -lt 2 ]]; then
echo "Usage (pair): $0 A.mp4 B.mp4 [options]" >&2
echo " or (scan): $0 --scan-dir [DIR] [--recursive] [options]" >&2
exit 2
fi
A="${REM_ARR[0]}"; B="${REM_ARR[1]}"
[[ -f "$A" ]] || die "File not found: $A"
[[ -f "$B" ]] || die "File not found: $B"
compare_pair "$A" "$B"

155
wunf_upd_prefix.sh Executable file
View File

@ -0,0 +1,155 @@
#!/usr/bin/env bash
set -euo pipefail
CSV_DEFAULT="wunf_scenes.csv"
usage() {
echo "Användning:"
echo " $0 <filnamn> [csv-fil]"
echo " $0 --all <katalog> [csv-fil]"
echo " $0 --list <textfil> [csv-fil] # en väg per rad"
echo " ls -1 --quoting-style=literal \[WUNF\]_*.mp4 > filenames.txt && ./script/wunf_upd_prefix.sh --list filenames.txt wunf_scenes.csv && rm filenames.txt"
exit 1
}
rename_file() {
local FILE="$1"
local CSV="$2"
local base dir rest first second remainder name code code_us newbase newpath
if [[ ! -f "$FILE" ]]; then
echo "Fel: Filen finns inte: $FILE" >&2
return 1
fi
base="$(basename -- "$FILE")"
dir="$(dirname -- "$FILE")"
# Kontrollera prefix
if [[ "$base" != "[WUNF]"* ]]; then
echo "Hoppar över (ej [WUNF]-prefix): $base" >&2
return 0
fi
# Plocka ut allt efter prefixet [WUNF]_
rest="${base#"[WUNF]"}"
rest="${rest#_}"
IFS='_' read -r first second remainder <<< "$rest"
if [[ -z "${first:-}" || -z "${second:-}" ]]; then
echo "Fel: Kunde inte extrahera två namn-delar i: $base" >&2
return 1
fi
# Namn = två första token, med '_' -> ' '
name="${first//_/ } ${second//_/ }"
name="$(printf '%s' "$name" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
# slå upp i CSV (format Namn;Kod;Tid)
code="$(
awk -F';' -v target="$name" '
BEGIN{IGNORECASE=1}
{
n=$1; gsub(/^[ \t]+|[ \t]+$/, "", n);
if (n==target) {
c=$2; gsub(/^[ \t]+|[ \t]+$/, "", c);
print c; exit
}
}' "$CSV"
)"
if [[ -z "$code" ]]; then
echo "Ingen kod för \"$name\" hittades i $CSV (fil: $base)" >&2
return 1
fi
code_us="${code// /_}"
newbase="$(printf '%s' "$base" | sed -E "s/^\[WUNF\]/[$code_us]/")"
newpath="$dir/$newbase"
if [[ "$base" == "$newbase" ]]; then
echo "Hoppar över (redan rätt namn): $base" >&2
return 0
fi
if [[ -e "$newpath" ]]; then
echo "Fel: Målfilen finns redan: $newpath" >&2
return 1
fi
mv -- "$dir/$base" "$newpath"
echo "Bytt namn:"
echo " Från: $base"
echo " Till: $newbase"
}
### Huvudprogram
if [[ $# -lt 1 ]]; then
usage
fi
mode="$1"
case "$mode" in
--all)
if [[ $# -lt 2 ]]; then usage; fi
DIR="$2"
CSV="${3:-$CSV_DEFAULT}"
if [[ ! -d "$DIR" ]]; then
echo "Fel: Katalog finns inte: $DIR" >&2
exit 1
fi
if [[ ! -f "$CSV" ]]; then
echo "Fel: CSV-filen finns inte: $CSV" >&2
exit 1
fi
shopt -s nullglob
for f in "$DIR"/\[WUNF\]*.mp4; do
rename_file "$f" "$CSV" || true
done
;;
--list)
if [[ $# -lt 2 ]]; then usage; fi
LIST="$2"
CSV="${3:-$CSV_DEFAULT}"
if [[ ! -f "$LIST" ]]; then
echo "Fel: Textfil finns inte: $LIST" >&2
exit 1
fi
if [[ ! -f "$CSV" ]]; then
echo "Fel: CSV-filen finns inte: $CSV" >&2
exit 1
fi
# Läs en rad i taget; ignorera tomma rader och kommentarer
# Hanterar vägar med mellanslag korrekt
while IFS= read -r line || [[ -n "$line" ]]; do
# Trim CR (om filen är CRLF)
line="${line%$'\r'}"
# Trim whitespace runt
trimmed="$(printf '%s' "$line" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
# Hoppa över tomma rader och kommentarer
[[ -z "$trimmed" || "$trimmed" =~ ^# ]] && continue
rename_file "$trimmed" "$CSV" || true
done < "$LIST"
;;
*)
FILE="$mode"
CSV="${2:-$CSV_DEFAULT}"
if [[ ! -f "$CSV" ]]; then
echo "Fel: CSV-filen finns inte: $CSV" >&2
exit 1
fi
rename_file "$FILE" "$CSV"
;;
esac