Spaces:
Sleeping
Sleeping
| import os | |
| os.environ["IMAGE_MAGICK_BINARY"] = "/usr/bin/convert" | |
| import gradio as gr | |
| import pandas as pd | |
| import re | |
| from faster_whisper import WhisperModel | |
| from moviepy import VideoFileClip, TextClip, CompositeVideoClip | |
| from arabic_reshaper import reshape | |
| # --- الإعدادات --- | |
| model = WhisperModel("large-v3", device="cpu", compute_type="int8") | |
| def process_arabic_text(text): | |
| if not text: return "" | |
| # 1. إعادة تشكيل الحروف العربية لتظهر متصلة وصحيحة (بدون النقاط الإضافية •) | |
| reshaped = reshape(text) | |
| # 2. إضافة سطر فارغ في الأسفل لمنع قص النقاط السفلية للأحرف | |
| return reshaped + "\n " | |
| def clean_color(color_str): | |
| if color_str.startswith('rgba'): | |
| nums = re.findall(r"\d+\.?\d*", color_str) | |
| if len(nums) >= 3: | |
| r, g, b = int(float(nums[0])), int(float(nums[1])), int(float(nums[2])) | |
| return f'rgb({r},{g},{b})' | |
| return color_str | |
| def step_1_extract_words(video_path, progress=gr.Progress()): | |
| if not video_path: return None, "الرجاء رفع فيديو." | |
| segments, _ = model.transcribe(video_path, word_timestamps=True, language="ar") | |
| words_data = [] | |
| for segment in segments: | |
| for word in segment.words: | |
| words_data.append([word.word.strip(), round(word.start, 2), round(word.end, 2)]) | |
| return pd.DataFrame(words_data, columns=["الكلمة", "البداية", "النهاية"]), "تم استخراج الكلمات!" | |
| def step_2_render_video(video_path, df_edited, font_selection, text_color, font_size, progress=gr.Progress()): | |
| if video_path is None or df_edited is None: return None, "بيانات ناقصة." | |
| safe_color = clean_color(text_color) | |
| actual_font = font_selection if os.path.exists(font_selection) else "DejaVu-Sans-Bold" | |
| output_path = "final_clean_text_video.mp4" | |
| video = VideoFileClip(video_path) | |
| w, h = video.size | |
| clips = [video] | |
| words_list = df_edited.values.tolist() | |
| for row in words_list: | |
| word_text = str(row[0]) | |
| t_start = float(row[1]) | |
| t_end = float(row[2]) | |
| if not word_text.strip(): continue | |
| clean_word = process_arabic_text(word_text) | |
| txt = TextClip( | |
| text=clean_word, | |
| font_size=int(font_size), | |
| color=safe_color, | |
| stroke_color='black', | |
| stroke_width=2.0, # تقليل سمك التحديد قليلاً ليتناسب مع النص الأصغر | |
| font=actual_font, | |
| method='label' | |
| ).with_start(t_start).with_duration(max(0.1, t_end - t_start)).with_position(('center', int(h * 0.5))) | |
| clips.append(txt) | |
| final = CompositeVideoClip(clips, size=(w, h)) | |
| final.write_videofile(output_path, codec="libx264", audio_codec="aac", fps=video.fps, logger='bar') | |
| return output_path, "تم إنتاج الفيديو بنجاح!" | |
| # --- الواجهة --- | |
| with gr.Blocks() as app: | |
| gr.Markdown("## 🎬 محرر الفيديو: نصوص نظيفة") | |
| with gr.Row(): | |
| v_in = gr.Video(); v_out = gr.Video() | |
| with gr.Row(): | |
| font_opt = gr.Dropdown(choices=["arialbd.ttf"], value="arialbd.ttf", label="الخط") | |
| color_opt = gr.ColorPicker(value="#FF8C00", label="لون ذهبي برتقالي") | |
| # تم تصغير الحجم الافتراضي من 130 إلى 90 | |
| size_opt = gr.Slider(30, 200, value=90, label="حجم النص") | |
| btn_1 = gr.Button("1. تحليل الكلمات"); table = gr.Dataframe(interactive=True) | |
| btn_2 = gr.Button("2. إنتاج الفيديو"); status = gr.Textbox() | |
| btn_1.click(step_1_extract_words, [v_in], [table, status]) | |
| btn_2.click(step_2_render_video, [v_in, table, font_opt, color_opt, size_opt], [v_out, status]) | |
| app.launch() | |