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

feat: allow adding cookies from the UI directly and fix the ytdl bug #215

Merged
merged 9 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'

Check warning on line 42 in Contents/Code/youtube_dl_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/youtube_dl_helper.py#L42

Added line #L42 was not covered by tests


def process_youtube(url):
# type: (str) -> Optional[str]
"""
Expand All @@ -41,8 +62,12 @@
>>> 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 @@
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 = [

Check warning on line 85 in Contents/Code/youtube_dl_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/youtube_dl_helper.py#L81-L85

Added lines #L81 - L85 were not covered by tests
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))

Check warning on line 96 in Contents/Code/youtube_dl_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/youtube_dl_helper.py#L94-L96

Added lines #L94 - L96 were not covered by tests

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))

Check warning on line 112 in Contents/Code/youtube_dl_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/youtube_dl_helper.py#L112

Added line #L112 was not covered by tests
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

Check warning on line 143 in Contents/Code/youtube_dl_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/youtube_dl_helper.py#L142-L143

Added lines #L142 - L143 were not covered by tests
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']

Check warning on line 160 in Contents/Code/youtube_dl_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/youtube_dl_helper.py#L159-L160

Added lines #L159 - L160 were not covered by tests

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))

Check warning on line 167 in Contents/Code/youtube_dl_helper.py

View check run for this annotation

Codecov / codecov/patch

Contents/Code/youtube_dl_helper.py#L166-L167

Added lines #L166 - L167 were not covered by tests
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
Loading