@@ -132,6 +132,23 @@ def duration_ms(self) -> int:
132132 return int (timeparse (self .duration ) * 1000 )
133133
134134
135+ def _parse_album_type (raw_type ) -> AlbumType :
136+ text = str (raw_type or "" ).strip ().lower ()
137+ if not text :
138+ return AlbumType .standard
139+ if "single" in text or "单曲" in text :
140+ return AlbumType .single
141+ if text == "ep" or re .search (r"\bep\b" , text ):
142+ return AlbumType .ep
143+ if "live" in text :
144+ return AlbumType .live
145+ if "compilation" in text :
146+ return AlbumType .compilation
147+ if "retrospective" in text :
148+ return AlbumType .retrospective
149+ return AlbumType .standard
150+
151+
135152class YtmusicAlbumSong (BaseModel , YtmusicArtistsMixin , YtmusicDurationMixin ):
136153 title : str
137154 album : str
@@ -144,7 +161,7 @@ def v2_brief_model(self) -> BriefSongModel:
144161 title = self .title ,
145162 artists_name = self .artists_name ,
146163 album_name = self .album ,
147- duration_ms = self .duration ,
164+ duration_ms = self .duration or "" ,
148165 )
149166
150167 def v2_model_with_brief_album (self , album : BriefAlbumModel ) -> SongModelV2 :
@@ -192,7 +209,7 @@ def v2_brief_model(self) -> BriefSongModel:
192209 title = self .title ,
193210 artists_name = self .artists_name ,
194211 album_name = self .album .name if self .album else "" ,
195- duration_ms = self .duration ,
212+ duration_ms = self .duration or "" ,
196213 )
197214 if not song .identifier :
198215 song .state = ModelState .not_exists
@@ -219,9 +236,17 @@ def v2_model(self) -> SongModelV2:
219236
220237class YtmusicWatchPlaylistSong (YtmusicSearchSong ):
221238 year : str # This field exists in get_watch_playlist API.
239+ length : str # watch playlist uses `length` instead of `duration`.
240+ watch_thumbnail : List [SearchNestedThumbnail ] = Field (
241+ default_factory = list , alias = "thumbnail"
242+ ) # watch playlist uses singular key.
222243
223244 def v2_model (self ) -> SongModelV2 :
224245 song = super ().v2_model ()
246+ if song .duration <= 0 and self .length :
247+ song .duration = int (timeparse (self .length ) * 1000 )
248+ if not song .pic_url and self .watch_thumbnail :
249+ song .pic_url = self .watch_thumbnail [- 1 ].url or ""
225250 song .date = self .year or ""
226251 return song
227252
@@ -261,7 +286,7 @@ def v2_brief_model(self) -> BriefSongModel:
261286 title = self .title ,
262287 artists_name = self .artists_name ,
263288 album_name = self .album .name if self .album else "" ,
264- duration_ms = self .duration ,
289+ duration_ms = self .duration or "" ,
265290 )
266291 if not song .identifier :
267292 song .state = ModelState .not_exists
@@ -277,9 +302,7 @@ class YtmusicSearchAlbum(YtmusicSearchBase, YtmusicCoverMixin, YtmusicArtistsMix
277302
278303 @property
279304 def album_type (self ) -> AlbumType :
280- if self .type == "Single" :
281- return AlbumType .single
282- return AlbumType .standard
305+ return _parse_album_type (self .type )
283306
284307 def v2_brief_model (self ) -> BriefAlbumModel :
285308 return BriefAlbumModel (
@@ -334,9 +357,12 @@ def v2_model(self) -> VideoModel:
334357 identifier = self .videoId ,
335358 source = self .source ,
336359 title = self .title ,
337- cover = self .cover ,
360+ cover = self .cover or "" ,
338361 artists = self .v2_brief_artist_models (),
339362 duration = self .duration_ms ,
363+ # YTMusic list APIs expose localized text views (e.g. 2.8B/28亿次观看),
364+ # which is not stable enough for reliable numeric parsing.
365+ play_count = - 1 ,
340366 )
341367
342368
@@ -406,8 +432,14 @@ def v2_model(self, identifier) -> ArtistModelV2:
406432 name = self .name ,
407433 pic_url = (self .thumbnails [0 ].url if self .thumbnails else "" ),
408434 aliases = [],
409- hot_songs = [],
435+ hot_songs = [
436+ song .v2_brief_model ()
437+ for song in (self .songs .results if self .songs else []) or []
438+ ],
410439 description = self .description or "" ,
440+ song_count = - 1 ,
441+ album_count = - 1 ,
442+ mv_count = - 1 ,
411443 )
412444
413445
@@ -422,6 +454,10 @@ class AlbumInfo(BaseModel, YtmusicArtistsMixin, YtmusicCoverMixin):
422454 # ytmusicapi.get_album has this field. Not sure if other api has this field.
423455 description : str = ""
424456
457+ @property
458+ def album_type (self ) -> AlbumType :
459+ return _parse_album_type (self .type )
460+
425461 def v2_model_with_identifier (self , identifier ) -> AlbumModelV2 :
426462 brief_album = BriefAlbumModel (
427463 identifier = identifier ,
@@ -434,7 +470,9 @@ def v2_model_with_identifier(self, identifier) -> AlbumModelV2:
434470 source = self .source ,
435471 name = self .title ,
436472 cover = self .cover ,
473+ type_ = self .album_type ,
437474 songs = [t .v2_model_with_brief_album (brief_album ) for t in self .tracks ],
475+ song_count = self .trackCount if self .trackCount is not None else - 1 ,
438476 artists = self .v2_brief_artist_models (),
439477 description = self .description or "" , # description may be None
440478 released = self .year or "" , # year may be None
0 commit comments