Start generalizing to work for Ward too
* Updates the command-line options in various ways, notably changing --start-url to --book * Renames the "book" directory to "staging" * Abstracts out most book-specific data into books.js (but not yet cover or substitutions.json) * Makes per-book subfolders under cache and staging directories
This commit is contained in:
parent
27e14360c9
commit
fd914b43f5
5 changed files with 87 additions and 49 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -2,5 +2,5 @@
|
||||||
/npm-debug.log
|
/npm-debug.log
|
||||||
|
|
||||||
cache/
|
cache/
|
||||||
book/
|
staging/
|
||||||
Worm.epub
|
*.epub
|
||||||
|
|
|
||||||
41
lib/books.js
Normal file
41
lib/books.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// TODO cover
|
||||||
|
|
||||||
|
exports.worm = {
|
||||||
|
startURL: "https://parahumans.wordpress.com/2011/06/11/1-1/",
|
||||||
|
title: "Worm",
|
||||||
|
author: "wildbow",
|
||||||
|
id: "e7f3532d-8db6-4888-be80-1976166b7059",
|
||||||
|
|
||||||
|
// First paragraph of https://parahumans.wordpress.com/about/
|
||||||
|
description: `
|
||||||
|
An introverted teenage girl with an unconventional superpower, Taylor goes out in costume to find escape from a deeply
|
||||||
|
unhappy and frustrated civilian life. Her first attempt at taking down a supervillain sees her mistaken for one,
|
||||||
|
thrusting her into the midst of the local ‘cape’ scene’s politics, unwritten rules, and ambiguous morals. As she risks
|
||||||
|
life and limb, Taylor faces the dilemma of having to do the wrong things for the right reasons.
|
||||||
|
`.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.ward = {
|
||||||
|
startURL: "https://www.parahumans.net/2017/09/11/daybreak-1-1/",
|
||||||
|
title: "Ward",
|
||||||
|
author: "wildbow",
|
||||||
|
id: "a6b6b156-2f17-43c0-8bb1-bfa91f3ef62a",
|
||||||
|
|
||||||
|
// Synposis from https://www.parahumans.net/
|
||||||
|
description: `
|
||||||
|
The unwritten rules that govern the fights and outright wars between ‘capes’ have been amended: everyone gets their
|
||||||
|
second chance. It’s an uneasy thing to come to terms with when notorious supervillains and even monsters are playing at
|
||||||
|
being hero. The world ended two years ago, and as humanity straddles the old world and the new, there aren’t records,
|
||||||
|
witnesses, or facilities to answer the villains’ past actions in the present. One of many compromises, uneasy truces and
|
||||||
|
deceptions that are starting to splinter as humanity rebuilds.
|
||||||
|
|
||||||
|
None feel the injustice of this new status quo or the lack of established footing more than the past residents of the
|
||||||
|
parahuman asylums. The facilities hosted parahumans and their victims, but the facilities are ruined or gone; one of
|
||||||
|
many fragile ex-patients is left to find a place in a fractured world. She’s perhaps the person least suited to have
|
||||||
|
anything to do with this tenuous peace or to stand alongside these false heroes. She’s put in a position to make the
|
||||||
|
decision: will she compromise to help forge what they call, with dark sentiment, a second golden age? Or will she stand
|
||||||
|
tall as a gilded dark age dawns?
|
||||||
|
`.trim()
|
||||||
|
};
|
||||||
|
|
@ -3,17 +3,7 @@ const fs = require("fs").promises;
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const cpr = require("util").promisify(require("cpr"));
|
const cpr = require("util").promisify(require("cpr"));
|
||||||
|
|
||||||
const BOOK_TITLE = "Worm";
|
|
||||||
const BOOK_AUTHOR = "wildbow";
|
|
||||||
const BOOK_PUBLISHER = "Domenic Denicola";
|
const BOOK_PUBLISHER = "Domenic Denicola";
|
||||||
const BOOK_ID = "urn:uuid:e7f3532d-8db6-4888-be80-1976166b7059";
|
|
||||||
|
|
||||||
// First paragraph of https://parahumans.wordpress.com/about/
|
|
||||||
const BOOK_DESCRIPTION = `
|
|
||||||
An introverted teenage girl with an unconventional superpower, Taylor goes out in costume to find escape from a deeply
|
|
||||||
unhappy and frustrated civilian life. Her first attempt at taking down a supervillain sees her mistaken for one,
|
|
||||||
thrusting her into the midst of the local ‘cape’ scene’s politics, unwritten rules, and ambiguous morals. As she risks
|
|
||||||
life and limb, Taylor faces the dilemma of having to do the wrong things for the right reasons.`;
|
|
||||||
|
|
||||||
const NCX_FILENAME = "toc.ncx";
|
const NCX_FILENAME = "toc.ncx";
|
||||||
|
|
||||||
|
|
@ -21,13 +11,13 @@ const COVER_IMG_FILENAME = "cover.png";
|
||||||
const COVER_XHTML_FILENAME = "cover.xhtml";
|
const COVER_XHTML_FILENAME = "cover.xhtml";
|
||||||
const COVER_MIMETYPE = "image/png";
|
const COVER_MIMETYPE = "image/png";
|
||||||
|
|
||||||
module.exports = async (scaffoldingPath, bookPath, contentPath, chaptersPath, manifestPath) => {
|
module.exports = async (scaffoldingPath, bookPath, contentPath, chaptersPath, manifestPath, bookInfo) => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
cpr(scaffoldingPath, bookPath, { overwrite: true, confirm: true, filter: noThumbs }),
|
cpr(scaffoldingPath, bookPath, { overwrite: true, confirm: true, filter: noThumbs }),
|
||||||
getChapters(contentPath, chaptersPath, manifestPath).then(chapters => {
|
getChapters(contentPath, chaptersPath, manifestPath).then(chapters => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
writeOPF(chapters, contentPath),
|
writeOPF(chapters, contentPath, bookInfo),
|
||||||
writeNcx(chapters, contentPath)
|
writeNcx(chapters, contentPath, bookInfo)
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
@ -38,7 +28,7 @@ function noThumbs(filePath) {
|
||||||
return path.basename(filePath) !== "Thumbs.db";
|
return path.basename(filePath) !== "Thumbs.db";
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeOPF(chapters, contentPath) {
|
function writeOPF(chapters, contentPath, bookInfo) {
|
||||||
const manifestChapters = chapters.map(c => {
|
const manifestChapters = chapters.map(c => {
|
||||||
return `<item id="${c.id}" href="${c.href}" media-type="application/xhtml+xml"/>`;
|
return `<item id="${c.id}" href="${c.href}" media-type="application/xhtml+xml"/>`;
|
||||||
}).join("\n");
|
}).join("\n");
|
||||||
|
|
@ -51,12 +41,12 @@ function writeOPF(chapters, contentPath) {
|
||||||
<package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId">
|
<package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId">
|
||||||
|
|
||||||
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
|
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
|
||||||
<dc:title>${BOOK_TITLE}</dc:title>
|
<dc:title>${bookInfo.title}</dc:title>
|
||||||
<dc:language>en</dc:language>
|
<dc:language>en</dc:language>
|
||||||
<dc:identifier id="BookId" opf:scheme="UUID">${BOOK_ID}</dc:identifier>
|
<dc:identifier id="BookId" opf:scheme="UUID">urn:uuid:${bookInfo.id}</dc:identifier>
|
||||||
<dc:creator opf:file-as="${BOOK_AUTHOR}" opf:role="aut">${BOOK_AUTHOR}</dc:creator>
|
<dc:creator opf:file-as="${bookInfo.author}" opf:role="aut">${bookInfo.author}</dc:creator>
|
||||||
<dc:publisher>${BOOK_PUBLISHER}</dc:publisher>
|
<dc:publisher>${BOOK_PUBLISHER}</dc:publisher>
|
||||||
<dc:description>${BOOK_DESCRIPTION}</dc:description>
|
<dc:description>${bookInfo.description}</dc:description>
|
||||||
<meta name="cover" content="cover-image"/>
|
<meta name="cover" content="cover-image"/>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
||||||
|
|
@ -80,7 +70,7 @@ ${spineChapters}
|
||||||
return fs.writeFile(path.resolve(contentPath, "content.opf"), contents);
|
return fs.writeFile(path.resolve(contentPath, "content.opf"), contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeNcx(chapters, contentPath) {
|
function writeNcx(chapters, contentPath, bookInfo) {
|
||||||
const navPoints = chapters.map((c, i) => {
|
const navPoints = chapters.map((c, i) => {
|
||||||
return `<navPoint class="chapter" id="${c.id}" playOrder="${i + 1}">
|
return `<navPoint class="chapter" id="${c.id}" playOrder="${i + 1}">
|
||||||
<navLabel><text>${c.title}</text></navLabel>
|
<navLabel><text>${c.title}</text></navLabel>
|
||||||
|
|
@ -92,18 +82,18 @@ function writeNcx(chapters, contentPath) {
|
||||||
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
|
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
|
||||||
<ncx version="2005-1" xml:lang="en" xmlns="http://www.daisy.org/z3986/2005/ncx/">
|
<ncx version="2005-1" xml:lang="en" xmlns="http://www.daisy.org/z3986/2005/ncx/">
|
||||||
<head>
|
<head>
|
||||||
<meta name="dtb:uid" content="${BOOK_ID}"/>
|
<meta name="dtb:uid" content="urn:uuid:${bookInfo.id}"/>
|
||||||
<meta name="dtb:depth" content="1"/>
|
<meta name="dtb:depth" content="1"/>
|
||||||
<meta name="dtb:totalPageCount" content="0"/>
|
<meta name="dtb:totalPageCount" content="0"/>
|
||||||
<meta name="dtb:maxPageNumber" content="0"/>
|
<meta name="dtb:maxPageNumber" content="0"/>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<docTitle>
|
<docTitle>
|
||||||
<text>${BOOK_TITLE}</text>
|
<text>${bookInfo.title}</text>
|
||||||
</docTitle>
|
</docTitle>
|
||||||
|
|
||||||
<docAuthor>
|
<docAuthor>
|
||||||
<text>${BOOK_AUTHOR}</text>
|
<text>${bookInfo.author}</text>
|
||||||
</docAuthor>
|
</docAuthor>
|
||||||
|
|
||||||
<navMap>
|
<navMap>
|
||||||
|
|
|
||||||
|
|
@ -6,51 +6,54 @@ const fs = require("fs").promises;
|
||||||
const yargs = require("yargs");
|
const yargs = require("yargs");
|
||||||
|
|
||||||
const packageJson = require("../package.json");
|
const packageJson = require("../package.json");
|
||||||
|
const books = require("./books.js");
|
||||||
const download = require("./download.js");
|
const download = require("./download.js");
|
||||||
const convert = require("./convert.js");
|
const convert = require("./convert.js");
|
||||||
const scaffold = require("./scaffold.js");
|
const scaffold = require("./scaffold.js");
|
||||||
const zip = require("./zip.js");
|
const zip = require("./zip.js");
|
||||||
|
|
||||||
|
const OUTPUT_DEFAULT = "(Book name).epub";
|
||||||
|
|
||||||
const argv = yargs
|
const argv = yargs
|
||||||
.usage(`${packageJson.description}\n\n${packageJson.name} [<command1> [<command2> [<command3> ...]]]\n\n` +
|
.usage(`${packageJson.description}\n\n${packageJson.name} [<command1> [<command2> [<command3> ...]]]\n\n` +
|
||||||
"Each command will fail if the previously-listed one has not yet been run (with matching options).")
|
"Each command will fail if the previously-listed one has not yet been run (with matching options).")
|
||||||
.command("download", "download all chapters by crawling parahumans.wordpress.com")
|
.command("download", "download all chapters into the cache")
|
||||||
.command("convert", "convert the raw chapter HTML files into cleaned-up ebook chapters")
|
.command("convert", "convert the raw HTML into cleaned-up ebook chapters")
|
||||||
.command("scaffold", "assemble the table of contents, etc. to complete the EPUB")
|
.command("scaffold", "assemble the table of contents, etc.")
|
||||||
.command("zip", "zip up the EPUB files into a .epub output")
|
.command("zip", "zip up the created files into a .epub output")
|
||||||
.option("s", {
|
.option("b", {
|
||||||
alias: "start-url",
|
alias: "book",
|
||||||
default: "https://parahumans.wordpress.com/2011/06/11/1-1/",
|
default: Object.keys(books)[0],
|
||||||
describe: "the URL from which to start crawling, for the download command",
|
describe: "the book to operate on",
|
||||||
|
choices: Object.keys(books),
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
global: true
|
global: true
|
||||||
})
|
})
|
||||||
.option("c", {
|
.option("c", {
|
||||||
alias: "cache-directory",
|
alias: "cache",
|
||||||
default: "cache",
|
default: "cache",
|
||||||
describe: "cache directory, for the download and convert commands",
|
describe: "cache directory for downloaded raw chapters",
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
global: true
|
global: true
|
||||||
})
|
})
|
||||||
.option("b", {
|
.option("s", {
|
||||||
alias: "book-directory",
|
alias: "staging",
|
||||||
default: "book",
|
default: "staging",
|
||||||
describe: "directory in which to assemble the EPUB files before zipping, for the convert, scaffold, and zip " +
|
describe: "directory in which to assemble the EPUB files",
|
||||||
"commands",
|
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
global: true
|
global: true
|
||||||
})
|
})
|
||||||
.option("o", {
|
.option("o", {
|
||||||
alias: "out",
|
alias: "out",
|
||||||
default: "Worm.epub",
|
default: OUTPUT_DEFAULT,
|
||||||
describe: "output file destination, for the zip command",
|
describe: "output file destination",
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
global: true
|
global: true
|
||||||
})
|
})
|
||||||
.option("j", {
|
.option("j", {
|
||||||
alias: "jobs",
|
alias: "jobs",
|
||||||
default: 10,
|
default: 10,
|
||||||
describe: "Number of concurrent read/write jobs to run, for the convert command",
|
describe: "Number of concurrent read/write conversion jobs",
|
||||||
requiresArg: true,
|
requiresArg: true,
|
||||||
global: true
|
global: true
|
||||||
})
|
})
|
||||||
|
|
@ -60,18 +63,21 @@ const argv = yargs
|
||||||
.version()
|
.version()
|
||||||
.argv;
|
.argv;
|
||||||
|
|
||||||
const cachePath = path.resolve(argv.cacheDirectory);
|
const outputFilename = argv.out === OUTPUT_DEFAULT ? `${books[argv.book].title}.epub` : argv.out;
|
||||||
|
|
||||||
|
const cachePath = path.resolve(argv.cache, argv.book);
|
||||||
const manifestPath = path.resolve(cachePath, "manifest.json");
|
const manifestPath = path.resolve(cachePath, "manifest.json");
|
||||||
|
|
||||||
const scaffoldingPath = path.resolve(__dirname, "../scaffolding");
|
const scaffoldingPath = path.resolve(__dirname, "../scaffolding");
|
||||||
const bookPath = path.resolve(argv.bookDirectory);
|
const stagingPath = path.resolve(argv.staging, argv.book);
|
||||||
const contentPath = path.resolve(bookPath, "OEBPS");
|
const contentPath = path.resolve(stagingPath, "OEBPS");
|
||||||
const chaptersPath = path.resolve(contentPath, "chapters");
|
const chaptersPath = path.resolve(contentPath, "chapters");
|
||||||
|
|
||||||
const commands = [];
|
const commands = [];
|
||||||
|
|
||||||
if (argv._.includes("download")) {
|
if (argv._.includes("download")) {
|
||||||
commands.push(() => download(argv.startUrl, cachePath, manifestPath));
|
const startURL = books[argv.book].startURL;
|
||||||
|
commands.push(() => download(startURL, cachePath, manifestPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv._.includes("convert")) {
|
if (argv._.includes("convert")) {
|
||||||
|
|
@ -83,11 +89,12 @@ if (argv._.includes("convert")) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv._.includes("scaffold")) {
|
if (argv._.includes("scaffold")) {
|
||||||
commands.push(() => scaffold(scaffoldingPath, bookPath, contentPath, chaptersPath, manifestPath));
|
const bookInfo = books[argv.book];
|
||||||
|
commands.push(() => scaffold(scaffoldingPath, stagingPath, contentPath, chaptersPath, manifestPath, bookInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv._.includes("zip")) {
|
if (argv._.includes("zip")) {
|
||||||
commands.push(() => zip(bookPath, contentPath, path.resolve(argv.out)));
|
commands.push(() => zip(stagingPath, contentPath, path.resolve(outputFilename)));
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,6 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<img src="cover.png" alt="Worm"/>
|
<img src="cover.png" alt=""/>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue