|
|
@@ -0,0 +1,295 @@ |
|
|
#!/usr/bin/env python3 |
|
|
# -*- coding: UTF-8 -*- |
|
|
|
|
|
# ---------------------------------------------------- |
|
|
# Purpose: MythTV Python Bindings for TheTVDB v4 API |
|
|
# Copyright: (c) 2021 Roland Ernst |
|
|
# License: GPL v2 or later, see COPYING for details |
|
|
# ---------------------------------------------------- |
|
|
|
|
|
|
|
|
import argparse |
|
|
import sys |
|
|
import os |
|
|
import shutil |
|
|
import shlex |
|
|
from pprint import pprint |
|
|
from configparser import ConfigParser |
|
|
|
|
|
|
|
|
__title__ = "TheTVDatabaseV4" |
|
|
__author__ = "Roland Ernst" |
|
|
__version__ = "0.3.3" |
|
|
|
|
|
|
|
|
def print_etree(etostr): |
|
|
"""lxml.etree.tostring is a bytes object in python3, and a str in python2. |
|
|
""" |
|
|
if sys.version_info[0] == 2: |
|
|
sys.stdout.write(etostr) |
|
|
else: |
|
|
sys.stdout.write(etostr.decode("utf-8")) |
|
|
|
|
|
|
|
|
def _parse_config(config): |
|
|
""" Parse the config read by ConfigParser.""" |
|
|
d = {} |
|
|
for section in config.sections(): |
|
|
d[section] = {} |
|
|
for k, v in config[section].items(): |
|
|
d[section][k] = v |
|
|
return d |
|
|
|
|
|
|
|
|
def buildVersion(): |
|
|
from lxml import etree |
|
|
version = etree.XML(u'<grabber></grabber>') |
|
|
etree.SubElement(version, "name").text = __title__ |
|
|
etree.SubElement(version, "author").text = __author__ |
|
|
etree.SubElement(version, "thumbnail").text = 'ttvdb.png' |
|
|
etree.SubElement(version, "command").text = 'ttvdb4.py' |
|
|
etree.SubElement(version, "type").text = 'television' |
|
|
etree.SubElement(version, "description").text = \ |
|
|
'Search and downloads metadata from TheTVDB.com (API v4)' |
|
|
etree.SubElement(version, "version").text = __version__ |
|
|
print_etree(etree.tostring(version, encoding='UTF-8', pretty_print=True, |
|
|
xml_declaration=True)) |
|
|
sys.exit(0) |
|
|
|
|
|
|
|
|
def performSelfTest(args): |
|
|
err = 0 |
|
|
try: |
|
|
import lxml |
|
|
except: |
|
|
err = 1 |
|
|
print("Failed to import python lxml library.") |
|
|
try: |
|
|
import requests |
|
|
import requests_cache |
|
|
except: |
|
|
err = 1 |
|
|
print("Failed to import python-requests or python-request-cache library.") |
|
|
try: |
|
|
import MythTV |
|
|
except: |
|
|
err = 1 |
|
|
print("Failed to import MythTV bindings. Check your `configure` output " |
|
|
"to make sure installation was not disabled due to external dependencies.") |
|
|
try: |
|
|
from MythTV.ttvdbv4.myth4ttvdbv4 import Myth4TTVDBv4 |
|
|
from MythTV.ttvdbv4 import ttvdbv4_api as ttvdb |
|
|
if args.debug: |
|
|
print("TheTVDBv4 Script Version: ", __version__) |
|
|
print("TheTVDBv4-API version: ", ttvdb.MYTHTV_TTVDBV4_API_VERSION) |
|
|
print("TheTVDBv4-API file location: ", ttvdb.__file__) |
|
|
except: |
|
|
err = 1 |
|
|
print("Failed to import Py TTVDB4 library. This should have been included " |
|
|
"with the python MythTV bindings.") |
|
|
try: |
|
|
inipath = os.path.abspath(os.path.dirname(sys.argv[0])) |
|
|
inifile = os.path.join(inipath, "ttvdb4.ini") |
|
|
config = ConfigParser() |
|
|
# preserve capital letters: |
|
|
config.optionxform = str |
|
|
config.read(inifile, 'UTF-8') |
|
|
config_dict = _parse_config(config) |
|
|
config_version = config_dict['ConfigVersion']['TTVDBv4ConfigVersion'] |
|
|
if args.debug: |
|
|
print("Config version of 'ttvdb4.ini': ", config_version) |
|
|
except: |
|
|
err = 1 |
|
|
print("Failed to read the ini file 'ttvdb4.ini'. Check your installation " |
|
|
"if such a file exists alongside this grabber script.") |
|
|
if not err: |
|
|
print("Everything appears in order.") |
|
|
sys.exit(err) |
|
|
|
|
|
|
|
|
def main(): |
|
|
""" |
|
|
Main executor for MythTV's ttvdb v4 grabber. |
|
|
""" |
|
|
description = '''A python script to retrieve metadata for TV-Shows.''' |
|
|
|
|
|
parser = argparse.ArgumentParser(description=description) |
|
|
|
|
|
parser.add_argument('-v', '--version', action="store_true", |
|
|
dest="version", help="Display version and author") |
|
|
|
|
|
parser.add_argument('-t', '--test', action="store_true", default=False, |
|
|
dest="test", help="Perform self-test for dependencies.") |
|
|
|
|
|
parser.add_argument('-l', "--language", metavar="LANGUAGE", default=u'en', |
|
|
dest="language", help="Specify language for filtering.") |
|
|
|
|
|
parser.add_argument('-a', "--area", metavar="COUNTRY", default=None, |
|
|
dest="country", help="Specify country for custom data.") |
|
|
|
|
|
group = parser.add_mutually_exclusive_group() |
|
|
|
|
|
group.add_argument('-M', "--list", nargs=1, |
|
|
dest="tvtitle", help="Get TV Shows matching 'tvtitle'.") |
|
|
|
|
|
group.add_argument('-D', "--data", metavar=("INETREF","SEASON","EPISODE"), |
|
|
nargs=3, type=int, dest="tvdata", |
|
|
help="Get TV-Show data for 'inetref', 'season' and 'episode.") |
|
|
|
|
|
group.add_argument('-C', "--collection", nargs=1, type=int, dest="collectionref", |
|
|
help="Get Collection data for 'collectionref'.") |
|
|
|
|
|
group.add_argument('-N', "--numbers", metavar=("ARG0","ARG1"), nargs=2, type=str, |
|
|
dest="tvnumbers", |
|
|
help="Get Season and Episode numbers: " |
|
|
"'ARG0' can be ['series-title', 'inetref'] and " |
|
|
"'ARG1': ['episode-title', 'iso-date-time'].") |
|
|
|
|
|
parser.add_argument('--configure', nargs='?', type=str, default='ttvdb4.ini', |
|
|
dest="inifile", help="Use local configuration file, defaults to " |
|
|
"'~/.mythtv/'ttvdb4.ini'.") |
|
|
|
|
|
parser.add_argument('--debug', action="store_true", default=False, dest="debug", |
|
|
help="Disable caching and enable raw data output.") |
|
|
|
|
|
parser.add_argument('--jsondebug', action="store_true", default=False, dest="jsondebug", |
|
|
help="Enable raw json data output.") |
|
|
|
|
|
parser.add_argument('--doctest', action="store_true", default=False, |
|
|
dest="doctest", help="Run doctests.") |
|
|
|
|
|
args = parser.parse_args() |
|
|
|
|
|
if args.version: |
|
|
buildVersion() |
|
|
|
|
|
if args.test: |
|
|
performSelfTest(args) |
|
|
|
|
|
# assemble arguments |
|
|
cmd_args = vars(args) |
|
|
if args.debug: |
|
|
print("0000: Init: cmd_args: ", cmd_args) |
|
|
|
|
|
# read the ini files |
|
|
import requests |
|
|
confdir = os.environ.get('MYTHCONFDIR', '') |
|
|
if (not confdir) or (confdir == '/'): |
|
|
confdir = os.environ.get('HOME', '') |
|
|
if (not confdir) or (confdir == '/'): |
|
|
print("Unable to find MythTV directory for grabber initialization.") |
|
|
sys.exit(1) |
|
|
confdir = os.path.join(confdir, '.mythtv') |
|
|
|
|
|
cachedir = os.path.join(confdir, 'cache') |
|
|
if not os.path.exists(cachedir): |
|
|
os.makedirs(cachedir) |
|
|
|
|
|
if not args.debug and not args.doctest: |
|
|
if sys.version_info[0] == 2: |
|
|
cache_name = os.path.join(cachedir, 'py2ttvdb4') |
|
|
else: |
|
|
cache_name = os.path.join(cachedir, 'py3ttvdb4') |
|
|
import requests_cache |
|
|
requests_cache.install_cache(cache_name, backend='sqlite', expire_after=3600) |
|
|
|
|
|
# Add config from config file to cmd_args: |
|
|
config_dict = {} |
|
|
# read global config |
|
|
inipath = os.path.abspath(os.path.dirname(sys.argv[0])) |
|
|
inifile = os.path.join(inipath, "ttvdb4.ini") |
|
|
try: |
|
|
global_config = ConfigParser() |
|
|
# preserve capital letters: |
|
|
global_config.optionxform = str |
|
|
global_config.read(inifile, 'UTF-8') |
|
|
config_dict = _parse_config(global_config) |
|
|
if args.debug: |
|
|
print("0000: Init: Global Config File parsed successfully.") |
|
|
except KeyError: |
|
|
if args.debug: |
|
|
print("0000: Init: Parsing Global Config File failed.") |
|
|
# read local config, which overrides the global one |
|
|
if args.inifile: |
|
|
local_config_file = os.path.join(confdir, args.inifile) |
|
|
if os.path.isfile(local_config_file): |
|
|
try: |
|
|
local_config = ConfigParser() |
|
|
# preserve capital letters: |
|
|
local_config.optionxform = str |
|
|
local_config.read(local_config_file, 'UTF-8') |
|
|
for section in local_config.sections(): |
|
|
for k,v in local_config[section].items(): |
|
|
config_dict[section][k] = v |
|
|
if args.debug: |
|
|
print("0000: Init: Local Config File '%s' parsed successfully." |
|
|
% local_config_file) |
|
|
except KeyError: |
|
|
if args.debug: |
|
|
print("0000: Init: Parsing Local Config File failed.") |
|
|
else: |
|
|
# create local config with values from global config |
|
|
shutil.copy(inifile, local_config_file) |
|
|
if args.debug: |
|
|
print("0000: Init: Local config file '%s' created." % local_config_file) |
|
|
|
|
|
# storage for authentication bearer token |
|
|
if sys.version_info[0] == 2: |
|
|
config_dict['auth_file'] = os.path.join(cachedir, "py2ttvdb4_bearer.pickle") |
|
|
else: |
|
|
config_dict['auth_file'] = os.path.join(cachedir, "py3ttvdb4_bearer.pickle") |
|
|
|
|
|
cmd_args["config"] = config_dict |
|
|
if args.debug: |
|
|
print("0000: Init: Using this configuration:") |
|
|
pprint(cmd_args["config"]) |
|
|
|
|
|
if args.doctest: |
|
|
import doctest |
|
|
try: |
|
|
with open("ttvdb4_doctests") as f: |
|
|
if sys.version_info[0] == 2: |
|
|
dtests = b"".join(f.readlines()).decode('utf-8') |
|
|
else: |
|
|
dtests = "".join(f.readlines()) |
|
|
main.__doc__ += dtests |
|
|
except IOError: |
|
|
pass |
|
|
# perhaps try optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE |
|
|
return doctest.testmod(verbose=args.debug, optionflags=doctest.ELLIPSIS) |
|
|
|
|
|
# finally, grab the damn metadata |
|
|
try: |
|
|
from MythTV.ttvdbv4.myth4ttvdbv4 import Myth4TTVDBv4 |
|
|
from lxml import etree |
|
|
mttvdb = Myth4TTVDBv4(**cmd_args) |
|
|
if args.tvdata: |
|
|
# option -D inetref season episode |
|
|
mttvdb.buildSingle() |
|
|
elif args.collectionref: |
|
|
# option -C inetref |
|
|
mttvdb.buildCollection() |
|
|
elif args.tvtitle: |
|
|
# option -M title |
|
|
mttvdb.buildList() |
|
|
elif args.tvnumbers: |
|
|
# option -N title subtitle |
|
|
# option -N inetref subtitle |
|
|
mttvdb.buildNumbers() |
|
|
else: |
|
|
sys.stdout.write('ERROR: This script must be called with one of ' |
|
|
'[-t, -v, -C, -D, -M, -N] switches.') |
|
|
sys.exit(1) |
|
|
|
|
|
print_etree(etree.tostring(mttvdb.tree, encoding='UTF-8', pretty_print=True, |
|
|
xml_declaration=True)) |
|
|
except: |
|
|
if args.debug: |
|
|
raise |
|
|
sys.stdout.write('ERROR: ' + str(sys.exc_info()[0]) + ' : ' |
|
|
+ str(sys.exc_info()[1]) + '\n') |
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |