返回 MoneyPrinterTurbo
schema.py
根目录 / app / models / schema.py
1 import warnings
2 from enum import Enum
3 from typing import Any, List, Optional, Union
4
5 import pydantic
6 from pydantic import BaseModel, Field
7
8 from app.config import config
9
10 # 忽略 Pydantic 的特定警告
11 warnings.filterwarnings(
12 "ignore",
13 category=UserWarning,
14 message="Field name.*shadows an attribute in parent.*",
15 )
16
17
18 class VideoConcatMode(str, Enum):
19 random = "random"
20 sequential = "sequential"
21
22
23 class VideoTransitionMode(str, Enum):
24 none = None
25 shuffle = "Shuffle"
26 fade_in = "FadeIn"
27 fade_out = "FadeOut"
28 slide_in = "SlideIn"
29 slide_out = "SlideOut"
30
31
32 class VideoAspect(str, Enum):
33 landscape = "16:9"
34 portrait = "9:16"
35 square = "1:1"
36
37 def to_resolution(self):
38 if self == VideoAspect.landscape:
39 return 1920, 1080
40 elif self == VideoAspect.portrait:
41 return 1080, 1920
42 elif self == VideoAspect.square:
43 return 1080, 1080
44 raise ValueError(f"unsupported video aspect: {self}")
45
46
47 class _Config:
48 arbitrary_types_allowed = True
49
50
51 @pydantic.dataclasses.dataclass(config=_Config)
52 class MaterialInfo:
53 provider: str = "pexels"
54 url: str = ""
55 duration: int = 0
56
57
58 class VideoParams(BaseModel):
59 """
60 {
61 "video_subject": "",
62 "video_aspect": "横屏 16:9(西瓜视频)",
63 "voice_name": "女生-晓晓",
64 "bgm_name": "random",
65 "font_name": "STHeitiMedium 黑体-中",
66 "text_color": "#FFFFFF",
67 "font_size": 60,
68 "stroke_color": "#000000",
69 "stroke_width": 1.5
70 }
71 """
72
73 video_subject: str
74 video_script: str = "" # Script used to generate the video
75 video_terms: Optional[str | list] = None # Keywords used to generate the video
76 video_aspect: Optional[VideoAspect] = VideoAspect.portrait.value
77 video_concat_mode: Optional[VideoConcatMode] = VideoConcatMode.random.value
78 video_transition_mode: Optional[VideoTransitionMode] = None
79 video_clip_duration: Optional[int] = 5
80 match_materials_to_script: bool = False
81 video_count: Optional[int] = 1
82
83 video_source: Optional[str] = "pexels"
84 video_materials: Optional[List[MaterialInfo]] = (
85 None # Materials used to generate the video
86 )
87
88 custom_audio_file: Optional[str] = None # Custom audio file path, will ignore TTS and can still use Whisper subtitles
89 video_language: Optional[str] = "" # auto detect
90
91 voice_name: Optional[str] = ""
92 voice_volume: Optional[float] = 1.0
93 voice_rate: Optional[float] = 1.0
94 bgm_type: Optional[str] = "random"
95 bgm_file: Optional[str] = ""
96 bgm_volume: Optional[float] = 0.2
97
98 subtitle_enabled: Optional[bool] = True
99 subtitle_position: Optional[str] = config.ui.get("subtitle_position", "bottom") # top, bottom, center, custom
100 custom_position: float = config.ui.get("custom_position", 70.0)
101 font_name: Optional[str] = "STHeitiMedium.ttc"
102 text_fore_color: Optional[str] = "#FFFFFF"
103 text_background_color: Union[bool, str] = True
104 rounded_subtitle_background: bool = False
105
106 font_size: int = 60
107 stroke_color: Optional[str] = "#000000"
108 stroke_width: float = 1.5
109 n_threads: Optional[int] = 2
110 paragraph_number: int = Field(default=1, ge=1, le=10)
111 video_script_prompt: str = Field(default="", max_length=2000)
112 custom_system_prompt: str = Field(default="", max_length=8000)
113
114
115 class SubtitleRequest(BaseModel):
116 video_script: str
117 video_language: Optional[str] = ""
118 voice_name: Optional[str] = "zh-CN-XiaoxiaoNeural-Female"
119 voice_volume: Optional[float] = 1.0
120 voice_rate: Optional[float] = 1.2
121 bgm_type: Optional[str] = "random"
122 bgm_file: Optional[str] = ""
123 bgm_volume: Optional[float] = 0.2
124 subtitle_position: Optional[str] = config.ui.get("subtitle_position", "bottom")
125 font_name: Optional[str] = "STHeitiMedium.ttc"
126 text_fore_color: Optional[str] = "#FFFFFF"
127 text_background_color: Union[bool, str] = True
128 rounded_subtitle_background: bool = False
129 font_size: int = 60
130 stroke_color: Optional[str] = "#000000"
131 stroke_width: float = 1.5
132 video_source: Optional[str] = "local"
133 subtitle_enabled: Optional[str] = "true"
134
135
136 class AudioRequest(BaseModel):
137 video_script: str
138 video_language: Optional[str] = ""
139 voice_name: Optional[str] = "zh-CN-XiaoxiaoNeural-Female"
140 voice_volume: Optional[float] = 1.0
141 voice_rate: Optional[float] = 1.2
142 bgm_type: Optional[str] = "random"
143 bgm_file: Optional[str] = ""
144 bgm_volume: Optional[float] = 0.2
145 video_source: Optional[str] = "local"
146
147
148 class VideoScriptParams:
149 """
150 {
151 "video_subject": "春天的花海",
152 "video_language": "",
153 "paragraph_number": 1,
154 "video_script_prompt": "",
155 "custom_system_prompt": ""
156 }
157 """
158
159 video_subject: Optional[str] = "春天的花海"
160 video_language: Optional[str] = ""
161 paragraph_number: int = Field(default=1, ge=1, le=10)
162 video_script_prompt: str = Field(default="", max_length=2000)
163 custom_system_prompt: str = Field(default="", max_length=8000)
164
165
166 class VideoTermsParams:
167 """
168 {
169 "video_subject": "",
170 "video_script": "",
171 "amount": 5,
172 "match_materials_to_script": false
173 }
174 """
175
176 video_subject: Optional[str] = "春天的花海"
177 video_script: Optional[str] = (
178 "春天的花海,如诗如画般展现在眼前。万物复苏的季节里,大地披上了一袭绚丽多彩的盛装。金黄的迎春、粉嫩的樱花、洁白的梨花、艳丽的郁金香……"
179 )
180 amount: Optional[int] = 5
181 match_materials_to_script: bool = False
182
183
184 class VideoSocialMetadataParams:
185 """
186 {
187 "video_subject": "A day in Shanghai",
188 "video_script": "",
189 "language": "auto",
190 "platform": "tiktok"
191 }
192 """
193
194 video_subject: Optional[str] = Field(default="A day in Shanghai", max_length=500)
195 video_script: Optional[str] = Field(default="", max_length=8000)
196 language: Optional[str] = Field(default="auto", max_length=64)
197 platform: Optional[str] = Field(default="tiktok", max_length=64)
198
199
200 class BaseResponse(BaseModel):
201 status: int = 200
202 message: Optional[str] = "success"
203 data: Any = None
204
205
206 class TaskVideoRequest(VideoParams, BaseModel):
207 pass
208
209
210 class TaskQueryRequest(BaseModel):
211 pass
212
213
214 class VideoScriptRequest(VideoScriptParams, BaseModel):
215 pass
216
217
218 class VideoTermsRequest(VideoTermsParams, BaseModel):
219 pass
220
221
222 class VideoSocialMetadataRequest(VideoSocialMetadataParams, BaseModel):
223 pass
224
225
226 ######################################################################################################
227 ######################################################################################################
228 ######################################################################################################
229 ######################################################################################################
230 class TaskResponse(BaseResponse):
231 class TaskResponseData(BaseModel):
232 task_id: str
233
234 data: TaskResponseData
235
236 class Config:
237 json_schema_extra = {
238 "example": {
239 "status": 200,
240 "message": "success",
241 "data": {"task_id": "6c85c8cc-a77a-42b9-bc30-947815aa0558"},
242 },
243 }
244
245
246 class TaskQueryResponse(BaseResponse):
247 class Config:
248 json_schema_extra = {
249 "example": {
250 "status": 200,
251 "message": "success",
252 "data": {
253 "state": 1,
254 "progress": 100,
255 "videos": [
256 "http://127.0.0.1:8080/tasks/6c85c8cc-a77a-42b9-bc30-947815aa0558/final-1.mp4"
257 ],
258 "combined_videos": [
259 "http://127.0.0.1:8080/tasks/6c85c8cc-a77a-42b9-bc30-947815aa0558/combined-1.mp4"
260 ],
261 },
262 },
263 }
264
265
266 class TaskDeletionResponse(BaseResponse):
267 class Config:
268 json_schema_extra = {
269 "example": {
270 "status": 200,
271 "message": "success",
272 "data": {
273 "state": 1,
274 "progress": 100,
275 "videos": [
276 "http://127.0.0.1:8080/tasks/6c85c8cc-a77a-42b9-bc30-947815aa0558/final-1.mp4"
277 ],
278 "combined_videos": [
279 "http://127.0.0.1:8080/tasks/6c85c8cc-a77a-42b9-bc30-947815aa0558/combined-1.mp4"
280 ],
281 },
282 },
283 }
284
285
286 class VideoScriptResponse(BaseResponse):
287 class Config:
288 json_schema_extra = {
289 "example": {
290 "status": 200,
291 "message": "success",
292 "data": {
293 "video_script": "春天的花海,是大自然的一幅美丽画卷。在这个季节里,大地复苏,万物生长,花朵争相绽放,形成了一片五彩斑斓的花海..."
294 },
295 },
296 }
297
298
299 class VideoTermsResponse(BaseResponse):
300 class Config:
301 json_schema_extra = {
302 "example": {
303 "status": 200,
304 "message": "success",
305 "data": {"video_terms": ["sky", "tree"]},
306 },
307 }
308
309
310 class VideoSocialMetadataResponse(BaseResponse):
311 class Config:
312 json_schema_extra = {
313 "example": {
314 "status": 200,
315 "message": "success",
316 "data": {
317 "title": "A Day in Shanghai You Should Not Miss",
318 "caption": "Save this quick Shanghai inspiration and follow for more short travel ideas.",
319 "hashtags": ["#shorts", "#travel", "#shanghai", "#viral", "#fyp"],
320 },
321 },
322 }
323
324
325 class BgmRetrieveResponse(BaseResponse):
326 class Config:
327 json_schema_extra = {
328 "example": {
329 "status": 200,
330 "message": "success",
331 "data": {
332 "files": [
333 {
334 "name": "output013.mp3",
335 "size": 1891269,
336 "file": "/MoneyPrinterTurbo/resource/songs/output013.mp3",
337 }
338 ]
339 },
340 },
341 }
342
343
344 class BgmUploadResponse(BaseResponse):
345 class Config:
346 json_schema_extra = {
347 "example": {
348 "status": 200,
349 "message": "success",
350 "data": {"file": "/MoneyPrinterTurbo/resource/songs/example.mp3"},
351 },
352 }
353
354 class VideoMaterialRetrieveResponse(BaseResponse):
355 class Config:
356 json_schema_extra = {
357 "example": {
358 "status": 200,
359 "message": "success",
360 "data": {
361 "files": [
362 {
363 "name": "example.mp4",
364 "size": 12345678,
365 "file": "/MoneyPrinterTurbo/resource/videos/example.mp4",
366 }
367 ]
368 },
369 },
370 }
371
372 class VideoMaterialUploadResponse(BaseResponse):
373 class Config:
374 json_schema_extra = {
375 "example": {
376 "status": 200,
377 "message": "success",
378 "data": {
379 "file": "/MoneyPrinterTurbo/resource/videos/example.mp4",
380 },
381 },
382 }
383
383 lines PYTHON