Skip to content
This repository has been archived by the owner on Oct 13, 2024. It is now read-only.

Commit

Permalink
feat: allow adding cookies from the UI directly and fix the ytdl bug (#…
Browse files Browse the repository at this point in the history
…215)

Co-authored-by: ReenigneArcher <[email protected]>
  • Loading branch information
zdimension and ReenigneArcher authored Nov 14, 2023
1 parent aba0560 commit 400052c
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 63 deletions.
1 change: 0 additions & 1 deletion Contents/Code/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
plugin_support_directory = os.path.join(app_support_directory, 'Plug-in Support')
plugin_support_data_directory = os.path.join(plugin_support_directory, 'Data')
themerr_data_directory = os.path.join(plugin_support_data_directory, plugin_identifier, 'DataItems')
cookie_jar_file = os.path.join(plugin_support_data_directory, plugin_identifier, 'HTTPCookies')

contributes_to = [
'tv.plex.agents.movie',
Expand Down
1 change: 1 addition & 0 deletions Contents/Code/default_prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
int_plexapi_upload_threads='3',
str_youtube_user='',
str_youtube_passwd='',
str_youtube_cookies='',
enum_webapp_locale='en',
str_webapp_http_host='0.0.0.0',
int_webapp_http_port='9494',
Expand Down
171 changes: 112 additions & 59 deletions Contents/Code/youtube_dl_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

# standard imports
import logging
import json
import os
import tempfile

# plex debugging
try:
Expand All @@ -13,14 +16,32 @@
from plexhints.prefs_kit import Prefs # prefs kit

# imports from Libraries\Shared
from constants import plugin_identifier, cookie_jar_file
from constants import plugin_identifier, plugin_support_data_directory
from typing import Optional
import youtube_dl

# get the plugin logger
plugin_logger = logging.getLogger(plugin_identifier)


def nsbool(value):
# type: (bool) -> str
"""
Format a boolean value for a Netscape cookie jar file.
Parameters
----------
value : bool
The boolean value to format.
Returns
-------
str
'TRUE' or 'FALSE'.
"""
return 'TRUE' if value else 'FALSE'


def process_youtube(url):
# type: (str) -> Optional[str]
"""
Expand All @@ -41,8 +62,12 @@ def process_youtube(url):
>>> process_youtube(url='https://www.youtube.com/watch?v=dQw4w9WgXcQ')
...
"""

cookie_jar_file = tempfile.NamedTemporaryFile(dir=plugin_support_data_directory, delete=False)
cookie_jar_file.write('# Netscape HTTP Cookie File\n')

youtube_dl_params = dict(
cookiefile=cookie_jar_file,
cookiefile=cookie_jar_file.name,
logger=plugin_logger,
outtmpl=u'%(id)s.%(ext)s',
password=Prefs['str_youtube_passwd'] if Prefs['str_youtube_passwd'] else None,
Expand All @@ -51,64 +76,92 @@ def process_youtube(url):
youtube_include_dash_manifest=False,
)

ydl = youtube_dl.YoutubeDL(params=youtube_dl_params)

with ydl:
if Prefs['str_youtube_cookies']:
try:
result = ydl.extract_info(
url=url,
download=False # We just want to extract the info
)
except Exception as exc:
if isinstance(exc, youtube_dl.utils.ExtractorError) and exc.expected:
Log.Info('YDL returned YT error while downloading %s: %s' % (url, exc))
else:
Log.Exception('YDL returned an unexpected error while downloading %s: %s' % (url, exc))
return None

if 'entries' in result:
# Can be a playlist or a list of videos
video_data = result['entries'][0]
else:
# Just a video
video_data = result

selected = {
'opus': {
'size': 0,
'audio_url': None
},
'mp4a': {
'size': 0,
'audio_url': None
},
}
if video_data:
for fmt in video_data['formats']: # loop through formats, select largest audio size for better quality
if 'audio only' in fmt['format']:
if 'opus' == fmt['acodec']:
temp_codec = 'opus'
elif 'mp4a' == fmt['acodec'].split('.')[0]:
temp_codec = 'mp4a'
cookies = json.loads(Prefs['str_youtube_cookies'])
for cookie in cookies:
include_subdom = cookie['domain'].startswith('.')
expiry = int(cookie.get('expiry', 0))
values = [
cookie['domain'],
nsbool(include_subdom),
cookie['path'],
nsbool(cookie['secure']),
str(expiry),
cookie['name'],
cookie['value']
]
cookie_jar_file.write('{}\n'.format('\t'.join(values)))
except Exception as e:
Log.Exception('Failed to write YouTube cookies to file, will try anyway. Error: {}'.format(e))

cookie_jar_file.flush()
cookie_jar_file.close()

try:
ydl = youtube_dl.YoutubeDL(params=youtube_dl_params)

with ydl:
try:
result = ydl.extract_info(
url=url,
download=False # We just want to extract the info
)
except Exception as exc:
if isinstance(exc, youtube_dl.utils.ExtractorError) and exc.expected:
Log.Info('YDL returned YT error while downloading {}: {}'.format(url, exc))
else:
Log.Debug('Unknown codec: %s' % fmt['acodec'])
continue # unknown codec
filesize = int(fmt['filesize'])
if filesize > selected[temp_codec]['size']:
selected[temp_codec]['size'] = filesize
selected[temp_codec]['audio_url'] = fmt['url']

audio_url = None

if 0 < selected['opus']['size'] > selected['mp4a']['size']:
audio_url = selected['opus']['audio_url']
elif 0 < selected['mp4a']['size'] > selected['opus']['size']:
audio_url = selected['mp4a']['audio_url']

if audio_url and Prefs['bool_prefer_mp4a_codec']: # mp4a codec is preferred
if selected['mp4a']['audio_url']: # mp4a codec is available
audio_url = selected['mp4a']['audio_url']
elif selected['opus']['audio_url']: # fallback to opus :(
Log.Exception('YDL returned an unexpected error while downloading {}: {}'.format(url, exc))
return None

if 'entries' in result:
# Can be a playlist or a list of videos
video_data = result['entries'][0]
else:
# Just a video
video_data = result

selected = {
'opus': {
'size': 0,
'audio_url': None
},
'mp4a': {
'size': 0,
'audio_url': None
},
}
if video_data:
for fmt in video_data['formats']: # loop through formats, select largest audio size for better quality
if 'audio only' in fmt['format']:
if 'opus' == fmt['acodec']:
temp_codec = 'opus'
elif 'mp4a' == fmt['acodec'].split('.')[0]:
temp_codec = 'mp4a'
else:
Log.Debug('Unknown codec: %s' % fmt['acodec'])
continue # unknown codec
filesize = int(fmt['filesize'])
if filesize > selected[temp_codec]['size']:
selected[temp_codec]['size'] = filesize
selected[temp_codec]['audio_url'] = fmt['url']

audio_url = None

if 0 < selected['opus']['size'] > selected['mp4a']['size']:
audio_url = selected['opus']['audio_url']
elif 0 < selected['mp4a']['size'] > selected['opus']['size']:
audio_url = selected['mp4a']['audio_url']

return audio_url # return None or url found
if audio_url and Prefs['bool_prefer_mp4a_codec']: # mp4a codec is preferred
if selected['mp4a']['audio_url']: # mp4a codec is available
audio_url = selected['mp4a']['audio_url']
elif selected['opus']['audio_url']: # fallback to opus :(
audio_url = selected['opus']['audio_url']

return audio_url # return None or url found
finally:
try:
os.remove(cookie_jar_file.name)
except Exception as e:
Log.Exception('Failed to delete cookie jar file: {}'.format(e))
7 changes: 7 additions & 0 deletions Contents/DefaultPrefs.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@
"option": "hidden",
"secure": "true"
},
{
"id": "str_youtube_cookies",
"type": "text",
"label": "YouTube Cookies (JSON format)",
"default": "",
"secure": "true"
},
{
"id": "enum_webapp_locale",
"type": "enum",
Expand Down
9 changes: 6 additions & 3 deletions docs/source/about/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ Adding your YouTube credentials (e-mail and password) in Themerr's preference ma
YouTube also sometimes changes the way its login page works, preventing YouTube-DL from using those credentials.

A workaround is to login in a web browser, and then export your YouTube cookies with a tool such as `Get cookies.txt
locally <https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc>`__. You
should then add the exported cookies to the ``HTTPCookies`` file that can be found in the
``Plug-in Support/Data/dev.lizardbyte.themerr-plex/`` directory, starting from the Plex data root.
locally <https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc>`__. Note
that Themerr currently only supports Chromium's JSON export format. In the exporter you use, if prompted, you need to
use the "JSON" or "Chrome" format.

You can then paste that value in the "YouTube Cookies" field in the plugin preferences page. On the next media update
or scheduled run, the cookies will be used and hopefully videos will start downloading again.

Plugin Logs
-----------
Expand Down
10 changes: 10 additions & 0 deletions docs/source/about/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,16 @@ Description
Default
None

YouTube Cookies
^^^^^^^^^^^^^^^^

Description
The cookies to use for the requests to YouTube. Should be in Chromium JSON export format.
`Example exporter <https://chrome.google.com/webstore/detail/get-cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>`__.

Default
None

Web UI Locale
^^^^^^^^^^^^^

Expand Down

0 comments on commit 400052c

Please sign in to comment.