Skip to content

Commit

Permalink
final commit for 3.6
Browse files Browse the repository at this point in the history
  • Loading branch information
natumbri committed Jul 9, 2022
1 parent 650f6b4 commit 410a9d4
Show file tree
Hide file tree
Showing 13 changed files with 1,632 additions and 98 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
Changelog
*********

v3.6 (2022-07-09)
========================================

- preload support for mopidy-tubeify


v3.5 (2021-12-20)
========================================

- changes


v3.4 (2021-04-11)
========================================

Expand Down
5 changes: 2 additions & 3 deletions mopidy_youtube/apis/youtube_japi.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
watchVideoPath,
)
from mopidy_youtube.comms import Client
from mopidy_youtube.timeformat import format_duration
from mopidy_youtube.youtube import Video


Expand Down Expand Up @@ -490,9 +491,7 @@ def json_to_items(result_json):

try:
duration_text = video["lengthText"]["simpleText"]
duration = "PT" + Client.format_duration(
re.match(Client.time_regex, duration_text)
)
duration = "PT" + format_duration(duration_text)
logger.debug(f"video {videoId} duration: {duration}")
except Exception as e:
logger.warn(f"video {videoId} no video-time, possibly live: {e}")
Expand Down
64 changes: 34 additions & 30 deletions mopidy_youtube/apis/youtube_music.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json

from concurrent.futures import as_completed
from concurrent.futures.thread import ThreadPoolExecutor
from itertools import repeat
Expand Down Expand Up @@ -34,7 +33,7 @@ def __init__(self, proxy, headers, *args, **kwargs):
else json.dumps(headers)
)
try:
ytmusic = YTMusic(auth=auth)
ytmusic = YTMusic(auth=auth, requests_session=self.session)
except Exception as e:
logger.error("YTMusic init error: %s", str(e))
ytmusic = YTMusic()
Expand Down Expand Up @@ -63,66 +62,72 @@ def list_related_videos(cls, video_id):
returns related videos for a given video_id
"""

logger.debug(
f"youtube_music list_related_videos triggered "
f"ytmusic.get_watch_playlist {video_id}"
)

# this is untested - try to add artist and channel to related
# videos by calling get_song for each related song
# this would be faster with threading, but it all happens in the
# background, so who cares?

# What is better: get_watch_playlist or get_song_related? Are they different?
get_song_related_tracks = []
get_watch_playlist = ytmusic.get_watch_playlist(video_id)
logger.debug(
f"youtube_music list_related_videos triggered "
f"ytmusic.get_watch_playlist: {video_id}"
)
get_watch_playlist = {}

related_browseId = get_watch_playlist["related"]
try:
logger.debug(
f"youtube_music list_related_videos triggered "
f"ytmusic.get_watch_playlist: {video_id}"
)

get_watch_playlist = ytmusic.get_watch_playlist(video_id)
related_browseId = get_watch_playlist.get("related", "none")

except Exception as e:
logger.error(
f"youtube_music list_related_videos get_watch_playlist "
f"error:{e}. videoId: {video_id}"
)

related_videos = []
get_song_related_tracks = []
try:
logger.debug(
f"youtube_music list_related_videos triggered "
f"ytmusic.get_song_related ({related_browseId})"
)
get_song_related_tracks = ytmusic.get_song_related(related_browseId)[0][
"contents"
]

logger.debug(
f"youtube_music list_related_videos triggered "
f"ytmusic.get_song for {len(related_videos)} tracks."
)
related_videos = [
ytmusic.get_song(track["videoId"])["videoDetails"]
for track in get_song_related_tracks
]

logger.debug(
f"youtube_music list_related_videos triggered "
f"ytmusic.get_song_related ({related_browseId}), and ytmusic.get_song "
f"for {len(related_videos)} tracks."
)

except Exception as e:
logger.error(
f"youtube_music list_related_videos error:{e} "
f"Related_browseId: {related_browseId}"
)
related_videos = []

if len(related_videos) < 10:
logger.warn(
f"get_song_related returned {len(related_videos)} tracks. "
f"Trying get_watch_playlist['tracks'] for more"
)
try:
logger.debug(
f"youtube_music list_related_videos triggered "
f"ytmusic.get_song for {len(get_watch_playlist['tracks'])} tracks"
)

related_videos.extend(
[
ytmusic.get_song(track["videoId"])["videoDetails"]
for track in get_watch_playlist["tracks"]
]
)

logger.debug(
f"youtube_music list_related_videos triggered "
f"ytmusic.get_song for {len(get_watch_playlist['tracks'])} tracks"
)
except Exception as e:
logger.error(f"youtube_music list_related_videos error:{e}")

Expand All @@ -146,8 +151,7 @@ def list_related_videos(cls, video_id):
if len(tracks) < 10:
logger.warn(
f"get_song_related and get_watch_playlist only returned "
f"{len(tracks)} tracks. "
f"Trying youtube_japi.jAPI.list_related_videos"
f"{len(tracks)} tracks. Trying youtube_japi.jAPI.list_related_videos"
)
japi_related_videos = youtube_japi.jAPI.list_related_videos(video_id)
tracks.extend(japi_related_videos["items"])
Expand Down Expand Up @@ -229,7 +233,8 @@ def list_playlists(cls, ids):
results = []

logger.debug(
f"youtube_music list_playlists triggered _get_playlist_or_album x {len(ids)}: {ids}"
f"youtube_music list_playlists triggered "
f"_get_playlist_or_album x {len(ids)}: {ids}"
)

with ThreadPoolExecutor() as executor:
Expand Down Expand Up @@ -483,7 +488,6 @@ def yt_listitem_to_playlist(item, channelTitle=None):
"contentDetails": {"itemCount": itemCount},
"artists": item.get("artists", None),
}

if "tracks" in item:
fields = ["artists", "thumbnails"]
[
Expand Down
6 changes: 2 additions & 4 deletions mopidy_youtube/apis/ytm_item_to_video.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import re

from mopidy_youtube import logger
from mopidy_youtube.comms import Client
from mopidy_youtube.timeformat import format_duration


def ytm_item_to_video(item):
Expand Down Expand Up @@ -35,7 +33,7 @@ def _convertMillis(milliseconds):
logger.error(f"youtube_music yt_item_to_video duration error {e}: {item}")

try:
duration = "PT" + Client.format_duration(re.match(Client.time_regex, duration))
duration = "PT" + format_duration(duration)
except Exception as e:
logger.error(
f"youtube_music yt_item_to_video format duration error {e}: {item}"
Expand Down
13 changes: 7 additions & 6 deletions mopidy_youtube/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os

import pykka
from cachetools import TTLCache, cached
from mopidy import backend, httpclient
from mopidy.core import CoreListener
from mopidy.models import Image, Ref, SearchResult, Track, model_json_decoder
Expand Down Expand Up @@ -144,7 +145,12 @@ class YouTubeLibraryProvider(backend.LibraryProvider):
When enabled makes possible to browse public playlists of the channel as well as browse
separate tracks in playlists.
"""
cache_max_len = 4000
cache_ttl = 21600

