73 Commits
1.0 ... master

Author SHA1 Message Date
3a7c6dc7b3 Sort the duplicate list 2013-09-09 19:48:38 +10:00
1fb8543c62 Sort the duplicate list 2013-09-09 19:47:25 +10:00
d185b0047e Sort the duplicate list 2013-09-09 19:45:30 +10:00
4e0060a3ce Ignore case when comparing file extensions 2013-09-09 17:16:24 +10:00
5f788998a3 fixing bug looking for non-existant directories 2013-08-01 19:16:24 +10:00
60584b4dfb fixing bug 2013-07-30 19:59:45 +10:00
eca7f5710b making looking for duplicates case insensitive 2013-07-29 09:40:35 +10:00
ac508047bd Added : as an illegal character 2013-07-28 10:12:38 +10:00
5ef02c1e8e Added a check to search the output directories for duplicate video files 2013-07-23 23:59:30 +10:00
5ae07c01e7 Removed debugging lines 2013-07-23 23:07:41 +10:00
5aa5da1a92 fixed error finding duplicate file 2013-07-22 23:43:58 +10:00
d9da2d18f7 Fixed fuzzy episode name matching logic 2013-07-22 22:34:36 +10:00
1297a07e37 fixing pylint suggestions 2013-07-21 22:35:53 +10:00
5ca6c3861e added check for pre-existing video files 2013-07-21 22:23:14 +10:00
aa514032f5 Also check for the output file without the _ in the filename to see if it exists 2013-07-21 21:50:58 +10:00
3cb3930c1c removing unneeded files 2013-07-21 09:35:53 +10:00
34e2a89066 Added unit test for email 2013-07-20 22:26:43 +10:00
f0ac96de94 Added email messaging 2013-07-20 20:58:29 +10:00
00de89d02c Added string substitutions to config and corrected logging error 2013-07-19 23:18:51 +10:00
be97271559 added logging 2013-07-19 22:36:46 +10:00
c06d43b3e7 Merge branch 'master' of https://github.com/sfrischkorn/recordingprocessing 2013-07-18 23:34:37 +10:00
48e4eae2be Ignore ? in filenames since they are illegal 2013-07-18 23:33:17 +10:00
12cd3bdeea Update README 2013-07-16 15:07:09 +10:00
35ca21e4e4 Added checks for potential problems when listing files 2013-07-15 22:27:06 +10:00
df4aefeca1 fixing typo 2013-07-15 08:33:32 +10:00
1ee1c1e988 fixing capitalisation error 2013-07-15 08:31:45 +10:00
3c707b52ac removing debug statements 2013-07-14 08:49:01 +10:00
9a35c1e1f4 fixing capitalisation error 2013-07-14 08:46:50 +10:00
3d914c704a Fixing renamed properties 2013-07-13 21:09:14 +10:00
7e777a4719 Fixed error parsing input options 2013-07-13 21:06:47 +10:00
dc5691130f fixing check for existing output files 2013-07-13 16:19:30 +10:00
f25bf65fda fixing colored error 2013-07-13 16:18:14 +10:00
263a45eeb5 fixing check for existing output files 2013-07-13 16:07:45 +10:00
b9df25ddf0 Added some unit tests and fixed the mythtv ones 2013-07-13 15:50:47 +10:00
3c2d87e6de Added a check for existing files 2013-07-13 11:55:36 +10:00
22b94e372c Fixing call to renamed method 2013-07-13 10:53:03 +10:00
fde0324b6a Fixed error calling lower 2013-07-13 10:17:12 +10:00
0e3e1a2b14 fixing for pylint and pep8 recommendatins 2013-07-12 00:02:42 +10:00
ca4c95219a fixing pep8 and pylint recomendations 2013-07-11 00:30:00 +10:00
29388ba43f fixing pylint and pep8 recommendations 2013-07-10 23:13:44 +10:00
e08a415617 Fixing pep8 recomendations 2013-07-10 15:45:42 +10:00
57002e2124 fixed bug calling settings 2013-07-10 14:29:23 +10:00
745591cb8b fixed bug calling non-existant logger 2013-07-10 11:16:15 +10:00
e52c4a8137 Fixed bug calling settings 2013-07-10 11:14:45 +10:00
493fc5cb8d Fixed bug in arguments 2013-07-10 11:13:42 +10:00
1148dce0ab bug fixing 2013-07-09 21:23:07 +10:00
de54bfb876 fixed some errors getting encoding files 2013-07-09 20:28:47 +10:00
74d82516ae fixed bug 2013-07-09 12:14:02 +10:00
e7fa163480 added some unit tests 2013-07-08 23:49:54 +10:00
6e2aff7ab8 bug fixing 2013-07-08 17:40:55 +10:00
7a34fd01c8 added settings file 2013-07-08 17:21:42 +10:00
ccb103ad36 move error fixing 2013-07-06 23:14:41 +10:00
4f7d7db678 move error fixing 2013-07-06 21:59:49 +10:00
a10bed2ddb move error fixing 2013-07-06 21:59:33 +10:00
4a5cc6d0c6 move error fixing 2013-07-06 21:23:39 +10:00
48beb3e662 move error fixing 2013-07-06 21:22:33 +10:00
27b132becb move error fixing 2013-07-06 21:20:21 +10:00
dee7b7bc14 move error fixing 2013-07-06 21:18:01 +10:00
9385b00bec move error fixing 2013-07-06 21:16:57 +10:00
07d8a19035 move error fixing 2013-07-06 21:15:46 +10:00
bb02b34f9e move error fixing 2013-07-06 21:13:58 +10:00
d0d66fe9a8 move error fixing 2013-07-06 21:10:16 +10:00
74b50ef3a2 fixing errors 2013-07-06 20:28:49 +10:00
b8acbcbdc1 finished initial coding 2013-07-06 20:18:52 +10:00
61a5eb9768 interim commit 2013-07-05 22:40:17 +10:00
cb914cb65e interim commit 2013-07-05 21:30:01 +10:00
864ef4d525 Added settings library 2013-07-05 21:27:53 +10:00
e006c45567 Added main program arguments 2013-07-05 15:35:12 +10:00
ad5a3d4865 Added sickbeard logic 2013-07-05 15:24:09 +10:00
15952044f7 Added mythtv logic 2013-07-05 14:48:21 +10:00
40ca301e0e Added handbrake logic 2013-07-05 14:39:25 +10:00
d3440df493 Added new files 2013-07-05 14:15:03 +10:00
Shane Frischkorn
d63b2da062 Removing old files 2013-07-05 13:58:17 +10:00
27 changed files with 1630 additions and 446 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.pyc
.spyderproject

View File

