├── LICENSE
├── README.md
├── en
└── plex-scanner
│ ├── config.ini
│ ├── plex-scanner.py
│ ├── start.bat
│ └── start.command
└── zh
└── plex-scanner
├── config.ini
├── plex-scanner.py
├── start.bat
└── start.command
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 x1ao4
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # plex-scanner
2 | 由于 Plex 原生不支持挂载目录的自动扫描和局部扫描,我们在媒体库管理来自挂载目录的媒体文件时不是很方便,新增的文件并不会自动入库,我们常常需要通过手动扫描/刷新或者定期扫描才能让来自挂载目录的新增文件入库,这对网盘用户来说是非常不方便的。
3 |
4 | 虽然已经存在一些手段可以间接、变相的实现类似于自动扫描、局部扫描的功能,比如 CloudDrive 的文件变更通知,或者其他定时遍历或扫描指定目录的脚本,但可能也会存在一定的局限性。所以我还是自己写了一个脚本,可以用更快捷、更便利、更灵活的方式,通过手动输入文件夹名称来快速实现局部扫描。
5 |
6 | 当你的网盘更新了电影文件时,你在 Plex 内能实现的最小范围的扫描是扫描影片所在库的所有目录,也就是点击对应库的「扫描资料库文件」;当你的网盘剧集有更新时,你在 Plex 内能实现的最小范围的扫描是扫描该剧对应的所有目录,也就是点击该剧的「刷新元数据」。
7 |
8 | 而通过 plex-scanner,可以将扫描范围缩小到新增文件所在的文件夹,也就是可以实现只扫描新增的电影文件所在的文件夹,或者只扫描新增的剧集文件所在的季的文件夹等等,从而实现挂载目录的局部扫描。使用 plex-scanner 可以对任何类型的库的任何目录执行局部扫描。
9 |
10 | ## 示例
11 | 运行 plex-scanner 后按照提示输入要扫描的文件夹名称即可,若需要扫描多个文件夹,可以分次输入,或使用 `;` 隔开多个文件夹名称,支持多级目录。
12 | ```
13 | 请输入要扫描的文件夹名称,多个文件夹名称用分号隔开:彗星来的那一夜 (2013)
14 |
15 | 成功触发Plex扫描文件夹:/Users/x1ao4/Media/阿里资源主/影视/电影/彗星来的那一夜 (2013)
16 |
17 | 请输入要扫描的文件夹名称,多个文件夹名称用分号隔开:兰戈 (2011);洛基 (2021)/洛基 - S02;花儿与少年 (2014)
18 |
19 | 成功触发Plex扫描文件夹:/Users/x1ao4/Media/阿里资源主/影视/电影/兰戈 (2011)
20 | 成功触发Plex扫描文件夹:/Users/x1ao4/Media/阿里资源主/影视/电视剧/洛基 (2021)/洛基 - S02
21 | 成功触发Plex扫描文件夹:/Users/x1ao4/Media/阿里资源主/影视/综艺/花儿与少年 (2014)
22 |
23 | 请输入要扫描的文件夹名称,多个文件夹名称用分号隔开:周六夜现场 (1975)/周六夜现场 - S49;爱乐之城 (2016)
24 |
25 | 成功触发Plex扫描文件夹:/Users/x1ao4/Media/阿里资源主/影视/综艺/周六夜现场 (1975)/周六夜现场 - S49
26 | 成功触发Plex扫描文件夹:/Users/x1ao4/Media/阿里资源主/影视/电影/爱乐之城 (2016)
27 | ```
28 |
29 | ## 运行条件
30 | - 安装了 Python 3.0 或更高版本。
31 | - 安装了必要的第三方库:requests。(可以通过 `pip3 install requests` 安装)
32 |
33 | ## 配置文件
34 | 在运行脚本前,请先打开配置文件 `config.ini`,参照以下提示(示例)进行配置。
35 | ```
36 | [server]
37 | # Plex 服务器的地址,格式为 http://服务器 IP 地址:32400 或 http(s)://域名:端口号
38 | address = http://127.0.0.1:32400
39 | # Plex 服务器的 token,用于身份验证
40 | token = xxxxxxxxxxxxxxxxxxxx
41 |
42 | [mode]
43 | # 连续扫描模式的开关,如果设置为 True,可以连续多次请求扫描;如果设置为 False,则会在处理请求后结束运行
44 | continuous_mode = True
45 | # 本机模式的开关,当脚本与 Plex 服务器在相同设备/系统下运行时,设置为 True;在不同设备/系统下运行时,设置为 False
46 | local_mode = True
47 |
48 | [directories]
49 | # 指定需要进行扫描的文件夹的上级目录,格式为 库名 = 目录1;目录2;目录3
50 | 电影 = /Users/x1ao4/Media/阿里资源主/影视/电影
51 | 电视剧 = /Users/x1ao4/Media/阿里资源主/影视/电视剧;/Users/x1ao4/Media/迅雷云盘/电视剧
52 | 综艺 = /Users/x1ao4/Media/阿里资源主/影视/综艺
53 |
54 | [libraries]
55 | # 指定需要进行扫描的文件夹所在的库,格式为 库名1;库名2;库名3,如果没有设置此项且 [directories] 为空,则会默认需要进行扫描的文件夹可能存在于任何库中
56 | libraries = 电影;电视剧;综艺
57 |
58 | [exclude_directories]
59 | # 指定需要排除的上级目录,格式为 库名 = 目录1;目录2;目录3,如果设置了此项,则会在扫描时忽略这些目录下的文件夹
60 | 电影 = /Users/x1ao4/Media/夸克主盘/电影;/Users/x1ao4/Media/天翼云盘/电影
61 | 综艺 = /Users/x1ao4/Media/阿里资源副/影视/综艺
62 | ```
63 | 配置文件中只有 `[server]` 和 `[mode]` 是必填项目,其他项目请按需设置,可以留空。
64 |
65 | ## 工作原理
66 | plex-scanner 的工作原理是通过配置文件中的 `[directories]`、`[libraries]` 和 `[exclude_directories]` 筛选出目录前缀,然后加上用户提供的 `文件夹名称` 构建出需要进行扫描的文件夹的可能路径,然后通过检查这些路径是否存在(当 `local_mode = True` 时),筛选出需要扫描的文件夹的真实路径并进行扫描。
67 |
68 | 例如当配置如下时:
69 | ```
70 | [mode]
71 | continuous_mode = True
72 | local_mode = True
73 |
74 | [directories]
75 | 电影 = /Users/x1ao4/Media/阿里资源主/影视/电影
76 | 电视剧 = /Users/x1ao4/Media/阿里资源主/影视/电视剧;/Users/x1ao4/Media/迅雷云盘/电视剧
77 | 综艺 = /Users/x1ao4/Media/阿里资源主/影视/综艺
78 |
79 | [libraries]
80 | libraries =
81 |
82 | [exclude_directories]
83 |
84 | ```
85 | 假如用户输入的文件夹名称为 `乱世佳人 (1939)`,那么脚本会在后台构建出如下目录:
86 | ```
87 | /Users/x1ao4/Media/阿里资源主/影视/电影/乱世佳人 (1939)
88 | /Users/x1ao4/Media/阿里资源主/影视/电视剧/乱世佳人 (1939)
89 | /Users/x1ao4/Media/迅雷云盘/电视剧/乱世佳人 (1939)
90 | /Users/x1ao4/Media/阿里资源主/影视/综艺/乱世佳人 (1939)
91 | ```
92 | 然后排除不存在的目录,筛选出真实存在的文件夹路径 `/Users/x1ao4/Media/阿里资源主/影视/电影/乱世佳人 (1939)` 进行扫描。若 `local_mode = False` 则会扫描这四个文件夹,虽然另外三个文件夹并不存在。
93 |
94 | plex-scanner 提供了两种设置目录前缀的方式:`[directories]` 和 `[libraries]`。其实这两个选项就是用来设置更新文件可能存在的目录范围的,选其一进行配置即可。
95 |
96 | - `[directories]`:假如你需要扫描的文件都集中在某几个目录内,可以使用 `[directories]` 来指定这些目录,把 `[libraries]` 和 `[exclude_directories]` 留空。
97 | - `[libraries]`:假如你需要扫描的文件比较分散,他们所属的目录比较多,可以使用 `[libraries]` 来指定库,脚本会自动获取这些库的所有目录(若 `[libraries]` 为空则会获取所有库的所有目录),然后再使用 `[exclude_directories]` 排除掉不需要手动扫描的目录,把 `[directories]` 留空。
98 |
99 | 简单说就是需要手动扫描的目录较少可以选择配置 `[directories]`,较多可以选择配置 `[libraries]` 和 `[exclude_directories]`,若这三个选项全部留空(默认设置),表示会使用服务器上的所有库的所有目录作为目录前缀,然后与用户提供的文件夹名称分别进行配对,找出需要被扫描的文件夹进行扫描。
100 |
101 | ### 本机模式
102 | 你需要注意一下 `local_mode` 这个配置选项,他会影响脚本的工作方式。脚本中有一段代码的功能是检查文件夹是否存在,这就要求脚本可以访问到 Plex 媒体库文件的存储目录,也就是说只有在安装 Plex 服务器的设备或系统上运行 plex-scanner 才能正确判断文件夹是否存在;若脚本无法访问媒体文件的存储位置,就会将所有文件夹路径视为不存在,不会进行扫描。所以你需要根据使用环境设置正确的模式。
103 |
104 | - `local_mode = True`:当脚本与 Plex 服务器在同一设备上运行并且可以访问到媒体文件的存储目录时,请启用本机模式,在该模式下,脚本将排除不存在的目录,只扫描确实存在的文件夹。
105 | - `local_mode = False`:当脚本与 Plex 服务器在不同的设备或系统上运行并且无法访问媒体文件的存储目录时,请关闭本机模式,此时脚本将对构建的所有目录进行扫描,不论该文件夹是否存在。
106 |
107 | 不论哪种模式,都不会扫描不应该被扫描的文件,如果脚本扫描了不存在的文件夹,除了会在「警告」中显示一条扫描记录以外,唯一的不足是可能会触发对应的库对部分项目执行刷新元数据的操作。
108 |
109 | 配置时需要填写的目录也就是你在媒体库添加文件夹时使用的目录,库名也要与 Plex 内一致,例如:
110 |
111 |
112 |
113 | ## 使用方法
114 | 1. 将仓库克隆或下载到计算机上的一个目录中。
115 | 2. 修改 `start.command (Mac)` 或 `start.bat (Win)` 中的路径,以指向你存放 `plex-scanner.py` 脚本的目录。
116 | 3. 打开 `config.ini`,填写你的 Plex 服务器地址(`address`)和 [X-Plex-Token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)(`token`),按照需要选填其他配置选项。
117 | 4. 双击运行 `start.command` 或 `start.bat` 脚本以执行 `plex-scanner.py` 脚本。
118 | 5. 按照提示输入 `文件夹名称`,按回车。
119 | 6. 脚本会根据配置文件触发 Plex 扫描对应的文件夹,并在控制台显示扫描结果(你也可以在服务器的「设置 - 状态 - 警告」中查看扫描记录)。若没有返回扫描结果则表示扫描失败,请检查你输入的文件夹名称或配置范围是否有误。
120 |
121 | ## 注意事项
122 | - 请确保你提供了正确的 Plex 服务器地址和 X-Plex-Token。
123 | - 请确保你提供了正确的库名、目录与文件夹名。
124 | - 请确保运行脚本的设备可以连接到你的服务器。
125 | - 脚本在某些情况下会触发媒体库刷新其他项目的元数据,但并不会触发非指定文件夹的扫描动作。
126 | - 在输入文件夹名称时若使用了删除键,会导致无法正确识别文件夹名称,请确保输入过程中不要输错字符,最好直接复制粘贴。
127 |
128 |
129 | # plex-scanner
130 | Due to Plex's native lack of support for automatic and partial scanning of mounted cloud storage, WebDAV, and other non-local storage solutions on local systems or devices, it’s inconvenient for us to manage media files from mounted directories in our media library. New files will not be automatically added to the library. We often need to manually scan/refresh or schedule scans to add new files from mounted directories to the library, which is very inconvenient for cloud drive users.
131 |
132 | Although there are already some means to indirectly achieve functions similar to automatic scanning and partial scanning, such as CloudDrive’s file change notifications, or other scripts that periodically traverse or scan specified directories, there may also be certain limitations. So, I wrote a script myself that can quickly implement partial scanning in a more convenient, flexible, and faster way by manually entering the folder name.
133 |
134 | When your cloud drive updates a movie file, the smallest range of scanning you can achieve in Plex is to scan all directories in the library where the movie is located, that is, click on “Scan Library Files” of the corresponding library. When your cloud drive series is updated, the smallest range of scanning you can achieve in Plex is to scan all directories corresponding to the series, that is, click on “Refresh Metadata” of the series.
135 |
136 | Through plex-scanner, the scanning range can be reduced to the folder where the new file is located, that is, it can only scan the folder where the new movie file is located, or only scan the folder of the season where the new episode file is located, etc., thereby achieving partial scanning of the mounted directory. plex-scanner can perform partial scanning on any directory of any type of library.
137 |
138 | ## Example
139 | After running plex-scanner, follow the prompts to enter the name of the folder you want to scan. If you need to scan multiple folders, you can enter them one by one, or use `;` to separate multiple folder names, supporting multi-level directories.
140 | ```
141 | Please enter the folder name(s) you want to scan, separating multiple names with semicolons: Coherence (2013)
142 |
143 | Successfully triggered Plex to scan the folder: /Users/x1ao4/Media/Ali/Movies/Coherence (2013)
144 |
145 | Please enter the folder name(s) you want to scan, separating multiple names with semicolons: Rango (2011);Loki (2021)/Loki - S02;DIVAS Hit The Road (2014)
146 |
147 | Successfully triggered Plex to scan the folder: /Users/x1ao4/Media/Ali/Movies/Rango (2011)
148 | Successfully triggered Plex to scan the folder: /Users/x1ao4/Media/Ali/TV/Loki (2021)/Loki - S02
149 | Successfully triggered Plex to scan the folder: /Users/x1ao4/Media/Ali/Variety/DIVAS Hit The Road (2014)
150 |
151 | Please enter the folder name(s) you want to scan, separating multiple names with semicolons: Saturday Night Live (1975)/Saturday Night Live - S49;La La Land (2016)
152 |
153 | Successfully triggered Plex to scan the folder: /Users/x1ao4/Media/Ali/Variety/Saturday Night Live (1975)/Saturday Night Live - S49
154 | Successfully triggered Plex to scan the folder: /Users/x1ao4/Media/Ali/Movies/La La Land (2016)
155 | ```
156 |
157 | ## Requirements
158 | - Installed Python 3.0 or higher.
159 | - Installed required third-party library: requests. (Install with `pip3 install requests`)
160 |
161 | ## Config
162 | Before running the script, please open the configuration file `config.ini` and configure it according to the following prompts (examples).
163 | ```
164 | [server]
165 | # Address of the Plex server, formatted as http://server IP address:32400 or http(s)://domain:port
166 | address = http://127.0.0.1:32400
167 | # Token of the Plex server for authentication
168 | token = xxxxxxxxxxxxxxxxxxxx
169 |
170 | [mode]
171 | # Switch for continuous scanning mode, if set to True, multiple scan requests can be made continuously; if set to False, the script will end after processing the request
172 | continuous_mode = True
173 | # Switch for local mode, set to True when the script and Plex server run on the same device/system; set to False when they run on different devices/systems
174 | local_mode = True
175 |
176 | [directories]
177 | # Specify the parent directories of the folders to be scanned, formatted as LibraryName = Directory1;Directory2;Directory3
178 | Movies = /Users/x1ao4/Media/Ali/Movies
179 | TV = /Users/x1ao4/Media/Ali/TV;/Users/x1ao4/Media/Xunlei/TV
180 | Variety = /Users/x1ao4/Media/Ali/Variety
181 |
182 | [libraries]
183 | # Specify the libraries where the folders to be scanned are located, formatted as LibraryName1;LibraryName2;LibraryName3. If this item is not set and [directories] is empty, it will default that the folders to be scanned may be located in any library
184 | libraries = Movies;TV;Variety
185 |
186 | [exclude_directories]
187 | # Specify the parent directories to be excluded, formatted as LibraryName = Directory1;Directory2;Directory3. If this item is set, the folders under these directories will be ignored during scanning
188 | Movies = /Users/x1ao4/Media/Quark/Movies;/Users/x1ao4/Media/Baidu/Movies
189 | Variety = /Users/x1ao4/Media/PikPak/Variety
190 | ```
191 | Only `[server]` and `[mode]` are required items in the configuration file, other items can be set as needed, or left blank.
192 |
193 | ## How the Script Works
194 | plex-scanner works by using the `[directories]`, `[libraries]`, and `[exclude_directories]` in the configuration file to filter out directory prefixes. It then combines these with the `folder name` provided by the user to construct potential paths for the folders that need to be scanned. By checking whether these paths exist (when `local_mode = True`), it filters out the actual paths of the folders that need to be scanned and performs the scanning.
195 |
196 | For example, when the configuration is as follows:
197 | ```
198 | [mode]
199 | continuous_mode = True
200 | local_mode = True
201 |
202 | [directories]
203 | Movies = /Users/x1ao4/Media/Ali/Movies
204 | TV = /Users/x1ao4/Media/Ali/TV;/Users/x1ao4/Media/Xunlei/TV
205 | Variety = /Users/x1ao4/Media/Ali/Variety
206 |
207 | [libraries]
208 | libraries =
209 |
210 | [exclude_directories]
211 |
212 | ```
213 | If the user enters the folder name `Gone with the Wind (1939)`, the script will build the following directories in the background:
214 | ```
215 | /Users/x1ao4/Media/Ali/Movies/Gone with the Wind (1939)
216 | /Users/x1ao4/Media/Ali/TV/Gone with the Wind (1939)
217 | /Users/x1ao4/Media/Xunlei/TV/Gone with the Wind (1939)
218 | /Users/x1ao4/Media/Ali/Variety/Gone with the Wind (1939)
219 | ```
220 | Then exclude directories that do not exist, filter out the real existing folder path `/Users/x1ao4/Media/Ali/Movies/Gone with the Wind (1939)` for scanning.
221 | If `local_mode = False`, it will scan these four folders, even though the other three folders do not exist.
222 |
223 | plex-scanner provides two ways to set the directory prefix: `[directories]` and `[libraries]`. In fact, these two options are used to set the directory range where the updated files may exist, and you can choose one to configure.
224 |
225 | - `[directories]`: If the files you need to scan are concentrated in a few directories, you can use `[directories]` to specify these directories, leaving `[libraries]` and `[exclude_directories]` blank.
226 | - `[libraries]`: If the files you need to scan are more scattered and belong to many directories, you can use `[libraries]` to specify libraries. The script will automatically obtain all directories of these libraries (if `[libraries]` is empty, it will obtain all directories of all libraries), and then use `[exclude_directories]` to exclude directories that do not need manual scanning, leaving `[directories]` blank.
227 |
228 | In simple terms, if there are fewer directories that need manual scanning, you can choose to configure `[directories]`; if there are more directories, you can choose to configure `[libraries]` and `[exclude_directories]`. If all three options are left blank (default settings), it means that all directories of all libraries on the server will be used as directory prefixes, and then matched with the folder names provided by the user to find out the folders that need to be scanned.
229 |
230 | ### Local Mode
231 | `local_mode` is a setting that changes how this script behaves. There’s a part of the script that checks if a folder exists. This means that the script needs to be able to access the location where your Plex media files are stored. So, if the script and the Plex server are running on the same device, it can correctly check if a folder exists. But if the script can’t access the media file storage location, it will consider all folder paths as non-existent and won’t perform any scanning. So, you need to set the right mode based on your setup.
232 |
233 | - `local_mode = True`: Enable this mode when the script and Plex server run on the same device, and the script have access to the storage directory of media files. In this mode, the script skips non-existent folders and only scans those that truly exist.
234 | - `local_mode = False`: Disable this mode when the script and Plex server run on different devices or systems, or the script cannot access the storage directory of media files. In this scenario, the script scans all constructed directories, irrespective of whether the folders exist or not.
235 |
236 | No matter which mode you’re in, the script won’t scan files that shouldn’t be scanned. If the script scans a non-existent folder, the only downside is that it might trigger the corresponding library to refresh the metadata of some items.
237 |
238 | The directory to be filled in during configuration is the directory you use when adding folders to your media library. The library name should also match the one in Plex. For example:
239 |
240 |
241 |
242 | ## Usage
243 | 1. Clone or download the repository to a directory on your computer.
244 | 2. Modify the path in `start.command (Mac)` or `start.bat (Win)` to point to the directory where you store the `plex-scanner.py` script.
245 | 3. Open `config.ini`, fill in your Plex server address (`address`) and [X-Plex-Token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) (`token`), and fill in other configuration options as needed.
246 | 4. Double-click `start.command` or `start.bat` to execute the `plex-scanner.py` script.
247 | 5. Follow the prompts to enter the `folder name` and press Enter.
248 | 6. The script will trigger Plex to scan the corresponding folder according to the configuration file and display the scanning result in the console (you can also view the scanning record in the server’s “Settings - Status - Alerts”). If there is no return scanning result, it means that the scanning failed. Please check whether your input folder name or configuration range is incorrect.
249 |
250 | ## Notes
251 | - Make sure you've provided the correct Plex server address and X-Plex-Token.
252 | - Make sure you have provided the correct library names, directories, and folder names.
253 | - Make sure the device running the script can connect to your server.
254 | - The script may trigger the media library to refresh other items’ metadata in some cases, but it will not trigger the scanning action of non-specified folders.
255 | - Using the delete key while entering folder names will cause the folder names to not be recognized correctly. Please make sure not to input wrong characters during the input process, preferably copy and paste directly.
256 |
257 |
258 |
--------------------------------------------------------------------------------
/en/plex-scanner/config.ini:
--------------------------------------------------------------------------------
1 | [server]
2 | address = http://127.0.0.1:32400
3 | token =
4 |
5 | [mode]
6 | continuous_mode = True
7 | local_mode = True
8 |
9 | [directories]
10 |
11 |
12 | [libraries]
13 | libraries =
14 |
15 | [exclude_directories]
16 |
17 |
18 |
--------------------------------------------------------------------------------
/en/plex-scanner/plex-scanner.py:
--------------------------------------------------------------------------------
1 | import os
2 | import requests
3 | import configparser
4 | import xml.etree.ElementTree as ET
5 | from pathlib import Path
6 |
7 | # Define the configuration file path
8 | config_file: Path = Path(__file__).parent / 'config.ini'
9 |
10 | # Read the configuration file
11 | config = configparser.ConfigParser()
12 | config.read(config_file)
13 |
14 | # Get the server address and token
15 | plex_server = config.get('server', 'address')
16 | plex_token = config.get('server', 'token')
17 |
18 | # Get Running Mode
19 | continuous_mode = config.getboolean('mode', 'continuous_mode')
20 | local_mode = config.getboolean('mode', 'local_mode')
21 |
22 | # Check if [directories] exists
23 | if config.has_section('directories'):
24 | user_directories = {k: v.split(';') for k, v in config.items('directories')}
25 | else:
26 | user_directories = None
27 |
28 | # Get library directories
29 | libraries_value = config.get('libraries', 'libraries').strip()
30 | if libraries_value and not user_directories:
31 | # Get the directories of the specified libraries
32 | response = requests.get(f"{plex_server}/library/sections?X-Plex-Token={plex_token}")
33 | root = ET.fromstring(response.content)
34 | directories = root.findall('Directory')
35 | user_libraries = {directory.get('title'): [location.get('path') for location in directory.findall('Location')] for directory in directories if directory.get('title') in libraries_value.split(';')}
36 | elif not user_directories:
37 | # Get the directories of all libraries
38 | response = requests.get(f"{plex_server}/library/sections?X-Plex-Token={plex_token}")
39 | root = ET.fromstring(response.content)
40 | directories = root.findall('Directory')
41 | user_libraries = {directory.get('title'): [location.get('path') for location in directory.findall('Location')] for directory in directories}
42 | else:
43 | user_libraries = {}
44 |
45 | # Get candidate directories
46 | if not user_directories:
47 | candidate_directories = user_libraries
48 | else:
49 | candidate_directories = user_directories
50 |
51 | # Exclude the directories that the user needs to exclude through [exclude_directories]
52 | if config.has_section('exclude_directories'):
53 | exclude_directories = {k: v.split(';') for k, v in config.items('exclude_directories')}
54 | final_directories = {}
55 | for lib, folders in candidate_directories.items():
56 | if lib in exclude_directories:
57 | final_directories[lib] = [folder for folder in folders if folder not in exclude_directories[lib]]
58 | else:
59 | final_directories[lib] = folders
60 |
61 | # Get library IDs
62 | response = requests.get(f"{plex_server}/library/sections?X-Plex-Token={plex_token}")
63 | root = ET.fromstring(response.content)
64 | directories = root.findall('Directory')
65 | library_ids = {directory.get('title'): directory.get('key') for directory in directories if directory.get('title') in final_directories}
66 |
67 | def refresh_plex_folder(folder_name):
68 | # Add two new attributes to track whether it is the first successful or failed scan
69 | if not hasattr(refresh_plex_folder, "first_success"):
70 | refresh_plex_folder.first_success = True
71 | if not hasattr(refresh_plex_folder, "first_failure"):
72 | refresh_plex_folder.first_failure = True
73 |
74 | # Construct the full folder path and trigger Plex scan
75 | for library, folder_prefixes in final_directories.items():
76 | for folder_prefix in folder_prefixes:
77 | folder_path = os.path.join(folder_prefix, folder_name)
78 |
79 | # When local mode is enabled, check if the folder exists
80 | if local_mode and not os.path.isdir(folder_path):
81 | continue
82 |
83 | # Construct the request URL
84 | library_id = library_ids[library]
85 | url = f"{plex_server}/library/sections/{library_id}/refresh?path={folder_path}&X-Plex-Token={plex_token}"
86 |
87 | # Send the request
88 | response = requests.get(url)
89 |
90 | # Check the response status
91 | if response.status_code == 200:
92 | # If this is the first successful scan, print a newline
93 | if refresh_plex_folder.first_success:
94 | print()
95 | refresh_plex_folder.first_success = False
96 | print(f"Successfully triggered Plex to scan the folder: {folder_path}")
97 | else:
98 | # If this is the first failed scan, print a newline
99 | if refresh_plex_folder.first_failure:
100 | print()
101 | refresh_plex_folder.first_failure = False
102 | print(f"Failed to trigger Plex to scan the folder, status code: {response.status_code}")
103 |
104 | while True:
105 | # User enters the folder name
106 | folder_names = input("\nPlease enter the folder name(s) you want to scan, separating multiple names with semicolons: ").split(';')
107 |
108 | # Reset first_success and first_failure attributes
109 | refresh_plex_folder.first_success = True
110 | refresh_plex_folder.first_failure = True
111 |
112 | # Trigger Plex scan
113 | for folder_name in folder_names:
114 | refresh_plex_folder(folder_name.strip())
115 |
116 | # If the continuous mode is off, end the run after the scan is complete
117 | if not continuous_mode:
118 | break
119 |
--------------------------------------------------------------------------------
/en/plex-scanner/start.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | cd C:\path\to\plex-scanner
4 |
5 | python3 plex-scanner.py
6 |
7 | pause
8 |
--------------------------------------------------------------------------------
/en/plex-scanner/start.command:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd /path/to/plex-scanner
4 |
5 | python3 plex-scanner.py
--------------------------------------------------------------------------------
/zh/plex-scanner/config.ini:
--------------------------------------------------------------------------------
1 | [server]
2 | address = http://127.0.0.1:32400
3 | token =
4 |
5 | [mode]
6 | continuous_mode = True
7 | local_mode = True
8 |
9 | [directories]
10 |
11 |
12 | [libraries]
13 | libraries =
14 |
15 | [exclude_directories]
16 |
17 |
18 |
--------------------------------------------------------------------------------
/zh/plex-scanner/plex-scanner.py:
--------------------------------------------------------------------------------
1 | import os
2 | import requests
3 | import configparser
4 | import xml.etree.ElementTree as ET
5 | from pathlib import Path
6 |
7 | # 定义配置文件路径
8 | config_file: Path = Path(__file__).parent / 'config.ini'
9 |
10 | # 读取配置文件
11 | config = configparser.ConfigParser()
12 | config.read(config_file)
13 |
14 | # 获取服务器地址和 token
15 | plex_server = config.get('server', 'address')
16 | plex_token = config.get('server', 'token')
17 |
18 | # 获取运行模式
19 | continuous_mode = config.getboolean('mode', 'continuous_mode')
20 | local_mode = config.getboolean('mode', 'local_mode')
21 |
22 | # 检查 [directories] 是否存在
23 | if config.has_section('directories'):
24 | user_directories = {k: v.split(';') for k, v in config.items('directories')}
25 | else:
26 | user_directories = None
27 |
28 | # 获取库目录
29 | libraries_value = config.get('libraries', 'libraries').strip()
30 | if libraries_value and not user_directories:
31 | # 获取指定库的目录
32 | response = requests.get(f"{plex_server}/library/sections?X-Plex-Token={plex_token}")
33 | root = ET.fromstring(response.content)
34 | directories = root.findall('Directory')
35 | user_libraries = {directory.get('title'): [location.get('path') for location in directory.findall('Location')] for directory in directories if directory.get('title') in libraries_value.split(';')}
36 | elif not user_directories:
37 | # 获取所有库的目录
38 | response = requests.get(f"{plex_server}/library/sections?X-Plex-Token={plex_token}")
39 | root = ET.fromstring(response.content)
40 | directories = root.findall('Directory')
41 | user_libraries = {directory.get('title'): [location.get('path') for location in directory.findall('Location')] for directory in directories}
42 | else:
43 | user_libraries = {}
44 |
45 | # 获取候选目录
46 | if not user_directories:
47 | candidate_directories = user_libraries
48 | else:
49 | candidate_directories = user_directories
50 |
51 | # 通过 [exclude_directories] 排除掉用户设置的需要排除的目录
52 | if config.has_section('exclude_directories'):
53 | exclude_directories = {k: v.split(';') for k, v in config.items('exclude_directories')}
54 | final_directories = {}
55 | for lib, folders in candidate_directories.items():
56 | if lib in exclude_directories:
57 | final_directories[lib] = [folder for folder in folders if folder not in exclude_directories[lib]]
58 | else:
59 | final_directories[lib] = folders
60 |
61 | # 获取库 ID
62 | response = requests.get(f"{plex_server}/library/sections?X-Plex-Token={plex_token}")
63 | root = ET.fromstring(response.content)
64 | directories = root.findall('Directory')
65 | library_ids = {directory.get('title'): directory.get('key') for directory in directories if directory.get('title') in final_directories}
66 |
67 | def refresh_plex_folder(folder_name):
68 | # 添加两个新属性来跟踪是否是第一次成功或失败的扫描
69 | if not hasattr(refresh_plex_folder, "first_success"):
70 | refresh_plex_folder.first_success = True
71 | if not hasattr(refresh_plex_folder, "first_failure"):
72 | refresh_plex_folder.first_failure = True
73 |
74 | # 构造完整的文件夹路径并触发 Plex 扫描
75 | for library, folder_prefixes in final_directories.items():
76 | for folder_prefix in folder_prefixes:
77 | folder_path = os.path.join(folder_prefix, folder_name)
78 |
79 | # 当本机模式开启时,检查文件夹是否存在
80 | if local_mode and not os.path.isdir(folder_path):
81 | continue
82 |
83 | # 构造请求 URL
84 | library_id = library_ids[library]
85 | url = f"{plex_server}/library/sections/{library_id}/refresh?path={folder_path}&X-Plex-Token={plex_token}"
86 |
87 | # 发送请求
88 | response = requests.get(url)
89 |
90 | # 检查响应状态
91 | if response.status_code == 200:
92 | # 如果这是第一次成功扫描,打印一个换行符
93 | if refresh_plex_folder.first_success:
94 | print()
95 | refresh_plex_folder.first_success = False
96 | print(f"成功触发Plex扫描文件夹:{folder_path}")
97 | else:
98 | # 如果这是第一次失败扫描,打印一个换行符
99 | if refresh_plex_folder.first_failure:
100 | print()
101 | refresh_plex_folder.first_failure = False
102 | print(f"触发Plex扫描文件夹失败,状态码:{response.status_code}")
103 |
104 | while True:
105 | # 用户输入文件夹名称
106 | folder_names = input("\n请输入要扫描的文件夹名称,多个文件夹名称用分号隔开:").split(';')
107 |
108 | # 重置 first_success 和 first_failure 属性
109 | refresh_plex_folder.first_success = True
110 | refresh_plex_folder.first_failure = True
111 |
112 | # 触发 Plex 扫描
113 | for folder_name in folder_names:
114 | refresh_plex_folder(folder_name.strip())
115 |
116 | # 如果连续模式关闭,扫描完成后就结束运行
117 | if not continuous_mode:
118 | break
119 |
--------------------------------------------------------------------------------
/zh/plex-scanner/start.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | cd C:\path\to\plex-scanner
4 |
5 | python3 plex-scanner.py
6 |
7 | pause
8 |
--------------------------------------------------------------------------------
/zh/plex-scanner/start.command:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd /path/to/plex-scanner
4 |
5 | python3 plex-scanner.py
--------------------------------------------------------------------------------