This commit is contained in:
BeeWall 2023-08-12 11:20:48 +05:30 committed by GitHub
commit 6513a27669
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 290 additions and 0 deletions

View File

@ -4,3 +4,4 @@ source "https://rubygems.org"
gem 'nokogiri'
gem 'paru'
gem 'humanize'
gem 'rubyzip'

View File

@ -11,6 +11,7 @@ If you are interested in just generating the books, follow the guide on the READ
- (mobi only): `ebook-convert` (from calibre) available to generate the mobi file
- (pdf) `wkhtmltopdf` for converting html to pdf
- (pdf) `pdftk` to stitch the final PDF file
(annotating scripts only) `zip` to unpack the EPUB
### Notes
@ -76,3 +77,23 @@ All the generated files will be saved with the filename `books/skyward.{epub|pdf
This is just lazily using Pandoc, since there is just a single page.
pandoc -t epub https://brandonsanderson.com/defending-elysium/ -o defending-elysium.epub --epub-cover-image=covers/defending-elysium.jpg --epub-metadata=metadata/defending-elysium.xml
## Annotation
There is support for adding annotations to a few books. You can either generate an eBook consisting only of the annotations, or add the annotations on to a provided EPUB file (other formats are not supported).
To provide a legally owned copy of the EPUB to annotate, place the file under `original-books` and rename it to the given filename.
### Elantris
To generate the annotations:
ruby elantris-annotations.rb
All the generated files will be saved with the filename `elantris-annotations.{epub|pdf|mobi|html}`
To annotate the EPUB, place the file under `original-books` and name it `elantris.epub`, and run:
ruby annotate-elantris.rb
The generated file will be saved with the filename `annotated-elantris.epub`

View File

@ -135,6 +135,8 @@ Below description from [Sanderson's website](https://www.brandonsanderson.com/wa
In progress, only Chapter 1 so far.
There is also experimental support for adding Sanderson's annotations to a few books (currently supported: Elantris). For more, see [HACKING.md](HACKING.md).
## Extra
If you'd like to see any other books covered here, please [create an issue](https://github.com/captn3m0/cosmere-books/issues/new), or reach out to me: <https://captnemo.in/contact/>

101
annotate-elantris.rb Normal file
View File

@ -0,0 +1,101 @@
# frozen_string_literal: true
require 'date'
require 'fileutils'
require 'nokogiri'
require 'zip'
require_relative './methods'
require_relative './zip-file-generator'
require_relative './elantris-annotations'
FileUtils.mkdir_p('annotated-elantris')
Zip.on_exists_proc = true
Zip.continue_on_exists_proc = true
Zip::File.open('original-books/elantris.epub') do |zipfile|
zipfile.each do |entry|
FileUtils::mkdir_p(File.dirname("annotated-elantris/#{entry.name}"))
zipfile.extract(entry, "annotated-elantris/#{entry.name}")
end
end
Zip::File.open('books/elantris-annotations.epub') do |zipfile|
zipfile.each do |entry|
if (File.dirname(entry.name) == "EPUB/text")
new_path = "annotated-elantris/OEBPS/xhtml/annotation-#{File.basename(entry.name)}"
zipfile.extract(entry, new_path)
end
end
end
files = [
{
"annotation-ch002" => "title",
"annotation-ch003" => "dedication",
"annotation-ch004" => "acknowledgments",
"annotation-ch005" => "prologue",
"annotation-ch033" => "part1"
},
(1..4).map{|x| ["annotation-ch00#{5+x}", "chapter#{x}"]}.to_h,
(5..27).map{|x| ["annotation-ch0#{5+x}", "chapter#{x}"]}.to_h,
{"annotation-ch061" =>"part2"},
(28..54).map{|x| ["annotation-ch0#{6+x}", "chapter#{x}"]}.to_h,
{"annotation-ch072" => "part3"},
(55..63).map{|x| ["annotation-ch0#{7+x}", "chapter#{x}"]}.to_h,
{"annotation-ch071" => "epilogue"}
].reduce(:merge)
package = Nokogiri::XML(open("annotated-elantris/OEBPS/volume.opf"))
toc = Nokogiri::XML(open("annotated-elantris/OEBPS/nav.xhtml"))
toc.at_css('ol') << "<li><a href=\"xhtml/annotation-ch001.xhtml\">Annotations</a><ol id=\"annotations\"></ol></li>"
Dir.glob('annotated-elantris/OEBPS/xhtml/annotation-ch*.xhtml').sort.each {|file|
page = File.basename(file, '.xhtml')
package.at_css('manifest') << "<item id=\"#{page}\" href=\"xhtml/#{page}.xhtml\" media-type=\"application/xhtml+xml\" />"
package.at_css('spine') << "<itemref idref=\"#{page}\" />"
if files.key?(page)
chapter = files[page]
chapter_file = Nokogiri::XML(open("annotated-elantris/OEBPS/xhtml/#{chapter}.xhtml"))
annotation_file = Nokogiri::XML(open(file))
case chapter
when 'title'
id = '#tit'
when 'dedication'
id = '#ded'
when 'acknowledgments'
id = '.FMH'
when 'prologue'
id = '.CT'
when 'epilogue'
id = '.CT'
when /part/
id = '.PT'
else
id = '.CN'
end
chapter_file.at_css(id).next = "<a href=\"#{page}.xhtml\">Go to Annotations</a><br>"
unless chapter.include?('title') || chapter.include?('dedication') || chapter.include?('part')
chapter_file.at_css('body') << "<a href=\"#{page}.xhtml\">Go to Annotations</a><br>"
end
annotation_file.at_css('h1').next = "<a href=\"#{chapter}.xhtml\">Back to Chapter</a><br>"
annotation_file.at_css('body') << "<a href=\"#{chapter}.xhtml\">Back to Chapter</a><br>"
toc.at_css('#annotations') << "<li><a href=\"xhtml/#{page}.xhtml\">#{annotation_file.at_css('h1').inner_html}</a></li>"
File.open("annotated-elantris/OEBPS/xhtml/#{chapter}.xhtml", 'w') { |file| file.write(chapter_file.to_xhtml) }
File.open(file, 'w') { |file| file.write(annotation_file.to_xhtml) }
end
}
File.open("annotated-elantris/OEBPS/volume.opf", 'w') { |file| file.write(package.to_xml) }
File.open("annotated-elantris/OEBPS/nav.xhtml", 'w') { |file| file.write(toc.to_xhtml) }
File.delete('books/annotated-elantris.epub') if File.exists? 'books/annotated-elantris.epub'
ZipFileGenerator.new('annotated-elantris', 'books/annotated-elantris.epub').write()
puts '[epub] Generated annotated EPUB file'

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

104
elantris-annotations.rb Normal file
View File

@ -0,0 +1,104 @@
# frozen_string_literal: true
require 'date'
require 'fileutils'
require 'nokogiri'
require_relative './methods'
FileUtils.mkdir_p('elantris-annotations')
BASE = 'https://brandonsanderson.com'
links = ["/annotation-elantris-introduction/"] +
["/annotation-elantris-title-page/"] +
["/annotation-elantris-dedication/"] +
["/annotation-elantris-acknowledgements-page/"] +
["/annotation-elantris-prologue/"] +
(1..27).map{|x| "/annotation-elantris-chapter-#{x}/"} +
["/annotation-elantris-part-one-wrap-up/"] +
(28..45).map{|x| "/annotation-elantris-chapter-#{x}/"} +
(1..2).map{|x| "/annotation-elantris-chapter-46-#{x}/"} +
(47..48).map{|x| "/annotation-elantris-chapter-#{x}/"} +
(1..2).map{|x| "/annotation-elantris-49-#{x}/"} +
(50..52).map{|x| "/annotation-elantris-chapter-#{x}/"} +
(1..3).map{|x| "/annotation-elantris-53-#{x}/"} +
["/annotation-elantris-chapter-54/"] +
["/annotation-elantris-part-two-wrap-up/"] +
(55..57).map{|x| "/annotation-elantris-chapter-#{x}/"} +
(1..4).map{|x| "/annotation-elantris-58-#{x}/"} +
(1..2).map{|x| "/annotation-elantris-59-#{x}/"} +
(1..4).map{|x| "/annotation-elantris-60-#{x}/"} +
(1..4).map{|x| "/annotation-elantris-61-#{x}/"} +
(1..3).map{|x| "/annotation-elantris-62-#{x}/"} +
["/annotation-elantris-chapter-63/"] +
["/annotation-elantris-epilogue/"] +
["/annotation-elantris-book-wrap-up/"]
titles = ["Introduction"] +
["Title Page"] +
["Dedication"] +
["Acknowledgements"] +
["Prologue"] +
(1..27).map{|x| "Chapter #{x}"} +
["Part One Wrap-Up"] +
(28..45).map{|x| "Chapter #{x}"} +
(1..2).map{|x| "Chapter 46"} +
(47..48).map{|x| "Chapter #{x}"} +
(1..2).map{|x| "Chapter 49"} +
(50..52).map{|x| "Chapter #{x}"} +
(1..3).map{|x| "Chapter 53"} +
["Chapter 54"] +
["Part Two Wrap-Up"] +
(55..57).map{|x| "Chapter #{x}"} +
(1..4).map{|x| "Chapter 58"} +
(1..2).map{|x| "Chapter 59"} +
(1..4).map{|x| "Chapter 60"} +
(1..4).map{|x| "Chapter 61"} +
(1..3).map{|x| "Chapter 62"} +
["Chapter 63"] +
["Epilogue"] +
["Book Wrap-Up"]
episode=1
links.each do |link|
url = BASE + link
puts "Download #{url}"
unless File.exist? "elantris-annotations/#{episode}.html"
`wget --no-clobber "#{url}" --output-document "elantris-annotations/#{episode}.html" -o /dev/null`
end
episode+=1
end
html = '<html lang=en><head><title>Elantris Annotations</title></head><body>'
(1..(links.length)).each do |i|
complete_html = Nokogiri::HTML(open("elantris-annotations/#{i}.html"))
# Show all spoiler sections
complete_html.css('.sh-content.sh-hide').each do |e|
e.remove_attribute("style")
end
# For whatever reason, these pages have a different class on the container.
if [82,83,84].include? i
page = complete_html.css('.vc_col-sm-8 .wpb_wrapper')[1]
else
page = complete_html.css('.vc_col-sm-7 .wpb_wrapper')[0]
end
# Some chapters have the annotations split in several parts. This appends them all to their first parts.
if [53,57,62,63,70,71,72,74,76,77,78,80,81,82,84,85].include? i
html += "<hr>" + page.inner_html
else
html += "<h1>Annotations for #{titles[i - 1]}</h1>" + page.inner_html
end
end
html += '</body></html>'
html.gsub! "Show Spoiler", "Spoiler Ahead"
File.open('books/elantris-annotations.html', 'w') { |file| file.write(html) }
puts '[html] Generated HTML file'
generate('elantris-annotations', :all)

View File

@ -0,0 +1,4 @@
<dc:title id="epub-title-1">Elantris Annotations</dc:title>
<dc:date>2006-05-26</dc:date>
<dc:language>en-US</dc:language>
<dc:creator id="epub-creator-1" opf:role="aut">Brandon Sanderson</dc:creator>

2
original-books/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

55
zip-file-generator.rb Normal file
View File

@ -0,0 +1,55 @@
# Taken from the rubyzip sample files
require 'zip'
# This is a simple example which uses rubyzip to
# recursively generate a zip file from the contents of
# a specified directory. The directory itself is not
# included in the archive, rather just its contents.
#
# Usage:
# directory_to_zip = "/tmp/input"
# output_file = "/tmp/out.zip"
# zf = ZipFileGenerator.new(directory_to_zip, output_file)
# zf.write()
class ZipFileGenerator
# Initialize with the directory to zip and the location of the output archive.
def initialize(input_dir, output_file)
@input_dir = input_dir
@output_file = output_file
end
# Zip the input directory.
def write
entries = Dir.entries(@input_dir) - %w[. ..]
::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
write_entries entries, '', zipfile
end
end
private
# A helper method to make the recursion work.
def write_entries(entries, path, zipfile)
entries.each do |e|
zipfile_path = path == '' ? e : File.join(path, e)
disk_file_path = File.join(@input_dir, zipfile_path)
if File.directory? disk_file_path
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
else
put_into_archive(disk_file_path, zipfile, zipfile_path)
end
end
end
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
zipfile.mkdir zipfile_path
subdir = Dir.entries(disk_file_path) - %w[. ..]
write_entries subdir, zipfile_path, zipfile
end
def put_into_archive(disk_file_path, zipfile, zipfile_path)
zipfile.add(zipfile_path, disk_file_path)
end
end