Оформи это в класс, чтобы я смог запустить его в потоке
import os
import requests
import time
import json
import numpy as np
import torch
import soundfile as sf
import librosa
from pydub import AudioSegment

import kaldi_native_fbank as knf
import onnxruntime as ort

from sbert_punc_case_ru import SbertPuncCase


from flask import Flask, request, jsonify
import threading


# --- сюда вставляем класс OnnxModel и все функции create_fbank и compute_features ---

def create_fbank():
    opts = knf.FbankOptions()
    opts.frame_opts.dither = 0
    opts.frame_opts.remove_dc_offset = False
    opts.frame_opts.preemph_coeff = 0
    opts.frame_opts.window_type = "hann"
    opts.frame_opts.round_to_power_of_two = False
    opts.mel_opts.low_freq = 0
    opts.mel_opts.high_freq = 8000
    opts.mel_opts.num_bins = 64
    fbank = knf.OnlineFbank(opts)
    return fbank

def compute_features(audio, fbank):
    assert len(audio.shape) == 1, audio.shape
    fbank.accept_waveform(16000, audio)
    ans = []
    processed = 0
    while processed < fbank.num_frames_ready:
        ans.append(np.array(fbank.get_frame(processed)))
        processed += 1
    ans = np.stack(ans)
    return ans

class OnnxModel:
    def __init__(self, encoder: str, decoder: str, joiner: str):
        self.init_encoder(encoder)
        self.init_decoder(decoder)
        self.init_joiner(joiner)

    def init_encoder(self, encoder):
        session_opts = ort.SessionOptions()
        session_opts.inter_op_num_threads = 1
        session_opts.intra_op_num_threads = 1
        self.encoder = ort.InferenceSession(
            encoder,
            sess_options=session_opts,
            providers=["CPUExecutionProvider"],
        )
        meta = self.encoder.get_modelmeta().custom_metadata_map
        self.normalize_type = meta.get("normalize_type", "")
        self.pred_rnn_layers = int(meta.get("pred_rnn_layers", "1"))
        self.pred_hidden = int(meta.get("pred_hidden", "320"))

    def init_decoder(self, decoder):
        session_opts = ort.SessionOptions()
        session_opts.inter_op_num_threads = 1
        session_opts.intra_op_num_threads = 1
        self.decoder = ort.InferenceSession(
            decoder,
            sess_options=session_opts,
            providers=["CPUExecutionProvider"],
        )

    def init_joiner(self, joiner):
        session_opts = ort.SessionOptions()
        session_opts.inter_op_num_threads = 1
        session_opts.intra_op_num_threads = 1
        self.joiner = ort.InferenceSession(
            joiner,
            sess_options=session_opts,
            providers=["CPUExecutionProvider"],
        )

    def get_decoder_state(self):
        batch_size = 1
        state0 = torch.zeros(self.pred_rnn_layers, batch_size, self.pred_hidden).numpy()
        state1 = torch.zeros(self.pred_rnn_layers, batch_size, self.pred_hidden).numpy()
        return state0, state1

    def run_encoder(self, x: np.ndarray):
        x = torch.from_numpy(x)
        x = x.t().unsqueeze(0)  # Shape: [1, C, T]
        x_lens = torch.tensor([x.shape[-1]], dtype=torch.int64)

        (encoder_out, out_len) = self.encoder.run(
            [
                self.encoder.get_outputs()[0].name,
                self.encoder.get_outputs()[1].name,
            ],
            {
                self.encoder.get_inputs()[0].name: x.numpy(),
                self.encoder.get_inputs()[1].name: x_lens.numpy(),
            },
        )
        return encoder_out

    def run_decoder(self, token: int, state0: np.ndarray, state1: np.ndarray):
        target = torch.tensor([[token]], dtype=torch.int32).numpy()
        target_len = torch.tensor([1], dtype=torch.int32).numpy()

        (decoder_out, decoder_out_length, state0_next, state1_next,) = self.decoder.run(
            [
                self.decoder.get_outputs()[0].name,
                self.decoder.get_outputs()[1].name,
                self.decoder.get_outputs()[2].name,
                self.decoder.get_outputs()[3].name,
            ],
            {
                self.decoder.get_inputs()[0].name: target,
                self.decoder.get_inputs()[1].name: target_len,
                self.decoder.get_inputs()[2].name: state0,
                self.decoder.get_inputs()[3].name: state1,
            },
        )
        return decoder_out, state0_next, state1_next

    def run_joiner(self, encoder_out: np.ndarray, decoder_out: np.ndarray):
        logit = self.joiner.run(
            [
                self.joiner.get_outputs()[0].name,
            ],
            {
                self.joiner.get_inputs()[0].name: encoder_out,
                self.joiner.get_inputs()[1].name: decoder_out,
            },
        )[0]
        return logit


