Display thumbnails on the frontend, generate thumbnails from google drive
This commit is contained in:
parent
21fce9a5b5
commit
e48bdf9d5a
5
cps.py
5
cps.py
|
@ -43,6 +43,7 @@ from cps.gdrive import gdrive
|
||||||
from cps.editbooks import editbook
|
from cps.editbooks import editbook
|
||||||
from cps.remotelogin import remotelogin
|
from cps.remotelogin import remotelogin
|
||||||
from cps.error_handler import init_errorhandler
|
from cps.error_handler import init_errorhandler
|
||||||
|
from cps.schedule import register_jobs
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cps.kobo import kobo, get_kobo_activated
|
from cps.kobo import kobo, get_kobo_activated
|
||||||
|
@ -78,6 +79,10 @@ def main():
|
||||||
app.register_blueprint(kobo_auth)
|
app.register_blueprint(kobo_auth)
|
||||||
if oauth_available:
|
if oauth_available:
|
||||||
app.register_blueprint(oauth)
|
app.register_blueprint(oauth)
|
||||||
|
|
||||||
|
# Register scheduled jobs
|
||||||
|
register_jobs()
|
||||||
|
|
||||||
success = web_server.start()
|
success = web_server.start()
|
||||||
sys.exit(0 if success else 1)
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,6 @@ from flask_principal import Principal
|
||||||
from . import config_sql, logger, cache_buster, cli, ub, db
|
from . import config_sql, logger, cache_buster, cli, ub, db
|
||||||
from .reverseproxy import ReverseProxied
|
from .reverseproxy import ReverseProxied
|
||||||
from .server import WebServer
|
from .server import WebServer
|
||||||
from .services.background_scheduler import BackgroundScheduler
|
|
||||||
from .tasks.thumbnail import TaskThumbnail
|
|
||||||
|
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
|
@ -117,10 +115,6 @@ def create_app():
|
||||||
config.config_goodreads_api_secret,
|
config.config_goodreads_api_secret,
|
||||||
config.config_use_goodreads)
|
config.config_use_goodreads)
|
||||||
|
|
||||||
scheduler = BackgroundScheduler()
|
|
||||||
# Generate 100 book cover thumbnails every 5 minutes
|
|
||||||
scheduler.add_task(user=None, task=lambda: TaskThumbnail(config=config, limit=100), trigger='interval', minutes=5)
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ except ImportError:
|
||||||
|
|
||||||
from . import calibre_db
|
from . import calibre_db
|
||||||
from .tasks.convert import TaskConvert
|
from .tasks.convert import TaskConvert
|
||||||
from . import logger, config, get_locale, db, ub
|
from . import logger, config, get_locale, db, thumbnails, ub
|
||||||
from . import gdriveutils as gd
|
from . import gdriveutils as gd
|
||||||
from .constants import STATIC_DIR as _STATIC_DIR
|
from .constants import STATIC_DIR as _STATIC_DIR
|
||||||
from .subproc_wrapper import process_wait
|
from .subproc_wrapper import process_wait
|
||||||
|
@ -538,24 +538,27 @@ def get_cover_on_failure(use_generic_cover):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover(book_id):
|
def get_book_cover(book_id, resolution=1):
|
||||||
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
|
return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution)
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover_with_uuid(book_uuid,
|
def get_book_cover_with_uuid(book_uuid, use_generic_cover_on_failure=True):
|
||||||
use_generic_cover_on_failure=True):
|
|
||||||
book = calibre_db.get_book_by_uuid(book_uuid)
|
book = calibre_db.get_book_by_uuid(book_uuid)
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure)
|
return get_book_cover_internal(book, use_generic_cover_on_failure)
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover_internal(book, use_generic_cover_on_failure):
|
def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=1, disable_thumbnail=False):
|
||||||
if book and book.has_cover:
|
if book and book.has_cover:
|
||||||
# if thumbnails.cover_thumbnail_exists_for_book(book):
|
|
||||||
# thumbnail = ub.session.query(ub.Thumbnail).filter(ub.Thumbnail.book_id == book.id).first()
|
# Send the book cover thumbnail if it exists in cache
|
||||||
# return send_from_directory(thumbnails.get_thumbnail_cache_dir(), thumbnail.filename)
|
if not disable_thumbnail:
|
||||||
# else:
|
thumbnail = get_book_cover_thumbnail(book, resolution)
|
||||||
# WorkerThread.add(None, TaskThumbnail(book, _(u'Generating cover thumbnail for: ' + book.title)))
|
if thumbnail:
|
||||||
|
if os.path.isfile(thumbnails.get_thumbnail_cache_path(thumbnail)):
|
||||||
|
return send_from_directory(thumbnails.get_thumbnail_cache_dir(), thumbnail.filename)
|
||||||
|
|
||||||
|
# Send the book cover from Google Drive if configured
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
try:
|
try:
|
||||||
if not gd.is_gdrive_ready():
|
if not gd.is_gdrive_ready():
|
||||||
|
@ -569,6 +572,8 @@ def get_book_cover_internal(book, use_generic_cover_on_failure):
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.debug_or_exception(ex)
|
log.debug_or_exception(ex)
|
||||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
return get_cover_on_failure(use_generic_cover_on_failure)
|
||||||
|
|
||||||
|
# Send the book cover from the Calibre directory
|
||||||
else:
|
else:
|
||||||
cover_file_path = os.path.join(config.config_calibre_dir, book.path)
|
cover_file_path = os.path.join(config.config_calibre_dir, book.path)
|
||||||
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
|
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
|
||||||
|
@ -579,6 +584,16 @@ def get_book_cover_internal(book, use_generic_cover_on_failure):
|
||||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
return get_cover_on_failure(use_generic_cover_on_failure)
|
||||||
|
|
||||||
|
|
||||||
|
def get_book_cover_thumbnail(book, resolution=1):
|
||||||
|
if book and book.has_cover:
|
||||||
|
return ub.session\
|
||||||
|
.query(ub.Thumbnail)\
|
||||||
|
.filter(ub.Thumbnail.book_id == book.id)\
|
||||||
|
.filter(ub.Thumbnail.resolution == resolution)\
|
||||||
|
.filter(ub.Thumbnail.expiration > datetime.utcnow())\
|
||||||
|
.first()
|
||||||
|
|
||||||
|
|
||||||
# saves book cover from url
|
# saves book cover from url
|
||||||
def save_cover_from_url(url, book_path):
|
def save_cover_from_url(url, book_path):
|
||||||
try:
|
try:
|
||||||
|
|
34
cps/schedule.py
Normal file
34
cps/schedule.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2020 mmonkey
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from . import logger
|
||||||
|
from .services.background_scheduler import BackgroundScheduler
|
||||||
|
from .tasks.thumbnail import TaskThumbnail
|
||||||
|
|
||||||
|
log = logger.create()
|
||||||
|
|
||||||
|
|
||||||
|
def register_jobs():
|
||||||
|
scheduler = BackgroundScheduler()
|
||||||
|
|
||||||
|
# Generate 100 book cover thumbnails every 5 minutes
|
||||||
|
scheduler.add_task(user=None, task=lambda: TaskThumbnail(limit=100), trigger='interval', minutes=5)
|
||||||
|
|
||||||
|
# TODO: validate thumbnail scheduled task
|
|
@ -19,11 +19,13 @@
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from cps import db, logger, ub
|
from cps import config, db, gdriveutils, logger, ub
|
||||||
from cps.constants import CACHE_DIR as _CACHE_DIR
|
from cps.constants import CACHE_DIR as _CACHE_DIR
|
||||||
from cps.services.worker import CalibreTask
|
from cps.services.worker import CalibreTask
|
||||||
|
from cps.thumbnails import THUMBNAIL_RESOLUTION_1X, THUMBNAIL_RESOLUTION_2X
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from wand.image import Image
|
from wand.image import Image
|
||||||
|
@ -31,14 +33,10 @@ try:
|
||||||
except (ImportError, RuntimeError) as e:
|
except (ImportError, RuntimeError) as e:
|
||||||
use_IM = False
|
use_IM = False
|
||||||
|
|
||||||
THUMBNAIL_RESOLUTION_1X = 1.0
|
|
||||||
THUMBNAIL_RESOLUTION_2X = 2.0
|
|
||||||
|
|
||||||
|
|
||||||
class TaskThumbnail(CalibreTask):
|
class TaskThumbnail(CalibreTask):
|
||||||
def __init__(self, config, limit=100, task_message=u'Generating cover thumbnails'):
|
def __init__(self, limit=100, task_message=u'Generating cover thumbnails'):
|
||||||
super(TaskThumbnail, self).__init__(task_message)
|
super(TaskThumbnail, self).__init__(task_message)
|
||||||
self.config = config
|
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.log = logger.create()
|
self.log = logger.create()
|
||||||
self.app_db_session = ub.get_new_session_instance()
|
self.app_db_session = ub.get_new_session_instance()
|
||||||
|
@ -114,17 +112,39 @@ class TaskThumbnail(CalibreTask):
|
||||||
|
|
||||||
def generate_book_thumbnail(self, book, thumbnail):
|
def generate_book_thumbnail(self, book, thumbnail):
|
||||||
if book and thumbnail:
|
if book and thumbnail:
|
||||||
if self.config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
self.log.info('google drive thumbnail')
|
if not gdriveutils.is_gdrive_ready():
|
||||||
else:
|
raise Exception('Google Drive is configured but not ready')
|
||||||
book_cover_filepath = os.path.join(self.config.config_calibre_dir, book.path, 'cover.jpg')
|
|
||||||
if os.path.isfile(book_cover_filepath):
|
web_content_link = gdriveutils.get_cover_via_gdrive(book.path)
|
||||||
with Image(filename=book_cover_filepath) as img:
|
if not web_content_link:
|
||||||
|
raise Exception('Google Drive cover url not found')
|
||||||
|
|
||||||
|
stream = None
|
||||||
|
try:
|
||||||
|
stream = urlopen(web_content_link)
|
||||||
|
with Image(file=stream) as img:
|
||||||
height = self.get_thumbnail_height(thumbnail)
|
height = self.get_thumbnail_height(thumbnail)
|
||||||
if img.height > height:
|
if img.height > height:
|
||||||
width = self.get_thumbnail_width(height, img)
|
width = self.get_thumbnail_width(height, img)
|
||||||
img.resize(width=width, height=height, filter='lanczos')
|
img.resize(width=width, height=height, filter='lanczos')
|
||||||
img.save(filename=self.get_thumbnail_cache_path(thumbnail))
|
img.save(filename=self.get_thumbnail_cache_path(thumbnail))
|
||||||
|
except Exception as ex:
|
||||||
|
# Bubble exception to calling function
|
||||||
|
raise ex
|
||||||
|
finally:
|
||||||
|
stream.close()
|
||||||
|
else:
|
||||||
|
book_cover_filepath = os.path.join(config.config_calibre_dir, book.path, 'cover.jpg')
|
||||||
|
if not os.path.isfile(book_cover_filepath):
|
||||||
|
raise Exception('Book cover file not found')
|
||||||
|
|
||||||
|
with Image(filename=book_cover_filepath) as img:
|
||||||
|
height = self.get_thumbnail_height(thumbnail)
|
||||||
|
if img.height > height:
|
||||||
|
width = self.get_thumbnail_width(height, img)
|
||||||
|
img.resize(width=width, height=height, filter='lanczos')
|
||||||
|
img.save(filename=self.get_thumbnail_cache_path(thumbnail))
|
||||||
|
|
||||||
def get_thumbnail_height(self, thumbnail):
|
def get_thumbnail_height(self, thumbnail):
|
||||||
return int(225 * thumbnail.resolution)
|
return int(225 * thumbnail.resolution)
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
|
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
|
{{ book_cover_image(entry.id, entry.title) }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|
8
cps/templates/book_cover.html
Normal file
8
cps/templates/book_cover.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{% macro book_cover_image(book_id, book_title) -%}
|
||||||
|
<img
|
||||||
|
srcset="{{ url_for('web.get_cover', book_id=book_id, resolution=1) }} 1x,
|
||||||
|
{{ url_for('web.get_cover', book_id=book_id, resolution=2) }} 2x"
|
||||||
|
src="{{ url_for('web.get_cover', book_id=book_id) }}"
|
||||||
|
alt="{{ book_title }}"
|
||||||
|
/>
|
||||||
|
{%- endmacro %}
|
|
@ -1,9 +1,11 @@
|
||||||
|
{% from 'book_cover.html' import book_cover_image %}
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% if book %}
|
{% if book %}
|
||||||
<div class="col-sm-3 col-lg-3 col-xs-12">
|
<div class="col-sm-3 col-lg-3 col-xs-12">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/>
|
{{ book_cover_image(book.id, book.title) }}
|
||||||
|
<!-- <img src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/>-->
|
||||||
</div>
|
</div>
|
||||||
{% if g.user.role_delete_books() %}
|
{% if g.user.role_delete_books() %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-3 col-lg-3 col-xs-5">
|
<div class="col-sm-3 col-lg-3 col-xs-5">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />
|
{{ book_cover_image(entry.id, entry.title) }}
|
||||||
|
<!-- <img src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-9 col-lg-9 book-meta">
|
<div class="col-sm-9 col-lg-9 book-meta">
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% from 'book_cover.html' import book_cover_image %}
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="discover load-more">
|
<div class="discover load-more">
|
||||||
|
@ -8,7 +9,7 @@
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
{% if entry.has_cover is defined %}
|
{% if entry.has_cover is defined %}
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
{{ book_cover_image(entry.id, entry.title) }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% from 'book_cover.html' import book_cover_image %}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% from 'book_cover.html' import book_cover_image %}
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="{{page}}">{{_(title)}}</h1>
|
<h1 class="{{page}}">{{_(title)}}</h1>
|
||||||
|
@ -28,7 +29,7 @@
|
||||||
<div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}">
|
<div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{url_for('web.books_list', data=data, sort_param='new', book_id=entry[0].series[0].id )}}">
|
<a href="{{url_for('web.books_list', data=data, sort_param='new', book_id=entry[0].series[0].id )}}">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
|
{{ book_cover_image(entry[0].id, entry[0].name) }}
|
||||||
<span class="badge">{{entry.count}}</span>
|
<span class="badge">{{entry.count}}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% from 'book_cover.html' import book_cover_image %}
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% if g.user.show_detail_random() %}
|
{% if g.user.show_detail_random() %}
|
||||||
|
@ -8,7 +9,7 @@
|
||||||
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
|
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
{{ book_cover_image(entry.id, entry.title) }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
@ -82,7 +83,7 @@
|
||||||
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
|
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/>
|
{{ book_cover_image(entry.id, entry.title) }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal %}
|
{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal %}
|
||||||
|
{% from 'book_cover.html' import book_cover_image %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ g.user.locale }}">
|
<html lang="{{ g.user.locale }}">
|
||||||
<head>
|
<head>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% from 'book_cover.html' import book_cover_image %}
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="discover">
|
<div class="discover">
|
||||||
|
@ -43,7 +44,7 @@
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
{% if entry.has_cover is defined %}
|
{% if entry.has_cover is defined %}
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
{{ book_cover_image(entry.id, entry.title) }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% from 'book_cover.html' import book_cover_image %}
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="discover">
|
<div class="discover">
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||||
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
|
{{ book_cover_image(entry.id, entry.title) }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|
|
@ -21,13 +21,11 @@ import os
|
||||||
|
|
||||||
from . import logger, ub
|
from . import logger, ub
|
||||||
from .constants import CACHE_DIR as _CACHE_DIR
|
from .constants import CACHE_DIR as _CACHE_DIR
|
||||||
from .services.worker import WorkerThread
|
|
||||||
from .tasks.thumbnail import TaskThumbnail
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
THUMBNAIL_RESOLUTION_1X = 1.0
|
THUMBNAIL_RESOLUTION_1X = 1
|
||||||
THUMBNAIL_RESOLUTION_2X = 2.0
|
THUMBNAIL_RESOLUTION_2X = 2
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
|
@ -35,17 +33,14 @@ log = logger.create()
|
||||||
def get_thumbnail_cache_dir():
|
def get_thumbnail_cache_dir():
|
||||||
if not os.path.isdir(_CACHE_DIR):
|
if not os.path.isdir(_CACHE_DIR):
|
||||||
os.makedirs(_CACHE_DIR)
|
os.makedirs(_CACHE_DIR)
|
||||||
|
|
||||||
if not os.path.isdir(os.path.join(_CACHE_DIR, 'thumbnails')):
|
if not os.path.isdir(os.path.join(_CACHE_DIR, 'thumbnails')):
|
||||||
os.makedirs(os.path.join(_CACHE_DIR, 'thumbnails'))
|
os.makedirs(os.path.join(_CACHE_DIR, 'thumbnails'))
|
||||||
|
|
||||||
return os.path.join(_CACHE_DIR, 'thumbnails')
|
return os.path.join(_CACHE_DIR, 'thumbnails')
|
||||||
|
|
||||||
|
|
||||||
def get_thumbnail_cache_path(thumbnail):
|
def get_thumbnail_cache_path(thumbnail):
|
||||||
if thumbnail:
|
if thumbnail:
|
||||||
return os.path.join(get_thumbnail_cache_dir(), thumbnail.filename)
|
return os.path.join(get_thumbnail_cache_dir(), thumbnail.filename)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ except ImportError:
|
||||||
oauth_support = False
|
oauth_support = False
|
||||||
from sqlalchemy import create_engine, exc, exists, event
|
from sqlalchemy import create_engine, exc, exists, event
|
||||||
from sqlalchemy import Column, ForeignKey
|
from sqlalchemy import Column, ForeignKey
|
||||||
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, JSON, Numeric
|
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, JSON
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.ext.hybrid import hybrid_property
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
@ -442,7 +442,7 @@ class Thumbnail(Base):
|
||||||
book_id = Column(Integer)
|
book_id = Column(Integer)
|
||||||
uuid = Column(String, default=lambda: str(uuid.uuid4()), unique=True)
|
uuid = Column(String, default=lambda: str(uuid.uuid4()), unique=True)
|
||||||
format = Column(String, default='jpeg')
|
format = Column(String, default='jpeg')
|
||||||
resolution = Column(Numeric(precision=2, scale=1, asdecimal=False), default=1.0)
|
resolution = Column(SmallInteger, default=1)
|
||||||
expiration = Column(DateTime, default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30))
|
expiration = Column(DateTime, default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30))
|
||||||
|
|
||||||
@hybrid_property
|
@hybrid_property
|
||||||
|
|
11
cps/web.py
11
cps/web.py
|
@ -1171,14 +1171,17 @@ def advanced_search_form():
|
||||||
|
|
||||||
|
|
||||||
@web.route("/cover/<int:book_id>")
|
@web.route("/cover/<int:book_id>")
|
||||||
|
@web.route("/cover/<int:book_id>/<int:resolution>")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def get_cover(book_id):
|
def get_cover(book_id, resolution=1):
|
||||||
return get_book_cover(book_id)
|
return get_book_cover(book_id, resolution)
|
||||||
|
|
||||||
|
|
||||||
@web.route("/robots.txt")
|
@web.route("/robots.txt")
|
||||||
def get_robots():
|
def get_robots():
|
||||||
return send_from_directory(constants.STATIC_DIR, "robots.txt")
|
return send_from_directory(constants.STATIC_DIR, "robots.txt")
|
||||||
|
|
||||||
|
|
||||||
@web.route("/show/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
|
@web.route("/show/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
|
||||||
@web.route("/show/<int:book_id>/<book_format>/<anyname>")
|
@web.route("/show/<int:book_id>/<book_format>/<anyname>")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
|
@ -1205,7 +1208,6 @@ def serve_book(book_id, book_format, anyname):
|
||||||
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/download/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
|
@web.route("/download/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
|
||||||
@web.route("/download/<int:book_id>/<book_format>/<anyname>")
|
@web.route("/download/<int:book_id>/<book_format>/<anyname>")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
|
@ -1387,9 +1389,6 @@ def logout():
|
||||||
return redirect(url_for('web.login'))
|
return redirect(url_for('web.login'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ################################### Users own configuration #########################################################
|
# ################################### Users own configuration #########################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user