Possibly working?
This commit is contained in:
parent
22304132f3
commit
c8b4d57027
|
@ -0,0 +1 @@
|
||||||
|
__pycache__
|
|
@ -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
|
|
@ -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
|
|
@ -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
173
sucker.py
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue