AZLABS commited on
Commit
ff73331
·
verified ·
1 Parent(s): 252b3c6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -305
app.py CHANGED
@@ -1,330 +1,171 @@
1
  import os
2
- import json
 
 
3
  import urllib.request
4
  from PIL import Image
5
  from gtts import gTTS
6
- import cv2
7
  import moviepy.editor as mp
8
- import logging
9
- import uuid
10
- import time
11
  import gradio as gr
12
- from hercai import Hercai
13
- import requests
14
- from random import randint
15
- # Configure logging
16
- log_dir = os.getenv('LOG_DIRECTORY', './')
17
- LOGGER_FILE_PATH = os.path.join(str(log_dir), 'utils.log')
18
 
 
19
  logging.basicConfig(
20
- filename=LOGGER_FILE_PATH,
21
- filemode='a',
22
- format='[%(asctime)s] [%(levelname)s] [%(filename)s] [%(lineno)s:%(funcName)s()] %(message)s',
23
- datefmt='%Y-%b-%d %H:%M:%S'
 
 
24
  )
25
- LOGGER = logging.getLogger(__name__)
26
-
27
- log_level_env = os.getenv('LOG_LEVEL', 'INFO')
28
- log_level_dict = {
29
- 'DEBUG': logging.DEBUG,
30
- 'INFO': logging.INFO,
31
- 'WARNING': logging.WARNING,
32
- 'ERROR': logging.ERROR,
33
- 'CRITICAL': logging.CRITICAL
34
- }
35
- if log_level_env in log_level_dict:
36
- log_level = log_level_dict[log_level_env]
37
- else:
38
- log_level = log_level_dict['INFO']
39
- LOGGER.setLevel(log_level)
40
-
41
-
42
- class Text2Video:
43
- """A class to generate videos from text prompts."""
44
-
45
- def __init__(self) -> None:
46
- """
47
- Initialize the Text2Video class.
48
- Args:
49
- file_path (str): Path to the configuration file.
50
- """
51
- # Replace Azure OpenAI with Hercai
52
- self.hercai = Hercai("") # Replace "" with your Hercai API key if you have one
53
- self.prodia_model = "stable-diffusion-xl"
54
- self.pollinations_model = None
55
-
56
- def get_image(self, img_prompt: str) -> str:
57
- """
58
- Generate an image based on the provided text prompt using Hercai's draw_image method.
59
- Args:
60
- img_prompt (str): Text prompt for generating the image.
61
- Returns:
62
- str: URL of the generated image.
63
- """
64
  try:
65
- # Generate image using Hercai's draw_image method
66
- image_result = self.hercai.draw_image(model="v3", prompt=img_prompt, negative_prompt="")
67
- image_url = image_result["url"]
68
- return image_url
69
-
 
 
 
 
 
 
 
 
 
70
  except Exception as e:
71
- # Log any errors encountered during image generation
72
- LOGGER.error(f"Error generating image: {e}")
73
- return ""
74
 
75
- def download_img_from_url(self, image_url: str, image_path: str) -> str:
76
- """
77
- Download an image from a URL.
78
- Args:
79
- image_url (str): URL of the image to download.
80
- image_path (str): Path to save the downloaded image.
81
- Returns:
82
- str: Path of the downloaded image.
83
- """
84
  try:
85
- # Download the image from the provided URL and save it to the specified path
86
- urllib.request.urlretrieve(image_url, image_path)
87
- return image_path # Return the path of the downloaded image if successful
88
-
 
 
 
 
 
 
 
 
 
89
  except Exception as e:
90
- # Log any errors encountered during image download
91
- LOGGER.error(f"Error downloading image from URL: {e}")
92
- return "" # Return an empty string if an error occurs
93
 
94
- def text_to_audio(self, img_prompt: str, audio_path: str) -> str:
95
- """
96
- Convert text to speech and save it as an audio file.
97
- Args:
98
- img_prompt (str): Text to convert to speech.
99
- audio_path (str): Path to save the audio file.
100
- Returns:
101
- str: Path of the saved audio file.
102
- """
103
  try:
