File size: 6,904 Bytes
1beec06 a415bff 81326b0 a415bff 81326b0 db02ed6 81326b0 db02ed6 81326b0 1beec06 81326b0 c65d8c1 a415bff 1beec06 a415bff 06afa15 a415bff 7ea2e38 a415bff 06afa15 1beec06 a415bff 06afa15 1beec06 a415bff 1beec06 a415bff 1beec06 81326b0 2f92718 81326b0 1beec06 2f92718 81326b0 a415bff c65d8c1 2f92718 c65d8c1 a415bff 1beec06 db02ed6 bc8ec9d 2f92718 f308b21 bc8ec9d 2f92718 f308b21 2f92718 f308b21 bc8ec9d f308b21 2f92718 f308b21 c65d8c1 f308b21 2f92718 f308b21 bc8ec9d f308b21 2f92718 bc8ec9d 81326b0 1beec06 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | import sys, os, re, torch, torchaudio, numpy as np
from typing import Dict, Any
class EndpointHandler:
def __init__(self, path=""):
if path and path not in sys.path:
sys.path.insert(0, path)
from configuration_moss_audio import MossAudioConfig
from processing_moss_audio import MossAudioProcessor
from modeling_moss_audio import MossAudioModel
print(f"[MOSS-8B] Loading (no time markers)...")
self.config = MossAudioConfig.from_pretrained(path)
self.processor = MossAudioProcessor.from_pretrained(
path, trust_remote_code=True, enable_time_marker=False
)
self.model = MossAudioModel.from_pretrained(
path, config=self.config, torch_dtype=torch.bfloat16,
device_map="auto", trust_remote_code=True,
)
self.model.eval()
print(f"[MOSS-8B] Loaded.")
self.sr = self.processor.config.mel_sr
def _load_audio(self, audio_input):
if isinstance(audio_input, str) and audio_input.startswith(("http://", "https://")):
import requests, tempfile
resp = requests.get(audio_input, timeout=120)
resp.raise_for_status()
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
tmp.write(resp.content)
tmp_path = tmp.name
waveform, sr = torchaudio.load(tmp_path)
os.unlink(tmp_path)
elif isinstance(audio_input, str):
waveform, sr = torchaudio.load(audio_input)
else:
raise ValueError("audio must be URL or file path")
if waveform.shape[0] > 1:
waveform = waveform.mean(dim=0, keepdim=True)
if sr != self.sr:
waveform = torchaudio.transforms.Resample(sr, self.sr)(waveform)
return waveform
def _transcribe(self, raw_audio, prompt, gen_kwargs):
inputs = self.processor(text=prompt, audios=[raw_audio], return_tensors="pt")
inputs = inputs.to(self.model.device)
if inputs.get("audio_data") is not None:
inputs["audio_data"] = inputs["audio_data"].to(self.model.dtype)
inputs["audio_input_mask"] = inputs["input_ids"] == self.processor.audio_token_id
ids = self.model.generate(**inputs, **gen_kwargs)
text = self.processor.decode(ids[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True).strip()
text = re.sub(r'<think>.*?</think>\s*\n?\s*', '', text, flags=re.DOTALL).strip()
return text
@torch.no_grad()
def __call__(self, data):
inputs = data.get("inputs", data)
try:
if isinstance(inputs, str):
audio_input, prompt = inputs, "转写这段日语音频,标注说话人。格式:说话人:内容"
elif isinstance(inputs, dict):
audio_input = inputs.get("audio", "")
prompt = inputs.get("prompt", inputs.get("text", "转写这段日语音频,标注说话人。格式:说话人:内容"))
else:
return {"error": f"Invalid input type: {type(inputs)}"}
gen_kwargs = dict(
max_new_tokens=int(data.get("max_new_tokens", inputs.get("max_new_tokens", 384))),
num_beams=int(data.get("num_beams", inputs.get("num_beams", 2))),
use_cache=True,
do_sample=bool(data.get("do_sample", inputs.get("do_sample", False))),
temperature=float(data.get("temperature", inputs.get("temperature", 0.5))),
top_p=float(data.get("top_p", inputs.get("top_p", 0.9))),
top_k=int(data.get("top_k", inputs.get("top_k", 50))),
repetition_penalty=float(data.get("repetition_penalty", inputs.get("repetition_penalty", 1.15))),
no_repeat_ngram_size=int(data.get("no_repeat_ngram_size", inputs.get("no_repeat_ngram_size", 3))),
length_penalty=float(data.get("length_penalty", inputs.get("length_penalty", 0.9))),
early_stopping=True,
)
waveform = self._load_audio(audio_input)
total_sec = waveform.shape[1] / self.sr
print(f"[MOSS-8B] Audio: {total_sec:.1f}s (no time markers)")
# 2-minute (120s) chunks with 10s overlap
chunk_sec = 120
overlap_sec = 10
step_samples = int((chunk_sec - overlap_sec) * self.sr)
chunk_samples = int(chunk_sec * self.sr)
n_frames = waveform.shape[1]
chunks_list = []
pos = 0
while pos < n_frames:
end = min(pos + chunk_samples, n_frames)
chunks_list.append((pos, end))
pos += step_samples
if end >= n_frames:
break
segments = []
prev_speakers = ""
for idx, (st, en) in enumerate(chunks_list):
chunk = waveform[:, st:en]
if chunk.abs().max() < 0.005:
text = ""
else:
ctx_prompt = prompt
if prev_speakers and idx > 0:
ctx_prompt = prompt + f" 前段说话人:{prev_speakers}"
raw = chunk.squeeze(0).numpy().astype(np.float32)
text = self._transcribe(raw, ctx_prompt, gen_kwargs)
speakers = set(re.findall(r'([\u3040-\u30ff\u4e00-\u9fff]+)[::]', text))
if speakers:
prev_speakers = "、".join(sorted(speakers)[:4])
if torch.cuda.is_available():
torch.cuda.empty_cache()
start_t = st / self.sr
end_t = en / self.sr
segments.append({"start": round(start_t, 2), "end": round(end_t, 2), "text": text})
print(f"[Chunk {idx+1}/{len(chunks_list)}] ({len(text)}c) {text[:80]}")
full_text = " ".join(s["text"] for s in segments if s["text"])
srt_lines = []
idx_n = 1
for seg in segments:
t = seg["text"].strip()
if not t:
continue
s, e = seg["start"], seg["end"]
srt_lines.append(str(idx_n))
srt_lines.append(f"{int(s//3600):02d}:{int(s%3600//60):02d}:{s%60:06.3f} --> {int(e//3600):02d}:{int(e%3600//60):02d}:{e%60:06.3f}")
srt_lines.append(t)
srt_lines.append("")
idx_n += 1
return {
"transcription": full_text,
"segments": segments,
"srt": "\n".join(srt_lines),
"num_chunks": len(chunks_list),
}
except Exception as e:
import traceback
return {"error": str(e), "traceback": traceback.format_exc()}
|