├── 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 | 
18 | ```
19 |
20 | which will produce the following image,
21 |
22 | 
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 |
--------------------------------------------------------------------------------