李豪威 5 months ago
parent 2d85d172fc
commit 055f3daf38

@ -35,8 +35,8 @@ public class AudioSeparationService {
@Value("${app.upload.dir:uploads}")
private String uploadDir;
private static final String OUTPUT_DIR = "F:\\traeprojects\\DeAudio\\project\\separated";
@Value("${app.separated.dir:separated}") // 冒号后是默认值,配置文件没定义时使用
private String outputDir; // 注意变量名改为小写开头(规范)
@Value("${app.temp.dir:temp}")
private String tempDir;
@ -208,7 +208,8 @@ public class AudioSeparationService {
String accompanimentFile = (String) jsonResponse.get("accompaniment_file");
// 构建本地文件路径
String outputDir = Paths.get(OUTPUT_DIR, taskId).toString();
// 使用注入的outputDir变量小写开头而非已删除的OUTPUT_DIR
String outputDirPath = Paths.get(outputDir, taskId).toString();
Path vocalsPath = Paths.get(outputDir, vocalsFile);
Path accompanimentPath = Paths.get(outputDir, accompanimentFile);
@ -556,7 +557,8 @@ public class AudioSeparationService {
private void createDirectories() throws Exception {
Files.createDirectories(Paths.get(uploadDir));
Files.createDirectories(Paths.get(OUTPUT_DIR));
// 使用注入的outputDir变量小写开头创建目录
Files.createDirectories(Paths.get(outputDir));
Files.createDirectories(Paths.get(tempDir));
}

@ -1,12 +1,14 @@
spring.application.name=deaudio
server.port=8081
# 上传文件目录(相对路径,自动在项目根目录创建)
app.upload.dir=uploads
# 修复Python路径 - 使用正斜杠或双反斜杠
app.python.commands=C:\Python39\python.exe
# Python可执行文件路径正确转义推荐正斜杠
app.python.commands=C:/Python39/python.exe
# 或者使用转义的反斜杠
# app.python.commands=C:\\Users\\32708\\AppData\\Local\\Programs\\Python\\Python39\\python.exe
# 音频分离输出目录(可配置,自动创建)
app.separated.dir=separated
# 文件上传大小限制
spring.servlet.multipart.max-file-size=500MB

@ -1,89 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DeAudio - 复制</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
<style>
:root{--bg:#f4f7fb;--card:#ffffff;--primary:#2f80ed;--muted:#6b7280;--radius:12px}
*{box-sizing:border-box}html,body{height:100%}body{font-family:'Inter',Arial,sans-serif;margin:0;background:var(--bg);color:#0f172a;display:flex;flex-direction:column}
.topbar{height:64px;background:linear-gradient(90deg,#fff,#f8fbff);display:flex;align-items:center;justify-content:space-between;padding:0 24px;box-shadow:0 1px 0 rgba(15,23,42,0.04);flex:0 0 64px}
.brand{display:flex;align-items:center;gap:12px;font-weight:700;color:var(--primary)}.brand i{font-size:20px}
.layout{display:flex;gap:16px;padding:18px;flex:1 1 auto;width:100%;margin:0}
.sidebar{width:260px;background:linear-gradient(180deg,#ffffff,#fbfdff);padding:18px;border-radius:var(--radius);box-shadow:0 6px 18px rgba(15,23,42,0.06);height:calc(100vh - 64px);overflow:auto;display:flex;flex-direction:column}
.nav-links{display:flex;flex-direction:column;justify-content:space-around;flex:1;padding:8px 0}
.nav-links a{display:block;padding:14px;border-radius:10px;color:#0f172a;text-decoration:none;margin:6px 0;font-weight:700;font-size:15px}
.nav-links a.active{background:var(--primary);color:#fff}
.nav-item{position:relative}
.nav-item .nav-sub{display:none;margin-left:6px}
.nav-item.open .nav-sub{display:block}
.nav-sub a{display:block;padding:10px 18px;border-radius:8px;margin:4px 0;color:#264d7a;font-weight:600;font-size:13px}
.main{flex:1;min-height:0}
.card{background:var(--card);padding:28px;border-radius:var(--radius);box-shadow:0 8px 30px rgba(15,23,42,0.06);height:100%;display:flex;flex-direction:column}
h2{margin:0 0 14px 0;font-size:22px}
.controls{display:flex;gap:8px;margin-top:12px}
.btn{padding:10px 14px;border-radius:8px;border:none;background:var(--primary);color:#fff}
@media(max-width:880px){.layout{flex-direction:column;padding:16px}.sidebar{width:100%;height:auto}.nav-links{justify-content:flex-start}}
</style>
</head>
<body>
<div class="topbar">
<div class="brand"><i class="fas fa-music"></i><div>DeAudio</div></div>
<div class="small">复制</div>
</div>
<div class="layout">
<aside class="sidebar card">
<h3 style="margin-top:0;margin-bottom:10px">导航</h3>
<div class="nav-links">
<a href="audio-processing.html">音频处理</a>
<a href="results-preview.html">结果预览</a>
<div class="nav-item">
<a href="#">剪辑 ▾</a>
<div class="nav-sub">
<a href="clipping-cut.html">剪切</a>
<a href="clipping-copy.html" class="active">复制</a>
<a href="clipping-delete.html">删除</a>
<a href="clipping-merge.html">合并</a>
</div>
</div>
</div>
<div style="margin-top:18px;border-top:1px dashed #eef6ff;padding-top:14px">
<div style="font-weight:700">欢迎用户A</div>
<div style="margin-top:10px"><button class="btn ghost" style="width:100%" onclick="logout()">退出登录</button></div>
</div>
</aside>
<main class="main">
<div class="card">
<h2>复制Copy</h2>
<div style="margin-top:8px">请选择音频,设置复制区间和插入位置,复制选定片段并插入到指定时间点。</div>
<div style="margin-top:12px">
<label class="btn" for="fileIn">选择音频</label>
<input id="fileIn" type="file" accept="audio/*" style="display:none" />
<div id="name" style="margin-top:8px;color:var(--muted)"></div>
<div style="margin-top:12px"><audio id="player" controls style="width:100%"></audio></div>
<div style="margin-top:12px;display:flex;gap:8px">
<input id="start" type="number" placeholder="开始 秒" step="0.1" />
<input id="end" type="number" placeholder="结束 秒" step="0.1" />
</div>
<div style="margin-top:10px">
<input id="insertAt" type="number" placeholder="插入位置 秒" step="0.1" />
</div>
<div class="controls">
<button id="doCopy" class="btn">执行复制并下载</button>
</div>
<div id="status" style="margin-top:10px;color:#666"></div>
</div>
</div>
</main>
</div>
<script src="plugins/jquery-3.7.1/jquery-3.7.1.js"></script>
<script src="js/site-common.js"></script>
<script src="js/clipping-common.js"></script>
<script src="js/clipping-copy.js"></script>
</body>
</html>

@ -1,118 +0,0 @@
$(function(){
clippingCommon.bindFileInput('#fileIn', '#player', '#name');
// 文件选择时检查格式
$('#fileIn').on('change', function() {
const file = this.files[0];
if (file) {
const fileName = file.name.toLowerCase();
if (!fileName.endsWith('.wav') && !fileName.endsWith('.mp3')) {
clippingCommon.alertStatus('#status','仅支持WAV和MP3格式的音频文件');
this.value = ''; // 清空文件选择
$('#player').attr('src', '');
$('#name').text('');
} else {
clippingCommon.alertStatus('#status','文件格式正确,可以设置复制参数');
}
}
});
$('#doCopy').on('click', function(){
const fileInput = $('#fileIn')[0];
const start = parseFloat($('#start').val());
const end = parseFloat($('#end').val());
const insertAt = parseFloat($('#insertAt').val());
if(!fileInput.files || fileInput.files.length === 0){
clippingCommon.alertStatus('#status','请先选择音频文件');
return;
}
// 检查文件格式
const fileName = fileInput.files[0].name.toLowerCase();
if (!fileName.endsWith('.wav') && !fileName.endsWith('.mp3')) {
clippingCommon.alertStatus('#status','仅支持WAV和MP3格式的音频文件');
return;
}
if(isNaN(start) || isNaN(end) || start>=end){
clippingCommon.alertStatus('#status','请填写正确的开始/结束时间');
return;
}
if(isNaN(insertAt)){
clippingCommon.alertStatus('#status','请填写插入位置');
return;
}
// 检查插入位置是否合理
if (insertAt < 0) {
clippingCommon.alertStatus('#status','插入位置不能小于0');
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('startTime', start);
formData.append('endTime', end);
formData.append('insertTime', insertAt);
clippingCommon.alertStatus('#status','正在执行复制操作...');
$.ajax({
url: '/api/clipping/copy',
type: 'POST',
data: formData,
processData: false,
contentType: false,
xhr: function() {
const xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
return xhr;
},
success: function(data, status, xhr) {
const filename = xhr.getResponseHeader('Content-Disposition')?.split('filename=')[1]?.replace(/"/g, '') || 'copy_audio.wav';
// 根据原始文件类型设置正确的MIME类型
const originalFileName = fileInput.files[0].name.toLowerCase();
const mimeType = originalFileName.endsWith('.mp3') ? 'audio/mpeg' : 'audio/wav';
// 为播放创建独立的Blob URL
const playBlob = new Blob([data], { type: mimeType });
const playUrl = window.URL.createObjectURL(playBlob);
$('#player').attr('src', playUrl);
// 为下载创建独立的Blob URL
const downloadBlob = new Blob([data], { type: mimeType });
const downloadUrl = window.URL.createObjectURL(downloadBlob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(downloadUrl);
document.body.removeChild(a);
clippingCommon.alertStatus('#status','复制完成!文件已下载');
},
error: function(xhr, status, error) {
try {
const response = JSON.parse(xhr.responseText);
let errorMessage = '复制失败:' + response.error;
// 提供更友好的错误提示
if (response.error.includes('MP3') || response.error.includes('解码')) {
errorMessage = 'MP3文件处理失败请确保文件格式正确且未损坏';
} else if (response.error.includes('时间') || response.error.includes('参数')) {
errorMessage = '参数错误:请检查时间设置是否合理';
}
clippingCommon.alertStatus('#status', errorMessage);
} catch (e) {
clippingCommon.alertStatus('#status','复制失败:网络或服务器错误');
}
}
});
});
});

@ -1,80 +0,0 @@
// 导航高亮
document.querySelectorAll('.nav-links a').forEach(link => {
link.addEventListener('click', (e) => {
document.querySelectorAll('.nav-links a').forEach(a => a.classList.remove('nav-link-active'));
e.target.closest('a').classList.add('nav-link-active');
});
});
let isSyncEnabled = true;
const originalAudio = document.getElementById('originalAudio');
const separatedAudio = document.getElementById('separatedAudio');
const syncToggle = document.getElementById('syncToggle');
const syncStatus = document.getElementById('syncStatus');
const originalInfo = document.getElementById('originalInfo');
const separatedInfo = document.getElementById('separatedInfo');
function setupAudioSync() {
function syncPlayback(source, target) {
source.addEventListener('play', () => {
if (isSyncEnabled && target.paused) {
target.play().catch(() => {});
}
});
source.addEventListener('pause', () => {
if (isSyncEnabled) {
target.pause();
}
});
source.addEventListener('seeked', () => {
if (isSyncEnabled && !target.paused) {
target.currentTime = source.currentTime;
}
});
source.addEventListener('timeupdate', () => {
if (isSyncEnabled && !target.paused &&
Math.abs(source.currentTime - target.currentTime) > 0.2) {
target.currentTime = source.currentTime;
}
});
}
syncPlayback(originalAudio, separatedAudio);
syncPlayback(separatedAudio, originalAudio);
}
function updateAudioInfo() {
originalAudio.addEventListener('loadedmetadata', () => {
const duration = originalAudio.duration;
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
originalInfo.textContent = `音频时长: ${minutes}:${seconds.toString().padStart(2, '0')}`;
});
separatedAudio.addEventListener('loadedmetadata', () => {
const duration = separatedAudio.duration;
const minutes = Math.floor(duration / 60);
const seconds = Math.floor(duration % 60);
separatedInfo.textContent = `音频时长: ${minutes}:${seconds.toString().padStart(2, '0')}`;
});
}
syncToggle.addEventListener('click', () => {
isSyncEnabled = !isSyncEnabled;
syncStatus.textContent = isSyncEnabled ? '同步已启用' : '同步已禁用';
syncToggle.textContent = isSyncEnabled ? '🔗 同步播放' : '🔴 暂停同步';
});
document.addEventListener('DOMContentLoaded', () => {
setupAudioSync();
updateAudioInfo();
});
function logout() {
if (confirm('确定退出?')) {
window.location.href = 'index.html';
}
}

@ -1,108 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DeAudio - 结果预览</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
<style>
:root{
--bg: #f4f7fb;
--card: #ffffff;
--primary: #2f80ed;
--muted: #6b7280;
--radius: 12px;
}
*{box-sizing:border-box}
html,body{height:100%;}
body{font-family: 'Inter', Arial, sans-serif;margin:0;background:var(--bg);color:#0f172a;display:flex;flex-direction:column}
.topbar{height:64px;background:linear-gradient(90deg,#fff,#f8fbff);display:flex;align-items:center;justify-content:space-between;padding:0 24px;box-shadow:0 1px 0 rgba(15,23,42,0.04);flex:0 0 64px}
.brand{display:flex;align-items:center;gap:12px;font-weight:700;color:var(--primary)}
.brand i{font-size:20px}
.layout{display:flex;gap:16px;padding:18px;flex:1 1 auto;width:100%;margin:0}
.sidebar{width:260px;background:linear-gradient(180deg,#ffffff,#fbfdff);padding:18px;border-radius:var(--radius);box-shadow:0 6px 18px rgba(15,23,42,0.06);height:calc(100vh - 64px);overflow:auto;display:flex;flex-direction:column}
.nav-links{display:flex;flex-direction:column;justify-content:space-around;flex:1;padding:8px 0}
.nav-links a{display:block;padding:14px;border-radius:10px;color:#0f172a;text-decoration:none;margin:6px 0;font-weight:700;font-size:15px}
.nav-links a.active{background:var(--primary);color:#fff}
.main{flex:1;min-height:0}
.card{background:var(--card);padding:28px;border-radius:var(--radius);box-shadow:0 8px 30px rgba(15,23,42,0.06);height:100%;display:flex;flex-direction:column}
h2{margin:0 0 14px 0;font-size:22px}
.audio-player{margin-bottom:1.5rem;border:1px solid #e0e0e0;border-radius:8px;padding:1rem;background-color:#f8f9fa}
.audio-player h4{margin-top:0;margin-bottom:0.5rem;color:#555}
.audio-player audio{width:100%}
.comparison-controls{display:flex;gap:1rem;margin-bottom:1.5rem;align-items:center;flex-wrap:wrap}
.comparison-controls button{padding:0.75rem 1.2rem;background:var(--primary);color:#fff;border:none;border-radius:8px}
.audio-info{font-size:0.9rem;color:#666;margin-top:0.5rem}
@media(max-width:880px){.layout{flex-direction:column;padding:16px}.sidebar{width:100%;height:auto}.nav-links{justify-content:flex-start}}
</style>
</head>
<body>
<div class="topbar">
<div class="brand"><i class="fas fa-music"></i><div>DeAudio</div></div>
<div class="small">结果预览</div>
</div>
<div class="layout">
<aside class="sidebar card">
<h3 style="margin-top:0;margin-bottom:10px">导航</h3>
<div class="nav-links">
<a href="audio-processing.html">音频处理</a>
<a href="results-preview.html" class="active">结果预览</a>
<div class="nav-item">
<a href="#">剪辑 ▾</a>
<div class="nav-sub">
<a href="clipping-cut.html">剪切</a>
<a href="clipping-copy.html">复制</a>
<a href="clipping-delete.html">删除</a>
<a href="clipping-merge.html">合并</a>
</div>
</div>
</div>
<div style="margin-top:18px;border-top:1px dashed #eef6ff;padding-top:14px">
<div style="font-weight:700">欢迎用户A</div>
<div style="margin-top:10px"><button class="btn ghost" style="width:100%" onclick="logout()">退出登录</button></div>
</div>
</aside>
<main class="main">
<div class="card">
<h2>结果预览</h2>
<div class="comparison-section">
<div class="comparison-controls">
<button id="syncToggle">🔗 同步播放</button>
<span id="syncStatus">同步已启用</span>
</div>
<div class="audio-player">
<h4>🎤 原音频</h4>
<audio id="originalAudio" controls>
<source src="demo-original.mp3" type="audio/mpeg">
您的浏览器不支持音频播放。
</audio>
<div class="audio-info" id="originalInfo">音频时长: 加载中...</div>
</div>
<div class="audio-player">
<h4>🎵 分离音频</h4>
<audio id="separatedAudio" controls>
<source src="demo-separated.mp3" type="audio/mpeg">
您的浏览器不支持音频播放。
</audio>
<div class="audio-info" id="separatedInfo">音频时长: 加载中...</div>
</div>
</div>
</div>
</main>
</div>
<script src="plugins/jquery-3.7.1/jquery-3.7.1.js"></script>
<script src="js/site-common.js"></script>
<script src="js/results-preview.js"></script>
</body>
</html>
Loading…
Cancel
Save