VibesAI-api 1.3.2__py3-none-any.whl

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.
server/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """FastAPI server exposing vibes_api as REST API."""
2
+ from .app import app, get_client
3
+ __all__ = ["app", "get_client"]
server/app.py ADDED
@@ -0,0 +1,332 @@
1
+ """FastAPI server exposing VibesClient as a REST API."""
2
+ from __future__ import annotations
3
+ import os, sys, threading
4
+ from typing import Any, Dict, List, Optional
5
+ from fastapi import FastAPI, HTTPException, Header, Depends
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import BaseModel
8
+
9
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10
+ from vibes_api import VibesClient, VibesAPIError
11
+
12
+ _client: Optional[VibesClient] = None
13
+ _lock = threading.Lock()
14
+
15
+ def get_client() -> VibesClient:
16
+ global _client
17
+ if _client is None:
18
+ with _lock:
19
+ if _client is None:
20
+ ms = os.environ.get("VIBES_META_SESSION")
21
+ if not ms:
22
+ raise HTTPException(500, "VIBES_META_SESSION env var not set.")
23
+ cf = os.environ.get("VIBES_COOKIE_FILE")
24
+ if cf and os.path.exists(cf):
25
+ try:
26
+ with open(cf) as f: ms = f.read().strip()
27
+ except: pass
28
+ def save(c):
29
+ if cf:
30
+ try:
31
+ with open(cf, "w") as f: f.write(c)
32
+ except: pass
33
+ _client = VibesClient(meta_session=ms, auto_refresh=True,
34
+ background_refresh=True,
35
+ refresh_interval=float(os.environ.get("VIBES_REFRESH_INTERVAL","1500")),
36
+ on_cookie_refresh=save)
37
+ return _client
38
+
39
+ def verify_api_key(x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
40
+ authorization: Optional[str] = Header(None)):
41
+ exp = os.environ.get("VIBES_API_KEY")
42
+ if not exp: return
43
+ prov = x_api_key or (authorization[7:] if authorization and authorization.startswith("Bearer ") else None)
44
+ if prov != exp:
45
+ raise HTTPException(401, "Invalid or missing API key.")
46
+
47
+ def _err(e):
48
+ if isinstance(e, VibesAPIError):
49
+ raise HTTPException(e.status or 500, detail={"error": str(e), "code": e.code, "response": e.response})
50
+ raise HTTPException(500, str(e))
51
+
52
+ class PC(BaseModel):
53
+ name: str = "Untitled"
54
+ composition: Optional[dict] = None
55
+ class PU(BaseModel):
56
+ name: Optional[str] = None
57
+ composition: Optional[dict] = None
58
+ class VG(BaseModel):
59
+ project_id: str; prompt: str; aspect_ratio: str = "9:16"; resolution: str = "480p"
60
+ variations: int = 4; video_model: str = "midjen-short"; image_model: str = "midjen-base"
61
+ prompt_model: str = "gemini-2.5-flash"; ingredients: Optional[List[dict]] = None
62
+ create_ingredients: Optional[List[dict]] = None; start_frame: Optional[dict] = None
63
+ end_frame: Optional[dict] = None; moodboard: Optional[dict] = None
64
+ poll: bool = True; poll_timeout: float = 300.0
65
+ class IG(BaseModel):
66
+ project_id: str; prompt: str; aspect_ratio: str = "1:1"; resolution: str = "480p"
67
+ variations: int = 1; image_model: str = "midjen-base"; prompt_model: str = "gemini-2.5-flash"
68
+ ingredients: Optional[List[dict]] = None; create_ingredients: Optional[List[dict]] = None
69
+ moodboard: Optional[dict] = None
70
+ class IE(BaseModel):
71
+ source_image_ent_id: str; edit_prompt: str; project_id: Optional[str] = None
72
+ class PE(BaseModel):
73
+ prompt: str; project_id: Optional[str] = None; batch_type: str = "videos"
74
+ class TT(BaseModel):
75
+ text: str; voice: str; output_format: str = "mp3"; language: Optional[str] = None
76
+ class LR(BaseModel):
77
+ project_id: str; image_prompt: str; script: str; audio_url: str
78
+ audio_duration_ms: int; engine: str = "midjen"; ingredients: Optional[List[dict]] = None
79
+ aspect_ratio: Optional[str] = None; music_track: Optional[dict] = None
80
+ custom_motion_prompt: Optional[str] = None
81
+ class EV(BaseModel):
82
+ project_id: str; batch_id: str; content_id: Optional[str] = None
83
+ prompt: Optional[str] = None; poll: bool = True; poll_timeout: float = 300.0
84
+ class EDV(BaseModel):
85
+ project_id: str; batch_id: str; content_id: Optional[str] = None
86
+ prompt: str; poll: bool = True; poll_timeout: float = 300.0
87
+ class SL(BaseModel):
88
+ entity_type: str; entity_id: str; expires_at: Optional[str] = None; max_uses: Optional[int] = None
89
+ class IC(BaseModel):
90
+ name: str; ingredient_type: str; source_image_ent_id: Optional[str] = None
91
+ image_url: Optional[str] = None; description: Optional[str] = None
92
+ class TC(BaseModel):
93
+ input: str; instructions: Optional[str] = None; tools: Optional[List[dict]] = None; composition: Optional[dict] = None
94
+ class TE(BaseModel):
95
+ composition: dict
96
+ class PR(BaseModel):
97
+ content_item_id: str; batch_id: Optional[str] = None; caption: Optional[str] = None
98
+ audio_types: Optional[List[str]] = None; prompt: Optional[str] = None
99
+ image_prompt: Optional[str] = None; video_prompt: Optional[str] = None
100
+ class PM(BaseModel):
101
+ prompt: str
102
+ class UI(BaseModel):
103
+ image_base64: str
104
+
105
+ app = FastAPI(title="VibesAI API", version="1.3.0", docs_url="/docs", redoc_url="/redoc")
106
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
107
+ AUTH = [Depends(verify_api_key)]
108
+
109
+ @app.get("/", tags=["Health"])
110
+ async def root(): return {"name": "VibesAI API", "version": "1.3.0", "docs": "/docs"}
111
+
112
+ @app.get("/health", tags=["Health"])
113
+ async def health():
114
+ try:
115
+ u = get_client().get_me(); return {"status": "healthy", "user": u.get("username")}
116
+ except Exception as e: return {"status": "unhealthy", "error": str(e)}
117
+
118
+ @app.get("/api/v1/me", tags=["Auth"], dependencies=AUTH)
119
+ async def me():
120
+ try: return get_client().get_me()
121
+ except Exception as e: _err(e)
122
+
123
+ @app.get("/api/v1/check-token", tags=["Auth"], dependencies=AUTH)
124
+ async def check_token():
125
+ try: return {"valid": get_client().check_token()}
126
+ except Exception as e: _err(e)
127
+
128
+ @app.get("/api/v1/current-cookie", tags=["Auth"], dependencies=AUTH)
129
+ async def current_cookie(): return {"meta_session": get_client().get_current_cookie()}
130
+
131
+ @app.get("/api/v1/projects", tags=["Projects"], dependencies=AUTH)
132
+ async def list_projects(limit: int = 25, offset: int = 0, sort: str = "newest", search: Optional[str] = None):
133
+ try: return get_client().list_projects(limit=limit, offset=offset, sort=sort, search=search)
134
+ except Exception as e: _err(e)
135
+
136
+ @app.post("/api/v1/projects", tags=["Projects"], dependencies=AUTH)
137
+ async def create_project(b: PC):
138
+ try: return get_client().create_project(name=b.name, composition=b.composition)
139
+ except Exception as e: _err(e)
140
+
141
+ @app.get("/api/v1/projects/{pid}", tags=["Projects"], dependencies=AUTH)
142
+ async def get_project(pid: str):
143
+ try: return get_client().get_project(pid)
144
+ except Exception as e: _err(e)
145
+
146
+ @app.put("/api/v1/projects/{pid}", tags=["Projects"], dependencies=AUTH)
147
+ async def update_project(pid: str, b: PU):
148
+ try: return get_client().update_project(pid, name=b.name, composition=b.composition)
149
+ except Exception as e: _err(e)
150
+
151
+ @app.delete("/api/v1/projects/{pid}", tags=["Projects"], dependencies=AUTH)
152
+ async def delete_project(pid: str, delete_assets: bool = False):
153
+ try: get_client().delete_project(pid, delete_assets=delete_assets); return {"success": True}
154
+ except Exception as e: _err(e)
155
+
156
+ @app.post("/api/v1/videos/generate", tags=["Videos"], dependencies=AUTH)
157
+ async def gen_video(b: VG):
158
+ try: return get_client().generate_video(project_id=b.project_id, prompt=b.prompt,
159
+ aspect_ratio=b.aspect_ratio, resolution=b.resolution, variations=b.variations,
160
+ video_model=b.video_model, image_model=b.image_model, prompt_model=b.prompt_model,
161
+ ingredients=b.ingredients, create_ingredients=b.create_ingredients,
162
+ start_frame=b.start_frame, end_frame=b.end_frame, moodboard=b.moodboard,
163
+ poll=b.poll, poll_timeout=b.poll_timeout)
164
+ except Exception as e: _err(e)
165
+
166
+ @app.post("/api/v1/videos/extend", tags=["Videos"], dependencies=AUTH)
167
+ async def ext_video(b: EV):
168
+ try:
169
+ c = get_client(); batch = c.get_batch(b.batch_id)
170
+ src = next((x for x in batch.get("content",[]) if x["id"]==b.content_id), None) or (batch["content"][0] if batch.get("content") else None)
171
+ if not src: raise HTTPException(404, "No content item found")
172
+ return c.extend_video(project_id=b.project_id, source_video=src, prompt=b.prompt, poll=b.poll, poll_timeout=b.poll_timeout)
173
+ except HTTPException: raise
174
+ except Exception as e: _err(e)
175
+
176
+ @app.post("/api/v1/videos/edit", tags=["Videos"], dependencies=AUTH)
177
+ async def edit_video(b: EDV):
178
+ try:
179
+ c = get_client(); batch = c.get_batch(b.batch_id)
180
+ src = next((x for x in batch.get("content",[]) if x["id"]==b.content_id), None) or (batch["content"][0] if batch.get("content") else None)
181
+ if not src: raise HTTPException(404, "No content item found")
182
+ return c.edit_video(project_id=b.project_id, source_video=src, prompt=b.prompt, poll=b.poll, poll_timeout=b.poll_timeout)
183
+ except HTTPException: raise
184
+ except Exception as e: _err(e)
185
+
186
+ @app.post("/api/v1/images/generate", tags=["Images"], dependencies=AUTH)
187
+ async def gen_image(b: IG):
188
+ try: return get_client().generate_image(project_id=b.project_id, prompt=b.prompt,
189
+ aspect_ratio=b.aspect_ratio, resolution=b.resolution, variations=b.variations,
190
+ image_model=b.image_model, prompt_model=b.prompt_model, ingredients=b.ingredients,
191
+ create_ingredients=b.create_ingredients, moodboard=b.moodboard)
192
+ except Exception as e: _err(e)
193
+
194
+ @app.post("/api/v1/images/edit", tags=["Images"], dependencies=AUTH)
195
+ async def edit_image(b: IE):
196
+ try: return get_client().edit_image(source_image_ent_id=b.source_image_ent_id, edit_prompt=b.edit_prompt, project_id=b.project_id)
197
+ except Exception as e: _err(e)
198
+
199
+ @app.post("/api/v1/upload/image", tags=["Uploads"], dependencies=AUTH)
200
+ async def upload_image(b: UI):
201
+ try: return get_client().upload_image(b.image_base64)
202
+ except Exception as e: _err(e)
203
+
204
+ @app.post("/api/v1/prompts/enhance", tags=["Prompts"], dependencies=AUTH)
205
+ async def enhance_prompt(b: PE):
206
+ try: return {"variations": get_client().enhance_prompt(prompt=b.prompt, project_id=b.project_id, batch_type=b.batch_type)}
207
+ except Exception as e: _err(e)
208
+
209
+ @app.get("/api/v1/voices", tags=["TTS"], dependencies=AUTH)
210
+ async def list_voices():
211
+ try: return {"voices": get_client().list_voices()}
212
+ except Exception as e: _err(e)
213
+
214
+ @app.post("/api/v1/tts", tags=["TTS"], dependencies=AUTH)
215
+ async def tts(b: TT):
216
+ try: return get_client().tts(text=b.text, voice=b.voice, output_format=b.output_format, language=b.language)
217
+ except Exception as e: _err(e)
218
+
219
+ @app.get("/api/v1/media", tags=["Media"], dependencies=AUTH)
220
+ async def list_media(limit: int = 50, offset: int = 0, type: Optional[str] = None, search: Optional[str] = None):
221
+ try: return get_client().list_media(limit=limit, offset=offset, type=type, search=search)
222
+ except Exception as e: _err(e)
223
+
224
+ @app.get("/api/v1/media/{item_id}/download", tags=["Media"], dependencies=AUTH)
225
+ async def download_media(item_id: str, type: str = "video"):
226
+ from fastapi import Response; import tempfile
227
+ try:
228
+ c = get_client(); suffix = ".mp4" if type=="video" else ".png"
229
+ with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as f: path = f.name
230
+ if type=="video": c.download_video(item_id, path)
231
+ else: c.download_image(item_id, path)
232
+ with open(path,"rb") as f: content = f.read()
233
+ os.unlink(path)
234
+ return Response(content=content, media_type="video/mp4" if type=="video" else "image/png")
235
+ except Exception as e: _err(e)
236
+
237
+ @app.delete("/api/v1/media/{item_id}", tags=["Media"], dependencies=AUTH)
238
+ async def del_media(item_id: str):
239
+ try: get_client().delete_content_item(item_id); return {"success": True}
240
+ except Exception as e: _err(e)
241
+
242
+ @app.get("/api/v1/batches", tags=["Batches"], dependencies=AUTH)
243
+ async def list_batches(limit: int = 12, offset: int = 0, project_id: Optional[str] = None):
244
+ try: return get_client().list_batches(limit=limit, offset=offset, project_id=project_id)
245
+ except Exception as e: _err(e)
246
+
247
+ @app.get("/api/v1/batches/{bid}", tags=["Batches"], dependencies=AUTH)
248
+ async def get_batch(bid: str):
249
+ try: return get_client().get_batch(bid)
250
+ except Exception as e: _err(e)
251
+
252
+ @app.post("/api/v1/batches/{bid}/poll", tags=["Batches"], dependencies=AUTH)
253
+ async def poll_batch(bid: str, timeout: float = 180.0):
254
+ try: return get_client().poll_batch(bid, timeout=timeout)
255
+ except Exception as e: _err(e)
256
+
257
+ @app.get("/api/v1/ingredients", tags=["Ingredients"], dependencies=AUTH)
258
+ async def list_ingredients(owner_filter: str = "LIBRARY", ingredient_type: Optional[str] = None):
259
+ try: return {"ingredients": get_client().list_ingredients(owner_filter=owner_filter, ingredient_type=ingredient_type)}
260
+ except Exception as e: _err(e)
261
+
262
+ @app.post("/api/v1/ingredients", tags=["Ingredients"], dependencies=AUTH)
263
+ async def create_ingredient(b: IC):
264
+ try: return get_client().create_ingredient(name=b.name, ingredient_type=b.ingredient_type,
265
+ source_image_ent_id=b.source_image_ent_id, image_url=b.image_url, description=b.description)
266
+ except Exception as e: _err(e)
267
+
268
+ @app.delete("/api/v1/ingredients/{iid}", tags=["Ingredients"], dependencies=AUTH)
269
+ async def del_ingredient(iid: str):
270
+ try: get_client().delete_ingredient(iid); return {"success": True}
271
+ except Exception as e: _err(e)
272
+
273
+ @app.post("/api/v1/share-links", tags=["Share"], dependencies=AUTH)
274
+ async def create_share(b: SL):
275
+ try: return get_client().create_share_link(b.entity_type, b.entity_id, expires_at=b.expires_at, max_uses=b.max_uses)
276
+ except Exception as e: _err(e)
277
+
278
+ @app.get("/api/v1/share-links", tags=["Share"], dependencies=AUTH)
279
+ async def list_shares(entity_type: str, entity_id: str):
280
+ try: return {"shareLinks": get_client().list_share_links(entity_type, entity_id)}
281
+ except Exception as e: _err(e)
282
+
283
+ @app.post("/api/v1/timeline/chat", tags=["Timeline"], dependencies=AUTH)
284
+ async def timeline_chat(b: TC):
285
+ try:
286
+ events = []
287
+ for ev in get_client().timeline_chat(b.input, instructions=b.instructions, tools=b.tools, composition=b.composition):
288
+ events.append(ev)
289
+ if ev.get("type") in ("completed","error"): break
290
+ return {"events": events}
291
+ except Exception as e: _err(e)
292
+
293
+ @app.post("/api/v1/timeline/export", tags=["Timeline"], dependencies=AUTH)
294
+ async def export_timeline(project_id: str, b: TE):
295
+ from fastapi import Response
296
+ try:
297
+ mp4 = get_client().export_timeline(project_id, b.composition)
298
+ return Response(content=mp4, media_type="video/mp4")
299
+ except Exception as e: _err(e)
300
+
301
+ @app.post("/api/v1/publish", tags=["Publishing"], dependencies=AUTH)
302
+ async def publish(b: PR):
303
+ try: return get_client().publish_to_vibes(content_item_id=b.content_item_id, batch_id=b.batch_id,
304
+ caption=b.caption, audio_types=b.audio_types, prompt=b.prompt, image_prompt=b.image_prompt, video_prompt=b.video_prompt)
305
+ except Exception as e: _err(e)
306
+
307
+ @app.post("/api/v1/lipsync", tags=["LipSync"], dependencies=AUTH)
308
+ async def lipsync(b: LR):
309
+ try: return get_client().generate_lipsync(project_id=b.project_id, image_prompt=b.image_prompt,
310
+ script=b.script, audio_url=b.audio_url, audio_duration_ms=b.audio_duration_ms, engine=b.engine,
311
+ ingredients=b.ingredients, aspect_ratio=b.aspect_ratio, music_track=b.music_track, custom_motion_prompt=b.custom_motion_prompt)
312
+ except Exception as e: _err(e)
313
+
314
+ @app.get("/api/v1/music/search", tags=["Music"], dependencies=AUTH)
315
+ async def search_music(q: str = "", limit: int = 30, cursor: Optional[str] = None):
316
+ try: return get_client().search_music_filtered(query=q, limit=limit, cursor=cursor)
317
+ except Exception as e: _err(e)
318
+
319
+ @app.get("/api/v1/moodboards", tags=["Moodboards"], dependencies=AUTH)
320
+ async def moodboards():
321
+ try: return {"moodboards": get_client().list_moodboards()}
322
+ except Exception as e: _err(e)
323
+
324
+ @app.post("/api/v1/utils/parse-midjourney", tags=["Utils"])
325
+ async def parse_mj(b: PM): return VibesClient.parse_midjourney_params(b.prompt)
326
+
327
+ @app.post("/api/v1/utils/validate-prompt", tags=["Utils"])
328
+ async def validate_p(b: PM): return VibesClient.validate_prompt_length(b.prompt)
329
+
330
+ if __name__ == "__main__":
331
+ import uvicorn
332
+ uvicorn.run("server.app:app", host=os.environ.get("HOST","0.0.0.0"), port=int(os.environ.get("PORT","8000")))
vibes_api/__init__.py ADDED
@@ -0,0 +1,121 @@
1
+ """
2
+ Vibes AI - Unofficial Python API Client
3
+ =======================================
4
+
5
+ A Python wrapper around the vibes.ai private REST API. Supports:
6
+
7
+ - Project management (list / create / get / update / delete / duplicate)
8
+ - Text-to-video generation (midjen-short, 9:16/16:9/1:1, 480p/720p)
9
+ - Text-to-image generation (midjen-base, multiple aspect ratios)
10
+ - **Video extend (auto + manual)** — extend a video by ~5 seconds
11
+ - **Video-to-video editing (v2v)** — re-render with a directive
12
+ - **Image-to-video animate (auto + manual)** — animate a still image
13
+ - **Batch regeneration** — re-roll with same or new prompt
14
+ - **Start/end frame** support (image-to-video with keyframes)
15
+ - **Ingredients** (characters, styles, scenes) — apply, create inline,
16
+ or CRUD via the studio API
17
+ - **Moodboards** — apply style references
18
+ - Image editing (prompt-driven)
19
+ - Prompt enhancement (returns 4 AI-rewritten variations)
20
+ - Lip sync / animation generation
21
+ - Text-to-speech (TTS) with 41 preset voices
22
+ - Media library (list / favorite / delete / download)
23
+ - Generation batch polling & **SSE streaming**
24
+ - Share links
25
+ - Studio ingredients (characters, styles, settings)
26
+ - Timeline chat (streaming AI assistant)
27
+ - Music / audio clip extraction
28
+ - Direct file uploads (image / video / audio / profile picture)
29
+ - Timeline export to MP4 (sync + async SurfGuard)
30
+ - **Real-time sync** (SSE for collaborative editing)
31
+ - Account settings (delete account / media / posts)
32
+ - Quota & upsell info
33
+ - Bug reports & consent
34
+
35
+ Authentication
36
+ --------------
37
+ This client uses cookie-based auth. Provide your `meta_session` cookie value
38
+ (obtained from your browser after logging in at https://vibes.ai). Cookies
39
+ rotate, so refresh when needed.
40
+
41
+ Example
42
+ -------
43
+ from vibes_api import VibesClient, AspectRatio, Resolution, IngredientType
44
+ from vibes_api.ingredients import IngredientRef, CreateIngredient
45
+
46
+ client = VibesClient(meta_session="e60e910a-...-K54E")
47
+ project = client.create_project(name="My first video")
48
+
49
+ # Apply a saved character ingredient
50
+ character = IngredientRef.by_id(
51
+ ingredient_id="800957099700717",
52
+ ingredient_type=IngredientType.CHARACTER,
53
+ name="Valdrin",
54
+ image_url="https://...",
55
+ )
56
+
57
+ batch = client.generate_video(
58
+ project_id=project["id"],
59
+ prompt="A serene mountain landscape at sunset",
60
+ aspect_ratio=AspectRatio.LANDSCAPE,
61
+ resolution=Resolution.P720,
62
+ variations=4,
63
+ ingredients=[character],
64
+ )
65
+
66
+ # Auto-extend the first variation by ~5 seconds
67
+ extended = client.extend_video(
68
+ project_id=project["id"],
69
+ source_video=batch["content"][0],
70
+ )
71
+
72
+ client.download_video(extended["content"][0]["id"], "sunset.mp4")
73
+ """
74
+
75
+ from .client import VibesClient, VibesAPIError
76
+ from .models import (
77
+ AspectRatio,
78
+ EntityType,
79
+ GenerationType,
80
+ ImageModel,
81
+ IngredientType,
82
+ OwnerFilter,
83
+ PromptModel,
84
+ Resolution,
85
+ SyncMode,
86
+ TextOverlayPosition,
87
+ TextOverlayPreset,
88
+ VideoModel,
89
+ )
90
+ from .ingredients import (
91
+ IngredientRef,
92
+ CreateIngredient,
93
+ build_ingredient_payload,
94
+ )
95
+ from .composition import Composition
96
+
97
+ __version__ = "1.3.0"
98
+ __all__ = [
99
+ # Client
100
+ "VibesClient",
101
+ "VibesAPIError",
102
+ # Enums
103
+ "AspectRatio",
104
+ "EntityType",
105
+ "GenerationType",
106
+ "ImageModel",
107
+ "IngredientType",
108
+ "OwnerFilter",
109
+ "PromptModel",
110
+ "Resolution",
111
+ "SyncMode",
112
+ "TextOverlayPosition",
113
+ "TextOverlayPreset",
114
+ "VideoModel",
115
+ # Ingredient helpers
116
+ "IngredientRef",
117
+ "CreateIngredient",
118
+ "build_ingredient_payload",
119
+ # Composition helper
120
+ "Composition",
121
+ ]