mirror of
https://github.com/oskvr37/tiddl.git
synced 2026-06-13 04:05:08 +03:00
🐛 Fixed video downloading (#235)
* rename `VideoQuality` to `StreamVideoQuality` * remove bad logic from predicting video quality * print info when skipping video * bump to 3.1.1 alpha
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from tiddl.core.api.models.base import StreamVideoQuality
|
||||||
from tiddl.core.metadata import add_video_metadata
|
from tiddl.core.metadata import add_video_metadata
|
||||||
from tiddl.core.api.models.base import VideoQuality
|
|
||||||
from tiddl.core.utils import get_video_stream_data
|
from tiddl.core.utils import get_video_stream_data
|
||||||
from tiddl.core.utils.ffmpeg import convert_to_mp4, is_ffmpeg_installed
|
from tiddl.core.utils.ffmpeg import convert_to_mp4, is_ffmpeg_installed
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ from .fetch_api import api
|
|||||||
|
|
||||||
# Old Town Road by Lil Nas X
|
# Old Town Road by Lil Nas X
|
||||||
VIDEO_ID = 113483426
|
VIDEO_ID = 113483426
|
||||||
QUALITY: VideoQuality = "HIGH"
|
QUALITY: StreamVideoQuality = "HIGH"
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("fetching video_stream")
|
print("fetching video_stream")
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tiddl"
|
name = "tiddl"
|
||||||
version = "3.1.0"
|
version = "3.1.1a1"
|
||||||
description = "Download Tidal tracks with CLI downloader."
|
description = "Download Tidal tracks with CLI downloader."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|||||||
@@ -162,13 +162,8 @@ def download_callback(
|
|||||||
return TRACK_QUALITY
|
return TRACK_QUALITY
|
||||||
|
|
||||||
elif isinstance(item, Video):
|
elif isinstance(item, Video):
|
||||||
if item.quality == "LOW":
|
# TODO add missing Video.quality literals so this function can work properly
|
||||||
return "sd"
|
return VIDEO_QUALITY
|
||||||
|
|
||||||
if item.quality == "MEDIUM":
|
|
||||||
if VIDEO_QUALITY == "hd":
|
|
||||||
return "hd"
|
|
||||||
return "fhd"
|
|
||||||
|
|
||||||
raise TypeError("Unsupported item type")
|
raise TypeError("Unsupported item type")
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import shutil
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp
|
import shutil
|
||||||
import aiofiles
|
|
||||||
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
from tiddl.core.api.models import TrackQuality, VideoQuality, Track, Video
|
import aiofiles
|
||||||
from tiddl.core.api import TidalAPI, ApiError
|
import aiohttp
|
||||||
|
|
||||||
|
from tiddl.cli.config import VIDEOS_FILTER_LITERAL
|
||||||
|
from tiddl.cli.utils.download import get_existing_track_filename
|
||||||
|
from tiddl.core.api import ApiError, TidalAPI
|
||||||
|
from tiddl.core.api.models import StreamVideoQuality, Track, TrackQuality, Video
|
||||||
from tiddl.core.utils import parse_track_stream, parse_video_stream
|
from tiddl.core.utils import parse_track_stream, parse_video_stream
|
||||||
from tiddl.core.utils.ffmpeg import convert_to_mp4, extract_flac
|
|
||||||
from tiddl.core.utils.const import (
|
from tiddl.core.utils.const import (
|
||||||
TRACK_QUALITY_LITERAL,
|
TRACK_QUALITY_LITERAL,
|
||||||
VIDEO_QUALITY_LITERAL,
|
VIDEO_QUALITY_LITERAL,
|
||||||
track_qualities,
|
track_qualities,
|
||||||
video_qualities,
|
video_qualities,
|
||||||
)
|
)
|
||||||
from tiddl.cli.config import VIDEOS_FILTER_LITERAL
|
from tiddl.core.utils.ffmpeg import convert_to_mp4, extract_flac
|
||||||
from tiddl.cli.utils.download import get_existing_track_filename
|
|
||||||
|
|
||||||
from .output import RichOutput
|
from .output import RichOutput
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ track_qualities_color: dict[TrackQuality, str] = {
|
|||||||
"HI_RES_LOSSLESS": "[yellow]",
|
"HI_RES_LOSSLESS": "[yellow]",
|
||||||
}
|
}
|
||||||
|
|
||||||
video_qualities_color: dict[VideoQuality, str] = {
|
video_qualities_color: dict[StreamVideoQuality, str] = {
|
||||||
"LOW": "[gray]360p",
|
"LOW": "[gray]360p",
|
||||||
"MEDIUM": "[cyan]720p",
|
"MEDIUM": "[cyan]720p",
|
||||||
"HIGH": "[yellow]1080p",
|
"HIGH": "[yellow]1080p",
|
||||||
@@ -46,7 +45,7 @@ class Downloader:
|
|||||||
rich_output: RichOutput
|
rich_output: RichOutput
|
||||||
semaphore: asyncio.Semaphore
|
semaphore: asyncio.Semaphore
|
||||||
track_quality: TrackQuality
|
track_quality: TrackQuality
|
||||||
video_quality: VideoQuality
|
video_quality: StreamVideoQuality
|
||||||
videos_filter: VIDEOS_FILTER_LITERAL
|
videos_filter: VIDEOS_FILTER_LITERAL
|
||||||
skip_existing: bool
|
skip_existing: bool
|
||||||
download_path: Path
|
download_path: Path
|
||||||
@@ -121,7 +120,10 @@ class Downloader:
|
|||||||
elif (isinstance(item, Video) and self.videos_filter == "none") or (
|
elif (isinstance(item, Video) and self.videos_filter == "none") or (
|
||||||
isinstance(item, Track) and self.videos_filter == "only"
|
isinstance(item, Track) and self.videos_filter == "only"
|
||||||
):
|
):
|
||||||
log.info(f"skipping {item.id} due to {self.videos_filter=}")
|
log.debug(f"skipping {item.id} due to {self.videos_filter=}")
|
||||||
|
self.rich_output.console.print(
|
||||||
|
f"Skipping '{item.title}' due to video filter set to '{self.videos_filter}'"
|
||||||
|
)
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
should_extract_flac = False
|
should_extract_flac = False
|
||||||
|
|||||||
+14
-15
@@ -1,34 +1,33 @@
|
|||||||
from requests_cache import DO_NOT_CACHE, EXPIRE_IMMEDIATELY
|
|
||||||
|
|
||||||
from typing import Literal, TypeAlias
|
from typing import Literal, TypeAlias
|
||||||
|
|
||||||
|
from requests_cache import DO_NOT_CACHE, EXPIRE_IMMEDIATELY
|
||||||
|
|
||||||
from .client import TidalClient
|
from .client import TidalClient
|
||||||
from .models.resources import (
|
|
||||||
Album,
|
|
||||||
Artist,
|
|
||||||
Playlist,
|
|
||||||
Track,
|
|
||||||
Video,
|
|
||||||
TrackQuality,
|
|
||||||
VideoQuality,
|
|
||||||
)
|
|
||||||
from .models.base import (
|
from .models.base import (
|
||||||
AlbumItems,
|
AlbumItems,
|
||||||
AlbumItemsCredits,
|
AlbumItemsCredits,
|
||||||
ArtistAlbumsItems,
|
ArtistAlbumsItems,
|
||||||
ArtistVideosItems,
|
ArtistVideosItems,
|
||||||
Favorites,
|
Favorites,
|
||||||
TrackLyrics,
|
|
||||||
PlaylistItems,
|
|
||||||
MixItems,
|
MixItems,
|
||||||
|
PlaylistItems,
|
||||||
Search,
|
Search,
|
||||||
SessionResponse,
|
SessionResponse,
|
||||||
|
TrackLyrics,
|
||||||
TrackStream,
|
TrackStream,
|
||||||
VideoStream,
|
VideoStream,
|
||||||
)
|
)
|
||||||
|
from .models.resources import (
|
||||||
|
Album,
|
||||||
|
Artist,
|
||||||
|
Playlist,
|
||||||
|
StreamVideoQuality,
|
||||||
|
Track,
|
||||||
|
TrackQuality,
|
||||||
|
Video,
|
||||||
|
)
|
||||||
from .models.review import AlbumReview
|
from .models.review import AlbumReview
|
||||||
|
|
||||||
|
|
||||||
ID: TypeAlias = str | int
|
ID: TypeAlias = str | int
|
||||||
|
|
||||||
|
|
||||||
@@ -243,7 +242,7 @@ class TidalAPI:
|
|||||||
expire_after=3600,
|
expire_after=3600,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_video_stream(self, video_id: ID, quality: VideoQuality):
|
def get_video_stream(self, video_id: ID, quality: StreamVideoQuality):
|
||||||
return self.client.fetch(
|
return self.client.fetch(
|
||||||
VideoStream,
|
VideoStream,
|
||||||
f"videos/{video_id}/playbackinfopostpaywall",
|
f"videos/{video_id}/playbackinfopostpaywall",
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
from .resources import Album, Artist, Playlist, Track, Video, TrackQuality, VideoQuality
|
|
||||||
from .base import (
|
from .base import (
|
||||||
AlbumItems,
|
AlbumItems,
|
||||||
AlbumItemsCredits,
|
AlbumItemsCredits,
|
||||||
ArtistAlbumsItems,
|
ArtistAlbumsItems,
|
||||||
Favorites,
|
Favorites,
|
||||||
TrackLyrics,
|
|
||||||
PlaylistItems,
|
|
||||||
MixItems,
|
MixItems,
|
||||||
|
PlaylistItems,
|
||||||
Search,
|
Search,
|
||||||
SessionResponse,
|
SessionResponse,
|
||||||
|
TrackLyrics,
|
||||||
TrackStream,
|
TrackStream,
|
||||||
VideoStream,
|
VideoStream,
|
||||||
)
|
)
|
||||||
|
from .resources import (
|
||||||
|
Album,
|
||||||
|
Artist,
|
||||||
|
Playlist,
|
||||||
|
StreamVideoQuality,
|
||||||
|
Track,
|
||||||
|
TrackQuality,
|
||||||
|
Video,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Album",
|
"Album",
|
||||||
@@ -20,7 +28,7 @@ __all__ = [
|
|||||||
"Track",
|
"Track",
|
||||||
"Video",
|
"Video",
|
||||||
"TrackQuality",
|
"TrackQuality",
|
||||||
"VideoQuality",
|
"StreamVideoQuality",
|
||||||
"AlbumItems",
|
"AlbumItems",
|
||||||
"AlbumItemsCredits",
|
"AlbumItemsCredits",
|
||||||
"ArtistAlbumsItems",
|
"ArtistAlbumsItems",
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
from pydantic import BaseModel
|
from typing import List, Literal, Optional, Union
|
||||||
from typing import Optional, List, Literal, Union
|
|
||||||
|
|
||||||
from .resources import Album, Artist, Playlist, Track, TrackQuality, Video, VideoQuality
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .resources import (
|
||||||
|
Album,
|
||||||
|
Artist,
|
||||||
|
Playlist,
|
||||||
|
StreamVideoQuality,
|
||||||
|
Track,
|
||||||
|
TrackQuality,
|
||||||
|
Video,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SessionResponse(BaseModel):
|
class SessionResponse(BaseModel):
|
||||||
@@ -133,7 +142,7 @@ class VideoStream(BaseModel):
|
|||||||
videoId: int
|
videoId: int
|
||||||
streamType: Literal["ON_DEMAND"]
|
streamType: Literal["ON_DEMAND"]
|
||||||
assetPresentation: Literal["FULL"]
|
assetPresentation: Literal["FULL"]
|
||||||
videoQuality: VideoQuality
|
videoQuality: StreamVideoQuality
|
||||||
# streamingSessionId: str # only in web?
|
# streamingSessionId: str # only in web?
|
||||||
manifestMimeType: Literal["application/dash+xml", "application/vnd.tidal.emu"]
|
manifestMimeType: Literal["application/dash+xml", "application/vnd.tidal.emu"]
|
||||||
manifestHash: str
|
manifestHash: str
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, List, Literal, Dict, Any
|
from typing import Any, Dict, List, Literal, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
TrackQuality = Literal["LOW", "HIGH", "LOSSLESS", "HI_RES_LOSSLESS"]
|
TrackQuality = Literal["LOW", "HIGH", "LOSSLESS", "HI_RES_LOSSLESS"]
|
||||||
|
|
||||||
# audio_only is not stable
|
# audio_only is not stable
|
||||||
VideoQuality = Literal["AUDIO_ONLY", "LOW", "MEDIUM", "HIGH"]
|
StreamVideoQuality = Literal["AUDIO_ONLY", "LOW", "MEDIUM", "HIGH"]
|
||||||
|
|
||||||
MediaMetadataTags = Literal["LOSSLESS", "HIRES_LOSSLESS", "DOLBY_ATMOS"]
|
MediaMetadataTags = Literal["LOSSLESS", "HIRES_LOSSLESS", "DOLBY_ATMOS"]
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ class Video(BaseModel):
|
|||||||
imageId: str
|
imageId: str
|
||||||
vibrantColor: Optional[str] = None
|
vibrantColor: Optional[str] = None
|
||||||
duration: int
|
duration: int
|
||||||
quality: VideoQuality
|
quality: Literal["MP4_1080P"] | str
|
||||||
streamReady: bool
|
streamReady: bool
|
||||||
adSupportedStreamReady: bool
|
adSupportedStreamReady: bool
|
||||||
djReady: bool
|
djReady: bool
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from tiddl.core.api.models import TrackQuality, VideoQuality
|
from tiddl.core.api.models import StreamVideoQuality, TrackQuality
|
||||||
|
|
||||||
|
|
||||||
TRACK_QUALITY_LITERAL = Literal["low", "normal", "high", "max"]
|
TRACK_QUALITY_LITERAL = Literal["low", "normal", "high", "max"]
|
||||||
VIDEO_QUALITY_LITERAL = Literal["sd", "hd", "fhd"]
|
VIDEO_QUALITY_LITERAL = Literal["sd", "hd", "fhd"]
|
||||||
@@ -13,7 +12,7 @@ track_qualities: dict[TRACK_QUALITY_LITERAL, TrackQuality] = {
|
|||||||
"max": "HI_RES_LOSSLESS",
|
"max": "HI_RES_LOSSLESS",
|
||||||
}
|
}
|
||||||
|
|
||||||
video_qualities: dict[VIDEO_QUALITY_LITERAL, VideoQuality] = {
|
video_qualities: dict[VIDEO_QUALITY_LITERAL, StreamVideoQuality] = {
|
||||||
"sd": "LOW",
|
"sd": "LOW",
|
||||||
"hd": "MEDIUM",
|
"hd": "MEDIUM",
|
||||||
"fhd": "HIGH",
|
"fhd": "HIGH",
|
||||||
|
|||||||
Reference in New Issue
Block a user