apick-api 1.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.0 - 2026-09-03
4
+
5
+ - `maskResidenceCard()`의 개인정보 마스킹 대상을 외국인등록증·영주증·외국국적동포 국내거소신고증으로 확대했습니다.
6
+ - 요청·응답 형식과 `document_type: residence_card` 계약은 유지합니다.
7
+ - 진위확인 API의 지원 범위는 기존 외국인등록증으로 유지됩니다.
8
+
9
+ ## 2.1.0 - 2026-09-01
10
+
11
+ - `downloadTtsSubtitles(jobId)`로 발화 타이밍 ASS 자막을 별도 1회 다운로드할 수 있습니다.
12
+ - TTS 상태 타입에 `subtitles_available`을 추가했습니다.
13
+ - 허용 IP 공란, 단일 IPv4·CIDR 등록, 즉시 반영 규칙을 한영 문서에 추가했습니다.
14
+
15
+ ## 2.0.1 - 2026-08-31
16
+
17
+ - TTS 작업 취소 범위를 `waiting`과 `processing` 상태로 확장했습니다.
18
+ - 실행 중 취소도 접수 시 과금된 금액을 환불하지 않는 계약을 한영 문서에 반영했습니다.
19
+ - 17개 TTS `voice_id`의 사용자 표시 이름을 공개 문서에 추가했습니다.
20
+
21
+ ## 2.0.0 - 2026-08-30
22
+
23
+ - 종료된 동기 `textToSpeech()` 계약을 제거했습니다.
24
+ - `createTtsJob()`, `getTtsJob()`, `cancelTtsJob()`, `downloadTtsResult()`로 비동기 한국어 TTS Jobs 계약을 제공합니다.
25
+ - 지원 목소리, 최대 800자, 상태 조회, 대기 중 취소, 결과 MP3(`audio/mpeg`) 1회 다운로드를 문서화했습니다.
26
+ - 정식 게시 전 지원 목소리를 17개 중립 내레이션 음성으로 확장하고 `TTS_VOICE_IDS`를 추가했습니다.
27
+
3
28
  ## 1.1.1 - 2026-08-30
4
29
 
5
30
  - 공개된 25개 API 계약에 종료 대상 이미지 생성 기능이 포함되지 않았음을 재검증했습니다.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  # APICK API for Node.js
4
4
 
5
- **에이픽 주요 API 20개를 API 키 하나로 간편하게 호출하는 공식 Node.js SDK**
5
+ **에이픽 주요 API 25개를 API 키 하나로 간편하게 호출하는 공식 Node.js SDK**
6
6
 
