Compare commits

...

No commits in common. "main" and "master" have entirely different histories.
main ... master

6 changed files with 112 additions and 2 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
__pycache__/
*.pyc
*.pyo
logs/
*.log
nohup.out
py3/
*.egg-info/
*.pid

View File

@ -1,2 +0,0 @@
# songrate-service

59
ah.py Normal file
View File

@ -0,0 +1,59 @@
# -*- coding:utf-8 -*-
"""songrate standalone service - 音乐评估 GPU 服务 (端口 8900)"""
import json
import os
import sys
import tempfile
import asyncio
from ahserver.webapp import webapp
from ahserver.serverenv import ServerEnv
from appPublic.registerfunction import RegisterFunction
from appPublic.log import debug
# Add songrate package to path
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__))))
from songrate.evaluator import evaluate_song
async def do_evaluate(request, *args, **kw):
"""POST /api/evaluate - 评估歌曲"""
env = request._run_ns
pkw = env.params_kw
scene = getattr(pkw, 'scene', 'pop') or 'pop'
filepath = getattr(pkw, 'filepath', '') or ''
audio_path = getattr(pkw, 'audio_path', '') or ''
if not filepath:
filepath = audio_path
if not filepath:
return json.dumps({"error": "missing filepath or audio_path"}, ensure_ascii=False)
# If it's a relative/web path, resolve to local
if not os.path.isabs(filepath):
from ahserver.filestorage import FileStorage
fs = FileStorage()
filepath = fs.realPath(filepath)
if not os.path.exists(filepath):
return json.dumps({"error": f"file not found: {filepath}"}, ensure_ascii=False)
# Run evaluation in thread pool (GPU bound)
from concurrent.futures import ThreadPoolExecutor
loop = asyncio.get_event_loop()
with ThreadPoolExecutor(max_workers=1) as pool:
result = await loop.run_in_executor(pool, evaluate_song, filepath, scene)
return json.dumps(result, ensure_ascii=False)
def init():
rf = RegisterFunction()
rf.register('evaluate', do_evaluate)
debug('songrate service initialized')
if __name__ == '__main__':
webapp(init)

15
conf/config.json Normal file
View File

@ -0,0 +1,15 @@
{
"website":{
"paths":[["$[workdir]$/wwwroot",""]],
"client_max_size":500000000,
"host":"0.0.0.0",
"port":8900,
"coding":"utf-8",
"indexes":["index.html","index.dspy"],
"processors":[[".dspy","dspy"]],
"startswiths":[
{"leading":"/api/evaluate","registerfunction":"evaluate"},
{"leading":"/idfile","registerfunction":"idfile"}
]
}
}

1
songrate Symbolic link
View File

@ -0,0 +1 @@
/data/ymq/songrate/songrate

28
songrate_audio_patch.py Normal file
View File

@ -0,0 +1,28 @@
"""Patch songrate's load_audio to use soundfile instead of torchaudio"""
import soundfile as sf
import numpy as np
import torch
def load_audio_patched(filepath, sr=22050):
"""使用 soundfile 加载音频,避免 torchcodec 依赖"""
# 读取音频
data, orig_sr = sf.read(filepath)
# 转单声道
if len(data.shape) > 1:
data = np.mean(data, axis=1)
# 重采样 (使用 librosa)
if orig_sr != sr:
import librosa
data = librosa.resample(data, orig_sr=orig_sr, target_sr=sr)
# 转 tensor 并移到 GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
waveform = torch.from_numpy(data).float().to(device)
return waveform, sr
# Monkey patch
import songrate.analyzers
songrate.analyzers.load_audio = load_audio_patched