diff --git a/api/video/youtube.py b/api/video/youtube.py index fe0b8e71..313938cf 100644 --- a/api/video/youtube.py +++ b/api/video/youtube.py @@ -36,6 +36,14 @@ def _server(): def register_routes(bp): + @bp.route("/youtube/video//segments", methods=["GET"]) + def video_youtube_segments(video_id): + """SponsorBlock crowd segments stored for a video (sponsor/intro/outro/…), + so the player can offer skips. [] until the SponsorBlock worker enriches it.""" + from . import get_video_db + return jsonify({"video_id": video_id, + "segments": get_video_db().youtube_video_segments(video_id)}) + @bp.route("/youtube/resolve", methods=["GET"]) def video_youtube_resolve(): """Preview a pasted channel URL without committing. Returns the channel + diff --git a/tests/test_video_api.py b/tests/test_video_api.py index 5ff8736a..bf28c923 100644 --- a/tests/test_video_api.py +++ b/tests/test_video_api.py @@ -334,12 +334,18 @@ def test_enrichment_config_save_load(tmp_path, monkeypatch): try: assert client.get("/api/video/enrichment/config").get_json() == { "tmdb_api_key": "", "tvdb_api_key": "", "omdb_api_key": "", + "fanart_api_key": "", "opensubtitles_api_key": "", + "ryd_enabled": True, "sponsorblock_enabled": True, "billboard_autoplay": True, "watch_region": "US"} client.post("/api/video/enrichment/config", json={"tmdb_api_key": "abc", "tvdb_api_key": "xyz", "omdb_api_key": "om", + "fanart_api_key": "fa", "opensubtitles_api_key": "os", + "ryd_enabled": False, "sponsorblock_enabled": True, "billboard_autoplay": False, "watch_region": "gb"}) assert client.get("/api/video/enrichment/config").get_json() == { "tmdb_api_key": "abc", "tvdb_api_key": "xyz", "omdb_api_key": "om", + "fanart_api_key": "fa", "opensubtitles_api_key": "os", + "ryd_enabled": False, "sponsorblock_enabled": True, "billboard_autoplay": False, "watch_region": "GB"} assert db.get_setting("tmdb_api_key") == "abc" and db.get_setting("omdb_api_key") == "om" assert client.get("/api/video/prefs").get_json() == { diff --git a/webui/static/video/video-detail.js b/webui/static/video/video-detail.js index 826e1a00..6c38d6aa 100644 --- a/webui/static/video/video-detail.js +++ b/webui/static/video/video-detail.js @@ -975,7 +975,10 @@ : ''; var dur = ep.yt_duration ? '' + esc(ep.yt_duration) + '' : ''; var meta = []; - if (ep.view_count) { var yc = window.VideoYoutube; meta.push((yc ? yc.compactCount(ep.view_count) : ep.view_count) + ' views'); } + var yc0 = window.VideoYoutube; + if (ep.view_count) { meta.push((yc0 ? yc0.compactCount(ep.view_count) : ep.view_count) + ' views'); } + if (ep.like_count) { meta.push('👍 ' + (yc0 ? yc0.compactCount(ep.like_count) : ep.like_count)); } + if (ep.dislike_count) { meta.push('👎 ' + (yc0 ? yc0.compactCount(ep.dislike_count) : ep.dislike_count)); } if (ep.air_date) meta.push(fmtDate(ep.air_date)); var wished = !!ep.owned; return '
' + @@ -1373,7 +1376,8 @@ return { episode_number: i + 1, title: v.title, overview: v.description || '', air_date: v.published_at, owned: !!v.wished, has_still: false, still_url: ytProx(v.thumbnail_url), youtube_id: v.youtube_id, - yt_duration: v.duration || '', view_count: v.view_count || 0 }; + yt_duration: v.duration || '', view_count: v.view_count || 0, + like_count: v.like_count || 0, dislike_count: v.dislike_count || 0 }; } function ytDurSecs(d) { if (!d) return 0;