Merge branch 'main' into deldesir-youtube-download
This commit is contained in:
commit
d522e0a1ca
|
@ -142,14 +142,17 @@ except ValueError:
|
|||
del env_CALIBRE_PORT
|
||||
|
||||
|
||||
EXTENSIONS_AUDIO = {'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac', 'm4a', 'm4b'}
|
||||
EXTENSIONS_AUDIO = {'mp3', 'ogg', 'opus', 'wav', 'flac', 'm4a', 'm4b'}
|
||||
EXTENSIONS_VIDEO = {'mp4', 'webm', 'avi', 'mkv', 'm4v', 'mpg', 'mpeg', 'ogv'}
|
||||
EXTENSIONS_IMAGE = {'jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'}
|
||||
EXTENSIONS_CONVERT_FROM = ['pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf',
|
||||
'txt', 'htmlz', 'rtf', 'odt', 'cbz', 'cbr']
|
||||
EXTENSIONS_CONVERT_TO = ['pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2',
|
||||
'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt']
|
||||
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'cb7', 'djvu', 'djv',
|
||||
'prc', 'doc', 'docx', 'fb2', 'html', 'rtf', 'lit', 'odt', 'mp3', 'mp4', 'ogg',
|
||||
'opus', 'wav', 'flac', 'm4a', 'm4b'}
|
||||
'opus', 'wav', 'flac', 'm4a', 'm4b', "avi", "mkv", "webm", "m4v", "mov", "wmv", "mpg",
|
||||
"mpeg", "flv", "3gp", "3g2", "ogv", "jpg", "jpeg", "png", "gif", "svg", "webp"}
|
||||
|
||||
|
||||
def has_flag(value, bit_flag):
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import smartcrop
|
||||
from datetime import datetime
|
||||
import json
|
||||
from PIL import Image
|
||||
from shutil import copyfile
|
||||
from uuid import uuid4
|
||||
from markupsafe import escape # dependency of flask
|
||||
|
@ -972,9 +974,19 @@ def move_coverfile(meta, db_book):
|
|||
else:
|
||||
cover_file = os.path.join(constants.STATIC_DIR, 'generic_cover.jpg')
|
||||
new_cover_path = os.path.join(config.config_calibre_dir, db_book.path)
|
||||
|
||||
try:
|
||||
os.makedirs(new_cover_path, exist_ok=True)
|
||||
copyfile(cover_file, os.path.join(new_cover_path, "cover.jpg"))
|
||||
image = Image.open(cover_file)
|
||||
|
||||
# crop image to square 150x150 using smartcrop
|
||||
cropper = smartcrop.SmartCrop()
|
||||
result = cropper.crop(image, 150, 150)
|
||||
x, y, width, height = result['top_crop']['x'], result['top_crop']['y'], \
|
||||
result['top_crop']['width'], result['top_crop']['height']
|
||||
cropped_image = image.crop((x, y, x + width, y + height))
|
||||
cropped_image.save(os.path.join(new_cover_path, "cover.jpg"))
|
||||
|
||||
if meta.cover:
|
||||
os.unlink(meta.cover)
|
||||
except OSError as e:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
.tooltip.bottom .tooltip-inner {
|
||||
font-size: 13px;
|
||||
font-family: Open Sans Semibold, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
|
@ -140,15 +139,36 @@ table .bg-dark-danger a { color: #fff; }
|
|||
|
||||
.container-fluid .book {
|
||||
margin-top: 20px;
|
||||
max-width: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 10px;
|
||||
max-width: 150px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container-fluid .book:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.cover { margin-bottom: 10px; }
|
||||
|
||||
.container-fluid .book .cover {
|
||||
height: 225px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.container-fluid .book .cover img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.author-link img {
|
||||
|
@ -437,4 +457,4 @@ div.log {
|
|||
|
||||
.error-list {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,7 +120,61 @@
|
|||
<a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=entry.audio_entries[0]) }}" id="listenbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-music"></span> {{ _('Listen in Browser') }} - {{ entry.audio_entries[0] }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elif entry.video_entries|length > 0 and current_user.role_viewer() %}
|
||||
<div class="btn-group" role="group">
|
||||
{% if entry.video_entries|length > 1 %}
|
||||
<button id="listen-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-film"></span> {{ _('Watch in Browser') }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
|
||||
{% for format in entry.reader_list %}
|
||||
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{ format }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
|
||||
|
||||
{% for format in entry.data %}
|
||||
{% if format.format|lower in entry.video_entries %}
|
||||
<li><a target="_blank"
|
||||
href="{{ url_for('web.read_book', book_id=entry.id, book_format=format.format|lower) }}">{{ format.format|lower }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=entry.video_entries[0]) }}" id="listenbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-film"></span> {{ _('Watch in Browser') }} - {{ entry.video_entries[0] }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif entry.image_entries|length > 0 and current_user.role_viewer() %}
|
||||
<div class="btn-group" role="group">
|
||||
{% if entry.image_entries|length > 1 %}
|
||||
<button id="listen-in-browser" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-picture"></span> {{ _('View in Browser') }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
|
||||
{% for format in entry.reader_list %}
|
||||
<li><a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=format) }}">{{ format }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<ul class="dropdown-menu" aria-labelledby="listen-in-browser">
|
||||
|
||||
{% for format in entry.data %}
|
||||
{% if format.format|lower in entry.image_entries %}
|
||||
<li><a target="_blank"
|
||||
href="{{ url_for('web.read_book', book_id=entry.id, book_format=format.format|lower) }}">{{ format.format|lower }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<a target="_blank" href="{{ url_for('web.read_book', book_id=entry.id, book_format=entry.image_entries[0]) }}" id="listenbtn" class="btn btn-primary" role="button"><span class="glyphicon glyphicon-picture"></span> {{ _('View in Browser') }} - {{ entry.image_entries[0] }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="title">{{ entry.title }}</h2>
|
||||
|
|
|
@ -25,7 +25,7 @@ from tempfile import gettempdir
|
|||
from flask_babel import gettext as _
|
||||
|
||||
from . import logger, comic, isoLanguages
|
||||
from .constants import BookMeta
|
||||
from .constants import BookMeta, EXTENSIONS_IMAGE, EXTENSIONS_VIDEO
|
||||
from .helper import split_authors
|
||||
|
||||
log = logger.create()
|
||||
|
@ -89,7 +89,6 @@ def process(tmp_file_path, original_file_name, original_file_extension, rar_exec
|
|||
rar_executable)
|
||||
elif extension_upper in ['.MP4', '.WEBM', '.AVI', '.MKV', '.M4V', '.MPG', '.MPEG','.OGV']:
|
||||
meta = video_metadata(tmp_file_path, original_file_name, original_file_extension)
|
||||
|
||||
elif extension_upper in ['.JPG', '.JPEG', '.PNG', '.GIF', '.SVG', '.WEBP']:
|
||||
meta = image_metadata(tmp_file_path, original_file_name, original_file_extension)
|
||||
|
||||
|
@ -217,7 +216,7 @@ def pdf_meta(tmp_file_path, original_file_name, original_file_extension):
|
|||
cover=pdf_preview(tmp_file_path, original_file_name),
|
||||
description=subject,
|
||||
tags=tags,
|
||||
series="",
|
||||
series="",Deldesir y
|
||||
series_id="",
|
||||
languages=','.join(languages),
|
||||
publisher=publisher,
|
||||
|
@ -247,6 +246,63 @@ def pdf_preview(tmp_file_path, tmp_dir):
|
|||
log.warning('On Windows this error could be caused by missing ghostscript')
|
||||
return None
|
||||
|
||||
def video_metadata(tmp_file_path, original_file_name, original_file_extension):
|
||||
video_cover(tmp_file_path)
|
||||
meta = BookMeta(
|
||||
file_path=tmp_file_path,
|
||||
extension=original_file_extension,
|
||||
title=original_file_name,
|
||||
author='Unknown',
|
||||
cover=os.path.splitext(tmp_file_path)[0] + '.cover.jpg',
|
||||
description='',
|
||||
tags='',
|
||||
series="",
|
||||
series_id="",
|
||||
languages="",
|
||||
publisher="",
|
||||
pubdate="",
|
||||
identifiers=[])
|
||||
return meta
|
||||
|
||||
|
||||
def video_cover(tmp_file_path):
|
||||
"""generate cover image from video using ffmpeg"""
|
||||
ffmpeg_executable = os.getenv('FFMPEG_PATH', 'ffmpeg')
|
||||
ffmpeg_output_file = os.path.splitext(tmp_file_path)[0] + '.cover.jpg'
|
||||
ffmpeg_args = [
|
||||
ffmpeg_executable,
|
||||
'-i', tmp_file_path,
|
||||
'-vframes', '1',
|
||||
'-y', ffmpeg_output_file
|
||||
]
|
||||
|
||||
try:
|
||||
ffmpeg_result = run(ffmpeg_args, capture_output=True, check=True)
|
||||
log.debug(f"ffmpeg output: {ffmpeg_result.stdout}")
|
||||
|
||||
except Exception as e:
|
||||
log.warning(f"ffmpeg failed: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def image_metadata(tmp_file_path, original_file_name, original_file_extension):
|
||||
shutil.copyfile(tmp_file_path, os.path.splitext(tmp_file_path)[0] + '.cover.jpg')
|
||||
meta = BookMeta(
|
||||
file_path=tmp_file_path,
|
||||
extension=original_file_extension,
|
||||
title=original_file_name,
|
||||
author='Unknown',
|
||||
cover=os.path.splitext(tmp_file_path)[0] + '.cover.jpg',
|
||||
description='',
|
||||
tags='',
|
||||
series="",
|
||||
series_id="",
|
||||
languages="",
|
||||
publisher="",
|
||||
pubdate="",
|
||||
identifiers=[])
|
||||
return meta
|
||||
|
||||
|
||||
def video_metadata(tmp_file_path, original_file_name, original_file_extension):
|
||||
video_id = os.path.splitext(original_file_name)[0][:11]
|
||||
|
|
16
cps/web.py
16
cps/web.py
|
@ -1559,6 +1559,16 @@ def read_book(book_id, book_format):
|
|||
log.debug("Start mp3 listening for %d", book_id)
|
||||
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
|
||||
entry=entries, bookmark=bookmark)
|
||||
for fileExt in constants.EXTENSIONS_IMAGE:
|
||||
if book_format.lower() == fileExt:
|
||||
entries = calibre_db.get_filtered_book(book_id)
|
||||
log.debug("Start image viewing for %d", book_id)
|
||||
return serve_book.__closure__[0].cell_contents(book_id, book_format.lower(), anyname="")
|
||||
for fileExt in constants.EXTENSIONS_VIDEO:
|
||||
if book_format.lower() == fileExt:
|
||||
entries = calibre_db.get_filtered_book(book_id)
|
||||
log.debug("Start video watching for %d", book_id)
|
||||
return serve_book.__closure__[0].cell_contents(book_id, book_format.lower(), anyname="")
|
||||
for fileExt in ["cbr", "cbt", "cbz"]:
|
||||
if book_format.lower() == fileExt:
|
||||
all_name = str(book_id)
|
||||
|
@ -1603,9 +1613,15 @@ def show_book(book_id):
|
|||
entry.reader_list = check_read_formats(entry)
|
||||
|
||||
entry.audio_entries = []
|
||||
entry.video_entries = []
|
||||
entry.image_entries = []
|
||||
for media_format in entry.data:
|
||||
if media_format.format.lower() in constants.EXTENSIONS_AUDIO:
|
||||
entry.audio_entries.append(media_format.format.lower())
|
||||
if media_format.format.lower() in constants.EXTENSIONS_VIDEO:
|
||||
entry.video_entries.append(media_format.format.lower())
|
||||
if media_format.format.lower() in constants.EXTENSIONS_IMAGE:
|
||||
entry.image_entries.append(media_format.format.lower())
|
||||
|
||||
return render_title_template('detail.html',
|
||||
entry=entry,
|
||||
|
|
|
@ -17,3 +17,5 @@ flask-wtf>=0.14.2,<1.2.0
|
|||
chardet>=3.0.0,<4.1.0
|
||||
advocate>=1.0.0,<1.1.0
|
||||
Flask-Limiter>=2.3.0,<3.4.0
|
||||
smartcrop>=0.4.0
|
||||
Pillow>=10.0.0
|
||||
|
|
Loading…
Reference in New Issue