Refactor. CD maybe working?

This commit is contained in:
Neale Pickett 2022-08-25 10:17:25 -06:00
parent 2860a1405c
commit 91a332ca53
13 changed files with 329 additions and 286 deletions

View File

@ -10,6 +10,7 @@
"gnudb", "gnudb",
"newfn", "newfn",
"RDONLY", "RDONLY",
"cdparanoia",
"TTITLE" "TTITLE"
] ]
} }

View File

@ -7,24 +7,17 @@ RUN true \
&& sed -i 's/main$/main contrib non-free/' /etc/apt/sources.list \ && sed -i 's/main$/main contrib non-free/' /etc/apt/sources.list \
&& apt-get -y update \ && apt-get -y update \
&& DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -y install \ && DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -y install \
ffmpeg \
handbrake-cli libavcodec-extra \
abcde eyed3 \
glyrc setcd eject \
dvdbackup \ dvdbackup \
libdvd-pkg libdvdcss2 \ libdvd-pkg libdvdcss2 \
handbrake-cli libavcodec-extra \
cd-discid cdparanoia lame \
python3 \ python3 \
cowsay \ python3-slugify \
&& true && true
RUN dpkg-reconfigure libdvd-pkg RUN dpkg-reconfigure libdvd-pkg
RUN true \ RUN true \
&& DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -y install \ && DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -y install \
lame \
busybox \
jq \
procps \
moreutils \
cowsay cowsay
COPY src/* /app/ COPY src/* /app/

View File

@ -14,6 +14,13 @@ At the time I'm writing this README, it will:
* ~~Rip audio CDs, look them up in cddb, encode them to VBR MP3, then tag them.~~ A rewrite broke this; I plan to fix it soon. * ~~Rip audio CDs, look them up in cddb, encode them to VBR MP3, then tag them.~~ A rewrite broke this; I plan to fix it soon.
* Rip video DVDs, transcode them to mkv * Rip video DVDs, transcode them to mkv
## Requirements
* HandBrakeCLI
* cdparanoia
* cd-discid
*
## How To Run This ## How To Run This
You need a place to store your stuff. You need a place to store your stuff.

54
doc/architecture.md Normal file
View File

@ -0,0 +1,54 @@
# Web Server
There is one web server,
which provides static content,
and a single entrypoint for dynamic state information.
The static content is some HTML and JavaScript,
which the browser runs to pull the dynamic state,
and update the page with current status of everything.
# Workers
There are at least two Workers:
a Reader and an Encoder.
Each Worker runs in its own thread,
and can do its job without interfering with another Worker.
## Readers
Readers monitor a device for media.
Right now, those devices are always CD-ROM drives.
As soon as media is inserted,
a MediaHandler is created to scan and then copy it.
## Encoders
Encoders wait for jobs to show up,
and then they re-invoke a MediaHandler to encode everything in that job.
# MediaHandlers
MediaHandlers have a work directory,
where they store all their stuff.
They have the following stages of execution:
1. *scan* the media to figure out its title, list of tracks, and other metadata
2. *copy* the media to the work directory
3. *encode* the work directory into the desired format (eg. MP3, MKV)
4. *clean* the work directory
Before each step,
state is read out of the work directory.
During each step,
a MediaHandler continually updates its Worker with a completion percentage.
This is passed up to the Web Server's dynamic state.
After each step,
a MediaHandler updates its state,
which is stored on disk.
The only way to communicate state between execution stages is by writing to disk.
This provides some tolerance of job interruption, power loss, etc.

View File

@ -13,7 +13,7 @@ SECOND = 1
MINUTE = 60 * SECOND MINUTE = 60 * SECOND
HOUR = 60 * MINUTE HOUR = 60 * MINUTE
def read(device, status): def scan(state, device):
# Get disc ID # Get disc ID
p = subprocess.run( p = subprocess.run(
[ [
@ -24,7 +24,7 @@ def read(device, status):
capture_output=True, capture_output=True,
) )
discid = p.stdout.strip() discid = p.stdout.strip()
status["discid"] = discid state["discid"] = discid
# Look it up in cddb # Look it up in cddb
email = os.environ.get("EMAIL") # You should really set this variable, tho email = os.environ.get("EMAIL") # You should really set this variable, tho
@ -42,23 +42,24 @@ def read(device, status):
# We're expected to be automatic here, # We're expected to be automatic here,
# so just use the first one. # so just use the first one.
for k in ("title", "artist", "genre", "year", "tracks"): for k in ("title", "artist", "genre", "year", "tracks"):
status[k] = disc[k] state[k] = disc[k]
else: else:
now = time.strftime("%Y-%m-%dT%H%M%S") now = time.strftime("%Y-%m-%dT%H%M%S")
num_tracks = int(discid.split()[1]) num_tracks = int(discid.split()[1])
status["title"] = "Unknown CD - %s" % now state["title"] = "Unknown CD - %s" % now
status["tracks"] = ["Track %02d" % i for i in range(num_tracks)] state["tracks"] = ["Track %02d" % i for i in range(num_tracks)]
def rip(device, status, directory):
def copy(state, device, directory):
# cdparanoia reports completion in samples # cdparanoia reports completion in samples
# use discid duration to figure out total number of samples # use discid duration to figure out total number of samples
duration = int(status["discid"].split()[-1]) * SECOND # disc duration in seconds duration = int(state["discid"].split()[-1]) * SECOND # disc duration in seconds
total_samples = duration * (75 / SECOND) * 1176 # 75 sectors per second, 1176 samples per sector total_samples = duration * (75 / SECOND) * 1176 # 75 sectors per second, 1176 samples per sector
status["total_samples"] = total_samples state["total_samples"] = total_samples
track_num = 1 track_num = 1
for track_name in status["tracks"]: for track_name in state["tracks"]:
logging.debug("Ripping track %d of %d", track_num, len(status["tracks"])) logging.debug("Ripping track %d of %d", track_num, len(state["tracks"]))
p = subprocess.Popen( p = subprocess.Popen(
[ [
"cdparanoia", "cdparanoia",
@ -76,16 +77,17 @@ def rip(device, status, directory):
line = line.strip() line = line.strip()
if line.startswith("##: -2"): if line.startswith("##: -2"):
samples = int(line.split()[-1]) samples = int(line.split()[-1])
status["complete"] = samples / total_samples yield samples / total_samples
track_num += 1 track_num += 1
def encode(status, directory):
def encode(state, directory):
track_num = 1 track_num = 1
durations = [int(d) for d in status["discid"].split()[2:-1]] durations = [int(d) for d in state["discid"].split()[2:-1]]
total_duration = sum(durations) total_duration = sum(durations)
encoded_duration = 0 encoded_duration = 0
for track_name in status["tracks"]: for track_name in state["tracks"]:
logging.debug("Encoding track %d (%s)" % (track_num, track_name)) logging.debug("Encoding track %d (%s)" % (track_num, track_name))
duration = durations[track_num-1] duration = durations[track_num-1]
argv = [ argv = [
@ -94,15 +96,15 @@ def encode(status, directory):
"--nohist", "--nohist",
"--disptime", "1", "--disptime", "1",
"--preset", "standard", "--preset", "standard",
"--tl", status["title"], "--tl", state["title"],
"--tn", "%d/%d" % (track_num, len(status["tracks"])), "--tn", "%d/%d" % (track_num, len(state["tracks"])),
] ]
if status.get("artist"): if state.get("artist"):
argv.extend(["--ta", status["artist"]]) argv.extend(["--ta", state["artist"]])
if status.get("genre"): if state.get("genre"):
argv.extend(["--tg", status["genre"]]) argv.extend(["--tg", state["genre"]])
if status.get("year"): if state.get("year"):
argv.extend(["--ty", status["year"]]) argv.extend(["--ty", state["year"]])
if track_name: if track_name:
argv.extend(["--tt", track_name]) argv.extend(["--tt", track_name])
outfn = "%02d - %s.mp3" % (track_num, track_name) outfn = "%02d - %s.mp3" % (track_num, track_name)
@ -122,34 +124,38 @@ def encode(status, directory):
p = line.split("(")[1] p = line.split("(")[1]
p = p.split("%")[0] p = p.split("%")[0]
pct = int(p) / 100 pct = int(p) / 100
status["complete"] = (encoded_duration + (duration * pct)) / total_duration yield (encoded_duration + (duration * pct)) / total_duration
print(status["complete"])
encoded_duration += duration encoded_duration += duration
track_num += 1 track_num += 1
def clean(state, directory):
pass
if __name__ == "__main__": if __name__ == "__main__":
import pprint import pprint
import sys import sys
import json import json
if len(sys.argv) > 1: logging.basicConfig(level=logging.DEBUG)
directory = sys.argv[1]
fn = os.path.join(directory, "status.json")
f = open(fn)
status = json.load(f)
else:
logging.basicConfig(level=logging.DEBUG)
status = {}
read("/dev/sr0", status)
pprint.pprint(status)
directory = os.path.join(".", status["title"]) state = {}
os.makedirs(directory, exist_ok=True) scan(state, "/dev/sr0")
rip("/dev/sr0", status, directory) pprint.pprint(state)
pprint.pprint(status)
encode(status, directory) directory = os.path.join(".", state["title"])
pprint.pprint(status) os.makedirs(directory, exist_ok=True)
with open(os.path.join(directory, "state.json"), "w") as f:
json.dump(f, state)
for pct in copy(state, "/dev/sr0", directory):
sys.stdout.write("Copying: %3d%%\r" % (pct*100))
pprint.pprint(state)
for pct in encode(state, directory):
sys.stdout.write("Encoding: %3d%%\r" % (pct*100))
pprint.pprint(state)
# vi: sw=4 ts=4 et ai # vi: sw=4 ts=4 et ai

View File

@ -10,164 +10,153 @@ SECOND = 1
MINUTE = 60 * SECOND MINUTE = 60 * SECOND
HOUR = 60 * MINUTE HOUR = 60 * MINUTE
class Copier: def collect(collection, track):
def __init__(self, device, status): newCollection = []
self.device = device for t in collection:
self.status = status if t["length"] == track["length"]:
self.scan() # If the length is exactly the same,
# assume it's the same track,
def collect(self, track): # and pick the one with the most stuff.
newCollection = [] if len(track["audio"]) < len(t["audio"]):
for t in self.collection: return collection
if t["length"] == track["length"]: elif len(track["subp"]) < len(t["subp"]):
# If the length is exactly the same, return collection
# assume it's the same track, newCollection.append(t)
# and pick the one with the most stuff. newCollection.append(track)
if len(track["audio"]) < len(t["audio"]): return newCollection
return
elif len(track["subp"]) < len(t["subp"]):
return
newCollection.append(t)
newCollection.append(track)
self.collection = newCollection
def scan(self):
self.status["state"] = "scanning"
self.collection = []
p = subprocess.run(
[
"lsdvd",
"-Oy",
"-x",
self.device,
],
encoding="utf-8",
capture_output=True,
)
lsdvd = eval(p.stdout[8:]) # s/^lsdvd = //
title = lsdvd["title"]
if title in ('No', 'unknown'):
title = lsdvd["provider_id"]
if title == "$PACKAGE_STRING":
title = "DVD"
now = time.strftime("%Y-%m-%dT%H%M%S")
title = "%s %s" % (title, now)
# Go through all the tracks, looking for the largest referenced sector.
max_sector = 0
max_length = 0
tracks = lsdvd["track"]
for track in tracks:
max_length = max(track["length"], max_length)
for cell in track["cell"]:
max_sector = max(cell["last_sector"], max_sector)
if max_sector == 0:
logging.info("Media size = 0; aborting")
return
# Make a guess about what's on this DVD.
# We will categories into three types:
# * A feature, which has one track much longer than any other
# * A collection of shows, which has several long tracks, more or less the same lengths
# * Something else
for track in tracks:
if track["length"] / max_length > 0.80:
self.collect(track)
if (max_length < 20 * MINUTE) and (len(self.collection) < len(track) * 0.6):
self.collection = tracks
self.status["title"] = title
self.status["size"] = max_sector * 2048 # DVD sector size = 2048
self.status["tracks"] = [(t["ix"], t["length"]) for t in self.collection]
def copy(self, directory): def scan(state, device):
self.status["state"] = "copying" p = subprocess.run(
[
"lsdvd",
"-Oy",
"-x",
device,
],
encoding="utf-8",
capture_output=True,
)
lsdvd = eval(p.stdout[8:]) # s/^lsdvd = //
title = lsdvd["title"]
if title in ('No', 'unknown'):
title = lsdvd["provider_id"]
if title == "$PACKAGE_STRING":
title = "DVD"
now = time.strftime(r"%Y-%m-%dT%H%M%S")
title = "%s %s" % (title, now)
# Go through all the tracks, looking for the largest referenced sector.
max_sector = 0
max_length = 0
tracks = lsdvd["track"]
for track in tracks:
max_length = max(track["length"], max_length)
for cell in track["cell"]:
max_sector = max(cell["last_sector"], max_sector)
if max_sector == 0:
logging.info("Media size = 0; aborting")
return
# Make a guess about what's on this DVD.
# We will categories into three types:
# * A feature, which has one track much longer than any other
# * A collection of shows, which has several long tracks, more or less the same lengths
# * Something else
collection = []
for track in tracks:
if track["length"] / max_length > 0.80:
collection = collect(track)
if (max_length < 20 * MINUTE) and (len(collection) < len(track) * 0.6):
collection = tracks
state["title"] = title
state["size"] = max_sector * 2048 # DVD sector size = 2048
state["tracks"] = [(t["ix"], t["length"]) for t in collection]
def copy(state, device, directory):
p = subprocess.Popen(
[
"dvdbackup",
"--input=" + device,
"--name=" + state["title"],
"--mirror",
"--progress",
],
encoding="utf-8",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=directory,
)
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
yield (totalBytes + titleSize) / state["size"]
def encode(state, directory):
title = state["title"]
logging.info("encoding: %s (%s)" % (title, directory))
total_length = sum(t[1] for t in state["tracks"])
finished_length = 0
for track, length in state["tracks"]:
outfn = "%s-%d.mkv" % (title, track)
tmppath = os.path.join(directory, outfn)
outpath = os.path.join(directory, "..", outfn)
p = subprocess.Popen( p = subprocess.Popen(
[ [
"dvdbackup", "nice",
"--input=" + self.device, "HandBrakeCLI",
"--name=" + self.status["title"], "--json",
"--mirror", "--input", "%s/VIDEO_TS" % directory,
"--progress", "--output", tmppath,
"--title", str(track),
"--native-language", "eng",
"--markers",
"--loose-anamorphic",
"--all-subtitles",
"--all-audio",
"--aencoder", "copy",
"--audio-copy-mask", "aac,ac3,mp3",
"--audio-fallback", "aac",
], ],
encoding="utf-8", encoding="utf-8",
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stderr=None,
cwd=directory,
) )
totalBytes = titleSize = lastTitleSize = 0
progressRe = re.compile(r"^Copying.*([0-9.]+)/[0-9.]+ (MiB|KiB)") # 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: for line in p.stdout:
line = line.strip() line = line.strip()
m = progressRe.search(line) m = progressRe.search(line)
if m and m[2] == "MiB": if m:
titleSize = float(m[1]) * 1024 * 1024 progress = float(m[1])
elif m and m[2] == "KiB": complete = (finished_length + progress*length) / total_length
titleSize = float(m[1]) * 1024 state["complete"] = complete
if titleSize < lastTitleSize:
totalBytes += lastTitleSize finished_length += length
lastTitleSize = titleSize os.rename(
self.status["complete"] = (totalBytes + titleSize) / self.status["size"] src=tmppath,
dst=outpath,
)
logging.info("Finished track %d; length %d" % (track, length))
class Encoder: def clean(state, directory):
def __init__(self, basedir, status): pass
self.basedir = basedir
self.status = status
def encode(self, obj):
title = obj["title"]
logging.info("encoding: %s (%s)" % (title, self.basedir))
total_length = sum(t[1] for t in obj["tracks"])
finished_length = 0
for track, length in obj["tracks"]:
outfn = "%s-%d.mkv" % (title, track)
tmppath = os.path.join(self.basedir, outfn)
outpath = os.path.join(self.basedir, "..", outfn)
p = subprocess.Popen(
[
"nice",
"HandBrakeCLI",
"--json",
"--input", "%s/VIDEO_TS" % self.basedir,
"--output", tmppath,
"--title", str(track),
"--native-language", "eng",
"--markers",
"--loose-anamorphic",
"--all-subtitles",
"--all-audio",
"--aencoder", "copy",
"--audio-copy-mask", "aac,ac3,mp3",
"--audio-fallback", "aac",
],
encoding="utf-8",
stdout=subprocess.PIPE,
stderr=None,
)
# 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])
complete = (finished_length + progress*length) / total_length
self.status["complete"] = complete
finished_length += length
os.rename(
src=tmppath,
dst=outpath,
)
logging.info("Finished track %d; length %d" % (track, length))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,7 +1,6 @@
#! /usr/bin/python3 #! /usr/bin/python3
import os import os
import threading
import subprocess import subprocess
import glob import glob
import os import os
@ -13,40 +12,41 @@ import re
import logging import logging
import dvd import dvd
import cd import cd
import worker
class Encoder(threading.Thread): class Encoder(worker.Worker):
def __init__(self, directory=None, **kwargs): def __init__(self, directory=None):
self.status = {} self.status = {}
self.directory = directory self.directory = directory
return super().__init__(**kwargs) return super().__init__(directory)
def run(self): def run(self):
while True: while True:
wait = True wait = True
self.status = {"type": "encoder", "state": "idle"} self.status = {"type": "encoder", "state": "idle"}
for fn in glob.glob(os.path.join(self.directory, "*", "sucker.json")): for fn in glob.glob(self.workdir("*", "state.json")):
fdir = os.path.dirname(fn) self.encode(os.path.dirname(fn), obj)
with open(fn) as f:
obj = json.load(f)
self.encode(fdir, obj)
wait = False wait = False
if wait: if wait:
time.sleep(12) time.sleep(12)
def encode(self, fdir, obj): def encode(self, directory, obj):
self.status["state"] = "encoding" self.status["state"] = "encoding"
self.status["title"] = obj["title"]
if obj["type"] == "audio":
self.encode_audio(fdir, obj)
else:
self.encode_video(fdir, obj)
shutil.rmtree(fdir)
def encode_audio(self, fdir, obj):
cd.encode(obj, fdir)
def encode_video(self, fdir, obj): state = self.read_state(directory)
enc = dvd.Encoder(fdir, self.status) self.status["title"] = state["title"]
enc.encode(obj)
if state["video"]:
media = dvd
else:
media = cd
logging.info("Encoding %s (%s)" % (directory, state["title"]))
for pct in media.encode(state, directory):
self.status["complete"] = pct
media.clean(state, directory)
logging.info("Finished encoding")
# vi: sw=4 ts=4 et ai # vi: sw=4 ts=4 et ai

6
src/mediahandler.py Normal file
View File

@ -0,0 +1,6 @@
class MediaHandler:
def __init__(self, basedir, state):
self.basedir = basedir
self.state = state
def

View File

@ -1,7 +1,6 @@
#! /usr/bin/python3 #! /usr/bin/python3
import os import os
import threading
import subprocess import subprocess
import time import time
import re import re
@ -9,8 +8,10 @@ import fcntl
import traceback import traceback
import json import json
import logging import logging
import slugify
import dvd import dvd
import cd import cd
import worker
CDROM_DRIVE_STATUS = 0x5326 CDROM_DRIVE_STATUS = 0x5326
CDS_NO_INFO = 0 CDS_NO_INFO = 0
@ -28,25 +29,21 @@ CDS_DATA_2 = 102
CDROM_LOCKDOOR = 0x5329 CDROM_LOCKDOOR = 0x5329
CDROM_EJECT = 0x5309 CDROM_EJECT = 0x5309
class Reader(threading.Thread): class Reader(worker.Worker):
def __init__(self, device, directory=None, **kwargs): def __init__(self, device, directory):
super().__init__(device)
self.device = device self.device = device
self.directory = directory self.status["type"] = "reader"
self.status = { self.status["device"] = device
"type": "reader",
"state": "idle",
"device": self.device,
}
self.complete = 0 self.complete = 0
self.staleness = 0 self.staleness = 0
self.drive = None self.drive = None
logging.info("Starting reader on %s" % self.device) logging.info("Starting reader on %s" % self.device)
return super().__init__(**kwargs)
def reopen(self): def reopen(self):
if (self.staleness > 15) or not self.drive: if (self.staleness > 15) or not self.drive:
if self.drive: if self.drive:
self.drive.close() os.close(self.drive)
self.drive = None self.drive = None
try: try:
self.drive = os.open(self.device, os.O_RDONLY | os.O_NONBLOCK) self.drive = os.open(self.device, os.O_RDONLY | os.O_NONBLOCK)
@ -69,9 +66,9 @@ class Reader(threading.Thread):
rv = fcntl.ioctl(self.drive, CDROM_DISC_STATUS) rv = fcntl.ioctl(self.drive, CDROM_DISC_STATUS)
try: try:
if rv == CDS_AUDIO: if rv == CDS_AUDIO:
self.handle_audio() self.handle(false)
elif rv in [CDS_DATA_1, CDS_DATA_2]: elif rv in [CDS_DATA_1, CDS_DATA_2]:
self.handle_data() self.handle(true)
else: else:
logging.info("Can't handle disc type %d" % rv) logging.info("Can't handle disc type %d" % rv)
except Exception as e: except Exception as e:
@ -96,32 +93,26 @@ class Reader(threading.Thread):
logging.error("Ejecting: %v" % e) logging.error("Ejecting: %v" % e)
time.sleep(i * 5) time.sleep(i * 5)
# XXX: rename this to something like "write_status" def handle(self, video):
def finished(self, **kwargs): self.status["video"] = video
self.status["state"] = "finished read"
fn = os.path.join(self.directory, self.status["title"], "sucker.json")
newfn = fn + ".new"
with open(newfn, "w") as fout:
json.dump(obj=self.status, fp=fout)
os.rename(src=newfn, dst=fn)
def handle_audio(self):
self.status["video"] = False
self.status["state"] = "reading" self.status["state"] = "reading"
cd.read(self.device, self.status)
state = {}
state["video"] = video
if video:
media = cd
else:
media = dvd
media.scan(state, self.device)
self.status["title"] = state["title"]
subdir = slugify.slugify(state["title"])
directory = os.path.join(self.directory, status["title"])
os.makedirs(directory, exist_ok=True)
self.status["state"] = "copying" self.status["state"] = "copying"
cd.copy(self.device, self.status, self.directory) for pct in media.copy(device, self.workdir(subdir)):
self.finished() # XXX: rename this to something like "write_status" self.status["complete"] = pct
self.write_state(subdir, state)
def handle_data(self):
self.status["video"] = True
src = dvd.Copier(self.device, self.status)
src.copy(self.directory)
self.finished()
# vi: sw=4 ts=4 et ai # vi: sw=4 ts=4 et ai

View File

@ -1,25 +0,0 @@
#! /usr/bin/python3
import json
class State(dict):
def __init__(self, path):
super().__init__()
self.path = path
self.read()
def read(self):
try:
f = open(self.path)
except FileNotFoundError:
return
obj = json.load(f)
f.close()
for k in obj:
self[k] = obj[k]
def write(self):
f = open(self.path, "w")
json.dump(self, f)
f.close()

View File

@ -7,17 +7,17 @@ import time
import os import os
class Statuser(threading.Thread): class Statuser(threading.Thread):
def __init__(self, workers, directory=None, **kwargs): def __init__(self, workers, directory):
self.workers = workers self.workers = workers
self.directory = directory self.directory = directory
self.status = {} self.status = {}
super().__init__(**kwargs) super().__init__(daemon=True)
def run(self): def run(self):
while True: while True:
self.status["finished"] = { self.status["finished"] = {
"video": glob.glob(os.path.join(self.directory, "*.mkv")), "video": glob.glob(os.path.join(self.directory, "*.mkv")),
"audio": glob.glob(os.path.join(self.directory, "*/*/*.mp3")), "audio": glob.glob(os.path.join(self.directory, "*/*.mp3")),
} }
self.status["workers"] = [w.status for w in self.workers] self.status["workers"] = [w.status for w in self.workers]
time.sleep(12) time.sleep(12)

