From: Marcel van der Veldt Date: Thu, 21 Apr 2022 11:31:03 +0000 (+0200) Subject: Add preview stream feature (#259) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=71e2c9c98520dfb24f4e2f4d89dd16dab381217d;p=music-assistant-server.git Add preview stream feature (#259) --- diff --git a/music_assistant/controllers/stream.py b/music_assistant/controllers/stream.py index c7ef8683..373a465b 100644 --- a/music_assistant/controllers/stream.py +++ b/music_assistant/controllers/stream.py @@ -13,6 +13,7 @@ from music_assistant.helpers.audio import ( check_audio_support, crossfade_pcm_parts, get_media_stream, + get_preview_stream, get_sox_args_for_pcm_stream, get_stream_details, strip_silence, @@ -47,10 +48,18 @@ class StreamController: return f"http://{self._ip}:{self._port}/{queue_id}/{child_player}.{fmt}" return f"http://{self._ip}:{self._port}/{queue_id}.{fmt}" + async def get_preview_url(self, provider: str, track_id: str) -> str: + """Return url to short preview sample.""" + track = await self.mass.music.tracks.get_provider_item(track_id, provider) + if preview := track.metadata.get("preview"): + return preview + return f"http://{self._ip}:{self._port}/preview/{provider}/{track_id}.mp3" + async def setup(self) -> None: """Async initialize of module.""" app = web.Application() + app.router.add_get("/preview/{provider}/{item_id}.mp3", self.serve_preview) app.router.add_get( "/{queue_id}/{player_id}.{format}", self.serve_multi_client_queue_stream, @@ -89,6 +98,18 @@ class StreamController: self.logger.info("Started stream server on port %s", self._port) + async def serve_preview(self, request: web.Request): + """Serve short preview sample.""" + provider = request.match_info["provider"] + item_id = request.match_info["item_id"] + resp = web.StreamResponse( + status=200, reason="OK", headers={"Content-Type": "audio/mp3"} + ) + await resp.prepare(request) + async for _, chunk in get_preview_stream(self.mass, provider, item_id): + await resp.write(chunk) + return resp + async def serve_queue_stream(self, request: web.Request): """Serve queue audio stream to a single player (encoded to fileformat of choice).""" queue_id = request.match_info["queue_id"] diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py index ca07938f..7776535e 100644 --- a/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py @@ -570,3 +570,66 @@ async def get_sox_args_for_pcm_stream( else: output_args = ["-t", output_format.sox_format(), "-"] return input_args + output_args + + +async def get_preview_stream( + mass: MusicAssistant, + provider: str, + track_id: str, +) -> AsyncGenerator[Tuple[bool, bytes], None]: + """Get the audio stream for the given streamdetails.""" + music_prov = mass.music.get_provider(provider) + + streamdetails = await music_prov.get_stream_details(track_id) + + mass.signal_event( + MassEvent( + EventType.STREAM_STARTED, + object_id=streamdetails.provider, + data=streamdetails, + ) + ) + if streamdetails.type == StreamType.EXECUTABLE: + # stream from executable + input_args = [ + streamdetails.path, + "|", + "ffmpeg", + "-hide_banner", + "-loglevel", + "error", + "-f", + streamdetails.content_type.value, + "-i", + "-", + ] + else: + input_args = [ + "ffmpeg", + "-hide_banner", + "-loglevel", + "error", + "-i", + streamdetails.path, + ] + output_args = ["-ss", "30", "-to", "60", "-f", "mp3", "-q:a", "9", "-"] + async with AsyncProcess(input_args + output_args) as proc: + + # yield chunks from stdout + # we keep 1 chunk behind to detect end of stream properly + try: + prev_chunk = b"" + async for chunk in proc.iterate_chunks(): + if prev_chunk: + yield (False, prev_chunk) + prev_chunk = chunk + # send last chunk + yield (True, prev_chunk) + finally: + mass.signal_event( + MassEvent( + EventType.STREAM_ENDED, + object_id=streamdetails.provider, + data=streamdetails, + ) + ) diff --git a/music_assistant/providers/spotify/__init__.py b/music_assistant/providers/spotify/__init__.py index f3da3a1e..e1d93cec 100644 --- a/music_assistant/providers/spotify/__init__.py +++ b/music_assistant/providers/spotify/__init__.py @@ -355,6 +355,8 @@ class SpotifyProvider(MusicProvider): track.artists.append(artist) track.metadata["explicit"] = str(track_obj["explicit"]).lower() + if "preview_url" in track_obj: + track.metadata["preview"] = track_obj["preview_url"] if "external_ids" in track_obj and "isrc" in track_obj["external_ids"]: track.isrc = track_obj["external_ids"]["isrc"] if "album" in track_obj: