mirror of
https://github.com/Kakune55/ComiPy.git
synced 2025-10-14 20:24:40 +08:00
Compare commits
6 Commits
dev
...
f2c51f45b6
Author | SHA1 | Date | |
---|---|---|---|
f2c51f45b6 | |||
eaec2dbad7 | |||
b8252d412c | |||
dbcb6a79de | |||
86a47a8aab | |||
8c86e94e71 |
96
README.md
96
README.md
@@ -1,2 +1,94 @@
|
|||||||
# ComiPy
|
# ComiPy - Python 漫画管理器
|
||||||
|
|
||||||
|
ComiPy 是一个由 Python 编写的漫画管理器,旨在简化漫画文件的管理和查看。该工具支持上传压缩的 ZIP 文件格式的漫画,并通过一个直观的 Web 页面进行浏览。其特性包括:
|
||||||
|
|
||||||
|
- 支持上传并处理 ZIP 打包的漫画文件
|
||||||
|
- Web 界面查看漫画
|
||||||
|
- 实时生成压缩后的 WebP 图像进行传输,优化加载速度
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- **漫画上传**:上传 ZIP 格式的漫画文件,自动解压并展示。
|
||||||
|
- **Web 浏览**:通过简单易用的 Web 页面查看漫画内容。
|
||||||
|
- **图像压缩**:实时将图像转换为 WebP 格式,以减少加载时间并提升用户体验。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### 先决条件
|
||||||
|
|
||||||
|
- Python 3.8 或更高版本
|
||||||
|
- 必须安装 `pip` 包管理工具
|
||||||
|
|
||||||
|
### 安装步骤
|
||||||
|
|
||||||
|
1. 克隆本仓库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Kakune55/ComiPy.git
|
||||||
|
cd ComiPy
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 创建虚拟环境并激活(可选):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv .venv
|
||||||
|
source venv/bin/activate # Linux/MacOS
|
||||||
|
venv\Scripts\activate # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 安装所需依赖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
1. 启动 Web 服务:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash app_control.sh start
|
||||||
|
```
|
||||||
|
|
||||||
|
默认情况下,Web 服务会在 `http://127.0.0.1:8080` 启动。
|
||||||
|
|
||||||
|
2. 打开浏览器并访问 `http://127.0.0.1:8080`,即可上传和浏览漫画。
|
||||||
|
|
||||||
|
## 配置文件
|
||||||
|
|
||||||
|
1. 项目自带了一个模版配置文件 `app_d.ini`,使用时需要复制一份并重命名为 `app.ini`。
|
||||||
|
2. `app.ini` 文件中包含以下配置:
|
||||||
|
|
||||||
|
以下是 `app_d.ini` 配置文件的说明:
|
||||||
|
|
||||||
|
### [server] 部分
|
||||||
|
- **port=8080**: 服务器监听的端口号为 8080。
|
||||||
|
- **debug=0**: 是否开启调试模式,0 表示关闭,1 表示开启。
|
||||||
|
- **host=0.0.0.0**: 服务器绑定的主机地址,0.0.0.0 表示监听所有可用网络接口。
|
||||||
|
- **threaded=0**: 是否启用多线程处理请求,0 表示关闭,1 表示开启。
|
||||||
|
|
||||||
|
### [user] 部分
|
||||||
|
- **username=admin**: 用户名,默认为 admin。
|
||||||
|
- **password=admin**: 密码,默认为 admin。建议在生产环境中修改此密码以增强安全性。
|
||||||
|
|
||||||
|
### [database] 部分
|
||||||
|
- **path=./data/metadata.db**: 数据库文件路径,相对路径为当前目录下的 `data` 文件夹中的 `metadata.db` 文件。
|
||||||
|
|
||||||
|
### [file] 部分
|
||||||
|
- **inputdir=./input**: 输入文件夹路径,用于存放输入文件。
|
||||||
|
- **storedir=./data/file**: 存储文件夹路径,用于存放处理后的文件。
|
||||||
|
- **tmpdir=./data/tmp**: 临时文件夹路径,用于存放临时文件。
|
||||||
|
|
||||||
|
### [img] 部分
|
||||||
|
- **encode=jpg**: 图片编码格式,默认为 jpg 支持(jpg/webp)。
|
||||||
|
- **miniSize=400**: 图片的最小边长,默认为 400 像素。
|
||||||
|
- **fullSize=1000**: 图片的最大边长,默认为 1000 像素。
|
||||||
|
|
||||||
|
|
||||||
|
## 贡献
|
||||||
|
|
||||||
|
欢迎贡献!如果你有任何想法或建议,欢迎提交 Issue 或 Pull Request。
|
||||||
|
|
||||||
|
## 许可
|
||||||
|
|
||||||
|
该项目遵循 MIT 许可证 - 详情请参见 [LICENSE](LICENSE) 文件。
|
@@ -2,33 +2,99 @@
|
|||||||
|
|
||||||
VENV_DIR=".venv"
|
VENV_DIR=".venv"
|
||||||
PYTHON_APP="main.py"
|
PYTHON_APP="main.py"
|
||||||
|
LOG_FILE="output.log"
|
||||||
|
PID_FILE="app.pid"
|
||||||
|
|
||||||
|
# 显示帮助信息
|
||||||
|
show_help() {
|
||||||
|
echo -e "\033[1mUsage: $0 {start|stop|status|restart|help}\033[0m"
|
||||||
|
echo -e "\n\033[1mCommands:\033[0m"
|
||||||
|
echo -e " start Start the application (with virtual environment and logging)."
|
||||||
|
echo -e " stop Stop the application (based on PID stored in $PID_FILE)."
|
||||||
|
echo -e " status Check if the application is running (based on PID file)."
|
||||||
|
echo -e " restart Stop and then start the application."
|
||||||
|
echo -e " help Display this help message.\n"
|
||||||
|
echo -e "\033[1mEnvironment:\033[0m"
|
||||||
|
echo -e " VENV_DIR The directory for the Python virtual environment (default: .venv)."
|
||||||
|
echo -e " PYTHON_APP The Python application to run (default: main.py)."
|
||||||
|
echo -e " LOG_FILE The log file where the output is stored (default: output.log)."
|
||||||
|
echo -e " PID_FILE The file where the PID of the application is stored (default: app.pid).\n"
|
||||||
|
echo -e "Make sure to set up your Python virtual environment before running the script.\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查Python3是否可用
|
||||||
|
check_python() {
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo -e "\033[31mError: Python3 is not installed or not found in PATH.\033[0m"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
start_app() {
|
start_app() {
|
||||||
|
if [ ! -d "$VENV_DIR" ]; then
|
||||||
|
echo -e "\033[31mError: Virtual environment directory $VENV_DIR not found! \033[0m"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$PYTHON_APP" ]; then
|
||||||
|
echo -e "\033[31mError: Python application $PYTHON_APP not found! \033[0m"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
source "$VENV_DIR/bin/activate"
|
source "$VENV_DIR/bin/activate"
|
||||||
nohup python3 $PYTHON_APP > output.log 2>&1 &
|
|
||||||
echo -e "\033[32m Application started! \033[0m"
|
# 检查Python是否正确安装
|
||||||
|
check_python
|
||||||
|
|
||||||
|
# 启动应用并将输出重定向到日志文件
|
||||||
|
nohup python3 "$PYTHON_APP" > "$LOG_FILE" 2>&1 &
|
||||||
|
echo $! > "$PID_FILE"
|
||||||
|
echo -e "\033[32mApplication started! Logs are being written to $LOG_FILE\033[0m"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 停止应用
|
||||||
stop_app() {
|
stop_app() {
|
||||||
pid=$(ps aux | grep $PYTHON_APP | grep -v grep | awk '{print $2}')
|
if [ ! -f "$PID_FILE" ]; then
|
||||||
if [ -n "$pid" ]; then
|
echo -e "\033[31mError: PID file $PID_FILE not found! \033[0m"
|
||||||
kill $pid
|
exit 1
|
||||||
echo -e "\033[32m Application ended! \033[0m"
|
fi
|
||||||
|
|
||||||
|
pid=$(cat "$PID_FILE")
|
||||||
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||||
|
kill "$pid"
|
||||||
|
rm "$PID_FILE"
|
||||||
|
echo -e "\033[32mApplication stopped!\033[0m"
|
||||||
else
|
else
|
||||||
echo -e "\033[31m Application not runing! \033[0m"
|
echo -e "\033[31mError: Application not running or PID not found! \033[0m"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 检查应用状态
|
||||||
check_app_status() {
|
check_app_status() {
|
||||||
pid=$(ps aux | grep $PYTHON_APP | grep -v grep | awk '{print $2}')
|
if [ ! -f "$PID_FILE" ]; then
|
||||||
if [ -n "$pid" ]; then
|
echo -e "\033[31mError: Application not running! No PID file found.\033[0m"
|
||||||
echo -e "PID:" $pid
|
exit 1
|
||||||
echo -e "\033[32m Application runing! \033[0m"
|
fi
|
||||||
|
|
||||||
|
pid=$(cat "$PID_FILE")
|
||||||
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||||
|
echo -e "PID: $pid"
|
||||||
|
echo -e "\033[32mApplication is running.\033[0m"
|
||||||
else
|
else
|
||||||
echo -e "\033[31m Application not runing! \033[0m"
|
echo -e "\033[31mError: Application not running. PID not found or process not running.\033[0m"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 重启应用
|
||||||
|
restart_app() {
|
||||||
|
stop_app
|
||||||
|
start_app
|
||||||
|
echo -e "\033[32mApplication restarted!\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 处理命令行参数
|
||||||
case "$1" in
|
case "$1" in
|
||||||
start)
|
start)
|
||||||
start_app
|
start_app
|
||||||
@@ -39,8 +105,15 @@ case "$1" in
|
|||||||
status)
|
status)
|
||||||
check_app_status
|
check_app_status
|
||||||
;;
|
;;
|
||||||
|
restart)
|
||||||
|
restart_app
|
||||||
|
;;
|
||||||
|
help)
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "\033[33m Usage: $0 {start|stop|status}\033[0m"
|
echo -e "\033[33mInvalid command.\033[0m"
|
||||||
|
show_help
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
@@ -49,7 +49,7 @@ def new(filename: str, pagenumber:int):
|
|||||||
def getMetadata(form: int, num: int, search:str = None):
|
def getMetadata(form: int, num: int, search:str = None):
|
||||||
conn = util.getConn()
|
conn = util.getConn()
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
if search is None:
|
if search == None:
|
||||||
cursor = c.execute(
|
cursor = c.execute(
|
||||||
"SELECT * FROM Metadata ORDER BY num desc LIMIT ?, ?", (form, num)
|
"SELECT * FROM Metadata ORDER BY num desc LIMIT ?, ?", (form, num)
|
||||||
)
|
)
|
||||||
|
2
file.py
2
file.py
@@ -44,7 +44,7 @@ def raedZip(bookid: str, index: int):
|
|||||||
image_files = [
|
image_files = [
|
||||||
file
|
file
|
||||||
for file in zip_ref.namelist()
|
for file in zip_ref.namelist()
|
||||||
if file.lower().endswith((".png", ".jpg", ".jpeg"))
|
if file.lower().endswith((".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"))
|
||||||
]
|
]
|
||||||
|
|
||||||
if not image_files:
|
if not image_files:
|
||||||
|
8
main.py
8
main.py
@@ -3,10 +3,10 @@ import db.util
|
|||||||
import db.file, file
|
import db.file, file
|
||||||
from flask import *
|
from flask import *
|
||||||
|
|
||||||
from web.api_Img import api_Img_bp
|
from router.api_Img import api_Img_bp
|
||||||
from web.page import page_bp
|
from router.page import page_bp
|
||||||
from web.admin_page import admin_page_bp
|
from router.admin_page import admin_page_bp
|
||||||
from web.api_comment import comment_api_bp
|
from router.api_comment import comment_api_bp
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ def overview(page): # 概览
|
|||||||
lastPageList = range(page - 3, page)
|
lastPageList = range(page - 3, page)
|
||||||
nextPageList = range(page + 1, page + 4)
|
nextPageList = range(page + 1, page + 4)
|
||||||
return render_template(
|
return render_template(
|
||||||
"overview.html",
|
"overview.html.j2",
|
||||||
list=metaDataList,
|
list=metaDataList,
|
||||||
lastPageList=lastPageList,
|
lastPageList=lastPageList,
|
||||||
pagenow=page,
|
pagenow=page,
|
||||||
@@ -60,7 +60,7 @@ def book(bookid): # 接口
|
|||||||
)
|
)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"book.html",
|
"book.html.j2",
|
||||||
id=bookid,
|
id=bookid,
|
||||||
data=data,
|
data=data,
|
||||||
time=time.strftime("%Y-%m-%d %H:%M:%S", local_time),
|
time=time.strftime("%Y-%m-%d %H:%M:%S", local_time),
|
||||||
@@ -77,7 +77,7 @@ def view(bookid): # 接口
|
|||||||
data = db.file.searchByid(bookid)
|
data = db.file.searchByid(bookid)
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
return abort(404)
|
return abort(404)
|
||||||
return render_template("view.html.j2", id=bookid, index=range(1, data[0][3]))
|
return render_template("view.html.j2", id=bookid, index=range(0, data[0][3]))
|
||||||
|
|
||||||
|
|
||||||
@page_bp.route("/upload", methods=["GET", "POST"]) # 文件上传
|
@page_bp.route("/upload", methods=["GET", "POST"]) # 文件上传
|
@@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<title>详情页面</title>
|
<title>ComiPy-详情页面</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="movie-poster">
|
<div class="movie-poster">
|
||||||
<!-- 封面 -->
|
<!-- 封面 -->
|
||||||
<img class="img-thumbnail" src="/api/img/{{ id }}/1?mini=yes" alt="封面" style="max-width: 100%;">
|
<img class="img-thumbnail" src="/api/img/{{ id }}/0?mini=yes" alt="封面" style="max-width: 100%;">
|
||||||
</div>
|
</div>
|
||||||
<div class="movie-details">
|
<div class="movie-details">
|
||||||
<!-- 详细信息 -->
|
<!-- 详细信息 -->
|
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>登录页面</title>
|
<title>ComiPy-登录</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<title>展示图片列表和封面</title>
|
<title>ComiPy-概览</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
{% if item[4] > aftertime %}
|
{% if item[4] > aftertime %}
|
||||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">New</span>
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">New</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<img src="/api/img/{{ item[1] }}/1?mini=yes" class="img-thumbnail card-img-top"
|
<img src="/api/img/{{ item[1] }}/0?mini=yes" class="img-thumbnail card-img-top"
|
||||||
onclick="linkjump('{{ item[1] }}')" />
|
onclick="linkjump('{{ item[1] }}')" />
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">{{ item[2] }}</p>
|
<p class="card-text">{{ item[2] }}</p>
|
@@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
<title>上传文件</title>
|
<title>ComiPy-上传文件</title>
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>漫画详情页</title>
|
<title>ComiPy-漫画详情页</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
|
Reference in New Issue
Block a user