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 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( | ||||||
|  |  | ||||||
|  | @ -52,6 +52,7 @@ | ||||||
|           pymupdf |           pymupdf | ||||||
|           requests |           requests | ||||||
|           dlsite-async |           dlsite-async | ||||||
|  |           jinja2 | ||||||
|         ]; |         ]; | ||||||
|         src = ./.; |         src = ./.; | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
| requests | requests | ||||||
| PyMuPDF | PyMuPDF | ||||||
| dlsite-async | 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