File size: 9,275 Bytes
b87c1b6
d872fa5
bc2cb72
275a10f
b78c4e1
10616f8
b87c1b6
d872fa5
b87c1b6
29ea35b
 
6b2dd9c
29ea35b
d872fa5
 
275a10f
b87c1b6
275a10f
 
b87c1b6
275a10f
 
 
5f47e36
e34f60e
275a10f
 
b87c1b6
275a10f
 
 
d872fa5
 
24c348f
 
b87c1b6
d872fa5
b87c1b6
275a10f
d872fa5
b87c1b6
 
 
 
 
 
 
 
 
 
 
29ea35b
 
24c348f
e34f60e
b87c1b6
 
 
 
 
 
 
 
 
e34f60e
 
 
 
 
b87c1b6
e34f60e
b87c1b6
e34f60e
b87c1b6
e34f60e
b87c1b6
 
e34f60e
b87c1b6
 
 
 
 
 
 
 
 
 
 
 
5f47e36
 
b87c1b6
5f47e36
b87c1b6
 
e34f60e
 
 
b87c1b6
e34f60e
d872fa5
e34f60e
b87c1b6
 
 
 
 
 
 
 
 
d872fa5
e34f60e
d872fa5
10616f8
8d392f5
10616f8
b78c4e1
10616f8
 
 
b87c1b6
 
 
 
8d392f5
 
 
 
 
 
 
10616f8
8d392f5
10616f8
 
8d392f5
10616f8
 
 
8d392f5
 
 
 
6b2dd9c
b78c4e1
 
 
 
 
 
b87c1b6
8d392f5
6b2dd9c
b78c4e1
 
8d392f5
b78c4e1
8d392f5
10616f8
fea8f7a
 
 
6b2dd9c
 
b87c1b6
10616f8
6b2dd9c
b87c1b6
10616f8
 
 
 
 
 
 
 
 
 
 
 
b87c1b6
10616f8
 
 
ec68f4a
2a343c0
10616f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ec68f4a
10616f8
8d392f5
10616f8
 
 
2a343c0
10616f8
 
 
 
 
ec68f4a
10616f8
 
b87c1b6
6b2dd9c
d872fa5
 
275a10f
 
 
 
 
 
b87c1b6
6b2dd9c
2a343c0
 
 
 
57451e8
2a343c0
10616f8
 
 
 
2a343c0
10616f8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import gradio as gr
import os
import tempfile
import shutil
from typing import Optional, Tuple, Union
from huggingface_hub import InferenceClient
from pathlib import Path

# Initialize Hugging Face Inference Client with fal-ai provider
client = InferenceClient(
    provider="fal-ai",
    api_key=os.environ.get("HF_TOKEN"),
    bill_to="huggingface",
)

def cleanup_temp_files():
    """Clean up old temporary video files to prevent storage overflow."""
    try:
        temp_dir = tempfile.gettempdir()
        # Clean up old .mp4 files in temp directory
        for file_path in Path(temp_dir).glob("*.mp4"):
            try:
                # Remove files older than 5 minutes
                import time
                if file_path.stat().st_mtime < (time.time() - 300):
                    file_path.unlink(missing_ok=True)
            except Exception:
                pass
    except Exception as e:
        print(f"Cleanup error: {e}")

def generate_video(
    prompt: str,
    duration: int = 8,
    size: str = "1280x720",
    api_key: Optional[str] = None
) -> Tuple[Optional[str], str]:
    """Generate video using Sora-2 through Hugging Face Inference API with fal-ai provider."""
    cleanup_temp_files()
    try:
        if api_key:
            temp_client = InferenceClient(
                provider="fal-ai",
                api_key=api_key,
                bill_to="huggingface",
            )
        else:
            temp_client = client
            if not os.environ.get("HF_TOKEN") and not api_key:
                return None, "❌ Please set HF_TOKEN environment variable."
        
        video_bytes = temp_client.text_to_video(
            prompt,
            model="akhaliq/sora-2",
        )
        
        temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
        try:
            temp_file.write(video_bytes)
            temp_file.flush()
            video_path = temp_file.name
        finally:
            temp_file.close()
        
        return video_path, "βœ… Video generated successfully!"
    except Exception as e:
        return None, f"❌ Error generating video: {str(e)}"

def generate_video_from_image(
    image: Union[str, bytes],
    prompt: str,
    api_key: Optional[str] = None
) -> Tuple[Optional[str], str]:
    """Generate a video from a single input image + prompt using Sora-2 image-to-video."""
    cleanup_temp_files()
    if not prompt or prompt.strip() == "":
        return None, "❌ Please enter a prompt"
    try:
        if api_key:
            temp_client = InferenceClient(
                provider="fal-ai",
                api_key=api_key,
                bill_to="huggingface",
            )
        else:
            temp_client = client
            if not os.environ.get("HF_TOKEN") and not api_key:
                return None, "❌ Please set HF_TOKEN environment variable."

        if isinstance(image, str):
            with open(image, "rb") as f:
                input_image = f.read()
        elif isinstance(image, (bytes, bytearray)):
            input_image = image
        else:
            return None, "❌ Invalid image input. Please upload an image."

        video_bytes = temp_client.image_to_video(
            input_image,
            prompt=prompt,
            model="akhaliq/sora-2-image-to-video",
        )

        temp_file = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
        try:
            temp_file.write(video_bytes)
            temp_file.flush()
            video_path = temp_file.name
        finally:
            temp_file.close()

        return video_path, "βœ… Video generated from image successfully!"
    except Exception as e:
        return None, f"❌ Error generating video from image: {str(e)}"

