├── .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 | --------------------------------------------------------------------------------