youtube_library_cache = TTLCache(maxsize=cache_max_len, ttl=cache_ttl)

@cached(cache=youtube_library_cache)
def browse(self, uri):
if uri == "youtube:browse":
return [
Expand Down Expand Up @@ -325,15 +331,10 @@ def lookup(self, uri):
preload = extract_preload_tracks(uri)
if preload:
for track in preload[1]:
logger.info(track)
video = Video.get(track["id"]["videoId"])
minimum_fields = ["title", "length", "channel"]
item, extended_fields = video.extend_fields(track, minimum_fields)
logger.info(f"{extended_fields}")
video._set_api_data(
extended_fields,
item,
)
video._set_api_data(extended_fields, item)
return [self.lookup_video_track(preload[0])]

playlist_id = extract_playlist_id(uri)
Expand Down
17 changes: 0 additions & 17 deletions mopidy_youtube/comms.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@ def init_poolmanager(self, *args, **kwargs):


class Client:
time_regex = (
r"(?:(?:(?P<durationHours>[0-9]+)\:)?"
r"(?P<durationMinutes>[0-9]+)\:"
r"(?P<durationSeconds>[0-9]{2}))"
)

def __init__(self, proxy, headers):
if not hasattr(type(self), "session"):
self._create_session(proxy, headers)
Expand Down Expand Up @@ -58,14 +52,3 @@ def _create_session(
cls.session.mount("https://", adapter)
cls.session.proxies = {"http": proxy, "https": proxy}
cls.session.headers = headers

@classmethod
def format_duration(cls, match):
duration = ""
if match.group("durationHours") is not None:
duration += match.group("durationHours") + "H"
if match.group("durationMinutes") is not None:
duration += match.group("durationMinutes") + "M"
if match.group("durationSeconds") is not None:
duration += match.group("durationSeconds") + "S"
return duration
2 changes: 2 additions & 0 deletions mopidy_youtube/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def format_channel_uri(id) -> str:


def extract_video_id(uri) -> str:
if uri is None:
return ""
if "youtube.com" in uri:
url = urlparse(uri.replace("yt:", "").replace("youtube:", ""))
req = parse_qs(url.query)
Expand Down
13 changes: 11 additions & 2 deletions mopidy_youtube/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ def track_playback_started(self, tl_track):
"better results"
)
return None

logger.debug(f"getting current track id for {track.uri}")
current_track_id = extract_video_id(track.uri)

logger.debug(f"track id {current_track_id}")
if self.max_degrees_of_separation:
if self.degrees_of_separation < self.max_degrees_of_separation:
self.degrees_of_separation += 1
Expand All @@ -81,38 +81,46 @@ def track_playback_started(self, tl_track):
current_track_id = self.base_track_id
self.degrees_of_separation = 0
logger.debug("resetting to autoplay base track id")
logger.debug(f"degrees of sep {self.degrees_of_separation}")

if current_track_id not in autoplayed:
self.base_track_id = current_track_id
autoplayed.append(current_track_id) # avoid replaying track
self.degrees_of_separation = 0
logger.debug("setting new autoplay base id")
logger.debug(f"base track id {self.base_track_id}")

current_track = youtube.Video.get(current_track_id)
logger.debug(f"triggered related videos for {current_track.id}")
current_track.related_videos
logger.debug("getting related videos")
related_videos = current_track.related_videos.get()
logger.debug(
f"autoplayer is adding a track related to {current_track.title.get()}"
)
logger.debug(f"related videos {related_videos}")
# remove already autoplayed
related_videos[:] = [
related_video
for related_video in related_videos
if related_video.id not in autoplayed
]
logger.debug(f"related videos edit 1 {related_videos}")
# remove if track_length is 0 (probably a live video) or None
related_videos[:] = [
related_video
for related_video in related_videos
if related_video.length.get()
]
logger.debug(f"related videos edit 2 {related_videos}")
# remove if too long
if self.max_autoplay_length:
related_videos[:] = [
related_video
for related_video in related_videos
if related_video.length.get() < self.max_autoplay_length
]
logger.debug(f"related videos edit 3{related_videos}")

if len(related_videos) == 0:
logger.warn(
Expand All @@ -122,6 +130,7 @@ def track_playback_started(self, tl_track):
return None
else:
next_video = random.choice(related_videos)
logger.debug(f"next video {next_video.id}")
autoplayed.append(next_video.id)
uri = [format_video_uri(next_video.id)]
tl.add(uris=uri).get()
Expand Down
47 changes: 47 additions & 0 deletions mopidy_youtube/timeformat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import re


def format_duration(duration_text):

time_regex = (
r"(?:(?:(?P<durationHours>[0-9]+)\:)?"
r"(?P<durationMinutes>[0-9]+)\:"
r"(?P<durationSeconds>[0-9]{2}))"
)

match = re.match(time_regex, duration_text)

duration = ""
if match.group("durationHours") is not None:
duration += match.group("durationHours") + "H"
if match.group("durationMinutes") is not None:
duration += match.group("durationMinutes") + "M"
if match.group("durationSeconds") is not None:
duration += match.group("durationSeconds") + "S"

return duration


def ISO8601_to_seconds(iso_duration):

# convert PT1H2M10S to 3730
m = re.search(
r"P((?P<weeks>\d+)W)?"
+ r"((?P<days>\d+)D)?"
+ r"T((?P<hours>\d+)H)?"
+ r"((?P<minutes>\d+)M)?"
+ r"((?P<seconds>\d+)S)?",
iso_duration,
)
if m:
val = (
int(m.group("weeks") or 0) * 604800
+ int(m.group("days") or 0) * 86400
+ int(m.group("hours") or 0) * 3600
+ int(m.group("minutes") or 0) * 60
+ int(m.group("seconds") or 0)
)
else:
val = 0

return val
Loading

0 comments on commit 410a9d4

Please sign in to comment.