7
- **Official zero-dependency Node.js SDK for 20 popular APICK Korean data and AI APIs**
7
+ **Official zero-dependency Node.js SDK for 25 popular APICK Korean data and AI APIs**
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/apick-api?color=%230a7cff&label=npm%20apick-api)](https://www.npmjs.com/package/apick-api)
10
10
  [![Node.js](https://img.shields.io/badge/Node.js-18%2B-339933)](https://nodejs.org/)
@@ -45,6 +45,9 @@ console.log(result.data);
45
45
  인증키는 [apick.app](https://apick.app) 가입 후 마이페이지에서 발급할 수 있습니다.
46
46
  Get an API key from your account page after signing up at [apick.app](https://apick.app).
47
47
 
48
+ 마이페이지의 허용 IP가 공란이면 제한 없이 호출할 수 있습니다. 제한하려면 APICK에 도착하는 공인 IPv4를 단일 주소 또는 CIDR(`/32` 등)로 등록하세요. 저장 즉시 반영되며 별도 동기화는 필요하지 않습니다.
49
+ Leave the allowed-IP list blank for unrestricted access. To restrict access, register the public IPv4 address seen by APICK as an exact address or CIDR such as `/32`. Changes apply immediately with no separate synchronization.
50
+
48
51
  ## 제공 서비스 / Included services
49
52
 
50
53
  | Method | APICK service | Result |
@@ -64,7 +67,11 @@ Get an API key from your account page after signing up at [apick.app](https://ap
64
67
  | `googleSearch(keyword, options)` | 웹 검색 / Web search | JSON |
65
68
  | `googleImageSearch(keyword, options)` | 이미지 검색 / Image search | JSON |
66
69
  | `screenshot(url)` | 웹페이지 화면캡처 / Web screenshot | Binary |
67
- | `textToSpeech(text, options)` | 음성 합성 / Text to speech | Binary |
70
+ | `createTtsJob(text, options)` | 한국어 내레이션 작업 접수 / Create TTS job | JSON |
71
+ | `getTtsJob(jobId)` | TTS 작업 상태 / TTS job status | JSON |
72
+ | `cancelTtsJob(jobId)` | 대기·생성 중 TTS 작업 취소 / Cancel waiting or processing TTS job | JSON |
73
+ | `downloadTtsResult(jobId)` | TTS 결과 1회 다운로드 / One-time TTS result | MP3 |
74
+ | `downloadTtsSubtitles(jobId)` | TTS 자막 1회 다운로드 / One-time TTS subtitles | ASS |
68
75
  | `htmlToPdf(html, options)` | HTML→PDF | Binary |
69
76
  | `jsonToExcel(data, options)` | JSON→Excel | Binary |
70
77
  | `summarize(text)` | 텍스트 요약 / Text summarization | JSON |
@@ -127,6 +134,40 @@ await excel.save('./scores.xlsx');
127
134
 
128
135
  `ApickBinaryResult` provides `bytes`, `size`, `filename`, `contentType`, `meta`, `toArrayBuffer()`, `toBlob()`, and `save(path)`.
129
136
 
137
+ ## 비동기 TTS Jobs / Asynchronous TTS Jobs
138
+
139
+ 기존 동기 TTS는 종료되었습니다. 한국어 내레이션은 작업을 접수하고 `completed`가 될 때까지 2~5초 간격으로 상태를 확인한 뒤 MP3 결과를 한 번만 내려받습니다.
140
+
141
+ The legacy synchronous TTS API has retired. Create a Korean narration job, poll every 2–5 seconds until it is `completed`, then download the MP3 result once.
142
+
143
+ 17 neutral narration voices are supported: the five original `narrator_m_01`–`narrator_m_05` voices plus `narrator_f_10s_01`–`03`, `narrator_m_20s_01`, `narrator_f_20s_01`–`04`, `narrator_m_30s_01`–`02`, `narrator_m_40s_01`, and `narrator_m_80s_01`. Import `TTS_VOICE_IDS` for the exact list.
144
+
145
+ 표시 이름 / voice labels: `narrator_m_01` 태준, `narrator_m_02` 민석, `narrator_m_03` 도현, `narrator_m_04` 강우, `narrator_m_05` 성훈, `narrator_f_10s_01` 서아, `narrator_f_10s_02` 하린, `narrator_f_10s_03` 예린, `narrator_m_20s_01` 도윤, `narrator_f_20s_01` 지안, `narrator_f_20s_02` 서윤, `narrator_f_20s_03` 소연, `narrator_f_20s_04` 유나, `narrator_m_30s_01` 현우, `narrator_m_30s_02` 준혁, `narrator_m_40s_01` 정우, `narrator_m_80s_01` 영수.
146
+
147
+ ```js
148
+ const created = await apick.createTtsJob('오늘의 이야기를 시작합니다.', {
149
+ voiceId: 'narrator_m_03'
150
+ });
151
+ const jobId = created.data.job_id;
152
+
153
+ let job;
154
+ do {
155
+ await new Promise(resolve => setTimeout(resolve, 3000));
156
+ job = await apick.getTtsJob(jobId);
157
+ } while (job.data.status === 'waiting' || job.data.status === 'processing');
158
+
159
+ if (job.data.status === 'completed') {
160
+ const result = await apick.downloadTtsResult(jobId);
161
+ await result.save(`./${jobId}.mp3`);
162
+ const subtitles = await apick.downloadTtsSubtitles(jobId);
163
+ await subtitles.save(`./${jobId}.ass`);
164
+ }
165
+ ```
166
+
167
+ 접수 성공 시 과금되며 취소해도 환불되지 않습니다. 취소는 `waiting` 또는 `processing` 상태에서 가능하고, MP3와 ASS 자막은 각각 한 번만 내려받을 수 있습니다. 각 다운로드가 시작되면 해당 서버 원본이 즉시 폐기되므로 전송 중단 시에도 다시 받을 수 없습니다.
168
+
169
+ The charge is final when the job is accepted. Cancellation is allowed while `waiting` or `processing`. The MP3 and ASS subtitles can each be downloaded once. Starting either download immediately consumes that server copy, so an interrupted transfer cannot be downloaded again.
170
+
130
171
  ## 신분증 마스킹 / Identity masking
131
172
 
132
173
  ```js
@@ -140,6 +181,9 @@ console.log(passport.data.result.fields);
140
181
  `maskResidenceCard`, `maskPassport`, `maskIdCard`, `maskDriverLicense`는 JSON 결과를 반환합니다. `maskResidentNumber`는 PNG 바이너리를 반환하며 `type`은 `1`, `2`, `3` 중 하나입니다.
141
182
  The four document-specific methods return JSON. `maskResidentNumber` returns PNG bytes and requires `type` 1, 2, or 3.
142
183
 
184
+ `maskResidenceCard`는 외국인등록증·영주증·외국국적동포 국내거소신고증의 앞면 한 장을 지원합니다. 영주증과 외국국적동포 국내거소신고증 지원은 개인정보 마스킹에만 적용되며 외국인등록증 진위확인 범위는 변경되지 않습니다.
185
+ `maskResidenceCard` accepts one front-side image of a residence card, permanent resident card, or overseas Korean resident card. Permanent and overseas Korean card support is limited to PII masking and does not expand the alien registration card authenticity-check scope.
186
+
143
187
  ## 오류 처리 / Error handling
144
188
 
145
189
  ```js
package/docs/guide.en.md CHANGED
@@ -19,6 +19,8 @@ const client = new ApickClient({
19
19
 
20
20
  Pass the API key only to the constructor. The SDK does not keep it in enumerable client properties and never prints it in logs or error messages.
21
21
 
22
+ Leave the allowed-IP list blank for unrestricted access. To restrict access, register the public IPv4 address seen by APICK as an exact address or CIDR such as `/32`. Changes apply immediately with no separate synchronization.
23
+
22
24
  ## Business, validation, and addresses
23
25
 
24
26
  ```js
@@ -76,12 +78,30 @@ await client.ocr(bytes, {
76
78
 
77
79
  ## Generated files
78
80
 
81
+ TTS supports 17 neutral narration `voice_id` values: five original narrators and twelve new voices. Import `TTS_VOICE_IDS` for the exact list.
82
+
83
+ Voice labels: `narrator_m_01` 태준, `narrator_m_02` 민석, `narrator_m_03` 도현, `narrator_m_04` 강우, `narrator_m_05` 성훈, `narrator_f_10s_01` 서아, `narrator_f_10s_02` 하린, `narrator_f_10s_03` 예린, `narrator_m_20s_01` 도윤, `narrator_f_20s_01` 지안, `narrator_f_20s_02` 서윤, `narrator_f_20s_03` 소연, `narrator_f_20s_04` 유나, `narrator_m_30s_01` 현우, `narrator_m_30s_02` 준혁, `narrator_m_40s_01` 정우, `narrator_m_80s_01` 영수.
84
+
79
85
  ```js
80
86
  const screenshot = await client.screenshot('https://example.com');
81
87
  await screenshot.save('./example.jpeg');
82
88
 
83
- const speech = await client.textToSpeech('Hello.', { language: 'en' });
84
- await speech.save('./hello.mp3');
89
+ const created = await client.createTtsJob('오늘의 이야기를 시작합니다.', { voiceId: 'narrator_m_03' });
90
+ const jobId = created.data.job_id;
91
+ let job = await client.getTtsJob(jobId);
92
+ while (job.data.status === 'waiting' || job.data.status === 'processing') {
93
+ await new Promise(resolve => setTimeout(resolve, 3000));
94
+ job = await client.getTtsJob(jobId);
95
+ }
96
+ if (job.data.status === 'completed') {
97
+ const result = await client.downloadTtsResult(jobId);
98
+ await result.save(`./${jobId}.mp3`); // audio/mpeg; downloadable once
99
+ const subtitles = await client.downloadTtsSubtitles(jobId);
100
+ await subtitles.save(`./${jobId}.ass`); // ASS subtitles; separately downloadable once
101
+ }
102
+
103
+ // Cancellation is available while waiting or processing and does not refund the accepted charge.
104
+ // MP3 and ASS downloads each consume their server copy immediately and cannot be repeated.
85
105
 
86
106
  const pdf = await client.htmlToPdf('<h1>Report</h1>', { pagination: true });
87
107
  await pdf.save('./report.pdf');
@@ -131,6 +151,8 @@ console.log(result.data.result.fields);
131
151
 
132
152
  The document-specific methods are `maskResidenceCard`, `maskPassport`, `maskIdCard`, and `maskDriverLicense`. Identity errors are exposed as `IDENTITY_TEXT_UNREADABLE`, `IDENTITY_DOCUMENT_MISMATCH`, or `IDENTITY_PROCESSING_FAILED` through `ApickApiError.serviceCode`.
133
153
 
154
+ `maskResidenceCard` accepts one front-side image of a residence card, permanent resident card, or overseas Korean resident card. Permanent and overseas Korean card support is limited to PII masking and does not expand the alien registration card authenticity-check scope.
155
+
134
156
  ## Errors and retries
135
157
 
136
158
  `ApickApiError` includes public error information: `code`, optional `serviceCode`, `status`, and `message`. The SDK does not retry automatically because a retry could duplicate an API call and its charge. If your application needs retries, decide explicitly after checking the error code and whether the operation is safe to repeat.
package/docs/guide.ko.md CHANGED
@@ -19,6 +19,8 @@ const client = new ApickClient({
19
19
 
20
20
  인증키는 생성자에만 전달하세요. 클라이언트 객체의 열거 가능한 속성에 저장되지 않으며 SDK가 로그로 출력하지 않습니다.
21
21
 
22
+ 마이페이지의 허용 IP가 공란이면 제한 없이 호출할 수 있습니다. 제한하려면 APICK에 도착하는 공인 IPv4를 단일 주소 또는 CIDR(`/32` 등)로 등록하세요. 저장 즉시 반영되며 별도 동기화는 필요하지 않습니다.
23
+
22
24
  ## 조회와 검증
23
25
 
24
26
  ```js
@@ -76,12 +78,30 @@ await client.ocr(bytes, {
76
78
 
77
79
  ## 파일 생성
78
80
 
81
+ TTS는 기존 남성 내레이터 5개와 신규 중립 내레이션 12개를 합쳐 17개 `voice_id`를 지원합니다. 정확한 목록은 `TTS_VOICE_IDS` 상수로 확인할 수 있습니다.
82
+
83
+ 표시 이름: `narrator_m_01` 태준, `narrator_m_02` 민석, `narrator_m_03` 도현, `narrator_m_04` 강우, `narrator_m_05` 성훈, `narrator_f_10s_01` 서아, `narrator_f_10s_02` 하린, `narrator_f_10s_03` 예린, `narrator_m_20s_01` 도윤, `narrator_f_20s_01` 지안, `narrator_f_20s_02` 서윤, `narrator_f_20s_03` 소연, `narrator_f_20s_04` 유나, `narrator_m_30s_01` 현우, `narrator_m_30s_02` 준혁, `narrator_m_40s_01` 정우, `narrator_m_80s_01` 영수.
84
+
79
85
  ```js
80
86
  const screenshot = await client.screenshot('https://example.com');
81
87
  await screenshot.save('./example.jpeg');
82
88
 
83
- const speech = await client.textToSpeech('안녕하세요.', { language: 'ko' });
84
- await speech.save('./hello.mp3');
89
+ const created = await client.createTtsJob('오늘의 이야기를 시작합니다.', { voiceId: 'narrator_m_03' });
90
+ const jobId = created.data.job_id;
91
+ let job = await client.getTtsJob(jobId);
92
+ while (job.data.status === 'waiting' || job.data.status === 'processing') {
93
+ await new Promise(resolve => setTimeout(resolve, 3000));
94
+ job = await client.getTtsJob(jobId);
95
+ }
96
+ if (job.data.status === 'completed') {
97
+ const result = await client.downloadTtsResult(jobId);
98
+ await result.save(`./${jobId}.mp3`); // audio/mpeg, 1회만 다운로드 가능
99
+ const subtitles = await client.downloadTtsSubtitles(jobId);
100
+ await subtitles.save(`./${jobId}.ass`); // ASS 자막, 별도 1회 다운로드
101
+ }
102
+
103
+ // 취소는 waiting 또는 processing 상태에서 가능하며 이미 과금된 금액은 환불되지 않습니다.
104
+ // MP3와 ASS는 각각 다운로드 시작 시 해당 서버 원본이 즉시 폐기되어 재다운로드할 수 없습니다.
85
105
 
86
106
  const pdf = await client.htmlToPdf('<h1>보고서</h1>', { pagination: true });
87
107
  await pdf.save('./report.pdf');
@@ -131,6 +151,8 @@ console.log(result.data.result.fields);
131
151
 
132
152
  문서별 메서드는 `maskResidenceCard`, `maskPassport`, `maskIdCard`, `maskDriverLicense`입니다. 글자 판독 불가, 문서 불일치, 처리 실패는 각각 `IDENTITY_TEXT_UNREADABLE`, `IDENTITY_DOCUMENT_MISMATCH`, `IDENTITY_PROCESSING_FAILED`로 `ApickApiError.serviceCode`에 제공됩니다.
133
153
 
154
+ `maskResidenceCard`는 외국인등록증·영주증·외국국적동포 국내거소신고증의 앞면 한 장을 지원합니다. 영주증과 외국국적동포 국내거소신고증은 개인정보 마스킹만 지원하며 외국인등록증 진위확인 범위에는 포함되지 않습니다.
155
+
134
156
  ## 오류와 재시도
135
157
 
136
158
  `ApickApiError`에는 공개 오류 정보인 `code`, `serviceCode`, `status`, `message`가 포함됩니다. SDK는 중복 호출과 중복 과금을 방지하기 위해 자동 재시도를 하지 않습니다. 재시도가 필요하면 작업의 멱등성과 오류 코드를 확인한 뒤 애플리케이션에서 명시적으로 결정하세요.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apick-api",
3
- "version": "1.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Official zero-dependency Node.js client for 25 APICK APIs. 에이픽 주요 API 25개를 간편하게 호출하는 공식 Node.js SDK.",
5
5
  "type": "module",
6
6
  "main": "./src/index.cjs",
@@ -64,7 +64,7 @@
64
64
  "google-search",
65
65
  "pdf",
66
66
  "excel",
67
- "tts",
67
+ "tts-jobs",
68
68
  "text-summary"
69
69
  ]
70
70
  }
package/src/index.cjs CHANGED
@@ -3,6 +3,14 @@
3
3
  const DEFAULT_BASE_URL = 'https://apick.app';
4
4
  const DEFAULT_TIMEOUT_MS = 30_000;
5
5
  const MAX_OCR_BYTES = 50 * 1024 * 1024;
6
+ const TTS_VOICE_IDS = Object.freeze([
7
+ 'narrator_m_01', 'narrator_m_02', 'narrator_m_03', 'narrator_m_04', 'narrator_m_05',
8
+ 'narrator_f_10s_01', 'narrator_f_10s_02', 'narrator_f_10s_03',
9
+ 'narrator_m_20s_01', 'narrator_f_20s_01', 'narrator_f_20s_02',
10
+ 'narrator_f_20s_03', 'narrator_f_20s_04', 'narrator_m_30s_01',
11
+ 'narrator_m_30s_02', 'narrator_m_40s_01', 'narrator_m_80s_01'
12
+ ]);
13
+ const TTS_VOICE_ID_SET = new Set(TTS_VOICE_IDS);
6
14
 
7
15
  const SERVICE_DEFINITIONS = Object.freeze({
8
16
  businessDetails: { endpoint: '/rest/biz_detail', timeoutMs: 50_000, output: 'json' },
@@ -25,7 +33,7 @@ const SERVICE_DEFINITIONS = Object.freeze({
25
33
  googleSearch: { endpoint: '/rest/google_search', timeoutMs: 35_000, output: 'json' },
26
34
  googleImageSearch: { endpoint: '/rest/google_image_search', timeoutMs: 35_000, output: 'json' },
27
35
  screenshot: { endpoint: '/rest/url_screenshot', timeoutMs: 75_000, output: 'binary', filename: 'screenshot.jpeg' },
28
- textToSpeech: { endpoint: '/rest/tts', timeoutMs: 35_000, output: 'binary', filename: 'speech.mp3' },
36
+ createTtsJob: { endpoint: '/rest/tts/jobs', timeoutMs: 35_000, output: 'json' },
29
37
  htmlToPdf: { endpoint: '/rest/html_to_pdf', timeoutMs: 25_000, output: 'binary', filename: 'document.pdf' },
30
38
  jsonToExcel: { endpoint: '/rest/json_to_excel', timeoutMs: 45_000, output: 'binary', filename: 'data.xlsx' },
31
39
  summarize: { endpoint: '/rest/llm/text_summary', timeoutMs: 75_000, output: 'json' },
@@ -87,6 +95,18 @@ function normalizeUrl(value) {
87
95
  return parsed.toString();
88
96
  }
89
97
 
98
+ function normalizeTtsJobId(value) {
99
+ const jobId = requiredString('jobId', value);
100
+ if (!/^[a-f0-9]{32}$/.test(jobId)) throw new TypeError('jobId must be a 32-character lowercase hexadecimal string.');
101
+ return jobId;
102
+ }
103
+
104
+ function normalizeTtsVoice(value) {
105
+ const voiceId = requiredString('voiceId', value);
106
+ if (!TTS_VOICE_ID_SET.has(voiceId)) throw new RangeError('voiceId must be one of the supported TTS voice IDs.');
107
+ return voiceId;
108
+ }
109
+
90
110
  function numberOrNull(value) {
91
111
  if (value === undefined || value === null || value === '') return null;
92
112
  const number = Number(value);
@@ -252,9 +272,10 @@ class ApickClient {
252
272
  : positiveInteger('timeoutMs', options.timeoutMs);
253
273
  }
254
274
 
255
- async _call(serviceName, payload, formData) {
256
- const definition = SERVICE_DEFINITIONS[serviceName];
257
- if (!definition) throw new TypeError(`Unknown APICK service: ${serviceName}`);
275
+ async _call(serviceName, payload, formData, requestOptions) {
276
+ const baseDefinition = SERVICE_DEFINITIONS[serviceName];
277
+ if (!baseDefinition) throw new TypeError(`Unknown APICK service: ${serviceName}`);
278
+ const definition = Object.assign({}, baseDefinition, requestOptions || {});
258
279
 
259
280
  const controller = new AbortController();
260
281
  const timeoutMs = this.#timeoutMs || definition.timeoutMs || DEFAULT_TIMEOUT_MS;
@@ -263,14 +284,17 @@ class ApickClient {
263
284
  Accept: definition.output === 'binary' ? '*/*' : 'application/json',
264
285
  CL_AUTH_KEY: this.#apiKey
265
286
  };
287
+ const method = definition.method || 'POST';
266
288
  const request = {
267
- method: 'POST',
289
+ method,
268
290
  headers,
269
- body: formData || JSON.stringify(payload || {}),
270
291
  signal: controller.signal,
271
292
  redirect: 'error'
272
293
  };
273
- if (!formData) headers['Content-Type'] = 'application/json';
294
+ if (method !== 'GET') {
295
+ request.body = formData || JSON.stringify(payload || {});
296
+ if (!formData) headers['Content-Type'] = 'application/json';
297
+ }
274
298
 
275
299
  let response;
276
300
  try {
@@ -318,7 +342,7 @@ class ApickClient {
318
342
  throw new ApickApiError(failureMessage, {
319
343
  status: response.status,
320
344
  code: response.status === 401 ? 'APICK_AUTH_ERROR' : 'APICK_API_ERROR',
321
- serviceCode: body && body.data && typeof body.data.error_code === 'string' ? body.data.error_code : undefined
345
+ serviceCode: body && body.data && typeof (body.data.code || body.data.error_code) === 'string' ? (body.data.code || body.data.error_code) : undefined
322
346
  });
323
347
  }
324
348
 
@@ -437,15 +461,41 @@ class ApickClient {
437
461
  return this._call('screenshot', { url: normalizeUrl(url) });
438
462
  }
439
463
 
440
- textToSpeech(text, options) {
464
+ createTtsJob(text, options) {
441
465
  const config = options || {};
442
- const language = config.language || 'ko';
443
- if (!['ko', 'en', 'zh', 'de', 'es', 'fr', 'ja'].includes(language)) {
444
- throw new RangeError('language must be one of: ko, en, zh, de, es, fr, ja.');
445
- }
446
- return this._call('textToSpeech', {
447
- content: requiredString('text', text, 1_000),
448
- language
466
+ return this._call('createTtsJob', {
467
+ voice_id: normalizeTtsVoice(config.voiceId || 'narrator_m_03'),
468
+ text: requiredString('text', text, 800)
469
+ });
470
+ }
471
+
472
+ getTtsJob(jobId) {
473
+ const id = normalizeTtsJobId(jobId);
474
+ return this._call('createTtsJob', null, null, { endpoint: '/rest/tts/jobs/' + id, method: 'GET' });
475
+ }
476
+
477
+ cancelTtsJob(jobId) {
478
+ const id = normalizeTtsJobId(jobId);
479
+ return this._call('createTtsJob', null, null, { endpoint: '/rest/tts/jobs/' + id + '/cancel' });
480
+ }
481
+
482
+ downloadTtsResult(jobId) {
483
+ const id = normalizeTtsJobId(jobId);
484
+ return this._call('createTtsJob', null, null, {
485
+ endpoint: '/rest/tts/jobs/' + id + '/result',
486
+ method: 'GET',
487
+ output: 'binary',
488
+ filename: id + '.mp3'
489
+ });
490
+ }
491
+
492
+ downloadTtsSubtitles(jobId) {
493
+ const id = normalizeTtsJobId(jobId);
494
+ return this._call('createTtsJob', null, null, {
495
+ endpoint: '/rest/tts/jobs/' + id + '/subtitles',
496
+ method: 'GET',
497
+ output: 'binary',
498
+ filename: id + '.ass'
449
499
  });
450
500
  }
451
501
 
@@ -479,5 +529,6 @@ module.exports = {
479
529
  ApickApiError,
480
530
  ApickBinaryResult,
481
531
  SERVICES,
532
+ TTS_VOICE_IDS,
482
533
  DEFAULT_BASE_URL
483
534
  };
package/src/index.d.ts CHANGED
@@ -31,6 +31,24 @@ export interface MaskResidentNumberOptions extends OcrOptions {
31
31
  type: 1 | 2 | 3;
32
32
  }
33
33
 
34
+ export const TTS_VOICE_IDS: readonly [
35
+ 'narrator_m_01', 'narrator_m_02', 'narrator_m_03', 'narrator_m_04', 'narrator_m_05',
36
+ 'narrator_f_10s_01', 'narrator_f_10s_02', 'narrator_f_10s_03',
37
+ 'narrator_m_20s_01', 'narrator_f_20s_01', 'narrator_f_20s_02',
38
+ 'narrator_f_20s_03', 'narrator_f_20s_04', 'narrator_m_30s_01',
39
+ 'narrator_m_30s_02', 'narrator_m_40s_01', 'narrator_m_80s_01'
40
+ ];
41
+ export type TtsVoiceId = typeof TTS_VOICE_IDS[number];
42
+
43
+ export interface TtsJobData {
44
+ job_id: string;
45
+ status: 'waiting' | 'processing' | 'completed' | 'cancelled' | 'failed';
46
+ voice_id?: TtsVoiceId;
47
+ character_count?: number;
48
+ result_available?: boolean;
49
+ subtitles_available?: boolean;
50
+ }
51
+
34
52
  export class ApickApiError extends Error {
35
53
  readonly status: number;
36
54
  readonly code: string;
@@ -84,7 +102,11 @@ export class ApickClient {
84
102
  googleSearch(keyword: string, options?: { page?: number }): Promise<ApickResult>;
85
103
  googleImageSearch(keyword: string, options?: { page?: number }): Promise<ApickResult>;
86
104
  screenshot(url: string): Promise<ApickBinaryResult>;
87
- textToSpeech(text: string, options?: { language?: 'ko' | 'en' | 'zh' | 'de' | 'es' | 'fr' | 'ja' }): Promise<ApickBinaryResult>;
105
+ createTtsJob(text: string, options?: { voiceId?: TtsVoiceId }): Promise<ApickResult<TtsJobData>>;
106
+ getTtsJob(jobId: string): Promise<ApickResult<TtsJobData>>;
107
+ cancelTtsJob(jobId: string): Promise<ApickResult<TtsJobData>>;
108
+ downloadTtsResult(jobId: string): Promise<ApickBinaryResult>;
109
+ downloadTtsSubtitles(jobId: string): Promise<ApickBinaryResult>;
88
110
  htmlToPdf(html: string, options?: { pagination?: boolean }): Promise<ApickBinaryResult>;
89
111
  jsonToExcel(data: unknown[], options?: { sheetName?: string }): Promise<ApickBinaryResult>;
90
112
  summarize(text: string): Promise<ApickResult>;
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ export const {
5
5
  ApickApiError,
6
6
  ApickBinaryResult,
7
7
  SERVICES,
8
+ TTS_VOICE_IDS,
8
9
  DEFAULT_BASE_URL
9
10
  } = sdk;
10
11