@weirdfingers/baseboards 0.9.6 → 0.9.7
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/dist/index.js +560 -469
- package/dist/index.js.map +1 -1
- package/package.json +2 -5
- package/templates/README.md +0 -122
- package/templates/api/.env.example +0 -65
- package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +0 -148
- package/templates/api/Dockerfile +0 -32
- package/templates/api/README.md +0 -264
- package/templates/api/alembic/env.py +0 -114
- package/templates/api/alembic/script.py.mako +0 -28
- package/templates/api/alembic/versions/20250101_000000_initial_schema.py +0 -506
- package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +0 -75
- package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +0 -467
- package/templates/api/alembic/versions/20251202_000000_add_artifact_lineage.py +0 -134
- package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +0 -88
- package/templates/api/alembic.ini +0 -36
- package/templates/api/config/generators.yaml +0 -237
- package/templates/api/config/storage_config.yaml +0 -26
- package/templates/api/docs/ADDING_GENERATORS.md +0 -409
- package/templates/api/docs/GENERATORS_API.md +0 -502
- package/templates/api/docs/MIGRATIONS.md +0 -472
- package/templates/api/docs/TESTING_LIVE_APIS.md +0 -417
- package/templates/api/docs/storage_providers.md +0 -337
- package/templates/api/pyproject.toml +0 -205
- package/templates/api/src/boards/__init__.py +0 -10
- package/templates/api/src/boards/api/app.py +0 -172
- package/templates/api/src/boards/api/auth.py +0 -75
- package/templates/api/src/boards/api/endpoints/__init__.py +0 -3
- package/templates/api/src/boards/api/endpoints/jobs.py +0 -76
- package/templates/api/src/boards/api/endpoints/setup.py +0 -505
- package/templates/api/src/boards/api/endpoints/sse.py +0 -129
- package/templates/api/src/boards/api/endpoints/storage.py +0 -155
- package/templates/api/src/boards/api/endpoints/tenant_registration.py +0 -296
- package/templates/api/src/boards/api/endpoints/uploads.py +0 -149
- package/templates/api/src/boards/api/endpoints/webhooks.py +0 -13
- package/templates/api/src/boards/auth/__init__.py +0 -15
- package/templates/api/src/boards/auth/adapters/__init__.py +0 -27
- package/templates/api/src/boards/auth/adapters/auth0.py +0 -220
- package/templates/api/src/boards/auth/adapters/base.py +0 -73
- package/templates/api/src/boards/auth/adapters/clerk.py +0 -172
- package/templates/api/src/boards/auth/adapters/jwt.py +0 -122
- package/templates/api/src/boards/auth/adapters/none.py +0 -102
- package/templates/api/src/boards/auth/adapters/oidc.py +0 -284
- package/templates/api/src/boards/auth/adapters/supabase.py +0 -110
- package/templates/api/src/boards/auth/context.py +0 -35
- package/templates/api/src/boards/auth/factory.py +0 -129
- package/templates/api/src/boards/auth/middleware.py +0 -221
- package/templates/api/src/boards/auth/provisioning.py +0 -129
- package/templates/api/src/boards/auth/tenant_extraction.py +0 -278
- package/templates/api/src/boards/cli.py +0 -354
- package/templates/api/src/boards/config.py +0 -131
- package/templates/api/src/boards/database/__init__.py +0 -7
- package/templates/api/src/boards/database/cli.py +0 -110
- package/templates/api/src/boards/database/connection.py +0 -292
- package/templates/api/src/boards/database/models.py +0 -19
- package/templates/api/src/boards/database/seed_data.py +0 -182
- package/templates/api/src/boards/dbmodels/__init__.py +0 -441
- package/templates/api/src/boards/generators/__init__.py +0 -57
- package/templates/api/src/boards/generators/artifact_resolution.py +0 -405
- package/templates/api/src/boards/generators/artifacts.py +0 -53
- package/templates/api/src/boards/generators/base.py +0 -144
- package/templates/api/src/boards/generators/implementations/__init__.py +0 -14
- package/templates/api/src/boards/generators/implementations/fal/__init__.py +0 -25
- package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +0 -23
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_music_generation.py +0 -171
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_sound_effect_generation.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_text_to_speech.py +0 -176
- package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_tts_turbo.py +0 -195
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_sound_effects_v2.py +0 -194
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_tts_eleven_v3.py +0 -209
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_elevenlabs_tts_turbo_v2_5.py +0 -206
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_minimax_speech_26_hd.py +0 -237
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +0 -63
- package/templates/api/src/boards/generators/implementations/fal/image/bytedance_seedream_v45_edit.py +0 -219
- package/templates/api/src/boards/generators/implementations/fal/image/clarity_upscaler.py +0 -220
- package/templates/api/src/boards/generators/implementations/fal/image/crystal_upscaler.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/image/fal_ideogram_character.py +0 -227
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2.py +0 -203
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_edit.py +0 -230
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro.py +0 -204
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro_edit.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +0 -216
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +0 -197
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image.py +0 -177
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image_edit.py +0 -208
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_15_edit.py +0 -216
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_5.py +0 -177
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_edit_image.py +0 -182
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_mini.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_character_edit.py +0 -299
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_v2.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +0 -191
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +0 -179
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +0 -183
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +0 -212
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro.py +0 -179
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro_edit.py +0 -226
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image.py +0 -249
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image_edit.py +0 -244
- package/templates/api/src/boards/generators/implementations/fal/image/reve_edit.py +0 -178
- package/templates/api/src/boards/generators/implementations/fal/image/reve_text_to_image.py +0 -155
- package/templates/api/src/boards/generators/implementations/fal/image/seedream_v45_text_to_image.py +0 -180
- package/templates/api/src/boards/generators/implementations/fal/utils.py +0 -61
- package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +0 -77
- package/templates/api/src/boards/generators/implementations/fal/video/bytedance_seedance_v1_pro_text_to_video.py +0 -209
- package/templates/api/src/boards/generators/implementations/fal/video/creatify_lipsync.py +0 -161
- package/templates/api/src/boards/generators/implementations/fal/video/fal_bytedance_seedance_v1_pro_image_to_video.py +0 -222
- package/templates/api/src/boards/generators/implementations/fal/video/fal_minimax_hailuo_02_standard_text_to_video.py +0 -152
- package/templates/api/src/boards/generators/implementations/fal/video/fal_pixverse_lipsync.py +0 -197
- package/templates/api/src/boards/generators/implementations/fal/video/fal_sora_2_text_to_video.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/video/infinitalk.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_pro.py +0 -168
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_standard.py +0 -159
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_image_to_video.py +0 -175
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +0 -168
- package/templates/api/src/boards/generators/implementations/fal/video/minimax_hailuo_2_3_pro_image_to_video.py +0 -153
- package/templates/api/src/boards/generators/implementations/fal/video/sora2_image_to_video.py +0 -172
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_image_to_video_pro.py +0 -175
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_text_to_video_pro.py +0 -163
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2_pro.py +0 -155
- package/templates/api/src/boards/generators/implementations/fal/video/veed_fabric_1_0.py +0 -180
- package/templates/api/src/boards/generators/implementations/fal/video/veed_lipsync.py +0 -174
- package/templates/api/src/boards/generators/implementations/fal/video/veo3.py +0 -194
- package/templates/api/src/boards/generators/implementations/fal/video/veo31.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast_image_to_video.py +0 -191
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +0 -187
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_image_to_video.py +0 -183
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_reference_to_video.py +0 -172
- package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_image_to_video.py +0 -212
- package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_text_to_video.py +0 -208
- package/templates/api/src/boards/generators/implementations/fal/video/wan_pro_image_to_video.py +0 -158
- package/templates/api/src/boards/generators/implementations/kie/__init__.py +0 -11
- package/templates/api/src/boards/generators/implementations/kie/base.py +0 -316
- package/templates/api/src/boards/generators/implementations/kie/image/__init__.py +0 -3
- package/templates/api/src/boards/generators/implementations/kie/image/nano_banana_edit.py +0 -190
- package/templates/api/src/boards/generators/implementations/kie/utils.py +0 -98
- package/templates/api/src/boards/generators/implementations/kie/video/__init__.py +0 -8
- package/templates/api/src/boards/generators/implementations/kie/video/veo3.py +0 -161
- package/templates/api/src/boards/generators/implementations/openai/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/audio/whisper.py +0 -69
- package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/image/dalle3.py +0 -96
- package/templates/api/src/boards/generators/implementations/replicate/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/image/flux_pro.py +0 -88
- package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/video/lipsync.py +0 -73
- package/templates/api/src/boards/generators/loader.py +0 -253
- package/templates/api/src/boards/generators/registry.py +0 -114
- package/templates/api/src/boards/generators/resolution.py +0 -632
- package/templates/api/src/boards/generators/testmods/class_gen.py +0 -34
- package/templates/api/src/boards/generators/testmods/import_side_effect.py +0 -35
- package/templates/api/src/boards/graphql/__init__.py +0 -7
- package/templates/api/src/boards/graphql/access_control.py +0 -136
- package/templates/api/src/boards/graphql/mutations/root.py +0 -148
- package/templates/api/src/boards/graphql/queries/root.py +0 -116
- package/templates/api/src/boards/graphql/resolvers/__init__.py +0 -8
- package/templates/api/src/boards/graphql/resolvers/auth.py +0 -12
- package/templates/api/src/boards/graphql/resolvers/board.py +0 -1053
- package/templates/api/src/boards/graphql/resolvers/generation.py +0 -666
- package/templates/api/src/boards/graphql/resolvers/generator.py +0 -50
- package/templates/api/src/boards/graphql/resolvers/lineage.py +0 -381
- package/templates/api/src/boards/graphql/resolvers/upload.py +0 -463
- package/templates/api/src/boards/graphql/resolvers/user.py +0 -25
- package/templates/api/src/boards/graphql/schema.py +0 -81
- package/templates/api/src/boards/graphql/types/board.py +0 -102
- package/templates/api/src/boards/graphql/types/generation.py +0 -166
- package/templates/api/src/boards/graphql/types/generator.py +0 -17
- package/templates/api/src/boards/graphql/types/user.py +0 -47
- package/templates/api/src/boards/jobs/repository.py +0 -153
- package/templates/api/src/boards/logging.py +0 -195
- package/templates/api/src/boards/middleware.py +0 -339
- package/templates/api/src/boards/progress/__init__.py +0 -4
- package/templates/api/src/boards/progress/models.py +0 -25
- package/templates/api/src/boards/progress/publisher.py +0 -64
- package/templates/api/src/boards/py.typed +0 -0
- package/templates/api/src/boards/redis_pool.py +0 -118
- package/templates/api/src/boards/storage/__init__.py +0 -52
- package/templates/api/src/boards/storage/base.py +0 -363
- package/templates/api/src/boards/storage/config.py +0 -187
- package/templates/api/src/boards/storage/factory.py +0 -288
- package/templates/api/src/boards/storage/implementations/__init__.py +0 -27
- package/templates/api/src/boards/storage/implementations/gcs.py +0 -340
- package/templates/api/src/boards/storage/implementations/local.py +0 -201
- package/templates/api/src/boards/storage/implementations/s3.py +0 -294
- package/templates/api/src/boards/storage/implementations/supabase.py +0 -218
- package/templates/api/src/boards/tenant_isolation.py +0 -446
- package/templates/api/src/boards/validation.py +0 -262
- package/templates/api/src/boards/workers/__init__.py +0 -1
- package/templates/api/src/boards/workers/actors.py +0 -274
- package/templates/api/src/boards/workers/cli.py +0 -125
- package/templates/api/src/boards/workers/context.py +0 -348
- package/templates/api/src/boards/workers/middleware.py +0 -58
- package/templates/api/src/py.typed +0 -0
- package/templates/compose.web.yaml +0 -35
- package/templates/compose.yaml +0 -116
- package/templates/docker/env.example +0 -23
- package/templates/web/.env.example +0 -28
- package/templates/web/Dockerfile +0 -51
- package/templates/web/components.json +0 -22
- package/templates/web/imageLoader.js +0 -18
- package/templates/web/next-env.d.ts +0 -5
- package/templates/web/next.config.js +0 -36
- package/templates/web/package.json +0 -41
- package/templates/web/postcss.config.mjs +0 -7
- package/templates/web/public/favicon.ico +0 -0
- package/templates/web/src/app/boards/[boardId]/page.tsx +0 -353
- package/templates/web/src/app/globals.css +0 -123
- package/templates/web/src/app/layout.tsx +0 -31
- package/templates/web/src/app/lineage/[generationId]/page.tsx +0 -235
- package/templates/web/src/app/page.tsx +0 -35
- package/templates/web/src/app/providers.tsx +0 -18
- package/templates/web/src/components/boards/ArtifactInputSlots.tsx +0 -206
- package/templates/web/src/components/boards/ArtifactPreview.tsx +0 -466
- package/templates/web/src/components/boards/GenerationGrid.tsx +0 -282
- package/templates/web/src/components/boards/GenerationInput.tsx +0 -370
- package/templates/web/src/components/boards/GeneratorSelector.tsx +0 -272
- package/templates/web/src/components/boards/UploadArtifact.tsx +0 -563
- package/templates/web/src/components/header.tsx +0 -32
- package/templates/web/src/components/theme-provider.tsx +0 -10
- package/templates/web/src/components/theme-toggle.tsx +0 -75
- package/templates/web/src/components/ui/alert-dialog.tsx +0 -157
- package/templates/web/src/components/ui/button.tsx +0 -58
- package/templates/web/src/components/ui/card.tsx +0 -92
- package/templates/web/src/components/ui/dropdown-menu.tsx +0 -200
- package/templates/web/src/components/ui/navigation-menu.tsx +0 -168
- package/templates/web/src/components/ui/toast.tsx +0 -128
- package/templates/web/src/components/ui/toaster.tsx +0 -35
- package/templates/web/src/components/ui/use-toast.ts +0 -187
- package/templates/web/src/hooks/useGeneratorMRU.ts +0 -57
- package/templates/web/src/lib/utils.ts +0 -6
- package/templates/web/tsconfig.json +0 -41
package/templates/api/src/boards/generators/implementations/fal/image/seedream_v45_text_to_image.py
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Generate high-quality images using ByteDance's Seedream 4.5 text-to-image model.
|
|
3
|
-
|
|
4
|
-
Based on Fal AI's fal-ai/bytedance/seedream/v4.5/text-to-image model.
|
|
5
|
-
See: https://fal.ai/models/fal-ai/bytedance/seedream/v4.5/text-to-image
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
from typing import Literal
|
|
10
|
-
|
|
11
|
-
from pydantic import BaseModel, Field
|
|
12
|
-
|
|
13
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class SeedreamV45TextToImageInput(BaseModel):
|
|
17
|
-
"""Input schema for Seedream V4.5 text-to-image generation.
|
|
18
|
-
|
|
19
|
-
Seedream 4.5 is ByteDance's new-generation image creation model that integrates
|
|
20
|
-
image generation and editing capabilities into a unified architecture.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
prompt: str = Field(description="The text prompt used to generate the image")
|
|
24
|
-
num_images: int = Field(
|
|
25
|
-
default=1,
|
|
26
|
-
ge=1,
|
|
27
|
-
le=6,
|
|
28
|
-
description="Number of images to generate",
|
|
29
|
-
)
|
|
30
|
-
image_size: (
|
|
31
|
-
Literal[
|
|
32
|
-
"square_hd",
|
|
33
|
-
"portrait_4_3",
|
|
34
|
-
"landscape_16_9",
|
|
35
|
-
"auto_2K",
|
|
36
|
-
"auto_4K",
|
|
37
|
-
]
|
|
38
|
-
| None
|
|
39
|
-
) = Field(
|
|
40
|
-
default=None,
|
|
41
|
-
description=(
|
|
42
|
-
"The size preset for the generated image. Options include "
|
|
43
|
-
"square_hd, portrait_4_3, landscape_16_9, auto_2K, auto_4K"
|
|
44
|
-
),
|
|
45
|
-
)
|
|
46
|
-
seed: int | None = Field(
|
|
47
|
-
default=None,
|
|
48
|
-
description="Random seed for reproducibility",
|
|
49
|
-
)
|
|
50
|
-
enable_safety_checker: bool = Field(
|
|
51
|
-
default=True,
|
|
52
|
-
description="Enable or disable the safety checker",
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class FalSeedreamV45TextToImageGenerator(BaseGenerator):
|
|
57
|
-
"""Generator for high-quality images using ByteDance's Seedream 4.5 model."""
|
|
58
|
-
|
|
59
|
-
name = "fal-seedream-v45-text-to-image"
|
|
60
|
-
artifact_type = "image"
|
|
61
|
-
description = "Fal: ByteDance Seedream 4.5 - high-quality text-to-image generation"
|
|
62
|
-
|
|
63
|
-
def get_input_schema(self) -> type[SeedreamV45TextToImageInput]:
|
|
64
|
-
"""Return the input schema for this generator."""
|
|
65
|
-
return SeedreamV45TextToImageInput
|
|
66
|
-
|
|
67
|
-
async def generate(
|
|
68
|
-
self, inputs: SeedreamV45TextToImageInput, context: GeneratorExecutionContext
|
|
69
|
-
) -> GeneratorResult:
|
|
70
|
-
"""Generate images using fal.ai ByteDance Seedream 4.5 model."""
|
|
71
|
-
# Check for API key (fal-client uses FAL_KEY environment variable)
|
|
72
|
-
if not os.getenv("FAL_KEY"):
|
|
73
|
-
raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
|
|
74
|
-
|
|
75
|
-
# Import fal_client
|
|
76
|
-
try:
|
|
77
|
-
import fal_client
|
|
78
|
-
except ImportError as e:
|
|
79
|
-
raise ImportError(
|
|
80
|
-
"fal.ai SDK is required for FalSeedreamV45TextToImageGenerator. "
|
|
81
|
-
"Install with: pip install weirdfingers-boards[generators-fal]"
|
|
82
|
-
) from e
|
|
83
|
-
|
|
84
|
-
# Prepare arguments for fal.ai API
|
|
85
|
-
arguments: dict[str, object] = {
|
|
86
|
-
"prompt": inputs.prompt,
|
|
87
|
-
"num_images": inputs.num_images,
|
|
88
|
-
"enable_safety_checker": inputs.enable_safety_checker,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
# Add optional parameters
|
|
92
|
-
if inputs.image_size is not None:
|
|
93
|
-
arguments["image_size"] = inputs.image_size
|
|
94
|
-
|
|
95
|
-
if inputs.seed is not None:
|
|
96
|
-
arguments["seed"] = inputs.seed
|
|
97
|
-
|
|
98
|
-
# Submit async job and get handler
|
|
99
|
-
handler = await fal_client.submit_async(
|
|
100
|
-
"fal-ai/bytedance/seedream/v4.5/text-to-image",
|
|
101
|
-
arguments=arguments,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
# Store the external job ID for tracking
|
|
105
|
-
await context.set_external_job_id(handler.request_id)
|
|
106
|
-
|
|
107
|
-
# Stream progress updates (sample every 3rd event to avoid spam)
|
|
108
|
-
from .....progress.models import ProgressUpdate
|
|
109
|
-
|
|
110
|
-
event_count = 0
|
|
111
|
-
async for event in handler.iter_events(with_logs=True):
|
|
112
|
-
event_count += 1
|
|
113
|
-
|
|
114
|
-
# Process every 3rd event to provide feedback without overwhelming
|
|
115
|
-
if event_count % 3 == 0:
|
|
116
|
-
# Extract logs if available
|
|
117
|
-
logs = getattr(event, "logs", None)
|
|
118
|
-
if logs:
|
|
119
|
-
# Join log entries into a single message
|
|
120
|
-
if isinstance(logs, list):
|
|
121
|
-
message = " | ".join(str(log) for log in logs if log)
|
|
122
|
-
else:
|
|
123
|
-
message = str(logs)
|
|
124
|
-
|
|
125
|
-
if message:
|
|
126
|
-
await context.publish_progress(
|
|
127
|
-
ProgressUpdate(
|
|
128
|
-
job_id=handler.request_id,
|
|
129
|
-
status="processing",
|
|
130
|
-
progress=50.0, # Approximate mid-point progress
|
|
131
|
-
phase="processing",
|
|
132
|
-
message=message,
|
|
133
|
-
)
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
# Get final result
|
|
137
|
-
result = await handler.get()
|
|
138
|
-
|
|
139
|
-
# Extract image data from result
|
|
140
|
-
# fal.ai seedream returns:
|
|
141
|
-
# {"images": [{"url": "...", "width": ..., "height": ..., ...}], "seed": ...}
|
|
142
|
-
images = result.get("images", [])
|
|
143
|
-
if not images:
|
|
144
|
-
raise ValueError("No images returned from fal.ai API")
|
|
145
|
-
|
|
146
|
-
# Store each image using output_index
|
|
147
|
-
artifacts = []
|
|
148
|
-
for idx, image_data in enumerate(images):
|
|
149
|
-
image_url = image_data.get("url")
|
|
150
|
-
|
|
151
|
-
if not image_url:
|
|
152
|
-
raise ValueError(f"Image {idx} missing URL in fal.ai response")
|
|
153
|
-
|
|
154
|
-
# Extract dimensions if available, use defaults otherwise
|
|
155
|
-
width = image_data.get("width", 2048)
|
|
156
|
-
height = image_data.get("height", 2048)
|
|
157
|
-
|
|
158
|
-
# Determine format from content_type (e.g., "image/png" -> "png")
|
|
159
|
-
content_type = image_data.get("content_type", "image/png")
|
|
160
|
-
format = content_type.split("/")[-1] if "/" in content_type else "png"
|
|
161
|
-
|
|
162
|
-
# Store with appropriate output_index
|
|
163
|
-
artifact = await context.store_image_result(
|
|
164
|
-
storage_url=image_url,
|
|
165
|
-
format=format,
|
|
166
|
-
width=width,
|
|
167
|
-
height=height,
|
|
168
|
-
output_index=idx,
|
|
169
|
-
)
|
|
170
|
-
artifacts.append(artifact)
|
|
171
|
-
|
|
172
|
-
return GeneratorResult(outputs=artifacts)
|
|
173
|
-
|
|
174
|
-
async def estimate_cost(self, inputs: SeedreamV45TextToImageInput) -> float:
|
|
175
|
-
"""Estimate cost for Seedream V4.5 generation.
|
|
176
|
-
|
|
177
|
-
Seedream V4.5 pricing is approximately $0.03 per image generation.
|
|
178
|
-
Note: Actual pricing may vary. Check Fal AI documentation for current rates.
|
|
179
|
-
"""
|
|
180
|
-
return 0.03 * inputs.num_images
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Shared utilities for Fal.ai generators.
|
|
3
|
-
|
|
4
|
-
Provides helper functions for common operations across Fal generators.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
|
|
9
|
-
from ...artifacts import AudioArtifact, DigitalArtifact, ImageArtifact, VideoArtifact
|
|
10
|
-
from ...base import GeneratorExecutionContext
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
async def upload_artifacts_to_fal[T: DigitalArtifact](
|
|
14
|
-
artifacts: list[ImageArtifact] | list[VideoArtifact] | list[AudioArtifact] | list[T],
|
|
15
|
-
context: GeneratorExecutionContext,
|
|
16
|
-
) -> list[str]:
|
|
17
|
-
"""
|
|
18
|
-
Upload artifacts to Fal's temporary storage for use in API requests.
|
|
19
|
-
|
|
20
|
-
Fal API endpoints require publicly accessible URLs for file inputs. Since our
|
|
21
|
-
storage URLs might be local or private (localhost, private S3 buckets, etc.),
|
|
22
|
-
we need to:
|
|
23
|
-
1. Resolve each artifact to a local file path
|
|
24
|
-
2. Upload to Fal's public temporary storage
|
|
25
|
-
3. Get back publicly accessible URLs
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
artifacts: List of artifacts (image, video, or audio) to upload
|
|
29
|
-
context: Generator execution context for artifact resolution
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
List of publicly accessible URLs from Fal storage
|
|
33
|
-
|
|
34
|
-
Raises:
|
|
35
|
-
ImportError: If fal_client is not installed
|
|
36
|
-
Any exceptions from file resolution or upload are propagated
|
|
37
|
-
"""
|
|
38
|
-
# Import fal_client
|
|
39
|
-
try:
|
|
40
|
-
import fal_client
|
|
41
|
-
except ImportError as e:
|
|
42
|
-
raise ImportError(
|
|
43
|
-
"fal.ai SDK is required for Fal generators. "
|
|
44
|
-
"Install with: pip install weirdfingers-boards[generators-fal]"
|
|
45
|
-
) from e
|
|
46
|
-
|
|
47
|
-
async def upload_single_artifact(artifact: DigitalArtifact) -> str:
|
|
48
|
-
"""Upload a single artifact and return its public URL."""
|
|
49
|
-
# Resolve artifact to local file path (downloads if needed)
|
|
50
|
-
file_path_str = await context.resolve_artifact(artifact)
|
|
51
|
-
|
|
52
|
-
# Upload to Fal's temporary storage and get public URL
|
|
53
|
-
# fal_client.upload_file_async expects a file path
|
|
54
|
-
url = await fal_client.upload_file_async(file_path_str) # type: ignore[arg-type]
|
|
55
|
-
|
|
56
|
-
return url
|
|
57
|
-
|
|
58
|
-
# Upload all artifacts in parallel for performance
|
|
59
|
-
urls = await asyncio.gather(*[upload_single_artifact(artifact) for artifact in artifacts])
|
|
60
|
-
|
|
61
|
-
return list(urls)
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"""Fal.ai video generators."""
|
|
2
|
-
|
|
3
|
-
from .bytedance_seedance_v1_pro_text_to_video import (
|
|
4
|
-
FalBytedanceSeedanceV1ProTextToVideoGenerator,
|
|
5
|
-
)
|
|
6
|
-
from .creatify_lipsync import FalCreatifyLipsyncGenerator
|
|
7
|
-
from .fal_bytedance_seedance_v1_pro_image_to_video import (
|
|
8
|
-
FalBytedanceSeedanceV1ProImageToVideoGenerator,
|
|
9
|
-
)
|
|
10
|
-
from .fal_minimax_hailuo_02_standard_text_to_video import (
|
|
11
|
-
FalMinimaxHailuo02StandardTextToVideoGenerator,
|
|
12
|
-
)
|
|
13
|
-
from .fal_pixverse_lipsync import FalPixverseLipsyncGenerator
|
|
14
|
-
from .fal_sora_2_text_to_video import FalSora2TextToVideoGenerator
|
|
15
|
-
from .infinitalk import FalInfinitalkGenerator
|
|
16
|
-
from .kling_video_ai_avatar_v2_pro import FalKlingVideoAiAvatarV2ProGenerator
|
|
17
|
-
from .kling_video_ai_avatar_v2_standard import (
|
|
18
|
-
FalKlingVideoAiAvatarV2StandardGenerator,
|
|
19
|
-
)
|
|
20
|
-
from .kling_video_v2_5_turbo_pro_image_to_video import (
|
|
21
|
-
FalKlingVideoV25TurboProImageToVideoGenerator,
|
|
22
|
-
)
|
|
23
|
-
from .kling_video_v2_5_turbo_pro_text_to_video import (
|
|
24
|
-
FalKlingVideoV25TurboProTextToVideoGenerator,
|
|
25
|
-
)
|
|
26
|
-
from .minimax_hailuo_2_3_pro_image_to_video import (
|
|
27
|
-
FalMinimaxHailuo23ProImageToVideoGenerator,
|
|
28
|
-
)
|
|
29
|
-
from .sora2_image_to_video import FalSora2ImageToVideoGenerator
|
|
30
|
-
from .sora_2_image_to_video_pro import FalSora2ImageToVideoProGenerator
|
|
31
|
-
from .sora_2_text_to_video_pro import FalSora2TextToVideoProGenerator
|
|
32
|
-
from .sync_lipsync_v2 import FalSyncLipsyncV2Generator
|
|
33
|
-
from .sync_lipsync_v2_pro import FalSyncLipsyncV2ProGenerator
|
|
34
|
-
from .veed_fabric_1_0 import FalVeedFabric10Generator
|
|
35
|
-
from .veed_lipsync import FalVeedLipsyncGenerator
|
|
36
|
-
from .veo3 import FalVeo3Generator
|
|
37
|
-
from .veo31 import FalVeo31Generator
|
|
38
|
-
from .veo31_fast import FalVeo31FastGenerator
|
|
39
|
-
from .veo31_fast_image_to_video import FalVeo31FastImageToVideoGenerator
|
|
40
|
-
from .veo31_first_last_frame_to_video import FalVeo31FirstLastFrameToVideoGenerator
|
|
41
|
-
from .veo31_image_to_video import FalVeo31ImageToVideoGenerator
|
|
42
|
-
from .veo31_reference_to_video import FalVeo31ReferenceToVideoGenerator
|
|
43
|
-
from .wan_25_preview_image_to_video import FalWan25PreviewImageToVideoGenerator
|
|
44
|
-
from .wan_25_preview_text_to_video import FalWan25PreviewTextToVideoGenerator
|
|
45
|
-
from .wan_pro_image_to_video import FalWanProImageToVideoGenerator
|
|
46
|
-
|
|
47
|
-
__all__ = [
|
|
48
|
-
"FalInfinitalkGenerator",
|
|
49
|
-
"FalCreatifyLipsyncGenerator",
|
|
50
|
-
"FalBytedanceSeedanceV1ProImageToVideoGenerator",
|
|
51
|
-
"FalBytedanceSeedanceV1ProTextToVideoGenerator",
|
|
52
|
-
"FalKlingVideoAiAvatarV2ProGenerator",
|
|
53
|
-
"FalKlingVideoAiAvatarV2StandardGenerator",
|
|
54
|
-
"FalKlingVideoV25TurboProImageToVideoGenerator",
|
|
55
|
-
"FalKlingVideoV25TurboProTextToVideoGenerator",
|
|
56
|
-
"FalPixverseLipsyncGenerator",
|
|
57
|
-
"FalSora2TextToVideoProGenerator",
|
|
58
|
-
"FalSora2TextToVideoGenerator",
|
|
59
|
-
"FalMinimaxHailuo02StandardTextToVideoGenerator",
|
|
60
|
-
"FalMinimaxHailuo23ProImageToVideoGenerator",
|
|
61
|
-
"FalSora2ImageToVideoGenerator",
|
|
62
|
-
"FalSora2ImageToVideoProGenerator",
|
|
63
|
-
"FalSyncLipsyncV2Generator",
|
|
64
|
-
"FalVeedFabric10Generator",
|
|
65
|
-
"FalVeedLipsyncGenerator",
|
|
66
|
-
"FalSyncLipsyncV2ProGenerator",
|
|
67
|
-
"FalVeo3Generator",
|
|
68
|
-
"FalVeo31Generator",
|
|
69
|
-
"FalVeo31FastGenerator",
|
|
70
|
-
"FalVeo31FastImageToVideoGenerator",
|
|
71
|
-
"FalVeo31FirstLastFrameToVideoGenerator",
|
|
72
|
-
"FalVeo31ImageToVideoGenerator",
|
|
73
|
-
"FalVeo31ReferenceToVideoGenerator",
|
|
74
|
-
"FalWan25PreviewImageToVideoGenerator",
|
|
75
|
-
"FalWan25PreviewTextToVideoGenerator",
|
|
76
|
-
"FalWanProImageToVideoGenerator",
|
|
77
|
-
]
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Bytedance Seedance 1.0 Pro text-to-video generator.
|
|
3
|
-
|
|
4
|
-
A high quality video generation model developed by Bytedance that transforms
|
|
5
|
-
text prompts into professional-grade videos with customizable parameters.
|
|
6
|
-
|
|
7
|
-
Based on Fal AI's fal-ai/bytedance/seedance/v1/pro/text-to-video model.
|
|
8
|
-
See: https://fal.ai/models/fal-ai/bytedance/seedance/v1/pro/text-to-video
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import os
|
|
12
|
-
from typing import Literal
|
|
13
|
-
|
|
14
|
-
from pydantic import BaseModel, Field
|
|
15
|
-
|
|
16
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class BytedanceSeedanceV1ProTextToVideoInput(BaseModel):
|
|
20
|
-
"""Input schema for Bytedance Seedance 1.0 Pro text-to-video generation."""
|
|
21
|
-
|
|
22
|
-
prompt: str = Field(
|
|
23
|
-
description="Text description of the desired video content",
|
|
24
|
-
)
|
|
25
|
-
aspect_ratio: Literal["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"] = Field(
|
|
26
|
-
default="16:9",
|
|
27
|
-
description="Video aspect ratio",
|
|
28
|
-
)
|
|
29
|
-
resolution: Literal["480p", "720p", "1080p"] = Field(
|
|
30
|
-
default="1080p",
|
|
31
|
-
description="Video resolution quality",
|
|
32
|
-
)
|
|
33
|
-
duration: Literal["2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] = Field(
|
|
34
|
-
default="5",
|
|
35
|
-
description="Video length in seconds (2-12)",
|
|
36
|
-
)
|
|
37
|
-
enable_safety_checker: bool = Field(
|
|
38
|
-
default=True,
|
|
39
|
-
description="Enable safety checker to filter unsafe content",
|
|
40
|
-
)
|
|
41
|
-
camera_fixed: bool = Field(
|
|
42
|
-
default=False,
|
|
43
|
-
description="Whether to fix camera position during generation",
|
|
44
|
-
)
|
|
45
|
-
seed: int | None = Field(
|
|
46
|
-
default=None,
|
|
47
|
-
description="Random seed for reproducibility; use -1 for randomization",
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class FalBytedanceSeedanceV1ProTextToVideoGenerator(BaseGenerator):
|
|
52
|
-
"""Generator for text-to-video using Bytedance Seedance 1.0 Pro."""
|
|
53
|
-
|
|
54
|
-
name = "fal-bytedance-seedance-v1-pro-text-to-video"
|
|
55
|
-
description = "Fal: Bytedance Seedance 1.0 Pro - high quality text-to-video generation"
|
|
56
|
-
artifact_type = "video"
|
|
57
|
-
|
|
58
|
-
def get_input_schema(self) -> type[BytedanceSeedanceV1ProTextToVideoInput]:
|
|
59
|
-
"""Return the input schema for this generator."""
|
|
60
|
-
return BytedanceSeedanceV1ProTextToVideoInput
|
|
61
|
-
|
|
62
|
-
async def generate(
|
|
63
|
-
self, inputs: BytedanceSeedanceV1ProTextToVideoInput, context: GeneratorExecutionContext
|
|
64
|
-
) -> GeneratorResult:
|
|
65
|
-
"""Generate video using fal.ai Bytedance Seedance 1.0 Pro model."""
|
|
66
|
-
# Check for API key
|
|
67
|
-
if not os.getenv("FAL_KEY"):
|
|
68
|
-
raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
|
|
69
|
-
|
|
70
|
-
# Import fal_client
|
|
71
|
-
try:
|
|
72
|
-
import fal_client
|
|
73
|
-
except ImportError as e:
|
|
74
|
-
raise ImportError(
|
|
75
|
-
"fal.ai SDK is required for FalBytedanceSeedanceV1ProTextToVideoGenerator. "
|
|
76
|
-
"Install with: pip install weirdfingers-boards[generators-fal]"
|
|
77
|
-
) from e
|
|
78
|
-
|
|
79
|
-
# Prepare arguments for fal.ai API
|
|
80
|
-
arguments = {
|
|
81
|
-
"prompt": inputs.prompt,
|
|
82
|
-
"aspect_ratio": inputs.aspect_ratio,
|
|
83
|
-
"resolution": inputs.resolution,
|
|
84
|
-
"duration": inputs.duration,
|
|
85
|
-
"enable_safety_checker": inputs.enable_safety_checker,
|
|
86
|
-
"camera_fixed": inputs.camera_fixed,
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# Add seed if provided
|
|
90
|
-
if inputs.seed is not None:
|
|
91
|
-
arguments["seed"] = inputs.seed
|
|
92
|
-
|
|
93
|
-
# Submit async job
|
|
94
|
-
handler = await fal_client.submit_async(
|
|
95
|
-
"fal-ai/bytedance/seedance/v1/pro/text-to-video",
|
|
96
|
-
arguments=arguments,
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
# Store external job ID
|
|
100
|
-
await context.set_external_job_id(handler.request_id)
|
|
101
|
-
|
|
102
|
-
# Stream progress updates
|
|
103
|
-
from .....progress.models import ProgressUpdate
|
|
104
|
-
|
|
105
|
-
event_count = 0
|
|
106
|
-
async for event in handler.iter_events(with_logs=True):
|
|
107
|
-
event_count += 1
|
|
108
|
-
# Sample every 3rd event to avoid spam
|
|
109
|
-
if event_count % 3 == 0:
|
|
110
|
-
# Extract logs if available
|
|
111
|
-
logs = getattr(event, "logs", None)
|
|
112
|
-
if logs:
|
|
113
|
-
# Join log entries into a single message
|
|
114
|
-
if isinstance(logs, list):
|
|
115
|
-
message = " | ".join(str(log) for log in logs if log)
|
|
116
|
-
else:
|
|
117
|
-
message = str(logs)
|
|
118
|
-
|
|
119
|
-
if message:
|
|
120
|
-
await context.publish_progress(
|
|
121
|
-
ProgressUpdate(
|
|
122
|
-
job_id=handler.request_id,
|
|
123
|
-
status="processing",
|
|
124
|
-
progress=50.0, # Approximate mid-point progress
|
|
125
|
-
phase="processing",
|
|
126
|
-
message=message,
|
|
127
|
-
)
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
# Get final result
|
|
131
|
-
result = await handler.get()
|
|
132
|
-
|
|
133
|
-
# Extract video from result
|
|
134
|
-
# fal.ai returns: {"video": {"url": "...", "content_type": "video/mp4", ...}, "seed": 123}
|
|
135
|
-
video_data = result.get("video")
|
|
136
|
-
if not video_data:
|
|
137
|
-
raise ValueError("No video returned from fal.ai API")
|
|
138
|
-
|
|
139
|
-
video_url = video_data.get("url")
|
|
140
|
-
if not video_url:
|
|
141
|
-
raise ValueError("Video missing URL in fal.ai response")
|
|
142
|
-
|
|
143
|
-
# Calculate video dimensions based on aspect ratio and resolution
|
|
144
|
-
width, height = self._calculate_dimensions(inputs.aspect_ratio, inputs.resolution)
|
|
145
|
-
|
|
146
|
-
# Store video result
|
|
147
|
-
artifact = await context.store_video_result(
|
|
148
|
-
storage_url=video_url,
|
|
149
|
-
format="mp4",
|
|
150
|
-
width=width,
|
|
151
|
-
height=height,
|
|
152
|
-
duration=float(inputs.duration),
|
|
153
|
-
output_index=0,
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
return GeneratorResult(outputs=[artifact])
|
|
157
|
-
|
|
158
|
-
def _calculate_dimensions(self, aspect_ratio: str, resolution: str) -> tuple[int, int]:
|
|
159
|
-
"""Calculate video dimensions based on aspect ratio and resolution.
|
|
160
|
-
|
|
161
|
-
Args:
|
|
162
|
-
aspect_ratio: Video aspect ratio (e.g., "16:9", "21:9")
|
|
163
|
-
resolution: Video resolution (e.g., "1080p", "720p", "480p")
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
Tuple of (width, height) in pixels
|
|
167
|
-
"""
|
|
168
|
-
# Base heights for each resolution
|
|
169
|
-
resolution_heights = {
|
|
170
|
-
"1080p": 1080,
|
|
171
|
-
"720p": 720,
|
|
172
|
-
"480p": 480,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
# Parse aspect ratio
|
|
176
|
-
aspect_parts = aspect_ratio.split(":")
|
|
177
|
-
aspect_width = int(aspect_parts[0])
|
|
178
|
-
aspect_height = int(aspect_parts[1])
|
|
179
|
-
|
|
180
|
-
# Get base height for resolution
|
|
181
|
-
height = resolution_heights[resolution]
|
|
182
|
-
|
|
183
|
-
# Calculate width based on aspect ratio
|
|
184
|
-
width = int((height * aspect_width) / aspect_height)
|
|
185
|
-
|
|
186
|
-
return width, height
|
|
187
|
-
|
|
188
|
-
async def estimate_cost(self, inputs: BytedanceSeedanceV1ProTextToVideoInput) -> float:
|
|
189
|
-
"""Estimate cost for Bytedance Seedance 1.0 Pro generation.
|
|
190
|
-
|
|
191
|
-
Pricing information not provided in official documentation.
|
|
192
|
-
Estimated at $0.12 per video based on typical video generation costs.
|
|
193
|
-
Cost may vary based on duration and resolution settings.
|
|
194
|
-
"""
|
|
195
|
-
# Base cost per video
|
|
196
|
-
base_cost = 0.12
|
|
197
|
-
|
|
198
|
-
# Adjust for longer durations (higher cost for longer videos)
|
|
199
|
-
duration_seconds = int(inputs.duration)
|
|
200
|
-
duration_multiplier = 1.0 + ((duration_seconds - 5) * 0.05) # +5% per second above 5s
|
|
201
|
-
|
|
202
|
-
# Adjust for higher resolutions
|
|
203
|
-
resolution_multiplier = {
|
|
204
|
-
"480p": 0.8, # Lower quality, lower cost
|
|
205
|
-
"720p": 1.0, # Standard
|
|
206
|
-
"1080p": 1.3, # Higher quality, higher cost
|
|
207
|
-
}[inputs.resolution]
|
|
208
|
-
|
|
209
|
-
return base_cost * duration_multiplier * resolution_multiplier
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
fal.ai creatify/lipsync video generator.
|
|
3
|
-
|
|
4
|
-
Generates realistic lip-synchronization videos from audio and video inputs
|
|
5
|
-
using Creatify's lipsync model on fal.ai. Optimized for speed, quality, and
|
|
6
|
-
consistency.
|
|
7
|
-
|
|
8
|
-
Based on Fal AI's creatify/lipsync model.
|
|
9
|
-
See: https://fal.ai/models/creatify/lipsync
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import os
|
|
13
|
-
|
|
14
|
-
from pydantic import BaseModel, Field
|
|
15
|
-
|
|
16
|
-
from ....artifacts import AudioArtifact, VideoArtifact
|
|
17
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class CreatifyLipsyncInput(BaseModel):
|
|
21
|
-
"""Input schema for creatify/lipsync.
|
|
22
|
-
|
|
23
|
-
Artifact fields are automatically detected via type introspection
|
|
24
|
-
and resolved from generation IDs to artifact objects.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
video: VideoArtifact = Field(description="The video to use for lipsync")
|
|
28
|
-
audio: AudioArtifact = Field(description="The audio to use for lipsync")
|
|
29
|
-
loop: bool = Field(
|
|
30
|
-
default=True,
|
|
31
|
-
description="Repeats video if shorter than audio",
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class FalCreatifyLipsyncGenerator(BaseGenerator):
|
|
36
|
-
"""Generator for realistic lip-synchronization videos."""
|
|
37
|
-
|
|
38
|
-
name = "fal-creatify-lipsync"
|
|
39
|
-
description = "Fal: Creatify Lipsync - Realistic lipsync video optimized for speed and quality"
|
|
40
|
-
artifact_type = "video"
|
|
41
|
-
|
|
42
|
-
def get_input_schema(self) -> type[CreatifyLipsyncInput]:
|
|
43
|
-
"""Return the input schema for this generator."""
|
|
44
|
-
return CreatifyLipsyncInput
|
|
45
|
-
|
|
46
|
-
async def generate(
|
|
47
|
-
self, inputs: CreatifyLipsyncInput, context: GeneratorExecutionContext
|
|
48
|
-
) -> GeneratorResult:
|
|
49
|
-
"""Generate lip-synced video using creatify/lipsync."""
|
|
50
|
-
# Check for API key
|
|
51
|
-
if not os.getenv("FAL_KEY"):
|
|
52
|
-
raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
|
|
53
|
-
|
|
54
|
-
# Import fal_client
|
|
55
|
-
try:
|
|
56
|
-
import fal_client
|
|
57
|
-
except ImportError as e:
|
|
58
|
-
raise ImportError(
|
|
59
|
-
"fal.ai SDK is required for FalCreatifyLipsyncGenerator. "
|
|
60
|
-
"Install with: pip install weirdfingers-boards[generators-fal]"
|
|
61
|
-
) from e
|
|
62
|
-
|
|
63
|
-
# Upload video and audio artifacts to Fal's public storage
|
|
64
|
-
# Fal API requires publicly accessible URLs
|
|
65
|
-
from ..utils import upload_artifacts_to_fal
|
|
66
|
-
|
|
67
|
-
# Upload video and audio separately
|
|
68
|
-
video_urls = await upload_artifacts_to_fal([inputs.video], context)
|
|
69
|
-
audio_urls = await upload_artifacts_to_fal([inputs.audio], context)
|
|
70
|
-
|
|
71
|
-
# Prepare arguments for fal.ai API
|
|
72
|
-
arguments = {
|
|
73
|
-
"video_url": video_urls[0],
|
|
74
|
-
"audio_url": audio_urls[0],
|
|
75
|
-
"loop": inputs.loop,
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
# Submit async job
|
|
79
|
-
handler = await fal_client.submit_async(
|
|
80
|
-
"creatify/lipsync",
|
|
81
|
-
arguments=arguments,
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
# Store external job ID
|
|
85
|
-
await context.set_external_job_id(handler.request_id)
|
|
86
|
-
|
|
87
|
-
# Stream progress updates
|
|
88
|
-
from .....progress.models import ProgressUpdate
|
|
89
|
-
|
|
90
|
-
event_count = 0
|
|
91
|
-
async for event in handler.iter_events(with_logs=True):
|
|
92
|
-
event_count += 1
|
|
93
|
-
# Sample every 3rd event to avoid spam
|
|
94
|
-
if event_count % 3 == 0:
|
|
95
|
-
# Extract logs if available
|
|
96
|
-
logs = getattr(event, "logs", None)
|
|
97
|
-
if logs:
|
|
98
|
-
# Join log entries into a single message
|
|
99
|
-
if isinstance(logs, list):
|
|
100
|
-
message = " | ".join(str(log) for log in logs if log)
|
|
101
|
-
else:
|
|
102
|
-
message = str(logs)
|
|
103
|
-
|
|
104
|
-
if message:
|
|
105
|
-
await context.publish_progress(
|
|
106
|
-
ProgressUpdate(
|
|
107
|
-
job_id=handler.request_id,
|
|
108
|
-
status="processing",
|
|
109
|
-
progress=50.0, # Approximate mid-point progress
|
|
110
|
-
phase="processing",
|
|
111
|
-
message=message,
|
|
112
|
-
)
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
# Get final result
|
|
116
|
-
result = await handler.get()
|
|
117
|
-
|
|
118
|
-
# Extract video from result
|
|
119
|
-
# fal.ai returns: {"video": {"url": "...", "content_type": "video/mp4", ...}}
|
|
120
|
-
video_data = result.get("video")
|
|
121
|
-
|
|
122
|
-
if not video_data:
|
|
123
|
-
raise ValueError("No video returned from fal.ai API")
|
|
124
|
-
|
|
125
|
-
video_url = video_data.get("url")
|
|
126
|
-
if not video_url:
|
|
127
|
-
raise ValueError("Video missing URL in fal.ai response")
|
|
128
|
-
|
|
129
|
-
# Extract format from content_type (e.g., "video/mp4" -> "mp4")
|
|
130
|
-
# Creatify lipsync always produces MP4 videos, so default to mp4
|
|
131
|
-
content_type = video_data.get("content_type", "video/mp4")
|
|
132
|
-
if content_type.startswith("video/"):
|
|
133
|
-
video_format = content_type.split("/")[-1]
|
|
134
|
-
else:
|
|
135
|
-
# If content_type is not a video mime type (e.g., application/octet-stream),
|
|
136
|
-
# default to mp4 since creatify/lipsync only produces mp4 videos
|
|
137
|
-
video_format = "mp4"
|
|
138
|
-
|
|
139
|
-
# Store the video result
|
|
140
|
-
# Note: The API doesn't return width/height/duration/fps, so we use defaults
|
|
141
|
-
# The actual dimensions will be the same as the input video
|
|
142
|
-
artifact = await context.store_video_result(
|
|
143
|
-
storage_url=video_url,
|
|
144
|
-
format=video_format,
|
|
145
|
-
width=inputs.video.width,
|
|
146
|
-
height=inputs.video.height,
|
|
147
|
-
duration=inputs.audio.duration,
|
|
148
|
-
fps=inputs.video.fps,
|
|
149
|
-
output_index=0,
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
return GeneratorResult(outputs=[artifact])
|
|
153
|
-
|
|
154
|
-
async def estimate_cost(self, inputs: CreatifyLipsyncInput) -> float:
|
|
155
|
-
"""Estimate cost for creatify/lipsync generation in USD.
|
|
156
|
-
|
|
157
|
-
Pricing not specified in documentation, using estimate based on
|
|
158
|
-
typical video processing costs.
|
|
159
|
-
"""
|
|
160
|
-
# Base cost estimate per generation
|
|
161
|
-
return 0.05
|