Compare commits
2 Commits
5db9199705
...
724e03176e
| Author | SHA1 | Date | |
|---|---|---|---|
| 724e03176e | |||
| 3b0c93a35e |
@ -17,9 +17,12 @@
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:gist.github.com)",
|
||||
"Bash(dotnet list:*)",
|
||||
"Bash(dotnet build:*)"
|
||||
"Bash(dotnet build:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(dotnet add package:*)",
|
||||
"Bash(dir:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
Areas/AudioTools/Controllers/SpeechToTextController.cs
Normal file
69
Areas/AudioTools/Controllers/SpeechToTextController.cs
Normal file
@ -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<SpeechToTextController> _logger;
|
||||
|
||||
public SpeechToTextController(IAudioTranscriptionService transcriptionService, ILogger<SpeechToTextController> logger)
|
||||
{
|
||||
_transcriptionService = transcriptionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> 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<IRequestCultureFeature>()?.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<IActionResult> HandleShare(IFormFile audio)
|
||||
{
|
||||
// O Android via Share Target costuma enviar como 'audio' ou 'file'
|
||||
return await Transcribe(audio);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Areas/AudioTools/Controllers/TextToSpeechController.cs
Normal file
15
Areas/AudioTools/Controllers/TextToSpeechController.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Areas/AudioTools/Views/SpeechToText/Index.cshtml
Normal file
69
Areas/AudioTools/Views/SpeechToText/Index.cshtml
Normal file
@ -0,0 +1,69 @@
|
||||
@{
|
||||
ViewData["Title"] = "Áudio para Texto (Transcrição)";
|
||||
var culture = ViewContext.RouteData.Values["culture"] as string ?? "pt-BR";
|
||||
}
|
||||
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-4">@ViewData["Title"]</h1>
|
||||
<p class="lead">Converta áudios do WhatsApp, reuniões ou gravações em texto automaticamente usando IA.</p>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-custom p-4">
|
||||
<form asp-action="Transcribe" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-4">
|
||||
<label for="audioFile" class="form-label h5">Selecione o arquivo de áudio</label>
|
||||
<input type="file" class="form-control form-control-lg" id="audioFile" name="audioFile" accept="audio/*" required>
|
||||
<div class="form-text mt-2">Formatos suportados: MP3, WAV, OGG, OPUS, M4A, etc.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-mic-fill me-2"></i>Transcrever Áudio
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if (ViewBag.Error != null)
|
||||
{
|
||||
<div class="alert alert-danger mt-4" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>@ViewBag.Error
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (ViewBag.Result != null)
|
||||
{
|
||||
<div class="mt-5">
|
||||
<h4 class="mb-3">Transcrição:</h4>
|
||||
<div class="p-3 bg-light border rounded" style="min-height: 150px; white-space: pre-wrap;">@ViewBag.Result</div>
|
||||
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button class="btn btn-outline-secondary btn-sm" onclick="copyTranscription()">
|
||||
<i class="bi bi-clipboard me-1"></i>Copiar Texto
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<h3 class="h5 mb-3"><i class="bi bi-shield-check me-2"></i>Privacidade e Tecnologia</h3>
|
||||
<p class="text-muted small">
|
||||
Seu áudio é processado usando a tecnologia <strong>OpenAI Whisper</strong> 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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function copyTranscription() {
|
||||
const text = document.querySelector('.bg-light.border.rounded').innerText;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('Transcrição copiada!');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
122
Areas/AudioTools/Views/TextToSpeech/Index.cshtml
Normal file
122
Areas/AudioTools/Views/TextToSpeech/Index.cshtml
Normal file
@ -0,0 +1,122 @@
|
||||
@{
|
||||
ViewData["Title"] = "Texto para Áudio (Voz)";
|
||||
}
|
||||
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-4">@ViewData["Title"]</h1>
|
||||
<p class="lead">Converta qualquer texto em fala usando vozes neurais de alta qualidade.</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 que seja lido..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="voiceSelect" class="form-label">Escolher Voz</label>
|
||||
<select id="voiceSelect" class="form-select"></select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="rate" class="form-label">Velocidade</label>
|
||||
<input type="range" class="form-range" min="0.5" max="2" step="0.1" id="rate" value="1">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="pitch" class="form-label">Tom</label>
|
||||
<input type="range" class="form-range" min="0" max="2" step="0.1" id="pitch" value="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
|
||||
<button type="button" class="btn btn-primary btn-lg px-5" onclick="speak()">
|
||||
<i class="bi bi-play-fill me-2"></i>Ouvir
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-lg" onclick="stop()">
|
||||
<i class="bi bi-stop-fill me-2"></i>Parar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
Esta ferramenta usa as vozes instaladas no seu dispositivo. No Android e Windows, você encontrará opções de vozes neurais muito naturais.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
const synth = window.speechSynthesis;
|
||||
const voiceSelect = document.querySelector('#voiceSelect');
|
||||
const textInput = document.querySelector('#textInput');
|
||||
const rate = document.querySelector('#rate');
|
||||
const pitch = document.querySelector('#pitch');
|
||||
|
||||
let voices = [];
|
||||
|
||||
function populateVoiceList() {
|
||||
voices = synth.getVoices().sort(function (a, b) {
|
||||
const aname = a.name.toUpperCase();
|
||||
const bname = b.name.toUpperCase();
|
||||
if (aname < bname) return -1;
|
||||
else if (aname > bname) return 1;
|
||||
else return 0;
|
||||
});
|
||||
|
||||
const selectedIndex = voiceSelect.selectedIndex < 0 ? 0 : voiceSelect.selectedIndex;
|
||||
voiceSelect.innerHTML = '';
|
||||
|
||||
for (let i = 0; i < voices.length; i++) {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = voices[i].name + ' (' + voices[i].lang + ')';
|
||||
|
||||
if (voices[i].default) {
|
||||
option.textContent += ' -- PADRÃO';
|
||||
}
|
||||
|
||||
option.setAttribute('data-lang', voices[i].lang);
|
||||
option.setAttribute('data-name', voices[i].name);
|
||||
voiceSelect.appendChild(option);
|
||||
}
|
||||
voiceSelect.selectedIndex = selectedIndex;
|
||||
}
|
||||
|
||||
populateVoiceList();
|
||||
if (speechSynthesis.onvoiceschanged !== undefined) {
|
||||
speechSynthesis.onvoiceschanged = populateVoiceList;
|
||||
}
|
||||
|
||||
function speak() {
|
||||
if (synth.speaking) {
|
||||
console.error('speechSynthesis.speaking');
|
||||
return;
|
||||
}
|
||||
if (textInput.value !== '') {
|
||||
const utterThis = new SpeechSynthesisUtterance(textInput.value);
|
||||
utterThis.onend = function (event) {
|
||||
console.log('SpeechSynthesisUtterance.onend');
|
||||
}
|
||||
utterThis.onerror = function (event) {
|
||||
console.error('SpeechSynthesisUtterance.onerror');
|
||||
}
|
||||
const selectedOption = voiceSelect.selectedOptions[0].getAttribute('data-name');
|
||||
for (let i = 0; i < voices.length; i++) {
|
||||
if (voices[i].name === selectedOption) {
|
||||
utterThis.voice = voices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
utterThis.pitch = pitch.value;
|
||||
utterThis.rate = rate.value;
|
||||
synth.speak(utterThis);
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
synth.cancel();
|
||||
}
|
||||
</script>
|
||||
}
|
||||
5
Areas/AudioTools/Views/_ViewImports.cshtml
Normal file
5
Areas/AudioTools/Views/_ViewImports.cshtml
Normal file
@ -0,0 +1,5 @@
|
||||
@using Convert_It_Online
|
||||
@using Microsoft.AspNetCore.Mvc.Localization
|
||||
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@inject IViewLocalizer Localizer
|
||||
3
Areas/AudioTools/Views/_ViewStart.cshtml
Normal file
3
Areas/AudioTools/Views/_ViewStart.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
319
Areas/DocumentConverters/Controllers/PdfBarcodeLineController.cs
Normal file
319
Areas/DocumentConverters/Controllers/PdfBarcodeLineController.cs
Normal file
@ -0,0 +1,319 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Kernel.Pdf.Canvas.Parser;
|
||||
using iText.Kernel.Pdf.Canvas.Parser.Listener;
|
||||
using iText.Kernel.Exceptions;
|
||||
|
||||
namespace Convert_It_Online.Areas.DocumentConverters.Controllers
|
||||
{
|
||||
[Area("DocumentConverters")]
|
||||
public class PdfBarcodeLineController : Controller
|
||||
{
|
||||
private readonly IStringLocalizer<SharedResource> _localizer;
|
||||
private readonly ILogger<PdfBarcodeLineController> _logger;
|
||||
private const long MaxPreviewSize = 5 * 1024 * 1024; // 5MB
|
||||
private static readonly Regex CandidatePattern = new(@"[0-9\s\.-]{44,70}", RegexOptions.Compiled);
|
||||
|
||||
public PdfBarcodeLineController(IStringLocalizer<SharedResource> localizer, ILogger<PdfBarcodeLineController> logger)
|
||||
{
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private void SetCommonViewBagProperties()
|
||||
{
|
||||
ViewBag.HomeLink = _localizer["HomeLink"];
|
||||
ViewBag.TextMenuTitle = _localizer["TextMenuTitle"];
|
||||
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
|
||||
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
|
||||
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
|
||||
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
|
||||
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
|
||||
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
|
||||
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
|
||||
ViewBag.FooterText = _localizer["FooterText"];
|
||||
ViewBag.About = _localizer["About"];
|
||||
ViewBag.Contact = _localizer["Contact"];
|
||||
ViewBag.Terms = _localizer["Terms"];
|
||||
}
|
||||
|
||||
private void PrepareIndexView()
|
||||
{
|
||||
SetCommonViewBagProperties();
|
||||
ViewBag.PageTitle = _localizer["PdfBarcodeConverterPageTitle"];
|
||||
ViewBag.PageDescription = _localizer["PdfBarcodeConverterPageDescription"];
|
||||
ViewBag.PdfBarcodeDetectTabTitle = _localizer["PdfBarcodeDetectTabTitle"];
|
||||
ViewBag.PdfBarcodeDownloadTabTitle = _localizer["PdfBarcodeDownloadTabTitle"];
|
||||
ViewBag.PdfBarcodeFileInputLabel = _localizer["PdfBarcodeFileInputLabel"];
|
||||
ViewBag.PdfPasswordLabel = _localizer["PdfPasswordLabel"];
|
||||
ViewBag.PdfPasswordPlaceholder = _localizer["PdfPasswordPlaceholder"];
|
||||
ViewBag.PdfBarcodePasswordHint = _localizer["PdfBarcodePasswordHint"];
|
||||
ViewBag.DetectBarcodeButton = _localizer["DetectBarcodeButton"];
|
||||
ViewBag.DownloadBarcodeButton = _localizer["DownloadBarcodeButton"];
|
||||
ViewBag.PdfBarcodePreviewTitle = _localizer["PdfBarcodePreviewTitle"];
|
||||
ViewBag.SelectFileError = _localizer["SelectFileError"];
|
||||
ViewBag.CopyButtonLabel = _localizer["CopyButtonLabel"];
|
||||
ViewBag.CopyButtonSuccess = _localizer["CopyButtonSuccess"];
|
||||
|
||||
ViewBag.FaqWhatTitle = _localizer["PdfBarcodeFaqWhatTitle"];
|
||||
ViewBag.FaqWhatContent = _localizer["PdfBarcodeFaqWhatContent"];
|
||||
ViewBag.FaqHowTitle = _localizer["PdfBarcodeFaqHowTitle"];
|
||||
ViewBag.FaqHowContent = _localizer["PdfBarcodeFaqHowContent"];
|
||||
ViewBag.FaqWhyTitle = _localizer["PdfBarcodeFaqWhyTitle"];
|
||||
ViewBag.FaqWhyContent = _localizer["PdfBarcodeFaqWhyContent"];
|
||||
ViewBag.FaqSecurityTitle = _localizer["PdfBarcodeFaqSecurityTitle"];
|
||||
ViewBag.FaqSecurityContent = _localizer["PdfBarcodeFaqSecurityContent"];
|
||||
ViewBag.FaqLimitsTitle = _localizer["PdfBarcodeFaqLimitsTitle"];
|
||||
ViewBag.FaqLimitsContent = _localizer["PdfBarcodeFaqLimitsContent"];
|
||||
|
||||
ViewBag.MetaDescription = ViewBag.PageDescription;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
PrepareIndexView();
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> DetectLine(IFormFile pdfFile, string? password, bool preview = false)
|
||||
{
|
||||
return await HandleDetection(pdfFile, password, preview, forDownload: false);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> DownloadLine(IFormFile pdfFile, string? password, bool preview = false)
|
||||
{
|
||||
return await HandleDetection(pdfFile, password, preview, forDownload: true);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> HandleDetection(IFormFile? pdfFile, string? password, bool preview, bool forDownload)
|
||||
{
|
||||
if (pdfFile == null || pdfFile.Length == 0)
|
||||
{
|
||||
_logger.LogWarning("[PDF-BARCODE] Attempt without file");
|
||||
if (preview)
|
||||
{
|
||||
return Json(new { success = false, message = _localizer["SelectFileError"].Value });
|
||||
}
|
||||
|
||||
ModelState.AddModelError("pdfFile", _localizer["SelectFileError"]);
|
||||
PrepareIndexView();
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
if (!IsValidPdf(pdfFile))
|
||||
{
|
||||
_logger.LogWarning("[PDF-BARCODE] Invalid file type: {ContentType}", pdfFile.ContentType);
|
||||
if (preview)
|
||||
{
|
||||
return Json(new { success = false, message = _localizer["InvalidPdfFileError"].Value });
|
||||
}
|
||||
|
||||
ModelState.AddModelError("pdfFile", _localizer["InvalidPdfFileError"]);
|
||||
PrepareIndexView();
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
if (preview && pdfFile.Length > MaxPreviewSize)
|
||||
{
|
||||
return Json(new { success = false, message = _localizer["PdfPreviewTooLarge"].Value });
|
||||
}
|
||||
|
||||
var extraction = await TryExtractTextAsync(pdfFile, password);
|
||||
if (!extraction.Success)
|
||||
{
|
||||
var message = _localizer[extraction.ErrorKey].Value;
|
||||
if (preview)
|
||||
{
|
||||
return Json(new { success = false, message });
|
||||
}
|
||||
|
||||
ModelState.AddModelError("pdfFile", _localizer[extraction.ErrorKey]);
|
||||
ViewBag.ConversionError = message;
|
||||
PrepareIndexView();
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
var lineResult = FindBarcodeLine(extraction.Content ?? string.Empty);
|
||||
if (lineResult == null)
|
||||
{
|
||||
var message = _localizer["PdfNoBarcodeFound"].Value;
|
||||
if (preview)
|
||||
{
|
||||
return Json(new { success = false, message });
|
||||
}
|
||||
|
||||
ModelState.AddModelError("pdfFile", _localizer["PdfNoBarcodeFound"]);
|
||||
ViewBag.ConversionError = message;
|
||||
PrepareIndexView();
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
if (preview)
|
||||
{
|
||||
return Json(new
|
||||
{
|
||||
success = true,
|
||||
content = lineResult,
|
||||
format = "digit-line"
|
||||
});
|
||||
}
|
||||
|
||||
var fileBaseName = Path.GetFileNameWithoutExtension(pdfFile.FileName);
|
||||
var downloadFileName = string.IsNullOrWhiteSpace(fileBaseName) ? "linha-digitavel.txt" : fileBaseName + "-linha-digitavel.txt";
|
||||
var payload = Encoding.UTF8.GetBytes(lineResult + Environment.NewLine);
|
||||
return File(payload, "text/plain", downloadFileName);
|
||||
}
|
||||
|
||||
private async Task<(bool Success, string? Content, string ErrorKey)> TryExtractTextAsync(IFormFile pdfFile, string? password)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var memoryStream = new MemoryStream();
|
||||
await pdfFile.CopyToAsync(memoryStream);
|
||||
var pdfBytes = memoryStream.ToArray();
|
||||
|
||||
// Try multiple password encodings
|
||||
var passwords = new List<byte[]?> { null }; // Start with no password
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
passwords.Add(System.Text.Encoding.UTF8.GetBytes(password));
|
||||
passwords.Add(System.Text.Encoding.ASCII.GetBytes(password));
|
||||
passwords.Add(System.Text.Encoding.Latin1.GetBytes(password));
|
||||
}
|
||||
|
||||
foreach (var pwd in passwords)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReaderProperties readerProperties = new ReaderProperties();
|
||||
if (pwd != null)
|
||||
{
|
||||
readerProperties.SetPassword(pwd);
|
||||
}
|
||||
|
||||
using var pdfReader = new PdfReader(new MemoryStream(pdfBytes), readerProperties);
|
||||
using var pdfDocument = new PdfDocument(pdfReader);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
int numberOfPages = pdfDocument.GetNumberOfPages();
|
||||
|
||||
for (int i = 1; i <= numberOfPages; i++)
|
||||
{
|
||||
var page = pdfDocument.GetPage(i);
|
||||
|
||||
// Try multiple extraction strategies for barcode detection
|
||||
var strategies = new ITextExtractionStrategy[]
|
||||
{
|
||||
new LocationTextExtractionStrategy(),
|
||||
new SimpleTextExtractionStrategy()
|
||||
};
|
||||
|
||||
foreach (var strategy in strategies)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pageText = PdfTextExtractor.GetTextFromPage(page, strategy);
|
||||
if (!string.IsNullOrWhiteSpace(pageText))
|
||||
{
|
||||
builder.AppendLine(pageText);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = builder.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
return (true, result, string.Empty);
|
||||
}
|
||||
}
|
||||
catch (BadPasswordException) when (pwd != null)
|
||||
{
|
||||
continue; // Try next password encoding
|
||||
}
|
||||
catch (BadPasswordException) when (pwd == null && !string.IsNullOrEmpty(password))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
return (false, null, "PdfInvalidPassword");
|
||||
}
|
||||
|
||||
return (false, null, "InvalidPdfFileError");
|
||||
}
|
||||
catch (BadPasswordException)
|
||||
{
|
||||
return (false, null, string.IsNullOrEmpty(password) ? "PdfPasswordRequired" : "PdfInvalidPassword");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "[PDF-BARCODE] Failed to extract text from {FileName}", pdfFile.FileName);
|
||||
return (false, null, "InvalidPdfFileError");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidPdf(IFormFile file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var contentType = file.ContentType?.ToLowerInvariant();
|
||||
if (contentType == "application/pdf" || contentType == "application/x-pdf")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Path.GetExtension(file.FileName).Equals(".pdf", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string? FindBarcodeLine(string rawText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawText))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (Match match in CandidatePattern.Matches(rawText))
|
||||
{
|
||||
var digits = Regex.Replace(match.Value, "[^0-9]", string.Empty);
|
||||
if (digits.Length == 47 || digits.Length == 48 || digits.Length == 44)
|
||||
{
|
||||
return digits;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback: check full text digits
|
||||
var fullDigits = Regex.Replace(rawText, "[^0-9]", string.Empty);
|
||||
if (fullDigits.Length >= 44)
|
||||
{
|
||||
var candidates = new[] { 48, 47, 44 };
|
||||
foreach (var length in candidates)
|
||||
{
|
||||
if (fullDigits.Length >= length)
|
||||
{
|
||||
return fullDigits.Substring(0, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
318
Areas/DocumentConverters/Controllers/PdfToTextController.cs
Normal file
318
Areas/DocumentConverters/Controllers/PdfToTextController.cs
Normal file
@ -0,0 +1,318 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using iText.Kernel.Pdf;
|
||||
using iText.Kernel.Pdf.Canvas.Parser;
|
||||
using iText.Kernel.Pdf.Canvas.Parser.Listener;
|
||||
using iText.Kernel.Exceptions;
|
||||
|
||||
namespace Convert_It_Online.Areas.DocumentConverters.Controllers
|
||||
{
|
||||
[Area("DocumentConverters")]
|
||||
public class PdfToTextController : Controller
|
||||
{
|
||||
private readonly IStringLocalizer<SharedResource> _localizer;
|
||||
private readonly ILogger<PdfToTextController> _logger;
|
||||
private const long MaxPreviewSize = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
public PdfToTextController(IStringLocalizer<SharedResource> localizer, ILogger<PdfToTextController> logger)
|
||||
{
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private void SetCommonViewBagProperties()
|
||||
{
|
||||
ViewBag.HomeLink = _localizer["HomeLink"];
|
||||
ViewBag.TextMenuTitle = _localizer["TextMenuTitle"];
|
||||
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
|
||||
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
|
||||
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
|
||||
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
|
||||
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
|
||||
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
|
||||
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
|
||||
ViewBag.FooterText = _localizer["FooterText"];
|
||||
ViewBag.About = _localizer["About"];
|
||||
ViewBag.Contact = _localizer["Contact"];
|
||||
ViewBag.Terms = _localizer["Terms"];
|
||||
}
|
||||
|
||||
private void PrepareIndexView()
|
||||
{
|
||||
SetCommonViewBagProperties();
|
||||
ViewBag.PageTitle = _localizer["PdfTextConverterPageTitle"];
|
||||
ViewBag.PageDescription = _localizer["PdfTextConverterPageDescription"];
|
||||
ViewBag.PdfPlainTextTabTitle = _localizer["PdfPlainTextTabTitle"];
|
||||
ViewBag.PdfMarkdownTabTitle = _localizer["PdfMarkdownTabTitle"];
|
||||
ViewBag.PdfFileInputLabel = _localizer["PdfFileInputLabel"];
|
||||
ViewBag.PdfPasswordLabel = _localizer["PdfPasswordLabel"];
|
||||
ViewBag.PdfPasswordPlaceholder = _localizer["PdfPasswordPlaceholder"];
|
||||
ViewBag.PdfPasswordHint = _localizer["PdfPasswordHint"];
|
||||
ViewBag.ExtractPlainTextButton = _localizer["ExtractPlainTextButton"];
|
||||
ViewBag.ExtractMarkdownButton = _localizer["ExtractMarkdownButton"];
|
||||
ViewBag.DownloadPlainTextButton = _localizer["DownloadPlainTextButton"];
|
||||
ViewBag.DownloadMarkdownButton = _localizer["DownloadMarkdownButton"];
|
||||
ViewBag.PdfTextPreviewTitle = _localizer["PdfTextPreviewTitle"];
|
||||
ViewBag.SelectFileError = _localizer["SelectFileError"];
|
||||
|
||||
ViewBag.FaqWhatTitle = _localizer["PdfTextFaqWhatTitle"];
|
||||
ViewBag.FaqWhatContent = _localizer["PdfTextFaqWhatContent"];
|
||||
ViewBag.FaqHowTitle = _localizer["PdfTextFaqHowTitle"];
|
||||
ViewBag.FaqHowContent = _localizer["PdfTextFaqHowContent"];
|
||||
ViewBag.FaqWhyTitle = _localizer["PdfTextFaqWhyTitle"];
|
||||
ViewBag.FaqWhyContent = _localizer["PdfTextFaqWhyContent"];
|
||||
ViewBag.FaqSecurityTitle = _localizer["PdfTextFaqSecurityTitle"];
|
||||
ViewBag.FaqSecurityContent = _localizer["PdfTextFaqSecurityContent"];
|
||||
ViewBag.FaqLimitsTitle = _localizer["PdfTextFaqLimitsTitle"];
|
||||
ViewBag.FaqLimitsContent = _localizer["PdfTextFaqLimitsContent"];
|
||||
|
||||
ViewBag.MetaDescription = ViewBag.PageDescription;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
PrepareIndexView();
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Test()
|
||||
{
|
||||
return Json(new { success = true, message = "Roteamento funcionando!", timestamp = DateTime.Now });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ExtractPlainText(IFormFile pdfFile, string? password, bool preview = false)
|
||||
{
|
||||
return await HandleExtraction(pdfFile, password, preview, toMarkdown: false);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ExtractMarkdown(IFormFile pdfFile, string? password, bool preview = false)
|
||||
{
|
||||
return await HandleExtraction(pdfFile, password, preview, toMarkdown: true);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> HandleExtraction(IFormFile? pdfFile, string? password, bool preview, bool toMarkdown)
|
||||
{
|
||||
if (pdfFile == null || pdfFile.Length == 0)
|
||||
{
|
||||
_logger.LogWarning("[PDF-TEXT] Attempt without file");
|
||||
if (preview)
|
||||
{
|
||||
return Json(new { success = false, message = _localizer["SelectFileError"].Value });
|
||||
}
|
||||
|
||||
ModelState.AddModelError("pdfFile", _localizer["SelectFileError"]);
|
||||
PrepareIndexView();
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
if (!IsValidPdf(pdfFile))
|
||||
{
|
||||
_logger.LogWarning("[PDF-TEXT] Invalid file type: {ContentType}", pdfFile.ContentType);
|
||||
if (preview)
|
||||
{
|
||||
return Json(new { success = false, message = _localizer["InvalidPdfFileError"].Value });
|
||||
}
|
||||
|
||||
ModelState.AddModelError("pdfFile", _localizer["InvalidPdfFileError"]);
|
||||
PrepareIndexView();
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
if (preview && pdfFile.Length > MaxPreviewSize)
|
||||
{
|
||||
if (preview)
|
||||
{
|
||||
return Json(new { success = false, message = _localizer["PdfPreviewTooLarge"].Value });
|
||||
}
|
||||
}
|
||||
|
||||
var extraction = await TryExtractTextAsync(pdfFile, password);
|
||||
if (!extraction.Success)
|
||||
{
|
||||
var message = _localizer[extraction.ErrorKey].Value;
|
||||
if (preview)
|
||||
{
|
||||
return Json(new { success = false, message });
|
||||
}
|
||||
|
||||
ModelState.AddModelError("pdfFile", _localizer[extraction.ErrorKey]);
|
||||
ViewBag.ConversionError = message;
|
||||
PrepareIndexView();
|
||||
return View("Index");
|
||||
}
|
||||
|
||||
var textContent = extraction.Content ?? string.Empty;
|
||||
if (toMarkdown)
|
||||
{
|
||||
textContent = ToMarkdown(textContent);
|
||||
}
|
||||
|
||||
var fileBaseName = Path.GetFileNameWithoutExtension(pdfFile.FileName);
|
||||
var extension = toMarkdown ? ".md" : ".txt";
|
||||
var downloadFileName = string.IsNullOrWhiteSpace(fileBaseName) ? (toMarkdown ? "resultado.md" : "resultado.txt") : fileBaseName + extension;
|
||||
var contentType = toMarkdown ? "text/markdown" : "text/plain";
|
||||
|
||||
if (preview)
|
||||
{
|
||||
return Json(new
|
||||
{
|
||||
success = true,
|
||||
content = textContent,
|
||||
filename = downloadFileName,
|
||||
format = toMarkdown ? "markdown" : "text"
|
||||
});
|
||||
}
|
||||
|
||||
var payload = Encoding.UTF8.GetBytes(textContent);
|
||||
return File(payload, contentType, downloadFileName);
|
||||
}
|
||||
|
||||
private async Task<(bool Success, string? Content, string ErrorKey)> TryExtractTextAsync(IFormFile pdfFile, string? password)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var memoryStream = new MemoryStream();
|
||||
await pdfFile.CopyToAsync(memoryStream);
|
||||
var pdfBytes = memoryStream.ToArray();
|
||||
|
||||
// Try multiple password encodings and extraction strategies
|
||||
var passwords = new List<byte[]?> { null }; // Start with no password
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
passwords.Add(System.Text.Encoding.UTF8.GetBytes(password));
|
||||
passwords.Add(System.Text.Encoding.ASCII.GetBytes(password));
|
||||
passwords.Add(System.Text.Encoding.Latin1.GetBytes(password));
|
||||
}
|
||||
|
||||
foreach (var pwd in passwords)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReaderProperties readerProperties = new ReaderProperties();
|
||||
if (pwd != null)
|
||||
{
|
||||
readerProperties.SetPassword(pwd);
|
||||
}
|
||||
|
||||
using var pdfReader = new PdfReader(new MemoryStream(pdfBytes), readerProperties);
|
||||
using var pdfDocument = new PdfDocument(pdfReader);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
int numberOfPages = pdfDocument.GetNumberOfPages();
|
||||
|
||||
for (int i = 1; i <= numberOfPages; i++)
|
||||
{
|
||||
var page = pdfDocument.GetPage(i);
|
||||
|
||||
// Try multiple extraction strategies
|
||||
var strategies = new ITextExtractionStrategy[]
|
||||
{
|
||||
new LocationTextExtractionStrategy(),
|
||||
new SimpleTextExtractionStrategy()
|
||||
};
|
||||
|
||||
string pageText = "";
|
||||
foreach (var strategy in strategies)
|
||||
{
|
||||
try
|
||||
{
|
||||
pageText = PdfTextExtractor.GetTextFromPage(page, strategy);
|
||||
if (!string.IsNullOrWhiteSpace(pageText))
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(pageText))
|
||||
{
|
||||
builder.AppendLine(pageText);
|
||||
builder.AppendLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($"[Página {i} - texto não detectado ou pode conter apenas imagens]");
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
var result = builder.ToString().Trim();
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
return (true, result, string.Empty);
|
||||
}
|
||||
}
|
||||
catch (BadPasswordException) when (pwd != null)
|
||||
{
|
||||
continue; // Try next password encoding
|
||||
}
|
||||
catch (BadPasswordException) when (pwd == null && !string.IsNullOrEmpty(password))
|
||||
{
|
||||
// Document requires password but none worked
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, either password was wrong or no text found
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
return (false, null, "PdfInvalidPassword");
|
||||
}
|
||||
|
||||
return (false, null, "InvalidPdfFileError");
|
||||
}
|
||||
catch (BadPasswordException)
|
||||
{
|
||||
return (false, null, string.IsNullOrEmpty(password) ? "PdfPasswordRequired" : "PdfInvalidPassword");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "[PDF-TEXT] Failed to extract text from {FileName}", pdfFile.FileName);
|
||||
return (false, null, "InvalidPdfFileError");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidPdf(IFormFile file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var contentType = file.ContentType?.ToLowerInvariant();
|
||||
if (contentType == "application/pdf" || contentType == "application/x-pdf")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Path.GetExtension(file.FileName).Equals(".pdf", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string ToMarkdown(string text)
|
||||
{
|
||||
var normalized = text.Replace("\r\n", "\n").Trim();
|
||||
if (string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var lines = normalized.Split('\n');
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var trimmed = line.TrimEnd();
|
||||
builder.AppendLine(trimmed);
|
||||
}
|
||||
|
||||
return builder.ToString().Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
354
Areas/DocumentConverters/Views/PdfBarcodeLine/Index.cshtml
Normal file
354
Areas/DocumentConverters/Views/PdfBarcodeLine/Index.cshtml
Normal file
@ -0,0 +1,354 @@
|
||||
@{
|
||||
ViewData["Title"] = ViewBag.PageTitle;
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="text-center mb-4">
|
||||
<h1 class="display-5">@ViewBag.PageTitle</h1>
|
||||
<p class="lead text-muted">@ViewBag.PageDescription</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10 col-xl-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-upc-scan me-2"></i>@ViewBag.DocumentMenuTitle
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (ViewBag.ConversionError != null)
|
||||
{
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
@ViewBag.ConversionError
|
||||
</div>
|
||||
}
|
||||
|
||||
<ul class="nav nav-tabs mb-4" id="barcodeTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="detect-tab" data-bs-toggle="tab" data-bs-target="#detect" type="button" role="tab" aria-controls="detect" aria-selected="true">
|
||||
<i class="bi bi-eyeglasses me-2"></i>@ViewBag.PdfBarcodeDetectTabTitle
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="download-tab" data-bs-toggle="tab" data-bs-target="#download" type="button" role="tab" aria-controls="download" aria-selected="false">
|
||||
<i class="bi bi-download me-2"></i>@ViewBag.PdfBarcodeDownloadTabTitle
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="barcodeTabContent">
|
||||
<div class="tab-pane fade show active" id="detect" role="tabpanel" aria-labelledby="detect-tab">
|
||||
<form id="detectForm" action="/pt-BR/DocumentConverters/PdfBarcodeLine/DetectLine" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="detectPdfFile" class="form-label fw-semibold">@ViewBag.PdfBarcodeFileInputLabel</label>
|
||||
<input class="form-control" type="file" id="detectPdfFile" name="pdfFile" accept=".pdf" required>
|
||||
<div class="invalid-feedback">
|
||||
@ViewBag.SelectFileError
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="detectPassword" class="form-label fw-semibold">@ViewBag.PdfPasswordLabel</label>
|
||||
<input class="form-control" type="password" id="detectPassword" name="password" placeholder="@ViewBag.PdfPasswordPlaceholder">
|
||||
<div class="form-text">
|
||||
<i class="bi bi-shield-lock me-1"></i>@ViewBag.PdfBarcodePasswordHint
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" id="previewDetectBtn" class="btn btn-primary">
|
||||
<i class="bi bi-upc-scan me-2"></i>@ViewBag.DetectBarcodeButton
|
||||
</button>
|
||||
<button type="submit" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-download me-2"></i>@ViewBag.DownloadBarcodeButton
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="download" role="tabpanel" aria-labelledby="download-tab">
|
||||
<form id="downloadForm" action="/pt-BR/DocumentConverters/PdfBarcodeLine/DownloadLine" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="downloadPdfFile" class="form-label fw-semibold">@ViewBag.PdfBarcodeFileInputLabel</label>
|
||||
<input class="form-control" type="file" id="downloadPdfFile" name="pdfFile" accept=".pdf" required>
|
||||
<div class="invalid-feedback">
|
||||
@ViewBag.SelectFileError
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="downloadPassword" class="form-label fw-semibold">@ViewBag.PdfPasswordLabel</label>
|
||||
<input class="form-control" type="password" id="downloadPassword" name="password" placeholder="@ViewBag.PdfPasswordPlaceholder">
|
||||
<div class="form-text">
|
||||
<i class="bi bi-shield-lock me-1"></i>@ViewBag.PdfBarcodePasswordHint
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" id="previewDownloadBtn" class="btn btn-primary">
|
||||
<i class="bi bi-eye me-2"></i>@ViewBag.DetectBarcodeButton
|
||||
</button>
|
||||
<button type="submit" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-download me-2"></i>@ViewBag.DownloadBarcodeButton
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="barcodeLoading" class="text-center my-4" style="display: none;">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Carregando...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="barcodePreview" class="mt-4" style="display: none;">
|
||||
<h5 class="mb-3">@ViewBag.PdfBarcodePreviewTitle</h5>
|
||||
<div class="input-group">
|
||||
<input type="text" id="barcodeResult" class="form-control" readonly>
|
||||
<button id="copyBarcodeBtn" class="btn btn-outline-success" type="button">
|
||||
<i class="bi bi-clipboard-check me-1"></i>@ViewBag.CopyButtonLabel
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-end mt-3">
|
||||
<button id="downloadBarcodeBtn" class="btn btn-success btn-sm">
|
||||
<i class="bi bi-box-arrow-down me-2"></i>@ViewBag.DownloadBarcodeButton
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="barcodeError" class="mt-4" style="display: none;">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<span id="barcodeErrorMessage"></span>
|
||||
<button id="fallbackBarcodeBtn" class="btn btn-outline-primary btn-sm ms-2">
|
||||
@ViewBag.DownloadBarcodeButton
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-5">
|
||||
<div class="col-lg-10 mx-auto">
|
||||
<div class="converter-faq">
|
||||
<h3 class="h4 mb-3 text-center">Perguntas Frequentes</h3>
|
||||
<div class="accordion" id="barcodeFaqAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="barcodeFaqWhat">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseWhat" aria-expanded="false" aria-controls="barcodeCollapseWhat">
|
||||
<i class="bi bi-question-circle me-2"></i>@ViewBag.FaqWhatTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="barcodeCollapseWhat" class="accordion-collapse collapse" aria-labelledby="barcodeFaqWhat" data-bs-parent="#barcodeFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqWhatContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="barcodeFaqHow">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseHow" aria-expanded="false" aria-controls="barcodeCollapseHow">
|
||||
<i class="bi bi-gear me-2"></i>@ViewBag.FaqHowTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="barcodeCollapseHow" class="accordion-collapse collapse" aria-labelledby="barcodeFaqHow" data-bs-parent="#barcodeFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqHowContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="barcodeFaqWhy">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseWhy" aria-expanded="false" aria-controls="barcodeCollapseWhy">
|
||||
<i class="bi bi-lightbulb me-2"></i>@ViewBag.FaqWhyTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="barcodeCollapseWhy" class="accordion-collapse collapse" aria-labelledby="barcodeFaqWhy" data-bs-parent="#barcodeFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqWhyContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="barcodeFaqSecurity">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseSecurity" aria-expanded="false" aria-controls="barcodeCollapseSecurity">
|
||||
<i class="bi bi-shield-check me-2"></i>@ViewBag.FaqSecurityTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="barcodeCollapseSecurity" class="accordion-collapse collapse" aria-labelledby="barcodeFaqSecurity" data-bs-parent="#barcodeFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqSecurityContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="barcodeFaqLimits">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#barcodeCollapseLimits" aria-expanded="false" aria-controls="barcodeCollapseLimits">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>@ViewBag.FaqLimitsTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="barcodeCollapseLimits" class="accordion-collapse collapse" aria-labelledby="barcodeFaqLimits" data-bs-parent="#barcodeFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqLimitsContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#barcodeResult {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
(function () {
|
||||
const detectForm = document.getElementById('detectForm');
|
||||
const downloadForm = document.getElementById('downloadForm');
|
||||
const previewDetectBtn = document.getElementById('previewDetectBtn');
|
||||
const previewDownloadBtn = document.getElementById('previewDownloadBtn');
|
||||
const downloadBarcodeBtn = document.getElementById('downloadBarcodeBtn');
|
||||
const fallbackBarcodeBtn = document.getElementById('fallbackBarcodeBtn');
|
||||
const copyBarcodeBtn = document.getElementById('copyBarcodeBtn');
|
||||
const barcodeLoading = document.getElementById('barcodeLoading');
|
||||
const barcodePreview = document.getElementById('barcodePreview');
|
||||
const barcodeError = document.getElementById('barcodeError');
|
||||
const barcodeResult = document.getElementById('barcodeResult');
|
||||
const barcodeErrorMessage = document.getElementById('barcodeErrorMessage');
|
||||
|
||||
let currentLine = '';
|
||||
let currentFileName = 'linha-digitavel.txt';
|
||||
|
||||
const forms = document.querySelectorAll('.needs-validation');
|
||||
Array.from(forms).forEach(function (form) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
});
|
||||
});
|
||||
|
||||
previewDetectBtn.addEventListener('click', function () {
|
||||
handlePreview(detectForm);
|
||||
});
|
||||
|
||||
previewDownloadBtn.addEventListener('click', function () {
|
||||
handlePreview(downloadForm);
|
||||
});
|
||||
|
||||
downloadBarcodeBtn.addEventListener('click', function () {
|
||||
if (!currentLine) {
|
||||
return;
|
||||
}
|
||||
const blob = new Blob([currentLine + '\n'], { type: 'text/plain;charset=utf-8' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = currentFileName;
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
window.URL.revokeObjectURL(url);
|
||||
});
|
||||
|
||||
fallbackBarcodeBtn.addEventListener('click', function () {
|
||||
const activeTab = document.querySelector('.nav-link.active');
|
||||
const currentForm = activeTab.id === 'detect-tab' ? detectForm : downloadForm;
|
||||
currentForm.submit();
|
||||
});
|
||||
|
||||
copyBarcodeBtn.addEventListener('click', function () {
|
||||
if (!currentLine) {
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(currentLine).then(function () {
|
||||
copyBarcodeBtn.innerHTML = '<i class="bi bi-clipboard-check-fill me-1"></i>@ViewBag.CopyButtonSuccess';
|
||||
setTimeout(function () {
|
||||
copyBarcodeBtn.innerHTML = '<i class="bi bi-clipboard-check me-1"></i>@ViewBag.CopyButtonLabel';
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
|
||||
function handlePreview(form) {
|
||||
if (!form.checkValidity()) {
|
||||
form.classList.add('was-validated');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading();
|
||||
const formData = new FormData(form);
|
||||
|
||||
fetch(form.action + '?preview=true', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(async response => {
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(text || response.statusText);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || 'Erro na conversão');
|
||||
}
|
||||
|
||||
currentLine = result.content;
|
||||
currentFileName = (form === downloadForm ? 'linha-digitavel-download.txt' : 'linha-digitavel.txt');
|
||||
barcodeResult.value = currentLine;
|
||||
showPreview();
|
||||
}).catch(error => {
|
||||
showError(error.message || 'Erro inesperado.');
|
||||
});
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
barcodeLoading.style.display = 'block';
|
||||
barcodePreview.style.display = 'none';
|
||||
barcodeError.style.display = 'none';
|
||||
}
|
||||
|
||||
function showPreview() {
|
||||
barcodeLoading.style.display = 'none';
|
||||
barcodeError.style.display = 'none';
|
||||
barcodePreview.style.display = 'block';
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
barcodeLoading.style.display = 'none';
|
||||
barcodePreview.style.display = 'none';
|
||||
barcodeErrorMessage.textContent = message;
|
||||
barcodeError.style.display = 'block';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
350
Areas/DocumentConverters/Views/PdfToText/Index.cshtml
Normal file
350
Areas/DocumentConverters/Views/PdfToText/Index.cshtml
Normal file
@ -0,0 +1,350 @@
|
||||
@{
|
||||
ViewData["Title"] = ViewBag.PageTitle;
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="text-center mb-4">
|
||||
<h1 class="display-5">@ViewBag.PageTitle</h1>
|
||||
<p class="lead text-muted">@ViewBag.PageDescription</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10 col-xl-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-file-earmark-text me-2"></i>@ViewBag.DocumentMenuTitle
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (ViewBag.ConversionError != null)
|
||||
{
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
@ViewBag.ConversionError
|
||||
</div>
|
||||
}
|
||||
|
||||
<ul class="nav nav-tabs mb-4" id="pdfTextTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="plain-text-tab" data-bs-toggle="tab" data-bs-target="#plain-text" type="button" role="tab" aria-controls="plain-text" aria-selected="true">
|
||||
<i class="bi bi-file-text me-2"></i>@ViewBag.PdfPlainTextTabTitle
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="markdown-tab" data-bs-toggle="tab" data-bs-target="#markdown" type="button" role="tab" aria-controls="markdown" aria-selected="false">
|
||||
<i class="bi bi-markdown me-2"></i>@ViewBag.PdfMarkdownTabTitle
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="pdfTextTabContent">
|
||||
<div class="tab-pane fade show active" id="plain-text" role="tabpanel" aria-labelledby="plain-text-tab">
|
||||
<form id="plainTextForm" action="/pt-BR/DocumentConverters/PdfToText/ExtractPlainText" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="plainTextPdfFile" class="form-label fw-semibold">@ViewBag.PdfFileInputLabel</label>
|
||||
<input class="form-control" type="file" id="plainTextPdfFile" name="pdfFile" accept=".pdf" required>
|
||||
<div class="invalid-feedback">
|
||||
@ViewBag.SelectFileError
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="plainTextPassword" class="form-label fw-semibold">@ViewBag.PdfPasswordLabel</label>
|
||||
<input class="form-control" type="password" id="plainTextPassword" name="password" placeholder="@ViewBag.PdfPasswordPlaceholder">
|
||||
<div class="form-text">
|
||||
<i class="bi bi-shield-lock me-1"></i>@ViewBag.PdfPasswordHint
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" id="previewPlainTextBtn" class="btn btn-primary">
|
||||
<i class="bi bi-eye me-2"></i>@ViewBag.ExtractPlainTextButton
|
||||
</button>
|
||||
<button type="submit" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-download me-2"></i>@ViewBag.DownloadPlainTextButton
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="markdown" role="tabpanel" aria-labelledby="markdown-tab">
|
||||
<form id="markdownForm" action="/pt-BR/DocumentConverters/PdfToText/ExtractMarkdown" method="post" enctype="multipart/form-data" class="needs-validation" novalidate>
|
||||
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="markdownPdfFile" class="form-label fw-semibold">@ViewBag.PdfFileInputLabel</label>
|
||||
<input class="form-control" type="file" id="markdownPdfFile" name="pdfFile" accept=".pdf" required>
|
||||
<div class="invalid-feedback">
|
||||
@ViewBag.SelectFileError
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="markdownPassword" class="form-label fw-semibold">@ViewBag.PdfPasswordLabel</label>
|
||||
<input class="form-control" type="password" id="markdownPassword" name="password" placeholder="@ViewBag.PdfPasswordPlaceholder">
|
||||
<div class="form-text">
|
||||
<i class="bi bi-shield-lock me-1"></i>@ViewBag.PdfPasswordHint
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" id="previewMarkdownBtn" class="btn btn-primary">
|
||||
<i class="bi bi-eye me-2"></i>@ViewBag.ExtractMarkdownButton
|
||||
</button>
|
||||
<button type="submit" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-download me-2"></i>@ViewBag.DownloadMarkdownButton
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loadingSpinner" class="text-center my-4" style="display: none;">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Carregando...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="previewArea" class="mt-4" style="display: none;">
|
||||
<h5 class="mb-3">@ViewBag.PdfTextPreviewTitle</h5>
|
||||
<textarea id="previewTextArea" class="form-control" rows="12" readonly></textarea>
|
||||
<div class="text-end mt-3">
|
||||
<button id="downloadPreviewBtn" class="btn btn-success btn-sm">
|
||||
<i class="bi bi-box-arrow-down me-2"></i>@ViewBag.DownloadPlainTextButton
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="errorArea" class="mt-4" style="display: none;">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<span id="errorMessage"></span>
|
||||
<button id="fallbackDownloadBtn" class="btn btn-outline-primary btn-sm ms-2">
|
||||
@ViewBag.DownloadPlainTextButton
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-5">
|
||||
<div class="col-lg-10 mx-auto">
|
||||
<div class="converter-faq">
|
||||
<h3 class="h4 mb-3 text-center">Perguntas Frequentes</h3>
|
||||
<div class="accordion" id="pdfTextFaqAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="faqWhat">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWhat" aria-expanded="false" aria-controls="collapseWhat">
|
||||
<i class="bi bi-question-circle me-2"></i>@ViewBag.FaqWhatTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseWhat" class="accordion-collapse collapse" aria-labelledby="faqWhat" data-bs-parent="#pdfTextFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqWhatContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="faqHow">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseHow" aria-expanded="false" aria-controls="collapseHow">
|
||||
<i class="bi bi-gear me-2"></i>@ViewBag.FaqHowTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseHow" class="accordion-collapse collapse" aria-labelledby="faqHow" data-bs-parent="#pdfTextFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqHowContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="faqWhy">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWhy" aria-expanded="false" aria-controls="collapseWhy">
|
||||
<i class="bi bi-lightbulb me-2"></i>@ViewBag.FaqWhyTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseWhy" class="accordion-collapse collapse" aria-labelledby="faqWhy" data-bs-parent="#pdfTextFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqWhyContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="faqSecurity">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSecurity" aria-expanded="false" aria-controls="collapseSecurity">
|
||||
<i class="bi bi-shield-check me-2"></i>@ViewBag.FaqSecurityTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseSecurity" class="accordion-collapse collapse" aria-labelledby="faqSecurity" data-bs-parent="#pdfTextFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqSecurityContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="faqLimits">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLimits" aria-expanded="false" aria-controls="collapseLimits">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>@ViewBag.FaqLimitsTitle
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseLimits" class="accordion-collapse collapse" aria-labelledby="faqLimits" data-bs-parent="#pdfTextFaqAccordion">
|
||||
<div class="accordion-body">
|
||||
@ViewBag.FaqLimitsContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#previewTextArea {
|
||||
font-family: monospace;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
(function () {
|
||||
const plainForm = document.getElementById('plainTextForm');
|
||||
const markdownForm = document.getElementById('markdownForm');
|
||||
const previewPlainBtn = document.getElementById('previewPlainTextBtn');
|
||||
const previewMarkdownBtn = document.getElementById('previewMarkdownBtn');
|
||||
const downloadPreviewBtn = document.getElementById('downloadPreviewBtn');
|
||||
const fallbackDownloadBtn = document.getElementById('fallbackDownloadBtn');
|
||||
const loadingSpinner = document.getElementById('loadingSpinner');
|
||||
const previewArea = document.getElementById('previewArea');
|
||||
const errorArea = document.getElementById('errorArea');
|
||||
const previewTextArea = document.getElementById('previewTextArea');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const previewTitleButton = document.getElementById('downloadPreviewBtn');
|
||||
|
||||
let previewContent = '';
|
||||
let previewFileName = 'resultado.txt';
|
||||
let previewMime = 'text/plain';
|
||||
|
||||
const forms = document.querySelectorAll('.needs-validation');
|
||||
Array.from(forms).forEach(function (form) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
});
|
||||
});
|
||||
|
||||
previewPlainBtn.addEventListener('click', function () {
|
||||
handlePreview(plainForm, 'text/plain');
|
||||
});
|
||||
|
||||
previewMarkdownBtn.addEventListener('click', function () {
|
||||
handlePreview(markdownForm, 'text/markdown');
|
||||
});
|
||||
|
||||
downloadPreviewBtn.addEventListener('click', function () {
|
||||
if (!previewContent) {
|
||||
return;
|
||||
}
|
||||
const blob = new Blob([previewContent], { type: previewMime + ';charset=utf-8' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = previewFileName;
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
window.URL.revokeObjectURL(url);
|
||||
});
|
||||
|
||||
fallbackDownloadBtn.addEventListener('click', function () {
|
||||
const activeTab = document.querySelector('.nav-link.active');
|
||||
const currentForm = activeTab.id === 'plain-text-tab' ? plainForm : markdownForm;
|
||||
currentForm.submit();
|
||||
});
|
||||
|
||||
function handlePreview(form, mimeType) {
|
||||
if (!form.checkValidity()) {
|
||||
form.classList.add('was-validated');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading();
|
||||
const formData = new FormData(form);
|
||||
const requestUrl = form.action + '?preview=true';
|
||||
|
||||
console.log('DEBUG: Requesting URL:', requestUrl);
|
||||
console.log('DEBUG: Form action:', form.action);
|
||||
|
||||
fetch(requestUrl, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(async response => {
|
||||
console.log('DEBUG: Response status:', response.status);
|
||||
console.log('DEBUG: Response headers:', response.headers.get('content-type'));
|
||||
|
||||
const responseText = await response.text();
|
||||
console.log('DEBUG: Response text (first 200 chars):', responseText.substring(0, 200));
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(responseText || response.statusText);
|
||||
}
|
||||
|
||||
const result = JSON.parse(responseText);
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || 'Erro na conversão');
|
||||
}
|
||||
|
||||
previewContent = result.content;
|
||||
previewFileName = result.filename;
|
||||
previewMime = mimeType;
|
||||
previewTextArea.value = result.content;
|
||||
previewTitleButton.innerHTML = `<i class="bi bi-box-arrow-down me-2"></i>${result.format === 'markdown' ? '@ViewBag.DownloadMarkdownButton' : '@ViewBag.DownloadPlainTextButton'}`;
|
||||
showPreview();
|
||||
}).catch(error => {
|
||||
showError(error.message || 'Erro inesperado.');
|
||||
});
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
loadingSpinner.style.display = 'block';
|
||||
previewArea.style.display = 'none';
|
||||
errorArea.style.display = 'none';
|
||||
}
|
||||
|
||||
function showPreview() {
|
||||
loadingSpinner.style.display = 'none';
|
||||
errorArea.style.display = 'none';
|
||||
previewArea.style.display = 'block';
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
loadingSpinner.style.display = 'none';
|
||||
previewArea.style.display = 'none';
|
||||
errorMessage.textContent = message;
|
||||
errorArea.style.display = 'block';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
@ -27,6 +27,9 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
|
||||
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
|
||||
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
|
||||
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
|
||||
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
|
||||
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
|
||||
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
|
||||
ViewBag.FooterText = _localizer["FooterText"];
|
||||
ViewBag.About = _localizer["About"];
|
||||
ViewBag.Contact = _localizer["Contact"];
|
||||
@ -56,6 +59,7 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
|
||||
ViewBag.FaqSecurityContent = _localizer["HeicFaqSecurityContent"];
|
||||
ViewBag.FaqLimitsTitle = _localizer["HeicFaqLimitsTitle"];
|
||||
ViewBag.FaqLimitsContent = _localizer["HeicFaqLimitsContent"];
|
||||
ViewBag.MetaDescription = ViewBag.PageDescription;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
@ -270,4 +274,4 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,10 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
|
||||
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
|
||||
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
|
||||
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
|
||||
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
|
||||
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
|
||||
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
|
||||
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
|
||||
ViewBag.FooterText = _localizer["FooterText"];
|
||||
ViewBag.About = _localizer["About"];
|
||||
ViewBag.Contact = _localizer["Contact"];
|
||||
@ -54,6 +58,7 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
|
||||
ViewBag.FaqSecurityContent = _localizer["JpgWebpFaqSecurityContent"];
|
||||
ViewBag.FaqLimitsTitle = _localizer["JpgWebpFaqLimitsTitle"];
|
||||
ViewBag.FaqLimitsContent = _localizer["JpgWebpFaqLimitsContent"];
|
||||
ViewBag.MetaDescription = ViewBag.PageDescription;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
@ -252,4 +257,4 @@ namespace Convert_It_Online.Areas.ImageConverters.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +37,10 @@ namespace Convert_It_Online.Areas.TextTools.Controllers
|
||||
ViewBag.ImageMenuTitle = _localizer["ImageMenuTitle"];
|
||||
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
|
||||
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
|
||||
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
|
||||
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
|
||||
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
|
||||
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
|
||||
ViewBag.FooterText = _localizer["FooterText"];
|
||||
ViewBag.About = _localizer["About"];
|
||||
ViewBag.Contact = _localizer["Contact"];
|
||||
@ -66,6 +70,7 @@ namespace Convert_It_Online.Areas.TextTools.Controllers
|
||||
ViewBag.FaqSecurityContent = _localizer["CaseFaqSecurityContent"];
|
||||
ViewBag.FaqLimitsTitle = _localizer["CaseFaqLimitsTitle"];
|
||||
ViewBag.FaqLimitsContent = _localizer["CaseFaqLimitsContent"];
|
||||
ViewBag.MetaDescription = ViewBag.PageTitle;
|
||||
|
||||
var model = new CaseConverterViewModel();
|
||||
return View(model);
|
||||
@ -83,6 +88,7 @@ namespace Convert_It_Online.Areas.TextTools.Controllers
|
||||
ViewBag.ToLowerButton = _localizer["ToLowerButton"];
|
||||
ViewBag.ToSentenceCaseButton = _localizer["ToSentenceCaseButton"];
|
||||
ViewBag.ResultTitle = _localizer["ResultTitle"];
|
||||
ViewBag.MetaDescription = ViewBag.PageTitle;
|
||||
|
||||
if (!ModelState.IsValid || string.IsNullOrEmpty(model.InputText))
|
||||
{
|
||||
|
||||
10
CLAUDE.md
10
CLAUDE.md
@ -13,10 +13,12 @@ Convert-It Online é uma aplicação web ASP.NET Core que oferece ferramentas gr
|
||||
**Português (pt-BR):**
|
||||
- Ferramentas de Texto: `/pt-BR/ferramentas-de-texto/conversor-de-maiusculas-minusculas`
|
||||
- Conversores de Imagem: `/pt-BR/conversores-de-imagem/jpg-para-webp`
|
||||
- Conversores de Documento: `/pt-BR/conversores-de-documento/pdf-para-texto`
|
||||
|
||||
**Espanhol (es-MX, es-CL, es-PY):**
|
||||
- Herramientas de Texto: `/es-MX/herramientas-de-texto/conversor-de-mayusculas-minusculas`
|
||||
- Convertidores de Imagen: `/es-MX/convertidores-de-imagen/jpg-a-webp`
|
||||
- Convertidores de Documento: `/es-MX/convertidores-de-documento/pdf-a-texto`
|
||||
|
||||
### Arquitetura do Sistema de URLs
|
||||
|
||||
@ -53,6 +55,12 @@ Convert-It Online é uma aplicação web ASP.NET Core que oferece ferramentas gr
|
||||
- **URL PT**: `/pt-BR/conversores-de-imagem/jpg-para-webp`
|
||||
- **URL ES**: `/es-MX/convertidores-de-imagen/jpg-a-webp`
|
||||
|
||||
### DocumentConverters
|
||||
- **Controllers**: `PdfToTextController`, `PdfBarcodeLineController`
|
||||
- **Funcionalidades**: Extrair texto/Markdown de PDFs e detectar linha digitável de boletos
|
||||
- **URL PT**: `/pt-BR/conversores-de-documento/pdf-para-texto` e `/pt-BR/conversores-de-documento/linha-digitavel-do-pdf`
|
||||
- **URL ES**: `/es-MX/convertidores-de-documento/pdf-a-texto` e `/es-MX/convertidores-de-documento/linea-digitada-desde-pdf`
|
||||
|
||||
## Desenvolvimento Guidelines
|
||||
|
||||
### Adicionando Novos Conversores
|
||||
@ -86,4 +94,4 @@ Convert-It Online é uma aplicação web ASP.NET Core que oferece ferramentas gr
|
||||
🔄 Aguardando testes finais
|
||||
|
||||
---
|
||||
*Mantenha este arquivo atualizado quando adicionar novos conversores ou idiomas.*
|
||||
*Mantenha este arquivo atualizado quando adicionar novos conversores ou idiomas.*
|
||||
|
||||
@ -32,6 +32,9 @@ namespace Convert_It_Online.Controllers
|
||||
ViewBag.CaseConverterTitle = _localizer["CaseConverterTitle"];
|
||||
ViewBag.JpgToWebpTitle = _localizer["JpgToWebpTitle"];
|
||||
ViewBag.HeicToJpgTitle = _localizer["HeicToJpgTitle"];
|
||||
ViewBag.DocumentMenuTitle = _localizer["DocumentMenuTitle"];
|
||||
ViewBag.PdfToTextTitle = _localizer["PdfToTextTitle"];
|
||||
ViewBag.PdfBarcodeTitle = _localizer["PdfBarcodeTitle"];
|
||||
ViewBag.FooterText = _localizer["FooterText"];
|
||||
ViewBag.About = _localizer["About"];
|
||||
ViewBag.Contact = _localizer["Contact"];
|
||||
@ -51,6 +54,7 @@ namespace Convert_It_Online.Controllers
|
||||
ViewBag.SecurityContent = _localizer["SecurityContent"];
|
||||
ViewBag.AllConvertersTitle = _localizer["AllConvertersTitle"];
|
||||
ViewBag.UseConverterButton = _localizer["UseConverterButton"];
|
||||
ViewBag.MetaDescription = ViewBag.Subtitle;
|
||||
|
||||
var individualConverters = new List<ToolViewModel>
|
||||
{
|
||||
@ -77,6 +81,22 @@ namespace Convert_It_Online.Controllers
|
||||
Area = "ImageConverters",
|
||||
Controller = "HeicToJpg",
|
||||
IconClass = "bi-phone"
|
||||
},
|
||||
new ToolViewModel
|
||||
{
|
||||
Title = _localizer["PdfToTextIndividualTitle"],
|
||||
Description = _localizer["PdfToTextIndividualDescription"],
|
||||
Area = "DocumentConverters",
|
||||
Controller = "PdfToText",
|
||||
IconClass = "bi-file-text"
|
||||
},
|
||||
new ToolViewModel
|
||||
{
|
||||
Title = _localizer["PdfBarcodeIndividualTitle"],
|
||||
Description = _localizer["PdfBarcodeIndividualDescription"],
|
||||
Area = "DocumentConverters",
|
||||
Controller = "PdfBarcodeLine",
|
||||
IconClass = "bi-upc-scan"
|
||||
}
|
||||
};
|
||||
|
||||
@ -97,6 +117,7 @@ namespace Convert_It_Online.Controllers
|
||||
ViewBag.Feature3 = _localizer["Feature3"];
|
||||
ViewBag.Feature4 = _localizer["Feature4"];
|
||||
ViewBag.StartConverting = _localizer["StartConverting"];
|
||||
ViewBag.MetaDescription = ViewBag.WhatIsContent;
|
||||
return View();
|
||||
}
|
||||
|
||||
@ -109,6 +130,7 @@ namespace Convert_It_Online.Controllers
|
||||
ViewBag.SupportTitle = _localizer["SupportTitle"];
|
||||
ViewBag.SupportTime = _localizer["SupportTime"];
|
||||
ViewBag.FaqTitle = _localizer["FaqTitle"];
|
||||
ViewBag.MetaDescription = ViewBag.ContactIntro;
|
||||
return View();
|
||||
}
|
||||
|
||||
@ -129,7 +151,8 @@ namespace Convert_It_Online.Controllers
|
||||
ViewBag.ChangesContent = _localizer["ChangesContent"];
|
||||
ViewBag.LastUpdated = _localizer["LastUpdated"];
|
||||
ViewBag.BackToHome = _localizer["BackToHome"];
|
||||
ViewBag.MetaDescription = ViewBag.TermsIntro;
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
@ -11,6 +11,7 @@
|
||||
<PackageReference Include="HeyRed.ImageSharp.Heif" Version="2.1.3" />
|
||||
<PackageReference Include="LibHeif.Native.win-x64" Version="1.15.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageReference Include="itext7" Version="8.0.5" />
|
||||
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
@ -22,6 +23,9 @@
|
||||
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Enrichers.Process" Version="3.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
<PackageReference Include="Whisper.net" Version="1.9.0" />
|
||||
<PackageReference Include="Whisper.net.Runtime" Version="1.9.0" />
|
||||
<PackageReference Include="Xabe.FFmpeg" Version="6.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -34,17 +34,25 @@ namespace Convert_It_Online.Middleware
|
||||
var translatedArea = match.Groups[2].Value;
|
||||
var translatedController = match.Groups[3].Value;
|
||||
var action = match.Groups[4].Success ? match.Groups[4].Value : "Index";
|
||||
|
||||
|
||||
// Skip if this is already an internal area/controller name (starts with capital letter)
|
||||
if (char.IsUpper(translatedArea[0]) || char.IsUpper(translatedController[0]))
|
||||
{
|
||||
_logger.LogInformation($"Skipping internal path: {path}");
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Matched: culture={cultureName}, area={translatedArea}, controller={translatedController}, action={action}");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var culture = new CultureInfo(cultureName);
|
||||
var originalArea = _urlTranslationService.GetOriginalArea(translatedArea, culture);
|
||||
var originalController = _urlTranslationService.GetOriginalController(translatedController, culture);
|
||||
|
||||
|
||||
_logger.LogInformation($"Translations: originalArea={originalArea}, originalController={originalController}");
|
||||
|
||||
|
||||
if (originalArea != null && originalController != null)
|
||||
{
|
||||
// Rewrite the path to use original controller names
|
||||
@ -53,7 +61,7 @@ namespace Convert_It_Online.Middleware
|
||||
{
|
||||
newPath += $"/{action}";
|
||||
}
|
||||
|
||||
|
||||
_logger.LogInformation($"Rewriting path from {path} to {newPath}");
|
||||
context.Request.Path = newPath;
|
||||
}
|
||||
|
||||
@ -163,6 +163,7 @@ builder.Host.UseSerilog();
|
||||
builder.Services.AddLocalization();
|
||||
|
||||
builder.Services.AddSingleton<IUrlTranslationService, UrlTranslationService>();
|
||||
builder.Services.AddSingleton<IAudioTranscriptionService, AudioTranscriptionService>();
|
||||
|
||||
var supportedCultures = new[] { "pt-BR", "es-MX", "es-CL", "es-PY" };
|
||||
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
|
||||
48
Readme.md
48
Readme.md
@ -1 +1,47 @@
|
||||
|
||||
# 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.
|
||||
|
||||
97
Services/AudioTranscriptionService.cs
Normal file
97
Services/AudioTranscriptionService.cs
Normal file
@ -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<AudioTranscriptionService> _logger;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public AudioTranscriptionService(ILogger<AudioTranscriptionService> 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<string> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Services/IAudioTranscriptionService.cs
Normal file
9
Services/IAudioTranscriptionService.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Convert_It_Online.Services
|
||||
{
|
||||
public interface IAudioTranscriptionService
|
||||
{
|
||||
Task<string> TranscribeAsync(string inputPath, string culture = "pt-BR");
|
||||
}
|
||||
}
|
||||
@ -16,22 +16,26 @@ namespace Convert_It_Online.Services
|
||||
["pt-BR"] = new Dictionary<string, string>
|
||||
{
|
||||
["TextTools"] = "ferramentas-de-texto",
|
||||
["ImageConverters"] = "conversores-de-imagem"
|
||||
["ImageConverters"] = "conversores-de-imagem",
|
||||
["DocumentConverters"] = "conversores-de-documento"
|
||||
},
|
||||
["es-MX"] = new Dictionary<string, string>
|
||||
{
|
||||
["TextTools"] = "herramientas-de-texto",
|
||||
["ImageConverters"] = "convertidores-de-imagen"
|
||||
["ImageConverters"] = "convertidores-de-imagen",
|
||||
["DocumentConverters"] = "convertidores-de-documento"
|
||||
},
|
||||
["es-CL"] = new Dictionary<string, string>
|
||||
{
|
||||
["TextTools"] = "herramientas-de-texto",
|
||||
["ImageConverters"] = "convertidores-de-imagen"
|
||||
["ImageConverters"] = "convertidores-de-imagen",
|
||||
["DocumentConverters"] = "convertidores-de-documento"
|
||||
},
|
||||
["es-PY"] = new Dictionary<string, string>
|
||||
{
|
||||
["TextTools"] = "herramientas-de-texto",
|
||||
["ImageConverters"] = "convertidores-de-imagen"
|
||||
["ImageConverters"] = "convertidores-de-imagen",
|
||||
["DocumentConverters"] = "convertidores-de-documento"
|
||||
}
|
||||
};
|
||||
|
||||
@ -41,25 +45,33 @@ namespace Convert_It_Online.Services
|
||||
{
|
||||
["CaseConverter"] = "conversor-de-maiusculas-minusculas",
|
||||
["JpgToWebp"] = "jpg-para-webp",
|
||||
["HeicToJpg"] = "heic-para-jpg"
|
||||
["HeicToJpg"] = "heic-para-jpg",
|
||||
["PdfToText"] = "pdf-para-texto",
|
||||
["PdfBarcodeLine"] = "linha-digitavel-do-pdf"
|
||||
},
|
||||
["es-MX"] = new Dictionary<string, string>
|
||||
{
|
||||
["CaseConverter"] = "conversor-de-mayusculas-minusculas",
|
||||
["JpgToWebp"] = "jpg-a-webp",
|
||||
["HeicToJpg"] = "heic-a-jpg"
|
||||
["HeicToJpg"] = "heic-a-jpg",
|
||||
["PdfToText"] = "pdf-a-texto",
|
||||
["PdfBarcodeLine"] = "linea-digitada-desde-pdf"
|
||||
},
|
||||
["es-CL"] = new Dictionary<string, string>
|
||||
{
|
||||
["CaseConverter"] = "conversor-de-mayusculas-minusculas",
|
||||
["JpgToWebp"] = "jpg-a-webp",
|
||||
["HeicToJpg"] = "heic-a-jpg"
|
||||
["HeicToJpg"] = "heic-a-jpg",
|
||||
["PdfToText"] = "pdf-a-texto",
|
||||
["PdfBarcodeLine"] = "linea-digitada-desde-pdf"
|
||||
},
|
||||
["es-PY"] = new Dictionary<string, string>
|
||||
{
|
||||
["CaseConverter"] = "conversor-de-mayusculas-minusculas",
|
||||
["JpgToWebp"] = "jpg-a-webp",
|
||||
["HeicToJpg"] = "heic-a-jpg"
|
||||
["HeicToJpg"] = "heic-a-jpg",
|
||||
["PdfToText"] = "pdf-a-texto",
|
||||
["PdfBarcodeLine"] = "linea-digitada-desde-pdf"
|
||||
}
|
||||
};
|
||||
|
||||
@ -145,4 +157,4 @@ namespace Convert_It_Online.Services
|
||||
return $"/{culture.Name}/{translatedArea}/{translatedController}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,12 +51,21 @@
|
||||
<data name="ImageMenuTitle" xml:space="preserve">
|
||||
<value>Imagen</value>
|
||||
</data>
|
||||
<data name="DocumentMenuTitle" xml:space="preserve">
|
||||
<value>Documentos</value>
|
||||
</data>
|
||||
<data name="CaseConverterTitle" xml:space="preserve">
|
||||
<value>Convertidor de Mayúsculas/Minúsculas</value>
|
||||
</data>
|
||||
<data name="JpgToWebpTitle" xml:space="preserve">
|
||||
<value>JPG a WebP</value>
|
||||
</data>
|
||||
<data name="PdfToTextTitle" xml:space="preserve">
|
||||
<value>PDF a Texto/Markdown</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeTitle" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
|
||||
<!-- Footer -->
|
||||
<data name="FooterText" xml:space="preserve">
|
||||
@ -113,12 +122,24 @@
|
||||
<data name="UrlImageConverters" xml:space="preserve">
|
||||
<value>convertidores-de-imagen</value>
|
||||
</data>
|
||||
<data name="UrlDocumentConverters" xml:space="preserve">
|
||||
<value>convertidores-de-documento</value>
|
||||
</data>
|
||||
<data name="UrlCaseConverter" xml:space="preserve">
|
||||
<value>conversor-de-mayusculas-minusculas</value>
|
||||
</data>
|
||||
<data name="UrlJpgToWebp" xml:space="preserve">
|
||||
<value>jpg-a-webp</value>
|
||||
</data>
|
||||
<data name="UrlHeicToJpg" xml:space="preserve">
|
||||
<value>heic-a-jpg</value>
|
||||
</data>
|
||||
<data name="UrlPdfToText" xml:space="preserve">
|
||||
<value>pdf-a-texto</value>
|
||||
</data>
|
||||
<data name="UrlPdfBarcode" xml:space="preserve">
|
||||
<value>linea-digitada-desde-pdf</value>
|
||||
</data>
|
||||
|
||||
<!-- New individual converter resources - Mexican Spanish -->
|
||||
<data name="AllConvertersTitle" xml:space="preserve">
|
||||
@ -142,6 +163,18 @@
|
||||
<data name="HeicToJpgIndividualDescription" xml:space="preserve">
|
||||
<value>Convierte fotos HEIC del iPhone al formato JPG universalmente compatible.</value>
|
||||
</data>
|
||||
<data name="PdfToTextIndividualTitle" xml:space="preserve">
|
||||
<value>Extraer texto de PDF</value>
|
||||
</data>
|
||||
<data name="PdfToTextIndividualDescription" xml:space="preserve">
|
||||
<value>Transforma tus PDFs en texto simple o Markdown, incluso cuando requieren contraseña.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeIndividualTitle" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeIndividualDescription" xml:space="preserve">
|
||||
<value>Obtén la línea digitada de boletos directamente desde el PDF, con soporte para archivos protegidos.</value>
|
||||
</data>
|
||||
|
||||
<!-- HEIC converter menu item -->
|
||||
<data name="HeicToJpgTitle" xml:space="preserve">
|
||||
@ -204,16 +237,167 @@
|
||||
<value>No hay límites en la cantidad de conversiones. Para garantizar un buen rendimiento, recomendamos archivos de hasta 10MB. El procesamiento es más rápido en computadoras modernas.</value>
|
||||
</data>
|
||||
|
||||
<!-- PDF to Text Converter Resources -->
|
||||
<data name="PdfTextConverterPageTitle" xml:space="preserve">
|
||||
<value>Extraer texto de PDF</value>
|
||||
</data>
|
||||
<data name="PdfTextConverterPageDescription" xml:space="preserve">
|
||||
<value>Convierte tus PDFs en texto simple o Markdown con soporte para archivos protegidos con contraseña.</value>
|
||||
</data>
|
||||
<data name="PdfPlainTextTabTitle" xml:space="preserve">
|
||||
<value>PDF → Texto simple</value>
|
||||
</data>
|
||||
<data name="PdfMarkdownTabTitle" xml:space="preserve">
|
||||
<value>PDF → Markdown</value>
|
||||
</data>
|
||||
<data name="PdfFileInputLabel" xml:space="preserve">
|
||||
<value>Seleccionar archivo PDF</value>
|
||||
</data>
|
||||
<data name="PdfPasswordLabel" xml:space="preserve">
|
||||
<value>Contraseña del PDF</value>
|
||||
</data>
|
||||
<data name="PdfPasswordPlaceholder" xml:space="preserve">
|
||||
<value>Ingresa la contraseña si el PDF está protegido</value>
|
||||
</data>
|
||||
<data name="PdfPasswordHint" xml:space="preserve">
|
||||
<value>Si el PDF requiere contraseña de apertura, escríbela aquí. De lo contrario, deja el campo vacío.</value>
|
||||
</data>
|
||||
<data name="ExtractPlainTextButton" xml:space="preserve">
|
||||
<value>Extraer y visualizar</value>
|
||||
</data>
|
||||
<data name="ExtractMarkdownButton" xml:space="preserve">
|
||||
<value>Extraer en Markdown</value>
|
||||
</data>
|
||||
<data name="DownloadPlainTextButton" xml:space="preserve">
|
||||
<value>Descargar texto</value>
|
||||
</data>
|
||||
<data name="DownloadMarkdownButton" xml:space="preserve">
|
||||
<value>Descargar Markdown</value>
|
||||
</data>
|
||||
<data name="PdfTextPreviewTitle" xml:space="preserve">
|
||||
<value>Vista previa del contenido</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhatTitle" xml:space="preserve">
|
||||
<value>¿Qué hace este conversor?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhatContent" xml:space="preserve">
|
||||
<value>Extrae el contenido textual del PDF y lo entrega como texto simple o Markdown, preservando cortes y secciones siempre que sea posible.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqHowTitle" xml:space="preserve">
|
||||
<value>¿Cómo usar el conversor?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqHowContent" xml:space="preserve">
|
||||
<value>Selecciona el archivo PDF, ingresa la contraseña si es necesario y elige la pestaña deseada. Visualiza el resultado en línea o descarga el archivo.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhyTitle" xml:space="preserve">
|
||||
<value>¿Por qué convertir PDF a texto o Markdown?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhyContent" xml:space="preserve">
|
||||
<value>El texto simple permite ediciones rápidas, mientras que Markdown facilita reutilizar el contenido en blogs o documentación.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqSecurityTitle" xml:space="preserve">
|
||||
<value>¿Es seguro subir mis PDFs?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqSecurityContent" xml:space="preserve">
|
||||
<value>Sí. Procesamos los archivos siguiendo estrictas políticas de privacidad y los eliminamos tras la conversión.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqLimitsTitle" xml:space="preserve">
|
||||
<value>¿Existen límites de tamaño?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqLimitsContent" xml:space="preserve">
|
||||
<value>Para la vista previa recomendamos PDFs de hasta 10MB. Archivos mayores pueden descargarse directamente en texto o Markdown.</value>
|
||||
</data>
|
||||
|
||||
<!-- PDF Barcode Converter Resources -->
|
||||
<data name="PdfBarcodeConverterPageTitle" xml:space="preserve">
|
||||
<value>Extraer línea digitada de PDF</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeConverterPageDescription" xml:space="preserve">
|
||||
<value>Detecta la línea digitada de boletos o facturas directamente desde el PDF, incluso si está protegido.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeDetectTabTitle" xml:space="preserve">
|
||||
<value>PDF → Ver línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeDownloadTabTitle" xml:space="preserve">
|
||||
<value>PDF → Descargar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFileInputLabel" xml:space="preserve">
|
||||
<value>Seleccionar PDF del boleto</value>
|
||||
</data>
|
||||
<data name="PdfBarcodePasswordHint" xml:space="preserve">
|
||||
<value>Si el PDF está protegido con contraseña, ingrésala para permitir la lectura.</value>
|
||||
</data>
|
||||
<data name="DetectBarcodeButton" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
<data name="DownloadBarcodeButton" xml:space="preserve">
|
||||
<value>Descargar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodePreviewTitle" xml:space="preserve">
|
||||
<value>Línea digitada detectada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhatTitle" xml:space="preserve">
|
||||
<value>¿Qué es la línea digitada?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhatContent" xml:space="preserve">
|
||||
<value>Es la secuencia numérica del código de barras del boleto, utilizada para realizar pagos en bancos y aplicaciones.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqHowTitle" xml:space="preserve">
|
||||
<value>¿Cómo funciona el conversor?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqHowContent" xml:space="preserve">
|
||||
<value>Extrae el texto del PDF, identifica patrones de boletos (47 o 48 dígitos) y muestra la línea lista para copiar o descargar.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhyTitle" xml:space="preserve">
|
||||
<value>¿Cuándo usar este recurso?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhyContent" xml:space="preserve">
|
||||
<value>Ideal para boletos sin código de barras legible o cuando necesitas copiar la línea digitada para pagar en línea.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqSecurityTitle" xml:space="preserve">
|
||||
<value>¿Mis datos están seguros?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqSecurityContent" xml:space="preserve">
|
||||
<value>Sí. El PDF se procesa solo para extraer la línea digitada y se descarta inmediatamente después.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqLimitsTitle" xml:space="preserve">
|
||||
<value>¿Existen limitaciones?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqLimitsContent" xml:space="preserve">
|
||||
<value>Para mejores resultados utiliza PDFs con texto seleccionable. Boletos en formato de imagen pueden requerir una solución OCR externa.</value>
|
||||
</data>
|
||||
|
||||
<!-- Error Messages -->
|
||||
<data name="SelectFileError" xml:space="preserve">
|
||||
<value>Por favor, selecciona un archivo para convertir.</value>
|
||||
</data>
|
||||
<data name="CopyButtonLabel" xml:space="preserve">
|
||||
<value>Copiar</value>
|
||||
</data>
|
||||
<data name="CopyButtonSuccess" xml:space="preserve">
|
||||
<value>¡Copiado!</value>
|
||||
</data>
|
||||
<data name="InvalidHeicFileError" xml:space="preserve">
|
||||
<value>Archivo HEIC inválido o formato no compatible. Revisa el archivo o intenta con otro. La biblioteca nativa 'libheif' podría faltar en el sistema.</value>
|
||||
</data>
|
||||
<data name="InvalidJpgFileError" xml:space="preserve">
|
||||
<value>Archivo JPG inválido o formato no compatible. Verifica que el archivo sea una imagen JPG válida.</value>
|
||||
</data>
|
||||
<data name="InvalidPdfFileError" xml:space="preserve">
|
||||
<value>Archivo PDF inválido, dañado o con protecciones no compatibles.</value>
|
||||
</data>
|
||||
<data name="PdfPasswordRequired" xml:space="preserve">
|
||||
<value>Este PDF está protegido. Ingresa la contraseña para continuar.</value>
|
||||
</data>
|
||||
<data name="PdfInvalidPassword" xml:space="preserve">
|
||||
<value>La contraseña ingresada no pudo desbloquear el PDF.</value>
|
||||
</data>
|
||||
<data name="PdfNoBarcodeFound" xml:space="preserve">
|
||||
<value>No encontramos una línea digitada en este PDF.</value>
|
||||
</data>
|
||||
<data name="PdfPreviewTooLarge" xml:space="preserve">
|
||||
<value>El contenido es demasiado grande para la vista previa. Descarga el resultado directamente.</value>
|
||||
</data>
|
||||
<!-- JPG to WebP Converter Resources -->
|
||||
<data name="JpgWebpConverterPageTitle" xml:space="preserve">
|
||||
<value>Convertidor JPG ↔ WebP Online Gratis</value>
|
||||
@ -270,4 +454,4 @@
|
||||
<data name="ProcessingError" xml:space="preserve">
|
||||
<value>Ocurrió un error al procesar tu solicitud. Inténtalo de nuevo más tarde.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
||||
@ -51,12 +51,21 @@
|
||||
<data name="ImageMenuTitle" xml:space="preserve">
|
||||
<value>Imagen</value>
|
||||
</data>
|
||||
<data name="DocumentMenuTitle" xml:space="preserve">
|
||||
<value>Documentos</value>
|
||||
</data>
|
||||
<data name="CaseConverterTitle" xml:space="preserve">
|
||||
<value>Convertidor de Mayúsculas/Minúsculas</value>
|
||||
</data>
|
||||
<data name="JpgToWebpTitle" xml:space="preserve">
|
||||
<value>JPG a WebP</value>
|
||||
</data>
|
||||
<data name="PdfToTextTitle" xml:space="preserve">
|
||||
<value>PDF a Texto/Markdown</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeTitle" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
|
||||
<!-- Footer -->
|
||||
<data name="FooterText" xml:space="preserve">
|
||||
@ -113,12 +122,21 @@
|
||||
<data name="UrlImageConverters" xml:space="preserve">
|
||||
<value>convertidores-de-imagen</value>
|
||||
</data>
|
||||
<data name="UrlDocumentConverters" xml:space="preserve">
|
||||
<value>convertidores-de-documento</value>
|
||||
</data>
|
||||
<data name="UrlCaseConverter" xml:space="preserve">
|
||||
<value>conversor-de-mayusculas-minusculas</value>
|
||||
</data>
|
||||
<data name="UrlJpgToWebp" xml:space="preserve">
|
||||
<value>jpg-a-webp</value>
|
||||
</data>
|
||||
<data name="UrlPdfToText" xml:space="preserve">
|
||||
<value>pdf-a-texto</value>
|
||||
</data>
|
||||
<data name="UrlPdfBarcode" xml:space="preserve">
|
||||
<value>linea-digitada-desde-pdf</value>
|
||||
</data>
|
||||
|
||||
<!-- New individual converter resources - Mexican Spanish -->
|
||||
<data name="AllConvertersTitle" xml:space="preserve">
|
||||
@ -142,6 +160,18 @@
|
||||
<data name="HeicToJpgIndividualDescription" xml:space="preserve">
|
||||
<value>Convierte fotos HEIC del iPhone al formato JPG universalmente compatible.</value>
|
||||
</data>
|
||||
<data name="PdfToTextIndividualTitle" xml:space="preserve">
|
||||
<value>Extraer texto de PDF</value>
|
||||
</data>
|
||||
<data name="PdfToTextIndividualDescription" xml:space="preserve">
|
||||
<value>Transforma tus PDFs en texto simple o Markdown, incluso cuando requieren contraseña.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeIndividualTitle" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeIndividualDescription" xml:space="preserve">
|
||||
<value>Obtén la línea digitada de boletos directamente desde el PDF, con soporte para archivos protegidos.</value>
|
||||
</data>
|
||||
|
||||
<!-- HEIC converter menu item -->
|
||||
<data name="HeicToJpgTitle" xml:space="preserve">
|
||||
@ -204,16 +234,167 @@
|
||||
<value>No hay límites en la cantidad de conversiones. Para garantizar un buen rendimiento, recomendamos archivos de hasta 10MB. El procesamiento es más rápido en computadoras modernas.</value>
|
||||
</data>
|
||||
|
||||
<!-- PDF to Text Converter Resources -->
|
||||
<data name="PdfTextConverterPageTitle" xml:space="preserve">
|
||||
<value>Extraer texto de PDF</value>
|
||||
</data>
|
||||
<data name="PdfTextConverterPageDescription" xml:space="preserve">
|
||||
<value>Convierte tus PDFs en texto simple o Markdown con soporte para archivos protegidos con contraseña.</value>
|
||||
</data>
|
||||
<data name="PdfPlainTextTabTitle" xml:space="preserve">
|
||||
<value>PDF → Texto simple</value>
|
||||
</data>
|
||||
<data name="PdfMarkdownTabTitle" xml:space="preserve">
|
||||
<value>PDF → Markdown</value>
|
||||
</data>
|
||||
<data name="PdfFileInputLabel" xml:space="preserve">
|
||||
<value>Seleccionar archivo PDF</value>
|
||||
</data>
|
||||
<data name="PdfPasswordLabel" xml:space="preserve">
|
||||
<value>Contraseña del PDF</value>
|
||||
</data>
|
||||
<data name="PdfPasswordPlaceholder" xml:space="preserve">
|
||||
<value>Ingresa la contraseña si el PDF está protegido</value>
|
||||
</data>
|
||||
<data name="PdfPasswordHint" xml:space="preserve">
|
||||
<value>Si el PDF requiere contraseña de apertura, escríbela aquí. De lo contrario, deja el campo vacío.</value>
|
||||
</data>
|
||||
<data name="ExtractPlainTextButton" xml:space="preserve">
|
||||
<value>Extraer y visualizar</value>
|
||||
</data>
|
||||
<data name="ExtractMarkdownButton" xml:space="preserve">
|
||||
<value>Extraer en Markdown</value>
|
||||
</data>
|
||||
<data name="DownloadPlainTextButton" xml:space="preserve">
|
||||
<value>Descargar texto</value>
|
||||
</data>
|
||||
<data name="DownloadMarkdownButton" xml:space="preserve">
|
||||
<value>Descargar Markdown</value>
|
||||
</data>
|
||||
<data name="PdfTextPreviewTitle" xml:space="preserve">
|
||||
<value>Vista previa del contenido</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhatTitle" xml:space="preserve">
|
||||
<value>¿Qué hace este conversor?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhatContent" xml:space="preserve">
|
||||
<value>Extrae el contenido textual del PDF y lo entrega como texto simple o Markdown, preservando cortes y secciones siempre que sea posible.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqHowTitle" xml:space="preserve">
|
||||
<value>¿Cómo usar el conversor?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqHowContent" xml:space="preserve">
|
||||
<value>Selecciona el archivo PDF, ingresa la contraseña si es necesario y elige la pestaña deseada. Visualiza el resultado en línea o descarga el archivo.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhyTitle" xml:space="preserve">
|
||||
<value>¿Por qué convertir PDF a texto o Markdown?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhyContent" xml:space="preserve">
|
||||
<value>El texto simple permite ediciones rápidas, mientras que Markdown facilita reutilizar el contenido en blogs o documentación.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqSecurityTitle" xml:space="preserve">
|
||||
<value>¿Es seguro subir mis PDFs?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqSecurityContent" xml:space="preserve">
|
||||
<value>Sí. Procesamos los archivos siguiendo estrictas políticas de privacidad y los eliminamos tras la conversión.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqLimitsTitle" xml:space="preserve">
|
||||
<value>¿Existen límites de tamaño?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqLimitsContent" xml:space="preserve">
|
||||
<value>Para la vista previa recomendamos PDFs de hasta 10MB. Archivos mayores pueden descargarse directamente en texto o Markdown.</value>
|
||||
</data>
|
||||
|
||||
<!-- PDF Barcode Converter Resources -->
|
||||
<data name="PdfBarcodeConverterPageTitle" xml:space="preserve">
|
||||
<value>Extraer línea digitada de PDF</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeConverterPageDescription" xml:space="preserve">
|
||||
<value>Detecta la línea digitada de boletos o facturas directamente desde el PDF, incluso si está protegido.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeDetectTabTitle" xml:space="preserve">
|
||||
<value>PDF → Ver línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeDownloadTabTitle" xml:space="preserve">
|
||||
<value>PDF → Descargar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFileInputLabel" xml:space="preserve">
|
||||
<value>Seleccionar PDF del boleto</value>
|
||||
</data>
|
||||
<data name="PdfBarcodePasswordHint" xml:space="preserve">
|
||||
<value>Si el PDF está protegido con contraseña, ingrésala para permitir la lectura.</value>
|
||||
</data>
|
||||
<data name="DetectBarcodeButton" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
<data name="DownloadBarcodeButton" xml:space="preserve">
|
||||
<value>Descargar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodePreviewTitle" xml:space="preserve">
|
||||
<value>Línea digitada detectada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhatTitle" xml:space="preserve">
|
||||
<value>¿Qué es la línea digitada?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhatContent" xml:space="preserve">
|
||||
<value>Es la secuencia numérica del código de barras del boleto, utilizada para realizar pagos en bancos y aplicaciones.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqHowTitle" xml:space="preserve">
|
||||
<value>¿Cómo funciona el conversor?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqHowContent" xml:space="preserve">
|
||||
<value>Extrae el texto del PDF, identifica patrones de boletos (47 o 48 dígitos) y muestra la línea lista para copiar o descargar.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhyTitle" xml:space="preserve">
|
||||
<value>¿Cuándo usar este recurso?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhyContent" xml:space="preserve">
|
||||
<value>Ideal para boletos sin código de barras legible o cuando necesitas copiar la línea digitada para pagar en línea.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqSecurityTitle" xml:space="preserve">
|
||||
<value>¿Mis datos están seguros?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqSecurityContent" xml:space="preserve">
|
||||
<value>Sí. El PDF se procesa solo para extraer la línea digitada y se descarta inmediatamente después.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqLimitsTitle" xml:space="preserve">
|
||||
<value>¿Existen limitaciones?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqLimitsContent" xml:space="preserve">
|
||||
<value>Para mejores resultados utiliza PDFs con texto seleccionable. Boletos en formato de imagen pueden requerir una solución OCR externa.</value>
|
||||
</data>
|
||||
|
||||
<!-- Error Messages -->
|
||||
<data name="SelectFileError" xml:space="preserve">
|
||||
<value>Por favor, selecciona un archivo para convertir.</value>
|
||||
</data>
|
||||
<data name="CopyButtonLabel" xml:space="preserve">
|
||||
<value>Copiar</value>
|
||||
</data>
|
||||
<data name="CopyButtonSuccess" xml:space="preserve">
|
||||
<value>¡Copiado!</value>
|
||||
</data>
|
||||
<data name="InvalidHeicFileError" xml:space="preserve">
|
||||
<value>Archivo HEIC inválido o formato no compatible. Revisa el archivo o intenta con otro. La biblioteca nativa 'libheif' podría faltar en el sistema.</value>
|
||||
</data>
|
||||
<data name="InvalidJpgFileError" xml:space="preserve">
|
||||
<value>Archivo JPG inválido o formato no compatible. Verifica que el archivo sea una imagen JPG válida.</value>
|
||||
</data>
|
||||
<data name="InvalidPdfFileError" xml:space="preserve">
|
||||
<value>Archivo PDF inválido, dañado o con protecciones no compatibles.</value>
|
||||
</data>
|
||||
<data name="PdfPasswordRequired" xml:space="preserve">
|
||||
<value>Este PDF está protegido. Ingresa la contraseña para continuar.</value>
|
||||
</data>
|
||||
<data name="PdfInvalidPassword" xml:space="preserve">
|
||||
<value>La contraseña ingresada no pudo desbloquear el PDF.</value>
|
||||
</data>
|
||||
<data name="PdfNoBarcodeFound" xml:space="preserve">
|
||||
<value>No encontramos una línea digitada en este PDF.</value>
|
||||
</data>
|
||||
<data name="PdfPreviewTooLarge" xml:space="preserve">
|
||||
<value>El contenido es demasiado grande para la vista previa. Descarga el resultado directamente.</value>
|
||||
</data>
|
||||
<!-- JPG to WebP Converter Resources -->
|
||||
<data name="JpgWebpConverterPageTitle" xml:space="preserve">
|
||||
<value>Convertidor JPG ↔ WebP Online Gratis</value>
|
||||
@ -270,4 +451,4 @@
|
||||
<data name="ProcessingError" xml:space="preserve">
|
||||
<value>Ocurrió un error al procesar tu solicitud. Inténtalo de nuevo más tarde.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
||||
@ -51,12 +51,21 @@
|
||||
<data name="ImageMenuTitle" xml:space="preserve">
|
||||
<value>Imagen</value>
|
||||
</data>
|
||||
<data name="DocumentMenuTitle" xml:space="preserve">
|
||||
<value>Documentos</value>
|
||||
</data>
|
||||
<data name="CaseConverterTitle" xml:space="preserve">
|
||||
<value>Convertidor de Mayúsculas/Minúsculas</value>
|
||||
</data>
|
||||
<data name="JpgToWebpTitle" xml:space="preserve">
|
||||
<value>JPG a WebP</value>
|
||||
</data>
|
||||
<data name="PdfToTextTitle" xml:space="preserve">
|
||||
<value>PDF a Texto/Markdown</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeTitle" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
|
||||
<!-- Footer -->
|
||||
<data name="FooterText" xml:space="preserve">
|
||||
@ -113,12 +122,21 @@
|
||||
<data name="UrlImageConverters" xml:space="preserve">
|
||||
<value>convertidores-de-imagen</value>
|
||||
</data>
|
||||
<data name="UrlDocumentConverters" xml:space="preserve">
|
||||
<value>convertidores-de-documento</value>
|
||||
</data>
|
||||
<data name="UrlCaseConverter" xml:space="preserve">
|
||||
<value>conversor-de-mayusculas-minusculas</value>
|
||||
</data>
|
||||
<data name="UrlJpgToWebp" xml:space="preserve">
|
||||
<value>jpg-a-webp</value>
|
||||
</data>
|
||||
<data name="UrlPdfToText" xml:space="preserve">
|
||||
<value>pdf-a-texto</value>
|
||||
</data>
|
||||
<data name="UrlPdfBarcode" xml:space="preserve">
|
||||
<value>linea-digitada-desde-pdf</value>
|
||||
</data>
|
||||
|
||||
<!-- New individual converter resources - Mexican Spanish -->
|
||||
<data name="AllConvertersTitle" xml:space="preserve">
|
||||
@ -142,6 +160,18 @@
|
||||
<data name="HeicToJpgIndividualDescription" xml:space="preserve">
|
||||
<value>Convierte fotos HEIC del iPhone al formato JPG universalmente compatible.</value>
|
||||
</data>
|
||||
<data name="PdfToTextIndividualTitle" xml:space="preserve">
|
||||
<value>Extraer texto de PDF</value>
|
||||
</data>
|
||||
<data name="PdfToTextIndividualDescription" xml:space="preserve">
|
||||
<value>Transforma tus PDFs en texto simple o Markdown, incluso cuando requieren contraseña.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeIndividualTitle" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeIndividualDescription" xml:space="preserve">
|
||||
<value>Obtén la línea digitada de boletos directamente desde el PDF, con soporte para archivos protegidos.</value>
|
||||
</data>
|
||||
|
||||
<!-- HEIC converter menu item -->
|
||||
<data name="HeicToJpgTitle" xml:space="preserve">
|
||||
@ -204,16 +234,167 @@
|
||||
<value>No hay límites en la cantidad de conversiones. Para garantizar un buen rendimiento, recomendamos archivos de hasta 10MB. El procesamiento es más rápido en computadoras modernas.</value>
|
||||
</data>
|
||||
|
||||
<!-- PDF to Text Converter Resources -->
|
||||
<data name="PdfTextConverterPageTitle" xml:space="preserve">
|
||||
<value>Extraer texto de PDF</value>
|
||||
</data>
|
||||
<data name="PdfTextConverterPageDescription" xml:space="preserve">
|
||||
<value>Convierte tus PDFs en texto simple o Markdown con soporte para archivos protegidos con contraseña.</value>
|
||||
</data>
|
||||
<data name="PdfPlainTextTabTitle" xml:space="preserve">
|
||||
<value>PDF → Texto simple</value>
|
||||
</data>
|
||||
<data name="PdfMarkdownTabTitle" xml:space="preserve">
|
||||
<value>PDF → Markdown</value>
|
||||
</data>
|
||||
<data name="PdfFileInputLabel" xml:space="preserve">
|
||||
<value>Seleccionar archivo PDF</value>
|
||||
</data>
|
||||
<data name="PdfPasswordLabel" xml:space="preserve">
|
||||
<value>Contraseña del PDF</value>
|
||||
</data>
|
||||
<data name="PdfPasswordPlaceholder" xml:space="preserve">
|
||||
<value>Ingresa la contraseña si el PDF está protegido</value>
|
||||
</data>
|
||||
<data name="PdfPasswordHint" xml:space="preserve">
|
||||
<value>Si el PDF requiere contraseña de apertura, escríbela aquí. De lo contrario, deja el campo vacío.</value>
|
||||
</data>
|
||||
<data name="ExtractPlainTextButton" xml:space="preserve">
|
||||
<value>Extraer y visualizar</value>
|
||||
</data>
|
||||
<data name="ExtractMarkdownButton" xml:space="preserve">
|
||||
<value>Extraer en Markdown</value>
|
||||
</data>
|
||||
<data name="DownloadPlainTextButton" xml:space="preserve">
|
||||
<value>Descargar texto</value>
|
||||
</data>
|
||||
<data name="DownloadMarkdownButton" xml:space="preserve">
|
||||
<value>Descargar Markdown</value>
|
||||
</data>
|
||||
<data name="PdfTextPreviewTitle" xml:space="preserve">
|
||||
<value>Vista previa del contenido</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhatTitle" xml:space="preserve">
|
||||
<value>¿Qué hace este conversor?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhatContent" xml:space="preserve">
|
||||
<value>Extrae el contenido textual del PDF y lo entrega como texto simple o Markdown, preservando cortes y secciones siempre que sea posible.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqHowTitle" xml:space="preserve">
|
||||
<value>¿Cómo usar el conversor?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqHowContent" xml:space="preserve">
|
||||
<value>Selecciona el archivo PDF, ingresa la contraseña si es necesario y elige la pestaña deseada. Visualiza el resultado en línea o descarga el archivo.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhyTitle" xml:space="preserve">
|
||||
<value>¿Por qué convertir PDF a texto o Markdown?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhyContent" xml:space="preserve">
|
||||
<value>El texto simple permite ediciones rápidas, mientras que Markdown facilita reutilizar el contenido en blogs o documentación.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqSecurityTitle" xml:space="preserve">
|
||||
<value>¿Es seguro subir mis PDFs?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqSecurityContent" xml:space="preserve">
|
||||
<value>Sí. Procesamos los archivos siguiendo estrictas políticas de privacidad y los eliminamos tras la conversión.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqLimitsTitle" xml:space="preserve">
|
||||
<value>¿Existen límites de tamaño?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqLimitsContent" xml:space="preserve">
|
||||
<value>Para la vista previa recomendamos PDFs de hasta 10MB. Archivos mayores pueden descargarse directamente en texto o Markdown.</value>
|
||||
</data>
|
||||
|
||||
<!-- PDF Barcode Converter Resources -->
|
||||
<data name="PdfBarcodeConverterPageTitle" xml:space="preserve">
|
||||
<value>Extraer línea digitada de PDF</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeConverterPageDescription" xml:space="preserve">
|
||||
<value>Detecta la línea digitada de boletos o facturas directamente desde el PDF, incluso si está protegido.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeDetectTabTitle" xml:space="preserve">
|
||||
<value>PDF → Ver línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeDownloadTabTitle" xml:space="preserve">
|
||||
<value>PDF → Descargar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFileInputLabel" xml:space="preserve">
|
||||
<value>Seleccionar PDF del boleto</value>
|
||||
</data>
|
||||
<data name="PdfBarcodePasswordHint" xml:space="preserve">
|
||||
<value>Si el PDF está protegido con contraseña, ingrésala para permitir la lectura.</value>
|
||||
</data>
|
||||
<data name="DetectBarcodeButton" xml:space="preserve">
|
||||
<value>Detectar línea digitada</value>
|
||||
</data>
|
||||
<data name="DownloadBarcodeButton" xml:space="preserve">
|
||||
<value>Descargar línea digitada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodePreviewTitle" xml:space="preserve">
|
||||
<value>Línea digitada detectada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhatTitle" xml:space="preserve">
|
||||
<value>¿Qué es la línea digitada?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhatContent" xml:space="preserve">
|
||||
<value>Es la secuencia numérica del código de barras del boleto, utilizada para realizar pagos en bancos y aplicaciones.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqHowTitle" xml:space="preserve">
|
||||
<value>¿Cómo funciona el conversor?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqHowContent" xml:space="preserve">
|
||||
<value>Extrae el texto del PDF, identifica patrones de boletos (47 o 48 dígitos) y muestra la línea lista para copiar o descargar.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhyTitle" xml:space="preserve">
|
||||
<value>¿Cuándo usar este recurso?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhyContent" xml:space="preserve">
|
||||
<value>Ideal para boletos sin código de barras legible o cuando necesitas copiar la línea digitada para pagar en línea.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqSecurityTitle" xml:space="preserve">
|
||||
<value>¿Mis datos están seguros?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqSecurityContent" xml:space="preserve">
|
||||
<value>Sí. El PDF se procesa solo para extraer la línea digitada y se descarta inmediatamente después.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqLimitsTitle" xml:space="preserve">
|
||||
<value>¿Existen limitaciones?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqLimitsContent" xml:space="preserve">
|
||||
<value>Para mejores resultados utiliza PDFs con texto seleccionable. Boletos en formato de imagen pueden requerir una solución OCR externa.</value>
|
||||
</data>
|
||||
|
||||
<!-- Error Messages -->
|
||||
<data name="SelectFileError" xml:space="preserve">
|
||||
<value>Por favor, selecciona un archivo para convertir.</value>
|
||||
</data>
|
||||
<data name="CopyButtonLabel" xml:space="preserve">
|
||||
<value>Copiar</value>
|
||||
</data>
|
||||
<data name="CopyButtonSuccess" xml:space="preserve">
|
||||
<value>¡Copiado!</value>
|
||||
</data>
|
||||
<data name="InvalidHeicFileError" xml:space="preserve">
|
||||
<value>Archivo HEIC inválido o formato no compatible. Revisa el archivo o intenta con otro. La biblioteca nativa 'libheif' podría faltar en el sistema.</value>
|
||||
</data>
|
||||
<data name="InvalidJpgFileError" xml:space="preserve">
|
||||
<value>Archivo JPG inválido o formato no compatible. Verifica que el archivo sea una imagen JPG válida.</value>
|
||||
</data>
|
||||
<data name="InvalidPdfFileError" xml:space="preserve">
|
||||
<value>Archivo PDF inválido, dañado o con protecciones no compatibles.</value>
|
||||
</data>
|
||||
<data name="PdfPasswordRequired" xml:space="preserve">
|
||||
<value>Este PDF está protegido. Ingresa la contraseña para continuar.</value>
|
||||
</data>
|
||||
<data name="PdfInvalidPassword" xml:space="preserve">
|
||||
<value>La contraseña ingresada no pudo desbloquear el PDF.</value>
|
||||
</data>
|
||||
<data name="PdfNoBarcodeFound" xml:space="preserve">
|
||||
<value>No encontramos una línea digitada en este PDF.</value>
|
||||
</data>
|
||||
<data name="PdfPreviewTooLarge" xml:space="preserve">
|
||||
<value>El contenido es demasiado grande para la vista previa. Descarga el resultado directamente.</value>
|
||||
</data>
|
||||
<!-- JPG to WebP Converter Resources -->
|
||||
<data name="JpgWebpConverterPageTitle" xml:space="preserve">
|
||||
<value>Convertidor JPG ↔ WebP Online Gratis</value>
|
||||
@ -270,4 +451,4 @@
|
||||
<data name="ProcessingError" xml:space="preserve">
|
||||
<value>Ocurrió un error al procesar tu solicitud. Inténtalo de nuevo más tarde.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
||||
@ -183,12 +183,21 @@
|
||||
<data name="ImageMenuTitle" xml:space="preserve">
|
||||
<value>Imagem</value>
|
||||
</data>
|
||||
<data name="DocumentMenuTitle" xml:space="preserve">
|
||||
<value>Documentos</value>
|
||||
</data>
|
||||
<data name="CaseConverterTitle" xml:space="preserve">
|
||||
<value>Conversor de Maiúsculas/Minúsculas</value>
|
||||
</data>
|
||||
<data name="JpgToWebpTitle" xml:space="preserve">
|
||||
<value>JPG para WebP</value>
|
||||
</data>
|
||||
<data name="PdfToTextTitle" xml:space="preserve">
|
||||
<value>PDF para Texto/Markdown</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeTitle" xml:space="preserve">
|
||||
<value>Extrair Linha Digitável</value>
|
||||
</data>
|
||||
|
||||
<!-- Footer Resources -->
|
||||
<data name="FooterText" xml:space="preserve">
|
||||
@ -211,12 +220,24 @@
|
||||
<data name="UrlImageConverters" xml:space="preserve">
|
||||
<value>conversores-de-imagem</value>
|
||||
</data>
|
||||
<data name="UrlDocumentConverters" xml:space="preserve">
|
||||
<value>conversores-de-documento</value>
|
||||
</data>
|
||||
<data name="UrlCaseConverter" xml:space="preserve">
|
||||
<value>conversor-de-maiusculas-minusculas</value>
|
||||
</data>
|
||||
<data name="UrlJpgToWebp" xml:space="preserve">
|
||||
<value>jpg-para-webp</value>
|
||||
</data>
|
||||
<data name="UrlHeicToJpg" xml:space="preserve">
|
||||
<value>heic-para-jpg</value>
|
||||
</data>
|
||||
<data name="UrlPdfToText" xml:space="preserve">
|
||||
<value>pdf-para-texto</value>
|
||||
</data>
|
||||
<data name="UrlPdfBarcode" xml:space="preserve">
|
||||
<value>linha-digitavel-do-pdf</value>
|
||||
</data>
|
||||
|
||||
<!-- FAQ Resources for Contact Page -->
|
||||
<data name="Faq1Question" xml:space="preserve">
|
||||
@ -254,6 +275,18 @@
|
||||
<data name="HeicToJpgIndividualDescription" xml:space="preserve">
|
||||
<value>Converta fotos HEIC do iPhone para o formato JPG universalmente compatível.</value>
|
||||
</data>
|
||||
<data name="PdfToTextIndividualTitle" xml:space="preserve">
|
||||
<value>Extrair Texto de PDF</value>
|
||||
</data>
|
||||
<data name="PdfToTextIndividualDescription" xml:space="preserve">
|
||||
<value>Transforme PDFs protegidos ou não em texto simples ou Markdown em poucos segundos.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeIndividualTitle" xml:space="preserve">
|
||||
<value>Detectar Linha Digitável</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeIndividualDescription" xml:space="preserve">
|
||||
<value>Identifique a linha digitável de boletos diretamente do PDF, mesmo quando há proteção por senha.</value>
|
||||
</data>
|
||||
|
||||
<!-- HEIC converter menu item -->
|
||||
<data name="HeicToJpgTitle" xml:space="preserve">
|
||||
@ -316,6 +349,136 @@
|
||||
<value>Não há limites para a quantidade de conversões. Para garantir um bom desempenho, recomendamos arquivos de até 10MB. O processamento é mais rápido em computadores modernos.</value>
|
||||
</data>
|
||||
|
||||
<!-- PDF to Text Converter Resources -->
|
||||
<data name="PdfTextConverterPageTitle" xml:space="preserve">
|
||||
<value>Extrair Texto de PDF</value>
|
||||
</data>
|
||||
<data name="PdfTextConverterPageDescription" xml:space="preserve">
|
||||
<value>Converta seus PDFs em texto simples ou Markdown, com suporte a arquivos protegidos por senha.</value>
|
||||
</data>
|
||||
<data name="PdfPlainTextTabTitle" xml:space="preserve">
|
||||
<value>PDF → Texto Simples</value>
|
||||
</data>
|
||||
<data name="PdfMarkdownTabTitle" xml:space="preserve">
|
||||
<value>PDF → Markdown</value>
|
||||
</data>
|
||||
<data name="PdfFileInputLabel" xml:space="preserve">
|
||||
<value>Selecionar arquivo PDF</value>
|
||||
</data>
|
||||
<data name="PdfPasswordLabel" xml:space="preserve">
|
||||
<value>Senha do PDF</value>
|
||||
</data>
|
||||
<data name="PdfPasswordPlaceholder" xml:space="preserve">
|
||||
<value>Informe a senha, se o PDF estiver protegido</value>
|
||||
</data>
|
||||
<data name="PdfPasswordHint" xml:space="preserve">
|
||||
<value>Se o PDF exigir senha de abertura, digite-a abaixo. Caso contrário, deixe em branco.</value>
|
||||
</data>
|
||||
<data name="ExtractPlainTextButton" xml:space="preserve">
|
||||
<value>Extrair e Visualizar</value>
|
||||
</data>
|
||||
<data name="ExtractMarkdownButton" xml:space="preserve">
|
||||
<value>Extrair em Markdown</value>
|
||||
</data>
|
||||
<data name="DownloadPlainTextButton" xml:space="preserve">
|
||||
<value>Baixar Texto</value>
|
||||
</data>
|
||||
<data name="DownloadMarkdownButton" xml:space="preserve">
|
||||
<value>Baixar Markdown</value>
|
||||
</data>
|
||||
<data name="PdfTextPreviewTitle" xml:space="preserve">
|
||||
<value>Pré-visualização do Conteúdo</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhatTitle" xml:space="preserve">
|
||||
<value>O que faz este conversor PDF para Texto?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhatContent" xml:space="preserve">
|
||||
<value>Ele extrai o conteúdo textual do seu PDF e o disponibiliza como texto simples ou Markdown, preservando quebras de linha e seções sempre que possível.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqHowTitle" xml:space="preserve">
|
||||
<value>Como usar o conversor?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqHowContent" xml:space="preserve">
|
||||
<value>Selecione o arquivo PDF, informe a senha se necessário e escolha a aba desejada. Visualize o resultado online ou faça o download direto.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhyTitle" xml:space="preserve">
|
||||
<value>Por que converter PDF para texto ou Markdown?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqWhyContent" xml:space="preserve">
|
||||
<value>Texto simples facilita edições rápidas, enquanto Markdown permite reaproveitar o conteúdo em blogs, documentações e geradores de site estático.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqSecurityTitle" xml:space="preserve">
|
||||
<value>É seguro enviar meus PDFs?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqSecurityContent" xml:space="preserve">
|
||||
<value>Sim. O processamento ocorre seguindo políticas rígidas de privacidade e os arquivos são descartados após a conversão.</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqLimitsTitle" xml:space="preserve">
|
||||
<value>Existem limites de tamanho?</value>
|
||||
</data>
|
||||
<data name="PdfTextFaqLimitsContent" xml:space="preserve">
|
||||
<value>Para pré-visualização recomendamos PDFs de até 10MB. Conteúdos maiores podem ser baixados diretamente em texto ou Markdown.</value>
|
||||
</data>
|
||||
|
||||
<!-- PDF Barcode Converter Resources -->
|
||||
<data name="PdfBarcodeConverterPageTitle" xml:space="preserve">
|
||||
<value>Extrair Linha Digitável de PDF</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeConverterPageDescription" xml:space="preserve">
|
||||
<value>Detecte a linha digitável de boletos ou faturas diretamente a partir do PDF, com suporte a arquivos protegidos.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeDetectTabTitle" xml:space="preserve">
|
||||
<value>PDF → Visualizar Linha Digitável</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeDownloadTabTitle" xml:space="preserve">
|
||||
<value>PDF → Baixar Linha Digitável</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFileInputLabel" xml:space="preserve">
|
||||
<value>Selecionar PDF do boleto</value>
|
||||
</data>
|
||||
<data name="PdfBarcodePasswordHint" xml:space="preserve">
|
||||
<value>Caso o PDF esteja protegido por senha, informe-a abaixo para permitir a leitura.</value>
|
||||
</data>
|
||||
<data name="DetectBarcodeButton" xml:space="preserve">
|
||||
<value>Detectar Linha Digitável</value>
|
||||
</data>
|
||||
<data name="DownloadBarcodeButton" xml:space="preserve">
|
||||
<value>Baixar Linha Digitável</value>
|
||||
</data>
|
||||
<data name="PdfBarcodePreviewTitle" xml:space="preserve">
|
||||
<value>Linha Digitável Detectada</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhatTitle" xml:space="preserve">
|
||||
<value>O que é a linha digitável?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhatContent" xml:space="preserve">
|
||||
<value>É a sequência numérica que representa o código de barras de boletos e faturas, usada para pagamentos em bancos e aplicativos.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqHowTitle" xml:space="preserve">
|
||||
<value>Como o conversor funciona?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqHowContent" xml:space="preserve">
|
||||
<value>O sistema extrai o texto do PDF, identifica padrões numéricos de boletos (47 ou 48 dígitos) e exibe a linha digitável pronta para copiar ou baixar.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhyTitle" xml:space="preserve">
|
||||
<value>Quando usar este recurso?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqWhyContent" xml:space="preserve">
|
||||
<value>Ideal para boletos sem código de barras legível ou quando você precisa copiar a linha digitável para pagamento online rapidamente.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqSecurityTitle" xml:space="preserve">
|
||||
<value>Meus dados ficam seguros?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqSecurityContent" xml:space="preserve">
|
||||
<value>Sim. O PDF é processado apenas para extrair a linha digitável e o conteúdo é descartado logo após a operação.</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqLimitsTitle" xml:space="preserve">
|
||||
<value>Existe algum limite?</value>
|
||||
</data>
|
||||
<data name="PdfBarcodeFaqLimitsContent" xml:space="preserve">
|
||||
<value>Para melhores resultados, utilize PDFs com texto selecionável. Boletos apenas com imagem podem exigir uma solução OCR externa.</value>
|
||||
</data>
|
||||
|
||||
<!-- JPG to WebP Converter Resources -->
|
||||
<data name="JpgWebpConverterPageTitle" xml:space="preserve">
|
||||
<value>Conversor JPG ↔ WebP Online Grátis</value>
|
||||
@ -370,6 +533,12 @@
|
||||
<data name="UseConverterButton" xml:space="preserve">
|
||||
<value>Acessar Conversor</value>
|
||||
</data>
|
||||
<data name="CopyButtonLabel" xml:space="preserve">
|
||||
<value>Copiar</value>
|
||||
</data>
|
||||
<data name="CopyButtonSuccess" xml:space="preserve">
|
||||
<value>Copiado!</value>
|
||||
</data>
|
||||
|
||||
<!-- Error Messages -->
|
||||
<data name="SelectFileError" xml:space="preserve">
|
||||
@ -384,7 +553,22 @@
|
||||
<data name="JpgToHeicNotAvailableError" xml:space="preserve">
|
||||
<value>A conversão de JPG para HEIC ainda não está disponível. Esta funcionalidade será implementada em breve.</value>
|
||||
</data>
|
||||
<data name="InvalidPdfFileError" xml:space="preserve">
|
||||
<value>Arquivo PDF inválido, corrompido ou protegido por recursos não suportados.</value>
|
||||
</data>
|
||||
<data name="PdfPasswordRequired" xml:space="preserve">
|
||||
<value>Este PDF está protegido. Informe a senha para continuar.</value>
|
||||
</data>
|
||||
<data name="PdfInvalidPassword" xml:space="preserve">
|
||||
<value>A senha informada não conseguiu desbloquear o PDF.</value>
|
||||
</data>
|
||||
<data name="PdfNoBarcodeFound" xml:space="preserve">
|
||||
<value>Não encontramos uma linha digitável neste PDF.</value>
|
||||
</data>
|
||||
<data name="PdfPreviewTooLarge" xml:space="preserve">
|
||||
<value>O conteúdo é muito grande para pré-visualização. Faça o download direto.</value>
|
||||
</data>
|
||||
<data name="ProcessingError" xml:space="preserve">
|
||||
<value>Ocorreu um erro ao processar sua solicitação. Tente novamente mais tarde.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@using Microsoft.Extensions.Options
|
||||
@using Microsoft.Extensions.Configuration
|
||||
|
||||
@inject IOptions<RequestLocalizationOptions> LocOptions
|
||||
@inject IConfiguration Configuration
|
||||
|
||||
@{
|
||||
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
|
||||
var supportedCultures = LocOptions.Value.SupportedCultures;
|
||||
|
||||
// Configuração de Ads
|
||||
var adSection = Configuration.GetSection("Ads");
|
||||
var adEnabled = adSection.GetValue<bool>("Enabled");
|
||||
var adProvider = adSection.GetValue<string>("Provider"); // "House" ou "Google"
|
||||
var googlePublisherId = adSection.GetValue<string>("GooglePublisherId");
|
||||
var slotTop = adSection.GetValue<string>("Slots:Top");
|
||||
var slotSide = adSection.GetValue<string>("Slots:Side");
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
@ -13,11 +23,19 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="@(ViewBag.MetaDescription ?? ViewBag.PageDescription ?? ViewBag.Subtitle ?? ViewBag.PageTitle ?? "Convert-It Online")" />
|
||||
<title>@ViewData["Title"] - Convert-It Online</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="~/css/site.css" />
|
||||
<link rel="manifest" href="~/manifest.json" />
|
||||
|
||||
@if (adEnabled && adProvider == "Google" && !string.IsNullOrEmpty(googlePublisherId))
|
||||
{
|
||||
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=@googlePublisherId"
|
||||
crossorigin="anonymous"></script>
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@ -42,6 +60,19 @@
|
||||
</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown mx-2">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="documentToolsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-file-earmark-text me-1"></i>@ViewBag.DocumentMenuTitle
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="documentToolsDropdown">
|
||||
<li><a class="dropdown-item" href="@Html.LocalizedUrl("DocumentConverters", "PdfToText")">
|
||||
<i class="bi bi-file-text me-2"></i>@ViewBag.PdfToTextTitle
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="@Html.LocalizedUrl("DocumentConverters", "PdfBarcodeLine")">
|
||||
<i class="bi bi-upc-scan me-2"></i>@ViewBag.PdfBarcodeTitle
|
||||
</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown mx-2">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="imageToolsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-image-alt me-1"></i>@ViewBag.ImageMenuTitle
|
||||
@ -55,6 +86,19 @@
|
||||
</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown mx-2">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="audioToolsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-mic me-1"></i>Áudio
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="audioToolsDropdown">
|
||||
<li><a class="dropdown-item" href="@Html.LocalizedUrl("AudioTools", "SpeechToText")">
|
||||
<i class="bi bi-chat-left-text me-2"></i>Áudio para Texto
|
||||
</a></li>
|
||||
<li><a class="dropdown-item" href="@Html.LocalizedUrl("AudioTools", "TextToSpeech")">
|
||||
<i class="bi bi-megaphone me-2"></i>Texto para Áudio
|
||||
</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="languageDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
@ -84,17 +128,98 @@
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-9">
|
||||
<div class="ad-placeholder ad-top d-none d-lg-flex">
|
||||
<span>Ad Placeholder (Top)</span>
|
||||
</div>
|
||||
<!-- AD TOP -->
|
||||
@if (adEnabled)
|
||||
{
|
||||
@if (adProvider == "House")
|
||||
{
|
||||
<a href="https://qrrapido.site" target="_blank" class="house-ad-top text-decoration-none d-none d-lg-block">
|
||||
<div class="ad-content">
|
||||
<div class="qr-animation">
|
||||
<div class="qr-box"></div>
|
||||
<div class="scan-line"></div>
|
||||
</div>
|
||||
<div class="ad-text">
|
||||
<span class="badge bg-warning text-dark mb-1">NOVO</span>
|
||||
<h5>Gerador de QR Code Ultrarrápido</h5>
|
||||
<p>Crie QR Codes para WiFi, Links e Pix em 1 segundo. Grátis!</p>
|
||||
</div>
|
||||
<div class="cta-button">
|
||||
Criar Agora <i class="bi bi-arrow-right"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
else if (adProvider == "Google" && !string.IsNullOrEmpty(slotTop))
|
||||
{
|
||||
<div class="d-none d-lg-block mb-4" style="min-height: 90px; text-align:center;">
|
||||
<!-- Topo -->
|
||||
<ins class="adsbygoogle"
|
||||
style="display:block"
|
||||
data-ad-client="@googlePublisherId"
|
||||
data-ad-slot="@slotTop"
|
||||
data-ad-format="auto"
|
||||
data-full-width-responsive="true"></ins>
|
||||
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Placeholder visível apenas se não houver ad configurado e estiver em debug/dev se desejado -->
|
||||
<!-- <div class="ad-placeholder ad-top d-none d-lg-flex"><span>Ad Space</span></div> -->
|
||||
}
|
||||
}
|
||||
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
<div class="col-lg-3 d-none d-lg-block">
|
||||
<div class="ad-placeholder ad-side">
|
||||
<span>Ad Placeholder (Side)</span>
|
||||
</div>
|
||||
<!-- AD SIDE -->
|
||||
@if (adEnabled)
|
||||
{
|
||||
@if (adProvider == "House")
|
||||
{
|
||||
<a href="https://jobmaker.com.br" target="_blank" class="house-ad-side text-decoration-none">
|
||||
<div class="terminal-header">
|
||||
<span class="dot red"></span><span class="dot yellow"></span><span class="dot green"></span>
|
||||
</div>
|
||||
<div class="ad-body">
|
||||
<div class="code-typing">
|
||||
<span class="comment">// Precisa integrar sistemas?</span><br>
|
||||
<span class="keyword">await</span> JobMaker.<span class="method">Automate</span>(legacy_sys);<br>
|
||||
<span class="success">>> Processo Otimizado!</span>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<h5>Especialistas em .NET e Integrações</h5>
|
||||
<ul class="list-unstyled text-small text-muted mt-2">
|
||||
<li><i class="bi bi-check2"></i> APIs & Microserviços</li>
|
||||
<li><i class="bi bi-check2"></i> Robôs de Dados</li>
|
||||
<li><i class="bi bi-check2"></i> Modernização de Legado</li>
|
||||
</ul>
|
||||
<button class="btn btn-outline-primary btn-sm w-100 mt-3">Fale Conosco</button>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
else if (adProvider == "Google" && !string.IsNullOrEmpty(slotSide))
|
||||
{
|
||||
<div class="mt-4 sticky-top" style="top: 2rem; min-height: 600px;">
|
||||
<!-- Lateral -->
|
||||
<ins class="adsbygoogle"
|
||||
style="display:block"
|
||||
data-ad-client="@googlePublisherId"
|
||||
data-ad-slot="@slotSide"
|
||||
data-ad-format="auto"
|
||||
data-full-width-responsive="true"></ins>
|
||||
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- <div class="ad-placeholder ad-side"><span>Ad Space</span></div> -->
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -115,6 +240,11 @@
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js');
|
||||
}
|
||||
</script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@ -9,5 +9,14 @@
|
||||
"AllowedHosts": "*",
|
||||
"Serilog": {
|
||||
"OpenSearchUrl": ""
|
||||
},
|
||||
"Ads": {
|
||||
"Enabled": true,
|
||||
"Provider": "House",
|
||||
"GooglePublisherId": "pub-3475956393038764",
|
||||
"Slots": {
|
||||
"Top": "0000000000",
|
||||
"Side": "0000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -430,4 +430,191 @@ h1, h2, h3, h4, h5, h6, p, div, span, a {
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ========== HOUSE ADS (BANNERS PRÓPRIOS) ========== */
|
||||
|
||||
/* Banner Topo - QR Rápido */
|
||||
.house-ad-top {
|
||||
display: block;
|
||||
background: linear-gradient(135deg, #6610f2 0%, #d63384 100%);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem 2rem;
|
||||
color: white !important;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
margin-bottom: 2rem;
|
||||
border: none;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.house-ad-top:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(102, 16, 242, 0.3);
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.house-ad-top .ad-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.house-ad-top .qr-animation {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
.house-ad-top .qr-box {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid white;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.house-ad-top .qr-box::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.house-ad-top .scan-line {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: #00ff00;
|
||||
top: 10%;
|
||||
left: 0;
|
||||
animation: scanMove 1.5s infinite alternate ease-in-out;
|
||||
box-shadow: 0 0 4px #00ff00;
|
||||
}
|
||||
|
||||
@keyframes scanMove {
|
||||
0% { top: 10%; }
|
||||
100% { top: 90%; }
|
||||
}
|
||||
|
||||
.house-ad-top .ad-text h5 {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.house-ad-top .ad-text p {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.house-ad-top .cta-button {
|
||||
background: white;
|
||||
color: #6610f2;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 2rem;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Banner Lateral - JobMaker */
|
||||
.house-ad-side {
|
||||
display: block;
|
||||
background-color: #0f172a; /* Slate 900 */
|
||||
border-radius: 1rem;
|
||||
border: 1px solid #1e293b;
|
||||
color: #e2e8f0 !important;
|
||||
overflow: hidden;
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
text-decoration: none !important;
|
||||
transition: border-color 0.3s ease;
|
||||
min-height: auto; /* Remove min-height fixo se necessário */
|
||||
}
|
||||
|
||||
.house-ad-side:hover {
|
||||
border-color: #3b82f6;
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
.house-ad-side .terminal-header {
|
||||
background: #1e293b;
|
||||
padding: 0.75rem 1rem;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
border-bottom: 1px solid #334155;
|
||||
}
|
||||
|
||||
.house-ad-side .dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.house-ad-side .dot.red { background: #ef4444; }
|
||||
.house-ad-side .dot.yellow { background: #f59e0b; }
|
||||
.house-ad-side .dot.green { background: #22c55e; }
|
||||
|
||||
.house-ad-side .ad-body {
|
||||
padding: 1.5rem;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
|
||||
.house-ad-side .code-typing {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.house-ad-side .comment { color: #64748b; }
|
||||
.house-ad-side .keyword { color: #c084fc; }
|
||||
.house-ad-side .method { color: #60a5fa; }
|
||||
.house-ad-side .success { color: #4ade80; display: block; margin-top: 0.5rem; font-weight: bold;}
|
||||
|
||||
.house-ad-side h5 {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
color: white !important;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.house-ad-side .text-muted {
|
||||
color: #94a3b8 !important; /* Slate 400 */
|
||||
}
|
||||
|
||||
.house-ad-side .btn-outline-primary {
|
||||
border-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.house-ad-side .btn-outline-primary:hover {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Responsividade para o House Ad Top */
|
||||
@media (max-width: 992px) {
|
||||
.house-ad-top {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
29
wwwroot/manifest.json
Normal file
29
wwwroot/manifest.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "Convert-It Online",
|
||||
"short_name": "Convert-It",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#0d6efd",
|
||||
"theme_color": "#0d6efd",
|
||||
"description": "Conversores rápidos de imagem, documento e áudio.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon.ico",
|
||||
"sizes": "64x64",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"share_target": {
|
||||
"action": "/AudioTools/SpeechToText/HandleShare",
|
||||
"method": "POST",
|
||||
"enctype": "multipart/form-data",
|
||||
"params": {
|
||||
"files": [
|
||||
{
|
||||
"name": "audio",
|
||||
"accept": ["audio/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
7
wwwroot/sw.js
Normal file
7
wwwroot/sw.js
Normal file
@ -0,0 +1,7 @@
|
||||
self.addEventListener('install', (e) => {
|
||||
// Instalado
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (e) => {
|
||||
// Necessário para ser instalável
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user