Revision a5afc963
Added by David Sorber over 7 years ago
| software/hbkcd/hbkcd/command_server.py | ||
|---|---|---|
|
response_dict['message'] = 'Command format invalid'
|
||
|
|
||
|
if 'type' in content:
|
||
|
# Delegate potentiall valid commands for additional handling
|
||
|
# Delegate potentially valid commands for additional handling
|
||
|
if content['type'] == 'add':
|
||
|
response = self.handle_add_cmd(content)
|
||
|
response_dict['success'] = response[0]
|
||
|
response_dict['message'] = response[1]
|
||
|
elif content['type'] == 'remove':
|
||
|
response = self.handle_remove_cmd(content)
|
||
|
response_dict['success'] = response[0]
|
||
|
response_dict['message'] = response[1]
|
||
|
|
||
|
except Exception as exp:
|
||
|
response_dict['success'] = False
|
||
| ... | ... | |
|
def handle_add_cmd(self, command_data):
|
||
|
""" Handler for the "add" command
|
||
|
"""
|
||
|
if 'path' not in command_data:
|
||
|
if 'path' not in command_data or 'output' not in command_data:
|
||
|
return False, 'Command format invalid'
|
||
|
|
||
|
# Check if the path actually exists:
|
||
| ... | ... | |
|
msg = msg.format(path, mimetype)
|
||
|
logging.error(msg)
|
||
|
return False, msg
|
||
|
|
||
|
# Check output default assume that the path is a direct file name
|
||
|
output_path = command_data['output']
|
||
|
if output_path[-4:].lower() not in ['.mkv', '.mp4']:
|
||
|
# Assume output directory, name output based on input filename
|
||
|
if not os.path.isdir(output_path):
|
||
|
try:
|
||
|
os.makedirs(output_path, exist_ok=True)
|
||
|
except PermissionError:
|
||
|
msg = 'Unable to create output directory: {:s}'
|
||
|
msg = msg.format(output_path)
|
||
|
logging.error(msg)
|
||
|
return False, msg
|
||
|
|
||
|
# Always default to Matroska output
|
||
|
filename = os.path.basename(path)
|
||
|
output_filename = '{:s}.mkv'.format(os.path.splitext(filename)[0])
|
||
|
|
||
|
# Build final output path
|
||
|
output_path = os.path.join(output_path, output_filename)
|
||
|
|
||
|
# Create a QueueItem instance and then probe the file to retrieve its
|
||
|
# attributes
|
||
|
item = QueueItem(path)
|
||
|
item = QueueItem(path, output_path)
|
||
|
if not item.probe():
|
||
|
msg = 'An error ocurred while probing the video file'
|
||
|
logging.error(msg)
|
||
| ... | ... | |
|
|
||
|
return True, 'Added to queue'
|
||
|
|
||
|
def handle_remove_cmd(self, command_data):
|
||
|
""" Handler for the "remove" command
|
||
|
"""
|
||
|
if 'position' not in command_data:
|
||
|
return False, 'Command format invalid'
|
||
|
|
||
|
position = int(command_data['position'])
|
||
|
|
||
|
success = ENCODE_QUEUE.remove(position - 1)
|
||
|
msg = 'Removed element {:d}'.format(position)
|
||
|
if not success:
|
||
|
msg = 'Failed to remove element {:d}'.format(position)
|
||
|
|
||
|
return success, msg
|
||
|
|
||
|
def main():
|
||
|
print('starting server...')
|
||
| software/hbkcd/hbkcd/config.py | ||
|---|---|---|
|
'-f', 'av_mkv', '-e', 'x265', '--encoder-preset', 'veryslow',
|
||
|
'--encoder-level', 'auto', '-q', '24', '-2', '-T', '-r',
|
||
|
'23.98', '-E', 'copy:aac']
|
||
|
|
||
|
HANDBRAKE_CMD_INPUT_IDX = 2
|
||
|
HANDBRAKE_CMD_OUTPUT_IDX = 4
|
||
|
HANDBRAKE_CMD_FPS_IDX = 18
|
||
|
HANDBRAKE_CMD_AUDIO_IDX = 20
|
||
|
|
||
|
# Absolute path to the ffprobe executable
|
||
|
FFPROBE_PATH = '/home/dsorber/ffprobe'
|
||
| software/hbkcd/hbkcd/daemon.py | ||
|---|---|---|
|
item.encode_started = datetime.datetime.now()
|
||
|
|
||
|
# Start Handbrake process
|
||
|
cmd = HANDBRAKE_CMD
|
||
|
cmd[2] = item.path
|
||
|
|
||
|
cmd = item.build_hbk_cmd()
|
||
|
hbk_proc = subprocess.Popen(cmd,
|
||
|
stderr=subprocess.PIPE,
|
||
|
stdout=subprocess.PIPE,
|
||
| ... | ... | |
|
status_thread.start()
|
||
|
|
||
|
# Let the HandBrake process run and print a message to the log
|
||
|
# every minute
|
||
|
# every 5 minutes
|
||
|
logging.info('Start encode of {:s}'.format(item.path))
|
||
|
ctr = 0
|
||
|
while hbk_proc.poll() is None:
|
||
|
ctr += 1
|
||
|
if ctr % 60 == 0:
|
||
|
logging.info('Encode in progress... {:s}'.format(ctr, item.status))
|
||
|
if ctr % 300 == 0:
|
||
|
logging.info('Encode in progress... {:s}'.format(item.status))
|
||
|
ctr = 0
|
||
|
time.sleep(1)
|
||
|
|
||
| ... | ... | |
|
status_thread.join()
|
||
|
item.in_progress = False
|
||
|
|
||
|
# Calculate elapsed time
|
||
|
elapsed = item.encode_finished - item.encode_started
|
||
|
hours, remainder = divmod(elapsed.total_seconds(), 3600)
|
||
|
minutes, seconds = divmod(remainder, 60)
|
||
|
elapsed_str = '{:02}:{:02}:{:02}'
|
||
|
elapsed_str = elapsed_str.format(int(hours), int(minutes), int(seconds))
|
||
|
|
||
|
# Print success or failure message to the log depending on the
|
||
|
# HandBrake process's return code
|
||
|
if hbk_proc.returncode == 0:
|
||
|
logging.info('Encode of {:s} completed!'.format(item.path))
|
||
|
logging.info('Encode of {:s} completed in {:s}'.format(item.path,
|
||
|
elapsed_str))
|
||
|
else:
|
||
|
logging.error('Error while encoding {:s}'.format(item.path))
|
||
|
|
||
| software/hbkcd/hbkcd/queue.py | ||
|---|---|---|
|
|
||
|
# Re-set positions for all items
|
||
|
ctr = 1
|
||
|
for item in self.queue[]
|
||
|
for item in self.queue:
|
||
|
item.position = ctr
|
||
|
ctr += 1
|
||
|
|
||
| ... | ... | |
|
|
||
|
class QueueItem(object):
|
||
|
|
||
|
def __init__(self, path):
|
||
|
def __init__(self, path, output_file):
|
||
|
self.path = path
|
||
|
self.filename = os.path.basename(path)
|
||
|
self.output_file = output_file
|
||
|
self.position = None
|
||
|
self.in_progress = False
|
||
|
self.status = 'queued'
|
||
| ... | ... | |
|
def get_dict_rep(self):
|
||
|
return {'position': self.position,
|
||
|
'path': self.path,
|
||
|
'output_file': self.output_file,
|
||
|
'in_progress': self.in_progress,
|
||
|
'status': self.status,
|
||
|
'width': self.width,
|
||
| ... | ... | |
|
'encode_finished': format_ds(self.encode_finished)}
|
||
|
|
||
|
def update_status(self, stdout_iter):
|
||
|
logging.info('Update status thread running for: {:s}'.format(self.path))
|
||
|
logging.debug('Update status thread running for: {:s}'.format(self.path))
|
||
|
for stdout_line in stdout_iter:
|
||
|
# Naturally this is very specific parsing for the HBK output
|
||
|
try:
|
||
|
self.status = stdout_line.strip().split(',', 1)[1].strip()
|
||
|
except IndexError:
|
||
|
self.status = stdout_line
|
||
|
logging.info('Update status thread terminating')
|
||
|
|
||
|
|
||
|
logging.debug('Update status thread terminating')
|
||
|
|
||
|
def build_hbk_cmd(self):
|
||
|
""" Build and return templated HandBrake command.
|
||
|
"""
|
||
|
cmd = HANDBRAKE_CMD
|
||
|
cmd[HANDBRAKE_CMD_INPUT_IDX] = self.path
|
||
|
cmd[HANDBRAKE_CMD_OUTPUT_IDX] = self.output_file
|
||
|
cmd[HANDBRAKE_CMD_FPS_IDX] = self.framerate
|
||
|
|
||
|
if self.convert_audio:
|
||
|
# If converting audio use AAC
|
||
|
# TODO: double check this audio settings make sure VBR is used
|
||
|
cmd[HANDBRAKE_CMD_AUDIO_IDX] = 'fdk_aac --aq 5'
|
||
|
|
||
|
return cmd
|
||
|
|
||
|
# Does this need to be protected?
|
||
|
ENCODE_QUEUE = EncodeQueue()
|
||
| software/hbkcd/scripts/hbkcd_add.sh | ||
|---|---|---|
|
#!/bin/bash
|
||
|
ADDRESS="brutus"
|
||
|
PORT="8081"
|
||
|
|
||
|
if [ "$#" -ne 2 ]; then
|
||
|
echo "USAGE: hbkcd_add <path to input file> <path to output file/directory>"
|
||
|
exit -1
|
||
|
fi
|
||
|
|
||
|
# Make sure curl ad jq are installed
|
||
|
command -v curl >/dev/null 2>&1 || { echo >&2 "Please install curl to use this script. Aborting."; exit 1; }
|
||
|
command -v jq >/dev/null 2>&1 || { echo >&2 "Please install jq to use this script. Aborting."; exit 1; }
|
||
|
|
||
|
INPUT_PATH=$(readlink -f $1)
|
||
|
curl -s -X POST -d '{"type": "add", "path": "'$INPUT_PATH'", "output": "'$2'"}' http://$ADDRESS:$PORT/command | jq
|
||
| software/hbkcd/scripts/hbkcd_queue.sh | ||
|---|---|---|
|
#!/bin/bash
|
||
|
ADDRESS="brutus"
|
||
|
PORT="8081"
|
||
|
|
||
|
# Make sure curl ad jq are installed
|
||
|
command -v curl >/dev/null 2>&1 || { echo >&2 "Please install curl to use this script. Aborting."; exit 1; }
|
||
|
command -v jq >/dev/null 2>&1 || { echo >&2 "Please install jq to use this script. Aborting."; exit 1; }
|
||
|
|
||
|
curl -s -X GET http://$ADDRESS:$PORT/queue | jq
|
||
| software/hbkcd/scripts/hbkcd_remove.sh | ||
|---|---|---|
|
#!/bin/bash
|
||
|
ADDRESS="brutus"
|
||
|
PORT="8081"
|
||
|
|
||
|
if [ "$#" -ne 1 ]; then
|
||
|
echo "USAGE: hbkcd_remove <queue position>"
|
||
|
exit -1
|
||
|
fi
|
||
|
|
||
|
# Make sure curl ad jq are installed
|
||
|
command -v curl >/dev/null 2>&1 || { echo >&2 "Please install curl to use this script. Aborting."; exit 1; }
|
||
|
command -v jq >/dev/null 2>&1 || { echo >&2 "Please install jq to use this script. Aborting."; exit 1; }
|
||
|
|
||
|
curl -s -X POST -d '{"type": "remove", "position": "'$1'"}' http://$ADDRESS:$PORT/command | jq
|
||
| software/hbkcd/setup.py | ||
|---|---|---|
|
|
||
|
setup(
|
||
|
name = 'hbkcd',
|
||
|
version = '0.0.3',
|
||
|
version = '0.1.0',
|
||
|
description = 'HandBrake Control Daemon',
|
||
|
packages = find_packages(),
|
||
|
python_requires = '>=3.6.0',
|
||
Add remove command and helper scripts.