Author Topic: TV Show Automatic Sort/Rename like SABnzbd  (Read 8281 times)

Offline somestranger26

  • Contributor
  • ***
  • Posts: 11
TV Show Automatic Sort/Rename like SABnzbd
« on: November 28, 2010, 07:44:52 am »
This is probably not the easiest feature to implement, but I think it would be great if AltBinz could sort and rename TV shows by parsing the filename for needed information.

I have written a script to work in conjunction with the RSS feature, but I have to manually specify the unrar folders and then tell my script what show and season it was. I consider this fairly hackish (and it doesn't match some files that SABnzbd does) and would love an easier way to organize my files.

In SABnzbd you can choose to have it do this with zero user intervention; it uses regex to parse the filename for necessary information and then sorts/renames based on a format string that users can edit. Since SABnzbd is open source, I figure it would not be too difficult to adapt their tvsort algorithm to AltBinz.

For example, the default format string in SABnzbd "%sn/Season %s/%sn - S%0sE%0e.%ext" renames a file to be like "The Sausage S01 E01.mkv" and then puts it into "<unrar directory>/Season 1/The Sausage/"
« Last Edit: November 28, 2010, 12:12:47 pm by Hecks »

Offline Hecks

  • Contributor
  • ***
  • Posts: 2011
  • naughty cop
Re: TV Show Automatic Sort/Rename like SABnzbd
« Reply #1 on: November 28, 2010, 12:09:00 pm »
File renaming is evil. However, there are quite a few tools already for doing this that also do lookups for metadata, e.g.:

http://tvrenamer.com/

To be incorporated into Alt.Binz, first I think we'd really need something like SABnzbd Categories, about which there are a few open requests. Or of course you can launch any script you want from Alt.Binz to do post-processing, including python (for the SABnzbd code). If you don't want to install a scripting language and you're on Windows, try Powershell (installed with Windows 7 by default) - it allows regex matching & file renaming quite trivially.

Info: http://powershell.com/cs/blogs/ebook/archive/2009/03/30/chapter-13-text-and-regular-expressions.aspx#forming-groups

Example:

Code: [Select]
$file = "The.Sausage.S01E01.720p.HDTV.x264-FOO"

$pattern = "^(?<title>.*?)\.S(?<season>\d+)\.?E(?<episode>\d+)\.(?<tail>.*)$"

if ($file -match $pattern)
{
  write ("Title: " + ($matches.title -replace "[\._]"," "))
  write ("Season: " + $matches.season)
  write ("Episode: " + $matches.episode)
}
else
{
  write "No matches"
}

« Last Edit: November 28, 2010, 12:43:44 pm by Hecks »

Offline somestranger26

  • Contributor
  • ***
  • Posts: 11
Re: TV Show Automatic Sort/Rename like SABnzbd
« Reply #2 on: December 01, 2010, 04:55:29 am »
I ended up writing a much improved version of my python script that does what SABnzbd's sorter does without relying on categories. I'll post it here if anyone else is interested in using it, it will look through all files in the START_PATH directory and rename them if it recognizes them as TV shows, it matches most naming formats. It will then move them to DESTINATION_PATH\Show\Season X (if it has seasons)\Show S0XEXX.ext. So if a show has a date it will be put into the Dest_path\Show\ directory. It will also rename and move any files that it recognizes as being in the wrong place.

My script should be Linux/OSX compatible too since it's written in Python.

Edit: I made some refinements to the code so it will also match files with spaces and underscores and removed some unnecessary parts
Edit2: I fixed a bug where the script crashed if movies were present. Added movie rename support since it coincided with fixing this bug.
Edit3: Added support for shows like S01E02E03
Code: [Select]
# This script sorts tv and movie files by identifying frequently used naming schemes
#
# TV files sent to tvdest\show\season\Show S0x E0x.ext
# Movie files sent to movdest\Movie Title.ext
#
# Read comments & edit formatting section of script yourself to modify the file name
# Edit the main loop to change how directories are laid out
#
# Edit the paths below to specify the starting path, tv and movie destination directories.
TV_DESTINATION = "D:\\Videos\\TV"
MOV_DESTINATION = "D:\\Videos\\Movies"
START_PATH = "D:\\Downloads\\Usenet"
##################################################################################
import os
MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]

