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
| Field | Required | Type | Description |
|---|---|---|---|
audioUploadId | Yes | String | ID of the uploaded media file. See File Upload Guide |
textUploadId | Yes | String | ID of the uploaded text file. See File Upload Guide |
language | Yes | String | Language of the uploaded text file. One of: 'en', 'ko', 'ja', 'zh-cn' |
Response Body
| Field | Type | Description |
|---|---|---|
jobId | String | ID 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
| Parameter | Required | Description |
|---|---|---|
jobId | Yes | ID of the job to retrieve. See Create a Job |
Response Body
| Field | Type | Description |
|---|---|---|
jobId | String | ID of the retrieved job. |
status | String | Current 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 |
expireAt | String | Expiration time of the job result data. Result files cannot be downloaded after this time. |
downloadUrl | Object | URL information for job result data. Contains download URLs for result files. |
└lyrics | Object | Lyrics download information |
└ file | String | synced lyrics csv file download URL |
└reports | Object | Report download information |
└ file | String | synced 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}
| Field | Description |
|---|---|
timestamp | Start time of the lyric in seconds (decimal format) |
lyric_text | The synchronized lyric text |
confidence_score | Confidence 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
| Field | Type | Description |
|---|---|---|
resultType | String | Type of result format |
EC | Object | Error check status flags |
text | Array | Synchronized lyric lines with timing |
summaryReport | Object | Summary statistics of the synchronization |
syncReport | Object | Detailed synchronization metrics |
Summary Report Fields
| Field | Type | Description |
|---|---|---|
InAudioFileID | String | Input audio file identifier. |
InLyricsFileID | String | Input lyrics file identifier. |
OutLyricsFileID | String | Output lyrics file identifier. |
playTime | Number | Total audio duration in seconds |
NOL_in | Number | Total number of lines in the input text. Range: 0 to unlimited. |
NOL_out | Number | Total number of lines in the output text. Range: 0 to unlimited. |
EC Fields
| Field | Description |
|---|---|
EC0 | Indicates 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. |
EC2 | A word from the exception lexicon was detected. The line containing this word might be deleted. |
EC4 | The text contains too many newlines, or a single line is too long. The current limit is 60 characters for Korean/English text (including spaces). |
EC8 | The number of lines in the text is greater than the threshold of 180. This does not necessarily stop the process. |
EC9 | The 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_lyricsare generated.
Text Fields
| Field | Type | Description |
|---|---|---|
no | Number | Line number |
time | String | Start time in seconds |
text | String | Lyric text |
confidence | String | Confidence score (0-100) |
Sync Report Fields
| Field | Type | Description | Range |
|---|---|---|---|
mCL | Number | Minimum Confidence Level (CL*) The minimum confidence score among all lines, indicating the line with the highest expected error. | 0 to 100 |
aCL | Number | Average CL The average confidence score for all lines in the song. | 0 to 100 |
sdCL | Number | Standard Deviation of CL Measures the variability of confidence scores to predict the extent of timing errors. | 0.1 to 50 |
Ncut | Number | Number of Cut-off Lines The number of lines where the confidence score is below the LOK** threshold. | 0 to number of lines (NOL) |
Lf | Array | Coordinates of Cut-off Lines Shows the location of lines that missed the confidence cutoff. | |
LOK | Number | Lines processed successfully. | |
DL | Array | Deleted Lines Information about lines deleted for consisting only of numbers or being in a non-dominant language. | |
NL | Array | Numbered Lines Information about lines that start with a number. | |
ET | Array | Timestamp Errors Returns the line and time of the previous line if a timestamp is the same or earlier than the previous one. | |
EL | Array | Deleted 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()