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

Added an option to export channels thumbnails for Jellyfin #449

Merged
merged 12 commits into from
Feb 26, 2024
Merged

Added an option to export channels thumbnails for Jellyfin #449

merged 12 commits into from
Feb 26, 2024

Conversation

InterN0te
Copy link
Contributor

@InterN0te InterN0te commented Dec 10, 2023

Added an option to download channel thumbnails to poster.jpg and season-poster.jpg for Youtube channels :
image

This uses BeautifulSoup to find the thumbnail URL and only works for Youtube channels (playlists don't have thumbnails)

These 2 images are then used by Jellyfin:
image
image

I didn't manage to hide this option while adding a Playlist but it is ignore if it is

@meeb
Copy link
Owner

meeb commented Dec 11, 2023

Thanks! This looks reasonable to add poster support for Jellyfin. I'll comment on inline.

@property
def get_thumbnail_url(self):
if self.source_type==Source.SOURCE_TYPE_YOUTUBE_PLAYLIST:
raise Exception('This source is a playlist so it doesn\'t have thumbnail.')
Copy link
Owner

Choose a reason for hiding this comment

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

Likely this needs nicer error handling. This will bubble up as a 500 probably if someone ticks the tickbox on a playlist.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function is only called if the tickbox is checked AND the source is not a playlist, so it cannot be triggered until it is called from somewhere it should not

Copy link
Owner

Choose a reason for hiding this comment

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

Ah right, yes I see. Could probably be a Django-flavoured exception at least then?

def get_thumbnail_url(self):
if self.source_type==Source.SOURCE_TYPE_YOUTUBE_PLAYLIST:
raise Exception('This source is a playlist so it doesn\'t have thumbnail.')
soup = BeautifulSoup(requests.get(self.url, cookies={'CONSENT': 'YES+1'}).text, "html.parser")
Copy link
Owner

Choose a reason for hiding this comment

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

This is the only concern I have with the commit. How stable is using requests.get to load pages from YouTube? This needs confirmation it's reliable, as well as wrapping in some exception catches.

Copy link
Owner

Choose a reason for hiding this comment

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

Oh and the HTTP status code probably needs a check.

soup = BeautifulSoup(requests.get(self.url, cookies={'CONSENT': 'YES+1'}).text, "html.parser")
data = re.search(r"var ytInitialData = ({.*});", str(soup.prettify())).group(1)
json_data = json.loads(data)
return json_data["header"]["c4TabbedHeaderRenderer"]["avatar"]["thumbnails"][2]["url"]
Copy link
Owner

Choose a reason for hiding this comment

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

This likely needs to be wrapped in a KeyError catch to politely handle YouTube changing their JSON format.

Copy link
Owner

Choose a reason for hiding this comment

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

This reminded me, I believe yt-dlp extracts the posters/banners for channels and adds them to the metadata for media items. There are usually very large images in the "thumbnails" metadata key with resolutions of 1920x1080. Are these the same as the posters fetched above? Could these be used instead?

Where possible it would be highly preferable to use yt-dlp's well maintained upstream for features.

raise Exception('This source is a playlist so it doesn\'t have thumbnail.')
soup = BeautifulSoup(requests.get(self.url, cookies={'CONSENT': 'YES+1'}).text, "html.parser")
data = re.search(r"var ytInitialData = ({.*});", str(soup.prettify())).group(1)
json_data = json.loads(data)
Copy link
Owner

Choose a reason for hiding this comment

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

And wrap this to catch ValueError and TypeError in case the JSON found is invalid.

@InterN0te
Copy link
Contributor Author

You're right, YT-DLP can do it. I switch to use this

Test :

import json
import yt_dlp

URL = 'https://www.youtube.com/playlist?list=PLyHWd4LxGoS8cdwh0unZXqVA3AqHoeDPU'

ydl_opts = {'extract_flat': True}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    channel_info = ydl.extract_info(URL, download=False)
    
    banner_url = None
    avatar_url = None
    for thumbnail in channel_info['thumbnails']:
        if thumbnail['id'] == 'banner_uncropped':
            banner_url = thumbnail['url']
        if thumbnail['id'] == 'avatar_uncropped':
            avatar_url = thumbnail['url']
        if banner_url != None and avatar_url != None:
            break

print(banner_url)
print(avatar_url)

@InterN0te
Copy link
Contributor Author

It is now perfect :
image

image

I still have to look for the Django-flavoured exception on playlist images request
I have no clue on how to do it. Is it just a declaration in the file tubesync/common/errors.py ?

@@ -35,6 +35,35 @@ def get_yt_opts():
opts.update({'cookiefile': cookie_file_path})
return opts

def get_channel_image_info(url):
Copy link
Owner

Choose a reason for hiding this comment

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

One final comment here, this info is almost certainly in the metadata stored in Media.metadata - you can likely just load it straight out of the JSON without having to invoke yt-dlp at all.

@meeb
Copy link
Owner

meeb commented Dec 12, 2023

I still have to look for the Django-flavoured exception on playlist images request I have no clue on how to do it. Is it just a declaration in the file tubesync/common/errors.py ?

Some exceptions defined in Django are handled better (auto-modify HTTP status codes, logging, etc), that's all I meant. Raise a SuspiciousOperation exception rather than a plain Exception:

https://docs.djangoproject.com/en/4.2/ref/exceptions/#suspiciousoperation

@meeb meeb merged commit 4c02d45 into meeb:main Feb 26, 2024
@meeb
Copy link
Owner

meeb commented Feb 26, 2024

I'll merge this for now, thanks for the feature PR, most appreciated. I'll rework your function that uses yt-dlp again to use the already downloaded metadata at a later date.

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.

2 participants