View Single Post
Old 04-27-2024, 05:54 PM   #87
capink
Wizard
capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.
 
Posts: 1,108
Karma: 1954138
Join Date: Aug 2015
Device: Kindle
Quote:
Originally Posted by thiago.eec View Post
Thank you very much. This custom action gave me full control of the editor!

Now, one last question:

I've always wanted to automate merging splitted files (filename_split0001.xhtml, filename_split0002.xhtml, etc). With the custom action you provided, I can access the 'Merge files' action, but I didn't find a way to select the files based on a regular expression, just like when using 'Search and replace' and the filename filter.

Is it possible?
Not without an action to convert scopes to selection. That one if fairly straight forward:

Code:
from qt.core import (QWidget, QVBoxLayout, QGroupBox)

from calibre_plugins.editor_chains.actions.base import EditorAction
from calibre_plugins.editor_chains.scope import scope_names, ScopeWidget, validate_scope, scope_is_headless

class ConfigWidget(QWidget):
    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        self._init_controls()

    def _init_controls(self):

        l = self.l = QVBoxLayout()
        self.setLayout(l)

        scope_groupbox = QGroupBox(_('Files to select'))
        l.addWidget(scope_groupbox)
        scope_l = QVBoxLayout()
        scope_groupbox.setLayout(scope_l)
        self.where_box = ScopeWidget(self, self.plugin_action, orientation='vertical',
                                     headless=self.plugin_action.gui is None,
                                     hide=['selected-text','open','current'])
        scope_l.addWidget(self.where_box)

        l.addStretch(1)
        self.setMinimumSize(400,200)

    def load_settings(self, settings):
        if settings:
            self.where_box.where = settings['where']

    def save_settings(self):
        settings = {}
        settings['where'] = self.where_box.where
        return settings

class SelectFiles(EditorAction):

    name = 'Select Files'
    headless = True

    def run(self, chain, settings, *args, **kwargs):
        names = scope_names(chain, settings['where'])
        chain.gui.file_list.select_names(names)

    def validate(self, settings):
        scope_ok = validate_scope(settings['where'])
        if scope_ok is not True:
            return scope_ok
        return True

    def config_widget(self):
        return ConfigWidget

    def is_headless(self, settings):
        return scope_is_headless(settings['where'])
The action above will select files based on filters. Note however, that merge in an interactive step which will require you to select the master file into which all other files will be merged.

Calibre uses a function called merge() to merge files. You can build an action around this function:

Code:
from qt.core import (QWidget, QVBoxLayout, QGroupBox)

from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
from calibre.ebooks.oeb.polish.split import merge
from calibre.gui2.tweak_book import editors

from calibre_plugins.editor_chains.actions.base import EditorAction
from calibre_plugins.editor_chains.scope import scope_names, ScopeWidget, validate_scope, scope_is_headless


def spine_index(container, name):
    idx = 0
    for spine_name, is_linear in container.spine_names:
        idx +=1
        if name == spine_name:
            return idx

class ConfigWidget(QWidget):
    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        self._init_controls()

    def _init_controls(self):

        l = self.l = QVBoxLayout()
        self.setLayout(l)

        scope_groupbox = QGroupBox(_('Files to merge'))
        l.addWidget(scope_groupbox)
        scope_l = QVBoxLayout()
        scope_groupbox.setLayout(scope_l)
        self.where_box = ScopeWidget(self, self.plugin_action, orientation='vertical',
                                     headless=self.plugin_action.gui is None,
                                     hide=['selected-text','open','current'])
        scope_l.addWidget(self.where_box)

        l.addStretch(1)
        self.setMinimumSize(400,200)

    def load_settings(self, settings):
        if settings:
            self.where_box.where = settings['where']

    def save_settings(self):
        settings = {}
        settings['where'] = self.where_box.where
        return settings

class MergeFiles(EditorAction):

    name = 'Merge Files'
    headless = True

    def run(self, chain, settings, *args, **kwargs):
        names = scope_names(chain, settings['where'])
        print(f'DEBUG: Merging Files\n       names: {names}')
        if len(names) < 2:
            print('Error: You need to select at least 2 files to merge')
            return
        container = chain.current_container
        spine_names = [name for name, is_linear in container.spine_names]
        if names[0] in set(container.manifest_items_of_type(OEB_DOCS)):
            category = 'text'
        elif names[0] in set(container.manifest_items_of_type(OEB_STYLES)):
            category = 'styles'
        else:
            print('Error: Can only merge files of categories: text or styles')
            return
        if category == 'text':
            if not set(names).issubset(set(container.manifest_items_of_type(OEB_DOCS))):
                print('Error: some files are your trying to merge are not of category text')
                return
            if not set(names).issubset(set(spine_names)):
                print('Error: Some files you are trying to merge not in spine')
                return
            names = sorted(names, key=lambda name: spine_index(container, name))
        else:
            if not set(names).issubset(set(container.manifest_items_of_type(OEB_STYLES))):
                print('Error: some files are your trying to merge are not of category styles')
                return
            names = sorted(names)
        master = names[0]
        print(f'DEBUG: Merging Files\n       master: {master}')
        merge(container, category, names, master)
        if master in editors:
            chain.boss.show_editor(master)

    def validate(self, settings):
        scope_ok = validate_scope(settings['where'])
        if scope_ok is not True:
            return scope_ok
        return True

    def config_widget(self):
        return ConfigWidget

    def is_headless(self, settings):
        return scope_is_headless(settings['where'])
Notes:
  • The action above will merge text files according to their spine index. The master file into which others will be merged is the one with the lowest spine index. Style files are sorted alphabetically.
  • This action is provided without any guarantee that it will work properly. I have not tested it much. You must test vigorously to ensure it merges files in correct order before accepting the results it provides
capink is offline   Reply With Quote