Transition to Python to work around some weird kernel bug
This commit is contained in:
parent
dbfd5b2d57
commit
22304132f3
|
@ -27,10 +27,10 @@ RUN true \
|
||||||
moreutils \
|
moreutils \
|
||||||
cowsay
|
cowsay
|
||||||
|
|
||||||
COPY scripts /scripts
|
COPY sucker.py /usr/bin
|
||||||
COPY abcde.conf httpd.conf /etc/
|
COPY abcde.conf httpd.conf /etc/
|
||||||
COPY --chown=linuxserver:linuxserver www /www
|
COPY --chown=linuxserver:linuxserver www /www
|
||||||
USER linuxserver
|
USER linuxserver
|
||||||
ENTRYPOINT ["/scripts/init.sh"]
|
ENTRYPOINT ["/usr/bin/sucker.py"]
|
||||||
|
|
||||||
# vi: ts=2 sw=2 et ai
|
# vi: ts=2 sw=2 et ai
|
||||||
|
|
|
@ -22,3 +22,7 @@ setenv () {
|
||||||
&& mv env.json.new env.json
|
&& mv env.json.new env.json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status () {
|
||||||
|
echo "$2" > status.$1.new
|
||||||
|
mv status.$1.new status.$1
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
. $(dirname $0)/common.sh
|
. $(dirname $0)/common.sh
|
||||||
|
|
||||||
while sleep 2; do
|
while sleep 2; do
|
||||||
echo "idle" > $OUTDIR/status.encoder
|
status encoder idle
|
||||||
for mtype in audio video; do
|
for mtype in audio video; do
|
||||||
ls $mtype | while read d; do
|
ls $mtype | while read d; do
|
||||||
encode=$SCRIPTS/$mtype.encode.sh
|
encode=$SCRIPTS/$mtype.encode.sh
|
||||||
|
@ -14,7 +14,7 @@ while sleep 2; do
|
||||||
(cd $workdir && setenv status "encode interrupted")
|
(cd $workdir && setenv status "encode interrupted")
|
||||||
;;
|
;;
|
||||||
"read finished")
|
"read finished")
|
||||||
echo "encoding" > $OUTDIR/status.encoder
|
status encoder encoding
|
||||||
(cd $workdir && setenv status "encoding")
|
(cd $workdir && setenv status "encoding")
|
||||||
if ! (cd $workdir && $encode); then
|
if ! (cd $workdir && $encode); then
|
||||||
log "$encode failed"
|
log "$encode failed"
|
||||||
|
|
|
@ -14,7 +14,7 @@ with_time_dir () {
|
||||||
setenv directory "$dir"
|
setenv directory "$dir"
|
||||||
setenv status "reading"
|
setenv status "reading"
|
||||||
|
|
||||||
echo "$mtype" > $OUTDIR/status.reader
|
tatusreader "$mtype"
|
||||||
if ! "$@"; then
|
if ! "$@"; then
|
||||||
log "$1 failed"
|
log "$1 failed"
|
||||||
setenv status "read failed"
|
setenv status "read failed"
|
||||||
|
@ -26,7 +26,7 @@ with_time_dir () {
|
||||||
}
|
}
|
||||||
|
|
||||||
while sleep 2; do
|
while sleep 2; do
|
||||||
echo "idle" > $OUTDIR/status.reader
|
status reader idle
|
||||||
case $(setcd -i) in
|
case $(setcd -i) in
|
||||||
*"Disc found in drive: audio"*)
|
*"Disc found in drive: audio"*)
|
||||||
log "Found audio disc"
|
log "Found audio disc"
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
|
set -x # Why the heck is this eating 100% CPU in an unkillable state?
|
||||||
|
|
||||||
. $(dirname $0)/common.sh
|
. $(dirname $0)/common.sh
|
||||||
|
|
||||||
queue () {
|
queue () {
|
||||||
ls audio/*/env.json video/*/env.json 2>/dev/null \
|
find audio video -name env.json \
|
||||||
| while read envjson
|
| while read envjson # This is the line that's dying at 100% CPU
|
||||||
do
|
do
|
||||||
dir=${envjson%/env.json}
|
dir=${envjson%/env.json}
|
||||||
cat $envjson \
|
cat $envjson \
|
||||||
|
|
|
@ -0,0 +1,190 @@
|
||||||
|
#! /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
|
|
@ -17,9 +17,8 @@
|
||||||
<div class="card-header-title">Status</div>
|
<div class="card-header-title">Status</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<span class="tag reader is-info is-hidden"></span>
|
<div class="workers tags"></div>
|
||||||
<span class="tag encoder is-info is-hidden"></span>
|
<div class="workers jobs"></div>
|
||||||
<div class="jobs"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,26 +5,28 @@ async function update() {
|
||||||
let resp = await fetch("status.json", {cache: "no-store"})
|
let resp = await fetch("status.json", {cache: "no-store"})
|
||||||
let s = await resp.json()
|
let s = await resp.json()
|
||||||
|
|
||||||
for (let activity of ["reader", "encoder"]) {
|
let jobs = document.querySelector(".workers.jobs")
|
||||||
let val = s.status[activity]
|
|
||||||
let e = document.querySelector(`.status .${activity}`)
|
|
||||||
e.textContent = val
|
|
||||||
if (val == "idle") {
|
|
||||||
e.classList.add("is-hidden")
|
|
||||||
} else {
|
|
||||||
e.classList.remove("is-hidden")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let qtmpl = document.querySelector("template.job-item").content
|
while (jobs.firstChild) jobs.firstChild.remove()
|
||||||
let qelem = document.querySelector(".jobs")
|
|
||||||
while (qelem.firstChild) qelem.firstChild.remove()
|
for (let worker of s.workers) {
|
||||||
for (let qitem of s.queue) {
|
if (worker.state != "idle") {
|
||||||
let e = qtmpl.cloneNode(true)
|
let job = jobs.appendChild(document.createElement("div"))
|
||||||
e.querySelector(".job-title").textContent = qitem.title
|
|
||||||
e.querySelector(".job-status").textContent = qitem.status
|
let tag = job.appendChild(document.createElement("span"))
|
||||||
e.querySelector("progress").value = qitem.complete
|
tag.classList.add("tag", "is-info")
|
||||||
qelem.append(e)
|
tag.textContent = worker.type
|
||||||
|
|
||||||
|
let txt = job.appendChild(document.createElement("span"))
|
||||||
|
txt.textContent = worker.title
|
||||||
|
txt.classList.add("mx-1")
|
||||||
|
|
||||||
|
if (worker.complete) {
|
||||||
|
let progress = job.appendChild(document.createElement("progress"))
|
||||||
|
progress.classList.add("is-primary")
|
||||||
|
progress.value = worker.complete
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileItem = document.querySelector("template.panel-file-item").content
|
let fileItem = document.querySelector("template.panel-file-item").content
|
||||||
|
|
Loading…
Reference in New Issue