diff --git a/cps/about.py b/cps/about.py
index 9053dfeee..fb0313434 100644
--- a/cps/about.py
+++ b/cps/about.py
@@ -27,7 +27,6 @@ import importlib
from collections import OrderedDict
import flask
-import jinja2
from flask_babel import gettext as _
from . import db, calibre_db, converter, uploader, constants, dep_check
diff --git a/cps/admin.py b/cps/admin.py
index 5daad9588..8c65e8589 100644
--- a/cps/admin.py
+++ b/cps/admin.py
@@ -567,7 +567,7 @@ def update_view_configuration():
_config_string(to_save, "config_calibre_web_title")
_config_string(to_save, "config_columns_to_ignore")
if _config_string(to_save, "config_title_regex"):
- calibre_db.update_title_sort(config)
+ calibre_db.create_functions(config)
if not check_valid_read_column(to_save.get("config_read_column", "0")):
flash(_("Invalid Read Column"), category="error")
diff --git a/cps/audio.py b/cps/audio.py
index 8fd28c812..b17bc7bd5 100644
--- a/cps/audio.py
+++ b/cps/audio.py
@@ -26,7 +26,7 @@ from cps.constants import BookMeta
log = logger.create()
-def get_audio_file_info(tmp_file_path, original_file_extension, original_file_name):
+def get_audio_file_info(tmp_file_path, original_file_extension, original_file_name, no_cover_processing):
tmp_cover_name = None
audio_file = mutagen.File(tmp_file_path)
comments = None
@@ -50,7 +50,7 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na
pubdate = str(audio_file.tags.get('TDRC').text[0]) if "TDRC" in audio_file.tags else None
if not pubdate:
pubdate = str(audio_file.tags.get('TDOR').text[0]) if "TDOR" in audio_file.tags else None
- if cover_data:
+ if cover_data and not no_cover_processing:
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
cover_info = cover_data[0]
for dat in cover_data:
@@ -68,18 +68,19 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na
publisher = audio_file.tags.get('LABEL')[0] if "LABEL" in audio_file else None
pubdate = audio_file.tags.get('DATE')[0] if "DATE" in audio_file else None
cover_data = audio_file.tags.get('METADATA_BLOCK_PICTURE')
- if cover_data:
- tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
- cover_info = mutagen.flac.Picture(base64.b64decode(cover_data[0]))
- cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:])
- if hasattr(audio_file, "pictures"):
- cover_info = audio_file.pictures[0]
- for dat in audio_file.pictures:
- if dat.type == mutagen.id3.PictureType.COVER_FRONT:
- cover_info = dat
- break
- tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
- cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:])
+ if not no_cover_processing:
+ if cover_data:
+ tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
+ cover_info = mutagen.flac.Picture(base64.b64decode(cover_data[0]))
+ cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:])
+ if hasattr(audio_file, "pictures"):
+ cover_info = audio_file.pictures[0]
+ for dat in audio_file.pictures:
+ if dat.type == mutagen.id3.PictureType.COVER_FRONT:
+ cover_info = dat
+ break
+ tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
+ cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:])
elif original_file_extension in [".aac"]:
title = audio_file.tags.get('Title').value if "Title" in audio_file else None
author = audio_file.tags.get('Artist').value if "Artist" in audio_file else None
@@ -90,7 +91,7 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na
publisher = audio_file.tags.get('Label').value if "Label" in audio_file else None
pubdate = audio_file.tags.get('Year').value if "Year" in audio_file else None
cover_data = audio_file.tags['Cover Art (Front)']
- if cover_data:
+ if cover_data and not no_cover_processing:
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
with open(tmp_cover_name, "wb") as cover_file:
cover_file.write(cover_data.value.split(b"\x00",1)[1])
@@ -104,7 +105,7 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na
publisher = audio_file.tags.get('Label')[0].value if "Label" in audio_file else None
pubdate = audio_file.tags.get('Year')[0].value if "Year" in audio_file else None
cover_data = audio_file.tags.get('WM/Picture', None)
- if cover_data:
+ if cover_data and not no_cover_processing:
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
with open(tmp_cover_name, "wb") as cover_file:
cover_file.write(cover_data[0].value)
@@ -118,7 +119,7 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na
publisher = ""
pubdate = audio_file.tags.get('©day')[0] if "©day" in audio_file.tags else None
cover_data = audio_file.tags.get('covr', None)
- if cover_data:
+ if cover_data and not no_cover_processing:
tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg')
cover_type = None
for c in cover_data:
diff --git a/cps/comic.py b/cps/comic.py
index 27c86c9a3..a4a4f136e 100644
--- a/cps/comic.py
+++ b/cps/comic.py
@@ -130,7 +130,7 @@ def _extract_cover(tmp_file_name, original_file_extension, rar_executable):
return cover.cover_processing(tmp_file_name, cover_data, extension)
-def get_comic_info(tmp_file_path, original_file_name, original_file_extension, rar_executable):
+def get_comic_info(tmp_file_path, original_file_name, original_file_extension, rar_executable, no_cover_processing):
if use_comic_meta:
try:
archive = ComicArchive(tmp_file_path, rar_exe_path=rar_executable)
@@ -155,14 +155,17 @@ def get_comic_info(tmp_file_path, original_file_name, original_file_extension, r
lang = loaded_metadata.language or ""
loaded_metadata.language = isoLanguages.get_lang3(lang)
-
+ if not no_cover_processing:
+ cover_file = _extract_cover(tmp_file_path, original_file_extension, rar_executable)
+ else:
+ cover_file = None
return BookMeta(
file_path=tmp_file_path,
extension=original_file_extension,
title=loaded_metadata.title or original_file_name,
author=" & ".join([credit["person"]
for credit in loaded_metadata.credits if credit["role"] == "Writer"]) or 'Unknown',
- cover=_extract_cover(tmp_file_path, original_file_extension, rar_executable),
+ cover=cover_file,
description=loaded_metadata.comments or "",
tags="",
series=loaded_metadata.series or "",
@@ -171,13 +174,17 @@ def get_comic_info(tmp_file_path, original_file_name, original_file_extension, r
publisher="",
pubdate="",
identifiers=[])
+ if not no_cover_processing:
+ cover_file = _extract_cover(tmp_file_path, original_file_extension, rar_executable)
+ else:
+ cover_file = None
return BookMeta(
file_path=tmp_file_path,
extension=original_file_extension,
title=original_file_name,
author='Unknown',
- cover=_extract_cover(tmp_file_path, original_file_extension, rar_executable),
+ cover=cover_file,
description="",
tags="",
series="",
diff --git a/cps/db.py b/cps/db.py
index dc08a7de9..33b195fac 100644
--- a/cps/db.py
+++ b/cps/db.py
@@ -24,6 +24,7 @@ from datetime import datetime, timezone
from urllib.parse import quote
import unidecode
from weakref import WeakSet
+from uuid import uuid4
from sqlite3 import OperationalError as sqliteOperationalError
from sqlalchemy import create_engine
@@ -533,7 +534,7 @@ class CalibreDB:
def init_session(self, expire_on_commit=True):
self.session = self.session_factory()
self.session.expire_on_commit = expire_on_commit
- self.update_title_sort(self.config)
+ self.create_functions(self.config)
@classmethod
def setup_db_cc_classes(cls, cc):
@@ -901,7 +902,8 @@ class CalibreDB:
def get_typeahead(self, database, query, replace=('', ''), tag_filter=true()):
query = query or ''
- self.session.connection().connection.connection.create_function("lower", 1, lcase)
+ self.create_functions()
+ # self.session.connection().connection.connection.create_function("lower", 1, lcase)
entries = self.session.query(database).filter(tag_filter). \
filter(func.lower(database.name).ilike("%" + query + "%")).all()
# json_dumps = json.dumps([dict(name=escape(r.name.replace(*replace))) for r in entries])
@@ -909,7 +911,8 @@ class CalibreDB:
return json_dumps
def check_exists_book(self, authr, title):
- self.session.connection().connection.connection.create_function("lower", 1, lcase)
+ self.create_functions()
+ # self.session.connection().connection.connection.create_function("lower", 1, lcase)
q = list()
author_terms = re.split(r'\s*&\s*', authr)
for author_term in author_terms:
@@ -920,7 +923,8 @@ class CalibreDB:
def search_query(self, term, config, *join):
strip_whitespaces(term).lower()
- self.session.connection().connection.connection.create_function("lower", 1, lcase)
+ self.create_functions()
+ # self.session.connection().connection.connection.create_function("lower", 1, lcase)
q = list()
author_terms = re.split("[, ]+", term)
for author_term in author_terms:
@@ -1018,7 +1022,7 @@ class CalibreDB:
lang.name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
return sorted(languages, key=lambda x: x.name, reverse=reverse_order)
- def update_title_sort(self, config, conn=None):
+ def create_functions(self, config=None):
# user defined sort function for calibre databases (Series, etc.)
def _title_sort(title):
# calibre sort stuff
@@ -1031,12 +1035,15 @@ class CalibreDB:
try:
# sqlalchemy <1.4.24 and sqlalchemy 2.0
- conn = conn or self.session.connection().connection.driver_connection
+ conn = self.session.connection().connection.driver_connection
except AttributeError:
# sqlalchemy >1.4.24
- conn = conn or self.session.connection().connection.connection
+ conn = self.session.connection().connection.connection
try:
- conn.create_function("title_sort", 1, _title_sort)
+ if config:
+ conn.create_function("title_sort", 1, _title_sort)
+ conn.create_function('uuid4', 0, lambda: str(uuid4()))
+ conn.create_function("lower", 1, lcase)
except sqliteOperationalError:
pass
diff --git a/cps/editbooks.py b/cps/editbooks.py
index 122b1c2cc..6eefc61fb 100644
--- a/cps/editbooks.py
+++ b/cps/editbooks.py
@@ -24,7 +24,7 @@ import os
from datetime import datetime, timezone
import json
from shutil import copyfile
-from uuid import uuid4
+
from markupsafe import escape, Markup # dependency of flask
from functools import wraps
@@ -97,161 +97,22 @@ def show_edit_book(book_id):
@login_required_if_no_ano
@edit_required
def edit_book(book_id):
- modify_date = False
- edit_error = False
-
- # create the function for sorting...
- calibre_db.update_title_sort(config)
-
- book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
- # Book not found
- if not book:
- flash(_("Oops! Selected book is unavailable. File does not exist or is not accessible"),
- category="error")
- return redirect(url_for("web.index"))
-
- to_save = request.form.to_dict()
-
- try:
- # Update folder of book on local disk
- edited_books_id = None
- title_author_error = None
- # handle book title change
- title_change = handle_title_on_edit(book, to_save["book_title"])
- # handle book author change
- input_authors, author_change = handle_author_on_edit(book, to_save["author_name"])
- if author_change or title_change:
- edited_books_id = book.id
- modify_date = True
- title_author_error = helper.update_dir_structure(edited_books_id,
- config.get_book_path(),
- input_authors[0])
- if title_author_error:
- flash(title_author_error, category="error")
- calibre_db.session.rollback()
- book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
-
- # handle upload other formats from local disk
- meta = upload_single_file(request, book, book_id)
- # only merge metadata if file was uploaded and no error occurred (meta equals not false or none)
- upload_format = False
- if meta:
- upload_format = merge_metadata(to_save, meta)
- # handle upload covers from local disk
- cover_upload_success = upload_cover(request, book)
- if cover_upload_success:
- book.has_cover = 1
- modify_date = True
-
- # upload new covers or new file formats to google drive
- if config.config_use_google_drive:
- gdriveutils.updateGdriveCalibreFromLocal()
-
- if to_save.get("cover_url", None):
- if not current_user.role_upload():
- edit_error = True
- flash(_("User has no rights to upload cover"), category="error")
- if to_save["cover_url"].endswith('/static/generic_cover.jpg'):
- book.has_cover = 0
- else:
- result, error = helper.save_cover_from_url(to_save["cover_url"].strip(), book.path)
- if result is True:
- book.has_cover = 1
- modify_date = True
- helper.replace_cover_thumbnail_cache(book.id)
- else:
- flash(error, category="error")
-
- # Add default series_index to book
- modify_date |= edit_book_series_index(to_save["series_index"], book)
- # Handle book comments/description
- modify_date |= edit_book_comments(Markup(to_save['description']).unescape(), book)
- # Handle identifiers
- input_identifiers = identifier_list(to_save, book)
- modification, warning = modify_identifiers(input_identifiers, book.identifiers, calibre_db.session)
- if warning:
- flash(_("Identifiers are not Case Sensitive, Overwriting Old Identifier"), category="warning")
- modify_date |= modification
- # Handle book tags
- modify_date |= edit_book_tags(to_save['tags'], book)
- # Handle book series
- modify_date |= edit_book_series(to_save["series"], book)
- # handle book publisher
- modify_date |= edit_book_publisher(to_save['publisher'], book)
- # handle book languages
- try:
- modify_date |= edit_book_languages(to_save['languages'], book, upload_format)
- except ValueError as e:
- flash(str(e), category="error")
- edit_error = True
- # handle book ratings
- modify_date |= edit_book_ratings(to_save, book)
- # handle cc data
- modify_date |= edit_all_cc_data(book_id, book, to_save)
-
- if to_save.get("pubdate", None):
- try:
- book.pubdate = datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
- except ValueError as e:
- book.pubdate = db.Books.DEFAULT_PUBDATE
- flash(str(e), category="error")
- edit_error = True
- else:
- book.pubdate = db.Books.DEFAULT_PUBDATE
-
- if modify_date:
- book.last_modified = datetime.now(timezone.utc)
- kobo_sync_status.remove_synced_book(edited_books_id, all=True)
- calibre_db.set_metadata_dirty(book.id)
-
- calibre_db.session.merge(book)
- calibre_db.session.commit()
- if config.config_use_google_drive:
- gdriveutils.updateGdriveCalibreFromLocal()
- if meta is not False \
- and edit_error is not True \
- and title_author_error is not True \
- and cover_upload_success is not False:
- flash(_("Metadata successfully updated"), category="success")
- if "detail_view" in to_save:
- return redirect(url_for('web.show_book', book_id=book.id))
- else:
- return render_edit_book(book_id)
- except ValueError as e:
- log.error_or_exception("Error: {}".format(e))
- calibre_db.session.rollback()
- flash(str(e), category="error")
- return redirect(url_for('web.show_book', book_id=book.id))
- except (OperationalError, IntegrityError, StaleDataError, InterfaceError) as e:
- log.error_or_exception("Database error: {}".format(e))
- calibre_db.session.rollback()
- flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e), category="error")
- return redirect(url_for('web.show_book', book_id=book.id))
- except Exception as ex:
- log.error_or_exception(ex)
- calibre_db.session.rollback()
- flash(_("Error editing book: {}".format(ex)), category="error")
- return redirect(url_for('web.show_book', book_id=book.id))
+ return do_edit_book(book_id)
@editbook.route("/upload", methods=["POST"])
@login_required_if_no_ano
@upload_required
def upload():
- if not config.config_uploading:
- abort(404)
- if request.method == 'POST' and 'btn-upload' in request.files:
+ if len(request.files.getlist("btn-upload-format")):
+ book_id = request.form.get('book_id', -1)
+ return do_edit_book(book_id, request.files.getlist("btn-upload-format"))
+ elif len(request.files.getlist("btn-upload")):
for requested_file in request.files.getlist("btn-upload"):
try:
modify_date = False
# create the function for sorting...
- calibre_db.update_title_sort(config)
- try:
- # sqlalchemy 2.0
- uuid_func = calibre_db.session.connection().connection.driver_connection
- except AttributeError:
- uuid_func = calibre_db.session.connection().connection.connection
- uuid_func.create_function('uuid4', 0,lambda: str(uuid4()))
+ calibre_db.create_functions(config)
meta, error = file_handling_on_upload(requested_file)
if error:
return error
@@ -279,9 +140,7 @@ def upload():
input_authors[0],
meta.file_path,
title_dir + meta.extension.lower())
-
move_coverfile(meta, db_book)
-
if modify_date:
calibre_db.set_metadata_dirty(book_id)
# save data to database, reread data
@@ -309,6 +168,7 @@ def upload():
flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e),
category="error")
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
+ abort(404)
@editbook.route("/admin/book/convert/", methods=['POST'])
@@ -575,23 +435,166 @@ def table_xchange_author_title():
return ""
-def merge_metadata(to_save, meta):
- if to_save.get('author_name', "") == _('Unknown'):
- to_save['author_name'] = ''
- if to_save.get('book_title', "") == _('Unknown'):
- to_save['book_title'] = ''
- if not to_save["languages"] and meta.languages:
- upload_language = True
- else:
- upload_language = False
+def do_edit_book(book_id, upload_formats=None):
+ modify_date = False
+ edit_error = False
+
+ # create the function for sorting...
+ calibre_db.create_functions(config)
+
+ book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
+ # Book not found
+ if not book:
+ flash(_("Oops! Selected book is unavailable. File does not exist or is not accessible"),
+ category="error")
+ return redirect(url_for("web.index"))
+
+ to_save = request.form.to_dict()
+
+ try:
+ # Update folder of book on local disk
+ edited_books_id = None
+ title_author_error = None
+ # upload_mode = False
+ # handle book title change
+ if "title" in to_save:
+ title_change = handle_title_on_edit(book, to_save["title"])
+ # handle book author change
+ if not upload_formats:
+ input_authors, author_change = handle_author_on_edit(book, to_save["authors"])
+ if author_change or title_change:
+ edited_books_id = book.id
+ modify_date = True
+ title_author_error = helper.update_dir_structure(edited_books_id,
+ config.get_book_path(),
+ input_authors[0])
+ if title_author_error:
+ flash(title_author_error, category="error")
+ calibre_db.session.rollback()
+ book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
+
+ # handle book ratings
+ modify_date |= edit_book_ratings(to_save, book)
+ else:
+ # handle upload other formats from local disk
+ to_save, edit_error = upload_book_formats(upload_formats, book, book_id, book.has_cover)
+ # handle upload covers from local disk
+ cover_upload_success = upload_cover(request, book)
+ if cover_upload_success or to_save.get("format_cover"):
+ book.has_cover = 1
+ modify_date = True
+
+ # upload new covers or new file formats to google drive
+ if config.config_use_google_drive:
+ gdriveutils.updateGdriveCalibreFromLocal()
+
+ if to_save.get("cover_url",):
+ if not current_user.role_upload():
+ edit_error = True
+ flash(_("User has no rights to upload cover"), category="error")
+ if to_save["cover_url"].endswith('/static/generic_cover.jpg'):
+ book.has_cover = 0
+ else:
+ result, error = helper.save_cover_from_url(to_save["cover_url"].strip(), book.path)
+ if result is True:
+ book.has_cover = 1
+ modify_date = True
+ helper.replace_cover_thumbnail_cache(book.id)
+ else:
+ flash(error, category="error")
+
+ # Add default series_index to book
+ modify_date |= edit_book_series_index(to_save.get("series_index"), book)
+ # Handle book comments/description
+ modify_date |= edit_book_comments(Markup(to_save.get('comments')).unescape(), book)
+ # Handle identifiers
+ input_identifiers = identifier_list(to_save, book)
+ modification, warning = modify_identifiers(input_identifiers, book.identifiers, calibre_db.session)
+ if warning:
+ flash(_("Identifiers are not Case Sensitive, Overwriting Old Identifier"), category="warning")
+ modify_date |= modification
+ # Handle book tags
+ modify_date |= edit_book_tags(to_save.get('tags'), book)
+ # Handle book series
+ modify_date |= edit_book_series(to_save.get("series"), book)
+ # handle book publisher
+ modify_date |= edit_book_publisher(to_save.get('publisher'), book)
+ # handle book languages
+ try:
+ invalid = []
+ modify_date |= edit_book_languages(to_save.get('languages'), book, upload_mode=upload_formats,
+ invalid=invalid)
+ if invalid:
+ for lang in invalid:
+ flash(_("'%(langname)s' is not a valid language", langname=lang), category="warning")
+ except ValueError as e:
+ flash(str(e), category="error")
+ edit_error = True
+ # handle cc data
+ modify_date |= edit_all_cc_data(book_id, book, to_save)
+
+ if to_save.get("pubdate") is not None:
+ if to_save.get("pubdate"):
+ try:
+ book.pubdate = datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
+ except ValueError as e:
+ book.pubdate = db.Books.DEFAULT_PUBDATE
+ flash(str(e), category="error")
+ edit_error = True
+ else:
+ book.pubdate = db.Books.DEFAULT_PUBDATE
+
+ if modify_date:
+ book.last_modified = datetime.now(timezone.utc)
+ kobo_sync_status.remove_synced_book(edited_books_id, all=True)
+ calibre_db.set_metadata_dirty(book.id)
+
+ calibre_db.session.merge(book)
+ calibre_db.session.commit()
+ if config.config_use_google_drive:
+ gdriveutils.updateGdriveCalibreFromLocal()
+ if edit_error is not True and title_author_error is not True and cover_upload_success is not False:
+ flash(_("Metadata successfully updated"), category="success")
+
+ if upload_formats:
+ resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)}
+ return Response(json.dumps(resp), mimetype='application/json')
+
+ if "detail_view" in to_save:
+ return redirect(url_for('web.show_book', book_id=book.id))
+ else:
+ return render_edit_book(book_id)
+ except ValueError as e:
+ log.error_or_exception("Error: {}".format(e))
+ calibre_db.session.rollback()
+ flash(str(e), category="error")
+ return redirect(url_for('web.show_book', book_id=book.id))
+ except (OperationalError, IntegrityError, StaleDataError, InterfaceError) as e:
+ log.error_or_exception("Database error: {}".format(e))
+ calibre_db.session.rollback()
+ flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e), category="error")
+ return redirect(url_for('web.show_book', book_id=book.id))
+ except Exception as ex:
+ log.error_or_exception(ex)
+ calibre_db.session.rollback()
+ flash(_("Error editing book: {}".format(ex)), category="error")
+ return redirect(url_for('web.show_book', book_id=book.id))
+
+
+def merge_metadata(book, meta, to_save):
+ if meta.cover:
+ to_save['cover_format'] = meta.cover
for s_field, m_field in [
- ('tags', 'tags'), ('author_name', 'author'), ('series', 'series'),
+ ('tags', 'tags'), ('authors', 'author'), ('series', 'series'),
('series_index', 'series_id'), ('languages', 'languages'),
- ('book_title', 'title')]:
- to_save[s_field] = to_save[s_field] or getattr(meta, m_field, '')
- to_save["description"] = to_save["description"] or Markup(
- getattr(meta, 'description', '')).unescape()
- return upload_language
+ ('title', 'title'), ('comments', 'description')]:
+ try:
+ val = None if len(getattr(book, s_field)) else getattr(meta, m_field, '')
+ except TypeError:
+ val = None if len(str(getattr(book, s_field))) else getattr(meta, m_field, '')
+ if val:
+ to_save[s_field] = val
+
def identifier_list(to_save, book):
"""Generate a list of Identifiers from form information"""
@@ -1002,84 +1005,93 @@ def edit_book_ratings(to_save, book):
def edit_book_tags(tags, book):
- input_tags = tags.split(',')
- input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
- # Remove duplicates
- input_tags = helper.uniq(input_tags)
- return modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags')
-
+ if tags is not None:
+ input_tags = tags.split(',')
+ input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
+ # Remove duplicates
+ input_tags = helper.uniq(input_tags)
+ return modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags')
+ return False
def edit_book_series(series, book):
- input_series = [strip_whitespaces(series)]
- input_series = [x for x in input_series if x != '']
- return modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series')
+ if series is not None:
+ input_series = [strip_whitespaces(series)]
+ input_series = [x for x in input_series if x != '']
+ return modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series')
+ return False
def edit_book_series_index(series_index, book):
- # Add default series_index to book
- modify_date = False
- series_index = series_index or '1'
- if not series_index.replace('.', '', 1).isdigit():
- flash(_("Seriesindex: %(seriesindex)s is not a valid number, skipping", seriesindex=series_index), category="warning")
- return False
- if str(book.series_index) != series_index:
- book.series_index = series_index
- modify_date = True
- return modify_date
+ if series_index:
+ # Add default series_index to book
+ modify_date = False
+ series_index = series_index or '1'
+ if not series_index.replace('.', '', 1).isdigit():
+ flash(_("Seriesindex: %(seriesindex)s is not a valid number, skipping", seriesindex=series_index), category="warning")
+ return False
+ if str(book.series_index) != series_index:
+ book.series_index = series_index
+ modify_date = True
+ return modify_date
+ return False
# Handle book comments/description
def edit_book_comments(comments, book):
- modify_date = False
- if comments:
- comments = clean_string(comments, book.id)
- if len(book.comments):
- if book.comments[0].text != comments:
- book.comments[0].text = comments
- modify_date = True
- else:
+ if comments is not None:
+ modify_date = False
if comments:
- book.comments.append(db.Comments(comment=comments, book=book.id))
- modify_date = True
- return modify_date
+ comments = clean_string(comments, book.id)
+ if len(book.comments):
+ if book.comments[0].text != comments:
+ book.comments[0].text = comments
+ modify_date = True
+ else:
+ if comments:
+ book.comments.append(db.Comments(comment=comments, book=book.id))
+ modify_date = True
+ return modify_date
def edit_book_languages(languages, book, upload_mode=False, invalid=None):
- input_languages = languages.split(',')
- unknown_languages = []
- if not upload_mode:
- input_l = isoLanguages.get_language_codes(get_locale(), input_languages, unknown_languages)
- else:
- input_l = isoLanguages.get_valid_language_codes(get_locale(), input_languages, unknown_languages)
- for lang in unknown_languages:
- log.error("'%s' is not a valid language", lang)
- if isinstance(invalid, list):
- invalid.append(lang)
+ if languages is not None:
+ input_languages = languages.split(',')
+ unknown_languages = []
+ if not upload_mode:
+ input_l = isoLanguages.get_language_code_from_name(get_locale(), input_languages, unknown_languages)
else:
- raise ValueError(_("'%(langname)s' is not a valid language", langname=lang))
- # ToDo: Not working correct
- if upload_mode and len(input_l) == 1:
- # If the language of the file is excluded from the users view, it's not imported, to allow the user to view
- # the book it's language is set to the filter language
- if input_l[0] != current_user.filter_language() and current_user.filter_language() != "all":
- input_l[0] = calibre_db.session.query(db.Languages). \
- filter(db.Languages.lang_code == current_user.filter_language()).first().lang_code
- # Remove duplicates
- input_l = helper.uniq(input_l)
- return modify_database_object(input_l, book.languages, db.Languages, calibre_db.session, 'languages')
+ input_l = isoLanguages.get_valid_language_codes_from_code(get_locale(), input_languages, unknown_languages)
+ for lang in unknown_languages:
+ log.error("'%s' is not a valid language", lang)
+ if isinstance(invalid, list):
+ invalid.append(lang)
+ else:
+ raise ValueError(_("'%(langname)s' is not a valid language", langname=lang))
+ # ToDo: Not working correct
+ if upload_mode and len(input_l) == 1:
+ # If the language of the file is excluded from the users view, it's not imported, to allow the user to view
+ # the book it's language is set to the filter language
+ if input_l[0] != current_user.filter_language() and current_user.filter_language() != "all":
+ input_l[0] = calibre_db.session.query(db.Languages). \
+ filter(db.Languages.lang_code == current_user.filter_language()).first().lang_code
+ # Remove duplicates from normalized langcodes
+ input_l = helper.uniq(input_l)
+ return modify_database_object(input_l, book.languages, db.Languages, calibre_db.session, 'languages')
+ return False
def edit_book_publisher(publishers, book):
- changed = False
- if publishers:
- publisher = strip_whitespaces(publishers)
- if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name):
- changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session,
- 'publisher')
- elif len(book.publishers):
- changed |= modify_database_object([], book.publishers, db.Publishers, calibre_db.session, 'publisher')
- return changed
-
+ if publishers is not None:
+ changed = False
+ if publishers:
+ publisher = strip_whitespaces(publishers)
+ if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name):
+ changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session,
+ 'publisher')
+ elif len(book.publishers):
+ changed |= modify_database_object([], book.publishers, db.Publishers, calibre_db.session, 'publisher')
+ return changed
+ return False
def edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string):
changed = False
@@ -1160,61 +1172,66 @@ def edit_cc_data(book_id, book, to_save, cc):
changed = False
for c in cc:
cc_string = "custom_column_" + str(c.id)
- if not c.is_multiple:
- if len(getattr(book, cc_string)) > 0:
- cc_db_value = getattr(book, cc_string)[0].value
- else:
- cc_db_value = None
- if strip_whitespaces(to_save[cc_string]):
- if c.datatype in ['int', 'bool', 'float', "datetime", "comments"]:
- change, to_save = edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string)
+ if to_save.get(cc_string) is not None:
+ if not c.is_multiple:
+ if len(getattr(book, cc_string)) > 0:
+ cc_db_value = getattr(book, cc_string)[0].value
else:
- change, to_save = edit_cc_data_string(book, c, to_save, cc_db_value, cc_string)
- changed |= change
+ cc_db_value = None
+ if strip_whitespaces(to_save[cc_string]):
+ if c.datatype in ['int', 'bool', 'float', "datetime", "comments"]:
+ change, to_save = edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string)
+ else:
+ change, to_save = edit_cc_data_string(book, c, to_save, cc_db_value, cc_string)
+ changed |= change
+ else:
+ if cc_db_value is not None:
+ # remove old cc_val
+ del_cc = getattr(book, cc_string)[0]
+ getattr(book, cc_string).remove(del_cc)
+ if not del_cc.books or len(del_cc.books) == 0:
+ calibre_db.session.delete(del_cc)
+ changed = True
else:
- if cc_db_value is not None:
- # remove old cc_val
- del_cc = getattr(book, cc_string)[0]
- getattr(book, cc_string).remove(del_cc)
- if not del_cc.books or len(del_cc.books) == 0:
- calibre_db.session.delete(del_cc)
- changed = True
- else:
- input_tags = to_save[cc_string].split(',')
- input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
- changed |= modify_database_object(input_tags,
- getattr(book, cc_string),
- db.cc_classes[c.id],
- calibre_db.session,
- 'custom')
+ input_tags = to_save[cc_string].split(',')
+ input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
+ changed |= modify_database_object(input_tags,
+ getattr(book, cc_string),
+ db.cc_classes[c.id],
+ calibre_db.session,
+ 'custom')
return changed
-# returns None if no file is uploaded
-# returns False if an error occurs, in all other cases the ebook metadata is returned
-def upload_single_file(file_request, book, book_id):
+# returns False if an error occurs or no book is uploaded, in all other cases the ebook metadata to change is returned
+def upload_book_formats(requested_files, book, book_id, no_cover=True):
# Check and handle Uploaded file
- requested_file = file_request.files.get('btn-upload-format', None)
+ to_save = dict()
+ error = False
allowed_extensions = config.config_upload_formats.split(',')
- if requested_file:
+ for requested_file in requested_files:
+ current_filename = requested_file.filename
if config.config_check_extensions and allowed_extensions != ['']:
if not validate_mime_type(requested_file, allowed_extensions):
flash(_("File type isn't allowed to be uploaded to this server"), category="error")
- return False
- # check for empty request
- if requested_file.filename != '':
+ error = True
+ continue
+ if current_filename != '':
if not current_user.role_upload():
flash(_("User has no rights to upload additional file formats"), category="error")
- return False
- if '.' in requested_file.filename:
- file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
+ error = True
+ continue
+ if '.' in current_filename:
+ file_ext = current_filename.rsplit('.', 1)[-1].lower()
if file_ext not in allowed_extensions and '' not in allowed_extensions:
flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext),
category="error")
- return False
+ error = True
+ continue
else:
flash(_('File to be uploaded must have an extension'), category="error")
- return False
+ error = True
+ continue
file_name = book.path.rsplit('/', 1)[-1]
filepath = os.path.normpath(os.path.join(config.get_book_path(), book.path))
@@ -1227,41 +1244,50 @@ def upload_single_file(file_request, book, book_id):
except OSError:
flash(_("Failed to create path %(path)s (Permission denied).", path=filepath),
category="error")
- return False
+ error = True
+ continue
try:
requested_file.save(saved_filename)
except OSError:
flash(_("Failed to store file %(file)s.", file=saved_filename), category="error")
- return False
+ error = True
+ continue
file_size = os.path.getsize(saved_filename)
- is_format = calibre_db.get_book_format(book_id, file_ext.upper())
# Format entry already exists, no need to update the database
- if is_format:
+ if calibre_db.get_book_format(book_id, file_ext.upper()):
log.warning('Book format %s already existing', file_ext.upper())
else:
try:
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
calibre_db.session.add(db_format)
calibre_db.session.commit()
- calibre_db.update_title_sort(config)
+ calibre_db.create_functions(config)
except (OperationalError, IntegrityError, StaleDataError) as e:
calibre_db.session.rollback()
log.error_or_exception("Database error: {}".format(e))
flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e),
category="error")
- return False # return redirect(url_for('web.show_book', book_id=book.id))
+ error = True
+ continue
# Queue uploader info
link = '{}'.format(url_for('web.show_book', book_id=book.id), escape(book.title))
upload_text = N_("File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=link)
WorkerThread.add(current_user.name, TaskUpload(upload_text, escape(book.title)))
-
- return uploader.process(
- saved_filename, *os.path.splitext(requested_file.filename),
- rar_executable=config.config_rarfile_location)
- return None
+ meta = uploader.process(
+ saved_filename,
+ *os.path.splitext(current_filename),
+ rar_executable=config.config_rarfile_location,
+ no_cover=no_cover)
+ merge_metadata(book, meta, to_save)
+ #if to_save.get('languages'):
+ # langs = []
+ # for lang_code in to_save['languages'].split(','):
+ # langs.append(isoLanguages.get_language_name(get_locale(), lang_code))
+ # to_save['languages'] = ",".join(langs)
+ return to_save, error
def upload_cover(cover_request, book):
@@ -1295,7 +1321,6 @@ def handle_title_on_edit(book, book_title):
def handle_author_on_edit(book, author_name, update_stored=True):
change = False
- # handle author(s)
input_authors = prepare_authors(author_name, config.get_book_path(), config.config_use_google_drive)
# Search for each author if author is in database, if not, author name and sorted author name is generated new
@@ -1325,7 +1350,6 @@ def search_objects_remove(db_book_object, db_type, input_elements):
if db_type == 'custom':
type_elements = c_elements.value
else:
- # type_elements = c_elements.name
type_elements = c_elements
for inp_element in input_elements:
if type_elements == inp_element:
diff --git a/cps/epub.py b/cps/epub.py
index e84822f3d..626e68eea 100644
--- a/cps/epub.py
+++ b/cps/epub.py
@@ -66,7 +66,7 @@ def get_epub_layout(book, book_data):
return layout[0]
-def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
+def get_epub_info(tmp_file_path, original_file_name, original_file_extension, no_cover_processing):
ns = {
'n': 'urn:oasis:names:tc:opendocument:xmlns:container',
'pkg': 'http://www.idpf.org/2007/opf',
@@ -117,7 +117,10 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
epub_metadata = parse_epub_series(ns, tree, epub_metadata)
epub_zip = zipfile.ZipFile(tmp_file_path)
- cover_file = parse_epub_cover(ns, tree, epub_zip, cover_path, tmp_file_path)
+ if not no_cover_processing:
+ cover_file = parse_epub_cover(ns, tree, epub_zip, cover_path, tmp_file_path)
+ else:
+ cover_file = None
identifiers = []
for node in p.xpath('dc:identifier', namespaces=ns):
diff --git a/cps/helper.py b/cps/helper.py
index 39c9c3845..c2fc9c237 100644
--- a/cps/helper.py
+++ b/cps/helper.py
@@ -328,7 +328,7 @@ def edit_book_read_status(book_id, read_status=None):
ub.session_commit("Book {} readbit toggled".format(book_id))
else:
try:
- calibre_db.update_title_sort(config)
+ calibre_db.create_functions(config)
book = calibre_db.get_filtered_book(book_id)
book_read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
if len(book_read_status):
diff --git a/cps/isoLanguages.py b/cps/isoLanguages.py
index 574736582..bbab70893 100644
--- a/cps/isoLanguages.py
+++ b/cps/isoLanguages.py
@@ -15,24 +15,30 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import sys
from .iso_language_names import LANGUAGE_NAMES as _LANGUAGE_NAMES
from . import logger
+from .string_helper import strip_whitespaces
log = logger.create()
try:
- from iso639 import languages, __version__
+ from iso639 import languages
+ # iso_version = importlib.metadata.version("iso639")
get = languages.get
-except ImportError:
- from pycountry import languages as pyc_languages
try:
- import pkg_resources
- __version__ = pkg_resources.get_distribution('pycountry').version + ' (PyCountry)'
- del pkg_resources
- except (ImportError, Exception):
- __version__ = "? (PyCountry)"
+ if sys.version_info >= (3, 12):
+ import pkg_resources
+ except ImportError:
+ print("Python 3.12 isn't compatible with iso-639. Please install pycountry.")
+except ImportError as ex:
+ from pycountry import languages as pyc_languages
+ #try:
+ # iso_version = importlib.metadata.version("pycountry") + ' (PyCountry)'
+ #except (ImportError, Exception):
+ # iso_version = "?" + ' (PyCountry)'
def _copy_fields(l):
l.part1 = getattr(l, 'alpha_2', None)
@@ -69,20 +75,20 @@ def get_language_name(locale, lang_code):
return name
-def get_language_codes(locale, language_names, remainder=None):
- language_names = set(x.strip().lower() for x in language_names if x)
+def get_language_code_from_name(locale, language_names, remainder=None):
+ language_names = set(strip_whitespaces(x).lower() for x in language_names if x)
lang = list()
- for k, v in get_language_names(locale).items():
- v = v.lower()
- if v in language_names:
- lang.append(k)
- language_names.remove(v)
+ for key, val in get_language_names(locale).items():
+ val = val.lower()
+ if val in language_names:
+ lang.append(key)
+ language_names.remove(val)
if remainder is not None and language_names:
remainder.extend(language_names)
return lang
-def get_valid_language_codes(locale, language_names, remainder=None):
+def get_valid_language_codes_from_code(locale, language_names, remainder=None):
lang = list()
if "" in language_names:
language_names.remove("")
diff --git a/cps/jinjia.py b/cps/jinjia.py
index 01abf5a6c..39c7f20cb 100644
--- a/cps/jinjia.py
+++ b/cps/jinjia.py
@@ -27,7 +27,7 @@ import datetime
import mimetypes
from uuid import uuid4
-from flask import Blueprint, request, url_for
+from flask import Blueprint, request, url_for, g
from flask_babel import format_date
from .cw_login import current_user
@@ -182,3 +182,12 @@ def get_cover_srcset(series):
url = url_for('web.get_series_cover', series_id=series.id, resolution=shortname, c=cache_timestamp())
srcset.append(f'{url} {resolution}x')
return ', '.join(srcset)
+
+
+@jinjia.app_template_filter('music')
+def contains_music(book_formats):
+ result = False
+ for format in book_formats:
+ if format.format.lower() in g.constants.EXTENSIONS_AUDIO:
+ result = True
+ return result
diff --git a/cps/search.py b/cps/search.py
index da74984b6..8cdb9b4a7 100644
--- a/cps/search.py
+++ b/cps/search.py
@@ -244,7 +244,8 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
pagination = None
cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
- calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
+ calibre_db.create_functions()
+ # calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
query = calibre_db.generate_linked_query(config.config_read_column, db.Books)
q = query.outerjoin(db.books_series_link, db.Books.id == db.books_series_link.c.book)\
.outerjoin(db.Series)\
@@ -257,8 +258,8 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
tags['include_' + element] = term.get('include_' + element)
tags['exclude_' + element] = term.get('exclude_' + element)
- author_name = term.get("author_name")
- book_title = term.get("book_title")
+ author_name = term.get("authors")
+ book_title = term.get("title")
publisher = term.get("publisher")
pub_start = term.get("publishstart")
pub_end = term.get("publishend")
diff --git a/cps/static/js/edit_books.js b/cps/static/js/edit_books.js
index c1eb319d4..46cc238c9 100644
--- a/cps/static/js/edit_books.js
+++ b/cps/static/js/edit_books.js
@@ -3,9 +3,9 @@
*/
/* global Bloodhound, language, Modernizr, tinymce, getPath */
-if ($("#description").length) {
+if ($("#comments").length) {
tinymce.init({
- selector: "#description",
+ selector: "#comments",
plugins: 'code',
branding: false,
menubar: "edit view format",
@@ -93,7 +93,7 @@ var authors = new Bloodhound({
},
});
-$(".form-group #bookAuthor").typeahead(
+$(".form-group #authors").typeahead(
{
highlight: true,
minLength: 1,
@@ -243,13 +243,13 @@ $("#search").on("change input.typeahead:selected", function(event) {
});
});
-$("#btn-upload-format").on("change", function () {
+/*$("#btn-upload-format").on("change", function () {
var filename = $(this).val();
if (filename.substring(3, 11) === "fakepath") {
filename = filename.substring(12);
} // Remove c:\fake at beginning from localhost chrome
$("#upload-format").text(filename);
-});
+});*/
$("#btn-upload-cover").on("change", function () {
var filename = $(this).val();
@@ -261,8 +261,8 @@ $("#btn-upload-cover").on("change", function () {
$("#xchange").click(function () {
this.blur();
- var title = $("#book_title").val();
- $("#book_title").val($("#bookAuthor").val());
- $("#bookAuthor").val(title);
+ var title = $("#title").val();
+ $("#title").val($("#authors").val());
+ $("#authors").val(title);
});
diff --git a/cps/static/js/get_meta.js b/cps/static/js/get_meta.js
index 43a40fa65..df4119dca 100644
--- a/cps/static/js/get_meta.js
+++ b/cps/static/js/get_meta.js
@@ -38,12 +38,12 @@ $(function () {
}
function populateForm (book) {
- tinymce.get("description").setContent(book.description);
+ tinymce.get("comments").setContent(book.description);
var uniqueTags = getUniqueValues('tags', book)
var uniqueLanguages = getUniqueValues('languages', book)
var ampSeparatedAuthors = (book.authors || []).join(" & ");
- $("#bookAuthor").val(ampSeparatedAuthors);
- $("#book_title").val(book.title);
+ $("#authors").val(ampSeparatedAuthors);
+ $("#title").val(book.title);
$("#tags").val(uniqueTags.join(", "));
$("#languages").val(uniqueLanguages.join(", "));
$("#rating").data("rating").setValue(Math.round(book.rating));
@@ -172,7 +172,7 @@ $(function () {
$("#get_meta").click(function () {
populate_provider();
- var bookTitle = $("#book_title").val();
+ var bookTitle = $("#title").val();
$("#keyword").val(bookTitle);
keyword = bookTitle;
doSearch(bookTitle);
diff --git a/cps/static/js/main.js b/cps/static/js/main.js
index 2dbea6bc2..d04a04599 100644
--- a/cps/static/js/main.js
+++ b/cps/static/js/main.js
@@ -130,8 +130,13 @@ $(".container-fluid").bind('drop', function (e) {
}
});
if (dt.files.length) {
- $("#btn-upload")[0].files = dt.files;
- $("#form-upload").submit();
+ if($("#btn-upload-format").length) {
+ $("#btn-upload-format")[0].files = dt.files;
+ $("#form-upload-format").submit();
+ } else {
+ $("#btn-upload")[0].files = dt.files;
+ $("#form-upload").submit();
+ }
}
}
});
@@ -140,14 +145,28 @@ $("#btn-upload").change(function() {
$("#form-upload").submit();
});
-$("#form-upload").uploadprogress({
- redirect_url: getPath() + "/", //"{{ url_for('web.index')}}",
- uploadedMsg: $("#form-upload").data("message"), //"{{_('Upload done, processing, please wait...')}}",
- modalTitle: $("#form-upload").data("title"), //"{{_('Uploading...')}}",
- modalFooter: $("#form-upload").data("footer"), //"{{_('Close')}}",
- modalTitleFailed: $("#form-upload").data("failed") //"{{_('Error')}}"
+$("#btn-upload-format").change(function() {
+ $("#form-upload-format").submit();
});
+
+$("#form-upload").uploadprogress({
+ redirect_url: getPath() + "/",
+ uploadedMsg: $("#form-upload").data("message"),
+ modalTitle: $("#form-upload").data("title"),
+ modalFooter: $("#form-upload").data("footer"),
+ modalTitleFailed: $("#form-upload").data("failed")
+});
+
+$("#form-upload-format").uploadprogress({
+ redirect_url: getPath() + "/",
+ uploadedMsg: $("#form-upload-format").data("message"),
+ modalTitle: $("#form-upload-format").data("title"),
+ modalFooter: $("#form-upload-format").data("footer"),
+ modalTitleFailed: $("#form-upload-format").data("failed")
+});
+
+
$(document).ready(function() {
var inp = $('#query').first()
if (inp.length) {
diff --git a/cps/tasks/thumbnail.py b/cps/tasks/thumbnail.py
index 884ae0c30..a7658c02f 100644
--- a/cps/tasks/thumbnail.py
+++ b/cps/tasks/thumbnail.py
@@ -201,6 +201,7 @@ class TaskGenerateCoverThumbnails(CalibreTask):
with open(filename, 'wb') as fd:
copyfileobj(stream, fd)
+
except Exception as ex:
# Bubble exception to calling function
self.log.debug('Error generating thumbnail file: ' + str(ex))
diff --git a/cps/templates/author.html b/cps/templates/author.html
index f7314586d..1e02597c2 100644
--- a/cps/templates/author.html
+++ b/cps/templates/author.html
@@ -62,11 +62,9 @@
{{author.name.replace('|',',')|shortentitle(30)}}
{% endif %}
{% endfor %}
- {% for format in entry.Books.data %}
- {% if format.format|lower in g.constants.EXTENSIONS_AUDIO %}
+ {% if entry.Books.data|music %}
- {% endif %}
- {% endfor %}
+ {% endif %}
{% if entry.Books.series.__len__() > 0 %}
diff --git a/cps/templates/book_edit.html b/cps/templates/book_edit.html
index 57fe701c7..94dbbb3bf 100644
--- a/cps/templates/book_edit.html
+++ b/cps/templates/book_edit.html
@@ -47,26 +47,41 @@
{% endif %}
+{% if current_user.role_upload() and g.allow_upload %}
+
+{% endif %}
+