Merge branch #Yakova/Rectifier' into 'azils3/Tester'
Browse files- App/Generate/Schema.py +14 -2
- App/Generate/Story/Story.py +5 -1
- App/Generate/database/CharacterAPI.py +123 -0
- App/Generate/database/ElevenLab.py +52 -0
- App/Generate/database/Modal.py +51 -0
- App/Generate/database/Model.py +94 -41
- App/Generate/database/Vercel.py +47 -25
- App/Generate/database/Video3d.py +120 -0
- App/Generate/generatorRoutes.py +57 -12
- App/Generate/utils/Cohere.py +9 -2
- App/Generate/utils/RenderVideo.py +3 -1
- App/Worker.py +1 -1
- App/app.py +4 -2
- Dockerfile +1 -1
- Remotion-app/package.json +0 -2
- Remotion-app/remotion.config.js +1 -1
- Remotion-app/src/HelloWorld/TextStream.jsx +1 -0
- Remotion-app/src/HelloWorld/VideoStream.jsx +14 -8
App/Generate/Schema.py
CHANGED
@@ -3,11 +3,23 @@ from pydantic import BaseModel, HttpUrl
|
|
3 |
from pydantic import validator
|
4 |
|
5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
class GeneratorRequest(BaseModel):
|
7 |
prompt: str
|
8 |
-
grok: Optional[bool] = True
|
9 |
batch_size: int = 4
|
10 |
-
|
|
|
11 |
|
12 |
|
13 |
class GeneratorBulkRequest(BaseModel):
|
|
|
3 |
from pydantic import validator
|
4 |
|
5 |
|
6 |
+
class Scene(BaseModel):
|
7 |
+
narration: str
|
8 |
+
image_prompts: List[str]
|
9 |
+
voice: str
|
10 |
+
|
11 |
+
|
12 |
+
class Story(BaseModel):
|
13 |
+
scenes: List[Scene]
|
14 |
+
reference_image_url: Optional[str] = None
|
15 |
+
ip_adapter_weight: Optional[float] = 0.4 # Add this line
|
16 |
+
|
17 |
+
|
18 |
class GeneratorRequest(BaseModel):
|
19 |
prompt: str
|
|
|
20 |
batch_size: int = 4
|
21 |
+
grok: bool = False
|
22 |
+
model: str = "command"
|
23 |
|
24 |
|
25 |
class GeneratorBulkRequest(BaseModel):
|
App/Generate/Story/Story.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1 |
import json
|
2 |
from pydantic import BaseModel
|
3 |
-
from typing import List
|
4 |
|
5 |
|
6 |
class Scene(BaseModel):
|
7 |
narration: str
|
8 |
image_prompts: List[str]
|
|
|
9 |
|
10 |
def num_images(self):
|
11 |
return len(self.image_prompts)
|
@@ -13,6 +14,9 @@ class Scene(BaseModel):
|
|
13 |
|
14 |
class Story(BaseModel):
|
15 |
scenes: List[Scene]
|
|
|
|
|
|
|
16 |
|
17 |
@classmethod
|
18 |
def from_dict(cls, data):
|
|
|
1 |
import json
|
2 |
from pydantic import BaseModel
|
3 |
+
from typing import List, Optional
|
4 |
|
5 |
|
6 |
class Scene(BaseModel):
|
7 |
narration: str
|
8 |
image_prompts: List[str]
|
9 |
+
voice: Optional[str] = None
|
10 |
|
11 |
def num_images(self):
|
12 |
return len(self.image_prompts)
|
|
|
14 |
|
15 |
class Story(BaseModel):
|
16 |
scenes: List[Scene]
|
17 |
+
voice: Optional[str] = None
|
18 |
+
reference_image_url: Optional[str] = None
|
19 |
+
ip_adapter_weight: Optional[float] = 0.4
|
20 |
|
21 |
@classmethod
|
22 |
def from_dict(cls, data):
|
App/Generate/database/CharacterAPI.py
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import aiohttp
|
2 |
+
import asyncio
|
3 |
+
import os
|
4 |
+
import uuid
|
5 |
+
import tempfile
|
6 |
+
from typing import List, Dict, Any
|
7 |
+
|
8 |
+
from pydantic import BaseModel
|
9 |
+
|
10 |
+
|
11 |
+
class AlignmentData(BaseModel):
|
12 |
+
word: str
|
13 |
+
start: float
|
14 |
+
end: float
|
15 |
+
|
16 |
+
def to_dict(self) -> dict:
|
17 |
+
return {
|
18 |
+
"word": self.word,
|
19 |
+
"alignedWord": self.word,
|
20 |
+
"startTime": self.start,
|
21 |
+
"endTime": self.end,
|
22 |
+
"hasFailedAlignment": False,
|
23 |
+
}
|
24 |
+
|
25 |
+
|
26 |
+
class CharacterAITTS:
|
27 |
+
def __init__(self):
|
28 |
+
self.api_url = "https://yakova-embedding.hf.space"
|
29 |
+
self.dir = str(tempfile.mkdtemp())
|
30 |
+
self.descript = "https://yakova-embedding.hf.space"
|
31 |
+
self.headers = {"Connection": "keep-alive", "Content-Type": "application/json"}
|
32 |
+
|
33 |
+
async def _make_transcript(self, links, text):
|
34 |
+
|
35 |
+
data = {"audio_url": links, "text": text, "file_extenstion": ".mp3"}
|
36 |
+
response_data = await self._make_request(
|
37 |
+
"post", "descript_transcript", json=data, external=self.descript
|
38 |
+
)
|
39 |
+
if not response_data:
|
40 |
+
data["audio_url"] = data["audio_url"][0]
|
41 |
+
print(data)
|
42 |
+
response_data = await self.aligner(
|
43 |
+
"post",
|
44 |
+
"align/url",
|
45 |
+
json=data,
|
46 |
+
)
|
47 |
+
print(response_data)
|
48 |
+
response_data = self.process_alignments(
|
49 |
+
data=response_data["alignment"], offset=0
|
50 |
+
)
|
51 |
+
return response_data
|
52 |
+
|
53 |
+
def process_alignments(
|
54 |
+
self, data: List[Dict[str, Any]], offset: float = 0
|
55 |
+
) -> List[Dict[str, Any]]:
|
56 |
+
alignments = [AlignmentData(**item) for item in data]
|
57 |
+
return [alignment.to_dict() for alignment in alignments]
|
58 |
+
|
59 |
+
async def aligner(
|
60 |
+
self,
|
61 |
+
method,
|
62 |
+
endpoint,
|
63 |
+
json=None,
|
64 |
+
external="https://yakova-aligner.hf.space/align/url",
|
65 |
+
):
|
66 |
+
async with aiohttp.ClientSession() as session:
|
67 |
+
if external:
|
68 |
+
url = f"{external}"
|
69 |
+
else:
|
70 |
+
url = f"{self.api_url}/{endpoint}"
|
71 |
+
async with getattr(session, method)(url=url, json=json) as response:
|
72 |
+
return await response.json()
|
73 |
+
|
74 |
+
async def _make_request(self, method, endpoint, json=None, external=None):
|
75 |
+
async with aiohttp.ClientSession() as session:
|
76 |
+
if external:
|
77 |
+
url = f"{external}/{endpoint}"
|
78 |
+
else:
|
79 |
+
url = f"{self.api_url}/{endpoint}"
|
80 |
+
async with getattr(session, method)(url=url, json=json) as response:
|
81 |
+
return await response.json()
|
82 |
+
|
83 |
+
async def say(self, text, speaker=None):
|
84 |
+
|
85 |
+
data = {"text": text, "voice": speaker}
|
86 |
+
|
87 |
+
response_data = await self._make_request("post", "cai_tts", json=data)
|
88 |
+
# print(response_data)
|
89 |
+
audio_url = response_data["audio"]
|
90 |
+
temp = await self.download_file(audio_url)
|
91 |
+
return audio_url, temp
|
92 |
+
|
93 |
+
async def download_file(self, url):
|
94 |
+
filename = str(uuid.uuid4()) + ".mp3"
|
95 |
+
os.makedirs(self.dir, exist_ok=True)
|
96 |
+
save_path = os.path.join(self.dir, filename)
|
97 |
+
async with aiohttp.ClientSession() as session:
|
98 |
+
async with session.get(url) as response:
|
99 |
+
if response.status == 200:
|
100 |
+
with open(save_path, "wb") as file:
|
101 |
+
while True:
|
102 |
+
chunk = await response.content.read(1024)
|
103 |
+
if not chunk:
|
104 |
+
break
|
105 |
+
file.write(chunk)
|
106 |
+
|
107 |
+
return save_path
|
108 |
+
|
109 |
+
|
110 |
+
# # Usage
|
111 |
+
# async def main():
|
112 |
+
# tts = CharacterAITTS()
|
113 |
+
# url, temp = await tts.say(
|
114 |
+
# "Did you know that you don't have the balls to talk to me"
|
115 |
+
# )
|
116 |
+
# tranny = await tts._make_transcript(
|
117 |
+
# links=[url], text="Did you know that you don't have the balls to talk to me"
|
118 |
+
# )
|
119 |
+
# print(tranny)
|
120 |
+
|
121 |
+
|
122 |
+
# # Run the main function
|
123 |
+
# asyncio.run(main())
|
App/Generate/database/ElevenLab.py
CHANGED
@@ -3,6 +3,24 @@ import asyncio
|
|
3 |
import os
|
4 |
import uuid
|
5 |
import tempfile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
|
8 |
class ElevenLab:
|
@@ -748,8 +766,41 @@ class ElevenLab:
|
|
748 |
response_data = await self._make_request(
|
749 |
"post", "descript_transcript", json=data, external=self.descript
|
750 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
751 |
return response_data
|
752 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
753 |
async def _make_request(self, method, endpoint, json=None, external=None):
|
754 |
async with aiohttp.ClientSession() as session:
|
755 |
if external:
|
@@ -765,6 +816,7 @@ class ElevenLab:
|
|
765 |
name = i["name"].split(" ")[0]
|
766 |
if name.lower() == speaker.lower():
|
767 |
voiceId = i["voice_id"]
|
|
|
768 |
data = {"voiceId": voiceId, "text": text}
|
769 |
|
770 |
response_data = await self._make_request("post", "convert", json=data)
|
|
|
3 |
import os
|
4 |
import uuid
|
5 |
import tempfile
|
6 |
+
from typing import List, Dict, Any
|
7 |
+
|
8 |
+
from pydantic import BaseModel
|
9 |
+
|
10 |
+
|
11 |
+
class AlignmentData(BaseModel):
|
12 |
+
word: str
|
13 |
+
start: float
|
14 |
+
end: float
|
15 |
+
|
16 |
+
def to_dict(self) -> dict:
|
17 |
+
return {
|
18 |
+
"word": self.word,
|
19 |
+
"alignedWord": self.word,
|
20 |
+
"startTime": self.start,
|
21 |
+
"endTime": self.end,
|
22 |
+
"hasFailedAlignment": False,
|
23 |
+
}
|
24 |
|
25 |
|
26 |
class ElevenLab:
|
|
|
766 |
response_data = await self._make_request(
|
767 |
"post", "descript_transcript", json=data, external=self.descript
|
768 |
)
|
769 |
+
if not response_data:
|
770 |
+
data["audio_url"] = data["audio_url"][0]
|
771 |
+
print(data)
|
772 |
+
response_data = await self.aligner(
|
773 |
+
"post",
|
774 |
+
"align/url",
|
775 |
+
json=data,
|
776 |
+
)
|
777 |
+
print(response_data)
|
778 |
+
response_data = self.process_alignments(
|
779 |
+
data=response_data["alignment"], offset=0
|
780 |
+
)
|
781 |
return response_data
|
782 |
|
783 |
+
def process_alignments(
|
784 |
+
self, data: List[Dict[str, Any]], offset: float = 0
|
785 |
+
) -> List[Dict[str, Any]]:
|
786 |
+
alignments = [AlignmentData(**item) for item in data]
|
787 |
+
return [alignment.to_dict() for alignment in alignments]
|
788 |
+
|
789 |
+
async def aligner(
|
790 |
+
self,
|
791 |
+
method,
|
792 |
+
endpoint,
|
793 |
+
json=None,
|
794 |
+
external="https://yakova-aligner.hf.space/align/url",
|
795 |
+
):
|
796 |
+
async with aiohttp.ClientSession() as session:
|
797 |
+
if external:
|
798 |
+
url = f"{external}"
|
799 |
+
else:
|
800 |
+
url = f"{self.api_url}/{endpoint}"
|
801 |
+
async with getattr(session, method)(url=url, json=json) as response:
|
802 |
+
return await response.json()
|
803 |
+
|
804 |
async def _make_request(self, method, endpoint, json=None, external=None):
|
805 |
async with aiohttp.ClientSession() as session:
|
806 |
if external:
|
|
|
816 |
name = i["name"].split(" ")[0]
|
817 |
if name.lower() == speaker.lower():
|
818 |
voiceId = i["voice_id"]
|
819 |
+
# voiceId = "yl2ZDV1MzN4HbQJbMihG"
|
820 |
data = {"voiceId": voiceId, "text": text}
|
821 |
|
822 |
response_data = await self._make_request("post", "convert", json=data)
|
App/Generate/database/Modal.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import aiohttp
|
2 |
+
import asyncio
|
3 |
+
import json
|
4 |
+
from typing import Dict, Any
|
5 |
+
|
6 |
+
|
7 |
+
class ModalImageGenerator:
|
8 |
+
def __init__(self, session: aiohttp.ClientSession):
|
9 |
+
self.session = session
|
10 |
+
self.base_url = (
|
11 |
+
"https://allanwatts705--kolors-app-model-web-inference.modal.run/"
|
12 |
+
)
|
13 |
+
|
14 |
+
async def generate_image(
|
15 |
+
self,
|
16 |
+
prompt: str,
|
17 |
+
reference_image_url: str = "https://image.lexica.art/full_webp/d6ddd5c5-060c-4aba-b9d0-cf0e02dc65bd",
|
18 |
+
ip_adapter_weight: float = 0.4,
|
19 |
+
) -> Dict[str, Any]:
|
20 |
+
params = {
|
21 |
+
"ip_adapter_weight": ip_adapter_weight,
|
22 |
+
"reference_image_url": reference_image_url,
|
23 |
+
"prompt": prompt,
|
24 |
+
}
|
25 |
+
|
26 |
+
async with self.session.get(self.base_url, params=params) as response:
|
27 |
+
if response.status == 200:
|
28 |
+
return await response.json()
|
29 |
+
else:
|
30 |
+
raise Exception(f"Failed to generate image: {response.status}")
|
31 |
+
|
32 |
+
async def wait_for_image(
|
33 |
+
self, result: Dict[str, Any], max_attempts: int = 60, delay: int = 5
|
34 |
+
) -> Dict[str, Any]:
|
35 |
+
attempts = 0
|
36 |
+
while attempts < max_attempts:
|
37 |
+
if result.get("image", {}).get("url"):
|
38 |
+
return result
|
39 |
+
await asyncio.sleep(delay)
|
40 |
+
attempts += 1
|
41 |
+
|
42 |
+
raise Exception("Image generation timed out")
|
43 |
+
|
44 |
+
async def generate_and_wait(
|
45 |
+
self, prompt: str, reference_image_url: str, ip_adapter_weight: float = 0.4
|
46 |
+
) -> str:
|
47 |
+
result = await self.generate_image(
|
48 |
+
prompt, reference_image_url, ip_adapter_weight
|
49 |
+
)
|
50 |
+
final_result = await self.wait_for_image(result)
|
51 |
+
return final_result["image"]["url"]
|
App/Generate/database/Model.py
CHANGED
@@ -4,13 +4,17 @@ import asyncio, os
|
|
4 |
import uuid, random
|
5 |
from pydub import AudioSegment
|
6 |
from .DescriptAPI import Speak
|
7 |
-
|
|
|
|
|
8 |
from .Vercel import AsyncImageGenerator
|
|
|
9 |
import aiohttp
|
10 |
from typing import List
|
11 |
from pydantic import BaseModel
|
12 |
import tempfile
|
13 |
import json
|
|
|
14 |
|
15 |
SUPABASE = os.environ.get("SUPABASE", "RANDOM_STRING")
|
16 |
database_url = SUPABASE
|
@@ -95,6 +99,7 @@ class Project(orm.Model):
|
|
95 |
|
96 |
# generate transcripts
|
97 |
temp = await scene.generate_scene_transcript(offset=self.start)
|
|
|
98 |
end_word = temp[-1]
|
99 |
|
100 |
# narration of the story
|
@@ -113,34 +118,68 @@ class Project(orm.Model):
|
|
113 |
)
|
114 |
text_stream.extend(temp[:-1])
|
115 |
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
|
145 |
self.assets.append({"type": "audio", "sequence": audio_assets})
|
146 |
## add the images to assets
|
@@ -160,12 +199,14 @@ class Project(orm.Model):
|
|
160 |
|
161 |
|
162 |
class Scene(orm.Model):
|
163 |
-
tts =
|
|
|
164 |
# eleven = ElevenLab()
|
165 |
tablename = "scenes"
|
166 |
registry = models
|
167 |
fields = {
|
168 |
"id": orm.Integer(primary_key=True),
|
|
|
169 |
"project": orm.ForeignKey(Project),
|
170 |
"images": orm.JSON(default=None),
|
171 |
"narration": orm.String(max_length=10_000, allow_null=True, default=""),
|
@@ -186,9 +227,13 @@ class Scene(orm.Model):
|
|
186 |
transcript = await self.tts._make_transcript(links=links, text=text)
|
187 |
return transform_alignment_data(data=transcript, offset=offset)
|
188 |
|
189 |
-
async def generate_scene_data(
|
|
|
|
|
190 |
# Run narrate() and generate_images() concurrently
|
191 |
-
await asyncio.gather(
|
|
|
|
|
192 |
self.calculate_durations()
|
193 |
|
194 |
async def narrate(self):
|
@@ -197,12 +242,11 @@ class Scene(orm.Model):
|
|
197 |
self.narration_link = link
|
198 |
|
199 |
async def retry_narration_generation(self):
|
200 |
-
print(self.narration)
|
201 |
retry_count = 0
|
202 |
while retry_count < 3:
|
203 |
try:
|
204 |
return await self.tts.say(
|
205 |
-
text=self.narration + " master"
|
206 |
) ### The blanks help to even stuff up.
|
207 |
except Exception as e:
|
208 |
print(f"Failed to generate narration: {e}")
|
@@ -217,14 +261,23 @@ class Scene(orm.Model):
|
|
217 |
self.narration_duration = int(len(audio_file) / 1000)
|
218 |
self.image_duration = self.narration_duration / len(self.image_prompts)
|
219 |
|
220 |
-
async def generate_images(
|
|
|
|
|
|
|
|
|
221 |
self.images = []
|
222 |
async with aiohttp.ClientSession() as session:
|
223 |
-
image_generator =
|
224 |
-
for
|
225 |
-
|
226 |
-
|
227 |
-
|
|
|
|
|
|
|
|
|
|
|
228 |
|
229 |
|
230 |
class Transition(orm.Model):
|
@@ -262,7 +315,7 @@ class BackgroundMusic(orm.Model):
|
|
262 |
# pass
|
263 |
|
264 |
|
265 |
-
# #
|
266 |
# async def create_tables():
|
267 |
# datas = {
|
268 |
# "narration": "Welcome to a journey through some of history's strangest moments! Get ready to explore the bizarre, the unusual, and the downright weird.",
|
|
|
4 |
import uuid, random
|
5 |
from pydub import AudioSegment
|
6 |
from .DescriptAPI import Speak
|
7 |
+
|
8 |
+
# from .ElevenLab import ElevenLab
|
9 |
+
from .CharacterAPI import CharacterAITTS
|
10 |
from .Vercel import AsyncImageGenerator
|
11 |
+
from .Video3d import VideoGenerator
|
12 |
import aiohttp
|
13 |
from typing import List
|
14 |
from pydantic import BaseModel
|
15 |
import tempfile
|
16 |
import json
|
17 |
+
from .Modal import ModalImageGenerator
|
18 |
|
19 |
SUPABASE = os.environ.get("SUPABASE", "RANDOM_STRING")
|
20 |
database_url = SUPABASE
|
|
|
99 |
|
100 |
# generate transcripts
|
101 |
temp = await scene.generate_scene_transcript(offset=self.start)
|
102 |
+
await asyncio.sleep(1)
|
103 |
end_word = temp[-1]
|
104 |
|
105 |
# narration of the story
|
|
|
118 |
)
|
119 |
text_stream.extend(temp[:-1])
|
120 |
|
121 |
+
sample_image_extension = scene.images[0].split(".")[-1]
|
122 |
+
|
123 |
+
if sample_image_extension == "mp4":
|
124 |
+
## moving images
|
125 |
+
for image in scene.images:
|
126 |
+
file_name = str(uuid.uuid4()) + ".mp4"
|
127 |
+
self.links.append({"file_name": file_name, "link": image})
|
128 |
+
video_assets.append(
|
129 |
+
{
|
130 |
+
"type": "video",
|
131 |
+
"name": file_name,
|
132 |
+
"start": self.start,
|
133 |
+
"loop": "true",
|
134 |
+
"end": self.start + scene.image_duration,
|
135 |
+
"props": {
|
136 |
+
"volume": 0,
|
137 |
+
"startFrom": 1 * 30,
|
138 |
+
"endAt": 2.9 * 30,
|
139 |
+
"playbackRate": 0.7,
|
140 |
+
"style": {
|
141 |
+
"transform": "translate(-50%, -50%)",
|
142 |
+
"position": "absolute",
|
143 |
+
"top": "50%",
|
144 |
+
"left": "50%",
|
145 |
+
"width": 1920,
|
146 |
+
"height": 1080,
|
147 |
+
"objectFit": "cover",
|
148 |
+
},
|
149 |
+
},
|
150 |
+
}
|
151 |
+
)
|
152 |
+
self.start = self.start + scene.image_duration
|
153 |
+
|
154 |
+
else:
|
155 |
+
## images and transitions
|
156 |
+
for image in scene.images:
|
157 |
+
file_name = str(uuid.uuid4()) + ".png"
|
158 |
+
self.links.append({"file_name": file_name, "link": image})
|
159 |
+
image_assets.append(
|
160 |
+
{
|
161 |
+
"type": "image",
|
162 |
+
"name": file_name,
|
163 |
+
"start": self.start,
|
164 |
+
"end": self.start + scene.image_duration,
|
165 |
+
}
|
166 |
+
)
|
167 |
+
self.start = self.start + scene.image_duration
|
168 |
+
|
169 |
+
## transitions between images
|
170 |
+
# video_assets.append(
|
171 |
+
# {
|
172 |
+
# "type": "video",
|
173 |
+
# "name": "Effects/" + random.choice(transitions),
|
174 |
+
# "start": self.start - 1,
|
175 |
+
# "end": self.start + 2,
|
176 |
+
# "props": {
|
177 |
+
# "startFrom": 1 * 30,
|
178 |
+
# "endAt": 3 * 30,
|
179 |
+
# "volume": 0,
|
180 |
+
# },
|
181 |
+
# }
|
182 |
+
# )
|
183 |
|
184 |
self.assets.append({"type": "audio", "sequence": audio_assets})
|
185 |
## add the images to assets
|
|
|
199 |
|
200 |
|
201 |
class Scene(orm.Model):
|
202 |
+
tts = CharacterAITTS()
|
203 |
+
voice = ""
|
204 |
# eleven = ElevenLab()
|
205 |
tablename = "scenes"
|
206 |
registry = models
|
207 |
fields = {
|
208 |
"id": orm.Integer(primary_key=True),
|
209 |
+
"voice": orm.String(max_length=100, allow_null=True, default=""),
|
210 |
"project": orm.ForeignKey(Project),
|
211 |
"images": orm.JSON(default=None),
|
212 |
"narration": orm.String(max_length=10_000, allow_null=True, default=""),
|
|
|
227 |
transcript = await self.tts._make_transcript(links=links, text=text)
|
228 |
return transform_alignment_data(data=transcript, offset=offset)
|
229 |
|
230 |
+
async def generate_scene_data(
|
231 |
+
self, reference_image_url: str = None, ip_adapter_weight: float = 0.4
|
232 |
+
):
|
233 |
# Run narrate() and generate_images() concurrently
|
234 |
+
await asyncio.gather(
|
235 |
+
self.narrate(), self.generate_images(reference_image_url, ip_adapter_weight)
|
236 |
+
)
|
237 |
self.calculate_durations()
|
238 |
|
239 |
async def narrate(self):
|
|
|
242 |
self.narration_link = link
|
243 |
|
244 |
async def retry_narration_generation(self):
|
|
|
245 |
retry_count = 0
|
246 |
while retry_count < 3:
|
247 |
try:
|
248 |
return await self.tts.say(
|
249 |
+
text=self.narration + " master", speaker=self.voice
|
250 |
) ### The blanks help to even stuff up.
|
251 |
except Exception as e:
|
252 |
print(f"Failed to generate narration: {e}")
|
|
|
261 |
self.narration_duration = int(len(audio_file) / 1000)
|
262 |
self.image_duration = self.narration_duration / len(self.image_prompts)
|
263 |
|
264 |
+
async def generate_images(
|
265 |
+
self,
|
266 |
+
reference_image_url: str = "https://image.lexica.art/full_webp/d6ddd5c5-060c-4aba-b9d0-cf0e02dc65bd",
|
267 |
+
ip_adapter_weight: float = 0.4,
|
268 |
+
):
|
269 |
self.images = []
|
270 |
async with aiohttp.ClientSession() as session:
|
271 |
+
image_generator = ModalImageGenerator(session)
|
272 |
+
for prompt in self.image_prompts:
|
273 |
+
try:
|
274 |
+
image_url = await image_generator.generate_image(
|
275 |
+
prompt, reference_image_url, ip_adapter_weight
|
276 |
+
)
|
277 |
+
self.images.append(image_url)
|
278 |
+
except Exception as e:
|
279 |
+
print(f"Failed to generate image for prompt '{prompt}': {str(e)}")
|
280 |
+
await asyncio.sleep(1) # Add a small delay between requests
|
281 |
|
282 |
|
283 |
class Transition(orm.Model):
|
|
|
315 |
# pass
|
316 |
|
317 |
|
318 |
+
# # Create the tables
|
319 |
# async def create_tables():
|
320 |
# datas = {
|
321 |
# "narration": "Welcome to a journey through some of history's strangest moments! Get ready to explore the bizarre, the unusual, and the downright weird.",
|
App/Generate/database/Vercel.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
import aiohttp, asyncio, pprint
|
2 |
from collections import deque
|
|
|
3 |
|
4 |
|
5 |
class AsyncImageGenerator:
|
@@ -13,35 +14,56 @@ class AsyncImageGenerator:
|
|
13 |
try:
|
14 |
url = f"{self.base}/predictions"
|
15 |
data = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
# "input": {
|
17 |
-
# "prompt": payload,
|
18 |
-
# "negative_prompt": "CGI, Unreal, Airbrushed, Digital",
|
19 |
# "width": 1024,
|
20 |
-
# "height":
|
|
|
|
|
21 |
# "num_outputs": 1,
|
22 |
-
# "
|
23 |
-
# "
|
24 |
-
# "
|
|
|
25 |
# },
|
26 |
-
"input": {
|
27 |
-
"width": 1024,
|
28 |
-
"height": 1536,
|
29 |
-
"prompt": payload,
|
30 |
-
"scheduler": "DPMSolver++",
|
31 |
-
"num_outputs": 1,
|
32 |
-
"guidance_scale": 3,
|
33 |
-
"negative_prompt": "doll opened mouth toy, text, sculpture pixar, cartoon, anime, low quality, plastic, 2 heads, 2 faces, poorly drawn, ugly, poorly drawn face, extra arms, extra legs, ugly, bad anatomy, bad fingers, ugly, deformed, noisy, blurry, distorted, grainy, nudity, nude",
|
34 |
-
# "negative_prompt": "text, watermark, blurry, haze, low contrast, low quality, underexposed, ugly, deformed, boring, bad quality, cartoon, ((disfigured)), ((bad art)), ((deformed)), ((poorly drawn)), ((extra limbs)), ((close up)), ((b&w)), weird colors, blurry, ugly, tiling, poorly drawn hands, poorly drawn feet, poorly drawn face, out of frame, extra limbs, disfigured, body out of frame, blurry, bad anatomy, blurred, watermark, grainy, signature, cut off, draft, low detail, low quality, double face, 2 faces, cropped, ugly, low-res, tiling, grainy, cropped, ostentatious, ugly, oversaturated, grain, low resolution, disfigured, blurry, bad anatomy, disfigured, poorly drawn face, mutant, mutated, extra limb, ugly, poorly drawn hands, missing limbs, blurred, floating limbs, disjointed limbs, deformed hands, blurred, out of focus, long neck, long body, ugly, disgusting, childish, cut off cropped, distorted, imperfect, surreal, bad hands, text, error, extra digit, fewer digits, cropped , worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, artist name, Lots of hands, extra limbs, extra fingers, conjoined fingers, deformed fingers, old, ugly eyes, imperfect eyes, skewed eyes , unnatural face, stiff face, stiff body, unbalanced body, unnatural body, lacking body, details are not clear, cluttered, details are sticky, details are low, distorted details, ugly hands, imperfect hands, (mutated hands and fingers:1.5), (long body :1.3), (mutation, poorly drawn :1.2) bad hands, fused ha nd, missing hand, disappearing arms, hands, disappearing thigh, disappearing calf, disappearing legs, ui, missing fingers",
|
35 |
-
"num_inference_steps": 25,
|
36 |
-
},
|
37 |
# "input": {
|
38 |
# "prompt": payload,
|
39 |
# "aspect_ratio": "9:16",
|
40 |
# "output_quality": 79,
|
41 |
# "negative_prompt": "doll opened mouth toy, text, sculpture pixar, cartoon, anime, low quality, plastic, 2 heads, 2 faces, poorly drawn, ugly, poorly drawn face, extra arms, extra legs, ugly, bad anatomy, bad fingers, ugly, deformed, noisy, blurry, distorted, grainy, nudity, nude",
|
42 |
# },
|
43 |
-
"
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
# "path": "models/lucataco/proteus-v0.4/versions/34a427535a3c45552b94369280b823fcd0e5c9710e97af020bf445c033d4569e",
|
46 |
# "path": "models/lucataco/juggernaut-xl-v9/versions/bea09cf018e513cef0841719559ea86d2299e05448633ac8fe270b5d5cd6777e",
|
47 |
}
|
@@ -73,6 +95,7 @@ class AsyncImageGenerator:
|
|
73 |
status["status"] = "404"
|
74 |
|
75 |
while status["status"] != "succeeded":
|
|
|
76 |
try:
|
77 |
status = await self._fetch_image_status(image_id)
|
78 |
except Exception as e:
|
@@ -102,7 +125,9 @@ async def process_images(payloads):
|
|
102 |
result = await image_generator.generate_image(payload)
|
103 |
status = await image_generator.fetch_image_status(result["id"])
|
104 |
print(status["output"])
|
105 |
-
|
|
|
|
|
106 |
|
107 |
for payload in payloads:
|
108 |
tasks.append(payload)
|
@@ -118,11 +143,8 @@ async def process_images(payloads):
|
|
118 |
# # # Example payloads
|
119 |
# payloads = [
|
120 |
# """
|
121 |
-
|
122 |
-
#
|
123 |
-
|
124 |
-
|
125 |
-
# """
|
126 |
# ]
|
127 |
|
128 |
|
|
|
1 |
import aiohttp, asyncio, pprint
|
2 |
from collections import deque
|
3 |
+
import json
|
4 |
|
5 |
|
6 |
class AsyncImageGenerator:
|
|
|
14 |
try:
|
15 |
url = f"{self.base}/predictions"
|
16 |
data = {
|
17 |
+
# "input": {
|
18 |
+
# "prompt": payload
|
19 |
+
# + """
|
20 |
+
# """,
|
21 |
+
# # "aspect_ratio": "2:3",
|
22 |
+
# "negative_prompt": "text, watermark, blurry, haze, low contrast, low quality, underexposed, ugly, deformed, boring, bad quality, cartoon, ((disfigured)), ((bad art)), ((deformed)), ((poorly drawn)), ((extra limbs)), ((close up)), ((b&w)), weird colors, blurry, ugly, tiling, poorly drawn hands, poorly drawn feet, poorly drawn face, out of frame, extra limbs, disfigured, body out of frame, blurry, bad anatomy, blurred, watermark, grainy, signature, cut off, draft, low detail, low quality, double face, 2 faces, cropped, ugly, low-res, tiling, grainy, cropped, ostentatious, ugly, oversaturated, grain, low resolution, disfigured, blurry, bad anatomy, disfigured, poorly drawn face, mutant, mutated, extra limb, ugly, poorly drawn hands, missing limbs, blurred, floating limbs, disjointed limbs, deformed hands, blurred, out of focus, long neck, long body, ugly, disgusting, childish, cut off cropped, distorted, imperfect, surreal, bad hands, text, error, extra digit, fewer digits, cropped , worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, artist name, Lots of hands, extra limbs, extra fingers, conjoined fingers, deformed fingers, old, ugly eyes, imperfect eyes, skewed eyes , unnatural face, stiff face, stiff body, unbalanced body, unnatural body, lacking body, details are not clear, cluttered, details are sticky, details are low, distorted details, ugly hands, imperfect hands, (mutated hands and fingers:1.5), (long body :1.3), (mutation, poorly drawn :1.2) bad hands, fused ha nd, missing hand, disappearing arms, hands, disappearing thigh, disappearing calf, disappearing legs, ui, missing fingers",
|
23 |
+
# "steps": 28,
|
24 |
+
# },
|
25 |
# "input": {
|
|
|
|
|
26 |
# "width": 1024,
|
27 |
+
# "height": 1536,
|
28 |
+
# "prompt": payload,
|
29 |
+
# "scheduler": "DPMSolver++",
|
30 |
# "num_outputs": 1,
|
31 |
+
# "guidance_scale": 3,
|
32 |
+
# "negative_prompt": "doll opened mouth toy, text, sculpture pixar, cartoon, anime, low quality, plastic, 2 heads, 2 faces, poorly drawn, ugly, poorly drawn face, extra arms, extra legs, ugly, bad anatomy, bad fingers, ugly, deformed, noisy, blurry, distorted, grainy, nudity, nude",
|
33 |
+
# # "negative_prompt": "text, watermark, blurry, haze, low contrast, low quality, underexposed, ugly, deformed, boring, bad quality, cartoon, ((disfigured)), ((bad art)), ((deformed)), ((poorly drawn)), ((extra limbs)), ((close up)), ((b&w)), weird colors, blurry, ugly, tiling, poorly drawn hands, poorly drawn feet, poorly drawn face, out of frame, extra limbs, disfigured, body out of frame, blurry, bad anatomy, blurred, watermark, grainy, signature, cut off, draft, low detail, low quality, double face, 2 faces, cropped, ugly, low-res, tiling, grainy, cropped, ostentatious, ugly, oversaturated, grain, low resolution, disfigured, blurry, bad anatomy, disfigured, poorly drawn face, mutant, mutated, extra limb, ugly, poorly drawn hands, missing limbs, blurred, floating limbs, disjointed limbs, deformed hands, blurred, out of focus, long neck, long body, ugly, disgusting, childish, cut off cropped, distorted, imperfect, surreal, bad hands, text, error, extra digit, fewer digits, cropped , worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, artist name, Lots of hands, extra limbs, extra fingers, conjoined fingers, deformed fingers, old, ugly eyes, imperfect eyes, skewed eyes , unnatural face, stiff face, stiff body, unbalanced body, unnatural body, lacking body, details are not clear, cluttered, details are sticky, details are low, distorted details, ugly hands, imperfect hands, (mutated hands and fingers:1.5), (long body :1.3), (mutation, poorly drawn :1.2) bad hands, fused ha nd, missing hand, disappearing arms, hands, disappearing thigh, disappearing calf, disappearing legs, ui, missing fingers",
|
34 |
+
# "num_inference_steps": 25,
|
35 |
# },
|
36 |
+
# "input": {"workflow_json": payload},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
# "input": {
|
38 |
# "prompt": payload,
|
39 |
# "aspect_ratio": "9:16",
|
40 |
# "output_quality": 79,
|
41 |
# "negative_prompt": "doll opened mouth toy, text, sculpture pixar, cartoon, anime, low quality, plastic, 2 heads, 2 faces, poorly drawn, ugly, poorly drawn face, extra arms, extra legs, ugly, bad anatomy, bad fingers, ugly, deformed, noisy, blurry, distorted, grainy, nudity, nude",
|
42 |
# },
|
43 |
+
"input": {
|
44 |
+
"cfg": 2.5,
|
45 |
+
# "seed": 42,
|
46 |
+
# "image": "https://image.lexica.art/full_webp/e41b87fb-4cc3-43cd-a6e6-f3dbb08c2399",
|
47 |
+
# "steps": 28,
|
48 |
+
# "width": 1024,
|
49 |
+
# "height": 1536,
|
50 |
+
# "aspect_ratio": "16:9",
|
51 |
+
"prompt": payload,
|
52 |
+
# "sampler": "dpmpp_2m_sde_gpu",
|
53 |
+
# "scheduler": "karras",
|
54 |
+
# "output_format": "png",
|
55 |
+
# "output_quality": 95,
|
56 |
+
# "negative_prompt": "clouds, people, buildings",
|
57 |
+
# "number_of_images": 1,
|
58 |
+
# "ip_adapter_weight": 1,
|
59 |
+
# "ip_adapter_weight_type": "style transfer precise",
|
60 |
+
},
|
61 |
+
# "path": "models/fofr/kolors-with-ipadapter/versions/5a1a92b2c0f81813225d48ed8e411813da41aa84e7582fb705d1af46eea36eed",
|
62 |
+
# "path": "models/playgroundai/playground-v2.5-1024px-aesthetic/versions/a45f82a1382bed5c7aeb861dac7c7d191b0fdf74d8d57c4a0e6ed7d4d0bf7d24",
|
63 |
+
# "path": "models/fofr/any-comfyui-workflow/versions/cd385285ba75685a040afbded7b79814a971f3febf46c5eab7c716e200c784e1",
|
64 |
+
# "path": "models/fofr/sd3-explorer/versions/a9f4aebd943ad7db13de8e34debea359d5578d08f128e968f9a36c3e9b0148d4",
|
65 |
+
"path": "models/bingbangboom-lab/flux-new-whimscape/versions/2e8de10f217bc56da163a0204cf09f89995eaf643459014803fae79753183682",
|
66 |
+
# "path": "models/black-forest-labs/flux-schnell",
|
67 |
# "path": "models/lucataco/proteus-v0.4/versions/34a427535a3c45552b94369280b823fcd0e5c9710e97af020bf445c033d4569e",
|
68 |
# "path": "models/lucataco/juggernaut-xl-v9/versions/bea09cf018e513cef0841719559ea86d2299e05448633ac8fe270b5d5cd6777e",
|
69 |
}
|
|
|
95 |
status["status"] = "404"
|
96 |
|
97 |
while status["status"] != "succeeded":
|
98 |
+
print(status["status"])
|
99 |
try:
|
100 |
status = await self._fetch_image_status(image_id)
|
101 |
except Exception as e:
|
|
|
125 |
result = await image_generator.generate_image(payload)
|
126 |
status = await image_generator.fetch_image_status(result["id"])
|
127 |
print(status["output"])
|
128 |
+
if type(status["output"]) == list:
|
129 |
+
results.extend(status["output"])
|
130 |
+
results.append(status["output"])
|
131 |
|
132 |
for payload in payloads:
|
133 |
tasks.append(payload)
|
|
|
143 |
# # # Example payloads
|
144 |
# payloads = [
|
145 |
# """
|
146 |
+
# comicbook illustration artistic, beautiful Awsome cat
|
147 |
+
# """
|
|
|
|
|
|
|
148 |
# ]
|
149 |
|
150 |
|
App/Generate/database/Video3d.py
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import aiohttp
|
2 |
+
import asyncio
|
3 |
+
from itertools import chain
|
4 |
+
|
5 |
+
|
6 |
+
class VideoGenerator:
|
7 |
+
def __init__(self):
|
8 |
+
self.base_urls = [f"https://yakova-depthflow-{i}.hf.space" for i in range(10)]
|
9 |
+
self.headers = {"accept": "application/json"}
|
10 |
+
self.default_params = {
|
11 |
+
"frame_rate": 30,
|
12 |
+
"duration": 3,
|
13 |
+
"quality": 1,
|
14 |
+
"ssaa": 0.8,
|
15 |
+
"raw": "false",
|
16 |
+
}
|
17 |
+
|
18 |
+
async def generate_video(self, base_url, params):
|
19 |
+
url = f"{base_url}/generate_video"
|
20 |
+
|
21 |
+
async with aiohttp.ClientSession() as session:
|
22 |
+
async with session.post(
|
23 |
+
url, params=params, headers=self.headers
|
24 |
+
) as response:
|
25 |
+
if response.status == 200:
|
26 |
+
data = await response.json()
|
27 |
+
output_file = data.get("output_file")
|
28 |
+
return output_file
|
29 |
+
else:
|
30 |
+
print(f"Request to {url} failed with status: {response.status}")
|
31 |
+
return None
|
32 |
+
|
33 |
+
async def check_video_ready(self, base_url, output_file):
|
34 |
+
url = f"{base_url}/download/{output_file}"
|
35 |
+
|
36 |
+
async with aiohttp.ClientSession() as session:
|
37 |
+
while True:
|
38 |
+
async with session.get(url, headers=self.headers) as response:
|
39 |
+
if response.status == 200:
|
40 |
+
video_content = await response.read()
|
41 |
+
if len(video_content) > 0:
|
42 |
+
return url
|
43 |
+
else:
|
44 |
+
print(
|
45 |
+
f"Video {output_file} is ready but the file size is zero, retrying in 10 seconds..."
|
46 |
+
)
|
47 |
+
await asyncio.sleep(10)
|
48 |
+
elif response.status == 404:
|
49 |
+
data = await response.json()
|
50 |
+
if data.get("detail") == "Video not found":
|
51 |
+
print(
|
52 |
+
f"Video {output_file} not ready yet, retrying in 10 seconds..."
|
53 |
+
)
|
54 |
+
await asyncio.sleep(180)
|
55 |
+
else:
|
56 |
+
print(f"Unexpected response for {output_file}: {data}")
|
57 |
+
return None
|
58 |
+
else:
|
59 |
+
print(f"Request to {url} failed with status: {response.status}")
|
60 |
+
return None
|
61 |
+
|
62 |
+
async def process_image(self, base_url, image_link):
|
63 |
+
params = self.default_params.copy()
|
64 |
+
params["image_link"] = image_link
|
65 |
+
|
66 |
+
output_file = await self.generate_video(base_url, params)
|
67 |
+
if output_file:
|
68 |
+
print(f"Generated video file id: {output_file} for image {image_link}")
|
69 |
+
video_url = await self.check_video_ready(base_url, output_file)
|
70 |
+
if video_url:
|
71 |
+
print(
|
72 |
+
f"Video for {image_link} is ready and can be downloaded from: {video_url}"
|
73 |
+
)
|
74 |
+
return video_url
|
75 |
+
else:
|
76 |
+
print(f"Failed to get the video URL for {image_link}")
|
77 |
+
return None
|
78 |
+
else:
|
79 |
+
print(f"Failed to generate the video for {image_link}")
|
80 |
+
return None
|
81 |
+
|
82 |
+
def flatten(self, nested_list):
|
83 |
+
return list(chain.from_iterable(nested_list))
|
84 |
+
|
85 |
+
def nest(self, flat_list, nested_dims):
|
86 |
+
it = iter(flat_list)
|
87 |
+
return [[next(it) for _ in inner_list] for inner_list in nested_dims]
|
88 |
+
|
89 |
+
async def run(self, nested_image_links):
|
90 |
+
flat_image_links = self.flatten(nested_image_links)
|
91 |
+
tasks = []
|
92 |
+
base_index = 0
|
93 |
+
|
94 |
+
for image_link in flat_image_links:
|
95 |
+
base_url = self.base_urls[base_index % len(self.base_urls)]
|
96 |
+
tasks.append(self.process_image(base_url, image_link))
|
97 |
+
base_index += 1
|
98 |
+
|
99 |
+
flat_video_urls = await asyncio.gather(*tasks)
|
100 |
+
nested_video_urls = self.nest(flat_video_urls, nested_image_links)
|
101 |
+
return nested_video_urls
|
102 |
+
|
103 |
+
|
104 |
+
# # Example usage
|
105 |
+
# nested_image_links = [
|
106 |
+
# [
|
107 |
+
# "https://replicate.delivery/yhqm/mQId1rdf4Z3odCyB7cPsx1KwhHfdRc3w44eYAGNG9AQfV0dMB/out-0.png"
|
108 |
+
# ],
|
109 |
+
# [
|
110 |
+
# "https://replicate.delivery/yhqm/mQId1rdf4Z3odCyB7cPsx1KwhHfdRc3w44eYAGNG9AQfV0dMB/out-1.png",
|
111 |
+
# "https://replicate.delivery/yhqm/mQId1rdf4Z3odCyB7cPsx1KwhHfdRc3w44eYAGNG9AQfV0dMB/out-2.png",
|
112 |
+
# ],
|
113 |
+
# # Add more nested image links here
|
114 |
+
# ]
|
115 |
+
|
116 |
+
# loop = asyncio.get_event_loop()
|
117 |
+
# video_generator = VideoGenerator()
|
118 |
+
# nested_video_urls = loop.run_until_complete(video_generator.run(nested_image_links))
|
119 |
+
|
120 |
+
# print("Generated video URLs:", nested_video_urls)
|
App/Generate/generatorRoutes.py
CHANGED
@@ -6,16 +6,29 @@ from .utils.HuggingChat import Hugging
|
|
6 |
from .Story.Story import Story
|
7 |
import asyncio, pprint, json
|
8 |
from tqdm import tqdm
|
9 |
-
from .database.Model import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
from .utils.RenderVideo import RenderVideo
|
11 |
from .Prompts.StoryGen import Prompt
|
12 |
from App.Editor.editorRoutes import celery_task, EditorRequest
|
13 |
import uuid
|
14 |
|
15 |
|
16 |
-
async def update_scene(model_scene):
|
17 |
-
await model_scene.generate_scene_data()
|
18 |
-
await model_scene.update(
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
|
21 |
async def from_dict_generate(data: Story):
|
@@ -23,38 +36,70 @@ async def from_dict_generate(data: Story):
|
|
23 |
await generate_assets(generated_story=generated_strory)
|
24 |
|
25 |
|
26 |
-
async def generate_assets(generated_story: Story, batch_size=4):
|
27 |
x = await Project.objects.create(name=str(uuid.uuid4()))
|
28 |
|
29 |
# Assuming generated_story.scenes is a list of scenes
|
30 |
-
scene_updates = []
|
31 |
with tqdm(total=len(generated_story.scenes)) as pbar:
|
|
|
|
|
|
|
32 |
for i in range(0, len(generated_story.scenes), batch_size):
|
33 |
batch = generated_story.scenes[
|
34 |
i : i + batch_size
|
35 |
] # Get a batch of two story scenes
|
36 |
batch_updates = []
|
37 |
|
|
|
38 |
for story_scene in batch:
|
39 |
model_scene = await Scene.objects.create(project=x)
|
40 |
model_scene.image_prompts = story_scene.image_prompts
|
41 |
model_scene.narration = story_scene.narration
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
batch_updates.append(
|
44 |
-
update_scene(
|
|
|
|
|
|
|
|
|
45 |
) # Append update coroutine to batch_updates
|
46 |
-
|
47 |
await asyncio.gather(
|
48 |
*batch_updates
|
49 |
) # Await update coroutines for this batch
|
|
|
50 |
pbar.update(len(batch)) # Increment progress bar by the size of the batch
|
51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
temp = await x.generate_json()
|
53 |
# print(temp)
|
54 |
|
55 |
-
|
|
|
56 |
request = EditorRequest.model_validate(temp)
|
57 |
-
|
|
|
58 |
|
59 |
|
60 |
async def main(request: GeneratorRequest):
|
@@ -98,7 +143,7 @@ async def generate_video(
|
|
98 |
|
99 |
@generator_router.post("/generate_video_from_json")
|
100 |
async def generate_video_from_json(jsonReq: Story, background_task: BackgroundTasks):
|
101 |
-
background_task.add_task(
|
102 |
return {"task_id": "started"}
|
103 |
|
104 |
|
|
|
6 |
from .Story.Story import Story
|
7 |
import asyncio, pprint, json
|
8 |
from tqdm import tqdm
|
9 |
+
from .database.Model import (
|
10 |
+
models,
|
11 |
+
database_url,
|
12 |
+
Scene,
|
13 |
+
Project,
|
14 |
+
database,
|
15 |
+
VideoGenerator,
|
16 |
+
)
|
17 |
from .utils.RenderVideo import RenderVideo
|
18 |
from .Prompts.StoryGen import Prompt
|
19 |
from App.Editor.editorRoutes import celery_task, EditorRequest
|
20 |
import uuid
|
21 |
|
22 |
|
23 |
+
async def update_scene(model_scene, reference_image_url=None, ip_adapter_weight=0.4):
|
24 |
+
await model_scene.generate_scene_data(reference_image_url, ip_adapter_weight)
|
25 |
+
await model_scene.update(
|
26 |
+
**{
|
27 |
+
k: v
|
28 |
+
for k, v in model_scene.__dict__.items()
|
29 |
+
if k not in ["reference_image_url", "ip_adapter_weight"]
|
30 |
+
}
|
31 |
+
)
|
32 |
|
33 |
|
34 |
async def from_dict_generate(data: Story):
|
|
|
36 |
await generate_assets(generated_story=generated_strory)
|
37 |
|
38 |
|
39 |
+
async def generate_assets(generated_story: Story, batch_size=4, threeD=True):
|
40 |
x = await Project.objects.create(name=str(uuid.uuid4()))
|
41 |
|
42 |
# Assuming generated_story.scenes is a list of scenes
|
|
|
43 |
with tqdm(total=len(generated_story.scenes)) as pbar:
|
44 |
+
|
45 |
+
all_scenes: list[Scene] = []
|
46 |
+
# create the batches
|
47 |
for i in range(0, len(generated_story.scenes), batch_size):
|
48 |
batch = generated_story.scenes[
|
49 |
i : i + batch_size
|
50 |
] # Get a batch of two story scenes
|
51 |
batch_updates = []
|
52 |
|
53 |
+
# generate pictures or narration per batch
|
54 |
for story_scene in batch:
|
55 |
model_scene = await Scene.objects.create(project=x)
|
56 |
model_scene.image_prompts = story_scene.image_prompts
|
57 |
model_scene.narration = story_scene.narration
|
58 |
+
model_scene.voice = story_scene.voice
|
59 |
+
await model_scene.update(
|
60 |
+
**{
|
61 |
+
k: v
|
62 |
+
for k, v in model_scene.__dict__.items()
|
63 |
+
if k not in ["reference_image_url", "ip_adapter_weight"]
|
64 |
+
}
|
65 |
+
)
|
66 |
+
all_scenes.append(model_scene)
|
67 |
batch_updates.append(
|
68 |
+
update_scene(
|
69 |
+
model_scene,
|
70 |
+
generated_story.reference_image_url,
|
71 |
+
generated_story.ip_adapter_weight,
|
72 |
+
)
|
73 |
) # Append update coroutine to batch_updates
|
74 |
+
# pause per batch
|
75 |
await asyncio.gather(
|
76 |
*batch_updates
|
77 |
) # Await update coroutines for this batch
|
78 |
+
|
79 |
pbar.update(len(batch)) # Increment progress bar by the size of the batch
|
80 |
|
81 |
+
###### Here we generate the videos
|
82 |
+
|
83 |
+
if False:
|
84 |
+
vid_gen = VideoGenerator()
|
85 |
+
nested_images = []
|
86 |
+
for scene in all_scenes:
|
87 |
+
nested_images.append(scene.images)
|
88 |
+
|
89 |
+
results = await vid_gen.run(nested_image_links=nested_images)
|
90 |
+
print(results)
|
91 |
+
for result, _scene in zip(results, all_scenes):
|
92 |
+
_scene.images = result
|
93 |
+
await _scene.update(**_scene.__dict__)
|
94 |
+
|
95 |
temp = await x.generate_json()
|
96 |
# print(temp)
|
97 |
|
98 |
+
renderr = RenderVideo()
|
99 |
+
await renderr.render_video(temp)
|
100 |
request = EditorRequest.model_validate(temp)
|
101 |
+
#
|
102 |
+
# await celery_task(video_task=request)
|
103 |
|
104 |
|
105 |
async def main(request: GeneratorRequest):
|
|
|
143 |
|
144 |
@generator_router.post("/generate_video_from_json")
|
145 |
async def generate_video_from_json(jsonReq: Story, background_task: BackgroundTasks):
|
146 |
+
background_task.add_task(generate_assets, jsonReq)
|
147 |
return {"task_id": "started"}
|
148 |
|
149 |
|
App/Generate/utils/Cohere.py
CHANGED
@@ -22,7 +22,11 @@ class VideoOutput(BaseModel):
|
|
22 |
|
23 |
# Patching the Cohere client with the instructor for enhanced capabilities
|
24 |
client = instructor.from_cohere(
|
25 |
-
cohere.Client(
|
|
|
|
|
|
|
|
|
26 |
# max_tokens=5000,
|
27 |
model="command-r-plus",
|
28 |
)
|
@@ -36,7 +40,7 @@ def chatbot(prompt: str, model: str = "command-r-plus"):
|
|
36 |
|
37 |
response: VideoOutput = client.chat.completions.create(
|
38 |
model=model,
|
39 |
-
max_tokens=5000,
|
40 |
response_model=VideoOutput,
|
41 |
messages=[
|
42 |
{
|
@@ -46,3 +50,6 @@ def chatbot(prompt: str, model: str = "command-r-plus"):
|
|
46 |
],
|
47 |
)
|
48 |
return response.dict()
|
|
|
|
|
|
|
|
22 |
|
23 |
# Patching the Cohere client with the instructor for enhanced capabilities
|
24 |
client = instructor.from_cohere(
|
25 |
+
cohere.Client(
|
26 |
+
os.environ.get(
|
27 |
+
"COHERE_API",
|
28 |
+
)
|
29 |
+
),
|
30 |
# max_tokens=5000,
|
31 |
model="command-r-plus",
|
32 |
)
|
|
|
40 |
|
41 |
response: VideoOutput = client.chat.completions.create(
|
42 |
model=model,
|
43 |
+
# max_tokens=5000,
|
44 |
response_model=VideoOutput,
|
45 |
messages=[
|
46 |
{
|
|
|
50 |
],
|
51 |
)
|
52 |
return response.dict()
|
53 |
+
|
54 |
+
|
55 |
+
# print(chatbot("A horror story"))
|
App/Generate/utils/RenderVideo.py
CHANGED
@@ -2,7 +2,9 @@ import aiohttp, json
|
|
2 |
|
3 |
|
4 |
class RenderVideo:
|
5 |
-
def __init__(
|
|
|
|
|
6 |
self.url = url
|
7 |
|
8 |
@staticmethod
|
|
|
2 |
|
3 |
|
4 |
class RenderVideo:
|
5 |
+
def __init__(
|
6 |
+
self, url="https://mbonea-mjema--rectifier-run-rectifier.modal.run/create-video"
|
7 |
+
):
|
8 |
self.url = url
|
9 |
|
10 |
@staticmethod
|
App/Worker.py
CHANGED
@@ -284,7 +284,7 @@ async def cleanup_temp_directory(
|
|
284 |
|
285 |
# @celery.task(name="All")
|
286 |
async def celery_task(video_task: EditorRequest):
|
287 |
-
remotion_app_dir = os.path.join("
|
288 |
project_id = str(uuid.uuid4())
|
289 |
temp_dir = f"/tmp/{project_id}"
|
290 |
output_dir = f"/tmp/{project_id}/out/video.mp4"
|
|
|
284 |
|
285 |
# @celery.task(name="All")
|
286 |
async def celery_task(video_task: EditorRequest):
|
287 |
+
remotion_app_dir = os.path.join("./", "Remotion-app")
|
288 |
project_id = str(uuid.uuid4())
|
289 |
temp_dir = f"/tmp/{project_id}"
|
290 |
output_dir = f"/tmp/{project_id}/out/video.mp4"
|
App/app.py
CHANGED
@@ -15,12 +15,14 @@ manager = WorkerClient()
|
|
15 |
|
16 |
@app.on_event("startup")
|
17 |
async def startup_event():
|
|
|
18 |
app.state.db = database
|
19 |
app.state.models = models
|
20 |
-
|
21 |
try:
|
22 |
# print(type(database.url), database_url)
|
23 |
# await models.create_all()
|
|
|
24 |
await models._create_all(str(database.url))
|
25 |
except:
|
26 |
print("failed to create")
|
@@ -30,7 +32,7 @@ async def startup_event():
|
|
30 |
await database.connect()
|
31 |
# await database.execute("pragma journal_mode=wal;")
|
32 |
|
33 |
-
await bot.start()
|
34 |
# if SERVER_STATE.MASTER:
|
35 |
|
36 |
# response = await manager.register_worker()
|
|
|
15 |
|
16 |
@app.on_event("startup")
|
17 |
async def startup_event():
|
18 |
+
print("StartUp running")
|
19 |
app.state.db = database
|
20 |
app.state.models = models
|
21 |
+
|
22 |
try:
|
23 |
# print(type(database.url), database_url)
|
24 |
# await models.create_all()
|
25 |
+
print("Error registering worker")
|
26 |
await models._create_all(str(database.url))
|
27 |
except:
|
28 |
print("failed to create")
|
|
|
32 |
await database.connect()
|
33 |
# await database.execute("pragma journal_mode=wal;")
|
34 |
|
35 |
+
# await bot.start()
|
36 |
# if SERVER_STATE.MASTER:
|
37 |
|
38 |
# response = await manager.register_worker()
|
Dockerfile
CHANGED
@@ -71,7 +71,7 @@ COPY --chown=admin . /srv
|
|
71 |
RUN chown -R admin:admin /srv
|
72 |
RUN chmod 755 /srv
|
73 |
USER admin
|
74 |
-
CMD python -m uvicorn App.app:app --workers 1 --host 0.0.0.0 --port 7860
|
75 |
|
76 |
# Expose port
|
77 |
EXPOSE 7860
|
|
|
71 |
RUN chown -R admin:admin /srv
|
72 |
RUN chmod 755 /srv
|
73 |
USER admin
|
74 |
+
CMD python -m uvicorn App.app:app --workers 1 --host 0.0.0.0 --port 7860 --log-level debug
|
75 |
|
76 |
# Expose port
|
77 |
EXPOSE 7860
|
Remotion-app/package.json
CHANGED
@@ -17,8 +17,6 @@
|
|
17 |
"@remotion/transitions": "4.0.147",
|
18 |
"@remotion/zod-types": "4.0.147",
|
19 |
"@remotion/tailwind": "4.0.147",
|
20 |
-
"class-variance-authority": "^0.7.0",
|
21 |
-
"clsx": "^2.1.0",
|
22 |
"react": "^18.0.0",
|
23 |
"react-dom": "^18.0.0",
|
24 |
"remotion": "4.0.147",
|
|
|
17 |
"@remotion/transitions": "4.0.147",
|
18 |
"@remotion/zod-types": "4.0.147",
|
19 |
"@remotion/tailwind": "4.0.147",
|
|
|
|
|
20 |
"react": "^18.0.0",
|
21 |
"react-dom": "^18.0.0",
|
22 |
"remotion": "4.0.147",
|
Remotion-app/remotion.config.js
CHANGED
@@ -12,4 +12,4 @@ Config.overrideWebpackConfig((currentConfiguration) => {
|
|
12 |
|
13 |
//Config.setBrowserExecutable("/usr/bin/chrome-headless-shell");
|
14 |
Config.setVideoImageFormat('jpeg');
|
15 |
-
|
|
|
12 |
|
13 |
//Config.setBrowserExecutable("/usr/bin/chrome-headless-shell");
|
14 |
Config.setVideoImageFormat('jpeg');
|
15 |
+
Config.setConcurrency(1);
|
Remotion-app/src/HelloWorld/TextStream.jsx
CHANGED
@@ -43,6 +43,7 @@ const TextStream = React.memo(() => {
|
|
43 |
backgroundColor: 'transparent',
|
44 |
justifyContent: 'center',
|
45 |
alignItems: 'center',
|
|
|
46 |
}}
|
47 |
>
|
48 |
{memoizedTranscriptData.map((entry, index) => {
|
|
|
43 |
backgroundColor: 'transparent',
|
44 |
justifyContent: 'center',
|
45 |
alignItems: 'center',
|
46 |
+
zIndex: 2,
|
47 |
}}
|
48 |
>
|
49 |
{memoizedTranscriptData.map((entry, index) => {
|
Remotion-app/src/HelloWorld/VideoStream.jsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import {
|
2 |
import React from 'react';
|
3 |
import {Video, staticFile, useVideoConfig} from 'remotion';
|
4 |
import videoSequences from './Assets/VideoSequences.json';
|
@@ -30,14 +30,20 @@ const VideoStream = React.memo(() => {
|
|
30 |
|
31 |
const VideoX = React.memo(({entry}) => {
|
32 |
const {fps} = useVideoConfig();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
return (
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
endAt={(fps * entry.props.endAt) / 30}
|
38 |
-
volume={(fps * entry.props.volume) / 30}
|
39 |
-
src={staticFile(entry.name)}
|
40 |
-
/>
|
41 |
);
|
42 |
});
|
43 |
|
|
|
1 |
+
import {Loop} from 'remotion';
|
2 |
import React from 'react';
|
3 |
import {Video, staticFile, useVideoConfig} from 'remotion';
|
4 |
import videoSequences from './Assets/VideoSequences.json';
|
|
|
30 |
|
31 |
const VideoX = React.memo(({entry}) => {
|
32 |
const {fps} = useVideoConfig();
|
33 |
+
const videoProps = {
|
34 |
+
pauseWhenBuffering: true,
|
35 |
+
startFrom: (fps * entry.props.startFrom) / 30,
|
36 |
+
endAt: (fps * entry.props.endAt) / 30,
|
37 |
+
volume: (fps * entry.props.volume) / 30,
|
38 |
+
src: staticFile(entry.name),
|
39 |
+
style: entry?.style ? entry.style : {},
|
40 |
+
playbackRate: entry.props.playbackRate ? entry.props.playbackRate : 1,
|
41 |
+
};
|
42 |
+
|
43 |
return (
|
44 |
+
<>
|
45 |
+
{entry?.loop ? <Video loop {...videoProps} /> : <Video {...videoProps} />}
|
46 |
+
</>
|
|
|
|
|
|
|
|
|
47 |
);
|
48 |
});
|
49 |
|