Looney Tunes???
#31
Good luck! Also make sure to review the original post, there have been quite a few changes since you last used the script.
Reply
#32
FINALLY got around to giving this a go... Worked great for me! Everything's nicely renamed and moved to the proper folder.

THANK YOU for your help Tim.!
Reply
#33
jaydash,

Glad to hear it worked out for you! I've put a lot of work into this simple script and hoped others would find it useful. I have recently been teaching myself vb.net programming and should soon be releasing a GUI version for Windows PC's!
Reply
#34
Awesome script.

Moving the files doesn't work for me in linux. On line 85, the slashes are backwards and I end up with a bunch of empty folders and files all over the root (in this case, my home directory). Re-running the script on these files without the 'move' option fixes this though. And swapping the slashes on line 85 makes it work A-OK.

Thanks for awesome!
Reply
#35
Thank you, Tim, for posting that script! Helped me save a ton of time. I made a few modifications which some people might find useful:
  1. Now if a show doesn't match exactly, it will display the closest matches and provide an easy mechanism to select one (either by number or string).
  2. It's also a little more Linux friendly in that it accepts plenty of command line arguments and plays nice with all paths.
  3. Episodes don't need to be in the same directory anymore; it scans recursively.
One thing I noted was that in the CSV file I downloaded, the names and season-episode information was swapped. So, likewise, I swapped it here. It should work with this csv file, orginally posted by kinetik: http://pastebin.com/tHyb9504

Help:
Code:
usage: episode_renamer.py [-h] [--debug] [--dry] [--skip]
                          [--num-matches NUM_MATCHES] [--copy]
                          [series] [csv_file] [input_dir] [output_dir]

Renames episodes in a directory according to a CSV file from the TVDB.

positional arguments:
  series                Name of series.
  csv_file              CSV file containing episode name and number.
  input_dir             Directory containing episodes.
  output_dir            Directory to place renamed episodes in.

optional arguments:
  -h, --help            show this help message and exit
  --debug               Print debugging output.
  --dry                 Print what we would do, but do not do it.
  --skip                Skip non-matching episodes instead of interactively
                        renaming them.
  --num-matches NUM_MATCHES
                        When interactively renaming, show this many close
                        matches.
  --copy                Copy files instead of renaming them.

Script:
Code:
#!/usr/bin/env python
# Episode Name - File Renamer
# Renames files without accurate episode order using the Episode Name only
# Originally coded by: Tim
# Cleaned and modified by: Kyle

# Import modules
from argparse import ArgumentParser, ArgumentTypeError
import os.path
import csv
import re
import errno
import unicodedata
import shutil


def getLevenshteinValue(a, b):
    '''
    The Levenshtein distance is defined as the minimal number of characters
    you have to replace, insert or delete to transform one string into
    another one. We use the same concept here to compare two lists of
    sorted values to see how far off one is from the other.
    
    See: http://en.wikipedia.org/wiki/Levenshtein_distance
    '''
    len_a = len(a)
    len_b = len(b)

    d = []
    for i in xrange(len_a+1):
        d.append([0] * (len_b+1))

    for i in xrange(len_a+1):
        d[i][0] = i

    for j in xrange(len_b+1):
        d[0][j] = j

    for j in xrange(1, len_b+1):
        for i in xrange(1, len_a+1):
            if a[i-1] == b[j-1]:
                d[i][j] = d[i-1][j-1]
            else:
                d[i][j] = min(d[i-1][j] + 1,
                              d[i][j-1] + 1,
                              d[i-1][j-1] + 1)

    return d[len_a][len_b]

def cleanName(s):
    """
    Strip the invalid filename characters from the string selected.
    Feel free to add/remove additional .replace(X,X) as needed if you
    want to remove other characters even if they are valid.
    For example: , or [ or !
    """
    s = s.replace("?","")
    s = s.replace(":","")
    s = s.replace("*","")
    s = s.replace("<","")
    s = s.replace(">","")
    s = s.replace("|","")
    s = s.replace("/","")
    s = s.replace("\\","")
    s = s.replace('"',"")
    return s

