├── epub_md ├── images │ ├── cover.jpg │ └── matplotlib_plot.png ├── chapter4.md ├── css │ ├── general.css │ ├── specific.css │ └── code_styles.css ├── chapter3.md ├── description.json ├── chapter2.md └── chapter1.md ├── README.md └── mark2epub.py /epub_md/images/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexPof/mark2epub/HEAD/epub_md/images/cover.jpg -------------------------------------------------------------------------------- /epub_md/chapter4.md: -------------------------------------------------------------------------------- 1 | # Chapter 4 2 | 3 | This is a chapter on which a specific .css file 4 | has been applied. -------------------------------------------------------------------------------- /epub_md/images/matplotlib_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexPof/mark2epub/HEAD/epub_md/images/matplotlib_plot.png -------------------------------------------------------------------------------- /epub_md/css/general.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 0.8em; 3 | } 4 | 5 | 6 | img { 7 | max-width: 100%; 8 | height: auto; 9 | } 10 | 11 | 12 | table { 13 | border-collapse: collapse; 14 | width: 100%; 15 | font-size: 0.5em; 16 | } 17 | 18 | td,th { 19 | border: 1px solid #ddd; 20 | padding: 8px; 21 | } 22 | 23 | tr:nth-child(even){background-color: #f2f2f2;} 24 | 25 | th { 26 | padding-top: 12px; 27 | padding-bottom: 12px; 28 | text-align: left; 29 | background-color: #444444; 30 | color: white; 31 | font-size: 0.8em; 32 | } -------------------------------------------------------------------------------- /epub_md/css/specific.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 5em; 3 | } 4 | 5 | 6 | img { 7 | max-width: 100%; 8 | height: auto; 9 | } 10 | 11 | 12 | table { 13 | border-collapse: collapse; 14 | width: 100%; 15 | font-size: 0.5em; 16 | } 17 | 18 | td,th { 19 | border: 1px solid #ddd; 20 | padding: 8px; 21 | } 22 | 23 | tr:nth-child(even){background-color: #f2f2f2;} 24 | 25 | th { 26 | padding-top: 12px; 27 | padding-bottom: 12px; 28 | text-align: left; 29 | background-color: #444444; 30 | color: white; 31 | font-size: 0.8em; 32 | } -------------------------------------------------------------------------------- /epub_md/chapter3.md: -------------------------------------------------------------------------------- 1 | ## Chapter 3 2 | 3 | It is possible to include images. 4 | You can either include html directly: 5 | 6 | ``` 7 | 8 | ``` 9 | 10 | which will produce the following image, 11 | 12 | 13 | 14 | or use the Markdown syntax 15 | 16 | ``` 17 | ![Some image](images/matplotlib_plot.png) 18 | ``` 19 | 20 | which will produce the following image, 21 | 22 | ![Some image](images/matplotlib_plot.png) 23 | 24 | The provided CSS file ensures that both images are resized to 100% width. 25 | If different CSS styles are used, the Markdown syntax and the direct html syntax may produce different results. 26 | -------------------------------------------------------------------------------- /epub_md/description.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata":{ 3 | "dc:title":"Mark2Epub Sample", 4 | "dc:creator":"Mark2Epub", 5 | "dc:language":"en-US", 6 | "dc:identifier":"mark2epub-sample", 7 | "dc:source":"", 8 | "meta":"", 9 | "dc:date":"2023-01-01", 10 | "dc:publisher":"", 11 | "dc:contributor":"", 12 | "dc:rights":"", 13 | "dc:description":"", 14 | "dc:subject":"" 15 | }, 16 | "cover_image":"cover.jpg", 17 | "default_css":["code_styles.css","general.css"], 18 | "chapters":[ 19 | {"markdown":"chapter1.md","css":""}, 20 | {"markdown":"chapter2.md","css":""}, 21 | {"markdown":"chapter3.md","css":""}, 22 | {"markdown":"chapter4.md","css":"specific.css"} 23 | ] 24 | } -------------------------------------------------------------------------------- /epub_md/chapter2.md: -------------------------------------------------------------------------------- 1 | # Chapter 2 2 | 3 | Tables can be defined in Markdown as well. 4 | 5 | For example this syntax 6 | 7 | ``` 8 | | A | Table | Example | 9 | | ------------- |:-------------:| -----:| 10 | | Row 1 | A | 0.5 | 11 | | Row 2 | B | -0.6 | 12 | | Row3 | C | 0.9 | 13 | ``` 14 | 15 | will produce the following table: 16 | 17 | | A | Table | Example | 18 | | ------------- |:-------------:| -----:| 19 | | Row 1 | A | 0.5 | 20 | | Row 2 | B | -0.6 | 21 | | Row3 | C | 0.9 | 22 | 23 | 24 | With the included CSS files, you can also format code. For example, this syntax 25 | 26 | ``` 27 | :::python 28 | def myfunction(x): 29 | return x**2 30 | ``` 31 | 32 | will produce the following result 33 | 34 | :::python 35 | def myfunction(x): 36 | return x**2 37 | -------------------------------------------------------------------------------- /epub_md/chapter1.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 2 | 3 | This is some simple text in Markdown. 4 | 5 | ## Section 1 6 | 7 | Denique Antiochensis **ordinis vertices sub uno elogio iussit occidi** ideo efferatus, 8 | quod ei celebrari vilitatem intempestivam urgenti, cum inpenderet inopia, 9 | gravius rationabili responderunt; et perissent ad unum ni comes orientis tunc 10 | Honoratus fixa constantia restitisset. 11 | 12 | ## Section 2 13 | 14 | Et interdum acciderat, *ut siquid in penetrali secreto nullo citerioris vitae* 15 | ministro praesente paterfamilias uxori susurrasset in aurem, velut Amphiarao 16 | referente aut Marcio, quondam vatibus inclitis, postridie disceret imperator. 17 | ideoque etiam parietes arcanorum soli conscii timebantur. 18 | 19 | ## Section 3 20 | 21 | Saepissime igitur mihi de amicitia cogitanti maxime illud considerandum 22 | videri solet, utrum propter imbecillitatem atque inopiam desiderata sit amicitia, 23 | ut dandis recipiendisque meritis 24 | 25 | * quod quisque minus per se ipse posset, 26 | * id acciperet 27 | * ab alio vicissimque redderet, 28 | * an esset hoc quidem proprium amicitiae, 29 | *sed antiquior et 30 | * pulchrior et magis a natura ipsa profecta alia causa. 31 | 32 | ### Subsection 33 | 34 | Amor enim, ex quo amicitia nominata est, 35 | princeps est ad benevolentiam coniungendam. 36 | 37 | 1. Nam utilitates quidem etiam ab iis percipiuntur saepe 38 | 1. qui simulatione amicitiae coluntur 39 | 2. et observantur temporis causa, 40 | 2. in amicitia autem nihil fictum est, nihil simulatum et, quidquid est, id est verum et voluntarium. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mark2epub 2 | 3 | mark2epub is a simple Python script for converting Markdown files, images, and 4 | css files to a single ePub book. 5 | 6 | ## Installation and use 7 | 8 | ### Dependencies 9 | 10 | mark2epub requires: 11 | 12 | - Python (>= 3.4) 13 | - markdown (>= 3.1) 14 | 15 | ### Running mark2epub 16 | 17 | The syntax for mark2epub is the following: 18 | 19 | $ python md2epub.py 20 | 21 | The directory `epub_md` is a sample markdown directory for mark2epub. 22 | 23 | Note that the directory `markdown_directory` **must** contain 24 | 25 | * Markdown `.md` files. Each file represent a chapter in the resulting ePub. 26 | They are processed by name order, and will appear correspondingly in the e-book. 27 | 28 | * An `images` folder, containing the images to be included. Only GIF (`.gif` 29 | extension), JPEG (`.jpg` or `.jpeg` extensions), and PNG (`.png` extension) 30 | files are currently supported. This folder is *not* processed recursively, so 31 | all images should be placed at the root of this folder. 32 | 33 | * A `css` folder, containing the CSS files. This folder is *not* processed 34 | recursively, so all css files should be placed at the root of this folder. 35 | 36 | * A `description.json` containing meta-information about the e-book. The key 37 | `cover_image` should indicate the name of the cover image. 38 | The key `default_css` is a list of css file names that are applied by default 39 | on all chapters. 40 | The key `chapters` is a list of dictionaries, each one containing a key `markdown` 41 | indicating the name of the corresponding markdown file, and a key `css` indicating 42 | the name of the css file that should be applied specifically to this chapter. 43 | See the example in the repository for a typical `description.json` file. 44 | 45 | ## Limitations/Features to be addressed 46 | 47 | * Robustness checks in the `mark2epub.py` script 48 | * Recursive processing of the `images` and `css` folders 49 | * Support for additional fonts 50 | * Support for mathematical notation 51 | -------------------------------------------------------------------------------- /epub_md/css/code_styles.css: -------------------------------------------------------------------------------- 1 | .codehilite { 2 | font-size: 0.7em; 3 | } 4 | .codehilite .hll { background-color: #ffffcc } 5 | .codehilite { background: #f8f8f8; } 6 | .codehilite .c { color: #408080; font-style: italic } /* Comment */ 7 | .codehilite .err { border: 1px solid #FF0000 } /* Error */ 8 | .codehilite .k { color: #008000; font-weight: bold } /* Keyword */ 9 | .codehilite .o { color: #666666 } /* Operator */ 10 | .codehilite .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ 11 | .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 12 | .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */ 13 | .codehilite .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ 14 | .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */ 15 | .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */ 16 | .codehilite .gd { color: #A00000 } /* Generic.Deleted */ 17 | .codehilite .ge { font-style: italic } /* Generic.Emph */ 18 | .codehilite .gr { color: #FF0000 } /* Generic.Error */ 19 | .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 20 | .codehilite .gi { color: #00A000 } /* Generic.Inserted */ 21 | .codehilite .go { color: #888888 } /* Generic.Output */ 22 | .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 23 | .codehilite .gs { font-weight: bold } /* Generic.Strong */ 24 | .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 25 | .codehilite .gt { color: #0044DD } /* Generic.Traceback */ 26 | .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 27 | .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 28 | .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 29 | .codehilite .kp { color: #008000 } /* Keyword.Pseudo */ 30 | .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 31 | .codehilite .kt { color: #B00040 } /* Keyword.Type */ 32 | .codehilite .m { color: #666666 } /* Literal.Number */ 33 | .codehilite .s { color: #BA2121 } /* Literal.String */ 34 | .codehilite .na { color: #7D9029 } /* Name.Attribute */ 35 | .codehilite .nb { color: #008000 } /* Name.Builtin */ 36 | .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 37 | .codehilite .no { color: #880000 } /* Name.Constant */ 38 | .codehilite .nd { color: #AA22FF } /* Name.Decorator */ 39 | .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */ 40 | .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 41 | .codehilite .nf { color: #0000FF } /* Name.Function */ 42 | .codehilite .nl { color: #A0A000 } /* Name.Label */ 43 | .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 44 | .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */ 45 | .codehilite .nv { color: #19177C } /* Name.Variable */ 46 | .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 47 | .codehilite .w { color: #bbbbbb } /* Text.Whitespace */ 48 | .codehilite .mb { color: #666666 } /* Literal.Number.Bin */ 49 | .codehilite .mf { color: #666666 } /* Literal.Number.Float */ 50 | .codehilite .mh { color: #666666 } /* Literal.Number.Hex */ 51 | .codehilite .mi { color: #666666 } /* Literal.Number.Integer */ 52 | .codehilite .mo { color: #666666 } /* Literal.Number.Oct */ 53 | .codehilite .sa { color: #BA2121 } /* Literal.String.Affix */ 54 | .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */ 55 | .codehilite .sc { color: #BA2121 } /* Literal.String.Char */ 56 | .codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */ 57 | .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 58 | .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */ 59 | .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 60 | .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */ 61 | .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 62 | .codehilite .sx { color: #008000 } /* Literal.String.Other */ 63 | .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */ 64 | .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */ 65 | .codehilite .ss { color: #19177C } /* Literal.String.Symbol */ 66 | .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */ 67 | .codehilite .fm { color: #0000FF } /* Name.Function.Magic */ 68 | .codehilite .vc { color: #19177C } /* Name.Variable.Class */ 69 | .codehilite .vg { color: #19177C } /* Name.Variable.Global */ 70 | .codehilite .vi { color: #19177C } /* Name.Variable.Instance */ 71 | .codehilite .vm { color: #19177C } /* Name.Variable.Magic */ 72 | .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */ 73 | -------------------------------------------------------------------------------- /mark2epub.py: -------------------------------------------------------------------------------- 1 | import markdown 2 | import os 3 | from xml.dom import minidom 4 | import zipfile 5 | import sys 6 | import json 7 | 8 | ## markdown version 3.1 9 | 10 | ''' 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | 14 | plt.style.use('ggplot') 15 | 16 | X = np.linspace(-5,5,100) 17 | Y = np.sin(X)+0.2*np.random.randn(100) 18 | Z = -0.2*X 19 | 20 | plt.figure(figsize=(8,5)) 21 | plt.scatter(X,Y,c="darkgray",s=50) 22 | plt.plot(X,Z,linewidth=5,c="black") 23 | plt.savefig("./a.png",dpi=150) 24 | plt.show() 25 | ''' 26 | 27 | def get_all_filenames(the_dir,extensions=[]): 28 | all_files = [x for x in os.listdir(the_dir)] 29 | all_files = [x for x in all_files if x.split(".")[-1] in extensions] 30 | 31 | return all_files 32 | 33 | 34 | def get_packageOPF_XML(md_filenames=[],image_filenames=[],css_filenames=[],description_data=None): 35 | 36 | doc = minidom.Document() 37 | 38 | package = doc.createElement('package') 39 | package.setAttribute('xmlns',"http://www.idpf.org/2007/opf") 40 | package.setAttribute('version',"3.0") 41 | package.setAttribute('xml:lang',"en") 42 | package.setAttribute("unique-identifier","pub-id") 43 | 44 | ## Now building the metadata 45 | 46 | metadata = doc.createElement('metadata') 47 | metadata.setAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/') 48 | 49 | for k,v in description_data["metadata"].items(): 50 | if len(v): 51 | x = doc.createElement(k) 52 | for metadata_type,id_label in [("dc:title","title"),("dc:creator","creator"),("dc:identifier","book-id")]: 53 | if k==metadata_type: 54 | x.setAttribute('id',id_label) 55 | x.appendChild(doc.createTextNode(v)) 56 | metadata.appendChild(x) 57 | 58 | 59 | ## Now building the manifest 60 | 61 | manifest = doc.createElement('manifest') 62 | 63 | ## TOC.xhtml file for EPUB 3 64 | x = doc.createElement('item') 65 | x.setAttribute('id',"toc") 66 | x.setAttribute('properties',"nav") 67 | x.setAttribute('href',"TOC.xhtml") 68 | x.setAttribute('media-type',"application/xhtml+xml") 69 | manifest.appendChild(x) 70 | 71 | ## Ensure retrocompatibility by also providing a TOC.ncx file 72 | x = doc.createElement('item') 73 | x.setAttribute('id',"ncx") 74 | x.setAttribute('href',"toc.ncx") 75 | x.setAttribute('media-type',"application/x-dtbncx+xml") 76 | manifest.appendChild(x) 77 | 78 | x = doc.createElement('item') 79 | x.setAttribute('id',"titlepage") 80 | x.setAttribute('href',"titlepage.xhtml") 81 | x.setAttribute('media-type',"application/xhtml+xml") 82 | manifest.appendChild(x) 83 | 84 | for i,md_filename in enumerate(md_filenames): 85 | x = doc.createElement('item') 86 | x.setAttribute('id',"s{:05d}".format(i)) 87 | x.setAttribute('href',"s{:05d}-{}.xhtml".format(i,md_filename.split(".")[0])) 88 | x.setAttribute('media-type',"application/xhtml+xml") 89 | manifest.appendChild(x) 90 | 91 | for i,image_filename in enumerate(image_filenames): 92 | x = doc.createElement('item') 93 | x.setAttribute('id',"image-{:05d}".format(i)) 94 | x.setAttribute('href',"images/{}".format(image_filename)) 95 | if "gif" in image_filename: 96 | x.setAttribute('media-type',"image/gif") 97 | elif "jpg" in image_filename: 98 | x.setAttribute('media-type',"image/jpeg") 99 | elif "jpeg" in image_filename: 100 | x.setAttribute('media-type',"image/jpg") 101 | elif "png" in image_filename: 102 | x.setAttribute('media-type',"image/png") 103 | if image_filename==description_data["cover_image"]: 104 | x.setAttribute('properties',"cover-image") 105 | 106 | ## Ensure compatibility by also providing a meta tag in the metadata 107 | y = doc.createElement('meta') 108 | y.setAttribute('name',"cover") 109 | y.setAttribute('content',"image-{:05d}".format(i)) 110 | metadata.appendChild(y) 111 | manifest.appendChild(x) 112 | 113 | for i,css_filename in enumerate(css_filenames): 114 | x = doc.createElement('item') 115 | x.setAttribute('id',"css-{:05d}".format(i)) 116 | x.setAttribute('href',"css/{}".format(css_filename)) 117 | x.setAttribute('media-type',"text/css") 118 | manifest.appendChild(x) 119 | 120 | ## Now building the spine 121 | 122 | spine = doc.createElement('spine') 123 | spine.setAttribute('toc', "ncx") 124 | 125 | x = doc.createElement('itemref') 126 | x.setAttribute('idref',"titlepage") 127 | x.setAttribute('linear',"yes") 128 | spine.appendChild(x) 129 | for i,md_filename in enumerate(all_md_filenames): 130 | x = doc.createElement('itemref') 131 | x.setAttribute('idref',"s{:05d}".format(i)) 132 | x.setAttribute('linear',"yes") 133 | spine.appendChild(x) 134 | 135 | guide = doc.createElement('guide') 136 | x = doc.createElement('reference') 137 | x.setAttribute('type',"cover") 138 | x.setAttribute('title',"Cover image") 139 | x.setAttribute('href',"titlepage.xhtml") 140 | guide.appendChild(x) 141 | 142 | 143 | package.appendChild(metadata) 144 | package.appendChild(manifest) 145 | package.appendChild(spine) 146 | package.appendChild(guide) 147 | doc.appendChild(package) 148 | 149 | return doc.toprettyxml() 150 | 151 | 152 | def get_container_XML(): 153 | container_data = """\n""" 154 | container_data += """\n""" 155 | container_data += """\n""" 156 | container_data += """\n""" 157 | container_data += """\n""" 158 | 159 | return container_data 160 | 161 | 162 | def get_coverpage_XML(cover_image_path): 163 | ## Returns the XML data for the coverpage.xhtml file 164 | 165 | all_xhtml = """\n""" 166 | all_xhtml += """\n""" 167 | all_xhtml += """\n\n\n""" 168 | all_xhtml += """\n""".format(cover_image_path) 169 | all_xhtml += """\n""" 170 | 171 | return all_xhtml 172 | 173 | def get_TOC_XML(default_css_filenames,markdown_filenames): 174 | ## Returns the XML data for the TOC.xhtml file 175 | 176 | toc_xhtml = """\n""" 177 | toc_xhtml += """\n""" 178 | toc_xhtml += """\n\n""" 179 | toc_xhtml += """Contents\n""" 180 | 181 | for css_filename in default_css_filenames: 182 | toc_xhtml += """\n""".format(css_filename) 183 | 184 | toc_xhtml += """\n\n""" 185 | toc_xhtml += """\n\n""" 189 | 190 | return toc_xhtml 191 | 192 | def get_TOCNCX_XML(markdown_filenames): 193 | ## Returns the XML data for the TOC.ncx file 194 | 195 | toc_ncx = """\n""" 196 | toc_ncx += """\n""" 197 | toc_ncx += """\n\n""" 198 | toc_ncx += """\n""" 199 | for i,md_filename in enumerate(markdown_filenames): 200 | toc_ncx += """\n""".format(i) 201 | toc_ncx += """\n{}\n""".format(md_filename.split(".")[0]) 202 | toc_ncx += """""".format(i,md_filename.split(".")[0]) 203 | toc_ncx += """ """ 204 | toc_ncx += """\n""" 205 | 206 | return toc_ncx 207 | 208 | def get_chapter_XML(md_filename,css_filenames): 209 | ## Returns the XML data for a given markdown chapter file, with the corresponding css chapter files 210 | 211 | with open(os.path.join(work_dir,md_filename),"r",encoding="utf-8") as f: 212 | markdown_data = f.read() 213 | html_text = markdown.markdown(markdown_data, 214 | extensions=["codehilite","tables","fenced_code","footnotes"], 215 | extension_configs={"codehilite":{"guess_lang":False}} 216 | ) 217 | 218 | all_xhtml = """\n""" 219 | all_xhtml += """\n""" 220 | all_xhtml += """\n\n""" 221 | 222 | 223 | for css_filename in css_filenames: 224 | all_xhtml += """\n""".format(css_filename) 225 | 226 | all_xhtml += """\n\n""" 227 | 228 | all_xhtml += html_text 229 | all_xhtml += """\n\n""" 230 | 231 | return all_xhtml 232 | 233 | if __name__ == "__main__": 234 | if len(sys.argv[1:])<2: 235 | print("\nUsage:\n python md2epub.py ") 236 | exit(1) 237 | 238 | 239 | work_dir = sys.argv[1] 240 | output_path = sys.argv[2] 241 | 242 | images_dir = os.path.join(work_dir,r'images/') 243 | css_dir = os.path.join(work_dir,r'css/') 244 | 245 | ## Reading the JSON file containing the description of the eBook 246 | ## and compiling the list of relevant Markdown, CSS, and image files 247 | 248 | with open(os.path.join(work_dir,"description.json"),"r") as f: 249 | json_data = json.load(f) 250 | 251 | all_md_filenames=[] 252 | all_css_filenames=json_data["default_css"][:] 253 | for chapter in json_data["chapters"]: 254 | if not chapter["markdown"] in all_md_filenames: 255 | all_md_filenames.append(chapter["markdown"]) 256 | if len(chapter["css"]) and (not chapter["css"] in all_css_filenames): 257 | all_css_filenames.append(chapter["css"]) 258 | all_image_filenames = get_all_filenames(images_dir,extensions=["gif","jpg","jpeg","png"]) 259 | 260 | ###################################################### 261 | ## Now creating the ePUB book 262 | 263 | with zipfile.ZipFile(output_path, "w" ) as myZipFile: 264 | 265 | ## First, write the mimetype 266 | myZipFile.writestr("mimetype","application/epub+zip", zipfile.ZIP_DEFLATED ) 267 | 268 | ## Then, the file container.xml which just points to package.opf 269 | container_data = get_container_XML() 270 | myZipFile.writestr("META-INF/container.xml",container_data, zipfile.ZIP_DEFLATED ) 271 | 272 | ## Then, the package.opf file itself 273 | package_data = get_packageOPF_XML(md_filenames=all_md_filenames, 274 | image_filenames=all_image_filenames, 275 | css_filenames=all_css_filenames, 276 | description_data=json_data 277 | ) 278 | myZipFile.writestr("OPS/package.opf",package_data, zipfile.ZIP_DEFLATED) 279 | 280 | ## First, we create the cover page 281 | coverpage_data = get_coverpage_XML(json_data["cover_image"]) 282 | myZipFile.writestr("OPS/titlepage.xhtml",coverpage_data.encode('utf-8'),zipfile.ZIP_DEFLATED) 283 | 284 | ## Now, we are going to convert the Markdown files to xhtml files 285 | for i,chapter in enumerate(json_data["chapters"]): 286 | chapter_md_filename = chapter["markdown"] 287 | chapter_css_filenames = json_data["default_css"][:] 288 | if len(chapter["css"]): 289 | chapter_css_filenames.append(chapter["css"]) 290 | 291 | chapter_data = get_chapter_XML(chapter_md_filename,chapter_css_filenames) 292 | myZipFile.writestr("OPS/s{:05d}-{}.xhtml".format(i,chapter_md_filename.split(".")[0]), 293 | chapter_data.encode('utf-8'), 294 | zipfile.ZIP_DEFLATED) 295 | 296 | 297 | ## Writing the TOC.xhtml file 298 | toc_xml_data = get_TOC_XML(json_data["default_css"],all_md_filenames) 299 | myZipFile.writestr("OPS/TOC.xhtml",toc_xml_data.encode('utf-8'),zipfile.ZIP_DEFLATED) 300 | 301 | ## Writing the TOC.ncx file 302 | toc_ncx_data = get_TOCNCX_XML(all_md_filenames) 303 | myZipFile.writestr("OPS/toc.ncx",toc_ncx_data.encode('utf-8'),zipfile.ZIP_DEFLATED) 304 | 305 | ## Copy image files 306 | for i,image_filename in enumerate(all_image_filenames): 307 | with open(os.path.join(images_dir,image_filename),"rb") as f: 308 | filedata = f.read() 309 | myZipFile.writestr("OPS/images/{}".format(image_filename), 310 | filedata, 311 | zipfile.ZIP_DEFLATED) 312 | 313 | ## Copy CSS files 314 | for i,css_filename in enumerate(all_css_filenames): 315 | with open(os.path.join(css_dir,css_filename),"rb") as f: 316 | filedata = f.read() 317 | myZipFile.writestr("OPS/css/{}".format(css_filename), 318 | filedata, 319 | zipfile.ZIP_DEFLATED) 320 | 321 | print("eBook creation complete") 322 | --------------------------------------------------------------------------------