Skip to content

Commit

Permalink
associates preview proxy video for any non-video asset that closes #152
Browse files Browse the repository at this point in the history
… and fixes #131 where sometimes the wrong video is previewed on UI Item hover
  • Loading branch information
swillisart committed Jan 4, 2023
1 parent d4c7846 commit 2926052
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 26 deletions.
1 change: 1 addition & 0 deletions library/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ def setVideo(self, data):
continue
asset = view.lastIndex.data(Qt.UserRole)
if asset:
# TODO: this is a temporary local implementation.
on_complete = partial(setattr, asset, 'video')
worker = LocalThumbnail(data, on_complete)
self.pool.start(worker)
Expand Down
27 changes: 17 additions & 10 deletions library/io/ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,29 +254,36 @@ def processTOOL(in_path, out_path, flag):
asset.classification = flag.value
return asset

def getDurationFramerate(stream):
framerate = float(stream.rate)
duration = stream.duration * float(stream.time_base)
return duration, framerate

def processMOV(in_path, out_path, flag):
def generateProxy(in_path, out_path, modulus=10):
input_container = av.open(str(in_path))
input_stream = input_container.streams.video[0]
input_stream.thread_type = "AUTO"

framerate = float(input_stream.rate)
duration = input_stream.duration * float(input_stream.time_base)
middle_frame = int((duration * framerate) / 2 )
duration, framerate = getDurationFramerate(input_stream)
middle_frame = int((duration * framerate) / 2)

# decode and encode streams
with VidOut(out_path, input_stream) as vidout:
for frame_number, frame in enumerate(input_container.decode(video=0)):
# Only encode the preview every 10th frame to speed it up.
if frame_number % 10 == 0:
if frame_number % modulus == 0:
icon_frame = vidout.encodePreview(frame_number, frame)
if frame_number == middle_frame:
array = icon_frame.to_rgb().to_ndarray()
middle_pixels = icon_frame.to_rgb().to_ndarray()
vidout.encodeProxy(frame)

input_container.close()
ih, iw, _ = array.shape
src_img = QImage(array, iw, ih, QImage.Format_RGB888)
return duration, framerate, middle_pixels, input_stream


def processMOV(in_path, out_path, flag):
duration, framerate, middle_pixels, stream = generateProxy(in_path, out_path)
ih, iw, _ = middle_pixels.shape
src_img = QImage(middle_pixels, iw, ih, QImage.Format_RGB888)
icon_img = makeImagePreview(src_img)
icon_img.save(str(out_path.suffixed('_icon', ext='.jpg')))
asset = TempAsset(
Expand All @@ -285,7 +292,7 @@ def processMOV(in_path, out_path, flag):
type=AssetType.COMPONENT.index,
duration=int(duration),
framerate=int(framerate),
resolution=f'{input_stream.width}x{input_stream.height}x3',
resolution=f'{stream.width}x{stream.height}x3',
path=in_path,
icon=QPixmap.fromImage(icon_img),
)
Expand Down
7 changes: 5 additions & 2 deletions library/objectmodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,18 @@ def local_path(self):
def network_path(self):
return NETWORK_STORAGE / str(self.relativePath)

@property
def icon_path(self):
return self.network_path.suffixed('_icon', '.jpg')

@property
def relativePath(self):
stem = str(self.path).rsplit('/', 1)[-1]
partial_path = Path(self.categoryName) / str(self.path)
return partial_path.parents(0) / partial_path.name / stem

def fetchIcon(self):
icon_path = self.relativePath.suffixed('_icon', ext='.jpg')
rc = 'retrieveIcon/{}'.format(icon_path)
rc = 'retrieveIcon/{}'.format(self.icon_path)
db.accessor.doStream(rc, self.id)

def stream_video_to(self, slot=None):
Expand Down
58 changes: 44 additions & 14 deletions library/widgets/assets_alt.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

# -- Module --
import library.config as config
from relic.local import Category, TempAsset, FileType, AssetType
from relic.scheme import Classification
from relic.local import Category, TempAsset, FileType, AssetType, EXTENSION_MAP
from relic.scheme import Classification as Class
from library.io import ingest
from library.io.util import LocalThumbnail

Expand All @@ -30,9 +30,9 @@
QListView, QMenu, QStyle, QStyledItemDelegate, QWidgetAction,
QStyleOption, QStyleOptionViewItem, QWidget, QApplication, QCheckBox, QMessageBox)

# -- Globals --
THREAD_POOL = QThreadPool.globalInstance()


PREVIEWABLE = [Class.MODEL, Class.TOOL, Class.ANIMATION, Class.MOVIE, Class.PLATE]