View File

@ -33,13 +33,9 @@ def main():
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
readers = [] readers = [reader.Reader(d, args.incoming) for d in args.drive]
for d in args.drive: encoders = [encoder.Encoder(args.incoming) for i in range(1)]
readers.append(reader.Reader(d, directory=args.incoming, daemon=True)) st = statuser.Statuser(readers + encoders, args.incoming)
encoders = []
for i in range(1):
encoders.append(encoder.Encoder(directory=args.incoming, daemon=True))
st = statuser.Statuser(readers + encoders, directory=args.incoming, daemon=True)
[w.start() for w in readers + encoders] [w.start() for w in readers + encoders]
st.start() st.start()

25
src/worker.py Normal file
View File

@ -0,0 +1,25 @@
import threading
import os
import json
class Worker(threading.Thread):
def __init__(self, directory, **kwargs):
self.directory = directory
self.status = {
"state": "idle",
"directory": directory,
}
kwargs["daemon"] = True
return super().__init__(**kwargs)
def workdir(self, *path):
return os.path.join(self.directory, *path)
def write_state(self, subdir, state):
with open(self.workdir(subdir, "state.json"), "w") as f:
json.dump(f, state)
def read_state(self, subdir):
with open(self.workdir(subdir, "state.json")) as f:
return json.load(f)