we've got a website, sorta!
This commit is contained in:
		
							parent
							
								
									7680a174fc
								
							
						
					
					
						commit
						6c94a346c4
					
				
					 10 changed files with 314 additions and 3 deletions
				
			
		
							
								
								
									
										56
									
								
								dlibrary.py
									
										
									
									
									
								
							
							
						
						
									
										56
									
								
								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( | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ | |||
|           pymupdf | ||||
|           requests | ||||
|           dlsite-async | ||||
|           jinja2 | ||||
|         ]; | ||||
|         src = ./.; | ||||
|       }; | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| requests | ||||
| PyMuPDF | ||||
| dlsite-async | ||||
| jinja2 | ||||
|  |  | |||
							
								
								
									
										76
									
								
								static/dlibrary.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								static/dlibrary.css
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										6
									
								
								static/viewer.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| html, body { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     padding: 0; | ||||
|     margin: 0; | ||||
| } | ||||
							
								
								
									
										120
									
								
								static/viewer.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								static/viewer.js
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										15
									
								
								templates/base.html
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										20
									
								
								templates/list.html
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										1
									
								
								templates/utils.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| {% macro root() %}{% for i in range(depth) %}../{% endfor %}{% endmacro %} | ||||
							
								
								
									
										21
									
								
								templates/viewer.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								templates/viewer.html
									
										
									
									
									
										Normal 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 %} | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue