Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow music videos to be imported from Jellyfin #1870

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

shidetian
Copy link

@shidetian shidetian commented Jan 14, 2025

This PR adds basic support for music video type from Jellyfin to allow music only playback. Technically the same approach can be applied to all videos from Jellyfin, but that is unlikely to be desirable for most users. Maybe this should be a setting for what libraries to include.

Note this requires Jc2k/aiojellyfin#2

Maybe also need a note as this requires the user set up in Jellyfin to have transcode permissions.

@shidetian shidetian changed the title feat: allow music videos to be imported feat: allow music videos to be imported from Jellyfin Jan 14, 2025
@@ -454,7 +456,15 @@ async def get_stream_details(
jellyfin_track = await self._client.get_track(item_id)
mimetype = self._media_mime_type(jellyfin_track)
media_stream = jellyfin_track[ITEM_KEY_MEDIA_STREAMS][0]
url = self._client.audio_url(jellyfin_track[ITEM_KEY_ID], SUPPORTED_CONTAINER_FORMATS)
if jellyfin_track[ITEM_KEY_MEDIA_TYPE] == "Video":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the code on dev assumes there is a MediaStream[0] and if there is len(MediaStream) > 1, then the first one is the right one. This has been something I've wanted to change.

I think you looping over all streams is something we should do for all streams, regardless of the ITEM_KEY_MEDIA_TYPE.

Maybe loop once and find a supported codec of type audio, and just stream it as is. If that falls through, loop again and find video containers and transcode those.

If neither matches right now i think its best if we raise a MusicAssistantError.

url = self._client.audio_url(jellyfin_track[ITEM_KEY_ID], SUPPORTED_CONTAINER_FORMATS)
if jellyfin_track[ITEM_KEY_MEDIA_TYPE] == "Video":
for stream in jellyfin_track[ITEM_KEY_MEDIA_STREAMS]:
if stream.get(ITEM_KEY_MEDIA_CODEC) in SUPPORTED_CONTAINER_FORMATS:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theres a couple of bits i'm not sure about here.

  • The for loop doesn't have an else, so if every stream fails this test, it will transcode the last stream in the list regardless. Feels like it should bail if there are no matching streams?
  • I'm surprised that mypy is passing here - .get can return None, and SUPPORTED_CONTAINER_FORMATS is a string, so you'd be doing None in "foo" which is a typing error?

(Anywhere you see us passing strings containing a comma seperated list (SUPPORTED_CONTAINER_FORMATS), its something from the old upstream jellyfin library that needs fixing (list/set of enums?) that we haven't had time to get to yet).

Copy link
Author

@shidetian shidetian Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This code as currently written (unless I am mistaken) keeps the current behavior by choosing the first element if no matching streams are found
  • SUPPORTED_CONTAINER_FORMATS is an array, not string sorry I misread

Copy link
Author

@shidetian shidetian Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mypy might not be passing. I don't think it ran in CI pipeline.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I see it now. It selects the media_stream that matches SUPPORTED_CONTAINER_FORMATS, or it selects the first one.

Then, it transcodes. It doesn't matter which stream we have selected, Jellyfin picks its own.

Then the AudioFormat data is based on either the stream that matches SUPPORTED_CONTAINER_FORMATS, or the first stream in the list. Not the stream the Jellyfin returns.

I think if there are multiple audio or video streams, we actually can't predict which of those Jellyfin will return, so we might set AudioFormat incorrectly (thats a bug that already exists). If there is only 1 stream, and its a video, we can set the content type because we know that we asked for flac?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I found that even you choose the exact same format as the original, Jellyfin always transcodes if the source is a video (hence my comment about demux). Since it will always transcode, I choose flac to maintain the original audio quality. I found that without specifiying the transcode container, Jellyfin just picks ACC as the codec to transcode to. As for what Jellyfin picks as the audio stream if there are multiple, the documented behavior for the Audio endpoint is If omitted the first audio stream will be used. which I assume applies to the universal endpoint as well. Since the AudioFormat ends up being shown on the UI, I choose to report the source's original format even though in actuality it's always going to be flac when playing videos.

As for what happens in the fallback, if it's a video stream, then it will not have a channel param, so the code fails on media_stream[ITEM_KEY_MEDIA_CHANNELS], and playback will fail. Maybe the first stream that contains channels might be better as a fallback since we're transcoding anyways. I didn't test the really pathological case where a video contains no audio stream but that should currently fail the same way.

@marcelveldt marcelveldt marked this pull request as draft January 14, 2025 19:04
@marcelveldt
Copy link
Member

Marked as draft until the discussion in Jc2k/aiojellyfin#2 is settled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants