windows package

This commit is contained in:
jxxghp 2023-10-09 12:57:52 +08:00
parent e15f5ab93e
commit 97f16289c9
22 changed files with 217 additions and 47 deletions

36
.github/workflows/build-windows.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: MoviePilot Windows Builder
on:
workflow_dispatch:
push:
branches:
- master
paths:
- version.py
- .github/workflows/build-windows.yml
jobs:
Windows-build:
runs-on: windows-latest
steps:
- 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 numpy==1.23.5 pyparsing==3.0.9 wxpython==4.2.0 pyinstaller==5.7.0
git clone --depth=1 -b main https://github.com/jxxghp/MoviePilot
cd MoviePilot
pip install -r requirements.txt
shell: pwsh
- name: Pyinstaller
run: |
cd MoviePilot
pyinstaller windows.spec
shell: pwsh
- name: Upload Windows File
uses: actions/upload-artifact@v3
with:
name: windows
path: MoviePilot/dist/MoviePilot.exe

View File

@ -1,4 +1,4 @@
name: MoviePilot Builder name: MoviePilot Docker Builder
on: on:
workflow_dispatch: workflow_dispatch:
push: push:

BIN
app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -1,10 +1,13 @@
import os import os
import secrets import secrets
import sys
from pathlib import Path from pathlib import Path
from typing import List from typing import List
from pydantic import BaseSettings from pydantic import BaseSettings
from app.utils.system import SystemUtils
class Settings(BaseSettings): class Settings(BaseSettings):
# 项目名称 # 项目名称
@ -28,7 +31,7 @@ class Settings(BaseSettings):
# 是否开发模式 # 是否开发模式
DEV: bool = False DEV: bool = False
# 配置文件目录 # 配置文件目录
CONFIG_DIR: str = "/config" CONFIG_DIR: str = None
# 超级管理员 # 超级管理员
SUPERUSER: str = "admin" SUPERUSER: str = "admin"
# 超级管理员初始密码 # 超级管理员初始密码
@ -209,7 +212,11 @@ class Settings(BaseSettings):
def CONFIG_PATH(self): def CONFIG_PATH(self):
if self.CONFIG_DIR: if self.CONFIG_DIR:
return Path(self.CONFIG_DIR) return Path(self.CONFIG_DIR)
return self.INNER_CONFIG_PATH elif SystemUtils.is_docker():
return Path("/config")
elif SystemUtils.is_frozen():
return Path(sys.executable).parent / "config"
return self.ROOT_PATH / "config"
@property @property
def TEMP_PATH(self): def TEMP_PATH(self):
@ -274,6 +281,9 @@ class Settings(BaseSettings):
with self.CONFIG_PATH as p: with self.CONFIG_PATH as p:
if not p.exists(): if not p.exists():
p.mkdir(parents=True, exist_ok=True) p.mkdir(parents=True, exist_ok=True)
if SystemUtils.is_frozen():
if not (p / "app.env").exists():
SystemUtils.copy(self.INNER_CONFIG_PATH / "app.env", p / "app.env")
with self.TEMP_PATH as p: with self.TEMP_PATH as p:
if not p.exists(): if not p.exists():
p.mkdir(parents=True, exist_ok=True) p.mkdir(parents=True, exist_ok=True)

View File

@ -39,7 +39,7 @@ def update_db():
更新数据库 更新数据库
""" """
db_location = settings.CONFIG_PATH / 'user.db' db_location = settings.CONFIG_PATH / 'user.db'
script_location = settings.ROOT_PATH / 'alembic' script_location = settings.ROOT_PATH / 'database'
try: try:
alembic_cfg = Config() alembic_cfg = Config()
alembic_cfg.set_main_option('script_location', str(script_location)) alembic_cfg.set_main_option('script_location', str(script_location))

View File

@ -2,12 +2,15 @@ from pyvirtualdisplay import Display
from app.log import logger from app.log import logger
from app.utils.singleton import Singleton from app.utils.singleton import Singleton
from app.utils.system import SystemUtils
class DisplayHelper(metaclass=Singleton): class DisplayHelper(metaclass=Singleton):
_display: Display = None _display: Display = None
def __init__(self): def __init__(self):
if not SystemUtils.is_docker():
return
try: try:
self._display = Display(visible=False, size=(1024, 768)) self._display = Display(visible=False, size=(1024, 768))
self._display.start() self._display.start()