104
- language = 'en'
105
-
106
- # Create a gTTS object to convert text to speech
107
- myobj = gTTS(text=img_prompt, lang=language, slow=False)
108
-
109
- # Save the audio file at the specified path
110
- myobj.save(audio_path)
111
-
112
- # Return the path of the saved audio file if successful
113
- return audio_path
114
  except Exception as e:
115
-
116
- # Log any errors encountered during text-to-audio conversion
117
- LOGGER.error(f"Error converting text to audio: {e}")
118
- return ""
119
-
120
- def get_images_and_audio(self, list_prompts: list) -> tuple:
121
- """
122
- Generate images and corresponding audio files from a list of prompts.
123
- Args:
124
- list_prompts (list): List of text prompts.
125
- Returns:
126
- tuple: A tuple containing lists of image paths and audio paths.
127
- """
128
- img_list = [] # Initialize an empty list to store image paths
129
- audio_paths = [] # Initialize an empty list to store audio paths
130
- for img_prompt in list_prompts:
131
- try:
132
- # Generate a unique identifier for this file
133
- unique_id = uuid.uuid4().hex
134
-
135
- # Construct the image path using the unique identifier
136
- image_path = f"{img_prompt[:9]}_{unique_id}.png"
137
-
138
- # Generate image URL based on the prompt
139
- img_url = self.get_image(img_prompt)
140
-
141
- # Download and save the image
142
- image = self.download_img_from_url(img_url, image_path)
143
-
144
- # Add the image path to the list
145
- img_list.append(image)
146
-
147
- # Construct the audio path using the unique identifier
148
- audio_path = f"{img_prompt[:9]}_{unique_id}.mp3"
149
-
150
- # Convert text to audio and save it
151
- audio = self.text_to_audio(img_prompt, audio_path)
152
-
153
- # Add the audio path to the list
154
- audio_paths.append(audio)
155
-
156
- except Exception as e:
157
- LOGGER.error(f"Error processing prompt: {img_prompt}, {e}")
158
-
159
- # Return lists of image paths and audio paths as a tuple
160
- return img_list, audio_paths
161
-
162
- def create_video_from_images_and_audio(self, image_files: list, audio_files: list, output_path: str) -> None:
163
- """
164
- Create a video from images and corresponding audio files.
165
- Args:
166
- image_files (list): List of image files.
167
- audio_files (list): List of audio files.
168
- output_path (str): Path to save the output video file.
169
- """
170
  try:
171
- # Check if the number of images matches the number of audio files
172
- if len(image_files) != len(audio_files):
173
- LOGGER.error("Error: Number of images doesn't match the number of audio files.")
174
- return
175
-
176
- # Initialize an empty list to store video clips
177
- video_clips = []
178
-
179
- for image_file, audio_file in zip(image_files, audio_files):
180
-
181
- # Read the image frame
182
- frame = cv2.imread(image_file)
183
-
184
- # Load the audio clip
185
- audio_clip = mp.AudioFileClip(audio_file)
186
-
187
- # Create video clip with image
188
- video_clip = mp.ImageClip(image_file).set_duration(audio_clip.duration)
189
-
190
- # Set audio for the video clip
191
- video_clip = video_clip.set_audio(audio_clip)
192
-
193
- # Append the video clip to the list
194
- video_clips.append(video_clip)
195
-
196
- # Concatenate all video clips into a single clip
197
- final_clip = mp.concatenate_videoclips(video_clips)
198
-
199
- # Write the final video to the output path
200
- final_clip.write_videofile(output_path, codec='libx264', fps=24)
201
- print("Video created successfully.")
202
-
203
  except Exception as e:
204
- # Log any errors encountered during video creation
205
- LOGGER.error(f"Error creating video: {e}")
206
 
