diff --git a/Areas/AudioTools/Controllers/SpeechToTextController.cs b/Areas/AudioTools/Controllers/SpeechToTextController.cs new file mode 100644 index 0000000..007546c --- /dev/null +++ b/Areas/AudioTools/Controllers/SpeechToTextController.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Mvc; +using Convert_It_Online.Services; +using Microsoft.AspNetCore.Localization; + +namespace Convert_It_Online.Areas.AudioTools.Controllers +{ + [Area("AudioTools")] + [Route("{culture}/[area]/[controller]")] + [Route("[area]/[controller]")] // Adicionado para Share Target sem cultura fixa + public class SpeechToTextController : Controller + { + private readonly IAudioTranscriptionService _transcriptionService; + private readonly ILogger _logger; + + public SpeechToTextController(IAudioTranscriptionService transcriptionService, ILogger logger) + { + _transcriptionService = transcriptionService; + _logger = logger; + } + + [HttpGet] + public IActionResult Index() + { + return View(); + } + + [HttpPost] + public async Task Transcribe(IFormFile audioFile) + { + if (audioFile == null || audioFile.Length == 0) + { + ViewBag.Error = "Por favor, selecione um arquivo de áudio."; + return View("Index"); + } + + var culture = HttpContext.Features.Get()?.RequestCulture.UICulture.Name ?? "pt-BR"; + var tempPath = Path.GetTempFileName(); + + try + { + using (var stream = new FileStream(tempPath, FileMode.Create)) + { + await audioFile.CopyToAsync(stream); + } + + var transcription = await _transcriptionService.TranscribeAsync(tempPath, culture); + ViewBag.Result = transcription; + } + catch (Exception ex) + { + _logger.LogError(ex, "Erro no controller ao transcrever."); + ViewBag.Error = "Erro ao processar o áudio. Verifique se o formato é suportado."; + } + finally + { + if (System.IO.File.Exists(tempPath)) System.IO.File.Delete(tempPath); + } + + return View("Index"); + } + + [HttpPost("HandleShare")] + public async Task HandleShare(IFormFile audio) + { + // O Android via Share Target costuma enviar como 'audio' ou 'file' + return await Transcribe(audio); + } + } +} diff --git a/Areas/AudioTools/Controllers/TextToSpeechController.cs b/Areas/AudioTools/Controllers/TextToSpeechController.cs new file mode 100644 index 0000000..421cd3f --- /dev/null +++ b/Areas/AudioTools/Controllers/TextToSpeechController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Convert_It_Online.Areas.AudioTools.Controllers +{ + [Area("AudioTools")] + [Route("{culture}/[area]/[controller]")] + public class TextToSpeechController : Controller + { + [HttpGet] + public IActionResult Index() + { + return View(); + } + } +} diff --git a/Areas/AudioTools/Views/SpeechToText/Index.cshtml b/Areas/AudioTools/Views/SpeechToText/Index.cshtml new file mode 100644 index 0000000..91a90cb --- /dev/null +++ b/Areas/AudioTools/Views/SpeechToText/Index.cshtml @@ -0,0 +1,69 @@ +@{ + ViewData["Title"] = "Áudio para Texto (Transcrição)"; + var culture = ViewContext.RouteData.Values["culture"] as string ?? "pt-BR"; +} + +
+

@ViewData["Title"]

+

Converta áudios do WhatsApp, reuniões ou gravações em texto automaticamente usando IA.

+
+ +
+
+
+
+
+ + +
Formatos suportados: MP3, WAV, OGG, OPUS, M4A, etc.
+
+ +
+ +
+
+ + @if (ViewBag.Error != null) + { + + } + + @if (ViewBag.Result != null) + { +
+

Transcrição:

+
@ViewBag.Result
+ +
+ +
+
+ } +
+ +
+

Privacidade e Tecnologia

+

+ Seu áudio é processado usando a tecnologia OpenAI Whisper rodando diretamente em nosso servidor. + Não enviamos seus dados para APIs externas e os arquivos temporários são deletados imediatamente após a conversão. +

+
+
+
+ +@section Scripts { + +} diff --git a/Areas/AudioTools/Views/TextToSpeech/Index.cshtml b/Areas/AudioTools/Views/TextToSpeech/Index.cshtml new file mode 100644 index 0000000..5b3513a --- /dev/null +++ b/Areas/AudioTools/Views/TextToSpeech/Index.cshtml @@ -0,0 +1,122 @@ +@{ + ViewData["Title"] = "Texto para Áudio (Voz)"; +} + +
+

@ViewData["Title"]

+

Converta qualquer texto em fala usando vozes neurais de alta qualidade.

+
+ +
+
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ +
+ + Esta ferramenta usa as vozes instaladas no seu dispositivo. No Android e Windows, você encontrará opções de vozes neurais muito naturais. +
+
+
+ +@section Scripts { + +} diff --git a/Areas/AudioTools/Views/_ViewImports.cshtml b/Areas/AudioTools/Views/_ViewImports.cshtml new file mode 100644 index 0000000..f0e69f5 --- /dev/null +++ b/Areas/AudioTools/Views/_ViewImports.cshtml @@ -0,0 +1,5 @@ +@using Convert_It_Online +@using Microsoft.AspNetCore.Mvc.Localization + +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@inject IViewLocalizer Localizer diff --git a/Areas/AudioTools/Views/_ViewStart.cshtml b/Areas/AudioTools/Views/_ViewStart.cshtml new file mode 100644 index 0000000..820a2f6 --- /dev/null +++ b/Areas/AudioTools/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/Convert-It.csproj b/Convert-It.csproj index 4c9bd92..d785ae4 100644 --- a/Convert-It.csproj +++ b/Convert-It.csproj @@ -23,6 +23,9 @@ + + + diff --git a/Dockerfile b/Dockerfile index 5df3d9e..b252db6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,6 +44,14 @@ FROM base AS final WORKDIR /app COPY --from=publish /app/publish . +# Instalar ffmpeg e bibliotecas nativas (rodar como root) +USER root +RUN apt-get update && apt-get install -y \ + ffmpeg \ + libc6-dev \ + && rm -rf /var/lib/apt/lists/* +USER app + # Variáveis de ambiente otimizadas para produção ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false ENV DOTNET_USE_POLLING_FILE_WATCHER=true diff --git a/Program.cs b/Program.cs index e37836c..6fd6f22 100644 --- a/Program.cs +++ b/Program.cs @@ -163,6 +163,7 @@ builder.Host.UseSerilog(); builder.Services.AddLocalization(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); var supportedCultures = new[] { "pt-BR", "es-MX", "es-CL", "es-PY" }; builder.Services.Configure(options => diff --git a/Readme.md b/Readme.md index 5f28270..e5a6adb 100644 --- a/Readme.md +++ b/Readme.md @@ -1 +1,47 @@ - \ No newline at end of file +# Convert-It Online + +Ferramenta multiuso de conversão de arquivos (Imagens, Documentos, Texto e Áudio) desenvolvida em ASP.NET Core 8 MVC. + +## 🛠️ Funcionalidades +- **Imagens:** HEIC para JPG, JPG para WebP. +- **Documentos:** PDF para Texto, Extração de Linha Digitável de Boletos (Barcode). +- **Texto:** Conversor de Case (Maiúsculo/Minúsculo). +- **Áudio:** Transcrição de Áudio para Texto (Whisper AI) e Texto para Voz (Web Speech API). +- **PWA:** Suporte a instalação e integração com menu de compartilhamento do Android (Share Target). + +## 🚀 Dependências Externas (Obrigatório) + +Para as funcionalidades de áudio (transcrição), o projeto depende do **FFmpeg**. + +### 🐧 Linux (Ubuntu/Debian) +```bash +sudo apt update +sudo apt install ffmpeg +``` + +### 🪟 Windows +1. Baixe os binários em [ffmpeg.org](https://ffmpeg.org/download.html). +2. Extraia para uma pasta (ex: `C:\ffmpeg`). +3. Adicione a pasta `bin` (ex: `C:\ffmpeg\bin`) às **Variáveis de Ambiente do Sistema (PATH)**. +4. Reinicie o terminal ou o Visual Studio. + +### 🐳 Docker +A imagem Docker já está configurada para instalar o `ffmpeg` automaticamente durante o build. + +## 💻 Desenvolvimento Local + +1. Certifique-se de ter o .NET 8 SDK instalado. +2. Clone o repositório. +3. Configure o FFmpeg conforme instruções acima. +4. Execute o comando: + ```bash + dotnet run + ``` + +## 📱 PWA & Android Share Target +O projeto está configurado como um Progressive Web App. Ao "Instalar" o site no Android: +1. Ele aparecerá como um aplicativo nativo. +2. Você poderá compartilhar arquivos de áudio diretamente do WhatsApp para o Convert-It para transcrição automática. + +--- +Desenvolvido por Ricardo. diff --git a/Services/AudioTranscriptionService.cs b/Services/AudioTranscriptionService.cs new file mode 100644 index 0000000..39d987d --- /dev/null +++ b/Services/AudioTranscriptionService.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Whisper.net; +using Whisper.net.Ggml; +using Xabe.FFmpeg; +using Microsoft.Extensions.Logging; + +namespace Convert_It_Online.Services +{ + public class AudioTranscriptionService : IAudioTranscriptionService + { + private readonly string _modelPath; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public AudioTranscriptionService(ILogger logger) + { + _logger = logger; + _httpClient = new HttpClient(); + _modelPath = Path.Combine(AppContext.BaseDirectory, "Models", "ggml-base.bin"); + + // Garantir que a pasta Models existe + var modelsDir = Path.GetDirectoryName(_modelPath); + if (!Directory.Exists(modelsDir)) + { + Directory.CreateDirectory(modelsDir!); + } + } + + private async Task EnsureModelExistsAsync() + { + if (!System.IO.File.Exists(_modelPath)) + { + _logger.LogInformation("Baixando modelo Whisper Base..."); + var downloader = new WhisperGgmlDownloader(_httpClient); + using var modelStream = await downloader.GetGgmlModelAsync(GgmlType.Base); + using var fileStream = System.IO.File.Create(_modelPath); + await modelStream.CopyToAsync(fileStream); + _logger.LogInformation("Modelo Whisper baixado com sucesso."); + } + } + + public async Task TranscribeAsync(string inputPath, string culture = "pt-BR") + { + await EnsureModelExistsAsync(); + + string tempWavPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.wav"); + + try + { + _logger.LogInformation("Convertendo áudio para WAV 16kHz Mono..."); + + // Configurar FFmpeg (assume que está no PATH em Linux) + // Se estiver no Windows, pode precisar de FFmpeg.SetExecutablesPath + + var conversion = await FFmpeg.Conversions.New() + .AddParameter($"-i \"{inputPath}\"") + .AddParameter("-ar 16000") + .AddParameter("-ac 1") + .AddParameter("-c:a pcm_s16le") + .SetOutput(tempWavPath) + .Start(); + + _logger.LogInformation("Iniciando transcrição com Whisper..."); + + using var factory = WhisperFactory.FromPath(_modelPath); + using var processor = factory.CreateBuilder() + .WithLanguage(culture.Split('-')[0]) // Usa "pt", "es", etc + .Build(); + + using var wavStream = System.IO.File.OpenRead(tempWavPath); + + var result = ""; + await foreach (var segment in processor.ProcessAsync(wavStream)) + { + result += segment.Text + " "; + } + + return result.Trim(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Erro durante a transcrição de áudio."); + throw; + } + finally + { + if (System.IO.File.Exists(tempWavPath)) + { + System.IO.File.Delete(tempWavPath); + } + } + } + } +} diff --git a/Services/IAudioTranscriptionService.cs b/Services/IAudioTranscriptionService.cs new file mode 100644 index 0000000..2c15745 --- /dev/null +++ b/Services/IAudioTranscriptionService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Convert_It_Online.Services +{ + public interface IAudioTranscriptionService + { + Task TranscribeAsync(string inputPath, string culture = "pt-BR"); + } +} diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index bc34fde..8cb5e23 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -29,6 +29,7 @@ + @if (adEnabled && adProvider == "Google" && !string.IsNullOrEmpty(googlePublisherId)) { @@ -85,6 +86,19 @@ +