~aleteoryx/employ

2a9972922ff79de7acdf38cb21e2ac66df6c7ea7 — Aleteoryx 9 days ago ad29286
queuers
2 files changed, 277 insertions(+), 0 deletions(-)

A queuers/employ.sh
A queuers/reenc.py
A queuers/employ.sh => queuers/employ.sh +18 -0
@@ 0,0 1,18 @@
#!/bin/sh

if [ $# \!= 3 ]; then
	1>&2 echo 'usage: employ.sh <host> <port> <file>'
	exit 1
fi

host="$1"
port="$2"
file="$3"

{
	echo -n 'NEW JOB '
	stat -c %s "$file"
	cat "$file"
	echo 'MARK AS available'
	echo QUIT
} | nc "$host" "$port"

A queuers/reenc.py => queuers/reenc.py +259 -0
@@ 0,0 1,259 @@
#!/bin/env python

"""
this script assumes a layout like the following, in the pwd of your
employees
/Videos
	/shows
		/????p
			/ShowName
				/[Season ?? ]Episode ?? [ - TITLE].mkv
	/movies
		/????p
			/[A-Z0-9@]
				/MovieName.mkv
"""

from sys import argv, stderr, exit
from glob import iglob
from itertools import chain
import shlex
import re
import readline
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
from os import system
import socket

RESOLUTIONS=[(1280, 720), (1366, 768), (1920, 1080)]

argv0 = 'reenc.py'

def usage():
	global argv0
	print(f'usage: {argv0} <host> <port> <srcdir>', file=stderr)
	exit(-1)


@dataclass
class Episode:
	file: Path

	subs: Optional[str] = None
	title: Optional[str] = None
	season: int = 0
	number: int = 0

	def outname(self):
		name = f'Episode {self.number:02}'
		if self.title is not None:
			name = f'{name} - {self.title}'
		if self.season != 0:
			name = f'Season {self.season:02} {name}'
		
		return f'{name}.mkv'
	
	def outpath(self, res, show):
		wid, hei = res
		return Path('Videos/shows') / f'{hei}p' / show / self.outname()

	def generate_script(self, show, res):
		wid, hei = res
		inparg = shlex.quote(str(self.file))
		outarg = shlex.quote(str(self.outpath(res, show)))
		return '#!/bin/sh\n' f'mkdir -p `dirname {outarg}`\n' f'ffmpeg -loglevel quiet -i {inparg} -map 0 -c:a copy -c:s ass -c:v h264 -s {wid}x{hei} {outarg}\n'
		
	def slug(self):
		if self.season == 0:
			return f'E{self.number:02}'
		else:
			return f'S{self.season:02}E{self.number:02}'

def find_common_ends(files):
	files = iter(files)
	needle = next(files)
	pfxlen = len(needle)
	sfxlen = len(needle)
	
	for file in files:
		while needle[:pfxlen] != file[:pfxlen]:
			pfxlen -= 1
		while needle[-sfxlen:] != file[-sfxlen:]:
			sfxlen -= 1
	
	return needle[:pfxlen], needle[-sfxlen:]

def find_sXXeXX(name):
	m = re.search('S([0-9]+)E([0-9]+)', name, re.I)
	if m is not None:
		season, episode = m.groups()
		return int(season), int(episode)
	
	season, episode = 0, 0
	
	if (m := re.search('S([0-9]+)', name, re.I)) is not None:
		season = int(m.group(1))
	elif (m := re.search('Season ([0-9]+)', name, re.I)) is not None:
		season = int(m.group(1))
	
	if (m := re.search('E([0-9]+)', name, re.I)) is not None:
		episode = int(m.group(1))
	elif (m := re.search('Episode ([0-9]+)', name, re.I)) is not None:
		episode = int(m.group(1))
	elif (m := re.search('([0-9]+)', name, re.I)) is not None:
		episode = int(m.group(1))
	
	return season, episode

def dump_metafile(path, name, files):
	with open(path, 'wt') as fp:
		print(f'showname: {name}',file=fp)
		print(file=fp)
		
		pfx, sfx = find_common_ends(map(lambda x: x.name, files))
		
		for file in files:
			season, episode = find_sXXeXX(file.name)
			title = file.name[len(pfx):-len(sfx)]

			print(f'file: {str(file)}', file=fp)
			print(f'season: {season}', file=fp)
			print(f'number: {episode}', file=fp)
			print(f'title: {title}', file=fp)
			print(file=fp)

def load_metafile(path):
	with open(path, 'rt') as fp:
		lines = map(str.strip, filter(lambda x: not x.startswith('#'), fp.readlines()))
		
		showname = None
		
		for line in lines:
			if line == '':
				break
			if ':' not in line:
				continue
			k,v = line.split(':', 1)
			
			if k.strip() == 'showname':
				showname = v.strip()


		episodes = []
		data = {}

		for line in lines:
			if line == '':
				if len(data) > 0:
					episodes.append(Episode(**data))
					data = {}
			if ':' not in line:
				continue
			k,v = line.split(':', 1)
			
			try:
				v = int(v.strip())
			except:
				v = v.strip()
			
			data[k.strip()] = v

		if len(data) != 0:
			episodes.append(Episode(**data))

		return showname, episodes

def check_overlap(episodes):
	overmap = {}
	ret = False
	for episode in episodes:
		name = episode.outname()
		if name in overmap:
			overmap[name].append(episode)
			ret = True
		else:
			overmap[name] = [episode]
	
	for path, eps in overmap.items():
		if len(eps) > 1:
			print(f'multiple files would be saved at {path}')
			for ep in eps:
				print(f'\t{ep.file}')
	
	return ret

def do_connect(host, port):
	sok = socket.create_connection((host, port))
	return sok.makefile('r'), sok.makefile('wb')

def queue_job(reader, writer, show, res, episode):
	jobname = f'{show} {episode.slug()} @ {res}'
	script = episode.generate_script(show, res).encode()
	
	writer.write(f'NEW JOB {len(script)}\n'.encode())
	writer.write(script)
	writer.flush()
	jid = reader.readline().strip()
	if jid == '?\n':
		print(f'couldn\'t queue {jobname}')
		return
	else:
		jid = int(jid)
	reader.readline()

	writer.write(f'META name IS {jobname}\n'.encode())
	writer.flush()
	reader.readline()

	writer.write(f'MARK AS available\n'.encode())
	writer.flush()
	reader.readline()
	
	print(f'{jobname} queued as {jid}')


if __name__ == '__main__':
	if len(argv) > 0:
		argv0 = argv[0]
		argv = argv[1:]
	if len(argv) != 3:
		usage()
	host, port, dir = argv


	print(f'reencoding {dir}...')

	dir = Path(dir)
	metapath = Path(dir) / 'reenc.meta'
	
	if metapath.exists():
		showname, episodes = load_metafile(metapath)
		if check_overlap(episodes):
			print("exiting.")
			exit(-2)
		
		reader, writer = do_connect(host, port)
		
		for episode in episodes:
			for res in RESOLUTIONS:
				if episode.outpath(res, showname).exists():
					print(f'{episode.slug()} @ {res} already exists, skipping')
			
				queue_job(reader, writer, showname, res, episode)
		
		writer.write(b'QUIT\n')
		writer.flush()

	else:
		print(f'no metafile found, generating {metapath}...')

		mkglob = lambda x: iglob(f'**/*.{x}', root_dir=dir, recursive=True)
		globs = chain(mkglob('mkv'), mkglob('mp4'), mkglob('mov'), mkglob('avi'))
		files = [*map(lambda p: dir / p, globs)]

		showname = input('enter the name of this show\n% ')
		
		dump_metafile(metapath, showname, files)
		system(f'xdg-open {shlex.quote(str(metapath))}')