From da4e67638d35b7f8f2291e8e76476d8c6874b141 Mon Sep 17 00:00:00 2001 From: cosven Date: Sat, 27 Jul 2024 14:08:28 +0800 Subject: [PATCH] refactor naming --- feeluown/player/__init__.py | 5 +- feeluown/player/metadata_assembler.py | 87 +++++++++++++++++++++++++++ feeluown/player/playlist.py | 86 ++------------------------ tests/player/test_fm.py | 4 +- tests/player/test_playlist.py | 12 ++-- 5 files changed, 104 insertions(+), 90 deletions(-) create mode 100644 feeluown/player/metadata_assembler.py diff --git a/feeluown/player/__init__.py b/feeluown/player/__init__.py index afa9330b8..78d14665e 100644 --- a/feeluown/player/__init__.py +++ b/feeluown/player/__init__.py @@ -2,7 +2,8 @@ from .playlist import PlaybackMode, PlaylistRepeatMode, PlaylistShuffleMode from .base_player import State from .mpvplayer import MpvPlayer as Player -from .playlist import PlaylistMode, Playlist, MetadataManager +from .playlist import PlaylistMode, Playlist +from .metadata_assembler import MetadataAssembler from .fm import FM from .radio import SongRadio from .lyric import LiveLyric, parse_lyric_text, Line as LyricLine, Lyric @@ -24,9 +25,9 @@ 'Playlist', 'PlayerPositionDelegate', - 'MetadataManager', 'Metadata', 'MetadataFields', + 'MetadataAssembler', 'LiveLyric', 'parse_lyric_text', diff --git a/feeluown/player/metadata_assembler.py b/feeluown/player/metadata_assembler.py new file mode 100644 index 000000000..d5ffc49f0 --- /dev/null +++ b/feeluown/player/metadata_assembler.py @@ -0,0 +1,87 @@ +import logging +from typing import TYPE_CHECKING + +from feeluown.excs import ResourceNotFound, ModelNotFound +from feeluown.library import reverse, SongModel +from feeluown.player import Metadata, MetadataFields +from feeluown.utils import aio + +if TYPE_CHECKING: + from feeluown.app import App + +logger = logging.getLogger(__name__) + + +class MetadataAssembler: + """Cook and fetch metadata for songs and videos""" + def __init__(self, app: 'App'): + self._app = app + + @staticmethod + def cook_basic_metadata_for_song(song): + return Metadata({ + MetadataFields.uri: reverse(song), + MetadataFields.source: song.source, + MetadataFields.title: song.title_display or '', + # The song.artists_name should return a list of strings + MetadataFields.artists: [song.artists_name_display or ''], + MetadataFields.album: song.album_name_display or '', + }) + + async def fetch_from_song(self, song): + empty_result = ('', '', None) + try: + usong: SongModel = await aio.wait_for( + aio.run_fn(self._app.library.song_upgrade, song), + timeout=1, + ) + except ResourceNotFound: + return empty_result + except: # noqa + logger.exception(f"fetching song's meta failed, song:'{song.title_display}'") + return empty_result + return usong.pic_url, usong.date, usong.album + + async def fetch_from_album(self, album): + empty_result = ('', '') + try: + album = await aio.wait_for( + aio.run_fn(self._app.library.album_upgrade, album), + timeout=1 + ) + except ResourceNotFound: + return empty_result + except: # noqa + logger.warning( + f"fetching album meta failed, album:{album.name}") + return empty_result + return album.cover, album.released + + async def prepare_for_song(self, song): + metadata = self.cook_basic_metadata_for_song(song) + + artwork, released, album = await self.fetch_from_song(song) + if not (artwork and released) and album is not None: + album_cover, album_released = await self.fetch_from_album(album) + # Try to use album meta first. + artwork = album_cover or artwork + released = album_released or released + metadata[MetadataFields.artwork] = artwork + metadata[MetadataFields.released] = released + + return metadata + + async def prepare_for_video(self, video): + metadata = Metadata({ + # The value of model v1 title_display may be None. + MetadataFields.title: video.title_display or '', + MetadataFields.source: video.source, + MetadataFields.uri: reverse(video), + }) + try: + video = await aio.run_fn(self._app.library.video_upgrade, video) + except ModelNotFound as e: + logger.warning(f"can't get cover of video due to {str(e)}") + else: + metadata[MetadataFields.artwork] = video.cover + return metadata diff --git a/feeluown/player/playlist.py b/feeluown/player/playlist.py index dc07b6eec..82b3910ce 100644 --- a/feeluown/player/playlist.py +++ b/feeluown/player/playlist.py @@ -5,17 +5,16 @@ from enum import IntEnum, Enum from typing import Optional, TYPE_CHECKING -from feeluown.excs import ModelNotFound, ProviderIOError +from feeluown.excs import ProviderIOError from feeluown.utils import aio from feeluown.utils.aio import run_fn, run_afn from feeluown.utils.dispatch import Signal from feeluown.utils.utils import DedupList -from feeluown.player import Metadata, MetadataFields from feeluown.library import ( - MediaNotFound, SongModel, ModelType, ResourceNotFound, VideoModel, + MediaNotFound, SongModel, ModelType, VideoModel, ) from feeluown.media import Media -from feeluown.library import reverse +from .metadata_assembler import MetadataAssembler if TYPE_CHECKING: from feeluown.app import App @@ -27,7 +26,7 @@ class PlaybackMode(IntEnum): """ Playlist playback mode. - .. versiondeprecated:: 3.8.12 + .. deprecated:: 3.8.12 Please use PlaylistRepeatMode and PlaylistShuffleMode instead. """ one_loop = 0 #: One Loop @@ -81,7 +80,7 @@ def __init__(self, app: 'App', songs=None, playback_mode=PlaybackMode.loop, :param playback_mode: :class:`feeluown.player.PlaybackMode` """ self._app = app - self._metadata_mgr = MetadataManager(app) + self._metadata_mgr = MetadataAssembler(app) #: init playlist mode normal self._mode = PlaylistMode.normal @@ -328,7 +327,7 @@ def _get_good_song(self, base=0, random_=False, direction=1, loop=True): """从播放列表中获取一首可以播放的歌曲 :param base: base index - :param random: random strategy or not + :param random_: random strategy or not :param direction: forward if > 0 else backward :param loop: regard the song list as a loop @@ -676,76 +675,3 @@ def cb(future): self._app.player.resume() logger.info(f'play a model ({model}) succeed') task.add_done_callback(cb) - - -class MetadataManager: - def __init__(self, app: 'App'): - self._app = app - - def _prepare_basic_metadata_for_song(self, song): - return Metadata({ - MetadataFields.uri: reverse(song), - MetadataFields.source: song.source, - MetadataFields.title: song.title_display or '', - # The song.artists_name should return a list of strings - MetadataFields.artists: [song.artists_name_display or ''], - MetadataFields.album: song.album_name_display or '', - }) - - async def fetch_from_song(self, song): - empty_result = ('', '', None) - try: - usong: SongModel = await aio.wait_for( - aio.run_fn(self._app.library.song_upgrade, song), - timeout=1, - ) - except ResourceNotFound: - return empty_result - except: # noqa - logger.exception(f"fetching song's meta failed, song:'{song.title_display}'") - return empty_result - return (usong.pic_url, usong.date, usong.album) - - async def fetch_from_album(self, album): - empty_result = ('', '') - try: - album = await aio.wait_for( - aio.run_fn(self._app.library.album_upgrade, album), - timeout=1 - ) - except ResourceNotFound: - return empty_result - except: # noqa - logger.warning( - f"fetching album meta failed, album:{album.name}") - return empty_result - return (album.cover, album.released) - - async def prepare_for_song(self, song): - metadata = self._prepare_basic_metadata_for_song(song) - - artwork, released, album = await self.fetch_from_song(song) - if not (artwork and released) and album is not None: - album_cover, album_released = await self.fetch_from_album(album) - # Try to use album meta first. - artwork = album_cover or artwork - released = album_released or released - metadata[MetadataFields.artwork] = artwork - metadata[MetadataFields.released] = released - - return metadata - - async def prepare_for_video(self, video): - metadata = Metadata({ - # The value of model v1 title_display may be None. - MetadataFields.title: video.title_display or '', - MetadataFields.source: video.source, - MetadataFields.uri: reverse(video), - }) - try: - video = await aio.run_fn(self._app.library.video_upgrade, video) - except ModelNotFound as e: - logger.warning(f"can't get cover of video due to {str(e)}") - else: - metadata[MetadataFields.artwork] = video.cover - return metadata diff --git a/tests/player/test_fm.py b/tests/player/test_fm.py index 421f2c03b..90a3c23c9 100644 --- a/tests/player/test_fm.py +++ b/tests/player/test_fm.py @@ -3,7 +3,7 @@ import pytest from feeluown.excs import ProviderIOError -from feeluown.player import Playlist, PlaylistMode, FM, MetadataManager +from feeluown.player import Playlist, PlaylistMode, FM, MetadataAssembler from feeluown.task import TaskManager @@ -76,7 +76,7 @@ async def test_multiple_eof_reached_signal(app_mock, song, mocker): async def test_reactivate_fm_mode_after_playing_other_songs( app_mock, song, song1, mocker): - mocker.patch.object(MetadataManager, 'prepare_for_song') + mocker.patch.object(MetadataAssembler, 'prepare_for_song') def f(*args, **kwargs): return [song1] diff --git a/tests/player/test_playlist.py b/tests/player/test_playlist.py index 0a620d72a..791e8c561 100644 --- a/tests/player/test_playlist.py +++ b/tests/player/test_playlist.py @@ -6,7 +6,7 @@ from feeluown.library.excs import MediaNotFound from feeluown.player import ( Playlist, PlaylistMode, Player, PlaybackMode, - PlaylistRepeatMode, PlaylistShuffleMode, MetadataManager + PlaylistRepeatMode, PlaylistShuffleMode, MetadataAssembler ) from feeluown.utils.dispatch import Signal @@ -115,7 +115,7 @@ async def test_set_current_song_with_bad_song_1( mock_pure_set_current_song = mocker.patch.object(Playlist, 'pure_set_current_song') mock_mark_as_bad = mocker.patch.object(Playlist, 'mark_as_bad') sentinal = object() - mocker.patch.object(MetadataManager, 'prepare_for_song', return_value=sentinal) + mocker.patch.object(MetadataAssembler, 'prepare_for_song', return_value=sentinal) await pl.a_set_current_song(song2) # A song that has no valid media should be marked as bad assert mock_mark_as_bad.called @@ -131,7 +131,7 @@ async def test_set_current_song_with_bad_song_2( mock_pure_set_current_song = mocker.patch.object(Playlist, 'pure_set_current_song') mock_mark_as_bad = mocker.patch.object(Playlist, 'mark_as_bad') sentinal = object() - mocker.patch.object(MetadataManager, 'prepare_for_song', return_value=sentinal) + mocker.patch.object(MetadataAssembler, 'prepare_for_song', return_value=sentinal) await pl.a_set_current_song(song2) # A song that has no valid media should be marked as bad assert mock_mark_as_bad.called @@ -149,7 +149,7 @@ async def test_set_current_song_with_bad_song_3( mock_pure_set_current_song = mocker.patch.object(Playlist, 'pure_set_current_song') mock_prepare_mv_media = mocker.patch.object(Playlist, '_prepare_mv_media', return_value=media) - mocker.patch.object(MetadataManager, 'prepare_for_song', return_value=metadata) + mocker.patch.object(MetadataAssembler, 'prepare_for_song', return_value=metadata) app_mock.config.ENABLE_MV_AS_STANDBY = 1 pl = Playlist(app_mock) @@ -180,7 +180,7 @@ async def test_set_an_existing_bad_song_as_current_song( song1 is bad, standby is [song2] play song1, song2 should be insert after song1 instead of song """ - mocker.patch.object(MetadataManager, 'prepare_for_song') + mocker.patch.object(MetadataAssembler, 'prepare_for_song') await pl.a_set_current_song(song1) assert pl.list().index(song2) == 2 @@ -298,7 +298,7 @@ async def test_play_next_bad_song(app_mock, song, song1, mocker): be marked as bad. Besides, it should try to find standby. """ mock_pure_set_current_song = mocker.patch.object(Playlist, 'pure_set_current_song') - mocker.patch.object(MetadataManager, 'prepare_for_song', return_value=object()) + mocker.patch.object(MetadataAssembler, 'prepare_for_song', return_value=object()) mock_standby = mocker.patch.object(Playlist, 'find_and_use_standby', return_value=(song1, None))