mirror of
https://github.com/justUmen/Bjornulf_custom_nodes.git
synced 2026-03-25 22:35:43 -03:00
0.70
This commit is contained in:
237
ffmpeg_images_to_video.py
Normal file
237
ffmpeg_images_to_video.py
Normal file
@@ -0,0 +1,237 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import torch
|
||||
import subprocess
|
||||
import json
|
||||
from PIL import Image
|
||||
import soundfile as sf
|
||||
import glob
|
||||
|
||||
class imagesToVideo:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"fps": ("FLOAT", {"default": 24, "min": 1, "max": 120}),
|
||||
"name_prefix": ("STRING", {"default": "imgs2video/me"}),
|
||||
"use_python_ffmpeg": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"optional": {
|
||||
"audio": ("AUDIO",),
|
||||
"FFMPEG_CONFIG_JSON": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING",)
|
||||
RETURN_NAMES = ("comment", "ffmpeg_command",)
|
||||
FUNCTION = "image_to_video"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def parse_ffmpeg_config(self, config_json):
|
||||
if not config_json:
|
||||
return None
|
||||
try:
|
||||
return json.loads(config_json)
|
||||
except json.JSONDecodeError:
|
||||
print("Error parsing FFmpeg config JSON")
|
||||
return None
|
||||
|
||||
def run_ffmpeg_python(self, ffmpeg_cmd, output_file, ffmpeg_path):
|
||||
try:
|
||||
import ffmpeg
|
||||
except ImportError as e:
|
||||
print(f"Error importing ffmpeg-python: {e}")
|
||||
return False, "ffmpeg-python library not installed"
|
||||
|
||||
try:
|
||||
# Reconstruct the command using ffmpeg-python syntax
|
||||
inputs = []
|
||||
streams = []
|
||||
audio_added = False
|
||||
|
||||
# Parse command elements
|
||||
i = 0
|
||||
while i < len(ffmpeg_cmd):
|
||||
if ffmpeg_cmd[i] == "-framerate":
|
||||
framerate = float(ffmpeg_cmd[i+1])
|
||||
i += 2
|
||||
elif ffmpeg_cmd[i] == "-i":
|
||||
if "frame_" in ffmpeg_cmd[i+1]: # Image sequence input
|
||||
video_input = ffmpeg.input(ffmpeg_cmd[i+1], framerate=framerate)
|
||||
streams.append(video_input.video)
|
||||
else: # Audio input
|
||||
audio_input = ffmpeg.input(ffmpeg_cmd[i+1])
|
||||
streams.append(audio_input.audio)
|
||||
audio_added = True
|
||||
i += 2
|
||||
elif ffmpeg_cmd[i] == "-vf":
|
||||
filters = ffmpeg_cmd[i+1].split(',')
|
||||
for f in filters:
|
||||
if 'scale=' in f:
|
||||
w, h = f.split('=')[1].split(':')
|
||||
video_input = video_input.filter('scale', w, h)
|
||||
i += 2
|
||||
elif ffmpeg_cmd[i] in ["-c:v", "-preset", "-crf", "-cq", "-b:v", "-pix_fmt"]:
|
||||
key = ffmpeg_cmd[i][1:]
|
||||
value = ffmpeg_cmd[i+1]
|
||||
if key == 'c:v':
|
||||
streams[-1] = streams[-1].output(vcodec=value)
|
||||
elif key == 'preset':
|
||||
streams[-1] = streams[-1].output(preset=value)
|
||||
elif key in ['crf', 'cq']:
|
||||
streams[-1] = streams[-1].output(**{key: value})
|
||||
elif key == 'b:v':
|
||||
streams[-1] = streams[-1].output(**{'b:v': value})
|
||||
elif key == 'pix_fmt':
|
||||
streams[-1] = streams[-1].output(pix_fmt=value)
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# Handle output
|
||||
output = ffmpeg.output(*streams, output_file)
|
||||
output.run(cmd=ffmpeg_path, overwrite_output=True)
|
||||
return True, "Success"
|
||||
|
||||
except ffmpeg.Error as e:
|
||||
return False, f"FFmpeg error: {e.stderr.decode()}"
|
||||
except Exception as e:
|
||||
return False, f"Error: {str(e)}"
|
||||
|
||||
def image_to_video(self, images, fps, name_prefix, use_python_ffmpeg=False, audio=None, FFMPEG_CONFIG_JSON=None):
|
||||
ffmpeg_config = self.parse_ffmpeg_config(FFMPEG_CONFIG_JSON)
|
||||
|
||||
format = "mp4"
|
||||
if ffmpeg_config and ffmpeg_config["output"]["container_format"] != "None":
|
||||
format = ffmpeg_config["output"]["container_format"]
|
||||
|
||||
name_prefix = os.path.splitext(name_prefix)[0]
|
||||
output_base = os.path.join("output", name_prefix)
|
||||
|
||||
existing_files = glob.glob(f"{output_base}_*.{format}")
|
||||
if existing_files:
|
||||
max_num = max([int(f.split('_')[-1].split('.')[0]) for f in existing_files])
|
||||
next_num = max_num + 1
|
||||
else:
|
||||
next_num = 1
|
||||
|
||||
output_file = f"{output_base}_{next_num:04d}.{format}"
|
||||
|
||||
temp_dir = "Bjornulf/temp_images_imgs2video"
|
||||
if os.path.exists(temp_dir) and os.path.isdir(temp_dir):
|
||||
for file in os.listdir(temp_dir):
|
||||
os.remove(os.path.join(temp_dir, file))
|
||||
os.rmdir(temp_dir)
|
||||
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
os.makedirs(os.path.dirname(output_file) if os.path.dirname(output_file) else ".", exist_ok=True)
|
||||
|
||||
for i, img_tensor in enumerate(images):
|
||||
img = Image.fromarray((img_tensor.cpu().numpy() * 255).astype(np.uint8))
|
||||
if format == "webm":
|
||||
img = img.convert("RGBA")
|
||||
img.save(os.path.join(temp_dir, f"frame_{i:04d}.png"))
|
||||
|
||||
temp_audio_file = None
|
||||
if audio is not None and (not ffmpeg_config or not ffmpeg_config["audio"]["enabled"]):
|
||||
temp_audio_file = os.path.join(temp_dir, "temp_audio.wav")
|
||||
waveform = audio['waveform'].squeeze().numpy()
|
||||
sample_rate = audio['sample_rate']
|
||||
sf.write(temp_audio_file, waveform, sample_rate)
|
||||
|
||||
ffmpeg_path = "ffmpeg"
|
||||
if ffmpeg_config and ffmpeg_config["ffmpeg"]["path"]:
|
||||
ffmpeg_path = ffmpeg_config["ffmpeg"]["path"]
|
||||
|
||||
ffmpeg_cmd = [
|
||||
ffmpeg_path,
|
||||
"-y",
|
||||
"-framerate", str(fps),
|
||||
"-i", os.path.join(temp_dir, "frame_%04d.png"),
|
||||
]
|
||||
|
||||
if temp_audio_file:
|
||||
ffmpeg_cmd.extend(["-i", temp_audio_file])
|
||||
|
||||
if ffmpeg_config and format == "webm" and ffmpeg_config["video"]["force_transparency"]:
|
||||
ffmpeg_cmd.extend([
|
||||
"-vf", "scale=iw:ih,format=rgba,split[s0][s1];[s0]lutrgb=r=0:g=0:b=0:a=0[transparent];[transparent][s1]overlay"
|
||||
])
|
||||
|
||||
if ffmpeg_config:
|
||||
if ffmpeg_config["video"]["codec"] != "None":
|
||||
ffmpeg_cmd.extend(["-c:v", ffmpeg_config["video"]["codec"]])
|
||||
|
||||
if ffmpeg_config["video"]["preset"] != "None":
|
||||
ffmpeg_cmd.extend(["-preset", ffmpeg_config["video"]["preset"]])
|
||||
|
||||
if ffmpeg_config["video"]["bitrate"]:
|
||||
ffmpeg_cmd.extend(["-b:v", ffmpeg_config["video"]["bitrate"]])
|
||||
|
||||
if ffmpeg_config["video"]["crf"]:
|
||||
if "nvenc" in (ffmpeg_config["video"]["codec"] or ""):
|
||||
ffmpeg_cmd.extend(["-cq", str(ffmpeg_config["video"]["crf"])])
|
||||
else:
|
||||
ffmpeg_cmd.extend(["-crf", str(ffmpeg_config["video"]["crf"])])
|
||||
|
||||
if ffmpeg_config["video"]["pixel_format"] != "None":
|
||||
ffmpeg_cmd.extend(["-pix_fmt", ffmpeg_config["video"]["pixel_format"]])
|
||||
|
||||
if ffmpeg_config["video"]["resolution"]:
|
||||
scale_filter = f"scale={ffmpeg_config['video']['resolution']['width']}:{ffmpeg_config['video']['resolution']['height']}"
|
||||
if format == "webm" and ffmpeg_config["video"]["force_transparency"]:
|
||||
current_filter_idx = ffmpeg_cmd.index("-vf") + 1
|
||||
current_filter = ffmpeg_cmd[current_filter_idx]
|
||||
ffmpeg_cmd[current_filter_idx] = scale_filter + "," + current_filter
|
||||
else:
|
||||
ffmpeg_cmd.extend(["-vf", scale_filter])
|
||||
|
||||
if ffmpeg_config["video"]["fps"]["enabled"]:
|
||||
ffmpeg_cmd.extend(["-r", str(ffmpeg_config["video"]["fps"]["force_fps"])])
|
||||
|
||||
if not ffmpeg_config["audio"]["enabled"]:
|
||||
ffmpeg_cmd.extend(["-an"])
|
||||
elif ffmpeg_config["audio"]["codec"] != "None" and temp_audio_file:
|
||||
ffmpeg_cmd.extend(["-c:a", ffmpeg_config["audio"]["codec"]])
|
||||
if ffmpeg_config["audio"]["bitrate"]:
|
||||
ffmpeg_cmd.extend(["-b:a", ffmpeg_config["audio"]["bitrate"]])
|
||||
else:
|
||||
if format == "mp4":
|
||||
ffmpeg_cmd.extend([
|
||||
"-c:v", "libx264",
|
||||
"-preset", "medium",
|
||||
"-crf", "19",
|
||||
"-pix_fmt", "yuv420p"
|
||||
])
|
||||
if temp_audio_file:
|
||||
ffmpeg_cmd.extend(["-c:a", "aac"])
|
||||
elif format == "webm":
|
||||
ffmpeg_cmd.extend([
|
||||
"-c:v", "libvpx-vp9",
|
||||
"-crf", "30",
|
||||
"-b:v", "0",
|
||||
"-pix_fmt", "yuva420p"
|
||||
])
|
||||
if temp_audio_file:
|
||||
ffmpeg_cmd.extend(["-c:a", "libvorbis"])
|
||||
|
||||
ffmpeg_cmd.append(output_file)
|
||||
|
||||
try:
|
||||
if use_python_ffmpeg:
|
||||
success, message = self.run_ffmpeg_python(ffmpeg_cmd, output_file, ffmpeg_path)
|
||||
comment = f"Python FFmpeg: {message}" if not success else f"Video created successfully with {'custom' if ffmpeg_config else 'default'} settings (Python FFmpeg)"
|
||||
else:
|
||||
subprocess.run(ffmpeg_cmd, check=True)
|
||||
comment = f"Video created successfully with {'custom' if ffmpeg_config else 'default'} FFmpeg settings"
|
||||
|
||||
print(f"Video created successfully: {output_file}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error creating video: {e}")
|
||||
comment = f"Error creating video: {e}"
|
||||
finally:
|
||||
print("Temporary files not removed for debugging purposes.")
|
||||
|
||||
return (comment,ffmpeg_cmd,)
|
||||
Reference in New Issue
Block a user