Compare commits
No commits in common. "master" and "main" have entirely different histories.
33
.gitignore
vendored
33
.gitignore
vendored
@ -1,33 +0,0 @@
|
|||||||
# Python
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
*.so
|
|
||||||
*.egg-info/
|
|
||||||
dist/
|
|
||||||
build/
|
|
||||||
*.egg
|
|
||||||
|
|
||||||
# Virtual environments
|
|
||||||
venv/
|
|
||||||
.venv/
|
|
||||||
env/
|
|
||||||
|
|
||||||
# IDE
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
nohup.out
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Temporary files
|
|
||||||
*.tmp
|
|
||||||
*.bak
|
|
||||||
190
README.md
190
README.md
@ -1,192 +1,2 @@
|
|||||||
# demucs-service
|
# demucs-service
|
||||||
|
|
||||||
Vocal/accompaniment separation web service using [Demucs](https://github.com/adefossez/demucs) (htdemucs model).
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This service provides an async API for separating audio files into vocals and accompaniment tracks using Meta's Demucs neural network model. It follows the ahserver + longtasks + Redis pattern.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
- **ahserver**: Async HTTP server framework
|
|
||||||
- **longtasks**: Background task processing via Redis queues
|
|
||||||
- **Redis**: Task queue for separation jobs
|
|
||||||
- **Demucs 4.0.1**: AI-powered source separation model (htdemucs)
|
|
||||||
|
|
||||||
## 模型下载(离线部署)
|
|
||||||
|
|
||||||
Demucs 使用 htdemucs 模型(PyTorch hub 格式),首次运行时自动下载,也可手动预下载。
|
|
||||||
|
|
||||||
### 方法1: PyTorch Hub 自动下载(默认)
|
|
||||||
|
|
||||||
服务首次运行时会通过 `torch.hub.load("facebookresearch/demucs", "htdemucs")` 自动下载模型。
|
|
||||||
|
|
||||||
**下载位置**: `~/.cache/torch/hub/checkpoints/`
|
|
||||||
**下载大小**: ~80MB
|
|
||||||
|
|
||||||
### 方法2: 手动预下载
|
|
||||||
|
|
||||||
如果部署环境无法访问外网,可以先在有网络的机器上下载,再拷贝:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 在有网络的机器上运行 Python
|
|
||||||
python3 << "PYTHON"
|
|
||||||
import torch
|
|
||||||
model = torch.hub.load("facebookresearch/demucs", "htdemucs", pretrained=True)
|
|
||||||
print("Model downloaded to:", torch.hub.get_dir())
|
|
||||||
PYTHON
|
|
||||||
|
|
||||||
# 找到模型文件
|
|
||||||
ls ~/.cache/torch/hub/checkpoints/
|
|
||||||
# 应该看到 htdemucs-*.pt 或类似文件
|
|
||||||
|
|
||||||
# 拷贝到部署服务器
|
|
||||||
scp ~/.cache/torch/hub/checkpoints/htdemucs*.pt ymq@opencomputing.net:~/.cache/torch/hub/checkpoints/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方法3: 直接下载模型文件
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 创建缓存目录
|
|
||||||
mkdir -p ~/.cache/torch/hub/checkpoints/
|
|
||||||
|
|
||||||
# 下载 htdemucs 模型
|
|
||||||
wget -O ~/.cache/torch/hub/checkpoints/htdemucs_v4.pt \
|
|
||||||
https://dl.fbaipublicfiles.com/demucs/v4.0/htdemucs.pth
|
|
||||||
```
|
|
||||||
|
|
||||||
**下载大小**: ~80MB
|
|
||||||
**下载时间**: 约5-15秒
|
|
||||||
|
|
||||||
### 验证下载
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启动服务后检查日志
|
|
||||||
tail -f /data/ymq/demucs-service/nohup.out | grep -i "model"
|
|
||||||
# 应该看到 "Model loaded" 而不是 "Downloading"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 模型来源
|
|
||||||
|
|
||||||
- **GitHub**: https://github.com/facebookresearch/demucs
|
|
||||||
- **PyTorch Hub**: facebookresearch/demucs
|
|
||||||
- **License**: MIT
|
|
||||||
- **Paper**: Hybrid Spectrogram and Waveform Source Separation
|
|
||||||
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### Submit Separation Task
|
|
||||||
|
|
||||||
Send a JSON payload to the longtask endpoint:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"task_type": "separate",
|
|
||||||
"audio_path": "/path/to/audio.wav",
|
|
||||||
"output_dir": "/tmp/demucs_custom_output" // optional
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `audio_path` (required): Absolute path to the input audio file
|
|
||||||
- `output_dir` (optional): Output directory. Default: `/tmp/demucs_{task_id}`
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"vocals_path": "/tmp/demucs_123/htdemucs/audio/vocals.wav",
|
|
||||||
"no_vocals_path": "/tmp/demucs_123/htdemucs/audio/no_vocals.wav",
|
|
||||||
"duration": 12.34,
|
|
||||||
"output_dir": "/tmp/demucs_123",
|
|
||||||
"model": "htdemucs"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Health Check
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /app/health.dspy
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
```json
|
|
||||||
{"status":"ok","service":"demucs-service","model":"htdemucs"}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Config file: `conf/config.json`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"port": 9083,
|
|
||||||
"queue": "demucs",
|
|
||||||
"filesroot": "/tmp/demucs-outputs",
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"debug": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `DEMUCS_GPU_ID` | `5` | GPU device ID for CUDA |
|
|
||||||
| `CUDA_VISIBLE_DEVICES` | `5` | CUDA device visibility |
|
|
||||||
| `PYTHONPATH` | `/data/ymq/demucs-service` | Python module path |
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Python venv at `/data/ymq/demucs_venv` with demucs 4.0.1 and torchcodec
|
|
||||||
- Redis server running on `127.0.0.1:6379`
|
|
||||||
- GPU with CUDA support
|
|
||||||
|
|
||||||
### Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash start.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stop
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash stop.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tail -f nohup.out
|
|
||||||
```
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
demucs-service/
|
|
||||||
├── ah.py # Main entry point
|
|
||||||
├── workers/
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ └── separate.py # Separation worker
|
|
||||||
├── conf/
|
|
||||||
│ └── config.json # Service configuration
|
|
||||||
├── app/
|
|
||||||
│ └── health.dspy # Health check endpoint
|
|
||||||
├── start.sh # Start script
|
|
||||||
├── stop.sh # Stop script
|
|
||||||
└── README.md # This file
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
Demucs outputs to: `{output_dir}/htdemucs/{basename}/`
|
|
||||||
- `vocals.wav` - Isolated vocal track
|
|
||||||
- `no_vocals.wav` - Accompaniment (everything except vocals)
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- **GPU OOM**: The htdemucs model requires significant VRAM. Ensure the assigned GPU has enough memory.
|
|
||||||
- **Process timeout**: Long audio files may exceed the stuck_seconds timeout (default: 600s). Increase if needed.
|
|
||||||
- **Missing output files**: Check nohup.out for demucs stderr output to diagnose issues.
|
|
||||||
|
|||||||
34
ah.py
34
ah.py
@ -1,34 +0,0 @@
|
|||||||
import os
|
|
||||||
from ahserver.webapp import webapp
|
|
||||||
from ahserver.serverenv import ServerEnv
|
|
||||||
from ahserver.configuredServer import add_startup
|
|
||||||
from longtasks.longtasks import LongTasks, schedule_once
|
|
||||||
from appPublic.log import debug
|
|
||||||
|
|
||||||
class DemucsTasks(LongTasks):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.gpu_id = int(os.environ.get('DEMUCS_GPU_ID', '5'))
|
|
||||||
async def process_task(self, payload, workid=None):
|
|
||||||
import json
|
|
||||||
if isinstance(payload, str): payload = json.loads(payload)
|
|
||||||
task_type = payload.get('task_type', '')
|
|
||||||
if task_type == 'separate':
|
|
||||||
from workers.separate import run_separate
|
|
||||||
return await run_separate(self, payload)
|
|
||||||
raise ValueError(f'Unknown task_type: {task_type}')
|
|
||||||
|
|
||||||
async def on_app_built(app):
|
|
||||||
env = ServerEnv()
|
|
||||||
lt = env.longtasks
|
|
||||||
if lt:
|
|
||||||
schedule_once(0.1, lt.run)
|
|
||||||
debug(f'Demucs longtasks worker started, GPU: {lt.gpu_id}')
|
|
||||||
|
|
||||||
def init():
|
|
||||||
env = ServerEnv()
|
|
||||||
env.longtasks = DemucsTasks('redis://127.0.0.1:6379', 'demucs', worker_cnt=1, stuck_seconds=600, max_age_hours=24)
|
|
||||||
add_startup(on_app_built)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
webapp(init)
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
# GET /api/status - Demucs服务状态
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import json
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'service': 'demucs-vocal-separation',
|
|
||||||
'model': 'htdemucs',
|
|
||||||
'gpu_id': 5,
|
|
||||||
'gpus': []
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
out = subprocess.check_output(
|
|
||||||
['nvidia-smi', '--query-gpu=index,utilization.gpu,memory.used,memory.total',
|
|
||||||
'--format=csv,noheader,nounits'],
|
|
||||||
timeout=5
|
|
||||||
).decode().strip()
|
|
||||||
for line in out.split('\n'):
|
|
||||||
parts = [p.strip() for p in line.split(',')]
|
|
||||||
result['gpus'].append({
|
|
||||||
'id': int(parts[0]),
|
|
||||||
'util': int(parts[1]),
|
|
||||||
'mem_used': int(parts[2]),
|
|
||||||
'mem_total': int(parts[3])
|
|
||||||
})
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return json.dumps(result)
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
# POST /api/submit - 提交Demucs人声分离任务
|
|
||||||
|
|
||||||
import json
|
|
||||||
import uuid
|
|
||||||
from ahserver.serverenv import ServerEnv
|
|
||||||
|
|
||||||
method = request.method
|
|
||||||
|
|
||||||
if method == 'POST':
|
|
||||||
audio_path = params_kw.get('audio_path', '')
|
|
||||||
if not audio_path:
|
|
||||||
return json.dumps({'error': 'audio_path is required'}, ensure_ascii=False)
|
|
||||||
|
|
||||||
task_id = params_kw.get('task_id', str(uuid.uuid4()).replace("-", "")[:12])
|
|
||||||
model = params_kw.get('model', 'htdemucs')
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
'task_type': 'separate',
|
|
||||||
'task_id': task_id,
|
|
||||||
'audio_path': audio_path,
|
|
||||||
'model': model
|
|
||||||
}
|
|
||||||
|
|
||||||
env = ServerEnv()
|
|
||||||
longtasks = env.longtasks
|
|
||||||
if longtasks is None:
|
|
||||||
return json.dumps({'error': 'service not ready'}, ensure_ascii=False)
|
|
||||||
|
|
||||||
result = await longtasks.submit_task(payload)
|
|
||||||
real_task_id = result.get('task_id', str(result)) if isinstance(result, dict) else str(result)
|
|
||||||
|
|
||||||
return json.dumps({
|
|
||||||
'task_id': real_task_id,
|
|
||||||
'status': 'queued',
|
|
||||||
'audio_path': audio_path,
|
|
||||||
'model': model,
|
|
||||||
'message': 'task submitted',
|
|
||||||
'check_url': f'/api/task?task_id={real_task_id}'
|
|
||||||
}, ensure_ascii=False)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return json.dumps({
|
|
||||||
'usage': 'POST with JSON body',
|
|
||||||
'params': {
|
|
||||||
'audio_path': 'string (required, server path to audio file)',
|
|
||||||
'model': 'string (default htdemucs, options: htdemucs/htdemucs_ft/mdx_extra_q)',
|
|
||||||
'task_id': 'string (optional, auto-generated)',
|
|
||||||
}
|
|
||||||
}, ensure_ascii=False)
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
# GET /api/task?task_id=xxx - 查询任务状态
|
|
||||||
|
|
||||||
import json
|
|
||||||
from ahserver.serverenv import ServerEnv
|
|
||||||
|
|
||||||
task_id = params_kw.get('task_id', '')
|
|
||||||
if not task_id:
|
|
||||||
return json.dumps({'error': 'task_id is required'}, ensure_ascii=False)
|
|
||||||
|
|
||||||
env = ServerEnv()
|
|
||||||
longtasks = env.longtasks
|
|
||||||
if longtasks is None:
|
|
||||||
return json.dumps({'error': 'service not ready'}, ensure_ascii=False)
|
|
||||||
|
|
||||||
status = await longtasks.get_status(task_id)
|
|
||||||
return json.dumps(status)
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import json
|
|
||||||
result = {"status": "ok", "service": "$svc"}
|
|
||||||
print(json.dumps(result))
|
|
||||||
150
build.sh
150
build.sh
@ -1,150 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# 一键部署脚本模板
|
|
||||||
# 用法: ./build.sh [deploy|update|stop|status]
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SERVICE_NAME="demucs-service"
|
|
||||||
GIT_REPO="git@git.opencomputing.cn:yumoqing/demucs-service.git"
|
|
||||||
SERVICE_PORT=9083
|
|
||||||
DEPLOY_DIR="/data/ymq/$SERVICE_NAME"
|
|
||||||
VENV_PATH="/data/ymq/wan22-service/py3"
|
|
||||||
GPU_ID="5"
|
|
||||||
|
|
||||||
# 颜色输出
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
||||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
||||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
||||||
|
|
||||||
check_deps() {
|
|
||||||
command -v git >/dev/null || { log_error "git not found"; exit 1; }
|
|
||||||
[ -f "$VENV_PATH/bin/python" ] || { log_error "Python venv not found: $VENV_PATH"; exit 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy() {
|
|
||||||
log_info "Deploying $SERVICE_NAME..."
|
|
||||||
|
|
||||||
# 检查依赖
|
|
||||||
check_deps
|
|
||||||
|
|
||||||
# 克隆或更新代码
|
|
||||||
if [ -d "$DEPLOY_DIR/.git" ]; then
|
|
||||||
log_info "Updating existing deployment..."
|
|
||||||
cd "$DEPLOY_DIR"
|
|
||||||
git fetch origin
|
|
||||||
git reset --hard origin/master
|
|
||||||
else
|
|
||||||
log_info "Cloning repository..."
|
|
||||||
cd /data/ymq
|
|
||||||
git clone "$GIT_REPO" "$SERVICE_NAME"
|
|
||||||
cd "$DEPLOY_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 创建必要目录
|
|
||||||
mkdir -p "$DEPLOY_DIR/app/api/status"
|
|
||||||
mkdir -p "$DEPLOY_DIR/app/api/submit"
|
|
||||||
mkdir -p "$DEPLOY_DIR/app/api/task"
|
|
||||||
|
|
||||||
# 设置权限
|
|
||||||
chmod +x start.sh stop.sh 2>/dev/null || true
|
|
||||||
|
|
||||||
# 启动服务
|
|
||||||
start_service
|
|
||||||
}
|
|
||||||
|
|
||||||
start_service() {
|
|
||||||
log_info "Starting $SERVICE_NAME on port $SERVICE_PORT..."
|
|
||||||
|
|
||||||
# 停止旧进程
|
|
||||||
if [ -f stop.sh ]; then
|
|
||||||
bash stop.sh 2>/dev/null || true
|
|
||||||
sleep 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 启动新进程
|
|
||||||
bash start.sh
|
|
||||||
|
|
||||||
# 等待启动
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# 验证
|
|
||||||
if ss -tlnp | grep -q ":$SERVICE_PORT "; then
|
|
||||||
log_info "✓ Service started successfully"
|
|
||||||
verify_api
|
|
||||||
else
|
|
||||||
log_error "✗ Service failed to start"
|
|
||||||
log_error "Check logs: $DEPLOY_DIR/nohup.out"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
verify_api() {
|
|
||||||
log_info "Verifying API endpoints..."
|
|
||||||
|
|
||||||
# 检查 status endpoint
|
|
||||||
if curl -s "http://127.0.0.1:$SERVICE_PORT/api/status" | grep -q "service"; then
|
|
||||||
log_info "✓ /api/status OK"
|
|
||||||
else
|
|
||||||
log_warn "✗ /api/status failed"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
stop_service() {
|
|
||||||
log_info "Stopping $SERVICE_NAME..."
|
|
||||||
if [ -f "$DEPLOY_DIR/stop.sh" ]; then
|
|
||||||
cd "$DEPLOY_DIR"
|
|
||||||
bash stop.sh
|
|
||||||
log_info "✓ Service stopped"
|
|
||||||
else
|
|
||||||
log_warn "stop.sh not found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
show_status() {
|
|
||||||
echo "=== $SERVICE_NAME Status ==="
|
|
||||||
echo "Port: $SERVICE_PORT"
|
|
||||||
echo "Deploy Dir: $DEPLOY_DIR"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 检查进程
|
|
||||||
if ss -tlnp | grep -q ":$SERVICE_PORT "; then
|
|
||||||
echo -e "Status: ${GREEN}RUNNING${NC}"
|
|
||||||
PID=$(ss -tlnp | grep ":$SERVICE_PORT " | grep -oP 'pid=\K[0-9]+')
|
|
||||||
echo "PID: $PID"
|
|
||||||
else
|
|
||||||
echo -e "Status: ${RED}STOPPED${NC}"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 检查 API
|
|
||||||
echo "API Endpoints:"
|
|
||||||
curl -s "http://127.0.0.1:$SERVICE_PORT/api/status" 2>/dev/null | python3 -m json.tool 2>/dev/null || echo " (not responding)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主入口
|
|
||||||
case "${1:-deploy}" in
|
|
||||||
deploy|install)
|
|
||||||
deploy
|
|
||||||
;;
|
|
||||||
update|upgrade)
|
|
||||||
deploy
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop_service
|
|
||||||
;;
|
|
||||||
start)
|
|
||||||
start_service
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
show_status
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 {deploy|update|stop|start|status}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"password_key": "DemucsService2026Key",
|
|
||||||
"databases": {},
|
|
||||||
"session_redis": {
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": 6379,
|
|
||||||
"db": 1
|
|
||||||
},
|
|
||||||
"website": {
|
|
||||||
"paths": [
|
|
||||||
[
|
|
||||||
"$[workdir]$/app",
|
|
||||||
""
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"port": 9083,
|
|
||||||
"coding": "utf-8",
|
|
||||||
"indexes": [
|
|
||||||
"index.html",
|
|
||||||
"index.dspy"
|
|
||||||
],
|
|
||||||
"processors": [
|
|
||||||
[
|
|
||||||
".dspy",
|
|
||||||
"dspy"
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"startswiths": [
|
|
||||||
{
|
|
||||||
"leading": "/idfile",
|
|
||||||
"registerfunction": "idfile"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hot_reload": false,
|
|
||||||
"filesroot": "/tmp/demucs-outputs"
|
|
||||||
}
|
|
||||||
7
start.sh
7
start.sh
@ -1,7 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
cd /data/ymq/demucs-service
|
|
||||||
export DEMUCS_GPU_ID=5
|
|
||||||
export CUDA_VISIBLE_DEVICES=5
|
|
||||||
export PYTHONPATH=/data/ymq/demucs-service
|
|
||||||
nohup /data/ymq/wan22-service/py3/bin/python ah.py > nohup.out 2>&1 &
|
|
||||||
echo "demucs-service started, PID: $!, GPU: $DEMUCS_GPU_ID"
|
|
||||||
26
stop.sh
26
stop.sh
@ -1,26 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
echo "Stopping demucs-service..."
|
|
||||||
|
|
||||||
# Find and kill processes running ah.py for demucs-service
|
|
||||||
PIDS=$(ps aux | grep '[d]emucs.*ah.py' | awk '{print $2}')
|
|
||||||
|
|
||||||
if [ -z "$PIDS" ]; then
|
|
||||||
echo "No demucs-service processes found."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
for pid in $PIDS; do
|
|
||||||
echo "Killing PID: $pid"
|
|
||||||
kill "$pid" 2>/dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
# Wait briefly then force kill if still running
|
|
||||||
sleep 2
|
|
||||||
for pid in $PIDS; do
|
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
echo "Force killing PID: $pid"
|
|
||||||
kill -9 "$pid" 2>/dev/null
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "demucs-service stopped."
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
import os
|
|
||||||
import json
|
|
||||||
import asyncio
|
|
||||||
import time
|
|
||||||
from appPublic.log import debug, error
|
|
||||||
|
|
||||||
async def run_separate(task_obj, payload):
|
|
||||||
"""
|
|
||||||
Run demucs vocal/accompaniment separation.
|
|
||||||
|
|
||||||
payload:
|
|
||||||
audio_path (str, required): Path to input audio file
|
|
||||||
output_dir (str, optional): Output directory, default /tmp/demucs_{task_id}
|
|
||||||
"""
|
|
||||||
audio_path = payload.get('audio_path')
|
|
||||||
if not audio_path:
|
|
||||||
raise ValueError('audio_path is required')
|
|
||||||
|
|
||||||
if not os.path.isfile(audio_path):
|
|
||||||
raise FileNotFoundError(f'Audio file not found: {audio_path}')
|
|
||||||
|
|
||||||
task_id = payload.get('task_id', str(int(time.time())))
|
|
||||||
output_dir = payload.get('output_dir', f'/tmp/demucs_{task_id}')
|
|
||||||
|
|
||||||
gpu_id = task_obj.gpu_id
|
|
||||||
basename = os.path.splitext(os.path.basename(audio_path))[0]
|
|
||||||
|
|
||||||
# Expected output paths from demucs
|
|
||||||
result_dir = os.path.join(output_dir, 'htdemucs', basename)
|
|
||||||
vocals_path = os.path.join(result_dir, 'vocals.wav')
|
|
||||||
no_vocals_path = os.path.join(result_dir, 'no_vocals.wav')
|
|
||||||
|
|
||||||
# Build the command
|
|
||||||
env = os.environ.copy()
|
|
||||||
env['CUDA_VISIBLE_DEVICES'] = str(gpu_id)
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
'/data/ymq/demucs_venv/bin/python', '-m', 'demucs',
|
|
||||||
'--two-stems', 'vocals',
|
|
||||||
audio_path,
|
|
||||||
'-o', output_dir
|
|
||||||
]
|
|
||||||
|
|
||||||
debug(f'[demucs] Running separation: audio={audio_path}, output={output_dir}, gpu={gpu_id}')
|
|
||||||
debug(f'[demucs] Command: {" ".join(cmd)}')
|
|
||||||
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
proc = await asyncio.create_subprocess_exec(
|
|
||||||
*cmd,
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.PIPE,
|
|
||||||
env=env
|
|
||||||
)
|
|
||||||
|
|
||||||
stdout, stderr = await proc.communicate()
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
|
|
||||||
if proc.returncode != 0:
|
|
||||||
stderr_text = stderr.decode('utf-8', errors='replace')
|
|
||||||
stdout_text = stdout.decode('utf-8', errors='replace')
|
|
||||||
error(f'[demucs] Process failed (rc={proc.returncode})')
|
|
||||||
error(f'[demucs] stdout: {stdout_text[-2000:]}')
|
|
||||||
error(f'[demucs] stderr: {stderr_text[-2000:]}')
|
|
||||||
raise RuntimeError(
|
|
||||||
f'Demucs separation failed (rc={proc.returncode}): {stderr_text[-500:]}'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify output files exist
|
|
||||||
if not os.path.isfile(vocals_path):
|
|
||||||
raise FileNotFoundError(f'Expected vocals output not found: {vocals_path}')
|
|
||||||
if not os.path.isfile(no_vocals_path):
|
|
||||||
raise FileNotFoundError(f'Expected no_vocals output not found: {no_vocals_path}')
|
|
||||||
|
|
||||||
vocals_size = os.path.getsize(vocals_path)
|
|
||||||
no_vocals_size = os.path.getsize(no_vocals_path)
|
|
||||||
|
|
||||||
debug(f'[demucs] Separation complete in {elapsed:.1f}s')
|
|
||||||
debug(f'[demucs] vocals.wav: {vocals_size} bytes, no_vocals.wav: {no_vocals_size} bytes')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'vocals_path': vocals_path,
|
|
||||||
'no_vocals_path': no_vocals_path,
|
|
||||||
'duration': round(elapsed, 2),
|
|
||||||
'output_dir': output_dir,
|
|
||||||
'model': 'htdemucs'
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user