From b6d155f071bc815be179ada6139c04e0c13e2088 Mon Sep 17 00:00:00 2001 From: "Ethan J. Hadley" Date: Fri, 18 Sep 2026 23:45:26 -0600 Subject: [PATCH] Artist Ripper working --- happy_ripper.py | 109 +++++++++++++- templates/base.html | 47 +++++++ templates/browse.html | 95 +++++++++++++ templates/index.html | 321 ++++++++++++++++++++---------------------- 4 files changed, 403 insertions(+), 169 deletions(-) create mode 100644 templates/base.html create mode 100644 templates/browse.html diff --git a/happy_ripper.py b/happy_ripper.py index cde97bb..4301c5e 100644 --- a/happy_ripper.py +++ b/happy_ripper.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from http.client import NETWORK_AUTHENTICATION_REQUIRED import pathlib import os import re @@ -16,6 +17,7 @@ from mutagen.easyid3 import EasyID3 from mutagen.mp3 import MP3 from mutagen.id3 import ID3, APIC from flask import Flask, render_template, request, jsonify +from ytmusicapi import YTMusic MUSIC_FOLDER = os.environ.get("HAPPY_RIPPER_MUSIC_DIR", "Music") ACCESS_URL = os.environ.get("HAPPY_RIPPER_ACCESS_URL", "https://test.test.test") @@ -284,11 +286,32 @@ def rip_worker(rip_queue: list[str], status_callback: Callable): status_callback("Finished sucessfully", True) +def get_all_playlists_from_channel(browseId: str) -> list[str]: + urls: list[str] = [] + yt = YTMusic() + artist = yt.get_artist(browseId) + albums_info = artist["albums"] + albums = yt.get_artist_albums( + albums_info["browseId"], + albums_info["params"], + limit=None, + ) + + for album in albums: + playlist_id = album.get("playlistId") + if playlist_id: + urls.append(f"https://music.youtube.com/playlist?list={playlist_id}") + + return urls + + if __name__ == "__main__": app = Flask(__name__) rip_queue: list[str] = [] finished_queue: list[ReleaseStatus] = [] + yt_music = YTMusic() + current_release_info: ReleaseInfo | None = None current_release_status_message: str = "" current_release_destinations: set[str] = set() @@ -328,13 +351,33 @@ if __name__ == "__main__": def index(): return render_template("index.html") + @app.route("/browse") + def browse(): + return render_template("browse.html") + @app.route("/rip", methods=["POST"]) def rip(): + print("RIP") + url = request.form.get("url") if url is None: return jsonify({"success": False, "message": "Url is None"}) - rip_queue.append(url) + is_channel = request.form.get("is-channel-check") + is_channel = is_channel is not None + + if is_channel: + channel_playlists = get_all_playlists_from_channel(url) + + if len(channel_playlists) == 0: + print("ERROR: Found no playlists in channel") + return jsonify({"success": False, "message": "channel has no playlists"}) + + for playlist_url in channel_playlists: + rip_queue.append(playlist_url) + else: + rip_queue.append(url) + return jsonify({"success": True, "message": f"Added, {url}!"}) @app.route("/status", methods=["GET"]) @@ -364,4 +407,68 @@ if __name__ == "__main__": return jsonify(data) + @app.route("/search", methods=["POST"]) + def search(): + global yt_music + + query = request.form.get("search") + if query is None: + return jsonify({"success": False, "message": "Search query is None"}) + + responce = yt_music.search(query) + + data = [] + for release in responce: + release_data = {} + + # Category =============================================================== + category = release.get("resultType") + if category is None: + return jsonify({"success": False, "message": "Category is None"}) + + release_data["category"] = category + + # Artist =============================================================== + if "artist" in release: + release_data["artist"] = release.get("artist", "Unknown") + + elif "artists" in release: + artists = release.get("artists") + if artists is None: + return jsonify({"success": False, "message": "Artists exists but is None"}) + + release_data["artist"] = artists[0].get("name", "Unknown") + print(artists) + + elif "author" in release: + release_data["artist"] = release.get("author", "Unknown") + + else: + release_data["artist"] = "Unknown" + + # Title =============================================================== + if category == "artist": + release_data["title"] = release_data["artist"] + else: + release_data["title"] = release.get("title", "Unknown") + + # Thumbnails =============================================================== + if "thumbnails" in release: + release_data["thumbnails"] = release.get("thumbnails") + else: + return jsonify({"success": False, "message": "Thumbnails is None"}) + + if "playlistId" in release: + release_data["url"] = f"https://music.youtube.com/playlist?list={release['playlistId']}" + elif "videoId" in release: + release_data["url"] = f"https://music.youtube.com/watch?v={release['videoId']}" + elif "browseId" in release and category == "artist": + release_data["url"] = release.get("browseId") + elif category == "artist" and "artists" in release: + release_data["url"] = release.get("artists")[0].get("id") + + data.append(release_data) + + return jsonify(data) + app.run(host="0.0.0.0", port=PORT) diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..9f51f02 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,47 @@ + + + + + + Happy Ripper + + + + + + +
{% block content %}{% endblock %}
+ + + + diff --git a/templates/browse.html b/templates/browse.html new file mode 100644 index 0000000..cffdf0e --- /dev/null +++ b/templates/browse.html @@ -0,0 +1,95 @@ +{% extends 'base.html' %} {% block content %} +
+
+
+ +
+ + + +
+
+ +
+ +
+ + +{% endblock %} diff --git a/templates/index.html b/templates/index.html index 6ca544e..18bd70b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,174 +1,159 @@ - - - - - - Happy Ripper - - - - -
-

Request

-
-
- +{% extends 'base.html' %} {% block content %} +
+

Request

+ +
+ +
+ +
+ + +
+ + + +
+ +
+ +
+

Current

+
+
+ +
+ +
+

Queue

+ + + + + + + + +
#URL
+
+ +
+ +
+

Finished

+
+
+ + - - - - + else { + statusMessage.innerHTML = `

${statusJson.status}

`; + } + + queueTableBody.innerHTML = ""; + + var i = 1; + for (const url of statusJson.queue) { + queueTableBody.innerHTML += `${i}${url}`; + i ++; + } + + finishTableBody.innerHTML = ""; + for (const q of statusJson.finishedQueue.toReversed()) { + finishTableBody.innerHTML += createFinishCard(q.artist, q.title, q.status, q.art_url, q.destinations); + } + } + + setInterval(status, 100); + +{% endblock %}