#! /usr/bin/python3 import os import argparse import threading import subprocess import http.server import pathlib import json import glob import io import time import re import fcntl CDROM_DRIVE_STATUS = 0x5326 CDS_NO_INFO = 0 CDS_NO_DISC = 1 CDS_TRAY_OPEN = 2 CDS_DRIVE_NOT_READ =3 CDS_DISC_OK = 4 CDS_STR = ["no info", "no disc", "tray open", "drive not read", "disc ok"] CDROM_DISC_STATUS = 0x5327 CDS_AUDIO = 100 CDS_DATA_1 = 101 CDS_DATA_2 = 102 CDROM_EJECT = 0x5309 status = { "queue": [], } class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): def do_GET(self): if self.path == "/status.json": return self.get_status() return super().do_GET() def get_status(self): self.send_response(200) self.end_headers() buf = json.dumps(status).encode("utf-8") self.wfile.write(buf) class Reader(threading.Thread): def __init__(self, path, directory=None, **kwargs): self.path = path self.directory = directory self.status = {"state": "idle"} self.complete = 0 self.drive = os.open(path, os.O_RDONLY | os.O_NONBLOCK) return super().__init__(**kwargs) def run(self): while True: self.status = { "type": "reader", "state": "idle", "device": self.path, } rv = fcntl.ioctl(self.drive, CDROM_DRIVE_STATUS) if rv == CDS_DISC_OK: rv = fcntl.ioctl(self.drive, CDROM_DISC_STATUS) try: if rv == CDS_AUDIO: self.handle_audio() elif rv in [CDS_DATA_1, CDS_DATA_2]: self.handle_data() else: print("Can't handle disc type %d" % rv) except Exception as e: print("Error in disc handler:", e) fcntl.ioctl(self.drive, CDROM_EJECT) else: time.sleep(3) def handle_audio(self): pass # XXX def handle_data(self): self.status["state"] = "Scanning for DVD title" p = subprocess.run( [ "dvdbackup", "--input=" + self.path, "--info", ], encoding="utf-8", capture_output=True, ) mediaSize = 0 title = None for l in p.stdout.split("\n"): if l.startswith("DVD-Video information"): title = l.split('"')[1] elif l.endswith("MiB"): parts = l.split() mediaSize += float(parts[-2]) * 1024 * 1024 elif l.endswith("KiB"): parts = l.split() mediaSize += float(parts[-2]) * 1024 self.status["title"] = title print("Copying %r (%d bytes)" % (title, mediaSize)) self.copy(mediaSize) open(os.path.join(title, "finished"), "w") def copy(self, mediaSize): self.status["state"] = "copying" p = subprocess.Popen( [ "dvdbackup", "--input=" + self.path, "--mirror", "--progress", ], encoding="utf-8", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=os.path.join(self.directory, "video"), ) totalBytes = titleSize = lastTitleSize = 0 progressRe = re.compile(r"^Copying.*([0-9.]+)/[0-9.]+ (MiB|KiB)") for line in p.stdout: line = line.strip() m = progressRe.search(line) if m and m[2] == "MiB": titleSize = float(m[1]) * 1024 * 1024 elif m and m[2] == "KiB": titleSize = float(m[1]) * 1024 if titleSize < lastTitleSize: totalBytes += lastTitleSize lastTitleSize = titleSize self.status["complete"] = (totalBytes + titleSize) / mediaSize class Encoder(threading.Thread): def __init__(self, directory=None, **kwargs): self.status = {"state": "idle"} self.directory = directory for d in ("audio", "video"): os.makedirs(os.path.join(directory, d), exist_ok=True) return super().__init__(**kwargs) def run(self): self.status = {"type": "encoder", "state": "idle"} class Statuser(threading.Thread): def __init__(self, workers, directory=None, **kwargs): self.workers = workers self.directory = directory super().__init__(**kwargs) def run(self): while True: status["finished"] = { "video": glob.glob(os.path.join(self.directory, "*.mkv")), "audio": glob.glob(os.path.join(self.directory, "*/*/*.mp3")), } status["workers"] = [w.status for w in self.workers] time.sleep(12) def main(): parser = argparse.ArgumentParser(description="Rip/encode optical media") parser.add_argument("-incoming", type=pathlib.Path, default="/incoming") parser.add_argument("-www", type=pathlib.Path, default="/www") parser.add_argument("-port", type=int, default=8080) parser.add_argument("-drive", nargs="+", default=["/dev/sr0"]) args = parser.parse_args() readers = [Reader(d, directory=args.incoming, daemon=True) for d in args.drive] encoder = Encoder(directory=args.incoming, daemon=True) statuser = Statuser(readers + [encoder], directory=args.incoming, daemon=True) [r.start() for r in readers] encoder.start() statuser.start() handler = lambda r, a, s: HTTPRequestHandler(r, a, s, directory=args.www) httpd = http.server.ThreadingHTTPServer(('', args.port), handler) httpd.serve_forever() if __name__ == "__main__": main() # vi: sw=4 ts=4 et ai