gamdl 3.1__tar.gz → 3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gamdl-3.1 → gamdl-3.2}/PKG-INFO +1 -1
- gamdl-3.2/gamdl/__init__.py +1 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/cli/cli.py +16 -5
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/base.py +0 -2
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/downloader.py +6 -7
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/exceptions.py +0 -5
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/exceptions.py +11 -2
- gamdl-3.2/gamdl/interface/interface.py +470 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/music_video.py +22 -19
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/song.py +25 -25
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/types.py +2 -2
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/uploaded_video.py +18 -11
- {gamdl-3.1 → gamdl-3.2}/gamdl.egg-info/PKG-INFO +1 -1
- {gamdl-3.1 → gamdl-3.2}/pyproject.toml +1 -1
- gamdl-3.1/gamdl/__init__.py +0 -1
- gamdl-3.1/gamdl/interface/interface.py +0 -636
- {gamdl-3.1 → gamdl-3.2}/LICENSE +0 -0
- {gamdl-3.1 → gamdl-3.2}/README.md +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/__main__.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/api/__init__.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/api/apple_music.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/api/constants.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/api/exceptions.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/api/itunes.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/cli/__init__.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/cli/cli_config.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/cli/config_file.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/cli/constants.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/cli/database.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/cli/interactive_prompts.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/cli/utils.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/__init__.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/amdecrypt.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/constants.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/enums.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/music_video.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/song.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/types.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/downloader/uploaded_video.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/__init__.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/base.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/constants.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/enums.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/interface/wvd.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl/utils.py +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl.egg-info/SOURCES.txt +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl.egg-info/dependency_links.txt +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl.egg-info/entry_points.txt +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl.egg-info/requires.txt +0 -0
- {gamdl-3.1 → gamdl-3.2}/gamdl.egg-info/top_level.txt +0 -0
- {gamdl-3.1 → gamdl-3.2}/setup.cfg +0 -0
{gamdl-3.1 → gamdl-3.2}/PKG-INFO
RENAMED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "3.2"
|
|
@@ -17,9 +17,7 @@ from ..downloader import (
|
|
|
17
17
|
AppleMusicMusicVideoDownloader,
|
|
18
18
|
AppleMusicSongDownloader,
|
|
19
19
|
AppleMusicUploadedVideoDownloader,
|
|
20
|
-
DownloadItem,
|
|
21
20
|
GamdlDownloaderDependencyNotFoundError,
|
|
22
|
-
GamdlDownloaderFlatFilterExcludedError,
|
|
23
21
|
GamdlDownloaderMediaFileExistsError,
|
|
24
22
|
GamdlDownloaderSyncedLyricsOnlyError,
|
|
25
23
|
)
|
|
@@ -31,6 +29,7 @@ from ..interface import (
|
|
|
31
29
|
AppleMusicUploadedVideoInterface,
|
|
32
30
|
GamdlInterfaceArtistMediaTypeError,
|
|
33
31
|
GamdlInterfaceDecryptionNotAvailableError,
|
|
32
|
+
GamdlInterfaceFlatFilterExcludedError,
|
|
34
33
|
GamdlInterfaceFormatNotAvailableError,
|
|
35
34
|
GamdlInterfaceMediaNotStreamableError,
|
|
36
35
|
GamdlInterfaceUrlParseError,
|
|
@@ -152,7 +151,6 @@ async def main(config: CliConfig):
|
|
|
152
151
|
synced_lyrics_format=config.synced_lyrics_format,
|
|
153
152
|
codec_priority=config.song_codec_piority,
|
|
154
153
|
use_album_date=config.use_album_date,
|
|
155
|
-
skip_decryption_key_non_legacy=config.use_wrapper,
|
|
156
154
|
skip_stream_info=config.synced_lyrics_only,
|
|
157
155
|
ask_codec_function=interactive_prompts.ask_song_codec,
|
|
158
156
|
)
|
|
@@ -262,8 +260,21 @@ async def main(config: CliConfig):
|
|
|
262
260
|
)
|
|
263
261
|
else "Unknown Title"
|
|
264
262
|
)
|
|
263
|
+
media_type = (
|
|
264
|
+
download_item.media.media_metadata["type"]
|
|
265
|
+
if download_item.media.media_metadata
|
|
266
|
+
else None
|
|
267
|
+
)
|
|
265
268
|
|
|
266
|
-
|
|
269
|
+
if download_item.media.partial and media_type in {
|
|
270
|
+
None,
|
|
271
|
+
"songs",
|
|
272
|
+
"library-songs",
|
|
273
|
+
"music-videos",
|
|
274
|
+
"library-music-videos",
|
|
275
|
+
"uploaded-videos",
|
|
276
|
+
}:
|
|
277
|
+
track_log.info(f'Downloading "{media_title}"')
|
|
267
278
|
|
|
268
279
|
try:
|
|
269
280
|
await downloader.download(download_item)
|
|
@@ -275,7 +286,7 @@ async def main(config: CliConfig):
|
|
|
275
286
|
GamdlDownloaderSyncedLyricsOnlyError,
|
|
276
287
|
GamdlDownloaderMediaFileExistsError,
|
|
277
288
|
GamdlDownloaderDependencyNotFoundError,
|
|
278
|
-
|
|
289
|
+
GamdlInterfaceFlatFilterExcludedError,
|
|
279
290
|
) as e:
|
|
280
291
|
track_log.warning(f'Skipping "{media_title}": {e}')
|
|
281
292
|
continue
|
|
@@ -27,7 +27,6 @@ class AppleMusicBaseDownloader:
|
|
|
27
27
|
mp4decrypt_path: str = "mp4decrypt",
|
|
28
28
|
ffmpeg_path: str = "ffmpeg",
|
|
29
29
|
mp4box_path: str = "MP4Box",
|
|
30
|
-
use_wrapper: bool = False,
|
|
31
30
|
wrapper_decrypt_ip: str = "127.0.0.1:10020",
|
|
32
31
|
download_mode: DownloadMode = DownloadMode.YTDLP,
|
|
33
32
|
album_folder_template: str = "{album_artist}/{album}",
|
|
@@ -50,7 +49,6 @@ class AppleMusicBaseDownloader:
|
|
|
50
49
|
self.mp4decrypt_path = mp4decrypt_path
|
|
51
50
|
self.ffmpeg_path = ffmpeg_path
|
|
52
51
|
self.mp4box_path = mp4box_path
|
|
53
|
-
self.use_wrapper = use_wrapper
|
|
54
52
|
self.wrapper_decrypt_ip = wrapper_decrypt_ip
|
|
55
53
|
self.download_mode = download_mode
|
|
56
54
|
self.album_folder_template = album_folder_template
|
|
@@ -5,12 +5,10 @@ from typing import AsyncGenerator
|
|
|
5
5
|
import structlog
|
|
6
6
|
|
|
7
7
|
from ..interface.types import AppleMusicMedia
|
|
8
|
-
|
|
9
8
|
from .constants import TEMP_PATH_TEMPLATE
|
|
10
9
|
from .enums import DownloadMode, RemuxMode
|
|
11
10
|
from .exceptions import (
|
|
12
11
|
GamdlDownloaderDependencyNotFoundError,
|
|
13
|
-
GamdlDownloaderFlatFilterExcludedError,
|
|
14
12
|
GamdlDownloaderMediaFileExistsError,
|
|
15
13
|
GamdlDownloaderSyncedLyricsOnlyError,
|
|
16
14
|
)
|
|
@@ -60,7 +58,10 @@ class AppleMusicDownloader:
|
|
|
60
58
|
self,
|
|
61
59
|
media: AppleMusicMedia,
|
|
62
60
|
) -> DownloadItem:
|
|
63
|
-
if media.error
|
|
61
|
+
if media.error:
|
|
62
|
+
return DownloadItem(media)
|
|
63
|
+
|
|
64
|
+
if media.partial:
|
|
64
65
|
return DownloadItem(media)
|
|
65
66
|
|
|
66
67
|
elif media.media_metadata["type"] in {"songs", "library-songs"}:
|
|
@@ -80,10 +81,8 @@ class AppleMusicDownloader:
|
|
|
80
81
|
if item.media.error:
|
|
81
82
|
raise item.media.error
|
|
82
83
|
|
|
83
|
-
if item.media.
|
|
84
|
-
|
|
85
|
-
item.media.media_metadata["id"]
|
|
86
|
-
)
|
|
84
|
+
if item.media.partial:
|
|
85
|
+
return
|
|
87
86
|
|
|
88
87
|
await self._initial_processing(item)
|
|
89
88
|
await self._download(item)
|
|
@@ -18,8 +18,3 @@ class GamdlDownloaderMediaFileExistsError(GamdlDownloaderError):
|
|
|
18
18
|
class GamdlDownloaderDependencyNotFoundError(GamdlDownloaderError):
|
|
19
19
|
def __init__(self, dependency_name: str) -> None:
|
|
20
20
|
super().__init__(f"Required dependency not found: {dependency_name}")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class GamdlDownloaderFlatFilterExcludedError(GamdlDownloaderError):
|
|
24
|
-
def __init__(self, media_id: str) -> None:
|
|
25
|
-
super().__init__(f"Media is excluded by flat filter: {media_id}")
|
|
@@ -38,5 +38,14 @@ class GamdlInterfaceUrlParseError(GamdlInterfaceError):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class GamdlInterfaceArtistMediaTypeError(GamdlInterfaceError):
|
|
41
|
-
def __init__(self, media_type: str):
|
|
42
|
-
super().__init__(
|
|
41
|
+
def __init__(self, media_id: str, media_type: str):
|
|
42
|
+
super().__init__(
|
|
43
|
+
f"Artist has no media of type (media ID: {media_id}): {media_type}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class GamdlInterfaceFlatFilterExcludedError(GamdlInterfaceError):
|
|
48
|
+
def __init__(self, media_id: str, result: Any):
|
|
49
|
+
super().__init__(f"Media excluded by flat filter: {media_id}")
|
|
50
|
+
|
|
51
|
+
self.result = result
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, AsyncGenerator, Callable
|
|
3
|
+
|
|
4
|
+
import structlog
|
|
5
|
+
|
|
6
|
+
from ..utils import safe_gather
|
|
7
|
+
from .constants import VALID_URL_PATTERN
|
|
8
|
+
from .enums import ArtistMediaType
|
|
9
|
+
from .exceptions import (
|
|
10
|
+
GamdlInterfaceMediaNotAllowedError,
|
|
11
|
+
GamdlInterfaceUrlParseError,
|
|
12
|
+
GamdlInterfaceArtistMediaTypeError,
|
|
13
|
+
GamdlInterfaceFlatFilterExcludedError,
|
|
14
|
+
)
|
|
15
|
+
from .music_video import AppleMusicMusicVideoInterface
|
|
16
|
+
from .song import AppleMusicSongInterface
|
|
17
|
+
from .types import AppleMusicMedia, AppleMusicUrlInfo
|
|
18
|
+
from .uploaded_video import AppleMusicUploadedVideoInterface
|
|
19
|
+
|
|
20
|
+
logger = structlog.get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AppleMusicInterface:
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
song: AppleMusicSongInterface,
|
|
27
|
+
music_video: AppleMusicMusicVideoInterface,
|
|
28
|
+
uploaded_video: AppleMusicUploadedVideoInterface,
|
|
29
|
+
artist_select_media_type_function: (
|
|
30
|
+
Callable[[list[ArtistMediaType], dict], ArtistMediaType | None] | None
|
|
31
|
+
) = None,
|
|
32
|
+
artist_select_items_function: (
|
|
33
|
+
Callable[[ArtistMediaType, list[dict]], list[dict] | None] | None
|
|
34
|
+
) = None,
|
|
35
|
+
flat_filter_function: Callable[[dict], Any] | None = None,
|
|
36
|
+
concurrency: int = 1,
|
|
37
|
+
disallowed_media_types: list[str] | None = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
self.song = song
|
|
40
|
+
self.music_video = music_video
|
|
41
|
+
self.uploaded_video = uploaded_video
|
|
42
|
+
self.artist_select_media_type_function = artist_select_media_type_function
|
|
43
|
+
self.artist_select_items_function = artist_select_items_function
|
|
44
|
+
self.flat_filter_function = flat_filter_function
|
|
45
|
+
self.concurrency = concurrency
|
|
46
|
+
self.disallowed_media_types = disallowed_media_types
|
|
47
|
+
|
|
48
|
+
self.base = song.base
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def get_url_info(url: str) -> AppleMusicUrlInfo | None:
|
|
52
|
+
log = logger.bind(action="get_url_info", url=url)
|
|
53
|
+
|
|
54
|
+
match = VALID_URL_PATTERN.match(url)
|
|
55
|
+
if not match:
|
|
56
|
+
log.debug("invalid_url_pattern")
|
|
57
|
+
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
url_match = AppleMusicUrlInfo(
|
|
61
|
+
**match.groupdict(),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
log.debug("success", url_info=url_match)
|
|
65
|
+
|
|
66
|
+
return url_match
|
|
67
|
+
|
|
68
|
+
async def _run_flat_filter(self, media: AppleMusicMedia) -> None:
|
|
69
|
+
if not self.flat_filter_function or not media.partial:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
result = self.flat_filter_function(media.media_metadata)
|
|
73
|
+
if asyncio.iscoroutine(result):
|
|
74
|
+
result = await result
|
|
75
|
+
|
|
76
|
+
if result:
|
|
77
|
+
raise GamdlInterfaceFlatFilterExcludedError(media.media_id, result)
|
|
78
|
+
|
|
79
|
+
def _run_media_type_filter(self, media: AppleMusicMedia) -> None:
|
|
80
|
+
if not self.disallowed_media_types or not media.partial:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
if media.media_metadata["type"] in self.disallowed_media_types:
|
|
84
|
+
raise GamdlInterfaceMediaNotAllowedError(
|
|
85
|
+
media.media_metadata["type"],
|
|
86
|
+
media.media_id,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def _collect_generator(
|
|
90
|
+
self, generator_or_coroutine: AsyncGenerator[AppleMusicMedia, None]
|
|
91
|
+
) -> list[AppleMusicMedia]:
|
|
92
|
+
results = []
|
|
93
|
+
async for result in generator_or_coroutine:
|
|
94
|
+
results.append(result)
|
|
95
|
+
return results
|
|
96
|
+
|
|
97
|
+
async def _get_song_media(
|
|
98
|
+
self,
|
|
99
|
+
media_id: str,
|
|
100
|
+
index: int | None = None,
|
|
101
|
+
total: int | None = None,
|
|
102
|
+
media_metadata: dict | None = None,
|
|
103
|
+
playlist_metadata: dict | None = None,
|
|
104
|
+
) -> AsyncGenerator[AppleMusicMedia, None]:
|
|
105
|
+
media = AppleMusicMedia(
|
|
106
|
+
media_id=media_id,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if index is not None:
|
|
110
|
+
media.index = index
|
|
111
|
+
if total is not None:
|
|
112
|
+
media.total = total
|
|
113
|
+
|
|
114
|
+
media.media_metadata = media_metadata
|
|
115
|
+
media.playlist_metadata = playlist_metadata
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
async for media in self.song.get_media(media):
|
|
119
|
+
yield media
|
|
120
|
+
|
|
121
|
+
self._run_media_type_filter(media)
|
|
122
|
+
await self._run_flat_filter(media)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
media.partial = False
|
|
125
|
+
media.error = e
|
|
126
|
+
yield media
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
async def _get_music_video_media(
|
|
130
|
+
self,
|
|
131
|
+
media_id: str,
|
|
132
|
+
index: int | None = None,
|
|
133
|
+
total: int | None = None,
|
|
134
|
+
media_metadata: dict | None = None,
|
|
135
|
+
playlist_metadata: dict | None = None,
|
|
136
|
+
) -> AsyncGenerator[AppleMusicMedia, None]:
|
|
137
|
+
media = AppleMusicMedia(
|
|
138
|
+
media_id=media_id,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if index is not None:
|
|
142
|
+
media.index = index
|
|
143
|
+
if total is not None:
|
|
144
|
+
media.total = total
|
|
145
|
+
|
|
146
|
+
media.media_metadata = media_metadata
|
|
147
|
+
media.playlist_metadata = playlist_metadata
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
async for media in self.music_video.get_media(media):
|
|
151
|
+
yield media
|
|
152
|
+
|
|
153
|
+
self._run_media_type_filter(media)
|
|
154
|
+
await self._run_flat_filter(media)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
media.partial = False
|
|
157
|
+
media.error = e
|
|
158
|
+
yield media
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
async def _get_uploaded_video_media(
|
|
162
|
+
self,
|
|
163
|
+
media_id: str,
|
|
164
|
+
) -> AsyncGenerator[AppleMusicMedia, None]:
|
|
165
|
+
media = AppleMusicMedia(
|
|
166
|
+
media_id=media_id,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
async for media in self.music_video.get_media(media):
|
|
171
|
+
yield
|
|
172
|
+
|
|
173
|
+
self._run_media_type_filter(media)
|
|
174
|
+
await self._run_flat_filter(media)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
media.partial = False
|
|
177
|
+
media.error = e
|
|
178
|
+
yield media
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
async def _get_album_media(
|
|
182
|
+
self,
|
|
183
|
+
media_id: str,
|
|
184
|
+
is_library: bool = False,
|
|
185
|
+
) -> AsyncGenerator[AppleMusicMedia, None]:
|
|
186
|
+
base_media = AppleMusicMedia(media_id)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
base_media.media_metadata = (
|
|
190
|
+
await self.base.apple_music_api.get_library_album(
|
|
191
|
+
media_id,
|
|
192
|
+
)
|
|
193
|
+
if is_library
|
|
194
|
+
else await self.base.apple_music_api.get_album(
|
|
195
|
+
media_id,
|
|
196
|
+
)
|
|
197
|
+
)["data"][0]
|
|
198
|
+
|
|
199
|
+
self._run_media_type_filter(base_media)
|
|
200
|
+
await self._run_flat_filter(base_media)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
base_media.partial = False
|
|
203
|
+
base_media.error = e
|
|
204
|
+
yield base_media
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
yield base_media
|
|
208
|
+
|
|
209
|
+
tracks = base_media.media_metadata["relationships"]["tracks"]["data"]
|
|
210
|
+
tasks = [
|
|
211
|
+
(
|
|
212
|
+
self._get_song_media(
|
|
213
|
+
media_id=track["id"],
|
|
214
|
+
index=index,
|
|
215
|
+
total=base_media.media_metadata["attributes"]["trackCount"],
|
|
216
|
+
media_metadata=track,
|
|
217
|
+
)
|
|
218
|
+
if track["type"] in {"songs", "library-songs"}
|
|
219
|
+
else self._get_music_video_media(
|
|
220
|
+
media_id=track["id"],
|
|
221
|
+
index=index,
|
|
222
|
+
total=base_media.media_metadata["attributes"]["trackCount"],
|
|
223
|
+
media_metadata=track,
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
for index, track in enumerate(tracks)
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
if self.concurrency == 1:
|
|
230
|
+
for task in tasks:
|
|
231
|
+
async for media in task:
|
|
232
|
+
yield media
|
|
233
|
+
else:
|
|
234
|
+
collected_tasks = [self._collect_generator(task) for task in tasks]
|
|
235
|
+
batches = await safe_gather(*collected_tasks, limit=self.concurrency)
|
|
236
|
+
for batch in batches:
|
|
237
|
+
for media in batch:
|
|
238
|
+
yield media
|
|
239
|
+
|
|
240
|
+
async def _get_playlist_media(
|
|
241
|
+
self,
|
|
242
|
+
media_id: str,
|
|
243
|
+
is_library: bool = False,
|
|
244
|
+
) -> AsyncGenerator[AppleMusicMedia, None]:
|
|
245
|
+
base_media = AppleMusicMedia(media_id)
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
base_media.media_metadata = (
|
|
249
|
+
await self.base.apple_music_api.get_library_playlist(
|
|
250
|
+
media_id,
|
|
251
|
+
)
|
|
252
|
+
if is_library
|
|
253
|
+
else await self.base.apple_music_api.get_playlist(
|
|
254
|
+
media_id,
|
|
255
|
+
)
|
|
256
|
+
)["data"][0]
|
|
257
|
+
|
|
258
|
+
self._run_media_type_filter(base_media)
|
|
259
|
+
await self._run_flat_filter(base_media)
|
|
260
|
+
|
|
261
|
+
tracks = base_media.media_metadata["relationships"]["tracks"]["data"]
|
|
262
|
+
next_uri = base_media.media_metadata["relationships"]["tracks"].get("next")
|
|
263
|
+
href_uri = base_media.media_metadata["relationships"]["tracks"].get("href")
|
|
264
|
+
while next_uri:
|
|
265
|
+
extended_data = await self.base.apple_music_api.get_extended_api_data(
|
|
266
|
+
next_uri,
|
|
267
|
+
href_uri,
|
|
268
|
+
)
|
|
269
|
+
tracks.extend(extended_data["data"])
|
|
270
|
+
next_uri = extended_data.get("next")
|
|
271
|
+
except Exception as e:
|
|
272
|
+
base_media.partial = False
|
|
273
|
+
base_media.error = e
|
|
274
|
+
yield base_media
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
yield base_media
|
|
278
|
+
|
|
279
|
+
tasks = [
|
|
280
|
+
(
|
|
281
|
+
self._get_song_media(
|
|
282
|
+
media_id=track["id"],
|
|
283
|
+
index=index,
|
|
284
|
+
total=base_media.media_metadata["attributes"]["trackCount"],
|
|
285
|
+
media_metadata=track,
|
|
286
|
+
playlist_metadata=base_media.media_metadata,
|
|
287
|
+
)
|
|
288
|
+
if track["type"] in {"songs", "library-songs"}
|
|
289
|
+
else self._get_music_video_media(
|
|
290
|
+
media_id=track["id"],
|
|
291
|
+
index=index,
|
|
292
|
+
total=base_media.media_metadata["attributes"]["trackCount"],
|
|
293
|
+
media_metadata=track,
|
|
294
|
+
playlist_metadata=base_media.media_metadata,
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
for index, track in enumerate(tracks)
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
if self.concurrency == 1:
|
|
301
|
+
for task in tasks:
|
|
302
|
+
async for media in task:
|
|
303
|
+
yield media
|
|
304
|
+
else:
|
|
305
|
+
collected_tasks = [self._collect_generator(task) for task in tasks]
|
|
306
|
+
batches = await safe_gather(*collected_tasks, limit=self.concurrency)
|
|
307
|
+
for batch in batches:
|
|
308
|
+
for media in batch:
|
|
309
|
+
yield media
|
|
310
|
+
|
|
311
|
+
async def _get_artist_media(
|
|
312
|
+
self,
|
|
313
|
+
media_id: str,
|
|
314
|
+
) -> AsyncGenerator[AppleMusicMedia, None]:
|
|
315
|
+
base_media = AppleMusicMedia(media_id)
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
base_media.media_metadata = (
|
|
319
|
+
await self.base.apple_music_api.get_artist(
|
|
320
|
+
media_id,
|
|
321
|
+
)
|
|
322
|
+
)["data"][0]
|
|
323
|
+
|
|
324
|
+
self._run_media_type_filter(base_media)
|
|
325
|
+
await self._run_flat_filter(base_media)
|
|
326
|
+
|
|
327
|
+
if self.artist_select_media_type_function:
|
|
328
|
+
artist_media_type = self.artist_select_media_type_function(
|
|
329
|
+
list(ArtistMediaType),
|
|
330
|
+
base_media.media_metadata,
|
|
331
|
+
)
|
|
332
|
+
if asyncio.iscoroutine(artist_media_type):
|
|
333
|
+
artist_media_type = await artist_media_type
|
|
334
|
+
else:
|
|
335
|
+
artist_media_type = list(ArtistMediaType)[0]
|
|
336
|
+
|
|
337
|
+
relation_key, type_key = artist_media_type.path_key
|
|
338
|
+
|
|
339
|
+
items_relation = base_media.media_metadata.get(relation_key, {}).get(
|
|
340
|
+
type_key, {}
|
|
341
|
+
)
|
|
342
|
+
items = items_relation.get("data", [])
|
|
343
|
+
if not items:
|
|
344
|
+
raise GamdlInterfaceArtistMediaTypeError(
|
|
345
|
+
base_media.media_id,
|
|
346
|
+
str(artist_media_type),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
next_uri = items_relation.get("next")
|
|
350
|
+
href_uri = items_relation.get("href")
|
|
351
|
+
while next_uri:
|
|
352
|
+
extended_data = await self.base.apple_music_api.get_extended_api_data(
|
|
353
|
+
next_uri,
|
|
354
|
+
href_uri,
|
|
355
|
+
)
|
|
356
|
+
items.extend(extended_data.get("data", []))
|
|
357
|
+
next_uri = extended_data.get("next")
|
|
358
|
+
except Exception as e:
|
|
359
|
+
yield AppleMusicMedia(
|
|
360
|
+
media_id=media_id,
|
|
361
|
+
media_metadata=None,
|
|
362
|
+
error=e,
|
|
363
|
+
)
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
yield base_media
|
|
367
|
+
|
|
368
|
+
if self.artist_select_items_function:
|
|
369
|
+
selected_items = self.artist_select_items_function(
|
|
370
|
+
artist_media_type,
|
|
371
|
+
items,
|
|
372
|
+
)
|
|
373
|
+
if asyncio.iscoroutine(selected_items):
|
|
374
|
+
selected_items = await selected_items
|
|
375
|
+
else:
|
|
376
|
+
selected_items = items[:1]
|
|
377
|
+
|
|
378
|
+
tasks = []
|
|
379
|
+
for index, item in enumerate(selected_items):
|
|
380
|
+
if item["type"] in {"songs", "library-songs"}:
|
|
381
|
+
tasks.append(
|
|
382
|
+
self._get_song_media(
|
|
383
|
+
media_id=item["id"],
|
|
384
|
+
index=index,
|
|
385
|
+
total=len(selected_items),
|
|
386
|
+
media_metadata=item,
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
elif item["type"] in {"albums", "library-albums"}:
|
|
390
|
+
tasks.append(
|
|
391
|
+
self._get_album_media(
|
|
392
|
+
media_id=item["id"],
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
else:
|
|
396
|
+
tasks.append(
|
|
397
|
+
self._get_music_video_media(
|
|
398
|
+
media_id=item["id"],
|
|
399
|
+
index=index,
|
|
400
|
+
total=len(selected_items),
|
|
401
|
+
media_metadata=item,
|
|
402
|
+
)
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
if self.concurrency == 1:
|
|
406
|
+
for task in tasks:
|
|
407
|
+
async for media in task:
|
|
408
|
+
yield media
|
|
409
|
+
else:
|
|
410
|
+
collected_tasks = [self._collect_generator(task) for task in tasks]
|
|
411
|
+
batches = await safe_gather(*collected_tasks, limit=self.concurrency)
|
|
412
|
+
for batch in batches:
|
|
413
|
+
for media in batch:
|
|
414
|
+
yield media
|
|
415
|
+
|
|
416
|
+
async def get_media_from_url(
|
|
417
|
+
self,
|
|
418
|
+
url: str,
|
|
419
|
+
) -> AsyncGenerator[AppleMusicMedia, None]:
|
|
420
|
+
url_info = self.get_url_info(url)
|
|
421
|
+
|
|
422
|
+
if not url_info:
|
|
423
|
+
raise GamdlInterfaceUrlParseError(url)
|
|
424
|
+
|
|
425
|
+
if self.disallowed_media_types and url_info.type in self.disallowed_media_types:
|
|
426
|
+
raise GamdlInterfaceMediaNotAllowedError(
|
|
427
|
+
url_info.type,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if url_info.type == "song" or url_info.sub_id:
|
|
431
|
+
async for media in self._get_song_media(
|
|
432
|
+
media_id=url_info.sub_id or url_info.id,
|
|
433
|
+
index=0,
|
|
434
|
+
total=1,
|
|
435
|
+
):
|
|
436
|
+
yield media
|
|
437
|
+
|
|
438
|
+
elif url_info.type == "music-video":
|
|
439
|
+
async for media in self._get_music_video_media(
|
|
440
|
+
media_id=url_info.id,
|
|
441
|
+
index=0,
|
|
442
|
+
total=1,
|
|
443
|
+
):
|
|
444
|
+
yield media
|
|
445
|
+
|
|
446
|
+
elif url_info.type == "album" or url_info.library_type == "albums":
|
|
447
|
+
async for media in self._get_album_media(
|
|
448
|
+
media_id=url_info.library_id or url_info.id,
|
|
449
|
+
is_library=bool(url_info.library_type),
|
|
450
|
+
):
|
|
451
|
+
yield media
|
|
452
|
+
|
|
453
|
+
elif url_info.type == "playlist" or url_info.library_type == "playlist":
|
|
454
|
+
async for media in self._get_playlist_media(
|
|
455
|
+
media_id=url_info.library_id or url_info.id,
|
|
456
|
+
is_library=bool(url_info.library_type),
|
|
457
|
+
):
|
|
458
|
+
yield media
|
|
459
|
+
|
|
460
|
+
elif url_info.type == "post":
|
|
461
|
+
async for media in self._get_uploaded_video_media(
|
|
462
|
+
media_id=url_info.id,
|
|
463
|
+
):
|
|
464
|
+
yield media
|
|
465
|
+
|
|
466
|
+
elif url_info.type == "artist":
|
|
467
|
+
async for media in self._get_artist_media(
|
|
468
|
+
media_id=url_info.id,
|
|
469
|
+
):
|
|
470
|
+
yield media
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import urllib.parse
|
|
3
|
-
from typing import Callable
|
|
3
|
+
from typing import AsyncGenerator, Callable
|
|
4
4
|
|
|
5
5
|
import m3u8
|
|
6
6
|
import structlog
|
|
@@ -378,35 +378,36 @@ class AppleMusicMusicVideoInterface:
|
|
|
378
378
|
|
|
379
379
|
async def get_media(
|
|
380
380
|
self,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
)
|
|
381
|
+
media: AppleMusicMedia,
|
|
382
|
+
) -> AsyncGenerator[AppleMusicMedia, None]:
|
|
383
|
+
if not media.media_metadata:
|
|
384
|
+
media.media_metadata = (
|
|
385
|
+
await self.base.apple_music_api.get_music_video(media.media_id)
|
|
386
|
+
)["data"][0]
|
|
387
|
+
|
|
388
|
+
media.media_id = self.base.parse_catalog_media_id(media.media_metadata)
|
|
389
389
|
|
|
390
|
-
|
|
390
|
+
yield media
|
|
391
|
+
|
|
392
|
+
if not self.base.is_media_streamable(media.media_metadata):
|
|
391
393
|
raise GamdlInterfaceMediaNotStreamableError(media.media_id)
|
|
392
394
|
|
|
393
|
-
if playlist_metadata
|
|
394
|
-
media.playlist_metadata = playlist_metadata
|
|
395
|
+
if media.playlist_metadata:
|
|
395
396
|
media.playlist_tags = self.base.get_playlist_tags(
|
|
396
|
-
playlist_metadata,
|
|
397
|
-
|
|
397
|
+
media.playlist_metadata,
|
|
398
|
+
media.index,
|
|
398
399
|
)
|
|
399
400
|
|
|
400
|
-
media.cover = await self.base.get_cover(
|
|
401
|
+
media.cover = await self.base.get_cover(media.media_metadata)
|
|
401
402
|
|
|
402
|
-
itunes_page_metadata = await self.get_itunes_page_metadata(
|
|
403
|
+
itunes_page_metadata = await self.get_itunes_page_metadata(media.media_metadata)
|
|
403
404
|
media.tags = await self.get_tags(
|
|
404
|
-
|
|
405
|
+
media.media_metadata,
|
|
405
406
|
itunes_page_metadata,
|
|
406
407
|
)
|
|
407
408
|
|
|
408
409
|
media.stream_info = await self.get_stream_info(
|
|
409
|
-
|
|
410
|
+
media.media_metadata,
|
|
410
411
|
itunes_page_metadata,
|
|
411
412
|
)
|
|
412
413
|
if not media.stream_info:
|
|
@@ -423,4 +424,6 @@ class AppleMusicMusicVideoInterface:
|
|
|
423
424
|
|
|
424
425
|
media.decryption_key = await self.get_decryption_key(media.stream_info)
|
|
425
426
|
|
|
426
|
-
|
|
427
|
+
media.partial = False
|
|
428
|
+
|
|
429
|
+
yield media
|