Phoe2004 commited on
Commit
c99da26
ยท
verified ยท
1 Parent(s): 93ebfeb

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +61 -1
  2. index.html +163 -5
app.py CHANGED
@@ -1057,7 +1057,67 @@ def api_gemini_voices():
1057
  ]
1058
  return jsonify(ok=True, voices=voices)
1059
 
1060
- # โ”€โ”€ VIDEO CLIP PREVIEW โ€” full download, cache for reuse, show first 10s โ”€โ”€
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1061
  @app.route('/api/preview_clip', methods=['POST'])
1062
  def api_preview_clip():
1063
  try:
 
1057
  ]
1058
  return jsonify(ok=True, voices=voices)
1059
 
1060
+ # โ”€โ”€ TTS FREE โ”€โ”€
1061
+ def _tts_day_key():
1062
+ from datetime import date
1063
+ return 'tts_day_' + date.today().isoformat() # e.g. tts_day_2025-06-15
1064
+
1065
+ @app.route('/api/tts_usage')
1066
+ def api_tts_usage():
1067
+ u = request.args.get('u', '').strip()
1068
+ db = load_db()
1069
+ if u not in db['users']:
1070
+ return jsonify(used=0)
1071
+ used = db['users'][u].get(_tts_day_key(), 0)
1072
+ return jsonify(used=used)
1073
+
1074
+ @app.route('/api/tts_free', methods=['POST'])
1075
+ def api_tts_free():
1076
+ try:
1077
+ u = (request.form.get('u') or '').strip()
1078
+ text = (request.form.get('text') or '').strip()
1079
+ voice = request.form.get('voice', 'my-MM-ThihaNeural')
1080
+ speed = int(request.form.get('speed', 30))
1081
+
1082
+ if not u:
1083
+ return jsonify(ok=False, msg='โŒ Not logged in')
1084
+ if not text:
1085
+ return jsonify(ok=False, msg='โŒ No text provided')
1086
+
1087
+ db = load_db()
1088
+ is_adm = (u == ADMIN_U)
1089
+
1090
+ if not is_adm:
1091
+ if u not in db['users']:
1092
+ return jsonify(ok=False, msg='โŒ User not found')
1093
+ if db['users'][u].get('banned'):
1094
+ return jsonify(ok=False, msg='โŒ Account banned')
1095
+ day_key = _tts_day_key()
1096
+ used = db['users'][u].get(day_key, 0)
1097
+ if used >= 10:
1098
+ return jsonify(ok=False, msg='โŒ แ€แ€…แ€บแ€›แ€€แ€บ 10 แ€€แ€ผแ€ญแ€™แ€บ แ€€แ€”แ€ทแ€บแ€žแ€แ€บแ€แ€ปแ€€แ€บ แ€•แ€ผแ€Šแ€ทแ€บแ€žแ€ฝแ€ฌแ€ธแ€•แ€ผแ€ฎแŠ แ€™แ€”แ€€แ€บแ€–แ€ผแ€”แ€บ แ€•แ€ผแ€”แ€บแ€œแ€ฌแ€•แ€ซ')
1099
+ db['users'][u][day_key] = used + 1
1100
+ save_db(db)
1101
+ new_used = used + 1
1102
+ else:
1103
+ new_used = 0
1104
+
1105
+ out = str(OUTPUT_DIR / f'tts_free_{uuid.uuid4().hex[:10]}.mp3')
1106
+ rate = f'+{speed}%' if speed >= 0 else f'{speed}%'
1107
+
1108
+ import asyncio
1109
+
1110
+ async def _do_tts():
1111
+ await edge_tts.Communicate(text, voice, rate=rate).save(out)
1112
+
1113
+ asyncio.run(_do_tts())
1114
+
1115
+ return jsonify(ok=True, url='/outputs/' + Path(out).name, used=new_used)
1116
+ except Exception as e:
1117
+ import traceback; traceback.print_exc()
1118
+ return jsonify(ok=False, msg=str(e))
1119
+
1120
+
1121
  @app.route('/api/preview_clip', methods=['POST'])
