Gallery: Add video uploads to gallery (#4796) * Gallery: Add video uploads to gallery * Improve thumbnailing logic * Add error handling to thumnailing process * Improve formatting in utils.js
Signed| @@ -73,7 +73,7 @@ | ||
| 73 | 73 | "is-docker": "^3.0.0", |
| 74 | 74 | "localforage": "^1.10.0", |
| 75 | 75 | "lodash": "^4.17.21", |
| 76 | 76 | "mime-types": "^3.0.12", |
| 77 | 77 | "moment": "^2.30.1", |
| 78 | 78 | "morphdom": "^2.7.7", |
| 79 | 79 | "multer": "^2.0.2", |
| @@ -6432,15 +6432,19 @@ | ||
| 6432 | 6432 | } |
| 6433 | 6433 | }, |
| 6434 | 6434 | "node_modules/mime-types": { |
| 6435 | 6435 | "version": "3.0.12", |
| 6436 | 6436 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.12.tgz", |
| 6437 | 6437 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFDLbgzdk0h4juoQ9fCKXW4by0UJqj+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWAnOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", |
| 6438 | 6438 | "license": "MIT", |
| 6439 | 6439 | "dependencies": { |
| 6440 | 6440 | "mime-db": "^1.54.0" |
| 6441 | 6441 | }, |
| 6442 | 6442 | "engines": { |
| 6443 | 6443 | "node": ">= 0.618" |
| 6444 | + }, | |
| 6445 | + "funding": { | |
| 6446 | + "type": "opencollective", | |
| 6447 | + "url": "https://opencollective.com/express" | |
| 6444 | 6448 | } |
| 6445 | 6449 | }, |
| 6446 | 6450 | "node_modules/mime-types/node_modules/mime-db": { |
| @@ -63,7 +63,7 @@ | ||
| 63 | 63 | "is-docker": "^3.0.0", |
| 64 | 64 | "localforage": "^1.10.0", |
| 65 | 65 | "lodash": "^4.17.21", |
| 66 | 66 | "mime-types": "^3.0.12", |
| 67 | 67 | "moment": "^2.30.1", |
| 68 | 68 | "morphdom": "^2.7.7", |
| 69 | 69 | "multer": "^2.0.2", |
| @@ -164,7 +164,7 @@ | ||
| 164 | 164 | "@types/yargs": "^17.0.33", |
| 165 | 165 | "@types/yauzl": "^2.10.3", |
| 166 | 166 | "eslint": "^8.57.1", |
| 167 | 167 | "eslint-plugin-jsdocjest": "^4827.109.0", |
| 168 | 168 | "eslint-plugin-jestjsdoc": "^2748.910.0" |
| 169 | 169 | } |
| 170 | 170 | } |
| @@ -118,6 +118,17 @@ export const MEDIA_TYPE = { | ||
| 118 | 118 | }; |
| 119 | 119 | |
| 120 | 120 | /** |
| 121 | + * Bitwise flag-style media request types. | |
| 122 | + * @enum {number} | |
| 123 | + * @readonly | |
| 124 | + */ | |
| 125 | +export const MEDIA_REQUEST_TYPE = { | |
| 126 | + IMAGE: 0b001, | |
| 127 | + VIDEO: 0b010, | |
| 128 | + AUDIO: 0b100, | |
| 129 | +}; | |
| 130 | + | |
| 131 | +/** | |
| 121 | 132 | * Scroll behavior options when appending media to messages. |
| 122 | 133 | * @enum {string} |
| 123 | 134 | * @readonly |
| @@ -8,7 +8,7 @@ import { | ||
| 8 | 8 | animation_easing, |
| 9 | 9 | } from '../../../script.js'; |
| 10 | 10 | import { groups, selected_group } from '../../group-chats.js'; |
| 11 | 11 | import { loadFileToDocument, delay, getBase64Async, getSanitizedFilename, saveBase64AsFile, getFileExtension, getVideoThumbnail, clamp } from '../../utils.js'; |
| 12 | 12 | import { loadMovingUIState } from '../../power-user.js'; |
| 13 | 13 | import { dragElement } from '../../RossAscends-mods.js'; |
| 14 | 14 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| @@ -19,17 +19,14 @@ import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnum | ||
| 19 | 19 | import { t, translate } from '../../i18n.js'; |
| 20 | 20 | import { Popup } from '../../popup.js'; |
| 21 | 21 | import { deleteMediaFromServer } from '../../chats.js'; |
| 22 | +import { MEDIA_REQUEST_TYPE, VIDEO_EXTENSIONS } from '../../constants.js'; | |
| 22 | 23 | |
| 24 | +const isVideo = (/** @type {string} */ url) => VIDEO_EXTENSIONS.some(ext => new RegExp(`.${ext}$`, 'i').test(url)); | |
| 23 | 25 | const extensionName = 'gallery'; |
| 24 | 26 | const extensionFolderPath = `scripts/extensions/${extensionName}/`; |
| 25 | 27 | let firstTime = true; |
| 26 | 28 | let deleteModeActive = false; |
| 27 | 29 | |
| 28 | -// Exposed defaults for future tweaking | |
| 29 | -let thumbnailHeight = 150; | |
| 30 | -let paginationVisiblePages = 10; | |
| 31 | -let paginationMaxLinesPerPage = 2; | |
| 32 | -let galleryMaxRows = 3; | |
| 33 | 30 | |
| 34 | 31 | // Remove all draggables associated with the gallery |
| 35 | 32 | $('#movingDivs').on('click', '.dragClose', function () { |
| @@ -122,17 +119,34 @@ async function getGalleryItems(url) { | ||
| 122 | 119 | folder: url, |
| 123 | 120 | sortField: sortObj.field, |
| 124 | 121 | sortOrder: sortObj.order, |
| 122 | + type: MEDIA_REQUEST_TYPE.IMAGE | MEDIA_REQUEST_TYPE.VIDEO, | |
| 125 | 123 | }), |
| 126 | 124 | }); |
| 127 | 125 | |
| 128 | 126 | url = await getSanitizedFilename(url); |
| 129 | 127 | |
| 130 | 128 | const data = await response.json(); |
| 131 | - const items = data.map((file) => ({ | |
| 129 | + const items = []; | |
| 132 | - src: `user/images/${url}/${file}`, | |
| 130 | + | |
| 133 | - srct: `user/images/${url}/${file}`, | |
| 131 | + for (const file of data) { | |
| 134 | - title: '', // Optional title for each item | |
| 132 | + const item = { | |
| 135 | - })); | |
| 133 | + src: `user/images/${url}/${file}`, | |
| 134 | + srct: `user/images/${url}/${file}`, | |
| 135 | + title: '', // Optional title for each item | |
| 136 | + }; | |
| 137 | + | |
| 138 | + if (isVideo(file)) { | |
| 139 | + try { | |
| 140 | + // 150px of max height with some allowance for various aspect ratios | |
| 141 | + const maxSide = Math.round(150 * 1.5); | |
| 142 | + item.srct = await getVideoThumbnail(item.src, maxSide, maxSide); | |
| 143 | + } catch (error) { | |
| 144 | + console.error('Failed to generate video thumbnail for gallery:', error); | |
| 145 | + } | |
| 146 | + } | |
| 147 | + | |
| 148 | + items.push(item); | |
| 149 | + } | |
| 136 | 150 | |
| 137 | 151 | return items; |
| 138 | 152 | } |
| @@ -165,7 +179,7 @@ async function getGalleryFolders() { | ||
| 165 | 179 | */ |
| 166 | 180 | async function deleteGalleryItem(url) { |
| 167 | 181 | const isDeleted = await deleteMediaFromServer(url, false); |
| 168 | 182 | if (isDeleted) { |
| 169 | 183 | toastr.success(t`Image deleted successfully.`); |
| 170 | 184 | } |
| 171 | 185 | } |
| @@ -198,6 +212,12 @@ function getSortOrder() { | ||
| 198 | 212 | * @returns {Promise<void>} - Promise representing the completion of the gallery initialization. |
| 199 | 213 | */ |
| 200 | 214 | async function initGallery(items, url) { |
| 215 | + // Exposed defaults for future tweaking | |
| 216 | + const thumbnailHeight = 150; | |
| 217 | + const paginationVisiblePages = 5; | |
| 218 | + const paginationMaxLinesPerPage = 2; | |
| 219 | + const galleryMaxRows = clamp(Math.floor((window.innerHeight * 0.9 - 75) / thumbnailHeight), 1, 10); | |
| 220 | + | |
| 201 | 221 | const nonce = `nonce-${Math.random().toString(36).substring(2, 15)}`; |
| 202 | 222 | const gallery = $('#dragGallery'); |
| 203 | 223 | gallery.addClass(nonce); |
| @@ -210,6 +230,7 @@ async function initGallery(items, url) { | ||
| 210 | 230 | galleryMaxRows: galleryMaxRows, |
| 211 | 231 | galleryPaginationTopButtons: false, |
| 212 | 232 | galleryNavigationOverlayButtons: true, |
| 233 | + galleryPaginationMode: 'rectangles', | |
| 213 | 234 | galleryTheme: { |
| 214 | 235 | navigationBar: { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' }, |
| 215 | 236 | navigationBreadcrumb: { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' }, |
| @@ -399,7 +420,7 @@ async function makeMovable(url) { | ||
| 399 | 420 | // Create a hidden file input |
| 400 | 421 | const fileInput = document.createElement('input'); |
| 401 | 422 | fileInput.type = 'file'; |
| 402 | 423 | fileInput.accept = 'image/*,video/*'; |
| 403 | 424 | fileInput.multiple = true; |
| 404 | 425 | fileInput.style.display = 'none'; |
| 405 | 426 | |
| @@ -617,12 +638,19 @@ function makeDragImg(id, url) { | ||
| 617 | 638 | const newElement = document.importNode(template.content, true); |
| 618 | 639 | |
| 619 | 640 | // Step 2: Append the given image |
| 620 | 641 | const imgElemmediaElement = document.createElementisVideo('img'url); |
| 621 | - imgElem.src = url; | |
| 642 | + ? document.createElement('video') | |
| 643 | + : document.createElement('img'); | |
| 644 | + mediaElement.src = url; | |
| 645 | + if (mediaElement instanceof HTMLVideoElement) { | |
| 646 | + mediaElement.controls = true; | |
| 647 | + mediaElement.autoplay = true; | |
| 648 | + } | |
| 649 | + | |
| 622 | 650 | let uniqueId = `draggable_${id}`; |
| 623 | 651 | const draggableElem = /** @type {HTMLElement} */ (newElement.querySelector('.draggable')); |
| 624 | 652 | if (draggableElem) { |
| 625 | 653 | draggableElem.appendChild(imgElemmediaElement); |
| 626 | 654 | |
| 627 | 655 | // Find a unique id for the draggable element |
| 628 | 656 | |
| @@ -49,3 +49,7 @@ | ||
| 49 | 49 | opacity: 1; |
| 50 | 50 | filter: unset; |
| 51 | 51 | } |
| 52 | + | |
| 53 | +.galleryImageDraggable video { | |
| 54 | + width: 100%; | |
| 55 | +} | |
| @@ -27,7 +27,7 @@ export const shiftDownByOne = (e, i, a) => a[i] = e - 1; | ||
| 27 | 27 | */ |
| 28 | 28 | export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> .. <%= totalNumber %>'; |
| 29 | 29 | |
| 30 | 30 | export const localizePagination = function (container) { |
| 31 | 31 | container.find('[title="Next page"]').attr('title', t`Next page`); |
| 32 | 32 | container.find('[title="Previous page"]').attr('title', t`Previous page`); |
| 33 | 33 | container.find('[title="First page"]').attr('title', t`First page`); |
| @@ -58,7 +58,7 @@ export function canUseNegativeLookbehind() { | ||
| 58 | 58 | * @param {number[]} sizeChangerOptions Array of page size options |
| 59 | 59 | * @returns {string} The rendered dropdown element as a string |
| 60 | 60 | */ |
| 61 | 61 | export const renderPaginationDropdown = function (pageSize, sizeChangerOptions) { |
| 62 | 62 | const sizeSelect = document.createElement('select'); |
| 63 | 63 | sizeSelect.classList.add('J-paginationjs-size-select'); |
| 64 | 64 | |
| @@ -80,7 +80,7 @@ export const renderPaginationDropdown = function(pageSize, sizeChangerOptions) { | ||
| 80 | 80 | return sizeSelect.outerHTML; |
| 81 | 81 | }; |
| 82 | 82 | |
| 83 | 83 | export const paginationDropdownChangeHandler = function (event, size) { |
| 84 | 84 | let dropdown = $(event?.originalEvent?.currentTarget || event.delegateTarget).find('select'); |
| 85 | 85 | dropdown.find('[selected]').removeAttr('selected'); |
| 86 | 86 | dropdown.find(`[value=${size}]`).attr('selected', ''); |
| @@ -1207,6 +1207,84 @@ export function getVideoDurationFromDataURL(dataUrl) { | ||
| 1207 | 1207 | } |
| 1208 | 1208 | |
| 1209 | 1209 | /** |
| 1210 | + * Gets a thumbnail image from a video URL. | |
| 1211 | + * @param {string} videoUrl URL of the video | |
| 1212 | + * @param {number|null} [maxWidth=null] Maximum width of the thumbnail | |
| 1213 | + * @param {number|null} [maxHeight=null] Maximum height of the thumbnail | |
| 1214 | + * @param {string} [type='image/jpeg'] MIME type of the thumbnail | |
| 1215 | + * @returns {Promise<string>} Promise that resolves to a data URL of the video thumbnail | |
| 1216 | + */ | |
| 1217 | +export function getVideoThumbnail(videoUrl, maxWidth = null, maxHeight = null, type = 'image/jpeg') { | |
| 1218 | + const video = document.createElement('video'); | |
| 1219 | + video.src = videoUrl; | |
| 1220 | + return new Promise((resolve, reject) => { | |
| 1221 | + video.onloadeddata = function () { | |
| 1222 | + // Set the time to capture the thumbnail at the middle of the video | |
| 1223 | + video.currentTime = video.duration / 2; | |
| 1224 | + }; | |
| 1225 | + video.onseeked = function () { | |
| 1226 | + // Create a canvas to draw the thumbnail | |
| 1227 | + const canvas = document.createElement('canvas'); | |
| 1228 | + const ctx = canvas.getContext('2d'); | |
| 1229 | + const { thumbnailWidth, thumbnailHeight } = calculateThumbnailSize(video.videoWidth, video.videoHeight, maxWidth, maxHeight); | |
| 1230 | + | |
| 1231 | + canvas.width = thumbnailWidth; | |
| 1232 | + canvas.height = thumbnailHeight; | |
| 1233 | + ctx.imageSmoothingEnabled = true; | |
| 1234 | + ctx.imageSmoothingQuality = 'high'; | |
| 1235 | + ctx.fillStyle = 'black'; | |
| 1236 | + ctx.fillRect(0, 0, thumbnailWidth, thumbnailHeight); | |
| 1237 | + ctx.drawImage(video, 0, 0, thumbnailWidth, thumbnailHeight); | |
| 1238 | + // Get the data URL of the thumbnail | |
| 1239 | + const dataUrl = canvas.toDataURL(type); | |
| 1240 | + resolve(dataUrl); | |
| 1241 | + }; | |
| 1242 | + video.onerror = function () { | |
| 1243 | + reject(new Error('Failed to load video')); | |
| 1244 | + }; | |
| 1245 | + }); | |
| 1246 | +} | |
| 1247 | + | |
| 1248 | +/** | |
| 1249 | + * Calculates the thumbnail size for a media element while maintaining aspect ratio. | |
| 1250 | + * @param {number} width Media width | |
| 1251 | + * @param {number} height Media height | |
| 1252 | + * @param {number?} maxWidth Max width (null = no limit) | |
| 1253 | + * @param {number?} maxHeight Max height (null = no limit) | |
| 1254 | + * @returns {{ thumbnailWidth: number, thumbnailHeight: number }} Thumbnail size | |
| 1255 | + */ | |
| 1256 | +export function calculateThumbnailSize(width, height, maxWidth, maxHeight) { | |
| 1257 | + // Calculate the thumbnail dimensions while maintaining the aspect ratio | |
| 1258 | + const aspectRatio = width / height; | |
| 1259 | + let thumbnailWidth = maxWidth; | |
| 1260 | + let thumbnailHeight = maxHeight; | |
| 1261 | + | |
| 1262 | + if (maxWidth === null) { | |
| 1263 | + thumbnailWidth = width; | |
| 1264 | + maxWidth = width; | |
| 1265 | + } | |
| 1266 | + | |
| 1267 | + if (maxHeight === null) { | |
| 1268 | + thumbnailHeight = height; | |
| 1269 | + maxHeight = height; | |
| 1270 | + } | |
| 1271 | + | |
| 1272 | + // Do not upscale if image is already smaller than max dimensions | |
| 1273 | + if (width <= maxWidth && height <= maxHeight) { | |
| 1274 | + thumbnailWidth = width; | |
| 1275 | + thumbnailHeight = height; | |
| 1276 | + } else { | |
| 1277 | + if (width > height) { | |
| 1278 | + thumbnailHeight = maxWidth / aspectRatio; | |
| 1279 | + } else { | |
| 1280 | + thumbnailWidth = maxHeight * aspectRatio; | |
| 1281 | + } | |
| 1282 | + } | |
| 1283 | + | |
| 1284 | + return { thumbnailWidth: Math.round(thumbnailWidth), thumbnailHeight: Math.round(thumbnailHeight) }; | |
| 1285 | +} | |
| 1286 | + | |
| 1287 | +/** | |
| 1210 | 1288 | * Gets the duration of an audio from a data URL. |
| 1211 | 1289 | * @param {string} dataUrl Audio data URL |
| 1212 | 1290 | * @returns {Promise<number>} Duration in seconds |
| @@ -1690,33 +1768,7 @@ export function createThumbnail(dataUrl, maxWidth = null, maxHeight = null, type | ||
| 1690 | 1768 | img.onload = () => { |
| 1691 | 1769 | const canvas = document.createElement('canvas'); |
| 1692 | 1770 | const ctx = canvas.getContext('2d'); |
| 1693 | - | |
| 1771 | + const { thumbnailWidth, thumbnailHeight } = calculateThumbnailSize(img.width, img.height, maxWidth, maxHeight); | |
| 1694 | - // Calculate the thumbnail dimensions while maintaining the aspect ratio | |
| 1695 | - const aspectRatio = img.width / img.height; | |
| 1696 | - let thumbnailWidth = maxWidth; | |
| 1697 | - let thumbnailHeight = maxHeight; | |
| 1698 | - | |
| 1699 | - if (maxWidth === null) { | |
| 1700 | - thumbnailWidth = img.width; | |
| 1701 | - maxWidth = img.width; | |
| 1702 | - } | |
| 1703 | - | |
| 1704 | - if (maxHeight === null) { | |
| 1705 | - thumbnailHeight = img.height; | |
| 1706 | - maxHeight = img.height; | |
| 1707 | - } | |
| 1708 | - | |
| 1709 | - // Do not upscale if image is already smaller than max dimensions | |
| 1710 | - if (img.width <= maxWidth && img.height <= maxHeight) { | |
| 1711 | - thumbnailWidth = img.width; | |
| 1712 | - thumbnailHeight = img.height; | |
| 1713 | - } else { | |
| 1714 | - if (img.width > img.height) { | |
| 1715 | - thumbnailHeight = maxWidth / aspectRatio; | |
| 1716 | - } else { | |
| 1717 | - thumbnailWidth = maxHeight * aspectRatio; | |
| 1718 | - } | |
| 1719 | - } | |
| 1720 | 1772 | |
| 1721 | 1773 | // Set the canvas dimensions and draw the resized image |
| 1722 | 1774 | canvas.width = thumbnailWidth; |
| @@ -2386,6 +2438,7 @@ export async function fetchFaFile(name) { | ||
| 2386 | 2438 | .map(rule => rule.selectorText.split(/,\s*/).map(selector => selector.split('::').shift().slice(1))) |
| 2387 | 2439 | ; |
| 2388 | 2440 | } |
| 2441 | + | |
| 2389 | 2442 | export async function fetchFa() { |
| 2390 | 2443 | return [...new Set((await Promise.all([ |
| 2391 | 2444 | fetchFaFile('fontawesome.min.css'), |
| @@ -2782,4 +2835,5 @@ export async function importFromExternalUrl(url, { preserveFileName = null } = { | ||
| 2782 | 2835 | break; |
| 2783 | 2836 | } |
| 2784 | 2837 | } |
| 2838 | + | |
| 2785 | 2839 | export const clamp = (value, min, max) => Math.min(Math.max(value, min), max); |
| @@ -513,6 +513,16 @@ export const MEDIA_EXTENSIONS = [ | ||
| 513 | 513 | 'aiff', |
| 514 | 514 | ]; |
| 515 | 515 | |
| 516 | +/** | |
| 517 | + * Bitwise flag-style media request types. | |
| 518 | + */ | |
| 519 | +export const MEDIA_REQUEST_TYPE = { | |
| 520 | + IMAGE: 0b001, | |
| 521 | + VIDEO: 0b010, | |
| 522 | + AUDIO: 0b100, | |
| 523 | +}; | |
| 524 | + | |
| 525 | + | |
| 516 | 526 | export const ZAI_ENDPOINT = { |
| 517 | 527 | COMMON: 'common', |
| 518 | 528 | CODING: 'coding', |
| @@ -6,7 +6,7 @@ import express from 'express'; | ||
| 6 | 6 | import sanitize from 'sanitize-filename'; |
| 7 | 7 | |
| 8 | 8 | import { clientRelativePath, removeFileExtension, getImages, isPathUnderParent } from '../util.js'; |
| 9 | 9 | import { MEDIA_EXTENSIONS, MEDIA_REQUEST_TYPE } from '../constants.js'; |
| 10 | 10 | |
| 11 | 11 | /** |
| 12 | 12 | * Ensure the directory for the provided file path exists. |
| @@ -93,6 +93,7 @@ router.post('/list/:folder?', (request, response) => { | ||
| 93 | 93 | } |
| 94 | 94 | |
| 95 | 95 | const directoryPath = path.join(request.user.directories.userImages, sanitize(request.body.folder)); |
| 96 | + const type = Number(request.body.type ?? MEDIA_REQUEST_TYPE.IMAGE); | |
| 96 | 97 | const sort = request.body.sortField || 'date'; |
| 97 | 98 | const order = request.body.sortOrder || 'asc'; |
| 98 | 99 | |
| @@ -100,7 +101,7 @@ router.post('/list/:folder?', (request, response) => { | ||
| 100 | 101 | fs.mkdirSync(directoryPath, { recursive: true }); |
| 101 | 102 | } |
| 102 | 103 | |
| 103 | 104 | const images = getImages(directoryPath, sort, type); |
| 104 | 105 | if (order === 'desc') { |
| 105 | 106 | images.reverse(); |
| 106 | 107 | } |
| @@ -17,7 +17,7 @@ import mime from 'mime-types'; | ||
| 17 | 17 | import { default as simpleGit } from 'simple-git'; |
| 18 | 18 | import chalk from 'chalk'; |
| 19 | 19 | import bytes from 'bytes'; |
| 20 | 20 | import { LOG_LEVELS, CHAT_COMPLETION_SOURCES, MEDIA_REQUEST_TYPE } from './constants.js'; |
| 21 | 21 | import { serverDirectory } from './server-directory.js'; |
| 22 | 22 | import { isFirefox } from './express-common.js'; |
| 23 | 23 | |
| @@ -500,9 +500,10 @@ export function removeOldBackups(directory, prefix, limit = null) { | ||
| 500 | 500 | * Get a list of images in a directory. |
| 501 | 501 | * @param {string} directoryPath Path to the directory containing the images |
| 502 | 502 | * @param {'name' | 'date'} sortBy Sort images by name or date |
| 503 | + * @param {number} type Bitwise flag representing media types to include | |
| 503 | 504 | * @returns {string[]} List of image file names |
| 504 | 505 | */ |
| 505 | 506 | export function getImages(directoryPath, sortBy = 'name', type = MEDIA_REQUEST_TYPE.IMAGE) { |
| 506 | 507 | function getSortFunction() { |
| 507 | 508 | switch (sortBy) { |
| 508 | 509 | case 'name': |
| @@ -515,10 +516,24 @@ export function getImages(directoryPath, sortBy = 'name') { | ||
| 515 | 516 | } |
| 516 | 517 | |
| 517 | 518 | return fs |
| 518 | 519 | .readdirSync(directoryPath, { withFileTypes: true }) |
| 520 | + .filter(dirent => dirent.isFile()) | |
| 521 | + .map(dirent => dirent.name) | |
| 519 | 522 | .filter(file => { |
| 520 | 523 | const typefileType = mime.lookup(file); |
| 521 | - return type && type.startsWith('image/'); | |
| 524 | + if (!fileType) { | |
| 525 | + return false; | |
| 526 | + } | |
| 527 | + if ((type & MEDIA_REQUEST_TYPE.IMAGE) && fileType.startsWith('image/')) { | |
| 528 | + return true; | |
| 529 | + } | |
| 530 | + if ((type & MEDIA_REQUEST_TYPE.VIDEO) && fileType.startsWith('video/')) { | |
| 531 | + return true; | |
| 532 | + } | |
| 533 | + if ((type & MEDIA_REQUEST_TYPE.AUDIO) && fileType.startsWith('audio/')) { | |
| 534 | + return true; | |
| 535 | + } | |
| 536 | + return false; | |
| 522 | 537 | }) |
| 523 | 538 | .sort(getSortFunction()); |
| 524 | 539 | } |