api funktioniert
This commit is contained in:
parent
88fc7017e6
commit
5edc3b9f3a
250
api_server.py
250
api_server.py
@ -1,15 +1,26 @@
|
||||
# api_server.py - REST-API für den Videogenerator
|
||||
# api_server_fixed.py - Korrigierte REST-API für den Videogenerator mit job_id-Unterstützung
|
||||
# Im webui-Verzeichnis speichern
|
||||
|
||||
from fastapi import FastAPI, File, UploadFile, BackgroundTasks, Form
|
||||
from fastapi import FastAPI, BackgroundTasks
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import uvicorn
|
||||
import os
|
||||
import subprocess
|
||||
import uuid
|
||||
import shutil
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
# Datenmodell für die Videogenerierungsanfrage
|
||||
class VideoGenerationRequest(BaseModel):
|
||||
image_path: str
|
||||
prompt: str
|
||||
n_prompt: Optional[str] = ""
|
||||
seed: Optional[int] = 31337
|
||||
length: Optional[float] = 5.0
|
||||
steps: Optional[int] = 25
|
||||
use_teacache: Optional[bool] = True
|
||||
output_filename: Optional[str] = "output.mp4"
|
||||
|
||||
app = FastAPI(title="Hunyuan Video Generator API")
|
||||
|
||||
@ -22,30 +33,45 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Verzeichnisse für temporäre Dateien und Ausgaben
|
||||
TEMP_DIR = os.path.join(os.path.dirname(__file__), "temp_uploads")
|
||||
# Verzeichnis für Ausgaben
|
||||
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "outputs")
|
||||
PYTHON_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "system", "python", "python.exe")
|
||||
|
||||
os.makedirs(TEMP_DIR, exist_ok=True)
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
# In-Memory Speicher für laufende Jobs
|
||||
active_jobs = {}
|
||||
|
||||
def generate_video_task(image_path: str, prompt: str, n_prompt: str,
|
||||
seed: int, length: float, steps: int,
|
||||
use_teacache: bool, output_filename: str,
|
||||
job_id: str):
|
||||
"""Führt die Videogenerierung als Hintergrundaufgabe aus"""
|
||||
|
||||
# Befehlszeile erstellen
|
||||
# Absoluten Pfad sicherstellen
|
||||
if not os.path.isabs(image_path):
|
||||
# Wenn relativer Pfad, relativ zum aktuellen Verzeichnis interpretieren
|
||||
image_path = os.path.abspath(os.path.join(os.path.dirname(__file__), image_path))
|
||||
|
||||
# Prüfen, ob die Bilddatei existiert
|
||||
if not os.path.exists(image_path):
|
||||
print(f"Fehler: Bilddatei nicht gefunden: {image_path}")
|
||||
error_file = os.path.join(OUTPUT_DIR, f"{job_id}_error.txt")
|
||||
with open(error_file, 'w') as f:
|
||||
f.write(f"Fehler: Bilddatei nicht gefunden: {image_path}")
|
||||
return
|
||||
|
||||
# Befehlszeile erstellen - WICHTIG: Wir geben die job_id als Parameter weiter
|
||||
cmd = [
|
||||
PYTHON_PATH,
|
||||
"hunyuan_cli.py",
|
||||
"hunyuan_cli.py", # Aktualisierte Version mit job_id-Unterstützung
|
||||
"--image", image_path,
|
||||
"--prompt", prompt,
|
||||
"--seed", str(seed),
|
||||
"--length", str(length),
|
||||
"--steps", str(steps),
|
||||
"--output", f"{job_id}_{output_filename}"
|
||||
"--output", output_filename,
|
||||
"--job_id", job_id # Wichtig: Gebe job_id weiter
|
||||
]
|
||||
|
||||
if n_prompt:
|
||||
@ -54,49 +80,70 @@ def generate_video_task(image_path: str, prompt: str, n_prompt: str,
|
||||
if use_teacache:
|
||||
cmd.append("--teacache")
|
||||
|
||||
# Befehl ausführen
|
||||
# Befehl ausführen und Ausgabe protokollieren
|
||||
try:
|
||||
subprocess.run(cmd, check=True, cwd=os.path.dirname(__file__))
|
||||
print(f"Starte Videogenerierung mit Befehl: {' '.join(cmd)}")
|
||||
process = subprocess.run(cmd,
|
||||
check=True,
|
||||
cwd=os.path.dirname(__file__),
|
||||
capture_output=True,
|
||||
text=True)
|
||||
|
||||
# Ausgabe in Protokolldatei schreiben
|
||||
log_path = os.path.join(OUTPUT_DIR, f"{job_id}_log.txt")
|
||||
with open(log_path, 'w') as log_file:
|
||||
log_file.write(f"STDOUT:\n{process.stdout}\n\nSTDERR:\n{process.stderr}")
|
||||
|
||||
print(f"Video generation completed for job {job_id}")
|
||||
|
||||
# Aus aktiven Jobs entfernen
|
||||
if job_id in active_jobs:
|
||||
del active_jobs[job_id]
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error generating video: {e}")
|
||||
# Fehlermeldung in Protokolldatei schreiben
|
||||
error_path = os.path.join(OUTPUT_DIR, f"{job_id}_error.txt")
|
||||
with open(error_path, 'w') as error_file:
|
||||
error_file.write(f"Command: {' '.join(cmd)}\n\nError:\n{str(e)}\n\nSTDOUT:\n{e.stdout}\n\nSTDERR:\n{e.stderr}")
|
||||
|
||||
# Aus aktiven Jobs entfernen
|
||||
if job_id in active_jobs:
|
||||
del active_jobs[job_id]
|
||||
|
||||
@app.post("/generate/")
|
||||
async def generate_video(
|
||||
background_tasks: BackgroundTasks,
|
||||
image: UploadFile = File(...),
|
||||
prompt: str = Form(...),
|
||||
n_prompt: Optional[str] = Form(""),
|
||||
seed: Optional[int] = Form(31337),
|
||||
length: Optional[float] = Form(5.0),
|
||||
steps: Optional[int] = Form(25),
|
||||
use_teacache: Optional[bool] = Form(True),
|
||||
output_filename: Optional[str] = Form("output.mp4")
|
||||
request: VideoGenerationRequest,
|
||||
background_tasks: BackgroundTasks
|
||||
):
|
||||
"""
|
||||
Generiert ein Video basierend auf den angegebenen Parametern
|
||||
Generiert ein Video basierend auf den angegebenen Parametern und einem lokalen Bildpfad
|
||||
"""
|
||||
# Eindeutige Job-ID generieren
|
||||
job_id = str(uuid.uuid4())
|
||||
|
||||
# Temporären Dateipfad für das Bild erstellen
|
||||
temp_image_path = os.path.join(TEMP_DIR, f"{job_id}_{image.filename}")
|
||||
# In aktive Jobs aufnehmen
|
||||
active_jobs[job_id] = {
|
||||
"status": "initializing",
|
||||
"parameters": request.dict()
|
||||
}
|
||||
|
||||
# Bild speichern
|
||||
with open(temp_image_path, "wb") as buffer:
|
||||
shutil.copyfileobj(image.file, buffer)
|
||||
# Statusdatei sofort erstellen
|
||||
status_path = os.path.join(OUTPUT_DIR, f"{job_id}_status.txt")
|
||||
with open(status_path, 'w') as f:
|
||||
f.write(f"Status: Initialisierung\nJob-ID: {job_id}\nBild: {request.image_path}\nPrompt: {request.prompt}\n")
|
||||
|
||||
# Videogenerierung als Hintergrundaufgabe starten
|
||||
background_tasks.add_task(
|
||||
generate_video_task,
|
||||
temp_image_path,
|
||||
prompt,
|
||||
n_prompt,
|
||||
seed,
|
||||
length,
|
||||
steps,
|
||||
use_teacache,
|
||||
output_filename,
|
||||
request.image_path,
|
||||
request.prompt,
|
||||
request.n_prompt,
|
||||
request.seed,
|
||||
request.length,
|
||||
request.steps,
|
||||
request.use_teacache,
|
||||
request.output_filename,
|
||||
job_id
|
||||
)
|
||||
|
||||
@ -104,7 +151,7 @@ async def generate_video(
|
||||
"status": "processing",
|
||||
"job_id": job_id,
|
||||
"message": "Video generation started in background",
|
||||
"result_url": f"/result/{job_id}_{output_filename}"
|
||||
"result_url": f"/result/{job_id}_{request.output_filename}"
|
||||
}
|
||||
|
||||
@app.get("/result/{filename}")
|
||||
@ -118,33 +165,138 @@ async def get_result(filename: str):
|
||||
return FileResponse(
|
||||
file_path,
|
||||
media_type="video/mp4",
|
||||
filename=filename.split("_", 1)[1] # Entferne Job-ID vom Dateinamen
|
||||
filename=filename.split("_", 1)[1] if "_" in filename else filename # Entferne Job-ID vom Dateinamen
|
||||
)
|
||||
else:
|
||||
return {"status": "not_found", "message": "Requested video not found or still processing"}
|
||||
return {"status": "not_found", "message": f"Requested video not found or still processing: {file_path}"}
|
||||
|
||||
@app.get("/status/{job_id}")
|
||||
async def check_status(job_id: str):
|
||||
"""
|
||||
Prüft den Status einer Videogenerierung
|
||||
"""
|
||||
# Suche nach Dateien, die mit der Job-ID beginnen
|
||||
result_files = [f for f in os.listdir(OUTPUT_DIR) if f.startswith(job_id)]
|
||||
# 1. Prüfen, ob der Job in den aktiven Jobs ist
|
||||
if job_id in active_jobs:
|
||||
return {
|
||||
"status": "processing",
|
||||
"job_id": job_id,
|
||||
"details": "Job wird verarbeitet",
|
||||
"active_job": True
|
||||
}
|
||||
|
||||
if result_files:
|
||||
# 2. Prüfen, ob eine Statusdatei existiert
|
||||
status_file = os.path.join(OUTPUT_DIR, f"{job_id}_status.txt")
|
||||
if os.path.exists(status_file):
|
||||
with open(status_file, 'r') as f:
|
||||
status_content = f.read()
|
||||
|
||||
# 3. Prüfen, ob ein fertiges Video existiert
|
||||
video_files = [f for f in os.listdir(OUTPUT_DIR)
|
||||
if f.startswith(job_id) and f.endswith('.mp4')]
|
||||
|
||||
if video_files:
|
||||
return {
|
||||
"status": "completed",
|
||||
"job_id": job_id,
|
||||
"files": result_files,
|
||||
"download_urls": [f"/result/{file}" for file in result_files]
|
||||
"files": video_files,
|
||||
"download_urls": [f"/result/{file}" for file in video_files],
|
||||
"status_info": status_content
|
||||
}
|
||||
else:
|
||||
# Prüfen, ob das Eingabebild noch existiert (Verarbeitung läuft noch)
|
||||
input_files = [f for f in os.listdir(TEMP_DIR) if f.startswith(job_id)]
|
||||
if input_files:
|
||||
return {"status": "processing", "job_id": job_id}
|
||||
else:
|
||||
|
||||
# 4. Prüfen, ob ein Fehler aufgetreten ist
|
||||
error_file = os.path.join(OUTPUT_DIR, f"{job_id}_error.txt")
|
||||
if os.path.exists(error_file):
|
||||
with open(error_file, 'r') as f:
|
||||
error_text = f.read()
|
||||
return {
|
||||
"status": "error",
|
||||
"job_id": job_id,
|
||||
"error": error_text,
|
||||
"status_info": status_content
|
||||
}
|
||||
|
||||
# 5. Sonst läuft der Job noch
|
||||
return {
|
||||
"status": "processing",
|
||||
"job_id": job_id,
|
||||
"status_info": status_content
|
||||
}
|
||||
|
||||
# 6. Prüfen auf alte Dateien ohne Status-Datei
|
||||
video_files = [f for f in os.listdir(OUTPUT_DIR)
|
||||
if f.startswith(job_id) and f.endswith('.mp4')]
|
||||
|
||||
if video_files:
|
||||
return {
|
||||
"status": "completed",
|
||||
"job_id": job_id,
|
||||
"files": video_files,
|
||||
"download_urls": [f"/result/{file}" for file in video_files],
|
||||
"note": "Status file not found, but video exists"
|
||||
}
|
||||
|
||||
# 7. Prüfen, ob ein Fehlerprotokoll existiert ohne Status-Datei
|
||||
error_file = os.path.join(OUTPUT_DIR, f"{job_id}_error.txt")
|
||||
if os.path.exists(error_file):
|
||||
with open(error_file, 'r') as f:
|
||||
error_text = f.read()
|
||||
return {
|
||||
"status": "error",
|
||||
"job_id": job_id,
|
||||
"error": error_text,
|
||||
"note": "Status file not found, but error exists"
|
||||
}
|
||||
|
||||
# 8. Prüfen, ob ein Ausführungsprotokoll existiert ohne Status-Datei
|
||||
log_file = os.path.join(OUTPUT_DIR, f"{job_id}_log.txt")
|
||||
if os.path.exists(log_file):
|
||||
return {
|
||||
"status": "unknown",
|
||||
"job_id": job_id,
|
||||
"note": "Status file not found, but log exists, check logs for details"
|
||||
}
|
||||
|
||||
# 9. Keine Dateien oder Informationen zu dieser Job-ID gefunden
|
||||
return {"status": "not_found", "job_id": job_id}
|
||||
|
||||
@app.get("/debug/")
|
||||
async def debug():
|
||||
"""
|
||||
Gibt Debug-Informationen zurück
|
||||
"""
|
||||
try:
|
||||
# Alle Dateien im OUTPUT_DIR auflisten
|
||||
files = os.listdir(OUTPUT_DIR)
|
||||
|
||||
# Gruppieren nach Präfix
|
||||
file_groups = {}
|
||||
for file in files:
|
||||
parts = file.split('_', 1)
|
||||
if len(parts) > 1:
|
||||
prefix = parts[0]
|
||||
if prefix not in file_groups:
|
||||
file_groups[prefix] = []
|
||||
file_groups[prefix].append(file)
|
||||
|
||||
return {
|
||||
"active_jobs": active_jobs,
|
||||
"output_dir": OUTPUT_DIR,
|
||||
"files_count": len(files),
|
||||
"file_groups": file_groups
|
||||
}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
@app.get("/debug/files/")
|
||||
async def list_files():
|
||||
"""
|
||||
Listet alle Dateien im OUTPUT_DIR auf
|
||||
"""
|
||||
try:
|
||||
files = os.listdir(OUTPUT_DIR)
|
||||
return {"files": files}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
@ -1,4 +1,4 @@
|
||||
# hunyuan_cli.py - Kommandozeilen-Interface für den Videogenerator
|
||||
# hunyuan_cli_fixed.py - Kommandozeilen-Interface für den Videogenerator mit job_id-Unterstützung
|
||||
from diffusers_helper.hf_login import login
|
||||
import os
|
||||
import argparse
|
||||
@ -36,6 +36,7 @@ parser.add_argument('--gpu_mem', type=float, default=6.0, help='GPU Speicherrese
|
||||
parser.add_argument('--teacache', action='store_true', help='TeaCache aktivieren')
|
||||
parser.add_argument('--mp4_crf', type=int, default=16, help='MP4 Kompression (0-100)')
|
||||
parser.add_argument('--output', type=str, default='output.mp4', help='Ausgabedatei (MP4)')
|
||||
parser.add_argument('--job_id', type=str, default='', help='Job-ID (wenn von API aufgerufen)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@ -121,15 +122,28 @@ def process_cli():
|
||||
mp4_crf = args.mp4_crf
|
||||
output_path = args.output
|
||||
|
||||
# WICHTIG: Job-ID entweder von Parameter nehmen oder einen Zeitstempel generieren
|
||||
job_id = args.job_id if args.job_id else generate_timestamp()
|
||||
|
||||
# Status-Datei erstellen oder aktualisieren
|
||||
status_file = os.path.join(outputs_folder, f"{job_id}_status.txt")
|
||||
with open(status_file, "w") as f:
|
||||
f.write(f"Status: Initialisierung\nJob-ID: {job_id}\nStart-Zeit: {generate_timestamp()}\n")
|
||||
|
||||
total_latent_sections = (total_second_length * 30) / (latent_window_size * 4)
|
||||
total_latent_sections = int(max(round(total_latent_sections), 1))
|
||||
|
||||
job_id = generate_timestamp()
|
||||
final_output_path = os.path.join(outputs_folder, output_path)
|
||||
final_output_path_with_job_id = os.path.join(outputs_folder, f"{job_id}_{output_path}")
|
||||
|
||||
try:
|
||||
print(f"Job-ID: {job_id}")
|
||||
|
||||
# Eingabebild laden
|
||||
print("Lade Eingabebild...")
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Status: Lade Eingabebild\n")
|
||||
|
||||
input_image = np.array(Image.open(input_image_path))
|
||||
|
||||
# Clean GPU
|
||||
@ -140,6 +154,9 @@ def process_cli():
|
||||
|
||||
# Text encoding
|
||||
print("Text-Encoding...")
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Status: Text-Encoding\n")
|
||||
|
||||
if not high_vram:
|
||||
fake_diffusers_current_device(text_encoder, gpu)
|
||||
load_model_as_complete(text_encoder_2, target_device=gpu)
|
||||
@ -156,17 +173,24 @@ def process_cli():
|
||||
|
||||
# Bild verarbeiten
|
||||
print("Verarbeite Eingabebild...")
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Status: Verarbeite Eingabebild\n")
|
||||
|
||||
H, W, C = input_image.shape
|
||||
height, width = find_nearest_bucket(H, W, resolution=640)
|
||||
input_image_np = resize_and_center_crop(input_image, target_width=width, target_height=height)
|
||||
|
||||
Image.fromarray(input_image_np).save(os.path.join(outputs_folder, f'{job_id}.png'))
|
||||
# WICHTIG: Speichere das Input-Bild mit job_id Präfix
|
||||
Image.fromarray(input_image_np).save(os.path.join(outputs_folder, f'{job_id}_input.png'))
|
||||
|
||||
input_image_pt = torch.from_numpy(input_image_np).float() / 127.5 - 1
|
||||
input_image_pt = input_image_pt.permute(2, 0, 1)[None, :, None]
|
||||
|
||||
# VAE encoding
|
||||
print("VAE-Encoding...")
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Status: VAE-Encoding\n")
|
||||
|
||||
if not high_vram:
|
||||
load_model_as_complete(vae, target_device=gpu)
|
||||
|
||||
@ -174,6 +198,9 @@ def process_cli():
|
||||
|
||||
# CLIP Vision
|
||||
print("CLIP Vision-Encoding...")
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Status: CLIP Vision-Encoding\n")
|
||||
|
||||
if not high_vram:
|
||||
load_model_as_complete(image_encoder, target_device=gpu)
|
||||
|
||||
@ -189,6 +216,9 @@ def process_cli():
|
||||
|
||||
# Sampling
|
||||
print("Starte Sampling...")
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Status: Starte Sampling\n")
|
||||
|
||||
rnd = torch.Generator("cpu").manual_seed(seed)
|
||||
num_frames = latent_window_size * 4 - 3
|
||||
|
||||
@ -206,6 +236,8 @@ def process_cli():
|
||||
latent_padding_size = latent_padding * latent_window_size
|
||||
|
||||
print(f'latent_padding_size = {latent_padding_size}, is_last_section = {is_last_section}')
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Sampling: latent_padding_size = {latent_padding_size}, is_last_section = {is_last_section}\n")
|
||||
|
||||
indices = torch.arange(0, sum([1, latent_padding_size, latent_window_size, 1, 2, 16])).unsqueeze(0)
|
||||
clean_latent_indices_pre, blank_indices, latent_indices, clean_latent_indices_post, clean_latent_2x_indices, clean_latent_4x_indices = indices.split([1, latent_padding_size, latent_window_size, 1, 2, 16], dim=1)
|
||||
@ -228,6 +260,8 @@ def process_cli():
|
||||
current_step = d['i'] + 1
|
||||
percentage = int(100.0 * current_step / steps)
|
||||
print(f'Sampling {current_step}/{steps} ({percentage}%)')
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Sampling Schritt {current_step}/{steps} ({percentage}%)\n")
|
||||
|
||||
generated_latents = sample_hunyuan(
|
||||
transformer=transformer,
|
||||
@ -283,22 +317,32 @@ def process_cli():
|
||||
if not high_vram:
|
||||
unload_complete_models()
|
||||
|
||||
# WICHTIG: Der temporäre Ausgabefilename enthält die job_id
|
||||
tmp_output_filename = os.path.join(outputs_folder, f'{job_id}_{total_generated_latent_frames}.mp4')
|
||||
|
||||
save_bcthw_as_mp4(history_pixels, tmp_output_filename, fps=30, crf=mp4_crf)
|
||||
|
||||
print(f'Decoded. Aktuelle Latent Shape {real_history_latents.shape}; Pixel Shape {history_pixels.shape}')
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f'Dekodiert. Aktuelle Latent Shape {real_history_latents.shape}; Pixel Shape {history_pixels.shape}\n')
|
||||
|
||||
if is_last_section:
|
||||
# Kopiere das finale Video zum gewünschten Ausgabepfad
|
||||
import shutil
|
||||
shutil.copy(tmp_output_filename, final_output_path)
|
||||
# Stelle sicher, dass die finale Ausgabedatei auch die job_id enthält
|
||||
shutil.copy(tmp_output_filename, final_output_path_with_job_id)
|
||||
break
|
||||
|
||||
print(f"Video erfolgreich erstellt: {final_output_path}")
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Status: Abgeschlossen\nErgebnis: {final_output_path_with_job_id}\n")
|
||||
|
||||
print(f"Video erfolgreich erstellt: {final_output_path_with_job_id}")
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
with open(status_file, "a") as f:
|
||||
f.write(f"Status: Fehler\nFehlermeldung: {str(e)}\n{traceback.format_exc()}\n")
|
||||
|
||||
print(f"Fehler bei der Videogenerierung: {str(e)}")
|
||||
|
||||
if not high_vram:
|
||||
|
||||
@ -6,7 +6,7 @@ cd %~dp0webui
|
||||
|
||||
"%DIR%\python\python.exe" -m pip install fastapi uvicorn python-multipart
|
||||
|
||||
"%DIR%\python\python.exe" api_server.py
|
||||
"%DIR%\python\python.exe" api_server_path.py
|
||||
|
||||
:done
|
||||
pause
|
||||
Loading…
x
Reference in New Issue
Block a user