View File

@ -3,6 +3,7 @@ import os
import platform import platform
import re import re
import shutil import shutil
import sys
from pathlib import Path from pathlib import Path
from typing import List, Union, Tuple from typing import List, Union, Tuple
@ -27,20 +28,39 @@ class SystemUtils:
@staticmethod @staticmethod
def is_docker() -> bool: def is_docker() -> bool:
"""
判断是否为Docker环境
"""
return Path("/.dockerenv").exists() return Path("/.dockerenv").exists()
@staticmethod @staticmethod
def is_synology() -> bool: def is_synology() -> bool:
"""
判断是否为群晖系统
"""
if SystemUtils.is_windows(): if SystemUtils.is_windows():
return False return False
return True if "synology" in SystemUtils.execute('uname -a') else False return True if "synology" in SystemUtils.execute('uname -a') else False
@staticmethod @staticmethod
def is_windows() -> bool: def is_windows() -> bool:
"""
判断是否为Windows系统
"""
return True if os.name == "nt" else False return True if os.name == "nt" else False
@staticmethod
def is_frozen() -> bool:
"""
判断是否为冻结的二进制文件
"""
return True if getattr(sys, 'frozen', False) else False
@staticmethod @staticmethod
def is_macos() -> bool: def is_macos() -> bool:
"""
判断是否为MacOS系统
"""
return True if platform.system() == 'Darwin' else False return True if platform.system() == 'Darwin' else False
@staticmethod @staticmethod

View File

@ -3,7 +3,7 @@
####################################################################### #######################################################################
#################################### ####################################
# 系统设置 # # 基础设置 #
#################################### ####################################
# 时区 # 时区
TZ=Asia/Shanghai TZ=Asia/Shanghai
@ -21,6 +21,10 @@ SUPERUSER_PASSWORD=password
API_TOKEN=moviepilot API_TOKEN=moviepilot
# 网络代理 IP:PORT # 网络代理 IP:PORT
PROXY_HOST= PROXY_HOST=
# TMDB图片地址无需修改需保留默认值
TMDB_IMAGE_DOMAIN=image.tmdb.org
# TMDB API地址无需修改需保留默认值
TMDB_API_DOMAIN=api.themoviedb.org
# 大内存模式 # 大内存模式
BIG_MEMORY_MODE=false BIG_MEMORY_MODE=false
@ -35,10 +39,42 @@ SCRAP_METADATA=true
SCRAP_FOLLOW_TMDB=true SCRAP_FOLLOW_TMDB=true
# 刮削来源 themoviedb/douban # 刮削来源 themoviedb/douban
SCRAP_SOURCE=themoviedb SCRAP_SOURCE=themoviedb
# TMDB图片地址无需修改需保留默认值
TMDB_IMAGE_DOMAIN=image.tmdb.org ####################################
# TMDB API地址无需修改需保留默认值 # 媒体库 #
TMDB_API_DOMAIN=api.themoviedb.org ####################################
# 【*】转移方式 link/copy/move/softlink
TRANSFER_TYPE=copy
# 【*】媒体库目录,多个目录使用,分隔
LIBRARY_PATH=
# 电影媒体库目录名,默认电影
LIBRARY_MOVIE_NAME=
# 电视剧媒体库目录名,默认电视剧
LIBRARY_TV_NAME=
# 动漫媒体库目录名,默认电视剧/动漫
LIBRARY_ANIME_NAME=
# 二级分类
LIBRARY_CATEGORY=true
# 电影重命名格式
MOVIE_RENAME_FORMAT={{title}}{% if year %} ({{year}}){% endif %}/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}{{fileExt}}
# 电视剧重命名格式
TV_RENAME_FORMAT={{title}}{% if year %} ({{year}}){% endif %}/Season {{season}}/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}}{% endif %}{{fileExt}}
####################################
# 站点 #
####################################
# 【*】CookieCloud服务器地址默认为公共服务器
COOKIECLOUD_HOST=https://movie-pilot.org/cookiecloud
# 【*】CookieCloud用户KEY
COOKIECLOUD_KEY=
# 【*】CookieCloud端对端加密密码
COOKIECLOUD_PASSWORD=
# 【*】CookieCloud同步间隔分钟
COOKIECLOUD_INTERVAL=1440
# OCR服务器地址
OCR_HOST=https://movie-pilot.org
# 【*】CookieCloud对应的浏览器UA
USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57
#################################### ####################################
# 订阅 & 搜索 # # 订阅 & 搜索 #
@ -149,39 +185,3 @@ JELLYFIN_API_KEY=
PLEX_HOST= PLEX_HOST=
# Plex Token # Plex Token
PLEX_TOKEN= PLEX_TOKEN=
####################################
# 站点 #
####################################
# 【*】CookieCloud服务器地址默认为公共服务器
COOKIECLOUD_HOST=https://movie-pilot.org/cookiecloud
# 【*】CookieCloud用户KEY
COOKIECLOUD_KEY=
# 【*】CookieCloud端对端加密密码
COOKIECLOUD_PASSWORD=
# 【*】CookieCloud同步间隔分钟
COOKIECLOUD_INTERVAL=1440
# OCR服务器地址
OCR_HOST=https://movie-pilot.org
# 【*】CookieCloud对应的浏览器UA
USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57
####################################
# 媒体库 #
####################################
# 【*】转移方式 link/copy/move/softlink
TRANSFER_TYPE=copy
# 【*】媒体库目录,多个目录使用,分隔
LIBRARY_PATH=
# 电影媒体库目录名,默认电影
LIBRARY_MOVIE_NAME=
# 电视剧媒体库目录名,默认电视剧
LIBRARY_TV_NAME=
# 动漫媒体库目录名,默认电视剧/动漫
LIBRARY_ANIME_NAME=
# 二级分类
LIBRARY_CATEGORY=true
# 电影重命名格式
MOVIE_RENAME_FORMAT={{title}}{% if year %} ({{year}}){% endif %}/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}{{fileExt}}
# 电视剧重命名格式
TV_RENAME_FORMAT={{title}}{% if year %} ({{year}}){% endif %}/Season {{season}}/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}}{% endif %}{{fileExt}}

