From 900914377b5d64bf40282176f472ab02d2473887 Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Wed, 24 Apr 2024 07:55:43 +0200 Subject: [PATCH] Add testing on github --- .github/workflows/test.yml | 27 +++++ pyproject.toml | 5 + twitchdl/cli.py | 6 +- twitchdl/commands/info.py | 9 +- twitchdl/playlists.py | 6 +- twitchdl/twitch.py | 4 +- video_before.json | 203 +++++++++++++++++++++++++++++++++++++ 7 files changed, 250 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 video_before.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..95493b9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Run tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-22.04 + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[test]" + - name: Run tests + run: | + pytest + - name: Validate minimum required version + run: | + vermin --no-tips twitchdl diff --git a/pyproject.toml b/pyproject.toml index b186a85..f866263 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,11 @@ dev = [ "vermin", ] +test = [ + "pytest", + "vermin", +] + [project.urls] "Homepage" = "https://twitch-dl.bezdomni.net/" "Source" = "https://github.com/ihabunek/twitch-dl" diff --git a/twitchdl/cli.py b/twitchdl/cli.py index 331bc38..bb799ae 100644 --- a/twitchdl/cli.py +++ b/twitchdl/cli.py @@ -2,7 +2,7 @@ import logging import platform import re import sys -from typing import Optional +from typing import Optional, Tuple import click @@ -255,7 +255,7 @@ def clips( default=5, ) def download( - ids: tuple[str, ...], + ids: Tuple[str, ...], auth_token: Optional[str], chapter: Optional[int], concat: bool, @@ -374,7 +374,7 @@ def videos( channel_name: str, all: bool, compact: bool, - games_tuple: tuple[str, ...], + games_tuple: Tuple[str, ...], json: bool, limit: Optional[int], pager: Optional[int], diff --git a/twitchdl/commands/info.py b/twitchdl/commands/info.py index 9b44cf7..f4f3a15 100644 --- a/twitchdl/commands/info.py +++ b/twitchdl/commands/info.py @@ -7,6 +7,7 @@ from twitchdl import twitch, utils from twitchdl.commands.download import get_video_placeholders from twitchdl.exceptions import ConsoleError from twitchdl.output import bold, print_clip, print_json, print_log, print_table, print_video +from twitchdl.playlists import parse_playlists from twitchdl.twitch import Chapter, Clip, Video @@ -50,13 +51,13 @@ def info(id: str, *, json: bool = False): raise ConsoleError(f"Invalid input: {id}") -def video_info(video: Video, playlists, chapters: List[Chapter]): +def video_info(video: Video, playlists: str, chapters: List[Chapter]): click.echo() print_video(video) click.echo("Playlists:") - for p in m3u8.loads(playlists).playlists: - click.echo(f"{bold(p.stream_info.video)} {p.uri}") + for p in parse_playlists(playlists): + click.echo(f"{bold(p.name)} {p.url}") if chapters: click.echo() @@ -72,7 +73,7 @@ def video_info(video: Video, playlists, chapters: List[Chapter]): print_table(["Placeholder", "Value"], placeholders) -def video_json(video, playlists, chapters): +def video_json(video: Video, playlists: str, chapters: List[Chapter]): playlists = m3u8.loads(playlists).playlists video["playlists"] = [ diff --git a/twitchdl/playlists.py b/twitchdl/playlists.py index 84b3a44..f83c3bf 100644 --- a/twitchdl/playlists.py +++ b/twitchdl/playlists.py @@ -29,11 +29,15 @@ class Vod: """Segment duration in seconds""" -def parse_playlists(playlists_m3u8: str): +def parse_playlists(playlists_m3u8: str) -> List[Playlist]: def _parse(source: str) -> Generator[Playlist, None, None]: document = load_m3u8(source) for p in document.playlists: + from pprint import pp + + pp(p.__dict__) + pp(p.stream_info.__dict__) if p.stream_info.resolution: name = p.media[0].name resolution = "x".join(str(r) for r in p.stream_info.resolution) diff --git a/twitchdl/twitch.py b/twitchdl/twitch.py index 3551646..ff22fb1 100644 --- a/twitchdl/twitch.py +++ b/twitchdl/twitch.py @@ -3,7 +3,7 @@ Twitch API access. """ import json -from typing import Dict, Generator, List, Literal, Mapping, Optional, TypedDict +from typing import Dict, Generator, List, Literal, Mapping, Optional, Tuple, TypedDict import click import httpx @@ -345,7 +345,7 @@ def channel_videos_generator( sort: VideosSort, type: VideosType, game_ids: Optional[List[str]] = None, -) -> tuple[int, Generator[Video, None, None]]: +) -> Tuple[int, Generator[Video, None, None]]: game_ids = game_ids or [] def _generator(videos: Data, max_videos: int) -> Generator[Video, None, None]: diff --git a/video_before.json b/video_before.json new file mode 100644 index 0000000..45c4a99 --- /dev/null +++ b/video_before.json @@ -0,0 +1,203 @@ +{ + "id": "2115833882", + "title": "Games I Feel Like Speedrun Marathon !newyt !Slender !Merch", + "description": null, + "publishedAt": "2024-04-10T05:01:12Z", + "broadcastType": "ARCHIVE", + "lengthSeconds": 30163, + "game": { + "id": "21063", + "name": "Saw" + }, + "creator": { + "login": "ecdycis", + "displayName": "Ecdycis" + }, + "playlists": [ + { + "bandwidth": 6395968, + "resolution": [ + 1920, + 1080 + ], + "codecs": "avc1.64002A,mp4a.40.2", + "video": "chunked", + "uri": "https://d2nvs31859zcd8.cloudfront.net/3104d817048588f268fa_ecdycis_43996750059_1712725267/chunked/index-muted-GKGIUOE29B.m3u8" + }, + { + "bandwidth": 3430341, + "resolution": [ + 1280, + 720 + ], + "codecs": "avc1.4D0020,mp4a.40.2", + "video": "720p60", + "uri": "https://d2nvs31859zcd8.cloudfront.net/3104d817048588f268fa_ecdycis_43996750059_1712725267/720p60/index-muted-GKGIUOE29B.m3u8" + }, + { + "bandwidth": 1448243, + "resolution": [ + 852, + 480 + ], + "codecs": "avc1.4D001F,mp4a.40.2", + "video": "480p30", + "uri": "https://d2nvs31859zcd8.cloudfront.net/3104d817048588f268fa_ecdycis_43996750059_1712725267/480p30/index-muted-GKGIUOE29B.m3u8" + }, + { + "bandwidth": 215544, + "resolution": null, + "codecs": "mp4a.40.2", + "video": "audio_only", + "uri": "https://d2nvs31859zcd8.cloudfront.net/3104d817048588f268fa_ecdycis_43996750059_1712725267/audio_only/index-muted-GKGIUOE29B.m3u8" + }, + { + "bandwidth": 709051, + "resolution": [ + 640, + 360 + ], + "codecs": "avc1.4D001E,mp4a.40.2", + "video": "360p30", + "uri": "https://d2nvs31859zcd8.cloudfront.net/3104d817048588f268fa_ecdycis_43996750059_1712725267/360p30/index-muted-GKGIUOE29B.m3u8" + }, + { + "bandwidth": 288844, + "resolution": [ + 284, + 160 + ], + "codecs": "avc1.4D000C,mp4a.40.2", + "video": "160p30", + "uri": "https://d2nvs31859zcd8.cloudfront.net/3104d817048588f268fa_ecdycis_43996750059_1712725267/160p30/index-muted-GKGIUOE29B.m3u8" + } + ], + "chapters": [ + { + "id": "6049e803f47a0b9cf64ce95adcf3d96d", + "durationMilliseconds": 4891000, + "positionMilliseconds": 0, + "type": "GAME_CHANGE", + "description": "Saw", + "subDescription": "", + "thumbnailURL": "", + "video": { + "id": "2115833882", + "lengthSeconds": 30163, + "__typename": "Video" + }, + "__typename": "VideoMoment", + "game": { + "id": "21063", + "displayName": "Saw", + "boxArtURL": "https://static-cdn.jtvnw.net/ttv-boxart/21063_IGDB-40x53.jpg", + "__typename": "Game" + } + }, + { + "id": "c3be4ce5a7bae802d7a6df02baf62a85", + "durationMilliseconds": 6235000, + "positionMilliseconds": 4891000, + "type": "GAME_CHANGE", + "description": "Resident Evil 7: Biohazard", + "subDescription": "", + "thumbnailURL": "", + "video": { + "id": "2115833882", + "lengthSeconds": 30163, + "__typename": "Video" + }, + "__typename": "VideoMoment", + "game": { + "id": "492934", + "displayName": "Resident Evil 7: Biohazard", + "boxArtURL": "https://static-cdn.jtvnw.net/ttv-boxart/492934_IGDB-40x53.jpg", + "__typename": "Game" + } + }, + { + "id": "bd584f17bc1f91fc11ed14dcec5e4742", + "durationMilliseconds": 3401000, + "positionMilliseconds": 11126000, + "type": "GAME_CHANGE", + "description": "Silent Hill: Homecoming", + "subDescription": "", + "thumbnailURL": "", + "video": { + "id": "2115833882", + "lengthSeconds": 30163, + "__typename": "Video" + }, + "__typename": "VideoMoment", + "game": { + "id": "18864", + "displayName": "Silent Hill: Homecoming", + "boxArtURL": "https://static-cdn.jtvnw.net/ttv-boxart/18864_IGDB-40x53.jpg", + "__typename": "Game" + } + }, + { + "id": "9f4be8bc7b5bf398213bc602a4b39c4d", + "durationMilliseconds": 3490000, + "positionMilliseconds": 14527000, + "type": "GAME_CHANGE", + "description": "Silent Hill 2", + "subDescription": "", + "thumbnailURL": "", + "video": { + "id": "2115833882", + "lengthSeconds": 30163, + "__typename": "Video" + }, + "__typename": "VideoMoment", + "game": { + "id": "9891", + "displayName": "Silent Hill 2", + "boxArtURL": "https://static-cdn.jtvnw.net/ttv-boxart/9891_IGDB-40x53.jpg", + "__typename": "Game" + } + }, + { + "id": "7a42d537681decc10660c33f9071a37f", + "durationMilliseconds": 7241000, + "positionMilliseconds": 18017000, + "type": "GAME_CHANGE", + "description": "The Darkness", + "subDescription": "", + "thumbnailURL": "", + "video": { + "id": "2115833882", + "lengthSeconds": 30163, + "__typename": "Video" + }, + "__typename": "VideoMoment", + "game": { + "id": "8448", + "displayName": "The Darkness", + "boxArtURL": "https://static-cdn.jtvnw.net/ttv-boxart/8448_IGDB-40x53.jpg", + "__typename": "Game" + } + }, + { + "id": "64418186f436d80b1359d2e6686222ef", + "durationMilliseconds": 4905000, + "positionMilliseconds": 25258000, + "type": "GAME_CHANGE", + "description": "Silent Hill 4: The Room", + "subDescription": "", + "thumbnailURL": "", + "video": { + "id": "2115833882", + "lengthSeconds": 30163, + "__typename": "Video" + }, + "__typename": "VideoMoment", + "game": { + "id": "5804", + "displayName": "Silent Hill 4: The Room", + "boxArtURL": "https://static-cdn.jtvnw.net/ttv-boxart/5804_IGDB-40x53.jpg", + "__typename": "Game" + } + } + ] +}