mirror of
https://github.com/MrUnknownDE/vrc-ytdlp-resolver.git
synced 2026-04-18 22:13:45 +02:00
128 lines
4.6 KiB
HTML
128 lines
4.6 KiB
HTML
<!-- /public/index.html -->
|
||
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width,initial-scale=1,viewport-fit=cover"
|
||
/>
|
||
<title>VRChat YouTube Resolver</title>
|
||
<style>
|
||
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu;max-width:760px;margin:4rem auto;padding:0 1rem;line-height:1.45}
|
||
header{margin-bottom:1rem}
|
||
.card{border:1px solid #ddd;border-radius:12px;padding:1rem}
|
||
.row{display:flex;gap:.5rem}
|
||
input[type="url"]{flex:1;padding:.75rem;border:1px solid #ccc;border-radius:8px}
|
||
button{padding:.75rem 1rem;border:0;border-radius:8px;background:#4f46e5;color:white;cursor:pointer}
|
||
button.secondary{background:#475569}
|
||
pre{white-space:pre-wrap;word-break:break-all;background:#0b1020;color:#e6ecff;padding:1rem;border-radius:8px}
|
||
.hint{font-size:.9rem;color:#334155}
|
||
.warn{color:#9a3412}
|
||
.ok{color:#065f46}
|
||
.grid{display:grid;gap:.5rem}
|
||
a.buttonlike { text-decoration: none; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<h1>VRChat YouTube Resolver</h1>
|
||
<p class="hint">
|
||
Generates a direct <code>googlevideo.com</code> URL via <code>yt-dlp</code>.
|
||
Paste the result into your in-world video player. Note: URLs are time-limited.
|
||
</p>
|
||
</header>
|
||
|
||
<div id="ytver" class="hint" style="margin-bottom:1rem; font-size:0.9rem; color:#475569;">
|
||
yt-dlp version: loading…
|
||
</div>
|
||
|
||
<section class="card grid">
|
||
<form id="f" class="row">
|
||
<input id="url" type="url" placeholder="https://www.youtube.com/watch?v=..." required />
|
||
<button type="submit">Resolve</button>
|
||
</form>
|
||
|
||
<div id="out"></div>
|
||
<p class="hint">
|
||
Tip: Prefer a “Direct stream” (single URL). If you only get “Adaptive streams,” many world players
|
||
won’t accept separate video+audio URLs unless they have a proxy/muxer.
|
||
</p>
|
||
</section>
|
||
|
||
<script>
|
||
const $ = (s) => document.querySelector(s);
|
||
const out = $("#out");
|
||
|
||
async function showVersion() {
|
||
try {
|
||
const r = await fetch("/api/version");
|
||
const v = await r.json();
|
||
const el = $("#ytver");
|
||
|
||
if (!v.latest) {
|
||
el.textContent = `yt-dlp version: ${v.local}`;
|
||
return;
|
||
}
|
||
|
||
if (v.updateAvailable) {
|
||
el.innerHTML = `
|
||
yt-dlp version: <strong>${v.local}</strong> →
|
||
<span style="color:#b91c1c;">Update available: ${v.latest}</span><br>
|
||
<a class="buttonlike" href="https://github.com/yt-dlp/yt-dlp/releases/latest" target="_blank">Open latest release</a>
|
||
`;
|
||
} else {
|
||
el.innerHTML = `yt-dlp version: <strong>${v.local}</strong> (up to date)`;
|
||
}
|
||
} catch {
|
||
$("#ytver").textContent = "yt-dlp version: (could not be queried)";
|
||
}
|
||
}
|
||
|
||
$("#f").addEventListener("submit", async (e) => {
|
||
e.preventDefault();
|
||
out.innerHTML = "Working…";
|
||
const url = $("#url").value.trim();
|
||
|
||
try {
|
||
const r = await fetch("/api/resolve", {
|
||
method: "POST",
|
||
headers: { "content-type": "application/json" },
|
||
body: JSON.stringify({ url }),
|
||
});
|
||
const data = await r.json();
|
||
if (!data.ok) throw new Error(data.error || "Unknown error");
|
||
|
||
const blocks = [];
|
||
if (data.url) {
|
||
const safe = data.url.replaceAll("'", "\\'");
|
||
blocks.push(`
|
||
<h3 class="ok">Direct stream</h3>
|
||
<pre id="u">${data.url}</pre>
|
||
<div class="row">
|
||
<button type="button" onclick="navigator.clipboard.writeText('${safe}')">Copy</button>
|
||
<a class="buttonlike" href="${data.url}" target="_blank"><button type="button" class="secondary">Open</button></a>
|
||
</div>
|
||
<p class="hint">${data.note || ""}</p>
|
||
`);
|
||
}
|
||
if (data.audioUrl) {
|
||
blocks.push(`
|
||
<h3 class="warn">Adaptive streams</h3>
|
||
<p class="hint warn">This likely won’t work in players that expect a single URL.</p>
|
||
<strong>Video:</strong>
|
||
<pre>${data.url}</pre>
|
||
<strong>Audio:</strong>
|
||
<pre>${data.audioUrl}</pre>
|
||
`);
|
||
}
|
||
out.innerHTML = blocks.join("");
|
||
} catch (err) {
|
||
out.innerHTML = `<p class="warn">${err.message}</p>`;
|
||
}
|
||
});
|
||
|
||
showVersion();
|
||
</script>
|
||
</body>
|
||
</html> |