mirror of
https://github.com/justUmen/Bjornulf_custom_nodes.git
synced 2026-03-21 12:42:11 -03:00
330 lines
13 KiB
Python
330 lines
13 KiB
Python
import subprocess
|
|
import json
|
|
from pathlib import Path
|
|
import os
|
|
import re
|
|
try:
|
|
import ffmpeg
|
|
FFMPEG_PYTHON_AVAILABLE = True
|
|
except ImportError:
|
|
FFMPEG_PYTHON_AVAILABLE = False
|
|
|
|
class VideoDetails:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"video_path": ("STRING", {"default": "", "forceInput": True}),
|
|
"ffprobe_path": ("STRING", {"default": "ffprobe"}),
|
|
"use_python_ffmpeg": ("BOOLEAN", {"default": False}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "INT", "INT", "FLOAT", "INT", "INT", "STRING", "STRING",
|
|
"STRING", "STRING", "STRING", "STRING", "FLOAT", "STRING", "STRING")
|
|
RETURN_NAMES = ("filename", "video_path", "width", "height", "fps", "total_frames", "duration_seconds",
|
|
"video_codec", "video_bitrate", "pixel_format",
|
|
"audio_codec", "audio_bitrate", "container_format",
|
|
"duration_seconds_float", "full_info", "FFMPEG_CONFIG_JSON")
|
|
FUNCTION = "get_video_info"
|
|
CATEGORY = "Bjornulf"
|
|
|
|
def extract_bitrate(self, text):
|
|
"""Extract bitrate value from text."""
|
|
match = re.search(r'(\d+(?:\.\d+)?)\s*(?:kb/s|Kb/s|KB/s|Mb/s|MB/s)', text)
|
|
if match:
|
|
value = float(match.group(1))
|
|
if 'mb/s' in text.lower() or 'MB/s' in text:
|
|
value *= 1000
|
|
return f"{value:.0f}k"
|
|
return "N/A"
|
|
|
|
def create_json_output(self, filename, video_path, width, height, fps, total_frames,
|
|
duration_seconds, duration_seconds_float, video_codec,
|
|
video_bitrate, pixel_format, audio_codec, audio_bitrate,
|
|
container_format):
|
|
"""Create a JSON string containing all video information in FFmpegConfig format."""
|
|
video_info = {
|
|
"ffmpeg": {
|
|
"path": "ffmpeg", # Default value since this is from probe
|
|
# "use_python_ffmpeg": False # Default value since this is from probe
|
|
},
|
|
"video": {
|
|
"codec": video_codec if video_codec != "N/A" else "None",
|
|
"bitrate": video_bitrate if video_bitrate != "N/A" else "0k",
|
|
"preset": "None", # Not available from probe
|
|
"pixel_format": pixel_format if pixel_format != "N/A" else "None",
|
|
"crf": 0, # Not available from probe
|
|
"resolution": {
|
|
"width": width,
|
|
"height": height
|
|
},
|
|
"fps": {
|
|
"force_fps": fps,
|
|
"enabled": False # This is source fps, not forced
|
|
}
|
|
},
|
|
"audio": {
|
|
"enabled": audio_codec != "N/A" and audio_codec != "None",
|
|
"codec": audio_codec if audio_codec != "N/A" else "None",
|
|
"bitrate": audio_bitrate if audio_bitrate != "N/A" else "0k"
|
|
},
|
|
"output": {
|
|
"container_format": container_format if container_format != "N/A" else "None"
|
|
}
|
|
}
|
|
return json.dumps(video_info, indent=2)
|
|
|
|
def create_full_info_string(self, video_path, width, height, fps, total_frames,
|
|
duration_seconds, duration_seconds_float, video_codec,
|
|
video_bitrate, pixel_format, audio_codec, audio_bitrate,
|
|
container_format):
|
|
return f"""Video Information:
|
|
Filename: {os.path.basename(video_path)}
|
|
Resolution: {width}x{height}
|
|
FPS: {fps:.3f}
|
|
Total Frames: {total_frames}
|
|
Duration: {duration_seconds} seconds ({duration_seconds_float:.3f})
|
|
Video Codec: {video_codec}
|
|
Video Bitrate: {video_bitrate}
|
|
Pixel Format: {pixel_format}
|
|
Audio Codec: {audio_codec}
|
|
Audio Bitrate: {audio_bitrate}
|
|
Container Format: {container_format}
|
|
"""
|
|
|
|
def get_video_info_python_ffmpeg(self, video_path):
|
|
"""Get video info using python-ffmpeg."""
|
|
if not FFMPEG_PYTHON_AVAILABLE:
|
|
raise RuntimeError("python-ffmpeg is not installed. Please install it with 'pip install ffmpeg-python'")
|
|
|
|
try:
|
|
probe = ffmpeg.probe(video_path)
|
|
|
|
# Initialize variables with default values
|
|
width = 0
|
|
height = 0
|
|
fps = 0.0
|
|
total_frames = 0
|
|
duration_seconds = 0
|
|
duration_seconds_float = 0.0
|
|
video_codec = "N/A"
|
|
video_bitrate = "N/A"
|
|
pixel_format = "N/A"
|
|
audio_codec = "N/A"
|
|
audio_bitrate = "N/A"
|
|
container_format = "N/A"
|
|
|
|
# Extract format information
|
|
format_data = probe['format']
|
|
|
|
container_format = format_data.get('format_name', "N/A").split(',')[0]
|
|
|
|
# With:
|
|
format_name = format_data.get('format_name', "N/A")
|
|
if 'mp4' in format_name.lower():
|
|
container_format = 'mp4'
|
|
else:
|
|
container_format = format_name.split(',')[0]
|
|
|
|
duration_seconds_float = float(format_data.get('duration', 0))
|
|
duration_seconds = int(duration_seconds_float)
|
|
|
|
# Process streams
|
|
for stream in probe['streams']:
|
|
if stream['codec_type'] == 'video':
|
|
width = int(stream.get('width', 0))
|
|
height = int(stream.get('height', 0))
|
|
|
|
fps_str = stream.get('r_frame_rate', '')
|
|
if fps_str and fps_str != '0/0':
|
|
num, den = map(int, fps_str.split('/'))
|
|
fps = num / den if den != 0 else 0.0
|
|
|
|
total_frames = int(stream.get('nb_frames', 0))
|
|
if total_frames == 0 and fps > 0 and duration_seconds_float > 0:
|
|
total_frames = int(duration_seconds_float * fps)
|
|
|
|
video_codec = stream.get('codec_name', "N/A")
|
|
pixel_format = stream.get('pix_fmt', "N/A")
|
|
video_bitrate = f"{int(int(stream.get('bit_rate', 0))/1000)}k"
|
|
|
|
elif stream['codec_type'] == 'audio':
|
|
audio_codec = stream.get('codec_name', "N/A")
|
|
audio_bitrate = stream.get('bit_rate', "N/A")
|
|
if audio_bitrate != "N/A":
|
|
audio_bitrate = f"{int(int(audio_bitrate)/1000)}k"
|
|
|
|
filename = os.path.basename(video_path)
|
|
|
|
# Create full info string and JSON outputs
|
|
full_info = self.create_full_info_string(
|
|
video_path, width, height, fps, total_frames,
|
|
duration_seconds, duration_seconds_float, video_codec,
|
|
video_bitrate, pixel_format, audio_codec, audio_bitrate,
|
|
container_format
|
|
)
|
|
|
|
full_info_json = self.create_json_output(
|
|
filename, video_path, width, height, fps, total_frames,
|
|
duration_seconds, duration_seconds_float, video_codec,
|
|
video_bitrate, pixel_format, audio_codec, audio_bitrate,
|
|
container_format
|
|
)
|
|
|
|
return (
|
|
filename,
|
|
video_path,
|
|
width,
|
|
height,
|
|
fps,
|
|
total_frames,
|
|
duration_seconds,
|
|
video_codec,
|
|
video_bitrate,
|
|
pixel_format,
|
|
audio_codec,
|
|
audio_bitrate,
|
|
container_format,
|
|
duration_seconds_float,
|
|
full_info,
|
|
full_info_json
|
|
)
|
|
|
|
except Exception as e:
|
|
raise RuntimeError(f"Error analyzing video with python-ffmpeg: {str(e)}")
|
|
|
|
def get_video_info(self, video_path: str, ffprobe_path: str, use_python_ffmpeg: bool):
|
|
"""Get detailed information about a video file."""
|
|
video_path = os.path.abspath(video_path)
|
|
if not os.path.exists(video_path):
|
|
raise ValueError(f"Video file not found: {video_path}")
|
|
|
|
if use_python_ffmpeg:
|
|
return self.get_video_info_python_ffmpeg(video_path)
|
|
|
|
# Original ffmpeg/ffprobe implementation
|
|
probe_cmd = [
|
|
ffprobe_path,
|
|
'-v', 'quiet',
|
|
'-print_format', 'json',
|
|
'-show_format',
|
|
'-show_streams',
|
|
video_path
|
|
]
|
|
|
|
info_cmd = [
|
|
ffprobe_path,
|
|
'-i', video_path,
|
|
'-hide_banner'
|
|
]
|
|
|
|
try:
|
|
probe_result = subprocess.run(probe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
probe_data = json.loads(probe_result.stdout)
|
|
|
|
info_result = subprocess.run(info_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
ffmpeg_output = info_result.stderr
|
|
|
|
# Initialize variables with default values
|
|
width = 0
|
|
height = 0
|
|
fps = 0.0
|
|
total_frames = 0
|
|
duration_seconds = 0
|
|
duration_seconds_float = 0.0
|
|
video_codec = "N/A"
|
|
video_bitrate = "N/A"
|
|
pixel_format = "N/A"
|
|
audio_codec = "N/A"
|
|
audio_bitrate = "N/A"
|
|
container_format = "N/A"
|
|
|
|
# Extract information from probe data
|
|
if 'format' in probe_data:
|
|
format_data = probe_data['format']
|
|
# container_format = format_data.get('format_name', "N/A").split(',')[0]
|
|
container_format = format_data.get('format_name', "N/A").split(',')[0]
|
|
# With:
|
|
format_name = format_data.get('format_name', "N/A")
|
|
if 'mp4' in format_name.lower():
|
|
container_format = 'mp4'
|
|
else:
|
|
container_format = format_name.split(',')[0]
|
|
duration_seconds_float = float(format_data.get('duration', 0))
|
|
duration_seconds = int(duration_seconds_float)
|
|
|
|
# Process streams
|
|
for stream in probe_data.get('streams', []):
|
|
if stream['codec_type'] == 'video':
|
|
width = int(stream.get('width', 0))
|
|
height = int(stream.get('height', 0))
|
|
|
|
fps_str = stream.get('r_frame_rate', '')
|
|
if fps_str and fps_str != '0/0':
|
|
num, den = map(int, fps_str.split('/'))
|
|
fps = num / den if den != 0 else 0.0
|
|
|
|
total_frames = int(stream.get('nb_frames', 0))
|
|
if total_frames == 0 and fps > 0 and duration_seconds_float > 0:
|
|
total_frames = int(duration_seconds_float * fps)
|
|
|
|
video_codec = stream.get('codec_name', "N/A")
|
|
pixel_format = stream.get('pix_fmt', "N/A")
|
|
|
|
elif stream['codec_type'] == 'audio':
|
|
audio_codec = stream.get('codec_name', "N/A")
|
|
audio_bitrate = stream.get('bit_rate', "N/A")
|
|
if audio_bitrate != "N/A":
|
|
audio_bitrate = f"{int(int(audio_bitrate)/1000)}k"
|
|
|
|
# Extract video bitrate from ffmpeg output
|
|
video_bitrate = self.extract_bitrate(ffmpeg_output)
|
|
|
|
filename = os.path.basename(video_path)
|
|
|
|
# Create full info string
|
|
full_info = self.create_full_info_string(
|
|
video_path, width, height, fps, total_frames,
|
|
duration_seconds, duration_seconds_float, video_codec,
|
|
video_bitrate, pixel_format, audio_codec, audio_bitrate,
|
|
container_format
|
|
)
|
|
|
|
# Create JSON output
|
|
full_info_json = self.create_json_output(
|
|
filename, video_path, width, height, fps, total_frames,
|
|
duration_seconds, duration_seconds_float, video_codec,
|
|
video_bitrate, pixel_format, audio_codec, audio_bitrate,
|
|
container_format
|
|
)
|
|
|
|
return (
|
|
filename,
|
|
video_path,
|
|
width,
|
|
height,
|
|
fps,
|
|
total_frames,
|
|
duration_seconds,
|
|
video_codec,
|
|
video_bitrate,
|
|
pixel_format,
|
|
audio_codec,
|
|
audio_bitrate,
|
|
container_format,
|
|
duration_seconds_float,
|
|
full_info,
|
|
full_info_json
|
|
)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
raise RuntimeError(f"Error running ffmpeg/ffprobe: {e.stderr}")
|
|
except json.JSONDecodeError:
|
|
raise RuntimeError("Error parsing ffprobe output")
|
|
except Exception as e:
|
|
raise RuntimeError(f"Error analyzing video: {str(e)}")
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, **kwargs):
|
|
return float("NaN") |