Skip to main content

Job Management

This document explains how to create, monitor, and manage results for lyrics recognition jobs using the Capybara1 model. Jobs are processed asynchronously after creation, and you can check the status to confirm results.

Create a Job

POST
https://restapi.gaudiolab.io/developers/api/v1/gts_lyrics_line_v1/jobs

Create a job. When the job is successfully created, the response includes the job ID.

Request Body

FieldRequiredTypeDescription
audioUploadIdYesStringID of the uploaded media file. See File Upload Guide
textUploadIdYesStringID of the uploaded text file. See File Upload Guide
languageYesStringLanguage of the uploaded text file. One of: 'en', 'ko', 'ja', 'zh-cn'

Response Body

FieldTypeDescription
jobIdStringID of the created job

Example

Request

curl -X POST 'https://restapi.gaudiolab.io/developers/api/v1/gts_lyrics_line_v1/jobs' \
-H 'Content-Type: application/json' \
-H 'x-ga-apikey: {API_KEY}' \
-d '{
"audioUploadId": "{UPLOAD_ID}",
"textUploadId": "{UPLOAD_ID}",
"language": "en"
}'

Response

{
'resultCode': 1000,
'resultData': {
'jobId': 'aaffd346-74fc-11f0-8de9-0242ac120002'
}
}

Get a Job

GET
https://restapi.gaudiolab.io/developers/api/v1/gts_lyrics_line_v1/jobs/{jobId}

Retrieves the current status of the created job.

Path Parameters

ParameterRequiredDescription
jobIdYesID of the job to retrieve. See Create a Job

Response Body

FieldTypeDescription
jobIdStringID of the retrieved job.
statusStringCurrent status of the job. One of the following values:
- success: Job completed successfully
- running: The job is currently being processed. Processing time varies depending on the product, processing options, and input file.
- waiting: The job has passed validation and entered the queue. Processing time may be longer if there are many jobs waiting in the queue.
- failed: Job failed
expireAtStringExpiration time of the job result data.
Result files cannot be downloaded after this time.
downloadUrlObjectURL information for job result data.
Contains download URLs for result files.
lyricsObjectLyrics download information
    └ fileStringsynced lyrics csv file download URL
reportsObjectReport download information
    └ fileStringsynced lyrics and report file download URL

Example

Request

curl -X GET "https://restapi.gaudiolab.io/developers/api/v1/gts_lyrics_line_v1/jobs/{JOB_ID}" \
-H 'x-ga-apikey: {API_KEY}'

Response

{
'resultCode': 1000,
'resultData': {
'jobId': 'aaffd346-74fc-11f0-8de9-0242ac120002',
'status': 'success',
'errorMessage': null,
'expireAt': '2025-09-01T12:00:00Z',
'downloadUrl': {
'lyrics': {
'file': 'https://cdn_url/input-filename_lyrics.csv'
},
'reports': {
'file': 'https://cdn_url/input-filename_lyrics_and_report.json'
}
}
}
}

Result File Formats

Lyrics CSV File Format

The lyrics CSV file contains synchronized lyrics with timing information. Each line represents a synchronized lyric with the following format:

{timestamp},{lyric_text},{confidence_score}
FieldDescription
timestampStart time of the lyric in seconds (decimal format)
lyric_textThe synchronized lyric text
confidence_scoreConfidence score of the synchronization (0-100)

Example

0.9,I will send fireflies of that night,100
13.4,Close to your window tonight,95
25.9,These words mean I love you,95
44.9,I remember our first kiss,95
57.1,So close your eyes anytime,95

Reports JSON File Format

The reports JSON file contains detailed analysis and synchronization results with the following structure:

{
"resultCode": 1000,
"resultData": {
"resultType": "json",
"EC": {
"EC0": true,
"EC4": true,
"EC8": true
},
"text": [
{
"no": 1,
"time": "0.9",
"text": "I will send fireflies of that night",
"confidence": "100"
}
],
"summaryReport": {
"InAudioFileID": "audio_file",
"InLyricsFileID": "audio_file",
"OutLyricsFileID": "audio_file",
"playTime": 253.1495238095238,
"NOL_in": 27,
"NOL_out": 27
},
"syncReport": {
"mCL": 63.54185104370117,
"aCL": 94.55740356445312,
"sdCL": 6.307178497314453,
"Ncut": 0,
"Lf": [],
"LOK": 55,
"DL": [],
"NL": [],
"ET": [],
"EL": []
}
}
}

Result Data Fields

FieldTypeDescription
resultTypeStringType of result format
ECObjectError check status flags
textArraySynchronized lyric lines with timing
summaryReportObjectSummary statistics of the synchronization
syncReportObjectDetailed synchronization metrics

Summary Report Fields

FieldTypeDescription
InAudioFileIDStringInput audio file identifier.
InLyricsFileIDStringInput lyrics file identifier.
OutLyricsFileIDStringOutput lyrics file identifier.
playTimeNumberTotal audio duration in seconds
NOL_inNumberTotal number of lines in the input text.
Range: 0 to unlimited.
NOL_outNumberTotal number of lines in the output text.
Range: 0 to unlimited.

