magicshelf 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +6 -0
- data/Procfile +4 -0
- data/README.md +38 -0
- data/Rakefile +16 -0
- data/bin/console +14 -0
- data/bin/magicconvert +40 -0
- data/bin/server +8 -0
- data/bin/setup +7 -0
- data/config.ru +6 -0
- data/lib/magicshelf.rb +33 -0
- data/lib/magicshelf/dirchanger.rb +22 -0
- data/lib/magicshelf/dirrenamer.rb +30 -0
- data/lib/magicshelf/dirstructureflattener.rb +25 -0
- data/lib/magicshelf/epubgenerator.rb +86 -0
- data/lib/magicshelf/exception.rb +4 -0
- data/lib/magicshelf/executionpipe.rb +105 -0
- data/lib/magicshelf/filecleaner.rb +19 -0
- data/lib/magicshelf/fileextractor.rb +48 -0
- data/lib/magicshelf/filemover.rb +24 -0
- data/lib/magicshelf/filenamevalidator.rb +31 -0
- data/lib/magicshelf/fileserver.rb +103 -0
- data/lib/magicshelf/kindlegenwrapper.rb +33 -0
- data/lib/magicshelf/kindlestrip.rb +250 -0
- data/lib/magicshelf/kindlestripper.rb +25 -0
- data/lib/magicshelf/makeitvertical.rb +41 -0
- data/lib/magicshelf/mobitask.rb +58 -0
- data/lib/magicshelf/tempdiropener.rb +18 -0
- data/lib/magicshelf/version.rb +3 -0
- data/magicshelf.gemspec +37 -0
- data/public/css/index.css +29 -0
- data/public/css/pure-min.css +11 -0
- data/server_config.yml.sample +2 -0
- data/views/generate_mobi.erb +62 -0
- data/views/index.erb +51 -0
- metadata +290 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'magicshelf/exception'
|
2
|
+
|
3
|
+
module MagicShelf
|
4
|
+
class FileCleanerError < Error; end
|
5
|
+
|
6
|
+
class FileCleaner
|
7
|
+
attr_accessor :file
|
8
|
+
|
9
|
+
def enter()
|
10
|
+
#raise MagicShelf::FileCleanerError.new("workdir is not set") if @workdir == nil
|
11
|
+
yield
|
12
|
+
end
|
13
|
+
|
14
|
+
def process()
|
15
|
+
FileUtils.remove_file(@file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'filemagic'
|
2
|
+
require 'zip'
|
3
|
+
require 'open3'
|
4
|
+
require 'shellwords'
|
5
|
+
require 'magicshelf/exception'
|
6
|
+
|
7
|
+
module MagicShelf
|
8
|
+
class FileExtractorError < Error; end
|
9
|
+
|
10
|
+
class FileExtractor
|
11
|
+
attr_accessor :inputfile, :destdir
|
12
|
+
|
13
|
+
def enter()
|
14
|
+
raise MagicShelf::FileExtractorError.new("inputfile is not set") if @inputfile == nil
|
15
|
+
mimetype = FileMagic.new(FileMagic::MAGIC_MIME_TYPE).file(@inputfile)
|
16
|
+
raise MagicShelf::FileExtractorError.new("unsupported filetype: #{mimetype}") if not %w{application/x-rar application/zip}.include?(mimetype)
|
17
|
+
@mimetype = mimetype
|
18
|
+
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
|
22
|
+
def process()
|
23
|
+
case @mimetype
|
24
|
+
when "application/zip"
|
25
|
+
Zip::File.open(@inputfile) do |zip_file|
|
26
|
+
zip_file.each do |entry|
|
27
|
+
MagicShelf.logger.info("Extracting #{entry.name} ...")
|
28
|
+
entry.extract(File.join(@destdir,entry.name))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
when "application/x-rar"
|
32
|
+
out, err, status = Open3.capture3("which unrar")
|
33
|
+
if status.exitstatus != 0
|
34
|
+
raise MagicShelf::FileExtractorError.new("cannot execute unrar, is it on your PATH?")
|
35
|
+
end
|
36
|
+
|
37
|
+
out, err, status = Open3.capture3("unrar x #{Shellwords.escape(@inputfile)} #{Shellwords.escape(@destdir)}")
|
38
|
+
if status.exitstatus != 0
|
39
|
+
raise MagicShelf::FileExtractorError.new("unrar exits with status #{status.exitstatus}: \n #{out} \n #{err}")
|
40
|
+
end
|
41
|
+
else
|
42
|
+
raise MagicShelf::FileExtractorError.new("no way to extract file for the file with filetype: #{@mimetype}")
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'magicshelf/exception'
|
2
|
+
require 'gepub'
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
module MagicShelf
|
6
|
+
class FileMoverError < Error; end
|
7
|
+
|
8
|
+
class FileMover
|
9
|
+
attr_accessor :inputfile, :outputfile
|
10
|
+
|
11
|
+
def enter()
|
12
|
+
MagicShelf.logger.debug('enter FileMover')
|
13
|
+
raise MagicShelf::FileMoverError.new("inputfile is not set") if @inputfile == nil
|
14
|
+
raise MagicShelf::FileMoverError.new("outputfile is not set") if @outputfile == nil
|
15
|
+
|
16
|
+
yield
|
17
|
+
end
|
18
|
+
|
19
|
+
def process()
|
20
|
+
FileUtils.mv(@inputfile, @outputfile)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'magicshelf/exception'
|
2
|
+
|
3
|
+
module MagicShelf
|
4
|
+
class FileNameValidatorError < Error; end
|
5
|
+
|
6
|
+
class FileNameValidator
|
7
|
+
attr_accessor :workdir
|
8
|
+
def initialize()
|
9
|
+
@erase_original = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def enter()
|
13
|
+
yield
|
14
|
+
end
|
15
|
+
|
16
|
+
def process()
|
17
|
+
@workdir ||= Dir.pwd
|
18
|
+
Dir.glob(File.join(@workdir,'**/*')).select{|f|File.file?(f)}.each do |f|
|
19
|
+
dirname = File.dirname(f)
|
20
|
+
basename = File.basename(f,'.*')
|
21
|
+
extname = File.extname(f)
|
22
|
+
newbasename = basename.gsub(/#/, '_').gsub(/\+/, '_')
|
23
|
+
if not (basename == newbasename)
|
24
|
+
newfilename = File.join(dirname, newbasename + extname)
|
25
|
+
FileUtils.mv(f, newfilename)
|
26
|
+
MagicShelf.logger.info("move #{f} to #{newfilename}.")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/config_file'
|
3
|
+
require 'sinatra/reloader'
|
4
|
+
require 'tilt/erb'
|
5
|
+
require 'redis'
|
6
|
+
require 'magicshelf/mobitask'
|
7
|
+
|
8
|
+
module MagicShelf
|
9
|
+
class FileServer < Sinatra::Application
|
10
|
+
register Sinatra::ConfigFile
|
11
|
+
configure :development do
|
12
|
+
register Sinatra::Reloader
|
13
|
+
end
|
14
|
+
|
15
|
+
set :root, File.join(File.dirname(__FILE__), '../..') #set :views, File.join(File.dirname(__FILE__), '../..', 'views')
|
16
|
+
set :bind, '0.0.0.0'
|
17
|
+
config_file 'server_config.yml'
|
18
|
+
|
19
|
+
get '/' do
|
20
|
+
redirect "/files/", 303
|
21
|
+
end
|
22
|
+
|
23
|
+
get '/files/*' do |path|
|
24
|
+
sort_type = params['sort_by'] || "title"
|
25
|
+
files = []
|
26
|
+
Dir.chdir(settings.library_directory) {
|
27
|
+
files = Dir.glob(File.join('.',path, '*'))
|
28
|
+
case sort_type
|
29
|
+
when 'title'
|
30
|
+
files = files.sort
|
31
|
+
when 'title_reverse'
|
32
|
+
files = files.sort.reverse
|
33
|
+
when 'date'
|
34
|
+
files = files.sort_by{ |f| File.mtime(f) }
|
35
|
+
when 'date_reverse'
|
36
|
+
files = files.sort_by{ |f| File.mtime(f) }.reverse
|
37
|
+
else
|
38
|
+
end
|
39
|
+
files_withmtime = files.map do |f|
|
40
|
+
fname = (f.start_with?('./') ? f[2..-1] : f)
|
41
|
+
[fname, File.mtime(f).strftime("%Y/%m/%d %H:%M:%S")]
|
42
|
+
end
|
43
|
+
upperpath = nil
|
44
|
+
upperpath = path.split('/')[0...-1].join('/') if path != ""
|
45
|
+
erb :index, :locals => {:page_title => settings.page_title, :files_withmtime => files_withmtime, :path => path, :upperpath => upperpath, :sort_type => sort_type}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
get '/get_file/*' do |path|
|
50
|
+
pass unless path # pass to a subsequent route
|
51
|
+
send_file(File.join(settings.library_directory, path))
|
52
|
+
end
|
53
|
+
|
54
|
+
get '/get_file*' do
|
55
|
+
'you come to this page without specifying the path to file. go back to the previous page!'
|
56
|
+
end
|
57
|
+
|
58
|
+
get '/generate_mobi/*' do |path|
|
59
|
+
#path = params['splat'].first
|
60
|
+
pass unless path # pass to a subsequent route
|
61
|
+
|
62
|
+
erb :generate_mobi, :locals => {:page_title => settings.page_title, :library_directory => settings.library_directory, :path => path}
|
63
|
+
end
|
64
|
+
|
65
|
+
post '/generate_mobi/*' do |path|
|
66
|
+
title = params['title']
|
67
|
+
author = params['author']
|
68
|
+
booktype = params['booktype']
|
69
|
+
outputfile = params['outputfile']
|
70
|
+
outputfile = title + ".mobi" if outputfile.empty?
|
71
|
+
|
72
|
+
taskparams = {}
|
73
|
+
#taskparams.update(params)
|
74
|
+
taskparams['title'] = params['title']
|
75
|
+
taskparams['author'] = params['author']
|
76
|
+
taskparams['booktype'] = params['booktype']
|
77
|
+
taskparams['inputfile'] = File.join(settings.library_directory,path)
|
78
|
+
taskparams['outputfile'] = File.join(settings.library_directory,File.dirname(path),outputfile)
|
79
|
+
|
80
|
+
Resque.enqueue(MobiTask, taskparams)
|
81
|
+
|
82
|
+
<<-EOF
|
83
|
+
<!DOCTYPE html>
|
84
|
+
<html lang="en">
|
85
|
+
<head>
|
86
|
+
<meta charset="UTF-8">
|
87
|
+
<title></title>
|
88
|
+
</head>
|
89
|
+
<body>
|
90
|
+
Now generating mobi file. wait for a while. <br><a href="/">Back to Top</a>
|
91
|
+
</body>
|
92
|
+
</html>
|
93
|
+
EOF
|
94
|
+
end
|
95
|
+
|
96
|
+
get '/generate_mobi*' do |f|
|
97
|
+
'you come to this page without specifying the path to file. go back to the previous page!'
|
98
|
+
end
|
99
|
+
|
100
|
+
run! if app_file == $0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'magicshelf/exception'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module MagicShelf
|
5
|
+
class KindleGenWrapperError < Error; end
|
6
|
+
|
7
|
+
class KindleGenWrapper
|
8
|
+
attr_accessor :inputfile, :outputfile
|
9
|
+
|
10
|
+
def enter()
|
11
|
+
MagicShelf.logger.debug('enter KindleGenWrapper')
|
12
|
+
|
13
|
+
# check parameters
|
14
|
+
raise MagicShelf::KindleGenWrapperError.new("inputfile is not set") if @inputfile == nil
|
15
|
+
raise MagicShelf::KindleGenWrapperError.new("outputfile is not set") if @outputfile == nil
|
16
|
+
out, err, status = Open3.capture3("which kindlegen")
|
17
|
+
if status.exitstatus != 0
|
18
|
+
raise MagicShelf::KindleGenWrapperError.new("cannot execute kindlegen, is it on your PATH?")
|
19
|
+
end
|
20
|
+
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
|
24
|
+
def process()
|
25
|
+
out, err, status = Open3.capture3("kindlegen #{@inputfile} -o #{@outputfile}")
|
26
|
+
if status.exitstatus != 0
|
27
|
+
raise MagicShelf::KindleGenWrapperError.new("kindlegen exited with #{status.exitstatus}: \n" + out + "\n" + err)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,250 @@
|
|
1
|
+
#! ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
#
|
4
|
+
# It was translated into Ruby script by whiteleaf.
|
5
|
+
# ( https://github.com/whiteleaf7/narou/blob/master/lib/kindlestrip.rb )
|
6
|
+
#
|
7
|
+
# original source code:
|
8
|
+
# kindlestrip.py v.1.35 http://www.mobileread.com/forums/showthread.php?t=96903
|
9
|
+
#
|
10
|
+
# This script strips the penultimate record from a Mobipocket file.
|
11
|
+
# This is useful because the current KindleGen add a compressed copy
|
12
|
+
# of the source files used in this record, making the ebook produced
|
13
|
+
# about twice as big as it needs to be.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# This is free and unencumbered software released into the public domain.
|
17
|
+
#
|
18
|
+
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
19
|
+
# distribute this software, either in source code form or as a compiled
|
20
|
+
# binary, for any purpose, commercial or non-commercial, and by any
|
21
|
+
# means.
|
22
|
+
#
|
23
|
+
# In jurisdictions that recognize copyright laws, the author or authors
|
24
|
+
# of this software dedicate any and all copyright interest in the
|
25
|
+
# software to the public domain. We make this dedication for the benefit
|
26
|
+
# of the public at large and to the detriment of our heirs and
|
27
|
+
# successors. We intend this dedication to be an overt act of
|
28
|
+
# relinquishment in perpetuity of all present and future rights to this
|
29
|
+
# software under copyright law.
|
30
|
+
#
|
31
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
32
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
33
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
34
|
+
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
35
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
36
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
37
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
38
|
+
#
|
39
|
+
# For more information, please refer to <http://unlicense.org/>
|
40
|
+
#
|
41
|
+
# Written by Paul Durrant, 2010-2011, paul@durrant.co.uk, pdurrant on mobileread.com
|
42
|
+
# With enhancements by Kevin Hendricks, KevinH on mobileread.com
|
43
|
+
#
|
44
|
+
# Changelog
|
45
|
+
# 1.00 - Initial version
|
46
|
+
# 1.10 - Added an option to output the stripped data
|
47
|
+
# 1.20 - Added check for source files section (thanks Piquan)
|
48
|
+
# 1.30 - Added prelim Support for K8 style mobis
|
49
|
+
# 1.31 - removed the SRCS section but kept a 0 size entry for it
|
50
|
+
# 1.32 - removes the SRCS section and its entry, now updates metadata 121 if needed
|
51
|
+
# 1.33 - now uses and modifies mobiheader SRCS and CNT
|
52
|
+
# 1.34 - added credit for Kevin Hendricks
|
53
|
+
# 1.35 - fixed bug when more than one compilation (SRCS/CMET) records
|
54
|
+
|
55
|
+
KINDLESTRIP_VERSION = '1.35'
|
56
|
+
|
57
|
+
class StripException < StandardError; end
|
58
|
+
|
59
|
+
class SectionStripper
|
60
|
+
def load_section(section)
|
61
|
+
if section + 1 == @num_sections
|
62
|
+
endoff = @data_file.length
|
63
|
+
else
|
64
|
+
endoff = @sections[section + 1][0]
|
65
|
+
end
|
66
|
+
off = @sections[section][0]
|
67
|
+
@data_file[off...endoff]
|
68
|
+
end
|
69
|
+
|
70
|
+
def patch(off, _new)
|
71
|
+
@data_file = @data_file[0, off] + new + @data_file[off + _new.length .. -1]
|
72
|
+
end
|
73
|
+
|
74
|
+
def strip(off, len)
|
75
|
+
@data_file = @data_file[0, off] + @data_file[off + len .. -1]
|
76
|
+
end
|
77
|
+
|
78
|
+
def patch_section(section, _new, in_off = 0)
|
79
|
+
if section + 1 == @num_sections
|
80
|
+
endoff = @data_file.length
|
81
|
+
else
|
82
|
+
endoff = @sections[section + 1][0]
|
83
|
+
end
|
84
|
+
raise unless off + in_off + _new.length <= endoff
|
85
|
+
patch(off + in_off, _new)
|
86
|
+
end
|
87
|
+
|
88
|
+
def updateEXTH121(srcs_secnum, srcs_cnt, mobiheader)
|
89
|
+
mobi_length, = mobiheader[0x14...0x18].unpack("N")
|
90
|
+
exth_flag, = mobiheader[0x80...0x84].unpack("N")
|
91
|
+
exth = "NONE"
|
92
|
+
begin
|
93
|
+
if exth_flag & 0x40 != 0
|
94
|
+
exth = mobiheader[16 + mobi_length .. -1]
|
95
|
+
if exth.length >= 4 && exth[0, 4] == "EXTH"
|
96
|
+
nitems, = exth[8...12].unpack("N")
|
97
|
+
pos = 12
|
98
|
+
nitems.times do
|
99
|
+
type, size = exth[pos ... pos + 8].unpack("NN")
|
100
|
+
#puts "#{type}, #{size}"
|
101
|
+
if type == 121
|
102
|
+
boundaryptr, = exth[pos + 8 ... pos + size].unpack("N")
|
103
|
+
if srcs_secnum <= boundaryptr
|
104
|
+
boundaryptr -= srcs_cnt
|
105
|
+
prefix = mobiheader[0, 16 + mobi_length + pos + 8]
|
106
|
+
suffix = mobiheader[16 + mobi_length + pos + 8 + 4 .. -1]
|
107
|
+
nval = [boundaryptr].pack("N")
|
108
|
+
mobiheader = prefix + nval + suffix
|
109
|
+
end
|
110
|
+
end
|
111
|
+
pos += size
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
rescue
|
116
|
+
end
|
117
|
+
mobiheader
|
118
|
+
end
|
119
|
+
|
120
|
+
def initialize(datain, verbose = true)
|
121
|
+
@verbose = verbose
|
122
|
+
if datain[0x3C...0x3C+8] != "BOOKMOBI"
|
123
|
+
raise StripException, "invalid file format"
|
124
|
+
end
|
125
|
+
@num_sections, = datain[76...78].unpack("n")
|
126
|
+
|
127
|
+
# get mobiheader and check SRCS section number and count
|
128
|
+
offset0, = datain.unpack("@78N")
|
129
|
+
offset1, = datain.unpack("@86N")
|
130
|
+
mobiheader = datain[offset0 ... offset1]
|
131
|
+
srcs_secnum, srcs_cnt = mobiheader.unpack("@224NN")
|
132
|
+
if srcs_secnum == 0xffffffff || srcs_cnt == 0
|
133
|
+
raise StripException, "File doesn't contain the sources section."
|
134
|
+
end
|
135
|
+
|
136
|
+
puts "Found SRCS section number %d, and count %d" % [srcs_secnum, srcs_cnt] if @verbose
|
137
|
+
# find its offset and length
|
138
|
+
_next = srcs_secnum + srcs_cnt
|
139
|
+
srcs_offset, = datain.unpack("@#{78+srcs_secnum*8}NN")
|
140
|
+
next_offset, = datain.unpack("@#{78+_next*8}NN")
|
141
|
+
srcs_length = next_offset - srcs_offset
|
142
|
+
if datain[srcs_offset ... srcs_offset+4] != "SRCS"
|
143
|
+
raise StripException, "SRCS section num does not point to SRCS."
|
144
|
+
end
|
145
|
+
puts " beginning at offset %0x and ending at offset %0x" % [srcs_offset, srcs_length] if @verbose
|
146
|
+
|
147
|
+
# it appears bytes 68-71 always contain (2*num_sections) + 1
|
148
|
+
# this is not documented anyplace at all but it appears to be some sort of next
|
149
|
+
# available unique_id used to identify specific sections in the palm db
|
150
|
+
@data_file = datain[0, 68] + [(@num_sections - srcs_cnt) * 2 + 1].pack("N")
|
151
|
+
@data_file += datain[72...76]
|
152
|
+
|
153
|
+
# write out the number of sections reduced by srtcs_cnt
|
154
|
+
@data_file = @data_file + [@num_sections - srcs_cnt].pack("n")
|
155
|
+
|
156
|
+
# we are going to remove srcs_cnt SRCS sections so the offset of every entry in the table
|
157
|
+
# up to the srcs secnum must begin 8 bytes earlier per section removed (each table entry is 8 )
|
158
|
+
delta = -8 * srcs_cnt
|
159
|
+
srcs_secnum.times do |i|
|
160
|
+
offset, flgval = datain.unpack("@#{78+i*8}NN")
|
161
|
+
offset += delta
|
162
|
+
@data_file += [offset].pack("N") + [flgval].pack("N")
|
163
|
+
end
|
164
|
+
|
165
|
+
# for every record after the srcs_cnt SRCS records we must start it
|
166
|
+
# earlier by 8*srcs_cnt + the length of the srcs sections themselves)
|
167
|
+
delta = delta - srcs_length
|
168
|
+
(srcs_secnum + srcs_cnt ... @num_sections).each do |i|
|
169
|
+
offset, = datain.unpack("@#{78+i*8}NN")
|
170
|
+
offset += delta
|
171
|
+
flgval = 2 * (i - srcs_cnt)
|
172
|
+
@data_file += [offset].pack("N") + [flgval].pack("N")
|
173
|
+
end
|
174
|
+
|
175
|
+
# now pad it out to begin right at the first offset
|
176
|
+
# typically this is 2 bytes of nulls
|
177
|
+
first_offset, = @data_file.unpack("@78NN")
|
178
|
+
@data_file += "\0" * (first_offset - @data_file.length)
|
179
|
+
|
180
|
+
# now finally add on every thing up to the original src_offset
|
181
|
+
@data_file += datain[offset0...srcs_offset]
|
182
|
+
|
183
|
+
# and everything afterwards
|
184
|
+
@data_file += datain[srcs_offset + srcs_length .. -1]
|
185
|
+
|
186
|
+
#store away the SRCS section in case the user wants it output
|
187
|
+
@stripped_data_header = datain[srcs_offset ... srcs_offset + 16]
|
188
|
+
@stripped_data = datain[srcs_offset + 16 ... srcs_offset + srcs_length]
|
189
|
+
|
190
|
+
# update the number of sections count
|
191
|
+
@num_section = @num_sections - srcs_cnt
|
192
|
+
|
193
|
+
# update the srcs_secnum and srcs_cnt in the mobiheader
|
194
|
+
offset0, = @data_file.unpack("@78NN")
|
195
|
+
offset1, = @data_file.unpack("@86NN")
|
196
|
+
mobiheader = @data_file[offset0 ... offset1]
|
197
|
+
mobiheader = mobiheader[0, 0xe0] + [-1].pack("N") + [0].pack("N") + mobiheader[0xe8 .. -1]
|
198
|
+
|
199
|
+
# if K8 mobi, handle metadata 121 in old mobiheader
|
200
|
+
mobiheader = updateEXTH121(srcs_secnum, srcs_cnt, mobiheader)
|
201
|
+
@data_file = @data_file[0, offset0] + mobiheader + @data_file[offset1 .. -1]
|
202
|
+
puts "done" if @verbose
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_result
|
206
|
+
@data_file
|
207
|
+
end
|
208
|
+
|
209
|
+
def get_stripped_data
|
210
|
+
@stripped_data
|
211
|
+
end
|
212
|
+
|
213
|
+
def get_header
|
214
|
+
@stripped_data_header
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.strip(infile, outfile = nil, verbose = true)
|
218
|
+
outfile = infile unless outfile
|
219
|
+
data_file = File.binread(infile)
|
220
|
+
stripped_file = new(data_file, verbose)
|
221
|
+
File.binwrite(outfile, stripped_file.get_result)
|
222
|
+
stripped_file
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
if __FILE__ == $0
|
227
|
+
puts "KndleStrip v#{KINDLESTRIP_VERSION}. " +
|
228
|
+
"Written 2010-2012 by Paul Durrant and Kevin Hendricks."
|
229
|
+
if ARGV.length < 2 || ARGV.length > 3
|
230
|
+
puts "Strips the Sources record from Mobipocket ebooks"
|
231
|
+
puts "For ebooks generated using KindleGen 1.1 and later that add the source"
|
232
|
+
puts "Usage:"
|
233
|
+
puts " %s <infile> <outfile> <strippeddatafile>" % File.basename(__FILE__)
|
234
|
+
puts "<strippeddatafile> is optional."
|
235
|
+
exit Narou::EXIT_ERROR_CODE
|
236
|
+
else
|
237
|
+
infile = ARGV[0]
|
238
|
+
outfile = ARGV[1]
|
239
|
+
begin
|
240
|
+
stripped_file = SectionStripper.strip(infile, outfile)
|
241
|
+
#print "Header Bytes: " + binascii.b2a_hex(strippedFile.getHeader())
|
242
|
+
if ARGV.length == 3
|
243
|
+
File.binwrite(ARGV[2], stripped_file.get_stripped_data)
|
244
|
+
end
|
245
|
+
rescue StripException => e
|
246
|
+
warn "Error: #{e.message}"
|
247
|
+
exit Narou::EXIT_ERROR_CODE
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|