wranglebot
Version:
open source media asset management
346 lines (320 loc) • 11.6 kB
text/typescript
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();