Spaces:
Running
Running
<html lang="fa"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Karim23657</title> | |
<link rel="stylesheet" href="/static/bootstrap.min.css"> | |
<script src="/static/jquery.min.js"></script> | |
<style> | |
@font-face { | |
font-family: 'Sahel'; | |
src: url('/static/sahel.ttf') format('truetype'); | |
font-weight: normal; | |
font-style: normal; | |
font-display: swap; | |
} | |
body { | |
font-family: Sahel, sans-serif; | |
background-color: #f4f4f9; | |
padding: 20px; | |
text-align: center; | |
direction: rtl; /* Right-to-left direction */ | |
} | |
.content { | |
padding: 20px; | |
text-align: center; | |
} | |
.tab-pane { | |
display: none; | |
} | |
.tab-pane.active { | |
display: block; | |
} | |
.visualizer { | |
display: flex; | |
justify-content: center; | |
align-items: flex-end; | |
height: 30px; | |
gap: 3px; | |
margin: 20px 0; | |
} | |
.bar { | |
width: 3px; | |
background: #1db9b9; | |
border-radius: 3px; | |
animation: bounce 1s infinite; | |
} | |
@keyframes bounce { | |
0%, | |
100% { | |
height: 5px; | |
} | |
50% { | |
height: 20px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container mt-5"> | |
<!-- Tabs --> | |
<ul class="nav nav-tabs"> | |
<li class="nav-item"> | |
<a class="nav-link active" href="#" data-target="#home">متن به گفتار 🗣️</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link" href="#" data-target="#menu1">ویرایش تلفظ</a> | |
</li> | |
<li class="nav-item"> | |
<a class="nav-link" href="#" data-target="#menu2">واژه های اخیر</a> | |
</li> | |
</ul> | |
<!-- Tab Content --> | |
<div class="tab-content"> | |
<div id="home" class="container tab-pane active content"><br> | |
<h3 class="mb-3">متن به گفتار 🗣️</h3> | |
<div class="mb-3"><a href="https://t.me/persian_tts" rel="nofollow" class="mt-3"><img src="https://camo.githubusercontent.com/5816246a32fe5752272b083398ef7ced06a11b0eb73d7c0bcefad490ba5f32b1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f54656c656772616d2d6368616e6e656c2d626c75653f7374796c653d666c61742d737175617265266c6f676f3d74656c656772616d" alt="Telegram channel" data-canonical-src="https://img.shields.io/badge/Telegram-channel-blue?style=flat-square&logo=telegram" style="max-width: 100%;"></a> | |
</div> | |
<div class="row"> | |
<form class="form col-md-6 input" dir="rtl" id="tts"> | |
<input type="hidden" name="tab" value="tts"> | |
<div class="form-group"> | |
<div class="rounded p-2 border" style="background-color: #f8f9fa;"> | |
<div class="form-group"> | |
<label for="textarea" class="d-flex justify-content-start">متن:</label> | |
<textarea class="form-control" id="textarea" rows="3" name="text" required></textarea> | |
</div> | |
<div class="form-group"> | |
<label class="d-flex justify-content-start">انتخاب صدا:</label> | |
{{ components|safe }} | |
</div> | |
</div> | |
</div> | |
<div class="form-group"> | |
<div class="row mb-3"> | |
<div class="col"> | |
<button class="btn btn-light btn-outline-dark btn-block" type="submit" value="submit">بگو</button> | |
</div> | |
<div class="col"> | |
<button class="btn btn-light btn-outline-dark btn-block" value="stop">توقف</button> | |
</div> | |
</div> | |
</div> | |
</form> | |
<div class="form-group col-md-6 output"> | |
<div class="rounded p-2 border" style="background-color: #f8f9fa;"> | |
<label for="tts-audio" class="d-flex justify-content-start">صوت:</label> | |
<audio controls id="tts-audio" class="col-12 result">Your browser does not support the audio element.</audio> | |
<label for="status" class="d-flex justify-content-start">وضعیت:</label> | |
<textarea class="form-control result" id="status" rows="2" disabled></textarea> | |
</div> | |
</div> | |
</div> | |
<div class="rounded p-2 border" dir="rtl" style="text-align: right;"> | |
<p>اینجا می توانید مدل های فارسی را با کتابخانه <a href="https://github.com/k2-fsa/sherpa-onnx">sherpa-onnx</a> امتحان کنید</p> | |
<p>در صورت تلفظ اشتباه ، واژه را در سربرگ <span style="color:#2980b9"><span style="background-color:#bdc3c7">ویرایش تلفظ </span></span> ارسال کنید.</p> | |
<p>بروزرسانی ها را در کانال تلگرام <a href="https://t.me/persian_tts">persian_tts@</a> دنبال کنید .</p> | |
<p>نظراتتون رو برای بهتر شدن برنامه در گروه تلگرام <a href="https://t.me/persian_tts_chat">persian_tts_chat@</a> بگید</p> | |
<p>کانال تلگرام : <a href="https://t.me/persian_tts">https://t.me/persian_tts</a></p> | |
<p>گیتهاب : <a href="https://github.com/karim23657/Persian-tts-coqui">https://github.com/karim23657/Persian-tts-coqui</a></p> | |
<p> </p> | |
<p> </p> | |
</div> | |
</div> | |
<div id="menu1" class="container tab-pane content"> | |
<br> | |
<h3 class="mb-3">ویرایش تلفظ واژه</h3> | |
<div class="row"> | |
<div class="form-group col-md-6"> | |
<form dir="rtl" class="form-group " id="phonemize"> | |
<input type="hidden" name="tab" value="phonemize"> | |
<div class="rounded p-2 border mb-3" style="background-color: #f8f9fa;"> | |
<div class="form-group row align-items-center"> | |
<div class="col-2"><label for="input_word" class="col-form-label">واژه:</label></div> | |
<div class="col-10"><input type="text" class="form-control" id="input_word" name="word" placeholder="واژه را وارد کنید"></div> | |
</div> | |
<div class="form-group row align-items-center"> | |
<div class="col-2"><label for="phonetics" class="d-flex justify-content-start">تلفظ:</label></div> | |
<div class="col-10"><input type="text" class="form-control result" id="phonetics" name="phonetic" placeholder="تلفظ را وارد کنید"></div> | |
</div> | |
</div> | |
<div class="form-group row align-items-center"> | |
<div class="col"><button class="btn btn-light btn-outline-dark btn-block" type="submit" value="phonemize">آوانویسی</button></div> | |
<div class="col"><button class="btn btn-light btn-outline-dark btn-block" type="submit" value="say">بگو</button></div> | |
<div class="col"><button class="btn btn-light btn-outline-dark btn-block" type="submit" value="send">ارسال واژه</button></div> | |
</div> | |
</form> | |
</div> | |
<div class="form-group col-md-6"> | |
<div class="rounded p-2 border" style="background-color: #f8f9fa;"> | |
<label for="audio" class="d-flex justify-content-start">صوت:</label> | |
<audio controls id="audio" class="col-12 result">Your browser does not support the audio element.</audio> | |
<label for="status" class="d-flex justify-content-start">وضعیت:</label> | |
<textarea class="form-control result" id="status" rows="2" disabled></textarea> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div id="menu2" class="container tab-pane content"><br> | |
<h3>واژه هایی که ثبت شده</h3> | |
<form dir="rtl" class="form-group " id="words"> | |
<input type="hidden" name="tab" value="words"> | |
<div class="form-group row align-items-center"> | |
<div class="col-6"><button class="btn btn-light btn-outline-dark btn-block" type="submit" value="words">بروز رسانی</button></div> | |
<div class="col-3"><a class="btn btn-light btn-outline-dark btn-block" href="/download/fa_dict">dict ⇩</a></div> | |
<div class="col-3"><a class="btn btn-light btn-outline-dark btn-block" href="/download/fa_extra">list ⇩</a></div> | |
</div> | |
<table class="table table-bordered table-striped result"> | |
<thead> | |
<tr> | |
<th>#</th> | |
<th>واژه</th> | |
<th>تلفظ</th> | |
</tr> | |
</thead> | |
<tbody> | |
</tbody> | |
</table> | |
</form> | |
</div> | |
</div> | |
</div> | |
<script> | |
$(document).ready(function() { | |
$('.nav-link').on('click', function(e) { | |
e.preventDefault(); | |
$('.nav-link').removeClass('active'); | |
$(this).addClass('active'); | |
var target = $(this).data('target'); | |
$('.tab-pane').removeClass('active'); | |
$(target).addClass('active'); | |
}); | |
let currentTaskId = null; | |
let resultCheckInterval = null; | |
function createLoadingOverlay(element) { | |
const overlay = $("<div>", { | |
class: "loading-overlay position-absolute top-0 start-0 d-flex justify-content-center align-items-center bg-secondary bg-opacity-50", | |
css: { | |
zIndex: 1000, | |
fontFamily: 'sans-serif' // Or a suitable Persian font | |
} | |
}); | |
// Use RTL layout for Persian text | |
overlay.html('<span class="spinner-border spinner-border-sm text-light ms-2" role="status"></span><span class="text-light mr-3" dir="rtl">در حال بارگذاری... <span id="loading-counter">0.00</span></span>'); | |
$("body").append(overlay); | |
let counter = 0.00; | |
const counterInterval = setInterval(() => { | |
counter = parseFloat((counter + 0.01).toFixed(2)); | |
$("#loading-counter", overlay).text(counter.toFixed(2)); | |
}, 10); | |
overlay.data('counterInterval', counterInterval); | |
overlay.data('element', element); | |
updateOverlayPosition(overlay); | |
$(window).on("resize.overlay scroll.overlay", function() { | |
updateOverlayPosition(overlay); | |
}); | |
return overlay; | |
} | |
function updateOverlayPosition(overlay) { | |
const element = overlay.data('element'); | |
if (!element || !element.length) { | |
return; | |
} | |
const elementPosition = element.offset(); | |
const elementWidth = element.outerWidth(); | |
const elementHeight = element.outerHeight(); | |
overlay.css({ | |
top: elementPosition.top, | |
left: elementPosition.left, | |
width: elementWidth, | |
height: elementHeight | |
}); | |
} | |
function destroyOverlayAll(elements) { | |
elements.each(function() { | |
const $this = $(this); | |
destroyOverlay($this); | |
}); | |
} | |
function destroyOverlay(element) { | |
const overlay = $(".loading-overlay").filter(function() { | |
return $(this).data('element')[0] === element[0]; | |
}); | |
if (overlay.length) { | |
clearInterval(overlay.data('counterInterval')); | |
$(window).off("resize.overlay scroll.overlay"); | |
overlay.remove(); | |
} | |
} | |
$('#tts').on('submit', function(e) { | |
e.preventDefault(); | |
const formData = new FormData(this); | |
$(this).parent().find('.result').each(function() { | |
const $this = $(this); | |
const overlay = createLoadingOverlay($this); | |
}); | |
const clickedButton = $(e.originalEvent.submitter); | |
if(clickedButton.val()=='submit'){ | |
clickedButton.prop('disabled',true) | |
$.ajax({ | |
url: '/submit', | |
type: 'POST', | |
data: formData, | |
contentType: false, | |
processData: false, | |
success: function(data) { | |
currentTaskId = data.task_id; | |
const position = data.position; | |
//$('#queuePosition').html(`Your task is in position: ${position}`); | |
checkResult(currentTaskId); | |
}, | |
error: function(xhr) { | |
destroyOverlayAll($('#tts').parent().find('.result')); | |
if (xhr.status === 429) { | |
//$('#queuePosition').html('Error: Task queue is full. Please try again later.'); | |
clickedButton.prop('disabled',false) | |
} | |
} | |
}); | |
}else if(clickedButton.val()=='stop'){ | |
if (currentTaskId) { | |
$.post(`/stop/${currentTaskId}`, function(data) { | |
destroyOverlayAll($('#tts').parent().find('.result')); | |
clearInterval(resultCheckInterval); | |
}).fail(function() { | |
//$('#queuePosition').html('Error stopping the task. It may have already completed.'); | |
}); | |
} | |
} | |
function checkResult(taskId) { | |
resultCheckInterval = setInterval(function() { | |
$.get(`/result/${taskId}/tts`, function(data) { | |
if (data.status === "completed") { | |
clearInterval(resultCheckInterval); | |
$('#status').val(data.result.status); | |
$('#tts-audio').attr('src', data.result.audio); // Update audio source | |
destroyOverlayAll($('#tts').parent().find('.result')); | |
clickedButton.prop('disabled',false) | |
} | |
}); | |
}, 1000); // Check every second for a faster response | |
} | |
}); | |
$('#phonemize').on('submit', function(e) { | |
e.preventDefault(); | |
const formData = new FormData(this); | |
let parentEl = $(this).parent().parent(); | |
let result_holders = parentEl.find('.result'); | |
result_holders.each(function() { | |
const $this = $(this); | |
const overlay = createLoadingOverlay($this); | |
}); | |
const clickedButton = $(e.originalEvent.submitter); | |
console.log(clickedButton.val()); | |
if(['say','phonemize','send'].includes(clickedButton.val())){ | |
clickedButton.prop('disabled',true); | |
formData.append('task',clickedButton.val()); | |
$.ajax({ | |
url: '/submit', | |
type: 'POST', | |
data: formData, | |
contentType: false, | |
processData: false, | |
success: function(data) { | |
currentTaskId = data.task_id; | |
const position = data.position; | |
//$('#queuePosition').html(`Your task is in position: ${position}`); | |
checkResult(currentTaskId); | |
}, | |
error: function(xhr) { | |
destroyOverlayAll(result_holders); | |
if (xhr.status === 429) { | |
//$('#queuePosition').html('Error: Task queue is full. Please try again later.'); | |
clickedButton.prop('disabled',false) | |
} | |
} | |
}); | |
} | |
function checkResult(taskId) { | |
resultCheckInterval = setInterval(function() { | |
$.get(`/result/${taskId}/phonemize`, function(data) { | |
if (data.status === "completed") { | |
clearInterval(resultCheckInterval); | |
parentEl.find('#phonetics').val(data.result.phonemes); | |
parentEl.find('#status').val(data.result.status); | |
parentEl.find('#audio').attr('src', data.result.audio); // Update audio source | |
destroyOverlayAll(result_holders); | |
clickedButton.prop('disabled',false) | |
} | |
}); | |
}, 1000); // Check every second for a faster response | |
} | |
}); | |
$('#words').on('submit', function(e) { | |
e.preventDefault(); | |
let parentEl = $(this).parent(); | |
let result_holders = parentEl.find('.result'); | |
const formData = new FormData(this); | |
result_holders.each(function() { | |
const $this = $(this); | |
const overlay = createLoadingOverlay($this); | |
}); | |
const clickedButton = $(e.originalEvent.submitter); | |
console.log(clickedButton.val()); | |
clickedButton.prop('disabled',true); | |
formData.append('task',clickedButton.val()); | |
$.ajax({ | |
url: '/submit', | |
type: 'POST', | |
data: formData, | |
contentType: false, | |
processData: false, | |
success: function(data) { | |
currentTaskId = data.task_id; | |
const position = data.position; | |
//$('#queuePosition').html(`Your task is in position: ${position}`); | |
checkResult(currentTaskId); | |
}, | |
error: function(xhr) { | |
destroyOverlayAll(result_holders); | |
if (xhr.status === 429) { | |
//$('#queuePosition').html('Error: Task queue is full. Please try again later.'); | |
clickedButton.prop('disabled',false) | |
} | |
} | |
}); | |
function checkResult(taskId) { | |
resultCheckInterval = setInterval(function() { | |
$.get(`/result/${taskId}/words`, function(data) { | |
if (data.status === "completed") { | |
clearInterval(resultCheckInterval); | |
let tbody = parentEl.find('tbody'); | |
tbody.empty(); | |
data.result.forEach(function(item, index) { | |
tbody.append(`<tr><td>${index + 1}</td><td>${item.word}</td><td>${item.phonetic}</td></tr>`); | |
}); | |
destroyOverlayAll(result_holders); | |
clickedButton.prop('disabled',false) | |
} | |
}); | |
}, 1000); // Check every second for a faster response | |
} | |
}); | |
}); | |
</script> | |
<script src="/static/iframeResizer.contentWindow.min.js"></script> | |
</body> | |
</html> |