diff --git a/content/perso/docs/package/javascript/DOC.md b/content/perso/docs/package/javascript/DOC.md new file mode 100644 index 00000000..417291ff --- /dev/null +++ b/content/perso/docs/package/javascript/DOC.md @@ -0,0 +1,597 @@ +--- +name: perso-api-dubbing +description: "Perso AI video/audio dubbing and translation REST API — JavaScript/TypeScript guide with fetch" +metadata: + languages: "javascript" + versions: "1.0.0" + revision: 1 + updated-on: "2026-03-18" + source: maintainer + tags: "perso,dubbing,translation,video,audio,lip-sync,ai,javascript,typescript" +--- + +# Perso API — JavaScript / TypeScript + +Perso is an AI-powered video and audio dubbing/translation platform. The API lets you upload media, request translations into multiple languages, edit translated sentences, generate lip-synced video, and download results. + +## Configuration + +```javascript +const API_BASE = "https://api.perso.ai"; // All API requests +const SERVICE_DOMAIN = "https://perso.ai"; // File path access (e.g. /perso-storage/...) +const headers = { + "XP-API-KEY": "pk-live-xxxxxxxxxxxxxxxx", + "Content-Type": "application/json" +}; +``` + +- **API Base URL** (`https://api.perso.ai`): all API requests go here. +- **Service Domain** (`https://perso.ai`): used only for accessing file paths (e.g. `/perso-storage/...`). + +## Authentication + +All endpoints require an API key in the `XP-API-KEY` header. Keys are prefixed `pk-live-` (production) or `pk-test-` (sandbox). + +## Common Response Format + +```javascript +// Success +{ "result": { /* response data */ } } + +// Error +{ "code": "VT4041", "status": "NOT_FOUND", "message": "Error description" } +``` + +## File Paths + +Response fields like `videoFilePath`, `audioFilePath`, `thumbnailFilePath` are **relative paths**. Prepend the **Service Domain** (`https://perso.ai`), NOT the API Base URL: + +```javascript +const videoPath = upload.videoFilePath; // e.g. "/perso-storage/.../video.mp4" +const fullUrl = `${SERVICE_DOMAIN}${videoPath}`; +// https://perso.ai/perso-storage/.../video.mp4 +``` + +## Prerequisites — Resolve Your Space + +The Perso API is **space-based**. Most endpoints require a `{spaceSeq}` path parameter. First retrieve your spaces and find your default space: + +```javascript +const spaces = await fetch(`${API_BASE}/portal/api/v1/spaces`, { headers }).then(r => r.json()); +const defaultSpace = spaces.result.find(s => s.isDefaultSpaceOwned); +const spaceSeq = defaultSpace.spaceSeq; +``` + +Use `spaceSeq` for all subsequent requests that require `{spaceSeq}`. + +--- + +## Core Workflow + +### Option A — Direct file upload + +``` +1. GET /file/api/upload/sas-token → Get SAS token from File API +2. PUT {blobSasUrl} → Client uploads file directly to Azure Blob Storage +3. PUT /file/api/upload/video (or /audio) → Register the uploaded blob URL with File API (returns mediaSeq) +4. PUT /video-translator/.../queue → Initialize queue (required once per space) +5. POST /video-translator/.../translate → Request translation (returns projectSeq) +6. GET /video-translator/.../progress → Poll until progress reaches 100 +7. GET /video-translator/.../download → Download translated video/audio/subtitles +``` + +**Step 1–2 detail:** The SAS token endpoint returns a `blobSasUrl` — a pre-signed Azure Blob Storage URL with write permission. The **client uploads the file directly** to Azure Blob Storage via a `PUT` request to this URL. The Perso API server is not involved in the actual file transfer. After upload, pass the blob URL (without query params) as `fileUrl` to the Upload Video/Audio endpoint. + +### Option B — External platform upload + +``` +1. POST /file/api/v1/video-translator/external/metadata → Preview video info (optional) +2. POST /file/api/v1/media/validate → Validate media constraints (optional) +3. PUT /file/api/upload/video/external → Upload from YouTube/TikTok/Google Drive (returns mediaSeq) +4–7. Same as Option A steps 4–7 +``` + +**Step 3 detail:** The server downloads the video from the external platform on your behalf. This is synchronous and may take **up to 10 minutes** depending on video size. + +--- + +## Full Translation Workflow (Direct Upload) + +```javascript +// 1. Get SAS token from File API +const sas = await fetch( + `${API_BASE}/file/api/upload/sas-token?fileName=video.mp4`, + { headers } +).then(r => r.json()); + +// 2. Client uploads file directly to Azure Blob Storage +const file = await fs.promises.readFile("video.mp4"); // or File from +await fetch(sas.blobSasUrl, { + method: "PUT", + body: file, + headers: { + "x-ms-blob-type": "BlockBlob", + "Content-Type": "application/octet-stream" + } +}); + +// 3. Register uploaded video with File API (returns mediaSeq) +const upload = await fetch(`${API_BASE}/file/api/upload/video`, { + method: "PUT", + headers, + body: JSON.stringify({ + spaceSeq, + fileUrl: sas.blobSasUrl.split("?")[0], + fileName: "video.mp4" + }) +}).then(r => r.json()); +const mediaSeq = upload.seq; + +// 4. Initialize queue (required once per space) +await fetch(`${API_BASE}/video-translator/api/v1/projects/spaces/${spaceSeq}/queue`, { + method: "PUT", + headers +}); + +// 5. Request translation +const result = await fetch(`${API_BASE}/video-translator/api/v1/projects/spaces/${spaceSeq}/translate`, { + method: "POST", + headers, + body: JSON.stringify({ + mediaSeq, + isVideoProject: true, + sourceLanguageCode: "en", + targetLanguageCodes: ["ko"], + numberOfSpeakers: 1, + preferredSpeedType: "GREEN" + }) +}).then(r => r.json()); +const projectSeq = result.result.startGenerateProjectIdList[0]; + +// 6. Poll progress until complete +while (true) { + const progress = await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/space/${spaceSeq}/progress`, + { headers } + ).then(r => r.json()); + const p = progress.result; + console.log(`Progress: ${p.progress}% — ${p.progressReason}`); + if (p.progress >= 100 || p.hasFailed) break; + await new Promise(r => setTimeout(r, 10000)); +} + +// 7. Download translated files +const downloads = await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}/download?target=all`, + { headers } +).then(r => r.json()); +console.log(downloads.result); +``` + +--- + +## File API + +### Get SAS Token + +```javascript +const sas = await fetch( + `${API_BASE}/file/api/upload/sas-token?fileName=video.mp4`, + { headers } +).then(r => r.json()); +// {"blobSasUrl": "https://{account}.blob.core.windows.net/...?sv=...&sig=...", "expirationDatetime": "..."} +``` + +Token expires in 30 minutes. Upload your file to `blobSasUrl` via PUT before expiration. + +### Upload Video + +```javascript +const upload = await fetch(`${API_BASE}/file/api/upload/video`, { + method: "PUT", + headers, + body: JSON.stringify({ spaceSeq, fileUrl: blobUrl, fileName: "video.mp4" }) +}).then(r => r.json()); +const mediaSeq = upload.seq; // use as mediaSeq for translation +// Also returns: originalName, videoFilePath, thumbnailFilePath, size, durationMs +``` + +### Upload Audio + +```javascript +const upload = await fetch(`${API_BASE}/file/api/upload/audio`, { + method: "PUT", + headers, + body: JSON.stringify({ spaceSeq, fileUrl: blobUrl, fileName: "audio.mp3" }) +}).then(r => r.json()); +const mediaSeq = upload.seq; +``` + +### Upload External Video + +Upload from YouTube, TikTok, or Google Drive. Synchronous — may take up to 10 minutes. + +**IMPORTANT:** This endpoint uses **snake_case** request body. + +```javascript +const upload = await fetch(`${API_BASE}/file/api/upload/video/external`, { + method: "PUT", + headers, + body: JSON.stringify({ + space_seq: spaceSeq, + url: "https://www.youtube.com/watch?v=xxxxx", + lang: "en" + }) +}).then(r => r.json()); +const mediaSeq = upload.seq; +``` + +### Validate Media + +Pre-validate media metadata before upload. + +```javascript +const validation = await fetch(`${API_BASE}/file/api/v1/media/validate`, { + method: "POST", + headers, + body: JSON.stringify({ + spaceSeq, + durationMs: 30000, + originalName: "video.mp4", + mediaType: "video", + extension: ".mp4", + size: 52428800, + width: 1920, + height: 1080 + }) +}).then(r => r.json()); +// {"status": true} on success, error response on failure +``` + +**Constraints:** +| | Video | Audio | +|---|---|---| +| Extensions | .mp4, .webm, .mov | .mp3, .wav | +| Max size | 2GB | 2GB | +| Duration | 5s – plan limit | 5s – plan limit | +| Resolution | 201×201 – 7999×7999 | N/A | + +### Get External Metadata + +Preview external video info without downloading. Uses **snake_case** body. + +```javascript +const metadata = await fetch(`${API_BASE}/file/api/v1/video-translator/external/metadata`, { + method: "POST", + headers, + body: JSON.stringify({ + space_seq: spaceSeq, + url: "https://www.youtube.com/watch?v=xxxxx", + lang: "en" + }) +}).then(r => r.json()); +// Returns: durationMs, originalName, thumbnailFilePath, mediaType, size, extension, width, height +``` + +--- + +## Space API + +### List Spaces + +```javascript +const spaces = await fetch(`${API_BASE}/portal/api/v1/spaces`, { headers }).then(r => r.json()); +``` + +### Get Space + +```javascript +const space = await fetch(`${API_BASE}/portal/api/v1/spaces/${spaceSeq}`, { headers }).then(r => r.json()); +``` + +--- + +## Dubbing API (Translation) + +### Initialize Queue (required before first translation) + +```javascript +await fetch(`${API_BASE}/video-translator/api/v1/projects/spaces/${spaceSeq}/queue`, { + method: "PUT", + headers +}); +``` + +### Request Translation + +```javascript +const result = await fetch(`${API_BASE}/video-translator/api/v1/projects/spaces/${spaceSeq}/translate`, { + method: "POST", + headers, + body: JSON.stringify({ + mediaSeq, + isVideoProject: true, + sourceLanguageCode: "en", + targetLanguageCodes: ["ko", "ja"], + numberOfSpeakers: 2, + withLipSync: false, + preferredSpeedType: "GREEN" + }) +}).then(r => r.json()); +const projectSeq = result.result.startGenerateProjectIdList[0]; +``` + +- `mediaSeq`: the `seq` from Upload Video/Audio response +- `numberOfSpeakers`: **required** — number of speakers for multi-speaker detection +- `preferredSpeedType`: `"GREEN"` (standard) or `"RED"` (priority) +- Optional: `customDictionaryBlobPath`, `srtBlobPath` + +### Poll Progress + +```javascript +const progress = await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/space/${spaceSeq}/progress`, + { headers } +).then(r => r.json()); +// Returns: projectSeq, progress (0-100), progressReason, hasFailed, expectedRemainingTimeMinutes, isCancelable +``` + +### Get Project + +```javascript +const project = await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}`, + { headers } +).then(r => r.json()); +``` + +### List Projects + +```javascript +const projects = await fetch( + `${API_BASE}/video-translator/api/v1/projects/spaces/${spaceSeq}?memberRole=developer&size=10&offset=0&sortDirection=desc`, + { headers } +).then(r => r.json()); +``` + +### Download Files + +```javascript +const downloads = await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}/download?target=all`, + { headers } +).then(r => r.json()); +// Returns download links for: videoFile, audioFile, srtFile, zippedFileDownloadLink +``` + +### Get Script + +```javascript +const script = await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}/script?size=10000`, + { headers } +).then(r => r.json()); +// Returns sentences with translations, matching rates, speaker info. Cursor pagination via cursorId. +``` + +### Cancel Project + +```javascript +await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}/cancel`, + { method: "POST", headers } +); +// Only for GREEN zone projects in PENDING state +``` + +### Update Title / Delete / Update Access / Toggle Share + +```javascript +// Update title +await fetch(`${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}/title`, { + method: "PATCH", headers, body: JSON.stringify({ newTitle: "New Name" }) +}); + +// Delete project +await fetch(`${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}`, { + method: "DELETE", headers +}); + +// Update access +await fetch(`${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}/access?permission=all`, { + method: "PATCH", headers +}); + +// Toggle share +await fetch(`${API_BASE}/video-translator/api/v1/projects/${projectSeq}/share?sharedStatus=true`, { + method: "PATCH", headers +}); +``` + +--- + +## Editing API + +Edit individual sentences within a completed translation project. + +### Translate Sentence + +```javascript +const updated = await fetch( + `${API_BASE}/video-translator/api/v1/project/${projectSeq}/audio-sentence/${sentenceSeq}`, + { method: "PATCH", headers, body: JSON.stringify({ targetText: "Updated translation text" }) } +).then(r => r.json()); +// Returns: scriptSeq, translatedText, matchingRate, rewrite +``` + +### Generate Audio + +```javascript +const audio = await fetch( + `${API_BASE}/video-translator/api/v1/project/${projectSeq}/audio-sentence/${sentenceSeq}/generate-audio`, + { method: "PATCH", headers, body: JSON.stringify({ targetText: "Text to generate audio for" }) } +).then(r => r.json()); +``` + +### Reset Translation + +```javascript +await fetch( + `${API_BASE}/video-translator/api/v1/project/${projectSeq}/audio-sentence/${sentenceSeq}/reset`, + { method: "PUT", headers } +); +``` + +### Request Proofread + +```javascript +await fetch( + `${API_BASE}/video-translator/api/v1/project/${projectSeq}/space/${spaceSeq}/proofread`, + { method: "POST", headers, body: JSON.stringify({ isLipSync: false, preferredSpeedType: "GREEN" }) } +); +``` + +--- + +## Lip Sync API + +### Request Lip Sync + +```javascript +const lipSync = await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}/lip-sync`, + { method: "POST", headers, body: JSON.stringify({ preferredSpeedType: "GREEN" }) } +).then(r => r.json()); +``` + +### Get Generation History + +```javascript +const history = await fetch( + `${API_BASE}/video-translator/api/v1/projects/${projectSeq}/spaces/${spaceSeq}/lip-sync/generated?page=1&pageSize=10`, + { headers } +).then(r => r.json()); +``` + +--- + +## Usage API + +### Get User Quota + +```javascript +const quota = await fetch( + `${API_BASE}/video-translator/api/v1/projects/spaces/${spaceSeq}/plan/status`, + { headers } +).then(r => r.json()); +console.log(`Remaining: ${quota.result.remainingQuota.remainingQuota}`); +``` + +### Estimate Quota Usage + +```javascript +const estimate = await fetch( + `${API_BASE}/video-translator/api/v1/projects/spaces/${spaceSeq}/media/quota?mediaType=VIDEO&lipSync=false&durationMs=30000&width=1920&height=1080`, + { headers } +).then(r => r.json()); +``` + +--- + +## Language API + +### List Languages + +```javascript +const languages = await fetch( + `${API_BASE}/video-translator/api/v1/languages`, + { headers } +).then(r => r.json()); +languages.result.languages.forEach(lang => { + console.log(`${lang.code}: ${lang.name}${lang.experiment ? " (experimental)" : ""}`); +}); +``` + +--- + +## Feedback API + +### Submit Feedback + +```javascript +await fetch(`${API_BASE}/video-translator/api/v1/projects/feedbacks`, { + method: "POST", headers, body: JSON.stringify({ projectSeq: 101, rating: 4 }) +}); +``` + +### Get Feedback + +```javascript +const feedback = await fetch( + `${API_BASE}/video-translator/api/v1/projects/feedbacks?projectSeq=101`, + { headers } +).then(r => r.json()); +``` + +--- + +## Community Spotlight API + +### List Featured Projects + +```javascript +const featured = await fetch( + `${API_BASE}/video-translator/api/v1/projects/recommended?page=0&size=10&languageCode=ko`, + { headers } +).then(r => r.json()); +``` + +### Get Featured / Shared Project + +```javascript +const project = await fetch( + `${API_BASE}/video-translator/api/v1/projects/recommended/${projectSeq}`, + { headers } +).then(r => r.json()); + +const shared = await fetch( + `${API_BASE}/video-translator/api/v1/projects/shared/${sharedQuery}`, + { headers } +).then(r => r.json()); +``` + +--- + +## Browser: Upload from File Input + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// 1. Get SAS token +const sas = await fetch( + `${API_BASE}/file/api/upload/sas-token?fileName=${encodeURIComponent(file.name)}`, + { headers } +).then(r => r.json()); + +// 2. Upload directly to Azure Blob Storage from browser +await fetch(sas.blobSasUrl, { + method: "PUT", + body: file, + headers: { + "x-ms-blob-type": "BlockBlob", + "Content-Type": "application/octet-stream" + } +}); + +// 3. Register with File API +const upload = await fetch(`${API_BASE}/file/api/upload/video`, { + method: "PUT", + headers, + body: JSON.stringify({ + spaceSeq, + fileUrl: sas.blobSasUrl.split("?")[0], + fileName: file.name + }) +}).then(r => r.json()); +``` diff --git a/content/perso/docs/package/python/DOC.md b/content/perso/docs/package/python/DOC.md new file mode 100644 index 00000000..398052dd --- /dev/null +++ b/content/perso/docs/package/python/DOC.md @@ -0,0 +1,584 @@ +--- +name: perso-api-dubbing +description: "Perso AI video/audio dubbing and translation REST API — Python guide with requests" +metadata: + languages: "python" + versions: "1.0.0" + revision: 1 + updated-on: "2026-03-18" + source: maintainer + tags: "perso,dubbing,translation,video,audio,lip-sync,ai,python" +--- + +# Perso API — Python + +Perso is an AI-powered video and audio dubbing/translation platform. The API lets you upload media, request translations into multiple languages, edit translated sentences, generate lip-synced video, and download results. + +```bash +pip install requests +``` + +## Configuration + +```python +import requests +import time + +API_BASE = "https://api.perso.ai" # All API requests +SERVICE_DOMAIN = "https://perso.ai" # File path access (e.g. /perso-storage/...) +HEADERS = {"XP-API-KEY": "pk-live-xxxxxxxxxxxxxxxx"} +``` + +- **API Base URL** (`https://api.perso.ai`): all API requests go here. +- **Service Domain** (`https://perso.ai`): used only for accessing file paths (e.g. `/perso-storage/...`). + +## Authentication + +All endpoints require an API key in the `XP-API-KEY` header. Keys are prefixed `pk-live-` (production) or `pk-test-` (sandbox). + +## Common Response Format + +```python +# Success +{"result": { ... }} + +# Error +{"code": "VT4041", "status": "NOT_FOUND", "message": "Error description"} +``` + +## File Paths + +Response fields like `videoFilePath`, `audioFilePath`, `thumbnailFilePath` are **relative paths**. Prepend the **Service Domain** (`https://perso.ai`), NOT the API Base URL: + +```python +video_path = upload["videoFilePath"] # e.g. "/perso-storage/.../video.mp4" +full_url = f"{SERVICE_DOMAIN}{video_path}" +# https://perso.ai/perso-storage/.../video.mp4 +``` + +## Prerequisites — Resolve Your Space + +The Perso API is **space-based**. Most endpoints require a `{spaceSeq}` path parameter. First retrieve your spaces and find your default space: + +```python +spaces = requests.get(f"{API_BASE}/portal/api/v1/spaces", headers=HEADERS).json() +default_space = next(s for s in spaces["result"] if s["isDefaultSpaceOwned"]) +space_seq = default_space["spaceSeq"] +``` + +Use `space_seq` for all subsequent requests that require `{spaceSeq}`. + +--- + +## Core Workflow + +### Option A — Direct file upload + +``` +1. GET /file/api/upload/sas-token → Get SAS token from File API +2. PUT {blobSasUrl} → Client uploads file directly to Azure Blob Storage +3. PUT /file/api/upload/video (or /audio) → Register the uploaded blob URL with File API (returns mediaSeq) +4. PUT /video-translator/.../queue → Initialize queue (required once per space) +5. POST /video-translator/.../translate → Request translation (returns projectSeq) +6. GET /video-translator/.../progress → Poll until progress reaches 100 +7. GET /video-translator/.../download → Download translated video/audio/subtitles +``` + +**Step 1–2 detail:** The SAS token endpoint returns a `blobSasUrl` — a pre-signed Azure Blob Storage URL with write permission. The **client uploads the file directly** to Azure Blob Storage via a `PUT` request to this URL. The Perso API server is not involved in the actual file transfer. After upload, pass the blob URL (without query params) as `fileUrl` to the Upload Video/Audio endpoint. + +### Option B — External platform upload + +``` +1. POST /file/api/v1/video-translator/external/metadata → Preview video info (optional) +2. POST /file/api/v1/media/validate → Validate media constraints (optional) +3. PUT /file/api/upload/video/external → Upload from YouTube/TikTok/Google Drive (returns mediaSeq) +4–7. Same as Option A steps 4–7 +``` + +**Step 3 detail:** The server downloads the video from the external platform on your behalf. This is synchronous and may take **up to 10 minutes** depending on video size. + +--- + +## Full Translation Workflow (Direct Upload) + +```python +# 1. Get SAS token from File API +sas = requests.get( + f"{API_BASE}/file/api/upload/sas-token", + headers=HEADERS, + params={"fileName": "video.mp4"} +).json() + +# 2. Client uploads file directly to Azure Blob Storage +with open("video.mp4", "rb") as f: + requests.put( + sas["blobSasUrl"], + data=f, + headers={ + "x-ms-blob-type": "BlockBlob", + "Content-Type": "application/octet-stream" + } + ) + +# 3. Register uploaded video with File API (returns mediaSeq) +upload = requests.put( + f"{API_BASE}/file/api/upload/video", + headers=HEADERS, + json={ + "spaceSeq": space_seq, + "fileUrl": sas["blobSasUrl"].split("?")[0], + "fileName": "video.mp4" + } +).json() +media_seq = upload["seq"] + +# 4. Initialize queue (required once per space) +requests.put( + f"{API_BASE}/video-translator/api/v1/projects/spaces/{space_seq}/queue", + headers=HEADERS +) + +# 5. Request translation +result = requests.post( + f"{API_BASE}/video-translator/api/v1/projects/spaces/{space_seq}/translate", + headers=HEADERS, + json={ + "mediaSeq": media_seq, + "isVideoProject": True, + "sourceLanguageCode": "en", + "targetLanguageCodes": ["ko"], + "numberOfSpeakers": 1, + "preferredSpeedType": "GREEN" + } +).json() +project_seq = result["result"]["startGenerateProjectIdList"][0] + +# 6. Poll progress until complete +while True: + progress = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/space/{space_seq}/progress", + headers=HEADERS + ).json() + p = progress["result"] + print(f"Progress: {p['progress']}% — {p['progressReason']}") + if p["progress"] >= 100 or p["hasFailed"]: + break + time.sleep(10) + +# 7. Download translated files +downloads = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}/download", + headers=HEADERS, + params={"target": "all"} +).json() +print(downloads["result"]) +``` + +--- + +## File API + +### Get SAS Token + +```python +sas = requests.get( + f"{API_BASE}/file/api/upload/sas-token", + headers=HEADERS, + params={"fileName": "video.mp4"} +).json() +# {"blobSasUrl": "https://{account}.blob.core.windows.net/...?sv=...&sig=...", "expirationDatetime": "..."} +``` + +Token expires in 30 minutes. Upload your file to `blobSasUrl` via PUT before expiration. + +### Upload Video + +```python +upload = requests.put( + f"{API_BASE}/file/api/upload/video", + headers=HEADERS, + json={"spaceSeq": space_seq, "fileUrl": blob_url, "fileName": "video.mp4"} +).json() +media_seq = upload["seq"] # use as mediaSeq for translation +# Also returns: originalName, videoFilePath, thumbnailFilePath, size, durationMs +``` + +### Upload Audio + +```python +upload = requests.put( + f"{API_BASE}/file/api/upload/audio", + headers=HEADERS, + json={"spaceSeq": space_seq, "fileUrl": blob_url, "fileName": "audio.mp3"} +).json() +media_seq = upload["seq"] +``` + +### Upload External Video + +Upload from YouTube, TikTok, or Google Drive. Synchronous — may take up to 10 minutes. + +**IMPORTANT:** This endpoint uses **snake_case** request body. + +```python +upload = requests.put( + f"{API_BASE}/file/api/upload/video/external", + headers=HEADERS, + json={ + "space_seq": space_seq, + "url": "https://www.youtube.com/watch?v=xxxxx", + "lang": "en" + } +).json() +media_seq = upload["seq"] +``` + +### Validate Media + +Pre-validate media metadata before upload. + +```python +validation = requests.post( + f"{API_BASE}/file/api/v1/media/validate", + headers=HEADERS, + json={ + "spaceSeq": space_seq, + "durationMs": 30000, + "originalName": "video.mp4", + "mediaType": "video", + "extension": ".mp4", + "size": 52428800, + "width": 1920, + "height": 1080 + } +).json() +# {"status": true} on success, error response on failure +``` + +**Constraints:** +| | Video | Audio | +|---|---|---| +| Extensions | .mp4, .webm, .mov | .mp3, .wav | +| Max size | 2GB | 2GB | +| Duration | 5s – plan limit | 5s – plan limit | +| Resolution | 201×201 – 7999×7999 | N/A | + +### Get External Metadata + +Preview external video info without downloading. Uses **snake_case** body. + +```python +metadata = requests.post( + f"{API_BASE}/file/api/v1/video-translator/external/metadata", + headers=HEADERS, + json={ + "space_seq": space_seq, + "url": "https://www.youtube.com/watch?v=xxxxx", + "lang": "en" + } +).json() +# Returns: durationMs, originalName, thumbnailFilePath, mediaType, size, extension, width, height +``` + +--- + +## Space API + +### List Spaces + +```python +spaces = requests.get(f"{API_BASE}/portal/api/v1/spaces", headers=HEADERS).json() +``` + +### Get Space + +```python +space = requests.get(f"{API_BASE}/portal/api/v1/spaces/{space_seq}", headers=HEADERS).json() +``` + +--- + +## Dubbing API (Translation) + +### Initialize Queue (required before first translation) + +```python +requests.put( + f"{API_BASE}/video-translator/api/v1/projects/spaces/{space_seq}/queue", + headers=HEADERS +) +``` + +### Request Translation + +```python +result = requests.post( + f"{API_BASE}/video-translator/api/v1/projects/spaces/{space_seq}/translate", + headers=HEADERS, + json={ + "mediaSeq": media_seq, + "isVideoProject": True, + "sourceLanguageCode": "en", + "targetLanguageCodes": ["ko", "ja"], + "numberOfSpeakers": 2, + "withLipSync": False, + "preferredSpeedType": "GREEN" + } +).json() +project_seq = result["result"]["startGenerateProjectIdList"][0] +``` + +- `mediaSeq`: the `seq` from Upload Video/Audio response +- `numberOfSpeakers`: **required** — number of speakers for multi-speaker detection +- `preferredSpeedType`: `"GREEN"` (standard) or `"RED"` (priority) +- Optional: `customDictionaryBlobPath`, `srtBlobPath` + +### Poll Progress + +```python +progress = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/space/{space_seq}/progress", + headers=HEADERS +).json() +# Returns: projectSeq, progress (0-100), progressReason, hasFailed, expectedRemainingTimeMinutes, isCancelable +``` + +### Get Project + +```python +project = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}", + headers=HEADERS +).json() +``` + +### List Projects + +```python +projects = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/spaces/{space_seq}", + headers=HEADERS, + params={"memberRole": "developer", "size": 10, "offset": 0, "sortDirection": "desc"} +).json() +``` + +### Download Files + +```python +downloads = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}/download", + headers=HEADERS, + params={"target": "all"} +).json() +# Returns download links for: videoFile, audioFile, srtFile, zippedFileDownloadLink +``` + +### Get Script + +```python +script = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}/script", + headers=HEADERS, + params={"size": 10000} +).json() +# Returns sentences with translations, matching rates, speaker info. Cursor pagination via cursorId. +``` + +### Cancel Project + +```python +requests.post( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}/cancel", + headers=HEADERS +) +# Only for GREEN zone projects in PENDING state +``` + +### Update Title / Delete / Update Access / Toggle Share + +```python +# Update title +requests.patch( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}/title", + headers=HEADERS, json={"newTitle": "New Name"} +) + +# Delete project +requests.delete( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}", + headers=HEADERS +) + +# Update access +requests.patch( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}/access", + headers=HEADERS, params={"permission": "all"} +) + +# Toggle share +requests.patch( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/share", + headers=HEADERS, params={"sharedStatus": "true"} +) +``` + +--- + +## Editing API + +Edit individual sentences within a completed translation project. + +### Translate Sentence + +```python +updated = requests.patch( + f"{API_BASE}/video-translator/api/v1/project/{project_seq}/audio-sentence/{sentence_seq}", + headers=HEADERS, + json={"targetText": "Updated translation text"} +).json() +# Returns: scriptSeq, translatedText, matchingRate, rewrite +``` + +### Generate Audio + +```python +audio = requests.patch( + f"{API_BASE}/video-translator/api/v1/project/{project_seq}/audio-sentence/{sentence_seq}/generate-audio", + headers=HEADERS, + json={"targetText": "Text to generate audio for"} +).json() +``` + +### Reset Translation + +```python +requests.put( + f"{API_BASE}/video-translator/api/v1/project/{project_seq}/audio-sentence/{sentence_seq}/reset", + headers=HEADERS +) +``` + +### Request Proofread + +```python +requests.post( + f"{API_BASE}/video-translator/api/v1/project/{project_seq}/space/{space_seq}/proofread", + headers=HEADERS, + json={"isLipSync": False, "preferredSpeedType": "GREEN"} +) +``` + +--- + +## Lip Sync API + +### Request Lip Sync + +```python +lip_sync = requests.post( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}/lip-sync", + headers=HEADERS, + json={"preferredSpeedType": "GREEN"} +).json() +``` + +### Get Generation History + +```python +history = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/{project_seq}/spaces/{space_seq}/lip-sync/generated", + headers=HEADERS, + params={"page": 1, "pageSize": 10} +).json() +``` + +--- + +## Usage API + +### Get User Quota + +```python +quota = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/spaces/{space_seq}/plan/status", + headers=HEADERS +).json() +print(f"Remaining: {quota['result']['remainingQuota']['remainingQuota']}") +``` + +### Estimate Quota Usage + +```python +estimate = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/spaces/{space_seq}/media/quota", + headers=HEADERS, + params={"mediaType": "VIDEO", "lipSync": False, "durationMs": 30000, "width": 1920, "height": 1080} +).json() +``` + +--- + +## Language API + +### List Languages + +```python +languages = requests.get( + f"{API_BASE}/video-translator/api/v1/languages", + headers=HEADERS +).json() +for lang in languages["result"]["languages"]: + print(f"{lang['code']}: {lang['name']} {'(experimental)' if lang['experiment'] else ''}") +``` + +--- + +## Feedback API + +### Submit Feedback + +```python +requests.post( + f"{API_BASE}/video-translator/api/v1/projects/feedbacks", + headers=HEADERS, + json={"projectSeq": 101, "rating": 4} +) +``` + +### Get Feedback + +```python +feedback = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/feedbacks", + headers=HEADERS, + params={"projectSeq": 101} +).json() +``` + +--- + +## Community Spotlight API + +### List Featured Projects + +```python +featured = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/recommended", + headers=HEADERS, + params={"page": 0, "size": 10, "languageCode": "ko"} +).json() +``` + +### Get Featured / Shared Project + +```python +project = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/recommended/{project_seq}", + headers=HEADERS +).json() + +shared = requests.get( + f"{API_BASE}/video-translator/api/v1/projects/shared/{shared_query}", + headers=HEADERS +).json() +``` diff --git a/content/perso/docs/package/references/endpoints-detail.md b/content/perso/docs/package/references/endpoints-detail.md new file mode 100644 index 00000000..b8fc9186 --- /dev/null +++ b/content/perso/docs/package/references/endpoints-detail.md @@ -0,0 +1,125 @@ +# Perso API — Detailed Endpoint Reference + +## Request Body Conventions + +Most endpoints use **camelCase** for request bodies. Two exceptions use **snake_case**: +- `PUT /file/api/upload/video/external` — `space_seq`, `url`, `lang` +- `POST /file/api/v1/video-translator/external/metadata` — `space_seq`, `url`, `lang` + +## Endpoint Summary + +### File API + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/file/api/upload/sas-token` | Get Azure Blob SAS token | +| PUT | `/file/api/upload/video` | Register uploaded video | +| PUT | `/file/api/upload/audio` | Register uploaded audio | +| PUT | `/file/api/upload/video/external` | Upload from YouTube/TikTok/GDrive | +| POST | `/file/api/v1/media/validate` | Pre-validate media metadata | +| POST | `/file/api/v1/video-translator/external/metadata` | Get external video metadata | + +### Space API + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/portal/api/v1/spaces` | List user's spaces | +| GET | `/portal/api/v1/spaces/{spaceSeq}` | Get space details | + +### Dubbing API + +| Method | Path | Description | +|--------|------|-------------| +| POST | `/video-translator/api/v1/projects/spaces/{spaceSeq}/translate` | Request translation | +| GET | `/video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}` | Get project | +| GET | `/video-translator/api/v1/projects/spaces/{spaceSeq}` | List projects | +| DELETE | `/video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}` | Delete project | +| PATCH | `.../projects/{projectSeq}/spaces/{spaceSeq}/title` | Update title | +| PATCH | `.../projects/{projectSeq}/spaces/{spaceSeq}/access` | Update access | +| GET | `.../projects/{projectSeq}/spaces/{spaceSeq}/script` | Get script | +| GET | `.../projects/{projectSeq}/spaces/{spaceSeq}/download` | Download files | +| GET | `.../projects/{projectSeq}/space/{spaceSeq}/progress` | Poll progress | +| POST | `.../projects/{projectSeq}/spaces/{spaceSeq}/cancel` | Cancel project | +| GET | `.../projects/{projectSeq}/share` | Get share link | +| PATCH | `.../projects/{projectSeq}/share` | Toggle sharing | +| GET | `.../projects/{projectSeq}/spaces/{spaceSeq}/video-info` | Video metadata | +| GET | `.../projects/{projectSeq}/space/{spaceSeq}/export-history` | Export history | +| GET | `.../projects/{projectSeq}/spaces/{spaceSeq}/download-info` | Download availability | +| GET | `.../projects/{projectSeq}/spaces/{spaceSeq}/retranslation/status` | Retranslation status | +| GET | `.../projects/{projectSeq}/spaces/{spaceSeq}/used-features` | Used features | + +### Editing API + +| Method | Path | Description | +|--------|------|-------------| +| PATCH | `/video-translator/api/v1/project/{projectSeq}/audio-sentence/{sentenceSeq}` | Translate sentence | +| PATCH | `.../audio-sentence/{audioSentenceSeq}/generate-audio` | Generate audio | +| PUT | `.../audio-sentence/{audioSentenceSeq}/reset` | Reset translation | +| PUT | `.../audio-sentence/{audioSentenceSeq}/cancel` | Cancel translation | +| POST | `.../audio-sentence/{audioSentenceSeq}/temp-save` | Save draft | +| POST | `.../audio-sentence/{audioSentenceSeq}/match-rewrite` | Get match rate | +| POST | `.../project/{projectSeq}/space/{spaceSeq}/proofread` | Request proofread | + +### Lip Sync API + +| Method | Path | Description | +|--------|------|-------------| +| POST | `/video-translator/api/v1/projects/{projectSeq}/spaces/{spaceSeq}/lip-sync` | Request lip sync | +| GET | `.../lip-sync/generated` | Generation history | + +### Usage API + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/video-translator/api/v1/projects/spaces/{spaceSeq}/plan/status` | Get quota | +| GET | `.../spaces/{spaceSeq}/media/quota` | Estimate quota | +| PUT | `.../spaces/{spaceSeq}/queue` | Init/get queue | + +### Language API + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/video-translator/api/v1/languages` | List supported languages | + +### Feedback API + +| Method | Path | Description | +|--------|------|-------------| +| POST | `/video-translator/api/v1/projects/feedbacks` | Submit feedback | +| GET | `/video-translator/api/v1/projects/feedbacks` | Get feedback | + +### Community Spotlight API + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/video-translator/api/v1/projects/recommended` | List featured projects | +| GET | `.../recommended/{projectSeq}` | Get featured project | +| GET | `.../shared/{sharedQuery}` | Get shared project | + +## Supported External Platforms + +| Platform | URL Patterns | +|----------|-------------| +| YouTube | `youtube.com/watch?v=`, `youtu.be/`, `youtube.com/shorts/` | +| TikTok | `tiktok.com/@{user}/video/{id}` | +| Google Drive | `drive.google.com/file/d/{id}/` | + +## Matching Rate Levels + +Translation quality is measured by matching rate: + +| Level | Type | Description | +|-------|------|-------------| +| 1 | VERY_LOW | Poor timing match | +| 2 | LOW | Below target | +| 3 | GOOD | Acceptable | +| 4 | EXCELLENT | Strong match | +| 5 | PERFECT | Optimal timing | + +## Member Roles + +| Role | Description | +|------|-------------| +| space_owner | Space owner | +| space_member | Space member | +| individual | Individual plan user | diff --git a/content/perso/docs/package/references/error-codes.md b/content/perso/docs/package/references/error-codes.md new file mode 100644 index 00000000..936197da --- /dev/null +++ b/content/perso/docs/package/references/error-codes.md @@ -0,0 +1,56 @@ +# Perso API Error Codes + +## File API Errors + +| HTTP | Code | Status | Description | +|:----:|------|--------|-------------| +| 400 | F4003 | MISSING_REQUEST_PART | Required parameter missing | +| 400 | F4004 | FILE_SIZE_LIMIT_EXCEEDED | File exceeds 2GB limit | +| 400 | F4006 | NOT_VALID_EXTERNAL_LINK | Unsupported external URL | +| 400 | F4007 | INVALID_VIDEO_TYPE | Unsupported video/audio format | +| 400 | F4008 | VIDEO_DURATION_LIMIT_EXCEEDED | Duration exceeds plan limit | +| 400 | F4009 | VIDEO_DURATION_TOO_SHORT | Duration under 5 seconds | +| 400 | F40010 | VIDEO_RESOLUTION_LIMIT_EXCEEDED | Resolution exceeds 7999×7999 | +| 400 | F40011 | VIDEO_RESOLUTION_TOO_LOW | Resolution under 201×201 | +| 400 | F40012 | EXTERNAL_VIDEO_REGION_UNAVAILABLE | Region-restricted video | +| 400 | F40013 | EXTERNAL_VIDEO_LIVE_STREAM_OFFLINE | Live stream not supported | +| 400 | F40014 | EXTERNAL_VIDEO_MEMBERS_ONLY | Membership-only content | +| 400 | F40015 | EXTERNAL_VIDEO_PAYMENT_REQUIRED | Paid content | +| 400 | F40016 | INVALID_YOUTUBE_URL | Malformed YouTube URL | +| 400 | F40017 | INVALID_MEDIA_URL | Malformed media URL | +| 400 | F40018 | INVALID_MEDIA_DATA | Corrupted or unreadable media | +| 400 | F4019 | NO_AUDIO_CHANNEL | Media has no audio track | +| 401 | F4001 | UNAUTHORIZED | Invalid or missing API key | +| 403 | F4005 | FILE_PLAN_USAGE_LIMIT_EXCEEDED | Plan quota exceeded | +| 403 | F4031 | UNACCESSIBLE_GOOGLE_DRIVE_LINK | Google Drive sharing not enabled | +| 403 | F4032 | GEO_RESTRICTED_YOUTUBE_VIDEO | Geo-restricted YouTube video | +| 403 | F4033 | AGE_RESTRICTED_YOUTUBE_VIDEO | Age-restricted YouTube video | +| 403 | F4035 | UNACCESSIBLE_EXTERNAL_MEDIA_LINK | External media access denied | +| 404 | F4040 | JOB_NOT_FOUND | Processing job not found | +| 422 | F4220 | VALIDATION_FAILED | Media validation failed | +| 500 | F5001 | INTERNAL_SERVER_ERROR | Server error | +| 500 | F5002 | DURATION_LIMIT_EXCEEDED | Credit exhaustion duration limit | + +## Video Translator API Errors + +| HTTP | Code | Status | Description | +|:----:|------|--------|-------------| +| 400 | VT4004 | BAD_REQUEST | Cancellation not allowed | +| 402 | VT4021 | PAYMENT_REQUIRED | Insufficient credits | +| 403 | VT4031 | FORBIDDEN | Access denied to project | +| 403 | VT4033 | FORBIDDEN | No access to the space | +| 404 | VT4041 | NOT_FOUND | Project not found | +| 404 | VT4042 | NOT_FOUND | Video not found | +| 404 | VT4043 | NOT_FOUND | Source language not found | +| 404 | VT4044 | NOT_FOUND | Target language not found | +| 404 | VT4045 | NOT_FOUND | Project deleted | +| 404 | VT4046 | NOT_FOUND | Project space not found | +| 409 | VT4091 | CONFLICT | Video generation failed | +| 503 | VT5034 | SERVICE_UNAVAILABLE | Translation queue full | + +## Portal API Errors + +| HTTP | Code | Status | Description | +|:----:|------|--------|-------------| +| 401 | PT0027 | UNAUTHORIZED | Not an approved space member | +| 404 | PT0026 | NOT_FOUND | Space subscription info not found |