207
- def generate_video(self, text: list) -> None:
208
- """
209
- Generate a video from a list of text prompts.
210
- Args:
211
- list_prompts (list): List of text prompts.
212
- """
213
  try:
214
- list_prompts = [sentence.strip() for sentence in text.split(",,") if sentence.strip()]
215
-
216
- # Set the output path for the generated video
217
- output_path = "output_video1.mp4"
218
-
219
- # Generate images and corresponding audio files
220
- img_list, audio_paths = self.get_images_and_audio(list_prompts)
221
-
222
- # Create video from images and audio
223
- self.create_video_from_images_and_audio(img_list, audio_paths, output_path)
224
-
225
- return output_path
226
  except Exception as e:
227
-
228
- # Log any errors encountered during video generation
229
- LOGGER.error(f"Error generating video: {e}")
230
-
231
- def gradio_interface(self):
232
-
233
- with gr.Blocks(css="style.css", theme='abidlabs/dracula_revamped') as demo:
234
- example_txt = """once upon a time there was a village. It was a nice place to live, except for one thing. people did not like to share.,, One day a visitor came to town.
235
- 'Hello. Does anybody have food to share?' He asked. 'No', said everyone.,,
236
- That's okay', said the visitor. 'I will make stone soup for everyone'.Then he took a stone and dropped it into a giant pot,,"""
237
-
238
- gr.HTML("""
239
- <center><h1 style="color:#fff">Comics Video Generator</h1></center>""")
240
-
241
- with gr.Row(elem_id="col-container"):
242
- input_text = gr.Textbox(label="Comics Text", placeholder="Enter the comics by double comma separated")
243
-
244
- with gr.Row(elem_id="col-container"):
245
- button = gr.Button("Generate Video")
246
-
247
- with gr.Row(elem_id="col-container"):
248
- output = gr.PlayableVideo()
249
-
250
- with gr.Row(elem_id="col-container"):
251
- example = gr.Examples([example_txt], input_text)
252
-
253
- button.click(self.generate_video, [input_text], output)
254
- demo.launch(debug=True)
255
-
256
- # --- Prodia & Pollinations Methods ---
257
- def prodia_generate(self, model, prompt, output_file="prodia_output.png"):
258
- s = requests.Session()
259
- headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"}
260
-
261
- resp = s.get(
262
- "https://api.prodia.com/generate",
263
- params={
264
- "new": "true", "prompt": prompt, "model": model,
265
- "negative_prompt": "verybadimagenegative_v1.3",
266
- "steps": "20", "cfg": "7", "seed": randint(1, 10000),
267
- "sample": "DPM++ 2M Karras", "aspect_ratio": "square"
268
- },
269
- headers=headers
270
  )