def generate_newname(filename):
    """ Parses a filename and returns the new filename if its destination is different from the old location """
    global TITLE, SEASON, TYPE
    elems = str.split(str.lower(filename.replace("_", ".").replace(" ", ".")), '.')    # split the filename by periods etc.
    ext = elems[-1]
   
    if ext in ["mkv", "avi", "ts"]:            # Edit what extensions you want it to match
        TYPE = "tv"
        showname = []
        for elem in elems[0:-1]:               # parse the elements
            if not str.isdigit(elem[0:4]):     # shows w/o a date
                if elem[0] == 's' and elem[1].isdigit():
                    temp = elem.replace('s', '').split('e')
                    season = temp[0]
                    episode = temp[1] if len(temp) == 2 else 'E'.join(temp[1:3]) # s01e01 OR s01e01e02
                    break                         
                elif elem[0].isdigit():
                    if elem[1].isdigit():
                        if elem[2].isdigit() and elem[-1] != 'p' and elem[-1] != 'i': # do not match 720p, 1080i, etc.
                            if elem[1] == '0':    # 101
                                season = '0' + elem[0]
                                episode = elem[1:3]
                            elif len(elem) == 4:  # 0101
                                season = elem[0:2]
                                episode = elem[2:4]
                        elif elem[2] == 'x':      # 01x01
                            season, episode = elem.split('x')
                    elif elem[1] == 'x':          # 1x01
                        season, episode = elem.split('x')
                        season = '0' + season
                    break   # season found, showname should be before it so break
                else:       # assume this is a part of showname
                    showname.append(elem)
            else:           # this show is probably named using a date, assume year.month.day format
                i = elems.index(elem)
                TITLE = titler(' '.join(showname))
                SEASON = ""
                if 'p' in elems[i+1] or 'i' in elems[i+1]: # Looks like a movie. Handle as movie.title.year.quality.otherstuff.ext
                    TYPE = "movie"
                    return "%s.%s" % (TITLE, ext)  # Rename to Movie Title.ext, elems[i] is year and elems[i+1] is quality
                return "%s %s %s %s.%s" % (TITLE, MONTHS[int(elems[i+1]) - 1], elems[i+2], elems[i], ext)
        TITLE = titler(' '.join(showname))
        SEASON = "Season %s" % (season[-1] if season[0] == '0' else season)
        return "%s S%s E%s.%s" % (TITLE, season, episode, ext)

def titler(x): # python's title capitalizes after apostrophes... deal with it for certain cases
    return str.title(x).replace("'S", "'s").replace(" In", " in").replace("'T", "'t").replace("'V", "'v").\
           replace(" Of ", " of ").replace("Its", "It's").replace("Us", "").strip(" ")

for root, dirs, files in os.walk(START_PATH):  # Processes all the files in START_PATH
    for old in files:
        new = generate_newname(old)
        if new:
            if TYPE == "tv":
                newdir = os.path.join(TV_DESTINATION, TITLE)
                if not os.path.exists(newdir): # If the new show directory doesn't exist, create it
                    os.mkdir(newdir)
                newdir = os.path.join(newdir, SEASON)
                if not os.path.exists(newdir) and SEASON != "": # If the new season directory doesn't exist, create it
                    os.mkdir(newdir)
                new = os.path.join(newdir, new)
            else: # movie
                new = os.path.join(MOV_DESTINATION, new)
            old = os.path.join(root, old)
            if not (os.path.exists(new) or TITLE[0].isdigit()): # make sure there isn't a duplicate file,
                try:
                    os.rename(old, new)
                except:
                    pass
« Last Edit: December 11, 2010, 12:57:15 am by somestranger26 »

Offline Baird GoW

  • Contributor
  • ***
  • Posts: 11
Re: TV Show Automatic Sort/Rename like SABnzbd
« Reply #3 on: December 01, 2010, 11:41:32 am »
Somestranger26's script is awesome. I would love to have this feature in altbinz. Please add. :)

Offline zoned

  • Contributor
  • ***
  • Posts: 115
Re: TV Show Automatic Sort/Rename like SABnzbd
« Reply #4 on: December 02, 2010, 08:34:20 am »
Would be good if is built in, if user can also modify this naming "The Sausage S01 E01.mkv" to some other ;)

Offline somestranger26

  • Contributor
  • ***
  • Posts: 11
Re: TV Show Automatic Sort/Rename like SABnzbd
« Reply #5 on: January 26, 2011, 07:29:49 am »
Someone told me shows like "23" and "30 Sausage" didn't work and I implemented a fix. If you don't want a shell window to open when the script runs you can use the included vbs script in conjunction with the python one. To do that put in same folder and run turbo_sort.vbs instead of turbo_sort.py.

turbo_sort.py:
Code: [Select]
# This script sorts tv and movie files by identifying frequently used naming schemes
#
# TV files sent to tvdest\show\season\Show S0x E0x.ext
# Movie files sent to movdest\Movie Title.ext
#
# Read comments & edit formatting section of script yourself to modify the file name
# Edit the main loop to change how directories are laid out
#
# Edit the paths below to specify the starting path, tv and movie destination directories.
TV_DESTINATION = "D:\\Videos\\TV"
MOV_DESTINATION = "D:\\Videos\\Movies"
START_PATH = "D:\\Downloads\\Usenet"
##################################################################################
import os
MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]

