diff options
Diffstat (limited to 'emacs/.emacs.d/lisp/my/my-epub.el')
| -rw-r--r-- | emacs/.emacs.d/lisp/my/my-epub.el | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/emacs/.emacs.d/lisp/my/my-epub.el b/emacs/.emacs.d/lisp/my/my-epub.el new file mode 100644 index 0000000..9c3ad59 --- /dev/null +++ b/emacs/.emacs.d/lisp/my/my-epub.el @@ -0,0 +1,176 @@ +;;; my-epub.el -- epub utils -*- lexical-binding: t -*- + +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; Author: Yuchen Pei <id@ypei.org> +;; Package-Requires: ((emacs "30.1")) + +;; This file is part of dotted. + +;; dotted is free software: you can redistribute it and/or modify it under +;; the terms of the GNU Affero General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; dotted is distributed in the hope that it will be useful, but WITHOUT +;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General +;; Public License for more details. + +;; You should have received a copy of the GNU Affero General Public +;; License along with dotted. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; epub utils. + +;;; Code: + + +(defun my-epub-content-file-name (file-name) + (with-temp-buffer + (if (eq 0 (call-process "unzip" nil t nil + "-p" file-name "META-INF/container.xml")) + (let ((dom (libxml-parse-xml-region (point-min) (point-max)))) + (dom-attr + (dom-by-tag + (dom-by-tag (dom-by-tag dom 'container) 'rootfiles) + 'rootfile) + 'full-path)) + (message "Failed to extract container.xml: %s" (buffer-string)) + nil))) + +(defun my-epub-metadata (file-name) + "Get metadata of an epub file." + (when-let ((content-file-name (my-epub-content-file-name file-name))) + (with-temp-buffer + (call-process "unzip" nil t nil "-p" file-name content-file-name) + (let* ((dom (libxml-parse-xml-region (point-min) (point-max))) + (metadata (dom-by-tag dom 'metadata)) + (title (dom-text (dom-by-tag metadata 'title))) + (authors (dom-texts (dom-by-tag metadata 'creator) ", ")) + (identifier + (replace-regexp-in-string + "[^0-9,]" "" + (dom-texts + (seq-filter + (lambda (node) + (or (equal "ISBN" (dom-attr node 'scheme)) + (string-match-p "^[0-9]+$" (dom-text node)))) + (dom-by-tag metadata 'identifier)) + ","))) + (date (replace-regexp-in-string + "[^0-9]" "" + (dom-text (dom-by-tag metadata 'date)))) + (year (substring date 0 (min 4 (length date))))) + `((title . ,title) + (authors . ,authors) + (year . ,year) + (identifier . ,identifier)) + ;; (pp metadata) + )) + )) + +;; generate epub +(defun my-epub-create (dir title author) + "Create an epub by concatenating htmls in DIR." + (let* ((name + (file-name-nondirectory (directory-file-name dir))) + (tmpdir + (make-temp-file (format "/tmp/%s.epub." name) t)) + (files (directory-files dir nil directory-files-no-dot-files-regexp))) + (my-epub--create-mimetype tmpdir) + (my-epub--create-container tmpdir) + (my-epub--create-opf tmpdir files title author) + (my-epub--create-toc tmpdir files title) + (my-epub--add-html + tmpdir + (directory-files dir t directory-files-no-dot-files-regexp)) + (my-epub--zip tmpdir name) + )) + +(defun my-epub--create-mimetype (dir) + (with-temp-buffer + (insert "application/epub+zip") + (write-file (file-name-concat dir "mimetype")))) + +(defun my-epub--create-container (dir) + (with-temp-buffer + (insert + "<?xml version=\"1.0\"?> +<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\"> + <rootfiles> + <rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/> + </rootfiles> +</container>") + (make-directory + (file-name-concat dir "META-INF")) + (write-file (file-name-concat dir "META-INF/container.xml")))) + +(defun my-epub--create-opf (dir files title author) + (with-temp-buffer + (insert + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "\n") + (dom-print + `(package + ((unique-identifier . "BookID") + (version . "2.0")) + (metadata nil + (title nil ,title) + (creator nil ,author) + (language nil "en") + (identifier + ((id . "BookID")) + "Hello ID")) + (manifest + nil + (item ((id . "ncx") + (href . "toc.ncx") + (media-type . "application/x-dtbncx+xml"))) + ,@(seq-map + (lambda (file) + `(item ((id . ,file) + (href . ,file) + (media-type . "application/xhtml+xml")))) + files)) + (spine + ((toc . "ncx")) + ,@(seq-map + (lambda (file) + `(itemref ((idref . ,file)))) + files))) + t t) + (make-directory + (file-name-concat dir "OEBPS")) + (write-file (file-name-concat dir "OEBPS/content.opf")) + (message (buffer-string)))) + +(defun my-epub--create-toc (dir files title) + (with-temp-buffer + (insert "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "\n") + (dom-print + `(ncx nil + (docTitle nil + (text nil ,title)) + (navMap + nil + ,@(seq-map + (lambda (file) + `(navPoint nil + (content ((src . ,file))))) + files))) + t t) + (write-file (file-name-concat dir "OEBPS/toc.ncx")))) + +(defun my-epub--add-html (dir files) + (seq-do + (lambda (file) + (copy-file file (file-name-concat dir "OEBPS/"))) + files)) + +(defun my-epub--zip (dir name) + (let ((default-directory dir)) + (call-process "zip" nil nil nil "-r" (format "/tmp/%s.epub" name) "."))) + +(provide 'my-epub) +;;; my-epub.el ends here |
