From ff9e1ed7c8d2a810c0ddd371fffbe029724b32d9 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 24 Feb 2024 10:57:10 +0100 Subject: [PATCH] Implemented embed metadata on send to ereader --- cps/admin.py | 3 ++- cps/embed_helper.py | 55 ++++++++++++++++++++++++++++++++++++++++++++ cps/helper.py | 37 +++-------------------------- cps/tasks/convert.py | 1 + cps/tasks/mail.py | 54 +++++++++++++++---------------------------- 5 files changed, 80 insertions(+), 70 deletions(-) create mode 100644 cps/embed_helper.py diff --git a/cps/admin.py b/cps/admin.py index fa29759e..9b504094 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -47,7 +47,8 @@ from . import constants, logger, helper, services, cli_param from . import db, calibre_db, ub, web_server, config, updater_thread, gdriveutils, \ kobo_sync_status, schedule from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \ - valid_email, check_username, get_calibre_binarypath + valid_email, check_username +from .embed_helper import get_calibre_binarypath from .gdriveutils import is_gdrive_ready, gdrive_support from .render_template import render_title_template, get_sidebar_config from .services.worker import WorkerThread diff --git a/cps/embed_helper.py b/cps/embed_helper.py new file mode 100644 index 00000000..f5c3a084 --- /dev/null +++ b/cps/embed_helper.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) +# Copyright (C) 2024 OzzieIsaacs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +from uuid import uuid4 +import os + +from .file_helper import get_temp_dir +from .subproc_wrapper import process_open +from . import logger, config +from .constants import SUPPORTED_CALIBRE_BINARIES + +log = logger.create() + +def do_calibre_export(book_id, book_format): + try: + quotes = [3, 5, 7, 9] + tmp_dir = get_temp_dir() + calibredb_binarypath = get_calibre_binarypath("calibredb") + temp_file_name = str(uuid4()) + opf_command = [calibredb_binarypath, 'export', '--dont-write-opf', '--with-library', config.config_calibre_dir, + '--to-dir', tmp_dir, '--formats', book_format, "--template", "{}".format(temp_file_name), + str(book_id)] + p = process_open(opf_command, quotes) + _, err = p.communicate() + if err: + log.error('Metadata embedder encountered an error: %s', err) + return tmp_dir, temp_file_name + except OSError as ex: + # ToDo real error handling + log.error_or_exception(ex) + return None, None + +def get_calibre_binarypath(binary): + binariesdir = config.config_binariesdir + if binariesdir: + try: + return os.path.join(binariesdir, SUPPORTED_CALIBRE_BINARIES[binary]) + except KeyError as ex: + log.error("Binary not supported by Calibre-Web: %s", SUPPORTED_CALIBRE_BINARIES[binary]) + pass + return "" diff --git a/cps/helper.py b/cps/helper.py index 975a2523..33da0f37 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -28,7 +28,6 @@ from datetime import datetime, timedelta import requests import unidecode from uuid import uuid4 -from lxml import etree from flask import send_from_directory, make_response, redirect, abort, url_for from flask_babel import gettext as _ @@ -56,13 +55,14 @@ from .tasks.convert import TaskConvert from . import logger, config, db, ub, fs from . import gdriveutils as gd from .constants import STATIC_DIR as _STATIC_DIR, CACHE_TYPE_THUMBNAILS, THUMBNAIL_TYPE_COVER, THUMBNAIL_TYPE_SERIES, SUPPORTED_CALIBRE_BINARIES -from .subproc_wrapper import process_wait, process_open +from .subproc_wrapper import process_wait from .services.worker import WorkerThread from .tasks.mail import TaskEmail from .tasks.thumbnail import TaskClearCoverThumbnailCache, TaskGenerateCoverThumbnails from .tasks.metadata_backup import TaskBackupMetadata from .file_helper import get_temp_dir from .epub_helper import get_content_opf, create_new_metadata_backup, updateEpub, replace_metadata +from .embed_helper import do_calibre_export log = logger.create() @@ -225,7 +225,7 @@ def send_mail(book_id, book_format, convert, ereader_mail, calibrepath, user_id) email_text = N_("%(book)s send to eReader", book=link) WorkerThread.add(user_id, TaskEmail(_("Send to eReader"), book.path, converted_file_name, config.get_mail_settings(), ereader_mail, - email_text, _('This Email has been sent via Calibre-Web.'))) + email_text, _('This Email has been sent via Calibre-Web.'),book.id)) return return _("The requested file could not be read. Maybe wrong permissions?") @@ -1001,26 +1001,6 @@ def do_kepubify_metadata_replace(book, file_path): return tmp_dir, temp_file_name -def do_calibre_export(book_id, book_format, ): - try: - quotes = [3, 5, 7, 9] - tmp_dir = get_temp_dir() - calibredb_binarypath = get_calibre_binarypath("calibredb") - temp_file_name = str(uuid4()) - opf_command = [calibredb_binarypath, 'export', '--dont-write-opf', '--with-library', config.config_calibre_dir, - '--to-dir', tmp_dir, '--formats', book_format, "--template", "{}".format(temp_file_name), - str(book_id)] - p = process_open(opf_command, quotes) - _, err = p.communicate() - if err: - log.error('Metadata embedder encountered an error: %s', err) - return tmp_dir, temp_file_name - except OSError as ex: - # ToDo real error handling - log.error_or_exception(ex) - return None, None - - ################################## @@ -1142,17 +1122,6 @@ def get_download_link(book_id, book_format, client): abort(404) -def get_calibre_binarypath(binary): - binariesdir = config.config_binariesdir - if binariesdir: - try: - return os.path.join(binariesdir, SUPPORTED_CALIBRE_BINARIES[binary]) - except KeyError as ex: - log.error("Binary not supported by Calibre-Web: %s", SUPPORTED_CALIBRE_BINARIES[binary]) - pass - return "" - - def clear_cover_thumbnail_cache(book_id): if config.schedule_generate_book_covers: WorkerThread.add(None, TaskClearCoverThumbnailCache(book_id), hidden=True) diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index 8cb29197..f32c4581 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -110,6 +110,7 @@ class TaskConvert(CalibreTask): self.ereader_mail, EmailText, self.settings['body'], + id=self.book_id, internal=True) ) except Exception as ex: diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 257df4e9..39ad919f 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -21,7 +21,6 @@ import smtplib import ssl import threading import socket -from shutil import copy import mimetypes from io import StringIO @@ -29,14 +28,11 @@ from email.message import EmailMessage from email.utils import formatdate, parseaddr from email.generator import Generator from flask_babel import lazy_gettext as N_ -from email.utils import formatdate from cps.services.worker import CalibreTask from cps.services import gmail +from cps.embed_helper import do_calibre_export from cps import logger, config -from cps.subproc_wrapper import process_open -from cps.constants import SUPPORTED_CALIBRE_BINARIES - from cps import gdriveutils import uuid @@ -113,7 +109,7 @@ class EmailSSL(EmailBase, smtplib.SMTP_SSL): class TaskEmail(CalibreTask): - def __init__(self, subject, filepath, attachment, settings, recipient, task_message, text, internal=False): + def __init__(self, subject, filepath, attachment, settings, recipient, task_message, text, id=0, internal=False): super(TaskEmail, self).__init__(task_message) self.subject = subject self.attachment = attachment @@ -122,6 +118,7 @@ class TaskEmail(CalibreTask): self.recipient = recipient self.text = text self.asyncSMTP = None + self.book_id = id self.results = dict() # from calibre code: @@ -144,7 +141,7 @@ class TaskEmail(CalibreTask): message['To'] = self.recipient message['Subject'] = self.subject message['Date'] = formatdate(localtime=True) - message['Message-Id'] = "{}@{}".format(uuid.uuid4(), self.get_msgid_domain()) # f"<{uuid.uuid4()}@{get_msgid_domain(from_)}>" # make_msgid('calibre-web') + message['Message-Id'] = "{}@{}".format(uuid.uuid4(), self.get_msgid_domain()) message.set_content(self.text.encode('UTF-8'), "text", "plain") if self.attachment: data = self._get_attachment(self.filepath, self.attachment) @@ -164,6 +161,8 @@ class TaskEmail(CalibreTask): try: # create MIME message msg = self.prepare_message() + if not msg: + return if self.settings['mail_server_type'] == 0: self.send_standard_email(msg) else: @@ -239,10 +238,10 @@ class TaskEmail(CalibreTask): self.asyncSMTP = None self._progress = x - @classmethod - def _get_attachment(cls, book_path, filename): + def _get_attachment(self, book_path, filename): """Get file as MIMEBase message""" calibre_path = config.get_book_path() + extension = os.path.splitext(filename)[1][1:] if config.config_use_google_drive: df = gdriveutils.getFileFromEbooksFolder(book_path, filename) if df: @@ -252,22 +251,21 @@ class TaskEmail(CalibreTask): df.GetContentFile(datafile) else: return None - if config.config_binariesdir: - datafile = cls._embed_metadata(calibre_path, book_path, filename, datafile) - os.remove(os.path.join(calibre_path, book_path, filename)) - file_ = open(datafile, 'rb') - data = file_.read() - file_.close() + if config.config_binariesdir and config.config_embed_metadata: + data_path, data_file = do_calibre_export(self.book_id, extension) + datafile = os.path.join(data_path, data_file + "." + extension) + with open(datafile, 'rb') as file_: + data = file_.read() os.remove(datafile) else: datafile = os.path.join(calibre_path, book_path, filename) try: - if config.config_binariesdir: - datafile = cls._embed_metadata(calibre_path, book_path, filename, datafile) - file_ = open(datafile, 'rb') - data = file_.read() - file_.close() - if config.config_binariesdir: + if config.config_binariesdir and config.config_embed_metadata: + data_path, data_file = do_calibre_export(self.book_id, extension) + datafile = os.path.join(data_path, data_file + "." + extension) + with open(datafile, 'rb') as file_: + data = file_.read() + if config.config_binariesdir and config.config_embed_metadata: os.remove(datafile) except IOError as e: log.error_or_exception(e, stacklevel=3) @@ -285,17 +283,3 @@ class TaskEmail(CalibreTask): def __str__(self): return "E-mail {}, {}".format(self.name, self.subject) - - def _embed_metadata(self, calibre_path, book_path, filename, datafile): - datafile_tmp = os.path.join(calibre_path, book_path, "tmp_" + filename) - path_opf = os.path.join(calibre_path, book_path, "metadata.opf") - copy(datafile, datafile_tmp) - - calibredb_binarypath = os.path.join(config.config_binariesdir, SUPPORTED_CALIBRE_BINARIES["ebook-meta"]) - opf_command = [calibredb_binarypath, datafile_tmp, "--from-opf", path_opf] - p = process_open(opf_command) - _, err = p.communicate() - if err: - # ToDo: Improve error handling - log.error('Metadata embedder encountered an error: %s', err) - return datafile_tmp