project_properties.py•20.1 kB
#!/usr/bin/env python3
"""
DaVinci Resolve MCP Server - Project Properties Utilities
This module provides functions for working with DaVinci Resolve project properties:
- Getting and setting project settings
- Managing project metadata
- Handling project-specific configurations
"""
import os
import logging
import json
from typing import Dict, List, Any, Optional, Union
# Configure logging
logger = logging.getLogger("davinci-resolve-mcp.project_properties")
# Common project properties with their types
PROJECT_PROPERTY_TYPES = {
# Timeline settings
"timelineFrameRate": "float",
"timelineResolutionWidth": "int",
"timelineResolutionHeight": "int",
"timelineOutputResolutionWidth": "int",
"timelineOutputResolutionHeight": "int",
"timelineInterlaceProcessing": "int",
# Color settings
"colorScienceMode": "int",
"timelineColorSpace": "string",
"timelineGamma": "string",
"inputDRT": "string",
"outputDRT": "string",
# Image processing
"superScaleEnabled": "bool",
"superScaleQuality": "int",
"noiseReductionEnabled": "bool",
"noiseReductionMode": "int",
"noiseReductionValue": "float",
# Format settings
"timelineAudioSampleRate": "int",
"timelineAudioBitDepth": "int",
"mediaPoolRelativePath": "bool",
# Cache settings
"CacheMode": "int",
"CacheClipMode": "int",
"OptimizedMediaMode": "int",
"ProxyMode": "int",
"ProxyQuality": "int",
"TimelineCacheMode": "int",
}
def get_all_project_properties(project_obj) -> Dict[str, Any]:
"""
Get all project properties and their values.
Args:
project_obj: Project object from DaVinci Resolve API
Returns:
Dictionary of all available project properties and their values
"""
if project_obj is None:
return {"error": "Invalid project object"}
try:
# Get all settings using empty string as key
all_settings = project_obj.GetSetting('')
# Check if we got a valid response
if all_settings is None or not isinstance(all_settings, dict):
logger.warning("GetSetting('') did not return expected dictionary")
# Fall back to getting known properties individually
properties = {}
for prop_name in PROJECT_PROPERTY_TYPES.keys():
try:
value = project_obj.GetSetting(prop_name)
properties[prop_name] = value
except Exception as e:
logger.debug(f"Error getting property {prop_name}: {str(e)}")
return properties
else:
# Return all settings
return all_settings
except Exception as e:
logger.error(f"Error getting project properties: {str(e)}")
return {"error": f"Error getting project properties: {str(e)}"}
def get_project_property(project_obj, property_name: str) -> Any:
"""
Get a specific project property value.
Args:
project_obj: Project object from DaVinci Resolve API
property_name: Name of the property to get
Returns:
Value of the specified property or error information
"""
if project_obj is None:
return {"error": "Invalid project object"}
try:
# Get the specified property
value = project_obj.GetSetting(property_name)
# Properly convert the value based on expected type
if property_name in PROJECT_PROPERTY_TYPES:
property_type = PROJECT_PROPERTY_TYPES[property_name]
if property_type == "int" and not isinstance(value, int):
try:
value = int(value)
except (ValueError, TypeError):
pass
elif property_type == "float" and not isinstance(value, float):
try:
value = float(value)
except (ValueError, TypeError):
pass
elif property_type == "bool" and not isinstance(value, bool):
# Convert string representations of boolean
if isinstance(value, str):
value = value.lower() in ("true", "yes", "1", "on")
return value
except Exception as e:
logger.error(f"Error getting project property {property_name}: {str(e)}")
return {"error": f"Error getting project property: {str(e)}"}
def set_project_property(project_obj, property_name: str, property_value: Any) -> bool:
"""
Set a project property value.
Args:
project_obj: Project object from DaVinci Resolve API
property_name: Name of the property to set
property_value: Value to set for the property
Returns:
True if successful, False otherwise
"""
if project_obj is None:
return False
try:
# Handle type conversion based on expected property type
if property_name in PROJECT_PROPERTY_TYPES:
property_type = PROJECT_PROPERTY_TYPES[property_name]
if property_type == "int":
try:
property_value = int(property_value)
except (ValueError, TypeError):
logger.warning(f"Invalid integer value for property {property_name}: {property_value}")
elif property_type == "float":
try:
property_value = float(property_value)
except (ValueError, TypeError):
logger.warning(f"Invalid float value for property {property_name}: {property_value}")
elif property_type == "bool":
if isinstance(property_value, str):
property_value = property_value.lower() in ("true", "yes", "1", "on")
property_value = bool(property_value)
# Set the property
return project_obj.SetSetting(property_name, property_value)
except Exception as e:
logger.error(f"Error setting project property {property_name}: {str(e)}")
return False
def get_timeline_format_settings(project_obj) -> Dict[str, Any]:
"""
Get timeline format settings for the project.
Args:
project_obj: Project object from DaVinci Resolve API
Returns:
Dictionary of timeline format settings
"""
if project_obj is None:
return {"error": "Invalid project object"}
try:
# Get relevant timeline format settings
settings = {}
format_properties = [
"timelineFrameRate",
"timelineResolutionWidth",
"timelineResolutionHeight",
"timelineOutputResolutionWidth",
"timelineOutputResolutionHeight",
"timelineInterlaceProcessing"
]
for prop in format_properties:
settings[prop] = get_project_property(project_obj, prop)
# Add frame rate details
if "timelineFrameRate" in settings:
fps = settings["timelineFrameRate"]
# Check if it's a drop frame rate
is_drop_frame = False
if isinstance(fps, (int, float)):
# Common drop frame rates: 29.97, 59.94
is_drop_frame = abs(fps - 29.97) < 0.01 or abs(fps - 59.94) < 0.01
settings["isDropFrame"] = is_drop_frame
# Add resolution name if standard
if "timelineResolutionWidth" in settings and "timelineResolutionHeight" in settings:
width = settings["timelineResolutionWidth"]
height = settings["timelineResolutionHeight"]
# Determine common resolution names
resolution_name = None
if width == 3840 and height == 2160:
resolution_name = "UHD 4K"
elif width == 1920 and height == 1080:
resolution_name = "FHD 1080p"
elif width == 1280 and height == 720:
resolution_name = "HD 720p"
elif width == 4096 and height in [2160, 2304]:
resolution_name = "DCI 4K"
elif width == 2048 and height in [1080, 1152]:
resolution_name = "DCI 2K"
if resolution_name:
settings["resolutionName"] = resolution_name
return settings
except Exception as e:
logger.error(f"Error getting timeline format settings: {str(e)}")
return {"error": f"Error getting timeline format settings: {str(e)}"}
def set_timeline_format(project_obj, width: int, height: int, frame_rate: float,
interlaced: bool = False) -> bool:
"""
Set timeline format (resolution and frame rate).
Args:
project_obj: Project object from DaVinci Resolve API
width: Timeline width in pixels
height: Timeline height in pixels
frame_rate: Timeline frame rate
interlaced: Whether the timeline should use interlaced processing
Returns:
True if successful, False otherwise
"""
if project_obj is None:
return False
try:
# Set timeline format properties
success = True
# Set resolution
if not set_project_property(project_obj, "timelineResolutionWidth", width):
success = False
if not set_project_property(project_obj, "timelineResolutionHeight", height):
success = False
# Set frame rate
if not set_project_property(project_obj, "timelineFrameRate", frame_rate):
success = False
# Set interlaced processing
interlace_value = 1 if interlaced else 0
if not set_project_property(project_obj, "timelineInterlaceProcessing", interlace_value):
success = False
return success
except Exception as e:
logger.error(f"Error setting timeline format: {str(e)}")
return False
def get_superscale_settings(project_obj) -> Dict[str, Any]:
"""
Get SuperScale settings for the project.
Args:
project_obj: Project object from DaVinci Resolve API
Returns:
Dictionary of SuperScale settings
"""
if project_obj is None:
return {"error": "Invalid project object"}
try:
# Get SuperScale settings
settings = {}
# Check if SuperScale is enabled
superscale_enabled = get_project_property(project_obj, "superScaleEnabled")
settings["enabled"] = bool(superscale_enabled)
# Get quality setting
quality = get_project_property(project_obj, "superScaleQuality")
settings["quality"] = quality
# Translate quality number to descriptive name
quality_names = {
0: "Auto",
1: "Better Quality", # Sharper but might have artifacts
2: "Smoother", # Less sharp but fewer artifacts
}
if quality in quality_names:
settings["qualityName"] = quality_names[quality]
# Add additional SuperScale properties if available
for prop in ["superScaleOverrideWidth", "superScaleOverrideHeight"]:
value = get_project_property(project_obj, prop)
if value is not None:
settings[prop] = value
return settings
except Exception as e:
logger.error(f"Error getting SuperScale settings: {str(e)}")
return {"error": f"Error getting SuperScale settings: {str(e)}"}
def set_superscale_settings(project_obj, enabled: bool, quality: int = 0) -> bool:
"""
Set SuperScale settings for the project.
Args:
project_obj: Project object from DaVinci Resolve API
enabled: Whether SuperScale is enabled
quality: SuperScale quality (0=Auto, 1=Better Quality, 2=Smoother)
Returns:
True if successful, False otherwise
"""
if project_obj is None:
return False
try:
# Validate quality value
if quality not in [0, 1, 2]:
logger.warning(f"Invalid SuperScale quality value: {quality}. Using 0 (Auto)")
quality = 0
# Set SuperScale properties
success = True
# Set enabled state
if not set_project_property(project_obj, "superScaleEnabled", enabled):
success = False
# Set quality
if not set_project_property(project_obj, "superScaleQuality", quality):
success = False
return success
except Exception as e:
logger.error(f"Error setting SuperScale settings: {str(e)}")
return False
def get_color_settings(project_obj) -> Dict[str, Any]:
"""
Get color science and color space settings for the project.
Args:
project_obj: Project object from DaVinci Resolve API
Returns:
Dictionary of color settings
"""
if project_obj is None:
return {"error": "Invalid project object"}
try:
# Get color-related settings
settings = {}
color_properties = [
"colorScienceMode",
"timelineColorSpace",
"timelineGamma",
"inputDRT",
"outputDRT"
]
for prop in color_properties:
value = get_project_property(project_obj, prop)
if value is not None:
settings[prop] = value
# Translate colorScienceMode to descriptive name
if "colorScienceMode" in settings:
mode = settings["colorScienceMode"]
mode_names = {
0: "DaVinci YRGB",
1: "DaVinci YRGB Color Managed",
2: "ACEScct"
}
if mode in mode_names:
settings["colorScienceName"] = mode_names[mode]
return settings
except Exception as e:
logger.error(f"Error getting color settings: {str(e)}")
return {"error": f"Error getting color settings: {str(e)}"}
def set_color_science_mode(project_obj, mode: str) -> bool:
"""
Set color science mode for the project.
Args:
project_obj: Project object from DaVinci Resolve API
mode: Color science mode ('YRGB', 'YRGB Color Managed', 'ACEScct', or numeric value)
Returns:
True if successful, False otherwise
"""
if project_obj is None:
return False
try:
# Map string modes to numeric values
mode_values = {
"YRGB": 0,
"DaVinci YRGB": 0,
"YRGB Color Managed": 1,
"DaVinci YRGB Color Managed": 1,
"ACEScct": 2,
"ACES": 2
}
# Get numeric value
mode_value = None
if isinstance(mode, int) and 0 <= mode <= 2:
mode_value = mode
elif isinstance(mode, str):
mode_value = mode_values.get(mode)
if mode_value is None:
logger.error(f"Invalid color science mode: {mode}")
return False
# Set the color science mode
return set_project_property(project_obj, "colorScienceMode", mode_value)
except Exception as e:
logger.error(f"Error setting color science mode: {str(e)}")
return False
def set_color_space(project_obj, color_space: str, gamma: str = None) -> bool:
"""
Set timeline color space and gamma.
Args:
project_obj: Project object from DaVinci Resolve API
color_space: Timeline color space (e.g., 'Rec.709', 'DCI-P3 D65', 'Rec.2020')
gamma: Timeline gamma (e.g., 'Rec.709 Gamma', 'Gamma 2.4')
Returns:
True if successful, False otherwise
"""
if project_obj is None:
return False
try:
success = True
# Set timeline color space
if not set_project_property(project_obj, "timelineColorSpace", color_space):
success = False
# Set gamma if provided
if gamma is not None:
if not set_project_property(project_obj, "timelineGamma", gamma):
success = False
return success
except Exception as e:
logger.error(f"Error setting color space: {str(e)}")
return False
def get_project_metadata(project_obj) -> Dict[str, Any]:
"""
Get project metadata.
Args:
project_obj: Project object from DaVinci Resolve API
Returns:
Dictionary of project metadata
"""
if project_obj is None:
return {"error": "Invalid project object"}
try:
metadata = {}
# Add basic project info
metadata["name"] = project_obj.GetName()
# Add project path if available
if hasattr(project_obj, "GetPath"):
metadata["path"] = project_obj.GetPath()
# Get current timeline
current_timeline = project_obj.GetCurrentTimeline()
if current_timeline:
metadata["currentTimeline"] = current_timeline.GetName()
# Add timeline count
timeline_count = project_obj.GetTimelineCount()
metadata["timelineCount"] = timeline_count
# Add frame rate and resolution
format_settings = get_timeline_format_settings(project_obj)
if "error" not in format_settings:
metadata.update(format_settings)
# Add color settings
color_settings = get_color_settings(project_obj)
if "error" not in color_settings:
metadata["colorSettings"] = color_settings
# Add SuperScale settings
superscale_settings = get_superscale_settings(project_obj)
if "error" not in superscale_settings:
metadata["superScale"] = superscale_settings
return metadata
except Exception as e:
logger.error(f"Error getting project metadata: {str(e)}")
return {"error": f"Error getting project metadata: {str(e)}"}
def get_project_info(project_obj) -> Dict[str, Any]:
"""
Get comprehensive project information including settings and metadata.
Args:
project_obj: Project object from DaVinci Resolve API
Returns:
Dictionary with project information
"""
if project_obj is None:
return {"error": "Invalid project object"}
try:
# Get project name
project_name = project_obj.GetName()
# Combine all project information
project_info = {
"name": project_name,
"metadata": get_project_metadata(project_obj),
"settings": get_all_project_properties(project_obj),
"timelines": []
}
# Add timeline information
timeline_count = project_obj.GetTimelineCount()
current_timeline = project_obj.GetCurrentTimeline()
current_timeline_name = current_timeline.GetName() if current_timeline else None
for i in range(1, timeline_count + 1):
timeline = project_obj.GetTimelineByIndex(i)
if timeline:
timeline_info = {
"name": timeline.GetName(),
"isCurrent": timeline.GetName() == current_timeline_name,
"duration": timeline.GetEndFrame() - timeline.GetStartFrame() + 1
}
project_info["timelines"].append(timeline_info)
return project_info
except Exception as e:
logger.error(f"Error getting project info: {str(e)}")
return {"error": f"Error getting project info: {str(e)}"}