Spaces:
Sleeping
Sleeping
Commit ยท
a4fc4ec
0
Parent(s):
Re-upload correct version of SyncMaster2
Browse filesThis view is limited to 50 files because it contains too many changes. ย See raw diff
- .gitattributes +35 -0
- .gitignore +12 -0
- .streamlit/config.toml +7 -0
- Dockerfile +34 -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 +528 -0
- app_config.py +59 -0
- app_launcher.py +43 -0
- audio_processor.py +334 -0
- comprehensive_test.py +225 -0
- database.py +231 -0
- diagnose_summary.py +257 -0
- fast_loading.py +52 -0
- integrated_server.py +235 -0
- lecture_notes.db +0 -0
- main.py +49 -0
- mp3_embedder.py +323 -0
- package-lock.json +12 -0
- package.json +19 -0
- packages.txt +5 -0
- performance_test.py +59 -0
- pyproject.toml +15 -0
- recorder_server.py +452 -0
- requirements.txt +15 -0
- setup_enhanced.py +196 -0
- start_debug.py +209 -0
- start_enhanced.bat +57 -0
- startup.py +218 -0
- summarizer.py +308 -0
- templates/recorder.html +1958 -0
- test_recording.py +134 -0
- test_server_translation.py +78 -0
- test_servers.py +17 -0
- test_summarize.py +117 -0
- test_summary_button.py +113 -0
- test_system.py +241 -0
- test_translation.py +39 -0
- tmp1dop8abr.json +0 -0
- tmp1g8szy_i.json +0 -0
- tmp43q9lzdr.json +0 -0
- tmp52qryepl.json +0 -0
- tmp6i84ktkz.json +0 -0
.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ignore environment files
|
| 2 |
+
.env
|
| 3 |
+
|
| 4 |
+
# Python
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.py[cod]
|
| 7 |
+
|
| 8 |
+
# Virtual environments
|
| 9 |
+
.venv/
|
| 10 |
+
|
| 11 |
+
# Other
|
| 12 |
+
.DS_Store
|
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[server]
|
| 2 |
+
headless = true
|
| 3 |
+
address = "0.0.0.0"
|
| 4 |
+
port = 5000
|
| 5 |
+
|
| 6 |
+
[theme]
|
| 7 |
+
base = "light"
|
Dockerfile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
# Expose the port that Streamlit runs on
|
| 28 |
+
EXPOSE 8501
|
| 29 |
+
|
| 30 |
+
# Add a health check
|
| 31 |
+
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 32 |
+
|
| 33 |
+
# Command to run the app
|
| 34 |
+
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 and translation for international students
|
| 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,528 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py - Final, Corrected, and Heavily Logged Version
|
| 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 |
+
|
| 12 |
+
# --- Integrated Server Import ---
|
| 13 |
+
import threading
|
| 14 |
+
|
| 15 |
+
# Global variable to track server status
|
| 16 |
+
recorder_server_status = {"started": False, "error": None}
|
| 17 |
+
|
| 18 |
+
def start_recorder_async():
|
| 19 |
+
"""Start recorder server in background thread"""
|
| 20 |
+
try:
|
| 21 |
+
from integrated_server import ensure_recorder_server, stop_all_servers
|
| 22 |
+
result = ensure_recorder_server()
|
| 23 |
+
recorder_server_status["started"] = result
|
| 24 |
+
# Make stop_all_servers available globally
|
| 25 |
+
globals()['stop_all_servers'] = stop_all_servers
|
| 26 |
+
except Exception as e:
|
| 27 |
+
recorder_server_status["error"] = str(e)
|
| 28 |
+
print(f"Warning: Could not start integrated recorder server: {e}")
|
| 29 |
+
|
| 30 |
+
# Start recorder server in background to avoid blocking UI
|
| 31 |
+
recorder_thread = threading.Thread(target=start_recorder_async, daemon=True)
|
| 32 |
+
recorder_thread.start()
|
| 33 |
+
|
| 34 |
+
# --- Critical Imports and Initial Checks ---
|
| 35 |
+
AUDIO_PROCESSOR_CLASS = None
|
| 36 |
+
IMPORT_ERROR_TRACEBACK = None
|
| 37 |
+
try:
|
| 38 |
+
from audio_processor import AudioProcessor
|
| 39 |
+
AUDIO_PROCESSOR_CLASS = AudioProcessor
|
| 40 |
+
except Exception:
|
| 41 |
+
IMPORT_ERROR_TRACEBACK = traceback.format_exc()
|
| 42 |
+
|
| 43 |
+
from video_generator import VideoGenerator
|
| 44 |
+
from mp3_embedder import MP3Embedder
|
| 45 |
+
from utils import format_timestamp, validate_audio_file, get_audio_info
|
| 46 |
+
from translator import get_translator, get_translation, UI_TRANSLATIONS
|
| 47 |
+
|
| 48 |
+
# --- Page Configuration ---
|
| 49 |
+
st.set_page_config(
|
| 50 |
+
page_title="SyncMaster - AI Audio-Text Synchronization",
|
| 51 |
+
page_icon="๐ต",
|
| 52 |
+
layout="wide"
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
# --- Browser Console Logging Utility ---
|
| 56 |
+
def log_to_browser_console(messages):
|
| 57 |
+
"""Injects JavaScript to log messages to the browser's console."""
|
| 58 |
+
if isinstance(messages, str):
|
| 59 |
+
messages = [messages]
|
| 60 |
+
escaped_messages = [json.dumps(str(msg)) for msg in messages]
|
| 61 |
+
js_code = f"""
|
| 62 |
+
<script>
|
| 63 |
+
(function() {{
|
| 64 |
+
const logs = [{', '.join(escaped_messages)}];
|
| 65 |
+
console.group("Backend Logs from SyncMaster");
|
| 66 |
+
logs.forEach(log => {{
|
| 67 |
+
const content = String(log);
|
| 68 |
+
if (content.includes('--- ERROR') || content.includes('--- FATAL')) {{
|
| 69 |
+
console.error(log);
|
| 70 |
+
}} else if (content.includes('--- WARNING')) {{
|
| 71 |
+
console.warn(log);
|
| 72 |
+
}} else if (content.includes('--- DEBUG')) {{
|
| 73 |
+
console.debug(log);
|
| 74 |
+
}} else {{
|
| 75 |
+
console.log(log);
|
| 76 |
+
}}
|
| 77 |
+
}});
|
| 78 |
+
console.groupEnd();
|
| 79 |
+
}})();
|
| 80 |
+
</script>
|
| 81 |
+
"""
|
| 82 |
+
components.html(js_code, height=0, scrolling=False)
|
| 83 |
+
|
| 84 |
+
# --- Session State Initialization ---
|
| 85 |
+
def initialize_session_state():
|
| 86 |
+
"""Initializes the session state variables if they don't exist."""
|
| 87 |
+
if 'step' not in st.session_state:
|
| 88 |
+
st.session_state.step = 1
|
| 89 |
+
if 'audio_file' not in st.session_state:
|
| 90 |
+
st.session_state.audio_file = None
|
| 91 |
+
if 'language' not in st.session_state:
|
| 92 |
+
st.session_state.language = 'en'
|
| 93 |
+
if 'enable_translation' not in st.session_state:
|
| 94 |
+
st.session_state.enable_translation = True
|
| 95 |
+
if 'target_language' not in st.session_state:
|
| 96 |
+
st.session_state.target_language = 'ar'
|
| 97 |
+
if 'transcription_data' not in st.session_state:
|
| 98 |
+
st.session_state.transcription_data = None
|
| 99 |
+
if 'edited_text' not in st.session_state:
|
| 100 |
+
st.session_state.edited_text = ""
|
| 101 |
+
if 'video_style' not in st.session_state:
|
| 102 |
+
st.session_state.video_style = {
|
| 103 |
+
'animation_style': 'Karaoke Style', 'text_color': '#FFFFFF',
|
| 104 |
+
'highlight_color': '#FFD700', 'background_color': '#000000',
|
| 105 |
+
'font_family': 'Arial', 'font_size': 48
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
# --- Main Application Logic ---
|
| 109 |
+
def main():
|
| 110 |
+
initialize_session_state()
|
| 111 |
+
|
| 112 |
+
# Apply fast loading optimizations
|
| 113 |
+
st.markdown("""
|
| 114 |
+
<style>
|
| 115 |
+
.stSpinner { display: none !important; }
|
| 116 |
+
.main .block-container { animation: fadeIn 0.2s ease-in-out; }
|
| 117 |
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
| 118 |
+
.block-container { padding-top: 1rem; }
|
| 119 |
+
</style>
|
| 120 |
+
""", unsafe_allow_html=True)
|
| 121 |
+
|
| 122 |
+
# Check recorder server status (non-blocking)
|
| 123 |
+
if recorder_server_status.get("error"):
|
| 124 |
+
st.warning(f"โ ๏ธ Recorder server issue: {recorder_server_status['error']}")
|
| 125 |
+
|
| 126 |
+
# Language Selection in sidebar
|
| 127 |
+
with st.sidebar:
|
| 128 |
+
st.markdown("## ๐ Language Settings")
|
| 129 |
+
language_options = {'English': 'en', 'ุงูุนุฑุจูุฉ': 'ar'}
|
| 130 |
+
selected_lang_display = st.selectbox(
|
| 131 |
+
"Interface Language",
|
| 132 |
+
options=list(language_options.keys()),
|
| 133 |
+
index=0 if st.session_state.language == 'en' else 1
|
| 134 |
+
)
|
| 135 |
+
st.session_state.language = language_options[selected_lang_display]
|
| 136 |
+
|
| 137 |
+
# Translation Settings
|
| 138 |
+
st.markdown("## ๐ค Translation Settings")
|
| 139 |
+
st.session_state.enable_translation = st.checkbox(
|
| 140 |
+
"Enable AI Translation" if st.session_state.language == 'en' else "ุชูุนูู ุงูุชุฑุฌู
ุฉ ุจุงูุฐูุงุก ุงูุงุตุทูุงุนู",
|
| 141 |
+
value=st.session_state.enable_translation,
|
| 142 |
+
help="Automatically translate transcribed text" if st.session_state.language == 'en' else "ุชุฑุฌู
ุฉ ุงููุต ุชููุงุฆูุงู"
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
if st.session_state.enable_translation:
|
| 146 |
+
target_lang_options = {
|
| 147 |
+
'Arabic (ุงูุนุฑุจูุฉ)': 'ar',
|
| 148 |
+
'English': 'en',
|
| 149 |
+
'French (Franรงais)': 'fr',
|
| 150 |
+
'Spanish (Espaรฑol)': 'es'
|
| 151 |
+
}
|
| 152 |
+
selected_target = st.selectbox(
|
| 153 |
+
"Target Language" if st.session_state.language == 'en' else "ุงููุบุฉ ุงูู
ุณุชูุฏูุฉ",
|
| 154 |
+
options=list(target_lang_options.keys()),
|
| 155 |
+
index=0
|
| 156 |
+
)
|
| 157 |
+
st.session_state.target_language = target_lang_options[selected_target]
|
| 158 |
+
|
| 159 |
+
# Get translations for current language
|
| 160 |
+
current_translations = UI_TRANSLATIONS.get(st.session_state.language, UI_TRANSLATIONS['en'])
|
| 161 |
+
|
| 162 |
+
# Main title with translation support
|
| 163 |
+
st.title("๐ต SyncMaster")
|
| 164 |
+
if st.session_state.language == 'ar':
|
| 165 |
+
st.markdown("### ู
ูุตุฉ ุงูู
ุฒุงู
ูุฉ ุงูุฐููุฉ ุจูู ุงูุตูุช ูุงููุต")
|
| 166 |
+
st.markdown("ุญูู ู
ููุงุชู ุงูุตูุชูุฉ ุฅูู ู
ููุงุช MP3 ู
ุชูุงููุฉ ู
ุน ุงูุฃุฌูุฒุฉ ุงูู
ุญู
ููุฉ ู
ุน ููู
ุงุช ู
ุชุฒุงู
ูุฉ ูู
ูุงุทุน ููุฏูู MP4 ู
ุชุญุฑูุฉ.")
|
| 167 |
+
else:
|
| 168 |
+
st.markdown("### The Intelligent Audio-Text Synchronization Platform")
|
| 169 |
+
st.markdown("Transform your audio files into mobile-compatible MP3s with synchronized lyrics and animated MP4 videos.")
|
| 170 |
+
|
| 171 |
+
col1, col2, col3 = st.columns(3)
|
| 172 |
+
|
| 173 |
+
with col1:
|
| 174 |
+
if st.session_state.step >= 1:
|
| 175 |
+
st.success("Step 1: Upload & Process")
|
| 176 |
+
else:
|
| 177 |
+
st.info("Step 1: Upload & Process")
|
| 178 |
+
with col2:
|
| 179 |
+
if st.session_state.step >= 2:
|
| 180 |
+
st.success("Step 2: Review & Customize")
|
| 181 |
+
else:
|
| 182 |
+
st.info("Step 2: Review & Customize")
|
| 183 |
+
with col3:
|
| 184 |
+
if st.session_state.step >= 3:
|
| 185 |
+
st.success("Step 3: Export")
|
| 186 |
+
else:
|
| 187 |
+
st.info("Step 3: Export")
|
| 188 |
+
|
| 189 |
+
st.divider()
|
| 190 |
+
|
| 191 |
+
if AUDIO_PROCESSOR_CLASS is None:
|
| 192 |
+
st.error("Fatal Error: The application could not start correctly.")
|
| 193 |
+
st.subheader("An error occurred while trying to import `AudioProcessor`:")
|
| 194 |
+
st.code(IMPORT_ERROR_TRACEBACK, language="python")
|
| 195 |
+
st.stop()
|
| 196 |
+
|
| 197 |
+
if st.session_state.step == 1:
|
| 198 |
+
step_1_upload_and_process()
|
| 199 |
+
elif st.session_state.step == 2:
|
| 200 |
+
step_2_review_and_customize()
|
| 201 |
+
elif st.session_state.step == 3:
|
| 202 |
+
step_3_export()
|
| 203 |
+
|
| 204 |
+
# --- Step 1: Upload and Process ---
|
| 205 |
+
def step_1_upload_and_process():
|
| 206 |
+
st.header("Step 1: Choose Your Audio Source")
|
| 207 |
+
|
| 208 |
+
upload_tab, record_tab = st.tabs(["๐ค Upload a File", "๐๏ธ Record Audio"])
|
| 209 |
+
|
| 210 |
+
with upload_tab:
|
| 211 |
+
st.subheader("Upload an existing audio file")
|
| 212 |
+
uploaded_file = st.file_uploader("Choose an audio file", type=['mp3', 'wav', 'm4a'], help="Supported formats: MP3, WAV, M4A")
|
| 213 |
+
if uploaded_file:
|
| 214 |
+
st.session_state.audio_file = uploaded_file
|
| 215 |
+
st.success(f"File uploaded: {uploaded_file.name}")
|
| 216 |
+
st.audio(uploaded_file)
|
| 217 |
+
if st.button("๐ Start AI Processing", type="primary", use_container_width=True):
|
| 218 |
+
process_audio()
|
| 219 |
+
if st.session_state.audio_file:
|
| 220 |
+
if st.button("๐ Upload Different File"):
|
| 221 |
+
reset_session()
|
| 222 |
+
st.rerun()
|
| 223 |
+
|
| 224 |
+
with record_tab:
|
| 225 |
+
st.subheader("Record audio directly from your microphone")
|
| 226 |
+
st.info("Use the recorder below. After processing, copy the resulting file path and paste it here.")
|
| 227 |
+
|
| 228 |
+
# Show the recorder component
|
| 229 |
+
show_recorder_page()
|
| 230 |
+
|
| 231 |
+
st.divider()
|
| 232 |
+
|
| 233 |
+
# Input for the file path
|
| 234 |
+
result_file_path = st.text_input("Paste the result file path here:")
|
| 235 |
+
|
| 236 |
+
if st.button("Process Recorded Audio", use_container_width=True):
|
| 237 |
+
if result_file_path and os.path.exists(result_file_path):
|
| 238 |
+
process_recorded_audio(result_file_path)
|
| 239 |
+
else:
|
| 240 |
+
st.error("Please provide a valid file path.")
|
| 241 |
+
|
| 242 |
+
def show_recorder_page():
|
| 243 |
+
"""Renders the HTML for the recorder page."""
|
| 244 |
+
try:
|
| 245 |
+
with open('templates/recorder.html', 'r', encoding='utf-8') as f:
|
| 246 |
+
html_string = f.read()
|
| 247 |
+
components.html(html_string, height=650, scrolling=True)
|
| 248 |
+
except FileNotFoundError:
|
| 249 |
+
st.error("Could not find the recorder HTML file. Please ensure 'templates/recorder.html' exists.")
|
| 250 |
+
|
| 251 |
+
def process_recorded_audio(file_path):
|
| 252 |
+
"""Processes the audio data from the recorded file."""
|
| 253 |
+
log_to_browser_console("--- INFO: Starting recorded audio processing. ---")
|
| 254 |
+
try:
|
| 255 |
+
with open(file_path, 'r') as f:
|
| 256 |
+
data = json.load(f)
|
| 257 |
+
log_to_browser_console(f"--- DEBUG: Loaded data from {file_path}. ---")
|
| 258 |
+
|
| 259 |
+
# Convert hex back to bytes
|
| 260 |
+
data['audio_bytes'] = bytes.fromhex(data['audio_bytes'])
|
| 261 |
+
log_to_browser_console("--- DEBUG: Converted hex audio data to bytes. ---")
|
| 262 |
+
|
| 263 |
+
st.session_state.transcription_data = data
|
| 264 |
+
st.session_state.edited_text = data['text']
|
| 265 |
+
st.session_state.step = 2
|
| 266 |
+
st.success("๐ Recording processed successfully! Please review the results.")
|
| 267 |
+
log_to_browser_console("--- SUCCESS: Recording processed and session updated. ---")
|
| 268 |
+
|
| 269 |
+
except Exception as e:
|
| 270 |
+
st.error(f"An error occurred while processing the recorded audio: {e}")
|
| 271 |
+
log_to_browser_console(f"--- FATAL ERROR in process_recorded_audio: {traceback.format_exc()} ---")
|
| 272 |
+
finally:
|
| 273 |
+
if os.path.exists(file_path):
|
| 274 |
+
os.unlink(file_path)
|
| 275 |
+
log_to_browser_console(f"--- DEBUG: Cleaned up temp file: {file_path}")
|
| 276 |
+
|
| 277 |
+
time.sleep(1)
|
| 278 |
+
st.rerun()
|
| 279 |
+
|
| 280 |
+
def process_audio():
|
| 281 |
+
if not st.session_state.audio_file:
|
| 282 |
+
st.error("No audio file found.")
|
| 283 |
+
return
|
| 284 |
+
|
| 285 |
+
tmp_file_path = None
|
| 286 |
+
log_to_browser_console("--- INFO: Starting enhanced audio processing with translation. ---")
|
| 287 |
+
try:
|
| 288 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=Path(st.session_state.audio_file.name).suffix) as tmp_file:
|
| 289 |
+
st.session_state.audio_file.seek(0)
|
| 290 |
+
tmp_file.write(st.session_state.audio_file.getvalue())
|
| 291 |
+
tmp_file_path = tmp_file.name
|
| 292 |
+
|
| 293 |
+
processor = AUDIO_PROCESSOR_CLASS()
|
| 294 |
+
|
| 295 |
+
# Enhanced processing with translation support
|
| 296 |
+
if st.session_state.enable_translation:
|
| 297 |
+
with st.spinner("๐ค Performing AI processing (Transcription, Timestamps & Translation)..."):
|
| 298 |
+
result_data, processor_logs = processor.get_word_timestamps_with_translation(
|
| 299 |
+
tmp_file_path,
|
| 300 |
+
st.session_state.target_language
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
log_to_browser_console(processor_logs)
|
| 304 |
+
|
| 305 |
+
if not result_data or not result_data.get('original_text'):
|
| 306 |
+
st.warning("Could not generate transcription with translation. Check the browser console (F12) for detailed error logs.")
|
| 307 |
+
return
|
| 308 |
+
|
| 309 |
+
# Store enhanced results
|
| 310 |
+
word_timestamps = result_data['word_timestamps']
|
| 311 |
+
full_text = result_data['original_text']
|
| 312 |
+
translated_text = result_data['translated_text']
|
| 313 |
+
translation_success = result_data.get('translation_success', False)
|
| 314 |
+
detected_language = result_data.get('language_detected', 'unknown')
|
| 315 |
+
|
| 316 |
+
# Show translation results
|
| 317 |
+
if translation_success and translated_text != full_text:
|
| 318 |
+
st.success(f"๐ Translation completed successfully! Detected language: {detected_language}")
|
| 319 |
+
|
| 320 |
+
col1, col2 = st.columns(2)
|
| 321 |
+
with col1:
|
| 322 |
+
st.subheader("Original Text")
|
| 323 |
+
st.text_area("Original Transcription", value=full_text, height=150, disabled=True)
|
| 324 |
+
|
| 325 |
+
with col2:
|
| 326 |
+
st.subheader(f"Translation ({st.session_state.target_language.upper()})")
|
| 327 |
+
st.text_area("Translated Text", value=translated_text, height=150, disabled=True)
|
| 328 |
+
|
| 329 |
+
# Store both versions
|
| 330 |
+
st.session_state.translated_text = translated_text
|
| 331 |
+
st.session_state.translation_success = True
|
| 332 |
+
else:
|
| 333 |
+
st.info("Translation was requested but may not have been successful.")
|
| 334 |
+
st.session_state.translation_success = False
|
| 335 |
+
|
| 336 |
+
else:
|
| 337 |
+
# Standard processing without translation
|
| 338 |
+
with st.spinner("๐ค Performing AI processing (Transcription & Timestamps)..."):
|
| 339 |
+
word_timestamps, processor_logs = processor.get_word_timestamps(tmp_file_path)
|
| 340 |
+
|
| 341 |
+
log_to_browser_console(processor_logs)
|
| 342 |
+
|
| 343 |
+
if not word_timestamps:
|
| 344 |
+
st.warning("Could not generate timestamps. Check the browser console (F12) for detailed error logs.")
|
| 345 |
+
return
|
| 346 |
+
|
| 347 |
+
full_text = " ".join([d['word'] for d in word_timestamps])
|
| 348 |
+
st.session_state.translation_success = False
|
| 349 |
+
|
| 350 |
+
st.session_state.audio_file.seek(0)
|
| 351 |
+
audio_bytes = st.session_state.audio_file.read()
|
| 352 |
+
|
| 353 |
+
st.session_state.transcription_data = {
|
| 354 |
+
'text': full_text, # Use the text we derived
|
| 355 |
+
'word_timestamps': word_timestamps,
|
| 356 |
+
'audio_bytes': audio_bytes,
|
| 357 |
+
'original_suffix': Path(st.session_state.audio_file.name).suffix
|
| 358 |
+
}
|
| 359 |
+
st.session_state.edited_text = full_text
|
| 360 |
+
st.session_state.step = 2
|
| 361 |
+
st.success("๐ AI processing complete! Please review the results.")
|
| 362 |
+
|
| 363 |
+
except Exception as e:
|
| 364 |
+
st.error("An unexpected error occurred in the main processing flow!")
|
| 365 |
+
st.exception(e)
|
| 366 |
+
log_to_browser_console(f"--- FATAL ERROR in app.py's process_audio: {traceback.format_exc()} ---")
|
| 367 |
+
finally:
|
| 368 |
+
if tmp_file_path and os.path.exists(tmp_file_path):
|
| 369 |
+
os.unlink(tmp_file_path)
|
| 370 |
+
|
| 371 |
+
time.sleep(1)
|
| 372 |
+
st.rerun()
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
# --- Step 2: Review and Customize ---
|
| 377 |
+
def step_2_review_and_customize():
|
| 378 |
+
st.header("Step 2: Review & Customize")
|
| 379 |
+
if 'transcription_data' not in st.session_state or not st.session_state.transcription_data:
|
| 380 |
+
st.error("No data found. Please return to Step 1.")
|
| 381 |
+
if st.button("โ Back to Step 1"):
|
| 382 |
+
st.session_state.step = 1; st.rerun()
|
| 383 |
+
return
|
| 384 |
+
col1, col2 = st.columns([3, 2])
|
| 385 |
+
with col1:
|
| 386 |
+
st.subheader("๐ Text Editor")
|
| 387 |
+
edited_text = st.text_area("Transcribed Text", value=st.session_state.edited_text, height=300)
|
| 388 |
+
st.session_state.edited_text = edited_text
|
| 389 |
+
with col2:
|
| 390 |
+
st.subheader("๐จ Video Style Customization")
|
| 391 |
+
st.session_state.video_style['animation_style'] = st.selectbox("Animation Style", ["Karaoke Style", "Pop-up Word"])
|
| 392 |
+
st.session_state.video_style['text_color'] = st.color_picker("Text Color", st.session_state.video_style['text_color'])
|
| 393 |
+
st.session_state.video_style['highlight_color'] = st.color_picker("Highlight Color", st.session_state.video_style['highlight_color'])
|
| 394 |
+
|
| 395 |
+
col1_nav, _, col3_nav = st.columns([1, 2, 1])
|
| 396 |
+
with col1_nav:
|
| 397 |
+
if st.button("โ Back to Upload"):
|
| 398 |
+
st.session_state.step = 1; st.rerun()
|
| 399 |
+
with col3_nav:
|
| 400 |
+
if st.button("Continue to Export โ", type="primary"):
|
| 401 |
+
st.session_state.step = 3; st.rerun()
|
| 402 |
+
|
| 403 |
+
# --- Step 3: Export ---
|
| 404 |
+
def step_3_export():
|
| 405 |
+
st.header("Step 3: Export Your Synchronized Media")
|
| 406 |
+
if 'transcription_data' not in st.session_state or not st.session_state.transcription_data:
|
| 407 |
+
st.error("No data found. Please return to Step 1.")
|
| 408 |
+
if st.button("โ Back to Step 1"):
|
| 409 |
+
st.session_state.step = 1; st.rerun()
|
| 410 |
+
return
|
| 411 |
+
|
| 412 |
+
col1, col2 = st.columns(2)
|
| 413 |
+
with col1:
|
| 414 |
+
st.subheader("๐ต MP3 Export")
|
| 415 |
+
if st.button("๐ฑ Export MP3 with Lyrics", type="primary", use_container_width=True):
|
| 416 |
+
export_mp3()
|
| 417 |
+
with col2:
|
| 418 |
+
st.subheader("๐ฌ MP4 Video Export")
|
| 419 |
+
if st.button("๐ฅ Generate Video Summary", type="primary", use_container_width=True):
|
| 420 |
+
export_mp4()
|
| 421 |
+
st.divider()
|
| 422 |
+
col1_nav, col2_nav, _ = st.columns([1, 1, 3])
|
| 423 |
+
with col1_nav:
|
| 424 |
+
if st.button("โ Back to Customize"):
|
| 425 |
+
st.session_state.step = 2; st.rerun()
|
| 426 |
+
with col2_nav:
|
| 427 |
+
if st.button("๐ Start Over"):
|
| 428 |
+
reset_session(); st.rerun()
|
| 429 |
+
|
| 430 |
+
# --- MP3 Export Function (with robust stateless logic and logging) ---
|
| 431 |
+
def export_mp3():
|
| 432 |
+
audio_path_for_export = None
|
| 433 |
+
log_to_browser_console("--- INFO: Starting MP3 export process. ---")
|
| 434 |
+
try:
|
| 435 |
+
with st.spinner("Embedding lyrics..."):
|
| 436 |
+
suffix = st.session_state.transcription_data['original_suffix']
|
| 437 |
+
audio_bytes = st.session_state.transcription_data['audio_bytes']
|
| 438 |
+
log_to_browser_console(f"--- DEBUG: Retrieved {len(audio_bytes) / 1024:.2f} KB of audio data from session_state.")
|
| 439 |
+
|
| 440 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp_audio_file:
|
| 441 |
+
tmp_audio_file.write(audio_bytes)
|
| 442 |
+
audio_path_for_export = tmp_audio_file.name
|
| 443 |
+
log_to_browser_console(f"--- DEBUG: Temp file for export created at: {audio_path_for_export}")
|
| 444 |
+
|
| 445 |
+
embedder = MP3Embedder()
|
| 446 |
+
word_timestamps = st.session_state.transcription_data['word_timestamps']
|
| 447 |
+
log_to_browser_console(f"--- DEBUG: Passing {len(word_timestamps)} timestamps to embedder.")
|
| 448 |
+
|
| 449 |
+
output_filename = f"synced_{Path(st.session_state.audio_file.name).stem}.mp3"
|
| 450 |
+
|
| 451 |
+
output_path, log_messages = embedder.embed_sylt_lyrics(
|
| 452 |
+
audio_path_for_export, word_timestamps,
|
| 453 |
+
st.session_state.edited_text, output_filename
|
| 454 |
+
)
|
| 455 |
+
log_to_browser_console(log_messages)
|
| 456 |
+
|
| 457 |
+
st.subheader("โ
Export Complete")
|
| 458 |
+
if os.path.exists(output_path):
|
| 459 |
+
with open(output_path, 'rb') as f:
|
| 460 |
+
audio_bytes_to_download = f.read()
|
| 461 |
+
st.audio(audio_bytes_to_download, format='audio/mp3')
|
| 462 |
+
|
| 463 |
+
verification = embedder.verify_sylt_embedding(output_path)
|
| 464 |
+
log_to_browser_console(f"--- DEBUG: Verification result: {json.dumps(verification)}")
|
| 465 |
+
st.json(verification)
|
| 466 |
+
|
| 467 |
+
if verification.get('has_sylt'):
|
| 468 |
+
st.success(f"Successfully embedded {verification.get('sylt_entries', 0)} words!")
|
| 469 |
+
else:
|
| 470 |
+
st.warning("Warning: Could not verify SYLT embedding.")
|
| 471 |
+
|
| 472 |
+
st.download_button("Download Synced MP3", audio_bytes_to_download,
|
| 473 |
+
output_filename, "audio/mpeg", use_container_width=True)
|
| 474 |
+
else:
|
| 475 |
+
st.error("Failed to create the final MP3 file.")
|
| 476 |
+
log_to_browser_console(f"--- ERROR: Final output file not found at expected path: {output_path}")
|
| 477 |
+
|
| 478 |
+
except Exception as e:
|
| 479 |
+
st.error(f"An unexpected error occurred during MP3 export: {e}")
|
| 480 |
+
log_to_browser_console(f"--- FATAL ERROR in export_mp3: {traceback.format_exc()} ---")
|
| 481 |
+
finally:
|
| 482 |
+
if audio_path_for_export and os.path.exists(audio_path_for_export):
|
| 483 |
+
os.unlink(audio_path_for_export)
|
| 484 |
+
log_to_browser_console(f"--- DEBUG: Cleaned up temp export file: {audio_path_for_export}")
|
| 485 |
+
|
| 486 |
+
# --- Placeholder and Utility Functions ---
|
| 487 |
+
def export_mp4():
|
| 488 |
+
st.info("MP4 export functionality is not yet implemented.")
|
| 489 |
+
|
| 490 |
+
def reset_session():
|
| 491 |
+
"""Resets the session state by clearing specific keys and re-initializing."""
|
| 492 |
+
log_to_browser_console("--- INFO: Resetting session state. ---")
|
| 493 |
+
keys_to_clear = ['step', 'audio_file', 'transcription_data', 'edited_text', 'video_style']
|
| 494 |
+
for key in keys_to_clear:
|
| 495 |
+
if key in st.session_state:
|
| 496 |
+
del st.session_state[key]
|
| 497 |
+
initialize_session_state()
|
| 498 |
+
|
| 499 |
+
# --- Entry Point ---
|
| 500 |
+
if __name__ == "__main__":
|
| 501 |
+
# Compatibility Patches for older Streamlit versions
|
| 502 |
+
import inspect as _st_inspect
|
| 503 |
+
if "type" not in _st_inspect.signature(st.button).parameters:
|
| 504 |
+
_orig_button = st.button
|
| 505 |
+
def _patched_button(label, *args, **kwargs):
|
| 506 |
+
kwargs.pop("type", None); kwargs.pop("use_container_width", None)
|
| 507 |
+
return _orig_button(label, *args, **kwargs)
|
| 508 |
+
st.button = _patched_button
|
| 509 |
+
if hasattr(st, "download_button"):
|
| 510 |
+
import inspect as _dl_inspect
|
| 511 |
+
_dl_sig = _dl_inspect.signature(st.download_button)
|
| 512 |
+
if "use_container_width" not in _dl_sig.parameters:
|
| 513 |
+
_orig_download_button = st.download_button
|
| 514 |
+
def _patched_download_button(label, data, *args, **kwargs):
|
| 515 |
+
kwargs.pop("use_container_width", None)
|
| 516 |
+
return _orig_download_button(label, data, *args, **kwargs)
|
| 517 |
+
st.download_button = _patched_download_button
|
| 518 |
+
|
| 519 |
+
# Setup cleanup handler
|
| 520 |
+
import atexit
|
| 521 |
+
try:
|
| 522 |
+
from integrated_server import stop_all_servers
|
| 523 |
+
atexit.register(stop_all_servers)
|
| 524 |
+
except ImportError:
|
| 525 |
+
pass
|
| 526 |
+
|
| 527 |
+
initialize_session_state()
|
| 528 |
+
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,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
from google import genai
|
| 19 |
+
from google.genai import types
|
| 20 |
+
from translator import AITranslator
|
| 21 |
+
|
| 22 |
+
class AudioProcessor:
|
| 23 |
+
def __init__(self):
|
| 24 |
+
self.client = None
|
| 25 |
+
self.translator = None
|
| 26 |
+
self.init_error = None
|
| 27 |
+
self._initialize_gemini()
|
| 28 |
+
self._initialize_translator()
|
| 29 |
+
|
| 30 |
+
def _initialize_gemini(self):
|
| 31 |
+
try:
|
| 32 |
+
load_dotenv()
|
| 33 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 34 |
+
if not api_key:
|
| 35 |
+
raise ValueError("GEMINI_API_KEY not found in environment variables.")
|
| 36 |
+
self.client = genai.Client(api_key=api_key)
|
| 37 |
+
except Exception as e:
|
| 38 |
+
self.init_error = f"--- FATAL ERROR during Gemini Init: {str(e)} ---"
|
| 39 |
+
self.client = None
|
| 40 |
+
|
| 41 |
+
def _initialize_translator(self):
|
| 42 |
+
"""Initialize AI translator for multi-language support"""
|
| 43 |
+
try:
|
| 44 |
+
self.translator = AITranslator()
|
| 45 |
+
if self.translator.init_error:
|
| 46 |
+
print(f"--- WARNING: Translator has initialization error: {self.translator.init_error} ---")
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"--- WARNING: Translator initialization failed: {str(e)} ---")
|
| 49 |
+
self.translator = None
|
| 50 |
+
|
| 51 |
+
def transcribe_audio(self, audio_file_path: str) -> Tuple[Optional[str], Optional[str]]:
|
| 52 |
+
"""
|
| 53 |
+
Transcribes audio. Returns (text, error_message).
|
| 54 |
+
"""
|
| 55 |
+
if self.init_error:
|
| 56 |
+
return None, self.init_error
|
| 57 |
+
if not self.client:
|
| 58 |
+
return None, "--- ERROR: Gemini client is not available. ---"
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
if not os.path.exists(audio_file_path):
|
| 62 |
+
return None, f"--- ERROR: Audio file for transcription not found at: {audio_file_path} ---"
|
| 63 |
+
|
| 64 |
+
with open(audio_file_path, 'rb') as f:
|
| 65 |
+
audio_bytes = f.read()
|
| 66 |
+
|
| 67 |
+
file_ext = os.path.splitext(audio_file_path)[1].lower()
|
| 68 |
+
mime_type = {'mp3': 'audio/mpeg', 'wav': 'audio/wav', 'm4a': 'audio/mp4'}.get(file_ext, 'audio/mpeg')
|
| 69 |
+
|
| 70 |
+
response = self.client.models.generate_content(
|
| 71 |
+
model="gemini-2.5-flash",
|
| 72 |
+
contents=[types.Part.from_bytes(data=audio_bytes, mime_type=mime_type), "Transcribe this audio."]
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
if response and response.text:
|
| 76 |
+
return response.text.strip(), None
|
| 77 |
+
else:
|
| 78 |
+
return None, "--- WARNING: Gemini returned an empty response for transcription. ---"
|
| 79 |
+
|
| 80 |
+
except Exception as e:
|
| 81 |
+
error_msg = f"--- FATAL ERROR during transcription: {traceback.format_exc()} ---"
|
| 82 |
+
return None, error_msg
|
| 83 |
+
|
| 84 |
+
def get_audio_duration(self, audio_file_path: str) -> Tuple[Optional[float], Optional[str]]:
|
| 85 |
+
"""
|
| 86 |
+
Gets audio duration. Returns (duration, error_message).
|
| 87 |
+
"""
|
| 88 |
+
try:
|
| 89 |
+
if not os.path.exists(audio_file_path):
|
| 90 |
+
return None, f"--- ERROR: Audio file for duration not found at: {audio_file_path} ---"
|
| 91 |
+
|
| 92 |
+
duration = librosa.get_duration(path=audio_file_path)
|
| 93 |
+
if duration is None or duration < 0.1:
|
| 94 |
+
return None, f"--- ERROR: librosa returned an invalid duration: {duration}s ---"
|
| 95 |
+
return duration, None
|
| 96 |
+
except Exception as e:
|
| 97 |
+
error_msg = f"--- FATAL ERROR getting audio duration with librosa: {traceback.format_exc()} ---"
|
| 98 |
+
return None, error_msg
|
| 99 |
+
|
| 100 |
+
def get_word_timestamps(self, audio_file_path: str) -> Tuple[List[Dict], List[str]]:
|
| 101 |
+
"""
|
| 102 |
+
Generates timestamps. Returns (timestamps, log_messages).
|
| 103 |
+
"""
|
| 104 |
+
logs = ["--- INFO: Starting get_word_timestamps... ---"]
|
| 105 |
+
|
| 106 |
+
transcription, error = self.transcribe_audio(audio_file_path)
|
| 107 |
+
if error:
|
| 108 |
+
logs.append(error)
|
| 109 |
+
return [], logs
|
| 110 |
+
logs.append(f"--- DEBUG: Transcription successful. Text: '{transcription[:50]}...'")
|
| 111 |
+
|
| 112 |
+
audio_duration, error = self.get_audio_duration(audio_file_path)
|
| 113 |
+
if error:
|
| 114 |
+
logs.append(error)
|
| 115 |
+
return [], logs
|
| 116 |
+
logs.append(f"--- DEBUG: Audio duration successful. Duration: {audio_duration:.2f}s")
|
| 117 |
+
|
| 118 |
+
words = transcription.split()
|
| 119 |
+
if not words:
|
| 120 |
+
logs.append("--- WARNING: Transcription resulted in zero words. ---")
|
| 121 |
+
return [], logs
|
| 122 |
+
|
| 123 |
+
logs.append(f"--- INFO: Distributing {len(words)} words across the duration. ---")
|
| 124 |
+
word_timestamps = []
|
| 125 |
+
total_words = len(words)
|
| 126 |
+
usable_duration = max(0, audio_duration - 1.0)
|
| 127 |
+
|
| 128 |
+
for i, word in enumerate(words):
|
| 129 |
+
start_time = 0.5 + (i * (usable_duration / total_words))
|
| 130 |
+
end_time = 0.5 + ((i + 1) * (usable_duration / total_words))
|
| 131 |
+
word_timestamps.append({'word': word.strip(), 'start': round(start_time, 3), 'end': round(end_time, 3)})
|
| 132 |
+
|
| 133 |
+
logs.append(f"--- SUCCESS: Generated {len(word_timestamps)} word timestamps. ---")
|
| 134 |
+
return word_timestamps, logs
|
| 135 |
+
|
| 136 |
+
def get_word_timestamps_with_translation(self, audio_file_path: str, target_language: str = 'ar') -> Tuple[Dict, List[str]]:
|
| 137 |
+
"""
|
| 138 |
+
Enhanced function that provides both transcription and translation
|
| 139 |
+
|
| 140 |
+
Args:
|
| 141 |
+
audio_file_path: Path to audio file
|
| 142 |
+
target_language: Target language for translation ('ar' for Arabic)
|
| 143 |
+
|
| 144 |
+
Returns:
|
| 145 |
+
Tuple of (result_dict, log_messages)
|
| 146 |
+
result_dict contains: {
|
| 147 |
+
'original_text': str,
|
| 148 |
+
'translated_text': str,
|
| 149 |
+
'word_timestamps': List[Dict],
|
| 150 |
+
'translated_timestamps': List[Dict],
|
| 151 |
+
'language_detected': str,
|
| 152 |
+
'target_language': str
|
| 153 |
+
}
|
| 154 |
+
"""
|
| 155 |
+
logs = ["--- INFO: Starting enhanced transcription with translation... ---"]
|
| 156 |
+
|
| 157 |
+
# Get original transcription and timestamps
|
| 158 |
+
word_timestamps, transcription_logs = self.get_word_timestamps(audio_file_path)
|
| 159 |
+
logs.extend(transcription_logs)
|
| 160 |
+
|
| 161 |
+
if not word_timestamps:
|
| 162 |
+
logs.append("--- ERROR: No transcription available for translation ---")
|
| 163 |
+
return {}, logs
|
| 164 |
+
|
| 165 |
+
# Extract original text
|
| 166 |
+
original_text = " ".join([d['word'] for d in word_timestamps])
|
| 167 |
+
logs.append(f"--- INFO: Original transcription: '{original_text[:50]}...' ---")
|
| 168 |
+
|
| 169 |
+
# Initialize result dictionary
|
| 170 |
+
result = {
|
| 171 |
+
'original_text': original_text,
|
| 172 |
+
'translated_text': '',
|
| 173 |
+
'word_timestamps': word_timestamps,
|
| 174 |
+
'translated_timestamps': [],
|
| 175 |
+
'language_detected': 'unknown',
|
| 176 |
+
'target_language': target_language,
|
| 177 |
+
'translation_success': False
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
# Check if translator is available
|
| 181 |
+
if not self.translator:
|
| 182 |
+
logs.append("--- WARNING: Translator not available, returning original text only ---")
|
| 183 |
+
result['translated_text'] = original_text
|
| 184 |
+
return result, logs
|
| 185 |
+
|
| 186 |
+
try:
|
| 187 |
+
# Detect original language
|
| 188 |
+
detected_lang, detect_error = self.translator.detect_language(original_text)
|
| 189 |
+
if detected_lang and detected_lang != 'unknown':
|
| 190 |
+
result['language_detected'] = detected_lang
|
| 191 |
+
logs.append(f"--- INFO: Detected language: {detected_lang} ---")
|
| 192 |
+
else:
|
| 193 |
+
logs.append(f"--- WARNING: Language detection failed: {detect_error} ---")
|
| 194 |
+
|
| 195 |
+
# Translate the text
|
| 196 |
+
translated_text, translation_error = self.translator.translate_text(
|
| 197 |
+
original_text,
|
| 198 |
+
target_language=target_language
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
if translated_text:
|
| 202 |
+
result['translated_text'] = translated_text
|
| 203 |
+
result['translation_success'] = True
|
| 204 |
+
logs.append(f"--- SUCCESS: Translation completed: '{translated_text[:50]}...' ---")
|
| 205 |
+
|
| 206 |
+
# Create translated timestamps by mapping words
|
| 207 |
+
translated_timestamps = self._create_translated_timestamps(
|
| 208 |
+
word_timestamps,
|
| 209 |
+
original_text,
|
| 210 |
+
translated_text
|
| 211 |
+
)
|
| 212 |
+
result['translated_timestamps'] = translated_timestamps
|
| 213 |
+
logs.append(f"--- INFO: Created {len(translated_timestamps)} translated timestamps ---")
|
| 214 |
+
|
| 215 |
+
else:
|
| 216 |
+
logs.append(f"--- ERROR: Translation failed: {translation_error} ---")
|
| 217 |
+
result['translated_text'] = original_text # Fallback to original
|
| 218 |
+
result['translated_timestamps'] = word_timestamps # Use original timestamps
|
| 219 |
+
|
| 220 |
+
except Exception as e:
|
| 221 |
+
error_msg = f"--- FATAL ERROR during translation process: {traceback.format_exc()} ---"
|
| 222 |
+
logs.append(error_msg)
|
| 223 |
+
result['translated_text'] = original_text # Fallback
|
| 224 |
+
result['translated_timestamps'] = word_timestamps
|
| 225 |
+
|
| 226 |
+
return result, logs
|
| 227 |
+
|
| 228 |
+
def _create_translated_timestamps(self, original_timestamps: List[Dict], original_text: str, translated_text: str) -> List[Dict]:
|
| 229 |
+
"""
|
| 230 |
+
Create timestamps for translated text by proportional mapping
|
| 231 |
+
|
| 232 |
+
Args:
|
| 233 |
+
original_timestamps: Original word timestamps
|
| 234 |
+
original_text: Original transcribed text
|
| 235 |
+
translated_text: Translated text
|
| 236 |
+
|
| 237 |
+
Returns:
|
| 238 |
+
List of translated word timestamps
|
| 239 |
+
"""
|
| 240 |
+
try:
|
| 241 |
+
translated_words = translated_text.split()
|
| 242 |
+
if not translated_words:
|
| 243 |
+
return []
|
| 244 |
+
|
| 245 |
+
# Get total duration from original timestamps
|
| 246 |
+
if not original_timestamps:
|
| 247 |
+
return []
|
| 248 |
+
|
| 249 |
+
start_time = original_timestamps[0]['start']
|
| 250 |
+
end_time = original_timestamps[-1]['end']
|
| 251 |
+
total_duration = end_time - start_time
|
| 252 |
+
|
| 253 |
+
# Create proportional timestamps for translated words
|
| 254 |
+
translated_timestamps = []
|
| 255 |
+
word_count = len(translated_words)
|
| 256 |
+
|
| 257 |
+
for i, word in enumerate(translated_words):
|
| 258 |
+
# Calculate proportional timing
|
| 259 |
+
word_start = start_time + (i * total_duration / word_count)
|
| 260 |
+
word_end = start_time + ((i + 1) * total_duration / word_count)
|
| 261 |
+
|
| 262 |
+
translated_timestamps.append({
|
| 263 |
+
'word': word.strip(),
|
| 264 |
+
'start': round(word_start, 3),
|
| 265 |
+
'end': round(word_end, 3)
|
| 266 |
+
})
|
| 267 |
+
|
| 268 |
+
return translated_timestamps
|
| 269 |
+
|
| 270 |
+
except Exception as e:
|
| 271 |
+
print(f"--- ERROR creating translated timestamps: {str(e)} ---")
|
| 272 |
+
return []
|
| 273 |
+
|
| 274 |
+
def batch_translate_transcription(self, audio_file_path: str, target_languages: List[str]) -> Tuple[Dict, List[str]]:
|
| 275 |
+
"""
|
| 276 |
+
Transcribe audio and translate to multiple languages
|
| 277 |
+
|
| 278 |
+
Args:
|
| 279 |
+
audio_file_path: Path to audio file
|
| 280 |
+
target_languages: List of target language codes
|
| 281 |
+
|
| 282 |
+
Returns:
|
| 283 |
+
Tuple of (results_dict, log_messages)
|
| 284 |
+
"""
|
| 285 |
+
logs = ["--- INFO: Starting batch translation process... ---"]
|
| 286 |
+
|
| 287 |
+
# Get original transcription
|
| 288 |
+
word_timestamps, transcription_logs = self.get_word_timestamps(audio_file_path)
|
| 289 |
+
logs.extend(transcription_logs)
|
| 290 |
+
|
| 291 |
+
if not word_timestamps:
|
| 292 |
+
return {}, logs
|
| 293 |
+
|
| 294 |
+
original_text = " ".join([d['word'] for d in word_timestamps])
|
| 295 |
+
|
| 296 |
+
# Initialize results
|
| 297 |
+
results = {
|
| 298 |
+
'original': {
|
| 299 |
+
'text': original_text,
|
| 300 |
+
'timestamps': word_timestamps,
|
| 301 |
+
'language': 'detected'
|
| 302 |
+
},
|
| 303 |
+
'translations': {}
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
# Translate to each target language
|
| 307 |
+
if self.translator:
|
| 308 |
+
for lang_code in target_languages:
|
| 309 |
+
try:
|
| 310 |
+
translated_text, error = self.translator.translate_text(original_text, lang_code)
|
| 311 |
+
if translated_text:
|
| 312 |
+
translated_timestamps = self._create_translated_timestamps(
|
| 313 |
+
word_timestamps, original_text, translated_text
|
| 314 |
+
)
|
| 315 |
+
results['translations'][lang_code] = {
|
| 316 |
+
'text': translated_text,
|
| 317 |
+
'timestamps': translated_timestamps,
|
| 318 |
+
'success': True
|
| 319 |
+
}
|
| 320 |
+
logs.append(f"--- SUCCESS: Translation to {lang_code} completed ---")
|
| 321 |
+
else:
|
| 322 |
+
results['translations'][lang_code] = {
|
| 323 |
+
'text': original_text,
|
| 324 |
+
'timestamps': word_timestamps,
|
| 325 |
+
'success': False,
|
| 326 |
+
'error': error
|
| 327 |
+
}
|
| 328 |
+
logs.append(f"--- ERROR: Translation to {lang_code} failed: {error} ---")
|
| 329 |
+
except Exception as e:
|
| 330 |
+
logs.append(f"--- FATAL ERROR translating to {lang_code}: {str(e)} ---")
|
| 331 |
+
else:
|
| 332 |
+
logs.append("--- WARNING: Translator not available for batch translation ---")
|
| 333 |
+
|
| 334 |
+
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()
|
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)
|
integrated_server.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Integrated Server Module
|
| 3 |
+
ูุฏู
ุฌ ุฎุงุฏู
Flask ููุชุณุฌูู ู
ุน ุชุทุจูู Streamlit
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import threading
|
| 7 |
+
import time
|
| 8 |
+
import logging
|
| 9 |
+
import sys
|
| 10 |
+
import subprocess
|
| 11 |
+
import os
|
| 12 |
+
import socket
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
# Import configuration
|
| 16 |
+
try:
|
| 17 |
+
from app_config import config
|
| 18 |
+
except ImportError:
|
| 19 |
+
# Fallback configuration
|
| 20 |
+
class FallbackConfig:
|
| 21 |
+
RECORDER_PORT = 5001
|
| 22 |
+
RECORDER_HOST = "localhost"
|
| 23 |
+
USE_INTEGRATED_SERVER = True
|
| 24 |
+
config = FallbackConfig()
|
| 25 |
+
|
| 26 |
+
# Configure logging
|
| 27 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 28 |
+
|
| 29 |
+
def is_port_in_use(port):
|
| 30 |
+
"""Check if a port is already in use"""
|
| 31 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| 32 |
+
try:
|
| 33 |
+
s.bind(('localhost', port))
|
| 34 |
+
return False
|
| 35 |
+
except socket.error:
|
| 36 |
+
return True
|
| 37 |
+
|
| 38 |
+
class IntegratedServer:
|
| 39 |
+
def __init__(self):
|
| 40 |
+
self.flask_process = None
|
| 41 |
+
self.flask_thread = None
|
| 42 |
+
self.is_running = False
|
| 43 |
+
self.port = config.RECORDER_PORT
|
| 44 |
+
self.host = config.RECORDER_HOST
|
| 45 |
+
|
| 46 |
+
def start_recorder_server_thread(self):
|
| 47 |
+
"""Start the recorder server in a separate thread"""
|
| 48 |
+
try:
|
| 49 |
+
# Check if port is already in use
|
| 50 |
+
if is_port_in_use(self.port):
|
| 51 |
+
logging.info(f"โ
Port {self.port} already in use, assuming recorder server is running")
|
| 52 |
+
self.is_running = True
|
| 53 |
+
return True
|
| 54 |
+
|
| 55 |
+
# Import Flask app from recorder_server
|
| 56 |
+
from recorder_server import app
|
| 57 |
+
|
| 58 |
+
# Configure Flask to run without debug mode
|
| 59 |
+
app.config['DEBUG'] = False
|
| 60 |
+
app.config['TESTING'] = False
|
| 61 |
+
|
| 62 |
+
# Run Flask app in thread
|
| 63 |
+
def run_flask():
|
| 64 |
+
try:
|
| 65 |
+
app.run(
|
| 66 |
+
host=self.host,
|
| 67 |
+
port=self.port,
|
| 68 |
+
debug=False,
|
| 69 |
+
use_reloader=False,
|
| 70 |
+
threaded=True
|
| 71 |
+
)
|
| 72 |
+
except Exception as e:
|
| 73 |
+
logging.error(f"Error running Flask server: {e}")
|
| 74 |
+
|
| 75 |
+
self.flask_thread = threading.Thread(target=run_flask, daemon=True)
|
| 76 |
+
self.flask_thread.start()
|
| 77 |
+
|
| 78 |
+
# Wait for server to start with shorter intervals
|
| 79 |
+
max_retries = 15
|
| 80 |
+
for i in range(max_retries):
|
| 81 |
+
time.sleep(0.5) # Reduced from 1 second to 0.5 seconds
|
| 82 |
+
if self.is_server_responding():
|
| 83 |
+
self.is_running = True
|
| 84 |
+
logging.info(f"โ
Recorder server started successfully on port {self.port}")
|
| 85 |
+
return True
|
| 86 |
+
if i < 5: # Only log first few attempts to reduce noise
|
| 87 |
+
logging.info(f"โณ Waiting for recorder server to start... ({i+1}/{max_retries})")
|
| 88 |
+
|
| 89 |
+
logging.warning("โ ๏ธ Recorder server thread started but not responding")
|
| 90 |
+
return False
|
| 91 |
+
|
| 92 |
+
except Exception as e:
|
| 93 |
+
logging.error(f"โ Failed to start recorder server thread: {e}")
|
| 94 |
+
return False
|
| 95 |
+
|
| 96 |
+
def start_recorder_server_process(self):
|
| 97 |
+
"""Start the recorder server as a separate process (fallback method)"""
|
| 98 |
+
try:
|
| 99 |
+
# Check if port is already in use
|
| 100 |
+
if is_port_in_use(self.port):
|
| 101 |
+
logging.info(f"โ
Port {self.port} already in use, assuming recorder server is running")
|
| 102 |
+
self.is_running = True
|
| 103 |
+
return True
|
| 104 |
+
|
| 105 |
+
# Start recorder server as subprocess
|
| 106 |
+
self.flask_process = subprocess.Popen(
|
| 107 |
+
[sys.executable, 'recorder_server.py'],
|
| 108 |
+
stdout=subprocess.PIPE,
|
| 109 |
+
stderr=subprocess.PIPE,
|
| 110 |
+
cwd=os.getcwd()
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# Wait for server to start with shorter intervals
|
| 114 |
+
max_retries = 20
|
| 115 |
+
for i in range(max_retries):
|
| 116 |
+
time.sleep(0.5) # Reduced from 1 second to 0.5 seconds
|
| 117 |
+
|
| 118 |
+
# Check if process is still running
|
| 119 |
+
if self.flask_process.poll() is not None:
|
| 120 |
+
logging.error("โ Recorder server process terminated")
|
| 121 |
+
return False
|
| 122 |
+
|
| 123 |
+
# Check if server is responding
|
| 124 |
+
if self.is_server_responding():
|
| 125 |
+
self.is_running = True
|
| 126 |
+
logging.info(f"โ
Recorder server started successfully as subprocess on port {self.port}")
|
| 127 |
+
return True
|
| 128 |
+
|
| 129 |
+
if i < 5: # Only log first few attempts
|
| 130 |
+
logging.info(f"โณ Waiting for recorder server to respond... ({i+1}/{max_retries})")
|
| 131 |
+
|
| 132 |
+
logging.warning("โ ๏ธ Recorder server process started but not responding")
|
| 133 |
+
return False
|
| 134 |
+
|
| 135 |
+
except Exception as e:
|
| 136 |
+
logging.error(f"โ Failed to start recorder server process: {e}")
|
| 137 |
+
return False
|
| 138 |
+
|
| 139 |
+
def start_recorder_server(self):
|
| 140 |
+
"""Start recorder server using the best available method"""
|
| 141 |
+
logging.info("๐ Starting integrated recorder server...")
|
| 142 |
+
|
| 143 |
+
# If already running, don't start again
|
| 144 |
+
if self.is_running and self.is_server_responding():
|
| 145 |
+
logging.info("โ
Recorder server already running and responding")
|
| 146 |
+
return True
|
| 147 |
+
|
| 148 |
+
# Try thread method first (cleaner for integration)
|
| 149 |
+
if self.start_recorder_server_thread():
|
| 150 |
+
logging.info("โ
Recorder server started using thread method")
|
| 151 |
+
return True
|
| 152 |
+
|
| 153 |
+
# Fallback to process method
|
| 154 |
+
logging.info("โ ๏ธ Thread method failed, trying process method...")
|
| 155 |
+
if self.start_recorder_server_process():
|
| 156 |
+
logging.info("โ
Recorder server started using process method")
|
| 157 |
+
return True
|
| 158 |
+
|
| 159 |
+
logging.error("โ Failed to start recorder server using any method")
|
| 160 |
+
return False
|
| 161 |
+
|
| 162 |
+
def stop_recorder_server(self):
|
| 163 |
+
"""Stop the recorder server"""
|
| 164 |
+
try:
|
| 165 |
+
if self.flask_process and self.flask_process.poll() is None:
|
| 166 |
+
self.flask_process.terminate()
|
| 167 |
+
try:
|
| 168 |
+
self.flask_process.wait(timeout=5)
|
| 169 |
+
logging.info("โ
Recorder server process terminated")
|
| 170 |
+
except subprocess.TimeoutExpired:
|
| 171 |
+
self.flask_process.kill()
|
| 172 |
+
logging.info("โ ๏ธ Recorder server process killed")
|
| 173 |
+
|
| 174 |
+
self.is_running = False
|
| 175 |
+
|
| 176 |
+
except Exception as e:
|
| 177 |
+
logging.error(f"โ Error stopping recorder server: {e}")
|
| 178 |
+
|
| 179 |
+
def is_server_responding(self):
|
| 180 |
+
"""Check if the recorder server is responding"""
|
| 181 |
+
try:
|
| 182 |
+
import requests
|
| 183 |
+
# Very fast check - reduce timeout further
|
| 184 |
+
response = requests.get(f'http://{self.host}:{self.port}/record', timeout=0.5)
|
| 185 |
+
return response.status_code == 200
|
| 186 |
+
except:
|
| 187 |
+
return False
|
| 188 |
+
|
| 189 |
+
def is_server_running(self):
|
| 190 |
+
"""Check if the recorder server is running"""
|
| 191 |
+
return self.is_running and self.is_server_responding()
|
| 192 |
+
|
| 193 |
+
# Global instance
|
| 194 |
+
integrated_server = IntegratedServer()
|
| 195 |
+
|
| 196 |
+
def ensure_recorder_server():
|
| 197 |
+
"""Ensure recorder server is running, start if not"""
|
| 198 |
+
try:
|
| 199 |
+
# Quick check first - if server responds immediately, no delay
|
| 200 |
+
if integrated_server.is_server_responding():
|
| 201 |
+
logging.info("โ
Recorder server already running")
|
| 202 |
+
integrated_server.is_running = True
|
| 203 |
+
return True
|
| 204 |
+
|
| 205 |
+
# Only start if not detected
|
| 206 |
+
logging.info("๐ Recorder server not detected, starting...")
|
| 207 |
+
result = integrated_server.start_recorder_server()
|
| 208 |
+
return result
|
| 209 |
+
except Exception as e:
|
| 210 |
+
logging.error(f"โ Error in ensure_recorder_server: {e}")
|
| 211 |
+
return False
|
| 212 |
+
|
| 213 |
+
def stop_all_servers():
|
| 214 |
+
"""Stop all integrated servers"""
|
| 215 |
+
integrated_server.stop_recorder_server()
|
| 216 |
+
|
| 217 |
+
# Auto-start when module is imported (but not in main)
|
| 218 |
+
if __name__ != "__main__":
|
| 219 |
+
# Only auto-start if not running as main module and not already started
|
| 220 |
+
try:
|
| 221 |
+
if not integrated_server.is_running:
|
| 222 |
+
ensure_recorder_server()
|
| 223 |
+
except Exception as e:
|
| 224 |
+
logging.error(f"โ Error during auto-start: {e}")
|
| 225 |
+
|
| 226 |
+
if __name__ == "__main__":
|
| 227 |
+
# Test the integrated server
|
| 228 |
+
print("Testing Integrated Server...")
|
| 229 |
+
success = ensure_recorder_server()
|
| 230 |
+
if success:
|
| 231 |
+
print("โ
Server test successful")
|
| 232 |
+
time.sleep(2)
|
| 233 |
+
stop_all_servers()
|
| 234 |
+
else:
|
| 235 |
+
print("โ Server test failed")
|
lecture_notes.db
ADDED
|
Binary file (16.4 kB). View file
|
|
|
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
|
package-lock.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "syncmaster",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "syncmaster",
|
| 9 |
+
"version": "0.1.0"
|
| 10 |
+
}
|
| 11 |
+
}
|
| 12 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "syncmaster",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"description": "AI Audio-Text Synchronization Platform โ convenience wrapper for Streamlit dev server with integrated recorder",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "streamlit run app.py --server.port 5050 --server.address localhost",
|
| 8 |
+
"start": "streamlit run app.py --server.port 5050 --server.address 0.0.0.0",
|
| 9 |
+
"dev-launcher": "streamlit run app_launcher.py --server.port 5050 --server.address localhost",
|
| 10 |
+
"dev-separate": "python startup.py",
|
| 11 |
+
"build": "echo 'Installing Python dependencies...' && pip install -r requirements.txt"
|
| 12 |
+
},
|
| 13 |
+
"dependencies": {
|
| 14 |
+
"streamlit": "^1.28.0"
|
| 15 |
+
},
|
| 16 |
+
"keywords": ["ai", "audio", "transcription", "synchronization", "streamlit"],
|
| 17 |
+
"author": "SyncMaster Team",
|
| 18 |
+
"license": "MIT"
|
| 19 |
+
}
|
packages.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ffmpeg
|
| 2 |
+
libavcodec-extra
|
| 3 |
+
libavformat-dev
|
| 4 |
+
libavutil-dev
|
| 5 |
+
libmp3lame0
|
performance_test.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Performance Test for SyncMaster
|
| 3 |
+
ุงุฎุชุจุงุฑ ุฃุฏุงุก ุงูุชุทุจูู
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import time
|
| 7 |
+
import requests
|
| 8 |
+
import threading
|
| 9 |
+
|
| 10 |
+
def test_load_time(url, test_name):
|
| 11 |
+
"""Test page load time"""
|
| 12 |
+
try:
|
| 13 |
+
start = time.time()
|
| 14 |
+
response = requests.get(url, timeout=10)
|
| 15 |
+
end = time.time()
|
| 16 |
+
|
| 17 |
+
load_time = end - start
|
| 18 |
+
status = response.status_code
|
| 19 |
+
|
| 20 |
+
print(f"๐งช {test_name}:")
|
| 21 |
+
print(f" โฑ๏ธ Load Time: {load_time:.3f} seconds")
|
| 22 |
+
print(f" ๐ Status: {status}")
|
| 23 |
+
print(f" โ
{'FAST' if load_time < 0.5 else 'SLOW' if load_time > 1.0 else 'OK'}")
|
| 24 |
+
print()
|
| 25 |
+
|
| 26 |
+
return load_time, status
|
| 27 |
+
except Exception as e:
|
| 28 |
+
print(f"โ {test_name} failed: {e}")
|
| 29 |
+
return None, None
|
| 30 |
+
|
| 31 |
+
def run_performance_tests():
|
| 32 |
+
"""Run comprehensive performance tests"""
|
| 33 |
+
print("๐ SyncMaster Performance Test")
|
| 34 |
+
print("=" * 40)
|
| 35 |
+
|
| 36 |
+
# Test multiple requests to see consistency
|
| 37 |
+
tests = [
|
| 38 |
+
("First Load", "http://localhost:5050"),
|
| 39 |
+
("Second Load", "http://localhost:5050"),
|
| 40 |
+
("Third Load", "http://localhost:5050"),
|
| 41 |
+
("Recorder API", "http://localhost:5001/record")
|
| 42 |
+
]
|
| 43 |
+
|
| 44 |
+
results = []
|
| 45 |
+
for test_name, url in tests:
|
| 46 |
+
load_time, status = test_load_time(url, test_name)
|
| 47 |
+
if load_time:
|
| 48 |
+
results.append(load_time)
|
| 49 |
+
time.sleep(0.5) # Small delay between tests
|
| 50 |
+
|
| 51 |
+
if results:
|
| 52 |
+
avg_time = sum(results) / len(results)
|
| 53 |
+
print(f"๐ Average Load Time: {avg_time:.3f} seconds")
|
| 54 |
+
print(f"๐ฏ Performance Rating: {'EXCELLENT' if avg_time < 0.2 else 'GOOD' if avg_time < 0.5 else 'NEEDS IMPROVEMENT'}")
|
| 55 |
+
|
| 56 |
+
return results
|
| 57 |
+
|
| 58 |
+
if __name__ == "__main__":
|
| 59 |
+
run_performance_tests()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "repl-nix-workspace"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Add your description here"
|
| 5 |
+
requires-python = ">=3.11"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"google-genai>=1.23.0",
|
| 8 |
+
"librosa>=0.11.0",
|
| 9 |
+
"moviepy>=2.2.1",
|
| 10 |
+
"mutagen>=1.47.0",
|
| 11 |
+
"numpy>=2.2.6",
|
| 12 |
+
"openai>=1.93.0",
|
| 13 |
+
"sift-stack-py>=0.7.0",
|
| 14 |
+
"streamlit>=1.46.1",
|
| 15 |
+
]
|
recorder_server.py
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify
|
| 2 |
+
import os
|
| 3 |
+
import tempfile
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from audio_processor import AudioProcessor
|
| 6 |
+
import logging
|
| 7 |
+
import json
|
| 8 |
+
import traceback
|
| 9 |
+
from flask_cors import CORS
|
| 10 |
+
import soundfile as sf
|
| 11 |
+
import librosa
|
| 12 |
+
from translator import get_translator, get_translation
|
| 13 |
+
from database import NotesDatabase
|
| 14 |
+
from summarizer import LectureSummarizer
|
| 15 |
+
|
| 16 |
+
app = Flask(__name__)
|
| 17 |
+
# ุฅุนุฏุงุฏ CORS ุจุดูู ุตุญูุญ ูุชุฌูุจ ุชูุฑุงุฑ headers
|
| 18 |
+
CORS(app,
|
| 19 |
+
origins="*",
|
| 20 |
+
methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
| 21 |
+
allow_headers=['Content-Type', 'Authorization']
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
# Configure logging
|
| 25 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 26 |
+
|
| 27 |
+
# Initialize database and summarizer
|
| 28 |
+
db = NotesDatabase()
|
| 29 |
+
summarizer = LectureSummarizer()
|
| 30 |
+
|
| 31 |
+
@app.route('/record', methods=['GET', 'POST', 'OPTIONS'])
|
| 32 |
+
def record():
|
| 33 |
+
if request.method == 'OPTIONS':
|
| 34 |
+
# Handle CORS preflight request - flask-cors ุณุชุชููู headers
|
| 35 |
+
return '', 204
|
| 36 |
+
|
| 37 |
+
if request.method == 'GET':
|
| 38 |
+
logging.info("--- RECORD ENDPOINT: Health check request received. ---")
|
| 39 |
+
return jsonify({'status': 'ok', 'message': 'Recorder server is running.'})
|
| 40 |
+
|
| 41 |
+
logging.info("--- RECORD ENDPOINT: POST request received. ---")
|
| 42 |
+
logging.info(f"Request Headers: {request.headers}")
|
| 43 |
+
|
| 44 |
+
if 'audio_data' not in request.files:
|
| 45 |
+
logging.error("--- RECORD ENDPOINT: 'audio_data' not in request files. ---")
|
| 46 |
+
return jsonify({'success': False, 'error': 'No audio file found'})
|
| 47 |
+
|
| 48 |
+
audio_file = request.files['audio_data']
|
| 49 |
+
markers = json.loads(request.form.get('markers', '[]'))
|
| 50 |
+
target_language = request.form.get('target_language', 'ar') # Default to Arabic
|
| 51 |
+
enable_translation = request.form.get('enable_translation', 'true').lower() == 'true'
|
| 52 |
+
|
| 53 |
+
print(f"๐ RECORD ENDPOINT DEBUG:")
|
| 54 |
+
print(f"- enable_translation: {enable_translation}")
|
| 55 |
+
print(f"- target_language: {target_language}")
|
| 56 |
+
|
| 57 |
+
logging.info(f"--- RECORD ENDPOINT: Received file: {audio_file.filename} ---")
|
| 58 |
+
logging.info(f"--- RECORD ENDPOINT: Target language: {target_language} ---")
|
| 59 |
+
logging.info(f"--- RECORD ENDPOINT: Translation enabled: {enable_translation} ---")
|
| 60 |
+
|
| 61 |
+
if markers:
|
| 62 |
+
logging.info(f"--- RECORD ENDPOINT: Received {len(markers)} markers: {markers} ---")
|
| 63 |
+
|
| 64 |
+
tmp_file_path = None
|
| 65 |
+
try:
|
| 66 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
|
| 67 |
+
audio_file.save(tmp_file.name)
|
| 68 |
+
tmp_file_path = tmp_file.name
|
| 69 |
+
logging.info(f"--- RECORD ENDPOINT: Audio saved to temporary file: {tmp_file_path} ---")
|
| 70 |
+
|
| 71 |
+
# --- File Standardization Step ---
|
| 72 |
+
try:
|
| 73 |
+
logging.info(f"--- RECORD ENDPOINT: Standardizing audio file: {tmp_file_path} ---")
|
| 74 |
+
y, sr = librosa.load(tmp_file_path, sr=None, mono=True)
|
| 75 |
+
sf.write(tmp_file_path, y, sr)
|
| 76 |
+
logging.info(f"--- RECORD ENDPOINT: Audio file successfully standardized. ---")
|
| 77 |
+
except Exception as e:
|
| 78 |
+
logging.warning(f"--- RECORD ENDPOINT: Could not standardize audio file, proceeding with original. Error: {e} ---")
|
| 79 |
+
# --- End Standardization ---
|
| 80 |
+
|
| 81 |
+
processor = AudioProcessor()
|
| 82 |
+
|
| 83 |
+
# Choose processing method based on translation requirement
|
| 84 |
+
if enable_translation:
|
| 85 |
+
logging.info("--- RECORD ENDPOINT: Calling enhanced processor with translation ---")
|
| 86 |
+
result_data, processor_logs = processor.get_word_timestamps_with_translation(
|
| 87 |
+
tmp_file_path, target_language
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
for log_msg in processor_logs:
|
| 91 |
+
logging.info(f"--- AUDIO_PROCESSOR LOG: {log_msg} ---")
|
| 92 |
+
|
| 93 |
+
if not result_data or not result_data.get('original_text'):
|
| 94 |
+
logging.error("--- RECORD ENDPOINT: Failed to generate transcription with translation. ---")
|
| 95 |
+
return jsonify({
|
| 96 |
+
'success': False,
|
| 97 |
+
'error': 'Could not generate transcription with translation.',
|
| 98 |
+
'logs': processor_logs
|
| 99 |
+
})
|
| 100 |
+
|
| 101 |
+
# Prepare enhanced response with translation
|
| 102 |
+
full_text = result_data['original_text']
|
| 103 |
+
translated_text = result_data['translated_text']
|
| 104 |
+
word_timestamps = result_data['word_timestamps']
|
| 105 |
+
translated_timestamps = result_data.get('translated_timestamps', [])
|
| 106 |
+
|
| 107 |
+
logging.info(f"--- RECORD ENDPOINT: Original text: '{full_text[:100]}...'")
|
| 108 |
+
logging.info(f"--- RECORD ENDPOINT: Translated text: '{translated_text[:100]}...'")
|
| 109 |
+
|
| 110 |
+
else:
|
| 111 |
+
# Standard processing without translation
|
| 112 |
+
logging.info("--- RECORD ENDPOINT: Calling standard audio_processor.get_word_timestamps ---")
|
| 113 |
+
word_timestamps, processor_logs = processor.get_word_timestamps(tmp_file_path)
|
| 114 |
+
|
| 115 |
+
for log_msg in processor_logs:
|
| 116 |
+
logging.info(f"--- AUDIO_PROCESSOR LOG: {log_msg} ---")
|
| 117 |
+
|
| 118 |
+
if not word_timestamps:
|
| 119 |
+
logging.error("--- RECORD ENDPOINT: Failed to generate timestamps. ---")
|
| 120 |
+
return jsonify({
|
| 121 |
+
'success': False,
|
| 122 |
+
'error': 'Could not generate timestamps.',
|
| 123 |
+
'logs': processor_logs
|
| 124 |
+
})
|
| 125 |
+
|
| 126 |
+
full_text = " ".join([d['word'] for d in word_timestamps])
|
| 127 |
+
translated_text = full_text # No translation requested
|
| 128 |
+
translated_timestamps = word_timestamps
|
| 129 |
+
result_data = {
|
| 130 |
+
'original_text': full_text,
|
| 131 |
+
'translated_text': translated_text,
|
| 132 |
+
'word_timestamps': word_timestamps,
|
| 133 |
+
'translated_timestamps': translated_timestamps,
|
| 134 |
+
'translation_success': not enable_translation
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
logging.info(f"--- RECORD ENDPOINT: Transcription successful. Text: '{full_text[:100]}...'")
|
| 138 |
+
|
| 139 |
+
# Read audio file for storage
|
| 140 |
+
with open(tmp_file_path, 'rb') as f:
|
| 141 |
+
audio_bytes = f.read()
|
| 142 |
+
|
| 143 |
+
# Prepare comprehensive transcription data
|
| 144 |
+
transcription_data = {
|
| 145 |
+
'original_text': result_data['original_text'],
|
| 146 |
+
'translated_text': result_data['translated_text'],
|
| 147 |
+
'word_timestamps': result_data['word_timestamps'],
|
| 148 |
+
'translated_timestamps': result_data.get('translated_timestamps', []),
|
| 149 |
+
'audio_bytes': audio_bytes.hex(),
|
| 150 |
+
'original_suffix': '.wav',
|
| 151 |
+
'markers': markers,
|
| 152 |
+
'target_language': target_language,
|
| 153 |
+
'translation_enabled': enable_translation,
|
| 154 |
+
'translation_success': result_data.get('translation_success', False),
|
| 155 |
+
'language_detected': result_data.get('language_detected', 'unknown')
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
# Save comprehensive data to file
|
| 159 |
+
result_file_path = ""
|
| 160 |
+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".json", dir=".", encoding='utf-8') as json_file:
|
| 161 |
+
json.dump(transcription_data, json_file, ensure_ascii=False, indent=2)
|
| 162 |
+
result_file_path = json_file.name
|
| 163 |
+
logging.info(f"--- RECORD ENDPOINT: Enhanced transcription data saved to {result_file_path} ---")
|
| 164 |
+
|
| 165 |
+
# Prepare successful response
|
| 166 |
+
response_data = {
|
| 167 |
+
'success': True,
|
| 168 |
+
'original_text': result_data['original_text'],
|
| 169 |
+
'translated_text': result_data['translated_text'],
|
| 170 |
+
'file_path': result_file_path,
|
| 171 |
+
'markers': markers,
|
| 172 |
+
'target_language': target_language,
|
| 173 |
+
'translation_enabled': enable_translation,
|
| 174 |
+
'translation_success': result_data.get('translation_success', False),
|
| 175 |
+
'language_detected': result_data.get('language_detected', 'unknown')
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
print(f"๐ DEBUG RESPONSE DATA:")
|
| 179 |
+
print(f"- success: {response_data['success']}")
|
| 180 |
+
print(f"- original_text: {response_data['original_text']}")
|
| 181 |
+
print(f"- translated_text: {response_data['translated_text']}")
|
| 182 |
+
print(f"- target_language: {response_data['target_language']}")
|
| 183 |
+
print(f"- translation_enabled: {response_data['translation_enabled']}")
|
| 184 |
+
print(f"- translation_success: {response_data['translation_success']}")
|
| 185 |
+
print(f"- language_detected: {response_data['language_detected']}")
|
| 186 |
+
|
| 187 |
+
logging.info(f"--- RECORD ENDPOINT: Sending enhanced response with translation support ---")
|
| 188 |
+
return jsonify(response_data)
|
| 189 |
+
|
| 190 |
+
except Exception as e:
|
| 191 |
+
logging.error(f"--- RECORD ENDPOINT FATAL ERROR: {traceback.format_exc()} ---")
|
| 192 |
+
return jsonify({'success': False, 'error': str(e), 'trace': traceback.format_exc()}), 500
|
| 193 |
+
finally:
|
| 194 |
+
if tmp_file_path and os.path.exists(tmp_file_path):
|
| 195 |
+
os.unlink(tmp_file_path)
|
| 196 |
+
|
| 197 |
+
@app.route('/translate', methods=['POST', 'OPTIONS'])
|
| 198 |
+
def translate_text():
|
| 199 |
+
"""Endpoint for standalone text translation"""
|
| 200 |
+
if request.method == 'OPTIONS':
|
| 201 |
+
# flask-cors ุณุชุชููู ู
ุนุงูุฌุฉ CORS headers
|
| 202 |
+
return '', 204
|
| 203 |
+
|
| 204 |
+
try:
|
| 205 |
+
data = request.get_json()
|
| 206 |
+
if not data or 'text' not in data:
|
| 207 |
+
return jsonify({'success': False, 'error': 'No text provided for translation'})
|
| 208 |
+
|
| 209 |
+
text = data['text']
|
| 210 |
+
target_language = data.get('target_language', 'ar')
|
| 211 |
+
source_language = data.get('source_language', 'auto')
|
| 212 |
+
|
| 213 |
+
logging.info(f"--- TRANSLATE ENDPOINT: Translating text to {target_language} ---")
|
| 214 |
+
|
| 215 |
+
translator = get_translator()
|
| 216 |
+
if not translator:
|
| 217 |
+
return jsonify({'success': False, 'error': 'Translation service not available'})
|
| 218 |
+
|
| 219 |
+
translated_text, error = translator.translate_text(text, target_language, source_language)
|
| 220 |
+
|
| 221 |
+
if translated_text:
|
| 222 |
+
# Detect source language if auto
|
| 223 |
+
detected_lang = 'unknown'
|
| 224 |
+
if source_language == 'auto':
|
| 225 |
+
detected_lang, _ = translator.detect_language(text)
|
| 226 |
+
|
| 227 |
+
response_data = {
|
| 228 |
+
'success': True,
|
| 229 |
+
'original_text': text,
|
| 230 |
+
'translated_text': translated_text,
|
| 231 |
+
'source_language': detected_lang if source_language == 'auto' else source_language,
|
| 232 |
+
'target_language': target_language
|
| 233 |
+
}
|
| 234 |
+
logging.info(f"--- TRANSLATE ENDPOINT: Translation successful ---")
|
| 235 |
+
return jsonify(response_data)
|
| 236 |
+
else:
|
| 237 |
+
logging.error(f"--- TRANSLATE ENDPOINT: Translation failed: {error} ---")
|
| 238 |
+
return jsonify({'success': False, 'error': error})
|
| 239 |
+
|
| 240 |
+
except Exception as e:
|
| 241 |
+
logging.error(f"--- TRANSLATE ENDPOINT ERROR: {traceback.format_exc()} ---")
|
| 242 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 243 |
+
|
| 244 |
+
@app.route('/languages', methods=['GET'])
|
| 245 |
+
def get_supported_languages():
|
| 246 |
+
"""Get list of supported languages"""
|
| 247 |
+
try:
|
| 248 |
+
translator = get_translator()
|
| 249 |
+
if translator:
|
| 250 |
+
languages = translator.get_supported_languages()
|
| 251 |
+
return jsonify({'success': True, 'languages': languages})
|
| 252 |
+
else:
|
| 253 |
+
return jsonify({'success': False, 'error': 'Translation service not available'})
|
| 254 |
+
except Exception as e:
|
| 255 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 256 |
+
|
| 257 |
+
@app.route('/ui-translations/<language>', methods=['GET'])
|
| 258 |
+
def get_ui_translations(language):
|
| 259 |
+
"""Get UI translations for a specific language"""
|
| 260 |
+
try:
|
| 261 |
+
from translator import UI_TRANSLATIONS
|
| 262 |
+
translations = UI_TRANSLATIONS.get(language, UI_TRANSLATIONS['en'])
|
| 263 |
+
return jsonify({'success': True, 'translations': translations, 'language': language})
|
| 264 |
+
except Exception as e:
|
| 265 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 266 |
+
|
| 267 |
+
# ======= ูุธุงุฆู ุงูู
ูุฎุต ูุงูู
ูุงุญุธุงุช ุงูุฌุฏูุฏุฉ =======
|
| 268 |
+
|
| 269 |
+
@app.route('/summarize', methods=['POST', 'OPTIONS'])
|
| 270 |
+
def summarize_text():
|
| 271 |
+
"""ุฅูุดุงุก ู
ูุฎุต ุฐูู ู
ู ุงููุต"""
|
| 272 |
+
logging.info(f"--- SUMMARIZE ENDPOINT: {request.method} request received ---")
|
| 273 |
+
logging.info(f"--- SUMMARIZE ENDPOINT: Origin: {request.headers.get('Origin', 'None')} ---")
|
| 274 |
+
logging.info(f"--- SUMMARIZE ENDPOINT: Headers: {dict(request.headers)} ---")
|
| 275 |
+
|
| 276 |
+
if request.method == 'OPTIONS':
|
| 277 |
+
logging.info("--- SUMMARIZE ENDPOINT: Handling OPTIONS preflight request ---")
|
| 278 |
+
# ูุง ุญุงุฌุฉ ูุฅุนุฏุงุฏ headers ูุฏููุงูุ flask-cors ุณุชุชููู ุงูุฃู
ุฑ
|
| 279 |
+
return '', 204
|
| 280 |
+
|
| 281 |
+
try:
|
| 282 |
+
data = request.get_json()
|
| 283 |
+
if not data or 'text' not in data:
|
| 284 |
+
return jsonify({'success': False, 'error': 'ูู
ูุชู
ุชูููุฑ ูุต ููุชูุฎูุต'})
|
| 285 |
+
|
| 286 |
+
text = data['text']
|
| 287 |
+
language = data.get('language', 'ar')
|
| 288 |
+
subject = data.get('subject', '')
|
| 289 |
+
summary_type = data.get('type', 'full') # full, key_points, questions
|
| 290 |
+
|
| 291 |
+
logging.info(f"--- SUMMARIZE ENDPOINT: Creating {summary_type} summary ---")
|
| 292 |
+
|
| 293 |
+
if summary_type == 'key_points':
|
| 294 |
+
result, error = summarizer.extract_key_points(text, language)
|
| 295 |
+
result_key = 'key_points'
|
| 296 |
+
elif summary_type == 'questions':
|
| 297 |
+
result, error = summarizer.generate_review_questions(text, language)
|
| 298 |
+
result_key = 'questions'
|
| 299 |
+
elif summary_type == 'study_notes':
|
| 300 |
+
result, error = summarizer.generate_study_notes(text, subject, language)
|
| 301 |
+
result_key = 'study_notes'
|
| 302 |
+
else: # full summary
|
| 303 |
+
result, error = summarizer.generate_summary(text, language)
|
| 304 |
+
result_key = 'summary'
|
| 305 |
+
|
| 306 |
+
if result:
|
| 307 |
+
response_data = {
|
| 308 |
+
'success': True,
|
| 309 |
+
result_key: result,
|
| 310 |
+
'type': summary_type,
|
| 311 |
+
'language': language
|
| 312 |
+
}
|
| 313 |
+
logging.info(f"--- SUMMARIZE ENDPOINT: {summary_type} summary created successfully ---")
|
| 314 |
+
return jsonify(response_data)
|
| 315 |
+
else:
|
| 316 |
+
logging.error(f"--- SUMMARIZE ENDPOINT: Failed to create {summary_type}: {error} ---")
|
| 317 |
+
return jsonify({'success': False, 'error': error})
|
| 318 |
+
|
| 319 |
+
except Exception as e:
|
| 320 |
+
logging.error(f"--- SUMMARIZE ENDPOINT ERROR: {traceback.format_exc()} ---")
|
| 321 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 322 |
+
|
| 323 |
+
@app.route('/test-summarize', methods=['GET'])
|
| 324 |
+
def test_summarize():
|
| 325 |
+
"""ุงุฎุชุจุงุฑ ุจุณูุท ูู summarize endpoint"""
|
| 326 |
+
return jsonify({
|
| 327 |
+
'success': True,
|
| 328 |
+
'message': 'Summarize endpoint is working!',
|
| 329 |
+
'test_summary': {
|
| 330 |
+
'main_summary': 'This is a test summary',
|
| 331 |
+
'key_points': ['Point 1', 'Point 2', 'Point 3'],
|
| 332 |
+
'review_questions': ['Question 1?', 'Question 2?'],
|
| 333 |
+
'study_notes': 'These are test study notes'
|
| 334 |
+
}
|
| 335 |
+
})
|
| 336 |
+
|
| 337 |
+
@app.route('/notes', methods=['GET', 'POST', 'OPTIONS'])
|
| 338 |
+
def handle_notes():
|
| 339 |
+
"""ุงูุชุนุงู
ู ู
ุน ุงูู
ูุงุญุธุงุช - ุนุฑุถ ูุญูุธ"""
|
| 340 |
+
if request.method == 'OPTIONS':
|
| 341 |
+
# flask-cors ุณุชุชููู ู
ุนุงูุฌุฉ CORS headers
|
| 342 |
+
return '', 204
|
| 343 |
+
|
| 344 |
+
if request.method == 'GET':
|
| 345 |
+
try:
|
| 346 |
+
# ุงูุจุญุซ ุฃู ุนุฑุถ ุฌู
ูุน ุงูู
ูุงุญุธุงุช
|
| 347 |
+
search_query = request.args.get('search', '')
|
| 348 |
+
subject = request.args.get('subject', '')
|
| 349 |
+
limit = int(request.args.get('limit', 20))
|
| 350 |
+
|
| 351 |
+
if search_query:
|
| 352 |
+
notes = db.search_notes(search_query)
|
| 353 |
+
elif subject:
|
| 354 |
+
notes = db.get_notes_by_subject(subject)
|
| 355 |
+
else:
|
| 356 |
+
notes = db.get_all_notes(limit)
|
| 357 |
+
|
| 358 |
+
# ุฅุญุตุงุฆูุงุช ุณุฑูุนุฉ
|
| 359 |
+
subjects = db.get_subjects()
|
| 360 |
+
|
| 361 |
+
response = jsonify({
|
| 362 |
+
'success': True,
|
| 363 |
+
'notes': notes,
|
| 364 |
+
'subjects': subjects,
|
| 365 |
+
'total': len(notes)
|
| 366 |
+
})
|
| 367 |
+
return response
|
| 368 |
+
|
| 369 |
+
except Exception as e:
|
| 370 |
+
response = jsonify({'success': False, 'error': str(e)})
|
| 371 |
+
return response, 500
|
| 372 |
+
|
| 373 |
+
if request.method == 'POST':
|
| 374 |
+
try:
|
| 375 |
+
data = request.get_json()
|
| 376 |
+
|
| 377 |
+
# ุงูุชุญูู ู
ู ุงูุจูุงูุงุช ุงูู
ุทููุจุฉ
|
| 378 |
+
if not data or 'original_text' not in data:
|
| 379 |
+
return jsonify({'success': False, 'error': 'ุงูุจูุงูุงุช ุบูุฑ ู
ูุชู
ูุฉ'})
|
| 380 |
+
|
| 381 |
+
# ุญูุธ ุงูู
ูุงุญุธุฉ
|
| 382 |
+
note_id = db.save_lecture_note(data)
|
| 383 |
+
|
| 384 |
+
logging.info(f"--- NOTES ENDPOINT: Note saved with ID {note_id} ---")
|
| 385 |
+
|
| 386 |
+
response = jsonify({
|
| 387 |
+
'success': True,
|
| 388 |
+
'note_id': note_id,
|
| 389 |
+
'message': 'ุชู
ุญูุธ ุงูู
ูุงุญุธุฉ ุจูุฌุงุญ'
|
| 390 |
+
})
|
| 391 |
+
return response
|
| 392 |
+
|
| 393 |
+
except Exception as e:
|
| 394 |
+
logging.error(f"--- NOTES ENDPOINT ERROR: {traceback.format_exc()} ---")
|
| 395 |
+
response = jsonify({'success': False, 'error': str(e)})
|
| 396 |
+
return response, 500
|
| 397 |
+
|
| 398 |
+
@app.route('/notes/<int:note_id>', methods=['GET', 'PUT', 'DELETE', 'OPTIONS'])
|
| 399 |
+
def handle_single_note(note_id):
|
| 400 |
+
"""ุงูุชุนุงู
ู ู
ุน ู
ูุงุญุธุฉ ูุงุญุฏุฉ - ุนุฑุถุ ุชุญุฏูุซุ ุญุฐู"""
|
| 401 |
+
if request.method == 'OPTIONS':
|
| 402 |
+
# flask-cors ุณุชุชููู ู
ุนุงูุฌุฉ CORS headers
|
| 403 |
+
return '', 204
|
| 404 |
+
|
| 405 |
+
if request.method == 'GET':
|
| 406 |
+
try:
|
| 407 |
+
note = db.get_note_by_id(note_id)
|
| 408 |
+
if note:
|
| 409 |
+
return jsonify({'success': True, 'note': note})
|
| 410 |
+
else:
|
| 411 |
+
return jsonify({'success': False, 'error': 'ุงูู
ูุงุญุธุฉ ุบูุฑ ู
ูุฌูุฏุฉ'}), 404
|
| 412 |
+
|
| 413 |
+
except Exception as e:
|
| 414 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 415 |
+
|
| 416 |
+
if request.method == 'PUT':
|
| 417 |
+
try:
|
| 418 |
+
data = request.get_json()
|
| 419 |
+
if not data:
|
| 420 |
+
return jsonify({'success': False, 'error': 'ูู
ูุชู
ุชูููุฑ ุจูุงูุงุช ููุชุญุฏูุซ'})
|
| 421 |
+
|
| 422 |
+
success = db.update_note(note_id, data)
|
| 423 |
+
if success:
|
| 424 |
+
return jsonify({'success': True, 'message': 'ุชู
ุชุญุฏูุซ ุงูู
ูุงุญุธุฉ ุจูุฌุงุญ'})
|
| 425 |
+
else:
|
| 426 |
+
return jsonify({'success': False, 'error': 'ูุดู ูู ุชุญุฏูุซ ุงูู
ูุงุญุธุฉ'}), 400
|
| 427 |
+
|
| 428 |
+
except Exception as e:
|
| 429 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 430 |
+
|
| 431 |
+
if request.method == 'DELETE':
|
| 432 |
+
try:
|
| 433 |
+
success = db.delete_note(note_id)
|
| 434 |
+
if success:
|
| 435 |
+
return jsonify({'success': True, 'message': 'ุชู
ุญุฐู ุงูู
ูุงุญุธุฉ ุจูุฌุงุญ'})
|
| 436 |
+
else:
|
| 437 |
+
return jsonify({'success': False, 'error': 'ูุดู ูู ุญุฐู ุงูู
ูุงุญุธุฉ'}), 400
|
| 438 |
+
|
| 439 |
+
except Exception as e:
|
| 440 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 441 |
+
|
| 442 |
+
@app.route('/notes/subjects', methods=['GET'])
|
| 443 |
+
def get_subjects():
|
| 444 |
+
"""ุงุณุชุฑุฌุงุน ูุงุฆู
ุฉ ุงูู
ูุงุฏ ุงูุฏุฑุงุณูุฉ"""
|
| 445 |
+
try:
|
| 446 |
+
subjects = db.get_subjects()
|
| 447 |
+
return jsonify({'success': True, 'subjects': subjects})
|
| 448 |
+
except Exception as e:
|
| 449 |
+
return jsonify({'success': False, 'error': str(e)}), 500
|
| 450 |
+
|
| 451 |
+
if __name__ == '__main__':
|
| 452 |
+
app.run(port=5001)
|
requirements.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
google-generativeai>=0.8.0
|
| 3 |
+
librosa>=0.10.0
|
| 4 |
+
moviepy>=1.0.3
|
| 5 |
+
mutagen>=1.47.0
|
| 6 |
+
numpy>=1.24.0
|
| 7 |
+
streamlit>=1.28.0
|
| 8 |
+
altair>=5.0.0
|
| 9 |
+
python-dotenv>=1.0.0
|
| 10 |
+
Flask>=2.3.0
|
| 11 |
+
Flask-Cors>=4.0.0
|
| 12 |
+
soundfile>=0.12.0
|
| 13 |
+
# Additional dependencies for enhanced translation features
|
| 14 |
+
requests>=2.31.0
|
| 15 |
+
typing-extensions>=4.7.0
|
setup_enhanced.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SyncMaster Enhanced Setup Script
|
| 2 |
+
# ุชุดุบูู SyncMaster ุงูู
ุญุณู ู
ุน ุฏุนู
ุงูุชุฑุฌู
ุฉ
|
| 3 |
+
|
| 4 |
+
import subprocess
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
import time
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
def print_header():
|
| 11 |
+
print("=" * 60)
|
| 12 |
+
print("๐ต SyncMaster Enhanced - AI-Powered Translation Setup")
|
| 13 |
+
print("ู
ูุตุฉ ุงูู
ุฒุงู
ูุฉ ุงูุฐููุฉ ู
ุน ุฏุนู
ุงูุชุฑุฌู
ุฉ ุจุงูุฐูุงุก ุงูุงุตุทูุงุนู")
|
| 14 |
+
print("=" * 60)
|
| 15 |
+
|
| 16 |
+
def check_python_version():
|
| 17 |
+
"""Check if Python version is compatible"""
|
| 18 |
+
if sys.version_info < (3, 8):
|
| 19 |
+
print("โ Error: Python 3.8 or higher is required.")
|
| 20 |
+
print("ุฎุทุฃ: ูุชุทูุจ Python 3.8 ุฃู ุฃุญุฏุซ")
|
| 21 |
+
return False
|
| 22 |
+
print(f"โ
Python version: {sys.version}")
|
| 23 |
+
return True
|
| 24 |
+
|
| 25 |
+
def install_requirements():
|
| 26 |
+
"""Install required packages"""
|
| 27 |
+
print("\n๐ฆ Installing required packages...")
|
| 28 |
+
print("ุชุซุจูุช ุงูุญุฒู
ุงูู
ุทููุจุฉ...")
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
|
| 32 |
+
print("โ
All packages installed successfully!")
|
| 33 |
+
print("ุชู
ุชุซุจูุช ุฌู
ูุน ุงูุญุฒู
ุจูุฌุงุญ!")
|
| 34 |
+
return True
|
| 35 |
+
except subprocess.CalledProcessError as e:
|
| 36 |
+
print(f"โ Error installing packages: {e}")
|
| 37 |
+
print(f"ุฎุทุฃ ูู ุชุซุจูุช ุงูุญุฒู
: {e}")
|
| 38 |
+
return False
|
| 39 |
+
|
| 40 |
+
def check_env_file():
|
| 41 |
+
"""Check if .env file exists and has required keys"""
|
| 42 |
+
env_path = Path(".env")
|
| 43 |
+
if not env_path.exists():
|
| 44 |
+
print("โ .env file not found!")
|
| 45 |
+
print("ู
ูู .env ุบูุฑ ู
ูุฌูุฏ!")
|
| 46 |
+
create_env_file()
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
with open(env_path, 'r') as f:
|
| 50 |
+
content = f.read()
|
| 51 |
+
if "GEMINI_API_KEY" not in content:
|
| 52 |
+
print("โ GEMINI_API_KEY not found in .env file!")
|
| 53 |
+
print("ู
ูุชุงุญ GEMINI_API_KEY ุบูุฑ ู
ูุฌูุฏ ูู ู
ูู .env!")
|
| 54 |
+
return False
|
| 55 |
+
|
| 56 |
+
print("โ
Environment file configured correctly!")
|
| 57 |
+
print("ู
ูู ุงูุจูุฆุฉ ู
ูุนุฏู ุจุดูู ุตุญูุญ!")
|
| 58 |
+
return True
|
| 59 |
+
|
| 60 |
+
def create_env_file():
|
| 61 |
+
"""Create a template .env file"""
|
| 62 |
+
print("\n๐ Creating .env template file...")
|
| 63 |
+
print("ุฅูุดุงุก ู
ูู ูุงูุจ .env...")
|
| 64 |
+
|
| 65 |
+
env_content = """# SyncMaster Configuration
|
| 66 |
+
# ุฅุนุฏุงุฏุงุช SyncMaster
|
| 67 |
+
|
| 68 |
+
# Gemini AI API Key (Required for transcription and translation)
|
| 69 |
+
# ู
ูุชุงุญ Gemini AI (ู
ุทููุจ ูููุณุฎ ูุงูุชุฑุฌู
ุฉ)
|
| 70 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 71 |
+
|
| 72 |
+
# Optional: Set default language (en/ar)
|
| 73 |
+
# ุงุฎุชูุงุฑู: ุชุนููู ุงููุบุฉ ุงูุงูุชุฑุงุถูุฉ
|
| 74 |
+
DEFAULT_LANGUAGE=en
|
| 75 |
+
|
| 76 |
+
# Optional: Enable translation by default
|
| 77 |
+
# ุงุฎุชูุงุฑู: ุชูุนูู ุงูุชุฑุฌู
ุฉ ุงูุชุฑุงุถูุงู
|
| 78 |
+
ENABLE_TRANSLATION=true
|
| 79 |
+
|
| 80 |
+
# Optional: Default target language for translation
|
| 81 |
+
# ุงุฎุชูุงุฑู: ุงููุบุฉ ุงูู
ุณุชูุฏูุฉ ููุชุฑุฌู
ุฉ ุงูุชุฑุงุถูุงู
|
| 82 |
+
DEFAULT_TARGET_LANGUAGE=ar
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
with open(".env", "w", encoding="utf-8") as f:
|
| 86 |
+
f.write(env_content)
|
| 87 |
+
|
| 88 |
+
print("โ
.env template created!")
|
| 89 |
+
print("ุชู
ุฅูุดุงุก ูุงูุจ .env!")
|
| 90 |
+
print("\n๐ Please edit .env file and add your Gemini API key:")
|
| 91 |
+
print("ูุฑุฌู ุชุญุฑูุฑ ู
ูู .env ูุฅุถุงูุฉ ู
ูุชุงุญ Gemini API ุงูุฎุงุต ุจู:")
|
| 92 |
+
print("GEMINI_API_KEY=your_actual_api_key")
|
| 93 |
+
|
| 94 |
+
def start_recorder_server():
|
| 95 |
+
"""Start the Flask recorder server"""
|
| 96 |
+
print("\n๐ Starting recorder server...")
|
| 97 |
+
print("ุจุฏุก ุชุดุบูู ุฎุงุฏู
ุงูุชุณุฌูู...")
|
| 98 |
+
|
| 99 |
+
try:
|
| 100 |
+
# Start recorder server in background
|
| 101 |
+
process = subprocess.Popen([
|
| 102 |
+
sys.executable, "recorder_server.py"
|
| 103 |
+
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 104 |
+
|
| 105 |
+
print("โ
Recorder server started on http://localhost:5001")
|
| 106 |
+
print("ุชู
ุจุฏุก ุชุดุบูู ุฎุงุฏู
ุงูุชุณุฌูู ุนูู http://localhost:5001")
|
| 107 |
+
return process
|
| 108 |
+
except Exception as e:
|
| 109 |
+
print(f"โ Error starting recorder server: {e}")
|
| 110 |
+
print(f"ุฎุทุฃ ูู ุจุฏุก ุชุดุบูู ุฎุงุฏู
ุงูุชุณุฌูู: {e}")
|
| 111 |
+
return None
|
| 112 |
+
|
| 113 |
+
def start_streamlit_app():
|
| 114 |
+
"""Start the main Streamlit application"""
|
| 115 |
+
print("\n๐ Starting SyncMaster main application...")
|
| 116 |
+
print("ุจุฏุก ุชุดุบูู ุชุทุจูู SyncMaster ุงูุฑุฆูุณู...")
|
| 117 |
+
|
| 118 |
+
try:
|
| 119 |
+
subprocess.run([
|
| 120 |
+
sys.executable, "-m", "streamlit", "run", "app.py",
|
| 121 |
+
"--server.port", "8501",
|
| 122 |
+
"--server.address", "localhost"
|
| 123 |
+
])
|
| 124 |
+
except KeyboardInterrupt:
|
| 125 |
+
print("\n๐ Application stopped by user.")
|
| 126 |
+
print("ุชู
ุฅููุงู ุงูุชุทุจูู ุจูุงุณุทุฉ ุงูู
ุณุชุฎุฏู
.")
|
| 127 |
+
except Exception as e:
|
| 128 |
+
print(f"โ Error starting Streamlit app: {e}")
|
| 129 |
+
print(f"ุฎุทุฃ ูู ุจุฏุก ุชุดุบูู ุชุทุจูู Streamlit: {e}")
|
| 130 |
+
|
| 131 |
+
def print_usage_instructions():
|
| 132 |
+
"""Print usage instructions"""
|
| 133 |
+
print("\n๐ Usage Instructions / ุชุนููู
ุงุช ุงูุงุณุชุฎุฏุงู
:")
|
| 134 |
+
print("-" * 40)
|
| 135 |
+
print("1. Open your browser and go to: http://localhost:8501")
|
| 136 |
+
print(" ุงูุชุญ ู
ุชุตูุญู ูุงุฐูุจ ุฅูู: http://localhost:8501")
|
| 137 |
+
print("\n2. For recording, the recorder interface is at: http://localhost:5001")
|
| 138 |
+
print(" ููุชุณุฌููุ ูุงุฌูุฉ ุงูุชุณุฌูู ู
ุชุงุญุฉ ุนูู: http://localhost:5001")
|
| 139 |
+
print("\n3. Choose your language (English/ุงูุนุฑุจูุฉ) from the sidebar")
|
| 140 |
+
print(" ุงุฎุชุฑ ูุบุชู (English/ุงูุนุฑุจูุฉ) ู
ู ุงูุดุฑูุท ุงูุฌุงูุจู")
|
| 141 |
+
print("\n4. Enable translation and select target language")
|
| 142 |
+
print(" ูุนูู ุงูุชุฑุฌู
ุฉ ูุงุฎุชุฑ ุงููุบุฉ ุงูู
ุณุชูุฏูุฉ")
|
| 143 |
+
print("\n5. Upload audio or record directly from microphone")
|
| 144 |
+
print(" ุงุฑูุน ู
ูู ุตูุชู ุฃู ุณุฌู ู
ุจุงุดุฑุฉ ู
ู ุงูู
ููุฑูููู")
|
| 145 |
+
print("\n๐ For detailed instructions, see README_AR.md")
|
| 146 |
+
print("ููุชุนููู
ุงุช ุงูู
ูุตูุฉุ ุฑุงุฌุน ู
ูู README_AR.md")
|
| 147 |
+
|
| 148 |
+
def main():
|
| 149 |
+
"""Main setup function"""
|
| 150 |
+
print_header()
|
| 151 |
+
|
| 152 |
+
# Check Python version
|
| 153 |
+
if not check_python_version():
|
| 154 |
+
return
|
| 155 |
+
|
| 156 |
+
# Install requirements
|
| 157 |
+
if not install_requirements():
|
| 158 |
+
return
|
| 159 |
+
|
| 160 |
+
# Check environment configuration
|
| 161 |
+
if not check_env_file():
|
| 162 |
+
print("\nโ ๏ธ Please configure your .env file before running the application.")
|
| 163 |
+
print("ูุฑุฌู ุฅุนุฏุงุฏ ู
ูู .env ูุจู ุชุดุบูู ุงูุชุทุจูู.")
|
| 164 |
+
return
|
| 165 |
+
|
| 166 |
+
print("\n๐ Setup completed successfully!")
|
| 167 |
+
print("ุชู
ุงูุฅุนุฏุงุฏ ุจูุฌุงุญ!")
|
| 168 |
+
|
| 169 |
+
print_usage_instructions()
|
| 170 |
+
|
| 171 |
+
# Ask user if they want to start the application
|
| 172 |
+
print("\n" + "=" * 60)
|
| 173 |
+
start_now = input("Start SyncMaster now? (y/n) / ุชุดุบูู SyncMaster ุงูุขูุ (y/n): ").lower().strip()
|
| 174 |
+
|
| 175 |
+
if start_now in ['y', 'yes', 'ูุนู
']:
|
| 176 |
+
# Start recorder server
|
| 177 |
+
recorder_process = start_recorder_server()
|
| 178 |
+
|
| 179 |
+
# Wait a moment for server to start
|
| 180 |
+
time.sleep(2)
|
| 181 |
+
|
| 182 |
+
# Start main application
|
| 183 |
+
try:
|
| 184 |
+
start_streamlit_app()
|
| 185 |
+
finally:
|
| 186 |
+
# Clean up recorder server
|
| 187 |
+
if recorder_process:
|
| 188 |
+
recorder_process.terminate()
|
| 189 |
+
print("\n๐งน Cleaning up processes...")
|
| 190 |
+
print("ุชูุธูู ุงูุนู
ููุงุช...")
|
| 191 |
+
else:
|
| 192 |
+
print("\n๐ Setup complete. Run 'python setup_enhanced.py' when ready.")
|
| 193 |
+
print("ุงูุฅุนุฏุงุฏ ู
ูุชู
ู. ุดุบูู 'python setup_enhanced.py' ุนูุฏู
ุง ุชููู ุฌุงูุฒุงู.")
|
| 194 |
+
|
| 195 |
+
if __name__ == "__main__":
|
| 196 |
+
main()
|
start_debug.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Enhanced startup script for SyncMaster with debugging capabilities
|
| 4 |
+
ูุต ุจุฏุก ุงูุชุดุบูู ุงูู
ุญุณู ูู SyncMaster ู
ุน ูุฏุฑุงุช ุงูุชุชุจุน
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
import time
|
| 10 |
+
import socket
|
| 11 |
+
import subprocess
|
| 12 |
+
import psutil
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
def check_port_available(port):
|
| 16 |
+
"""Check if a port is available"""
|
| 17 |
+
try:
|
| 18 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| 19 |
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 20 |
+
s.bind(('localhost', port))
|
| 21 |
+
return True
|
| 22 |
+
except:
|
| 23 |
+
return False
|
| 24 |
+
|
| 25 |
+
def kill_processes_on_port(port):
|
| 26 |
+
"""Kill processes using a specific port"""
|
| 27 |
+
try:
|
| 28 |
+
for proc in psutil.process_iter(['pid', 'name', 'connections']):
|
| 29 |
+
try:
|
| 30 |
+
connections = proc.info['connections']
|
| 31 |
+
if connections:
|
| 32 |
+
for conn in connections:
|
| 33 |
+
if conn.laddr.port == port:
|
| 34 |
+
print(f"๐ Killing process {proc.info['name']} (PID: {proc.info['pid']}) using port {port}")
|
| 35 |
+
proc.kill()
|
| 36 |
+
time.sleep(1)
|
| 37 |
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
| 38 |
+
continue
|
| 39 |
+
except Exception as e:
|
| 40 |
+
print(f"โ ๏ธ Error killing processes on port {port}: {e}")
|
| 41 |
+
|
| 42 |
+
def check_dependencies():
|
| 43 |
+
"""Check if required dependencies are installed"""
|
| 44 |
+
required_packages = [
|
| 45 |
+
'streamlit', 'flask', 'librosa', 'soundfile',
|
| 46 |
+
'google-generativeai', 'python-dotenv'
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
missing_packages = []
|
| 50 |
+
for package in required_packages:
|
| 51 |
+
try:
|
| 52 |
+
__import__(package.replace('-', '_'))
|
| 53 |
+
except ImportError:
|
| 54 |
+
missing_packages.append(package)
|
| 55 |
+
|
| 56 |
+
if missing_packages:
|
| 57 |
+
print(f"โ Missing packages: {', '.join(missing_packages)}")
|
| 58 |
+
print("๐ฆ Installing missing packages...")
|
| 59 |
+
subprocess.run([sys.executable, '-m', 'pip', 'install'] + missing_packages)
|
| 60 |
+
return False
|
| 61 |
+
return True
|
| 62 |
+
|
| 63 |
+
def check_env_file():
|
| 64 |
+
"""Check if .env file exists and has required keys"""
|
| 65 |
+
env_path = Path('.env')
|
| 66 |
+
if not env_path.exists():
|
| 67 |
+
print("โ .env file not found!")
|
| 68 |
+
print("๐ Creating sample .env file...")
|
| 69 |
+
with open('.env', 'w') as f:
|
| 70 |
+
f.write("GEMINI_API_KEY=your_api_key_here\n")
|
| 71 |
+
print("โ
Please add your Gemini API key to .env file")
|
| 72 |
+
return False
|
| 73 |
+
|
| 74 |
+
# Check if API key is set
|
| 75 |
+
try:
|
| 76 |
+
from dotenv import load_dotenv
|
| 77 |
+
load_dotenv()
|
| 78 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 79 |
+
if not api_key or api_key == "your_api_key_here":
|
| 80 |
+
print("โ ๏ธ GEMINI_API_KEY not properly set in .env file")
|
| 81 |
+
return False
|
| 82 |
+
except Exception as e:
|
| 83 |
+
print(f"โ Error reading .env file: {e}")
|
| 84 |
+
return False
|
| 85 |
+
|
| 86 |
+
return True
|
| 87 |
+
|
| 88 |
+
def start_recorder_server():
|
| 89 |
+
"""Start the recorder server"""
|
| 90 |
+
print("๐๏ธ Starting recorder server...")
|
| 91 |
+
|
| 92 |
+
# Kill any existing processes on port 5001
|
| 93 |
+
if not check_port_available(5001):
|
| 94 |
+
print("๐ Port 5001 is busy, killing existing processes...")
|
| 95 |
+
kill_processes_on_port(5001)
|
| 96 |
+
time.sleep(2)
|
| 97 |
+
|
| 98 |
+
if check_port_available(5001):
|
| 99 |
+
try:
|
| 100 |
+
# Start recorder server
|
| 101 |
+
server_process = subprocess.Popen(
|
| 102 |
+
[sys.executable, 'recorder_server.py'],
|
| 103 |
+
stdout=subprocess.PIPE,
|
| 104 |
+
stderr=subprocess.PIPE,
|
| 105 |
+
creationflags=subprocess.CREATE_NEW_CONSOLE if os.name == 'nt' else 0
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
# Wait for server to start
|
| 109 |
+
time.sleep(3)
|
| 110 |
+
|
| 111 |
+
# Test server connection
|
| 112 |
+
import requests
|
| 113 |
+
try:
|
| 114 |
+
response = requests.get('http://localhost:5001/record', timeout=5)
|
| 115 |
+
if response.status_code == 200:
|
| 116 |
+
print("โ
Recorder server started successfully on port 5001")
|
| 117 |
+
return server_process
|
| 118 |
+
else:
|
| 119 |
+
raise Exception(f"Server responded with status {response.status_code}")
|
| 120 |
+
except Exception as e:
|
| 121 |
+
print(f"โ Failed to connect to recorder server: {e}")
|
| 122 |
+
server_process.terminate()
|
| 123 |
+
return None
|
| 124 |
+
|
| 125 |
+
except Exception as e:
|
| 126 |
+
print(f"โ Failed to start recorder server: {e}")
|
| 127 |
+
return None
|
| 128 |
+
else:
|
| 129 |
+
print("โ Port 5001 is still not available")
|
| 130 |
+
return None
|
| 131 |
+
|
| 132 |
+
def start_main_app():
|
| 133 |
+
"""Start the main Streamlit application"""
|
| 134 |
+
print("๐ Starting main SyncMaster application...")
|
| 135 |
+
|
| 136 |
+
# Find available port for Streamlit
|
| 137 |
+
streamlit_port = 8501
|
| 138 |
+
while not check_port_available(streamlit_port) and streamlit_port < 8510:
|
| 139 |
+
streamlit_port += 1
|
| 140 |
+
|
| 141 |
+
if streamlit_port >= 8510:
|
| 142 |
+
print("โ No available ports for Streamlit (tried 8501-8509)")
|
| 143 |
+
return None
|
| 144 |
+
|
| 145 |
+
try:
|
| 146 |
+
# Start Streamlit app
|
| 147 |
+
subprocess.run([
|
| 148 |
+
sys.executable, '-m', 'streamlit', 'run', 'app.py',
|
| 149 |
+
'--server.port', str(streamlit_port),
|
| 150 |
+
'--server.address', 'localhost',
|
| 151 |
+
'--browser.gatherUsageStats', 'false'
|
| 152 |
+
])
|
| 153 |
+
except KeyboardInterrupt:
|
| 154 |
+
print("\n๐ Application stopped by user")
|
| 155 |
+
except Exception as e:
|
| 156 |
+
print(f"โ Failed to start main application: {e}")
|
| 157 |
+
|
| 158 |
+
def main():
|
| 159 |
+
"""Main startup function"""
|
| 160 |
+
print("=" * 60)
|
| 161 |
+
print("๐ต SyncMaster Enhanced - Startup Script")
|
| 162 |
+
print("ู
ูุตุฉ ุงูู
ุฒุงู
ูุฉ ุงูุฐููุฉ - ุณูุฑูุจุช ุงูุจุฏุก")
|
| 163 |
+
print("=" * 60)
|
| 164 |
+
|
| 165 |
+
# Change to script directory
|
| 166 |
+
script_dir = Path(__file__).parent
|
| 167 |
+
os.chdir(script_dir)
|
| 168 |
+
print(f"๐ Working directory: {script_dir}")
|
| 169 |
+
|
| 170 |
+
# Step 1: Check dependencies
|
| 171 |
+
print("\n๐ฆ Checking dependencies...")
|
| 172 |
+
if not check_dependencies():
|
| 173 |
+
print("โ Please restart after installing dependencies")
|
| 174 |
+
return
|
| 175 |
+
print("โ
All dependencies available")
|
| 176 |
+
|
| 177 |
+
# Step 2: Check environment file
|
| 178 |
+
print("\n๐ Checking environment configuration...")
|
| 179 |
+
if not check_env_file():
|
| 180 |
+
print("โ Please configure .env file and restart")
|
| 181 |
+
return
|
| 182 |
+
print("โ
Environment configuration OK")
|
| 183 |
+
|
| 184 |
+
# Step 3: Start recorder server
|
| 185 |
+
print("\n๐๏ธ Starting recording server...")
|
| 186 |
+
server_process = start_recorder_server()
|
| 187 |
+
if not server_process:
|
| 188 |
+
print("โ Failed to start recorder server")
|
| 189 |
+
return
|
| 190 |
+
|
| 191 |
+
# Step 4: Start main application
|
| 192 |
+
print("\n๐ Starting web interface...")
|
| 193 |
+
print("๐ฑ The application will open in your browser")
|
| 194 |
+
print("๐๏ธ Recording interface: http://localhost:5001")
|
| 195 |
+
print("๐ป Main interface: http://localhost:8501")
|
| 196 |
+
print("\nPress Ctrl+C to stop all services")
|
| 197 |
+
|
| 198 |
+
try:
|
| 199 |
+
start_main_app()
|
| 200 |
+
finally:
|
| 201 |
+
# Cleanup
|
| 202 |
+
print("\n๐งน Cleaning up...")
|
| 203 |
+
if server_process:
|
| 204 |
+
server_process.terminate()
|
| 205 |
+
print("โ
Recorder server stopped")
|
| 206 |
+
print("๐ Goodbye!")
|
| 207 |
+
|
| 208 |
+
if __name__ == "__main__":
|
| 209 |
+
main()
|
start_enhanced.bat
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo ===============================================
|
| 3 |
+
echo SyncMaster Enhanced - Quick Start
|
| 4 |
+
echo ู
ูุตุฉ ุงูู
ุฒุงู
ูุฉ ุงูุฐููุฉ - ุงูุจุฏุก ุงูุณุฑูุน
|
| 5 |
+
echo ===============================================
|
| 6 |
+
echo.
|
| 7 |
+
|
| 8 |
+
echo โ
All system tests passed! / ุฌู
ูุน ุงูุงุฎุชุจุงุฑุงุช ูุฌุญุช!
|
| 9 |
+
echo ๐ Starting SyncMaster Enhanced...
|
| 10 |
+
echo.
|
| 11 |
+
|
| 12 |
+
REM Check if Python is installed
|
| 13 |
+
python --version >nul 2>&1
|
| 14 |
+
if errorlevel 1 (
|
| 15 |
+
echo โ ERROR: Python is not installed or not in PATH
|
| 16 |
+
echo ุฎุทุฃ: Python ุบูุฑ ู
ุซุจุช ุฃู ุบูุฑ ู
ูุฌูุฏ ูู PATH
|
| 17 |
+
echo Please install Python 3.8+ from python.org
|
| 18 |
+
pause
|
| 19 |
+
exit /b 1
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
REM Check if .env file exists
|
| 23 |
+
if not exist ".env" (
|
| 24 |
+
echo โ ๏ธ WARNING: .env file not found!
|
| 25 |
+
echo ุชุญุฐูุฑ: ู
ูู .env ุบูุฑ ู
ูุฌูุฏ!
|
| 26 |
+
echo Creating sample .env file...
|
| 27 |
+
echo GEMINI_API_KEY=your_api_key_here > .env
|
| 28 |
+
echo Please add your Gemini API key to .env file
|
| 29 |
+
echo ูุฑุฌู ุฅุถุงูุฉ ู
ูุชุงุญ Gemini API ุฅูู ู
ูู .env
|
| 30 |
+
pause
|
| 31 |
+
exit /b 1
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
echo ๐ Running system test...
|
| 35 |
+
echo ุชุดุบูู ุงุฎุชุจุงุฑ ุงููุธุงู
...
|
| 36 |
+
python test_system.py
|
| 37 |
+
if errorlevel 1 (
|
| 38 |
+
echo โ System test failed! Please fix issues first.
|
| 39 |
+
echo ูุดู ุงุฎุชุจุงุฑ ุงููุธุงู
! ูุฑุฌู ุฅุตูุงุญ ุงูู
ุดุงูู ุฃููุงู.
|
| 40 |
+
pause
|
| 41 |
+
exit /b 1
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
echo.
|
| 45 |
+
echo โ
System test passed! Starting application...
|
| 46 |
+
echo ูุฌุญ ุงุฎุชุจุงุฑ ุงููุธุงู
! ุจุฏุก ุชุดุบูู ุงูุชุทุจูู...
|
| 47 |
+
echo.
|
| 48 |
+
|
| 49 |
+
REM Use debug startup for better error handling
|
| 50 |
+
echo ๐ Starting with advanced debugging...
|
| 51 |
+
echo ุจุฏุก ุงูุชุดุบูู ู
ุน ุงูุชุดุฎูุต ุงูู
ุชูุฏู
...
|
| 52 |
+
python start_debug.py
|
| 53 |
+
|
| 54 |
+
echo.
|
| 55 |
+
echo ๐ Application stopped. Press any key to exit.
|
| 56 |
+
echo ุชู
ุฅููุงู ุงูุชุทุจูู. ุงุถุบุท ุฃู ุฒุฑ ููุฎุฑูุฌ.
|
| 57 |
+
pause
|
startup.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Startup Script for SyncMaster
|
| 4 |
+
ููุทุฉ ุฏุฎูู ู
ูุญุฏุฉ ุชุถู
ู ุชุดุบูู ุฌู
ูุน ุงูู
ูููุงุช ุงูู
ุทููุจุฉ
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
import time
|
| 10 |
+
import logging
|
| 11 |
+
import subprocess
|
| 12 |
+
import signal
|
| 13 |
+
import atexit
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
# Configure logging
|
| 17 |
+
logging.basicConfig(
|
| 18 |
+
level=logging.INFO,
|
| 19 |
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
class SyncMasterLauncher:
|
| 23 |
+
def __init__(self):
|
| 24 |
+
self.recorder_process = None
|
| 25 |
+
self.streamlit_process = None
|
| 26 |
+
self.cleanup_registered = False
|
| 27 |
+
|
| 28 |
+
def setup_cleanup(self):
|
| 29 |
+
"""Setup cleanup handlers"""
|
| 30 |
+
if not self.cleanup_registered:
|
| 31 |
+
atexit.register(self.cleanup)
|
| 32 |
+
signal.signal(signal.SIGINT, self.signal_handler)
|
| 33 |
+
signal.signal(signal.SIGTERM, self.signal_handler)
|
| 34 |
+
self.cleanup_registered = True
|
| 35 |
+
|
| 36 |
+
def signal_handler(self, signum, frame):
|
| 37 |
+
"""Handle termination signals"""
|
| 38 |
+
logging.info(f"Received signal {signum}, cleaning up...")
|
| 39 |
+
self.cleanup()
|
| 40 |
+
sys.exit(0)
|
| 41 |
+
|
| 42 |
+
def cleanup(self):
|
| 43 |
+
"""Clean up all processes"""
|
| 44 |
+
logging.info("๐งน Cleaning up processes...")
|
| 45 |
+
|
| 46 |
+
if self.recorder_process and self.recorder_process.poll() is None:
|
| 47 |
+
try:
|
| 48 |
+
self.recorder_process.terminate()
|
| 49 |
+
self.recorder_process.wait(timeout=5)
|
| 50 |
+
logging.info("โ
Recorder server terminated")
|
| 51 |
+
except:
|
| 52 |
+
try:
|
| 53 |
+
self.recorder_process.kill()
|
| 54 |
+
logging.info("โ ๏ธ Recorder server killed")
|
| 55 |
+
except:
|
| 56 |
+
pass
|
| 57 |
+
|
| 58 |
+
if self.streamlit_process and self.streamlit_process.poll() is None:
|
| 59 |
+
try:
|
| 60 |
+
self.streamlit_process.terminate()
|
| 61 |
+
self.streamlit_process.wait(timeout=5)
|
| 62 |
+
logging.info("โ
Streamlit server terminated")
|
| 63 |
+
except:
|
| 64 |
+
try:
|
| 65 |
+
self.streamlit_process.kill()
|
| 66 |
+
logging.info("โ ๏ธ Streamlit server killed")
|
| 67 |
+
except:
|
| 68 |
+
pass
|
| 69 |
+
|
| 70 |
+
def start_recorder_server(self):
|
| 71 |
+
"""Start the recorder server"""
|
| 72 |
+
try:
|
| 73 |
+
logging.info("๐ Starting recorder server...")
|
| 74 |
+
self.recorder_process = subprocess.Popen(
|
| 75 |
+
[sys.executable, 'recorder_server.py'],
|
| 76 |
+
stdout=subprocess.PIPE,
|
| 77 |
+
stderr=subprocess.PIPE
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
# Wait for server to start
|
| 81 |
+
time.sleep(3)
|
| 82 |
+
|
| 83 |
+
# Check if process is running
|
| 84 |
+
if self.recorder_process.poll() is None:
|
| 85 |
+
# Verify server is responding
|
| 86 |
+
try:
|
| 87 |
+
import requests
|
| 88 |
+
response = requests.get('http://localhost:5001/record', timeout=5)
|
| 89 |
+
if response.status_code == 200:
|
| 90 |
+
logging.info("โ
Recorder server started successfully on port 5001")
|
| 91 |
+
return True
|
| 92 |
+
else:
|
| 93 |
+
logging.warning(f"โ ๏ธ Recorder server responded with status: {response.status_code}")
|
| 94 |
+
except Exception as e:
|
| 95 |
+
logging.warning(f"โ ๏ธ Could not verify recorder server: {e}")
|
| 96 |
+
|
| 97 |
+
# Server process is running even if verification failed
|
| 98 |
+
return True
|
| 99 |
+
else:
|
| 100 |
+
logging.error("โ Recorder server process failed to start")
|
| 101 |
+
return False
|
| 102 |
+
|
| 103 |
+
except Exception as e:
|
| 104 |
+
logging.error(f"โ Failed to start recorder server: {e}")
|
| 105 |
+
return False
|
| 106 |
+
|
| 107 |
+
def start_streamlit_app(self, port=5050, host="0.0.0.0"):
|
| 108 |
+
"""Start the Streamlit application"""
|
| 109 |
+
try:
|
| 110 |
+
logging.info(f"๐ Starting Streamlit app on {host}:{port}...")
|
| 111 |
+
|
| 112 |
+
cmd = [
|
| 113 |
+
sys.executable, '-m', 'streamlit', 'run', 'app.py',
|
| 114 |
+
'--server.port', str(port),
|
| 115 |
+
'--server.address', host,
|
| 116 |
+
'--server.headless', 'true',
|
| 117 |
+
'--browser.gatherUsageStats', 'false'
|
| 118 |
+
]
|
| 119 |
+
|
| 120 |
+
self.streamlit_process = subprocess.Popen(cmd)
|
| 121 |
+
|
| 122 |
+
# Wait a bit for Streamlit to start
|
| 123 |
+
time.sleep(5)
|
| 124 |
+
|
| 125 |
+
if self.streamlit_process.poll() is None:
|
| 126 |
+
logging.info(f"โ
Streamlit app started successfully on http://{host}:{port}")
|
| 127 |
+
return True
|
| 128 |
+
else:
|
| 129 |
+
logging.error("โ Streamlit app failed to start")
|
| 130 |
+
return False
|
| 131 |
+
|
| 132 |
+
except Exception as e:
|
| 133 |
+
logging.error(f"โ Failed to start Streamlit app: {e}")
|
| 134 |
+
return False
|
| 135 |
+
|
| 136 |
+
def launch_integrated(self):
|
| 137 |
+
"""Launch with integrated server (recommended for HuggingFace)"""
|
| 138 |
+
logging.info("๐ Launching SyncMaster with integrated server...")
|
| 139 |
+
self.setup_cleanup()
|
| 140 |
+
|
| 141 |
+
# Start Streamlit with integrated server
|
| 142 |
+
try:
|
| 143 |
+
# Import to trigger integrated server startup
|
| 144 |
+
import app
|
| 145 |
+
|
| 146 |
+
# Run Streamlit
|
| 147 |
+
import streamlit.web.cli as stcli
|
| 148 |
+
import sys
|
| 149 |
+
|
| 150 |
+
# Set command line arguments for Streamlit
|
| 151 |
+
sys.argv = [
|
| 152 |
+
"streamlit", "run", "app.py",
|
| 153 |
+
"--server.port", "5050",
|
| 154 |
+
"--server.address", "0.0.0.0",
|
| 155 |
+
"--server.headless", "true",
|
| 156 |
+
"--browser.gatherUsageStats", "false"
|
| 157 |
+
]
|
| 158 |
+
|
| 159 |
+
# Run Streamlit CLI
|
| 160 |
+
stcli.main()
|
| 161 |
+
|
| 162 |
+
except Exception as e:
|
| 163 |
+
logging.error(f"โ Failed to launch integrated mode: {e}")
|
| 164 |
+
return False
|
| 165 |
+
|
| 166 |
+
def launch_separate(self):
|
| 167 |
+
"""Launch with separate processes (development mode)"""
|
| 168 |
+
logging.info("๐ Launching SyncMaster with separate processes...")
|
| 169 |
+
self.setup_cleanup()
|
| 170 |
+
|
| 171 |
+
# Start recorder server first
|
| 172 |
+
if not self.start_recorder_server():
|
| 173 |
+
logging.error("โ Failed to start recorder server, aborting...")
|
| 174 |
+
return False
|
| 175 |
+
|
| 176 |
+
# Start Streamlit app
|
| 177 |
+
if not self.start_streamlit_app():
|
| 178 |
+
logging.error("โ Failed to start Streamlit app, aborting...")
|
| 179 |
+
self.cleanup()
|
| 180 |
+
return False
|
| 181 |
+
|
| 182 |
+
logging.info("โ
All services started successfully!")
|
| 183 |
+
logging.info("๐ Access the application at: http://localhost:5050")
|
| 184 |
+
logging.info("๐๏ธ Recorder API available at: http://localhost:5001")
|
| 185 |
+
|
| 186 |
+
try:
|
| 187 |
+
# Keep the main process alive
|
| 188 |
+
while True:
|
| 189 |
+
time.sleep(1)
|
| 190 |
+
|
| 191 |
+
# Check if processes are still running
|
| 192 |
+
if self.recorder_process and self.recorder_process.poll() is not None:
|
| 193 |
+
logging.error("โ Recorder server process died")
|
| 194 |
+
break
|
| 195 |
+
|
| 196 |
+
if self.streamlit_process and self.streamlit_process.poll() is not None:
|
| 197 |
+
logging.error("โ Streamlit app process died")
|
| 198 |
+
break
|
| 199 |
+
|
| 200 |
+
except KeyboardInterrupt:
|
| 201 |
+
logging.info("๐ Shutting down...")
|
| 202 |
+
finally:
|
| 203 |
+
self.cleanup()
|
| 204 |
+
|
| 205 |
+
def main():
|
| 206 |
+
"""Main entry point"""
|
| 207 |
+
launcher = SyncMasterLauncher()
|
| 208 |
+
|
| 209 |
+
# Check if running in HuggingFace or similar environment
|
| 210 |
+
if os.getenv('SPACE_ID') or '--integrated' in sys.argv:
|
| 211 |
+
# Use integrated mode for cloud deployments
|
| 212 |
+
launcher.launch_integrated()
|
| 213 |
+
else:
|
| 214 |
+
# Use separate processes for local development
|
| 215 |
+
launcher.launch_separate()
|
| 216 |
+
|
| 217 |
+
if __name__ == "__main__":
|
| 218 |
+
main()
|
summarizer.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# summarizer.py - ูุธุงู
ุงูู
ูุฎุต ุงูุฐูู ููู
ุญุงุถุฑุงุช
|
| 2 |
+
|
| 3 |
+
from translator import get_translator
|
| 4 |
+
from typing import Tuple, List, Dict, Optional
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
+
class LectureSummarizer:
|
| 8 |
+
"""ูุธุงู
ุงูู
ูุฎุต ุงูุฐูู ููู
ุญุงุถุฑุงุช ุงูุฏุฑุงุณูุฉ"""
|
| 9 |
+
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.translator = get_translator()
|
| 12 |
+
|
| 13 |
+
def generate_summary(self, text: str, language: str = 'ar') -> Tuple[Optional[str], Optional[str]]:
|
| 14 |
+
"""
|
| 15 |
+
ุฅูุดุงุก ู
ูุฎุต ุฐูู ูููุต
|
| 16 |
+
|
| 17 |
+
Args:
|
| 18 |
+
text: ุงููุต ุงูู
ุฑุงุฏ ุชูุฎูุตู
|
| 19 |
+
language: ูุบุฉ ุงูู
ูุฎุต ('ar' ููุนุฑุจูุฉุ 'en' ููุฅูุฌููุฒูุฉ)
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
Tuple of (summary, error_message)
|
| 23 |
+
"""
|
| 24 |
+
if not self.translator or not self.translator.client:
|
| 25 |
+
return None, "ุฎุฏู
ุฉ ุงูุฐูุงุก ุงูุงุตุทูุงุนู ุบูุฑ ู
ุชููุฑุฉ"
|
| 26 |
+
|
| 27 |
+
if not text or len(text.strip()) < 50:
|
| 28 |
+
return None, "ุงููุต ูุตูุฑ ุฌุฏุงู ูุฅูุดุงุก ู
ูุฎุต ู
ููุฏ"
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
prompt = self._create_summary_prompt(text, language)
|
| 32 |
+
|
| 33 |
+
response = self.translator.client.models.generate_content(
|
| 34 |
+
model="gemini-2.5-flash",
|
| 35 |
+
contents=[prompt]
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
if response and response.text:
|
| 39 |
+
summary = response.text.strip()
|
| 40 |
+
summary = self._clean_summary_output(summary)
|
| 41 |
+
return summary, None
|
| 42 |
+
else:
|
| 43 |
+
return None, "ูุดู ูู ุฅูุดุงุก ุงูู
ูุฎุต"
|
| 44 |
+
|
| 45 |
+
except Exception as e:
|
| 46 |
+
return None, f"ุฎุทุฃ ูู ุฅูุดุงุก ุงูู
ูุฎุต: {str(e)}"
|
| 47 |
+
|
| 48 |
+
def extract_key_points(self, text: str, language: str = 'ar') -> Tuple[Optional[List[str]], Optional[str]]:
|
| 49 |
+
"""
|
| 50 |
+
ุงุณุชุฎุฑุงุฌ ุงูููุงุท ุงูุฑุฆูุณูุฉ ู
ู ุงููุต
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
text: ุงููุต ุงูู
ุฑุงุฏ ุงุณุชุฎุฑุงุฌ ุงูููุงุท ู
ูู
|
| 54 |
+
language: ูุบุฉ ุงูููุงุท
|
| 55 |
+
|
| 56 |
+
Returns:
|
| 57 |
+
Tuple of (key_points_list, error_message)
|
| 58 |
+
"""
|
| 59 |
+
if not self.translator or not self.translator.client:
|
| 60 |
+
return None, "ุฎุฏู
ุฉ ุงูุฐูุงุก ุงูุงุตุทูุงุนู ุบูุฑ ู
ุชููุฑุฉ"
|
| 61 |
+
|
| 62 |
+
try:
|
| 63 |
+
prompt = self._create_key_points_prompt(text, language)
|
| 64 |
+
|
| 65 |
+
response = self.translator.client.models.generate_content(
|
| 66 |
+
model="gemini-2.5-flash",
|
| 67 |
+
contents=[prompt]
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
if response and response.text:
|
| 71 |
+
key_points_text = response.text.strip()
|
| 72 |
+
key_points = self._parse_key_points(key_points_text)
|
| 73 |
+
return key_points, None
|
| 74 |
+
else:
|
| 75 |
+
return None, "ูุดู ูู ุงุณุชุฎุฑุงุฌ ุงูููุงุท ุงูุฑุฆูุณูุฉ"
|
| 76 |
+
|
| 77 |
+
except Exception as e:
|
| 78 |
+
return None, f"ุฎุทุฃ ูู ุงุณุชุฎุฑุงุฌ ุงูููุงุท: {str(e)}"
|
| 79 |
+
|
| 80 |
+
def generate_study_notes(self, text: str, subject: str = "", language: str = 'ar') -> Tuple[Optional[Dict], Optional[str]]:
|
| 81 |
+
"""
|
| 82 |
+
ุฅูุดุงุก ู
ุฐูุฑุฉ ุฏุฑุงุณูุฉ ุดุงู
ูุฉ
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
text: ุงููุต ุงูุฃุตูู
|
| 86 |
+
subject: ุงูู
ุงุฏุฉ ุงูุฏุฑุงุณูุฉ
|
| 87 |
+
language: ูุบุฉ ุงูู
ุฐูุฑุฉ
|
| 88 |
+
|
| 89 |
+
Returns:
|
| 90 |
+
Tuple of (study_notes_dict, error_message)
|
| 91 |
+
"""
|
| 92 |
+
try:
|
| 93 |
+
# ุฅูุดุงุก ุงูู
ูุฎุต
|
| 94 |
+
summary, summary_error = self.generate_summary(text, language)
|
| 95 |
+
if summary_error and not summary:
|
| 96 |
+
return None, summary_error
|
| 97 |
+
|
| 98 |
+
# ุงุณุชุฎุฑุงุฌ ุงูููุงุท ุงูุฑุฆูุณูุฉ
|
| 99 |
+
key_points, points_error = self.extract_key_points(text, language)
|
| 100 |
+
if points_error and not key_points:
|
| 101 |
+
key_points = []
|
| 102 |
+
|
| 103 |
+
# ุฅูุดุงุก ุฃุณุฆูุฉ ู
ุฑุงุฌุนุฉ
|
| 104 |
+
review_questions, questions_error = self.generate_review_questions(text, language)
|
| 105 |
+
if questions_error and not review_questions:
|
| 106 |
+
review_questions = []
|
| 107 |
+
|
| 108 |
+
study_notes = {
|
| 109 |
+
'summary': summary or "ูู
ูุชู
ุฅูุดุงุก ู
ูุฎุต",
|
| 110 |
+
'key_points': key_points or [],
|
| 111 |
+
'review_questions': review_questions or [],
|
| 112 |
+
'subject': subject,
|
| 113 |
+
'word_count': len(text.split()),
|
| 114 |
+
'estimated_reading_time': max(1, len(text.split()) // 200) # ุฏูุงุฆู ุชูุฑูุจูุฉ
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
return study_notes, None
|
| 118 |
+
|
| 119 |
+
except Exception as e:
|
| 120 |
+
return None, f"ุฎุทุฃ ูู ุฅูุดุงุก ุงูู
ุฐูุฑุฉ: {str(e)}"
|
| 121 |
+
|
| 122 |
+
def generate_review_questions(self, text: str, language: str = 'ar') -> Tuple[Optional[List[str]], Optional[str]]:
|
| 123 |
+
"""
|
| 124 |
+
ุฅูุดุงุก ุฃุณุฆูุฉ ู
ุฑุงุฌุนุฉ ู
ู ุงููุต
|
| 125 |
+
|
| 126 |
+
Args:
|
| 127 |
+
text: ุงููุต ุงูู
ุฑุงุฏ ุฅูุดุงุก ุฃุณุฆูุฉ ู
ูู
|
| 128 |
+
language: ูุบุฉ ุงูุฃุณุฆูุฉ
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
Tuple of (questions_list, error_message)
|
| 132 |
+
"""
|
| 133 |
+
if not self.translator or not self.translator.client:
|
| 134 |
+
return None, "ุฎุฏู
ุฉ ุงูุฐูุงุก ุงูุงุตุทูุงุนู ุบูุฑ ู
ุชููุฑุฉ"
|
| 135 |
+
|
| 136 |
+
try:
|
| 137 |
+
prompt = self._create_questions_prompt(text, language)
|
| 138 |
+
|
| 139 |
+
response = self.translator.client.models.generate_content(
|
| 140 |
+
model="gemini-2.5-flash",
|
| 141 |
+
contents=[prompt]
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
if response and response.text:
|
| 145 |
+
questions_text = response.text.strip()
|
| 146 |
+
questions = self._parse_questions(questions_text)
|
| 147 |
+
return questions, None
|
| 148 |
+
else:
|
| 149 |
+
return None, "ูุดู ูู ุฅูุดุงุก ุฃุณุฆูุฉ ุงูู
ุฑุงุฌุนุฉ"
|
| 150 |
+
|
| 151 |
+
except Exception as e:
|
| 152 |
+
return None, f"ุฎุทุฃ ูู ุฅูุดุงุก ุงูุฃุณุฆูุฉ: {str(e)}"
|
| 153 |
+
|
| 154 |
+
def _create_summary_prompt(self, text: str, language: str) -> str:
|
| 155 |
+
"""ุฅูุดุงุก prompt ููู
ูุฎุต"""
|
| 156 |
+
if language == 'ar':
|
| 157 |
+
return f"""
|
| 158 |
+
ูู
ุจุฅูุดุงุก ู
ูุฎุต ุดุงู
ู ูู
ููุฏ ููุฐุง ุงููุต ู
ู ู
ุญุงุถุฑุฉ ุฏุฑุงุณูุฉ:
|
| 159 |
+
|
| 160 |
+
ู
ุชุทูุจุงุช ุงูู
ูุฎุต:
|
| 161 |
+
1. ุงูุชุจ ุจุงูุนุฑุจูุฉ ุงููุตุญู ุงููุงุถุญุฉ
|
| 162 |
+
2. ุงุฐูุฑ ุงูู
ูุถูุน ุงูุฑุฆูุณู ูุงูุฃููุงุฑ ุงูู
ูู
ุฉ
|
| 163 |
+
3. ุฑุชุจ ุงูู
ุนููู
ุงุช ุจุดูู ู
ูุทูู
|
| 164 |
+
4. ุงุฌุนู ุงูู
ูุฎุต ู
ูุงุณุจ ููุทูุงุจ ุงูุฌุงู
ุนููู
|
| 165 |
+
5. ูุง ุชุชุฌุงูุฒ 200 ููู
ุฉ
|
| 166 |
+
6. ุฃุถู ุงูุนูุงููู ุงููุฑุนูุฉ ุฅุฐุง ูุฒู
ุงูุฃู
ุฑ
|
| 167 |
+
|
| 168 |
+
ุงููุต:
|
| 169 |
+
{text}
|
| 170 |
+
|
| 171 |
+
ุงูู
ูุฎุต:
|
| 172 |
+
"""
|
| 173 |
+
else:
|
| 174 |
+
return f"""
|
| 175 |
+
Create a comprehensive and useful summary of this lecture text:
|
| 176 |
+
|
| 177 |
+
Requirements:
|
| 178 |
+
1. Write in clear, academic English
|
| 179 |
+
2. Mention the main topic and important ideas
|
| 180 |
+
3. Organize information logically
|
| 181 |
+
4. Make it suitable for university students
|
| 182 |
+
5. Don't exceed 200 words
|
| 183 |
+
6. Add subheadings if necessary
|
| 184 |
+
|
| 185 |
+
Text:
|
| 186 |
+
{text}
|
| 187 |
+
|
| 188 |
+
Summary:
|
| 189 |
+
"""
|
| 190 |
+
|
| 191 |
+
def _create_key_points_prompt(self, text: str, language: str) -> str:
|
| 192 |
+
"""ุฅูุดุงุก prompt ููููุงุท ุงูุฑุฆูุณูุฉ"""
|
| 193 |
+
if language == 'ar':
|
| 194 |
+
return f"""
|
| 195 |
+
ุงุณุชุฎุฑุฌ ุฃูู
ุงูููุงุท ุงูุฑุฆูุณูุฉ ู
ู ูุฐุง ุงููุต:
|
| 196 |
+
|
| 197 |
+
ู
ุชุทูุจุงุช:
|
| 198 |
+
1. ุงูุชุจ ูู ููุทุฉ ูู ุณุทุฑ ู
ููุตู
|
| 199 |
+
2. ุงุจุฏุฃ ูู ููุทุฉ ุจู "โข"
|
| 200 |
+
3. ุงุฌุนู ูู ููุทุฉ ูุงุถุญุฉ ูู
ููุฏุฉ ููุฏุฑุงุณุฉ
|
| 201 |
+
4. ูุง ุชุฒูุฏ ุนู 8 ููุงุท
|
| 202 |
+
5. ุฑุชุจ ุงูููุงุท ุญุณุจ ุงูุฃูู
ูุฉ
|
| 203 |
+
|
| 204 |
+
ุงููุต:
|
| 205 |
+
{text}
|
| 206 |
+
|
| 207 |
+
ุงูููุงุท ุงูุฑุฆูุณูุฉ:
|
| 208 |
+
"""
|
| 209 |
+
else:
|
| 210 |
+
return f"""
|
| 211 |
+
Extract the most important key points from this text:
|
| 212 |
+
|
| 213 |
+
Requirements:
|
| 214 |
+
1. Write each point on a separate line
|
| 215 |
+
2. Start each point with "โข"
|
| 216 |
+
3. Make each point clear and useful for studying
|
| 217 |
+
4. No more than 8 points
|
| 218 |
+
5. Order points by importance
|
| 219 |
+
|
| 220 |
+
Text:
|
| 221 |
+
{text}
|
| 222 |
+
|
| 223 |
+
Key Points:
|
| 224 |
+
"""
|
| 225 |
+
|
| 226 |
+
def _create_questions_prompt(self, text: str, language: str) -> str:
|
| 227 |
+
"""ุฅูุดุงุก prompt ูุฃุณุฆูุฉ ุงูู
ุฑุงุฌุนุฉ"""
|
| 228 |
+
if language == 'ar':
|
| 229 |
+
return f"""
|
| 230 |
+
ุฃูุดุฆ ุฃุณุฆูุฉ ู
ุฑุงุฌุนุฉ ู
ููุฏุฉ ู
ู ูุฐุง ุงููุต:
|
| 231 |
+
|
| 232 |
+
ู
ุชุทูุจุงุช:
|
| 233 |
+
1. ุงูุชุจ ูู ุณุคุงู ูู ุณุทุฑ ู
ููุตู
|
| 234 |
+
2. ุงุจุฏุฃ ูู ุณุคุงู ุจุฑูู
(1ุ 2ุ 3...)
|
| 235 |
+
3. ุงุฌุนู ุงูุฃุณุฆูุฉ ุชุบุทู ุงูู
ูุงููู
ุงูู
ูู
ุฉ
|
| 236 |
+
4. ุชููุน ูู ุฃููุงุน ุงูุฃุณุฆูุฉ (ู
ุงุ ูููุ ูู
ุงุฐุงุ ุงุดุฑุญ)
|
| 237 |
+
5. ูุง ุชุฒูุฏ ุนู 6 ุฃุณุฆูุฉ
|
| 238 |
+
6. ุงุฌุนู ุงูุฃุณุฆูุฉ ู
ูุงุณุจุฉ ููุงู
ุชุญุงูุงุช
|
| 239 |
+
|
| 240 |
+
ุงููุต:
|
| 241 |
+
{text}
|
| 242 |
+
|
| 243 |
+
ุฃุณุฆูุฉ ุงูู
ุฑุงุฌุนุฉ:
|
| 244 |
+
"""
|
| 245 |
+
else:
|
| 246 |
+
return f"""
|
| 247 |
+
Create useful review questions from this text:
|
| 248 |
+
|
| 249 |
+
Requirements:
|
| 250 |
+
1. Write each question on a separate line
|
| 251 |
+
2. Start each question with a number (1, 2, 3...)
|
| 252 |
+
3. Make questions cover important concepts
|
| 253 |
+
4. Vary question types (what, how, why, explain)
|
| 254 |
+
5. No more than 6 questions
|
| 255 |
+
6. Make questions suitable for exams
|
| 256 |
+
|
| 257 |
+
Text:
|
| 258 |
+
{text}
|
| 259 |
+
|
| 260 |
+
Review Questions:
|
| 261 |
+
"""
|
| 262 |
+
|
| 263 |
+
def _clean_summary_output(self, text: str) -> str:
|
| 264 |
+
"""ุชูุธูู ูุต ุงูู
ูุฎุต"""
|
| 265 |
+
# ุฅุฒุงูุฉ ุงูุฑู
ูุฒ ุบูุฑ ุงูู
ุฑุบูุจุฉ
|
| 266 |
+
text = re.sub(r'\*+', '', text)
|
| 267 |
+
text = re.sub(r'#+', '', text)
|
| 268 |
+
text = text.strip()
|
| 269 |
+
|
| 270 |
+
# ุชูุธูู ุงูุฃุณุทุฑ ุงููุงุฑุบุฉ ุงูุฒุงุฆุฏุฉ
|
| 271 |
+
text = re.sub(r'\n\s*\n', '\n\n', text)
|
| 272 |
+
|
| 273 |
+
return text
|
| 274 |
+
|
| 275 |
+
def _parse_key_points(self, text: str) -> List[str]:
|
| 276 |
+
"""ุชุญููู ุงูููุงุท ุงูุฑุฆูุณูุฉ ู
ู ุงููุต"""
|
| 277 |
+
points = []
|
| 278 |
+
lines = text.split('\n')
|
| 279 |
+
|
| 280 |
+
for line in lines:
|
| 281 |
+
line = line.strip()
|
| 282 |
+
if line and (line.startswith('โข') or line.startswith('-') or line.startswith('*')):
|
| 283 |
+
# ุฅุฒุงูุฉ ุงูุฑู
ุฒ ู
ู ุงูุจุฏุงูุฉ
|
| 284 |
+
point = re.sub(r'^[โข\-\*]\s*', '', line)
|
| 285 |
+
if point:
|
| 286 |
+
points.append(point)
|
| 287 |
+
elif line and re.match(r'^\d+\.', line):
|
| 288 |
+
# ููุงุท ู
ุฑูู
ุฉ
|
| 289 |
+
point = re.sub(r'^\d+\.\s*', '', line)
|
| 290 |
+
if point:
|
| 291 |
+
points.append(point)
|
| 292 |
+
|
| 293 |
+
return points[:8] # ุญุฏ ุฃูุตู 8 ููุงุท
|
| 294 |
+
|
| 295 |
+
def _parse_questions(self, text: str) -> List[str]:
|
| 296 |
+
"""ุชุญููู ุงูุฃุณุฆูุฉ ู
ู ุงููุต"""
|
| 297 |
+
questions = []
|
| 298 |
+
lines = text.split('\n')
|
| 299 |
+
|
| 300 |
+
for line in lines:
|
| 301 |
+
line = line.strip()
|
| 302 |
+
if line and ('ุ' in line or '?' in line):
|
| 303 |
+
# ุฅุฒุงูุฉ ุงูุชุฑููู
ู
ู ุงูุจุฏุงูุฉ
|
| 304 |
+
question = re.sub(r'^\d+[\.\-\)]\s*', '', line)
|
| 305 |
+
if question:
|
| 306 |
+
questions.append(question)
|
| 307 |
+
|
| 308 |
+
return questions[:6] # ุญุฏ ุฃูุตู 6 ุฃุณุฆูุฉ
|
templates/recorder.html
ADDED
|
@@ -0,0 +1,1958 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" dir="ltr">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Lecture Recorder - SyncMaster</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root {
|
| 9 |
+
--primary-color: #2563eb;
|
| 10 |
+
--primary-dark: #1d4ed8;
|
| 11 |
+
--success-color: #16a34a;
|
| 12 |
+
--danger-color: #dc2626;
|
| 13 |
+
--warning-color: #ea580c;
|
| 14 |
+
--neutral-100: #f5f5f5;
|
| 15 |
+
--neutral-200: #e5e5e5;
|
| 16 |
+
--neutral-300: #d4d4d8;
|
| 17 |
+
--neutral-600: #525252;
|
| 18 |
+
--neutral-700: #404040;
|
| 19 |
+
--neutral-800: #262626;
|
| 20 |
+
--neutral-900: #171717;
|
| 21 |
+
--white: #ffffff;
|
| 22 |
+
--text-primary: #0f172a;
|
| 23 |
+
--text-secondary: #64748b;
|
| 24 |
+
--border-radius: 8px;
|
| 25 |
+
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
| 26 |
+
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
| 27 |
+
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/* RTL Support for Arabic */
|
| 31 |
+
html[dir="rtl"] {
|
| 32 |
+
direction: rtl;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
html[dir="rtl"] .language-selector {
|
| 36 |
+
left: 20px;
|
| 37 |
+
right: auto;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
* {
|
| 41 |
+
box-sizing: border-box;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
body {
|
| 45 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
| 46 |
+
margin: 0;
|
| 47 |
+
padding: 20px;
|
| 48 |
+
min-height: 100vh;
|
| 49 |
+
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
| 50 |
+
color: var(--text-primary);
|
| 51 |
+
display: flex;
|
| 52 |
+
align-items: center;
|
| 53 |
+
justify-content: center;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.recorder-container {
|
| 57 |
+
background: var(--white);
|
| 58 |
+
border-radius: 16px;
|
| 59 |
+
box-shadow: var(--shadow-lg);
|
| 60 |
+
width: 100%;
|
| 61 |
+
max-width: 640px;
|
| 62 |
+
padding: 32px;
|
| 63 |
+
text-align: center;
|
| 64 |
+
position: relative;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
/* Language Selector */
|
| 68 |
+
.language-selector {
|
| 69 |
+
position: absolute;
|
| 70 |
+
top: 20px;
|
| 71 |
+
right: 20px;
|
| 72 |
+
z-index: 100;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.language-selector select {
|
| 76 |
+
padding: 8px 12px;
|
| 77 |
+
border: 1px solid var(--neutral-300);
|
| 78 |
+
border-radius: var(--border-radius);
|
| 79 |
+
background: var(--white);
|
| 80 |
+
color: var(--text-primary);
|
| 81 |
+
font-size: 14px;
|
| 82 |
+
font-weight: 500;
|
| 83 |
+
cursor: pointer;
|
| 84 |
+
outline: none;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
.language-selector select:focus {
|
| 88 |
+
border-color: var(--primary-color);
|
| 89 |
+
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
/* Translation Controls */
|
| 93 |
+
.translation-controls {
|
| 94 |
+
margin: 24px 0;
|
| 95 |
+
padding: 20px;
|
| 96 |
+
background: var(--neutral-100);
|
| 97 |
+
border-radius: var(--border-radius);
|
| 98 |
+
border: 1px solid var(--neutral-200);
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.translation-toggle {
|
| 102 |
+
display: flex;
|
| 103 |
+
align-items: center;
|
| 104 |
+
justify-content: center;
|
| 105 |
+
gap: 16px;
|
| 106 |
+
margin-bottom: 12px;
|
| 107 |
+
flex-wrap: wrap;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.toggle-switch {
|
| 111 |
+
position: relative;
|
| 112 |
+
width: 48px;
|
| 113 |
+
height: 24px;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.toggle-switch input {
|
| 117 |
+
opacity: 0;
|
| 118 |
+
width: 0;
|
| 119 |
+
height: 0;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.slider {
|
| 123 |
+
position: absolute;
|
| 124 |
+
cursor: pointer;
|
| 125 |
+
top: 0;
|
| 126 |
+
left: 0;
|
| 127 |
+
right: 0;
|
| 128 |
+
bottom: 0;
|
| 129 |
+
background-color: var(--neutral-300);
|
| 130 |
+
border-radius: 24px;
|
| 131 |
+
transition: background-color 0.3s ease;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.slider:before {
|
| 135 |
+
position: absolute;
|
| 136 |
+
content: "";
|
| 137 |
+
height: 18px;
|
| 138 |
+
width: 18px;
|
| 139 |
+
left: 3px;
|
| 140 |
+
bottom: 3px;
|
| 141 |
+
background-color: var(--white);
|
| 142 |
+
border-radius: 50%;
|
| 143 |
+
transition: transform 0.3s ease;
|
| 144 |
+
box-shadow: var(--shadow-sm);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
input:checked + .slider {
|
| 148 |
+
background-color: var(--primary-color);
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
input:checked + .slider:before {
|
| 152 |
+
transform: translateX(24px);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.target-language-select {
|
| 156 |
+
padding: 8px 12px;
|
| 157 |
+
border: 1px solid var(--neutral-300);
|
| 158 |
+
border-radius: var(--border-radius);
|
| 159 |
+
background: var(--white);
|
| 160 |
+
min-width: 140px;
|
| 161 |
+
font-size: 14px;
|
| 162 |
+
color: var(--text-primary);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.target-language-select:focus {
|
| 166 |
+
border-color: var(--primary-color);
|
| 167 |
+
outline: none;
|
| 168 |
+
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
h2 {
|
| 172 |
+
margin-top: 0;
|
| 173 |
+
margin-bottom: 8px;
|
| 174 |
+
color: var(--text-primary);
|
| 175 |
+
font-size: 28px;
|
| 176 |
+
font-weight: 700;
|
| 177 |
+
letter-spacing: -0.025em;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.subtitle {
|
| 181 |
+
color: var(--text-secondary);
|
| 182 |
+
margin-bottom: 32px;
|
| 183 |
+
font-size: 16px;
|
| 184 |
+
line-height: 1.5;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.button-group {
|
| 188 |
+
margin-top: 24px;
|
| 189 |
+
display: grid;
|
| 190 |
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
| 191 |
+
gap: 12px;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.button {
|
| 195 |
+
color: var(--white);
|
| 196 |
+
border: none;
|
| 197 |
+
padding: 12px 20px;
|
| 198 |
+
font-size: 14px;
|
| 199 |
+
font-weight: 600;
|
| 200 |
+
border-radius: var(--border-radius);
|
| 201 |
+
cursor: pointer;
|
| 202 |
+
display: flex;
|
| 203 |
+
align-items: center;
|
| 204 |
+
justify-content: center;
|
| 205 |
+
gap: 8px;
|
| 206 |
+
min-height: 44px;
|
| 207 |
+
text-transform: none;
|
| 208 |
+
letter-spacing: normal;
|
| 209 |
+
transition: none;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
.button:disabled {
|
| 213 |
+
opacity: 0.5;
|
| 214 |
+
cursor: not-allowed;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.button:focus {
|
| 218 |
+
outline: none;
|
| 219 |
+
box-shadow: 0 0 0 3px rgb(0 0 0 / 0.1);
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
#recordButton {
|
| 223 |
+
background: var(--success-color);
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
#stopButton {
|
| 227 |
+
background: var(--danger-color);
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
#pauseButton {
|
| 231 |
+
background: var(--warning-color);
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
#markButton {
|
| 235 |
+
background: var(--primary-color);
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
#extractButton {
|
| 239 |
+
background: var(--primary-color);
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
#rerecordButton {
|
| 243 |
+
background: var(--neutral-600);
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.status-display {
|
| 247 |
+
margin: 24px 0;
|
| 248 |
+
font-size: 16px;
|
| 249 |
+
color: var(--text-secondary);
|
| 250 |
+
display: flex;
|
| 251 |
+
align-items: center;
|
| 252 |
+
justify-content: center;
|
| 253 |
+
gap: 12px;
|
| 254 |
+
font-weight: 500;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.recording-indicator {
|
| 258 |
+
width: 10px;
|
| 259 |
+
height: 10px;
|
| 260 |
+
background-color: var(--danger-color);
|
| 261 |
+
border-radius: 50%;
|
| 262 |
+
animation: pulse 2s infinite;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
@keyframes pulse {
|
| 266 |
+
0%, 100% {
|
| 267 |
+
opacity: 1;
|
| 268 |
+
transform: scale(1);
|
| 269 |
+
}
|
| 270 |
+
50% {
|
| 271 |
+
opacity: 0.5;
|
| 272 |
+
transform: scale(1.1);
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
#timer {
|
| 277 |
+
font-size: 36px;
|
| 278 |
+
font-weight: 300;
|
| 279 |
+
margin: 20px 0;
|
| 280 |
+
color: var(--primary-color);
|
| 281 |
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
| 282 |
+
letter-spacing: 0.05em;
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
#audio-level-container {
|
| 286 |
+
width: 100%;
|
| 287 |
+
height: 6px;
|
| 288 |
+
background-color: var(--neutral-200);
|
| 289 |
+
border-radius: 3px;
|
| 290 |
+
overflow: hidden;
|
| 291 |
+
margin: 20px 0;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
#audio-level-indicator {
|
| 295 |
+
width: 0%;
|
| 296 |
+
height: 100%;
|
| 297 |
+
background: linear-gradient(90deg, var(--success-color), var(--warning-color), var(--danger-color));
|
| 298 |
+
transition: width 0.1s ease;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
#review-section, #transcription-container {
|
| 302 |
+
margin-top: 32px;
|
| 303 |
+
text-align: left;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
.section-title {
|
| 307 |
+
font-size: 18px;
|
| 308 |
+
font-weight: 600;
|
| 309 |
+
margin-bottom: 16px;
|
| 310 |
+
color: var(--text-primary);
|
| 311 |
+
border-bottom: 2px solid var(--primary-color);
|
| 312 |
+
padding-bottom: 8px;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
textarea {
|
| 316 |
+
width: 100%;
|
| 317 |
+
border-radius: var(--border-radius);
|
| 318 |
+
border: 1px solid var(--neutral-300);
|
| 319 |
+
padding: 16px;
|
| 320 |
+
font-family: inherit;
|
| 321 |
+
resize: vertical;
|
| 322 |
+
font-size: 14px;
|
| 323 |
+
line-height: 1.6;
|
| 324 |
+
color: var(--text-primary);
|
| 325 |
+
background: var(--white);
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
textarea:focus {
|
| 329 |
+
outline: none;
|
| 330 |
+
border-color: var(--primary-color);
|
| 331 |
+
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
.translation-result {
|
| 335 |
+
margin-top: 20px;
|
| 336 |
+
padding: 20px;
|
| 337 |
+
background: var(--neutral-100);
|
| 338 |
+
border-radius: var(--border-radius);
|
| 339 |
+
border: 1px solid var(--neutral-200);
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
.translation-header {
|
| 343 |
+
font-weight: 600;
|
| 344 |
+
color: var(--primary-color);
|
| 345 |
+
margin-bottom: 12px;
|
| 346 |
+
font-size: 16px;
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
.language-info {
|
| 350 |
+
margin-top: 12px;
|
| 351 |
+
font-size: 13px;
|
| 352 |
+
color: var(--text-secondary);
|
| 353 |
+
padding: 8px 12px;
|
| 354 |
+
background: var(--white);
|
| 355 |
+
border-radius: var(--border-radius);
|
| 356 |
+
border: 1px solid var(--neutral-200);
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
/* Loading Spinner */
|
| 360 |
+
.loading-spinner {
|
| 361 |
+
display: inline-block;
|
| 362 |
+
width: 20px;
|
| 363 |
+
height: 20px;
|
| 364 |
+
border: 2px solid var(--neutral-300);
|
| 365 |
+
border-top: 2px solid var(--primary-color);
|
| 366 |
+
border-radius: 50%;
|
| 367 |
+
animation: spin 1s linear infinite;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
@keyframes spin {
|
| 371 |
+
0% { transform: rotate(0deg); }
|
| 372 |
+
100% { transform: rotate(360deg); }
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
/* Messages */
|
| 376 |
+
.error-message {
|
| 377 |
+
background: #fef2f2;
|
| 378 |
+
color: var(--danger-color);
|
| 379 |
+
padding: 12px 16px;
|
| 380 |
+
border-radius: var(--border-radius);
|
| 381 |
+
margin: 12px 0;
|
| 382 |
+
border: 1px solid #fecaca;
|
| 383 |
+
font-size: 14px;
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
.success-message {
|
| 387 |
+
background: #f0fdf4;
|
| 388 |
+
color: var(--success-color);
|
| 389 |
+
padding: 12px 16px;
|
| 390 |
+
border-radius: var(--border-radius);
|
| 391 |
+
margin: 12px 0;
|
| 392 |
+
border: 1px solid #bbf7d0;
|
| 393 |
+
font-size: 14px;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
.translation-status {
|
| 397 |
+
font-size: 12px;
|
| 398 |
+
padding: 6px 10px;
|
| 399 |
+
border-radius: 4px;
|
| 400 |
+
margin-top: 8px;
|
| 401 |
+
font-weight: 500;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
/* Responsive Design */
|
| 405 |
+
@media (max-width: 768px) {
|
| 406 |
+
body {
|
| 407 |
+
padding: 12px;
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
.recorder-container {
|
| 411 |
+
padding: 24px 20px;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
.button-group {
|
| 415 |
+
grid-template-columns: 1fr;
|
| 416 |
+
gap: 8px;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
#timer {
|
| 420 |
+
font-size: 28px;
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
h2 {
|
| 424 |
+
font-size: 24px;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.translation-toggle {
|
| 428 |
+
flex-direction: column;
|
| 429 |
+
gap: 12px;
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.language-selector {
|
| 433 |
+
position: static;
|
| 434 |
+
margin-bottom: 16px;
|
| 435 |
+
text-align: center;
|
| 436 |
+
}
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
@media (max-width: 480px) {
|
| 440 |
+
.recorder-container {
|
| 441 |
+
padding: 20px 16px;
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
h2 {
|
| 445 |
+
font-size: 20px;
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
.subtitle {
|
| 449 |
+
font-size: 14px;
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
#timer {
|
| 453 |
+
font-size: 24px;
|
| 454 |
+
}
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
/* Focus indicators for accessibility */
|
| 458 |
+
.button:focus,
|
| 459 |
+
select:focus,
|
| 460 |
+
input:focus {
|
| 461 |
+
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
/* Summary and Notes Styles */
|
| 465 |
+
.note-item {
|
| 466 |
+
background: var(--white);
|
| 467 |
+
border: 1px solid var(--neutral-200);
|
| 468 |
+
border-radius: var(--border-radius);
|
| 469 |
+
padding: 20px;
|
| 470 |
+
margin-bottom: 16px;
|
| 471 |
+
transition: all 0.2s ease;
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
.note-item:hover {
|
| 475 |
+
border-color: var(--primary-color);
|
| 476 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
.note-header {
|
| 480 |
+
display: flex;
|
| 481 |
+
justify-content: space-between;
|
| 482 |
+
align-items: flex-start;
|
| 483 |
+
margin-bottom: 12px;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
.note-header h4 {
|
| 487 |
+
margin: 0;
|
| 488 |
+
color: var(--text-primary);
|
| 489 |
+
font-size: 16px;
|
| 490 |
+
font-weight: 600;
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
.note-date {
|
| 494 |
+
font-size: 12px;
|
| 495 |
+
color: var(--text-secondary);
|
| 496 |
+
white-space: nowrap;
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
.note-summary {
|
| 500 |
+
color: var(--text-secondary);
|
| 501 |
+
font-size: 14px;
|
| 502 |
+
line-height: 1.5;
|
| 503 |
+
margin-bottom: 12px;
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
.note-tags {
|
| 507 |
+
margin-bottom: 12px;
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
.tag {
|
| 511 |
+
display: inline-block;
|
| 512 |
+
background: var(--primary-color);
|
| 513 |
+
color: white;
|
| 514 |
+
padding: 4px 8px;
|
| 515 |
+
border-radius: 12px;
|
| 516 |
+
font-size: 12px;
|
| 517 |
+
margin-right: 6px;
|
| 518 |
+
margin-bottom: 4px;
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
.note-actions {
|
| 522 |
+
display: flex;
|
| 523 |
+
gap: 8px;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.btn-secondary {
|
| 527 |
+
background: var(--neutral-200);
|
| 528 |
+
color: var(--text-primary);
|
| 529 |
+
border: 1px solid var(--neutral-300);
|
| 530 |
+
padding: 6px 12px;
|
| 531 |
+
border-radius: 4px;
|
| 532 |
+
font-size: 12px;
|
| 533 |
+
cursor: pointer;
|
| 534 |
+
transition: all 0.2s ease;
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
.btn-secondary:hover {
|
| 538 |
+
background: var(--neutral-300);
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
.btn-danger {
|
| 542 |
+
background: #dc3545;
|
| 543 |
+
color: white;
|
| 544 |
+
border: 1px solid #dc3545;
|
| 545 |
+
padding: 6px 12px;
|
| 546 |
+
border-radius: 4px;
|
| 547 |
+
font-size: 12px;
|
| 548 |
+
cursor: pointer;
|
| 549 |
+
transition: all 0.2s ease;
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
.btn-danger:hover {
|
| 553 |
+
background: #c82333;
|
| 554 |
+
border-color: #c82333;
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
/* Modal Styles */
|
| 558 |
+
.note-modal {
|
| 559 |
+
position: fixed;
|
| 560 |
+
top: 0;
|
| 561 |
+
left: 0;
|
| 562 |
+
width: 100%;
|
| 563 |
+
height: 100%;
|
| 564 |
+
background: rgba(0, 0, 0, 0.5);
|
| 565 |
+
display: flex;
|
| 566 |
+
justify-content: center;
|
| 567 |
+
align-items: center;
|
| 568 |
+
z-index: 1000;
|
| 569 |
+
padding: 20px;
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
.note-modal-content {
|
| 573 |
+
background: var(--white);
|
| 574 |
+
border-radius: var(--border-radius);
|
| 575 |
+
max-width: 800px;
|
| 576 |
+
width: 100%;
|
| 577 |
+
max-height: 90vh;
|
| 578 |
+
overflow-y: auto;
|
| 579 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
.note-modal-header {
|
| 583 |
+
display: flex;
|
| 584 |
+
justify-content: space-between;
|
| 585 |
+
align-items: center;
|
| 586 |
+
padding: 20px 24px;
|
| 587 |
+
border-bottom: 1px solid var(--neutral-200);
|
| 588 |
+
position: sticky;
|
| 589 |
+
top: 0;
|
| 590 |
+
background: var(--white);
|
| 591 |
+
z-index: 1;
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
.note-modal-header h2 {
|
| 595 |
+
margin: 0;
|
| 596 |
+
color: var(--text-primary);
|
| 597 |
+
font-size: 20px;
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
.close-btn {
|
| 601 |
+
background: none;
|
| 602 |
+
border: none;
|
| 603 |
+
font-size: 24px;
|
| 604 |
+
cursor: pointer;
|
| 605 |
+
color: var(--text-secondary);
|
| 606 |
+
padding: 0;
|
| 607 |
+
width: 30px;
|
| 608 |
+
height: 30px;
|
| 609 |
+
display: flex;
|
| 610 |
+
align-items: center;
|
| 611 |
+
justify-content: center;
|
| 612 |
+
border-radius: 50%;
|
| 613 |
+
transition: all 0.2s ease;
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
.close-btn:hover {
|
| 617 |
+
background: var(--neutral-100);
|
| 618 |
+
color: var(--text-primary);
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
.note-modal-body {
|
| 622 |
+
padding: 24px;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
.note-section {
|
| 626 |
+
margin-bottom: 24px;
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
.note-section h3 {
|
| 630 |
+
color: var(--primary-color);
|
| 631 |
+
font-size: 16px;
|
| 632 |
+
font-weight: 600;
|
| 633 |
+
margin-bottom: 12px;
|
| 634 |
+
border-bottom: 1px solid var(--neutral-200);
|
| 635 |
+
padding-bottom: 8px;
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
.note-section p {
|
| 639 |
+
color: var(--text-primary);
|
| 640 |
+
line-height: 1.6;
|
| 641 |
+
margin: 0;
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
.note-section ul {
|
| 645 |
+
margin: 0;
|
| 646 |
+
padding-left: 20px;
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
+
.note-section li {
|
| 650 |
+
color: var(--text-primary);
|
| 651 |
+
line-height: 1.6;
|
| 652 |
+
margin-bottom: 8px;
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
/* Search Styles */
|
| 656 |
+
.search-container {
|
| 657 |
+
margin-bottom: 20px;
|
| 658 |
+
}
|
| 659 |
+
|
| 660 |
+
.search-container input {
|
| 661 |
+
width: 100%;
|
| 662 |
+
padding: 12px 16px;
|
| 663 |
+
border: 1px solid var(--neutral-300);
|
| 664 |
+
border-radius: var(--border-radius);
|
| 665 |
+
font-size: 14px;
|
| 666 |
+
background: var(--white);
|
| 667 |
+
color: var(--text-primary);
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
.search-container input:focus {
|
| 671 |
+
outline: none;
|
| 672 |
+
border-color: var(--primary-color);
|
| 673 |
+
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
/* Empty state */
|
| 677 |
+
.text-center {
|
| 678 |
+
text-align: center;
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
.text-muted {
|
| 682 |
+
color: var(--text-secondary);
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
/* Form Styles for Summary Section */
|
| 686 |
+
.form-group {
|
| 687 |
+
margin-bottom: 16px;
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
.form-group label {
|
| 691 |
+
display: block;
|
| 692 |
+
margin-bottom: 6px;
|
| 693 |
+
font-weight: 500;
|
| 694 |
+
color: var(--text-primary);
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
.form-group input,
|
| 698 |
+
.form-group textarea {
|
| 699 |
+
width: 100%;
|
| 700 |
+
padding: 10px 12px;
|
| 701 |
+
border: 1px solid var(--neutral-300);
|
| 702 |
+
border-radius: 4px;
|
| 703 |
+
font-size: 14px;
|
| 704 |
+
background: var(--white);
|
| 705 |
+
color: var(--text-primary);
|
| 706 |
+
}
|
| 707 |
+
|
| 708 |
+
.form-group input:focus,
|
| 709 |
+
.form-group textarea:focus {
|
| 710 |
+
outline: none;
|
| 711 |
+
border-color: var(--primary-color);
|
| 712 |
+
box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
/* Summary Content Styling */
|
| 716 |
+
.summary-content {
|
| 717 |
+
background: var(--neutral-50);
|
| 718 |
+
border: 1px solid var(--neutral-200);
|
| 719 |
+
border-radius: var(--border-radius);
|
| 720 |
+
padding: 16px;
|
| 721 |
+
margin-bottom: 16px;
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
.summary-content h4 {
|
| 725 |
+
color: var(--primary-color);
|
| 726 |
+
margin-bottom: 12px;
|
| 727 |
+
font-size: 16px;
|
| 728 |
+
}
|
| 729 |
+
|
| 730 |
+
.summary-content ul {
|
| 731 |
+
margin: 0;
|
| 732 |
+
padding-left: 20px;
|
| 733 |
+
}
|
| 734 |
+
|
| 735 |
+
.summary-content li {
|
| 736 |
+
margin-bottom: 8px;
|
| 737 |
+
line-height: 1.5;
|
| 738 |
+
}
|
| 739 |
+
</style>
|
| 740 |
+
</head>
|
| 741 |
+
<body>
|
| 742 |
+
<div class="recorder-container">
|
| 743 |
+
<!-- Language Selector -->
|
| 744 |
+
<div class="language-selector">
|
| 745 |
+
<select id="languageSelect" aria-label="Select Language">
|
| 746 |
+
<option value="en">English</option>
|
| 747 |
+
<option value="ar">ุงูุนุฑุจูุฉ</option>
|
| 748 |
+
</select>
|
| 749 |
+
</div>
|
| 750 |
+
|
| 751 |
+
<h2 id="main-title">Lecture Recorder</h2>
|
| 752 |
+
<p class="subtitle" id="subtitle">Record and transcribe your lectures with AI-powered translation</p>
|
| 753 |
+
|
| 754 |
+
<!-- Translation Controls -->
|
| 755 |
+
<div class="translation-controls">
|
| 756 |
+
<div class="translation-toggle">
|
| 757 |
+
<label for="translationToggle" id="translation-label">Enable Translation:</label>
|
| 758 |
+
<label class="toggle-switch">
|
| 759 |
+
<input type="checkbox" id="translationToggle" checked>
|
| 760 |
+
<span class="slider"></span>
|
| 761 |
+
</label>
|
| 762 |
+
<select id="targetLanguage" class="target-language-select">
|
| 763 |
+
<option value="ar">ุงูุนุฑุจูุฉ (Arabic)</option>
|
| 764 |
+
<option value="en">English</option>
|
| 765 |
+
<option value="fr">Franรงais</option>
|
| 766 |
+
<option value="es">Espaรฑol</option>
|
| 767 |
+
</select>
|
| 768 |
+
</div>
|
| 769 |
+
</div>
|
| 770 |
+
|
| 771 |
+
<div id="recording-controls">
|
| 772 |
+
<div class="status-display">
|
| 773 |
+
<div id="recording-indicator" class="recording-indicator" style="display: none;"></div>
|
| 774 |
+
<span id="status">Ready to Record</span>
|
| 775 |
+
</div>
|
| 776 |
+
<div id="timer">00:00:00</div>
|
| 777 |
+
<div id="audio-level-container">
|
| 778 |
+
<div id="audio-level-indicator"></div>
|
| 779 |
+
</div>
|
| 780 |
+
<div class="button-group">
|
| 781 |
+
<button id="recordButton" class="button" aria-label="Start Recording">
|
| 782 |
+
<span>๐๏ธ</span>
|
| 783 |
+
<span id="record-text">Start</span>
|
| 784 |
+
</button>
|
| 785 |
+
<button id="pauseButton" class="button" disabled aria-label="Pause Recording">
|
| 786 |
+
<span>โธ๏ธ</span>
|
| 787 |
+
<span id="pause-text">Pause</span>
|
| 788 |
+
</button>
|
| 789 |
+
<button id="stopButton" class="button" disabled aria-label="Stop Recording">
|
| 790 |
+
<span>โน๏ธ</span>
|
| 791 |
+
<span id="stop-text">Stop</span>
|
| 792 |
+
</button>
|
| 793 |
+
<button id="markButton" class="button" disabled aria-label="Mark Important Point">
|
| 794 |
+
<span>๐</span>
|
| 795 |
+
<span id="mark-text">Mark Important</span>
|
| 796 |
+
</button>
|
| 797 |
+
</div>
|
| 798 |
+
</div>
|
| 799 |
+
|
| 800 |
+
<div id="review-section" style="display: none;">
|
| 801 |
+
<h3 class="section-title" id="review-title">Review Recording</h3>
|
| 802 |
+
<audio id="audio-playback" controls style="width: 100%;"></audio>
|
| 803 |
+
<div class="button-group">
|
| 804 |
+
<button id="extractButton" class="button" aria-label="Extract Text">
|
| 805 |
+
<span>๐</span>
|
| 806 |
+
<span id="extract-text">Extract Text</span>
|
| 807 |
+
</button>
|
| 808 |
+
<button id="rerecordButton" class="button" aria-label="Record Again">
|
| 809 |
+
<span>๐</span>
|
| 810 |
+
<span id="rerecord-text">Re-record</span>
|
| 811 |
+
</button>
|
| 812 |
+
</div>
|
| 813 |
+
</div>
|
| 814 |
+
|
| 815 |
+
<div id="transcription-container" style="display: none;">
|
| 816 |
+
<h3 class="section-title" id="transcription-title">Processing Result</h3>
|
| 817 |
+
|
| 818 |
+
<!-- Original Transcription -->
|
| 819 |
+
<div class="transcription-section">
|
| 820 |
+
<h4 id="original-text-label">Original Text:</h4>
|
| 821 |
+
<textarea id="original-transcription-box" rows="6" readonly></textarea>
|
| 822 |
+
</div>
|
| 823 |
+
|
| 824 |
+
<!-- Translation Result -->
|
| 825 |
+
<div id="translation-section" class="translation-result" style="display: none;">
|
| 826 |
+
<h4 class="translation-header" id="translated-text-label">Arabic Translation:</h4>
|
| 827 |
+
<textarea id="translated-transcription-box" rows="6" readonly></textarea>
|
| 828 |
+
|
| 829 |
+
<!-- Language Info -->
|
| 830 |
+
<div class="language-info" style="margin-top: 10px; font-size: 0.9em; color: #6c757d;">
|
| 831 |
+
<span id="detected-language">Detected Language: Unknown</span> โ
|
| 832 |
+
<span id="target-language-info">Arabic</span>
|
| 833 |
+
</div>
|
| 834 |
+
</div>
|
| 835 |
+
|
| 836 |
+
<!-- Markers Section -->
|
| 837 |
+
<div id="markers-section" style="display: none; margin-top: 20px;">
|
| 838 |
+
<h4 id="markers-label">Important Markers:</h4>
|
| 839 |
+
<div id="markers-list" style="background: #f8f9fa; padding: 10px; border-radius: 6px;"></div>
|
| 840 |
+
</div>
|
| 841 |
+
|
| 842 |
+
<!-- Summary Action Button (Always Visible) -->
|
| 843 |
+
<div id="summary-action-section" style="display: none; margin-top: 24px; text-align: center;">
|
| 844 |
+
<button id="generateSummaryBtn" class="button" style="padding: 12px 24px; font-size: 16px; min-height: auto; background: var(--primary-color); color: white;">
|
| 845 |
+
<span>๐ค</span>
|
| 846 |
+
<span>Generate Smart Lecture Summary</span>
|
| 847 |
+
</button>
|
| 848 |
+
</div>
|
| 849 |
+
|
| 850 |
+
<!-- Smart Summary Section -->
|
| 851 |
+
<div id="summary-section" style="display: none; margin-top: 24px;">
|
| 852 |
+
<div class="section-title">
|
| 853 |
+
<span>๐ Smart Summary</span>
|
| 854 |
+
</div>
|
| 855 |
+
|
| 856 |
+
<!-- Summary Content -->
|
| 857 |
+
<div id="summary-content" style="background: var(--neutral-100); border-radius: var(--border-radius); padding: 16px; margin-top: 12px;">
|
| 858 |
+
|
| 859 |
+
<!-- Summary Text -->
|
| 860 |
+
<div id="summary-text-section" style="display: none;">
|
| 861 |
+
<h5 style="color: var(--primary-color); margin: 0 0 8px 0; font-size: 14px;">๐ Summary:</h5>
|
| 862 |
+
<div id="summary-text" style="background: var(--white); padding: 12px; border-radius: 6px; border: 1px solid var(--neutral-200); line-height: 1.6; font-size: 14px;"></div>
|
| 863 |
+
</div>
|
| 864 |
+
|
| 865 |
+
<!-- Key Points -->
|
| 866 |
+
<div id="key-points-section" style="display: none; margin-top: 16px;">
|
| 867 |
+
<h5 style="color: var(--primary-color); margin: 0 0 8px 0; font-size: 14px;">๐ฏ Key Points:</h5>
|
| 868 |
+
<ul id="key-points-list" style="background: var(--white); padding: 12px; border-radius: 6px; border: 1px solid var(--neutral-200); margin: 0;"></ul>
|
| 869 |
+
</div>
|
| 870 |
+
|
| 871 |
+
<!-- Review Questions -->
|
| 872 |
+
<div id="questions-section" style="display: none; margin-top: 16px;">
|
| 873 |
+
<h5 style="color: var(--primary-color); margin: 0 0 8px 0; font-size: 14px;">โ Review Questions:</h5>
|
| 874 |
+
<ol id="questions-list" style="background: var(--white); padding: 12px; border-radius: 6px; border: 1px solid var(--neutral-200); margin: 0;"></ol>
|
| 875 |
+
</div>
|
| 876 |
+
|
| 877 |
+
<!-- Save Note Section -->
|
| 878 |
+
<div id="save-note-section" style="display: none; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--neutral-200);">
|
| 879 |
+
<h5 style="color: var(--primary-color); margin: 0 0 8px 0; font-size: 14px;">๐พ Save to Notes:</h5>
|
| 880 |
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px;">
|
| 881 |
+
<input type="text" id="noteTitle" placeholder="Note title..." style="padding: 8px; border: 1px solid var(--neutral-300); border-radius: 4px; font-size: 14px;">
|
| 882 |
+
<input type="text" id="noteTags" placeholder="Tags (comma separated)..." style="padding: 8px; border: 1px solid var(--neutral-300); border-radius: 4px; font-size: 14px;">
|
| 883 |
+
</div>
|
| 884 |
+
<button id="saveNoteBtn" class="button" style="width: 100%; padding: 8px; font-size: 14px; min-height: auto;">
|
| 885 |
+
<span>๐พ</span>
|
| 886 |
+
<span>Save Note</span>
|
| 887 |
+
</button>
|
| 888 |
+
</div>
|
| 889 |
+
</div>
|
| 890 |
+
</div>
|
| 891 |
+
|
| 892 |
+
<!-- Notes Library Section -->
|
| 893 |
+
<div id="notes-library-section" style="display: none; margin-top: 24px;">
|
| 894 |
+
<div class="section-title" style="display: flex; align-items: center; justify-content: space-between;">
|
| 895 |
+
<span>๐ ู
ูุชุจุฉ ุงูู
ุฐูุฑุงุช</span>
|
| 896 |
+
<div style="display: flex; gap: 8px;">
|
| 897 |
+
<button id="refreshNotesBtn" class="button" style="padding: 6px 12px; font-size: 12px; min-height: auto;">
|
| 898 |
+
<span>๐</span>
|
| 899 |
+
<span>ุชุญุฏูุซ</span>
|
| 900 |
+
</button>
|
| 901 |
+
<button id="toggleNotesBtn" class="button" style="padding: 6px 12px; font-size: 12px; min-height: auto;">
|
| 902 |
+
<span>๐</span>
|
| 903 |
+
<span>ุนุฑุถ ุงูู
ุฐูุฑุงุช</span>
|
| 904 |
+
</button>
|
| 905 |
+
</div>
|
| 906 |
+
</div>
|
| 907 |
+
|
| 908 |
+
<!-- Search and Filter -->
|
| 909 |
+
<div id="notes-controls" style="display: none; margin-top: 12px; padding: 16px; background: var(--neutral-100); border-radius: var(--border-radius);">
|
| 910 |
+
<div style="display: grid; grid-template-columns: 1fr auto; gap: 8px; margin-bottom: 12px;">
|
| 911 |
+
<input type="text" id="notes-search" placeholder="ุงูุจุญุซ ูู ุงูู
ุฐูุฑุงุช..." style="padding: 8px; border: 1px solid var(--neutral-300); border-radius: 4px; font-size: 14px;">
|
| 912 |
+
<select id="subject-filter" style="padding: 8px; border: 1px solid var(--neutral-300); border-radius: 4px; font-size: 14px; min-width: 120px;">
|
| 913 |
+
<option value="">ุฌู
ูุน ุงูู
ูุงุฏ</option>
|
| 914 |
+
</select>
|
| 915 |
+
</div>
|
| 916 |
+
<button id="searchNotesBtn" class="button" style="padding: 6px 16px; font-size: 12px; min-height: auto;">
|
| 917 |
+
<span>๐</span>
|
| 918 |
+
<span>ุจุญุซ</span>
|
| 919 |
+
</button>
|
| 920 |
+
</div>
|
| 921 |
+
|
| 922 |
+
<!-- Notes List -->
|
| 923 |
+
<div id="notes-list" style="display: none; margin-top: 12px; max-height: 400px; overflow-y: auto;">
|
| 924 |
+
<!-- Notes will be loaded here -->
|
| 925 |
+
</div>
|
| 926 |
+
</div>
|
| 927 |
+
</div>
|
| 928 |
+
|
| 929 |
+
<!-- Loading Indicator -->
|
| 930 |
+
<div id="loading-indicator" style="display: none; margin: 20px 0;">
|
| 931 |
+
<div class="loading-spinner"></div>
|
| 932 |
+
<span id="loading-text" style="margin-left: 10px;">Processing...</span>
|
| 933 |
+
</div>
|
| 934 |
+
|
| 935 |
+
<!-- Error/Success Messages -->
|
| 936 |
+
<div id="message-container"></div>
|
| 937 |
+
</div>
|
| 938 |
+
|
| 939 |
+
<script>
|
| 940 |
+
// --- Global Variables ---
|
| 941 |
+
let currentLanguage = 'en';
|
| 942 |
+
let translations = {};
|
| 943 |
+
|
| 944 |
+
// --- DOM Elements ---
|
| 945 |
+
const elements = {
|
| 946 |
+
recordButton: document.getElementById('recordButton'),
|
| 947 |
+
stopButton: document.getElementById('stopButton'),
|
| 948 |
+
pauseButton: document.getElementById('pauseButton'),
|
| 949 |
+
markButton: document.getElementById('markButton'),
|
| 950 |
+
extractButton: document.getElementById('extractButton'),
|
| 951 |
+
rerecordButton: document.getElementById('rerecordButton'),
|
| 952 |
+
status: document.getElementById('status'),
|
| 953 |
+
timer: document.getElementById('timer'),
|
| 954 |
+
recordingIndicator: document.getElementById('recording-indicator'),
|
| 955 |
+
audioLevelIndicator: document.getElementById('audio-level-indicator'),
|
| 956 |
+
reviewSection: document.getElementById('review-section'),
|
| 957 |
+
audioPlayback: document.getElementById('audio-playback'),
|
| 958 |
+
transcriptionContainer: document.getElementById('transcription-container'),
|
| 959 |
+
originalTranscriptionBox: document.getElementById('original-transcription-box'),
|
| 960 |
+
translatedTranscriptionBox: document.getElementById('translated-transcription-box'),
|
| 961 |
+
recordingControls: document.getElementById('recording-controls'),
|
| 962 |
+
languageSelect: document.getElementById('languageSelect'),
|
| 963 |
+
translationToggle: document.getElementById('translationToggle'),
|
| 964 |
+
targetLanguage: document.getElementById('targetLanguage'),
|
| 965 |
+
translationSection: document.getElementById('translation-section'),
|
| 966 |
+
markersSection: document.getElementById('markers-section'),
|
| 967 |
+
markersList: document.getElementById('markers-list'),
|
| 968 |
+
loadingIndicator: document.getElementById('loading-indicator'),
|
| 969 |
+
messageContainer: document.getElementById('message-container')
|
| 970 |
+
};
|
| 971 |
+
|
| 972 |
+
// --- State ---
|
| 973 |
+
let state = {
|
| 974 |
+
mediaRecorder: null,
|
| 975 |
+
audioChunks: [],
|
| 976 |
+
audioBlob: null,
|
| 977 |
+
timerInterval: null,
|
| 978 |
+
seconds: 0,
|
| 979 |
+
microphoneStream: null,
|
| 980 |
+
markers: [],
|
| 981 |
+
currentResult: null
|
| 982 |
+
};
|
| 983 |
+
|
| 984 |
+
// --- Event Listeners ---
|
| 985 |
+
elements.recordButton.addEventListener('click', startRecording);
|
| 986 |
+
elements.stopButton.addEventListener('click', stopRecording);
|
| 987 |
+
elements.pauseButton.addEventListener('click', togglePause);
|
| 988 |
+
elements.markButton.addEventListener('click', markTimestamp);
|
| 989 |
+
elements.extractButton.addEventListener('click', processAudio);
|
| 990 |
+
elements.rerecordButton.addEventListener('click', resetRecorder);
|
| 991 |
+
elements.languageSelect.addEventListener('change', changeLanguage);
|
| 992 |
+
elements.translationToggle.addEventListener('change', toggleTranslationUI);
|
| 993 |
+
elements.targetLanguage.addEventListener('change', updateTargetLanguageInfo);
|
| 994 |
+
|
| 995 |
+
// --- Translation System ---
|
| 996 |
+
async function loadTranslations(language) {
|
| 997 |
+
try {
|
| 998 |
+
const response = await fetch(`http://localhost:5001/ui-translations/${language}`);
|
| 999 |
+
if (response.ok) {
|
| 1000 |
+
const data = await response.json();
|
| 1001 |
+
if (data.success) {
|
| 1002 |
+
translations = data.translations;
|
| 1003 |
+
applyTranslations();
|
| 1004 |
+
|
| 1005 |
+
// Update HTML direction for RTL languages
|
| 1006 |
+
if (language === 'ar') {
|
| 1007 |
+
document.documentElement.setAttribute('dir', 'rtl');
|
| 1008 |
+
document.documentElement.setAttribute('lang', 'ar');
|
| 1009 |
+
} else {
|
| 1010 |
+
document.documentElement.setAttribute('dir', 'ltr');
|
| 1011 |
+
document.documentElement.setAttribute('lang', language);
|
| 1012 |
+
}
|
| 1013 |
+
}
|
| 1014 |
+
}
|
| 1015 |
+
} catch (error) {
|
| 1016 |
+
console.error('Failed to load translations:', error);
|
| 1017 |
+
// Fallback to default English
|
| 1018 |
+
translations = getDefaultTranslations();
|
| 1019 |
+
applyTranslations();
|
| 1020 |
+
}
|
| 1021 |
+
}
|
| 1022 |
+
|
| 1023 |
+
function getDefaultTranslations() {
|
| 1024 |
+
return {
|
| 1025 |
+
'start_recording': 'Start Recording',
|
| 1026 |
+
'stop_recording': 'Stop Recording',
|
| 1027 |
+
'pause_recording': 'Pause Recording',
|
| 1028 |
+
'resume_recording': 'Resume Recording',
|
| 1029 |
+
'mark_important': 'Mark Important',
|
| 1030 |
+
'extract_text': 'Extract Text',
|
| 1031 |
+
'rerecord': 'Re-record',
|
| 1032 |
+
'processing': 'Processing...',
|
| 1033 |
+
'ready_to_record': 'Ready to Record',
|
| 1034 |
+
'recording': 'Recording...',
|
| 1035 |
+
'paused': 'Paused',
|
| 1036 |
+
'review_recording': 'Review your recording',
|
| 1037 |
+
'processing_complete': 'Processing Complete!',
|
| 1038 |
+
'microphone_permission': 'Microphone permission denied.',
|
| 1039 |
+
'browser_not_supported': 'Your browser does not support audio recording.',
|
| 1040 |
+
'transcription': 'Transcription',
|
| 1041 |
+
'translation': 'Translation',
|
| 1042 |
+
'markers': 'Important Markers',
|
| 1043 |
+
'error_occurred': 'An error occurred',
|
| 1044 |
+
'success': 'Success'
|
| 1045 |
+
};
|
| 1046 |
+
}
|
| 1047 |
+
|
| 1048 |
+
function applyTranslations() {
|
| 1049 |
+
// Update text elements
|
| 1050 |
+
const textMappings = {
|
| 1051 |
+
'main-title': 'Lecture Recorder',
|
| 1052 |
+
'subtitle': 'Record and transcribe your lectures with AI-powered translation',
|
| 1053 |
+
'translation-label': 'Enable Translation:',
|
| 1054 |
+
'record-text': translations.start_recording || 'Start',
|
| 1055 |
+
'pause-text': translations.pause_recording || 'Pause',
|
| 1056 |
+
'stop-text': translations.stop_recording || 'Stop',
|
| 1057 |
+
'mark-text': translations.mark_important || 'Mark Important',
|
| 1058 |
+
'extract-text': translations.extract_text || 'Extract Text',
|
| 1059 |
+
'rerecord-text': translations.rerecord || 'Re-record',
|
| 1060 |
+
'review-title': translations.review_recording || 'Review Recording',
|
| 1061 |
+
'transcription-title': translations.processing_complete || 'Processing Result',
|
| 1062 |
+
'original-text-label': 'Original Text:',
|
| 1063 |
+
'translated-text-label': getTargetLanguageName() + ' Translation:',
|
| 1064 |
+
'markers-label': translations.markers || 'Important Markers'
|
| 1065 |
+
};
|
| 1066 |
+
|
| 1067 |
+
Object.entries(textMappings).forEach(([id, text]) => {
|
| 1068 |
+
const element = document.getElementById(id);
|
| 1069 |
+
if (element) {
|
| 1070 |
+
element.textContent = text;
|
| 1071 |
+
}
|
| 1072 |
+
});
|
| 1073 |
+
}
|
| 1074 |
+
|
| 1075 |
+
function getTargetLanguageName() {
|
| 1076 |
+
const langMap = {
|
| 1077 |
+
'ar': 'Arabic (ุงูุนุฑุจูุฉ)',
|
| 1078 |
+
'en': 'English',
|
| 1079 |
+
'fr': 'French (Franรงais)',
|
| 1080 |
+
'es': 'Spanish (Espaรฑol)'
|
| 1081 |
+
};
|
| 1082 |
+
return langMap[elements.targetLanguage.value] || 'Translation';
|
| 1083 |
+
}
|
| 1084 |
+
|
| 1085 |
+
async function changeLanguage() {
|
| 1086 |
+
currentLanguage = elements.languageSelect.value;
|
| 1087 |
+
await loadTranslations(currentLanguage);
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
+
function toggleTranslationUI() {
|
| 1091 |
+
const isEnabled = elements.translationToggle.checked;
|
| 1092 |
+
elements.targetLanguage.disabled = !isEnabled;
|
| 1093 |
+
updateTargetLanguageInfo();
|
| 1094 |
+
|
| 1095 |
+
// Show/hide translation section preview
|
| 1096 |
+
updateTranslationSectionVisibility();
|
| 1097 |
+
}
|
| 1098 |
+
|
| 1099 |
+
function updateTranslationSectionVisibility() {
|
| 1100 |
+
// Always prepare translation section when translation is enabled
|
| 1101 |
+
if (elements.translationToggle.checked) {
|
| 1102 |
+
// Make sure translation section is ready to be shown
|
| 1103 |
+
const translatedLabelElement = document.getElementById('translated-text-label');
|
| 1104 |
+
if (translatedLabelElement) {
|
| 1105 |
+
translatedLabelElement.textContent = getTargetLanguageName() + ' Translation:';
|
| 1106 |
+
}
|
| 1107 |
+
}
|
| 1108 |
+
}
|
| 1109 |
+
|
| 1110 |
+
function updateTargetLanguageInfo() {
|
| 1111 |
+
const targetLangElement = document.getElementById('target-language-info');
|
| 1112 |
+
if (targetLangElement) {
|
| 1113 |
+
targetLangElement.textContent = getTargetLanguageName();
|
| 1114 |
+
}
|
| 1115 |
+
|
| 1116 |
+
const translatedLabelElement = document.getElementById('translated-text-label');
|
| 1117 |
+
if (translatedLabelElement) {
|
| 1118 |
+
translatedLabelElement.textContent = getTargetLanguageName() + ' Translation:';
|
| 1119 |
+
}
|
| 1120 |
+
}
|
| 1121 |
+
// --- Core Recording Functions ---
|
| 1122 |
+
async function startRecording() {
|
| 1123 |
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
| 1124 |
+
showMessage(translations.browser_not_supported || 'Your browser does not support audio recording.', 'error');
|
| 1125 |
+
return;
|
| 1126 |
+
}
|
| 1127 |
+
try {
|
| 1128 |
+
state.microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 1129 |
+
updateUIMode('recording');
|
| 1130 |
+
|
| 1131 |
+
state.audioChunks = [];
|
| 1132 |
+
state.markers = [];
|
| 1133 |
+
state.mediaRecorder = new MediaRecorder(state.microphoneStream);
|
| 1134 |
+
state.mediaRecorder.ondataavailable = event => state.audioChunks.push(event.data);
|
| 1135 |
+
state.mediaRecorder.onstop = handleRecordingStop;
|
| 1136 |
+
|
| 1137 |
+
state.mediaRecorder.start();
|
| 1138 |
+
startTimer();
|
| 1139 |
+
visualizeAudio(state.microphoneStream);
|
| 1140 |
+
} catch (error) {
|
| 1141 |
+
showMessage(translations.microphone_permission || 'Microphone permission denied.', 'error');
|
| 1142 |
+
console.error('Error accessing microphone:', error);
|
| 1143 |
+
}
|
| 1144 |
+
}
|
| 1145 |
+
|
| 1146 |
+
function stopRecording() {
|
| 1147 |
+
if (state.mediaRecorder && state.mediaRecorder.state !== 'inactive') {
|
| 1148 |
+
state.mediaRecorder.stop();
|
| 1149 |
+
}
|
| 1150 |
+
}
|
| 1151 |
+
|
| 1152 |
+
function handleRecordingStop() {
|
| 1153 |
+
state.audioBlob = new Blob(state.audioChunks, { type: 'audio/wav' });
|
| 1154 |
+
elements.audioPlayback.src = URL.createObjectURL(state.audioBlob);
|
| 1155 |
+
state.microphoneStream.getTracks().forEach(track => track.stop());
|
| 1156 |
+
clearInterval(state.timerInterval);
|
| 1157 |
+
updateUIMode('review');
|
| 1158 |
+
}
|
| 1159 |
+
|
| 1160 |
+
function togglePause() {
|
| 1161 |
+
if (!state.mediaRecorder) return;
|
| 1162 |
+
if (state.mediaRecorder.state === 'recording') {
|
| 1163 |
+
state.mediaRecorder.pause();
|
| 1164 |
+
updateUIMode('paused');
|
| 1165 |
+
} else if (state.mediaRecorder.state === 'paused') {
|
| 1166 |
+
state.mediaRecorder.resume();
|
| 1167 |
+
updateUIMode('recording');
|
| 1168 |
+
}
|
| 1169 |
+
}
|
| 1170 |
+
|
| 1171 |
+
function markTimestamp() {
|
| 1172 |
+
state.markers.push(state.seconds);
|
| 1173 |
+
const markCount = state.markers.length;
|
| 1174 |
+
elements.markButton.innerHTML = `<span>๐</span><span>Marked (${markCount})</span>`;
|
| 1175 |
+
setTimeout(() => {
|
| 1176 |
+
elements.markButton.innerHTML = `<span>๐</span><span>${translations.mark_important || 'Mark Important'}</span>`;
|
| 1177 |
+
}, 1000);
|
| 1178 |
+
}
|
| 1179 |
+
|
| 1180 |
+
async function processAudio() {
|
| 1181 |
+
if (!state.audioBlob) return;
|
| 1182 |
+
updateUIMode('processing');
|
| 1183 |
+
|
| 1184 |
+
const formData = new FormData();
|
| 1185 |
+
formData.append('audio_data', state.audioBlob, 'recording.wav');
|
| 1186 |
+
formData.append('markers', JSON.stringify(state.markers));
|
| 1187 |
+
formData.append('enable_translation', elements.translationToggle.checked.toString());
|
| 1188 |
+
formData.append('target_language', elements.targetLanguage.value);
|
| 1189 |
+
|
| 1190 |
+
try {
|
| 1191 |
+
// Try multiple server URLs in case of port conflicts
|
| 1192 |
+
const serverUrls = [
|
| 1193 |
+
'http://localhost:5001/record',
|
| 1194 |
+
'http://127.0.0.1:5001/record',
|
| 1195 |
+
'http://localhost:5000/record'
|
| 1196 |
+
];
|
| 1197 |
+
|
| 1198 |
+
let response = null;
|
| 1199 |
+
let lastError = null;
|
| 1200 |
+
|
| 1201 |
+
for (const url of serverUrls) {
|
| 1202 |
+
try {
|
| 1203 |
+
console.log(`Trying server URL: ${url}`);
|
| 1204 |
+
response = await fetch(url, {
|
| 1205 |
+
method: 'POST',
|
| 1206 |
+
body: formData,
|
| 1207 |
+
timeout: 30000 // 30 seconds timeout
|
| 1208 |
+
});
|
| 1209 |
+
|
| 1210 |
+
if (response.ok) {
|
| 1211 |
+
console.log(`Successfully connected to: ${url}`);
|
| 1212 |
+
break;
|
| 1213 |
+
}
|
| 1214 |
+
} catch (error) {
|
| 1215 |
+
console.log(`Failed to connect to ${url}:`, error.message);
|
| 1216 |
+
lastError = error;
|
| 1217 |
+
continue;
|
| 1218 |
+
}
|
| 1219 |
+
}
|
| 1220 |
+
|
| 1221 |
+
if (!response || !response.ok) {
|
| 1222 |
+
throw new Error(`Server connection failed. Last error: ${lastError?.message || 'Unknown error'}`);
|
| 1223 |
+
}
|
| 1224 |
+
|
| 1225 |
+
const result = await response.json();
|
| 1226 |
+
|
| 1227 |
+
// Debug logging for translation analysis
|
| 1228 |
+
console.log('๐ SERVER RESPONSE ANALYSIS:');
|
| 1229 |
+
console.log('- Success:', result.success);
|
| 1230 |
+
console.log('- Translation enabled:', result.translation_enabled);
|
| 1231 |
+
console.log('- Translation success:', result.translation_success);
|
| 1232 |
+
console.log('- Original text:', result.original_text?.substring(0, 50) + '...');
|
| 1233 |
+
console.log('- Translated text:', result.translated_text?.substring(0, 50) + '...');
|
| 1234 |
+
console.log('- Target language:', result.target_language);
|
| 1235 |
+
console.log('- Detected language:', result.language_detected);
|
| 1236 |
+
console.log('- Are texts different?:', result.translated_text !== result.original_text);
|
| 1237 |
+
|
| 1238 |
+
if (result.success) {
|
| 1239 |
+
state.currentResult = result;
|
| 1240 |
+
console.log('๐พ STORED RESULT IN STATE:');
|
| 1241 |
+
console.log('- Complete result object:', result);
|
| 1242 |
+
console.log('- Available keys:', Object.keys(result));
|
| 1243 |
+
console.log('- state.currentResult set to:', state.currentResult);
|
| 1244 |
+
displayResults(result);
|
| 1245 |
+
updateUIMode('result');
|
| 1246 |
+
showMessage(translations.success || 'Success!', 'success');
|
| 1247 |
+
} else {
|
| 1248 |
+
throw new Error(result.error || 'Processing failed');
|
| 1249 |
+
}
|
| 1250 |
+
} catch (error) {
|
| 1251 |
+
updateUIMode('review');
|
| 1252 |
+
const errorMessage = `${translations.error_occurred || 'Error'}: ${error.message}`;
|
| 1253 |
+
showMessage(errorMessage, 'error');
|
| 1254 |
+
console.error("Processing Error:", error);
|
| 1255 |
+
|
| 1256 |
+
// Show detailed error for debugging
|
| 1257 |
+
const detailedError = document.createElement('div');
|
| 1258 |
+
detailedError.className = 'error-message';
|
| 1259 |
+
detailedError.innerHTML = `
|
| 1260 |
+
<strong>Connection Error Details:</strong><br>
|
| 1261 |
+
โข Make sure the recorder server is running on port 5001<br>
|
| 1262 |
+
โข Try running: <code>python recorder_server.py</code><br>
|
| 1263 |
+
โข Check if port 5001 is available<br>
|
| 1264 |
+
โข Error: ${error.message}
|
| 1265 |
+
`;
|
| 1266 |
+
elements.messageContainer.appendChild(detailedError);
|
| 1267 |
+
}
|
| 1268 |
+
}
|
| 1269 |
+
|
| 1270 |
+
function displayResults(result) {
|
| 1271 |
+
console.log('๐ฏ DISPLAY RESULTS FUNCTION:');
|
| 1272 |
+
console.log('- Translation enabled:', result.translation_enabled);
|
| 1273 |
+
console.log('- Has translated text:', !!result.translated_text);
|
| 1274 |
+
console.log('- Translation section element:', !!elements.translationSection);
|
| 1275 |
+
|
| 1276 |
+
// Display original transcription
|
| 1277 |
+
elements.originalTranscriptionBox.value = result.original_text || result.text || '';
|
| 1278 |
+
|
| 1279 |
+
// Always show translation section if translation toggle is enabled or we have translated text
|
| 1280 |
+
const shouldShowTranslation = (
|
| 1281 |
+
elements.translationToggle.checked ||
|
| 1282 |
+
(result.translated_text && result.translated_text.trim())
|
| 1283 |
+
);
|
| 1284 |
+
|
| 1285 |
+
console.log('- Should show translation:', shouldShowTranslation);
|
| 1286 |
+
console.log('- Translation toggle checked:', elements.translationToggle.checked);
|
| 1287 |
+
console.log('- Has translated text content:', !!(result.translated_text && result.translated_text.trim()));
|
| 1288 |
+
|
| 1289 |
+
if (shouldShowTranslation) {
|
| 1290 |
+
console.log('โ
Showing translation section');
|
| 1291 |
+
|
| 1292 |
+
// Set translated text or show placeholder if no translation
|
| 1293 |
+
if (result.translated_text && result.translated_text.trim()) {
|
| 1294 |
+
elements.translatedTranscriptionBox.value = result.translated_text;
|
| 1295 |
+
} else {
|
| 1296 |
+
elements.translatedTranscriptionBox.value = result.original_text || 'Translation in progress...';
|
| 1297 |
+
}
|
| 1298 |
+
|
| 1299 |
+
elements.translationSection.style.display = 'block';
|
| 1300 |
+
|
| 1301 |
+
// Update language info
|
| 1302 |
+
const detectedLangElement = document.getElementById('detected-language');
|
| 1303 |
+
const targetLangElement = document.getElementById('target-language-info');
|
| 1304 |
+
if (detectedLangElement) {
|
| 1305 |
+
const detectedLang = result.language_detected || 'Unknown';
|
| 1306 |
+
detectedLangElement.textContent = `Detected Language: ${detectedLang}`;
|
| 1307 |
+
}
|
| 1308 |
+
if (targetLangElement) {
|
| 1309 |
+
targetLangElement.textContent = getTargetLanguageName();
|
| 1310 |
+
}
|
| 1311 |
+
|
| 1312 |
+
// Add status indicator for translation quality
|
| 1313 |
+
const translationStatus = document.createElement('div');
|
| 1314 |
+
translationStatus.style.cssText = 'margin-top: 5px; font-size: 0.8em; padding: 3px 8px; border-radius: 4px;';
|
| 1315 |
+
|
| 1316 |
+
if (result.translation_success === true) {
|
| 1317 |
+
translationStatus.style.backgroundColor = '#d4edda';
|
| 1318 |
+
translationStatus.style.color = '#155724';
|
| 1319 |
+
translationStatus.textContent = 'โ Translation successful';
|
| 1320 |
+
} else if (!result.translated_text || result.translated_text === result.original_text) {
|
| 1321 |
+
translationStatus.style.backgroundColor = '#fff3cd';
|
| 1322 |
+
translationStatus.style.color = '#856404';
|
| 1323 |
+
translationStatus.textContent = 'โ Translation failed - showing original text';
|
| 1324 |
+
} else {
|
| 1325 |
+
translationStatus.style.backgroundColor = '#cce7ff';
|
| 1326 |
+
translationStatus.style.color = '#004085';
|
| 1327 |
+
translationStatus.textContent = '๐ Translation completed';
|
| 1328 |
+
}
|
| 1329 |
+
|
| 1330 |
+
// Remove existing status if any
|
| 1331 |
+
const existingStatus = elements.translationSection.querySelector('.translation-status');
|
| 1332 |
+
if (existingStatus) {
|
| 1333 |
+
existingStatus.remove();
|
| 1334 |
+
}
|
| 1335 |
+
|
| 1336 |
+
// Add status indicator
|
| 1337 |
+
translationStatus.className = 'translation-status';
|
| 1338 |
+
elements.translationSection.appendChild(translationStatus);
|
| 1339 |
+
|
| 1340 |
+
} else {
|
| 1341 |
+
console.log('โ Hiding translation section - conditions not met');
|
| 1342 |
+
elements.translationSection.style.display = 'none';
|
| 1343 |
+
}
|
| 1344 |
+
|
| 1345 |
+
// Display markers if available
|
| 1346 |
+
if (result.markers && result.markers.length > 0) {
|
| 1347 |
+
displayMarkers(result.markers);
|
| 1348 |
+
elements.markersSection.style.display = 'block';
|
| 1349 |
+
} else {
|
| 1350 |
+
elements.markersSection.style.display = 'none';
|
| 1351 |
+
}
|
| 1352 |
+
|
| 1353 |
+
// Show summary button after successful transcription
|
| 1354 |
+
const summaryActionSection = document.getElementById('summary-action-section');
|
| 1355 |
+
if (summaryActionSection) {
|
| 1356 |
+
summaryActionSection.style.display = 'block';
|
| 1357 |
+
}
|
| 1358 |
+
}
|
| 1359 |
+
|
| 1360 |
+
function displayMarkers(markers) {
|
| 1361 |
+
const markersList = elements.markersList;
|
| 1362 |
+
markersList.innerHTML = '';
|
| 1363 |
+
|
| 1364 |
+
markers.forEach((timestamp, index) => {
|
| 1365 |
+
const markerElement = document.createElement('span');
|
| 1366 |
+
markerElement.style.cssText = 'display: inline-block; margin: 2px 5px; padding: 2px 8px; background: #007bff; color: white; border-radius: 12px; font-size: 0.85em;';
|
| 1367 |
+
markerElement.textContent = `${index + 1}: ${formatTime(timestamp)}`;
|
| 1368 |
+
markersList.appendChild(markerElement);
|
| 1369 |
+
});
|
| 1370 |
+
}
|
| 1371 |
+
|
| 1372 |
+
function resetRecorder() {
|
| 1373 |
+
state = {
|
| 1374 |
+
...state,
|
| 1375 |
+
audioChunks: [],
|
| 1376 |
+
audioBlob: null,
|
| 1377 |
+
seconds: 0,
|
| 1378 |
+
markers: [],
|
| 1379 |
+
currentResult: null,
|
| 1380 |
+
currentSummary: null
|
| 1381 |
+
};
|
| 1382 |
+
clearInterval(state.timerInterval);
|
| 1383 |
+
elements.translationSection.style.display = 'none';
|
| 1384 |
+
elements.markersSection.style.display = 'none';
|
| 1385 |
+
|
| 1386 |
+
// Hide summary sections
|
| 1387 |
+
const summaryActionSection = document.getElementById('summary-action-section');
|
| 1388 |
+
const summarySection = document.getElementById('summary-section');
|
| 1389 |
+
if (summaryActionSection) summaryActionSection.style.display = 'none';
|
| 1390 |
+
if (summarySection) summarySection.style.display = 'none';
|
| 1391 |
+
|
| 1392 |
+
updateUIMode('idle');
|
| 1393 |
+
}
|
| 1394 |
+
|
| 1395 |
+
function updateUIMode(mode) {
|
| 1396 |
+
const isRecording = mode === 'recording';
|
| 1397 |
+
const isPaused = mode === 'paused';
|
| 1398 |
+
const isReview = mode === 'review';
|
| 1399 |
+
const isProcessing = mode === 'processing';
|
| 1400 |
+
const isResult = mode === 'result';
|
| 1401 |
+
const isIdle = mode === 'idle';
|
| 1402 |
+
|
| 1403 |
+
// Button states
|
| 1404 |
+
elements.recordButton.disabled = !isIdle;
|
| 1405 |
+
elements.stopButton.disabled = !isRecording && !isPaused;
|
| 1406 |
+
elements.pauseButton.disabled = !isRecording && !isPaused;
|
| 1407 |
+
elements.markButton.disabled = !isRecording && !isPaused;
|
| 1408 |
+
elements.extractButton.disabled = !isReview;
|
| 1409 |
+
elements.rerecordButton.disabled = !isReview && !isResult;
|
| 1410 |
+
|
| 1411 |
+
// UI Visibility
|
| 1412 |
+
elements.recordingControls.style.display = (isReview || isResult || isProcessing) ? 'none' : 'block';
|
| 1413 |
+
elements.reviewSection.style.display = (isReview || isResult) ? 'block' : 'none';
|
| 1414 |
+
elements.transcriptionContainer.style.display = isResult ? 'block' : 'none';
|
| 1415 |
+
elements.loadingIndicator.style.display = isProcessing ? 'block' : 'none';
|
| 1416 |
+
|
| 1417 |
+
// Recording indicator
|
| 1418 |
+
elements.recordingIndicator.style.display = isRecording ? 'block' : 'none';
|
| 1419 |
+
|
| 1420 |
+
// Button text updates
|
| 1421 |
+
const pauseTextElement = document.getElementById('pause-text');
|
| 1422 |
+
if (pauseTextElement) {
|
| 1423 |
+
pauseTextElement.textContent = isPaused ?
|
| 1424 |
+
(translations.resume_recording || 'Resume') :
|
| 1425 |
+
(translations.pause_recording || 'Pause');
|
| 1426 |
+
}
|
| 1427 |
+
|
| 1428 |
+
// Status updates
|
| 1429 |
+
if (isIdle) {
|
| 1430 |
+
elements.status.textContent = translations.ready_to_record || 'Ready to Record';
|
| 1431 |
+
elements.timer.textContent = '00:00:00';
|
| 1432 |
+
elements.audioLevelIndicator.style.width = '0%';
|
| 1433 |
+
} else if (isRecording) {
|
| 1434 |
+
elements.status.textContent = translations.recording || 'Recording...';
|
| 1435 |
+
} else if (isPaused) {
|
| 1436 |
+
elements.status.textContent = translations.paused || 'Paused';
|
| 1437 |
+
} else if (isReview) {
|
| 1438 |
+
elements.status.textContent = translations.review_recording || 'Review your recording';
|
| 1439 |
+
} else if (isProcessing) {
|
| 1440 |
+
elements.status.textContent = translations.processing || 'Processing... please wait';
|
| 1441 |
+
const loadingTextElement = document.getElementById('loading-text');
|
| 1442 |
+
if (loadingTextElement) {
|
| 1443 |
+
loadingTextElement.textContent = translations.processing || 'Processing...';
|
| 1444 |
+
}
|
| 1445 |
+
} else if (isResult) {
|
| 1446 |
+
elements.status.textContent = translations.processing_complete || 'Processing Complete!';
|
| 1447 |
+
}
|
| 1448 |
+
}
|
| 1449 |
+
|
| 1450 |
+
function startTimer() {
|
| 1451 |
+
state.seconds = 0;
|
| 1452 |
+
state.timerInterval = setInterval(() => {
|
| 1453 |
+
state.seconds++;
|
| 1454 |
+
elements.timer.textContent = formatTime(state.seconds);
|
| 1455 |
+
}, 1000);
|
| 1456 |
+
}
|
| 1457 |
+
|
| 1458 |
+
function formatTime(totalSeconds) {
|
| 1459 |
+
const hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0');
|
| 1460 |
+
const minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0');
|
| 1461 |
+
const seconds = (totalSeconds % 60).toString().padStart(2, '0');
|
| 1462 |
+
return `${hours}:${minutes}:${seconds}`;
|
| 1463 |
+
}
|
| 1464 |
+
|
| 1465 |
+
function visualizeAudio(stream) {
|
| 1466 |
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
| 1467 |
+
const analyser = audioContext.createAnalyser();
|
| 1468 |
+
const microphone = audioContext.createMediaStreamSource(stream);
|
| 1469 |
+
microphone.connect(analyser);
|
| 1470 |
+
analyser.fftSize = 256;
|
| 1471 |
+
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
| 1472 |
+
|
| 1473 |
+
function draw() {
|
| 1474 |
+
if (state.mediaRecorder && state.mediaRecorder.state === 'inactive') return;
|
| 1475 |
+
requestAnimationFrame(draw);
|
| 1476 |
+
analyser.getByteFrequencyData(dataArray);
|
| 1477 |
+
const avg = dataArray.reduce((a, b) => a + b, 0) / dataArray.length;
|
| 1478 |
+
elements.audioLevelIndicator.style.width = `${(avg / 128) * 100}%`;
|
| 1479 |
+
}
|
| 1480 |
+
draw();
|
| 1481 |
+
}
|
| 1482 |
+
|
| 1483 |
+
function showMessage(message, type = 'info') {
|
| 1484 |
+
const messageElement = document.createElement('div');
|
| 1485 |
+
messageElement.className = `${type}-message`;
|
| 1486 |
+
messageElement.textContent = message;
|
| 1487 |
+
|
| 1488 |
+
elements.messageContainer.innerHTML = '';
|
| 1489 |
+
elements.messageContainer.appendChild(messageElement);
|
| 1490 |
+
|
| 1491 |
+
// Auto-hide after 5 seconds
|
| 1492 |
+
setTimeout(() => {
|
| 1493 |
+
if (elements.messageContainer.contains(messageElement)) {
|
| 1494 |
+
elements.messageContainer.removeChild(messageElement);
|
| 1495 |
+
}
|
| 1496 |
+
}, 5000);
|
| 1497 |
+
}
|
| 1498 |
+
|
| 1499 |
+
// --- Keyboard Shortcuts ---
|
| 1500 |
+
document.addEventListener('keydown', function(event) {
|
| 1501 |
+
if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') return;
|
| 1502 |
+
|
| 1503 |
+
switch(event.code) {
|
| 1504 |
+
case 'Space':
|
| 1505 |
+
event.preventDefault();
|
| 1506 |
+
if (!elements.recordButton.disabled) {
|
| 1507 |
+
startRecording();
|
| 1508 |
+
} else if (!elements.stopButton.disabled) {
|
| 1509 |
+
stopRecording();
|
| 1510 |
+
}
|
| 1511 |
+
break;
|
| 1512 |
+
case 'KeyM':
|
| 1513 |
+
if (!elements.markButton.disabled) {
|
| 1514 |
+
markTimestamp();
|
| 1515 |
+
}
|
| 1516 |
+
break;
|
| 1517 |
+
case 'KeyP':
|
| 1518 |
+
if (!elements.pauseButton.disabled) {
|
| 1519 |
+
togglePause();
|
| 1520 |
+
}
|
| 1521 |
+
break;
|
| 1522 |
+
case 'KeyR':
|
| 1523 |
+
if (!elements.rerecordButton.disabled) {
|
| 1524 |
+
resetRecorder();
|
| 1525 |
+
}
|
| 1526 |
+
break;
|
| 1527 |
+
}
|
| 1528 |
+
});
|
| 1529 |
+
|
| 1530 |
+
// --- Server Status Check ---
|
| 1531 |
+
async function checkServerStatus() {
|
| 1532 |
+
const statusIndicator = document.createElement('div');
|
| 1533 |
+
statusIndicator.id = 'server-status';
|
| 1534 |
+
statusIndicator.style.cssText = `
|
| 1535 |
+
position: fixed;
|
| 1536 |
+
top: 10px;
|
| 1537 |
+
left: 10px;
|
| 1538 |
+
padding: 8px 12px;
|
| 1539 |
+
border-radius: 6px;
|
| 1540 |
+
font-size: 12px;
|
| 1541 |
+
font-weight: bold;
|
| 1542 |
+
z-index: 1000;
|
| 1543 |
+
transition: all 0.3s;
|
| 1544 |
+
`;
|
| 1545 |
+
document.body.appendChild(statusIndicator);
|
| 1546 |
+
|
| 1547 |
+
try {
|
| 1548 |
+
const response = await fetch('http://localhost:5001/record', { method: 'GET' });
|
| 1549 |
+
if (response.ok) {
|
| 1550 |
+
statusIndicator.textContent = '๐ข Server Connected';
|
| 1551 |
+
statusIndicator.style.background = '#d4edda';
|
| 1552 |
+
statusIndicator.style.color = '#155724';
|
| 1553 |
+
statusIndicator.style.border = '1px solid #c3e6cb';
|
| 1554 |
+
} else {
|
| 1555 |
+
throw new Error('Server not responding');
|
| 1556 |
+
}
|
| 1557 |
+
} catch (error) {
|
| 1558 |
+
statusIndicator.textContent = '๐ด Server Disconnected';
|
| 1559 |
+
statusIndicator.style.background = '#f8d7da';
|
| 1560 |
+
statusIndicator.style.color = '#721c24';
|
| 1561 |
+
statusIndicator.style.border = '1px solid #f5c6cb';
|
| 1562 |
+
|
| 1563 |
+
// Show connection instructions
|
| 1564 |
+
setTimeout(() => {
|
| 1565 |
+
if (statusIndicator.parentNode) {
|
| 1566 |
+
statusIndicator.innerHTML = `
|
| 1567 |
+
๐ด Server Offline<br>
|
| 1568 |
+
<small>Run: python recorder_server.py</small>
|
| 1569 |
+
`;
|
| 1570 |
+
}
|
| 1571 |
+
}, 2000);
|
| 1572 |
+
}
|
| 1573 |
+
}
|
| 1574 |
+
|
| 1575 |
+
// --- Summary and Notes System ---
|
| 1576 |
+
async function generateSummary() {
|
| 1577 |
+
console.log('๐ค GENERATE SUMMARY DEBUG:');
|
| 1578 |
+
console.log('- state.currentResult:', state.currentResult);
|
| 1579 |
+
console.log('- original_text:', state.currentResult?.original_text);
|
| 1580 |
+
console.log('- text:', state.currentResult?.text);
|
| 1581 |
+
console.log('- translated_text:', state.currentResult?.translated_text);
|
| 1582 |
+
|
| 1583 |
+
if (!state.currentResult) {
|
| 1584 |
+
console.log('โ No currentResult found');
|
| 1585 |
+
showMessage('ูุง ุชูุฌุฏ ูุชุงุฆุฌ ู
ุชุงุญุฉ ููู
ูุฎุต', 'error');
|
| 1586 |
+
return;
|
| 1587 |
+
}
|
| 1588 |
+
|
| 1589 |
+
const textToSummarize = state.currentResult.original_text || state.currentResult.text || state.currentResult.translated_text;
|
| 1590 |
+
|
| 1591 |
+
if (!textToSummarize || textToSummarize.trim() === '') {
|
| 1592 |
+
console.log('โ No text available for summarization');
|
| 1593 |
+
console.log('Available properties:', Object.keys(state.currentResult));
|
| 1594 |
+
showMessage('ูุง ููุฌุฏ ูุต ู
ุชุงุญ ููู
ูุฎุต', 'error');
|
| 1595 |
+
return;
|
| 1596 |
+
}
|
| 1597 |
+
|
| 1598 |
+
console.log('โ
Text to summarize:', textToSummarize.substring(0, 100) + '...');
|
| 1599 |
+
|
| 1600 |
+
const summaryBtn = document.getElementById('generateSummaryBtn');
|
| 1601 |
+
const originalContent = summaryBtn.innerHTML;
|
| 1602 |
+
summaryBtn.disabled = true;
|
| 1603 |
+
summaryBtn.innerHTML = '<span>โณ</span><span>ุฌุงุฑู ุฅูุดุงุก ุงูู
ูุฎุต...</span>';
|
| 1604 |
+
|
| 1605 |
+
try {
|
| 1606 |
+
console.log('๐ค Sending summarization request...');
|
| 1607 |
+
console.log('๐ค Request payload:', {
|
| 1608 |
+
text: textToSummarize.substring(0, 100) + '...',
|
| 1609 |
+
language: elements.targetLanguage.value || 'arabic'
|
| 1610 |
+
});
|
| 1611 |
+
|
| 1612 |
+
const response = await fetch('http://localhost:5001/summarize', {
|
| 1613 |
+
method: 'POST',
|
| 1614 |
+
headers: {
|
| 1615 |
+
'Content-Type': 'application/json',
|
| 1616 |
+
'Accept': 'application/json'
|
| 1617 |
+
},
|
| 1618 |
+
body: JSON.stringify({
|
| 1619 |
+
text: textToSummarize,
|
| 1620 |
+
language: elements.targetLanguage.value || 'arabic',
|
| 1621 |
+
type: 'full'
|
| 1622 |
+
})
|
| 1623 |
+
});
|
| 1624 |
+
|
| 1625 |
+
console.log('๐ฅ Response status:', response.status);
|
| 1626 |
+
console.log('๐ฅ Response headers:', response.headers);
|
| 1627 |
+
|
| 1628 |
+
if (response.ok) {
|
| 1629 |
+
const data = await response.json();
|
| 1630 |
+
console.log('๐ฅ Summarization response:', data);
|
| 1631 |
+
|
| 1632 |
+
if (data.success) {
|
| 1633 |
+
// ุงูุชุนุงู
ู ู
ุน ุงูุงุณุชุฌุงุจุฉ ุงูุฌุฏูุฏุฉ
|
| 1634 |
+
if (data.summary) {
|
| 1635 |
+
displaySummaryResults(data.summary);
|
| 1636 |
+
showMessage('ุชู
ุฅูุดุงุก ุงูู
ูุฎุต ุจูุฌุงุญ!', 'success');
|
| 1637 |
+
} else {
|
| 1638 |
+
console.error('โ No summary in response:', data);
|
| 1639 |
+
showMessage('ูู
ูุชู
ุงูุนุซูุฑ ุนูู ู
ูุฎุต ูู ุงูุงุณุชุฌุงุจุฉ', 'error');
|
| 1640 |
+
}
|
| 1641 |
+
} else {
|
| 1642 |
+
throw new Error(data.error || 'ูุดู ูู ุฅูุดุงุก ุงูู
ูุฎุต');
|
| 1643 |
+
}
|
| 1644 |
+
} else {
|
| 1645 |
+
const errorText = await response.text();
|
| 1646 |
+
console.error('โ HTTP Error:', response.status, errorText);
|
| 1647 |
+
throw new Error(`ุฎุทุฃ ูู ุงูุดุจูุฉ: ${response.status} - ${errorText}`);
|
| 1648 |
+
}
|
| 1649 |
+
} catch (error) {
|
| 1650 |
+
console.error('โ Summary generation error:', error);
|
| 1651 |
+
|
| 1652 |
+
// ุฑุณุงุฆู ุฎุทุฃ ู
ุญุฏุฏุฉ
|
| 1653 |
+
let errorMessage = 'ูุดู ูู ุฅูุดุงุก ุงูู
ูุฎุต';
|
| 1654 |
+
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
| 1655 |
+
errorMessage = 'ูุง ูู
ูู ุงูุงุชุตุงู ุจุฎุงุฏู
ุงูุชูุฎูุต - ุชุฃูุฏ ู
ู ุชุดุบูู ุงูุฎุงุฏู
';
|
| 1656 |
+
} else if (error.message.includes('CORS')) {
|
| 1657 |
+
errorMessage = 'ู
ุดููุฉ ูู ุฅุนุฏุงุฏุงุช CORS - ูุฌุจ ุฅุนุงุฏุฉ ุชุดุบูู ุงูุฎุงุฏู
';
|
| 1658 |
+
} else {
|
| 1659 |
+
errorMessage = error.message;
|
| 1660 |
+
}
|
| 1661 |
+
|
| 1662 |
+
showMessage(errorMessage, 'error');
|
| 1663 |
+
} finally {
|
| 1664 |
+
summaryBtn.disabled = false;
|
| 1665 |
+
summaryBtn.innerHTML = originalContent;
|
| 1666 |
+
}
|
| 1667 |
+
}
|
| 1668 |
+
|
| 1669 |
+
function displaySummaryResults(summary) {
|
| 1670 |
+
console.log('๐ Displaying summary results:', summary);
|
| 1671 |
+
|
| 1672 |
+
// Show the summary section
|
| 1673 |
+
document.getElementById('summary-section').style.display = 'block';
|
| 1674 |
+
|
| 1675 |
+
// ุงูุชุนุงู
ู ู
ุน ุชูุณูู ุงูุงุณุชุฌุงุจุฉ - ูุฏ ูููู ุงููุต ู
ุจุงุดุฑุฉ ุฃู ูุงุฆู
|
| 1676 |
+
let summaryText = '';
|
| 1677 |
+
if (typeof summary === 'string') {
|
| 1678 |
+
summaryText = summary;
|
| 1679 |
+
} else if (summary && summary.main_summary) {
|
| 1680 |
+
summaryText = summary.main_summary;
|
| 1681 |
+
} else if (summary && typeof summary === 'object') {
|
| 1682 |
+
// ุฅุฐุง ูุงูุช ุงูุงุณุชุฌุงุจุฉ ูุงุฆูุ ูุฃุฎุฐ ุงูู
ุญุชูู ุงููุตู
|
| 1683 |
+
summaryText = JSON.stringify(summary, null, 2);
|
| 1684 |
+
}
|
| 1685 |
+
|
| 1686 |
+
// Display main summary
|
| 1687 |
+
const summaryTextSection = document.getElementById('summary-text-section');
|
| 1688 |
+
const summaryTextElement = document.getElementById('summary-text');
|
| 1689 |
+
if (summaryTextSection && summaryTextElement && summaryText) {
|
| 1690 |
+
summaryTextElement.innerHTML = summaryText.replace(/\n/g, '<br>');
|
| 1691 |
+
summaryTextSection.style.display = 'block';
|
| 1692 |
+
}
|
| 1693 |
+
|
| 1694 |
+
// Display key points (ุฅุฐุง ูุงูุช ู
ุชููุฑุฉ)
|
| 1695 |
+
const keyPointsSection = document.getElementById('key-points-section');
|
| 1696 |
+
const keyPointsList = document.getElementById('key-points-list');
|
| 1697 |
+
if (keyPointsSection && keyPointsList && summary && summary.key_points && Array.isArray(summary.key_points)) {
|
| 1698 |
+
keyPointsList.innerHTML = summary.key_points.map(point => `<li>${point}</li>`).join('');
|
| 1699 |
+
keyPointsSection.style.display = 'block';
|
| 1700 |
+
}
|
| 1701 |
+
|
| 1702 |
+
// Display review questions (ุฅุฐุง ูุงูุช ู
ุชููุฑุฉ)
|
| 1703 |
+
const questionsSection = document.getElementById('questions-section');
|
| 1704 |
+
const questionsList = document.getElementById('questions-list');
|
| 1705 |
+
if (questionsSection && questionsList && summary && summary.review_questions && Array.isArray(summary.review_questions)) {
|
| 1706 |
+
questionsList.innerHTML = summary.review_questions.map(question => `<li>${question}</li>`).join('');
|
| 1707 |
+
questionsSection.style.display = 'block';
|
| 1708 |
+
}
|
| 1709 |
+
|
| 1710 |
+
// Show save note section
|
| 1711 |
+
const saveNoteSection = document.getElementById('save-note-section');
|
| 1712 |
+
if (saveNoteSection) {
|
| 1713 |
+
saveNoteSection.style.display = 'block';
|
| 1714 |
+
}
|
| 1715 |
+
|
| 1716 |
+
// Store current summary in state for saving
|
| 1717 |
+
state.currentSummary = summary;
|
| 1718 |
+
}
|
| 1719 |
+
|
| 1720 |
+
async function saveNote() {
|
| 1721 |
+
if (!state.currentSummary) {
|
| 1722 |
+
showMessage('ูุง ููุฌุฏ ู
ูุฎุต ู
ุชุงุญ ููุญูุธ', 'error');
|
| 1723 |
+
return;
|
| 1724 |
+
}
|
| 1725 |
+
|
| 1726 |
+
const title = document.getElementById('noteTitle').value.trim();
|
| 1727 |
+
const tags = document.getElementById('noteTags').value.trim();
|
| 1728 |
+
|
| 1729 |
+
if (!title) {
|
| 1730 |
+
showMessage('ูุฑุฌู ุฅุฏุฎุงู ุนููุงู ููู
ุฐูุฑุฉ', 'error');
|
| 1731 |
+
return;
|
| 1732 |
+
}
|
| 1733 |
+
|
| 1734 |
+
const saveBtn = document.getElementById('saveNoteBtn');
|
| 1735 |
+
const originalText = saveBtn.textContent;
|
| 1736 |
+
saveBtn.disabled = true;
|
| 1737 |
+
saveBtn.textContent = 'ุฌุงุฑู ุงูุญูุธ...';
|
| 1738 |
+
|
| 1739 |
+
try {
|
| 1740 |
+
const contentToSave = state.currentResult.original_text || state.currentResult.text;
|
| 1741 |
+
const response = await fetch('http://localhost:5001/notes', {
|
| 1742 |
+
method: 'POST',
|
| 1743 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1744 |
+
body: JSON.stringify({
|
| 1745 |
+
title: title,
|
| 1746 |
+
content: contentToSave,
|
| 1747 |
+
summary: state.currentSummary,
|
| 1748 |
+
tags: tags ? tags.split(',').map(tag => tag.trim()) : [],
|
| 1749 |
+
markers: state.markers
|
| 1750 |
+
})
|
| 1751 |
+
});
|
| 1752 |
+
|
| 1753 |
+
if (response.ok) {
|
| 1754 |
+
const data = await response.json();
|
| 1755 |
+
if (data.success) {
|
| 1756 |
+
showMessage('ุชู
ุญูุธ ุงูู
ุฐูุฑุฉ ุจูุฌุงุญ!', 'success');
|
| 1757 |
+
// Clear form
|
| 1758 |
+
document.getElementById('noteTitle').value = '';
|
| 1759 |
+
document.getElementById('noteTags').value = '';
|
| 1760 |
+
// Refresh notes list
|
| 1761 |
+
loadNotesList();
|
| 1762 |
+
} else {
|
| 1763 |
+
throw new Error(data.error || 'Failed to save note');
|
| 1764 |
+
}
|
| 1765 |
+
} else {
|
| 1766 |
+
throw new Error('Network error while saving note');
|
| 1767 |
+
}
|
| 1768 |
+
} catch (error) {
|
| 1769 |
+
console.error('Note saving error:', error);
|
| 1770 |
+
showMessage('ูุดู ูู ุญูุธ ุงูู
ุฐูุฑุฉ: ' + error.message, 'error');
|
| 1771 |
+
} finally {
|
| 1772 |
+
saveBtn.disabled = false;
|
| 1773 |
+
saveBtn.textContent = originalText;
|
| 1774 |
+
}
|
| 1775 |
+
}
|
| 1776 |
+
|
| 1777 |
+
async function loadNotesList() {
|
| 1778 |
+
try {
|
| 1779 |
+
const response = await fetch('http://localhost:5001/notes');
|
| 1780 |
+
if (response.ok) {
|
| 1781 |
+
const data = await response.json();
|
| 1782 |
+
if (data.success) {
|
| 1783 |
+
displayNotesList(data.notes);
|
| 1784 |
+
}
|
| 1785 |
+
}
|
| 1786 |
+
} catch (error) {
|
| 1787 |
+
console.error('Failed to load notes:', error);
|
| 1788 |
+
}
|
| 1789 |
+
}
|
| 1790 |
+
|
| 1791 |
+
function displayNotesList(notes) {
|
| 1792 |
+
const notesContainer = document.getElementById('notesContainer');
|
| 1793 |
+
|
| 1794 |
+
if (notes.length === 0) {
|
| 1795 |
+
notesContainer.innerHTML = '<p class="text-center text-muted">No saved notes yet</p>';
|
| 1796 |
+
return;
|
| 1797 |
+
}
|
| 1798 |
+
|
| 1799 |
+
notesContainer.innerHTML = notes.map(note => `
|
| 1800 |
+
<div class="note-item" data-note-id="${note.id}">
|
| 1801 |
+
<div class="note-header">
|
| 1802 |
+
<h4>${note.title}</h4>
|
| 1803 |
+
<span class="note-date">${new Date(note.created_at).toLocaleDateString()}</span>
|
| 1804 |
+
</div>
|
| 1805 |
+
<div class="note-summary">
|
| 1806 |
+
${note.summary ? note.summary.main_summary.substring(0, 150) + '...' : 'No summary available'}
|
| 1807 |
+
</div>
|
| 1808 |
+
<div class="note-tags">
|
| 1809 |
+
${note.tags ? note.tags.map(tag => `<span class="tag">${tag}</span>`).join('') : ''}
|
| 1810 |
+
</div>
|
| 1811 |
+
<div class="note-actions">
|
| 1812 |
+
<button onclick="viewNote(${note.id})" class="btn-secondary">View</button>
|
| 1813 |
+
<button onclick="deleteNote(${note.id})" class="btn-danger">Delete</button>
|
| 1814 |
+
</div>
|
| 1815 |
+
</div>
|
| 1816 |
+
`).join('');
|
| 1817 |
+
}
|
| 1818 |
+
|
| 1819 |
+
async function viewNote(noteId) {
|
| 1820 |
+
try {
|
| 1821 |
+
const response = await fetch(`http://localhost:5001/notes/${noteId}`);
|
| 1822 |
+
if (response.ok) {
|
| 1823 |
+
const data = await response.json();
|
| 1824 |
+
if (data.success) {
|
| 1825 |
+
displayNoteDetails(data.note);
|
| 1826 |
+
}
|
| 1827 |
+
}
|
| 1828 |
+
} catch (error) {
|
| 1829 |
+
console.error('Failed to load note:', error);
|
| 1830 |
+
showMessage('Failed to load note details', 'error');
|
| 1831 |
+
}
|
| 1832 |
+
}
|
| 1833 |
+
|
| 1834 |
+
function displayNoteDetails(note) {
|
| 1835 |
+
// Create modal or expand view for note details
|
| 1836 |
+
const modal = document.createElement('div');
|
| 1837 |
+
modal.className = 'note-modal';
|
| 1838 |
+
modal.innerHTML = `
|
| 1839 |
+
<div class="note-modal-content">
|
| 1840 |
+
<div class="note-modal-header">
|
| 1841 |
+
<h2>${note.title}</h2>
|
| 1842 |
+
<button onclick="closeNoteModal()" class="close-btn">×</button>
|
| 1843 |
+
</div>
|
| 1844 |
+
<div class="note-modal-body">
|
| 1845 |
+
<div class="note-section">
|
| 1846 |
+
<h3>Summary</h3>
|
| 1847 |
+
<p>${note.summary ? note.summary.main_summary : 'No summary available'}</p>
|
| 1848 |
+
</div>
|
| 1849 |
+
${note.summary && note.summary.key_points ? `
|
| 1850 |
+
<div class="note-section">
|
| 1851 |
+
<h3>Key Points</h3>
|
| 1852 |
+
<ul>${note.summary.key_points.map(point => `<li>${point}</li>`).join('')}</ul>
|
| 1853 |
+
</div>
|
| 1854 |
+
` : ''}
|
| 1855 |
+
<div class="note-section">
|
| 1856 |
+
<h3>Full Transcription</h3>
|
| 1857 |
+
<p>${note.content}</p>
|
| 1858 |
+
</div>
|
| 1859 |
+
${note.tags && note.tags.length > 0 ? `
|
| 1860 |
+
<div class="note-section">
|
| 1861 |
+
<h3>Tags</h3>
|
| 1862 |
+
<div class="note-tags">
|
| 1863 |
+
${note.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
|
| 1864 |
+
</div>
|
| 1865 |
+
</div>
|
| 1866 |
+
` : ''}
|
| 1867 |
+
</div>
|
| 1868 |
+
</div>
|
| 1869 |
+
`;
|
| 1870 |
+
document.body.appendChild(modal);
|
| 1871 |
+
}
|
| 1872 |
+
|
| 1873 |
+
function closeNoteModal() {
|
| 1874 |
+
const modal = document.querySelector('.note-modal');
|
| 1875 |
+
if (modal) {
|
| 1876 |
+
modal.remove();
|
| 1877 |
+
}
|
| 1878 |
+
}
|
| 1879 |
+
|
| 1880 |
+
async function deleteNote(noteId) {
|
| 1881 |
+
if (!confirm('Are you sure you want to delete this note?')) {
|
| 1882 |
+
return;
|
| 1883 |
+
}
|
| 1884 |
+
|
| 1885 |
+
try {
|
| 1886 |
+
const response = await fetch(`http://localhost:5001/notes/${noteId}`, {
|
| 1887 |
+
method: 'DELETE'
|
| 1888 |
+
});
|
| 1889 |
+
|
| 1890 |
+
if (response.ok) {
|
| 1891 |
+
const data = await response.json();
|
| 1892 |
+
if (data.success) {
|
| 1893 |
+
showMessage('Note deleted successfully', 'success');
|
| 1894 |
+
loadNotesList();
|
| 1895 |
+
} else {
|
| 1896 |
+
throw new Error(data.error || 'Failed to delete note');
|
| 1897 |
+
}
|
| 1898 |
+
} else {
|
| 1899 |
+
throw new Error('Network error while deleting note');
|
| 1900 |
+
}
|
| 1901 |
+
} catch (error) {
|
| 1902 |
+
console.error('Delete note error:', error);
|
| 1903 |
+
showMessage('Failed to delete note: ' + error.message, 'error');
|
| 1904 |
+
}
|
| 1905 |
+
}
|
| 1906 |
+
|
| 1907 |
+
function searchNotes() {
|
| 1908 |
+
const searchTerm = document.getElementById('notesSearch').value.toLowerCase();
|
| 1909 |
+
const noteItems = document.querySelectorAll('.note-item');
|
| 1910 |
+
|
| 1911 |
+
noteItems.forEach(item => {
|
| 1912 |
+
const title = item.querySelector('h4').textContent.toLowerCase();
|
| 1913 |
+
const summary = item.querySelector('.note-summary').textContent.toLowerCase();
|
| 1914 |
+
const tags = item.querySelector('.note-tags').textContent.toLowerCase();
|
| 1915 |
+
|
| 1916 |
+
if (title.includes(searchTerm) || summary.includes(searchTerm) || tags.includes(searchTerm)) {
|
| 1917 |
+
item.style.display = 'block';
|
| 1918 |
+
} else {
|
| 1919 |
+
item.style.display = 'none';
|
| 1920 |
+
}
|
| 1921 |
+
});
|
| 1922 |
+
}
|
| 1923 |
+
|
| 1924 |
+
// --- Initialization ---
|
| 1925 |
+
async function initialize() {
|
| 1926 |
+
await loadTranslations(currentLanguage);
|
| 1927 |
+
await checkServerStatus();
|
| 1928 |
+
toggleTranslationUI();
|
| 1929 |
+
updateTargetLanguageInfo();
|
| 1930 |
+
resetRecorder();
|
| 1931 |
+
|
| 1932 |
+
// Load existing notes
|
| 1933 |
+
loadNotesList();
|
| 1934 |
+
|
| 1935 |
+
// Add event listeners for summary and notes
|
| 1936 |
+
const generateSummaryBtn = document.getElementById('generateSummaryBtn');
|
| 1937 |
+
const saveNoteBtn = document.getElementById('saveNoteBtn');
|
| 1938 |
+
const notesSearch = document.getElementById('notesSearch');
|
| 1939 |
+
|
| 1940 |
+
if (generateSummaryBtn) {
|
| 1941 |
+
generateSummaryBtn.addEventListener('click', generateSummary);
|
| 1942 |
+
}
|
| 1943 |
+
if (saveNoteBtn) {
|
| 1944 |
+
saveNoteBtn.addEventListener('click', saveNote);
|
| 1945 |
+
}
|
| 1946 |
+
if (notesSearch) {
|
| 1947 |
+
notesSearch.addEventListener('input', searchNotes);
|
| 1948 |
+
}
|
| 1949 |
+
|
| 1950 |
+
// Check server status every 30 seconds
|
| 1951 |
+
setInterval(checkServerStatus, 30000);
|
| 1952 |
+
}
|
| 1953 |
+
|
| 1954 |
+
// Start the application
|
| 1955 |
+
initialize();
|
| 1956 |
+
</script>
|
| 1957 |
+
</body>
|
| 1958 |
+
</html>
|
test_recording.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
ุงุฎุชุจุงุฑ ุฒุฑ ุงูุชุณุฌูู - ู
ุญุงูุงุฉ ู
ูู ุตูุชู
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import io
|
| 9 |
+
import wave
|
| 10 |
+
import struct
|
| 11 |
+
import random
|
| 12 |
+
|
| 13 |
+
def create_test_audio():
|
| 14 |
+
"""ุฅูุดุงุก ู
ูู ุตูุชู ุชุฌุฑูุจู"""
|
| 15 |
+
# ุฅูุดุงุก ุตูุช ุชุฌุฑูุจู (silence ู
ุน ุจุนุถ ุงูุถูุถุงุก)
|
| 16 |
+
duration = 2 # ุซุงููุชูู
|
| 17 |
+
sample_rate = 44100
|
| 18 |
+
samples = duration * sample_rate
|
| 19 |
+
|
| 20 |
+
# ุฅูุดุงุก ุจูุงูุงุช ุตูุชูุฉ ุจุณูุทุฉ
|
| 21 |
+
audio_data = []
|
| 22 |
+
for i in range(samples):
|
| 23 |
+
# ุถูุถุงุก ุจุณูุทุฉ
|
| 24 |
+
value = int(random.uniform(-1000, 1000))
|
| 25 |
+
audio_data.append(value)
|
| 26 |
+
|
| 27 |
+
# ุฅูุดุงุก ู
ูู WAV ูู ุงูุฐุงูุฑุฉ
|
| 28 |
+
buffer = io.BytesIO()
|
| 29 |
+
with wave.open(buffer, 'wb') as wav_file:
|
| 30 |
+
wav_file.setnchannels(1) # Mono
|
| 31 |
+
wav_file.setsampwidth(2) # 16-bit
|
| 32 |
+
wav_file.setframerate(sample_rate)
|
| 33 |
+
|
| 34 |
+
# ูุชุงุจุฉ ุงูุจูุงูุงุช
|
| 35 |
+
for sample in audio_data:
|
| 36 |
+
wav_file.writeframes(struct.pack('<h', sample))
|
| 37 |
+
|
| 38 |
+
buffer.seek(0)
|
| 39 |
+
return buffer
|
| 40 |
+
|
| 41 |
+
def test_recording_endpoint():
|
| 42 |
+
"""ุงุฎุชุจุงุฑ endpoint ุงูุชุณุฌูู"""
|
| 43 |
+
print("๐๏ธ ุงุฎุชุจุงุฑ endpoint ุงูุชุณุฌูู...")
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
# ุฅูุดุงุก ู
ูู ุตูุชู ุชุฌุฑูุจู
|
| 47 |
+
audio_buffer = create_test_audio()
|
| 48 |
+
|
| 49 |
+
# ุฅุนุฏุงุฏ ุงูุจูุงูุงุช
|
| 50 |
+
files = {
|
| 51 |
+
'audio_data': ('test_audio.wav', audio_buffer, 'audio/wav')
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
data = {
|
| 55 |
+
'markers': '[]',
|
| 56 |
+
'target_language': 'ar',
|
| 57 |
+
'enable_translation': 'true'
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
print("๐ค ุฅุฑุณุงู ุทูุจ ุงูุชุณุฌูู...")
|
| 61 |
+
response = requests.post(
|
| 62 |
+
'http://localhost:5001/record',
|
| 63 |
+
files=files,
|
| 64 |
+
data=data,
|
| 65 |
+
timeout=30
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
print(f"Status Code: {response.status_code}")
|
| 69 |
+
|
| 70 |
+
if response.status_code == 200:
|
| 71 |
+
result = response.json()
|
| 72 |
+
print(f"Success: {result.get('success')}")
|
| 73 |
+
|
| 74 |
+
if result.get('success'):
|
| 75 |
+
print("โ
ุงูุชุณุฌูู ูุฌุญ!")
|
| 76 |
+
print(f"Original Text: {result.get('original_text', 'ุบูุฑ ู
ูุฌูุฏ')}")
|
| 77 |
+
print(f"Translation Enabled: {result.get('translation_enabled')}")
|
| 78 |
+
print(f"Target Language: {result.get('target_language')}")
|
| 79 |
+
return True
|
| 80 |
+
else:
|
| 81 |
+
print(f"โ ูุดู ุงูุชุณุฌูู: {result.get('error')}")
|
| 82 |
+
return False
|
| 83 |
+
else:
|
| 84 |
+
print(f"โ ุฎุทุฃ HTTP: {response.status_code}")
|
| 85 |
+
print(f"Response: {response.text}")
|
| 86 |
+
return False
|
| 87 |
+
|
| 88 |
+
except Exception as e:
|
| 89 |
+
print(f"โ ุฎุทุฃ ูู ุงุฎุชุจุงุฑ ุงูุชุณุฌูู: {e}")
|
| 90 |
+
return False
|
| 91 |
+
|
| 92 |
+
def test_options_request():
|
| 93 |
+
"""ุงุฎุชุจุงุฑ ุทูุจ OPTIONS ููุชุณุฌูู"""
|
| 94 |
+
print("\n๐ง ุงุฎุชุจุงุฑ ุทูุจ OPTIONS ููุชุณุฌูู...")
|
| 95 |
+
|
| 96 |
+
try:
|
| 97 |
+
response = requests.options('http://localhost:5001/record', timeout=5)
|
| 98 |
+
print(f"Status Code: {response.status_code}")
|
| 99 |
+
|
| 100 |
+
if response.status_code == 204:
|
| 101 |
+
print("โ
ุทูุจ OPTIONS ูุฌุญ")
|
| 102 |
+
return True
|
| 103 |
+
else:
|
| 104 |
+
print(f"โ ู
ุดููุฉ ูู ุทูุจ OPTIONS: {response.status_code}")
|
| 105 |
+
return False
|
| 106 |
+
|
| 107 |
+
except Exception as e:
|
| 108 |
+
print(f"โ ุฎุทุฃ ูู ุทูุจ OPTIONS: {e}")
|
| 109 |
+
return False
|
| 110 |
+
|
| 111 |
+
def main():
|
| 112 |
+
"""ุงูุฏุงูุฉ ุงูุฑุฆูุณูุฉ ูุงุฎุชุจุงุฑ ุงูุชุณุฌูู"""
|
| 113 |
+
print("๐ ุงุฎุชุจุงุฑ ูุธููุฉ ุงูุชุณุฌูู")
|
| 114 |
+
print("=" * 50)
|
| 115 |
+
|
| 116 |
+
# ุงุฎุชุจุงุฑ OPTIONS ุฃููุงู
|
| 117 |
+
options_ok = test_options_request()
|
| 118 |
+
|
| 119 |
+
if options_ok:
|
| 120 |
+
# ุงุฎุชุจุงุฑ ุงูุชุณุฌูู ุงููุนูู
|
| 121 |
+
recording_ok = test_recording_endpoint()
|
| 122 |
+
|
| 123 |
+
if recording_ok:
|
| 124 |
+
print("\n๐ ุฌู
ูุน ุงุฎุชุจุงุฑุงุช ุงูุชุณุฌูู ูุฌุญุช!")
|
| 125 |
+
print("โ
ุฒุฑ ุงูุชุณุฌูู ูุนู
ู ุจุดูู ู
ุซุงูู")
|
| 126 |
+
else:
|
| 127 |
+
print("\nโ ููุงู ู
ุดููุฉ ูู ูุธููุฉ ุงูุชุณุฌูู")
|
| 128 |
+
else:
|
| 129 |
+
print("\nโ ู
ุดููุฉ ูู CORS ููุชุณุฌูู")
|
| 130 |
+
|
| 131 |
+
print("=" * 50)
|
| 132 |
+
|
| 133 |
+
if __name__ == "__main__":
|
| 134 |
+
main()
|
test_server_translation.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# test_server_translation.py - Test server translation response
|
| 2 |
+
|
| 3 |
+
import requests
|
| 4 |
+
import json
|
| 5 |
+
import tempfile
|
| 6 |
+
import wave
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
def create_test_audio():
|
| 10 |
+
"""Create a simple test audio file"""
|
| 11 |
+
sample_rate = 16000
|
| 12 |
+
duration = 2 # seconds
|
| 13 |
+
frequency = 440 # Hz (A note)
|
| 14 |
+
|
| 15 |
+
# Generate sine wave
|
| 16 |
+
t = np.linspace(0, duration, int(sample_rate * duration))
|
| 17 |
+
audio_data = (0.3 * np.sin(2 * np.pi * frequency * t) * 32767).astype(np.int16)
|
| 18 |
+
|
| 19 |
+
# Create temporary WAV file
|
| 20 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp_file:
|
| 21 |
+
with wave.open(tmp_file.name, 'w') as wav_file:
|
| 22 |
+
wav_file.setnchannels(1) # Mono
|
| 23 |
+
wav_file.setsampwidth(2) # 2 bytes per sample
|
| 24 |
+
wav_file.setframerate(sample_rate)
|
| 25 |
+
wav_file.writeframes(audio_data.tobytes())
|
| 26 |
+
return tmp_file.name
|
| 27 |
+
|
| 28 |
+
def test_server_translation():
|
| 29 |
+
print("๐ Testing Server Translation Response...")
|
| 30 |
+
|
| 31 |
+
# Create test audio
|
| 32 |
+
audio_file = create_test_audio()
|
| 33 |
+
print(f"๐ Created test audio: {audio_file}")
|
| 34 |
+
|
| 35 |
+
try:
|
| 36 |
+
# Prepare request data
|
| 37 |
+
with open(audio_file, 'rb') as f:
|
| 38 |
+
files = {'audio': f}
|
| 39 |
+
data = {
|
| 40 |
+
'target_language': 'ar',
|
| 41 |
+
'enable_translation': 'true',
|
| 42 |
+
'markers': '[]'
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
# Send request to server
|
| 46 |
+
response = requests.post('http://localhost:5001/record', files=files, data=data)
|
| 47 |
+
|
| 48 |
+
if response.status_code == 200:
|
| 49 |
+
result = response.json()
|
| 50 |
+
print(f"โ
Server Response Received")
|
| 51 |
+
print(f"๐ Success: {result.get('success')}")
|
| 52 |
+
print(f"๐ค Original text: {result.get('original_text', 'None')}")
|
| 53 |
+
print(f"๐ Translated text: {result.get('translated_text', 'None')}")
|
| 54 |
+
print(f"๐ Translation enabled: {result.get('translation_enabled')}")
|
| 55 |
+
print(f"โ
Translation success: {result.get('translation_success')}")
|
| 56 |
+
print(f"๐ Target language: {result.get('target_language')}")
|
| 57 |
+
print(f"๐ Detected language: {result.get('language_detected')}")
|
| 58 |
+
|
| 59 |
+
# Check if texts are different
|
| 60 |
+
original = result.get('original_text', '')
|
| 61 |
+
translated = result.get('translated_text', '')
|
| 62 |
+
print(f"๐ Texts are different: {original != translated}")
|
| 63 |
+
|
| 64 |
+
else:
|
| 65 |
+
print(f"โ Server error: {response.status_code}")
|
| 66 |
+
print(f"Response: {response.text}")
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
print(f"โ Test failed: {str(e)}")
|
| 70 |
+
|
| 71 |
+
finally:
|
| 72 |
+
# Clean up
|
| 73 |
+
import os
|
| 74 |
+
if os.path.exists(audio_file):
|
| 75 |
+
os.unlink(audio_file)
|
| 76 |
+
|
| 77 |
+
if __name__ == "__main__":
|
| 78 |
+
test_server_translation()
|
test_servers.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
|
| 3 |
+
print('๐งช Testing servers...')
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
r1 = requests.get('http://localhost:5050', timeout=5)
|
| 7 |
+
print(f'โ
Streamlit: OK - {r1.status_code}')
|
| 8 |
+
except Exception as e:
|
| 9 |
+
print(f'โ Streamlit: FAILED - {e}')
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
r2 = requests.get('http://localhost:5001/record', timeout=5)
|
| 13 |
+
print(f'โ
Recorder: OK - {r2.status_code}')
|
| 14 |
+
except Exception as e:
|
| 15 |
+
print(f'โ Recorder: FAILED - {e}')
|
| 16 |
+
|
| 17 |
+
print('๐ Test completed')
|
test_summarize.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
ุงุฎุชุจุงุฑ ู
ุดููุฉ endpoint ุงูุชูุฎูุต
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
|
| 10 |
+
def test_cors_preflight():
|
| 11 |
+
"""ุงุฎุชุจุงุฑ ุทูุจ OPTIONS (CORS preflight)"""
|
| 12 |
+
print("๐ ุงุฎุชุจุงุฑ CORS preflight...")
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
response = requests.options('http://localhost:5001/summarize')
|
| 16 |
+
print(f"Status Code: {response.status_code}")
|
| 17 |
+
print(f"Headers: {dict(response.headers)}")
|
| 18 |
+
|
| 19 |
+
# ุชุญูู ู
ู CORS headers
|
| 20 |
+
cors_origin = response.headers.get('Access-Control-Allow-Origin')
|
| 21 |
+
print(f"CORS Origin: '{cors_origin}'")
|
| 22 |
+
|
| 23 |
+
if cors_origin and ',' in cors_origin:
|
| 24 |
+
print("โ ู
ุดููุฉ: CORS header ูุญุชูู ุนูู ููู
ู
ุชุนุฏุฏุฉ!")
|
| 25 |
+
return False
|
| 26 |
+
elif cors_origin == '*':
|
| 27 |
+
print("โ
CORS header ุตุญูุญ")
|
| 28 |
+
return True
|
| 29 |
+
else:
|
| 30 |
+
print(f"โ ๏ธ CORS header ุบูุฑ ู
ุชููุน: {cors_origin}")
|
| 31 |
+
return False
|
| 32 |
+
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print(f"โ ุฎุทุฃ ูู ุทูุจ OPTIONS: {e}")
|
| 35 |
+
return False
|
| 36 |
+
|
| 37 |
+
def test_summarize_endpoint():
|
| 38 |
+
"""ุงุฎุชุจุงุฑ endpoint ุงูุชูุฎูุต"""
|
| 39 |
+
print("\n๐ค ุงุฎุชุจุงุฑ endpoint ุงูุชูุฎูุต...")
|
| 40 |
+
|
| 41 |
+
test_data = {
|
| 42 |
+
"text": "Hello, how are you? What are you doing today? Tell me.",
|
| 43 |
+
"language": "arabic"
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
try:
|
| 47 |
+
response = requests.post(
|
| 48 |
+
'http://localhost:5001/summarize',
|
| 49 |
+
json=test_data,
|
| 50 |
+
headers={'Content-Type': 'application/json'}
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
print(f"Status Code: {response.status_code}")
|
| 54 |
+
print(f"Response: {response.text}")
|
| 55 |
+
|
| 56 |
+
if response.status_code == 200:
|
| 57 |
+
data = response.json()
|
| 58 |
+
if data.get('success'):
|
| 59 |
+
print("โ
ุงูุชูุฎูุต ูุฌุญ!")
|
| 60 |
+
return True
|
| 61 |
+
else:
|
| 62 |
+
print(f"โ ูุดู ุงูุชูุฎูุต: {data.get('error')}")
|
| 63 |
+
return False
|
| 64 |
+
else:
|
| 65 |
+
print(f"โ ุฎุทุฃ HTTP: {response.status_code}")
|
| 66 |
+
return False
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
print(f"โ ุฎุทุฃ ูู ุทูุจ POST: {e}")
|
| 70 |
+
return False
|
| 71 |
+
|
| 72 |
+
def test_server_status():
|
| 73 |
+
"""ุงุฎุชุจุงุฑ ุญุงูุฉ ุงูุฎุงุฏู
"""
|
| 74 |
+
print("๐ ุงุฎุชุจุงุฑ ุญุงูุฉ ุงูุฎุงุฏู
...")
|
| 75 |
+
|
| 76 |
+
try:
|
| 77 |
+
response = requests.get('http://localhost:5001/record')
|
| 78 |
+
print(f"Status Code: {response.status_code}")
|
| 79 |
+
|
| 80 |
+
if response.status_code == 200:
|
| 81 |
+
data = response.json()
|
| 82 |
+
print(f"Server Status: {data.get('status')}")
|
| 83 |
+
return True
|
| 84 |
+
else:
|
| 85 |
+
print(f"โ ุงูุฎุงุฏู
ุบูุฑ ู
ุชุงุญ: {response.status_code}")
|
| 86 |
+
return False
|
| 87 |
+
|
| 88 |
+
except Exception as e:
|
| 89 |
+
print(f"โ ูุง ูู
ูู ุงููุตูู ููุฎุงุฏู
: {e}")
|
| 90 |
+
return False
|
| 91 |
+
|
| 92 |
+
if __name__ == "__main__":
|
| 93 |
+
print("=" * 50)
|
| 94 |
+
print("๐ ุจุฏุก ุงุฎุชุจุงุฑ ู
ุดููุฉ ุงูุชูุฎูุต")
|
| 95 |
+
print("=" * 50)
|
| 96 |
+
|
| 97 |
+
# ุงุฎุชุจุงุฑ ุญุงูุฉ ุงูุฎุงุฏู
|
| 98 |
+
server_ok = test_server_status()
|
| 99 |
+
|
| 100 |
+
if server_ok:
|
| 101 |
+
# ุงุฎุชุจุงุฑ CORS
|
| 102 |
+
cors_ok = test_cors_preflight()
|
| 103 |
+
|
| 104 |
+
if cors_ok:
|
| 105 |
+
# ุงุฎุชุจุงุฑ ุงูุชูุฎูุต
|
| 106 |
+
summarize_ok = test_summarize_endpoint()
|
| 107 |
+
|
| 108 |
+
if summarize_ok:
|
| 109 |
+
print("\n๐ ุฌู
ูุน ุงูุงุฎุชุจุงุฑุงุช ูุฌุญุช!")
|
| 110 |
+
else:
|
| 111 |
+
print("\nโ ูุดู ูู ุงุฎุชุจุงุฑ ุงูุชูุฎูุต")
|
| 112 |
+
else:
|
| 113 |
+
print("\nโ ู
ุดููุฉ ูู CORS - ูุฌุจ ุฅุนุงุฏุฉ ุชุดุบูู ุงูุฎุงุฏู
")
|
| 114 |
+
else:
|
| 115 |
+
print("\nโ ุงูุฎุงุฏู
ุบูุฑ ู
ุชุงุญ - ูุฌุจ ุชุดุบููู ุฃููุงู")
|
| 116 |
+
|
| 117 |
+
print("=" * 50)
|
test_summary_button.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
ุงุฎุชุจุงุฑ ุณุฑูุน ูู
ุจุณุท ูุฒุฑ ุงูุชูุฎูุต
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
|
| 10 |
+
def test_summary_button():
|
| 11 |
+
"""ู
ุญุงูุงุฉ ููุณ ุงูุทูุจ ุงูุฐู ูุฑุณูู ุฒุฑ ุงูุชูุฎูุต"""
|
| 12 |
+
|
| 13 |
+
print("๐ค ุงุฎุชุจุงุฑ ุฒุฑ ุงูุชูุฎูุต...")
|
| 14 |
+
print("=" * 50)
|
| 15 |
+
|
| 16 |
+
# ููุณ ุงูุจูุงูุงุช ุงูุชู ุชุฑุณููุง ุงููุงุฌูุฉ
|
| 17 |
+
test_data = {
|
| 18 |
+
"text": "Hello, how are you? What are you doing today? Tell me.",
|
| 19 |
+
"language": "arabic",
|
| 20 |
+
"type": "full"
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
print("๐ค ุฅุฑุณุงู ุทูุจ ุงูุชูุฎูุต...")
|
| 25 |
+
print(f"ุงูุจูุงูุงุช: {test_data}")
|
| 26 |
+
|
| 27 |
+
response = requests.post(
|
| 28 |
+
'http://localhost:5001/summarize',
|
| 29 |
+
json=test_data,
|
| 30 |
+
headers={
|
| 31 |
+
'Content-Type': 'application/json',
|
| 32 |
+
'Accept': 'application/json'
|
| 33 |
+
},
|
| 34 |
+
timeout=30
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
print(f"\n๐ฅ ุงุณุชูุงู
ุงูุฑุฏ:")
|
| 38 |
+
print(f"Status Code: {response.status_code}")
|
| 39 |
+
print(f"Headers: {dict(response.headers)}")
|
| 40 |
+
|
| 41 |
+
if response.status_code == 200:
|
| 42 |
+
data = response.json()
|
| 43 |
+
print(f"\nโ
ูุฌุญ ุงูุชูุฎูุต!")
|
| 44 |
+
print(f"Keys ูู ุงูุฑุฏ: {list(data.keys())}")
|
| 45 |
+
|
| 46 |
+
if data.get('success'):
|
| 47 |
+
print(f"ููุน ุงูุชูุฎูุต: {data.get('type')}")
|
| 48 |
+
|
| 49 |
+
if 'summary' in data:
|
| 50 |
+
summary = data['summary']
|
| 51 |
+
print(f"\nุงูู
ูุฎุต:")
|
| 52 |
+
print("-" * 30)
|
| 53 |
+
print(summary)
|
| 54 |
+
print("-" * 30)
|
| 55 |
+
return True
|
| 56 |
+
else:
|
| 57 |
+
print("โ ๏ธ ูุง ููุฌุฏ ู
ูุฎุต ูู ุงูุฑุฏ")
|
| 58 |
+
return False
|
| 59 |
+
else:
|
| 60 |
+
print(f"โ ูุดู ุงูุชูุฎูุต: {data.get('error')}")
|
| 61 |
+
return False
|
| 62 |
+
else:
|
| 63 |
+
print(f"โ ุฎุทุฃ HTTP: {response.status_code}")
|
| 64 |
+
print(f"ุงูุฑุฏ: {response.text}")
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
except Exception as e:
|
| 68 |
+
print(f"โ ุฎุทุฃ ูู ุงูุทูุจ: {e}")
|
| 69 |
+
return False
|
| 70 |
+
|
| 71 |
+
def test_cors_manually():
|
| 72 |
+
"""ุงุฎุชุจุงุฑ CORS ูุฏููุงู"""
|
| 73 |
+
print("\n๐ง ุงุฎุชุจุงุฑ CORS...")
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
# ุทูุจ OPTIONS ุฃููุงู
|
| 77 |
+
options_response = requests.options('http://localhost:5001/summarize')
|
| 78 |
+
print(f"OPTIONS Status: {options_response.status_code}")
|
| 79 |
+
|
| 80 |
+
cors_origin = options_response.headers.get('Access-Control-Allow-Origin')
|
| 81 |
+
print(f"CORS Origin: '{cors_origin}'")
|
| 82 |
+
|
| 83 |
+
if cors_origin == '*':
|
| 84 |
+
print("โ
CORS ุตุญูุญ")
|
| 85 |
+
return True
|
| 86 |
+
else:
|
| 87 |
+
print(f"โ ู
ุดููุฉ CORS: {cors_origin}")
|
| 88 |
+
return False
|
| 89 |
+
|
| 90 |
+
except Exception as e:
|
| 91 |
+
print(f"โ ุฎุทุฃ ูู ุงุฎุชุจุงุฑ CORS: {e}")
|
| 92 |
+
return False
|
| 93 |
+
|
| 94 |
+
if __name__ == "__main__":
|
| 95 |
+
print("๐ ุงุฎุชุจุงุฑ ุฒุฑ ุงูุชูุฎูุต ุงูู
ุญุฏุซ")
|
| 96 |
+
print("=" * 50)
|
| 97 |
+
|
| 98 |
+
# ุงุฎุชุจุงุฑ CORS
|
| 99 |
+
cors_ok = test_cors_manually()
|
| 100 |
+
|
| 101 |
+
if cors_ok:
|
| 102 |
+
# ุงุฎุชุจุงุฑ ุงูุชูุฎูุต
|
| 103 |
+
summary_ok = test_summary_button()
|
| 104 |
+
|
| 105 |
+
if summary_ok:
|
| 106 |
+
print("\n๐ ุฌู
ูุน ุงูุงุฎุชุจุงุฑุงุช ูุฌุญุช!")
|
| 107 |
+
print("โ
ุฒุฑ ุงูุชูุฎูุต ูุนู
ู ุจุดูู ุตุญูุญ")
|
| 108 |
+
else:
|
| 109 |
+
print("\nโ ููุงู ู
ุดููุฉ ูู ุงูุชูุฎูุต")
|
| 110 |
+
else:
|
| 111 |
+
print("\nโ ู
ุดููุฉ CORS ุชู
ูุน ุงูุชูุฎูุต")
|
| 112 |
+
|
| 113 |
+
print("=" * 50)
|
test_system.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
System Test Script for SyncMaster Enhanced
|
| 4 |
+
ุณูุฑูุจุช ุงุฎุชุจุงุฑ ุงููุธุงู
ูู SyncMaster ุงูู
ุญุณู
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
import traceback
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
def test_imports():
|
| 13 |
+
"""Test all required imports"""
|
| 14 |
+
print("๐ Testing imports...")
|
| 15 |
+
|
| 16 |
+
tests = [
|
| 17 |
+
('google.genai', 'Google Generative AI'),
|
| 18 |
+
('librosa', 'LibROSA'),
|
| 19 |
+
('soundfile', 'SoundFile'),
|
| 20 |
+
('streamlit', 'Streamlit'),
|
| 21 |
+
('flask', 'Flask'),
|
| 22 |
+
('dotenv', 'Python-dotenv'),
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
failed_imports = []
|
| 26 |
+
|
| 27 |
+
for module, name in tests:
|
| 28 |
+
try:
|
| 29 |
+
__import__(module)
|
| 30 |
+
print(f" โ
{name}")
|
| 31 |
+
except ImportError as e:
|
| 32 |
+
print(f" โ {name}: {e}")
|
| 33 |
+
failed_imports.append(name)
|
| 34 |
+
|
| 35 |
+
return len(failed_imports) == 0
|
| 36 |
+
|
| 37 |
+
def test_env_file():
|
| 38 |
+
"""Test environment configuration"""
|
| 39 |
+
print("\n๐ Testing environment...")
|
| 40 |
+
|
| 41 |
+
if not Path('.env').exists():
|
| 42 |
+
print(" โ .env file not found")
|
| 43 |
+
return False
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
from dotenv import load_dotenv
|
| 47 |
+
load_dotenv()
|
| 48 |
+
|
| 49 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 50 |
+
if not api_key:
|
| 51 |
+
print(" โ GEMINI_API_KEY not found in .env")
|
| 52 |
+
return False
|
| 53 |
+
elif api_key == "your_api_key_here":
|
| 54 |
+
print(" โ ๏ธ GEMINI_API_KEY needs to be replaced with actual key")
|
| 55 |
+
return False
|
| 56 |
+
else:
|
| 57 |
+
print(f" โ
GEMINI_API_KEY configured (length: {len(api_key)})")
|
| 58 |
+
return True
|
| 59 |
+
|
| 60 |
+
except Exception as e:
|
| 61 |
+
print(f" โ Error reading .env: {e}")
|
| 62 |
+
return False
|
| 63 |
+
|
| 64 |
+
def test_translator():
|
| 65 |
+
"""Test translator initialization"""
|
| 66 |
+
print("\n๐ Testing translator...")
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
from translator import AITranslator, get_translator
|
| 70 |
+
|
| 71 |
+
# Test direct initialization
|
| 72 |
+
translator = AITranslator()
|
| 73 |
+
if translator.init_error:
|
| 74 |
+
print(f" โ Translator init error: {translator.init_error}")
|
| 75 |
+
return False
|
| 76 |
+
|
| 77 |
+
print(" โ
Translator initialized successfully")
|
| 78 |
+
|
| 79 |
+
# Test simple translation
|
| 80 |
+
test_text = "Hello, this is a test"
|
| 81 |
+
translated, error = translator.translate_text(test_text, 'ar')
|
| 82 |
+
|
| 83 |
+
if error:
|
| 84 |
+
print(f" โ Translation test failed: {error}")
|
| 85 |
+
return False
|
| 86 |
+
elif translated:
|
| 87 |
+
print(f" โ
Translation test successful: '{test_text}' โ '{translated}'")
|
| 88 |
+
return True
|
| 89 |
+
else:
|
| 90 |
+
print(" โ ๏ธ Translation returned empty result")
|
| 91 |
+
return False
|
| 92 |
+
|
| 93 |
+
except Exception as e:
|
| 94 |
+
print(f" โ Translator test failed: {e}")
|
| 95 |
+
print(f" ๐ Traceback: {traceback.format_exc()}")
|
| 96 |
+
return False
|
| 97 |
+
|
| 98 |
+
def test_audio_processor():
|
| 99 |
+
"""Test audio processor initialization"""
|
| 100 |
+
print("\n๐ต Testing audio processor...")
|
| 101 |
+
|
| 102 |
+
try:
|
| 103 |
+
from audio_processor import AudioProcessor
|
| 104 |
+
|
| 105 |
+
processor = AudioProcessor()
|
| 106 |
+
if processor.init_error:
|
| 107 |
+
print(f" โ Audio processor init error: {processor.init_error}")
|
| 108 |
+
return False
|
| 109 |
+
|
| 110 |
+
print(" โ
Audio processor initialized successfully")
|
| 111 |
+
|
| 112 |
+
# Check if translator is available
|
| 113 |
+
if processor.translator:
|
| 114 |
+
print(" โ
Translator integration available")
|
| 115 |
+
else:
|
| 116 |
+
print(" โ ๏ธ Translator integration not available")
|
| 117 |
+
|
| 118 |
+
return True
|
| 119 |
+
|
| 120 |
+
except Exception as e:
|
| 121 |
+
print(f" โ Audio processor test failed: {e}")
|
| 122 |
+
print(f" ๐ Traceback: {traceback.format_exc()}")
|
| 123 |
+
return False
|
| 124 |
+
|
| 125 |
+
def test_server_components():
|
| 126 |
+
"""Test server components"""
|
| 127 |
+
print("\n๐ฅ๏ธ Testing server components...")
|
| 128 |
+
|
| 129 |
+
try:
|
| 130 |
+
from recorder_server import app
|
| 131 |
+
print(" โ
Recorder server imports OK")
|
| 132 |
+
|
| 133 |
+
# Test routes
|
| 134 |
+
with app.test_client() as client:
|
| 135 |
+
# Test health check
|
| 136 |
+
response = client.get('/record')
|
| 137 |
+
if response.status_code == 200:
|
| 138 |
+
print(" โ
Health check endpoint working")
|
| 139 |
+
else:
|
| 140 |
+
print(f" โ ๏ธ Health check returned {response.status_code}")
|
| 141 |
+
|
| 142 |
+
# Test languages endpoint
|
| 143 |
+
response = client.get('/languages')
|
| 144 |
+
if response.status_code == 200:
|
| 145 |
+
print(" โ
Languages endpoint working")
|
| 146 |
+
else:
|
| 147 |
+
print(f" โ ๏ธ Languages endpoint returned {response.status_code}")
|
| 148 |
+
|
| 149 |
+
return True
|
| 150 |
+
|
| 151 |
+
except Exception as e:
|
| 152 |
+
print(f" โ Server test failed: {e}")
|
| 153 |
+
print(f" ๐ Traceback: {traceback.format_exc()}")
|
| 154 |
+
return False
|
| 155 |
+
|
| 156 |
+
def test_ui_translations():
|
| 157 |
+
"""Test UI translations"""
|
| 158 |
+
print("\n๐จ Testing UI translations...")
|
| 159 |
+
|
| 160 |
+
try:
|
| 161 |
+
from translator import UI_TRANSLATIONS, get_translation
|
| 162 |
+
|
| 163 |
+
# Test English translations
|
| 164 |
+
en_keys = len(UI_TRANSLATIONS.get('en', {}))
|
| 165 |
+
ar_keys = len(UI_TRANSLATIONS.get('ar', {}))
|
| 166 |
+
|
| 167 |
+
print(f" โ
English translations: {en_keys} keys")
|
| 168 |
+
print(f" โ
Arabic translations: {ar_keys} keys")
|
| 169 |
+
|
| 170 |
+
if en_keys != ar_keys:
|
| 171 |
+
print(f" โ ๏ธ Translation key count mismatch: EN={en_keys}, AR={ar_keys}")
|
| 172 |
+
|
| 173 |
+
# Test translation function
|
| 174 |
+
test_key = 'start_recording'
|
| 175 |
+
en_text = get_translation(test_key, 'en')
|
| 176 |
+
ar_text = get_translation(test_key, 'ar')
|
| 177 |
+
|
| 178 |
+
print(f" โ
Test translation: '{en_text}' โ '{ar_text}'")
|
| 179 |
+
|
| 180 |
+
return True
|
| 181 |
+
|
| 182 |
+
except Exception as e:
|
| 183 |
+
print(f" โ UI translations test failed: {e}")
|
| 184 |
+
return False
|
| 185 |
+
|
| 186 |
+
def main():
|
| 187 |
+
"""Run all tests"""
|
| 188 |
+
print("=" * 60)
|
| 189 |
+
print("๐งช SyncMaster Enhanced - System Test")
|
| 190 |
+
print("ุงุฎุชุจุงุฑ ุงููุธุงู
ุงูุดุงู
ู ูู SyncMaster ุงูู
ุญุณู")
|
| 191 |
+
print("=" * 60)
|
| 192 |
+
|
| 193 |
+
# Change to script directory
|
| 194 |
+
script_dir = Path(__file__).parent
|
| 195 |
+
os.chdir(script_dir)
|
| 196 |
+
print(f"๐ Working directory: {script_dir}")
|
| 197 |
+
|
| 198 |
+
tests = [
|
| 199 |
+
("Import Test", test_imports),
|
| 200 |
+
("Environment Test", test_env_file),
|
| 201 |
+
("Translator Test", test_translator),
|
| 202 |
+
("Audio Processor Test", test_audio_processor),
|
| 203 |
+
("Server Components Test", test_server_components),
|
| 204 |
+
("UI Translations Test", test_ui_translations),
|
| 205 |
+
]
|
| 206 |
+
|
| 207 |
+
results = {}
|
| 208 |
+
|
| 209 |
+
for test_name, test_func in tests:
|
| 210 |
+
try:
|
| 211 |
+
results[test_name] = test_func()
|
| 212 |
+
except Exception as e:
|
| 213 |
+
print(f"\nโ {test_name} crashed: {e}")
|
| 214 |
+
results[test_name] = False
|
| 215 |
+
|
| 216 |
+
# Summary
|
| 217 |
+
print("\n" + "=" * 60)
|
| 218 |
+
print("๐ TEST SUMMARY / ู
ูุฎุต ุงูุงุฎุชุจุงุฑ")
|
| 219 |
+
print("=" * 60)
|
| 220 |
+
|
| 221 |
+
passed = sum(results.values())
|
| 222 |
+
total = len(results)
|
| 223 |
+
|
| 224 |
+
for test_name, result in results.items():
|
| 225 |
+
status = "โ
PASS" if result else "โ FAIL"
|
| 226 |
+
print(f" {status} {test_name}")
|
| 227 |
+
|
| 228 |
+
print(f"\n๐ฏ Results: {passed}/{total} tests passed")
|
| 229 |
+
|
| 230 |
+
if passed == total:
|
| 231 |
+
print("๐ All tests passed! System is ready to use.")
|
| 232 |
+
print("๐ You can now run: python start_debug.py")
|
| 233 |
+
else:
|
| 234 |
+
print("โ ๏ธ Some tests failed. Please fix the issues before running the application.")
|
| 235 |
+
print("๐ Check the error messages above for details.")
|
| 236 |
+
|
| 237 |
+
return passed == total
|
| 238 |
+
|
| 239 |
+
if __name__ == "__main__":
|
| 240 |
+
success = main()
|
| 241 |
+
sys.exit(0 if success else 1)
|
test_translation.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# test_translation.py - Quick translation test
|
| 2 |
+
|
| 3 |
+
from translator import get_translator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
def test_translation():
|
| 7 |
+
print("๐ Testing AI Translation System...")
|
| 8 |
+
|
| 9 |
+
# Get translator instance
|
| 10 |
+
translator = get_translator()
|
| 11 |
+
|
| 12 |
+
if translator.init_error:
|
| 13 |
+
print(f"โ Translation Error: {translator.init_error}")
|
| 14 |
+
return
|
| 15 |
+
|
| 16 |
+
print("โ
Translator initialized successfully")
|
| 17 |
+
|
| 18 |
+
# Test translation
|
| 19 |
+
test_text = "Hello, this is a test for the translation system"
|
| 20 |
+
print(f"\n๐ค Original text: {test_text}")
|
| 21 |
+
|
| 22 |
+
translated_text, error = translator.translate_text(test_text, target_language='ar')
|
| 23 |
+
|
| 24 |
+
if translated_text:
|
| 25 |
+
print(f"โ
Arabic translation: {translated_text}")
|
| 26 |
+
print(f"๐ Translation success: True")
|
| 27 |
+
print(f"๐ Texts are different: {translated_text != test_text}")
|
| 28 |
+
else:
|
| 29 |
+
print(f"โ Translation failed: {error}")
|
| 30 |
+
|
| 31 |
+
# Test language detection
|
| 32 |
+
detected_lang, detect_error = translator.detect_language(test_text)
|
| 33 |
+
if detected_lang:
|
| 34 |
+
print(f"๐ Detected language: {detected_lang}")
|
| 35 |
+
else:
|
| 36 |
+
print(f"โ Language detection failed: {detect_error}")
|
| 37 |
+
|
| 38 |
+
if __name__ == "__main__":
|
| 39 |
+
test_translation()
|
tmp1dop8abr.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tmp1g8szy_i.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tmp43q9lzdr.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tmp52qryepl.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tmp6i84ktkz.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|