apick-api 3.11.0 → 4.0.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/docs/tts.md CHANGED
@@ -1,54 +1,34 @@
1
- # TTS · 한국어 안내
1
+ # Gemini·ChatGPT TTS
2
2
 
3
- apick 직접 제작, apick → Gemini 자동 전환, Gemini 직접 제작은 같은 작업 조회·MP3·ASS 다운로드 API를 사용합니다. 파일 구조와 규격은 같으며 음성은 엔진과 목소리에 따라 다릅니다.
3
+ 기존 APICK 목소리와 자동 전환은 종료되었습니다. `createTtsJob`은 Gemini 기본 목소리 `Kore`를 사용합니다. `getTtsQuality`, `retryTtsJob`, `downloadTtsCandidate`의 기존 경로는 HTTP 409를 반환합니다.
4
+
5
+ Legacy APICK voices and automatic fallback have retired. `createTtsJob` now defaults to Gemini voice `Kore`. The legacy quality, retry and candidate endpoints return HTTP 409.
4
6
 
5
7
  ```js
6
- const apick = new ApickClient({ apiKey: process.env.APICK_API_KEY });
7
- const job = await apick.createTtsJob('기온은 5℃입니다.', {
8
- voiceId: 'v2_ann_m_30s_01',
9
- normalizeText: true,
10
- fallbackPolicy: 'busy',
11
- fallbackOptions: { voice_id: 'Charon', style: '차분하게' },
12
- idempotencyKey: 'narration-001'
13
- });
14
- const direct = await apick.createGeminiTtsJob({
15
- voice_id: 'Kore', style: '또렷하게', normalize_text: false,
16
- utterances: [{ text: '오늘의 이야기입니다.', speaker: '진행자' }]
17
- }, { idempotencyKey: 'gemini-001' });
18
- const voices = await apick.listGeminiTtsVoices();
19
- const quote = await apick.quoteTts({ engine: 'gemini', text: '본문', voice_id: 'Kore' });
20
- const status = await apick.getTtsJob(job.data.job_id);
21
- // completed 이후 기존 downloadTtsResult / downloadTtsSubtitles 사용
8
+ const job = await client.createTtsJob('기온은 오 도입니다.', { voiceId: 'Kore', normalizeText: false, idempotencyKey: 'tts-001' });
9
+ const dialogue = await client.createOpenAiTtsJob({
10
+ utterances: [{ speaker: '진행', text: '반갑습니다.' }, { speaker: '손님', text: '안녕하세요.' }],
11
+ speakers: { 진행: { voice_id: 'alloy' }, 손님: { voice_id: 'nova', emotion: 'calm' } },
12
+ multi_speaker: true, normalize_text: false
13
+ }, { idempotencyKey: 'tts-002' });
14
+ const voices = await client.listGeminiTtsVoices();
15
+ const otherVoices = await client.listOpenAiTtsVoices();
16
+ const options = await client.getTtsOptions();
17
+ const quote = await client.quoteTts({ engine: 'gemini', text: '본문', voice_id: 'Kore' });
22
18
  ```
23
19
 
