Project

General

Profile

« Previous | Next » 

Revision 3ce1c609

Added by David Sorber over 7 years ago

Greatly improved the queue.

View differences:

software/hbkcd/hbkcd/command_server.py
import time
import threading
from hbkcd.queue import ENCODE_QUEUE
import magic
from hbkcd.queue import ENCODE_QUEUE, QueueItem
# https://blog.gocept.com/2011/08/04/shutting-down-an-httpserver/
# https://docs.aws.amazon.com/polly/latest/dg/example-Python-server-code.html
......
if path == 'queue':
response_code = 200
response_type = 'application/json'
response_dict = {'queue': ENCODE_QUEUE}
response_dict = {'queue': ENCODE_QUEUE.get_list()}
response_data = json.dumps(response_dict).encode('utf-8')
elif not path:
......
response_dict['success'] = response[0]
response_dict['message'] = response[1]
except Exception as exp:
response_dict['success'] = False
error = '{:s}: {!r}'.format(type(exp).__name__, exp.args)
error = '{:s}: {!r}'.format(type(exp).__name__, exp.args)
response_dict['message'] = error
logging.error(error)
# Send response
self.send_response(response_code)
......
# Check if the path actually exists:
path = command_data['path']
if not os.path.isfile(path):
return False, 'File does not exist: {:s}'.format(path)
msg = 'File does not exist: {:s}'.format(path)
logging.error(msg)
return False, msg
# TODO: check if file is video file
# TODO: get width, heigh and frame rate using ffprobe
# Check if file is video file
mimetype = magic.from_file(path, mime=True)
if not mimetype.startswith('video/'):
msg = 'File "{:s}" does not appear to be a video file ({:s})'
msg = msg.format(path, mimetype)
logging.error(msg)
return False, msg
# Create a QueueItem instance and then probe the file to retrieve its
# attributes
item = QueueItem(path)
if not item.probe():
msg = 'An error ocurred while probing the video file'
logging.error(msg)
return False, msg
ENCODE_QUEUE.append(path)
# Add item to the queue
ENCODE_QUEUE.add(item)
return True, 'Added to queue'
software/hbkcd/hbkcd/config.py
import logging
###############################################################################
# Configuration Options
###############################################################################
LOGFILE_PATH = '/var/log/hbkcd/hbkcd.log'
LOG_FORMAT_STR = '%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s|%(message)s'
LOG_DATE_FORMAT_STR = '%a %b %d %H:%M:%S %Y'
DEFAULT_LOG_LEVEL = logging.DEBUG
# Absolute path the to the HandbrakeCLI executable
HANDBRAKE_PATH = '/opt/local/bin/HandBrakeCLI'
# Absolute path to the ffprobe executable
FFPROBE_PATH = '/home/dsorber/ffprobe'
# The ffprobe command used to get information about the video file
FFPROBE_CMD = '{:s} -show_streams -show_entries stream=width,height,'\
'display_aspect_ratio,r_frame_rate,bit_rate,codec_type,'\
'codec_name -of json -v quiet -i "{:s}"'
# Command server port
COMMAND_SERVER_PORT = 8081
software/hbkcd/hbkcd/daemon.py
import threading
import time
from hbkcd.config import *
from hbkcd.command_server import StoppableHTTPServer, CommandHandler
from hbkcd.queue import ENCODE_QUEUE
# ~ from hbkcd.queue import ENCODE_QUEUE
#
# HbkCD - Handbrake Control Daemon
#
###############################################################################
# Configuration Options
###############################################################################
LOGFILE_PATH = '/var/log/hbkcd/hbkcd.log'
LOG_FORMAT_STR = '%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s|%(message)s'
LOG_DATE_FORMAT_STR = '%a %b %d %H:%M:%S %Y'
DEFAULT_LOG_LEVEL = logging.DEBUG
# Absolute path the to the HandbrakeCLI executable
HANDBRAKE_PATH = '/opt/local/bin/HandBrakeCLI'
# Command server port
COMMAND_SERVER_PORT = 8081
def __test_executable(exe_name, exe_path):
""" Helper function to cut down on code duplication.
"""
# Make sure specified exe path exists and is a file
if not os.path.isfile(exe_path):
logging.error('{:s} does not exist at path: {:s}'
.format(exe_name, exe_path))
return False
# Make sure the specified exe file is executable
if not os.access(exe_path, os.X_OK):
logging.error('{:s} exists but is not executable ({:s})'
.format(exe_name, exe_path))
return False
return True
def check_configuration():
""" Check runtime parameters for correctness before attempting to use them.
"""
# Make sure specified HandbrakeCLI path exists and is a file
if not os.path.isfile(HANDBRAKE_PATH):
logging.error('Handbrake does not exist at path: {:s}'
.format(HANDBRAKE_PATH))
return False
# Make sure the specified HandbrakeCLI file is executable
if not os.access(HANDBRAKE_PATH, os.X_OK):
logging.error('Handbrake exists but is not executable ({:s})'
.format(HANDBRAKE_PATH))
return False
# Check for both the Handbrake and ffprobe executables
for exe in [('Handbrake', HANDBRAKE_PATH), ('ffprobe', FFPROBE_PATH)]:
if not __test_executable(exe[0], exe[1]):
return False
logging.debug('Runtime configuration checks OKAY')
software/hbkcd/hbkcd/queue.py
import datetime
import json
import os
import subprocess
ENCODE_QUEUE = []
from hbkcd.config import *
def format_ds(ds):
""" Helper function for formatting datetime stamps
"""
if ds is None:
return ''
else:
return ds.strftime('%m/%d/%Y %H:%M:%S')
class EncodeQueue(object):
def __init__(self):
self.queue = []
def add(self, item):
# Add item then set position
logging.info('Adding: {:s} -- {}x{} at {} fps'.format(item.path,
item.width,
item.height,
item.framerate))
self.queue.append(item)
position = len(self.queue)
item.position = position
def remove(self, idx):
# TODO: implement removal
pass
def get_list(self):
""" Get queue as a list of dicts that can be converted to JSON easily
"""
return [item.get_dict_rep() for item in self.queue]
class QueueItem(object):
def __init__(self, path):
self.path = path
self.filename = os.path.basename(path)
self.position = None
self.in_progress = False
# Video
self.width = None
self.height = None
self.framerate = None
# Audio
self.convert_audio = False
self.time_added = datetime.datetime.now()
self.encode_started = None
self.encode_finished = None
self.encode_done = None
def probe(self):
# Fill in ffprobe command
cmd = FFPROBE_CMD.format(FFPROBE_PATH, self.path)
proc = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
probe_okay = True
if proc.returncode == 0:
# Parse the output
output = json.loads(proc.stdout)
# TODO: consider the posibility of more than 2 streams
# (would handbrake handle this???)
for stream_info in output['streams']:
if stream_info['codec_type'] == 'video':
# Grab video parameters
self.width = stream_info['width']
self.height = stream_info['height']
# Frame rate is a tricky devil
rate_ratio = stream_info['r_frame_rate'].split('/')
framerate = float(rate_ratio[0]) / float(rate_ratio[1])
framerate = round(framerate, 2)
# Determine if there is a fractional part
if (framerate - int(framerate)) == 0:
self.framerate = str(int(framerate))
else:
self.framerate = str(framerate)
elif stream_info['codec_type'] == 'audio':
# For now we're converting everything to AAC
self.convert_audio = stream_info['codec_name'] != 'aac'
else:
probe_okay = False
logging.error('ffprobe failed: {:s}'.format(
proc.stderr.decode('utf-8')))
return probe_okay
def get_dict_rep(self):
return {'position': self.position,
'path': self.path,
'in_progress': self.in_progress,
'width': self.width,
'height': self.height,
'framerate': self.framerate,
'convert_audio': self.convert_audio,
'time_added': format_ds(self.time_added),
'encode_started': format_ds(self.encode_started),
'encode_finished': format_ds(self.encode_finished)}
# Does this need to be protected?
ENCODE_QUEUE = EncodeQueue()
software/hbkcd/setup.py
setup(
name = 'hbkcd',
version = '0.0.1',
version = '0.0.2',
description = 'HandBrake Control Daemon',
packages = find_packages(),
python_requires = '>=3.6.0',
zip_safe = True
)

Also available in: Unified diff