Yao211 commited on
Commit
dacf963
Β·
verified Β·
1 Parent(s): 8b4d06b

Upload 9 files

Browse files
.gitattributes CHANGED
@@ -35,3 +35,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  assets/3.jpg filter=lfs diff=lfs merge=lfs -text
37
  assets/4.jpg filter=lfs diff=lfs merge=lfs -text
 
 
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  assets/3.jpg filter=lfs diff=lfs merge=lfs -text
37
  assets/4.jpg filter=lfs diff=lfs merge=lfs -text
38
+ static/assets/3.jpg filter=lfs diff=lfs merge=lfs -text
39
+ static/assets/4.jpg filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import os
4
+ import json
5
+ import time
6
+ from pathlib import Path
7
+ from tempfile import NamedTemporaryFile
8
+ from dotenv import load_dotenv
9
+ from datetime import datetime, timedelta
10
+
11
+ # Load environment variables
12
+ load_dotenv()
13
+
14
+ # Configuration
15
+ API_URL = os.getenv("MIRAGIC_API_URL")
16
+ MAX_FREE_TRIALS = 2
17
+ ATTEMPTS_FILE = "user_attempts.json"
18
+
19
+ class IPTracker:
20
+ def __init__(self):
21
+ self.attempts_file = ATTEMPTS_FILE
22
+ self.attempts = self.load_attempts()
23
+
24
+ def load_attempts(self):
25
+ try:
26
+ if Path(self.attempts_file).exists():
27
+ with open(self.attempts_file, "r") as f:
28
+ data = json.load(f)
29
+ # Clean old entries (older than 24 hours)
30
+ now = datetime.now()
31
+ cleaned_data = {}
32
+ for ip, info in data.items():
33
+ last_attempt = datetime.fromisoformat(info.get("last_attempt", "2000-01-01"))
34
+ if now - last_attempt < timedelta(hours=24):
35
+ cleaned_data[ip] = info
36
+ return cleaned_data
37
+ except:
38
+ pass
39
+ return {}
40
+
41
+ def save_attempts(self):
42
+ with open(self.attempts_file, "w") as f:
43
+ json.dump(self.attempts, f, indent=2)
44
+
45
+ def get_attempts(self, ip):
46
+ if ip not in self.attempts:
47
+ return 0
48
+
49
+ # Check if 24 hours have passed
50
+ last_attempt = datetime.fromisoformat(self.attempts[ip]["last_attempt"])
51
+ if datetime.now() - last_attempt > timedelta(hours=24):
52
+ self.attempts[ip] = {"count": 0, "last_attempt": datetime.now().isoformat()}
53
+ self.save_attempts()
54
+ return 0
55
+
56
+ return self.attempts[ip]["count"]
57
+
58
+ def increment_attempts(self, ip):
59
+ if ip not in self.attempts:
60
+ self.attempts[ip] = {"count": 0, "last_attempt": datetime.now().isoformat()}
61
+
62
+ self.attempts[ip]["count"] += 1
63
+ self.attempts[ip]["last_attempt"] = datetime.now().isoformat()
64
+ self.save_attempts()
65
+ return self.attempts[ip]["count"]
66
+
67
+ # Initialize IP tracker
68
+ ip_tracker = IPTracker()
69
+
70
+ def get_client_ip(request: gr.Request):
71
+ """Get client IP address from request"""
72
+ if request:
73
+ # Try to get real IP from headers (for proxies)
74
+ forwarded = request.headers.get("X-Forwarded-For")
75
+ if forwarded:
76
+ return forwarded.split(",")[0].strip()
77
+
78
+ real_ip = request.headers.get("X-Real-IP")
79
+ if real_ip:
80
+ return real_ip
81
+
82
+ # Fallback to client host
83
+ return request.client.host if hasattr(request, 'client') else "unknown"
84
+ return "unknown"
85
+
86
+ def call_speedpainting_api(image):
87
+ """Call the Miragic API with proper error handling"""
88
+ try:
89
+ with NamedTemporaryFile(suffix=".png") as tmp:
90
+ image.save(tmp.name)
91
+ with open(tmp.name, 'rb') as f:
92
+ files = {'file': f}
93
+ response = requests.post(API_URL, files=files, timeout=120)
94
+
95
+ if response.status_code == 200:
96
+ result = response.json()
97
+ if result.get("status") == "success":
98
+ video_url = result["link"]
99
+ if "dl=0" in video_url:
100
+ return video_url.replace("dl=0", "raw=1")
101
+ elif "?dl=" in video_url:
102
+ return video_url.split("?dl=")[0] + "?raw=1"
103
+ return video_url
104
+ raise gr.Error(f"API error: {result.get('message', 'Unknown error')}")
105
+ raise gr.Error(f"API request failed with status {response.status_code}")
106
+ except requests.exceptions.RequestException as e:
107
+ raise gr.Error(f"Network error: {str(e)}")
108
+ except Exception as e:
109
+ raise gr.Error(f"Processing error: {str(e)}")
110
+
111
+ def generate_speedpainting(image, request: gr.Request):
112
+ """Generate speedpainting with IP-based limiting"""
113
+ if not image:
114
+ raise gr.Error("Please upload an image first!")
115
+
116
+ client_ip = get_client_ip(request)
117
+ current_attempts = ip_tracker.get_attempts(client_ip)
118
+
119
+ if current_attempts >= MAX_FREE_TRIALS:
120
+ raise gr.Error(
121
+ f"You've used {MAX_FREE_TRIALS} free generations today. "
122
+ f"Please visit https://miragic.ai/ to sign up for unlimited access!"
123
+ )
124
+
125
+ # Process the image
126
+ video_url = call_speedpainting_api(image)
127
+
128
+ # Increment attempts after successful generation
129
+ new_count = ip_tracker.increment_attempts(client_ip)
130
+
131
+ return video_url
132
+
133
+ def get_remaining_attempts(request: gr.Request):
134
+ """Get remaining attempts for current IP"""
135
+ client_ip = get_client_ip(request)
136
+ current_attempts = ip_tracker.get_attempts(client_ip)
137
+ remaining = MAX_FREE_TRIALS - current_attempts
138
+
139
+ if remaining <= 0:
140
+ return f"Daily limit reached ({MAX_FREE_TRIALS}/{MAX_FREE_TRIALS}). Sign up for unlimited access!"
141
+ else:
142
+ return f"Remaining free generations: {remaining}/{MAX_FREE_TRIALS}"
143
+
144
+ # Example images
145
+ EXAMPLE_IMAGES = [
146
+ ["static/assets/1.jpg"],
147
+ ["static/assets/2.jpg"],
148
+ ["static/assets/3.jpg"],
149
+ ["static/assets/4.jpg"],
150
+ ]
151
+
152
+ # Company information
153
+ COMPANY_INFO = """
154
+ ## About US
155
+ ### Miragic is a cutting-edge platform to serve AI-powered tools like Virtual Try-on, Speed Painting, Background Remover and Sales Pilot.
156
+ ### [Visit our website](https://miragic.ai) to explore more innovative generative AI tools!
157
+ """
158
+
159
+ # Custom CSS
160
+ css = """
161
+ footer {visibility: hidden}
162
+ .banner {
163
+ background-color: #f8f9fa;
164
+ padding: 10px;
165
+ border-radius: 5px;
166
+ margin-bottom: 20px;
167
+ text-align: center;
168
+ }
169
+ .input-row {
170
+ display: flex;
171
+ flex-direction: row;
172
+ gap: 20px;
173
+ }
174
+ .input-column {
175
+ flex: 1;
176
+ }
177
+ .button-gradient, .signup-button {
178
+ background: linear-gradient(45deg, #ff416c, #ff4b2b, #ff9b00, #ff416c);
179
+ background-size: 400% 400%;
180
+ border: none;
181
+ padding: 14px 28px;
182
+ font-size: 16px;
183
+ font-weight: bold;
184
+ color: white;
185
+ border-radius: 10px;
186
+ cursor: pointer;
187
+ transition: 0.3s ease-in-out;
188
+ animation: gradientAnimation 2s infinite linear;
189
+ box-shadow: 0 4px 10px rgba(255, 65, 108, 0.6);
190
+ text-decoration: none;
191
+ display: inline-block;
192
+ margin-top: 10px;
193
+ }
194
+ .signup-button {
195
+ margin: 0 auto;
196
+ }
197
+ @keyframes gradientAnimation {
198
+ 0% { background-position: 0% 50%; }
199
+ 100% { background-position: 100% 50%; }
200
+ }
201
+ .button-gradient:hover, .signup-button:hover {
202
+ transform: scale(1.05);
203
+ box-shadow: 0 6px 15px rgba(255, 75, 43, 0.8);
204
+ }
205
+ .signup-container {
206
+ text-align: center;
207
+ padding: 20px;
208
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
209
+ border-radius: 8px;
210
+ margin-top: 20px;
211
+ color: white;
212
+ box-shadow: 0 4px 15px rgba(0,0,0,0.2);
213
+ }
214
+ .signup-container h3 {
215
+ margin-bottom: 10px;
216
+ color: white;
217
+ }
218
+ .signup-container p {
219
+ margin-bottom: 15px;
220
+ color: #f0f0f0;
221
+ }
222
+ .attempts-counter {
223
+ background: #e3f2fd;
224
+ padding: 10px;
225
+ border-radius: 5px;
226
+ margin: 10px 0;
227
+ text-align: center;
228
+ font-weight: bold;
229
+ color: #1976d2;
230
+ }
231
+ """
232
+
233
+ # Create Gradio interface
234
+ with gr.Blocks(title="Miragic Speed-Painting", theme=gr.themes.Ocean(), css=css) as demo:
235
+ gr.Markdown("""
236
+ <div style="display: flex; align-items: center;">
237
+ <img src="https://avatars.githubusercontent.com/u/211682198?s=200&v=4" style="width: 80px; margin-right: 20px;"/>
238
+ <div>
239
+ <h1 style="margin-bottom: 0;">Miragic Speed-Painting 🎨</h1>
240
+ <p>Upload an image to see AI create speedpainting animations!</p>
241
+ </div>
242
+ </div>
243
+ """)
244
+
245
+ gr.Markdown(COMPANY_INFO)
246
+
247
+ # Usage counter
248
+ usage_display = gr.HTML(elem_classes="attempts-counter")
249
+
250
+ with gr.Row():
251
+ with gr.Column():
252
+ image_input = gr.Image(
253
+ label="Upload Image",
254
+ type="pil",
255
+ sources=["upload", "clipboard"],
256
+ height=300
257
+ )
258
+
259
+ gr.Examples(
260
+ examples=EXAMPLE_IMAGES,
261
+ inputs=image_input,
262
+ label="Try these examples!",
263
+ examples_per_page=4
264
+ )
265
+
266
+ submit_btn = gr.Button("Generate Speedpainting πŸš€", elem_classes="button-gradient")
267
+
268
+ with gr.Column():
269
+ video_output = gr.Video(
270
+ label="Speedpainting Result",
271
+ autoplay=True,
272
+ format="mp4",
273
+ height=300
274
+ )
275
+
276
+ # status_output = gr.Textbox(
277
+ # label="Status",
278
+ # interactive=False,
279
+ # visible=False
280
+ # )
281
+
282
+ signup_prompt = gr.HTML(
283
+ visible=True,
284
+ value="""<div class="signup-container">
285
+ <h3>πŸš€ Want unlimited generations?</h3>
286
+ <p>Sign up at Miragic.ai for unlimited access to all our AI tools!</p>
287
+ <a href='https://miragic.ai/' target='_blank' class="signup-button">
288
+ SignUp for Free πŸš€
289
+ </a>
290
+ </div>"""
291
+ )
292
+
293
+ # Update usage display on page load
294
+ demo.load(
295
+ fn=get_remaining_attempts,
296
+ outputs=usage_display
297
+ )
298
+
299
+ # Handle generation
300
+ submit_btn.click(
301
+ fn=generate_speedpainting,
302
+ inputs=[image_input],
303
+ outputs=video_output
304
+ ).then(
305
+ fn=get_remaining_attempts,
306
+ outputs=usage_display
307
+ )
308
+
309
+ if __name__ == "__main__":
310
+ demo.launch(server_name="0.0.0.0", server_port=7860)
static/assets/1.jpg ADDED
static/assets/2.jpg ADDED
static/assets/3.jpg ADDED

