├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── icons
├── Identifier.png
├── Infuse.png
├── bangumi.jpg
├── danmu.png
├── discord.png
└── logo.jpg
├── package.json
├── package.v2.json
├── plugins.v2
├── IdentifierHelper
│ └── __init__.py
├── bangumi
│ ├── README.md
│ ├── __init__.py
│ ├── bangumi_db.py
│ └── bangumi_db_oper.py
├── bdremuxer
│ ├── README.md
│ ├── __init__.py
│ └── requirements.txt
├── danmu
│ ├── README.md
│ ├── __init__.py
│ ├── danmu_generator.py
│ ├── dist
│ │ ├── assets
│ │ │ ├── __federation_expose_Config-BdvrOUFg.js
│ │ │ ├── __federation_expose_Config-mmMv5D16.css
│ │ │ ├── __federation_expose_Page-CyDIESC3.css
│ │ │ ├── __federation_expose_Page-DF3RUHu3.js
│ │ │ ├── __federation_fn_import-JrT3xvdd.js
│ │ │ ├── __federation_shared_vuetify
│ │ │ │ └── styles-CZ3C7oSZ.css
│ │ │ ├── _plugin-vue_export-helper-pcqpp-6-.js
│ │ │ ├── date-BMtbN87Q.js
│ │ │ ├── index-DNuvSYik.js
│ │ │ ├── index-cr18jDRr.css
│ │ │ └── remoteEntry.js
│ │ └── index.html
│ ├── doc
│ │ ├── api.png
│ │ ├── api2.png
│ │ └── page.png
│ ├── index.html
│ ├── package.json
│ ├── src
│ │ ├── App.vue
│ │ ├── components
│ │ │ ├── Config.vue
│ │ │ └── Page.vue
│ │ └── main.js
│ ├── vite.config.js
│ └── yarn.lock
├── discord
│ ├── README.md
│ ├── __init__.py
│ ├── cogs
│ │ └── moviepilot_cog.py
│ ├── discord_bot.py
│ ├── gpt.py
│ ├── requirements.txt
│ └── tokenes.py
└── test
│ └── __init__.py
└── plugins
├── bangumi
├── README.md
├── __init__.py
├── bangumi_db.py
└── bangumi_db_oper.py
├── bdremuxer
├── README.md
├── __init__.py
└── requirements.txt
├── danmu
├── README.md
├── __init__.py
└── danmu_generator.py
├── discord
├── README.md
├── __init__.py
├── cogs
│ └── moviepilot_cog.py
├── discord_bot.py
├── gpt.py
├── requirements.txt
└── tokenes.py
├── rmcdata
├── README.md
└── __init__.py
└── test
└── __init__.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | test.py
6 | update_version.py
7 | bdextractor.py
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | build/
14 | develop-eggs/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 | cover/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | .pybuilder/
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | # For a library or package, you might want to ignore these files since the code is
88 | # intended to run in multiple environments; otherwise, check them in:
89 | # .python-version
90 |
91 | # pipenv
92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
95 | # install all needed dependencies.
96 | #Pipfile.lock
97 |
98 | # poetry
99 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
100 | # This is especially recommended for binary packages to ensure reproducibility, and is more
101 | # commonly ignored for libraries.
102 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
103 | #poetry.lock
104 |
105 | # pdm
106 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
107 | #pdm.lock
108 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
109 | # in version control.
110 | # https://pdm.fming.dev/#use-with-ide
111 | .pdm.toml
112 |
113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
114 | __pypackages__/
115 |
116 | # Celery stuff
117 | celerybeat-schedule
118 | celerybeat.pid
119 |
120 | # SageMath parsed files
121 | *.sage.py
122 |
123 | # Environments
124 | .env
125 | .venv
126 | env/
127 | venv/
128 | ENV/
129 | env.bak/
130 | venv.bak/
131 |
132 | # Spyder project settings
133 | .spyderproject
134 | .spyproject
135 |
136 | # Rope project settings
137 | .ropeproject
138 |
139 | # mkdocs documentation
140 | /site
141 |
142 | # mypy
143 | .mypy_cache/
144 | .dmypy.json
145 | dmypy.json
146 |
147 | # Pyre type checker
148 | .pyre/
149 |
150 | # pytype static type analyzer
151 | .pytype/
152 |
153 | # Cython debug symbols
154 | cython_debug/
155 |
156 | # PyCharm
157 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
159 | # and can be added to the global gitignore or merged into this file. For a more nuclear
160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
161 | #.idea/
162 | test.test
163 | json.py
164 |
165 | ignore/
166 | test_danmu.py
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MoviePilot-Plugins
2 | MoviePilot官方插件市场:https://raw.githubusercontent.com/jxxghp/MoviePilot-Plugins/main/
3 |
4 | ### 使用
5 |
6 | 添加 https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/ 到 PLUGIN_MARKET
7 |
8 | 请别使用版本0.x开头的插件,还在开发中...
9 |
10 | ---
11 |
12 | ### [Discord 消息推送插件](plugins/discord/README.md)
13 |
14 | 通过discord webhook推送MoviePilot的消息到discord频道中。
15 | 使用discord bot发送命令,加入OpenAI token后,可以使用GPT-3.5进行对话。
16 |
17 |
18 |
19 | ### [Infuse nfo 简介修复](plugins/rmcdata/README.md)
20 |
21 | 通过去除nfo中的CDATA标签修复infuse无法读取nfo内容导致媒体简介空白的问题。
22 |
23 |
24 |
25 | ### [Bangumi 同步](plugins/bangumi/README.md)
26 |
27 | 同步MoviePilot的观看记录到 Bangumi 收藏中。
28 | 自动订阅/下载 Bangumi 收藏的"想看"条目。
29 | 更改本地动漫 NFO文件评分为Bangumi评分。
30 | 更改订阅页面的评分为Bangumi评分。
31 |
32 | 需要申请Bangumi API Key,申请地址:https://next.bgm.tv/demo/access-token
33 |
34 |
35 |
36 | ### [弹幕刮削](plugins.v2/danmu/README.md)
37 |
38 | 自动刮削新入库文件,可以全局文件刮削。
39 | 使用弹弹Play弹幕库刮削弹幕到本地转为ass文件。
40 | .danmu为刮削出来的纯弹幕,.withDanmu为原生字幕与弹幕合并后的文件。方便不支持双字幕的播放器使用。
41 |
--------------------------------------------------------------------------------
/icons/Identifier.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/b129f043eddcec0f9028a0eea86f0ef5db4d2cc1/icons/Identifier.png
--------------------------------------------------------------------------------
/icons/Infuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/b129f043eddcec0f9028a0eea86f0ef5db4d2cc1/icons/Infuse.png
--------------------------------------------------------------------------------
/icons/bangumi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/b129f043eddcec0f9028a0eea86f0ef5db4d2cc1/icons/bangumi.jpg
--------------------------------------------------------------------------------
/icons/danmu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/b129f043eddcec0f9028a0eea86f0ef5db4d2cc1/icons/danmu.png
--------------------------------------------------------------------------------
/icons/discord.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/b129f043eddcec0f9028a0eea86f0ef5db4d2cc1/icons/discord.png
--------------------------------------------------------------------------------
/icons/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/b129f043eddcec0f9028a0eea86f0ef5db4d2cc1/icons/logo.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "Discord": {
3 | "name": "Discord 消息推送",
4 | "description": "添加discord消息推送",
5 | "version": "1.5.8",
6 | "icon": "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/discord.png",
7 | "color": "#3B5E8E",
8 | "author": "hankun",
9 | "level": 1,
10 | "history":{
11 | "v1.5.8": "回退到老版本,针对v2有另外的版本",
12 | "v1.5.0": "增加对v2的支持"
13 | },
14 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyNe4GKw15qXHWAYBhHDlUDxM7MfrdmstwRoTtn+PNK9uvpsZip1/R6azzJi4hHLsEOvVLyfjjaEVDyZrJVGTxC+Fj+6JFrq4LnwB0CHlbXAN+UkMk3uD/srRx3eOVSZuqoCDplsX98DUim5Vx/xzyoRSwbWqMJvYsIawaKO9yZyA7ErjmyNOk1jduy1FYLISzyICtz0ubYsohzUZxbcZgqb+qBeECv0SofSMM+luvIHiqTPPMRKmjD6WufGerwm7r9r26coG5CvYgOX+jOJITIpAqXkAqqXNNoVCuTRazxF/OJFjbTsJLO1WnyIr6qB6oQW/7XgLk0Ot5M1Jx2JW/QIDAQAB"
15 |
16 | },
17 | "RmCdata": {
18 | "name": "Infuse nfo 简介修复",
19 | "description": "去除cdata标签以修复infuse简介显示",
20 | "version": "1.2.6",
21 | "icon": "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/Infuse.png",
22 | "color": "#32699D",
23 | "author": "hankun",
24 | "level": 1,
25 | "v2": true
26 | },
27 | "Bangumi": {
28 | "name": "Bangumi 同步",
29 | "description": "动漫资源引用Bangumi评分,同步订阅/库存番剧到Bangumi",
30 | "version": "1.0.15",
31 | "icon": "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/bangumi.jpg",
32 | "color": "#5378A4",
33 | "author": "hankun",
34 | "level": 1
35 | },
36 | "BDRemuxer": {
37 | "name": "BD Remuxer",
38 | "description": "使用ffmpeg自动提取BDMV文件夹中的视频流和音频流,合并为MKV文件。低性能设备不推荐使用",
39 | "version": "1.1.0",
40 | "icon": "",
41 | "color": "#5378A4",
42 | "author": "hankun",
43 | "level": 1
44 | },
45 | "Danmu": {
46 | "name": "弹幕刮削",
47 | "description": "使用弹弹play平台生成弹幕的字幕文件,实现弹幕播放。",
48 | "version": "1.1.2",
49 | "icon": "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/danmu.png",
50 | "color": "#3B5E8E",
51 | "author": "hankun",
52 | "level": 1,
53 | "history": {
54 | "v1.1.2": "修复nfo文件不存在时导致错误。",
55 | "v1.1.1": "修复错误导致弹幕生成失败,增加log信息显示匹配弹幕数量。",
56 | "v1.1.0": "优化弹幕生成算法,让生成的弹幕分布更均匀。",
57 | "v1.0.9": "移除定期全局刮削,如果有需要请使用 设置->服务 手动启动全局刮削。刮削过于频繁会导致IP被封。",
58 | "v1.0.8": "修复弹幕时长设为10以上时无法生成弹幕",
59 | "v1.0.7": "修复cron表达式修改后无法应用",
60 | "v1.0.6": "提取内嵌字幕时自动识别并选择中文字幕(如果可用的话",
61 | "v1.0.5": "添加在hash匹配失败后,通过nfo标题匹配弹幕池",
62 | "v1.0.4": "修复我傻逼打错变量名",
63 | "v1.0.3": "修复合并字幕不是utf8无法读取的问题以及线程排序会漏任务的问题。",
64 | "v1.0.2": "修复合并字幕命名错误。",
65 | "v1.0.1": "修复ffmpeg有时候解码错误。",
66 | "v1.0.0": "基本功能实现,暂时不支持srt字幕。"
67 | }
68 | },
69 | "Test":{
70 | "name": "测试",
71 | "description": "测试",
72 | "version": "1.0.0",
73 | "icon": "",
74 | "color": "#3B5E8E",
75 | "author": "hankun",
76 | "level": 99,
77 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyNe4GKw15qXHWAYBhHDlUDxM7MfrdmstwRoTtn+PNK9uvpsZip1/R6azzJi4hHLsEOvVLyfjjaEVDyZrJVGTxC+Fj+6JFrq4LnwB0CHlbXAN+UkMk3uD/srRx3eOVSZuqoCDplsX98DUim5Vx/xzyoRSwbWqMJvYsIawaKO9yZyA7ErjmyNOk1jduy1FYLISzyICtz0ubYsohzUZxbcZgqb+qBeECv0SofSMM+luvIHiqTPPMRKmjD6WufGerwm7r9r26coG5CvYgOX+jOJITIpAqXkAqqXNNoVCuTRazxF/OJFjbTsJLO1WnyIr6qB6oQW/7XgLk0Ot5M1Jx2JW/QIDAQAB"
78 | }
79 | }
--------------------------------------------------------------------------------
/package.v2.json:
--------------------------------------------------------------------------------
1 | {
2 | "Discord": {
3 | "name": "Discord 消息推送",
4 | "description": "添加discord消息推送",
5 | "version": "1.5.9",
6 | "icon": "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/discord.png",
7 | "color": "#3B5E8E",
8 | "author": "hankun",
9 | "level": 1,
10 | "history":{
11 | "v1.5.8": "增加对v2的支持,OpenAI暂时无法使用"
12 | },
13 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyNe4GKw15qXHWAYBhHDlUDxM7MfrdmstwRoTtn+PNK9uvpsZip1/R6azzJi4hHLsEOvVLyfjjaEVDyZrJVGTxC+Fj+6JFrq4LnwB0CHlbXAN+UkMk3uD/srRx3eOVSZuqoCDplsX98DUim5Vx/xzyoRSwbWqMJvYsIawaKO9yZyA7ErjmyNOk1jduy1FYLISzyICtz0ubYsohzUZxbcZgqb+qBeECv0SofSMM+luvIHiqTPPMRKmjD6WufGerwm7r9r26coG5CvYgOX+jOJITIpAqXkAqqXNNoVCuTRazxF/OJFjbTsJLO1WnyIr6qB6oQW/7XgLk0Ot5M1Jx2JW/QIDAQAB"
14 |
15 | },
16 | "Bangumi": {
17 | "name": "Bangumi 同步",
18 | "description": "动漫资源引用Bangumi评分,同步订阅/库存番剧到Bangumi",
19 | "version": "1.0.15",
20 | "icon": "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/bangumi.jpg",
21 | "color": "#5378A4",
22 | "author": "hankun",
23 | "level": 99,
24 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyNe4GKw15qXHWAYBhHDlUDxM7MfrdmstwRoTtn+PNK9uvpsZip1/R6azzJi4hHLsEOvVLyfjjaEVDyZrJVGTxC+Fj+6JFrq4LnwB0CHlbXAN+UkMk3uD/srRx3eOVSZuqoCDplsX98DUim5Vx/xzyoRSwbWqMJvYsIawaKO9yZyA7ErjmyNOk1jduy1FYLISzyICtz0ubYsohzUZxbcZgqb+qBeECv0SofSMM+luvIHiqTPPMRKmjD6WufGerwm7r9r26coG5CvYgOX+jOJITIpAqXkAqqXNNoVCuTRazxF/OJFjbTsJLO1WnyIr6qB6oQW/7XgLk0Ot5M1Jx2JW/QIDAQAB"
25 |
26 | },
27 | "BDRemuxer": {
28 | "name": "BD Remuxer",
29 | "description": "使用ffmpeg自动提取BDMV文件夹中的视频流和音频流,合并为MKV文件。低性能设备不推荐使用",
30 | "version": "1.1.0",
31 | "icon": "",
32 | "color": "#5378A4",
33 | "author": "hankun",
34 | "level": 1
35 | },
36 | "Danmu": {
37 | "name": "弹幕刮削",
38 | "description": "使用弹弹play平台生成弹幕的字幕文件,实现弹幕播放。",
39 | "version": "1.4.0",
40 | "icon": "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/danmu.png",
41 | "color": "#3B5E8E",
42 | "author": "hankun",
43 | "level": 1,
44 | "history": {
45 | "v1.4.0": "修复文件路径无法访问的问题,新增无弹幕或者弹幕数量过少时定时重试",
46 | "v1.3.0": "新增联邦组件支持,增加UI手动刮削,需求MP v2.4.5+",
47 | "v1.2.0": "针对新番(90天内发布的媒体资源)使用较短时间缓存以获取最新弹幕数据",
48 | "v1.1.15": "增加根据路径刮削弹幕功能。增加单文件路径刮削。能力有限无法在同一个页面实现按钮功能……",
49 | "v1.1.13": "先检查当前目录下有没有.id结尾的文件 如果有,则获取文件名作为弹幕ID(如190270001 则使用19027.id)。如果需要请访问 https://dandanapi.hankun.online/docs 尝试获取epsoide id。",
50 | "v1.1.11": "修复弹幕获取过滤错误",
51 | "v1.1.10": "使用TMDB ID作为预备匹配方案,当无法匹配文件hash时尝试使用TMDB ID。",
52 | "v1.1.8": "增加了中转服务器,恢复了对弹弹的访问以及缓存。修复透明度问题。增加了弹幕过滤选项。"
53 | }
54 | },
55 | "Test":{
56 | "name": "测试",
57 | "description": "测试",
58 | "version": "1.3.9",
59 | "icon": "",
60 | "color": "#3B5E8E",
61 | "author": "hankun",
62 | "level": 99,
63 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyNe4GKw15qXHWAYBhHDlUDxM7MfrdmstwRoTtn+PNK9uvpsZip1/R6azzJi4hHLsEOvVLyfjjaEVDyZrJVGTxC+Fj+6JFrq4LnwB0CHlbXAN+UkMk3uD/srRx3eOVSZuqoCDplsX98DUim5Vx/xzyoRSwbWqMJvYsIawaKO9yZyA7ErjmyNOk1jduy1FYLISzyICtz0ubYsohzUZxbcZgqb+qBeECv0SofSMM+luvIHiqTPPMRKmjD6WufGerwm7r9r26coG5CvYgOX+jOJITIpAqXkAqqXNNoVCuTRazxF/OJFjbTsJLO1WnyIr6qB6oQW/7XgLk0Ot5M1Jx2JW/QIDAQAB"
64 | },
65 | "IdentifierHelper":{
66 | "name": "自定义识别词助手",
67 | "description": "帮助管理自定义识别词",
68 | "version": "1.0.0",
69 | "icon": "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/Identifier.png",
70 | "color": "#3B5E8E",
71 | "author": "hankun",
72 | "level": 99,
73 | "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyNe4GKw15qXHWAYBhHDlUDxM7MfrdmstwRoTtn+PNK9uvpsZip1/R6azzJi4hHLsEOvVLyfjjaEVDyZrJVGTxC+Fj+6JFrq4LnwB0CHlbXAN+UkMk3uD/srRx3eOVSZuqoCDplsX98DUim5Vx/xzyoRSwbWqMJvYsIawaKO9yZyA7ErjmyNOk1jduy1FYLISzyICtz0ubYsohzUZxbcZgqb+qBeECv0SofSMM+luvIHiqTPPMRKmjD6WufGerwm7r9r26coG5CvYgOX+jOJITIpAqXkAqqXNNoVCuTRazxF/OJFjbTsJLO1WnyIr6qB6oQW/7XgLk0Ot5M1Jx2JW/QIDAQAB"
74 | }
75 | }
--------------------------------------------------------------------------------
/plugins.v2/IdentifierHelper/__init__.py:
--------------------------------------------------------------------------------
1 | from typing import List, Tuple, Dict, Any
2 |
3 | import datetime, re
4 | from apscheduler.schedulers.background import BackgroundScheduler
5 | from apscheduler.triggers.cron import CronTrigger
6 |
7 | from app.core.config import settings
8 | from app.utils.http import RequestUtils
9 | from app.log import logger
10 |
11 | from app.plugins import _PluginBase
12 | from ...db.systemconfig_oper import SystemConfigOper
13 | from ...schemas.types import SystemConfigKey
14 | from app.utils.common import retry
15 |
16 |
17 | class IdentifierHelper(_PluginBase):
18 | # 插件名称
19 | plugin_name = "自定义识别词助手"
20 | # 插件描述
21 | plugin_desc = "帮助管理自定义识别词"
22 | # 插件图标
23 | plugin_icon = "https://raw.githubusercontent.com/HankunYu/MoviePilot-Plugins/main/icons/Identifier.png"
24 | # 插件版本
25 | plugin_version = "1.0.0"
26 | # 插件作者
27 | plugin_author = "hankun"
28 | # 作者主页
29 | author_url = "https://github.com/hankunyu"
30 | # 插件配置项ID前缀
31 | plugin_config_prefix = "identifierHelper_"
32 | # 加载顺序
33 | plugin_order = 10
34 | # 可使用的用户级别
35 | auth_level = 1
36 | # 条目的类型
37 | _type = ['屏蔽', '替换', '集偏移', '替换和集偏移']
38 | _entries = []
39 | _catgroies = []
40 |
41 | def init_plugin(self, config: dict = None):
42 | if not config:
43 | return
44 |
45 | # config操作
46 | self.__update_config()
47 |
48 |
49 |
50 | def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
51 | return [
52 | {
53 | "component": "VRow",
54 | "content": [
55 | {
56 | "component": "VCol",
57 | "props": {
58 | "cols": 12,
59 | },
60 | "content": [
61 | {
62 | "component": "VAlert",
63 | "props": {
64 | "type": "warning",
65 | "variant": "tonal",
66 | "text": "注意备份"
67 | }
68 | }
69 | ]
70 | }
71 | ]
72 | },
73 | "component": "VTabs",
74 | "props": {
75 | "model": "_tabs",
76 | "height": 72,
77 | "fixed-tabs": True,
78 | "style": {
79 | "margin-top": "8px",
80 | "margin-bottom": "10px",
81 | }
82 | },
83 | ], {
84 |
85 | }
86 |
87 | def __update_config(self):
88 | self.update_config({
89 | })
90 |
91 | def stop_service(self):
92 | pass
93 |
94 | def get_page(self) -> List[dict]:
95 | pass
96 |
97 | def get_state(self) -> bool:
98 | return False
99 |
100 | @staticmethod
101 | def get_command() -> List[Dict[str, Any]]:
102 | pass
103 |
104 | def get_api(self) -> List[Dict[str, Any]]:
105 | pass
106 |
107 | def add_entry(self, catgory, entry_type, content):
108 | # 创建一个字典表示条目
109 | entry = {
110 | "Catgory": catgory,
111 | "Type": entry_type,
112 | "Content": content
113 | }
114 | # 将条目添加到列表中
115 | self._entries.append(entry)
116 |
117 | def extract_identifier(self):
118 | text = SystemConfigOper.get(SystemConfigKey.CustomIdentifiers)
119 |
120 | # 初始化结果
121 | self._categories = []
122 | current_category = '未分类'
123 | self._categories.append(current_category)
124 |
125 | # 按行分割文本
126 | lines = text.strip().split('\n')
127 |
128 | # 遍历每一行
129 | for line in lines:
130 | line = line.strip() # 去除行首尾空白
131 |
132 | # 匹配分类开始
133 | category_start_match = re.match(r'^##\s*Catgory\s*(.*)$', line)
134 | if category_start_match:
135 | current_category = category_start_match.group(1).strip()
136 | self._categories.append(current_category)
137 | continue
138 |
139 | # 匹配分类结束
140 | if line.startswith('## Catgory End'):
141 | current_category = '未分类' # 结束当前分类
142 | continue
143 |
144 | # 开始匹配条目
145 | # 匹配屏蔽词
146 | if re.match(r'^\S+$', line):
147 | self.add_entry(current_category, '屏蔽', line)
148 | continue
149 |
150 | # 匹配结合替换和集偏移量的格式
151 | complex_match = re.match(r'^(.*?)\s*=>\s*(.*)\s*&&\s*(.*?)\s*<>\s*(.*?)\s*>>\s*(.*)$', line)
152 | if complex_match:
153 | self.add_entry(current_category, '替换和集偏移', line)
154 | continue
155 |
156 | # 匹配被替换词 => 替换词
157 | simple_replace_match = re.match(r'^(.*?)\s*=>\s*(.*)$', line)
158 | if simple_replace_match:
159 | self.add_entry(current_category, '替换', line)
160 | continue
161 |
162 | # 匹配前定位词 <> 后定位词 >> 集偏移量(EP)
163 | offset_match = re.match(r'^(.*?)\s*<>\s*(.*?)\s*>>\s*(.*)$', line)
164 | if offset_match:
165 | self.add_entry(current_category, '集偏移', line)
166 | continue
167 |
168 |
169 |
170 |
--------------------------------------------------------------------------------
/plugins.v2/bangumi/README.md:
--------------------------------------------------------------------------------
1 | ### 配置
2 |
3 | 需要 Bangumi API Key,申请地址:https://next.bgm.tv/demo/access-token
4 |
5 | 只会同步选择的媒体服务器中的媒体内容。
6 | 媒体库路径只用于更改本地NFO文件的评分。
7 |
8 | **自定义识别词**
9 | 只会使用识别词替换,请在前一个使用Bangumi标题,后一个使用TMDB标题。如有季数,请用中文添加。
10 | 如:
11 | ```
12 | 银魂 => 银魂 第二季
13 | 银魂 => 银魂 第三季
14 | 银魂 => 银魂 第四季
15 | 银魂' => 银魂 第五季
16 | 银魂'延长战 => 银魂 第六季
17 | 银魂° => 银魂 第七季
18 | ```
19 | ---
20 | ### 更新日志
21 |
22 | #### 1.0.9
23 | - 更新评分增加了对整个番剧评分的更新。
24 |
25 | #### 1.0.8
26 | - 修复MP更新后,脚本无法启动的问题。
27 | - 修复插件页面空白问题。
28 |
29 | #### 1.0.7
30 | - 修复新增想看条目缓慢,要等订阅/下载后才增加
31 |
32 | #### 1.0.6
33 | - 修复缓存时自定义词替换失效的问题
34 | - 提高识别的准确度
35 |
36 | #### 1.0.5
37 |
38 | - 修复了一些bug
39 | - 增加季数识别,当添加想看条目到下载时,根据自定义识别词识别季数并下载
40 |
41 | #### 1.0.4
42 |
43 | - 新增自定义识别词替换,用于识别 Bangumi 标题与 TMDB 标题的互相识别。请在自定义识别词页面自行增加 'Bangumi上的标题' => 'TMDB上的标题'。(如果发现重复下载想看上的项目,请添加识别词)
44 | - 新增脚本版本识别,防止多个脚本版本不一致卡死进程。目前是因为MP不会自动更新除了主脚本外的附带脚本。相信很快就会修复。
45 | - 优化缓存更新逻辑。
46 |
--------------------------------------------------------------------------------
/plugins.v2/bangumi/bangumi_db.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from sqlalchemy import Column, Integer, String, Sequence
4 | from sqlalchemy.orm import Session
5 |
6 | from app.db import db_query
7 | from app.db import Base, db_update
8 |
9 |
10 | class BangumiInfo(Base):
11 | plugin_version = "1.0.9"
12 | """
13 | Bangumi 数据表
14 | """
15 | id = Column(Integer, Sequence('id'), primary_key=True, index=True)
16 | # 标题
17 | title = Column(String, index=True)
18 | # 原标题
19 | original_title = Column(String)
20 | # bangumi 项目 ID
21 | subject_id = Column(String, index=True)
22 | # 评分
23 | rating = Column(String)
24 | # 收藏状态 1:想看 2:看过 3:在看 4:搁置 5:抛弃
25 | status = Column(String, index=True)
26 | # 是否同步过
27 | synced = Column(String, index=True)
28 | # poster
29 | poster = Column(String)
30 |
31 | @staticmethod
32 | @db_query
33 | def get_by_title(db: Session, title: str):
34 | return db.query(BangumiInfo).filter(BangumiInfo.title == title).first()
35 |
36 | @staticmethod
37 | @db_update
38 | def empty(db: Session):
39 | db.query(BangumiInfo).delete()
40 |
41 | @staticmethod
42 | @db_query
43 | def exists_by_title(db: Session, title: str):
44 | return db.query(BangumiInfo).filter(BangumiInfo.title == title).first()
45 |
46 | @staticmethod
47 | @db_query
48 | def get_amount(db: Session):
49 | return db.query(BangumiInfo).count()
50 |
51 | @staticmethod
52 | @db_query
53 | def get_all(db: Session):
54 | return db.query(BangumiInfo).all()
55 |
56 | @staticmethod
57 | @db_query
58 | def get_all_bangumi(db: Session):
59 | return db.query(BangumiInfo).filter(BangumiInfo.subject_id != None).all()
60 |
61 | @staticmethod
62 | @db_update
63 | def update_info(db: Session, title: str, original_title: str ,subject_id: str, rating: str, status: str, synced: bool, poster: str):
64 | db.query(BangumiInfo).filter(BangumiInfo.title == title).update({
65 | "original_title": original_title,
66 | "subject_id": subject_id,
67 | "rating": rating,
68 | "status": status,
69 | "synced": synced,
70 | "poster": poster,
71 | })
72 |
73 | @staticmethod
74 | @db_query
75 | def get_wish(db: Session):
76 | return db.query(BangumiInfo).filter(BangumiInfo.status == 1).all()
77 |
78 | @staticmethod
79 | @db_query
80 | def get_watched(db: Session):
81 | return db.query(BangumiInfo).filter(BangumiInfo.status == 2).all()
82 |
83 | @staticmethod
84 | @db_query
85 | def get_watching(db: Session):
86 | return db.query(BangumiInfo).filter(BangumiInfo.status == 3).all()
87 |
88 | @staticmethod
89 | @db_query
90 | def get_dropped(db: Session):
91 | return db.query(BangumiInfo).filter(BangumiInfo.status == 4).all()
92 |
93 | @staticmethod
94 | @db_query
95 | def exists_by_subject_id(db: Session, subject_id: str):
96 | return db.query(BangumiInfo).filter(BangumiInfo.subject_id == subject_id).first()
--------------------------------------------------------------------------------
/plugins.v2/bangumi/bangumi_db_oper.py:
--------------------------------------------------------------------------------
1 | import json
2 | from typing import Optional
3 |
4 | from sqlalchemy.orm import Session
5 |
6 | from app.db import DbOper
7 | from app.plugins.bangumi.bangumi_db import BangumiInfo
8 |
9 |
10 | class BangumiOper(DbOper):
11 |
12 | plugin_version = "1.0.9"
13 | """
14 | 媒体服务器数据管理
15 | """
16 |
17 | def __init__(self, db: Session = None):
18 | super().__init__(db)
19 |
20 | def add(self, **kwargs) -> bool:
21 | """
22 | 新增媒体服务器数据
23 | """
24 | item = BangumiInfo(**kwargs)
25 | if not item.get_by_title(self._db, kwargs.get("title")):
26 | item.create(self._db)
27 | return True
28 | return False
29 |
30 | def empty(self):
31 | """
32 | 清空 Bangumi 数据
33 | """
34 | BangumiInfo.empty(self._db)
35 |
36 | def exists(self, **kwargs) -> Optional[BangumiInfo]:
37 | """
38 | 判断媒体服务器数据是否存在
39 | """
40 | if kwargs.get("title"):
41 | item = BangumiInfo.exists_by_title(self._db, title=kwargs.get("title"))
42 | else:
43 | return None
44 | if not item:
45 | return None
46 | return item
47 |
48 | def get_subject_id(self, **kwargs) -> Optional[str]:
49 | """
50 | 获取 Bangumi ID
51 | """
52 | item = self.exists(**kwargs)
53 | if not item:
54 | return None
55 | return str(item.subject_id)
56 |
57 | def get_amount(self) -> int:
58 | """
59 | 获取 Bangumi 数据量
60 | """
61 | return BangumiInfo.get_amount(self._db)
62 |
63 | def get_all(self) -> list:
64 | """
65 | 获取所有 Bangumi 数据
66 | """
67 | return BangumiInfo.get_all(self._db)
68 |
69 | def update_info(self, **kwargs) -> bool:
70 | """
71 | 更新 Bangumi 数据
72 | """
73 | item = self.exists(title = kwargs.get("title"))
74 | if not item:
75 | return False
76 | item.update_info(self._db, **kwargs)
77 | return True
78 |
79 | def get_original_title(self, **kwargs) -> Optional[str]:
80 | """
81 | 获取原标题
82 | """
83 | item = self.exists(**kwargs)
84 | if not item:
85 | return None
86 | return str(item.original_title)
87 |
88 | def get_all_bangumi(self) -> list:
89 | """
90 | 获取所有 Bangumi 数据
91 | """
92 | return BangumiInfo.get_all_bangumi(self._db)
93 |
94 | def get_wish(self) -> list:
95 | """
96 | 获取所有 Bangumi 上 想看 的条目
97 | """
98 | return BangumiInfo.get_wish(self._db)
99 |
100 | def get_watched(self) -> list:
101 | """
102 | 获取所有 Bangumi 上 看过 的条目
103 | """
104 | return BangumiInfo.get_watched(self._db)
105 |
106 | def get_watching(self) -> list:
107 | """
108 | 获取所有 Bangumi 上 在看 的条目
109 | """
110 | return BangumiInfo.get_watching(self._db)
111 |
112 | def get_synced(self) -> list:
113 | """
114 | 获取所有 Bangumi 上 已同步 的条目
115 | """
116 | return BangumiInfo.get_synced(self._db)
117 |
118 | def get_exist_by_subject_id(self, subject_id: str) -> bool:
119 | """
120 | 判断是否存在指定 Bangumi ID 的条目
121 | """
122 | item = BangumiInfo.exists_by_subject_id(self._db, subject_id)
123 | if not item:
124 | return False
125 | return True
126 |
--------------------------------------------------------------------------------
/plugins.v2/bdremuxer/README.md:
--------------------------------------------------------------------------------
1 | ### 注意
2 |
3 | 此插件吃性能,不建议在低性能设备上使用。
--------------------------------------------------------------------------------
/plugins.v2/bdremuxer/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | # MoviePilot library
3 | from app.log import logger
4 | from app.plugins import _PluginBase
5 | from app.core.event import eventmanager
6 | from app.schemas.types import EventType
7 | from app.utils.system import SystemUtils
8 | from typing import Any, List, Dict, Tuple
9 | import subprocess
10 | import os
11 | import shutil
12 | import threading
13 | try:
14 | from pyparsebluray import mpls
15 | except:
16 | subprocess.run(["pip3", "install", "pyparsebluray"])
17 | subprocess.run(["pip3", "install", "ffmpeg-python"])
18 |
19 | try:
20 | import ffmpeg
21 | except:
22 | logger.error("requirements 安装失败")
23 |
24 | class BDRemuxer(_PluginBase):
25 | # 插件名称
26 | plugin_name = "BDMV Remuxer"
27 | # 插件描述
28 | plugin_desc = "自动提取BDMV文件夹中的视频流和音频流,合并为MKV文件"
29 | # 插件图标
30 | plugin_icon = ""
31 | # 主题色
32 | plugin_color = "#3B5E8E"
33 | # 插件版本
34 | plugin_version = "1.1.0"
35 | # 插件作者
36 | plugin_author = "hankun"
37 | # 作者主页
38 | author_url = "https://github.com/hankunyu"
39 | # 插件配置项ID前缀
40 | plugin_config_prefix = "bdremuxer_"
41 | # 加载顺序
42 | plugin_order = 1
43 | # 可使用的用户级别
44 | auth_level = 1
45 |
46 | # 私有属性
47 | _enabled = False
48 | _delete = False
49 | _run_once = False
50 | _path = ""
51 |
52 | def init_plugin(self, config: dict = None):
53 | if config:
54 | self._enabled = config.get("enabled")
55 | self._delete = config.get("delete")
56 | self._run_once = config.get("run_once")
57 | self._path = config.get("path")
58 | if self._enabled:
59 | logger.info("BD Remuxer 插件初始化完成")
60 | if self._run_once:
61 | thread = threading.Thread(target=self.extract, args=(self._path,))
62 | thread.start()
63 | self.update_config({
64 | "enabled": self._enabled,
65 | "delete": self._delete,
66 | "run_once": False,
67 | "path": self._path
68 | })
69 |
70 | def get_state(self) -> bool:
71 | return self._enabled
72 |
73 |
74 |
75 | @staticmethod
76 | def get_command() -> List[Dict[str, Any]]:
77 | pass
78 |
79 | def get_api(self) -> List[Dict[str, Any]]:
80 | pass
81 |
82 | # 插件配置页面
83 | def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
84 | """
85 | 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构
86 | """
87 | return [
88 | {
89 | 'component': 'VForm',
90 | 'content': [
91 | {
92 | 'component': 'VRow',
93 | 'content': [
94 | {
95 | 'component': 'VCol',
96 | 'props': {
97 | 'cols': 12,
98 | 'md': 6
99 | },
100 | 'content': [
101 | {
102 | 'component': 'VSwitch',
103 | 'props': {
104 | 'model': 'enabled',
105 | 'label': '启用插件',
106 | }
107 | }
108 | ]
109 | },
110 | {
111 | 'component': 'VCol',
112 | 'props': {
113 | 'cols': 12,
114 | 'md': 6
115 | },
116 | 'content': [
117 | {
118 | 'component': 'VSwitch',
119 | 'props': {
120 | 'model': 'delete',
121 | 'label': '删除原始文件',
122 | }
123 | }
124 | ]
125 | }
126 | ]
127 | },
128 | {
129 | 'component': 'VRow',
130 | 'content': [
131 | {
132 | 'component': 'VCol',
133 | 'props': {
134 | 'cols': 12
135 | },
136 | 'content': [
137 | {
138 | 'component': 'VTextarea',
139 | 'props': {
140 | 'model': 'path',
141 | 'label': '手动指定BDMV文件夹路径',
142 | 'rows': 1,
143 | 'placeholder': '路径指向BDMV父文件夹',
144 | }
145 | }
146 | ]
147 | },
148 | {
149 | 'component': 'VCol',
150 | 'content': [
151 | {
152 | 'component': 'VSwitch',
153 | 'props': {
154 | 'model': 'run_once',
155 | 'label': '提取指定目录BDMV',
156 | }
157 | }
158 | ]
159 | }
160 | ]
161 | },
162 | {
163 | 'component': 'VRow',
164 | 'content': [
165 | {
166 | 'component': 'VCol',
167 | 'content': [
168 | {
169 | 'component': 'VAlert',
170 | 'props': {
171 | 'type': 'info',
172 | 'variant': 'flat',
173 | 'text': '自用插件,可能不稳定',
174 | }
175 | }
176 | ]
177 | }
178 | ]
179 | }
180 | ]
181 | }
182 | ], {
183 | "enabled": False,
184 | "delete": False,
185 | "path": "",
186 | "run_once": False,
187 | }
188 |
189 | def get_page(self) -> List[dict]:
190 | pass
191 |
192 | def extract(self,bd_path : str):
193 | logger.info('开始提取BDMV。')
194 | output_name = os.path.basename(bd_path) + ".mkv"
195 | output_name = os.path.join(bd_path, output_name)
196 | bd_path = bd_path + '/BDMV'
197 | if not os.path.exists(bd_path):
198 | logger.info('失败。输入路径不存在BDMV文件夹')
199 | return
200 | mpls_path = bd_path + '/PLAYLIST/'
201 | if not os.path.exists(mpls_path):
202 | logger.info('失败。找不到PLAYLIST文件夹')
203 | return
204 | file_paths = self.get_all_m2ts(mpls_path)
205 | if not file_paths:
206 | logger.info('失败。找不到m2ts文件')
207 | return
208 |
209 | filelist_string = '\n'.join([f"file '{file}'" for file in file_paths])
210 | # 将filelist_string写入filelist.txt
211 | logger.info('搜索到需要提取的m2ts文件: ' + filelist_string)
212 | with open('/tmp/filelist.txt', 'w') as file:
213 | file.write(filelist_string)
214 |
215 | # 提取流程
216 | # 分析m2ts文件,提取视频流和音频流信息
217 | test_file = file_paths[0]
218 | probe = ffmpeg.probe(test_file)
219 | video_streams = [stream for stream in probe['streams'] if stream['codec_type'] == 'video']
220 | audio_streams = [stream for stream in probe['streams'] if stream['codec_type'] == 'audio']
221 | subtitle_streams = [stream for stream in probe['streams'] if stream['codec_type'] == 'subtitle']
222 |
223 | # 选取第一个视频流作为流编码信息
224 | video_codec = video_streams[0]['codec_name']
225 | # 获得每一条音频流的编码信息
226 | audio_codec = []
227 | for audio_stream in audio_streams:
228 | if audio_stream['codec_name'] == 'pcm_bluray':
229 | audio_codec.append('pcm_s16le')
230 | else:
231 | audio_codec.append('copy')
232 | # print(audio_stream['codec_name'])
233 |
234 | # 获得每一条字幕流的编码信息
235 | subtitle_codec = []
236 | for subtitle_stream in subtitle_streams:
237 | if subtitle_stream['codec_name'] == 'hdmv_pgs_subtitle':
238 | subtitle_codec.append('copy')
239 | else:
240 | subtitle_codec.append('copy')
241 |
242 | # 整理参数作为字典
243 | dict = { }
244 | for i in range(len(audio_codec)):
245 | dict[f'acodec:{i}'] = audio_codec[i]
246 | for i in range(len(subtitle_codec)):
247 | dict[f'scodec:{i}'] = subtitle_codec[i]
248 | # 使用ffmpeg合并m2ts文件
249 | try:
250 | (
251 | ffmpeg
252 | .input(
253 | '/tmp/filelist.txt',
254 | format='concat',
255 | safe=0,
256 | )
257 | .output(
258 | output_name,
259 | vcodec='copy',
260 | **dict,
261 | map='0', # 映射所有输入流
262 | map_metadata='0', # 复制输入流的元数据
263 | map_chapters='0', # 复制输入流的章节信息
264 | )
265 | .run()
266 | )
267 | except ffmpeg.Error as e:
268 | logger.error(e.stderr)
269 | logger.info('失败。')
270 | return
271 | # 删除原始文件
272 | if self._delete:
273 | shutil.rmtree(bd_path)
274 | logger.info('成功提取BDMV。并删除原始文件。')
275 | else:
276 | logger.info('成功提取BDMV。')
277 |
278 |
279 | def get_all_m2ts(self,mpls_path) -> list:
280 | """
281 | Get all useful m2ts file paths from mpls file
282 | :param mpls_path: path to mpls 00000 file
283 | :return: list of m2ts file paths
284 | """
285 | files = []
286 | play_items = []
287 | for file in os.listdir(mpls_path):
288 | if os.path.isfile(os.path.join(mpls_path, file)) and file.endswith('.mpls'):
289 | if file == '00000.mpls': continue # 跳过00000.mpls
290 | files.append(os.path.join(mpls_path, file))
291 | files.sort()
292 | for file in files:
293 | with open(file, 'rb') as mpls_file:
294 | header = mpls.load_movie_playlist(mpls_file)
295 | mpls_file.seek(header.playlist_start_address, os.SEEK_SET)
296 | pls = mpls.load_playlist(mpls_file)
297 | for item in pls.play_items:
298 | if item.uo_mask_table == 0:
299 | stream_path = os.path.dirname(os.path.dirname(file)) + '/STREAM/'
300 | file_path = stream_path + item.clip_information_filename + '.m2ts'
301 | play_items.append(file_path)
302 | if play_items:
303 | return play_items
304 | return play_items
305 |
306 | @eventmanager.register(EventType.TransferComplete)
307 | def remuxer(self, event):
308 | if not self._enabled:
309 | return
310 | def __to_dict(_event):
311 | """
312 | 递归将对象转换为字典
313 | """
314 | if isinstance(_event, dict):
315 | for k, v in _event.items():
316 | _event[k] = __to_dict(v)
317 | return _event
318 | elif isinstance(_event, list):
319 | for i in range(len(_event)):
320 | _event[i] = __to_dict(_event[i])
321 | return _event
322 | elif isinstance(_event, tuple):
323 | return tuple(__to_dict(list(_event)))
324 | elif isinstance(_event, set):
325 | return set(__to_dict(list(_event)))
326 | elif hasattr(_event, 'to_dict'):
327 | return __to_dict(_event.to_dict())
328 | elif hasattr(_event, '__dict__'):
329 | return __to_dict(_event.__dict__)
330 | elif isinstance(_event, (int, float, str, bool, type(None))):
331 | return _event
332 | else:
333 | return str(_event)
334 |
335 | raw_data = __to_dict(event.event_data)
336 | target_file = raw_data.get("transferinfo").get("file_list_new")[0]
337 | target_path = os.path.dirname(target_file)
338 |
339 | # 检查是否存在BDMV文件夹
340 | bd_path = os.path.dirname(target_path)
341 | if not os.path.exists(bd_path + '/BDMV'):
342 | logger.warn('失败。找不到BDMV文件夹: ' + bd_path)
343 | return
344 | # 提取流程
345 | thread = threading.Thread(target=self.extract, args=(bd_path,))
346 | thread.start()
347 |
348 |
349 |
350 |
351 | def stop_service(self):
352 | """
353 | 退出插件
354 | """
355 | pass
356 |
--------------------------------------------------------------------------------
/plugins.v2/bdremuxer/requirements.txt:
--------------------------------------------------------------------------------
1 | pyparsebluray~=0.1.4
2 | ffmpeg-python~=0.2.0
3 |
--------------------------------------------------------------------------------
/plugins.v2/danmu/README.md:
--------------------------------------------------------------------------------
1 | # 弹幕刮削
2 |
3 | 
4 |
5 | 此插件根据文件匹配弹弹平台上的弹幕,将弹幕文件转换为ass格式字幕,用于实现在不支持弹幕播放的设备上模拟弹幕.
6 |
7 |
8 |
9 | ### 文件匹配规则
10 | 首先会尝试使用文件hash直接匹配文件,如果没有匹配到怎会尝试使用TMDB ID来进行匹配.
11 |
以上匹配方式可以解决百分之八九十文件的识别问题.
12 |
如果匹配失败,提示找不到对应弹幕,有一个暂时的方法...可以尝试访问 https://dandanapi.hankun.online/docs
13 | 
14 | 使用try it out,搜索对应文件
15 |
file_hash随便选一个值修改,只要保持长度32位,file_name更改为你想要找的文件然后点execute.在返回的值中找到animeId.
16 | 
17 | 在这个媒体文件的目录中添加一个空白文件,命名为xxxx.id (这个xxxx是你找到的对应的animeId)
18 | 之后插件会有限使用这个id来进行弹幕的搜索以及匹配.
19 |
20 |
21 |
22 | ### 更新日志
23 |
24 | - v1.4.0: 修复文件路径无法访问的问题,新增无弹幕或者弹幕数量过少时定时重试
25 | - v1.3.0: 新增联邦组件支持, 增加UI手动刮削, 需求MP v2.4.5+
26 | - v1.2.0: 针对新番(90天内发布的媒体资源)使用较短时间缓存以获取最新弹幕数据
27 | - v1.1.15: 增加根据路径刮削弹幕功能。增加单文件路径刮削。能力有限无法在同一个页面实现按钮功能……
28 | - v1.1.13: 先检查当前目录下有没有.id结尾的文件 如果有,则获取文件名作为弹幕ID(如190270001 则使用19027.id)。如果需要请访问 https://dandanapi.hankun.online/docs 尝试获取epsoide id。
29 | - v1.1.11: 修复弹幕获取过滤错误
30 | - v1.1.10: 使用TMDB ID作为预备匹配方案,当无法匹配文件hash时尝试使用TMDB ID。
31 | - v1.1.8: 增加了中转服务器,恢复了对弹弹的访问以及缓存。修复透明度问题。增加了弹幕过滤选项。
32 |
--------------------------------------------------------------------------------
/plugins.v2/danmu/dist/assets/__federation_expose_Config-mmMv5D16.css:
--------------------------------------------------------------------------------
1 |
2 | .plugin-config[data-v-2b6e1a1e] {
3 | max-width: 80rem;
4 | margin: 0 auto;
5 | padding: 0.5rem;
6 | }
7 | .bg-primary-lighten-5[data-v-2b6e1a1e] {
8 | background-color: rgba(var(--v-theme-primary), 0.07);
9 | }
10 | .border[data-v-2b6e1a1e] {
11 | border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
12 | }
13 | .config-card[data-v-2b6e1a1e] {
14 | background-image: linear-gradient(to right, rgba(var(--v-theme-surface), 0.98), rgba(var(--v-theme-surface), 0.95)),
15 | repeating-linear-gradient(45deg, rgba(var(--v-theme-primary), 0.03), rgba(var(--v-theme-primary), 0.03) 10px, transparent 10px, transparent 20px);
16 | background-attachment: fixed;
17 | box-shadow: 0 1px 2px rgba(var(--v-border-color), 0.05) !important;
18 | transition: all 0.3s ease;
19 | }
20 | .config-card[data-v-2b6e1a1e]:hover {
21 | box-shadow: 0 3px 6px rgba(var(--v-border-color), 0.1) !important;
22 | }
23 | .setting-item[data-v-2b6e1a1e] {
24 | border-radius: 8px;
25 | transition: all 0.2s ease;
26 | padding: 0.5rem;
27 | margin-bottom: 4px;
28 | }
29 | .setting-item[data-v-2b6e1a1e]:hover {
30 | background-color: rgba(var(--v-theme-primary), 0.03);
31 | }
32 | .small-switch[data-v-2b6e1a1e] {
33 | transform: scale(0.8);
34 | margin-right: -8px;
35 | }
36 | .text-subtitle-2[data-v-2b6e1a1e] {
37 | font-size: 14px !important;
38 | font-weight: 500;
39 | margin-bottom: 2px;
40 | }
41 |
--------------------------------------------------------------------------------
/plugins.v2/danmu/dist/assets/__federation_expose_Page-CyDIESC3.css:
--------------------------------------------------------------------------------
1 |
2 | .plugin-page[data-v-0853f3af] {
3 | max-width: 80rem;
4 | margin: 0 auto;
5 | padding: 0.5rem;
6 | }
7 | .bg-primary-lighten-5[data-v-0853f3af] {
8 | background-color: rgba(var(--v-theme-primary), 0.07);
9 | }
10 | .border[data-v-0853f3af] {
11 | border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
12 | }
13 | .status-card[data-v-0853f3af] {
14 | background-image: linear-gradient(to right, rgba(var(--v-theme-surface), 0.98), rgba(var(--v-theme-surface), 0.95)),
15 | repeating-linear-gradient(45deg, rgba(var(--v-theme-primary), 0.03), rgba(var(--v-theme-primary), 0.03) 10px, transparent 10px, transparent 20px);
16 | background-attachment: fixed;
17 | box-shadow: 0 1px 2px rgba(var(--v-border-color), 0.05) !important;
18 | transition: all 0.3s ease;
19 | }
20 | .status-card[data-v-0853f3af]:hover {
21 | box-shadow: 0 3px 6px rgba(var(--v-border-color), 0.1) !important;
22 | }
23 | .status-item[data-v-0853f3af] {
24 | border-radius: 8px;
25 | transition: all 0.2s ease;
26 | padding: 0.5rem;
27 | margin-bottom: 4px;
28 | }
29 | .status-item[data-v-0853f3af]:hover {
30 | background-color: rgba(var(--v-theme-primary), 0.03);
31 | }
32 | .text-subtitle-2[data-v-0853f3af] {
33 | font-size: 14px !important;
34 | font-weight: 500;
35 | margin-bottom: 2px;
36 | }
37 | .directory-content[data-v-0853f3af] {
38 | max-height: 600px;
39 | overflow-y: auto;
40 | }
41 | .directory-item[data-v-0853f3af] {
42 | border-radius: 4px;
43 | transition: all 0.2s ease;
44 | cursor: pointer;
45 | }
46 | .directory-item[data-v-0853f3af]:hover {
47 | background-color: rgba(var(--v-theme-primary), 0.03);
48 | }
49 | .back-item[data-v-0853f3af] {
50 | border-radius: 4px;
51 | transition: all 0.2s ease;
52 | cursor: pointer;
53 | border: 1px dashed rgba(var(--v-theme-primary), 0.3);
54 | }
55 | .back-item[data-v-0853f3af]:hover {
56 | background-color: rgba(var(--v-theme-primary), 0.05);
57 | border-color: rgba(var(--v-theme-primary), 0.5);
58 | }
59 | .media-item[data-v-0853f3af] {
60 | border-radius: 4px;
61 | transition: all 0.2s ease;
62 | }
63 | .media-item[data-v-0853f3af]:hover {
64 | background-color: rgba(var(--v-theme-primary), 0.03);
65 | }
66 | .cursor-pointer[data-v-0853f3af] {
67 | cursor: pointer;
68 | }
69 |
--------------------------------------------------------------------------------
/plugins.v2/danmu/dist/assets/__federation_fn_import-JrT3xvdd.js:
--------------------------------------------------------------------------------
1 | const buildIdentifier = "[0-9A-Za-z-]+";
2 | const build = `(?:\\+(${buildIdentifier}(?:\\.${buildIdentifier})*))`;
3 | const numericIdentifier = "0|[1-9]\\d*";
4 | const numericIdentifierLoose = "[0-9]+";
5 | const nonNumericIdentifier = "\\d*[a-zA-Z-][a-zA-Z0-9-]*";
6 | const preReleaseIdentifierLoose = `(?:${numericIdentifierLoose}|${nonNumericIdentifier})`;
7 | const preReleaseLoose = `(?:-?(${preReleaseIdentifierLoose}(?:\\.${preReleaseIdentifierLoose})*))`;
8 | const preReleaseIdentifier = `(?:${numericIdentifier}|${nonNumericIdentifier})`;
9 | const preRelease = `(?:-(${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*))`;
10 | const xRangeIdentifier = `${numericIdentifier}|x|X|\\*`;
11 | const xRangePlain = `[v=\\s]*(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:${preRelease})?${build}?)?)?`;
12 | const hyphenRange = `^\\s*(${xRangePlain})\\s+-\\s+(${xRangePlain})\\s*$`;
13 | const mainVersionLoose = `(${numericIdentifierLoose})\\.(${numericIdentifierLoose})\\.(${numericIdentifierLoose})`;
14 | const loosePlain = `[v=\\s]*${mainVersionLoose}${preReleaseLoose}?${build}?`;
15 | const gtlt = "((?:<|>)?=?)";
16 | const comparatorTrim = `(\\s*)${gtlt}\\s*(${loosePlain}|${xRangePlain})`;
17 | const loneTilde = "(?:~>?)";
18 | const tildeTrim = `(\\s*)${loneTilde}\\s+`;
19 | const loneCaret = "(?:\\^)";
20 | const caretTrim = `(\\s*)${loneCaret}\\s+`;
21 | const star = "(<|>)?=?\\s*\\*";
22 | const caret = `^${loneCaret}${xRangePlain}$`;
23 | const mainVersion = `(${numericIdentifier})\\.(${numericIdentifier})\\.(${numericIdentifier})`;
24 | const fullPlain = `v?${mainVersion}${preRelease}?${build}?`;
25 | const tilde = `^${loneTilde}${xRangePlain}$`;
26 | const xRange = `^${gtlt}\\s*${xRangePlain}$`;
27 | const comparator = `^${gtlt}\\s*(${fullPlain})$|^$`;
28 | const gte0 = "^\\s*>=\\s*0.0.0\\s*$";
29 | function parseRegex(source) {
30 | return new RegExp(source);
31 | }
32 | function isXVersion(version) {
33 | return !version || version.toLowerCase() === "x" || version === "*";
34 | }
35 | function pipe(...fns) {
36 | return (x) => {
37 | return fns.reduce((v, f) => f(v), x);
38 | };
39 | }
40 | function extractComparator(comparatorString) {
41 | return comparatorString.match(parseRegex(comparator));
42 | }
43 | function combineVersion(major, minor, patch, preRelease2) {
44 | const mainVersion2 = `${major}.${minor}.${patch}`;
45 | if (preRelease2) {
46 | return `${mainVersion2}-${preRelease2}`;
47 | }
48 | return mainVersion2;
49 | }
50 | function parseHyphen(range) {
51 | return range.replace(
52 | parseRegex(hyphenRange),
53 | (_range, from, fromMajor, fromMinor, fromPatch, _fromPreRelease, _fromBuild, to, toMajor, toMinor, toPatch, toPreRelease) => {
54 | if (isXVersion(fromMajor)) {
55 | from = "";
56 | } else if (isXVersion(fromMinor)) {
57 | from = `>=${fromMajor}.0.0`;
58 | } else if (isXVersion(fromPatch)) {
59 | from = `>=${fromMajor}.${fromMinor}.0`;
60 | } else {
61 | from = `>=${from}`;
62 | }
63 | if (isXVersion(toMajor)) {
64 | to = "";
65 | } else if (isXVersion(toMinor)) {
66 | to = `<${+toMajor + 1}.0.0-0`;
67 | } else if (isXVersion(toPatch)) {
68 | to = `<${toMajor}.${+toMinor + 1}.0-0`;
69 | } else if (toPreRelease) {
70 | to = `<=${toMajor}.${toMinor}.${toPatch}-${toPreRelease}`;
71 | } else {
72 | to = `<=${to}`;
73 | }
74 | return `${from} ${to}`.trim();
75 | }
76 | );
77 | }
78 | function parseComparatorTrim(range) {
79 | return range.replace(parseRegex(comparatorTrim), "$1$2$3");
80 | }
81 | function parseTildeTrim(range) {
82 | return range.replace(parseRegex(tildeTrim), "$1~");
83 | }
84 | function parseCaretTrim(range) {
85 | return range.replace(parseRegex(caretTrim), "$1^");
86 | }
87 | function parseCarets(range) {
88 | return range.trim().split(/\s+/).map((rangeVersion) => {
89 | return rangeVersion.replace(
90 | parseRegex(caret),
91 | (_, major, minor, patch, preRelease2) => {
92 | if (isXVersion(major)) {
93 | return "";
94 | } else if (isXVersion(minor)) {
95 | return `>=${major}.0.0 <${+major + 1}.0.0-0`;
96 | } else if (isXVersion(patch)) {
97 | if (major === "0") {
98 | return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`;
99 | } else {
100 | return `>=${major}.${minor}.0 <${+major + 1}.0.0-0`;
101 | }
102 | } else if (preRelease2) {
103 | if (major === "0") {
104 | if (minor === "0") {
105 | return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${minor}.${+patch + 1}-0`;
106 | } else {
107 | return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`;
108 | }
109 | } else {
110 | return `>=${major}.${minor}.${patch}-${preRelease2} <${+major + 1}.0.0-0`;
111 | }
112 | } else {
113 | if (major === "0") {
114 | if (minor === "0") {
115 | return `>=${major}.${minor}.${patch} <${major}.${minor}.${+patch + 1}-0`;
116 | } else {
117 | return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`;
118 | }
119 | }
120 | return `>=${major}.${minor}.${patch} <${+major + 1}.0.0-0`;
121 | }
122 | }
123 | );
124 | }).join(" ");
125 | }
126 | function parseTildes(range) {
127 | return range.trim().split(/\s+/).map((rangeVersion) => {
128 | return rangeVersion.replace(
129 | parseRegex(tilde),
130 | (_, major, minor, patch, preRelease2) => {
131 | if (isXVersion(major)) {
132 | return "";
133 | } else if (isXVersion(minor)) {
134 | return `>=${major}.0.0 <${+major + 1}.0.0-0`;
135 | } else if (isXVersion(patch)) {
136 | return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`;
137 | } else if (preRelease2) {
138 | return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`;
139 | }
140 | return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`;
141 | }
142 | );
143 | }).join(" ");
144 | }
145 | function parseXRanges(range) {
146 | return range.split(/\s+/).map((rangeVersion) => {
147 | return rangeVersion.trim().replace(
148 | parseRegex(xRange),
149 | (ret, gtlt2, major, minor, patch, preRelease2) => {
150 | const isXMajor = isXVersion(major);
151 | const isXMinor = isXMajor || isXVersion(minor);
152 | const isXPatch = isXMinor || isXVersion(patch);
153 | if (gtlt2 === "=" && isXPatch) {
154 | gtlt2 = "";
155 | }
156 | preRelease2 = "";
157 | if (isXMajor) {
158 | if (gtlt2 === ">" || gtlt2 === "<") {
159 | return "<0.0.0-0";
160 | } else {
161 | return "*";
162 | }
163 | } else if (gtlt2 && isXPatch) {
164 | if (isXMinor) {
165 | minor = 0;
166 | }
167 | patch = 0;
168 | if (gtlt2 === ">") {
169 | gtlt2 = ">=";
170 | if (isXMinor) {
171 | major = +major + 1;
172 | minor = 0;
173 | patch = 0;
174 | } else {
175 | minor = +minor + 1;
176 | patch = 0;
177 | }
178 | } else if (gtlt2 === "<=") {
179 | gtlt2 = "<";
180 | if (isXMinor) {
181 | major = +major + 1;
182 | } else {
183 | minor = +minor + 1;
184 | }
185 | }
186 | if (gtlt2 === "<") {
187 | preRelease2 = "-0";
188 | }
189 | return `${gtlt2 + major}.${minor}.${patch}${preRelease2}`;
190 | } else if (isXMinor) {
191 | return `>=${major}.0.0${preRelease2} <${+major + 1}.0.0-0`;
192 | } else if (isXPatch) {
193 | return `>=${major}.${minor}.0${preRelease2} <${major}.${+minor + 1}.0-0`;
194 | }
195 | return ret;
196 | }
197 | );
198 | }).join(" ");
199 | }
200 | function parseStar(range) {
201 | return range.trim().replace(parseRegex(star), "");
202 | }
203 | function parseGTE0(comparatorString) {
204 | return comparatorString.trim().replace(parseRegex(gte0), "");
205 | }
206 | function compareAtom(rangeAtom, versionAtom) {
207 | rangeAtom = +rangeAtom || rangeAtom;
208 | versionAtom = +versionAtom || versionAtom;
209 | if (rangeAtom > versionAtom) {
210 | return 1;
211 | }
212 | if (rangeAtom === versionAtom) {
213 | return 0;
214 | }
215 | return -1;
216 | }
217 | function comparePreRelease(rangeAtom, versionAtom) {
218 | const { preRelease: rangePreRelease } = rangeAtom;
219 | const { preRelease: versionPreRelease } = versionAtom;
220 | if (rangePreRelease === void 0 && !!versionPreRelease) {
221 | return 1;
222 | }
223 | if (!!rangePreRelease && versionPreRelease === void 0) {
224 | return -1;
225 | }
226 | if (rangePreRelease === void 0 && versionPreRelease === void 0) {
227 | return 0;
228 | }
229 | for (let i = 0, n = rangePreRelease.length; i <= n; i++) {
230 | const rangeElement = rangePreRelease[i];
231 | const versionElement = versionPreRelease[i];
232 | if (rangeElement === versionElement) {
233 | continue;
234 | }
235 | if (rangeElement === void 0 && versionElement === void 0) {
236 | return 0;
237 | }
238 | if (!rangeElement) {
239 | return 1;
240 | }
241 | if (!versionElement) {
242 | return -1;
243 | }
244 | return compareAtom(rangeElement, versionElement);
245 | }
246 | return 0;
247 | }
248 | function compareVersion(rangeAtom, versionAtom) {
249 | return compareAtom(rangeAtom.major, versionAtom.major) || compareAtom(rangeAtom.minor, versionAtom.minor) || compareAtom(rangeAtom.patch, versionAtom.patch) || comparePreRelease(rangeAtom, versionAtom);
250 | }
251 | function eq(rangeAtom, versionAtom) {
252 | return rangeAtom.version === versionAtom.version;
253 | }
254 | function compare(rangeAtom, versionAtom) {
255 | switch (rangeAtom.operator) {
256 | case "":
257 | case "=":
258 | return eq(rangeAtom, versionAtom);
259 | case ">":
260 | return compareVersion(rangeAtom, versionAtom) < 0;
261 | case ">=":
262 | return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) < 0;
263 | case "<":
264 | return compareVersion(rangeAtom, versionAtom) > 0;
265 | case "<=":
266 | return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) > 0;
267 | case void 0: {
268 | return true;
269 | }
270 | default:
271 | return false;
272 | }
273 | }
274 | function parseComparatorString(range) {
275 | return pipe(
276 | parseCarets,
277 | parseTildes,
278 | parseXRanges,
279 | parseStar
280 | )(range);
281 | }
282 | function parseRange(range) {
283 | return pipe(
284 | parseHyphen,
285 | parseComparatorTrim,
286 | parseTildeTrim,
287 | parseCaretTrim
288 | )(range.trim()).split(/\s+/).join(" ");
289 | }
290 | function satisfy(version, range) {
291 | if (!version) {
292 | return false;
293 | }
294 | const parsedRange = parseRange(range);
295 | const parsedComparator = parsedRange.split(" ").map((rangeVersion) => parseComparatorString(rangeVersion)).join(" ");
296 | const comparators = parsedComparator.split(/\s+/).map((comparator2) => parseGTE0(comparator2));
297 | const extractedVersion = extractComparator(version);
298 | if (!extractedVersion) {
299 | return false;
300 | }
301 | const [
302 | ,
303 | versionOperator,
304 | ,
305 | versionMajor,
306 | versionMinor,
307 | versionPatch,
308 | versionPreRelease
309 | ] = extractedVersion;
310 | const versionAtom = {
311 | version: combineVersion(
312 | versionMajor,
313 | versionMinor,
314 | versionPatch,
315 | versionPreRelease
316 | ),
317 | major: versionMajor,
318 | minor: versionMinor,
319 | patch: versionPatch,
320 | preRelease: versionPreRelease == null ? void 0 : versionPreRelease.split(".")
321 | };
322 | for (const comparator2 of comparators) {
323 | const extractedComparator = extractComparator(comparator2);
324 | if (!extractedComparator) {
325 | return false;
326 | }
327 | const [
328 | ,
329 | rangeOperator,
330 | ,
331 | rangeMajor,
332 | rangeMinor,
333 | rangePatch,
334 | rangePreRelease
335 | ] = extractedComparator;
336 | const rangeAtom = {
337 | operator: rangeOperator,
338 | version: combineVersion(
339 | rangeMajor,
340 | rangeMinor,
341 | rangePatch,
342 | rangePreRelease
343 | ),
344 | major: rangeMajor,
345 | minor: rangeMinor,
346 | patch: rangePatch,
347 | preRelease: rangePreRelease == null ? void 0 : rangePreRelease.split(".")
348 | };
349 | if (!compare(rangeAtom, versionAtom)) {
350 | return false;
351 | }
352 | }
353 | return true;
354 | }
355 |
356 | // eslint-disable-next-line no-undef
357 | const moduleMap = {};
358 | const moduleCache = Object.create(null);
359 | async function importShared(name, shareScope = 'default') {
360 | return moduleCache[name]
361 | ? new Promise((r) => r(moduleCache[name]))
362 | : (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name)
363 | }
364 | async function getSharedFromRuntime(name, shareScope) {
365 | let module = null;
366 | if (globalThis?.__federation_shared__?.[shareScope]?.[name]) {
367 | const versionObj = globalThis.__federation_shared__[shareScope][name];
368 | const requiredVersion = moduleMap[name]?.requiredVersion;
369 | const hasRequiredVersion = !!requiredVersion;
370 | if (hasRequiredVersion) {
371 | const versionKey = Object.keys(versionObj).find((version) =>
372 | satisfy(version, requiredVersion)
373 | );
374 | if (versionKey) {
375 | const versionValue = versionObj[versionKey];
376 | module = await (await versionValue.get())();
377 | } else {
378 | console.log(
379 | `provider support ${name}(${versionKey}) is not satisfied requiredVersion(\${moduleMap[name].requiredVersion})`
380 | );
381 | }
382 | } else {
383 | const versionKey = Object.keys(versionObj)[0];
384 | const versionValue = versionObj[versionKey];
385 | module = await (await versionValue.get())();
386 | }
387 | }
388 | if (module) {
389 | return flattenModule(module, name)
390 | }
391 | }
392 | async function getSharedFromLocal(name) {
393 | if (moduleMap[name]?.import) {
394 | let module = await (await moduleMap[name].get())();
395 | return flattenModule(module, name)
396 | } else {
397 | console.error(
398 | `consumer config import=false,so cant use callback shared module`
399 | );
400 | }
401 | }
402 | function flattenModule(module, name) {
403 | // use a shared module which export default a function will getting error 'TypeError: xxx is not a function'
404 | if (typeof module.default === 'function') {
405 | Object.keys(module).forEach((key) => {
406 | if (key !== 'default') {
407 | module.default[key] = module[key];
408 | }
409 | });
410 | moduleCache[name] = module.default;
411 | return module.default
412 | }
413 | if (module.default) module = Object.assign({}, module.default, module);
414 | moduleCache[name] = module;
415 | return module
416 | }
417 |
418 | export { importShared, getSharedFromLocal as importSharedLocal, getSharedFromRuntime as importSharedRuntime };
419 |
--------------------------------------------------------------------------------
/plugins.v2/danmu/dist/assets/_plugin-vue_export-helper-pcqpp-6-.js:
--------------------------------------------------------------------------------
1 | const _export_sfc = (sfc, props) => {
2 | const target = sfc.__vccOpts || sfc;
3 | for (const [key, val] of props) {
4 | target[key] = val;
5 | }
6 | return target;
7 | };
8 |
9 | export { _export_sfc as _ };
10 |
--------------------------------------------------------------------------------
/plugins.v2/danmu/dist/assets/index-cr18jDRr.css:
--------------------------------------------------------------------------------
1 |
2 | .plugin-app {
3 | width: 100%;
4 | height: 100%;
5 | display: flex;
6 | flex-direction: column;
7 | }
8 | @supports not selector(:focus-visible) {
9 | }
10 | @supports not selector(:focus-visible) {
11 | }
12 | @supports selector(:focus-visible) {
13 | }@supports not selector(:focus-visible) {
14 | }@keyframes progress-circular-dash {
15 | 0% {
16 | stroke-dasharray: 1, 200;
17 | stroke-dashoffset: 0px;
18 | }
19 | 50% {
20 | stroke-dasharray: 100, 200;
21 | stroke-dashoffset: -15px;
22 | }
23 | 100% {
24 | stroke-dasharray: 100, 200;
25 | stroke-dashoffset: -124px;
26 | }
27 | }
28 | @keyframes progress-circular-rotate {
29 | 100% {
30 | transform: rotate(270deg);
31 | }
32 | }@media (forced-colors: active) {
33 | }
34 |
35 | @media (forced-colors: active) {
36 | }
37 | @media (forced-colors: active) {
38 | }
39 |
40 | @keyframes indeterminate-ltr {
41 | 0% {
42 | left: -90%;
43 | right: 100%;
44 | }
45 | 60% {
46 | left: -90%;
47 | right: 100%;
48 | }
49 | 100% {
50 | left: 100%;
51 | right: -35%;
52 | }
53 | }
54 | @keyframes indeterminate-rtl {
55 | 0% {
56 | left: 100%;
57 | right: -90%;
58 | }
59 | 60% {
60 | left: 100%;
61 | right: -90%;
62 | }
63 | 100% {
64 | left: -35%;
65 | right: 100%;
66 | }
67 | }
68 | @keyframes indeterminate-short-ltr {
69 | 0% {
70 | left: -200%;
71 | right: 100%;
72 | }
73 | 60% {
74 | left: 107%;
75 | right: -8%;
76 | }
77 | 100% {
78 | left: 107%;
79 | right: -8%;
80 | }
81 | }
82 | @keyframes indeterminate-short-rtl {
83 | 0% {
84 | left: 100%;
85 | right: -200%;
86 | }
87 | 60% {
88 | left: -8%;
89 | right: 107%;
90 | }
91 | 100% {
92 | left: -8%;
93 | right: 107%;
94 | }
95 | }
96 | @keyframes stream {
97 | to {
98 | transform: translateX(var(--v-progress-linear-stream-to));
99 | }
100 | }
101 | @keyframes progress-linear-stripes {
102 | 0% {
103 | background-position-x: var(--v-progress-linear-height);
104 | }
105 | }@supports not selector(:focus-visible) {
106 | }
107 | @supports not selector(:focus-visible) {
108 | }@supports not selector(:focus-visible) {
109 | }
110 | @supports not selector(:focus-visible) {
111 | }
112 | @supports selector(:focus-visible) {
113 | }/* region BLOCK */
114 |
115 | /* endregion */
116 | /* region ELEMENTS */
117 |
118 | /* endregion *//* region INPUT */
119 |
120 | /* endregion */
121 | /* region MODIFIERS */
122 |
123 | /* endregion */
124 | /* region ELEMENTS */
125 |
126 | /* endregion */
127 | /* region AFFIXES */
128 | @media (hover: hover) {
129 | }
130 | @media (hover: none) {
131 | }
132 |
133 | /* endregion */
134 | /* region LABEL */
135 |
136 | /* endregion */
137 | /* region OUTLINE */
138 | @media (hover: hover) {
139 | }
140 |
141 | /* endregion */
142 | /* region LOADER */
143 |
144 | /* endregion */
145 | /* region OVERLAY */
146 | @media (hover: hover) {
147 | }
148 | @media (hover: hover) {
149 | }
150 | @media (hover: hover) {
151 | }
152 |
153 | /* endregion */
154 | /* region MODIFIERS */
155 |
156 | /* endregion */.bottom-sheet-transition-enter-from {
157 | transform: translateY(100%);
158 | }
159 | .bottom-sheet-transition-leave-to {
160 | transform: translateY(100%);
161 | }
162 | @media (min-width: 600px) {
163 | }@supports not selector(:focus-visible) {
164 | }
165 | @supports not selector(:focus-visible) {
166 | }@media (forced-colors: active) {
167 | }
168 |
169 | @media (hover: hover) {
170 | }@media (forced-colors: active) {
171 | }
172 | @media (forced-colors: active) {
173 | }
174 | @media (forced-colors: active) {
175 | }@media (min-width: 960px) {
176 | }
177 | @media (min-width: 1280px) {
178 | }
179 | @media (min-width: 1920px) {
180 | }
181 | @media (min-width: 2560px) {
182 | }
183 |
184 | .offset-1 {
185 | margin-inline-start: 8.3333333333%;
186 | }
187 |
188 | .offset-2 {
189 | margin-inline-start: 16.6666666667%;
190 | }
191 |
192 | .offset-3 {
193 | margin-inline-start: 25%;
194 | }
195 |
196 | .offset-4 {
197 | margin-inline-start: 33.3333333333%;
198 | }
199 |
200 | .offset-5 {
201 | margin-inline-start: 41.6666666667%;
202 | }
203 |
204 | .offset-6 {
205 | margin-inline-start: 50%;
206 | }
207 |
208 | .offset-7 {
209 | margin-inline-start: 58.3333333333%;
210 | }
211 |
212 | .offset-8 {
213 | margin-inline-start: 66.6666666667%;
214 | }
215 |
216 | .offset-9 {
217 | margin-inline-start: 75%;
218 | }
219 |
220 | .offset-10 {
221 | margin-inline-start: 83.3333333333%;
222 | }
223 |
224 | .offset-11 {
225 | margin-inline-start: 91.6666666667%;
226 | }
227 |
228 | @media (min-width: 600px) {
229 | .offset-sm-0 {
230 | margin-inline-start: 0;
231 | }
232 | .offset-sm-1 {
233 | margin-inline-start: 8.3333333333%;
234 | }
235 | .offset-sm-2 {
236 | margin-inline-start: 16.6666666667%;
237 | }
238 | .offset-sm-3 {
239 | margin-inline-start: 25%;
240 | }
241 | .offset-sm-4 {
242 | margin-inline-start: 33.3333333333%;
243 | }
244 | .offset-sm-5 {
245 | margin-inline-start: 41.6666666667%;
246 | }
247 | .offset-sm-6 {
248 | margin-inline-start: 50%;
249 | }
250 | .offset-sm-7 {
251 | margin-inline-start: 58.3333333333%;
252 | }
253 | .offset-sm-8 {
254 | margin-inline-start: 66.6666666667%;
255 | }
256 | .offset-sm-9 {
257 | margin-inline-start: 75%;
258 | }
259 | .offset-sm-10 {
260 | margin-inline-start: 83.3333333333%;
261 | }
262 | .offset-sm-11 {
263 | margin-inline-start: 91.6666666667%;
264 | }
265 | }
266 | @media (min-width: 960px) {
267 | .offset-md-0 {
268 | margin-inline-start: 0;
269 | }
270 | .offset-md-1 {
271 | margin-inline-start: 8.3333333333%;
272 | }
273 | .offset-md-2 {
274 | margin-inline-start: 16.6666666667%;
275 | }
276 | .offset-md-3 {
277 | margin-inline-start: 25%;
278 | }
279 | .offset-md-4 {
280 | margin-inline-start: 33.3333333333%;
281 | }
282 | .offset-md-5 {
283 | margin-inline-start: 41.6666666667%;
284 | }
285 | .offset-md-6 {
286 | margin-inline-start: 50%;
287 | }
288 | .offset-md-7 {
289 | margin-inline-start: 58.3333333333%;
290 | }
291 | .offset-md-8 {
292 | margin-inline-start: 66.6666666667%;
293 | }
294 | .offset-md-9 {
295 | margin-inline-start: 75%;
296 | }
297 | .offset-md-10 {
298 | margin-inline-start: 83.3333333333%;
299 | }
300 | .offset-md-11 {
301 | margin-inline-start: 91.6666666667%;
302 | }
303 | }
304 | @media (min-width: 1280px) {
305 | .offset-lg-0 {
306 | margin-inline-start: 0;
307 | }
308 | .offset-lg-1 {
309 | margin-inline-start: 8.3333333333%;
310 | }
311 | .offset-lg-2 {
312 | margin-inline-start: 16.6666666667%;
313 | }
314 | .offset-lg-3 {
315 | margin-inline-start: 25%;
316 | }
317 | .offset-lg-4 {
318 | margin-inline-start: 33.3333333333%;
319 | }
320 | .offset-lg-5 {
321 | margin-inline-start: 41.6666666667%;
322 | }
323 | .offset-lg-6 {
324 | margin-inline-start: 50%;
325 | }
326 | .offset-lg-7 {
327 | margin-inline-start: 58.3333333333%;
328 | }
329 | .offset-lg-8 {
330 | margin-inline-start: 66.6666666667%;
331 | }
332 | .offset-lg-9 {
333 | margin-inline-start: 75%;
334 | }
335 | .offset-lg-10 {
336 | margin-inline-start: 83.3333333333%;
337 | }
338 | .offset-lg-11 {
339 | margin-inline-start: 91.6666666667%;
340 | }
341 | }
342 | @media (min-width: 1920px) {
343 | .offset-xl-0 {
344 | margin-inline-start: 0;
345 | }
346 | .offset-xl-1 {
347 | margin-inline-start: 8.3333333333%;
348 | }
349 | .offset-xl-2 {
350 | margin-inline-start: 16.6666666667%;
351 | }
352 | .offset-xl-3 {
353 | margin-inline-start: 25%;
354 | }
355 | .offset-xl-4 {
356 | margin-inline-start: 33.3333333333%;
357 | }
358 | .offset-xl-5 {
359 | margin-inline-start: 41.6666666667%;
360 | }
361 | .offset-xl-6 {
362 | margin-inline-start: 50%;
363 | }
364 | .offset-xl-7 {
365 | margin-inline-start: 58.3333333333%;
366 | }
367 | .offset-xl-8 {
368 | margin-inline-start: 66.6666666667%;
369 | }
370 | .offset-xl-9 {
371 | margin-inline-start: 75%;
372 | }
373 | .offset-xl-10 {
374 | margin-inline-start: 83.3333333333%;
375 | }
376 | .offset-xl-11 {
377 | margin-inline-start: 91.6666666667%;
378 | }
379 | }
380 | @media (min-width: 2560px) {
381 | .offset-xxl-0 {
382 | margin-inline-start: 0;
383 | }
384 | .offset-xxl-1 {
385 | margin-inline-start: 8.3333333333%;
386 | }
387 | .offset-xxl-2 {
388 | margin-inline-start: 16.6666666667%;
389 | }
390 | .offset-xxl-3 {
391 | margin-inline-start: 25%;
392 | }
393 | .offset-xxl-4 {
394 | margin-inline-start: 33.3333333333%;
395 | }
396 | .offset-xxl-5 {
397 | margin-inline-start: 41.6666666667%;
398 | }
399 | .offset-xxl-6 {
400 | margin-inline-start: 50%;
401 | }
402 | .offset-xxl-7 {
403 | margin-inline-start: 58.3333333333%;
404 | }
405 | .offset-xxl-8 {
406 | margin-inline-start: 66.6666666667%;
407 | }
408 | .offset-xxl-9 {
409 | margin-inline-start: 75%;
410 | }
411 | .offset-xxl-10 {
412 | margin-inline-start: 83.3333333333%;
413 | }
414 | .offset-xxl-11 {
415 | margin-inline-start: 91.6666666667%;
416 | }
417 | }.date-picker-header-transition-enter-active,
418 | .date-picker-header-reverse-transition-enter-active {
419 | transition-duration: 0.3s;
420 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
421 | }
422 | .date-picker-header-transition-leave-active,
423 | .date-picker-header-reverse-transition-leave-active {
424 | transition-duration: 0.3s;
425 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
426 | }
427 |
428 | .date-picker-header-transition-enter-from {
429 | transform: translate(0, 100%);
430 | }
431 | .date-picker-header-transition-leave-to {
432 | opacity: 0;
433 | transform: translate(0, -100%);
434 | }
435 |
436 | .date-picker-header-reverse-transition-enter-from {
437 | transform: translate(0, -100%);
438 | }
439 | .date-picker-header-reverse-transition-leave-to {
440 | opacity: 0;
441 | transform: translate(0, 100%);
442 | }@supports not selector(:focus-visible) {
443 | }
444 | @supports not selector(:focus-visible) {
445 | }@keyframes loading {
446 | 100% {
447 | transform: translateX(100%);
448 | }
449 | }@supports not selector(:focus-visible) {
450 | }
451 | @supports not selector(:focus-visible) {
452 | }@media (forced-colors: active) {
453 | }@media (max-width: 1279.98px) {
454 | }/** Modifiers **/
--------------------------------------------------------------------------------
/plugins.v2/danmu/dist/assets/remoteEntry.js:
--------------------------------------------------------------------------------
1 | const currentImports = {};
2 | const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']);
3 | let moduleMap = {
4 | "./Page":()=>{
5 | dynamicLoadingCss(["__federation_expose_Page-CyDIESC3.css"], false, './Page');
6 | return __federation_import('./__federation_expose_Page-DF3RUHu3.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},
7 | "./Config":()=>{
8 | dynamicLoadingCss(["__federation_expose_Config-mmMv5D16.css"], false, './Config');
9 | return __federation_import('./__federation_expose_Config-BdvrOUFg.js').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},};
10 | const seen = {};
11 | const dynamicLoadingCss = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => {
12 | const metaUrl = import.meta.url;
13 | if (typeof metaUrl === 'undefined') {
14 | console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".');
15 | return;
16 | }
17 |
18 | const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('remoteEntry.js'));
19 | const base = '/';
20 | 'assets';
21 |
22 | cssFilePaths.forEach(cssPath => {
23 | let href = '';
24 | const baseUrl = base || curUrl;
25 | if (baseUrl) {
26 | const trimmer = {
27 | trailing: (path) => (path.endsWith('/') ? path.slice(0, -1) : path),
28 | leading: (path) => (path.startsWith('/') ? path.slice(1) : path)
29 | };
30 | const isAbsoluteUrl = (url) => url.startsWith('http') || url.startsWith('//');
31 |
32 | const cleanBaseUrl = trimmer.trailing(baseUrl);
33 | const cleanCssPath = trimmer.leading(cssPath);
34 | const cleanCurUrl = trimmer.trailing(curUrl);
35 |
36 | if (isAbsoluteUrl(baseUrl)) {
37 | href = [cleanBaseUrl, cleanCssPath].filter(Boolean).join('/');
38 | } else {
39 | if (cleanCurUrl.includes(cleanBaseUrl)) {
40 | href = [cleanCurUrl, cleanCssPath].filter(Boolean).join('/');
41 | } else {
42 | href = [cleanCurUrl + cleanBaseUrl, cleanCssPath].filter(Boolean).join('/');
43 | }
44 | }
45 | } else {
46 | href = cssPath;
47 | }
48 |
49 | if (dontAppendStylesToHead) {
50 | const key = 'css__Logsclean__' + exposeItemName;
51 | window[key] = window[key] || [];
52 | window[key].push(href);
53 | return;
54 | }
55 |
56 | if (href in seen) return;
57 | seen[href] = true;
58 |
59 | const element = document.createElement('link');
60 | element.rel = 'stylesheet';
61 | element.href = href;
62 | document.head.appendChild(element);
63 | });
64 | };
65 | async function __federation_import(name) {
66 | currentImports[name] ??= import(name);
67 | return currentImports[name]
68 | } const get =(module) => {
69 | if(!moduleMap[module]) throw new Error('Can not find remote module ' + module)
70 | return moduleMap[module]();
71 | };
72 | const init =(shareScope) => {
73 | globalThis.__federation_shared__= globalThis.__federation_shared__|| {};
74 | Object.entries(shareScope).forEach(([key, value]) => {
75 | for (const [versionKey, versionValue] of Object.entries(value)) {
76 | const scope = versionValue.scope || 'default';
77 | globalThis.__federation_shared__[scope] = globalThis.__federation_shared__[scope] || {};
78 | const shared= globalThis.__federation_shared__[scope];
79 | (shared[key] = shared[key]||{})[versionKey] = versionValue;
80 | }
81 | });
82 | };
83 |
84 | export { dynamicLoadingCss, get, init };
85 |
--------------------------------------------------------------------------------
/plugins.v2/danmu/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |