|
#!/usr/bin/python3
|
|
import datetime
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
from hbkcd.config import *
|
|
from hbkcd.command_server import StoppableHTTPServer, CommandHandler
|
|
from hbkcd.queue import ENCODE_QUEUE
|
|
|
|
#
|
|
# HbkCD - Handbrake Control Daemon
|
|
#
|
|
|
|
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.
|
|
"""
|
|
|
|
# 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')
|
|
|
|
return True
|
|
|
|
def datetime_diff(dt_start, dt_end):
|
|
""" Helper function to calculate the elapsed time between to datetime
|
|
objects and return in a readable "HH::MM:SS" string format.
|
|
"""
|
|
elapsed = dt_end - dt_start
|
|
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))
|
|
return elapsed_str
|
|
|
|
|
|
def main():
|
|
|
|
# Configure logging before attempting anything serious
|
|
logging.basicConfig(format=LOG_FORMAT_STR,
|
|
datefmt=LOG_DATE_FORMAT_STR,
|
|
filename=LOGFILE_PATH,
|
|
level=DEFAULT_LOG_LEVEL)
|
|
|
|
logging.info('hbkcd started')
|
|
|
|
# Validate runtime configuration before proceeding
|
|
if not check_configuration():
|
|
return -1
|
|
|
|
# Create the command server
|
|
logging.info('Starting command server')
|
|
server = StoppableHTTPServer(('', COMMAND_SERVER_PORT), CommandHandler)
|
|
|
|
# Start the command server in a separate "thread"
|
|
thread = threading.Thread(None, server.run)
|
|
thread.start()
|
|
logging.info('Command server running')
|
|
|
|
# Run the command server until CTRL-C'ed
|
|
try:
|
|
while True:
|
|
time.sleep(1)
|
|
item = ENCODE_QUEUE.get_next()
|
|
if item is None:
|
|
continue
|
|
|
|
# Mark next item as in progress
|
|
item.in_progress = True
|
|
item.encode_started = datetime.datetime.now()
|
|
|
|
# Start Handbrake process
|
|
cmd = item.build_hbk_cmd()
|
|
hbk_proc = subprocess.Popen(cmd,
|
|
stderr=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
encoding='utf-8')
|
|
|
|
# Create an interator to the stdout of the process so we can
|
|
# monitor status
|
|
proc_stdout = iter(hbk_proc.stdout.readline, '')
|
|
status_thread = threading.Thread(target=item.update_status,
|
|
args=(proc_stdout,))
|
|
status_thread.start()
|
|
|
|
# Let the HandBrake process run and print a message to the log
|
|
# every 5 minutes
|
|
logging.info('Start encode of {:s}'.format(item.path))
|
|
ctr = 0
|
|
while hbk_proc.poll() is None:
|
|
ctr += 1
|
|
if ctr % 300 == 0:
|
|
logging.info('Encode in progress... {:s}'.format(item.status))
|
|
ctr = 0
|
|
time.sleep(1)
|
|
|
|
item.encode_finished = datetime.datetime.now()
|
|
status_thread.join()
|
|
item.in_progress = False
|
|
|
|
# Create encode log
|
|
ts_str = item.encode_started.strftime('%Y%m%d_%H%M%S')
|
|
filename = 'encode_{:s}.log'.format(ts_str)
|
|
log_path = os.path.join(LOG_DIR, filename)
|
|
with open(log_path, 'w') as log_file:
|
|
log_file.write(hbk_proc.stderr.read())
|
|
logging.info('Write encode log file: {:s}'.format(log_path))
|
|
|
|
# Calculate elapsed time of encode
|
|
elapsed_str = datetime_diff(item.encode_finished, item.encode_started)
|
|
|
|
# Calculated queued time
|
|
queued_str = datetime_diff(item.encode_finished, item.time_added)
|
|
|
|
# Print success or failure message to the log depending on the
|
|
# HandBrake process's return code
|
|
if hbk_proc.returncode == 0:
|
|
fmt_str = 'Encode of {:s} completed in {:s} (queued for: {:s})'
|
|
logging.info(fmt_str.format(item.path, elapsed_str, queued_str))
|
|
else:
|
|
fmt_str = 'Error while encoding {:s} (queued for: {:s})'
|
|
logging.error(fmt_str.format(item.path, queued_str))
|
|
|
|
# Remove the item from the queue
|
|
ENCODE_QUEUE.remove(0)
|
|
|
|
except KeyboardInterrupt:
|
|
logging.warn('Command server interrupted')
|
|
finally:
|
|
# Shutdown server
|
|
server.shutdown()
|
|
thread.join()
|
|
logging.info('Command server stopped')
|
|
|
|
logging.info('hbkcd stopping')
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|