forked from Ethanscharlie/Happy-Ripper
Cleaned code
This commit is contained in:
@@ -12,3 +12,4 @@ build/
|
|||||||
.idea/
|
.idea/
|
||||||
*.swp
|
*.swp
|
||||||
*~
|
*~
|
||||||
|
tmp
|
||||||
|
|||||||
+118
-143
@@ -1,7 +1,7 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from flask import Flask, render_template, request, jsonify
|
from flask import Flask, render_template, request, jsonify
|
||||||
from threading import RLock, Thread
|
from threading import Thread
|
||||||
import time
|
import time
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
from yt_dlp.utils import DownloadError, ExtractorError
|
from yt_dlp.utils import DownloadError, ExtractorError
|
||||||
@@ -16,13 +16,19 @@ class ReleaseInfo:
|
|||||||
artist: str
|
artist: str
|
||||||
title: str
|
title: str
|
||||||
art_url: str
|
art_url: str
|
||||||
|
is_album: bool
|
||||||
|
is_topic: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReleaseStatus:
|
||||||
|
info: ReleaseInfo | None
|
||||||
status: str
|
status: str
|
||||||
|
|
||||||
|
|
||||||
class YtdlpErrorCode(Exception):
|
class RipError(Exception):
|
||||||
def __init__(self, code):
|
def __init__(self, msg):
|
||||||
super().__init__(f"Some videos failed with code {code}")
|
super().__init__(msg)
|
||||||
self.code = code
|
|
||||||
|
|
||||||
|
|
||||||
def create_or_wipe_folder(path: str) -> None:
|
def create_or_wipe_folder(path: str) -> None:
|
||||||
@@ -41,6 +47,7 @@ def get_content_json(url: str):
|
|||||||
|
|
||||||
|
|
||||||
def download_content(path: str, url: str) -> None:
|
def download_content(path: str, url: str) -> None:
|
||||||
|
try:
|
||||||
with YoutubeDL(
|
with YoutubeDL(
|
||||||
{
|
{
|
||||||
"format": "m4a/bestaudio/best",
|
"format": "m4a/bestaudio/best",
|
||||||
@@ -55,7 +62,61 @@ def download_content(path: str, url: str) -> None:
|
|||||||
) as ydl:
|
) as ydl:
|
||||||
error_code = ydl.download([url])
|
error_code = ydl.download([url])
|
||||||
if error_code:
|
if error_code:
|
||||||
raise YtdlpErrorCode(error_code)
|
raise RipError(f"Content download failed ({error_code})")
|
||||||
|
|
||||||
|
except DownloadError:
|
||||||
|
raise RipError("Content Download Failed")
|
||||||
|
|
||||||
|
except ExtractorError:
|
||||||
|
raise RipError("Content Extraction Failed")
|
||||||
|
|
||||||
|
|
||||||
|
def find_release_info(url: str) -> ReleaseInfo:
|
||||||
|
try:
|
||||||
|
info = get_content_json(url)
|
||||||
|
|
||||||
|
except DownloadError:
|
||||||
|
raise RipError("Info Download Failed")
|
||||||
|
|
||||||
|
except ExtractorError:
|
||||||
|
raise RipError("Info Extraction Failed")
|
||||||
|
|
||||||
|
entries = info.get("entries")
|
||||||
|
if entries is None:
|
||||||
|
raise RipError("Entries is none")
|
||||||
|
|
||||||
|
first_entry = next(iter(entries), None)
|
||||||
|
if not first_entry:
|
||||||
|
raise RipError("Entries is empty")
|
||||||
|
|
||||||
|
artist = first_entry.get("uploader")
|
||||||
|
if artist is None:
|
||||||
|
raise RipError("Uploader tag is none")
|
||||||
|
|
||||||
|
is_topic = False
|
||||||
|
if " - Topic" in artist:
|
||||||
|
is_topic = True
|
||||||
|
artist = artist.replace(" - Topic", "")
|
||||||
|
|
||||||
|
title = info.get("title")
|
||||||
|
if title is None:
|
||||||
|
raise RipError("Title tag is none")
|
||||||
|
|
||||||
|
is_album = False
|
||||||
|
if "Album - " in title:
|
||||||
|
is_album = True
|
||||||
|
title = title.replace("Album - ", "")
|
||||||
|
|
||||||
|
thumbnails = info.get("thumbnails")
|
||||||
|
if thumbnails is None:
|
||||||
|
raise RipError("Thumbnails is none")
|
||||||
|
|
||||||
|
art_url = thumbnails[1]["url"]
|
||||||
|
|
||||||
|
if not is_album:
|
||||||
|
raise RipError("Failed, not an album")
|
||||||
|
|
||||||
|
return ReleaseInfo(artist, title, art_url, is_album, is_topic)
|
||||||
|
|
||||||
|
|
||||||
def find_tmp_path() -> str:
|
def find_tmp_path() -> str:
|
||||||
@@ -63,9 +124,7 @@ def find_tmp_path() -> str:
|
|||||||
return f"{project_root_path}/tmp"
|
return f"{project_root_path}/tmp"
|
||||||
|
|
||||||
|
|
||||||
def rip_worker(
|
def rip_worker(rip_queue: list[str], status_callback: Callable[[ReleaseInfo | str, bool],]):
|
||||||
rip_queue: list[str], status_callback: Callable[[ReleaseInfo],], finish_callback: Callable[[ReleaseInfo],]
|
|
||||||
):
|
|
||||||
tmp_path = find_tmp_path()
|
tmp_path = find_tmp_path()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -73,144 +132,55 @@ def rip_worker(
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
status_callback("Gathering Info", False)
|
||||||
|
|
||||||
|
url = rip_queue.pop(0)
|
||||||
create_or_wipe_folder(tmp_path)
|
create_or_wipe_folder(tmp_path)
|
||||||
|
|
||||||
current_url = rip_queue.pop(0)
|
|
||||||
release_info = ReleaseInfo(
|
|
||||||
"",
|
|
||||||
current_url,
|
|
||||||
"https://imgs.search.brave.com/LeS4HHKZ1oz1T15VY5MwiUjWDjLiYKj0vgRABB3D2BY/rs:fit:860:0:0:0/g:ce/aHR0cHM6Ly90My5m/dGNkbi5uZXQvanBn/LzA2Lzg2LzE5LzM0/LzM2MF9GXzY4NjE5/MzQwN19ESFp3amV5/ZEJPUjF0RURrTEF6/d00zdzVrWXN0Unp6/Qi5qcGc",
|
|
||||||
"Starting",
|
|
||||||
)
|
|
||||||
status_callback(release_info)
|
|
||||||
|
|
||||||
is_album = False
|
|
||||||
is_topic = False
|
|
||||||
|
|
||||||
# Info json --------------------------------------------------
|
|
||||||
|
|
||||||
release_info.status = "Gathering Info"
|
|
||||||
status_callback(release_info)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = get_content_json(current_url)
|
info = find_release_info(url)
|
||||||
|
status_callback(info, False)
|
||||||
|
|
||||||
except DownloadError:
|
status_callback("Downloading Content", False)
|
||||||
release_info.status = "Info Download Failed"
|
|
||||||
finish_callback(release_info)
|
|
||||||
continue
|
|
||||||
|
|
||||||
except ExtractorError:
|
download_content(tmp_path, url)
|
||||||
release_info.status = "Info Extraction Failed"
|
|
||||||
finish_callback(release_info)
|
|
||||||
continue
|
|
||||||
|
|
||||||
entries = info.get("entries")
|
except RipError as e:
|
||||||
if entries is None:
|
status_callback(repr(e), True)
|
||||||
release_info.status = "Entries is none"
|
|
||||||
finish_callback(release_info)
|
|
||||||
continue
|
|
||||||
|
|
||||||
first_entry = next(iter(entries), None)
|
else:
|
||||||
if not first_entry:
|
status_callback("Finished Sucessfully", True)
|
||||||
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
|
|
||||||
|
|
||||||
thumbnails = info.get("thumbnails")
|
|
||||||
if thumbnails is None:
|
|
||||||
release_info.status = "Thumbnails is none"
|
|
||||||
finish_callback(release_info)
|
|
||||||
continue
|
|
||||||
|
|
||||||
art_url = thumbnails[1]["url"]
|
|
||||||
release_info.art_url = art_url
|
|
||||||
|
|
||||||
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:
|
|
||||||
download_content(tmp_path, current_url)
|
|
||||||
|
|
||||||
except DownloadError:
|
|
||||||
release_info.status = "Content Download Failed"
|
|
||||||
finish_callback(release_info)
|
|
||||||
continue
|
|
||||||
|
|
||||||
except YtdlpErrorCode:
|
|
||||||
release_info.status = "Content Download Failed with code"
|
|
||||||
finish_callback(release_info)
|
|
||||||
continue
|
|
||||||
|
|
||||||
except ExtractorError:
|
|
||||||
release_info.status = "Content Extraction Failed"
|
|
||||||
finish_callback(release_info)
|
|
||||||
continue
|
|
||||||
|
|
||||||
release_info.status = "Writting Metadata"
|
|
||||||
status_callback(release_info)
|
|
||||||
|
|
||||||
release_info.status = "Finished Successfully"
|
|
||||||
finish_callback(release_info)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
rip_queue = []
|
rip_queue: list[str] = []
|
||||||
finished_queue = []
|
finished_queue: list[ReleaseStatus] = []
|
||||||
|
|
||||||
current_release_info = ReleaseInfo("", "", "", "Waiting")
|
current_release_info: ReleaseInfo | None = None
|
||||||
|
current_release_status_message: str = ""
|
||||||
|
|
||||||
def get_current_release_info(release_info: ReleaseInfo):
|
def get_current_release_status(x: ReleaseInfo | str, finished: bool):
|
||||||
global current_release_info
|
|
||||||
current_release_info = release_info
|
|
||||||
|
|
||||||
def get_current_release_info_on_finish(release_info: ReleaseInfo):
|
|
||||||
global current_release_info
|
global current_release_info
|
||||||
|
global current_release_status_message
|
||||||
global finished_queue
|
global finished_queue
|
||||||
current_release_info = ReleaseInfo("", "", "", "Waiting")
|
|
||||||
finished_queue.append(release_info)
|
if type(x) is ReleaseInfo:
|
||||||
|
current_release_info = x
|
||||||
|
|
||||||
|
elif type(x) is str:
|
||||||
|
current_release_status_message = x
|
||||||
|
|
||||||
|
if finished:
|
||||||
|
finished_queue.append(ReleaseStatus(current_release_info, current_release_status_message))
|
||||||
|
current_release_info = None
|
||||||
|
current_release_status_message = "Waiting"
|
||||||
|
|
||||||
if len(finished_queue) > 30:
|
if len(finished_queue) > 30:
|
||||||
finished_queue.pop(0)
|
finished_queue.pop(0)
|
||||||
|
|
||||||
ripping_thread = Thread(
|
ripping_thread = Thread(target=rip_worker, args=(rip_queue, get_current_release_status), daemon=True).start()
|
||||||
target=rip_worker, args=(rip_queue, get_current_release_info, get_current_release_info_on_finish), daemon=True
|
|
||||||
).start()
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
@@ -219,26 +189,31 @@ if __name__ == "__main__":
|
|||||||
@app.route("/rip", methods=["POST"])
|
@app.route("/rip", methods=["POST"])
|
||||||
def rip():
|
def rip():
|
||||||
url = request.form.get("url")
|
url = request.form.get("url")
|
||||||
|
if url is None:
|
||||||
|
return jsonify({"success": False, "message": "Url is None"})
|
||||||
|
|
||||||
rip_queue.append(url)
|
rip_queue.append(url)
|
||||||
return jsonify({"success": True, "message": f"Added, {url}!"})
|
return jsonify({"success": True, "message": f"Added, {url}!"})
|
||||||
|
|
||||||
@app.route("/status", methods=["GET"])
|
@app.route("/status", methods=["GET"])
|
||||||
def status():
|
def status():
|
||||||
return jsonify(
|
data = {"success": True, "queue": rip_queue, "finishedQueue": [], "status": current_release_status_message}
|
||||||
{
|
|
||||||
"success": True,
|
for q in finished_queue:
|
||||||
"queue": rip_queue,
|
if q.info is None:
|
||||||
"finishedQueue": [
|
continue
|
||||||
{"artist": q.artist, "title": q.title, "status": q.status, "art_url": q.art_url}
|
|
||||||
for q in finished_queue
|
data["finishedQueue"].append(
|
||||||
],
|
{"artist": q.info.artist, "title": q.info.title, "status": q.status, "art_url": q.info.art_url}
|
||||||
"currentReleaseInfo": {
|
|
||||||
"artist": current_release_info.artist,
|
|
||||||
"title": current_release_info.title,
|
|
||||||
"status": current_release_info.status,
|
|
||||||
"art_url": current_release_info.art_url,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if current_release_info is not None:
|
||||||
|
data["currentReleaseInfo"] = {
|
||||||
|
"artist": current_release_info.artist,
|
||||||
|
"title": current_release_info.title,
|
||||||
|
"art_url": current_release_info.art_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify(data)
|
||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
|
|||||||
@@ -106,8 +106,13 @@
|
|||||||
const response = await fetch("/status", { method: "GET" });
|
const response = await fetch("/status", { method: "GET" });
|
||||||
const statusJson = await response.json();
|
const statusJson = await response.json();
|
||||||
|
|
||||||
|
if ("currentReleaseInfo" in statusJson) {
|
||||||
const currentInfo = statusJson.currentReleaseInfo;
|
const currentInfo = statusJson.currentReleaseInfo;
|
||||||
statusMessage.innerHTML = createCard(currentInfo.artist, currentInfo.title, currentInfo.status, currentInfo.art_url);
|
statusMessage.innerHTML = createCard(currentInfo.artist, currentInfo.title, statusJson.status, currentInfo.art_url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
statusMessage.innerHTML = `<p>${statusJson.status}</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
queueTableBody.innerHTML = "";
|
queueTableBody.innerHTML = "";
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user