@@ -1,125 +0,0 @@
# -*- coding: utf-8 -*-
"""
Created on Sat Jun 29 23:25:55 2013
@author: shane
"""
import glob
import logging
import os
import shutil
import subprocess
from xml.etree import ElementTree
SETTINGSFILE = "settings.xml"
HANDBRAKECOMMAND = ['HandBrakeCLI', '--verbose', '-i', '"{0}"', '-o', '"{1}"',
'-f', 'mkv', '-e', 'x264', '-x264-preset', 'slower',
'-x264-tune', 'animation', '-q', '20',
'--loose-anamorphic', '--decomb', '--detelecine',
'--denoise="2:1:2:3"', '--deblock']
TVRECORDINGSDIR = "/srv/storage2/videos/TVRecordings/"
LOGFILE = "encoding.log"
ACTIONLOG = "needsAction.log"
class TVShow:
def __init__(self, name, inputDirectory, outputDirectory):
self.name = name
self.inputDirectory = inputDirectory
self.outputDirectory = outputDirectory
def LoadSettings(source):
shows = []
settingsXml = ElementTree.parse(source).getroot()
for show in settingsXml.findall('show'):
newShow = TVShow(show[0].text, show[1].text, show[2].text)
shows.append(newShow)
return shows
def FindSeason(path, fileName):
season = "Season {0}".format(fileName[1:3])
seasonPath = os.path.join(path, season)
if not os.path.exists(seasonPath):
os.makedirs(seasonPath)
return seasonPath
def GetRecordingFile(file):
return os.path.join(TVRECORDINGSDIR, os.path.dirname(inputFile).split("/")[-1] + ".mpg")
def CheckOldOutputFileExists(file, myLogger):
myLogger.debug("Searching for existing file: {0}".format(file[:-3]+"*"))
return glob.glob(file[:-3]+"*")
def CreateLogger(name, filename, level):
logger = logging.getLogger(name)
handler = logging.FileHandler(filename)
formatter = logging.Formatter('%(asctime)s %(message)s')
handler.setFormatter(formatter)
handler.setLevel(level)
logger.addHandler(handler)
return logger
#generalLogger.basicConfig(filename=LOGFILE, level=logging.DEBUG, format='%(asctime)s %(message)s')
#actionLogger = logging.getLogger("action")
#actionLogger.basicConfig(filename=ACTIONLOG, level=logging.INFO, format='%(asctime)s %(message)s')
logging.basicConfig(level=logging.DEBUG)
generalLogger = CreateLogger("general", LOGFILE, logging.DEBUG)
actionLogger = CreateLogger("action", ACTIONLOG, logging.INFO)
generalLogger.debug("Loading settings from {0}".format(SETTINGSFILE))
shows = LoadSettings(SETTINGSFILE)
for show in shows:
generalLogger.info("Processing {0}".format(show.name))
fileList = []
for r,d,f in os.walk(show.inputDirectory):
for files in f:
if files.endswith(".mpg"):
fileList.append(os.path.join(r,files))
for inputFile in fileList:
generalLogger.info("Processing file {0}".format(inputFile))
inFile = os.path.basename(inputFile)
outFilename = inFile[:-3]+"mkv"
outPath = FindSeason(show.outputDirectory, outFilename)
outFile = os.path.join(outPath, outFilename)
generalLogger.debug("Output file is {0}".format(outFile))
if os.path.isfile(outFile):
message = "File {0} already exists. Not processing any further.".format(outFile)
generalLogger.warning(message)
actionLogger.info(message)
else:
existingFile = CheckOldOutputFileExists(outFile, generalLogger)
generalLogger.debug("Search returned {0}".format(existingFile))
if len(existingFile) > 0:
message = "There is an existing version of {0} at {1}.".format(outFilename, existingFile[0])
generalLogger.info(message)
actionLogger.info(message)
HANDBRAKECOMMAND[3] = inputFile
HANDBRAKECOMMAND[5] = outFile
generalLogger.debug("Handbrake command is: {0}".format(HANDBRAKECOMMAND))
po = subprocess.Popen(HANDBRAKECOMMAND)
po.wait()
generalLogger.info("Handbrake completed with return code {0}".format(po.returncode))
generalLogger.info("Deleting input files from {0}".format(os.path.dirname(inputFile)))
shutil.rmtree(os.path.dirname(inputFile))
linkAddress = GetRecordingFile(inputFile)
generalLogger.info("Deleting original file from {0}".format(linkAddress))
os.remove(linkAddress)
generalLogger.info("Creating symlink from {0} to {1}".format(linkAddress, outFile))
os.symlink(outFile, linkAddress)
generalLogger.info("Processing completed for {0}".format(inputFile))

5
EmailSettings.cfg Normal file
View File

@@ -0,0 +1,5 @@
SMTPServer = ""
SMTPUser = ""
SMTPPassword = ""
From = ""
To = ""

View File

@@ -1,32 +0,0 @@
import os
from xml.etree import ElementTree
SETTINGSFILE = "settings.xml"
class TVShow:
def __init__(self, name, inputDirectory, outputDirectory):
self.name = name
self.inputDirectory = inputDirectory
self.outputDirectory = outputDirectory
def LoadSettings(source):
shows = []
settingsXml = ElementTree.parse(source).getroot()
for show in settingsXml.findall('show'):
newShow = TVShow(show[0].text, show[1].text, show[2].text)
shows.append(newShow)
return shows
shows = LoadSettings(SETTINGSFILE)
for show in shows:
fileList = []
for r,d,f in os.walk(show.inputDirectory):
for files in f:
if files.endswith(".mpg"):
fileList.append(os.path.join(r,files))
for inputFile in fileList:
print inputFile

View File

@@ -1,154 +0,0 @@
import os
import shutil
import MySQLdb as mdb
import glob
import json
from urllib import urlopen
from fuzzywuzzy import fuzz
from operator import itemgetter
PROCESSDIR="/srv/storage2/files/VideoProcessing/"
THOMAS="Thomas"
CHUGGINGTON="Chuggington"
MIKE="MikeTheKnight"
OCTONAUTS="Octonauts"
NIGHTGARDEN="InTheNightGarden"
RAARAA="RaaRaa"
INPUTDIR="Input"
SICKBEARDAPI="http://192.168.0.2:8081/api/3678177136222bf5002be209220ccb20/"
class TVShow:
def __init__(self, episode, season, title, subtitle, description):
self.episode = episode
self.season = season
self.title = title
self.subtitle = subtitle
self.description = description
def FindShowId(showName):
jsonurl = urlopen(SICKBEARDAPI+"?cmd=shows")
result = json.loads(jsonurl.read())
shows = []
for show in result['data']:
shows.append((show, fuzz.partial_ratio(showName.lower(), result['data'][show]['show_name'].lower())))
shows = sorted(shows, key=itemgetter(1), reverse=True)
if shows[0][1] > 85:
return shows[0][0]
def FindEpisode(showId, name=None, description=None):
jsonurl = urlopen("{0}?cmd=show.seasons&tvdbid={1}".format(SICKBEARDAPI, showId))
result = json.loads(jsonurl.read())
for season in result['data']:
for episode in result['data'][season]:
if name is not None and name.lower() == result['data'][season][episode]['name'].lower():
return (season, episode)
elif description is not None:
result = FindEpisodeByDescription(showId, season, episode, description)
if result is not None:
return result
return (0, 0)
def GetEpisodeName(subtitle, showName):
if subtitle[:len(showName)].lower() == showName.lower():
return subtitle[len(showName + ' and the '):]
else:
return subtitle
def FindEpisodeByDescription(showId, season, episode, description):
jsonEpisodeUrl = urlopen("{0}?cmd=episode&tvdbid={1}&season={2}&episode={3}".format(SICKBEARDAPI, showId, season, episode))
episodeResult = json.loads(jsonEpisodeUrl.read())
if fuzz.ratio(episodeResult['data']['description'].lower(), description.lower()) > 85:
return (season, episode)
return None
def DetermineTargetFilename(directory, filename, inputFilename):
dir = os.path.join(directory, inputFilename[:-4])
if not os.path.exists(dir):
os.makedirs(dir)
return os.path.join(dir, filename)
def ProcessKnownEpisode(directory, filename, inputFilename):
target = DetermineTargetFilename(directory, filename, inputFilename)
shutil.move(inputFilename, target)
def ProcessUnknownEpisode(inputFilename):
print "do this"
def RetrieveEpisodeData(inputFile):
con = mdb.connect('localhost', 'script', 'script', 'mythconverg')
with con:
cur = con.cursor(mdb.cursors.DictCursor)
cur.execute("select episode, season, title, subtitle, description from mythconverg.recorded where basename = '{0}'".format(inputFile))
result = cur.fetchone()
return TVShow(result['episode'], result['season'], result['title'], result['subtitle'], result['description'])
def FixEpisodeSeasonNumber(number):
if number < 10:
return "0{0}".format(number)
else:
return str(number)
def GetDirectory(title, season):
directory = ""
if title == "Thomas and Friends" or title == "Thomas the Tank Engine & Friends":
directory = THOMAS
elif title == "Chuggington":
directory = CHUGGINGTON
elif title == "Mike the Knight":
directory = MIKE
elif title == "Octonauts" or title == "The Octonauts":
directory = OCTONAUTS
elif title == "In the Night Garden":
directory = NIGHTGARDEN
elif title == "Raa Raa! The Noisy Lion":
directory = RAARAA
else:
print "Didn't match"
return os.path.join(PROCESSDIR, directory, INPUTDIR, season)
def ProcessEpisode(inputFile):
show = RetrieveEpisodeData(inputFile)
if show.title:
if show.subtitle:
show.subtitle = GetEpisodeName(show.subtitle, show.title)
if (show.season == '0' or show.episode == '0'):
showId = FindShowId(show.title)
result = FindEpisode(showId, show.subtitle, show.description)
show.season = result[0]
show.episode = result[1]
if show.season != "0" and show.episode != "0":
show.season = FixEpisodeSeasonNumber(show.season)
show.episode = FixEpisodeSeasonNumber(show.episode)
seasonFolder = "Season {0}".format(show.season)
season = "S{0}".format(show.season)
episode = "E{0}".format(show.episode)
renamedFile = "{0}{1} - {2} - SD TV_.mpg".format(season, episode, show.subtitle)
directory = GetDirectory(show.title, seasonFolder)
ProcessKnownEpisode(directory, renamedFile, os.path.basename(inputFile))
else:
print "no show name"
def GetFilesToProcess():
return glob.glob("*.mpg")
for file in GetFilesToProcess():
ProcessEpisode(file)

