I have previously written a post explaining how to install and configure TVNamer to automatically rename downloaded TV episode files into nice, human-readable formats. I found that if you create a configuration file for TVNamer, however, it can do even more than that. I will outline how to use TVNamer to move TV episodes to a folder tree, based on metadata such as the series name and season number. This organizes your media perfectly for a HTPC front-end.

Example of the TV folder tree:

  • tv
    • series name 1
      • Season 01
    • series name 2
      • Season 01
      • Season 02
    • series name 3
      • Season 05

Install TVNamer

First, install TVNamer using the instructions found in my prior post on the subject.

Generate and Customize the TVNamer Configuration File

Running TVNamer without a configuration file renames files in place, without too many options as to how it is done. Running TVNamer with a configuration file, on the other hand, unlocks more powerful functionality. I will show you how to configure TVNAmer to move your TV episodes into a well-organized folder tree, using the series name, season number, and other metadata to create the folder- and file names. (A word of caution: While TVNamer works superbly on episodic content, it does not currently work shows broadcast daily, such as “The Daily Show” and “The Colbert Report”, due to how the files are named.)

To begin using a TVNamer configuration file, you must create one. Luckily, it is easy to tell TVNamer to create a default configuration file for you. Run TVNamer, as follows, to create a configuration file in your home folder.

$ tvnamer --save=~/.tvnamerconfig.json

Now, edit that configuration file. The file is in JSON (JavaScript Object Notation) format, which means that you must be sure your syntax is consistent, and all quotation marks and braces are closed.

$ nano ~/.tvnamerconfig.json

Your configuration file will look similar to my file, which is reproduced below. Any lines that I changed from the default are in boldface. I recommend setting “batch” and “recursive” to true. To move renamed files to a new folder, edit the following attributes: “move_files_confirmation”, “move_files_destination” and “move_files”.

You can pick metadata keywords out of the various “filename_*” attributes. The most useful ones will likely be ”seriesname”, “seasonno”, ”episodename”, and “episode”. These can be used not only in file names, but also in the folder path for the “move_files_destination” attribute. Note the syntax used to insert the metadata values into the attribute value: %(keyword)s. When inserting these metadata strings into the attributes you edit, you must keep the control characters–the “%(” that precedes the keyword, and the )s that follows it–intact. Numbers are treated a little differently in terms of the suffix: 02dx instead of s. This follows Python string formatting syntax, and is explained in the “Custom Output Filenames” section of TVNamer’s web site.

The configuration file below sets up TVNamer to run in batch mode, recursively process any folders passed to it on the command line, process only files with video file extensions, and to move any renamed files to a specific folder tree (~/media/tv/(series name)/Season (season number)),  I don’t change how files are named, but you could easily do so by editing the attributes that start with “filename_”. I highly recommend limiting TVNamer’s activities to video files using the “valid_extensions” attribute, because I’ve had non-video files confuse the script. (I never believed that would happen, but it did, and the fix is simple.)

