-
Notifications
You must be signed in to change notification settings - Fork 1
Создание собственных плагинов
Вы можете расширять возможности Libro2 с помощью собственных плагинов, написанных на языке Python. Плагины дают возможность пакетной обработки метаданных файлов в форматах fb2 и epub (версии 2 и 3), а также самих файлов (копирование, переименование, архивация и проч.). Вам не нужно дополнительно устанавливать Python - Libro2 уже содержит встроенный интерпретатор, а также предоставляет ряд модулей для написания плагинов.
Файл плагина с расширением .py должен размещаться в папке %USERPROFILE%\libro2\plugins для Microsoft Windows или ~/.libro2/plugins для MacOS и Linux. Libro2 при запуске загружает файлы, расположенные в этой папке и успешно загруженные плагины помещает в меню "Инструменты".
Для запуска плагина загрузите в программу файлы fb2 или epub, выделите в списке необходимые файлы и запустите нужный плагин из меню "Инструменты" - отобразится форма запуска плагина. Если плагин имеет дополнительные параметры, на этой форме отразятся соответствующие поля для ввода дополнительных параметров. После нажатия кнопки "OK" Libro2 запустит выбранный плагин для каждого файла из выбранного списка. В момент обработки на экране будет отображаться ее прогресс, по окончании будет выведен перечень ошибок или сообщений плагина для выбранного списка файлов.
Как выше уже было сказано, файл плагина должен размещаться в соответствующей папке и иметь расширение .py. Программный код плагина пишется на языке Python с использованием системных и специализированных модулей, поставляемых в составе Libro2. Полный перечень поддерживаемых модулей см. в разделе "Список поддерживаемых модулей".
Плагин должен содержать один класс типа MetaPlugin или FilePlugin
from plugin_collection import MetaPlugin
class MyPlugin(MetaPlugin):
...
В классе должны быть определены два метода:
- init - метод инициализации системных параметров, дополнительных параметров и атрибутов плагина
- perform_operation - метод, содержащий реализацию непосредственной фунциональности плагина.
Также дополнительно класс может содержать реализацию метода validate.
Порядок вызова методов следующий. Метод init вызывается каждый раз при вызове плагина, непосредственно перед отображением формы плагина. Метод validate вызывается после изменения значения любого специального атрибута и нажатия кнопки OK на форме плагина (детали см. ниже). Метод perform_operation вызывается последовательно для каждого файла из выделенных в списке файлов.
В методе init должны быть установлены значения следующих системных параметров плагина:
- _title - название плагина, которое помещается в меню "Инструменты",
- _description - необязательное описание плагина,
- _hotkey - "горячая" клавиша, с помощью которой можно запустить плагин (необязательный атрибут),
- _is_context_menu - помещать ли плагин в контекстное меню списка файлов (необязательный атрибут).
Также здесь могут быть определены дополнительные атрибуты класса плагина (см. ниже).
from plugin_collection import MetaPlugin
class MyPlugin(MetaPlugin):
def init(self):
self._title = 'My plugin title'
self._description = 'My plugin description'
self._hotkey = 'Ctrl-J'
self._is_context_menu = True
...
Метод perform_operation является входной точкой для запуска плагина. Входные и выходные параметры определяются типом базового класса плагина.
Для класса типа MetaPlugin perform_operation получает на вход экземпляр класса ebookmeta.Metadata, содержащий метаданные обрабатываемого файла. Возвращаемое значение - также экземпляр класса ebookmeta.Metadata, содержащий измененные метаданные обрабатываемого файла. Полученные метаданные Libro2 самостоятельно записывает в обрабатываемый файл. Если в результате обработки не требуется сохранять изменение, метод должен вернуть None.
from plugin_collection import MetaPlugin
class MyPlugin(MetaPlugin):
def init(self):
self._title = 'My plugin title'
def perform_operation(self, meta):
meta.title = 'New book title'
return meta
...
Для класса типа FilePlugin метод preform_operation получает на вход полное имя обрабатываемого файла. Возвращаемое значение - новое имя файла или None, если не требуется обработка. После завершения работы плагина Libro2 заменит в списке обрабатываемый файл на полученный и перечитает метаданные.
from plugin_collection import MetaPlugin
import shutil
class MyPlugin(MetaPlugin):
def init(self):
self._title= 'My plugin title'
def perform_operation(self, filename):
new_filename = 'file1.fb2'
shutil.copy(filename, new_filename)
return new_filename
...
В методе init класса, описывающего плагин вы можете определять собственные атрибуты. Поддерживаются все типы данных Python. Например:
from plugin_collection import FilePlugin
def init(self):
self._title = 'My plugin title'
self.my_str_attr = 'Some value'
self.my_int_attr = 1
Также вы можете определить специальные атрибуты, значения которых можно вводить на форме запуска плагина. Каждому типу параметра соответствует свой элемент ввода.
Поддерживаются следующие типы специальных атрибутов:
- ТextField - на форме отражается поле ввода,
- BoolField - на форме отражается checkbox,
- ChoiceField - на форме отражается выпадающий список (combobox),
- FolderField - на форме отражается элемент для выбора каталога,
- FileField - на форме отражается элемент для выбора файла.
Инициализация дополнительных параметров производится в методе init. Также из модуля plugin_collection необходимо импортировать соответствующие типы для описания специальных атрибутов. Введенные на форме значения внутри методов preform_operation и validate можно получить, обратившись к свойству value соответствующего атрибута:
from plugin_collection import FilePlugin, TextField, BoolField
class MyPlugin(FilePlugin):
def init(self):
self._title = 'My plugin title'
self.bool_attr = BoolField(label='Bool attribute label')
self.bool_attr.value = False
self.text_attr = TextField(label='Text attribute label')
self.text_attr.value = 'Some text'
def preform_operation(self, file):
if self.bool_attr.value:
new_file_name = self.text_attr.value
...
Все типы специальных атрибутов имеют следующие свойства (все свойства доступны для записи и чтения):
- value - значение атрибута.
- label - метка поля ввода, связанного с данным атрибутом.
- enabled - данное свойство позволяет управлять доступностью связанного с атрибутом поля ввода формы.
- visible - данное свойство позволяет управлять видимостью связанного с атрибутом поля ввода формы.
Кроме того, тип ChoiceField имеет свойство items, содержащий список доступных для выбора элементов.
Данный метод позволяет реализовать проверку введенных на форме плагина значений специальных атрибутов, а также позволяет управлять поведением элементов формы в зависимости от свойств связанных атрибутов.
Метод validate вызывается после изменения значения в поле ввода формы, а также после того, как пользователь нажал кнопку "OK".
Метод имеет один параметр source, содержащий ссылку на атрибут, значение которого было изменено на форме, либо None, если была нажата кнопка "OK".
from plugin_collection import FilePlugin, BoolField, FolderField, DebugException
class MyPlugin(FilePlugin):
def init(self):
self._title = 'Тестовый плагин'
# Объявляем специальные атрибуты, которые будут отражаться на форме
self.is_copy_to_folder = BoolField(label='Копировать файлы в папку')
self.folder_to_copy = FolderField(label='Папка для копирования')
# Инициализируем начальное значение
# (на форме связанный с атрибутом чекбокс будет выключен
self.is_copy_to_folder.value = False
# На форме связанное поле будет недоступно для ввода
self.folder_to_copy.enabled = False
...
def validate(self, source):
if source == self.is_copy_to_folder:
# Пользователь поставил отметку в чекбоксе -
# открываем folder_to_copy на редактирование
self.folder_to_copy.enabled = True
elif source is None:
# Нажали кнопку OK - самое время проверить введенные на форме значения
if not self.folder_to_copy.value and self.is_copy_to_folder.value:
raise DebugException('Вы не указали папку для копирования!')
def preform_operation(self, file):
...
Вы можете сохранять и в последующем восстанавливать введенные значения дополнительных параметров в системных настройка Libro2. Для этого существуют соответствующие методы save_settings и load_settings:
from plugin_collection import FilePlugin, TextField, DebugException
class MyPlugin(FilePlugin):
def init(self):
self._title = 'My plugin title'
self.param1 = TextField(label='Label for field')
# Чтение значения из настроек
self.param1.value = self.load_settings('MyPlugin.Param1', default_value='Param1 value')
...
def validate(self, source):
...
if source is None:
# Запись значения в настройки. Запись обязательно делать в секции source is None!
self.save_settings('MyPlugin1.Param1', value=self.param1.value)
def preform_operation(self, file):
...
Плагин удаляет отчества у всех авторов книги
from plugin_collection import MetaPlugin
from config import locale
class RemoveAuthorMiddleName(MetaPlugin):
def init(self):
if locale == 'ru_RU':
self._title = 'Удалить отчество у автора'
self._description = ('Некотрые предпочитают в библиотеке видеть '
'автора в формате "Имя Фамилия" вместо '
'"Иия Отчество Фамилия". Например, Виктор '
'Пелевин вместо Виктор Олегович Пелевин. '
'Данный плагин удаляет отчество у автора, '
'если оно присутствует.')
else:
self._title = 'Remove author\'s middle name'
self._description = ('Remove middle name for each author'
'in authors list')
def perform_operation(self, meta):
new_authors = []
for author in meta.author_list:
author_part = author.split()
if len(author_part) == 3: # Author have middle name
new_author = author_part[0] + ' ' + author_part[2]
new_authors.append(new_author)
else:
new_authors.append(author)
meta.set_author_list_from_string(','.join(new_authors))
return meta
Плагин автоматически нумерует серию для выбранных книг. Нумерация выполняется в соответствии с установленным порядком сортировки в программе
from plugin_collection import MetaPlugin, DebugException, TextField
from config import locale
class SeriesIndexer(MetaPlugin):
def init(self):
default_title = self.load_settings('SeriesIdx.Title', default_value='')
start_index = self.load_settings('SeriesIdx.StartValue',
default_value='1')
self.series_title = TextField()
self.series_index_start = TextField()
self.series_title.value = default_title
self.series_index_start.value = int(start_index)
if locale == 'ru_RU':
self._title = 'Автоматическая нумерация серии'
self._description = ('Плагин нумерует серию в выбранных книгах '
'согласно сортировке в списке')
self.series_title.label = 'Название серии (опционально)'
self.series_index_start.label = 'Начинать индексирование с'
else:
self._title = 'Series index generator'
self._description = 'Generate series index in book sort order'
self.series_title.label = 'Series title (optional)'
self.series_index_start.label = 'Series index start value'
def validate(self, source):
if source is None:
try:
self.start_index = int(self.series_index_start.value)
self.save_settings('SeriesIdx.Title',
value=self.series_title.value)
self.save_settings('SeriesIdx.StartValue',
value=self.start_index)
except ValueError:
if locale == 'ru_RU':
raise DebugException(
'Неверное значение начального индекса!')
else:
raise DebugException('Wrong series index start value!')
def perform_operation(self, meta):
if self.series_title.value:
meta.series = self.series_title.value
meta.series_index = self.start_index
self.start_index += 1
return meta
Плагин архивирует файлы .fb2 в .fb2.zip
from plugin_collection import FilePlugin, DebugException, BoolField
from ebookmeta.myzipfile import ZipFile, ZIP_DEFLATED
import os
from config import locale
class ZipFb2(FilePlugin):
def init(self):
self._hotkey = 'Ctrl+Z'
self._is_context_menu = True
self.delete_source = BoolField()
self.delete_source.value = False
if locale == 'ru_RU':
self._title = 'Упаковать fb2 в fb2.zip'
self._description = ('Преобразовывает файлы fb2 в формат fb2.zip. '
'Если указано, после преобразования исходный '
'fb2 удаляется.')
self.delete_source.label = 'Удалить исходные файлы после архивации'
else:
self._title = 'Zip fb2 into fb2.zip'
self._description = ('Zip fb2 files into fb2.zip, '
'then delete original fb2, if specified.')
self.delete_source.label = 'Delete original fb2 after zipping'
def perform_operation(self, file):
if file.lower().endswith('.fb2'):
file_path = os.path.dirname(file)
file_name = os.path.basename(file)
zip_name = file + '.zip'
if not os.path.exists(zip_name):
os.chdir(file_path)
zip = ZipFile(zip_name, mode='w', compression=ZIP_DEFLATED)
zip.write(file_name)
zip.close()
if self.delete_source.value:
os.remove(file)
return zip_name
else:
if locale == 'ru_RU':
raise DebugException(f'Файл "{zip_name}" уже существует!')
else:
raise DebugException(f'File "{zip_name}" already exist!')
else:
if file.lower().endswith('.fb2.zip'):
if locale == 'ru_RU':
raise DebugException(f'"{file}" уже упакован в fb2.zip.')
else:
raise DebugException(f'"{file}" zipped already.')
else:
if locale == 'ru_RU':
raise DebugException(f'"{file}" не является файлом fb2!')
else:
raise DebugException(f'"{file}" not fb2 file!')
- os - модуль содержит интерфейсы для различных функций операционной системы. Наиболее употребительным является подмодуль os.path, содержащий функции по работе с файлами.
- sys - модуль содержит системные параметры и функции.
- shutil - набор высокоуровневых файловых операций - копирование, перемещение, каскадное удаление и др.
- lxml - модуль для работы с xml и html.
- json - модуль для работы с json
- codecs - модуль содержит набор классов для работы с различными кодировками. В основном используется для чтения/записи файлов в кодировках с поддержкой кириллицы.
- traceback - модуль для трассировки исключений. Используется для получения отладочной информации.