Files
MontagenAI-ComfyUI-Montagen/server/VideoMetadataCache.py
T
2025-01-14 15:14:54 +08:00

224 lines
8.3 KiB
Python

import os
import hashlib
import subprocess
import json
import logging
import folder_paths
class VideoMetadataCache:
instance = None
template = """
{
"last_node_id": 1,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "MontagenDefaultLoadVideo",
"pos": [
1009.0526733398438,
565.4109497070312
],
"size": [
315,
58
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"Node name for S&R": "MontagenDefaultLoadVideo"
},
"widgets_values": [
"{relative_path}"
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.7513148009015781,
"offset": [
-563.6113369820617,
-206.78960955150157
]
}
},
"version": 0.4
}
"""
def __init__(self):
self.cache = {}
VideoMetadataCache.instance = self
@staticmethod
def hash(video_path, block_size=65536):
file_size = os.path.getsize(video_path)
m = hashlib.sha256()
with open(video_path, "rb") as f:
# 读取文件开头部分
m.update(f.read(block_size))
# 读取文件中间部分
if file_size > block_size * 2:
f.seek(file_size // 2)
m.update(f.read(block_size))
# 读取文件结尾部分
if file_size > block_size:
f.seek(-block_size, os.SEEK_END)
m.update(f.read(block_size))
return m.hexdigest()
def getMetadata(self, video_path):
video_hash = self.hash(video_path)
if video_hash in self.cache:
return self.cache[video_hash]
else:
metadata = self._extractMetadata(video_path)
self.cache[video_hash] = metadata
return metadata
def _extractMetadata(self, video_path):
if self._isVideoFile(video_path):
try:
metadata = self._parseVideoMetadata(video_path)
if metadata:
if "workflow" in metadata:
return metadata["workflow"]
except Exception as e:
logging.exception(f"Error parsing video metadata: {e}")
cmd = ["ffmpeg", "-i", video_path, "-f", "ffmetadata", "-"]
try:
result = subprocess.run(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
if result.returncode != 0:
raise Exception(f"ffmpeg error: {result.stderr}")
metadata_str = result.stdout
metadata_json = self._parseFfmetadata(metadata_str)
if "workflow" in metadata_json:
return metadata_json["workflow"]
raise Exception(f"empty workflow metadata")
except Exception as e:
logging.exception(f"Error parsing video metadata: {e}")
relative_path = os.path.relpath(
video_path, folder_paths.get_output_directory()
).strip("/")
relative_path = json.dumps(relative_path).strip('"')
return json.loads(self.template.replace("{relative_path}", relative_path))
def _parseFfmetadata(self, metadata_str):
metadata = {}
lines = metadata_str.splitlines()
for line in lines:
if line.startswith(";"):
continue
key_value = line.split("=", 1)
if len(key_value) == 2:
key, value = key_value
# 尝试解析值为 JSON
try:
parsed_value = json.loads(value)
metadata[key] = parsed_value
except json.JSONDecodeError:
metadata[key] = value
return metadata
def _parseVideoMetadata(self, video_path):
buffer_size = 65536 # 64KB buffer size
try:
with open(video_path, "rb") as f:
buffer = f.read(buffer_size)
if buffer[:4] == b"\x1A\x45\xDF\xA3":
# webm
offset = 4 + 8
f.seek(offset)
while True:
i = 0
buffer = f.read(buffer_size)
bufferlen = len(buffer)
if not buffer:
break
while i < (bufferlen - 16):
if buffer[i : i + 2] == b"\x44\x87":
f.seek(i - 7 - bufferlen, os.SEEK_CUR)
buffer = f.read(buffer_size)
bufferlen = len(buffer)
i = 7
name = buffer[i - 7 : i].decode("utf-8")
if name == "COMMENT":
vint = int.from_bytes(buffer[i + 2 : i + 6], "big")
n_octets = (vint.bit_length() + 7) // 8
if n_octets < 4:
length = (vint >> (8 * (4 - n_octets))) & ~(
1 << (7 * n_octets)
)
f.seek(
i + 2 + n_octets - bufferlen, os.SEEK_CUR
)
content = f.read(length)
content = content.decode("utf-8")
try:
json_content = json.loads(content)
return json_content
except json.JSONDecodeError:
pass
i = 8
i = i + 1
else:
if buffer[4:8] == b"ftyp" and buffer[8:12] == b"isom":
bufferlen = 0
f.seek(-buffer_size, os.SEEK_END)
while f.tell() > 16:
buffer = f.read(buffer_size)
bufferlen = len(buffer)
i = bufferlen - 4
while i >= 0:
if buffer[i : i + 4] == b"data":
f.seek(i - 8 - bufferlen, os.SEEK_CUR)
buffer = f.read(buffer_size)
bufferlen = len(buffer)
i = 0
if buffer[0:4] == b"\xa9cmt":
size = int.from_bytes(buffer[4:8], "big") - 16
f.seek(20 - len(buffer), os.SEEK_CUR)
buffer = f.read(size)
content = buffer[0:size].decode("utf-8")
try:
json_content = json.loads(content)
return json_content
except json.JSONDecodeError:
pass
i = i - 1
f.seek(-buffer_size - bufferlen, os.SEEK_CUR)
else:
raise ValueError("Unknown video format")
except Exception as e:
logging.exception("Error parsing video metadata")
return {}
def _isVideoFile(self, file):
# Implement your logic to check if the file is a video file
return file.endswith((".mp4",))
VideoMetadataCache()