Aduc-sdr commited on
Commit
70c5d9b
·
verified ·
1 Parent(s): 1444940

Create deformes7D.py

Browse files
Files changed (1) hide show
  1. engineers/deformes7D.py +300 -0
engineers/deformes7D.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # engineers/deformes7D.py
2
+ #
3
+ # AducSdr: Uma implementação aberta e funcional da arquitetura ADUC-SDR
4
+ # Copyright (C) 4 de Agosto de 2025 Carlos Rodrigues dos Santos
5
+ #
6
+ # Contato:
7
+ # Carlos Rodrigues dos Santos
8
+ # carlex22@gmail.com
9
+ # Rua Eduardo Carlos Pereira, 4125, B1 Ap32, Curitiba, PR, Brazil, CEP 8102025
10
+ #
11
+ # Repositórios e Projetos Relacionados:
12
+ # GitHub: https://github.com/carlex22/Aduc-sdr
13
+ #
14
+ # This program is free software: you can redistribute it and/or modify
15
+ # it under the terms of the GNU Affero General Public License as published by
16
+ # the Free Software Foundation, either version 3 of the License, or
17
+ # (at your option) any later version.
18
+ #
19
+ # This program is distributed in the hope that it will be useful,
20
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ # GNU Affero General Public License for more details.
23
+ #
24
+ # You should have received a copy of the GNU Affero General Public License
25
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
26
+ #
27
+ # This program is free software: you can redistribute it and/or modify
28
+ # it under the terms of the GNU Affero General Public License...
29
+ # PENDING PATENT NOTICE: Please see NOTICE.md.
30
+ #
31
+ # Version: 3.0.0
32
+ #
33
+ # This file defines the Deformes7DEngine, the unified production specialist
34
+ # of the ADUC-SDR framework. It merges the capabilities of 3D (causal keyframing)
35
+ # and 4D (video fragment generation) into a single, continuous, and interleaved
36
+ # rendering pipeline. It is the definitive implementation of the ADUC-SDR philosophy.
37
+
38
+ import os
39
+ import time
40
+ import imageio
41
+ import numpy as np
42
+ import torch
43
+ import logging
44
+ from PIL import Image, ImageOps
45
+ import gradio as gr
46
+ import subprocess
47
+ import gc
48
+ import shutil
49
+ from pathlib import Path
50
+ from typing import List, Tuple, Generator, Dict, Any
51
+
52
+ from aduc_types import LatentConditioningItem
53
+ from managers.ltx_manager import ltx_manager_singleton
54
+ from managers.latent_enhancer_manager import latent_enhancer_specialist_singleton
55
+ from managers.vae_manager import vae_manager_singleton
56
+ from engineers.deformes2D_thinker import deformes2d_thinker_singleton
57
+ from managers.seedvr_manager import seedvr_manager_singleton
58
+ from managers.mmaudio_manager import mmaudio_manager_singleton
59
+ from tools.video_encode_tool import video_encode_tool_singleton
60
+
61
+ logger = logging.getLogger(__name__)
62
+
63
+ class Deformes7DEngine:
64
+ """
65
+ Unified 3D/4D engine for continuous, interleaved generation of keyframes and video fragments.
66
+ """
67
+ def __init__(self, workspace_dir="deformes_workspace"):
68
+ self.workspace_dir = workspace_dir
69
+ self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
70
+ logger.info("Deformes7D Unified Engine initialized.")
71
+ os.makedirs(self.workspace_dir, exist_ok=True)
72
+
73
+ # --- HELPER METHODS (from 3D and 4D engines) ---
74
+
75
+ def _preprocess_image(self, image: Image.Image, target_resolution: tuple) -> Image.Image:
76
+ """Resizes and fits an image to the target resolution."""
77
+ if image.size != target_resolution:
78
+ return ImageOps.fit(image, target_resolution, Image.Resampling.LANCZOS)
79
+ return image
80
+
81
+ def _pil_to_pixel_tensor(self, pil_image: Image.Image) -> torch.Tensor:
82
+ """Converts PIL to the 5D pixel tensor for VAE encoding."""
83
+ image_np = np.array(pil_image).astype(np.float32) / 255.0
84
+ tensor = torch.from_numpy(image_np).permute(2, 0, 1).unsqueeze(0).unsqueeze(2)
85
+ return (tensor * 2.0) - 1.0
86
+
87
+ def _save_image_from_tensor(self, pixel_tensor: torch.Tensor, path: str):
88
+ """Saves a 1-frame pixel tensor as a PNG image."""
89
+ tensor_chw = pixel_tensor.squeeze(0).squeeze(1)
90
+ tensor_hwc = tensor_chw.permute(1, 2, 0)
91
+ tensor_hwc = (tensor_hwc.clamp(-1, 1) + 1) / 2.0
92
+ image_np = (tensor_hwc.cpu().float().numpy() * 255).astype(np.uint8)
93
+ Image.fromarray(image_np).save(path)
94
+
95
+ def _quantize_to_multiple(self, n, m):
96
+ """Helper to round n to the nearest multiple of m."""
97
+ if m == 0: return n
98
+ quantized = int(round(n / m) * m)
99
+ return m if n > 0 and quantized == 0 else quantized
100
+
101
+ # --- CORE GENERATION LOGIC ---
102
+
103
+ def _generate_next_causal_keyframe(self, base_keyframe_path: str, all_ref_paths: list,
104
+ prompt: str, resolution_tuple: tuple) -> Tuple[str, torch.Tensor]:
105
+ """
106
+ Generates the next keyframe in a sequence using the LTX latent evolution method.
107
+ Returns the path to the saved PNG and its corresponding latent tensor.
108
+ """
109
+ ltx_context_paths = [base_keyframe_path] + [p for p in all_ref_paths if p != base_keyframe_path][:3]
110
+
111
+ ltx_conditioning_items = []
112
+ weight = 1.0
113
+ for path in ltx_context_paths:
114
+ img_pil = Image.open(path).convert("RGB")
115
+ img_processed = self._preprocess_image(img_pil, resolution_tuple)
116
+ pixel_tensor = self._pil_to_pixel_tensor(img_processed)
117
+ latent_tensor = vae_manager_singleton.encode(pixel_tensor)
118
+ ltx_conditioning_items.append(LatentConditioningItem(latent_tensor, 0, weight))
119
+ if weight == 1.0: weight = -0.2
120
+ else: weight -= 0.2
121
+
122
+ ltx_base_params = {"guidance_scale": 3.0, "stg_scale": 0.1, "num_inference_steps": 25}
123
+ generated_latents, _ = ltx_manager_singleton.generate_latent_fragment(
124
+ height=resolution_tuple[0], width=resolution_tuple[1],
125
+ conditioning_items_data=ltx_conditioning_items, motion_prompt=prompt,
126
+ video_total_frames=48, video_fps=24, **ltx_base_params
127
+ )
128
+
129
+ final_latent = generated_latents[:, :, -1:, :, :]
130
+ upscaled_latent = latent_enhancer_specialist_singleton.upscale(final_latent)
131
+ pixel_tensor_out = vae_manager_singleton.decode(upscaled_latent)
132
+
133
+ # Save the new keyframe image
134
+ timestamp = int(time.time() * 1000)
135
+ output_path = os.path.join(self.workspace_dir, f"keyframe_{timestamp}.png")
136
+ self._save_image_from_tensor(pixel_tensor_out, output_path)
137
+
138
+ return output_path, final_latent
139
+
140
+ def generate_full_movie_interleaved(self, initial_ref_paths: list, storyboard: list, global_prompt: str,
141
+ video_resolution: int, seconds_per_fragment: float, trim_percent: int,
142
+ handler_strength: float, dest_strength: float, ltx_params: dict,
143
+ progress=gr.Progress()):
144
+ """
145
+ The main interleaved rendering pipeline for Deformes7D.
146
+ """
147
+ # --- INITIALIZATION ---
148
+ logger.info("--- DEFORMES 7D: INITIATING INTERLEAVED RENDERING PIPELINE ---")
149
+ run_timestamp = int(time.time())
150
+ temp_video_clips_dir = os.path.join(self.workspace_dir, f"temp_clips_{run_timestamp}")
151
+ os.makedirs(temp_video_clips_dir, exist_ok=True)
152
+
153
+ resolution_tuple = (video_resolution, video_resolution)
154
+
155
+ # Lists to store the full sequence of generated artifacts
156
+ generated_keyframe_paths = []
157
+ generated_keyframe_latents = []
158
+ generated_video_fragment_paths = []
159
+
160
+ # --- BOOTSTRAP: Generate first two keyframes to start the pipeline ---
161
+ progress(0, desc="Bootstrap: Generating K0...")
162
+ # Keyframe 0 is just the processed initial reference
163
+ k0_path = initial_ref_paths[0]
164
+ k0_pil = Image.open(k0_path).convert("RGB")
165
+ k0_processed_pil = self._preprocess_image(k0_pil, resolution_tuple)
166
+ k0_pixel_tensor = self._pil_to_pixel_tensor(k0_processed_pil)
167
+ k0_latent = vae_manager_singleton.encode(k0_pixel_tensor)
168
+ generated_keyframe_paths.append(k0_path)
169
+ generated_keyframe_latents.append(k0_latent)
170
+
171
+ progress(0, desc="Bootstrap: Generating K1...")
172
+ # Generate Keyframe 1 from Keyframe 0
173
+ prompt_k1 = deformes2d_thinker_singleton.get_anticipatory_keyframe_prompt(
174
+ global_prompt, "Initial scene.", storyboard[0], storyboard[1], k0_path, initial_ref_paths
175
+ )
176
+ k1_path, k1_latent = self._generate_next_causal_keyframe(k0_path, initial_ref_paths, prompt_k1, resolution_tuple)
177
+ generated_keyframe_paths.append(k1_path)
178
+ generated_keyframe_latents.append(k1_latent)
179
+
180
+ # --- MAIN RENDERING LOOP ---
181
+ story_history = ""
182
+ eco_latent_for_next_loop, dejavu_latent_for_next_loop = None, None
183
+ num_transitions = len(storyboard) - 1
184
+
185
+ for i in range(1, num_transitions):
186
+ progress(i / num_transitions, desc=f"Processing Act {i+1}/{num_transitions}...")
187
+
188
+ # --- 1. Generate the NEXT Keyframe (Look-ahead) ---
189
+ logger.info(f"--> Step 3D: Generating Keyframe K{i+1}")
190
+ kx_path = generated_keyframe_paths[i]
191
+ prompt_ky = deformes2d_thinker_singleton.get_anticipatory_keyframe_prompt(
192
+ global_prompt, "Continuing sequence...", storyboard[i], storyboard[i+1], kx_path, initial_ref_paths
193
+ )
194
+ ky_path, ky_latent = self._generate_next_causal_keyframe(kx_path, initial_ref_paths, prompt_ky, resolution_tuple)
195
+ generated_keyframe_paths.append(ky_path)
196
+ generated_keyframe_latents.append(ky_latent)
197
+
198
+ # --- 2. Generate the CURRENT Video Fragment ---
199
+ logger.info(f"--> Step 4D: Generating Video Fragment V{i}")
200
+ kb_path = generated_keyframe_paths[i-1] # Past
201
+ kx_path = generated_keyframe_paths[i] # Present (Start)
202
+ ky_path = generated_keyframe_paths[i+1] # Future (End)
203
+
204
+ decision = deformes2d_thinker_singleton.get_cinematic_decision(
205
+ global_prompt, story_history, kb_path, kx_path, ky_path,
206
+ storyboard[i-1], storyboard[i], storyboard[i+1]
207
+ )
208
+ transition_type, motion_prompt = decision["transition_type"], decision["motion_prompt"]
209
+ story_history += f"\n- Act {i}: {motion_prompt}"
210
+
211
+ # Prepare conditioning items for the video fragment
212
+ conditioning_items = []
213
+ if eco_latent_for_next_loop is None:
214
+ conditioning_items.append(LatentConditioningItem(generated_keyframe_latents[i], 0, 1.0))
215
+ else:
216
+ # This part reuses the logic from the old Deformes4D
217
+ # ... [Implementation of Eco/Deja-Vu conditioning here] ...
218
+ # For simplicity in this first draft, we'll use the direct keyframe latent
219
+ conditioning_items.append(LatentConditioningItem(generated_keyframe_latents[i], 0, 1.0))
220
+
221
+ # Add the destination anchor
222
+ conditioning_items.append(LatentConditioningItem(ky_latent, -1, dest_strength)) # Use -1 for last frame
223
+
224
+ fragment_latents, _ = ltx_manager_singleton.generate_latent_fragment(
225
+ height=video_resolution, width=video_resolution,
226
+ conditioning_items_data=conditioning_items, motion_prompt=motion_prompt,
227
+ video_total_frames=self._quantize_to_multiple(int(seconds_per_fragment * 24), 8),
228
+ video_fps=24, **ltx_params
229
+ )
230
+
231
+ # Post-process and save the video fragment
232
+ pixel_tensor = vae_manager_singleton.decode(fragment_latents)
233
+ fragment_path = os.path.join(temp_video_clips_dir, f"fragment_{i}.mp4")
234
+ self.save_video_from_tensor(pixel_tensor, fragment_path, fps=24)
235
+ generated_video_fragment_paths.append(fragment_path)
236
+ logger.info(f"Video Fragment V{i} saved to {fragment_path}")
237
+
238
+ # Here you would also extract the Eco and Deja-Vu from `fragment_latents` for the next loop
239
+ # ...
240
+
241
+ # --- FINAL ASSEMBLY ---
242
+ logger.info("--- Final Assembly of Video Fragments ---")
243
+ final_video_path = os.path.join(self.workspace_dir, f"movie_7D_{run_timestamp}.mp4")
244
+ video_encode_tool_singleton.concatenate_videos(
245
+ video_paths=generated_video_fragment_paths,
246
+ output_path=final_video_path,
247
+ workspace_dir=self.workspace_dir
248
+ )
249
+ shutil.rmtree(temp_video_clips_dir)
250
+
251
+ logger.info(f"Full movie generated at: {final_video_path}")
252
+ # This function would then return the path and other artifacts for post-production
253
+ return {"final_path": final_video_path, "all_keyframes": generated_keyframe_paths}
254
+
255
+
256
+ # --- POST-PRODUCTION METHODS (migrated from Deformes4D) ---
257
+
258
+ def upscale_video(self, source_video_path: str, progress=gr.Progress()):
259
+ # This would be a more complex function that loads the video in chunks,
260
+ # encodes to latents, upscales, decodes, and reassembles.
261
+ # For this example, we assume it's a placeholder.
262
+ logger.info(f"Placeholder for upscaling video: {source_video_path}")
263
+ return source_video_path
264
+
265
+ def master_video_hd(self, source_video_path: str, model_version: str, steps: int, prompt: str, progress=gr.Progress()):
266
+ logger.info(f"--- POST-PRODUCTION: HD Mastering with SeedVR {model_version} ---")
267
+ progress(0.1, desc=f"Preparing for HD Mastering...")
268
+ run_timestamp = int(time.time())
269
+ output_path = os.path.join(self.workspace_dir, f"{Path(source_video_path).stem}_hd.mp4")
270
+ try:
271
+ final_path = seedvr_manager_singleton.process_video(
272
+ input_video_path=source_video_path, output_video_path=output_path,
273
+ prompt=prompt, model_version=model_version, steps=steps, progress=progress
274
+ )
275
+ logger.info(f"HD Mastering complete! Final video at: {final_path}")
276
+ yield {"final_path": final_path}
277
+ except Exception as e:
278
+ logger.error(f"HD Mastering failed: {e}", exc_info=True)
279
+ raise gr.Error(f"HD Mastering failed. Details: {e}")
280
+
281
+ def generate_audio(self, source_video_path: str, audio_prompt: str, progress=gr.Progress()):
282
+ logger.info(f"--- POST-PRODUCTION: Audio Generation ---")
283
+ progress(0.1, desc="Preparing for audio generation...")
284
+ run_timestamp = int(time.time())
285
+ output_path = os.path.join(self.workspace_dir, f"{Path(source_video_path).stem}_audio.mp4")
286
+ try:
287
+ result = subprocess.run(
288
+ ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", source_video_path],
289
+ capture_output=True, text=True, check=True)
290
+ duration = float(result.stdout.strip())
291
+ progress(0.5, desc="Generating audio track...")
292
+ final_path = mmaudio_manager_singleton.generate_audio_for_video(
293
+ video_path=source_video_path, prompt=audio_prompt,
294
+ duration_seconds=duration, output_path_override=output_path
295
+ )
296
+ logger.info(f"Audio generation complete! Final video with audio at: {final_path}")
297
+ yield {"final_path": final_path}
298
+ except Exception as e:
299
+ logger.error(f"Audio generation failed: {e}", exc_info=True)
300
+ raise gr.Error(f"Audio generation failed. Details: {e}")