first commit
This commit is contained in:
commit
504a719a58
130
app.py
Executable file
130
app.py
Executable 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
81
fast.py
Executable 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
5
requirements.txt
Executable 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
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
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
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
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
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
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
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
121
templates/index.html
Executable 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>
|
Loading…
Reference in New Issue
Block a user