mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-29 10:17:27 +02:00
126 lines
4.5 KiB
Python
126 lines
4.5 KiB
Python
from PySide2.QtCore import QObject, Slot, QSize, QUrl, Qt
|
|
from PySide2.QtGui import QImageReader, QImageWriter
|
|
|
|
import os
|
|
import pathlib
|
|
import hashlib
|
|
import time
|
|
|
|
|
|
class ThumbnailCache(QObject):
|
|
"""
|
|
ThumbnailCache provides an abstraction for the thumbnail cache on disk, available in QML.
|
|
|
|
For a given image file, it ensures the corresponding thumbnail exists (by creating it if necessary)
|
|
and gives access to it.
|
|
|
|
The default cache location is a subdirectory of the user's home directory:
|
|
~/Meshroom/thumbnails.
|
|
This location can be overriden with the MESHROOM_THUMBNAIL_DIR environment variable.
|
|
|
|
This class also takes care of cleaning the thumbnail directory,
|
|
i.e. scanning this directory and removing thumbnails that have not been used for too long.
|
|
The default time limit is one week.
|
|
|
|
The main use case for thumbnails in Meshroom is in the ImageGallery.
|
|
"""
|
|
|
|
# Thumbnail cache directory
|
|
thumbnailDir = os.path.join(pathlib.Path.home(), 'Meshroom', 'thumbnails')
|
|
|
|
# Thumbnail dimensions limit (the actual dimensions of a thumbnail will depend on the aspect ratio)
|
|
thumbnailSize = QSize(100, 100)
|
|
|
|
# Time limit for thumbnail storage on disk, expressed in days
|
|
storageTimeLimit = 7
|
|
|
|
@Slot(QUrl, result=QUrl)
|
|
def thumbnail(self, imgSource):
|
|
"""
|
|
Retrieve the filepath of the thumbnail corresponding to a given image.
|
|
If the thumbnail does not exist on disk, it is created.
|
|
|
|
Args:
|
|
imgSource (QUrl): the filepath to the input image
|
|
|
|
Returns:
|
|
QUrl: the filepath to the corresponding thumbnail
|
|
"""
|
|
# Safety check
|
|
if not imgSource.isValid():
|
|
return None
|
|
|
|
imgPath = imgSource.toLocalFile()
|
|
|
|
# Use SHA1 hashing to associate a unique thumbnail to the image
|
|
digest = hashlib.sha1(imgPath.encode('utf-8')).hexdigest()
|
|
path = os.path.join(ThumbnailCache.thumbnailDir, f'{digest}.jpg')
|
|
source = QUrl.fromLocalFile(path)
|
|
|
|
# Check if thumbnail already exists
|
|
if os.path.exists(path):
|
|
# Update last modification time
|
|
pathlib.Path(path).touch(exist_ok=True)
|
|
return source
|
|
|
|
# Thumbnail does not exist, therefore we create it:
|
|
# 1. read the image
|
|
# 2. scale it to thumbnail dimensions
|
|
# 3. write it in the cache
|
|
print(f'[ThumbnailCache] Creating thumbnail {path} for image {imgPath}')
|
|
|
|
# Initialize image reader object
|
|
reader = QImageReader()
|
|
reader.setFileName(imgPath)
|
|
reader.setAutoTransform(True)
|
|
|
|
# Read image and check for potential errors
|
|
img = reader.read()
|
|
if img.isNull():
|
|
print(f'[ThumbnailCache] Error when reading image: {reader.errorString()}')
|
|
return None
|
|
|
|
# Make sure the thumbnail directory exists before writing into it
|
|
os.makedirs(ThumbnailCache.thumbnailDir, exist_ok=True)
|
|
|
|
# Scale image while preserving aspect ratio
|
|
thumbnail = img.scaled(ThumbnailCache.thumbnailSize, aspectMode=Qt.KeepAspectRatio)
|
|
|
|
# Write thumbnail to disk and check for potential errors
|
|
writer = QImageWriter(path)
|
|
success = writer.write(thumbnail)
|
|
if not success:
|
|
print(f'[ThumbnailCache] Error when writing thumbnail: {writer.errorString()}')
|
|
return None
|
|
|
|
return source
|
|
|
|
@staticmethod
|
|
def clean():
|
|
"""
|
|
Scan the thumbnail directory and
|
|
remove all thumbnails that have not been used for more than storageTimeLimit days.
|
|
"""
|
|
# Get current time
|
|
now = time.time()
|
|
|
|
# Scan thumbnail directory and gather all thumbnails to remove
|
|
toRemove = []
|
|
for entry in os.scandir(ThumbnailCache.thumbnailDir):
|
|
if not entry.is_file():
|
|
continue
|
|
|
|
# Compute storage duration since last usage of thumbnail
|
|
lastUsage = os.path.getmtime(entry.path)
|
|
storageTime = now - lastUsage
|
|
print(f'[ThumbnailCache] Thumbnail {entry.name} has been stored for {storageTime}s')
|
|
|
|
# Mark as removable if storage time exceeds limit
|
|
if storageTime > ThumbnailCache.storageTimeLimit * 3600 * 24:
|
|
print(f'[ThumbnailCache] {entry.name} exceeded storage time limit')
|
|
toRemove.append(entry.path)
|
|
|
|
# Remove all thumbnails marked as removable
|
|
for path in toRemove:
|
|
print(f'[ThumbnailCache] Remove {path}')
|
|
os.remove(path)
|