Amazon Polly, Transcribe, Comprehend та Translate
Amazon Polly, Transcribe, Comprehend та Translate
AWS надає набір спеціалізованих AI-сервісів для роботи зі звуком та текстом, які доступні через прості REST API та не потребують знань у машинному навчанні. Ці сервіси масштабуються автоматично та тарифікуються за фактом використання (pay-per-use).
Огляд сервісів
Amazon Polly
Amazon Transcribe
Amazon Comprehend
Amazon Translate
Встановлення залежностей у .NET 8
Для підключення відповідних сервісів встановіть NuGet-пакети:
dotnet add package AWSSDK.Polly
dotnet add package AWSSDK.TranscribeService
dotnet add package AWSSDK.Comprehend
dotnet add package AWSSDK.Translate
Повна реалізація сервісів на .NET 8
Нижче наведено готові до використання повністю реалізовані C# класи для кожного з чотирьох сервісів.
1. Amazon Polly (Синтез мовлення)
using System;
using System.IO;
using System.Threading.Tasks;
using Amazon.Polly;
using Amazon.Polly.Model;
namespace AwsAiPlayground.Services;
public sealed class PollyService
{
private readonly IAmazonPolly _pollyClient;
public PollyService(IAmazonPolly pollyClient)
{
_pollyClient = pollyClient;
}
/// <summary>
/// Перетворює текст на аудіопотік MP3 за допомогою Neural-голосу.
/// </summary>
public async Task<Stream> SynthesizeSpeechAsync(string text, string voiceId = "Matthew")
{
var request = new SynthesizeSpeechRequest
{
Text = text,
OutputFormat = OutputFormat.Mp3,
VoiceId = voiceId,
Engine = Engine.Neural, // Neural забезпечує більш природне звукоутворення
TextType = text.StartsWith("<speak>") ? TextType.Ssml : TextType.Text
};
try
{
var response = await _pollyClient.SynthesizeSpeechAsync(request);
return response.AudioStream;
}
catch (AmazonPollyException ex)
{
throw new Exception($"Amazon Polly error: {ex.Message}", ex);
}
}
}
2. Amazon Transcribe (Розпізнавання аудіо)
Оскільки розпізнавання великих аудіофайлів триває певний час, робота виконується асинхронно через створення задачі (Transcription Job) з періодичним опитуванням (polling) її статусу.
using System;
using System.Threading.Tasks;
using Amazon.TranscribeService;
using Amazon.TranscribeService.Model;
namespace AwsAiPlayground.Services;
public sealed class TranscribeService
{
private readonly IAmazonTranscribeService _transcribeClient;
public TranscribeService(IAmazonTranscribeService transcribeClient)
{
_transcribeClient = transcribeClient;
}
/// <summary>
/// Запускає задачу розпізнавання аудіо з S3 та чекає її завершення, повертаючи URL результату.
/// </summary>
public async Task<string> StartAndPollTranscriptionJobAsync(
string jobName,
string mediaFileUri,
string languageCode = "en-US")
{
var startRequest = new StartTranscriptionJobRequest
{
TranscriptionJobName = jobName,
Media = new Media { MediaFileUri = mediaFileUri },
MediaFormat = MediaFormat.Mp3, // підтримується mp3, wav, mp4, ogg, flac
LanguageCode = languageCode
};
try
{
await _transcribeClient.StartTranscriptionJobAsync(startRequest);
}
catch (AmazonTranscribeServiceException ex)
{
throw new Exception($"Failed to start Transcribe job: {ex.Message}", ex);
}
// Опитування (Polling) статусу виконання задачі
while (true)
{
var statusRequest = new GetTranscriptionJobRequest { TranscriptionJobName = jobName };
var response = await _transcribeClient.GetTranscriptionJobAsync(statusRequest);
var status = response.TranscriptionJob.TranscriptionJobStatus;
if (status == TranscriptionJobStatus.COMPLETED)
{
// Повертає підписане посилання на JSON-файл із розпізнаним текстом
return response.TranscriptionJob.Transcript.TranscriptFileUri;
}
if (status == TranscriptionJobStatus.FAILED)
{
throw new Exception($"Transcription job failed: {response.TranscriptionJob.FailureReason}");
}
// Очікуємо 5 секунд перед наступним запитом
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
}
3. Amazon Comprehend (Аналіз тексту / NLP)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.Comprehend;
using Amazon.Comprehend.Model;
namespace AwsAiPlayground.Services;
public record EntityDto(string Text, string Type, float Score);
public record TextAnalysisResultDto(
string Sentiment,
float PositiveScore,
float NegativeScore,
List<EntityDto> Entities,
List<string> KeyPhrases);
public sealed class ComprehendService
{
private readonly IAmazonComprehend _comprehendClient;
public ComprehendService(IAmazonComprehend comprehendClient)
{
_comprehendClient = comprehendClient;
}
/// <summary>
/// Аналізує тональність, сутності та ключові фрази у тексті.
/// </summary>
public async Task<TextAnalysisResultDto> AnalyzeTextAsync(string text, string languageCode = "en")
{
var sentimentRequest = new DetectSentimentRequest { Text = text, LanguageCode = languageCode };
var entitiesRequest = new DetectEntitiesRequest { Text = text, LanguageCode = languageCode };
var keyPhrasesRequest = new DetectKeyPhrasesRequest { Text = text, LanguageCode = languageCode };
try
{
// Виконуємо запити паралельно для швидкодії
var sentimentTask = _comprehendClient.DetectSentimentAsync(sentimentRequest);
var entitiesTask = _comprehendClient.DetectEntitiesAsync(entitiesRequest);
var keyPhrasesTask = _comprehendClient.DetectKeyPhrasesAsync(keyPhrasesRequest);
await Task.WhenAll(sentimentTask, entitiesTask, keyPhrasesTask);
var sentiment = await sentimentTask;
var entities = await entitiesTask;
var keyPhrases = await keyPhrasesTask;
return new TextAnalysisResultDto(
Sentiment: sentiment.Sentiment.Value,
PositiveScore: sentiment.SentimentScore.Positive,
NegativeScore: sentiment.SentimentScore.Negative,
Entities: entities.Entities.Select(e => new EntityDto(e.Text, e.Type.Value, e.Score)).ToList(),
KeyPhrases: keyPhrases.KeyPhrases.Select(kp => kp.Text).ToList()
);
}
catch (AmazonComprehendException ex)
{
throw new Exception($"Amazon Comprehend error: {ex.Message}", ex);
}
}
}
4. Amazon Translate (Переклад тексту)
using System;
using System.Threading.Tasks;
using Amazon.Translate;
using Amazon.Translate.Model;
namespace AwsAiPlayground.Services;
public sealed class TranslateService
{
private readonly IAmazonTranslate _translateClient;
public TranslateService(IAmazonTranslate translateClient)
{
_translateClient = translateClient;
}
/// <summary>
/// Перекладає вказаний текст з однієї мови на іншу.
/// </summary>
public async Task<string> TranslateTextAsync(
string text,
string sourceLang = "auto",
string targetLang = "uk")
{
var request = new TranslateTextRequest
{
Text = text,
SourceLanguageCode = sourceLang, // "auto" вмикає автоматичне визначення мови
TargetLanguageCode = targetLang
};
try
{
var response = await _translateClient.TranslateTextAsync(request);
return response.TranslatedText;
}
catch (AmazonTranslateException ex)
{
throw new Exception($"Amazon Translate error: {ex.Message}", ex);
}
}
}
Інтеграція з React: Консоль обробки аудіо та тексту
Створимо інтерактивний React-компонент, який дозволяє перекладати текст, аналізувати тональність та прослуховувати аудіо, озвучене за допомогою Amazon Polly.
Повний React компонент AudioNlpConsole
import React, { useState } from 'react';
interface Entity {
text: string;
type: string;
score: number;
}
interface AnalysisResult {
sentiment: string;
positiveScore: number;
negativeScore: number;
entities: Entity[];
keyPhrases: string[];
}
export function AudioNlpConsole() {
const [inputText, setInputText] = useState('');
const [targetLang, setTargetLang] = useState('uk');
const [translatedText, setTranslatedText] = useState('');
const [analysis, setAnalysis] = useState<AnalysisResult | null>(null);
const [audioUrl, setAudioUrl] = useState<string | null>(null);
const [pollyVoice, setPollyVoice] = useState('Matthew'); // English (Matthew), Spanish (Conchita) тощо
const [loading, setLoading] = useState({
translate: false,
analyze: false,
speak: false,
});
const handleTranslate = async () => {
if (!inputText.trim()) return;
setLoading((prev) => ({ ...prev, translate: true }));
try {
const response = await fetch('http://localhost:5000/api/nlp/translate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: inputText, targetLanguage: targetLang }),
});
const data = await response.json();
setTranslatedText(data.translatedText);
} catch (err) {
console.error('Помилка перекладу:', err);
} finally {
setLoading((prev) => ({ ...prev, translate: false }));
}
};
const handleAnalyze = async () => {
if (!inputText.trim()) return;
setLoading((prev) => ({ ...prev, analyze: true }));
try {
const response = await fetch('http://localhost:5000/api/nlp/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: inputText }),
});
const data = await response.json();
setAnalysis(data);
} catch (err) {
console.error('Помилка аналізу тексту:', err);
} finally {
setLoading((prev) => ({ ...prev, analyze: false }));
}
};
const handleSpeak = async () => {
if (!inputText.trim()) return;
setLoading((prev) => ({ ...prev, speak: true }));
try {
const response = await fetch('http://localhost:5000/api/nlp/speak', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: inputText, voiceId: pollyVoice }),
});
if (!response.ok) throw new Error('Не вдалося синтезувати мовлення.');
// Отримуємо аудіопотік у вигляді Blob
const audioBlob = await response.blob();
const audioBlobUrl = URL.createObjectURL(audioBlob);
setAudioUrl(audioBlobUrl);
} catch (err) {
console.error('Помилка Polly синтезу:', err);
} finally {
setLoading((prev) => ({ ...prev, speak: false }));
}
};
return (
<div style={styles.container}>
<h2 style={styles.header}>AWS Audio & NLP Playroom</h2>
<div style={styles.textSection}>
<label style={styles.label}>Вхідний текст для обробки (англійською мовою):</label>
<textarea
style={styles.textarea}
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Type something here to translate, synthesize, or analyze..."
rows={4}
/>
<div style={styles.btnRow}>
<button style={styles.actionBtn} onClick={handleTranslate} disabled={loading.translate}>
{loading.translate ? 'Переклад...' : '🌐 Перекласти'}
</button>
<button style={styles.actionBtn} onClick={handleAnalyze} disabled={loading.analyze}>
{loading.analyze ? 'Аналіз...' : '🧠 Аналіз тональності (NLP)'}
</button>
<button style={styles.actionBtn} onClick={handleSpeak} disabled={loading.speak}>
{loading.speak ? 'Генерація...' : '🔊 Озвучити (Polly)'}
</button>
</div>
</div>
<div style={styles.resultsGrid}>
{/* Переклад */}
<div style={styles.resultBox}>
<h4 style={styles.boxTitle}>Переклад:</h4>
<select
value={targetLang}
onChange={(e) => setTargetLang(e.target.value)}
style={styles.select}
>
<option value="uk">Українська (uk)</option>
<option value="de">Німецька (de)</option>
<option value="fr">Французька (fr)</option>
<option value="es">Іспанська (es)</option>
</select>
<div style={styles.textContent}>{translatedText || 'Тут з\'явиться перекладений текст.'}</div>
</div>
{/* Озвучування */}
<div style={styles.resultBox}>
<h4 style={styles.boxTitle}>Синтез мовлення (Polly):</h4>
<select
value={pollyVoice}
onChange={(e) => setPollyVoice(e.target.value)}
style={styles.select}
>
<option value="Matthew">Matthew (Чоловічий, US)</option>
<option value="Joanna">Joanna (Жіночий, US)</option>
<option value="Kendra">Kendra (Жіночий, US)</option>
<option value="Ruth">Ruth (Жіночий, US - Neural Only)</option>
</select>
<div style={styles.audioWrapper}>
{audioUrl ? (
<audio src={audioUrl} controls style={styles.audioPlayer} />
) : (
<span style={styles.placeholder}>Згенеруйте аудіо для відтворення.</span>
)}
</div>
</div>
</div>
{/* NLP Аналіз */}
{analysis && (
<div style={styles.nlpPanel}>
<h3 style={styles.boxTitle}>Результати NLP Аналізу (Comprehend)</h3>
<p>
<strong>Тональність:</strong> {analysis.sentiment} (Positive: {Math.round(analysis.positiveScore * 100)}%, Negative: {Math.round(analysis.negativeScore * 100)}%)
</p>
<div style={styles.nlpDetails}>
<div style={styles.nlpColumn}>
<h4>Виявлені сутності (Entities)</h4>
<ul style={styles.list}>
{analysis.entities.map((e, index) => (
<li key={index} style={styles.listItem}>
<strong>{e.text}</strong> — <span style={styles.badge}>{e.type}</span> (conf: {Math.round(e.score * 100)}%)
</li>
))}
{analysis.entities.length === 0 && <li>Сутностей не виявлено.</li>}
</ul>
</div>
<div style={styles.nlpColumn}>
<h4>Ключові фрази</h4>
<div style={styles.phrasesContainer}>
{analysis.keyPhrases.map((phrase, index) => (
<span key={index} style={styles.phraseTag}>{phrase}</span>
))}
{analysis.keyPhrases.length === 0 && <span>Фраз не знайдено.</span>}
</div>
</div>
</div>
</div>
)}
</div>
);
}
const styles = {
container: {
background: '#1f2937',
padding: '24px',
borderRadius: '12px',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.3)',
color: '#f3f4f6',
fontFamily: 'Inter, system-ui, sans-serif',
maxWidth: '900px',
margin: '20px auto',
},
header: {
marginTop: 0,
background: 'linear-gradient(90deg, #34d399, #60a5fa)',
-webkit-background-clip: text,
-webkit-text-fill-color: transparent,
},
textSection: {
marginBottom: '24px',
},
label: {
display: 'block',
fontSize: '0.9rem',
color: '#9ca3af',
marginBottom: '8px',
},
textarea: {
width: '100%',
background: '#111827',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '8px',
padding: '12px',
color: '#fff',
fontSize: '1rem',
outline: 'none',
boxSizing: 'border-box' as const,
},
btnRow: {
display: 'flex',
gap: '12px',
marginTop: '12px',
flexWrap: 'wrap' as const,
},
actionBtn: {
background: '#3b82f6',
color: '#fff',
border: 'none',
padding: '10px 16px',
borderRadius: '6px',
cursor: 'pointer',
fontWeight: 500,
transition: 'background 0.2s',
},
resultsGrid: {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '20px',
marginBottom: '24px',
},
resultBox: {
background: '#111827',
padding: '16px',
borderRadius: '8px',
border: '1px solid rgba(255, 255, 255, 0.03)',
display: 'flex',
flexDirection: 'column' as const,
},
boxTitle: {
margin: '0 0 12px 0',
color: '#34d399',
},
select: {
background: '#1f2937',
border: '1px solid rgba(255, 255, 255, 0.1)',
color: '#fff',
padding: '8px',
borderRadius: '4px',
marginBottom: '12px',
},
textContent: {
background: 'rgba(255, 255, 255, 0.02)',
padding: '12px',
borderRadius: '6px',
minHeight: '80px',
fontSize: '0.95rem',
whiteSpace: 'pre-wrap' as const,
border: '1px solid rgba(255, 255, 255, 0.02)',
},
audioWrapper: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '80px',
},
audioPlayer: {
width: '100%',
},
nlpPanel: {
background: '#111827',
padding: '20px',
borderRadius: '8px',
border: '1px solid rgba(255, 255, 255, 0.05)',
},
nlpDetails: {
display: 'flex',
gap: '24px',
marginTop: '16px',
},
nlpColumn: {
flex: 1,
},
list: {
listStyleType: 'none',
padding: 0,
margin: 0,
},
listItem: {
padding: '6px 0',
borderBottom: '1px solid rgba(255, 255, 255, 0.03)',
fontSize: '0.9rem',
},
badge: {
background: '#6b7280',
padding: '2px 6px',
borderRadius: '4px',
fontSize: '0.8rem',
},
phrasesContainer: {
display: 'flex',
flexWrap: 'wrap' as const,
gap: '8px',
},
phraseTag: {
background: 'rgba(96, 165, 250, 0.15)',
color: '#60a5fa',
padding: '4px 10px',
borderRadius: '16px',
fontSize: '0.85rem',
},
placeholder: {
color: '#6b7280',
fontSize: '0.9rem',
},
};
Amazon Textract - Інтелектуальний аналіз документів
Практичний посібник з використання Amazon Textract. Повна реалізація аналізу форм та таблиць з PDF/зображень у .NET 8. Клієнтський React-компонент для відображення структур документів.
Що таке Tailwind CSS і навіщо він потрібен
Глибоке занурення у філософію utility-first CSS. Чому Tailwind вирішує реальні проблеми CSS у великих проєктах, як він працює під капотом і коли його варто, а коли не варто використовувати.