From 2c2aa65947938d5bb3e8b3a2e1b4307be60435ee Mon Sep 17 00:00:00 2001 From: Nyako <46496367+Hell13Cat@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:52:48 +0300 Subject: [PATCH] 0.1.9s --- doc/install_en.md | 1 + doc/install_ru.md | 1 + run.py | 9 +- system_module/db.py | 6 +- system_module/tasks.py | 114 ++++++++++++++++++----- templates/analytics.html | 191 +++++++++++++++++++-------------------- templates/index.html | 2 - templates/task.html | 42 --------- 8 files changed, 193 insertions(+), 173 deletions(-) diff --git a/doc/install_en.md b/doc/install_en.md index dee7309..df0bd8a 100644 --- a/doc/install_en.md +++ b/doc/install_en.md @@ -53,6 +53,7 @@ Config explanation: | `file`-`repos` | `["data/files"]` | List of directories where source files are stored | | `file`-`dw` | `"data/files"` | Directory for new downloads | | `file`-`thumb` | `"data/thumb"` | Directory for thumbnails | +| `file`-`data` | `"data"` | Directory for database | 1. You must obtain your Gelbooru User ID and Hash and add them to the config file for the system to function correctly. 2. Install the script in [VioletMonkey](https://git.nekono.su/Nyako/Local-Booru/raw/branch/main/button_script.user.js). diff --git a/doc/install_ru.md b/doc/install_ru.md index ef9ff8d..ca74f81 100644 --- a/doc/install_ru.md +++ b/doc/install_ru.md @@ -54,6 +54,7 @@ | `file`-`repos` | `["data/files"]` | Список каталогов где храняться полные файлы | | `file`-`dw` | `"data/files"` | Каталог хранения загрузок | | `file`-`thumb` | `"data/thumb"` | Каталог обложек | +| `file`-`data` | `"data"` | Каталог базы данных | 1. Обязательно надо получить ID и Hash пользователя и внести в конфиг для работы! diff --git a/run.py b/run.py index cee9d0c..17b2a4d 100644 --- a/run.py +++ b/run.py @@ -6,7 +6,7 @@ from pathlib import Path import requests from flask import (Flask, jsonify, render_template, render_template_string, - request, send_from_directory, make_response) + request, send_from_directory, make_response, send_file) from system_module import db, gelbooru, image_processor, logger, tasks @@ -26,12 +26,13 @@ GB_HEADERS = CONFIG_ROOT["gb"]["headers"] FILE_FOLDERS = CONFIG_ROOT["file"]["repos"] FILE_DW_FOLDERS = CONFIG_ROOT["file"]["dw"] THUMB_FOLDER = CONFIG_ROOT["file"]["thumb"] +DATA_FOLDER = CONFIG_ROOT["file"]["data"] gb = gelbooru.GB(GB_ID, GB_HASH, GB_HEADERS, FILE_DW_FOLDERS) log = logger.Logger() log.init_global_exception_hook() -database = db.DB(True) +database = db.DB(DATA_FOLDER) app = Flask(__name__) if not image_processor.check_ffmpeg(): print("WARNING!\n\nУ вас не установлен ffmpeg. Генерация превью для видео не доступна!") @@ -54,7 +55,7 @@ def post(post_id): @app.route('/file/') def serve_image(filename): - folder_path = task.find_file(filename, FILE_FOLDERS) + folder_path = tasks.find_file(filename, FILE_FOLDERS) if folder_path == None: return make_response("Not found", 404) else: @@ -91,6 +92,8 @@ def api(): post_id = data["post_id"] result_post = database.get_from_id(post_id) return jsonify(result_post) + elif data["type"] == "analytic": + return send_file(os.path.join(DATA_FOLDER, "analytic.json"), mimetype="application/json") elif data["type"] == "task": if data["task"] == "analytic": if tasks.calculate_analytic(database, CONFIG_ROOT): diff --git a/system_module/db.py b/system_module/db.py index 3b491ae..2780c1a 100644 --- a/system_module/db.py +++ b/system_module/db.py @@ -7,11 +7,7 @@ from system_module.gelbooru import gelbooru_date_parse class DB: - def __init__(self, debug: bool): - if debug: - db_path = "data_test" - else: - db_path = "data" + def __init__(self, db_path: str): if not os.path.exists(db_path): os.makedirs(db_path) diff --git a/system_module/tasks.py b/system_module/tasks.py index a7e73a7..8755f9b 100644 --- a/system_module/tasks.py +++ b/system_module/tasks.py @@ -2,13 +2,13 @@ import os import json from pathlib import Path -def find_file(filename, file_folders): +def find_file(filename: str, file_folders: list[str], is_folder: bool = True): for folder in file_folders: folder_path = Path(folder) if not folder_path.exists(): continue for file_path in folder_path.rglob(filename): - return folder_path + return str(folder_path) if is_folder else str(file_path) return None def get_size_format(b, factor=1024, suffix="B"): @@ -36,9 +36,10 @@ def get_file_list_size(file_list: list[str]): def get_size_edge(type_searсh: bool, file_list: list[str]): if type_searсh: - return min(file_list, key=os.path.getsize) + result = min(file_list, key=lambda f: Path(f).stat().st_size) else: - return max(file_list, key=os.path.getsize) + result = max(file_list, key=lambda f: Path(f).stat().st_size) + return str(result) def calculate_analytic(database, config): all_db = database.get_all() @@ -50,8 +51,9 @@ def calculate_analytic(database, config): type_list_file_path = {"images":[], "videos":[], "gifs":[]} files_not_found = 0 for one_post in all_db: - file_path = find_file(one_post["local_filename"], config["file"]["repos"]) + file_path = find_file(one_post["local_filename"], config["file"]["repos"], is_folder=False) if file_path != None: + file_path = str(file_path) rating_list_file_path[one_post["rating"]].append(file_path) if one_post["is_gif"] == 1: type_list_file_path["gifs"].append(file_path) @@ -88,11 +90,78 @@ def calculate_analytic(database, config): } } total_file_data_size = file_size_dict["general"]+file_size_dict["questionable"]+file_size_dict["sensitive"]+file_size_dict["explicit"] - total_thumb_size = get_dir_size(config[["file"]["thumb"]]) + total_thumb_size = get_dir_size(config["file"]["thumb"]) total_log_size = get_file_list_size(["logs.log"]) - total_db_size = get_file_list_size([os.path.join("data", "db.sqlite")]) - weight_stats_smallest = get_size_edge(True, type_list_file_path["gifs"]+type_list_file_path["images"]+type_list_file_path["videos"]) - weight_stats_largest = get_size_edge(False, type_list_file_path["gifs"]+type_list_file_path["images"]+type_list_file_path["videos"]) + total_db_size = get_file_list_size([os.path.join(config["file"]["data"], "db.sqlite")]) + weight_stats_smallest_file = get_size_edge(True, type_list_file_path["gifs"]+type_list_file_path["images"]+type_list_file_path["videos"]) + weight_stats_largest_file = get_size_edge(False, type_list_file_path["gifs"]+type_list_file_path["images"]+type_list_file_path["videos"]) + weight_stats_data = { + "smallest":{ + "file":"", + "md5":"", + "size":0 + }, + "largest":{ + "file":"", + "md5":"", + "size":0 + } + } + resolution_stats_data = { + "smallest":{ + "file":"", + "md5":"", + "res":{ + "width":0, + "height":0, + "total":99999999999999999999999999 + } + }, + "largest":{ + "file":"", + "md5":"", + "res":{ + "width":0, + "height":0, + "total":0 + } + } + } + for one_post_scan in all_db: + if one_post_scan["local_filename"] == os.path.basename(weight_stats_smallest_file): + weight_stats_data["smallest"] = { + "file":os.path.basename(weight_stats_smallest_file), + "md5":one_post_scan["md5"], + "size":get_file_list_size([weight_stats_smallest_file]) + } + if one_post_scan["local_filename"] == os.path.basename(weight_stats_largest_file): + weight_stats_data["largest"] = { + "file":os.path.basename(weight_stats_largest_file), + "md5":one_post_scan["md5"], + "size":get_file_list_size([weight_stats_largest_file]) + } + print(get_file_list_size([os.path.basename(weight_stats_largest_file)])) + one_post_res_total = one_post_scan["width"]*one_post_scan["height"] + if resolution_stats_data["largest"]["res"]["total"] < one_post_res_total: + resolution_stats_data["largest"] = { + "file":one_post_scan["local_filename"], + "md5":one_post_scan["md5"], + "res":{ + "width":one_post_scan["width"], + "height":one_post_scan["height"], + "total":one_post_res_total + } + } + if resolution_stats_data["smallest"]["res"]["total"] > one_post_res_total: + resolution_stats_data["smallest"] = { + "file":one_post_scan["local_filename"], + "md5":one_post_scan["md5"], + "res":{ + "width":one_post_scan["width"], + "height":one_post_scan["height"], + "total":one_post_res_total + } + } result = { "total_images_analyzed": len(all_db), "files_not_found":files_not_found, @@ -117,29 +186,30 @@ def calculate_analytic(database, config): "ratings_stats": ratings_stats, "resolution_stats": { "largest": { - "filename": max_res["filename"], - "md5": max_res["md5"], - "resolution": f'{max_res["width"]}x{max_res["height"]}' + "filename": resolution_stats_data["largest"]["file"], + "md5": resolution_stats_data["largest"]["md5"], + "resolution": str(resolution_stats_data["largest"]["res"]["width"]) + " x " + str(resolution_stats_data["largest"]["res"]["height"]) }, "smallest": { - "filename": min_res["filename"], - "md5": min_res["md5"], - "resolution": f'{min_res["width"]}x{min_res["height"]}' + "filename": resolution_stats_data["smallest"]["file"], + "md5": resolution_stats_data["smallest"]["md5"], + "resolution": str(resolution_stats_data["smallest"]["res"]["width"]) + " x " + str(resolution_stats_data["smallest"]["res"]["height"]) } }, "weight_stats": { "largest": { - "filename": weight_stats_largest, - "md5": max_weight["md5"], - "size_formatted": get_size_format(max_weight["size"]) + "filename": os.path.basename(weight_stats_data["largest"]["file"]), + "md5": weight_stats_data["largest"]["md5"], + "size_formatted": get_size_format(weight_stats_data["largest"]["size"]) }, "smallest": { - "filename": weight_stats_smallest, - "md5": min_weight["md5"], - "size_formatted": get_size_format(min_weight["size"]) + "filename": os.path.basename(weight_stats_data["smallest"]["file"]), + "md5": weight_stats_data["smallest"]["md5"], + "size_formatted": get_size_format(weight_stats_data["smallest"]["size"]) } }, "top_tag":database.get_top_tag("") } with open(os.path.join(config["file"]["data"], "analytic.json"), 'w', encoding='utf-8') as out_f: - json.dump(result, out_f, indent=4, ensure_ascii=False) \ No newline at end of file + json.dump(result, out_f, indent=4, ensure_ascii=False) + return True \ No newline at end of file diff --git a/templates/analytics.html b/templates/analytics.html index 3fe3ee8..d80e558 100644 --- a/templates/analytics.html +++ b/templates/analytics.html @@ -41,22 +41,21 @@ .ext-block a { color: var(--link-color); text-decoration: none; word-break: break-all; } .ext-block a:hover { text-decoration: underline; } + .loader { color: var(--text-muted); font-size: 16px; } + @media (max-width: 1000px) { .dashboard-grid { grid-template-columns: 1fr 1fr; } .left-col { grid-column: 1/-1; display: grid; grid-template-columns: 1fr 1fr; } } @media (max-width: 700px) { .dashboard-grid, .left-col { grid-template-columns: 1fr; } header { flex-wrap: wrap; } .search-wrapper { order: 3; min-width: 100%; margin-top: 10px;} }
- - 📊 Analytics -
- -
    -
    +

    Database Analytics

    +
    Fetching data from API...
    +
    diff --git a/templates/index.html b/templates/index.html index 1d6df03..a557621 100644 --- a/templates/index.html +++ b/templates/index.html @@ -64,9 +64,7 @@
    - ⚙️ Tasks
    diff --git a/templates/task.html b/templates/task.html index c58608b..ece992c 100644 --- a/templates/task.html +++ b/templates/task.html @@ -56,10 +56,6 @@
    -
    - -
      -
      @@ -139,44 +135,6 @@ btn.innerText = "Запустить"; } } - - - // === ЛОГИКА ПОИСКА (оставлена из предыдущей версии) === - const searchInput = document.getElementById('searchInput'); - const list = document.getElementById('autocompleteList'); - let debounceTimer; - - searchInput.addEventListener('input', () => { - clearTimeout(debounceTimer); - const words = searchInput.value.split(' '); - const currentWord = words[words.length - 1]; - if (currentWord.length < 2) { list.style.display = 'none'; return; } - - debounceTimer = setTimeout(async () => { - const req = { type: "autocomplete", q: currentWord }; - const data = await mockAutocompleteAPI(req); - - if (data.length === 0) { list.style.display = 'none'; return; } - - list.innerHTML = data.map(item => `
    • ${item.tag}${item.count}
    • `).join(''); - list.style.display = 'block'; - - list.querySelectorAll('.autocomplete-item').forEach(li => { - li.addEventListener('click', () => { - words[words.length - 1] = li.getAttribute('data-tag'); - searchInput.value = words.join(' ') + ' '; - list.style.display = 'none'; - searchInput.focus(); - }); - }); - }, 200); - }); - - document.addEventListener('click', (e) => { if (e.target !== searchInput && e.target !== list) list.style.display = 'none'; }); - searchInput.addEventListener('keypress', (e) => { if(e.key === 'Enter') window.location.href = `index.html?query=${searchInput.value.trim()}`; }); - - // Заглушка для поиска (оставьте или замените на ваш fetch) - function mockAutocompleteAPI(req) { return new Promise(res => { const db = [{tag: "long_hair", count: 8184}, {tag: "1girl", count: 11061}]; res(db.filter(t => t.tag.includes(req.q.toLowerCase()))); }); } \ No newline at end of file