Update source code and add resources

This commit is contained in:
indigo 2025-12-29 09:04:25 +08:00
parent 7cc823c64e
commit 531726bd70
15 changed files with 4129 additions and 74 deletions

2
.gitignore vendored
View File

@ -2,6 +2,8 @@ venv
__pycache__/ __pycache__/
*.pyc *.pyc
*.pbtxt *.pbtxt
*.mp4
example/
lib/ lib/
build/ build/
vendor/ vendor/

View File

@ -7,7 +7,7 @@ Use google mediapipe to convert video to ARKit blendshape in maya
## Build and Install ## Build and Install
```python ```python
mayapy -m pip install mediapipe opencv-python mayapy -m pip install -r requirements.txt
``` ```
Set `MAYA_MODULE_PATH` to Video2ARKit.mod directory in Maya.env Set `MAYA_MODULE_PATH` to Video2ARKit.mod directory in Maya.env
```powershell ```powershell

BIN
requirements.txt Normal file

Binary file not shown.

View File

@ -1,12 +1,14 @@
import maya.api.OpenMaya as om import maya.api.OpenMaya as om
from V2A.Video2ARKitNode import Video2ARKitNode from V2A.Video2ARKitNode import Video2ARKitNode
from V2A.Video2ARKitCommand import Video2ARKitCommand from V2A.Video2ARKitCommand import V2ACommand
from V2A.Video2ARKitCommand import V2AConvertCommnad
def maya_useNewAPI(): def maya_useNewAPI():
pass pass
def initializePlugin(mobject): def initializePlugin(mobject):
mplugin = om.MFnPlugin(mobject, "YourName", "1.0", "Any") mplugin = om.MFnPlugin(mobject, "Video2ARKit", "1.0", "Any")
try: try:
mplugin.registerNode(Video2ARKitNode.NODE_NAME, Video2ARKitNode.NODE_ID, mplugin.registerNode(Video2ARKitNode.NODE_NAME, Video2ARKitNode.NODE_ID,
Video2ARKitNode.creator, Video2ARKitNode.initialize) Video2ARKitNode.creator, Video2ARKitNode.initialize)
@ -15,12 +17,20 @@ def initializePlugin(mobject):
om.MGlobal.displayError("Failed to register node") om.MGlobal.displayError("Failed to register node")
try: try:
mplugin.registerCommand(Video2ARKitCommand.COMMAND_NAME, mplugin.registerCommand(V2ACommand.COMMAND_NAME,
Video2ARKitCommand.creator, V2ACommand.creator,
Video2ARKitCommand.createSyntax) V2ACommand.createSyntax)
mplugin.registerCommand(V2AConvertCommnad.COMMAND_NAME,
V2AConvertCommnad.creator,
V2AConvertCommnad.createSyntax)
except: except:
import traceback
om.MGlobal.displayError(traceback.format_exc())
om.MGlobal.displayError("Failed to register command") om.MGlobal.displayError("Failed to register command")
def uninitializePlugin(mobject): def uninitializePlugin(mobject):
mplugin = om.MFnPlugin(mobject) mplugin = om.MFnPlugin(mobject)
try: try:
@ -29,6 +39,7 @@ def uninitializePlugin(mobject):
om.MGlobal.displayError("Failed to deregister node") om.MGlobal.displayError("Failed to deregister node")
try: try:
mplugin.deregisterCommand(Video2ARKitCommand.COMMAND_NAME) mplugin.deregisterCommand(V2ACommand.COMMAND_NAME)
mplugin.deregisterCommand(V2AConvertCommnad.COMMAND_NAME)
except: except:
om.MGlobal.displayError("Failed to deregister command") om.MGlobal.displayError("Failed to deregister command")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,134 @@
global proc AEInputDeviceNew (string $attr)
{
setUITemplate -pst attributeEditorTemplate;
optionMenuGrp -label "Input Device" a2vAEInputDevice;
string $deviceNames[] = `v2aCmds -d`;
for($device in $deviceNames)
{
menuItem -label $device;
}
// AEInputDeviceReplace($attr);
setUITemplate -ppt;
}
global proc AEInputDeviceReplace(string $attr)
{
string $value = `getAttr $attr`;
optionMenuGrp -e -v $value -changeCommand ("AEadjustInputDevice "+$attr) a2vAEInputDevice;
}
global proc AEadjustInputDevice(string $attr)
{
string $value = `optionMenuGrp -q -v a2vAEInputDevice`;
setAttr $attr $value -type "string";
}
global proc AEInputTypeNew(string $attr)
{
setUITemplate -pst attributeEditorTemplate;
optionMenuGrp -label "Input Type" a2vAEInputType;
menuItem -l "Video File"
menuItem -l "Camera Stream"
menuItem -l "Network Stream"
menuItem -l "VMC Portocol"
setUITemplate -ppt;
}
global proc AEInputTypeReplace(string $attr)
{
string $value = `getAttr $attr`;
optionMenuGrp -e -v $value -changeCommand ("AEadjustInputType "+$attr) a2vAEInputType;
}
global proc AEadjustInputType(string $attr)
{
string $value = `optionMenuGrp -q -sl a2vAEInputType` - 1;
setAttr $attr $value;
}
global AEVideoPathNew(string $attr)
{
setUITemplate -pst attributeEditorTemplate;
textFieldButtonGrp -l "Video Path" a2VAEVideoPath;
setUITemplate -ppt;
textFieldButtonGrp -e
-buttonCommand ("AEVideoPathBrowse "+$attr)
-changeCommand ("AEAdjustVideoPath "+$attr)
-forceChangeCommand
a2VAEVideoPath;
}
global AEVideoPathReplace(string $attr)
{
string $value = `getAttr $attr`;
textFieldButtonGrp -e -tx $value a2VAEVideoPath;
}
global AEVideoPathBrowse(string $attr)
{
string $value = `getAttr $attr`;
string $currentDir = `dirname $value`;
string $videoFilters = "MP4 Files (*.mp4);;MOV Files (*.mov);;AVI Files (*.avi);;MPEG Files (*.mpg);;All Files (*.*)";
string $result[] = `fileDialog2 -caption "Select Video File" -fileMode 1 -fileFilter $videoFilters -selectFileFilter "MP4 Files" -dialogStyle 2`
if($result){
textFieldButtonGrp -e -tx $result[0] a2VAEVideoPath;
setAttr $attr $result[0] -type "string";
}
}
global AEAdjustVideoPath(string $attr)
{
string $value = `textFieldButtonGrp -q -tx a2VAEVideoPath`;
setAttr $attr $value;
}
global proc AEVideo2ARKitTemplate(string $nodeName)
{
string $shapeNames[] = {
"browDownLeft", "browDownRight", "browInnerUp", "browOuterUpLeft", "browOuterUpRight",
"cheekPuff", "cheekSquintLeft", "cheekSquintRight", "eyeBlinkLeft", "eyeBlinkRight",
"eyeLookDownLeft", "eyeLookDownRight", "eyeLookInLeft", "eyeLookInRight", "eyeLookOutLeft",
"eyeLookOutRight", "eyeLookUpLeft", "eyeLookUpRight", "eyeSquintLeft", "eyeSquintRight",
"eyeWideLeft", "eyeWideRight", "jawForward", "jawLeft", "jawOpen", "jawRight",
"mouthClose", "mouthDimpleLeft", "mouthDimpleRight", "mouthFrownLeft", "mouthFrownRight",
"mouthFunnel", "mouthLeft", "mouthLowerDownLeft", "mouthLowerDownRight", "mouthPressLeft",
"mouthPressRight", "mouthPucker", "mouthRight", "mouthRollLower", "mouthRollUpper",
"mouthShrugLower", "mouthShrugUpper", "mouthSmileLeft", "mouthSmileRight", "mouthStretchLeft",
"mouthStretchRight", "mouthUpperUpLeft", "mouthUpperUpRight", "noseSneerLeft", "noseSneerRight", "tongueOut"
};
// Placeholder for future AE template customization
editorTemplate -beginScrollLayout;
editorTemplate -beginLayout "General" -collapse false;
editorTemplate -addControl "inTime";
editorTemplate -addControl "inputType";
// editorTemplate -addControl "inputDevice";
editorTemplate -callCustom "AEInputDeviceNew"
"AEInputDeviceReplace"
$nodeName;
editorTemplate -addControl "videoPath";
editorTemplate -addControl "networkUrl";
editorTemplate -addControl "networkPort";
editorTemplate -addControl "modelPath";
editorTemplate -addControl "processTrigger";
editorTemplate -suppress "attribute";
editorTemplate -endLayout;
editorTemplate -beginLayout "Weights" -collapse false;
for($shape in $shapeNames)
{
editorTemplate -addControl ($shape+"Weight");
}
editorTemplate -endLayout;
editorTemplate -suppress "parentList";
editorTemplate -suppress "previous";
editorTemplate -suppress "selfEnabled";
editorTemplate -suppress "parentEnabled";
editorTemplate -suppress "parentNumIsolatedChildren";
editorTemplate -suppress "localRender";
editorTemplate -addExtraControls;
editorTemplate -endScrollLayout;
}

