import gradio as gr import numpy as np import torch import soundfile as sf import librosa from matplotlib import pyplot as plt from transformers import AutoFeatureExtractor, AutoModelForAudioFrameClassification from recitations_segmenter import segment_recitations, clean_speech_intervals import io from PIL import Image import tempfile import os import zipfile # Setup device and model device = 'cuda' if torch.cuda.is_available() else 'cpu' dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32 print(f"Loading model on {device}...") processor = AutoFeatureExtractor.from_pretrained("obadx/recitation-segmenter-v2") model = AutoModelForAudioFrameClassification.from_pretrained( "obadx/recitation-segmenter-v2", torch_dtype=dtype, device_map=device ) print("Model loaded successfully!") def read_audio(path, sampling_rate=16000): """قراءة ملف صوتي وتحويله""" audio, sr = sf.read(path) if len(audio.shape) > 1: audio = audio.mean(axis=1) if sr != sampling_rate: audio = librosa.resample(audio, orig_sr=sr, target_sr=sampling_rate) return torch.tensor(audio).float() def get_interval(x: np.ndarray, intervals: list[list[int]], idx: int, sr=16000, delta=0.3, exact_boundries=False): """استخراج مقطع صوتي من الفواصل""" start = int((intervals[idx][0] - delta) * sr) end = int(intervals[idx][1] * sr) if not exact_boundries: start = 0 if idx == 0 else int((intervals[idx][0] - delta) * sr) end = len(x) if idx == len(intervals) - 1 else int((intervals[idx + 1][0] - delta) * sr) return x[start: end] def plot_signal(x: np.ndarray, intervals: list[list[float]], log_min_count=5, sr=16000): """رسم الإشارة الصوتية مع الفواصل""" fig, ax = plt.subplots(figsize=(20, 4)) if isinstance(x, torch.Tensor): x = x.numpy() ax.plot(x, linewidth=0.5) intervals_flat = np.array(intervals).reshape(-1) diffs = np.diff(intervals_flat) min_silence_diffs_idx = float('-inf') info_text = "" if len(intervals_flat) > 2: silence_diffs = diffs[1: len(diffs): 2] min_silence_diffs_ids = silence_diffs.argsort()[: log_min_count] min_silence_diffs_idx = min_silence_diffs_ids[0] * 2 + 1 info_text += f'Minimum Silence Interval IDs: {min_silence_diffs_ids}\n' info_text += f'Minimum Silence Intervals: {silence_diffs[min_silence_diffs_ids]}\n' speech_diffs = diffs[0: len(diffs): 2] min_speech_diffs_ids = speech_diffs.argsort()[: log_min_count] info_text += f'Minimum Speech Interval IDs: {min_speech_diffs_ids}\n' info_text += f'Minimum Speech Intervals: {speech_diffs[min_speech_diffs_ids]}\n' ymin = x.min() ymax = x.max() for idx, val in enumerate(intervals_flat): color = 'red' if idx in [min_silence_diffs_idx, min_silence_diffs_idx + 1]: color = 'green' ax.axvline(x=val * sr, ymin=0, ymax=1, color=color, alpha=0.6, linewidth=1) ax.set_xlabel('Samples') ax.set_ylabel('Amplitude') ax.set_title('Audio Signal with Detected Intervals') ax.grid(True, alpha=0.3) plt.tight_layout() buf = io.BytesIO() plt.savefig(buf, format='png', dpi=100, bbox_inches='tight') buf.seek(0) img = Image.open(buf) plt.close() return img, info_text def process_audio(audio_file, min_silence_ms, min_speech_ms, pad_ms): """معالجة الملف الصوتي وتقطيعه""" if audio_file is None: return None, "⚠️ من فضلك ارفع ملف صوتي", None, [] try: # قراءة الملف wav = read_audio(audio_file) # تقسيم التلاوة sampled_outputs = segment_recitations( [wav], model, processor, device=device, dtype=dtype, batch_size=4, ) # تنظيف الفواصل clean_out = clean_speech_intervals( sampled_outputs[0].speech_intervals, sampled_outputs[0].is_complete, min_silence_duration_ms=min_silence_ms, min_speech_duration_ms=min_speech_ms, pad_duration_ms=pad_ms, return_seconds=True, ) intervals = clean_out.clean_speech_intervals # رسم الإشارة plot_img, stats_text = plot_signal(wav, intervals) # استخراج المقاطع الصوتية num_segments = len(intervals) result_text = f"✅ تم التقطيع بنجاح!\n\n" result_text += f"📊 عدد المقاطع: {num_segments}\n" result_text += f"⏱️ طول الملف الأصلي: {len(wav)/16000:.2f} ثانية\n\n" result_text += "=" * 50 + "\n" result_text += stats_text result_text += "=" * 50 + "\n\n" # إنشاء مجلد مؤقت للمقاطع temp_dir = tempfile.mkdtemp() segment_files = [] for idx in range(num_segments): audio_seg = get_interval( x=wav, intervals=intervals, idx=idx, delta=0.050, exact_boundries=True ) if isinstance(audio_seg, torch.Tensor): audio_seg = audio_seg.cpu().numpy() duration = len(audio_seg) / 16000 result_text += f"مقطع {idx + 1}: من {intervals[idx][0]:.2f}s إلى {intervals[idx][1]:.2f}s (المدة: {duration:.2f}s)\n" # حفظ المقطع segment_path = os.path.join(temp_dir, f"segment_{idx+1:03d}.wav") sf.write(segment_path, audio_seg, 16000) segment_files.append(segment_path) # إنشاء ملف ZIP zip_path = os.path.join(temp_dir, "segments.zip") with zipfile.ZipFile(zip_path, 'w') as zipf: for seg_file in segment_files: zipf.write(seg_file, os.path.basename(seg_file)) # إنشاء HTML لعرض المقاطع audio_html = "
" for idx, seg_file in enumerate(segment_files): audio_html += f"""

