Skip to content

Commit

Permalink
Blender thumbnail support (#273)
Browse files Browse the repository at this point in the history
* Update thumb_renderer.py

Included support for rendering blender thumbnails

* Add files via upload

Add functions that get the thumbnail's data

* Update thumb_renderer.py

* Update blender_thumbnailer.py

* Update thumb_renderer.py

* Update thumb_renderer.py

Changed where imports are according to feedback

* Update thumb_renderer.py

Changed blender thumbnail function name to reduce ambiguity

* Update blender_thumbnailer.py

Updated function name

* Update blender_thumbnailer.py

* Update blender_thumbnailer.py

Ruff format

* Update thumb_renderer.py

Ruff format

* Update constants.py

Add .blend1, 2, 3 etc file support

* Update blender_thumbnailer.py

Refactor to follow requested changes

* Update thumb_renderer.py

More refactoring

* Update blender_thumbnailer.py

Ruff format

* Update thumb_renderer.py

Ruff format
  • Loading branch information
050011-code authored Jul 3, 2024
1 parent 6862f89 commit 883354b
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
35 changes: 35 additions & 0 deletions tagstudio/src/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,41 @@
".7z",
".s7z",
]
BLENDER_TYPES: list[str] = [
".blend",
".blend1",
".blend2",
".blend3",
".blend4",
".blend5",
".blend6",
".blend7",
".blend8",
".blend9",
".blend10",
".blend11",
".blend12",
".blend13",
".blend14",
".blend15",
".blend16",
".blend17",
".blend18",
".blend19",
".blend20",
".blend21",
".blend22",
".blend23",
".blend24",
".blend25",
".blend26",
".blend27",
".blend28",
".blend29",
".blend30",
".blend31",
".blend32",
]
PROGRAM_TYPES: list[str] = [".exe", ".app"]
SHORTCUT_TYPES: list[str] = [".lnk", ".desktop", ".url"]

Expand Down
109 changes: 109 additions & 0 deletions tagstudio/src/qt/helpers/blender_thumbnailer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env python3

# ##### 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 #####

# <pep8 compliant>


## This file is a modified script that gets the thumbnail data stored in a blend file


import struct
from PIL import (
Image,
ImageOps,
)
import gzip
import os


def blend_extract_thumb(path):
REND = b"REND"
TEST = b"TEST"

blendfile = open(path, "rb")

head = blendfile.read(12)

if head[0:2] == b"\x1f\x8b": # gzip magic
blendfile.close()
blendfile = gzip.GzipFile("", "rb", 0, open(path, "rb"))
head = blendfile.read(12)

if not head.startswith(b"BLENDER"):
blendfile.close()
return None, 0, 0

is_64_bit = head[7] == b"-"[0]

# true for PPC, false for X86
is_big_endian = head[8] == b"V"[0]

# blender pre 2.5 had no thumbs
if head[9:11] <= b"24":
return None, 0, 0

sizeof_bhead = 24 if is_64_bit else 20
int_endian = ">i" if is_big_endian else "<i"
int_endian_pair = int_endian + "i"

while True:
bhead = blendfile.read(sizeof_bhead)

if len(bhead) < sizeof_bhead:
return None, 0, 0

code = bhead[:4]
length = struct.unpack(int_endian, bhead[4:8])[0] # 4 == sizeof(int)

if code == REND:
blendfile.seek(length, os.SEEK_CUR)
else:
break

if code != TEST:
return None, 0, 0

try:
x, y = struct.unpack(int_endian_pair, blendfile.read(8)) # 8 == sizeof(int) * 2
except struct.error:
return None, 0, 0

length -= 8 # sizeof(int) * 2

if length != x * y * 4:
return None, 0, 0

image_buffer = blendfile.read(length)

if len(image_buffer) != length:
return None, 0, 0

return image_buffer, x, y


def blend_thumb(file_in):
buf, width, height = blend_extract_thumb(file_in)
image = Image.frombuffer(
"RGBA",
(width, height),
buf,
)
image = ImageOps.flip(image)
return image
32 changes: 32 additions & 0 deletions tagstudio/src/qt/widgets/thumb_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
VIDEO_TYPES,
IMAGE_TYPES,
RAW_IMAGE_TYPES,
BLENDER_TYPES,
)
from src.core.utils.encoding import detect_char_encoding
from src.qt.helpers.blender_thumbnailer import blend_thumb

ImageFile.LOAD_TRUNCATED_IMAGES = True

Expand Down Expand Up @@ -204,6 +206,36 @@ def render(
# img_buf = io.BytesIO()
# plt.savefig(img_buf, format='png')
# image = Image.open(img_buf)

# Blender ===========================================================
elif _filepath.suffix.lower() in BLENDER_TYPES:
try:
blend_image = blend_thumb(str(_filepath))

bg = Image.new("RGB", blend_image.size, color="#1e1e1e")
bg.paste(blend_image, mask=blend_image.getchannel(3))
image = bg

except (
AttributeError,
UnidentifiedImageError,
FileNotFoundError,
TypeError,
) as e:
if str(e) == "expected string or buffer":
logging.info(
f"[ThumbRenderer]{ERROR} {_filepath.name} Doesn't have thumbnail saved. ({type(e).__name__})"
)

else:
logging.info(
f"[ThumbRenderer]{ERROR}: Couldn't render thumbnail for {_filepath.name} ({type(e).__name__})"
)

image = ThumbRenderer.thumb_file_default_512.resize(
(adj_size, adj_size), resample=Image.Resampling.BILINEAR
)

# No Rendered Thumbnail ========================================
else:
image = ThumbRenderer.thumb_file_default_512.resize(
Expand Down

0 comments on commit 883354b

Please sign in to comment.