first commit

This commit is contained in:
mei 2025-08-01 10:45:13 +08:00
commit 504a719a58
11 changed files with 373 additions and 0 deletions

130
app.py Executable file
View File

@ -0,0 +1,130 @@
from flask import Flask, render_template, request, flash, send_from_directory, redirect, url_for, jsonify
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, SubmitField
from wtforms.validators import DataRequired
import mysql.connector
from PIL import Image
import os
import shutil
import hashlib
from math import ceil
app = Flask(__name__)
app.config['SECRET_KEY'] = 'linux'
# 数据库
db_config = {
'host': '192.168.1.245',
'user': 'api',
'password': '35NR6idXCHkQdx4M',
'database': 'api'
}
def get_db_connection():
return mysql.connector.connect(**db_config)
class TagImageForm(FlaskForm):
filename = StringField('New Filename', validators=[DataRequired()])
theme = SelectField('Theme', choices=[
('light', 'Light'),
('dark', 'Dark'),
('favicon', 'Favicon'),
('rainyun', 'Rainyun'),
('fox', 'Fox'),
('bj', 'BJ')
], validators=[DataRequired()])
et = SelectField('ET', choices=[
('pc', 'PC'),
('phone', 'Phone'),
('all', 'All')
], validators=[DataRequired()], default='pc')
submit = SubmitField('Tag and Move Image')
@app.route('/', methods=['GET', 'POST'])
def index():
form = TagImageForm()
page = request.args.get('page', 1, type=int)
per_page = 6
if form.validate_on_submit():
new_filename = form.filename.data
theme = form.theme.data
et = form.et.data
original_filename = request.form.get('original_filename')
try:
conn = get_db_connection()
cursor = conn.cursor()
query = "INSERT INTO images (filename, theme, et) VALUES (%s, %s, %s)"
cursor.execute(query, (new_filename, theme, et))
conn.commit()
catch_dir = os.path.join(app.root_path, 'catch')
img_dir = os.path.join(app.root_path, 'img')
current_path = os.path.join(catch_dir, original_filename)
new_path = os.path.join(img_dir, new_filename)
shutil.move(current_path, new_path)
conn.commit()
flash('图片已标记成功', 'success')
return redirect(url_for('index'))
except Exception as e:
flash(f'Error: {str(e)}', 'danger')
return redirect(url_for('index'))
catch_dir = os.path.join(app.root_path, 'catch')
all_images = [f for f in os.listdir(catch_dir) if os.path.isfile(os.path.join(catch_dir, f)) and f.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp'))]
total_pages = ceil(len(all_images) / per_page)
start = (page - 1) * per_page
end = start + per_page
images = all_images[start:end]
return render_template('index.html', form=form, images=images, page=page, total_pages=total_pages)
@app.route('/delete/<filename>', methods=['GET'])
def delete_image(filename):
try:
# 删除图片
catch_dir = os.path.join(app.root_path, 'catch')
file_path = os.path.join(catch_dir, filename)
if os.path.exists(file_path):
os.remove(file_path)
flash('图片删除成功', 'success')
else:
flash('未找到图片', 'warning')
except Exception as e:
flash(f'Error: {str(e)}', 'danger')
return redirect(url_for('index'))
@app.route('/catch/<path:filename>')
def serve_catch_file(filename):
catch_dir = os.path.join(app.root_path, 'catch')
return send_from_directory(catch_dir, filename)
@app.route('/img/<path:filename>')
def serve_img_file(filename):
img_dir = os.path.join(app.root_path, 'img')
return send_from_directory(img_dir, filename)
# 短哈希
@app.route('/generate-short-hash', methods=['POST'])
def generate_short_hash():
filename = request.form.get('filename')
short_hash = generate_short_hash(filename)
return jsonify({'short_hash': short_hash})
Image.MAX_IMAGE_PIXELS = 1000000000
def generate_short_hash(filename, length=8):
hash_object = hashlib.sha256(filename.encode())
hex_dig = hash_object.hexdigest()
return hex_dig[:length]
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

81
fast.py Executable file
View File

@ -0,0 +1,81 @@
import os
import hashlib
from PIL import Image
import mysql.connector
from math import ceil
from tqdm import tqdm
# 数据库连接配置
db_config = {
'host': '192.168.1.193',
'user': 'meiapi',
'password': '',
'database': 'meiapi'
}
# 获取数据库连接
def get_db_connection():
return mysql.connector.connect(**db_config)
# 生成短哈希
def generate_short_hash(filename, length=8):
hash_object = hashlib.sha256(filename.encode())
hex_dig = hash_object.hexdigest()
return hex_dig[:length]
# 压缩图像转换为WebP格式
def compress_image(input_path, output_path):
with Image.open(input_path) as img:
img.save(output_path, "WEBP")
# 批量处理图像
def batch_process_images(source_dir, target_dir, theme, et):
# 确保目标目录存在
if not os.path.exists(target_dir):
os.makedirs(target_dir)
# 获取数据库连接
conn = get_db_connection()
cursor = conn.cursor()
# 获取源目录中的所有文件列表
files = [f for f in os.listdir(source_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico'))]
# 使用tqdm包装文件列表以显示进度条
for filename in tqdm(files, desc="Processing images", unit="file"):
file_path = os.path.join(source_dir, filename)
file_extension = os.path.splitext(filename)[1].lower()
# 生成短哈希
short_hash = generate_short_hash(filename)
new_filename = f"{short_hash}{file_extension}"
new_file_path = os.path.join(target_dir, new_filename)
# 重命名并移动文件
os.rename(file_path, new_file_path)
# 压缩图像
webp_filename = f"{short_hash}.webp"
webp_file_path = os.path.join(target_dir, webp_filename)
compress_image(new_file_path, webp_file_path)
# 插入数据库
insert_query = "INSERT INTO images (filename, theme, et, webp_path) VALUES (%s, %s, %s, %s)"
cursor.execute(insert_query, (new_filename, theme, et, webp_filename))
conn.commit()
# 关闭数据库连接
cursor.close()
conn.close()
if __name__ == '__main__':
# 指定源目录和目标目录
source_directory = 'catch/'
target_directory = 'img/'
# 指定theme和et字段
theme = 'fox' # 可以改为其他主题
et = 'all' # 可以改为其他设备类型
# 执行批量处理
batch_process_images(source_directory, target_directory, theme, et)

5
requirements.txt Executable file
View File

@ -0,0 +1,5 @@
Flask>=2.0.0
flask-wtf>=1.0.0
WTForms>=3.0.0
mysql-connector-python>=8.0.0
Pillow>=9.0.0

7
static/css/bootstrap.min.css vendored Executable file

File diff suppressed because one or more lines are too long

1
static/css/sweetalert2.min.css vendored Executable file

File diff suppressed because one or more lines are too long

6
static/css/toastr.min.css vendored Executable file

File diff suppressed because one or more lines are too long

7
static/js/bootstrap.bundle.min.js vendored Executable file

File diff suppressed because one or more lines are too long

2
static/js/jquery.min.js vendored Executable file

File diff suppressed because one or more lines are too long

6
static/js/sweetalert2@11 Executable file

File diff suppressed because one or more lines are too long

7
static/js/toastr.min.js vendored Executable file

File diff suppressed because one or more lines are too long

121
templates/index.html Executable file
View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Tagging</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/toastr.min.css">
<link rel="stylesheet" href="/static/css/sweetalert2.min.css">
<script src="/static/js/sweetalert2@11"></script>
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">Images to Tag</h1>
<!-- 图片列表 -->
<div class="row">
{% for image in images %}
<div class="col-md-4 mb-4">
<div class="card">
<img src="{{ url_for('serve_catch_file', filename=image) }}" alt="{{ image }}" class="card-img-top">
<div class="card-body text-center">
<form action="{{ url_for('index') }}" method="post">
<input type="hidden" name="original_filename" value="{{ image }}">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.filename.label(class="form-control-label") }}
{{ form.filename(id='filename', class="form-control", value=image) }}
</div>
<div class="form-group">
{{ form.theme.label(class="form-control-label") }}
{{ form.theme(class="form-control") }}
</div>
<div class="form-group">
{{ form.et.label(class="form-control-label") }}
{{ form.et(class="form-control") }}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-primary btn-block") }}
</div>
</form>
<button class="btn btn-danger btn-block delete-image" data-filename="{{ image }}">Delete Image</button>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- 分页导航 -->
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
{% if page > 1 %}
<li class="page-item">
<a class="page-link" href="?page={{ page - 1 }}">Previous</a>
</li>
{% endif %}
{% if page < total_pages %}
<li class="page-item">
<a class="page-link" href="?page={{ page + 1 }}">Next</a>
</li>
{% endif %}
</ul>
</nav>
</div>
<!-- 引入JavaScript库 -->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/toastr.min.js"></script>
<script>
$(document).ready(function () {
// 使用toastr显示闪现消息
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
toastr.{{ category }}("{{ message|safe }}");
{% endfor %}
{% endif %}
{% endwith %}
// 自动填充新文件名
function autoFillFilename(original_filename) {
$.ajax({
url: '/generate-short-hash',
type: 'POST',
data: { filename: original_filename },
success: function (response) {
$('#filename').val(response.short_hash + '.' + original_filename.split('.').pop());
}
});
}
// 图片点击事件
$('.card-img-top').click(function () {
var original_filename = $(this).attr('alt');
autoFillFilename(original_filename);
});
// 删除图片事件
$('.delete-image').click(function (e) {
e.preventDefault();
var filename = $(this).data('filename');
Swal.fire({
title: '删除?',
text: "图片将无法恢复!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: '是的'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = "{{ url_for('delete_image', filename='') }}" + encodeURIComponent(filename);
}
});
});
});
</script>
</body>
</html>