Spodcast/spodcast/config.py
Frank de Lange 14b213b315 New release, 0.4.1
Changes:
 - added --transcode yes/no to enable transcoding .off into .mp3 for handicapped devices which do not support open codes (iOS, looking at you here)
 - added webcron endpoint to run feed updates in situations where the system scheduler can not be used
 - feed manager is now mostly a single page app with live updates
 - added -v (version) option
 - added versioned updatees for feed and index manager

Fixes:
 - direct login now works as intended

New install requirements:
 - ffmpeg-python
 - setuptools

The change to a SPA was necessitated by the introduction of the `--transcode yes/no` option which (when activated) causes feed updates to take much more time, especially on less powerful hardware. This would cause the feed manager process to timeout before the feeds were updated. This problem is mostly fixed but can still occur in the webcron update process. If this happens the php-fpm and/or web server timeout needs to be increased. This should only happen on slower hardware and/or slow links.
2022-03-01 23:37:26 +00:00

222 lines
7.8 KiB
Python

import json
import os
import pkg_resources
import sys
from typing import Any
CONFIG_FILE_PATH = '../spodcast.json'
CONFIG_DIR = 'CONFIG_DIR'
CONFIG_PATH = 'CONFIG_PATH'
VERSION = 'VERSION'
VERSION_STR = pkg_resources.require("Spodcast")[0].version
ROOT_PATH = 'ROOT_PATH'
SKIP_EXISTING_FILES = 'SKIP_EXISTING_FILES'
CHUNK_SIZE = 'CHUNK_SIZE'
DOWNLOAD_REAL_TIME = 'DOWNLOAD_REAL_TIME'
LANGUAGE = 'LANGUAGE'
CREDENTIALS_LOCATION = 'CREDENTIALS_LOCATION'
RETRY = 'RETRY'
MAX_EPISODES = 'MAX_EPISODES'
LOG_LEVEL = 'LOG_LEVEL'
RSS_FEED = 'RSS_FEED'
TRANSCODE = 'TRANSCODE'
CONFIG_VALUES = {
ROOT_PATH: { 'default': '../Spodcast/',
'type': str,
'arg': '--root-path',
'help': 'set root path for podcast cache' },
SKIP_EXISTING_FILES: { 'default': 'True',
'type': bool,
'arg': '--skip-existing',
'help': '[yes|no] skip files with the same name and size. Defaults to "yes".' },
RETRY: { 'default': 5,
'type': int,
'arg': '--retry',
'help': 'retry count for Spotify API access' },
MAX_EPISODES: { 'default': 1000,
'type': int,
'arg': '--max-episodes',
'help': 'number of episodes to download' },
CHUNK_SIZE: { 'default': 50000,
'type': int,
'arg': '--chunk-size',
'help': 'download chunk size' },
DOWNLOAD_REAL_TIME: { 'default': 'False',
'type': bool,
'arg': '--download-real-time',
'help': 'simulate streaming client' },
LANGUAGE: { 'default': 'en',
'type': str,
'arg': '--language',
'help': 'preferred content language' },
CREDENTIALS_LOCATION: { 'default': 'credentials.json',
'type': str,
'arg': '--credentials-location',
'help': 'path to credentials file. If a relative path is used the file will be stored in the same directory as the configuration file (-c /path/to/config.json -> /path/to).' },
RSS_FEED: { 'default': 'True',
'type': bool,
'arg': '--rss-feed',
'help': '[yes|no] add a (php) RSS feed server and related metadata for feed. To manage feeds, point a web server at the spodcast root path as configured using --root-path. Defaults to "yes".' },
TRANSCODE: { 'default': 'False',
'type': bool,
'arg': '--transcode',
'help': '[yes|no] transcode ogg/vorbis to mp3 (where applicable) - only needed for devices which do not support open formats (e.g. iOS). Defaults to "no".' },
LOG_LEVEL: { 'default': 'warning',
'type': str,
'arg': '--log-level',
'help': 'log level (debug/info/warning/error/critical)' }
}
class Config:
Values = {}
@classmethod
def load(cls, args) -> None:
app_dir = os.path.dirname(__file__)
dump_config=False
config_fp = CONFIG_FILE_PATH
if args.config_location:
config_fp = args.config_location
true_config_file_path = os.path.join(app_dir, config_fp)
# Load config from spodcast.json
if not os.path.exists(true_config_file_path):
dump_config=True
os.makedirs(os.path.dirname(true_config_file_path), exist_ok=True)
cls.Values = cls.get_default_json()
else:
with open(true_config_file_path, encoding='utf-8') as config_file:
jsonvalues = json.load(config_file)
cls.Values = {}
for key in CONFIG_VALUES:
if key in jsonvalues:
cls.Values[key] = cls.parse_arg_value(key, jsonvalues[key])
# Add default values for missing keys
for key in CONFIG_VALUES:
if key not in cls.Values:
cls.Values[key] = cls.parse_arg_value(key, CONFIG_VALUES[key]['default'])
# Override config from commandline arguments
for key in CONFIG_VALUES:
if key.lower() in vars(args) and vars(args)[key.lower()] is not None:
cls.Values[key] = cls.parse_arg_value(key, vars(args)[key.lower()])
# dump current config to config file
if dump_config:
with open(true_config_file_path, 'w', encoding='utf-8') as config_file:
json.dump(cls.get_config_json(), config_file, indent=4)
# these values should not be overriden
cls.Values[CONFIG_DIR] = os.path.dirname(true_config_file_path)
cls.Values[CONFIG_PATH] = str(true_config_file_path)
cls.Values[VERSION] = str(VERSION_STR)
@classmethod
def get_default_json(cls) -> Any:
r = {}
for key in CONFIG_VALUES:
r[key] = CONFIG_VALUES[key]['default']
return r
@classmethod
def get_config_json(cls) -> Any:
r = {}
for key in CONFIG_VALUES:
r[key]=cls.Values[key]
return r
@classmethod
def parse_arg_value(cls, key: str, value: Any) -> Any:
if type(value) == CONFIG_VALUES[key]['type']:
return value
if CONFIG_VALUES[key]['type'] == str:
return str(value)
if CONFIG_VALUES[key]['type'] == int:
return int(value)
if CONFIG_VALUES[key]['type'] == bool:
if str(value).lower() in ['yes', 'true', '1']:
return True
if str(value).lower() in ['no', 'false', '0']:
return False
raise ValueError("Not a boolean: " + value)
raise ValueError("Unknown Type: " + value)
@classmethod
def get(cls, key: str) -> Any:
return cls.Values.get(key)
@classmethod
def get_config_dir(cls) -> str:
return cls.get(CONFIG_DIR)
@classmethod
def get_root_path(cls) -> str:
return os.path.join(os.path.dirname(__file__), cls.get(ROOT_PATH))
@classmethod
def get_skip_existing_files(cls) -> bool:
return cls.get(SKIP_EXISTING_FILES)
@classmethod
def get_chunk_size(cls) -> int:
return cls.get(CHUNK_SIZE)
@classmethod
def get_language(cls) -> str:
return cls.get(LANGUAGE)
@classmethod
def get_download_real_time(cls) -> bool:
return cls.get(DOWNLOAD_REAL_TIME)
@classmethod
def get_credentials_location(cls) -> str:
credentials_location = cls.get(CREDENTIALS_LOCATION)
return credentials_location if os.path.isabs(credentials_location) else os.path.join(cls.get(CONFIG_DIR), cls.get(CREDENTIALS_LOCATION))
@classmethod
def get_retry(cls) -> int:
return cls.get(RETRY)
@classmethod
def get_max_episodes(cls) -> int:
return cls.get(MAX_EPISODES)
@classmethod
def get_rss_feed(cls) -> bool:
return cls.get(RSS_FEED)
@classmethod
def get_transcode(cls) -> bool:
return cls.get(TRANSCODE)
@classmethod
def get_log_level(cls) -> str:
return str(cls.get(LOG_LEVEL)).upper()
@classmethod
def get_bin_path(cls) -> str:
return str(sys.argv[0])
@classmethod
def get_config_path(cls) -> str:
return cls.get(CONFIG_PATH)
@classmethod
def get_version_str(cls) -> str:
return cls.get(VERSION)
@classmethod
def get_version_int(cls) -> int:
return int(cls.get(VERSION).replace('.',''))