we've got a website, sorta!

This commit is contained in:
xenofem 2024-01-22 07:01:41 -05:00
parent 7680a174fc
commit 6c94a346c4
10 changed files with 314 additions and 3 deletions

View file

@ -5,12 +5,14 @@ import asyncio
from pathlib import Path from pathlib import Path
from os.path import relpath, splitext from os.path import relpath, splitext
import re import re
import shutil
import sqlite3 import sqlite3
from urllib.parse import urlparse from urllib.parse import urlparse
import zipfile import zipfile
from dlsite_async import DlsiteAPI from dlsite_async import DlsiteAPI
import fitz import fitz
from jinja2 import Environment, FileSystemLoader, select_autoescape
import requests import requests
NUMBER_REGEX = re.compile('[0-9]+') NUMBER_REGEX = re.compile('[0-9]+')
@ -159,7 +161,7 @@ def collate(args):
for work_path in extraction_dir.iterdir(): for work_path in extraction_dir.iterdir():
work_id = work_path.name 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(): if collation_dir.exists():
continue continue
@ -200,7 +202,7 @@ def collate(args):
con.close() con.close()
def manual_collate(args): 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: if collation_dir.exists() and len(list(collation_dir.iterdir())) > 0:
print(f'Collation directory already exists!') print(f'Collation directory already exists!')
return return
@ -256,7 +258,55 @@ def metadata(args):
con.close() con.close()
def publish(args): 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 = argparse.ArgumentParser(prog='dlibrary')
argparser.add_argument( argparser.add_argument(

View file

@ -52,6 +52,7 @@
pymupdf pymupdf
requests requests
dlsite-async dlsite-async
jinja2
]; ];
src = ./.; src = ./.;
}; };

View file

@ -1,3 +1,4 @@
requests requests
PyMuPDF PyMuPDF
dlsite-async dlsite-async
jinja2

76
static/dlibrary.css Normal file
View file

@ -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;
}

6
static/viewer.css Normal file
View file

@ -0,0 +1,6 @@
html, body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}

120
static/viewer.js Normal file
View file

@ -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;
}
};
});

15
templates/base.html Normal file
View file

@ -0,0 +1,15 @@
{% from 'utils.html' import root with context -%}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>{% if title %}{{ title }} - {% else %}{% endif %}DLibrary</title>
<link rel="stylesheet" type="text/css" href="{{ root() }}static/dlibrary.css">
{% block head %}{% endblock %}
</head>
<body>
{% block body required %}{% endblock %}
</body>
</html>

20
templates/list.html Normal file
View file

@ -0,0 +1,20 @@
{% extends 'base.html' %}
{% block body %}
{% from 'utils.html' import root with context %}
<h1 id="list-title"><a href={{ root() }}>DLibrary</a> {% block list_title %}{% endblock %}</h1>
<div class="card-listing">
{% for work in works %}
<div class="card">
<a href="{{ root() }}works/{{ work['id'] }}/">
<img src="{{ root() }}thumbnails/{{ work['id'] }}.jpg">
<div class="card-authors">
[{% if work['circle'] %}{{ work['circle'] }}{% endif %}{% if work['circle'] and work['authors'] %} ({% endif %}{{ ', '.join(work['authors']) }}{% if work['circle'] and work['authors'] %}){% endif %}]
</div>
<div class="card-title">
{{ work['title'] }}
</div>
</a>
</div>
{% endfor %}
</div>
{% endblock %}

1
templates/utils.html Normal file
View file

@ -0,0 +1 @@
{% macro root() %}{% for i in range(depth) %}../{% endfor %}{% endmacro %}

21
templates/viewer.html Normal file
View file

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% from 'utils.html' import root with context %}
{% block head %}
<link rel="stylesheet" type="text/css" href="{{ root() }}static/viewer.css">
<script>
const WORK_ID = "{{ work['id'] }}";
const ROOT = "{{ root() }}";
</script>
<script src="{{ root() }}static/viewer.js"></script>
{% endblock %}
{% block body %}
<div id="viewer-images">
{% for filename in images %}
<img src="{{ root() }}images/{{ work['id'] }}/{{ filename }}" class="viewer-image">
{% endfor %}
</div>
<div id="progress"></div>
<div id="page-num"></div>
<div id="duration"></div>
<div id="image-container"></div>
{% endblock %}