api funktioniert

This commit is contained in:
Fedja Windows 2025-04-26 10:04:08 +02:00
parent 88fc7017e6
commit 5edc3b9f3a
3 changed files with 252 additions and 56 deletions

View File

@ -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)

View File

@ -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:

View File

@ -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