Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Movie and TV Show Name Resolver

0.00/5 (No votes)
13 Nov 2019 1  
One-file shell-integrated easy solution for automatic recognition of movies and TV shows

Introduction

This VBScript code was written for rather a specific purpose, so maybe it won't be very reusable in its original form, though I am sure with some recoding, it could be very useful for some other purpose, or at least some of its parts/subs.

To explain the purpose of this code, I first have to explain the problem/challenge I was facing. I have a media server installed on my computer, which creates a library of movies and TV series which are located in specific folders on the hard drive, where I sort and categorize them by a naming convention.

I'll give an example. If I download a new movie or an episode of a series, it is usually named something like „Series.name.S01E01.encodername.x264-WebRIP.mkv“. I want the name to be „Series Name – S01E01 – Episode Name.mkv", and I want to put it to a folder D:\Series\Series Name\Season 1\. So first, I have to look up the episode name, then rename the file, then manually copy/move it to the destination folder (and create the folder if it doesn't exist). I wanted to automate this – and also, I wanted to have this in only one executable file, which is easily editable, transferrable, readable, without (actual) installation, and that I can run it from shell directly on a file or a folder. So I decided to do this in VBscript, as a CMD file that calls the script code from within (the script is written within the batch file).

Prerequisites

For the purpose of finding the information about the media (movie or series/episode), I used the www.omdbapi.com, which is an open (free) movie database API based on RESTful web service. To use the API, you need to create an API key, which is for free usage limited to 1,000 requests daily.

To have an unlimited API key, you need to become a patron of the page (send donation). Just go to this webpage, enter your email and follow the instructions:

Using the Code

There are three possible call options:

  • Installation: resolve_video.cmd -install
  • Uninstallation: resolve_video.cmd -uninstall
  • File processing: resolve_video.cmd <file_or_folder_path> <series/movie> <OMDB_API_key>

IMPORTANT NOTE: For the installation and uninstallation process, the Command Prompt should be run with administrator privileges (Run as Administrator), otherwise nothing will happen.

Installation

The „installation“ process is not a real installation – it does not actually copy any files (there are no installation files since this is just one file batch script) – it just creates the necessary registry entries so that the batch could be run directly on the file/folder from the shell.

Once the batch is run with the option -install, it will first ask for the OMDB API key, then it will print out the necessary registry entries that need to be created in order to enable it to be run from the shell – the user needs to confirm before these entries are actually written into the registry.

Uninstallation

The uninstallation process will delete all those entries that were written into the registry during the installation process; it will not delete any files. Just like with the installation, it will prompt the user first with the list of registry entries that will be deleted.

File Processing

The file processing part takes three arguments; the first one is the filepath of a folder or a file to parse, the second is a string movie or series, depending on which of the two you want to search on OMDB. The third argument is the OMDB API key.

If the „installation“ to the registry was already done, the correct call to the batch with all the correct arguments is already written in the registry. However, if you are running the batch independently, you need to provide the arguments explicitly.

In the next section, the main subs of the file processing part are explained.

Main Subs

The code is primarily divided into four parts:

  1. Collect the files for processing (important if the script is run on a folder) – CollectFiles
  2. Parse the filename for each file (pull out stuff to be used to query OMDB) – ParseFilename
  3. Query OMDB and read the response – QueryOMDB and ParseOMDB
  4. Rename and copy the files – GetNewFilename and CopyFiles

CollectFiles

This sub will first detect if the given path is a file or a folder, and in case of a folder, it will „collect“ all the files from it that have one of the extensions in Const c_valid_ext (Function CheckExtension). It will also search for subtitle files that start with the video file's name, i.e., for the file „Batman.Begins.2005.BRRIP.1080p.mkv“ it will look for a subtitle file named „Batman.Begins.2005.BRRIP.1080p……*sometext*……..“ (Function FindSubtitle). The subtitle file needs to have a valid extension (one from the Const c_sub_ext).

The outputs of this sub are:

  • String array files (video file paths)
  • Dictionary(string,string) extensions (extensions of the files, video file path is the key)
  • Dictionary(string,string) subtitles (subtitle file paths, video file path is the key)

ParseFilename

ParseFilename will call the appropriate Sub based on the second argument of the call – ParseSeries for series, and ParseMovie for movie.

ParseSeries

It is assumed that the usual filename for an episode of a series looks like this:

<series_name>…<SyyEzz|yyxzz>….

The dots may be any non-alphanumeric character. There may also come an episode name after the season/episode string, but the Sub will ignore this, as the correct episode name shall be searched for later on through OMDB API.

