diff --git a/jobs/jobsDeleteMedia.ts b/jobs/jobsDeleteMedia.ts index 6d79969..96423d9 100644 --- a/jobs/jobsDeleteMedia.ts +++ b/jobs/jobsDeleteMedia.ts @@ -1,15 +1,9 @@ import { Request, Response } from "express"; import fs from "fs-extra"; -import path from "path"; import { logger } from "../server"; -import GenerateThumbnail from "../util/generateThumbnail"; import MediaFile from "../util/interfaces/MediaFile"; -import ListableChecker from "../util/listableChecker"; -import GenerateUrl from "../util/MediaUrlGen"; import { PathToRoFolder } from "../util/pathGenerators"; -import { FolderPaths, JobRelativeFilePath } from "../util/serverInit"; -import ft from "file-type"; -import core from "file-type/core"; +import { JobRelativeFilePath } from "../util/serverInit"; export async function JobsDeleteMedia(req: Request, res: Response) { const jobid: string = (req.body.jobid || "").trim(); diff --git a/jobs/jobsDownloadMedia.ts b/jobs/jobsDownloadMedia.ts index 42973ad..0fc39a2 100644 --- a/jobs/jobsDownloadMedia.ts +++ b/jobs/jobsDownloadMedia.ts @@ -1,15 +1,10 @@ import { Request, Response } from "express"; import fs from "fs-extra"; -import multer from "multer"; +import JSZip from "jszip"; import path from "path"; import { logger } from "../server"; -import GenerateThumbnail from "../util/generateThumbnail"; -import generateUniqueFilename from "../util/generateUniqueFilename"; -import { ConvertHeicFiles } from "../util/heicConverter"; -import { PathToRoFolder } from "../util/pathGenerators"; -import { JobsListMedia } from "./jobsListMedia"; -import JSZip from "jszip"; import ListableChecker from "../util/listableChecker"; +import { PathToRoFolder } from "../util/pathGenerators"; import { JobRelativeFilePath } from "../util/serverInit"; //param: files: string[] | array of filenames. diff --git a/jobs/jobsListMedia.ts b/jobs/jobsListMedia.ts index 8240c20..ce8cae2 100644 --- a/jobs/jobsListMedia.ts +++ b/jobs/jobsListMedia.ts @@ -2,7 +2,9 @@ import { Request, Response } from "express"; import fs from "fs-extra"; import path from "path"; import { logger } from "../server"; -import GenerateThumbnail from "../util/generateThumbnail"; +import GenerateThumbnail, { + GenerateOptimized, +} from "../util/generateThumbnail"; import MediaFile from "../util/interfaces/MediaFile"; import ListableChecker from "../util/listableChecker"; import GenerateUrl from "../util/MediaUrlGen"; @@ -29,6 +31,9 @@ export async function JobsListMedia(req: Request, res: Response) { const relativeThumbPath: string = await GenerateThumbnail( relativeFilePath ); + const relativeOptimizedPath: string = await GenerateOptimized( + relativeFilePath + ); const type: core.FileTypeResult | undefined = await ft.fromFile( relativeFilePath @@ -49,6 +54,12 @@ export async function JobsListMedia(req: Request, res: Response) { jobid, relativeThumbPath, ]), + optimized: GenerateUrl([ + FolderPaths.StaticPath, + FolderPaths.JobsFolder, + jobid, + relativeOptimizedPath, + ]), thumbnailHeight: 250, thumbnailWidth: 250, filename: file.filename, @@ -73,6 +84,9 @@ export async function JobsListMedia(req: Request, res: Response) { const relativeThumbPath: string = await GenerateThumbnail( relativeFilePath ); + const relativeOptimizedPath: string = await GenerateOptimized( + relativeFilePath + ); const type: core.FileTypeResult | undefined = await ft.fromFile( relativeFilePath ); @@ -92,6 +106,12 @@ export async function JobsListMedia(req: Request, res: Response) { jobid, relativeThumbPath, ]), + optimized: GenerateUrl([ + FolderPaths.StaticPath, + FolderPaths.JobsFolder, + jobid, + relativeOptimizedPath, + ]), thumbnailHeight: 250, thumbnailWidth: 250, filename: file.name, diff --git a/jobs/jobsMoveMedia.ts b/jobs/jobsMoveMedia.ts index 6f90851..445e8cc 100644 --- a/jobs/jobsMoveMedia.ts +++ b/jobs/jobsMoveMedia.ts @@ -1,13 +1,10 @@ import { Request, Response } from "express"; import fs from "fs-extra"; import path from "path"; -import GenerateThumbnail from "../util/generateThumbnail"; -import GenerateUrl from "../util/MediaUrlGen"; -import { FolderPaths } from "../util/serverInit"; -import multer from "multer"; -import { JobsListMedia } from "./jobsListMedia"; -import { PathToRoFolder } from "../util/pathGenerators"; import { logger } from "../server"; +import { PathToRoFolder } from "../util/pathGenerators"; +import { FolderPaths } from "../util/serverInit"; +import { JobsListMedia } from "./jobsListMedia"; export async function JobsMoveMedia(req: Request, res: Response) { const jobid: string = (req.body.jobid || "").trim(); diff --git a/jobs/jobsUploadMedia.ts b/jobs/jobsUploadMedia.ts index f196ed7..23ecf7b 100644 --- a/jobs/jobsUploadMedia.ts +++ b/jobs/jobsUploadMedia.ts @@ -3,7 +3,9 @@ import fs from "fs-extra"; import multer from "multer"; import path from "path"; import { logger } from "../server"; -import GenerateThumbnail from "../util/generateThumbnail"; +import GenerateThumbnail, { + GenerateOptimized, +} from "../util/generateThumbnail"; import generateUniqueFilename from "../util/generateUniqueFilename"; import { ConvertHeicFiles } from "../util/heicConverter"; import { PathToRoFolder } from "../util/pathGenerators"; @@ -60,6 +62,7 @@ export async function jobsUploadMedia(req: Request, res: Response) { //for each file.path, generate the thumbnail. (req.files as Express.Multer.File[]).forEach((file) => { thumbnailGenerationQueue.push(GenerateThumbnail(file.path)); + thumbnailGenerationQueue.push(GenerateOptimized(file.path)); }); await Promise.all(thumbnailGenerationQueue); diff --git a/util/generateThumbnail.ts b/util/generateThumbnail.ts index 36529e1..36239ce 100644 --- a/util/generateThumbnail.ts +++ b/util/generateThumbnail.ts @@ -1,13 +1,12 @@ -import fs from "fs-extra"; -import { access } from "fs/promises"; -import imageThumbnail from "image-thumbnail"; -import path from "path"; -import gm from "gm"; import ft from "file-type"; import core from "file-type/core"; -import GenerateUrl from "./MediaUrlGen"; -import { AssetPaths, FolderPaths } from "./serverInit"; +import fs from "fs-extra"; +import { access } from "fs/promises"; +import gm from "gm"; +import imageThumbnail from "image-thumbnail"; +import path from "path"; import { logger } from "../server"; +import { AssetPaths, FolderPaths } from "./serverInit"; const simpleThumb = require("simple-thumbnail"); const ffmpeg = require("ffmpeg-static"); @@ -67,6 +66,67 @@ export default async function GenerateThumbnail( } } +/** @returns {string} Returns the relative path from the file to the thumbnail on the server. This must be converted to a URL. */ +export async function GenerateOptimized( + file: string + //thumbPath: string +) { + const type: core.FileTypeResult | undefined = await ft.fromFile(file); + let thumbnailExtension: string = GetThumbnailExtension(type, true); + let thumbPath: string = path.join( + path.dirname(file), + FolderPaths.OptimizedSubDir, + path.parse(path.basename(file)).name + thumbnailExtension + ); + + try { + //Ensure the thumbs directory exists. + await fs.ensureDir(path.dirname(thumbPath)); + + try { + await access(thumbPath); + logger.debug("Optimized image already exists for : " + thumbPath); + + return path.relative(path.dirname(file), thumbPath); + } catch {} + + //Check to see if the file is an image, PDF, or video. + + if (type?.mime === "application/pdf") { + const fileOnDisk: Buffer = await fs.readFile(file); + await GeneratePdfThumbnail(file, thumbPath); + } else if (type?.mime.startsWith("video")) { + await simpleThumb(file, thumbPath, "250x?", { + path: ffmpeg, + }); + } else { + logger.debug("Optimized image being created for : " + thumbPath); + await OptimizeImage(file, thumbPath); + } + return path.relative(path.dirname(file), thumbPath); + } catch (err) { + logger.error("Error when genenerating optimized image:", { + thumbPath, + err, + message: (err as Error).message, + }); + return path.relative(path.dirname(file), AssetPaths.File); + } +} + +async function OptimizeImage(file: string, optimizedPath: string) { + const fileOnDisk: Buffer = await fs.readFile(file); + return new Promise((resolve, reject) => { + const result = gm(fileOnDisk) + .resize(1500) + .quality(75) + .write(optimizedPath, (error) => { + if (error) reject(error.message); + resolve(optimizedPath); + }); + }); +} + async function GeneratePdfThumbnail(file: string, thumbPath: string) { const fileOnDisk: Buffer = await fs.readFile(file); return new Promise((resolve, reject) => { @@ -82,7 +142,11 @@ async function GeneratePdfThumbnail(file: string, thumbPath: string) { }); } -function GetThumbnailExtension(file: core.FileTypeResult | undefined) { +function GetThumbnailExtension( + file: core.FileTypeResult | undefined, + useJpeg?: boolean +) { + if (useJpeg) return ".jpg"; if (file === undefined) return ".png"; return ".png"; } diff --git a/util/heicConverter.ts b/util/heicConverter.ts index e8a6a49..f93727b 100644 --- a/util/heicConverter.ts +++ b/util/heicConverter.ts @@ -1,22 +1,13 @@ -import { Request, Response } from "express"; -import multer from "multer"; -import GenerateThumbnail from "../util/generateThumbnail"; -import generateUniqueFilename from "../util/generateUniqueFilename"; -import { PathToRoFolder } from "../util/pathGenerators"; import fs from "fs-extra"; -import { access } from "fs/promises"; -import imageThumbnail from "image-thumbnail"; -import path, { resolve } from "path"; -import gm from "gm"; +import dotenv from "dotenv"; import ft from "file-type"; import core from "file-type/core"; -import GenerateUrl from "./MediaUrlGen"; -import { FolderPaths } from "./serverInit"; +import path, { resolve } from "path"; import { logger } from "../server"; -import dotenv from "dotenv"; +import { FolderPaths } from "./serverInit"; const heicConverter = require("heic-convert"); diff --git a/util/serverInit.ts b/util/serverInit.ts index 4fc0419..6a9eb2b 100644 --- a/util/serverInit.ts +++ b/util/serverInit.ts @@ -17,6 +17,7 @@ export const FolderPaths = { Jobs: path.join(RootDirectory, JobsFolder), Vendors: path.join(RootDirectory, VendorsFolder), ThumbsSubDir: "/thumbs", + OptimizedSubDir: "/optimized", BillsSubDir: "/bills", ConvertedOriginalSubDir: "/ConvertedOriginal", StaticPath: "/static",