@@ -27,6 +27,7 @@ import {
2727 TaskProviderHelper ,
2828 type TextToImageTaskHelper ,
2929 type TextToVideoTaskHelper ,
30+ type ImageToVideoTaskHelper ,
3031} from "./providerHelper.js" ;
3132import { HF_HUB_URL } from "../config.js" ;
3233import type { AutomaticSpeechRecognitionArgs } from "../tasks/audio/automaticSpeechRecognition.js" ;
@@ -35,7 +36,7 @@ import {
3536 InferenceClientProviderApiError ,
3637 InferenceClientProviderOutputError ,
3738} from "../errors.js" ;
38- import type { ImageToImageArgs } from "../tasks/index.js" ;
39+ import type { ImageToImageArgs , ImageToVideoArgs } from "../tasks/index.js" ;
3940import type { ImageSegmentationArgs } from "../tasks/cv/imageSegmentation.js" ;
4041
4142export interface FalAiQueueOutput {
@@ -329,6 +330,73 @@ export class FalAITextToVideoTask extends FalAiQueueTask implements TextToVideoT
329330 }
330331}
331332
333+ export class FalAIImageToVideoTask extends FalAiQueueTask implements ImageToVideoTaskHelper {
334+ task : InferenceTask ;
335+
336+ constructor ( ) {
337+ super ( "https://queue.fal.run" ) ;
338+ this . task = "image-to-video" ;
339+ }
340+
341+ /** Same queue routing rule as the other Fal queue tasks */
342+ override makeRoute ( params : UrlParams ) : string {
343+ return params . authMethod !== "provider-key" ? `/${ params . model } ?_subdomain=queue` : `/${ params . model } ` ;
344+ }
345+
346+ /** Synchronous case – caller already gave us base64 or a URL */
347+ override preparePayload ( params : BodyParams ) : Record < string , unknown > {
348+ return {
349+ ...omit ( params . args , [ "inputs" , "parameters" ] ) ,
350+ ...( params . args . parameters as Record < string , unknown > ) ,
351+ // args.inputs is expected to be a base64 data URI or an URL
352+ image_url : params . args . image_url ,
353+ } ;
354+ }
355+
356+ /** Asynchronous helper – caller gave us a Blob */
357+ async preparePayloadAsync ( args : ImageToVideoArgs ) : Promise < RequestArgs > {
358+ const mimeType = args . inputs instanceof Blob ? args . inputs . type : "image/png" ;
359+ return {
360+ ...omit ( args , [ "inputs" , "parameters" ] ) ,
361+ image_url : `data:${ mimeType } ;base64,${ base64FromBytes (
362+ new Uint8Array ( args . inputs instanceof ArrayBuffer ? args . inputs : await ( args . inputs as Blob ) . arrayBuffer ( ) )
363+ ) } `,
364+ ...args . parameters ,
365+ ...args ,
366+ } ;
367+ }
368+
369+ /** Queue polling + final download – mirrors Text‑to‑Video */
370+ override async getResponse (
371+ response : FalAiQueueOutput ,
372+ url ?: string ,
373+ headers ?: Record < string , string >
374+ ) : Promise < Blob > {
375+ const result = await this . getResponseFromQueueApi ( response , url , headers ) ;
376+
377+ if (
378+ typeof result === "object" &&
379+ result !== null &&
380+ "video" in result &&
381+ typeof result . video === "object" &&
382+ result . video !== null &&
383+ "url" in result . video &&
384+ typeof result . video . url === "string" &&
385+ "url" in result . video &&
386+ isUrl ( result . video . url )
387+ ) {
388+ const urlResponse = await fetch ( result . video . url ) ;
389+ return await urlResponse . blob ( ) ;
390+ }
391+
392+ throw new InferenceClientProviderOutputError (
393+ `Received malformed response from Fal.ai image‑to‑video API: expected { video: { url: string } }, got: ${ JSON . stringify (
394+ result
395+ ) } `
396+ ) ;
397+ }
398+ }
399+
332400export class FalAIAutomaticSpeechRecognitionTask extends FalAITask implements AutomaticSpeechRecognitionTaskHelper {
333401 override prepareHeaders ( params : HeaderParams , binary : boolean ) : Record < string , string > {
334402 const headers = super . prepareHeaders ( params , binary ) ;
0 commit comments