EC Fields

FieldDescription
EC0Indicates misidentification by the AI due to a low confidence level.
This can be caused by irrelevant phrases in the text or missing text lines when audio is present.
EC2A word from the exception lexicon was detected.
The line containing this word might be deleted.
EC4The text contains too many newlines, or a single line is too long.
The current limit is 60 characters for Korean/English text (including spaces).
EC8The number of lines in the text is greater than the threshold of 180.
This does not necessarily stop the process.
EC9The number of newlines in the input text is different from the number in the output text.

Note: ECx values are independent, meaning multiple exceptions can occur. The occurrence of an ECx exception is independent of whether Sync_lyrics are generated.

Text Fields

FieldTypeDescription
noNumberLine number
timeStringStart time in seconds
textStringLyric text
confidenceStringConfidence score (0-100)

Sync Report Fields

FieldTypeDescriptionRange
mCLNumberMinimum Confidence Level (CL*)
The minimum confidence score among all lines, indicating the line with the highest expected error.
0 to 100
aCLNumberAverage CL
The average confidence score for all lines in the song.
0 to 100
sdCLNumberStandard Deviation of CL
Measures the variability of confidence scores to predict the extent of timing errors.
0.1 to 50
NcutNumberNumber of Cut-off Lines
The number of lines where the confidence score is below the LOK** threshold.
0 to number of lines (NOL)
LfArrayCoordinates of Cut-off Lines
Shows the location of lines that missed the confidence cutoff.
LOKNumberLines processed successfully.
DLArrayDeleted Lines
Information about lines deleted for consisting only of numbers or being in a non-dominant language.
NLArrayNumbered Lines
Information about lines that start with a number.
ETArrayTimestamp Errors
Returns the line and time of the previous line if a timestamp is the same or earlier than the previous one.
ELArrayDeleted Exception Lines
Information about lines deleted due to exception settings (e.g., containing keywords like "[Chorus]").

* CL: Confidence Level, a score for how close the AI auto sync estimate for each line is to the correct answer, expressed as a confidence index. Ranges from 0 to 100 (100: High Confidence, 0: Low Confidence).
** LOK: Line OK Threshold.
*** ECC: (Exception Case Corpus): Stores words (phrases) to be used for ECC detection. GTS's pre-processor utilizes this to detect exceptions and update EC2 values.

Sample Code

import os
import requests
import time
import json

SLEEP_INTERVAL = 10
SERVER = "https://restapi.gaudiolab.io/developers/api"

API_CREATE_UPLOAD = "/v1/files/upload-multipart/create"
API_COMPLETE_UPLOAD = "/v1/files/upload-multipart/complete"
API_CREATE_JOB = "/v1/gts_lyrics_line_v1/jobs"
API_JOB_STATUS = "/v1/gts_lyrics_line_v1/jobs/{}"

API_KEY = 'api_key_here'
INPUT_AUDIO_FILE = './test_audio_file.wav'
INPUT_TEXT_FILE = './test_text_file.txt'


def send_request(method, api, payload):
headers = {
'x-ga-apikey': API_KEY
}
if method == "POST":
return requests.post(SERVER + api, headers=headers, json=payload)
elif method == "GET":
return requests.get(SERVER + api, headers=headers)
else:
raise ValueError(f"Invalid method: {method}")

def read_in_chunks(file_object, chunk_size):
while True:
data = file_object.read(chunk_size)
if not data:
break
yield data

def main():
# Upload audio file
print("\n[STEP] Create Upload URL")
print("-" * 40)
file_size = os.path.getsize(INPUT_AUDIO_FILE)
payload = {
'fileName': os.path.basename(INPUT_AUDIO_FILE),
'fileSize': file_size
}
audio_upload_resp = send_request("POST", API_CREATE_UPLOAD, payload)
if audio_upload_resp.status_code != 200:
print(f" ERROR) Request failed: status_code {audio_upload_resp.status_code}, {audio_upload_resp.text}")
return

result_code = audio_upload_resp.json().get('resultCode')
if result_code != 1000:
result_message = audio_upload_resp.json().get('resultMessage')
print(f" ERROR) Create upload url failed: resultCode {result_code}, resultMessage {result_message}")
return

result_data = audio_upload_resp.json()['resultData']
chunk_size = result_data['chunkSize']
audio_upload_id = result_data['uploadId']
audio_upload_urls = result_data['preSignedUrl']
print(f" SUCCESS) audio_upload_id: {audio_upload_id}")
print(f" SUCCESS) audio_upload_urls: {audio_upload_urls}")

