|
#!/usr/bin/python3
|
|
import datetime
|
|
from email.mime.text import MIMEText
|
|
import logging
|
|
import os
|
|
from smtplib import SMTP_SSL
|
|
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
|
|
from hbkcd.utils import *
|
|
|
|
# These should probably be protected...
|
|
RELAY_EMAIL = 'dsorber.phalanx@gmail.com'
|
|
RELAY_PASSWD = "You'reNotMySupervisor"
|
|
TO_EMAIL = 'baranovich@gmail.com'
|
|
|
|
#
|
|
# HbkCD - Handbrake Control Daemon
|
|
#
|
|
|
|
def send_email(subject, msg_text):
|
|
""" Simple helper function to send an email
|
|
"""
|
|
msg = MIMEText(msg_text, 'plain')
|
|
msg['Subject'] = subject
|
|
msg['To'] = TO_EMAIL
|
|
try:
|
|
conn = SMTP_SSL('smtp.gmail.com')
|
|
conn.set_debuglevel(True)
|
|
conn.login(RELAY_EMAIL, RELAY_PASSWD)
|
|
try:
|
|
conn.sendmail(RELAY_EMAIL, TO_EMAIL, msg.as_string())
|
|
finally:
|
|
conn.close()
|
|
except Exception as exp:
|
|
error = '{:s}: {!r}'.format(type(exp).__name__, exp.args)
|
|
logging.error(error)
|
|
|
|
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_start - dt_end
|
|
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_started, item.time_added)
|
|
|
|
# Print success or failure message to the log depending on the
|
|
# HandBrake process's return code
|
|
status = ''
|
|
if hbk_proc.returncode == 0:
|
|
status = 'COMPLETED SUCCESSFULLY'
|
|
item.final_size = os.stat(item.output_file).st_size
|
|
fmt_str = 'Encode of {:s} completed in {:s} (queued for: {:s})'
|
|
logging.info(fmt_str.format(item.path, elapsed_str, queued_str))
|
|
else:
|
|
status = 'ERROR'
|
|
fmt_str = 'Error while encoding {:s} (queued for: {:s})'
|
|
logging.error(fmt_str.format(item.path, queued_str))
|
|
|
|
# If configured send encode complete email
|
|
if ENABLE_ENCODE_COMPLETE_EMAILS:
|
|
subject = 'hbkcd Encode Complete'
|
|
msg = '{:s}\n\nEncoded: {:s}\nInitial Size: {:s}\n\n'\
|
|
'Output: {:s}\nFinal Size: {:s} ({:.2f}%)\n\n'\
|
|
'Duration: {:s}\nQueued time: {:s}\nEncode log: {:s}\n'
|
|
|
|
ratio = (item.final_size / (item.initial_size * 1.0))
|
|
percent = ratio * 100
|
|
send_email(subject, msg.format(status, item.path,
|
|
pp_filesize(item.initial_size),
|
|
item.output_file,
|
|
pp_filesize(item.final_size),
|
|
percent, elapsed_str, queued_str,
|
|
log_path))
|
|
logging.debug('Sent encode complete email')
|
|
|
|
# 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())
|