View File

@ -3,42 +3,114 @@ import os
import json import json
from V2A.core import process from V2A.core import process
from V2A.core.device import CameraDeviceManager
def maya_useNewAPI(): def maya_useNewAPI():
pass pass
class V2AConvertCommnad(om.MPxCommand):
class Video2ARKitCommand(om.MPxCommand): COMMAND_NAME = "v2aConvert"
COMMAND_NAME = "v2aCmds" kInputVideo = 'i'
kVideoPath = 'v' kInputVideoLong = 'inputVideo'
kVideoPathLong = 'videoPath'
kModelPath = 'm' kModelPath = 'm'
kModelPathLong = 'modelPath' kModelPathLong = 'modelPath'
kOutputPath = 'o' kOutputPath = 'o'
kOutputPathLong = 'outputPath' kOutputPathLong = 'outputPath'
kReload = 'r' kVerb = 'v'
kReloadLong = 'reload' kVerbLong = 'verbose'
def __init__(self): def __init__(self):
om.MPxCommand.__init__(self) om.MPxCommand.__init__(self)
@staticmethod
def createSyntax(): def createSyntax():
syntax = om.MSyntax() syntax = om.MSyntax()
syntax.addFlag("v", "videoPath", om.MSyntax.kString) syntax.addFlag(V2AConvertCommnad.kInputVideo, V2AConvertCommnad.kInputVideoLong, om.MSyntax.kString)
syntax.addFlag("m", "modelPath", om.MSyntax.kString) syntax.addFlag(V2AConvertCommnad.kModelPath, V2AConvertCommnad.kModelPathLong, om.MSyntax.kString)
syntax.addFlag("o", "outputPath", om.MSyntax.kString) syntax.addFlag(V2AConvertCommnad.kOutputPath, V2AConvertCommnad.kOutputPathLong, om.MSyntax.kString)
syntax.addFlag("r", "reload", om.MSyntax.kString) syntax.addFlag(V2AConvertCommnad.kInfo, V2AConvertCommnad.kInfoLong, om.MSyntax.kBoolean)
return syntax return syntax
def doIt(self, args): def doIt(self, args):
argData = om.MArgParser(self.syntax(), args) argData = om.MArgParser(self.syntax(), args)
reload_object = None # 處理影片並返回快取結果
if not argData.isFlagSet(self.kInputVideo):
om.MGlobal.displayError("Video path is required.")
return
video_path = argData.flagArgumentString(self.kInputVideo, 0)
if not video_path:
om.MGlobal.displayError(f"Invalid video path : {video_path}")
if video_path and not os.path.exists(video_path):
om.MGlobal.displayError(f"Video path does not exists : {video_path}")
model_path = os.getenv('V2A_MODEL_PATH')
if argData.isFlagSet(self.kModelPath):
model_path = argData.flagArgumentString(self.kModelPath, 0)
else:
if argData.isFlagSet(self.kVerb):
om.MGlobal.displayInfo(f'Using default model : {model_path}')
if not model_path:
om.MGlobal.displayError(f'Invalid model path : {model_path}')
if model_path and not os.path.exists(model_path):
om.MGlobal.displayError(f'Model path does not exists : {model_path}')
output_path = None
if argData.isFlagSet(self.kOutputPath):
output_path = argData.flagArgumentString(self.kOutputPath, 0)
if not output_path:
om.MGlobal.displayError('Please specify output path.')
if video_path and output_path and model_path:
if argData.isFlagSet(self.kVerbLong):
om.MGlobal.displayInfo(f'Processing video: {video_path} with model: {model_path}')
om.MGlobal.displayInfo(f'Model: {model_path}')
om.MGlobal.displayInfo(f'Output path: {output_path}')
result = process.process_video(video_path, model_path=model_path, output_json=output_path)
if not result:
om.MGlobal.displayWarning('Output result is invalid')
@staticmethod
def creator():
return V2AConvertCommnad()
class V2ACommand(om.MPxCommand):
COMMAND_NAME = "v2aCmds"
kDeviceList = 'd'
kDeviceListLong = 'deviceList'
kDecodeMethod = 'm'
kDecodeMethodLong = 'decodeMethod'
kReload = 'r'
kReloadLong = 'reload'
def __init__(self):
om.MPxCommand.__init__(self)
@staticmethod
def createSyntax():
syntax = om.MSyntax()
syntax.useSelectionAsDefault(True)
syntax.addFlag(V2ACommand.kDeviceList, V2ACommand.kDeviceListLong)
syntax.addFlag(V2ACommand.kReload, V2ACommand.kReloadLong)
return syntax
def doIt(self, args):
argData = om.MArgParser(self.syntax(), args)
if argData.isFlagSet(self.kReload): if argData.isFlagSet(self.kReload):
# 重新加載快取 # 重新加載快取
node_name = argData.flagArgumentString("reload", 0) selection = argData.getObjectList() if not argData.getObjectList().isEmpty() else om.MGlobal.getActiveSelectionList()
s_list = om.MGlobal.getSelectionListByName(node_name) if node_name else om.MGlobal.getActiveSelectionList()
s_iter = om.MItSelectionList(s_list, om.MFn.kDependencyNode) s_iter = om.MItSelectionList(selection, om.MFn.kDependencyNode)
if not s_iter.isDone(): if not s_iter.isDone():
depend_node = s_iter.getDependNode() depend_node = s_iter.getDependNode()
node_fn = om.MFnDependencyNode(depend_node) node_fn = om.MFnDependencyNode(depend_node)
@ -47,37 +119,18 @@ class Video2ARKitCommand(om.MPxCommand):
trigger = node_fn.findPlug("processTrigger", True) trigger = node_fn.findPlug("processTrigger", True)
value = trigger.getValue() value = trigger.getValue()
trigger.setValue(value) trigger.setValue(value)
else: s_iter.next()
# 處理影片並返回快取結果
if argData.isFlagSet("videoPath"):
video_path = argData.flagArgumentString("videoPath", 0)
model_path = os.getenv('V2A_MODEL_PATH')
if argData.isFlagSet("modelPath"):
model_path = argData.flagArgumentString("modelPath", 0)
output_path = None
if argData.isFlagSet("outputPath"):
output_path = argData.flagArgumentString("outputPath", 0)
if not video_path:
om.MGlobal.displayError("Video path is required.")
return return
if not model_path:
om.MGlobal.displayError("Model path is required.") if argData.isFlagSet(self.kDecodeMethod):
self.setResult(process.support_decode_methods())
return return
if not output_path:
om.MGlobal.displayInfo("No output path specified. Results will not be saved to file.")
else:
om.MGlobal.displayInfo(f"Output path set to: {output_path}")
om.MGlobal.displayInfo(f"Processing video: {video_path} with model: {model_path}") if argData.isFlagSet(self.kDeviceList):
cache = process.process_video(video_path, model_path, output_path) device_manager = CameraDeviceManager()
if not output_path: self.setResult(device_manager.list_available_devices())
super(Video2ARKitCommand, self).setResult(json.dumps(cache)) return
else:
om.MGlobal.displayInfo(f"Output path set to: {output_path}")
# super(Video2ARKitCommand, self).setResult(json.dumps(cache))
# om.MGlobal.displayInfo("Video2ARKit processing command executed.")
@staticmethod @staticmethod
def creator(): def creator():
return Video2ARKitCommand() return V2ACommand()