🎵 مقطع {idx + 1}

""" audio_html += "
" return plot_img, result_text, zip_path, segment_files except Exception as e: return None, f"❌ حدث خطأ: {str(e)}", None, [] # إنشاء واجهة Gradio with gr.Blocks(title="تقطيع التلاوات القرآنية") as demo: gr.Markdown(""" # 🕌 تقطيع التلاوات القرآنية أداة لتقطيع ملفات التلاوات القرآنية تلقائياً باستخدام AI **استخدم Model:** `obadx/recitation-segmenter-v2` """) with gr.Row(): with gr.Column(scale=1): audio_input = gr.Audio( label="📤 ارفع ملف التلاوة", type="filepath" ) with gr.Accordion("⚙️ إعدادات التقطيع", open=True): min_silence = gr.Slider( minimum=10, maximum=500, value=30, step=10, label="أقل مدة للسكوت (ميلي ثانية)" ) min_speech = gr.Slider( minimum=10, maximum=500, value=30, step=10, label="أقل مدة للكلام (ميلي ثانية)" ) padding = gr.Slider( minimum=0, maximum=200, value=30, step=10, label="Padding (ميلي ثانية)" ) process_btn = gr.Button("🚀 ابدأ التقطيع", variant="primary", size="lg") with gr.Column(scale=2): plot_output = gr.Image(label="📈 الإشارة الصوتية") result_text = gr.Textbox( label="📋 النتائج", lines=15, max_lines=20 ) gr.Markdown("### 💾 تحميل المقاطع") zip_download = gr.File(label="📦 حمل كل المقاطع (ZIP)") gr.Markdown("### 🎵 استماع للمقاطع") # عرض المقاطع الصوتية segment_outputs = [] for i in range(50): # حد أقصى 50 مقطع audio_out = gr.Audio(label=f"مقطع {i+1}", visible=False) segment_outputs.append(audio_out) def process_and_show(audio, min_sil, min_sp, pad): plot, text, zip_file, segments = process_audio(audio, min_sil, min_sp, pad) outputs = [plot, text, zip_file] # إظهار المقاطع for i in range(50): if i < len(segments): outputs.append(gr.Audio(value=segments[i], visible=True, label=f"مقطع {i+1}")) else: outputs.append(gr.Audio(visible=False)) return outputs process_btn.click( fn=process_and_show, inputs=[audio_input, min_silence, min_speech, padding], outputs=[plot_output, result_text, zip_download] + segment_outputs ) gr.Markdown(""" --- ### 💡 معلومات - الأداة تستخدم نموذج AI مدرب خصيصاً لتقطيع التلاوات القرآنية - يتم اكتشاف فترات الكلام والسكوت تلقائياً - يمكنك تحميل كل المقاطع دفعة واحدة من ملف ZIP - أو الاستماع لكل مقطع على حدة """) if __name__ == "__main__": demo.launch()