271
-
272
- job_id = resp.json()['job']
273
- while True:
274
- time.sleep(5)
275
- status = s.get(f"https://api.prodia.com/job/{job_id}", headers=headers).json()
276
- if status["status"] == "succeeded":
277
- img_data = s.get(f"https://images.prodia.xyz/{job_id}.png?download=1", headers=headers).content
278
- with open(output_file, 'wb') as f:
279
- f.write(img_data)
280
- return output_file
281
- return None
282
-
283
- def pollinations_generate(self, prompt, output_file="pollinations_output.png"):
284
- response = requests.get(f"https://image.pollinations.ai/prompt/{prompt}{randint(1, 10000)}")
285
- if response.status_code == 200:
286
- with open(output_file, 'wb') as f:
287
- f.write(response.content)
288
- return output_file
289
- return None
290
-
291
- # --- Hercai Class ---
292
- class Hercai:
293
- def __init__(self, api_key=None):
294
- self.api_key = api_key
295
-
296
- def question(self, model="v3", content="", personality=None):
297
- url = f"https://hercai.onrender.com/v3/hercai?question={content}&model={model}"
298
- if personality:
299
- url += f"&personality={personality}"
300
- if self.api_key:
301
- url += f"&key={self.api_key}"
302
- response = requests.get(url)
303
- return response.json()
304
-
305
- def draw_image(self, model="v3", prompt="", negative_prompt=""):
306
- url = f"https://hercai.onrender.com/v3/text2image?prompt={prompt}&model={model}&negative_prompt={negative_prompt}"
307
- if self.api_key:
308
- url += f"&key={self.api_key}"
309
- response = requests.get(url)
310
- return response.json()
311
-
312
- def beta_question(self, model="v3", content="", personality=None):
313
- url = f"https://hercai.onrender.com/beta/hercai?question={content}&model={model}"
314
- if personality:
315
- url += f"&personality={personality}"
316
- if self.api_key:
317
- url += f"&key={self.api_key}"
318
- response = requests.get(url)
319
- return response.json()
320
-
321
- def beta_draw_image(self, model="v3", prompt="", negative_prompt=""):
322
- url = f"https://hercai.onrender.com/beta/text2image?prompt={prompt}&model={model}&negative_prompt={negative_prompt}"
323
- if self.api_key:
324
- url += f"&key={self.api_key}"
325
- response = requests.get(url)
326
- return response.json()
327
 
328
  if __name__ == "__main__":
329
- text2video = Text2Video()
330
- text2video.gradio_interface()
 
1
  import os
2
+ import logging
3
+ import uuid
4
+ from typing import List, Tuple
5
  import urllib.request
6
  from PIL import Image
7
  from gtts import gTTS
 
8
  import moviepy.editor as mp
 
 
 
9
  import gradio as gr
10
+ from hercai import Hercai
 
 
 
 
 
11
 
12
+ # Configure logging
13
  logging.basicConfig(
14
+ level=logging.INFO,
15
+ format='%(asctime)s - %(levelname)s - %(message)s',
16
+ handlers=[
17
+ logging.FileHandler('comic_generator.log'),
18
+ logging.StreamHandler()
19
+ ]
20
  )