1122
  def api_preview_clip():
1123
  try:
index.html CHANGED
@@ -695,6 +695,9 @@ body{background:var(--bg);color:var(--text);font-family:var(--F);min-height:100v
695
  <button class="mode-btn" id="mode-srt" onclick="switchMode('srt')">
696
  <i class="fas fa-closed-captioning"></i> TRANSLATE SRT
697
  </button>
 
 
 
698
  </div>
699
 
700
  <!-- โ•โ• MOVIE RECAP MODE โ•โ• -->
@@ -1402,6 +1405,82 @@ body{background:var(--bg);color:var(--text);font-family:var(--F);min-height:100v
1402
  </div><!-- /ab -->
1403
  </div><!-- /section-srt -->
1404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1405
 
1406
  </div><!-- /app-screen -->
1407
 
@@ -2432,11 +2511,11 @@ document.addEventListener('keydown',e=>{if(e.key==='Enter'&&document.getElementB
2432
 
2433
  /* โ•โ• MODE SWITCH โ•โ• */
2434
  function switchMode(m){
2435
- const isRecap=m==='recap';
2436
- document.getElementById('section-recap').style.display=isRecap?'':'none';
2437
- document.getElementById('section-srt').style.display=isRecap?'none':'';
2438
- document.getElementById('mode-recap').classList.toggle('on',isRecap);
2439
- document.getElementById('mode-srt').classList.toggle('on',!isRecap);
2440
  }
2441
 
2442
  /* โ•โ• TRANSLATE SRT โ•โ• */
@@ -3161,6 +3240,85 @@ function srtReset(){
3161
  srtResetBtn();
3162
  }
3163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3164
 
3165
  </script>
3166
 
 
695
  <button class="mode-btn" id="mode-srt" onclick="switchMode('srt')">
696
  <i class="fas fa-closed-captioning"></i> TRANSLATE SRT
697
  </button>
698
+ <button class="mode-btn" id="mode-tts" onclick="switchMode('tts')">
699
+ <i class="fas fa-microphone-alt"></i> TEXT TO SPEECH
700
+ </button>
701
  </div>
702
 
703
  <!-- โ•โ• MOVIE RECAP MODE โ•โ• -->
 
1405
  </div><!-- /ab -->
1406
  </div><!-- /section-srt -->
1407
 
1408
+ <!-- โ•โ• TEXT TO SPEECH MODE โ•โ• -->
1409
+ <div id="section-tts" style="display:none">
1410
+ <div class="ab">
1411
+
1412
+ <div class="card">
1413
+ <div class="ch">
1414
+ <div class="ci ci-p"><i class="fas fa-microphone-alt"></i></div>
1415
+ <div class="ct">Text to Speech <span style="font-size:.72rem;color:#10b981;font-weight:800;background:#d1fae5;padding:2px 8px;border-radius:20px;margin-left:6px">FREE</span></div>
1416
+ <span id="tts-usage-badge" style="margin-left:auto;font-size:.75rem;font-weight:700;padding:3px 10px;border-radius:20px;background:#ede9fe;color:#7c3aed">0/10 แ€€แ€ผแ€ญแ€™แ€บ</span>
1417
+ </div>
1418
+
1419
+ <!-- Voice Select -->
1420
+ <div style="margin-bottom:12px">
1421
+ <label style="font-size:.8rem;font-weight:700;color:var(--text);display:block;margin-bottom:6px"><i class="fas fa-volume-up" style="color:var(--primary);margin-right:4px"></i>แ€กแ€žแ€ถแ€›แ€ฝแ€ฑแ€ธแ€แ€ปแ€šแ€บแ€›แ€”แ€บ</label>
1422
+ <select id="tts-voice" style="width:100%;padding:9px 12px;border:1.5px solid var(--border);border-radius:8px;font-family:var(--F);font-size:.83rem;background:#fff;color:var(--text)">
1423
+ <option value="my-MM-ThihaNeural">Thiha (Male) ๐Ÿ‡ฒ๐Ÿ‡ฒ</option>
1424
+ <option value="my-MM-NilarNeural">Nilar (Female) ๐Ÿ‡ฒ๐Ÿ‡ฒ</option>
1425
+ <option value="en-US-AndrewMultilingualNeural">Andrew Multilingual (Male)</option>
1426
+ <option value="en-US-AvaMultilingualNeural">Ava Multilingual (Female)</option>
1427
+ <option value="en-US-BrianMultilingualNeural">Brian Multilingual (Male)</option>
1428
+ <option value="en-US-EmmaMultilingualNeural">Emma Multilingual (Female)</option>
1429
+ <option value="en-US-JennyMultilingualNeural">Jenny Multilingual (Female)</option>
1430
+ <option value="en-US-RyanMultilingualNeural">Ryan Multilingual (Male)</option>
1431
+ <option value="th-TH-NiwatNeural">Niwat Thai (Male) ๐Ÿ‡น๐Ÿ‡ญ</option>
1432
+ <option value="th-TH-PremwadeeNeural">Premwadee Thai (Female) ๐Ÿ‡น๐Ÿ‡ญ</option>
1433
+ <option value="th-TH-AcharaNeural">Achara Thai (Female) ๐Ÿ‡น๐Ÿ‡ญ</option>
1434
+ </select>
1435
+ </div>
1436
+
1437
+ <!-- Speed -->
1438
+ <div style="margin-bottom:14px;display:flex;align-items:center;gap:10px">
1439
+ <label style="font-size:.8rem;font-weight:700;color:var(--text);white-space:nowrap"><i class="fas fa-tachometer-alt" style="color:var(--primary);margin-right:4px"></i>Speed</label>
1440
+ <input type="range" id="tts-speed" min="-50" max="100" value="30" style="flex:1">
1441
+ <span id="tts-speed-lbl" style="font-size:.8rem;font-weight:700;color:var(--primary);min-width:40px;text-align:right">+30%</span>
1442
+ </div>
1443
+
1444
+ <!-- Text Textarea -->
1445
+ <div style="margin-bottom:10px">
1446
+ <label style="font-size:.8rem;font-weight:700;color:var(--text);display:block;margin-bottom:6px"><i class="fas fa-align-left" style="color:var(--primary);margin-right:4px"></i>แ€…แ€ฌแ€žแ€ฌแ€ธแ€‘แ€Šแ€ทแ€บแ€›แ€”แ€บ <span style="color:var(--muted);font-weight:400">(unlimited)</span></label>
1447
+ <textarea id="tts-text" placeholder="แ€’แ€ฎแ€”แ€ฑแ€›แ€ฌแ€™แ€พแ€ฌ แ€…แ€ฌแ€žแ€ฌแ€ธแ€‘แ€Šแ€ทแ€บแ€•แ€ซโ€ฆ" style="width:100%;min-height:200px;padding:10px 12px;border:1.5px solid var(--border);border-radius:8px;font-family:var(--F);font-size:.85rem;resize:vertical;box-sizing:border-box;line-height:1.7;color:var(--text);background:#fafafa"></textarea>
1448
+ <div style="font-size:.72rem;color:var(--muted);margin-top:4px;text-align:right"><span id="tts-char-count">0</span> characters</div>
1449
+ </div>
1450
+
1451
+ <!-- TXT File Upload -->
1452
+ <div style="margin-bottom:16px">
1453
+ <label style="font-size:.8rem;font-weight:700;color:var(--text);display:block;margin-bottom:6px"><i class="fas fa-file-alt" style="color:var(--primary);margin-right:4px"></i>.txt แ€–แ€ญแ€ฏแ€„แ€บแ€แ€„แ€บแ€›แ€”แ€บ <span style="color:var(--muted);font-weight:400">(optional)</span></label>
1454
+ <div class="srt-drop" id="tts-drop-zone" style="padding:14px" onclick="document.getElementById('tts-file-inp').click()">
1455
+ <i class="fas fa-upload" style="font-size:1.2rem;color:var(--primary)"></i>
1456
+ <div class="srt-drop-t" id="tts-file-lbl">Click to upload .txt file</div>
1457
+ <input type="file" id="tts-file-inp" accept=".txt" style="display:none" onchange="ttsLoadFile(this)">
1458
+ </div>
1459
+ </div>
1460
+
1461
+ <!-- Generate Button -->
1462
+ <button id="tts-gen-btn" onclick="ttsGenerate()" style="width:100%;padding:13px;background:linear-gradient(135deg,var(--primary),#7c3aed);color:#fff;border:none;border-radius:10px;font-family:var(--F);font-size:.9rem;font-weight:800;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;transition:.2s">
1463
+ <i class="fas fa-microphone-alt"></i> Generate Audio
1464
+ </button>
1465
+
1466
+ <!-- Progress -->
1467
+ <div id="tts-prog" style="display:none;margin-top:12px">
1468
+ <div class="ph"><span class="ptit">๐ŸŽ™๏ธ TTS แ€‘แ€ฏแ€แ€บแ€”แ€ฑแ€žแ€Šแ€บโ€ฆ</span><span class="ptmr" id="tts-ptmr">โฑ 0s</span></div>
1469
+ </div>
1470
+
1471
+ <!-- Result / Preview -->
1472
+ <div id="tts-result" style="display:none;margin-top:14px;padding:14px;background:#f5f3ff;border-radius:10px;border:1.5px solid #c4b5fd">
1473
+ <div style="font-size:.82rem;font-weight:700;color:#7c3aed;margin-bottom:10px"><i class="fas fa-check-circle" style="margin-right:4px"></i>TTS Preview</div>
1474
+ <audio id="tts-audio" controls style="width:100%;border-radius:8px"></audio>
1475
+ <div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">
1476
+ <a id="tts-dl" href="#" download="tts_output.mp3" style="display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:var(--primary);color:#fff;border-radius:8px;font-size:.8rem;font-weight:700;text-decoration:none"><i class="fas fa-download"></i> Download MP3</a>
1477
+ </div>
1478
+ </div>
1479
+
1480
+ </div>
1481
+ </div>
1482
+ </div><!-- /section-tts -->
1483
+
1484
 
1485
  </div><!-- /app-screen -->
1486
 
 
2511
 
2512
  /* โ•โ• MODE SWITCH โ•โ• */
2513
  function switchMode(m){
2514
+ ['recap','srt','tts'].forEach(x=>{
2515
+ document.getElementById('section-'+x).style.display=x===m?'':'none';
2516
+ document.getElementById('mode-'+x).classList.toggle('on',x===m);
2517
+ });
2518
+ if(m==='tts') ttsLoadUsage();
2519
  }
2520
 
2521
  /* โ•โ• TRANSLATE SRT โ•โ• */
 
3240
  srtResetBtn();
3241
  }
3242
 
3243
+ /* โ•โ• TEXT TO SPEECH (FREE) โ•โ• */
3244
+ let TTS_TICK=null, TTS_T0=0;
3245
+
3246
+ (function(){
3247
+ const sp=document.getElementById('tts-speed');
3248
+ const tx=document.getElementById('tts-text');
3249
+ if(sp) sp.addEventListener('input',function(){
3250
+ document.getElementById('tts-speed-lbl').textContent=(this.value>=0?'+':'')+this.value+'%';
3251
+ });
3252
+ if(tx) tx.addEventListener('input',function(){
3253
+ document.getElementById('tts-char-count').textContent=this.value.length.toLocaleString();
3254
+ });
3255
+ })();
3256
+
3257
+ function ttsLoadFile(inp){
3258
+ const f=inp.files[0]; if(!f) return;
3259
+ const r=new FileReader();
3260
+ r.onload=e=>{
3261
+ document.getElementById('tts-text').value=e.target.result;
3262
+ document.getElementById('tts-char-count').textContent=e.target.result.length.toLocaleString();
3263
+ document.getElementById('tts-file-lbl').textContent='โœ… '+f.name;
3264
+ };
3265
+ r.onerror=()=>toast('โŒ แ€–แ€ญแ€ฏแ€„แ€บแ€–แ€แ€บแ€™แ€›แ€•แ€ซ');
3266
+ r.readAsText(f,'utf-8');
3267
+ }
3268
+
3269
+ function ttsLoadUsage(){
3270
+ if(!U) return;
3271
+ fetch('/api/tts_usage?u='+encodeURIComponent(U))
3272
+ .then(r=>r.json())
3273
+ .then(d=>{ document.getElementById('tts-usage-badge').textContent=(d.used||0)+'/10 แ€€แ€ผแ€ญแ€™แ€บ'; })
3274
+ .catch(()=>{});
3275
+ }
3276
+
3277
+ async function ttsGenerate(){
3278
+ if(!U){ toast('โŒ แ€€แ€ปแ€ฑแ€ธแ€‡แ€ฐแ€ธแ€•แ€ผแ€ฏแ แ€กแ€€แ€ฑแ€ฌแ€„แ€ทแ€บแ€แ€„แ€บแ€•แ€ซ'); return; }
3279
+ const txt=(document.getElementById('tts-text').value||'').trim();
3280
+ if(!txt){ toast('โŒ แ€…แ€ฌแ€žแ€ฌแ€ธแ€‘แ€Šแ€ทแ€บแ€•แ€ซ'); return; }
3281
+ const voice=document.getElementById('tts-voice').value;
3282
+ const speed=document.getElementById('tts-speed').value;
3283
+ const btn=document.getElementById('tts-gen-btn');
3284
+
3285
+ btn.disabled=true;
3286
+ btn.innerHTML='<i class="fas fa-spinner fa-spin"></i> Generatingโ€ฆ';
3287
+ document.getElementById('tts-prog').style.display='';
3288
+ document.getElementById('tts-result').style.display='none';
3289
+ TTS_T0=Date.now();
3290
+ TTS_TICK=setInterval(()=>{
3291
+ document.getElementById('tts-ptmr').textContent='โฑ '+Math.floor((Date.now()-TTS_T0)/1000)+'s';
3292
+ },1000);
3293
+
3294
+ try{
3295
+ const fd=new FormData();
3296
+ fd.append('u',U);
3297
+ fd.append('text',txt);
3298
+ fd.append('voice',voice);
3299
+ fd.append('speed',speed);
3300
+ const res=await fetch('/api/tts_free',{method:'POST',body:fd});
3301
+ const d=await res.json();
3302
+ clearInterval(TTS_TICK);
3303
+ document.getElementById('tts-prog').style.display='none';
3304
+ btn.disabled=false;
3305
+ btn.innerHTML='<i class="fas fa-microphone-alt"></i> Generate Audio';
3306
+ if(!d.ok){ toast('โŒ '+d.msg); return; }
3307
+ const au=document.getElementById('tts-audio');
3308
+ au.src=d.url+'?t='+Date.now();
3309
+ document.getElementById('tts-dl').href=d.url;
3310
+ document.getElementById('tts-result').style.display='';
3311
+ document.getElementById('tts-usage-badge').textContent=d.used+'/10 แ€€แ€ผแ€ญแ€™แ€บ';
3312
+ toast('โœ… TTS แ€•แ€ผแ€ฎแ€ธแ€•แ€ผแ€ฎ!');
3313
+ } catch(e){
3314
+ clearInterval(TTS_TICK);
3315
+ document.getElementById('tts-prog').style.display='none';
3316
+ btn.disabled=false;
3317
+ btn.innerHTML='<i class="fas fa-microphone-alt"></i> Generate Audio';
3318
+ toast('โŒ Error: '+e.message);
3319
+ }
3320
+ }
3321
+
3322
 
3323
  </script>
3324