diff --git a/dlibrary.py b/dlibrary.py index aff179c..0e57883 100755 --- a/dlibrary.py +++ b/dlibrary.py @@ -5,12 +5,14 @@ import asyncio from pathlib import Path from os.path import relpath, splitext import re +import shutil import sqlite3 from urllib.parse import urlparse import zipfile from dlsite_async import DlsiteAPI import fitz +from jinja2 import Environment, FileSystemLoader, select_autoescape import requests NUMBER_REGEX = re.compile('[0-9]+') @@ -159,7 +161,7 @@ def collate(args): for work_path in extraction_dir.iterdir(): work_id = work_path.name - collation_dir = args.destdir / 'site' / 'works' / work_id + collation_dir = args.destdir / 'site' / 'images' / work_id if collation_dir.exists(): continue @@ -200,7 +202,7 @@ def collate(args): con.close() def manual_collate(args): - collation_dir = args.destdir / 'site' / 'works' / args.work_id + collation_dir = args.destdir / 'site' / 'images' / args.work_id if collation_dir.exists() and len(list(collation_dir.iterdir())) > 0: print(f'Collation directory already exists!') return @@ -256,7 +258,55 @@ def metadata(args): con.close() def publish(args): - pass + source_dir = Path(__file__).parent + + jenv = Environment( + loader=FileSystemLoader(source_dir / "templates"), + autoescape=select_autoescape() + ) + + viewer_template = jenv.get_template("viewer.html") + + con = sqlite3.connect(args.destdir / 'meta.db') + cur = con.cursor() + + collated_work_ids = {p.name for p in (args.destdir / 'site' / 'images').iterdir()} + + works = [] + for (work_id, title, circle, date, description, series) in cur.execute('SELECT id, title, circle, date, description, series FROM works ORDER BY date DESC').fetchall(): + if work_id not in collated_work_ids: + continue + authors = [author for (author,) in cur.execute('SELECT author FROM authors WHERE work = ?', (work_id,))] + tags = [tag for (tag,) in cur.execute('SELECT tag FROM tags WHERE work = ?', (work_id,))] + work = { + 'id': work_id, + 'title': title, + 'circle': circle, + 'date': date, + 'description': description, + 'series': series, + 'authors': authors, + 'tags': tags, + } + works.append(work) + + images = [path.name for path in (args.destdir / 'site' / 'images' / work_id).iterdir()] + images.sort() + + work_dir = args.destdir / 'site' / 'works' / work_id + work_dir.mkdir(parents=True, exist_ok=True) + with open(work_dir / 'index.html', 'w') as f: + f.write(viewer_template.render(depth=2, work=work, title=title, images=images)) + + shutil.copytree(source_dir / 'static', args.destdir / 'site' / 'static', dirs_exist_ok=True) + + list_template = jenv.get_template("list.html") + + with open(args.destdir / 'site' / 'index.html', 'w') as f: + f.write(list_template.render(depth=0, works=works)) + + con.close() + argparser = argparse.ArgumentParser(prog='dlibrary') argparser.add_argument( diff --git a/flake.nix b/flake.nix index 6f3863f..8ec9a08 100644 --- a/flake.nix +++ b/flake.nix @@ -52,6 +52,7 @@ pymupdf requests dlsite-async + jinja2 ]; src = ./.; }; diff --git a/requirements.txt b/requirements.txt index 534d29c..75ceaf5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ requests PyMuPDF dlsite-async +jinja2 diff --git a/static/dlibrary.css b/static/dlibrary.css new file mode 100644 index 0000000..6edb8d2 --- /dev/null +++ b/static/dlibrary.css @@ -0,0 +1,76 @@ +body { + background: #111; + color: #eee; + font-family: sans-serif; +} + +/* listing stuff */ + +#list-title { + text-align: center; +} + +.card-listing { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 20px; +} + +.card { + background: #333; + padding: 10px; + flex-grow: 1; + max-width: 360px; + text-align: center; + font-weight: bold; + font-size: 18px; +} + +.card img { + max-width: 100%; + max-height: 100%; +} + +/* viewer stuff */ + +#viewer-images { + display: none; +} + +#image-container { + height: 100vh; + width: 100vw; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} + +#page-num, #duration { + position: fixed; + font-size: 14pt; + top: 10px; + font-weight: bold; + opacity: 0.75; + text-shadow: /* Duplicate the same shadow to make it very strong */ + 0 0 2px #222, + 0 0 2px #222, + 0 0 2px #222; +} + +#page-num { + left: 10px; +} + +#duration { + right: 10px; +} + +#progress { + background-color: #4488ffcc; + height: 5px; + width: 0; + position: fixed; + top: 0; + left: 0; +} diff --git a/static/viewer.css b/static/viewer.css new file mode 100644 index 0000000..9045ee9 --- /dev/null +++ b/static/viewer.css @@ -0,0 +1,6 @@ +html, body { + height: 100%; + width: 100%; + padding: 0; + margin: 0; +} diff --git a/static/viewer.js b/static/viewer.js new file mode 100644 index 0000000..89760d2 --- /dev/null +++ b/static/viewer.js @@ -0,0 +1,120 @@ +document.addEventListener('DOMContentLoaded', () => { + const pages = Array.from(document.querySelectorAll('img.viewer-image')); + let currentPage = parseInt(localStorage.getItem(`${WORK_ID}-currentPage`)) || 0; + let duration = parseInt(localStorage.getItem(`${WORK_ID}-duration`)) || 10; + let paused = true; + let interval; + let elapsed = 0; + + function startTimer() { + if (interval) { + clearInterval(interval); + } + interval = setInterval( + function () { + if (paused) { + return; + } + elapsed += 100; + if (elapsed >= duration*1000) { + changePage(currentPage + 1); + } + updateBar(); + }, + 100 + ); + } + + const progressBar = document.getElementById('progress'); + function updateBar() { + progressBar.style.width = `${100*elapsed/(1000*duration)}%`; + } + + function stopTimer() { + if (interval) { + clearInterval(interval); + interval = null; + } + elapsed = 0; + updateBar(); + } + + function changePage(pageNum) { + elapsed = 0; + + const previous = pages[currentPage]; + const current = pages[pageNum]; + + if (current == null) { + return; + } + + previous.classList.remove('current'); + current.classList.add('current'); + + currentPage = pageNum; + localStorage.setItem(`${WORK_ID}-currentPage`, currentPage); + + const display = document.getElementById('image-container'); + display.style.backgroundImage = `url("${current.src}")`; + + document.getElementById('page-num') + .innerText = [ + (pageNum + 1).toLocaleString(), + pages.length.toLocaleString() + ].join('\u200a/\u200a'); + } + + function changeDuration(secs, pause) { + duration = secs; + localStorage.setItem(`${WORK_ID}-duration`, duration); + paused = pause; + + document.getElementById('duration').textContent = (paused ? '[paused] ' : '') + duration.toLocaleString() + 's'; + if (paused) { + stopTimer(); + } else { + startTimer(); + } + } + + changePage(currentPage); + changeDuration(duration, paused); + + document.onkeydown = event =>{ + switch (event.keyCode) { + case 32: //space + changeDuration(duration, !paused); + break; + case 37: //left + changePage(currentPage - 1); + break; + case 38: //up + if (2 <= duration && duration <= 10) { + changeDuration(duration - 1, false); + } else if (10 < duration && duration <= 20) { + changeDuration(duration - 2.5, false); + } else if (20 < duration) { + changeDuration(duration - 5, false); + } + break; + case 39: //right + changePage(currentPage + 1); + break; + case 40: //down + if (duration < 10) { + changeDuration(duration + 1, false); + } else if (10 <= duration && duration < 20) { + changeDuration(duration + 2.5, false); + } else if (20 <= duration) { + changeDuration(duration + 5, false); + } + break; + case 13: //enter + changeDuration(duration, true); + localStorage.setItem(`${WORK_ID}-currentPage`, 0); + window.location.href = ROOT; + break; + } + }; +}); diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..558de22 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,15 @@ +{% from 'utils.html' import root with context -%} + + + + + + + {% if title %}{{ title }} - {% else %}{% endif %}DLibrary + + {% block head %}{% endblock %} + + + {% block body required %}{% endblock %} + + diff --git a/templates/list.html b/templates/list.html new file mode 100644 index 0000000..ea3cd6e --- /dev/null +++ b/templates/list.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% block body %} +{% from 'utils.html' import root with context %} +

DLibrary {% block list_title %}{% endblock %}

+
+ {% for work in works %} +
+ + +
+ [{% if work['circle'] %}{{ work['circle'] }}{% endif %}{% if work['circle'] and work['authors'] %} ({% endif %}{{ ', '.join(work['authors']) }}{% if work['circle'] and work['authors'] %}){% endif %}] +
+
+ {{ work['title'] }} +
+
+
+ {% endfor %} +
+{% endblock %} diff --git a/templates/utils.html b/templates/utils.html new file mode 100644 index 0000000..7990bda --- /dev/null +++ b/templates/utils.html @@ -0,0 +1 @@ +{% macro root() %}{% for i in range(depth) %}../{% endfor %}{% endmacro %} diff --git a/templates/viewer.html b/templates/viewer.html new file mode 100644 index 0000000..3ff1918 --- /dev/null +++ b/templates/viewer.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% from 'utils.html' import root with context %} +{% block head %} + + + +{% endblock %} +{% block body %} +
+ {% for filename in images %} + + {% endfor %} +
+
+
+
+
+{% endblock %}