@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
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Shared utilities for Kie.ai generators.
|
|
3
|
-
|
|
4
|
-
Provides helper functions for common operations across Kie generators.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
import os
|
|
9
|
-
|
|
10
|
-
import httpx
|
|
11
|
-
|
|
12
|
-
from ...artifacts import AudioArtifact, DigitalArtifact, ImageArtifact, VideoArtifact
|
|
13
|
-
from ...base import GeneratorExecutionContext
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
async def upload_artifacts_to_kie[T: DigitalArtifact](
|
|
17
|
-
artifacts: list[ImageArtifact] | list[VideoArtifact] | list[AudioArtifact] | list[T],
|
|
18
|
-
context: GeneratorExecutionContext,
|
|
19
|
-
) -> list[str]:
|
|
20
|
-
"""
|
|
21
|
-
Upload artifacts to Kie.ai's temporary storage for use in API requests.
|
|
22
|
-
|
|
23
|
-
Kie.ai API endpoints require publicly accessible URLs for file inputs. Since our
|
|
24
|
-
storage URLs might be local or private (localhost, private S3 buckets, etc.),
|
|
25
|
-
we need to:
|
|
26
|
-
1. Resolve each artifact to a local file path
|
|
27
|
-
2. Upload to Kie.ai's public temporary storage
|
|
28
|
-
3. Get back publicly accessible URLs
|
|
29
|
-
|
|
30
|
-
Note: Files uploaded to Kie.ai storage expire after 3 days.
|
|
31
|
-
|
|
32
|
-
Args:
|
|
33
|
-
artifacts: List of artifacts (image, video, or audio) to upload
|
|
34
|
-
context: Generator execution context for artifact resolution
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
List of publicly accessible URLs from Kie.ai storage
|
|
38
|
-
|
|
39
|
-
Raises:
|
|
40
|
-
ValueError: If KIE_API_KEY is not set
|
|
41
|
-
Any exceptions from file resolution or upload are propagated
|
|
42
|
-
"""
|
|
43
|
-
api_key = os.getenv("KIE_API_KEY")
|
|
44
|
-
if not api_key:
|
|
45
|
-
raise ValueError("KIE_API_KEY environment variable is required for file uploads")
|
|
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 Kie.ai's temporary storage
|
|
53
|
-
# Using file stream upload API
|
|
54
|
-
async with httpx.AsyncClient() as client:
|
|
55
|
-
with open(file_path_str, "rb") as f:
|
|
56
|
-
files = {"file": f}
|
|
57
|
-
# uploadPath is required by Kie.ai API - specifies the storage path
|
|
58
|
-
data = {"uploadPath": "boards/temp"}
|
|
59
|
-
response = await client.post(
|
|
60
|
-
"https://kieai.redpandaai.co/api/file-stream-upload",
|
|
61
|
-
files=files,
|
|
62
|
-
data=data,
|
|
63
|
-
headers={"Authorization": f"Bearer {api_key}"},
|
|
64
|
-
timeout=120.0, # 2 minute timeout for uploads
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
if response.status_code != 200:
|
|
68
|
-
raise ValueError(f"File upload failed: {response.status_code} {response.text}")
|
|
69
|
-
|
|
70
|
-
result = response.json()
|
|
71
|
-
|
|
72
|
-
if not result.get("success"):
|
|
73
|
-
raise ValueError(f"File upload failed: {result.get('msg')}")
|
|
74
|
-
|
|
75
|
-
# Extract the public URL from response data
|
|
76
|
-
data = result.get("data", {})
|
|
77
|
-
|
|
78
|
-
# The actual field name is 'downloadUrl' based on API response
|
|
79
|
-
file_url = data.get("downloadUrl")
|
|
80
|
-
|
|
81
|
-
if not file_url:
|
|
82
|
-
# Fallback to other possible field names
|
|
83
|
-
file_url = data.get("fileUrl") or data.get("file_url") or data.get("url")
|
|
84
|
-
|
|
85
|
-
if not file_url:
|
|
86
|
-
# If we still can't find the URL, provide detailed error message
|
|
87
|
-
raise ValueError(
|
|
88
|
-
f"File upload succeeded but couldn't find URL in response. "
|
|
89
|
-
f"Response data keys: {list(data.keys())}, "
|
|
90
|
-
f"Full response: {result}"
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
return file_url
|
|
94
|
-
|
|
95
|
-
# Upload all artifacts in parallel for performance
|
|
96
|
-
urls = await asyncio.gather(*[upload_single_artifact(artifact) for artifact in artifacts])
|
|
97
|
-
|
|
98
|
-
return list(urls)
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Kie.ai Veo 3.1 text-to-video and image-to-video generator.
|
|
3
|
-
|
|
4
|
-
Generate high-quality videos from text prompts with optional image inputs
|
|
5
|
-
using Kie.ai's Google Veo 3.1 model (Dedicated API).
|
|
6
|
-
|
|
7
|
-
Based on Kie.ai's Veo 3.1 API.
|
|
8
|
-
See: https://docs.kie.ai/veo3-api/generate-veo-3-video
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from typing import Any, Literal
|
|
12
|
-
|
|
13
|
-
from pydantic import BaseModel, Field
|
|
14
|
-
|
|
15
|
-
from ....artifacts import ImageArtifact
|
|
16
|
-
from ....base import GeneratorExecutionContext, GeneratorResult
|
|
17
|
-
from ..base import KieDedicatedAPIGenerator
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class KieVeo3Input(BaseModel):
|
|
21
|
-
"""Input schema for Kie.ai Veo 3.1 video generation.
|
|
22
|
-
|
|
23
|
-
Supports both text-to-video and image-to-video modes.
|
|
24
|
-
Artifact fields (like image_sources) are automatically detected via type
|
|
25
|
-
introspection and resolved from generation IDs to ImageArtifact objects.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
prompt: str = Field(
|
|
29
|
-
description="The text prompt describing the video you want to generate",
|
|
30
|
-
max_length=5000,
|
|
31
|
-
)
|
|
32
|
-
image_sources: list[ImageArtifact] | None = Field(
|
|
33
|
-
default=None,
|
|
34
|
-
description="Optional list of 1-2 input images for image-to-video generation",
|
|
35
|
-
min_length=1,
|
|
36
|
-
max_length=2,
|
|
37
|
-
)
|
|
38
|
-
aspect_ratio: Literal["16:9", "9:16", "Auto"] = Field(
|
|
39
|
-
default="16:9",
|
|
40
|
-
description="Aspect ratio of the generated video",
|
|
41
|
-
)
|
|
42
|
-
model: Literal["veo3", "veo3_fast"] = Field(
|
|
43
|
-
default="veo3",
|
|
44
|
-
description="Model variant to use (veo3 for quality, veo3_fast for speed)",
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class KieVeo3Generator(KieDedicatedAPIGenerator):
|
|
49
|
-
"""Veo 3.1 video generator using Kie.ai Dedicated API."""
|
|
50
|
-
|
|
51
|
-
name = "kie-veo3"
|
|
52
|
-
artifact_type = "video"
|
|
53
|
-
description = "Kie.ai: Google Veo 3.1 - High-quality AI video generation"
|
|
54
|
-
|
|
55
|
-
# Dedicated API configuration
|
|
56
|
-
model_id = "veo3"
|
|
57
|
-
|
|
58
|
-
def get_input_schema(self) -> type[KieVeo3Input]:
|
|
59
|
-
return KieVeo3Input
|
|
60
|
-
|
|
61
|
-
def _get_status_url(self, task_id: str) -> str:
|
|
62
|
-
"""Get the Veo3-specific status check URL."""
|
|
63
|
-
return f"https://api.kie.ai/api/v1/veo/record-info?taskId={task_id}"
|
|
64
|
-
|
|
65
|
-
async def generate(
|
|
66
|
-
self, inputs: KieVeo3Input, context: GeneratorExecutionContext
|
|
67
|
-
) -> GeneratorResult:
|
|
68
|
-
"""Generate video using Kie.ai Veo 3.1 model."""
|
|
69
|
-
# Get API key using base class method
|
|
70
|
-
api_key = self._get_api_key()
|
|
71
|
-
|
|
72
|
-
# Prepare request body for Dedicated API
|
|
73
|
-
body: dict[str, Any] = {
|
|
74
|
-
"prompt": inputs.prompt,
|
|
75
|
-
"aspectRatio": inputs.aspect_ratio,
|
|
76
|
-
"model": inputs.model,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
# Upload image artifacts if provided (for image-to-video mode)
|
|
80
|
-
if inputs.image_sources:
|
|
81
|
-
from ..utils import upload_artifacts_to_kie
|
|
82
|
-
|
|
83
|
-
image_urls = await upload_artifacts_to_kie(inputs.image_sources, context)
|
|
84
|
-
body["imageUrls"] = image_urls
|
|
85
|
-
|
|
86
|
-
# Submit task to Dedicated API endpoint using base class method
|
|
87
|
-
submit_url = "https://api.kie.ai/api/v1/veo/generate"
|
|
88
|
-
result = await self._make_request(submit_url, "POST", api_key, json=body)
|
|
89
|
-
|
|
90
|
-
# Extract task ID from Dedicated API response
|
|
91
|
-
# Try direct taskId first, then nested under 'data'
|
|
92
|
-
task_id = result.get("taskId")
|
|
93
|
-
if not task_id:
|
|
94
|
-
data = result.get("data", {})
|
|
95
|
-
task_id = data.get("taskId")
|
|
96
|
-
|
|
97
|
-
if not task_id:
|
|
98
|
-
raise ValueError(f"No taskId returned from Kie.ai API. Response: {result}")
|
|
99
|
-
|
|
100
|
-
# Store external job ID
|
|
101
|
-
await context.set_external_job_id(task_id)
|
|
102
|
-
|
|
103
|
-
# Poll for completion using base class method
|
|
104
|
-
result_data = await self._poll_for_completion(task_id, api_key, context)
|
|
105
|
-
|
|
106
|
-
# Extract video URLs from response.resultUrls field
|
|
107
|
-
# Dedicated API nests the results inside a 'response' object
|
|
108
|
-
response_data = result_data.get("response")
|
|
109
|
-
if not response_data:
|
|
110
|
-
raise ValueError(f"No response field in result. Response: {result_data}")
|
|
111
|
-
|
|
112
|
-
result_urls = response_data.get("resultUrls")
|
|
113
|
-
if not result_urls or not isinstance(result_urls, list):
|
|
114
|
-
raise ValueError(f"No resultUrls in response. Response: {result_data}")
|
|
115
|
-
|
|
116
|
-
# Determine video dimensions based on aspect ratio
|
|
117
|
-
# Default to 1080p quality
|
|
118
|
-
if inputs.aspect_ratio == "16:9":
|
|
119
|
-
width, height = 1920, 1080
|
|
120
|
-
elif inputs.aspect_ratio == "9:16":
|
|
121
|
-
width, height = 1080, 1920
|
|
122
|
-
else: # Auto
|
|
123
|
-
# Default to 16:9 for Auto
|
|
124
|
-
width, height = 1920, 1080
|
|
125
|
-
|
|
126
|
-
# Veo 3 generates ~8 second videos by default
|
|
127
|
-
duration = 8.0
|
|
128
|
-
|
|
129
|
-
# Store each video using output_index
|
|
130
|
-
artifacts = []
|
|
131
|
-
for idx, video_url in enumerate(result_urls):
|
|
132
|
-
if not video_url:
|
|
133
|
-
raise ValueError(f"Video {idx} missing URL in Kie.ai response")
|
|
134
|
-
|
|
135
|
-
artifact = await context.store_video_result(
|
|
136
|
-
storage_url=video_url,
|
|
137
|
-
format="mp4",
|
|
138
|
-
width=width,
|
|
139
|
-
height=height,
|
|
140
|
-
duration=duration,
|
|
141
|
-
output_index=idx,
|
|
142
|
-
)
|
|
143
|
-
artifacts.append(artifact)
|
|
144
|
-
|
|
145
|
-
return GeneratorResult(outputs=artifacts)
|
|
146
|
-
|
|
147
|
-
async def estimate_cost(self, inputs: KieVeo3Input) -> float:
|
|
148
|
-
"""Estimate cost for Veo 3.1 video generation.
|
|
149
|
-
|
|
150
|
-
Veo 3.1 pricing is estimated based on typical video generation costs.
|
|
151
|
-
Actual pricing should be verified at https://kie.ai/pricing
|
|
152
|
-
|
|
153
|
-
Base cost estimates:
|
|
154
|
-
- veo3: $0.08 per video (standard quality)
|
|
155
|
-
- veo3_fast: $0.04 per video (faster generation)
|
|
156
|
-
"""
|
|
157
|
-
# Cost varies by model
|
|
158
|
-
if inputs.model == "veo3_fast":
|
|
159
|
-
return 0.04
|
|
160
|
-
else: # veo3
|
|
161
|
-
return 0.08
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""OpenAI provider generators."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""OpenAI audio generators."""
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Whisper audio transcription using OpenAI API.
|
|
3
|
-
|
|
4
|
-
Demonstrates audio processing generator that outputs text.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from pydantic import BaseModel, Field
|
|
8
|
-
|
|
9
|
-
from ....artifacts import AudioArtifact
|
|
10
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class WhisperInput(BaseModel):
|
|
14
|
-
"""Input schema for Whisper transcription."""
|
|
15
|
-
|
|
16
|
-
audio_source: AudioArtifact = Field(description="Audio file to transcribe")
|
|
17
|
-
language: str = Field(default="en", description="Language code (e.g., 'en', 'es', 'fr')")
|
|
18
|
-
prompt: str = Field(default="", description="Optional prompt to guide transcription")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class OpenAIWhisperGenerator(BaseGenerator):
|
|
22
|
-
"""Whisper speech-to-text transcription using OpenAI API."""
|
|
23
|
-
|
|
24
|
-
name = "openai-whisper"
|
|
25
|
-
artifact_type = "text"
|
|
26
|
-
description = "OpenAI: Whisper - speech-to-text transcription"
|
|
27
|
-
|
|
28
|
-
def get_input_schema(self) -> type[WhisperInput]:
|
|
29
|
-
return WhisperInput
|
|
30
|
-
|
|
31
|
-
async def generate(
|
|
32
|
-
self, inputs: WhisperInput, context: GeneratorExecutionContext
|
|
33
|
-
) -> GeneratorResult:
|
|
34
|
-
"""Transcribe audio using OpenAI Whisper."""
|
|
35
|
-
try:
|
|
36
|
-
from openai import AsyncOpenAI
|
|
37
|
-
except ImportError as e:
|
|
38
|
-
raise ImportError(
|
|
39
|
-
"OpenAI SDK is required for WhisperGenerator. "
|
|
40
|
-
"Install with: pip install weirdfingers-boards[generators-openai]"
|
|
41
|
-
) from e
|
|
42
|
-
|
|
43
|
-
client = AsyncOpenAI()
|
|
44
|
-
|
|
45
|
-
# Resolve audio artifact to file path via context
|
|
46
|
-
audio_file_path = await context.resolve_artifact(inputs.audio_source)
|
|
47
|
-
|
|
48
|
-
# Use OpenAI SDK for transcription
|
|
49
|
-
with open(audio_file_path, "rb") as audio_file:
|
|
50
|
-
transcript = await client.audio.transcriptions.create(
|
|
51
|
-
model="whisper-1",
|
|
52
|
-
file=audio_file,
|
|
53
|
-
language=inputs.language,
|
|
54
|
-
prompt=inputs.prompt,
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
# Create text artifact
|
|
58
|
-
text_artifact = await context.store_text_result(
|
|
59
|
-
content=transcript.text,
|
|
60
|
-
format="plain",
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
return GeneratorResult(outputs=[text_artifact])
|
|
64
|
-
|
|
65
|
-
async def estimate_cost(self, inputs: WhisperInput) -> float:
|
|
66
|
-
"""Estimate cost for Whisper transcription."""
|
|
67
|
-
# Whisper pricing is $0.006 per minute
|
|
68
|
-
duration_minutes = (inputs.audio_source.duration or 60) / 60
|
|
69
|
-
return duration_minutes * 0.006
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""OpenAI image generators."""
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
DALL-E 3 generator using OpenAI API.
|
|
3
|
-
|
|
4
|
-
Demonstrates integration with OpenAI's SDK for image generation.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
|
|
9
|
-
from pydantic import BaseModel, Field
|
|
10
|
-
|
|
11
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class DallE3Input(BaseModel):
|
|
15
|
-
"""Input schema for DALL-E 3 image generation."""
|
|
16
|
-
|
|
17
|
-
prompt: str = Field(description="Text prompt for image generation")
|
|
18
|
-
size: str = Field(
|
|
19
|
-
default="1024x1024",
|
|
20
|
-
description="Image size",
|
|
21
|
-
pattern="^(1024x1024|1024x1792|1792x1024)$",
|
|
22
|
-
)
|
|
23
|
-
quality: str = Field(default="standard", description="Image quality", pattern="^(standard|hd)$")
|
|
24
|
-
style: str = Field(default="vivid", description="Image style", pattern="^(vivid|natural)$")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class OpenAIDallE3Generator(BaseGenerator):
|
|
28
|
-
"""DALL-E 3 image generator using OpenAI API."""
|
|
29
|
-
|
|
30
|
-
name = "openai-dall-e-3"
|
|
31
|
-
artifact_type = "image"
|
|
32
|
-
description = "OpenAI: DALL-E 3 - advanced text-to-image generation"
|
|
33
|
-
|
|
34
|
-
def get_input_schema(self) -> type[DallE3Input]:
|
|
35
|
-
return DallE3Input
|
|
36
|
-
|
|
37
|
-
async def generate(
|
|
38
|
-
self, inputs: DallE3Input, context: GeneratorExecutionContext
|
|
39
|
-
) -> GeneratorResult:
|
|
40
|
-
"""Generate image using OpenAI DALL-E 3."""
|
|
41
|
-
# Check for API key
|
|
42
|
-
if not os.getenv("OPENAI_API_KEY"):
|
|
43
|
-
raise ValueError("API configuration invalid")
|
|
44
|
-
|
|
45
|
-
# Import SDK directly
|
|
46
|
-
try:
|
|
47
|
-
from openai import AsyncOpenAI
|
|
48
|
-
except ImportError as e:
|
|
49
|
-
raise ImportError(
|
|
50
|
-
"OpenAI SDK is required for DallE3Generator. "
|
|
51
|
-
"Install with: pip install weirdfingers-boards[generators-openai]"
|
|
52
|
-
) from e
|
|
53
|
-
|
|
54
|
-
client = AsyncOpenAI()
|
|
55
|
-
|
|
56
|
-
# Use OpenAI SDK directly
|
|
57
|
-
response = await client.images.generate(
|
|
58
|
-
model="dall-e-3",
|
|
59
|
-
prompt=inputs.prompt,
|
|
60
|
-
size=inputs.size, # pyright: ignore[reportArgumentType]
|
|
61
|
-
quality=inputs.quality, # pyright: ignore[reportArgumentType]
|
|
62
|
-
style=inputs.style, # pyright: ignore[reportArgumentType]
|
|
63
|
-
n=1,
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
# Get the generated image URL
|
|
67
|
-
if not response.data or not response.data[0].url:
|
|
68
|
-
raise ValueError("No image generated")
|
|
69
|
-
image_url = response.data[0].url
|
|
70
|
-
|
|
71
|
-
# Parse dimensions from size
|
|
72
|
-
width, height = map(int, inputs.size.split("x"))
|
|
73
|
-
|
|
74
|
-
# Store via context (downloads from OpenAI and uploads to our storage)
|
|
75
|
-
image_artifact = await context.store_image_result(
|
|
76
|
-
storage_url=image_url,
|
|
77
|
-
format="png", # DALL-E 3 outputs PNG
|
|
78
|
-
width=width,
|
|
79
|
-
height=height,
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
return GeneratorResult(outputs=[image_artifact])
|
|
83
|
-
|
|
84
|
-
async def estimate_cost(self, inputs: DallE3Input) -> float:
|
|
85
|
-
"""Estimate cost for DALL-E 3 generation."""
|
|
86
|
-
# DALL-E 3 pricing varies by quality and size
|
|
87
|
-
if inputs.quality == "hd":
|
|
88
|
-
if inputs.size in ["1024x1792", "1792x1024"]:
|
|
89
|
-
return 0.080 # HD, non-square
|
|
90
|
-
else:
|
|
91
|
-
return 0.080 # HD, square
|
|
92
|
-
else: # standard quality
|
|
93
|
-
if inputs.size in ["1024x1792", "1792x1024"]:
|
|
94
|
-
return 0.040 # Standard, non-square
|
|
95
|
-
else:
|
|
96
|
-
return 0.040 # Standard, square
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Replicate provider generators."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Replicate image generators."""
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
FLUX.1.1 Pro generator using Replicate API.
|
|
3
|
-
|
|
4
|
-
This demonstrates the simple pattern for creating generators:
|
|
5
|
-
1. Define Pydantic input/output models
|
|
6
|
-
2. Implement generation logic using provider SDK directly
|
|
7
|
-
3. Register with the global registry
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
from collections.abc import AsyncIterator
|
|
12
|
-
|
|
13
|
-
from pydantic import BaseModel, Field
|
|
14
|
-
|
|
15
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class FluxProInput(BaseModel):
|
|
19
|
-
"""Input schema for FLUX.1.1 Pro image generation."""
|
|
20
|
-
|
|
21
|
-
prompt: str = Field(description="Text prompt for image generation")
|
|
22
|
-
aspect_ratio: str = Field(
|
|
23
|
-
default="1:1",
|
|
24
|
-
description="Image aspect ratio",
|
|
25
|
-
pattern="^(1:1|16:9|21:9|2:3|3:2|4:5|5:4|9:16|9:21)$",
|
|
26
|
-
)
|
|
27
|
-
safety_tolerance: int = Field(default=2, ge=1, le=5, description="Safety tolerance level (1-5)")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class ReplicateFluxProGenerator(BaseGenerator):
|
|
31
|
-
"""FLUX.1.1 Pro image generator using Replicate."""
|
|
32
|
-
|
|
33
|
-
name = "replicate-flux-pro"
|
|
34
|
-
artifact_type = "image"
|
|
35
|
-
description = "Replicate: FLUX.1.1 [pro] by Black Forest Labs - high-quality image generation"
|
|
36
|
-
|
|
37
|
-
def get_input_schema(self) -> type[FluxProInput]:
|
|
38
|
-
return FluxProInput
|
|
39
|
-
|
|
40
|
-
async def generate(
|
|
41
|
-
self, inputs: FluxProInput, context: GeneratorExecutionContext
|
|
42
|
-
) -> GeneratorResult:
|
|
43
|
-
"""Generate image using Replicate FLUX.1.1 Pro model."""
|
|
44
|
-
# Check for API key
|
|
45
|
-
if not os.getenv("REPLICATE_API_TOKEN"):
|
|
46
|
-
raise ValueError("API configuration invalid. Missing REPLICATE_API_TOKEN")
|
|
47
|
-
|
|
48
|
-
# Import SDK directly
|
|
49
|
-
try:
|
|
50
|
-
import replicate
|
|
51
|
-
from replicate.helpers import FileOutput
|
|
52
|
-
except ImportError as e:
|
|
53
|
-
raise ImportError(
|
|
54
|
-
"Replicate SDK is required for FluxProGenerator. "
|
|
55
|
-
"Install with: pip install weirdfingers-boards[generators-replicate]"
|
|
56
|
-
) from e
|
|
57
|
-
|
|
58
|
-
# Use Replicate SDK directly
|
|
59
|
-
prediction: FileOutput | AsyncIterator[FileOutput] = await replicate.async_run(
|
|
60
|
-
"black-forest-labs/flux-1.1-pro",
|
|
61
|
-
input={
|
|
62
|
-
"prompt": inputs.prompt,
|
|
63
|
-
"aspect_ratio": inputs.aspect_ratio,
|
|
64
|
-
"safety_tolerance": inputs.safety_tolerance,
|
|
65
|
-
},
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
# If prediction is an async iterator, get the first item; else, use as is.
|
|
69
|
-
if isinstance(prediction, AsyncIterator):
|
|
70
|
-
file_output = await anext(prediction)
|
|
71
|
-
else:
|
|
72
|
-
file_output = prediction
|
|
73
|
-
|
|
74
|
-
output_url = file_output.url
|
|
75
|
-
|
|
76
|
-
image_artifact = await context.store_image_result(
|
|
77
|
-
storage_url=output_url,
|
|
78
|
-
format="png",
|
|
79
|
-
width=1024,
|
|
80
|
-
height=1024,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
return GeneratorResult(outputs=[image_artifact])
|
|
84
|
-
|
|
85
|
-
async def estimate_cost(self, inputs: FluxProInput) -> float:
|
|
86
|
-
"""Estimate cost for FLUX.1.1 Pro generation."""
|
|
87
|
-
# FLUX.1.1 Pro typically costs around $0.055 per generation
|
|
88
|
-
return 0.055
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Replicate video generators."""
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Lipsync generator using Replicate API.
|
|
3
|
-
|
|
4
|
-
This demonstrates how generators can use multiple artifact inputs
|
|
5
|
-
with automatic artifact resolution.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from pydantic import BaseModel, Field
|
|
9
|
-
|
|
10
|
-
from ....artifacts import AudioArtifact, VideoArtifact
|
|
11
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class LipsyncInput(BaseModel):
|
|
15
|
-
"""Input schema for lipsync generation."""
|
|
16
|
-
|
|
17
|
-
audio_source: AudioArtifact = Field(description="Audio track for lip sync")
|
|
18
|
-
video_source: VideoArtifact = Field(description="Video to sync lips in")
|
|
19
|
-
prompt: str | None = Field(None, description="Optional prompt for generation")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class ReplicateLipsyncGenerator(BaseGenerator):
|
|
23
|
-
"""Lipsync generator that syncs lips in video to audio."""
|
|
24
|
-
|
|
25
|
-
name = "replicate-lipsync"
|
|
26
|
-
artifact_type = "video"
|
|
27
|
-
description = "Replicate: Sync lips in video to match audio track"
|
|
28
|
-
|
|
29
|
-
def get_input_schema(self) -> type[LipsyncInput]:
|
|
30
|
-
return LipsyncInput
|
|
31
|
-
|
|
32
|
-
async def generate(
|
|
33
|
-
self, inputs: LipsyncInput, context: GeneratorExecutionContext
|
|
34
|
-
) -> GeneratorResult:
|
|
35
|
-
"""Generate lip-synced video."""
|
|
36
|
-
# Import SDK directly
|
|
37
|
-
try:
|
|
38
|
-
import replicate # type: ignore
|
|
39
|
-
except ImportError as e:
|
|
40
|
-
raise ImportError(
|
|
41
|
-
"Replicate SDK is required for LipsyncGenerator. "
|
|
42
|
-
"Install with: pip install weirdfingers-boards[generators-replicate]"
|
|
43
|
-
) from e
|
|
44
|
-
|
|
45
|
-
# Resolve artifacts via context
|
|
46
|
-
audio_file = await context.resolve_artifact(inputs.audio_source)
|
|
47
|
-
video_file = await context.resolve_artifact(inputs.video_source)
|
|
48
|
-
|
|
49
|
-
# Use Replicate SDK directly with proper file handling
|
|
50
|
-
with open(audio_file, "rb") as audio_f, open(video_file, "rb") as video_f:
|
|
51
|
-
result = await replicate.async_run(
|
|
52
|
-
"cjwbw/wav2lip",
|
|
53
|
-
input={
|
|
54
|
-
"audio": audio_f,
|
|
55
|
-
"video": video_f,
|
|
56
|
-
},
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# Store output and create artifact via context
|
|
60
|
-
video_artifact = await context.store_video_result(
|
|
61
|
-
storage_url=str(result),
|
|
62
|
-
format="mp4",
|
|
63
|
-
width=inputs.video_source.width,
|
|
64
|
-
height=inputs.video_source.height,
|
|
65
|
-
duration=inputs.audio_source.duration,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
return GeneratorResult(outputs=[video_artifact])
|
|
69
|
-
|
|
70
|
-
async def estimate_cost(self, inputs: LipsyncInput) -> float:
|
|
71
|
-
"""Estimate cost for lipsync generation."""
|
|
72
|
-
# Wav2Lip is typically free on Replicate, but let's add a small cost
|
|
73
|
-
return 0.01
|