24
- | 옵션 | 동작 |
25
- | --- | --- |
26
- | `never` | 기본값. apick 대기열 등록, 가득 차면 429 |
27
- | `queue_full` | 대기열에도 빈자리가 없을 때 Gemini 전환 |
28
- | `busy` | 즉시 apick 실행이 불가능하면 Gemini 전환 |
29
- | `fallbackOptions` | 전환할 때만 적용되는 `voice_id`, `style` |
30
- | `normalizeText` / `normalize_text` | 기본 true. false는 정규화 스킬 실행·과금 생략 |
31
-
32
- 정규화는 숫자·단위·약어의 문맥에 맞는 발음을 다듬으며, **추가 스킬 요금**이 합산됩니다. 정규화 on 입력 합계(발화 사이 줄바꿈 포함)는 2,000자, Gemini off는 8,000자, apick 기존 입력 한도는 800자입니다. 기본·확장 공개 목소리는 `listGeminiTtsVoices()`로 확인합니다. 자동 선택은 같은 성별의 한국어 목소리를 우선하고 공식 설명 또는 별도 검수로 확인한 연령대와 음색을 비교합니다. `age_basis: catalog_description`은 공식 설명에 적힌 설정 연령이며 실제 화자의 나이를 뜻하지 않습니다. 근거가 없는 목소리는 `age_verified: false`입니다. 대소문자 별칭은 목록에 중복 표시하지 않고 기존 입력도 허용합니다.
33
-
34
- Gemini 사용 시 목소리와 가격이 달라질 수 있습니다. 실제 AI 원가 × 호출 직전 적용 환율 × 1.4로 판매하고, 스킬은 기본요금(판매자 금액 + 기본 수수료) + AI 사용료입니다. Gemini 100만 토큰당 입력/출력 공급자 가격은 2026-12-31까지 $0.50/$6.00, 2027-01-01 00:00 UTC부터 $1.00/$12.00입니다. $0.54/시간은 출력 참고값이며 과금 단위가 아닙니다.
35
-
36
- `api.cost`는 접수 예약금을 확정액으로 표시하지 않습니다. `data.billing`의 `synthesis`, `skill`, `total`, `reserved`, `released`, `refunded`, `status`를 확인하세요. `voice_id`는 원래 요청을 보존하고 실제 엔진·모델·목소리는 `synthesis`에 표시합니다. 서버·공급자의 최종 제작 실패는 정규화까지 전액 환불합니다. 클라이언트 연결 종료는 정상 과금하고, 명시 취소는 이미 수행된 유료 처리분을 정산합니다.
37
-
38
- MP3 24kHz·모노·48kbps와 입력 원문 ASS를 완료 후 24시간 제공합니다. 파일별 한 번 다운로드, 중단 시 재시도가 가능합니다. 동일 요청의 접수 재시도에는 같은 `idempotencyKey`를 사용하세요. 이전 방식 작업의 품질·후보·재개 API는 해당 이전 작업에만 적용됩니다.
39
-
40
- [개발가이드](https://apick.app/dev_guide/tts) · [정규화 스킬](https://apick.app/skills/korean-text-normalize) · [공식 가격표](https://ai.google.dev/gemini-api/docs/pricing#gemini-3.8-flash-lite-tts)
20
+ `style`, `emotion`, `tone`, `accent`, `pace`(0.5–2), `pitch`(-12–12), `volume_gain_db`(-12–12)는 최상위·화자·발화에 지정할 수 있습니다. `speakers`는 화자 이름별 설정이고 `multi_speaker: true`는 Gemini 최대 2명, ChatGPT 최대 8명입니다. 엔진에 따라 표현 효과가 달라지며 정확한 속도 배율·반음·dB 적용을 보장하지 않습니다. 특히 ChatGPT는 수치의 방향을 낭독 지시로 전달합니다.
41
21
 
42
- # TTS · English
22
+ Style, emotion, tone, accent, pace, pitch and volume options can be set per request, speaker or utterance. Gemini supports up to two native speakers; ChatGPT supports up to eight speakers across utterances. Numeric acoustic adjustments are not guaranteed: ChatGPT receives directional instructions rather than precise rate, semitone or decibel controls.
43
23
 
44
- `createTtsJob` supports APICK synthesis and optional Gemini fallback. `createGeminiTtsJob` accepts Gemini voices, style, text or per-speaker utterances. All three paths share `getTtsJob`, `downloadTtsResult`, and `downloadTtsSubtitles`. The MP3/ASS format and response structure match; the generated voices differ.
24
+ 정규화는 기본 켜짐이며 추가 스킬 요금이 합산됩니다. `normalize_text: false`(SDK 문자열 호출은 `normalizeText: false`)로 끌 수 있습니다. 정규화 입력은 발화 사이 줄바꿈을 포함해 최대 2,000자, 정규화 없이 최대 8,000자입니다. 견적은 `quoteTts`, 현재 목소리는 엔진별 목록, 옵션은 `getTtsOptions`로 조회합니다.
45
25
 
46
- Fallback policies: `never` (default) queues APICK and returns 429 when its queue is full; `queue_full` uses Gemini only when the waiting queue is full; `busy` uses Gemini whenever immediate APICK execution is unavailable. `fallbackOptions.voice_id/style` apply only after fallback. Unsupported explicit voice IDs are rejected. `listGeminiTtsVoices` returns public basic and extended voices without case-alias duplicates, while existing aliases remain accepted. Automatic matching prioritizes Korean voices of the same gender, then the closest supported age band and vocal traits. `age_basis: catalog_description` refers to an age explicitly stated in the official voice description, not a real speaker's biological age. Voices without age evidence remain unverified.
26
+ Normalization is enabled by default and costs extra. Disable it with `normalize_text: false` (`normalizeText: false` for the string SDK helper). Input limits are 2,000 characters including utterance separators with normalization, or 8,000 without it. Query a quote, engine-specific voices and supported options before submitting.
47
27
 
48
- Text normalization is **on by default and costs extra**. Set `normalizeText: false` on SDK APICK options or `normalize_text: false` in a request body to skip both the skill and its charge. Normalization allows 2,000 characters across utterances including separating newlines. Gemini without normalization allows 8,000 characters; APICK retains its 800-character limit. The original spelling appears in ASS subtitles.
28
+ 실제 AI 원가 × 고정된 환율 × 1.4에 정규화 요금을 더해 정산합니다. Gemini 프로모션 입력/출력 단가는 2026-12-31까지 백만 토큰당 $0.50/$6.00이며 2027-01-01 00:00 UTC부터 $1.00/$12.00입니다. 약 $0.54/시간은 참고값이며 시간 단위 청구가 아닙니다. 엔진별 요금이 다릅니다.
49
29
 
50
- External AI charges equal verified provider cost × the pinned USD/KRW exchange rate × 1.4. A skill adds its base fee once, including seller amount and base commission, without a second markup on AI usage. Gemini provider input/output prices per million tokens are $0.50/$6.00 through December 31, 2026, then $1.00/$12.00 from January 1, 2027, 00:00 UTC. The approximate $0.54/hour figure is informational, not a billing unit. Fallback may change the price and voice.
30
+ AI usage is charged at verified cost × the pinned exchange rate × 1.4, plus normalization. Gemini promotional input/output rates are $0.50/$6.00 per million tokens through 2026-12-31, then $1.00/$12.00 from 2027-01-01 00:00 UTC. The approximate hourly figure is informational; billing uses actual usage and differs by engine.
51
31
 
52
- Read the job's `billing` breakdown for reserved, released, captured, and refunded amounts. Submission `api.cost` does not treat a reservation as a final charge. Top-level `voice_id` preserves the APICK request; `synthesis` reports the actual provider/model/voice. Final server/provider failures refund synthesis and normalization. Disconnecting does not cancel background work or billing; explicit cancellation settles work already performed.
32
+ 두 엔진은 같은 작업 조회·취소·MP3·원문 ASS 계약을 사용합니다. `billing`의 예약·해제·확정·환불을 확인하세요. MP3는 24kHz 모노 48kbps, 결과는 완료 후 24시간 보관합니다. MP3와 ASS는 각각 완료된 다운로드 1회만 허용하며 전송 중단은 재시도할 수 있습니다. 접수 응답 유실 시 같은 입력과 멱등 키를 재사용하세요. 서버·공급자 최종 실패는 정규화까지 환불하며, 연결 종료는 작업 취소가 아닙니다. 취소 시 이미 수행된 작업분을 정산합니다.
53
33
 
54
- Results are MP3 (24 kHz, mono, 48 kbps) and ASS, retained for 24 hours after completion. Each file permits one completed download; interrupted transfers may be retried. Reuse the same `idempotencyKey` and input after a lost submission response. Legacy quality/candidate/retry methods apply only to legacy jobs.
34
+ Both engines share job status, cancellation, MP3 and original-text ASS downloads. Check reserved, released, captured and refunded amounts in `billing`. MP3 is 24 kHz mono at 48 kbps. Files are kept for 24 hours; each permits one completed download, and interrupted transfers can be retried. Reuse the same input and idempotency key after a lost response. Final server/provider failures refund synthesis and normalization; disconnecting does not cancel work. Explicit cancellation settles work already performed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apick-api",
3
- "version": "3.11.0",
3
+ "version": "4.0.0",
4
4
  "description": "Official zero-dependency Node.js client for APICK data, AI, and image APIs. 에이픽 데이터·AI·이미지 API 공식 Node.js SDK.",
5
5
  "type": "module",
6
6
  "main": "./src/index.cjs",
package/src/index.cjs CHANGED
@@ -118,9 +118,9 @@ function normalizeTtsJobId(value) {
118
118
  return jobId;
119
119
  }
120
120
 
121
- function normalizeTtsVoice(value) {
122
- const voiceId = requiredString('voiceId', value);
123
- if (!TTS_VOICE_ID_SET.has(voiceId)) throw new RangeError('voiceId must be one of the supported TTS voice IDs.');
121
+ function normalizeTtsVoice(value) {
122
+ const voiceId = requiredString('voiceId', value, 160);
123
+ if (TTS_VOICE_ID_SET.has(voiceId)) throw new RangeError('This voiceId has retired. Select a current Gemini voice with listGeminiTtsVoices().');
124
124
  return voiceId;
125
125
  }
126
126
 
@@ -633,15 +633,14 @@ class ApickClient {
633
633
  createTtsJob(text, options) {
634
634
  const config = options || {};
635
635
  const payload = typeof text==='object' && text!==null ? {...text} : {
636
- voice_id: normalizeTtsVoice(config.voiceId || 'v2_ann_m_30s_01'),
637
- text: requiredString('text', text, 800)
636
+ voice_id: normalizeTtsVoice(config.voiceId || 'Kore'),
637
+ text: requiredString('text', text, config.normalizeText === false ? 8000 : 2000)
638
638
  };
639
639
  if(config.normalizeText!==undefined)payload.normalize_text=config.normalizeText;
640
- if(config.fallbackPolicy!==undefined)payload.fallback_policy=config.fallbackPolicy;
641
- if(config.fallbackOptions!==undefined)payload.fallback_options=config.fallbackOptions;
640
+ if(config.fallbackPolicy!==undefined||config.fallbackOptions!==undefined||payload.fallback_policy!==undefined||payload.fallback_options!==undefined)throw new TypeError('TTS fallback has retired. Use createGeminiTtsJob or createOpenAiTtsJob.');
642
641
  if(payload.normalize_text!==undefined&&typeof payload.normalize_text!=='boolean')throw new TypeError('normalizeText must be boolean.');
643
- if(payload.fallback_policy!==undefined&&!['never','queue_full','busy'].includes(payload.fallback_policy))throw new TypeError('Invalid fallbackPolicy.');
644
- return this._call('createTtsJob',payload,null,{json:typeof text==='object'||config.normalizeText!==undefined||config.fallbackPolicy!==undefined||config.fallbackOptions!==undefined,idempotencyKey:config.idempotencyKey});
642
+ if(payload.voice_id!==undefined)normalizeTtsVoice(payload.voice_id);
643
+ return this._call('createTtsJob',payload,null,{json:true,idempotencyKey:config.idempotencyKey});
645
644
  }
646
645
 
647
646
  createGeminiTtsJob(input, options) {
@@ -649,6 +648,12 @@ class ApickClient {
649
648
  return this._call('createTtsJob', input, null, {endpoint:'/rest/tts/gemini/jobs',json:true,idempotencyKey:options?.idempotencyKey});
650
649
  }
651
650
  listGeminiTtsVoices() { return this._call('createTtsJob',null,null,{endpoint:'/rest/tts/gemini/voices',method:'GET'}); }
651
+ createOpenAiTtsJob(input, options) {
652
+ if(!input||typeof input!=='object'||Array.isArray(input))throw new TypeError('OpenAI TTS input is required.');
653
+ return this._call('createTtsJob',input,null,{endpoint:'/rest/tts/openai/jobs',json:true,idempotencyKey:options?.idempotencyKey});
654
+ }
655
+ listOpenAiTtsVoices() { return this._call('createTtsJob',null,null,{endpoint:'/rest/tts/openai/voices',method:'GET'}); }
656
+ getTtsOptions() { return this._call('createTtsJob',null,null,{endpoint:'/rest/tts/options',method:'GET'}); }
652
657
  quoteTts(input) { return this._call('createTtsJob',input,null,{endpoint:'/rest/tts/quote',json:true}); }
653
658
 
654
659
  getTtsJob(jobId) {
package/src/index.d.ts CHANGED
@@ -200,15 +200,22 @@ export interface YoutubeSubtitleList {
200
200
  }
201
201
  export interface YoutubeSubtitleOptions { format?: 'vtt' | 'srt' | 'txt'; type?: 'any' | 'manual' | 'auto'; }
202
202
 
203
- export const TTS_VOICE_IDS: readonly [
203
+ /** @deprecated Retired APICK voice IDs. Query listGeminiTtsVoices or listOpenAiTtsVoices. */
204
+ export const TTS_VOICE_IDS: readonly [
204
205
  'v2_ann_m_30s_01', 'v2_ann_m_30s_02', 'v2_ann_m_30s_04', 'v2_ann_m_30s_05', 'v2_ann_f_30s_02', 'v2_ann_f_30s_03', 'v2_ann_f_30s_04', 'v2_ann_f_30s_05', 'v2_m_teen_01', 'v2_m_young_01', 'v2_m_mid_01', 'v2_m_senior_01', 'v2_f_young_01', 'v2_f_senior_01'
205
206
  ];
206
- export type TtsVoiceId = typeof TTS_VOICE_IDS[number];
207
+ export type TtsVoiceId = string;
208
+ /** @deprecated Automatic fallback is no longer supported. */
207
209
  export type TtsFallbackPolicy = 'never' | 'queue_full' | 'busy';
208
210
  export interface TtsFallbackOptions { voice_id?: string; style?: string; }
211
+ /** @deprecated Retired APICK request shape. Use GeminiTtsInput or OpenAiTtsInput. */
209
212
  export interface ApickTtsInput { voice_id: TtsVoiceId; text?: string; utterances?: Array<{text:string;emotion?:string;speed?:number;pause_after_ms?:number}>; normalize_text?:boolean; fallback_policy?:TtsFallbackPolicy; fallback_options?:TtsFallbackOptions; }
210
- export interface GeminiTtsInput { voice_id?:string; text?:string; style?:string; utterances?:Array<{text:string;voice_id?:string;style?:string;speaker?:string}>; normalize_text?:boolean; language_code?:'ko'|'ko-KR'; }
211
- export interface TtsOptions { voiceId?:TtsVoiceId; normalizeText?:boolean; fallbackPolicy?:TtsFallbackPolicy; fallbackOptions?:TtsFallbackOptions; idempotencyKey?:string; }
213
+ export interface TtsStyleOptions { style?:string; emotion?:'neutral'|'calm'|'cheerful'|'excited'|'sad'|'serious'|'friendly'|'empathetic'|'confident'|'gentle'|'whisper'|'narration'; tone?:'narration'|'news'|'audiobook'|'documentary'|'ad'|'conversation'|'announcement'|'tutorial'|'storytelling'; accent?:'standard'|'seoul'|'gyeongsang'|'jeolla'|'chungcheong'; pace?:number; pitch?:number; volume_gain_db?:number; }
214
+ export interface TtsSpeakerOptions extends TtsStyleOptions { voice_id?:string; }
215
+ export interface TtsUtterance extends TtsSpeakerOptions { text:string; speaker?:string; }
216
+ export interface GeminiTtsInput extends TtsStyleOptions { voice_id?:string; text?:string; utterances?:TtsUtterance[]; speakers?:Record<string,TtsSpeakerOptions>; multi_speaker?:boolean; normalize_text?:boolean; language_code?:'ko'|'ko-KR'; }
217
+ export interface OpenAiTtsInput extends GeminiTtsInput {}
218
+ export interface TtsOptions { voiceId?:TtsVoiceId; normalizeText?:boolean; idempotencyKey?:string; }
212
219
  export interface GeminiTtsVoice { voice_id:string; name:string; gender:string|null; tone:string; age_band:string|null; age_years?:number|null; age_verified?:boolean; age_basis?:'catalog_description'|'reviewed'; age_source?:string; description?:string; language_code?:string|null; reviewed_at?:string; library:string; }
213
220
  export interface TtsQuote { synthesis:number; normalization:number; estimated_total:number; maximum:number; synthesis_maximum:number; normalization_maximum:number; estimated_only:true; currency:'POINT'; fallback_price_may_change:boolean; }
214
221
 
@@ -216,7 +223,7 @@ export interface TtsJobData {
216
223
  job_id: string;
217
224
  status: 'waiting' | 'processing' | 'completed' | 'cancelled' | 'failed';
218
225
  voice_id?: string;
219
- synthesis?: {engine:'apick'|'gemini';model:string;voice_id:string;fallback_reason:string|null;age_verified:boolean;notice:string|null};
226
+ synthesis?: {engine:'apick'|'gemini'|'openai';model:string;voice_id:string;fallback_reason:string|null;age_verified:boolean;notice:string|null};
220
227
  normalization?: {enabled:boolean;run_id:string|null;status:string;skill_id:string|null};
221
228
  billing?: {status:string;synthesis:number;skill:number;total:number;reserved:number;refunded:number;released:number;currency:'POINT'};
222
229
  expires_at?:string|null;
@@ -302,16 +309,22 @@ export class ApickClient {
302
309
  youtubeThumbnail(url: string): Promise<ApickBinaryResult>;
303
310
  youtubeSubtitleList(url: string): Promise<ApickResult<YoutubeSubtitleList>>;
304
311
  youtubeSubtitle(url: string, lang: string, options?: YoutubeSubtitleOptions): Promise<ApickBinaryResult>;
305
- createTtsJob(text: string | ApickTtsInput, options?: TtsOptions): Promise<ApickResult<TtsJobData>>;
312
+ createTtsJob(text: string | GeminiTtsInput, options?: TtsOptions): Promise<ApickResult<TtsJobData>>;
306
313
  createGeminiTtsJob(input:GeminiTtsInput, options?:{idempotencyKey?:string}):Promise<ApickResult<TtsJobData>>;
307
314
  listGeminiTtsVoices():Promise<ApickResult<{voices:GeminiTtsVoice[];complete:boolean;checked_at:string|null}>>;
308
- quoteTts(input:(ApickTtsInput|GeminiTtsInput)&{engine?:'apick'|'gemini'}):Promise<ApickResult<TtsQuote>>;
315
+ createOpenAiTtsJob(input:OpenAiTtsInput, options?:{idempotencyKey?:string}):Promise<ApickResult<TtsJobData>>;
316
+ listOpenAiTtsVoices():Promise<ApickResult<{voices:GeminiTtsVoice[];complete:boolean;checked_at:string|null}>>;
317
+ getTtsOptions():Promise<ApickResult<{engines:Record<'gemini'|'openai',Record<string,unknown>>}>>;
318
+ quoteTts(input:GeminiTtsInput&{engine?:'gemini'|'openai'}):Promise<ApickResult<TtsQuote>>;
309
319
  getTtsJob(jobId: string): Promise<ApickResult<TtsJobData>>;
310
320
  cancelTtsJob(jobId: string): Promise<ApickResult<TtsJobData>>;
311
321
  downloadTtsResult(jobId: string): Promise<ApickBinaryResult>;
312
322
  downloadTtsSubtitles(jobId: string): Promise<ApickBinaryResult>;
313
- getTtsQuality(jobId: string): Promise<ApickResult<TtsQualityData>>;
314
- retryTtsJob(jobId: string, utteranceIds: string[], idempotencyKey: string): Promise<ApickResult<TtsJobData>>;
323
+ /** @deprecated Retired endpoint; the service returns HTTP 409. */
324
+ getTtsQuality(jobId: string): Promise<ApickResult<TtsQualityData>>;
325
+ /** @deprecated Retired endpoint; the service returns HTTP 409. */
326
+ retryTtsJob(jobId: string, utteranceIds: string[], idempotencyKey: string): Promise<ApickResult<TtsJobData>>;
327
+ /** @deprecated Retired endpoint; the service returns HTTP 409. */
315
328
  downloadTtsCandidate(jobId: string, candidateId: string): Promise<ApickBinaryResult>;
316
329
  htmlToPdf(html: string, options?: { pagination?: boolean }): Promise<ApickBinaryResult>;
317
330
  jsonToExcel(data: unknown[], options?: { sheetName?: string }): Promise<ApickBinaryResult>;