diff --git a/src/calibre/devices/mtp/defaults.py b/src/calibre/devices/mtp/defaults.py index db3c9400e0..f79c25dd4c 100644 --- a/src/calibre/devices/mtp/defaults.py +++ b/src/calibre/devices/mtp/defaults.py @@ -26,6 +26,7 @@ class DeviceDefaults: 'format_map': ['azw3', 'mobi', 'azw', 'azw1', 'azw4', 'kfx', 'pdf'], 'send_to': ['documents', 'kindle', 'books'], + 'apnx': {'send': True, 'method': 'fast', 'custom_column_page_count': None, 'custom_column_method': None} } ), # B&N devices diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index cba7775468..a92ad011df 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -86,6 +86,12 @@ class MTP_DEVICE(BASE): p.defaults['history'] = {} p.defaults['rules'] = [] p.defaults['ignored_folders'] = {} + p.defaults['apnx'] = { + 'send': False, + 'method': 'fast', + 'custom_column_page_count': None, + 'custom_column_method': None, + } return self._prefs @@ -544,6 +550,11 @@ class MTP_DEVICE(BASE): mtp_file = self.put_file(parent, path[-1], stream, sz) try: self.upload_cover(parent, relpath, storage, mi, stream) + # Upload the apnx file + if self.is_kindle and self.get_pref('apnx').get('send', False): + name = path[-1].rpartition('.')[0] + debug('Uploading APNX file for', name) + self.upload_apnx(parent, name, storage, mi, infile) except Exception: import traceback traceback.print_exc() @@ -632,6 +643,58 @@ class MTP_DEVICE(BASE): debug(f'Restored {count} cover thumbnails that were destroyed by Amazon') # }}} + def upload_apnx(self, parent, name, storage, mi, filepath): + debug('upload_apnx() called') + from calibre.devices.kindle.apnx import APNXBuilder + from calibre.ptempfile import PersistentTemporaryFile + + apnx_local_file = PersistentTemporaryFile('.apnx') + apnx_local_path = apnx_local_file.name + apnx_local_file.close() + + try: + pref = self.get_pref('apnx') + + custom_page_count = 0 + cust_col_name = pref.get('custom_column_page_count', None) + if cust_col_name: + try: + custom_page_count = int(mi.get(cust_col_name, 0)) + except Exception: + pass + + method = pref.get('method', 'fast') + + cust_col_method = pref.get('custom_column_method', None) + if cust_col_method: + try: + method = str(mi.get(cust_col_method)).lower() + if method is not None: + method = method.lower() + if method not in ('fast', 'accurate', 'pagebreak'): + method = None + except Exception: + prints(f'Invalid custom column method: {cust_col_method}, ignoring') + + apnx_builder = APNXBuilder() + apnx_builder.write_apnx(filepath, apnx_local_path, method=method, page_count=custom_page_count) + + apnx_size = os.path.getsize(apnx_local_path) + + with open(apnx_local_path, 'rb') as apnx_stream: + apnx_filename = f'{name}.apnx' + apnx_path = parent.name, f'{name}.sdr', apnx_filename + sdr_parent = self.ensure_parent(storage, apnx_path) + self.put_file(sdr_parent, apnx_filename, apnx_stream, apnx_size) + + except Exception: + print('Failed to generate APNX') + import traceback + traceback.print_exc() + finally: + os.remove(apnx_local_path) + debug('upload_apnx() ended') + def add_books_to_metadata(self, mtp_files, metadata, booklists): debug('add_books_to_metadata() called') from calibre.devices.mtp.books import Book diff --git a/src/calibre/gui2/device_drivers/mtp_config.py b/src/calibre/gui2/device_drivers/mtp_config.py index 2800c3e8cd..5d00077942 100644 --- a/src/calibre/gui2/device_drivers/mtp_config.py +++ b/src/calibre/gui2/device_drivers/mtp_config.py @@ -31,6 +31,7 @@ from qt.core import ( QVBoxLayout, QWidget, pyqtSignal, + QCheckBox, ) from calibre.ebooks import BOOK_EXTENSIONS @@ -351,6 +352,51 @@ class FormatRules(QGroupBox): yield r # }}} +class APNX(QWidget): + def __init__(self, apnx): + QWidget.__init__(self) + self.layout = l = QVBoxLayout() + self.setLayout(l) + + self.layout.setAlignment(Qt.AlignTop) + + self.send = f1 = QCheckBox(_('Send page number information when sending books')) + f1.setChecked(apnx.get('send', False)) + l.addWidget(f1) + + label2 = QLabel('

' + _('Page count calculation method') + '

') + label2.setWordWrap(True) + l.addWidget(label2) + self.method = f2 = QComboBox(self) + f2.addItem('fast', 'fast') + f2.addItem('accurate', 'accurate') + f2.addItem('pagebrek', 'pagebreak') + f2.setCurrentIndex(f2.findText(apnx.get('method', 'fast'))) + l.addWidget(f2) + + label3 = QLabel('

' + _('Custom column name to retrieve page counts from') + '

') + label3.setWordWrap(True) + l.addWidget(label3) + self.column_page_count = f3 = QLineEdit(self) + f3.setText(apnx.get('custom_column_page_count', '')) + l.addWidget(f3) + + label4 = QLabel('

' + _('Custom column name to retrieve calculation method from') + '

') + label4.setWordWrap(True) + l.addWidget(label4) + self.column_method = f4 = QLineEdit(self) + f4.setText(apnx.get('custom_column_method', '')) + l.addWidget(f4) + + @property + def apnx(self): + result = { + 'send': self.send.isChecked(), + 'method': str(self.method.currentData()).strip(), + 'custom_column_page_count': str(self.column_page_count.text()).strip(), + 'custom_column_method': str(self.column_method.text()).strip(), + } + return result class MTPConfig(QTabWidget): @@ -421,6 +467,10 @@ class MTPConfig(QTabWidget): l.addWidget(r, 7, 0, 1, 2) l.setRowStretch(7, 100) + if self.device.is_kindle: + self.apnxTab = APNX(self.get_pref('apnx')) + self.addTab(self.apnxTab, _('Page numbering (APNX)')) + self.igntab = IgnoredDevices(self.device.prefs['history'], self.device.prefs['blacklist']) self.addTab(self.igntab, _('Ignored devices')) @@ -509,6 +559,9 @@ class MTPConfig(QTabWidget): if self.current_ignored_folders != self.initial_ignored_folders: p['ignored_folders'] = self.current_ignored_folders + p.pop('apnx', None) + p['apnx'] = self.apnxTab.apnx + if self.current_device_key is not None: self.device.prefs[self.current_device_key] = p