mirror of
https://github.com/nikdoof/tvorganise.git
synced 2025-12-13 06:42:16 +00:00
236 lines
7.4 KiB
Python
Executable File
236 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#encoding:utf-8
|
|
"""
|
|
tvorganise.py
|
|
|
|
This parses "Show Name - [01x23] - Episode Name.avi"
|
|
filenames and automatically copies them to
|
|
the location format defined in the configuration file.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import re
|
|
from optparse import OptionParser
|
|
import shutil
|
|
import ConfigParser
|
|
import logging
|
|
|
|
def same_partition(path1, path2):
|
|
"""
|
|
Checks to see if two paths are on the same device, returns a
|
|
bool to indicate
|
|
"""
|
|
return os.stat(path1).st_dev == os.stat(path2).st_dev
|
|
|
|
def find_files(args):
|
|
"""
|
|
Take a singular path or a list of paths and provides a list of all files in
|
|
that directory structure.
|
|
"""
|
|
filelist = []
|
|
|
|
if isinstance(args, list):
|
|
for path in args:
|
|
for root, dirs, files in os.walk(path, topdown=False):
|
|
for name in files:
|
|
filelist.append(os.path.join(root, name))
|
|
else:
|
|
for root, dirs, files in os.walk(args, topdown=False):
|
|
for name in files:
|
|
filelist.append(os.path.join(root, name))
|
|
|
|
return filelist
|
|
|
|
|
|
class TvOrganiser():
|
|
"""
|
|
TvOrganiser takes a list of files and validates them against known filename
|
|
formats, then moves the files to a specified tree layout.
|
|
"""
|
|
|
|
_config = {}
|
|
__logger = None
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
@property
|
|
def _logger(self):
|
|
"""
|
|
Returns the class logger instance
|
|
"""
|
|
if not hasattr(self, "__logger"):
|
|
self.__logger = logging.getLogger(self.__class__.__name__)
|
|
return self.__logger
|
|
|
|
def _get_config(self, cfile):
|
|
"""
|
|
Parses the TVOrganiser style config file and produces a dict
|
|
with all the elements contained within.
|
|
|
|
Also, all regex specified in the file are compiled
|
|
"""
|
|
|
|
config = {}
|
|
|
|
configpsr = ConfigParser.RawConfigParser()
|
|
configpsr.read(cfile)
|
|
|
|
if configpsr.has_section('main'):
|
|
for key, value in configpsr.items('main'):
|
|
config[key] = value
|
|
|
|
if configpsr.has_section('regex'):
|
|
|
|
regex_config = {}
|
|
regex = []
|
|
|
|
# Load in subs before reading in the regex
|
|
for key, value in configpsr.items('regex'):
|
|
if key[:5] != 'regex':
|
|
regex_config[key] = value
|
|
|
|
for key, value in configpsr.items('regex'):
|
|
if key[:5] == 'regex':
|
|
regex.append(re.compile(value % regex_config))
|
|
|
|
config['regex'] = regex
|
|
|
|
self._config = config
|
|
return config
|
|
|
|
def parse_filenames(self, names):
|
|
"""
|
|
Takes list of names, runs them though the regexs and breaks them down
|
|
into logical information.
|
|
"""
|
|
episodelist = []
|
|
for efile in names:
|
|
filepath, filename = os.path.split(efile)
|
|
filename, ext = os.path.splitext(filename)
|
|
|
|
# Remove leading . from extension
|
|
ext = ext.replace(".", "", 1)
|
|
|
|
for regex in self._config['regex']:
|
|
match = regex.match(filename)
|
|
if match:
|
|
showname, seasno, epno, epname = match.groups()
|
|
|
|
#remove ._- characters from name
|
|
showname = re.sub("[\._]|\-(?=$)", " ", showname).strip()
|
|
|
|
seasno, epno = int(seasno), int(epno)
|
|
|
|
self._logger.debug("File: %s" % filename)
|
|
self._logger.debug("Pattern: %s" % regex.pattern)
|
|
self._logger.debug("Showname: %s" % showname)
|
|
self._logger.debug("Seas: %s" % seasno)
|
|
self._logger.debug("Ep: %s" % epno)
|
|
|
|
episodelist.append({'showname': showname,
|
|
'seasonnum': seasno,
|
|
'episodenum': epno,
|
|
'filepath': filepath,
|
|
'filename': filename,
|
|
'episodename': epname,
|
|
'ext': ext})
|
|
break # Matched - to the next file!
|
|
else:
|
|
self._logger.warning("Invalid name: %s" % (efile))
|
|
|
|
return episodelist
|
|
|
|
def main(self):
|
|
"""
|
|
TVOrganiser, provide a path or file to process
|
|
"""
|
|
|
|
parser = OptionParser(usage="%prog [options] <file or directories>")
|
|
parser.add_option("-a", "--always", dest="always",
|
|
action="store_true", default=False,
|
|
help="Do not ask for confirmation before copying")
|
|
parser.add_option("-q", "--quiet", dest="quiet",
|
|
action="store_true", default=False,
|
|
help="Silence output")
|
|
parser.add_option("-c", "--config", dest="config",
|
|
action="store", default="tvorganise.cfg",
|
|
help="Use a custom configuration file")
|
|
parser.add_option("-v", "", dest="verbose",
|
|
action="store_true", default=False,
|
|
help="Verbose output")
|
|
|
|
opts, args = parser.parse_args()
|
|
cfile = None
|
|
|
|
if os.path.exists(opts.config):
|
|
cfile = opts.config
|
|
else:
|
|
for path in [ '%s/.tvorganise.cfg' % os.environ['HOME'], '/etc/tvorganise.cfg']:
|
|
if os.path.exists(path):
|
|
cfile = path
|
|
break
|
|
|
|
if not cfile:
|
|
self._logger.error('Unable to find configuration file!')
|
|
sys.exit(1)
|
|
|
|
self._logger.info('Using config file: %s' % cfile)
|
|
config = self._get_config(cfile)
|
|
|
|
files = find_files(args)
|
|
files = self.parse_filenames(files)
|
|
|
|
self._logger.debug(files)
|
|
|
|
# Warn if no files are found, then exit
|
|
if len(files) == 0:
|
|
self._logger.error('No files found')
|
|
sys.exit(0)
|
|
|
|
for name in files:
|
|
filename = "%s.%s" % (name['filename'], name['ext'])
|
|
oldfile = os.path.join(name['filepath'], filename)
|
|
newpath = config['target_path'] % name
|
|
newfile = os.path.join(newpath, filename)
|
|
|
|
self._logger.info("Old path: %s" % oldfile)
|
|
self._logger.info("New path: %s" % newfile)
|
|
|
|
if opts.always:
|
|
if not os.path.exists(newpath):
|
|
os.makedirs(newpath)
|
|
if os.path.exists(newfile):
|
|
self._logger.warning("File already exists, not copying")
|
|
else:
|
|
if same_partition(oldfile, newpath):
|
|
self._logger.info("Moving file")
|
|
try:
|
|
os.rename(oldfile, newfile)
|
|
except OSError, errormsg:
|
|
self._logger.error("Error moving file! %s" % errormsg)
|
|
else:
|
|
self._logger.info("Copying file")
|
|
try:
|
|
shutil.copy(oldfile, newfile)
|
|
except IOError, errormsg:
|
|
self._logger.error("Error copying file! %s" % errormsg)
|
|
else:
|
|
self._logger.info("done")
|
|
else:
|
|
self._logger.warning("Skipping file: %s" % filename)
|
|
|
|
def main():
|
|
"""
|
|
Start a stand alone instance of TvOrganise
|
|
"""
|
|
|
|
logging.basicConfig(level=logging.INFO,format="%(name)s:%(levelname)s: %(message)s")
|
|
|
|
tvorg = TvOrganiser()
|
|
tvorg.main()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|