- Sources: "free" (Creative Commons songs with media from github.com/UltraStar-Deluxe/songs) and "usdb" (usdb.animux.de with an account; audio/video via yt-dlp from the song's #VIDEO hints or YouTube links in the comments, covers from fanart.tv/YouTube) - Aggregated search (all sources in parallel, in-library flag), download queue into <library>/Downloads/<source>/<Artist - Title>, then rescan - Web UI in wwwroot (plain JS, no build): search, downloads with progress, library; phone first, dark mode, installable - Docker image with yt-dlp, ffmpeg and deno; library mounted writable - Tests for matching, USDB parsing and song rewriting Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.7 KiB
HTML
70 lines
2.7 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<meta name="theme-color" content="#7c3aed">
|
|
<title>Karaoke Songs</title>
|
|
<link rel="manifest" href="manifest.webmanifest">
|
|
<link rel="icon" href="icon.svg" type="image/svg+xml">
|
|
<link rel="apple-touch-icon" href="icon.svg">
|
|
<link rel="stylesheet" href="app.css">
|
|
</head>
|
|
<body>
|
|
<header class="top">
|
|
<h1><img src="icon.svg" alt="" width="28" height="28"> Karaoke Songs</h1>
|
|
<span id="libraryCount" class="muted"></span>
|
|
</header>
|
|
|
|
<main>
|
|
<section id="tab-search" class="tab active" aria-label="Search">
|
|
<form id="searchForm" class="searchbar" role="search">
|
|
<input id="query" type="search" placeholder="Artist or title" autocomplete="off" enterkeyhint="search" aria-label="Search songs">
|
|
</form>
|
|
<div id="sources" class="chips"></div>
|
|
<div id="searchStatus" class="status"></div>
|
|
<ul id="results" class="list"></ul>
|
|
</section>
|
|
|
|
<section id="tab-downloads" class="tab" aria-label="Downloads">
|
|
<div id="downloadsEmpty" class="status">No downloads yet. Find songs under Search.</div>
|
|
<ul id="downloads" class="list"></ul>
|
|
</section>
|
|
|
|
<section id="tab-library" class="tab" aria-label="Library">
|
|
<form class="searchbar" onsubmit="return false">
|
|
<input id="libraryFilter" type="search" placeholder="Filter the library" autocomplete="off" aria-label="Filter the library">
|
|
</form>
|
|
<div id="libraryStatus" class="status"></div>
|
|
<ul id="library" class="list compact"></ul>
|
|
</section>
|
|
</main>
|
|
|
|
<nav class="tabs" aria-label="Sections">
|
|
<button data-tab="search" class="active">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-4-4"/></svg>
|
|
Search
|
|
</button>
|
|
<button data-tab="downloads">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v11m0 0-4-4m4 4 4-4M5 20h14"/></svg>
|
|
Downloads <span id="activeBadge" class="badge" hidden></span>
|
|
</button>
|
|
<button data-tab="library">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 18V6l11-2v12"/><circle cx="6" cy="18" r="3"/><circle cx="17" cy="16" r="3"/></svg>
|
|
Library
|
|
</button>
|
|
</nav>
|
|
|
|
<dialog id="keyDialog">
|
|
<form method="dialog">
|
|
<h2>Access key</h2>
|
|
<p class="muted">This song server needs an access key.</p>
|
|
<input id="keyInput" type="password" autocomplete="current-password" aria-label="Access key">
|
|
<menu><button value="ok" class="primary">Save</button></menu>
|
|
</form>
|
|
</dialog>
|
|
|
|
<script src="app.js"></script>
|
|
</body>
|
|
</html>
|