View File

@ -1,4 +1,5 @@
import maya.api.OpenMaya as om import maya.api.OpenMaya as om
from maya import cmds, mel
import mediapipe as mp import mediapipe as mp
from mediapipe.tasks import python from mediapipe.tasks import python
from mediapipe.tasks.python import vision from mediapipe.tasks.python import vision
@ -30,12 +31,17 @@ class Video2ARKitNode(om.MPxNode):
# 屬性定義 # 屬性定義
aInTime = om.MObject() aInTime = om.MObject()
aInputType = om.MObject()
aInputDevice = om.MObject()
aNetworkUrl = om.MObject()
aNetworkPort = om.MObject()
aVideoPath = om.MObject() aVideoPath = om.MObject()
aModelPath = om.MObject() aModelPath = om.MObject()
aProcessTrigger = om.MObject() aProcessTrigger = om.MObject()
# 輸出屬性字典 # 輸出屬性字典
output_attrs = {} output_attrs = {}
output_weights = {}
def __init__(self): def __init__(self):
om.MPxNode.__init__(self) om.MPxNode.__init__(self)
@ -50,6 +56,7 @@ class Video2ARKitNode(om.MPxNode):
trigger = data_block.inputValue(self.aProcessTrigger).asBool() trigger = data_block.inputValue(self.aProcessTrigger).asBool()
video_path = data_block.inputValue(self.aVideoPath).asString() video_path = data_block.inputValue(self.aVideoPath).asString()
model_path = data_block.inputValue(self.aModelPath).asString() model_path = data_block.inputValue(self.aModelPath).asString()
input_type = data_block.inputValue(self.aInputType).asInt()
# 檢查是否需要重新分析影片 # 檢查是否需要重新分析影片
if trigger and not self._cache: if trigger and not self._cache:
@ -78,12 +85,19 @@ class Video2ARKitNode(om.MPxNode):
self.aProcessTrigger.setBool(current_value) self.aProcessTrigger.setBool(current_value)
om.MGlobal.displayInfo("Reload cache complete. cache populated.") om.MGlobal.displayInfo("Reload cache complete. cache populated.")
def postConstructor(self):
device = om.MGlobal.executeCommandStringResult('v2aCmds -d')
device_plug = om.MPlug(self.thisMObject(), self.aInputDevice)
device_plug.setString(device)
om.MGlobal.displayInfo(device)
@staticmethod @staticmethod
def creator(): def creator():
return Video2ARKitNode() return Video2ARKitNode()
@staticmethod @staticmethod
def initialize(): def initialize():
eAttr = om.MFnEnumAttribute()
nAttr = om.MFnNumericAttribute() nAttr = om.MFnNumericAttribute()
tAttr = om.MFnTypedAttribute() tAttr = om.MFnTypedAttribute()
uAttr = om.MFnUnitAttribute() uAttr = om.MFnUnitAttribute()
@ -92,6 +106,23 @@ class Video2ARKitNode(om.MPxNode):
Video2ARKitNode.aInTime = uAttr.create("inTime", "it", om.MFnUnitAttribute.kTime, 0.0) Video2ARKitNode.aInTime = uAttr.create("inTime", "it", om.MFnUnitAttribute.kTime, 0.0)
om.MPxNode.addAttribute(Video2ARKitNode.aInTime) om.MPxNode.addAttribute(Video2ARKitNode.aInTime)
# Input: 輸入類型 (預留未來擴展)
Video2ARKitNode.aInputType = eAttr.create("inputType", "ity", 0)
eAttr.addField("Video File", 0)
eAttr.addField("Camera Stream", 1)
eAttr.addField("Network Stream", 2)
eAttr.addField("VMC Protocol", 3)
om.MPxNode.addAttribute(Video2ARKitNode.aInputType)
Video2ARKitNode.aInputDevice = tAttr.create("inputDevice", "idev", om.MFnData.kString)
om.MPxNode.addAttribute(Video2ARKitNode.aInputDevice)
Video2ARKitNode.aNetworkUrl = tAttr.create("networkUrl", "nu", om.MFnData.kString)
om.MPxNode.addAttribute(Video2ARKitNode.aNetworkUrl)
Video2ARKitNode.aNetworkPort = nAttr.create("networkPort", "np", om.MFnNumericData.kInt, 8080)
om.MPxNode.addAttribute(Video2ARKitNode.aNetworkPort)
# 輸入:影片路徑 # 輸入:影片路徑
Video2ARKitNode.aVideoPath = tAttr.create("videoPath", "vp", om.MFnData.kString) Video2ARKitNode.aVideoPath = tAttr.create("videoPath", "vp", om.MFnData.kString)
om.MPxNode.addAttribute(Video2ARKitNode.aVideoPath) om.MPxNode.addAttribute(Video2ARKitNode.aVideoPath)
@ -115,6 +146,14 @@ class Video2ARKitNode(om.MPxNode):
nAttr.storable = False nAttr.storable = False
om.MPxNode.addAttribute(attr) om.MPxNode.addAttribute(attr)
Video2ARKitNode.output_attrs[shape_name] = attr Video2ARKitNode.output_attrs[shape_name] = attr
weight_attr = nAttr.create(f'{shape_name}Weight', f'{shape_name}_w', om.MFnNumericData.kFloat, 1.0)
nAttr.writable = True
nAttr.storable = True
nAttr.keyable = True
om.MPxNode.addAttribute(weight_attr)
om.MPxNode.attributeAffects(weight_attr, attr)
# 建立依賴 # 建立依賴
om.MPxNode.attributeAffects(Video2ARKitNode.aVideoPath, attr) om.MPxNode.attributeAffects(Video2ARKitNode.aVideoPath, attr)
om.MPxNode.attributeAffects(Video2ARKitNode.aInTime, attr) om.MPxNode.attributeAffects(Video2ARKitNode.aInTime, attr)

