@@ -5,6 +5,8 @@ import type { OvsConfig, VideoProviderConfig } from '@orkas/video-studio-core';
55
66const ARK_DEFAULT_BASE = 'https://ark.cn-beijing.volces.com/api/v3' ;
77const DEFAULT_MODEL = 'doubao-seedance-2-0-260128' ;
8+ const ATLAS_DEFAULT_BASE = 'https://api.atlascloud.ai/api/v1' ;
9+ const ATLAS_DEFAULT_MODEL = 'bytedance/seedance-2.0/text-to-video' ;
810const POLL_INTERVAL_MS = 10_000 ;
911const POLL_TIMEOUT_MS = 30_000 ; // per-poll request timeout — one slow poll must not fail the task
1012const TASK_TIMEOUT_MS = 60 * 60 * 1000 ;
@@ -38,6 +40,38 @@ function arkBase(cfg: VideoProviderConfig): string {
3840 return ( cfg . base_url ?? ARK_DEFAULT_BASE ) . replace ( / \/ + $ / , '' ) ;
3941}
4042
43+ function atlasBase ( cfg : VideoProviderConfig ) : string {
44+ return ( cfg . base_url ?? ATLAS_DEFAULT_BASE ) . replace ( / \/ + $ / , '' ) ;
45+ }
46+
47+ /** Build an Atlas Cloud media task request (`POST {base}/model/generateVideo`). */
48+ export function buildAtlasCreateRequest ( cfg : VideoProviderConfig , p : VideoParams ) : ProviderRequest {
49+ if ( ! cfg . api_key ) throw new Error ( 'video: no api_key configured' ) ;
50+ if ( p . operation !== undefined && p . operation !== 'generate' ) {
51+ throw new Error ( 'video: Atlas Cloud currently supports the generate operation' ) ;
52+ }
53+ if ( p . reference_video_urls ?. length || p . reference_image_urls ?. length ) {
54+ throw new Error ( 'video: Atlas Cloud accepts a single first-frame image_url; additional references are not supported' ) ;
55+ }
56+ const duration = p . duration ?? 5 ;
57+ if ( ! Number . isFinite ( duration ) || duration < 4 || duration > 15 ) {
58+ throw new Error ( 'video: duration must be between 4 and 15 seconds' ) ;
59+ }
60+ return {
61+ url : `${ atlasBase ( cfg ) } /model/generateVideo` ,
62+ headers : { authorization : `Bearer ${ cfg . api_key } ` , 'content-type' : 'application/json' } ,
63+ body : {
64+ model : p . model ?? cfg . model ?? ATLAS_DEFAULT_MODEL ,
65+ prompt : p . prompt ,
66+ duration,
67+ resolution : p . resolution ?? '720p' ,
68+ ratio : p . ratio ?? '16:9' ,
69+ generate_audio : p . generate_audio !== false ,
70+ ...( p . image_url ? { image : p . image_url } : { } ) ,
71+ } ,
72+ } ;
73+ }
74+
4175/** Build the Doubao Seedance task-create request (`POST {base}/contents/generations/tasks`). */
4276export function buildSeedanceCreateRequest ( cfg : VideoProviderConfig , p : VideoParams ) : ProviderRequest {
4377 if ( ! cfg . api_key ) throw new Error ( 'video: no api_key configured' ) ;
@@ -88,6 +122,16 @@ interface PollResp {
88122 content ?: { video_url ?: string } ;
89123 error ?: { message ?: string } ;
90124}
125+ interface AtlasResp {
126+ code ?: number ;
127+ data ?: {
128+ id ?: string ;
129+ status ?: string ;
130+ outputs ?: string [ ] ;
131+ output ?: string | string [ ] ;
132+ error ?: string ;
133+ } ;
134+ }
91135
92136const sleep = ( ms : number ) => new Promise < void > ( ( r ) => setTimeout ( r , ms ) ) ;
93137
@@ -129,7 +173,7 @@ export function validateDownloadedVideo(buffer: Buffer): void {
129173}
130174
131175/**
132- * Generate a video with the configured BYO provider (Doubao Seedance ): create an
176+ * Generate a video with the configured BYO provider (Doubao or Atlas Cloud ): create an
133177 * async task, poll until it succeeds, then download the result. Text-to-video by
134178 * default; pass a PUBLIC `image_url` for image-to-video.
135179 */
@@ -141,20 +185,31 @@ export async function generateVideo(params: VideoParams, config: OvsConfig = loa
141185 const now = opts . now ?? Date . now ;
142186 const interval = opts . pollIntervalMs ?? POLL_INTERVAL_MS ;
143187
144- const req = buildSeedanceCreateRequest ( cfg , params ) ;
145- const created = ( await postJson ( req . url , req . body , req . headers , POLL_TIMEOUT_MS ) ) as CreateResp ;
146- const id = created . id ;
188+ const provider = cfg . provider ?? 'doubao' ;
189+ const req = provider === 'atlas' ? buildAtlasCreateRequest ( cfg , params ) : buildSeedanceCreateRequest ( cfg , params ) ;
190+ const created = ( await postJson ( req . url , req . body , req . headers , POLL_TIMEOUT_MS ) ) as CreateResp & AtlasResp ;
191+ const id = provider === 'atlas' ? created . data ?. id : created . id ;
147192 if ( ! id ) throw new Error ( 'video: task create returned no id' ) ;
148193
149- const base = arkBase ( cfg ) ;
194+ const base = provider === 'atlas' ? atlasBase ( cfg ) : arkBase ( cfg ) ;
150195 const authHeaders = { authorization : `Bearer ${ cfg . api_key } ` } ;
151196 const start = now ( ) ;
152197
153198 for ( ; ; ) {
154199 if ( now ( ) - start > TASK_TIMEOUT_MS ) throw new Error ( `video: task ${ id } timed out after ${ TASK_TIMEOUT_MS } ms` ) ;
155- const poll = ( await getJson ( `${ base } /contents/generations/tasks/${ id } ` , authHeaders , POLL_TIMEOUT_MS ) ) as PollResp ;
156- if ( poll . status === 'succeeded' ) {
157- const url = poll . content ?. video_url ;
200+ const pollUrl = provider === 'atlas'
201+ ? `${ base } /model/prediction/${ id } `
202+ : `${ base } /contents/generations/tasks/${ id } ` ;
203+ const response = ( await getJson ( pollUrl , authHeaders , POLL_TIMEOUT_MS ) ) as PollResp & AtlasResp ;
204+ const atlasPoll = provider === 'atlas' ? response . data : undefined ;
205+ const doubaoPoll = provider === 'atlas' ? undefined : response ;
206+ const status = atlasPoll ?. status ?? doubaoPoll ?. status ;
207+ const succeeded = status === 'succeeded' || status === 'completed' ;
208+ if ( succeeded ) {
209+ const atlasOutput = provider === 'atlas'
210+ ? ( Array . isArray ( atlasPoll ?. output ) ? atlasPoll . output [ 0 ] : atlasPoll ?. output ) ?? atlasPoll ?. outputs ?. [ 0 ]
211+ : undefined ;
212+ const url = provider === 'atlas' ? atlasOutput : doubaoPoll ?. content ?. video_url ;
158213 if ( ! url ) throw new Error ( `video: task ${ id } succeeded but returned no video_url` ) ;
159214 const dl = await fetchWithTimeout ( url , { method : 'GET' , timeoutMs : DOWNLOAD_TIMEOUT_MS } ) ;
160215 if ( ! dl . ok ) throw new Error ( `video download failed with HTTP ${ dl . status } ` ) ;
@@ -171,8 +226,8 @@ export async function generateVideo(params: VideoParams, config: OvsConfig = loa
171226 }
172227 return { output : resolve ( params . output ) , bytes : buf . byteLength , task_id : id } ;
173228 }
174- if ( poll . status === 'failed' || poll . status === 'canceled' ) {
175- throw new Error ( `video: task ${ id } ${ poll . status } ` ) ;
229+ if ( status === 'failed' || status === 'canceled' ) {
230+ throw new Error ( `video: task ${ id } ${ status } ` ) ;
176231 }
177232 await sleep ( interval ) ;
178233 }
0 commit comments