UNPKG

wranglebot

Version:

open source media asset management

346 lines (320 loc) 11.6 kB
import { finder } from "../system/index.js"; import PdfPrinter from "pdfmake"; import prettyBytes from "pretty-bytes"; import prettyMilliseconds from "pretty-ms"; import { Scraper } from "../media/Scraper.js"; import { v4 as uuidv4 } from "uuid"; import { dirname } from "path"; import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); class ExportBot { pathToAssets = finder.join(__dirname, "../../../assets/"); assets = { fonts: { body: { normal: finder.join(this.pathToAssets, "fonts", "overpass", "regular.otf"), bold: finder.join(this.pathToAssets, "fonts", "overpass", "bold.otf"), italics: finder.join(this.pathToAssets, "fonts", "overpass", "italic.otf"), bolditalics: finder.join(this.pathToAssets, "fonts", "overpass", "bold-italic.otf"), }, mono: { normal: finder.join(this.pathToAssets, "fonts", "overpass-mono", "regular.otf"), bold: finder.join(this.pathToAssets, "fonts", "overpass-mono", "bold.otf"), light: finder.join(this.pathToAssets, "fonts", "overpass-mono", "light.otf"), }, }, }; //keys to convert for better readability toPrettyBytes = ["video-bit-rate", "audio-bit-rate"]; toPrettyTime = ["video-duration", "audio-duration"]; async exportPDF(metaFiles, options) { if (options.paths.length === 0) throw new Error("No path to export to"); try { // Define font files const printer = new PdfPrinter(this.assets.fonts); // Define document layout // thumbnail, filename, hash, size ,[...], creation date let widthCols = ["auto", "auto", "auto", "auto", "auto"]; // metadata columns are dynamic Scraper.getColumns().forEach(() => { widthCols.push("auto"); }); let lines: any = []; for (const file of metaFiles) { let metaData = file.getMetaData(); let cells: any = []; //prettify entries for (const column of Scraper.getColumns()) { //find the entry in the file let val = metaData.entries[column.id]; if (!val) { cells.push(""); continue; } //convert if needed if (this.toPrettyTime.indexOf(column.id) >= 0) { val = prettyMilliseconds(Number(val) * 1000); } if (this.toPrettyBytes.indexOf(column.id) >= 0) { val = prettyBytes(Number(val)); } //add to list cells.push(val); } // add thumbnail or empty string let thumbnail: any = ""; if (file.thumbnails.length > 0) { thumbnail = { image: "data:image/jpeg;base64," + (await file.thumbnails[Math.floor(file.thumbnails.length / 2)].data), width: 100, }; } //add line lines.push([thumbnail, file.hash, file.basename, prettyBytes(file.size), ...cells, file.creationDate.toLocaleString()]); } let countOfVideoFiles = 0; let countOfAudioFiles = 0; let countOfImageFiles = 0; let countOfOtherFiles = 0; let totalSize = 0; let sizeOfVideoFiles = 0; let sizeOfAudioFiles = 0; let totalDuration = 0; let durationOfVideoFiles = 0; let durationOfAudioFiles = 0; for (const file of metaFiles) { if (file.fileType === "video") { countOfVideoFiles++; sizeOfVideoFiles += file.size; totalDuration += Number(file.getMetaData().get("video-duration")); durationOfVideoFiles += Number(file.getMetaData().get("video-duration")); } else if (file.fileType === "audio") { countOfAudioFiles++; sizeOfAudioFiles += file.size; totalDuration += Number(file.getMetaData().get("audio-duration")); durationOfAudioFiles += Number(file.getMetaData().get("audio-duration")); } else if (file.fileType === "image") { countOfImageFiles++; } else { countOfOtherFiles++; } totalSize += file.size; } let countOfScenes = 0; let mapOfScenes = new Map(); for (const file of metaFiles) { if (file.fileType === "video") { let scene = file.getMetaData().get("scene"); if (scene.length > 0) { if (mapOfScenes.has(scene)) { mapOfScenes.set(scene, mapOfScenes.get(scene) + 1); } else { mapOfScenes.set(scene, 1); } countOfScenes++; } } } let totalScenesSize = 0; let totalScenesDuration = 0; let totalScenesAverageMinutes = 0; let totalScenesAverageMinuteToSize = 0; let scenes = new Map([...mapOfScenes.entries()].sort((a, b) => b[1] - a[1])); if (countOfScenes > 0) { for (let scene of mapOfScenes.keys()) { //caluclate size and duration of scene let size = 0; let duration = 0; for (const file of metaFiles) { if (file.fileType === "video" && file.getMetaData().get("scene") === scene) { size += file.size; duration += Number(file.getMetaData().get("video-duration")); totalScenesSize += file.size; totalScenesDuration += Number(file.getMetaData().get("video-duration")); } } scenes.set(scene, { count: mapOfScenes.get(scene), size: size, duration: duration }); } totalScenesAverageMinutes = totalScenesDuration / 60 / countOfScenes; totalScenesAverageMinuteToSize = totalScenesSize / totalScenesAverageMinutes; } const docDefinition = { content: [ { columns: [ [ { text: `${options.credits.title}`, style: "header", }, { text: prettyBytes(metaFiles.reduce((a, b) => a + b.size, 0)) + " over " + metaFiles.length + " Files", }, { text: `Exported on ${new Date().toLocaleString()}`, }, { text: `${options.credits.owner ? "All Rights Reserved " + options.credits.owner : ""}`, }, ], [ { image: options.logo ? options.logo : this.pathToAssets + "images/logo.png", width: 64, height: 64, alignment: "right", style: "logo", }, { text: [ `Unique ID: ${uuidv4()}`, //add random id to prevent caching ], bold: true, alignment: "right", }, { text: "Learn more at https://wranglebot.io", bold: true, link: "https://wranglebot.io", margin: [0, 20, 0, 8], alignment: "right", style: "footer", }, ], ], }, [ { text: `Metafiles (${metaFiles.length})`, }, { style: "table", table: { widths: widthCols, headerRows: 1, body: [["Thumbnail", "Hash", "File Name", "Size", ...Scraper.getColumnNames(), "Creation Date"], ...lines], }, layout: "lightHorizontalLines", }, ], { columns: [ [ { text: "Summary", style: "head", }, { style: "tableSummary", table: { headerRows: 1, widths: ["auto", "auto", "auto"], body: [ ["", "Audio", "Video"], ["Count", countOfAudioFiles, countOfVideoFiles], ["Size", prettyBytes(sizeOfAudioFiles), prettyBytes(sizeOfVideoFiles)], ["Duration", prettyMilliseconds(durationOfAudioFiles * 1000), prettyMilliseconds(durationOfVideoFiles * 1000)], ], }, layout: "lightHorizontalLines", }, ], [ { text: "Scene Breakdown", style: "head", }, { style: "tableSummary", layout: "lightHorizontalLines", table: { headerRows: 1, widths: ["auto", "auto", "auto", "auto", "auto", "auto"], body: [ ["Scene", "Size", "Duration", "Count", "Average Duration", "Average Minute / Size"], ...[...scenes.keys()].map((scene) => { return [ scene, //scene name prettyBytes(scenes.get(scene).size), //size prettyMilliseconds(Number(scenes.get(scene).duration) * 1000), //duration scenes.get(scene).count, //count prettyMilliseconds((Number(scenes.get(scene).duration) * 1000) / scenes.get(scene).count), //average duration "1m : " + prettyBytes(Number(scenes.get(scene).size) / (Number(scenes.get(scene).duration) / 60)), //1 min : size ]; }), [ "Total", prettyBytes(totalScenesSize), prettyMilliseconds(totalScenesDuration * 1000), countOfScenes, prettyMilliseconds(totalScenesAverageMinutes * 1000), "1m : " + prettyBytes(totalScenesAverageMinuteToSize), ], ], }, }, ], ], }, ], defaultStyle: { font: "body", }, styles: { header: { fontSize: 18, bold: true, }, footer: { fontSize: 8, }, subheader: { fontSize: 15, bold: true, }, quote: { italics: true, }, small: { fontSize: 6, }, table: { fontSize: 6, font: "mono", margin: [0, 5, 0, 15], }, tableSummary: { fontSize: 12, font: "mono", margin: [0, 5, 0, 15], }, }, pageSize: "A1", pageOrientation: "landscape", }; const docOptions = { // ... }; for (let path of options.paths) { const pdfDoc = printer.createPdfKitDocument(docDefinition, docOptions); finder.mkdirSync(path, { recursive: true }); const writeStream = finder.createWriteStream(finder.join(path, options.fileName + (options.uniqueNames ? "_" + Date.now() : "") + ".pdf")); writeStream.on("error", (e) => { console.log(e); }); try { pdfDoc.pipe(writeStream); pdfDoc.end(); } catch (e) { console.log(e); return false; } } return true; } catch (e) { console.log(e); throw e; } } } export default new ExportBot();