View File

@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
"""
Created on Thu Jul 04 14:40:38 2013
@author: shane
"""
import ProcessRecordings
import unittest
from minimock import mock
import os.path
class ProcessRecordingsTest(unittest.TestCase):
def test_fixnumber(self):
result = ProcessRecordings.FixEpisodeSeasonNumber(1)
self.assertEqual("01", result)
def test_fixnumber2(self):
result = ProcessRecordings.FixEpisodeSeasonNumber(9)
self.assertEqual("09", result)
def test_fixnumber3(self):
result = ProcessRecordings.FixEpisodeSeasonNumber(11)
self.assertEqual("11", result)
def test_episodeName(self):
subtitle = 'Mike the Knight and the Test Case'
title = 'Mike the Knight'
result = ProcessRecordings.GetEpisodeName(subtitle, title)
self.assertEqual('Test Case', result)
def test_episodeName2(self):
subtitle = 'Test Case 2'
title = 'Mike the Knight'
result = ProcessRecordings.GetEpisodeName(subtitle, title)
self.assertEqual('Test Case 2', result)
def test_GetDirectoryThomas(self):
title = 'Thomas and Friends'
season = 'Season 01'
result = ProcessRecordings.GetDirectory(title, season)
self.assertEqual("/srv/storage2/files/VideoProcessing/Thomas/Input/Season 01", result)
def test_GetDirectoryThomas2(self):
title = 'Thomas the Tank Engine & Friends'
season = 'Season 01'
result = ProcessRecordings.GetDirectory(title, season)
self.assertEqual("/srv/storage2/files/VideoProcessing/Thomas/Input/Season 01", result)
def test_GetDirectoryChuggington(self):
title = 'Chuggington'
season = 'Season 02'
result = ProcessRecordings.GetDirectory(title, season)
self.assertEqual("/srv/storage2/files/VideoProcessing/Chuggington/Input/Season 02", result)
def test_DetermineTargetFilename(self):
directory = '/srv/storage2/test/Input'
filename = 'S01E02 - test episode - SD TV_.mpg'
inputFilename = '123456.mpg'
mock('os.path.exists', returns=True)
result = ProcessRecordings.DetermineTargetFilename(directory, filename, inputFilename)
self.assertEqual('/srv/storage2/test/Input/123456/S01E02 - test episode - SD TV_.mpg', result)
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(ProcessRecordingsTest)
unittest.TextTestRunner(verbosity=2).run(suite)

1
README
View File

@@ -0,0 +1 @@
Testing README file

204
TVEncoder.py Normal file
View File

