00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 __title__ ="mirobridge - Maintains Miro's Video files with MythTV";
00023 __author__="R.D.Vaughan"
00024 __purpose__='''
00025 This python script is intended to synchronise Miro's video files with MythTV's "Watch Recordings" and MythVideo.
00026
00027 The source of all video files are from those downloaded my Miro.
00028 The source of all meta data for the video files is from the Miro data base.
00029 The source of all cover art and screen shots are from those downloaded and maintained by Miro.
00030 Miro v2.0.3 or later must already be installed and configured and capable of downloading videos.
00031 '''
00032
00033 __version__=u"v0.6.8"
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213 examples_txt=u'''
00214 For examples, please see the Mirobridge's wiki page at http://www.mythtv.org/wiki/MiroBridge
00215 '''
00216
00217
00218 import sys, os, re, locale, subprocess, locale, ConfigParser, codecs, shutil, struct
00219 import datetime, fnmatch, string, time, logging, traceback, platform, fnmatch, ConfigParser
00220 from datetime import date
00221 from optparse import OptionParser
00222 from socket import gethostbyname
00223 import formatter
00224 import htmlentitydefs
00225
00226
00227 parser = OptionParser(usage=u"%prog usage: mirobridge -huevstdociVHSCWM [parameters]\n")
00228
00229 parser.add_option( "-e", "--examples", action="store_true", default=False, dest="examples",
00230 help=u"Display examples for executing the mirobridge script")
00231 parser.add_option( "-v", "--version", action="store_true", default=False, dest="version",
00232 help=u"Display version and author information")
00233 parser.add_option( "-s", "--simulation", action="store_true", default=False, dest="simulation",
00234 help=u"Simulation (dry run), no files are copied, symlinks created or MythTV data "\
00235 u"bases altered. If option (-n) is NOT specified Miro auto downloads WILL take "\
00236 u"place. See option (-n) help for details.")
00237 parser.add_option( "-t", "--testenv", action="store_true", default=False, dest="testenv",
00238 help=u"Test that the local environment can run all mirobridge functionality")
00239 parser.add_option( "-n", "--no_autodownload", action="store_true", default=False, dest="no_autodownload",
00240 help=u"Do not perform Miro Channel updates, video expiry and auto-downloadings. "\
00241 u"Default is to perform all perform all Channel maintenance features.")
00242 parser.add_option( "-o", "--nosubdirs", action="store_true", default=False, dest="nosubdirs",
00243 help=u"Organise MythVideo's Miro directory WITHOUT Miro channel subdirectories. "\
00244 u"The default is to have Channel subdirectories.")
00245 parser.add_option( "-c", "--channel", metavar="CHANNEL_ID:CHANNEL_NUM", default="", dest="channel",
00246 help=u'Specifies the channel id that is used for Miros unplayed recordings. Enter '\
00247 u'as "xxxx:yyy". Default is 9999:999. Be warned that once you change the '\
00248 u'default channel_id "9999" you must always use this option!')
00249 parser.add_option( "-i", "--import_opml", metavar="OPMLFILEPATH", default="", dest="import_opml",
00250 help=u'Import Miro exported OPML file containing Channel configurations. File '\
00251 u'name must be a fully qualified path. This option is exclusive to Miro '\
00252 u'v2.5.x and higher.')
00253 parser.add_option( "-V", "--verbose", action="store_true", default=False, dest="verbose",
00254 help=u"Display verbose messages when processing")
00255 parser.add_option( "-H", "--hostname", metavar="HOSTNAME", default="", dest="hostname",
00256 help=u"MythTV Backend hostname mirobridge is to up date")
00257 parser.add_option( "-S", "--sleeptime", metavar="SLEEP_DELAY_SECONDS", default="", dest="sleeptime",
00258 help=u"The amount of seconds to wait for an auto download to start.\nThe default "\
00259 u"is 60 seconds, but this may need to be adjusted for slower Internet connections.")
00260 parser.add_option( "-C", "--addchannel", metavar="ICONFILE_PATH", default="OFF", dest="addchannel",
00261 help=u'Add a Miro Channel record to MythTV. This gets rid of the "#9999 #9999" '\
00262 u'on the Watch Recordings screen and replaces it with the usual\nthe channel '\
00263 u'number and channel name.\nThe default if not overridden by the (-c) option '\
00264 u'is channel number 999.\nIf a filename and path is supplied it will be set '\
00265 u'as the channels icon. Make sure your override channel number is NOT one of '\
00266 u'your current MythTV channel numbers.\nThis option is typically only used '\
00267 u'once as there can only be one Miro channel record at a time.')
00268 parser.add_option( "-N", "--new_watch_copy", action="store_true", default=False, dest="new_watch_copy",
00269 help=u'For ALL Miro Channels: Use the "Watch Recording" screen to watch new Miro '\
00270 u'downloads then once watched copy the videos, icons, screen shot and metadata '\
00271 u'to MythVideo. Once coping is complete delete the video from Miro.\nThis option '\
00272 u'overrides any "mirobridge.conf" settings.')
00273 parser.add_option( "-W", "--watch_only", action="store_true", default=False, dest="watch_only",
00274 help=u'For ALL Miro Channels: Only use "Watch Recording" never move any Miro videos '\
00275 u'to MythVideo.\nThis option overrides any "mirobridge.conf" settings.')
00276 parser.add_option( "-M", "--mythvideo_only", action="store_true", default=False, dest="mythvideo_only",
00277 help=u'For ALL Miro Channel videos: Copy newly downloaded Miro videos to MythVideo '\
00278 u'and removed from Miro. These Miro videos never appear in the MythTV "Watch '\
00279 u'Recording" screen.\nThis option overrides any "mirobridge.conf" settings.')
00280
00281
00282
00283 opts, args = parser.parse_args()
00284 local_only = True
00285 dir_dict = {u'posterdir': u"VideoArtworkDir", u'bannerdir': u'mythvideo.bannerDir',
00286 u'fanartdir': u'mythvideo.fanartDir', u'episodeimagedir': u'mythvideo.screenshotDir',
00287 u'mythvideo': u'VideoStartupDir'}
00288 vid_graphics_dirs = {u'default' : u'', u'mythvideo': u'', u'posterdir' : u'',
00289 u'bannerdir': u'', u'fanartdir': u'', u'episodeimagedir': u'',}
00290 key_trans = {u'filename' : u'mythvideo', u'coverfile': u'posterdir',
00291 u'banner' : u'bannerdir', u'fanart' : u'fanartdir',
00292 u'screenshot': u'episodeimagedir'}
00293
00294 graphic_suffix = {u'default' : u'', u'mythvideo': u'', u'posterdir': u'_coverart',
00295 u'bannerdir': u'_banner', u'fanartdir': u'_fanart', u'episodeimagedir': u'_screenshot',}
00296 graphic_path_suffix = u"%s%s%s.%s"
00297 graphic_name_suffix = u"%s%s.%s"
00298
00299 storagegroupnames = {u'Default' : u'default', u'Videos' : u'mythvideo',
00300 u'Coverart': u'posterdir', u'Banners' : u'bannerdir',
00301 u'Fanart' : u'fanartdir', u'Screenshots': u'episodeimagedir'}
00302 storagegroups={}
00303 image_extensions = [u"png", u"jpg", u"bmp"]
00304 simulation = False
00305 verbose = False
00306 ffmpeg = True
00307 channel_id = 9999
00308 channel_num = 999
00309 symlink_filename_format = u"%s - %s"
00310 flat = False
00311 download_sleeptime = float(60)
00312 channel_icon_override = []
00313 channel_watch_only = []
00314 channel_mythvideo_only = {}
00315 channel_new_watch_copy = {}
00316 tv_channels = {}
00317 movie_trailers = []
00318 test_environment = False
00319 requirements_are_met = True
00320 imagemagick = True
00321 mythcommflag_recordings = u'%s -c %%s -s "%%s" --rebuild'
00322 mythcommflag_videos = u'%s --rebuild --video "%%s"'
00323 filename_char_filter = u"/%\000"
00324 emptyTitle = u'_NO_TITLE_From_Miro'
00325 emptySubTitle = u'_NO_SUBTITLE_From_Miro'
00326
00327
00328
00329 statistics = {u'WR_watched': 0, u'Miro_marked_watch_seen': 0,
00330 u'Miro_videos_deleted': 0, u'Miros_videos_downloaded': 0,
00331 u'Miros_MythVideos_video_removed': 0, u'Miros_MythVideos_added': 0,
00332 u'Miros_MythVideos_copied': 0, u'Total_unwatched': 0,
00333 u'Total_Miro_expiring': 0, u'Total_Miro_MythVideos': 0}
00334
00335
00336 class OutStreamEncoder(object):
00337 """Wraps a stream with an encoder
00338 """
00339 def __init__(self, outstream, encoding=None):
00340 self.out = outstream
00341 if not encoding:
00342 self.encoding = sys.getfilesystemencoding()
00343 else:
00344 self.encoding = encoding
00345
00346 def write(self, obj):
00347 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
00348 if isinstance(obj, unicode):
00349 self.out.write(obj.encode(self.encoding))
00350 else:
00351 self.out.write(obj)
00352
00353 def __getattr__(self, attr):
00354 """Delegate everything but write to the stream"""
00355 return getattr(self.out, attr)
00356
00357 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
00358 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
00359
00360
00361 logger = logging.getLogger(u"mirobridge")
00362 logger.setLevel(logging.DEBUG)
00363
00364 ch = logging.StreamHandler()
00365 ch.setLevel(logging.DEBUG)
00366
00367 formatter = logging.Formatter(u"%(asctime)s - %(name)s - %(levelname)s - %(message)s")
00368
00369 ch.setFormatter(formatter)
00370
00371 logger.addHandler(ch)
00372
00373
00374 try:
00375 from pyparsing import *
00376 import pyparsing
00377 if pyparsing.__version__ < "1.5.0":
00378 logger.critical(u"The python library 'pyparsing' must be at version 1.5.0 or higher. "\
00379 u"Your version is v%s" % pyparsing.__version__)
00380 sys.exit(1)
00381 except Exception, e:
00382 logger.critical(u"The python library 'pyparsing' must be installed and be version 1.5.0 or "\
00383 u"higher, error(%s)" % e)
00384 sys.exit(1)
00385 logger.info(u"Using python library 'pyparsing' version %s" % pyparsing.__version__)
00386
00387
00388
00389 try:
00390
00391
00392 from MythTV import OldRecorded, Recorded, RecordedProgram, Record, Channel, \
00393 MythDB, Video, MythVideo, MythBE, MythError, MythLog
00394 from MythTV.database import DBDataWrite
00395 mythdb = None
00396 mythbeconn = None
00397 try:
00398
00399 mythdb = MythDB()
00400 except MythError, e:
00401 print u'\n! Warning - %s' % e.args[0]
00402 filename = os.path.expanduser("~")+'/.mythtv/config.xml'
00403 if not os.path.isfile(filename):
00404 logger.critical(u'A correctly configured (%s) file must exist\n' % filename)
00405 else:
00406 logger.critical(u'Check that (%s) is correctly configured\n' % filename)
00407 sys.exit(1)
00408 except Exception, e:
00409 logger.critical(u"Creating an instance caused an error for one of: MythDB or "\
00410 u"MythVideo, error(%s)\n" % e)
00411 sys.exit(1)
00412 localhostname = mythdb.gethostname()
00413 try:
00414 mythbeconn = MythBE(backend=localhostname, db=mythdb)
00415 except MythError, e:
00416 logger.critical(u'MiroBridge must be run on a MythTV backend, error(%s)' % e.args[0])
00417 sys.exit(1)
00418 except Exception, e:
00419 logger.critical(u"MythTV python bindings could not be imported, error(%s)" % e)
00420 sys.exit(1)
00421
00422
00423 try:
00424
00425 try:
00426
00427 from miro.plat import utils
00428 utils.initialize_locale()
00429 except:
00430 pass
00431
00432
00433 from miro import config
00434 from miro import eventloop
00435 from miro import gtcache
00436 config.load()
00437 gtcache.init()
00438
00439
00440
00441
00442 if sys.version_info[0] == 2 and sys.version_info[1] > 5:
00443 import miro.feedparser
00444 import miro.storedatabase
00445 sys.modules['feedparser'] = miro.feedparser
00446 sys.modules['storedatabase'] = miro.storedatabase
00447
00448 from miro import prefs
00449 from miro import startup
00450 from miro import app
00451 from miro.frontends.cli.events import EventHandler
00452
00453
00454
00455 try:
00456 dummy = app.config.get(prefs.APP_VERSION)
00457
00458
00459 eventloop.setup_config_watcher()
00460 from miro import signals
00461 from miro import messages
00462 from miro import eventloop
00463 from miro import feed
00464 from miro import workerprocess
00465 from miro.frontends.cli.application import InfoUpdaterCallbackList
00466 from miro.frontends.cli.application import InfoUpdater
00467 from miro.plat.renderers.gstreamerrenderer import movie_data_program_info
00468 miroConfiguration = app.config.get
00469 from miro import controller
00470 app.controller = controller.Controller()
00471 except:
00472 miroConfiguration = config.get
00473 pass
00474
00475 except Exception, e:
00476 logger.critical(u"Importing Miro functions has an issue. Miro must be installed "\
00477 u"and functional, error(%s)", e)
00478 sys.exit(1)
00479
00480 logger.info(u"Miro Bridge version %s with Miro version %s" % \
00481 (__version__, miroConfiguration(prefs.APP_VERSION)))
00482 if miroConfiguration(prefs.APP_VERSION) < u"2.0.3":
00483 logger.critical((u"Your version of Miro (v%s) is not recent enough. Miro v2.0.3 is "\
00484 u"the minimum and it is preferred that you upgrade to Miro v2.5.2 or "\
00485 u"later.") % miroConfiguration(prefs.APP_VERSION))
00486 sys.exit(1)
00487
00488 try:
00489 if miroConfiguration(prefs.APP_VERSION) < u"2.5.2":
00490 logger.info("Using mirobridge_interpreter_2_0_3")
00491 from mirobridge.mirobridge_interpreter_2_0_3 import MiroInterpreter
00492 elif miroConfiguration(prefs.APP_VERSION) < u"3.0":
00493 logger.info("Using mirobridge_interpreter_2_5_2")
00494 from mirobridge.mirobridge_interpreter_2_5_2 import MiroInterpreter
00495 elif miroConfiguration(prefs.APP_VERSION) < u"3.5":
00496 logger.info("Using mirobridge_interpreter_3_0_0")
00497 from mirobridge.mirobridge_interpreter_3_0_0 import MiroInterpreter
00498 elif miroConfiguration(prefs.APP_VERSION) < u"4.0":
00499 logger.info("Using mirobridge_interpreter_3_5_0")
00500 from mirobridge.mirobridge_interpreter_3_5_0 import MiroInterpreter
00501 else:
00502 logger.info("Using mirobridge_interpreter_4_0_2")
00503 from mirobridge.mirobridge_interpreter_4_0_2 import MiroInterpreter
00504 from mirobridge.metadata import MetaData
00505 except Exception, e:
00506 logger.critical(u"Importing mirobridge functions has failed. At least a 'mirobridge_interpreter' "\
00507 u"file that matches your Miro version must be in the subdirectory 'mirobridge'.\n'"\
00508 u"e.g. mirobridge_interpreter_2_0_3.py', 'mirobridge_interpreter_2_5_2.py' ... etc, "\
00509 u"error(%s)" % e)
00510 sys.exit(1)
00511
00512 def _can_int(x):
00513 """Takes a string, checks if it is numeric.
00514 >>> _can_int("2")
00515 True
00516 >>> _can_int("A test")
00517 False
00518 """
00519 if x == None:
00520 return False
00521 try:
00522 int(x)
00523 except ValueError:
00524 return False
00525 else:
00526 return True
00527
00528
00529 def displayMessage(message):
00530 """Displays messages through stdout. Usually used with option (-V) verbose mode.
00531 returns nothing
00532 """
00533 global verbose
00534 if verbose:
00535 logger.info(message)
00536
00537
00538 def getExtention(filename):
00539 """Get the graphic file extension from a filename
00540 return the file extension from the filename
00541 """
00542 (dirName, fileName) = os.path.split(filename)
00543 (fileBaseName, fileExtension)=os.path.splitext(fileName)
00544 return fileExtension[1:]
00545
00546
00547
00548 def sanitiseFileName(name):
00549 '''Take a file name and change it so that invalid or problematic characters are substituted with a "_"
00550 return a sanitised valid file name
00551 '''
00552 global filename_char_filter
00553 if name == None or name == u'':
00554 return u'_'
00555 for char in filename_char_filter:
00556 name = name.replace(char, u'_')
00557 if name[0] == u'.':
00558 name = u'_'+name[1:]
00559 return name
00560
00561
00562
00563 def useImageMagick(cmd):
00564 """ Process graphics files using ImageMagick's utility 'mogrify'.
00565 >>> useImageMagick('convert screenshot.jpg -resize 50% screenshot.png')
00566 >>> 0
00567 >>> -1
00568 """
00569 return subprocess.call(u'%s > /dev/null' % cmd, shell=True)
00570
00571
00572 class singleinstance(object):
00573 '''
00574 singleinstance - based on Windows version by Dragan Jovelic this is a Linux
00575 version that accomplishes the same task: make sure that
00576 only a single instance of an application is running.
00577 '''
00578
00579 def __init__(self, pidPath):
00580 '''
00581 pidPath - full path/filename where pid for running application is to be
00582 stored. Often this is ./var/<pgmname>.pid
00583 '''
00584 from os import kill
00585 self.pidPath=pidPath
00586
00587
00588
00589 if os.path.exists(pidPath):
00590
00591
00592
00593 try:
00594 pid=int(open(pidPath, 'r').read().strip())
00595
00596
00597
00598
00599 try:
00600 kill(pid, 0)
00601 pidRunning = 1
00602 except OSError:
00603 pidRunning = 0
00604 if pidRunning:
00605 self.lasterror=True
00606 else:
00607 self.lasterror=False
00608 except:
00609 self.lasterror=False
00610 else:
00611 self.lasterror=False
00612
00613 if not self.lasterror:
00614
00615
00616
00617
00618 fp=open(pidPath, 'w')
00619 fp.write(str(os.getpid()))
00620 fp.close()
00621
00622 def alreadyrunning(self):
00623 return self.lasterror
00624
00625 def __del__(self):
00626 if not self.lasterror:
00627 import os
00628 os.unlink(self.pidPath)
00629
00630
00631 def isMiroRunning():
00632 '''Check if Miro is running. Only one can be running at the same time.
00633 return True if Miro us already running
00634 return False if Miro is NOT running
00635 '''
00636 try:
00637 p = subprocess.Popen(u'ps aux | grep "miro.real"', shell=True, bufsize=4096,
00638 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
00639 close_fds=True)
00640 except:
00641 return False
00642
00643 while True:
00644 data = p.stdout.readline()
00645 if not data:
00646 return False
00647 try:
00648 data = unicode(data, 'utf8')
00649 except (UnicodeEncodeError, TypeError):
00650 pass
00651 if data.find(u'grep') != -1:
00652 continue
00653
00654 if data.find(u'miro.real') != -1:
00655 logger.critical(u"Miro is already running and therefore Miro Bridge should not be run:")
00656 logger.critical(u"(%s)" % data)
00657 break
00658
00659 return True
00660
00661
00662
00663
00664 def is_punct_char(char):
00665 '''check if char is punctuation char
00666 return True if char is punctuation
00667 return False if char is not punctuation
00668 '''
00669 return char in string.punctuation
00670
00671 def is_not_punct_char(char):
00672 '''check if char is not punctuation char
00673 return True if char is not punctuation
00674 return False if char is punctuation
00675 '''
00676 return not is_punct_char(char)
00677
00678
00679 class delOldRecorded( OldRecorded ):
00680 '''
00681 Just delete an oldrecorded record. Never abort as sometimes a record may not exist.
00682 This routine is not supported in the native python bindings as MiroBridge uses the
00683 oldrecorded table outside of its original intent. return nothing
00684 '''
00685 _table = 'oldrecorded'
00686 def delete(self):
00687 """
00688 Delete video entry from database.
00689 """
00690 DBDataWrite.delete(self)
00691
00692 MythDB.searchOldRecorded.handler = delOldRecorded
00693
00694
00695 class delRecorded( Recorded ):
00696 '''Just delete a recorded record. Never abort as sometimes a record may not exist.
00697 return nothing
00698 '''
00699 _table = 'recorded'
00700 def delete(self):
00701 """
00702 Delete video entry from database.
00703 """
00704 DBDataWrite.delete(self)
00705
00706 MythDB.searchRecorded.handler = delRecorded
00707
00708
00709 def hashFile(filename):
00710 '''Create metadata hash values for mythvideo files
00711 return a hash value
00712 return u'' if the was an error with the video file or the video file length was zero bytes
00713 '''
00714
00715 if filename[0] != u'/':
00716 hash_value = mythbeconn.getHash(filename, u'Videos')
00717 if hash_value == u'NULL':
00718 return u''
00719 else:
00720 return hash_value
00721
00722
00723
00724 try:
00725 longlongformat = 'q'
00726 bytesize = struct.calcsize(longlongformat)
00727 f = open(filename, "rb")
00728 filesize = os.path.getsize(filename)
00729 hash = filesize
00730 if filesize < 65536 * 2:
00731 return u''
00732 for x in range(65536/bytesize):
00733 buffer = f.read(bytesize)
00734 (l_value,)= struct.unpack(longlongformat, buffer)
00735 hash += l_value
00736 hash = hash & 0xFFFFFFFFFFFFFFFF
00737 f.seek(max(0,filesize-65536),0)
00738 for x in range(65536/bytesize):
00739 buffer = f.read(bytesize)
00740 (l_value,)= struct.unpack(longlongformat, buffer)
00741 hash += l_value
00742 hash = hash & 0xFFFFFFFFFFFFFFFF
00743 f.close()
00744 returnedhash = "%016x" % hash
00745 return returnedhash
00746
00747 except(IOError):
00748 return u''
00749
00750
00751
00752 def rtnAbsolutePath(relpath, filetype=u'mythvideo'):
00753 '''Check if there is a Storage Group for the file type (mythvideo, coverfile, banner, fanart,
00754 screenshot) and form an appropriate absolute path and file name.
00755 return an absolute path and file name
00756 return the relpath sting if the file does not actually exist in the absolute path location
00757 '''
00758 if relpath == None or relpath == u'':
00759 return relpath
00760
00761
00762 if relpath[0] == u'/':
00763 return relpath
00764
00765 if not storagegroups.has_key(filetype):
00766 return relpath
00767
00768 for directory in storagegroups[filetype]:
00769 abpath = u"%s/%s" % (directory, relpath)
00770 if os.path.isfile(abpath):
00771 return abpath
00772 else:
00773 return relpath
00774
00775
00776
00777 def getStorageGroups():
00778 '''Populate the storage group dictionary with the host's storage groups.
00779 return False if there is an error
00780 '''
00781 records = mythdb.getStorageGroup(hostname=localhostname)
00782 if records:
00783 for record in records:
00784 if record.groupname in storagegroupnames.keys():
00785 try:
00786 dirname = unicode(record.dirname, 'utf8')
00787 except (UnicodeDecodeError):
00788 logger.error((u"The local Storage group (%s) directory contained\ncharacters "\
00789 u"that caused a UnicodeDecodeError. This storage group has been "\
00790 u"rejected.") % (record.groupname))
00791 continue
00792 except (UnicodeEncodeError, TypeError):
00793 dirname = record.dirname
00794
00795
00796 if dirname[-1:] == u'/':
00797 storagegroups[storagegroupnames[record.groupname]] = dirname
00798 else:
00799 storagegroups[storagegroupnames[record.groupname]] = dirname+u'/'
00800 continue
00801
00802 if len(storagegroups):
00803
00804 storagegroup_ok = True
00805 for key in storagegroups.keys():
00806 if not os.path.isdir(storagegroups[key]):
00807 logger.critical(u"The Storage group (%s) directory (%s) does not exist" % \
00808 (key, storagegroups[key]))
00809 storagegroup_ok = False
00810 if not storagegroup_ok:
00811 sys.exit(1)
00812
00813
00814 def getMythtvDirectories():
00815 """
00816 Get all video and graphics directories found in the MythTV DB and add them to the dictionary.
00817 Ignore any MythTV Frontend setting when there is already a storage group configured.
00818 """
00819
00820 global localhostname, vid_graphics_dirs, dir_dict, storagegroups, local_only, verbose
00821
00822
00823
00824 if storagegroups.has_key(u'mythvideo'):
00825 local_only = False
00826
00827 for key in storagegroups.keys():
00828 vid_graphics_dirs[key] = storagegroups[key]
00829 for key in dir_dict.keys():
00830 if key == u'default' or key == u'mythvideo':
00831 continue
00832 if not storagegroups.has_key(key):
00833
00834 vid_graphics_dirs[key] = storagegroups[u'mythvideo']
00835
00836 storagegroups[key] = storagegroups[u'mythvideo']
00837 else:
00838 local_only = True
00839 if storagegroups.has_key(u'default'):
00840 vid_graphics_dirs[u'default'] = storagegroups[u'default']
00841
00842 if local_only:
00843 logger.warning(u'There is no "Videos" Storage Group set so ONLY MythTV Frontend local '\
00844 u'paths for videos and graphics that are accessable from this MythTV '\
00845 u'Backend can be used.')
00846
00847 for key in dir_dict.keys():
00848 if vid_graphics_dirs[key]:
00849 continue
00850 graphics_dir = mythdb.settings[localhostname][dir_dict[key]]
00851
00852 if key == u'mythvideo':
00853 if graphics_dir:
00854 tmp_directories = graphics_dir.split(u':')
00855 if len(tmp_directories):
00856 for i in range(len(tmp_directories)):
00857 tmp_directories[i] = tmp_directories[i].strip()
00858 if tmp_directories[i] != u'':
00859 if os.path.exists(tmp_directories[i]):
00860 if tmp_directories[i][-1] != u'/':
00861 tmp_directories[i]+=u'/'
00862 vid_graphics_dirs[key] = tmp_directories[i]
00863 break
00864 else:
00865 logger.error(u"MythVideo video directory (%s) does not exist(%s)" % \
00866 (key, tmp_directories[i]))
00867 else:
00868 logger.error(u"MythVideo video directory (%s) is not set" % (key, ))
00869
00870 if key != u'mythvideo':
00871 if graphics_dir and os.path.exists(graphics_dir):
00872 if graphics_dir[-1] != u'/':
00873 graphics_dir+=u'/'
00874 vid_graphics_dirs[key] = graphics_dir
00875 else:
00876 logger.error(u"(%s) directory is not set or does not exist(%s)" % (key, dir_dict[key]))
00877
00878
00879 dir_for_all = True
00880 for key in vid_graphics_dirs.keys():
00881 if not vid_graphics_dirs[key]:
00882 logger.critical(u"There must be a directory for Videos and each graphics type "\
00883 u"the (%s) directory is missing." % (key))
00884 dir_for_all = False
00885 if not dir_for_all:
00886 sys.exit(1)
00887
00888
00889 access_issue = False
00890 for key in vid_graphics_dirs.keys():
00891 if not os.access(vid_graphics_dirs[key], os.F_OK | os.R_OK | os.W_OK):
00892 logger.critical(u"\nEvery Video and graphics directory must be read/writable for "\
00893 u"Miro Bridge to function. There is a permissions issue with (%s)." % \
00894 (vid_graphics_dirs[key], ))
00895 access_issue = True
00896 if access_issue:
00897 sys.exit(1)
00898
00899
00900 if verbose:
00901 dir_types={'posterdir' : "Cover art ", 'bannerdir': 'Banners ',
00902 'fanartdir' : 'Fan art ', 'episodeimagedir': 'Screenshots',
00903 'mythvideo': 'Video ', 'default': 'Default '}
00904 sys.stdout.write(u"""
00905 ==========================================================================================
00906 Listed below are the types and base directories that will be use for processing.
00907 The list reflects your current configuration for the '%s' back end
00908 and whether a directory is a 'SG' (storage group) or not.
00909 Note: All directories are from settings in the MythDB specific to hostname (%s).
00910 ------------------------------------------------------------------------------------------
00911 """ % (localhostname, localhostname))
00912 for key in vid_graphics_dirs.keys():
00913 sg_flag = 'NO '
00914 if storagegroups.has_key(key):
00915 if vid_graphics_dirs[key] == storagegroups[key]:
00916 sg_flag = 'YES'
00917 sys.stdout.write(u"Type: %s - SG-%s - Directory: (%s)\n" % \
00918 (dir_types[key], sg_flag, vid_graphics_dirs[key]))
00919 sys.stdout.write(\
00920 u"""------------------------------------------------------------------------------------------
00921 If a directory you set from a separate Front end is not displayed it means
00922 that the directory is not accessible from this backend OR
00923 you must add the missing directories using the Front end on this Back end.
00924 Front end settings are host machine specific.
00925 ==========================================================================================
00926
00927 """)
00928
00929
00930 def setUseroptions():
00931 """ Change variables through a user supplied configuration file
00932 abort the script if there are issues with the configuration file values
00933 """
00934 global simulation, verbose, channel_icon_override, channel_watch_only, channel_mythvideo_only
00935 global vid_graphics_dirs, tv_channels, movie_trailers, filename_char_filter
00936
00937 useroptions=u"~/.mythtv/mirobridge.conf"
00938
00939 if useroptions[0]==u'~':
00940 useroptions=os.path.expanduser(u"~")+useroptions[1:]
00941 if os.path.isfile(useroptions) == False:
00942 logger.info(
00943 u"There was no mirobridge configuration file found (%s)" % useroptions)
00944 return
00945
00946 cfg = ConfigParser.SafeConfigParser()
00947 cfg.read(useroptions)
00948 for section in cfg.sections():
00949 if section[:5] == u'File ':
00950 continue
00951 if section == u'variables':
00952 for option in cfg.options(section):
00953 if option == u'filename_char_filter':
00954 for char in cfg.get(section, option):
00955 filename_char_filter+=char
00956 continue
00957 if section == u'icon_override':
00958
00959 for option in cfg.options(section):
00960 channel_icon_override.append(option)
00961 continue
00962 if section == u'watch_only':
00963
00964 for option in cfg.options(section):
00965 if option == u'all miro channels':
00966 channel_watch_only = [u'all']
00967 break
00968 else:
00969 channel_watch_only.append(filter(is_not_punct_char, option.lower()))
00970 continue
00971 if section == u'mythvideo_only':
00972
00973 for option in cfg.options(section):
00974 if option == u'all miro channels':
00975 channel_mythvideo_only[u'all'] = cfg.get(section, option)
00976 break
00977 else:
00978 channel_mythvideo_only[filter(is_not_punct_char, option.lower())] = \
00979 cfg.get(section, option)
00980 for key in channel_mythvideo_only.keys():
00981 if not channel_mythvideo_only[key].startswith(vid_graphics_dirs[u'mythvideo']):
00982 logger.critical((u"All Mythvideo only configuration (%s) directories (%s) must "\
00983 u"be a subdirectory of the MythVideo base directory (%s).") % \
00984 (key, channel_mythvideo_only[key],
00985 vid_graphics_dirs[u'mythvideo']))
00986 sys.exit(1)
00987 if channel_mythvideo_only[key][-1] != u'/':
00988 channel_mythvideo_only[key]+=u'/'
00989 continue
00990 if section == u'watch_then_copy':
00991
00992 for option in cfg.options(section):
00993 if option == u'all miro channels':
00994 channel_new_watch_copy[u'all'] = cfg.get(section, option)
00995 break
00996 else:
00997 channel_new_watch_copy[filter(is_not_punct_char, option.lower())] =\
00998 cfg.get(section, option)
00999 for key in channel_new_watch_copy.keys():
01000 if not channel_new_watch_copy[key].startswith(vid_graphics_dirs[u'mythvideo']):
01001 logger.critical((u"All 'new->watch->copy' channel (%s) directory (%s) must "\
01002 u"be a subdirectory of the MythVideo base directory (%s).") % \
01003 (key, channel_new_watch_copy[key],
01004 vid_graphics_dirs[u'mythvideo']))
01005 sys.exit(1)
01006 if channel_new_watch_copy[key][-1] != u'/':
01007 channel_new_watch_copy[key]+=u'/'
01008 continue
01009
01010
01011 def massageDescription(description, extras=False):
01012 '''Massage the Miro description removing all HTML.
01013 return the massaged description
01014 '''
01015
01016 def unescape(text):
01017 """Removes HTML or XML character references
01018 and entities from a text string.
01019 @param text The HTML (or XML) source text.
01020 @return The plain text, as a Unicode string, if necessary.
01021 from Fredrik Lundh
01022 2008-01-03: input only unicode characters string.
01023 http://effbot.org/zone/re-sub.htm#unescape-html
01024 """
01025 def fixup(m):
01026 text = m.group(0)
01027 if text[:2] == u"&#":
01028
01029 try:
01030 if text[:3] == u"&#x":
01031 return unichr(int(text[3:-1], 16))
01032 else:
01033 return unichr(int(text[2:-1]))
01034 except ValueError:
01035 logger.warn(u"Remove HTML or XML character references: Value Error")
01036 pass
01037 else:
01038
01039
01040 try:
01041 if text[1:-1] == u"amp":
01042 text = u"&amp;"
01043 elif text[1:-1] == u"gt":
01044 text = u"&gt;"
01045 elif text[1:-1] == u"lt":
01046 text = u"&lt;"
01047 else:
01048 logger.info(u"%s" % text[1:-1])
01049 text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
01050 except KeyError:
01051 logger.warn(u"Remove HTML or XML character references: keyerror")
01052 pass
01053 return text
01054 return re.sub(u"&#?\w+;", fixup, text)
01055
01056 details = {}
01057 if not description:
01058 if extras:
01059 details[u'plot'] = description
01060 return details
01061 else:
01062 return description
01063
01064 director_text = u'Director: '
01065 director_re = re.compile(director_text, re.UNICODE)
01066 ratings_text = u'Rating: '
01067 ratings_re = re.compile(ratings_text, re.UNICODE)
01068
01069 removeText = replaceWith("")
01070 scriptOpen,scriptClose = makeHTMLTags(u"script")
01071 scriptBody = scriptOpen + SkipTo(scriptClose) + scriptClose
01072 scriptBody.setParseAction(removeText)
01073
01074 anyTag,anyClose = makeHTMLTags(Word(alphas,alphanums+u":_"))
01075 anyTag.setParseAction(removeText)
01076 anyClose.setParseAction(removeText)
01077 htmlComment.setParseAction(removeText)
01078
01079 commonHTMLEntity.setParseAction(replaceHTMLEntity)
01080
01081
01082 firstPass = (htmlComment | scriptBody | commonHTMLEntity |
01083 anyTag | anyClose ).transformString(description)
01084
01085
01086 repeatedNewlines = LineEnd() + OneOrMore(LineEnd())
01087 repeatedNewlines.setParseAction(replaceWith(u"\n\n"))
01088 secondPass = repeatedNewlines.transformString(firstPass)
01089 text = secondPass.replace(u"Link to Catalog\n ",u'')
01090 text = unescape(text)
01091
01092 if extras:
01093 text_lines = text.split(u'\n')
01094 for index in range(len(text_lines)):
01095 text_lines[index] = text_lines[index].rstrip()
01096 index+=1
01097 else:
01098 text_lines = [text.replace(u'\n', u' ')]
01099
01100 if extras:
01101 description = u''
01102 for text in text_lines:
01103 if len(director_re.findall(text)):
01104 details[u'director'] = text.replace(director_text, u'')
01105 continue
01106
01107 if len(ratings_re.findall(text)):
01108 data = text[text.index(ratings_text):].replace(ratings_text, u'')
01109 try:
01110 number = data[:data.index(u'/')]
01111
01112 try:
01113 details[u'userrating'] = float(number) * 2
01114 except ValueError:
01115 details[u'userrating'] = 0.0
01116 except:
01117 details[u'userrating'] = 0.0
01118 text = text[:text.index(ratings_text)]
01119 if text.rstrip():
01120 description+=text+u' '
01121 else:
01122 description = text_lines[0].replace(u"[...]Rating:", u"[...] Rating:")
01123
01124 if extras:
01125 details[u'plot'] = description.rstrip()
01126 return details
01127 else:
01128 return description
01129
01130
01131
01132 def getOldrecordedOrphans():
01133 """Retrieves the Miro oldrecorded records for localhostname. Match them against the Miro recorded
01134 records and identify any orphaned oldrecorded records. Those records mean a MythTV user deleted the
01135 Miro video from the Watched Recordings screen or from MythVideo. Delete the orphaned records from
01136 MythTV plus any left over graphic files.
01137 return array of oldrecorded dictionary records which are orphaned
01138 return None if there are no orphaned oldrecorded records
01139 """
01140 global simulation, verbose, channel_id, localhostname, vid_graphics_dirs, statistics
01141 global channel_icon_override
01142 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
01143
01144
01145
01146 metadata.convertOldMiroVideos()
01147
01148 recorded_array = list(mythdb.searchRecorded(chanid=channel_id, hostname=localhostname))
01149 oldrecorded_array = list(mythdb.searchOldRecorded(chanid=channel_id, ))
01150 videometadata = list(mythdb.searchVideos(category=u'Miro'))
01151
01152 orphans = []
01153 for record in oldrecorded_array:
01154 for recorded in recorded_array:
01155 if recorded[u'starttime'] == record[u'starttime'] and recorded[u'endtime'] == \
01156 record[u'endtime']:
01157 break
01158 else:
01159 for video in videometadata:
01160 if video[u'title'] == record[u'title'] and video[u'subtitle'] == record[u'subtitle']:
01161 break
01162 else:
01163 orphans.append(record)
01164
01165 for data in orphans:
01166 if simulation:
01167 logger.info(u"Simulation: Remove orphaned oldrecorded record (%s - %s)" % \
01168 (data[u'title'], data[u'subtitle']))
01169 else:
01170 try:
01171
01172
01173 delOldRecorded((channel_id, data['starttime'], data['endtime'], data['title'],
01174 data['subtitle'], data['description'])).delete()
01175 except:
01176 pass
01177
01178
01179 metadata.cleanupVideoAndGraphics(u'%s%s_%s.%s' % \
01180 (vid_graphics_dirs[u'default'], channel_id,
01181 data[u'starttime'].strftime('%Y%m%d%H%M%S'), u'png'))
01182
01183
01184 metadata.cleanupVideoAndGraphics(u'%s%s - %s.%s' % \
01185 (vid_graphics_dirs[u'default'], data[u'title'],
01186 data[u'subtitle'], u'png'))
01187
01188
01189 metadata.cleanupVideoAndGraphics(u'%s%s - %s%s.%s' % \
01190 (vid_graphics_dirs[u'episodeimagedir'], data[u'title'],
01191 data[u'subtitle'], graphic_suffix[u'episodeimagedir'], u'png'))
01192
01193
01194 if data[u'title'].lower() in channel_icon_override:
01195 metadata.cleanupVideoAndGraphics(u'%s%s - %s%s.%s' % \
01196 (vid_graphics_dirs[u'posterdir'], data[u'title'],
01197 data[u'subtitle'], graphic_suffix[u'posterdir'], u'png'))
01198
01199 displayMessage(u"Removed orphaned Miro video and graphics files (%s - %s)" % \
01200 (data[u'title'], data[u'subtitle']))
01201
01202 return orphans
01203
01204
01205
01206 def getStartEndTimes(duration, downloadedTime):
01207 '''Calculate a videos start and end times and isotime for the recorded file name.
01208 return an array of initialised values if either duration or downloadedTime is invalid
01209 return an array of the video's start, end times and isotime
01210 '''
01211 starttime = datetime.datetime.now()
01212 end = starttime
01213 start_end = [starttime.strftime('%Y-%m-%d %H:%M:%S'),
01214 starttime.strftime('%Y-%m-%d %H:%M:%S'),
01215 starttime.strftime('%Y%m%d%H%M%S')]
01216
01217 if downloadedTime != None:
01218 try:
01219 dummy = downloadedTime.strftime('%Y-%m-%d')
01220 except ValueError:
01221 downloadedTime = datetime.datetime.now()
01222 end = downloadedTime+datetime.timedelta(seconds=duration)
01223 start_end[0] = downloadedTime.strftime('%Y-%m-%d %H:%M:%S')
01224 start_end[1] = end.strftime('%Y-%m-%d %H:%M:%S')
01225 start_end[2] = downloadedTime.strftime('%Y%m%d%H%M%S')
01226
01227
01228
01229 while True:
01230 if not len(list(mythdb.searchOldRecorded(chanid=channel_id, starttime=start_end[0]))):
01231 break
01232 starttime = starttime + datetime.timedelta(0,1)
01233 end = end + datetime.timedelta(0,1)
01234 start_end[0] = starttime.strftime('%Y-%m-%d %H:%M:%S')
01235 start_end[1] = end.strftime('%Y-%m-%d %H:%M:%S')
01236 start_end[2] = starttime.strftime('%Y%m%d%H%M%S')
01237 continue
01238
01239 return start_end
01240
01241
01242 def setSymbolic(filename, storagegroupkey, symbolic_name, allow_symlink=False):
01243 '''Convert the file into a symbolic name according to it's storage group (there may be
01244 no storage group for the key). Check if a symbolic link exists and replace the link with a copy of
01245 the file. except for video files. Abort if the file does not exist.
01246 return the symbolic link to the file
01247 '''
01248 global simulation, verbose, storagegroups, vid_graphics_dirs
01249 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
01250 global local_only
01251
01252 if not os.path.isfile(filename):
01253 logger.error(u"The file (%s) must exist to create a symbolic link" % filename)
01254 return None
01255
01256 ext = getExtention(filename)
01257 if ext.lower() == u'm4v':
01258 ext = u'mpg'
01259
01260 convert = False
01261 if ext.lower() in [u'gif', u'jpeg', u'JPG', ]:
01262 ext = u'jpg'
01263 convert = True
01264
01265 if storagegroupkey in storagegroups.keys() and storagegroupkey == u'default':
01266 sym_filepath = graphic_path_suffix % (storagegroups[storagegroupkey], symbolic_name,
01267 graphic_suffix[storagegroupkey], ext)
01268 sym_filename = graphic_name_suffix % (symbolic_name, graphic_suffix[storagegroupkey], ext)
01269 elif storagegroupkey in storagegroups.keys() and not local_only:
01270 sym_filepath = graphic_path_suffix % (storagegroups[storagegroupkey], symbolic_name,
01271 graphic_suffix[storagegroupkey], ext)
01272 sym_filename = graphic_name_suffix % (symbolic_name, graphic_suffix[storagegroupkey], ext)
01273 else:
01274 sym_filepath = graphic_path_suffix % (vid_graphics_dirs[storagegroupkey], symbolic_name,
01275 graphic_suffix[storagegroupkey], ext)
01276 sym_filename = sym_filepath
01277
01278 if allow_symlink:
01279 if os.path.isfile(os.path.realpath(sym_filepath)):
01280 return sym_filename
01281 else:
01282 if os.path.isfile(os.path.realpath(sym_filepath)) and not os.path.islink(sym_filepath):
01283 return sym_filename
01284 try:
01285 os.remove(sym_filepath)
01286 except OSError:
01287 pass
01288
01289 if simulation:
01290 if allow_symlink:
01291 logger.info(u"Simulation: Used file (%s) to create symlink as (%s)" % \
01292 (filename, sym_filepath))
01293 else:
01294 logger.info(u"Simulation: Used file (%s) copy as (%s)" % (filename, sym_filepath))
01295 return sym_filename
01296
01297 try:
01298 if allow_symlink:
01299 os.symlink(filename, sym_filepath)
01300 displayMessage(u"Used file (%s) to created symlink (%s)" % (filename, sym_filepath))
01301 else:
01302 try:
01303 if convert:
01304 useImageMagick(u'convert "%s" "%s"' % (filename, sym_filepath))
01305 displayMessage(u"Convert and copy Miro file (%s) to file (%s)" % \
01306 (filename, sym_filepath))
01307 else:
01308 shutil.copy2(filename, sym_filepath)
01309 displayMessage(u"Copied Miro file (%s) to file (%s)" % (filename, sym_filepath))
01310 except OSError, e:
01311 logger.critical((u"Trying to copy the Miro file (%s) to the file (%s).\nError(%s)"\
01312 u"\nThis maybe a permissions error (mirobridge.py does not have "\
01313 u"permission to write to the directory).") % \
01314 (filename ,sym_filepath, e))
01315 sys.exit(1)
01316 except OSError, e:
01317 logger.critical((u"Trying to create the Miro file (%s) symlink (%s).\nError(%s)\nThis "\
01318 u"maybe a permissions error (mirobridge.py does not have permission to "\
01319 u"write to the directory).") % (sym_filepath, e))
01320 sys.exit(1)
01321
01322 return sym_filename
01323
01324
01325
01326 def createOldRecordedRecord(item, start, end, inetref):
01327 '''Using the details from a Miro item record create a MythTV oldrecorded record
01328 return an array of MythTV of a full initialised oldrecorded record dictionaries
01329 '''
01330 global localhostname, simulation, verbose, storagegroups, ffmpeg, channel_id
01331
01332 tmp_oldrecorded={}
01333
01334
01335 tmp_oldrecorded[u'chanid'] = channel_id
01336 tmp_oldrecorded[u'starttime'] = start
01337 tmp_oldrecorded[u'endtime'] = end
01338 tmp_oldrecorded[u'title'] = item[u'channelTitle']
01339 tmp_oldrecorded[u'subtitle'] = item[u'title']
01340 tmp_oldrecorded[u'category'] = u'Miro'
01341 tmp_oldrecorded[u'station'] = u'MIRO'
01342 tmp_oldrecorded[u'inetref'] = inetref
01343 tmp_oldrecorded[u'season'] = item[u'season']
01344 tmp_oldrecorded[u'episode'] = item[u'episode']
01345
01346 try:
01347 tmp_oldrecorded[u'description'] = massageDescription(item[u'description'])
01348 except TypeError:
01349 tmp_oldrecorded[u'description'] = item[u'description']
01350 return tmp_oldrecorded
01351
01352
01353
01354 def createRecordedRecords(item):
01355 '''Using the details from a Miro item record create a MythTV recorded record
01356 return an array of MythTV full initialised recorded and recordedprogram record dictionaries
01357 '''
01358 global localhostname, simulation, verbose, storagegroups, ffmpeg, channel_id
01359
01360 tmp_recorded={}
01361 tmp_recordedprogram={}
01362
01363
01364 graphics = metadata.getMetadata(sanitiseFileName(item[u'channelTitle']))
01365
01366 ffmpeg_details = metadata.getVideoDetails(item[u'videoFilename'])
01367 start_end = getStartEndTimes(ffmpeg_details[u'duration'], item[u'downloadedTime'])
01368
01369 if item[u'releasedate'] == None:
01370 item[u'releasedate'] = item[u'downloadedTime']
01371 try:
01372 dummy = item[u'releasedate'].strftime('%Y-%m-%d')
01373 except ValueError:
01374 item[u'releasedate'] = item[u'downloadedTime']
01375
01376
01377 tmp_recorded[u'chanid'] = channel_id
01378 tmp_recorded[u'starttime'] = start_end[0]
01379 tmp_recorded[u'endtime'] = start_end[1]
01380 tmp_recorded[u'title'] = item[u'channelTitle']
01381 tmp_recorded[u'subtitle'] = item[u'title']
01382 tmp_recorded[u'season'] = item[u'season']
01383 tmp_recorded[u'episode'] = item[u'episode']
01384 tmp_recorded[u'inetref'] = graphics[u'inetref']
01385 try:
01386 tmp_recorded[u'description'] = massageDescription(item[u'description'])
01387 except TypeError:
01388 print
01389 print u"Channel title(%s) subtitle(%s)" % (item[u'channelTitle'], item[u'title'])
01390 print u"The 'massageDescription()' function could not remove HTML and XML tags from:"
01391 print u"Description (%s)\n\n" % item[u'description']
01392 tmp_recorded[u'description'] = item[u'description']
01393 tmp_recorded[u'category'] = u'Miro'
01394 tmp_recorded[u'hostname'] = localhostname
01395 tmp_recorded[u'lastmodified'] = tmp_recorded[u'endtime']
01396 tmp_recorded[u'filesize'] = item[u'size']
01397 if item[u'releasedate'] != None:
01398 tmp_recorded[u'originalairdate'] = item[u'releasedate'].strftime('%Y-%m-%d')
01399
01400 basename = setSymbolic(item[u'videoFilename'], u'default', u"%s_%s" % \
01401 (channel_id, start_end[2]), allow_symlink=True)
01402 if basename != None:
01403 tmp_recorded[u'basename'] = basename
01404 else:
01405 logger.critical(u"The file (%s) must exist to create a recorded record" % \
01406 item[u'videoFilename'])
01407 sys.exit(1)
01408
01409 tmp_recorded[u'progstart'] = start_end[0]
01410 tmp_recorded[u'progend'] = start_end[1]
01411
01412
01413 tmp_recordedprogram[u'chanid'] = channel_id
01414 tmp_recordedprogram[u'starttime'] = start_end[0]
01415 tmp_recordedprogram[u'endtime'] = start_end[1]
01416 tmp_recordedprogram[u'title'] = item[u'channelTitle']
01417 tmp_recordedprogram[u'subtitle'] = item[u'title']
01418 try:
01419 tmp_recordedprogram[u'description'] = massageDescription(item[u'description'])
01420 except TypeError:
01421 tmp_recordedprogram[u'description'] = item[u'description']
01422
01423 tmp_recordedprogram[u'category'] = u"Miro"
01424 tmp_recordedprogram[u'category_type'] = u"series"
01425 if item[u'releasedate'] != None:
01426 tmp_recordedprogram[u'airdate'] = item[u'releasedate'].strftime('%Y')
01427 tmp_recordedprogram[u'originalairdate'] = item[u'releasedate'].strftime('%Y-%m-%d')
01428 tmp_recordedprogram[u'stereo'] = ffmpeg_details[u'stereo']
01429 tmp_recordedprogram[u'hdtv'] = ffmpeg_details[u'hdtv']
01430 tmp_recordedprogram[u'audioprop'] = ffmpeg_details[u'audio']
01431 tmp_recordedprogram[u'videoprop'] = ffmpeg_details[u'video']
01432
01433 return [tmp_recorded, tmp_recordedprogram,
01434 createOldRecordedRecord(item, start_end[0], start_end[1], graphics[u'inetref'])]
01435
01436
01437
01438 def createVideometadataRecord(item):
01439 '''Using the details from a Miro item create a MythTV videometadata record
01440 return an dictionary of MythTV an initialised videometadata record
01441 '''
01442 global localhostname, simulation, verbose, storagegroups, ffmpeg, channel_id
01443 global vid_graphics_dirs, channel_icon_override, flat, image_extensions
01444 global local_only
01445
01446 ffmpeg_details = metadata.getVideoDetails(item[u'videoFilename'])
01447 start_end = getStartEndTimes(ffmpeg_details[u'duration'], item[u'downloadedTime'])
01448
01449 sympath = u'Miro'
01450 if not flat:
01451 sympath+=u"/%s" % item[u'channelTitle']
01452
01453
01454 graphics = metadata.getMetadata(sanitiseFileName(item[u'channelTitle']))
01455
01456 videometadata = {}
01457
01458 videometadata[u'title'] = item[u'channelTitle']
01459 videometadata[u'subtitle'] = item[u'title']
01460 videometadata[u'inetref'] = graphics[u'inetref']
01461 videometadata[u'season'] = item[u'season']
01462 videometadata[u'episode'] = item[u'episode']
01463
01464 try:
01465 details = massageDescription(item[u'description'], extras=True)
01466 except TypeError:
01467 print
01468 print u"MythVideo-Channel title(%s) subtitle(%s)" % \
01469 (item[u'channelTitle'], item[u'title'])
01470 print u"The 'massageDescription()' function could not remove HTML and XML tags from:"
01471 print u"Description (%s)\n\n" % item[u'description']
01472 details = {u'plot': item[u'description']}
01473
01474 for key in details.keys():
01475 videometadata[key] = details[key]
01476
01477 if item[u'releasedate'] == None:
01478 item[u'releasedate'] = item[u'downloadedTime']
01479 try:
01480 dummy = item[u'releasedate'].strftime('%Y-%m-%d')
01481 except ValueError:
01482 item[u'releasedate'] = item[u'downloadedTime']
01483
01484 if item[u'releasedate'] != None:
01485 videometadata[u'year'] = item[u'releasedate'].strftime('%Y')
01486 videometadata[u'releasedate'] = item[u'releasedate'].strftime('%Y-%m-%d')
01487 videometadata[u'length'] = ffmpeg_details[u'duration']/60
01488 if item.has_key(u'copied'):
01489 videometadata[u'category'] = u'Video Cast'
01490 else:
01491 videometadata[u'category'] = u'Miro'
01492
01493 if not item.has_key(u'copied'):
01494 videofile = setSymbolic(item[u'videoFilename'], u'mythvideo', "%s/%s - %s" % \
01495 (sympath, sanitiseFileName(item[u'channelTitle']),
01496 sanitiseFileName(item[u'title'])), allow_symlink=True)
01497 if videofile != None:
01498 videometadata[u'filename'] = videofile
01499 if not local_only and videometadata[u'filename'][0] != u'/':
01500 videometadata[u'host'] = localhostname.lower()
01501 else:
01502 logger.critical(u"The file (%s) must exist to create a videometadata record" % \
01503 item[u'videoFilename'])
01504 sys.exit(1)
01505 else:
01506 videometadata[u'filename'] = item[u'videoFilename']
01507 if not local_only and videometadata[u'filename'][0] != u'/':
01508 videometadata[u'host'] = localhostname.lower()
01509
01510 videometadata[u'hash'] = hashFile(videometadata[u'filename'])
01511
01512 if not item.has_key(u'copied'):
01513 if graphics['coverart']:
01514 videometadata[u'coverfile'] = graphics['coverart']
01515 elif item[u'channel_icon'] and not item[u'channelTitle'].lower() in channel_icon_override:
01516 filename = setSymbolic(item[u'channel_icon'], u'posterdir', u"%s" % \
01517 (sanitiseFileName(item[u'channelTitle'])))
01518 if filename != None:
01519 videometadata[u'coverfile'] = filename
01520 else:
01521 if item[u'item_icon']:
01522 filename = setSymbolic(item[u'item_icon'], u'posterdir', u"%s - %s" % \
01523 (sanitiseFileName(item[u'channelTitle']),
01524 sanitiseFileName(item[u'title'])))
01525 if filename != None:
01526 videometadata[u'coverfile'] = filename
01527 else:
01528 videometadata[u'coverfile'] = item[u'channel_icon']
01529
01530 if not item.has_key(u'copied'):
01531 if item[u'screenshot']:
01532 filename = setSymbolic(item[u'screenshot'], u'episodeimagedir', u"%s - %s" % \
01533 (sanitiseFileName(item[u'channelTitle']),
01534 sanitiseFileName(item[u'title'])))
01535 if filename != None:
01536 videometadata[u'screenshot'] = filename
01537 else:
01538 if item[u'screenshot']:
01539 videometadata[u'screenshot'] = item[u'screenshot']
01540
01541 if graphics['banner']:
01542 videometadata[u'banner'] = graphics['banner']
01543 if graphics['fanart']:
01544 videometadata[u'fanart'] = graphics['fanart']
01545
01546 return [videometadata, createOldRecordedRecord(item, start_end[0], start_end[1],
01547 graphics[u'inetref'])]
01548
01549
01550
01551 def createChannelRecord(icon, channel_id, channel_num):
01552 '''
01553 Create the optional Miro "channel" record as it makes the Watch Recordings screen look better.
01554 return True if the record was created and written successfully
01555 abort if the processing failed
01556 '''
01557 global localhostname, simulation, verbose, vid_graphics_dirs
01558
01559 if icon != u"":
01560 if not os.path.isfile(icon):
01561 logger.critical((u'The Miro channel icon file (%s) does not exist.\nThe variable '\
01562 u'needs to be a fully qualified file name and path.\ne.g. '\
01563 u'./mirobridge.py -C "/path to the channel icon/miro_channel_icon.'\
01564 u'jpg"') % (icon))
01565 sys.exit(1)
01566
01567 data={}
01568 data['chanid'] = channel_id
01569 data['channum'] = str(channel_num)
01570 data['freqid'] = str(channel_num)
01571 data['atsc_major_chan'] = int(channel_num)
01572 data['icon'] = u''
01573 if icon != u'':
01574 data['icon'] = icon
01575 data['callsign'] = u'Miro'
01576 data['name'] = u'Miro'
01577 data['last_record'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
01578
01579 if simulation:
01580 logger.info(u"Simulation: Create Miro channel record channel_id(%d) and channel_num(%d)" % \
01581 (channel_id, channel_num))
01582 logger.info(u"Simulation: Channel icon file(%s)" % (icon))
01583 else:
01584 try:
01585 Channel().create(data)
01586 except MythError, e:
01587 logger.critical((u"Failed writing the Miro channel record. Most likely the Channel "\
01588 u"Id and number already exists.\nUse MythTV set up program "\
01589 u"(mythtv-setup) to alter or remove the offending channel.\nYou "\
01590 u"specified Channel ID (%d) and Channel Number (%d), error(%s)") % \
01591 (channel_id, channel_num, e.args[0]))
01592 sys.exit(1)
01593 return True
01594
01595
01596
01597 def checkVideometadataFails(record, flat):
01598 '''
01599 Verify that the real path exists for both video and graphics for this MythVideo Miro record.
01600 return False if there were no failures
01601 return True if a failure was found
01602 '''
01603 global localhostname, verbose, vid_graphics_dirs, key_trans
01604
01605 for field in key_trans.keys():
01606 if not record[field]:
01607 continue
01608 if record[field] == u'No Cover':
01609 continue
01610 if not record[u'host'] or record[field][0] == u'/':
01611 if not os.path.isfile(record[field]):
01612 return True
01613 else:
01614 if not os.path.isfile(vid_graphics_dirs[key_trans[field]]+record[field]):
01615 return True
01616 return False
01617
01618
01619
01620 def createMiroMythVideoDirectory():
01621 '''If the "Miro" directory does not exist in MythVideo then create it.
01622 abort if there is an issue creating the directory
01623 '''
01624 global localhostname, vid_graphics_dirs, storagegroups, channel_id, flat, simulation, verbose
01625
01626
01627
01628 miro = u'Miro'
01629 miro_path = vid_graphics_dirs[u'mythvideo']+miro
01630 if not os.path.isdir(miro_path):
01631 try:
01632 if simulation:
01633 logger.info(u"Simulation: Create Miro Mythvideo directory (%s)" % (miro_path,))
01634 else:
01635 try:
01636 os.mkdir(miro_path)
01637 except OSError, e:
01638 logger.critical((u"Create Miro Mythvideo directory (%s).\nError(%s)\n" \
01639 u"This may be due to a permissions error.") % \
01640 (miro_path, e))
01641 sys.exit(1)
01642 except OSError, e:
01643 logger.critical((u"Creation of MythVideo 'Miro' directory (%s) failed.\nError(%s)"\
01644 u"\nThis may be due to a permissions error.") % (miro_path, e))
01645 sys.exit(1)
01646
01647
01648 def createMiroChannelSubdirectory(item):
01649 '''Create the Miro Channel subdirectory in MythVideo
01650 abort if the subdirectory cannot be made
01651 '''
01652 global localhostname, vid_graphics_dirs, storagegroups, channel_id, flat, simulation, verbose
01653
01654 miro = u'Miro'
01655 path = u"%s%s/%s" % (vid_graphics_dirs[u'mythvideo'], miro,
01656 sanitiseFileName(item[u'channelTitle']))
01657
01658 if simulation:
01659 logger.info(u"Simulation: Make subdirectory(%s)" % (path))
01660 else:
01661 if not os.path.isdir(path):
01662 try:
01663 os.mkdir(path)
01664 except OSError, e:
01665 logger.critical((u"Creation of MythVideo 'Miro' subdirectory path (%s) failed."\
01666 u"\nError(%s)\nThis may be due to a permissions error.") % \
01667 (path, e))
01668 sys.exit(1)
01669
01670
01671
01672 def getPlayedMiroVideos():
01673 '''From the MythTV database recorded records identify all "played" Miro Video files.
01674 return None if there were either no Miro recorded records or none that were in "watched" status
01675 return an array of subtitles of those Miro video files that were "watched"
01676 '''
01677 global localhostname, vid_graphics_dirs, storagegroups, verbose, channel_id, statistics
01678
01679 filenames=[]
01680 recorded = list(mythdb.searchRecorded(chanid=channel_id, hostname=localhostname))
01681 for record in recorded:
01682 if record[u'watched'] == 0:
01683 continue
01684 try:
01685 filenames.append(os.path.realpath(storagegroups[u'default']+record[u'basename']))
01686 statistics[u'WR_watched']+=1
01687 except OSError, e:
01688 logger.info(u"Miro video file has been removed (%s) outside of mirobridge\nError(%s)" % \
01689 (storagegroups[u'default']+record[u'basename'], e))
01690 continue
01691 displayMessage(u"Miro video (%s) (%s) has been marked as watched in MythTV." % \
01692 (record[u'title'], record[u'subtitle']))
01693 if len(filenames):
01694 return filenames
01695 else:
01696 return None
01697
01698
01699 def updateMythRecorded(items):
01700 '''
01701 Add and delete MythTV (Watch Recordings) Miro recorded records. Add and delete symbolic links
01702 to coverart/Miro icons.
01703 Abort if processing failed
01704 return True if processing was successful
01705 '''
01706 global localhostname, vid_graphics_dirs, storagegroups, channel_id, simulation, imagemagick
01707 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
01708
01709 if not items:
01710 items = []
01711 items_copy = list(items)
01712
01713
01714 recorded = list(mythdb.searchRecorded(chanid=channel_id, hostname=localhostname))
01715 for record in recorded:
01716 if storagegroups.has_key(u'default'):
01717 sym_filepath = u"%s%s" % (storagegroups[u'default'], record[u'basename'])
01718 else:
01719 sym_filepath = u"%s%s" % (vid_graphics_dirs[u'default'], record[u'basename'])
01720
01721 remove = False
01722 for item in items:
01723 if item[u'channelTitle'] == record[u'title'] and item[u'title'] == record[u'subtitle']:
01724 break
01725 else:
01726 remove = True
01727
01728
01729 if record[u'watched'] == 1 or not os.path.isfile(os.path.realpath(sym_filepath)):
01730 remove = True
01731 if remove:
01732 displayMessage(u"Removing watched Miro recording (%s) (%s)" % \
01733 (record[u'title'], record[u'subtitle']))
01734
01735 if simulation:
01736 logger.info(u"Simulation: Remove recorded/recordedprogram/oldrecorded records and "\
01737 u"associated Miro Video file for chanid(%s), starttime(%s)" % \
01738 (record['chanid'], record['starttime']))
01739 else:
01740 try:
01741
01742
01743 rtn = delRecorded((record['chanid'], record['starttime'])).delete()
01744 except MythError, e:
01745 pass
01746
01747
01748 metadata.cleanupVideoAndGraphics(u'%s%s_%s.%s' % \
01749 (vid_graphics_dirs[u'default'], record['chanid'],
01750 record[u'starttime'].strftime('%Y%m%d%H%M%S'), u'png'))
01751
01752 try:
01753 rtn = delOldRecorded((record['chanid'], record['starttime'],
01754 record['endtime'], record['title'],
01755 record['subtitle'], record['description'])).delete()
01756 except Exception, e:
01757 pass
01758
01759 recorded = list(mythdb.searchRecorded(chanid=channel_id, hostname=localhostname))
01760 for record in recorded:
01761 for item in items:
01762 if item[u'channelTitle'] == record[u'title'] and item[u'title'] == record[u'subtitle']:
01763 items_copy.remove(item)
01764 break
01765
01766
01767 for item in items_copy:
01768
01769 if item[u'videoFilename'] == None:
01770 continue
01771
01772 if not os.path.isfile(os.path.realpath(item[u'videoFilename'])):
01773 continue
01774 if not os.path.isfile(os.path.realpath(item[u'videoFilename'])):
01775 continue
01776 records = createRecordedRecords(item)
01777 if records:
01778 if simulation:
01779 logger.info(u"Simulation: Added recorded and recordedprogram records for "\
01780 u"(%s - %s)" % (item[u'channelTitle'], item[u'title'],))
01781 else:
01782 try:
01783 Recorded().create(records[0])
01784 RecordedProgram().create(records[1])
01785 OldRecorded().create(records[2])
01786 except MythError, e:
01787 logger.warning(u"Inserting recorded/recordedprogram/oldrecorded records "\
01788 u"non-critical error (%s) for (%s - %s)" % \
01789 (e.args[0], item[u'channelTitle'], item[u'title']))
01790 else:
01791 logger.critical(u"Creation of recorded/recordedprogram/oldrecorded record data for "\
01792 u"(%s - %s)" % (item[u'channelTitle'], item[u'title'],))
01793 sys.exit(1)
01794
01795 if item[u'channel_icon']:
01796 ext = getExtention(item[u'channel_icon'])
01797 coverart_filename = u"%s%s%s.%s" % (vid_graphics_dirs[u'posterdir'],
01798 sanitiseFileName(item[u'channelTitle']),
01799 graphic_suffix[u'posterdir'], ext)
01800 if not os.path.isfile(os.path.realpath(coverart_filename)):
01801 if simulation:
01802 logger.info(u"Simulation: Remove symbolic link(%s)" % (coverart_filename,))
01803 else:
01804 try:
01805 os.remove(coverart_filename)
01806 except OSError:
01807 pass
01808 if simulation:
01809 logger.info(u"Simulation: Create icon file(%s) cover art file(%s)" % \
01810 (item[u'channel_icon'], coverart_filename))
01811 else:
01812 try:
01813 shutil.copy2(item[u'channel_icon'], coverart_filename)
01814 displayMessage((u"Copied a Miro Channel Icon file (%s) to MythTV as "\
01815 u"file (%s).") % (item[u'channel_icon'], coverart_filename))
01816 except OSError, e:
01817 logger.critical((u"Copying an icon file(%s) to coverart file(%s) failed."\
01818 u"\nError(%s)\nThis may be due to a permissions error.") % \
01819 (item[u'channel_icon'], coverart_filename, e))
01820 sys.exit(1)
01821
01822 if item[u'screenshot'] and imagemagick:
01823 screenshot_recorded = u"%s%s.png" % (vid_graphics_dirs[u'default'], records[0][u'basename'])
01824 if not os.path.isfile(screenshot_recorded):
01825 if simulation:
01826 logger.info(u"Simulation: Create screenshot file(%s) as(%s)" % \
01827 (item[u'screenshot'], screenshot_recorded))
01828 else:
01829 try:
01830 demensions = u''
01831 try:
01832 demensions = takeScreenShot(item[u'videoFilename'],
01833 screenshot_recorded,
01834 size_limit=True,
01835 just_demensions=False)
01836 except:
01837 pass
01838 if demensions:
01839 demensions = u"-size %s" % demensions
01840 useImageMagick(u'convert "%s" %s "%s"' % \
01841 (item[u'screenshot'], demensions,
01842 screenshot_recorded))
01843 displayMessage((u"Used a Miro Channel screenshot file (%s) to\ncreate "\
01844 u"using ImageMagick the MythTV Watch Recordings screen "\
01845 u"shot file\n(%s).") % \
01846 (item[u'screenshot'], screenshot_recorded))
01847 except OSError, e:
01848 logger.critical((u"Creating screenshot file(%s) as(%s) failed.\nError"\
01849 u"(%s)\nThis may be due to a permissions error.") % \
01850 (item[u'screenshot'], screenshot_recorded, e))
01851 sys.exit(1)
01852 else:
01853 screenshot_recorded = u"%s%s.png" % \
01854 (vid_graphics_dirs[u'default'], records[0][u'basename'])
01855 try:
01856 takeScreenShot(item[u'videoFilename'], screenshot_recorded, size_limit=True)
01857 except:
01858 pass
01859
01860 return True
01861
01862
01863 def updateMythVideo(items):
01864 '''Add and delete MythVideo records for played Miro Videos. Add and delete symbolic links
01865 to Miro Videos, to coverart/Miro icons, banners and Miro screenshots and fanart.
01866 NOTE: banner and fanart graphics were provided with the script and are used only if present.
01867 Abort if processing failed
01868 return True if processing was successful
01869 '''
01870 global localhostname, vid_graphics_dirs, storagegroups, channel_id, flat, simulation, verbose
01871 global channel_watch_only, statistics
01872 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
01873 global local_only
01874
01875 if not items:
01876 items = []
01877
01878
01879 createMiroMythVideoDirectory()
01880
01881
01882 records = list(mythdb.searchVideos(category=u'Miro'))
01883 statistics[u'Total_Miro_MythVideos'] = len(records)
01884 for record in records:
01885 if record[u'filename'][0] == u'/':
01886 if os.path.islink(record[u'filename']) and os.path.isfile(record[u'filename']):
01887 statistics[u'Total_Miro_expiring']+=1
01888 elif record[u'host'] and storagegroups.has_key(u'mythvideo'):
01889 if os.path.islink(storagegroups[u'mythvideo']+record[u'filename']) and \
01890 os.path.isfile(storagegroups[u'mythvideo']+record[u'filename']):
01891 statistics[u'Total_Miro_expiring']+=1
01892 for record in records:
01893 if checkVideometadataFails(record, flat):
01894 delete = False
01895 if os.path.islink(record[u'filename']):
01896 if not record[u'host'] or record[u'filename'][0] == '/':
01897 if not os.path.isfile(record[u'filename']):
01898 delete = True
01899 else:
01900 if not os.path.isfile(vid_graphics_dirs[key_trans[field]]+record[u'filename']):
01901 delete = True
01902 else:
01903 if not os.path.isfile(record[u'filename']):
01904 delete = True
01905 if delete:
01906 if simulation:
01907 logger.info(u"Simulation: DELETE videometadata for intid = %s" % \
01908 (record[u'intid'],))
01909 logger.info(u"Simulation: DELETE oldrecorded for title(%s), subtitle(%s)" % \
01910 (record[u'title'], record[u'subtitle']))
01911 else:
01912 rtn = Video(record[u'intid'], db=mythdb).delete()
01913 try:
01914 for oldrecorded in mythdb.searchOldRecorded(title=record[u'title'],
01915 subtitle=record[u'subtitle'] ):
01916 rtn = delOldRecorded((channel_id, oldrecorded['starttime'], \
01917 oldrecorded['endtime'], oldrecorded['title'], \
01918 oldrecorded['subtitle'], oldrecorded['description'])).\
01919 delete()
01920 except Exception, e:
01921 pass
01922 statistics[u'Total_Miro_MythVideos']-=1
01923
01924 metadata.deleteFile(record[u'filename'], record[u'host'], u'mythvideo')
01925 if record[u'screenshot']:
01926 metadata.deleteFile(record[u'screenshot'], record[u'host'], u'episodeimagedir')
01927
01928 if record[u'title'].lower() in channel_icon_override:
01929 metadata.deleteFile(record[u'coverfile'], record[u'host'], u'posterdir')
01930
01931 if not items:
01932 return True
01933
01934
01935 items_copy = list(items)
01936 records = list(mythdb.searchVideos(category=u'Miro'))
01937 for record in records:
01938 for item in items:
01939 if item[u'channelTitle'] == record[u'title'] and item[u'title'] == record[u'subtitle']:
01940 try:
01941 items_copy.remove(item)
01942 except ValueError:
01943 logger.info((u"Video (%s - %s) was found multiple times in list of (watched "\
01944 u"and/or saved) items from Miro - skipping") % \
01945 (item[u'channelTitle'], item[u'title']))
01946 pass
01947 break
01948
01949 for item in items:
01950 if filter(is_not_punct_char, item[u'channelTitle'].lower()) in channel_watch_only:
01951 try:
01952 items_copy.remove(item)
01953 except ValueError:
01954 pass
01955
01956
01957 for item in items_copy:
01958 if not flat and not item.has_key(u'copied'):
01959 createMiroChannelSubdirectory(item)
01960 if not item[u'screenshot']:
01961 screenshot_mythvideo = u"%s%s - %s%s.jpg" % \
01962 (vid_graphics_dirs[u'episodeimagedir'],
01963 sanitiseFileName(item[u'channelTitle']),
01964 sanitiseFileName(item[u'title']),
01965 graphic_suffix[u'episodeimagedir'])
01966 try:
01967 result = takeScreenShot(item[u'videoFilename'], screenshot_mythvideo, size_limit=False)
01968 except:
01969 result = None
01970 if result != None:
01971 item[u'screenshot'] = screenshot_mythvideo
01972 tmp_array = createVideometadataRecord(item)
01973 videometadata = tmp_array[0]
01974 oldrecorded = tmp_array[1]
01975 if simulation:
01976 logger.info(u"Simulation: Create videometadata record for (%s - %s)" % \
01977 (item[u'channelTitle'], item[u'title']))
01978 else:
01979 if not local_only and videometadata[u'filename'][0] != u'/':
01980 intid = list(mythdb.searchVideos(exactfile=videometadata[u'filename'],
01981 host=localhostname.lower()))
01982 else:
01983 intid = list(mythdb.searchVideos(exactfile=videometadata[u'filename']))
01984
01985 if intid == []:
01986 try:
01987 intid = Video(db=mythdb).create(videometadata).intid
01988 except MythError, e:
01989 logger.critical(u"Adding Miro video to MythVideo (%s - %s) failed for (%s)." % \
01990 (item[u'channelTitle'], item[u'title'], e.args[0]))
01991 sys.exit(1)
01992 if not item.has_key(u'copied'):
01993 try:
01994 OldRecorded().create(oldrecorded)
01995 except MythError, e:
01996 logger.critical(u"Failed writing the oldrecorded record for(%s)" % (e.args[0]))
01997 sys.exit(1)
01998 if videometadata[u'filename'][0] == u'/':
01999 cmd = mythcommflag_videos % videometadata[u'filename']
02000 elif videometadata[u'host'] and storagegroups[u'mythvideo']:
02001 cmd = mythcommflag_videos % \
02002 ((storagegroups[u'mythvideo']+videometadata[u'filename']))
02003 statistics[u'Miros_MythVideos_added']+=1
02004 statistics[u'Total_Miro_expiring']+=1
02005 statistics[u'Total_Miro_MythVideos']+=1
02006 displayMessage(u"Added Miro video to MythVideo (%s - %s)" % \
02007 (videometadata[u'title'], videometadata[u'subtitle']))
02008 else:
02009 sys.stdout.write(u'')
02010 displayMessage(\
02011 u"""Skipped adding a duplicate Miro video to MythVideo:
02012 (%s - %s)
02013 Sometimes a Miro channel has the same video downloaded multiple times.
02014 This is a Miro/Channel web site issue and often rectifies itself overtime.
02015 """ % (videometadata[u'title'], videometadata[u'subtitle']))
02016
02017 return True
02018
02019
02020 def printStatistics():
02021 global statistics
02022
02023
02024 sys.stdout.write(u"""
02025
02026 -------------------Statistics--------------------
02027 Number of Watch Recording's watched...... (% 5d)
02028 Number of Miro videos marked as seen..... (% 5d)
02029 Number of Miro videos deleted............ (% 5d)
02030 Number of New Miro videos downloaded..... (% 5d)
02031 Number of Miro/MythVideo's removed....... (% 5d)
02032 Number of Miro/MythVideo's added......... (% 5d)
02033 Number of Miro videos copies to MythVideo (% 5d)
02034 -------------------------------------------------
02035 Total Unwatched Miro/Watch Recordings.... (% 5d)
02036 Total Miro/MythVideo videos to expire.... (% 5d)
02037 Total Miro/MythVideo videos.............. (% 5d)
02038 -------------------------------------------------
02039
02040 """ % (statistics[u'WR_watched'], statistics[u'Miro_marked_watch_seen'],
02041 statistics[u'Miro_videos_deleted'], statistics[u'Miros_videos_downloaded'],
02042 statistics[u'Miros_MythVideos_video_removed'], statistics[u'Miros_MythVideos_added'],
02043 statistics[u'Miros_MythVideos_copied'], statistics[u'Total_unwatched'],
02044 statistics[u'Total_Miro_expiring'], statistics[u'Total_Miro_MythVideos'], ))
02045
02046
02047
02048
02049 def main():
02050 """Support mirobridge from the command line
02051 returns True
02052 """
02053 global localhostname, simulation, verbose, storagegroups, ffmpeg, channel_id, channel_num
02054 global flat, download_sleeptime, channel_watch_only, channel_mythvideo_only, channel_new_watch_copy
02055 global vid_graphics_dirs, imagemagick, statistics, requirements_are_met
02056 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
02057 global mythcommflag_recordings, mythcommflag_videos
02058 global local_only, metadata
02059 global parser, opts, args
02060
02061 if opts.examples:
02062 sys.stdout.write(examples_txt+'\n')
02063 sys.exit(0)
02064
02065 if opts.version:
02066 sys.stdout.write(u"\nTitle: (%s); Version: description(%s); Author: (%s)\n%s\n" % (
02067 __title__, __version__, __author__, __purpose__ ))
02068 sys.exit(0)
02069
02070 if opts.testenv:
02071 test_environment = True
02072 else:
02073 test_environment = False
02074
02075
02076 if isMiroRunning():
02077 sys.exit(1)
02078
02079
02080 x = 0
02081 if opts.new_watch_copy: x+=1
02082 if opts.watch_only: x+=1
02083 if opts.mythvideo_only: x+=1
02084 if opts.import_opml: x+=1
02085 if x > 1:
02086 logger.critical(u"The (-W), (-M), (-N) and (-i) options are mutually exclusive, "\
02087 u"so only one can be specified at a time.")
02088 sys.exit(1)
02089
02090
02091 simulation = opts.simulation
02092 verbose = opts.verbose
02093 if opts.hostname:
02094 localhostname = opts.hostname
02095
02096
02097
02098
02099 base_video_dir = miroConfiguration(prefs.MOVIES_DIRECTORY)
02100 miro_version_rev = u"%s r%s" % (miroConfiguration(prefs.APP_VERSION),
02101 miroConfiguration(prefs.APP_REVISION_NUM))
02102
02103 displayMessage(u"Miro Version (%s)" % (miro_version_rev))
02104 displayMessage(u"Base Miro Video Directory (%s)" % (base_video_dir,))
02105 logger.info(u'')
02106
02107
02108 if not os.path.isdir(base_video_dir):
02109 logger.critical(u"The Miro Videos directory (%s) does not exist." % str(base_video_dir))
02110 if test_environment:
02111 requirements_are_met = False
02112 else:
02113 sys.exit(1)
02114
02115 if miroConfiguration(prefs.APP_VERSION) < u"2.0.3":
02116 logger.critical((u"The installed version of Miro (%s) is too old. It must be at least "\
02117 u"v2.0.3 or higher.") % miroConfiguration(prefs.APP_VERSION))
02118 if test_environment:
02119 requirements_are_met = False
02120 else:
02121 sys.exit(1)
02122
02123
02124 if miroConfiguration(prefs.APP_VERSION) == u"4.0.1":
02125 logger.critical((u"The installed version of Miro (%s) must be upgraded to Miro version "\
02126 u"4.0.2 or higher.") % miroConfiguration(prefs.APP_VERSION))
02127 if test_environment:
02128 requirements_are_met = False
02129 else:
02130 sys.exit(1)
02131
02132
02133 if opts.import_opml:
02134 if miroConfiguration(prefs.APP_VERSION) < u"2.5.2":
02135 logger.critical(u"The OPML import option requires Miro v2.5.2 or higher your Miro "\
02136 u"(%s) is too old." % miroConfiguration(prefs.APP_VERSION))
02137 if test_environment:
02138 requirements_are_met = False
02139 else:
02140 sys.exit(1)
02141 if not os.path.isfile(opts.import_opml):
02142 logger.critical(u"The OPML import file (%s) does not exist" % opts.import_opml)
02143 if test_environment:
02144 requirements_are_met = False
02145 else:
02146 sys.exit(1)
02147 if len(opts.import_opml) > 5:
02148 if not opts.import_opml[:-4] != '.opml':
02149 logger.critical(u"The OPML import file (%s) must had a file extention of '.opml'" % \
02150 opts.import_opml)
02151 if test_environment:
02152 requirements_are_met = False
02153 else:
02154 sys.exit(1)
02155 else:
02156 logger.critical(u"The OPML import file (%s) must had a file extention of '.opml'" % \
02157 opts.import_opml)
02158 if test_environment:
02159 requirements_are_met = False
02160 else:
02161 sys.exit(1)
02162
02163
02164 if getStorageGroups() == False:
02165 logger.critical(u"Retrieving storage groups from the MythTV data base failed")
02166 if test_environment:
02167 requirements_are_met = False
02168 else:
02169 sys.exit(1)
02170 elif not u'default' in storagegroups.keys():
02171 logger.critical(u"There must be a 'Default' storage group")
02172 if test_environment:
02173 requirements_are_met = False
02174 else:
02175 sys.exit(1)
02176
02177 if opts.channel:
02178 channel = opts.channel.split(u':')
02179 if len(channel) != 2:
02180 logger.critical((u"The Channel (%s) must be in the format xxx:yyy with x and "\
02181 u"y all numeric.") % str(opts.channel))
02182 if test_environment:
02183 requirements_are_met = False
02184 else:
02185 sys.exit(1)
02186 elif not _can_int(channel[0]) or not _can_int(channel[1]):
02187 logger.critical(u"The Channel_id (%s) and Channel_num (%s) must be numeric." % \
02188 (channel[0], channel[1]))
02189 if test_environment:
02190 requirements_are_met = False
02191 else:
02192 sys.exit(1)
02193 else:
02194 channel_id = int(channel[0])
02195 channel_num = int(channel[1])
02196
02197 if opts.sleeptime:
02198 if not _can_int(opts.sleeptime):
02199 logger.critical(u"Auto-download sleep time (%s) must be numeric." % str(opts.sleeptime))
02200 if test_environment:
02201 requirements_are_met = False
02202 else:
02203 sys.exit(1)
02204 else:
02205 download_sleeptime = float(opts.sleeptime)
02206
02207 getMythtvDirectories()
02208
02209 if opts.nosubdirs:
02210 flat = True
02211
02212
02213 setUseroptions()
02214
02215 if opts.watch_only:
02216
02217 channel_watch_only = [u'all']
02218
02219 if opts.mythvideo_only:
02220 channel_mythvideo_only = {u'all': vid_graphics_dirs[u'mythvideo']+u'Miro/'}
02221
02222
02223 if opts.new_watch_copy:
02224 channel_new_watch_copy = {u'all': vid_graphics_dirs[u'mythvideo']+u'Miro/'}
02225
02226
02227 if len(channel_mythvideo_only) and len(channel_new_watch_copy):
02228 for key in channel_mythvideo_only.keys():
02229 if key in channel_new_watch_copy.keys():
02230 logger.critical((u'The Miro Channel (%s) cannot be used as both a "Mythvideo '\
02231 u'Only" and "New-Watch-Copy" channel.') % key)
02232 if test_environment:
02233 requirements_are_met = False
02234 else:
02235 sys.exit(1)
02236
02237
02238 ret = useImageMagick(u"convert -version")
02239 if ret < 0 or ret > 1:
02240 logger.critical(u"ImageMagick must be installed, graphics cannot be resized or "\
02241 u"converted to the required graphics format (e.g. jpg and or png)")
02242 if test_environment:
02243 requirements_are_met = False
02244 else:
02245 sys.exit(1)
02246
02247
02248 if mythdb.settings.NULL.DeletesFollowLinks == '1':
02249 logger.critical(u'The MythTV back end setting "Follow symbolic links when deleting '\
02250 u'files" is checked and it is incompatible with MiroBridge processing. '\
02251 u'It must be unchecked it to use MiroBridge.\nTo uncheck this setting '\
02252 u'start "mythtv-setup" or with Mythbuntu start "MythTV Backend Setup" '\
02253 u'and then General->Miscellaneous Settings and uncheck the "Follow '\
02254 u'symbolic links when deleting files" setting')
02255 if test_environment:
02256 requirements_are_met = False
02257 else:
02258 sys.exit(1)
02259
02260
02261 metadata = MetaData(mythdb,
02262 Video,
02263 Record,
02264 storagegroups,
02265 vid_graphics_dirs,
02266 channel_id,
02267 ffmpeg,
02268 logger,
02269 simulation,
02270 verbose,
02271 )
02272
02273 if opts.testenv:
02274 metadata.getVideoDetails(u"")
02275 if ffmpeg and requirements_are_met:
02276 logger.info(u"The environment test passed !\n\n")
02277 sys.exit(0)
02278 else:
02279 logger.critical(u"The environment test FAILED. See previously displayed error messages!")
02280 sys.exit(1)
02281
02282 if opts.addchannel != u'OFF':
02283 createChannelRecord(opts.addchannel, channel_id, channel_num)
02284 logger.info(u"The Miro Channel record has been successfully created !\n\n")
02285 sys.exit(0)
02286
02287
02288
02289
02290
02291
02292
02293
02294
02295 displayMessage(u"Starting Miro Frontend and Backend")
02296 startup.initialize(miroConfiguration(prefs.THEME_NAME))
02297 if miroConfiguration(prefs.APP_VERSION) > u"4.0":
02298 app.info_updater = InfoUpdater()
02299 app.cli_events = EventHandler()
02300 app.cli_events.connect_to_signals()
02301
02302 if miroConfiguration(prefs.APP_VERSION) > u"4.0":
02303 startup.install_first_time_handler(app.cli_events.handle_first_time)
02304
02305 startup.startup()
02306 app.cli_events.startup_event.wait()
02307 if app.cli_events.startup_failure:
02308 logger.critical(u"Starting Miro Frontend and Backend failed: (%s)\n(%s)" % \
02309 (app.cli_events.startup_failure[0], app.cli_events.startup_failure[1]))
02310 app.controller.shutdown()
02311 time.sleep(5)
02312 sys.exit(1)
02313
02314 if miroConfiguration(prefs.APP_VERSION) > u"4.0":
02315 app.movie_data_program_info = movie_data_program_info
02316 messages.FrontendStarted().send_to_backend()
02317
02318 app.cli_interpreter = MiroInterpreter()
02319 if opts.verbose:
02320 app.cli_interpreter.verbose = True
02321 else:
02322 app.cli_interpreter.verbose = False
02323 app.cli_interpreter.simulation = opts.simulation
02324 app.cli_interpreter.videofiles = []
02325 app.cli_interpreter.downloading = False
02326 app.cli_interpreter.icon_cache_dir = miroConfiguration(prefs.ICON_CACHE_DIRECTORY)
02327 app.cli_interpreter.imagemagick = imagemagick
02328 app.cli_interpreter.statistics = statistics
02329 if miroConfiguration(prefs.APP_VERSION) < u"2.5.0":
02330 app.renderer = app.cli_interpreter
02331 elif miroConfiguration(prefs.APP_VERSION) > u"4.0":
02332 pass
02333 else:
02334 app.movie_data_program_info = app.cli_interpreter.movie_data_program_info
02335
02336
02337
02338
02339 if opts.import_opml:
02340 results = 0
02341 try:
02342 app.cli_interpreter.do_mythtv_import_opml(opts.import_opml)
02343 time.sleep(30)
02344 except Exception, e:
02345 logger.critical(u"Import of OPML file (%s) failed, error(%s)." % (opts.import_opml, e))
02346 results = 1
02347
02348 app.controller.shutdown()
02349 time.sleep(5)
02350 sys.exit(results)
02351
02352
02353
02354
02355
02356 if not opts.no_autodownload:
02357 if opts.verbose:
02358 app.cli_interpreter.verbose = False
02359 app.cli_interpreter.do_mythtv_getunwatched(u'')
02360 before_download = len(app.cli_interpreter.videofiles)
02361 if opts.verbose:
02362 app.cli_interpreter.verbose = True
02363 if miroConfiguration(prefs.APP_VERSION) < u"4.0":
02364
02365 app.cli_interpreter.do_mythtv_update_autodownload(u'')
02366 time.sleep(download_sleeptime)
02367 firsttime = True
02368 while True:
02369 app.cli_interpreter.do_mythtv_check_downloading(u'')
02370 if app.cli_interpreter.downloading:
02371 time.sleep(30)
02372 firsttime = False
02373 continue
02374 elif firsttime:
02375 time.sleep(download_sleeptime)
02376 firsttime = False
02377 continue
02378 else:
02379 break
02380 if opts.verbose:
02381 app.cli_interpreter.verbose = False
02382 app.cli_interpreter.do_mythtv_getunwatched(u'')
02383 after_download = len(app.cli_interpreter.videofiles)
02384 statistics[u'Miros_videos_downloaded'] = after_download - before_download
02385 if opts.verbose:
02386 app.cli_interpreter.verbose = True
02387
02388
02389
02390
02391
02392 videostodelete = getOldrecordedOrphans()
02393 if len(videostodelete):
02394 displayMessage(u"Starting Miro delete of videos deleted in the MythTV "\
02395 u"Watched Recordings screen.")
02396 for video in videostodelete:
02397
02398 app.cli_interpreter.do_mythtv_item_remove([video[u'title'], video[u'subtitle']])
02399
02400
02401
02402
02403 app.cli_interpreter.videofiles = getPlayedMiroVideos()
02404
02405
02406
02407
02408 if app.cli_interpreter.videofiles:
02409 displayMessage(u"Starting Miro update of watched MythTV videos")
02410 app.cli_interpreter.do_mythtv_updatewatched(u'')
02411
02412
02413
02414
02415 app.cli_interpreter.do_mythtv_getunwatched(u'')
02416 unwatched = app.cli_interpreter.videofiles
02417
02418
02419
02420
02421 app.cli_interpreter.do_mythtv_getwatched(u'')
02422 watched = app.cli_interpreter.videofiles
02423
02424
02425
02426
02427 for item in unwatched:
02428
02429 if not item[u'channelTitle']:
02430 item[u'channelTitle'] = emptyTitle
02431 if not item[u'title']:
02432 item[u'title'] = emptySubTitle
02433 for item in watched:
02434
02435 if not item[u'channelTitle']:
02436 item[u'channelTitle'] = emptyTitle
02437 if not item[u'title']:
02438 item[u'title'] = emptySubTitle
02439
02440
02441
02442
02443
02444
02445 unwatched_copy = []
02446 for item in unwatched:
02447 unwatched_copy.append(item)
02448 for item in unwatched_copy:
02449 for x in watched:
02450 if item[u'channelTitle'] == x[u'channelTitle'] and item[u'title'] == x[u'title']:
02451 try:
02452 unwatched.remove(item)
02453
02454 app.cli_interpreter.do_mythtv_item_remove(item[u'videoFilename'])
02455 displayMessage((u"Skipped adding a duplicate Miro video to the MythTV "\
02456 u"Watch Recordings screen (%s - %s) which is already in "\
02457 u"MythVideo.\nSometimes a Miro channel has the same video "\
02458 u"downloaded multiple times.\nThis is a Miro/Channel web "\
02459 u"site issue and often rectifies itself overtime.") % \
02460 (item[u'channelTitle'], item[u'title']))
02461 except ValueError:
02462 pass
02463 duplicates = []
02464 for item in unwatched_copy:
02465 dup_flag = 0
02466 for x in unwatched:
02467 if item[u'channelTitle'] == x[u'channelTitle'] and item[u'title'] == x[u'title']:
02468 dup_flag+=1
02469 if dup_flag > 1:
02470 for x in duplicates:
02471 if item[u'channelTitle'] == x[u'channelTitle'] and item[u'title'] == x[u'title']:
02472 break
02473 else:
02474 duplicates.append(item)
02475
02476 for duplicate in duplicates:
02477 try:
02478 unwatched.remove(duplicate)
02479
02480 app.cli_interpreter.do_mythtv_item_remove(duplicate[u'videoFilename'])
02481 displayMessage((u"Skipped adding a Miro video to the MythTV Watch Recordings "\
02482 u"screen (%s - %s) as there are duplicate 'new' video items."\
02483 u"\nSometimes a Miro channel has the same video downloaded "\
02484 u"multiple times.\nThis is a Miro/Channel web site issue and "\
02485 u"often rectifies itself overtime.") % \
02486 (duplicate[u'channelTitle'], duplicate[u'title']))
02487 except ValueError:
02488 pass
02489
02490
02491
02492
02493 copy_items = []
02494
02495 if u'all' in channel_mythvideo_only.keys():
02496 for array in [watched, unwatched]:
02497 for item in array:
02498 copy_items.append(item)
02499 elif len(channel_mythvideo_only):
02500 for array in [watched, unwatched]:
02501 for video in array:
02502 if filter(is_not_punct_char, video[u'channelTitle'].lower()) in \
02503 channel_mythvideo_only.keys():
02504 copy_items.append(video)
02505
02506 if u'all' in channel_new_watch_copy.keys():
02507 for video in watched:
02508 copy_items.append(video)
02509 elif len(channel_new_watch_copy):
02510 for video in watched:
02511 if filter(is_not_punct_char, video[u'channelTitle'].lower()) in \
02512 channel_new_watch_copy.keys():
02513 copy_items.append(video)
02514
02515 channels_to_copy = {}
02516 for key in channel_mythvideo_only.keys():
02517 channels_to_copy[key] = channel_mythvideo_only[key]
02518 for key in channel_new_watch_copy.keys():
02519 channels_to_copy[key] = channel_new_watch_copy[key]
02520
02521 for video in copy_items:
02522 if channels_to_copy.has_key('all'):
02523 copy_dir = u"%s%s/" % (channels_to_copy['all'], sanitiseFileName(video[u'channelTitle']))
02524 else:
02525 copy_dir = channels_to_copy[filter(is_not_punct_char, video[u'channelTitle'].lower())]
02526
02527
02528 if not os.path.isdir(copy_dir):
02529 if simulation:
02530 logger.info(u"Simulation: Creating the MythVideo directory (%s)." % (copy_dir))
02531 else:
02532 os.makedirs(copy_dir)
02533
02534
02535
02536 save_video_filename = video[u'videoFilename']
02537 ext = getExtention(video[u'videoFilename'])
02538 if ext.lower() == u'm4v':
02539 ext = u'mpg'
02540 filepath = u"%s%s - %s.%s" % (copy_dir, sanitiseFileName(video[u'channelTitle']),
02541 sanitiseFileName(video[u'title']), ext)
02542 if simulation:
02543 logger.info(u"Simulation: Copying the Miro video (%s) to the MythVideo directory (%s)." % \
02544 (video[u'videoFilename'], filepath))
02545 else:
02546 try:
02547 shutil.copy2(video[u'videoFilename'], filepath)
02548 statistics[u'Miros_MythVideos_copied']+=1
02549 if u'mythvideo' in storagegroups.keys() and not local_only:
02550 video[u'videoFilename'] = filepath.replace(storagegroups[u'mythvideo'], u'')
02551 else:
02552 video[u'videoFilename'] = filepath
02553 except Exception, e:
02554 logger.critical((u"Copying the Miro video (%s) to the MythVideo directory (%s)."\
02555 u"\n This maybe a permissions error (mirobridge.py does "\
02556 u"not have permission to write to the directory), error(%s)") % \
02557 (video[u'videoFilename'], filepath, e))
02558
02559 app.controller.shutdown()
02560 time.sleep(5)
02561 sys.exit(1)
02562
02563
02564 if video[u'channel_icon'] and not video[u'channelTitle'].lower() in channel_icon_override:
02565 pass
02566 else:
02567 if video[u'item_icon']:
02568 video[u'channel_icon'] = video[u'item_icon']
02569
02570 graphics = metadata.getMetadata(sanitiseFileName(video[u'channelTitle']))
02571 if video[u'channel_icon'] and graphics['coverart'] == u'':
02572 ext = getExtention(video[u'channel_icon'])
02573 if video[u'channelTitle'].lower() in channel_icon_override:
02574 filepath = u"%s%s - %s%s.%s" % (vid_graphics_dirs[u'posterdir'],
02575 sanitiseFileName(video[u'channelTitle']),
02576 sanitiseFileName(video[u'title']),
02577 graphic_suffix[u'posterdir'], ext)
02578 else:
02579 filepath = u"%s%s%s.%s" % (vid_graphics_dirs[u'posterdir'],
02580 sanitiseFileName(video[u'channelTitle']),
02581 graphic_suffix[u'posterdir'], ext)
02582
02583
02584 if not os.path.isfile(filepath) or os.path.islink(filepath):
02585 if simulation:
02586 logger.info((u"Simulation: Copying the Channel Icon (%s) to the poster "\
02587 u"directory (%s).") % (video[u'channel_icon'], filepath))
02588 else:
02589 try:
02590 try:
02591 os.remove(filepath)
02592 except OSError:
02593 pass
02594 shutil.copy2(video[u'channel_icon'], filepath)
02595 if u'posterdir' in storagegroups.keys() and not local_only:
02596 video[u'channel_icon'] = filepath.replace(storagegroups[u'posterdir'], u'')
02597 else:
02598 video[u'channel_icon'] = filepath
02599 except Exception, e:
02600 logger.critical((u"Copying the Channel Icon (%s) to the poster directory "\
02601 u"(%s).\n This maybe a permissions error "\
02602 u"(mirobridge.py does not have permission to write to the "\
02603 u"directory), error(%s)") % \
02604 (video[u'channel_icon'], filepath, e))
02605
02606 app.controller.shutdown()
02607 time.sleep(5)
02608 sys.exit(1)
02609 else:
02610 if u'posterdir' in storagegroups.keys() and not local_only:
02611 video[u'channel_icon'] = filepath.replace(storagegroups[u'posterdir'], u'')
02612 else:
02613 video[u'channel_icon'] = filepath
02614 else:
02615 video[u'channel_icon'] = graphics['coverart']
02616
02617
02618 if video[u'screenshot']:
02619 ext = getExtention(video[u'screenshot'])
02620 filepath = u"%s%s - %s%s.%s" % (vid_graphics_dirs[u'episodeimagedir'],
02621 sanitiseFileName(video[u'channelTitle']),
02622 sanitiseFileName(video[u'title']),
02623 graphic_suffix[u'episodeimagedir'], ext)
02624 else:
02625 filepath = u''
02626
02627 if not os.path.isfile(filepath) or os.path.islink(filepath):
02628 if video[u'screenshot']:
02629 if simulation:
02630 logger.info((u"Simulation: Copying the Screenshot (%s) to the Screenshot "\
02631 u"directory (%s).") % (video[u'screenshot'], filepath))
02632 else:
02633 try:
02634 try:
02635 os.remove(filepath)
02636 except OSError:
02637 pass
02638 shutil.copy2(video[u'screenshot'], filepath)
02639 displayMessage(u"Copied Miro screenshot file (%s) to MythVideo (%s)" % \
02640 (video[u'screenshot'], filepath))
02641 if u'episodeimagedir' in storagegroups.keys() and not local_only:
02642 video[u'screenshot'] = filepath.replace(storagegroups[u'episodeimagedir'],
02643 u'')
02644 else:
02645 video[u'screenshot'] = filepath
02646 except Exception, e:
02647 logger.critical((u"Copying the Screenshot (%s) to the Screenshot directory "\
02648 u"(%s).\n This maybe a permissions error "\
02649 u"(mirobridge.py does not have permission to write to the "\
02650 u"directory), error(%s)") % \
02651 (video[u'screenshot'], filepath, e))
02652
02653 app.controller.shutdown()
02654 time.sleep(5)
02655 sys.exit(1)
02656 elif video[u'screenshot']:
02657 if u'episodeimagedir' in storagegroups.keys() and not local_only:
02658 video[u'screenshot'] = filepath.replace(storagegroups[u'episodeimagedir'], u'')
02659 else:
02660 video[u'screenshot'] = filepath
02661 video[u'copied'] = True
02662
02663
02664 app.cli_interpreter.do_mythtv_item_remove(save_video_filename)
02665
02666
02667
02668 app.controller.shutdown()
02669 time.sleep(5)
02670
02671
02672
02673
02674
02675
02676
02677 if channel_mythvideo_only.has_key(u'all'):
02678 for video in unwatched:
02679 watched.append(video)
02680 unwatched = []
02681 else:
02682 if len(channel_mythvideo_only):
02683 unwatched_copy = []
02684 for video in unwatched:
02685 if not filter(is_not_punct_char, video[u'channelTitle'].lower()) in \
02686 channel_mythvideo_only.keys():
02687 unwatched_copy.append(video)
02688 else:
02689 watched.append(video)
02690 unwatched = unwatched_copy
02691
02692 statistics[u'Total_unwatched'] = len(unwatched)
02693 if not len(unwatched):
02694 displayMessage(u"There are no Miro unwatched video items to add as MythTV Recorded videos.")
02695 if not updateMythRecorded(unwatched):
02696 logger.critical(u"Updating MythTV Recording with Miro video files failed." % \
02697 str(base_video_dir))
02698 sys.exit(1)
02699
02700
02701
02702
02703
02704
02705 if len(channel_watch_only):
02706 if channel_watch_only[0].lower() == u'all':
02707 printStatistics()
02708 return True
02709
02710 if not len(watched):
02711 displayMessage(u"There are no Miro watched items to add to MythVideo")
02712 if not updateMythVideo(watched):
02713 logger.critical(u"Updating MythVideo with Miro video files failed.")
02714 sys.exit(1)
02715
02716 printStatistics()
02717 return True
02718
02719
02720 if __name__ == "__main__":
02721 myapp = singleinstance(u'/tmp/mirobridge.pid')
02722
02723
02724
02725 if myapp.alreadyrunning():
02726 print u'\nMiro Bridge is already running only one instance can run at a time\n\n'
02727 sys.exit(0)
02728
02729 main()
02730 displayMessage(u"Miro Bridge Processing completed")
02731