UNPKG

r2-shared-js

Version:

Readium 2 'shared' for NodeJS (TypeScript)

550 lines 21.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const crypto = require("crypto"); const deepEqual = require("fast-deep-equal"); const fs = require("fs"); const jsonDiff = require("json-diff"); const path = require("path"); const url_1 = require("url"); const util = require("util"); const media_overlay_1 = require("../models/media-overlay"); const publication_1 = require("../models/publication"); const publication_link_1 = require("../models/publication-link"); const audiobook_1 = require("../parser/audiobook"); const daisy_1 = require("../parser/daisy"); const daisy_convert_to_epub_1 = require("../parser/daisy-convert-to-epub"); const epub_1 = require("../parser/epub"); const epub_daisy_common_1 = require("../parser/epub-daisy-common"); const publication_parser_1 = require("../parser/publication-parser"); const lcp_1 = require("r2-lcp-js/dist/es7-es2016/src/parser/epub/lcp"); const serializable_1 = require("r2-lcp-js/dist/es7-es2016/src/serializable"); const UrlUtils_1 = require("r2-utils-js/dist/es7-es2016/src/_utils/http/UrlUtils"); const BufferUtils_1 = require("r2-utils-js/dist/es7-es2016/src/_utils/stream/BufferUtils"); const transformer_1 = require("../transform/transformer"); const init_globals_1 = require("../init-globals"); const zipHasEntry_1 = require("./zipHasEntry"); (0, init_globals_1.initGlobalConverters_SHARED)(); (0, init_globals_1.initGlobalConverters_GENERIC)(); (0, lcp_1.setLcpNativePluginPath)(path.join(process.cwd(), "LCP", "lcp.node")); console.log("process.cwd():"); console.log(process.cwd()); console.log("__dirname: "); console.log(__dirname); const args = process.argv.slice(2); console.log("args:"); console.log(args); if (!args[0]) { console.log("FILEPATH ARGUMENT IS MISSING."); process.exit(1); } const argPath = args[0].trim(); let filePath = argPath; console.log(filePath); if (!(0, UrlUtils_1.isHTTP)(filePath)) { if (!fs.existsSync(filePath)) { filePath = path.join(__dirname, argPath); console.log(filePath); if (!fs.existsSync(filePath)) { filePath = path.join(process.cwd(), argPath); console.log(filePath); if (!fs.existsSync(filePath)) { console.log("FILEPATH DOES NOT EXIST."); process.exit(1); } } } const stats = fs.lstatSync(filePath); if (!stats.isFile() && !stats.isDirectory()) { console.log("FILEPATH MUST BE FILE OR DIRECTORY."); process.exit(1); } } let fileName = filePath; if ((0, UrlUtils_1.isHTTP)(filePath)) { const url = new url_1.URL(filePath); fileName = url.pathname; } fileName = fileName.replace(/META-INF[\/|\\]container.xml$/, ""); fileName = path.basename(fileName); let generateDaisyAudioManifestOnly = false; let decryptKeys; if (args[2]) { if (args[2] === "generate-daisy-audio-manifest-only") { generateDaisyAudioManifestOnly = true; } else { decryptKeys = args[2].trim().split(";"); } } let outputDirPath; if (args[1]) { const argDir = args[1].trim(); let dirPath = argDir; console.log(dirPath); if (!fs.existsSync(dirPath)) { dirPath = path.join(__dirname, argDir); console.log(dirPath); if (!fs.existsSync(dirPath)) { dirPath = path.join(process.cwd(), argDir); console.log(dirPath); if (!fs.existsSync(dirPath)) { console.log("DIRPATH DOES NOT EXIST."); process.exit(1); } else { if (!fs.lstatSync(dirPath).isDirectory()) { console.log("DIRPATH MUST BE DIRECTORY."); process.exit(1); } } } } dirPath = fs.realpathSync(dirPath); if (generateDaisyAudioManifestOnly) { outputDirPath = dirPath; console.log(outputDirPath); } else { const fileNameNoExt = fileName + "_R2_EXTRACTED"; console.log(fileNameNoExt); outputDirPath = path.join(dirPath, fileNameNoExt); console.log(outputDirPath); if (fs.existsSync(outputDirPath)) { console.log("OUTPUT FOLDER ALREADY EXISTS!"); process.exit(1); } } } (() => tslib_1.__awaiter(void 0, void 0, void 0, function* () { var _a, _b; let publication; try { publication = yield (0, publication_parser_1.PublicationParsePromise)(filePath); } catch (err) { console.log("== Publication Parser: reject"); console.log(err); return; } const isAnEPUB = (0, epub_1.isEPUBlication)(filePath); let isAnAudioBook; try { isAnAudioBook = yield (0, audiobook_1.isAudioBookPublication)(filePath); } catch (_err) { } let isDaisyBook; try { isDaisyBook = yield (0, daisy_1.isDaisyPublication)(filePath); } catch (_err) { } if ((isDaisyBook || isAnAudioBook || isAnEPUB) && outputDirPath) { try { if (isDaisyBook) { yield (0, daisy_convert_to_epub_1.convertDaisyToReadiumWebPub)(outputDirPath, publication, generateDaisyAudioManifestOnly ? fileName : undefined); const isFullTextAudio = ((_a = publication.Metadata) === null || _a === void 0 ? void 0 : _a.AdditionalJSON) && (publication.Metadata.AdditionalJSON["dtb:multimediaType"] === "audioFullText" || publication.Metadata.AdditionalJSON["ncc:multimediaType"] === "audioFullText" || (!publication.Metadata.AdditionalJSON["dtb:multimediaType"] && !publication.Metadata.AdditionalJSON["ncc:multimediaType"])); if (isFullTextAudio && !((_b = publication.Spine) === null || _b === void 0 ? void 0 : _b.length)) { console.log("%%%%% FAILED audio+text DAISY convert, trying again as audio-only ..."); publication.freeDestroy(); try { publication = yield (0, publication_parser_1.PublicationParsePromise)(filePath); } catch (err) { console.log("== Publication Parser: reject"); console.log(err); return; } yield new Promise((reso) => { setTimeout(() => tslib_1.__awaiter(void 0, void 0, void 0, function* () { reso(yield (0, daisy_convert_to_epub_1.convertDaisyToReadiumWebPub)(outputDirPath, publication, generateDaisyAudioManifestOnly ? fileName : undefined, true)); }), 500); }); } } else { yield extractEPUB((isAnEPUB || isDaisyBook) ? true : false, publication, outputDirPath, decryptKeys); } } catch (err) { console.log("== Publication extract FAIL"); console.log(err); return; } } else { yield dumpPublication(publication); } }))(); function extractEPUB_ManifestJSON(pub, outDir, keys) { const manifestJson = (0, serializable_1.TaJsonSerialize)(pub); const arrLinks = []; if (manifestJson.readingOrder) { arrLinks.push(...manifestJson.readingOrder); } if (manifestJson.resources) { arrLinks.push(...manifestJson.resources); } if (keys) { arrLinks.forEach((link) => { if (link.properties && link.properties.encrypted && link.properties.encrypted.scheme === "http://readium.org/2014/01/lcp") { delete link.properties.encrypted; let atLeastOne = false; const jsonProps = Object.keys(link.properties); if (jsonProps) { jsonProps.forEach((jsonProp) => { if (link.properties.hasOwnProperty(jsonProp)) { atLeastOne = true; return false; } return true; }); } if (!atLeastOne) { delete link.properties; } } }); if (manifestJson.links) { const lks = manifestJson.links; let index = -1; for (let i = 0; i < lks.length; i++) { const link = lks[i]; if (link.type === "application/vnd.readium.lcp.license.v1.0+json" && link.rel === "license") { index = i; break; } } if (index >= 0) { lks.splice(index, 1); } if (lks.length === 0) { delete manifestJson.links; } } } arrLinks.forEach((link) => { if (link.properties && link.properties.encrypted && (link.properties.encrypted.algorithm === "http://www.idpf.org/2008/embedding" || link.properties.encrypted.algorithm === "http://ns.adobe.com/pdf/enc#RC")) { delete link.properties.encrypted; let atLeastOne = false; const jsonProps = Object.keys(link.properties); if (jsonProps) { jsonProps.forEach((jsonProp) => { if (link.properties.hasOwnProperty(jsonProp)) { atLeastOne = true; return false; } return true; }); } if (!atLeastOne) { delete link.properties; } } }); const manifestJsonStr = JSON.stringify(manifestJson, null, " "); const manifestJsonPath = path.join(outDir, "manifest.json"); fs.writeFileSync(manifestJsonPath, manifestJsonStr, "utf8"); } function extractEPUB_Check(zip, outDir) { return tslib_1.__awaiter(this, void 0, void 0, function* () { let zipEntries; try { zipEntries = yield zip.getEntries(); } catch (err) { console.log(err); } if (zipEntries) { for (const zipEntry of zipEntries) { if (zipEntry !== "mimetype" && !zipEntry.startsWith("META-INF/") && !/\.opf$/i.test(zipEntry) && !/ncc\.html$/i.test(zipEntry) && zipEntry !== "publication.json" && zipEntry !== "license.lcpl" && !zipEntry.endsWith(".DS_Store") && !zipEntry.startsWith("__MACOSX/")) { const expectedOutputPath = path.join(outDir, zipEntry); if (!fs.existsSync(expectedOutputPath)) { console.log("Zip entry not extracted??"); console.log(expectedOutputPath); } } } } }); } function extractEPUB_ProcessKeys(pub, keys) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!pub.LCP || !keys) { return; } const keysSha256Hex = keys.map((key) => { console.log("@@@"); console.log(key); if (key.length === 64) { let isHex = true; for (let i = 0; i < key.length; i += 2) { const hexByte = key.substr(i, 2).toLowerCase(); const parsedInt = parseInt(hexByte, 16); if (isNaN(parsedInt)) { isHex = false; break; } } if (isHex) { return key; } } const checkSum = crypto.createHash("sha256"); checkSum.update(key); const keySha256Hex = checkSum.digest("hex"); console.log(keySha256Hex); return keySha256Hex; }); try { yield pub.LCP.tryUserKeys(keysSha256Hex); } catch (err) { console.log(err); throw Error("FAIL publication.LCP.tryUserKeys()"); } }); } function extractEPUB_Link(pub, zip, outDir, link) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const hrefDecoded = link.HrefDecoded; console.log("===== " + hrefDecoded); if (!hrefDecoded) { console.log("!?link.HrefDecoded"); return; } const has = yield (0, zipHasEntry_1.zipHasEntry)(zip, hrefDecoded, link.Href); if (!has) { console.log(`NOT IN ZIP (extractEPUB_Link): ${link.Href} --- ${hrefDecoded}`); const zipEntries = yield zip.getEntries(); for (const zipEntry of zipEntries) { if (zipEntry.startsWith("__MACOSX/")) { continue; } console.log(zipEntry); } return; } let zipStream_; try { zipStream_ = yield zip.entryStreamPromise(hrefDecoded); } catch (err) { console.log(hrefDecoded); console.log(err); return; } let transformedStream; try { transformedStream = yield transformer_1.Transformers.tryStream(pub, link, undefined, zipStream_, false, 0, 0, undefined); } catch (err) { console.log(hrefDecoded); console.log(err); return; } zipStream_ = transformedStream; let zipData; try { zipData = yield (0, BufferUtils_1.streamToBufferPromise)(zipStream_.stream); } catch (err) { console.log(hrefDecoded); console.log(err); return; } const linkOutputPath = path.join(outDir, hrefDecoded); ensureDirs(linkOutputPath); fs.writeFileSync(linkOutputPath, zipData); }); } function extractEPUB(isEPUB, pub, outDir, keys) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const zipInternal = pub.findFromInternal("zip"); if (!zipInternal) { console.log("No publication zip!?"); return; } const zip = zipInternal.Value; try { yield extractEPUB_ProcessKeys(pub, keys); } catch (err) { console.log(err); throw err; } ensureDirs(path.join(outDir, "DUMMY_FILE.EXT")); try { yield extractEPUB_MediaOverlays(pub, zip, outDir); } catch (err) { console.log(err); } extractEPUB_ManifestJSON(pub, outDir, keys); const links = []; if (pub.Resources) { links.push(...pub.Resources); } if (pub.Spine) { links.push(...pub.Spine); } if (!keys) { const lic = (isEPUB ? "META-INF/" : "") + "license.lcpl"; const has = yield (0, zipHasEntry_1.zipHasEntry)(zip, lic, undefined); if (has) { const l = new publication_link_1.Link(); l.setHrefDecoded(lic); links.push(l); } } for (const link of links) { try { yield extractEPUB_Link(pub, zip, outDir, link); } catch (err) { console.log(err); } } try { yield extractEPUB_Check(zip, outDir); } catch (err) { console.log(err); } }); } function extractEPUB_MediaOverlays(pub, _zip, outDir) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (!pub.Spine) { return; } let i = -1; for (const spineItem of pub.Spine) { if (spineItem.MediaOverlays) { const mo = spineItem.MediaOverlays; try { yield (0, epub_daisy_common_1.lazyLoadMediaOverlays)(pub, mo); } catch (err) { return Promise.reject(err); } const moJsonObj = (0, serializable_1.TaJsonSerialize)(mo); const moJsonStr = global.JSON.stringify(moJsonObj, null, " "); i++; const p = `media-overlays_${i}.json`; const moJsonPath = path.join(outDir, p); fs.writeFileSync(moJsonPath, moJsonStr, "utf8"); if (spineItem.Properties && spineItem.Properties.MediaOverlay) { spineItem.Properties.MediaOverlay = p; } if (spineItem.Alternate) { for (const altLink of spineItem.Alternate) { if (altLink.TypeLink === "application/vnd.syncnarr+json") { altLink.Href = p; } } } } } }); } function ensureDirs(fspath) { const dirname = path.dirname(fspath); if (!fs.existsSync(dirname)) { ensureDirs(dirname); fs.mkdirSync(dirname); } } function dumpPublication(publication) { return tslib_1.__awaiter(this, void 0, void 0, function* () { console.log("#### RAW OBJECT:"); console.log(util.inspect(publication, { showHidden: false, depth: 1000, colors: true, customInspect: true })); const publicationJsonObj = (0, serializable_1.TaJsonSerialize)(publication); console.log(util.inspect(publicationJsonObj, { showHidden: false, depth: 1000, colors: true, customInspect: true })); const publicationJsonStr = global.JSON.stringify(publicationJsonObj, null, " "); const publicationReverse = (0, serializable_1.TaJsonDeserialize)(publicationJsonObj, publication_1.Publication); const publicationJsonObjReverse = (0, serializable_1.TaJsonSerialize)(publicationReverse); const eq = deepEqual(publicationJsonObj, publicationJsonObjReverse); if (!eq) { console.log("#### TA-JSON SERIALIZED JSON OBJ:"); console.log(publicationJsonObj); console.log("#### STRINGIFIED JSON OBJ:"); console.log(publicationJsonStr); console.log("#### TA-JSON DESERIALIZED (REVERSE):"); console.log(util.inspect(publicationReverse, { showHidden: false, depth: 1000, colors: true, customInspect: true })); console.log("#### TA-JSON SERIALIZED JSON OBJ (REVERSE):"); console.log(publicationJsonObjReverse); console.log("#### REVERSE NOT DEEP EQUAL!\n\n"); console.log("#### REVERSE NOT DEEP EQUAL!\n\n"); console.log("#### REVERSE NOT DEEP EQUAL!\n\n"); } console.log(jsonDiff.diffString(publicationJsonObj, publicationJsonObjReverse)); if (publication.Spine) { for (const spineItem of publication.Spine) { if (spineItem.Properties && spineItem.Properties.MediaOverlay) { console.log(spineItem.Href); console.log(spineItem.Properties.MediaOverlay); console.log(spineItem.Duration); } if (spineItem.Alternate) { for (const altLink of spineItem.Alternate) { if (altLink.TypeLink === "application/vnd.syncnarr+json") { console.log(altLink.Href); console.log(altLink.TypeLink); console.log(altLink.Duration); } } } if (spineItem.MediaOverlays) { const mo = spineItem.MediaOverlays; if (!mo.initialized) { console.log(util.inspect(mo, { showHidden: false, depth: 1000, colors: true, customInspect: true })); } console.log(mo.SmilPathInZip); try { yield (0, epub_daisy_common_1.lazyLoadMediaOverlays)(publication, mo); } catch (err) { return Promise.reject(err); } const moJsonObj = (0, serializable_1.TaJsonSerialize)(mo); const moJsonStr = global.JSON.stringify(moJsonObj, null, " "); console.log(moJsonStr.substr(0, 1000) + "\n...\n"); const moReverse = (0, serializable_1.TaJsonDeserialize)(moJsonObj, media_overlay_1.MediaOverlayNode); const moJsonObjReverse = (0, serializable_1.TaJsonSerialize)(moReverse); const equa = deepEqual(moJsonObj, moJsonObjReverse); if (!equa) { console.log("#### TA-JSON SERIALIZED JSON OBJ:"); console.log(moJsonObj); console.log("#### STRINGIFIED JSON OBJ:"); console.log(moJsonStr); console.log("#### TA-JSON DESERIALIZED (REVERSE):"); console.log(util.inspect(moReverse, { showHidden: false, depth: 1000, colors: true, customInspect: true })); console.log("#### TA-JSON SERIALIZED JSON OBJ (REVERSE):"); console.log(moJsonObjReverse); console.log("#### REVERSE NOT DEEP EQUAL!\n\n"); console.log("#### REVERSE NOT DEEP EQUAL!\n\n"); console.log("#### REVERSE NOT DEEP EQUAL!\n\n"); } console.log(jsonDiff.diffString(moJsonObj, moJsonObjReverse)); } } } }); } //# sourceMappingURL=cli.js.map