Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -9,61 +9,122 @@ from PIL import Image, ImageDraw, ImageFont
|
|
9 |
import arabic_reshaper
|
10 |
from bidi.algorithm import get_display
|
11 |
from pydub import AudioSegment
|
|
|
12 |
|
13 |
-
# تحميل نموذج Whisper
|
14 |
-
model = whisper.load_model("
|
15 |
|
16 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
"""تحضير النص العربي للعرض بشكل صحيح"""
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
21 |
|
22 |
-
def create_text_image(text, size=(720, 480),
|
|
|
|
|
23 |
"""إنشاء صورة تحتوي على نص"""
|
24 |
-
#
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
26 |
draw = ImageDraw.Draw(img)
|
27 |
|
28 |
try:
|
29 |
-
# محاولة تحميل الخط
|
30 |
-
font = ImageFont.truetype(
|
31 |
-
except:
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
except:
|
36 |
-
# استخدام الخط الافتراضي إذا فشلت المحاولات السابقة
|
37 |
-
font = ImageFont.load_default()
|
38 |
|
39 |
-
# تحضير النص
|
40 |
if any('\u0600' <= c <= '\u06FF' for c in text):
|
41 |
-
text = prepare_arabic_text(text)
|
42 |
|
43 |
-
# حساب
|
44 |
text_bbox = draw.textbbox((0, 0), text, font=font)
|
45 |
text_width = text_bbox[2] - text_bbox[0]
|
46 |
text_height = text_bbox[3] - text_bbox[1]
|
47 |
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
# رسم النص
|
52 |
-
draw.text((x, y), text, font=font, fill=
|
53 |
|
54 |
return np.array(img)
|
55 |
|
56 |
-
def
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
|
66 |
-
def create_video_with_text(audio_path, transcription
|
|
|
67 |
"""إنشاء فيديو مع نص متزامن"""
|
68 |
try:
|
69 |
# تحويل الملف الصوتي إلى WAV
|
@@ -73,29 +134,33 @@ def create_video_with_text(audio_path, transcription):
|
|
73 |
audio_clip = AudioFileClip(wav_path)
|
74 |
duration = audio_clip.duration
|
75 |
|
76 |
-
# تقسيم النص إلى
|
77 |
-
|
78 |
-
|
79 |
-
|
|
|
|
|
|
|
80 |
|
81 |
# إنشاء قائمة المقاطع
|
82 |
clips = []
|
83 |
current_time = 0
|
84 |
-
frame_duration = duration / (len(words) / words_per_frame)
|
85 |
|
86 |
-
for
|
87 |
-
# تجميع الكلمات لهذا الإطار
|
88 |
-
frame_words = ' '.join(words[i:i + words_per_frame])
|
89 |
-
|
90 |
# إنشاء صورة للنص
|
91 |
-
text_image = create_text_image(
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
# تحويل الصورة إلى مقطع
|
94 |
-
text_clip = ImageClip(text_image).set_duration(
|
95 |
|
96 |
# إضافة المقطع مع توقيته
|
97 |
clips.append(text_clip.set_start(current_time))
|
98 |
-
current_time +=
|
99 |
|
100 |
# دمج جميع المقاطع
|
101 |
video = CompositeVideoClip(clips, size=(720, 480))
|
@@ -119,43 +184,43 @@ def create_video_with_text(audio_path, transcription):
|
|
119 |
print(f"خطأ في create_video_with_text: {str(e)}")
|
120 |
raise
|
121 |
|
122 |
-
def
|
123 |
-
"""
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
# تحويل الصوت إلى نص
|
132 |
-
print("جاري تحويل الصوت إلى نص...")
|
133 |
-
result = model.transcribe(audio_path)
|
134 |
-
transcription = result["text"]
|
135 |
-
print("تم استخراج النص بنجاح")
|
136 |
-
|
137 |
-
# إنشاء الفيديو
|
138 |
-
print("جاري إنشاء الفيديو...")
|
139 |
-
video_path = create_video_with_text(audio_path, transcription)
|
140 |
-
print("تم إنشاء الفيديو بنجاح")
|
141 |
-
|
142 |
-
return video_path, transcription
|
143 |
-
except Exception as e:
|
144 |
-
print(f"خطأ في process_audio: {str(e)}")
|
145 |
-
return None, f"حدث خطأ أثناء المعالجة: {str(e)}"
|
146 |
|
147 |
# إنشاء واجهة Gradio
|
|
|
|
|
148 |
iface = gr.Interface(
|
149 |
fn=process_audio,
|
150 |
inputs=[
|
151 |
-
gr.Audio(type="filepath", label="قم بتحميل ملف صوتي (MP3 أو WAV)")
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
],
|
153 |
outputs=[
|
154 |
gr.Video(label="الفيديو المنشأ"),
|
155 |
gr.Textbox(label="النص المستخرج")
|
156 |
],
|
157 |
title="محول الصوت إلى فيديو مع النص",
|
158 |
-
description="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
examples=[],
|
160 |
cache_examples=False
|
161 |
)
|
|
|
9 |
import arabic_reshaper
|
10 |
from bidi.algorithm import get_display
|
11 |
from pydub import AudioSegment
|
12 |
+
import glob
|
13 |
|
14 |
+
# تحميل نموذج Whisper - استخدام النموذج المتوسط لدقة أفضل في اللغة العربية
|
15 |
+
model = whisper.load_model("medium")
|
16 |
|
17 |
+
def get_available_fonts():
|
18 |
+
"""الحصول على قائمة الخطوط المتاحة من مجلد fonts"""
|
19 |
+
fonts_dir = "fonts"
|
20 |
+
if not os.path.exists(fonts_dir):
|
21 |
+
os.makedirs(fonts_dir)
|
22 |
+
|
23 |
+
font_files = glob.glob(os.path.join(fonts_dir, "*.ttf")) + \
|
24 |
+
glob.glob(os.path.join(fonts_dir, "*.TTF")) + \
|
25 |
+
glob.glob(os.path.join(fonts_dir, "*.otf")) + \
|
26 |
+
glob.glob(os.path.join(fonts_dir, "*.OTF"))
|
27 |
+
|
28 |
+
return [os.path.basename(f) for f in font_files]
|
29 |
+
|
30 |
+
def prepare_arabic_text(text, font_path):
|
31 |
"""تحضير النص العربي للعرض بشكل صحيح"""
|
32 |
+
try:
|
33 |
+
reshaped_text = arabic_reshaper.reshape(text)
|
34 |
+
bidi_text = get_display(reshaped_text)
|
35 |
+
return bidi_text
|
36 |
+
except Exception as e:
|
37 |
+
print(f"خطأ في معالجة النص العربي: {str(e)}")
|
38 |
+
return text
|
39 |
|
40 |
+
def create_text_image(text, size=(720, 480), font_path="fonts/bein-normal.ttf",
|
41 |
+
font_size=30, text_color='white', background_color='black',
|
42 |
+
background_image=None, text_position='center'):
|
43 |
"""إنشاء صورة تحتوي على نص"""
|
44 |
+
# استخدام الصورة الخلفية إذا كانت موجودة
|
45 |
+
if background_image:
|
46 |
+
img = Image.open(background_image).convert('RGB')
|
47 |
+
img = img.resize(size)
|
48 |
+
else:
|
49 |
+
img = Image.new('RGB', size, background_color)
|
50 |
+
|
51 |
draw = ImageDraw.Draw(img)
|
52 |
|
53 |
try:
|
54 |
+
# محاولة تحميل الخط المحدد
|
55 |
+
font = ImageFont.truetype(font_path, font_size)
|
56 |
+
except Exception as e:
|
57 |
+
print(f"خطأ في تحميل الخط {font_path}: {str(e)}")
|
58 |
+
# استخدام الخط الافتراضي
|
59 |
+
font = ImageFont.load_default()
|
|
|
|
|
|
|
60 |
|
61 |
+
# تحضير النص
|
62 |
if any('\u0600' <= c <= '\u06FF' for c in text):
|
63 |
+
text = prepare_arabic_text(text, font_path)
|
64 |
|
65 |
+
# حساب أبعاد النص
|
66 |
text_bbox = draw.textbbox((0, 0), text, font=font)
|
67 |
text_width = text_bbox[2] - text_bbox[0]
|
68 |
text_height = text_bbox[3] - text_bbox[1]
|
69 |
|
70 |
+
# تحديد موقع النص
|
71 |
+
if text_position == 'center':
|
72 |
+
x = (size[0] - text_width) // 2
|
73 |
+
y = (size[1] - text_height) // 2
|
74 |
+
elif text_position == 'bottom':
|
75 |
+
x = (size[0] - text_width) // 2
|
76 |
+
y = size[1] - text_height - 20
|
77 |
+
elif text_position == 'top':
|
78 |
+
x = (size[0] - text_width) // 2
|
79 |
+
y = 20
|
80 |
+
|
81 |
+
# إضافة خلفية شبه شفافة للنص
|
82 |
+
padding = 10
|
83 |
+
background_box = [
|
84 |
+
x - padding,
|
85 |
+
y - padding,
|
86 |
+
x + text_width + padding,
|
87 |
+
y + text_height + padding
|
88 |
+
]
|
89 |
+
draw.rectangle(background_box, fill=(0, 0, 0, 128))
|
90 |
|
91 |
# رسم النص
|
92 |
+
draw.text((x, y), text, font=font, fill=text_color)
|
93 |
|
94 |
return np.array(img)
|
95 |
|
96 |
+
def process_audio(audio_path, font_name="bein-normal.ttf", background_image=None,
|
97 |
+
text_position='center', editable_text=None):
|
98 |
+
"""معالجة الملف الصوتي وإنشاء الفيديو"""
|
99 |
+
try:
|
100 |
+
if not isinstance(audio_path, str):
|
101 |
+
raise ValueError("يجب أن يكون المدخل مسار ملف صوتي")
|
102 |
+
|
103 |
+
if not os.path.exists(audio_path):
|
104 |
+
raise FileNotFoundError("الملف غير موجود")
|
105 |
+
|
106 |
+
# تحويل الصوت إلى نص
|
107 |
+
print("جاري تحويل الصوت إلى نص...")
|
108 |
+
result = model.transcribe(audio_path, language='ar') # تحديد اللغة العربية
|
109 |
+
|
110 |
+
# استخدام النص المعدل إذا تم توفيره
|
111 |
+
transcription = editable_text if editable_text else result["text"]
|
112 |
+
print("تم استخراج النص بنجاح")
|
113 |
+
|
114 |
+
# إنشاء الفيديو
|
115 |
+
print("جاري إنشاء الفيديو...")
|
116 |
+
font_path = os.path.join("fonts", font_name)
|
117 |
+
video_path = create_video_with_text(audio_path, transcription, font_path,
|
118 |
+
background_image, text_position)
|
119 |
+
print("تم إنشاء الفيديو بنجاح")
|
120 |
+
|
121 |
+
return video_path, transcription
|
122 |
+
except Exception as e:
|
123 |
+
print(f"خطأ في process_audio: {str(e)}")
|
124 |
+
return None, f"حدث خطأ أثناء المعالجة: {str(e)}"
|
125 |
|
126 |
+
def create_video_with_text(audio_path, transcription, font_path, background_image=None,
|
127 |
+
text_position='center'):
|
128 |
"""إنشاء فيديو مع نص متزامن"""
|
129 |
try:
|
130 |
# تحويل الملف الصوتي إلى WAV
|
|
|
134 |
audio_clip = AudioFileClip(wav_path)
|
135 |
duration = audio_clip.duration
|
136 |
|
137 |
+
# تقسيم النص إلى جمل
|
138 |
+
sentences = [s.strip() for s in transcription.split('.') if s.strip()]
|
139 |
+
if not sentences:
|
140 |
+
sentences = [transcription]
|
141 |
+
|
142 |
+
# حساب مدة كل جملة
|
143 |
+
sentence_duration = duration / len(sentences)
|
144 |
|
145 |
# إنشاء قائمة المقاطع
|
146 |
clips = []
|
147 |
current_time = 0
|
|
|
148 |
|
149 |
+
for sentence in sentences:
|
|
|
|
|
|
|
150 |
# إنشاء صورة للنص
|
151 |
+
text_image = create_text_image(
|
152 |
+
sentence,
|
153 |
+
font_path=font_path,
|
154 |
+
background_image=background_image,
|
155 |
+
text_position=text_position
|
156 |
+
)
|
157 |
|
158 |
# تحويل الصورة إلى مقطع
|
159 |
+
text_clip = ImageClip(text_image).set_duration(sentence_duration)
|
160 |
|
161 |
# إضافة المقطع مع توقيته
|
162 |
clips.append(text_clip.set_start(current_time))
|
163 |
+
current_time += sentence_duration
|
164 |
|
165 |
# دمج جميع المقاطع
|
166 |
video = CompositeVideoClip(clips, size=(720, 480))
|
|
|
184 |
print(f"خطأ في create_video_with_text: {str(e)}")
|
185 |
raise
|
186 |
|
187 |
+
def convert_audio_to_wav(audio_path):
|
188 |
+
"""تحويل الملف الصوتي إلى صيغة WAV"""
|
189 |
+
audio_path = Path(audio_path)
|
190 |
+
if audio_path.suffix.lower() != '.wav':
|
191 |
+
wav_path = audio_path.with_suffix('.wav')
|
192 |
+
audio = AudioSegment.from_file(str(audio_path))
|
193 |
+
audio.export(str(wav_path), format='wav')
|
194 |
+
return str(wav_path)
|
195 |
+
return str(audio_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
|
197 |
# إنشاء واجهة Gradio
|
198 |
+
available_fonts = get_available_fonts()
|
199 |
+
|
200 |
iface = gr.Interface(
|
201 |
fn=process_audio,
|
202 |
inputs=[
|
203 |
+
gr.Audio(type="filepath", label="قم بتحميل ملف صوتي (MP3 أو WAV)"),
|
204 |
+
gr.Dropdown(choices=available_fonts, value=available_fonts[0] if available_fonts else None,
|
205 |
+
label="اختر الخط"),
|
206 |
+
gr.Image(label="صورة الخلفية (اختياري)", type="filepath"),
|
207 |
+
gr.Radio(["top", "center", "bottom"], value="center",
|
208 |
+
label="موقع النص", info="اختر موقع النص في الفيديو"),
|
209 |
+
gr.Textbox(label="تعديل النص (اختياري)", placeholder="اترك فارغاً لاستخدام النص المستخرج تلقائياً")
|
210 |
],
|
211 |
outputs=[
|
212 |
gr.Video(label="الفيديو المنشأ"),
|
213 |
gr.Textbox(label="النص المستخرج")
|
214 |
],
|
215 |
title="محول الصوت إلى فيديو مع النص",
|
216 |
+
description="""
|
217 |
+
قم بتحميل ملف صوتي لإنشاء فيديو مع نص متزامن.
|
218 |
+
- يدعم اللغة العربية بشكل كامل
|
219 |
+
- يمكنك اختيار الخط المناسب
|
220 |
+
- يمكنك إضافة صورة خلفية
|
221 |
+
- يمكنك تحديد موقع النص
|
222 |
+
- يمكنك تعديل النص المستخرج
|
223 |
+
""",
|
224 |
examples=[],
|
225 |
cache_examples=False
|
226 |
)
|