Substantial refactoring of code structure
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
class IIIFResource {
|
||||
id;
|
||||
type;
|
||||
|
||||
generateId(serviceURL, filename) {}
|
||||
toObject() {}
|
||||
}
|
||||
|
||||
export default IIIFResource;
|
||||
292
src/common.js
292
src/common.js
@@ -1,292 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import Manifest from './Manifest.js';
|
||||
import Sequence from './Sequence.js';
|
||||
import Canvas from './Canvas.js';
|
||||
import Image from './Image.js';
|
||||
import ManifestMetadata from './Metadata.js';
|
||||
|
||||
/**
|
||||
* @namespace Common
|
||||
*/
|
||||
const Common = {};
|
||||
|
||||
const authors = {
|
||||
DAN: 'Danilo P. Pavone',
|
||||
AUR: 'Aurélie Tournié',
|
||||
SAB: 'Sabrina Samelo',
|
||||
SOF: 'Sofia Ceccarelli',
|
||||
};
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @returns {{papyrus:string,imageAuthor:string,date:string}}
|
||||
*/
|
||||
function extractHIROXNIRMetadata(imgFilename) {
|
||||
return {
|
||||
papyrus: imgFilename.split('_')[0].split('-')[1],
|
||||
imageAuthor: authors[imgFilename.split('-')[0].replace(/\d{4}/,'')],
|
||||
date: imgFilename.split('-')[0].match(/\d{4}/)[0],
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @returns {{papyrus:string,imageAuthor:string,date:string}}
|
||||
*/
|
||||
function extractNIRMetadata(imgFilename) {
|
||||
return {
|
||||
papyrus: imgFilename.split('_')[0].split('-')[2],
|
||||
imageAuthor: authors[imgFilename.split('-')[0].replace(/\d{4}/,'')],
|
||||
date: imgFilename.split('-')[0].match(/\d{4}/)[0],
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @returns {{papyrus:string,imageAuthor:string,date:string,copyright:string}}
|
||||
*/
|
||||
function extractDNMetadata(imgFilename) {
|
||||
return {
|
||||
papyrus: imgFilename.split('_')[1],
|
||||
imageAuthor: '',
|
||||
date: '',
|
||||
copyright: "Ministero della Cultura (Biblioteca Nazionale 'Vittorio Emanuele III' di Napoli)",
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @returns {{papyrus:string,imageAuthor:string,date:string,copyright:string}}
|
||||
*/
|
||||
function extractDOMetadata(imgFilename) {
|
||||
return {
|
||||
papyrus: imgFilename.split('_')[1],
|
||||
imageAuthor: '',
|
||||
date: '',
|
||||
copyright: "The Bodleian Libraries, University of Oxford",
|
||||
}
|
||||
}
|
||||
|
||||
Common.TECH_NAMES = {
|
||||
dn: "Disegni Napoletani",
|
||||
do: "Disegni Oxoniensi",
|
||||
nir: "Near Infrared Imaging 1000nm",
|
||||
hsi: "SWIR Hyperspectral Imaging",
|
||||
uvf: "Technical Photography UVF",
|
||||
mbi: "Multispectral Imaging",
|
||||
hiroxnir: "HIROX Near Infrared",
|
||||
};
|
||||
/**
|
||||
* Retrieves available image techniques
|
||||
* for all papyruses based on folder contents
|
||||
* @returns {object}
|
||||
*/
|
||||
Common.getParamsFromFolders = async function() {
|
||||
let params = [];
|
||||
const papyri = await fs.promises.readdir(process.env.IMAGES_DIR);
|
||||
|
||||
for (let p of papyri) {
|
||||
let techniques = [];
|
||||
for (let tech of await fs.promises.readdir(`${process.env.IMAGES_DIR}/${p}`)) {
|
||||
let files = await fs.promises.readdir(
|
||||
`${process.env.IMAGES_DIR}/${p}/${tech}`
|
||||
);
|
||||
files = files.filter(file => /(tiff?|jpe?g|jp2|bmp)/.test(file));
|
||||
if (files.length) {
|
||||
techniques.push(tech.replace(/PHerc_\d+_/i, ''));
|
||||
}
|
||||
}
|
||||
|
||||
params.push({
|
||||
name : p.replace('_', ' '),
|
||||
techniques
|
||||
});
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
/**
|
||||
* @param {string} manifestId
|
||||
* @returns {string[]}
|
||||
* @throws `readdir` will thrown an ENOENT error if the images folder doesn't exist.
|
||||
* The manifest route should catch it.
|
||||
*/
|
||||
Common.getImageList = async function (manifestId) {
|
||||
// Regex to exclude images with certain patterns in filename
|
||||
const regexFilter = new RegExp(/(c2r|copertin.|camice|tit)/, 'i');
|
||||
let folderName = manifestId.replace(/pherc-(\d+)-(\w+)$/, function (_match, g1, g2) {
|
||||
return `PHerc_${g1}_${g2.toUpperCase()}`;
|
||||
});
|
||||
|
||||
let baseFolder = `${folderName.split('_')[0]}_${folderName.split('_')[1]}`;
|
||||
|
||||
let files = await fs.promises.readdir(
|
||||
`${process.env.IMAGES_DIR}/${baseFolder}/${folderName}`
|
||||
);
|
||||
|
||||
files = files.filter(file => !regexFilter.test(file) && !file.startsWith('.'));
|
||||
|
||||
return files;
|
||||
}
|
||||
/**
|
||||
* @param {string} imageId The image's id as a URL to the image server
|
||||
* @returns {{width: number, height: number, thumb: {width: number, height: number}}}
|
||||
*/
|
||||
Common.getImageSize = async function (imageId) {
|
||||
let infoURL = imageId.replace(/full.*$/,'info.json');
|
||||
const res = await fetch(infoURL);
|
||||
|
||||
let size = {};
|
||||
|
||||
if (res.ok) {
|
||||
const infoJson = await res.json();
|
||||
const maxSize = infoJson.sizes[infoJson.sizes.length - 1];
|
||||
size.height = maxSize.height;
|
||||
size.width = maxSize.width;
|
||||
size.thumb = {
|
||||
width: infoJson.sizes[1].width,
|
||||
height: infoJson.sizes[1].height,
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
/**
|
||||
* Get image name for given canvas
|
||||
* @todo Use regex in filter!!
|
||||
* @param {Canvas} canvas
|
||||
* @returns {string}
|
||||
*/
|
||||
Common.getImageName = async function (canvas) {
|
||||
const images = await this.getImageList(canvas.resourceId);
|
||||
|
||||
let name = canvas.name;
|
||||
|
||||
// Adjust canvas name for HSI with PCA...
|
||||
if (/pc(1|3)/.test(name)) {
|
||||
name = name.replace(
|
||||
/pc((1|3))/,
|
||||
function (match, group1) {
|
||||
return `_HSI_PC${group1}`;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
console.log('Canvas name: ' + name);
|
||||
|
||||
return images.filter(i => i.includes(name))[0];
|
||||
}
|
||||
/**
|
||||
* Create a canvas from an image filename
|
||||
* @param {Manifest} manifest
|
||||
* @param {string} filename The image filename
|
||||
* @returns {Canvas}
|
||||
*/
|
||||
Common.createCanvas = async function (manifest, filename) {
|
||||
let canvas = new Canvas(
|
||||
process.env.IIIF_API_VERSION,
|
||||
process.env.BASE_URL
|
||||
);
|
||||
|
||||
const namePos = {
|
||||
hiroxnir: 1,
|
||||
nir: 1,
|
||||
hsi: 1,
|
||||
do: 2,
|
||||
dn: 2
|
||||
};
|
||||
|
||||
// Remove file extension
|
||||
let canvasName = filename.split('_')[namePos[manifest.technique]]
|
||||
.replace(/\.\w{1,3}$/, '');
|
||||
|
||||
let label = canvasName.replace(
|
||||
/c(\w{1,2})0+(\d+).*(\.\w{2,3})?$/i,
|
||||
function (str, c, number) {
|
||||
return `C${c}. ${number}`;
|
||||
});
|
||||
// Add PCA to canvas label for HSI images
|
||||
if (manifest.technique === 'hsi') {
|
||||
label += ` ${filename.split('_')[3].replace(/\..*$/,'')}`;
|
||||
canvasName += `${filename.split('_')[3].replace(/\..*$/,'')}`;
|
||||
}
|
||||
|
||||
canvas.generateID(manifest.resourceId, canvasName.toLowerCase());
|
||||
canvas.label = label;
|
||||
|
||||
let image = new Image(canvas.id);
|
||||
image.generateID(process.env.IMAGE_SERVER_URL, filename);
|
||||
|
||||
const imgSize = await this.getImageSize(image.id);
|
||||
image.setSize(imgSize.height, imgSize.width);
|
||||
|
||||
canvas.setThumbnail(
|
||||
imgSize.thumb.height,
|
||||
imgSize.thumb.width,
|
||||
image.id
|
||||
);
|
||||
canvas.addImage(image);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
/**
|
||||
* @param {Manifest} manifest The manifest object
|
||||
* @param {string[]} images List of image filenames from folder
|
||||
* @returns {Manifest}
|
||||
*/
|
||||
Common.populateCanvases = async function (manifest, images) {
|
||||
const sequence = new Sequence(process.env.BASE_URL);
|
||||
|
||||
// There's only one sequence
|
||||
sequence.generateID(manifest.resourceId, 0);
|
||||
|
||||
for (let img of images) {
|
||||
// Skip failing images (TODO log error to file)
|
||||
try {
|
||||
let canvas = await this.createCanvas(manifest, img);
|
||||
sequence.addCanvas(canvas);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.log(`\nAffected image: ${img}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort them according to their ID number... (cornice, colonna, ecc...)
|
||||
sequence.canvases.sort((a, b) => {
|
||||
const firstId = a['@id'].slice(a['@id'].lastIndexOf('/') + 1).replace(/[a-z]+/ig,'');
|
||||
const secondId = b['@id'].slice(b['@id'].lastIndexOf('/') + 1).replace(/[a-z]+/ig,'');
|
||||
return Number(firstId) - Number(secondId);
|
||||
});
|
||||
|
||||
manifest.addSequence(sequence);
|
||||
|
||||
return manifest;
|
||||
}
|
||||
/**
|
||||
* @param {Manifest} manifest The Manifest object
|
||||
* @param {string} imgFilename
|
||||
* @returns {ManifestMetadata}
|
||||
*/
|
||||
Common.createMetadata = function (manifest, imgFilename) {
|
||||
let metadata = this.getMetadataFromImgName(imgFilename, manifest.technique);
|
||||
metadata.technique = Common.TECH_NAMES[manifest.technique];
|
||||
|
||||
return new ManifestMetadata(metadata);
|
||||
}
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @param {string} technique
|
||||
* @returns {{papyrus:string,imageAuthor:string,date:string,copyright:?string}}
|
||||
*/
|
||||
Common.getMetadataFromImgName = function (imgFilename, technique) {
|
||||
const extractor = {
|
||||
nir: extractNIRMetadata,
|
||||
dn: extractDNMetadata,
|
||||
do: extractDOMetadata,
|
||||
hsi: extractNIRMetadata,
|
||||
hiroxnir: extractHIROXNIRMetadata,
|
||||
}
|
||||
|
||||
return extractor[technique](imgFilename);
|
||||
}
|
||||
|
||||
export default Common;
|
||||
24
src/constants.js
Normal file
24
src/constants.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export const authors = {
|
||||
DAN: 'Danilo P. Pavone',
|
||||
AUR: 'Aurélie Tournié',
|
||||
SAB: 'Sabrina Samelo',
|
||||
SOF: 'Sofia Ceccarelli',
|
||||
};
|
||||
|
||||
export const TECH_NAMES = {
|
||||
dn: "Disegni Napoletani",
|
||||
do: "Disegni Oxoniensi",
|
||||
nir: "Near Infrared Imaging 1000nm",
|
||||
visr: "Visible Raking Light",
|
||||
hsi: "SWIR Hyperspectral Imaging",
|
||||
uvf: "Technical Photography UVF",
|
||||
mbi: "Multispectral Imaging",
|
||||
hiroxnir: "HIROX Near Infrared",
|
||||
};
|
||||
|
||||
export const COPYRIGHT = {
|
||||
dn: "Ministero della Cultura (Biblioteca Nazionale 'Vittorio Emanuele III' di Napoli)",
|
||||
do: "The Bodleian Libraries, University of Oxford",
|
||||
nir: "CNR - Consiglio Nazionale delle Ricerche, Ministero della Cultura (Biblioteca Nazionale 'Vittorio Emanuele III' di Napoli)",
|
||||
visr: "CNR - Consiglio Nazionale delle Ricerche, Ministero della Cultura (Biblioteca Nazionale 'Vittorio Emanuele III' di Napoli)",
|
||||
};
|
||||
@@ -1,27 +1,30 @@
|
||||
import IIIFResource from './IIIFResource.js';
|
||||
import Image from './Image.js';
|
||||
/**
|
||||
* @implements IIIFResource
|
||||
*/
|
||||
class Canvas {
|
||||
import ManifestMetadata from './Metadata.js';
|
||||
|
||||
class Canvas extends IIIFResource {
|
||||
id = '';
|
||||
#type = 'sc:Canvas';
|
||||
#label = '';
|
||||
#metadata = {};
|
||||
resourceId = '';
|
||||
name = '';
|
||||
images = [];
|
||||
thumbnail = {};
|
||||
|
||||
/**
|
||||
* @param {Number} IIIFApiVersion
|
||||
* @param {String} baseURL
|
||||
*/
|
||||
constructor(IIIFApiVersion, baseURL) {
|
||||
this.context = `https://iiif.io/api/presentation/${IIIFApiVersion}/context.json`;
|
||||
this.BASE_URL = baseURL;
|
||||
super(IIIFApiVersion, baseURL);
|
||||
}
|
||||
/**
|
||||
* @param {string} resourceId The resource ID for this canvas
|
||||
* @param {int|string} name A unique name for this canvas
|
||||
*/
|
||||
generateID(resourceId, name) {
|
||||
name = name.replace(/cr([1-9]$)/, `cr0$1`);
|
||||
//name = name.replace(/cr([1-9]$)/, `cr0$1`);
|
||||
this.id = `${this.BASE_URL}/${resourceId}/canvas/${name}`;
|
||||
this.resourceId = resourceId;
|
||||
this.name = name;
|
||||
@@ -53,6 +56,12 @@ class Canvas {
|
||||
width
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {ManifestMetadata} metadata
|
||||
*/
|
||||
setMetadata(metadata) {
|
||||
this.#metadata = metadata.toObject();
|
||||
}
|
||||
/**
|
||||
* Object representation
|
||||
* @returns {object}
|
||||
@@ -63,6 +72,7 @@ class Canvas {
|
||||
"@id" : this.id,
|
||||
"@type" : this.#type,
|
||||
"label" : this.#label,
|
||||
"metadata" : this.#metadata,
|
||||
"images" : this.images,
|
||||
"thumbnail" : this.thumbnail
|
||||
}
|
||||
19
src/iiif/IIIFResource.js
Normal file
19
src/iiif/IIIFResource.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
class IIIFResource {
|
||||
|
||||
/**
|
||||
* @param {Number} IIIFApiVersion
|
||||
* @param {String} baseURL
|
||||
*/
|
||||
constructor(IIIFApiVersion, baseURL) {
|
||||
this.context = `https://iiif.io/api/presentation/${IIIFApiVersion}/context.json`;
|
||||
this.BASE_URL = baseURL;
|
||||
}
|
||||
|
||||
generateId(serviceURL, filename) {}
|
||||
toObject() {}
|
||||
}
|
||||
|
||||
export default IIIFResource;
|
||||
@@ -1,53 +1,8 @@
|
||||
'use strict;'
|
||||
|
||||
import { getImagePath } from '../service/FilenameParser.js';
|
||||
import IIIFResource from './IIIFResource.js';
|
||||
|
||||
/**
|
||||
* @todo Move to common.js?!
|
||||
*/
|
||||
const splitter = {
|
||||
HIROXNIR: splitHIROXNIR,
|
||||
NIR: splitNIR,
|
||||
DN: splitDNO,
|
||||
DO: splitDNO,
|
||||
HSI: splitHSI
|
||||
};
|
||||
function splitHIROXNIR(filename) {
|
||||
let splitFilename = filename.split('_');
|
||||
const papyrusNum = splitFilename[0].split('-')[1];
|
||||
const baseFolder = `PHerc_${papyrusNum}`;
|
||||
const subfolder = `PHerc_${papyrusNum}_HIROXNIR`;
|
||||
|
||||
return {baseFolder, subfolder};
|
||||
}
|
||||
|
||||
function splitNIR(filename) {
|
||||
let splitFilename = filename.split('_');
|
||||
const papyrusNum = splitFilename[0].split('-')[2];
|
||||
const baseFolder = `PHerc_${papyrusNum}`;
|
||||
const subfolder = `PHerc_${papyrusNum}_${splitFilename[2].split('-')[0]}`;
|
||||
|
||||
return {baseFolder, subfolder};
|
||||
}
|
||||
/**
|
||||
* @todo Redundant...
|
||||
*/
|
||||
function splitHSI(filename) {
|
||||
let splitFilename = filename.split('_');
|
||||
const papyrusNumb = splitFilename[0].split('-')[2];
|
||||
const baseFolder = `PHerc_${papyrusNumb}`;
|
||||
const subfolder = `PHerc_${papyrusNumb}_${splitFilename[2]}`;
|
||||
|
||||
return {baseFolder, subfolder};
|
||||
}
|
||||
|
||||
function splitDNO(filename) {
|
||||
let splitFilename = filename.split('_');
|
||||
const papyrusNumb = splitFilename[1];
|
||||
const baseFolder = `PHerc_${papyrusNumb}`;
|
||||
const subfolder = `PHerc_${papyrusNumb}_${splitFilename[0]}`;
|
||||
|
||||
return {baseFolder, subfolder};
|
||||
}
|
||||
|
||||
/**
|
||||
* @implements IIIFResource
|
||||
*/
|
||||
@@ -87,11 +42,10 @@ class Image {
|
||||
* server endpoint for this image
|
||||
* @param {string} serviceURL The image server base URL
|
||||
* @param {string} filename The image's complete filename
|
||||
* @param {string} technique The imaging technique (from the manifest)
|
||||
*/
|
||||
generateID(serviceURL, filename) {
|
||||
let splitFn = splitter[/((HIROX)?NIR|DO|DN|HSI)/.exec(filename)[0]];
|
||||
|
||||
const {baseFolder, subfolder} = splitFn(filename);
|
||||
generateID(serviceURL, filename, technique) {
|
||||
const {baseFolder, subfolder} = getImagePath(filename, technique);
|
||||
|
||||
this.id = `${serviceURL}/${this.#IIIF_API_VERSION}/${baseFolder}%2F${subfolder}%2F${filename}/full/max/0/default.jpg`;
|
||||
this.service['@id'] = this.id.replace(/\/full.*$/,'');
|
||||
@@ -2,10 +2,7 @@ import IIIFResource from './IIIFResource.js';
|
||||
import Sequence from "./Sequence.js";
|
||||
import ManifestMetadata from './Metadata.js';
|
||||
|
||||
/**
|
||||
* @implements IIIFResource
|
||||
*/
|
||||
class Manifest {
|
||||
class Manifest extends IIIFResource {
|
||||
id = '';
|
||||
#type = 'sc:Manifest';
|
||||
#label = '';
|
||||
@@ -17,9 +14,12 @@ class Manifest {
|
||||
*/
|
||||
sequences = [];
|
||||
|
||||
/**
|
||||
* @param {Number} IIIFApiVersion
|
||||
* @param {String} baseURL
|
||||
*/
|
||||
constructor(IIIFApiVersion, baseURL) {
|
||||
this.context = `https://iiif.io/api/presentation/${IIIFApiVersion}/context.json`;
|
||||
this.BASE_URL = baseURL;
|
||||
super(IIIFApiVersion, baseURL);
|
||||
}
|
||||
get technique() {
|
||||
return this.#technique;
|
||||
@@ -46,7 +46,6 @@ class Manifest {
|
||||
this.#label = `P.Herc. ${this.resourceId.split('-')[1]}`;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {ManifestMetadata} metadata
|
||||
*/
|
||||
setMetadata(metadata) {
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"source": {
|
||||
"include": ["src/"],
|
||||
"includePattern": ".js$",
|
||||
"excludePattern": "(node_modules/|docs)"
|
||||
},
|
||||
|
||||
"plugins": ["plugins/markdown"],
|
||||
|
||||
"opts": {
|
||||
"encoding": "utf8",
|
||||
"destination": "docs/",
|
||||
"recurse": true,
|
||||
"verbose": true,
|
||||
"template": "/home/nicolo/.npm-global/lib/node_modules/clean-jsdoc-theme",
|
||||
"theme_opts": {
|
||||
"theme": "light"
|
||||
}
|
||||
}
|
||||
}
|
||||
206
src/service/FilenameParser.js
Normal file
206
src/service/FilenameParser.js
Normal file
@@ -0,0 +1,206 @@
|
||||
'use strict;'
|
||||
|
||||
import { authors, COPYRIGHT } from '../constants.js';
|
||||
|
||||
/**
|
||||
* This module parses image filenames to extract
|
||||
* relevant information, according to predefined naming
|
||||
* conventions.
|
||||
*
|
||||
* Although `parseDNFilename()` and `parseDOFilename()` are
|
||||
* functionally equivalent, they're kept separate because
|
||||
* it's not clear at the moment whether the naming conventions
|
||||
* for the two techniques will remain equivalent.
|
||||
* @module FilenameParser
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ParsedMetadata
|
||||
* @property {String} papyrus
|
||||
* @property {String} imageAuthor
|
||||
* @property {String} date
|
||||
* @property {String} [copyright]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef ImgFolderNames
|
||||
* @property {String} baseFolder
|
||||
* @property {String} subfolder
|
||||
*/
|
||||
|
||||
const namePos = {
|
||||
hiroxnir: 1,
|
||||
nir: 1,
|
||||
visr: 1,
|
||||
hsi: 1,
|
||||
do: 2,
|
||||
dn: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @returns {ParsedMetadata}
|
||||
*/
|
||||
function parseHIROXNIRFilename(imgFilename) {
|
||||
const prefix = imgFilename.split('_')[0];
|
||||
const parts = prefix.split('-');
|
||||
return {
|
||||
papyrus: parts[1],
|
||||
imageAuthor: authors[parts[0].replace(/\d{4}/,'')],
|
||||
date: parts[0].match(/\d{4}/)[0],
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @returns {ParsedMetadata}
|
||||
*/
|
||||
function parseNIRFilename(imgFilename) {
|
||||
const prefix = imgFilename.split('_')[0];
|
||||
const parts = prefix.split('-');
|
||||
return {
|
||||
papyrus: parts[2],
|
||||
imageAuthor: authors[parts[0].replace(/\d{4}/, '')],
|
||||
date: parts[0].match(/\d{4}/)[0],
|
||||
copyright: COPYRIGHT.nir,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @returns {ParsedMetadata}
|
||||
*/
|
||||
function parseDNFilename(imgFilename) {
|
||||
return {
|
||||
papyrus: imgFilename.split('_')[1],
|
||||
imageAuthor: '',
|
||||
date: '',
|
||||
copyright: COPYRIGHT.dn,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} imgFilename
|
||||
* @returns {ParsedMetadata}
|
||||
*/
|
||||
function parseDOFilename(imgFilename) {
|
||||
return {
|
||||
papyrus: imgFilename.split('_')[1],
|
||||
imageAuthor: '',
|
||||
date: '',
|
||||
copyright: COPYRIGHT.do,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} filename The image filename
|
||||
* @returns {ImgFolderNames}
|
||||
*/
|
||||
function splitHIROXNIR(filename) {
|
||||
let splitFilename = filename.split('_');
|
||||
const papyrusNum = splitFilename[0].split('-')[1];
|
||||
const baseFolder = `PHerc_${papyrusNum}`;
|
||||
const subfolder = `PHerc_${papyrusNum}_HIROXNIR`;
|
||||
|
||||
return {baseFolder, subfolder};
|
||||
}
|
||||
/**
|
||||
* @param {string} filename The image filename
|
||||
* @returns {ImgFolderNames}
|
||||
*/
|
||||
function splitNIR(filename) {
|
||||
let splitFilename = filename.split('_');
|
||||
const papyrusNum = splitFilename[0].split('-')[2];
|
||||
const baseFolder = `PHerc_${papyrusNum}`;
|
||||
const subfolder = `PHerc_${papyrusNum}_${splitFilename[2].split('-')[0]}`;
|
||||
|
||||
return {baseFolder, subfolder};
|
||||
}
|
||||
/**
|
||||
* @todo Redundant...
|
||||
* @param {string} filename The image filename
|
||||
* @returns {ImgFolderNames}
|
||||
*/
|
||||
function splitHSI(filename) {
|
||||
let splitFilename = filename.split('_');
|
||||
const papyrusNumb = splitFilename[0].split('-')[2];
|
||||
const baseFolder = `PHerc_${papyrusNumb}`;
|
||||
const subfolder = `PHerc_${papyrusNumb}_${splitFilename[2]}`;
|
||||
|
||||
return {baseFolder, subfolder};
|
||||
}
|
||||
/**
|
||||
* @param {string} filename The image filename
|
||||
* @returns {ImgFolderNames}
|
||||
*/
|
||||
function splitDNO(filename) {
|
||||
let splitFilename = filename.split('_');
|
||||
const papyrusNumb = splitFilename[1];
|
||||
const baseFolder = `PHerc_${papyrusNumb}`;
|
||||
const subfolder = `PHerc_${papyrusNumb}_${splitFilename[0]}`;
|
||||
|
||||
return {baseFolder, subfolder};
|
||||
}
|
||||
|
||||
const splitters = {
|
||||
hiroxnir: splitHIROXNIR,
|
||||
nir: splitNIR,
|
||||
visr: splitNIR,
|
||||
dn: splitDNO,
|
||||
do: splitDNO,
|
||||
hsi: splitHSI
|
||||
};
|
||||
|
||||
const extractors = {
|
||||
nir: parseNIRFilename,
|
||||
dn: parseDNFilename,
|
||||
do: parseDOFilename,
|
||||
hsi: parseNIRFilename,
|
||||
visr: parseNIRFilename,
|
||||
hiroxnir: parseHIROXNIRFilename,
|
||||
}
|
||||
/**
|
||||
* Return metadata object from parsed
|
||||
* image filename
|
||||
* @param {string} imgFilename
|
||||
* @param {string} technique
|
||||
* @returns {ParsedMetadata}
|
||||
*/
|
||||
export function parse (imgFilename, technique) {
|
||||
return extractors[technique](imgFilename);
|
||||
}
|
||||
/**
|
||||
* Extract canvas name from image filename
|
||||
* @param {String} imgFilename
|
||||
* @param {String} technique
|
||||
* @returns {String}
|
||||
*/
|
||||
export function getCanvasName(imgFilename, technique) {
|
||||
// Remove file extension
|
||||
let canvasName = imgFilename.split('_')[namePos[technique]]
|
||||
.replace(/\.\w{1,3}$/, '');
|
||||
|
||||
if (technique === 'hsi') {
|
||||
canvasName += imgFilename.split('_')[3].replace(/\..*$/,'');
|
||||
}
|
||||
|
||||
return canvasName;
|
||||
}
|
||||
/**
|
||||
* Generate canvas label from canvasName
|
||||
* @todo Verify for HSI filenames
|
||||
* @param {String} canvasName
|
||||
* @returns {String}
|
||||
*/
|
||||
export function getCanvasLabel(canvasName) {
|
||||
return canvasName.replace(
|
||||
/c(\w{1,2})0+(\d+).*(\.\w{2,3})?$/i,
|
||||
(str, c, number) => `C${c}. ${number}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path (folder name) for a given image file
|
||||
* @param {String} filename
|
||||
* @param {String} technique
|
||||
* @returns {ImgFolderNames}
|
||||
*/
|
||||
export function getImagePath(filename, technique) {
|
||||
return splitters[technique](filename);
|
||||
}
|
||||
61
src/service/ImageRepository.js
Normal file
61
src/service/ImageRepository.js
Normal file
@@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
import * as fs from 'fs';
|
||||
/**
|
||||
* Handles filesystem for image repository
|
||||
* @module ImageRepository
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} manifestId
|
||||
* @returns {string[]}
|
||||
* @throws `readdir` will thrown an ENOENT error if the images folder doesn't exist.
|
||||
* The manifest route should catch it.
|
||||
*/
|
||||
export async function getImageList (manifestId) {
|
||||
// Regex to exclude images with certain patterns in filename
|
||||
const regexFilter = new RegExp(/(c2r|copertin.|camice|tit)/, 'i');
|
||||
let folderName = manifestId.replace(/pherc-(\d+)-(\w+)$/, function (_match, g1, g2) {
|
||||
return `PHerc_${g1}_${g2.toUpperCase()}`;
|
||||
});
|
||||
|
||||
let baseFolder = `${folderName.split('_')[0]}_${folderName.split('_')[1]}`;
|
||||
|
||||
let files = await fs.promises.readdir(
|
||||
`${process.env.IMAGES_DIR}/${baseFolder}/${folderName}`
|
||||
);
|
||||
|
||||
files = files.filter(file => !regexFilter.test(file) && !file.startsWith('.'));
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves available image techniques
|
||||
* for all papyruses based on folder contents
|
||||
* @returns {object}
|
||||
*/
|
||||
export async function getParamsFromFolders () {
|
||||
let params = [];
|
||||
const papyri = await fs.promises.readdir(process.env.IMAGES_DIR);
|
||||
|
||||
for (let p of papyri) {
|
||||
let techniques = [];
|
||||
for (let tech of await fs.promises.readdir(`${process.env.IMAGES_DIR}/${p}`)) {
|
||||
let files = await fs.promises.readdir(
|
||||
`${process.env.IMAGES_DIR}/${p}/${tech}`
|
||||
);
|
||||
files = files.filter(file => /(tiff?|jpe?g|jp2|bmp)/.test(file));
|
||||
if (files.length) {
|
||||
techniques.push(tech.replace(/PHerc_\d+_/i, ''));
|
||||
}
|
||||
}
|
||||
|
||||
params.push({
|
||||
name : p.replace('_', ' '),
|
||||
techniques
|
||||
});
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
176
src/service/ManifestBuilder.js
Normal file
176
src/service/ManifestBuilder.js
Normal file
@@ -0,0 +1,176 @@
|
||||
'use strict';
|
||||
|
||||
import Manifest from '../iiif/Manifest.js';
|
||||
import Sequence from '../iiif/Sequence.js';
|
||||
import Canvas from '../iiif/Canvas.js';
|
||||
import Image from '../iiif/Image.js';
|
||||
import ManifestMetadata from '../iiif/Metadata.js';
|
||||
import { parse, getCanvasLabel, getCanvasName } from './FilenameParser.js';
|
||||
import { getImageList } from './ImageRepository.js';
|
||||
import { TECH_NAMES } from '../constants.js';
|
||||
|
||||
/**
|
||||
* Builds a manifest object based on canvases
|
||||
* and available metadata
|
||||
* @module ManifestBuilder
|
||||
*/
|
||||
|
||||
const IIIF_API_VERSION = process.env.IIIF_API_VERSION;
|
||||
const BASE_URL = process.env.BASE_URL;
|
||||
const IMAGE_SERVER_URL = process.env.IMAGE_SERVER_URL;
|
||||
|
||||
/**
|
||||
* Builds a Manifest object
|
||||
* @param {String} manifestId
|
||||
* @returns {Object}
|
||||
*/
|
||||
export async function buildManifest(manifestId) {
|
||||
|
||||
let manifest = new Manifest(IIIF_API_VERSION, BASE_URL);
|
||||
manifest.generateID(manifestId);
|
||||
manifest.generateLabel();
|
||||
|
||||
const images = await getImageList(manifestId);
|
||||
|
||||
manifest = await populateCanvases(manifest, images, manifestId);
|
||||
|
||||
manifest.setMetadata(
|
||||
createMetadata(
|
||||
manifest,
|
||||
images[0], // A single image filename is sufficient to extract metadata
|
||||
)
|
||||
);
|
||||
|
||||
return manifest.toObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Canvas object from route parameters
|
||||
* @param {String} manifestId
|
||||
* @param {String} name The canvas name
|
||||
*/
|
||||
export async function buildCanvas(manifestId, name) {
|
||||
const manifest = new Manifest(IIIF_API_VERSION, BASE_URL);
|
||||
manifest.generateID(manifestId);
|
||||
let filename = await getImageName(name, manifestId);
|
||||
|
||||
return createCanvas(manifest, filename);
|
||||
}
|
||||
/**
|
||||
* Create a canvas from an image filename
|
||||
* @param {Manifest} manifest
|
||||
* @param {string} filename The image filename
|
||||
* @returns {Canvas}
|
||||
*/
|
||||
async function createCanvas (manifest, filename) {
|
||||
let canvas = new Canvas(IIIF_API_VERSION, BASE_URL);
|
||||
const canvasName = getCanvasName(filename, manifest.technique);
|
||||
|
||||
canvas.generateID(manifest.resourceId, canvasName.toLowerCase());
|
||||
canvas.label = getCanvasLabel(canvasName);
|
||||
|
||||
let image = new Image(canvas.id);
|
||||
image.generateID(IMAGE_SERVER_URL, filename, manifest.technique);
|
||||
|
||||
const imgSize = await getImageSize(image.id);
|
||||
image.setSize(imgSize.height, imgSize.width);
|
||||
|
||||
canvas.setThumbnail(
|
||||
imgSize.thumb.height,
|
||||
imgSize.thumb.width,
|
||||
image.id
|
||||
);
|
||||
canvas.addImage(image);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
/**
|
||||
* @param {string} imageId The image's id as a URL to the image server
|
||||
* @returns {{width: number, height: number, thumb: {width: number, height: number}}}
|
||||
*/
|
||||
async function getImageSize(imageId) {
|
||||
let infoURL = imageId.replace(/full.*$/,'info.json');
|
||||
const res = await fetch(infoURL);
|
||||
|
||||
let size = {};
|
||||
|
||||
if (res.ok) {
|
||||
const infoJson = await res.json();
|
||||
const maxSize = infoJson.sizes[infoJson.sizes.length - 1];
|
||||
size.height = maxSize.height;
|
||||
size.width = maxSize.width;
|
||||
size.thumb = {
|
||||
width: infoJson.sizes[1].width,
|
||||
height: infoJson.sizes[1].height,
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
/**
|
||||
* @param {Manifest} manifest The manifest object
|
||||
* @param {string[]} images List of image filenames from folder
|
||||
* @returns {Manifest}
|
||||
*/
|
||||
async function populateCanvases (manifest, images) {
|
||||
const sequence = new Sequence(BASE_URL);
|
||||
|
||||
// There's only one sequence
|
||||
sequence.generateID(manifest.resourceId, 0);
|
||||
|
||||
for (let img of images) {
|
||||
// Skip failing images (TODO log error to file)
|
||||
try {
|
||||
let canvas = await createCanvas(manifest, img);
|
||||
sequence.addCanvas(canvas);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.log(`\nAffected image: ${img}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort them according to their ID number... (cornice, colonna, ecc...)
|
||||
sequence.canvases.sort((a, b) => {
|
||||
const firstId = a['@id'].slice(a['@id'].lastIndexOf('/') + 1).replace(/[a-z]+/ig,'');
|
||||
const secondId = b['@id'].slice(b['@id'].lastIndexOf('/') + 1).replace(/[a-z]+/ig,'');
|
||||
return Number(firstId) - Number(secondId);
|
||||
});
|
||||
|
||||
manifest.addSequence(sequence);
|
||||
|
||||
return manifest;
|
||||
}
|
||||
/**
|
||||
* Get image name for given canvas
|
||||
* @todo Use regex in filter!!
|
||||
* @param {String} name The canvas name
|
||||
* @param {String} manifestId The manifest (resource) id
|
||||
* @returns {string}
|
||||
*/
|
||||
async function getImageName(name, manifestId) {
|
||||
const images = await getImageList(manifestId);
|
||||
|
||||
// Adjust canvas name for HSI with PCA...
|
||||
if (/pc(1|3)/.test(name)) {
|
||||
name = name.replace(
|
||||
/pc((1|3))/,
|
||||
function (match, group1) {
|
||||
return `_HSI_PC${group1}`;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return images.filter(i => i.includes(name))[0];
|
||||
}
|
||||
/**
|
||||
* @param {Manifest} manifest The Manifest object
|
||||
* @param {string} imgFilename
|
||||
* @returns {ManifestMetadata}
|
||||
*/
|
||||
function createMetadata(manifest, imgFilename) {
|
||||
let metadata = parse(imgFilename, manifest.technique);
|
||||
metadata.technique = TECH_NAMES[manifest.technique];
|
||||
|
||||
return new ManifestMetadata(metadata);
|
||||
}
|
||||
Reference in New Issue
Block a user