@@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:14:22 2013
@author: shanef
"""
import sys
import getopt
from libfilemanager import FileManager
from libsettings import Settings
import libhandbrake
import libemail
from libtvdatasource import TVData
from collections import namedtuple
from termcolor import colored
import logging
SETTINGS = "settings.cfg"
EMAIL_SETTINGS = "EmailSettings.cfg"
def showhelp():
"""
Prints the command lines switches that are valid for the program.
"""
print 'TVEncoder.py -p -n <number of files to prepare for processing> ' \
'- prepare n recordings'
print 'TVEncoder.py -p -l -n <number of files to process> - lists the ' \
'files that will be processed without actually encoding them'
print 'TVEncoder.py -e - encode the files that have been processed'
print 'TVEncoder.py -e -l - list the files that would be encoded'
print 'TVEncoder.py -c - check the output directories for duplicates'
def print_shows(shows):
"""
Prints he details of the shows.
"""
okshows = []
noepisodes = []
existingfiles = []
for show in shows:
showstr = str(show)
errors = show.checkproblems()
if not errors:
okshows.append(showstr)
elif "NO_EPISODE" in errors:
noepisodes.append(showstr)
elif "FILE_EXISTS" in errors:
existingfiles.append(showstr)
for show in okshows:
print show
if noepisodes:
print colored("\nDetails of the episode could not be determined for "
"the following shows:", 'red')
for show in noepisodes:
print colored(show, 'red')
if existingfiles:
print colored("\nThe following shows have a pre-existing "
"output file:", 'red')
for show in existingfiles:
print colored(show, 'red')
def processarguments(options):
"""
Determine the actions required from the input flags
"""
inputoptions = namedtuple("inputoptions",
"numfiles doencode readonly dolist "
"checkduplicates")
inputoptions.readonly = False
inputoptions.checkduplicates = False
for opt, arg in options:
if opt == '-h':
showhelp()
sys.exit()
elif opt == "-p":
inputoptions.doencode = False
elif opt == "-e":
inputoptions.doencode = True
elif opt == "-n":
inputoptions.numfiles = arg
elif opt == "-l":
inputoptions.readonly = True
elif opt == "-c":
inputoptions.checkduplicates = True
return inputoptions
def main(argv):
"""
The main program for TVEncoder.
"""
try:
opts, _ = getopt.getopt(argv, "hlpecn:")
except getopt.GetoptError:
showhelp()
sys.exit(2)
inputoptions = processarguments(opts)
settings = Settings(SETTINGS)
filemanager = FileManager(settings)
if inputoptions.checkduplicates:
print "Searching for duplicates..."
duplicates = filemanager.checkexistingduplicates()
if duplicates:
for duplicate in duplicates:
print duplicate
else:
print "No duplicates found."
return
if inputoptions.readonly:
if inputoptions.doencode:
#Generate the list of files that would be encoded
showdata = filemanager.getencodingfiles(inputoptions.readonly)
print_shows(showdata)
else:
# Generate the list of files to process
shows = filemanager.getfilestoprepare(inputoptions.numfiles)
print "num results: {0}".format(len(shows))
print_shows(shows)
else:
if inputoptions.doencode:
#Encode the files and move them to their final destination
logging.basicConfig(level=logging.DEBUG)
generallogger = createlogger("general", settings.generallogfile(),
logging.DEBUG)
actionlogger = createlogger("action", settings.actionlogfile(),
logging.INFO)
showdata = filemanager.getencodingfiles(inputoptions.readonly)
generallogger.info("There are {0} files to process."
.format(len(showdata)))
for show in showdata:
generallogger.info("========================================")
generallogger.info("Processing {0} of {1}, {2}".format(
showdata.index(show) + 1, len(showdata), str(show)))
if filemanager.checkfileexists(show.outputfile):
message = "File {0} already exists. Cannot process." \
.format(show.outputfile)
generallogger.warning(message)
actionlogger.warning(message)
else:
result = libhandbrake.encode(settings.handbrakecommand(),
show.inputfile,
show.outputfile)
generallogger.info("Encode finished with result: {0}"
.format(result))
filemanager.performpostencodefileoperations(
show.inputfile, show.outputfile)
if filemanager.checkduplicates(show.outputfile):
actionlogger.info("There is an existing video file"
"present for {0}"
.format(show.outputfile))
generallogger.info("Processing finished.")
generallogger.info("==========================="
"=============\n\n")
libemail.sendemail(EMAIL_SETTINGS, "Encoding Complete",
"Finished encoding {0} shows."
.format(len(showdata)))
else:
# Process files for encoding
shows = filemanager.getfilestoprepare(inputoptions.numfiles)
print "Preparing {0} files".format(len(shows))
tvdata = TVData(settings)
tvdata.prepareepisodes(shows)
def createlogger(name, filename, level):
"""
Create a logger named <name> that will write to the file <filename>
"""
logger = logging.getLogger(name)
handler = logging.FileHandler(filename, mode='w')
formatter = logging.Formatter('%(asctime)s %(message)s')
handler.setFormatter(formatter)
handler.setLevel(level)
logger.addHandler(handler)
return logger
if __name__ == "__main__":
main(sys.argv[1:])

32
libemail.py Normal file
View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
"""
Created on Sat Jul 20 20:48:10 2013
@author: shanef
"""
from libsettings import EmailSettings
import smtplib
from email.mime.text import MIMEText
def sendemail(settingsfilename, subject, body):
"""
Send an email using the settings defined in settingsfilename
"""
settings = EmailSettings(settingsfilename)
msg = MIMEText(body, "plain")
msg["Subject"] = subject
msg["From"] = settings.getfromaddress()
msg["To"] = settings.gettoaddress()
smtp = smtplib.SMTP(settings.getsmtpserver())
smtp.ehlo()
smtp.starttls()
smtp.login(settings.getsmtpuser(), settings.getsmtppassword())
smtp.sendmail(settings.getfromaddress(), [settings.gettoaddress()],
msg.as_string())
smtp.quit()

244
libfilemanager.py Normal file
View File

@@ -0,0 +1,244 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:11:31 2013
@author: shanef
"""
import glob
from libtvdatasource import TVData
import os
import shutil
class EncodeData:
"""
Contains detais of files to encode.
inputfile - The source file
outputfile - The destination file
show - The name of the show
"""
def __init__(self, show=None, inputfile='', outputfile=''):
self.inputfile = inputfile
self.show = show
self.outputfile = outputfile
def __str__(self):
return "Show: {0}\nInput: {1}\nOutput: " \
"{2}\n".format(self.show, self.inputfile, self.outputfile)
def checkproblems(self):
"""
Check the EncodeData object for any potential problems.
"""
errors = []
if checkfileexists(self.outputfile, False):
errors.append("FILE_EXISTS")
if self.outputfile[-5:-4] == "_":
tempoutfile = self.outputfile[:-5] + self.outputfile[-4:]
if checkfileexists(tempoutfile, False):
errors.append("FILE_EXISTS")
return errors
class FileManager:
"""
Perform file operations
"""
def __init__(self, settings):
self.__settings = settings
def getencodingfiles(self, readonly=True):
"""
Get the details of the shows that are ready for encoding
"""
showsdata = self.__getinputfilestoencode()
for showdata in showsdata:
showdata.outputfile = self.__getencodeoutputfile(
showdata.inputfile, showdata.show, readonly)
return showsdata
def performpostencodefileoperations(self, inputfilename, outputfilename):
"""
Delete the input file, and the original recorded file. Then create a
symlink from the new encoded file to the original mythtv file.
"""
shutil.rmtree(os.path.dirname(inputfilename))
linkaddress = self.__getrecordingfile(inputfilename)
os.remove(linkaddress)
os.symlink(outputfilename, linkaddress)
def getfilestoprepare(self, numberoffiles):
"""
Get the details of the first <numberoffiles> to prepare for encoding.
If there are less files than <numberoffiles> available, it will
return the details of the number available.
"""
path = self.__settings.tvrecordingdirectory()
potentialfiles = glob.glob("{0}*.mpg".format(path))
potentialfiles = sorted(potentialfiles, key=os.path.getctime)
potentialfiles = [potentialfile for potentialfile in potentialfiles
if not os.path.islink(potentialfile)]
#files is now a list of unprocessed files, but contains shows other
#than those we are interested in
showstoprocess = []
i = 0
print "Found {0} potential files".format(len(potentialfiles))
tvdata = TVData(self.__settings)
for potentialfile in potentialfiles:
showdata = tvdata.retrieveepisodedata(potentialfile)
if showdata:
showstoprocess.append(showdata)
i = i + 1
if i == int(numberoffiles):
return showstoprocess
#will reach here if there were less than numberofFiles found
return showstoprocess
def checkexistingduplicates(self):
"""
Check the existing files in the output directories for duplicate
files, typically in different formats
"""
duplicates = []
for show in self.__settings.getshownames():
outputdir = self.__settings.getshowoutputdirectory(show)
for rootdir, dirnames, filenames in os.walk(outputdir):
for fle in filenames:
filename = os.path.join(rootdir, fle)
if os.path.splitext(fle)[1].lower() in [".avi", ".mpg", ".mpeg",
"mp4", ".mkv"]:
if self.checkduplicates(filename):
duplicates.append(filename)
return sorted(duplicates)
@staticmethod
def checkduplicates(filename):
"""
Check to see if there are any other video files existing for the
episode
"""
dirname = os.path.dirname(filename)
filename = os.path.basename(filename)
fileseasonepisode = filename[:6]
fileextension = os.path.splitext(filename)[1]
for _, _, filenames in os.walk(dirname):
for show in filenames:
extension = os.path.splitext(show)[1]
if (extension.lower() in [".avi", ".mpg", ".mpeg", "mp4", ".mkv"] and
show[:6] == fileseasonepisode
and fileextension != extension):
return True
return False
@staticmethod
def checkfileexists(filename, casesensitive=True):
"""
Check to see if a file currently exists
"""
if casesensitive:
return os.path.exists(filename)
else:
filename = os.path.basename(filename)
for dirfile in os.listdir(os.path.dirname(filename)):
if (filename.lower() == dirfile.lower()):
return True
return False
def __getinputfilestoencode(self):
"""
Get the details of the files that are waiting to be encoded
"""
filelist = []
for show in self.__settings.getshownames():
for dirpath, _, filenames in os.walk(
self.__settings.getshowinputdirectory(show)):
for inputfile in filenames:
if inputfile.endswith(".mpg"):
data = EncodeData(show, os.path.join(
dirpath, inputfile))
filelist.append(data)
return filelist
def __getencodeoutputfile(self, inputfile, showname, readonly):
"""
Get the full path of the output filename to save the encoded video to
"""
infile = os.path.basename(inputfile)
outfilename = infile[:-3]+"mkv"
outpath = findseason(self.__settings.getshowoutputdirectory(
showname), outfilename, readonly)
return os.path.join(outpath, outfilename)
def __getrecordingfile(self, filename):
"""
Get the name of the mythtv recording based on the filename. The
filename contains the name of the mythtv recording as the
final directory in it's path.
"""
return os.path.join(self.__settings.tvrecordingdirectory(),
os.path.dirname(filename).split("/")[-1] + ".mpg")
def findseason(path, filename, readonly):
"""
Get the name of the season folder. eg. Season 01
"""
season = "Season {0}".format(filename[1:3])
seasonpath = os.path.join(path, season)
if not readonly:
if not os.path.exists(seasonpath):
os.makedirs(seasonpath)
return seasonpath
def checkfileexists(filename, casesensitive=True):
"""
Check to see if a file currently exists
"""
dirname = os.path.dirname(filename)
if casesensitive:
return os.path.exists(filename)
else:
if not os.path.exists(dirname):
return False
basename = os.path.basename(filename)
for dirfile in os.listdir(dirname):
if (basename.lower() == dirfile.lower()):
return True
return False