def generate_with_auth(
    prompt: str, 
    profile: gr.OAuthProfile | None
) -> Tuple[Optional[str], str]:
    """Wrapper function that checks if user is logged in before generating video."""
    if profile is None:
        raise gr.Error("Click Sign in with Hugging Face button to use this app for free")
    
    if not prompt or prompt.strip() == "":
        return None, "❌ Please enter a prompt"
    
    return generate_video(
        prompt, 
        duration=8, 
        size="1280x720", 
        api_key=None
    )

def generate_with_auth_image(
    prompt: str,
    image_path: Optional[str],
    profile: gr.OAuthProfile | None
) -> Tuple[Optional[str], str]:
    """Checks login status then calls image->video generator."""
    if profile is None:
        raise gr.Error("Click Sign in with Hugging Face button to use this app for free")
    if not image_path:
        return None, "❌ Please upload an image"
    return generate_video_from_image(image=image_path, prompt=prompt, api_key=None)

def create_ui():
    css = '''
    .logo-dark{display: none}
    .dark .logo-dark{display: block !important}
    .dark .logo-light{display: none}
    #sub_title{margin-top: -20px !important}
    '''
    
    with gr.Blocks(title="Sora-2 Text-to-Video Generator", theme=gr.themes.Soft(), css=css) as demo:
        gr.HTML("""
            <div style="text-align: center; max-width: 800px; margin: 0 auto;">
                <h1 style="font-size: 2.5em; margin-bottom: 0.5em;">
                    🎬 Sora-2 Text-to-Video Generator
                </h1>
                <p style="font-size: 1.1em; color: #666; margin-bottom: 20px;">Generate stunning videos using OpenAI's Sora-2 model</p>
                <p style='color: orange;'>⚠️ You must Sign in with Hugging Face using the button to use this app.</p>
                <p style="font-size: 0.9em; color: #999; margin-top: 15px;">
                    Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #667eea;">anycoder</a>
                </p>
            </div>
        """)
        
        # Add login button - required for OAuth
        gr.LoginButton()
        
        # Text -> Video
        with gr.Row():
            with gr.Column(scale=1):
                prompt_input = gr.Textbox(
                    label="Enter your prompt",
                    placeholder="Describe the video you want to create...",
                    lines=4
                )
                generate_btn = gr.Button("πŸŽ₯ Generate Video", variant="primary", size="lg")
            with gr.Column(scale=1):
                video_output = gr.Video(label="Generated Video", height=400, interactive=False, show_download_button=True)
                status_output = gr.Textbox(label="Status", interactive=False, visible=True)
        
        generate_btn.click(
            fn=generate_with_auth,
            inputs=[prompt_input],
            outputs=[video_output, status_output],
            # Queue will be automatically enabled with OAuth
        )

        # Image -> Video UI
        gr.HTML("""
            <div style="text-align: center; margin: 40px 0 10px;">
                <h3 style="margin-bottom: 8px;">πŸ–ΌοΈ ➜ 🎬 Image β†’ Video (beta)</h3>
                <p style="color:#666; margin:0;">Turn a single image into a short video with a guiding prompt.</p>
            </div>
        """)
        with gr.Row():
            with gr.Column(scale=1):
                img_prompt_input = gr.Textbox(
                    label="Describe how the scene should evolve",
                    placeholder="e.g., The cat starts to dance and spins playfully",
                    lines=3,
                )
                image_input = gr.Image(label="Upload an image", type="filepath")
                generate_img_btn = gr.Button("πŸŽ₯ Generate from Image", variant="primary")
            with gr.Column(scale=1):
                video_output_img = gr.Video(label="Generated Video (from Image)", height=400, interactive=False, show_download_button=True)
                status_output_img = gr.Textbox(label="Status", interactive=False, visible=True)

        generate_img_btn.click(
            fn=generate_with_auth_image,
            inputs=[img_prompt_input, image_input],
            outputs=[video_output_img, status_output_img],
        )
        
        # Example usage guidance
        gr.Examples(
            examples=[
                ["A majestic golden eagle soaring through a vibrant sunset sky"],
            ],
            inputs=prompt_input,
            outputs=video_output,
            fn=generate_video,  # Examples use the original function
            cache_examples=False,
            api_name=False,
            show_api=False,
        )
    
    return demo

if __name__ == "__main__":
    try:
        cleanup_temp_files()
        if os.path.exists("gradio_cached_examples"):
            shutil.rmtree("gradio_cached_examples", ignore_errors=True)
    except Exception as e:
        print(f"Initial cleanup error: {e}")
    
    app = create_ui()
    # Configure queue with optimized settings for OAuth-enabled app
    app.queue(
        status_update_rate="auto",
        api_open=False,  # Disable public API access for security
        default_concurrency_limit=50  # Allow multiple concurrent requests
    )
    app.launch(
        show_api=False,
        enable_monitoring=False,
        quiet=True,
        max_threads=40,  # Increase thread pool for better performance
    )