Sprachausgabe für Dokumente mit Ruby erzeugen
Bei einem kurzen Dokument können Sie den Text mit Ruby extrahieren und in einer einzigen Anfrage an Google Cloud Text-to-Speech senden. Das folgende Beispiel zeigt eine Vorschau des ausgewählten Textes und speichert die MP3-Sprachausgabe in einer neuen Datei. Es akzeptiert UTF-8-Klartext und HTML, mit einem Limit von 1 MiB für die Quelldatei und 5.000 Bytes für den extrahierten Text.
Google Cloud Text-to-Speech einrichten
Verwenden Sie GNU/Linux mit installiertem Ruby und Bundler. Diese Anleitung wurde mit Ruby 4.0.7,
Bundler 4.0.23 und timeout aus GNU coreutils getestet. Zum Prüfen des Ergebnisses benötigen Sie einen lokalen MP3-Player.
Verwenden Sie eine weiterhin gepflegte Ruby-Version von der Ruby-Downloadseite.
Der folgende timeout-Befehl setzt GNU coreutils voraus und ist daher nicht direkt auf macOS oder
Windows übertragbar.
Sie benötigen außerdem ein Google-Cloud-Projekt mit aktivierter Abrechnung und Text-to-Speech-API sowie Zugangsdaten, die deren Nutzung erlauben. Folgen Sie vor der Sprachsynthese Googles Anleitung zur Projekteinrichtung und der Anleitung für Application Default Credentials. Der Code verwendet ADC; er meldet Sie weder an noch ändert er Ihre Cloud-Konfiguration. Der Dokumenttext wird an Google gesendet, und Anfragen können Kosten verursachen. Beginnen Sie mit Beispieltext ohne sensible Inhalte.
Erstellen Sie ein neues, leeres Arbeitsverzeichnis, öffnen Sie dort ein Bash-Terminal und speichern
Sie Folgendes als Gemfile.
Die explizite Gem-Quelle ermöglicht Bundler, die Abhängigkeiten herunterzuladen:
source 'https://rubygems.org'
gem 'google-cloud-text_to_speech', '2.2.1'
gem 'nokogiri', '1.19.4'
Installieren Sie die Abhängigkeiten in diesem Projekt und bewahren Sie die dabei erzeugte Datei
Gemfile.lock zusammen mit Ihrem Code auf:
bundle config set --local path vendor/bundle &&
bundle install --gemfile ./Gemfile
Einfaches Skript für die Sprachausgabe von Dokumenten
Speichern Sie diese Klasse als document_narrator.rb. Die Validierung erfolgt vor dem Erstellen des Clients.
Daher benötigen weder die Textvorschau noch die Ablehnung eines vorhandenen Zielpfads
Cloud-Zugangsdaten. Der extrahierte Text muss neben Leerraum weitere Zeichen enthalten und Googles
Inhaltslimit von 5.000 Bytes einhalten.
Beispielsweise überschreiten 2.501 Wiederholungen von é dieses Limit, obwohl es weniger als 5.000 Zeichen sind.
require 'google/cloud/text_to_speech'
require 'nokogiri'
require 'tempfile'
class DocumentNarrator
class InputError < StandardError; end
def initialize(client: nil)
@client = client
end
def extract_text(file_path)
raise DocumentNarrator::InputError, 'Input must be a regular file' unless File.file?(file_path)
source = File.binread(file_path, 1024 * 1024 + 1)
raise DocumentNarrator::InputError, 'Source exceeds 1 MiB' if source.bytesize > 1024 * 1024
source.force_encoding(Encoding::UTF_8)
raise DocumentNarrator::InputError, 'Source must be valid UTF-8' unless source.valid_encoding?
text = case File.extname(file_path).downcase
when '.txt'
source
when '.html', '.htm'
begin
doc = Nokogiri::HTML5(source, nil, 'UTF-8', max_tree_depth: 400, max_attributes: 400)
rescue ArgumentError
raise InputError, 'HTML nesting or attribute limit exceeded; prepare a .txt file'
end
doc.css('script, style, template, noscript').remove
doc.at_css('body').xpath('.//text()').map(&:text).join(' ').strip
else
raise DocumentNarrator::InputError, 'Use a .txt, .html, or .htm input'
end
raise DocumentNarrator::InputError, 'Extracted text is empty' if text.strip.empty?
raise DocumentNarrator::InputError, 'Extracted text exceeds 5,000 bytes' if text.bytesize > 5000
text
end
def narrate_file(input_path, output_path, language_code: 'en-US')
text = extract_text(input_path)
if File.exist?(output_path) || File.symlink?(output_path)
raise DocumentNarrator::InputError, 'Output must not exist; choose a new filename'
end
Tempfile.create(['.narration-', '.mp3'], File.dirname(output_path)) do |file|
file.binmode
client = @client || Google::Cloud::TextToSpeech.text_to_speech
response = client.synthesize_speech(
input: { text: text },
voice: { language_code: language_code, ssml_gender: :FEMALE },
audio_config: { audio_encoding: :MP3 }
)
raise IOError, 'Synthesis returned empty audio' if response.audio_content.empty?
file.write(response.audio_content)
file.flush
# A hard link publishes the complete candidate without replacing another writer's file.
File.link(file.path, output_path)
end
end
end
Die HTML-Extraktion liest den Text des Dokumentkörpers in Dokumentreihenfolge, fügt ein Leerzeichen
zwischen Textknoten ein und entfernt Elemente vom Typ script, style, template und noscript.
Sie rendert kein CSS, berücksichtigt nicht jedes Attribut zum Ausblenden von Inhalten und bildet
nicht die barrierefreie Lesereihenfolge eines Browsers nach. Außerdem kann sie ein Wort trennen,
das sich über mehrere Inline-Elemente erstreckt. Prüfen Sie die Vorschau und bereiten Sie eine
Klartextversion vor, wenn diese Reihenfolge ungeeignet ist.
Der HTML-Parser begrenzt außerdem die Tiefe des DOM-Baums auf 400
und die Anzahl der Attribute auf 400 pro Element. PDFs und Textverarbeitungsdateien benötigen einen
separaten Extraktionsschritt und werden von diesem Skript nicht abgedeckt.
Erweiterte Funktionen und Fehlerbehandlung
Speichern Sie diesen Einstiegspunkt als narrate.rb neben der Klasse. Dateinamen müssen gültige
UTF-8-Zeichenfolgen sein, auch wenn die Shell die POSIX-Locale verwendet. Das Trennzeichen
-- unterscheidet Syntheseargumente vom Vorschaumodus und ermöglicht Dateinamen, die mit einem
Bindestrich beginnen.
require_relative 'document_narrator'
begin
args = ARGV.map { |arg| arg.dup.force_encoding(Encoding::UTF_8) }
raise DocumentNarrator::InputError, 'Arguments must be valid UTF-8' unless args.all?(&:valid_encoding?)
mode = args.shift
narrator = DocumentNarrator.new
if mode == '--preview' && args.length == 1
puts narrator.extract_text(args[0])
elsif mode == '--' && (2..3).cover?(args.length)
narrator.narrate_file(args[0], args[1], language_code: args[2] || 'en-US')
puts 'Narration saved'
else
raise DocumentNarrator::InputError, 'Usage: narrate.rb --preview INPUT | -- INPUT NEW_OUTPUT [LANGUAGE]'
end
rescue DocumentNarrator::InputError => error
warn error.message
exit 1
rescue StandardError
warn 'Narration failed. Check file permissions, credentials, API access, and your connection.'
exit 1
end
Erstellen Sie document.html mit diesem kleinen Beispiel:
<!doctype html>
<html lang="en">
<head><title>Not spoken</title></head>
<body><p>The copper robot carries a yellow umbrella.</p><script>Not spoken either.</script></body>
</html>
Sehen Sie sich die Vorschau an, bevor Sie eine kostenpflichtige Anfrage senden:
bundle exec ruby narrate.rb --preview document.html
Die Vorschau sollte abgesehen von umgebendem Leerraum nur diesen Satz enthalten:
The copper robot carries a yellow umbrella.
Erzeugen Sie anschließend die Sprachausgabe:
timeout --signal=TERM --kill-after=2s 60s \
bundle exec ruby narrate.rb -- document.html narration.mp3 en-US
Damit stehen dem gesamten Befehl 60 Sekunden zur Verfügung, einschließlich Ruby-Start, Laden der
Zugangsdaten oder OAuth-Aktualisierung und Sprachsynthese. GNU timeout gewährt weitere zwei
Sekunden, bevor ein nicht reagierender Prozess zwangsweise beendet wird. Eine Zeitüberschreitung
ist ein Fehler, selbst wenn der Anbieter die Anfrage erhalten hat. Prüfen Sie den Zielpfad vor
einem erneuten Versuch. Eine erzwungene Beendigung kann eine temporäre Datei .narration- hinterlassen.
Sprachausgabe prüfen und Fehler behandeln
Bei Erfolg gibt der Befehl Narration saved aus und erstellt
narration.mp3. Öffnen Sie genau diese Datei in Ihrem MP3-Player und hören Sie sie bis zum Ende an.
Vergleichen Sie sie dabei mit der Vorschau. Eine nicht leere oder abspielbare MP3-Datei allein
belegt nicht, dass der richtige Text gesprochen wurde. Die Spracheinstellung der Stimme übersetzt
das Dokument nicht. Verwenden Sie einen Sprachcode, der zum Text passt.
Das übergeordnete Verzeichnis des Zielpfads muss bereits existieren und Hardlinks unterstützen. Eine vorhandene Datei oder ein symbolischer Link wird abgelehnt. Auch die Bereitstellung schlägt fehl, wenn ein anderer Prozess diesen Pfad während der Synthese anlegt. Bei einem Anbieterfehler bleibt eine vorhandene Zieldatei unverändert. Wählen Sie für eine zweite Aufnahme einen anderen Zielpfad; das Skript bietet keine Option zum Überschreiben.
Bei Eingabefehlern wird die fehlgeschlagene Prüfung gemeldet. Bereiten Sie bei einer Quelldatei über 1 MiB oder einem extrahierten Text über 5.000 Bytes ein kleineres Dokument vor, statt es stillschweigend abzuschneiden. Wird HTML wegen der Verschachtelungstiefe oder der Attribute abgelehnt, bereiten Sie eine Klartextversion vor. Versuchen Sie bei einem allgemeinen Fehler zunächst die Vorschau. Prüfen Sie anschließend die Dateisystemberechtigungen sowie ADC, Abrechnung, API-Zugriff und Netzwerkverbindung für das ausgewählte Projekt. Details zu Anbieterfehlern und Dokumenttext werden nicht in die Synthesediagnose aufgenommen. Längere Sprachausgaben benötigen einen separaten Ablauf zum Aufteilen und Zusammenfügen; dieses Beispiel sendet bewusst nur eine synchrone Anfrage.