36
libhandbrake.py Normal file
View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:11:00 2013
@author: shanef
Library to interface with handbrake to encode video files
"""
import subprocess
def encode(handbrakecommand, inputfile, outputfile, waitforcompletion=True,
logger=None):
"""
Encode inputfile and save the result to outputfile. handbrakecommand is
a list of strings containing the arguments to handbrakecli.
"""
handbrakecommand[3] = inputfile
handbrakecommand[5] = outputfile
if logger:
logger.debug("Handbrake command is: {0}".format(handbrakecommand))
process = subprocess.Popen(handbrakecommand)
if waitforcompletion:
process.wait()
if logger is not None:
logger.info("Handbrake completed with return code {0}".format(
process.returncode))
return process.returncode
return None

53
libmythtv.py Normal file
View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:10:47 2013
@author: shanef
"""
import MySQLdb as mdb
from libtvshow import TVShow
class MythTV:
"""
Contains methods used for interacting with mythtv
"""
def __init__(self, settings):
self.__settings = settings
def retrieveepisodedata(self, inputfile):
"""
Retrieve the data that mythtv knows about the recorded file.
"""
con = mdb.connect(self.__settings.mythtvaddress(),
self.__settings.mythtvuser(),
self.__settings.mythtvpassword(),
self.__settings.mythtvdatabase())
with con:
cur = con.cursor(mdb.cursors.DictCursor)
cur.execute("select episode, season, title, subtitle, "
"description from mythconverg.recorded where "
"basename = '{0}'".format(inputfile))
result = cur.fetchone()
return TVShow(result['episode'], result['season'],
result['title'], result['subtitle'],
result['description'])
def fixmythtvepisodename(self, showname, episodetitle):
"""
Look for any prefixes listed in the configuration file. If there are
any and the episide title starts with the prefix, remove the prefix
from the episode title. The searching is done in the order that the
prefixes are listed in the configuration file.
"""
for prefix in self.__settings.getshowmythtvepisodeprefix(showname):
if episodetitle.lower().startswith(prefix.lower()):
return episodetitle[len(prefix):]
#didn't find anything so return the episode title
return episodetitle

287
libsettings.py Normal file
View File

@@ -0,0 +1,287 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 20:14:15 2013
@author: shanef
"""
from configobj import ConfigObj
class Settings:
"""
Accessor for the configuration file
"""
def __init__(self, settingsfile):
"""
Initialise settingsfile as a configobj
"""
self.__config = ConfigObj(settingsfile)
def tvrecordingdirectory(self):
"""
Get the TVRecordings setting
"""
return self.__config["TVRecordings"]
def handbrakecommand(self):
"""
Get the HandbrakeCommand setting
"""
return self.__config["HandbrakeCommand"]
def illegalcharacters(self):
"""
Get a list of illegal characters for filenames
"""
return self.__config["IllegalCharacters"]
def generallogfile(self):
"""
Get the filename to save general log messages to
"""
return self.__config["Logging"]["General"]
def actionlogfile(self):
"""
Get the filename to save the action log messages to
"""
return self.__config["Logging"]["Action"]
def mythtvaddress(self):
"""
Get the MythTV/address setting
"""
return self.__config["MythTV"]["address"]
def mythtvuser(self):
"""
Get the MythTV/user setting
"""
return self.__config["MythTV"]["user"]
def mythtvpassword(self):
"""
Get the MythTV/password setting
"""
return self.__config["MythTV"]["password"]
def mythtvdatabase(self):
"""
Get the MythTV/database setting
"""
return self.__config["MythTV"]["database"]
def sickbeardaddress(self):
"""
Get the Sickbeard/address setting
"""
return self.__config["Sickbeard"]["address"]
def sickbeardport(self):
"""
Get the Sickbeard/port setting
"""
return int(self.__config["Sickbeard"]["port"])
def sickbeardapikey(self):
"""
Get the Sickbeard/APIKey setting
"""
return self.__config["Sickbeard"]["APIKey"]
def unknowndirectory(self):
"""
Get the Shows/UnknownInput directory. It is the directory used for
episodes where nothing is known about it
"""
return self.__config["Shows"]["UnknownInput"]
def getshownames(self, includealias=False):
"""
Get a list of the names of the shows that are specified in the
settings file. If includealias is True, it will also include any
defined aliases in the list.
"""
shows = self.__config["Shows"].sections
result = shows[:]
if includealias:
for show in shows:
for alias in self.__config["Shows"][show]["alias"]:
result.append(alias)
return result
def findshownameforalias(self, aliasname):
"""
Find the name of the show. If the supplied aliasname is an alias, it
will return the show name. If aliasname is the name of a show, it will
return aliasname
"""
if aliasname in self.getshownames():
# aliasname is the name of an actual show
return aliasname
# search for the show that the alias belongs to
for showsettings in self.__config["Shows"]:
if aliasname in showsettings["alias"]:
return showsettings.name
# Could not find it anywhere
return None
def getshowinputdirectory(self, showname):
"""
Get the InputDirectory setting for the show, showname.
"""
show = self.__getshowsubsection(showname)
if show is None:
return ""
else:
return show["InputDirectory"]
def getshowunknowndirectory(self, showname):
"""
Get the UnknownDirectory setting for the show, showname. It is used
when the show is known, but the season or episode are not.
"""
show = self.__getshowsubsection(showname)
if show is None:
return ""
else:
return show["UnknownDirectory"]
def getshowoutputdirectory(self, showname):
"""
Get the OutputDirectory setting for the show, showname.
"""
show = self.__getshowsubsection(showname)
if show is None:
return ""
else:
return show["OutputDirectory"]
def getshowalias(self, showname):
"""
Get the alias setting for the show, showname. It returns a list of
aliases.
"""
show = self.__getshowsubsection(showname)
if show is None:
return ""
else:
return show["alias"]
def getshowmythtvepisodeprefix(self, showname):
"""
Get the MythTVEpisodePrefix setting for the show, showname.
"""
show = self.__getshowsubsection(showname)
if show is None:
return ""
else:
return show["MythTvEpisodePrefix"]
def getshowsickbeardepisodeprefix(self, showname):
"""
Get the SickbeardPrefix setting for the show, showname.
"""
show = self.__getshowsubsection(showname)
if show is None:
return ""
else:
return show["SickbeardPrefix"]
def getshow(self, showname):
"""
Get the name of the show, showname.
"""
showsection = self.__getshowsubsection(showname)
if showsection is None:
return None
else:
return showsection.name
def __getshowsubsection(self, showname):
"""
Get the configuration options for the show, showname.
"""
if showname in self.getshownames():
return self.__config["Shows"][showname]
else: # check liases
for show in self.getshownames():
if showname in self.__config["Shows"][show]["alias"]:
return self.__config["Shows"][show]
return None
class EmailSettings:
"""
Accessor for the email configuration file
"""
def __init__(self, settingsfile):
"""
Initialise settingsfile as a configobj
"""
self.__config = ConfigObj(settingsfile)
def getsmtpserver(self):
"""
Get the address of the smtp server
"""
return self.__config["SMTPServer"]
def getsmtpuser(self):
"""
Get the username for the smtp server
"""
return self.__config["SMTPUser"]
def getsmtppassword(self):
"""
Get the username for the smtp server
"""
return self.__config["SMTPPassword"]
def getfromaddress(self):
"""
Get the from address for emails
"""
return self.__config["From"]
def gettoaddress(self):
"""
Get the to address for emails
"""
return self.__config["To"]

153
libsickbeard.py Normal file
View File

@@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:10:37 2013
@author: shanef
"""
import json
from urllib import urlopen
from fuzzywuzzy import fuzz
from operator import itemgetter
class Sickbeard:
"""
Contains operations used to interact with sickbeard
"""
def __init__(self, settings):
self.__settings = settings
self.__address = settings.sickbeardaddress()
self.__port = settings.sickbeardport()
self.__apikey = settings.sickbeardapikey()
def findshowid(self, showname):
"""
Get the tvdb show id for the show
"""
jsonurl = urlopen(self.__getapiurl()+"?cmd=shows")
result = json.loads(jsonurl.read())
showname = self.__settings.findshownameforalias(showname)
shows = []
for show in result['data']:
shows.append((show, fuzz.partial_ratio(showname.lower(),
result['data'][show]
['show_name'].lower())))
shows = sorted(shows, key=itemgetter(1), reverse=True)
if shows[0][1] > 85:
return shows[0][0]
def findepisodename(self, showid, season, episode):
"""
Get the name of an episode, given it's season and episode numbers
"""
jsonurl = urlopen("{0}?cmd=episode&tvdbid={1}&season={2}"
"&episode={3}".format(self.__getapiurl(), showid,
int(season), int(episode)))
result = json.loads(jsonurl.read())
if result['result'] == 'error':
return ""
else:
return result['data']['name']
def findepisode(self, showid, name=None, description=None):
"""
Find an episode, either by it's name or it's description. This is used
when the season and episode numbers are not known
"""
jsonurl = urlopen("{0}?cmd=show.seasons&tvdbid={1}".format(
self.__getapiurl(), showid))
result = json.loads(jsonurl.read())
for season in result['data']:
for episode in result['data'][season]:
episodename = result['data'][season][episode]['name']
if name is not None and fuzz.ratio(name.lower(),
episodename.lower()) > 85:
return (season, episode, episodename)
elif description is not None:
descriptionqueryresult = \
self.__findepisodebydescription(showid, season,
episode, description)
if descriptionqueryresult is not None:
return descriptionqueryresult
return (0, 0, '')
def fixepisodetitle(self, showname, episodetitle):
"""
Check to see if there is a prefix specified for the show. If there is,
add the prefix to the start of the episode title
"""
sickbeardprefix = \
self.__settings.getshowsickbeardepisodeprefix(showname)
if sickbeardprefix != "":
if not episodetitle.lower().startswith(sickbeardprefix.lower()):
return "{0} {1}".format(sickbeardprefix.rstrip(),
episodetitle.lstrip())
return episodetitle
def __getapiurl(self):
"""
Get the url of the sickbeard api, substituting the values from the
settings
"""
return "http://{0}:{1}/api/{2}/".format(self.__address, self.__port,
self.__apikey)
def __findepisodebydescription(self, showid, season, episode, description):
"""
Find the details of an episode by searching for it's description
"""
jsonepisodeurl = urlopen("{0}?cmd=episode&tvdbid={1}&season={2}"
"&episode={3}".format(self.__getapiurl(),
showid, season,
episode))
episoderesult = json.loads(jsonepisodeurl.read())
sickbearddescription = episoderesult['data']['description']
if fuzzystringcompare(sickbearddescription, description):
return (season, episode, episoderesult['data']['name'])
return None
def fuzzystringcompare(string1, string2, matchvalue=85, casesensitive=False):
"""
Compare two strings to see if they match it first does a straight
comparison. Secondly, it concatenates the longer string to the length of
the shorter one, and tries to compare them again.
"""
if not casesensitive:
string1 = string1.lower()
string2 = string2.lower()
if fuzz.ratio(string1, string2) > matchvalue:
return True
if len(string1) > len(string2):
if fuzz.ratio(string1[:len(string2)], string2) > matchvalue:
return True
elif len(string2) > len(string1):
if fuzz.ratio(string1, string2[:len(string1)]) > matchvalue:
return True
return False

