haraberget commited on
Commit
7dc8d5d
·
verified ·
1 Parent(s): 3bf99af

Upload 11 files

Browse files
Files changed (11) hide show
  1. .gitattributes +35 -35
  2. README.md +33 -12
  3. a.py +618 -0
  4. a1.py +539 -0
  5. aaa.py +693 -0
  6. add.py +163 -0
  7. app.py +379 -0
  8. full.py +843 -0
  9. mess.py +488 -0
  10. p.py +14 -0
  11. requirements.txt +2 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz 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
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz 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
README.md CHANGED
@@ -1,12 +1,33 @@
1
- ---
2
- title: Upload Cover
3
- emoji: 🌖
4
- colorFrom: green
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 6.10.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: SUNO Upload and Cover
3
+ emoji: 📁
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 6.1.0
8
+ app_file: aaa.py
9
+ pinned: true
10
+ ---
11
+
12
+ # File Upload & Download Manager
13
+
14
+ Upload files and download them as original or zipped archives.
15
+
16
+ ## Features:
17
+ - Single file upload and download
18
+ - Multiple file batch processing
19
+ - ZIP file creation with compression
20
+ - Password-protected ZIPs (optional)
21
+ - File information display
22
+ - Custom filename support
23
+
24
+ ## Usage:
25
+ 1. Upload files using the interface
26
+ 2. Choose processing options
27
+ 3. Download the processed files
28
+
29
+ ## Requirements:
30
+ - gradio>=6.1.0
31
+ - pyminizip>=0.2.6 (optional, for password protection)
32
+
33
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
a.py ADDED
@@ -0,0 +1,618 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ import requests
6
+ import json
7
+ import uuid
8
+ import time
9
+ from datetime import datetime
10
+ from dotenv import load_dotenv
11
+ from urllib.parse import urlparse, parse_qs
12
+
13
+ # Load environment variables
14
+ load_dotenv()
15
+
16
+ # Configuration
17
+ SUNO_API_KEY = os.environ.get("SunoKey", "")
18
+ FIXED_CALLBACK_URL = "https://1hit.no/cover/cb.php"
19
+ SUNO_API_URL = "https://api.sunoapi.org/api/v1/generate/upload-cover"
20
+
21
+ # Create uploads directory
22
+ UPLOADS_FOLDER = "uploads"
23
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
24
+
25
+ # ============================================
26
+ # PAGE 1: FILE UPLOAD
27
+ # ============================================
28
+
29
+ def upload_file(file_obj, custom_name):
30
+ """Simple file upload - returns file for download"""
31
+ if not file_obj:
32
+ return None, "❌ Please upload a file first"
33
+
34
+ try:
35
+ # Handle Gradio 6+ file object
36
+ if isinstance(file_obj, dict):
37
+ file_path = file_obj["path"]
38
+ original_name = file_obj["name"]
39
+ else:
40
+ file_path = file_obj.name
41
+ original_name = os.path.basename(file_path)
42
+
43
+ # Create temp directory
44
+ temp_dir = tempfile.mkdtemp()
45
+
46
+ # Determine filename
47
+ if custom_name and custom_name.strip():
48
+ ext = os.path.splitext(original_name)[1]
49
+ base_name = custom_name.strip()
50
+ if not base_name.endswith(ext):
51
+ final_name = base_name + ext
52
+ else:
53
+ final_name = base_name
54
+ else:
55
+ final_name = original_name
56
+
57
+ # Copy file
58
+ final_path = os.path.join(temp_dir, final_name)
59
+ shutil.copy2(file_path, final_path)
60
+
61
+ return final_path, f"✅ Ready: {final_name}"
62
+
63
+ except Exception as e:
64
+ return None, f"❌ Error: {str(e)}"
65
+
66
+ def get_file_info(file_obj):
67
+ """Display file information"""
68
+ if not file_obj:
69
+ return "📁 No file selected"
70
+
71
+ try:
72
+ if isinstance(file_obj, dict):
73
+ file_path = file_obj["path"]
74
+ file_name = file_obj["name"]
75
+ else:
76
+ file_path = file_obj.name
77
+ file_name = os.path.basename(file_path)
78
+
79
+ size = os.path.getsize(file_path)
80
+ return f"📄 **{file_name}**\n• Size: {size/1024:.1f} KB\n• Type: {os.path.splitext(file_name)[1]}"
81
+ except:
82
+ return "📁 File selected"
83
+
84
+ # ============================================
85
+ # PAGE 2: COVER CREATION
86
+ # ============================================
87
+
88
+ def generate_cover(
89
+ prompt,
90
+ title,
91
+ style,
92
+ upload_url_type,
93
+ custom_upload_url,
94
+ uploaded_file,
95
+ instrumental=True,
96
+ model="V4_5ALL",
97
+ persona_id="",
98
+ negative_tags="",
99
+ vocal_gender="m",
100
+ style_weight=0.65,
101
+ weirdness_constraint=0.65,
102
+ audio_weight=0.65,
103
+ custom_mode=True
104
+ ):
105
+ """Generate cover using Suno API"""
106
+
107
+ # Check API key
108
+ if not SUNO_API_KEY:
109
+ return "❌ Suno API key not found. Please set 'SunoKey' environment variable."
110
+
111
+ # Determine upload URL
112
+ if upload_url_type == "uploaded":
113
+ # Use uploaded file - in real implementation, you'd upload to storage and get URL
114
+ if not uploaded_file:
115
+ return "❌ Please upload a file first or select another URL type"
116
+
117
+ # For demo - simulate URL from uploaded file
118
+ if isinstance(uploaded_file, dict):
119
+ file_name = uploaded_file.get("name", "uploaded_file.mp3")
120
+ else:
121
+ file_name = getattr(uploaded_file, "name", "uploaded_file.mp3")
122
+
123
+ upload_url = f"https://storage.temp.example.com/uploads/{int(time.time())}_{file_name}"
124
+
125
+ elif upload_url_type == "custom":
126
+ upload_url = custom_upload_url.strip()
127
+ if not upload_url:
128
+ return "❌ Please provide a custom upload URL"
129
+ else: # auto
130
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
131
+ unique_id = str(uuid.uuid4())[:8]
132
+ upload_url = f"https://storage.temp.example.com/uploads/{timestamp}_{unique_id}.mp3"
133
+
134
+ # Prepare payload
135
+ payload = {
136
+ "uploadUrl": upload_url,
137
+ "customMode": custom_mode,
138
+ "instrumental": instrumental,
139
+ "model": model,
140
+ "callBackUrl": FIXED_CALLBACK_URL,
141
+ "prompt": prompt,
142
+ "style": style,
143
+ "title": title,
144
+ "personaId": persona_id,
145
+ "negativeTags": negative_tags,
146
+ "vocalGender": vocal_gender,
147
+ "styleWeight": style_weight,
148
+ "weirdnessConstraint": weirdness_constraint,
149
+ "audioWeight": audio_weight
150
+ }
151
+
152
+ # Remove empty fields
153
+ payload = {k: v for k, v in payload.items() if v not in ["", None]}
154
+
155
+ # Headers
156
+ headers = {
157
+ "Authorization": f"Bearer {SUNO_API_KEY}",
158
+ "Content-Type": "application/json"
159
+ }
160
+
161
+ try:
162
+ # Make API request
163
+ response = requests.post(SUNO_API_URL, json=payload, headers=headers)
164
+
165
+ if response.status_code == 200:
166
+ result = response.json()
167
+ if result.get("code") == 200:
168
+ task_id = result.get("data", {}).get("taskId", "Unknown")
169
+ return f"""✅ **Generation Started!**
170
+
171
+ **Task ID:** `{task_id}`
172
+
173
+ **Upload URL:** {upload_url}
174
+ **Callback URL:** {FIXED_CALLBACK_URL}
175
+
176
+ 📋 **Full Response:**
177
+ ```json
178
+ {json.dumps(result, indent=2)}
179
+ ```"""
180
+ else:
181
+ return f"""❌ **API Error:** {result.get('msg', 'Unknown error')}
182
+
183
+ 📋 **Response:**
184
+ ```json
185
+ {json.dumps(result, indent=2)}
186
+ ```"""
187
+ else:
188
+ return f"""❌ **HTTP Error {response.status_code}**
189
+
190
+ {response.text}"""
191
+
192
+ except Exception as e:
193
+ return f"❌ **Error:** {str(e)}"
194
+
195
+ # ============================================
196
+ # PAGE 3: CHECK TASK - ORIGINAL CODE
197
+ # ============================================
198
+
199
+ def get_task_info(task_id):
200
+ """Manually check any Suno task status - ORIGINAL CODE"""
201
+ if not task_id:
202
+ return "❌ Please enter a Task ID ❌"
203
+
204
+ try:
205
+ resp = requests.get(
206
+ "https://api.sunoapi.org/api/v1/generate/record-info",
207
+ headers={"Authorization": f"Bearer {SUNO_API_KEY}"},
208
+ params={"taskId": task_id},
209
+ timeout=30
210
+ )
211
+
212
+ if resp.status_code != 200:
213
+ return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}"
214
+
215
+ data = resp.json()
216
+
217
+ # Format the response for display
218
+ output = f"## 🔍 Task Status: `{task_id}`\n\n"
219
+
220
+ if data.get("code") == 200:
221
+ task_data = data.get("data", {})
222
+ status = task_data.get("status", "UNKNOWN")
223
+
224
+ output += f"**Status:** {status}\n"
225
+ output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
226
+ output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
227
+ output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
228
+
229
+ if status == "SUCCESS":
230
+ response_data = task_data.get("response", {})
231
+
232
+ # Try to parse response (could be string or dict)
233
+ if isinstance(response_data, str):
234
+ try:
235
+ response_data = json.loads(response_data)
236
+ except:
237
+ output += f"\n**Raw Response:**\n```\n{response_data}\n```\n"
238
+ response_data = {}
239
+
240
+ # Check for song data
241
+ songs = []
242
+ if isinstance(response_data, dict):
243
+ songs = response_data.get("sunoData", [])
244
+ if not songs:
245
+ songs = response_data.get("data", [])
246
+ elif isinstance(response_data, list):
247
+ songs = response_data
248
+
249
+ if songs:
250
+ output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
251
+
252
+ for i, song in enumerate(songs, 1):
253
+ if isinstance(song, dict):
254
+ output += f"### Song {i}\n"
255
+ output += f"**Title:** {song.get('title', 'Untitled')}\n"
256
+ output += f"**ID:** `{song.get('id', 'N/A')}`\n"
257
+
258
+ # Audio URLs
259
+ audio_url = song.get('audioUrl') or song.get('audio_url')
260
+ stream_url = song.get('streamUrl') or song.get('stream_url')
261
+ download_url = song.get('downloadUrl') or song.get('download_url')
262
+
263
+ if audio_url:
264
+ output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
265
+ elif stream_url:
266
+ output += f"**Stream:** [Play]({stream_url})\n"
267
+
268
+ if download_url:
269
+ output += f"**Download:** [MP3]({download_url})\n"
270
+
271
+ # Audio player
272
+ play_url = audio_url or stream_url
273
+ if play_url:
274
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
275
+ <source src="{play_url}" type="audio/mpeg">
276
+ Your browser does not support audio.
277
+ </audio>\n"""
278
+
279
+ output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
280
+ output += f"**Duration:** {song.get('duration', 'N/A')}s\n"
281
+ output += f"**Created:** {song.get('createTime', 'N/A')}\n\n"
282
+ output += "---\n\n"
283
+ else:
284
+ output += "\n**No song data found in response.**\n"
285
+
286
+ elif status == "FAILED":
287
+ error_msg = task_data.get("errorMessage", "Unknown error")
288
+ output += f"\n**Error:** {error_msg}\n"
289
+
290
+ elif status in ["PENDING", "PROCESSING", "RUNNING"]:
291
+ output += f"\n**Task is still processing...**\n"
292
+ output += f"Check again in 30 seconds.\n"
293
+
294
+ else:
295
+ output += f"\n**Unknown status:** {status}\n"
296
+
297
+ else:
298
+ output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
299
+
300
+ # Show raw JSON for debugging
301
+ output += "\n## 📋 Raw Response\n"
302
+ output += f"```json\n{json.dumps(data, indent=2)}\n```"
303
+
304
+ return output
305
+
306
+ except Exception as e:
307
+ return f"❌ Error checking task: {str(e)}"
308
+
309
+ # ============================================
310
+ # URL PARAMETER HANDLING
311
+ # ============================================
312
+
313
+ def parse_url_params(request: gr.Request):
314
+ """Parse taskid from URL parameters"""
315
+ task_id = None
316
+ if request:
317
+ try:
318
+ query_params = parse_qs(urlparse(request.request.url).query)
319
+ if 'taskid' in query_params:
320
+ task_id = query_params['taskid'][0]
321
+ # Remove any whitespace
322
+ task_id = task_id.strip()
323
+ except Exception as e:
324
+ print(f"Error parsing URL params: {e}")
325
+
326
+ return task_id
327
+
328
+ def on_page_load(request: gr.Request):
329
+ """Handle URL parameters when page loads"""
330
+ task_id = parse_url_params(request)
331
+
332
+ if task_id:
333
+ # We have a task ID from URL, return it and fetch results
334
+ task_result = get_task_info(task_id)
335
+ return (
336
+ task_id, # For check_task_id
337
+ task_result, # For check_output
338
+ gr.Tabs(selected="tab_check"), # Switch to check tab
339
+ True # Mark as loaded
340
+ )
341
+ else:
342
+ # No task ID in URL, stay on first tab
343
+ return (
344
+ "", # Empty check_task_id
345
+ "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.", # Default message
346
+ gr.Tabs(selected="tab_upload"), # Stay on upload tab
347
+ True # Mark as loaded
348
+ )
349
+
350
+ # ============================================
351
+ # BUILD THE APP
352
+ # ============================================
353
+
354
+ # Custom CSS
355
+ css = """
356
+ .status-badge {
357
+ padding: 5px 10px;
358
+ border-radius: 20px;
359
+ font-weight: bold;
360
+ }
361
+ .api-status-ok { background-color: #d4edda; color: #155724; }
362
+ .api-status-missing { background-color: #f8d7da; color: #721c24; }
363
+ audio { width: 100%; margin: 10px 0; }
364
+ """
365
+
366
+ # Build interface
367
+ with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app:
368
+
369
+ gr.Markdown("""
370
+ <div style="text-align: center; margin-bottom: 20px;">
371
+ <h1>🎵 Suno Cover Creator</h1>
372
+ <p>Upload → Create Cover → Check Status</p>
373
+ </div>
374
+ """)
375
+
376
+ # API Status Banner
377
+ api_status_color = "✅ API Key: Loaded" if SUNO_API_KEY else "❌ API Key: Not Found"
378
+ api_status_class = "api-status-ok" if SUNO_API_KEY else "api-status-missing"
379
+
380
+ with gr.Row():
381
+ gr.Markdown(f"""
382
+ <div class="{api_status_class}" style="padding: 10px; border-radius: 5px; text-align: center;">
383
+ {api_status_color}
384
+ </div>
385
+ """)
386
+
387
+ # State for initial load tracking
388
+ initial_load_done = gr.State(value=False)
389
+
390
+ # Main tabs
391
+ with gr.Tabs() as tabs:
392
+ # ========== PAGE 1: FILE UPLOAD ==========
393
+ with gr.TabItem("📤 1. Upload File", id="tab_upload"):
394
+ with gr.Row():
395
+ with gr.Column():
396
+ file_input = gr.File(
397
+ label="Upload Your Audio File",
398
+ file_count="single",
399
+ file_types=["audio", ".mp3", ".wav", ".m4a", ".flac", ".ogg"]
400
+ )
401
+
402
+ file_info = gr.Markdown("📁 No file selected")
403
+
404
+ custom_filename = gr.Textbox(
405
+ label="Custom Filename (optional)",
406
+ placeholder="my-audio-file",
407
+ info="Extension preserved automatically"
408
+ )
409
+
410
+ upload_btn = gr.Button("⚡ Prepare File", variant="primary", size="lg")
411
+
412
+ upload_status = gr.Textbox(label="Status", interactive=False)
413
+ upload_download = gr.File(label="Download File", interactive=False)
414
+
415
+ # ========== PAGE 2: COVER CREATION ==========
416
+ with gr.TabItem("🎨 2. Create Cover", id="tab_create"):
417
+ with gr.Row():
418
+ with gr.Column(scale=1):
419
+ gr.Markdown("### 🎵 Cover Details")
420
+
421
+ cover_prompt = gr.Textbox(
422
+ label="Prompt",
423
+ value="A dramatic orchestral cover with dark undertones",
424
+ lines=2
425
+ )
426
+
427
+ cover_title = gr.Textbox(
428
+ label="Title",
429
+ value="The Fool's Ascension"
430
+ )
431
+
432
+ cover_style = gr.Textbox(
433
+ label="Style",
434
+ value="Epic Orchestral, Dark Cinematic"
435
+ )
436
+
437
+ # URL Selection
438
+ gr.Markdown("### 🔗 Upload URL")
439
+
440
+ url_type = gr.Radio(
441
+ label="Source",
442
+ choices=[
443
+ ("Use uploaded file", "uploaded"),
444
+ ("Use custom URL", "custom"),
445
+ ("Auto-generate", "auto")
446
+ ],
447
+ value="uploaded",
448
+ info="Choose where the audio comes from"
449
+ )
450
+
451
+ custom_url = gr.Textbox(
452
+ label="Custom URL",
453
+ placeholder="https://example.com/audio.mp3",
454
+ visible=False
455
+ )
456
+
457
+ # Reference to uploaded file from Page 1
458
+ uploaded_file_ref = gr.State()
459
+
460
+ with gr.Column(scale=1):
461
+ gr.Markdown("### ⚙️ Advanced Settings")
462
+
463
+ cover_model = gr.Dropdown(
464
+ label="Model",
465
+ choices=["V4_5ALL", "V5", "V4"],
466
+ value="V4_5ALL"
467
+ )
468
+
469
+ cover_instrumental = gr.Checkbox(
470
+ label="Instrumental",
471
+ value=True
472
+ )
473
+
474
+ cover_vocal = gr.Dropdown(
475
+ label="Vocal Gender",
476
+ choices=["m", "f", "none"],
477
+ value="m"
478
+ )
479
+
480
+ cover_negative = gr.Textbox(
481
+ label="Negative Tags",
482
+ value="Distorted, Low Quality",
483
+ placeholder="What to avoid"
484
+ )
485
+
486
+ # Show/hide custom URL
487
+ def toggle_custom_url(choice):
488
+ return gr.update(visible=choice == "custom")
489
+
490
+ url_type.change(
491
+ toggle_custom_url,
492
+ inputs=url_type,
493
+ outputs=custom_url
494
+ )
495
+
496
+ # Generate button
497
+ generate_btn = gr.Button("🎬 Generate Cover", variant="primary", size="lg")
498
+ generate_output = gr.Markdown("Ready to generate...")
499
+
500
+ # ========== PAGE 3: CHECK TASK - ORIGINAL ==========
501
+ with gr.TabItem("🔍 3. Check Any Task", id="tab_check"):
502
+ with gr.Row():
503
+ with gr.Column(scale=1):
504
+ gr.Markdown("### Check Task Status")
505
+ gr.Markdown("Enter any Suno Task ID to check its status")
506
+
507
+ check_task_id = gr.Textbox(
508
+ label="Task ID",
509
+ placeholder="Enter Task ID from generation",
510
+ info="From Cover Creator or any Suno API request"
511
+ )
512
+
513
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
514
+ check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
515
+
516
+ # URL parameter info
517
+ gr.Markdown("""
518
+ **Quick access via URL:**
519
+ Add `?taskid=YOUR_TASK_ID` to the URL
520
+
521
+ Example:
522
+ `https://www.1hit.no/cover/logs.php`
523
+ """)
524
+
525
+ with gr.Column(scale=2):
526
+ check_output = gr.Markdown(
527
+ value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
528
+ )
529
+
530
+ # ============================================
531
+ # EVENT HANDLERS
532
+ # ============================================
533
+
534
+ # Page 1: File Upload
535
+ file_input.change(
536
+ fn=get_file_info,
537
+ inputs=[file_input],
538
+ outputs=file_info
539
+ )
540
+
541
+ upload_btn.click(
542
+ fn=upload_file,
543
+ inputs=[file_input, custom_filename],
544
+ outputs=[upload_download, upload_status]
545
+ )
546
+
547
+ # Store uploaded file reference for Page 2
548
+ def store_file_ref(file_obj):
549
+ return file_obj
550
+
551
+ file_input.change(
552
+ fn=store_file_ref,
553
+ inputs=[file_input],
554
+ outputs=uploaded_file_ref
555
+ )
556
+
557
+ # Page 2: Generate Cover
558
+ generate_btn.click(
559
+ fn=generate_cover,
560
+ inputs=[
561
+ cover_prompt, cover_title, cover_style,
562
+ url_type, custom_url, uploaded_file_ref,
563
+ cover_instrumental, cover_model,
564
+ gr.State(""), # persona_id placeholder
565
+ cover_negative, cover_vocal,
566
+ gr.State(0.65), gr.State(0.65), gr.State(0.65), # weights
567
+ gr.State(True) # custom_mode
568
+ ],
569
+ outputs=generate_output
570
+ )
571
+
572
+ # Page 3: Check Task - Original handlers
573
+ def clear_check():
574
+ return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
575
+
576
+ check_clear_btn.click(
577
+ clear_check,
578
+ outputs=[check_task_id, check_output]
579
+ )
580
+
581
+ check_btn.click(
582
+ get_task_info,
583
+ inputs=[check_task_id],
584
+ outputs=check_output
585
+ )
586
+
587
+ # Load URL parameters when the app starts
588
+ app.load(
589
+ fn=on_page_load,
590
+ inputs=[],
591
+ outputs=[check_task_id, check_output, tabs, initial_load_done],
592
+ queue=False
593
+ )
594
+
595
+ # Footer
596
+ gr.Markdown("---")
597
+ gr.Markdown("""
598
+ <div style="text-align: center; padding: 20px;">
599
+ <p>🔗 <b>Quick URL Access:</b> Add <code>?taskid=YOUR_TASK_ID</code> to auto-load task</p>
600
+
601
+ </div>
602
+ """)
603
+
604
+ # Launch
605
+ if __name__ == "__main__":
606
+ if not SUNO_API_KEY:
607
+ print("⚠️ Warning: Suno API key not found")
608
+ print("Set 'SunoKey' environment variable or add to .env file")
609
+
610
+ print("🚀 Starting Suno Cover Creator")
611
+ print(f"🔑 SunoKey: {'✅ Set' if SUNO_API_KEY else '❌ Not set'}")
612
+ print("🔗 Use URL parameter: http://localhost:7860/?taskid=YOUR_TASK_ID")
613
+
614
+ app.launch(
615
+ server_name="0.0.0.0",
616
+ server_port=7860,
617
+ share=False
618
+ )
a1.py ADDED
@@ -0,0 +1,539 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ import requests
6
+ import json
7
+ import uuid
8
+ import time
9
+ from datetime import datetime
10
+ from dotenv import load_dotenv
11
+
12
+ # Load environment variables
13
+ load_dotenv()
14
+
15
+ # Configuration
16
+ SUNO_API_KEY = os.environ.get("SunoKey")
17
+ FIXED_CALLBACK_URL = "https://1hit.no/cover/cb.php"
18
+ SUNO_API_URL = "https://api.sunoapi.org/api/v1/generate/upload-cover"
19
+ CHECK_TASK_URL = "https://api.sunoapi.org/api/v1/task" # Adjust if different
20
+
21
+ # Create uploads directory
22
+ UPLOADS_FOLDER = "uploads"
23
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
24
+
25
+ # ============================================
26
+ # PAGE 1: FILE UPLOAD
27
+ # ============================================
28
+
29
+ def upload_file(file_obj, custom_name):
30
+ """Simple file upload - returns file for download"""
31
+ if not file_obj:
32
+ return None, "❌ Please upload a file first"
33
+
34
+ try:
35
+ # Handle Gradio 6+ file object
36
+ if isinstance(file_obj, dict):
37
+ file_path = file_obj["path"]
38
+ original_name = file_obj["name"]
39
+ else:
40
+ file_path = file_obj.name
41
+ original_name = os.path.basename(file_path)
42
+
43
+ # Create temp directory
44
+ temp_dir = tempfile.mkdtemp()
45
+
46
+ # Determine filename
47
+ if custom_name and custom_name.strip():
48
+ ext = os.path.splitext(original_name)[1]
49
+ base_name = custom_name.strip()
50
+ if not base_name.endswith(ext):
51
+ final_name = base_name + ext
52
+ else:
53
+ final_name = base_name
54
+ else:
55
+ final_name = original_name
56
+
57
+ # Copy file
58
+ final_path = os.path.join(temp_dir, final_name)
59
+ shutil.copy2(file_path, final_path)
60
+
61
+ return final_path, f"✅ Ready: {final_name}"
62
+
63
+ except Exception as e:
64
+ return None, f"❌ Error: {str(e)}"
65
+
66
+ def get_file_info(file_obj):
67
+ """Display file information"""
68
+ if not file_obj:
69
+ return "📁 No file selected"
70
+
71
+ try:
72
+ if isinstance(file_obj, dict):
73
+ file_path = file_obj["path"]
74
+ file_name = file_obj["name"]
75
+ else:
76
+ file_path = file_obj.name
77
+ file_name = os.path.basename(file_path)
78
+
79
+ size = os.path.getsize(file_path)
80
+ return f"📄 **{file_name}**\n• Size: {size/1024:.1f} KB\n• Type: {os.path.splitext(file_name)[1]}"
81
+ except:
82
+ return "📁 File selected"
83
+
84
+ # ============================================
85
+ # PAGE 2: COVER CREATION
86
+ # ============================================
87
+
88
+ def generate_cover(
89
+ prompt,
90
+ title,
91
+ style,
92
+ upload_url_type,
93
+ custom_upload_url,
94
+ uploaded_file,
95
+ instrumental=True,
96
+ model="V4_5ALL",
97
+ persona_id="",
98
+ negative_tags="",
99
+ vocal_gender="m",
100
+ style_weight=0.65,
101
+ weirdness_constraint=0.65,
102
+ audio_weight=0.65,
103
+ custom_mode=True
104
+ ):
105
+ """Generate cover using Suno API"""
106
+
107
+ # Check API key
108
+ if not SUNO_API_KEY:
109
+ return "❌ Suno API key not found. Please set 'SunoKey' environment variable."
110
+
111
+ # Determine upload URL
112
+ if upload_url_type == "uploaded":
113
+ # Use uploaded file - in real implementation, you'd upload to storage and get URL
114
+ if not uploaded_file:
115
+ return "❌ Please upload a file first or select another URL type"
116
+
117
+ # For demo - simulate URL from uploaded file
118
+ if isinstance(uploaded_file, dict):
119
+ file_name = uploaded_file.get("name", "uploaded_file.mp3")
120
+ else:
121
+ file_name = getattr(uploaded_file, "name", "uploaded_file.mp3")
122
+
123
+ upload_url = f"https://storage.temp.example.com/uploads/{int(time.time())}_{file_name}"
124
+
125
+ elif upload_url_type == "custom":
126
+ upload_url = custom_upload_url.strip()
127
+ if not upload_url:
128
+ return "❌ Please provide a custom upload URL"
129
+ else: # auto
130
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
131
+ unique_id = str(uuid.uuid4())[:8]
132
+ upload_url = f"https://storage.temp.example.com/uploads/{timestamp}_{unique_id}.mp3"
133
+
134
+ # Prepare payload
135
+ payload = {
136
+ "uploadUrl": upload_url,
137
+ "customMode": custom_mode,
138
+ "instrumental": instrumental,
139
+ "model": model,
140
+ "callBackUrl": FIXED_CALLBACK_URL,
141
+ "prompt": prompt,
142
+ "style": style,
143
+ "title": title,
144
+ "personaId": persona_id,
145
+ "negativeTags": negative_tags,
146
+ "vocalGender": vocal_gender,
147
+ "styleWeight": style_weight,
148
+ "weirdnessConstraint": weirdness_constraint,
149
+ "audioWeight": audio_weight
150
+ }
151
+
152
+ # Remove empty fields
153
+ payload = {k: v for k, v in payload.items() if v not in ["", None]}
154
+
155
+ # Headers
156
+ headers = {
157
+ "Authorization": f"Bearer {SUNO_API_KEY}",
158
+ "Content-Type": "application/json"
159
+ }
160
+
161
+ try:
162
+ # Make API request
163
+ response = requests.post(SUNO_API_URL, json=payload, headers=headers)
164
+
165
+ if response.status_code == 200:
166
+ result = response.json()
167
+ if result.get("code") == 200:
168
+ task_id = result.get("data", {}).get("taskId", "Unknown")
169
+ return f"""✅ **Generation Started!**
170
+
171
+ **Task ID:** `{task_id}`
172
+
173
+ **Upload URL:** {upload_url}
174
+ **Callback URL:** {FIXED_CALLBACK_URL}
175
+
176
+ 📋 **Full Response:**
177
+ ```json
178
+ {json.dumps(result, indent=2)}
179
+ ```"""
180
+ else:
181
+ return f"""❌ **API Error:** {result.get('msg', 'Unknown error')}
182
+
183
+ 📋 **Response:**
184
+ ```json
185
+ {json.dumps(result, indent=2)}
186
+ ```"""
187
+ else:
188
+ return f"""❌ **HTTP Error {response.status_code}**
189
+
190
+ {response.text}"""
191
+
192
+ except Exception as e:
193
+ return f"❌ **Error:** {str(e)}"
194
+
195
+ # ============================================
196
+ # PAGE 3: CHECK TASK
197
+ # ============================================
198
+
199
+ def check_task_status(task_id):
200
+ """Check status of a generation task"""
201
+ if not task_id or not task_id.strip():
202
+ return "❌ Please enter a Task ID"
203
+
204
+ if not SUNO_API_KEY:
205
+ return "❌ Suno API key not found"
206
+
207
+ headers = {
208
+ "Authorization": f"Bearer {SUNO_API_KEY}",
209
+ "Content-Type": "application/json"
210
+ }
211
+
212
+ try:
213
+ # Try different endpoint patterns - adjust based on actual API
214
+ endpoints = [
215
+ f"{CHECK_TASK_URL}/{task_id}",
216
+ f"{CHECK_TASK_URL}?taskId={task_id}",
217
+ f"https://api.sunoapi.org/api/v1/task/{task_id}"
218
+ ]
219
+
220
+ for url in endpoints:
221
+ try:
222
+ response = requests.get(url, headers=headers)
223
+ if response.status_code == 200:
224
+ result = response.json()
225
+ return f"""✅ **Task Status**
226
+
227
+ 📋 **Result:**
228
+ ```json
229
+ {json.dumps(result, indent=2)}
230
+ ```"""
231
+ except:
232
+ continue
233
+
234
+ return f"❌ Could not find task with ID: {task_id}"
235
+
236
+ except Exception as e:
237
+ return f"❌ **Error:** {str(e)}"
238
+
239
+ def poll_task(task_id, interval=5, max_attempts=12):
240
+ """Poll task until completion"""
241
+ if not task_id or not task_id.strip():
242
+ return "❌ Please enter a Task ID"
243
+
244
+ status_msg = ""
245
+ for attempt in range(max_attempts):
246
+ status_msg += f"⏳ Polling attempt {attempt + 1}/{max_attempts}...\n"
247
+
248
+ # Get status
249
+ result = check_task_status(task_id)
250
+ status_msg += f"{result}\n\n"
251
+
252
+ # Check if completed (adjust based on actual response format)
253
+ if "completed" in result.lower() or "success" in result.lower():
254
+ status_msg += "✅ **Task completed!**"
255
+ break
256
+
257
+ if attempt < max_attempts - 1:
258
+ time.sleep(interval)
259
+
260
+ return status_msg
261
+
262
+ # ============================================
263
+ # BUILD THE APP
264
+ # ============================================
265
+
266
+ # Custom CSS
267
+ css = """
268
+ .status-badge {
269
+ padding: 5px 10px;
270
+ border-radius: 20px;
271
+ font-weight: bold;
272
+ }
273
+ .api-status-ok { background-color: #d4edda; color: #155724; }
274
+ .api-status-missing { background-color: #f8d7da; color: #721c24; }
275
+ """
276
+
277
+ # Build interface
278
+ with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app:
279
+
280
+ gr.Markdown("""
281
+ <div style="text-align: center; margin-bottom: 20px;">
282
+ <h1>🎵 Suno Cover Creator</h1>
283
+ <p>Upload → Create Cover → Check Status</p>
284
+ </div>
285
+ """)
286
+
287
+ # API Status Banner
288
+ api_status_color = "✅ API Key: Loaded" if SUNO_API_KEY else "❌ API Key: Not Found"
289
+ api_status_class = "api-status-ok" if SUNO_API_KEY else "api-status-missing"
290
+
291
+ with gr.Row():
292
+ gr.Markdown(f"""
293
+ <div class="{api_status_class}" style="padding: 10px; border-radius: 5px; text-align: center;">
294
+ {api_status_color} | Callback URL: <code>{FIXED_CALLBACK_URL}</code>
295
+ </div>
296
+ """)
297
+
298
+ # Main tabs
299
+ with gr.Tabs():
300
+ # ========== PAGE 1: FILE UPLOAD ==========
301
+ with gr.TabItem("📤 1. Upload File", id="tab_upload"):
302
+ with gr.Row():
303
+ with gr.Column():
304
+ file_input = gr.File(
305
+ label="Upload Your Audio File",
306
+ file_count="single",
307
+ file_types=["audio", ".mp3", ".wav", ".m4a", ".flac", ".ogg"]
308
+ )
309
+
310
+ file_info = gr.Markdown("📁 No file selected")
311
+
312
+ custom_filename = gr.Textbox(
313
+ label="Custom Filename (optional)",
314
+ placeholder="my-audio-file",
315
+ info="Extension preserved automatically"
316
+ )
317
+
318
+ upload_btn = gr.Button("⚡ Prepare File", variant="primary", size="lg")
319
+
320
+ upload_status = gr.Textbox(label="Status", interactive=False)
321
+ upload_download = gr.File(label="Download File", interactive=False)
322
+
323
+ # ========== PAGE 2: COVER CREATION ==========
324
+ with gr.TabItem("🎨 2. Create Cover", id="tab_create"):
325
+ with gr.Row():
326
+ with gr.Column(scale=1):
327
+ gr.Markdown("### 🎵 Cover Details")
328
+
329
+ cover_prompt = gr.Textbox(
330
+ label="Prompt",
331
+ value="A dramatic orchestral cover with dark undertones",
332
+ lines=2
333
+ )
334
+
335
+ cover_title = gr.Textbox(
336
+ label="Title",
337
+ value="The Fool's Ascension"
338
+ )
339
+
340
+ cover_style = gr.Textbox(
341
+ label="Style",
342
+ value="Epic Orchestral, Dark Cinematic"
343
+ )
344
+
345
+ # URL Selection
346
+ gr.Markdown("### 🔗 Upload URL")
347
+
348
+ url_type = gr.Radio(
349
+ label="Source",
350
+ choices=[
351
+ ("Use uploaded file", "uploaded"),
352
+ ("Use custom URL", "custom"),
353
+ ("Auto-generate", "auto")
354
+ ],
355
+ value="uploaded",
356
+ info="Choose where the audio comes from"
357
+ )
358
+
359
+ custom_url = gr.Textbox(
360
+ label="Custom URL",
361
+ placeholder="https://example.com/audio.mp3",
362
+ visible=False
363
+ )
364
+
365
+ # Reference to uploaded file from Page 1
366
+ uploaded_file_ref = gr.State()
367
+
368
+ with gr.Column(scale=1):
369
+ gr.Markdown("### ⚙️ Advanced Settings")
370
+
371
+ cover_model = gr.Dropdown(
372
+ label="Model",
373
+ choices=["V4_5ALL", "V5", "V4", "V3"],
374
+ value="V4_5ALL"
375
+ )
376
+
377
+ cover_instrumental = gr.Checkbox(
378
+ label="Instrumental",
379
+ value=True
380
+ )
381
+
382
+ cover_vocal = gr.Dropdown(
383
+ label="Vocal Gender",
384
+ choices=["m", "f", "none"],
385
+ value="m"
386
+ )
387
+
388
+ cover_negative = gr.Textbox(
389
+ label="Negative Tags",
390
+ value="Distorted, Low Quality",
391
+ placeholder="What to avoid"
392
+ )
393
+
394
+ # Show/hide custom URL
395
+ def toggle_custom_url(choice):
396
+ return gr.update(visible=choice == "custom")
397
+
398
+ url_type.change(
399
+ toggle_custom_url,
400
+ inputs=url_type,
401
+ outputs=custom_url
402
+ )
403
+
404
+ # Generate button
405
+ generate_btn = gr.Button("🎬 Generate Cover", variant="primary", size="lg")
406
+ generate_output = gr.Markdown("Ready to generate...")
407
+
408
+ # ========== PAGE 3: CHECK TASK ==========
409
+ with gr.TabItem("🔍 3. Check Task", id="tab_check"):
410
+ with gr.Row():
411
+ with gr.Column():
412
+ gr.Markdown("### Check Single Task")
413
+
414
+ task_id_input = gr.Textbox(
415
+ label="Task ID",
416
+ placeholder="Enter task ID from generation response",
417
+ lines=1
418
+ )
419
+
420
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
421
+ check_output = gr.Markdown("Enter a Task ID to check...")
422
+
423
+ with gr.Column():
424
+ gr.Markdown("### Poll Until Complete")
425
+
426
+ poll_task_id = gr.Textbox(
427
+ label="Task ID",
428
+ placeholder="Enter task ID to poll"
429
+ )
430
+
431
+ poll_interval = gr.Slider(
432
+ label="Poll Interval (seconds)",
433
+ minimum=2,
434
+ maximum=10,
435
+ value=5,
436
+ step=1
437
+ )
438
+
439
+ poll_attempts = gr.Slider(
440
+ label="Max Attempts",
441
+ minimum=5,
442
+ maximum=30,
443
+ value=12,
444
+ step=1
445
+ )
446
+
447
+ poll_btn = gr.Button("⏳ Poll Until Complete", variant="secondary")
448
+ poll_output = gr.Markdown("Enter a Task ID to poll...")
449
+
450
+ # ============================================
451
+ # EVENT HANDLERS
452
+ # ============================================
453
+
454
+ # Page 1: File Upload
455
+ file_input.change(
456
+ fn=get_file_info,
457
+ inputs=[file_input],
458
+ outputs=file_info
459
+ )
460
+
461
+ upload_btn.click(
462
+ fn=upload_file,
463
+ inputs=[file_input, custom_filename],
464
+ outputs=[upload_download, upload_status]
465
+ )
466
+
467
+ # Store uploaded file reference for Page 2
468
+ def store_file_ref(file_obj):
469
+ return file_obj
470
+
471
+ file_input.change(
472
+ fn=store_file_ref,
473
+ inputs=[file_input],
474
+ outputs=uploaded_file_ref
475
+ )
476
+
477
+ # Page 2: Generate Cover
478
+ generate_btn.click(
479
+ fn=generate_cover,
480
+ inputs=[
481
+ cover_prompt, cover_title, cover_style,
482
+ url_type, custom_url, uploaded_file_ref,
483
+ cover_instrumental, cover_model,
484
+ gr.State(""), # persona_id placeholder
485
+ cover_negative, cover_vocal,
486
+ gr.State(0.65), gr.State(0.65), gr.State(0.65), # weights
487
+ gr.State(True) # custom_mode
488
+ ],
489
+ outputs=generate_output
490
+ )
491
+
492
+ # Page 3: Check Task
493
+ check_btn.click(
494
+ fn=check_task_status,
495
+ inputs=[task_id_input],
496
+ outputs=check_output
497
+ )
498
+
499
+ def poll_wrapper(task_id, interval, attempts):
500
+ if not task_id:
501
+ return "❌ Please enter a Task ID"
502
+
503
+ status_msg = ""
504
+ for attempt in range(int(attempts)):
505
+ status_msg += f"⏳ Attempt {attempt + 1}/{int(attempts)}...\n"
506
+ result = check_task_status(task_id)
507
+ status_msg += f"{result}\n\n"
508
+
509
+ # Check if completed (simplified)
510
+ if "completed" in result.lower() or "success" in result.lower():
511
+ status_msg += "✅ **Task completed!**"
512
+ break
513
+
514
+ if attempt < int(attempts) - 1:
515
+ time.sleep(int(interval))
516
+
517
+ return status_msg
518
+
519
+ poll_btn.click(
520
+ fn=poll_wrapper,
521
+ inputs=[poll_task_id, poll_interval, poll_attempts],
522
+ outputs=poll_output
523
+ )
524
+
525
+ # Footer
526
+ gr.Markdown("---")
527
+ gr.Markdown("💡 **Tips:** Upload your audio in Page 1, create cover in Page 2, then check status in Page 3")
528
+
529
+ # Launch
530
+ if __name__ == "__main__":
531
+ if not SUNO_API_KEY:
532
+ print("⚠️ Warning: Suno API key not found")
533
+ print("Set 'SunoKey' environment variable or add to .env file")
534
+
535
+ app.launch(
536
+ server_name="0.0.0.0",
537
+ server_port=7860,
538
+ share=False
539
+ )
aaa.py ADDED
@@ -0,0 +1,693 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ import requests
6
+ import json
7
+ import uuid
8
+ import time
9
+ from datetime import datetime
10
+ from dotenv import load_dotenv
11
+ from urllib.parse import urlparse, parse_qs
12
+
13
+ # Load environment variables
14
+ load_dotenv()
15
+
16
+ # ============================================
17
+ # CONFIGURATION
18
+ # ============================================
19
+
20
+ SUNO_API_KEY = os.environ.get("SunoKey", "")
21
+ FIXED_CALLBACK_URL = "https://1hit.no/cover/cb.php"
22
+ SUNO_API_URL = "https://api.sunoapi.org/api/v1/generate/upload-cover"
23
+ LOGS_URL = "https://www.1hit.no/cover/compu.php"
24
+ BACK_URL = "https://www.1hit.no/cover/ib.php"
25
+
26
+ # Create uploads directory
27
+ UPLOADS_FOLDER = "uploads"
28
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
29
+
30
+ # ============================================
31
+ # PAGE 1: FILE UPLOAD
32
+ # ============================================
33
+
34
+ def upload_file(file_obj, custom_name):
35
+ """Simple file upload - returns file for download"""
36
+ if not file_obj:
37
+ return None, "❌ Please upload a file first"
38
+
39
+ try:
40
+ if isinstance(file_obj, dict):
41
+ file_path = file_obj["path"]
42
+ original_name = file_obj["name"]
43
+ else:
44
+ file_path = file_obj.name
45
+ original_name = os.path.basename(file_path)
46
+
47
+ temp_dir = tempfile.mkdtemp()
48
+
49
+ if custom_name and custom_name.strip():
50
+ ext = os.path.splitext(original_name)[1]
51
+ base_name = custom_name.strip()
52
+ if not base_name.endswith(ext):
53
+ final_name = base_name + ext
54
+ else:
55
+ final_name = base_name
56
+ else:
57
+ final_name = original_name
58
+
59
+ final_path = os.path.join(temp_dir, final_name)
60
+ shutil.copy2(file_path, final_path)
61
+
62
+ return final_path, f"✅ Ready: {final_name}"
63
+
64
+ except Exception as e:
65
+ return None, f"❌ Error: {str(e)}"
66
+
67
+ def get_file_info(file_obj):
68
+ """Display file information"""
69
+ if not file_obj:
70
+ return "📁 No file selected"
71
+
72
+ try:
73
+ if isinstance(file_obj, dict):
74
+ file_path = file_obj["path"]
75
+ file_name = file_obj["name"]
76
+ else:
77
+ file_path = file_obj.name
78
+ file_name = os.path.basename(file_path)
79
+
80
+ size = os.path.getsize(file_path)
81
+ return f"📄 **{file_name}**\n• Size: {size/1024:.1f} KB\n• Type: {os.path.splitext(file_name)[1]}"
82
+ except:
83
+ return "📁 File selected"
84
+
85
+ # ============================================
86
+ # PAGE 2: COVER CREATION WITH PREVIEW
87
+ # ============================================
88
+
89
+ def get_file_path(file_obj):
90
+ """Extract file path from uploaded file object"""
91
+ if not file_obj:
92
+ return None
93
+ if isinstance(file_obj, dict):
94
+ return file_obj.get("path", "")
95
+ return getattr(file_obj, "name", "")
96
+
97
+ def generate_cover(
98
+ prompt,
99
+ title,
100
+ style,
101
+ use_custom_url,
102
+ custom_url_input,
103
+ uploaded_file,
104
+ instrumental=True,
105
+ model="V4_5ALL",
106
+ persona_id="",
107
+ negative_tags="",
108
+ vocal_gender="m",
109
+ style_weight=0.65,
110
+ weirdness_constraint=0.65,
111
+ audio_weight=0.65,
112
+ custom_mode=True
113
+ ):
114
+ """Generate cover using Suno API"""
115
+
116
+ if not SUNO_API_KEY:
117
+ return "❌ Suno API key not found"
118
+
119
+ # Determine which URL to use
120
+ if use_custom_url:
121
+ upload_url = custom_url_input.strip()
122
+ if not upload_url:
123
+ return "❌ Please enter a custom URL"
124
+ url_source = f"Custom URL: {upload_url}"
125
+ else:
126
+ if not uploaded_file:
127
+ return "❌ Please upload a file first"
128
+ #upload_url = get_file_path(uploaded_file)
129
+ #upload_url = f"/file={get_file_path(uploaded_file)}"
130
+ upload_url = f"https://1hit-upload-cover.hf.space/gradio_api/file={get_file_path(uploaded_file)}"
131
+ if not upload_url:
132
+ return "❌ Could not get file path from uploaded file"
133
+
134
+ url_source = f"Uploaded file: {upload_url}"
135
+
136
+ # Prepare payload
137
+ payload = {
138
+ "uploadUrl": upload_url,
139
+ "customMode": custom_mode,
140
+ "instrumental": instrumental,
141
+ "model": model,
142
+ "callBackUrl": FIXED_CALLBACK_URL,
143
+ "prompt": prompt,
144
+ "style": style,
145
+ "title": title,
146
+ "personaId": persona_id,
147
+ "negativeTags": negative_tags,
148
+ "vocalGender": vocal_gender,
149
+ "styleWeight": style_weight,
150
+ "weirdnessConstraint": weirdness_constraint,
151
+ "audioWeight": audio_weight
152
+ }
153
+
154
+ payload = {k: v for k, v in payload.items() if v not in ["", None]}
155
+
156
+ headers = {
157
+ "Authorization": f"Bearer {SUNO_API_KEY}",
158
+ "Content-Type": "application/json"
159
+ }
160
+
161
+ try:
162
+ response = requests.post(SUNO_API_URL, json=payload, headers=headers)
163
+
164
+ if response.status_code == 200:
165
+ result = response.json()
166
+ if result.get("code") == 200:
167
+ task_id = result.get("data", {}).get("taskId", "Unknown")
168
+ return f"""✅ **Generation Started!**
169
+
170
+ **Task ID:** `{task_id}`
171
+
172
+ **URL Source:** {url_source}
173
+
174
+ 📋 **Full Response:**
175
+ ```json
176
+ {json.dumps(result, indent=2)}
177
+ ```"""
178
+ else:
179
+ return f"""❌ **API Error:** {result.get('msg', 'Unknown error')}
180
+ 📋 **Response:**
181
+ ```json
182
+ {json.dumps(result, indent=2)}
183
+ ```"""
184
+ else:
185
+ return f"""❌ **HTTP Error {response.status_code}**
186
+ {response.text}"""
187
+
188
+ except Exception as e:
189
+ return f"❌ **Error:** {str(e)}"
190
+
191
+ def update_preview(use_custom_url, custom_url_input, uploaded_file):
192
+ """Update audio preview based on selected source"""
193
+ if use_custom_url:
194
+ url = custom_url_input.strip()
195
+ if url:
196
+ return gr.update(value=url, visible=True, label="Preview URL")
197
+ else:
198
+ return gr.update(value=None, visible=False, label="Preview URL")
199
+ else:
200
+ if uploaded_file:
201
+ file_path = get_file_path(uploaded_file)
202
+ if file_path:
203
+ return gr.update(value=file_path, visible=True, label="Preview from uploaded file")
204
+ return gr.update(value=None, visible=False, label="Preview URL")
205
+
206
+ # ============================================
207
+ # PAGE 3: CHECK TASK
208
+ # ============================================
209
+
210
+ def get_task_info(task_id):
211
+ """Check Suno task status"""
212
+ if not task_id:
213
+ return "❌ Please enter a Task ID ❌"
214
+
215
+ if not SUNO_API_KEY:
216
+ return "❌ Suno API key not configured"
217
+
218
+ try:
219
+ resp = requests.get(
220
+ "https://api.sunoapi.org/api/v1/generate/record-info",
221
+ headers={"Authorization": f"Bearer {SUNO_API_KEY}"},
222
+ params={"taskId": task_id},
223
+ timeout=30
224
+ )
225
+
226
+ if resp.status_code == 401:
227
+ return "❌ Authentication failed - Invalid API key"
228
+ elif resp.status_code == 404:
229
+ return f"❌ Task ID '{task_id}' not found"
230
+ elif resp.status_code != 200:
231
+ return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}"
232
+
233
+ data = resp.json()
234
+
235
+ # Format the response
236
+ output = f"## 🔍 Task Status: `{task_id}`\n\n"
237
+
238
+ if data.get("code") == 200:
239
+ task_data = data.get("data", {})
240
+ status = task_data.get("status", "UNKNOWN")
241
+
242
+ output += f"**Status:** {status}\n"
243
+ output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
244
+ output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
245
+ output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
246
+
247
+ if status == "SUCCESS":
248
+ response_data = task_data.get("response", {})
249
+
250
+ if isinstance(response_data, str):
251
+ try:
252
+ response_data = json.loads(response_data)
253
+ except:
254
+ response_data = {}
255
+
256
+ songs = []
257
+ if isinstance(response_data, dict):
258
+ songs = response_data.get("sunoData", []) or response_data.get("data", [])
259
+ elif isinstance(response_data, list):
260
+ songs = response_data
261
+
262
+ if songs:
263
+ output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
264
+
265
+ for i, song in enumerate(songs, 1):
266
+ if isinstance(song, dict):
267
+ output += f"### Song {i}\n"
268
+ output += f"**Title:** {song.get('title', 'Untitled')}\n"
269
+ output += f"**ID:** `{song.get('id', 'N/A')}`\n"
270
+
271
+ audio_url = song.get('audioUrl') or song.get('audio_url')
272
+ stream_url = song.get('streamUrl') or song.get('stream_url')
273
+
274
+ if audio_url:
275
+ output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
276
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
277
+ <source src="{audio_url}" type="audio/mpeg">
278
+ Your browser does not support audio.
279
+ </audio>\n"""
280
+ elif stream_url:
281
+ output += f"**Stream:** [Play]({stream_url})\n"
282
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
283
+ <source src="{stream_url}" type="audio/mpeg">
284
+ Your browser does not support audio.
285
+ </audio>\n"""
286
+
287
+ output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
288
+ output += f"**Duration:** {song.get('duration', 'N/A')}s\n\n"
289
+ output += "---\n\n"
290
+ else:
291
+ output += "\n**No song data found in response.**\n"
292
+ else:
293
+ output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
294
+
295
+ output += f"\n## 📋 Raw Response\n```json\n{json.dumps(data, indent=2)}\n```"
296
+ return output
297
+
298
+ except Exception as e:
299
+ return f"❌ Error checking task: {str(e)}"
300
+
301
+ # ============================================
302
+ # URL PARAMETER HANDLING
303
+ # ============================================
304
+
305
+ def parse_url_params(request: gr.Request):
306
+ """Parse taskid from URL parameters"""
307
+ if request:
308
+ try:
309
+ query_params = parse_qs(urlparse(request.request.url).query)
310
+ if 'taskid' in query_params:
311
+ return query_params['taskid'][0].strip()
312
+ except Exception as e:
313
+ print(f"Error parsing URL params: {e}")
314
+ return None
315
+
316
+ def on_page_load(request: gr.Request):
317
+ """Handle URL parameters when page loads"""
318
+ task_id = parse_url_params(request)
319
+
320
+ if task_id:
321
+ task_result = get_task_info(task_id)
322
+ return (
323
+ task_id,
324
+ task_result,
325
+ gr.Tabs(selected="tab_check"),
326
+ True
327
+ )
328
+ else:
329
+ return (
330
+ "",
331
+ "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.",
332
+ gr.Tabs(selected="tab_upload"),
333
+ True
334
+ )
335
+
336
+ # ============================================
337
+ # BUILD THE APP
338
+ # ============================================
339
+
340
+ css = """
341
+ .status-badge { padding: 5px 10px; border-radius: 20px; font-weight: bold; }
342
+ .api-status-ok { background-color: #d4edda; color: #155724; }
343
+ .api-status-missing { background-color: #f8d7da; color: #721c24; }
344
+ audio { width: 100%; margin: 10px 0; }
345
+ .logs-iframe { width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 5px; }
346
+ .preview-box {
347
+ border: 1px solid #ddd;
348
+ border-radius: 5px;
349
+ padding: 15px;
350
+ margin: 10px 0;
351
+ background-color: #f9f9f9;
352
+ }
353
+ """
354
+
355
+ with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app:
356
+
357
+ gr.Markdown("""
358
+ <div style="text-align: center; margin-bottom: 20px;">
359
+ <h1>🎵 Suno Cover Creator</h1>
360
+ <p>Upload → Preview Audio → Create Cover → Check Status → View Logs</p>
361
+ <p>Friendly advice: Dont cover originals, cover covers, tv-jingles, weird fringe stuff.</p>
362
+ </div>
363
+ """)
364
+
365
+ # API Status Banner
366
+ api_status_color = "✅ API Key: Loaded" if SUNO_API_KEY else "❌ API Key: Not Found"
367
+ api_status_class = "api-status-ok" if SUNO_API_KEY else "api-status-missing"
368
+
369
+ with gr.Row():
370
+ gr.Markdown(f"""
371
+ <div class="{api_status_class}" style="padding: 10px; border-radius: 5px; text-align: center;">
372
+ {api_status_color}
373
+ </div>
374
+ """)
375
+
376
+ initial_load_done = gr.State(value=False)
377
+ uploaded_file_state = gr.State()
378
+
379
+ with gr.Tabs() as tabs:
380
+ # ========== PAGE 1: FILE UPLOAD ==========
381
+ with gr.TabItem("📤 1. Upload File", id="tab_upload"):
382
+ with gr.Row():
383
+ with gr.Column():
384
+ file_input = gr.File(
385
+ label="Upload Your Audio File",
386
+ file_count="single",
387
+ file_types=["audio", ".mp3", ".wav", ".m4a", ".flac", ".ogg"]
388
+ )
389
+
390
+ file_info = gr.Markdown("📁 No file selected")
391
+
392
+ custom_filename = gr.Textbox(
393
+ label="Custom Filename (optional)",
394
+ placeholder="my-audio-file",
395
+ info="Extension preserved automatically"
396
+ )
397
+
398
+ upload_btn = gr.Button("⚡ Prepare File", variant="primary", size="lg")
399
+
400
+ upload_status = gr.Textbox(label="Status", interactive=False)
401
+ upload_download = gr.File(label="Download File", interactive=False)
402
+
403
+ # ========== PAGE 2: COVER CREATION WITH PREVIEW ==========
404
+ with gr.TabItem("🎨 2. Create Cover", id="tab_create"):
405
+ with gr.Row():
406
+ with gr.Column(scale=1):
407
+ gr.Markdown("### 🎵 Cover Details")
408
+
409
+ cover_prompt = gr.Textbox(
410
+ label="Prompt/Lyrics",
411
+ value="",
412
+ lines=5
413
+ )
414
+
415
+ cover_title = gr.Textbox(
416
+ label="Title",
417
+ value=""
418
+ )
419
+
420
+ cover_style = gr.Textbox(
421
+ label="Style",
422
+ value=""
423
+ )
424
+
425
+ gr.Markdown("### 🔗 Audio Source")
426
+
427
+ # Radio button to choose source
428
+ use_custom_url = gr.Radio(
429
+ label="Select source type",
430
+ choices=[
431
+ ("Use uploaded file", False),
432
+ ("Use custom URL", True)
433
+ ],
434
+ value=False,
435
+ info="Choose where your audio comes from"
436
+ )
437
+
438
+ # Custom URL input (visible when custom is selected)
439
+ custom_url_input = gr.Textbox(
440
+ label="Custom URL",
441
+ placeholder="https://example.com/audio.mp3 or /file=/path/to/file",
442
+ visible=False
443
+ )
444
+
445
+ # Preview section
446
+ gr.Markdown("### 🎧 Audio Preview")
447
+ gr.Markdown("Listen to verify your audio source works:")
448
+
449
+ preview_audio = gr.Audio(
450
+ label="Preview",
451
+ type="filepath",
452
+ interactive=False,
453
+ visible=False
454
+ )
455
+
456
+ # Store uploaded file reference
457
+ uploaded_file_ref = gr.State()
458
+
459
+ with gr.Column(scale=1):
460
+ gr.Markdown("### ⚙️ Advanced Settings")
461
+
462
+ cover_model = gr.Dropdown(
463
+ label="Model",
464
+ choices=["V4_5ALL", "V5", "V4"],
465
+ value="V4_5ALL"
466
+ )
467
+
468
+ cover_instrumental = gr.Checkbox(
469
+ label="Instrumental",
470
+ value=True
471
+ )
472
+
473
+ cover_vocal = gr.Dropdown(
474
+ label="Vocal Gender",
475
+ choices=["m", "f", "none"],
476
+ value="m"
477
+ )
478
+
479
+ cover_negative = gr.Textbox(
480
+ label="Negative Tags",
481
+ value="Distorted, Low Quality",
482
+ placeholder="What to avoid"
483
+ )
484
+
485
+ # Update visibility of custom URL input
486
+ def toggle_custom_url(choice):
487
+ return gr.update(visible=choice)
488
+
489
+ use_custom_url.change(
490
+ toggle_custom_url,
491
+ inputs=use_custom_url,
492
+ outputs=custom_url_input
493
+ )
494
+
495
+ # Update preview when source changes
496
+ def update_preview_with_source(use_custom, custom_url, uploaded):
497
+ if use_custom and custom_url.strip():
498
+ return gr.update(value=custom_url.strip(), visible=True)
499
+ elif not use_custom and uploaded:
500
+ file_path = get_file_path(uploaded)
501
+ if file_path:
502
+ return gr.update(value=file_path, visible=True)
503
+ return gr.update(value=None, visible=False)
504
+
505
+ # Trigger preview updates
506
+ use_custom_url.change(
507
+ fn=update_preview_with_source,
508
+ inputs=[use_custom_url, custom_url_input, uploaded_file_ref],
509
+ outputs=preview_audio
510
+ )
511
+
512
+ custom_url_input.change(
513
+ fn=update_preview_with_source,
514
+ inputs=[use_custom_url, custom_url_input, uploaded_file_ref],
515
+ outputs=preview_audio
516
+ )
517
+
518
+ # Generate button
519
+ generate_btn = gr.Button("🎬 Generate Cover", variant="primary", size="lg")
520
+ generate_output = gr.Markdown("Ready to generate...")
521
+
522
+ # ========== PAGE 3: CHECK TASK ==========
523
+ with gr.TabItem("🔍 3. Check Any Task", id="tab_check"):
524
+ with gr.Row():
525
+ with gr.Column(scale=1):
526
+ gr.Markdown("### Check Task Status")
527
+
528
+ check_task_id = gr.Textbox(
529
+ label="Task ID",
530
+ placeholder="Enter Task ID from generation"
531
+ )
532
+
533
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
534
+ check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
535
+
536
+ gr.Markdown("""
537
+ **Quick access via URL:**
538
+ Add `?taskid=YOUR_TASK_ID` to the URL
539
+
540
+ Example:
541
+ `https://1hit.no/gen/view.php?task_id=fa3529d5cbaa93427ee4451976ed5c4b`
542
+ """)
543
+
544
+ with gr.Column(scale=2):
545
+ check_output = gr.Markdown(
546
+ value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
547
+ )
548
+
549
+ # ========== PAGE 4: CALLBACK LOGS ==========
550
+ with gr.TabItem("📋 4. Callback Logs", id="tab_logs"):
551
+ gr.Markdown(f"""
552
+ ### 📋 Callback Logs
553
+ View all callback data from Suno API at: [`{LOGS_URL}`]({LOGS_URL})
554
+ """)
555
+
556
+ logs_iframe = gr.HTML(f"""
557
+ <iframe src="{LOGS_URL}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
558
+ """)
559
+
560
+ with gr.Row():
561
+ refresh_logs_btn = gr.Button("🔄 Refresh Logs", size="sm")
562
+
563
+ def refresh_logs():
564
+ return f"""
565
+ <iframe src="{LOGS_URL}?t={int(time.time())}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
566
+ """
567
+
568
+ refresh_logs_btn.click(
569
+ fn=refresh_logs,
570
+ outputs=logs_iframe
571
+ )
572
+
573
+ # ========== PAGE 4: CALLBACK LOGS ==========
574
+ with gr.TabItem("📋 5. Backup/Download", id="tab_backup"):
575
+ gr.Markdown(f"""
576
+ ### 📋 Callback Logs
577
+ View all callback data from Suno API at: [`{LOGS_URL}`]({LOGS_URL})
578
+ """)
579
+
580
+ logs_iframe = gr.HTML(f"""
581
+ <iframe src="{BACK_URL}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
582
+ """)
583
+
584
+ with gr.Row():
585
+ refresh_logs_btn = gr.Button("🔄 Refresh Logs", size="sm")
586
+
587
+ def refresh_logs():
588
+ return f"""
589
+ <iframe src="{BACK_URL}?t={int(time.time())}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
590
+ """
591
+
592
+ refresh_logs_btn.click(
593
+ fn=refresh_logs,
594
+ outputs=logs_iframe
595
+ )
596
+
597
+ # ============================================
598
+ # EVENT HANDLERS
599
+ # ============================================
600
+
601
+ # Page 1 handlers
602
+ file_input.change(
603
+ fn=get_file_info,
604
+ inputs=[file_input],
605
+ outputs=file_info
606
+ )
607
+
608
+ upload_btn.click(
609
+ fn=upload_file,
610
+ inputs=[file_input, custom_filename],
611
+ outputs=[upload_download, upload_status]
612
+ )
613
+
614
+ def store_file_ref(file_obj):
615
+ return file_obj
616
+
617
+ file_input.change(
618
+ fn=store_file_ref,
619
+ inputs=[file_input],
620
+ outputs=uploaded_file_ref
621
+ )
622
+
623
+ # Also update preview when new file is uploaded
624
+ file_input.change(
625
+ fn=lambda f, use_custom, custom: update_preview_with_source(use_custom, custom, f),
626
+ inputs=[file_input, use_custom_url, custom_url_input],
627
+ outputs=preview_audio
628
+ )
629
+
630
+ # Store uploaded file for Page 2
631
+ file_input.change(
632
+ fn=store_file_ref,
633
+ inputs=[file_input],
634
+ outputs=uploaded_file_state
635
+ )
636
+
637
+ # Page 2 generate handler
638
+ generate_btn.click(
639
+ fn=generate_cover,
640
+ inputs=[
641
+ cover_prompt, cover_title, cover_style,
642
+ use_custom_url, custom_url_input, uploaded_file_state,
643
+ cover_instrumental, cover_model,
644
+ gr.State(""), cover_negative, cover_vocal,
645
+ gr.State(0.65), gr.State(0.65), gr.State(0.65),
646
+ gr.State(True)
647
+ ],
648
+ outputs=generate_output
649
+ )
650
+
651
+ # Page 3 handlers
652
+ def clear_check():
653
+ return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
654
+
655
+ check_clear_btn.click(
656
+ clear_check,
657
+ outputs=[check_task_id, check_output]
658
+ )
659
+
660
+ check_btn.click(
661
+ get_task_info,
662
+ inputs=[check_task_id],
663
+ outputs=check_output
664
+ )
665
+
666
+ # URL parameter handling
667
+ app.load(
668
+ fn=on_page_load,
669
+ inputs=[],
670
+ outputs=[check_task_id, check_output, tabs, initial_load_done],
671
+ queue=False
672
+ )
673
+
674
+ # Footer
675
+ gr.Markdown("---")
676
+ gr.Markdown(f"""
677
+ <div style="text-align: center; padding: 20px;">
678
+ <p>🔗 <b>Quick URL Access:</b> Add <code>?taskid=YOUR_TASK_ID</code> to auto-load task</p>
679
+ <p>📋 <b>Logs:</b> <a href="{LOGS_URL}" target="_blank">{LOGS_URL}</a></p>
680
+ <p><b>💡 Preview Feature:</b> Listen to your audio before generating to verify the URL works!</p>
681
+ </div>
682
+ """)
683
+
684
+ if __name__ == "__main__":
685
+ print(f"🚀 Starting Suno Cover Creator with Audio Preview")
686
+ print(f"🔑 API Key: {'✅ Set' if SUNO_API_KEY else '❌ Not set'}")
687
+ print(f"📋 Logs: {LOGS_URL}")
688
+
689
+ app.launch(
690
+ server_name="0.0.0.0",
691
+ server_port=7860,
692
+ share=False
693
+ )
add.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ import time
6
+
7
+ # Create uploads directory
8
+ UPLOADS_FOLDER = "uploads"
9
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
10
+
11
+ def process_file(file_obj, custom_name):
12
+ """
13
+ Simple file processor - returns file for download
14
+ No zip, no passwords, just clean file handling
15
+ """
16
+ if not file_obj:
17
+ return None, "❌ Please upload a file first"
18
+
19
+ try:
20
+ # Handle Gradio 6+ file object
21
+ if isinstance(file_obj, dict):
22
+ file_path = file_obj["path"]
23
+ original_name = file_obj["name"]
24
+ else:
25
+ file_path = file_obj.name
26
+ original_name = os.path.basename(file_path)
27
+
28
+ # Create temp directory for processed file
29
+ temp_dir = tempfile.mkdtemp()
30
+
31
+ # Determine filename
32
+ if custom_name and custom_name.strip():
33
+ # Use custom name but preserve extension
34
+ ext = os.path.splitext(original_name)[1]
35
+ base_name = custom_name.strip()
36
+ if not base_name.endswith(ext):
37
+ final_name = base_name + ext
38
+ else:
39
+ final_name = base_name
40
+ else:
41
+ final_name = original_name
42
+
43
+ # Copy file to temp location
44
+ final_path = os.path.join(temp_dir, final_name)
45
+ shutil.copy2(file_path, final_path)
46
+
47
+ return final_path, f"✅ Ready: {final_name}"
48
+
49
+ except Exception as e:
50
+ return None, f"❌ Error: {str(e)}"
51
+
52
+ def get_file_info(file_obj):
53
+ """Display basic file information"""
54
+ if not file_obj:
55
+ return "📁 No file selected"
56
+
57
+ try:
58
+ if isinstance(file_obj, dict):
59
+ file_path = file_obj["path"]
60
+ file_name = file_obj["name"]
61
+ else:
62
+ file_path = file_obj.name
63
+ file_name = os.path.basename(file_path)
64
+
65
+ size = os.path.getsize(file_path)
66
+ modified = time.ctime(os.path.getmtime(file_path))
67
+
68
+ return f"""
69
+ 📄 **{file_name}**
70
+ • Size: {size/1024:.1f} KB
71
+ • Type: {os.path.splitext(file_name)[1]}
72
+ • Modified: {modified}
73
+ """
74
+ except:
75
+ return "📁 File selected"
76
+
77
+ # Simple CSS
78
+ css = """
79
+ .container { max-width: 600px; margin: auto; }
80
+ .title { text-align: center; margin-bottom: 20px; }
81
+ """
82
+
83
+ # Build minimal interface
84
+ with gr.Blocks(title="Simple File Upload", theme=gr.themes.Soft(), css=css) as app:
85
+
86
+ gr.Markdown("""
87
+ <div class="title">
88
+ <h1>📁 Simple File Upload</h1>
89
+ <p>Upload → Rename → Download</p>
90
+ </div>
91
+ """)
92
+
93
+ with gr.Row():
94
+ with gr.Column():
95
+ # File input
96
+ file_input = gr.File(
97
+ label="📤 Upload File",
98
+ file_count="single",
99
+ file_types=None # Accept all files
100
+ )
101
+
102
+ # File info display
103
+ file_info = gr.Markdown("📁 No file selected")
104
+
105
+ # Custom filename
106
+ custom_name = gr.Textbox(
107
+ label="✏️ Custom Filename (optional)",
108
+ placeholder="my-file",
109
+ info="Name without extension - original extension preserved"
110
+ )
111
+
112
+ # Process button
113
+ process_btn = gr.Button("⚡ Prepare for Download", variant="primary", size="lg")
114
+
115
+ # Status
116
+ status = gr.Textbox(label="Status", interactive=False)
117
+
118
+ # Download output
119
+ download = gr.File(label="📥 Download File", interactive=False)
120
+
121
+ # Update file info on upload
122
+ file_input.change(
123
+ fn=get_file_info,
124
+ inputs=[file_input],
125
+ outputs=file_info
126
+ )
127
+
128
+ # Process file on button click
129
+ process_btn.click(
130
+ fn=process_file,
131
+ inputs=[file_input, custom_name],
132
+ outputs=[download, status]
133
+ )
134
+
135
+ # Quick tips
136
+ with gr.Accordion("ℹ️ Tips", open=False):
137
+ gr.Markdown("""
138
+ **How to use:**
139
+ 1. Click **Upload File** to select a file
140
+ 2. (Optional) Enter a custom filename
141
+ 3. Click **Prepare for Download**
142
+ 4. Download your file
143
+
144
+ **Features:**
145
+ - ✅ Single file upload/download
146
+ - ✅ Custom filename support
147
+ - ✅ Original extension preserved
148
+ - ✅ Clean, simple interface
149
+ - ✅ No zip, no passwords, no complexity
150
+
151
+ **Perfect for:**
152
+ - Preparing files for Suno
153
+ - Quick file renaming
154
+ - Simple file transfers
155
+ """)
156
+
157
+ # Launch
158
+ if __name__ == "__main__":
159
+ app.launch(
160
+ show_error=True,
161
+ share=False,
162
+ server_name="0.0.0.0" # For Hugging Face Spaces
163
+ )
app.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import zipfile
4
+ import tempfile
5
+ import shutil
6
+ from pathlib import Path
7
+ import mimetypes
8
+
9
+ # Create uploads directory
10
+ UPLOADS_FOLDER = "uploads"
11
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
12
+
13
+ def process_file(file_obj, file_name, action):
14
+ """
15
+ Process uploaded file: return original or create zip
16
+ """
17
+ if not file_obj:
18
+ return None, "❌ No file uploaded"
19
+
20
+ try:
21
+ # Save uploaded file temporarily
22
+ temp_dir = tempfile.mkdtemp()
23
+ original_path = os.path.join(temp_dir, file_name or os.path.basename(file_obj.name))
24
+
25
+ # Copy file to temp location
26
+ shutil.copy2(file_obj.name, original_path)
27
+
28
+ if action == "original":
29
+ # Return original file
30
+ return original_path, f"✅ Ready to download: {os.path.basename(original_path)}"
31
+
32
+ elif action == "zip":
33
+ # Create zip file
34
+ base_name = os.path.splitext(os.path.basename(original_path))[0]
35
+ zip_path = os.path.join(temp_dir, f"{base_name}.zip")
36
+
37
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
38
+ zipf.write(original_path, os.path.basename(original_path))
39
+
40
+ return zip_path, f"✅ Zipped as: {os.path.basename(zip_path)}"
41
+
42
+ elif action == "zip_with_password":
43
+ # Create password-protected zip (requires pyminizip)
44
+ try:
45
+ import pyminizip
46
+ base_name = os.path.splitext(os.path.basename(original_path))[0]
47
+ zip_path = os.path.join(temp_dir, f"{base_name}_protected.zip")
48
+
49
+ # Compress with password
50
+ pyminizip.compress(
51
+ original_path,
52
+ None,
53
+ zip_path,
54
+ "password123", # Default password
55
+ 5 # Compression level
56
+ )
57
+ return zip_path, f"✅ Password-protected zip created (password: password123)"
58
+
59
+ except ImportError:
60
+ return None, "❌ pyminizip not installed. Install with: pip install pyminizip"
61
+
62
+ else:
63
+ return None, "❌ Invalid action selected"
64
+
65
+ except Exception as e:
66
+ return None, f"❌ Error: {str(e)}"
67
+
68
+ def process_multiple_files(files, action):
69
+ """
70
+ Process multiple uploaded files
71
+ """
72
+ if not files:
73
+ return None, "❌ No files uploaded"
74
+
75
+ try:
76
+ temp_dir = tempfile.mkdtemp()
77
+
78
+ if action == "individual":
79
+ # Create zip containing all files
80
+ zip_path = os.path.join(temp_dir, "files.zip")
81
+
82
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
83
+ for file_obj in files:
84
+ file_path = file_obj.name
85
+ zipf.write(file_path, os.path.basename(file_path))
86
+
87
+ return zip_path, f"✅ Created zip with {len(files)} files"
88
+
89
+ elif action == "separate":
90
+ # Create separate zips for each file
91
+ zip_path = os.path.join(temp_dir, "separate_files.zip")
92
+
93
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
94
+ for file_obj in files:
95
+ file_path = file_obj.name
96
+ # Create individual zip first
97
+ individual_zip = os.path.join(temp_dir, f"{os.path.splitext(os.path.basename(file_path))[0]}.zip")
98
+ with zipfile.ZipFile(individual_zip, 'w', zipfile.ZIP_DEFLATED) as ind_zip:
99
+ ind_zip.write(file_path, os.path.basename(file_path))
100
+ # Add individual zip to main zip
101
+ zipf.write(individual_zip, os.path.basename(individual_zip))
102
+
103
+ return zip_path, f"✅ Created zip with {len(files)} individual zips"
104
+
105
+ else:
106
+ return None, "❌ Invalid action selected"
107
+
108
+ except Exception as e:
109
+ return None, f"❌ Error: {str(e)}"
110
+
111
+ def get_file_info(file_obj):
112
+ """Get information about uploaded file"""
113
+ if not file_obj:
114
+ return "No file selected"
115
+
116
+ try:
117
+ file_path = file_obj.name
118
+ file_size = os.path.getsize(file_path)
119
+ file_ext = os.path.splitext(file_path)[1].lower()
120
+
121
+ # Get file type
122
+ mime_type, _ = mimetypes.guess_type(file_path)
123
+ file_type = mime_type.split('/')[0] if mime_type else "Unknown"
124
+
125
+ info = f"""
126
+ 📄 **File Information:**
127
+
128
+ **Name:** {os.path.basename(file_path)}
129
+ **Size:** {file_size:,} bytes ({file_size/1024:.2f} KB)
130
+ **Extension:** {file_ext}
131
+ **Type:** {file_type}
132
+ **Path:** {file_path}
133
+ """
134
+
135
+ return info
136
+
137
+ except Exception as e:
138
+ return f"❌ Error getting file info: {str(e)}"
139
+
140
+ # Create Gradio interface
141
+ with gr.Blocks(title="File Upload & Download Manager", theme=gr.themes.Soft()) as iface:
142
+
143
+ gr.Markdown("# 📁 File Upload & Download Manager")
144
+ gr.Markdown("Upload files and download them as original or zipped")
145
+
146
+ with gr.Tabs():
147
+ # Single File Tab
148
+ with gr.Tab("Single File"):
149
+ with gr.Row():
150
+ with gr.Column(scale=2):
151
+ single_file = gr.File(
152
+ label="Upload a File",
153
+ file_count="single"
154
+ )
155
+
156
+ file_name_input = gr.Textbox(
157
+ label="Custom Filename (optional)",
158
+ placeholder="Leave empty to keep original name...",
159
+ info="Enter a custom name for the downloaded file"
160
+ )
161
+
162
+ single_action = gr.Radio(
163
+ choices=[
164
+ ("Download Original", "original"),
165
+ ("Download as ZIP", "zip"),
166
+ ("Password-protected ZIP", "zip_with_password")
167
+ ],
168
+ label="Select Action",
169
+ value="original"
170
+ )
171
+
172
+ single_btn = gr.Button("Process File", variant="primary")
173
+
174
+ with gr.Column(scale=1):
175
+ file_info = gr.Markdown(label="File Information")
176
+ single_status = gr.Textbox(label="Status", interactive=False)
177
+ single_download = gr.File(label="Download Processed File", interactive=False)
178
+
179
+ # Update file info when file is uploaded
180
+ single_file.change(
181
+ fn=get_file_info,
182
+ inputs=[single_file],
183
+ outputs=file_info
184
+ )
185
+
186
+ # Multiple Files Tab
187
+ with gr.Tab("Multiple Files"):
188
+ with gr.Row():
189
+ with gr.Column(scale=2):
190
+ multi_files = gr.File(
191
+ label="Upload Multiple Files",
192
+ file_count="multiple",
193
+ file_types=["image", "video", "audio", "text", "pdf", ".zip"]
194
+ )
195
+
196
+ multi_action = gr.Radio(
197
+ choices=[
198
+ ("Combine all files into one ZIP", "individual"),
199
+ ("Create separate ZIPs for each file", "separate")
200
+ ],
201
+ label="Select Action",
202
+ value="individual"
203
+ )
204
+
205
+ multi_btn = gr.Button("Process Files", variant="primary")
206
+
207
+ with gr.Column(scale=1):
208
+ multi_status = gr.Textbox(label="Status", interactive=False)
209
+ multi_download = gr.File(label="Download Processed Files", interactive=False)
210
+
211
+ # Batch Processing Tab
212
+ with gr.Tab("Batch Processing"):
213
+ with gr.Row():
214
+ with gr.Column():
215
+ gr.Markdown("### Upload Multiple Files for Batch Processing")
216
+
217
+ batch_files = gr.File(
218
+ label="Upload Files",
219
+ file_count="multiple",
220
+ file_types=None # All file types
221
+ )
222
+
223
+ batch_options = gr.CheckboxGroup(
224
+ choices=[
225
+ "Create individual ZIPs",
226
+ "Create combined ZIP",
227
+ "Rename with timestamp",
228
+ "Add to existing ZIP"
229
+ ],
230
+ label="Processing Options",
231
+ value=["Create combined ZIP"]
232
+ )
233
+
234
+ with gr.Row():
235
+ batch_format = gr.Dropdown(
236
+ choices=[".zip", ".7z", ".tar.gz"],
237
+ value=".zip",
238
+ label="Archive Format"
239
+ )
240
+
241
+ compression_level = gr.Slider(
242
+ minimum=1,
243
+ maximum=9,
244
+ value=6,
245
+ step=1,
246
+ label="Compression Level"
247
+ )
248
+
249
+ batch_btn = gr.Button("Process Batch", variant="primary", size="lg")
250
+
251
+ with gr.Column():
252
+ batch_status = gr.Textbox(label="Status", interactive=False, lines=3)
253
+ batch_download = gr.File(label="Download Results", interactive=False)
254
+
255
+ # Instructions
256
+ with gr.Accordion("📖 Instructions & Features", open=False):
257
+ gr.Markdown("""
258
+ ## How to Use:
259
+
260
+ ### Single File Tab:
261
+ 1. **Upload** a single file
262
+ 2. (Optional) Enter a custom filename
263
+ 3. **Choose action**: Download original, as ZIP, or password-protected ZIP
264
+ 4. Click **Process File**
265
+
266
+ ### Multiple Files Tab:
267
+ 1. **Upload** multiple files (Ctrl+Click to select multiple)
268
+ 2. **Choose action**: Combine into one ZIP or create separate ZIPs
269
+ 3. Click **Process Files**
270
+
271
+ ### Batch Processing Tab:
272
+ 1. **Upload** multiple files
273
+ 2. **Select processing options**
274
+ 3. Choose archive format and compression level
275
+ 4. Click **Process Batch**
276
+
277
+ ## Features:
278
+ - ✅ Single file upload and download
279
+ - ✅ Multiple file upload and batch processing
280
+ - ✅ ZIP file creation with compression
281
+ - ✅ Password-protected ZIPs (requires pyminizip)
282
+ - ✅ File information display
283
+ - ✅ Custom filename support
284
+ - ✅ Multiple archive formats
285
+
286
+ ## Notes:
287
+ - Files are processed in temporary storage
288
+ - Original files are not modified
289
+ - Large files may take time to process
290
+ - Password for protected ZIPs: `password123`
291
+ """)
292
+
293
+ # Connect events
294
+ single_btn.click(
295
+ fn=process_file,
296
+ inputs=[single_file, file_name_input, single_action],
297
+ outputs=[single_download, single_status]
298
+ )
299
+
300
+ multi_btn.click(
301
+ fn=process_multiple_files,
302
+ inputs=[multi_files, multi_action],
303
+ outputs=[multi_download, multi_status]
304
+ )
305
+
306
+ # Batch processing function
307
+ def process_batch(files, options, format_type, compression):
308
+ if not files:
309
+ return None, "❌ No files uploaded"
310
+
311
+ try:
312
+ temp_dir = tempfile.mkdtemp()
313
+ results = []
314
+
315
+ # Process based on options
316
+ if "Create combined ZIP" in options:
317
+ zip_name = f"combined{format_type}"
318
+ zip_path = os.path.join(temp_dir, zip_name)
319
+
320
+ if format_type == ".zip":
321
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=compression) as zipf:
322
+ for file_obj in files:
323
+ file_path = file_obj.name
324
+ arcname = os.path.basename(file_path)
325
+ if "Rename with timestamp" in options:
326
+ import time
327
+ name, ext = os.path.splitext(arcname)
328
+ arcname = f"{name}_{int(time.time())}{ext}"
329
+ zipf.write(file_path, arcname)
330
+ results.append(zip_path)
331
+
332
+ if "Create individual ZIPs" in options:
333
+ for file_obj in files:
334
+ file_path = file_obj.name
335
+ base_name = os.path.splitext(os.path.basename(file_path))[0]
336
+ if "Rename with timestamp" in options:
337
+ import time
338
+ base_name = f"{base_name}_{int(time.time())}"
339
+
340
+ individual_zip = os.path.join(temp_dir, f"{base_name}{format_type}")
341
+
342
+ if format_type == ".zip":
343
+ with zipfile.ZipFile(individual_zip, 'w', zipfile.ZIP_DEFLATED, compresslevel=compression) as zipf:
344
+ zipf.write(file_path, os.path.basename(file_path))
345
+
346
+ results.append(individual_zip)
347
+
348
+ # If multiple results, create a final zip
349
+ if len(results) > 1:
350
+ final_zip = os.path.join(temp_dir, f"batch_results{format_type}")
351
+ with zipfile.ZipFile(final_zip, 'w', zipfile.ZIP_DEFLATED) as zipf:
352
+ for result in results:
353
+ zipf.write(result, os.path.basename(result))
354
+ output_file = final_zip
355
+ elif results:
356
+ output_file = results[0]
357
+ else:
358
+ return None, "⚠️ No processing options selected"
359
+
360
+ return output_file, f"✅ Processed {len(files)} file(s) with {len(options)} option(s)"
361
+
362
+ except Exception as e:
363
+ return None, f"❌ Error: {str(e)}"
364
+
365
+ batch_btn.click(
366
+ fn=process_batch,
367
+ inputs=[batch_files, batch_options, batch_format, compression_level],
368
+ outputs=[batch_download, batch_status]
369
+ )
370
+
371
+ # Launch the app
372
+ if __name__ == "__main__":
373
+ iface.launch(
374
+ server_name="127.0.0.1",
375
+ server_port=7861, # Different port to avoid conflict
376
+ show_error=True,
377
+ share=False,
378
+ ssr_mode=False
379
+ )
full.py ADDED
@@ -0,0 +1,843 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ import requests
6
+ import json
7
+ import uuid
8
+ import time
9
+ import threading
10
+ import socket
11
+ import mimetypes
12
+ from datetime import datetime
13
+ from dotenv import load_dotenv
14
+ from urllib.parse import urlparse, parse_qs
15
+
16
+ # Load environment variables
17
+ load_dotenv()
18
+
19
+ # ============================================
20
+ # CONFIGURATION
21
+ # ============================================
22
+
23
+ SUNO_API_KEY = os.environ.get("SunoKey", "")
24
+ FIXED_CALLBACK_URL = "https://1hit.no/cover/cb.php"
25
+ SUNO_API_URL = "https://api.sunoapi.org/api/v1/generate/upload-cover"
26
+ LOGS_URL = "https://www.1hit.no/cover/logs.php"
27
+
28
+ # Create uploads directory
29
+ UPLOADS_FOLDER = "uploads"
30
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
31
+
32
+ # ============================================
33
+ # ENVIRONMENT DETECTION
34
+ # ============================================
35
+
36
+ def get_base_url():
37
+ """Determine base URL based on environment"""
38
+ # Check if running on Hugging Face
39
+ if os.environ.get("SPACE_ID"):
40
+ return f"https://{os.environ.get('SPACE_ID')}.hf.space"
41
+
42
+ # Check if running on 1hit.no
43
+ hostname = socket.gethostname()
44
+ if '1hit' in hostname or '1hit.no' in os.environ.get('HOSTNAME', ''):
45
+ return "https://www.1hit.no"
46
+
47
+ # Local development
48
+ return "http://localhost:7860"
49
+
50
+ BASE_URL = get_base_url()
51
+
52
+ # ============================================
53
+ # FILE SERVING UTILITY
54
+ # ============================================
55
+
56
+ def get_public_url(file_obj):
57
+ """Generate a publicly accessible URL for uploaded file"""
58
+ if not file_obj:
59
+ return None, None, None
60
+
61
+ try:
62
+ if isinstance(file_obj, dict):
63
+ file_path = file_obj["path"]
64
+ file_name = file_obj["name"]
65
+ else:
66
+ file_path = file_obj.name
67
+ file_name = os.path.basename(file_path)
68
+
69
+ # Save to persistent location with timestamp
70
+ timestamp = int(time.time())
71
+ safe_name = file_name.replace(" ", "_").replace("/", "_")
72
+ filename = f"{timestamp}_{safe_name}"
73
+ dest_path = os.path.join(UPLOADS_FOLDER, filename)
74
+
75
+ # Copy file
76
+ shutil.copy2(file_path, dest_path)
77
+
78
+ # Return Gradio file URL and full URL
79
+ gradio_url = f"/file={dest_path}"
80
+ full_url = f"{BASE_URL}{gradio_url}"
81
+
82
+ return full_url, dest_path, file_name
83
+
84
+ except Exception as e:
85
+ print(f"Error serving file: {e}")
86
+ return None, None, None
87
+
88
+ # ============================================
89
+ # URL VALIDATION
90
+ # ============================================
91
+
92
+ def validate_url(url):
93
+ """Check if URL is accessible"""
94
+ if not url:
95
+ return False, "❌ Empty URL"
96
+
97
+ try:
98
+ # Quick HEAD request
99
+ response = requests.head(url, timeout=5, allow_redirects=True)
100
+ if response.status_code < 400:
101
+ return True, f"✅ URL accessible (HTTP {response.status_code})"
102
+ else:
103
+ return False, f"❌ URL returned HTTP {response.status_code}"
104
+ except requests.exceptions.Timeout:
105
+ return False, "❌ URL timeout - server not responding"
106
+ except requests.exceptions.ConnectionError:
107
+ return False, "❌ Cannot connect to URL - check address"
108
+ except Exception as e:
109
+ return False, f"❌ Error: {str(e)[:50]}"
110
+
111
+ # ============================================
112
+ # FILE CLEANUP UTILITY (Optional)
113
+ # ============================================
114
+
115
+ def cleanup_old_files(max_age_hours=24):
116
+ """Delete files older than max_age_hours"""
117
+ while True:
118
+ try:
119
+ now = time.time()
120
+ for filename in os.listdir(UPLOADS_FOLDER):
121
+ filepath = os.path.join(UPLOADS_FOLDER, filename)
122
+ if os.path.isfile(filepath):
123
+ file_age = now - os.path.getctime(filepath)
124
+ if file_age > max_age_hours * 3600:
125
+ os.remove(filepath)
126
+ print(f"Cleaned up: {filename}")
127
+ except Exception as e:
128
+ print(f"Cleanup error: {e}")
129
+
130
+ # Run every hour
131
+ time.sleep(3600)
132
+
133
+ # Start cleanup thread (uncomment if needed)
134
+ # cleanup_thread = threading.Thread(target=cleanup_old_files, daemon=True)
135
+ # cleanup_thread.start()
136
+
137
+ # ============================================
138
+ # PAGE 1: FILE UPLOAD
139
+ # ============================================
140
+
141
+ def upload_file(file_obj, custom_name):
142
+ """Simple file upload - returns file for download"""
143
+ if not file_obj:
144
+ return None, "❌ Please upload a file first"
145
+
146
+ try:
147
+ # Handle Gradio 6+ file object
148
+ if isinstance(file_obj, dict):
149
+ file_path = file_obj["path"]
150
+ original_name = file_obj["name"]
151
+ else:
152
+ file_path = file_obj.name
153
+ original_name = os.path.basename(file_path)
154
+
155
+ # Create temp directory
156
+ temp_dir = tempfile.mkdtemp()
157
+
158
+ # Determine filename with timestamp
159
+ if custom_name and custom_name.strip():
160
+ ext = os.path.splitext(original_name)[1]
161
+ base_name = custom_name.strip()
162
+ if not base_name.endswith(ext):
163
+ final_name = base_name + ext
164
+ else:
165
+ final_name = base_name
166
+ else:
167
+ final_name = original_name
168
+
169
+ # Add timestamp
170
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
171
+ base, ext = os.path.splitext(final_name)
172
+ timestamped_name = f"{base}_{timestamp}{ext}"
173
+
174
+ # Copy file
175
+ final_path = os.path.join(temp_dir, timestamped_name)
176
+ shutil.copy2(file_path, final_path)
177
+
178
+ return final_path, f"✅ Ready: {timestamped_name}"
179
+
180
+ except Exception as e:
181
+ return None, f"❌ Error: {str(e)}"
182
+
183
+ def get_file_info(file_obj):
184
+ """Display file information"""
185
+ if not file_obj:
186
+ return "📁 No file selected"
187
+
188
+ try:
189
+ if isinstance(file_obj, dict):
190
+ file_path = file_obj["path"]
191
+ file_name = file_obj["name"]
192
+ else:
193
+ file_path = file_obj.name
194
+ file_name = os.path.basename(file_path)
195
+
196
+ size = os.path.getsize(file_path)
197
+ return f"📄 **{file_name}**\n• Size: {size/1024:.1f} KB\n• Type: {os.path.splitext(file_name)[1]}"
198
+ except:
199
+ return "📁 File selected"
200
+
201
+ # ============================================
202
+ # PAGE 2: COVER CREATION - UPDATED WITH REAL URLS
203
+ # ============================================
204
+
205
+ def generate_cover(
206
+ prompt,
207
+ title,
208
+ style,
209
+ upload_url_type,
210
+ custom_upload_url,
211
+ uploaded_file,
212
+ instrumental=True,
213
+ model="V4_5ALL",
214
+ persona_id="",
215
+ negative_tags="",
216
+ vocal_gender="m",
217
+ style_weight=0.65,
218
+ weirdness_constraint=0.65,
219
+ audio_weight=0.65,
220
+ custom_mode=True
221
+ ):
222
+ """Generate cover using Suno API with real URLs"""
223
+
224
+ # Check API key
225
+ if not SUNO_API_KEY:
226
+ return "❌ Suno API key not found. Please set 'SunoKey' environment variable."
227
+
228
+ # Better error message for auth
229
+ if SUNO_API_KEY == "your_api_key_here":
230
+ return "❌ Please set your actual Suno API key in the .env file"
231
+
232
+ # Determine upload URL
233
+ if upload_url_type == "uploaded":
234
+ # Use uploaded file with real URL
235
+ if not uploaded_file:
236
+ return "❌ Please upload a file first in Page 1"
237
+
238
+ # Get public URL for the uploaded file
239
+ public_url, saved_path, orig_name = get_public_url(uploaded_file)
240
+
241
+ if public_url:
242
+ upload_url = public_url
243
+ url_source = f"Uploaded file: {orig_name}"
244
+ else:
245
+ # Fallback to placeholder
246
+ upload_url = f"https://storage.temp.example.com/uploads/{int(time.time())}_{orig_name}"
247
+ url_source = "⚠️ Using placeholder URL (file not publicly accessible)"
248
+
249
+ elif upload_url_type == "custom":
250
+ upload_url = custom_upload_url.strip()
251
+ if not upload_url:
252
+ return "❌ Please provide a custom upload URL"
253
+
254
+ # Quick validation
255
+ is_valid, msg = validate_url(upload_url)
256
+ if not is_valid and "timeout" not in msg: # Allow timeouts as they might still work
257
+ return f"❌ {msg}"
258
+
259
+ url_source = f"Custom URL: {upload_url}"
260
+ else: # auto
261
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
262
+ unique_id = str(uuid.uuid4())[:8]
263
+ upload_url = f"https://storage.temp.example.com/uploads/{timestamp}_{unique_id}.mp3"
264
+ url_source = "⚠️ Auto-generated URL (may not be accessible)"
265
+
266
+ # Prepare payload
267
+ payload = {
268
+ "uploadUrl": upload_url,
269
+ "customMode": custom_mode,
270
+ "instrumental": instrumental,
271
+ "model": model,
272
+ "callBackUrl": FIXED_CALLBACK_URL,
273
+ "prompt": prompt,
274
+ "style": style,
275
+ "title": title,
276
+ "personaId": persona_id,
277
+ "negativeTags": negative_tags,
278
+ "vocalGender": vocal_gender,
279
+ "styleWeight": style_weight,
280
+ "weirdnessConstraint": weirdness_constraint,
281
+ "audioWeight": audio_weight
282
+ }
283
+
284
+ # Remove empty fields
285
+ payload = {k: v for k, v in payload.items() if v not in ["", None]}
286
+
287
+ # Headers
288
+ headers = {
289
+ "Authorization": f"Bearer {SUNO_API_KEY}",
290
+ "Content-Type": "application/json"
291
+ }
292
+
293
+ try:
294
+ # Make API request
295
+ response = requests.post(SUNO_API_URL, json=payload, headers=headers)
296
+
297
+ if response.status_code == 200:
298
+ result = response.json()
299
+ if result.get("code") == 200:
300
+ task_id = result.get("data", {}).get("taskId", "Unknown")
301
+ return f"""✅ **Generation Started!**
302
+
303
+ **Task ID:** `{task_id}`
304
+
305
+ **URL Source:** {url_source}
306
+ **Upload URL:** {upload_url}
307
+ **Callback URL:** {FIXED_CALLBACK_URL}
308
+
309
+ 📋 **Full Response:**
310
+ ```json
311
+ {json.dumps(result, indent=2)}
312
+ ```"""
313
+ else:
314
+ return f"""❌ **API Error:** {result.get('msg', 'Unknown error')}
315
+
316
+ 📋 **Response:**
317
+ ```json
318
+ {json.dumps(result, indent=2)}
319
+ ```"""
320
+ elif response.status_code == 401:
321
+ return f"""❌ **Authentication Failed (HTTP 401)**
322
+
323
+ Your API key appears to be invalid. Please check your SunoKey environment variable.
324
+
325
+ Current key: {SUNO_API_KEY[:5]}...{SUNO_API_KEY[-5:] if len(SUNO_API_KEY) > 10 else ''}"""
326
+ elif response.status_code == 429:
327
+ return "❌ **Rate Limit Exceeded (HTTP 429)**\n\nPlease wait a few minutes and try again."
328
+ else:
329
+ return f"""❌ **HTTP Error {response.status_code}**
330
+
331
+ {response.text}"""
332
+
333
+ except Exception as e:
334
+ return f"❌ **Error:** {str(e)}"
335
+
336
+ # ============================================
337
+ # PAGE 3: CHECK TASK - ORIGINAL CODE WITH IMPROVED ERRORS
338
+ # ============================================
339
+
340
+ def get_task_info(task_id):
341
+ """Manually check any Suno task status - ORIGINAL CODE with better errors"""
342
+ if not task_id:
343
+ return "❌ Please enter a Task ID ❌"
344
+
345
+ if not SUNO_API_KEY:
346
+ return "❌ Suno API key not configured"
347
+
348
+ try:
349
+ resp = requests.get(
350
+ "https://api.sunoapi.org/api/v1/generate/record-info",
351
+ headers={"Authorization": f"Bearer {SUNO_API_KEY}"},
352
+ params={"taskId": task_id},
353
+ timeout=30
354
+ )
355
+
356
+ if resp.status_code == 401:
357
+ return "❌ Authentication failed - Invalid API key"
358
+ elif resp.status_code == 404:
359
+ return f"❌ Task ID '{task_id}' not found"
360
+ elif resp.status_code == 429:
361
+ return "❌ Rate limit exceeded - Try again later"
362
+ elif resp.status_code != 200:
363
+ return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}"
364
+
365
+ data = resp.json()
366
+
367
+ # Format the response for display
368
+ output = f"## 🔍 Task Status: `{task_id}`\n\n"
369
+
370
+ if data.get("code") == 200:
371
+ task_data = data.get("data", {})
372
+ status = task_data.get("status", "UNKNOWN")
373
+
374
+ output += f"**Status:** {status}\n"
375
+ output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
376
+ output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
377
+ output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
378
+
379
+ if status == "SUCCESS":
380
+ response_data = task_data.get("response", {})
381
+
382
+ # Try to parse response (could be string or dict)
383
+ if isinstance(response_data, str):
384
+ try:
385
+ response_data = json.loads(response_data)
386
+ except:
387
+ output += f"\n**Raw Response:**\n```\n{response_data}\n```\n"
388
+ response_data = {}
389
+
390
+ # Check for song data
391
+ songs = []
392
+ if isinstance(response_data, dict):
393
+ songs = response_data.get("sunoData", [])
394
+ if not songs:
395
+ songs = response_data.get("data", [])
396
+ elif isinstance(response_data, list):
397
+ songs = response_data
398
+
399
+ if songs:
400
+ output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
401
+
402
+ for i, song in enumerate(songs, 1):
403
+ if isinstance(song, dict):
404
+ output += f"### Song {i}\n"
405
+ output += f"**Title:** {song.get('title', 'Untitled')}\n"
406
+ output += f"**ID:** `{song.get('id', 'N/A')}`\n"
407
+
408
+ # Audio URLs
409
+ audio_url = song.get('audioUrl') or song.get('audio_url')
410
+ stream_url = song.get('streamUrl') or song.get('stream_url')
411
+ download_url = song.get('downloadUrl') or song.get('download_url')
412
+
413
+ if audio_url:
414
+ output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
415
+ elif stream_url:
416
+ output += f"**Stream:** [Play]({stream_url})\n"
417
+
418
+ if download_url:
419
+ output += f"**Download:** [MP3]({download_url})\n"
420
+
421
+ # Audio player
422
+ play_url = audio_url or stream_url
423
+ if play_url:
424
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
425
+ <source src="{play_url}" type="audio/mpeg">
426
+ Your browser does not support audio.
427
+ </audio>\n"""
428
+
429
+ output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
430
+ output += f"**Duration:** {song.get('duration', 'N/A')}s\n"
431
+ output += f"**Created:** {song.get('createTime', 'N/A')}\n\n"
432
+ output += "---\n\n"
433
+ else:
434
+ output += "\n**No song data found in response.**\n"
435
+
436
+ elif status == "FAILED":
437
+ error_msg = task_data.get("errorMessage", "Unknown error")
438
+ output += f"\n**Error:** {error_msg}\n"
439
+
440
+ elif status in ["PENDING", "PROCESSING", "RUNNING"]:
441
+ output += f"\n**Task is still processing...**\n"
442
+ output += f"Check again in 30 seconds.\n"
443
+
444
+ else:
445
+ output += f"\n**Unknown status:** {status}\n"
446
+
447
+ else:
448
+ output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
449
+
450
+ # Show raw JSON for debugging
451
+ output += "\n## 📋 Raw Response\n"
452
+ output += f"```json\n{json.dumps(data, indent=2)}\n```"
453
+
454
+ return output
455
+
456
+ except requests.exceptions.Timeout:
457
+ return "❌ Request timeout - API may be slow or unavailable"
458
+ except requests.exceptions.ConnectionError:
459
+ return "❌ Connection error - Cannot reach Suno API"
460
+ except Exception as e:
461
+ return f"❌ Error checking task: {str(e)}"
462
+
463
+ # ============================================
464
+ # URL PARAMETER HANDLING
465
+ # ============================================
466
+
467
+ def parse_url_params(request: gr.Request):
468
+ """Parse taskid from URL parameters"""
469
+ task_id = None
470
+ if request:
471
+ try:
472
+ query_params = parse_qs(urlparse(request.request.url).query)
473
+ if 'taskid' in query_params:
474
+ task_id = query_params['taskid'][0]
475
+ # Remove any whitespace
476
+ task_id = task_id.strip()
477
+ except Exception as e:
478
+ print(f"Error parsing URL params: {e}")
479
+
480
+ return task_id
481
+
482
+ def on_page_load(request: gr.Request):
483
+ """Handle URL parameters when page loads"""
484
+ task_id = parse_url_params(request)
485
+
486
+ if task_id:
487
+ # We have a task ID from URL, return it and fetch results
488
+ task_result = get_task_info(task_id)
489
+ return (
490
+ task_id, # For check_task_id
491
+ task_result, # For check_output
492
+ gr.Tabs(selected="tab_check"), # Switch to check tab
493
+ True # Mark as loaded
494
+ )
495
+ else:
496
+ # No task ID in URL, stay on first tab
497
+ return (
498
+ "", # Empty check_task_id
499
+ "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.", # Default message
500
+ gr.Tabs(selected="tab_upload"), # Stay on upload tab
501
+ True # Mark as loaded
502
+ )
503
+
504
+ # ============================================
505
+ # BUILD THE APP
506
+ # ============================================
507
+
508
+ # Custom CSS
509
+ css = """
510
+ .status-badge {
511
+ padding: 5px 10px;
512
+ border-radius: 20px;
513
+ font-weight: bold;
514
+ }
515
+ .api-status-ok { background-color: #d4edda; color: #155724; }
516
+ .api-status-missing { background-color: #f8d7da; color: #721c24; }
517
+ audio { width: 100%; margin: 10px 0; }
518
+ .logs-iframe {
519
+ width: 100%;
520
+ height: 600px;
521
+ border: 1px solid #ddd;
522
+ border-radius: 5px;
523
+ }
524
+ """
525
+
526
+ # Build interface
527
+ with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app:
528
+
529
+ gr.Markdown("""
530
+ <div style="text-align: center; margin-bottom: 20px;">
531
+ <h1>🎵 Suno Cover Creator</h1>
532
+ <p>Upload → Create Cover → Check Status → View Logs</p>
533
+ </div>
534
+ """)
535
+
536
+ # API Status Banner
537
+ api_status_color = "✅ API Key: Loaded" if SUNO_API_KEY and SUNO_API_KEY != "your_api_key_here" else "❌ API Key: Not Found"
538
+ api_status_class = "api-status-ok" if SUNO_API_KEY and SUNO_API_KEY != "your_api_key_here" else "api-status-missing"
539
+
540
+ with gr.Row():
541
+ gr.Markdown(f"""
542
+ <div class="{api_status_class}" style="padding: 10px; border-radius: 5px; text-align: center;">
543
+ {api_status_color} | Base URL: <code>{BASE_URL}</code>
544
+ </div>
545
+ """)
546
+
547
+ # State for initial load tracking
548
+ initial_load_done = gr.State(value=False)
549
+
550
+ # State for uploaded file info
551
+ uploaded_file_state = gr.State()
552
+
553
+ # Main tabs
554
+ with gr.Tabs() as tabs:
555
+ # ========== PAGE 1: FILE UPLOAD ==========
556
+ with gr.TabItem("📤 1. Upload File", id="tab_upload"):
557
+ with gr.Row():
558
+ with gr.Column():
559
+ file_input = gr.File(
560
+ label="Upload Your Audio File",
561
+ file_count="single",
562
+ file_types=["audio", ".mp3", ".wav", ".m4a", ".flac", ".ogg"]
563
+ )
564
+
565
+ file_info = gr.Markdown("📁 No file selected")
566
+
567
+ custom_filename = gr.Textbox(
568
+ label="Custom Filename (optional)",
569
+ placeholder="my-audio-file",
570
+ info="Extension preserved automatically"
571
+ )
572
+
573
+ upload_btn = gr.Button("⚡ Prepare File", variant="primary", size="lg")
574
+
575
+ upload_status = gr.Textbox(label="Status", interactive=False)
576
+ upload_download = gr.File(label="Download File", interactive=False)
577
+
578
+ # ========== PAGE 2: COVER CREATION ==========
579
+ with gr.TabItem("🎨 2. Create Cover", id="tab_create"):
580
+ with gr.Row():
581
+ with gr.Column(scale=1):
582
+ gr.Markdown("### 🎵 Cover Details")
583
+
584
+ cover_prompt = gr.Textbox(
585
+ label="Prompt",
586
+ value="A dramatic orchestral cover with dark undertones",
587
+ lines=2
588
+ )
589
+
590
+ cover_title = gr.Textbox(
591
+ label="Title",
592
+ value="The Fool's Ascension"
593
+ )
594
+
595
+ cover_style = gr.Textbox(
596
+ label="Style",
597
+ value="Epic Orchestral, Dark Cinematic"
598
+ )
599
+
600
+ # URL Selection
601
+ gr.Markdown("### 🔗 Upload URL")
602
+
603
+ url_type = gr.Radio(
604
+ label="Source",
605
+ choices=[
606
+ ("Use uploaded file", "uploaded"),
607
+ ("Use custom URL", "custom"),
608
+ ("Auto-generate", "auto")
609
+ ],
610
+ value="uploaded",
611
+ info="Choose where the audio comes from"
612
+ )
613
+
614
+ custom_url = gr.Textbox(
615
+ label="Custom URL",
616
+ placeholder="https://example.com/audio.mp3",
617
+ visible=False
618
+ )
619
+
620
+ with gr.Row():
621
+ validate_url_btn = gr.Button("🔍 Validate URL", size="sm", visible=False)
622
+ url_validation_result = gr.Markdown("", visible=False)
623
+
624
+ # Reference to uploaded file from Page 1
625
+ uploaded_file_ref = gr.State()
626
+
627
+ with gr.Column(scale=1):
628
+ gr.Markdown("### ⚙️ Advanced Settings")
629
+
630
+ cover_model = gr.Dropdown(
631
+ label="Model",
632
+ choices=["V4_5ALL", "V5", "V4"],
633
+ value="V4_5ALL"
634
+ )
635
+
636
+ cover_instrumental = gr.Checkbox(
637
+ label="Instrumental",
638
+ value=True
639
+ )
640
+
641
+ cover_vocal = gr.Dropdown(
642
+ label="Vocal Gender",
643
+ choices=["m", "f", "none"],
644
+ value="m"
645
+ )
646
+
647
+ cover_negative = gr.Textbox(
648
+ label="Negative Tags",
649
+ value="Distorted, Low Quality",
650
+ placeholder="What to avoid"
651
+ )
652
+
653
+ # Show/hide custom URL and validate button
654
+ def toggle_custom_url(choice):
655
+ show = choice == "custom"
656
+ return gr.update(visible=show), gr.update(visible=show), gr.update(visible=show)
657
+
658
+ url_type.change(
659
+ toggle_custom_url,
660
+ inputs=url_type,
661
+ outputs=[custom_url, validate_url_btn, url_validation_result]
662
+ )
663
+
664
+ # Validate URL function
665
+ def check_url(url):
666
+ if not url:
667
+ return "❌ Please enter a URL"
668
+ valid, message = validate_url(url)
669
+ return message
670
+
671
+ validate_url_btn.click(
672
+ fn=check_url,
673
+ inputs=[custom_url],
674
+ outputs=[url_validation_result]
675
+ )
676
+
677
+ # Generate button
678
+ generate_btn = gr.Button("🎬 Generate Cover", variant="primary", size="lg")
679
+ generate_output = gr.Markdown("Ready to generate...")
680
+
681
+ # ========== PAGE 3: CHECK TASK ==========
682
+ with gr.TabItem("🔍 3. Check Any Task", id="tab_check"):
683
+ with gr.Row():
684
+ with gr.Column(scale=1):
685
+ gr.Markdown("### Check Task Status")
686
+ gr.Markdown("Enter any Suno Task ID to check its status")
687
+
688
+ check_task_id = gr.Textbox(
689
+ label="Task ID",
690
+ placeholder="Enter Task ID from generation",
691
+ info="From Cover Creator or any Suno API request"
692
+ )
693
+
694
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
695
+ check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
696
+
697
+ # URL parameter info
698
+ gr.Markdown("""
699
+ **Quick access via URL:**
700
+ Add `?taskid=YOUR_TASK_ID` to the URL
701
+
702
+ Example:
703
+ `https://www.1hit.no/cover/?taskid=450bb58b4ada3fb0021d8b38ce1aa5d9`
704
+ """)
705
+
706
+ with gr.Column(scale=2):
707
+ check_output = gr.Markdown(
708
+ value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
709
+ )
710
+
711
+ # ========== PAGE 4: CALLBACK LOGS ==========
712
+ with gr.TabItem("📋 4. Callback Logs", id="tab_logs"):
713
+ gr.Markdown(f"""
714
+ ### 📋 Callback Logs
715
+ View all callback data from Suno API at: [`{LOGS_URL}`]({LOGS_URL})
716
+
717
+ This shows all callbacks received from your cover generations.
718
+ """)
719
+
720
+ # HTML iframe to display the logs page
721
+ gr.HTML(f"""
722
+ <iframe src="{LOGS_URL}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
723
+ """)
724
+
725
+ with gr.Row():
726
+ gr.Markdown("""
727
+ **What you'll see in the logs:**
728
+ - **Filename**: Timestamp and ID of each callback
729
+ - **Task ID**: The Suno task ID for reference
730
+ - **Type**: `complete`, `first`, `text` etc.
731
+ - **Modified**: When the callback was received
732
+ - **Size**: Size of the JSON data
733
+ - **Actions**: Play audio or view raw JSON
734
+ """)
735
+
736
+ refresh_logs_btn = gr.Button("🔄 Refresh Logs", size="sm")
737
+
738
+ def refresh_logs():
739
+ return f"""
740
+ <iframe src="{LOGS_URL}?t={int(time.time())}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
741
+ """
742
+
743
+ logs_iframe = gr.HTML(f"""
744
+ <iframe src="{LOGS_URL}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
745
+ """)
746
+
747
+ refresh_logs_btn.click(
748
+ fn=refresh_logs,
749
+ outputs=logs_iframe
750
+ )
751
+
752
+ # ============================================
753
+ # EVENT HANDLERS
754
+ # ============================================
755
+
756
+ # Page 1: File Upload
757
+ file_input.change(
758
+ fn=get_file_info,
759
+ inputs=[file_input],
760
+ outputs=file_info
761
+ )
762
+
763
+ upload_btn.click(
764
+ fn=upload_file,
765
+ inputs=[file_input, custom_filename],
766
+ outputs=[upload_download, upload_status]
767
+ )
768
+
769
+ # Store uploaded file reference for Page 2
770
+ def store_file_ref(file_obj):
771
+ return file_obj
772
+
773
+ file_input.change(
774
+ fn=store_file_ref,
775
+ inputs=[file_input],
776
+ outputs=uploaded_file_ref
777
+ )
778
+
779
+ # Page 2: Generate Cover
780
+ generate_btn.click(
781
+ fn=generate_cover,
782
+ inputs=[
783
+ cover_prompt, cover_title, cover_style,
784
+ url_type, custom_url, uploaded_file_ref,
785
+ cover_instrumental, cover_model,
786
+ gr.State(""), # persona_id placeholder
787
+ cover_negative, cover_vocal,
788
+ gr.State(0.65), gr.State(0.65), gr.State(0.65), # weights
789
+ gr.State(True) # custom_mode
790
+ ],
791
+ outputs=generate_output
792
+ )
793
+
794
+ # Page 3: Check Task - Original handlers
795
+ def clear_check():
796
+ return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
797
+
798
+ check_clear_btn.click(
799
+ clear_check,
800
+ outputs=[check_task_id, check_output]
801
+ )
802
+
803
+ check_btn.click(
804
+ get_task_info,
805
+ inputs=[check_task_id],
806
+ outputs=check_output
807
+ )
808
+
809
+ # Load URL parameters when the app starts
810
+ app.load(
811
+ fn=on_page_load,
812
+ inputs=[],
813
+ outputs=[check_task_id, check_output, tabs, initial_load_done],
814
+ queue=False
815
+ )
816
+
817
+ # Footer
818
+ gr.Markdown("---")
819
+ gr.Markdown(f"""
820
+ <div style="text-align: center; padding: 20px;">
821
+ <p>🔗 <b>Quick URL Access:</b> Add <code>?taskid=YOUR_TASK_ID</code> to auto-load task</p>
822
+ <p>📞 <b>Callback URL:</b> <code>{FIXED_CALLBACK_URL}</code></p>
823
+ <p>📋 <b>Logs:</b> <a href="{LOGS_URL}" target="_blank">{LOGS_URL}</a></p>
824
+ </div>
825
+ """)
826
+
827
+ # Launch
828
+ if __name__ == "__main__":
829
+ if not SUNO_API_KEY or SUNO_API_KEY == "your_api_key_here":
830
+ print("⚠️ Warning: Suno API key not found or using default")
831
+ print("Set 'SunoKey' environment variable or add to .env file")
832
+
833
+ print("🚀 Starting Suno Cover Creator")
834
+ print(f"🔑 SunoKey: {'✅ Set' if SUNO_API_KEY and SUNO_API_KEY != 'your_api_key_here' else '❌ Not set'}")
835
+ print(f"🌐 Base URL: {BASE_URL}")
836
+ print(f"📁 Uploads folder: {UPLOADS_FOLDER}")
837
+ print("🔗 Use URL parameter: http://localhost:7860/?taskid=YOUR_TASK_ID")
838
+
839
+ app.launch(
840
+ server_name="0.0.0.0",
841
+ server_port=7860,
842
+ share=False
843
+ )
mess.py ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import zipfile
4
+ import tempfile
5
+ import shutil
6
+ from pathlib import Path
7
+ import mimetypes
8
+ import time
9
+ import socket
10
+
11
+ # Create uploads directory
12
+ UPLOADS_FOLDER = "uploads"
13
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
14
+
15
+ def find_available_port(start_port=7860, max_attempts=10):
16
+ """Find an available port starting from start_port"""
17
+ for port in range(start_port, start_port + max_attempts):
18
+ try:
19
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
20
+ s.bind(('', port))
21
+ return port
22
+ except OSError:
23
+ continue
24
+ return start_port # Fallback to start_port if none found
25
+
26
+ def process_file(file_obj, file_name, action):
27
+ """
28
+ Process uploaded file: return original or create zip
29
+ """
30
+ if not file_obj:
31
+ return None, "❌ No file uploaded"
32
+
33
+ try:
34
+ # In Gradio 6+, file_obj is a dictionary
35
+ if isinstance(file_obj, dict):
36
+ file_path = file_obj["path"]
37
+ original_name = file_obj["name"]
38
+ else:
39
+ # Fallback for older versions
40
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
41
+ original_name = os.path.basename(file_path)
42
+
43
+ # Save uploaded file temporarily
44
+ temp_dir = tempfile.mkdtemp()
45
+ custom_name = file_name.strip() if file_name and file_name.strip() else original_name
46
+ original_path = os.path.join(temp_dir, custom_name)
47
+
48
+ # Copy file to temp location
49
+ shutil.copy2(file_path, original_path)
50
+
51
+ if action == "original":
52
+ # Return original file - for Gradio 6+, return a dictionary
53
+ return {
54
+ "path": original_path,
55
+ "name": custom_name
56
+ }, f"✅ Ready to download: {custom_name}"
57
+
58
+ elif action == "zip":
59
+ # Create zip file
60
+ base_name = os.path.splitext(custom_name)[0]
61
+ zip_name = f"{base_name}.zip"
62
+ zip_path = os.path.join(temp_dir, zip_name)
63
+
64
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
65
+ zipf.write(original_path, custom_name)
66
+
67
+ return {
68
+ "path": zip_path,
69
+ "name": zip_name
70
+ }, f"✅ Zipped as: {zip_name}"
71
+
72
+ elif action == "zip_with_password":
73
+ # Create password-protected zip
74
+ try:
75
+ import pyminizip
76
+ base_name = os.path.splitext(custom_name)[0]
77
+ zip_name = f"{base_name}_protected.zip"
78
+ zip_path = os.path.join(temp_dir, zip_name)
79
+
80
+ # Compress with password
81
+ pyminizip.compress(
82
+ original_path,
83
+ None,
84
+ zip_path,
85
+ "password123", # Default password
86
+ 5 # Compression level
87
+ )
88
+ return {
89
+ "path": zip_path,
90
+ "name": zip_name
91
+ }, f"✅ Password-protected zip created (password: password123)"
92
+
93
+ except ImportError:
94
+ return None, "❌ pyminizip not installed. Install with: pip install pyminizip"
95
+
96
+ else:
97
+ return None, "❌ Invalid action selected"
98
+
99
+ except Exception as e:
100
+ return None, f"❌ Error: {str(e)}"
101
+
102
+ def process_multiple_files(files, action):
103
+ """
104
+ Process multiple uploaded files
105
+ """
106
+ if not files:
107
+ return None, "❌ No files uploaded"
108
+
109
+ try:
110
+ temp_dir = tempfile.mkdtemp()
111
+
112
+ if action == "individual":
113
+ # Create zip containing all files
114
+ zip_name = "files.zip"
115
+ zip_path = os.path.join(temp_dir, zip_name)
116
+
117
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
118
+ for file_obj in files:
119
+ # Handle Gradio 6+ file object format
120
+ if isinstance(file_obj, dict):
121
+ file_path = file_obj["path"]
122
+ file_name = file_obj["name"]
123
+ else:
124
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
125
+ file_name = os.path.basename(file_path)
126
+
127
+ zipf.write(file_path, file_name)
128
+
129
+ return {
130
+ "path": zip_path,
131
+ "name": zip_name
132
+ }, f"✅ Created zip with {len(files)} files"
133
+
134
+ elif action == "separate":
135
+ # Create separate zips for each file
136
+ zip_name = "separate_files.zip"
137
+ zip_path = os.path.join(temp_dir, zip_name)
138
+
139
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
140
+ for file_obj in files:
141
+ # Handle Gradio 6+ file object format
142
+ if isinstance(file_obj, dict):
143
+ file_path = file_obj["path"]
144
+ file_name = file_obj["name"]
145
+ else:
146
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
147
+ file_name = os.path.basename(file_path)
148
+
149
+ # Create individual zip first
150
+ base_name = os.path.splitext(file_name)[0]
151
+ individual_zip_name = f"{base_name}.zip"
152
+ individual_zip_path = os.path.join(temp_dir, individual_zip_name)
153
+
154
+ with zipfile.ZipFile(individual_zip_path, 'w', zipfile.ZIP_DEFLATED) as ind_zip:
155
+ ind_zip.write(file_path, file_name)
156
+
157
+ # Add individual zip to main zip
158
+ zipf.write(individual_zip_path, individual_zip_name)
159
+
160
+ return {
161
+ "path": zip_path,
162
+ "name": zip_name
163
+ }, f"✅ Created zip with {len(files)} individual zips"
164
+
165
+ else:
166
+ return None, "❌ Invalid action selected"
167
+
168
+ except Exception as e:
169
+ return None, f"❌ Error: {str(e)}"
170
+
171
+ def get_file_info(file_obj):
172
+ """Get information about uploaded file"""
173
+ if not file_obj:
174
+ return "No file selected"
175
+
176
+ try:
177
+ # Handle Gradio 6+ file object format
178
+ if isinstance(file_obj, dict):
179
+ file_path = file_obj["path"]
180
+ file_name = file_obj["name"]
181
+ else:
182
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
183
+ file_name = os.path.basename(file_path)
184
+
185
+ file_size = os.path.getsize(file_path)
186
+ file_ext = os.path.splitext(file_name)[1].lower()
187
+
188
+ # Get file type
189
+ mime_type, _ = mimetypes.guess_type(file_path)
190
+ file_type = mime_type.split('/')[0] if mime_type else "Unknown"
191
+
192
+ # Get creation and modification times
193
+ ctime = time.ctime(os.path.getctime(file_path))
194
+ mtime = time.ctime(os.path.getmtime(file_path))
195
+
196
+ info = f"""
197
+ 📄 **File Information:**
198
+
199
+ **Name:** {file_name}
200
+ **Size:** {file_size:,} bytes ({file_size/1024:.2f} KB)
201
+ **Extension:** {file_ext}
202
+ **Type:** {file_type}
203
+ **Created:** {ctime}
204
+ **Modified:** {mtime}
205
+ **Path:** {file_path}
206
+ """
207
+
208
+ return info
209
+
210
+ except Exception as e:
211
+ return f"❌ Error getting file info: {str(e)}"
212
+
213
+ # Create Gradio interface
214
+ with gr.Blocks(title="File Upload & Download Manager") as iface:
215
+
216
+ gr.Markdown("# 📁 File Upload & Download Manager")
217
+ gr.Markdown("Upload files and download them as original or zipped")
218
+
219
+ with gr.Tabs():
220
+ # Single File Tab
221
+ with gr.Tab("Single File"):
222
+ with gr.Row():
223
+ with gr.Column(scale=2):
224
+ single_file = gr.File(
225
+ label="Upload a File",
226
+ file_count="single"
227
+ )
228
+
229
+ file_name_input = gr.Textbox(
230
+ label="Custom Filename (optional)",
231
+ placeholder="Leave empty to keep original name...",
232
+ info="Enter a custom name for the downloaded file"
233
+ )
234
+
235
+ single_action = gr.Radio(
236
+ choices=[
237
+ ("Download Original", "original"),
238
+ ("Download as ZIP", "zip"),
239
+ ("Password-protected ZIP", "zip_with_password")
240
+ ],
241
+ label="Select Action",
242
+ value="original"
243
+ )
244
+
245
+ single_btn = gr.Button("Process File", variant="primary")
246
+
247
+ with gr.Column(scale=1):
248
+ file_info = gr.Markdown(label="File Information")
249
+ single_status = gr.Textbox(label="Status", interactive=False)
250
+ single_download = gr.File(label="Download Processed File", interactive=False)
251
+
252
+ # Update file info when file is uploaded
253
+ single_file.change(
254
+ fn=get_file_info,
255
+ inputs=[single_file],
256
+ outputs=file_info
257
+ )
258
+
259
+ # Multiple Files Tab
260
+ with gr.Tab("Multiple Files"):
261
+ with gr.Row():
262
+ with gr.Column(scale=2):
263
+ multi_files = gr.File(
264
+ label="Upload Multiple Files",
265
+ file_count="multiple",
266
+ # Updated file_types format for Gradio 6+
267
+ file_types=[
268
+ "image", "video", "audio",
269
+ "text", ".pdf", ".zip", ".txt",
270
+ ".doc", ".docx", ".xls", ".xlsx"
271
+ ]
272
+ )
273
+
274
+ multi_action = gr.Radio(
275
+ choices=[
276
+ ("Combine all files into one ZIP", "individual"),
277
+ ("Create separate ZIPs for each file", "separate")
278
+ ],
279
+ label="Select Action",
280
+ value="individual"
281
+ )
282
+
283
+ multi_btn = gr.Button("Process Files", variant="primary")
284
+
285
+ with gr.Column(scale=1):
286
+ multi_status = gr.Textbox(label="Status", interactive=False)
287
+ multi_download = gr.File(label="Download Processed Files", interactive=False)
288
+
289
+ # Batch Processing Tab
290
+ with gr.Tab("Batch Processing"):
291
+ with gr.Row():
292
+ with gr.Column():
293
+ gr.Markdown("### Upload Multiple Files for Batch Processing")
294
+
295
+ batch_files = gr.File(
296
+ label="Upload Files",
297
+ file_count="multiple",
298
+ file_types=None # All file types
299
+ )
300
+
301
+ batch_options = gr.CheckboxGroup(
302
+ choices=[
303
+ "Create individual ZIPs",
304
+ "Create combined ZIP",
305
+ "Rename with timestamp",
306
+ "Add to existing ZIP"
307
+ ],
308
+ label="Processing Options",
309
+ value=["Create combined ZIP"]
310
+ )
311
+
312
+ with gr.Row():
313
+ batch_format = gr.Dropdown(
314
+ choices=[".zip", ".7z", ".tar.gz"],
315
+ value=".zip",
316
+ label="Archive Format"
317
+ )
318
+
319
+ compression_level = gr.Slider(
320
+ minimum=1,
321
+ maximum=9,
322
+ value=6,
323
+ step=1,
324
+ label="Compression Level"
325
+ )
326
+
327
+ batch_btn = gr.Button("Process Batch", variant="primary", size="lg")
328
+
329
+ with gr.Column():
330
+ batch_status = gr.Textbox(label="Status", interactive=False, lines=3)
331
+ batch_download = gr.File(label="Download Results", interactive=False)
332
+
333
+ # Instructions
334
+ with gr.Accordion("📖 Instructions & Features", open=False):
335
+ gr.Markdown("""
336
+ ## How to Use:
337
+
338
+ ### Single File Tab:
339
+ 1. **Upload** a single file
340
+ 2. (Optional) Enter a custom filename
341
+ 3. **Choose action**: Download original, as ZIP, or password-protected ZIP
342
+ 4. Click **Process File**
343
+
344
+ ### Multiple Files Tab:
345
+ 1. **Upload** multiple files (Ctrl+Click to select multiple)
346
+ 2. **Choose action**: Combine into one ZIP or create separate ZIPs
347
+ 3. Click **Process Files**
348
+
349
+ ### Batch Processing Tab:
350
+ 1. **Upload** multiple files
351
+ 2. **Select processing options**
352
+ 3. Choose archive format and compression level
353
+ 4. Click **Process Batch**
354
+
355
+ ## Features:
356
+ - ✅ Single file upload and download
357
+ - ✅ Multiple file upload and batch processing
358
+ - ✅ ZIP file creation with compression
359
+ - ✅ Password-protected ZIPs (requires pyminizip)
360
+ - ✅ File information display
361
+ - ✅ Custom filename support
362
+ - ✅ Multiple archive formats
363
+
364
+ ## Notes:
365
+ - Files are processed in temporary storage
366
+ - Original files are not modified
367
+ - Large files may take time to process
368
+ - Password for protected ZIPs: `password123`
369
+ - For Gradio 6+ compatibility, file objects are handled differently
370
+ """)
371
+
372
+ # Connect events
373
+ single_btn.click(
374
+ fn=process_file,
375
+ inputs=[single_file, file_name_input, single_action],
376
+ outputs=[single_download, single_status]
377
+ )
378
+
379
+ multi_btn.click(
380
+ fn=process_multiple_files,
381
+ inputs=[multi_files, multi_action],
382
+ outputs=[multi_download, multi_status]
383
+ )
384
+
385
+ # Batch processing function
386
+ def process_batch(files, options, format_type, compression):
387
+ if not files:
388
+ return None, "❌ No files uploaded"
389
+
390
+ try:
391
+ temp_dir = tempfile.mkdtemp()
392
+ results = []
393
+
394
+ # Process based on options
395
+ if "Create combined ZIP" in options:
396
+ zip_name = f"combined{format_type}"
397
+ zip_path = os.path.join(temp_dir, zip_name)
398
+
399
+ if format_type == ".zip":
400
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=compression) as zipf:
401
+ for file_obj in files:
402
+ # Handle Gradio 6+ file object format
403
+ if isinstance(file_obj, dict):
404
+ file_path = file_obj["path"]
405
+ arcname = file_obj["name"]
406
+ else:
407
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
408
+ arcname = os.path.basename(file_path)
409
+
410
+ if "Rename with timestamp" in options:
411
+ name, ext = os.path.splitext(arcname)
412
+ arcname = f"{name}_{int(time.time())}{ext}"
413
+ zipf.write(file_path, arcname)
414
+
415
+ results.append({
416
+ "path": zip_path,
417
+ "name": zip_name
418
+ })
419
+
420
+ if "Create individual ZIPs" in options:
421
+ for file_obj in files:
422
+ # Handle Gradio 6+ file object format
423
+ if isinstance(file_obj, dict):
424
+ file_path = file_obj["path"]
425
+ file_name = file_obj["name"]
426
+ else:
427
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
428
+ file_name = os.path.basename(file_path)
429
+
430
+ base_name = os.path.splitext(file_name)[0]
431
+ if "Rename with timestamp" in options:
432
+ base_name = f"{base_name}_{int(time.time())}"
433
+
434
+ individual_zip_name = f"{base_name}{format_type}"
435
+ individual_zip_path = os.path.join(temp_dir, individual_zip_name)
436
+
437
+ if format_type == ".zip":
438
+ with zipfile.ZipFile(individual_zip_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=compression) as zipf:
439
+ zipf.write(file_path, file_name)
440
+
441
+ results.append({
442
+ "path": individual_zip_path,
443
+ "name": individual_zip_name
444
+ })
445
+
446
+ # If multiple results, create a final zip
447
+ if len(results) > 1:
448
+ final_zip_name = f"batch_results{format_type}"
449
+ final_zip_path = os.path.join(temp_dir, final_zip_name)
450
+
451
+ with zipfile.ZipFile(final_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
452
+ for result in results:
453
+ zipf.write(result["path"], result["name"])
454
+
455
+ output_file = {
456
+ "path": final_zip_path,
457
+ "name": final_zip_name
458
+ }
459
+ elif results:
460
+ output_file = results[0]
461
+ else:
462
+ return None, "⚠️ No processing options selected"
463
+
464
+ return output_file, f"✅ Processed {len(files)} file(s) with {len(options)} option(s)"
465
+
466
+ except Exception as e:
467
+ return None, f"❌ Error: {str(e)}"
468
+
469
+ batch_btn.click(
470
+ fn=process_batch,
471
+ inputs=[batch_files, batch_options, batch_format, compression_level],
472
+ outputs=[batch_download, batch_status]
473
+ )
474
+
475
+ # Launch the app
476
+ if __name__ == "__main__":
477
+ # Find an available port
478
+ available_port = find_available_port(start_port=7860, max_attempts=20)
479
+
480
+ print(f"Starting server on port {available_port}...")
481
+
482
+ iface.launch(
483
+ server_name="0.0.0.0", # Changed to 0.0.0.0 for better compatibility
484
+ server_port=available_port, # Use dynamically found port
485
+ show_error=True,
486
+ share=False,
487
+ theme=gr.themes.Soft()
488
+ )
p.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ with gr.Blocks() as app:
4
+ gr.HTML("""
5
+ <iframe
6
+ src="https://www.1hit.no/cover/compu.php"
7
+ width="100%"
8
+ height="700"
9
+ style="border:none;">
10
+ </iframe>
11
+ """)
12
+
13
+ if __name__ == "__main__":
14
+ app.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=6.1.0
2
+ pyminizip>=0.2.6 # Optional, for password-protected zips