Allow import of KFX files from MTP Kindle devices rev 2

This commit is contained in:
j-howell 2025-01-30 08:01:29 -05:00
parent ea4aebf878
commit 45d7c87462
2 changed files with 43 additions and 1 deletions

View File

@ -114,8 +114,9 @@ class MTP_DEVICE(BASE):
'pictures', 'ringtones', 'samsung', 'sony', 'htc', 'bluetooth', 'fonts',
'games', 'lost.dir', 'video', 'whatsapp', 'image', 'com.zinio.mobile.android.reader'}:
return True
if lpath[0].startswith('.') and lpath[0] != '.tolino':
if lpath[0].startswith('.') and lpath[0] != '.tolino' and lpath[0] != '.notebooks':
# apparently the Tolino for some reason uses a hidden folder for its library, sigh.
# Kindle Scribe stores user notebooks in subfolders of '.notebooks'
return True
if lpath[0] == 'system' and not self.is_kindle:
# on Kindles we need the system/thumbnails folder for the amazon cover bug workaround
@ -349,6 +350,21 @@ class MTP_DEVICE(BASE):
from calibre.customize.ui import quick_metadata
from calibre.ebooks.metadata.meta import get_metadata
ext = mtp_file.name.rpartition('.')[-1].lower()
if self.is_kindle and ext == 'kfx' and mtp_file.name != 'metadata.kfx':
# locate the actual file containing KFX book metadata
stream = BytesIO()
try:
self.get_file_by_name(stream, mtp_file.parent, *(mtp_file.name[:-4] + '.sdr', 'assets', 'metadata.kfx'))
except Exception:
pass # expect failure for sideloaded books and personal documents
else:
stream.name = 'metadata.kfx'
with quick_metadata:
metadata = get_metadata(stream, stream_type=ext,
force_read_metadata=True,
pattern=self.build_template_regexp())
if metadata.title != 'metadata' and metadata.title != 'Unknown':
return metadata
stream = self.get_mtp_file(mtp_file)
with quick_metadata:
return get_metadata(stream, stream_type=ext,
@ -417,7 +433,26 @@ class MTP_DEVICE(BASE):
ans.append((path, e, traceback.format_exc()))
else:
ans.append(out.name)
if self.is_kindle and path.endswith('.kfx'):
# copy additional KFX files from the associated .sdr folder
try:
sdr_folder_name = f.name[:-4] + '.sdr'
new_sdr_path = os.path.join(os.path.split(out.name)[0], sdr_folder_name)
os.mkdir(new_sdr_path)
self.scan_sdr_for_kfx_files(f.parent, (sdr_folder_name, 'assets'), new_sdr_path)
except Exception:
traceback.print_exc()
return ans
def scan_sdr_for_kfx_files(self, parent, folders, new_sdr_path):
for ff in self.list_folder_by_name(parent, *folders):
if ff.is_folder:
self.scan_sdr_for_kfx_files(parent, folders + (ff.name,), new_sdr_path)
elif ff.name.endswith('.kfx') or ff.name == 'voucher':
name = ''.join(shorten_components_to(245 - len(new_sdr_path), [ff.name])) if iswindows else ff.name
with open(os.path.join(new_sdr_path, name), 'wb') as out:
self.get_file_by_name(out, parent, *(folders + (ff.name,)))
# }}}
# Sending files to the device {{{

View File

@ -97,6 +97,13 @@ class FileOrFolder:
self.is_ebook = (not self.is_folder and not self.is_storage and
self.name.rpartition('.')[-1].lower() in bexts and not self.name.startswith('._'))
# allow Kindle Scribe notebooks to be imported and preserve the notebook name
if self.name == 'nbk' and not (self.is_folder or self.is_storage):
if len(self.full_path) >= 3 and self.full_path[-3] == '.notebooks':
nbk_name = self.full_path[-2].replace('!!notebook', '').replace('!!', ' ')
self.name = f'Scribe {nbk_name} Notebook.kfx'
self.is_ebook = True
def __repr__(self):
if self.is_storage:
name = 'Storage'