Calibre’nin işlevselliğini artırmak için kendi eklentilerinizi yazmak¶
calibre oldukça modüler bir tasarıma sahiptir. Calibre’deki hemen hemen tüm işlevler eklentiler biçiminde gelir. Eklentiler dönüşüm için, haber indirmek için (bunlara tarif denir), kullanıcı arayüzünün çeşitli bileşenleri için, farklı cihazlara bağlanmak için, dosyaları calibre’ye eklerken işlemek için kullanılır. Tercihler → Gelişmiş → Eklentiler’e giderek calibre’deki tüm yerleşik eklentilerin tam listesini alabilirsiniz.
Burda, calibre’ye yeni işlevler kazandırmak için kendi eklentilerinizi nasıl oluşturacağınızı öğreteceğiz.
Not
Bu yalnızca >= 0.8.60 calibre sürümleri ile mümkündür
Bir calibre eklentisinin anatomisi¶
Calibre eklentisi çok basittir, sadece bazı Python kodlarını ve eklentinin ihtiyaç duyduğu görüntü dosyaları gibi diğer kaynakları içeren bir ZIP dosyasıdır. Lafı fazla uzatmadan basit bir örneğe bakalım.
Çeşitli e-belgeleri EPUB ve MOBI biçimlerinde dağıtmak için kullandığınız bir calibre kurulumunuz olduğunu varsayın. Calibre tarafından üretilen tüm dosyaların yayıncısının “Merhaba dünya” olmasını istersiniz, bunu şu şekilde yapabilirsiniz. __init__.py
isimli bir dosya oluşturun (bu özel bir isimdir ve eklentinizin ana dosyası için her zaman kullanılmak zorundadır) ve aşağıdaki Python kodunu girin:
from calibre.customize import FileTypePlugin
class HelloWorld(FileTypePlugin):
name = 'Hello World Plugin' # Name of the plugin
description = 'Set the publisher to Hello World for all new conversions'
supported_platforms = ['windows', 'osx', 'linux'] # Platforms this plugin will run on
author = 'Acme Inc.' # The author of this plugin
version = (1, 0, 0) # The version number of this plugin
file_types = {'epub', 'mobi'} # The file types that this plugin will be applied to
on_postprocess = True # Run this plugin after conversion is complete
minimum_calibre_version = (0, 7, 53)
def run(self, path_to_ebook):
from calibre.ebooks.metadata.meta import get_metadata, set_metadata
with open(path_to_ebook, 'r+b') as file:
ext = os.path.splitext(path_to_ebook)[-1][1:].lower()
mi = get_metadata(file, ext)
mi.publisher = 'Hello World'
set_metadata(file, mi, ext)
return path_to_ebook
Bu kadar. Bu kodu calibre’ye eklenti olarak eklemek için, oluşturduğunuz klasörde aşağıdakini çalıştırmanız yeterlidir __init__.py
:
calibre-customize -b .
Not
MacOS’ta komut satırı araçları calibre paketinin içindedir; örneğin, calibre’yi /Applications
dizinine yüklediyseniz komut satırı araçları /Applications/calibre.app/Contents/MacOS/
konumundadır .
Merhaba Dünya eklentisini helloworld_plugin.zip adresinden indirebilirsiniz.
Calibre kullanarak her bir kitap dönüştürme işlemi yaptığınızda, eklentinin run()
metodu çağrılacak ve dönüştürülen kitabın yayımcısı olarak “Merhaba Dünya” ayarlanacak. Bu önemsiz bir eklenti, daha karmaşık ve kullanıcı arayüzüne bir şeyler ekleyen bir tanesine geçelim.
Kullanıcı Arayüzü eklentisi¶
Bu eklenti birkaç dosyaya yayılacaktır (kodu temiz tutmak için). Eklenti ZIP dosyasından kaynakları (görüntüler veya veri dosyaları) nasıl alacağınızı, kullanıcıların eklentinizi yapılandırmasına olanak tanıyacağını, calibre kullanıcı arayüzünde nasıl öğeler oluşturacağınızı ve calibre’de kitap veritabanına nasıl erişip sorgulayacağınızı size gösterecektir.
Bu eklentiyi interface_demo_plugin.zip adresinden indirebilirsiniz.
Unutulmaması gereken ilk şey, bu ZIP dosyasının içinde çok daha fazla dosya bulunmasıdır, aşağıda açıklandığı gibi, ``eklenti-içe aktarma-adı-arabirimi_demo.txt``ye özellikle dikkat edin.
- plugin-import-name-interface_demo.txt
Çoklu dosya eklentisi büyüsünü etkinleştirmek için kullanılan boş bir metin dosyası. Bu dosya, birden fazla .py dosyası kullanan tüm eklentilerde bulunmalıdır. Boş olmalı ve dosya adı şu biçimde olmalıdır:
eklenti-içe aktarma-adı-**bir_adı**.txt
. Bu dosyanın varlığı, aşağıdaki gibi bir ifade kullanarak ZIP dosyasında bulunan .py dosyalarından kod içe aktarmanıza olanak tanır:from calibre_plugins.some_name.some_module import some_object
calibre_plugins
öneki her zaman mevcut olmalıdır.bir_ad
boş metin dosyasının dosya adından gelir.some_module
, ZIP dosyasındakisome_module.py
dosyasını ifade eder. Bu içe aktarmanın normal Python içe aktarmaları kadar güçlü olduğunu unutmayın. Tıpkı normalde yaptığınız gibi (her alt klasörde __init__.py tanımlayarak) ZIP dosyası içinde .py modüllerinin paketlerini ve alt paketlerini oluşturabilirsiniz ve her şey “çalışır”.
bir_ad
için kullandığınız ad, tüm eklentiler tarafından paylaşılan genel bir ad alanına girer, bu yüzden onu mümkün olduğunca benzersiz yapın. Ancak bunun geçerli bir Python tanımlayıcısı olması gerektiğini unutmayın (yalnızca harfler, sayılar ve alt çizgi).- __init__.py
Daha önce olduğu gibi, eklenti sınıfını belirleyen dosya
- main.py
Bu dosya işe yarar bir şeyler yapan gerçek kodu içerir
- ui.py
Bu dosya eklentinin arayüz kısmını tanımlar
- images/icon.png
Eklenti için simge
- about.txt
Eklenti hakkında bilgi veren bir metin dosyası
- translations
Eklentinizin değişik dillere çevirilerini içeren .mo dosyalarını içeren bir dizin. Detaylar için aşağıya bakın.
Şimdi koda bakalım.
__init__.py¶
Önce, eklenti metadatasını tanımlamak için zorunlu __init__.py
:
from calibre.customize import InterfaceActionBase
class InterfacePluginDemo(InterfaceActionBase):
'''
This class is a simple wrapper that provides information about the actual
plugin class. The actual interface plugin class is called InterfacePlugin
and is defined in the ui.py file, as specified in the actual_plugin field
below.
The reason for having two classes is that it allows the command line
calibre utilities to run without needing to load the GUI libraries.
'''
name = 'Interface Plugin Demo'
description = 'An advanced plugin demo'
supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal'
version = (1, 0, 0)
minimum_calibre_version = (0, 7, 53)
#: This field defines the GUI plugin class that contains all the code
#: that actually does something. Its format is module_path:class_name
#: The specified class must be defined in the specified module.
actual_plugin = 'calibre_plugins.interface_demo.ui:InterfacePlugin'
def is_customizable(self):
'''
This method must return True to enable customization via
Preferences->Plugins
'''
return True
def config_widget(self):
'''
Implement this method and :meth:`save_settings` in your plugin to
use a custom configuration dialog.
This method, if implemented, must return a QWidget. The widget can have
an optional method validate() that takes no arguments and is called
immediately after the user clicks OK. Changes are applied if and only
if the method returns True.
If for some reason you cannot perform the configuration at this time,
return a tuple of two strings (message, details), these will be
displayed as a warning dialog to the user and the process will be
aborted.
The base class implementation of this method raises NotImplementedError
so by default no user configuration is possible.
'''
# It is important to put this import statement here rather than at the
# top of the module as importing the config class will also cause the
# GUI libraries to be loaded, which we do not want when using calibre
# from the command line
from calibre_plugins.interface_demo.config import ConfigWidget
return ConfigWidget()
def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.
:param config_widget: The widget returned by :meth:`config_widget`.
'''
config_widget.save_settings()
# Apply the changes
ac = self.actual_plugin_
if ac is not None:
ac.apply_settings()
Dikkate değer tek özellik actual_plugin
alanıdır. Calibre’de hem komut satırı hem de GUI arayüzleri olduğundan, bunun gibi GUI eklentileri __init__.py dosyasına herhangi bir GUI kütüphanesi yüklememelidir. real_plugin alanı bunu sizin için calibre’ye gerçek eklentinin ZIP arşivinizdeki başka bir dosyada bulunacağını söyleyerek yapar ve bu dosya yalnızca GUI bağlamına yüklenir.
Bunun çalışması için yukarıda tartışıldığı gibi eklenti ZIP dosyanızda bir eklenti-içe aktarma adı-bir_adı.txt dosyanızın olması gerektiğini unutmayın.
Ayrıca eklentinin kullanıcı yapılandırmasının etkinleştirilmesi için bir kaç metod bulunmaktadır. Bunlardan aşağıda söz edilmektedir.
ui.py¶
Şimdi asıl GUI eklentisini tanımlayan ui.py ye bakalım. Kaynak kod yoğun şekilde yorumlanmıştır, kendini açıklıyor olması lazım:
from calibre.gui2.actions import InterfaceAction
from calibre_plugins.interface_demo.main import DemoDialog
class InterfacePlugin(InterfaceAction):
name = 'Interface Plugin Demo'
# Declare the main action associated with this plugin
# The keyboard shortcut can be None if you dont want to use a keyboard
# shortcut. Remember that currently calibre has no central management for
# keyboard shortcuts, so try to use an unusual/unused shortcut.
action_spec = ('Interface Plugin Demo', None,
'Run the Interface Plugin Demo', 'Ctrl+Shift+F1')
def genesis(self):
# This method is called once per plugin, do initial setup here
# Set the icon for this interface action
# The get_icons function is a builtin function defined for all your
# plugin code. It loads icons from the plugin zip file. It returns
# QIcon objects, if you want the actual data, use the analogous
# get_resources builtin function.
#
# Note that if you are loading more than one icon, for performance, you
# should pass a list of names to get_icons. In this case, get_icons
# will return a dictionary mapping names to QIcons. Names that
# are not found in the zip file will result in null QIcons.
icon = get_icons('images/icon.png', 'Interface Demo Plugin')
# The qaction is automatically created from the action_spec defined
# above
self.qaction.setIcon(icon)
self.qaction.triggered.connect(self.show_dialog)
def show_dialog(self):
# The base plugin object defined in __init__.py
base_plugin_object = self.interface_action_base_plugin
# Show the config dialog
# The config dialog can also be shown from within
# Preferences->Plugins, which is why the do_user_config
# method is defined on the base plugin class
do_user_config = base_plugin_object.do_user_config
# self.gui is the main calibre GUI. It acts as the gateway to access
# all the elements of the calibre user interface, it should also be the
# parent of the dialog
d = DemoDialog(self.gui, self.qaction.icon(), do_user_config)
d.show()
def apply_settings(self):
from calibre_plugins.interface_demo.config import prefs
# In an actual non trivial plugin, you would probably need to
# do something based on the settings in prefs
prefs
main.py¶
Arayüz Eklenti Tanıtım iletişim penceresini uygulayan asıl mantıksal kısım.
from qt.core import QDialog, QLabel, QMessageBox, QPushButton, QVBoxLayout
class DemoDialog(QDialog):
def __init__(self, gui, icon, do_user_config):
QDialog.__init__(self, gui)
self.gui = gui
self.do_user_config = do_user_config
# The current database shown in the GUI
# db is an instance of the class LibraryDatabase from db/legacy.py
# This class has many, many methods that allow you to do a lot of
# things. For most purposes you should use db.new_api, which has
# a much nicer interface from db/cache.py
self.db = gui.current_db
self.l = QVBoxLayout()
self.setLayout(self.l)
self.label = QLabel(prefs['hello_world_msg'])
self.l.addWidget(self.label)
self.setWindowTitle('Interface Plugin Demo')
self.setWindowIcon(icon)
self.about_button = QPushButton('About', self)
self.about_button.clicked.connect(self.about)
self.l.addWidget(self.about_button)
self.marked_button = QPushButton(
'Show books with only one format in the calibre GUI', self)
self.marked_button.clicked.connect(self.marked)
self.l.addWidget(self.marked_button)
self.view_button = QPushButton(
'View the most recently added book', self)
self.view_button.clicked.connect(self.view)
self.l.addWidget(self.view_button)
self.update_metadata_button = QPushButton(
'Update metadata in a book\'s files', self)
self.update_metadata_button.clicked.connect(self.update_metadata)
self.l.addWidget(self.update_metadata_button)
self.conf_button = QPushButton(
'Configure this plugin', self)
self.conf_button.clicked.connect(self.config)
self.l.addWidget(self.conf_button)
self.resize(self.sizeHint())
def about(self):
# Get the about text from a file inside the plugin zip file
# The get_resources function is a builtin function defined for all your
# plugin code. It loads files from the plugin zip file. It returns
# the bytes from the specified file.
#
# Note that if you are loading more than one file, for performance, you
# should pass a list of names to get_resources. In this case,
# get_resources will return a dictionary mapping names to bytes. Names that
# are not found in the zip file will not be in the returned dictionary.
text = get_resources('about.txt')
QMessageBox.about(self, 'About the Interface Plugin Demo',
text.decode('utf-8'))
def marked(self):
''' Show books with only one format '''
db = self.db.new_api
matched_ids = {book_id for book_id in db.all_book_ids() if len(db.formats(book_id)) == 1}
# Mark the records with the matching ids
# new_api does not know anything about marked books, so we use the full
# db object
self.db.set_marked_ids(matched_ids)
# Tell the GUI to search for all marked records
self.gui.search.setEditText('marked:true')
self.gui.search.do_search()
def view(self):
''' View the most recently added book '''
most_recent = most_recent_id = None
db = self.db.new_api
for book_id, timestamp in db.all_field_for('timestamp', db.all_book_ids()).items():
if most_recent is None or timestamp > most_recent:
most_recent = timestamp
most_recent_id = book_id
if most_recent_id is not None:
# Get a reference to the View plugin
view_plugin = self.gui.iactions['View']
# Ask the view plugin to launch the viewer for row_number
view_plugin._view_calibre_books([most_recent_id])
def update_metadata(self):
'''
Set the metadata in the files in the selected book's record to
match the current metadata in the database.
'''
from calibre.ebooks.metadata.meta import set_metadata
from calibre.gui2 import error_dialog, info_dialog
# Get currently selected books
rows = self.gui.library_view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
return error_dialog(self.gui, 'Cannot update metadata',
'No books selected', show=True)
# Map the rows to book ids
ids = list(map(self.gui.library_view.model().id, rows))
db = self.db.new_api
for book_id in ids:
# Get the current metadata for this book from the db
mi = db.get_metadata(book_id, get_cover=True, cover_as_data=True)
fmts = db.formats(book_id)
if not fmts:
continue
for fmt in fmts:
fmt = fmt.lower()
# Get a python file object for the format. This will be either
# an in memory file or a temporary on disk file
ffile = db.format(book_id, fmt, as_file=True)
ffile.seek(0)
# Set metadata in the format
set_metadata(ffile, mi, fmt)
ffile.seek(0)
# Now replace the file in the calibre library with the updated
# file. We dont use add_format_with_hooks as the hooks were
# already run when the file was first added to calibre.
db.add_format(book_id, fmt, ffile, run_hooks=False)
info_dialog(self, 'Updated files',
'Updated the metadata in the files of %d book(s)'%len(ids),
show=True)
def config(self):
self.do_user_config(parent=self)
# Apply the changes
self.label.setText(prefs['hello_world_msg'])
Eklenti ZIP dosyasından kaynak alma¶
calibre’nin eklenti yükleme sistemi, eklenti ZIP dosyasından dosyaları kolayca almanızı sağlayan birkaç yerleşik işlevi tanımlar.
- get_resources(isim_veya_isim_listesi)
Bu işlev, ZIP dosyası içindeki dosyaların yollarının listesiyle çağrılmalıdır. Örneğin, ZIP dosyasındaki resimler klasöründeki
icon.png
dosyasına erişmek için şunu kullanırsınız:images/icon.png
. Windows’ta bile her zaman yol ayırıcı olarak eğik çizgi kullanın. Tek bir ad girdiğinizde, işlev o dosyanın ham baytlarını döndürür veya ad ZIP dosyasında bulunamadıysa Yok’u döndürür. Birden fazla ad girerseniz, adları baytlarla eşleştiren bir sözlük döndürür. Bir ad bulunamazsa, döndürülen sözlükte mevcut olmayacaktır.- get_icons(name_or_list_of_names, plugin_name=’’)
get_resources() tarafından döndürülen ham baytlardan QIcon nesneleri oluşturan get_resources() sarmalayıcısı. ZIP dosyasında bir ad bulunamazsa ilgili QIcon boş olacaktır. Simge temasını desteklemek için eklentinizin insan dostu adını
eklenti_adı
olarak girin. Kullanıcı, eklentiniz için simgeler içeren bir simge teması kullanıyorsa, bunlar tercihen yüklenecektir.
Eklentiniz için kullanıcı yapılandırmasını etkinleştirmek¶
Kullanıcıların eklentinizi yapılandırmasına izin vermek için, temel eklenti sınıfınızda aşağıda gösterildiği gibi is_customizable, config_widget ve save_settings olmak üzere üç yöntem tanımlamanız gerekir:
def is_customizable(self):
'''
This method must return True to enable customization via
Preferences->Plugins
'''
return True
def config_widget(self):
'''
Implement this method and :meth:`save_settings` in your plugin to
use a custom configuration dialog.
This method, if implemented, must return a QWidget. The widget can have
an optional method validate() that takes no arguments and is called
immediately after the user clicks OK. Changes are applied if and only
if the method returns True.
If for some reason you cannot perform the configuration at this time,
return a tuple of two strings (message, details), these will be
displayed as a warning dialog to the user and the process will be
aborted.
The base class implementation of this method raises NotImplementedError
so by default no user configuration is possible.
'''
# It is important to put this import statement here rather than at the
# top of the module as importing the config class will also cause the
# GUI libraries to be loaded, which we do not want when using calibre
# from the command line
from calibre_plugins.interface_demo.config import ConfigWidget
return ConfigWidget()
def save_settings(self, config_widget):
'''
Save the settings specified by the user with config_widget.
:param config_widget: The widget returned by :meth:`config_widget`.
'''
config_widget.save_settings()
# Apply the changes
ac = self.actual_plugin_
if ac is not None:
ac.apply_settings()
calibre’nin yapılandırma verisi saklamak için bir çok yöntemi vardır (uzun geçmişinden miras). Önerilen metod JSONConfig sınıfının kullanılmasıdır, bu da yapılandırmanızı bir .json dosyasında tutar.
Tanıtım eklentisindeki yapılandırma verisini yöneten kod config.py’de:
from qt.core import QHBoxLayout, QLabel, QLineEdit, QWidget
# This is where all preferences for this plugin will be stored
# Remember that this name (i.e. plugins/interface_demo) is also
# in a global namespace, so make it as unique as possible.
# You should always prefix your config file name with plugins/,
# so as to ensure you dont accidentally clobber a calibre config file
prefs = JSONConfig('plugins/interface_demo')
# Set defaults
prefs.defaults['hello_world_msg'] = 'Hello, World!'
class ConfigWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.l = QHBoxLayout()
self.setLayout(self.l)
self.label = QLabel('Hello world &message:')
self.l.addWidget(self.label)
self.msg = QLineEdit(self)
self.msg.setText(prefs['hello_world_msg'])
self.l.addWidget(self.msg)
self.label.setBuddy(self.msg)
def save_settings(self):
prefs['hello_world_msg'] = self.msg.text()
prefs
nesnesi şimdi eklenti kodunda basitçe şu şekilde kullanılabilir:
from calibre_plugins.interface_demo.config import prefs
prefs
nesnesinin main.py’de kullanıldığını görebilirsiniz:
self.do_user_config(parent=self)
# Apply the changes
self.label.setText(prefs['hello_world_msg'])
Kitap eklentilerini düzenle¶
Şimdi biraz vites değiştirelim ve calibre kitap düzenleyicisine araçlar eklemek için bir eklenti oluşturmaya bakalım. Eklentiyi burada bulabilirsiniz: editor_demo_plugin.zip.
İlk adım, above de açıklandığı gibi tüm eklentiler için boş bir içe aktarma ismi dosyası oluşturmaktır. Eklentiyi plugin-import-name-editor_plugin_demo.txt
olarak adlandıralım.
Şimdi eklenti metadatasını içeren – ismi, yazarı, sürümü vs. zorunlu __init__.py
dosyasını oluşturuyoruz.
from calibre.customize import EditBookToolPlugin
class DemoPlugin(EditBookToolPlugin):
name = 'Edit Book plugin demo'
version = (1, 0, 0)
author = 'Kovid Goyal'
supported_platforms = ['windows', 'osx', 'linux']
description = 'A demonstration of the plugin interface for the ebook editor'
minimum_calibre_version = (1, 46, 0)
Tek bir düzenleyici eklenti her biri araç çubuğunda bir simgeye ve düzenleyicide Eklentiler menüsünde bir girdiye denk gelmek üzere bir çok araç sağlayabilir. Aracın birden çok eylemi bulunması durumunda alt menüleri de olabilir.
Araçların her biri eklentinizdeki main.py
içinde tanımlanmalıdır. Her araç calibre.gui2.tweak_book.plugin.Tool
sınıfından türetilen bir sınıftır. Tanıtım eklentisindeki main.py
ye bakalım, kaynak kod yoğun olarak yorumlanmış ve kendini anlatabiliyor durumdadır. Daha fazla detay için calibre.gui2.tweak_book.plugin.Tool
sınıfının API belgelerini okuyun.
main.py¶
Burda kitaptaki tüm yazı tipi boyutlarını kullanıcı tarafından sağlanan bir numarayla çarpan tek bir araç tanımı göreceğiz. Bu araç kendi eklentilerinizi geliştirirken ihtiyaç duyacağınız çeşitli önemli kavramı gösterir, yani (yoğun bir şekilde yorumlanmış) kaynak kodu dikkatli okumalısınız.
import re
from calibre import force_unicode
from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, serialize
from calibre.gui2 import error_dialog
# The base class that all tools must inherit from
from calibre.gui2.tweak_book.plugin import Tool
from css_parser.css import CSSRule
from qt.core import QAction, QInputDialog
class DemoTool(Tool):
#: Set this to a unique name it will be used as a key
name = 'demo-tool'
#: If True the user can choose to place this tool in the plugins toolbar
allowed_in_toolbar = True
#: If True the user can choose to place this tool in the plugins menu
allowed_in_menu = True
def create_action(self, for_toolbar=True):
# Create an action, this will be added to the plugins toolbar and
# the plugins menu
ac = QAction(get_icons('images/icon.png'), 'Magnify fonts', self.gui) # noqa
if not for_toolbar:
# Register a keyboard shortcut for this toolbar action. We only
# register it for the action created for the menu, not the toolbar,
# to avoid a double trigger
self.register_shortcut(ac, 'magnify-fonts-tool', default_keys=('Ctrl+Shift+Alt+D',))
ac.triggered.connect(self.ask_user)
return ac
def ask_user(self):
# Ask the user for a factor by which to multiply all font sizes
factor, ok = QInputDialog.getDouble(
self.gui, 'Enter a magnification factor', 'Allow font sizes in the book will be multiplied by the specified factor',
value=2, min=0.1, max=4
)
if ok:
# Ensure any in progress editing the user is doing is present in the container
self.boss.commit_all_editors_to_container()
try:
self.magnify_fonts(factor)
except Exception:
# Something bad happened report the error to the user
import traceback
error_dialog(self.gui, _('Failed to magnify fonts'), _(
'Failed to magnify fonts, click "Show details" for more info'),
det_msg=traceback.format_exc(), show=True)
# Revert to the saved restore point
self.boss.revert_requested(self.boss.global_undo.previous_container)
else:
# Show the user what changes we have made, allowing her to
# revert them if necessary
self.boss.show_current_diff()
# Update the editor UI to take into account all the changes we
# have made
self.boss.apply_container_update_to_gui()
def magnify_fonts(self, factor):
# Magnify all font sizes defined in the book by the specified factor
# First we create a restore point so that the user can undo all changes
# we make.
self.boss.add_savepoint('Before: Magnify fonts')
container = self.current_container # The book being edited as a container object
# Iterate over all style declarations in the book, this means css
# stylesheets, <style> tags and style="" attributes
for name, media_type in container.mime_map.items():
if media_type in OEB_STYLES:
# A stylesheet. Parsed stylesheets are css_parser CSSStylesheet
# objects.
self.magnify_stylesheet(container.parsed(name), factor)
container.dirty(name) # Tell the container that we have changed the stylesheet
elif media_type in OEB_DOCS:
# A HTML file. Parsed HTML files are lxml elements
for style_tag in container.parsed(name).xpath('//*[local-name="style"]'):
if style_tag.text and style_tag.get('type', None) in {None, 'text/css'}:
# We have an inline CSS <style> tag, parse it into a
# stylesheet object
sheet = container.parse_css(style_tag.text)
self.magnify_stylesheet(sheet, factor)
style_tag.text = serialize(sheet, 'text/css', pretty_print=True)
container.dirty(name) # Tell the container that we have changed the stylesheet
for elem in container.parsed(name).xpath('//*[@style]'):
# Process inline style attributes
block = container.parse_css(elem.get('style'), is_declaration=True)
self.magnify_declaration(block, factor)
elem.set('style', force_unicode(block.getCssText(separator=' '), 'utf-8'))
def magnify_stylesheet(self, sheet, factor):
# Magnify all fonts in the specified stylesheet by the specified
# factor.
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
self.magnify_declaration(rule.style, factor)
def magnify_declaration(self, style, factor):
# Magnify all fonts in the specified style declaration by the specified
# factor
val = style.getPropertyValue('font-size')
if not val:
return
# see if the font-size contains a number
num = re.search(r'[0-9.]+', val)
if num is not None:
num = num.group()
val = val.replace(num, '%f' % (float(num) * factor))
style.setProperty('font-size', val)
# We should also be dealing with the font shorthand property and
# font sizes specified as non numbers, but those are left as exercises
# for the reader
main.py
yi kısımlara ayıralım. Magnify fonts adında tek bir araç tanımladığını görüyoruz. Bu araç kullanıcıya bir sayı soracak ve kitaptaki tüm yazı tipi boyutlarını bu sayıyla çarpacak.
İlk önemli nokta, bu araç için anahtar olarak kullanılacağından benzersiz bir karakter dizisi olarak ayarlayacağınız araç ismidir.
Sonraki önemli nokta da calibre.gui2.tweak_book.plugin.Tool.create_action()
. Bu metod eklenti araç çubuğu ve eklenti menüsünde görünen QAction nesnelerini oluşturur. Bunun yanında, isteğe bağlı olarak kullanıcının özelleştirebileceği bir klavye kısayolu atar. QAction tarafından tetiklenen sinyal kullanıcıya yazı tipi boyutu çarpanını soran ask_user() metoduna bağlıdır, bu da çarpma kodunu çalıştırır.
Çarpma kodu iyi belgelenmiş ve oldukça basittir. Dikkate alınacan temel şeyler düzenleyici pencereye self.gui
ile, düzenleyici Boss una self.boss
ile bir referans aldığınızdır. Boss düzenleyici kullanıcı arayüzünü kontrol eden nesnedir. calibre.gui2.tweak_book.boss.Boss
sınıfında belgelenmiş bir çok faydalı metodu vardır.
Son olarak calibre.ebooks.oeb.polish.container.Container
nesnesi olan düzenlenen kitaba referans olan self.current_container
bulunmaktadır. Bu, kitabı, kitabı oluşturan HTML/CSS/resim dosyaları koleksiyonu olarak temsil eder ve daha bir çok faydalı iş yapan metodu vardır. Eklenti kodunuzda tekrar kullanabileceğiniz bir çok kullanışlı fonksiyon ve kapsayıcı nesne E-kitap düzenleme araçları için API belgeleri içinde belgelenmiştir.
Eklentinize çeviriler eklemek¶
Eklentinizdeki tüm kullanıcı arayüzü karakter dizilerini ana calibre kullanıcı arayüzü için hangi dil seçildiyse o dile çevirip gösterebilirsiniz.
İlk adım eklentinizin kaynak kodunun üstünden geçmek ve kullanıcıya görünür tüm metinleri _() içerisine alarak çevrilebilir olarak işaretlemektir. Örneğin:
action_spec = (_('My plugin'), None, _('My plugin is cool'), None)
Daha sonra eklenti kaynak kodunuzdan .po dosyaları oluşturmak için bir program kullanın. Çevirmek istediğiniz her dil için bir .po dosyası bulunmalıdır. Örneğin: Almanca için de.po, Fransızca için fr.po vb. Bunun için Poedit programını kullanabilirsiniz.
Bu .po dosyalarını çevirmenlerinize gönderin. Bunları geri aldıktan sonra .mo dosyaları halinde derleyin. Bunun için tekrar Poedit’i kullanabilir veya şunu yapabilirsiniz:
calibre-debug -c "from calibre.translations.msgfmt import main; main()" filename.po
.mo dosyalarını eklentinizdeki translations
dizinine koyun.
Son adım eklentinizin .py dosyalarının başına load_translations() fonksiyonunu eklemektir. Performans açısından bu fonksiyonu yalnızca çevrilebilir karakter dizilerinin olduğu dosyalara eklemelisiniz. Yani tipik bir kullanıcı arayüzü eklentisinde ui.py
başında çağırırken ``__init__.py``de çağırmazsınız.
Eklentilerinizin çevirilerini calibre’de Tercihler → Arayüz → Görünüm ve his altında kullanıcı arayüzü dilini değiştirerek veya calibre’yi CALIBRE_OVERRIDE_LANG
ortam değişken seti ile çalıştırarak test edebilirsiniz. Örneğin:
CALIBRE_OVERRIDE_LANG=de
de
yi test etmek istediğiniz dil koduyla değiştirin.
Çoğul içeren çeviriler için _()
yerine ngettext()
işlevini kullanın. Örneğin:
ngettext('Delete a book', 'Delete {} books', num_books).format(num_books)
Eklenti API’si¶
Yukarda fark etmiş olabileceğiniz gibi, calibre’de eklenti bir sınıftır. Calibre’de değişik türde eklentiler için değişik sınıflar bulunur. Tüm eklentiler için temel sınıf dahil her sınıfla ilgili detay Eklentiler için API belgeleri içinde bulunabilir.
Eklentiniz çoğu durumda calibre’den de kod kullanacaktır. calibre kod tabanındaki fonksiyonellikle ilgili çeşitli bilgiler bulmak için, calibre’deki Kod düzeni kısmını okuyun.
Eklentilerde hata ayıklama¶
İlk, en önemli adım calibre’yi hata ayıklama kipinde çalıştırmaktır. Bu işlemi komut satırından şu şekilde yapabilirsiniz:
calibre-debug -g
Veya calibre içinden Tercihler düğmesine sağ tıklayarak veya Ctrl+Shift+R klavye kısayolunu kullanarak.
Komut satırından çalıştırırken, hata ayıklama çıktısı konsola yazdırılacaktır, calibre içinden çalıştırılırken çıktı bir txt dosyasına gider.
Yazdırma ifadelerini eklenti kodunuzun herhangi bir yerine ekleyebilirsiniz, bunlar hata ayıklama modunda yayınlanacaktır. Unutmayın, bu Python, hata ayıklamak için print ifadelerinden daha fazlasına ihtiyacınız olmamalı ;) Calibre’nin tamamını sadece bu hata ayıklama tekniğini kullanarak geliştirdim.
Eklentinize yaptığınızı değişiklikleri hızlıca şu komut satırıyla test edebilirsiniz:
calibre-debug -s; calibre-customize -b /path/to/your/plugin/folder; calibre
Bu işlem calibre’yi kapatır, kapanmanın bitmesini bekler, eklentinizi calibre’de günceller ve calibre’yi baştan başlatır.
Daha fazla eklenti örneği¶
Birçok gelişmiş Calibre eklentisinin listesini burada <https://www.mobileread.com/forums/showthread.php?t=118764>`_ bulabilirsiniz.