144
libtvdatasource.py Normal file
View File

@@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:42:47 2013
@author: shanef
"""
from libmythtv import MythTV
from libsickbeard import Sickbeard
import os
import shutil
def fixepisodeseasonnumber(number):
"""
If the number is single digit, return a string with 0 in front of it.
"""
if len(number) == 1:
return "0{0}".format(number)
else:
return number
class TVData:
"""
Class contains logic for processing information about tv episodes
"""
def __init__(self, settings):
self.__settings = settings
def getdirectory(self, title, seasonfolder, season, episode):
"""
Get the directory where prepared episodes will be located.
"""
show = self.__settings.getshow(title)
if not show or show == "":
print "Couldn't find show for {0}".format(title)
return self.__settings.unknowndirectory()
elif season == "S00" or episode == "E00":
return self.__settings.getshowunknowndirectory(show)
else:
return os.path.join(self.__settings.getshowinputdirectory(show),
seasonfolder)
def retrieveepisodedata(self, inputfile):
"""
Retrieve the details of an episode. It first looks up the details that
mythtv recorded about it, then looks up sickbeard to attempt to find
any missing details. Finally it determined the output file for it.
"""
inputfilename = os.path.basename(inputfile)
mythtv = MythTV(self.__settings)
show = mythtv.retrieveepisodedata(inputfilename)
showstoprocess = self.__settings.getshownames(True)
if show.title and show.title in showstoprocess:
show.title = self.__settings.getshow(show.title)
if (show.season == "0" or show.episode == "0"):
sickbeard = Sickbeard(self.__settings)
showid = sickbeard.findshowid(show.title)
if show.subtitle is not None and show.subtitle:
show.subtitle = mythtv.fixmythtvepisodename(show.title,
show.subtitle)
show.subtitle = sickbeard.fixepisodetitle(show.title,
show.subtitle)
result = sickbeard.findepisode(showid, show.subtitle,
show.description)
show.season = str(result[0])
show.episode = str(result[1])
show.subtitle = result[2]
if show.subtitle is None or show.subtitle == "":
show.subtitle = sickbeard.findepisodename(showid, show.season,
show.episode)
show.season = fixepisodeseasonnumber(show.season)
show.episode = fixepisodeseasonnumber(show.episode)
seasonfolder = "Season {0}".format(show.season)
season = "S{0}".format(show.season)
episode = "E{0}".format(show.episode)
renamedfile = self.getoutputfilename(season, episode,
show.subtitle)
directory = self.getdirectory(show.title, seasonfolder,
season, episode)
show.outputfile = os.path.join(directory, inputfilename[:-4],
renamedfile)
show.inputfile = inputfile
return show
else:
return None
def getoutputfilename(self, season, episode, name):
"""
Get the output filename, and remove any illegal characters
"""
filename = "{0}{1} - {2} - SD TV_.mpg".format(season, episode, name)
for illegalcharacter in self.__settings.illegalcharacters():
filename = filename.replace(illegalcharacter, "")
return filename
@staticmethod
def processepisode(inputfile, outputfile):
"""
Copy inputfile to outputfile, creating the path for outputfile if
required.
"""
outputdir = os.path.dirname(outputfile)
if not os.path.exists(outputdir):
os.makedirs(outputdir)
shutil.copyfile(inputfile, outputfile)
def prepareepisodes(self, showsdata):
"""
Copy the files in showsdata from their input directory to their output
directory.
"""
for showdata in showsdata:
print "========================================"
print "Copying {0} to {1}".format(showdata.inputfile,
showdata.outputfile)
self.processepisode(showdata.inputfile, showdata.outputfile)
print "Finished copy"
print "========================================\n\n"

43
libtvshow.py Normal file
View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
"""
Created on Sat Jul 6 20:26:22 2013
@author: shanef
"""
import os
#from libfilemanager import FileManager
class TVShow(object):
"""
Describes the details of a tv episode
"""
def __init__(self, episode, season, title, subtitle, description,
inputfile='', outputfile=''):
self.episode = str(episode)
self.season = str(season)
self.title = title
self.subtitle = subtitle
self.description = description
self.inputfile = inputfile
self.outputfile = outputfile
def __str__(self):
return "Input: {0} -> Output: {1}".format(self.inputfile,
self.outputfile)
def checkproblems(self):
"""
Check the TVShow object for any potential problems.
"""
errors = []
if self.episode == "E00" or self.season == "S00" or not self.subtitle:
errors.append("NO_EPISODE")
if os.path.exists(self.outputfile):
errors.append("FILE_EXISTS")
return errors

3
pep8.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
pep8 libemail.py TVEncoder.py libfilemanager.py libhandbrake.py libmythtv.py libsettings.py libsickbeard.py libtvdatasource.py libtvshow.py

3
pylint.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
pylint TVEncoder.py libfilemanager.py libhandbrake.py libmythtv.py libsettings.py libsickbeard.py libtvdatasource.py libtvshow.py libemail.py

72
settings.cfg Normal file
View File

@@ -0,0 +1,72 @@
TVRecordings = "/Volumes/TV Recordings/"
HandbrakeCommand = "HandBrakeCLI", "--verbose", "-i", "SUBSTITUTE WITH INPUT FILE", "-o", "SUBSTITUDE WITH OUTPUT FILE", "-f", "mkv", "-e", "x264", "-x264-preset", "slower", "-x264-tune", "animation", "-q", "20", "--loose-anamorphic", "--decomb", "--detelecine", '--denoise="2:1:2:3"', "--deblock"
IllegalCharacters = "?", ":"
[ "Logging" ]
General = "logs/encoding.log"
Action = "logs/needsaction.log"
[ "MythTV" ]
address = 192.168.0.2
user = script
password = script
database = mythconverg
[ "Sickbeard" ]
address = 192.168.0.2
port = 8081
APIKey = 3678177136222bf5002be209220ccb20
[ "Shows" ]
VideoProcessingDir = "/srv/storage2/files/VideoProcessing/"
KidsTVDir = "/srv/storage2/videos/Kids/TV/"
UnknownInput = "%(VideoProcessingDir)sUnknown/"
[[ "Thomas the Tank Engine & Friends" ]]
InputDirectory = "%(VideoProcessingDir)sThomas/"
UnknownDirectory = "%(UnknownInput)sThomas/"
OutputDirectory = "%(KidsTVDir)sThomas The Tank Engine & Friends/"
alias = "Thomas and Friends",
MythTvEpisodePrefix = ,
SickbeardPrefix = ""
[[ "Chuggington" ]]
InputDirectory = "%(VideoProcessingDir)sChuggington/"
UnknownDirectory = "%(UnknownInput)sChuggington/"
OutputDirectory = "%(KidsTVDir)sChuggington/"
alias = ,
MythTvEpisodePrefix = ,
SickbeardPrefix = ""
[[ "Mike the Knight" ]]
InputDirectory = "%(VideoProcessingDir)sMikeTheKnight/"
UnknownDirectory = "%(UnknownInput)sMikeTheKnight/"
OutputDirectory = "%(KidsTVDir)sMike the Knight/"
alias = ,
MythTvEpisodePrefix = "Mike the Knight and the ", Mike the Knight and "
SickbeardPrefix = ""
[[ "Octonauts" ]]
InputDirectory = "%(VideoProcessingDir)sOctonauts/"
UnknownDirectory = "%(UnknownInput)sOctonauts/"
OutputDirectory = "%(KidsTVDir)sOctonauts/"
alias = "The Octonauts",
MythTvEpisodePrefix = "The Octonauts and ",
SickbeardPrefix = "The"
[[ "In the Night Garden" ]]
InputDirectory = "%(VideoProcessingDir)sInTheNightGarden/"
UnknownDirectory = "%(UnknownInput)sInTheNightGarden/"
OutputDirectory = "%(KidsTVDir)sIn The Night Garden/"
alias = ,
MythTvEpisodePrefix = ,
SickbeardPrefix = ""
[[ "Raa Raa! The Noisy Lion" ]]
InputDirectory = "%(VideoProcessingDir)sRaaRaa/"
UnknownDirectory = "%(UnknownInput)sRaaRaa/"
OutputDirectory = "%(KidsTVDir)sRaa Raa the Noisy Lion/"
alias = ,
MythTvEpisodePrefix = ,
SickbeardPrefix = ""
[[ "Fireman Sam" ]]
InputDirectory = "%(VideoProcessingDir)sFiremanSam/"
UnknownDirectory = "%(UnknownInput)sFiremanSam/"
OutputDirectory = "%(KidsTVDir)sFireman Sam/"
alias = ,
MythTvEpisodePrefix = ,
SickbeardPrefix = ""

View File

@@ -1,68 +0,0 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Jul 2 21:00:35 2013
@author: shane
"""
import json
from urllib import urlopen
from fuzzywuzzy import fuzz
from operator import itemgetter
def FindShowId(showName):
jsonurl = urlopen("http://192.168.0.2:8081/api/3678177136222bf5002be209220ccb20/?cmd=shows")
result = json.loads(jsonurl.read())
shows = []
for show in result['data']:
shows.append((show, fuzz.partial_ratio(showName.lower(), result['data'][show]['show_name'].lower())))
shows = sorted(shows, key=itemgetter(1), reverse=True)
if shows[0][1] > 85:
return shows[0][0]
def FindEpisode(showId, name=None, description=None):
jsonurl = urlopen("http://192.168.0.2:8081/api/3678177136222bf5002be209220ccb20/?cmd=show.seasons&tvdbid={0}".format(showId))
result = json.loads(jsonurl.read())
for season in result['data']:
for episode in result['data'][season]:
if name is not None and name.lower() == result['data'][season][episode]['name'].lower():
return (season, episode)
elif description is not None:
result = FindEpisodeByDescription(showId, season, episode, description)
if result is not None:
return result
return (0, 0)
def GetEpisodeName(subtitle, showName):
if subtitle[:len(showName)].lower() == showName.lower():
return subtitle[len(showName + ' and the '):]
else:
return subtitle
def FindEpisodeByDescription(showId, season, episode, description):
jsonEpisodeUrl = urlopen("http://192.168.0.2:8081/api/3678177136222bf5002be209220ccb20/?cmd=episode&tvdbid={0}&season={1}&episode={2}".format(showId, season, episode))
episodeResult = json.loads(jsonEpisodeUrl.read())
if fuzz.ratio(episodeResult['data']['description'].lower(), description.lower()) > 85:
return (season, episode)
return None
showId = FindShowId('Mike the Knight')
#showId = FindShowId("Octonauts")
#print showId
subtitle = 'Mike the Knight and the Knightly Campout'
description = "When the Octopod's waterworks are flooded with frightened Humuhumu fish, the Octonauts have to find a way to flush them out!"
episodeName = GetEpisodeName(subtitle, 'Mike the Knight')
result = FindEpisode(showId, episodeName)
#result = FindEpisodeByDescription(showId, description)
print result[0]
print result[1]

