generated from dellevin/template
22
This commit is contained in:
@@ -93,7 +93,7 @@
|
||||
}
|
||||
.slider-value { font-weight: 600; color: #0078d4; font-size: 12px; }
|
||||
input[type="range"] {
|
||||
width: 100%; height: 4px; -webkit-appearance: none; appearance: none;
|
||||
width: 50%; height: 4px; -webkit-appearance: none; appearance: none;
|
||||
background: #e0e0e0; border-radius: 2px; outline: none;
|
||||
}
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
@@ -212,9 +212,34 @@
|
||||
}
|
||||
.right-header h2 { font-size: 15px; font-weight: 600; color: #333; }
|
||||
.right-body {
|
||||
flex: 1; overflow-y: auto; padding: 16px 24px 24px;
|
||||
flex: 1; overflow-y: auto; padding: 16px 24px 24px; min-height: 0;
|
||||
}
|
||||
|
||||
/* 日志区域 */
|
||||
.log-area {
|
||||
flex: 1; flex-shrink: 0; display: flex; flex-direction: column;
|
||||
background: #1e1e1e; color: #d4d4d4; border-top: 1px solid #3e3e3e;
|
||||
min-height: 0;
|
||||
}
|
||||
.log-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 6px 10px; background: #2d2d2d; border-bottom: 1px solid #3e3e3e;
|
||||
font-size: 11px; color: #888; flex-shrink: 0;
|
||||
}
|
||||
.log-header i { margin-right: 4px; }
|
||||
.log-clear-btn {
|
||||
background: none; border: 1px solid #555; color: #999;
|
||||
border-radius: 4px; padding: 2px 8px; font-size: 10px;
|
||||
cursor: pointer; transition: all 0.15s;
|
||||
}
|
||||
.log-clear-btn:hover { background: #3e3e3e; color: #ccc; border-color: #777; }
|
||||
.log-content {
|
||||
flex: 1; overflow-y: auto; padding: 6px 10px;
|
||||
font-size: 11px; font-family: Consolas, "Courier New", monospace; line-height: 1.6;
|
||||
}
|
||||
.log-content::-webkit-scrollbar { width: 4px; }
|
||||
.log-content::-webkit-scrollbar-thumb { background: #555; border-radius: 2px; }
|
||||
|
||||
/* 进度条 */
|
||||
.progress-bar {
|
||||
width: 100%; height: 8px; background: #f0f0f0; border-radius: 4px;
|
||||
@@ -248,8 +273,8 @@
|
||||
.stem-icon.vocals { background: linear-gradient(135deg, #0078d4, #005fa3); }
|
||||
.stem-icon.instrumental { background: linear-gradient(135deg, #0a7a1a, #065f12); }
|
||||
.stem-icon.other { background: linear-gradient(135deg, #f57c00, #e65100); }
|
||||
.stem-info { flex: 1; }
|
||||
.stem-name { font-size: 14px; font-weight: 600; color: #333; }
|
||||
.stem-info { width: 180px; flex-shrink: 0; }
|
||||
.stem-name { font-size: 14px; font-weight: 600; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.stem-filename { font-size: 11px; color: #999; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.stem-actions { display: flex; gap: 6px; }
|
||||
.stem-btn {
|
||||
@@ -261,6 +286,35 @@
|
||||
.stem-btn:hover { background: #f0f0f0; color: #333; border-color: #ccc; }
|
||||
.stem-btn.play-btn:hover { color: #0078d4; border-color: #0078d4; }
|
||||
|
||||
/* 音频播放器 */
|
||||
.stem-player {
|
||||
display: none; align-items: center; gap: 8px;
|
||||
flex: 1; min-width: 0;
|
||||
}
|
||||
.stem-player.active { display: flex; }
|
||||
.player-btn {
|
||||
width: 26px; height: 26px; border-radius: 50%; border: none;
|
||||
background: #0078d4; color: #fff; cursor: pointer; display: flex;
|
||||
align-items: center; justify-content: center; font-size: 10px;
|
||||
flex-shrink: 0; transition: all 0.15s;
|
||||
}
|
||||
.player-btn:hover { background: #006cbd; }
|
||||
.player-btn.playing { background: #e53935; }
|
||||
.player-btn.playing:hover { background: #c62828; }
|
||||
.player-progress {
|
||||
width: 200px; flex-shrink: 0; height: 4px;
|
||||
-webkit-appearance: none; appearance: none;
|
||||
background: #e0e0e0; border-radius: 2px; outline: none; cursor: pointer;
|
||||
}
|
||||
.player-progress::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; width: 12px; height: 12px;
|
||||
background: #0078d4; border-radius: 50%; cursor: pointer;
|
||||
}
|
||||
.player-time {
|
||||
font-size: 11px; color: #999; flex-shrink: 0;
|
||||
font-variant-numeric: tabular-nums; min-width: 70px; text-align: right;
|
||||
}
|
||||
|
||||
/* 顶部操作栏 */
|
||||
.top-actions {
|
||||
display: flex; gap: 8px; margin-bottom: 16px;
|
||||
@@ -368,7 +422,7 @@
|
||||
|
||||
<div class="input-group">
|
||||
<label>选择模型</label>
|
||||
<select id="modelSelect" style="width:100%;padding:8px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;transition:all 0.15s ease;">
|
||||
<select id="modelSelect" onchange="onModelSelect(this)" style="width:100%;padding:8px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;transition:all 0.15s ease;">
|
||||
<option value="">-- 请先扫描模型目录 --</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -399,17 +453,16 @@
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Segment(分割参数大小)</label>
|
||||
<input type="text" id="demucsSegment" placeholder="如 Default、1、2、4"
|
||||
list="demucsSegmentOptions"
|
||||
style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<datalist id="demucsSegmentOptions">
|
||||
<option value="Default">
|
||||
<option value="1"><option value="2"><option value="3">
|
||||
<option value="4"><option value="5"><option value="6">
|
||||
<option value="7"><option value="8"><option value="10">
|
||||
<option value="12"><option value="15"><option value="20">
|
||||
<option value="30"><option value="40"><option value="50">
|
||||
</datalist>
|
||||
<select id="demucsSegment" onchange="toggleCustom(this,'demucsSegmentCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<option value="Default">Default</option>
|
||||
<option value="1">1</option><option value="2">2</option><option value="3">3</option>
|
||||
<option value="4">4</option><option value="5">5</option><option value="6">6</option>
|
||||
<option value="7">7</option><option value="8">8</option><option value="10">10</option>
|
||||
<option value="12">12</option><option value="15">15</option><option value="20">20</option>
|
||||
<option value="30">30</option><option value="40">40</option><option value="50">50</option>
|
||||
<option value="__custom__">自定义...</option>
|
||||
</select>
|
||||
<input type="text" id="demucsSegmentCustom" placeholder="输入自定义值" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<div class="param-desc">较小的尺寸占用资源较少。较大的尺寸效果可能更好。</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -419,24 +472,25 @@
|
||||
<div class="arch-params-label"><i class="fas fa-cog"></i> MDX-Net 参数</div>
|
||||
<div class="input-group">
|
||||
<label>Segment(分割参数大小)</label>
|
||||
<input type="text" id="mdxSegmentSize" placeholder="如 Default、256、512、1024"
|
||||
list="mdxSegmentOptions"
|
||||
style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<datalist id="mdxSegmentOptions">
|
||||
<option value="Default">
|
||||
<option value="128"><option value="256"><option value="512">
|
||||
<option value="1024"><option value="2048"><option value="4096">
|
||||
</datalist>
|
||||
<select id="mdxSegmentSize" onchange="toggleCustom(this,'mdxSegmentSizeCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<option value="Default">Default</option>
|
||||
<option value="128">128</option><option value="256">256</option>
|
||||
<option value="512">512</option><option value="1024">1024</option>
|
||||
<option value="2048">2048</option><option value="4096">4096</option>
|
||||
<option value="__custom__">自定义...</option>
|
||||
</select>
|
||||
<input type="text" id="mdxSegmentSizeCustom" placeholder="输入自定义值" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<div class="param-desc">默认大小为 256。音质可能会因你的选择而有所不同。</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Overlap(重叠度)</label>
|
||||
<input type="text" id="mdxOverlap" placeholder="如 0.250(范围 0.10 ~ 0.99)"
|
||||
list="mdxOverlapOptions"
|
||||
style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<datalist id="mdxOverlapOptions">
|
||||
<option value="0.100"><option value="0.250"><option value="0.500"><option value="0.750"><option value="0.990">
|
||||
</datalist>
|
||||
<select id="mdxOverlap" onchange="toggleCustom(this,'mdxOverlapCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<option value="0.100">0.100</option><option value="0.250">0.250</option>
|
||||
<option value="0.500">0.500</option><option value="0.750">0.750</option>
|
||||
<option value="0.990">0.990</option>
|
||||
<option value="__custom__">自定义...</option>
|
||||
</select>
|
||||
<input type="text" id="mdxOverlapCustom" placeholder="输入自定义值,如 0.350" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<div class="param-desc">较高的值可能会带来更好的效果,但也会导致处理时间延长。</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -446,22 +500,28 @@
|
||||
<div class="arch-params-label"><i class="fas fa-cog"></i> VR Arc 参数</div>
|
||||
<div class="input-group">
|
||||
<label>Window Size(窗口大小)</label>
|
||||
<input type="text" id="vrWindowSize" placeholder="如 1024、512、320"
|
||||
list="vrWindowSizeOptions"
|
||||
style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<datalist id="vrWindowSizeOptions">
|
||||
<option value="1024"><option value="512"><option value="320">
|
||||
</datalist>
|
||||
<select id="vrWindowSize" onchange="toggleCustom(this,'vrWindowSizeCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<option value="320">320</option>
|
||||
<option value="512">512</option>
|
||||
<option value="1024">1024</option>
|
||||
<option value="2048">2048</option>
|
||||
<option value="4096">4096</option>
|
||||
<option value="__custom__">自定义...</option>
|
||||
</select>
|
||||
<input type="text" id="vrWindowSizeCustom" placeholder="输入自定义值" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<div class="param-desc">选择窗口大小,以平衡音质与处理速度。</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Aggression(分离强度)</label>
|
||||
<input type="text" id="vrAggression" placeholder="如 5(范围 -100 ~ 100)"
|
||||
list="vrAggressionOptions"
|
||||
style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<datalist id="vrAggressionOptions">
|
||||
<option value="5"><option value="10"><option value="0"><option value="-5"><option value="100"><option value="-100">
|
||||
</datalist>
|
||||
<select id="vrAggression" onchange="toggleCustom(this,'vrAggressionCustom')" style="width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<option value="-100">-100</option><option value="-50">-50</option>
|
||||
<option value="-10">-10</option><option value="-5">-5</option>
|
||||
<option value="0">0</option><option value="5">5</option>
|
||||
<option value="10">10</option><option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="__custom__">自定义...</option>
|
||||
</select>
|
||||
<input type="text" id="vrAggressionCustom" placeholder="输入自定义值,如 20" style="display:none;margin-top:4px;width:100%;padding:7px 10px;border:1px solid #e0e0e0;border-radius:6px;font-size:13px;background:#fff;color:#333;outline:none;">
|
||||
<div class="param-desc">数值越大,提取越深入。对于人声和伴奏,通常设置为 5。</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -540,6 +600,13 @@
|
||||
<div id="stemsList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-area" id="logArea">
|
||||
<div class="log-header">
|
||||
<span><i class="fas fa-code"></i> 控制台日志</span>
|
||||
<button class="log-clear-btn" onclick="clearLogs()" title="清除日志">清除</button>
|
||||
</div>
|
||||
<div class="log-content" id="logContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置弹窗 -->
|
||||
@@ -605,6 +672,37 @@
|
||||
var currentArch = 'Demucs';
|
||||
var scannedModels = [];
|
||||
|
||||
// ── 自定义输入切换 ──────────────────────────────────────────────────────
|
||||
function toggleCustom(sel, customId) {
|
||||
var customEl = document.getElementById(customId);
|
||||
if (!customEl) return;
|
||||
customEl.style.display = sel.value === '__custom__' ? 'block' : 'none';
|
||||
if (sel.value === '__custom__') customEl.focus();
|
||||
}
|
||||
|
||||
function getSelectValue(selectId, customId) {
|
||||
var sel = document.getElementById(selectId);
|
||||
if (!sel) return '';
|
||||
if (sel.value === '__custom__') {
|
||||
var custom = document.getElementById(customId);
|
||||
return custom ? custom.value.trim() : '';
|
||||
}
|
||||
return sel.value;
|
||||
}
|
||||
|
||||
function restoreCustomSelect(selectId, customId, value) {
|
||||
var sel = document.getElementById(selectId);
|
||||
if (!sel) return;
|
||||
// Try matching a preset option
|
||||
for (var i = 0; i < sel.options.length; i++) {
|
||||
if (sel.options[i].value === value) { sel.selectedIndex = i; return; }
|
||||
}
|
||||
// No match — use custom
|
||||
sel.value = '__custom__';
|
||||
var custom = document.getElementById(customId);
|
||||
if (custom) { custom.style.display = 'block'; custom.value = value; }
|
||||
}
|
||||
|
||||
// ── 设置弹窗 ──────────────────────────────────────────────────────────
|
||||
function openSettings() {
|
||||
var dirs = window._savedModelDirs || {};
|
||||
@@ -893,19 +991,14 @@
|
||||
// 架构特异参数
|
||||
if (currentArch === 'Demucs') {
|
||||
var demucsStemSelect = document.getElementById('demucsStemSelect');
|
||||
var demucsSegment = document.getElementById('demucsSegment');
|
||||
formData.append('demucs_stems', demucsStemSelect ? demucsStemSelect.value : 'All Stems');
|
||||
formData.append('segment', demucsSegment ? demucsSegment.value : 'Default');
|
||||
formData.append('segment', getSelectValue('demucsSegment', 'demucsSegmentCustom') || 'Default');
|
||||
} else if (currentArch === 'MDX-Net') {
|
||||
var mdxSegmentSize = document.getElementById('mdxSegmentSize');
|
||||
var mdxOverlap = document.getElementById('mdxOverlap');
|
||||
formData.append('segment', mdxSegmentSize ? mdxSegmentSize.value : 'Default');
|
||||
formData.append('mdx_overlap', mdxOverlap ? mdxOverlap.value : '0.250');
|
||||
formData.append('segment', getSelectValue('mdxSegmentSize', 'mdxSegmentSizeCustom') || 'Default');
|
||||
formData.append('mdx_overlap', getSelectValue('mdxOverlap', 'mdxOverlapCustom') || '0.250');
|
||||
} else if (currentArch === 'VR Arc') {
|
||||
var vrWindowSize = document.getElementById('vrWindowSize');
|
||||
var vrAggression = document.getElementById('vrAggression');
|
||||
formData.append('vr_window_size', vrWindowSize ? vrWindowSize.value : '1024');
|
||||
formData.append('vr_aggression', vrAggression ? vrAggression.value : '5');
|
||||
formData.append('vr_window_size', getSelectValue('vrWindowSize', 'vrWindowSizeCustom') || '1024');
|
||||
formData.append('vr_aggression', getSelectValue('vrAggression', 'vrAggressionCustom') || '5');
|
||||
}
|
||||
|
||||
var startBtn = document.getElementById('startBtn');
|
||||
@@ -921,6 +1014,8 @@
|
||||
if (progressSection) progressSection.style.display = 'block';
|
||||
if (progressFill) progressFill.style.width = '0%';
|
||||
if (statusText) statusText.textContent = '正在上传并初始化...';
|
||||
var logContent = document.getElementById('logContent');
|
||||
if (logContent) logContent.innerHTML = '';
|
||||
|
||||
fetch('/uvr-sep/separate', { method: 'POST', body: formData })
|
||||
.then(function(r) { return r.json(); })
|
||||
@@ -944,6 +1039,22 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ── 日志 ──────────────────────────────────────────────────────────────
|
||||
function renderLogs(logs) {
|
||||
var el = document.getElementById('logContent');
|
||||
if (!el || !logs || !logs.length) return;
|
||||
el.innerHTML = logs.map(function(l) { return l.replace(/</g, '<'); }).join('<br>');
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
var el = document.getElementById('logContent');
|
||||
if (el) el.innerHTML = '';
|
||||
if (currentTaskId) {
|
||||
fetch('/uvr-sep/clear-logs/' + currentTaskId, { method: 'POST' });
|
||||
}
|
||||
}
|
||||
|
||||
// ── 轮询状态 ──────────────────────────────────────────────────────────
|
||||
function pollStatus() {
|
||||
if (!currentTaskId) return;
|
||||
@@ -960,6 +1071,7 @@
|
||||
var emptyState = document.getElementById('emptyState');
|
||||
|
||||
if (progressFill) progressFill.style.width = data.progress + '%';
|
||||
if (data.logs) renderLogs(data.logs);
|
||||
var devInfo = data.device ? ' [' + data.device + ']' : '';
|
||||
if (data.status === 'done') {
|
||||
clearInterval(pollTimer); pollTimer = null;
|
||||
@@ -990,14 +1102,25 @@
|
||||
var name = s.stem.toLowerCase();
|
||||
if (name === 'vocals' || name === 'vocal') { iconClass = 'vocals'; icon = 'fa-microphone'; }
|
||||
else if (name === 'instrumental' || name === 'no vocals') { iconClass = 'instrumental'; icon = 'fa-folder'; }
|
||||
var safeStem = s.stem.replace(/'/g, "\\'");
|
||||
var row = document.createElement('div');
|
||||
row.className = 'stem-row';
|
||||
row.innerHTML =
|
||||
'<div class="stem-icon ' + iconClass + '"><i class="fas ' + icon + '"></i></div>' +
|
||||
'<div class="stem-info"><div class="stem-name">' + s.stem + '</div><div class="stem-filename">' + s.filename + '</div></div>' +
|
||||
'<div class="stem-actions">' +
|
||||
'<button class="stem-btn play-btn" onclick="playStem(\'' + currentTaskId + '\',\'' + s.stem + '\')"><i class="fas fa-play"></i></button>' +
|
||||
'<button class="stem-btn" onclick="downloadStem(\'' + currentTaskId + '\',\'' + s.stem + '\')"><i class="fas fa-folder-plus"></i></button>' +
|
||||
'<div style="display:flex;align-items:center;gap:10px;margin-left:auto;">' +
|
||||
'<div class="stem-player active" id="player_' + safeStem + '">' +
|
||||
'<button class="player-btn" id="playerBtn_' + safeStem + '" onclick="playStem(\'' + currentTaskId + '\',\'' + safeStem + '\')" title="播放"><i class="fas fa-play"></i></button>' +
|
||||
'<input type="range" class="player-progress" id="progress_' + safeStem + '" min="0" max="100" value="0" step="0.1"' +
|
||||
' onmousedown="startSeeking(\'' + safeStem + '\')"' +
|
||||
' onmouseup="doSeek(\'' + safeStem + '\')"' +
|
||||
' ontouchstart="startSeeking(\'' + safeStem + '\')"' +
|
||||
' ontouchend="doSeek(\'' + safeStem + '\')">' +
|
||||
'<span class="player-time" id="time_' + safeStem + '">0:00 / 0:00</span>' +
|
||||
'</div>' +
|
||||
'<div class="stem-actions">' +
|
||||
'<button class="stem-btn" onclick="downloadStem(\'' + currentTaskId + '\',\'' + safeStem + '\')" title="下载"><i class="fas fa-folder-plus"></i></button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
list.appendChild(row);
|
||||
});
|
||||
@@ -1007,12 +1130,115 @@
|
||||
if (resultSection) resultSection.style.display = 'block';
|
||||
}
|
||||
|
||||
// ── 音频播放控制 ─────────────────────────────────────────────────────
|
||||
var currentStem = '';
|
||||
var playTimer = null;
|
||||
var seeking = false;
|
||||
|
||||
function playStem(taskId, stem) {
|
||||
var audio = document.getElementById('previewAudio');
|
||||
if (audio) {
|
||||
if (!audio) return;
|
||||
|
||||
// 切换音轨
|
||||
if (currentStem !== stem) {
|
||||
stopCurrentStem();
|
||||
currentStem = stem;
|
||||
audio.src = '/uvr-sep/download/' + encodeURIComponent(taskId) + '/' + encodeURIComponent(stem);
|
||||
audio.play();
|
||||
}
|
||||
|
||||
// 播放/暂停切换
|
||||
if (audio.paused) {
|
||||
audio.play();
|
||||
} else {
|
||||
audio.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function stopCurrentStem() {
|
||||
var audio = document.getElementById('previewAudio');
|
||||
if (!audio) return;
|
||||
audio.pause();
|
||||
audio.removeAttribute('src');
|
||||
if (playTimer) { clearInterval(playTimer); playTimer = null; }
|
||||
if (currentStem) {
|
||||
var btn = document.getElementById('playerBtn_' + currentStem);
|
||||
var prog = document.getElementById('progress_' + currentStem);
|
||||
var time = document.getElementById('time_' + currentStem);
|
||||
if (btn) { btn.className = 'player-btn'; btn.innerHTML = '<i class="fas fa-play"></i>'; }
|
||||
if (prog) prog.value = 0;
|
||||
if (time) time.textContent = '0:00 / 0:00';
|
||||
}
|
||||
currentStem = '';
|
||||
}
|
||||
|
||||
// audio 事件
|
||||
(function() {
|
||||
var audio = document.getElementById('previewAudio');
|
||||
if (!audio) return;
|
||||
|
||||
audio.addEventListener('play', function() {
|
||||
if (!currentStem) return;
|
||||
var btn = document.getElementById('playerBtn_' + currentStem);
|
||||
if (btn) { btn.className = 'player-btn playing'; btn.innerHTML = '<i class="fas fa-pause"></i>'; }
|
||||
if (playTimer) clearInterval(playTimer);
|
||||
playTimer = setInterval(updatePlayProgress, 200);
|
||||
});
|
||||
|
||||
audio.addEventListener('pause', function() {
|
||||
if (!currentStem) return;
|
||||
var btn = document.getElementById('playerBtn_' + currentStem);
|
||||
if (btn) { btn.className = 'player-btn'; btn.innerHTML = '<i class="fas fa-play"></i>'; }
|
||||
if (playTimer) { clearInterval(playTimer); playTimer = null; }
|
||||
});
|
||||
|
||||
audio.addEventListener('ended', function() {
|
||||
if (!currentStem) return;
|
||||
var btn = document.getElementById('playerBtn_' + currentStem);
|
||||
var prog = document.getElementById('progress_' + currentStem);
|
||||
var time = document.getElementById('time_' + currentStem);
|
||||
if (btn) { btn.className = 'player-btn'; btn.innerHTML = '<i class="fas fa-play"></i>'; }
|
||||
if (prog) prog.value = 100;
|
||||
if (time) time.textContent = formatPlayerTime(audio.duration) + ' / ' + formatPlayerTime(audio.duration);
|
||||
if (playTimer) { clearInterval(playTimer); playTimer = null; }
|
||||
});
|
||||
|
||||
audio.addEventListener('loadedmetadata', function() {
|
||||
updatePlayProgress();
|
||||
});
|
||||
})();
|
||||
|
||||
function updatePlayProgress() {
|
||||
var audio = document.getElementById('previewAudio');
|
||||
if (!audio || !currentStem || isNaN(audio.duration)) return;
|
||||
var prog = document.getElementById('progress_' + currentStem);
|
||||
var time = document.getElementById('time_' + currentStem);
|
||||
if (prog && !seeking) {
|
||||
prog.value = audio.currentTime / audio.duration * 100;
|
||||
}
|
||||
if (time) {
|
||||
time.textContent = formatPlayerTime(audio.currentTime) + ' / ' + formatPlayerTime(audio.duration);
|
||||
}
|
||||
}
|
||||
|
||||
function startSeeking(stem) {
|
||||
if (stem === currentStem) seeking = true;
|
||||
}
|
||||
|
||||
function doSeek(stem) {
|
||||
seeking = false;
|
||||
var audio = document.getElementById('previewAudio');
|
||||
if (!audio || stem !== currentStem || isNaN(audio.duration)) return;
|
||||
var prog = document.getElementById('progress_' + stem);
|
||||
if (prog) {
|
||||
audio.currentTime = parseFloat(prog.value) / 100 * audio.duration;
|
||||
}
|
||||
}
|
||||
|
||||
function formatPlayerTime(sec) {
|
||||
if (isNaN(sec)) return '0:00';
|
||||
var m = Math.floor(sec / 60);
|
||||
var s = Math.floor(sec % 60);
|
||||
return m + ':' + (s < 10 ? '0' : '') + s;
|
||||
}
|
||||
|
||||
function downloadStem(taskId, stem) {
|
||||
@@ -1048,11 +1274,6 @@
|
||||
var isSecondaryOnly = document.getElementById('isSecondaryOnly');
|
||||
var saveFormat = document.getElementById('saveFormat');
|
||||
var demucsStemSelect = document.getElementById('demucsStemSelect');
|
||||
var demucsSegment = document.getElementById('demucsSegment');
|
||||
var mdxSegmentSize = document.getElementById('mdxSegmentSize');
|
||||
var mdxOverlap = document.getElementById('mdxOverlap');
|
||||
var vrWindowSize = document.getElementById('vrWindowSize');
|
||||
var vrAggression = document.getElementById('vrAggression');
|
||||
|
||||
var cfg = {
|
||||
uvr_project_path: uvrPath ? uvrPath.value.trim() : '',
|
||||
@@ -1069,11 +1290,11 @@
|
||||
is_secondary_stem_only: isSecondaryOnly ? isSecondaryOnly.checked : false,
|
||||
save_format: saveFormat ? saveFormat.value : 'wav',
|
||||
demucs_stems: demucsStemSelect ? demucsStemSelect.value : 'All Stems',
|
||||
demucs_segment: demucsSegment ? demucsSegment.value : 'Default',
|
||||
mdx_segment_size: mdxSegmentSize ? mdxSegmentSize.value : 'Default',
|
||||
mdx_overlap: mdxOverlap ? parseFloat(mdxOverlap.value) : 0.25,
|
||||
vr_window_size: vrWindowSize ? parseInt(vrWindowSize.value) : 1024,
|
||||
vr_aggression: vrAggression ? parseInt(vrAggression.value) : 5,
|
||||
demucs_segment: getSelectValue('demucsSegment', 'demucsSegmentCustom') || 'Default',
|
||||
mdx_segment_size: getSelectValue('mdxSegmentSize', 'mdxSegmentSizeCustom') || 'Default',
|
||||
mdx_overlap: parseFloat(getSelectValue('mdxOverlap', 'mdxOverlapCustom')) || 0.25,
|
||||
vr_window_size: parseInt(getSelectValue('vrWindowSize', 'vrWindowSizeCustom')) || 1024,
|
||||
vr_aggression: parseInt(getSelectValue('vrAggression', 'vrAggressionCustom')) || 5,
|
||||
};
|
||||
|
||||
// 保存选中模型(按架构区分)
|
||||
@@ -1190,20 +1411,15 @@
|
||||
if (demucsStemSel.options[i].value === cfg.demucs_stems) { demucsStemSel.selectedIndex = i; break; }
|
||||
}
|
||||
}
|
||||
var demucsSegSel = document.getElementById('demucsSegment');
|
||||
if (demucsSegSel) demucsSegSel.value = cfg.demucs_segment;
|
||||
restoreCustomSelect('demucsSegment', 'demucsSegmentCustom', cfg.demucs_segment);
|
||||
|
||||
// 恢复 MDX 参数
|
||||
var mdxSegSel = document.getElementById('mdxSegmentSize');
|
||||
if (mdxSegSel) mdxSegSel.value = cfg.mdx_segment_size;
|
||||
var mdxOverlap = document.getElementById('mdxOverlap');
|
||||
if (mdxOverlap) mdxOverlap.value = cfg.mdx_overlap.toFixed(3);
|
||||
restoreCustomSelect('mdxSegmentSize', 'mdxSegmentSizeCustom', cfg.mdx_segment_size);
|
||||
restoreCustomSelect('mdxOverlap', 'mdxOverlapCustom', cfg.mdx_overlap.toFixed(3));
|
||||
|
||||
// 恢复 VR 参数
|
||||
var vrWinSel = document.getElementById('vrWindowSize');
|
||||
if (vrWinSel) vrWinSel.value = cfg.vr_window_size;
|
||||
var vrAggression = document.getElementById('vrAggression');
|
||||
if (vrAggression) vrAggression.value = cfg.vr_aggression;
|
||||
restoreCustomSelect('vrWindowSize', 'vrWindowSizeCustom', String(cfg.vr_window_size));
|
||||
restoreCustomSelect('vrAggression', 'vrAggressionCustom', String(cfg.vr_aggression));
|
||||
|
||||
// 恢复通用参数
|
||||
var isGpu = document.getElementById('isGpu');
|
||||
|
||||
Reference in New Issue
Block a user