feat(playout): enhance playout logging (#1495)
Some initial work on modernizing the playout app. This replace any custom logger or logging based logger with the logging tools from libretime_shared.logging and loguru. Removed all the thread/function assigned logger (self.logger = ...), as this makes it part of the logic (passing logger though function args) as it should not. Of a dedicated logger is required for a specific task, it should use the create_task_logger function. - refactor: remove dead code - refactor: remove py2 specific fix - feat: remove unused test command - feat: setup shared cli and logging tools - feat: replace logging with loguru - feat: setup shared cli and logging tools for notify - fix: warn method deos not exist - feat: make cli setup the entrypoint - fix: install shared modules globally in production use extra_requires to load local packages in dev environement - feat: configure log path in systemd service - feat: default behavior is to log to console only - feat: create log dir during install - chore: add comment - fix: don't create useless dir in install - fix: move notify logs to /var/log/libretime dir - fix: update setup_logger attrs - style: linting - fix: replace verbosity flag with log-level flag - feat: use shared logging tool in liquidsoap - fix: pass logger down to api client - feat: allow custom log_filepath in liquidsoap config - chore: add pylintrc to playout - refactor: fix pylint errors - feat: set liquidsoap log filepath in systemd service - fix: missing setup entrypoint update BREAKING CHANGE: for playout and liquidsoap the default log file path changed to None and will only log to the console when developing / testing. Unless you are running the app as a systemd service (production) the default logs filepaths changed: from "/var/log/airtime/pypo/pypo.log" to "/var/log/libretime/playout.log" and from "/var/log/airtime/pypo-liquidsoap/ls_script.log" to "/var/log/libretime/liquidsoap.log" BREAKING CHANGE: for playout-notify the default log file path changed from "/var/log/airtime/pypo/notify.log" to "/var/log/libretime/playout-notify.log"
This commit is contained in:
parent
56a3875e2d
commit
5c72f714a8
11
install
11
install
|
@ -93,6 +93,12 @@ skip_postgres=0
|
||||||
skip_rabbitmq=0
|
skip_rabbitmq=0
|
||||||
default_value="Y"
|
default_value="Y"
|
||||||
|
|
||||||
|
# <user:group> <path>
|
||||||
|
mkdir_and_chown() {
|
||||||
|
mkdir -p "$2"
|
||||||
|
chown -R "$1" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
function verbose() {
|
function verbose() {
|
||||||
if [[ ${_v} -eq 1 ]]; then
|
if [[ ${_v} -eq 1 ]]; then
|
||||||
echo -e "$@"
|
echo -e "$@"
|
||||||
|
@ -1027,12 +1033,17 @@ if [ ! -d /var/log/airtime ]; then
|
||||||
|
|
||||||
verbose "\n * Creating /var/log/airtime"
|
verbose "\n * Creating /var/log/airtime"
|
||||||
loudCmd "mkdir -p /var/log/airtime"
|
loudCmd "mkdir -p /var/log/airtime"
|
||||||
|
mkdir_and_chown "$web_user:$web_user" "/var/log/libretime"
|
||||||
|
|
||||||
verbose "\n * Copying logrotate files..."
|
verbose "\n * Copying logrotate files..."
|
||||||
loudCmd "cp ${AIRTIMEROOT}/legacy/build/airtime-php.logrotate /etc/logrotate.d/airtime-php"
|
loudCmd "cp ${AIRTIMEROOT}/legacy/build/airtime-php.logrotate /etc/logrotate.d/airtime-php"
|
||||||
loudCmd "cp ${AIRTIMEROOT}/playout/install/logrotate/libretime-liquidsoap.conf /etc/logrotate.d/libretime-liquidsoap"
|
loudCmd "cp ${AIRTIMEROOT}/playout/install/logrotate/libretime-liquidsoap.conf /etc/logrotate.d/libretime-liquidsoap"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
verbose "\n * Installing Shared..."
|
||||||
|
loudCmd "$pip_cmd install ${AIRTIMEROOT}/shared"
|
||||||
|
verbose "...Done"
|
||||||
|
|
||||||
verbose "\n * Installing API client..."
|
verbose "\n * Installing API client..."
|
||||||
loudCmd "$pip_cmd install ${AIRTIMEROOT}/api_client"
|
loudCmd "$pip_cmd install ${AIRTIMEROOT}/api_client"
|
||||||
verbose "...Done"
|
verbose "...Done"
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=missing-module-docstring,
|
||||||
|
missing-class-docstring,
|
||||||
|
missing-function-docstring,
|
|
@ -2,7 +2,7 @@ all: lint
|
||||||
|
|
||||||
include ../tools/python.mk
|
include ../tools/python.mk
|
||||||
|
|
||||||
PIP_INSTALL := --editable .
|
PIP_INSTALL := --editable .[dev]
|
||||||
PYLINT_ARG := libretime_liquidsoap libretime_playout
|
PYLINT_ARG := libretime_liquidsoap libretime_playout
|
||||||
MYPY_ARG := libretime_liquidsoap libretime_playout
|
MYPY_ARG := libretime_liquidsoap libretime_playout
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/var/log/airtime/pypo-liquidsoap/ls_script.log {
|
/var/log/libretime/liquidsoap.log {
|
||||||
compress
|
compress
|
||||||
rotate 10
|
rotate 10
|
||||||
size 1000k
|
size 1000k
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
Description=Libretime Liquidsoap Service
|
Description=Libretime Liquidsoap Service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
Environment=LIBRETIME_LOG_FILEPATH=/var/log/libretime/liquidsoap.log
|
||||||
|
|
||||||
ExecStart=/usr/local/bin/libretime-liquidsoap
|
ExecStart=/usr/local/bin/libretime-liquidsoap
|
||||||
User=libretime-playout
|
User=libretime-playout
|
||||||
Group=libretime-playout
|
Group=libretime-playout
|
||||||
|
|
|
@ -3,6 +3,8 @@ Description=Libretime Playout Service
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
Environment=LIBRETIME_LOG_FILEPATH=/var/log/libretime/playout.log
|
||||||
|
|
||||||
ExecStart=/usr/local/bin/libretime-playout
|
ExecStart=/usr/local/bin/libretime-playout
|
||||||
User=libretime-pypo
|
User=libretime-pypo
|
||||||
Group=libretime-pypo
|
Group=libretime-pypo
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from libretime_api_client.version1 import AirtimeApiClient
|
from libretime_api_client.version1 import AirtimeApiClient
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
def generate_liquidsoap_config(ss):
|
def generate_liquidsoap_config(ss, log_filepath: Optional[Path]):
|
||||||
data = ss["msg"]
|
data = ss["msg"]
|
||||||
fh = open("/etc/airtime/liquidsoap.cfg", "w")
|
fh = open("/etc/airtime/liquidsoap.cfg", "w")
|
||||||
fh.write("################################################\n")
|
fh.write("################################################\n")
|
||||||
|
@ -34,31 +36,31 @@ def generate_liquidsoap_config(ss):
|
||||||
fh.write("ignore(%s)\n" % key)
|
fh.write("ignore(%s)\n" % key)
|
||||||
|
|
||||||
auth_path = os.path.dirname(os.path.realpath(__file__))
|
auth_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
fh.write('log_file = "/var/log/airtime/pypo-liquidsoap/<script>.log"\n')
|
if log_filepath is not None:
|
||||||
|
fh.write(f'log_file = "{log_filepath.resolve()}"\n')
|
||||||
fh.write('auth_path = "%s/liquidsoap_auth.py"\n' % auth_path)
|
fh.write('auth_path = "%s/liquidsoap_auth.py"\n' % auth_path)
|
||||||
fh.close()
|
fh.close()
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run(log_filepath: Optional[Path]):
|
||||||
logging.basicConfig(format="%(message)s")
|
|
||||||
attempts = 0
|
attempts = 0
|
||||||
max_attempts = 10
|
max_attempts = 10
|
||||||
successful = False
|
successful = False
|
||||||
|
|
||||||
while not successful:
|
while not successful:
|
||||||
try:
|
try:
|
||||||
ac = AirtimeApiClient(logging.getLogger())
|
ac = AirtimeApiClient(logger)
|
||||||
ss = ac.get_stream_setting()
|
ss = ac.get_stream_setting()
|
||||||
generate_liquidsoap_config(ss)
|
generate_liquidsoap_config(ss, log_filepath)
|
||||||
successful = True
|
successful = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Unable to connect to the Airtime server.")
|
print("Unable to connect to the Airtime server.")
|
||||||
logging.error(str(e))
|
logger.error(str(e))
|
||||||
logging.error("traceback: %s", traceback.format_exc())
|
logger.error("traceback: %s", traceback.format_exc())
|
||||||
if attempts == max_attempts:
|
if attempts == max_attempts:
|
||||||
logging.error("giving up and exiting...")
|
logger.error("giving up and exiting...")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
logging.info("Retrying in 3 seconds...")
|
logger.info("Retrying in 3 seconds...")
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
attempts += 1
|
attempts += 1
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
""" Runs Airtime liquidsoap
|
""" Runs Airtime liquidsoap
|
||||||
"""
|
"""
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from libretime_playout import pure
|
import click
|
||||||
|
from libretime_shared.cli import cli_logging_options
|
||||||
|
from libretime_shared.logging import level_from_name, setup_logger
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from . import generate_liquidsoap_cfg
|
from . import generate_liquidsoap_cfg
|
||||||
|
|
||||||
PYPO_HOME = "/var/tmp/airtime/pypo/"
|
PYPO_HOME = "/var/tmp/airtime/pypo/"
|
||||||
|
|
||||||
|
|
||||||
def run():
|
@click.command()
|
||||||
"""Entry-point for this application"""
|
@cli_logging_options
|
||||||
print("Airtime Liquidsoap")
|
def cli(log_level: int, log_filepath: Optional[Path]):
|
||||||
parser = argparse.ArgumentParser()
|
"""
|
||||||
parser.add_argument("-d", "--debug", help="run in debug mode", action="store_true")
|
Run liquidsoap.
|
||||||
args = parser.parse_args()
|
"""
|
||||||
|
log_level = level_from_name(log_level)
|
||||||
|
setup_logger(log_level, log_filepath)
|
||||||
|
|
||||||
os.environ["HOME"] = PYPO_HOME
|
os.environ["HOME"] = PYPO_HOME
|
||||||
|
|
||||||
if args.debug:
|
generate_liquidsoap_cfg.run(log_filepath)
|
||||||
logging.basicConfig(level=getattr(logging, "DEBUG", None))
|
# check liquidsoap version so we can run a scripts matching the liquidsoap minor version
|
||||||
|
|
||||||
generate_liquidsoap_cfg.run()
|
|
||||||
""" check liquidsoap version so we can run a scripts matching the liquidsoap minor version """
|
|
||||||
liquidsoap_version = subprocess.check_output(
|
liquidsoap_version = subprocess.check_output(
|
||||||
"liquidsoap 'print(liquidsoap.version) shutdown()'",
|
"liquidsoap 'print(liquidsoap.version) shutdown()'",
|
||||||
shell=True,
|
shell=True,
|
||||||
|
@ -40,7 +42,8 @@ def run():
|
||||||
"--verbose",
|
"--verbose",
|
||||||
script_path,
|
script_path,
|
||||||
]
|
]
|
||||||
if args.debug:
|
if log_level.is_debug():
|
||||||
print(f"Liquidsoap {liquidsoap_version} using script: {script_path}")
|
|
||||||
exec_args.append("--debug")
|
exec_args.append("--debug")
|
||||||
|
|
||||||
|
logger.debug(f"Liquidsoap {liquidsoap_version} using script: {script_path}")
|
||||||
os.execl(*exec_args)
|
os.execl(*exec_args)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import base64
|
import base64
|
||||||
import logging
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
@ -10,20 +9,17 @@ from threading import Thread
|
||||||
|
|
||||||
import defusedxml.minidom
|
import defusedxml.minidom
|
||||||
from libretime_api_client import version1 as api_client
|
from libretime_api_client import version1 as api_client
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
class ListenerStat(Thread):
|
class ListenerStat(Thread):
|
||||||
|
|
||||||
HTTP_REQUEST_TIMEOUT = 30 # 30 second HTTP request timeout
|
HTTP_REQUEST_TIMEOUT = 30 # 30 second HTTP request timeout
|
||||||
|
|
||||||
def __init__(self, config, logger=None):
|
def __init__(self, config):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.config = config
|
self.config = config
|
||||||
self.api_client = api_client.AirtimeApiClient()
|
self.api_client = api_client.AirtimeApiClient()
|
||||||
if logger is None:
|
|
||||||
self.logger = logging.getLogger()
|
|
||||||
else:
|
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
def get_node_text(self, nodelist):
|
def get_node_text(self, nodelist):
|
||||||
rc = []
|
rc = []
|
||||||
|
@ -130,7 +126,7 @@ class ListenerStat(Thread):
|
||||||
try:
|
try:
|
||||||
self.update_listener_stat_error(v["mount"], str(e))
|
self.update_listener_stat_error(v["mount"], str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Exception: %s", e)
|
logger.error("Exception: %s", e)
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
@ -155,27 +151,6 @@ class ListenerStat(Thread):
|
||||||
if stats:
|
if stats:
|
||||||
self.push_stream_stats(stats)
|
self.push_stream_stats(stats)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Exception: %s", e)
|
logger.error("Exception: %s", e)
|
||||||
|
|
||||||
time.sleep(120)
|
time.sleep(120)
|
||||||
self.logger.info("ListenerStat thread exiting")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# create logger
|
|
||||||
logger = logging.getLogger("std_out")
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
# create console handler and set level to debug
|
|
||||||
# ch = logging.StreamHandler()
|
|
||||||
# ch.setLevel(logging.DEBUG)
|
|
||||||
# create formatter
|
|
||||||
formatter = logging.Formatter(
|
|
||||||
"%(asctime)s - %(name)s - %(lineno)s - %(levelname)s - %(message)s"
|
|
||||||
)
|
|
||||||
# add formatter to ch
|
|
||||||
# ch.setFormatter(formatter)
|
|
||||||
# add ch to logger
|
|
||||||
# logger.addHandler(ch)
|
|
||||||
|
|
||||||
# ls = ListenerStat(logger=logger)
|
|
||||||
# ls.run()
|
|
||||||
|
|
|
@ -3,9 +3,6 @@ Python part of radio playout (pypo)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import importlib
|
|
||||||
import locale
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
|
@ -13,17 +10,18 @@ import sys
|
||||||
import telnetlib
|
import telnetlib
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from optparse import OptionParser
|
from pathlib import Path
|
||||||
|
from queue import Queue
|
||||||
|
from threading import Lock
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import click
|
||||||
from configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
from libretime_api_client.version1 import AirtimeApiClient as ApiClient
|
from libretime_api_client.version1 import AirtimeApiClient as ApiClient
|
||||||
|
from libretime_shared.cli import cli_logging_options
|
||||||
try:
|
from libretime_shared.config import DEFAULT_ENV_PREFIX
|
||||||
from queue import Queue
|
from libretime_shared.logging import level_from_name, setup_logger
|
||||||
except ImportError: # Python 2.7.5 (CentOS 7)
|
from loguru import logger
|
||||||
from queue import Queue
|
|
||||||
|
|
||||||
from threading import Lock
|
|
||||||
|
|
||||||
from . import pure
|
from . import pure
|
||||||
from .listenerstat import ListenerStat
|
from .listenerstat import ListenerStat
|
||||||
|
@ -35,57 +33,6 @@ from .pypopush import PypoPush
|
||||||
from .recorder import Recorder
|
from .recorder import Recorder
|
||||||
from .timeout import ls_timeout
|
from .timeout import ls_timeout
|
||||||
|
|
||||||
LOG_PATH = "/var/log/airtime/pypo/pypo.log"
|
|
||||||
LOG_LEVEL = logging.INFO
|
|
||||||
logging.captureWarnings(True)
|
|
||||||
|
|
||||||
# Set up command-line options
|
|
||||||
parser = OptionParser()
|
|
||||||
|
|
||||||
# help screen / info
|
|
||||||
usage = "%prog [options]" + " - python playout system"
|
|
||||||
parser = OptionParser(usage=usage)
|
|
||||||
|
|
||||||
# Options
|
|
||||||
parser.add_option(
|
|
||||||
"-v",
|
|
||||||
"--compat",
|
|
||||||
help="Check compatibility with server API version",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
dest="check_compat",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_option(
|
|
||||||
"-t",
|
|
||||||
"--test",
|
|
||||||
help="Do a test to make sure everything is working properly.",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
dest="test",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_option(
|
|
||||||
"-b",
|
|
||||||
"--cleanup",
|
|
||||||
help="Cleanup",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
dest="cleanup",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_option(
|
|
||||||
"-c",
|
|
||||||
"--check",
|
|
||||||
help="Check the cached schedule and exit",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
dest="check",
|
|
||||||
)
|
|
||||||
|
|
||||||
# parse options
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
|
|
||||||
LIQUIDSOAP_MIN_VERSION = "1.1.1"
|
LIQUIDSOAP_MIN_VERSION = "1.1.1"
|
||||||
|
|
||||||
PYPO_HOME = "/var/tmp/airtime/pypo/"
|
PYPO_HOME = "/var/tmp/airtime/pypo/"
|
||||||
|
@ -96,42 +43,6 @@ def configure_environment():
|
||||||
os.environ["TERM"] = "xterm"
|
os.environ["TERM"] = "xterm"
|
||||||
|
|
||||||
|
|
||||||
configure_environment()
|
|
||||||
|
|
||||||
# need to wait for Python 2.7 for this..
|
|
||||||
logging.captureWarnings(True)
|
|
||||||
|
|
||||||
# configure logging
|
|
||||||
try:
|
|
||||||
# Set up logging
|
|
||||||
logFormatter = logging.Formatter(
|
|
||||||
"%(asctime)s [%(module)s] [%(levelname)-5.5s] %(message)s"
|
|
||||||
)
|
|
||||||
rootLogger = logging.getLogger()
|
|
||||||
rootLogger.setLevel(LOG_LEVEL)
|
|
||||||
logger = rootLogger
|
|
||||||
|
|
||||||
fileHandler = logging.handlers.RotatingFileHandler(
|
|
||||||
filename=LOG_PATH, maxBytes=1024 * 1024 * 30, backupCount=8
|
|
||||||
)
|
|
||||||
fileHandler.setFormatter(logFormatter)
|
|
||||||
rootLogger.addHandler(fileHandler)
|
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
|
||||||
consoleHandler.setFormatter(logFormatter)
|
|
||||||
rootLogger.addHandler(consoleHandler)
|
|
||||||
except Exception as e:
|
|
||||||
print("Couldn't configure logging: {}".format(e))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# loading config file
|
|
||||||
try:
|
|
||||||
config = ConfigObj("/etc/airtime/airtime.conf")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Error loading config file: %s", e)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
class Global:
|
class Global:
|
||||||
def __init__(self, api_client):
|
def __init__(self, api_client):
|
||||||
self.api_client = api_client
|
self.api_client = api_client
|
||||||
|
@ -144,13 +55,12 @@ class Global:
|
||||||
|
|
||||||
|
|
||||||
def keyboardInterruptHandler(signum, frame):
|
def keyboardInterruptHandler(signum, frame):
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.info("\nKeyboard Interrupt\n")
|
logger.info("\nKeyboard Interrupt\n")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
@ls_timeout
|
@ls_timeout
|
||||||
def liquidsoap_get_info(telnet_lock, host, port, logger):
|
def liquidsoap_get_info(telnet_lock, host, port):
|
||||||
logger.debug("Checking to see if Liquidsoap is running")
|
logger.debug("Checking to see if Liquidsoap is running")
|
||||||
try:
|
try:
|
||||||
telnet_lock.acquire()
|
telnet_lock.acquire()
|
||||||
|
@ -176,25 +86,16 @@ def get_liquidsoap_version(version_string):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if m:
|
|
||||||
current_version = m.group(1)
|
|
||||||
return pure.version_cmp(current_version, LIQUIDSOAP_MIN_VERSION) >= 0
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def liquidsoap_startup_test(telnet_lock, ls_host, ls_port):
|
def liquidsoap_startup_test(telnet_lock, ls_host, ls_port):
|
||||||
|
|
||||||
liquidsoap_version_string = liquidsoap_get_info(
|
liquidsoap_version_string = liquidsoap_get_info(telnet_lock, ls_host, ls_port)
|
||||||
telnet_lock, ls_host, ls_port, logger
|
|
||||||
)
|
|
||||||
while not liquidsoap_version_string:
|
while not liquidsoap_version_string:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Liquidsoap doesn't appear to be running!, " + "Sleeping and trying again"
|
"Liquidsoap doesn't appear to be running!, " + "Sleeping and trying again"
|
||||||
)
|
)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
liquidsoap_version_string = liquidsoap_get_info(
|
liquidsoap_version_string = liquidsoap_get_info(telnet_lock, ls_host, ls_port)
|
||||||
telnet_lock, ls_host, ls_port, logger
|
|
||||||
)
|
|
||||||
|
|
||||||
while pure.version_cmp(liquidsoap_version_string, LIQUIDSOAP_MIN_VERSION) < 0:
|
while pure.version_cmp(liquidsoap_version_string, LIQUIDSOAP_MIN_VERSION) < 0:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
@ -203,14 +104,28 @@ def liquidsoap_startup_test(telnet_lock, ls_host, ls_port):
|
||||||
% LIQUIDSOAP_MIN_VERSION
|
% LIQUIDSOAP_MIN_VERSION
|
||||||
)
|
)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
liquidsoap_version_string = liquidsoap_get_info(
|
liquidsoap_version_string = liquidsoap_get_info(telnet_lock, ls_host, ls_port)
|
||||||
telnet_lock, ls_host, ls_port, logger
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info("Liquidsoap version string found %s" % liquidsoap_version_string)
|
logger.info("Liquidsoap version string found %s" % liquidsoap_version_string)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
@click.command()
|
||||||
|
@cli_logging_options
|
||||||
|
def cli(log_level: str, log_filepath: Optional[Path]):
|
||||||
|
"""
|
||||||
|
Run playout.
|
||||||
|
"""
|
||||||
|
setup_logger(level_from_name(log_level), log_filepath)
|
||||||
|
|
||||||
|
configure_environment()
|
||||||
|
|
||||||
|
# loading config file
|
||||||
|
try:
|
||||||
|
config = ConfigObj("/etc/airtime/airtime.conf")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error loading config file: %s", e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
logger.info("###########################################")
|
logger.info("###########################################")
|
||||||
logger.info("# *** pypo *** #")
|
logger.info("# *** pypo *** #")
|
||||||
logger.info("# Liquidsoap Scheduled Playout System #")
|
logger.info("# Liquidsoap Scheduled Playout System #")
|
||||||
|
@ -246,15 +161,11 @@ def run():
|
||||||
|
|
||||||
liquidsoap_startup_test(telnet_lock, ls_host, ls_port)
|
liquidsoap_startup_test(telnet_lock, ls_host, ls_port)
|
||||||
|
|
||||||
if options.test:
|
|
||||||
g.test_api()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
pypoFetch_q = Queue()
|
pypoFetch_q = Queue()
|
||||||
recorder_q = Queue()
|
recorder_q = Queue()
|
||||||
pypoPush_q = Queue()
|
pypoPush_q = Queue()
|
||||||
|
|
||||||
pypo_liquidsoap = PypoLiquidsoap(logger, telnet_lock, ls_host, ls_port)
|
pypo_liquidsoap = PypoLiquidsoap(telnet_lock, ls_host, ls_port)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This queue is shared between pypo-fetch and pypo-file, where pypo-file
|
This queue is shared between pypo-fetch and pypo-file, where pypo-file
|
||||||
|
@ -295,5 +206,3 @@ def run():
|
||||||
# This allows CTRL-C to work!
|
# This allows CTRL-C to work!
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
logger.info("System exit")
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import traceback
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Python part of radio playout (pypo)
|
Python part of radio playout (pypo)
|
||||||
|
|
||||||
|
@ -16,9 +14,10 @@ Main case:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging.config
|
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# additional modules (should be checked)
|
# additional modules (should be checked)
|
||||||
from configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
|
@ -26,9 +25,14 @@ from configobj import ConfigObj
|
||||||
# custom imports
|
# custom imports
|
||||||
# from util import *
|
# from util import *
|
||||||
from libretime_api_client import version1 as api_client
|
from libretime_api_client import version1 as api_client
|
||||||
|
from libretime_shared.logging import INFO, setup_logger
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
LOG_LEVEL = logging.INFO
|
# TODO: Get log settings from cli/env variables
|
||||||
LOG_PATH = "/var/log/airtime/pypo/notify.log"
|
DEFAULT_LOG_LEVEL = INFO
|
||||||
|
DEFAULT_LOG_FILEPATH = Path("/var/log/libretime/playout-notify.log")
|
||||||
|
|
||||||
|
setup_logger(DEFAULT_LOG_LEVEL, DEFAULT_LOG_FILEPATH)
|
||||||
|
|
||||||
# help screeen / info
|
# help screeen / info
|
||||||
usage = "%prog [options]" + " - notification gateway"
|
usage = "%prog [options]" + " - notification gateway"
|
||||||
|
@ -98,25 +102,6 @@ parser.add_option(
|
||||||
# parse options
|
# parse options
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
logging.captureWarnings(True)
|
|
||||||
logFormatter = logging.Formatter(
|
|
||||||
"%(asctime)s [%(module)s] [%(levelname)-5.5s] %(message)s"
|
|
||||||
)
|
|
||||||
rootLogger = logging.getLogger()
|
|
||||||
rootLogger.setLevel(LOG_LEVEL)
|
|
||||||
|
|
||||||
fileHandler = logging.handlers.RotatingFileHandler(
|
|
||||||
filename=LOG_PATH, maxBytes=1024 * 1024 * 30, backupCount=8
|
|
||||||
)
|
|
||||||
fileHandler.setFormatter(logFormatter)
|
|
||||||
rootLogger.addHandler(fileHandler)
|
|
||||||
|
|
||||||
consoleHandler = logging.StreamHandler()
|
|
||||||
consoleHandler.setFormatter(logFormatter)
|
|
||||||
rootLogger.addHandler(consoleHandler)
|
|
||||||
logger = rootLogger
|
|
||||||
|
|
||||||
# need to wait for Python 2.7 for this..
|
# need to wait for Python 2.7 for this..
|
||||||
# logging.captureWarnings(True)
|
# logging.captureWarnings(True)
|
||||||
|
|
||||||
|
@ -150,8 +135,7 @@ class Notify:
|
||||||
logger.info("# Calling server to update liquidsoap status #")
|
logger.info("# Calling server to update liquidsoap status #")
|
||||||
logger.info("#################################################")
|
logger.info("#################################################")
|
||||||
logger.info("msg = " + str(msg))
|
logger.info("msg = " + str(msg))
|
||||||
response = self.api_client.notify_liquidsoap_status(msg, stream_id, time)
|
self.api_client.notify_liquidsoap_status(msg, stream_id, time)
|
||||||
logger.info("Response: " + json.dumps(response))
|
|
||||||
|
|
||||||
def notify_source_status(self, source_name, status):
|
def notify_source_status(self, source_name, status):
|
||||||
logger.debug("#################################################")
|
logger.debug("#################################################")
|
||||||
|
@ -165,8 +149,7 @@ class Notify:
|
||||||
logger.debug("#################################################")
|
logger.debug("#################################################")
|
||||||
logger.debug("# Calling server to update webstream data #")
|
logger.debug("# Calling server to update webstream data #")
|
||||||
logger.debug("#################################################")
|
logger.debug("#################################################")
|
||||||
response = self.api_client.notify_webstream_data(data, media_id)
|
self.api_client.notify_webstream_data(data, media_id)
|
||||||
logger.debug("Response: " + json.dumps(response))
|
|
||||||
|
|
||||||
def run_with_options(self, options):
|
def run_with_options(self, options):
|
||||||
if options.error and options.stream_id:
|
if options.error and options.stream_id:
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import logging.config
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
@ -15,21 +14,19 @@ from threading import Thread, Timer
|
||||||
|
|
||||||
from libretime_api_client import version1 as v1_api_client
|
from libretime_api_client import version1 as v1_api_client
|
||||||
from libretime_api_client import version2 as api_client
|
from libretime_api_client import version2 as api_client
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from . import pure
|
from . import pure
|
||||||
from .timeout import ls_timeout
|
from .timeout import ls_timeout
|
||||||
|
|
||||||
|
|
||||||
def keyboardInterruptHandler(signum, frame):
|
def keyboardInterruptHandler(signum, frame):
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.info("\nKeyboard Interrupt\n")
|
logger.info("\nKeyboard Interrupt\n")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, keyboardInterruptHandler)
|
signal.signal(signal.SIGINT, keyboardInterruptHandler)
|
||||||
|
|
||||||
logging.captureWarnings(True)
|
|
||||||
|
|
||||||
POLL_INTERVAL = 400
|
POLL_INTERVAL = 400
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,12 +50,10 @@ class PypoFetch(Thread):
|
||||||
|
|
||||||
self.telnet_lock = telnet_lock
|
self.telnet_lock = telnet_lock
|
||||||
|
|
||||||
self.logger = logging.getLogger()
|
|
||||||
|
|
||||||
self.pypo_liquidsoap = pypo_liquidsoap
|
self.pypo_liquidsoap = pypo_liquidsoap
|
||||||
|
|
||||||
self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
|
self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
|
||||||
self.logger.debug("Cache dir %s", self.cache_dir)
|
logger.debug("Cache dir %s", self.cache_dir)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not os.path.isdir(dir):
|
if not os.path.isdir(dir):
|
||||||
|
@ -67,13 +62,13 @@ class PypoFetch(Thread):
|
||||||
is a file. We are not handling the second case, but don't
|
is a file. We are not handling the second case, but don't
|
||||||
think we actually care about handling it.
|
think we actually care about handling it.
|
||||||
"""
|
"""
|
||||||
self.logger.debug("Cache dir does not exist. Creating...")
|
logger.debug("Cache dir does not exist. Creating...")
|
||||||
os.makedirs(dir)
|
os.makedirs(dir)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.schedule_data = []
|
self.schedule_data = []
|
||||||
self.logger.info("PypoFetch: init complete")
|
logger.info("PypoFetch: init complete")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Handle a message from RabbitMQ, put it into our yucky global var.
|
Handle a message from RabbitMQ, put it into our yucky global var.
|
||||||
|
@ -82,7 +77,7 @@ class PypoFetch(Thread):
|
||||||
|
|
||||||
def handle_message(self, message):
|
def handle_message(self, message):
|
||||||
try:
|
try:
|
||||||
self.logger.info("Received event from Pypo Message Handler: %s" % message)
|
logger.info("Received event from Pypo Message Handler: %s" % message)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = message.decode()
|
message = message.decode()
|
||||||
|
@ -90,7 +85,7 @@ class PypoFetch(Thread):
|
||||||
pass
|
pass
|
||||||
m = json.loads(message)
|
m = json.loads(message)
|
||||||
command = m["event_type"]
|
command = m["event_type"]
|
||||||
self.logger.info("Handling command: " + command)
|
logger.info("Handling command: " + command)
|
||||||
|
|
||||||
if command == "update_schedule":
|
if command == "update_schedule":
|
||||||
self.schedule_data = m["schedule"]
|
self.schedule_data = m["schedule"]
|
||||||
|
@ -98,29 +93,29 @@ class PypoFetch(Thread):
|
||||||
elif command == "reset_liquidsoap_bootstrap":
|
elif command == "reset_liquidsoap_bootstrap":
|
||||||
self.set_bootstrap_variables()
|
self.set_bootstrap_variables()
|
||||||
elif command == "update_stream_setting":
|
elif command == "update_stream_setting":
|
||||||
self.logger.info("Updating stream setting...")
|
logger.info("Updating stream setting...")
|
||||||
self.regenerate_liquidsoap_conf(m["setting"])
|
self.regenerate_liquidsoap_conf(m["setting"])
|
||||||
elif command == "update_stream_format":
|
elif command == "update_stream_format":
|
||||||
self.logger.info("Updating stream format...")
|
logger.info("Updating stream format...")
|
||||||
self.update_liquidsoap_stream_format(m["stream_format"])
|
self.update_liquidsoap_stream_format(m["stream_format"])
|
||||||
elif command == "update_station_name":
|
elif command == "update_station_name":
|
||||||
self.logger.info("Updating station name...")
|
logger.info("Updating station name...")
|
||||||
self.update_liquidsoap_station_name(m["station_name"])
|
self.update_liquidsoap_station_name(m["station_name"])
|
||||||
elif command == "update_transition_fade":
|
elif command == "update_transition_fade":
|
||||||
self.logger.info("Updating transition_fade...")
|
logger.info("Updating transition_fade...")
|
||||||
self.update_liquidsoap_transition_fade(m["transition_fade"])
|
self.update_liquidsoap_transition_fade(m["transition_fade"])
|
||||||
elif command == "switch_source":
|
elif command == "switch_source":
|
||||||
self.logger.info("switch_on_source show command received...")
|
logger.info("switch_on_source show command received...")
|
||||||
self.pypo_liquidsoap.get_telnet_dispatcher().switch_source(
|
self.pypo_liquidsoap.get_telnet_dispatcher().switch_source(
|
||||||
m["sourcename"], m["status"]
|
m["sourcename"], m["status"]
|
||||||
)
|
)
|
||||||
elif command == "disconnect_source":
|
elif command == "disconnect_source":
|
||||||
self.logger.info("disconnect_on_source show command received...")
|
logger.info("disconnect_on_source show command received...")
|
||||||
self.pypo_liquidsoap.get_telnet_dispatcher().disconnect_source(
|
self.pypo_liquidsoap.get_telnet_dispatcher().disconnect_source(
|
||||||
m["sourcename"]
|
m["sourcename"]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.info("Unknown command: %s" % command)
|
logger.info("Unknown command: %s" % command)
|
||||||
|
|
||||||
# update timeout value
|
# update timeout value
|
||||||
if command == "update_schedule":
|
if command == "update_schedule":
|
||||||
|
@ -131,12 +126,12 @@ class PypoFetch(Thread):
|
||||||
)
|
)
|
||||||
if self.listener_timeout < 0:
|
if self.listener_timeout < 0:
|
||||||
self.listener_timeout = 0
|
self.listener_timeout = 0
|
||||||
self.logger.info("New timeout: %s" % self.listener_timeout)
|
logger.info("New timeout: %s" % self.listener_timeout)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception("Exception in handling Message Handler message")
|
logger.exception("Exception in handling Message Handler message")
|
||||||
|
|
||||||
def switch_source_temp(self, sourcename, status):
|
def switch_source_temp(self, sourcename, status):
|
||||||
self.logger.debug('Switching source: %s to "%s" status', sourcename, status)
|
logger.debug('Switching source: %s to "%s" status', sourcename, status)
|
||||||
command = "streams."
|
command = "streams."
|
||||||
if sourcename == "master_dj":
|
if sourcename == "master_dj":
|
||||||
command += "master_dj_"
|
command += "master_dj_"
|
||||||
|
@ -157,13 +152,13 @@ class PypoFetch(Thread):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def set_bootstrap_variables(self):
|
def set_bootstrap_variables(self):
|
||||||
self.logger.debug("Getting information needed on bootstrap from Airtime")
|
logger.debug("Getting information needed on bootstrap from Airtime")
|
||||||
try:
|
try:
|
||||||
info = self.v1_api_client.get_bootstrap_info()
|
info = self.v1_api_client.get_bootstrap_info()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception("Unable to get bootstrap info.. Exiting pypo...")
|
logger.exception("Unable to get bootstrap info.. Exiting pypo...")
|
||||||
|
|
||||||
self.logger.debug("info:%s", info)
|
logger.debug("info:%s", info)
|
||||||
commands = []
|
commands = []
|
||||||
for k, v in info["switch_status"].items():
|
for k, v in info["switch_status"].items():
|
||||||
commands.append(self.switch_source_temp(k, v))
|
commands.append(self.switch_source_temp(k, v))
|
||||||
|
@ -191,13 +186,13 @@ class PypoFetch(Thread):
|
||||||
will be thrown."""
|
will be thrown."""
|
||||||
self.telnet_lock.acquire(False)
|
self.telnet_lock.acquire(False)
|
||||||
|
|
||||||
self.logger.info("Restarting Liquidsoap")
|
logger.info("Restarting Liquidsoap")
|
||||||
subprocess.call(
|
subprocess.call(
|
||||||
"kill -9 `pidof libretime-liquidsoap`", shell=True, close_fds=True
|
"kill -9 `pidof libretime-liquidsoap`", shell=True, close_fds=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Wait here and poll Liquidsoap until it has started up
|
# Wait here and poll Liquidsoap until it has started up
|
||||||
self.logger.info("Waiting for Liquidsoap to start")
|
logger.info("Waiting for Liquidsoap to start")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
tn = telnetlib.Telnet(
|
tn = telnetlib.Telnet(
|
||||||
|
@ -205,14 +200,14 @@ class PypoFetch(Thread):
|
||||||
)
|
)
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
tn.read_all()
|
tn.read_all()
|
||||||
self.logger.info("Liquidsoap is up and running")
|
logger.info("Liquidsoap is up and running")
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# sleep 0.5 seconds and try again
|
# sleep 0.5 seconds and try again
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
finally:
|
finally:
|
||||||
if self.telnet_lock.locked():
|
if self.telnet_lock.locked():
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
@ -242,18 +237,18 @@ class PypoFetch(Thread):
|
||||||
boot_up_time_command = (
|
boot_up_time_command = (
|
||||||
"vars.bootup_time " + str(current_time) + "\n"
|
"vars.bootup_time " + str(current_time) + "\n"
|
||||||
).encode("utf-8")
|
).encode("utf-8")
|
||||||
self.logger.info(boot_up_time_command)
|
logger.info(boot_up_time_command)
|
||||||
tn.write(boot_up_time_command)
|
tn.write(boot_up_time_command)
|
||||||
|
|
||||||
connection_status = ("streams.connection_status\n").encode("utf-8")
|
connection_status = ("streams.connection_status\n").encode("utf-8")
|
||||||
self.logger.info(connection_status)
|
logger.info(connection_status)
|
||||||
tn.write(connection_status)
|
tn.write(connection_status)
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
|
|
||||||
output = tn.read_all()
|
output = tn.read_all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -263,7 +258,7 @@ class PypoFetch(Thread):
|
||||||
# streamin info is in the form of:
|
# streamin info is in the form of:
|
||||||
# eg. s1:true,2:true,3:false
|
# eg. s1:true,2:true,3:false
|
||||||
streams = stream_info.split(",")
|
streams = stream_info.split(",")
|
||||||
self.logger.info(streams)
|
logger.info(streams)
|
||||||
|
|
||||||
fake_time = current_time + 1
|
fake_time = current_time + 1
|
||||||
for s in streams:
|
for s in streams:
|
||||||
|
@ -283,12 +278,12 @@ class PypoFetch(Thread):
|
||||||
self.telnet_lock.acquire()
|
self.telnet_lock.acquire()
|
||||||
tn = telnetlib.Telnet(self.config["ls_host"], self.config["ls_port"])
|
tn = telnetlib.Telnet(self.config["ls_host"], self.config["ls_port"])
|
||||||
command = ("vars.stream_metadata_type %s\n" % stream_format).encode("utf-8")
|
command = ("vars.stream_metadata_type %s\n" % stream_format).encode("utf-8")
|
||||||
self.logger.info(command)
|
logger.info(command)
|
||||||
tn.write(command)
|
tn.write(command)
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
tn.read_all()
|
tn.read_all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -300,12 +295,12 @@ class PypoFetch(Thread):
|
||||||
self.telnet_lock.acquire()
|
self.telnet_lock.acquire()
|
||||||
tn = telnetlib.Telnet(self.config["ls_host"], self.config["ls_port"])
|
tn = telnetlib.Telnet(self.config["ls_host"], self.config["ls_port"])
|
||||||
command = ("vars.default_dj_fade %s\n" % fade).encode("utf-8")
|
command = ("vars.default_dj_fade %s\n" % fade).encode("utf-8")
|
||||||
self.logger.info(command)
|
logger.info(command)
|
||||||
tn.write(command)
|
tn.write(command)
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
tn.read_all()
|
tn.read_all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -318,16 +313,16 @@ class PypoFetch(Thread):
|
||||||
self.telnet_lock.acquire()
|
self.telnet_lock.acquire()
|
||||||
tn = telnetlib.Telnet(self.config["ls_host"], self.config["ls_port"])
|
tn = telnetlib.Telnet(self.config["ls_host"], self.config["ls_port"])
|
||||||
command = ("vars.station_name %s\n" % station_name).encode("utf-8")
|
command = ("vars.station_name %s\n" % station_name).encode("utf-8")
|
||||||
self.logger.info(command)
|
logger.info(command)
|
||||||
tn.write(command)
|
tn.write(command)
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
tn.read_all()
|
tn.read_all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Process the schedule
|
Process the schedule
|
||||||
|
@ -340,7 +335,7 @@ class PypoFetch(Thread):
|
||||||
|
|
||||||
def process_schedule(self, schedule_data):
|
def process_schedule(self, schedule_data):
|
||||||
self.last_update_schedule_timestamp = time.time()
|
self.last_update_schedule_timestamp = time.time()
|
||||||
self.logger.debug(schedule_data)
|
logger.debug(schedule_data)
|
||||||
media = schedule_data["media"]
|
media = schedule_data["media"]
|
||||||
media_filtered = {}
|
media_filtered = {}
|
||||||
|
|
||||||
|
@ -376,17 +371,17 @@ class PypoFetch(Thread):
|
||||||
|
|
||||||
self.media_prepare_queue.put(copy.copy(media_filtered))
|
self.media_prepare_queue.put(copy.copy(media_filtered))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
# Send the data to pypo-push
|
# Send the data to pypo-push
|
||||||
self.logger.debug("Pushing to pypo-push")
|
logger.debug("Pushing to pypo-push")
|
||||||
self.push_queue.put(media_copy)
|
self.push_queue.put(media_copy)
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
try:
|
try:
|
||||||
self.cache_cleanup(media)
|
self.cache_cleanup(media)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
# do basic validation of file parameters. Useful for debugging
|
# do basic validation of file parameters. Useful for debugging
|
||||||
# purposes
|
# purposes
|
||||||
|
@ -402,9 +397,9 @@ class PypoFetch(Thread):
|
||||||
length2 = media_item["cue_out"] - media_item["cue_in"]
|
length2 = media_item["cue_out"] - media_item["cue_in"]
|
||||||
|
|
||||||
if abs(length2 - length1) > 1:
|
if abs(length2 - length1) > 1:
|
||||||
self.logger.error("end - start length: %s", length1)
|
logger.error("end - start length: %s", length1)
|
||||||
self.logger.error("cue_out - cue_in length: %s", length2)
|
logger.error("cue_out - cue_in length: %s", length2)
|
||||||
self.logger.error("Two lengths are not equal!!!")
|
logger.error("Two lengths are not equal!!!")
|
||||||
|
|
||||||
media_item["file_ext"] = mime_ext
|
media_item["file_ext"] = mime_ext
|
||||||
|
|
||||||
|
@ -438,22 +433,22 @@ class PypoFetch(Thread):
|
||||||
|
|
||||||
expired_files = cached_file_set - scheduled_file_set
|
expired_files = cached_file_set - scheduled_file_set
|
||||||
|
|
||||||
self.logger.debug("Files to remove " + str(expired_files))
|
logger.debug("Files to remove " + str(expired_files))
|
||||||
for f in expired_files:
|
for f in expired_files:
|
||||||
try:
|
try:
|
||||||
path = os.path.join(self.cache_dir, f)
|
path = os.path.join(self.cache_dir, f)
|
||||||
self.logger.debug("Removing %s" % path)
|
logger.debug("Removing %s" % path)
|
||||||
|
|
||||||
# check if this file is opened (sometimes Liquidsoap is still
|
# check if this file is opened (sometimes Liquidsoap is still
|
||||||
# playing the file due to our knowledge of the track length
|
# playing the file due to our knowledge of the track length
|
||||||
# being incorrect!)
|
# being incorrect!)
|
||||||
if not self.is_file_opened(path):
|
if not self.is_file_opened(path):
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
self.logger.info("File '%s' removed" % path)
|
logger.info("File '%s' removed" % path)
|
||||||
else:
|
else:
|
||||||
self.logger.info("File '%s' not removed. Still busy!" % path)
|
logger.info("File '%s' not removed. Still busy!" % path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception("Problem removing file '%s'" % f)
|
logger.exception("Problem removing file '%s'" % f)
|
||||||
|
|
||||||
def manual_schedule_fetch(self):
|
def manual_schedule_fetch(self):
|
||||||
try:
|
try:
|
||||||
|
@ -461,8 +456,8 @@ class PypoFetch(Thread):
|
||||||
self.process_schedule(self.schedule_data)
|
self.process_schedule(self.schedule_data)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Unable to fetch schedule")
|
logger.error("Unable to fetch schedule")
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def persistent_manual_schedule_fetch(self, max_attempts=1):
|
def persistent_manual_schedule_fetch(self, max_attempts=1):
|
||||||
|
@ -499,11 +494,11 @@ class PypoFetch(Thread):
|
||||||
success = self.persistent_manual_schedule_fetch(max_attempts=5)
|
success = self.persistent_manual_schedule_fetch(max_attempts=5)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.logger.info("Bootstrap schedule received: %s", self.schedule_data)
|
logger.info("Bootstrap schedule received: %s", self.schedule_data)
|
||||||
|
|
||||||
loops = 1
|
loops = 1
|
||||||
while True:
|
while True:
|
||||||
self.logger.info("Loop #%s", loops)
|
logger.info("Loop #%s", loops)
|
||||||
manual_fetch_needed = False
|
manual_fetch_needed = False
|
||||||
try:
|
try:
|
||||||
"""
|
"""
|
||||||
|
@ -526,16 +521,16 @@ class PypoFetch(Thread):
|
||||||
manual_fetch_needed = False
|
manual_fetch_needed = False
|
||||||
self.handle_message(message)
|
self.handle_message(message)
|
||||||
except Empty as e:
|
except Empty as e:
|
||||||
self.logger.info("Queue timeout. Fetching schedule manually")
|
logger.info("Queue timeout. Fetching schedule manually")
|
||||||
manual_fetch_needed = True
|
manual_fetch_needed = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if manual_fetch_needed:
|
if manual_fetch_needed:
|
||||||
self.persistent_manual_schedule_fetch(max_attempts=5)
|
self.persistent_manual_schedule_fetch(max_attempts=5)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception("Failed to manually fetch the schedule.")
|
logger.exception("Failed to manually fetch the schedule.")
|
||||||
|
|
||||||
loops += 1
|
loops += 1
|
||||||
|
|
||||||
|
@ -544,4 +539,3 @@ class PypoFetch(Thread):
|
||||||
Entry point of the thread
|
Entry point of the thread
|
||||||
"""
|
"""
|
||||||
self.main()
|
self.main()
|
||||||
self.logger.info("PypoFetch thread exiting")
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import configparser
|
import configparser
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
|
@ -14,17 +13,15 @@ from threading import Thread
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from libretime_api_client import version2 as api_client
|
from libretime_api_client import version2 as api_client
|
||||||
|
from loguru import logger
|
||||||
from requests.exceptions import ConnectionError, HTTPError, Timeout
|
from requests.exceptions import ConnectionError, HTTPError, Timeout
|
||||||
|
|
||||||
CONFIG_PATH = "/etc/airtime/airtime.conf"
|
CONFIG_PATH = "/etc/airtime/airtime.conf"
|
||||||
|
|
||||||
logging.captureWarnings(True)
|
|
||||||
|
|
||||||
|
|
||||||
class PypoFile(Thread):
|
class PypoFile(Thread):
|
||||||
def __init__(self, schedule_queue, config):
|
def __init__(self, schedule_queue, config):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.logger = logging.getLogger()
|
|
||||||
self.media_queue = schedule_queue
|
self.media_queue = schedule_queue
|
||||||
self.media = None
|
self.media = None
|
||||||
self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
|
self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
|
||||||
|
@ -56,7 +53,7 @@ class PypoFile(Thread):
|
||||||
# become an issue here... This needs proper cache management.
|
# become an issue here... This needs proper cache management.
|
||||||
# https://github.com/LibreTime/libretime/issues/756#issuecomment-477853018
|
# https://github.com/LibreTime/libretime/issues/756#issuecomment-477853018
|
||||||
# https://github.com/LibreTime/libretime/pull/845
|
# https://github.com/LibreTime/libretime/pull/845
|
||||||
self.logger.debug(
|
logger.debug(
|
||||||
"file %s already exists in local cache as %s, skipping copying..."
|
"file %s already exists in local cache as %s, skipping copying..."
|
||||||
% (src, dst)
|
% (src, dst)
|
||||||
)
|
)
|
||||||
|
@ -66,16 +63,16 @@ class PypoFile(Thread):
|
||||||
media_item["file_ready"] = not do_copy
|
media_item["file_ready"] = not do_copy
|
||||||
|
|
||||||
if do_copy:
|
if do_copy:
|
||||||
self.logger.info("copying from %s to local cache %s" % (src, dst))
|
logger.info("copying from %s to local cache %s" % (src, dst))
|
||||||
try:
|
try:
|
||||||
with open(dst, "wb") as handle:
|
with open(dst, "wb") as handle:
|
||||||
self.logger.info(media_item)
|
logger.info(media_item)
|
||||||
response = self.api_client.services.file_download_url(
|
response = self.api_client.services.file_download_url(
|
||||||
id=media_item["id"]
|
id=media_item["id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if not response.ok:
|
if not response.ok:
|
||||||
self.logger.error(response)
|
logger.error(response)
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"%s - Error occurred downloading file"
|
"%s - Error occurred downloading file"
|
||||||
% response.status_code
|
% response.status_code
|
||||||
|
@ -95,8 +92,8 @@ class PypoFile(Thread):
|
||||||
|
|
||||||
media_item["file_ready"] = True
|
media_item["file_ready"] = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Could not copy from %s to %s" % (src, dst))
|
logger.error("Could not copy from %s to %s" % (src, dst))
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
def report_file_size_and_md5_to_api(self, file_path, file_id):
|
def report_file_size_and_md5_to_api(self, file_path, file_id):
|
||||||
try:
|
try:
|
||||||
|
@ -112,10 +109,10 @@ class PypoFile(Thread):
|
||||||
md5_hash = m.hexdigest()
|
md5_hash = m.hexdigest()
|
||||||
except (OSError, IOError) as e:
|
except (OSError, IOError) as e:
|
||||||
file_size = 0
|
file_size = 0
|
||||||
self.logger.error(
|
logger.error(
|
||||||
"Error getting file size and md5 hash for file id %s" % file_id
|
"Error getting file size and md5 hash for file id %s" % file_id
|
||||||
)
|
)
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
# Make PUT request to LibreTime to update the file size and hash
|
# Make PUT request to LibreTime to update the file size and hash
|
||||||
error_msg = (
|
error_msg = (
|
||||||
|
@ -125,10 +122,10 @@ class PypoFile(Thread):
|
||||||
payload = {"filesize": file_size, "md5": md5_hash}
|
payload = {"filesize": file_size, "md5": md5_hash}
|
||||||
response = self.api_client.update_file(file_id, payload)
|
response = self.api_client.update_file(file_id, payload)
|
||||||
except (ConnectionError, Timeout):
|
except (ConnectionError, Timeout):
|
||||||
self.logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
return file_size
|
return file_size
|
||||||
|
|
||||||
|
@ -148,7 +145,7 @@ class PypoFile(Thread):
|
||||||
highest_priority = sorted_keys[0]
|
highest_priority = sorted_keys[0]
|
||||||
media_item = schedule[highest_priority]
|
media_item = schedule[highest_priority]
|
||||||
|
|
||||||
self.logger.debug("Highest priority item: %s" % highest_priority)
|
logger.debug("Highest priority item: %s" % highest_priority)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Remove this media_item from the dictionary. On the next iteration
|
Remove this media_item from the dictionary. On the next iteration
|
||||||
|
@ -168,13 +165,10 @@ class PypoFile(Thread):
|
||||||
try:
|
try:
|
||||||
config.readfp(open(config_path))
|
config.readfp(open(config_path))
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logging.debug(
|
logger.debug(
|
||||||
"Failed to open config file at %s: %s" % (config_path, e.strerror)
|
"Failed to open config file at %s: %s" % (config_path, e.strerror)
|
||||||
)
|
)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
except Exception as e:
|
|
||||||
logging.debug(e.strerror)
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -206,8 +200,8 @@ class PypoFile(Thread):
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
top = traceback.format_exc()
|
top = traceback.format_exc()
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
self.logger.error(top)
|
logger.error(top)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -218,6 +212,6 @@ class PypoFile(Thread):
|
||||||
self.main()
|
self.main()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
top = traceback.format_exc()
|
top = traceback.format_exc()
|
||||||
self.logger.error("PypoFile Exception: %s", top)
|
logger.error("PypoFile Exception: %s", top)
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
self.logger.info("PypoFile thread exiting")
|
logger.info("PypoFile thread exiting")
|
||||||
|
|
|
@ -7,9 +7,10 @@ from datetime import datetime
|
||||||
from queue import Empty
|
from queue import Empty
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
def keyboardInterruptHandler(signum, frame):
|
def keyboardInterruptHandler(signum, frame):
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.info("\nKeyboard Interrupt\n")
|
logger.info("\nKeyboard Interrupt\n")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
@ -18,10 +19,9 @@ signal.signal(signal.SIGINT, keyboardInterruptHandler)
|
||||||
|
|
||||||
|
|
||||||
class PypoLiqQueue(Thread):
|
class PypoLiqQueue(Thread):
|
||||||
def __init__(self, q, pypo_liquidsoap, logger):
|
def __init__(self, q, pypo_liquidsoap):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.queue = q
|
self.queue = q
|
||||||
self.logger = logger
|
|
||||||
self.pypo_liquidsoap = pypo_liquidsoap
|
self.pypo_liquidsoap = pypo_liquidsoap
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
|
@ -32,10 +32,10 @@ class PypoLiqQueue(Thread):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if time_until_next_play is None:
|
if time_until_next_play is None:
|
||||||
self.logger.info("waiting indefinitely for schedule")
|
logger.info("waiting indefinitely for schedule")
|
||||||
media_schedule = self.queue.get(block=True)
|
media_schedule = self.queue.get(block=True)
|
||||||
else:
|
else:
|
||||||
self.logger.info(
|
logger.info(
|
||||||
"waiting %ss until next scheduled item" % time_until_next_play
|
"waiting %ss until next scheduled item" % time_until_next_play
|
||||||
)
|
)
|
||||||
media_schedule = self.queue.get(
|
media_schedule = self.queue.get(
|
||||||
|
@ -54,7 +54,7 @@ class PypoLiqQueue(Thread):
|
||||||
else:
|
else:
|
||||||
time_until_next_play = None
|
time_until_next_play = None
|
||||||
else:
|
else:
|
||||||
self.logger.info("New schedule received: %s", media_schedule)
|
logger.info("New schedule received: %s", media_schedule)
|
||||||
|
|
||||||
# new schedule received. Replace old one with this.
|
# new schedule received. Replace old one with this.
|
||||||
schedule_deque.clear()
|
schedule_deque.clear()
|
||||||
|
@ -89,4 +89,4 @@ class PypoLiqQueue(Thread):
|
||||||
try:
|
try:
|
||||||
self.main()
|
self.main()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("PypoLiqQueue Exception: %s", traceback.format_exc())
|
logger.error("PypoLiqQueue Exception: %s", traceback.format_exc())
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from . import eventtypes
|
from . import eventtypes
|
||||||
from .pypofetch import PypoFetch
|
from .pypofetch import PypoFetch
|
||||||
from .telnetliquidsoap import TelnetLiquidsoap
|
from .telnetliquidsoap import TelnetLiquidsoap
|
||||||
|
|
||||||
|
|
||||||
class PypoLiquidsoap:
|
class PypoLiquidsoap:
|
||||||
def __init__(self, logger, telnet_lock, host, port):
|
def __init__(self, telnet_lock, host, port):
|
||||||
self.logger = logger
|
|
||||||
self.liq_queue_tracker = {
|
self.liq_queue_tracker = {
|
||||||
"s0": None,
|
"s0": None,
|
||||||
"s1": None,
|
"s1": None,
|
||||||
|
@ -18,7 +19,7 @@ class PypoLiquidsoap:
|
||||||
}
|
}
|
||||||
|
|
||||||
self.telnet_liquidsoap = TelnetLiquidsoap(
|
self.telnet_liquidsoap = TelnetLiquidsoap(
|
||||||
telnet_lock, logger, host, port, list(self.liq_queue_tracker.keys())
|
telnet_lock, host, port, list(self.liq_queue_tracker.keys())
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_telnet_dispatcher(self):
|
def get_telnet_dispatcher(self):
|
||||||
|
@ -64,10 +65,10 @@ class PypoLiquidsoap:
|
||||||
self.telnet_liquidsoap.queue_push(available_queue, media_item)
|
self.telnet_liquidsoap.queue_push(available_queue, media_item)
|
||||||
self.liq_queue_tracker[available_queue] = media_item
|
self.liq_queue_tracker[available_queue] = media_item
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
self.logger.warn(
|
logger.warning(
|
||||||
"File %s did not become ready in less than 5 seconds. Skipping...",
|
"File %s did not become ready in less than 5 seconds. Skipping...",
|
||||||
media_item["dst"],
|
media_item["dst"],
|
||||||
)
|
)
|
||||||
|
@ -158,7 +159,7 @@ class PypoLiquidsoap:
|
||||||
|
|
||||||
if not correct:
|
if not correct:
|
||||||
# need to re-add
|
# need to re-add
|
||||||
self.logger.info("Track %s found to have new attr." % i)
|
logger.info("Track %s found to have new attr." % i)
|
||||||
to_be_removed.add(i["row_id"])
|
to_be_removed.add(i["row_id"])
|
||||||
to_be_added.add(i["row_id"])
|
to_be_added.add(i["row_id"])
|
||||||
|
|
||||||
|
@ -166,9 +167,7 @@ class PypoLiquidsoap:
|
||||||
to_be_added.update(schedule_ids - liq_queue_ids)
|
to_be_added.update(schedule_ids - liq_queue_ids)
|
||||||
|
|
||||||
if to_be_removed:
|
if to_be_removed:
|
||||||
self.logger.info(
|
logger.info("Need to remove items from Liquidsoap: %s" % to_be_removed)
|
||||||
"Need to remove items from Liquidsoap: %s" % to_be_removed
|
|
||||||
)
|
|
||||||
|
|
||||||
# remove files from Liquidsoap's queue
|
# remove files from Liquidsoap's queue
|
||||||
for i in self.liq_queue_tracker:
|
for i in self.liq_queue_tracker:
|
||||||
|
@ -177,9 +176,7 @@ class PypoLiquidsoap:
|
||||||
self.stop(i)
|
self.stop(i)
|
||||||
|
|
||||||
if to_be_added:
|
if to_be_added:
|
||||||
self.logger.info(
|
logger.info("Need to add items to Liquidsoap *now*: %s" % to_be_added)
|
||||||
"Need to add items to Liquidsoap *now*: %s" % to_be_added
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in scheduled_now_files:
|
for i in scheduled_now_files:
|
||||||
if i["row_id"] in to_be_added:
|
if i["row_id"] in to_be_added:
|
||||||
|
@ -196,7 +193,7 @@ class PypoLiquidsoap:
|
||||||
self.telnet_liquidsoap.stop_web_stream_buffer()
|
self.telnet_liquidsoap.stop_web_stream_buffer()
|
||||||
self.telnet_liquidsoap.stop_web_stream_output()
|
self.telnet_liquidsoap.stop_web_stream_output()
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
self.logger.error("Error: Malformed event in schedule. " + str(e))
|
logger.error("Error: Malformed event in schedule. " + str(e))
|
||||||
|
|
||||||
def stop(self, queue):
|
def stop(self, queue):
|
||||||
self.telnet_liquidsoap.queue_remove(queue)
|
self.telnet_liquidsoap.queue_remove(queue)
|
||||||
|
@ -220,7 +217,7 @@ class PypoLiquidsoap:
|
||||||
diff_sec = self.date_interval_to_seconds(diff_td)
|
diff_sec = self.date_interval_to_seconds(diff_td)
|
||||||
|
|
||||||
if diff_sec > 0:
|
if diff_sec > 0:
|
||||||
self.logger.debug(
|
logger.debug(
|
||||||
"media item was supposed to start %s ago. Preparing to start..",
|
"media item was supposed to start %s ago. Preparing to start..",
|
||||||
diff_sec,
|
diff_sec,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
@ -13,8 +12,7 @@ from kombu.connection import Connection
|
||||||
from kombu.messaging import Exchange, Queue
|
from kombu.messaging import Exchange, Queue
|
||||||
from kombu.mixins import ConsumerMixin
|
from kombu.mixins import ConsumerMixin
|
||||||
from kombu.simple import SimpleQueue
|
from kombu.simple import SimpleQueue
|
||||||
|
from loguru import logger
|
||||||
logging.captureWarnings(True)
|
|
||||||
|
|
||||||
|
|
||||||
class RabbitConsumer(ConsumerMixin):
|
class RabbitConsumer(ConsumerMixin):
|
||||||
|
@ -36,13 +34,12 @@ class RabbitConsumer(ConsumerMixin):
|
||||||
class PypoMessageHandler(Thread):
|
class PypoMessageHandler(Thread):
|
||||||
def __init__(self, pq, rq, config):
|
def __init__(self, pq, rq, config):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.logger = logging.getLogger("message_h")
|
|
||||||
self.pypo_queue = pq
|
self.pypo_queue = pq
|
||||||
self.recorder_queue = rq
|
self.recorder_queue = rq
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def init_rabbit_mq(self):
|
def init_rabbit_mq(self):
|
||||||
self.logger.info("Initializing RabbitMQ stuff")
|
logger.info("Initializing RabbitMQ stuff")
|
||||||
try:
|
try:
|
||||||
schedule_exchange = Exchange(
|
schedule_exchange = Exchange(
|
||||||
"airtime-pypo", "direct", durable=True, auto_delete=True
|
"airtime-pypo", "direct", durable=True, auto_delete=True
|
||||||
|
@ -58,7 +55,7 @@ class PypoMessageHandler(Thread):
|
||||||
rabbit = RabbitConsumer(connection, [schedule_queue], self)
|
rabbit = RabbitConsumer(connection, [schedule_queue], self)
|
||||||
rabbit.run()
|
rabbit.run()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Handle a message from RabbitMQ, put it into our yucky global var.
|
Handle a message from RabbitMQ, put it into our yucky global var.
|
||||||
|
@ -67,7 +64,7 @@ class PypoMessageHandler(Thread):
|
||||||
|
|
||||||
def handle_message(self, message):
|
def handle_message(self, message):
|
||||||
try:
|
try:
|
||||||
self.logger.info("Received event from RabbitMQ: %s" % message)
|
logger.info("Received event from RabbitMQ: %s" % message)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = message.decode()
|
message = message.decode()
|
||||||
|
@ -75,50 +72,48 @@ class PypoMessageHandler(Thread):
|
||||||
pass
|
pass
|
||||||
m = json.loads(message)
|
m = json.loads(message)
|
||||||
command = m["event_type"]
|
command = m["event_type"]
|
||||||
self.logger.info("Handling command: " + command)
|
logger.info("Handling command: " + command)
|
||||||
|
|
||||||
if command == "update_schedule":
|
if command == "update_schedule":
|
||||||
self.logger.info("Updating schedule...")
|
logger.info("Updating schedule...")
|
||||||
self.pypo_queue.put(message)
|
self.pypo_queue.put(message)
|
||||||
elif command == "reset_liquidsoap_bootstrap":
|
elif command == "reset_liquidsoap_bootstrap":
|
||||||
self.logger.info("Resetting bootstrap vars...")
|
logger.info("Resetting bootstrap vars...")
|
||||||
self.pypo_queue.put(message)
|
self.pypo_queue.put(message)
|
||||||
elif command == "update_stream_setting":
|
elif command == "update_stream_setting":
|
||||||
self.logger.info("Updating stream setting...")
|
logger.info("Updating stream setting...")
|
||||||
self.pypo_queue.put(message)
|
self.pypo_queue.put(message)
|
||||||
elif command == "update_stream_format":
|
elif command == "update_stream_format":
|
||||||
self.logger.info("Updating stream format...")
|
logger.info("Updating stream format...")
|
||||||
self.pypo_queue.put(message)
|
self.pypo_queue.put(message)
|
||||||
elif command == "update_station_name":
|
elif command == "update_station_name":
|
||||||
self.logger.info("Updating station name...")
|
logger.info("Updating station name...")
|
||||||
self.pypo_queue.put(message)
|
self.pypo_queue.put(message)
|
||||||
elif command == "switch_source":
|
elif command == "switch_source":
|
||||||
self.logger.info("switch_source command received...")
|
logger.info("switch_source command received...")
|
||||||
self.pypo_queue.put(message)
|
self.pypo_queue.put(message)
|
||||||
elif command == "update_transition_fade":
|
elif command == "update_transition_fade":
|
||||||
self.logger.info("Updating trasition fade...")
|
logger.info("Updating trasition fade...")
|
||||||
self.pypo_queue.put(message)
|
self.pypo_queue.put(message)
|
||||||
elif command == "disconnect_source":
|
elif command == "disconnect_source":
|
||||||
self.logger.info("disconnect_source command received...")
|
logger.info("disconnect_source command received...")
|
||||||
self.pypo_queue.put(message)
|
self.pypo_queue.put(message)
|
||||||
elif command == "update_recorder_schedule":
|
elif command == "update_recorder_schedule":
|
||||||
self.recorder_queue.put(message)
|
self.recorder_queue.put(message)
|
||||||
elif command == "cancel_recording":
|
elif command == "cancel_recording":
|
||||||
self.recorder_queue.put(message)
|
self.recorder_queue.put(message)
|
||||||
else:
|
else:
|
||||||
self.logger.info("Unknown command: %s" % command)
|
logger.info("Unknown command: %s" % command)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Exception in handling RabbitMQ message: %s", e)
|
logger.error("Exception in handling RabbitMQ message: %s", e)
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
try:
|
try:
|
||||||
self.init_rabbit_mq()
|
self.init_rabbit_mq()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Exception: %s", e)
|
logger.error("Exception: %s", e)
|
||||||
self.logger.error("traceback: %s", traceback.format_exc())
|
logger.error("traceback: %s", traceback.format_exc())
|
||||||
self.logger.error(
|
logger.error("Error connecting to RabbitMQ Server. Trying again in few seconds")
|
||||||
"Error connecting to RabbitMQ Server. Trying again in few seconds"
|
|
||||||
)
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import calendar
|
import calendar
|
||||||
import logging.config
|
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -12,13 +11,12 @@ from threading import Thread
|
||||||
|
|
||||||
from configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
from libretime_api_client import version1 as api_client
|
from libretime_api_client import version1 as api_client
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from .pypofetch import PypoFetch
|
from .pypofetch import PypoFetch
|
||||||
from .pypoliqqueue import PypoLiqQueue
|
from .pypoliqqueue import PypoLiqQueue
|
||||||
from .timeout import ls_timeout
|
from .timeout import ls_timeout
|
||||||
|
|
||||||
logging.captureWarnings(True)
|
|
||||||
|
|
||||||
PUSH_INTERVAL = 2
|
PUSH_INTERVAL = 2
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,16 +38,13 @@ class PypoPush(Thread):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
self.pushed_objects = {}
|
self.pushed_objects = {}
|
||||||
self.logger = logging.getLogger("push")
|
|
||||||
self.current_prebuffering_stream_id = None
|
self.current_prebuffering_stream_id = None
|
||||||
self.queue_id = 0
|
self.queue_id = 0
|
||||||
|
|
||||||
self.future_scheduled_queue = Queue()
|
self.future_scheduled_queue = Queue()
|
||||||
self.pypo_liquidsoap = pypo_liquidsoap
|
self.pypo_liquidsoap = pypo_liquidsoap
|
||||||
|
|
||||||
self.plq = PypoLiqQueue(
|
self.plq = PypoLiqQueue(self.future_scheduled_queue, self.pypo_liquidsoap)
|
||||||
self.future_scheduled_queue, self.pypo_liquidsoap, self.logger
|
|
||||||
)
|
|
||||||
self.plq.daemon = True
|
self.plq.daemon = True
|
||||||
self.plq.start()
|
self.plq.start()
|
||||||
|
|
||||||
|
@ -63,10 +58,10 @@ class PypoPush(Thread):
|
||||||
try:
|
try:
|
||||||
media_schedule = self.queue.get(block=True)
|
media_schedule = self.queue.get(block=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
self.logger.debug(media_schedule)
|
logger.debug(media_schedule)
|
||||||
# separate media_schedule list into currently_playing and
|
# separate media_schedule list into currently_playing and
|
||||||
# scheduled_for_future lists
|
# scheduled_for_future lists
|
||||||
currently_playing, scheduled_for_future = self.separate_present_future(
|
currently_playing, scheduled_for_future = self.separate_present_future(
|
||||||
|
@ -77,7 +72,7 @@ class PypoPush(Thread):
|
||||||
self.future_scheduled_queue.put(scheduled_for_future)
|
self.future_scheduled_queue.put(scheduled_for_future)
|
||||||
|
|
||||||
if loops % heartbeat_period == 0:
|
if loops % heartbeat_period == 0:
|
||||||
self.logger.info("heartbeat")
|
logger.info("heartbeat")
|
||||||
loops = 0
|
loops = 0
|
||||||
loops += 1
|
loops += 1
|
||||||
|
|
||||||
|
@ -93,17 +88,17 @@ class PypoPush(Thread):
|
||||||
|
|
||||||
# Ignore track that already ended
|
# Ignore track that already ended
|
||||||
if media_item["end"] < tnow:
|
if media_item["end"] < tnow:
|
||||||
self.logger.debug(f"ignoring ended media_item: {media_item}")
|
logger.debug(f"ignoring ended media_item: {media_item}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
diff_td = tnow - media_item["start"]
|
diff_td = tnow - media_item["start"]
|
||||||
diff_sec = self.date_interval_to_seconds(diff_td)
|
diff_sec = self.date_interval_to_seconds(diff_td)
|
||||||
|
|
||||||
if diff_sec >= 0:
|
if diff_sec >= 0:
|
||||||
self.logger.debug(f"adding media_item to present: {media_item}")
|
logger.debug(f"adding media_item to present: {media_item}")
|
||||||
present.append(media_item)
|
present.append(media_item)
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"adding media_item to future: {media_item}")
|
logger.debug(f"adding media_item to future: {media_item}")
|
||||||
future[mkey] = media_item
|
future[mkey] = media_item
|
||||||
|
|
||||||
return present, future
|
return present, future
|
||||||
|
@ -128,22 +123,22 @@ class PypoPush(Thread):
|
||||||
|
|
||||||
# msg = 'dynamic_source.read_stop_all xxx\n'
|
# msg = 'dynamic_source.read_stop_all xxx\n'
|
||||||
msg = "http.stop\n"
|
msg = "http.stop\n"
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg)
|
tn.write(msg)
|
||||||
|
|
||||||
msg = "dynamic_source.output_stop\n"
|
msg = "dynamic_source.output_stop\n"
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg)
|
tn.write(msg)
|
||||||
|
|
||||||
msg = "dynamic_source.id -1\n"
|
msg = "dynamic_source.id -1\n"
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg)
|
tn.write(msg)
|
||||||
|
|
||||||
tn.write("exit\n")
|
tn.write("exit\n")
|
||||||
self.logger.debug(tn.read_all())
|
logger.debug(tn.read_all())
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -153,6 +148,6 @@ class PypoPush(Thread):
|
||||||
self.main()
|
self.main()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
top = traceback.format_exc()
|
top = traceback.format_exc()
|
||||||
self.logger.error("Pypo Push Exception: %s", top)
|
logger.error("Pypo Push Exception: %s", top)
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
self.logger.info("PypoPush thread exiting")
|
logger.info("PypoPush thread exiting")
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -14,16 +13,16 @@ from threading import Thread
|
||||||
import mutagen
|
import mutagen
|
||||||
import pytz
|
import pytz
|
||||||
from configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
from libretime_api_client import version1 as v1_api_client
|
from libretime_api_client.version1 import AirtimeApiClient as AirtimeApiClientV1
|
||||||
from libretime_api_client import version2 as api_client
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
def api_client(logger):
|
def api_client():
|
||||||
"""
|
"""
|
||||||
api_client returns the correct instance of AirtimeApiClient. Although there is only one
|
api_client returns the correct instance of AirtimeApiClient. Although there is only one
|
||||||
instance to choose from at the moment.
|
instance to choose from at the moment.
|
||||||
"""
|
"""
|
||||||
return v1_api_client.AirtimeApiClient(logger)
|
return AirtimeApiClientV1()
|
||||||
|
|
||||||
|
|
||||||
# loading config file
|
# loading config file
|
||||||
|
@ -58,8 +57,7 @@ PUSH_INTERVAL = 2
|
||||||
class ShowRecorder(Thread):
|
class ShowRecorder(Thread):
|
||||||
def __init__(self, show_instance, show_name, filelength, start_time):
|
def __init__(self, show_instance, show_name, filelength, start_time):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.logger = logging.getLogger("recorder")
|
self.api_client = api_client()
|
||||||
self.api_client = api_client(self.logger)
|
|
||||||
self.filelength = filelength
|
self.filelength = filelength
|
||||||
self.start_time = start_time
|
self.start_time = start_time
|
||||||
self.show_instance = show_instance
|
self.show_instance = show_instance
|
||||||
|
@ -96,8 +94,8 @@ class ShowRecorder(Thread):
|
||||||
)
|
)
|
||||||
args = command.split(" ")
|
args = command.split(" ")
|
||||||
|
|
||||||
self.logger.info("starting record")
|
logger.info("starting record")
|
||||||
self.logger.info("command " + command)
|
logger.info("command " + command)
|
||||||
|
|
||||||
self.p = Popen(args, stdout=PIPE, stderr=PIPE)
|
self.p = Popen(args, stdout=PIPE, stderr=PIPE)
|
||||||
|
|
||||||
|
@ -108,8 +106,8 @@ class ShowRecorder(Thread):
|
||||||
for msg in outmsgs:
|
for msg in outmsgs:
|
||||||
m = re.search("^ERROR", msg)
|
m = re.search("^ERROR", msg)
|
||||||
if not m == None:
|
if not m == None:
|
||||||
self.logger.info("Recording error is found: %s", outmsgs)
|
logger.info("Recording error is found: %s", outmsgs)
|
||||||
self.logger.info("finishing record, return code %s", self.p.returncode)
|
logger.info("finishing record, return code %s", self.p.returncode)
|
||||||
code = self.p.returncode
|
code = self.p.returncode
|
||||||
|
|
||||||
self.p = None
|
self.p = None
|
||||||
|
@ -118,7 +116,7 @@ class ShowRecorder(Thread):
|
||||||
|
|
||||||
def cancel_recording(self):
|
def cancel_recording(self):
|
||||||
# send signal interrupt (2)
|
# send signal interrupt (2)
|
||||||
self.logger.info("Show manually cancelled!")
|
logger.info("Show manually cancelled!")
|
||||||
if self.p is not None:
|
if self.p is not None:
|
||||||
self.p.send_signal(signal.SIGINT)
|
self.p.send_signal(signal.SIGINT)
|
||||||
|
|
||||||
|
@ -148,7 +146,7 @@ class ShowRecorder(Thread):
|
||||||
full_date, full_time = self.start_time.split(" ", 1)
|
full_date, full_time = self.start_time.split(" ", 1)
|
||||||
# No idea why we translated - to : before
|
# No idea why we translated - to : before
|
||||||
# full_time = full_time.replace(":","-")
|
# full_time = full_time.replace(":","-")
|
||||||
self.logger.info("time: %s" % full_time)
|
logger.info("time: %s" % full_time)
|
||||||
artist = "Airtime Show Recorder"
|
artist = "Airtime Show Recorder"
|
||||||
# set some metadata for our file daemon
|
# set some metadata for our file daemon
|
||||||
recorded_file = mutagen.File(filepath, easy=True)
|
recorded_file = mutagen.File(filepath, easy=True)
|
||||||
|
@ -161,38 +159,37 @@ class ShowRecorder(Thread):
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
top = traceback.format_exc()
|
top = traceback.format_exc()
|
||||||
self.logger.error("Exception: %s", e)
|
logger.error("Exception: %s", e)
|
||||||
self.logger.error("traceback: %s", top)
|
logger.error("traceback: %s", top)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
code, filepath = self.record_show()
|
code, filepath = self.record_show()
|
||||||
|
|
||||||
if code == 0:
|
if code == 0:
|
||||||
try:
|
try:
|
||||||
self.logger.info("Preparing to upload %s" % filepath)
|
logger.info("Preparing to upload %s" % filepath)
|
||||||
|
|
||||||
self.set_metadata_and_save(filepath)
|
self.set_metadata_and_save(filepath)
|
||||||
|
|
||||||
self.upload_file(filepath)
|
self.upload_file(filepath)
|
||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
else:
|
else:
|
||||||
self.logger.info("problem recording show")
|
logger.info("problem recording show")
|
||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
|
|
||||||
|
|
||||||
class Recorder(Thread):
|
class Recorder(Thread):
|
||||||
def __init__(self, q):
|
def __init__(self, q):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.logger = logging.getLogger("recorder")
|
self.api_client = api_client()
|
||||||
self.api_client = api_client(self.logger)
|
|
||||||
self.sr = None
|
self.sr = None
|
||||||
self.shows_to_record = {}
|
self.shows_to_record = {}
|
||||||
self.server_timezone = ""
|
self.server_timezone = ""
|
||||||
self.queue = q
|
self.queue = q
|
||||||
self.loops = 0
|
self.loops = 0
|
||||||
self.logger.info("RecorderFetch: init complete")
|
logger.info("RecorderFetch: init complete")
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
while not success:
|
while not success:
|
||||||
|
@ -200,7 +197,7 @@ class Recorder(Thread):
|
||||||
self.api_client.register_component("show-recorder")
|
self.api_client.register_component("show-recorder")
|
||||||
success = True
|
success = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
def handle_message(self):
|
def handle_message(self):
|
||||||
|
@ -212,7 +209,7 @@ class Recorder(Thread):
|
||||||
pass
|
pass
|
||||||
msg = json.loads(message)
|
msg = json.loads(message)
|
||||||
command = msg["event_type"]
|
command = msg["event_type"]
|
||||||
self.logger.info("Received msg from Pypo Message Handler: %s", msg)
|
logger.info("Received msg from Pypo Message Handler: %s", msg)
|
||||||
if command == "cancel_recording":
|
if command == "cancel_recording":
|
||||||
if self.currently_recording():
|
if self.currently_recording():
|
||||||
self.cancel_recording()
|
self.cancel_recording()
|
||||||
|
@ -224,7 +221,7 @@ class Recorder(Thread):
|
||||||
self.start_record()
|
self.start_record()
|
||||||
|
|
||||||
def process_recorder_schedule(self, m):
|
def process_recorder_schedule(self, m):
|
||||||
self.logger.info("Parsing recording show schedules...")
|
logger.info("Parsing recording show schedules...")
|
||||||
temp_shows_to_record = {}
|
temp_shows_to_record = {}
|
||||||
shows = m["shows"]
|
shows = m["shows"]
|
||||||
for show in shows:
|
for show in shows:
|
||||||
|
@ -253,9 +250,9 @@ class Recorder(Thread):
|
||||||
out = float(s)
|
out = float(s)
|
||||||
|
|
||||||
if out < 5:
|
if out < 5:
|
||||||
self.logger.debug("Shows %s", self.shows_to_record)
|
logger.debug("Shows %s", self.shows_to_record)
|
||||||
self.logger.debug("Next show %s", next_show)
|
logger.debug("Next show %s", next_show)
|
||||||
self.logger.debug("Now %s", tnow)
|
logger.debug("Now %s", tnow)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def cancel_recording(self):
|
def cancel_recording(self):
|
||||||
|
@ -274,7 +271,7 @@ class Recorder(Thread):
|
||||||
try:
|
try:
|
||||||
delta = self.get_time_till_next_show()
|
delta = self.get_time_till_next_show()
|
||||||
if delta < 5:
|
if delta < 5:
|
||||||
self.logger.debug("sleeping %s seconds until show", delta)
|
logger.debug("sleeping %s seconds until show", delta)
|
||||||
time.sleep(delta)
|
time.sleep(delta)
|
||||||
|
|
||||||
sorted_show_keys = sorted(self.shows_to_record.keys())
|
sorted_show_keys = sorted(self.shows_to_record.keys())
|
||||||
|
@ -306,7 +303,7 @@ class Recorder(Thread):
|
||||||
# avoiding CC-5299
|
# avoiding CC-5299
|
||||||
while True:
|
while True:
|
||||||
if self.currently_recording():
|
if self.currently_recording():
|
||||||
self.logger.info("Previous record not finished, sleeping 100ms")
|
logger.info("Previous record not finished, sleeping 100ms")
|
||||||
seconds_waiting = seconds_waiting + 0.1
|
seconds_waiting = seconds_waiting + 0.1
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
else:
|
else:
|
||||||
|
@ -326,8 +323,8 @@ class Recorder(Thread):
|
||||||
# self.time_till_next_show = self.get_time_till_next_show()
|
# self.time_till_next_show = self.get_time_till_next_show()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
top = traceback.format_exc()
|
top = traceback.format_exc()
|
||||||
self.logger.error("Exception: %s", e)
|
logger.error("Exception: %s", e)
|
||||||
self.logger.error("traceback: %s", top)
|
logger.error("traceback: %s", top)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
@ -336,19 +333,19 @@ class Recorder(Thread):
|
||||||
poll the server to get the upcoming schedule.
|
poll the server to get the upcoming schedule.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.logger.info("Started...")
|
logger.info("Started...")
|
||||||
# Bootstrap: since we are just starting up, we need to grab the
|
# Bootstrap: since we are just starting up, we need to grab the
|
||||||
# most recent schedule. After that we can just wait for updates.
|
# most recent schedule. After that we can just wait for updates.
|
||||||
try:
|
try:
|
||||||
temp = self.api_client.get_shows_to_record()
|
temp = self.api_client.get_shows_to_record()
|
||||||
if temp is not None:
|
if temp is not None:
|
||||||
self.process_recorder_schedule(temp)
|
self.process_recorder_schedule(temp)
|
||||||
self.logger.info("Bootstrap recorder schedule received: %s", temp)
|
logger.info("Bootstrap recorder schedule received: %s", temp)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
self.logger.info("Bootstrap complete: got initial copy of the schedule")
|
logger.info("Bootstrap complete: got initial copy of the schedule")
|
||||||
|
|
||||||
self.loops = 0
|
self.loops = 0
|
||||||
heartbeat_period = math.floor(30 / PUSH_INTERVAL)
|
heartbeat_period = math.floor(30 / PUSH_INTERVAL)
|
||||||
|
@ -363,18 +360,18 @@ class Recorder(Thread):
|
||||||
temp = self.api_client.get_shows_to_record()
|
temp = self.api_client.get_shows_to_record()
|
||||||
if temp is not None:
|
if temp is not None:
|
||||||
self.process_recorder_schedule(temp)
|
self.process_recorder_schedule(temp)
|
||||||
self.logger.info("updated recorder schedule received: %s", temp)
|
logger.info("updated recorder schedule received: %s", temp)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
try:
|
try:
|
||||||
self.handle_message()
|
self.handle_message()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
self.logger.error("Pypo Recorder Exception: %s", e)
|
logger.error("Pypo Recorder Exception: %s", e)
|
||||||
time.sleep(PUSH_INTERVAL)
|
time.sleep(PUSH_INTERVAL)
|
||||||
self.loops += 1
|
self.loops += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
top = traceback.format_exc()
|
top = traceback.format_exc()
|
||||||
self.logger.error("Exception: %s", e)
|
logger.error("Exception: %s", e)
|
||||||
self.logger.error("traceback: %s", top)
|
logger.error("traceback: %s", top)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import telnetlib
|
import telnetlib
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from .timeout import ls_timeout
|
from .timeout import ls_timeout
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,11 +44,10 @@ def create_liquidsoap_annotation(media):
|
||||||
|
|
||||||
|
|
||||||
class TelnetLiquidsoap:
|
class TelnetLiquidsoap:
|
||||||
def __init__(self, telnet_lock, logger, ls_host, ls_port, queues):
|
def __init__(self, telnet_lock, ls_host, ls_port, queues):
|
||||||
self.telnet_lock = telnet_lock
|
self.telnet_lock = telnet_lock
|
||||||
self.ls_host = ls_host
|
self.ls_host = ls_host
|
||||||
self.ls_port = ls_port
|
self.ls_port = ls_port
|
||||||
self.logger = logger
|
|
||||||
self.queues = queues
|
self.queues = queues
|
||||||
self.current_prebuffering_stream_id = None
|
self.current_prebuffering_stream_id = None
|
||||||
|
|
||||||
|
@ -72,11 +73,11 @@ class TelnetLiquidsoap:
|
||||||
|
|
||||||
for i in self.queues:
|
for i in self.queues:
|
||||||
msg = "queues.%s_skip\n" % i
|
msg = "queues.%s_skip\n" % i
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
self.logger.debug(tn.read_all().decode("utf-8"))
|
logger.debug(tn.read_all().decode("utf-8"))
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
@ -89,11 +90,11 @@ class TelnetLiquidsoap:
|
||||||
tn = self.__connect()
|
tn = self.__connect()
|
||||||
|
|
||||||
msg = "queues.%s_skip\n" % queue_id
|
msg = "queues.%s_skip\n" % queue_id
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
self.logger.debug(tn.read_all().decode("utf-8"))
|
logger.debug(tn.read_all().decode("utf-8"))
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
@ -110,16 +111,16 @@ class TelnetLiquidsoap:
|
||||||
tn = self.__connect()
|
tn = self.__connect()
|
||||||
annotation = create_liquidsoap_annotation(media_item)
|
annotation = create_liquidsoap_annotation(media_item)
|
||||||
msg = "%s.push %s\n" % (queue_id, annotation)
|
msg = "%s.push %s\n" % (queue_id, annotation)
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
show_name = media_item["show_name"]
|
show_name = media_item["show_name"]
|
||||||
msg = "vars.show_name %s\n" % show_name
|
msg = "vars.show_name %s\n" % show_name
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
self.logger.debug(tn.read_all().decode("utf-8"))
|
logger.debug(tn.read_all().decode("utf-8"))
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
@ -133,19 +134,19 @@ class TelnetLiquidsoap:
|
||||||
# dynamic_source.stop http://87.230.101.24:80/top100station.mp3
|
# dynamic_source.stop http://87.230.101.24:80/top100station.mp3
|
||||||
|
|
||||||
msg = "http.stop\n"
|
msg = "http.stop\n"
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
msg = "dynamic_source.id -1\n"
|
msg = "dynamic_source.id -1\n"
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
self.logger.debug(tn.read_all().decode("utf-8"))
|
logger.debug(tn.read_all().decode("utf-8"))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -157,15 +158,15 @@ class TelnetLiquidsoap:
|
||||||
# dynamic_source.stop http://87.230.101.24:80/top100station.mp3
|
# dynamic_source.stop http://87.230.101.24:80/top100station.mp3
|
||||||
|
|
||||||
msg = "dynamic_source.output_stop\n"
|
msg = "dynamic_source.output_stop\n"
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
self.logger.debug(tn.read_all().decode("utf-8"))
|
logger.debug(tn.read_all().decode("utf-8"))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -180,16 +181,16 @@ class TelnetLiquidsoap:
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
msg = "dynamic_source.output_start\n"
|
msg = "dynamic_source.output_start\n"
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
self.logger.debug(tn.read_all().decode("utf-8"))
|
logger.debug(tn.read_all().decode("utf-8"))
|
||||||
|
|
||||||
self.current_prebuffering_stream_id = None
|
self.current_prebuffering_stream_id = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -200,20 +201,20 @@ class TelnetLiquidsoap:
|
||||||
tn = telnetlib.Telnet(self.ls_host, self.ls_port)
|
tn = telnetlib.Telnet(self.ls_host, self.ls_port)
|
||||||
|
|
||||||
msg = "dynamic_source.id %s\n" % media_item["row_id"]
|
msg = "dynamic_source.id %s\n" % media_item["row_id"]
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
msg = "http.restart %s\n" % media_item["uri"]
|
msg = "http.restart %s\n" % media_item["uri"]
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
self.logger.debug(tn.read_all().decode("utf-8"))
|
logger.debug(tn.read_all().decode("utf-8"))
|
||||||
|
|
||||||
self.current_prebuffering_stream_id = media_item["row_id"]
|
self.current_prebuffering_stream_id = media_item["row_id"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -224,23 +225,23 @@ class TelnetLiquidsoap:
|
||||||
tn = telnetlib.Telnet(self.ls_host, self.ls_port)
|
tn = telnetlib.Telnet(self.ls_host, self.ls_port)
|
||||||
|
|
||||||
msg = "dynamic_source.get_id\n"
|
msg = "dynamic_source.get_id\n"
|
||||||
self.logger.debug(msg)
|
logger.debug(msg)
|
||||||
tn.write(msg.encode("utf-8"))
|
tn.write(msg.encode("utf-8"))
|
||||||
|
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
stream_id = tn.read_all().decode("utf-8").splitlines()[0]
|
stream_id = tn.read_all().decode("utf-8").splitlines()[0]
|
||||||
self.logger.debug("stream_id: %s" % stream_id)
|
logger.debug("stream_id: %s" % stream_id)
|
||||||
|
|
||||||
return stream_id
|
return stream_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
@ls_timeout
|
@ls_timeout
|
||||||
def disconnect_source(self, sourcename):
|
def disconnect_source(self, sourcename):
|
||||||
self.logger.debug("Disconnecting source: %s", sourcename)
|
logger.debug("Disconnecting source: %s", sourcename)
|
||||||
command = ""
|
command = ""
|
||||||
if sourcename == "master_dj":
|
if sourcename == "master_dj":
|
||||||
command += "master_harbor.stop\n"
|
command += "master_harbor.stop\n"
|
||||||
|
@ -250,12 +251,12 @@ class TelnetLiquidsoap:
|
||||||
try:
|
try:
|
||||||
self.telnet_lock.acquire()
|
self.telnet_lock.acquire()
|
||||||
tn = telnetlib.Telnet(self.ls_host, self.ls_port)
|
tn = telnetlib.Telnet(self.ls_host, self.ls_port)
|
||||||
self.logger.info(command)
|
logger.info(command)
|
||||||
tn.write(command.encode("utf-8"))
|
tn.write(command.encode("utf-8"))
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
tn.read_all().decode("utf-8")
|
tn.read_all().decode("utf-8")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
|
@ -266,7 +267,7 @@ class TelnetLiquidsoap:
|
||||||
|
|
||||||
tn = telnetlib.Telnet(self.ls_host, self.ls_port)
|
tn = telnetlib.Telnet(self.ls_host, self.ls_port)
|
||||||
for i in commands:
|
for i in commands:
|
||||||
self.logger.info(i)
|
logger.info(i)
|
||||||
if type(i) is str:
|
if type(i) is str:
|
||||||
i = i.encode("utf-8")
|
i = i.encode("utf-8")
|
||||||
tn.write(i)
|
tn.write(i)
|
||||||
|
@ -274,13 +275,13 @@ class TelnetLiquidsoap:
|
||||||
tn.write("exit\n".encode("utf-8"))
|
tn.write("exit\n".encode("utf-8"))
|
||||||
tn.read_all().decode("utf-8")
|
tn.read_all().decode("utf-8")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(str(e))
|
logger.error(str(e))
|
||||||
self.logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.telnet_lock.release()
|
self.telnet_lock.release()
|
||||||
|
|
||||||
def switch_source(self, sourcename, status):
|
def switch_source(self, sourcename, status):
|
||||||
self.logger.debug('Switching source: %s to "%s" status', sourcename, status)
|
logger.debug('Switching source: %s to "%s" status', sourcename, status)
|
||||||
command = "streams."
|
command = "streams."
|
||||||
if sourcename == "master_dj":
|
if sourcename == "master_dj":
|
||||||
command += "master_dj_"
|
command += "master_dj_"
|
||||||
|
@ -298,10 +299,9 @@ class TelnetLiquidsoap:
|
||||||
|
|
||||||
|
|
||||||
class DummyTelnetLiquidsoap:
|
class DummyTelnetLiquidsoap:
|
||||||
def __init__(self, telnet_lock, logger):
|
def __init__(self, telnet_lock):
|
||||||
self.telnet_lock = telnet_lock
|
self.telnet_lock = telnet_lock
|
||||||
self.liquidsoap_mock_queues = {}
|
self.liquidsoap_mock_queues = {}
|
||||||
self.logger = logger
|
|
||||||
|
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
self.liquidsoap_mock_queues["s" + str(i)] = []
|
self.liquidsoap_mock_queues["s" + str(i)] = []
|
||||||
|
@ -311,7 +311,7 @@ class DummyTelnetLiquidsoap:
|
||||||
try:
|
try:
|
||||||
self.telnet_lock.acquire()
|
self.telnet_lock.acquire()
|
||||||
|
|
||||||
self.logger.info("Pushing %s to queue %s" % (media_item, queue_id))
|
logger.info("Pushing %s to queue %s" % (media_item, queue_id))
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
print("Time now: {:s}".format(datetime.utcnow()))
|
print("Time now: {:s}".format(datetime.utcnow()))
|
||||||
|
@ -328,7 +328,7 @@ class DummyTelnetLiquidsoap:
|
||||||
try:
|
try:
|
||||||
self.telnet_lock.acquire()
|
self.telnet_lock.acquire()
|
||||||
|
|
||||||
self.logger.info("Purging queue %s" % queue_id)
|
logger.info("Purging queue %s" % queue_id)
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
print("Time now: {:s}".format(datetime.utcnow()))
|
print("Time now: {:s}".format(datetime.utcnow()))
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import logging
|
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
|
from libretime_shared.logging import TRACE, setup_logger
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from .pypoliqqueue import PypoLiqQueue
|
from .pypoliqqueue import PypoLiqQueue
|
||||||
from .telnetliquidsoap import DummyTelnetLiquidsoap, TelnetLiquidsoap
|
from .telnetliquidsoap import DummyTelnetLiquidsoap, TelnetLiquidsoap
|
||||||
|
|
||||||
|
|
||||||
def keyboardInterruptHandler(signum, frame):
|
def keyboardInterruptHandler(signum, frame):
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.info("\nKeyboard Interrupt\n")
|
logger.info("\nKeyboard Interrupt\n")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
@ -18,9 +19,7 @@ def keyboardInterruptHandler(signum, frame):
|
||||||
signal.signal(signal.SIGINT, keyboardInterruptHandler)
|
signal.signal(signal.SIGINT, keyboardInterruptHandler)
|
||||||
|
|
||||||
# configure logging
|
# configure logging
|
||||||
format = "%(levelname)s - %(pathname)s - %(lineno)s - %(asctime)s - %(message)s"
|
setup_logger(TRACE)
|
||||||
logging.basicConfig(level=logging.DEBUG, format=format)
|
|
||||||
logging.captureWarnings(True)
|
|
||||||
|
|
||||||
telnet_lock = Lock()
|
telnet_lock = Lock()
|
||||||
pypoPush_q = Queue()
|
pypoPush_q = Queue()
|
||||||
|
@ -34,12 +33,15 @@ liq_queue_tracker = {
|
||||||
"s3": None,
|
"s3": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
# dummy_telnet_liquidsoap = DummyTelnetLiquidsoap(telnet_lock, logging)
|
# dummy_telnet_liquidsoap = DummyTelnetLiquidsoap(telnet_lock)
|
||||||
dummy_telnet_liquidsoap = TelnetLiquidsoap(telnet_lock, logging, "localhost", 1234)
|
dummy_telnet_liquidsoap = TelnetLiquidsoap(
|
||||||
|
telnet_lock,
|
||||||
plq = PypoLiqQueue(
|
"localhost",
|
||||||
pypoLiq_q, telnet_lock, logging, liq_queue_tracker, dummy_telnet_liquidsoap
|
1234,
|
||||||
|
liq_queue_tracker,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plq = PypoLiqQueue(pypoLiq_q, dummy_telnet_liquidsoap)
|
||||||
plq.daemon = True
|
plq.daemon = True
|
||||||
plq.start()
|
plq.start()
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ setup(
|
||||||
package_data={"": ["**/*.liq", "*.cfg", "*.types"]},
|
package_data={"": ["**/*.liq", "*.cfg", "*.types"]},
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"libretime-playout=libretime_playout.main:run",
|
"libretime-playout=libretime_playout.main:cli",
|
||||||
"libretime-liquidsoap=libretime_liquidsoap.main:run",
|
"libretime-liquidsoap=libretime_liquidsoap.main:cli",
|
||||||
"libretime-playout-notify=libretime_playout.notify.main:run",
|
"libretime-playout-notify=libretime_playout.notify.main:run",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -43,5 +43,11 @@ setup(
|
||||||
"pytz",
|
"pytz",
|
||||||
"requests",
|
"requests",
|
||||||
],
|
],
|
||||||
|
extras_require={
|
||||||
|
"dev": [
|
||||||
|
f"libretime-shared @ file://localhost/{here.parent / 'shared'}#egg=libretime_shared",
|
||||||
|
f"libretime-api-client @ file://localhost/{here.parent / 'api_client'}#egg=libretime_api_client",
|
||||||
|
],
|
||||||
|
},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue