Remsky commited on
Commit
c079f49
β€’
1 Parent(s): 4e0f567

Enhance project structure with new app entry point, updated README, and improved UI components; refine .gitignore and loading messages

Browse files
Files changed (10) hide show
  1. .gitattributes +2 -0
  2. .gitignore +4 -2
  3. README.md +40 -14
  4. TestImage.png +3 -0
  5. TestVideo.mp4 +3 -0
  6. app.py +5 -0
  7. gradio_app.py +43 -54
  8. lib/status_utils.py +15 -10
  9. lib/ui_components.py +92 -46
  10. loading_messages.json +21 -1
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.png filter=lfs diff=lfs merge=lfs -text
37
+ *.mp4 filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -1,2 +1,4 @@
1
- *.env
2
- *.pyc
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .env
4
+ .DS_Store
README.md CHANGED
@@ -1,14 +1,40 @@
1
- ---
2
- title: Luma Api Dreammachine Gui
3
- emoji: πŸš€
4
- colorFrom: red
5
- colorTo: green
6
- sdk: gradio
7
- sdk_version: 5.4.0
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- short_description: BYOK LumaAI DreamMachine Interface
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LumaAI Video Generator
2
+
3
+ A Gradio web interface for generating videos using LumaAI's API. Transform your prompts into mesmerizing videos with optional image input and camera motion controls.
4
+
5
+ ## Features
6
+
7
+ - Text-to-video generation
8
+ - Image-to-video generation
9
+ - Camera motion controls
10
+ - Real-time status updates with fun messages
11
+ - Modern, user-friendly interface
12
+
13
+ ## Usage
14
+
15
+ 1. Enter your LumaAI API key
16
+ 2. Type your creative prompt
17
+ 3. (Optional) Select a camera motion
18
+ 4. (Optional) Upload an image
19
+ 5. Click Generate and watch the magic happen!
20
+
21
+ ## Development
22
+
23
+ To run locally:
24
+
25
+ ```bash
26
+ pip install -r requirements.txt
27
+ python gradio_app.py
28
+ ```
29
+
30
+ ## Deployment
31
+
32
+ This app is ready for Hugging Face Spaces deployment. Simply push to your Spaces repository and it will automatically deploy.
33
+
34
+ ## Environment Variables
35
+
36
+ - No environment variables required - API key is input through the UI
37
+
38
+ ## License
39
+
40
+ MIT
TestImage.png ADDED

Git LFS Details

  • SHA256: 8dfdee46b593749ea28568f368303d4693708ed64a3f52375dcb5fc9b2eb339f
  • Pointer size: 132 Bytes
  • Size of remote file: 2.3 MB
TestVideo.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa8d3ee0ed2c9f6f9de381a4d6ef8a5cee442d65ef5138fcc4a6396e2add1ada
3
+ size 8015996
app.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Hugging Face Spaces entry point
2
+ from gradio_app import app
3
+
4
+ if __name__ == "__main__":
5
+ app.launch()
gradio_app.py CHANGED
@@ -5,31 +5,36 @@ import requests
5
  from lumaai import LumaAI
6
  import traceback
7
 
8
- from lib.status_utils import StatusTracker, load_messages
9
  from lib.image_utils import prepare_image
10
  from lib.api_utils import get_camera_motions
11
- from lib.ui_components import create_input_column, create_output_column
12
 
13
- def generate_video(api_key, prompt, camera_motion, image=None, progress=gr.Progress()):
14
- status_box = gr.Markdown() # Create status box
15
  if not api_key or not prompt:
16
- return None, "Please provide both API key and prompt (I'm not a mind reader... yet)"
17
 
18
  try:
19
- status_tracker = StatusTracker(progress, status_box)
20
- status_tracker.add_step("LumaAI initialized", 0.01)
21
  client = LumaAI(auth_token=api_key)
22
 
 
 
 
 
 
 
23
  # Prepare generation parameters
24
  generation_params = {
25
  "prompt": f"{prompt} {camera_motion if camera_motion != 'None' else ''}",
26
- "loop": True,
27
  "aspect_ratio": "1:1" # Force square aspect ratio
28
  }
29
 
30
  # Handle image if provided
31
  if image is not None:
32
  try:
 
33
  cdn_url = prepare_image(image, status_tracker)
