{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f72992c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torchaudio\n",
    "import librosa\n",
    "import pyrubberband as pyrb\n",
    "from torch import Tensor, no_grad, device, cuda\n",
    "from torchaudio.transforms import FrequencyMasking, MelSpectrogram, AmplitudeToDB, Resample, TimeMasking, TimeStretch\n",
    "from torch_audiomentations import Gain, PitchShift, LowPassFilter, HighPassFilter, ApplyImpulseResponse, PolarityInversion\n",
    "from pathlib import Path\n",
    "from librosa import effects\n",
    "import torchaudio.functional as F\n",
    "from IPython.display import Audio\n",
    "import matplotlib.pyplot as plt\n",
    "import random\n",
    "from typing import TypedDict\n",
    "\n",
    "from tokenizer import ASRTokenizer\n",
    "from model import ASRModel\n",
    "\n",
    "CONFIG = {\n",
    "    # 模型配置\n",
    "    'input_dim': 256,\n",
    "    'num_heads': 8,\n",
    "    'ffn_dim': 2048,\n",
    "    'num_layers': 8,\n",
    "    'dropout': 0.1,\n",
    "}\n",
    "workspace_dir = Path.cwd().parent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15a9a926",
   "metadata": {},
   "outputs": [],
   "source": [
    "class AdvancedAugment:\n",
    "    def __init__(self, noise_folder=None):\n",
    "        # 如果你有噪音文件夹，可以预加载噪音路径列表\n",
    "        # 场景建议：1.汽车 2.餐厅 3.街道 4.办公室 5.下雨 6.风声 7.键盘声 8.婴儿哭声 9.音乐 10.走廊回声 11.白噪音 12.粉红噪音\n",
    "        self.noise_files = [] # 这里存放你的 .wav 噪音文件路径\n",
    "\n",
    "    def _add_noise(self, waveform: Tensor) -> Tensor:\n",
    "        \"\"\"\n",
    "        更丰富的加噪：随机选择 白噪音、粉红噪音 或 真实环境音\n",
    "        \"\"\"\n",
    "        # 1. 随机决定噪音类型\n",
    "        noise_type = random.choice(['white', 'pink', 'environmental'])\n",
    "        \n",
    "        # 2. 随机设定信噪比 (SNR) - 模拟各种清晰度\n",
    "        snr_db = random.uniform(5, 25) \n",
    "        \n",
    "        if noise_type == 'white':\n",
    "            noise = torch.randn_like(waveform)\n",
    "        elif noise_type == 'pink':\n",
    "            \n",
    "            noise = self._generate_pink_noise(waveform)\n",
    "        else:\n",
    "            # 模拟环境音 (如果你有噪音库)\n",
    "            # 这里演示如果没有噪音库，就用多种不同频率的随机噪音组合\n",
    "            noise = self._generate_simulated_env_noise(waveform)\n",
    "\n",
    "        # 3. 计算能量并叠加\n",
    "        return self._mix_signal_noise(waveform, noise, snr_db)\n",
    "\n",
    "    def _generate_pink_noise(self, waveform):\n",
    "        \"\"\"粉红噪音（比白噪音更像自然界的声音，低频能量更高）\"\"\"\n",
    "        # 简易实现：对白噪音做低通滤波\n",
    "        white = torch.randn_like(waveform)\n",
    "        return torch.cumsum(white, dim=-1) / 10.0 # 简单的积分近似\n",
    "\n",
    "    def _generate_simulated_env_noise(self, waveform):\n",
    "        \"\"\"模拟环境噪音（通过叠加不同频率的波形）\"\"\"\n",
    "        # 模拟 12 种以上变化：随机叠加正弦波或窄带噪音\n",
    "        noise = torch.zeros_like(waveform)\n",
    "        for _ in range(random.randint(3, 8)):\n",
    "            freq = random.uniform(50, 4000)\n",
    "            t = torch.arange(waveform.shape[-1]).to(waveform.device)\n",
    "            noise += torch.sin(2 * 3.14159 * freq * t / 16000)\n",
    "        return noise + torch.randn_like(waveform) * 0.5\n",
    "\n",
    "    def _mix_signal_noise(self, signal, noise, snr_db):\n",
    "        \"\"\"核心：根据 SNR 混合信号和噪音\"\"\"\n",
    "        s_p = signal.pow(2).mean()\n",
    "        n_p = noise.pow(2).mean()\n",
    "        \n",
    "        # 防止除以 0\n",
    "        if n_p == 0: return signal\n",
    "        \n",
    "        snr_linear = 10**(snr_db/10)\n",
    "        scale = torch.sqrt(s_p / (n_p * snr_linear))\n",
    "        \n",
    "        noisy = signal + scale * noise\n",
    "        return noisy / (noisy.abs().max() + 1e-7) # 归一化防止爆音\n",
    "\n",
    "    def _simulate_reverb(self, waveform):\n",
    "        \"\"\"\n",
    "        模拟走廊/房间回声 (Simple Reverb)\n",
    "        \"\"\"\n",
    "        # if random.random() < 0.3: # 30% 概率添加回声\n",
    "            # 模拟简单的延迟反馈（模拟大走廊）\n",
    "        delay_samples = random.randint(500, 2000) # 30ms - 125ms 延迟\n",
    "        decay = random.uniform(0.3, 0.6)\n",
    "        \n",
    "        reverb_signal = torch.zeros_like(waveform)\n",
    "        reverb_signal[:, delay_samples:] = waveform[:, :-delay_samples] * decay\n",
    "        return (waveform + reverb_signal) / (1 + decay)\n",
    "        return waveform\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0635d68",
   "metadata": {},
   "outputs": [],
   "source": [
    "class NoiseAugmentor:\n",
    "    def __init__(self, noise_root: Path, sample_rate=16000):\n",
    "        self.sample_rate = sample_rate\n",
    "        # 递归找到目录下所有的 wav 文件\n",
    "        self.noise_files = list(Path(noise_root).rglob(\"*.wav\"))\n",
    "        print(f\"成功加载 {len(self.noise_files)} 个噪音文件\")\n",
    "\n",
    "    def apply_real_noise(self, waveform: Tensor):\n",
    "        # 1. 随机选一个噪音文件\n",
    "        noise_path = random.choice(self.noise_files)\n",
    "        noise_waveform, sr = torchaudio.load_with_torchcodec(noise_path)\n",
    "        \n",
    "        # 2. 统一采样率\n",
    "        if sr != self.sample_rate:\n",
    "            resampler = Resample(sr, self.sample_rate)\n",
    "            noise_waveform = resampler(noise_waveform)\n",
    "        \n",
    "        # 3. 截取或填充，使其长度与语音一致\n",
    "        sig_len = waveform.shape[1]\n",
    "        noise_len = noise_waveform.shape[1]\n",
    "        \n",
    "        if noise_len >= sig_len:\n",
    "            # 随机截取一段\n",
    "            start = random.randint(0, noise_len - sig_len)\n",
    "            noise_waveform = noise_waveform[:, start:start + sig_len]\n",
    "        else:\n",
    "            full_noise = torch.zeros_like(waveform)\n",
    "            start = random.randint(0, sig_len - noise_len)\n",
    "            full_noise[:, start : start + noise_len] = noise_waveform\n",
    "            noise_waveform = full_noise\n",
    "            # 如果噪音太短，循环填充\n",
    "            # repeats = (sig_len // noise_len) + 1\n",
    "            # noise_waveform = noise_waveform.repeat(1, repeats)[:, :sig_len]\n",
    "            \n",
    "        # 4. 设定随机信噪比 SNR (5dB 到 20dB)\n",
    "        snr_db = random.uniform(5, 20)\n",
    "        \n",
    "        # 5. 混合\n",
    "        return self._mix_at_snr(waveform, noise_waveform, snr_db)\n",
    "\n",
    "    def _mix_at_snr(self, signal: Tensor, noise: Tensor, snr_db: float):\n",
    "        s_p = signal.pow(2).mean()\n",
    "        n_p = noise.pow(2).mean()\n",
    "        snr_linear = 10**(snr_db / 10)\n",
    "        scale = torch.sqrt(s_p / (n_p * snr_linear + 1e-8))\n",
    "        \n",
    "        noisy = signal + scale * noise\n",
    "        # 归一化，防止溢出\n",
    "        return noisy / (noisy.abs().max() + 1e-8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e77899f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ASRInference:\n",
    "    def __init__(self, model_path: Path, vocab_path: Path, noise_dir: Path, device: device, augment: bool = True, augment_prob: float = 0.5) -> None:\n",
    "        self.device = device\n",
    "        self.augment: bool = augment\n",
    "        self.augment_prob: float = augment_prob\n",
    "        self.noise_augmentor = NoiseAugmentor(noise_root=noise_dir)\n",
    "        self.tokenizer = ASRTokenizer(vocab_path=vocab_path)\n",
    "        self.model = ASRModel(vocab_size=self.tokenizer.vocab_size(), **CONFIG).to(device=device)\n",
    "        self.model.load_state_dict(torch.load(model_path, map_location=device)['model_state_dict'])\n",
    "        self.model.eval()\n",
    "\n",
    "        print(f\"params params: {self.model.get_num_params():,}\",)\n",
    "        print(f\"Vocabulary Size: {self.tokenizer.vocab_size():,}\",)\n",
    "\n",
    "        self.sample_rate = 16000\n",
    "\n",
    "        self.gain_up = Gain(min_gain_in_db=4, max_gain_in_db=8, p=1.0, output_type='tensor')\n",
    "        self.gain_down = Gain(min_gain_in_db=-15, max_gain_in_db=-8, p=1.0, output_type='tensor')\n",
    "        self.pitch_up = PitchShift(min_transpose_semitones=1, max_transpose_semitones=4, p=1.0, sample_rate=self.sample_rate, output_type='tensor')\n",
    "        self.pitch_down = PitchShift(min_transpose_semitones=-4, max_transpose_semitones=-1, p=1.0, sample_rate=self.sample_rate, output_type='tensor')\n",
    "        self.lowpass = LowPassFilter(min_cutoff_freq=1000, max_cutoff_freq=3000, p=1.0, output_type='tensor')\n",
    "        self.highpass = HighPassFilter(min_cutoff_freq=1000, max_cutoff_freq=3000, p=1.0, output_type='tensor')\n",
    "        self.apply_ir = ApplyImpulseResponse(ir_paths=noise_dir,convolve_mode='same', p=1, output_type=\"tensor\")\n",
    "        self.polarity_inversion = PolarityInversion(p=1.0, output_type=\"tensor\")\n",
    "        \n",
    "    def _load_audio(self, audio_path: Path) -> Tensor:\n",
    "        waveform, sample_rate = torchaudio.load_with_torchcodec(audio_path)\n",
    "\n",
    "        if sample_rate != self.sample_rate:\n",
    "          waveform = Resample(sample_rate, self.sample_rate)(waveform)\n",
    "\n",
    "        if waveform.shape[0] > 1:\n",
    "            waveform = waveform.mean(dim=0, keepdim=True)\n",
    "\n",
    "        waveform = waveform / (waveform.abs().max() + 1e-8)\n",
    "        return waveform\n",
    "    \n",
    "    def augment_waveform(self, waveform: Tensor) -> Tensor:\n",
    "        if not self.augment or random.random() > self.augment_prob:\n",
    "            return waveform\n",
    "        \n",
    "        # 1. voice Stretch/Compress \n",
    "        if random.random() < 0.6:\n",
    "            waveform = self._stretch_or_compress(waveform=waveform)\n",
    "        \n",
    "        if random.random() < 0.7:\n",
    "            waveform = self.noise_augmentor.apply_real_noise(waveform)\n",
    "        \n",
    "        if random.random() < 0.3:\n",
    "            waveform = self._time_mask_waveform(waveform=waveform)\n",
    "\n",
    "        # torch_audiomentations: [1, time] -> [1, 1, time]\n",
    "        if waveform.dim() == 2:\n",
    "            waveform_3d = waveform.unsqueeze(0)\n",
    "        # 随机选择一种频谱增强\n",
    "        choice = random.random()\n",
    "        if choice < 0.30:\n",
    "            # 增益变化（上或下）\n",
    "            if random.random() < 0.5:\n",
    "                print('gain_up')\n",
    "                waveform_3d = self.gain_up(waveform_3d, sample_rate=self.sample_rate)\n",
    "            else:\n",
    "                waveform_3d = self.gain_down(waveform_3d, sample_rate=self.sample_rate)\n",
    "                print('gain_down')\n",
    "        elif choice < 0.60:\n",
    "            # 音高变化（上或下）\n",
    "            if random.random() < 0.5:\n",
    "                print('pitch_up')\n",
    "                waveform_3d = self.pitch_up(waveform_3d, sample_rate=self.sample_rate)\n",
    "            else:\n",
    "                print('pitch_down')\n",
    "                waveform_3d = self.pitch_down(waveform_3d, sample_rate=self.sample_rate)\n",
    "        elif choice < 0.80:\n",
    "            # 低通滤波（声音发闷）\n",
    "            print('lowpass')\n",
    "            waveform_3d = self.lowpass(waveform_3d, sample_rate=self.sample_rate)\n",
    "        elif choice < 0.95:\n",
    "            print('highpass')\n",
    "            # 高通滤波（电话效果）\n",
    "            waveform_3d = self.highpass(waveform_3d, sample_rate=self.sample_rate)\n",
    "        else:\n",
    "            print('polarity_inversion')\n",
    "            self.polarity_inversion(waveform_3d, sample_rate=self.sample_rate)\n",
    "        \n",
    "        #  [1, 1, time] -> [1, time]\n",
    "        waveform = waveform_3d.squeeze(0)\n",
    "\n",
    "        # 防止多次 augment 后振幅溢出，最后归一化\n",
    "        max_amp = waveform.abs().max()\n",
    "        if max_amp > 1.0:\n",
    "            waveform = waveform / max_amp\n",
    "        \n",
    "        return waveform\n",
    "    \n",
    "    def _stretch_or_compress1(self, waveform: Tensor) -> Tensor:\n",
    "        speed_factor = random.uniform(0.8, 1.4) # (Speed Change: 0.6x - 1.4x)\n",
    "        waveform_np = waveform.squeeze(0).cpu().numpy()\n",
    "        y_stretch = librosa.effects.time_stretch(waveform_np, rate=speed_factor)\n",
    "        waveform = torch.from_numpy(y_stretch).float().to(waveform.device).unsqueeze(0)\n",
    "        return waveform\n",
    "    \n",
    "    def _stretch_or_compress(self, waveform: Tensor) -> Tensor:\n",
    "        speed_factor = random.uniform(0.8, 1.6) # (Speed Change: 0.8x - 1.6x)\n",
    "        spec = torch.stft(\n",
    "            waveform.squeeze(0),\n",
    "            n_fft=400,\n",
    "            hop_length=160,\n",
    "            window=torch.hann_window(400).to(waveform.device),\n",
    "            return_complex=True\n",
    "        )\n",
    "        \n",
    "        # 时间拉伸（不改变音高）\n",
    "        stretch = TimeStretch(hop_length=160, n_freq=spec.shape[-2], fixed_rate=speed_factor)\n",
    "        stretched_spec = stretch(spec)\n",
    "        \n",
    "        # 转回波形\n",
    "        waveform_stretched = torch.istft(\n",
    "            stretched_spec,\n",
    "            n_fft=400,\n",
    "            hop_length=160,\n",
    "            window=torch.hann_window(400).to(waveform.device)\n",
    "        ).unsqueeze(0)\n",
    "    \n",
    "        return waveform_stretched\n",
    "\n",
    "    def _stretch_or_compress2(self, waveform: Tensor) -> Tensor:\n",
    "        speed_factor = random.uniform(0.6, 1.4) # (Speed Change: 0.6x - 1.4x)\n",
    "        waveform_np = waveform.squeeze(0).cpu().numpy()\n",
    "        y_stretch = pyrb.time_stretch(waveform_np, self.sample_rate, speed_factor)\n",
    "        return torch.from_numpy(y_stretch).float().to(waveform.device).unsqueeze(0)\n",
    "    \n",
    "    def _time_mask_waveform(self, waveform: Tensor) -> Tensor:\n",
    "        audio_len = waveform.shape[1]\n",
    "        sr = self.sample_rate # 16000\n",
    "        \n",
    "        # 设置参数：单次遮盖最长 0.4 秒 (6400个点)\n",
    "        max_mask_time = 0.4\n",
    "        max_mask_samples = int(sr * max_mask_time)\n",
    "        \n",
    "        # 根据音频长度决定遮盖次数：\n",
    "        # 比如每 3 秒钟允许遮盖 1 次\n",
    "        num_masks = max(1, audio_len // (sr * 3)) \n",
    "        \n",
    "        for _ in range(num_masks):\n",
    "            # 每次随机遮盖 0.1s 到 0.4s\n",
    "            current_mask_len = random.randint(int(sr * 0.1), max_mask_samples)\n",
    "            \n",
    "            if audio_len > current_mask_len:\n",
    "                start_pos = random.randint(0, audio_len - current_mask_len)\n",
    "                \n",
    "                # 填充微小噪音（模拟环境底噪）\n",
    "                noise = torch.randn(1, current_mask_len).to(waveform.device) * 0.002\n",
    "                waveform[:, start_pos : start_pos + current_mask_len] = noise\n",
    "                \n",
    "        return waveform\n",
    "\n",
    "    def transcribe(self, waveform: Tensor) -> str:\n",
    "        waveform = waveform.to(device=self.device)\n",
    "        waveform_length = torch.tensor([waveform.shape[1]], dtype=torch.long, device=self.device)\n",
    "\n",
    "        with no_grad():\n",
    "            log_probs, _ = self.model(waveforms=waveform, waveform_lengths=waveform_length)\n",
    "        \n",
    "        text = self.tokenizer.ctc_greedy_decode(log_probs=log_probs[0])\n",
    "        return text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5af7c836",
   "metadata": {},
   "outputs": [],
   "source": [
    "device = torch.device('cuda:1')\n",
    "\n",
    "# checkpoint = sorted(workspace_dir.glob('.checkpoints/checkpoint_epoch_*.pt'), key=lambda p: int(p.stem.split('_')[-1]))[-1]\n",
    "# checkpoint = workspace_dir / \".checkpoints/best_wer_model.pt\"\n",
    "# checkpoint = workspace_dir / \".checkpoints/best_cer_model.pt\"\n",
    "checkpoint = workspace_dir / \".checkpoints/prodect/best_wer_model.pt\"\n",
    "print(f\"Load Checkpoint: {checkpoint}\")\n",
    "inference = ASRInference(model_path=checkpoint, vocab_path=workspace_dir / 'config/asr_vocab.json', noise_dir='/mnt/dataset/dataset/audio/noise', device=device, augment_prob=0.8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "967e23cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# audio_path = workspace_dir / '.data/ug/clips/common_voice_ug_42681424.mp3'\n",
    "# audio_path = workspace_dir / '.data/ug/clips/common_voice_ug_26188445.mp3'\n",
    "# audio_path = workspace_dir / '.data/ug/clips/common_voice_ug_43349581.mp3'\n",
    "# audio_path = workspace_dir / '.data/ug/clips/common_voice_ug_42640878.mp3'\n",
    "# audio_path = workspace_dir / '.data/ug/clips/common_voice_ug_26245615.mp3'\n",
    "# audio_path = workspace_dir / '.data/ug/clips/common_voice_ug_40794549.mp3'\n",
    "# audio_path = workspace_dir / 'data/ug/clips/common_voice_ug_26245614.mp3'\n",
    "# audio_path = '/mnt/train/audio/Dilnaz/only_vocals/3272800876_100006098_(vocals)_melband_roformer_big_beta5e.m4a'\n",
    "# audio_path = workspace_dir / 'data/test/how_are_you.mp3'\n",
    "# audio_path = workspace_dir / 'data/test/split_2.m4a'\n",
    "# audio_path = workspace_dir / 'data/test/let_me_see_the_computer.mp3'\n",
    "# audio_path = workspace_dir / 'data/test/introduce_myself.mp3'\n",
    "# audio_path = workspace_dir / 'data/test/F001_001.wav'\n",
    "# audio_path = workspace_dir / 'data/test/test_voise.m4a'\n",
    "# audio_path = workspace_dir / 'data/test/radio_low.m4a'\n",
    "# audio_path = workspace_dir / 'data/test/radio_high.m4a'\n",
    "audio_path = workspace_dir / 'data/test/3272800876_100006098_(vocals)_melband_roformer_big_beta5e.m4a'\n",
    "# audio_path = workspace_dir / 'data/test/download.wav'\n",
    "orginal_waveform = inference._load_audio(audio_path=audio_path)\n",
    "print('load audio:', orginal_waveform.shape)\n",
    "# orginal_waveform = orginal_waveform[:, :]\n",
    "\n",
    "def simple_corridor_echo(waveform, sample_rate=16000, delay_ms=80, decay=0.3):\n",
    "    \"\"\"\n",
    "    模拟一次明显的走廊回声：原始声音 + 一个衰减延迟的拷贝\n",
    "    delay_ms: 延迟毫秒数，80ms 模拟中等走廊\n",
    "    decay: 回声的衰减系数，0.2~0.4 比较自然\n",
    "    \"\"\"\n",
    "    delay_samples = int(sample_rate * delay_ms / 1000)\n",
    "    echo = torch.zeros_like(waveform)\n",
    "    echo[..., delay_samples:] = waveform[..., :-delay_samples] * decay\n",
    "    return waveform + echo\n",
    "\n",
    "print(\"Orginal audio\")\n",
    "display(Audio(orginal_waveform, rate=inference.sample_rate))\n",
    "\n",
    "# augment_waveform = inference.augment_waveform(waveform=orginal_waveform)\n",
    "# noise_augmentor_waveform = inference.noise_augmentor.apply_real_noise(waveform=orginal_waveform)\n",
    "# display(Audio(augment_waveform, rate=inference.sample_rate))\n",
    "# text = inference.transcribe(waveform=orginal_waveform)\n",
    "# print(f\"\\n{text}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "16195280",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 13815687])\n",
      "speech_timestamps: [{'start': 122240, 'end': 221312}, {'start': 223104, 'end': 288000}, {'start': 288000, 'end': 346368}, {'start': 346368, 'end': 416256}, {'start': 416256, 'end': 476416}, {'start': 476416, 'end': 502912}, {'start': 606080, 'end': 632960}, {'start': 645504, 'end': 710784}, {'start': 714112, 'end': 777856}, {'start': 781696, 'end': 819840}, {'start': 823168, 'end': 937088}, {'start': 941440, 'end': 1111680}, {'start': 1122176, 'end': 1211520}, {'start': 1214848, 'end': 1334400}, {'start': 1345408, 'end': 1455744}, {'start': 1479552, 'end': 1648768}, {'start': 1654656, 'end': 1779840}, {'start': 1783168, 'end': 1990784}, {'start': 2034560, 'end': 2108032}, {'start': 2108288, 'end': 2198144}, {'start': 2201984, 'end': 2236416}, {'start': 2236416, 'end': 2267264}, {'start': 2277248, 'end': 2329216}, {'start': 2331520, 'end': 2377344}, {'start': 2383232, 'end': 2403840}, {'start': 2403840, 'end': 2496768}, {'start': 2496768, 'end': 2594432}, {'start': 2595712, 'end': 2626688}, {'start': 2636160, 'end': 2659328}, {'start': 2659328, 'end': 2711168}, {'start': 2719104, 'end': 2837120}, {'start': 2890112, 'end': 3007744}, {'start': 3007744, 'end': 3055232}, {'start': 3059072, 'end': 3223168}, {'start': 3230080, 'end': 3289216}, {'start': 3296640, 'end': 3616384}, {'start': 3617152, 'end': 3677312}, {'start': 3683712, 'end': 3720832}, {'start': 3728256, 'end': 3771520}, {'start': 3795840, 'end': 3837056}, {'start': 3839872, 'end': 3906176}, {'start': 3924864, 'end': 3961984}, {'start': 3964288, 'end': 3986944}, {'start': 3986944, 'end': 4022912}, {'start': 4037504, 'end': 4070016}, {'start': 4080512, 'end': 4135040}, {'start': 4148608, 'end': 4189952}, {'start': 4189952, 'end': 4213888}, {'start': 4225920, 'end': 4264064}, {'start': 4274560, 'end': 4308096}, {'start': 4323200, 'end': 4425344}, {'start': 4427136, 'end': 4553344}, {'start': 4558720, 'end': 4670080}, {'start': 4672384, 'end': 4744832}, {'start': 4785024, 'end': 4913792}, {'start': 4915584, 'end': 5035648}, {'start': 5037952, 'end': 5106944}, {'start': 5106944, 'end': 5140480}, {'start': 5140480, 'end': 5216384}, {'start': 5223296, 'end': 5340800}, {'start': 5344128, 'end': 5365888}, {'start': 5366144, 'end': 5384320}, {'start': 5385088, 'end': 5458560}, {'start': 5515136, 'end': 5683840}, {'start': 5690240, 'end': 5913216}, {'start': 5919616, 'end': 5988480}, {'start': 5993344, 'end': 6111872}, {'start': 6118784, 'end': 6172800}, {'start': 6179712, 'end': 6260352}, {'start': 6264704, 'end': 6431872}, {'start': 6582144, 'end': 6690816}, {'start': 6690816, 'end': 6728320}, {'start': 6735744, 'end': 6766208}, {'start': 6769536, 'end': 6826624}, {'start': 6827904, 'end': 6892160}, {'start': 6900608, 'end': 6927360}, {'start': 6927360, 'end': 6948992}, {'start': 6950272, 'end': 6973568}, {'start': 6978944, 'end': 7029888}, {'start': 7035264, 'end': 7069312}, {'start': 7076736, 'end': 7127168}, {'start': 7135616, 'end': 7221376}, {'start': 7223680, 'end': 7336064}, {'start': 7345536, 'end': 7395456}, {'start': 7395712, 'end': 7474304}, {'start': 7478656, 'end': 7541376}, {'start': 7544704, 'end': 7626368}, {'start': 7628672, 'end': 7639168}, {'start': 7639424, 'end': 7666816}, {'start': 7667072, 'end': 7749760}, {'start': 7751040, 'end': 7841792}, {'start': 7841792, 'end': 7859840}, {'start': 7862144, 'end': 7929344}, {'start': 7929344, 'end': 8049152}, {'start': 8049152, 'end': 8105984}, {'start': 8105984, 'end': 8155776}, {'start': 8161664, 'end': 8202880}, {'start': 8207744, 'end': 8290432}, {'start': 8297856, 'end': 8312448}, {'start': 8313216, 'end': 8377088}, {'start': 8377088, 'end': 8436864}, {'start': 8438144, 'end': 8516736}, {'start': 8524672, 'end': 8583296}, {'start': 8595840, 'end': 8629248}, {'start': 8629248, 'end': 8696448}, {'start': 8706944, 'end': 8724096}, {'start': 8725888, 'end': 8810112}, {'start': 8817536, 'end': 8841856}, {'start': 8842112, 'end': 8900224}, {'start': 8904576, 'end': 9025664}, {'start': 9035648, 'end': 9063168}, {'start': 9063168, 'end': 9096832}, {'start': 9100672, 'end': 9170176}, {'start': 9170176, 'end': 9180160}, {'start': 9180160, 'end': 9364608}, {'start': 9379712, 'end': 9417856}, {'start': 9419648, 'end': 9505408}, {'start': 9510272, 'end': 9552896}, {'start': 9552896, 'end': 9580160}, {'start': 9584512, 'end': 9611392}, {'start': 9619840, 'end': 9638400}, {'start': 9638400, 'end': 9733760}, {'start': 9748864, 'end': 9810048}, {'start': 9814400, 'end': 9889920}, {'start': 9901952, 'end': 9919488}, {'start': 9919488, 'end': 9964672}, {'start': 9966464, 'end': 10050176}, {'start': 10057088, 'end': 10199680}, {'start': 10204032, 'end': 10253824}, {'start': 10253824, 'end': 10315904}, {'start': 10319744, 'end': 10379904}, {'start': 10388864, 'end': 10407040}, {'start': 10411392, 'end': 10464896}, {'start': 10484096, 'end': 10563712}, {'start': 10567552, 'end': 10594048}, {'start': 10594048, 'end': 10691200}, {'start': 10697088, 'end': 10708096}, {'start': 10719616, 'end': 10790528}, {'start': 10803072, 'end': 10835072}, {'start': 10837888, 'end': 10887808}, {'start': 10893184, 'end': 10924800}, {'start': 10924800, 'end': 11035264}, {'start': 11042688, 'end': 11074688}, {'start': 11076992, 'end': 11086976}, {'start': 11100032, 'end': 11177600}, {'start': 11182464, 'end': 11251840}, {'start': 11255680, 'end': 11424384}, {'start': 11612032, 'end': 11735680}, {'start': 11743104, 'end': 11810944}, {'start': 11811712, 'end': 11872896}, {'start': 11873664, 'end': 11917952}, {'start': 11920768, 'end': 12016768}, {'start': 12025216, 'end': 12056704}, {'start': 12073856, 'end': 12133504}, {'start': 12138880, 'end': 12241024}, {'start': 12248448, 'end': 12281984}, {'start': 12283264, 'end': 12316288}, {'start': 12318080, 'end': 12345472}, {'start': 12350848, 'end': 12390400}, {'start': 12390400, 'end': 12428928}, {'start': 12431744, 'end': 12473472}, {'start': 12477824, 'end': 12519552}, {'start': 12519808, 'end': 12553344}, {'start': 12559744, 'end': 12625664}, {'start': 12625664, 'end': 12749312}, {'start': 12749312, 'end': 12800128}, {'start': 12807040, 'end': 12837504}, {'start': 12838784, 'end': 12859008}, {'start': 12863360, 'end': 12910720}, {'start': 12912000, 'end': 12976768}, {'start': 12986752, 'end': 13062784}, {'start': 13068160, 'end': 13200000}, {'start': 13204864, 'end': 13276800}, {'start': 13401472, 'end': 13432960}, {'start': 13444992, 'end': 13470848}, {'start': 13478784, 'end': 13506176}, {'start': 13515648, 'end': 13542016}, {'start': 13555584, 'end': 13579392}, {'start': 13589376, 'end': 13616256}, {'start': 13630336, 'end': 13652608}, {'start': 13666688, 'end': 13696128}, {'start': 13706624, 'end': 13730432}]\n",
      "speech_timestamps: 182\n",
      "VAD原始片段: 182 -> 合并后片段: 150\n",
      "ھۇزۇرۇڭلاردا بولۇۋاتقىنى «ئايشەم مايىل تەبەسسۇم قەلىمىدە پۈتكەن «ئايرىلىش ھەققى» ناملىق نادىر ئەسەر| بۇ ئەسەر شىنجاڭ خەزىنە مەدەنىيەت تارقىتىش شىركىتىدە ئاۋازغا ئېلىنىپ| ئېسىم فىنىم سالونى ۋە دىلناز ئەپچىسىدىن تارقىتىلدى.| باشقا ھەرقانداق سالامن ۋە ئەپچىلەرنىڭ كۆچۈرۈپ تارقىتىشى بىردەك مەى قىلىنىدۇ.| خىلاپلىق قىلغۇچىلار ئەسەرگە كەتكەن بارلىق چىقىمنى ئۆز ئۈستىگە ئالىدۇ| -گىكىمىزگە ھۆرمەت قىلىڭ.تۇز ئۈچىنچى قىسىم| قىزىنىڭ ئۇچۇرىنى ئالالماي ئەندىشىگە چۈشكەن ئايالنىڭ كۆڭلى پەرىشان ئىدى.| قايتا قايتىلاپ بورى ۋەگەن تېلې فوننىڭ ھەممىسى ئېتىش بولۇپ چىقتى.| بۇ قىز تېلېفوننى ھېچ ئاچچاي دېمەك.| ئاخى بولماي خەمەتنىڭ تېلېفوننومۇرىنى تېپىپ، ئۇنىڭغا تېلېفون قىلغان بولسىمۇ، خەمىمۇ ئوخشاشلا ئۇنىڭ ئۇچۇرىنى بىلمەيدىغان بو چىقتى.| بىر كېچە- كۈندۈزنى پۇتى كۆيگەن توخۇدەك جىددىيلىشىپ، پىكپىرلاپ مىڭ تەستە ئۆتكۈزگەن ئايال، ئاخىرى قىززىنىڭ يوقاپ كەتكەنلىكىنى ئېرىگە ئېيتىپ، ئۇنىڭدىن بىر ئېلا قىلىپ قىزىنى تېپىشىنى ئۆتۈندى.| رۇستەمباي ھېلىغىچە قىدىن رەنجى بارغان پىكىردە بولغاچقا، ئايالىنىڭ گەپلىرىگە كۆڭۈل بۆلمەك.| لېكىن بىچارە ئاپىسى بالا دەرجىدە پۇچۇلىنىپ، قاق بىر كۈن ئېرىنىڭ قۇلاق-مۇنى يەپ زادىلا ئاراملىق تەمى.| شۇنىڭدىمۇ ئۇ ئادەمنى ئېرىتىشكە كۆزى يەتمەي، ئاخىرى بولدىلا» دېگىنىچە ئۆز يالغۇز ساقچىخانىغا يۈگۈردى.| ئايال: «ھاسىراپ-ھۆمى دەپ يېتىپ كەلگەندە: خەمىت مۇشۇ يەردە بىرەيلەننى يوقاپ كەتتى» دەپ دېلو مەلۇم قىلىۋاتقان بولۇپ، ھەر ئىككىلىسى يوقلۇقىنى مەلۇم قىلغان ئادەم دەل تېلا قىزبولۇپ چىقتى.| خەمىت ئۇ يەردە بىر ياقتىن ساقچىغا ئەھۋالنى مەلۇم قىلىپ، يەنە بىر ياقتىن پۈتۈن ئىشخانىنى بېشىغا كىيىپ يىغلاۋاتقان ئايالغا تەسەللى بېرىپ ئاۋارە ئى.| ساقچىلار قىزنى پۈتۈن كۈچى بىلەن ئىزدەيدىغانلىقىنى، ئۇنىڭغىچە بۇلارنىڭمۇ ئۇرۇق -تۇغقانلىرىنىڭ ئۆيلىرىدىن ۋە قىزنىڭ بېرىش مۇمكىنچىلىكى بولغان ھەممەيەرلەردىن ئېسىگە كەلگەنلىكى يەرلەرگىچە سۈرۈش قىپ بېقىشنى تاپىلاپ، ئۇلارنى يولغا سالدى.| خەمەك تىللا قىزنىڭ ئاپىسىنى ئۆييىگە ئاپىرىپ قويۇ ئۈچۈن ماشىنىغا چىقىرىپ ئېيت ماڭدى.| يولدا ئايال قارر يامغۇر يېغىلىغىنىچە بار ئەندىشىلىرىنى خەمىپ كۈتۆكىۋاتاتتى.| بەك ئەنسىرەپ قالسام جېنىم ئۇكام.| بىرەر ئىش بولمىغاندۇ قىزىمغا؟ بۇ بالا ئەزەلدىن بۇنداق جىممىدە يوقاپ كېتىپ باققان ئەمەس.| دادىسىدىنبەكلا كۆڭلى ئاغرىدى، بىچارە قىزىمنىڭ.| شۇ ئادەممۇ ئۆمرۈمنىڭ تەڭدىن تولىسىنى ياشاپ بوپتىمەن، ئەمدى قىزىم پوسىنى بەختىڭ تاپسۇن» دېگەن بولسا| ئاچچىقى يامان قىلىپ، تاشدىشىنىپ تۇۋاغىچە شۇ بانە كۆڭلى چۈشكەن ئادەم بىلەن ئۆتكىلى قويغان بولسا| ئۇنداق قىسمۇ بولماستى يە، ئەسىررى مەڭ ھەدە.| لا قىزنى تاپىمىز، مەن ئۇنى چوقۇم تاپىمەن.| -دېدى خەت يۈرىكىنىڭ ئېچىشىپ تۇرغانلىقىنى ئايالغا بىلدۈرمەسلىككە تىرىشىپ كۆزلىرىدە لىغىرلاپ قاغان ياشنى ئۇنىڭدىن يوشۇر| پەمەتت ئايالنى ئۆيىگە ئاپىرىپ قويغاندىن كېيىن، تىلا قىز بىلەن بىللە بېرىپ باققانلىكى جايلارغا بېرىپ، ھەممەيەردىن ئۇنى ئالنى -قويماي ئىززەتدى.| مەن تونۇش بىلىشلەردىن سۈرۈش تۈقىپ چىقتى.| ھەتتا قىز بېرىشنى ياخشى كۆرىدىغان قەھۋەخانا، ساتىراشخانا، ھۆسن تۈزەش سالونلىرى قاتارلىق جايلارغىچە بېرىپ يادىغا كەلگەنلىكى يەرلەرنىڭ ھەممىسىدىن يىپئۇچى ئىكەنلىك.| ئەپسۇسكى، ھېچبىر يەردىن قىزنىڭ دېرىگە بولمايۋاتاتتى.| ئاتىسى بىر ياقتا خەمەت بىرياقتا، ئەنە شۇنداق جان پىدالىق بىلەن ئۇنى ئىزدەپ يۈرگەندە، ساقچىلاردىن يېڭى ئۇچۇر كېلىپ، قىزنىڭ يوقاپ كېتىشىنىڭ ئالدىنقى ئاخشىمى تورسۇپۇسىدا بىر مېھمانخانىدىن ياتاقسەپەز قىلغانلىقى، ئەڭ ئاخىرقى قېتىم شۇ يەردە ياتاقتىزىملاش ئۇچۇرىنى قالدۇرغاندىن كېيىن، ھېچقانداق بىر تىجارەت سورۇنى ياكى يوللاردا پەيدا بولمىغانلىقى دەلىللەندى.| بۇ خەۋەرنى ئاڭلىغان خىزمىنىڭ كۆڭلىدىن كەچمىگەن خىياللار قىممىدى.| ئەجەباب قىز مېھمانقانىڭياتاققېچەر.| ئاشۇ يەردە جىممىداق ھاججەتتىن ئاخىرلاشتۇرغانمىدۇ؟| يا ياكى ئۆزىدىنبەك ئۈمىدسىزلىنىپ كېتىپ| ئۇھەببەت ئىزھار قىلىپ يۈرگەن بىرەر ئوقۇمى بىلەن جىمۋىدىلا ياتاققا كىرىۋالغانمىدۇ؟| ئاتا-ئانىسىنىڭ ئۆيىگە قايتقىسى بولمىسا| كۆڭۈلدىكىنى ئېيتىپ، ئۆزىنىڭ يېنىدا ھاسىمۇ تامامەن بولاتتى.| ئۇ زاتە نېمىشقا ياتاق ئېچىپ قالغان| ۋەيەنە نېمىشقا ھەممە ئادەمدىن ئۆزىنى قاچۇرىدىغاندۇ؟| شۇ تاپتا ئۇ ياتاقتا يالغۇزمىدۇ ياكى| ئىمنە رەسەپ بالىدۇ؟ ياپوننىسى ئاللىقاچان نەپەستىن توختىتى.| جەسىتە چىرىپ كېتىشكە باشلىغانمۇ؟| شۇ خىياللار بىلەن كۆڭلىنى بىردەملا ئارام تاپقۇزانلمىغان خەمىت ساقچىلاردىن دەرھال شۇ ياتاققا بىللە بېرىشنى ئۆتۈندى| ئاڭغىچە قىزنى ئۇچۇ بولغانلىقىدىن خەۋەر تاپقان ئايالمۇ ھاسىراپ-ئۆمۈدەپ پالاقشىپ يۈگۈرگەن پېتى، ساقچىخانىغا يېتىپ كەلگەنى.| قايىل بويىچە ئائىلە تەۋەلىرى ساقچىخانىدا قېلىپ، خەۋەر كۈتۈپ، ساقچىلار ئۆزىنى نەق مەيدانغا بېرىپ ئۇچۇر ئىگىلىسى توغرا بولاتتى.| ئەمما ئايال يىغلاپ يالۋۇرۇپ تۇرۇمىغاچقا، ئۇنىڭ بىللە ئېبېرىشىنى لايىق تاپتى.| مېھمانخانىنا مۇلازىمەت ئولىيايىدىكى خادىملار ئەھۋالنى ئۇققاندىكې، ساقچىلارنىڭ قولىدىكى مۇناسىۋەتلىك ئۇقتۇرۇشىقا ئاساسەن ئىشىكنى ئېچىپ بېرىشكە قوشۇلدى.| قادە بويىچە ئالدى بىلەن ئىشىكنى چېكىپ، ئىچىگە كىرىش كىرمەسلىك ھەققىدە ياتاق ئىچىدىكى مۇئامىلىدارلارنىڭ رۇخسىتىنى ئېلىشقا توغرا كېلەتتى.| شۇڭا مۇلازىمەت دىرېكتورى: ئىشىكنى نەچچە قېتىم ئۇرۇپ ئىچىدىن سادا كۈتتى.| ئۈنلۈك ئاۋازى چاقىرىپمۇ باقتى.| ئەمما ياتاقئىچىدىن چىۋىننىڭ ئۇچقۇنىغا چاغلىق بىرەر ئۈنتىگۈچىلەر كېلەر ئەمەس ئىدى.| بۇ قەدەر جىمجىتلىقنىڭ سەۋەبى، ھەممەيلەننى قىزىقتۇرۇپ، يۈرەكلەرنى سۇ قىلىۋېتىپ بارغاندا، بۇنىڭدىن ئارتۇق كۈتۈشكە ئىمكان بولمىدى.| ماسۇل ساقچىنىڭ چۈشىدە جىق| دېگەن بۇيرۇقىدىن كېيىن، مۇلازىمەت زېرىكتورى ياتاق ئىشىكىگە ئاچقۇ سېلىپ ئاچتى.| ئۇلار ياتاققا كىرگەندە، ياتاققىچى يىللىق قىدە پىۋا، قىزىللار بوتۇلكىلىرى بىلەن تولغان بولۇپ، ھەمىيە سېسىقچىلىق ۋەيرانە ھالدا يېمەكلىك قالدۇقلىرى ۋە ئەخلەتلەر بىلەن توشقانىدى.| دېرىزە پەردىلىرى تولۇق چۈشۈرۈلۈپ، ياتاغقىچى قاپ-قاراڭغۇ قىلىۋېتىلگەن بولۇپ، قاپقارا ئۇزۇن چاچلىرى چۇۋۇلۇپ، يۈزلىرىنى توسۇۋالغان ئورۇقلاپ يېدەكچىلىك قالغان بىر قىز يېرىم بەدىنى كارىۋاتتىن يەرگە ساڭگىلىغان پېتىدا قەداق قىلمايتو ياتاتتى.| چاتاقنىڭ چىرىغى ياندۇرۇلۇشى بىلەنلا بۇ ھالدىن ھەممەيلەننىڭ يۈرىكى ئاتى..| ئەڭ بېشىدا خەمىت يېنىدىكىلەرنى ئىتتىرىپ سۈرۈۋېتىپ، ئۇچقاندەك يۈگۈرگىنىچە قىزنىڭ يېنىغا بېرىپ، ئۇنىڭ بېشىنى كۆتۈرۈپ يۈزىگە قارىتى.| تەھەقىقەت ئالدىدىكى بۇ قىز تىللا قىز شۇ ئىدى.| بىزشۇنى بىلمەيدەك ھالدا مەست بولۇپ، زادى قانچىلىك ئىچكەنلىكىنى بىلگىلى بولمايتتى.| ئۇ تۇيۇقسىز يېنىدا بىر توپ كىشىلەرنىڭ پەيدا بولۇپب قاغانلىقىدىن ھەم ئۆيدىكىلەرنىڭ ئۆزىنى ئىزدەپ تەكپارا بولۇپ كەتكەنلىكىكى بىخەۋەر ھالدا شۇ پېتى كارىۋاتتىن ساڭگىلاپ سوزۇلۇپ ياتاتتى| خەلمىتتىن كېيىنلا قىنىڭ ئاپىسى ئىرغاڭلاپ يۈگۈرۈپ ئۇنىڭ يېنىغا پادى-دە، قىزىنى يۆلەپ، ئۇنىڭ ئىسمىنى چاقىرىپ| يۈزلىرىنى سىلاپ- يىغلاشقا باشلىدى| دادادامغا مەن كېرەك ئەمەس، دەپ گېپىنى باشلىدى تالا قىز ھوشىغا كەلگەندىن كېيىن| نېمە ئۈچۈن بۇ يەرگە بېكىننىۋالغانلىقى ھەققىدە سورالغان سوئالغا جاۋاب بېرىپ| ئۇنىڭغا پۇللىرى، يۈز ئابرۇيىيى.| جەمئىيەتتىكى ئورنى ھەم تالادا تۇتۇپ قويغان سانسىز ئاشنىلىرىلا كېرەك.| ئۇ ئەزەلدىن مېنى ئويلاپ باققان ئەسىز؟| بەلكى، ئىنە ساڭىللىرىمنىمۇ ئويلاپ باققان ئەمەس.| ئۇنىڭغا پەقەتتىن ئۆزى بولسىلا، كۆڭۈللۇق خۇشلۇقى، ئۆزى خالىغان ئىشلىرى بولسىلا بولدى.| ھېچقاچان باللىلىرىمنى نېمىنى خالايدۇ، قانداق بولسا خۇشال بولىدۇ، مەن قانداق دادا بولۇشۇم كېرەك دېگەنلەرنى ئويلاپمۇ باقمايدۇ.| دادام ئېھتىياجلىق چېغىدا، مەن ئۇنىڭغا يېنىدا ئىدىم.| ئۇنىڭ بەختلىك بولۇشى ئۈچۈن، ئائىلىسىنىڭ پۈتۈن بولۇشى ئۈچۈن بارلىق كۈچۈمنى چىقارغانىدىم.| لېكىنمەن ئېھتىياجلىق بولغاندا، دادام ئەزەلدىن يېنىمدا مۇ باقمىدى.| كۆڭلۈم يېرىم بولغاندا قولۇمنى تۇتۇپ، يىغلىسام يېشىمنى سىرتىپ باققان ئادەم ئەمەس سۇ.| ئىسىللەپ كۆيگىدىن تارتىپ| دادامنىڭ يۈكىنى يەڭگىنىپتىمەن، دەپ تىرىشىپ: جان-جەھلىم بىلەن ئۆگىنىپ ئىشلەپ كەلدى| ئالىي مەكتەپتە ئو قويۇمەنمىسەن، ئېرىكىنى تاپشۇرۇپ بار دېسەم، ئوخقۇشۇڭلار ھاجىتى يوقتىپ ئوقۇتمىدى.|  قالدىدىڭ، ئەمما مەن چوڭ دۇرغەمدا قىز با نىڭ ئىگىلىككىنى ياراشمايدۇ| مېنىڭ ئو غۇلبالا بولغاندىكىن دىلىكنىڭ ئۇنىڭغا تاپشۇرىمەن. كەييىن چۇساق يەر-يۆلەك بولىدۇ، ھازىر ساغا ئايرىم دۇكان ئېچىپ بېرەيدۇ.| دوپپارچىلىك بىر دۇكاننى تۇتقۇزۇپ قويۇپ بوللۇق| شۇنىڭ بىلەن ئوقۇششاش بانىمۇ يوق بولدى.| مەن بۇلار ئۈچۈن دادامدىن ئاغرىنىپ ئۇخمىدىم| شۇ دۇكاننىڭ پۇلىنى يورغىلىتىپ تېرىپ، تارقالمايدىنىڭ ھەممىسىنى دادامنىڭ چىندىكى سالدى.| باشتادام كارغا كەلگەنلىكىمنى كەتسە، پۇل تاپالايدىغانلىقىمنى دېۋىسە| بابارا بارا دۇكانئىقساددىنى ئۆزۈمدە ئوچىلاق بېرىدىغۇ دەپ ئويلىغانىدىم.| ئەمما ئۇ كېيىنكى ۋاقىتلاردىمۇ ئىزچىل دۇكاننىڭ پايدىسىنى ئۆزىگە تاپشۇرۇشۇمنى ئېيتىپ كەلدى.| ھېسابتا، مەن ئۇنىڭغا بىكارلىق ئىشلەپ رىدىغان مەبىكار بولدۇم.| ئۇ ھەتتا مەن خەجلىگەن| ئىنسانلىرىمغا بەرگەن ئازغىنە پۇلنىڭ ھېسابىنىمۇ مەندىن تولۇقى بىلەن ئالدى.| مەن تېخى ھاياتلىق مۇشۇنداق بولىدىغان ئوخشايدۇ، ئىقتىسادنى مۇشۇنداق تۇتىدىغان ئوخشايدۇ دەپ ئويلاپ كەتتىم.| ئەمما كىيىم بىللىسەم. ئۇ ئاشنىلىرىغا پۇل خەجلىگەندە ئەزەلدىن پۇلنىڭ كۆزىگە قارىماي دىكەن.| ھەتتاييېنەدا بومىسا باشقىلاردىن ئېلىپ، مەندىن ئېلىپ ئاپامغا ساقلاشقا خەجلەشكى بەرگەنلىنىمۇ تاتىلىپ تۇرۇپ ئاشنىلىرىغا خەجلەيدىكەن.| ئاپامنىڭ تۇغۇلغان كۈنىنى ئەزەلدىن ئىسىدا تۇتۇپ باقمىغان.| بالىلىرىنىڭ ھېچقانداق تۇغۇلغان كۈننى، خاتۈر كۈنلىرى يادىدا تۇرمايدىغان دادا| ئاشنىلىرىنىڭ ھېيت-بايراملىرىدىن تەتىپ تۇغۇلغان كۈنلىرىگىچە ھەتتا ئۇلارنىڭ تۇغقانلىرىنىڭ تۇغۇلغان كىلىرىگىچە مەھكەم ئېسىدە ساقلاپ، ھەممىسىنىڭ ئۇرۇققۇنى چۈشۈرۈپ مەي تولۇق ئۆتكۈزۈپ بېرىپ، سوۋغا -سولۇلارنى بېرىپ ماڭىدىكەن.| لېكىن بىز پادىسەك يوقدەي.| ئۆينىڭ چىقىملىرىغىمۇ مەڭلىك تەستە ئىنتايىن قاقشاپ تۇرۇپ كەتبۇ بېبېرىدىكەن| مۇ شۇنچىۋالا پەرقلەرنى بىلگەندىن كېيىپ| دادامدىن، رەنجىپ، خېلىلى قاتتىق رەنجى| شۇنداقتۇر. ئۇ دادام بولۋامدى ئۈچۈن رەنجىگەرلىكىمنى يېلىگە دېيەلمىدىم ياكى ئادانى ساقلاپ قارىماي قويالمايتىم| كېيىن، بۇ تەرەپلىمۇ كۆڭلۈمدىن چىقىرىۋېتىپ رەنجى مەسلا بولدۇم.| چۈنكى ھەرقانچە رەنجىسەممۇ دادامنىڭ بۇ رەنجىشلىرى بىلەن پەرۋايى پەلەك ئىدى.| كېيىن دادام، تاجىمىلىگەن ئايالنى تېپىۋېلىپ ئاپامنى تاشلىۋەتتى.| ئاجرىشىمەن دەپ غەلبە قىلىپ ھەقىقەتەن ئاپامغا تۆتتەڭگە پۇل تۇتقۇزۇپ قويۇپلاجرىشىپ كەتتى.| شۇ چاغدا مەن دادام تەرەپتە تۇرۇپ ئاپامنى بەگىزلىدىم. دادامدىن كەلمىگەن مۇھەببەتنى ئاپامغان مەن يەتكۈزۈپ، مانا مەن شۇ ئائىلىق قايتا بىر پۈتۈنلۈككى ئىگە قىلدىم.| ئەمما دادامنىڭ بۇلار بىلەن ئىككى كارى بولمى..| خۇددى بۇ ئىشلارنى قىلىشىم يوللۇقتەك، بۇ مېنىڭ مەسئۇلى تەندەك| دادام ھەممە ئىشنى ماڭا تاشلاپ قويۇپ، ئۆزى بىر چەتكە چىقىپ تۇرۇۋال.| بوپتۇ دېدىم، ئائىلە بەخلىك بول، مەنمۇ خاتىرجەم بولاتتىم| ئىنسىڭىلىرىمنىڭ كىرىزىدىكى ياخشى قۇرىتى. مەن شۇنىڭ ئۈچۈن تەبىلنى كېشى دېدىم| ئىشلاتىگەن. نۆۋەت مېنىڭ بىربىرنى تېپىشمە سىلىسىگە كەلگەندە، دادام يەنىڭلا ئۆرىزىنىڭ مەنپەلىرىنى لىدى.| ئىسسىننىڭ پرايىنى قوللاش ئۈچۈن خەمىنىڭ مەقتەمنى قۇرنىڭ كۆڭلىغا سېپىلدى.| ئېھ، ئېيقانلارچان| ئۇ نېمىشقا مېنى ياخشى كەلگەن ئادىمىدىن ئايرىيېتەردۇ| جاھاندىكى ھەممەتە ئۆزگەرتىش ئېگى ئادەم بىلەن تويقىسى بولىدىكەن، نېمىشقا بىر مىلەرناممۇنداق ئالامبۇنىڭ يېتى نېمىش| بىر نېمىش؟| تالاقىز ئەنە شۇدانە دەپ ئاپىسىغا ئۆزىنى تاشلاپ بۇكۇداپ يىغلاپ كەتتى.| ئۇنىڭ كۆز ياشلىرى تە يىغىسى ھەممەيلەننىڭ يۈرىكىنى ئېزىۋەتتۇ.| بولۇپمۇ خەمىت كۆزلىرىدىن تائنايلا دەپ قالغان ياشلىرىنى تۇتۇۋېلىش ئۈچۈن شۇنچىلىك زور كۈچ سەرپ قىلىپ ئولتۇراتتىكى، ئەمما يۈرىكىدىكى زەرداپنى بىر ئۆزى بىلەتتى.|  جېنىم بالى بىچارە قىزسەن مېنىڭ؟ شۇنداق بولسىمۇ بىزلەرنى تاشلاپ يەپ باق قالماي ئۆيدىن چىقىپ كەتكەن بارمۇ| نېمە گەپ بوسۇن دېيىشىپ، مەسلىھەتلىشىپ ھەل قىلىمىز ئەمەسمۇمۇ| داداڭدىن رەنجىسەڭ مېنىنى تاشلىۋەتسەڭ قانداق بولىدۇ| جېنىم بالام، مەن ئىزچىل ئەتتەرەپتە| ئۆز بەرگەن مۇشۇ ئىشلارنىڭ ئالدى-كەينىدىنمۇ، مەن داداڭنى قايىل قىلىشى ئۈچۈن شۇنچىلىك تىرئىچتىم.| ھېلىھەممە تىرىشىمەن بالام، جېنىم تېنىمىزلا بولىدىكەن، سېنى ھەرگىز يىغلاتمايمەن| داداڭ دېگەن ئۇ شورى قۇر-غۇر، ئۆزىنىڭ ئۆتكۈزگەن خاتالىقلىرى ئېسىدە يوق، ئەمدى سېنىڭ ئىشىغا كىرىشىۋالدى.| لېكىن مەن بۇ قېتىپ يول قويمايمەن.| تويانقى قولاسىمۇ قورشۇلىدۇ.| قوشۇلمىسىمۇ قوشۇلىدۇ، ئەگەر سائەتلا قوشۇلمايدىغان بولسا| سېنى ئېلىپ ئۇنداق ئۆيدىن چىقىپ كېتىمەن| ئۆمرۈمنىڭ تەڭدىن تولىسىنى ياشاپ بوپتىمەن.| بۇنىڭدىن بۇرۇن ئۇنىڭ گېپىنى ئاڭلاپلا كەلدىم| ئەمدى بۇنىڭدىن كېيىن ئۇنداق تالمايمەن.| بىۋاشلارچە گېپىنى ئاڭلاپ ئولتۇرسام ماڭا كۆرسەتكەن كۈنلىرى شۇ بولدى| سې ئۇنىڭ كېپىنى ئاڭلاپ دېگىنى بوچە قىپ بەرسەم، كىم بولىدۇ، تۆت كۈننە توختاپ، يەنە قايسى بىر جىمپۇچلار بىلەن تېپىشىپ قېلىپ| ئانا-بالا تۆتىمىزنىڭمۇ يۈرىكىنى تاتىلاپ قان قىلىۋېتىدۇ.| كۆڭلۈڭ توقتۇق جېنىم ما، يېنىڭدا مەن بار| بۇ ئىشتا داداڭنىڭ دېگەنلىرىگە ھەرگىز كۆنمەيمىز.| كۆڭلۈم كىمنى خالىغان بولسا، شۇنىڭ بىلەن تۇرمۇش قۇرۇپ بەخقىتىڭنى تاتىسەن.| ئايال ئەنە شۇلارنى دەپ نۆل-مىڭ يىغلىغىنىچە قىزىغا تەسەللى بەر.| ئازابتىن يۈرىكى پۇلىنىپ، نەپەس ئاغىدەك ھالى قالمىغان قىز ئاپىسىنىڭ تەسەللىسىدىن تېخىمۇ ئېزىلىپ، تېخىمۇ ئۆچسۈپ يىغلاپ كەتتى.| ئۇلارنىڭ بۇ ھالىغا قاراپ خەمىمۇ ئۆزىنى تۇتۇۋالالمىغىلى تاس قالدى.| ئۈن خىزمىتىگە قاتناشقۇچىلار، گۈلزار مۇدىيىم بەردى| گۈلنىگار ئوبۇل قاسىم، مۇستەسا ئابدۇرېھىم| ئەنۋەر ئىبراھىم، ئامىنە ئابدۇكېرەم| پەرھا تاش پولات، ئانىنە بەكلى قارلىغاچ| تەڭمۈر تۇرسۇن| "
     ]
    }
   ],
   "source": [
    "from silero_vad import load_silero_vad, read_audio, get_speech_timestamps\n",
    "class TimestampsType(TypedDict):\n",
    "    start: int\n",
    "    end: int\n",
    "\n",
    "THRESHOLD_MS = 2000\n",
    "SAMPLE_RATE = inference.sample_rate\n",
    "\n",
    "def merge_timestamps(timestamps: int, threshold_ms: int, sample_rate: int):\n",
    "    if not timestamps:\n",
    "        return []\n",
    "    threshold_samples = (threshold_ms / 1000) * sample_rate\n",
    "    merged = []\n",
    "    curr_start = timestamps[0]['start']\n",
    "    curr_end = timestamps[0]['end']\n",
    "    \n",
    "    for i in range(1, len(timestamps)):\n",
    "        # 如果当前积攒的长度不到 2 秒，就一直合并到当前的 end 上\n",
    "        if (curr_end - curr_start) < threshold_samples:\n",
    "            curr_end = timestamps[i]['end']\n",
    "        else:\n",
    "            merged.append({'start': curr_start, 'end': curr_end})\n",
    "            curr_start = timestamps[i]['start']\n",
    "            curr_end = timestamps[i]['end']\n",
    "    merged.append({'start': curr_start, 'end': curr_end})\n",
    "    return merged\n",
    "\n",
    "vad_model = load_silero_vad(onnx=False)\n",
    "waveform_vad = inference._load_audio(audio_path=audio_path)\n",
    "print(waveform_vad.shape)\n",
    "speech_timestamps: list[TimestampsType] = get_speech_timestamps(\n",
    "    waveform_vad, \n",
    "    vad_model, \n",
    "    sampling_rate=inference.sample_rate,\n",
    "    threshold=0.5,  # 可以根据需要调整阈值\n",
    "    min_speech_duration_ms=100, # 最小语音持续时间，防止短噪音被误判\n",
    "    min_silence_duration_ms=200, # 最小静音间隔，用于分割语音块\n",
    "    speech_pad_ms=200, # 在语音块前后添加的填充时间\n",
    ")\n",
    "print('speech_timestamps:', speech_timestamps)\n",
    "print('speech_timestamps:', len(speech_timestamps))\n",
    "final_timestamps = merge_timestamps(speech_timestamps, THRESHOLD_MS, SAMPLE_RATE)\n",
    "print(f'VAD原始片段: {len(speech_timestamps)} -> 合并后片段: {len(final_timestamps)}')\n",
    "for ts in final_timestamps:\n",
    "    ts: TimestampsType\n",
    "    start_sample = int(ts['start'])\n",
    "    end_sample = int(ts['end'])\n",
    "\n",
    "    segment_waveform = waveform_vad[:, start_sample:end_sample]\n",
    "    # display(Audio(segment_waveform, rate=inference.sample_rate))\n",
    "\n",
    "    segment_text = inference.transcribe(waveform=segment_waveform)\n",
    "    print(segment_text, end=\"| \", flush=True)\n",
    "cuda.empty_cache()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3671d334",
   "metadata": {},
   "outputs": [],
   "source": [
    "# mel_spec = inference._extract_features(waveform=orginal_waveform)\n",
    "# ################################\n",
    "# from PIL import Image, ImageFilter\n",
    "# import numpy as np\n",
    "# import math\n",
    "\n",
    "# img = (mel_spec.numpy())\n",
    "# print(img.max(), img.min())\n",
    "# img = img + np.abs(img.min())\n",
    "# img = img / img.max()\n",
    "# print(img.max(), img.min())\n",
    "# img = Image.fromarray((img * 255).astype(np.uint8))\n",
    "# shapened = img.filter(ImageFilter.UnsharpMask(radius=1, percent=150, threshold=3))\n",
    "# img.save(\"original_hd.jpg\")\n",
    "# # img.save(\"sharpen_hd.jpg\")\n",
    "\n",
    "# fig, axes = plt.subplots(2, 1, figsize=(30, 12))\n",
    "\n",
    "# axes[0].imshow(\n",
    "#     img,\n",
    "#     cmap=\"gray\",\n",
    "#     origin=\"lower\",\n",
    "#     vmin=0,\n",
    "#     vmax=255,\n",
    "#     aspect=\"auto\"\n",
    "# )\n",
    "# axes[0].set_title(\"Original\")\n",
    "# axes[1].imshow(\n",
    "#     shapened,\n",
    "#     cmap=\"gray\",\n",
    "#     origin=\"lower\",\n",
    "#     vmin=0,\n",
    "#     vmax=255,\n",
    "#     aspect=\"auto\"\n",
    "# )\n",
    "# axes[1].set_title(\"Sharpened\")\n",
    "# plt.tight_layout()\n",
    "# plt.show()\n",
    "\n",
    "# ################################\n",
    "# mel_spec = inference._augment_spec(mel_spec=mel_spec)\n",
    "# print(mel_spec.shape)\n",
    "# mel_spec = mel_spec.unsqueeze(0)\n",
    "# print(mel_spec.shape)\n",
    "# # 创建可视化\n",
    "# fig, axes = plt.subplots(2, 1, figsize=(18, 12))\n",
    "\n",
    "# # 原始波形\n",
    "# axes[0].plot(orginal_waveform[0].numpy())\n",
    "# axes[0].set_title(\"Original waveform\")\n",
    "# axes[0].set_xlabel(\"sample\")\n",
    "# axes[0].set_ylabel(\"Amplitude\")\n",
    "# axes[0].grid(True)\n",
    "\n",
    "# # Mel 频谱图\n",
    "# im = axes[1].imshow(\n",
    "#     mel_spec[0].numpy(),\n",
    "#     cmap='gray',\n",
    "#     aspect='auto',\n",
    "#     origin='lower',\n",
    "#     # vmin=-150,\n",
    "#     # vmax=1,\n",
    "#     extent=[0, orginal_waveform.shape[-1] / inference.sample_rate, 0, 80]\n",
    "# )\n",
    "# axes[1].set_title(\"Log Mel Spectrogram\")\n",
    "# axes[1].set_xlabel(\"Time (second)\")\n",
    "# axes[1].set_ylabel(\"Mels\")\n",
    "# # plt.colorbar(im, ax=axes[1], format='%+2.0f dB')\n",
    "\n",
    "# plt.tight_layout()\n",
    "# plt.show()\n",
    "\n",
    "\n",
    "# print(f\"Mel 频谱图形状: {mel_spec.shape}\")  # (channel, n_mels, time_frames)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "study-asr (3.11.12)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