{
“always_rename”: false,
“batch”: true,
“episode_separator”: “-”,
“episode_single”: “%02d”,
“filename_patterns”: [
"^\\[.+?\\][ ]? # group name\n        (?P<seriesname>.*?)[ ]?[-_][ ]?          # show name, padding, spaces?\n        (?P<episodenumberstart>\\d+)              # first episode number\n        ([-_]\\d+)*                               # optional repeating episodes\n        [-_](?P<episodenumberend>\\d+)            # last episode number\n        [^\\/]*$”,
“^\\[.+?\\][ ]? # group name\n        (?P<seriesname>.*) # show name\n        [ ]?[-_][ ]?(?P<episodenumber>\\d+)\n        [^\\/]*$”,

“\n        ^((?P<seriesname>.+?)[ \\._\\-])?          # show name\n        [Ss](?P<seasonnumber>[0-9]+)             # s01\n        [\\.\\- ]?                                 # separator\n        [Ee](?P<episodenumberstart>[0-9]+)       # first e23\n        ([\\.\\- ]+                                # separator\n        [Ss](?P=seasonnumber)                    # s01\n        [\\.\\- ]?                                 # separator\n        [Ee][0-9]+)*                             # e24 etc (middle groups)\n        ([\\.\\- ]+                                # separator\n        [Ss](?P=seasonnumber)                    # last s01\n        [\\.\\- ]?                                 # separator\n        [Ee](?P<episodenumberend>[0-9]+))        # final episode number\n        [^\\/]*$”,

“\n        ^((?P<seriesname>.+?)[ \\._\\-])?          # show name\n        [Ss](?P<seasonnumber>[0-9]+)             # s01\n        [\\.\\- ]?                                 # separator\n        [Ee](?P<episodenumberstart>[0-9]+)       # first e23\n        ([\\.\\- ]?                                # separator\n        [Ee][0-9]+)*                             # e24e25 etc\n        [\\.\\- ]?[Ee](?P<episodenumberend>[0-9]+) # final episode num\n        [^\\/]*$”,

“\n        ^((?P<seriesname>.+?)[ \\._\\-])?          # show name\n        (?P<seasonnumber>[0-9]+)                 # first season number (1)\n        [xX](?P<episodenumberstart>[0-9]+)       # first episode (x23)\n        ([ \\._\\-]+                               # separator\n        (?P=seasonnumber)                        # more season numbers (1)\n        [xX][0-9]+)*                             # more episode numbers (x24)\n        ([ \\._\\-]+                               # separator\n        (?P=seasonnumber)                        # last season number (1)\n        [xX](?P<episodenumberend>[0-9]+))        # last episode number (x25)\n        [^\\/]*$”,

“\n        ^((?P<seriesname>.+?)[ \\._\\-])?          # show name\n        (?P<seasonnumber>[0-9]+)                 # 1\n        [xX](?P<episodenumberstart>[0-9]+)       # first x23\n        ([xX][0-9]+)*                            # x24x25 etc\n        [xX](?P<episodenumberend>[0-9]+)         # final episode num\n        [^\\/]*$”,

“\n        ^((?P<seriesname>.+?)[ \\._\\-])?          # show name\n        [Ss](?P<seasonnumber>[0-9]+)             # s01\n        [\\.\\- ]?                                 # separator\n        [Ee](?P<episodenumberstart>[0-9]+)       # first e23\n        (                                        # -24 etc\n             [\\-]\n             [Ee]?[0-9]+\n        )*\n             [\\-]                                # separator\n             (?P<episodenumberend>[0-9]+)        # final episode num\n        [\\.\\- ]                                  # must have a separator (prevents s01e01-720p from being 720 episodes)\n        [^\\/]*$”,

“\n        ^((?P<seriesname>.+?)[ \\._\\-])?          # show name\n        (?P<seasonnumber>[0-9]+)                 # 1\n        [xX](?P<episodenumberstart>[0-9]+)       # first x23\n        (                                        # -24 etc\n             [\\-][0-9]+\n        )*\n             [\\-]                                # separator\n             (?P<episodenumberend>[0-9]+)        # final episode num\n        ([\\.\\- ].*                               # must have a separator (prevents 1×01-720p from being 720 episodes)\n        |\n        $)”,

“^(?P<seriesname>.+?)[ \\._\\-]          # show name and padding\n        \\[                                       # [\n            ?(?P<seasonnumber>[0-9]+)            # season\n        [xX]                                     # x\n            (?P<episodenumberstart>[0-9]+)       # episode\n            (- [0-9]+)*\n        -                                        # -\n            (?P<episodenumberend>[0-9]+)         # episode\n        \\]                                       # \\]\n        [^\\/]*$”,

“^(?P<seriesname>.+?)[ \\._\\-]\n        [Ss](?P<seasonnumber>[0-9]{2})\n        [\\.\\- ]?\n        (?P<episodenumber>[0-9]{2})\n        [^0-9]*$”,

“^((?P<seriesname>.+?)[ \\._\\-])?       # show name and padding\n        \\[?                                      # [ optional\n        (?P<seasonnumber>[0-9]+)                 # season\n        [xX]                                     # x\n        (?P<episodenumber>[0-9]+)                # episode\n        \\]?                                      # ] optional\n        [^\\/]*$”,

“^((?P<seriesname>.+?)[ \\._\\-])?\n        [Ss](?P<seasonnumber>[0-9]+)[\\.\\- ]?\n        [Ee]?(?P<episodenumber>[0-9]+)\n        [^\\/]*$”,

“\n        ^((?P<seriesname>.+?)[ \\._\\-])?         # show name\n        (?P<year>\\d{4})                          # year\n        [ \\._\\-]                                 # separator\n        (?P<month>\\d{2})                         # month\n        [ \\._\\-]                                 # separator\n        (?P<day>\\d{2})                           # day\n        [^\\/]*$”,

“^(?P<seriesname>.+?)[ ]?[ \\._\\-][ ]?\n        [Ss](?P<seasonnumber>[0-9]+)[\\.\\- ]?\n        [Ee]?[ ]?(?P<episodenumber>[0-9]+)\n        [^\\/]*$”,

“\n        (?P<seriesname>.+)                       # Showname\n        [ ]-[ ]                                  # -\n        [Ee]pisode[ ]\\d+                         # Episode 1234 (ignored)\n        [ ]\n        \\[                                       # [\n        [sS][ ]?(?P<seasonnumber>\\d+)            # s 12\n        ([ ]|[ ]-[ ]|-)                          # space, or -\n        ([eE]|[eE]p)[ ]?(?P<episodenumber>\\d+)   # e or ep 12\n        \\]                                       # ]\n        .*$                                      # rest of file\n        “,

“^(?P<seriesname>.+)[ \\._\\-]\n        (?P<seasonnumber>[0-9]{1})\n        (?P<episodenumber>[0-9]{2})\n        [\\._ -][^\\/]*$”,

“^(?P<seriesname>.+)[ \\._\\-]\n        (?P<seasonnumber>[0-9]{2})\n        (?P<episodenumber>[0-9]{2,3})\n        [\\._ -][^\\/]*$”,

“^(?P<seriesname>.+?)                  # Show name\n        [ \\._\\-]                                 # Padding\n        [Ee](?P<episodenumber>[0-9]+)            # E123\n        [\\._ -][^\\/]*$                          # More padding, then anything\n        ”

],

“filename_with_episode”: “%(seriesname)s – [%(seasonno)02dx%(episode)s] – %(episodename)s%(ext)s”,
“filename_with_episode_no_season”: “%(seriesname)s – [%(episode)s] – %(episodename)s%(ext)s”,
“filename_without_episode”: “%(seriesname)s – [%(seasonno)02dx%(episode)s]%(ext)s”,
“filename_without_episode_no_season”: “%(seriesname)s – [%(episode)s]%(ext)s”,
“input_filename_replacements”: [],
“language”: “en”,
“move_files_confirmation”: false,
“move_files_destination”: “/home/mjdescy/media/tv/%(seriesname)s/Season %(seasonnumber)02d”,
“move_files_enable”: true,
“move_files_fullpath_replacements”: [],
“multiep_join_name_with”: “, “,
“normalize_unicode_filenames”: false,
“output_filename_replacements”: [],
“recursive”: true,
“replace_invalid_characters_with”: “_”,
“search_all_languages”: true,
“select_first”: false,
“skip_file_on_error”: true,
“valid_extensions”: ["avi","mp4","m4v","wmv","mkv","mov"],
“verbose”: false,
“windows_safe_filenames”: true
}

Scheduling/Putting it All Together

I run TVNamer on my “completed torrents” folder every 15 minutes, via my personal account’s crontab. This process automatically sweeps any TV episodes out of Transmission‘s completed downloads folder and files them into my TV folder, which is shared via Samba and MediaTomb for use by my HTPC front-ends. This set-up is perfect for my WD TV LIVE front-end, XBMC, or for any HTPC front-end setup. For any TV shows that I rip from DVD and encode to MP4 files myself, TVNamer will rename and file them automatically, as long as I name the original files with a S01E01 (series number, episode number) syntax and drop them into the completed downloads folder.

$ crontab -e

Add the following line, being sure to modify the config file and TV folder paths, and save the crontab file.

*/15 * * * * /usr/local/bin/tvnamer -c /home/mjdescy/.tvnamerconfig.json /home/mjdescy/dl/complete >/dev/null 2>&1