Git LFS Details

  • SHA256: bbb1b84feebd3f67721a97ef30e80a42806965b1f9dcb5fae201dd8418e79374
  • Pointer size: 131 Bytes
  • Size of remote file: 119 kB
static/assets/4.jpg ADDED

Git LFS Details

  • SHA256: b69e73807d5549f03bc3d916b80574ecd829a21d4904df7d25eaa3304bd33a3e
  • Pointer size: 131 Bytes
  • Size of remote file: 106 kB
static/css/style.css ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
10
+ min-height: 100vh;
11
+ color: #333;
12
+ }
13
+
14
+ .container {
15
+ max-width: 1200px;
16
+ margin: 0 auto;
17
+ padding: 20px;
18
+ }
19
+
20
+ .header {
21
+ background: rgba(255, 255, 255, 0.95);
22
+ backdrop-filter: blur(10px);
23
+ border-radius: 15px;
24
+ padding: 20px;
25
+ margin-bottom: 20px;
26
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
27
+ }
28
+
29
+ .logo-section {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 20px;
33
+ }
34
+
35
+ .logo {
36
+ width: 80px;
37
+ height: 80px;
38
+ border-radius: 50%;
39
+ object-fit: cover;
40
+ }
41
+
42
+ .title-section h1 {
43
+ font-size: 2.5rem;
44
+ margin-bottom: 10px;
45
+ background: linear-gradient(45deg, #ff416c, #ff4b2b);
46
+ -webkit-background-clip: text;
47
+ -webkit-text-fill-color: transparent;
48
+ background-clip: text;
49
+ }
50
+
51
+ .title-section p {
52
+ font-size: 1.1rem;
53
+ color: #666;
54
+ }
55
+
56
+ .company-info {
57
+ background: rgba(255, 255, 255, 0.9);
58
+ border-radius: 12px;
59
+ padding: 20px;
60
+ margin-bottom: 20px;
61
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
62
+ }
63
+
64
+ .company-info h2 {
65
+ color: #333;
66
+ margin-bottom: 10px;
67
+ }
68
+
69
+ .company-info p {
70
+ line-height: 1.6;
71
+ margin-bottom: 10px;
72
+ }
73
+
74
+ .company-info a {
75
+ color: #ff416c;
76
+ text-decoration: none;
77
+ font-weight: bold;
78
+ }
79
+
80
+ .company-info a:hover {
81
+ text-decoration: underline;
82
+ }
83
+
84
+ .usage-counter {
85
+ background: rgba(255, 255, 255, 0.9);
86
+ padding: 15px;
87
+ border-radius: 10px;
88
+ margin-bottom: 20px;
89
+ text-align: center;
90
+ font-weight: bold;
91
+ color: #1976d2;
92
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
93
+ }
94
+
95
+ .main-content {
96
+ display: grid;
97
+ grid-template-columns: 1fr 1fr;
98
+ gap: 30px;
99
+ margin-bottom: 20px;
100
+ }
101
+
102
+ .input-section, .output-section {
103
+ background: rgba(255, 255, 255, 0.95);
104
+ border-radius: 15px;
105
+ padding: 25px;
106
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
107
+ backdrop-filter: blur(10px);
108
+ }
109
+
110
+ .upload-area {
111
+ border: 3px dashed #ddd;
112
+ border-radius: 12px;
113
+ padding: 40px;
114
+ text-align: center;
115
+ cursor: pointer;
116
+ transition: all 0.3s ease;
117
+ margin-bottom: 20px;
118
+ position: relative;
119
+ overflow: hidden;
120
+ }
121
+
122
+ .upload-area:hover {
123
+ border-color: #ff416c;
124
+ background: rgba(255, 65, 108, 0.05);
125
+ }
126
+
127
+ .upload-area.dragover {
128
+ border-color: #ff416c;
129
+ background: rgba(255, 65, 108, 0.1);
130
+ }
131
+
132
+ .upload-placeholder {
133
+ pointer-events: none;
134
+ }
135
+
136
+ .upload-icon {
137
+ font-size: 3rem;
138
+ margin-bottom: 15px;
139
+ }
140
+
141
+ .upload-hint {
142
+ color: #888;
143
+ font-size: 0.9rem;
144
+ margin-top: 10px;
145
+ }
146
+
147
+ .preview-image {
148
+ max-width: 100%;
149
+ max-height: 300px;
150
+ border-radius: 8px;
151
+ object-fit: contain;
152
+ }
153
+
154
+ .examples-section {
155
+ margin-bottom: 20px;
156
+ }
157
+
158
+ .examples-section h3 {
159
+ margin-bottom: 15px;
160
+ color: #333;
161
+ }
162
+
163
+ .examples-grid {
164
+ display: grid;
165
+ grid-template-columns: repeat(4, 1fr);
166
+ gap: 10px;
167
+ }
168
+
169
+ .example-image {
170
+ width: 100%;
171
+ height: 80px;
172
+ object-fit: cover;
173
+ border-radius: 8px;
174
+ cursor: pointer;
175
+ transition: transform 0.2s ease;
176
+ }
177
+
178
+ .example-image:hover {
179
+ transform: scale(1.05);
180
+ }
181
+
182
+ .button-gradient {
183
+ background: linear-gradient(45deg, #ff416c, #ff4b2b, #ff9b00, #ff416c);
184
+ background-size: 400% 400%;
185
+ border: none;
186
+ padding: 15px 30px;
187
+ font-size: 16px;
188
+ font-weight: bold;
189
+ color: white;
190
+ border-radius: 12px;
191
+ cursor: pointer;
192
+ transition: all 0.3s ease;
193
+ animation: gradientAnimation 3s ease infinite;
194
+ box-shadow: 0 6px 20px rgba(255, 65, 108, 0.4);
195
+ width: 100%;
196
+ }
197
+
198
+ .button-gradient:hover {
199
+ transform: translateY(-2px);
200
+ box-shadow: 0 8px 25px rgba(255, 65, 108, 0.6);
201
+ }
202
+
203
+ .button-gradient:disabled {
204
+ opacity: 0.6;
205
+ cursor: not-allowed;
206
+ transform: none;
207
+ }
208
+
209
+ @keyframes gradientAnimation {
210
+ 0% { background-position: 0% 50%; }
211
+ 50% { background-position: 100% 50%; }
212
+ 100% { background-position: 0% 50%; }
213
+ }
214
+
215
+ .video-container {
216
+ position: relative;
217
+ min-height: 300px;
218
+ border-radius: 12px;
219
+ overflow: hidden;
220
+ background: #f8f9fa;
221
+ }
222
+
223
+ .result-video {
224
+ width: 100%;
225
+ height: 300px;
226
+ border-radius: 12px;
227
+ object-fit: contain;
228
+ background: black;
229
+ }
230
+
231
+ .video-placeholder {
232
+ display: flex;
233
+ flex-direction: column;
234
+ align-items: center;
235
+ justify-content: center;
236
+ height: 300px;
237
+ color: #888;
238
+ }
239
+
240
+ .placeholder-icon {
241
+ font-size: 3rem;
242
+ margin-bottom: 15px;
243
+ }
244
+
245
+ .loading-spinner {
246
+ display: flex;
247
+ flex-direction: column;
248
+ align-items: center;
249
+ justify-content: center;
250
+ height: 300px;
251
+ color: #666;
252
+ }
253
+
254
+ .spinner {
255
+ width: 50px;
256
+ height: 50px;
257
+ border: 4px solid #f3f3f3;
258
+ border-top: 4px solid #ff416c;
259
+ border-radius: 50%;
260
+ animation: spin 1s linear infinite;
261
+ margin-bottom: 20px;
262
+ }
263
+
264
+ @keyframes spin {
265
+ 0% { transform: rotate(0deg); }
266
+ 100% { transform: rotate(360deg); }
267
+ }
268
+
269
+ .signup-container {
270
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
271
+ border-radius: 12px;
272
+ padding: 25px;
273
+ margin-top: 20px;
274
+ color: white;
275
+ text-align: center;
276
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
277
+ }
278
+
279
+ .signup-container h3 {
280
+ margin-bottom: 15px;
281
+ font-size: 1.5rem;
282
+ }
283
+
284
+ .signup-container p {
285
+ margin-bottom: 20px;
286
+ opacity: 0.9;
287
+ }
288
+
289
+ .signup-button {
290
+ background: linear-gradient(45deg, #ff416c, #ff4b2b);
291
+ border: none;
292
+ padding: 12px 25px;
293
+ font-size: 16px;
294
+ font-weight: bold;
295
+ color: white;
296
+ border-radius: 8px;
297
+ text-decoration: none;
298
+ display: inline-block;
299
+ transition: all 0.3s ease;
300
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
301
+ }
302
+
303
+ .signup-button:hover {
304
+ transform: translateY(-2px);
305
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
306
+ }
307
+
308
+ .error-message {
309
+ background: #ff4444;
310
+ color: white;
311
+ padding: 15px;
312
+ border-radius: 8px;
313
+ margin-top: 20px;
314
+ text-align: center;
315
+ font-weight: bold;
316
+ }
317
+
318
+ @media (max-width: 768px) {
319
+ .main-content {
320
+ grid-template-columns: 1fr;
321
+ }
322
+
323
+ .logo-section {
324
+ flex-direction: column;
325
+ text-align: center;
326
+ }
327
+
328
+ .title-section h1 {
329
+ font-size: 2rem;
330
+ }
331
+
332
+ .examples-grid {
333
+ grid-template-columns: repeat(2, 1fr);
334
+ }
335
+
336
+ .container {
337
+ padding: 10px;
338
+ }
339
+ }
static/js/main.js ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class SpeedPaintingApp {
2
+ constructor() {
3
+ this.initializeElements();
4
+ this.setupEventListeners();
5
+ this.checkUsage();
6
+ }
7
+
8
+ initializeElements() {
9
+ this.uploadArea = document.getElementById('uploadArea');
10
+ this.imageInput = document.getElementById('imageInput');
11
+ this.previewImage = document.getElementById('previewImage');
12
+ this.generateBtn = document.getElementById('generateBtn');
13
+ this.resultVideo = document.getElementById('resultVideo');
14
+ this.loadingSpinner = document.getElementById('loadingSpinner');
15
+ this.videoPlaceholder = document.getElementById('videoPlaceholder');
16
+ this.signupPrompt = document.getElementById('signupPrompt');
17
+ this.errorMessage = document.getElementById('errorMessage');
18
+ this.usageCounter = document.getElementById('usageCounter');
19
+ this.usageText = document.getElementById('usageText');
20
+ this.exampleImages = document.querySelectorAll('.example-image');
21
+
22
+ this.selectedFile = null;
23
+ this.maxAttempts = 2;
24
+ }
25
+
26
+ setupEventListeners() {
27
+ // Upload area click
28
+ this.uploadArea.addEventListener('click', () => {
29
+ this.imageInput.click();
30
+ });
31
+
32
+ // File input change
33
+ this.imageInput.addEventListener('change', (e) => {
34
+ this.handleFileSelect(e.target.files[0]);
35
+ });
36
+
37
+ // Drag and drop
38
+ this.uploadArea.addEventListener('dragover', (e) => {
39
+ e.preventDefault();
40
+ this.uploadArea.classList.add('dragover');
41
+ });
42
+
43
+ this.uploadArea.addEventListener('dragleave', () => {
44
+ this.uploadArea.classList.remove('dragover');
45
+ });
46
+
47
+ this.uploadArea.addEventListener('drop', (e) => {
48
+ e.preventDefault();
49
+ this.uploadArea.classList.remove('dragover');
50
+ this.handleFileSelect(e.dataTransfer.files[0]);
51
+ });
52
+
53
+ // Generate button
54
+ this.generateBtn.addEventListener('click', () => {
55
+ this.generateSpeedpainting();
56
+ });
57
+
58
+ // Example images
59
+ this.exampleImages.forEach(img => {
60
+ img.addEventListener('click', () => {
61
+ this.loadExampleImage(img.src);
62
+ });
63
+ });
64
+ }
65
+
66
+ handleFileSelect(file) {
67
+ if (!file) return;
68
+
69
+ if (!file.type.startsWith('image/')) {
70
+ this.showError('Please select a valid image file (JPG, PNG, WebP)');
71
+ return;
72
+ }
73
+
74
+ this.selectedFile = file;
75
+
76
+ // Show preview
77
+ const reader = new FileReader();
78
+ reader.onload = (e) => {
79
+ this.previewImage.src = e.target.result;
80
+ this.previewImage.style.display = 'block';
81
+ this.uploadArea.querySelector('.upload-placeholder').style.display = 'none';
82
+ this.generateBtn.disabled = false;
83
+ };
84
+ reader.readAsDataURL(file);
85
+ }
86
+
87
+ async loadExampleImage(src) {
88
+ try {
89
+ const response = await fetch(src);
90
+ const blob = await response.blob();
91
+ const file = new File([blob], 'example.jpg', { type: blob.type });
92
+ this.handleFileSelect(file);
93
+ } catch (error) {
94
+ this.showError('Failed to load example image');
95
+ }
96
+ }
97
+
98
+ async checkUsage() {
99
+ try {
100
+ const response = await fetch('/api/usage');
101
+ const data = await response.json();
102
+
103
+ if (data.remaining <= 0) {
104
+ this.usageText.textContent = `Daily limit reached (${this.maxAttempts}/${this.maxAttempts}). Sign up for unlimited access!`;
105
+ this.usageCounter.style.background = '#ffebee';
106
+ this.usageCounter.style.color = '#c62828';
107
+ this.generateBtn.disabled = true;
108
+ this.showSignupPrompt();
109
+ } else {
110
+ this.usageText.textContent = `Remaining free generations: ${data.remaining}/${this.maxAttempts}`;
111
+ this.usageCounter.style.background = '#e3f2fd';
112
+ this.usageCounter.style.color = '#1976d2';
113
+ }
114
+ } catch (error) {
115
+ this.usageText.textContent = 'Unable to check usage';
116
+ }
117
+ }
118
+
119
+ async generateSpeedpainting() {
120
+ if (!this.selectedFile) {
121
+ this.showError('Please select an image first');
122
+ return;
123
+ }
124
+
125
+ this.showLoading();
126
+ this.hideError();
127
+
128
+ try {
129
+ const formData = new FormData();
130
+ formData.append('image', this.selectedFile);
131
+
132
+ const response = await fetch('/api/generate', {
133
+ method: 'POST',
134
+ body: formData
135
+ });
136
+
137
+ const data = await response.json();
138
+
139
+ if (!response.ok) {
140
+ throw new Error(data.error || 'Generation failed');
141
+ }
142
+
143
+ if (data.success) {
144
+ this.showVideo(data.video_url);
145
+ this.checkUsage(); // Update usage counter
146
+
147
+ if (data.remaining <= 0) {
148
+ this.showSignupPrompt();
149
+ }
150
+ } else {
151
+ throw new Error(data.error || 'Generation failed');
152
+ }
153
+
154
+ } catch (error) {
155
+ this.showError(error.message);
156
+ } finally {
157
+ this.hideLoading();
158
+ }
159
+ }
160
+
161
+ showLoading() {
162
+ this.loadingSpinner.style.display = 'flex';
163
+ this.videoPlaceholder.style.display = 'none';
164
+ this.resultVideo.style.display = 'none';
165
+ this.generateBtn.disabled = true;
166
+ this.generateBtn.textContent = 'Generating...';
167
+ }
168
+
169
+ hideLoading() {
170
+ this.loadingSpinner.style.display = 'none';
171
+ this.generateBtn.disabled = false;
172
+ this.generateBtn.textContent = 'Generate Speedpainting πŸš€';
173
+ }
174
+
175
+ showVideo(videoUrl) {
176
+ this.resultVideo.src = videoUrl;
177
+ this.resultVideo.style.display = 'block';
178
+ this.videoPlaceholder.style.display = 'none';
179
+ this.resultVideo.load();
180
+ }
181
+
182
+ showSignupPrompt() {
183
+ this.signupPrompt.style.display = 'block';
184
+ }
185
+
186
+ showError(message) {
187
+ this.errorMessage.textContent = message;
188
+ this.errorMessage.style.display = 'block';
189
+
190
+ // Auto hide error after 5 seconds
191
+ setTimeout(() => {
192
+ this.hideError();
193
+ }, 5000);
194
+ }
195
+
196
+ hideError() {
197
+ this.errorMessage.style.display = 'none';
198
+ }
199
+ }
200
+
201
+ // Initialize the app when DOM is loaded
202
+ document.addEventListener('DOMContentLoaded', () => {
203
+ new SpeedPaintingApp();
204
+ });
templates/index.html ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Miragic Speed-Painting</title>
7
+ <link rel="stylesheet" href="/static/css/style.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <header class="header">
12
+ <div class="logo-section">
13
+ <img src="https://avatars.githubusercontent.com/u/211682198?s=200&v=4" alt="Miragic Logo" class="logo">
14
+ <div class="title-section">
15
+ <h1>Miragic Speed-Painting 🎨</h1>
16
+ <p>Upload an image to see AI create speedpainting animations!</p>
17
+ </div>
18
+ </div>
19
+ </header>
20
+
21
+ <section class="company-info">
22
+ <h2>About US</h2>
23
+ <p>Miragic is a cutting-edge platform to serve AI-powered tools like Virtual Try-on, Speed Painting, Background Remover and Sales Pilot.</p>
24
+ <p><a href="https://miragic.ai" target="_blank">Visit our website</a> to explore more innovative generative AI tools!</p>
25
+ </section>
26
+
27
+ <div class="usage-counter" id="usageCounter">
28
+ <span id="usageText">Checking usage...</span>
29
+ </div>
30
+
31
+ <div class="main-content">
32
+ <div class="input-section">
33
+ <div class="upload-area" id="uploadArea">
34
+ <input type="file" id="imageInput" accept="image/*" hidden>
35
+ <div class="upload-placeholder">
36
+ <div class="upload-icon">πŸ“·</div>
37
+ <p>Click to upload or drag and drop an image</p>
38
+ <p class="upload-hint">Supports JPG, PNG, WebP</p>
39
+ </div>
40
+ <img id="previewImage" class="preview-image" style="display: none;">
41
+ </div>
42
+
43
+ <div class="examples-section">
44
+ <h3>Try these examples!</h3>
45
+ <div class="examples-grid">
46
+ <img src="/static/assets/1.jpg" alt="Example 1" class="example-image">
47
+ <img src="/static/assets/2.jpg" alt="Example 2" class="example-image">
48
+ <img src="/static/assets/3.jpg" alt="Example 3" class="example-image">
49
+ <img src="/static/assets/4.jpg" alt="Example 4" class="example-image">
50
+ </div>
51
+ </div>
52
+
53
+ <button id="generateBtn" class="button-gradient" disabled>
54
+ Generate Speedpainting πŸš€
55
+ </button>
56
+ </div>
57
+
58
+ <div class="output-section">
59
+ <div class="video-container">
60
+ <video id="resultVideo" class="result-video" controls style="display: none;">
61
+ Your browser does not support the video tag.
62
+ </video>
63
+ <div id="loadingSpinner" class="loading-spinner" style="display: none;">
64
+ <div class="spinner"></div>
65
+ <p>Generating your speedpainting...</p>
66
+ </div>
67
+ <div id="videoPlaceholder" class="video-placeholder">
68
+ <div class="placeholder-icon">🎬</div>
69
+ <p>Your speedpainting video will appear here</p>
70
+ </div>
71
+ </div>
72
+
73
+ <div id="signupPrompt" class="signup-container" style="display: none;">
74
+ <h3>πŸš€ Want unlimited generations?</h3>
75
+ <p>You've reached your daily limit. Sign up at Miragic.ai for unlimited access to all our AI tools!</p>
76
+ <a href="https://miragic.ai/" target="_blank" class="signup-button">
77
+ SignUp for Free πŸš€
78
+ </a>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <div id="errorMessage" class="error-message" style="display: none;"></div>
84
+ </div>
85
+
86
+ <script src="/static/js/main.js"></script>
87
+ </body>
88
+ </html>
user_attempts.json ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ {
2
+ }