Merge 7b43f7c882
into 2fbad60613
This commit is contained in:
commit
6513a27669
1
Gemfile
1
Gemfile
|
@ -4,3 +4,4 @@ source "https://rubygems.org"
|
|||
gem 'nokogiri'
|
||||
gem 'paru'
|
||||
gem 'humanize'
|
||||
gem 'rubyzip'
|
||||
|
|
21
HACKING.md
21
HACKING.md
|
@ -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`
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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.
|
@ -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)
|
|
@ -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>
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -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
|
Loading…
Reference in New Issue