This commit is contained in:
justumen
2024-10-28 11:32:37 +01:00
parent 19cb4cbe49
commit 66eb5282c2
18 changed files with 950 additions and 213 deletions

View File

@@ -5,6 +5,7 @@ import tempfile
import torch
import numpy as np
from PIL import Image
import wave
class ImagesListToVideo:
@classmethod
@@ -13,6 +14,10 @@ class ImagesListToVideo:
"required": {
"images": ("IMAGE",),
"frames_per_second": ("FLOAT", {"default": 30, "min": 1, "max": 120, "step": 1}),
},
"optional": {
"audio_path": ("STRING", {"default": "", "multiline": False}),
"audio": ("AUDIO", {"default": None}),
}
}
@@ -21,7 +26,7 @@ class ImagesListToVideo:
FUNCTION = "images_to_video"
CATEGORY = "Bjornulf"
def images_to_video(self, images, frames_per_second=30):
def images_to_video(self, images, frames_per_second=30, audio_path="", audio=None):
# Create the output directory if it doesn't exist
output_dir = os.path.join("Bjornulf", "images_to_video")
os.makedirs(output_dir, exist_ok=True)
@@ -30,42 +35,85 @@ class ImagesListToVideo:
video_filename = f"video_{uuid.uuid4().hex}.mp4"
video_path = os.path.join(output_dir, video_filename)
# Create a temporary directory to store image files
# Create a temporary directory to store image files and audio
with tempfile.TemporaryDirectory() as temp_dir:
# Save each image as a PNG file in the temporary directory
for i, img in enumerate(images):
# Convert the image to the correct format
img_np = self.convert_to_numpy(img)
# Ensure the image is in RGB format
if img_np.shape[-1] != 3:
img_np = self.convert_to_rgb(img_np)
# Convert to PIL Image
img_pil = Image.fromarray(img_np)
img_path = os.path.join(temp_dir, f"frame_{i:05d}.png")
img_pil.save(img_path)
# Use FFmpeg to create a video from the image sequence
# Prepare FFmpeg command
ffmpeg_cmd = [
"ffmpeg",
"-framerate", str(frames_per_second),
"-i", os.path.join(temp_dir, "frame_%05d.png"),
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
"-crf", "23",
"-y", # Overwrite output file if it exists
video_path
"-crf", "19"
]
try:
subprocess.run(ffmpeg_cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print(f"FFmpeg error: {e.stderr}")
return ("",) # Return empty string if video creation fails
# Handle audio
temp_audio_path = None
if audio is not None and isinstance(audio, dict):
waveform = audio['waveform'].numpy().squeeze()
sample_rate = audio['sample_rate']
temp_audio_path = os.path.join(temp_dir, "temp_audio.wav")
self.write_wav(temp_audio_path, waveform, sample_rate)
elif audio_path and os.path.isfile(audio_path):
temp_audio_path = audio_path
if temp_audio_path:
# Create temporary video without audio first
temp_video = os.path.join(temp_dir, "temp_video.mp4")
temp_cmd = ffmpeg_cmd + ["-y", temp_video]
try:
# Create video without audio
subprocess.run(temp_cmd, check=True, capture_output=True, text=True)
# Add audio to the video
audio_cmd = [
"ffmpeg",
"-i", temp_video,
"-i", temp_audio_path,
"-c:v", "copy",
"-c:a", "aac",
"-shortest",
"-y",
video_path
]
subprocess.run(audio_cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print(f"FFmpeg error: {e.stderr}")
return ("",)
else:
# No audio, just create the video directly
ffmpeg_cmd.append("-y")
ffmpeg_cmd.append(video_path)
try:
subprocess.run(ffmpeg_cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print(f"FFmpeg error: {e.stderr}")
return ("",)
return (video_path,)
def write_wav(self, file_path, audio_data, sample_rate):
with wave.open(file_path, 'wb') as wav_file:
wav_file.setnchannels(1) # Mono
wav_file.setsampwidth(2) # 2 bytes per sample
wav_file.setframerate(sample_rate)
# Normalize and convert to 16-bit PCM
audio_data = np.int16(audio_data * 32767)
# Write audio data
wav_file.writeframes(audio_data.tobytes())
def convert_to_numpy(self, img):
if isinstance(img, torch.Tensor):
img = img.cpu().numpy()