commit a98a06f7125e788d82b81646582e49809aa7e8b9 Author: Victor Giers Date: Mon Jun 2 07:28:49 2025 +0200 Initial commit (generated by auto-git) diff --git a/README.md b/README.md new file mode 100644 index 0000000..603eb9c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Projekt in image_to_3d diff --git a/cleanup.php b/cleanup.php new file mode 100644 index 0000000..a9e5e02 --- /dev/null +++ b/cleanup.php @@ -0,0 +1,31 @@ + "Wrong password"]); + exit; +} +if (!isset($_POST['filename'])) { + http_response_code(400); + echo json_encode(["error" => "No filename"]); + exit; +} +$file = basename($_POST['filename']); +$file_path = $target_dir . $file; + +if (is_file($file_path) && preg_match('/\.(png|jpg|jpeg)$/i', $file)) { + if (unlink($file_path)) { + echo json_encode(["success" => true]); + } else { + http_response_code(500); + echo json_encode(["error" => "Could not delete file"]); + } +} else { + http_response_code(404); + echo json_encode(["error" => "File not found or wrong extension"]); +} +?> \ No newline at end of file diff --git a/image_to_3d.py b/image_to_3d.py new file mode 100644 index 0000000..7176889 --- /dev/null +++ b/image_to_3d.py @@ -0,0 +1,193 @@ +import sys +import os +os.environ["SYNEXA_API_KEY"] = "YOUR_SYNEXA_API_KEY" +import tempfile +import requests +import synexa +from PIL import Image + +UPLOAD_URL = "https://yourwebsite.com/yourtmpimagefolder/secure_upload.php" +CLEANUP_URL = "https://yourwebsite.com/yourtmpimagefolder/cleanup.php" +UPLOAD_PASSWORD = "YOUR_PASSWORD_AS_DEFINED_IN_PHP" +BASE_URL = "https://www.victorgiers.com/generate3d" +MODEL_NAME = "tencent/hunyuan3d-2" +TIMEOUT = 180 # seconds + +ALLOWED_EXTENSIONS = (".jpg", ".jpeg", ".png") + +def is_native_image(filename): + ext = os.path.splitext(filename)[1].lower() + return ext in ALLOWED_EXTENSIONS + +def convert_to_png(src_path): + img = Image.open(src_path) + fd, tmp_path = tempfile.mkstemp(suffix=".png") + os.close(fd) + img.save(tmp_path, "PNG") + return tmp_path + +def upload_image(image_path): + with open(image_path, 'rb') as f: + files = {'file': (os.path.basename(image_path), f)} + data = {'password': UPLOAD_PASSWORD} + r = requests.post(UPLOAD_URL, files=files, data=data) + try: + resp = r.json() + except Exception as e: + print(f"[UPLOAD] Fehler: {e}") + return None + if "url" in resp: + return resp["url"] + else: + print(f"[UPLOAD] Fehler: {resp}") + return None + +def cleanup_remote_file(filename): + data = {'password': UPLOAD_PASSWORD, 'filename': filename} + try: + r = requests.post(CLEANUP_URL, data=data) + if r.status_code == 200 and r.json().get("success"): + print(f"[CLEANUP] Remote Datei gelöscht: {filename}") + return True + else: + print(f"[CLEANUP] Fehler beim Löschen: {r.text}") + return False + except Exception as e: + print(f"[CLEANUP] Fehler: {e}") + return False + +def notify(title, message): + # macOS Notification über osascript + os.system(f'''osascript -e 'display notification "{message}" with title "{title}"' ''') + +def process_image(img_path): + orig_name = os.path.basename(img_path) + orig_dir = os.path.dirname(os.path.abspath(img_path)) + temp_files = [] + + try: + # 1. Konvertieren falls nötig + if is_native_image(img_path): + upload_path = img_path + upload_name = orig_name + else: + upload_path = convert_to_png(img_path) + temp_files.append(upload_path) + upload_name = os.path.basename(upload_path) + print(f"Konvertiert zu PNG: {upload_path}") + + # 2. Upload + url = upload_image(upload_path) + if not url: + msg = f"Upload von {upload_name} fehlgeschlagen." + print(msg) + notify("3D-Konvertierung Fehler", msg) + return None + print(f"Upload erfolgreich: {url}") + + # 3. Synexa: Model run + base_name, ext = os.path.splitext(upload_name) + if not upload_name.lower().endswith(ALLOWED_EXTENSIONS): + msg = f"Bild {upload_name} ist kein PNG/JPG/JPEG auf dem Server, überspringe Synexa." + print(msg) + cleanup_remote_file(upload_name) + notify("3D-Konvertierung Fehler", msg) + return None + + image_url = f"{BASE_URL}/{upload_name}" + output_filename = os.path.join(orig_dir, f"{base_name}.glb") + + print(f"→ Sende an Synexa: {image_url}") + try: + response_list = synexa.run( + MODEL_NAME, + input={ + "seed": 1234, + "image": image_url, + "steps": 5, + "caption": "", + "shape_only": False, + "guidance_scale": 5.5, + "multiple_views": [], + "check_box_rembg": True, + "octree_resolution": "256" + }, + wait=TIMEOUT + ) + except Exception as e: + msg = f"⚠️ Model run failed for {upload_name}: {e}" + print(msg) + cleanup_remote_file(upload_name) + notify("3D-Konvertierung Fehler", msg) + return None + + # 4. Find .glb + textured_url = None + for fo in response_list: + url = getattr(fo, "url", "") + if url.endswith("textured_mesh.glb"): + textured_url = url + break + + if not textured_url: + msg = f"⚠️ No textured_mesh.glb found in response for {upload_name}" + print(msg) + cleanup_remote_file(upload_name) + notify("3D-Konvertierung Fehler", msg) + return None + + # 5. Download + print(f"↓ Downloading textured mesh → {output_filename}") + try: + dl = requests.get(textured_url, timeout=TIMEOUT) + dl.raise_for_status() + with open(output_filename, "wb") as out_file: + out_file.write(dl.content) + msg = f"3D-Modell gespeichert: {output_filename}" + print(f"✅ {msg}") + notify("3D-Konvertierung Erfolgreich", msg) + except Exception as e: + msg = f"⚠️ Download failed for {upload_name}: {e}" + print(msg) + notify("3D-Konvertierung Fehler", msg) + + # 6. Remote Cleanup + cleanup_remote_file(upload_name) + + # 7. Lokale temp-Dateien löschen + for tfile in temp_files: + try: + os.remove(tfile) + print(f"[CLEANUP] Lokale temporäre Datei gelöscht: {tfile}") + except Exception as e: + print(f"[CLEANUP] Fehler beim lokalen Löschen: {e}") + + return output_filename + + except Exception as e: + msg = f"Allgemeiner Fehler bei {img_path}: {e}" + print(msg) + notify("3D-Konvertierung Fehler", msg) + return None + +def main(): + if len(sys.argv) < 2: + notify("3D-Konvertierung Fehler", "Keine Bilddateien angegeben!") + print("Verwendung: python image_to_3d.py [image2 ...]") + sys.exit(1) + + all_outputs = [] + for img_path in sys.argv[1:]: + print(f"\nBearbeite: {img_path}") + result = process_image(img_path) + if result: + all_outputs.append(result) + + # Sammel-Notification am Ende + if all_outputs: + notify("3D-Konvertierung", f"Fertig! {len(all_outputs)} Modell(e) gespeichert.") + else: + notify("3D-Konvertierung", "Keine 3D-Modelle erzeugt.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/secure_upload.php b/secure_upload.php new file mode 100644 index 0000000..550e1dd --- /dev/null +++ b/secure_upload.php @@ -0,0 +1,37 @@ + "Wrong password"]); + exit; +} + +if (!isset($_FILES["file"])) { + http_response_code(400); + echo json_encode(["error" => "No file uploaded"]); + exit; +} + +$target_file = $target_dir . basename($_FILES["file"]["name"]); + +// Optional: Dateityp prüfen (nur Bilder) +$allowed = ['jpg', 'jpeg', 'png']; +$ext = strtolower(pathinfo($target_file, PATHINFO_EXTENSION)); +if (!in_array($ext, $allowed)) { + http_response_code(400); + echo json_encode(["error" => "Invalid file type"]); + exit; +} + +if (move_uploaded_file($_FILES["file"]["tmp_name"], $target_file)) { + echo json_encode(["success" => true, "url" => "https://www.victorgiers.com/generate3d/" . basename($_FILES["file"]["name"])]); +} else { + http_response_code(500); + echo json_encode(["error" => "Failed to move uploaded file"]); +} +?> \ No newline at end of file