Revision d6a44a2b
Added by David Sorber over 7 years ago
| software/hbkcd/hbkcd.py | ||
|---|---|---|
|
#!/usr/bin/python3
|
||
|
import logging
|
||
|
import os
|
||
|
import sys
|
||
|
|
||
|
#
|
||
|
# 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'
|
||
|
|
||
|
# Path to queue file. Note that there is nothing special about this file, it
|
||
|
# is simply a text file containing a list of paths to video files to re-enocde
|
||
|
QUEUE_FILE_PATH = '/home/dsorber/encode_queue'
|
||
|
|
||
|
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
|
||
|
|
||
|
# Make sure specified encode queue file exists and is a file
|
||
|
if not os.path.isfile(QUEUE_FILE_PATH):
|
||
|
logging.error('The specified encode queue file ({:s}) does not exist, '
|
||
|
'please create it to proceed'.format(QUEUE_FILE_PATH))
|
||
|
return False
|
||
|
|
||
|
logging.debug('Runtime configuration checks OKAY')
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
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)
|
||
|
|
||
|
# ~ print('foobar', file=sys.stderr)
|
||
|
logging.info('hbkcf started')
|
||
|
|
||
|
# Validate runtime configuration before proceeding
|
||
|
if not check_configuration():
|
||
|
return -1
|
||
|
|
||
|
return 0
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main())
|
||
| software/hbkcd/hbkcd/command_server.py | ||
|---|---|---|
|
#!/usr/bin/python3
|
||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||
|
import logging
|
||
|
import json
|
||
|
import os
|
||
|
import sys
|
||
|
import time
|
||
|
import threading
|
||
|
|
||
|
from hbkcd.queue import ENCODE_QUEUE
|
||
|
|
||
|
# https://blog.gocept.com/2011/08/04/shutting-down-an-httpserver/
|
||
|
# https://docs.aws.amazon.com/polly/latest/dg/example-Python-server-code.html
|
||
|
|
||
|
class StoppableHTTPServer(HTTPServer):
|
||
|
|
||
|
def run(self):
|
||
|
try:
|
||
|
self.serve_forever()
|
||
|
except KeyboardInterrupt:
|
||
|
pass
|
||
|
finally:
|
||
|
# Clean-up server (close socket, etc.)
|
||
|
self.server_close()
|
||
|
|
||
|
|
||
|
class CommandHandler(BaseHTTPRequestHandler):
|
||
|
|
||
|
def log_message(format, *args):
|
||
|
logging.debug('Request: {} -- {}'.format(args[1], args[2]))
|
||
|
# ~ print('REQUEST: {} -- {}'.format(args[1], args[2]))
|
||
|
|
||
|
def do_GET(self):
|
||
|
""" Handle HTTP GET requests
|
||
|
"""
|
||
|
# Strip off leading and/or trailing slashes
|
||
|
path = self.path.strip('/')
|
||
|
|
||
|
# Defaults
|
||
|
response_code = 404
|
||
|
response_type = 'text/html'
|
||
|
response_data = ''
|
||
|
|
||
|
# Send response status code
|
||
|
if path == 'queue':
|
||
|
response_code = 200
|
||
|
response_type = 'application/json'
|
||
|
response_dict = {'queue': ENCODE_QUEUE}
|
||
|
response_data = json.dumps(response_dict).encode('utf-8')
|
||
|
|
||
|
elif not path:
|
||
|
response_code = 200
|
||
|
response = 'HandBrake Control Daemon - control interface'
|
||
|
response_data = response.encode('utf-8')
|
||
|
|
||
|
# Send response and data, if any
|
||
|
self.send_response(response_code)
|
||
|
|
||
|
if response_data:
|
||
|
self.send_header('Content-type', response_type)
|
||
|
|
||
|
self.end_headers()
|
||
|
|
||
|
if response_data:
|
||
|
self.wfile.write(response_data)
|
||
|
|
||
|
return
|
||
|
|
||
|
def do_POST(self):
|
||
|
""" Handle HTTP POST requests
|
||
|
"""
|
||
|
# Strip off leading and/or trailing slashes
|
||
|
path = self.path.strip('/')
|
||
|
|
||
|
# Defaults
|
||
|
response_code = 200
|
||
|
response_type = 'application/json'
|
||
|
response_dict = {'success': False, 'message': 'Invalid request'}
|
||
|
|
||
|
# Only attempt to parse things sent to /command
|
||
|
if path == 'command':
|
||
|
try:
|
||
|
content_length = int(self.headers['Content-Length'])
|
||
|
raw_content = self.rfile.read(content_length)
|
||
|
# ~ print(type(raw_content))
|
||
|
content = json.loads(raw_content)
|
||
|
|
||
|
# Default to invalid command
|
||
|
response_dict['message'] = 'Command format invalid'
|
||
|
|
||
|
if 'type' in content:
|
||
|
# Delegate potentiall 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]
|
||
|
|
||
|
|
||
|
except Exception as exp:
|
||
|
response_dict['success'] = False
|
||
|
error = '{:s}: {!r}'.format(type(exp).__name__, exp.args)
|
||
|
response_dict['message'] = error
|
||
|
|
||
|
# Send response
|
||
|
self.send_response(response_code)
|
||
|
self.send_header('Content-type', response_type)
|
||
|
self.end_headers()
|
||
|
self.wfile.write(json.dumps(response_dict).encode('utf-8'))
|
||
|
|
||
|
return
|
||
|
|
||
|
def handle_add_cmd(self, command_data):
|
||
|
""" Handler for the "add" command
|
||
|
"""
|
||
|
if 'path' not in command_data:
|
||
|
return False, 'Command format invalid'
|
||
|
|
||
|
# 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)
|
||
|
|
||
|
# TODO: check if file is video file
|
||
|
# TODO: get width, heigh and frame rate using ffprobe
|
||
|
|
||
|
ENCODE_QUEUE.append(path)
|
||
|
|
||
|
return True, 'Added to queue'
|
||
|
|
||
|
|
||
|
def main():
|
||
|
print('starting server...')
|
||
|
|
||
|
server = StoppableHTTPServer(('', 8081), CommandHandler)
|
||
|
|
||
|
# Start processing requests
|
||
|
thread = threading.Thread(None, server.run)
|
||
|
thread.start()
|
||
|
|
||
|
# Run fo' eva'
|
||
|
try:
|
||
|
while True:
|
||
|
time.sleep(1)
|
||
|
except KeyboardInterrupt:
|
||
|
print('[server interrupted]')
|
||
|
finally:
|
||
|
# Shutdown server
|
||
|
server.shutdown()
|
||
|
thread.join()
|
||
|
|
||
|
print('fin')
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main())
|
||
| software/hbkcd/hbkcd/daemon.py | ||
|---|---|---|
|
#!/usr/bin/python3
|
||
|
import logging
|
||
|
import os
|
||
|
import sys
|
||
|
import threading
|
||
|
import time
|
||
|
|
||
|
from hbkcd.command_server import StoppableHTTPServer, CommandHandler
|
||
|
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 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
|
||
|
|
||
|
logging.debug('Runtime configuration checks OKAY')
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
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)
|
||
|
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())
|
||
| software/hbkcd/hbkcd/queue.py | ||
|---|---|---|
|
|
||
|
ENCODE_QUEUE = []
|
||
|
|
||
|
class QueueItem(object):
|
||
|
|
||
|
def __init__(self, path):
|
||
|
self.path = path
|
||
| software/hbkcd/server.py | ||
|---|---|---|
|
#!/usr/bin/python3
|
||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||
|
import json
|
||
|
from multiprocessing import Process
|
||
|
import sys
|
||
|
import time
|
||
|
|
||
|
# https://blog.gocept.com/2011/08/04/shutting-down-an-httpserver/
|
||
|
# https://docs.aws.amazon.com/polly/latest/dg/example-Python-server-code.html
|
||
|
|
||
|
class CommandServer(Process):
|
||
|
|
||
|
def __init__(self):
|
||
|
Process.__init__(self)
|
||
|
|
||
|
self.server_address = ('127.0.0.1', 8081)
|
||
|
self.httpd = HTTPServer(self.server_address, CommandHandler)
|
||
|
self.keep_running = True
|
||
|
|
||
|
def stop(self):
|
||
|
print('stop called')
|
||
|
self.keep_running = False
|
||
|
# ~ if self.httpd:
|
||
|
# ~ print('pre shutdown')
|
||
|
# ~ self.httpd.shutdown()
|
||
|
# ~ print('post shutdown')
|
||
|
self.httpd.shutdown()
|
||
|
self.httpd.socket.close()
|
||
|
# ~ self.httpd.server_close()
|
||
|
print('stop returning')
|
||
|
|
||
|
def run(self):
|
||
|
print('command server running')
|
||
|
|
||
|
# ~ while self.keep_running:
|
||
|
self.httpd.serve_forever()
|
||
|
|
||
|
# ~ while self.keep_running:
|
||
|
# ~ self.httpd.handle_request()
|
||
|
|
||
|
print('command server stopping')
|
||
|
|
||
|
|
||
|
# HTTPRequestHandler class
|
||
|
class CommandHandler(BaseHTTPRequestHandler):
|
||
|
|
||
|
# GET
|
||
|
def do_GET(self):
|
||
|
# Strip off leading and/or trailing slashes
|
||
|
path = self.path.strip('/')
|
||
|
|
||
|
# Defaults
|
||
|
response_code = 400
|
||
|
response_type = 'text/html'
|
||
|
response_data = ''
|
||
|
|
||
|
# Send response status code
|
||
|
if path == 'queue':
|
||
|
response_code = 200
|
||
|
response_dict = {'queue': ['alpha', 'bravo', 'charlie']}
|
||
|
response_data = json.dumps(response_dict).encode()
|
||
|
|
||
|
elif not path:
|
||
|
response_code = 200
|
||
|
response = 'Why hello there'
|
||
|
response_data = response.encode('utf-8')
|
||
|
|
||
|
# Send response and data, if any
|
||
|
self.send_response(response_code)
|
||
|
|
||
|
if response_data:
|
||
|
self.send_header('Content-type', response_type)
|
||
|
|
||
|
self.end_headers()
|
||
|
|
||
|
if response_data:
|
||
|
self.wfile.write(response_data)
|
||
|
|
||
|
return
|
||
|
|
||
|
|
||
|
def main():
|
||
|
print('starting server...')
|
||
|
|
||
|
# Server settings
|
||
|
# Choose port 8080, for port 80, which is normally used for a http server,
|
||
|
# you need root access
|
||
|
# ~ server_address = ('127.0.0.1', 8081)
|
||
|
# ~ httpd = HTTPServer(server_address, CommandHandler)
|
||
|
# ~ print('running server...')
|
||
|
# ~ httpd.serve_forever()
|
||
|
|
||
|
cmd_server = CommandServer()
|
||
|
cmd_server.start()
|
||
|
|
||
|
# Run for a while
|
||
|
for idx in range(20):
|
||
|
print(idx)
|
||
|
time.sleep(1)
|
||
|
|
||
|
# Simluate a clean shutdown
|
||
|
cmd_server.stop()
|
||
|
print('post stop')
|
||
|
cmd_server.join()
|
||
|
|
||
|
print('fin')
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main())
|
||
| software/hbkcd/setup.py | ||
|---|---|---|
|
#!/usr/bin/python3
|
||
|
from setuptools import setup, find_packages
|
||
|
|
||
|
setup(
|
||
|
name = 'hbkcd',
|
||
|
version = '0.0.1',
|
||
|
description = 'HandBrake Control Daemon',
|
||
|
packages = find_packages(),
|
||
|
)
|
||
|
|
||
Adding very basic command server and module packaging.