diff --git a/src/constants.ts b/src/constants.ts index a54fed9..7332e68 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -36,6 +36,8 @@ export const ApiPath = { scenes: 'scenes', timeline: 'timeline', frame: 'frame', + generate: 'generate', + dub: 'dub', } as const; export const ResponseStatus = { diff --git a/src/core/collection.ts b/src/core/collection.ts index 49cb4ec..1e7d9bf 100644 --- a/src/core/collection.ts +++ b/src/core/collection.ts @@ -7,7 +7,14 @@ import type { ImageBase, } from '@/interfaces/core'; import { IndexType, SearchType } from '@/types/search'; -import type { FileUploadConfig, URLUploadConfig } from '@/types/collection'; +import type { + DubVideoConfig, + FileUploadConfig, + GenerateAudioConfig, + GenerateImageConfig, + GenerateVideoConfig, + URLUploadConfig, +} from '@/types/collection'; import type { GetVideos, GetAudios, @@ -25,6 +32,12 @@ import { Video } from './video'; import { Audio } from './audio'; import { Image } from './image'; import { VideodbError } from '@/utils/error'; +import { + DubVideoJob, + GenerateAudioJob, + GenerateImageJob, + GenerateVideoJob, +} from '@/utils/job'; const { video, audio, image } = ApiPath; @@ -237,4 +250,289 @@ export class Collection implements ICollection { }); return results; }; + + /** + * Generate an image from a prompt. + * @param prompt - Prompt for the image generation + * @param aspectRatio - Aspect ratio of the image (default: '1:1') + * @param callbackUrl - Optional URL to receive a callback when generation is complete + * @returns An Image object containing the generated image metadata + */ + public generateImage = async ( + prompt: string, + aspectRatio: '1:1' | '9:16' | '16:9' | '4:3' | '3:4' = '1:1', + callbackUrl?: string + ) => { + const generateImageConfig: GenerateImageConfig = { + prompt, + aspect_ratio: aspectRatio, + callback_url: callbackUrl, + }; + if (generateImageConfig.callback_url) { + const res = await this.#vhttp.post( + [ + ApiPath.collection, + this.meta.id, + ApiPath.generate, + ApiPath.image, + ] as string[], + { + prompt, + aspect_ratio: aspectRatio, + callback_url: callbackUrl, + } + ); + } else { + const job = new GenerateImageJob( + generateImageConfig, + this.meta.id, + this.#vhttp + ); + return job; + } + }; + + /** + * Generate music from a prompt. + * @param prompt - Prompt for the music generation + * @param duration - Duration of the music in seconds (default: 5) + * @param callbackUrl - Optional URL to receive a callback when generation is complete + * @returns An Audio object containing the generated music metadata + */ + public generateMusic = async ( + prompt: string, + duration: number = 5, + callbackUrl?: string + ) => { + const generateAudioConfig: GenerateAudioConfig = { + prompt, + duration, + audio_type: 'music', + callback_url: callbackUrl, + }; + + if (generateAudioConfig.callback_url) { + await this.#vhttp.post( + [ + ApiPath.collection, + this.meta.id, + ApiPath.generate, + ApiPath.audio, + ] as string[], + generateAudioConfig + ); + } else { + const job = new GenerateAudioJob( + generateAudioConfig, + this.meta.id, + this.#vhttp + ); + return job; + } + }; + + /** + * Generate a sound effect from a prompt. + * @param prompt - Prompt for the sound effect generation + * @param duration - Duration of the sound effect in seconds (default: 2) + * @param config - Optional configuration for the sound effect generation + * @param callbackUrl - Optional URL to receive a callback when generation is complete + * @returns An Audio object containing the generated sound effect metadata + */ + public generateSoundEffect = async ( + prompt: string, + duration: number = 2, + config: Record = {}, + callbackUrl?: string + ) => { + const generateAudioConfig: GenerateAudioConfig = { + prompt, + duration, + audio_type: 'sound_effect', + config, + callback_url: callbackUrl, + }; + + if (generateAudioConfig.callback_url) { + await this.#vhttp.post( + [ + ApiPath.collection, + this.meta.id, + ApiPath.generate, + ApiPath.audio, + ] as string[], + generateAudioConfig + ); + } else { + const job = new GenerateAudioJob( + generateAudioConfig, + this.meta.id, + this.#vhttp + ); + return job; + } + }; + + /** + * Generate a voice audio from text. + * @param text - Text to convert to voice + * @param voiceName - Name of the voice to use (default: 'Default') + * @param config - Optional configuration for the voice generation + * @param callbackUrl - Optional URL to receive a callback when generation is complete + * @returns An Audio object containing the generated voice metadata + */ + public generateVoice = async ( + text: string, + voiceName: string = 'Default', + config: Record = {}, + callbackUrl?: string + ) => { + const generateVoiceConfig: GenerateAudioConfig = { + text, + voice_name: voiceName, + config, + callback_url: callbackUrl, + audio_type: 'voice', + }; + + if (generateVoiceConfig.callback_url) { + await this.#vhttp.post( + [ + ApiPath.collection, + this.meta.id, + ApiPath.generate, + ApiPath.audio, + ] as string[], + generateVoiceConfig + ); + } else { + return new GenerateAudioJob( + generateVoiceConfig, + this.meta.id, + this.#vhttp + ); + } + }; + + /** + * Generate a video from a prompt. + * @param prompt - Prompt for the video generation + * @param duration - Duration of the video in seconds (default: 5) + * @param callbackUrl - Optional URL to receive a callback when generation is complete + * @returns A Video object containing the generated video metadata + */ + public generateVideo = async ( + prompt: string, + duration: number = 5, + callbackUrl?: string + ) => { + const generateVideoConfig: GenerateVideoConfig = { + prompt, + duration, + callback_url: callbackUrl, + }; + + if (generateVideoConfig.callback_url) { + await this.#vhttp.post( + [ + ApiPath.collection, + this.meta.id, + ApiPath.generate, + ApiPath.video, + ] as string[], + { + prompt, + duration, + callback_url: callbackUrl, + } + ); + } else { + const job = new GenerateVideoJob( + generateVideoConfig, + this.meta.id, + this.#vhttp + ); + return job; + } + }; + + /** + * Dub a video in a different language. + * @param videoId - ID of the video to dub + * @param languageCode - Language code to dub the video to + * @param callbackUrl - Optional URL to receive a callback when dubbing is complete + * @returns A Video object containing the dubbed video metadata + */ + public dubVideo = async ( + videoId: string, + languageCode: string, + callbackUrl?: string + ) => { + const dubVideoConfig: DubVideoConfig = { + video_id: videoId, + language_code: languageCode, + callback_url: callbackUrl, + }; + + if (dubVideoConfig.callback_url) { + await this.#vhttp.post( + [ + ApiPath.collection, + this.meta.id, + ApiPath.generate, + ApiPath.video, + ApiPath.dub, + ] as string[], + dubVideoConfig + ); + } else { + return new DubVideoJob(dubVideoConfig, this.meta.id, this.#vhttp); + } + }; + + /** + * Search for videos by title using LLM search. + * @param query - Query string to search for in video titles + * @returns An array of objects each containing a Video instance matching the title + */ + public searchTitle = async (query: string): Promise<{ video: Video }[]> => { + const res = await this.#vhttp.post( + [ApiPath.collection, this.meta.id, ApiPath.search, 'title'] as string[], + { + query, + search_type: 'llm', + } + ); + if (Array.isArray(res?.data)) { + return res.data.map((result: { video: VideoBase }) => ({ + video: new Video( + this.#vhttp, + fromSnakeToCamel(result.video) as VideoBase + ), + })); + } + return []; + }; + + /** + * Make the collection public. + * @returns A promise that resolves when the collection is made public + */ + public makePublic = async (): Promise => { + await this.#vhttp.patch([ApiPath.collection, this.meta.id] as string[], { + is_public: true, + }); + this.meta.isPublic = true; + }; + + /** + * Make the collection private. + * @returns A promise that resolves when the collection is made private + */ + public makePrivate = async (): Promise => { + await this.#vhttp.patch([ApiPath.collection, this.meta.id] as string[], { + is_public: false, + }); + this.meta.isPublic = false; + }; } diff --git a/src/core/video.ts b/src/core/video.ts index 66ecb22..cddbcaf 100644 --- a/src/core/video.ts +++ b/src/core/video.ts @@ -280,7 +280,6 @@ export class Video implements IVideo { const indexScenesPayload = fromCamelToSnake( Object.assign({}, defaultConfig, config) ); - console.log('this is payload', indexScenesPayload); const res = await this.#vhttp.post( [video, this.meta.id, index, scene], indexScenesPayload diff --git a/src/interfaces/core.ts b/src/interfaces/core.ts index ecd93fc..726b2c0 100644 --- a/src/interfaces/core.ts +++ b/src/interfaces/core.ts @@ -14,6 +14,7 @@ export interface CollectionBase { id: string; name?: string; description?: string; + isPublic?: boolean; } /** * Collection class interface for reference diff --git a/src/types/collection.ts b/src/types/collection.ts index 2db19bd..e70ac04 100644 --- a/src/types/collection.ts +++ b/src/types/collection.ts @@ -16,3 +16,65 @@ export interface URLUploadConfig extends CommonUploadConfig { export type UploadConfig = FileUploadConfig | URLUploadConfig; export type SyncUploadConfig = Omit; + +/** + * Generate a video from a prompt. + * @param prompt - Prompt for the video generation + * @param duration - Duration of the video in seconds (default: 5) + * @param callback_url - Optional URL to receive a callback when generation is complete + * @returns A Video object containing the generated video metadata + */ + +export interface GenerateVideoConfig { + prompt: string; + duration?: number; + callback_url?: string; +} + +export type SyncGenerateVideoConfig = Omit; + +export interface GenerateImageConfig { + prompt: string; + aspect_ratio: string; + callback_url?: string; +} + +export type SyncGenerateImageConfig = Omit; + +export interface GenerateMusicConfig { + prompt: string; + duration?: number; + audio_type?: 'music'; + callback_url?: string; +} + +export interface GenerateSoundEffectConfig { + prompt: string; + duration?: number; + audio_type?: 'sound_effect'; + config?: object; + callback_url?: string; +} + +export interface GenerateVoiceConfig { + text: string; + voice_name?: string; + audio_type?: 'voice'; + config?: object; + callback_url?: string; +} + +export type GenerateAudioConfig = + | GenerateMusicConfig + | GenerateSoundEffectConfig + | GenerateVoiceConfig; + +export type SyncGenerateAudioConfig = Omit; + +export interface DubVideoConfig { + video_id: string; + language_code: string; + callback_url?: string; +} + +export type SyncDubVideoConfig = Omit; diff --git a/src/utils/job.ts b/src/utils/job.ts index 72ab9af..3bda801 100644 --- a/src/utils/job.ts +++ b/src/utils/job.ts @@ -3,7 +3,13 @@ import { Video } from '@/core/video'; import { Audio } from '@/core/audio'; import { Image } from '@/core/image'; import type { AudioBase, ImageBase, VideoBase } from '@/interfaces/core'; -import type { SyncUploadConfig } from '@/types/collection'; +import type { + SyncDubVideoConfig, + SyncGenerateAudioConfig, + SyncGenerateImageConfig, + SyncGenerateVideoConfig, + SyncUploadConfig, +} from '@/types/collection'; import type { MediaBase, SceneIndexRecords } from '@/types/index'; import type { ExtractSceneConfig, IndexConfig } from '@/types/config'; import type { IndexType } from '@/types/search'; @@ -13,6 +19,9 @@ import type { TranscriptResponse, MediaResponse, GetSceneIndexResponse, + VideoResponse, + AudioResponse, + ImageResponse, } from '@/types/response'; import type { JobErrorCallback, JobSuccessCallback } from '@/types/utils'; import type { Transcript } from '@/types/video'; @@ -27,8 +36,19 @@ import { IndexSceneConfig } from '@/types/config'; import { IndexTypeValues } from '@/core/config'; const { in_progress, processing } = ResponseStatus; -const { video, transcription, collection, upload, index, scene, scenes } = - ApiPath; +const { + video, + transcription, + collection, + upload, + index, + scene, + scenes, + generate, + audio, + image, + dub, +} = ApiPath; /** * Base Job class used to create different kinds of jobs @@ -406,3 +426,187 @@ export class ExtractScenesJob extends Job { } }; } + +/** + * GenerateVideoJob is used to initialize a new video generation job. + * + * @remarks + * Uses the base Job class to implement a backoff to get the generated video data. + */ +export class GenerateVideoJob extends Job { + public uploadData: SyncGenerateVideoConfig; + public collectionId: string; + constructor( + data: SyncGenerateVideoConfig, + collectionId: string, + http: HttpClient + ) { + super(http); + this.uploadData = data; + this.collectionId = collectionId; + this.jobTitle = 'Generate Video Job'; + } + + /** + * Fetches the callbackURL from the server and initiates a backoff + */ + public start = async () => { + try { + const res = await this.vhttp.post< + SyncJobResponse, + SyncGenerateVideoConfig + >([collection, this.collectionId, generate, video], this.uploadData); + + void this._initiateBackoff(res.data.output_url); + } catch (err) { + this._handleError(err); + } + }; + + /** + * Converts the API response to a Video instance. + * @param data - Video data returned from the API and converted to camelCase + * @returns a new Video object + */ + protected beforeSuccess = (data: VideoBase) => { + return new Video(this.vhttp, data); + }; +} + +/** + * GenerateAudioJob is used to initialize a new audio generation job (music, sound effect, or voice). + * + * @remarks + * Uses the base Job class to implement a backoff to get the generated audio data. + */ +export class GenerateAudioJob extends Job { + public uploadData: SyncGenerateAudioConfig; + public collectionId: string; + constructor( + data: SyncGenerateAudioConfig, + collectionId: string, + http: HttpClient + ) { + super(http); + this.uploadData = data; + this.collectionId = collectionId; + this.jobTitle = 'Generate Audio Job'; + } + + /** + * Fetches the callbackURL from the server and initiates a backoff + */ + public start = async () => { + try { + const res = await this.vhttp.post< + SyncJobResponse, + SyncGenerateAudioConfig + >([collection, this.collectionId, generate, audio], this.uploadData); + + void this._initiateBackoff(res.data.output_url); + } catch (err) { + this._handleError(err); + } + }; + + /** + * Converts the API response to an Audio instance. + * @param data - Audio data returned from the API and converted to camelCase + * @returns a new Audio object + */ + protected beforeSuccess = (data: AudioBase) => { + return new Audio(this.vhttp, data); + }; +} + +/** + * GenerateImageJob is used to initialize a new image generation job. + * + * @remarks + * Uses the base Job class to implement a backoff to get the generated image data. + */ +export class GenerateImageJob extends Job { + public uploadData: SyncGenerateImageConfig; + public collectionId: string; + constructor( + data: SyncGenerateImageConfig, + collectionId: string, + http: HttpClient + ) { + super(http); + this.uploadData = data; + this.collectionId = collectionId; + this.jobTitle = 'Generate Image Job'; + } + + /** + * Fetches the callbackURL from the server and initiates a backoff + */ + public start = async () => { + try { + const res = await this.vhttp.post< + SyncJobResponse, + SyncGenerateImageConfig + >([collection, this.collectionId, generate, image], this.uploadData); + + void this._initiateBackoff(res.data.output_url); + } catch (err) { + this._handleError(err); + } + }; + + /** + * Converts the API response to an Image instance. + * @param data - Image data returned from the API and converted to camelCase + * @returns a new Image object + */ + protected beforeSuccess = (data: ImageBase) => { + return new Image(this.vhttp, data); + }; +} + +/** + * DubVideoJob is used to initialize a new video dubbing job. + * + * @remarks + * Uses the base Job class to implement a backoff to get the dubbed video data. + */ +export class DubVideoJob extends Job { + public uploadData: SyncDubVideoConfig; + public collectionId: string; + constructor( + data: SyncDubVideoConfig, + collectionId: string, + http: HttpClient + ) { + super(http); + this.uploadData = data; + this.collectionId = collectionId; + this.jobTitle = 'Dub Video Job'; + } + + /** + * Fetches the callbackURL from the server and initiates a backoff + */ + public start = async () => { + try { + const res = await this.vhttp.post( + [collection, this.collectionId, generate, video, dub], + this.uploadData + ); + + void this._initiateBackoff(res.data.output_url); + } catch (err) { + this._handleError(err); + } + }; + + /** + * Converts the API response to a Video instance. + * @param data - Video data returned from the API and converted to camelCase + * @returns a new Video object + */ + protected beforeSuccess = (data: VideoBase) => { + return new Video(this.vhttp, data); + }; +}