├── .gitignore
├── README.md
├── config.json
├── frames
├── content_frame.opf
├── cover_frame.xml
├── cover_meta_frame.xml
├── itemref_frame.xml
├── navmap_frame.xml
├── opf_manifest_frame.xml
├── page_frame.html
└── toc_frame.ncx
├── kindlegen.exe
└── main.py
/.gitignore:
--------------------------------------------------------------------------------
1 | run.bat
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # kc-generator
2 | kindle comic(mobi) generator
3 | 適用於將特定格式目錄下的圖片,自動編成一本kindle mobi格式的漫畫,可以省力、也不會在編排過程中被劇透
4 |
5 | ## 使用方式
6 |
7 | 執行主程式`main.py`後輸入 _工作根目錄_ 路徑,程式將會根據工作根目錄指定資料夾中的圖片,生成一個mobi檔。
8 | 可以選擇自訂章節排序。
9 |
10 | ## 自訂章節排序方式
11 |
12 | 如果執行中選擇了自訂章節排序,請依照提示去工作目錄下的sort.txt修改排序,完成後存檔,按下enter繼續執行。
13 | sort.txt中只能出現章節目錄,**建議使用剪下貼上來調整順序**,避免出現錯字導致錯誤,**也請勿在sort.txt中添加文字**,或是多餘的換行,避免錯誤。
14 |
15 | ## 工作根目錄結構要求
16 |
17 | _工作根目錄不一定要和此程式擺在一起_
18 |
19 | **工作根目錄必須包含以下結構**
20 |
21 | * 工作根目錄
22 | * **config.json**
23 | * 圖片根目錄
24 | * 章節根目錄
25 | * 圖片...
26 | * 章節根目錄
27 | * 圖片...
28 | * 封面圖檔
29 |
30 | 章節根目錄名稱會影響mobi檔中的目錄(章節名稱)
31 |
32 | ## config.json結構
33 |
34 | 以下皆為必要參數
35 |
36 | * `title`:書名
37 | * `language`:語言
38 | * `creator`:作者
39 | * `direction`:書寫方向(`rl`:點左側翻下一頁,`lr`:點右側翻下一頁)
40 | * `folder`:圖片根目錄位置
41 | * `width`:頁面寬度
42 | * `height`:頁面高度
43 | * `cover`:封面檔案(可選擇留空,但一定要有鍵)
44 |
45 | _*在[此檔案](https://github.com/HSSLC/kc-generator/blob/master/config.json)中有預填好部分內容的JSON_
46 |
47 | ## 注意
48 |
49 | * 預設排序將會優先依照檔名中第一組阿拉伯數字排序,若無,將會被排在連我都不知道的順序,亦可透過手動排序功能調整排序
50 |
51 | * 不同章節間的內容圖片檔名可以重複
52 |
53 | * 在程式執行過程中,會在 _工作根目錄_ 下生成暫存目錄`proj`,會將工作根目錄的大小幾乎翻倍,執行前請先確認磁碟空間夠用
54 |
55 | * 若磁碟空間吃緊,在執行完後可選擇將暫存目錄`proj`刪除
56 |
57 | * 可以藉由修改`frames\content_frame.opf`來修改日後生成的書的資訊模板
58 |
59 | ## 工作根目錄與`config.json`的生成結果範例
60 |
61 |
62 | ### 工作根目錄範例
63 | _名稱後括號為類型方便理解,實際上不存在_
64 | * `C槽`
65 | * `測試之書`(目錄)
66 | * `config.json`(檔案)
67 | * `jpg`(目錄)
68 | * `第一章 快狐`(目錄)
69 | * `image1.jpg`(檔案)
70 | * `image2.jpg`(檔案)
71 | * `image3.jpg`(檔案)
72 | * `第二章 越懶狗`(目錄)
73 | * `image4.jpg`(檔案)
74 | * `image5.jpg`(檔案)
75 | * `cover.jpg`(檔案)
76 |
77 | ### `config.json`範例
78 | `
79 | {
80 | "title":"測試之書",
81 | "language":"zh",
82 | "creator":"HSSLC",
83 | "direction":"rl",
84 | "folder":"jpg",
85 | "width":"800",
86 | "height":"1280",
87 | "cover":"cover.jpg"
88 | }
89 | `
90 | ## 執行過程
91 | 1. 執行`main.py`
92 | 2. 輸入`C:\測試之書`
93 | 3. 程式會在`測試之書`下方生成暫存目錄`proj`
94 | 4. 檔案生成完後程式會呼叫kindlegen.exe生成`測試之書.mobi`,位於`C:\測試之書\測試之書.mobi`
95 |
96 | ## 此範例生成結果
97 | 一本名為`測試之書`的書,翻書方向為點擊左側翻下一頁,位於`C:\測試之書\測試之書.mobi`,作者為`HSSLC`,有兩章,分別叫做`第一章 快狐`與`第二章 越懶狗`,第一章中有三頁,分別為`image1.jpg`、`image2.jpg`、`image3.jpg`,第二章中有兩頁,分別為`image4.jpg`、`image5.jpg`,此書封面為`cover.jpg`
98 |
99 | ## 絕對是個巧合
100 |
101 | [manhuagui-dlr](https://github.com/HSSLC/manhuagui-dlr) 做出來的目錄結構剛好符合這個程式
102 |
103 | ## 更多資訊
104 | https://incognitas.net/works/kc-generator-1/
105 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {"title":"在這輸入標題", "language":"zh", "creator":"在這輸入作者", "direction":"rl", "folder":"jpg", "width":"800", "height":"1280", "cover":"cover.jpg"}
--------------------------------------------------------------------------------
/frames/content_frame.opf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %s
9 | %s
10 | %s
11 |
12 |
13 |
14 |
15 |
16 | %s
17 |
18 |
19 |
20 | %s
21 | %s
22 |
23 |
24 | %s
25 |
26 |
27 |
--------------------------------------------------------------------------------
/frames/cover_frame.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frames/cover_meta_frame.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frames/itemref_frame.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frames/navmap_frame.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | %s
4 |
5 |
6 |
--------------------------------------------------------------------------------
/frames/opf_manifest_frame.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frames/page_frame.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %s
5 |
6 |
7 |
8 |

9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/frames/toc_frame.ncx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | %s
16 |
17 |
18 |
--------------------------------------------------------------------------------
/kindlegen.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HSSLC/kc-generator/792db822f42847cfe6ca35943c065bc3a479e826/kindlegen.exe
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import os, json, re, shutil, hashlib
2 | from PIL import Image
3 | skip_existed = True
4 |
5 | size = lambda d:sum(os.stat(os.path.join(cd, f)).st_size for cd, sd, fs in os.walk(d) for f in fs if os.path.isfile(os.path.join(cd, f)))
6 | #init
7 | os.chdir('frames')
8 | with open('page_frame.html', 'r') as page_frame_file:
9 | page_frame = page_frame_file.read()
10 | #title
11 | #width
12 | #height
13 | #top
14 | #bottom
15 | #src
16 | with open('content_frame.opf', 'r') as opf_frame_file:
17 | opf_frame = opf_frame_file.read()
18 | #uid
19 | #title
20 | #language
21 | #creator
22 | #direction
23 | #width
24 | #height
25 | #cover meta
26 | #cover src
27 | #manifest
28 | #spine
29 | with open('opf_manifest_frame.xml', 'r') as omf_file:
30 | omf_frame = omf_file.read()
31 | #href
32 | #id
33 | with open('itemref_frame.xml', 'r') as if_file:
34 | if_frame = if_file.read()
35 | #id
36 | with open('toc_frame.ncx', 'r') as toc_frame_file:
37 | toc_frame = toc_frame_file.read()
38 | #navmap
39 | with open('navmap_frame.xml', 'r') as navmap_frame_file:
40 | navmap_frame = navmap_frame_file.read()
41 | #playOrder
42 | #id
43 | #name
44 | #src
45 | with open('cover_frame.xml', 'r') as cover_frame_file:
46 | cover_frame = cover_frame_file.read()
47 | #src
48 | with open('cover_meta_frame.xml', 'r') as cover_meta_frame_file:
49 | cover_meta_frame = cover_meta_frame_file.read()
50 | os.chdir('..')
51 | program_dir = os.getcwd()
52 |
53 | def main():
54 | def sort_lambda(n):
55 | try:
56 | return float(re.search('(\d+(\.\d+)?)', n).group(0))
57 | except:
58 | return 0
59 | print('輸入工作目錄:', end='')
60 | os.chdir(input())
61 | try:
62 | with open('config.json', 'r', encoding='UTF-8') as config_json:
63 | conf = json.load(config_json)
64 | except:
65 | print('沒有config.json')
66 | return
67 | main_folder = title = creator = lang = None
68 | try:
69 | main_folder = conf['folder']
70 | title = conf['title']
71 | creator = conf['creator'] if 'creator' in conf else conf['authors']
72 | lang = conf['language']
73 | h = int(conf['height'])
74 | w = int(conf['width'])
75 | cover = conf['cover']
76 | direction = conf['direction']
77 | except:
78 | print('config.json不完整')
79 | return
80 |
81 | book_hash = hashlib.md5(bytes(title + creator, encoding='UTF-8')).hexdigest()
82 | ch_folders = os.listdir(main_folder)
83 | ch_folders.sort(key=sort_lambda)
84 | #自訂章節排序
85 | print('是否要自訂排序:(y/n)')
86 | isusersort = input()
87 | if isusersort == 'y':
88 | with open('sort.txt', 'w', encoding='utf-8') as sortlist:
89 | sortlist.write('\n'.join(ch_folders))
90 | print('請檢查sort.txt並修改排序 請勿加入其他資訊避免錯誤')
91 | print('修改完存檔並按enter繼續')
92 | input()
93 | with open('sort.txt', encoding='utf-8') as sortlist:
94 | ch_folders = sortlist.read().split('\n')
95 | #章節排序
96 | if not os.path.exists('proj') or not os.path.isdir('proj'):
97 | os.mkdir('proj')
98 | if not cover == '':
99 | shutil.copyfile(cover, os.path.join('proj', cover))
100 | #進入proj目錄
101 | os.chdir('proj')
102 | if not os.path.exists('html') or not os.path.isdir('html'):
103 | os.mkdir('html')
104 | os.chdir('html')
105 | if not os.path.exists('img') or not os.path.isdir('img'):
106 | os.mkdir('img')
107 | #返回工作目錄
108 | os.chdir('..')
109 | toc = open('toc.ncx', 'w', encoding='UTF-8')
110 | opf = open('content.opf', 'w', encoding='UTF-8')
111 | work_dir = os.getcwd()
112 | os.chdir('..')
113 | manifest = []
114 | spine = []
115 | navmap = []
116 | os.chdir(main_folder)
117 | page_count = 1
118 | print('正在建立檔案...')
119 | for ch in ch_folders:
120 | #run each chapter
121 | if not os.path.isdir(ch):
122 | continue
123 | print(ch)
124 | page_files = os.listdir(ch)
125 | #頁面排序
126 | page_files.sort(key=sort_lambda)
127 | os.chdir(ch)
128 | navmap.append(navmap_frame % (page_count, page_count, ch, 'html/Page-%s.html' % page_count))
129 | for page in page_files:
130 | #run each page
131 | if not os.path.isfile(page): #or if not file is not image
132 | continue
133 | try:
134 | im = Image.open(page)
135 | except:
136 | print('檔案格式錯誤: %s 已略過' % page)
137 | continue
138 | width, height = im.size
139 | scale = w / width
140 | adjh = int(height * scale)
141 | margin_sky_and_ground = int((h - adjh) / 2)
142 | #copy page image
143 | if not os.path.isfile(os.path.join('..', '..','proj', 'html', 'img', '%s.jpg' % page_count)) and skip_existed:
144 | shutil.copyfile(page, os.path.join('..', '..','proj', 'html', 'img', '%s.jpg' % page_count))
145 | if not os.path.isfile(os.path.join('..', '..','proj', 'html', 'Page-%s.html' % page_count)) and skip_existed:
146 | with open(os.path.join('..', '..','proj', 'html', 'Page-%s.html' % page_count), 'w', encoding='UTF-8') as page_html:
147 | page_html.write(page_frame % (page_count, w, adjh, margin_sky_and_ground, margin_sky_and_ground, '/'.join(['img', '%s.jpg' % page_count])))
148 | manifest.append(omf_frame % ('html/Page-%s.html' % page_count, str(page_count + 2)))
149 | spine.append(if_frame % str(page_count + 2))
150 | page_count += 1
151 | os.chdir('..')
152 | if not cover == '':
153 | cover_xml = cover_frame % cover
154 | cover_meta_xml = cover_meta_frame
155 | else:
156 | cover_xml = ''
157 | cover_meta_xml = ''
158 | opf.write(opf_frame % (book_hash, title, lang, creator, direction, w, h, cover_meta_xml, cover_xml, '\n\t\t'.join(manifest), '\n\t\t'.join(spine)))
159 | opf.close()
160 | toc.write(toc_frame % '\n'.join(navmap))
161 | toc.close()
162 | os.chdir(os.path.join('..', 'proj'))
163 | #呼叫kindlegen
164 | print('呼叫kindlegen...')
165 | output_name = re.sub(r'[\\/:*?"<>|]', '_', title) + '.mobi'
166 | kgres = os.system('""%s" -dont_append_source -verbose -locale en -o "%s" "%s""' % (os.path.join(program_dir, 'kindlegen.exe'), output_name, os.path.join(work_dir, 'content.opf')))
167 | #把輸出檔案向上移一層
168 | src_file = os.path.join('..', output_name)
169 | if os.path.exists(src_file) and os.path.isfile(src_file):
170 | os.remove(src_file)
171 | os.rename(output_name, src_file)
172 | if kgres == 1:
173 | print('kindlegen傳回錯誤 請手動檢查是否有成功生成 按下Enter結束', end='')
174 | input()
175 | main()
176 |
--------------------------------------------------------------------------------