generated from dellevin/template
555
This commit is contained in:
8
flask-dev-api/.idea/.gitignore
generated
vendored
Normal file
8
flask-dev-api/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
25
flask-dev-api/.idea/flask-dev-api.iml
generated
Normal file
25
flask-dev-api/.idea/flask-dev-api.iml
generated
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="Flask">
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
|
||||||
|
<option name="TEMPLATE_FOLDERS">
|
||||||
|
<list>
|
||||||
|
<option value="$MODULE_DIR$/templates" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
23
flask-dev-api/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
23
flask-dev-api/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<value>
|
||||||
|
<list size="9">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="sentence_transformers" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="fast_langdetect" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="jieba_fast" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="rotary_embedding_torch" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="ko_pron" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="pyopenjtalk" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="g2p_en" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="huggingface_hub" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="x_transformers" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
flask-dev-api/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
flask-dev-api/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
flask-dev-api/.idea/misc.xml
generated
Normal file
7
flask-dev-api/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
flask-dev-api/.idea/modules.xml
generated
Normal file
8
flask-dev-api/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/flask-dev-api.iml" filepath="$PROJECT_DIR$/.idea/flask-dev-api.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
flask-dev-api/.idea/vcs.xml
generated
Normal file
6
flask-dev-api/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
15
flask-dev-api/.idea/webResources.xml
generated
Normal file
15
flask-dev-api/.idea/webResources.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="WebResourcesPaths">
|
||||||
|
<contentEntries>
|
||||||
|
<entry url="file://$PROJECT_DIR$">
|
||||||
|
<entryData>
|
||||||
|
<resourceRoots>
|
||||||
|
<path value="file://$PROJECT_DIR$/config" />
|
||||||
|
<path value="file://$PROJECT_DIR$/static" />
|
||||||
|
</resourceRoots>
|
||||||
|
</entryData>
|
||||||
|
</entry>
|
||||||
|
</contentEntries>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -15,7 +15,7 @@ from blueprints import pin_tu_bp, base64_bp, down_video_bp, fen_ci_bp, content_t
|
|||||||
from utils.stats_db import init_db, record_call, get_total_count, get_avg_duration, get_daily_counts, get_monthly_counts, get_available_years, get_available_months, get_daily_counts_by_month, get_hourly_counts, get_endpoint_stats, get_today_count, get_yesterday_count, get_month_count, get_success_rate, get_duration_distribution, get_slowest_endpoints, get_week_compare, get_recent_calls, get_active_days
|
from utils.stats_db import init_db, record_call, get_total_count, get_avg_duration, get_daily_counts, get_monthly_counts, get_available_years, get_available_months, get_daily_counts_by_month, get_hourly_counts, get_endpoint_stats, get_today_count, get_yesterday_count, get_month_count, get_success_rate, get_duration_distribution, get_slowest_endpoints, get_week_compare, get_recent_calls, get_active_days
|
||||||
from utils.stats_db import ALLOWED_TABLES, get_table_info, get_table_rows, get_table_count, insert_row, update_row, delete_rows, clear_table
|
from utils.stats_db import ALLOWED_TABLES, get_table_info, get_table_rows, get_table_count, insert_row, update_row, delete_rows, clear_table
|
||||||
|
|
||||||
TOOL_COUNT = 19
|
TOOL_COUNT = 18
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"threshold": -41,
|
"threshold": -41,
|
||||||
"min_length": 5000,
|
"min_length": 5000,
|
||||||
"min_interval": 300,
|
"min_interval": 500,
|
||||||
"hop_size": 10,
|
"hop_size": 10,
|
||||||
"max_sil_kept": 1000
|
"max_sil_kept": 1000
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,13 @@
|
|||||||
"model_dir_mode": "absolute",
|
"model_dir_mode": "absolute",
|
||||||
"index_dir": "E:\\AI\\RVC\\rvc_con\\index",
|
"index_dir": "E:\\AI\\RVC\\rvc_con\\index",
|
||||||
"index_dir_mode": "absolute",
|
"index_dir_mode": "absolute",
|
||||||
"last_model": "",
|
"last_model": "bubu-sing.pth",
|
||||||
"last_index": "",
|
"last_index": "E:\\AI\\RVC\\rvc_con\\index\\bubu-sing_v2.index",
|
||||||
"f0_up_key": 0,
|
"f0_up_key": 0,
|
||||||
"f0_method": "rmvpe",
|
"f0_method": "rmvpe",
|
||||||
"index_rate": 0.75,
|
"index_rate": 1,
|
||||||
"filter_radius": 3,
|
"filter_radius": 3,
|
||||||
"resample_sr": 22050,
|
"resample_sr": 32000,
|
||||||
"rms_mix_rate": 0.25,
|
"rms_mix_rate": 0.5,
|
||||||
"protect": 0.33
|
"protect": 0.37
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -280,6 +280,56 @@
|
|||||||
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||||
border-radius: 8px; pointer-events: none;
|
border-radius: 8px; pointer-events: none;
|
||||||
}
|
}
|
||||||
|
#segmentOverlay {
|
||||||
|
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
border-radius: 8px; pointer-events: none; z-index: 1;
|
||||||
|
}
|
||||||
|
.seg-gray {
|
||||||
|
position: absolute; top: 0; height: 100%;
|
||||||
|
background: rgba(0,0,0,0.18); pointer-events: none;
|
||||||
|
}
|
||||||
|
#playCursor {
|
||||||
|
position: absolute; top: 0; height: 100%; width: 10px;
|
||||||
|
transform: translateX(-5px); z-index: 5;
|
||||||
|
pointer-events: auto; cursor: col-resize;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#playCursor::after {
|
||||||
|
content: ''; position: absolute; left: 50%; top: 0; bottom: 0;
|
||||||
|
width: 2px; transform: translateX(-50%);
|
||||||
|
background: #e53935; box-shadow: 0 0 4px rgba(229,57,53,0.4);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#cursorHandle {
|
||||||
|
position: absolute; left: 50%; top: -6px; transform: translateX(-50%);
|
||||||
|
width: 12px; height: 12px; border-radius: 50%;
|
||||||
|
background: #e53935; border: 2px solid #fff;
|
||||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.25);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.player-controls {
|
||||||
|
display: flex; align-items: center; gap: 10px;
|
||||||
|
padding: 6px 12px; margin-top: 4px;
|
||||||
|
background: #fff; border: 1px solid #e0e0e0; border-radius: 8px;
|
||||||
|
font-size: 12px; color: #555;
|
||||||
|
}
|
||||||
|
.player-controls button {
|
||||||
|
width: 30px; height: 30px; border: none; border-radius: 50%;
|
||||||
|
background: #0078d4; color: #fff; cursor: pointer;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 12px; transition: background 0.15s;
|
||||||
|
}
|
||||||
|
.player-controls button:hover { background: #005fa3; }
|
||||||
|
.player-controls .p-time { font-variant-numeric: tabular-nums; min-width: 36px; text-align: center; }
|
||||||
|
.player-controls input[type="range"] {
|
||||||
|
flex: 1; height: 4px; -webkit-appearance: none; appearance: none;
|
||||||
|
background: #e0e0e0; border-radius: 2px; outline: none; cursor: pointer;
|
||||||
|
}
|
||||||
|
.player-controls input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none; appearance: none;
|
||||||
|
width: 14px; height: 14px; border-radius: 50%;
|
||||||
|
background: #0078d4; cursor: pointer;
|
||||||
|
}
|
||||||
.cut-marker {
|
.cut-marker {
|
||||||
position: absolute; top: 0; width: 24px; height: 100%;
|
position: absolute; top: 0; width: 24px; height: 100%;
|
||||||
transform: translateX(-50%); z-index: 2; cursor: grab;
|
transform: translateX(-50%); z-index: 2; cursor: grab;
|
||||||
@@ -431,6 +481,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="timelineArea" style="display:none;">
|
<div id="timelineArea" style="display:none;">
|
||||||
<div class="timeline-wrap" id="timelineBar"></div>
|
<div class="timeline-wrap" id="timelineBar"></div>
|
||||||
|
<div class="player-controls" id="playerControls" style="display:none;">
|
||||||
|
<button onclick="playerToggle()" title="播放/暂停"><i id="playerIcon" class="fas fa-play"></i></button>
|
||||||
|
<span class="p-time" id="playerTime">0:00</span>
|
||||||
|
<input type="range" id="playerSeekBar" min="0" max="1000" value="0" step="1">
|
||||||
|
<span class="p-time" id="playerTotal">0:00</span>
|
||||||
|
</div>
|
||||||
<div id="redoWrap" style="display:none;text-align:center;margin-top:8px;">
|
<div id="redoWrap" style="display:none;text-align:center;margin-top:8px;">
|
||||||
<button class="action-btn slice" style="width:auto;padding:8px 20px;font-size:13px;" onclick="redoManualSlice()">
|
<button class="action-btn slice" style="width:auto;padding:8px 20px;font-size:13px;" onclick="redoManualSlice()">
|
||||||
<i class="fas fa-redo"></i> 切割点已更改,点击重新切割
|
<i class="fas fa-redo"></i> 切割点已更改,点击重新切割
|
||||||
@@ -466,6 +522,14 @@
|
|||||||
let manualSegChanged = false; // 用户是否手动调整过勾选
|
let manualSegChanged = false; // 用户是否手动调整过勾选
|
||||||
let manualSegInit = false; // 新建预览时重置勾选
|
let manualSegInit = false; // 新建预览时重置勾选
|
||||||
|
|
||||||
|
// 播放器状态
|
||||||
|
var playerAudio = null;
|
||||||
|
var playerPlaying = false;
|
||||||
|
var _playerRAF = null;
|
||||||
|
var _playerStartSec = 0;
|
||||||
|
var _playerEndSec = 0;
|
||||||
|
var _playerMode = 'full'; // 'full' 全曲 | 'seg' 片段
|
||||||
|
|
||||||
function showMessage(text, type, duration) {
|
function showMessage(text, type, duration) {
|
||||||
type = type || 'info'; duration = duration || 3000;
|
type = type || 'info'; duration = duration || 3000;
|
||||||
var t = document.createElement('div');
|
var t = document.createElement('div');
|
||||||
@@ -648,14 +712,6 @@
|
|||||||
document.getElementById('sliceList').innerHTML = html;
|
document.getElementById('sliceList').innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function playPreviewSeg(index) {
|
|
||||||
if (!taskId || !window._previewRanges || !window._previewRanges[index]) return;
|
|
||||||
if (currentAudio) { currentAudio.pause(); currentAudio = null; }
|
|
||||||
var r = window._previewRanges[index];
|
|
||||||
var audio = new Audio('/audio-slicer/preview-range/' + taskId + '?start=' + r[0] + '&end=' + r[1]);
|
|
||||||
audio.play();
|
|
||||||
currentAudio = audio;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSegment(idx) {
|
function toggleSegment(idx) {
|
||||||
var pos = selectedSegments.indexOf(idx);
|
var pos = selectedSegments.indexOf(idx);
|
||||||
@@ -696,6 +752,7 @@
|
|||||||
if (el) el.textContent = '已选 ' + selectedSegments.length + '/' + total;
|
if (el) el.textContent = '已选 ' + selectedSegments.length + '/' + total;
|
||||||
var btnSlice = document.getElementById('btnSlice');
|
var btnSlice = document.getElementById('btnSlice');
|
||||||
if (btnSlice) btnSlice.disabled = selectedSegments.length === 0;
|
if (btnSlice) btnSlice.disabled = selectedSegments.length === 0;
|
||||||
|
_updateSegmentOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doSlice() {
|
async function doSlice() {
|
||||||
@@ -790,13 +847,8 @@
|
|||||||
document.getElementById('sliceList').innerHTML = html;
|
document.getElementById('sliceList').innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var currentAudio = null;
|
var currentAudio = null;
|
||||||
function playSlice(index) {
|
|
||||||
if (currentAudio) { currentAudio.pause(); currentAudio = null; }
|
|
||||||
var audio = new Audio('/audio-slicer/download/' + taskId + '/' + index);
|
|
||||||
audio.play();
|
|
||||||
currentAudio = audio;
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadSlice(index) {
|
function downloadSlice(index) {
|
||||||
var a = document.createElement('a');
|
var a = document.createElement('a');
|
||||||
@@ -902,8 +954,6 @@
|
|||||||
// 新预览时重置勾选
|
// 新预览时重置勾选
|
||||||
if (manualSegInit) {
|
if (manualSegInit) {
|
||||||
manualSegInit = false;
|
manualSegInit = false;
|
||||||
manualSegSelected = [];
|
|
||||||
manualSegChanged = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var html = '';
|
var html = '';
|
||||||
@@ -918,8 +968,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var boundaries = [0].concat(cutPoints).concat([audioDuration]);
|
var boundaries = [0].concat(cutPoints).concat([audioDuration]);
|
||||||
// 确保勾选数组长度匹配
|
// 仅初始化时全选;用户已手动调整勾选则保留
|
||||||
if (manualSegSelected.length !== boundaries.length - 1) {
|
if (manualSegSelected.length !== boundaries.length - 1 && !manualSegChanged) {
|
||||||
manualSegSelected = [];
|
manualSegSelected = [];
|
||||||
for (var j = 0; j < boundaries.length - 1; j++) manualSegSelected.push(j);
|
for (var j = 0; j < boundaries.length - 1; j++) manualSegSelected.push(j);
|
||||||
}
|
}
|
||||||
@@ -965,13 +1015,6 @@
|
|||||||
markCutPointsChanged();
|
markCutPointsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
var previewAudio = null;
|
|
||||||
function playPreview(start, end) {
|
|
||||||
if (!taskId) return;
|
|
||||||
if (previewAudio) { previewAudio.pause(); previewAudio = null; }
|
|
||||||
previewAudio = new Audio('/audio-slicer/preview-range/' + taskId + '?start=' + start + '&end=' + end);
|
|
||||||
previewAudio.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleManualSeg(idx) {
|
function toggleManualSeg(idx) {
|
||||||
var pos = manualSegSelected.indexOf(idx);
|
var pos = manualSegSelected.indexOf(idx);
|
||||||
@@ -988,6 +1031,7 @@
|
|||||||
if (row) row.className = 'seg-row' + (pos < 0 ? ' selected' : ' dimmed');
|
if (row) row.className = 'seg-row' + (pos < 0 ? ' selected' : ' dimmed');
|
||||||
updateManualSegCount();
|
updateManualSegCount();
|
||||||
updateManualBtn();
|
updateManualBtn();
|
||||||
|
_updateSegmentOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectManualSegments() {
|
function selectManualSegments() {
|
||||||
@@ -995,6 +1039,7 @@
|
|||||||
for (var i = 0; i < cutPoints.length + 1; i++) manualSegSelected.push(i);
|
for (var i = 0; i < cutPoints.length + 1; i++) manualSegSelected.push(i);
|
||||||
manualSegChanged = true;
|
manualSegChanged = true;
|
||||||
renderCutList();
|
renderCutList();
|
||||||
|
_updateSegmentOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1030,7 +1075,9 @@
|
|||||||
|
|
||||||
bar.className = readOnly ? 'readonly' : '';
|
bar.className = readOnly ? 'readonly' : '';
|
||||||
|
|
||||||
var html = '<canvas id="waveformCanvas"></canvas>';
|
var html = '<canvas id="waveformCanvas"></canvas>'
|
||||||
|
+ '<div id="segmentOverlay"></div>'
|
||||||
|
+ '<div id="playCursor"><div id="cursorHandle"></div></div>';
|
||||||
// 时间刻度(在波形条上方)
|
// 时间刻度(在波形条上方)
|
||||||
var step = Math.max(1, Math.ceil(audioDuration / 10));
|
var step = Math.max(1, Math.ceil(audioDuration / 10));
|
||||||
for (var t = 0; t <= audioDuration; t += step) {
|
for (var t = 0; t <= audioDuration; t += step) {
|
||||||
@@ -1050,20 +1097,62 @@
|
|||||||
|
|
||||||
// 绘制波形
|
// 绘制波形
|
||||||
if (waveformPeaks) drawWaveform();
|
if (waveformPeaks) drawWaveform();
|
||||||
|
_updateSegmentOverlay();
|
||||||
|
|
||||||
if (readOnly) return; // 只读模式不绑定交互事件
|
// 播放控制条
|
||||||
|
var pc = document.getElementById('playerControls');
|
||||||
|
if (pc) { pc.style.display = audioDuration > 0 ? '' : 'none'; }
|
||||||
|
var pt = document.getElementById('playerTotal');
|
||||||
|
if (pt) pt.textContent = formatTime(audioDuration);
|
||||||
|
_showCursorAtCurrent();
|
||||||
|
|
||||||
// 时间轴空白区域点击 → 添加切割点
|
// 绑定事件(只绑一次)
|
||||||
if (!bar._bound) {
|
if (!bar._bound) {
|
||||||
bar._bound = true;
|
bar._bound = true;
|
||||||
|
|
||||||
|
// 时间轴点击:只读模式 → seek,手动模式 → 添加切割点
|
||||||
bar.addEventListener('click', function(e) {
|
bar.addEventListener('click', function(e) {
|
||||||
if (bar._suppressClick) { bar._suppressClick = false; return; }
|
if (bar._suppressClick) { bar._suppressClick = false; return; }
|
||||||
if (bar.classList.contains('readonly')) return;
|
if (e.target.closest('.cut-marker') || e.target.closest('#playCursor')) return;
|
||||||
if (e.target.closest('.cut-marker')) return;
|
|
||||||
var rect = bar.getBoundingClientRect();
|
var rect = bar.getBoundingClientRect();
|
||||||
var sec = (e.clientX - rect.left) / rect.width * audioDuration;
|
var sec = (e.clientX - rect.left) / rect.width * audioDuration;
|
||||||
|
if (bar.classList.contains('readonly')) {
|
||||||
|
playerSeekTo(sec);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 手动模式:不允许在灰色片段内添加切割点
|
||||||
|
var ranges = _getSegmentRanges();
|
||||||
|
var sel = _getSelectedSet();
|
||||||
|
if (ranges.length && sel.length) {
|
||||||
|
for (var i = 0; i < ranges.length; i++) {
|
||||||
|
if (sec >= ranges[i][0] && sec <= ranges[i][1]) {
|
||||||
|
if (sel.indexOf(i) < 0) return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (sec > 0.5 && sec < audioDuration - 0.5) addCutPoint(sec);
|
if (sec > 0.5 && sec < audioDuration - 0.5) addCutPoint(sec);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 播放游标拖拽
|
||||||
|
_bindCursorDrag(bar);
|
||||||
|
|
||||||
|
// 进度条拖拽 seek
|
||||||
|
var seekBar = document.getElementById('playerSeekBar');
|
||||||
|
if (seekBar) {
|
||||||
|
seekBar.addEventListener('input', function() {
|
||||||
|
var sec = (parseFloat(this.value) / 1000) * audioDuration;
|
||||||
|
var cursor = document.getElementById('playCursor');
|
||||||
|
var timeEl = document.getElementById('playerTime');
|
||||||
|
if (cursor) { cursor.style.display = 'block'; cursor.style.left = (sec / audioDuration * 100).toFixed(2) + '%'; }
|
||||||
|
if (timeEl) timeEl.textContent = formatTime(sec);
|
||||||
|
if (playerAudio) {
|
||||||
|
var rel = sec - _playerStartSec;
|
||||||
|
var dur = _playerEndSec - _playerStartSec;
|
||||||
|
if (rel >= 0 && rel <= dur) playerAudio.currentTime = rel;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个标记绑定拖动
|
// 为每个标记绑定拖动
|
||||||
@@ -1099,6 +1188,245 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 片段灰色覆盖(取消选中的片段变灰) ──
|
||||||
|
function _getSegmentRanges() {
|
||||||
|
// 手动模式:从 cutPoints 计算
|
||||||
|
if (cutMode === 'manual') {
|
||||||
|
if (cutPoints.length > 0) {
|
||||||
|
var b = [0].concat(cutPoints.slice().sort(function(a,b){return a-b;})).concat([audioDuration]);
|
||||||
|
var ranges = [];
|
||||||
|
for (var i = 0; i < b.length - 1; i++) ranges.push([b[i], b[i+1]]);
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// 自动模式:用预览区间
|
||||||
|
if (window._previewRanges && window._previewRanges.length) {
|
||||||
|
return window._previewRanges;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getSelectedSet() {
|
||||||
|
if (manualSegSelected && manualSegSelected.length > 0 && cutPoints.length > 0) {
|
||||||
|
return manualSegSelected;
|
||||||
|
}
|
||||||
|
return selectedSegments;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _updateSegmentOverlay() {
|
||||||
|
var overlay = document.getElementById('segmentOverlay');
|
||||||
|
if (!overlay || audioDuration <= 0) return;
|
||||||
|
var ranges = _getSegmentRanges();
|
||||||
|
if (!ranges.length) { overlay.innerHTML = ''; return; }
|
||||||
|
var sel = _getSelectedSet();
|
||||||
|
var html = '';
|
||||||
|
for (var i = 0; i < ranges.length; i++) {
|
||||||
|
if (sel.indexOf(i) < 0) {
|
||||||
|
var left = (ranges[i][0] / audioDuration * 100).toFixed(2);
|
||||||
|
var width = ((ranges[i][1] - ranges[i][0]) / audioDuration * 100).toFixed(2);
|
||||||
|
html += '<div class="seg-gray" style="left:' + left + '%;width:' + width + '%;"></div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overlay.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 播放器系统 ──
|
||||||
|
function _showCursorAtCurrent() {
|
||||||
|
var cursor = document.getElementById('playCursor');
|
||||||
|
if (!cursor) return;
|
||||||
|
if (playerAudio) {
|
||||||
|
var sec = _playerStartSec + playerAudio.currentTime;
|
||||||
|
cursor.style.left = (sec / audioDuration * 100).toFixed(2) + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _playerTick() {
|
||||||
|
var cursor = document.getElementById('playCursor');
|
||||||
|
var timeEl = document.getElementById('playerTime');
|
||||||
|
if (!playerAudio || !cursor) return;
|
||||||
|
var sec = _playerStartSec + playerAudio.currentTime;
|
||||||
|
cursor.style.left = (sec / audioDuration * 100).toFixed(2) + '%';
|
||||||
|
if (timeEl) timeEl.textContent = formatTime(sec);
|
||||||
|
var seekBar = document.getElementById('playerSeekBar');
|
||||||
|
if (seekBar && audioDuration > 0) seekBar.value = (sec / audioDuration * 1000).toFixed(0);
|
||||||
|
_playerRAF = requestAnimationFrame(_playerTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerStartTick() {
|
||||||
|
playerStopTick();
|
||||||
|
var cursor = document.getElementById('playCursor');
|
||||||
|
if (cursor) cursor.style.display = 'block';
|
||||||
|
_playerRAF = requestAnimationFrame(_playerTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerStopTick() {
|
||||||
|
if (_playerRAF) { cancelAnimationFrame(_playerRAF); _playerRAF = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerSeekTo(sec) {
|
||||||
|
sec = Math.max(0, Math.min(audioDuration, sec));
|
||||||
|
var cursor = document.getElementById('playCursor');
|
||||||
|
var timeEl = document.getElementById('playerTime');
|
||||||
|
if (cursor) {
|
||||||
|
cursor.style.display = 'block';
|
||||||
|
cursor.style.left = (sec / audioDuration * 100).toFixed(2) + '%';
|
||||||
|
}
|
||||||
|
if (timeEl) timeEl.textContent = formatTime(sec);
|
||||||
|
if (playerAudio && !playerAudio.paused) {
|
||||||
|
var rel = sec - _playerStartSec;
|
||||||
|
if (rel >= 0 && rel <= _playerEndSec - _playerStartSec) {
|
||||||
|
playerAudio.currentTime = rel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerToggle() {
|
||||||
|
if (playerPlaying) {
|
||||||
|
playerPause();
|
||||||
|
} else {
|
||||||
|
playerPlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerPlay() {
|
||||||
|
if (!taskId || audioDuration <= 0) return;
|
||||||
|
if (playerAudio && !playerAudio.paused) return;
|
||||||
|
// 恢复暂停的音频
|
||||||
|
if (playerAudio && playerAudio.paused && playerAudio.currentTime > 0 && playerAudio.duration > 0) {
|
||||||
|
playerAudio.play();
|
||||||
|
playerPlaying = true;
|
||||||
|
playerStartTick();
|
||||||
|
_updatePlayerIcon();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 新建全曲播放
|
||||||
|
_playerStopAll();
|
||||||
|
_playerMode = 'full';
|
||||||
|
_playerStartSec = 0;
|
||||||
|
_playerEndSec = audioDuration;
|
||||||
|
playerAudio = new Audio('/audio-slicer/preview-range/' + taskId + '?start=0&end=' + audioDuration);
|
||||||
|
playerAudio.addEventListener('ended', function() {
|
||||||
|
playerPlaying = false;
|
||||||
|
playerStopTick();
|
||||||
|
_updatePlayerIcon();
|
||||||
|
});
|
||||||
|
playerAudio.play();
|
||||||
|
playerPlaying = true;
|
||||||
|
playerStartTick();
|
||||||
|
_updatePlayerIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerPause() {
|
||||||
|
if (playerAudio) playerAudio.pause();
|
||||||
|
playerPlaying = false;
|
||||||
|
playerStopTick();
|
||||||
|
_updatePlayerIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _playerStopAll() {
|
||||||
|
playerStopTick();
|
||||||
|
if (playerAudio) {
|
||||||
|
playerAudio.pause();
|
||||||
|
playerAudio.src = '';
|
||||||
|
playerAudio = null;
|
||||||
|
}
|
||||||
|
playerPlaying = false;
|
||||||
|
_updatePlayerIcon();
|
||||||
|
if (currentAudio) { currentAudio.pause(); currentAudio = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function _updatePlayerIcon() {
|
||||||
|
var icon = document.getElementById('playerIcon');
|
||||||
|
if (icon) icon.className = playerPlaying ? 'fas fa-pause' : 'fas fa-play';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放片段(自动模式列表按钮)
|
||||||
|
function playPreviewSeg(index) {
|
||||||
|
if (!taskId || !window._previewRanges || !window._previewRanges[index]) return;
|
||||||
|
_playerStopAll();
|
||||||
|
var r = window._previewRanges[index];
|
||||||
|
_playerMode = 'seg';
|
||||||
|
_playerStartSec = r[0];
|
||||||
|
_playerEndSec = r[1];
|
||||||
|
playerAudio = new Audio('/audio-slicer/preview-range/' + taskId + '?start=' + r[0] + '&end=' + r[1]);
|
||||||
|
playerAudio.addEventListener('ended', function() {
|
||||||
|
playerPlaying = false;
|
||||||
|
playerStopTick();
|
||||||
|
_updatePlayerIcon();
|
||||||
|
});
|
||||||
|
playerAudio.play();
|
||||||
|
playerPlaying = true;
|
||||||
|
playerStartTick();
|
||||||
|
_updatePlayerIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放片段(手动模式列表按钮)
|
||||||
|
function playPreview(start, end) {
|
||||||
|
if (!taskId) return;
|
||||||
|
_playerStopAll();
|
||||||
|
_playerMode = 'seg';
|
||||||
|
_playerStartSec = start;
|
||||||
|
_playerEndSec = end;
|
||||||
|
playerAudio = new Audio('/audio-slicer/preview-range/' + taskId + '?start=' + start + '&end=' + end);
|
||||||
|
playerAudio.addEventListener('ended', function() {
|
||||||
|
playerPlaying = false;
|
||||||
|
playerStopTick();
|
||||||
|
_updatePlayerIcon();
|
||||||
|
});
|
||||||
|
playerAudio.play();
|
||||||
|
playerPlaying = true;
|
||||||
|
playerStartTick();
|
||||||
|
_updatePlayerIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放已切片结果
|
||||||
|
function playSlice(index) {
|
||||||
|
_playerStopAll();
|
||||||
|
var audio = new Audio('/audio-slicer/download/' + taskId + '/' + index);
|
||||||
|
audio.play();
|
||||||
|
currentAudio = audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游标拖拽
|
||||||
|
function _bindCursorDrag(bar) {
|
||||||
|
var cursor = document.getElementById('playCursor');
|
||||||
|
if (!cursor) return;
|
||||||
|
var dragging = false;
|
||||||
|
|
||||||
|
cursor.addEventListener('pointerdown', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
dragging = true;
|
||||||
|
cursor.setPointerCapture(e.pointerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
cursor.addEventListener('pointermove', function(e) {
|
||||||
|
if (!dragging) return;
|
||||||
|
var rect = bar.getBoundingClientRect();
|
||||||
|
var sec = (e.clientX - rect.left) / rect.width * audioDuration;
|
||||||
|
sec = Math.max(0, Math.min(audioDuration, sec));
|
||||||
|
cursor.style.left = (sec / audioDuration * 100).toFixed(2) + '%';
|
||||||
|
var timeEl = document.getElementById('playerTime');
|
||||||
|
if (timeEl) timeEl.textContent = formatTime(sec);
|
||||||
|
// 同步 audio currentTime
|
||||||
|
if (playerAudio) {
|
||||||
|
var rel = sec - _playerStartSec;
|
||||||
|
var dur = _playerEndSec - _playerStartSec;
|
||||||
|
if (rel >= 0 && rel <= dur) {
|
||||||
|
playerAudio.currentTime = rel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cursor.addEventListener('pointerup', function(e) {
|
||||||
|
if (!dragging) return;
|
||||||
|
dragging = false;
|
||||||
|
cursor.releasePointerCapture(e.pointerId);
|
||||||
|
bar._suppressClick = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function loadWaveform() {
|
async function loadWaveform() {
|
||||||
if (!taskId) return;
|
if (!taskId) return;
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user