def load_tokens(tokens_path="./tokens.txt"):
    id2token = dict()
    with open(tokens_path, encoding="utf-8") as f:
        for line in f:
            fields = line.strip().split()
            if len(fields) == 1:
                id2token[int(fields[0])] = " "
            else:
                t, idx = fields
                id2token[int(idx)] = t
    return id2token


# новая функция вместо transcribe_audio_to_json с новой оннх моделью
def transcribe_audio_to_json_onnx(model: OnnxModel, audio_file_path, id2token):
    # Здесь мы рассматриваем только одноканальное аудио (модель рассчитана на 1 канал)
    # Загружаем аудио (pydub можно убрать, тк мы работаем с numpy через soundfile)
    audio, sr = sf.read(audio_file_path, dtype="float32")
    if len(audio.shape) > 1:
        # если стерео, берём первый канал
        audio = audio[:, 0]
    if sr != 16000:
        audio = librosa.resample(audio, orig_sr=sr, target_sr=16000)
        sr = 16000

    # добавим padding 2 секунды тишины в конец
    tail_padding = np.zeros(sr * 2, dtype=np.float32)
    audio = np.concatenate([audio, tail_padding])

    fbank = create_fbank()
    features = compute_features(audio, fbank)

    blank = len(id2token) - 1
    ans = [blank]  # список индексов токенов
    state0, state1 = model.get_decoder_state()
    decoder_out, state0_next, state1_next = model.run_decoder(ans[-1], state0, state1)

    encoder_out = model.run_encoder(features)

    # decode loop
    for t in range(encoder_out.shape[2]):
        encoder_out_t = encoder_out[:, :, t : t + 1]
        logits = model.run_joiner(encoder_out_t, decoder_out)
        logits = torch.from_numpy(logits).squeeze()
        idx = torch.argmax(logits, dim=-1).item()
        if idx != blank:
            ans.append(idx)
            state0 = state0_next
            state1 = state1_next
            decoder_out, state0_next, state1_next = model.run_decoder(ans[-1], state0, state1)

    ans = ans[1:]  # удаляем первый blank токен

    tokens = [id2token.get(i, "") for i in ans]
    underline = "▁"
    text = "".join(tokens).replace(underline, " ").strip()

    # Вернём один "сегмент" с примерным временем от 0 до длины аудио в секундах
    audio_duration = len(audio) / sr

    result = [{
        "channel": "mono",
        "start": 0.0,
        "end": audio_duration,
        "text": text
    }]

    return result
   
def transcribe_channel_by_chunks(model: OnnxModel, audio_segment: AudioSegment, id2token, chunk_ms=60000):
    """
    Нарезаем audio_segment на куски chunk_ms мс (например, 1 минута = 60000 мс),
    транскрибируем каждый кусок, склеиваем результаты в один текст.
    """
    total_len = len(audio_segment)
    texts = []

    for start_ms in range(0, total_len, chunk_ms):
        end_ms = min(start_ms + chunk_ms, total_len)
        chunk = audio_segment[start_ms:end_ms]

        temp_file = f"temp_chunk_{start_ms}_{end_ms}.wav"
        chunk.export(temp_file, format="wav")

        res = transcribe_audio_to_json_onnx(model, temp_file, id2token)
        os.remove(temp_file)

        if res and len(res) > 0:
            text = res[0]["text"].strip()
            texts.append(text)

    full_text = " ".join(texts).strip()
    return full_text


