Files
Bjornulf_custom_nodes/video_details.py
justumen 038842e80e 0.61
2024-11-27 14:37:10 +01:00

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