View File

@ -0,0 +1,13 @@
from maya import cmds, mel
def Video2ARKitTemplate(node):
cmds.editorTemplate(beginScrollLayout=True)
cmds.editorTemplate(beginLayout=True, label='General Settings')
cmds.editorTemplate(addControl='inTime', label='Current Time')
cmds.editorTemplate(addControl='inputType', label='Input Type')
cmds.editorTemplate(addControl='inputDevice', label='Input Device')
cmds.editorTemplate(addControl='videoPath', label='Video Path')
cmds.editorTemplate(addControl='modelPath', label='Model Path')
cmds.editorTemplate(addControl='processTrigger', label='Reload Cache')
cmds.editorTemplate(endLayout=True)
cmds.editorTemplate(endScrollLayout=True)

View File

@ -0,0 +1,88 @@
import av
import platform
import subprocess
import os
import logging
# Disable comtypes logger
logging.getLogger("comtypes").setLevel(logging.INFO)
class CameraDeviceManager:
@staticmethod
def list_available_devices():
"""列出當前系統所有可用的攝影機名稱或路徑"""
os_name = platform.system()
devices = []
if os_name == "Windows":
try:
from pygrabber.dshow_graph import FilterGraph
devices = FilterGraph().get_input_devices()
except ImportError:
print("請安裝 pygrabber 以獲取 Windows 裝置清單")
elif os_name == "Linux":
# 搜尋 /dev/video* 檔案
devices = [f"/dev/{d}" for d in os.listdir('/dev') if d.startswith('video')]
elif os_name == "Darwin": # macOS
# macOS 通常使用索引 "0", "1"
devices = ["0", "1", "2"]
return devices
@staticmethod
def get_pyav_config(device_identifier):
"""根據裝置與系統生成 PyAV 的開啟參數"""
os_name = platform.system()
if os_name == "Windows":
return {
"file": f"video={device_identifier}",
"format": "dshow",
"options": {"rtbufsize": "64M", "framerate": "30"}
}
elif os_name == "Darwin":
return {
"file": str(device_identifier),
"format": "avfoundation",
"options": {"framerate": "30", "pixel_format": "uyvy422"}
}
else: # Linux
return {
"file": device_identifier,
"format": "v4l2",
"options": {"framerate": "30"}
}
# --- Maya 插件內部的讀取迴圈範例 ---
def run_camera_capture(device_name, model_path, result_callback):
config = CameraDeviceManager.get_pyav_config(device_name)
try:
# 開啟攝影機容器
container = av.open(
config["file"],
format=config["format"],
options=config["options"]
)
stream = container.streams.video[0]
# 設定低延遲模式:丟棄舊影格,只拿最新影格
stream.thread_type = "AUTO"
for frame in container.decode(stream):
# 將 PyAV 影格轉為 RGB
rgb_image = frame.to_ndarray(format='rgb24')
# 這裡接入你的 MediaPipe 處理邏輯
# process_mediapipe(rgb_image)
# 這是為了防止 Maya 畫面卡死的關鍵:不要在迴圈裡做太重的運算
# if some_stop_condition: # 建立停止機制
# break
container.close()
except Exception as e:
print(f"PyAV Camera Error: {e}")