def parseSeason(l):
    """
    Takes the first cell of the CSV copied from the TVDB website
    and strips out only the season.
    """
    if l == "Special":
        season = "00"
    else:
        season = l.split(" ")[0].zfill(2)
    return season

def parseEpisode(l):
    """
    Takes the first cell of the CSV copied from the TVDB website
    and strips out only the episode. Pads a 0 before single digits.
    """
    if l == "Special":
        episode = "00"
    else:
        episode = l.split(" ")[-1].zfill(2)
    return episode

def getEpisodeFiles(input_dir):
    """
    Recursively iterate over the input directory, returning any files found.
    """
    all_files = []
    for root, _dirs, files in os.walk(input_dir):
        for f in files:
            all_files.append(os.path.join(root, f))
    return all_files

def cleanEmptyDirectories(directory):
    for root, dirs, files in os.walk(directory, topdown=False):
        if not dirs and not files:
            print 'Removing empty: %s' % root
            os.rmdir(root)

def getEpisodeNames(csv_file):
    """
    Returns dictionary of clean episode name -> (clean name, season, episode).
    """
    episode_names = {}
    first = True
    with open(args.csv_file) as f:
        reader = csv.reader(f)
        for line in reader:
            if first:
                first = False
                continue
            line = [unicodedata.normalize('NFC', unicode(e, 'utf-8')) for e in line]
            debug('Original: %s' % ','.join(line))
            season = int(parseSeason(line[1]))
            episode = int(parseEpisode(line[1]))
            clean_name = cleanName(line[0])
            debug('Parsed: %s season: %d episode: %d' % (clean_name, season, episode))
            name_re = re.compile(clean_name, re.IGNORECASE | re.UNICODE)
            episode_names[name_re] = (clean_name, season, episode)
    return episode_names

def findMatch(episode_names, name):
    while True:
        for name_re in episode_names:
            if name_re.search(name):
                return episode_names[name_re]
        
        if not args.skip:
            # Get close matches
            print
            close_matches = [(getLevenshteinValue(n.pattern, name), n.pattern) for n in episode_names]
            close_matches.sort()
            print '"%s" not found; %d closest matches are:' % (name, args.num_matches)
            for i, (value, close_match) in enumerate(close_matches[:args.num_matches]):
                i += 1
                print '%d. "%s" (%d)' % (i, close_match, value)
            name = unicode(raw_input('Try (blank to skip): '), 'utf-8')
            name = unicodedata.normalize('NFC', name)
            if not name:
                return None
            try:
                n = int(name)
                _, name = close_matches[n-1]
            except ValueError:
                pass
        else:
            return None

def ensureExists(directory):
    try:
        os.makedirs(directory)
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise

def ensureArguments():
    if args.series is None:
        args.series = raw_input('Series name: ')
    if args.csv_file is None:
        while True:
            try:
                args.csv_file = existingFileType(raw_input('CSV file: '))
                break
            except ArgumentTypeError as e:
                print str(e)
    if args.input_dir is None:
        while True:
            try:
                args.input_dir = existingDirectoryType(raw_input('Directory containing episodes: '))
                break
            except ArgumentTypeError as e:
                print str(e)
    if args.output_dir is None:
        args.output_dir = raw_input('Destination directory: ')

def expandPath(path):
    path = os.path.expanduser(path)
    return os.path.expandvars(path)

def existingFileType(argument):
    argument = expandPath(argument)
    if not os.path.isfile(argument):
        raise ArgumentTypeError('%s does not exist!' % argument)
    return os.path.abspath(argument)

def existingDirectoryType(argument):
    argument = expandPath(argument)
    if not os.path.isdir(argument):
        raise ArgumentTypeError('%s does not exist!' % argument)
    return os.path.abspath(argument)