def transcribe_stereo_onnx_by_chunks(model: OnnxModel, audio_file_path: str, id2token):
    audio = AudioSegment.from_file(audio_file_path)
    channels = audio.split_to_mono()
    
    if len(channels) == 0:
        # Почему-то нет каналов - вернуть пустые списки или исключение
        return [], []
    elif len(channels) == 1:
        # Если канал один, обрабатываем только его (левый, например)
        left_text = transcribe_channel_by_chunks(model, channels[0], id2token)
        return [{"channel": "left", "start": 0.0, "end": len(channels[0]) / 1000, "text": left_text}], []
    else:
        # Два и более канала, берем первые два
        left_text = transcribe_channel_by_chunks(model, channels[0], id2token)
        right_text = transcribe_channel_by_chunks(model, channels[1], id2token)

        left_result = [{
            "channel": "left",
            "start": 0.0,
            "end": len(channels[0]) / 1000,
            "text": left_text
        }]

        right_result = [{
            "channel": "right",
            "start": 0.0,
            "end": len(channels[1]) / 1000,
            "text": right_text
        }]

        return left_result, right_result
     
def transcribe_stereo_onnx_by_chunks22(model: OnnxModel, audio_file_path: str, id2token):
    audio = AudioSegment.from_file(audio_file_path)
    channels = audio.split_to_mono()

    left_text = transcribe_channel_by_chunks(model, channels[0], id2token)
    right_text = transcribe_channel_by_chunks(model, channels[1], id2token)

    left_result = [{
        "channel": "left",
        "start": 0.0,
        "end": len(channels[0]) / 1000,
        "text": left_text
    }]

    right_result = [{
        "channel": "right",
        "start": 0.0,
        "end": len(channels[1]) / 1000,
        "text": right_text
    }]

    return left_result, right_result
    
# Обновленная функция обработки и разделения каналов
def transcribe_stereo_onnx(model: OnnxModel, audio_file_path, id2token):
    audio = AudioSegment.from_file(audio_file_path)
    channels = audio.split_to_mono()

    results_left = []
    results_right = []

    for i, channel in enumerate(channels):
        temp_path = f"temp_channel_{i + 1}.wav"
        channel.export(temp_path, format="wav")
        res = transcribe_audio_to_json_onnx(model, temp_path, id2token)

        # уточняем channel-метку
        for r in res:
            r["channel"] = "left" if i == 0 else "right"

        if i == 0:
            results_left.extend(res)
        else:
            results_right.extend(res)

        # Можно удалить временный файл
        os.remove(temp_path)

    # Добавляем id
    for idx, item in enumerate(results_left):
        item["id"] = idx + 1
    for idx, item in enumerate(results_right):
        item["id"] = idx + 1

    return results_left, results_right

# Ниже оставляем вспомогательные функции download_audio, get_json_from_url, trim_file_path без изменений


app = Flask(__name__)

# предполагается, что ниже уже есть весь ваш код с OnnxModel, transcribe_stereo_onnx_by_chunks и т.д.
# инициализируем модель и tokens при старте
path_model = "/home/user/call_record/sber/transducer-giga"

encoder_path = f"{path_model}/encoder.int8.onnx"
decoder_path = f"{path_model}/decoder.onnx"
joiner_path = f"{path_model}/joiner.onnx"
tokens_path = f"{path_model}/tokens.txt"

model_onnx = OnnxModel(encoder_path, decoder_path, joiner_path)
id2token = load_tokens(tokens_path)

pymodel = SbertPuncCase()



@app.route('/transcribe', methods=['POST'])
def transcribe():
    data = request.json
    if not data or 'audio_path' not in data:
        return jsonify({"error": "audio_path not provided"}), 400
    
    audio_path = data["audio_path"]
    
    print(f"{audio_path}")
    try:
        left_result, right_result = transcribe_stereo_onnx_by_chunks(model_onnx, audio_path, id2token)
        
        # Пунктуация текста для левого канала
        if left_result and len(left_result) > 0 and left_result[0].get('text'):
            left_result[0]['text'] = pymodel.punctuate(left_result[0]['text'])
        else:
            print("Left channel text missing or empty")

        # Пунктуация текста для правого канала
        if right_result and len(right_result) > 0 and right_result[0].get('text'):
            right_result[0]['text'] = pymodel.punctuate(right_result[0]['text'])
        else:
            print("Right channel text missing or empty")
   
        print(f"left_result {left_result[0]['text']}")
        print(f"right_result {right_result[0]['text']}")
        # Возвращаем JSON с результатами
        return jsonify({
            "left": left_result[0]['text'],
            "right": right_result[0]['text']
        })
        
    except Exception as e:
        return jsonify({"error": str(e)}), 500


def run_server():
    app.run(host="0.0.0.0", port=5002)

if __name__ == "__main__":
    run_server()