first commit
This commit is contained in:
BIN
._wunf_upd_prefix.sh
Executable file
BIN
._wunf_upd_prefix.sh
Executable file
Binary file not shown.
47
checkmp4.sh
Executable file
47
checkmp4.sh
Executable 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
42
clean_nfo.sh
Executable 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
127
compare_advanced.sh
Executable 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
87
compare_simple.sh
Executable 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
122
compare_snapshot.sh
Executable 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
183
count_words.sh
Executable 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
182
count_words.sh.save
Executable 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
90
detect_wunf.sh
Executable 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
106
mvi.sh
Executable 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
266
rename_and_filter.sh
Executable 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
40
rename_space.sh
Executable 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
94
run_wunf.sh
Executable 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
34
sanitycheck.sh
Executable 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
30
scan_and_prefix_pink.md
Normal 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 270–340 or lower sat-min for paler pink).
|
||||||
152
scan_and_prefix_pink.sh
Executable file
152
scan_and_prefix_pink.sh
Executable 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
28
scrapWUNFIdx.sh
Executable 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
1
thumbnail.sh
Normal 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
346
videocmp_select.sh
Executable 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
155
wunf_upd_prefix.sh
Executable 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
|
||||||
Reference in New Issue
Block a user