delarte/src/delarte/__main__.py

201 lines
5.6 KiB
Python

# License: GNU AGPL v3: http://www.gnu.org/licenses/
# This file is part of `delarte` (https://git.afpy.org/fcode/delarte.git)
"""delarte - ArteTV downloader.
Usage:
delarte (-h | --help)
delarte --version
delarte [options] URL
delarte [options] URL RENDITION
delarte [options] URL RENDITION VARIANT
Download a video from ArteTV streaming service. Omit RENDITION and/or
VARIANT to print the list of available values.
Arguments:
URL the URL from ArteTV website
RENDITION the rendition code [audio/subtitles language combination]
VARIANT the variant code [video quality version]
Options:
-h --help print this message
--version print current version of the program
--debug on error, print debugging information
--name-use-id use the program ID
--name-sep=<sep> field separator [default: - ]
--name-seq-pfx=<pfx> sequence counter prefix [default: - ]
--name-seq-no-pad disable sequence zero-padding
--name-add-rendition add rendition code
--name-add-variant add variant code
"""
import itertools
import sys
import time
import docopt
import urllib3
from . import (
ModuleError,
UnexpectedError,
HTTPError,
__version__,
download_targets,
fetch_program_sources,
fetch_rendition_sources,
fetch_targets,
fetch_variant_sources,
)
class Abort(ModuleError):
"""Aborted."""
class Fail(UnexpectedError):
"""Unexpected error."""
def _create_progress():
# create a progress handler for input downloads
state = {}
def on_progress(file, current, total):
now = time.time()
if current == 0:
print(f"Downloading {file!r}: 0.0%", end="")
state["start_time"] = now
state["last_time"] = now
state["last_count"] = 0
elif current == total:
elapsed_time = now - state["start_time"]
rate = int(total / elapsed_time) if elapsed_time else "NaN"
print(f"\rDownloading {file!r}: 100.0% [{rate}]")
state.clear()
elif now - state["last_time"] > 1:
elapsed_time1 = now - state["start_time"]
elapsed_time2 = now - state["last_time"]
progress = int(1000.0 * current / total) / 10.0
rate1 = int(current / elapsed_time1) if elapsed_time1 else "NaN"
rate2 = (
int((current - state["last_count"]) / elapsed_time2)
if elapsed_time2
else "NaN"
)
print(
f"\rDownloading {file!r}: {progress}% [{rate1}, {rate2}]",
end="",
)
state["last_time"] = now
state["last_count"] = current
return on_progress
def _select_rendition_sources(rendition_code, rendition_sources):
if rendition_code:
filtered = [s for s in rendition_sources if s.rendition.code == rendition_code]
if filtered:
return filtered
print(
f"{rendition_code!r} is not a valid rendition code. Available values are:"
)
else:
print("Available renditions:")
key = lambda s: (s.rendition.label, s.rendition.code)
rendition_sources.sort(key=key)
for (label, code), _ in itertools.groupby(rendition_sources, key=key):
print(f"{code:>12} : {label}")
raise Abort()
def _select_variant_sources(variant_code, variant_sources):
if variant_code:
filtered = [s for s in variant_sources if s.variant.code == variant_code]
if filtered:
return filtered
print(f"{variant_code!r} is not a valid variant code. Available values are:")
else:
print("Available variants:")
variant_sources.sort(key=lambda s: s.video_media.track.height, reverse=True)
for code, _ in itertools.groupby(variant_sources, key=lambda s: s.variant.code):
print(f"{code:>12}")
raise Abort()
def main():
"""CLI command."""
args = docopt.docopt(__doc__, sys.argv[1:], version=__version__)
http = urllib3.PoolManager(timeout=5)
try:
program_sources = fetch_program_sources(args["URL"], http)
rendition_sources = _select_rendition_sources(
args["RENDITION"],
fetch_rendition_sources(program_sources, http),
)
variant_sources = _select_variant_sources(
args["VARIANT"],
fetch_variant_sources(rendition_sources, http),
)
targets = fetch_targets(
variant_sources,
http,
**{
k[7:].replace("-", "_"): v
for k, v in args.items()
if k.startswith("--name-")
},
)
download_targets(targets, http, _create_progress())
except UnexpectedError as e:
if args["--debug"]:
raise e
print(str(e))
print()
print(
"This program is the result of browser/server traffic analysis and involves\n"
"some level of trying and guessing. This error might mean that we did not try\n"
"enough or that we guessed poorly."
)
print("")
print("Please consider submitting the issue to us so we may fix it.")
print("")
print("Issue tracker: https://git.afpy.org/fcode/delarte/issues")
print(f"Title: {e.args[0]}")
print("Body:")
print(f" {repr(e)}")
return 1
except ModuleError as e:
if args["--debug"]:
raise e
print(str(e))
return 1
except HTTPError as e:
if args["--debug"]:
raise e
print("Network error.")
return 1
if __name__ == "__main__":
sys.exit(main())