asmr: show cost per clip and per minute of audio on every voice
This commit is contained in:
1 parent
8d200c5988
commit
f02bc04c98
1 file changed
+14
-1
+14
-1
@@ -28,6 +28,7 @@ summary{cursor:pointer;color:var(--ink)}
|
||||
.v .bar{height:3px;background:var(--line);border-radius:2px;margin-top:6px;overflow:hidden}
|
||||
.v .bar span{display:block;height:100%;width:0;background:var(--acc)}
|
||||
.v.bad{opacity:.4;cursor:default}
|
||||
.v .n b{font-weight:500;color:var(--acc);font-size:12px;margin-left:6px}
|
||||
.stars{display:flex;gap:2px;flex:none}
|
||||
.stars button{background:none;border:0;color:#3a3a45;font-size:18px;padding:2px;cursor:pointer}
|
||||
.stars button.y{color:#ffd36b}
|
||||
@@ -43,6 +44,7 @@ summary{cursor:pointer;color:var(--ink)}
|
||||
<main>
|
||||
<h1>g<b>*</b>ndora asmr</h1>
|
||||
<p class="lede">Voice bake-off. Same 30-second goodnight script through every whisper-capable voice on the Venice worker. Tap to play, star the ones that land. Headphones.</p>
|
||||
<details><summary>Costs</summary><p>Venice bills TTS per input character, so a clip's cost is the script length × the model's rate. Per minute / hour is that cost spread over the clip's actual length, which is how long ASMR would run. Rates from Venice's model list, Oct 21 2026.</p></details>
|
||||
<details><summary>The script</summary><p id="script"></p><p><b>Style direction</b> (Qwen3 only): <span id="style"></span></p></details>
|
||||
<div id="list"></div>
|
||||
<div class="fam" id="nexth" hidden><h2>Next round</h2><small>Not on the shared worker's allowlist yet</small></div>
|
||||
@@ -123,7 +125,18 @@ fetch("voices.json").then((r) => r.json()).then((cfg) => {
|
||||
}
|
||||
const el = document.createElement("div"); el.className = "v";
|
||||
el.innerHTML = `<div class="ic">▶</div><div class="n"><span></span><i></i><div class="bar"><span></span></div></div><div class="stars"></div>`;
|
||||
el.querySelector(".n > span").textContent = v.voice; el.querySelector(".n i").textContent = v.model;
|
||||
el.querySelector(".n > span").textContent = v.voice;
|
||||
const rate = (cfg.pricing || {})[v.model];
|
||||
const text = (v.lead || "") + (v.tags ? cfg.script.replace("Hey...", "Hey... <sigh>") : cfg.script) + (v.tail || "");
|
||||
const clip = rate ? rate * text.length / 1e6 : null;
|
||||
const usd = (x) => "$" + (x < 0.01 ? x.toFixed(4) : x < 1 ? x.toFixed(3) : x.toFixed(2));
|
||||
const info = el.querySelector(".n i");
|
||||
info.textContent = v.model + (rate ? ` · $${rate}/1M chars` : "");
|
||||
if (clip != null) {
|
||||
const tag = document.createElement("b"); tag.textContent = usd(clip) + "/clip"; el.querySelector(".n > span").after(tag);
|
||||
const a = new Audio(); a.preload = "metadata"; a.src = "audio/" + v.id + ".mp3";
|
||||
a.onloadedmetadata = () => { if (a.duration > 0) { const pm = clip / a.duration * 60; tag.textContent = `${usd(clip)}/clip · ${usd(pm)}/min · ${usd(pm * 60)}/hr`; } };
|
||||
}
|
||||
const st = el.querySelector(".stars");
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const s = document.createElement("button"); s.textContent = "★"; s.ariaLabel = i + " stars";
|
||||
|
||||
Reference in new issue
Block a user