Add MOBI as an output format to the GUI. calibre now fully supports MOBI output, except for conversion of comics.

This commit is contained in:
Kovid Goyal 2009-01-20 23:49:25 -08:00
parent 7e00391f59
commit 00180c7f27
7 changed files with 177 additions and 86 deletions

View File

@ -28,7 +28,7 @@ def option_parser(usage=USAGE):
parser = config().option_parser(usage=usage)
return parser
def any2mobi(opts, path):
def any2mobi(opts, path, notification=None):
ext = os.path.splitext(path)[1]
if not ext:
raise ValueError('Unknown file type: '+path)

View File

@ -15,7 +15,7 @@ from lxml.etree import XPath
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.gui2.dialogs.epub_ui import Ui_Dialog
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig
from calibre.ebooks.metadata import MetaInformation
from calibre.ptempfile import PersistentTemporaryFile
from calibre.ebooks.metadata.opf import OPFCreator
@ -24,9 +24,12 @@ from calibre.ebooks.metadata import authors_to_string, string_to_authors
class Config(QDialog, Ui_Dialog):
def __init__(self, parent, db, row=None):
OUTPUT = 'EPUB'
def __init__(self, parent, db, row=None, config=epubconfig):
QDialog.__init__(self, parent)
self.setupUi(self)
self.hide_controls()
self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'),
self.show_category_help)
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
@ -38,7 +41,7 @@ class Config(QDialog, Ui_Dialog):
if row is not None:
self.id = db.id(row)
base = config().as_string() + '\n\n'
defaults = self.db.conversion_options(self.id, 'epub')
defaults = self.db.conversion_options(self.id, self.OUTPUT.lower())
defaults = base + (defaults if defaults else '')
self.config = config(defaults=defaults)
else:
@ -47,9 +50,18 @@ class Config(QDialog, Ui_Dialog):
self.get_source_format()
self.category_list.setCurrentRow(0)
if self.row is None:
self.setWindowTitle(_('Bulk convert to EPUB'))
self.setWindowTitle(_('Bulk convert to ')+self.OUTPUT)
else:
self.setWindowTitle(_(u'Convert %s to EPUB')%unicode(self.title.text()))
self.setWindowTitle((_(u'Convert %s to ')%unicode(self.title.text()))+self.OUTPUT)
def hide_controls(self):
self.source_profile_label.setVisible(False)
self.opt_source_profile.setVisible(False)
self.dest_profile_label.setVisible(False)
self.opt_dest_profile.setVisible(False)
self.opt_toc_title.setVisible(False)
self.toc_title_label.setVisible(False)
self.opt_rescale_images.setVisible(False)
def initialize(self):
self.__w = []
@ -81,8 +93,8 @@ class Config(QDialog, Ui_Dialog):
def show_category_help(self, item):
text = unicode(item.text())
help = {
_('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated EPUB file.'),
_('Look & Feel') : _('Adjust the look of the generated EPUB file by specifying things like font sizes.'),
_('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated %s file.')%self.OUTPUT,
_('Look & Feel') : _('Adjust the look of the generated ebook by specifying things like font sizes.'),
_('Page Setup') : _('Specify the page layout settings like margins.'),
_('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
}
@ -195,7 +207,7 @@ class Config(QDialog, Ui_Dialog):
elif isinstance(g, QCheckBox):
self.config.set(pref.name, bool(g.isChecked()))
if self.row is not None:
self.db.set_conversion_options(self.id, 'epub', self.config.src)
self.db.set_conversion_options(self.id, self.OUTPUT.lower(), self.config.src)
def initialize_options(self):
@ -235,7 +247,7 @@ class Config(QDialog, Ui_Dialog):
elif len(choices) == 1:
self.source_format = choices[0]
else:
d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to EPUB'), choices)
d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to ')+self.OUTPUT, choices)
if d.exec_() == QDialog.Accepted:
self.source_format = d.format()

View File

@ -89,36 +89,6 @@
<string>Book Cover</string>
</property>
<layout class="QGridLayout" name="_2" >
<item row="0" column="0" >
<layout class="QHBoxLayout" name="_3" >
<item>
<widget class="ImageView" name="cover" >
<property name="text" >
<string/>
</property>
<property name="pixmap" >
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
</property>
<property name="scaledContents" >
<bool>true</bool>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" >
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
<property name="text" >
<string>Use cover from &amp;source file</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" >
<layout class="QVBoxLayout" name="_4" >
<property name="spacing" >
@ -170,6 +140,36 @@
</item>
</layout>
</item>
<item row="2" column="0" >
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
<property name="text" >
<string>Use cover from &amp;source file</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" >
<layout class="QHBoxLayout" name="_3" >
<item>
<widget class="ImageView" name="cover" >
<property name="text" >
<string/>
</property>
<property name="pixmap" >
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
</property>
<property name="scaledContents" >
<bool>true</bool>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
<zorder>opt_prefer_metadata_cover</zorder>
<zorder></zorder>
@ -456,6 +456,13 @@
</property>
</widget>
</item>
<item row="4" column="0" >
<widget class="QCheckBox" name="opt_rescale_images" >
<property name="text" >
<string>&amp;Rescale images</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -475,7 +482,7 @@
<widget class="QWidget" name="pagesetup_page" >
<layout class="QGridLayout" name="_13" >
<item row="0" column="0" >
<widget class="QLabel" name="label_11" >
<widget class="QLabel" name="profile_label" >
<property name="text" >
<string>&amp;Profile:</string>
</property>
@ -494,7 +501,7 @@
</property>
</widget>
</item>
<item row="1" column="0" >
<item row="3" column="0" >
<widget class="QLabel" name="label_12" >
<property name="text" >
<string>&amp;Left Margin:</string>
@ -504,7 +511,7 @@
</property>
</widget>
</item>
<item row="1" column="1" >
<item row="3" column="1" >
<widget class="QSpinBox" name="opt_margin_left" >
<property name="suffix" >
<string> pt</string>
@ -517,7 +524,7 @@
</property>
</widget>
</item>
<item row="2" column="0" >
<item row="4" column="0" >
<widget class="QLabel" name="label_13" >
<property name="text" >
<string>&amp;Right Margin:</string>
@ -527,7 +534,7 @@
</property>
</widget>
</item>
<item row="2" column="1" >
<item row="4" column="1" >
<widget class="QSpinBox" name="opt_margin_right" >
<property name="suffix" >
<string> pt</string>
@ -540,7 +547,7 @@
</property>
</widget>
</item>
<item row="3" column="0" >
<item row="5" column="0" >
<widget class="QLabel" name="label_14" >
<property name="text" >
<string>&amp;Top Margin:</string>
@ -550,7 +557,7 @@
</property>
</widget>
</item>
<item row="3" column="1" >
<item row="5" column="1" >
<widget class="QSpinBox" name="opt_margin_top" >
<property name="suffix" >
<string> pt</string>
@ -563,7 +570,7 @@
</property>
</widget>
</item>
<item row="4" column="0" >
<item row="6" column="0" >
<widget class="QLabel" name="label_15" >
<property name="text" >
<string>&amp;Bottom Margin:</string>
@ -573,7 +580,7 @@
</property>
</widget>
</item>
<item row="4" column="1" >
<item row="6" column="1" >
<widget class="QSpinBox" name="opt_margin_bottom" >
<property name="suffix" >
<string> pt</string>
@ -586,13 +593,39 @@
</property>
</widget>
</item>
<item row="5" column="0" >
<item row="7" column="0" >
<widget class="QCheckBox" name="opt_dont_split_on_page_breaks" >
<property name="text" >
<string>Do not &amp;split on page breaks</string>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="source_profile_label" >
<property name="text" >
<string>&amp;Source profile:</string>
</property>
<property name="buddy" >
<cstring>opt_source_profile</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QComboBox" name="opt_source_profile" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="dest_profile_label" >
<property name="text" >
<string>&amp;Destination profile:</string>
</property>
<property name="buddy" >
<cstring>opt_dest_profile</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_dest_profile" />
</item>
</layout>
</widget>
<widget class="QWidget" name="chapterdetection_page" >
@ -721,6 +754,19 @@ p, li { white-space: pre-wrap; }
<item row="5" column="1" >
<widget class="QLineEdit" name="opt_level2_toc" />
</item>
<item row="6" column="1" >
<widget class="QLineEdit" name="opt_toc_title" />
</item>
<item row="6" column="0" >
<widget class="QLabel" name="toc_title_label" >
<property name="text" >
<string>&amp;Title for generated TOC</string>
</property>
<property name="buddy" >
<cstring>opt_toc_title</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
from calibre.gui2.dialogs.epub import Config as _Config
from calibre.ebooks.mobi.from_any import config as mobiconfig
class Config(_Config):
OUTPUT = 'MOBI'
def __init__(self, parent, db, row=None):
_Config.__init__(self, parent, db, row=row, config=mobiconfig)
def hide_controls(self):
self.profile_label.setVisible(False)
self.opt_profile.setVisible(False)
self.opt_dont_split_on_page_breaks.setVisible(False)
self.opt_preserve_tag_structure.setVisible(False)

View File

@ -25,7 +25,6 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
max_available_height, config, info_dialog, \
available_width
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
from calibre.library.database import LibraryDatabase
from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.gui2.update import CheckForUpdates
from calibre.gui2.dialogs.progress import ProgressDialog
@ -131,14 +130,14 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.stack, SIGNAL('currentChanged(int)'),
self.location_view.location_changed)
self.output_formats = sorted(['EPUB', 'LRF'])
self.output_formats = sorted(['EPUB', 'MOBI', 'LRF'])
for f in self.output_formats:
self.output_format.addItem(f)
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format']))
def change_output_format(x):
of = unicode(x).strip()
if of != prefs['output_format']:
if of in ('EPUB', 'LIT'):
if of not in ('LRF',):
warning_dialog(self, 'Warning',
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
prefs.set('output_format', of)

View File

@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog
from calibre.utils.config import prefs
from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
from calibre.gui2.dialogs.epub import Config as EPUBConvert
from calibre.gui2.dialogs.mobi import Config as MOBIConvert
import calibre.gui2.dialogs.comicconf as ComicConf
from calibre.gui2 import warning_dialog
from calibre.ptempfile import PersistentTemporaryFile
@ -19,14 +20,20 @@ from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_
from calibre.ebooks.metadata.opf import OPFCreator
from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS
def convert_single_epub(parent, db, comics, others):
def get_dialog(fmt):
return {
'epub':EPUBConvert,
'mobi':MOBIConvert,
}[fmt]
def convert_single(fmt, parent, db, comics, others):
changed = False
jobs = []
others_ids = [db.id(row) for row in others]
comics_ids = [db.id(row) for row in comics]
for row, row_id in zip(others, others_ids):
temp_files = []
d = EPUBConvert(parent, db, row)
d = get_dialog(fmt)(parent, db, row)
if d.source_format is not None:
d.exec_()
if d.result() == QDialog.Accepted:
@ -35,7 +42,7 @@ def convert_single_epub(parent, db, comics, others):
pt = PersistentTemporaryFile('.'+d.source_format.lower())
pt.write(data)
pt.close()
of = PersistentTemporaryFile('.epub')
of = PersistentTemporaryFile('.'+fmt)
of.close()
opts.output = of.name
opts.from_opf = d.opf_file.name
@ -45,8 +52,8 @@ def convert_single_epub(parent, db, comics, others):
temp_files.append(d.cover_file)
opts.cover = d.cover_file.name
temp_files.extend([d.opf_file, pt, of])
jobs.append(('any2epub', args, _('Convert book: ')+d.mi.title,
'EPUB', row_id, temp_files))
jobs.append(('any2'+fmt, args, _('Convert book: ')+d.mi.title,
fmt.upper(), row_id, temp_files))
changed = True
for row, row_id in zip(comics, comics_ids):
@ -61,24 +68,24 @@ def convert_single_epub(parent, db, comics, others):
if defaults is not None:
db.set_conversion_options(db.id(row), 'comic', defaults)
if opts is None: continue
for fmt in ['cbz', 'cbr']:
for _fmt in ['cbz', 'cbr']:
try:
data = db.format(row, fmt.upper())
data = db.format(row, _fmt.upper())
if data is not None:
break
except:
continue
pt = PersistentTemporaryFile('.'+fmt)
pt = PersistentTemporaryFile('.'+_fmt)
pt.write(data)
pt.close()
of = PersistentTemporaryFile('.epub')
of = PersistentTemporaryFile('.'+fmt)
of.close()
opts.output = of.name
opts.verbose = 2
args = [pt.name, opts]
changed = True
jobs.append(('comic2epub', args, _('Convert comic: ')+opts.title,
'EPUB', row_id, [pt, of]))
jobs.append(('comic2'+fmt, args, _('Convert comic: ')+opts.title,
fmt.upper(), row_id, [pt, of]))
return jobs, changed
@ -146,9 +153,9 @@ def convert_single_lrf(parent, db, comics, others):
return jobs, changed
def convert_bulk_epub(parent, db, comics, others):
def convert_bulk(fmt, parent, db, comics, others):
if others:
d = EPUBConvert(parent, db)
d = get_dialog(fmt)(parent, db)
if d.exec_() != QDialog.Accepted:
others = []
else:
@ -169,9 +176,9 @@ def convert_bulk_epub(parent, db, comics, others):
row_id = db.id(row)
if row in others:
data = None
for fmt in EPUB_PREFERRED_SOURCE_FORMATS:
for _fmt in EPUB_PREFERRED_SOURCE_FORMATS:
try:
data = db.format(row, fmt.upper())
data = db.format(row, _fmt.upper())
if data is not None:
break
except:
@ -185,10 +192,10 @@ def convert_bulk_epub(parent, db, comics, others):
opf_file = PersistentTemporaryFile('.opf')
opf.render(opf_file)
opf_file.close()
pt = PersistentTemporaryFile('.'+fmt.lower())
pt = PersistentTemporaryFile('.'+_fmt.lower())
pt.write(data)
pt.close()
of = PersistentTemporaryFile('.epub')
of = PersistentTemporaryFile('.'+fmt)
of.close()
cover = db.cover(row)
cf = None
@ -203,7 +210,7 @@ def convert_bulk_epub(parent, db, comics, others):
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
temp_files = [cf] if cf is not None else []
temp_files.extend([opf_file, pt, of])
jobs.append(('any2epub', args, desc, 'EPUB', row_id, temp_files))
jobs.append(('any2'+fmt, args, desc, fmt.upper(), row_id, temp_files))
else:
options = comic_opts.copy()
mi = db.get_metadata(row)
@ -212,24 +219,24 @@ def convert_bulk_epub(parent, db, comics, others):
if mi.authors:
options.author = ','.join(mi.authors)
data = None
for fmt in ['cbz', 'cbr']:
for _fmt in ['cbz', 'cbr']:
try:
data = db.format(row, fmt.upper())
data = db.format(row, _fmt.upper())
if data is not None:
break
except:
continue
pt = PersistentTemporaryFile('.'+fmt.lower())
pt = PersistentTemporaryFile('.'+_fmt.lower())
pt.write(data)
pt.close()
of = PersistentTemporaryFile('.epub')
of = PersistentTemporaryFile('.'+fmt)
of.close()
setattr(options, 'output', of.name)
options.verbose = 1
args = [pt.name, options]
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
jobs.append(('comic2epub', args, desc, 'EPUB', row_id, [pt, of]))
jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of]))
if bad_rows:
res = []
@ -345,15 +352,14 @@ def set_conversion_defaults_lrf(comic, parent, db):
else:
LRFSingleDialog(parent, None, None).exec_()
def set_conversion_defaults_epub(comic, parent, db):
def _set_conversion_defaults(dialog, comic, parent, db):
if comic:
ComicConf.set_conversion_defaults(parent)
else:
d = EPUBConvert(parent, db)
d = dialog(parent, db)
d.setWindowTitle(_('Set conversion defaults'))
d.exec_()
def _fetch_news(data, fmt):
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
pt.close()
@ -385,22 +391,22 @@ def convert_single_ebook(*args):
fmt = prefs['output_format'].lower()
if fmt == 'lrf':
return convert_single_lrf(*args)
elif fmt == 'epub':
return convert_single_epub(*args)
elif fmt in ('epub', 'mobi'):
return convert_single(fmt, *args)
def convert_bulk_ebooks(*args):
fmt = prefs['output_format'].lower()
if fmt == 'lrf':
return convert_bulk_lrf(*args)
elif fmt == 'epub':
return convert_bulk_epub(*args)
elif fmt in ('epub', 'mobi'):
return convert_bulk(fmt, *args)
def set_conversion_defaults(comic, parent, db):
fmt = prefs['output_format'].lower()
if fmt == 'lrf':
return set_conversion_defaults_lrf(comic, parent, db)
elif fmt == 'epub':
return set_conversion_defaults_epub(comic, parent, db)
elif fmt in ('epub', 'mobi'):
return _set_conversion_defaults(get_dialog(fmt), comic, parent, db)
def fetch_news(data):
fmt = prefs['output_format'].lower()

View File

@ -67,7 +67,15 @@ PARALLEL_FUNCS = {
'comic2epub' :
('calibre.ebooks.epub.from_comic', 'convert', {}, 'notification'),
'any2mobi' :
('calibre.ebooks.mobi.from_any', 'any2mobi', {}, None),
'feeds2mobi' :
('calibre.ebooks.mobi.from_feeds', 'main', {}, 'notification'),
'comic2mobi' :
('calibre.ebooks.mobi.from_comic', 'convert', {}, 'notification'),
}