21
+ logger = logging.getLogger(__name__)
22
+
23
+ class ComicVideoGenerator:
24
+ def __init__(self):
25
+ """Initialize comic video generator with Hercai API"""
26
+ self.api = Hercai()
27
+ self.image_size = (1024, 1024)
28
+
29
+ def generate_comic_image(self, prompt: str) -> str:
30
+ """Generate comic-style image from text prompt"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  try:
32
+ enhanced_prompt = (
33
+ f"{prompt}, comic book style, vibrant colors, "
34
+ "clear speech bubbles, dramatic lighting, "
35
+ "detailed backgrounds, professional illustration"
36
+ )
37
+
38
+ result = self.api.draw_image(
39
+ model="simurg",
40
+ prompt=enhanced_prompt,
41
+ negative_prompt="blurry, low quality, dark"
42
+ )
43
+ logger.info(f"Generated image for prompt: {prompt[:30]}...")
44
+ return result["url"]
45
+
46
  except Exception as e:
47
+ logger.error(f"Image generation failed: {e}")
48
+ raise
 
49
 
50
+ def process_image(self, url: str, save_path: str) -> str:
51
+ """Download and process image to correct size"""
 
 
 
 
 
 
 
52
  try:
53
+ urllib.request.urlretrieve(url, save_path)
54
+
55
+ with Image.open(save_path) as img:
56
+ img.thumbnail(self.image_size)
57
+ new_img = Image.new('RGB', self.image_size, 'white')
58
+ offset = tuple(map(lambda x, y: (x - y) // 2,
59
+ self.image_size, img.size))
60
+ new_img.paste(img, offset)
61
+ new_img.save(save_path, quality=95)
62
+
63
+ logger.info(f"Processed and saved image: {save_path}")
64
+ return save_path
65
+
66
  except Exception as e:
67
+ logger.error(f"Image processing failed: {e}")
68
+ raise
 
69
 
70
+ def create_audio(self, text: str, save_path: str) -> str:
71
+ """Generate audio narration from text"""
 
 
 
 
 
 
 
72
  try:
73
+ tts = gTTS(text=text, lang='en')
74
+ tts.save(save_path)
75
+ logger.info(f"Created audio: {save_path}")
76
+ return save_path
 
 
 
 
 
 
77
  except Exception as e:
78
+ logger.error(f"Audio generation failed: {e}")
79
+ raise
80
+
81
+ def process_scene(self, prompt: str, scene_id: str) -> Tuple[str, str]:
82
+ """Process single scene (image + audio)"""
83
+ image_path = f"scene_{scene_id}.png"
84
+ audio_path = f"audio_{scene_id}.mp3"
85
+
86
+ image_url = self.generate_comic_image(prompt)
87
+ image_file = self.process_image(image_url, image_path)
88
+ audio_file = self.create_audio(prompt, audio_path)
89
+
90
+ return image_file, audio_file
91
+
92
+ def create_video(self, images: List[str], audios: List[str],
93
+ output_path: str) -> str:
94
+ """Create final video from images and audio"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  try:
96
+ clips = []
97
+ for img, audio in zip(images, audios):
98
+ audio_clip = mp.AudioFileClip(audio)
99
+ video_clip = (mp.ImageClip(img)
100
+ .set_duration(audio_clip.duration)
101
+ .set_audio(audio_clip))
102
+ clips.append(video_clip)
103
+
104
+ final_clip = mp.concatenate_videoclips(clips)
105
+ final_clip.write_videofile(
106
+ output_path,
107
+ fps=24,
108
+ codec='libx264',
109
+ audio_codec='aac'
110
+ )
111
+ logger.info(f"Created video: {output_path}")
112
+ return output_path
113
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  except Exception as e:
115
+ logger.error(f"Video creation failed: {e}")
116
+ raise
117
 
118
+ def generate(self, text: str) -> str:
119
+ """Main video generation pipeline"""
 
 
 
 
120
  try:
121
+ scenes = [s.strip() for s in text.split(",,") if s.strip()]
122
+ output_path = f"comic_{uuid.uuid4().hex[:8]}.mp4"
123
+
124
+ images, audios = [], []
125
+ for i, scene in enumerate(scenes):
126
+ img, audio = self.process_scene(scene, f"{i}")
127
+ images.append(img)
128
+ audios.append(audio)
129
+
130
+ return self.create_video(images, audios, output_path)
131
+
 
132
  except Exception as e:
133
+ logger.error(f"Generation pipeline failed: {e}")
134
+ raise
135
+
136
+ def create_interface():
137
+ """Create Gradio interface"""
138
+ generator = ComicVideoGenerator()
139
+
140
+ examples = [
141
+ "A magical forest at sunset.,, A brave knight finds a glowing crystal.,, The crystal transforms into a dragon.",
142
+ "A busy city street.,, A mysterious package appears.,, The package opens to reveal a portal."
143
+ ]
144
+
145
+ with gr.Blocks(theme='default') as demo:
146
+ gr.Markdown("# Comic Video Generator")
147
+
148
+ with gr.Row():
149
+ text_input = gr.Textbox(
150
+ label="Story Text",
151
+ placeholder="Enter story scenes separated by ',,'",
152
+ lines=3
153
+ )
154
+
155
+ with gr.Row():
156
+ generate_btn = gr.Button("Generate Comic")
157
+ video_output = gr.Video(label="Generated Comic")
158
+
159
+ gr.Examples(examples, text_input)
160
+
161
+ generate_btn.click(
162
+ fn=generator.generate,
163
+ inputs=text_input,
164
+ outputs=video_output
 
 
 
 
 
 
 
 
 
 
 
165
  )
166
+
167
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  if __name__ == "__main__":
170
+ interface = create_interface()
171
+ interface.launch(debug=True)