All checks were successful
Deploy ASP.NET MVC to OCI / build-and-deploy (push) Successful in 17m26s
223 lines
9.1 KiB
Plaintext
223 lines
9.1 KiB
Plaintext
@{
|
|
ViewData["Title"] = "Texto para Áudio (Voz)";
|
|
Layout = "_Layout";
|
|
}
|
|
|
|
<div class="text-center mb-5">
|
|
<h1 class="display-4">@ViewData["Title"]</h1>
|
|
<p class="lead">Converta qualquer texto em áudio para compartilhar no WhatsApp ou baixar.</p>
|
|
</div>
|
|
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-8">
|
|
<div class="card shadow-custom p-4">
|
|
<div class="mb-4">
|
|
<label for="textInput" class="form-label h5">Digite ou cole seu texto</label>
|
|
<textarea class="form-control" id="textInput" rows="6" placeholder="Escreva aqui o que você deseja converter em áudio..." maxlength="5000"></textarea>
|
|
<div class="form-text text-end"><span id="charCount">0</span>/5000 caracteres</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<label for="voiceSelect" class="form-label">Idioma</label>
|
|
<select id="voiceSelect" class="form-select">
|
|
<option value="pt-br" selected>Português (Brasil)</option>
|
|
<option value="pt-pt">Português (Portugal)</option>
|
|
<option value="es">Espanhol</option>
|
|
<option value="en-us">Inglês (EUA)</option>
|
|
<option value="en-gb">Inglês (UK)</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="rate" class="form-label">Velocidade: <span id="rateValue">1.0x</span></label>
|
|
<input type="range" class="form-range" min="0.5" max="1.5" step="0.1" id="rate" value="1">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label for="voiceGender" class="form-label">Voz</label>
|
|
<select id="voiceGender" class="form-select">
|
|
<option value="f" selected>Feminina</option>
|
|
<option value="m">Masculina</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid">
|
|
<button type="button" class="btn btn-primary btn-lg" id="btnGenerate" onclick="generateAudio()">
|
|
<i class="bi bi-soundwave me-2"></i>Gerar Áudio
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Indicador de progresso -->
|
|
<div id="progressContainer" class="mt-4" style="display: none;">
|
|
<div class="text-center mb-2">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Gerando...</span>
|
|
</div>
|
|
</div>
|
|
<p class="text-center text-muted mb-0">Gerando áudio, aguarde...</p>
|
|
</div>
|
|
|
|
<!-- Player e botões de ação -->
|
|
<div id="resultContainer" class="mt-4" style="display: none;">
|
|
<div class="bg-light rounded p-3">
|
|
<label class="form-label fw-bold mb-2"><i class="bi bi-music-note-beamed me-2"></i>Áudio gerado:</label>
|
|
<audio id="audioPlayer" controls class="w-100 mb-3"></audio>
|
|
|
|
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
|
|
<button type="button" class="btn btn-success btn-lg" id="btnShare" onclick="shareAudio()">
|
|
<i class="bi bi-share me-2" id="iconShare"></i><span id="btnShareText">Compartilhar</span>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary btn-lg" id="btnDownload" onclick="downloadAudio()">
|
|
<i class="bi bi-download me-2"></i>Baixar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 alert alert-info">
|
|
<i class="bi bi-info-circle me-2"></i>
|
|
O áudio é gerado em formato OGG, compatível com WhatsApp e a maioria dos aplicativos de mensagem.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script>
|
|
// Variáveis globais
|
|
let audioBase64 = null;
|
|
let audioBlob = null;
|
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
|
|
// Atualizar contador de caracteres
|
|
document.getElementById('textInput').addEventListener('input', function() {
|
|
document.getElementById('charCount').textContent = this.value.length;
|
|
});
|
|
|
|
// Atualizar label de velocidade
|
|
document.getElementById('rate').addEventListener('input', function() {
|
|
document.getElementById('rateValue').textContent = this.value + 'x';
|
|
});
|
|
|
|
// Em mobile, trocar texto do botão compartilhar
|
|
if (!isMobile || !navigator.canShare) {
|
|
document.getElementById('btnShare').style.display = 'none';
|
|
}
|
|
|
|
// Gerar áudio
|
|
window.generateAudio = async function() {
|
|
const textInput = document.getElementById('textInput');
|
|
const voiceSelect = document.getElementById('voiceSelect');
|
|
const voiceGender = document.getElementById('voiceGender');
|
|
const rate = document.getElementById('rate');
|
|
const btnGenerate = document.getElementById('btnGenerate');
|
|
const progressContainer = document.getElementById('progressContainer');
|
|
const resultContainer = document.getElementById('resultContainer');
|
|
|
|
const text = textInput.value.trim();
|
|
if (text === '') {
|
|
alert('Por favor, digite algum texto para gerar o áudio.');
|
|
return;
|
|
}
|
|
|
|
// Mostrar progresso
|
|
btnGenerate.disabled = true;
|
|
progressContainer.style.display = 'block';
|
|
resultContainer.style.display = 'none';
|
|
|
|
try {
|
|
const response = await fetch('/api/tts/generate', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
text: text,
|
|
language: voiceSelect.value,
|
|
rate: parseFloat(rate.value),
|
|
gender: voiceGender.value
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
throw new Error(error || 'Erro ao gerar áudio');
|
|
}
|
|
|
|
const data = await response.json();
|
|
audioBase64 = data.audio;
|
|
|
|
// Converter base64 para blob
|
|
const byteCharacters = atob(audioBase64);
|
|
const byteNumbers = new Array(byteCharacters.length);
|
|
for (let i = 0; i < byteCharacters.length; i++) {
|
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
}
|
|
const byteArray = new Uint8Array(byteNumbers);
|
|
audioBlob = new Blob([byteArray], { type: 'audio/ogg' });
|
|
|
|
// Configurar player
|
|
const audioPlayer = document.getElementById('audioPlayer');
|
|
audioPlayer.src = 'data:audio/ogg;base64,' + audioBase64;
|
|
|
|
// Mostrar resultado
|
|
resultContainer.style.display = 'block';
|
|
|
|
} catch (error) {
|
|
console.error('Erro:', error);
|
|
alert('Erro ao gerar áudio: ' + error.message);
|
|
} finally {
|
|
btnGenerate.disabled = false;
|
|
progressContainer.style.display = 'none';
|
|
}
|
|
};
|
|
|
|
// Compartilhar áudio (mobile)
|
|
window.shareAudio = async function() {
|
|
if (!audioBlob) {
|
|
alert('Gere o áudio primeiro.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const file = new File([audioBlob], 'audio.ogg', { type: 'audio/ogg' });
|
|
|
|
if (navigator.canShare && navigator.canShare({ files: [file] })) {
|
|
await navigator.share({
|
|
files: [file],
|
|
title: 'Áudio',
|
|
text: ''
|
|
});
|
|
} else {
|
|
// Fallback para download
|
|
downloadAudio();
|
|
}
|
|
} catch (error) {
|
|
if (error.name !== 'AbortError') {
|
|
console.error('Erro ao compartilhar:', error);
|
|
downloadAudio();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Baixar áudio
|
|
window.downloadAudio = function() {
|
|
if (!audioBlob) {
|
|
alert('Gere o áudio primeiro.');
|
|
return;
|
|
}
|
|
|
|
const url = window.URL.createObjectURL(audioBlob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'audio.ogg';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
window.URL.revokeObjectURL(url);
|
|
};
|
|
|
|
console.log('Script de Text-to-Speech carregado com sucesso!');
|
|
</script>
|
|
}
|