147 lines
4.3 KiB
Python
147 lines
4.3 KiB
Python
|
#! /usr/bin/python3
|
||
|
|
||
|
import os
|
||
|
import threading
|
||
|
import subprocess
|
||
|
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_LOCKDOOR = 0x5329
|
||
|
CDROM_EJECT = 0x5309
|
||
|
|
||
|
class Reader(threading.Thread):
|
||
|
def __init__(self, device, directory=None, **kwargs):
|
||
|
self.device = device
|
||
|
self.directory = directory
|
||
|
self.status = {
|
||
|
"type": "reader",
|
||
|
"state": "idle",
|
||
|
"device": self.device,
|
||
|
}
|
||
|
self.complete = 0
|
||
|
self.drive = os.open(device, os.O_RDONLY | os.O_NONBLOCK)
|
||
|
return super().__init__(**kwargs)
|
||
|
|
||
|
def run(self):
|
||
|
while True:
|
||
|
self.status["state"] = "idle"
|
||
|
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)
|
||
|
self.eject()
|
||
|
else:
|
||
|
time.sleep(3)
|
||
|
|
||
|
def eject(self):
|
||
|
self.status["state"] = "ejecting"
|
||
|
for i in range(20):
|
||
|
try:
|
||
|
fcntl.ioctl(self.drive, CDROM_LOCKDOOR, 0)
|
||
|
fcntl.ioctl(self.drive, CDROM_EJECT)
|
||
|
return
|
||
|
except Exception as e:
|
||
|
time.sleep(i * 5)
|
||
|
|
||
|
def finished(self, **kwargs):
|
||
|
self.status["state"] = "finished read"
|
||
|
fn = os.path.join(self.directory, "video", self.status["title"], "sucker.json")
|
||
|
newfn = fn + ".new"
|
||
|
with open(newfn, "wb") as fout:
|
||
|
json.dump(obj=self.status, fp=fout)
|
||
|
os.rename(src=newfn, dst=fn)
|
||
|
|
||
|
def handle_audio(self):
|
||
|
pass # XXX
|
||
|
|
||
|
def handle_data(self):
|
||
|
self.video_scan()
|
||
|
self.video_copy()
|
||
|
self.finished()
|
||
|
|
||
|
def video_scan(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 = "Unknown DVD"
|
||
|
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
|
||
|
if mediaSize == 0:
|
||
|
print("Media size = 0; aborting")
|
||
|
return
|
||
|
self.status["size"] = mediaSize
|
||
|
|
||
|
def video_copy(self):
|
||
|
self.status["state"] = "copying"
|
||
|
|
||
|
mediaSize = self.status["size"]
|
||
|
title = self.status["title"]
|
||
|
|
||
|
basedir = os.path.join(self.directory, "video")
|
||
|
p = subprocess.Popen(
|
||
|
[
|
||
|
"dvdbackup",
|
||
|
"--input=" + self.path,
|
||
|
"--name=" + title,
|
||
|
"--mirror",
|
||
|
"--progress",
|
||
|
],
|
||
|
encoding="utf-8",
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.STDOUT,
|
||
|
cwd=basedir,
|
||
|
)
|
||
|
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
|
||
|
|
||
|
# vi: sw=4 ts=4 et ai
|