View File

@ -13,14 +13,35 @@ logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
DEFAULT_MODEL_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'models', 'face_landmarker.task')) DEFAULT_MODEL_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'models', 'face_landmarker.task'))
SUPPORT_DECODE_METHODS = ['cv2', 'av']
def process_video(video_path, model_path=DEFAULT_MODEL_PATH, output_json=None, method='av'): def support_decode_methods():
if method == 'cv2': return SUPPORT_DECODE_METHODS
return process_video_cv2(video_path, model_path, output_json)
else:
return process_video_av(video_path, model_path, output_json)
def process_video_cv2(video_path, model_path, output_json=None): def process_video(video_path, model_path=DEFAULT_MODEL_PATH, output_json=None, method='av', callback=None):
cache = {}
if method not in SUPPORT_DECODE_METHODS:
return {}
if method == ['cv2']:
cache = process_video_cv2(video_path, model_path, output_json=output_json, callback=callback)
if method in ['av']:
cache = process_video_av(video_path, model_path, output_json=output_json, callback=callback)
if output_json:
if output_json.endswith('.json'):
output_json_dir = os.path.dirname(output_json)
if not os.path.exists(output_json_dir):
os.makedirs(output_json_dir)
with open(output_json, 'w') as f:
json.dump(cache, f, indent=4)
logger.info(f"Output JSON saved to {output_json}")
return output_json
def process_video_cv2(video_path, model_path, output_json=None, callback=None):
"""Core MediaPipe processing logic using OpenCV""" """Core MediaPipe processing logic using OpenCV"""
# Initialize MediaPipe # Initialize MediaPipe
cache = {} cache = {}
@ -56,7 +77,7 @@ def process_video_cv2(video_path, model_path, output_json=None):
return cache return cache
def process_video_av(video_path, model_path, output_json=None): def process_video_av(video_path, model_path, output_json=None, callback=None):
# 這裡是處理影片的邏輯 # 這裡是處理影片的邏輯
"""Core MediaPipe processing logic using PyAV""" """Core MediaPipe processing logic using PyAV"""
cache = {} cache = {}
@ -95,17 +116,10 @@ def process_video_av(video_path, model_path, output_json=None):
frame_count += 1 frame_count += 1
if output_json:
if output_json.endswith('.json'):
output_json_dir = os.path.dirname(output_json)
if not os.path.exists(output_json_dir):
os.makedirs(output_json_dir)
with open(output_json, 'w') as f:
json.dump(cache, f, indent=4)
logger.info(f"Output JSON saved to {output_json}")
return cache return output_json
# return cache
if __name__ == "__main__": if __name__ == "__main__":
# 測試用主程式 # 測試用主程式

View File

@ -0,0 +1,5 @@
from maya import cmds
def init_ae_templetes():
pass