if __name__ == '__main__':
    parser = ArgumentParser(description='Renames episodes in a directory according to a CSV file from the TVDB.')
    parser.add_argument('series', nargs='?', help='Name of series.')
    parser.add_argument('csv_file', nargs='?', type=existingFileType, help='CSV file containing episode name and number.')
    parser.add_argument('input_dir', nargs='?', type=existingDirectoryType, help='Directory containing episodes.')
    parser.add_argument('output_dir', nargs='?', help='Directory to place renamed episodes in.')
    parser.add_argument('--debug', action='store_true', help='Print debugging output.')
    parser.add_argument('--dry', action='store_true', help='Print what we would do, but do not do it.')
    parser.add_argument('--skip', action='store_true', help='Skip non-matching episodes instead of interactively renaming them.')
    parser.add_argument('--num-matches', default=5, type=int, help='When interactively renaming, show this many close matches.')
    parser.add_argument('--copy', action='store_true', help='Copy files instead of renaming them.')
    args = parser.parse_args()

    if args.debug:
        def debug(s):
            print str(s)
    else:
        debug = lambda s: None

    ensureArguments()

    print 'Episode Renamer'
    print '==============='
    debug('Arguments: %s' % str(args))
    print 'Renaming: %s' % args.series
    print 'In: %s' % args.input_dir
    print 'Using: %s' % args.csv_file
    print 'To: %s' % args.output_dir
    print
    print 'Loading episode information ...'
    episode_names = getEpisodeNames(args.csv_file)
    
    num_renames = num_skipped = 0        
    for full_filename in getEpisodeFiles(args.input_dir):
        base_filename = os.path.basename(full_filename)
        base_name, extension = os.path.splitext(base_filename)
        clean_base_name = cleanName(base_name)
    
        name_match = findMatch(episode_names, clean_base_name)    
        if not name_match:            
            print 'skipping %s' % full_filename
            num_skipped += 1
            continue

        episode_name, season, episode_number = name_match        
        full_episode_name = 'S%dE%d %s' % (season, episode_number, episode_name)
        base_output_name = args.series + ' ' + full_episode_name + extension
        full_output_name = os.path.join(args.output_dir, 'Season %d' % season, base_output_name)
        num_renames += 1
        
        print full_output_name
        if not args.dry:
            output_dir = os.path.dirname(full_output_name)
            ensureExists(output_dir)
            if args.copy:
                shutil.copy2(full_filename, full_output_name)
            else:
                os.rename(full_filename, full_output_name)

    print 'Renamed %d file(s). (Skipped %d)' % (num_renames, num_skipped)
    if not args.dry:
        cleanEmptyDirectories(args.input_dir)
Reply
#36
A huge, juicy thanks for this. You've saved me countless hours of effort, and I greatly appreciate it.
Reply
#37
So I have begun the massive task that is adding the Looney Tunes Golden Collection to my XBMC library. Since I am just starting out the renaming utility won't do me much good so I have built an excel spreadsheet that has the files listed in order as they appear on the DVDs with the appropriate names according to theTVDB.com. Well long story short I have been able to get them into XBMC and have the scraper successfully capture the metadata. My issue now is that for whatever reason they shows do not appear in any smart playlists that I setup. The playlist is setup using path contains "Looney Tunes" and it should work perfectly, but nothing appears. Huh

I have many movies and other TV shows to include the new "Looney Tunes 2011" working perfectly with playlists.

PS When I fully completed the full list of episodes I will make it available if anyone is interested.
Reply
#38
I finally figured out that xbmc had incorrectly associated the folder with The Looney Tunes Show (2011) which I already had as a TV Show. This caused the other episodes to show up under that title instead of the straight Looney Tunes title. I simply renamed the folder and added (classic) and that solved my problem.
Reply
#39
Hey all. I am ripping and importing my looney tunes discs into xbmc for the first time, and Tim's script is a great help. Unfortunately, there were some errors due to the fact that python upgraded to 3.x. I have edited the script to account for those changes, and it works perfectly again. Here's the code:

Code:
# Episode Name - File Renamer
# Renames files without accurate episode order using the Episode Name only
# Coded by: Tim.