The filename shall first be broken down into a string array by any non-alphanumeric characters as separators. Next, each string is read and everything that is found before the season/episode string shall be considered a part of the series title, while the patterns SyyEzz or yyxzz shall be recognized as the number of season/episode.

The outputs are string series_name, and integers season_nr and episode_nr.

ParseMovie

The ParseMovie sub works the same way as ParseSeries, it just looks for the year of the release instead of season/episode pattern. Any word that is a 4-digit number and is between 1950 and current year is considered the release year, otherwise it is considered to be a part of the movie title. 1950 was chosen arbitrarily, you can put any year you like.

The outputs are two strings: name (the movie title) and yyyy (release year).

QueryOMDB & ParseOMDB

Once we have the series name + season_nr/episode_nr OR movie name + release year, we query OMDB API using the provided API key.

This is an example of the API request and response for the Supernatural TV show, season 14 (you need to enter a valid <<API key>>):

Request: http://www.omdbapi.com/?apikey=<<API_KEY>>&t=supernatural&type=series&season=14

Response:

{"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"}

The detailed instructions on how to use the API can be found on the homepage:

For Http requests, this script uses the object of type Microsoft.XmlHttp:

Public http: Set http = CreateObject("Microsoft.XmlHttp")
http.open "GET", url, False
http.send ""
respons = http.responseText

The response is parsed in the sub ParseOMDBResponse based on constants that begin with c_resp and represent the keywords from the 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:"

Assuming there was no error (c_resp_false), the outputs are: name (series or movie title), episode_name (episode title - for series), yyyy (release year), maxseasons (total number of seasons).

GetNewFilename & CopyFiles

Once we have all the variables, we can generate the new filename. This is done in the GetNewFilename sub.

This sub uses a mask provided in the code (constants c_mask_series and c_mask_movie) to create a new name/path for the processed file(s).

The destination folders are assigned in constants c_destination_folder_movie and c_destination_folder_series.

I used these, you should put in your own masks and paths for series / movie folders.

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\"

(The strings surrounded by %% are replaced with the respective variables.)

The GetNewFilename procedure will combine the appropriate destination folder and the mask to create a new path to each file. It will save the new filenames in the dictionary new_filename (original filename is the key). The new subtitle filenames will be saved in the dictionary new_sub.

The old and new file paths are first printed out for the user to confirm if he wants the files copied or not. After the confirmation, the sub CopyFiles is called, which will for each file call function FileRobocopy and Sub TrackCopyProgress.

FileRobocopy function will first create a new batch file which will first call windows robust copy (robocopy) command to copy the given file to the given destination, then it will rename the new file to the new filename using windows command ren. The robocopy function output will be routed to a file, then the function returns the robocopy output filename to the CopyFiles procedure, which then tracks the progress by reading the robocopy log file.

After the robocopy batch is done, it will delete itself.

TrackCopyProgress

The TrackCopyProgress will continuously read the robocopy log file until it reaches the line 100%. The output of the sub is a progress bar simulated by Unicode characters (returned by function calls ChrW(9608) and 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

Additional Files

The batch creates several temporary files during its processing, most of which get erased immediately once they are no longer needed. All the additional (temporary) files will be saved in the same directory as the batch file.

Batch Log File

This is the only additional file that will remain undeleted after each processing of the batch. The file will be named the same as the batch script, but with the .log extension to it – normally the name would be resolve_video.log. This file is the log for the whole batch processing – the Log sub writes the processing steps, with timestamps and name of the calling procedure. Each time the file is rewritten.

Sub Log(msg)
   logfile.WriteLine TimeStamp & Chr(9) & msg
End Sub

Copy File Temp Batch

For each file that needs to be copied, the function FileRobocopy will create a batch consisting of three commands: robocopy (to copy the file), ren (to rename the copied file), and del (to delete the temp batch file).

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

The robocopy command's output is routed to a file, which is named using the timestamp of when it is created (to avoid having several files at the same time with the same name).

This file is read by the sub TrackCopyProgress in order to find out what is the actual progress of copying the file. (The last line of this file will always contain the percentage of the copy progress – 0.1% to 100%.)

Path of the Batch Temp

One file is created immediately at the beginning – before even calling the VBscript – the batch writes out its path into the file „cmd.path.tmp“. The file gets read, then immediately erased at the beginning of the script.

This name is used in the „installation“ process to write the proper batch path in the call from the shell (written in the registry).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here