VSE edit right click images with Gimp
I wrote a addon that allows images on the VSE to be edited with gimp. by right clicking on it I like it to be part of blender, however i dont use 2 fact auth .. and without unable to give away may addon
(requesting a simpler contribution method)
Below is the script:
""" Open in GIMP - Blender VSE Addon Opens the selected image/video strip's source file in GIMP for editing.
Installation:
- Edit → Preferences → Add-ons
- Click "Install..."
- Select this .py file
- Enable the addon "Sequencer: Open in GIMP"
Usage:
- Select an image strip in the VSE
- Right-click → "Open in GIMP" OR use the shortcut: Ctrl+Shift+G OR go to Strip menu → "Open in GIMP"
Configuration:
- Set your GIMP path in the addon preferences if it's not auto-detected
"""
bl_info = { "name": "Sequencer: Open in GIMP", "author": "Claude", "version": (1, 0, 1), "blender": (5, 0, 0), "location": "Sequencer > Strip > Open in GIMP, or Right-click menu", "description": "Open selected image strip in GIMP for external editing", "category": "Sequencer", }
import bpy import subprocess import os import platform
def get_default_gimp_path(): """Try to find GIMP installation path based on OS.""" system = platform.system()
if system == "Windows":
# Common Windows installation paths
possible_paths = [
r"C:\Program Files\GIMP 2\bin\gimp-2.10.exe",
r"C:\Program Files\GIMP 2\bin\gimp-2.99.exe",
r"C:\Program Files\GIMP 3\bin\gimp-3.0.exe",
r"C:\Program Files (x86)\GIMP 2\bin\gimp-2.10.exe",
os.path.expandvars(r"%LOCALAPPDATA%\Programs\GIMP 2\bin\gimp-2.10.exe"),
]
for path in possible_paths:
if os.path.exists(path):
return path
# Fallback: try just "gimp" and hope it's in PATH
return "gimp"
elif system == "Darwin": # macOS
possible_paths = [
"/Applications/GIMP.app/Contents/MacOS/gimp",
"/Applications/GIMP-2.10.app/Contents/MacOS/gimp",
]
for path in possible_paths:
if os.path.exists(path):
return path
return "gimp"
else: # Linux
return "gimp"
class OpenInGIMPPreferences(bpy.types.AddonPreferences): bl_idname = package
gimp_path: bpy.props.StringProperty(
name="GIMP Executable Path",
description="Path to GIMP executable. Leave empty for auto-detect",
default="",
subtype='FILE_PATH'
)
def draw(self, context):
layout = self.layout
layout.prop(self, "gimp_path")
layout.label(text="Leave empty to auto-detect GIMP location")
# Show detected path
if not self.gimp_path:
detected = get_default_gimp_path()
layout.label(text=f"Auto-detected: {detected}", icon='INFO')
class SEQUENCER_OT_open_in_gimp(bpy.types.Operator): """Open the selected strip's source image in GIMP""" bl_idname = "sequencer.open_in_gimp" bl_label = "Open in GIMP" bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
# Check if we have an active strip that's an image or movie
if context.scene.sequence_editor is None:
return False
strip = context.scene.sequence_editor.active_strip
if strip is None:
return False
return strip.type in {'IMAGE', 'MOVIE'}
def execute(self, context):
strip = context.scene.sequence_editor.active_strip
if strip is None:
self.report({'ERROR'}, "No strip selected")
return {'CANCELLED'}
# Get the file path
filepath = None
if strip.type == 'IMAGE':
# For image strips, get directory + filename
directory = bpy.path.abspath(strip.directory)
if strip.elements:
# For single-image strips (like from our import script),
# just get the first element
# For multi-image sequences, get based on playhead position
if len(strip.elements) == 1:
# Single image strip - just open it
filename = strip.elements[0].filename
else:
# Multi-image sequence - calculate which frame
current_frame = context.scene.frame_current
strip_start = strip.frame_final_start
strip_offset = strip.frame_offset_start
element_index = current_frame - strip_start + strip_offset
element_index = max(0, min(element_index, len(strip.elements) - 1))
filename = strip.elements[element_index].filename
filepath = os.path.join(directory, filename)
else:
self.report({'ERROR'}, "Image strip has no elements")
return {'CANCELLED'}
elif strip.type == 'MOVIE':
filepath = bpy.path.abspath(strip.filepath)
self.report({'WARNING'}, "Opening video file - GIMP may not handle this well")
if not filepath:
self.report({'ERROR'}, "Could not determine file path")
return {'CANCELLED'}
if not os.path.exists(filepath):
self.report({'ERROR'}, f"File not found: {filepath}")
return {'CANCELLED'}
# Get GIMP path from preferences
prefs = context.preferences.addons[__package__].preferences
gimp_path = prefs.gimp_path if prefs.gimp_path else get_default_gimp_path()
# Open in GIMP
try:
if platform.system() == "Windows":
# On Windows, use subprocess with shell=False for security
subprocess.Popen([gimp_path, filepath])
else:
# On Linux/Mac
subprocess.Popen([gimp_path, filepath])
self.report({'INFO'}, f"Opened in GIMP: {os.path.basename(filepath)}")
except FileNotFoundError:
self.report({'ERROR'}, f"GIMP not found at: {gimp_path}\nSet the correct path in addon preferences")
return {'CANCELLED'}
except Exception as e:
self.report({'ERROR'}, f"Failed to open GIMP: {str(e)}")
return {'CANCELLED'}
return {'FINISHED'}
class SEQUENCER_OT_open_source_folder(bpy.types.Operator): """Open the folder containing the strip's source file""" bl_idname = "sequencer.open_source_folder" bl_label = "Open Source Folder" bl_options = {'REGISTER'}
@classmethod
def poll(cls, context):
if context.scene.sequence_editor is None:
return False
strip = context.scene.sequence_editor.active_strip
if strip is None:
return False
return strip.type in {'IMAGE', 'MOVIE', 'SOUND'}
def execute(self, context):
strip = context.scene.sequence_editor.active_strip
# Get directory
if strip.type == 'IMAGE':
directory = bpy.path.abspath(strip.directory)
elif strip.type in {'MOVIE', 'SOUND'}:
directory = os.path.dirname(bpy.path.abspath(strip.filepath))
else:
self.report({'ERROR'}, "Unsupported strip type")
return {'CANCELLED'}
if not os.path.exists(directory):
self.report({'ERROR'}, f"Folder not found: {directory}")
return {'CANCELLED'}
# Open folder in system file browser
system = platform.system()
try:
if system == "Windows":
os.startfile(directory)
elif system == "Darwin":
subprocess.Popen(["open", directory])
else:
subprocess.Popen(["xdg-open", directory])
self.report({'INFO'}, f"Opened folder: {directory}")
except Exception as e:
self.report({'ERROR'}, f"Failed to open folder: {str(e)}")
return {'CANCELLED'}
return {'FINISHED'}
Menu drawing functions
def draw_strip_menu(self, context): """Add to Strip menu""" layout = self.layout layout.separator() layout.operator("sequencer.open_in_gimp", icon='IMAGE_DATA') layout.operator("sequencer.open_source_folder", icon='FILE_FOLDER')
def draw_context_menu(self, context): """Add to right-click context menu""" layout = self.layout layout.separator() layout.operator("sequencer.open_in_gimp", icon='IMAGE_DATA') layout.operator("sequencer.open_source_folder", icon='FILE_FOLDER')
Keymap
addon_keymaps = []
def register_keymaps(): wm = bpy.context.window_manager kc = wm.keyconfigs.addon
if kc:
# Sequencer keymap
km = kc.keymaps.new(name='Sequencer', space_type='SEQUENCE_EDITOR')
# Ctrl+Shift+G to open in GIMP
kmi = km.keymap_items.new(
"sequencer.open_in_gimp",
type='G',
value='PRESS',
ctrl=True,
shift=True
)
addon_keymaps.append((km, kmi))
# Ctrl+Shift+F to open folder
kmi = km.keymap_items.new(
"sequencer.open_source_folder",
type='F',
value='PRESS',
ctrl=True,
shift=True
)
addon_keymaps.append((km, kmi))
def unregister_keymaps(): for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) addon_keymaps.clear()
Registration
classes = [ OpenInGIMPPreferences, SEQUENCER_OT_open_in_gimp, SEQUENCER_OT_open_source_folder, ]
def register(): for cls in classes: bpy.utils.register_class(cls)
# Add to menus
bpy.types.SEQUENCER_MT_strip.append(draw_strip_menu)
bpy.types.SEQUENCER_MT_context_menu.append(draw_context_menu)
# Register keymaps
register_keymaps()
def unregister(): # Unregister keymaps unregister_keymaps()
# Remove from menus
bpy.types.SEQUENCER_MT_strip.remove(draw_strip_menu)
bpy.types.SEQUENCER_MT_context_menu.remove(draw_context_menu)
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if name == " main ": register()
2 Comments
what if we want krita as our editor.
Hello, Welcome to RCS.
This platform is meant for posting and discussing feature proposals for Blender. To share or discuss about an addon you made, please use other platforms, such as Blender Artists Community or Blender Today . Though this platform really isn't ideal for long code snippets...