@weirdfingers/baseboards 0.9.5 → 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 +561 -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,222 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ByteDance SeedDance v1 Pro image-to-video generator.
|
|
3
|
-
|
|
4
|
-
A high quality video generation model developed by ByteDance that converts static
|
|
5
|
-
images into dynamic videos based on textual descriptions.
|
|
6
|
-
|
|
7
|
-
Based on Fal AI's fal-ai/bytedance/seedance/v1/pro/image-to-video model.
|
|
8
|
-
See: https://fal.ai/models/fal-ai/bytedance/seedance/v1/pro/image-to-video
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import os
|
|
12
|
-
from typing import Literal
|
|
13
|
-
|
|
14
|
-
from pydantic import BaseModel, Field
|
|
15
|
-
|
|
16
|
-
from ....artifacts import ImageArtifact
|
|
17
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class BytedanceSeedanceV1ProImageToVideoInput(BaseModel):
|
|
21
|
-
"""Input schema for ByteDance SeedDance v1 Pro image-to-video generation.
|
|
22
|
-
|
|
23
|
-
Artifact fields (image, end_image) are automatically detected via type
|
|
24
|
-
introspection and resolved from generation IDs to ImageArtifact objects.
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
prompt: str = Field(description="Text description for video generation")
|
|
28
|
-
image: ImageArtifact = Field(description="Source image for conversion to video")
|
|
29
|
-
aspect_ratio: Literal["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "auto"] = Field(
|
|
30
|
-
default="auto",
|
|
31
|
-
description=(
|
|
32
|
-
"Aspect ratio of the generated video. 'auto' uses the aspect ratio from input image"
|
|
33
|
-
),
|
|
34
|
-
)
|
|
35
|
-
resolution: Literal["480p", "720p", "1080p"] = Field(
|
|
36
|
-
default="1080p",
|
|
37
|
-
description="Resolution of the generated video",
|
|
38
|
-
)
|
|
39
|
-
duration: Literal["2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] = Field(
|
|
40
|
-
default="5",
|
|
41
|
-
description="Duration of the generated video in seconds",
|
|
42
|
-
)
|
|
43
|
-
end_image: ImageArtifact | None = Field(
|
|
44
|
-
default=None,
|
|
45
|
-
description="Optional ending frame image for directional guidance",
|
|
46
|
-
)
|
|
47
|
-
camera_fixed: bool = Field(
|
|
48
|
-
default=False,
|
|
49
|
-
description="Lock camera position to prevent camera movement",
|
|
50
|
-
)
|
|
51
|
-
seed: int | None = Field(
|
|
52
|
-
default=None,
|
|
53
|
-
description="Random seed for reproducible results. Use -1 for random seed",
|
|
54
|
-
)
|
|
55
|
-
enable_safety_checker: bool = Field(
|
|
56
|
-
default=True,
|
|
57
|
-
description="Activate content filtering to ensure safe outputs",
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class FalBytedanceSeedanceV1ProImageToVideoGenerator(BaseGenerator):
|
|
62
|
-
"""Generator for converting images to videos using ByteDance SeedDance v1 Pro."""
|
|
63
|
-
|
|
64
|
-
name = "fal-bytedance-seedance-v1-pro-image-to-video"
|
|
65
|
-
description = "Fal: SeedDance v1 Pro - High quality image-to-video generation by ByteDance"
|
|
66
|
-
artifact_type = "video"
|
|
67
|
-
|
|
68
|
-
def get_input_schema(self) -> type[BytedanceSeedanceV1ProImageToVideoInput]:
|
|
69
|
-
"""Return the input schema for this generator."""
|
|
70
|
-
return BytedanceSeedanceV1ProImageToVideoInput
|
|
71
|
-
|
|
72
|
-
async def generate(
|
|
73
|
-
self, inputs: BytedanceSeedanceV1ProImageToVideoInput, context: GeneratorExecutionContext
|
|
74
|
-
) -> GeneratorResult:
|
|
75
|
-
"""Generate video using fal.ai bytedance/seedance/v1/pro/image-to-video."""
|
|
76
|
-
# Check for API key
|
|
77
|
-
if not os.getenv("FAL_KEY"):
|
|
78
|
-
raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
|
|
79
|
-
|
|
80
|
-
# Import fal_client
|
|
81
|
-
try:
|
|
82
|
-
import fal_client
|
|
83
|
-
except ImportError as e:
|
|
84
|
-
raise ImportError(
|
|
85
|
-
"fal.ai SDK is required for FalBytedanceSeedanceV1ProImageToVideoGenerator. "
|
|
86
|
-
"Install with: pip install weirdfingers-boards[generators-fal]"
|
|
87
|
-
) from e
|
|
88
|
-
|
|
89
|
-
# Upload image artifacts to Fal's public storage
|
|
90
|
-
# Fal API requires publicly accessible URLs
|
|
91
|
-
from ..utils import upload_artifacts_to_fal
|
|
92
|
-
|
|
93
|
-
image_urls = await upload_artifacts_to_fal([inputs.image], context)
|
|
94
|
-
|
|
95
|
-
# Prepare arguments for fal.ai API
|
|
96
|
-
arguments = {
|
|
97
|
-
"prompt": inputs.prompt,
|
|
98
|
-
"image_url": image_urls[0],
|
|
99
|
-
"aspect_ratio": inputs.aspect_ratio,
|
|
100
|
-
"resolution": inputs.resolution,
|
|
101
|
-
"duration": inputs.duration,
|
|
102
|
-
"camera_fixed": inputs.camera_fixed,
|
|
103
|
-
"enable_safety_checker": inputs.enable_safety_checker,
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
# Upload end image if provided
|
|
107
|
-
if inputs.end_image is not None:
|
|
108
|
-
end_image_urls = await upload_artifacts_to_fal([inputs.end_image], context)
|
|
109
|
-
arguments["end_image_url"] = end_image_urls[0]
|
|
110
|
-
|
|
111
|
-
# Add seed if provided
|
|
112
|
-
if inputs.seed is not None:
|
|
113
|
-
arguments["seed"] = inputs.seed
|
|
114
|
-
|
|
115
|
-
# Submit async job
|
|
116
|
-
handler = await fal_client.submit_async(
|
|
117
|
-
"fal-ai/bytedance/seedance/v1/pro/image-to-video",
|
|
118
|
-
arguments=arguments,
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# Store external job ID
|
|
122
|
-
await context.set_external_job_id(handler.request_id)
|
|
123
|
-
|
|
124
|
-
# Stream progress updates
|
|
125
|
-
from .....progress.models import ProgressUpdate
|
|
126
|
-
|
|
127
|
-
event_count = 0
|
|
128
|
-
async for event in handler.iter_events(with_logs=True):
|
|
129
|
-
event_count += 1
|
|
130
|
-
# Sample every 3rd event to avoid spam
|
|
131
|
-
if event_count % 3 == 0:
|
|
132
|
-
# Extract logs if available
|
|
133
|
-
logs = getattr(event, "logs", None)
|
|
134
|
-
if logs:
|
|
135
|
-
# Join log entries into a single message
|
|
136
|
-
if isinstance(logs, list):
|
|
137
|
-
message = " | ".join(str(log) for log in logs if log)
|
|
138
|
-
else:
|
|
139
|
-
message = str(logs)
|
|
140
|
-
|
|
141
|
-
if message:
|
|
142
|
-
await context.publish_progress(
|
|
143
|
-
ProgressUpdate(
|
|
144
|
-
job_id=handler.request_id,
|
|
145
|
-
status="processing",
|
|
146
|
-
progress=50.0,
|
|
147
|
-
phase="processing",
|
|
148
|
-
message=message,
|
|
149
|
-
)
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
# Get final result
|
|
153
|
-
result = await handler.get()
|
|
154
|
-
|
|
155
|
-
# Extract video from result
|
|
156
|
-
# Expected structure: {"video": {"url": "...", "content_type": "...", ...}, "seed": 123}
|
|
157
|
-
video_data = result.get("video")
|
|
158
|
-
if not video_data:
|
|
159
|
-
raise ValueError("No video returned from fal.ai API")
|
|
160
|
-
|
|
161
|
-
video_url = video_data.get("url")
|
|
162
|
-
if not video_url:
|
|
163
|
-
raise ValueError("Video missing URL in fal.ai response")
|
|
164
|
-
|
|
165
|
-
# Determine video dimensions based on resolution
|
|
166
|
-
# Resolution mapping based on standard video dimensions
|
|
167
|
-
resolution_map = {
|
|
168
|
-
"480p": (854, 480),
|
|
169
|
-
"720p": (1280, 720),
|
|
170
|
-
"1080p": (1920, 1080),
|
|
171
|
-
}
|
|
172
|
-
width, height = resolution_map.get(inputs.resolution, (1920, 1080))
|
|
173
|
-
|
|
174
|
-
# Parse duration from string format
|
|
175
|
-
duration_seconds = int(inputs.duration)
|
|
176
|
-
|
|
177
|
-
# Store video result
|
|
178
|
-
artifact = await context.store_video_result(
|
|
179
|
-
storage_url=video_url,
|
|
180
|
-
format="mp4",
|
|
181
|
-
width=width,
|
|
182
|
-
height=height,
|
|
183
|
-
duration=duration_seconds,
|
|
184
|
-
output_index=0,
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
return GeneratorResult(outputs=[artifact])
|
|
188
|
-
|
|
189
|
-
async def estimate_cost(self, inputs: BytedanceSeedanceV1ProImageToVideoInput) -> float:
|
|
190
|
-
"""Estimate cost for this generation in USD.
|
|
191
|
-
|
|
192
|
-
Based on fal.ai pricing: approximately $0.74 per 1080p 5-second video.
|
|
193
|
-
Cost scales based on resolution and duration.
|
|
194
|
-
|
|
195
|
-
Formula: tokens(video) = (height x width x FPS x duration) / 1024
|
|
196
|
-
Pricing: $3.0 per 1 million video tokens for image-to-video
|
|
197
|
-
"""
|
|
198
|
-
# Resolution mapping
|
|
199
|
-
resolution_map = {
|
|
200
|
-
"480p": (854, 480),
|
|
201
|
-
"720p": (1280, 720),
|
|
202
|
-
"1080p": (1920, 1080),
|
|
203
|
-
}
|
|
204
|
-
width, height = resolution_map.get(inputs.resolution, (1920, 1080))
|
|
205
|
-
|
|
206
|
-
# Parse duration
|
|
207
|
-
duration_seconds = int(inputs.duration)
|
|
208
|
-
|
|
209
|
-
# Assume 30 FPS for video generation
|
|
210
|
-
fps = 30
|
|
211
|
-
|
|
212
|
-
# Calculate video tokens
|
|
213
|
-
# Formula from fal.ai: tokens = (height * width * fps * duration) / 1024
|
|
214
|
-
tokens = (height * width * fps * duration_seconds) / 1024
|
|
215
|
-
|
|
216
|
-
# Price per million tokens for image-to-video
|
|
217
|
-
price_per_million_tokens = 3.0
|
|
218
|
-
|
|
219
|
-
# Calculate cost
|
|
220
|
-
cost = (tokens / 1_000_000) * price_per_million_tokens
|
|
221
|
-
|
|
222
|
-
return cost
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MiniMax Hailuo 02 [Standard] text-to-video generator.
|
|
3
|
-
|
|
4
|
-
Advanced video generation model with 768p resolution that converts text prompts
|
|
5
|
-
into video content. Supports 6 and 10 second durations with optional prompt optimization.
|
|
6
|
-
|
|
7
|
-
Based on Fal AI's fal-ai/minimax/hailuo-02/standard/text-to-video model.
|
|
8
|
-
See: https://fal.ai/models/fal-ai/minimax/hailuo-02/standard/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 FalMinimaxHailuo02StandardTextToVideoInput(BaseModel):
|
|
20
|
-
"""Input schema for MiniMax Hailuo 02 Standard text-to-video generation."""
|
|
21
|
-
|
|
22
|
-
prompt: str = Field(
|
|
23
|
-
description="Text description of the video to generate",
|
|
24
|
-
min_length=1,
|
|
25
|
-
max_length=2000,
|
|
26
|
-
)
|
|
27
|
-
duration: Literal["6", "10"] = Field(
|
|
28
|
-
default="6",
|
|
29
|
-
description="Video duration in seconds. Choose 6 or 10 seconds.",
|
|
30
|
-
)
|
|
31
|
-
prompt_optimizer: bool = Field(
|
|
32
|
-
default=True,
|
|
33
|
-
description="Enable the model's prompt optimization to enhance generation quality",
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class FalMinimaxHailuo02StandardTextToVideoGenerator(BaseGenerator):
|
|
38
|
-
"""Generator for text-to-video using MiniMax Hailuo 02 Standard model."""
|
|
39
|
-
|
|
40
|
-
name = "fal-minimax-hailuo-02-standard-text-to-video"
|
|
41
|
-
description = "Fal: MiniMax Hailuo 02 [Standard] - Advanced 768p text-to-video generation"
|
|
42
|
-
artifact_type = "video"
|
|
43
|
-
|
|
44
|
-
def get_input_schema(self) -> type[FalMinimaxHailuo02StandardTextToVideoInput]:
|
|
45
|
-
"""Return the input schema for this generator."""
|
|
46
|
-
return FalMinimaxHailuo02StandardTextToVideoInput
|
|
47
|
-
|
|
48
|
-
async def generate(
|
|
49
|
-
self,
|
|
50
|
-
inputs: FalMinimaxHailuo02StandardTextToVideoInput,
|
|
51
|
-
context: GeneratorExecutionContext,
|
|
52
|
-
) -> GeneratorResult:
|
|
53
|
-
"""Generate video using fal.ai MiniMax Hailuo 02 Standard model."""
|
|
54
|
-
# Check for API key
|
|
55
|
-
if not os.getenv("FAL_KEY"):
|
|
56
|
-
raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
|
|
57
|
-
|
|
58
|
-
# Import fal_client
|
|
59
|
-
try:
|
|
60
|
-
import fal_client
|
|
61
|
-
except ImportError as e:
|
|
62
|
-
raise ImportError(
|
|
63
|
-
"fal.ai SDK is required for FalMinimaxHailuo02StandardTextToVideoGenerator. "
|
|
64
|
-
"Install with: pip install weirdfingers-boards[generators-fal]"
|
|
65
|
-
) from e
|
|
66
|
-
|
|
67
|
-
# Prepare arguments for fal.ai API
|
|
68
|
-
arguments = {
|
|
69
|
-
"prompt": inputs.prompt,
|
|
70
|
-
"duration": inputs.duration,
|
|
71
|
-
"prompt_optimizer": inputs.prompt_optimizer,
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
# Submit async job
|
|
75
|
-
handler = await fal_client.submit_async(
|
|
76
|
-
"fal-ai/minimax/hailuo-02/standard/text-to-video",
|
|
77
|
-
arguments=arguments,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# Store external job ID
|
|
81
|
-
await context.set_external_job_id(handler.request_id)
|
|
82
|
-
|
|
83
|
-
# Stream progress updates
|
|
84
|
-
from .....progress.models import ProgressUpdate
|
|
85
|
-
|
|
86
|
-
event_count = 0
|
|
87
|
-
async for event in handler.iter_events(with_logs=True):
|
|
88
|
-
event_count += 1
|
|
89
|
-
# Sample every 3rd event to avoid spam
|
|
90
|
-
if event_count % 3 == 0:
|
|
91
|
-
# Extract logs if available
|
|
92
|
-
logs = getattr(event, "logs", None)
|
|
93
|
-
if logs:
|
|
94
|
-
# Join log entries into a single message
|
|
95
|
-
if isinstance(logs, list):
|
|
96
|
-
message = " | ".join(str(log) for log in logs if log)
|
|
97
|
-
else:
|
|
98
|
-
message = str(logs)
|
|
99
|
-
|
|
100
|
-
if message:
|
|
101
|
-
await context.publish_progress(
|
|
102
|
-
ProgressUpdate(
|
|
103
|
-
job_id=handler.request_id,
|
|
104
|
-
status="processing",
|
|
105
|
-
progress=50.0, # Approximate mid-point progress
|
|
106
|
-
phase="processing",
|
|
107
|
-
message=message,
|
|
108
|
-
)
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
# Get final result
|
|
112
|
-
result = await handler.get()
|
|
113
|
-
|
|
114
|
-
# Extract video from result
|
|
115
|
-
# fal.ai returns: {"video": {"url": "...", "content_type": "video/mp4", ...}}
|
|
116
|
-
video_data = result.get("video")
|
|
117
|
-
if not video_data:
|
|
118
|
-
raise ValueError("No video returned from fal.ai API")
|
|
119
|
-
|
|
120
|
-
video_url = video_data.get("url")
|
|
121
|
-
if not video_url:
|
|
122
|
-
raise ValueError("Video missing URL in fal.ai response")
|
|
123
|
-
|
|
124
|
-
# Hailuo 02 Standard produces 768p resolution videos
|
|
125
|
-
# Using standard 16:9 aspect ratio dimensions for 768p
|
|
126
|
-
width = 1360
|
|
127
|
-
height = 768
|
|
128
|
-
|
|
129
|
-
# Store video result
|
|
130
|
-
artifact = await context.store_video_result(
|
|
131
|
-
storage_url=video_url,
|
|
132
|
-
format="mp4",
|
|
133
|
-
width=width,
|
|
134
|
-
height=height,
|
|
135
|
-
duration=float(inputs.duration), # Convert "6" or "10" to float
|
|
136
|
-
output_index=0,
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
return GeneratorResult(outputs=[artifact])
|
|
140
|
-
|
|
141
|
-
async def estimate_cost(self, inputs: FalMinimaxHailuo02StandardTextToVideoInput) -> float:
|
|
142
|
-
"""Estimate cost for MiniMax Hailuo 02 Standard generation.
|
|
143
|
-
|
|
144
|
-
Pricing information not provided in official documentation.
|
|
145
|
-
Estimated at $0.12 per video based on typical video generation costs.
|
|
146
|
-
Cost may vary based on duration settings.
|
|
147
|
-
"""
|
|
148
|
-
# Approximate cost per video
|
|
149
|
-
# 10-second videos may cost more than 6-second videos
|
|
150
|
-
base_cost = 0.12
|
|
151
|
-
duration_multiplier = 1.67 if inputs.duration == "10" else 1.0
|
|
152
|
-
return base_cost * duration_multiplier
|
package/templates/api/src/boards/generators/implementations/fal/video/fal_pixverse_lipsync.py
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
PixVerse Lipsync video generator.
|
|
3
|
-
|
|
4
|
-
Generates realistic lip-synchronization animations by synchronizing video with
|
|
5
|
-
audio or text-to-speech. Supports optional audio input or TTS with customizable
|
|
6
|
-
voice selection.
|
|
7
|
-
|
|
8
|
-
Based on Fal AI's fal-ai/pixverse/lipsync model.
|
|
9
|
-
See: https://fal.ai/models/fal-ai/pixverse/lipsync
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import os
|
|
13
|
-
from typing import Literal
|
|
14
|
-
|
|
15
|
-
from pydantic import BaseModel, Field
|
|
16
|
-
|
|
17
|
-
from ....artifacts import AudioArtifact, VideoArtifact
|
|
18
|
-
from ....base import BaseGenerator, GeneratorExecutionContext, GeneratorResult
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class PixverseLipsyncInput(BaseModel):
|
|
22
|
-
"""Input schema for PixVerse Lipsync.
|
|
23
|
-
|
|
24
|
-
Artifact fields are automatically detected via type introspection
|
|
25
|
-
and resolved from generation IDs to artifact objects.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
video_url: VideoArtifact = Field(description="Input video source for lip-sync animation")
|
|
29
|
-
audio_url: AudioArtifact | None = Field(
|
|
30
|
-
default=None, description="Input audio file; if omitted, TTS generates audio from text"
|
|
31
|
-
)
|
|
32
|
-
text: str | None = Field(
|
|
33
|
-
default=None,
|
|
34
|
-
description="Content for text-to-speech synthesis (used when audio_url not provided)",
|
|
35
|
-
)
|
|
36
|
-
voice_id: Literal[
|
|
37
|
-
"Emily",
|
|
38
|
-
"James",
|
|
39
|
-
"Isabella",
|
|
40
|
-
"Liam",
|
|
41
|
-
"Chloe",
|
|
42
|
-
"Adrian",
|
|
43
|
-
"Harper",
|
|
44
|
-
"Ava",
|
|
45
|
-
"Sophia",
|
|
46
|
-
"Julia",
|
|
47
|
-
"Mason",
|
|
48
|
-
"Jack",
|
|
49
|
-
"Oliver",
|
|
50
|
-
"Ethan",
|
|
51
|
-
"Auto",
|
|
52
|
-
] = Field(default="Auto", description="Voice selection for text-to-speech")
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class FalPixverseLipsyncGenerator(BaseGenerator):
|
|
56
|
-
"""Generator for PixVerse lip-sync animation."""
|
|
57
|
-
|
|
58
|
-
name = "fal-pixverse-lipsync"
|
|
59
|
-
description = "Fal: PixVerse Lipsync - Realistic lip-sync animation with audio or TTS"
|
|
60
|
-
artifact_type = "video"
|
|
61
|
-
|
|
62
|
-
def get_input_schema(self) -> type[PixverseLipsyncInput]:
|
|
63
|
-
"""Return the input schema for this generator."""
|
|
64
|
-
return PixverseLipsyncInput
|
|
65
|
-
|
|
66
|
-
async def generate(
|
|
67
|
-
self, inputs: PixverseLipsyncInput, context: GeneratorExecutionContext
|
|
68
|
-
) -> GeneratorResult:
|
|
69
|
-
"""Generate lip-synced video using fal.ai pixverse/lipsync."""
|
|
70
|
-
# Check for API key
|
|
71
|
-
if not os.getenv("FAL_KEY"):
|
|
72
|
-
raise ValueError("API configuration invalid. Missing FAL_KEY environment variable")
|
|
73
|
-
|
|
74
|
-
# Import fal_client
|
|
75
|
-
try:
|
|
76
|
-
import fal_client
|
|
77
|
-
except ImportError as e:
|
|
78
|
-
raise ImportError(
|
|
79
|
-
"fal.ai SDK is required for FalPixverseLipsyncGenerator. "
|
|
80
|
-
"Install with: pip install weirdfingers-boards[generators-fal]"
|
|
81
|
-
) from e
|
|
82
|
-
|
|
83
|
-
# Upload video artifact to Fal's public storage
|
|
84
|
-
from ..utils import upload_artifacts_to_fal
|
|
85
|
-
|
|
86
|
-
video_urls = await upload_artifacts_to_fal([inputs.video_url], context)
|
|
87
|
-
|
|
88
|
-
# Prepare arguments for fal.ai API
|
|
89
|
-
arguments: dict = {
|
|
90
|
-
"video_url": video_urls[0],
|
|
91
|
-
"voice_id": inputs.voice_id,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
# Add audio_url if provided, otherwise add text for TTS
|
|
95
|
-
if inputs.audio_url is not None:
|
|
96
|
-
audio_urls = await upload_artifacts_to_fal([inputs.audio_url], context)
|
|
97
|
-
arguments["audio_url"] = audio_urls[0]
|
|
98
|
-
elif inputs.text is not None:
|
|
99
|
-
arguments["text"] = inputs.text
|
|
100
|
-
else:
|
|
101
|
-
raise ValueError("Either audio_url or text must be provided for lip-sync generation")
|
|
102
|
-
|
|
103
|
-
# Submit async job
|
|
104
|
-
handler = await fal_client.submit_async(
|
|
105
|
-
"fal-ai/pixverse/lipsync",
|
|
106
|
-
arguments=arguments,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
# Store external job ID
|
|
110
|
-
await context.set_external_job_id(handler.request_id)
|
|
111
|
-
|
|
112
|
-
# Stream progress updates
|
|
113
|
-
from .....progress.models import ProgressUpdate
|
|
114
|
-
|
|
115
|
-
event_count = 0
|
|
116
|
-
async for event in handler.iter_events(with_logs=True):
|
|
117
|
-
event_count += 1
|
|
118
|
-
# Sample every 3rd event to avoid spam
|
|
119
|
-
if event_count % 3 == 0:
|
|
120
|
-
# Extract logs if available
|
|
121
|
-
logs = getattr(event, "logs", None)
|
|
122
|
-
if logs:
|
|
123
|
-
# Join log entries into a single message
|
|
124
|
-
if isinstance(logs, list):
|
|
125
|
-
message = " | ".join(str(log) for log in logs if log)
|
|
126
|
-
else:
|
|
127
|
-
message = str(logs)
|
|
128
|
-
|
|
129
|
-
if message:
|
|
130
|
-
await context.publish_progress(
|
|
131
|
-
ProgressUpdate(
|
|
132
|
-
job_id=handler.request_id,
|
|
133
|
-
status="processing",
|
|
134
|
-
progress=50.0, # Approximate mid-point progress
|
|
135
|
-
phase="processing",
|
|
136
|
-
message=message,
|
|
137
|
-
)
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
# Get final result
|
|
141
|
-
result = await handler.get()
|
|
142
|
-
|
|
143
|
-
# Extract video from result
|
|
144
|
-
# fal.ai returns: {"video": {"url": "...", "content_type": "video/mp4", ...}}
|
|
145
|
-
video_data = result.get("video")
|
|
146
|
-
|
|
147
|
-
if not video_data:
|
|
148
|
-
raise ValueError("No video returned from fal.ai API")
|
|
149
|
-
|
|
150
|
-
video_url = video_data.get("url")
|
|
151
|
-
if not video_url:
|
|
152
|
-
raise ValueError("Video missing URL in fal.ai response")
|
|
153
|
-
|
|
154
|
-
# Extract format from content_type (e.g., "video/mp4" -> "mp4")
|
|
155
|
-
content_type = video_data.get("content_type", "video/mp4")
|
|
156
|
-
video_format = content_type.split("/")[-1] if "/" in content_type else "mp4"
|
|
157
|
-
|
|
158
|
-
# Determine output duration
|
|
159
|
-
# If audio provided, use its duration; otherwise use video duration
|
|
160
|
-
output_duration = (
|
|
161
|
-
inputs.audio_url.duration if inputs.audio_url is not None else inputs.video_url.duration
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
# Store the video result
|
|
165
|
-
# Use input video dimensions and fps as they remain unchanged
|
|
166
|
-
artifact = await context.store_video_result(
|
|
167
|
-
storage_url=video_url,
|
|
168
|
-
format=video_format,
|
|
169
|
-
width=inputs.video_url.width,
|
|
170
|
-
height=inputs.video_url.height,
|
|
171
|
-
duration=output_duration,
|
|
172
|
-
fps=inputs.video_url.fps,
|
|
173
|
-
output_index=0,
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
return GeneratorResult(outputs=[artifact])
|
|
177
|
-
|
|
178
|
-
async def estimate_cost(self, inputs: PixverseLipsyncInput) -> float:
|
|
179
|
-
"""Estimate cost for PixVerse Lipsync generation in USD.
|
|
180
|
-
|
|
181
|
-
Pricing:
|
|
182
|
-
- $0.04 per second of output video
|
|
183
|
-
- $0.24 per 100 characters if using TTS (when text provided without audio)
|
|
184
|
-
"""
|
|
185
|
-
# Base cost: $0.04 per second of video
|
|
186
|
-
# Use input video duration as estimate for output duration
|
|
187
|
-
video_duration_seconds = inputs.video_url.duration or 5.0 # Default to 5 seconds if unknown
|
|
188
|
-
video_cost = video_duration_seconds * 0.04
|
|
189
|
-
|
|
190
|
-
# Add TTS cost if using text instead of audio
|
|
191
|
-
tts_cost = 0.0
|
|
192
|
-
if inputs.audio_url is None and inputs.text is not None:
|
|
193
|
-
# $0.24 per 100 characters
|
|
194
|
-
text_length = len(inputs.text)
|
|
195
|
-
tts_cost = (text_length / 100.0) * 0.24
|
|
196
|
-
|
|
197
|
-
return video_cost + tts_cost
|