Spaces:
Sleeping
Sleeping
import gradio as gr | |
import whisper | |
from moviepy.editor import VideoFileClip, AudioFileClip, ImageClip, CompositeVideoClip | |
import tempfile | |
import os | |
from pathlib import Path | |
import numpy as np | |
from PIL import Image, ImageDraw, ImageFont | |
import arabic_reshaper | |
from bidi.algorithm import get_display | |
from pydub import AudioSegment | |
# تحميل نموذج Whisper | |
model = whisper.load_model("base") # نموذج أفضل للغة العربية | |
# تحديد الخطوط المتاحة | |
FONTS = { | |
"الخط المثنى": "fonts/Al-Mothnna.ttf", | |
"خط بين": "fonts/bein-normal.ttf" | |
} | |
def prepare_arabic_text(text): | |
"""تحضير النص العربي للعرض بشكل صحيح""" | |
try: | |
reshaped_text = arabic_reshaper.reshape(text) | |
bidi_text = get_display(reshaped_text) | |
return bidi_text | |
except Exception as e: | |
print(f"خطأ في معالجة النص العربي: {str(e)}") | |
return text | |
def create_text_image(text, font_name, size=(720, 480), font_size=30): | |
"""إنشاء صورة تحتوي على نص""" | |
# إنشاء صورة جديدة مع خلفية سوداء | |
img = Image.new('RGB', size, 'black') | |
draw = ImageDraw.Draw(img) | |
# تحميل الخط المحدد | |
try: | |
font_path = FONTS[font_name] | |
if not os.path.exists(font_path): | |
raise FileNotFoundError(f"الخط غير موجود: {font_path}") | |
font = ImageFont.truetype(font_path, font_size) | |
except Exception as e: | |
print(f"خطأ في تحميل الخط: {str(e)}") | |
# استخدام الخط الافتراضي في حالة الفشل | |
font = ImageFont.load_default() | |
# تحضير النص العربي | |
prepared_text = prepare_arabic_text(text) | |
# حساب موقع النص في وسط الصورة | |
text_bbox = draw.textbbox((0, 0), prepared_text, font=font) | |
text_width = text_bbox[2] - text_bbox[0] | |
text_height = text_bbox[3] - text_bbox[1] | |
x = (size[0] - text_width) // 2 | |
y = (size[1] - text_height) // 2 | |
# رسم النص | |
draw.text((x, y), prepared_text, font=font, fill='white') | |
return np.array(img) | |
def convert_audio_to_wav(audio_path): | |
"""تحويل الملف الصوتي إلى صيغة WAV""" | |
audio_path = Path(audio_path) | |
if audio_path.suffix.lower() != '.wav': | |
wav_path = audio_path.with_suffix('.wav') | |
audio = AudioSegment.from_file(str(audio_path)) | |
audio.export(str(wav_path), format='wav') | |
return str(wav_path) | |
return str(audio_path) | |
def create_video_with_text(audio_path, transcription, font_name, video_size): | |
"""إنشاء فيديو مع نص متزامن""" | |
try: | |
# تحويل الملف الصوتي إلى WAV | |
wav_path = convert_audio_to_wav(audio_path) | |
# تحميل الملف الصوتي | |
audio_clip = AudioFileClip(wav_path) | |
duration = audio_clip.duration | |
# تقسيم النص إلى أجزاء | |
words = transcription.split() | |
total_frames = int(duration * 24) # 24 FPS | |
words_per_frame = max(1, len(words) // total_frames) | |
# إنشاء قائمة المقاطع | |
clips = [] | |
current_time = 0 | |
frame_duration = duration / (len(words) / words_per_frame) | |
for i in range(0, len(words), words_per_frame): | |
# تجميع الكلمات لهذا الإطار | |
frame_words = ' '.join(words[i:i + words_per_frame]) | |
# إنشاء صورة للنص | |
text_image = create_text_image(frame_words, font_name, size=video_size) | |
# تحويل الصورة إلى مقطع | |
text_clip = ImageClip(text_image).set_duration(frame_duration) | |
# إضافة المقطع مع توقيته | |
clips.append(text_clip.set_start(current_time)) | |
current_time += frame_duration | |
if not clips: | |
raise ValueError("لم يتم إنشاء أي مقاطع") | |
# دمج جميع المقاطع | |
video = CompositeVideoClip(clips, size=video_size) | |
video = video.set_audio(audio_clip) | |
# حفظ الفيديو | |
temp_output = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') | |
video_path = temp_output.name | |
video.write_videofile( | |
video_path, | |
fps=24, | |
codec='libx264', | |
audio_codec='aac', | |
verbose=False | |
) | |
return video_path | |
except Exception as e: | |
print(f"خطأ في create_video_with_text: {str(e)}") | |
raise | |
def process_audio(audio_path, selected_font, video_orientation): | |
"""معالجة الملف الصوتي وإنشاء الفيديو""" | |
try: | |
if not isinstance(audio_path, str): | |
raise ValueError("يجب أن يكون المدخل مسار ملف صوتي") | |
if not os.path.exists(audio_path): | |
raise FileNotFoundError("الملف غير موجود") | |
# تحويل الصوت إلى نص | |
print("جاري تحويل الصوت إلى نص...") | |
result = model.transcribe(audio_path, language="ar") # تحديد اللغة العربية | |
transcription = result["text"] | |
print("تم استخراج النص بنجاح") | |
# تحديد حجم الفيديو بناءً على الاختيار | |
video_size = (1280, 720) if video_orientation == 'أفقي' else (720, 1280) | |
# إنشاء الفيديو | |
print("جاري إنشاء الفيديو...") | |
video_path = create_video_with_text(audio_path, transcription, selected_font, video_size) | |
print("تم إنشاء الفيديو بنجاح") | |
return video_path, transcription | |
except Exception as e: | |
print(f"خطأ في process_audio: {str(e)}") | |
return None, f"حدث خطأ أثناء المعالجة: {str(e)}" | |
# إنشاء واجهة Gradio | |
iface = gr.Interface( | |
fn=process_audio, | |
inputs=[ | |
gr.Audio(type="filepath", label="قم بتحميل ملف صوتي (MP3 أو WAV)"), | |
gr.Dropdown( | |
choices=list(FONTS.keys()), | |
value=list(FONTS.keys())[0], | |
label="اختر الخط" | |
), | |
gr.Dropdown( | |
choices=["أفقي", "عمودي"], | |
value="أفقي", | |
label="اختيار اتجاه الفيديو" | |
) | |
], | |
outputs=[ | |
gr.Video(label="الفيديو المنشأ"), | |
gr.Textbox(label="النص المستخرج") | |
], | |
title="محول الصوت إلى فيديو مع النص", | |
description="قم بتحميل ملف صوتي لإنشاء فيديو مع نص متزامن باللغة العربية.", | |
) | |
if __name__ == "__main__": | |
# التحقق من وجود مجلد الخطوط | |
if not os.path.exists("fonts"): | |
print("تحذير: مجلد الخطوط غير موجود") | |
os.makedirs("fonts") | |
# التحقق من وجود الخطوط | |
for font_name, font_path in FONTS.items(): | |
if not os.path.exists(font_path): | |
print(f"تحذير: الخط {font_name} غير موجود في {font_path}") | |
iface.launch() | |