Thu Mar 13, 2025 9:42 am
I have a script to add to render queue all duration makers on a timeline with their names and use the selected render preset.
import os
import re
import tkinter as tk
from tkinter import scrolledtext, filedialog, messagebox
import threading
import DaVinciResolveScript as dvr
def sanitize_filename(filename):
"""Sanitizes a filename to remove invalid characters for Windows."""
return re.sub(r'[<>:"/\\|?*]', '_', filename)
class RenderQueueApp:
def __init__(self, master):
self.master = master
self.master.title("Render Queue Manager")
self.master.attributes('-topmost', True) # Keep the window on top
self.timeline_name_var = tk.StringVar()
self.export_path_var = tk.StringVar()
self.is_rendering = False # Flag to control rendering process
self.markers = {} # Store markers to restart from the beginning
# Initialize Resolve connection
self.resolve = dvr.scriptapp("Resolve")
if not self.resolve:
self.log("Error: Could not connect to DaVinci Resolve.")
messagebox.showerror("Connection Error", "Could not connect to DaVinci Resolve.")
return
self.project_manager = self.resolve.GetProjectManager()
self.current_project = self.project_manager.GetCurrentProject()
# Create UI elements
self.create_widgets()
def create_widgets(self):
# Timeline name input
tk.Label(self.master, text="Enter Timeline Name:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
tk.Entry(self.master, textvariable=self.timeline_name_var, width=40).grid(row=0, column=1, padx=5, pady=5)
# Find Timeline button
tk.Button(self.master, text="Find Timeline", command=self.find_timeline).grid(row=0, column=2, padx=5, pady=5)
# Export path input
tk.Label(self.master, text="Enter Export Path:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
tk.Entry(self.master, textvariable=self.export_path_var, width=40).grid(row=1, column=1, padx=5, pady=5)
# Browse button
tk.Button(self.master, text="Browse", command=self.browse_path).grid(row=1, column=2, padx=5, pady=5)
# Find Path button
tk.Button(self.master, text="Find Path", command=self.find_path).grid(row=2, column=2, padx=5, pady=5)
# Start and Stop buttons
tk.Button(self.master, text="Start", command=self.start_rendering, width=10).grid(row=3, column=0, padx=5, pady=10)
tk.Button(self.master, text="Stop", command=self.stop_rendering, width=10).grid(row=3, column=1, padx=5, pady=10)
# Clear All button
tk.Button(self.master, text="Clear All", command=self.clear_all, width=10).grid(row=3, column=2, padx=5, pady=10)
# Log window
self.log_window = scrolledtext.ScrolledText(self.master, width=80, height=20, state='disabled')
self.log_window.grid(row=4, column=0, columnspan=3, padx=5, pady=5)
def log(self, message):
"""Log messages to the log window."""
self.log_window.configure(state='normal')
self.log_window.insert(tk.END, message + "\n")
self.log_window.configure(state='disabled')
self.log_window.yview(tk.END) # Auto-scroll to the bottom
def browse_path(self):
"""Open a dialog to browse for the export directory."""
selected_dir = filedialog.askdirectory()
if selected_dir:
self.export_path_var.set(selected_dir)
if self.validate_path(selected_dir):
self.log(f"Selected export path: {selected_dir}")
else:
self.log(f"Warning: The selected path '{selected_dir}' is invalid or does not exist.")
messagebox.showwarning("Invalid Path", f"The selected path '{selected_dir}' is invalid or does not exist.")
def find_path(self):
"""Validate the entered export path."""
path = self.export_path_var.get().strip()
if not path:
self.log("Error: Export path cannot be empty.")
messagebox.showerror("Input Error", "Export path cannot be empty.")
return
if self.validate_path(path):
self.log(f"Export path '{path}' is valid.")
messagebox.showinfo("Path Validated", f"The export path '{path}' is valid.")
else:
self.log(f"Warning: The export path '{path}' is invalid or does not exist.")
messagebox.showwarning("Invalid Path", f"The export path '{path}' is invalid or does not exist.")
def validate_path(self, path):
"""Validate if the provided path exists and is a directory."""
return os.path.isdir(path)
def find_timeline(self):
"""Check if the specified timeline exists."""
timeline_name = sanitize_filename(self.timeline_name_var.get()).strip()
export_path = self.export_path_var.get().strip()
if not timeline_name:
self.log("Error: Timeline name cannot be empty.")
messagebox.showerror("Input Error", "Timeline name cannot be empty.")
return
if not export_path:
self.log("Error: Export path cannot be empty.")
messagebox.showerror("Input Error", "Export path cannot be empty.")
return
if not self.validate_path(export_path):
self.log(f"Warning: The export path '{export_path}' is invalid or does not exist.")
messagebox.showwarning("Invalid Path", f"The export path '{export_path}' is invalid or does not exist.")
return
if not self.current_project:
self.log("Error: No project is currently open.")
messagebox.showerror("Project Error", "No project is currently open.")
return
# Get all timelines and check if the specified one exists
timeline_count = self.current_project.GetTimelineCount()
found_timeline = False
for idx in range(1, timeline_count + 1):
timeline = self.current_project.GetTimelineByIndex(idx)
if timeline and timeline.GetName() == timeline_name:
found_timeline = True
self.current_timeline = timeline
self.markers = timeline.GetMarkers() # Store markers for rendering later
self.log(f"Timeline '{timeline_name}' has been successfully found.")
messagebox.showinfo("Timeline Found", f"Timeline '{timeline_name}' has been successfully found.")
break
if not found_timeline:
self.log(f"Warning: No timeline found with the name '{timeline_name}'.")
messagebox.showwarning("Timeline Not Found", f"No timeline found with the name '{timeline_name}'.")
def start_rendering(self):
"""Start adding render jobs based on duration markers."""
timeline_name = self.timeline_name_var.get().strip()
export_path = self.export_path_var.get().strip()
if not hasattr(self, 'current_timeline'):
self.log("Warning: Please find a timeline first.")
messagebox.showwarning("Timeline Not Found", "Please find a timeline first.")
return
if not self.markers:
self.log("No duration markers found in the timeline.")
messagebox.showinfo("No Markers", "No duration markers found in the timeline.")
return
if not self.validate_path(export_path):
self.log(f"Error: The export path '{export_path}' is invalid or does not exist.")
messagebox.showerror("Invalid Path", f"The export path '{export_path}' is invalid or does not exist.")
return
if not self.is_rendering: # Only start if not already rendering
self.is_rendering = True # Set rendering flag
self.log("Rendering process has started.")
# Start rendering in a separate thread
threading.Thread(target=self.render_jobs, daemon=True).start()
else:
self.log("Info: Rendering is already in progress.")
messagebox.showinfo("Rendering In Progress", "Rendering is already in progress.")
def render_jobs(self):
"""Render jobs based on duration markers."""
export_path = self.export_path_var.get().strip()
try:
for marker_timecode, marker_data in self.markers.items():
if not self.is_rendering: # Check if we should stop rendering
break
if 'duration' not in marker_data or marker_data['duration'] <= 0:
self.log(f"Skipping marker at {marker_timecode}: Invalid or zero duration.")
continue
marker_name = marker_data.get('name', 'Unnamed_Marker')
sanitized_marker_name = sanitize_filename(marker_name) # No .mp4 appended
start_time = marker_timecode
duration_frames = marker_data['duration']
end_time = start_time + duration_frames
render_settings = {
"SelectAllFrames": False,
"MarkIn": start_time,
"MarkOut": end_time,
"TargetDir": export_path,
"CustomName": sanitized_marker_name,
"renderPreset": "YT_EU_DEF"
}
if not self.current_project.SetRenderSettings(render_settings):
self.log(f"Error: Failed to set render settings for marker: {marker_name}")
continue
job_id = self.current_project.AddRenderJob()
if job_id is None:
self.log(f"Failed to add render job for marker: {marker_name}")
else:
self.log(f"Added render job for marker: {marker_name} with job ID: {job_id}")
if not self.is_rendering:
# If rendering was stopped before completion
self.log("Rendering process was stopped before completion.")
messagebox.showinfo("Rendering Stopped", "Rendering process was stopped before completion.")
else:
self.log("All valid markers have been added to the Render Queue.")
messagebox.showinfo("Rendering Complete", "All valid markers have been added to the Render Queue.")
except Exception as e:
self.log(f"Error occurred during rendering: {str(e)}")
messagebox.showerror("Rendering Error", f"An error occurred during rendering: {str(e)}")
self.is_rendering = False # Ensure we stop rendering on error
finally:
self.is_rendering = False # Reset rendering flag after completion or error
def stop_rendering(self):
"""Stop rendering process."""
if not self.is_rendering:
self.log("Info: Rendering is already stopped.")
messagebox.showinfo("Rendering Stopped", "Rendering is already stopped.")
return
try:
# Set rendering flag to False and log stopping action.
self.is_rendering = False
# Log stopping action.
self.log("Rendering process has been stopped.")
messagebox.showinfo("Rendering Stopped", "Rendering process has been stopped.")
except Exception as e:
self.log(f"Error occurred while stopping: {str(e)}")
messagebox.showerror("Stop Error", f"An error occurred while stopping rendering: {str(e)}")
def clear_all(self):
"""Clear all input fields and the log window."""
if self.is_rendering:
response = messagebox.askyesno("Confirm Clear", "Rendering is in progress. Do you want to stop rendering and clear all fields?")
if response:
self.stop_rendering()
else:
return
self.timeline_name_var.set("")
self.export_path_var.set("")
self.markers = {}
self.current_timeline = None
self.log_window.configure(state='normal')
self.log_window.delete('1.0', tk.END)
self.log_window.configure(state='disabled')
self.log("All fields have been cleared.")
messagebox.showinfo("Cleared", "All fields have been cleared.")
if __name__ == "__main__":
root = tk.Tk()
app = RenderQueueApp(root)
root.mainloop()