168 lines
7.1 KiB
Python
168 lines
7.1 KiB
Python
"""Small Linux integration fixes for the pinned community WebUI.
|
|
|
|
Keep these transformations explicit and fail the image build if upstream moves
|
|
the expected code. That prevents a future upstream update from silently
|
|
producing a half-patched console.
|
|
"""
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
root = Path(sys.argv[1])
|
|
app_js = root / "static" / "app.js"
|
|
index_html = root / "static" / "index.html"
|
|
server_py = root / "server.py"
|
|
|
|
|
|
def replace_once(path: Path, old: str, new: str) -> None:
|
|
text = path.read_text(encoding="utf-8")
|
|
if text.count(old) != 1:
|
|
raise RuntimeError(f"expected exactly one patch marker in {path}: {old[:80]!r}")
|
|
path.write_text(text.replace(old, new, 1), encoding="utf-8")
|
|
|
|
|
|
# The published UI calls refreshArt() during startup, but does not define it.
|
|
# Athena does not install the Windows-only stable-diffusion.cpp helper, so show
|
|
# that state honestly and keep the optional controls inert instead of crashing
|
|
# the whole page.
|
|
art_marker = ''' $("artDirAdd").addEventListener("click", function () {
|
|
'''
|
|
art_fix = ''' function refreshArt() {
|
|
return api("/api/art/status").then(function (data) {
|
|
STATE.art = data;
|
|
var installed = !!data.installed;
|
|
var supported = !!data.supported;
|
|
$("artState").textContent = data.busy || (installed ? "installed" :
|
|
(supported ? "not installed" : "not available on Linux"));
|
|
$("artState").dataset.s = data.busy ? "busy" : (installed ? "ready" : "missing");
|
|
$("artInstall").disabled = !!data.busy || !supported;
|
|
$("artInstall").textContent = installed ? "Reinstall" : "Install";
|
|
$("setArtAuto").checked = !!data.auto;
|
|
$("setArtAuto").disabled = !installed;
|
|
$("setArtModel").innerHTML = (data.models || []).length
|
|
? data.models.map(function (m) {
|
|
return '<option value="' + escape(m.path) + '">' + escape(m.name || m.file) + '</option>';
|
|
}).join("")
|
|
: '<option value="">no art model available</option>';
|
|
if (data.selected) $("setArtModel").value = data.selected;
|
|
$("setArtModel").disabled = !installed;
|
|
$("artDir").disabled = !supported;
|
|
$("artDirAdd").disabled = !supported;
|
|
$("artDirs").textContent = supported
|
|
? ((data.dirs || []).length ? "Also scanning: " + data.dirs.join(" · ") : "No extra folders configured.")
|
|
: "The community release only provides the cover-art installer for Windows; song generation is unaffected.";
|
|
$("artCatalog").innerHTML = "";
|
|
return data;
|
|
}).catch(function (error) {
|
|
$("artState").textContent = "unavailable";
|
|
$("artInstall").disabled = true;
|
|
$("artDirs").textContent = error.message;
|
|
});
|
|
}
|
|
|
|
$("artDirAdd").addEventListener("click", function () {
|
|
'''
|
|
replace_once(app_js, art_marker, art_fix)
|
|
|
|
# YuE2 natively supports an empty lyric string for true instrumentals. The
|
|
# community server rejected that valid request even though the browser did not.
|
|
replace_once(
|
|
server_py,
|
|
''' if not body.style.strip() or not body.lyrics.strip():
|
|
raise HTTPException(400, "A style prompt and lyrics are both required")
|
|
''',
|
|
''' if not body.style.strip():
|
|
raise HTTPException(400, "A style prompt is required")
|
|
''',
|
|
)
|
|
|
|
replace_once(
|
|
index_html,
|
|
'''<span class="label">Lyrics <em>section tags on their own line</em></span>''',
|
|
'''<span class="label">Lyrics <em>section tags on their own line · leave empty for a true instrumental</em></span>''',
|
|
)
|
|
|
|
# The backend already ships a complete SheetSage2 upload endpoint but hides it
|
|
# from the published console. Surface it beside the existing take-to-take
|
|
# remix controls so uploaded songs can seed a new melody/arrangement.
|
|
replace_once(
|
|
index_html,
|
|
''' <p class="row-hint" id="coverStatus"></p>
|
|
</div>
|
|
</details>
|
|
''',
|
|
''' <p class="row-hint" id="coverStatus"></p>
|
|
|
|
<h4>Remix an uploaded recording</h4>
|
|
<p class="hint">SheetSage2 listens to an uploaded song and writes an editable melody
|
|
and chord score for YuE2. This is structural transcription, not a sample or a copy of
|
|
the original sound. After analysis, choose a new style and generate normally.</p>
|
|
<div class="field-row">
|
|
<label class="field grow">
|
|
<span class="label">Source audio</span>
|
|
<input id="coverAudio" type="file" accept=".wav,.flac,.mp3,.m4a,.ogg,.opus,.aac,audio/*" />
|
|
</label>
|
|
<div class="field reset-cell">
|
|
<button type="button" class="btn ghost" id="coverFromAudio">Analyse uploaded melody</button>
|
|
</div>
|
|
</div>
|
|
<label class="check"><input type="checkbox" id="coverAudioMelodyOnly" checked />
|
|
<span>Keep the melody, but let the new style rebuild the harmony</span></label>
|
|
<p class="row-hint" id="coverAudioStatus">Checking SheetSage2…</p>
|
|
</div>
|
|
</details>
|
|
''',
|
|
)
|
|
|
|
replace_once(
|
|
app_js,
|
|
''' $("coverStatus").textContent = takes.length
|
|
? takes.length + " takes carry a score you can remix."
|
|
: "Make a song in Full plan or Melody only mode first; Direct mode keeps no score.";
|
|
}).catch(function () {});
|
|
''',
|
|
''' $("coverStatus").textContent = takes.length
|
|
? takes.length + " takes carry a score you can remix."
|
|
: "Make a song in Full plan or Melody only mode first; Direct mode keeps no score.";
|
|
var sheetsage = data.sheetsage || {};
|
|
$("coverFromAudio").disabled = !sheetsage.available;
|
|
$("coverAudioStatus").textContent = sheetsage.available
|
|
? "SheetSage2 is ready. Analysis temporarily parks YuE2 because both use the GPU."
|
|
: "SheetSage2 is not installed yet; uploaded-audio remix is unavailable.";
|
|
}).catch(function (error) {
|
|
$("coverFromAudio").disabled = true;
|
|
$("coverAudioStatus").textContent = error.message;
|
|
});
|
|
''',
|
|
)
|
|
|
|
replace_once(
|
|
app_js,
|
|
''' /* -------------------------------------------------------------- compose */
|
|
''',
|
|
''' $("coverFromAudio").addEventListener("click", function () {
|
|
var file = $("coverAudio").files[0];
|
|
if (!file) return toast("Choose an audio file first", "bad");
|
|
var button = $("coverFromAudio");
|
|
var body = new FormData();
|
|
body.append("file", file, file.name);
|
|
body.append("melody_only", $("coverAudioMelodyOnly").checked ? "true" : "false");
|
|
button.disabled = true;
|
|
button.textContent = "Analysing…";
|
|
$("coverAudioStatus").textContent = "Listening to “" + file.name + "”… this can take several minutes.";
|
|
api("/api/cover/from-audio", { method: "POST", body: body }).then(function (data) {
|
|
applyCoverScore(data.abc, "Melody analysed from “" + data.source + "” in " + data.seconds + " s. Choose the new style and generate.");
|
|
$("coverAudioStatus").textContent = "Analysis complete in " + data.seconds + " s. The editable score is loaded below.";
|
|
}).catch(function (error) {
|
|
$("coverAudioStatus").textContent = error.message;
|
|
toast(error.message, "bad");
|
|
}).then(function () {
|
|
button.textContent = "Analyse uploaded melody";
|
|
refreshCover();
|
|
});
|
|
});
|
|
|
|
/* -------------------------------------------------------------- compose */
|
|
''',
|
|
)
|