46
tests/TVEncodertest.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""
Created on Sat Jul 13 20:37:47 2013
@author: shanef
"""
import unittest
import os
import sys
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parentdir)
import TVEncoder
class TVEncoderTest(unittest.TestCase):
def test_processarguments_encodereadonly(self):
args = []
args.append(('-e', ''))
args.append(('-l', ''))
result = TVEncoder.processarguments(args)
self.assertTrue(result.doencode)
self.assertTrue(result.readonly)
def test_processarguments_encodereadonlyreverse(self):
args = []
args.append(('-l', ''))
args.append(('-e', ''))
result = TVEncoder.processarguments(args)
self.assertTrue(result.doencode)
self.assertTrue(result.readonly)
def test_processarguments_encode(self):
args = []
args.append(('-e', ''))
result = TVEncoder.processarguments(args)
self.assertTrue(result.doencode)
self.assertFalse(result.readonly)
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(TVEncoderTest)
unittest.TextTestRunner(verbosity=2).run(suite)

33
tests/emailtest.py Normal file
View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 19 23:31:16 2013
@author: shanef
"""
from minimock import Mock, mock
import unittest
import os
import sys
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parentdir)
import libemail
from libsettings import EmailSettings
import smtplib
class libemailtest(unittest.TestCase):
def test_SendEmail(self):
mock("EmailSettings.getfromaddress", returns="from@email.com")
mock("EmailSettings.gettoaddress", returns="to@email.com")
mock("EmailSettings.getsmtpserver", returns="smtp.test")
mock("EmailSettings.getsmtpuser", returns="user")
mock("EmailSettings.getsmtppassword", returns="password")
smtplib.SMTP = Mock('smtplib.SMTP')
smtplib.SMTP.mock_returns = Mock('smtp_connection')
libemail.sendemail("test", "subject", "body")
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(libemailtest)
unittest.TextTestRunner(verbosity=2).run(suite)