print("\n[STEP] Upload File")
print("-" * 40)
parts = []
with open(INPUT_AUDIO_FILE, 'rb') as f:
print(f" INFO) Uploading {len(audio_upload_urls)} chunk(s)")
for i, chunk in enumerate(read_in_chunks(f, chunk_size)):
resp = requests.put(audio_upload_urls[i], data=chunk)
etag = resp.headers.get('ETag', '').replace('"', '')
parts.append({'awsETag': etag, 'partNumber': i + 1})
print(f" PROGRESS) chunk {i + 1}/{len(audio_upload_urls)} uploaded")


print("\n[STEP] Complete Upload")
print("-" * 40)
complete_payload = {'uploadId': audio_upload_id, 'parts': parts}
complete_resp = send_request("POST", API_COMPLETE_UPLOAD, complete_payload)
if complete_resp.status_code != 200:
print(f" ERROR) Request failed: status_code {complete_resp.status_code}, {complete_resp.text}")
return

result_code = complete_resp.json().get('resultCode')
if result_code != 1000:
result_message = complete_resp.json().get('resultMessage')
print(f" ERROR) Complete upload failed: resultCode {result_code}, resultMessage {result_message}")
return
print(" SUCCESS) Upload completed successfully")


# Upload text file
print("\n[STEP] Create Upload URL")
print("-" * 40)
file_size = os.path.getsize(INPUT_TEXT_FILE)
payload = {
'fileName': os.path.basename(INPUT_TEXT_FILE),
'fileSize': file_size
}
text_upload_resp = send_request("POST", API_CREATE_UPLOAD, payload)
if text_upload_resp.status_code != 200:
print(f" ERROR) Request failed: status_code {text_upload_resp.status_code}, {text_upload_resp.text}")
return

result_code = text_upload_resp.json().get('resultCode')
if result_code != 1000:
result_message = text_upload_resp.json().get('resultMessage')
print(f" ERROR) Create upload url failed: resultCode {result_code}, resultMessage {result_message}")
return

result_data = text_upload_resp.json()['resultData']
chunk_size = result_data['chunkSize']
text_upload_id = result_data['uploadId']
text_upload_urls = result_data['preSignedUrl']
print(f" SUCCESS) text_upload_id: {text_upload_id}")
print(f" SUCCESS) text_upload_urls: {text_upload_urls}")

print("\n[STEP] Upload File")
print("-" * 40)
parts = []
with open(INPUT_TEXT_FILE, 'rb') as f:
print(f" INFO) Uploading {len(text_upload_urls)} chunk(s)")
for i, chunk in enumerate(read_in_chunks(f, chunk_size)):
resp = requests.put(text_upload_urls[i], data=chunk)
etag = resp.headers.get('ETag', '').replace('"', '')
parts.append({'awsETag': etag, 'partNumber': i + 1})
print(f" PROGRESS) chunk {i + 1}/{len(text_upload_urls)} uploaded")


print("\n[STEP] Complete Upload")
print("-" * 40)
complete_payload = {'uploadId': text_upload_id, 'parts': parts}
complete_resp = send_request("POST", API_COMPLETE_UPLOAD, complete_payload)
if complete_resp.status_code != 200:
print(f" ERROR) Request failed: status_code {complete_resp.status_code}, {complete_resp.text}")
return

result_code = complete_resp.json().get('resultCode')
if result_code != 1000:
result_message = complete_resp.json().get('resultMessage')
print(f" ERROR) Complete upload failed: resultCode {result_code}, resultMessage {result_message}")
return
print(" SUCCESS) Upload completed successfully")

# Create job
job_resp = send_request("POST", API_CREATE_JOB, {
"audioUploadId": audio_upload_id,
"textUploadId": text_upload_id,
"language": "en"
})
if job_resp.status_code != 200:
print(f" ERROR) Request failed: status_code {job_resp.status_code}, {job_resp.text}")
return

result_code = job_resp.json().get('resultCode')
if result_code != 1000:
result_message = job_resp.json().get('resultMessage')
print(f" ERROR) Create job failed: resultCode {result_code}, resultMessage {result_message}")
return

result_data = job_resp.json()['resultData']
job_id = result_data['jobId']
print(f" SUCCESS) job_id: {job_id}")

# Check job status
while True:
status_resp = send_request("GET", API_JOB_STATUS.format(job_id), {})
if status_resp.status_code != 200:
print(f" ERROR) Request failed: status_code {status_resp.status_code}, {status_resp.text}")
break

result_code = status_resp.json().get('resultCode')
if result_code != 1000:
result_message = status_resp.json().get('resultMessage')
print(f" ERROR) Status check failed: resultCode {result_code}, resultMessage {result_message}")
break

result_data = status_resp.json()['resultData']
status = result_data['status']

if status == 'success':
download_url = result_data.get('downloadUrl')
print(" SUCCESS) Job completed!")
print(f" Download URLs: {download_url}")
break

print(f" PROGRESS) Status: {status}")
print(f" PROGRESS) Retrying in {SLEEP_INTERVAL} seconds...")
time.sleep(SLEEP_INTERVAL)

if __name__ == "__main__":
main()