# Import modules
import os
import glob
import csv

# Assign inital values
repeat = "true"
edit = "true"

#Define custom functions
def invalid_char(s):
    """
    Strip the invalid filename characters from the string selected.
    Feel free to add/remove additional .replace(X,X) as needed if you
    want to remove other characters even if they are valid.
    For example: , or [ or !
    """
    return s.replace("?","").replace(":","").replace("*","").replace("<","").replace(">","").replace("|","").replace("/","").replace("\\","").replace('"',"")

def season(l):
    """
    Takes the first cell of the CSV copied from the TVDB website
    and strips out only the season.
    """
    if l == "Special":
        season = "00"
    else:
        season = l.split(" ")[0].zfill(2)
    return season

def episode(l):
    """
    Takes the first cell of the CSV copied from the TVDB website
    and strips out only the episode. Pads a 0 before single digits.
    """
    if l == "Special":
        episode = "00"
    else:
        episode = l.split(" ")[-1].zfill(2)
    return episode

# Overall loop, allows user to re-run the entire script
while repeat == "true":

    # Checks if the user defined variables need to be edited
    if edit == "true":

        # Prompt user to define static variables
        series_name = input("Please enter your series name: ")
        #series_name = "Charlie Brown"
        print("\n")
        data = input("Path to CSV: ")
        #data = "C:\lt\cb.csv"
        print("\n")
        dir1 = input("Path to episodes (format C:\*): ")
        #dir1 = "M:\TV Shows\CB\all\*"
        print("\n")
        move = input("Would you like to move renamed files? (Yes/No): ").lower()
        if move in ("y", "ye", "yes"):
            print("\n")
            print("Enter path to root folder where files should be moved")
            move_path = input("and season folders will be created (format C:\Show\): ")
        edit = "false"
    file_list = glob.glob(dir1)
    print ("\n\n")

    # Loop through file_list and look for matches in the CSV to the filename after the prefix assigned
    for file in file_list:
        fname = file
        ext = fname[-4:]
        with open(data, 'r') as file:
            reader = csv.reader(file)
            season_episode_name = ["S" + season(line[0]) + "E" + episode(line[0]) + " " + invalid_char(line[1]) for line in reader if invalid_char(line[1].lower()) in fname.lower() and line[1].lower() != ""]
            season_dir = (''.join(season_episode_name)).split("E")[0][1:]
        if season_episode_name:
            season_episode_name = ''.join(season_episode_name)
            fname2 = dir1[:-1] + series_name + " " + season_episode_name + ext

            # If user chose to move files to another directory, then fname2 has the path replaced
            if move in ("y", "ye", "yes"):
                fname2 = move_path + "Season " + season_dir + "\\" + fname2[len(dir1[:-1]):]

                # Generates the season directory if does not already exist
                if not os.path.exists(move_path + "Season " + season_dir):
                    os.makedirs(move_path + "Season " + season_dir)
            
            # Rename file and move if user requested
            print(fname + "\n    >>> " + fname2 + "\n")
            os.rename(fname, fname2)

        else:
            print(fname + "\n    >>> " "Unable to locate match, please rename manually.\n")
    
    # Check if user wants to repeat, edit or exit
    repeat = input ("\n\nType R to run again, Type E to edit the parameters, or Q to quit: ")
    if repeat.lower() in ("r", "retry"):
        repeat = "true"
        print("\nRunning again with the same parameters..")
    elif repeat.lower() in ("e", "edit"):
        edit = "true"
        repeat = "true"
        print("\nEditing paramaters before running again..")
    elif repeat.lower() in ("q", "quit"):
        repeat = "false"
        print("\nQuitting...")
    else:
        repeat = "false"
        print("\nInvalid command.")

# When repeat no longer is "true" the script exiits with the below message
else:
    input("\n\nPress enter to exit...")