117
tests/libfilemanagertest.py Normal file
View File

@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:12:26 2013
@author: shanef
"""
import unittest
import os
import sys
import minimock
from minimock import mock, Mock
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parentdir)
from libfilemanager import EncodeData, FileManager
class libfilemanagertest(unittest.TestCase):
def test_EncodeDataPrint(self):
showname = "test show"
inputname = "test input"
outputname = "test output"
data = EncodeData(showname, inputname, outputname)
result = str(data)
expected = "Show: {0}\nInput: {1}\nOutput: " \
"{2}\n".format(showname, inputname, outputname)
self.assertEqual(result, expected)
def test_EncodeDataCheckProblemsFileExists(self):
showname = "test show"
inputname = "test input"
outputname = "test_output.mkv"
data = EncodeData(showname, inputname, outputname)
mock("os.path.exists", returns=True)
result = data.checkproblems()
self.assertIn("FILE_EXISTS", result)
minimock.restore()
def test_EncodeDataCheckProblemsFile_Exists(self):
showname = "test show"
inputname = "test input"
outputname = "test_output_.mkv"
data = EncodeData(showname, inputname, outputname)
mock("os.path.exists", returns_iter=[False, True])
result = data.checkproblems()
self.assertIn("FILE_EXISTS", result)
minimock.restore()
def test_checkfileexistscaseinsensitive(self):
settings = Mock('libsettings.Settings')
filemanager = FileManager(settings)
mock("os.listdir", returns=["filename.test"])
result = filemanager.checkfileexists("/path/to/fiLename.test", False)
self.assertTrue(result)
minimock.restore()
def test_checkduplicateavi(self):
settings = Mock('libsettings.Settings')
filemanager = FileManager(settings)
os.walk = dummywalk
result = filemanager.checkduplicates("/path/to/S03E14 - Test - SD TV.mkv")
self.assertTrue(result)
minimock.restore()
def test_checkduplicatethomas(self):
settings = Mock('libsettings.Settings')
filemanager = FileManager(settings)
os.walk = thomaswalk
result = filemanager.checkduplicates("/path/to/S12E05 - Henry Gets It Wrong - SD TV.mkv")
self.assertTrue(result)
minimock.restore()
def test_checkduplicatenomatch(self):
settings = Mock('libsettings.Settings')
filemanager = FileManager(settings)
os.walk = dummywalk
result = filemanager.checkduplicates("/path/to/S03E13 - Test - SD TV.mkv")
self.assertFalse(result)
minimock.restore()
def test_checkduplicatesameextension(self):
settings = Mock('libsettings.Settings')
filemanager = FileManager(settings)
os.walk = dummywalk
result = filemanager.checkduplicates("/path/to/S03E14 - Test - SD TV.avi")
self.assertFalse(result)
minimock.restore()
def dummywalk(arg):
return [("/path/to/", [], ["S03E14 - Test - SD TV.avi"])]
def thomaswalk(arg):
return [(("/path/to/", [], ["S12E05 - Henry Gets It Wrong - Unknown.AVI"]))]
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(libfilemanagertest)
unittest.TextTestRunner(verbosity=2).run(suite)
minimock.restore()

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:13:19 2013
@author: shanef
"""

55
tests/libmythtvtest.py Normal file
View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:12:53 2013
@author: shanef
"""
import unittest
from minimock import Mock
import os
import sys
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parentdir)
import libmythtv
class MythTVTest(unittest.TestCase):
def test_FixEpisodeNameNoPrefix(self):
settings = Mock('libsettings.Settings')
settings.getshowmythtvepisodeprefix.mock_returns = ""
mythtv = libmythtv.MythTV(settings)
result = mythtv.fixmythtvepisodename("Show", "episode")
self.assertEqual(result, "episode")
def test_FixEpisodeNameNonMatchingPrefix(self):
settings = Mock('libsettings.Settings')
settings.getshowmythtvepisodeprefix.mock_returns = [ "BloohBlah" ]
mythtv = libmythtv.MythTV(settings)
result = mythtv.fixmythtvepisodename("Show", "episode")
self.assertEqual(result, "episode")
def test_FixEpisodeNameMatchingPrefix(self):
settings = Mock('libsettings.Settings')
settings.getshowmythtvepisodeprefix.mock_returns = [ "Match " ]
mythtv = libmythtv.MythTV(settings)
result = mythtv.fixmythtvepisodename("Show", "Match episode")
self.assertEqual(result, "episode")
def test_FixEpisodeNameMatchingFirstPrefix(self):
settings = Mock('libsettings.Settings')
settings.getshowmythtvepisodeprefix.mock_returns = [ "Match and ", "Match the " ]
mythtv = libmythtv.MythTV(settings)
result = mythtv.fixmythtvepisodename("Show", "Match and episode")
self.assertEqual(result, "episode")
def test_FixEpisodeNameMatchingSecondPrefix(self):
settings = Mock('libsettings.Settings')
settings.getshowmythtvepisodeprefix.mock_returns = [ "Match and ", "Match the " ]
mythtv = libmythtv.MythTV(settings)
result = mythtv.fixmythtvepisodename("Show", "Match the episode")
self.assertEqual(result, "episode")
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(MythTVTest)
unittest.TextTestRunner(verbosity=2).run(suite)

51
tests/libsickbeardtest.py Normal file
View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 5 14:12:38 2013
@author: shanef
"""
import unittest
from minimock import Mock
import os
import sys
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parentdir)
import libsickbeard
import urllib
class SickbeardTest(unittest.TestCase):
def test_findepisodeCloseSubtitle(self):
settings = Mock('libsettings.Settings')
settings.sickbeardaddress.mock_returns = "test"
settings.sickbeardport.mock_returns = "123"
settings.sickbeardapikey.mock_returns = "test"
urllib.urlopen = dummy_urlopen
sickbeard = libsickbeard.Sickbeard(settings)
result = sickbeard.findepisode("78949", "Splish, Splash, Splosh")
self.assertEqual("13", result[0])
self.assertEqual("15", result[1])
self.assertEqual("Splish, Splash, Splosh!", result[2])
def dummy_urlopen(arg):
class TmpClass:
def read(arg):
jsonresult = '{ "data": {"13": { "15": { "airdate": "2010-02-12", ' \
'"name": "Splish, Splash, Splosh!", "quality": "N/A", ' \
'"status": "Wanted" } } }, "message": "", ' \
'"result": "success" }'
return jsonresult
return TmpClass()
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(SickbeardTest)
unittest.TextTestRunner(verbosity=2).run(suite)

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""
Created on Thu Jul 18 23:13:15 2013
@author: shanef
"""
import unittest
from minimock import Mock
import os
import sys
parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parentdir)
import libtvdatasource
class tvdatasourceTest(unittest.TestCase):
def test_GetOutputFilenameNoIllegals(self):
result = self._dooutputfilenametest("S01", "E02", "test name", "")
self.assertEqual(result, "S01E02 - test name - SD TV_.mpg")
def test_GetOutputFilenameOneIllegals(self):
result = self._dooutputfilenametest("S01", "E02", "test name?", "?")
self.assertEqual(result, "S01E02 - test name - SD TV_.mpg")
def test_GetOutputFilenameTwoIllegals(self):
result = self._dooutputfilenametest("S01", "E02", "tes>t name?", ["?", ">"])
self.assertEqual(result, "S01E02 - test name - SD TV_.mpg")
def _dooutputfilenametest(self, season, episode, name, illegals):
settings = Mock('libsettings.Settings')
settings.illegalcharacters.mock_returns = illegals
tvdatasource = libtvdatasource.TVData(settings)
return tvdatasource.getoutputfilename(season, episode, name)
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(tvdatasourceTest)
unittest.TextTestRunner(verbosity=2).run(suite)