#!/usr/bin/env python3 """Transfer dashcam MP4 files from SD card to USB-SSD with date/camera structure.""" import fcntl import logging import os import re import shutil import sys SD_CARD = "/mnt/sdcard" SOURCE_DIR = os.path.join(SD_CARD, "Novatek", "Movie") SSD = "/mnt/ultratouch" TARGET_BASE = os.path.join(SSD, "dashcam") LOG_FILE = "/var/log/dashcam-transfer.log" LOCK_FILE = "/var/run/dashcam-transfer.lock" CAMERA_MAP = {"F": "front", "B": "back"} FILE_PATTERN = re.compile(r"^(\d{8})\d{6}_\d+([FB])\.MP4$") def setup_logging(): logging.basicConfig( filename=LOG_FILE, level=logging.INFO, format="[%(asctime)s] %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) def acquire_lock(): lock_fd = open(LOCK_FILE, "w") try: fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: logging.warning("Lockfile actief — script al bezig") sys.exit(1) return lock_fd def check_mount(path, name): if not os.path.ismount(path): logging.error("%s niet gemount: %s", name, path) sys.exit(1) def get_mp4_files(src_dir): if not os.path.isdir(src_dir): return [] result = [] for fname in os.listdir(src_dir): match = FILE_PATTERN.match(fname) if match: result.append((fname, match.group(1), match.group(2).upper())) return result def ensure_free_space(needed_bytes, target_base): stat = os.statvfs(target_base) free = stat.f_frsize * stat.f_bavail if free >= needed_bytes: return logging.warning( "Onvoldoende ruimte: %d bytes nodig, %d bytes vrij", needed_bytes, free ) date_dirs = sorted( d for d in os.listdir(target_base) if re.match(r"^\d{8}$", d) and os.path.isdir(os.path.join(target_base, d)) ) freed = 0 removed = [] for date_name in date_dirs: if free + freed >= needed_bytes: break dir_path = os.path.join(target_base, date_name) dir_size = sum( os.path.getsize(os.path.join(dirpath, f)) for dirpath, _, filenames in os.walk(dir_path) for f in filenames ) shutil.rmtree(dir_path) freed += dir_size removed.append(date_name) logging.info("Ruimte gemaakt: %s verwijderd (%d bytes)", date_name, dir_size) if free + freed < needed_bytes: logging.error( "Nog steeds onvoldoende ruimte na verwijderen %d mappen: " "%d bytes nodig, %d bytes vrij", len(removed), needed_bytes, free + freed, ) sys.exit(1) logging.info("Ruimte vrijgemaakt: %d bytes door %d mappen", freed, len(removed)) def main(): setup_logging() lock_fd = acquire_lock() check_mount(SD_CARD, "SD-kaart") check_mount(SSD, "Ultratouch SSD") files = get_mp4_files(SOURCE_DIR) if not files: logging.info("Geen MP4 bestanden gevonden in %s", SOURCE_DIR) return total_size = sum( os.path.getsize(os.path.join(SOURCE_DIR, fname)) for fname, _, _ in files ) logging.info( "Start transfer: %d bestanden, %d bytes", len(files), total_size ) ensure_free_space(total_size, TARGET_BASE) ok = 0 errors = 0 for fname, date_str, cam in files: target_dir = os.path.join(TARGET_BASE, date_str, CAMERA_MAP[cam]) os.makedirs(target_dir, exist_ok=True) src = os.path.join(SOURCE_DIR, fname) dst = os.path.join(target_dir, fname) try: shutil.move(src, dst) logging.info( "Verplaatst: %s → %s/%s/", fname, date_str, CAMERA_MAP[cam] ) ok += 1 except OSError as e: logging.error("Fout bij verplaatsen %s: %s", fname, e) errors += 1 logging.info( "Transfer voltooid: %d ok, %d fouten, %d bytes", ok, errors, total_size ) if __name__ == "__main__": main()