Created
July 15, 2026 16:47
-
-
Save bogdan/2b97e76c50fc06ba61dbb49eb52b2dca to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby | |
| # frozen_string_literal: true | |
| # Speech-to-Text v2 diarization test (chirp_3 model, Japanese by default). | |
| # | |
| # KEY FINDING: chirp_3 + diarizationConfig only works in the "us" multi-region. | |
| # It is rejected everywhere else tested (global, us-central1, europe-west4, | |
| # asia-southeast1, europe-west3) with either "model does not exist" or | |
| # "Permission denied ... It is no longer generally available." Only the | |
| # "us" multi-region endpoint accepts it, and only via the async | |
| # batch_recognize method (sync recognize never supports diarization, in | |
| # v1 or v2, in any region). | |
| # | |
| # Usage: | |
| # script/gcp_diarize_test.rb <path-to-audio-file> <gcs-bucket-name> [language-code] | |
| # | |
| # Example: | |
| # script/gcp_diarize_test.rb ./recording.mp3 my-project-scratch-bucket ja-JP | |
| # | |
| # Auth: uses local Application Default Credentials. | |
| # gcloud auth application-default login --project <PROJECT_ID> | |
| # | |
| # The GCS bucket must already exist in the same project you're calling the | |
| # Speech API against (cross-project GCS reads commonly hit permission | |
| # errors from the Speech service agent). | |
| require "bundler/inline" | |
| gemfile do | |
| source "https://rubygems.org" | |
| gem "google-cloud-speech-v2" | |
| end | |
| require "google/cloud/speech/v2" | |
| require "open3" | |
| require "json" | |
| audio_file, bucket, language_code = ARGV | |
| if audio_file.nil? || bucket.nil? | |
| warn "Usage: #{$PROGRAM_NAME} <audio-file> <gcs-bucket> [language-code]" | |
| exit 1 | |
| end | |
| language_code ||= "ja-JP" | |
| project = `gcloud config get-value project 2>/dev/null`.strip | |
| raise "No active gcloud project. Run: gcloud config set project <PROJECT_ID>" if project.empty? | |
| file_name = File.basename(audio_file) | |
| gcs_audio_uri = "gs://#{bucket}/diarize-test/#{file_name}" | |
| gcs_output_uri = "gs://#{bucket}/diarize-test/output/" | |
| puts "Project: #{project}" | |
| puts "Audio file: #{audio_file}" | |
| puts "Language: #{language_code}" | |
| puts "GCS audio URI: #{gcs_audio_uri}" | |
| puts | |
| puts "==> Uploading audio to GCS..." | |
| system("gcloud", "storage", "cp", audio_file, gcs_audio_uri, "--project", project, exception: true) | |
| client = Google::Cloud::Speech::V2::Speech::Client.new do |config| | |
| config.endpoint = "us-speech.googleapis.com" | |
| end | |
| recognizer = "projects/#{project}/locations/us/recognizers/_" | |
| request = { | |
| recognizer: recognizer, | |
| files: [ { uri: gcs_audio_uri } ], | |
| config: { | |
| auto_decoding_config: {}, | |
| model: "chirp_3", | |
| language_codes: [ language_code ], | |
| features: { | |
| enable_automatic_punctuation: true, | |
| enable_word_time_offsets: true, | |
| diarization_config: {} | |
| } | |
| }, | |
| recognition_output_config: { | |
| gcs_output_config: { uri: gcs_output_uri } | |
| } | |
| } | |
| puts "==> Submitting batch_recognize (model=chirp_3, location=us)..." | |
| operation = client.batch_recognize(request) | |
| puts "==> Waiting for operation to complete..." | |
| operation.wait_until_done! | |
| raise "Operation failed: #{operation.results}" if operation.error? | |
| result_uri = operation.response.results.values.first.uri | |
| puts "==> Downloading transcript from #{result_uri}" | |
| json_text, status = Open3.capture2("gcloud", "storage", "cat", result_uri, "--project", project) | |
| raise "Failed to download transcript" unless status.success? | |
| data = JSON.parse(json_text) | |
| puts | |
| puts "==================== DIARIZED TRANSCRIPT ====================" | |
| data.fetch("results", []).each do |r| | |
| alt = r.fetch("alternatives", []).first | |
| next unless alt | |
| words = alt.fetch("words", []) | |
| next if words.empty? | |
| current_speaker = nil | |
| line = [] | |
| words.each do |w| | |
| speaker = w["speakerLabel"] | |
| if speaker != current_speaker | |
| puts "Speaker #{current_speaker}: #{line.join}" unless line.empty? | |
| current_speaker = speaker | |
| line = [] | |
| end | |
| line << w["word"] | |
| end | |
| puts "Speaker #{current_speaker}: #{line.join}" unless line.empty? | |
| end | |
| puts "================================================================" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment