Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_tts_import_player.js
T
Claude d4603f8e8a feat(tts-import): folder-first grouping, visible skips, batches, preview
- A folder holding one character's GPT + SoVITS weights and its clips is that
  character, named after the folder: two characters whose weights are both
  "model" no longer merge, pinyin weights in a Chinese-named folder take the
  folder's name, version folders read "派蒙 v2". Package weight folders keep
  the name-by-stem rule.
- Skip rules match the node (interface v9): Python environments and base
  models always; GPT_SoVITS / logs / output / TEMP / tools only inside a
  package; the chosen or dropped folder never. The drop walker and the folder
  dialog apply them where the listing is known (the old re-check on each
  file's path skipped a chosen folder called "output"), local scans prefix
  the chosen folder so every source sorts alike, and the window names the
  skipped and too-deep folders instead of losing files quietly. Drops only
  open importable files and stop at 5000.
- Same-name files inside a character are renamed with their folder
  (难过_01.wav, sent as the file's "name") instead of dropped; an unclaimed
  .list goes to every character.
- Big adds open 20 characters (about 800 files) at a time; "next batch" opens
  the rest, and it opens by itself once a batch is imported, replacing the
  imported cards. Unassigned files upload only once put into a character.
- Every clip can be previewed while importing, local paths included (through
  the node's preview route): one at a time, ▶ turns into stop, another clip
  switches.
- Upload queue and preview player move to their own modules, the "add more"
  menu to the screens module (ui_tts_import.js 647 → 652 lines with batches and renames added).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012RMuwbsWZHnx2eCWpxYqAB
2026-09-26 15:20:24 +00:00

60 lines
2.1 KiB
JavaScript

import { t } from './interface_settings.js';
import { previewUrl } from './tts_setup_api.js';
/**
* Listening to clips in the import window, while they upload or wait unassigned:
* one clip at a time, its row marked and its button turned into "stop"; clicking
* another clip switches to it, so clips can be compared one after another. Browser
* files play from memory (`row.url`, an object URL the window revokes with the row);
* local paths play through the node (`/anomalous_tts/import/preview`).
*/
export function createImportPlayer() {
let audio = null;
let current = null;
const mark = (row, playing, error = '') => {
const button = row?.els?.play;
if (!button) return;
button.textContent = playing ? '■' : '▶';
button.title = error ? t('ttsPreviewFailed', { error }) : t(playing ? 'ttsPreviewStop' : 'audioPlay');
button.setAttribute('aria-label', button.title);
button.classList.toggle('is-failed', Boolean(error));
row.els.root.classList.toggle('is-playing', playing);
};
function stop() {
audio?.pause();
audio = null;
mark(current, false);
current = null;
}
/** Play `row`, or stop it when it is the one playing. */
function toggle(row) {
if (current === row) { stop(); return; }
stop();
if (!row.file && !row.path) return;
if (row.file) row.url = row.url || URL.createObjectURL(row.file);
const mine = new Audio(row.file ? row.url : previewUrl(row.path));
audio = mine;
current = row;
mark(row, true);
const fail = (error) => {
if (audio !== mine) return;
audio = null;
current = null;
mark(row, false, error);
};
mine.onended = () => { if (audio === mine) stop(); };
mine.onerror = () => fail(mine.error?.message || t('ttsPreviewUnreadable'));
mine.play().catch(e => fail(e.message || String(e)));
}
return {
toggle,
stop,
/** A row leaves the window: stop it if it is playing. */
forget: (row) => { if (current === row) stop(); },
};
}