#!/usr/bin/env python import pyanidb, pyanidb.hash try: import ConfigParser except ImportError: import configparser as ConfigParser import optparse, os, sys, getpass from collections import deque # Workaround for input/raw_input if hasattr(__builtins__, 'raw_input'): input = raw_input # Colors. red = lambda x: '\x1b[1;31m' + x + '\x1b[0m' green = lambda x: '\x1b[1;32m' + x + '\x1b[0m' yellow = lambda x: '\x1b[1;33m' + x + '\x1b[0m' blue = lambda x: '\x1b[1;34m' + x + '\x1b[0m' # Config. config = {} try: cp = ConfigParser.ConfigParser() cp.read(os.path.expanduser('~/.pyanidb.conf')) for option in cp.options('pyanidb'): config[option] = cp.get('pyanidb', option) except: pass # Options. op = optparse.OptionParser() op.add_option('-u', '--username', help = 'AniDB username.', action = 'store', dest = 'username', default = config.get('username')) op.add_option('-p', '--password', help = 'AniDB password.', action = 'store', dest = 'password', default = config.get('password')) op.add_option('-r', '--recursive', help = 'Recurse into directories.', action = 'store_true', dest = 'recursive', default = False) op.add_option('-s', '--suffix', help = 'File suffix for recursive matching.', action = 'append', dest = 'suffix', default = config.get('suffix', '').split()) op.add_option('-c', '--no-cache', help = 'Do not use cached values.', action = 'store_false', dest = 'cache', default = int(config.get('cache', '1'))) op.add_option('-m', '--multihash', help = 'Calculate additional checksums.', action = 'store_true', dest = 'multihash', default = False) op.add_option('-i', '--identify', help = 'Identify files.', action = 'store_true', dest = 'identify', default = False) op.add_option('-a', '--add', help = 'Add files to mylist.', action = 'store_true', dest = 'add', default = False) op.add_option('-w', '--watched', help = 'Mark files watched.', action = 'store_true', dest = 'watched', default = False) op.add_option('-n', '--rename', help = 'Rename files.', action = 'store_true', dest = 'rename', default = False) op.add_option('-f', '--format', help = 'Filename format.', action = 'store', dest = 'format', default = config.get('format')) options, args = op.parse_args(sys.argv[1:]) # Defaults. if options.cache: try: import xattr except ImportError: print(red('No xattr, caching disabled.')) options.cache = False options.identify = options.identify or options.rename options.login = options.add or options.watched or options.identify if not options.suffix: options.suffix = ['avi', 'ogm', 'mkv'] if not options.format: options.format = r'_[%group]_%anime_-_%epno%ver_[%CRC].%suf' if options.login: if not options.username: options.username = input('Username: ') if not options.password: options.password = getpass.getpass() # Input files. files = [] remaining = deque(args) while remaining: name = remaining.popleft() if not os.access(name, os.R_OK): print('{0} {1}'.format(red('Invalid file:'), name)) elif os.path.isfile(name): files.append(name) elif os.path.isdir(name): if not options.recursive: print('{0} {1}'.format(red('Is a directory:'), name)) else: for sub in sorted(os.listdir(name)): if sub.startswith('.'): continue sub = os.path.join(name, sub) if os.path.isfile(sub) and any(sub.endswith('.' + suffix) for suffix in options.suffix): files.append(sub) elif os.path.isdir(sub): remaining.appendleft(sub) if not files: print(blue('Nothing to do.')) sys.exit(0) # Authorization. if options.login: a = pyanidb.AniDB(options.username, options.password) try: a.auth() print('{0} {1}'.format(blue('Logged in as user:'), options.username)) except pyanidb.AniDBUserError: print(red('Invalid username/password.')) sys.exit(1) except pyanidb.AniDBTimeout: print(red('Connection timed out.')) sys.exit(1) except pyanidb.AniDBError as e: print('{0} {1}'.format(red('Fatal error:'), e)) sys.exit(1) # Hashing. hashed = unknown = 0 for file in pyanidb.hash.hash_files(files, options.cache, (('ed2k', 'md5', 'sha1', 'crc32') if options.multihash else ('ed2k',))): print('{0} ed2k://|file|{1}|{2}|{3}|{4}'.format(blue('Hashed:'), file.name, file.size, file.ed2k, ' (cached)' if file.cached else '')) fid = (file.size, file.ed2k) hashed += 1 try: # Multihash. if options.multihash: print('{0} {1}'.format(blue('MD5:'), file.md5)) print('{0} {1}'.format(blue('SHA1:'), file.sha1)) print('{0} {1}'.format(blue('CRC32:'), file.crc32)) # Identify. if options.identify: info = a.get_file(fid, ('gtag', 'romaji', 'epno', 'state', 'epromaji', 'crc32', 'filetype'), True) fid = int(info['fid']) print('{0} [{1}] {2} - {3} - {4}'.format(green('Identified:'), info['gtag'], info['romaji'], info['epno'], info['epromaji'])) # Renaming. if options.rename: s = options.format rename_data = { 'group': info['gtag'], 'anime': info['romaji'], 'epno': info['epno'], 'ver': {0: '', 4: 'v2', 8: 'v3', 16: 'v4', 32: 'v5'}[(int(info['state']) & 0x3c)], 'crc': info['crc32'], 'CRC': info['crc32'].upper(), 'suf': info['filetype']} for name, value in rename_data.items(): s = s.replace(r'%' + name, value) if s[0] == '_': s = s[1:].replace(' ', '_') s = s.replace('/', '_') print('{0} {1}'.format(yellow('Renaming to:'), s)) os.rename(file.name, os.path.join(os.path.split(file.name)[0], s)) # Adding. if options.add: a.add_file(fid, viewed = options.watched, retry = True) print(green('Added to mylist.')) # Watched. elif options.watched: a.add_file(fid, viewed = True, edit = True, retry = True) print(green('Marked watched.')) except pyanidb.AniDBUnknownFile: print(red('Unknown file.')) unknown += 1 except pyanidb.AniDBNotInMylist: print(red('File not in mylist.')) # Finished. print(blue('Hashed {0} files{1}.'.format(hashed, ', {0} unknown'.format(unknown) if unknown else '')))