From 0f6a62335f0d4f1dbbfdddbc909cae48ce5f0c10 Mon Sep 17 00:00:00 2001 From: "Ethan J. Hadley" Date: Sun, 6 Sep 2026 16:31:38 -0600 Subject: [PATCH] Working queue and info json --- happy_ripper.py | 222 +++++++++++++++++++++++++++++++++++-------- templates/index.html | 70 ++++++++++++++ 2 files changed, 253 insertions(+), 39 deletions(-) diff --git a/happy_ripper.py b/happy_ripper.py index 02a0a89..5da3c7c 100644 --- a/happy_ripper.py +++ b/happy_ripper.py @@ -1,65 +1,209 @@ +from dataclasses import dataclass + from flask import Flask, render_template, request, jsonify -from threading import Thread +from threading import RLock, Thread import time from yt_dlp import YoutubeDL from yt_dlp.utils import DownloadError +from collections.abc import Callable import pathlib import os import shutil -def rip_worker(rip_queue: list[str]): +@dataclass +class ReleaseInfo: + artist: str + title: str + status: str + + +class YtdlpErrorCode(Exception): + def __init__(self, code): + super().__init__(f"Some videos failed with code {code}") + self.code = code + + +def create_or_wipe_folder(path: str) -> None: + """Creates tmp directory, if it already exists, it clears it, and makes a new one.""" + + if not os.path.isdir(path): + os.mkdir(path) + else: + shutil.rmtree(path) + os.mkdir(path) + + +def get_content_json(url: str): + with YoutubeDL({"playlist_items": "1"}) as ydl: + return ydl.extract_info(url, download=False) + + +def download_content(path: str, url: str) -> None: + with YoutubeDL( + { + "format": "m4a/bestaudio/best", + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": "m4a", + } + ], + "outtmpl": f"{path}/%(title)s.%(ext)s", + } + ) as ydl: + error_code = ydl.download([url]) + if error_code: + raise YtdlpErrorCode(error_code) + + +def find_tmp_path() -> str: project_root_path = pathlib.Path(__file__).parent.resolve() - tmp_path = f"{project_root_path}/tmp" + return f"{project_root_path}/tmp" + + +def rip_worker( + rip_queue: list[str], status_callback: Callable[[ReleaseInfo],], finish_callback: Callable[[ReleaseInfo],] +): + tmp_path = find_tmp_path() while True: - queue_is_empty = len(rip_queue) == 0 - if queue_is_empty: + if len(rip_queue) == 0: + time.sleep(1) continue - # Creates tmp directory, if it already exists, it clears it, and makes a new one. - if not os.path.isdir(tmp_path): - os.mkdir(tmp_path) - else: - shutil.rmtree(tmp_path) - os.mkdir(tmp_path) + create_or_wipe_folder(tmp_path) - current_url = rip_queue[0] + current_url = rip_queue.pop(0) + release_info = ReleaseInfo("", current_url, "Starting") + status_callback(release_info) + + is_album = False + is_topic = False + + # Info json -------------------------------------------------- + + release_info.status = "Gathering Info" + status_callback(release_info) + + info = get_content_json(current_url) + + entries = info.get("entries") + if entries is None: + release_info.status = "Entries is none" + finish_callback(release_info) + continue + + first_entry = next(iter(entries), None) + if not first_entry: + release_info.status = "Entries is empty" + finish_callback(release_info) + continue + + artist = first_entry.get("uploader") + if artist is None: + release_info.status = "Uploader tag is none" + finish_callback(release_info) + continue + + if " - Topic" in artist: + is_topic = True + artist = artist.replace(" - Topic", "") + + release_info.artist = artist + + title = info.get("title") + if title is None: + release_info.status = "Title tag is none" + finish_callback(release_info) + continue + + if "Album - " in title: + is_album = True + title = title.replace("Album - ", "") + + release_info.title = title + + if not is_album: + release_info.status = "Failed, not an album" + finish_callback(release_info) + continue + + release_info.status = "Finished gathering info" + status_callback(release_info) + + # Info json -------------------------------------------------- + + release_info.status = "Downloading" + status_callback(release_info) try: - with YoutubeDL( - { - "format": "m4a/bestaudio/best", - "postprocessors": [ - { - "key": "FFmpegExtractAudio", - "preferredcodec": "m4a", - } - ], - "outtmpl": f"{tmp_path}/%(title)s.%(ext)s", - } - ) as ydl: - error_code = ydl.download([current_url]) + download_content(tmp_path, current_url) except DownloadError: - print("Download Error") + release_info.status = "Content Download Failed" + finish_callback(release_info) + continue - rip_queue.pop(0) - time.sleep(1) + except YtdlpErrorCode: + release_info.status = "Content Download Failed with code" + finish_callback(release_info) + continue + + release_info.status = "Writting Metadata" + status_callback(release_info) + + release_info.status = "Finished Successfully" + finish_callback(release_info) -app = Flask(__name__) -rip_queue = [] -ripping_thread = Thread(target=rip_worker, args=(rip_queue,), daemon=True).start() +if __name__ == "__main__": + app = Flask(__name__) + rip_queue = [] + finished_queue = [] -@app.route("/") -def index(): - return render_template("index.html") + current_release_info = ReleaseInfo("", "", "Waiting") + def get_current_release_info(release_info: ReleaseInfo): + global current_release_info + current_release_info = release_info -@app.route("/rip", methods=["POST"]) -def rip(): - url = request.form.get("url") - rip_queue.append(url) - return jsonify({"success": True, "message": f"Added, {url}!"}) + def get_current_release_info_on_finish(release_info: ReleaseInfo): + global current_release_info + global finished_queue + current_release_info = ReleaseInfo("", "", "Waiting") + finished_queue.append(release_info) + if len(finished_queue) > 30: + finished_queue.pop(0) + + ripping_thread = Thread( + target=rip_worker, args=(rip_queue, get_current_release_info, get_current_release_info_on_finish), daemon=True + ).start() + + @app.route("/") + def index(): + return render_template("index.html") + + @app.route("/rip", methods=["POST"]) + def rip(): + url = request.form.get("url") + rip_queue.append(url) + return jsonify({"success": True, "message": f"Added, {url}!"}) + + @app.route("/status", methods=["GET"]) + def status(): + return jsonify( + { + "success": True, + "queue": rip_queue, + "finishedQueue": [{"artist": q.artist, "title": q.title, "status": q.status} for q in finished_queue], + "currentReleaseInfo": { + "artist": current_release_info.artist, + "title": current_release_info.title, + "status": current_release_info.status, + }, + } + ) + + app.run() diff --git a/templates/index.html b/templates/index.html index d5d3d8f..c6c4b57 100644 --- a/templates/index.html +++ b/templates/index.html @@ -30,7 +30,47 @@ +
+ +
+

Current

+

Waiting

+
+ +
+ +
+

Queue

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

Finished

+ + + + + + + + + +
ArtistTitleStatus
+
+ +