Reply
#40
Can anyone help me on this? I'm using the csv that was uploaded on the pastebin link (renamed it from .txt to .csv of course), and I ran the script (the one in the original post) with python 2.7.5. My problem is that I'm directing the script to the proper files and folders, but I keep getting an error through all the files "Unable to locate match. Please rename manually." Can someone help me figure out what I'm doing wrong? The naming in my files are for example " 1 - EpisodeName.avi " I've also tried removing the numbers too but I can't get it to rename properly through the script.
I love walking into Radio Shack and when the worker asks what you need, I say "I'm looking for a 1N4148 Diode." Then they have the look on their face of absolute uselessness. If it's not a cell phone, they just don't know what it is any more.
Reply
#41
Here is my last updated version of my script: http://forum.xbmc.org/showthread.php?tid...pid1426648

You can grab the CSV I used here: http://pastebin.com/zTzurqDh

My method of generating the CSV is different than the powershell scipt kinetik created. In my case (as explained in the above post) you create a CSV by copying the episode list from the TVDB into Excel or Google Docs and save as CSV.

EDIT: Also, are your files named like: 'Baseball Bugs.avi' or 'BaseballBugs.avi'? The episode names in the filenames must match exactly.
Reply
#42
The Filenames are named exactly. I removed the extra numbers and stuff.

Also, when I copy the info from tvdb and paste it into an excel doc, it all shows up under the first column. Is it supposed to be in separate columns? The version I tried before was the txt file that was given earlier and I changed the extension to .csv on it. I'll try out what you're telling me here but if I'm overlooking something let me know. Thanks!
I love walking into Radio Shack and when the worker asks what you need, I say "I'm looking for a 1N4148 Diode." Then they have the look on their face of absolute uselessness. If it's not a cell phone, they just don't know what it is any more.
Reply
#43
I just tried and copied from TVDB to Excel by highlighting from the word 'Episode Number' down. This was my result:

Image

As you can see, separate columns. If they are all in the first column then yes the script wont work. Also the text file from kinetik's script is reversed from mine (I wanted to make it easy to create the CSV files by copy-paste from the TVDB) When you use the CSV I linked to I suspect it will work fine for you.
Reply
#44
Thanks for the help. I ended up using the script you referred right above here and I used the CSV you posted too. It was weird. It would only do about 780 files at a time and then it stopped on an error in the script. I wish I had written it down. It had something to do with calling the .py file I created the script in. Anyway, I kept running it until it wouldn't go any more. It renamed all but 77 files. Still an awesome time saver though. I can rename those by hand in the next few days. Thanks for the help though!
I love walking into Radio Shack and when the worker asks what you need, I say "I'm looking for a 1N4148 Diode." Then they have the look on their face of absolute uselessness. If it's not a cell phone, they just don't know what it is any more.
Reply
#45
(2013-05-22, 06:27)Tim. Wrote: What you need to do:
  1. Make a copy of your original Looney Tunes folder. Always good to start by making a backup! I renamed my folder from ‘Looney Tunes’ to ‘lt’ to make it easier to type.
  2. Move all the episodes into one folder (the script does not scan multiple or recursive folders) for easier renaming.
  3. Double click on the python file you downloaded from this thread.
  4. You will be prompted for the series name. In this case: Looney Tunes
  5. Enter the path to the CSV you downloaded from this thread. For example: C:\download\lt.csv
  6. Enter the path to your COPY of the episodes (to be safe). For example: C:\lt\disc1\*
  7. If you want to move the files while renaming (useful for the 300+ episodes in the Looney Tunes Golden Collection!) type "y" or "yes"
  8. If you chose to rename your files, enter the path to your normal show directory For example C:\Looney Tunes\
  9. Done!
old thread, still very useful,

for a python newb, anything obvious that would launch me a quick cmd prompt that just disappears? it is associating python(edits with idle fine). i downloaded the most recent stable 3.x.x. (tried trons rev also) also assumed the 64 bit version with AMD in the title was for all 64 bit processors...

thanks for the script btw,

cory
Reply

Logout Mark Read Team Forum Stats Members Help
Looney Tunes???2