|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import logging |
|
import subprocess |
|
from dataclasses import dataclass |
|
from datetime import timedelta |
|
from typing import Optional |
|
|
|
import numpy as np |
|
import sherpa_onnx |
|
|
|
from model import sample_rate |
|
|
|
|
|
@dataclass |
|
class Segment: |
|
start: float |
|
duration: float |
|
text: str = "" |
|
|
|
@property |
|
def end(self): |
|
return self.start + self.duration |
|
|
|
def __str__(self): |
|
s = f"0{timedelta(seconds=self.start)}"[:-3] |
|
s += " --> " |
|
s += f"0{timedelta(seconds=self.end)}"[:-3] |
|
s = s.replace(".", ",") |
|
s += "\n" |
|
s += self.text |
|
return s |
|
|
|
|
|
def decode( |
|
recognizer: sherpa_onnx.OfflineRecognizer, |
|
vad: sherpa_onnx.VoiceActivityDetector, |
|
punct: Optional[sherpa_onnx.OfflinePunctuation], |
|
filename: str, |
|
) -> str: |
|
ffmpeg_cmd = [ |
|
"ffmpeg", |
|
"-i", |
|
filename, |
|
"-f", |
|
"s16le", |
|
"-acodec", |
|
"pcm_s16le", |
|
"-ac", |
|
"1", |
|
"-ar", |
|
str(sample_rate), |
|
"-", |
|
] |
|
|
|
process = subprocess.Popen( |
|
ffmpeg_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL |
|
) |
|
|
|
frames_per_read = int(sample_rate * 100) |
|
|
|
window_size = 512 |
|
|
|
buffer = [] |
|
|
|
segment_list = [] |
|
|
|
logging.info("Started!") |
|
|
|
all_text = [] |
|
|
|
is_last = False |
|
|
|
while True: |
|
|
|
data = process.stdout.read(frames_per_read * 2) |
|
if not data: |
|
if is_last: |
|
break |
|
is_last = True |
|
data = np.zeros(sample_rate, dtype=np.int16) |
|
|
|
samples = np.frombuffer(data, dtype=np.int16) |
|
samples = samples.astype(np.float32) / 32768 |
|
|
|
buffer = np.concatenate([buffer, samples]) |
|
while len(buffer) > window_size: |
|
vad.accept_waveform(buffer[:window_size]) |
|
buffer = buffer[window_size:] |
|
|
|
streams = [] |
|
segments = [] |
|
while not vad.empty(): |
|
segment = Segment( |
|
start=vad.front.start / sample_rate, |
|
duration=len(vad.front.samples) / sample_rate, |
|
) |
|
segments.append(segment) |
|
|
|
stream = recognizer.create_stream() |
|
stream.accept_waveform(sample_rate, vad.front.samples) |
|
|
|
streams.append(stream) |
|
|
|
vad.pop() |
|
|
|
for s in streams: |
|
recognizer.decode_stream(s) |
|
|
|
for seg, stream in zip(segments, streams): |
|
seg.text = stream.result.text.strip() |
|
if len(seg.text) == 0: |
|
logging.info("Skip empty segment") |
|
continue |
|
|
|
if len(all_text) == 0: |
|
all_text.append(seg.text) |
|
elif len(all_text[-1][0].encode()) == 1 and len(seg.text[0].encode()) == 1: |
|
all_text.append(" ") |
|
all_text.append(seg.text) |
|
else: |
|
all_text.append(seg.text) |
|
|
|
if punct is not None: |
|
seg.text = punct.add_punctuation(seg.text) |
|
segment_list.append(seg) |
|
all_text = "".join(all_text) |
|
if punct is not None: |
|
all_text = punct.add_punctuation(all_text) |
|
|
|
return "\n\n".join(f"{i}\n{seg}" for i, seg in enumerate(segment_list, 1)), all_text |
|
|