mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Edit book: Allow creation of new, empty books via File->Create new book
This commit is contained in:
parent
b4abb37e74
commit
c8a2ac65e0
@ -1492,7 +1492,7 @@ def metadata_to_opf(mi, as_string=True, default_lang=None):
|
||||
|
||||
root = etree.fromstring(textwrap.dedent(
|
||||
'''
|
||||
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="uuid_id">
|
||||
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="uuid_id" version="2.0">
|
||||
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
|
||||
<dc:identifier opf:scheme="%(a)s" id="%(a)s_id">%(id)s</dc:identifier>
|
||||
<dc:identifier opf:scheme="uuid" id="uuid_id">%(uuid)s</dc:identifier>
|
||||
|
102
src/calibre/ebooks/oeb/polish/create.py
Normal file
102
src/calibre/ebooks/oeb/polish/create.py
Normal file
@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import sys, os
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre import prepare_string_for_xml, CurrentDir
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||
from calibre.ebooks.oeb.polish.container import OPF_NAMESPACES, guess_type, opf_to_azw3
|
||||
from calibre.ebooks.oeb.polish.pretty import pretty_xml_tree
|
||||
from calibre.ebooks.oeb.polish.toc import TOC, create_ncx
|
||||
from calibre.utils.localization import lang_as_iso639_1
|
||||
from calibre.utils.logging import DevNull
|
||||
from calibre.utils.zipfile import ZipFile, ZIP_STORED
|
||||
|
||||
def create_toc(mi, opf, html_name, lang):
|
||||
uuid = ''
|
||||
for u in opf.xpath('//*[@id="uuid_id"]'):
|
||||
uuid = u.text
|
||||
toc = TOC()
|
||||
toc.add(_('Start'), html_name)
|
||||
return create_ncx(toc, lambda x:x, mi.title, lang, uuid)
|
||||
|
||||
def create_book(mi, path, fmt='epub', opf_name='metadata.opf', html_name='start.xhtml', toc_name='toc.ncx'):
|
||||
''' Create an empty book in the specified format at the specified location. '''
|
||||
path = os.path.abspath(path)
|
||||
lang = 'und'
|
||||
opf = metadata_to_opf(mi, as_string=False)
|
||||
for l in opf.xpath('//*[local-name()="language"]'):
|
||||
if l.text:
|
||||
lang = l.text
|
||||
break
|
||||
lang = lang_as_iso639_1(lang) or lang
|
||||
|
||||
opfns = OPF_NAMESPACES['opf']
|
||||
m = opf.makeelement('{%s}manifest' % opfns)
|
||||
opf.insert(1, m)
|
||||
i = m.makeelement('{%s}item' % opfns, href=html_name, id='start')
|
||||
i.set('media-type', guess_type('a.xhtml'))
|
||||
m.append(i)
|
||||
i = m.makeelement('{%s}item' % opfns, href=toc_name, id='ncx')
|
||||
i.set('media-type', guess_type(toc_name))
|
||||
m.append(i)
|
||||
s = opf.makeelement('{%s}spine' % opfns, toc="ncx")
|
||||
opf.insert(2, s)
|
||||
i = s.makeelement('{%s}itemref' % opfns, idref='start')
|
||||
s.append(i)
|
||||
CONTAINER = '''\
|
||||
<?xml version="1.0"?>
|
||||
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
||||
<rootfiles>
|
||||
<rootfile full-path="{0}" media-type="application/oebps-package+xml"/>
|
||||
</rootfiles>
|
||||
</container>
|
||||
'''.format(prepare_string_for_xml(opf_name, True)).encode('utf-8')
|
||||
HTML = '''\
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<html lang="{1}" xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<title>{0}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{0}</h1>
|
||||
</body>
|
||||
</html>
|
||||
'''.format(prepare_string_for_xml(mi.title), lang).encode('utf-8')
|
||||
ncx = etree.tostring(create_toc(mi, opf, html_name, lang), encoding='utf-8', xml_declaration=True, pretty_print=True)
|
||||
pretty_xml_tree(opf)
|
||||
opf = etree.tostring(opf, encoding='utf-8', xml_declaration=True, pretty_print=True)
|
||||
if fmt == 'azw3':
|
||||
with TemporaryDirectory('create-azw3') as tdir, CurrentDir(tdir):
|
||||
for name, data in ((opf_name, opf), (html_name, HTML), (toc_name, ncx)):
|
||||
with open(name, 'wb') as f:
|
||||
f.write(data)
|
||||
opf_to_azw3(opf_name, path, DevNull())
|
||||
else:
|
||||
with ZipFile(path, 'w', compression=ZIP_STORED) as zf:
|
||||
zf.writestr('mimetype', b'application/epub+zip', compression=ZIP_STORED)
|
||||
zf.writestr('META-INF/', b'', 0755)
|
||||
zf.writestr('META-INF/container.xml', CONTAINER)
|
||||
zf.writestr(opf_name, opf)
|
||||
zf.writestr(html_name, HTML)
|
||||
zf.writestr(toc_name, ncx)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
mi = Metadata('Test book', authors=('Kovid Goyal',))
|
||||
path = sys.argv[-1]
|
||||
ext = path.rpartition('.')[-1].lower()
|
||||
if ext not in ('epub', 'azw3'):
|
||||
print ('Unsupported format:', ext)
|
||||
raise SystemExit(1)
|
||||
create_book(mi, path, fmt=ext)
|
@ -32,7 +32,7 @@ from calibre.gui2.tweak_book.save import SaveManager, save_container
|
||||
from calibre.gui2.tweak_book.preview import parse_worker, font_cache
|
||||
from calibre.gui2.tweak_book.toc import TOCEditor
|
||||
from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
|
||||
from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data
|
||||
from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data, NewBook
|
||||
from calibre.gui2.tweak_book.preferences import Preferences
|
||||
|
||||
def get_container(*args, **kwargs):
|
||||
@ -135,7 +135,7 @@ class Boss(QObject):
|
||||
self.container_count += 1
|
||||
return tempfile.mkdtemp(prefix='%s%05d-' % (prefix, self.container_count), dir=self.tdir)
|
||||
|
||||
def open_book(self, path=None, edit_file=None, clear_notify_data=True):
|
||||
def _check_before_open(self):
|
||||
if self.gui.action_save.isEnabled():
|
||||
if not question_dialog(self.gui, _('Unsaved changes'), _(
|
||||
'The current book has unsaved changes. If you open a new book, they will be lost'
|
||||
@ -145,7 +145,24 @@ class Boss(QObject):
|
||||
return info_dialog(self.gui, _('Cannot open'),
|
||||
_('The current book is being saved, you cannot open a new book until'
|
||||
' the saving is completed'), show=True)
|
||||
return True
|
||||
|
||||
def new_book(self):
|
||||
if not self._check_before_open():
|
||||
return
|
||||
d = NewBook(self.gui)
|
||||
if d.exec_() == d.Accepted:
|
||||
fmt = d.fmt
|
||||
path = choose_save_file(self.gui, 'edit-book-new-book', _('Choose file location'),
|
||||
filters=[(fmt.upper(), (fmt,))], all_files=False)
|
||||
if path is not None:
|
||||
from calibre.ebooks.oeb.polish.create import create_book
|
||||
create_book(d.mi, path, fmt=fmt)
|
||||
self.open_book(path=path)
|
||||
|
||||
def open_book(self, path=None, edit_file=None, clear_notify_data=True):
|
||||
if not self._check_before_open():
|
||||
return
|
||||
if not hasattr(path, 'rpartition'):
|
||||
path = choose_files(self.gui, 'open-book-for-tweaking', _('Choose book'),
|
||||
[(_('Books'), [x.lower() for x in SUPPORTED])], all_files=False, select_only_single_file=True)
|
||||
|
@ -7,6 +7,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import sys, os
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import (
|
||||
QDialog, QGridLayout, QDialogButtonBox, QSize, QListView, QStyledItemDelegate,
|
||||
@ -16,9 +17,13 @@ from PyQt4.Qt import (
|
||||
|
||||
from calibre import fit_image
|
||||
from calibre.constants import plugins
|
||||
from calibre.ebooks.metadata import string_to_authors
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
from calibre.gui2 import NONE, choose_files, error_dialog
|
||||
from calibre.gui2.languages import LanguagesEdit
|
||||
from calibre.gui2.tweak_book import current_container, tprefs
|
||||
from calibre.gui2.tweak_book.file_list import name_is_ok
|
||||
from calibre.utils.localization import get_lang, canonicalize_lang
|
||||
from calibre.utils.icu import sort_key
|
||||
|
||||
class Dialog(QDialog):
|
||||
@ -54,7 +59,9 @@ class Dialog(QDialog):
|
||||
tprefs.set(self.name + '-splitter-state', bytearray(self.splitter.saveState()))
|
||||
QDialog.reject(self)
|
||||
|
||||
class ChooseName(Dialog):
|
||||
class ChooseName(Dialog): # {{{
|
||||
|
||||
''' Chooses the filename for a newly imported file, with error checking '''
|
||||
|
||||
def __init__(self, candidate, parent=None):
|
||||
self.candidate = candidate
|
||||
@ -91,7 +98,9 @@ class ChooseName(Dialog):
|
||||
name, ext = n.rpartition('.')[0::2]
|
||||
self.filename = name + '.' + ext.lower()
|
||||
super(ChooseName, self).accept()
|
||||
# }}}
|
||||
|
||||
# Images {{{
|
||||
class ImageDelegate(QStyledItemDelegate):
|
||||
|
||||
MARGIN = 4
|
||||
@ -291,6 +300,7 @@ class InsertImage(Dialog):
|
||||
def filter_changed(self, *args):
|
||||
f = unicode(self.filter.text())
|
||||
self.fm.setFilterFixedString(f)
|
||||
# }}}
|
||||
|
||||
def get_resource_data(rtype, parent):
|
||||
if rtype == 'image':
|
||||
@ -298,6 +308,54 @@ def get_resource_data(rtype, parent):
|
||||
if d.exec_() == d.Accepted:
|
||||
return d.chosen_image, d.chosen_image_is_external
|
||||
|
||||
class NewBook(Dialog): # {{{
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.fmt = 'epub'
|
||||
Dialog.__init__(self, _('Create new book'), 'create-new-book', parent=parent)
|
||||
|
||||
def setup_ui(self):
|
||||
self.l = l = QFormLayout(self)
|
||||
self.setLayout(l)
|
||||
|
||||
self.title = t = QLineEdit(self)
|
||||
l.addRow(_('&Title:'), t)
|
||||
t.setFocus(Qt.OtherFocusReason)
|
||||
|
||||
self.authors = a = QLineEdit(self)
|
||||
l.addRow(_('&Authors:'), a)
|
||||
a.setText(tprefs.get('previous_new_book_authors', ''))
|
||||
|
||||
self.languages = la = LanguagesEdit(self)
|
||||
l.addRow(_('&Language:'), la)
|
||||
la.lang_codes = (tprefs.get('previous_new_book_lang', canonicalize_lang(get_lang())),)
|
||||
|
||||
bb = self.bb
|
||||
l.addRow(bb)
|
||||
bb.clear()
|
||||
bb.addButton(bb.Cancel)
|
||||
b = bb.addButton('&EPUB', bb.AcceptRole)
|
||||
b.clicked.connect(partial(self.set_fmt, 'epub'))
|
||||
b = bb.addButton('&AZW3', bb.AcceptRole)
|
||||
b.clicked.connect(partial(self.set_fmt, 'azw3'))
|
||||
|
||||
def set_fmt(self, fmt):
|
||||
self.fmt = fmt
|
||||
|
||||
def accept(self):
|
||||
tprefs.set('previous_new_book_authors', unicode(self.authors.text()))
|
||||
tprefs.set('previous_new_book_lang', (self.languages.lang_codes or [get_lang()])[0])
|
||||
super(NewBook, self).accept()
|
||||
|
||||
@property
|
||||
def mi(self):
|
||||
mi = Metadata(unicode(self.title.text()).strip() or _('Unknown'))
|
||||
mi.authors = string_to_authors(unicode(self.authors.text()).strip()) or [_('Unknown')]
|
||||
mi.languages = self.languages.lang_codes or [get_lang()]
|
||||
return mi
|
||||
|
||||
# }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication([]) # noqa
|
||||
from calibre.gui2.tweak_book import set_current_container
|
||||
|
@ -277,6 +277,7 @@ class Main(MainWindow):
|
||||
self.action_save_copy = reg('save.png', _('Save a ©'), self.boss.save_copy, 'save-copy', 'Ctrl+Alt+S', _('Save a copy of the book'))
|
||||
self.action_quit = reg('quit.png', _('&Quit'), self.boss.quit, 'quit', 'Ctrl+Q', _('Quit'))
|
||||
self.action_preferences = reg('config.png', _('&Preferences'), self.boss.preferences, 'preferences', 'Ctrl+P', _('Preferences'))
|
||||
self.action_new_book = reg('book.png', _('Create &new, empty book'), self.boss.new_book, 'new-book', (), _('Create a new, empty book'))
|
||||
|
||||
# Editor actions
|
||||
group = _('Editor actions')
|
||||
@ -391,6 +392,7 @@ class Main(MainWindow):
|
||||
f = b.addMenu(_('&File'))
|
||||
f.addAction(self.action_new_file)
|
||||
f.addAction(self.action_open_book)
|
||||
f.addAction(self.action_new_book)
|
||||
self.recent_books_menu = f.addMenu(_('&Recently opened books'))
|
||||
self.update_recent_books()
|
||||
f.addSeparator()
|
||||
|
Loading…
x
Reference in New Issue
Block a user