View File

@ -12,7 +12,7 @@ for module in Path(__file__).with_name("models").glob("*.py"):
db_version = input("请输入版本号:") db_version = input("请输入版本号:")
db_location = settings.CONFIG_PATH / 'user.db' db_location = settings.CONFIG_PATH / 'user.db'
script_location = settings.ROOT_PATH / 'alembic' script_location = settings.ROOT_PATH / 'database'
alembic_cfg = AlembicConfig() alembic_cfg = AlembicConfig()
alembic_cfg.set_main_option('script_location', str(script_location)) alembic_cfg.set_main_option('script_location', str(script_location))
alembic_cfg.set_main_option('sqlalchemy.url', f"sqlite:///{db_location}") alembic_cfg.set_main_option('sqlalchemy.url', f"sqlite:///{db_location}")

101
windows.spec Normal file
View File

@ -0,0 +1,101 @@
# -*- mode: python ; coding: utf-8 -*-
def collect_pkg_data(package, include_py_files=False, subdir=None):
"""
Collect all data files from the given package.
"""
import os
from PyInstaller.utils.hooks import get_package_paths, remove_prefix, PY_IGNORE_EXTENSIONS
# Accept only strings as packages.
if type(package) is not str:
raise ValueError
pkg_base, pkg_dir = get_package_paths(package)
if subdir:
pkg_dir = os.path.join(pkg_dir, subdir)
# 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'))
return data_toc
def collect_local_submodules(package):
"""
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}")
return submodules
hiddenimports = [
'passlib.handlers.bcrypt',
'app.modules',
'app.plugins',
] + collect_local_submodules('app.modules') \
+ collect_local_submodules('app.plugins')
a = Analysis(
['app/main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='MoviePilot',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon="app.ico"
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
collect_pkg_data('config'),
collect_pkg_data('cf_clearance'),
collect_pkg_data('database', include_py_files=True),
strip=False,
upx=True,
upx_exclude=[],
name='MoviePilot',
)