mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-03 20:32:07 -04:00
feat: FileBrowser video thumbnail (#2077)
* feat(filebrowser): add filebrowser video thumbnails display - Find cached thumbnails first - If not found, generate with ffmpegthumbnailer - Fallback to placeholder icon if dependency not met * fix(filebrowser): create thumbnail cache dir if not exists * refactor(filebrowser): prefer using Paths lib * fix(filebrowser): only check filetype once for each file * fix(filebrowser): early test for thumbnails * feat: add xdgCache path
This commit is contained in:
@@ -10,6 +10,7 @@ Singleton {
|
|||||||
|
|
||||||
readonly property url home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
readonly property url home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||||
readonly property url pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
|
readonly property url pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
|
||||||
|
readonly property url xdgCache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]
|
||||||
|
|
||||||
readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/DankMaterialShell`
|
readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/DankMaterialShell`
|
||||||
readonly property url state: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[0]}/DankMaterialShell`
|
readonly property url state: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[0]}/DankMaterialShell`
|
||||||
|
|||||||
@@ -75,6 +75,50 @@ StyledRect {
|
|||||||
return determineFileType(fileName) === "image";
|
return determineFileType(fileName) === "image";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isVideoFile(fileName) {
|
||||||
|
if (!fileName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return determineFileType(fileName) === "video";
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool isImage: isImageFile(delegateRoot.fileName)
|
||||||
|
property bool isVideo: isVideoFile(delegateRoot.fileName)
|
||||||
|
|
||||||
|
property string _xdgCacheHome: Paths.strip(Paths.xdgCache)
|
||||||
|
property string _thumbnailSize: iconSizeIndex >= 2 ? "x-large" : "large"
|
||||||
|
property int _thumbnailPx: iconSizeIndex >= 2 ? 512 : 256
|
||||||
|
property string videoThumbnailPath: {
|
||||||
|
if (!delegateRoot.fileIsDir && isVideo) {
|
||||||
|
const hash = Qt.md5("file://" + delegateRoot.filePath);
|
||||||
|
return _xdgCacheHome + "/thumbnails/" + _thumbnailSize + "/" + hash + ".png";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
property string _videoThumb: ""
|
||||||
|
|
||||||
|
onVideoThumbnailPathChanged: {
|
||||||
|
_videoThumb = "";
|
||||||
|
if (!videoThumbnailPath)
|
||||||
|
return;
|
||||||
|
const thumbPath = videoThumbnailPath;
|
||||||
|
const thumbDir = _xdgCacheHome + "/thumbnails/" + _thumbnailSize;
|
||||||
|
const size = _thumbnailPx;
|
||||||
|
const fp = delegateRoot.filePath;
|
||||||
|
Paths.mkdir(thumbDir);
|
||||||
|
Proc.runCommand(null, ["test", "-f", thumbPath], function(output, exitCode) {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
_videoThumb = thumbPath;
|
||||||
|
} else {
|
||||||
|
Proc.runCommand(null, ["ffmpegthumbnailer", "-i", fp, "-o", thumbPath, "-s", String(size), "-f"], function(output, exitCode) {
|
||||||
|
if (exitCode === 0)
|
||||||
|
_videoThumb = thumbPath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getIconForFile(fileName) {
|
function getIconForFile(fileName) {
|
||||||
const lowerName = fileName.toLowerCase();
|
const lowerName = fileName.toLowerCase();
|
||||||
if (lowerName.startsWith("dockerfile")) {
|
if (lowerName.startsWith("dockerfile")) {
|
||||||
@@ -124,7 +168,11 @@ StyledRect {
|
|||||||
property string imagePath: {
|
property string imagePath: {
|
||||||
if (weMode && delegateRoot.fileIsDir)
|
if (weMode && delegateRoot.fileIsDir)
|
||||||
return delegateRoot.filePath + "/preview" + weExtensions[weExtIndex];
|
return delegateRoot.filePath + "/preview" + weExtensions[weExtIndex];
|
||||||
return (!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) ? delegateRoot.filePath : "";
|
if (!delegateRoot.fileIsDir && isImage)
|
||||||
|
return delegateRoot.filePath;
|
||||||
|
if (_videoThumb)
|
||||||
|
return _videoThumb;
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
|
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
@@ -149,7 +197,7 @@ StyledRect {
|
|||||||
source: gridPreviewImage
|
source: gridPreviewImage
|
||||||
maskEnabled: true
|
maskEnabled: true
|
||||||
maskSource: gridImageMask
|
maskSource: gridImageMask
|
||||||
visible: gridPreviewImage.status === Image.Ready && ((!delegateRoot.fileIsDir && isImageFile(delegateRoot.fileName)) || (weMode && delegateRoot.fileIsDir))
|
visible: gridPreviewImage.status === Image.Ready && ((!delegateRoot.fileIsDir && (isImage || isVideo)) || (weMode && delegateRoot.fileIsDir))
|
||||||
maskThresholdMin: 0.5
|
maskThresholdMin: 0.5
|
||||||
maskSpreadAtMin: 1
|
maskSpreadAtMin: 1
|
||||||
}
|
}
|
||||||
@@ -175,7 +223,7 @@ StyledRect {
|
|||||||
name: delegateRoot.fileIsDir ? "folder" : getIconForFile(delegateRoot.fileName)
|
name: delegateRoot.fileIsDir ? "folder" : getIconForFile(delegateRoot.fileName)
|
||||||
size: iconSizes[iconSizeIndex] * 0.45
|
size: iconSizes[iconSizeIndex] * 0.45
|
||||||
color: delegateRoot.fileIsDir ? Theme.primary : Theme.surfaceText
|
color: delegateRoot.fileIsDir ? Theme.primary : Theme.surfaceText
|
||||||
visible: (!delegateRoot.fileIsDir && !isImageFile(delegateRoot.fileName)) || (delegateRoot.fileIsDir && !weMode)
|
visible: (!delegateRoot.fileIsDir && !isImage && !(isVideo && gridPreviewImage.status === Image.Ready)) || (delegateRoot.fileIsDir && !weMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,46 @@ StyledRect {
|
|||||||
return determineFileType(fileName) === "image";
|
return determineFileType(fileName) === "image";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isVideoFile(fileName) {
|
||||||
|
if (!fileName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return determineFileType(fileName) === "video";
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool isImage: isImageFile(listDelegateRoot.fileName)
|
||||||
|
property bool isVideo: isVideoFile(listDelegateRoot.fileName)
|
||||||
|
|
||||||
|
property string _xdgCacheHome: Paths.strip(Paths.xdgCache)
|
||||||
|
property string videoThumbnailPath: {
|
||||||
|
if (!listDelegateRoot.fileIsDir && isVideo) {
|
||||||
|
const hash = Qt.md5("file://" + listDelegateRoot.filePath);
|
||||||
|
return _xdgCacheHome + "/thumbnails/normal/" + hash + ".png";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
property string _videoThumb: ""
|
||||||
|
|
||||||
|
onVideoThumbnailPathChanged: {
|
||||||
|
_videoThumb = "";
|
||||||
|
if (!videoThumbnailPath)
|
||||||
|
return;
|
||||||
|
const thumbPath = videoThumbnailPath;
|
||||||
|
const fp = listDelegateRoot.filePath;
|
||||||
|
Paths.mkdir(_xdgCacheHome + "/thumbnails/normal");
|
||||||
|
Proc.runCommand(null, ["test", "-f", thumbPath], function(output, exitCode) {
|
||||||
|
if (exitCode === 0) {
|
||||||
|
_videoThumb = thumbPath;
|
||||||
|
} else {
|
||||||
|
Proc.runCommand(null, ["ffmpegthumbnailer", "-i", fp, "-o", thumbPath, "-s", "128", "-f"], function(output, exitCode) {
|
||||||
|
if (exitCode === 0)
|
||||||
|
_videoThumb = thumbPath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getIconForFile(fileName) {
|
function getIconForFile(fileName) {
|
||||||
const lowerName = fileName.toLowerCase();
|
const lowerName = fileName.toLowerCase();
|
||||||
if (lowerName.startsWith("dockerfile")) {
|
if (lowerName.startsWith("dockerfile")) {
|
||||||
@@ -127,7 +167,13 @@ StyledRect {
|
|||||||
Image {
|
Image {
|
||||||
id: listPreviewImage
|
id: listPreviewImage
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
property string imagePath: (!listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)) ? listDelegateRoot.filePath : ""
|
property string imagePath: {
|
||||||
|
if (!listDelegateRoot.fileIsDir && isImage)
|
||||||
|
return listDelegateRoot.filePath;
|
||||||
|
if (_videoThumb)
|
||||||
|
return _videoThumb;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
|
source: imagePath ? "file://" + imagePath.split('/').map(s => encodeURIComponent(s)).join('/') : ""
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
sourceSize.width: 32
|
sourceSize.width: 32
|
||||||
@@ -141,7 +187,7 @@ StyledRect {
|
|||||||
source: listPreviewImage
|
source: listPreviewImage
|
||||||
maskEnabled: true
|
maskEnabled: true
|
||||||
maskSource: listImageMask
|
maskSource: listImageMask
|
||||||
visible: listPreviewImage.status === Image.Ready && !listDelegateRoot.fileIsDir && isImageFile(listDelegateRoot.fileName)
|
visible: listPreviewImage.status === Image.Ready && !listDelegateRoot.fileIsDir && (isImage || isVideo)
|
||||||
maskThresholdMin: 0.5
|
maskThresholdMin: 0.5
|
||||||
maskSpreadAtMin: 1
|
maskSpreadAtMin: 1
|
||||||
}
|
}
|
||||||
@@ -166,7 +212,7 @@ StyledRect {
|
|||||||
name: listDelegateRoot.fileIsDir ? "folder" : getIconForFile(listDelegateRoot.fileName)
|
name: listDelegateRoot.fileIsDir ? "folder" : getIconForFile(listDelegateRoot.fileName)
|
||||||
size: Theme.iconSize - 2
|
size: Theme.iconSize - 2
|
||||||
color: listDelegateRoot.fileIsDir ? Theme.primary : Theme.surfaceText
|
color: listDelegateRoot.fileIsDir ? Theme.primary : Theme.surfaceText
|
||||||
visible: listDelegateRoot.fileIsDir || !isImageFile(listDelegateRoot.fileName)
|
visible: listDelegateRoot.fileIsDir || (!isImage && !(isVideo && listPreviewImage.status === Image.Ready))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user