mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-08 01:03:08 +08:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd391d14f9 | ||
|
|
d7844968ab | ||
|
|
70ea398f14 | ||
|
|
860d55a0e2 | ||
|
|
0e35cec6e2 | ||
|
|
5778e86260 | ||
|
|
967d0b1205 | ||
|
|
0b2d419000 | ||
|
|
149104063c | ||
|
|
498168a2d3 | ||
|
|
88e307416d | ||
|
|
3bb2eedb33 | ||
|
|
36c046ad6a | ||
|
|
85396df221 | ||
|
|
2f0f58783e | ||
|
|
2d989d4229 | ||
|
|
ecc8b6b385 | ||
|
|
aa90c5d5c0 | ||
|
|
5f7d93f170 | ||
|
|
0fbe51f257 | ||
|
|
be941ebdd1 | ||
|
|
4d900c2eb0 | ||
|
|
93c473afe7 | ||
|
|
4c9a66f586 | ||
|
|
375e16e0dc | ||
|
|
91085d13a3 | ||
|
|
3f83894dc6 | ||
|
|
5946684ee6 | ||
|
|
7e3f25879f | ||
|
|
48dcc3ee1b | ||
|
|
fca0a4b511 | ||
|
|
d6831a8881 | ||
|
|
39a646ed92 | ||
|
|
595965c5d0 | ||
|
|
3bb6f8a0c0 | ||
|
|
1924a2017e | ||
|
|
60140fd2e6 | ||
|
|
65b5219e45 | ||
|
|
ae2f649aee | ||
|
|
bf3e860a18 | ||
|
|
0b44a91493 | ||
|
|
16077b3341 | ||
|
|
a7cedde721 | ||
|
|
ecd53192dc | ||
|
|
a03c76e211 | ||
|
|
de427fd7a9 |
65
.github/workflows/build-windows.yml
vendored
65
.github/workflows/build-windows.yml
vendored
@@ -1,65 +0,0 @@
|
||||
name: MoviePilot Windows Builder
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- version.py
|
||||
|
||||
jobs:
|
||||
Windows-build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Release Version
|
||||
id: release_version
|
||||
run: |
|
||||
$app_version = Select-String -Path "version.py" -Pattern "APP_VERSION\s=\s'v(.*)'" | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||
$env:GITHUB_ENV += "app_version=$app_version"
|
||||
|
||||
- name: Init Python 3.11.4
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11.4'
|
||||
|
||||
- name: Install Dependent Packages
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install wheel pyinstaller
|
||||
pip install -r requirements.txt
|
||||
shell: pwsh
|
||||
|
||||
- name: Pyinstaller
|
||||
run: |
|
||||
pyinstaller windows.spec
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload Windows File
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows
|
||||
path: dist/MoviePilot.exe
|
||||
|
||||
- name: Generate Release
|
||||
id: generate_release
|
||||
uses: actions/create-release@latest
|
||||
with:
|
||||
tag_name: v${{ env.app_version }}
|
||||
release_name: v${{ env.app_version }}
|
||||
body: ${{ github.event.commits[0].message }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: dwenegar/upload-release-assets@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
release_id: ${{ steps.generate_release.outputs.id }}
|
||||
assets_path: |
|
||||
dist/MoviePilot.exe
|
||||
99
.github/workflows/build.yml
vendored
99
.github/workflows/build.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: MoviePilot Docker Builder
|
||||
name: MoviePilot Builder
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
app_version=$(cat version.py |sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp")
|
||||
echo "app_version=$app_version" >> $GITHUB_ENV
|
||||
|
||||
- name: Docker meta
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
@@ -56,4 +56,97 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha, scope=${{ github.workflow }}
|
||||
cache-to: type=gha, scope=${{ github.workflow }}
|
||||
cache-to: type=gha, scope=${{ github.workflow }}
|
||||
|
||||
Windows-build:
|
||||
runs-on: windows-latest
|
||||
name: Build Windows Binary
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Init Python 3.11.4
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11.4'
|
||||
|
||||
- name: Install Dependent Packages
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install wheel pyinstaller
|
||||
pip install -r requirements.txt
|
||||
shell: pwsh
|
||||
|
||||
- name: Prepare Frontend
|
||||
run: |
|
||||
Invoke-WebRequest -Uri "http://nginx.org/download/nginx-1.25.2.zip" -OutFile "nginx.zip"
|
||||
Expand-Archive -Path "nginx.zip" -DestinationPath "nginx-1.25.2"
|
||||
Move-Item -Path "nginx-1.25.2/nginx-1.25.2" -Destination "nginx"
|
||||
Remove-Item -Path "nginx.zip"
|
||||
Remove-Item -Path "nginx-1.25.2" -Recurse -Force
|
||||
$FRONTEND_VERSION = (Invoke-WebRequest -Uri "https://api.github.com/repos/jxxghp/MoviePilot-Frontend/releases/latest" | ConvertFrom-Json).tag_name
|
||||
Invoke-WebRequest -Uri "https://github.com/jxxghp/MoviePilot-Frontend/releases/download/$FRONTEND_VERSION/dist.zip" -OutFile "dist.zip"
|
||||
Expand-Archive -Path "dist.zip" -DestinationPath "dist"
|
||||
Move-Item -Path "dist/dist/*" -Destination "nginx/html" -Force
|
||||
Remove-Item -Path "dist.zip"
|
||||
Remove-Item -Path "dist" -Recurse -Force
|
||||
Move-Item -Path "nginx/html/nginx.conf" -Destination "nginx/conf/nginx.conf" -Force
|
||||
New-Item -Path "nginx/temp" -ItemType Directory -Force
|
||||
New-Item -Path "nginx/temp/__keep__.txt" -ItemType File -Force
|
||||
New-Item -Path "nginx/logs" -ItemType Directory -Force
|
||||
New-Item -Path "nginx/logs/__keep__.txt" -ItemType File -Force
|
||||
shell: pwsh
|
||||
|
||||
- name: Pyinstaller
|
||||
run: |
|
||||
pyinstaller windows.spec
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload Windows File
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows
|
||||
path: dist/MoviePilot.exe
|
||||
|
||||
Create-release:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ Windows-build, Docker-build ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Release Version
|
||||
id: release_version
|
||||
run: |
|
||||
app_version=$(cat version.py |sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp")
|
||||
echo "app_version=$app_version" >> $GITHUB_ENV
|
||||
|
||||
- name: Download Artifact
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- name: get release_informations
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir releases
|
||||
mv ./windows/MoviePilot.exe ./releases/MoviePilot_v${{ env.app_version }}.exe
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: v${{ env.app_version }}
|
||||
release_name: v${{ env.app_version }}
|
||||
body: ${{ github.event.commits[0].message }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: dwenegar/upload-release-assets@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
release_id: ${{ steps.create_release.outputs.id }}
|
||||
assets_path: |
|
||||
./releases/
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
.idea/
|
||||
*.c
|
||||
build/
|
||||
dist/
|
||||
nginx/
|
||||
test.py
|
||||
app/helper/sites.py
|
||||
config/user.db
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
FROM python:3.11.4-slim-bullseye
|
||||
ARG MOVIEPILOT_VERSION
|
||||
ENV LANG="C.UTF-8" \
|
||||
TZ="Asia/Shanghai" \
|
||||
HOME="/moviepilot" \
|
||||
CONFIG_DIR="/config" \
|
||||
TERM="xterm" \
|
||||
PUID=0 \
|
||||
PGID=0 \
|
||||
UMASK=000 \
|
||||
PORT=3001 \
|
||||
NGINX_PORT=3000 \
|
||||
PROXY_HOST="" \
|
||||
MOVIEPILOT_AUTO_UPDATE=true \
|
||||
MOVIEPILOT_AUTO_UPDATE_DEV=false \
|
||||
CONFIG_DIR="/config"
|
||||
AUTH_SITE="iyuu" \
|
||||
IYUU_SIGN=""
|
||||
WORKDIR "/app"
|
||||
RUN apt-get update -y \
|
||||
&& apt-get -y install \
|
||||
@@ -27,6 +31,7 @@ RUN apt-get update -y \
|
||||
dumb-init \
|
||||
jq \
|
||||
haproxy \
|
||||
rclone \
|
||||
&& \
|
||||
if [ "$(uname -m)" = "x86_64" ]; \
|
||||
then ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1; \
|
||||
|
||||
30
README.md
30
README.md
@@ -4,8 +4,6 @@
|
||||
|
||||
# 仅用于学习交流使用,请勿在任何国内平台宣传该项目!
|
||||
|
||||
Docker:https://hub.docker.com/r/jxxghp/moviepilot
|
||||
|
||||
发布频道:https://t.me/moviepilot_channel
|
||||
|
||||
## 主要特性
|
||||
@@ -33,19 +31,25 @@ MoviePilot需要配套下载器和媒体服务器配合使用。
|
||||
|
||||
### 4. **安装MoviePilot**
|
||||
|
||||
目前仅提供docker镜像,点击 [这里](https://hub.docker.com/r/jxxghp/moviepilot) 或执行命令:
|
||||
- Docker镜像
|
||||
|
||||
```shell
|
||||
docker pull jxxghp/moviepilot:latest
|
||||
```
|
||||
点击 [这里](https://hub.docker.com/r/jxxghp/moviepilot) 或执行命令:
|
||||
|
||||
```shell
|
||||
docker pull jxxghp/moviepilot:latest
|
||||
```
|
||||
|
||||
- Windows
|
||||
|
||||
下载 [MoviePilot.exe](https://github.com/jxxghp/MoviePilot/releases),双击运行后自动生成配置文件目录。
|
||||
|
||||
## 配置
|
||||
|
||||
项目的所有配置均通过环境变量进行设置,支持两种配置方式:
|
||||
- 在docker环境变量部分进行参数配置,部分环境建立容器后会自动显示待配置项,如未自动显示配置项则需要手动增加对应环境变量。
|
||||
- 下载 [app.env](https://github.com/jxxghp/MoviePilot/raw/main/config/app.env) 文件,修改好配置后放置到配置文件映射路径根目录,配置项可根据说明自主增减。
|
||||
- 在Docker环境变量部分或Wdinows系统环境变量中进行参数配置,如未自动显示配置项则需要手动增加对应环境变量。
|
||||
- 下载 [app.env](https://github.com/jxxghp/MoviePilot/raw/main/config/app.env) 配置文件,修改好配置后放置到配置文件映射路径根目录,配置项可根据说明自主增减。
|
||||
|
||||
配置文件映射路径:`/config`,配置项生效优先级:环境变量 > env文件 > 默认值,部分参数如路径映射、站点认证、权限端口等必须通过环境变量进行配置。
|
||||
配置文件映射路径:`/config`,配置项生效优先级:环境变量 > env文件 > 默认值,**部分参数如路径映射、站点认证、权限端口、时区等必须通过环境变量进行配置**。
|
||||
|
||||
> $\color{red}{*}$ 号标识的为必填项,其它为可选项,可选项可删除配置变量从而使用默认值。
|
||||
|
||||
@@ -56,13 +60,13 @@ docker pull jxxghp/moviepilot:latest
|
||||
- **PUID**:运行程序用户的`uid`,默认`0`(仅支持环境变量配置)
|
||||
- **PGID**:运行程序用户的`gid`,默认`0`(仅支持环境变量配置)
|
||||
- **UMASK**:掩码权限,默认`000`,可以考虑设置为`022`(仅支持环境变量配置)
|
||||
- **MOVIEPILOT_AUTO_UPDATE**:重启更新,`true`/`false`,默认`true` **注意:如果出现网络问题可以配置`PROXY_HOST`,具体看下方`PROXY_HOST`解释**(仅支持环境变量配置)
|
||||
- **PROXY_HOST:** 网络代理,访问themoviedb或者重启更新需要使用代理访问,格式为`http(s)://ip:port`、`socks5://user:pass@host:port`(仅支持环境变量配置)
|
||||
- **MOVIEPILOT_AUTO_UPDATE**:重启更新,`true`/`false`,默认`true` **注意:如果出现网络问题可以配置`PROXY_HOST`**(仅支持环境变量配置)
|
||||
- **MOVIEPILOT_AUTO_UPDATE_DEV**:重启时更新到未发布的开发版本代码,`true`/`false`,默认`false`(仅支持环境变量配置)
|
||||
---
|
||||
- **SUPERUSER $\color{red}{*}$ :** 超级管理员用户名,默认`admin`,安装后使用该用户登录后台管理界面
|
||||
- **SUPERUSER_PASSWORD $\color{red}{*}$ :** 超级管理员初始密码,默认`password`,建议修改为复杂密码
|
||||
- **API_TOKEN $\color{red}{*}$ :** API密钥,默认`moviepilot`,在媒体服务器Webhook、微信回调等地址配置中需要加上`?token=`该值,建议修改为复杂字符串
|
||||
- **PROXY_HOST:** 网络代理,访问themoviedb或者重启更新需要使用代理访问,格式为`http(s)://ip:port`、`socks5://user:pass@host:port`(可选)
|
||||
- **TMDB_API_DOMAIN:** TMDB API地址,默认`api.themoviedb.org`,也可配置为`api.tmdb.org`或其它中转代理服务地址,能连通即可
|
||||
- **TMDB_IMAGE_DOMAIN:** TMDB图片地址,默认`image.tmdb.org`,可配置为其它中转代理以加速TMDB图片显示,如:`static-mdb.v.geilijiasu.com`
|
||||
---
|
||||
@@ -70,7 +74,7 @@ docker pull jxxghp/moviepilot:latest
|
||||
- **SCRAP_SOURCE:** 刮削元数据及图片使用的数据源,`themoviedb`/`douban`,默认`themoviedb`
|
||||
- **SCRAP_FOLLOW_TMDB:** 新增已入库媒体是否跟随TMDB信息变化,`true`/`false`,默认`true`
|
||||
---
|
||||
- **TRANSFER_TYPE $\color{red}{*}$ :** 整理转移方式,支持`link`/`copy`/`move`/`softlink` **注意:在`link`和`softlink`转移方式下,转移后的文件会继承源文件的权限掩码,不受`UMASK`影响**
|
||||
- **TRANSFER_TYPE $\color{red}{*}$ :** 整理转移方式,支持`link`/`copy`/`move`/`softlink`/`rclone_copy`/`rclone_move` **注意:在`link`和`softlink`转移方式下,转移后的文件会继承源文件的权限掩码,不受`UMASK`影响;rclone需要自行映射rclone配置目录到容器中或在容器内完成rclone配置,节点名称必须为:`MP`**
|
||||
- **LIBRARY_PATH $\color{red}{*}$ :** 媒体库目录,多个目录使用`,`分隔
|
||||
- **LIBRARY_MOVIE_NAME:** 电影媒体库目录名称(不是完整路径),默认`电影`
|
||||
- **LIBRARY_TV_NAME:** 电视剧媒体库目录称(不是完整路径),默认`电视剧`
|
||||
@@ -169,7 +173,7 @@ docker pull jxxghp/moviepilot:latest
|
||||
|
||||
### 2. **用户认证**
|
||||
|
||||
`MoviePilot`需要认证后才能使用,配置`AUTH_SITE`后,需要根据下表配置对应站点的认证参数(**仅能通过docker环境变量配置**)
|
||||
`MoviePilot`需要认证后才能使用,配置`AUTH_SITE`后,需要根据下表配置对应站点的认证参数(**仅能通过环境变量配置**)
|
||||
|
||||
- **AUTH_SITE $\color{red}{*}$ :** 认证站点,支持`iyuu`/`hhclub`/`audiences`/`hddolby`/`zmpt`/`freefarm`/`hdfans`/`wintersakura`/`leaves`/`1ptba`/`icc2022`/`ptlsp`/`xingtan`
|
||||
|
||||
|
||||
@@ -139,6 +139,18 @@ def tv_weekly_global(page: int = 1,
|
||||
return [MediaInfo(douban_info=tv).to_dict() for tv in tvs]
|
||||
|
||||
|
||||
@router.get("/tv_animation", summary="豆瓣动画剧集", response_model=List[schemas.MediaInfo])
|
||||
def tv_animation(page: int = 1,
|
||||
count: int = 30,
|
||||
db: Session = Depends(get_db),
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
热门动画剧集
|
||||
"""
|
||||
tvs = DoubanChain(db).tv_animation(page=page, count=count)
|
||||
return [MediaInfo(douban_info=tv).to_dict() for tv in tvs]
|
||||
|
||||
|
||||
@router.get("/{doubanid}", summary="查询豆瓣详情", response_model=schemas.MediaInfo)
|
||||
def douban_info(doubanid: str,
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
@@ -49,10 +49,10 @@ async def login_access_token(
|
||||
user.create(db)
|
||||
elif not user.is_active:
|
||||
raise HTTPException(status_code=403, detail="用户未启用")
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
return schemas.Token(
|
||||
access_token=security.create_access_token(
|
||||
user.id, expires_delta=access_token_expires
|
||||
user.id,
|
||||
expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
),
|
||||
token_type="bearer",
|
||||
super_user=user.is_superuser,
|
||||
|
||||
@@ -29,18 +29,33 @@ class DoubanChain(ChainBase):
|
||||
"""
|
||||
根据豆瓣信息识别媒体信息
|
||||
"""
|
||||
# 使用原标题匹配
|
||||
meta = MetaInfo(title=doubaninfo.get("original_title") or doubaninfo.get("title"))
|
||||
# 优先使用原标题匹配
|
||||
season_meta = None
|
||||
if doubaninfo.get("original_title"):
|
||||
meta = MetaInfo(title=doubaninfo.get("original_title"))
|
||||
season_meta = MetaInfo(title=doubaninfo.get("title"))
|
||||
# 合并季
|
||||
meta.begin_season = season_meta.begin_season
|
||||
else:
|
||||
meta = MetaInfo(title=doubaninfo.get("title"))
|
||||
# 年份
|
||||
if doubaninfo.get("year"):
|
||||
meta.year = doubaninfo.get("year")
|
||||
# 处理类型
|
||||
if isinstance(doubaninfo.get('media_type'), MediaType):
|
||||
meta.type = doubaninfo.get('media_type')
|
||||
else:
|
||||
meta.type = MediaType.MOVIE if doubaninfo.get("type") == "movie" else MediaType.TV
|
||||
# 识别媒体信息
|
||||
mediainfo: MediaInfo = self.recognize_media(meta=meta, mtype=meta.type)
|
||||
# 使用原标题识别媒体信息
|
||||
mediainfo = self.recognize_media(meta=meta, mtype=meta.type)
|
||||
if not mediainfo:
|
||||
logger.warn(f'{meta.name} 未识别到TMDB媒体信息')
|
||||
return Context(meta_info=meta, media_info=MediaInfo(douban_info=doubaninfo))
|
||||
if season_meta and season_meta.name != meta.name:
|
||||
# 使用主标题识别媒体信息
|
||||
mediainfo = self.recognize_media(meta=season_meta, mtype=season_meta.type)
|
||||
if not mediainfo:
|
||||
logger.warn(f'{season_meta.name} 未识别到TMDB媒体信息')
|
||||
return Context(meta_info=season_meta, media_info=MediaInfo(douban_info=doubaninfo))
|
||||
logger.info(f'识别到媒体信息:{mediainfo.type.value} {mediainfo.title_year} {meta.season}')
|
||||
mediainfo.set_douban_info(doubaninfo)
|
||||
return Context(meta_info=meta, media_info=mediainfo)
|
||||
@@ -84,3 +99,9 @@ class DoubanChain(ChainBase):
|
||||
"""
|
||||
return self.run_module("douban_discover", mtype=mtype, sort=sort, tags=tags,
|
||||
page=page, count=count)
|
||||
|
||||
def tv_animation(self, page: int = 1, count: int = 30) -> List[dict]:
|
||||
"""
|
||||
获取动画剧集
|
||||
"""
|
||||
return self.run_module("tv_animation", page=page, count=count)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -26,6 +25,8 @@ class Settings(BaseSettings):
|
||||
HOST: str = "0.0.0.0"
|
||||
# API监听端口
|
||||
PORT: int = 3001
|
||||
# 前端监听端口
|
||||
NGINX_PORT: int = 3000
|
||||
# 是否调试模式
|
||||
DEBUG: bool = False
|
||||
# 是否开发模式
|
||||
|
||||
98
app/main.py
98
app/main.py
@@ -1,10 +1,22 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
import uvicorn as uvicorn
|
||||
from PIL import Image
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from uvicorn import Config
|
||||
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
# 禁用输出
|
||||
if SystemUtils.is_frozen():
|
||||
sys.stdout = open(os.devnull, 'w')
|
||||
sys.stderr = open(os.devnull, 'w')
|
||||
|
||||
from app.command import Command
|
||||
from app.core.config import settings
|
||||
from app.core.module import ModuleManager
|
||||
@@ -44,6 +56,82 @@ def init_routers():
|
||||
App.include_router(arr_router, prefix="/api/v3")
|
||||
|
||||
|
||||
def start_frontend():
|
||||
"""
|
||||
启动前端服务
|
||||
"""
|
||||
if not SystemUtils.is_frozen():
|
||||
return
|
||||
nginx_path = settings.ROOT_PATH / 'nginx'
|
||||
if not nginx_path.exists():
|
||||
return
|
||||
import subprocess
|
||||
if SystemUtils.is_windows():
|
||||
subprocess.Popen("start nginx.exe",
|
||||
cwd=nginx_path,
|
||||
shell=True)
|
||||
else:
|
||||
subprocess.Popen("nohup ./nginx &",
|
||||
cwd=nginx_path,
|
||||
shell=True)
|
||||
|
||||
|
||||
def stop_frontend():
|
||||
"""
|
||||
停止前端服务
|
||||
"""
|
||||
if not SystemUtils.is_frozen():
|
||||
return
|
||||
import subprocess
|
||||
if SystemUtils.is_windows():
|
||||
subprocess.Popen(f"taskkill /f /im nginx.exe", shell=True)
|
||||
else:
|
||||
subprocess.Popen(f"killall nginx", shell=True)
|
||||
|
||||
|
||||
def start_tray():
|
||||
"""
|
||||
启动托盘图标
|
||||
"""
|
||||
|
||||
if not SystemUtils.is_frozen():
|
||||
return
|
||||
|
||||
def open_web():
|
||||
"""
|
||||
调用浏览器打开前端页面
|
||||
"""
|
||||
import webbrowser
|
||||
webbrowser.open(f"http://localhost:{settings.NGINX_PORT}")
|
||||
|
||||
def quit_app():
|
||||
"""
|
||||
退出程序
|
||||
"""
|
||||
TrayIcon.stop()
|
||||
Server.should_exit = True
|
||||
|
||||
import pystray
|
||||
|
||||
# 托盘图标
|
||||
TrayIcon = pystray.Icon(
|
||||
settings.PROJECT_NAME,
|
||||
icon=Image.open(settings.ROOT_PATH / 'app.ico'),
|
||||
menu=pystray.Menu(
|
||||
pystray.MenuItem(
|
||||
'打开',
|
||||
open_web,
|
||||
),
|
||||
pystray.MenuItem(
|
||||
'退出',
|
||||
quit_app,
|
||||
)
|
||||
)
|
||||
)
|
||||
# 启动托盘图标
|
||||
threading.Thread(target=TrayIcon.run, daemon=True).start()
|
||||
|
||||
|
||||
@App.on_event("shutdown")
|
||||
def shutdown_server():
|
||||
"""
|
||||
@@ -59,6 +147,8 @@ def shutdown_server():
|
||||
DisplayHelper().stop()
|
||||
# 停止定时服务
|
||||
Scheduler().stop()
|
||||
# 停止前端服务
|
||||
stop_frontend()
|
||||
|
||||
|
||||
@App.on_event("startup")
|
||||
@@ -66,7 +156,7 @@ def start_module():
|
||||
"""
|
||||
启动模块
|
||||
"""
|
||||
# 虚伪显示
|
||||
# 虚拟显示
|
||||
DisplayHelper()
|
||||
# 站点管理
|
||||
SitesHelper()
|
||||
@@ -80,12 +170,16 @@ def start_module():
|
||||
Command()
|
||||
# 初始化路由
|
||||
init_routers()
|
||||
# 启动前端服务
|
||||
start_frontend()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 启动托盘
|
||||
start_tray()
|
||||
# 初始化数据库
|
||||
init_db()
|
||||
# 更新数据库
|
||||
update_db()
|
||||
# 启动服务
|
||||
# 启动API服务
|
||||
Server.run()
|
||||
|
||||
@@ -369,6 +369,16 @@ class DoubanModule(_ModuleBase):
|
||||
return []
|
||||
return infos.get("subject_collection_items")
|
||||
|
||||
def tv_animation(self, page: int = 1, count: int = 30) -> List[dict]:
|
||||
"""
|
||||
获取豆瓣动画剧
|
||||
"""
|
||||
infos = self.doubanapi.tv_animation(start=(page - 1) * count,
|
||||
count=count)
|
||||
if not infos:
|
||||
return []
|
||||
return infos.get("subject_collection_items")
|
||||
|
||||
def search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]:
|
||||
"""
|
||||
搜索媒体信息
|
||||
|
||||
@@ -161,7 +161,13 @@ class DoubanScraper:
|
||||
"""
|
||||
if file_path.exists():
|
||||
return
|
||||
if not url:
|
||||
return
|
||||
try:
|
||||
# 没有后缀时,处理URL转化为jpg格式
|
||||
if not file_path.suffix:
|
||||
url = url.replace("/format/webp", "/format/jpg")
|
||||
file_path.with_suffix(".jpg")
|
||||
logger.info(f"正在下载{file_path.stem}图片:{url} ...")
|
||||
r = RequestUtils().get_res(url=url)
|
||||
if r:
|
||||
|
||||
@@ -23,7 +23,7 @@ class Emby(metaclass=Singleton):
|
||||
if not self._host.startswith("http"):
|
||||
self._host = "http://" + self._host
|
||||
self._apikey = settings.EMBY_API_KEY
|
||||
self.user = self.get_user()
|
||||
self.user = self.get_user(settings.SUPERUSER)
|
||||
self.folders = self.get_emby_folders()
|
||||
|
||||
def is_inactive(self) -> bool:
|
||||
|
||||
@@ -80,6 +80,12 @@ class FileTransferModule(_ModuleBase):
|
||||
elif transfer_type == 'move':
|
||||
# 移动
|
||||
retcode, retmsg = SystemUtils.move(file_item, target_file)
|
||||
elif transfer_type == 'rclone_move':
|
||||
# Rclone 移动
|
||||
retcode, retmsg = SystemUtils.rclone_move(file_item, target_file)
|
||||
elif transfer_type == 'rclone_copy':
|
||||
# Rclone 复制
|
||||
retcode, retmsg = SystemUtils.rclone_copy(file_item, target_file)
|
||||
else:
|
||||
# 复制
|
||||
retcode, retmsg = SystemUtils.copy(file_item, target_file)
|
||||
|
||||
@@ -21,7 +21,7 @@ class Jellyfin(metaclass=Singleton):
|
||||
if not self._host.startswith("http"):
|
||||
self._host = "http://" + self._host
|
||||
self._apikey = settings.JELLYFIN_API_KEY
|
||||
self.user = self.get_user()
|
||||
self.user = self.get_user(settings.SUPERUSER)
|
||||
self.serverid = self.get_server_id()
|
||||
|
||||
def is_inactive(self) -> bool:
|
||||
|
||||
@@ -63,7 +63,10 @@ class TheMovieDbModule(_ModuleBase):
|
||||
# 直接查询详情
|
||||
info = self.tmdb.get_info(mtype=mtype, tmdbid=tmdbid)
|
||||
elif meta:
|
||||
logger.info(f"正在识别 {meta.name} ...")
|
||||
if meta.begin_season:
|
||||
logger.info(f"正在识别 {meta.name} 第{meta.begin_season}季 ...")
|
||||
else:
|
||||
logger.info(f"正在识别 {meta.name} ...")
|
||||
if meta.type == MediaType.UNKNOWN and not meta.year:
|
||||
info = self.tmdb.match_multi(meta.name)
|
||||
else:
|
||||
|
||||
@@ -858,7 +858,7 @@ class BrushFlow(_PluginBase):
|
||||
{
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
'src': '/plugin/upload.png'
|
||||
'src': '/plugin_icon/upload.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -928,7 +928,7 @@ class BrushFlow(_PluginBase):
|
||||
{
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
'src': '/plugin/download.png'
|
||||
'src': '/plugin_icon/download.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -998,7 +998,7 @@ class BrushFlow(_PluginBase):
|
||||
{
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
'src': '/plugin/seed.png'
|
||||
'src': '/plugin_icon/seed.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1068,7 +1068,7 @@ class BrushFlow(_PluginBase):
|
||||
{
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
'src': '/plugin/delete.png'
|
||||
'src': '/plugin_icon/delete.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -211,7 +211,7 @@ class CustomHosts(_PluginBase):
|
||||
# 添加新的Hosts
|
||||
system_hosts.add(new_entrys)
|
||||
system_hosts.write()
|
||||
logger.info("更新系统hosts文件成功")
|
||||
logger.info("更新系统hosts文件成功(注:容器运行则更新容器hosts!)")
|
||||
except Exception as err:
|
||||
err_flag = True
|
||||
logger.error(f"更新系统hosts文件失败:{str(err) or '请检查权限'}")
|
||||
|
||||
@@ -126,6 +126,12 @@ class DirMonitor(_PluginBase):
|
||||
if not mon_path:
|
||||
continue
|
||||
|
||||
# 自定义转移方式
|
||||
_transfer_type = self._transfer_type
|
||||
if mon_path.count("#") == 1:
|
||||
_transfer_type = mon_path.split("#")[1]
|
||||
mon_path = mon_path.split("#")[0]
|
||||
|
||||
# 存储目的目录
|
||||
if SystemUtils.is_windows():
|
||||
if mon_path.count(":") > 1:
|
||||
@@ -136,21 +142,19 @@ class DirMonitor(_PluginBase):
|
||||
else:
|
||||
paths = mon_path.split(":")
|
||||
|
||||
# 自定义转移方式
|
||||
if mon_path.count("#") == 1:
|
||||
self._transferconf[mon_path] = mon_path.split("#")[1]
|
||||
else:
|
||||
self._transferconf[mon_path] = self._transfer_type
|
||||
|
||||
# 目的目录
|
||||
target_path = None
|
||||
if len(paths) > 1:
|
||||
mon_path = paths[0]
|
||||
target_path = Path(paths[1])
|
||||
self._dirconf[mon_path] = target_path
|
||||
|
||||
# 转移方式
|
||||
self._transferconf[mon_path] = _transfer_type
|
||||
|
||||
# 检查媒体库目录是不是下载目录的子目录
|
||||
try:
|
||||
if target_path.is_relative_to(Path(mon_path)):
|
||||
if target_path and target_path.is_relative_to(Path(mon_path)):
|
||||
logger.warn(f"{target_path} 是下载目录 {mon_path} 的子目录,无法监控")
|
||||
self.systemmessage.put(f"{target_path} 是下载目录 {mon_path} 的子目录,无法监控")
|
||||
continue
|
||||
@@ -615,8 +619,9 @@ class DirMonitor(_PluginBase):
|
||||
'rows': 5,
|
||||
'placeholder': '每一行一个目录,支持三种配置方式:\n'
|
||||
'监控目录\n'
|
||||
'监控目录#转移方式(move|copy|link|softlink|rclone_copy|rclone_move)\n'
|
||||
'监控目录:转移目的目录(需同时在媒体库目录中配置该目的目录)\n'
|
||||
'监控目录:转移目的目录#转移方式(move|copy|link|softlink)'
|
||||
'监控目录:转移目的目录#转移方式(move|copy|link|softlink|rclone_copy|rclone_move)'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -449,15 +449,19 @@ class DoubanSync(_PluginBase):
|
||||
results = self.rsshelper.parse(url)
|
||||
if not results:
|
||||
logger.error(f"未获取到用户 {user_id} 豆瓣RSS数据:{url}")
|
||||
return
|
||||
continue
|
||||
else:
|
||||
logger.info(f"获取到用户 {user_id} 豆瓣RSS数据:{len(results)}")
|
||||
# 解析数据
|
||||
for result in results:
|
||||
try:
|
||||
dtype = result.get("title", "")[:2]
|
||||
title = result.get("title", "")[2:]
|
||||
if dtype not in ["想看"]:
|
||||
logger.info(f'标题:{title},非想看数据,跳过')
|
||||
continue
|
||||
if not result.get("link"):
|
||||
logger.warn(f'标题:{title},未获取到链接,跳过')
|
||||
continue
|
||||
# 判断是否在天数范围
|
||||
pubdate: Optional[datetime.datetime] = result.get("pubdate")
|
||||
@@ -468,6 +472,7 @@ class DoubanSync(_PluginBase):
|
||||
douban_id = result.get("link", "").split("/")[-2]
|
||||
# 检查是否处理过
|
||||
if not douban_id or douban_id in [h.get("doubanid") for h in history]:
|
||||
logger.info(f'标题:{title},豆瓣ID:{douban_id} 已处理过')
|
||||
continue
|
||||
# 根据豆瓣ID获取豆瓣数据
|
||||
doubaninfo: Optional[dict] = self.chain.douban_info(doubanid=douban_id)
|
||||
|
||||
@@ -22,7 +22,7 @@ class InvitesSignin(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "invites.png"
|
||||
# 主题色
|
||||
plugin_color = "#4FB647"
|
||||
plugin_color = "#FFFFFF"
|
||||
# 插件版本
|
||||
plugin_version = "1.0"
|
||||
# 插件作者
|
||||
|
||||
@@ -581,8 +581,10 @@ class PersonMeta(_PluginBase):
|
||||
"""
|
||||
获取豆瓣演员信息
|
||||
"""
|
||||
# 随机休眠1-5秒
|
||||
time.sleep(1 + int(time.time()) % 5)
|
||||
# 随机休眠 3-10 秒
|
||||
sleep_time = 3 + int(time.time()) % 7
|
||||
logger.info(f"随机休眠 {sleep_time}秒 ...")
|
||||
time.sleep(sleep_time)
|
||||
# 匹配豆瓣信息
|
||||
doubaninfo = self.chain.match_doubaninfo(name=mediainfo.title,
|
||||
mtype=mediainfo.type.value,
|
||||
@@ -712,7 +714,7 @@ class PersonMeta(_PluginBase):
|
||||
logger.error(f"获取Jellyfin媒体的所有子媒体项失败:{err}")
|
||||
return {}
|
||||
|
||||
def __get_plex_items(t: str) -> dict:
|
||||
def __get_plex_items() -> dict:
|
||||
"""
|
||||
获得Plex媒体的所有子媒体项
|
||||
"""
|
||||
@@ -721,7 +723,7 @@ class PersonMeta(_PluginBase):
|
||||
plex = Plex().get_plex()
|
||||
items['Items'] = []
|
||||
if parentid:
|
||||
if mtype and 'Season' in t:
|
||||
if mtype and 'Season' in mtype:
|
||||
plexitem = plex.library.fetchItem(ekey=parentid)
|
||||
items['Items'] = []
|
||||
for season in plexitem.seasons():
|
||||
@@ -732,7 +734,7 @@ class PersonMeta(_PluginBase):
|
||||
'Overview': season.summary
|
||||
}
|
||||
items['Items'].append(item)
|
||||
elif mtype and 'Episode' in t:
|
||||
elif mtype and 'Episode' in mtype:
|
||||
plexitem = plex.library.fetchItem(ekey=parentid)
|
||||
items['Items'] = []
|
||||
for episode in plexitem.episodes():
|
||||
@@ -783,7 +785,7 @@ class PersonMeta(_PluginBase):
|
||||
elif server == "jellyfin":
|
||||
return __get_jellyfin_items()
|
||||
else:
|
||||
return __get_plex_items(mtype)
|
||||
return __get_plex_items()
|
||||
|
||||
@staticmethod
|
||||
def set_iteminfo(server: str, itemid: str, iteminfo: dict):
|
||||
|
||||
@@ -467,7 +467,7 @@ class SiteStatistic(_PluginBase):
|
||||
{
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
'src': '/plugin/upload.png'
|
||||
'src': '/plugin_icon/upload.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -537,7 +537,7 @@ class SiteStatistic(_PluginBase):
|
||||
{
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
'src': '/plugin/download.png'
|
||||
'src': '/plugin_icon/download.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -607,7 +607,7 @@ class SiteStatistic(_PluginBase):
|
||||
{
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
'src': '/plugin/seed.png'
|
||||
'src': '/plugin_icon/seed.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -677,7 +677,7 @@ class SiteStatistic(_PluginBase):
|
||||
{
|
||||
'component': 'VImg',
|
||||
'props': {
|
||||
'src': '/plugin/database.png'
|
||||
'src': '/plugin_icon/database.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -56,7 +56,7 @@ class TransferHistory(BaseModel):
|
||||
src: Optional[str] = None
|
||||
# 目的目录
|
||||
dest: Optional[str] = None
|
||||
# 转移模式link/copy/move/softlink
|
||||
# 转移模式
|
||||
mode: Optional[str] = None
|
||||
# 类型:电影、电视剧
|
||||
type: Optional[str] = None
|
||||
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Union, Tuple
|
||||
@@ -97,7 +98,7 @@ class SystemUtils:
|
||||
"""
|
||||
try:
|
||||
# link到当前目录并改名
|
||||
tmp_path = (src.parent / dest.name).with_suffix(".mp")
|
||||
tmp_path = src.parent / (dest.name + ".mp")
|
||||
tmp_path.hardlink_to(src)
|
||||
# 移动到目标目录
|
||||
shutil.move(tmp_path, dest)
|
||||
@@ -118,6 +119,54 @@ class SystemUtils:
|
||||
print(str(err))
|
||||
return -1, str(err)
|
||||
|
||||
@staticmethod
|
||||
def rclone_move(src: Path, dest: Path):
|
||||
"""
|
||||
Rclone移动
|
||||
"""
|
||||
try:
|
||||
retcode = subprocess.run(
|
||||
[
|
||||
'rclone', 'moveto',
|
||||
str(src),
|
||||
f'MP:{dest}'
|
||||
],
|
||||
startupinfo=SystemUtils.__get_hidden_shell()
|
||||
).returncode
|
||||
return retcode, ""
|
||||
except Exception as err:
|
||||
print(str(err))
|
||||
return -1, str(err)
|
||||
|
||||
@staticmethod
|
||||
def rclone_copy(src: Path, dest: Path):
|
||||
"""
|
||||
Rclone复制
|
||||
"""
|
||||
try:
|
||||
retcode = subprocess.run(
|
||||
[
|
||||
'rclone', 'copyto',
|
||||
str(src),
|
||||
f'MP:{dest}'
|
||||
],
|
||||
startupinfo=SystemUtils.__get_hidden_shell()
|
||||
).returncode
|
||||
return retcode, ""
|
||||
except Exception as err:
|
||||
print(str(err))
|
||||
return -1, str(err)
|
||||
|
||||
@staticmethod
|
||||
def __get_hidden_shell():
|
||||
if SystemUtils.is_windows():
|
||||
st = subprocess.STARTUPINFO()
|
||||
st.dwFlags = subprocess.STARTF_USESHOWWINDOW
|
||||
st.wShowWindow = subprocess.SW_HIDE
|
||||
return st
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def list_files(directory: Path, extensions: list, min_filesize: int = 0) -> List[Path]:
|
||||
"""
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
####################################
|
||||
# 基础设置 #
|
||||
####################################
|
||||
# 时区
|
||||
TZ=Asia/Shanghai
|
||||
# 【*】API监听地址
|
||||
HOST=0.0.0.0
|
||||
# 是否调试模式
|
||||
@@ -19,8 +17,6 @@ SUPERUSER=admin
|
||||
SUPERUSER_PASSWORD=password
|
||||
# 【*】API密钥,建议更换复杂字符串
|
||||
API_TOKEN=moviepilot
|
||||
# 网络代理 IP:PORT
|
||||
PROXY_HOST=
|
||||
# TMDB图片地址,无需修改需保留默认值
|
||||
TMDB_IMAGE_DOMAIN=image.tmdb.org
|
||||
# TMDB API地址,无需修改需保留默认值
|
||||
@@ -43,7 +39,7 @@ SCRAP_SOURCE=themoviedb
|
||||
####################################
|
||||
# 媒体库 #
|
||||
####################################
|
||||
# 【*】转移方式 link/copy/move/softlink
|
||||
# 【*】转移方式 link/copy/move/softlink/rclone_copy/rclone_move
|
||||
TRANSFER_TYPE=copy
|
||||
# 【*】媒体库目录,多个目录使用,分隔
|
||||
LIBRARY_PATH=
|
||||
|
||||
@@ -53,4 +53,5 @@ requests_cache~=0.5.2
|
||||
parse~=1.19.0
|
||||
docker~=6.1.3
|
||||
cachetools~=5.3.1
|
||||
fast-bencode==1.1.3
|
||||
fast-bencode~=1.1.3
|
||||
pystray~=0.19.5
|
||||
@@ -1 +1 @@
|
||||
APP_VERSION = 'v1.2.9'
|
||||
APP_VERSION = 'v1.3.1'
|
||||
|
||||
57
windows.spec
57
windows.spec
@@ -1,11 +1,12 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
def collect_pkg_data(package, include_py_files=False, subdir=None):
|
||||
def collect_pkg_data(package: str, include_py_files: bool = False, subdir: str = None):
|
||||
"""
|
||||
Collect all data files from the given package.
|
||||
"""
|
||||
import os
|
||||
from PyInstaller.utils.hooks import get_package_paths, remove_prefix, PY_IGNORE_EXTENSIONS
|
||||
from pathlib import Path
|
||||
from PyInstaller.utils.hooks import get_package_paths, PY_IGNORE_EXTENSIONS
|
||||
from PyInstaller.building.datastruct import TOC
|
||||
|
||||
# Accept only strings as packages.
|
||||
if type(package) is not str:
|
||||
@@ -13,36 +14,36 @@ def collect_pkg_data(package, include_py_files=False, subdir=None):
|
||||
|
||||
pkg_base, pkg_dir = get_package_paths(package)
|
||||
if subdir:
|
||||
pkg_dir = os.path.join(pkg_dir, subdir)
|
||||
pkg_path = Path(pkg_dir) / subdir
|
||||
else:
|
||||
pkg_path = Path(pkg_dir)
|
||||
# Walk through all file in the given package, looking for data files.
|
||||
data_toc = TOC()
|
||||
for dir_path, dir_names, files in os.walk(pkg_dir):
|
||||
for f in files:
|
||||
extension = os.path.splitext(f)[1]
|
||||
if include_py_files or (extension not in PY_IGNORE_EXTENSIONS):
|
||||
source_file = os.path.join(dir_path, f)
|
||||
dest_folder = remove_prefix(dir_path, os.path.dirname(pkg_base) + os.sep)
|
||||
dest_file = os.path.join(dest_folder, f)
|
||||
data_toc.append((dest_file, source_file, 'DATA'))
|
||||
for file in pkg_path.rglob('*'):
|
||||
if file.is_file():
|
||||
extension = file.suffix
|
||||
if not include_py_files and (extension in PY_IGNORE_EXTENSIONS):
|
||||
continue
|
||||
data_toc.append((str(file.relative_to(pkg_base)), str(file), 'DATA'))
|
||||
return data_toc
|
||||
|
||||
|
||||
def collect_local_submodules(package):
|
||||
def collect_local_submodules(package: str):
|
||||
"""
|
||||
Collect all local submodules from the given package.
|
||||
"""
|
||||
import os
|
||||
base_dir = '..'
|
||||
package_dir = os.path.join(base_dir, package.replace('.', os.sep))
|
||||
submodules = []
|
||||
for dir_path, dir_names, files in os.walk(package_dir):
|
||||
for f in files:
|
||||
if f == '__init__.py':
|
||||
submodules.append(f"{package}.{os.path.basename(dir_path)}")
|
||||
elif f.endswith('.py'):
|
||||
submodules.append(f"{package}.{os.path.basename(dir_path)}.{os.path.splitext(f)[0]}")
|
||||
for d in dir_names:
|
||||
submodules.append(f"{package}.{os.path.basename(dir_path)}.{d}")
|
||||
from pathlib import Path
|
||||
package_dir = Path(package.replace('.', os.sep))
|
||||
submodules = [package]
|
||||
# Walk through all file in the given package, looking for data files.
|
||||
for file in package_dir.rglob('*.py'):
|
||||
if file.name == '__init__.py':
|
||||
module = f"{file.parent}".replace(os.sep, '.')
|
||||
else:
|
||||
module = f"{file.parent}.{file.stem}".replace(os.sep, '.')
|
||||
if module not in submodules:
|
||||
submodules.append(module)
|
||||
return submodules
|
||||
|
||||
|
||||
@@ -50,8 +51,7 @@ hiddenimports = [
|
||||
'passlib.handlers.bcrypt',
|
||||
'app.modules',
|
||||
'app.plugins',
|
||||
] + collect_local_submodules('app.modules') \
|
||||
+ collect_local_submodules('app.plugins')
|
||||
] + collect_local_submodules('app.modules') + collect_local_submodules('app.plugins')
|
||||
|
||||
block_cipher = None
|
||||
|
||||
@@ -75,8 +75,9 @@ exe = EXE(
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
a.datas + [('./app.ico', './app.ico', 'DATA')],
|
||||
collect_pkg_data('config'),
|
||||
collect_pkg_data('nginx'),
|
||||
collect_pkg_data('cf_clearance'),
|
||||
collect_pkg_data('database', include_py_files=True),
|
||||
[],
|
||||
@@ -87,7 +88,7 @@ exe = EXE(
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
|
||||
Reference in New Issue
Block a user