Skip to content

Commit

Permalink
add server-side decode support
Browse files Browse the repository at this point in the history
  • Loading branch information
URenko committed Jul 4, 2023
1 parent 0cbe096 commit f66c7b9
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 25 deletions.
3 changes: 0 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ Maybe multi-user?

## Plugin exception handling

## Better JPEG XL support
Server-side decode

## Other formats
EPUB...

Expand Down
8 changes: 6 additions & 2 deletions comiclib/config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from pydantic import BaseSettings

import re

class Settings(BaseSettings):
debug: bool = False
skip_exits: bool = True
content: str = '.'
thumb: str = './thumb'

UA_convert_jxl: str = 'Android'
UA_convert_all: str = r'\b\B' # default: match nothing

settings = Settings()

if settings.debug:
print(settings)

settings.UA_convert_jxl = re.compile(settings.UA_convert_jxl)
settings.UA_convert_all = re.compile(settings.UA_convert_all)
34 changes: 28 additions & 6 deletions comiclib/main.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
from .scan import watch, scan
from .config import settings
from .utils import extract_thumbnail
from .utils import extract_thumbnail, convert_image
from typing import Union, Annotated
from enum import Enum
from pathlib import Path
from zipfile import ZipFile
import re
import asyncio
import tempfile
from urllib.parse import quote, unquote, urlparse

from fastapi import FastAPI, Cookie, Request, Query, Depends, BackgroundTasks, Response, status, Form
from fastapi.responses import HTMLResponse, FileResponse, StreamingResponse, JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager

from template import Template

Expand All @@ -22,8 +24,15 @@
from sqlalchemy.orm import Session
Base.metadata.create_all(bind=engine)

import tempfile
@asynccontextmanager
async def lifespan(app: FastAPI):
global cache_dir
with tempfile.TemporaryDirectory() as cache_dir:
cache_dir = Path(cache_dir)
yield

app = FastAPI()
app = FastAPI(lifespan=lifespan)
app_path = Path(__file__).parent
app.mount("/css", StaticFiles(directory=app_path / "LANraragi/public/css"))
app.mount("/img", StaticFiles(directory=app_path / "LANraragi/public/img"))
Expand Down Expand Up @@ -253,17 +262,30 @@ def extract_archive(id: str, force: bool, db: Session = Depends(get_db)):


@app.get("/api/archives/{id}/page")
def get_archive_page(id: str, path: str, db: Session = Depends(get_db)):
def get_archive_page(request: Request, id: str, path: str, db: Session = Depends(get_db)):
a = db.get(Archive, id)
if a is None:
return JSONResponse({"operation": "", "error": "This ID doesn't exist on the server.", "success": 0}, status.HTTP_400_BAD_REQUEST)
db.close()
p = Path(settings.content) / a.path
path = unquote(path)
UA = request.headers.get("user-agent", "")
if not re.search(settings.UA_convert_all, UA) is None or (path.endswith('.jxl') and not re.search(settings.UA_convert_jxl, UA) is None):
saveto = (cache_dir / a.path / path).with_suffix('.webp')
if not saveto.exists():
saveto.parent.mkdir(parents=True, exist_ok=True)
if p.is_dir():
convert_image(p / path, saveto)
elif p.suffix == '.zip':
with ZipFile(p) as z, z.open(path) as f:
convert_image(f, saveto)
else:
raise NotImplementedError
return FileResponse(saveto)
if p.suffix == '.zip':
def iterfile():
with ZipFile(p) as z:
with z.open(path) as f:
yield from f
with ZipFile(p) as z, z.open(path) as f:
yield from f
return StreamingResponse(iterfile())
else:
return FileResponse(p / path)
Expand Down
34 changes: 20 additions & 14 deletions comiclib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@

from .config import settings

def convert_image(f_or_path, saveto: str, thumbnail=False):
try:
with Image.open(f_or_path) as im:
if thumbnail:
im.thumbnail((500, 709))
im.save(saveto)
except PIL.UnidentifiedImageError:
if not isinstance(f_or_path, Path):
with tempfile.NamedTemporaryFile() as tmpf: # libjxl has problem with pipe yet
f_or_path.seek(0)
shutil.copyfileobj(f_or_path, tmpf)
tmpf.flush()
cmd = ['ffmpeg', '-i', tmpf.name, '-vf', 'scale=500:-1', str(saveto), '-y'] if thumbnail else ['ffmpeg', '-i', tmpf.name, str(saveto), '-y']
subprocess.run(cmd, check=True, stderr=None if settings.debug else subprocess.DEVNULL)
else:
cmd = ['ffmpeg', '-i', str(f_or_path), '-vf', 'scale=500:-1', str(saveto), '-y'] if thumbnail else ['ffmpeg', '-i', str(f_or_path), str(saveto), '-y']
subprocess.run(cmd, check=True, stderr=None if settings.debug else subprocess.DEVNULL)


def extract_thumbnail(path: Union[str, Path], id: str, page: int, cache=False) -> Path:
path = Path(path)
saveto = Path(settings.thumb) / (id+'.webp') if page == 1 else Path(settings.thumb) / id / f'{page}.webp'
Expand All @@ -25,20 +44,7 @@ def extract_thumbnail(path: Union[str, Path], id: str, page: int, cache=False) -
elif path.suffix == '.zip':
with ZipFile(path) as z:
with z.open(list(filter(lambda z_info: not z_info.is_dir(), z.infolist()))[page-1].filename) as f:
try:
with Image.open(f) as im:
im.thumbnail((500, 709))
im.save(saveto)
except PIL.UnidentifiedImageError:
with tempfile.NamedTemporaryFile() as tmpf: # libjxl has problem with pipe yet
f.seek(0)
shutil.copyfileobj(f, tmpf)
tmpf.flush()
cmd = ['ffmpeg', '-i', tmpf.name, '-vf', 'scale=500:-1', str(saveto), '-y']
# proc = await asyncio.create_subprocess_exec(*cmd, stderr=None if settings.debug else subprocess.DEVNULL)
# if not (returncode := await proc.wait()) == 0:
# raise subprocess.CalledProcessError(returncode, cmd)
subprocess.run(cmd, check=True, stderr=None if settings.debug else subprocess.DEVNULL)
convert_image(f, saveto, thumbnail=True)
return saveto.relative_to(settings.thumb)
else:
raise NotImplementedError

0 comments on commit f66c7b9

Please sign in to comment.