class AssetItemModel(BaseItemModel):
Expand Down Expand Up @@ -82,6 +82,7 @@ def mimeData(self, indices):

return mime_data


class AssetListView(BaseView):

onSelection = Signal(QModelIndex)
Expand Down Expand Up @@ -193,14 +194,27 @@ def clipboardCopy(self, description=None):
def clipboardPaste(self):
clipboard = QApplication.clipboard()
clip_img = clipboard.image()
if not int(config.RELIC_PREFS.edit_mode) or not clip_img:
if not int(config.RELIC_PREFS.edit_mode):
return
if asset := self.getSelectedAsset():
mime_data = clipboard.mimeData()
asset = self.getSelectedAsset()
if not asset:
return

if mime_data.hasImage():
out_img = ingest.makeImagePreview(clip_img)
out_path = asset.network_path.suffixed('_icon', ext='.jpg')
out_path.path.parent.mkdir(parents=True, exist_ok=True)
out_img.save(str(out_path))
asset.icon = QPixmap.fromImage(out_img) # fromImageInPlace
elif mime_data.hasUrls():
for url in mime_data.urls():
if not url.isLocalFile():
continue
path = Path(url.toLocalFile())
file_type = EXTENSION_MAP[path.ext.lower()]
if path.is_file() and file_type == Class.MOVIE:
ingest.generateProxy(path, asset.network_path)

def groupSelectedItems(self):
"""Creates a new collection asset and links all the selected
Expand Down Expand Up @@ -246,19 +260,34 @@ def indexToItem(self, index):

return item

def resetLastIndex(self):
index = self.lastIndex
asset = index.data(Qt.UserRole)
asset.progress = 0
on_complete = partial(setattr, asset, 'icon')
worker = LocalThumbnail(asset.icon_path, on_complete)
THREAD_POOL.start(worker)
self.model.dataChanged.emit(index, index, [Qt.UserRole])
self.lastIndex = None

def mouseMoveEvent(self, event):
super(AssetListView, self).mouseMoveEvent(event)
mouse_pos = event.pos()
index = self.indexAt(mouse_pos)
if not index.isValid():
if self.lastIndex is not None:
self.resetLastIndex()
return
asset = index.data(Qt.UserRole)

if index != self.lastIndex:
if self.lastIndex is not None:
self.resetLastIndex()
self.lastIndex = index
THREAD_POOL.clear()
THREAD_POOL.waitForDone()
if BaseItemDelegate.VIEW_MODE == ItemDispalyModes.THUMBNAIL:
has_movie = hasattr(asset, 'duration') and asset.duration
if has_movie or getattr(asset, 'class') == Classification.MODEL:
has_duration = hasattr(asset, 'duration') and asset.duration
if has_duration or getattr(asset, 'class') in PREVIEWABLE:
asset.stream_video_to()
elif asset.type == int(AssetType.COLLECTION):
asset.video = []
Expand All @@ -267,9 +296,10 @@ def mouseMoveEvent(self, event):
if num > 15:
break
linked_asset = x.data(Qt.UserRole)
ico = linked_asset.network_path.suffixed('_icon', '.jpg')
worker = LocalThumbnail(ico, asset.video.append)
# TODO: this is a temporary local implementation.
worker = LocalThumbnail(linked_asset.icon_path, asset.video.append)
THREAD_POOL.start(worker)

if BaseItemDelegate.VIEW_MODE == ItemDispalyModes.THUMBNAIL:
rect = self.visualRect(index)
a = rect.bottomLeft()
Expand All @@ -281,7 +311,7 @@ def mouseMoveEvent(self, event):
asset.icon = asset.video[pos_idx]
asset.progress = relative_pos

self.dataChanged(index, index)
self.model.dataChanged.emit(index, index, [Qt.UserRole])

def dropEvent(self, event):
if not int(config.RELIC_PREFS.edit_mode):
Expand Down Expand Up @@ -389,14 +419,14 @@ def generatePreview(self):
# Reuse the ingest methods to make preview media.
found_type = FileTypes[asset.path.ext]
classifiy = found_type.value
is_image = classifiy & Classification.IMAGE
is_image = classifiy & Class.IMAGE
if path.isSequence() and is_image:
new = ingest.processSEQ(path, path)
elif found_type & FileTypes.exr:
new = ingest.processHDR(path, path)
elif is_image:
new = ingest.processLDR(path, path)
elif classifiy in Classification.MOVIE:
elif classifiy in Class.MOVIE:
new = ingest.processMOV(path, path)

asset.icon = new.icon
Expand Down

0 comments on commit 2926052

Please sign in to comment.