Spaces:
Sleeping
Sleeping
Commit ·
6123728
0
Parent(s):
Initial commit without node_modules
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +11 -0
- .gitignore +47 -0
- Dockerfile +41 -0
- INTEGRATION_SOLUTION.md +75 -0
- PERFORMANCE_IMPROVEMENTS.md +76 -0
- QUICK_START.md +202 -0
- README.md +221 -0
- README_AR.md +134 -0
- SOLUTION_SUMMARY.md +69 -0
- SUMMARY_FIX_REPORT.md +169 -0
- TECHNICAL_IMPLEMENTATION.md +299 -0
- TROUBLESHOOTING.md +251 -0
- app.py +693 -0
- app_config.py +59 -0
- app_launcher.py +43 -0
- audio_processor.py +388 -0
- comprehensive_test.py +225 -0
- custom_components/st-audiorec/.streamlit/config.toml +3 -0
- custom_components/st-audiorec/LICENCE +3 -0
- custom_components/st-audiorec/README.md +3 -0
- custom_components/st-audiorec/demo.py +3 -0
- custom_components/st-audiorec/setup.py +3 -0
- custom_components/st-audiorec/st_audiorec/__init__.py +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/.prettierrc +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/asset-manifest.json +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/bootstrap.min.css +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/index.html +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/precache-manifest.30096e2fd9f149157a833e729e772f72.js +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/service-worker.js +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/static/js/2.ca2bba73.chunk.js +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/static/js/2.ca2bba73.chunk.js.LICENSE.txt +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/static/js/2.ca2bba73.chunk.js.map +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/static/js/main.85742990.chunk.js +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/static/js/main.85742990.chunk.js.map +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/static/js/runtime-main.11ec9aca.js +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/static/js/runtime-main.11ec9aca.js.map +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/build/styles.css +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/package.json +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/public/bootstrap.min.css +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/public/index.html +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/public/styles.css +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/src/StreamlitAudioRecorder.tsx +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/src/index.tsx +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/src/react-app-env.d.ts +3 -0
- custom_components/st-audiorec/st_audiorec/frontend/tsconfig.json +3 -0
- database.py +231 -0
- diagnose_summary.py +257 -0
- fast_loading.py +52 -0
- main.py +49 -0
- mp3_embedder.py +323 -0
.gitattributes
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
custom_components/st-audiorec/** filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.wav filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.ogg filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.tar.gz filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
custom_components/** filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
*.pyc
|
| 7 |
+
*.log
|
| 8 |
+
*.sqlite3
|
| 9 |
+
*.db
|
| 10 |
+
venv/
|
| 11 |
+
.venv/
|
| 12 |
+
env/
|
| 13 |
+
ENV/
|
| 14 |
+
env.bak/
|
| 15 |
+
pip-wheel-metadata/
|
| 16 |
+
dist/
|
| 17 |
+
*.egg-info/
|
| 18 |
+
|
| 19 |
+
# Editor / OS
|
| 20 |
+
.vscode/
|
| 21 |
+
.idea/
|
| 22 |
+
*.swp
|
| 23 |
+
.DS_Store
|
| 24 |
+
Thumbs.db
|
| 25 |
+
|
| 26 |
+
# Node / frontend
|
| 27 |
+
node_modules/
|
| 28 |
+
npm-debug.log*
|
| 29 |
+
yarn-error.log*
|
| 30 |
+
package-lock.json
|
| 31 |
+
.pnpm-debug.log
|
| 32 |
+
|
| 33 |
+
# Specific frontend inside your component
|
| 34 |
+
custom_components/st-audiorec/st_audiorec/frontend/node_modules/
|
| 35 |
+
|
| 36 |
+
# Virtual env / credentials
|
| 37 |
+
*.env
|
| 38 |
+
.env.*
|
| 39 |
+
|
| 40 |
+
# Hugging Face / caches
|
| 41 |
+
.cache/
|
| 42 |
+
.hf/
|
| 43 |
+
|
| 44 |
+
# IDE metadata
|
| 45 |
+
*.sublime-workspace
|
| 46 |
+
*.sublime-project
|
| 47 |
+
custom_components/st-audiorec/st_audiorec/frontend/node_modules
|
Dockerfile
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# -- Dockerfile for Streamlit app --
|
| 3 |
+
#
|
| 4 |
+
|
| 5 |
+
# Base image
|
| 6 |
+
FROM python:3.9-slim
|
| 7 |
+
|
| 8 |
+
# Set working directory
|
| 9 |
+
WORKDIR /app
|
| 10 |
+
|
| 11 |
+
# Install system dependencies (including ffmpeg)
|
| 12 |
+
RUN apt-get update && apt-get install -y \
|
| 13 |
+
build-essential \
|
| 14 |
+
ffmpeg \
|
| 15 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 16 |
+
|
| 17 |
+
# Copy requirements file
|
| 18 |
+
COPY requirements.txt ./requirements.txt
|
| 19 |
+
|
| 20 |
+
# Install Python dependencies
|
| 21 |
+
RUN pip install --no-cache-dir --upgrade pip
|
| 22 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 23 |
+
|
| 24 |
+
# Copy the entire app
|
| 25 |
+
COPY . .
|
| 26 |
+
|
| 27 |
+
# Create .streamlit directory and set permissions
|
| 28 |
+
RUN mkdir -p /app/.streamlit && \
|
| 29 |
+
chmod -R 755 /app/.streamlit
|
| 30 |
+
|
| 31 |
+
# Set environment variable for Streamlit config
|
| 32 |
+
ENV STREAMLIT_CONFIG_DIR=/app/.streamlit
|
| 33 |
+
|
| 34 |
+
# Expose the port that Streamlit runs on
|
| 35 |
+
EXPOSE 8501
|
| 36 |
+
|
| 37 |
+
# Add a health check
|
| 38 |
+
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 39 |
+
|
| 40 |
+
# Command to run the app
|
| 41 |
+
ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
INTEGRATION_SOLUTION.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SyncMaster - Integrated Setup
|
| 2 |
+
|
| 3 |
+
## 🚀 التشغيل المبسط (HuggingFace Ready)
|
| 4 |
+
|
| 5 |
+
الآن يمكنك تشغيل التطبيق بأمر واحد فقط:
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
npm run dev
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
أو
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
npm start
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
## 🔧 كيف تم حل المشكلة
|
| 18 |
+
|
| 19 |
+
### المشكلة السابقة:
|
| 20 |
+
- كان يتطلب تشغيل `python recorder_server.py` و `npm run dev` بشكل منفصل
|
| 21 |
+
- غير مناسب للنشر على HuggingFace أو المنصات السحابية
|
| 22 |
+
|
| 23 |
+
### الحل الجديد:
|
| 24 |
+
1. **خادم متكامل**: تم إنشاء `integrated_server.py` الذي يشغل خادم التسجيل تلقائياً
|
| 25 |
+
2. **نقطة دخول موحدة**: ملف `main.py` يبدأ كل شيء معاً
|
| 26 |
+
3. **تكوين ذكي**: يكتشف البيئة تلقائياً (محلي أو سحابي)
|
| 27 |
+
|
| 28 |
+
## 📁 الملفات الجديدة
|
| 29 |
+
|
| 30 |
+
- `integrated_server.py` - يدير خادم التسجيل المدمج
|
| 31 |
+
- `main.py` - نقطة الدخول الرئيسية
|
| 32 |
+
- `app_config.py` - إعدادات التطبيق
|
| 33 |
+
- `startup.py` - مُشغل متقدم للتطوير
|
| 34 |
+
|
| 35 |
+
## 🎯 للاستخدام العادي
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
# تشغيل التطبيق (يشمل خادم التسجيل)
|
| 39 |
+
npm run dev
|
| 40 |
+
|
| 41 |
+
# أو استخدام Python مباشرة
|
| 42 |
+
streamlit run main.py
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
## ⚙️ للتطوير المتقدم
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
# تشغيل الخوادم بشكل منفصل (للتطوير)
|
| 49 |
+
npm run dev-separate
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
## 🌐 للنشر على HuggingFace
|
| 53 |
+
|
| 54 |
+
فقط ارفع المشروع واستخدم:
|
| 55 |
+
- **Command**: `npm run start`
|
| 56 |
+
- **Port**: `5050`
|
| 57 |
+
|
| 58 |
+
سيتم تشغيل خادم التسجيل تلقائياً في الخلفية!
|
| 59 |
+
|
| 60 |
+
## ✅ اختبار النظام
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
python integrated_server.py
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## 🎉 النتيجة
|
| 67 |
+
|
| 68 |
+
- **✅ تشغيل بأمر واحد فقط**
|
| 69 |
+
- **✅ جاهز للنشر على HuggingFace**
|
| 70 |
+
- **✅ يعمل محلياً وسحابياً**
|
| 71 |
+
- **✅ لا حاجة لتشغيل أوامر متعددة**
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
المشكلة محلولة! الآن يمكنك استخدام `npm run dev` فقط وسيعمل كل شيء تلقائياً 🎊
|
PERFORMANCE_IMPROVEMENTS.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 تحسينات الأداء - مشكلة الشاشة البيضاء محلولة
|
| 2 |
+
|
| 3 |
+
## 🔍 التحليل والمشكلة:
|
| 4 |
+
كانت المشكلة أن خادم التسجيل يبدأ **بشكل متزامن** عند تحميل الصفحة، مما يسبب:
|
| 5 |
+
- ⏰ تأخير في التحميل (نصف ثانية إلى ثانية)
|
| 6 |
+
- ⚪ شاشة بيضاء أثناء انتظار بدء الخادم
|
| 7 |
+
- 🐌 تجربة مستخدم بطيئة
|
| 8 |
+
|
| 9 |
+
## ✅ الحلول المطبقة:
|
| 10 |
+
|
| 11 |
+
### 1. **تشغيل غير متزامن للخادم**
|
| 12 |
+
```python
|
| 13 |
+
# بدلاً من:
|
| 14 |
+
ensure_recorder_server() # يحجب الواجهة
|
| 15 |
+
|
| 16 |
+
# الآن:
|
| 17 |
+
recorder_thread = threading.Thread(target=start_recorder_async, daemon=True)
|
| 18 |
+
recorder_thread.start() # لا يحجب الواجهة
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
### 2. **تسريع فحص الاستجابة**
|
| 22 |
+
```python
|
| 23 |
+
# قبل: timeout=3 ثوان
|
| 24 |
+
# الآن: timeout=0.5 ثانية
|
| 25 |
+
response = requests.get(url, timeout=0.5)
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
### 3. **تحسين انتظار بدء الخادم**
|
| 29 |
+
```python
|
| 30 |
+
# قبل: sleep(1) × 10 مرات = 10 ثوان
|
| 31 |
+
# الآن: sleep(0.5) × 15 مرة = 7.5 ثانية
|
| 32 |
+
time.sleep(0.5)
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
### 4. **تحسين CSS لمنع الفلاش**
|
| 36 |
+
```css
|
| 37 |
+
.main .block-container {
|
| 38 |
+
animation: fadeIn 0.2s ease-in-out;
|
| 39 |
+
}
|
| 40 |
+
.stSpinner { display: none !important; }
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### 5. **فحص ذكي للخادم**
|
| 44 |
+
```python
|
| 45 |
+
# فحص سريع أولاً
|
| 46 |
+
if integrated_server.is_server_responding():
|
| 47 |
+
return True # خروج فوري إذا كان يعمل
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
## 📊 النتائج:
|
| 51 |
+
|
| 52 |
+
### قبل التحسين:
|
| 53 |
+
- ⏱️ **تحميل الصفحة**: 1+ ثانية
|
| 54 |
+
- ⚪ **شاشة بيضاء**: نعم
|
| 55 |
+
- 🔄 **تأخير ملحوظ**: نعم
|
| 56 |
+
|
| 57 |
+
### بعد التحسين:
|
| 58 |
+
- ⏱️ **تحميل الصفحة**: 0.008-0.023 ثانية
|
| 59 |
+
- ⚪ **شاشة بيضاء**: لا
|
| 60 |
+
- ⚡ **تحميل فوري**: نعم
|
| 61 |
+
|
| 62 |
+
## 🎯 التحسينات الإضافية:
|
| 63 |
+
|
| 64 |
+
1. **عدم عرض رسائل تحميل غير ضرورية**
|
| 65 |
+
2. **بدء الخادم في الخلفية فقط عند الحاجة**
|
| 66 |
+
3. **تقليل عدد رسائل السجل**
|
| 67 |
+
4. **تحسين CSS للانتقالات السلسة**
|
| 68 |
+
|
| 69 |
+
## 🚀 النتيجة النهائية:
|
| 70 |
+
|
| 71 |
+
✅ **لا مزيد من الشاشة البيضاء**
|
| 72 |
+
✅ **تحميل فوري للمحتوى**
|
| 73 |
+
✅ **تجربة مستخدم سلسة**
|
| 74 |
+
✅ **أداء ممتاز (0.008 ثانية)**
|
| 75 |
+
|
| 76 |
+
**المشكلة محلولة تماماً!** 🎊
|
QUICK_START.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🎯 دليل الإصلاح والتشغيل السريع - SyncMaster Enhanced
|
| 2 |
+
# Quick Fix and Startup Guide - SyncMaster Enhanced
|
| 3 |
+
|
| 4 |
+
## ✅ النظام جاهز للعمل! / System Ready!
|
| 5 |
+
|
| 6 |
+
تم اختبار جميع المكونات بنجاح ✅ All components tested successfully
|
| 7 |
+
|
| 8 |
+
## 🚀 طرق التشغيل / Startup Methods
|
| 9 |
+
|
| 10 |
+
### 1. التشغيل التلقائي المتقدم / Advanced Auto-Start (موصى به / Recommended)
|
| 11 |
+
```bash
|
| 12 |
+
python start_debug.py
|
| 13 |
+
```
|
| 14 |
+
**المزايا / Benefits:**
|
| 15 |
+
- فحص تلقائي للمشاكل / Automatic problem detection
|
| 16 |
+
- إصلاح تضارب المنافذ / Port conflict resolution
|
| 17 |
+
- رسائل خطأ واضحة / Clear error messages
|
| 18 |
+
- تشغيل آمن / Safe startup
|
| 19 |
+
|
| 20 |
+
### 2. التشغيل اليدوي / Manual Startup
|
| 21 |
+
```bash
|
| 22 |
+
# النافذة الأولى / First Terminal
|
| 23 |
+
python recorder_server.py
|
| 24 |
+
|
| 25 |
+
# النافذة الثانية / Second Terminal
|
| 26 |
+
streamlit run app.py --server.port 8501
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
### 3. التشغيل السريع / Quick Start (Windows)
|
| 30 |
+
```bash
|
| 31 |
+
start_enhanced.bat
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## 🌐 الروابط / URLs
|
| 35 |
+
|
| 36 |
+
بعد التشغيل الناجح / After successful startup:
|
| 37 |
+
|
| 38 |
+
- **🎙️ واجهة التسجيل / Recording Interface**: http://localhost:5001
|
| 39 |
+
- **💻 التطبيق الرئيسي / Main Application**: http://localhost:8501
|
| 40 |
+
- **🔄 فحص حالة الخادم / Server Status**: http://localhost:5001/record
|
| 41 |
+
|
| 42 |
+
## 📋 خطوات الاستخدام / Usage Steps
|
| 43 |
+
|
| 44 |
+
### للطلاب الجدد / For New Users:
|
| 45 |
+
|
| 46 |
+
#### 1. إعداد اللغة / Language Setup
|
| 47 |
+
- اختر اللغة المفضلة (عربي/English)
|
| 48 |
+
- فعّل الترجمة التلقائية
|
| 49 |
+
- اختر اللغة المستهدفة
|
| 50 |
+
|
| 51 |
+
#### 2. التسجيل / Recording
|
| 52 |
+
- اذهب لتبويب "🎙️ Record Audio"
|
| 53 |
+
- اضغط "Start Recording" / "بدء التسجيل"
|
| 54 |
+
- تحدث بوضوح
|
| 55 |
+
- استخدم "Mark Important" للنقاط المهمة
|
| 56 |
+
- اضغط "Stop" عند الانتهاء
|
| 57 |
+
|
| 58 |
+
#### 3. المعالجة / Processing
|
| 59 |
+
- اضغط "Extract Text" / "استخراج النص"
|
| 60 |
+
- انتظر المعالجة (قد تستغرق دقائق)
|
| 61 |
+
- راجع النص الأصلي والمترجم
|
| 62 |
+
|
| 63 |
+
#### 4. الحفظ / Saving
|
| 64 |
+
- انسخ النص المطلوب
|
| 65 |
+
- احفظ ملف JSON للمراجعة لاحقاً
|
| 66 |
+
|
| 67 |
+
## 🔧 استكشاف الأخطاء / Troubleshooting
|
| 68 |
+
|
| 69 |
+
### المشكلة الأكثر شيوعاً / Most Common Issue:
|
| 70 |
+
```
|
| 71 |
+
Error: Failed to fetch
|
| 72 |
+
POST http://localhost:5001/record net::ERR_CONNECTION_REFUSED
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### الحل السريع / Quick Fix:
|
| 76 |
+
```bash
|
| 77 |
+
# 1. أوقف جميع العمليات / Stop all processes
|
| 78 |
+
taskkill /f /im python.exe
|
| 79 |
+
|
| 80 |
+
# 2. شغّل الاختبار / Run test
|
| 81 |
+
python test_system.py
|
| 82 |
+
|
| 83 |
+
# 3. شغّل النظام / Start system
|
| 84 |
+
python start_debug.py
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### إذا لم يعمل / If Still Not Working:
|
| 88 |
+
```bash
|
| 89 |
+
# فحص المنافذ / Check ports
|
| 90 |
+
netstat -an | findstr :5001
|
| 91 |
+
netstat -an | findstr :8501
|
| 92 |
+
|
| 93 |
+
# إعادة تثبيت التبعيات / Reinstall dependencies
|
| 94 |
+
pip install --upgrade -r requirements.txt
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
## 💡 نصائح مهمة / Important Tips
|
| 98 |
+
|
| 99 |
+
### للحصول على أفضل النتائج / For Best Results:
|
| 100 |
+
|
| 101 |
+
#### جودة التسجيل / Recording Quality:
|
| 102 |
+
- استخدم سماعة رأس بميكروفون
|
| 103 |
+
- اجلس في مكان هادئ
|
| 104 |
+
- تحدث بوضوح وبطء نسبي
|
| 105 |
+
- تجنب الضوضاء الخلفية
|
| 106 |
+
|
| 107 |
+
#### إعدادات الترجمة / Translation Settings:
|
| 108 |
+
- **للطلاب العرب**: فعّل الترجمة للإنجليزية لفهم المصطلحات التقنية
|
| 109 |
+
- **للطلاب الدوليين**: استخدم الترجمة للغتك الأم
|
| 110 |
+
- **للمحاضرات المختلطة**: راجع النص بكلا اللغتين
|
| 111 |
+
|
| 112 |
+
#### استخدام العلامات / Using Markers:
|
| 113 |
+
- ضع علامة عند المفاهيم الجديدة
|
| 114 |
+
- اعلم النقاط المهمة للامتحان
|
| 115 |
+
- استخدم العلامات للتنظيم
|
| 116 |
+
|
| 117 |
+
## 📱 متطلبات النظام / System Requirements
|
| 118 |
+
|
| 119 |
+
### الحد الأدنى / Minimum:
|
| 120 |
+
- Python 3.8+
|
| 121 |
+
- 4 GB RAM
|
| 122 |
+
- اتصال إنترنت للترجمة
|
| 123 |
+
- مساحة 1 GB على القرص الصلب
|
| 124 |
+
|
| 125 |
+
### الموصى به / Recommended:
|
| 126 |
+
- Python 3.10+
|
| 127 |
+
- 8 GB RAM
|
| 128 |
+
- اتصال إنترنت سريع
|
| 129 |
+
- SSD للتخزين
|
| 130 |
+
- ميكروفون عالي الجودة
|
| 131 |
+
|
| 132 |
+
## 🌟 ميزات متقدمة / Advanced Features
|
| 133 |
+
|
| 134 |
+
### اختصارات لوحة المفاتيح / Keyboard Shortcuts:
|
| 135 |
+
- **Space**: بدء/إيقاف التسجيل
|
| 136 |
+
- **M**: وضع علامة مهمة
|
| 137 |
+
- **P**: إيقاف مؤقت/استئناف
|
| 138 |
+
- **R**: إعادة تسجيل
|
| 139 |
+
|
| 140 |
+
### واجهة برمجة التطبيقات / API Features:
|
| 141 |
+
- ترجمة نصوص مستقلة
|
| 142 |
+
- معالجة مجمعة للملفات
|
| 143 |
+
- كشف اللغة التلقائي
|
| 144 |
+
- تخصيص إعدادات الصوت
|
| 145 |
+
|
| 146 |
+
## 📞 الدعم التقني / Technical Support
|
| 147 |
+
|
| 148 |
+
### أدوات التشخيص / Diagnostic Tools:
|
| 149 |
+
```bash
|
| 150 |
+
# اختبار شامل / Complete test
|
| 151 |
+
python test_system.py
|
| 152 |
+
|
| 153 |
+
# فحص الاتصال / Connection test
|
| 154 |
+
python -c "import requests; print(requests.get('http://localhost:5001/record').status_code)"
|
| 155 |
+
|
| 156 |
+
# اختبار الترجمة / Translation test
|
| 157 |
+
python -c "from translator import AITranslator; t=AITranslator(); print(t.translate_text('Hello', 'ar'))"
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
### ملفات السجل / Log Files:
|
| 161 |
+
- تحقق من console المتصفح (F12)
|
| 162 |
+
- راجع سجلات الطرفية
|
| 163 |
+
- ابحث عن ملفات tmp*.json
|
| 164 |
+
|
| 165 |
+
## 🎓 للمدرسين والمحاضرين / For Teachers and Lecturers
|
| 166 |
+
|
| 167 |
+
### إعدادات الفصل / Classroom Setup:
|
| 168 |
+
- تأكد من إذن التسجيل
|
| 169 |
+
- وضح للطلاب كيفية الاستخدام
|
| 170 |
+
- اقترح جلسات تدريبية
|
| 171 |
+
|
| 172 |
+
### نصائح للمحاضرات / Lecture Tips:
|
| 173 |
+
- تحدث بوضوح
|
| 174 |
+
- اكرر المصطلحات المهمة
|
| 175 |
+
- استخدم فترات صمت قصيرة
|
| 176 |
+
- اشرح بعدة لغات إذا أمكن
|
| 177 |
+
|
| 178 |
+
---
|
| 179 |
+
|
| 180 |
+
## 🎉 مبروك! / Congratulations!
|
| 181 |
+
|
| 182 |
+
**النظام جاهز للاستخدام! / System is ready to use!**
|
| 183 |
+
|
| 184 |
+
```bash
|
| 185 |
+
# للبدء الآن / To start now:
|
| 186 |
+
python start_debug.py
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
**استمتع بتجربة تعليمية محسنة مع SyncMaster! 🚀**
|
| 190 |
+
**Enjoy an enhanced learning experience with SyncMaster! 🚀**
|
| 191 |
+
|
| 192 |
+
---
|
| 193 |
+
|
| 194 |
+
### 📋 Checklist
|
| 195 |
+
|
| 196 |
+
- ✅ Python مثبت / Python installed
|
| 197 |
+
- ✅ التبعيات مثبتة / Dependencies installed
|
| 198 |
+
- ✅ مفتاح API مُعد / API key configured
|
| 199 |
+
- ✅ اختبار النظام نجح / System test passed
|
| 200 |
+
- ✅ جاهز للاستخدام / Ready to use
|
| 201 |
+
|
| 202 |
+
**🎯 التالي: python start_debug.py**
|
README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SyncMaster Enhanced
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 8501
|
| 8 |
+
tags:
|
| 9 |
+
- streamlit
|
| 10 |
+
- ai-translation
|
| 11 |
+
- speech-to-text
|
| 12 |
+
- multilingual
|
| 13 |
+
- education
|
| 14 |
+
pinned: false
|
| 15 |
+
short_description: AI-powered audio transcription
|
| 16 |
+
license: mit
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
# SyncMaster Enhanced - AI-Powered Audio Transcription & Translation
|
| 20 |
+
|
| 21 |
+
> **🌟 New: Enhanced with AI Translation Support for International Students**
|
| 22 |
+
> **جديد: محسن مع دعم الترجمة بالذكاء الاصطناعي للطلاب الدوليين**
|
| 23 |
+
|
| 24 |
+
SyncMaster is an intelligent audio-text synchronization platform specifically designed for international students in universities. It provides real-time audio recording, AI-powered transcription, and automatic translation to help students better understand and review their lectures.
|
| 25 |
+
|
| 26 |
+
## ✨ Key Features
|
| 27 |
+
|
| 28 |
+
### 🌐 Multi-Language Support
|
| 29 |
+
- **Full Arabic Interface**: Complete Arabic UI for better accessibility
|
| 30 |
+
- **AI-Powered Translation**: Automatic translation to Arabic, English, French, and Spanish
|
| 31 |
+
- **Language Detection**: Automatically detects the source language
|
| 32 |
+
- **Academic Context**: Specialized translation for academic content
|
| 33 |
+
|
| 34 |
+
### 🎙️ Enhanced Recording
|
| 35 |
+
- **Browser-based Recording**: Record directly from your web browser
|
| 36 |
+
- **Real-time Audio Visualization**: Visual feedback during recording
|
| 37 |
+
- **Important Markers**: Mark important points during lectures
|
| 38 |
+
- **Pause/Resume**: Full control over recording sessions
|
| 39 |
+
|
| 40 |
+
### 🤖 AI Technology
|
| 41 |
+
- **Gemini AI Integration**: Accurate transcription using Google's Gemini AI
|
| 42 |
+
- **Advanced Translation**: Context-aware translation for educational content
|
| 43 |
+
- **Parallel Processing**: Fast and efficient audio processing
|
| 44 |
+
|
| 45 |
+
### 📱 Student-Friendly Features
|
| 46 |
+
- **Responsive Design**: Works on desktop, tablet, and mobile
|
| 47 |
+
- **Keyboard Shortcuts**: Quick access to common functions
|
| 48 |
+
- **Accessibility**: Screen reader support and RTL language support
|
| 49 |
+
- **Offline Capability**: Process recordings without constant internet
|
| 50 |
+
|
| 51 |
+
## 🚀 Quick Start
|
| 52 |
+
|
| 53 |
+
### For International Students:
|
| 54 |
+
|
| 55 |
+
1. **Setup**:
|
| 56 |
+
```bash
|
| 57 |
+
# Clone or download the project
|
| 58 |
+
# Install Python 3.8+
|
| 59 |
+
python setup_enhanced.py
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
2. **Run**:
|
| 63 |
+
```bash
|
| 64 |
+
# Windows
|
| 65 |
+
start_enhanced.bat
|
| 66 |
+
|
| 67 |
+
# Linux/Mac
|
| 68 |
+
python setup_enhanced.py
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
3. **Configure**:
|
| 72 |
+
- Add your Gemini API key to `.env` file
|
| 73 |
+
- Choose your preferred language (Arabic/English)
|
| 74 |
+
- Enable translation and select target language
|
| 75 |
+
|
| 76 |
+
### API Key Setup:
|
| 77 |
+
1. Get a free Gemini API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
|
| 78 |
+
2. Add it to your `.env` file:
|
| 79 |
+
```
|
| 80 |
+
GEMINI_API_KEY=your_api_key_here
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
## 📖 Usage Guide
|
| 84 |
+
|
| 85 |
+
### Recording Lectures:
|
| 86 |
+
1. Go to the **Record Audio** tab
|
| 87 |
+
2. Click **Start Recording**
|
| 88 |
+
3. Use **Mark Important** for key points
|
| 89 |
+
4. Click **Stop** when finished
|
| 90 |
+
5. Click **Extract Text** to process
|
| 91 |
+
|
| 92 |
+
### Translation:
|
| 93 |
+
1. Enable translation in settings
|
| 94 |
+
2. Select target language
|
| 95 |
+
3. Process your audio
|
| 96 |
+
4. Review both original and translated text
|
| 97 |
+
|
| 98 |
+
### Export Options:
|
| 99 |
+
- Copy text for notes
|
| 100 |
+
- Save as files for later review
|
| 101 |
+
- Generate synchronized videos (coming soon)
|
| 102 |
+
|
| 103 |
+
## 🎓 For Students
|
| 104 |
+
|
| 105 |
+
### Arabic Students (للطلاب العرب):
|
| 106 |
+
- استخدم الواجهة العربية لسهولة الاستخدام
|
| 107 |
+
- فعّل الترجمة للإنجليزية لفهم المصطلحات التقنية
|
| 108 |
+
- ضع علامات على المفاهيم الجديدة أثناء المحاضرة
|
| 109 |
+
|
| 110 |
+
### International Students:
|
| 111 |
+
- Use translation to your native language for better understanding
|
| 112 |
+
- Mark important concepts during lectures
|
| 113 |
+
- Review both original and translated text together
|
| 114 |
+
|
| 115 |
+
## ⌨️ Keyboard Shortcuts
|
| 116 |
+
- **Space**: Start/Stop recording
|
| 117 |
+
- **M**: Mark important point
|
| 118 |
+
- **P**: Pause/Resume
|
| 119 |
+
- **R**: Re-record
|
| 120 |
+
|
| 121 |
+
## 🔧 Technical Requirements
|
| 122 |
+
|
| 123 |
+
### System Requirements:
|
| 124 |
+
- Python 3.8 or higher
|
| 125 |
+
- Modern web browser (Chrome, Firefox, Safari, Edge)
|
| 126 |
+
- Microphone access for recording
|
| 127 |
+
- Internet connection for AI processing
|
| 128 |
+
|
| 129 |
+
### Dependencies:
|
| 130 |
+
- Streamlit (Web interface)
|
| 131 |
+
- Google Generative AI (Transcription & Translation)
|
| 132 |
+
- Flask (Recording server)
|
| 133 |
+
- LibROSA (Audio processing)
|
| 134 |
+
|
| 135 |
+
## 📱 Browser Compatibility
|
| 136 |
+
|
| 137 |
+
| Browser | Recording | Translation | UI |
|
| 138 |
+
|---------|-----------|-------------|----|
|
| 139 |
+
| Chrome | ✅ | ✅ | ✅ |
|
| 140 |
+
| Firefox | ✅ | ✅ | ✅ |
|
| 141 |
+
| Safari | ✅ | ✅ | ✅ |
|
| 142 |
+
| Edge | ✅ | ✅ | ✅ |
|
| 143 |
+
|
| 144 |
+
## 🛠️ Troubleshooting
|
| 145 |
+
|
| 146 |
+
### Common Issues:
|
| 147 |
+
|
| 148 |
+
**Microphone not working:**
|
| 149 |
+
- Grant microphone permission to your browser
|
| 150 |
+
- Check system audio settings
|
| 151 |
+
- Try a different browser
|
| 152 |
+
|
| 153 |
+
**Translation errors:**
|
| 154 |
+
- Check internet connection
|
| 155 |
+
- Verify Gemini API key
|
| 156 |
+
- Try processing again
|
| 157 |
+
|
| 158 |
+
**Poor transcription quality:**
|
| 159 |
+
- Ensure clear audio recording
|
| 160 |
+
- Reduce background noise
|
| 161 |
+
- Speak clearly and at moderate pace
|
| 162 |
+
|
| 163 |
+
## 🔮 Roadmap
|
| 164 |
+
|
| 165 |
+
### Coming Soon:
|
| 166 |
+
- **Smart Content Analysis**: Automatic extraction of key concepts
|
| 167 |
+
- **Study Cards**: Generate flashcards from lectures
|
| 168 |
+
- **Platform Integration**: Connect with Moodle, Canvas, etc.
|
| 169 |
+
- **Collaborative Features**: Share recordings with classmates
|
| 170 |
+
- **Advanced Analytics**: Learning progress tracking
|
| 171 |
+
|
| 172 |
+
## 📚 Documentation
|
| 173 |
+
|
| 174 |
+
- [**Arabic Guide**](README_AR.md) - دليل باللغة العربية
|
| 175 |
+
- [**API Documentation**](docs/api.md) - Technical API reference
|
| 176 |
+
- [**Troubleshooting**](docs/troubleshooting.md) - Detailed problem solving
|
| 177 |
+
|
| 178 |
+
## 🤝 Contributing
|
| 179 |
+
|
| 180 |
+
We welcome contributions from the international student community:
|
| 181 |
+
|
| 182 |
+
1. Fork the repository
|
| 183 |
+
2. Create a feature branch
|
| 184 |
+
3. Add your improvements
|
| 185 |
+
4. Submit a pull request
|
| 186 |
+
|
| 187 |
+
### Areas for Contribution:
|
| 188 |
+
- Additional language support
|
| 189 |
+
- UI improvements
|
| 190 |
+
- Mobile optimization
|
| 191 |
+
- Documentation translation
|
| 192 |
+
|
| 193 |
+
## 📄 License
|
| 194 |
+
|
| 195 |
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
| 196 |
+
|
| 197 |
+
## 🙏 Acknowledgments
|
| 198 |
+
|
| 199 |
+
- Google Gemini AI for transcription and translation
|
| 200 |
+
- Streamlit team for the amazing web framework
|
| 201 |
+
- International student community for feedback and testing
|
| 202 |
+
|
| 203 |
+
## 📞 Support
|
| 204 |
+
|
| 205 |
+
For technical support or questions:
|
| 206 |
+
- Check the browser console (F12) for error details
|
| 207 |
+
- Review log files in the application directory
|
| 208 |
+
- Ensure all dependencies are up to date
|
| 209 |
+
|
| 210 |
+
---
|
| 211 |
+
|
| 212 |
+
**Made with ❤️ for international students worldwide**
|
| 213 |
+
**صُنع بـ ❤️ للطلاب الدوليين حول العالم**
|
| 214 |
+
|
| 215 |
+
---
|
| 216 |
+
|
| 217 |
+
### Quick Links:
|
| 218 |
+
- 🚀 [Quick Start Guide](docs/quickstart.md)
|
| 219 |
+
- 🌐 [Arabic Documentation](README_AR.md)
|
| 220 |
+
- 🎓 [Student Guide](docs/student-guide.md)
|
| 221 |
+
- 🔧 [Technical Setup](docs/technical-setup.md)
|
README_AR.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SyncMaster - دليل المستخدم للطلاب الأجانب
|
| 2 |
+
|
| 3 |
+
## 🎯 نظرة عامة
|
| 4 |
+
SyncMaster هو تطبيق ذكي مطور خصيصاً للطلاب الأجانب في الجامعات لتسجيل المحاضرات وتحويلها إلى نص مكتوب مع ترجمة فورية باستخدام الذكاء الاصطناعي.
|
| 5 |
+
|
| 6 |
+
## ✨ الميزات الجديدة
|
| 7 |
+
|
| 8 |
+
### 🌐 دعم متعدد اللغات
|
| 9 |
+
- **واجهة عربية كاملة**: تم تطوير واجهة باللغة العربية لتسهيل الاستخدام
|
| 10 |
+
- **ترجمة فورية**: ترجمة النص المنسوخ إلى العربية والإنجليزية والفرنسية والإسبانية
|
| 11 |
+
- **كشف اللغة التلقائي**: يتعرف النظام على لغة المحاضرة تلقائياً
|
| 12 |
+
|
| 13 |
+
### 🎙️ ميزات التسجيل المحسنة
|
| 14 |
+
- **تسجيل مباشر**: تسجيل المحاضرات مباشرة من المتصفح
|
| 15 |
+
- **علامات مهمة**: وضع علامات على النقاط المهمة أثناء التسجيل
|
| 16 |
+
- **مؤشر مستوى الصوت**: عرض مرئي لمستوى الصوت
|
| 17 |
+
- **إيقاف مؤقت واستئناف**: تحكم كامل في التسجيل
|
| 18 |
+
|
| 19 |
+
### 🤖 ذكاء اصطناعي متطور
|
| 20 |
+
- **نسخ دقيق**: استخدام Gemini AI لنسخ دقيق للمحاضرات
|
| 21 |
+
- **ترجمة محسنة**: ترجمة متخصصة للمحتوى الأكاديمي
|
| 22 |
+
- **معالجة متوازية**: معالجة سريعة وفعالة
|
| 23 |
+
|
| 24 |
+
## 🚀 كيفية الاستخدام
|
| 25 |
+
|
| 26 |
+
### الخطوة 1: إعداد اللغة
|
| 27 |
+
1. اختر لغة الواجهة من القائمة العلوية (العربية/English)
|
| 28 |
+
2. فعّل الترجمة التلقائية
|
| 29 |
+
3. اختر اللغة المستهدفة للترجمة
|
| 30 |
+
|
| 31 |
+
### الخطوة 2: التسجيل
|
| 32 |
+
1. اضغط على تبويب "🎙️ Record Audio"
|
| 33 |
+
2. اضغط "Start Recording" لبدء التسجيل
|
| 34 |
+
3. استخدم "Mark Important" لوضع علامات على النقاط المهمة
|
| 35 |
+
4. اضغط "Stop" لإنهاء التسجيل
|
| 36 |
+
|
| 37 |
+
### الخطوة 3: المعالجة والترجمة
|
| 38 |
+
1. اضغط "Extract Text" لبدء المعالجة
|
| 39 |
+
2. انتظر حتى يكتمل النسخ والترجمة
|
| 40 |
+
3. راجع النص الأصلي والمترجم
|
| 41 |
+
|
| 42 |
+
### الخطوة 4: التصدير
|
| 43 |
+
1. احفظ النتائج أو انسخها
|
| 44 |
+
2. استخدم الملف المحفوظ للمراجعة لاحقاً
|
| 45 |
+
|
| 46 |
+
## ⌨️ اختصارات لوحة المفاتيح
|
| 47 |
+
- **Space**: بدء/إيقاف التسجيل
|
| 48 |
+
- **M**: وضع علامة مهمة
|
| 49 |
+
- **P**: إيقاف مؤقت/استئناف
|
| 50 |
+
- **R**: إعادة تسجيل
|
| 51 |
+
|
| 52 |
+
## 📱 نصائح للطلاب الأجانب
|
| 53 |
+
|
| 54 |
+
### للطلاب العرب:
|
| 55 |
+
- استخدم الواجهة العربية للسهولة
|
| 56 |
+
- فعّل الترجمة للإنجليزية لفهم المصطلحات التقنية
|
| 57 |
+
- ضع علامات على المفاهيم الجديدة
|
| 58 |
+
|
| 59 |
+
### للطلاب الدوليين:
|
| 60 |
+
- استخدم الترجمة إلى لغتك الأم للفهم الأفضل
|
| 61 |
+
- اعتمد على العلامات المهمة للمراجعة السريعة
|
| 62 |
+
- راجع النص المترجم والأصلي معاً
|
| 63 |
+
|
| 64 |
+
## 🔧 إعدادات متقدمة
|
| 65 |
+
|
| 66 |
+
### جودة التسجيل:
|
| 67 |
+
- **عالية**: للمحاضرات المهمة (320 kbps)
|
| 68 |
+
- **متوسطة**: للاستخدام العادي (192 kbps)
|
| 69 |
+
- **منخفضة**: لتوفير المساحة (128 kbps)
|
| 70 |
+
|
| 71 |
+
### إعدادات الترجمة:
|
| 72 |
+
- **Arabic**: للطلاب العرب
|
| 73 |
+
- **English**: للمحتوى الدولي
|
| 74 |
+
- **French**: للطلاب الفرنكوفونيين
|
| 75 |
+
- **Spanish**: للطلاب الناطقين بالإسبانية
|
| 76 |
+
|
| 77 |
+
## 🛠️ استكشاف الأخطاء
|
| 78 |
+
|
| 79 |
+
### مشاكل الميكروفون:
|
| 80 |
+
1. تأكد من إعطاء إذن الميكروفون للمتصفح
|
| 81 |
+
2. تحقق من إعدادات الصوت في النظام
|
| 82 |
+
3. جرب متصفح آخر إذا لزم الأمر
|
| 83 |
+
|
| 84 |
+
### مشاكل الترجمة:
|
| 85 |
+
1. تأكد من اتصال الإنترنت
|
| 86 |
+
2. تحقق من صحة مفتاح API
|
| 87 |
+
3. جرب إعادة المعالجة
|
| 88 |
+
|
| 89 |
+
### مشاكل في النسخ:
|
| 90 |
+
1. تأكد من وضوح الصوت
|
| 91 |
+
2. قلل الضوضاء في الخلفية
|
| 92 |
+
3. تحدث بوضوح وبطء نسبياً
|
| 93 |
+
|
| 94 |
+
## 📞 الدعم التقني
|
| 95 |
+
|
| 96 |
+
### الحصول على المساعدة:
|
| 97 |
+
- تحقق من console المتصفح (F12) للأخطاء
|
| 98 |
+
- راجع ملفات السجل في مجلد التطبيق
|
| 99 |
+
- تأكد من تحديث جميع المكتبات
|
| 100 |
+
|
| 101 |
+
### نصائح للأداء الأفضل:
|
| 102 |
+
- استخدم Chrome أو Firefox للتوافق الأفضل
|
| 103 |
+
- أغلق التطبيقات الأخرى أثناء التسجيل
|
| 104 |
+
- تأكد من مساحة كافية على القرص الصلب
|
| 105 |
+
|
| 106 |
+
## 🎓 نصائح أكاديمية
|
| 107 |
+
|
| 108 |
+
### للمحاضرات:
|
| 109 |
+
- اجلس في مقدمة القاعة للصوت الأوضح
|
| 110 |
+
- استخدم علامات المحاضر المهمة كدليل
|
| 111 |
+
- راجع الترجمة مع زملاء الدراسة
|
| 112 |
+
|
| 113 |
+
### للمذاكرة:
|
| 114 |
+
- استخدم النص المترجم للمراجعة السريعة
|
| 115 |
+
- ابحث عن المفاهيم المترجمة في مصادر إضافية
|
| 116 |
+
- اربط النص الأصلي بالترجمة لتحسين اللغة
|
| 117 |
+
|
| 118 |
+
## 🔮 ميزات قادمة
|
| 119 |
+
|
| 120 |
+
### التحديثات المخططة:
|
| 121 |
+
- **تحليل المحتوى**: استخراج النقاط الرئيسية تلقائياً
|
| 122 |
+
- **بطاقات المراجعة**: إنشاء بطاقات دراسة من المحاضرات
|
| 123 |
+
- **التكامل مع المنصات**: ربط مع Moodle وCanvas
|
| 124 |
+
- **المشاركة التعاونية**: مشاركة المحاضرات مع الزملاء
|
| 125 |
+
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
## 📄 إخلاء المسؤولية
|
| 129 |
+
|
| 130 |
+
هذا التطبيق مخصص للاستخدام التعليمي. تأكد من الحصول على إذن المحاضر قبل تسجيل المحاضرات. النسخ والترجمة قد يحتويان على أخطاء، لذا راجعهما دائماً.
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
**نتمنى لك تجربة تعليمية ممتازة مع SyncMaster! 🎓✨**
|
SOLUTION_SUMMARY.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🎉 تم حل المشكلة بنجاح!
|
| 2 |
+
|
| 3 |
+
## ✅ ملخص الحل
|
| 4 |
+
|
| 5 |
+
تم حل مشكلة "system offline" في ميزة Lecture Recorder بنجاح. الآن يمكنك تشغيل التطبيق بأمر واحد فقط:
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
npm run dev
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
## 🔧 التغييرات التي تمت
|
| 12 |
+
|
| 13 |
+
### 1. ملفات جديدة تم إنشاؤها:
|
| 14 |
+
- `integrated_server.py` - خادم متكامل للتسجيل
|
| 15 |
+
- `main.py` - نقطة دخول بسيطة ومدمجة
|
| 16 |
+
- `app_config.py` - إعدادات التطبيق
|
| 17 |
+
- `startup.py` - مُشغل متقدم للتطوير
|
| 18 |
+
|
| 19 |
+
### 2. ملفات تم تعديلها:
|
| 20 |
+
- `app.py` - إضافة استيراد الخادم المدمج
|
| 21 |
+
- `package.json` - تحديث أوامر التشغيل
|
| 22 |
+
|
| 23 |
+
## 🚀 كيفية الاستخدام
|
| 24 |
+
|
| 25 |
+
### للاستخدام العادي:
|
| 26 |
+
```bash
|
| 27 |
+
npm run dev
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
### للنشر على HuggingFace:
|
| 31 |
+
```bash
|
| 32 |
+
npm start
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
### للتطوير المتقدم (خوادم منفصلة):
|
| 36 |
+
```bash
|
| 37 |
+
npm run dev-separate
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## ✨ المميزات الجديدة
|
| 41 |
+
|
| 42 |
+
1. **🎯 تشغيل موحد**: أمر واحد فقط لتشغيل كل شيء
|
| 43 |
+
2. **☁️ جاهز للسحابة**: يعمل تلقائياً على HuggingFace و Railway
|
| 44 |
+
3. **🔧 تكوين ذكي**: يكتشف البيئة ويتكيف معها
|
| 45 |
+
4. **🛡️ معالجة أخطاء محسنة**: تشغيل احتياطي في حالة فشل الطريقة الأولى
|
| 46 |
+
5. **📊 مراقبة الحالة**: فحص تلقائي لحالة الخوادم
|
| 47 |
+
|
| 48 |
+
## 🧪 اختبار النظام
|
| 49 |
+
|
| 50 |
+
تم اختبار النظام وأظهر النتائج التالية:
|
| 51 |
+
- ✅ خادم التسجيل يبدأ تلقائياً
|
| 52 |
+
- ✅ Streamlit يعمل على المنفذ 5050
|
| 53 |
+
- ✅ خادم التسجيل يعمل على المنفذ 5001
|
| 54 |
+
- ✅ التكامل بين الخوادم يعمل بنجاح
|
| 55 |
+
|
| 56 |
+
## 🎊 النتيجة النهائية
|
| 57 |
+
|
| 58 |
+
**المشكلة محلولة تماماً!**
|
| 59 |
+
|
| 60 |
+
لن تحتاج بعد الآن إلى:
|
| 61 |
+
- ❌ تشغيل `python recorder_server.py` منفصلاً
|
| 62 |
+
- ❌ القلق بشأن "system offline"
|
| 63 |
+
- ❌ تشغيل أوامر متعددة
|
| 64 |
+
|
| 65 |
+
فقط استخدم `npm run dev` وسيعمل كل شيء تلقائياً! 🚀
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
**جاهز للنشر على HuggingFace الآن!** 🌟
|
SUMMARY_FIX_REPORT.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# حل مشكلة زر التلخيص - تقرير الإصلاح النهائي 🎉
|
| 2 |
+
|
| 3 |
+
## 📋 ملخص المشكلة
|
| 4 |
+
كان زر "Generate Smart Lecture Summary" لا يعمل في تلخيص النص المستخرج من الذكاء الاصطناعي بعد جلبه من الصوت، مع ظهور خطأ CORS:
|
| 5 |
+
|
| 6 |
+
```
|
| 7 |
+
Access to fetch at 'http://localhost:5001/summarize' from origin 'http://localhost:5054' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
|
| 8 |
+
```
|
| 9 |
+
|
| 10 |
+
## 🔍 التشخيص المنجز
|
| 11 |
+
تم إنشاء نظام تشخيص شامل كشف عن:
|
| 12 |
+
|
| 13 |
+
### 1. مشكلة CORS الرئيسية ❌
|
| 14 |
+
- **المشكلة**: الخادم يرسل `'*, *'` بدلاً من `'*'`
|
| 15 |
+
- **السبب**: تكرار إعدادات CORS - مرة من `flask-cors` ومرة يدوياً في كل endpoint
|
| 16 |
+
- **النتيجة**: تكرار header `Access-Control-Allow-Origin`
|
| 17 |
+
|
| 18 |
+
### 2. مكتبة مفقودة ❌
|
| 19 |
+
- **المشكلة**: `google-generativeai` غير مثبتة
|
| 20 |
+
- **التأثير**: فشل في وظيفة التلخيص
|
| 21 |
+
|
| 22 |
+
## ✅ الحلول المطبقة
|
| 23 |
+
|
| 24 |
+
### 1. إصلاح مشكلة CORS
|
| 25 |
+
#### أ. تبسيط إعداد CORS في `recorder_server.py`:
|
| 26 |
+
```python
|
| 27 |
+
# قبل الإصلاح - إعداد معقد
|
| 28 |
+
CORS(app, resources={
|
| 29 |
+
r"/record": {"origins": "*"},
|
| 30 |
+
r"/translate": {"origins": "*"},
|
| 31 |
+
r"/languages": {"origins": "*"},
|
| 32 |
+
r"/ui-translations/*": {"origins": "*"},
|
| 33 |
+
r"/notes": {"origins": "*"},
|
| 34 |
+
r"/notes/*": {"origins": "*"},
|
| 35 |
+
r"/summarize": {"origins": "*"}
|
| 36 |
+
})
|
| 37 |
+
|
| 38 |
+
# بعد الإصلاح - إعداد مبسط وصحيح
|
| 39 |
+
CORS(app,
|
| 40 |
+
origins="*",
|
| 41 |
+
methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
| 42 |
+
allow_headers=['Content-Type', 'Authorization']
|
| 43 |
+
)
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
#### ب. إزالة الإعدادات اليدوية المكررة:
|
| 47 |
+
```python
|
| 48 |
+
# قبل الإصلاح - إعداد يدوي مكرر
|
| 49 |
+
if request.method == 'OPTIONS':
|
| 50 |
+
headers = {
|
| 51 |
+
'Access-Control-Allow-Origin': '*',
|
| 52 |
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
| 53 |
+
'Access-Control-Allow-Headers': 'Content-Type',
|
| 54 |
+
}
|
| 55 |
+
return ('', 204, headers)
|
| 56 |
+
|
| 57 |
+
# بعد الإصلاح - تبسيط
|
| 58 |
+
if request.method == 'OPTIONS':
|
| 59 |
+
return '', 204 # flask-cors ستتولى الأمر
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
### 2. تثبيت المكتبات المفقودة
|
| 63 |
+
```bash
|
| 64 |
+
pip install google-generativeai
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### 3. تحسين معالجة الأخطاء في JavaScript
|
| 68 |
+
تم تحديث دالة `generateSummary()` في `templates/recorder.html`:
|
| 69 |
+
- رسائل خطأ محددة باللغة العربية
|
| 70 |
+
- معالجة أفضل لتنسيقات الاستجابة المختلفة
|
| 71 |
+
- تشخيص أوضح للمشاكل
|
| 72 |
+
|
| 73 |
+
### 4. تحسين دالة عرض النتائج
|
| 74 |
+
تم تحديث `displaySummaryResults()` للتعامل مع:
|
| 75 |
+
- تنسيقات مختلفة للاستجابة (نص أو كائن)
|
| 76 |
+
- عرض محتوى احتياطي في حالة عدم وجود المحتوى المتوقع
|
| 77 |
+
|
| 78 |
+
## 🧪 أدوات التشخيص المُنشأة
|
| 79 |
+
|
| 80 |
+
### 1. `diagnose_summary.py`
|
| 81 |
+
نظام تشخيص شامل يفحص:
|
| 82 |
+
- حالة العمليات والمنافذ
|
| 83 |
+
- إعدادات CORS
|
| 84 |
+
- وظيفة التلخيص
|
| 85 |
+
- المكتبات المطلوبة
|
| 86 |
+
|
| 87 |
+
### 2. `test_summary_button.py`
|
| 88 |
+
اختبار مبسط ومباشر لزر التلخيص
|
| 89 |
+
|
| 90 |
+
### 3. `test_summarize.py`
|
| 91 |
+
اختبار أساسي لـ endpoint التلخيص
|
| 92 |
+
|
| 93 |
+
## 📊 نتائج الاختبار النهائية ✅
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
🎉 جميع الاختبارات نجحت!
|
| 97 |
+
✅ زر التلخيص يعمل بشكل صحيح
|
| 98 |
+
|
| 99 |
+
📊 ملخص التشخيص:
|
| 100 |
+
📦 المكتبات: ✅ موجودة
|
| 101 |
+
🔧 عملية Python: ✅ تعمل
|
| 102 |
+
🌐 المنفذ 5001: ✅ مفتوح
|
| 103 |
+
🔧 CORS: ✅ صحيح
|
| 104 |
+
🤖 التلخيص: ✅ يعمل
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
## 🔧 الملفات المُعدّلة
|
| 108 |
+
|
| 109 |
+
### 1. `recorder_server.py`
|
| 110 |
+
- إصلاح إعدادات CORS
|
| 111 |
+
- إزالة التكرار في headers
|
| 112 |
+
- تبسيط معالجة OPTIONS requests
|
| 113 |
+
|
| 114 |
+
### 2. `templates/recorder.html`
|
| 115 |
+
- تحسين دالة `generateSummary()`
|
| 116 |
+
- تحسين دالة `displaySummaryResults()`
|
| 117 |
+
- رسائل خطأ أوضح
|
| 118 |
+
|
| 119 |
+
### 3. ملفات التشخيص الجديدة
|
| 120 |
+
- `diagnose_summary.py`
|
| 121 |
+
- `test_summary_button.py`
|
| 122 |
+
- `test_summarize.py`
|
| 123 |
+
|
| 124 |
+
## 🚀 كيفية التحقق من الحل
|
| 125 |
+
|
| 126 |
+
### 1. تشغيل الخادم:
|
| 127 |
+
```bash
|
| 128 |
+
python recorder_server.py
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
### 2. تشغيل التشخيص:
|
| 132 |
+
```bash
|
| 133 |
+
python diagnose_summary.py
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### 3. اختبار زر التلخيص:
|
| 137 |
+
```bash
|
| 138 |
+
python test_summary_button.py
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
### 4. اختبار من الواجهة:
|
| 142 |
+
1. افتح `http://localhost:5054`
|
| 143 |
+
2. سجل صوت أو ادخل نص
|
| 144 |
+
3. اضغط زر "🤖 Generate Smart Lecture Summary"
|
| 145 |
+
4. تأكد من ظهور الملخص
|
| 146 |
+
|
| 147 |
+
## 💡 نصائح للمستقبل
|
| 148 |
+
|
| 149 |
+
### 1. تجنب تكرار CORS
|
| 150 |
+
- استخدم إعداد CORS واحد فقط
|
| 151 |
+
- لا تضع إعدادات يدوية إضافية
|
| 152 |
+
|
| 153 |
+
### 2. مراقبة التبعيات
|
| 154 |
+
- تأكد من تثبيت جميع المكتبات المطلوبة
|
| 155 |
+
- استخدم `requirements.txt` محدث
|
| 156 |
+
|
| 157 |
+
### 3. استخدام أدوات التشخيص
|
| 158 |
+
- شغل `diagnose_summary.py` عند مواجهة مشاكل
|
| 159 |
+
- يوفر تشخيص سريع وشامل
|
| 160 |
+
|
| 161 |
+
## 🎯 الخلاصة
|
| 162 |
+
|
| 163 |
+
تم حل مشكلة زر التلخيص بنجاح من خلال:
|
| 164 |
+
1. ✅ إصلاح مشكلة CORS المزدوجة
|
| 165 |
+
2. ✅ تثبيت المكتبات المفقودة
|
| 166 |
+
3. ✅ تحسين معالجة الأخطاء
|
| 167 |
+
4. ✅ إنشاء أدوات تشخيص شاملة
|
| 168 |
+
|
| 169 |
+
**النتيجة: زر التلخيص يعمل بشكل مثالي الآن! 🎉**
|
TECHNICAL_IMPLEMENTATION.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SyncMaster Enhanced - Technical Implementation Summary
|
| 2 |
+
|
| 3 |
+
## 🎯 Summary of Enhancements
|
| 4 |
+
|
| 5 |
+
This document outlines the comprehensive improvements made to SyncMaster to support AI-powered translation for international students.
|
| 6 |
+
|
| 7 |
+
## 🔧 New Components Added
|
| 8 |
+
|
| 9 |
+
### 1. `translator.py` - AI Translation Engine
|
| 10 |
+
```python
|
| 11 |
+
class AITranslator:
|
| 12 |
+
- translate_text(text, target_language='ar', source_language='auto')
|
| 13 |
+
- detect_language(text)
|
| 14 |
+
- translate_ui_elements(ui_dict, target_language='ar')
|
| 15 |
+
- batch_translate(texts, target_language='ar')
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
**Features:**
|
| 19 |
+
- Gemini AI-powered translation
|
| 20 |
+
- Academic content optimization
|
| 21 |
+
- Multi-language support (Arabic, English, French, Spanish)
|
| 22 |
+
- Batch processing capabilities
|
| 23 |
+
- Context-aware translation
|
| 24 |
+
|
| 25 |
+
### 2. Enhanced `audio_processor.py`
|
| 26 |
+
```python
|
| 27 |
+
class AudioProcessor:
|
| 28 |
+
- get_word_timestamps_with_translation(audio_file_path, target_language='ar')
|
| 29 |
+
- batch_translate_transcription(audio_file_path, target_languages)
|
| 30 |
+
- _create_translated_timestamps(original_timestamps, original_text, translated_text)
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
**New Features:**
|
| 34 |
+
- Integrated translation with transcription
|
| 35 |
+
- Proportional timestamp mapping for translated text
|
| 36 |
+
- Multi-language processing
|
| 37 |
+
- Enhanced error handling and logging
|
| 38 |
+
|
| 39 |
+
### 3. Updated `recorder_server.py`
|
| 40 |
+
```python
|
| 41 |
+
@app.route('/record', methods=['POST'])
|
| 42 |
+
def record():
|
| 43 |
+
# Enhanced with translation parameters:
|
| 44 |
+
# - target_language
|
| 45 |
+
# - enable_translation
|
| 46 |
+
# - comprehensive response with both original and translated text
|
| 47 |
+
|
| 48 |
+
@app.route('/translate', methods=['POST'])
|
| 49 |
+
def translate_text():
|
| 50 |
+
# Standalone translation endpoint
|
| 51 |
+
|
| 52 |
+
@app.route('/languages', methods=['GET'])
|
| 53 |
+
def get_supported_languages():
|
| 54 |
+
# Get list of supported languages
|
| 55 |
+
|
| 56 |
+
@app.route('/ui-translations/<language>', methods=['GET'])
|
| 57 |
+
def get_ui_translations(language):
|
| 58 |
+
# Get UI translations for specific language
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### 4. Enhanced `templates/recorder.html`
|
| 62 |
+
**New Features:**
|
| 63 |
+
- Multi-language interface (English/Arabic)
|
| 64 |
+
- RTL support for Arabic
|
| 65 |
+
- Translation toggle controls
|
| 66 |
+
- Target language selection
|
| 67 |
+
- Enhanced visual design
|
| 68 |
+
- Keyboard shortcuts
|
| 69 |
+
- Accessibility improvements
|
| 70 |
+
|
| 71 |
+
**UI Improvements:**
|
| 72 |
+
- Modern gradient design
|
| 73 |
+
- Responsive layout for mobile devices
|
| 74 |
+
- Real-time language switching
|
| 75 |
+
- Visual feedback for translation status
|
| 76 |
+
- Better error messaging
|
| 77 |
+
|
| 78 |
+
### 5. Updated `app.py` - Main Application
|
| 79 |
+
**Enhancements:**
|
| 80 |
+
- Language selection in sidebar
|
| 81 |
+
- Translation settings integration
|
| 82 |
+
- Enhanced processing workflow
|
| 83 |
+
- Bilingual interface support
|
| 84 |
+
- Improved user experience flow
|
| 85 |
+
|
| 86 |
+
## 🌐 Multi-Language Support Implementation
|
| 87 |
+
|
| 88 |
+
### UI Translation System
|
| 89 |
+
```python
|
| 90 |
+
UI_TRANSLATIONS = {
|
| 91 |
+
'en': { /* English translations */ },
|
| 92 |
+
'ar': { /* Arabic translations */ }
|
| 93 |
+
}
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### Dynamic Language Switching
|
| 97 |
+
- Client-side language detection
|
| 98 |
+
- Server-side translation API
|
| 99 |
+
- Real-time UI updates
|
| 100 |
+
- RTL text direction support
|
| 101 |
+
|
| 102 |
+
### Translation Workflow
|
| 103 |
+
1. **Audio Recording** → Record with language preferences
|
| 104 |
+
2. **Transcription** → AI-powered speech-to-text
|
| 105 |
+
3. **Language Detection** → Automatic source language identification
|
| 106 |
+
4. **Translation** → Context-aware AI translation
|
| 107 |
+
5. **Presentation** → Side-by-side original and translated text
|
| 108 |
+
|
| 109 |
+
## 🚀 API Enhancements
|
| 110 |
+
|
| 111 |
+
### Recording Endpoint (`/record`)
|
| 112 |
+
**Request Parameters:**
|
| 113 |
+
```json
|
| 114 |
+
{
|
| 115 |
+
"audio_data": "binary_audio_file",
|
| 116 |
+
"markers": "[timestamp_array]",
|
| 117 |
+
"target_language": "ar|en|fr|es",
|
| 118 |
+
"enable_translation": "true|false"
|
| 119 |
+
}
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
**Response Format:**
|
| 123 |
+
```json
|
| 124 |
+
{
|
| 125 |
+
"success": true,
|
| 126 |
+
"original_text": "Original transcription",
|
| 127 |
+
"translated_text": "Translated text",
|
| 128 |
+
"file_path": "path/to/saved/file.json",
|
| 129 |
+
"markers": [timestamps],
|
| 130 |
+
"target_language": "ar",
|
| 131 |
+
"translation_enabled": true,
|
| 132 |
+
"translation_success": true,
|
| 133 |
+
"language_detected": "en"
|
| 134 |
+
}
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
### Translation Endpoint (`/translate`)
|
| 138 |
+
**Request:**
|
| 139 |
+
```json
|
| 140 |
+
{
|
| 141 |
+
"text": "Text to translate",
|
| 142 |
+
"target_language": "ar",
|
| 143 |
+
"source_language": "auto"
|
| 144 |
+
}
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
**Response:**
|
| 148 |
+
```json
|
| 149 |
+
{
|
| 150 |
+
"success": true,
|
| 151 |
+
"original_text": "Original text",
|
| 152 |
+
"translated_text": "النص المترجم",
|
| 153 |
+
"source_language": "en",
|
| 154 |
+
"target_language": "ar"
|
| 155 |
+
}
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
## 📱 Frontend Enhancements
|
| 159 |
+
|
| 160 |
+
### JavaScript Features
|
| 161 |
+
```javascript
|
| 162 |
+
// Language Management
|
| 163 |
+
async function loadTranslations(language)
|
| 164 |
+
function applyTranslations()
|
| 165 |
+
function changeLanguage()
|
| 166 |
+
|
| 167 |
+
// Enhanced Recording
|
| 168 |
+
function displayResults(result)
|
| 169 |
+
function displayMarkers(markers)
|
| 170 |
+
function showMessage(message, type)
|
| 171 |
+
|
| 172 |
+
// Keyboard Shortcuts
|
| 173 |
+
document.addEventListener('keydown', handleKeyboardShortcuts)
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
### CSS Improvements
|
| 177 |
+
```css
|
| 178 |
+
/* RTL Support */
|
| 179 |
+
html[dir="rtl"] { direction: rtl; }
|
| 180 |
+
|
| 181 |
+
/* Modern Design */
|
| 182 |
+
:root {
|
| 183 |
+
--primary-color: #4A90E2;
|
| 184 |
+
--success-color: #50C878;
|
| 185 |
+
/* ... more color variables */
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
/* Responsive Design */
|
| 189 |
+
@media (max-width: 768px) {
|
| 190 |
+
/* Mobile optimizations */
|
| 191 |
+
}
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
## 🔒 Security & Performance
|
| 195 |
+
|
| 196 |
+
### Security Measures
|
| 197 |
+
- Input validation for all API endpoints
|
| 198 |
+
- CORS configuration for cross-origin requests
|
| 199 |
+
- Secure file handling with temporary files
|
| 200 |
+
- API key protection in environment variables
|
| 201 |
+
|
| 202 |
+
### Performance Optimizations
|
| 203 |
+
- Parallel processing for audio and translation
|
| 204 |
+
- Efficient memory management
|
| 205 |
+
- Chunked audio processing
|
| 206 |
+
- Client-side caching for translations
|
| 207 |
+
|
| 208 |
+
## 📊 File Structure Changes
|
| 209 |
+
|
| 210 |
+
```
|
| 211 |
+
SyncMaster - Copy (2)/
|
| 212 |
+
├── translator.py # NEW: AI Translation engine
|
| 213 |
+
├── audio_processor.py # ENHANCED: With translation support
|
| 214 |
+
├── recorder_server.py # ENHANCED: Additional endpoints
|
| 215 |
+
├── app.py # ENHANCED: Multi-language support
|
| 216 |
+
├── templates/
|
| 217 |
+
│ └── recorder.html # ENHANCED: Multi-language UI
|
| 218 |
+
├── README_AR.md # NEW: Arabic documentation
|
| 219 |
+
├── setup_enhanced.py # NEW: Enhanced setup script
|
| 220 |
+
├── start_enhanced.bat # NEW: Quick start script
|
| 221 |
+
├── requirements.txt # UPDATED: Additional dependencies
|
| 222 |
+
└── .env # UPDATED: Additional configuration
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
## 🎓 Educational Features
|
| 226 |
+
|
| 227 |
+
### For International Students
|
| 228 |
+
1. **Language Barrier Reduction**: Real-time translation of lectures
|
| 229 |
+
2. **Better Comprehension**: Side-by-side original and translated text
|
| 230 |
+
3. **Cultural Adaptation**: Interface in native language
|
| 231 |
+
4. **Academic Context**: Specialized translation for educational content
|
| 232 |
+
|
| 233 |
+
### For Arabic Students
|
| 234 |
+
1. **Native Interface**: Complete Arabic UI
|
| 235 |
+
2. **Technical Term Translation**: English technical terms with Arabic explanations
|
| 236 |
+
3. **Reading Direction**: Proper RTL text display
|
| 237 |
+
4. **Cultural Context**: Academic content adapted for Arabic speakers
|
| 238 |
+
|
| 239 |
+
## 🔧 Installation & Setup
|
| 240 |
+
|
| 241 |
+
### Enhanced Setup Process
|
| 242 |
+
1. **Automated Installation**: `python setup_enhanced.py`
|
| 243 |
+
2. **Dependency Management**: Automatic package installation
|
| 244 |
+
3. **Configuration Validation**: Environment file checking
|
| 245 |
+
4. **Service Management**: Automatic server startup
|
| 246 |
+
|
| 247 |
+
### Quick Start Options
|
| 248 |
+
- **Windows**: `start_enhanced.bat`
|
| 249 |
+
- **Cross-platform**: `python setup_enhanced.py`
|
| 250 |
+
- **Manual**: Individual component startup
|
| 251 |
+
|
| 252 |
+
## 📈 Testing & Quality Assurance
|
| 253 |
+
|
| 254 |
+
### Translation Quality
|
| 255 |
+
- Academic content optimization
|
| 256 |
+
- Technical term preservation
|
| 257 |
+
- Context-aware translation
|
| 258 |
+
- Fallback mechanisms
|
| 259 |
+
|
| 260 |
+
### User Experience Testing
|
| 261 |
+
- Multi-language interface testing
|
| 262 |
+
- Mobile responsiveness
|
| 263 |
+
- Accessibility compliance
|
| 264 |
+
- Performance optimization
|
| 265 |
+
|
| 266 |
+
## 🔮 Future Enhancements
|
| 267 |
+
|
| 268 |
+
### Planned Features
|
| 269 |
+
1. **Advanced Translation**: Subject-specific terminology
|
| 270 |
+
2. **Collaboration Tools**: Shared study sessions
|
| 271 |
+
3. **Learning Analytics**: Progress tracking
|
| 272 |
+
4. **Platform Integration**: LMS connectivity
|
| 273 |
+
5. **Offline Support**: Local processing capabilities
|
| 274 |
+
|
| 275 |
+
### Technical Roadmap
|
| 276 |
+
1. **Model Optimization**: Faster processing
|
| 277 |
+
2. **Caching System**: Reduced API calls
|
| 278 |
+
3. **Advanced UI**: More interactive features
|
| 279 |
+
4. **Mobile App**: Native mobile application
|
| 280 |
+
|
| 281 |
+
---
|
| 282 |
+
|
| 283 |
+
## 📞 Technical Support
|
| 284 |
+
|
| 285 |
+
### Debugging Features
|
| 286 |
+
- Comprehensive logging system
|
| 287 |
+
- Browser console integration
|
| 288 |
+
- Error message localization
|
| 289 |
+
- Performance monitoring
|
| 290 |
+
|
| 291 |
+
### Troubleshooting Resources
|
| 292 |
+
- Detailed error messages
|
| 293 |
+
- Multi-language support documentation
|
| 294 |
+
- Community forum integration
|
| 295 |
+
- Technical FAQ
|
| 296 |
+
|
| 297 |
+
---
|
| 298 |
+
|
| 299 |
+
**This enhanced version of SyncMaster represents a significant advancement in making educational technology accessible to international students worldwide.**
|
TROUBLESHOOTING.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🛠️ دليل استكشاف الأخطاء - SyncMaster Enhanced
|
| 2 |
+
# Troubleshooting Guide - SyncMaster Enhanced
|
| 3 |
+
|
| 4 |
+
## 🔍 الأخطاء الشائعة وحلولها / Common Errors and Solutions
|
| 5 |
+
|
| 6 |
+
### 1. خطأ الاتصال بالخادم / Server Connection Error
|
| 7 |
+
```
|
| 8 |
+
Error: Failed to fetch
|
| 9 |
+
POST http://localhost:5001/record net::ERR_CONNECTION_REFUSED
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
**الأسباب المحتملة / Possible Causes:**
|
| 13 |
+
- الخادم غير يعمل / Server not running
|
| 14 |
+
- منفذ 5001 مستخدم من برنامج آخر / Port 5001 used by another application
|
| 15 |
+
- جدار حماية يحجب الاتصال / Firewall blocking connection
|
| 16 |
+
|
| 17 |
+
**الحلول / Solutions:**
|
| 18 |
+
|
| 19 |
+
#### أ) تشغيل اختبار النظام / Run System Test:
|
| 20 |
+
```bash
|
| 21 |
+
python test_system.py
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
#### ب) تشغيل الخادم يدوياً / Start Server Manually:
|
| 25 |
+
```bash
|
| 26 |
+
# إيقاف جميع العمليات / Stop all processes
|
| 27 |
+
taskkill /f /im python.exe
|
| 28 |
+
|
| 29 |
+
# تشغيل الخادم / Start server
|
| 30 |
+
python recorder_server.py
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
#### ج) استخدام البدء المتقدم / Use Debug Startup:
|
| 34 |
+
```bash
|
| 35 |
+
python start_debug.py
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
#### د) فحص المنافذ / Check Ports:
|
| 39 |
+
```bash
|
| 40 |
+
# Windows
|
| 41 |
+
netstat -an | findstr :5001
|
| 42 |
+
|
| 43 |
+
# Linux/Mac
|
| 44 |
+
lsof -i :5001
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
### 2. مشكلة مفتاح API / API Key Issues
|
| 48 |
+
```
|
| 49 |
+
ERROR: GEMINI_API_KEY not found in environment variables
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
**الحل / Solution:**
|
| 53 |
+
1. تأكد من وجود ملف `.env`:
|
| 54 |
+
```bash
|
| 55 |
+
# إنشاء ملف .env / Create .env file
|
| 56 |
+
echo GEMINI_API_KEY=your_actual_api_key_here > .env
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
2. احصل على مفتاح API من:
|
| 60 |
+
- [Google AI Studio](https://makersuite.google.com/app/apikey)
|
| 61 |
+
|
| 62 |
+
3. أضف المفتاح إلى `.env`:
|
| 63 |
+
```
|
| 64 |
+
GEMINI_API_KEY=AIzaSyAS7JtrXjlNjyuo3RG5z6rkwocCwFy1YuA
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### 3. مشاكل الصوت / Audio Issues
|
| 68 |
+
```
|
| 69 |
+
UserWarning: PySoundFile failed. Trying audioread instead.
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
**الحلول / Solutions:**
|
| 73 |
+
|
| 74 |
+
#### أ) تثبيت SoundFile مرة أخرى / Reinstall SoundFile:
|
| 75 |
+
```bash
|
| 76 |
+
pip uninstall soundfile
|
| 77 |
+
pip install soundfile
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
#### ب) تثبيت FFmpeg (إذا لزم الأمر) / Install FFmpeg if needed:
|
| 81 |
+
```bash
|
| 82 |
+
# Windows (using chocolatey)
|
| 83 |
+
choco install ffmpeg
|
| 84 |
+
|
| 85 |
+
# Or download from: https://ffmpeg.org/download.html
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
#### ج) فحص تنسيق الملف / Check Audio Format:
|
| 89 |
+
- استخدم WAV بدلاً من MP3
|
| 90 |
+
- تأكد من جودة التسجيل
|
| 91 |
+
|
| 92 |
+
### 4. مشاكل الترجمة / Translation Issues
|
| 93 |
+
```
|
| 94 |
+
WARNING: Gemini returned empty translation response
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
**الحلول / Solutions:**
|
| 98 |
+
|
| 99 |
+
#### أ) فحص اتصال الإنترنت / Check Internet Connection:
|
| 100 |
+
```bash
|
| 101 |
+
ping google.com
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
#### ب) اختبار مفتاح API / Test API Key:
|
| 105 |
+
```python
|
| 106 |
+
python test_system.py
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
#### ج) تغيير النموذج / Change Model:
|
| 110 |
+
- إذا فشل `gemini-2.5-flash`، جرب `gemini-1.5-flash`
|
| 111 |
+
|
| 112 |
+
### 5. مشاكل الواجهة / UI Issues
|
| 113 |
+
|
| 114 |
+
#### أ) الواجهة لا تحمّل / Interface Won't Load:
|
| 115 |
+
```bash
|
| 116 |
+
# تحقق من المنفذ / Check port
|
| 117 |
+
python -c "import socket; s=socket.socket(); s.bind(('',8501)); print('Port 8501 available')"
|
| 118 |
+
|
| 119 |
+
# تشغيل على منفذ مختلف / Run on different port
|
| 120 |
+
streamlit run app.py --server.port 8502
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
#### ب) مشاكل اللغة العربية / Arabic Language Issues:
|
| 124 |
+
- تأكد من دعم المتصفح للـ RTL
|
| 125 |
+
- استخدم Chrome أو Firefox للأفضل
|
| 126 |
+
|
| 127 |
+
### 6. مشاكل الأداء / Performance Issues
|
| 128 |
+
|
| 129 |
+
#### أ) بطء في المعالجة / Slow Processing:
|
| 130 |
+
- تحقق من سرعة الإنترنت
|
| 131 |
+
- قلل حجم الملف الصوتي
|
| 132 |
+
- استخدم جودة أقل للتسجيل
|
| 133 |
+
|
| 134 |
+
#### ب) استهلاك ذاكرة عالي / High Memory Usage:
|
| 135 |
+
```bash
|
| 136 |
+
# إعادة تشغيل النظام / Restart system
|
| 137 |
+
python start_debug.py
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
## 🔧 أدوات التشخيص / Diagnostic Tools
|
| 141 |
+
|
| 142 |
+
### 1. اختبار شامل / Complete Test:
|
| 143 |
+
```bash
|
| 144 |
+
python test_system.py
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
### 2. فحص المنافذ / Port Check:
|
| 148 |
+
```python
|
| 149 |
+
python -c "
|
| 150 |
+
import socket
|
| 151 |
+
ports = [5001, 8501, 8502]
|
| 152 |
+
for port in ports:
|
| 153 |
+
try:
|
| 154 |
+
s = socket.socket()
|
| 155 |
+
s.bind(('localhost', port))
|
| 156 |
+
s.close()
|
| 157 |
+
print(f'Port {port}: Available ✅')
|
| 158 |
+
except:
|
| 159 |
+
print(f'Port {port}: Busy ❌')
|
| 160 |
+
"
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
### 3. فحص التبعيات / Dependencies Check:
|
| 164 |
+
```bash
|
| 165 |
+
pip list | grep -E "(streamlit|flask|librosa|soundfile|google-generativeai)"
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
### 4. فحص العمليات / Process Check:
|
| 169 |
+
```bash
|
| 170 |
+
# Windows
|
| 171 |
+
tasklist | findstr python
|
| 172 |
+
|
| 173 |
+
# Linux/Mac
|
| 174 |
+
ps aux | grep python
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
## 📱 نصائح لحل المشاكل / Troubleshooting Tips
|
| 178 |
+
|
| 179 |
+
### للطلاب الجدد / For New Users:
|
| 180 |
+
1. **ابدأ بالاختبار الشامل / Start with system test**:
|
| 181 |
+
```bash
|
| 182 |
+
python test_system.py
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
2. **استخدم البدء المتقدم / Use debug startup**:
|
| 186 |
+
```bash
|
| 187 |
+
python start_debug.py
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
3. **تحقق من المتطلبات / Check requirements**:
|
| 191 |
+
- Python 3.8+
|
| 192 |
+
- مفتاح Gemini API صالح
|
| 193 |
+
- اتصال إنترنت مستقر
|
| 194 |
+
|
| 195 |
+
### للطلاب المتقدمين / For Advanced Users:
|
| 196 |
+
1. **مراجعة السجلات / Check logs**:
|
| 197 |
+
- افتح console المتصفح (F12)
|
| 198 |
+
- راجع سجلات الطرفية
|
| 199 |
+
|
| 200 |
+
2. **تخصيص الإعدادات / Customize settings**:
|
| 201 |
+
- غير المنافذ في حالة التضارب
|
| 202 |
+
- عدّل إعدادات الصوت
|
| 203 |
+
|
| 204 |
+
3. **التشخيص المتقدم / Advanced diagnostics**:
|
| 205 |
+
```python
|
| 206 |
+
# اختبار الاتصال / Test connection
|
| 207 |
+
import requests
|
| 208 |
+
response = requests.get('http://localhost:5001/record')
|
| 209 |
+
print(response.status_code, response.text)
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
## 🆘 طلب المساعدة / Getting Help
|
| 213 |
+
|
| 214 |
+
### معلومات مطلوبة / Required Information:
|
| 215 |
+
1. نظام التشغيل / Operating System
|
| 216 |
+
2. إصدار Python / Python Version
|
| 217 |
+
3. نتائج `python test_system.py`
|
| 218 |
+
4. رسائل الخطأ الكاملة / Complete error messages
|
| 219 |
+
5. سجلات الطرفية / Terminal logs
|
| 220 |
+
|
| 221 |
+
### خطوات الإبلاغ / Reporting Steps:
|
| 222 |
+
1. شغّل الاختبار الشامل
|
| 223 |
+
2. احفظ النتائج
|
| 224 |
+
3. صوّر رسائل الخطأ
|
| 225 |
+
4. اذكر الخطوات التي أدت للمشكلة
|
| 226 |
+
|
| 227 |
+
---
|
| 228 |
+
|
| 229 |
+
## 🎯 Quick Fix Commands / أوامر الإصلاح السريع
|
| 230 |
+
|
| 231 |
+
```bash
|
| 232 |
+
# إعادة تعيين كامل / Complete Reset
|
| 233 |
+
taskkill /f /im python.exe
|
| 234 |
+
python test_system.py
|
| 235 |
+
python start_debug.py
|
| 236 |
+
|
| 237 |
+
# إصلاح التبعيات / Fix Dependencies
|
| 238 |
+
pip install --upgrade -r requirements.txt
|
| 239 |
+
|
| 240 |
+
# إصلاح المنافذ / Fix Ports
|
| 241 |
+
python start_debug.py
|
| 242 |
+
|
| 243 |
+
# اختبار الترجمة / Test Translation
|
| 244 |
+
python -c "from translator import AITranslator; t=AITranslator(); print(t.translate_text('Hello', 'ar'))"
|
| 245 |
+
```
|
| 246 |
+
|
| 247 |
+
---
|
| 248 |
+
|
| 249 |
+
**تذكر: معظم المشاكل تُحل بإعادة تشغيل النظام وتشغيل الاختبار الشامل! 🔄**
|
| 250 |
+
|
| 251 |
+
**Remember: Most issues are solved by restarting and running the system test! 🔄**
|
app.py
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py - Refactored to eliminate recorder_server.py dependency
|
| 2 |
+
|
| 3 |
+
import streamlit as st
|
| 4 |
+
import os
|
| 5 |
+
import tempfile
|
| 6 |
+
import json
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import time
|
| 9 |
+
import traceback
|
| 10 |
+
import streamlit.components.v1 as components
|
| 11 |
+
import hashlib
|
| 12 |
+
# from st_audiorec import st_audiorec # Import the new recorder component - OLD
|
| 13 |
+
# Reduce metrics/usage writes that can cause permission errors on hosted environments
|
| 14 |
+
try:
|
| 15 |
+
st.set_option('browser.gatherUsageStats', False)
|
| 16 |
+
except Exception:
|
| 17 |
+
pass
|
| 18 |
+
|
| 19 |
+
# Robust component declaration: prefer local build, else fall back to pip package
|
| 20 |
+
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
| 21 |
+
build_dir = os.path.join(parent_dir, "custom_components/st-audiorec/st_audiorec/frontend/build")
|
| 22 |
+
|
| 23 |
+
def st_audiorec(key=None):
|
| 24 |
+
"""Return audio recorder component value, trying local build first, then pip package fallback."""
|
| 25 |
+
try:
|
| 26 |
+
if os.path.isdir(build_dir):
|
| 27 |
+
_component_func = components.declare_component("st_audiorec", path=build_dir)
|
| 28 |
+
return _component_func(key=key, default=0)
|
| 29 |
+
# Fallback to pip-installed component if available
|
| 30 |
+
try:
|
| 31 |
+
from st_audiorec import st_audiorec as st_audiorec_pkg
|
| 32 |
+
return st_audiorec_pkg(key=key)
|
| 33 |
+
except Exception:
|
| 34 |
+
st.warning("Audio recorder component is unavailable on this deployment (missing local build and pip fallback).")
|
| 35 |
+
return None
|
| 36 |
+
except Exception:
|
| 37 |
+
# Final safety net
|
| 38 |
+
st.warning("Failed to initialize audio recorder component.")
|
| 39 |
+
return None
|
| 40 |
+
|
| 41 |
+
# --- Critical Imports and Initial Checks ---
|
| 42 |
+
AUDIO_PROCESSOR_CLASS = None
|
| 43 |
+
IMPORT_ERROR_TRACEBACK = None
|
| 44 |
+
try:
|
| 45 |
+
from audio_processor import AudioProcessor
|
| 46 |
+
AUDIO_PROCESSOR_CLASS = AudioProcessor
|
| 47 |
+
except Exception:
|
| 48 |
+
IMPORT_ERROR_TRACEBACK = traceback.format_exc()
|
| 49 |
+
|
| 50 |
+
from video_generator import VideoGenerator
|
| 51 |
+
from mp3_embedder import MP3Embedder
|
| 52 |
+
from utils import format_timestamp
|
| 53 |
+
from translator import get_translator, UI_TRANSLATIONS
|
| 54 |
+
import requests
|
| 55 |
+
from dotenv import load_dotenv
|
| 56 |
+
|
| 57 |
+
# --- API Key Check ---
|
| 58 |
+
def check_api_key():
|
| 59 |
+
"""Check for Gemini API key and display instructions if not found."""
|
| 60 |
+
load_dotenv()
|
| 61 |
+
if not os.getenv("GEMINI_API_KEY"):
|
| 62 |
+
st.error("🔴 FATAL ERROR: GEMINI_API_KEY is not set!")
|
| 63 |
+
st.info("To fix this, please follow these steps:")
|
| 64 |
+
st.markdown("""
|
| 65 |
+
1. **Find the file named `.env.example`** in the `syncmaster2` directory.
|
| 66 |
+
2. **Rename it to `.env`**.
|
| 67 |
+
3. **Open the `.env` file** with a text editor.
|
| 68 |
+
4. **Get your free API key** from [Google AI Studio](https://aistudio.google.com/app/apikey).
|
| 69 |
+
5. **Paste your key** into the file, replacing `"PASTE_YOUR_GEMINI_API_KEY_HERE"`.
|
| 70 |
+
6. **Save the file and restart the application.**
|
| 71 |
+
""")
|
| 72 |
+
return False
|
| 73 |
+
return True
|
| 74 |
+
|
| 75 |
+
# --- Summary Helper (robust to cached translator without summarize_text) ---
|
| 76 |
+
def generate_summary(text: str, target_language: str = 'ar'):
|
| 77 |
+
"""Generate a concise summary in target_language, with graceful fallback.
|
| 78 |
+
|
| 79 |
+
If summarize_text is unavailable (cached instance), fall back to Arabic summary
|
| 80 |
+
then translate to the target language if needed.
|
| 81 |
+
"""
|
| 82 |
+
tr = get_translator()
|
| 83 |
+
try:
|
| 84 |
+
if hasattr(tr, 'summarize_text') and callable(getattr(tr, 'summarize_text')):
|
| 85 |
+
s, err = tr.summarize_text(text or '', target_language=target_language)
|
| 86 |
+
if s:
|
| 87 |
+
return s, None
|
| 88 |
+
# Fallback path: Arabic summary first
|
| 89 |
+
s_ar, err_ar = tr.summarize_text_arabic(text or '')
|
| 90 |
+
if target_language and target_language != 'ar' and s_ar:
|
| 91 |
+
tx, err_tx = tr.translate_text(s_ar, target_language=target_language)
|
| 92 |
+
if tx:
|
| 93 |
+
return tx, None
|
| 94 |
+
return s_ar, err_tx
|
| 95 |
+
return s_ar, err_ar
|
| 96 |
+
except Exception as e:
|
| 97 |
+
return None, str(e)
|
| 98 |
+
|
| 99 |
+
# --- Page Configuration ---
|
| 100 |
+
st.set_page_config(
|
| 101 |
+
page_title="SyncMaster - AI Audio-Text Synchronization",
|
| 102 |
+
page_icon="🎵",
|
| 103 |
+
layout="wide"
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
# --- Browser Console Logging Utility ---
|
| 107 |
+
def log_to_browser_console(messages):
|
| 108 |
+
"""Injects JavaScript to log messages to the browser's console."""
|
| 109 |
+
if isinstance(messages, str):
|
| 110 |
+
messages = [messages]
|
| 111 |
+
escaped_messages = [json.dumps(str(msg)) for msg in messages]
|
| 112 |
+
js_code = f"""
|
| 113 |
+
<script>
|
| 114 |
+
(function() {{
|
| 115 |
+
const logs = [{', '.join(escaped_messages)}];
|
| 116 |
+
console.group("Backend Logs from SyncMaster");
|
| 117 |
+
logs.forEach(log => {{
|
| 118 |
+
const content = String(log);
|
| 119 |
+
if (content.includes('--- ERROR') || content.includes('--- FATAL')) {{
|
| 120 |
+
console.error(log);
|
| 121 |
+
}} else if (content.includes('--- WARNING')) {{
|
| 122 |
+
console.warn(log);
|
| 123 |
+
}} else if (content.includes('--- DEBUG')) {{
|
| 124 |
+
console.debug(log);
|
| 125 |
+
}} else {{
|
| 126 |
+
console.log(log);
|
| 127 |
+
}}
|
| 128 |
+
}});
|
| 129 |
+
console.groupEnd();
|
| 130 |
+
}})();
|
| 131 |
+
</script>
|
| 132 |
+
"""
|
| 133 |
+
components.html(js_code, height=0, scrolling=False)
|
| 134 |
+
|
| 135 |
+
# --- Session State Initialization ---
|
| 136 |
+
def initialize_session_state():
|
| 137 |
+
"""Initializes the session state variables if they don't exist."""
|
| 138 |
+
if 'step' not in st.session_state:
|
| 139 |
+
st.session_state.step = 1
|
| 140 |
+
if 'audio_data' not in st.session_state:
|
| 141 |
+
st.session_state.audio_data = None
|
| 142 |
+
if 'language' not in st.session_state:
|
| 143 |
+
st.session_state.language = 'en'
|
| 144 |
+
if 'enable_translation' not in st.session_state:
|
| 145 |
+
st.session_state.enable_translation = True
|
| 146 |
+
if 'target_language' not in st.session_state:
|
| 147 |
+
st.session_state.target_language = 'ar'
|
| 148 |
+
if 'transcription_data' not in st.session_state:
|
| 149 |
+
st.session_state.transcription_data = None
|
| 150 |
+
if 'edited_text' not in st.session_state:
|
| 151 |
+
st.session_state.edited_text = ""
|
| 152 |
+
if 'video_style' not in st.session_state:
|
| 153 |
+
st.session_state.video_style = {
|
| 154 |
+
'animation_style': 'Karaoke Style', 'text_color': '#FFFFFF',
|
| 155 |
+
'highlight_color': '#FFD700', 'background_color': '#000000',
|
| 156 |
+
'font_family': 'Arial', 'font_size': 48
|
| 157 |
+
}
|
| 158 |
+
if 'new_recording' not in st.session_state:
|
| 159 |
+
st.session_state.new_recording = None
|
| 160 |
+
# Transcript feed (prepend latest) and dedupe set
|
| 161 |
+
if 'transcript_feed' not in st.session_state:
|
| 162 |
+
st.session_state.transcript_feed = [] # list of {id, ts, text}
|
| 163 |
+
if 'transcript_ids' not in st.session_state:
|
| 164 |
+
st.session_state.transcript_ids = set()
|
| 165 |
+
# Incremental broadcast state
|
| 166 |
+
if 'broadcast_segments' not in st.session_state:
|
| 167 |
+
st.session_state.broadcast_segments = [] # [{id, recording_id, start_ms, end_ms, checksum, text}]
|
| 168 |
+
if 'lastFetchedEnd_ms' not in st.session_state:
|
| 169 |
+
st.session_state.lastFetchedEnd_ms = 0
|
| 170 |
+
# Broadcast translation language (separate from general UI translation target)
|
| 171 |
+
if 'broadcast_translation_lang' not in st.session_state:
|
| 172 |
+
# Default broadcast translation target to Arabic
|
| 173 |
+
st.session_state.broadcast_translation_lang = 'ar'
|
| 174 |
+
if 'summary_language' not in st.session_state:
|
| 175 |
+
# Default summary language to Arabic
|
| 176 |
+
st.session_state.summary_language = 'ar'
|
| 177 |
+
# Auto-generate Arabic summary toggle
|
| 178 |
+
if 'auto_generate_summary' not in st.session_state:
|
| 179 |
+
st.session_state.auto_generate_summary = True
|
| 180 |
+
|
| 181 |
+
# --- Centralized Audio Processing Function ---
|
| 182 |
+
def run_audio_processing(audio_bytes, original_filename="recorded_audio.wav"):
|
| 183 |
+
"""
|
| 184 |
+
A single, robust function to handle all audio processing.
|
| 185 |
+
Takes audio bytes as input and returns the processed data.
|
| 186 |
+
"""
|
| 187 |
+
# This function is the classic, non-Custom path; ensure editor sections are enabled
|
| 188 |
+
st.session_state['_custom_active'] = False
|
| 189 |
+
if not audio_bytes:
|
| 190 |
+
st.error("No audio data provided to process.")
|
| 191 |
+
return
|
| 192 |
+
|
| 193 |
+
tmp_file_path = None
|
| 194 |
+
log_to_browser_console("--- INFO: Starting unified audio processing. ---")
|
| 195 |
+
|
| 196 |
+
try:
|
| 197 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=Path(original_filename).suffix) as tmp_file:
|
| 198 |
+
tmp_file.write(audio_bytes)
|
| 199 |
+
tmp_file_path = tmp_file.name
|
| 200 |
+
|
| 201 |
+
processor = AUDIO_PROCESSOR_CLASS()
|
| 202 |
+
result_data = None
|
| 203 |
+
full_text = ""
|
| 204 |
+
word_timestamps = []
|
| 205 |
+
|
| 206 |
+
# Determine which processing path to take
|
| 207 |
+
if st.session_state.enable_translation:
|
| 208 |
+
with st.spinner("⏳ Performing AI Transcription & Translation... please wait."):
|
| 209 |
+
result_data, processor_logs = processor.get_word_timestamps_with_translation(
|
| 210 |
+
tmp_file_path,
|
| 211 |
+
st.session_state.target_language,
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
log_to_browser_console(processor_logs)
|
| 215 |
+
|
| 216 |
+
if not result_data or not result_data.get("original_text"):
|
| 217 |
+
st.warning(
|
| 218 |
+
"Could not generate transcription with translation. Check browser console (F12) for logs."
|
| 219 |
+
)
|
| 220 |
+
return
|
| 221 |
+
|
| 222 |
+
st.session_state.transcription_data = {
|
| 223 |
+
"text": result_data["original_text"],
|
| 224 |
+
"translated_text": result_data["translated_text"],
|
| 225 |
+
"word_timestamps": result_data["word_timestamps"],
|
| 226 |
+
"audio_bytes": audio_bytes,
|
| 227 |
+
"original_suffix": Path(original_filename).suffix,
|
| 228 |
+
"translation_success": result_data.get("translation_success", False),
|
| 229 |
+
"detected_language": result_data.get("language_detected", "unknown"),
|
| 230 |
+
}
|
| 231 |
+
# Update transcript feed (prepend, dedupe by digest)
|
| 232 |
+
try:
|
| 233 |
+
digest = hashlib.md5(audio_bytes).hexdigest()
|
| 234 |
+
except Exception:
|
| 235 |
+
digest = f"snap-{int(time.time()*1000)}"
|
| 236 |
+
if digest not in st.session_state.transcript_ids:
|
| 237 |
+
st.session_state.transcript_ids.add(digest)
|
| 238 |
+
st.session_state.transcript_feed.insert(
|
| 239 |
+
0,
|
| 240 |
+
{
|
| 241 |
+
"id": digest,
|
| 242 |
+
"ts": int(time.time() * 1000),
|
| 243 |
+
"text": result_data["original_text"],
|
| 244 |
+
},
|
| 245 |
+
)
|
| 246 |
+
# Rebuild edited_text with newest first
|
| 247 |
+
st.session_state.edited_text = "\n\n".join(
|
| 248 |
+
[s["text"] for s in st.session_state.transcript_feed]
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
else: # Standard processing without translation
|
| 252 |
+
with st.spinner("⏳ Performing AI Transcription... please wait."):
|
| 253 |
+
word_timestamps, processor_logs = processor.get_word_timestamps(
|
| 254 |
+
tmp_file_path
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
log_to_browser_console(processor_logs)
|
| 258 |
+
|
| 259 |
+
if not word_timestamps:
|
| 260 |
+
st.warning(
|
| 261 |
+
"Could not generate timestamps. Check browser console (F12) for logs."
|
| 262 |
+
)
|
| 263 |
+
return
|
| 264 |
+
|
| 265 |
+
full_text = " ".join([d["word"] for d in word_timestamps])
|
| 266 |
+
st.session_state.transcription_data = {
|
| 267 |
+
"text": full_text,
|
| 268 |
+
"word_timestamps": word_timestamps,
|
| 269 |
+
"audio_bytes": audio_bytes,
|
| 270 |
+
"original_suffix": Path(original_filename).suffix,
|
| 271 |
+
"translation_success": False,
|
| 272 |
+
}
|
| 273 |
+
# Update transcript feed (prepend, dedupe by digest)
|
| 274 |
+
try:
|
| 275 |
+
digest = hashlib.md5(audio_bytes).hexdigest()
|
| 276 |
+
except Exception:
|
| 277 |
+
digest = f"snap-{int(time.time()*1000)}"
|
| 278 |
+
if digest not in st.session_state.transcript_ids:
|
| 279 |
+
st.session_state.transcript_ids.add(digest)
|
| 280 |
+
st.session_state.transcript_feed.insert(
|
| 281 |
+
0, {"id": digest, "ts": int(time.time() * 1000), "text": full_text}
|
| 282 |
+
)
|
| 283 |
+
# Rebuild edited_text with newest first
|
| 284 |
+
st.session_state.edited_text = "\n\n".join(
|
| 285 |
+
[s["text"] for s in st.session_state.transcript_feed]
|
| 286 |
+
)
|
| 287 |
+
|
| 288 |
+
st.session_state.step = 1 # Keep it on the same step
|
| 289 |
+
st.success("🎉 AI processing complete! Results are shown below.")
|
| 290 |
+
|
| 291 |
+
except Exception as e:
|
| 292 |
+
st.error("An unexpected error occurred during audio processing!")
|
| 293 |
+
st.exception(e)
|
| 294 |
+
log_to_browser_console(f"--- FATAL ERROR in run_audio_processing: {traceback.format_exc()} ---")
|
| 295 |
+
finally:
|
| 296 |
+
if tmp_file_path and os.path.exists(tmp_file_path):
|
| 297 |
+
os.unlink(tmp_file_path)
|
| 298 |
+
|
| 299 |
+
time.sleep(1)
|
| 300 |
+
st.rerun()
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
# --- Main Application Logic ---
|
| 304 |
+
def main():
|
| 305 |
+
initialize_session_state()
|
| 306 |
+
|
| 307 |
+
st.markdown("""
|
| 308 |
+
<style>
|
| 309 |
+
.main .block-container { animation: fadeIn 0.2s ease-in-out; }
|
| 310 |
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
| 311 |
+
.block-container { padding-top: 1rem; }
|
| 312 |
+
</style>
|
| 313 |
+
""", unsafe_allow_html=True)
|
| 314 |
+
|
| 315 |
+
with st.sidebar:
|
| 316 |
+
st.markdown("## 🌐 Language Settings")
|
| 317 |
+
language_options = {'English': 'en', 'العربية': 'ar'}
|
| 318 |
+
selected_lang_display = st.selectbox(
|
| 319 |
+
"Interface Language",
|
| 320 |
+
options=list(language_options.keys()),
|
| 321 |
+
index=0 if st.session_state.language == 'en' else 1
|
| 322 |
+
)
|
| 323 |
+
st.session_state.language = language_options[selected_lang_display]
|
| 324 |
+
|
| 325 |
+
st.markdown("## 🔤 Translation Settings")
|
| 326 |
+
st.session_state.enable_translation = st.checkbox(
|
| 327 |
+
"Enable AI Translation" if st.session_state.language == 'en' else "تفعيل الترجمة بالذكاء الاصطناعي",
|
| 328 |
+
value=st.session_state.enable_translation,
|
| 329 |
+
help="Automatically translate transcribed text" if st.session_state.language == 'en' else "ترجمة النص تلقائياً"
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
if st.session_state.enable_translation:
|
| 333 |
+
target_lang_options = {
|
| 334 |
+
'Arabic (العربية)': 'ar', 'English': 'en', 'French (Français)': 'fr', 'Spanish (Español)': 'es'
|
| 335 |
+
}
|
| 336 |
+
selected_target = st.selectbox(
|
| 337 |
+
"Target Language" if st.session_state.language == 'en' else "اللغة المستهدفة",
|
| 338 |
+
options=list(target_lang_options.keys()), index=0
|
| 339 |
+
)
|
| 340 |
+
st.session_state.target_language = target_lang_options[selected_target]
|
| 341 |
+
# Auto summary toggle
|
| 342 |
+
st.session_state.auto_generate_summary = st.checkbox(
|
| 343 |
+
"Auto-generate Arabic summary" if st.session_state.language == 'en' else "توليد الملخص العربي تلقائياً",
|
| 344 |
+
value=st.session_state.auto_generate_summary
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
st.title("🎵 SyncMaster")
|
| 348 |
+
if st.session_state.language == 'ar':
|
| 349 |
+
st.markdown("### منصة المزامنة الذكية بين الصوت والنص")
|
| 350 |
+
else:
|
| 351 |
+
st.markdown("### The Intelligent Audio-Text Synchronization Platform")
|
| 352 |
+
|
| 353 |
+
col1, col2 = st.columns(2)
|
| 354 |
+
with col1:
|
| 355 |
+
st.markdown(f"**{'✅' if st.session_state.step >= 1 else '1️⃣'} Step 1: Upload & Process**")
|
| 356 |
+
with col2:
|
| 357 |
+
st.markdown(f"**{'✅' if st.session_state.step >= 2 else '2️⃣'} Step 2: Review & Customize**")
|
| 358 |
+
st.divider()
|
| 359 |
+
# Global settings for long recording retention and custom snapshot duration
|
| 360 |
+
with st.expander("⚙️ Recording Settings (Snapshots)", expanded=False):
|
| 361 |
+
st.session_state.setdefault('retention_minutes', 30)
|
| 362 |
+
# 0 means: use full buffer by default for Custom
|
| 363 |
+
st.session_state.setdefault('custom_snapshot_seconds', 0)
|
| 364 |
+
# Auto-Custom interval seconds (for frontend auto trigger)
|
| 365 |
+
st.session_state.setdefault('auto_custom_interval_sec', 10)
|
| 366 |
+
# Auto-start incremental snapshots when recording begins
|
| 367 |
+
st.session_state.setdefault('auto_start_custom', True)
|
| 368 |
+
st.session_state.retention_minutes = st.number_input("Retention window (minutes)", min_value=5, max_value=240, value=st.session_state.retention_minutes)
|
| 369 |
+
st.session_state.custom_snapshot_seconds = st.number_input("Custom snapshot (seconds; 0 = full buffer)", min_value=0, max_value=3600, value=st.session_state.custom_snapshot_seconds)
|
| 370 |
+
st.session_state.auto_custom_interval_sec = st.number_input("Auto Custom interval (seconds)", min_value=1, max_value=3600, value=st.session_state.auto_custom_interval_sec, help="How often to auto-trigger the same Custom action while recording.")
|
| 371 |
+
st.session_state.auto_start_custom = st.checkbox("Auto-start incremental snapshots on record", value=st.session_state.auto_start_custom, help="Start sending Custom intervals automatically as soon as you start recording.")
|
| 372 |
+
# Inject globals into the page for the component to pick up
|
| 373 |
+
components.html(f"""
|
| 374 |
+
<script>
|
| 375 |
+
window.ST_AREC_RETENTION_MINUTES = {int(st.session_state.retention_minutes)};
|
| 376 |
+
window.ST_AREC_CUSTOM_SNAPSHOT_SECONDS = {int(st.session_state.custom_snapshot_seconds)};
|
| 377 |
+
window.ST_AREC_LAST_FETCHED_END_MS = {int(st.session_state.get('lastFetchedEnd_ms', 0))};
|
| 378 |
+
window.ST_AREC_CUSTOM_AUTO_INTERVAL_SECONDS = {int(st.session_state.get('auto_custom_interval_sec', 10))};
|
| 379 |
+
window.ST_AREC_AUTO_START = {str(bool(st.session_state.get('auto_start_custom', True))).lower()};
|
| 380 |
+
console.log('Recorder config', window.ST_AREC_RETENTION_MINUTES, window.ST_AREC_CUSTOM_SNAPSHOT_SECONDS);
|
| 381 |
+
</script>
|
| 382 |
+
""", height=0)
|
| 383 |
+
|
| 384 |
+
if AUDIO_PROCESSOR_CLASS is None:
|
| 385 |
+
st.error("Fatal Error: The application could not start correctly.")
|
| 386 |
+
st.subheader("An error occurred while trying to import `AudioProcessor`:")
|
| 387 |
+
st.code(IMPORT_ERROR_TRACEBACK, language="python")
|
| 388 |
+
st.stop()
|
| 389 |
+
|
| 390 |
+
step_1_upload_and_process()
|
| 391 |
+
|
| 392 |
+
# Always show results if they exist, regardless of step
|
| 393 |
+
if st.session_state.transcription_data:
|
| 394 |
+
step_2_review_and_customize()
|
| 395 |
+
|
| 396 |
+
# --- Step 1: Upload and Process ---
|
| 397 |
+
def step_1_upload_and_process():
|
| 398 |
+
st.header("Step 1: Choose Your Audio Source")
|
| 399 |
+
|
| 400 |
+
upload_tab, record_tab = st.tabs(["📤 Upload a File", "🎙️ Record Audio"])
|
| 401 |
+
|
| 402 |
+
with upload_tab:
|
| 403 |
+
st.subheader("Upload an existing audio file")
|
| 404 |
+
uploaded_file = st.file_uploader("Choose an audio file", type=['mp3', 'wav', 'm4a'], help="Supported formats: MP3, WAV, M4A")
|
| 405 |
+
if uploaded_file:
|
| 406 |
+
st.session_state.audio_data = uploaded_file.getvalue()
|
| 407 |
+
st.success(f"File ready for processing: {uploaded_file.name}")
|
| 408 |
+
st.audio(st.session_state.audio_data)
|
| 409 |
+
if st.button("🚀 Start AI Processing", type="primary", use_container_width=True):
|
| 410 |
+
run_audio_processing(st.session_state.audio_data, uploaded_file.name)
|
| 411 |
+
if st.session_state.audio_data:
|
| 412 |
+
if st.button("🔄 Use a Different File"):
|
| 413 |
+
reset_session()
|
| 414 |
+
st.rerun()
|
| 415 |
+
|
| 416 |
+
with record_tab:
|
| 417 |
+
st.subheader("Record audio directly from your microphone")
|
| 418 |
+
st.info("Click the microphone icon to start recording. Use the ⏪ buttons to snapshot the last seconds without stopping. Processing can run automatically.")
|
| 419 |
+
|
| 420 |
+
# Use the audio recorder component
|
| 421 |
+
wav_audio_data = st_audiorec()
|
| 422 |
+
|
| 423 |
+
# Auto-process incoming snapshots using the existing flow (no external server)
|
| 424 |
+
st.session_state.setdefault('auto_process_snapshots', True)
|
| 425 |
+
st.checkbox("Auto-process snapshots (keeps recording)", key='auto_process_snapshots', help="When enabled, any snapshot from the recorder is processed immediately using the classic transcription method.")
|
| 426 |
+
|
| 427 |
+
if wav_audio_data:
|
| 428 |
+
# Two possible payload shapes: raw bytes array (legacy) or interval payload dict
|
| 429 |
+
if isinstance(wav_audio_data, dict) and wav_audio_data.get('type') in ('interval_wav', 'no_new'):
|
| 430 |
+
payload = wav_audio_data
|
| 431 |
+
# Mark Custom interval flow active so Step 2 editor/style can be hidden
|
| 432 |
+
st.session_state['_custom_active'] = True
|
| 433 |
+
if payload['type'] == 'no_new':
|
| 434 |
+
st.info("No new audio chunks yet.")
|
| 435 |
+
elif payload['type'] == 'interval_wav':
|
| 436 |
+
# Extract interval audio
|
| 437 |
+
b = bytes(payload['bytes'])
|
| 438 |
+
sr = int(payload.get('sr', 16000))
|
| 439 |
+
start_ms = int(payload['start_ms'])
|
| 440 |
+
end_ms = int(payload['end_ms'])
|
| 441 |
+
# Dedupe/trim logic
|
| 442 |
+
if end_ms <= start_ms:
|
| 443 |
+
st.warning("The received interval is empty.")
|
| 444 |
+
else:
|
| 445 |
+
# Prevent overlap with prior segment
|
| 446 |
+
last_end = st.session_state.lastFetchedEnd_ms or 0
|
| 447 |
+
eff_start_ms = max(start_ms, last_end)
|
| 448 |
+
if eff_start_ms < end_ms:
|
| 449 |
+
# If there is overlap, trim the audio bytes accordingly (assumes WAV PCM16 mono header 44 bytes)
|
| 450 |
+
try:
|
| 451 |
+
delta_ms = eff_start_ms - start_ms
|
| 452 |
+
if delta_ms > 0:
|
| 453 |
+
if len(b) >= 44 and b[0:4] == b'RIFF' and b[8:12] == b'WAVE':
|
| 454 |
+
bytes_per_sample = 2 # PCM16 mono
|
| 455 |
+
drop_samples = int(sr * (delta_ms / 1000.0))
|
| 456 |
+
drop_bytes = drop_samples * bytes_per_sample
|
| 457 |
+
data_size = int.from_bytes(b[40:44], 'little') if len(b) >= 44 else len(b) - 44
|
| 458 |
+
pcm = b[44:]
|
| 459 |
+
if drop_bytes < len(pcm):
|
| 460 |
+
pcm_trim = pcm[drop_bytes:]
|
| 461 |
+
else:
|
| 462 |
+
pcm_trim = b''
|
| 463 |
+
new_data_size = len(pcm_trim)
|
| 464 |
+
# Rebuild header sizes
|
| 465 |
+
header = bytearray(b[:44])
|
| 466 |
+
# ChunkSize at offset 4 = 36 + Subchunk2Size
|
| 467 |
+
(36 + new_data_size).to_bytes(4, 'little')
|
| 468 |
+
header[4:8] = (36 + new_data_size).to_bytes(4, 'little')
|
| 469 |
+
# Subchunk2Size at offset 40
|
| 470 |
+
header[40:44] = new_data_size.to_bytes(4, 'little')
|
| 471 |
+
b = bytes(header) + pcm_trim
|
| 472 |
+
else:
|
| 473 |
+
# Not a recognizable WAV header; keep as-is
|
| 474 |
+
pass
|
| 475 |
+
except Exception as _:
|
| 476 |
+
pass
|
| 477 |
+
# Compute checksum
|
| 478 |
+
digest = hashlib.md5(b).hexdigest()
|
| 479 |
+
# Skip if identical checksum and same window
|
| 480 |
+
exists = any(s.get('checksum') == digest and s.get('start_ms') == eff_start_ms and s.get('end_ms') == end_ms for s in st.session_state.broadcast_segments)
|
| 481 |
+
if not exists:
|
| 482 |
+
# Show spinner during extraction so the user sees a waiting icon until text appears
|
| 483 |
+
with st.spinner("⏳ Extracting text from interval..."):
|
| 484 |
+
# Run standard pipeline to get text (no translation to keep it light)
|
| 485 |
+
# Reuse run_audio_processing internals via a temp path
|
| 486 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tf:
|
| 487 |
+
tf.write(b)
|
| 488 |
+
tmp_path = tf.name
|
| 489 |
+
try:
|
| 490 |
+
processor = AUDIO_PROCESSOR_CLASS()
|
| 491 |
+
word_timestamps, processor_logs = processor.get_word_timestamps(tmp_path)
|
| 492 |
+
full_text = " ".join([d['word'] for d in word_timestamps]) if word_timestamps else ""
|
| 493 |
+
# Fallback: if timestamps extraction yielded no words, try plain transcription
|
| 494 |
+
if not full_text:
|
| 495 |
+
plain_text, err = processor.transcribe_audio(tmp_path)
|
| 496 |
+
if plain_text:
|
| 497 |
+
full_text = plain_text.strip()
|
| 498 |
+
finally:
|
| 499 |
+
if os.path.exists(tmp_path): os.unlink(tmp_path)
|
| 500 |
+
|
| 501 |
+
# Append segment immediately with only the original text
|
| 502 |
+
seg = {
|
| 503 |
+
'id': digest,
|
| 504 |
+
'recording_id': payload.get('session_id', 'local'),
|
| 505 |
+
'start_ms': eff_start_ms,
|
| 506 |
+
'end_ms': end_ms,
|
| 507 |
+
'checksum': digest,
|
| 508 |
+
'text': full_text,
|
| 509 |
+
'translations': {},
|
| 510 |
+
}
|
| 511 |
+
st.session_state.broadcast_segments.append(seg)
|
| 512 |
+
st.session_state.broadcast_segments.sort(key=lambda s: s['start_ms'])
|
| 513 |
+
st.session_state.lastFetchedEnd_ms = end_ms
|
| 514 |
+
if full_text:
|
| 515 |
+
if digest not in st.session_state.transcript_ids:
|
| 516 |
+
st.session_state.transcript_ids.add(digest)
|
| 517 |
+
st.session_state.transcript_feed.insert(
|
| 518 |
+
0,
|
| 519 |
+
{
|
| 520 |
+
"id": digest,
|
| 521 |
+
"ts": int(time.time() * 1000),
|
| 522 |
+
"text": full_text,
|
| 523 |
+
},
|
| 524 |
+
)
|
| 525 |
+
st.session_state.edited_text = "\n\n".join(
|
| 526 |
+
[s["text"] for s in st.session_state.transcript_feed]
|
| 527 |
+
)
|
| 528 |
+
st.success(f"Added new segment: {eff_start_ms/1000:.2f}s → {end_ms/1000:.2f}s")
|
| 529 |
+
|
| 530 |
+
# Now, asynchronously update translation and summary after segment is added
|
| 531 |
+
def update_translation_and_summary():
|
| 532 |
+
try:
|
| 533 |
+
if full_text and st.session_state.get('enable_translation', True):
|
| 534 |
+
translator = get_translator()
|
| 535 |
+
sel_lang = st.session_state.get('broadcast_translation_lang', 'ar')
|
| 536 |
+
tx, _ = translator.translate_text(full_text, target_language=sel_lang)
|
| 537 |
+
if tx:
|
| 538 |
+
seg['translations'][sel_lang] = tx
|
| 539 |
+
except Exception:
|
| 540 |
+
pass
|
| 541 |
+
# Update summary
|
| 542 |
+
if st.session_state.get('auto_generate_summary', True):
|
| 543 |
+
try:
|
| 544 |
+
source_text = " \n".join([s.get('text', '') for s in st.session_state.broadcast_segments if s.get('text')])
|
| 545 |
+
if source_text.strip():
|
| 546 |
+
summary, _ = generate_summary(source_text, target_language=st.session_state.get('summary_language', 'ar'))
|
| 547 |
+
if summary:
|
| 548 |
+
st.session_state.arabic_explanation = summary
|
| 549 |
+
except Exception:
|
| 550 |
+
pass
|
| 551 |
+
import threading
|
| 552 |
+
threading.Thread(target=update_translation_and_summary, daemon=True).start()
|
| 553 |
+
else:
|
| 554 |
+
st.info("Duplicate segment ignored.")
|
| 555 |
+
else:
|
| 556 |
+
st.info("No new parts after the last point.")
|
| 557 |
+
else:
|
| 558 |
+
# Legacy: treat as full wav bytes
|
| 559 |
+
bytes_data = bytes(wav_audio_data)
|
| 560 |
+
# This is not the Custom interval mode
|
| 561 |
+
st.session_state['_custom_active'] = False
|
| 562 |
+
st.session_state.audio_data = bytes_data
|
| 563 |
+
st.audio(bytes_data)
|
| 564 |
+
digest = hashlib.md5(bytes_data).hexdigest()
|
| 565 |
+
last_digest = st.session_state.get('_last_component_digest')
|
| 566 |
+
if st.session_state.auto_process_snapshots and digest != last_digest:
|
| 567 |
+
st.session_state['_last_component_digest'] = digest
|
| 568 |
+
run_audio_processing(bytes_data, "snapshot.wav")
|
| 569 |
+
else:
|
| 570 |
+
if st.button("📝 Extract Text", type="primary", use_container_width=True):
|
| 571 |
+
st.session_state['_last_component_digest'] = digest
|
| 572 |
+
run_audio_processing(bytes_data, "recorded_audio.wav")
|
| 573 |
+
|
| 574 |
+
# Simplified: removed external live slice server UI to avoid complexity
|
| 575 |
+
|
| 576 |
+
# Always show Broadcast view in Step 1 as well (regardless of transcription_data)
|
| 577 |
+
with st.expander("📻 Broadcast (latest first)", expanded=True):
|
| 578 |
+
# Language selector for broadcast translations
|
| 579 |
+
try:
|
| 580 |
+
translator = get_translator()
|
| 581 |
+
langs = translator.get_supported_languages()
|
| 582 |
+
codes = list(langs.keys())
|
| 583 |
+
labels = [f"{code} — {langs[code]}" for code in codes]
|
| 584 |
+
current = st.session_state.get('broadcast_translation_lang', 'ar')
|
| 585 |
+
default_index = codes.index(current) if current in codes else 0
|
| 586 |
+
sel_label = st.selectbox("Broadcast translation language", labels, index=default_index)
|
| 587 |
+
sel_code = sel_label.split(' — ')[0]
|
| 588 |
+
st.session_state.broadcast_translation_lang = sel_code
|
| 589 |
+
except Exception:
|
| 590 |
+
sel_code = st.session_state.get('broadcast_translation_lang', 'ar')
|
| 591 |
+
if st.session_state.broadcast_segments:
|
| 592 |
+
for s in sorted(st.session_state.broadcast_segments, key=lambda s: s['start_ms'], reverse=True):
|
| 593 |
+
st.markdown(f"**[{s['start_ms']/1000:.2f}s → {s['end_ms']/1000:.2f}s]**")
|
| 594 |
+
st.write(s.get('text', ''))
|
| 595 |
+
# Ensure and show translation in selected language
|
| 596 |
+
if s.get('text') and st.session_state.get('enable_translation', True):
|
| 597 |
+
if 'translations' not in s or not isinstance(s.get('translations'), dict):
|
| 598 |
+
s['translations'] = {}
|
| 599 |
+
if sel_code not in s['translations']:
|
| 600 |
+
try:
|
| 601 |
+
tx, _ = get_translator().translate_text(s.get('text', ''), target_language=sel_code)
|
| 602 |
+
if tx:
|
| 603 |
+
s['translations'][sel_code] = tx
|
| 604 |
+
except Exception:
|
| 605 |
+
pass
|
| 606 |
+
if s['translations'].get(sel_code):
|
| 607 |
+
st.caption(f"Translation ({sel_code.upper()}):")
|
| 608 |
+
st.write(s['translations'][sel_code])
|
| 609 |
+
st.divider()
|
| 610 |
+
else:
|
| 611 |
+
st.caption("No segments yet. Use the Custom button while recording.")
|
| 612 |
+
|
| 613 |
+
# Note: external live slice helper removed to keep the app simple and fully local
|
| 614 |
+
|
| 615 |
+
# --- Step 2: Review and Customize ---
|
| 616 |
+
def step_2_review_and_customize():
|
| 617 |
+
st.header("✅ Extracted Text & Translation")
|
| 618 |
+
|
| 619 |
+
# Display translation results if available
|
| 620 |
+
if st.session_state.transcription_data.get('translation_success', False):
|
| 621 |
+
st.success(f"🌐 Translation completed! Detected language: {st.session_state.transcription_data.get('detected_language', 'N/A')}")
|
| 622 |
+
col1, col2 = st.columns(2)
|
| 623 |
+
with col1:
|
| 624 |
+
st.subheader("Original Text")
|
| 625 |
+
st.text_area("Original Transcription", value=st.session_state.transcription_data['text'], height=150, key="original_text_area")
|
| 626 |
+
st.button("📋 Copy Original Text", on_click=lambda: st.toast("Copied to clipboard!"), args=(), kwargs={'clipboard': st.session_state.transcription_data['text']})
|
| 627 |
+
|
| 628 |
+
with col2:
|
| 629 |
+
st.subheader(f"Translation ({st.session_state.target_language.upper()})")
|
| 630 |
+
st.text_area("Translated Text", value=st.session_state.transcription_data['translated_text'], height=150, key="translated_text_area")
|
| 631 |
+
st.button("📋 Copy Translated Text", on_click=lambda: st.toast("Copied to clipboard!"), args=(), kwargs={'clipboard': st.session_state.transcription_data['translated_text']})
|
| 632 |
+
|
| 633 |
+
# Editor and style panels removed per request
|
| 634 |
+
# Remove navigation buttons
|
| 635 |
+
|
| 636 |
+
st.divider()
|
| 637 |
+
st.subheader("🧠 Summary")
|
| 638 |
+
st.info("A concise summary tied to the extracted broadcast text with key points and relevant examples.")
|
| 639 |
+
|
| 640 |
+
# Summary language selector (default Arabic)
|
| 641 |
+
try:
|
| 642 |
+
translator = get_translator()
|
| 643 |
+
langs = translator.get_supported_languages()
|
| 644 |
+
codes = list(langs.keys())
|
| 645 |
+
labels = [f"{code} — {langs[code]}" for code in codes]
|
| 646 |
+
cur = st.session_state.get('summary_language', 'ar')
|
| 647 |
+
idx = codes.index(cur) if cur in codes else 0
|
| 648 |
+
sel = st.selectbox("Summary language", labels, index=idx)
|
| 649 |
+
st.session_state.summary_language = sel.split(' — ')[0]
|
| 650 |
+
except Exception:
|
| 651 |
+
pass
|
| 652 |
+
|
| 653 |
+
# Build source from broadcast segments; fallback to full transcription if needed
|
| 654 |
+
source_text = ""
|
| 655 |
+
if st.session_state.broadcast_segments:
|
| 656 |
+
source_text = " \n".join([s.get('text', '') for s in st.session_state.broadcast_segments if s.get('text')])
|
| 657 |
+
elif st.session_state.transcription_data:
|
| 658 |
+
td = st.session_state.transcription_data
|
| 659 |
+
source_text = td.get('text') or td.get('translated_text', '') or ''
|
| 660 |
+
|
| 661 |
+
if 'arabic_explanation' not in st.session_state:
|
| 662 |
+
st.session_state.arabic_explanation = None
|
| 663 |
+
|
| 664 |
+
colE, colF = st.columns([1, 4])
|
| 665 |
+
with colE:
|
| 666 |
+
if st.button("✍️ Generate summary", use_container_width=True):
|
| 667 |
+
with st.spinner("⏳ Generating bullet-point summary..."):
|
| 668 |
+
explained, err = generate_summary(source_text or '', target_language=st.session_state.get('summary_language', 'ar'))
|
| 669 |
+
if explained:
|
| 670 |
+
st.session_state.arabic_explanation = explained
|
| 671 |
+
st.success("Summary generated successfully.")
|
| 672 |
+
else:
|
| 673 |
+
st.error(err or "Failed to create summary. Please try again.")
|
| 674 |
+
with colF:
|
| 675 |
+
st.text_area("Summary", value=st.session_state.arabic_explanation or "", height=350)
|
| 676 |
+
|
| 677 |
+
# --- Step 3: Export ---
|
| 678 |
+
# Removed Step 3 export UI and related functions per user request.
|
| 679 |
+
|
| 680 |
+
def reset_session():
|
| 681 |
+
"""Resets the session state by clearing specific keys and re-initializing."""
|
| 682 |
+
log_to_browser_console("--- INFO: Resetting session state. ---")
|
| 683 |
+
keys_to_clear = ['step', 'audio_data', 'transcription_data', 'edited_text', 'video_style', 'new_recording']
|
| 684 |
+
for key in keys_to_clear:
|
| 685 |
+
if key in st.session_state:
|
| 686 |
+
del st.session_state[key]
|
| 687 |
+
initialize_session_state()
|
| 688 |
+
|
| 689 |
+
# --- Entry Point ---
|
| 690 |
+
if __name__ == "__main__":
|
| 691 |
+
if check_api_key():
|
| 692 |
+
initialize_session_state()
|
| 693 |
+
main()
|
app_config.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration Module for SyncMaster
|
| 3 |
+
إعدادات التطبيق الأساسية
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import logging
|
| 8 |
+
|
| 9 |
+
# Configure logging
|
| 10 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 11 |
+
|
| 12 |
+
class AppConfig:
|
| 13 |
+
"""Configuration class for SyncMaster application"""
|
| 14 |
+
|
| 15 |
+
# Server settings
|
| 16 |
+
STREAMLIT_PORT = int(os.getenv('STREAMLIT_PORT', 5050))
|
| 17 |
+
RECORDER_PORT = int(os.getenv('RECORDER_PORT', 5001))
|
| 18 |
+
|
| 19 |
+
# Development vs Production
|
| 20 |
+
IS_PRODUCTION = os.getenv('SPACE_ID') is not None or os.getenv('RAILWAY_ENVIRONMENT') is not None
|
| 21 |
+
|
| 22 |
+
# Host settings
|
| 23 |
+
if IS_PRODUCTION:
|
| 24 |
+
STREAMLIT_HOST = "0.0.0.0"
|
| 25 |
+
RECORDER_HOST = "0.0.0.0"
|
| 26 |
+
else:
|
| 27 |
+
STREAMLIT_HOST = "localhost"
|
| 28 |
+
RECORDER_HOST = "localhost"
|
| 29 |
+
|
| 30 |
+
# Integration settings
|
| 31 |
+
USE_INTEGRATED_SERVER = IS_PRODUCTION or os.getenv('USE_INTEGRATED_SERVER', 'true').lower() == 'true'
|
| 32 |
+
|
| 33 |
+
# Logging
|
| 34 |
+
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
|
| 35 |
+
|
| 36 |
+
@classmethod
|
| 37 |
+
def get_streamlit_url(cls):
|
| 38 |
+
"""Get the Streamlit application URL"""
|
| 39 |
+
return f"http://{cls.STREAMLIT_HOST}:{cls.STREAMLIT_PORT}"
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def get_recorder_url(cls):
|
| 43 |
+
"""Get the recorder server URL"""
|
| 44 |
+
return f"http://{cls.RECORDER_HOST}:{cls.RECORDER_PORT}"
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def log_config(cls):
|
| 48 |
+
"""Log current configuration"""
|
| 49 |
+
logging.info("📋 SyncMaster Configuration:")
|
| 50 |
+
logging.info(f" • Production Mode: {cls.IS_PRODUCTION}")
|
| 51 |
+
logging.info(f" • Integrated Server: {cls.USE_INTEGRATED_SERVER}")
|
| 52 |
+
logging.info(f" • Streamlit: {cls.get_streamlit_url()}")
|
| 53 |
+
logging.info(f" • Recorder: {cls.get_recorder_url()}")
|
| 54 |
+
|
| 55 |
+
# Initialize configuration
|
| 56 |
+
config = AppConfig()
|
| 57 |
+
|
| 58 |
+
if __name__ == "__main__":
|
| 59 |
+
config.log_config()
|
app_launcher.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
App Launcher - يشغل التطبيق مع الخادم المدمج
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
# Add current directory to path
|
| 10 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 11 |
+
|
| 12 |
+
# Force start integrated server
|
| 13 |
+
print("🚀 Starting integrated recorder server...")
|
| 14 |
+
try:
|
| 15 |
+
from integrated_server import integrated_server
|
| 16 |
+
|
| 17 |
+
# Force start the server
|
| 18 |
+
if not integrated_server.is_running:
|
| 19 |
+
result = integrated_server.start_recorder_server()
|
| 20 |
+
if result:
|
| 21 |
+
print("✅ Recorder server started successfully")
|
| 22 |
+
else:
|
| 23 |
+
print("⚠️ Warning: Could not start recorder server")
|
| 24 |
+
else:
|
| 25 |
+
print("✅ Recorder server already running")
|
| 26 |
+
|
| 27 |
+
except Exception as e:
|
| 28 |
+
print(f"❌ Error starting recorder server: {e}")
|
| 29 |
+
|
| 30 |
+
print("📱 Loading main application...")
|
| 31 |
+
|
| 32 |
+
# Execute the app.py content directly
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
# If running directly, execute app.py
|
| 35 |
+
exec(open('app.py').read())
|
| 36 |
+
else:
|
| 37 |
+
# If imported by Streamlit, import and execute
|
| 38 |
+
try:
|
| 39 |
+
exec(open('app.py').read())
|
| 40 |
+
print("✅ Application loaded successfully")
|
| 41 |
+
except Exception as e:
|
| 42 |
+
print(f"❌ Error loading application: {e}")
|
| 43 |
+
raise
|
audio_processor.py
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# audio_processor.py - Enhanced with AI Translation Support
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
import tempfile
|
| 6 |
+
from typing import List, Dict, Optional, Tuple
|
| 7 |
+
import json
|
| 8 |
+
import traceback
|
| 9 |
+
|
| 10 |
+
# --- DEFINITIVE NUMBA FIX ---
|
| 11 |
+
# This MUST be done BEFORE importing librosa
|
| 12 |
+
os.environ["NUMBA_CACHE_DIR"] = "/tmp"
|
| 13 |
+
|
| 14 |
+
# Now, import librosa safely
|
| 15 |
+
import librosa
|
| 16 |
+
# --- END OF FIX ---
|
| 17 |
+
|
| 18 |
+
import google.generativeai as genai
|
| 19 |
+
from translator import AITranslator
|
| 20 |
+
import requests
|
| 21 |
+
from google.api_core import exceptions as google_exceptions
|
| 22 |
+
|
| 23 |
+
class AudioProcessor:
|
| 24 |
+
def __init__(self):
|
| 25 |
+
self.translator = None
|
| 26 |
+
self.init_error = None
|
| 27 |
+
self._initialize_translator()
|
| 28 |
+
|
| 29 |
+
def _initialize_translator(self):
|
| 30 |
+
"""Initialize AI translator for multi-language support"""
|
| 31 |
+
try:
|
| 32 |
+
self.translator = AITranslator()
|
| 33 |
+
if self.translator.init_error:
|
| 34 |
+
print(f"--- WARNING: Translator has initialization error: {self.translator.init_error} ---")
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"--- WARNING: Translator initialization failed: {str(e)} ---")
|
| 37 |
+
self.translator = None
|
| 38 |
+
|
| 39 |
+
def transcribe_audio(self, audio_file_path: str) -> Tuple[Optional[str], Optional[str]]:
|
| 40 |
+
"""
|
| 41 |
+
Transcribes audio. Returns (text, error_message).
|
| 42 |
+
Uses Gemini first (if available), then falls back to Groq Whisper.
|
| 43 |
+
"""
|
| 44 |
+
if not os.path.exists(audio_file_path):
|
| 45 |
+
return None, f"--- ERROR: Audio file for transcription not found at: {audio_file_path} ---"
|
| 46 |
+
|
| 47 |
+
# Try Gemini first if available
|
| 48 |
+
gemini_err = None
|
| 49 |
+
try:
|
| 50 |
+
if self.translator and self.translator.model:
|
| 51 |
+
audio_file = genai.upload_file(path=audio_file_path)
|
| 52 |
+
prompt = (
|
| 53 |
+
"You are an ASR system. Transcribe the audio accurately. "
|
| 54 |
+
"Auto-detect the spoken language and return ONLY the verbatim transcript in that same language. "
|
| 55 |
+
"Do not translate. Do not add labels or timestamps."
|
| 56 |
+
)
|
| 57 |
+
response = self.translator.model.generate_content([prompt, audio_file])
|
| 58 |
+
if response and hasattr(response, 'text') and response.text:
|
| 59 |
+
return response.text.strip(), None
|
| 60 |
+
else:
|
| 61 |
+
gemini_err = "--- WARNING: Gemini returned an empty response for transcription. ---"
|
| 62 |
+
except google_exceptions.ResourceExhausted:
|
| 63 |
+
gemini_err = "--- QUOTA ERROR: You have exceeded the daily free usage limit for the AI service. Please wait for your quota to reset (usually within 24 hours) or upgrade your Google AI plan. ---"
|
| 64 |
+
except Exception:
|
| 65 |
+
gemini_err = f"--- FATAL ERROR during Gemini transcription: {traceback.format_exc()} ---"
|
| 66 |
+
|
| 67 |
+
# Fallback: Groq Whisper
|
| 68 |
+
text, groq_err = self._transcribe_with_groq(audio_file_path)
|
| 69 |
+
if text:
|
| 70 |
+
return text, None
|
| 71 |
+
|
| 72 |
+
# If all failed
|
| 73 |
+
combined_err = groq_err or gemini_err or "--- ERROR: No transcription provider available. ---"
|
| 74 |
+
return None, combined_err
|
| 75 |
+
|
| 76 |
+
def _transcribe_with_groq(self, audio_file_path: str) -> Tuple[Optional[str], Optional[str]]:
|
| 77 |
+
"""Transcribe using Groq Whisper-compatible endpoint. Returns (text, error)."""
|
| 78 |
+
try:
|
| 79 |
+
load_dotenv()
|
| 80 |
+
groq_key = os.getenv("GROQ_API_KEY")
|
| 81 |
+
if not groq_key:
|
| 82 |
+
return None, "--- ERROR: GROQ_API_KEY not set. ---"
|
| 83 |
+
model = os.getenv("GROQ_WHISPER_MODEL", "whisper-large-v3")
|
| 84 |
+
url = "https://api.groq.com/openai/v1/audio/transcriptions"
|
| 85 |
+
headers = {"Authorization": f"Bearer {groq_key}"}
|
| 86 |
+
# Guess mime type by extension
|
| 87 |
+
filename = os.path.basename(audio_file_path)
|
| 88 |
+
mime = "audio/wav"
|
| 89 |
+
if filename.lower().endswith(".mp3"):
|
| 90 |
+
mime = "audio/mpeg"
|
| 91 |
+
elif filename.lower().endswith(".m4a"):
|
| 92 |
+
mime = "audio/mp4"
|
| 93 |
+
data = {
|
| 94 |
+
"model": model,
|
| 95 |
+
"response_format": "json",
|
| 96 |
+
}
|
| 97 |
+
with open(audio_file_path, "rb") as f:
|
| 98 |
+
files = {"file": (filename, f, mime)}
|
| 99 |
+
resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)
|
| 100 |
+
if not resp.ok:
|
| 101 |
+
try:
|
| 102 |
+
err = resp.json()
|
| 103 |
+
except Exception:
|
| 104 |
+
err = {"error": resp.text}
|
| 105 |
+
return None, f"--- ERROR: Groq transcription error {resp.status_code}: {err} ---"
|
| 106 |
+
out = resp.json()
|
| 107 |
+
text = out.get("text")
|
| 108 |
+
if not text:
|
| 109 |
+
return None, "--- ERROR: Groq transcription returned no text. ---"
|
| 110 |
+
return text.strip(), None
|
| 111 |
+
except Exception:
|
| 112 |
+
return None, f"--- FATAL ERROR during Groq transcription: {traceback.format_exc()} ---"
|
| 113 |
+
|
| 114 |
+
def get_audio_duration(self, audio_file_path: str) -> Tuple[Optional[float], Optional[str]]:
|
| 115 |
+
"""
|
| 116 |
+
Gets audio duration. Returns (duration, error_message).
|
| 117 |
+
"""
|
| 118 |
+
try:
|
| 119 |
+
if not os.path.exists(audio_file_path):
|
| 120 |
+
return None, f"--- ERROR: Audio file for duration not found at: {audio_file_path} ---"
|
| 121 |
+
|
| 122 |
+
duration = librosa.get_duration(path=audio_file_path)
|
| 123 |
+
if duration is None or duration < 0.1:
|
| 124 |
+
return None, f"--- ERROR: librosa returned an invalid duration: {duration}s ---"
|
| 125 |
+
return duration, None
|
| 126 |
+
except Exception as e:
|
| 127 |
+
error_msg = f"--- FATAL ERROR getting audio duration with librosa: {traceback.format_exc()} ---"
|
| 128 |
+
return None, error_msg
|
| 129 |
+
|
| 130 |
+
def get_word_timestamps(self, audio_file_path: str) -> Tuple[List[Dict], List[str]]:
|
| 131 |
+
"""
|
| 132 |
+
Generates timestamps. Returns (timestamps, log_messages).
|
| 133 |
+
"""
|
| 134 |
+
logs = ["--- INFO: Starting get_word_timestamps... ---"]
|
| 135 |
+
|
| 136 |
+
transcription, error = self.transcribe_audio(audio_file_path)
|
| 137 |
+
if error:
|
| 138 |
+
logs.append(error)
|
| 139 |
+
return [], logs
|
| 140 |
+
logs.append(f"--- DEBUG: Transcription successful. Text: '{transcription[:50]}...'")
|
| 141 |
+
|
| 142 |
+
audio_duration, error = self.get_audio_duration(audio_file_path)
|
| 143 |
+
if error:
|
| 144 |
+
logs.append(error)
|
| 145 |
+
return [], logs
|
| 146 |
+
logs.append(f"--- DEBUG: Audio duration successful. Duration: {audio_duration:.2f}s")
|
| 147 |
+
|
| 148 |
+
words = transcription.split()
|
| 149 |
+
if not words:
|
| 150 |
+
logs.append("--- WARNING: Transcription resulted in zero words. ---")
|
| 151 |
+
return [], logs
|
| 152 |
+
|
| 153 |
+
logs.append(f"--- INFO: Distributing {len(words)} words across the duration. ---")
|
| 154 |
+
word_timestamps = []
|
| 155 |
+
total_words = len(words)
|
| 156 |
+
usable_duration = max(0, audio_duration - 1.0)
|
| 157 |
+
|
| 158 |
+
for i, word in enumerate(words):
|
| 159 |
+
start_time = 0.5 + (i * (usable_duration / total_words))
|
| 160 |
+
end_time = 0.5 + ((i + 1) * (usable_duration / total_words))
|
| 161 |
+
word_timestamps.append({'word': word.strip(), 'start': round(start_time, 3), 'end': round(end_time, 3)})
|
| 162 |
+
|
| 163 |
+
logs.append(f"--- SUCCESS: Generated {len(word_timestamps)} word timestamps. ---")
|
| 164 |
+
return word_timestamps, logs
|
| 165 |
+
|
| 166 |
+
def get_word_timestamps_with_translation(self, audio_file_path: str, target_language: str = 'ar') -> Tuple[Dict, List[str]]:
|
| 167 |
+
"""
|
| 168 |
+
Enhanced function that provides both transcription and translation
|
| 169 |
+
|
| 170 |
+
Args:
|
| 171 |
+
audio_file_path: Path to audio file
|
| 172 |
+
target_language: Target language for translation ('ar' for Arabic)
|
| 173 |
+
|
| 174 |
+
Returns:
|
| 175 |
+
Tuple of (result_dict, log_messages)
|
| 176 |
+
result_dict contains: {
|
| 177 |
+
'original_text': str,
|
| 178 |
+
'translated_text': str,
|
| 179 |
+
'word_timestamps': List[Dict],
|
| 180 |
+
'translated_timestamps': List[Dict],
|
| 181 |
+
'language_detected': str,
|
| 182 |
+
'target_language': str
|
| 183 |
+
}
|
| 184 |
+
"""
|
| 185 |
+
logs = ["--- INFO: Starting enhanced transcription with translation... ---"]
|
| 186 |
+
|
| 187 |
+
# Get original transcription and timestamps
|
| 188 |
+
word_timestamps, transcription_logs = self.get_word_timestamps(audio_file_path)
|
| 189 |
+
logs.extend(transcription_logs)
|
| 190 |
+
|
| 191 |
+
if not word_timestamps:
|
| 192 |
+
# Fallback: try plain transcription (Gemini → Groq) then synthesize timestamps
|
| 193 |
+
logs.append("--- INFO: Falling back to plain transcription because timestamps are empty. ---")
|
| 194 |
+
plain_text, err = self.transcribe_audio(audio_file_path)
|
| 195 |
+
if not plain_text:
|
| 196 |
+
logs.append(err or "--- ERROR: Plain transcription fallback failed ---")
|
| 197 |
+
return {}, logs
|
| 198 |
+
logs.append("--- SUCCESS: Plain transcription fallback succeeded. ---")
|
| 199 |
+
# Synthesize naive word-level timestamps across duration
|
| 200 |
+
try:
|
| 201 |
+
duration, derr = self.get_audio_duration(audio_file_path)
|
| 202 |
+
if derr:
|
| 203 |
+
logs.append(derr)
|
| 204 |
+
duration = 0.0
|
| 205 |
+
words = plain_text.split()
|
| 206 |
+
if not words:
|
| 207 |
+
logs.append("--- WARNING: Fallback transcription produced zero words. ---")
|
| 208 |
+
return {}, logs
|
| 209 |
+
if duration and duration > 0.1:
|
| 210 |
+
usable_duration = max(0, duration - 1.0)
|
| 211 |
+
start_offset = 0.5
|
| 212 |
+
else:
|
| 213 |
+
# If duration not available, assume ~0.4s per word
|
| 214 |
+
usable_duration = 0.4 * max(1, len(words))
|
| 215 |
+
start_offset = 0.0
|
| 216 |
+
word_timestamps = []
|
| 217 |
+
total_words = len(words)
|
| 218 |
+
for i, w in enumerate(words):
|
| 219 |
+
start_time = start_offset + (i * (usable_duration / total_words))
|
| 220 |
+
end_time = start_offset + ((i + 1) * (usable_duration / total_words))
|
| 221 |
+
word_timestamps.append({'word': w.strip(), 'start': round(start_time, 3), 'end': round(end_time, 3)})
|
| 222 |
+
logs.append(f"--- INFO: Synthesized {len(word_timestamps)} timestamps from fallback transcript. ---")
|
| 223 |
+
except Exception:
|
| 224 |
+
logs.append(f"--- FATAL ERROR synthesizing timestamps: {traceback.format_exc()} ---")
|
| 225 |
+
return {}, logs
|
| 226 |
+
|
| 227 |
+
# Extract original text
|
| 228 |
+
original_text = " ".join([d['word'] for d in word_timestamps])
|
| 229 |
+
logs.append(f"--- INFO: Original transcription: '{original_text[:50]}...' ---")
|
| 230 |
+
|
| 231 |
+
# Initialize result dictionary
|
| 232 |
+
result = {
|
| 233 |
+
'original_text': original_text,
|
| 234 |
+
'translated_text': '',
|
| 235 |
+
'word_timestamps': word_timestamps,
|
| 236 |
+
'translated_timestamps': [],
|
| 237 |
+
'language_detected': 'unknown',
|
| 238 |
+
'target_language': target_language,
|
| 239 |
+
'translation_success': False
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
# Check if translator is available
|
| 243 |
+
if not self.translator:
|
| 244 |
+
logs.append("--- WARNING: Translator not available, returning original text only ---")
|
| 245 |
+
result['translated_text'] = original_text
|
| 246 |
+
return result, logs
|
| 247 |
+
|
| 248 |
+
try:
|
| 249 |
+
# Translate the text
|
| 250 |
+
translated_text, translation_error = self.translator.translate_text(
|
| 251 |
+
original_text,
|
| 252 |
+
target_language=target_language
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
if translated_text:
|
| 256 |
+
result['translated_text'] = translated_text
|
| 257 |
+
result['translation_success'] = True
|
| 258 |
+
logs.append(f"--- SUCCESS: Translation completed: '{translated_text[:50]}...' ---")
|
| 259 |
+
|
| 260 |
+
# Create translated timestamps by mapping words
|
| 261 |
+
translated_timestamps = self._create_translated_timestamps(
|
| 262 |
+
word_timestamps,
|
| 263 |
+
original_text,
|
| 264 |
+
translated_text
|
| 265 |
+
)
|
| 266 |
+
result['translated_timestamps'] = translated_timestamps
|
| 267 |
+
logs.append(f"--- INFO: Created {len(translated_timestamps)} translated timestamps ---")
|
| 268 |
+
|
| 269 |
+
else:
|
| 270 |
+
logs.append(f"--- ERROR: Translation failed: {translation_error} ---")
|
| 271 |
+
result['translated_text'] = original_text # Fallback to original
|
| 272 |
+
result['translated_timestamps'] = word_timestamps # Use original timestamps
|
| 273 |
+
|
| 274 |
+
except Exception as e:
|
| 275 |
+
error_msg = f"--- FATAL ERROR during translation process: {traceback.format_exc()} ---"
|
| 276 |
+
logs.append(error_msg)
|
| 277 |
+
result['translated_text'] = original_text # Fallback
|
| 278 |
+
result['translated_timestamps'] = word_timestamps
|
| 279 |
+
|
| 280 |
+
return result, logs
|
| 281 |
+
|
| 282 |
+
def _create_translated_timestamps(self, original_timestamps: List[Dict], original_text: str, translated_text: str) -> List[Dict]:
|
| 283 |
+
"""
|
| 284 |
+
Create timestamps for translated text by proportional mapping
|
| 285 |
+
|
| 286 |
+
Args:
|
| 287 |
+
original_timestamps: Original word timestamps
|
| 288 |
+
original_text: Original transcribed text
|
| 289 |
+
translated_text: Translated text
|
| 290 |
+
|
| 291 |
+
Returns:
|
| 292 |
+
List of translated word timestamps
|
| 293 |
+
"""
|
| 294 |
+
try:
|
| 295 |
+
translated_words = translated_text.split()
|
| 296 |
+
if not translated_words:
|
| 297 |
+
return []
|
| 298 |
+
|
| 299 |
+
# Get total duration from original timestamps
|
| 300 |
+
if not original_timestamps:
|
| 301 |
+
return []
|
| 302 |
+
|
| 303 |
+
start_time = original_timestamps[0]['start']
|
| 304 |
+
end_time = original_timestamps[-1]['end']
|
| 305 |
+
total_duration = end_time - start_time
|
| 306 |
+
|
| 307 |
+
# Create proportional timestamps for translated words
|
| 308 |
+
translated_timestamps = []
|
| 309 |
+
word_count = len(translated_words)
|
| 310 |
+
|
| 311 |
+
for i, word in enumerate(translated_words):
|
| 312 |
+
# Calculate proportional timing
|
| 313 |
+
word_start = start_time + (i * total_duration / word_count)
|
| 314 |
+
word_end = start_time + ((i + 1) * total_duration / word_count)
|
| 315 |
+
|
| 316 |
+
translated_timestamps.append({
|
| 317 |
+
'word': word.strip(),
|
| 318 |
+
'start': round(word_start, 3),
|
| 319 |
+
'end': round(word_end, 3)
|
| 320 |
+
})
|
| 321 |
+
|
| 322 |
+
return translated_timestamps
|
| 323 |
+
|
| 324 |
+
except Exception as e:
|
| 325 |
+
print(f"--- ERROR creating translated timestamps: {str(e)} ---")
|
| 326 |
+
return []
|
| 327 |
+
|
| 328 |
+
def batch_translate_transcription(self, audio_file_path: str, target_languages: List[str]) -> Tuple[Dict, List[str]]:
|
| 329 |
+
"""
|
| 330 |
+
Transcribe audio and translate to multiple languages
|
| 331 |
+
|
| 332 |
+
Args:
|
| 333 |
+
audio_file_path: Path to audio file
|
| 334 |
+
target_languages: List of target language codes
|
| 335 |
+
|
| 336 |
+
Returns:
|
| 337 |
+
Tuple of (results_dict, log_messages)
|
| 338 |
+
"""
|
| 339 |
+
logs = ["--- INFO: Starting batch translation process... ---"]
|
| 340 |
+
|
| 341 |
+
# Get original transcription
|
| 342 |
+
word_timestamps, transcription_logs = self.get_word_timestamps(audio_file_path)
|
| 343 |
+
logs.extend(transcription_logs)
|
| 344 |
+
|
| 345 |
+
if not word_timestamps:
|
| 346 |
+
return {}, logs
|
| 347 |
+
|
| 348 |
+
original_text = " ".join([d['word'] for d in word_timestamps])
|
| 349 |
+
|
| 350 |
+
# Initialize results
|
| 351 |
+
results = {
|
| 352 |
+
'original': {
|
| 353 |
+
'text': original_text,
|
| 354 |
+
'timestamps': word_timestamps,
|
| 355 |
+
'language': 'detected'
|
| 356 |
+
},
|
| 357 |
+
'translations': {}
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
# Translate to each target language
|
| 361 |
+
if self.translator:
|
| 362 |
+
for lang_code in target_languages:
|
| 363 |
+
try:
|
| 364 |
+
translated_text, error = self.translator.translate_text(original_text, lang_code)
|
| 365 |
+
if translated_text:
|
| 366 |
+
translated_timestamps = self._create_translated_timestamps(
|
| 367 |
+
word_timestamps, original_text, translated_text
|
| 368 |
+
)
|
| 369 |
+
results['translations'][lang_code] = {
|
| 370 |
+
'text': translated_text,
|
| 371 |
+
'timestamps': translated_timestamps,
|
| 372 |
+
'success': True
|
| 373 |
+
}
|
| 374 |
+
logs.append(f"--- SUCCESS: Translation to {lang_code} completed ---")
|
| 375 |
+
else:
|
| 376 |
+
results['translations'][lang_code] = {
|
| 377 |
+
'text': original_text,
|
| 378 |
+
'timestamps': word_timestamps,
|
| 379 |
+
'success': False,
|
| 380 |
+
'error': error
|
| 381 |
+
}
|
| 382 |
+
logs.append(f"--- ERROR: Translation to {lang_code} failed: {error} ---")
|
| 383 |
+
except Exception as e:
|
| 384 |
+
logs.append(f"--- FATAL ERROR translating to {lang_code}: {str(e)} ---")
|
| 385 |
+
else:
|
| 386 |
+
logs.append("--- WARNING: Translator not available for batch translation ---")
|
| 387 |
+
|
| 388 |
+
return results, logs
|
comprehensive_test.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
اختبار شامل للتحقق من إصلاح جميع المشاكل
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
def test_server_health():
|
| 12 |
+
"""اختبار صحة الخادم"""
|
| 13 |
+
print("🏥 اختبار صحة الخادم...")
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
response = requests.get('http://localhost:5001/record', timeout=5)
|
| 17 |
+
if response.status_code == 200:
|
| 18 |
+
data = response.json()
|
| 19 |
+
print(f"✅ الخادم يعمل: {data.get('message')}")
|
| 20 |
+
return True
|
| 21 |
+
else:
|
| 22 |
+
print(f"❌ مشكلة في الخادم: {response.status_code}")
|
| 23 |
+
return False
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f"❌ لا يمكن الوصول للخادم: {e}")
|
| 26 |
+
return False
|
| 27 |
+
|
| 28 |
+
def test_cors_headers():
|
| 29 |
+
"""اختبار CORS headers"""
|
| 30 |
+
print("\n🔧 اختبار CORS headers...")
|
| 31 |
+
|
| 32 |
+
try:
|
| 33 |
+
# اختبار OPTIONS request
|
| 34 |
+
response = requests.options('http://localhost:5001/summarize', timeout=5)
|
| 35 |
+
|
| 36 |
+
print(f"Status Code: {response.status_code}")
|
| 37 |
+
|
| 38 |
+
# فحص CORS headers
|
| 39 |
+
cors_origin = response.headers.get('Access-Control-Allow-Origin')
|
| 40 |
+
cors_methods = response.headers.get('Access-Control-Allow-Methods')
|
| 41 |
+
cors_headers = response.headers.get('Access-Control-Allow-Headers')
|
| 42 |
+
|
| 43 |
+
print(f"CORS Origin: '{cors_origin}'")
|
| 44 |
+
print(f"CORS Methods: '{cors_methods}'")
|
| 45 |
+
print(f"CORS Headers: '{cors_headers}'")
|
| 46 |
+
|
| 47 |
+
# التحقق من عدم وجود قيم مكررة
|
| 48 |
+
if cors_origin and ',' in cors_origin and cors_origin.count('*') > 1:
|
| 49 |
+
print("❌ مشكلة: CORS Origin يحتوي على قيم مكررة!")
|
| 50 |
+
return False
|
| 51 |
+
elif cors_origin == '*':
|
| 52 |
+
print("✅ CORS Origin صحيح")
|
| 53 |
+
return True
|
| 54 |
+
else:
|
| 55 |
+
print(f"⚠️ CORS Origin غير متوقع: {cors_origin}")
|
| 56 |
+
return False
|
| 57 |
+
|
| 58 |
+
except Exception as e:
|
| 59 |
+
print(f"❌ خطأ في اختبار CORS: {e}")
|
| 60 |
+
return False
|
| 61 |
+
|
| 62 |
+
def test_summarization():
|
| 63 |
+
"""اختبار وظيفة التلخيص"""
|
| 64 |
+
print("\n🤖 اختبار وظيفة التلخيص...")
|
| 65 |
+
|
| 66 |
+
test_data = {
|
| 67 |
+
"text": "Hello, how are you? What are you doing today? Tell me about your work and your plans.",
|
| 68 |
+
"language": "arabic",
|
| 69 |
+
"type": "full"
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
response = requests.post(
|
| 74 |
+
'http://localhost:5001/summarize',
|
| 75 |
+
json=test_data,
|
| 76 |
+
headers={'Content-Type': 'application/json'},
|
| 77 |
+
timeout=30
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
print(f"Status Code: {response.status_code}")
|
| 81 |
+
|
| 82 |
+
if response.status_code == 200:
|
| 83 |
+
data = response.json()
|
| 84 |
+
if data.get('success'):
|
| 85 |
+
print("✅ التلخيص نجح!")
|
| 86 |
+
summary = data.get('summary', '')
|
| 87 |
+
print(f"الملخص: {summary[:100]}...")
|
| 88 |
+
return True
|
| 89 |
+
else:
|
| 90 |
+
print(f"❌ فشل التلخيص: {data.get('error')}")
|
| 91 |
+
return False
|
| 92 |
+
else:
|
| 93 |
+
print(f"❌ خطأ HTTP: {response.status_code}")
|
| 94 |
+
print(f"الرد: {response.text}")
|
| 95 |
+
return False
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
print(f"❌ خطأ في اختبار التلخيص: {e}")
|
| 99 |
+
return False
|
| 100 |
+
|
| 101 |
+
def test_javascript_syntax():
|
| 102 |
+
"""اختبار صيغة JavaScript"""
|
| 103 |
+
print("\n📝 اختبار صيغة JavaScript...")
|
| 104 |
+
|
| 105 |
+
try:
|
| 106 |
+
with open('templates/recorder.html', 'r', encoding='utf-8') as f:
|
| 107 |
+
content = f.read()
|
| 108 |
+
|
| 109 |
+
# فحص بسيط للأقواس
|
| 110 |
+
js_start = content.find('<script>')
|
| 111 |
+
js_end = content.find('</script>')
|
| 112 |
+
|
| 113 |
+
if js_start == -1 or js_end == -1:
|
| 114 |
+
print("❌ لا يمكن العثور على JavaScript")
|
| 115 |
+
return False
|
| 116 |
+
|
| 117 |
+
js_content = content[js_start:js_end]
|
| 118 |
+
|
| 119 |
+
# عد الأقواس
|
| 120 |
+
open_braces = js_content.count('{')
|
| 121 |
+
close_braces = js_content.count('}')
|
| 122 |
+
|
| 123 |
+
print(f"أقواس فتح: {open_braces}")
|
| 124 |
+
print(f"أقواس إغلاق: {close_braces}")
|
| 125 |
+
|
| 126 |
+
if open_braces == close_braces:
|
| 127 |
+
print("✅ الأقواس متوازنة")
|
| 128 |
+
|
| 129 |
+
# فحص للكلمات المفتاحية الأساسية
|
| 130 |
+
if 'function' in js_content and 'async function' in js_content:
|
| 131 |
+
print("✅ الدوال موجودة")
|
| 132 |
+
return True
|
| 133 |
+
else:
|
| 134 |
+
print("⚠️ لا يمكن العثور على الدوال")
|
| 135 |
+
return False
|
| 136 |
+
else:
|
| 137 |
+
print(f"❌ الأقواس غير متوازنة! الفرق: {open_braces - close_braces}")
|
| 138 |
+
return False
|
| 139 |
+
|
| 140 |
+
except Exception as e:
|
| 141 |
+
print(f"❌ خطأ في فحص JavaScript: {e}")
|
| 142 |
+
return False
|
| 143 |
+
|
| 144 |
+
def test_translation_endpoints():
|
| 145 |
+
"""اختبار endpoints الترجمة"""
|
| 146 |
+
print("\n🌐 اختبار endpoints الترجمة...")
|
| 147 |
+
|
| 148 |
+
try:
|
| 149 |
+
# اختبار قائمة اللغات
|
| 150 |
+
response = requests.get('http://localhost:5001/languages', timeout=5)
|
| 151 |
+
if response.status_code == 200:
|
| 152 |
+
print("✅ endpoint اللغات يعمل")
|
| 153 |
+
else:
|
| 154 |
+
print(f"⚠️ مشكلة في endpoint اللغات: {response.status_code}")
|
| 155 |
+
|
| 156 |
+
# اختبار UI translations
|
| 157 |
+
response = requests.get('http://localhost:5001/ui-translations/en', timeout=5)
|
| 158 |
+
if response.status_code == 200:
|
| 159 |
+
print("✅ endpoint UI translations يعمل")
|
| 160 |
+
else:
|
| 161 |
+
print(f"⚠️ مشكلة في endpoint UI translations: {response.status_code}")
|
| 162 |
+
|
| 163 |
+
return True
|
| 164 |
+
|
| 165 |
+
except Exception as e:
|
| 166 |
+
print(f"❌ خطأ في اختبار endpoints الترجمة: {e}")
|
| 167 |
+
return False
|
| 168 |
+
|
| 169 |
+
def comprehensive_test():
|
| 170 |
+
"""اختبار شامل لجميع الوظائف"""
|
| 171 |
+
print("🚀 بدء الاختبار الشامل")
|
| 172 |
+
print("=" * 60)
|
| 173 |
+
|
| 174 |
+
tests = [
|
| 175 |
+
("صحة الخادم", test_server_health),
|
| 176 |
+
("CORS Headers", test_cors_headers),
|
| 177 |
+
("وظيفة التلخيص", test_summarization),
|
| 178 |
+
("صيغة JavaScript", test_javascript_syntax),
|
| 179 |
+
("endpoints الترجمة", test_translation_endpoints)
|
| 180 |
+
]
|
| 181 |
+
|
| 182 |
+
results = []
|
| 183 |
+
|
| 184 |
+
for test_name, test_func in tests:
|
| 185 |
+
print(f"\n🧪 اختبار: {test_name}")
|
| 186 |
+
print("-" * 40)
|
| 187 |
+
|
| 188 |
+
try:
|
| 189 |
+
result = test_func()
|
| 190 |
+
results.append((test_name, result))
|
| 191 |
+
|
| 192 |
+
if result:
|
| 193 |
+
print(f"✅ {test_name}: نجح")
|
| 194 |
+
else:
|
| 195 |
+
print(f"❌ {test_name}: فشل")
|
| 196 |
+
|
| 197 |
+
except Exception as e:
|
| 198 |
+
print(f"❌ {test_name}: خطأ - {e}")
|
| 199 |
+
results.append((test_name, False))
|
| 200 |
+
|
| 201 |
+
# النتائج النهائية
|
| 202 |
+
print("\n" + "=" * 60)
|
| 203 |
+
print("📊 ملخص نتائج الاختبار:")
|
| 204 |
+
print("=" * 60)
|
| 205 |
+
|
| 206 |
+
passed = 0
|
| 207 |
+
total = len(results)
|
| 208 |
+
|
| 209 |
+
for test_name, result in results:
|
| 210 |
+
status = "✅ نجح" if result else "❌ فشل"
|
| 211 |
+
print(f" {test_name}: {status}")
|
| 212 |
+
if result:
|
| 213 |
+
passed += 1
|
| 214 |
+
|
| 215 |
+
print(f"\nالنتيجة النهائية: {passed}/{total} اختبارات نجحت")
|
| 216 |
+
|
| 217 |
+
if passed == total:
|
| 218 |
+
print("🎉 جميع الاختبارات نجحت! النظام يعمل بشكل مثالي")
|
| 219 |
+
return True
|
| 220 |
+
else:
|
| 221 |
+
print(f"⚠️ {total - passed} اختبارات فشلت - هناك مشاكل تحتاج إصلاح")
|
| 222 |
+
return False
|
| 223 |
+
|
| 224 |
+
if __name__ == "__main__":
|
| 225 |
+
comprehensive_test()
|
custom_components/st-audiorec/.streamlit/config.toml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8eec7cce049f088766524596c7dd6229756df0d6331c8cfab099df7d2ebc9d5d
|
| 3 |
+
size 662
|
custom_components/st-audiorec/LICENCE
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:784d3a6fdb08d429f5de43125e9962e780d34cdf9b5f14b681c9a2d8e905bfec
|
| 3 |
+
size 1080
|
custom_components/st-audiorec/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:54716e3eaf4e6047fb50d99176bd2c6b24124b978af2eb30e44a8c6a74cdb9c0
|
| 3 |
+
size 1993
|
custom_components/st-audiorec/demo.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:efbe93ef9297821f3a1e1f1bdf0f75fb4a4351b154439d01d9ea0cbd49b996b8
|
| 3 |
+
size 2430
|
custom_components/st-audiorec/setup.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:492fd555416f2807df31dc43e10b9d3cd8d5c18636c586c7f37988e1d8b854c1
|
| 3 |
+
size 786
|
custom_components/st-audiorec/st_audiorec/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3f714847f1f4a2490e5dd80bc1eeb7b6fcb7850a3e682b491a28dd30fead486c
|
| 3 |
+
size 1622
|
custom_components/st-audiorec/st_audiorec/frontend/.prettierrc
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3375a44313ae0b6753868a7ae00dc03f618b0c23785b15980482e6b9457ca0f8
|
| 3 |
+
size 72
|
custom_components/st-audiorec/st_audiorec/frontend/build/asset-manifest.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d923ea03d2475f02335299c4a15a1ca84291e9cbbcd558e6abdb318abe5ccc6f
|
| 3 |
+
size 859
|
custom_components/st-audiorec/st_audiorec/frontend/build/bootstrap.min.css
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f396767523d7b7ce621d90aae93cbbd7a516275898efd19020be38aa5ae85d5c
|
| 3 |
+
size 206913
|
custom_components/st-audiorec/st_audiorec/frontend/build/index.html
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e007dc7fb036886292b996d076d57c6eb6eedf32b8e2c5489ad9f6d29d59f088
|
| 3 |
+
size 2175
|
custom_components/st-audiorec/st_audiorec/frontend/build/precache-manifest.30096e2fd9f149157a833e729e772f72.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e318206d88822468d480fd4047df1439dd2dadb500d846636314b39afabb8af4
|
| 3 |
+
size 564
|
custom_components/st-audiorec/st_audiorec/frontend/build/service-worker.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8243bfeb139d7acdf475a748daa48068cde6e5d62a4c2242326e3d6bbfbc6d78
|
| 3 |
+
size 1183
|
custom_components/st-audiorec/st_audiorec/frontend/build/static/js/2.ca2bba73.chunk.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8810a6d23c8292fa11953f5c2c762e6bd658f11316f12879d0a6d4e05f7df5a1
|
| 3 |
+
size 465885
|
custom_components/st-audiorec/st_audiorec/frontend/build/static/js/2.ca2bba73.chunk.js.LICENSE.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:83bbf722e5b20cfb2920ac1c234ffa5ccde3baa9d8d5a87b4cc90f81ef649a47
|
| 3 |
+
size 1653
|
custom_components/st-audiorec/st_audiorec/frontend/build/static/js/2.ca2bba73.chunk.js.map
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:25cecefabe1287205f5f99bfd33159566c8d97bc56dadba39d70fcaf160c7998
|
| 3 |
+
size 1634044
|
custom_components/st-audiorec/st_audiorec/frontend/build/static/js/main.85742990.chunk.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:28cf7e640dbe5e67fdde3e5e4eea1d9053901a0612be430efdd2968509feb279
|
| 3 |
+
size 13457
|
custom_components/st-audiorec/st_audiorec/frontend/build/static/js/main.85742990.chunk.js.map
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b9c4877643f7494e0fea5996bc57d8667c1258cb58311d1d530a957303ffd698
|
| 3 |
+
size 38454
|
custom_components/st-audiorec/st_audiorec/frontend/build/static/js/runtime-main.11ec9aca.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9d7973f912c527b00488df34a3789d515ddaa81aafb41c9e24a79faa86384a6d
|
| 3 |
+
size 1598
|
custom_components/st-audiorec/st_audiorec/frontend/build/static/js/runtime-main.11ec9aca.js.map
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f103d8abf2ee051ee5004a5cebac24b9120fd178ca04b1353b3c2fee903b2a99
|
| 3 |
+
size 8317
|
custom_components/st-audiorec/st_audiorec/frontend/build/styles.css
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cf6e39bcb150811879a65286bc7b5646ce91a4fe06bdc47279f709352d06b3ce
|
| 3 |
+
size 3005
|
custom_components/st-audiorec/st_audiorec/frontend/package.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b7d56e8585fd866ed7e923c7d236bc16572727379fa7c5210f864bbe415bb19d
|
| 3 |
+
size 1257
|
custom_components/st-audiorec/st_audiorec/frontend/public/bootstrap.min.css
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f396767523d7b7ce621d90aae93cbbd7a516275898efd19020be38aa5ae85d5c
|
| 3 |
+
size 206913
|
custom_components/st-audiorec/st_audiorec/frontend/public/index.html
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:62d507b41ba09c5eabbebe6b946091aac4dbdc42b8144610292a629d07b43114
|
| 3 |
+
size 819
|
custom_components/st-audiorec/st_audiorec/frontend/public/styles.css
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cf6e39bcb150811879a65286bc7b5646ce91a4fe06bdc47279f709352d06b3ce
|
| 3 |
+
size 3005
|
custom_components/st-audiorec/st_audiorec/frontend/src/StreamlitAudioRecorder.tsx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5d8e12966a0a2f84f79befd0a0a8af2e32e73d557402d703b9104562a0973914
|
| 3 |
+
size 22138
|
custom_components/st-audiorec/st_audiorec/frontend/src/index.tsx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:823a6cdb8619acf67b20f8652ad380d361e723214c145f4e68f28fb02276dc3e
|
| 3 |
+
size 236
|
custom_components/st-audiorec/st_audiorec/frontend/src/react-app-env.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dde16261952fc59aa0f2f4cd5364267a8a93b80499da193ac5e41997bb31e9c9
|
| 3 |
+
size 81
|
custom_components/st-audiorec/st_audiorec/frontend/tsconfig.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:74fb6cf888ac20a39e3f52b5bcce2b3ff0500c0090145f9b9df8367e12d4172c
|
| 3 |
+
size 539
|
database.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# database.py - نظام قاعدة البيانات البسيطة للملاحظات
|
| 2 |
+
|
| 3 |
+
import sqlite3
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
from typing import List, Dict, Optional
|
| 8 |
+
|
| 9 |
+
class NotesDatabase:
|
| 10 |
+
"""قاعدة بيانات بسيطة لحفظ الملاحظات والملخصات"""
|
| 11 |
+
|
| 12 |
+
def __init__(self, db_path: str = "lecture_notes.db"):
|
| 13 |
+
self.db_path = db_path
|
| 14 |
+
self.init_database()
|
| 15 |
+
|
| 16 |
+
def init_database(self):
|
| 17 |
+
"""إنشاء قاعدة البيانات والجداول"""
|
| 18 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 19 |
+
cursor = conn.cursor()
|
| 20 |
+
|
| 21 |
+
# جدول الملاحظات الرئيسي
|
| 22 |
+
cursor.execute('''
|
| 23 |
+
CREATE TABLE IF NOT EXISTS lecture_notes (
|
| 24 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 25 |
+
title TEXT NOT NULL,
|
| 26 |
+
original_text TEXT NOT NULL,
|
| 27 |
+
translated_text TEXT,
|
| 28 |
+
summary TEXT,
|
| 29 |
+
key_points TEXT,
|
| 30 |
+
subject TEXT,
|
| 31 |
+
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 32 |
+
date_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 33 |
+
audio_file_path TEXT,
|
| 34 |
+
language_detected TEXT,
|
| 35 |
+
target_language TEXT,
|
| 36 |
+
markers TEXT
|
| 37 |
+
)
|
| 38 |
+
''')
|
| 39 |
+
|
| 40 |
+
# جدول الملخصات السريعة
|
| 41 |
+
cursor.execute('''
|
| 42 |
+
CREATE TABLE IF NOT EXISTS quick_summaries (
|
| 43 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 44 |
+
note_id INTEGER,
|
| 45 |
+
summary_type TEXT,
|
| 46 |
+
content TEXT,
|
| 47 |
+
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 48 |
+
FOREIGN KEY (note_id) REFERENCES lecture_notes (id)
|
| 49 |
+
)
|
| 50 |
+
''')
|
| 51 |
+
|
| 52 |
+
conn.commit()
|
| 53 |
+
|
| 54 |
+
def save_lecture_note(self, data: Dict) -> int:
|
| 55 |
+
"""حفظ ملاحظة محاضرة جديدة"""
|
| 56 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 57 |
+
cursor = conn.cursor()
|
| 58 |
+
|
| 59 |
+
cursor.execute('''
|
| 60 |
+
INSERT INTO lecture_notes
|
| 61 |
+
(title, original_text, translated_text, summary, key_points,
|
| 62 |
+
subject, audio_file_path, language_detected, target_language, markers)
|
| 63 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 64 |
+
''', (
|
| 65 |
+
data.get('title', 'محاضرة جديدة'),
|
| 66 |
+
data.get('original_text', ''),
|
| 67 |
+
data.get('translated_text', ''),
|
| 68 |
+
data.get('summary', ''),
|
| 69 |
+
data.get('key_points', ''),
|
| 70 |
+
data.get('subject', ''),
|
| 71 |
+
data.get('audio_file_path', ''),
|
| 72 |
+
data.get('language_detected', ''),
|
| 73 |
+
data.get('target_language', ''),
|
| 74 |
+
json.dumps(data.get('markers', []))
|
| 75 |
+
))
|
| 76 |
+
|
| 77 |
+
note_id = cursor.lastrowid
|
| 78 |
+
conn.commit()
|
| 79 |
+
return note_id
|
| 80 |
+
|
| 81 |
+
def get_all_notes(self, limit: int = 50) -> List[Dict]:
|
| 82 |
+
"""استرجاع جميع الملاحظات"""
|
| 83 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 84 |
+
cursor = conn.cursor()
|
| 85 |
+
|
| 86 |
+
cursor.execute('''
|
| 87 |
+
SELECT * FROM lecture_notes
|
| 88 |
+
ORDER BY date_created DESC
|
| 89 |
+
LIMIT ?
|
| 90 |
+
''', (limit,))
|
| 91 |
+
|
| 92 |
+
columns = [description[0] for description in cursor.description]
|
| 93 |
+
notes = []
|
| 94 |
+
|
| 95 |
+
for row in cursor.fetchall():
|
| 96 |
+
note = dict(zip(columns, row))
|
| 97 |
+
# تحويل markers من JSON string إلى list
|
| 98 |
+
if note['markers']:
|
| 99 |
+
try:
|
| 100 |
+
note['markers'] = json.loads(note['markers'])
|
| 101 |
+
except:
|
| 102 |
+
note['markers'] = []
|
| 103 |
+
notes.append(note)
|
| 104 |
+
|
| 105 |
+
return notes
|
| 106 |
+
|
| 107 |
+
def get_note_by_id(self, note_id: int) -> Optional[Dict]:
|
| 108 |
+
"""استرجاع ملاحظة محددة بالـ ID"""
|
| 109 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 110 |
+
cursor = conn.cursor()
|
| 111 |
+
|
| 112 |
+
cursor.execute('SELECT * FROM lecture_notes WHERE id = ?', (note_id,))
|
| 113 |
+
row = cursor.fetchone()
|
| 114 |
+
|
| 115 |
+
if row:
|
| 116 |
+
columns = [description[0] for description in cursor.description]
|
| 117 |
+
note = dict(zip(columns, row))
|
| 118 |
+
if note['markers']:
|
| 119 |
+
try:
|
| 120 |
+
note['markers'] = json.loads(note['markers'])
|
| 121 |
+
except:
|
| 122 |
+
note['markers'] = []
|
| 123 |
+
return note
|
| 124 |
+
|
| 125 |
+
return None
|
| 126 |
+
|
| 127 |
+
def update_note(self, note_id: int, data: Dict) -> bool:
|
| 128 |
+
"""تحديث ملاحظة موجودة"""
|
| 129 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 130 |
+
cursor = conn.cursor()
|
| 131 |
+
|
| 132 |
+
# بناء query التحديث بناءً على البيانات الموجودة
|
| 133 |
+
update_fields = []
|
| 134 |
+
values = []
|
| 135 |
+
|
| 136 |
+
for field in ['title', 'summary', 'key_points', 'subject']:
|
| 137 |
+
if field in data:
|
| 138 |
+
update_fields.append(f"{field} = ?")
|
| 139 |
+
values.append(data[field])
|
| 140 |
+
|
| 141 |
+
if not update_fields:
|
| 142 |
+
return False
|
| 143 |
+
|
| 144 |
+
update_fields.append("date_modified = CURRENT_TIMESTAMP")
|
| 145 |
+
values.append(note_id)
|
| 146 |
+
|
| 147 |
+
query = f"UPDATE lecture_notes SET {', '.join(update_fields)} WHERE id = ?"
|
| 148 |
+
|
| 149 |
+
cursor.execute(query, values)
|
| 150 |
+
conn.commit()
|
| 151 |
+
|
| 152 |
+
return cursor.rowcount > 0
|
| 153 |
+
|
| 154 |
+
def delete_note(self, note_id: int) -> bool:
|
| 155 |
+
"""حذف ملاحظة"""
|
| 156 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 157 |
+
cursor = conn.cursor()
|
| 158 |
+
|
| 159 |
+
# حذف الملخصات المرتبطة أولاً
|
| 160 |
+
cursor.execute('DELETE FROM quick_summaries WHERE note_id = ?', (note_id,))
|
| 161 |
+
|
| 162 |
+
# ثم حذف الملاحظة
|
| 163 |
+
cursor.execute('DELETE FROM lecture_notes WHERE id = ?', (note_id,))
|
| 164 |
+
|
| 165 |
+
conn.commit()
|
| 166 |
+
return cursor.rowcount > 0
|
| 167 |
+
|
| 168 |
+
def search_notes(self, query: str) -> List[Dict]:
|
| 169 |
+
"""البحث في الملاحظات"""
|
| 170 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 171 |
+
cursor = conn.cursor()
|
| 172 |
+
|
| 173 |
+
search_query = f"%{query}%"
|
| 174 |
+
cursor.execute('''
|
| 175 |
+
SELECT * FROM lecture_notes
|
| 176 |
+
WHERE title LIKE ? OR original_text LIKE ?
|
| 177 |
+
OR translated_text LIKE ? OR summary LIKE ?
|
| 178 |
+
ORDER BY date_created DESC
|
| 179 |
+
''', (search_query, search_query, search_query, search_query))
|
| 180 |
+
|
| 181 |
+
columns = [description[0] for description in cursor.description]
|
| 182 |
+
notes = []
|
| 183 |
+
|
| 184 |
+
for row in cursor.fetchall():
|
| 185 |
+
note = dict(zip(columns, row))
|
| 186 |
+
if note['markers']:
|
| 187 |
+
try:
|
| 188 |
+
note['markers'] = json.loads(note['markers'])
|
| 189 |
+
except:
|
| 190 |
+
note['markers'] = []
|
| 191 |
+
notes.append(note)
|
| 192 |
+
|
| 193 |
+
return notes
|
| 194 |
+
|
| 195 |
+
def get_notes_by_subject(self, subject: str) -> List[Dict]:
|
| 196 |
+
"""استرجاع الملاحظات حسب المادة"""
|
| 197 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 198 |
+
cursor = conn.cursor()
|
| 199 |
+
|
| 200 |
+
cursor.execute('''
|
| 201 |
+
SELECT * FROM lecture_notes
|
| 202 |
+
WHERE subject = ?
|
| 203 |
+
ORDER BY date_created DESC
|
| 204 |
+
''', (subject,))
|
| 205 |
+
|
| 206 |
+
columns = [description[0] for description in cursor.description]
|
| 207 |
+
notes = []
|
| 208 |
+
|
| 209 |
+
for row in cursor.fetchall():
|
| 210 |
+
note = dict(zip(columns, row))
|
| 211 |
+
if note['markers']:
|
| 212 |
+
try:
|
| 213 |
+
note['markers'] = json.loads(note['markers'])
|
| 214 |
+
except:
|
| 215 |
+
note['markers'] = []
|
| 216 |
+
notes.append(note)
|
| 217 |
+
|
| 218 |
+
return notes
|
| 219 |
+
|
| 220 |
+
def get_subjects(self) -> List[str]:
|
| 221 |
+
"""استرجاع قائمة المواد الدراسية"""
|
| 222 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 223 |
+
cursor = conn.cursor()
|
| 224 |
+
|
| 225 |
+
cursor.execute('''
|
| 226 |
+
SELECT DISTINCT subject FROM lecture_notes
|
| 227 |
+
WHERE subject IS NOT NULL AND subject != ''
|
| 228 |
+
ORDER BY subject
|
| 229 |
+
''')
|
| 230 |
+
|
| 231 |
+
return [row[0] for row in cursor.fetchall()]
|
diagnose_summary.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
ملف تشخيص مشكلة زر التلخيص - معالجة شاملة
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import subprocess
|
| 8 |
+
import sys
|
| 9 |
+
import json
|
| 10 |
+
import requests
|
| 11 |
+
import time
|
| 12 |
+
import os
|
| 13 |
+
|
| 14 |
+
def check_server_process():
|
| 15 |
+
"""التحقق من عملية الخادم"""
|
| 16 |
+
print("🔍 البحث عن عملية الخادم...")
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
# البحث في العمليات الجارية
|
| 20 |
+
result = subprocess.run(
|
| 21 |
+
['tasklist', '/FI', 'IMAGENAME eq python.exe'],
|
| 22 |
+
capture_output=True, text=True, shell=True
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
python_processes = []
|
| 26 |
+
for line in result.stdout.split('\n'):
|
| 27 |
+
if 'python.exe' in line:
|
| 28 |
+
python_processes.append(line.strip())
|
| 29 |
+
|
| 30 |
+
print(f"عدد العمليات Python الجارية: {len(python_processes)}")
|
| 31 |
+
|
| 32 |
+
if python_processes:
|
| 33 |
+
print("✅ العمليات الموجودة:")
|
| 34 |
+
for proc in python_processes[:5]: # أول 5 فقط
|
| 35 |
+
print(f" {proc}")
|
| 36 |
+
|
| 37 |
+
return len(python_processes) > 0
|
| 38 |
+
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"❌ خطأ في فحص العمليات: {e}")
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
def check_port_status():
|
| 44 |
+
"""فحص حالة المنافذ"""
|
| 45 |
+
print("\n🌐 فحص حالة المنافذ...")
|
| 46 |
+
|
| 47 |
+
ports_to_check = [5001, 5054, 8501]
|
| 48 |
+
port_status = {}
|
| 49 |
+
|
| 50 |
+
for port in ports_to_check:
|
| 51 |
+
try:
|
| 52 |
+
result = subprocess.run(
|
| 53 |
+
['netstat', '-an'],
|
| 54 |
+
capture_output=True, text=True, shell=True
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
is_listening = f':{port}' in result.stdout and 'LISTENING' in result.stdout
|
| 58 |
+
port_status[port] = is_listening
|
| 59 |
+
|
| 60 |
+
status_emoji = "✅" if is_listening else "❌"
|
| 61 |
+
print(f" {status_emoji} Port {port}: {'LISTENING' if is_listening else 'NOT LISTENING'}")
|
| 62 |
+
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f" ❌ Port {port}: خطأ في الفحص - {e}")
|
| 65 |
+
port_status[port] = False
|
| 66 |
+
|
| 67 |
+
return port_status
|
| 68 |
+
|
| 69 |
+
def test_cors_issue():
|
| 70 |
+
"""اختبار مشكلة CORS"""
|
| 71 |
+
print("\n🔧 اختبار مشكلة CORS...")
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
# طلب OPTIONS
|
| 75 |
+
response = requests.options(
|
| 76 |
+
'http://localhost:5001/summarize',
|
| 77 |
+
timeout=5
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
print(f"Status Code: {response.status_code}")
|
| 81 |
+
|
| 82 |
+
# فحص CORS headers
|
| 83 |
+
headers = dict(response.headers)
|
| 84 |
+
cors_origin = headers.get('Access-Control-Allow-Origin', 'غير موجود')
|
| 85 |
+
|
| 86 |
+
print(f"CORS Origin: '{cors_origin}'")
|
| 87 |
+
|
| 88 |
+
# تحقق من المشكلة
|
| 89 |
+
if ',' in cors_origin:
|
| 90 |
+
print("❌ مشكلة CORS: القيمة تحتوي على فواصل متعددة!")
|
| 91 |
+
print(" هذا يسبب الخطأ: 'multiple values *, *'")
|
| 92 |
+
return False
|
| 93 |
+
elif cors_origin == '*':
|
| 94 |
+
print("✅ CORS header صحيح")
|
| 95 |
+
return True
|
| 96 |
+
else:
|
| 97 |
+
print(f"⚠️ CORS header غير متوقع: {cors_origin}")
|
| 98 |
+
return False
|
| 99 |
+
|
| 100 |
+
except requests.exceptions.ConnectionError:
|
| 101 |
+
print("❌ لا يمكن الاتصال بالخادم - الخادم غير مشتغل")
|
| 102 |
+
return False
|
| 103 |
+
except Exception as e:
|
| 104 |
+
print(f"❌ خطأ في اختبار CORS: {e}")
|
| 105 |
+
return False
|
| 106 |
+
|
| 107 |
+
def test_summarize_functionality():
|
| 108 |
+
"""اختبار وظيفة التلخيص"""
|
| 109 |
+
print("\n🤖 اختبار وظيفة التلخيص...")
|
| 110 |
+
|
| 111 |
+
test_data = {
|
| 112 |
+
"text": "Hello, how are you? What are you doing today? Tell me about your work.",
|
| 113 |
+
"language": "arabic",
|
| 114 |
+
"type": "full"
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
response = requests.post(
|
| 119 |
+
'http://localhost:5001/summarize',
|
| 120 |
+
json=test_data,
|
| 121 |
+
headers={'Content-Type': 'application/json'},
|
| 122 |
+
timeout=30
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
print(f"Status Code: {response.status_code}")
|
| 126 |
+
|
| 127 |
+
if response.status_code == 200:
|
| 128 |
+
data = response.json()
|
| 129 |
+
print(f"Response Keys: {list(data.keys())}")
|
| 130 |
+
|
| 131 |
+
if data.get('success'):
|
| 132 |
+
print("✅ التلخيص نجح!")
|
| 133 |
+
print(f"Summary Type: {data.get('type', 'غير محدد')}")
|
| 134 |
+
|
| 135 |
+
if 'summary' in data:
|
| 136 |
+
summary_preview = str(data['summary'])[:100] + "..."
|
| 137 |
+
print(f"Summary Preview: {summary_preview}")
|
| 138 |
+
return True
|
| 139 |
+
else:
|
| 140 |
+
print("⚠️ لا يوجد ملخص في الاستجابة")
|
| 141 |
+
return False
|
| 142 |
+
else:
|
| 143 |
+
print(f"❌ فشل التلخيص: {data.get('error', 'خطأ غير معروف')}")
|
| 144 |
+
return False
|
| 145 |
+
else:
|
| 146 |
+
error_text = response.text[:200]
|
| 147 |
+
print(f"❌ خطأ HTTP {response.status_code}: {error_text}")
|
| 148 |
+
return False
|
| 149 |
+
|
| 150 |
+
except requests.exceptions.Timeout:
|
| 151 |
+
print("❌ انتهت مهلة الطلب - الخادم بطيء أو لا يستجيب")
|
| 152 |
+
return False
|
| 153 |
+
except Exception as e:
|
| 154 |
+
print(f"❌ خطأ في اختبار التلخيص: {e}")
|
| 155 |
+
return False
|
| 156 |
+
|
| 157 |
+
def check_dependencies():
|
| 158 |
+
"""فحص المكتبات المطلوبة"""
|
| 159 |
+
print("\n📦 فحص المكتبات المطلوبة...")
|
| 160 |
+
|
| 161 |
+
required_packages = [
|
| 162 |
+
'flask', 'flask-cors', 'requests',
|
| 163 |
+
'google-generativeai', 'librosa', 'soundfile'
|
| 164 |
+
]
|
| 165 |
+
|
| 166 |
+
missing_packages = []
|
| 167 |
+
|
| 168 |
+
for package in required_packages:
|
| 169 |
+
try:
|
| 170 |
+
result = subprocess.run(
|
| 171 |
+
[sys.executable, '-c', f'import {package.replace("-", "_")}'],
|
| 172 |
+
capture_output=True, text=True
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
if result.returncode == 0:
|
| 176 |
+
print(f" ✅ {package}")
|
| 177 |
+
else:
|
| 178 |
+
print(f" ❌ {package} - غير مثبت")
|
| 179 |
+
missing_packages.append(package)
|
| 180 |
+
|
| 181 |
+
except Exception as e:
|
| 182 |
+
print(f" ❌ {package} - خطأ في الفحص: {e}")
|
| 183 |
+
missing_packages.append(package)
|
| 184 |
+
|
| 185 |
+
if missing_packages:
|
| 186 |
+
print(f"\n⚠️ المكتبات المفقودة: {', '.join(missing_packages)}")
|
| 187 |
+
print("تشغيل الأمر: pip install " + " ".join(missing_packages))
|
| 188 |
+
return False
|
| 189 |
+
else:
|
| 190 |
+
print("\n✅ جميع المكتبات مثبتة")
|
| 191 |
+
return True
|
| 192 |
+
|
| 193 |
+
def restart_server_suggestion():
|
| 194 |
+
"""اقتراحات لإعادة تشغيل الخادم"""
|
| 195 |
+
print("\n🔄 اقتراحات الإصلاح:")
|
| 196 |
+
print("1. إيقاف الخادم الحالي (Ctrl+C في التيرمينال)")
|
| 197 |
+
print("2. تشغيل الخادم مرة أخرى:")
|
| 198 |
+
print(" python recorder_server.py")
|
| 199 |
+
print("\n3. أو استخدام ملف البدء:")
|
| 200 |
+
print(" python start_debug.py")
|
| 201 |
+
print("\n4. التحقق من تشغيل الخادم:")
|
| 202 |
+
print(" curl http://localhost:5001/record")
|
| 203 |
+
|
| 204 |
+
def main():
|
| 205 |
+
"""الدالة الرئيسية للتشخيص"""
|
| 206 |
+
print("=" * 60)
|
| 207 |
+
print("🚀 تشخيص شامل لمشكلة زر التلخيص")
|
| 208 |
+
print("=" * 60)
|
| 209 |
+
|
| 210 |
+
# فحص العمليات
|
| 211 |
+
has_python_process = check_server_process()
|
| 212 |
+
|
| 213 |
+
# فحص المنافذ
|
| 214 |
+
port_status = check_port_status()
|
| 215 |
+
|
| 216 |
+
# فحص المكتبات
|
| 217 |
+
dependencies_ok = check_dependencies()
|
| 218 |
+
|
| 219 |
+
# إذا كان المنفذ مفتوح، اختبر CORS والتلخيص
|
| 220 |
+
if port_status.get(5001, False):
|
| 221 |
+
cors_ok = test_cors_issue()
|
| 222 |
+
if cors_ok:
|
| 223 |
+
summarize_ok = test_summarize_functionality()
|
| 224 |
+
else:
|
| 225 |
+
summarize_ok = False
|
| 226 |
+
print("\n❌ لا يمكن اختبار التلخيص بسبب مشكلة CORS")
|
| 227 |
+
else:
|
| 228 |
+
cors_ok = False
|
| 229 |
+
summarize_ok = False
|
| 230 |
+
print("\n❌ لا يمكن اختبار CORS/التلخيص - الخادم غير مشتغل")
|
| 231 |
+
|
| 232 |
+
# النتيجة النهائية
|
| 233 |
+
print("\n" + "=" * 60)
|
| 234 |
+
print("📊 ملخص التشخيص:")
|
| 235 |
+
print("=" * 60)
|
| 236 |
+
|
| 237 |
+
print(f" 📦 المكتبات: {'✅ موجودة' if dependencies_ok else '❌ مفقودة'}")
|
| 238 |
+
print(f" 🔧 عملية Python: {'✅ تعمل' if has_python_process else '❌ لا تعمل'}")
|
| 239 |
+
print(f" 🌐 المنفذ 5001: {'✅ مفتوح' if port_status.get(5001) else '❌ مغلق'}")
|
| 240 |
+
print(f" 🔧 CORS: {'✅ صحيح' if cors_ok else '❌ مشكلة'}")
|
| 241 |
+
print(f" 🤖 التلخيص: {'✅ يعمل' if summarize_ok else '❌ لا يعمل'}")
|
| 242 |
+
|
| 243 |
+
if summarize_ok:
|
| 244 |
+
print("\n🎉 جميع الاختبارات نجحت! المشكلة محلولة.")
|
| 245 |
+
elif not port_status.get(5001):
|
| 246 |
+
print("\n🔄 يجب تشغيل الخادم أولاً")
|
| 247 |
+
restart_server_suggestion()
|
| 248 |
+
elif not cors_ok:
|
| 249 |
+
print("\n🔄 مشكلة CORS - يجب إعادة تشغيل الخادم")
|
| 250 |
+
restart_server_suggestion()
|
| 251 |
+
else:
|
| 252 |
+
print("\n🔍 هناك مشكلة أخرى تحتاج لمزيد من التحقق")
|
| 253 |
+
|
| 254 |
+
print("=" * 60)
|
| 255 |
+
|
| 256 |
+
if __name__ == "__main__":
|
| 257 |
+
main()
|
fast_loading.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Fast Loading Configuration for Streamlit
|
| 3 |
+
تحسين سرعة تحميل Streamlit
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import streamlit as st
|
| 7 |
+
|
| 8 |
+
def apply_fast_loading_config():
|
| 9 |
+
"""Apply configurations for faster loading"""
|
| 10 |
+
|
| 11 |
+
# Custom CSS to prevent flash of unstyled content
|
| 12 |
+
st.markdown("""
|
| 13 |
+
<style>
|
| 14 |
+
/* Hide loading spinner faster */
|
| 15 |
+
.stSpinner {
|
| 16 |
+
display: none !important;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
/* Faster fade-in */
|
| 20 |
+
.main .block-container {
|
| 21 |
+
animation: fadeIn 0.1s ease-in-out;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
@keyframes fadeIn {
|
| 25 |
+
from { opacity: 0; }
|
| 26 |
+
to { opacity: 1; }
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
/* Optimize fonts loading */
|
| 30 |
+
body {
|
| 31 |
+
font-display: swap;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/* Remove unnecessary margins */
|
| 35 |
+
.block-container {
|
| 36 |
+
padding-top: 1rem;
|
| 37 |
+
}
|
| 38 |
+
</style>
|
| 39 |
+
""", unsafe_allow_html=True)
|
| 40 |
+
|
| 41 |
+
def show_instant_content():
|
| 42 |
+
"""Show content immediately without waiting"""
|
| 43 |
+
st.markdown("""
|
| 44 |
+
<div style="text-align: center; padding: 20px;">
|
| 45 |
+
<h1>🎵 SyncMaster</h1>
|
| 46 |
+
<p>منصة المزامنة الذكية بين الصوت والنص</p>
|
| 47 |
+
<div style="background: linear-gradient(45deg, #1f77b4, #17becf);
|
| 48 |
+
color: white; padding: 10px; border-radius: 5px; margin: 10px;">
|
| 49 |
+
✅ التطبيق جاهز للاستخدام
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
""", unsafe_allow_html=True)
|
main.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simple launcher for SyncMaster with integrated server
|
| 4 |
+
مُشغل بسيط مع خادم متكامل - مثالي لـ HuggingFace
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import streamlit as st
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import time
|
| 11 |
+
|
| 12 |
+
# Add current directory to path
|
| 13 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 14 |
+
|
| 15 |
+
# Initialize integrated server first
|
| 16 |
+
recorder_server_started = False
|
| 17 |
+
|
| 18 |
+
def start_integrated_server():
|
| 19 |
+
"""Start the integrated recorder server"""
|
| 20 |
+
global recorder_server_started
|
| 21 |
+
|
| 22 |
+
if recorder_server_started:
|
| 23 |
+
return True
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
from integrated_server import ensure_recorder_server
|
| 27 |
+
result = ensure_recorder_server()
|
| 28 |
+
if result:
|
| 29 |
+
recorder_server_started = True
|
| 30 |
+
st.success("✅ Integrated recorder server is running on port 5001")
|
| 31 |
+
else:
|
| 32 |
+
st.warning("⚠️ Could not start integrated recorder server")
|
| 33 |
+
return result
|
| 34 |
+
except Exception as e:
|
| 35 |
+
st.error(f"❌ Error starting integrated recorder server: {e}")
|
| 36 |
+
return False
|
| 37 |
+
|
| 38 |
+
# Start the integrated server when the module loads
|
| 39 |
+
start_integrated_server()
|
| 40 |
+
|
| 41 |
+
# Import the main app module
|
| 42 |
+
try:
|
| 43 |
+
import app
|
| 44 |
+
except Exception as e:
|
| 45 |
+
st.error(f"❌ Error loading main application: {e}")
|
| 46 |
+
st.stop()
|
| 47 |
+
|
| 48 |
+
if __name__ == "__main__":
|
| 49 |
+
print("🚀 SyncMaster main.py executed directly")
|
mp3_embedder.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from mutagen.mp3 import MP3
|
| 2 |
+
from mutagen.id3 import ID3, SYLT, USLT, Encoding
|
| 3 |
+
import os
|
| 4 |
+
import tempfile
|
| 5 |
+
import shutil
|
| 6 |
+
import subprocess
|
| 7 |
+
from typing import List, Dict, Tuple
|
| 8 |
+
|
| 9 |
+
# --- Helper function to check for ffmpeg ---
|
| 10 |
+
def is_ffmpeg_available():
|
| 11 |
+
"""Check if ffmpeg is installed and accessible in the system's PATH."""
|
| 12 |
+
return shutil.which("ffmpeg") is not None
|
| 13 |
+
|
| 14 |
+
class MP3Embedder:
|
| 15 |
+
"""Handles embedding SYLT synchronized lyrics into MP3 files with robust error handling."""
|
| 16 |
+
|
| 17 |
+
def __init__(self):
|
| 18 |
+
"""Initialize the MP3 embedder."""
|
| 19 |
+
self.temp_dir = "/tmp/audio_sync"
|
| 20 |
+
os.makedirs(self.temp_dir, exist_ok=True)
|
| 21 |
+
|
| 22 |
+
self.ffmpeg_available = is_ffmpeg_available()
|
| 23 |
+
|
| 24 |
+
def embed_sylt_lyrics(self, audio_path: str, word_timestamps: List[Dict],
|
| 25 |
+
text: str, output_filename: str) -> Tuple[str, List[str]]:
|
| 26 |
+
"""
|
| 27 |
+
Embeds SYLT synchronized lyrics into an MP3 file and returns logs.
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
A tuple containing:
|
| 31 |
+
- The path to the output MP3 file.
|
| 32 |
+
- A list of log messages detailing the process.
|
| 33 |
+
"""
|
| 34 |
+
log_messages = []
|
| 35 |
+
def log_and_print(message):
|
| 36 |
+
log_messages.append(message)
|
| 37 |
+
print(f"MP3_EMBEDDER: {message}")
|
| 38 |
+
log_and_print(f"--- MP3Embedder initialized. ffmpeg available: {self.ffmpeg_available} ---")
|
| 39 |
+
log_and_print(f"--- Starting SYLT embedding for: {os.path.basename(audio_path)} ---")
|
| 40 |
+
output_path = os.path.join(self.temp_dir, output_filename)
|
| 41 |
+
try:
|
| 42 |
+
# --- Step 1: Ensure the file is in MP3 format ---
|
| 43 |
+
if not audio_path.lower().endswith('.mp3'):
|
| 44 |
+
if self.ffmpeg_available:
|
| 45 |
+
log_and_print(f"'{os.path.basename(audio_path)}' is not an MP3. Converting with ffmpeg...")
|
| 46 |
+
try:
|
| 47 |
+
subprocess.run(
|
| 48 |
+
['ffmpeg', '-i', audio_path, '-codec:a', 'libmp3lame', '-q:a', '2', output_path],
|
| 49 |
+
check=True, capture_output=True, text=True
|
| 50 |
+
)
|
| 51 |
+
log_and_print("--- ffmpeg conversion successful. ---")
|
| 52 |
+
except subprocess.CalledProcessError as e:
|
| 53 |
+
log_and_print("--- ERROR: ffmpeg conversion failed. ---")
|
| 54 |
+
log_and_print(f"--- ffmpeg stderr: {e.stderr} ---")
|
| 55 |
+
log_and_print("--- Fallback: Copying original file without conversion. ---")
|
| 56 |
+
shutil.copy2(audio_path, output_path)
|
| 57 |
+
else:
|
| 58 |
+
log_and_print("--- WARNING: ffmpeg is not available. Cannot convert non-MP3 file. Copying directly. ---")
|
| 59 |
+
shutil.copy2(audio_path, output_path)
|
| 60 |
+
else:
|
| 61 |
+
log_and_print("--- Audio is already MP3. Copying to temporary location. ---")
|
| 62 |
+
shutil.copy2(audio_path, output_path)
|
| 63 |
+
|
| 64 |
+
# --- Step 2: Create SYLT data ---
|
| 65 |
+
log_and_print("--- Creating SYLT data from timestamps... ---")
|
| 66 |
+
sylt_data = self._create_sylt_data(word_timestamps)
|
| 67 |
+
if not sylt_data:
|
| 68 |
+
log_and_print("--- WARNING: No SYLT data could be created. Skipping embedding. ---")
|
| 69 |
+
return output_path, log_messages
|
| 70 |
+
|
| 71 |
+
log_and_print(f"--- Created {len(sylt_data)} SYLT entries. ---")
|
| 72 |
+
|
| 73 |
+
# --- Step 3: Embed data into the MP3 file ---
|
| 74 |
+
try:
|
| 75 |
+
log_and_print("--- Loading MP3 file with mutagen... ---")
|
| 76 |
+
audio_file = MP3(output_path, ID3=ID3)
|
| 77 |
+
|
| 78 |
+
if audio_file.tags is None:
|
| 79 |
+
log_and_print("--- No ID3 tags found. Creating new ones. ---")
|
| 80 |
+
audio_file.add_tags()
|
| 81 |
+
|
| 82 |
+
# --- Embed SYLT (Synchronized Lyrics) ---
|
| 83 |
+
log_and_print("--- Creating and adding SYLT frame... ---")
|
| 84 |
+
sylt_frame = SYLT(
|
| 85 |
+
encoding=Encoding.UTF8,
|
| 86 |
+
lang='eng',
|
| 87 |
+
format=2,
|
| 88 |
+
type=1,
|
| 89 |
+
text=sylt_data
|
| 90 |
+
)
|
| 91 |
+
audio_file.tags.delall('SYLT')
|
| 92 |
+
audio_file.tags.add(sylt_frame)
|
| 93 |
+
|
| 94 |
+
# --- Embed USLT (Unsynchronized Lyrics) as a fallback ---
|
| 95 |
+
log_and_print("--- Creating and adding USLT frame... ---")
|
| 96 |
+
uslt_frame = USLT(
|
| 97 |
+
encoding=Encoding.UTF8,
|
| 98 |
+
lang='eng',
|
| 99 |
+
desc='',
|
| 100 |
+
text=text
|
| 101 |
+
)
|
| 102 |
+
audio_file.tags.delall('USLT')
|
| 103 |
+
audio_file.tags.add(uslt_frame)
|
| 104 |
+
|
| 105 |
+
audio_file.save()
|
| 106 |
+
log_and_print("--- Successfully embedded SYLT and USLT frames. ---")
|
| 107 |
+
|
| 108 |
+
except Exception as e:
|
| 109 |
+
log_and_print(f"--- ERROR: Failed to embed SYLT/USLT: {e} ---")
|
| 110 |
+
return output_path, log_messages
|
| 111 |
+
|
| 112 |
+
except Exception as e:
|
| 113 |
+
log_and_print(f"--- ERROR: Unexpected error in embed_sylt_lyrics: {e} ---")
|
| 114 |
+
return output_path, log_messages
|
| 115 |
+
|
| 116 |
+
def _create_sylt_data(self, word_timestamps: List[Dict]) -> List[tuple]:
|
| 117 |
+
"""
|
| 118 |
+
Create SYLT data format from word timestamps
|
| 119 |
+
|
| 120 |
+
Args:
|
| 121 |
+
word_timestamps: List of word timestamp dictionaries
|
| 122 |
+
|
| 123 |
+
Returns:
|
| 124 |
+
List of tuples (text, timestamp_in_milliseconds)
|
| 125 |
+
"""
|
| 126 |
+
# Debug print to check incoming data
|
| 127 |
+
print(f"DEBUG: word_timestamps received in _create_sylt_data: {word_timestamps}")
|
| 128 |
+
try:
|
| 129 |
+
sylt_data = []
|
| 130 |
+
|
| 131 |
+
for word_data in word_timestamps:
|
| 132 |
+
word = word_data.get('word', '').strip()
|
| 133 |
+
start_time = word_data.get('start', 0)
|
| 134 |
+
|
| 135 |
+
if word:
|
| 136 |
+
# Convert seconds to milliseconds
|
| 137 |
+
timestamp_ms = int(start_time * 1000)
|
| 138 |
+
sylt_data.append((word, timestamp_ms))
|
| 139 |
+
|
| 140 |
+
return sylt_data
|
| 141 |
+
|
| 142 |
+
except Exception as e:
|
| 143 |
+
print(f"Error creating SYLT data: {str(e)}")
|
| 144 |
+
return []
|
| 145 |
+
|
| 146 |
+
def _create_line_based_sylt_data(self, word_timestamps: List[Dict], max_words_per_line: int = 6) -> List[tuple]:
|
| 147 |
+
"""
|
| 148 |
+
Create line-based SYLT data (alternative approach)
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
word_timestamps: List of word timestamp dictionaries
|
| 152 |
+
max_words_per_line: Maximum words per line
|
| 153 |
+
|
| 154 |
+
Returns:
|
| 155 |
+
List of tuples (line_text, timestamp_in_milliseconds)
|
| 156 |
+
"""
|
| 157 |
+
try:
|
| 158 |
+
sylt_data = []
|
| 159 |
+
current_line = []
|
| 160 |
+
|
| 161 |
+
for word_data in word_timestamps:
|
| 162 |
+
current_line.append(word_data)
|
| 163 |
+
|
| 164 |
+
# Check if we should end this line
|
| 165 |
+
if len(current_line) >= max_words_per_line:
|
| 166 |
+
if current_line:
|
| 167 |
+
line_text = ' '.join([w.get('word', '') for w in current_line]).strip()
|
| 168 |
+
start_time = current_line[0].get('start', 0)
|
| 169 |
+
timestamp_ms = int(start_time * 1000)
|
| 170 |
+
|
| 171 |
+
if line_text:
|
| 172 |
+
sylt_data.append((line_text, timestamp_ms))
|
| 173 |
+
|
| 174 |
+
current_line = []
|
| 175 |
+
|
| 176 |
+
# Add remaining words as final line
|
| 177 |
+
if current_line:
|
| 178 |
+
line_text = ' '.join([w.get('word', '') for w in current_line]).strip()
|
| 179 |
+
start_time = current_line[0].get('start', 0)
|
| 180 |
+
timestamp_ms = int(start_time * 1000)
|
| 181 |
+
|
| 182 |
+
if line_text:
|
| 183 |
+
sylt_data.append((line_text, timestamp_ms))
|
| 184 |
+
|
| 185 |
+
return sylt_data
|
| 186 |
+
|
| 187 |
+
except Exception as e:
|
| 188 |
+
print(f"Error creating line-based SYLT data: {str(e)}")
|
| 189 |
+
return []
|
| 190 |
+
|
| 191 |
+
def verify_sylt_embedding(self, mp3_path: str) -> Dict:
|
| 192 |
+
"""
|
| 193 |
+
Verify that SYLT lyrics are properly embedded
|
| 194 |
+
|
| 195 |
+
Args:
|
| 196 |
+
mp3_path: Path to the MP3 file
|
| 197 |
+
|
| 198 |
+
Returns:
|
| 199 |
+
Dictionary with verification results
|
| 200 |
+
"""
|
| 201 |
+
try:
|
| 202 |
+
audio_file = MP3(mp3_path)
|
| 203 |
+
|
| 204 |
+
result = {
|
| 205 |
+
'has_sylt': False,
|
| 206 |
+
'has_uslt': False,
|
| 207 |
+
'sylt_entries': 0,
|
| 208 |
+
'error': None
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
if audio_file.tags:
|
| 212 |
+
# Check for SYLT
|
| 213 |
+
sylt_frames = audio_file.tags.getall('SYLT')
|
| 214 |
+
if sylt_frames:
|
| 215 |
+
result['has_sylt'] = True
|
| 216 |
+
result['sylt_entries'] = len(sylt_frames[0].text) if sylt_frames[0].text else 0
|
| 217 |
+
|
| 218 |
+
# Check for USLT (fallback)
|
| 219 |
+
uslt_frames = audio_file.tags.getall('USLT')
|
| 220 |
+
if uslt_frames:
|
| 221 |
+
result['has_uslt'] = True
|
| 222 |
+
|
| 223 |
+
return result
|
| 224 |
+
|
| 225 |
+
except Exception as e:
|
| 226 |
+
return {
|
| 227 |
+
'has_sylt': False,
|
| 228 |
+
'has_uslt': False,
|
| 229 |
+
'sylt_entries': 0,
|
| 230 |
+
'error': str(e)
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
def extract_sylt_lyrics(self, mp3_path: str) -> List[Dict]:
|
| 234 |
+
"""
|
| 235 |
+
Extract SYLT lyrics from an MP3 file (for debugging)
|
| 236 |
+
|
| 237 |
+
Args:
|
| 238 |
+
mp3_path: Path to the MP3 file
|
| 239 |
+
|
| 240 |
+
Returns:
|
| 241 |
+
List of dictionaries with text and timestamp
|
| 242 |
+
"""
|
| 243 |
+
try:
|
| 244 |
+
audio_file = MP3(mp3_path)
|
| 245 |
+
lyrics_data = []
|
| 246 |
+
|
| 247 |
+
if audio_file.tags:
|
| 248 |
+
sylt_frames = audio_file.tags.getall('SYLT')
|
| 249 |
+
|
| 250 |
+
for frame in sylt_frames:
|
| 251 |
+
if frame.text:
|
| 252 |
+
for text, timestamp_ms in frame.text:
|
| 253 |
+
lyrics_data.append({
|
| 254 |
+
'text': text,
|
| 255 |
+
'timestamp': timestamp_ms / 1000.0 # Convert to seconds
|
| 256 |
+
})
|
| 257 |
+
|
| 258 |
+
return lyrics_data
|
| 259 |
+
|
| 260 |
+
except Exception as e:
|
| 261 |
+
print(f"Error extracting SYLT lyrics: {str(e)}")
|
| 262 |
+
return []
|
| 263 |
+
|
| 264 |
+
def create_lrc_file(self, word_timestamps: List[Dict], output_path: str) -> str:
|
| 265 |
+
"""
|
| 266 |
+
Create an LRC (lyrics) file as an additional export option
|
| 267 |
+
|
| 268 |
+
Args:
|
| 269 |
+
word_timestamps: List of word timestamp dictionaries
|
| 270 |
+
output_path: Path for the output LRC file
|
| 271 |
+
|
| 272 |
+
Returns:
|
| 273 |
+
Path to the created LRC file
|
| 274 |
+
"""
|
| 275 |
+
try:
|
| 276 |
+
lrc_lines = []
|
| 277 |
+
|
| 278 |
+
# Group words into lines
|
| 279 |
+
current_line = []
|
| 280 |
+
for word_data in word_timestamps:
|
| 281 |
+
current_line.append(word_data)
|
| 282 |
+
|
| 283 |
+
if len(current_line) >= 8: # 8 words per line
|
| 284 |
+
if current_line:
|
| 285 |
+
line_text = ' '.join([w.get('word', '') for w in current_line])
|
| 286 |
+
start_time = current_line[0].get('start', 0)
|
| 287 |
+
|
| 288 |
+
# Format timestamp as [mm:ss.xx]
|
| 289 |
+
minutes = int(start_time // 60)
|
| 290 |
+
seconds = start_time % 60
|
| 291 |
+
timestamp_str = f"[{minutes:02d}:{seconds:05.2f}]"
|
| 292 |
+
|
| 293 |
+
lrc_lines.append(f"{timestamp_str}{line_text}")
|
| 294 |
+
current_line = []
|
| 295 |
+
|
| 296 |
+
# Add remaining words
|
| 297 |
+
if current_line:
|
| 298 |
+
line_text = ' '.join([w.get('word', '') for w in current_line])
|
| 299 |
+
start_time = current_line[0].get('start', 0)
|
| 300 |
+
|
| 301 |
+
minutes = int(start_time // 60)
|
| 302 |
+
seconds = start_time % 60
|
| 303 |
+
timestamp_str = f"[{minutes:02d}:{seconds:05.2f}]"
|
| 304 |
+
|
| 305 |
+
lrc_lines.append(f"{timestamp_str}{line_text}")
|
| 306 |
+
|
| 307 |
+
# Write LRC file
|
| 308 |
+
with open(output_path, 'w', encoding='utf-8') as f:
|
| 309 |
+
f.write('\n'.join(lrc_lines))
|
| 310 |
+
|
| 311 |
+
return output_path
|
| 312 |
+
|
| 313 |
+
except Exception as e:
|
| 314 |
+
raise Exception(f"Error creating LRC file: {str(e)}")
|
| 315 |
+
|
| 316 |
+
def __del__(self):
|
| 317 |
+
"""Clean up temporary files"""
|
| 318 |
+
import shutil
|
| 319 |
+
if hasattr(self, 'temp_dir') and os.path.exists(self.temp_dir):
|
| 320 |
+
try:
|
| 321 |
+
shutil.rmtree(self.temp_dir)
|
| 322 |
+
except:
|
| 323 |
+
pass
|