Skip to content

Instantly share code, notes, and snippets.

@p2or
Last active September 13, 2024 08:16
Show Gist options
  • Save p2or/f9f855f3b6e8b533ce0d745bae92cd91 to your computer and use it in GitHub Desktop.
Save p2or/f9f855f3b6e8b533ce0d745bae92cd91 to your computer and use it in GitHub Desktop.
Loom Extension to remove the frame number from non-sequencial images within a given folder #BlenderAddon
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Loom Post-Render Rename Extension",
"description": "Remove frame numbers from non-sequencial images within a given folder",
"author": "Christian Brinkmann (p2or)",
"version": (0, 2),
"blender": (2, 82, 0),
"doc_url": "https://github.com/p2or/blender-loom",
"tracker_url": "https://github.com/p2or/blender-loom/issues",
"support": "COMMUNITY",
"category": "Development"
}
import bpy
import os
import re
from loom import replace_globals
def image_extension_callback(self, context):
items = []
for f in bpy.path.extensions_image:
items.append((f[1:], f[1:], ""))
return items
class LOOM_OT_utils_post_rename(bpy.types.Operator):
"""Remove frame numbers from non-sequencial images within a given folder"""
bl_idname = "loom.post_rename"
bl_label = "Remove Frame Numbers (Post-Render)"
bl_options = {'REGISTER'}
render_folder: bpy.props.StringProperty(
name="Folder Path",
description="Path to folder",
maxlen=1024,
subtype='DIR_PATH')
render_extension: bpy.props.EnumProperty(
name="Image Extension",
items=image_extension_callback)
open_folder: bpy.props.BoolProperty(
name="Open File Browser",
description="Open File Browser",
default=True)
def number_suffix(self, filename):
regex = re.compile(r'\d+\b')
digits = ([x for x in regex.findall(filename)])
return next(reversed(digits), None)
def execute(self, context):
if not self.render_folder:
self.report({'ERROR'}, "Please specify any valid folder")
return {"CANCELLED"}
if not os.path.isdir(self.render_folder):
self.report({'ERROR'}, "{} does not exist".format(self.render_folder))
return {"CANCELLED"}
if not self.render_extension:
self.report({'ERROR'}, "Please specify any image extension")
image_seqences = {}
for f in os.scandir(self.render_folder):
if not f.is_file():
continue
if not f.name.endswith(self.render_extension):
continue
filename_noext, extension = os.path.splitext(f.name)
frame_number = self.number_suffix(filename_noext)
if not frame_number or filename_noext == frame_number:
continue
image_seqences.setdefault(
filename_noext.replace(frame_number, ""),
[]).append(f.name)
renamed_images = []
existing_images = []
for k, v in image_seqences.items():
if len(v) == 1:
filename_noext, extension = os.path.splitext(v[0])
old_filepath = os.path.join(self.render_folder, v[0])
new_filepath = os.path.join(self.render_folder, k + extension)
if not os.path.isfile(new_filepath):
os.rename(old_filepath, new_filepath)
renamed_images.append(v[0])
else:
existing_images.append(v[0])
if len(existing_images) > 0:
self.report({'INFO'},
"'{}' could not be renamed (they already exist)".format(
", ".join(existing_images)))
if len(renamed_images) > 0:
self.report({'INFO'}, "Renamed '{}'".format(", ".join(renamed_images)))
else:
self.report({'INFO'}, "No potential image(s) found to rename")
if self.open_folder and len(renamed_images) > 0:
bpy.ops.loom.open_folder(folder_path=self.render_folder)
return {"FINISHED"}
def invoke(self, context, event):
scn = context.scene
prefs = context.preferences.addons["loom"].preferences
fp = bpy.path.abspath(scn.render.filepath)
output_folder, file_name = os.path.split(replace_globals(fp))
self.render_folder = os.path.realpath(output_folder)
self.render_extension = scn.render.file_extension[1:]
return context.window_manager.invoke_props_dialog(self,
width=(prefs.render_dialog_width))
def draw(self, context):
lum = context.scene.loom
layout = self.layout
layout.row().prop(self, "render_folder")
layout.row().prop(self, "render_extension")
layout.row().prop(self, "open_folder")
layout.separator(factor=0.05)
addon_keymaps = []
def register():
bpy.utils.register_class(LOOM_OT_utils_post_rename)
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc:
km = wm.keyconfigs.addon.keymaps.new(name='Screen', space_type='EMPTY')
kmi = km.keymap_items.new(LOOM_OT_utils_post_rename.bl_idname, 'F2', 'PRESS', ctrl=True, shift=True, alt=True)
addon_keymaps.append((km, kmi))
def unregister():
bpy.utils.unregister_class(LOOM_OT_utils_post_rename)
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
if __name__ == "__main__":
register()
# test call
#bpy.ops.loom.post_rename('INVOKE_DEFAULT')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment