Working queue and info json
This commit is contained in:
+183
-39
@@ -1,65 +1,209 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from flask import Flask, render_template, request, jsonify
|
from flask import Flask, render_template, request, jsonify
|
||||||
from threading import Thread
|
from threading import RLock, Thread
|
||||||
import time
|
import time
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
from yt_dlp.utils import DownloadError
|
from yt_dlp.utils import DownloadError
|
||||||
|
from collections.abc import Callable
|
||||||
import pathlib
|
import pathlib
|
||||||
import os
|
import os
|
||||||
import shutil
|
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()
|
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:
|
while True:
|
||||||
queue_is_empty = len(rip_queue) == 0
|
if len(rip_queue) == 0:
|
||||||
if queue_is_empty:
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Creates tmp directory, if it already exists, it clears it, and makes a new one.
|
create_or_wipe_folder(tmp_path)
|
||||||
if not os.path.isdir(tmp_path):
|
|
||||||
os.mkdir(tmp_path)
|
|
||||||
else:
|
|
||||||
shutil.rmtree(tmp_path)
|
|
||||||
os.mkdir(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:
|
try:
|
||||||
with YoutubeDL(
|
download_content(tmp_path, current_url)
|
||||||
{
|
|
||||||
"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])
|
|
||||||
|
|
||||||
except DownloadError:
|
except DownloadError:
|
||||||
print("Download Error")
|
release_info.status = "Content Download Failed"
|
||||||
|
finish_callback(release_info)
|
||||||
|
continue
|
||||||
|
|
||||||
rip_queue.pop(0)
|
except YtdlpErrorCode:
|
||||||
time.sleep(1)
|
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__)
|
if __name__ == "__main__":
|
||||||
rip_queue = []
|
|
||||||
ripping_thread = Thread(target=rip_worker, args=(rip_queue,), daemon=True).start()
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
rip_queue = []
|
||||||
|
finished_queue = []
|
||||||
|
|
||||||
@app.route("/")
|
current_release_info = ReleaseInfo("", "", "Waiting")
|
||||||
def index():
|
|
||||||
return render_template("index.html")
|
|
||||||
|
|
||||||
|
def get_current_release_info(release_info: ReleaseInfo):
|
||||||
|
global current_release_info
|
||||||
|
current_release_info = release_info
|
||||||
|
|
||||||
@app.route("/rip", methods=["POST"])
|
def get_current_release_info_on_finish(release_info: ReleaseInfo):
|
||||||
def rip():
|
global current_release_info
|
||||||
url = request.form.get("url")
|
global finished_queue
|
||||||
rip_queue.append(url)
|
current_release_info = ReleaseInfo("", "", "Waiting")
|
||||||
return jsonify({"success": True, "message": f"Added, {url}!"})
|
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()
|
||||||
|
|||||||
@@ -30,7 +30,47 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</br>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>Current</h2>
|
||||||
|
<p id="status-message">Waiting</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</br>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>Queue</h2>
|
||||||
|
<table class="table" id="queue-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">URL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="queue-table-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</br>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>Finished</h2>
|
||||||
|
<table class="table" id="finish-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Artist</th>
|
||||||
|
<th scope="col">Title</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="finish-table-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const ripFormInput = document.getElementById("urlInput");
|
||||||
|
|
||||||
document
|
document
|
||||||
.getElementById("rip-form")
|
.getElementById("rip-form")
|
||||||
.addEventListener("submit", async function (e) {
|
.addEventListener("submit", async function (e) {
|
||||||
@@ -44,8 +84,38 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
ripFormInput.value = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const queueTableBody = document.getElementById("queue-table-body");
|
||||||
|
const finishTableBody = document.getElementById("finish-table-body");
|
||||||
|
const statusMessage = document.getElementById("status-message");
|
||||||
|
|
||||||
|
async function status() {
|
||||||
|
|
||||||
|
const response = await fetch("/status", { method: "GET" });
|
||||||
|
const statusJson = await response.json();
|
||||||
|
|
||||||
|
const currentInfo = statusJson.currentReleaseInfo;
|
||||||
|
statusMessage.innerText = `${currentInfo.title} by ${currentInfo.artist} | ${currentInfo.status}`;
|
||||||
|
|
||||||
|
queueTableBody.innerHTML = "";
|
||||||
|
|
||||||
|
var i = 1;
|
||||||
|
for (const url of statusJson.queue) {
|
||||||
|
queueTableBody.innerHTML += `<tr><td>${i}</td><td>${url}</td></tr>`;
|
||||||
|
i ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
finishTableBody.innerHTML = "";
|
||||||
|
for (const q of statusJson.finishedQueue) {
|
||||||
|
finishTableBody.innerHTML += `<tr><td>${q.artist}</td><td>${q.title}</td><td>${q.status}</td></tr>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(status, 100);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script
|
<script
|
||||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
||||||
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
||||||
|
|||||||
Reference in New Issue
Block a user