diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f2a3e3f..0000000 --- a/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2024 xenofem - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/dlibrary/dlibrary.py b/dlibrary/dlibrary.py index 986a824..a24638d 100755 --- a/dlibrary/dlibrary.py +++ b/dlibrary/dlibrary.py @@ -4,12 +4,10 @@ import argparse import asyncio import importlib_resources as resources from pathlib import Path -from os import getenv from os.path import relpath, splitext import re import shutil import sqlite3 -import textwrap from urllib.parse import urlparse import zipfile @@ -298,15 +296,12 @@ def metadata(args): con.close() -def copy_recursive(src, dest): +def copy_contents(src, dest): dest.mkdir(parents=True, exist_ok=True) for item in src.iterdir(): - if item.is_dir() and not item.is_symlink(): - copy_recursive(item, dest / item.name) - else: - shutil.copyfile(item, dest / item.name) + shutil.copyfile(item, dest / item.name) -def generate(args): +def publish(args): jenv = Environment( loader=PackageLoader("dlibrary"), autoescape=select_autoescape() @@ -352,7 +347,7 @@ def generate(args): with open(viewer_dir / 'index.html', 'w') as f: f.write(viewer_template.render(depth=3, work=work, title=title, images=images)) - def make_categorization(categorization, query, work_filter, work_style_cards=False): + def make_categorization(categorization, query, work_filter): categorization_dir = args.destdir / 'site' / categorization cats = [cat for (cat,) in cur.execute(query)] @@ -379,7 +374,6 @@ def generate(args): categorization=categorization, categories=cats, samples=cat_samples, - work_style_cards=work_style_cards, )) make_categorization( @@ -401,11 +395,10 @@ def generate(args): 'series', 'SELECT DISTINCT series FROM works WHERE series NOT NULL ORDER BY series', lambda series: lambda work: work['series'] == series, - work_style_cards=True, ) with resources.as_file(resources.files("dlibrary")) as r: - copy_recursive(r / 'static', args.destdir / 'site' / 'static') + copy_contents(r / 'static', args.destdir / 'site' / 'static') with open(args.destdir / 'site' / 'index.html', 'w') as f: f.write(list_template.render(depth=0, works=works)) @@ -413,33 +406,12 @@ def generate(args): con.close() -argparser = argparse.ArgumentParser( - prog='dlibrary', - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent("""\ - Organize DRM-free works purchased from DLSite into a library - that can be viewed in a web browser. - - Intended workflow: - - `extract` a collection of zipfiles downloaded from DLSite - into DLibrary's data directory, giving each work its own - subfolder. - - `fetch` metadata and thumbnail images for extracted works - from DLSite. - - `collate` and/or `manual-collate` extracted works, - producing a single sequence of image files (or symlinks - into the extracted data, when possible) for each work. - - Manually adjust works' `metadata` when necessary. - - `generate` a static website providing a catalog and viewer - for all collated works. - """), -) - +argparser = argparse.ArgumentParser(prog='dlibrary') argparser.add_argument( '-d', '--destdir', type=Path, - default=Path(getenv('DLIBRARY_DIR', './dlibrary')), - help='directory to store dlibrary content and metadata to (default: $DLIBRARY_DIR or ./dlibrary)', + default=Path('./dlibrary'), + help='directory to store dlibrary content and metadata to (default: ./dlibrary)', ) subparsers = argparser.add_subparsers(title="subcommands", required=True) @@ -461,79 +433,23 @@ parser_extract.set_defaults(func=extract) parser_fetch = subparsers.add_parser('fetch', help='fetch metadata and thumbnails') parser_fetch.set_defaults(func=fetch) -parser_collate = subparsers.add_parser( - 'collate', - help='collate each work into a sequence of image files', - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent("""\ - For each extracted work that has not already been collated, - DLibrary will attempt to intuit its structure as follows: - - - Enter the work's directory. If the directory contains - nothing except a single subdirectory (ignoring a few types - of files that are definitely not relevant), traverse - downwards repeatedly. - - If the current directory contains nothing except a single - PDF (again, ignoring irrelevant files), attempt to extract - a series of images from the PDF. This process expects that - each page of the PDF consists of a single embedded image, - which will be extracted at full resolution. Support for - more complex PDFs is not yet implemented. - - If the current directory contains nothing except image - files, and the image files are named in a way that clearly - indicates a complete numerical order (each filename - consists of a shared prefix followed by a distinct - number), symlink files in the inferred order. - - Otherwise, skip processing this work for now. - - DLibrary can be given "collation hints" which provide - alternative starting points for this search process. A hint - is a path under $DLIBRARY_DIR/extract/[work id]/ - indicating a different directory or PDF file to begin the - search process for that work, rather than starting at the - top level of the extracted data. There can be at most one - hint per work; for more complicated scenarios where a work - includes multiple folders that need to be collated together, - or where filenames do not clearly indicate an ordering, use - `manual-collate` instead. - """), -) +parser_collate = subparsers.add_parser('collate', help='collate a single sequence of image files for each work') parser_collate.add_argument( 'hints', metavar='PATH', type=Path, nargs='*', - help='paths within extraction folders as collation hints' + help='manually-specified paths of subdirectories or PDFs within extraction folders, at most one per work', ) parser_collate.set_defaults(func=collate) -parser_manual_collate = subparsers.add_parser( - 'manual-collate', - help='collate a single work manually', - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent("""\ - All provided paths must be under $DLIBRARY_DIR/extract/[work id]/ - for the work being manually collated. `manual-collate` can - only handle one work at a time. Paths are used as follows: - - - If a path is a directory, all *image files* immediately - inside that directory will be appended to the sequence. If - files are named in a way which indicates a clear ordering, - that ordering will be used. Otherwise, filenames will be - sorted lexicographically. Non-image files and - subdirectories will be ignored. - - If a path is an image file, that image file will be - appended to the sequence. - - If a path is a PDF file, page images will be extracted - from that PDF and appended to the sequence. -"""), -) +parser_manual_collate = subparsers.add_parser('manual-collate', help='collate a specific work manually, specifying all paths to include') parser_manual_collate.add_argument( 'paths', metavar='PATH', type=Path, nargs='+', - help='paths within a single work to be collated in sequence', + help='paths of files (images to symlink, pdfs to extract) or directories (symlink all images in the directory, no recursion, best-effort sorting)' ) parser_manual_collate.set_defaults(func=manual_collate) @@ -546,20 +462,8 @@ parser_metadata.add_argument( ) parser_metadata.set_defaults(func=metadata) -parser_generate = subparsers.add_parser( - 'generate', - help='generate HTML/CSS/JS for library site', - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent("""\ - The static site will be generated under $DLIBRARY_DIR/site/ - and can be served by pointing an HTTP server at that - directory. Note that some files inside the static site - hierarchy will be symlinks into $DLIBRARY_DIR/extract/ - outside the site hierarchy, so make sure your HTTP server - will allow those symlinks to be read. - """), -) -parser_generate.set_defaults(func=generate) +parser_publish = subparsers.add_parser('publish', help='generate HTML/CSS/JS for library site') +parser_publish.set_defaults(func=publish) def main(): args = argparser.parse_args() diff --git a/dlibrary/static/dlibrary.css b/dlibrary/static/dlibrary.css index 03c78d4..d02110d 100644 --- a/dlibrary/static/dlibrary.css +++ b/dlibrary/static/dlibrary.css @@ -51,19 +51,12 @@ body { .work-container { display: flex; - flex-wrap: wrap; justify-content: center; gap: 30px; } -.work-preview img { - max-width: 100%; -} - .work-info { - flex-basis: 40%; - flex-grow: 1; - max-width: 500px; + max-width: min(50%, 500px); } .work-info td, .work-info th { diff --git a/dlibrary/static/viewer.css b/dlibrary/static/viewer.css index f521e83..9045ee9 100644 --- a/dlibrary/static/viewer.css +++ b/dlibrary/static/viewer.css @@ -4,39 +4,3 @@ html, body { padding: 0; margin: 0; } - -.tap { - background: #000000; - opacity: 0.8; - position: fixed; - animation: 2s linear forwards fade; - display: flex; - justify-content: center; - align-items: center; -} - -@keyframes fade { - to { opacity: 0; } -} - -#tap-left, #tap-right { - position: fixed; - bottom: 0px; - width: 30vw; - height: 100vh; -} - -#tap-left { - left: 0px; -} - -#tap-right { - right: 0px; -} - -#tap-back { - top: 0px; - left: 30vw; - width: 40vw; - height: 20vh; -} diff --git a/dlibrary/static/viewer.js b/dlibrary/static/viewer.js index ebf9c62..f2dd772 100644 --- a/dlibrary/static/viewer.js +++ b/dlibrary/static/viewer.js @@ -5,7 +5,6 @@ document.addEventListener('DOMContentLoaded', () => { let paused = true; let interval; let elapsed = 0; - let rtl = (localStorage.getItem(`${WORK_ID}-rtl`) !== "false"); function startTimer() { if (interval) { @@ -79,46 +78,16 @@ document.addEventListener('DOMContentLoaded', () => { } } - function left() { - if (currentPage === 0) { - rtl = true; - localStorage.setItem(`${WORK_ID}-rtl`, rtl); - } - changePage(currentPage + (rtl ? 1 : -1)); - } - - function right() { - if (currentPage === 0) { - rtl = false; - localStorage.setItem(`${WORK_ID}-rtl`, rtl); - } - changePage(currentPage + (rtl ? -1 : 1)); - } - - function exitToWork() { - changeDuration(duration, true); - localStorage.setItem(`${WORK_ID}-currentPage`, 0); - window.location.href = `../${INDEX}`; - } - - function hideTapZones() { - for (const el of document.getElementsByClassName('tap')) { - el.style.opacity = 0; - } - } - changePage(currentPage); changeDuration(duration, paused); - document.onkeydown = event => { - hideTapZones(); - + document.onkeydown = event =>{ switch (event.keyCode) { case 32: //space changeDuration(duration, !paused); break; case 37: //left - left(); + changePage(currentPage - 1); break; case 38: //up if (2 <= duration && duration <= 10) { @@ -130,7 +99,7 @@ document.addEventListener('DOMContentLoaded', () => { } break; case 39: //right - right(); + changePage(currentPage + 1); break; case 40: //down if (duration < 10) { @@ -142,13 +111,10 @@ document.addEventListener('DOMContentLoaded', () => { } break; case 13: //enter - exitToWork(); + changeDuration(duration, true); + localStorage.setItem(`${WORK_ID}-currentPage`, 0); + window.location.href = "../"; break; } }; - - document.onclick = hideTapZones; - document.getElementById("tap-left").onclick = left; - document.getElementById("tap-right").onclick = right; - document.getElementById("tap-back").onclick = exitToWork; }); diff --git a/dlibrary/templates/base.html b/dlibrary/templates/base.html index 1fb0b0a..40cb671 100644 --- a/dlibrary/templates/base.html +++ b/dlibrary/templates/base.html @@ -6,7 +6,7 @@ {% block title %}{% if title %}{{ title }} - {% else %}{% endif %}DLibrary{% endblock %} - + {% block head %}{% endblock %} diff --git a/dlibrary/templates/categorization.html b/dlibrary/templates/categorization.html index 321e7ed..3a5a3ac 100644 --- a/dlibrary/templates/categorization.html +++ b/dlibrary/templates/categorization.html @@ -1,18 +1,18 @@ {% extends "base.html" %} {% block title %}{{ categorization.capitalize() }} - DLibrary{% endblock %} {% block body %} -{% from 'utils.html' import urlcat, index, root with context %} -

DLibrary > {{ categorization.capitalize() }}

+{% from 'utils.html' import urlcat, root with context %} +

DLibrary > {{ categorization.capitalize() }}

{% include 'nav.html' %}
{% for cat in categories %} -
- + diff --git a/dlibrary/templates/list.html b/dlibrary/templates/list.html index d0e1139..f882c21 100644 --- a/dlibrary/templates/list.html +++ b/dlibrary/templates/list.html @@ -1,13 +1,13 @@ {% extends 'base.html' %} {% block body %} -{% from 'utils.html' import index, root with context %} -

DLibrary{% if categorization %} > {{ categorization.capitalize() }}{% endif %}{% if title %} > {{ title }}{% endif %}

+{% from 'utils.html' import root with context %} +

DLibrary{% if categorization %} > {{ categorization.capitalize() }}{% endif %}{% if title %} > {{ title }}{% endif %}

{% include 'nav.html' %}
{% 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 %}]
diff --git a/dlibrary/templates/nav.html b/dlibrary/templates/nav.html index 62af956..cbd9527 100644 --- a/dlibrary/templates/nav.html +++ b/dlibrary/templates/nav.html @@ -1,2 +1,2 @@ -{% from 'utils.html' import root, index with context %} -
+{% from 'utils.html' import root with context %} + diff --git a/dlibrary/templates/utils.html b/dlibrary/templates/utils.html index 161dc38..10ccd00 100644 --- a/dlibrary/templates/utils.html +++ b/dlibrary/templates/utils.html @@ -1,3 +1,2 @@ -{% macro root() %}{% if depth == 0 %}.{% else %}..{% endif %}{{ '/..' * (depth-1) }}{% endmacro %} -{% macro index() %}{% if not noindex %}index.html{% endif %}{% endmacro %} +{% macro root() %}{% for i in range(depth) %}../{% endfor %}{% endmacro %} {% macro urlcat(s) %}{{ s | replace('/', ' ') | urlencode }}{% endmacro %} diff --git a/dlibrary/templates/viewer.html b/dlibrary/templates/viewer.html index dc521e9..77f8076 100644 --- a/dlibrary/templates/viewer.html +++ b/dlibrary/templates/viewer.html @@ -1,38 +1,20 @@ {% extends 'base.html' %} -{% from 'utils.html' import index, root with context %} +{% from 'utils.html' import root with context %} {% block head %} - + - + {% endblock %} {% block body %}
{% for filename in images %} - + {% endfor %}
-
-
- - - -
-
- - - -
-
- - - -
-
{% endblock %} diff --git a/dlibrary/templates/work.html b/dlibrary/templates/work.html index 3717b17..b9e568c 100644 --- a/dlibrary/templates/work.html +++ b/dlibrary/templates/work.html @@ -1,11 +1,11 @@ {% extends 'base.html' %} {% block body %} -{% from 'utils.html' import urlcat, root, index with context %} -

DL > {{ title }}

+{% from 'utils.html' import urlcat, root with context %} +

DL > {{ title }}

@@ -13,29 +13,28 @@ {% if work['circle'] %} Circle - {{ work['circle'] }} + {{ work['circle'] }} {% endif %} {% if work['authors'] %} Authors - {% for author in work['authors'] %}{{ author }} {% endfor %} + {% for author in work['authors'] %}{{ author }} {% endfor %} {% endif %} {% if work['tags'] %} Tags - {% for tag in work['tags'] %}{{ tag }} {% endfor %} + {% for tag in work['tags'] %}{{ tag }} {% endfor %} {% endif %} {% if work['series'] %} Series - {{ work['series'] }} + {{ work['series'] }} {% endif %} - {{ work['description'] }}
{% endblock %} diff --git a/pyproject.toml b/pyproject.toml index 58df9bd..9f2b3a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,6 @@ [project] name = "dlibrary" version = "0.1" -description = "Cataloging tool and viewer for downloaded DLSite purchases" -license = {file = "LICENSE"} -authors = [{name = "xenofem"}] dependencies = [ "requests", "PyMuPDF",