From 9593619c68f31956eb9c8ea573fb53df9290fb16 Mon Sep 17 00:00:00 2001 From: Barbagus Date: Fri, 9 Dec 2022 00:34:15 +0100 Subject: [PATCH] Setup the execution arch --- README.md | 38 ++++++++++++- pyproject.toml | 2 +- src/delarte/__init__.py | 76 +++++-------------------- src/delarte/__main__.py | 120 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 169 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 252733d..29fe0ce 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,42 @@ ArteTV is a is a European public service channel dedicated to culture. Available 🚀 Quick start --------------- -_to be determined_ +Install [FFMPEG](https://ffmpeg.org/download.html) binaries and ensure it is in your `PATH` +``` +$ ffmpeg -version +ffmpeg version N-109344-g1bebcd43e1-20221202 Copyright (c) 2000-2022 the FFmpeg developers +built with gcc 12.2.0 (crosstool-NG 1.25.0.90_cf9beb1) +``` +Clone this repository +``` +$ git clone git@gitlab.com:Barbagus/delarte.git +$ cd delarte +``` + +Optionally create a virtual environement +``` +$ python3 -m venv .venv +$ source .venv/Scripts/activate +``` + +Install in edit mode +``` +$ pip install -e .[dev] +``` + +Now you can run the script +``` +$ python3 -m delarte --help +or +$ delarte --help +ArteTV dowloader. + +usage: delarte [-h|--help] - print this message + or: delarte program_page_url - show available versions + or: delarte program_page_url version - show available resolutions + or: delarte program_page_url version resolution - download the given video +``` 🔧 How it works ---------------- @@ -258,7 +292,7 @@ The actual build of the video file is handled by [ffmpeg](https://ffmpeg.org/). ##### Why not use FFMPEG direcly with the _version index_ URL ? -So we can select the video resolution _version_ and not rely on stream mapping arguments in `ffmpeg`. +So we can select the video resolution and not rely on stream mapping arguments in `ffmpeg`. ##### Why not use VTT subtitles direcly ? diff --git a/pyproject.toml b/pyproject.toml index b7ea7d0..0de1965 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,4 +25,4 @@ dev = [ ] [project.scripts] -delarte = "delarte:main" \ No newline at end of file +delarte = "delarte.__main__:main" \ No newline at end of file diff --git a/src/delarte/__init__.py b/src/delarte/__init__.py index 1de6f15..168ad1e 100644 --- a/src/delarte/__init__.py +++ b/src/delarte/__init__.py @@ -10,21 +10,15 @@ __version__ = "0.1" import io import json -import os import re -import subprocess -import sys import tempfile from http import HTTPStatus -from urllib.parse import urlparse from urllib.request import urlopen import m3u8 import webvtt -FFMPEG = os.environ.get("PATH_FFMPEG", "ffmpeg path not found") - def load_api_data(url): """Retrieve the root node (infamous "data") of an API call response.""" @@ -182,71 +176,29 @@ def find_resolution(version_index, resolution_code): return None -def build_args(video_index_url, audio_track, subtitles_track, file_base_name): +def build_ffmpeg_cmd(video_index_url, audio_track, subtitles_track, file_base_name): """Build FFMPEG args.""" audio_lang, audio_index_url = audio_track if subtitles_track: subtitles_lang, subtitles_file = subtitles_track - args = ["ffmpeg"] - args.extend(["-i", video_index_url]) - args.extend(["-i", audio_index_url]) + cmd = ["ffmpeg"] + cmd.extend(["-i", video_index_url]) + cmd.extend(["-i", audio_index_url]) if subtitles_track: - args.extend(["-i", subtitles_file]) + cmd.extend(["-i", subtitles_file]) - args.extend(["-c:v", "copy"]) - args.extend(["-c:a", "copy"]) + cmd.extend(["-c:v", "copy"]) + cmd.extend(["-c:a", "copy"]) if subtitles_track: - args.extend(["-c:s", "copy"]) + cmd.extend(["-c:s", "copy"]) - args.extend(["-bsf:a", "aac_adtstoasc"]) - args.extend(["-metadata:s:a:0", f"language={audio_lang}"]) + cmd.extend(["-bsf:a", "aac_adtstoasc"]) + cmd.extend(["-metadata:s:a:0", f"language={audio_lang}"]) if subtitles_track: - args.extend(["-metadata:s:s:0", f"language={subtitles_lang}"]) - args.extend(["-disposition:s:0", "default"]) + cmd.extend(["-metadata:s:s:0", f"language={subtitles_lang}"]) + cmd.extend(["-disposition:s:0", "default"]) - args.append(f"{file_base_name}.mkv") - return args - - -def main(): - """CLI function, options passed as arguments.""" - (ui_lang, _, stream_id, _slug) = urlparse(sys.argv[1]).path[1:-1].split("/") - version_code = sys.argv[2] if len(sys.argv) > 2 else "" - resolution_code = sys.argv[3] if len(sys.argv) > 3 else "" - - if ui_lang not in ("fr", "de", "en", "es", "pl", "it") or _ != "videos": - raise ValueError("Invalid URL") - - config = load_config_api(ui_lang, stream_id) - - version_index_url = find_version(config, version_code) - if version_index_url is None: - print(f"Available versions:", file=sys.stderr) - for (code, label, _) in iter_versions(config): - print(f"\t{code} - {label}", file=sys.stderr) - return 1 - - version_index = load_version_index(version_index_url) - - stream_info = find_resolution(version_index, resolution_code) - if stream_info is None: - print(f"Available resolutions:", file=sys.stderr) - for code, label in iter_resolutions(version_index): - print(f"\t{code} - {label}", file=sys.stderr) - return 1 - - video_index_url, audio_track, subtitles_track = stream_info - if subtitles_track: - subtitles_lang, subtitles_index_url = subtitles_track - subtitle_file = make_srt_tempfile(subtitles_index_url) - subtitles_track = (subtitles_lang, subtitle_file) - - file_base_name = build_file_base_name(config) - - args = build_args(video_index_url, audio_track, subtitles_track, file_base_name) - - subprocess.run(args) - if subtitle_file: - os.unlink(subtitle_file) + cmd.append(f"{file_base_name}.mkv") + return cmd diff --git a/src/delarte/__main__.py b/src/delarte/__main__.py index 8273c4f..548d0d2 100644 --- a/src/delarte/__main__.py +++ b/src/delarte/__main__.py @@ -1,3 +1,119 @@ -from . import main +"""ArteTV dowloader. -main() +usage: delarte [-h|--help] - print this message + or: delarte program_page_url - show available versions + or: delarte program_page_url version - show available resolutions + or: delarte program_page_url version resolution - download the given video +""" +import os +import subprocess +import sys + +from urllib.parse import urlparse + +from . import ( + build_ffmpeg_cmd, + build_file_base_name, + find_resolution, + find_version, + iter_resolutions, + iter_versions, + load_config_api, + load_version_index, + make_srt_tempfile, +) + + +def fail(message, code=1): + """Print a message to STDERR and return a given exit code.""" + print(message, file=sys.stderr) + return code + + +def print_available_versions(config, f): + """Print available program versions.""" + print(f"Available versions:", file=f) + for (code, label, _) in iter_versions(config): + print(f"\t{code} - {label}", file=f) + + +def print_available_resolutions(version_index, f): + """Print available version resolutions.""" + print(f"Available resolutions:", file=f) + for code, label in iter_resolutions(version_index): + print(f"\t{code} - {label}", file=f) + + +def main(): + """CLI command.""" + args = sys.argv[1:] + if not args or args[0] == "-h" or args[0] == "--help": + print(__doc__) + return 0 + + try: + program_page_url = urlparse(args.pop(0)) + if program_page_url.hostname != "www.arte.tv": + return fail("Not an ArteTV url") + + program_page_path = program_page_url.path.split("/")[1:] + + ui_language = program_page_path.pop(0) + + if ui_language not in ("fr", "de", "en", "es", "pl", "it"): + return fail(f"Invalid url language code: {ui_language}") + + if program_page_path.pop(0) != "videos": + return fail("Invalid ArteTV url") + + program_id = program_page_path.pop(0) + + except ValueError: + return fail("Invalid url") + + try: + config = load_config_api(ui_language, program_id) + except ValueError: + return fail("Invalid program") + + if not args: + print_available_versions(config, sys.stdout) + return 0 + + version_index_url = find_version(config, args.pop(0)) + if version_index_url is None: + fail("Invalid version") + print_available_versions(config, sys.stderr) + return 1 + + version_index = load_version_index(version_index_url) + + if not args: + print_available_resolutions(version_index, sys.stdout) + return 0 + + stream_info = find_resolution(version_index, args.pop(0)) + if stream_info is None: + fail("Invalid resolution") + print_available_resolutions(version_index, sys.stderr) + return 0 + + video_index_url, audio_track, subtitles_track = stream_info + if subtitles_track: + subtitles_lang, subtitles_index_url = subtitles_track + subtitle_file = make_srt_tempfile(subtitles_index_url) + subtitles_track = (subtitles_lang, subtitle_file) + + file_base_name = build_file_base_name(config) + + args = build_ffmpeg_cmd( + video_index_url, audio_track, subtitles_track, file_base_name + ) + + subprocess.run(args) + if subtitle_file: + os.unlink(subtitle_file) + + +if __name__ == "__main__": + sys.exit(main())