move move old scripts

This commit is contained in:
lucas committed 2026-10-16 14:00:42 +00:00
1 parent 93b262977b
commit 208fbd96b4
27 files changed
+346

No files matched your search

-170
View File
@@ -1,170 +0,0 @@
#!/usr/bin/env python3
import re
import sys
from pathlib import Path
from difflib import SequenceMatcher
def normalize(name):
name = Path(name).stem.lower()
# remove brackets
name = re.sub(r"\(.*?\)|\[.*?\]", "", name)
# replace punctuation with spaces
name = re.sub(r"[^a-z0-9]", " ", name)
# collapse whitespace
name = " ".join(name.split())
return name
def similarity(a, b):
words_a = set(normalize(a).split())
words_b = set(normalize(b).split())
if not words_a or not words_b:
return 0.0
# Jaccard similarity
intersection = words_a & words_b
union = words_a | words_b
return len(intersection) / len(union)
def load_playlist(path):
entries = []
with open(path, encoding="utf-8", errors="ignore") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
entries.append(Path(line).name)
return entries
def compare_playlists(orig_file, new_file, threshold=0.85):
orig_files = load_playlist(orig_file)
new_files = load_playlist(new_file)
print(f"\n=== {orig_file.parent} ===")
print(f"Comparing:")
print(f" {orig_file.name}")
print(f" {new_file.name}")
missing = []
for orig in orig_files:
best_match = None
best_score = 0
for candidate in new_files:
score = similarity(orig, candidate)
if score > best_score:
best_score = score
best_match = candidate
if best_score >= threshold:
pass
# print(
# f"MATCH {best_score:.1%} "
# f"{orig} -> {best_match}"
# )
else:
missing.append((orig, best_score, best_match))
if missing:
print("\nMISSING:")
for orig, score, best_match in sorted(
missing,
key=lambda x: x[1],
reverse=True,
):
print(
f" {orig}"
f" (best={score:.1%}"
f"{', candidate=' + best_match if best_match else ''})"
)
return len(missing)
def compare_directory(root, threshold=0.85):
total_missing = 0
playlist_pairs = 0
for orig_file in root.rglob("*_orig.m3u"):
new_file = orig_file.with_name(
orig_file.name.replace("_orig.m3u", ".m3u")
)
if not new_file.exists():
print(
f"\nWARNING: Missing playlist for "
f"{orig_file}"
)
continue
playlist_pairs += 1
total_missing += compare_playlists(
orig_file,
new_file,
threshold,
)
print("\n" + "=" * 80)
print(f"Playlists compared : {playlist_pairs}")
print(f"Total missing : {total_missing}")
def main():
args = sys.argv[1:]
threshold = 0.85
if len(args) == 3 and args[0] == "--threshold":
threshold = float(args[1])
args = [args[2]]
elif len(args) == 4 and args[0] == "--threshold":
threshold = float(args[1])
args = [args[2], args[3]]
if len(args) == 1:
path = Path(args[0])
if not path.is_dir():
print("Single argument must be a directory")
sys.exit(1)
compare_directory(path, threshold)
elif len(args) == 2:
compare_playlists(
Path(args[0]),
Path(args[1]),
threshold,
)
else:
print(
f"Usage:\n"
f" {Path(sys.argv[0]).name} "
f"<playlist_orig.m3u> <playlist.m3u>\n\n"
f" {Path(sys.argv[0]).name} "
f"<directory>\n\n"
f"Optional:\n"
f" --threshold 0.90"
)
sys.exit(1)
if __name__ == "__main__":
main()
-211
View File
@@ -1,211 +0,0 @@
#!/bin/bash
set -euo pipefail
shopt -s nullglob nocaseglob
LOGFILE="/home/lucas/logs/convertheic.log"
mkdir -p "$(dirname "$LOGFILE")"
exec >>"$LOGFILE" 2>&1
echo "Script started at $(date)"
convert_one() {
local src="$1"
if [[ ! -f "$src" ]]; then
echo "ERROR: file not found: $src"
return 1
fi
local base="${src%.*}"
local dst="${base}.jpg"
local tmp="${base}.tmp.jpg"
echo "Converting: $src"
# Convert HEIC -> JPEG
if command -v magick >/dev/null 2>&1; then
if ! magick "$src" \
-auto-orient \
-quality 100 \
-sampling-factor 4:4:4 \
"jpg:$tmp"; then
echo "ERROR: local conversion failed: $src"
rm -f "$tmp"
return 1
fi
else
if ! docker run --rm \
-u "$(id -u):$(id -g)" \
-v "$PWD:/work" \
-w /work \
--entrypoint magick \
dpokidov/imagemagick \
"$src" \
-auto-orient \
-quality 100 \
-sampling-factor 4:4:4 \
"jpg:$tmp"; then
echo "ERROR: docker conversion failed: $src"
rm -f "$tmp"
return 1
fi
fi
if [[ ! -s "$tmp" ]]; then
echo "ERROR: output JPEG is empty: $tmp"
rm -f "$tmp"
return 1
fi
echo "Copying metadata..."
if ! exiftool \
-overwrite_original \
-TagsFromFile "$src" \
-all:all \
-unsafe \
"$tmp" >/dev/null; then
echo "ERROR: metadata copy failed: $src"
rm -f "$tmp"
return 1
fi
touch -r "$src" "$tmp"
mv -f "$tmp" "$dst"
if [[ ! -s "$dst" ]]; then
echo "ERROR: final JPEG missing: $dst"
return 1
fi
rm -f "$src"
echo "Done: $dst"
echo "Removed: $src"
}
convert_mov() {
local src="$1"
if [[ ! -f "$src" ]]; then
echo "ERROR: file not found: $src"
return 1
fi
local base="${src%.*}"
local dst="${base}.mp4"
local tmp="${base}.tmp.mp4"
echo "Converting: $src"
# MOV -> MP4, keep video/audio, drop unsupported data streams
if ! ffmpeg \
-hide_banner \
-loglevel error \
-i "$src" \
-map 0:0 \
-map 0:1 \
-map_metadata 0 \
-c copy \
-movflags use_metadata_tags+faststart \
"$tmp"; then
echo "ERROR: conversion failed: $src"
rm -f "$tmp"
return 1
fi
if [[ ! -s "$tmp" ]]; then
echo "ERROR: output MP4 is empty: $tmp"
rm -f "$tmp"
return 1
fi
echo "Copying metadata..."
if ! exiftool \
-overwrite_original \
-TagsFromFile "$src" \
-all:all \
-unsafe \
"$tmp" >/dev/null; then
echo "ERROR: metadata copy failed: $src"
rm -f "$tmp"
return 1
fi
touch -r "$src" "$tmp"
mv -f "$tmp" "$dst"
if [[ ! -s "$dst" ]]; then
echo "ERROR: final MP4 missing: $dst"
return 1
fi
rm -f "$src"
echo "Done: $dst"
echo "Removed: $src"
}
process_file() {
case "${1,,}" in
*.heic)
convert_one "$1"
;;
*.mov)
convert_mov "$1"
;;
*)
echo "Skipping unsupported file: $1"
;;
esac
}
HOSTNAME_EXPECTED="mediahost"
SOURCE_DIR="/data/nextcloud/syncuser/pictures/Alina Share/Dropfolder"
TARGET_DIR="/data/nextcloud/syncuser/pictures/Camera"
# If running on serverhost, perform initial sync
if [[ "$(hostname)" == "$HOSTNAME_EXPECTED" ]]; then
echo Sync Nextcloud
syncNC -c
cd "$SOURCE_DIR" || exit 1
fi
if [[ $# -gt 0 ]]; then
echo Process single file $1
process_file "$1"
else
echo Process dir .
shopt -s nullglob
files=( *.heic *.mov )
if [[ ${#files[@]} -eq 0 ]]; then
echo "No HEIC or MOV files found."
else
for f in "${files[@]}"; do
process_file "$f"
done
fi
fi
# Run sync again if on serverhost
if [[ "$(hostname)" == "$HOSTNAME_EXPECTED" ]]; then
shopt -s nullglob
for ext in jpg mp4; do
files=("$SOURCE_DIR"/*."$ext")
if ((${#files[@]})); then
mv -n "${files[@]}" "$TARGET_DIR"/
fi
done
shopt -u nullglob
echo Sync Nextcloud
syncNC -c
fi
-392
View File
@@ -1,392 +0,0 @@
#!/usr/bin/env python3
import argparse
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
def check_command(command):
if shutil.which(command) is None:
print(f"ERROR: '{command}' is not installed.", file=sys.stderr)
sys.exit(1)
def scan_dvd(dvd_name: Path):
print("Scanning DVD")
print(dvd_name)
result = subprocess.run(
["lsdvd", dvd_name],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=True,
)
output = result.stdout
pattern = re.compile(
r"Title:\s*(\d+),\s*Length:\s*(\d+):(\d+):(\d+(?:\.\d+)?)"
)
titles = []
for match in pattern.finditer(output):
title = int(match.group(1))
hours = int(match.group(2))
minutes = int(match.group(3))
seconds = float(match.group(4))
duration = (
hours * 3600
+ minutes * 60
+ seconds
)
titles.append({
"title": title,
"duration": duration,
})
if not titles:
raise RuntimeError("lsdvd found no DVD titles")
return titles
def get_audio_tracks(dvd, title):
"""Return HandBrake audio track information for a DVD title."""
result = subprocess.run(
[
"HandBrakeCLI",
"-i", dvd,
"-t", str(title),
"--scan",
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=True,
)
output = result.stdout
# Examples HandBrake may produce:
#
# + 1, Deutsch (AC3, 2.0 ch, 192 kbps)
# + 2, English (AC3, 2.0 ch, 224 kbps)
#
# Also handles German/Englisch/deu/eng where present.
tracks = []
pattern = re.compile(
r"\+\s+(\d+),\s+(.+?)\s+\((?:AC3|AAC|MP3|E-AC-3|DTS)"
)
for match in pattern.finditer(output):
track_number = int(match.group(1))
description = match.group(2).strip()
tracks.append({
"number": track_number,
"description": description,
})
if not tracks:
tracks.append({
"number": 0,
"description": "",
})
return tracks
def choose_audio_tracks(tracks):
"""
Select German first, English second.
Returns a list of HandBrake track numbers.
"""
german = None
english = None
unkown = None
for track in tracks:
text = track["description"].lower()
if not text:
track["description"] = "Unknown"
unkown = track["number"]
# German
if any(x in text for x in (
"german",
"deutsch",
"deu",
)):
german = track["number"]
# English
if any(x in text for x in (
"english",
"englisch",
"eng",
)):
english = track["number"]
selected = []
# German FIRST -> becomes default audio
if german is not None:
selected.append(german)
# English SECOND
if english is not None and english != german:
selected.append(english)
if english is None and german is None:
selected.append(unkown)
return selected
def find_episode_count(dvd_name: Path, max_dvd_length: int=70):
"""Find the number of episodes using lsdvd.
Assumes:
- The longest title is a combined track containing all episodes.
- Individual episode titles immediately follow the longest title.
- Episode titles have approximately similar durations.
"""
titles = scan_dvd(dvd_name)
sorted_items = sorted(titles, key=lambda x: x["duration"], reverse=True)
combined_title = 0
if sorted_items[0]["duration"] > sorted_items[1]["duration"] * 2:
# Longest title = combined episode track
combined = max(titles, key=lambda x: x["duration"])
combined_title = combined["title"]
# Episode titles must immediately follow the combined title.
title_map = {
t["title"]: t["duration"]
for t in titles
}
first_episode = combined_title + 1
episode_titles = []
for title in range(first_episode, max(title_map) + 1):
if title not in title_map:
break
duration = title_map[title]
# Typical episode is ~20-35 minutes.
# Adjust these limits if your DVDs contain longer/shorter episodes.
if 15 * 60 <= duration <= max_dvd_length * 60:
episode_titles.append(title)
else:
continue
if not episode_titles:
raise RuntimeError(
f"Could not find episodes after combined title "
f"{first_episode}"
)
first_episode = episode_titles[0]
if combined_title > 0:
print(f"Combined title: {titles[combined_title]["title"]}")
print(f"Combined duration: {titles[combined_title]["duration"]:.1f}s")
print(f"Found {len(episode_titles)} episodes:")
print(f" Titles: {episode_titles}")
return first_episode, len(episode_titles), episode_titles
def process_dvd(dvd_source: Path,dvd_title: str,num_of_episodes: int, first_episode: int, args):
output_dir = Path("~/Downloads").expanduser() / dvd_source.parent.name
output_dir.mkdir(parents=True, exist_ok=True)
titles = scan_dvd(dvd_source)
last_episode = first_episode + num_of_episodes
available_titles = {t["title"] for t in titles}
missing = [
title
for title in range(first_episode, last_episode )
if title not in available_titles
]
if missing:
print(
"ERROR: Expected episode titles were not found:",
", ".join(map(str, missing)),
file=sys.stderr,
)
sys.exit(1)
print("Episodes to extract:")
for number in range(1, num_of_episodes + 1):
title = first_episode + number - 1
print(f" Episode {number}: DVD title {title}")
print()
os.makedirs(output_dir, exist_ok=True)
print("Output directory:")
print(f" {output_dir}")
print()
for episode in range(first_episode, first_episode + num_of_episodes ):
title = episode
title_from_zero = episode - first_episode +1
if title_from_zero in args.skip_episodes:
continue
output_file = output_dir / f"{dvd_title} - {title_from_zero:02d}.mp4"
if output_file.exists():
size_mb = output_file.stat().st_size / (1024 * 1024)
print(f"{output_file} ({size_mb:.1f} MB)")
audio_tracks = choose_audio_tracks(get_audio_tracks(dvd_source, title))
if not audio_tracks:
raise RuntimeError(
f"No German or English audio track found for title {title}"
)
print("=" * 50)
print(f"Episode {episode-first_episode +1} / {num_of_episodes}")
print(f"DVD title: {title}")
print(f"Output: {output_file}")
print("=" * 50)
command = [
"HandBrakeCLI",
"-i", dvd_source,
"-t", str(title),
"-o", output_file,
"-e", "x264",
"-q", "18",
"-a", ",".join(map(str, audio_tracks)),
"-E", "copy:ac3",
]
with open("handbrake.log", "w") as logfile:
try:
subprocess.run(
command,
stdout=logfile,
stderr=subprocess.STDOUT,
text=True,
check=True,
)
except subprocess.CalledProcessError as exc:
print(
f"ERROR: HandBrakeCLI failed for episode {episode}.",
file=sys.stderr,
)
sys.exit(exc.returncode)
print()
print("=" * 50)
print("Finished!")
print("=" * 50)
def main():
parser = argparse.ArgumentParser(
description="Extract episodes from a DVD using lsdvd and HandBrakeCLI."
)
parser.add_argument(
"dvd",
help="Path to DVD directory containing VIDEO_TS",
)
parser.add_argument(
"--episodes",
type=int,
default=0,
help="Number of episodes to extract (0 = auto-detect)",
)
parser.add_argument(
"--skip-episodes",
type=int,nargs="+",
default=[],
help="List of episodes to skip",
)
args = parser.parse_args()
dvd_name = os.path.abspath(args.dvd)
check_command("lsdvd")
check_command("HandBrakeCLI")
root = Path(dvd_name).resolve()
check_command("lsdvd")
check_command("HandBrakeCLI")
# Find all VIDEO_TS directories
video_ts_dirs = [
p for p in root.rglob("VIDEO_TS")
if p.is_dir()
]
# Find all ISO files
iso_files = [
p for p in root.rglob("*")
if p.is_file() and p.suffix.lower() == ".iso"
]
# Process VIDEO_TS directories
for video_ts in video_ts_dirs:
dvd_source = video_ts
# Parent of VIDEO_TS, e.g.
# /movies/Pippi DVD 1/VIDEO_TS
# -> Pippi DVD 1
dvd_title = video_ts.parent.name
print(f"Found DVD: {dvd_title}")
print(f" Source: {dvd_source}")
first_episode, episode_count, episode_titles = find_episode_count(dvd_source)
process_dvd(
dvd_source,
dvd_title,
episode_count,
first_episode,
args
)
# Process ISO files
for iso in iso_files:
dvd_source = str(iso)
# Filename without .iso
dvd_title = iso.stem
print(f"Found ISO: {dvd_title}")
print(f" Source: {dvd_source}")
first_episode, episode_count, episode_titles = find_episode_count(dvd_source)
process_dvd(
dvd_source,
dvd_title,
episode_count,
first_episode,
args
)
if __name__ == "__main__":
main()