windows package
This commit is contained in:
parent
e15f5ab93e
commit
97f16289c9
36
.github/workflows/build-windows.yml
vendored
Normal file
36
.github/workflows/build-windows.yml
vendored
Normal 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
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
name: MoviePilot Builder
|
name: MoviePilot Docker Builder
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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}}
|
|
||||||
|
@ -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
101
windows.spec
Normal 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',
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user