v3 / modules /voice_narration /voice_over_system.py
EGYADMIN's picture
Upload 115 files
82676b8 verified
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
وحدة الترجمة الصوتية متعددة اللغات لتفاصيل المشروع
تتيح هذه الوحدة تحويل محتوى المشروع النصي إلى مقاطع صوتية بلغات متعددة للتسهيل على المستخدمين
"""
import os
import sys
import streamlit as st
import pandas as pd
import numpy as np
import json
import base64
import tempfile
import time
import datetime
import logging
from typing import List, Dict, Any, Tuple, Optional, Union
import io
from io import BytesIO
import re
# إضافة مسار النظام للوصول للملفات المشتركة
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
# استيراد مكونات واجهة المستخدم
from utils.components.header import render_header
from utils.components.credits import render_credits
from utils.helpers import format_number, format_currency, styled_button
class VoiceOverSystem:
"""فئة نظام الترجمة الصوتية متعددة اللغات"""
def __init__(self):
"""تهيئة نظام الترجمة الصوتية"""
# تهيئة مجلدات حفظ البيانات
self.data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../data/voice_narration"))
os.makedirs(self.data_dir, exist_ok=True)
# إعداد الكاش المحلي للملفات الصوتية
self.cache_dir = os.path.join(self.data_dir, "cache")
os.makedirs(self.cache_dir, exist_ok=True)
# تهيئة سجل الأصوات المخزنة في الكاش
self.cache_index_file = os.path.join(self.data_dir, "voice_cache_index.json")
self.cache_index = self._load_cache_index()
# تعيين اللغات المدعومة
self.supported_languages = {
"ar": "العربية",
"en": "الإنجليزية",
"fr": "الفرنسية",
"es": "الإسبانية",
"de": "الألمانية",
"it": "الإيطالية",
"zh": "الصينية",
"ja": "اليابانية",
"ru": "الروسية",
"tr": "التركية"
}
# تعيين الأصوات لكل لغة
self.voices_by_language = {
"ar": [
{"id": "ar-female-1", "name": "فاطمة", "gender": "أنثى"},
{"id": "ar-male-1", "name": "محمد", "gender": "ذكر"},
{"id": "ar-female-2", "name": "نور", "gender": "أنثى"},
{"id": "ar-male-2", "name": "أحمد", "gender": "ذكر"}
],
"en": [
{"id": "en-female-1", "name": "Sarah", "gender": "أنثى"},
{"id": "en-male-1", "name": "John", "gender": "ذكر"},
{"id": "en-female-2", "name": "Emily", "gender": "أنثى"},
{"id": "en-male-2", "name": "Robert", "gender": "ذكر"}
],
"fr": [
{"id": "fr-female-1", "name": "Marie", "gender": "أنثى"},
{"id": "fr-male-1", "name": "Jean", "gender": "ذكر"}
],
"es": [
{"id": "es-female-1", "name": "Maria", "gender": "أنثى"},
{"id": "es-male-1", "name": "Carlos", "gender": "ذكر"}
],
"de": [
{"id": "de-female-1", "name": "Hannah", "gender": "أنثى"},
{"id": "de-male-1", "name": "Max", "gender": "ذكر"}
],
"it": [
{"id": "it-female-1", "name": "Sofia", "gender": "أنثى"},
{"id": "it-male-1", "name": "Marco", "gender": "ذكر"}
],
"zh": [
{"id": "zh-female-1", "name": "Li Wei", "gender": "أنثى"},
{"id": "zh-male-1", "name": "Zhang Wei", "gender": "ذكر"}
],
"ja": [
{"id": "ja-female-1", "name": "Yuki", "gender": "أنثى"},
{"id": "ja-male-1", "name": "Hiroshi", "gender": "ذكر"}
],
"ru": [
{"id": "ru-female-1", "name": "Olga", "gender": "أنثى"},
{"id": "ru-male-1", "name": "Ivan", "gender": "ذكر"}
],
"tr": [
{"id": "tr-female-1", "name": "Ayşe", "gender": "أنثى"},
{"id": "tr-male-1", "name": "Mehmet", "gender": "ذكر"}
]
}
# إعدادات الصوت الافتراضية
if "voice_settings" not in st.session_state:
st.session_state.voice_settings = {
"primary_language": "ar",
"secondary_language": "en",
"primary_voice": "ar-female-1",
"secondary_voice": "en-female-1",
"speaking_rate": 1.0,
"pitch": 0.0,
"auto_translate": True,
"include_subtitles": True,
"emphasis_keywords": True
}
# تحميل تاريخ التحويلات الصوتية
self.voice_history_file = os.path.join(self.data_dir, "voice_history.json")
self.voice_history = self._load_voice_history()
# تسجيل الأحداث
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(self.data_dir, "voice_narration.log")),
logging.StreamHandler()
]
)
self.logger = logging.getLogger("voice_over_system")
def render(self):
"""عرض واجهة نظام الترجمة الصوتية متعددة اللغات"""
render_header("نظام الترجمة الصوتية متعددة اللغات")
# تبويبات الوحدة
tabs = st.tabs([
"إنشاء ترجمة صوتية",
"مدير الترجمات الصوتية",
"إعدادات الصوت",
"ترجمة مستندات كاملة",
"إحصائيات ومقاييس"
])
# تبويب إنشاء ترجمة صوتية
with tabs[0]:
self._render_create_voice_over()
# تبويب مدير الترجمات الصوتية
with tabs[1]:
self._render_voice_over_manager()
# تبويب إعدادات الصوت
with tabs[2]:
self._render_voice_settings()
# تبويب ترجمة مستندات كاملة
with tabs[3]:
self._render_document_narration()
# تبويب إحصائيات ومقاييس
with tabs[4]:
self._render_voice_over_analytics()
# عرض حقوق النشر
render_credits()
def _render_create_voice_over(self):
"""عرض واجهة إنشاء ترجمة صوتية"""
st.markdown("""
<div class='custom-box info-box'>
<h3>🎙️ إنشاء ترجمة صوتية</h3>
<p>إنشاء ترجمة صوتية لنص معين بلغات متعددة.</p>
</div>
""", unsafe_allow_html=True)
# اختيار نوع المحتوى
content_type = st.radio(
"نوع المحتوى",
options=["نص حر", "بيانات مشروع", "ملخص مناقصة", "بنود عقد"],
horizontal=True,
key="voice_content_type"
)
# مقدار النص الذي سيتم عرضه بناءً على نوع المحتوى
if content_type == "نص حر":
content_text = st.text_area(
"النص المراد تحويله إلى صوت",
height=150,
placeholder="أدخل النص الذي ترغب في تحويله إلى صوت هنا...",
key="voice_content_text"
)
title = st.text_input(
"عنوان الملف الصوتي",
placeholder="عنوان لتسهيل الوصول للملف الصوتي لاحقاً",
key="voice_title"
)
elif content_type == "بيانات مشروع":
# عرض المشاريع المتاحة في النظام
projects = self._get_projects()
if projects:
selected_project_id = st.selectbox(
"اختر المشروع",
options=[p["id"] for p in projects],
format_func=lambda x: next((p["name"] for p in projects if p["id"] == x), ""),
key="voice_project_id"
)
# العثور على المشروع المحدد
selected_project = next((p for p in projects if p["id"] == selected_project_id), None)
if selected_project:
# نعرض بيانات المشروع
st.subheader(f"بيانات المشروع: {selected_project['name']}")
project_details = f"""
اسم المشروع: {selected_project['name']}
رقم المشروع: {selected_project['id']}
الحالة: {selected_project.get('status', 'غير محدد')}
الموقع: {selected_project.get('location', 'غير محدد')}
تاريخ البدء: {selected_project.get('start_date', 'غير محدد')}
تاريخ الانتهاء المتوقع: {selected_project.get('expected_end_date', 'غير محدد')}
الميزانية: {selected_project.get('budget', 'غير محدد')}
وصف المشروع: {selected_project.get('description', 'لا يوجد وصف متاح')}
"""
st.text_area(
"تفاصيل المشروع (يمكنك تعديلها قبل التحويل إلى صوت)",
value=project_details,
height=250,
key="voice_project_details"
)
content_text = st.session_state.voice_project_details
title = f"ملخص مشروع {selected_project['name']}"
else:
st.warning("لم يتم العثور على المشروع المحدد")
content_text = ""
title = ""
else:
st.info("لا توجد مشاريع متاحة حالياً")
content_text = ""
title = ""
elif content_type == "ملخص مناقصة":
# عرض المناقصات المتاحة في النظام
tenders = self._get_tenders()
if tenders:
selected_tender_id = st.selectbox(
"اختر المناقصة",
options=[t["id"] for t in tenders],
format_func=lambda x: next((t["name"] for t in tenders if t["id"] == x), ""),
key="voice_tender_id"
)
# العثور على المناقصة المحددة
selected_tender = next((t for t in tenders if t["id"] == selected_tender_id), None)
if selected_tender:
# نعرض بيانات المناقصة
st.subheader(f"بيانات المناقصة: {selected_tender['name']}")
tender_details = f"""
اسم المناقصة: {selected_tender['name']}
رقم المناقصة: {selected_tender['id']}
الجهة المالكة: {selected_tender.get('owner', 'غير محدد')}
تاريخ الطرح: {selected_tender.get('issue_date', 'غير محدد')}
تاريخ التسليم: {selected_tender.get('submission_date', 'غير محدد')}
القيمة التقديرية: {selected_tender.get('estimated_value', 'غير محدد')}
وصف المناقصة: {selected_tender.get('description', 'لا يوجد وصف متاح')}
"""
st.text_area(
"تفاصيل المناقصة (يمكنك تعديلها قبل التحويل إلى صوت)",
value=tender_details,
height=250,
key="voice_tender_details"
)
content_text = st.session_state.voice_tender_details
title = f"ملخص مناقصة {selected_tender['name']}"
else:
st.warning("لم يتم العثور على المناقصة المحددة")
content_text = ""
title = ""
else:
st.info("لا توجد مناقصات متاحة حالياً")
content_text = ""
title = ""
elif content_type == "بنود عقد":
# عرض العقود المتاحة في النظام
contracts = self._get_contracts()
if contracts:
selected_contract_id = st.selectbox(
"اختر العقد",
options=[c["id"] for c in contracts],
format_func=lambda x: next((c["name"] for c in contracts if c["id"] == x), ""),
key="voice_contract_id"
)
# العثور على العقد المحدد
selected_contract = next((c for c in contracts if c["id"] == selected_contract_id), None)
if selected_contract:
# نعرض بيانات العقد
st.subheader(f"بيانات العقد: {selected_contract['name']}")
# العثور على بنود العقد
contract_clauses = selected_contract.get("clauses", [])
if contract_clauses:
# السماح للمستخدم باختيار البنود التي يريد تحويلها
selected_clauses = st.multiselect(
"اختر البنود المراد تحويلها إلى صوت",
options=list(range(len(contract_clauses))),
format_func=lambda i: f"البند {i+1}: {contract_clauses[i]['title']}",
key="voice_contract_clauses"
)
if selected_clauses:
# تجميع النصوص المختارة
clauses_text = ""
for i in selected_clauses:
clauses_text += f"البند {i+1}: {contract_clauses[i]['title']}\n"
clauses_text += f"{contract_clauses[i]['content']}\n\n"
st.text_area(
"نص البنود المختارة (يمكنك تعديلها قبل التحويل إلى صوت)",
value=clauses_text,
height=250,
key="voice_contract_text"
)
content_text = st.session_state.voice_contract_text
title = f"بنود من عقد {selected_contract['name']}"
else:
st.info("الرجاء اختيار بند واحد على الأقل")
content_text = ""
title = ""
else:
st.info("لا توجد بنود متاحة لهذا العقد")
content_text = ""
title = ""
else:
st.warning("لم يتم العثور على العقد المحدد")
content_text = ""
title = ""
else:
st.info("لا توجد عقود متاحة حالياً")
content_text = ""
title = ""
# إعدادات اللغة للنص المدخل
st.markdown("### إعدادات اللغة")
col1, col2 = st.columns(2)
with col1:
source_language = st.selectbox(
"لغة النص المصدر",
options=list(self.supported_languages.keys()),
format_func=lambda x: self.supported_languages[x],
index=list(self.supported_languages.keys()).index(st.session_state.voice_settings["primary_language"]),
key="voice_source_language"
)
voice_id = st.selectbox(
"الصوت",
options=[v["id"] for v in self.voices_by_language[source_language]],
format_func=lambda x: next((v["name"] + f" ({v['gender']})" for v in self.voices_by_language[source_language] if v["id"] == x), ""),
index=0,
key="voice_source_voice"
)
with col2:
target_language = st.selectbox(
"لغة الترجمة (اختياري)",
options=["none"] + list(self.supported_languages.keys()),
format_func=lambda x: "بدون ترجمة" if x == "none" else self.supported_languages[x],
index=0,
key="voice_target_language"
)
if target_language != "none":
target_voice_id = st.selectbox(
"صوت الترجمة",
options=[v["id"] for v in self.voices_by_language[target_language]],
format_func=lambda x: next((v["name"] + f" ({v['gender']})" for v in self.voices_by_language[target_language] if v["id"] == x), ""),
index=0,
key="voice_target_voice"
)
else:
target_voice_id = None
# خيارات متقدمة
with st.expander("خيارات متقدمة"):
advanced_col1, advanced_col2 = st.columns(2)
with advanced_col1:
speaking_rate = st.slider(
"سرعة النطق",
min_value=0.5,
max_value=2.0,
value=st.session_state.voice_settings["speaking_rate"],
step=0.1,
key="voice_speaking_rate"
)
include_subtitles = st.checkbox(
"تضمين النص مع الصوت",
value=st.session_state.voice_settings["include_subtitles"],
key="voice_include_subtitles"
)
with advanced_col2:
pitch = st.slider(
"درجة الصوت",
min_value=-10.0,
max_value=10.0,
value=st.session_state.voice_settings["pitch"],
step=1.0,
key="voice_pitch"
)
emphasize_keywords = st.checkbox(
"تمييز الكلمات المهمة",
value=st.session_state.voice_settings["emphasis_keywords"],
key="voice_emphasize_keywords"
)
# شروط إنشاء الترجمة الصوتية
create_voice_over = False
if content_text:
# زر إنشاء الترجمة الصوتية
if styled_button("إنشاء الترجمة الصوتية", key="create_voice_over_btn", type="primary", icon="🎙️"):
if title:
create_voice_over = True
else:
st.warning("الرجاء إدخال عنوان للملف الصوتي")
else:
st.info("الرجاء إدخال أو اختيار محتوى للترجمة الصوتية")
# إنشاء الترجمة الصوتية
if create_voice_over:
with st.spinner("جاري إنشاء الترجمة الصوتية..."):
try:
# تحقق من وجود الملف في الكاش
cache_key = self._generate_cache_key(
content_text,
source_language,
voice_id,
speaking_rate,
pitch
)
cached_file = self._get_from_cache(cache_key)
if cached_file:
st.success("تم استرجاع الترجمة الصوتية من الكاش")
audio_file = cached_file
audio_duration = self._get_audio_duration(audio_file)
else:
# إنشاء الترجمة الصوتية
audio_file, audio_duration = self._generate_voice_over(
content_text,
source_language,
voice_id,
speaking_rate,
pitch
)
# حفظ الملف في الكاش
self._add_to_cache(cache_key, audio_file)
# تسجيل الترجمة الصوتية في التاريخ
voice_over_id = self._add_voice_to_history(
title=title,
content=content_text,
source_language=source_language,
voice_id=voice_id,
duration=audio_duration,
audio_file=os.path.basename(audio_file),
content_type=content_type
)
# ترجمة المحتوى إذا تم اختيار لغة ترجمة
if target_language != "none":
with st.spinner(f"جاري الترجمة إلى {self.supported_languages[target_language]}..."):
# ترجمة النص
translated_text = self._translate_text(
content_text,
source_language,
target_language
)
# تحقق من وجود الملف المترجم في الكاش
translated_cache_key = self._generate_cache_key(
translated_text,
target_language,
target_voice_id,
speaking_rate,
pitch
)
cached_translated_file = self._get_from_cache(translated_cache_key)
if cached_translated_file:
st.success("تم استرجاع الترجمة الصوتية المترجمة من الكاش")
translated_audio_file = cached_translated_file
translated_audio_duration = self._get_audio_duration(translated_audio_file)
else:
# إنشاء الترجمة الصوتية للنص المترجم
translated_audio_file, translated_audio_duration = self._generate_voice_over(
translated_text,
target_language,
target_voice_id,
speaking_rate,
pitch
)
# حفظ الملف المترجم في الكاش
self._add_to_cache(translated_cache_key, translated_audio_file)
# تسجيل الترجمة الصوتية المترجمة في التاريخ
translated_voice_over_id = self._add_voice_to_history(
title=f"{title} ({self.supported_languages[target_language]})",
content=translated_text,
source_language=target_language,
voice_id=target_voice_id,
duration=translated_audio_duration,
audio_file=os.path.basename(translated_audio_file),
content_type=content_type,
is_translation=True,
original_id=voice_over_id
)
# عرض الترجمة الصوتية المترجمة
st.subheader(f"الترجمة الصوتية بـ{self.supported_languages[target_language]}")
# عرض النص المترجم إذا تم اختيار ذلك
if include_subtitles:
st.markdown(f"**النص المترجم:**\n{translated_text}")
# عرض مشغل الصوت
self._display_audio_player(translated_audio_file)
# عرض الترجمة الصوتية
st.subheader(f"الترجمة الصوتية بـ{self.supported_languages[source_language]}")
# عرض النص إذا تم اختيار ذلك
if include_subtitles:
st.markdown(f"**النص:**\n{content_text}")
# عرض مشغل الصوت
self._display_audio_player(audio_file)
# زر تنزيل الملف الصوتي
with open(audio_file, "rb") as f:
audio_bytes = f.read()
st.download_button(
label="تنزيل الملف الصوتي",
data=audio_bytes,
file_name=f"{title}.mp3",
mime="audio/mpeg",
key="download_voice_over"
)
st.success("تم إنشاء الترجمة الصوتية بنجاح!")
except Exception as e:
st.error(f"حدث خطأ أثناء إنشاء الترجمة الصوتية: {str(e)}")
self.logger.error(f"خطأ في إنشاء الترجمة الصوتية: {str(e)}")
def _render_voice_over_manager(self):
"""عرض واجهة مدير الترجمات الصوتية"""
st.markdown("""
<div class='custom-box info-box'>
<h3>🎧 مدير الترجمات الصوتية</h3>
<p>استعراض وإدارة الترجمات الصوتية المخزنة.</p>
</div>
""", unsafe_allow_html=True)
# تحديث تاريخ الترجمات الصوتية
self.voice_history = self._load_voice_history()
# التحقق من وجود ترجمات صوتية
if not self.voice_history:
st.info("لا توجد ترجمات صوتية مخزنة.")
return
# أزرار التحكم
col1, col2 = st.columns(2)
with col1:
# فلترة حسب نوع المحتوى
content_types = ["الكل"] + list(set(item.get("content_type", "نص حر") for item in self.voice_history))
filter_content_type = st.selectbox(
"فلترة حسب نوع المحتوى",
options=content_types,
key="filter_content_type"
)
with col2:
# فلترة حسب اللغة
languages = ["الكل"] + [self.supported_languages.get(item.get("source_language", "ar"), "العربية") for item in self.voice_history]
filter_language = st.selectbox(
"فلترة حسب اللغة",
options=list(set(languages)),
key="filter_language"
)
# فلترة العناصر
filtered_history = self.voice_history
if filter_content_type != "الكل":
filtered_history = [item for item in filtered_history if item.get("content_type", "نص حر") == filter_content_type]
if filter_language != "الكل":
filtered_history = [
item for item in filtered_history
if self.supported_languages.get(item.get("source_language", "ar"), "العربية") == filter_language
]
# عرض الترجمات الصوتية
for voice_item in filtered_history:
with st.expander(f"{voice_item['title']} ({voice_item.get('created_at', 'تاريخ غير معروف')})", expanded=False):
# تفاصيل الترجمة الصوتية
item_col1, item_col2 = st.columns([3, 1])
with item_col1:
st.markdown(f"**النوع:** {voice_item.get('content_type', 'نص حر')}")
st.markdown(f"**اللغة:** {self.supported_languages.get(voice_item.get('source_language', 'ar'), 'العربية')}")
st.markdown(f"**المدة:** {voice_item.get('duration', 0):.2f} ثانية")
# عرض مشغل الصوت
audio_file_path = os.path.join(self.data_dir, voice_item.get('audio_file', ''))
if os.path.exists(audio_file_path):
self._display_audio_player(audio_file_path)
else:
st.warning("ملف الصوت غير متوفر")
with item_col2:
# عرض النص
if st.button("عرض النص", key=f"show_text_{voice_item.get('id', '')}"):
st.text_area(
"نص الترجمة الصوتية",
value=voice_item.get('content', ''),
height=150,
key=f"text_{voice_item.get('id', '')}",
disabled=True
)
# تنزيل الملف الصوتي
audio_file_path = os.path.join(self.data_dir, voice_item.get('audio_file', ''))
if os.path.exists(audio_file_path):
with open(audio_file_path, "rb") as f:
audio_bytes = f.read()
st.download_button(
label="تنزيل الملف الصوتي",
data=audio_bytes,
file_name=f"{voice_item['title']}.mp3",
mime="audio/mpeg",
key=f"download_{voice_item.get('id', '')}"
)
# حذف الترجمة الصوتية
if st.button("حذف", key=f"delete_{voice_item.get('id', '')}", type="primary"):
if self._delete_voice_from_history(voice_item.get('id', '')):
st.success("تم حذف الترجمة الصوتية بنجاح!")
st.rerun()
else:
st.error("حدث خطأ أثناء حذف الترجمة الصوتية")
def _render_voice_settings(self):
"""عرض واجهة إعدادات الصوت"""
st.markdown("""
<div class='custom-box info-box'>
<h3>⚙️ إعدادات الصوت</h3>
<p>تخصيص إعدادات الترجمة الصوتية الافتراضية.</p>
</div>
""", unsafe_allow_html=True)
# إعدادات اللغة
st.markdown("### إعدادات اللغة")
lang_col1, lang_col2 = st.columns(2)
with lang_col1:
# اللغة الأساسية
primary_language = st.selectbox(
"اللغة الأساسية",
options=list(self.supported_languages.keys()),
format_func=lambda x: self.supported_languages[x],
index=list(self.supported_languages.keys()).index(st.session_state.voice_settings["primary_language"]),
key="settings_primary_language"
)
# الصوت الأساسي
primary_voice = st.selectbox(
"الصوت الأساسي",
options=[v["id"] for v in self.voices_by_language[primary_language]],
format_func=lambda x: next((v["name"] + f" ({v['gender']})" for v in self.voices_by_language[primary_language] if v["id"] == x), ""),
index=0,
key="settings_primary_voice"
)
with lang_col2:
# اللغة الثانوية
secondary_language = st.selectbox(
"اللغة الثانوية",
options=list(self.supported_languages.keys()),
format_func=lambda x: self.supported_languages[x],
index=list(self.supported_languages.keys()).index(st.session_state.voice_settings["secondary_language"]),
key="settings_secondary_language"
)
# الصوت الثانوي
secondary_voice = st.selectbox(
"الصوت الثانوي",
options=[v["id"] for v in self.voices_by_language[secondary_language]],
format_func=lambda x: next((v["name"] + f" ({v['gender']})" for v in self.voices_by_language[secondary_language] if v["id"] == x), ""),
index=0,
key="settings_secondary_voice"
)
# إعدادات جودة الصوت
st.markdown("### إعدادات جودة الصوت")
quality_col1, quality_col2 = st.columns(2)
with quality_col1:
# سرعة النطق
speaking_rate = st.slider(
"سرعة النطق الافتراضية",
min_value=0.5,
max_value=2.0,
value=st.session_state.voice_settings["speaking_rate"],
step=0.1,
key="settings_speaking_rate"
)
with quality_col2:
# درجة الصوت
pitch = st.slider(
"درجة الصوت الافتراضية",
min_value=-10.0,
max_value=10.0,
value=st.session_state.voice_settings["pitch"],
step=1.0,
key="settings_pitch"
)
# إعدادات أخرى
st.markdown("### إعدادات أخرى")
other_col1, other_col2 = st.columns(2)
with other_col1:
# الترجمة التلقائية
auto_translate = st.checkbox(
"ترجمة تلقائية إلى اللغة الثانوية",
value=st.session_state.voice_settings["auto_translate"],
key="settings_auto_translate"
)
# تضمين النص مع الصوت
include_subtitles = st.checkbox(
"تضمين النص مع الصوت افتراضياً",
value=st.session_state.voice_settings["include_subtitles"],
key="settings_include_subtitles"
)
with other_col2:
# تمييز الكلمات المهمة
emphasis_keywords = st.checkbox(
"تمييز الكلمات المهمة تلقائياً",
value=st.session_state.voice_settings["emphasis_keywords"],
key="settings_emphasis_keywords"
)
# زر حفظ الإعدادات
if styled_button("حفظ الإعدادات", key="save_voice_settings", type="primary", icon="💾"):
# تحديث الإعدادات
st.session_state.voice_settings = {
"primary_language": primary_language,
"secondary_language": secondary_language,
"primary_voice": primary_voice,
"secondary_voice": secondary_voice,
"speaking_rate": speaking_rate,
"pitch": pitch,
"auto_translate": auto_translate,
"include_subtitles": include_subtitles,
"emphasis_keywords": emphasis_keywords
}
# حفظ الإعدادات
self._save_voice_settings()
st.success("تم حفظ الإعدادات بنجاح!")
# إعدادات متقدمة
with st.expander("إعدادات متقدمة", expanded=False):
st.markdown("### إعدادات الكاش")
cache_size = self._get_cache_size()
st.markdown(f"حجم الكاش الحالي: {cache_size / (1024 * 1024):.2f} ميجابايت")
if styled_button("مسح الكاش", key="clear_cache", type="danger", icon="🗑️"):
if self._clear_cache():
st.success("تم مسح الكاش بنجاح!")
else:
st.error("حدث خطأ أثناء مسح الكاش")
st.markdown("### إعدادات API")
# نموذج API للترجمة الصوتية
api_model = st.selectbox(
"نموذج API للترجمة الصوتية",
options=["local", "huggingface", "google", "amazon", "microsoft"],
format_func=lambda x: {
"local": "محلي (عرض توضيحي)",
"huggingface": "Hugging Face",
"google": "Google Cloud Text-to-Speech",
"amazon": "Amazon Polly",
"microsoft": "Microsoft Azure"
}[x],
index=0,
key="api_model"
)
# معلومات حول النموذج المحدد
api_info = {
"local": "هذا وضع العرض التوضيحي حيث يتم تشبيه الترجمة الصوتية دون الحاجة إلى اتصال API خارجي.",
"huggingface": "استخدام Hugging Face API لتحويل النص إلى صوت وترجمة النصوص.",
"google": "استخدام Google Cloud Text-to-Speech لإنتاج صوت عالي الجودة.",
"amazon": "استخدام Amazon Polly للترجمة الصوتية بجودة عالية ومجموعة متنوعة من الأصوات.",
"microsoft": "استخدام Microsoft Azure Speech Services للترجمة الصوتية والترجمة."
}
st.markdown(f"**معلومات:** {api_info[api_model]}")
if api_model != "local":
api_key = st.text_input(
f"مفتاح API لـ {api_model}",
type="password",
key=f"{api_model}_api_key"
)
if styled_button("حفظ مفتاح API", key="save_api_key", type="primary"):
st.success(f"تم حفظ مفتاح API لـ {api_model} بنجاح!")
def _render_document_narration(self):
"""عرض واجهة ترجمة مستندات كاملة"""
st.markdown("""
<div class='custom-box info-box'>
<h3>📄 ترجمة مستندات كاملة</h3>
<p>تحويل مستندات كاملة إلى ملفات صوتية وتقسيمها إلى فصول أو أقسام.</p>
</div>
""", unsafe_allow_html=True)
# اختيار المستند
documents = self._get_documents()
if documents:
selected_document_id = st.selectbox(
"اختر المستند",
options=[d["id"] for d in documents],
format_func=lambda x: next((d["name"] for d in documents if d["id"] == x), ""),
key="narration_document_id"
)
# العثور على المستند المحدد
selected_document = next((d for d in documents if d["id"] == selected_document_id), None)
if selected_document:
# عرض تفاصيل المستند
st.subheader(f"معلومات المستند: {selected_document['name']}")
doc_col1, doc_col2 = st.columns(2)
with doc_col1:
st.markdown(f"**النوع:** {selected_document.get('type', 'غير محدد')}")
st.markdown(f"**عدد الصفحات:** {selected_document.get('page_count', 'غير محدد')}")
with doc_col2:
st.markdown(f"**حجم المستند:** {selected_document.get('file_size', 'غير محدد')}")
st.markdown(f"**تاريخ الرفع:** {selected_document.get('upload_date', 'غير محدد')}")
# خيارات الترجمة الصوتية
st.markdown("### خيارات الترجمة الصوتية")
options_col1, options_col2 = st.columns(2)
with options_col1:
# اللغة
narration_language = st.selectbox(
"لغة الترجمة الصوتية",
options=list(self.supported_languages.keys()),
format_func=lambda x: self.supported_languages[x],
index=list(self.supported_languages.keys()).index(st.session_state.voice_settings["primary_language"]),
key="narration_language"
)
# الصوت
narration_voice = st.selectbox(
"الصوت",
options=[v["id"] for v in self.voices_by_language[narration_language]],
format_func=lambda x: next((v["name"] + f" ({v['gender']})" for v in self.voices_by_language[narration_language] if v["id"] == x), ""),
index=0,
key="narration_voice"
)
# تقسيم المستند
narration_split = st.selectbox(
"تقسيم المستند",
options=["لا تقسيم", "حسب الصفحات", "حسب العناوين", "حسب الفصول"],
key="narration_split"
)
with options_col2:
# سرعة النطق
narration_speaking_rate = st.slider(
"سرعة النطق",
min_value=0.5,
max_value=2.0,
value=st.session_state.voice_settings["speaking_rate"],
step=0.1,
key="narration_speaking_rate"
)
# درجة الصوت
narration_pitch = st.slider(
"درجة الصوت",
min_value=-10.0,
max_value=10.0,
value=st.session_state.voice_settings["pitch"],
step=1.0,
key="narration_pitch"
)
# تضمين الفهرس
narration_include_toc = st.checkbox(
"تضمين فهرس صوتي",
value=True,
key="narration_include_toc"
)
# خيارات إضافية
with st.expander("خيارات إضافية", expanded=False):
# تجاهل الصفحات
narration_skip_pages = st.text_input(
"تجاهل الصفحات (أرقام مفصولة بفواصل)",
placeholder="مثال: 1,2,5-7",
key="narration_skip_pages"
)
# إضافة مقدمة
narration_intro = st.text_area(
"مقدمة خاصة (سيتم إضافتها في بداية الترجمة الصوتية)",
placeholder="مقدمة اختيارية...",
key="narration_intro"
)
# إضافة خاتمة
narration_outro = st.text_area(
"خاتمة خاصة (سيتم إضافتها في نهاية الترجمة الصوتية)",
placeholder="خاتمة اختيارية...",
key="narration_outro"
)
# زر إنشاء الترجمة الصوتية للمستند
if styled_button("إنشاء الترجمة الصوتية للمستند", key="create_document_narration", type="primary", icon="🎙️"):
# تحقق من وجود قسم للترجمة الصوتية
narration_folder = os.path.join(self.data_dir, "document_narrations", str(selected_document_id))
os.makedirs(narration_folder, exist_ok=True)
# التقدم المستمر في إنشاء الترجمة الصوتية
progress_bar = st.progress(0)
status_text = st.empty()
# صنع ترجمة صوتية وهمية (للعرض التوضيحي)
total_sections = 5 # عدد أقسام افتراضي
for i in range(total_sections + 1):
# تحديث شريط التقدم
progress = i / total_sections
progress_bar.progress(progress)
if i == 0:
status_text.text("جاري تحليل المستند...")
elif i == 1:
status_text.text("جاري استخراج النص...")
elif i < total_sections:
status_text.text(f"جاري إنشاء الترجمة الصوتية للقسم {i}...")
else:
status_text.text("جاري تجميع الملفات الصوتية النهائية...")
time.sleep(1) # محاكاة العمل
# اكتمال العملية
progress_bar.progress(1.0)
status_text.text("تم إنشاء الترجمة الصوتية بنجاح!")
# إظهار نتائج وهمية
st.subheader("الترجمة الصوتية للمستند")
# عرض المقاطع الصوتية (وهمية)
for i in range(1, total_sections):
with st.expander(f"القسم {i}: العنوان الافتراضي {i}", expanded=i==1):
# محاكاة وجود ملف صوتي
st.audio("https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3")
# عرض معلومات القسم
st.markdown(f"**المدة:** {i + 2} دقائق")
st.markdown(f"**عدد الكلمات:** {i * 100} كلمة")
# زر تنزيل الملف الكامل
st.download_button(
label="تنزيل الترجمة الصوتية الكاملة",
data=b"Mock audio file", # بيانات وهمية باللغة الإنجليزية
file_name=f"{selected_document['name']}_narration.mp3",
mime="audio/mpeg",
key="download_full_narration"
)
else:
st.warning("لم يتم العثور على المستند المحدد")
else:
st.info("لا توجد مستندات متاحة حالياً")
# اختيار رفع مستند جديد
st.markdown("### رفع مستند جديد للترجمة الصوتية")
uploaded_file = st.file_uploader(
"اختر ملف للرفع (PDF, DOCX, TXT)",
type=["pdf", "docx", "txt"],
key="narration_upload_file"
)
if uploaded_file:
file_name = uploaded_file.name
# عرض معلومات الملف
st.markdown(f"**اسم الملف:** {file_name}")
st.markdown(f"**حجم الملف:** {uploaded_file.size / 1024:.2f} كيلوبايت")
document_name = st.text_input(
"اسم المستند",
value=file_name,
key="narration_document_name"
)
if styled_button("رفع المستند", key="upload_document", type="primary", icon="📤"):
st.success(f"تم رفع المستند '{document_name}' بنجاح!")
st.info("يمكنك الآن اختيار المستند لإنشاء ترجمة صوتية له.")
st.rerun()
def _render_voice_over_analytics(self):
"""عرض إحصائيات ومقاييس الترجمات الصوتية"""
st.markdown("""
<div class='custom-box info-box'>
<h3>📊 إحصائيات ومقاييس</h3>
<p>إحصائيات ومقاييس استخدام نظام الترجمة الصوتية.</p>
</div>
""", unsafe_allow_html=True)
# تحديث تاريخ الترجمات الصوتية
self.voice_history = self._load_voice_history()
# التحقق من وجود ترجمات صوتية
if not self.voice_history:
st.info("لا توجد ترجمات صوتية مخزنة.")
return
# إحصائيات عامة
st.markdown("### إحصائيات عامة")
# تحضير البيانات
total_voices = len(self.voice_history)
total_duration = sum(item.get("duration", 0) for item in self.voice_history)
total_languages = len(set(item.get("source_language", "ar") for item in self.voice_history))
# عرض الإحصائيات
stats_col1, stats_col2, stats_col3 = st.columns(3)
with stats_col1:
st.metric("إجمالي الترجمات الصوتية", total_voices)
with stats_col2:
st.metric("إجمالي المدة", f"{total_duration:.2f} ثانية")
with stats_col3:
st.metric("عدد اللغات المستخدمة", total_languages)
# رسم بياني لتوزيع الترجمات الصوتية حسب اللغة
st.markdown("### توزيع الترجمات الصوتية حسب اللغة")
language_counts = {}
for item in self.voice_history:
lang = item.get("source_language", "ar")
lang_name = self.supported_languages.get(lang, "غير معروف")
language_counts[lang_name] = language_counts.get(lang_name, 0) + 1
# تحويل إلى DataFrame
language_df = pd.DataFrame({
"اللغة": list(language_counts.keys()),
"العدد": list(language_counts.values())
})
# رسم بياني دائري
import plotly.express as px
fig1 = px.pie(
language_df,
values="العدد",
names="اللغة",
title="توزيع الترجمات الصوتية حسب اللغة",
color_discrete_sequence=px.colors.qualitative.Pastel
)
fig1.update_layout(
title_font_size=20,
font_family="Arial",
font_size=14,
height=400
)
st.plotly_chart(fig1, use_container_width=True)
# رسم بياني لتوزيع الترجمات الصوتية حسب نوع المحتوى
st.markdown("### توزيع الترجمات الصوتية حسب نوع المحتوى")
content_type_counts = {}
for item in self.voice_history:
content_type = item.get("content_type", "نص حر")
content_type_counts[content_type] = content_type_counts.get(content_type, 0) + 1
# تحويل إلى DataFrame
content_df = pd.DataFrame({
"نوع المحتوى": list(content_type_counts.keys()),
"العدد": list(content_type_counts.values())
})
# رسم بياني شريطي
fig2 = px.bar(
content_df,
x="نوع المحتوى",
y="العدد",
title="توزيع الترجمات الصوتية حسب نوع المحتوى",
color="نوع المحتوى",
color_discrete_sequence=px.colors.qualitative.Pastel
)
fig2.update_layout(
title_font_size=20,
font_family="Arial",
font_size=14,
height=400
)
st.plotly_chart(fig2, use_container_width=True)
# رسم بياني لتوزيع الترجمات الصوتية حسب التاريخ
st.markdown("### توزيع الترجمات الصوتية حسب التاريخ")
# استخراج التواريخ
dates = []
for item in self.voice_history:
created_at = item.get("created_at", "")
if created_at:
try:
date = datetime.datetime.strptime(created_at.split(" ")[0], "%Y-%m-%d").date()
dates.append(date)
except (ValueError, IndexError):
continue
if dates:
# عد التكرارات
date_counts = {}
for date in dates:
date_str = date.strftime("%Y-%m-%d")
date_counts[date_str] = date_counts.get(date_str, 0) + 1
# تحويل إلى DataFrame
date_df = pd.DataFrame({
"التاريخ": list(date_counts.keys()),
"العدد": list(date_counts.values())
})
# ترتيب حسب التاريخ
date_df["التاريخ"] = pd.to_datetime(date_df["التاريخ"])
date_df = date_df.sort_values("التاريخ")
# رسم بياني خطي
fig3 = px.line(
date_df,
x="التاريخ",
y="العدد",
title="توزيع الترجمات الصوتية حسب التاريخ",
markers=True
)
fig3.update_layout(
title_font_size=20,
font_family="Arial",
font_size=14,
height=400
)
st.plotly_chart(fig3, use_container_width=True)
else:
st.info("لا توجد بيانات تاريخ كافية لعرض الرسم البياني")
# تصدير البيانات
st.markdown("### تصدير البيانات")
export_col1, export_col2 = st.columns(2)
with export_col1:
if styled_button("تصدير CSV", key="export_voice_csv", type="primary", icon="📄"):
# تحويل البيانات إلى DataFrame
export_df = pd.DataFrame(self.voice_history)
# تنزيل الملف
csv_data = export_df.to_csv(index=False)
st.download_button(
label="تنزيل ملف CSV",
data=csv_data,
file_name=f"voice_over_history_{datetime.datetime.now().strftime('%Y%m%d')}.csv",
mime="text/csv",
key="download_voice_csv"
)
with export_col2:
if styled_button("تصدير JSON", key="export_voice_json", type="primary", icon="📄"):
# تحويل البيانات إلى JSON
json_data = json.dumps(self.voice_history, indent=2)
st.download_button(
label="تنزيل ملف JSON",
data=json_data,
file_name=f"voice_over_history_{datetime.datetime.now().strftime('%Y%m%d')}.json",
mime="application/json",
key="download_voice_json"
)
def _generate_voice_over(self, text, language, voice_id, speaking_rate=1.0, pitch=0.0):
"""
إنشاء ترجمة صوتية (محاكاة)
المعلمات:
text: النص المراد تحويله إلى صوت
language: رمز اللغة
voice_id: معرف الصوت
speaking_rate: سرعة النطق
pitch: درجة الصوت
الإرجاع:
مسار الملف الصوتي ومدته
"""
try:
# في الوضع العادي، سنستخدم API لتحويل النص إلى صوت
# هنا نستخدم ملف صوتي وهمي للعرض التوضيحي
# إنشاء ملف مؤقت لتمثيل الصوت
temp_file = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
temp_file.close()
# نسخ ملف صوتي وهمي (يمكن استبداله بإنشاء فعلي للصوت)
audio_file = os.path.join(self.data_dir, f"voice_{language}_{voice_id}_{int(time.time())}.mp3")
# محاكاة إنشاء ملف صوتي باستخدام صوت بسيط
# هنا يمكن استبدال هذا بمكالمة API حقيقية لتحويل النص إلى صوت
import wave
import struct
# إنشاء بيانات صوتية بسيطة
duration = len(text) * 0.1 # تقدير المدة بناءً على طول النص
sample_rate = 44100 # معدل العينات
# محاكاة تأثير سرعة النطق على المدة
duration = duration / speaking_rate
# إنشاء ملف WAV مؤقت
wav_file = temp_file.name.replace(".mp3", ".wav")
with wave.open(wav_file, "w") as f:
f.setnchannels(1) # أحادي القناة
f.setsampwidth(2) # 16 بت
f.setframerate(sample_rate)
# محاكاة صوت بسيط (موجة جيبية)
for i in range(int(duration * sample_rate)):
# تأثير درجة الصوت
value = 32767 * 0.3 * np.sin(2 * np.pi * (440 + pitch * 20) * i / sample_rate)
f.writeframes(struct.pack('h', int(value)))
# تحويل WAV إلى MP3 (في التطبيق الفعلي)
# هنا نفترض أن التحويل تم بنجاح
import shutil
shutil.copy(wav_file, audio_file)
# تنظيف الملفات المؤقتة
try:
os.remove(wav_file)
os.remove(temp_file.name)
except:
pass
# تسجيل العملية
self.logger.info(f"تم إنشاء ترجمة صوتية للنص (طول: {len(text)}) باللغة: {language}")
return audio_file, duration
except Exception as e:
self.logger.error(f"خطأ في إنشاء الترجمة الصوتية: {str(e)}")
raise e
def _translate_text(self, text, source_language, target_language):
"""
ترجمة نص من لغة إلى أخرى (محاكاة)
المعلمات:
text: النص المراد ترجمته
source_language: رمز اللغة المصدر
target_language: رمز اللغة الهدف
الإرجاع:
النص المترجم
"""
try:
# في الوضع العادي، سنستخدم API للترجمة
# هنا نستخدم ترجمة وهمية للعرض التوضيحي
# تسجيل العملية
self.logger.info(f"ترجمة نص (طول: {len(text)}) من {source_language} إلى {target_language}")
# ترجمة وهمية
translated_prefix = {
"en": "This is a sample translation of the text into English.",
"ar": "هذه ترجمة عينة للنص إلى اللغة العربية.",
"fr": "Ceci est un exemple de traduction du texte en français.",
"es": "Esta es una traducción de muestra del texto al español.",
"de": "Dies ist eine Beispielübersetzung des Textes ins Deutsche.",
"it": "Questa è una traduzione di esempio del testo in italiano.",
"zh": "这是文本翻译成中文的示例。",
"ja": "これはテキストの日本語への翻訳例です。",
"ru": "Это пример перевода текста на русский язык.",
"tr": "Bu, metnin Türkçe çevirisinin bir örneğidir."
}
# إرجاع ترجمة وهمية
return f"{translated_prefix.get(target_language, 'Translated sample')} {text[:100]}..."
except Exception as e:
self.logger.error(f"خطأ في ترجمة النص: {str(e)}")
raise e
def _get_audio_duration(self, audio_file):
"""
الحصول على مدة ملف صوتي
المعلمات:
audio_file: مسار الملف الصوتي
الإرجاع:
مدة الملف الصوتي بالثواني
"""
try:
if audio_file.endswith(".wav"):
# استخدام wave للحصول على مدة ملف WAV
with wave.open(audio_file, "rb") as f:
frames = f.getnframes()
rate = f.getframerate()
duration = frames / float(rate)
else:
# محاكاة لمدة الملف الصوتي
size_in_bytes = os.path.getsize(audio_file)
duration = size_in_bytes / 16000 # تقريب بسيط
return duration
except Exception as e:
self.logger.error(f"خطأ في الحصول على مدة الملف الصوتي: {str(e)}")
return 30.0 # قيمة افتراضية
def _get_from_cache(self, cache_key):
"""
البحث عن ملف في الكاش
المعلمات:
cache_key: مفتاح الكاش
الإرجاع:
مسار الملف إذا وجد، وإلا None
"""
if cache_key in self.cache_index:
cache_file = os.path.join(self.cache_dir, self.cache_index[cache_key])
if os.path.exists(cache_file):
return cache_file
return None
def _add_to_cache(self, cache_key, file_path):
"""
إضافة ملف إلى الكاش
المعلمات:
cache_key: مفتاح الكاش
file_path: مسار الملف
الإرجاع:
True إذا تمت الإضافة بنجاح، وإلا False
"""
try:
# نسخ الملف إلى الكاش
cache_file = os.path.join(self.cache_dir, os.path.basename(file_path))
if file_path != cache_file:
shutil.copy(file_path, cache_file)
# تحديث فهرس الكاش
self.cache_index[cache_key] = os.path.basename(file_path)
# حفظ الفهرس
with open(self.cache_index_file, "w", encoding="utf-8") as f:
json.dump(self.cache_index, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
self.logger.error(f"خطأ في إضافة الملف إلى الكاش: {str(e)}")
return False
def _generate_cache_key(self, text, language, voice_id, speaking_rate, pitch):
"""
إنشاء مفتاح كاش للترجمة الصوتية
المعلمات:
text: النص
language: اللغة
voice_id: معرف الصوت
speaking_rate: سرعة النطق
pitch: درجة الصوت
الإرجاع:
مفتاح الكاش
"""
import hashlib
# إنشاء نص للتجزئة
cache_text = f"{text}|{language}|{voice_id}|{speaking_rate}|{pitch}"
# إنشاء تجزئة MD5
hash_obj = hashlib.md5(cache_text.encode())
return hash_obj.hexdigest()
def _load_cache_index(self):
"""
تحميل فهرس الكاش
الإرجاع:
قاموس فهرس الكاش
"""
if os.path.exists(self.cache_index_file):
try:
with open(self.cache_index_file, "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
self.logger.error(f"خطأ في تحميل فهرس الكاش: {str(e)}")
return {}
def _get_cache_size(self):
"""
الحصول على حجم الكاش بالبايت
الإرجاع:
حجم الكاش بالبايت
"""
total_size = 0
for filename in os.listdir(self.cache_dir):
file_path = os.path.join(self.cache_dir, filename)
if os.path.isfile(file_path):
total_size += os.path.getsize(file_path)
return total_size
def _clear_cache(self):
"""
مسح كاش الترجمات الصوتية
الإرجاع:
True إذا تم المسح بنجاح، وإلا False
"""
try:
# حذف جميع الملفات في الكاش
for filename in os.listdir(self.cache_dir):
file_path = os.path.join(self.cache_dir, filename)
if os.path.isfile(file_path):
os.remove(file_path)
# إعادة تعيين فهرس الكاش
self.cache_index = {}
# حفظ الفهرس الفارغ
with open(self.cache_index_file, "w", encoding="utf-8") as f:
json.dump(self.cache_index, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
self.logger.error(f"خطأ في مسح الكاش: {str(e)}")
return False
def _add_voice_to_history(self, title, content, source_language, voice_id, duration, audio_file, content_type="نص حر", is_translation=False, original_id=None):
"""
إضافة ترجمة صوتية إلى التاريخ
المعلمات:
title: عنوان الترجمة الصوتية
content: محتوى النص
source_language: اللغة المصدر
voice_id: معرف الصوت
duration: مدة الترجمة الصوتية
audio_file: اسم ملف الترجمة الصوتية
content_type: نوع المحتوى
is_translation: هل هي ترجمة لنص آخر
original_id: معرف النص الأصلي
الإرجاع:
معرف الترجمة الصوتية
"""
try:
# إنشاء معرف فريد
voice_id = f"voice_{int(time.time())}_{len(self.voice_history)}"
# إنشاء كائن الترجمة الصوتية
voice_item = {
"id": voice_id,
"title": title,
"content": content,
"source_language": source_language,
"voice_id": voice_id,
"duration": duration,
"audio_file": audio_file,
"content_type": content_type,
"created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"is_translation": is_translation,
"original_id": original_id
}
# إضافة إلى التاريخ
self.voice_history.append(voice_item)
# حفظ التاريخ
self._save_voice_history()
return voice_id
except Exception as e:
self.logger.error(f"خطأ في إضافة الترجمة الصوتية إلى التاريخ: {str(e)}")
return None
def _delete_voice_from_history(self, voice_id):
"""
حذف ترجمة صوتية من التاريخ
المعلمات:
voice_id: معرف الترجمة الصوتية
الإرجاع:
True إذا تم الحذف بنجاح، وإلا False
"""
try:
# البحث عن الترجمة الصوتية
for i, item in enumerate(self.voice_history):
if item.get("id") == voice_id:
# حذف الملف الصوتي
audio_file = os.path.join(self.data_dir, item.get("audio_file", ""))
if os.path.exists(audio_file):
os.remove(audio_file)
# حذف العنصر من التاريخ
del self.voice_history[i]
# حفظ التاريخ
self._save_voice_history()
return True
return False
except Exception as e:
self.logger.error(f"خطأ في حذف الترجمة الصوتية من التاريخ: {str(e)}")
return False
def _load_voice_history(self):
"""
تحميل تاريخ الترجمات الصوتية
الإرجاع:
قائمة تاريخ الترجمات الصوتية
"""
if os.path.exists(self.voice_history_file):
try:
with open(self.voice_history_file, "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
self.logger.error(f"خطأ في تحميل تاريخ الترجمات الصوتية: {str(e)}")
return []
def _save_voice_history(self):
"""
حفظ تاريخ الترجمات الصوتية
الإرجاع:
True إذا تم الحفظ بنجاح، وإلا False
"""
try:
# التأكد من وجود المجلد
os.makedirs(os.path.dirname(self.voice_history_file), exist_ok=True)
# حفظ التاريخ
with open(self.voice_history_file, "w", encoding="utf-8") as f:
json.dump(self.voice_history, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
self.logger.error(f"خطأ في حفظ تاريخ الترجمات الصوتية: {str(e)}")
return False
def _save_voice_settings(self):
"""
حفظ إعدادات الترجمة الصوتية
الإرجاع:
True إذا تم الحفظ بنجاح، وإلا False
"""
try:
# التأكد من وجود المجلد
os.makedirs(self.data_dir, exist_ok=True)
# حفظ الإعدادات
settings_file = os.path.join(self.data_dir, "voice_settings.json")
with open(settings_file, "w", encoding="utf-8") as f:
json.dump(st.session_state.voice_settings, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
self.logger.error(f"خطأ في حفظ إعدادات الترجمة الصوتية: {str(e)}")
return False
def _display_audio_player(self, audio_file):
"""
عرض مشغل الصوت
المعلمات:
audio_file: مسار الملف الصوتي
"""
if os.path.exists(audio_file):
# قراءة الملف الصوتي
with open(audio_file, "rb") as f:
audio_bytes = f.read()
# عرض مشغل الصوت
st.audio(audio_bytes, format="audio/mp3")
else:
st.warning("الملف الصوتي غير متوفر")
def _get_projects(self):
"""
الحصول على قائمة المشاريع
الإرجاع:
قائمة المشاريع
"""
# في التطبيق الفعلي، سيتم جلب البيانات من قاعدة البيانات
# هنا نستخدم بيانات وهمية للعرض التوضيحي
return [
{
"id": "PRJ001",
"name": "مشروع تطوير البنية التحتية لمنطقة الرياض",
"status": "قيد التنفيذ",
"location": "الرياض",
"start_date": "2025-01-15",
"expected_end_date": "2026-06-30",
"budget": "15,000,000 ريال",
"description": "مشروع تطوير البنية التحتية في منطقة الرياض، ويشمل إنشاء طرق جديدة وتطوير شبكات الصرف الصحي وتحسين شبكات المياه والكهرباء."
},
{
"id": "PRJ002",
"name": "إنشاء مجمع سكني في جدة",
"status": "جديد",
"location": "جدة",
"start_date": "2025-04-01",
"expected_end_date": "2027-03-31",
"budget": "25,000,000 ريال",
"description": "مشروع إنشاء مجمع سكني في مدينة جدة، ويتكون من 50 فيلا و 100 شقة سكنية، بالإضافة إلى مرافق خدمية ومناطق ترفيهية."
},
{
"id": "PRJ003",
"name": "توسعة مستشفى الملك فهد",
"status": "قيد التنفيذ",
"location": "الدمام",
"start_date": "2024-10-15",
"expected_end_date": "2026-02-28",
"budget": "18,500,000 ريال",
"description": "مشروع توسعة مستشفى الملك فهد في مدينة الدمام، ويشمل إضافة مبنى جديد للعيادات الخارجية وزيادة عدد أسرّة المستشفى."
}
]
def _get_tenders(self):
"""
الحصول على قائمة المناقصات
الإرجاع:
قائمة المناقصات
"""
# في التطبيق الفعلي، سيتم جلب البيانات من قاعدة البيانات
# هنا نستخدم بيانات وهمية للعرض التوضيحي
return [
{
"id": "TND001",
"name": "مناقصة تطوير طريق الملك عبدالله",
"owner": "وزارة النقل",
"issue_date": "2025-02-10",
"submission_date": "2025-03-15",
"estimated_value": "12,000,000 ريال",
"description": "مناقصة لتطوير وتوسعة طريق الملك عبدالله بطول 15 كم، وتشمل الأعمال إنشاء مسارات جديدة وتحسين البنية التحتية للطريق."
},
{
"id": "TND002",
"name": "مناقصة إنشاء مدرسة ثانوية",
"owner": "وزارة التعليم",
"issue_date": "2025-01-20",
"submission_date": "2025-02-25",
"estimated_value": "8,500,000 ريال",
"description": "مناقصة لإنشاء مدرسة ثانوية جديدة في حي النزهة بمدينة الرياض، وتشمل الأعمال إنشاء مبنى المدرسة والمرافق التابعة لها."
},
{
"id": "TND003",
"name": "مناقصة صيانة وتأهيل محطات تحلية المياه",
"owner": "المؤسسة العامة لتحلية المياه المالحة",
"issue_date": "2025-03-01",
"submission_date": "2025-04-15",
"estimated_value": "22,000,000 ريال",
"description": "مناقصة لصيانة وتأهيل محطات تحلية المياه في المنطقة الشرقية، وتشمل الأعمال استبدال المعدات القديمة وتطوير أنظمة التحكم."
}
]
def _get_contracts(self):
"""
الحصول على قائمة العقود
الإرجاع:
قائمة العقود
"""
# في التطبيق الفعلي، سيتم جلب البيانات من قاعدة البيانات
# هنا نستخدم بيانات وهمية للعرض التوضيحي
return [
{
"id": "CNT001",
"name": "عقد إنشاء مجمع سكني",
"client": "شركة الرياض للتطوير العقاري",
"start_date": "2025-04-15",
"end_date": "2027-04-14",
"value": "28,500,000 ريال",
"clauses": [
{
"title": "نطاق الأعمال",
"content": "يشمل نطاق الأعمال في هذا العقد إنشاء مجمع سكني مكون من 40 فيلا و 80 شقة سكنية، بالإضافة إلى المرافق الخدمية والترفيهية."
},
{
"title": "مدة التنفيذ",
"content": "مدة تنفيذ المشروع 24 شهراً تبدأ من تاريخ استلام الموقع، ويمكن تمديد المدة في حال وجود ظروف قاهرة يتفق عليها الطرفان."
},
{
"title": "قيمة العقد وطريقة الدفع",
"content": "قيمة العقد الإجمالية هي 28,500,000 ريال سعودي، ويتم السداد على دفعات شهرية بناءً على نسبة الإنجاز في المشروع."
},
{
"title": "الضمانات",
"content": "يلتزم المقاول بتقديم ضمان بنكي بقيمة 5% من قيمة العقد لضمان حسن التنفيذ، وضمان صيانة لمدة سنة بعد الانتهاء من المشروع."
},
{
"title": "الغرامات والجزاءات",
"content": "في حال تأخر المقاول عن تسليم المشروع في الموعد المحدد، يتم فرض غرامة تأخير بنسبة 0.1% من قيمة العقد عن كل يوم تأخير، بحد أقصى 10% من قيمة العقد."
}
]
},
{
"id": "CNT002",
"name": "عقد توريد وتركيب أنظمة تكييف",
"client": "شركة التطوير العقاري المحدودة",
"start_date": "2025-03-01",
"end_date": "2025-08-31",
"value": "4,200,000 ريال",
"clauses": [
{
"title": "نطاق التوريد",
"content": "يشمل نطاق التوريد في هذا العقد توفير وتركيب 120 وحدة تكييف مركزي للمبنى الإداري الجديد، بالإضافة إلى خدمات الصيانة لمدة عام."
},
{
"title": "مواصفات الأجهزة",
"content": "يجب أن تكون جميع الأجهزة الموردة من إحدى العلامات التجارية المعتمدة (كارير، دايكن، أو ميتسوبيشي)، وأن تكون مطابقة للمواصفات الفنية المرفقة بالعقد."
},
{
"title": "مدة التوريد والتركيب",
"content": "يلتزم المورد بتوريد وتركيب جميع الأجهزة خلال مدة لا تتجاوز 6 أشهر من تاريخ توقيع العقد."
},
{
"title": "الضمان",
"content": "يقدم المورد ضماناً لجميع الأجهزة لمدة 3 سنوات من تاريخ التشغيل، ويشمل الضمان جميع أعمال الصيانة وقطع الغيار."
}
]
}
]
def _get_documents(self):
"""
الحصول على قائمة المستندات
الإرجاع:
قائمة المستندات
"""
# في التطبيق الفعلي، سيتم جلب البيانات من قاعدة البيانات
# هنا نستخدم بيانات وهمية للعرض التوضيحي
return [
{
"id": "DOC001",
"name": "كراسة شروط مناقصة تطوير طريق الملك عبدالله",
"type": "كراسة شروط",
"page_count": 85,
"file_size": "2.4 ميجابايت",
"upload_date": "2025-02-10"
},
{
"id": "DOC002",
"name": "عقد إنشاء مجمع سكني",
"type": "عقد",
"page_count": 42,
"file_size": "1.8 ميجابايت",
"upload_date": "2025-04-12"
},
{
"id": "DOC003",
"name": "تقرير دراسة جدوى مشروع توسعة مستشفى",
"type": "تقرير",
"page_count": 65,
"file_size": "3.1 ميجابايت",
"upload_date": "2025-01-25"
}
]
# تطبيق وحدة الترجمة الصوتية متعددة اللغات
class VoiceNarrationApp:
"""وحدة تطبيق الترجمة الصوتية متعددة اللغات"""
def __init__(self):
"""تهيئة وحدة تطبيق الترجمة الصوتية متعددة اللغات"""
self.voice_over_system = VoiceOverSystem()
def render(self):
"""عرض واجهة وحدة تطبيق الترجمة الصوتية متعددة اللغات"""
st.markdown("<h2 class='module-title'>نظام الترجمة الصوتية متعددة اللغات</h2>", unsafe_allow_html=True)
st.markdown("""
<div class="module-description">
يتيح لك نظام الترجمة الصوتية متعددة اللغات تحويل النصوص والمستندات إلى ملفات صوتية بلغات متعددة،
مما يساعد في توصيل المعلومات بشكل أفضل للأشخاص من خلفيات لغوية مختلفة.
</div>
""", unsafe_allow_html=True)
# عرض نظام الترجمة الصوتية
self.voice_over_system.render()
# تشغيل التطبيق بشكل مستقل عند استدعاء الملف مباشرة
if __name__ == "__main__":
st.set_page_config(
page_title="الترجمة الصوتية متعددة اللغات | WAHBi AI",
page_icon="🎙️",
layout="wide",
initial_sidebar_state="expanded"
)
app = VoiceNarrationApp()
app.render()