import os import json import hashlib import subprocess from datetime import datetime, timezone VIDEO_EXTENSIONS = {".mp4", ".mkv", ".avi", ".mov", ".webm", ".flv", ".wmv", ".m4v"} BACKUP_KEYWORDS = {"backup", "backups", "#recycle"} MEDIA_ROOT = os.environ.get("MEDIA_ROOT", "/mnt/mediaserver") SCAN_DIRS = os.environ.get("SCAN_DIRS", "Youtube,Movies,TV").split(",") INDEX_PATH = os.environ.get("INDEX_PATH", "/home/jarian/projects/media-transcriber/index.json") TRANSCRIPTS_DIR = os.environ.get("TRANSCRIPTS_DIR", "/home/jarian/projects/media-transcriber/transcripts") def is_video(path): return os.path.splitext(path)[1].lower() in VIDEO_EXTENSIONS def is_backup(path): parts = path.replace(MEDIA_ROOT, "").split("/") return any(k.lower() in {p.lower() for p in parts} for k in BACKUP_KEYWORDS) def video_hash(filepath): stat = os.stat(filepath) raw = f"{stat.st_size}:{stat.st_mtime_ns}:{filepath}" return hashlib.sha256(raw.encode()).hexdigest()[:16] def get_duration(filepath): try: result = subprocess.run( ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", filepath], capture_output=True, text=True, timeout=10 ) return round(float(result.stdout.strip()), 2) except Exception: return None def transcript_exists(rel_path): full_path = os.path.join(MEDIA_ROOT, rel_path) parent = os.path.dirname(full_path) trans_dir = os.path.join(parent, "_transcriptions") base = os.path.splitext(os.path.basename(full_path))[0] for ext in (".srt", ".vtt", ".txt", ".json"): candidate = os.path.join(trans_dir, f"{base}_transcription{ext}") if os.path.exists(candidate): return True, candidate return False, None def scan(skip_duration=False): videos = {} now = datetime.now(timezone.utc).isoformat() for scan_dir in SCAN_DIRS: root = os.path.join(MEDIA_ROOT, scan_dir) if not os.path.isdir(root): continue for dirpath, _, filenames in os.walk(root): for fname in filenames: full = os.path.join(dirpath, fname) if not is_video(full) or is_backup(full): continue rel = os.path.relpath(full, MEDIA_ROOT) has_transcript, transcript_file = transcript_exists(rel) vid_hash = video_hash(full) stat = os.stat(full) videos[rel] = { "path": full, "hash": vid_hash, "duration_seconds": None if skip_duration else get_duration(full), "size_bytes": stat.st_size, "transcribed": has_transcript, "transcript_file": transcript_file, "last_scanned": now, } transcribed_count = sum(1 for v in videos.values() if v["transcribed"]) index = { "version": "1", "last_updated": now, "total_videos": len(videos), "transcribed": transcribed_count, "videos": videos, } tmp = INDEX_PATH + ".tmp" with open(tmp, "w") as f: json.dump(index, f, indent=2) os.replace(tmp, INDEX_PATH) return index