├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── README.zh-CN.md
├── configserver
├── __init__.py
├── server.py
└── templates
│ ├── config.html
│ ├── index.html
│ └── source.html
├── main.py
├── preview.png
├── resolvers.conf
├── run.sh
├── scrapeflows
├── bangumi_movie.json
├── bangumi_tvshow.json
├── bangumi_tvshow_episode.json
├── douban_movie.json
├── douban_tvshow.json
├── douban_tvshow_episode.json
├── maoyan_movie.json
├── maoyan_tvshow.json
├── maoyan_tvshow_episode.json
├── mtime_movie.json
├── mtime_tvshow.json
├── mtime_tvshow_episode.json
├── tmdb_movie.json
├── tmdb_tvshow.json
└── tmdb_tvshow_episode.json
├── scraper
├── __init__.py
├── enums.py
├── exceptions.py
├── fake.py
├── functions
│ ├── __init__.py
│ ├── collect.py
│ ├── doh.py
│ ├── loop.py
│ ├── request.py
│ └── retval.py
├── scraper.py
└── utils.py
├── setup.py
└── version.py
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Python Package
2 |
3 | on:
4 | push:
5 | tags:
6 | - v[0-9]*
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout Repository
16 | uses: actions/checkout@v3
17 | - name: Setup Python
18 | uses: actions/setup-python@v4
19 | with:
20 | python-version: '3.8'
21 | - name: Build Package
22 | run: python setup.py sdist --formats=zip
23 | - name: Create Release
24 | uses: ncipollo/release-action@v1
25 | with:
26 | artifacts: "dist/*.zip"
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### VisualStudioCode template
2 | .vscode/*
3 |
4 | # Local History for Visual Studio Code
5 | .history/
6 |
7 | # Built Visual Studio Code Extensions
8 | *.vsix
9 |
10 | ### JetBrains template
11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
12 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
13 |
14 | # User-specific stuff
15 | .idea/**/workspace.xml
16 | .idea/**/tasks.xml
17 | .idea/**/usage.statistics.xml
18 | .idea/**/dictionaries
19 | .idea/**/shelf
20 |
21 | # AWS User-specific
22 | .idea/**/aws.xml
23 |
24 | # Generated files
25 | .idea/**/contentModel.xml
26 |
27 | # Sensitive or high-churn files
28 | .idea/**/dataSources/
29 | .idea/**/dataSources.ids
30 | .idea/**/dataSources.local.xml
31 | .idea/**/sqlDataSources.xml
32 | .idea/**/dynamic.xml
33 | .idea/**/uiDesigner.xml
34 | .idea/**/dbnavigator.xml
35 |
36 | # Gradle
37 | .idea/**/gradle.xml
38 | .idea/**/libraries
39 |
40 | # Gradle and Maven with auto-import
41 | # When using Gradle or Maven with auto-import, you should exclude module files,
42 | # since they will be recreated, and may cause churn. Uncomment if using
43 | # auto-import.
44 | # .idea/artifacts
45 | # .idea/compiler.xml
46 | # .idea/jarRepositories.xml
47 | # .idea/modules.xml
48 | # .idea/*.iml
49 | # .idea/modules
50 | # *.iml
51 | # *.ipr
52 |
53 | # CMake
54 | cmake-build-*/
55 |
56 | # Mongo Explorer plugin
57 | .idea/**/mongoSettings.xml
58 |
59 | # File-based project format
60 | *.iws
61 |
62 | # IntelliJ
63 | out/
64 |
65 | # mpeltonen/sbt-idea plugin
66 | .idea_modules/
67 |
68 | # JIRA plugin
69 | atlassian-ide-plugin.xml
70 |
71 | # Cursive Clojure plugin
72 | .idea/replstate.xml
73 |
74 | # SonarLint plugin
75 | .idea/sonarlint/
76 |
77 | # Crashlytics plugin (for Android Studio and IntelliJ)
78 | com_crashlytics_export_strings.xml
79 | crashlytics.properties
80 | crashlytics-build.properties
81 | fabric.properties
82 |
83 | # Editor-based Rest Client
84 | .idea/httpRequests
85 |
86 | # Android studio 3.1+ serialized cache file
87 | .idea/caches/build_file_checksums.ser
88 |
89 | ### VirtualEnv template
90 | # Virtualenv
91 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
92 | .Python
93 | [Bb]in
94 | [Ii]nclude
95 | [Ll]ib
96 | [Ll]ib64
97 | [Ll]ocal
98 | [Ss]cripts
99 | pyvenv.cfg
100 | .venv
101 | pip-selfcheck.json
102 |
103 | ### Eclipse template
104 | .metadata
105 | bin/
106 | tmp/
107 | *.tmp
108 | *.bak
109 | *.swp
110 | *~.nib
111 | local.properties
112 | .settings/
113 | .loadpath
114 | .recommenders
115 |
116 | # External tool builders
117 | .externalToolBuilders/
118 |
119 | # Locally stored "Eclipse launch configurations"
120 | *.launch
121 |
122 | # PyDev specific (Python IDE for Eclipse)
123 | *.pydevproject
124 |
125 | # CDT-specific (C/C++ Development Tooling)
126 | .cproject
127 |
128 | # CDT- autotools
129 | .autotools
130 |
131 | # Java annotation processor (APT)
132 | .factorypath
133 |
134 | # PDT-specific (PHP Development Tools)
135 | .buildpath
136 |
137 | # sbteclipse plugin
138 | .target
139 |
140 | # Tern plugin
141 | .tern-project
142 |
143 | # TeXlipse plugin
144 | .texlipse
145 |
146 | # STS (Spring Tool Suite)
147 | .springBeans
148 |
149 | # Code Recommenders
150 | .recommenders/
151 |
152 | # Annotation Processing
153 | .apt_generated/
154 | .apt_generated_test/
155 |
156 | # Scala IDE specific (Scala & Java development for Eclipse)
157 | .cache-main
158 | .scala_dependencies
159 | .worksheet
160 |
161 | # Uncomment this line if you wish to ignore the project description file.
162 | # Typically, this file would be tracked if it contains build/dependency configurations:
163 | #.project
164 |
165 | ### Windows template
166 | # Windows thumbnail cache files
167 | Thumbs.db
168 | Thumbs.db:encryptable
169 | ehthumbs.db
170 | ehthumbs_vista.db
171 |
172 | # Dump file
173 | *.stackdump
174 |
175 | # Folder config file
176 | [Dd]esktop.ini
177 |
178 | # Recycle Bin used on file shares
179 | $RECYCLE.BIN/
180 |
181 | # Windows Installer files
182 | *.cab
183 | *.msi
184 | *.msix
185 | *.msm
186 | *.msp
187 |
188 | # Windows shortcuts
189 | *.lnk
190 |
191 | ### macOS template
192 | # General
193 | .DS_Store
194 | .AppleDouble
195 | .LSOverride
196 |
197 | # Icon must end with two \r
198 | Icon
199 |
200 | # Thumbnails
201 | ._*
202 |
203 | # Files that might appear in the root of a volume
204 | .DocumentRevisions-V100
205 | .fseventsd
206 | .Spotlight-V100
207 | .TemporaryItems
208 | .Trashes
209 | .VolumeIcon.icns
210 | .com.apple.timemachine.donotpresent
211 |
212 | # Directories potentially created on remote AFP share
213 | .AppleDB
214 | .AppleDesktop
215 | Network Trash Folder
216 | Temporary Items
217 | .apdisk
218 |
219 | ### Python template
220 | # Byte-compiled / optimized / DLL files
221 | __pycache__/
222 | *.py[cod]
223 | *$py.class
224 |
225 | # C extensions
226 | *.so
227 |
228 | # Distribution / packaging
229 | .Python
230 | build/
231 | develop-eggs/
232 | dist/
233 | downloads/
234 | eggs/
235 | .eggs/
236 | lib/
237 | lib64/
238 | parts/
239 | sdist/
240 | var/
241 | wheels/
242 | share/python-wheels/
243 | *.egg-info/
244 | .installed.cfg
245 | *.egg
246 | MANIFEST
247 |
248 | # PyInstaller
249 | # Usually these files are written by a python script from a template
250 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
251 | *.manifest
252 | *.spec
253 |
254 | # Installer logs
255 | pip-log.txt
256 | pip-delete-this-directory.txt
257 |
258 | # Unit test / coverage reports
259 | htmlcov/
260 | .tox/
261 | .nox/
262 | .coverage
263 | .coverage.*
264 | .cache
265 | nosetests.xml
266 | coverage.xml
267 | *.cover
268 | *.py,cover
269 | .hypothesis/
270 | .pytest_cache/
271 | cover/
272 |
273 | # Translations
274 | *.mo
275 | *.pot
276 |
277 | # Django stuff:
278 | *.log
279 | local_settings.py
280 | db.sqlite3
281 | db.sqlite3-journal
282 |
283 | # Flask stuff:
284 | instance/
285 | .webassets-cache
286 |
287 | # Scrapy stuff:
288 | .scrapy
289 |
290 | # Sphinx documentation
291 | docs/_build/
292 |
293 | # PyBuilder
294 | .pybuilder/
295 | target/
296 |
297 | # Jupyter Notebook
298 | .ipynb_checkpoints
299 |
300 | # IPython
301 | profile_default/
302 | ipython_config.py
303 |
304 | # pyenv
305 | # For a library or package, you might want to ignore these files since the code is
306 | # intended to run in multiple environments; otherwise, check them in:
307 | # .python-version
308 |
309 | # pipenv
310 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
311 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
312 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
313 | # install all needed dependencies.
314 | #Pipfile.lock
315 |
316 | # poetry
317 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
318 | # This is especially recommended for binary packages to ensure reproducibility, and is more
319 | # commonly ignored for libraries.
320 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
321 | #poetry.lock
322 |
323 | # pdm
324 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
325 | #pdm.lock
326 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
327 | # in version control.
328 | # https://pdm.fming.dev/#use-with-ide
329 | .pdm.toml
330 |
331 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
332 | __pypackages__/
333 |
334 | # Celery stuff
335 | celerybeat-schedule
336 | celerybeat.pid
337 |
338 | # SageMath parsed files
339 | *.sage.py
340 |
341 | # Environments
342 | .env
343 | .venv
344 | env/
345 | venv/
346 | ENV/
347 | env.bak/
348 | venv.bak/
349 |
350 | # Spyder project settings
351 | .spyderproject
352 | .spyproject
353 |
354 | # Rope project settings
355 | .ropeproject
356 |
357 | # mkdocs documentation
358 | /site
359 |
360 | # mypy
361 | .mypy_cache/
362 | .dmypy.json
363 | dmypy.json
364 |
365 | # Pyre type checker
366 | .pyre/
367 |
368 | # pytype static type analyzer
369 | .pytype/
370 |
371 | # Cython debug symbols
372 | cython_debug/
373 |
374 | # PyCharm
375 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
376 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
377 | # and can be added to the global gitignore or merged into this file. For a more nuclear
378 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
379 | .idea/
380 |
381 | # syno-videoinfo-plugin
382 | INFO
383 | .cache_*
384 | scrapeflows.conf
385 | configserver/authorization
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # *Syno*logy Video Info Plugin
2 |
3 | [](https://github.com/C5H12O5/syno-videoinfo-plugin/releases)
4 | 
5 | 
6 | 
7 | [](LICENSE)
8 |
9 | ###### 📖 English / 📖 [简体中文](README.zh-CN.md)
10 |
11 | This project is a video information plugin for Synology **Video Station**. It provides a way to fetch metadata from websites
12 | other than the default ones.
13 |
14 | * Implemented in Python without any third-party dependencies.
15 | * Supports multiple sources, and can be easily extended to support more.
16 | * Has a simple configuration page where you can customize your plugin.
17 |
18 | 
19 |
20 | ## Usage
21 |
22 | Install the plugin:
23 |
24 | 1. Download the latest release from [***here***](https://github.com/C5H12O5/syno-videoinfo-plugin/releases).
25 | 2. Open your **Video Station**, go to ***Settings*** > ***Video Info Plugin***.
26 | 3. Click **[Add]**, select the downloaded file, and click **[OK]**.
27 |
28 | Configure the plugin:
29 |
30 | 1. Open your browser, go to `http://[NAS_IP]:5125` (replace `[NAS_IP]` with your NAS IP address) page.
31 | 2. Change the configuration as you want, and click **[ :floppy_disk: ]** button in the upper right corner.
32 | 3. Go back to your **Video Station**, the configuration should be applied automatically.
33 | > [!NOTE]
34 | > If you upgrade the plugin but the configuration page is not updated, you can restart the configuration service by following steps:
35 | > 1. Open the configuration page, click **[Exit]** button in the upper right corner to close the service.
36 | > 2. Go back to **Video Station**, go to ***Settings*** > ***Video Info Plugin***, and click **[Test Connection]** button to restart the service.
37 |
38 | ## Requirements
39 |
40 | * Python 3.6+
41 | * Video Station 2.5.0+ for DSM 6.0
42 | * Video Station 3.0.0+ for DSM 7.0
43 |
44 | ## References
45 |
46 | * [The Video Station Metadata](https://kb.synology.com/en-id/DSM/help/VideoStation/metadata?version=7)
47 | * [The Video Station API documentation](https://download.synology.com/download/Document/Software/DeveloperGuide/Package/VideoStation/All/enu/Synology_Video_Station_API_enu.pdf)
48 |
49 | > Tips for naming video files:
50 | >
51 | > Movie:
52 | >
53 | > * Naming format: Movie_Name (Release_Year).ext
54 | > * Example: Avatar (2009).avi
55 | >
56 | > TV Show:
57 | > * Naming format: TV_Show_Name.SXX.EYY.ext (***S*** as a shorthand for ***Season*** and ***E*** for ***Episode***)
58 | > * Example: Gossip Girl.S03.E04.avi
59 |
60 | ## Development
61 |
62 | You can develop your own plugin based on this project easily. Here are the steps:
63 |
64 | 1. Clone this repository to your local machine:
65 |
66 | ```shell
67 | git clone https://github.com/C5H12O5/syno-videoinfo-plugin
68 | ```
69 |
70 | 2. Modify the code as you want, and test it like this:
71 |
72 | ```shell
73 | python main.py --type movie --input "{\"title\":\"{movie_title}\"}" --limit 1 --loglevel debug
74 | ```
75 |
76 | 3. Package the plugin using the following command:
77 |
78 | ```shell
79 | python setup.py sdist --formats=zip
80 | ```
81 |
82 | ## License
83 |
84 | [Apache-2.0 license](LICENSE)
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | # *Syno*logy Video Info Plugin
2 |
3 | [](https://github.com/C5H12O5/syno-videoinfo-plugin/releases)
4 | 
5 | 
6 | 
7 | [](LICENSE)
8 |
9 | ###### 📖 [English](README.md) / 📖 简体中文
10 |
11 | 本项目是群晖 **Video Station** 的第三方视频信息插件,它提供了一种从各大影视数据库平台获取视频元数据的方法。
12 |
13 | * 使用Python标准库实现,无需安装任何依赖。
14 | * 支持多个数据来源,并且可以轻松扩展。
15 | * 有简单的配置页面,可以自定义你的插件。
16 |
17 | 
18 |
19 | ## 使用说明
20 |
21 | 安装插件:
22 |
23 | 1. 从[***此处***](https://github.com/C5H12O5/syno-videoinfo-plugin/releases)下载最新版本。
24 | 2. 打开 **Video Station**,进入 ***设置*** > ***视频信息插件***。
25 | 3. 点击 **[新增]**,选择第一步下载的压缩包,然后点击 **[确定]**。
26 |
27 | 配置插件:
28 |
29 | 1. 打开你的浏览器,输入`http://[NAS_IP]:5125`(将`[NAS_IP]`替换为你的NAS的IP地址)打开配置页面。
30 | 2. 根据你的需要修改配置,然后点击右上角的 **[ :floppy_disk: ]** 按钮。
31 | 3. 返回你的 **Video Station**,保存的配置将会自动生效。
32 | > [!NOTE]
33 | > 如果升级了插件但是配置页面没有更新,可以通过以下步骤来重启配置服务:
34 | > 1. 打开配置页面,点击右上角的 **[Exit]** 按钮关闭配置服务。
35 | > 2. 返回 **Video Station**,进入 ***设置*** > ***视频信息插件***,点击 **[测试连接]** 按钮即可重新启动配置服务。
36 |
37 | ## 版本要求
38 |
39 | * Python 3.6+
40 | * Video Station 2.5.0+(DSM 6.0)
41 | * Video Station 3.0.0+(DSM 7.0)
42 |
43 | ## 参考文献
44 |
45 | * [视频元数据](https://kb.synology.cn/zh-cn/DSM/help/VideoStation/metadata?version=7)
46 | * [Video Station API 文档](https://download.synology.com/download/Document/Software/DeveloperGuide/Package/VideoStation/All/enu/Synology_Video_Station_API_enu.pdf)
47 |
48 | > 视频文件命名提示:
49 | >
50 | > 电影:
51 | >
52 | > * 命名格式:电影名称 (发行年份).ext
53 | > * 例如:Avatar (2009).avi
54 | >
55 | > 电视节目:
56 | > * 命名格式:电视节目名称.SXX.EYY.ext(***S*** 是 ***季数*** 的缩写,***E*** 是 ***集数*** 的缩写)
57 | > * 例如:Gossip Girl.S03.E04.avi
58 |
59 | ## 如何开发
60 |
61 | 您可以基于本项目并按以下步骤来开发自己的插件:
62 |
63 | 1. 将本项目克隆到本地:
64 |
65 | ```shell
66 | git clone https://github.com/C5H12O5/syno-videoinfo-plugin
67 | ```
68 |
69 | 2. 根据需要修改代码,并可以使用以下命令进行测试:
70 |
71 | ```shell
72 | python main.py --type movie --input "{\"title\":\"{movie_title}\"}" --limit 1 --loglevel debug
73 | ```
74 |
75 | 3. 然后可以使用以下命令进行打包并上传使用:
76 |
77 | ```shell
78 | python setup.py sdist --formats=zip
79 | ```
80 |
81 | ## 使用许可
82 |
83 | [Apache-2.0 license](LICENSE)
--------------------------------------------------------------------------------
/configserver/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/C5H12O5/syno-videoinfo-plugin/27cc22b763343e85ea64fc5f5e34d078b8a7ab68/configserver/__init__.py
--------------------------------------------------------------------------------
/configserver/server.py:
--------------------------------------------------------------------------------
1 | """A simple HTTP server for configuration."""
2 | import ast
3 | import http
4 | import json
5 | import string
6 | import sys
7 | from http.server import HTTPServer
8 | from pathlib import Path
9 |
10 | HOST = "0.0.0.0"
11 | PORT = 5125
12 |
13 | # define the base directory
14 | _basedir = Path(__file__).resolve().parent
15 |
16 | # define the configuration files
17 | _resolvers_conf = _basedir / "../resolvers.conf"
18 | _flows_conf = _basedir / "../scrapeflows.conf"
19 | _auth_conf = _basedir / "authorization"
20 |
21 | # initialize the templates
22 | with open(_basedir / "templates/config.html", "r", encoding="utf-8") as html:
23 | _config_tmpl = string.Template(html.read())
24 | with open(_basedir / "templates/source.html", "r", encoding="utf-8") as html:
25 | _source_tmpl = string.Template(html.read())
26 | with open(_basedir / "templates/index.html", "r", encoding="utf-8") as html:
27 | _index_tmpl = string.Template(html.read())
28 |
29 |
30 | def render_index(saved=None):
31 | """Render the index page."""
32 | source_html = ""
33 | sites = load_sites()
34 | for site, site_conf in sites.items():
35 | saved_conf = saved.get(site) if saved is not None else None
36 | config_html = render_config(site, site_conf, saved_conf)
37 | types = site_conf["types"]
38 | doh_enabled = site_conf["doh_enabled"]
39 | source = {
40 | "site": site,
41 | "movie": "selected" if "movie" in types else "disabled",
42 | "tvshow": "selected" if "tvshow" in types else "disabled",
43 | "doh_enabled": "selected" if doh_enabled else "",
44 | "doh_disabled": "selected" if not doh_enabled else "",
45 | "priority": len(sites),
46 | "config": config_html,
47 | }
48 | if saved_conf is not None:
49 | saved_types = saved_conf["types"]
50 | saved_doh = saved_conf["doh"]
51 | source["movie"] = "selected" if "movie" in saved_types else ""
52 | source["tvshow"] = "selected" if "tvshow" in saved_types else ""
53 | source["doh_enabled"] = "selected" if saved_doh else ""
54 | source["doh_disabled"] = "selected" if not saved_doh else ""
55 | source["priority"] = saved_conf["priority"]
56 | source_html += _source_tmpl.substitute(source)
57 |
58 | return _index_tmpl.substitute(
59 | sources=source_html, resolvers=load_resolvers(), version=load_version()
60 | )
61 |
62 |
63 | def render_config(site, site_conf, saved_conf):
64 | """Render the configuration for a site."""
65 | config_html = ""
66 | config = site_conf.get("config")
67 | if config is not None:
68 | for key, option in config.items():
69 | value = saved_conf.get(key, "") if saved_conf is not None else ""
70 | mapping = {"site": site, "key": key, "value": value}
71 | mapping.update(option)
72 | config_html += _config_tmpl.substitute(mapping)
73 | return config_html
74 |
75 |
76 | def load_sites():
77 | """Load the list of sites and types from flow definitions."""
78 | sites = {}
79 | for filepath in (_basedir / "../scrapeflows").glob("*.json"):
80 | with open(filepath, "r", encoding="utf-8") as def_reader:
81 | flowdef = json.load(def_reader)
82 | site = flowdef["site"]
83 | site_conf = sites.get(site, {})
84 | site_conf["doh_enabled"] = flowdef.get("doh_enabled", False)
85 |
86 | # aggregate types
87 | type_ = flowdef["type"].split("_", 1)[0]
88 | types = site_conf.get("types", [])
89 | if type_ not in types:
90 | types.append(type_)
91 | site_conf["types"] = types
92 |
93 | # aggregate config
94 | if "config" in flowdef:
95 | config = site_conf.get("config", {})
96 | config.update(flowdef["config"])
97 | site_conf["config"] = config
98 |
99 | sites[site] = site_conf
100 |
101 | return dict(sorted(sites.items(), key=lambda x: x[0]))
102 |
103 |
104 | def load_resolvers():
105 | """Load the list of DoH resolvers."""
106 | with open(_resolvers_conf, "r", encoding="utf-8") as doh_reader:
107 | return ast.literal_eval(doh_reader.read())
108 |
109 |
110 | def load_version():
111 | """Load the plugin version from the directory name."""
112 | dir_name = _basedir.parent.name
113 | if "-" in dir_name:
114 | version = dir_name.split("-")[-1]
115 | if version != "plugin":
116 | return f"v{version}"
117 | return ""
118 |
119 |
120 | # initialize the index page
121 | _index_html = render_index()
122 |
123 |
124 | class RequestHandler(http.server.SimpleHTTPRequestHandler):
125 | """Request handler for the HTTP server."""
126 |
127 | def do_AUTH(self):
128 | if not _auth_conf.exists():
129 | return True
130 |
131 | with open(_auth_conf, "r", encoding="utf-8") as auth_reader:
132 | saved_auth = auth_reader.read()
133 |
134 | if self.headers.get("Authorization") is not None:
135 | auth_header = self.headers.get("Authorization")
136 | if auth_header.split("Basic ")[1] == saved_auth:
137 | return True
138 |
139 | self.send_response(401)
140 | self.send_header("WWW-Authenticate", 'Basic realm="Login Required"')
141 | self.send_header("Content-type", "text/html")
142 | self.end_headers()
143 | self.wfile.write(b"Unauthorized")
144 | return False
145 |
146 | def do_GET(self):
147 | if not self.do_AUTH():
148 | return
149 |
150 | self.send_response(200)
151 | self.send_header("Content-type", "text/html")
152 | self.end_headers()
153 |
154 | if self.path == "/":
155 | # index page
156 | if _flows_conf.exists():
157 | with open(_flows_conf, "r", encoding="utf-8") as conf_reader:
158 | saved_conf = json.load(conf_reader)
159 | self.wfile.write(render_index(saved_conf).encode("utf-8"))
160 | else:
161 | self.wfile.write(_index_html.encode("utf-8"))
162 |
163 | elif self.path == "/exit":
164 | # close the server
165 | self.server.server_close()
166 | sys.exit()
167 |
168 | def do_POST(self):
169 | if not self.do_AUTH():
170 | return
171 |
172 | self.send_response(200)
173 | self.end_headers()
174 | content_length = int(self.headers["Content-Length"])
175 | request_body = self.rfile.read(content_length)
176 |
177 | if self.path == "/save":
178 | # save the configuration
179 | conf = json.loads(request_body.decode("utf-8"))
180 | with open(_flows_conf, "w", encoding="utf-8") as conf_writer:
181 | conf_writer.write(json.dumps(
182 | conf["flows"], ensure_ascii=False, indent=2
183 | ))
184 | with open(_resolvers_conf, "w", encoding="utf-8") as doh_writer:
185 | doh_writer.write(json.dumps(
186 | conf["resolvers"], ensure_ascii=False, indent=2
187 | ))
188 |
189 | elif self.path == "/auth":
190 | # save the authorization
191 | with open(_auth_conf, "w", encoding="utf-8") as auth_writer:
192 | auth_writer.write(request_body.decode("utf-8"))
193 |
194 |
195 | if __name__ == "__main__":
196 | httpd = HTTPServer((HOST, PORT), RequestHandler)
197 | httpd.serve_forever()
198 |
--------------------------------------------------------------------------------
/configserver/templates/config.html:
--------------------------------------------------------------------------------
1 |