def generate_newname(filename):
    """ Parses a filename and returns the new filename if its destination is different from the old location """
    global TITLE, SEASON, TYPE
    elems = str.split(str.lower(filename.replace("_", ".").replace(" ", ".")), '.')    # split the filename by periods etc.
    ext = elems[-1]
    if ext in ["mkv", "avi", "ts"]:            # Edit what extensions you want it to match
        TYPE = "tv"
        showname = []
        for elem in elems[0:-1]:               # parse the elements
            if not str.isdigit(elem[0:4]):     # shows w/o a date
                if elem[0] == 's' and elem[1].isdigit():
                    temp = elem.replace('s', '').split('e')
                    season = temp[0]
                    episode = temp[1] if len(temp) == 2 else 'E'.join(temp[1:3]) # s01e01 OR s01e01e02
                    break                         
                elif elem[0].isdigit():
                    if elem[1].isdigit():
                        if elem[2].isdigit() and elem[-1] != 'p' and elem[-1] != 'i': # do not match 720p, 1080i, etc.
                            if elem[1] == '0':    # 101
                                season = '0' + elem[0]
                                episode = elem[1:3]
                            elif len(elem) == 4:  # 0101
                                season = elem[0:2]
                                episode = elem[2:4]
                        elif elem[2] == 'x':      # 01x01
                            season, episode = elem.split('x')
                    elif elem[1] == 'x':          # 1x01
                        season, episode = elem.split('x')
                        season = '0' + season
                    break   # season found, showname should be before it so break
                else:       # assume this is a part of showname
                    showname.append(elem)
            elif elem in ["24", "30"]: # special case for shows that start w/ number like 30 rock and 24
                showname.append(elem)
            else:           # this show is probably named using a date, assume year.month.day format
                i = elems.index(elem)
                TITLE = titler(' '.join(showname))
                SEASON = ""
                if 'p' in elems[i+1] or 'i' in elems[i+1]: # Looks like a movie. Handle as movie.title.year.quality.otherstuff.ext
                    TYPE = "movie"
                    return "%s.%s" % (TITLE, ext)  # Rename to Movie Title.ext, elems[i] is year and elems[i+1] is quality
                return "%s %s %s %s.%s" % (TITLE, MONTHS[int(elems[i+1]) - 1], elems[i+2], elems[i], ext)
        TITLE = titler(' '.join(showname))
        try:
            SEASON = "Season %s" % (season[-1] if season[0] == '0' else season)
            return "%s S%s E%s.%s" % (TITLE, season, episode, ext)
        except UnboundLocalError:
            print "An error occured parsing " + filename
            return None


def titler(x): # python's title capitalizes after apostrophes... deal with it for certain cases
    return str.title(x).replace("'S", "'s").replace(" In", " in").replace("'T", "'t").replace("'V", "'v").\
           replace(" Of ", " of ").replace("Its", "It's").replace("Us", "").strip(" ")

for root, dirs, files in os.walk(START_PATH):  # Processes all the files in START_PATH
    for old in files:
        new = generate_newname(old)
        if new:
            if TYPE == "tv":
                newdir = os.path.join(TV_DESTINATION, TITLE)
                if not os.path.exists(newdir): # If the new show directory doesn't exist, create it
                    os.mkdir(newdir)
                newdir = os.path.join(newdir, SEASON)
                if not os.path.exists(newdir) and SEASON != "": # If the new season directory doesn't exist, create it
                    os.mkdir(newdir)
                new = os.path.join(newdir, new)
            else: # movie
                new = os.path.join(MOV_DESTINATION, new)
            old = os.path.join(root, old)
            print old + "  --->  " + new
            if not os.path.exists(new): # make sure there isn't a duplicate file
                # or TITLE[0].isdigit()): <-- can't remember what this was for but caused problem with 30 rock/24
                try:
                    os.rename(old, new)
                except:
                    pass

turbo_sort.vbs:
Code: [Select]
Set WinScriptHost = CreateObject("WScript.Shell")
WinScriptHost.Run Chr(34) & "turbo_sort.py" & Chr(34), 0
Set WinScriptHost = Nothing

Offline murf78

  • Contributor
  • ***
  • Posts: 7
Re: TV Show Automatic Sort/Rename like SABnzbd
« Reply #6 on: February 16, 2011, 07:59:39 am »
Just a pointer in case someone else finds this thread and is looking for similar functionality...
It's worth checking out SickBeard, as you can have it monitoring an unrar folder for shows and doing rename/move for you.
http://sickbeard.com