电影和电视节目名称解析器





5.00/5 (5投票s)
用于自动识别电影和电视节目的单文件、集成到 Shell 的简便解决方案。
引言
这段 VBScript 代码是为一种相当具体purpose编写的,因此它可能无法在原始形式中很好地重用,尽管我相信通过一些重写,它对于其他目的,或者至少是它的某些部分/子程序,可能会非常有用。
为了解释这段代码的 purpose,我必须先解释我面临的问题/挑战。我电脑上安装了一个媒体服务器,它创建了一个电影和电视节目库,这些库位于硬盘的特定文件夹中,我通过命名约定对它们进行排序和分类。
我举个例子。如果我下载了一部新电影或一个节目剧集,它通常会被命名为“Series.name.S01E01.encodername.x264-WebRIP.mkv”。我希望将其命名为“Series Name – S01E01 – Episode Name.mkv”,然后将其放入文件夹D:\Series\Series Name\Season 1\。所以,我首先需要查找剧集名称,然后重命名文件,然后手动将其复制/移动到目标文件夹(如果不存在则创建该文件夹)。我想自动化这个过程——而且,我还希望将它保存在一个可执行文件中,这个文件易于编辑、传输、阅读,无需(实际)安装,并且可以直接从 shell 对文件或文件夹运行。所以我决定用 VBscript 来实现,将其打包成一个 CMD 文件,该文件从内部调用脚本代码(脚本是写在批处理文件里的)。
必备组件
为了获取有关媒体(电影或节目/剧集)的信息,我使用了 www.omdbapi.com,这是一个基于 RESTful Web 服务的开放(免费)电影数据库 API。要使用该 API,您需要创建一个 API 密钥,免费使用每天限制为 1000 次请求。
要获得无限 API 密钥,您需要成为该页面的赞助者(发送捐款)。只需访问此网页,输入您的电子邮件并按照说明操作
Using the Code
有三种可能的调用选项
- 安装:
resolve_video.cmd -install
- 卸载:
resolve_video.cmd -uninstall
- 文件处理:
resolve_video.cmd <file_or_folder_path> <series/movie> <OMDB_API_key>
重要提示:对于安装和卸载过程,命令提示符应以管理员权限运行(以管理员身份运行),否则将不会发生任何事情。
安装
“安装”过程并非真正的安装——它实际上不会复制任何文件(因为这只是一个批处理脚本文件,没有安装文件)——它只是创建了必要的注册表项,以便可以直接从 shell 上运行该批处理文件。
一旦批处理文件以 -install
选项运行,它将首先要求提供 OMDB API 密钥,然后它将输出需要创建的必要注册表项,以便能够从 shell 运行——用户在这些条目实际写入注册表之前需要确认。
卸载
卸载过程将删除在安装过程中写入注册表的所有条目;它不会删除任何文件。与安装一样,它会首先提示用户列出将被删除的注册表条目。
文件处理
文件处理部分需要三个参数;第一个是用于解析的文件夹或文件的文件路径,第二个是字符串 movie
或 series
,取决于您要在 OMDB 上搜索哪一个。第三个参数是 OMDB API 密钥。
如果已经完成了注册表“安装”,则在注册表中已写入了所有正确参数的批处理文件的正确调用。但是,如果您独立运行批处理文件,则需要显式提供参数。
在下一节中,将解释文件处理部分的主要子程序。
主要子程序
代码主要分为四个部分
- 收集要处理的文件(如果脚本在文件夹上运行,则很重要)——
CollectFiles
- 解析每个文件的文件名(提取用于查询 OMDB 的信息)——
ParseFilename
- 查询 OMDB 并读取响应——
QueryOMDB
和ParseOMDB
- 重命名并复制文件——
GetNewFilename
和CopyFiles
CollectFiles
此子程序将首先检测给定路径是文件还是文件夹,如果是文件夹,它将从其中“收集”所有具有 Const c_valid_ext
(Function CheckExtension
)中扩展名的文件。它还会查找以视频文件名为开头字幕文件,例如,对于文件“Batman.Begins.2005.BRRIP.1080p.mkv”,它将查找名为“Batman.Begins.2005.BRRIP.1080p……*sometext*……..”(Function FindSubtitle
)的字幕文件。字幕文件需要有一个有效的扩展名(Const c_sub_ext
中的一个)。
此子程序的输出是
String
数组files
(视频文件路径)Dictionary(string,string)
extensions
(文件的扩展名,视频文件路径是键)Dictionary(string,string)
subtitles
(字幕文件路径,视频文件路径是键)
ParseFilename
ParseFilename
将根据调用时的第二个参数调用相应的 Sub
——对于 series
,调用 ParseSeries
;对于 movie
,调用 ParseMovie
。
ParseSeries
假定一个节目剧集通常的文件名如下所示
<series_name>…<SyyEzz|yyxzz>….
点可以是任何非字母数字字符。在 season
/episode string
之后也可能出现剧集名称,但 Sub
将忽略它,因为正确的剧集名称将在稍后通过 OMDB API 搜索。
文件名将首先通过任何非字母数字字符作为分隔符分解为 string
数组。接下来,读取每个 string
,并且在季节/剧集 string
之前找到的所有内容都将被视为系列标题的一部分,而模式 SyyEzz
或 yyxzz
将被识别为季节/剧集编号。
输出是 string
series_name
,以及整数 season_nr
和 episode_nr
。
ParseMovie
ParseMovie sub
的工作方式与 ParseSeries
相同,只是它查找发布年份而不是季节/剧集模式。任何作为 4 位数字且介于 1950
和当前年份之间的单词都被视为发布年份,否则被视为电影标题的一部分。1950
是随意选择的,您可以输入您喜欢的任何年份。
输出是两个 string
:name
(电影标题)和 yyyy
(发布年份)。
QueryOMDB & ParseOMDB
一旦我们有了系列名称 + season_nr
/episode_nr
或电影名称 + 发布年份,我们就使用提供的 API 密钥查询 OMDB API。
以下是 Supernatural 电视节目第 14 季的 API 请求和响应示例(您需要输入一个有效的 <<API key>>)
请求: http://www.omdbapi.com/?apikey=<<API_KEY>>&t=supernatural&type=series&season=14
响应
{"Title":"Supernatural","Season":"14","totalSeasons":"14","Episodes":[{"Title":"Stranger in a Strange Land","Released":"2018-10-11","Episode":"1","imdbRating":"8.1","imdbID":"tt8226756"},{"Title":"Gods and Monsters","Released":"2018-10-18","Episode":"2","imdbRating":"8.7","imdbID":"tt8408494"},{"Title":"Episode #14.3","Released":"2018-10-25","Episode":"3","imdbRating":"N/A","imdbID":"tt8408498"},{"Title":"Mint Condition","Released":"2018-11-01","Episode":"4","imdbRating":"8.9","imdbID":"tt8408504"},{"Title":"Nightmare Logic","Released":"2018-11-08","Episode":"5","imdbRating":"N/A","imdbID":"tt8408508"},{"Title":"Optimism","Released":"2018-11-15","Episode":"6","imdbRating":"8.5","imdbID":"tt8408506"},{"Title":"Episode #14.7","Released":"2018-11-29","Episode":"7","imdbRating":"N/A","imdbID":"tt8408502"},{"Title":"Byzantium","Released":"2018-12-06","Episode":"8","imdbRating":"9.0","imdbID":"tt8408500"},{"Title":"Episode #14.9","Released":"2018-12-13","Episode":"9","imdbRating":"N/A","imdbID":"tt8408512"},{"Title":"Nihilism","Released":"2019-01-17","Episode":"10","imdbRating":"9.1","imdbID":"tt8408510"},{"Title":"Damaged Goods","Released":"2019-01-24","Episode":"11","imdbRating":"8.9","imdbID":"tt8962434"},{"Title":"Prophet and Loss","Released":"2019-01-31","Episode":"12","imdbRating":"8.7","imdbID":"tt8962440"},{"Title":"Lebanon","Released":"2019-02-07","Episode":"13","imdbRating":"9.6","imdbID":"tt8962446"},{"Title":"Ouroboros","Released":"2019-03-07","Episode":"14","imdbRating":"9.4","imdbID":"tt9271138"},{"Title":"Peace of Mind","Released":"2019-03-14","Episode":"15","imdbRating":"9.2","imdbID":"tt9271140"},{"Title":"Don't Go in the Woods","Released":"2019-03-21","Episode":"16","imdbRating":"8.5","imdbID":"tt9271142"}],"Response":"True"}
API 的详细使用说明可以在主页上找到
对于 Http 请求,此脚本使用 Microsoft.XmlHttp
类型的对象
Public http: Set http = CreateObject("Microsoft.XmlHttp")
http.open "GET", url, False
http.send ""
respons = http.responseText
响应在 sub
ParseOMDBResponse
中根据以 c_resp
开头的常量进行解析,这些常量代表 response string
中的关键字。
Const c_movie="movie"
Const c_series="series"
Const c_resp="Response:"
Const c_resp_true="True"
Const c_resp_false="False"
Const c_resp_title="Title:"
Const c_resp_year="Year:"
Const c_resp_totalseasons="totalSeasons:"
Const c_resp_episodes="Episodes:"
Const c_resp_ep_title="Title:"
Const c_resp_ep_nr="Episode:"
假设没有错误(c_resp_false
),输出是:name
(系列或电影标题),episode_name
(剧集标题 - 对于系列),yyyy
(发布年份),maxseasons
(总季数)。
GetNewFilename & CopyFiles
一旦我们有了所有变量,我们就可以生成新的文件名。这在 GetNewFilename sub
中完成。
此 sub
使用代码中提供的掩码(常量 c_mask_series
和 c_mask_movie
)为处理过的文件创建新的名称/路径。
目标文件夹在常量 c_destination_folder_movie
和 c_destination_folder_series
中指定。
我使用了这些,您应该在自己的系列/电影文件夹中放入自己的掩码和路径。
Const c_mask_series="%name%\Season %season_nr_1%\%name% - S%season_nr%E%episode_nr% - %episode_name%"
Const c_mask_movie="%name% (%yyyy%)\%name%"
Const c_destination_folder_movie="V:\MOVIES\"
Const c_destination_folder_series="V:\SERIES\"
(被 %%
包围的 string
会被替换为相应的变量。)
GetNewFilename
过程将结合适当的目标文件夹和掩码来创建每个文件的新路径。它会将新文件名保存在字典 new_filename
中(原始文件名是键)。新字幕文件名将保存在字典 new_sub
中。
旧文件和新文件路径首先会显示给用户,以确认他们是否希望复制文件。确认后,将调用 CopyFiles
子程序,该子程序将为每个文件调用 FileRobocopy
函数和 TrackCopyProgress
子程序。
FileRobocopy
函数将首先创建一个新的批处理文件,该文件将首先调用 Windows 的 robust copy(robocopy
)命令将给定文件复制到给定目标,然后使用 Windows 命令 ren
将新文件重命名为新文件名。robocopy
函数的输出将被路由到一个文件,然后该函数将 robocopy
输出文件名返回给 CopyFiles
过程,然后该过程通过读取 robocopy
日志文件来跟踪进度。
robocopy
批处理完成后,它将自行删除。
TrackCopyProgress
TrackCopyProgress
将连续读取 robocopy
日志文件,直到达到 100% 行。该子程序的输出是一个由 Unicode 字符模拟的进度条(由函数调用 ChrW(9608)
和 ChrW(9618)
返回)。
Sub PrintProgress(perc, length)
Dim progres
WScript.stdout.Write chr(13)
tmp = ChrW(9608)
For i = 1 To length
If i > CInt(length * perc / 100) Then tmp = ChrW(9618)
progres = progres & tmp
Next
WScript.stdout.Write progres & " ( " & CStr(CInt(perc)) & "% )"
End Sub
附加文件
批处理文件在处理过程中会创建几个临时文件,其中大多数一旦不再需要就会立即被删除。所有附加的(临时)文件将保存在与批处理文件相同的目录中。
批处理日志文件
这是批处理的每次处理后唯一不会被删除的附加文件。该文件将与批处理脚本同名,但扩展名为 .log——通常名称将是 resolve_video.log。此文件是整个批处理过程的日志——Log sub
写入处理步骤,带有时间戳和调用过程的名称。每次文件都会被重写。
Sub Log(msg)
logfile.WriteLine TimeStamp & Chr(9) & msg
End Sub
Copy File Temp Batch
对于每个需要复制的文件,FileRobocopy
函数将创建一个由三个命令组成的批处理:robocopy
(用于复制文件)、ren
(用于重命名复制的文件)和 del
(用于删除临时批处理文件)。
batchfile.WriteLine "robocopy """ & fromwhere & """ """ & towhere & """ """ & _
fso.GetFile(old_filename).Name & """ > """ & robocopy_log & """"
batchfile.WriteLine "ren """ & towhere & "\" & filename & """ """ & _
Replace(new_filename, towhere & "\", "") & """"
batchfile.WriteLine "del """ & batchfile_name & """"
Robocopy Output File
robocopy
命令的输出被路由到一个文件,该文件使用创建时间戳命名(以避免同时出现多个同名文件)。
此文件由 TrackCopyProgress
子程序读取,以了解实际的复制进度。(此文件的最后一行将始终包含复制进度的百分比——0.1% 到 100%。)
Path of the Batch Temp
在调用 VBscript 之前,会立即创建一个文件——批处理将其路径写入文件“cmd.path.tmp”。该文件在脚本开始时被读取,然后立即删除。
在“安装”过程中,此名称用于在从 shell 调用时写入正确的批处理路径(写入注册表)。