34
  generation_params["keyframes"] = {
35
  "frame0": {
@@ -37,23 +42,23 @@ def generate_video(api_key, prompt, camera_motion, image=None, progress=gr.Progr
37
  "url": cdn_url
38
  }
39
  }
40
- status_tracker.add_step("Image ready for its starring role", 0.1)
41
  except Exception as e:
42
- return None, f"🎭 Drama in the image department: {str(e)}"
43
 
44
- status_tracker.add_step("Sending your creative masterpiece to LumaAI", 0.15)
45
  try:
46
  generation = client.generations.create(**generation_params)
47
  except Exception as e:
48
- return None, f"🎬 LumaAI didn't like that: {str(e)}"
49
 
50
- # Load and shuffle status messages
51
  status_messages = load_messages()
52
  random.shuffle(status_messages)
 
 
53
 
54
  # Poll for completion
55
  start_time = time.time()
56
- message_index = 0
57
  last_status = None
58
 
59
  while True:
@@ -61,36 +66,35 @@ def generate_video(api_key, prompt, camera_motion, image=None, progress=gr.Progr
61
  generation_status = client.generations.get(generation.id)
62
  status = generation_status.state
63
  elapsed_time = time.time() - start_time
 
64
 
65
- if status != last_status:
66
- status_tracker.add_step(f"Status: {status}", min(0.2 + (elapsed_time/300), 0.8))
67
- last_status = status
68
-
69
- current_message = status_messages[message_index % len(status_messages)]
70
- status_tracker.update_message(current_message, min(0.2 + (elapsed_time/300), 0.8))
71
- message_index += 1
72
 
73
  if status == 'completed':
74
- status_tracker.add_step("Generation completed!", 0.9)
75
  download_url = generation_status.assets.video
76
  break
77
  elif status == 'failed':
78
- failure_reason = generation_status.failure_reason or "It's not you, it's me"
79
- return None, f"🎭 Generation failed: {failure_reason}"
80
 
81
  if elapsed_time > 300:
82
- return None, "⏰ Generation timeout (5 minutes of awkward silence)"
83
 
84
- time.sleep(10)
85
 
86
  except Exception as e:
87
  print(f"Error during generation polling: {str(e)}")
88
  print(traceback.format_exc())
89
- time.sleep(10)
90
  continue
91
 
92
  # Download the video
93
- status_tracker.update_message("Downloading your masterpiece...", 0.95)
94
  try:
95
  response = requests.get(download_url, stream=True, timeout=30)
96
  response.raise_for_status()
@@ -98,38 +102,23 @@ def generate_video(api_key, prompt, camera_motion, image=None, progress=gr.Progr
98
  with open(file_path, 'wb') as file:
99
  file.write(response.content)
100
 
101
- status_tracker.add_step("πŸŽ‰ Video ready!", 1.0)
102
- return file_path, status_box
103
  except Exception as e:
104
- return None, f"πŸ“Ί Video download failed: {str(e)}"
105
 
 
 
106
  except Exception as e:
107
  print(f"Error during generation: {str(e)}")
108
  print(traceback.format_exc())
109
- return None, f"πŸŽͺ The show must go on, but: {str(e)}"
110
 
111
- # Create Gradio interface with a modern theme
112
- with gr.Blocks(theme=gr.themes.Soft(
113
- primary_hue="indigo",
114
- secondary_hue="purple",
115
- )) as app:
116
- gr.Markdown(
117
- """
118
- # 🎬 LumaAI Video Generator
119
- ### Transform your prompts into mesmerizing videos
120
- """
121
- )
122
-
123
- with gr.Row():
124
- # Create input and output columns
125
- prompt, camera_motion, api_key, image_input, generate_btn, status_display = create_input_column()
126
- video_output = create_output_column()
127
-
128
- generate_btn.click(
129
- fn=generate_video,
130
- inputs=[api_key, prompt, camera_motion, image_input],
131
- outputs=[video_output, status_display]
132
- )
133
 
134
  if __name__ == "__main__":
135
  app.launch()
 
5
  from lumaai import LumaAI
6
  import traceback
7
 
8
+ from lib.status_utils import load_messages, StatusTracker
9
  from lib.image_utils import prepare_image
10
  from lib.api_utils import get_camera_motions
11
+ from lib.ui_components import create_interface
12
 
13
+ def generate_video(api_key, prompt, camera_motion, loop_video, image=None, progress=gr.Progress()):
 
14
  if not api_key or not prompt:
15
+ raise gr.Error("Please enter your LumaAI API key and prompt")
16
 
17
  try:
18
+ progress(0, desc="Initializing LumaAI...")
 
19
  client = LumaAI(auth_token=api_key)
20
 
21
+ # Create status tracker with progress object
22
+ status_tracker = StatusTracker(
23
+ progress=lambda x: progress(x),
24
+ status_box=None
25
+ )
26
+
27
  # Prepare generation parameters
28
  generation_params = {
29
  "prompt": f"{prompt} {camera_motion if camera_motion != 'None' else ''}",
30
+ "loop": loop_video,
31
  "aspect_ratio": "1:1" # Force square aspect ratio
32
  }
33
 
34
  # Handle image if provided
35
  if image is not None:
36
  try:
37
+ progress(0.1, desc="Preparing image...")
38
  cdn_url = prepare_image(image, status_tracker)
39
  generation_params["keyframes"] = {
40
  "frame0": {
 
42
  "url": cdn_url
43
  }
44
  }
 
45
  except Exception as e:
46
+ raise gr.Error("Failed to process the input image")
47
 
48
+ progress(0.2, desc="Starting generation...")
49
  try:
50
  generation = client.generations.create(**generation_params)
51
  except Exception as e:
52
+ raise gr.Error("Failed to start video generation. Please check your API key.")
53
 
54
+ # Load and shuffle status messages for variety
55
  status_messages = load_messages()
56
  random.shuffle(status_messages)
57
+ message_index = 0
58
+ last_message_time = time.time()
59
 
60
  # Poll for completion
61
  start_time = time.time()
 
62
  last_status = None
63
 
64
  while True:
 
66
  generation_status = client.generations.get(generation.id)
67
  status = generation_status.state
68
  elapsed_time = time.time() - start_time
69
+ current_time = time.time()
70
 
71
+ # Update status message at random intervals between 2-8 seconds
72
+ if current_time - last_message_time >= random.uniform(2, 8):
73
+ progress_val = min(0.2 + (elapsed_time/60), 0.8) # Adjusted for 1-minute expectation
74
+ progress(progress_val, desc=status_messages[message_index % len(status_messages)])
75
+ message_index += 1
76
+ last_message_time = current_time
 
77
 
78
  if status == 'completed':
79
+ progress(0.9, desc="Generation completed!")
80
  download_url = generation_status.assets.video
81
  break
82
  elif status == 'failed':
83
+ raise gr.Error("Video generation failed")
 
84
 
85
  if elapsed_time > 300:
86
+ raise gr.Error("Generation timed out after 5 minutes")
87
 
88
+ time.sleep(1)
89
 
90
  except Exception as e:
91
  print(f"Error during generation polling: {str(e)}")
92
  print(traceback.format_exc())
93
+ time.sleep(1)
94
  continue
95
 
96
  # Download the video
97
+ progress(0.95, desc="Downloading video...")
98
  try:
99
  response = requests.get(download_url, stream=True, timeout=30)
100
  response.raise_for_status()
 
102
  with open(file_path, 'wb') as file:
103
  file.write(response.content)
104
 
105
+ progress(1.0, desc="Video ready!")
106
+ return file_path
107
  except Exception as e:
108
+ raise gr.Error("Failed to download the generated video")
109
 
110
+ except gr.Error as e:
111
+ raise e
112
  except Exception as e:
113
  print(f"Error during generation: {str(e)}")
114
  print(traceback.format_exc())
115
+ raise gr.Error("An unexpected error occurred")
116
 
117
+ # Create Gradio interface
118
+ app = create_interface(generate_video)
119
+
120
+ # For Hugging Face Spaces, we want to specify a smaller queue size
121
+ app.queue(max_size=5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
  if __name__ == "__main__":
124
  app.launch()
lib/status_utils.py CHANGED
@@ -16,7 +16,7 @@ class StatusTracker:
16
  self.status_box = status_box
17
  self.steps = []
18
  self.current_message = ""
19
- self._status_markdown = "### 🎬 Ready to Generate"
20
 
21
  def add_step(self, message: str, progress_value: float):
22
  """
@@ -26,9 +26,8 @@ class StatusTracker:
26
  message: Step description
27
  progress_value: Progress value between 0 and 1
28
  """
29
- self.steps.append(f"βœ“ {message}")
30
  self._update_display(progress_value)
31
- time.sleep(0.5) # Brief pause for visibility
32
 
33
  def update_message(self, message: str, progress_value: float):
34
  """
@@ -38,7 +37,7 @@ class StatusTracker:
38
  message: Current status message
39
  progress_value: Progress value between 0 and 1
40
  """
41
- self.current_message = f"➀ {message}"
42
  self._update_display(progress_value)
43
 
44
  def _update_display(self, progress_value: float):
@@ -48,17 +47,23 @@ class StatusTracker:
48
  Args:
49
  progress_value: Progress value between 0 and 1
50
  """
51
- # Create markdown-formatted status display
52
- status_md = "### 🎬 Generation Progress:\n"
53
- for step in self.steps:
54
- status_md += f"- {step}\n"
 
 
 
 
 
 
55
  if self.current_message:
56
- status_md += f"\n**Current Step:**\n{self.current_message}"
57
 
58
  self._status_markdown = status_md
59
  self.progress(progress_value)
60
 
61
- # Only try to update status_box if it exists
62
  if self.status_box is not None:
63
  try:
64
  self.status_box.update(value=self._status_markdown)
 
16
  self.status_box = status_box
17
  self.steps = []
18
  self.current_message = ""
19
+ self._status_markdown = ""
20
 
21
  def add_step(self, message: str, progress_value: float):
22
  """
 
26
  message: Step description
27
  progress_value: Progress value between 0 and 1
28
  """
29
+ self.steps.append(message)
30
  self._update_display(progress_value)
 
31
 
32
  def update_message(self, message: str, progress_value: float):
33
  """
 
37
  message: Current status message
38
  progress_value: Progress value between 0 and 1
39
  """
40
+ self.current_message = message
41
  self._update_display(progress_value)
42
 
43
  def _update_display(self, progress_value: float):
 
47
  Args:
48
  progress_value: Progress value between 0 and 1
49
  """
50
+ # Create status display
51
+ status_md = "### 🎬 Generation Progress\n\n"
52
+
53
+ # Show completed steps
54
+ if self.steps:
55
+ for step in self.steps:
56
+ status_md += f"βœ“ {step}\n"
57
+ status_md += "\n"
58
+
59
+ # Show current message prominently
60
  if self.current_message:
61
+ status_md += f"### πŸ”„ {self.current_message}\n"
62
 
63
  self._status_markdown = status_md
64
  self.progress(progress_value)
65
 
66
+ # Update status box if it exists
67
  if self.status_box is not None:
68
  try:
69
  self.status_box.update(value=self._status_markdown)
lib/ui_components.py CHANGED
@@ -1,57 +1,103 @@
1
  import gradio as gr
2
  from .api_utils import get_camera_motions
 
3
 
4
- def create_input_column():
5
- """Create the input column of the UI."""
6
- with gr.Column(scale=1, min_width=400) as column:
7
- # Main inputs
8
- prompt = gr.Textbox(
9
- label="Prompt",
10
- placeholder="Describe your video scene here...",
11
- lines=3
12
- )
13
- camera_motion = gr.Dropdown(
14
- choices=get_camera_motions(),
15
- label="Camera Motion",
16
- value="None"
17
- )
18
-
19
- # Collapsible sections
20
- with gr.Accordion("πŸ”‘ API Settings", open=False):
 
 
 
 
 
 
 
 
 
21
  api_key = gr.Textbox(
22
  label="LumaAI API Key",
23
- placeholder="Enter your API key here",
24
- type="password"
 
 
 
 
25
  )
26
-
27
- with gr.Accordion("πŸ–ΌοΈ Advanced Options", open=False):
28
- image_input = gr.Image(
29
- label="Starting Image (will be resized to 512x512)",
30
- type="pil"
 
 
 
 
 
 
 
 
31
  )
 
 
 
 
 
 
 
 
 
32
 
33
- generate_btn = gr.Button("πŸš€ Generate Video", variant="primary", size="lg")
34
-
35
- # Status display
36
- status_display = gr.Markdown()
37
-
38
- gr.Markdown(
39
- """
40
- ### 🎯 Pro Tips:
41
- - Be specific and descriptive in your prompts
42
- - Try different camera motions for dynamic effects
43
- - Generation usually takes 1-3 minutes β˜•
44
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  )
46
 
47
- return prompt, camera_motion, api_key, image_input, generate_btn, status_display
48
-
49
- def create_output_column():
50
- """Create the output column of the UI."""
51
- with gr.Column(scale=1, min_width=600) as column:
52
- video_output = gr.Video(
53
- label="Generated Video",
54
- width="100%",
55
- height="400px"
56
  )
57
- return video_output
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from .api_utils import get_camera_motions
3
+ from pathlib import Path
4
 
5
+ def create_header():
6
+ """Create the header with title and API settings in 3 columns."""
7
+ with gr.Row():
8
+ # Title column
9
+ with gr.Column(scale=1):
10
+ gr.Markdown(
11
+ """
12
+ # 🎬 LumaAI Video Generator
13
+ ### Transform your prompts into mesmerizing videos
14
+ """
15
+ )
16
+
17
+ # API info column
18
+ with gr.Column(scale=1):
19
+ gr.Markdown("Get an API key from [LumaAI Dream Machine](https://lumalabs.ai/dream-machine/api)")
20
+ gr.Markdown("*$0.0032 USD/megapixel: 720p, 5 sec video is around $0.4 USD*")
21
+
22
+ # Empty column for symmetry
23
+ with gr.Column(scale=1):
24
+ pass
25
+
26
+ def create_main_interface(generate_fn):
27
+ """Create the main interface with input and output columns."""
28
+ with gr.Row():
29
+ # Input column
30
+ with gr.Column(scale=1):
31
  api_key = gr.Textbox(
32
  label="LumaAI API Key",
33
+ placeholder="Enter a LumaAI API key",
34
+ type="password",
35
+ autofocus=True,
36
+ show_label=False,
37
+ container=False,
38
+ scale=1
39
  )
40
+
41
+ with gr.Row():
42
+ prompt = gr.Textbox(
43
+ label="Prompt",
44
+ placeholder="Describe your video scene here...",
45
+ lines=3,
46
+ value="Dreamlike scene transforms into circuitry"
47
+ )
48
+
49
+ camera_motion = gr.Dropdown(
50
+ choices=get_camera_motions(),
51
+ label="Camera Motion",
52
+ value="None"
53
  )
54
+
55
+ loop_video = gr.Checkbox(
56
+ label="Loop Video",
57
+ value=False,
58
+ info="Enable video looping"
59
+ )
60
+
61
+ generate_btn = gr.Button("πŸš€ Generate Video", variant="primary", size="lg")
62
+ clear_btn = gr.Button("πŸ—‘οΈ Clear Placeholders", size="sm", variant="huggingface")
63
 
64
+ # Output column
65
+ with gr.Column(scale=1):
66
+ video_output = gr.Video(
67
+ label="Generated Video",
68
+ show_label=True,
69
+ width="100%",
70
+ height="400px",
71
+ value="TestVideo.mp4"
72
+ )
73
+
74
+ with gr.Accordion("πŸ–ΌοΈ Starting Image [Optional]", open=True):
75
+ image_input = gr.Image(
76
+ label="Starting Image (will be resized to 512x512)",
77
+ type="pil",
78
+ value="TestImage.png"
79
+ )
80
+
81
+ # Set up event handlers
82
+ def clear_all():
83
+ return ["", None, None] # Clear prompt, image, and video
84
+
85
+ clear_btn.click(
86
+ fn=clear_all,
87
+ inputs=[],
88
+ outputs=[prompt, image_input, video_output]
89
  )
90
 
91
+ generate_btn.click(
92
+ fn=generate_fn,
93
+ inputs=[api_key, prompt, camera_motion, loop_video, image_input],
94
+ outputs=video_output,
95
+ api_name=False
 
 
 
 
96
  )
97
+
98
+ def create_interface(generate_fn):
99
+ """Create the complete interface."""
100
+ with gr.Blocks(theme=gr.themes.Monochrome()) as interface:
101
+ create_header()
102
+ create_main_interface(generate_fn)
103
+ return interface
loading_messages.json CHANGED
@@ -29,6 +29,26 @@
29
  "Performing ritual sacrifices to the GPU gods...",
30
  "Translating prompt into binary and back again...",
31
  "Asking AI to think outside the box (it's stuck)...",
32
- "Generating random excuses for slow processing..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  ]
34
  }
 
29
  "Performing ritual sacrifices to the GPU gods...",
30
  "Translating prompt into binary and back again...",
31
  "Asking AI to think outside the box (it's stuck)...",
32
+ "Generating random excuses for slow processing...",
33
+ "Convincing the AI this isn't just another cat video...",
34
+ "Teaching neural networks about personal space...",
35
+ "Negotiating with the cloud for better weather...",
36
+ "Explaining to AI why it can't have a coffee break...",
37
+ "Converting prompt into interpretive dance moves...",
38
+ "Asking the GPU if it needs a hug...",
39
+ "Teaching AI about fashion (still wearing pixels)...",
40
+ "Consulting the magic 8-ball for rendering advice...",
41
+ "Trying to explain memes to the neural network...",
42
+ "Asking AI to stop playing Minecraft with the pixels...",
43
+ "Converting your ideas into digital daydreams...",
44
+ "Teaching AI about personal boundaries (again)...",
45
+ "Explaining to GPU why overtime is mandatory...",
46
+ "Convincing AI that lens flares aren't always necessary...",
47
+ "Debugging the space-time continuum...",
48
+ "Asking AI to stop making everything cyberpunk...",
49
+ "Teaching neural networks about work-life balance...",
50
+ "Explaining to AI why it can't have a TikTok account...",
51
+ "Negotiating with pixels for better composition...",
52
+ "Convincing the render farm to work weekends..."
53
  ]
54
  }