Possibly working?

This commit is contained in:
Neale Pickett 2022-02-26 08:05:20 -07:00
parent 22304132f3
commit c8b4d57027
5 changed files with 281 additions and 156 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__

89
encoder.py Normal file
View File

@ -0,0 +1,89 @@
#! /usr/bin/python3
import os
import threading
import subprocess
import glob
import os
import json
import io
import shutil
import time
import re
class Encoder(threading.Thread):
def __init__(self, directory=None, **kwargs):
self.status = {}
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):
while True:
wait = True
self.status = {"type": "encoder", "state": "idle"}
for mtype in ("audio", "video"):
for fn in glob.glob(os.path.join(self.directory, mtype, "*", "sucker.json")):
fdir = os.path.dirname(fn)
with open(fn) as f:
obj = json.load(f)
self.encode(mtype, fdir, obj)
wait = False
if wait:
time.sleep(12)
def encode(self, mtype, fdir, obj):
if mtype == "audio":
self.encode_audio(fdir, obj)
else:
self.encode_video(fdir, obj)
shutil.rmtree(fdir)
def encode_audio(self, fdir, obj):
print("Not implemented")
def encode_video(self, fdir, obj):
self.status["state"] = "encoding"
title = os.path.basename(fdir)
self.status["title"] = title
print("encoding", title, fdir)
outfn = "%s.mkv" % title
tmppath = os.path.join(fdir, outfn)
outpath = os.path.join(self.directory, outfn)
p = subprocess.Popen(
[
"HandBrakeCLI",
"--json",
"-i", "%s/VIDEO_TS" % fdir,
"--main-feature",
"--native-language", "eng",
"-Z", "Chromecast 1080p30 Surround",
"-o", tmppath,
],
encoding="utf-8",
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
# HandBrakeCLI spits out sort of JSON.
# But Python has no built-in way to stream JSON objects.
# Hence this kludge.
progressRe = re.compile(r'^"Progress": ([0-9.]+),')
for line in p.stdout:
line = line.strip()
m = progressRe.search(line)
if m:
progress = float(m[1])
self.status["complete"] = progress
os.rename(
src=tmppath,
dst=outpath,
)
pass
# vi: sw=4 ts=4 et ai

146
reader.py Normal file
View File

@ -0,0 +1,146 @@
#! /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

28
statuser.py Normal file
View File

@ -0,0 +1,28 @@
#! /usr/bin/python3
import threading
import json
import glob
import time
import os
class Statuser(threading.Thread):
def __init__(self, workers, directory=None, **kwargs):
self.workers = workers
self.directory = directory
self.status = {}
super().__init__(**kwargs)
def run(self):
while True:
self.status["finished"] = {
"video": glob.glob(os.path.join(self.directory, "*.mkv")),
"audio": glob.glob(os.path.join(self.directory, "*/*/*.mp3")),
}
self.status["workers"] = [w.status for w in self.workers]
time.sleep(12)
def json(self):
return json.dumps(self.status)
# vi: sw=4 ts=4 et ai

173
sucker.py
View File

@ -1,39 +1,16 @@
#! /usr/bin/python3 #! /usr/bin/python3
import os
import argparse import argparse
import threading
import subprocess
import http.server import http.server
import pathlib 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": [],
}
import reader, encoder, statuser
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def __init__(self, r, a, s, directory, statuser):
self.statuser = statuser
super().__init__(r, a, s, directory=directory)
def do_GET(self): def do_GET(self):
if self.path == "/status.json": if self.path == "/status.json":
return self.get_status() return self.get_status()
@ -42,129 +19,9 @@ class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def get_status(self): def get_status(self):
self.send_response(200) self.send_response(200)
self.end_headers() self.end_headers()
buf = json.dumps(status).encode("utf-8") buf = self.statuser.json().encode("utf-8")
self.wfile.write(buf) 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(): def main():
parser = argparse.ArgumentParser(description="Rip/encode optical media") parser = argparse.ArgumentParser(description="Rip/encode optical media")
parser.add_argument("-incoming", type=pathlib.Path, default="/incoming") parser.add_argument("-incoming", type=pathlib.Path, default="/incoming")
@ -173,14 +30,18 @@ def main():
parser.add_argument("-drive", nargs="+", default=["/dev/sr0"]) parser.add_argument("-drive", nargs="+", default=["/dev/sr0"])
args = parser.parse_args() args = parser.parse_args()
readers = [Reader(d, directory=args.incoming, daemon=True) for d in args.drive] readers = []
encoder = Encoder(directory=args.incoming, daemon=True) for d in args.drive:
statuser = Statuser(readers + [encoder], directory=args.incoming, daemon=True) readers.append(reader.Reader(d, directory=args.incoming, daemon=True))
[r.start() for r in readers] encoders = []
encoder.start() for i in range(1):
statuser.start() encoders.append(encoder.Encoder(directory=args.incoming, daemon=True))
st = statuser.Statuser(readers + encoders, directory=args.incoming, daemon=True)
handler = lambda r, a, s: HTTPRequestHandler(r, a, s, directory=args.www) [w.start() for w in readers + encoders]
st.start()
handler = lambda r, a, s: HTTPRequestHandler(r, a, s, directory=args.www, statuser=st)
httpd = http.server.ThreadingHTTPServer(('', args.port), handler) httpd = http.server.ThreadingHTTPServer(('', args.port), handler)
httpd.serve_forever() httpd.serve_forever()