AWS

Amazon Polly, Transcribe, Comprehend та Translate

Розробка рішень для обробки природної мови (NLP) та звуку за допомогою AWS AI Services. Повні C# класи для синтезу та транскрипції мовлення, аналізу тональності й перекладу з React-інтерфейсом.

Amazon Polly, Transcribe, Comprehend та Translate

AWS надає набір спеціалізованих AI-сервісів для роботи зі звуком та текстом, які доступні через прості REST API та не потребують знань у машинному навчанні. Ці сервіси масштабуються автоматично та тарифікуються за фактом використання (pay-per-use).

Огляд сервісів

Amazon Polly

Перетворює текст на реалістичне мовлення (Text-to-Speech). Підтримує технологію Neural TTS (природна інтонація), мову розмітки SSML для керування паузами й акцентами, а також десятки голосів і мов.

Amazon Transcribe

Розпізнає аудіо та перетворює його на текст (Speech-to-Text). Підтримує потокове розпізнавання у реальному часі, пакетну обробку аудіофайлів з S3, розпізнавання різних спікерів (diarization) та додавання кастомних словників.

Amazon Comprehend

Аналізує неструктурований текст (Natural Language Processing). Дозволяє визначати тональність (Sentiment), ключові слова, іменовані сутності (люди, локації, бренди), мову тексту та класифікувати контент.

Amazon Translate

Сервіс високоточного машинного перекладу. Підтримує переклад між 75+ мовами, автоматично визначає мову джерела та дозволяє завантажувати кастомні термінологічні словники.

Встановлення залежностей у .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 (Синтез мовлення)

Services/PollyService.cs
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) її статусу.

Services/TranscribeService.cs
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)

Services/ComprehendService.cs
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 (Переклад тексту)

Services/TranslateService.cs
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

src/components/AudioNlpConsole.tsx
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',
  },
};
Copyright © 2026