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:
Jonas L 2022-01-13 16:11:37 +01:00 committed by GitHub
parent 56a3875e2d
commit 5c72f714a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 323 additions and 452 deletions

11
install
View File

@ -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"

4
playout/.pylintrc Normal file
View File

@ -0,0 +1,4 @@
[MESSAGES CONTROL]
disable=missing-module-docstring,
missing-class-docstring,
missing-function-docstring,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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")

View File

@ -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:

View File

@ -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")

View File

@ -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")

View File

@ -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())

View File

@ -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,
) )

View File

@ -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)
""" """

View File

@ -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")

View File

@ -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)

View File

@ -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()))

View File

@ -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()

View File

@ -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,
) )