概要
このチュートリアルでは、Tiro API を使って音声ファイルをアップロードし、文字起こしを行い、複数の言語に翻訳するまでの一連の手順を説明します。前提条件
- 有効な Tiro API key
- 音声ファイル(MP3、WAV、M4A)
- 最大ファイルサイズ: 500MB
- 最大の長さ: 4 時間
Step 1: Voice File Job を作成する
まず、希望する文字起こしと翻訳の設定でジョブを作成します。curl -X POST https://api.tiro.ooo/v1/external/voice-file/jobs \
-H "Authorization: Bearer $TIRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transcriptLocaleHints": ["ko_KR"],
"translationLocales": ["en_US"]
}'
async function createVoiceFileJob() {
const response = await fetch('https://api.tiro.ooo/v1/external/voice-file/jobs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TIRO_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
transcriptLocaleHints: ['ko_KR'],
translationLocales: ['en_US']
})
});
if (!response.ok) {
throw new Error(`Failed to create job: ${response.status}`);
}
const job = await response.json();
console.log('Job created:', job.id);
console.log('Upload URL:', job.uploadUri);
return job;
}
import requests
import os
def create_voice_file_job():
response = requests.post(
'https://api.tiro.ooo/v1/external/voice-file/jobs',
headers={
'Authorization': f'Bearer {os.environ["TIRO_API_KEY"]}',
'Content-Type': 'application/json'
},
json={
'transcriptLocaleHints': ['ko_KR'],
'translationLocales': ['en_US']
}
)
response.raise_for_status()
job = response.json()
print(f'Job created: {job["id"]}')
print(f'Upload URL: {job["uploadUri"]}')
return job
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
type CreateJobRequest struct {
TranscriptLocaleHints []string `json:"transcriptLocaleHints"`
TranslationLocales []string `json:"translationLocales"`
}
type CreateJobResponse struct {
ID string `json:"id"`
UploadUri string `json:"uploadUri"`
}
func createVoiceFileJob() (*CreateJobResponse, error) {
reqBody := CreateJobRequest{
TranscriptLocaleHints: []string{"ko_KR"},
TranslationLocales: []string{"en_US"},
}
jsonBody, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", "https://api.tiro.ooo/v1/external/voice-file/jobs", bytes.NewBuffer(jsonBody))
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("TIRO_API_KEY")))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var job CreateJobResponse
json.NewDecoder(resp.Body).Decode(&job)
fmt.Printf("Job created: %s\n", job.ID)
fmt.Printf("Upload URL: %s\n", job.UploadUri)
return &job, nil
}
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.*
import org.springframework.web.client.RestTemplate
data class CreateJobRequest(
val transcriptLocaleHints: List<String>,
val translationLocales: List<String>
)
data class CreateJobResponse(
val id: String,
val uploadUri: String
)
@Service
class VoiceFileService {
@Value("\${tiro.api.key}")
private lateinit var apiKey: String
private val restTemplate = RestTemplate()
fun createVoiceFileJob(): CreateJobResponse {
val headers = HttpHeaders().apply {
set("Authorization", "Bearer $apiKey")
contentType = MediaType.APPLICATION_JSON
}
val request = CreateJobRequest(
transcriptLocaleHints = listOf("ko_KR"),
translationLocales = listOf("en_US")
)
val entity = HttpEntity(request, headers)
val response = restTemplate.exchange(
"https://api.tiro.ooo/v1/external/voice-file/jobs",
HttpMethod.POST,
entity,
CreateJobResponse::class.java
)
val job = response.body!!
println("Job created: ${job.id}")
println("Upload URL: ${job.uploadUri}")
return job
}
}
{
"id": "b2d1ab32-3fe2-4201-b0b4-391abdbaa023",
"uploadUri": "https://storage.example.com/upload/signed-url"
}
Step 2: 音声ファイルをアップロードする
提供された署名付き URL に音声ファイルをアップロードします。# Upload audio file to the signed URL (replace UPLOAD_URI with the uploadUri from Step 1)
curl -X PUT "$UPLOAD_URI" \
-H "Content-Type: audio/mpeg" \
--data-binary @/path/to/your/audio.mp3
async function uploadAudioFile(uploadUri, audioFile) {
const response = await fetch(uploadUri, {
method: 'PUT',
body: audioFile,
headers: {
'Content-Type': audioFile.type || 'audio/mpeg'
}
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
console.log('File uploaded successfully');
}
// For Node.js with file system
const fs = require('fs');
async function uploadAudioFileNode(uploadUri, filePath) {
const fileBuffer = fs.readFileSync(filePath);
const response = await fetch(uploadUri, {
method: 'PUT',
body: fileBuffer,
headers: {
'Content-Type': 'audio/mpeg'
}
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
console.log('File uploaded successfully');
}
def upload_audio_file(upload_uri, file_path):
with open(file_path, 'rb') as audio_file:
response = requests.put(
upload_uri,
data=audio_file,
headers={'Content-Type': 'audio/mpeg'}
)
response.raise_for_status()
print('File uploaded successfully')
func uploadAudioFile(uploadUri, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
req, _ := http.NewRequest("PUT", uploadUri, file)
req.Header.Set("Content-Type", "audio/mpeg")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("upload failed: %d", resp.StatusCode)
}
fmt.Println("File uploaded successfully")
return nil
}
fun uploadAudioFile(uploadUri: String, filePath: String) {
val file = File(filePath)
val headers = HttpHeaders().apply {
contentType = MediaType.parseMediaType("audio/mpeg")
}
val resource = FileSystemResource(file)
val entity = HttpEntity(resource, headers)
val response = restTemplate.exchange(
uploadUri,
HttpMethod.PUT,
entity,
String::class.java
)
if (response.statusCode.is2xxSuccessful) {
println("File uploaded successfully")
} else {
throw RuntimeException("Upload failed: ${response.statusCode}")
}
}
Step 3: アップロード完了を通知する
アップロードが完了したことを API に伝えます。curl -X PUT "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/upload-complete" \
-H "Authorization: Bearer $TIRO_API_KEY" \
-H "Content-Type: application/json"
async function notifyUploadComplete(jobId) {
const response = await fetch(
`https://api.tiro.ooo/v1/external/voice-file/jobs/${jobId}/upload-complete`,
{
method: 'PUT',
headers: {
'Authorization': `Bearer ${process.env.TIRO_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
if (!response.ok) {
throw new Error(`Failed to notify upload: ${response.status}`);
}
console.log('Upload notification sent');
}
def notify_upload_complete(job_id):
response = requests.put(
f'https://api.tiro.ooo/v1/external/voice-file/jobs/{job_id}/upload-complete',
headers={
'Authorization': f'Bearer {os.getenv("TIRO_API_KEY")}',
'Content-Type': 'application/json'
}
)
response.raise_for_status()
print('Upload notification sent')
func notifyUploadComplete(jobId string) error {
url := fmt.Sprintf("https://api.tiro.ooo/v1/external/voice-file/jobs/%s/upload-complete", jobId)
req, _ := http.NewRequest("PUT", url, nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("TIRO_API_KEY")))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println("Upload notification sent")
return nil
}
fun notifyUploadComplete(jobId: String) {
val headers = HttpHeaders().apply {
set("Authorization", "Bearer $apiKey")
contentType = MediaType.APPLICATION_JSON
}
val entity = HttpEntity<String>(headers)
restTemplate.exchange(
"https://api.tiro.ooo/v1/external/voice-file/jobs/$jobId/upload-complete",
HttpMethod.PUT,
entity,
String::class.java
)
println("Upload notification sent")
}
Step 4: ジョブの完了をポーリングする
処理が完了するまで、ジョブのステータスを監視します。# Check job status (poll until status is COMPLETED or FAILED)
curl -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID" \
-H "Authorization: Bearer $TIRO_API_KEY"
# Example response:
# {
# "id": "job_abc123",
# "status": "COMPLETED",
# "fileUploadedAt": "2024-01-01T12:00:20Z",
# "processStartedAt": "2024-01-01T12:01:00Z",
# "processCompletedAt": "2024-01-01T12:02:30Z"
# }
async function waitForJobCompletion(jobId) {
let delay = 2000; // Start with 2 seconds
const maxDelay = 30000; // Max 30 seconds
const maxAttempts = 60; // 30 minutes max
let attempts = 0;
while (attempts < maxAttempts) {
try {
const response = await fetch(
`https://api.tiro.ooo/v1/external/voice-file/jobs/${jobId}`,
{
headers: {
'Authorization': `Bearer ${process.env.TIRO_API_KEY}`
}
}
);
if (!response.ok) {
throw new Error(`Failed to get job status: ${response.status}`);
}
const job = await response.json();
console.log(`Job status: ${job.status}`);
if (job.status === 'COMPLETED') {
console.log('Job completed successfully!');
return job;
}
if (job.status === 'FAILED') {
throw new Error(`Job failed: ${job.errorMessage || 'Unknown error'}`);
}
// Still processing - wait with exponential backoff
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 1.2, maxDelay);
attempts++;
} catch (error) {
console.error('Error checking job status:', error);
attempts++;
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 1.5, maxDelay);
}
}
throw new Error('Job did not complete within timeout');
}
import time
import asyncio
async def wait_for_job_completion(job_id):
delay = 2 # Start with 2 seconds
max_delay = 30 # Max 30 seconds
max_attempts = 60 # 30 minutes max
attempts = 0
while attempts < max_attempts:
try:
response = requests.get(
f'https://api.tiro.ooo/v1/external/voice-file/jobs/{job_id}',
headers={
'Authorization': f'Bearer {os.getenv("TIRO_API_KEY")}'
}
)
response.raise_for_status()
job = response.json()
print(f'Job status: {job["status"]}')
if job['status'] == 'COMPLETED':
print('Job completed successfully!')
return job
if job['status'] == 'FAILED':
error_msg = job.get('errorMessage', 'Unknown error')
raise Exception(f'Job failed: {error_msg}')
# Still processing - wait with exponential backoff
await asyncio.sleep(delay)
delay = min(delay * 1.2, max_delay)
attempts += 1
except Exception as e:
print(f'Error checking job status: {e}')
attempts += 1
await asyncio.sleep(delay)
delay = min(delay * 1.5, max_delay)
raise Exception('Job did not complete within timeout')
type JobStatus struct {
ID string `json:"id"`
Status string `json:"status"`
ErrorMessage string `json:"errorMessage,omitempty"`
}
func waitForJobCompletion(jobId string) (*JobStatus, error) {
delay := 2 * time.Second
maxDelay := 30 * time.Second
maxAttempts := 60
client := &http.Client{}
url := fmt.Sprintf("https://api.tiro.ooo/v1/external/voice-file/jobs/%s", jobId)
for attempts := 0; attempts < maxAttempts; attempts++ {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("TIRO_API_KEY")))
resp, err := client.Do(req)
if err != nil {
time.Sleep(delay)
delay = time.Duration(float64(delay) * 1.5)
if delay > maxDelay {
delay = maxDelay
}
continue
}
var job JobStatus
json.NewDecoder(resp.Body).Decode(&job)
resp.Body.Close()
fmt.Printf("Job status: %s\n", job.Status)
if job.Status == "COMPLETED" {
fmt.Println("Job completed successfully!")
return &job, nil
}
if job.Status == "FAILED" {
return nil, fmt.Errorf("job failed: %s", job.ErrorMessage)
}
time.Sleep(delay)
delay = time.Duration(float64(delay) * 1.2)
if delay > maxDelay {
delay = maxDelay
}
}
return nil, fmt.Errorf("job did not complete within timeout")
}
import java.util.concurrent.TimeUnit
data class JobStatus(
val id: String,
val status: String,
val errorMessage: String? = null
)
fun waitForJobCompletion(jobId: String): JobStatus {
var delay = 2000L // 2 seconds
val maxDelay = 30000L // 30 seconds
val maxAttempts = 60
repeat(maxAttempts) { attempt ->
try {
val headers = HttpHeaders().apply {
set("Authorization", "Bearer $apiKey")
}
val entity = HttpEntity<String>(headers)
val response = restTemplate.exchange(
"https://api.tiro.ooo/v1/external/voice-file/jobs/$jobId",
HttpMethod.GET,
entity,
JobStatus::class.java
)
val job = response.body!!
println("Job status: ${job.status}")
when (job.status) {
"COMPLETED" -> {
println("Job completed successfully!")
return job
}
"FAILED" -> {
throw RuntimeException("Job failed: ${job.errorMessage}")
}
}
TimeUnit.MILLISECONDS.sleep(delay)
delay = minOf((delay * 1.2).toLong(), maxDelay)
} catch (e: Exception) {
println("Error checking job status: ${e.message}")
TimeUnit.MILLISECONDS.sleep(delay)
delay = minOf((delay * 1.5).toLong(), maxDelay)
}
}
throw RuntimeException("Job did not complete within timeout")
}
Step 5: 結果を取得する
ジョブが完了したら、transcript と translations を取得します。# Get transcript
curl -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/transcript" \
-H "Authorization: Bearer $TIRO_API_KEY"
# Get all translations
curl -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/translations" \
-H "Authorization: Bearer $TIRO_API_KEY"
# Get specific translation (e.g., English)
curl -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/translations/en_US" \
-H "Authorization: Bearer $TIRO_API_KEY"
# Get transcript paragraph summary
curl -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/transcript/paragraph-summary" \
-H "Authorization: Bearer $TIRO_API_KEY"
# Get translation paragraph summary
curl -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/translations/en_US/paragraph-summary" \
-H "Authorization: Bearer $TIRO_API_KEY"
async function getJobResults(jobId) {
// Get transcript
const transcriptResponse = await fetch(
`https://api.tiro.ooo/v1/external/voice-file/jobs/${jobId}/transcript`,
{
headers: {
'Authorization': `Bearer ${process.env.TIRO_API_KEY}`
}
}
);
if (!transcriptResponse.ok) {
throw new Error(`Failed to get transcript: ${transcriptResponse.status}`);
}
const transcript = await transcriptResponse.json();
console.log('Transcript:', transcript.text);
// Get available translations
const translationsResponse = await fetch(
`https://api.tiro.ooo/v1/external/voice-file/jobs/${jobId}/translations`,
{
headers: {
'Authorization': `Bearer ${process.env.TIRO_API_KEY}`
}
}
);
if (!translationsResponse.ok) {
throw new Error(`Failed to get translations: ${translationsResponse.status}`);
}
const translations = await translationsResponse.json();
// Get each translation
const translationResults = {};
for (const translation of translations) {
translationResults[translation.locale] = translation.text;
}
// Get paragraph summaries
const transcriptSummaryResponse = await fetch(
`https://api.tiro.ooo/v1/external/voice-file/jobs/${jobId}/transcript/paragraph-summary`,
{
headers: {
'Authorization': `Bearer ${process.env.TIRO_API_KEY}`
}
}
);
let transcriptSummary = null;
if (transcriptSummaryResponse.ok) {
transcriptSummary = await transcriptSummaryResponse.json();
}
// Get translation summaries
const translationSummaries = {};
for (const translation of translations) {
const summaryResponse = await fetch(
`https://api.tiro.ooo/v1/external/voice-file/jobs/${jobId}/translations/${translation.locale}/paragraph-summary`,
{
headers: {
'Authorization': `Bearer ${process.env.TIRO_API_KEY}`
}
}
);
if (summaryResponse.ok) {
const summaryData = await summaryResponse.json();
translationSummaries[translation.locale] = summaryData.summary;
}
}
return {
transcript: transcript.text,
translations: translationResults,
transcriptSummary: transcriptSummary?.summary,
translationSummaries
};
}
def get_job_results(job_id):
# Get transcript
transcript_response = requests.get(
f'https://api.tiro.ooo/v1/external/voice-file/jobs/{job_id}/transcript',
headers={'Authorization': f'Bearer {os.getenv("TIRO_API_KEY")}'}
)
transcript_response.raise_for_status()
transcript = transcript_response.json()
print(f'Transcript: {transcript["text"]}')
# Get available translations
translations_response = requests.get(
f'https://api.tiro.ooo/v1/external/voice-file/jobs/{job_id}/translations',
headers={'Authorization': f'Bearer {os.getenv("TIRO_API_KEY")}'}
)
translations_response.raise_for_status()
translations = translations_response.json()
# Get each translation
translation_results = {}
for translation in translations:
translation_results[translation['locale']] = translation['text']
# Get paragraph summaries
transcript_summary = None
try:
transcript_summary_response = requests.get(
f'https://api.tiro.ooo/v1/external/voice-file/jobs/{job_id}/transcript/paragraph-summary',
headers={'Authorization': f'Bearer {os.getenv("TIRO_API_KEY")}'}
)
if transcript_summary_response.ok:
transcript_summary_data = transcript_summary_response.json()
transcript_summary = transcript_summary_data['summary']
except:
pass
# Get translation summaries
translation_summaries = {}
for translation in translations:
try:
summary_response = requests.get(
f'https://api.tiro.ooo/v1/external/voice-file/jobs/{job_id}/translations/{translation["locale"]}/paragraph-summary',
headers={'Authorization': f'Bearer {os.getenv("TIRO_API_KEY")}'}
)
if summary_response.ok:
summary_data = summary_response.json()
translation_summaries[translation['locale']] = summary_data['summary']
except:
pass
return {
'transcript': transcript['text'],
'translations': translation_results,
'transcript_summary': transcript_summary,
'translation_summaries': translation_summaries
}
type Transcript struct {
JobId string `json:"jobId"`
Locales []string `json:"locales"`
Text string `json:"text"`
}
type Translation struct {
JobId string `json:"jobId"`
Locale string `json:"locale"`
Text string `json:"text"`
}
type JobResults struct {
Transcript string
Translations map[string]string
}
func getJobResults(jobId string) (*JobResults, error) {
client := &http.Client{}
apiKey := os.Getenv("TIRO_API_KEY")
// Get transcript
transcriptUrl := fmt.Sprintf("https://api.tiro.ooo/v1/external/voice-file/jobs/%s/transcript", jobId)
req, _ := http.NewRequest("GET", transcriptUrl, nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var transcript Transcript
json.NewDecoder(resp.Body).Decode(&transcript)
fmt.Printf("Transcript: %s\n", transcript.Text)
// Get translations
translationsUrl := fmt.Sprintf("https://api.tiro.ooo/v1/external/voice-file/jobs/%s/translations", jobId)
req, _ = http.NewRequest("GET", translationsUrl, nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
resp, err = client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var translations []Translation
json.NewDecoder(resp.Body).Decode(&translations)
translationResults := make(map[string]string)
for _, t := range translations {
translationResults[t.Locale] = t.Text
}
return &JobResults{
Transcript: transcript.Text,
Translations: translationResults,
}, nil
}
data class Transcript(
val jobId: String,
val locales: List<String>,
val text: String
)
data class Translation(
val jobId: String,
val locale: String,
val text: String
)
data class JobResults(
val transcript: String,
val translations: Map<String, String>
)
fun getJobResults(jobId: String): JobResults {
val headers = HttpHeaders().apply {
set("Authorization", "Bearer $apiKey")
}
val entity = HttpEntity<String>(headers)
// Get transcript
val transcriptResponse = restTemplate.exchange(
"https://api.tiro.ooo/v1/external/voice-file/jobs/$jobId/transcript",
HttpMethod.GET,
entity,
Transcript::class.java
)
val transcript = transcriptResponse.body!!
println("Transcript: ${transcript.text}")
// Get translations
val translationsResponse = restTemplate.exchange(
"https://api.tiro.ooo/v1/external/voice-file/jobs/$jobId/translations",
HttpMethod.GET,
entity,
object : ParameterizedTypeReference<List<Translation>>() {}
)
val translations = translationsResponse.body!!
val translationResults = translations.associate { it.locale to it.text }
return JobResults(
transcript = transcript.text,
translations = translationResults
)
}
完全な例
音声ファイルを最初から最後まで処理する完全な例を以下に示します。#!/bin/bash
# Complete Voice File Processing Script
# Set your API key
export TIRO_API_KEY="your_api_key_here"
AUDIO_FILE="/path/to/your/audio.mp3"
# Step 1: Create job
echo "Creating voice file job..."
RESPONSE=$(curl -s -X POST https://api.tiro.ooo/v1/external/voice-file/jobs \
-H "Authorization: Bearer $TIRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transcriptLocaleHints": ["ko_KR"],
"translationLocales": ["en_US"]
}')
JOB_ID=$(echo $RESPONSE | jq -r '.id')
UPLOAD_URI=$(echo $RESPONSE | jq -r '.uploadUri')
echo "Job created: $JOB_ID"
# Step 2: Upload file
echo "Uploading audio file..."
curl -s -X PUT "$UPLOAD_URI" \
-H "Content-Type: audio/mpeg" \
--data-binary @"$AUDIO_FILE"
echo "File uploaded successfully"
# Step 3: Notify upload complete
echo "Notifying upload completion..."
curl -s -X PUT "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/upload-complete" \
-H "Authorization: Bearer $TIRO_API_KEY" \
-H "Content-Type: application/json"
# Step 4: Poll for completion
echo "Waiting for processing to complete..."
while true; do
STATUS=$(curl -s -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID" \
-H "Authorization: Bearer $TIRO_API_KEY" | jq -r '.status')
echo "Status: $STATUS"
if [ "$STATUS" = "COMPLETED" ]; then
echo "Job completed successfully!"
break
elif [ "$STATUS" = "FAILED" ]; then
echo "Job failed!"
exit 1
fi
sleep 5
done
# Step 5: Get results
echo "Retrieving results..."
echo "=== Transcript ==="
curl -s -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/transcript" \
-H "Authorization: Bearer $TIRO_API_KEY" | jq '.text'
echo "=== Translation (en_US) ==="
curl -s -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/translations/en_US" \
-H "Authorization: Bearer $TIRO_API_KEY" | jq '.text'
echo "=== Paragraph Summary ==="
curl -s -X GET "https://api.tiro.ooo/v1/external/voice-file/jobs/$JOB_ID/transcript/paragraph-summary" \
-H "Authorization: Bearer $TIRO_API_KEY" | jq '.summary'
echo "Processing completed!"
async function processAudioFile(audioFile) {
try {
// Step 1: Create job
console.log('Creating voice file job...');
const job = await createVoiceFileJob();
// Step 2: Upload file
console.log('Uploading audio file...');
await uploadAudioFile(job.uploadUri, audioFile);
// Step 3: Notify upload complete
console.log('Notifying upload completion...');
await notifyUploadComplete(job.id);
// Step 4: Wait for processing
console.log('Waiting for processing to complete...');
const completedJob = await waitForJobCompletion(job.id);
// Step 5: Get results
console.log('Retrieving results...');
const results = await getJobResults(job.id);
console.log('Processing completed!');
console.log('Transcript:', results.transcript);
console.log('Translations:', results.translations);
console.log('Transcript Summary:', results.transcriptSummary);
console.log('Translation Summaries:', results.translationSummaries);
return results;
} catch (error) {
console.error('Error processing audio file:', error);
throw error;
}
}
// Usage
processAudioFile(myAudioFile)
.then(results => {
// Handle successful processing
displayResults(results);
})
.catch(error => {
// Handle error
showError(error.message);
});
async def process_audio_file(file_path):
try:
# Step 1: Create job
print('Creating voice file job...')
job = create_voice_file_job()
# Step 2: Upload file
print('Uploading audio file...')
upload_audio_file(job['uploadUri'], file_path)
# Step 3: Notify upload complete
print('Notifying upload completion...')
notify_upload_complete(job['id'])
# Step 4: Wait for processing
print('Waiting for processing to complete...')
completed_job = await wait_for_job_completion(job['id'])
# Step 5: Get results
print('Retrieving results...')
results = get_job_results(job['id'])
print('Processing completed!')
print(f'Transcript: {results["transcript"]}')
print(f'Translations: {results["translations"]}')
print(f'Transcript Summary: {results["transcript_summary"]}')
print(f'Translation Summaries: {results["translation_summaries"]}')
return results
except Exception as e:
print(f'Error processing audio file: {e}')
raise
# Usage
if __name__ == '__main__':
asyncio.run(process_audio_file('path/to/audio.mp3'))
Step 6: Paragraph Summary を理解する
Paragraph Summary 機能は、インテリジェントなコンテンツ要約を提供し、音声ファイルの要点をすばやく把握するのに役立ちます。Paragraph Summary とは
Paragraph summary は、transcript や translation のコンテンツを自動生成した概要で、論理的なセクションに分割されています。各要約は次のものを提供します。- 簡潔な概要: 各段落の要点を markdown 形式で
- 言語別: transcript と translations の両方について生成される要約
- 構造化されたコンテンツ: 解析が容易で、アプリケーションへの統合も簡単
レスポンスの例
paragraph summary を取得すると、次のようなレスポンスを受け取ります。{
"jobId": "job-123",
"locale": "ko_KR",
"summary": [
{
"type": "markdown",
"content": "### 회의는 프로젝트 진행 상황을 점검하고, 다음 단계에 대한 계획을 수립하는 데 중점을 두었습니다."
},
{
"type": "markdown",
"content": "### 팀원들은 각자의 담당 업무에 대한 현재 상태를 보고하고, 발생한 이슈들에 대해 논의했습니다."
}
]
}
要約はいつ利用できますか
- Transcript と Translation の要約: ジョブ完了後(
COMPLETEDステータス)に利用できますCOMPLETEDは、transcript、translation(リクエストした場合)、およびその両方の paragraph summary を含む、すべての処理が完了したことを意味します
- 処理時間: 要約は非同期で生成され、通常はメイン処理の完了後 30〜60 秒以内に生成されます
ポーリングのベストプラクティス
指数バックオフ戦略
- 開始時の遅延: 2 秒(処理の開始には時間がかかります)
- 増加係数: 1.2 倍(緩やかな増加)
- 最大の遅延: 30 秒(サーバーへの過負荷を避けます)
- 最大試行回数: 想定される処理時間に基づきます
ジョブの状態遷移
処理時間のガイドライン
| ファイルの長さ | 想定される処理時間 | 推奨ポーリング間隔 |
|---|---|---|
| 5 分未満 | 30〜60 秒 | 開始: 2s、最大: 10s |
| 5〜30 分 | 1〜5 分 | 開始: 5s、最大: 20s |
| 30〜120 分 | 5〜15 分 | 開始: 10s、最大: 30s |
| 2 時間超 | 15〜30 分 | 開始: 30s、最大: 60s |
よくある問題と解決策
アップロードの失敗
- 問題: 413 Payload Too Large
- 解決策: ファイルサイズ(最大 500MB)を確認し、必要に応じて圧縮してください
処理の失敗
- 問題: 音声品質が低い
- 解決策: サンプルレートが 8kHz 以上であること、背景ノイズが最小限であることを確認してください
タイムアウトの問題
- 問題: 想定時間内にジョブが完了しない
- 解決策: 長いファイルではタイムアウトを長くし、ジョブの状態にエラーがないか確認してください