You're welcome. Now behave. CHANGES: - Added rule34.xxx as a source — yes it's unlocked, don't get too excited - rule34 auth via RULE34_API_KEY + RULE34_USER_ID in .env, because you don't get in without permission - Fixed pagination for Gelbooru-style APIs (pid, 0-indexed) — the old page param was just embarrassing - Default download limit capped at 100 per request — you don't get unlimited, you get what you're given - Downloader now scans 10x the limit first, skips what's already owned, then fetches only fresh ones — efficient, like you should be - Progress bar now shows scan status: "skipped N | fetching X–Y of Z" — full transparency, no excuses - File size shown top-right of the image in small text — size matters and now you can see it
100 lines
3.2 KiB
Python
100 lines
3.2 KiB
Python
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
import uuid
|
|
from flask import Flask, render_template, request, redirect, url_for, Response
|
|
|
|
from db import init_db, search_images
|
|
|
|
app = Flask(__name__, static_folder='Pictures', static_url_path='/pictures')
|
|
init_db()
|
|
|
|
# job_id -> {'done': int, 'total': int, 'finished': bool, 'site': str, 'tags': str}
|
|
downloads = {}
|
|
|
|
|
|
@app.route('/')
|
|
def slideshow():
|
|
raw_query = request.args.get('tags', '').strip()
|
|
results = search_images(raw_query)
|
|
pictures_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Pictures')
|
|
image_urls = [f'pictures/{r["filename"]}' for r in results]
|
|
post_urls = [r['post_url'] for r in results]
|
|
tags_list = [r['tags'].split() for r in results]
|
|
file_sizes = [os.path.getsize(os.path.join(pictures_dir, r['filename'])) for r in results]
|
|
active_tags = raw_query.split() if raw_query else []
|
|
job_id = request.args.get('job_id')
|
|
return render_template(
|
|
'slideshow.html',
|
|
images=image_urls,
|
|
post_urls=post_urls,
|
|
tags_list=tags_list,
|
|
file_sizes=file_sizes,
|
|
active_tags=active_tags,
|
|
tag_query=raw_query,
|
|
job_id=job_id,
|
|
)
|
|
|
|
|
|
@app.route('/download', methods=['POST'])
|
|
def download():
|
|
tags = request.form.get('tags', '').strip()
|
|
site = request.form.get('site', 'e621')
|
|
if not tags:
|
|
return redirect(url_for('slideshow', tags=tags))
|
|
|
|
job_id = uuid.uuid4().hex[:8]
|
|
downloads[job_id] = {'done': 0, 'total': 0, 'finished': False, 'site': site, 'tags': tags, 'status': ''}
|
|
|
|
proc = subprocess.Popen(
|
|
[sys.executable, 'downloader.py', '--site', site, '--query', tags],
|
|
cwd=app.root_path,
|
|
stdout=subprocess.PIPE,
|
|
text=True,
|
|
)
|
|
|
|
def read_stdout():
|
|
for line in proc.stdout:
|
|
line = line.strip()
|
|
if line.startswith('status:'):
|
|
downloads[job_id]['status'] = line[7:]
|
|
elif line.startswith('total:'):
|
|
downloads[job_id]['total'] = int(line.split(':')[1])
|
|
elif line.startswith('progress:'):
|
|
done, total = line.split(':')[1].split('/')
|
|
downloads[job_id]['done'] = int(done)
|
|
downloads[job_id]['total'] = int(total)
|
|
elif line == 'done':
|
|
downloads[job_id]['finished'] = True
|
|
downloads[job_id]['finished'] = True
|
|
|
|
threading.Thread(target=read_stdout, daemon=True).start()
|
|
return redirect(url_for('slideshow', tags=tags, job_id=job_id))
|
|
|
|
|
|
@app.route('/download/progress/<job_id>')
|
|
def download_progress(job_id):
|
|
def generate():
|
|
while True:
|
|
info = downloads.get(job_id)
|
|
if not info:
|
|
yield f'data: {json.dumps({"error": "not found"})}\n\n'
|
|
break
|
|
yield f'data: {json.dumps({"done": info["done"], "total": info["total"], "finished": info["finished"], "status": info["status"]})}\n\n'
|
|
if info['finished']:
|
|
break
|
|
time.sleep(0.3)
|
|
|
|
return Response(
|
|
generate(),
|
|
mimetype='text/event-stream',
|
|
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'},
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', debug=False)
|