MythTV  0.26-pre
mirobridge.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # -*- coding: UTF-8 -*-
00003 # ----------------------
00004 # Name: mirobridge.py   Maintains MythTV database with Miro's downloaded video files.
00005 # Python Script
00006 # Author:   R.D. Vaughan
00007 # Purpose:  This python script is intended to perform synchronise Miro's video files with MythTV's
00008 #           "Watch Recordings" and MythVideo.
00009 #
00010 #           The source of all video files is from those downloaded my Miro.
00011 #           The source of all cover art and screen shoots are from those downloaded and maintained by
00012 #           Miro.
00013 #           Miro v2.03 or later must be already installed and configured and already capable of
00014 #           downloading videos.
00015 #
00016 # Command line examples:
00017 # See help (-u and -h) options
00018 #
00019 # License:Creative Commons GNU GPL v2
00020 # (http://creativecommons.org/licenses/GPL/2.0/)
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 # 0.1.0 Initial development
00035 # 0.2.0 Initial Alpha release for internal testing only
00036 # 0.2.1 Fixes from initial alpha test
00037 #       Renamed imported micro python modules
00038 # 0.2.2 Fixed the duplicate video situation when Miro and a Channel feed has an issue which causes
00039 #       the same video to be downloaded multiple time. This situation is now detected and protects
00040 #       the duplicates from being added to either the MythTV or MythVideo.
00041 #       Fixes a few problems with stripping HTML and converting HTML characters in Miro descriptions.
00042 #       Changed the identification of HD and 720 videos to conform to MythTV standards
00043 #       Removed the file "mirobridge_util.py" from the mirobridge distribution as the main Miro
00044 #       distribution file "util.py" is used instead.
00045 #       Changed ALL symlink Miro icons (coverart) to a copied Miro icon file, replace any coverart that
00046 #       is currently a symlink to a copied Miro icon file and check that each MythVideo subdirectory
00047 #       has an actual file that has the proper naming convention that supports storage groups.
00048 #       Use using ImageMagick's utility 'mogrify' to convert Miro's screenshots to true png's (they are
00049 #       really jpg files with a png extension) and reduce their size by 50% for the Watch Recordings
00050 #       screen. Imagemagick is now a requirement to have installed.
00051 #       Fixed a bug when either the Miro video's Channel title or title exceeded MythTV database 128
00052 #       characters limit for their equivalent title and subtitle fields.
00053 #       When Miro has no screen shot for a Video substitute the item icon if it is high enough quality.
00054 # 0.2.3 All subdirectory coverfiles names are changed to "folder".(jpg/png) and gif files are converted.
00055 #       Imagemagick is now mandatory as Miro has cover art which are gif types and must be converted
00056 #       to either jpg or png so they will be recognised as folder coverart for Storage Groups.
00057 #       Added if coverart from Miro is a gif type convert it to a jpg.
00058 #       Removed resizing screenshots by 50% when adding a screen shot to the Watch Recordings screen. As
00059 #       some graphics were already very small.
00060 #       Fixed a bug were a Watch Recordings video was deleted by the user but the graphics files were not
00061 #       also deleted.
00062 #       Converted all mirobridge console messages to proper logger format (critical, error, warning, info)
00063 #       Add changes as Anduin did to ttvdb.py to force 'utf8' output
00064 #       Tested foreign language video's with their foreign language metadata - No problem
00065 # 0.2.4 Added a percentage downloaded message for each item that is downloading - updated every 30 secs.
00066 #       Made Miro update and auto-download the default (no option -d). Added option (-n) to suppress
00067 #       update and download processing.
00068 #       If there is no screenshot then create one with ffmpeg. The size for Watch Recordings is 320 wide
00069 #       and the mythtvideo screenshots are the same size as the video. From this point on this feature
00070 #       will known as the "iamlindoro effect".
00071 #       Fixed a bug where fold covers were being created even if a "folder.png" was already available.
00072 #       As Anduin had done with ttvdb's support *.py files mirobridge's support *.py and example conf
00073 #       files have been moved to a mirobridge subdirectory.
00074 #       Added a check that makes sure that the Miro items are only video files. Audio files are skipped.
00075 #       This is the final version that will support Miro v2.0.3 all higher versions will support
00076 #       Miro v2.5.2 and higher. Except for small bugs this version will no longer be enhanced.
00077 #       Added a small statistics report.
00078 # 0.2.5 Changes required for mirobridge to support Miro v2.5.2 have been made now mirobridge dynamically
00079 #       supports both versions v2.0.3 and v2.5.2
00080 #       Fixed a statistics report bug for the copied to MythVideo totals and add new totals.
00081 #       Fixed a bug where the Miro screen shot was not being resized for the Watch Recordings screen.
00082 #       Fixed a bug when the Miro download time has not been set. In that case use the current date and
00083 #       time. If this is not done the video cannot be deleted from Watch Recordings.
00084 #       Fixed a bug where a video copied to MythVideo gets stranded in the Watch Recordings screen
00085 #       even though the video file has been deleted in Miro.
00086 #       Fixed a bug where a video that has been watched does not get removed from the Watch Recordings
00087 #       screen. This stranded in the Watch Recordings screen even though the video file has been
00088 #       deleted in Miro. This only happened when videos had been watched BUT there were no new videos
00089 #       to add to the Watch Recordings screen.
00090 #       Fixed a typo in the statistics report.
00091 #       Fixed a Miro version check bug
00092 #       Fixed a test environment option bug
00093 #       Changed the "except" statement on imports to "except Exception:" as Anduin did to ttvdb.py
00094 # 0.3.0 Beta release
00095 # 0.3.1 Changed the check for Imagmagick convert utility and ffmpeg as the positive return value
00096 #       is different on some distributions (0 or 1). The fail value is consistent (127).
00097 #       Fixed the environment test option (-t) at times could give incorrect success message
00098 # 0.3.2 Fixed a bug when an empty item description would abort the script on an "extras" request
00099 #       Fixed a bug when the Watched item processing failed message would abort the script
00100 #       Fixed a bug when a user accidental deletes a video file symlink then the video symlink is NOT
00101 #       recreated as it should have been.
00102 #       Fixed a bug where double quotes in the title or subtitle caused issues with file names of graphics
00103 #       Added to the statistics report the total number of Miro-MythVideo videos that will eventually be
00104 #       expired and removed by Miro.
00105 #       Added a info log message with the Miro Bridge version number, which may help in problem analysis.
00106 #       Added trapping and diagnostic messages when the HTML tags could not be removed from a description.
00107 #       Added a check that the installed "pyparsing" python library is at least v1.5.0
00108 #       Added detection of a Miro video deletion though the MythVideo UI. When this occurs
00109 #       Miro is told to also deletes the video, graphics and meta data.
00110 # 0.3.3 Status change from Beta to production. No code changes
00111 # 0.3.4 Fixed when checking for orphaned videometadata records and there were no Miro videometadata
00112 #       records.
00113 #       Added additional detection and restrictions to the supported versions of Miro (minimum v2.0.3)
00114 #       preferrably v2.5.2 or higher.
00115 # 0.3.5 Use the MythVideo.py binding rmMetadata() routine to delete old videometatdata records.
00116 #       Added access checks for all directories that Miro Bridge needs to write
00117 # 0.3.6 Modifications as per requested changes.
00118 # 0.3.7 Fixed a bug with previous modifications that impacted Miro v2.0.3 only
00119 # 0.3.8 Fixed unicode errors with file names
00120 #       Change to assist SG image hunting added the suffix "_coverart, _fanart, _banner,
00121 #       _screenshot" respectively for any copied/created graphics.
00122 # 0.3.9 Fixed an issue when deleting a Miro video and the title/subtitle was not found due to special
00123 #       characters. The search and matching is now more robust.
00124 #       Fixed a bug where file name unicode errors caused an abort when creating screen shots
00125 #       Removed from the mirobrodge.conf file the sections "[tv] and [movies]". This functionality
00126 #       will be added to the Jamu v0.5.0 -MW option.
00127 #       Added a check for locally available banners and fanart files when creating a MythVideo record.
00128 #       This is added as Jamu v0.5.0 option -MW downloads graphics from TVDB and TMDB for Miro videos
00129 #       when available.
00130 #       Modified the check for mirobridge.conf to accomodate the needs of Mythbuntu.
00131 #       Add mythcommflag seektable building for both recordings and mythvideo Miro videos.
00132 # 0.4.0 Fixed an abort where a variable had not been named properly due to a cut and paste error.
00133 # 0.4.1 Added a check that no other instance of Miro Bridge or Miro is already running. If there is
00134 #       then post a critical error message and exit.
00135 #       Do not add the Miro Bridge default banner when a Miro video has no subtitle as it overlaps
00136 #       the title display on MythVideo information pop-ups.
00137 #       Fixed a bug where a folder icon was being recreated even when it already existed.
00138 # 0.4.2 Added a missing import of "htmlentitydefs" which is rarely used in the description/plot XTML parsing
00139 # 0.4.3 Added support for audio type detection (2-channel, 6-channel, ...) that matches changes in ffmpeg.
00140 #       ffmpeg is used in the detection of a video file's audio properties.
00141 #       New Miro Videos added to the Default recordings directory have their names conform to MythTV standards
00142 #       of "CHANID_ISODATETIME.ext". This resolves an obsure bug that caused orphaned screen shot graphics and
00143 #       issues with Miro video deletions from the Watch Recordings screen if MiroBridge was run when the user
00144 #       had MythTV started and in the Watch Recordings screen.
00145 # 0.4.4 Fixed a unicode issue with data read from a subprocess call.
00146 #       Fixed an issue with the check for other instances of mirobridge.py running.
00147 # 0.4.5 Fixed a deletion issue when a Miro video subtitle contained more than 128 characters.
00148 #       Disabled seek table creation as a number of the Miro video types (e.g. mov) do not work in MythTV with
00149 #       seek tables.
00150 # 0.4.6 Changed "original air date" and "air date" to be Miro's item release date. This is more appropriate then
00151 #       using download date as was done previously. Download date is still the fall back of there is no
00152 #       release date.
00153 # 0.4.7 Changed all occurances of "strftime(u'" to "strftime('" as the unicode causes issues with python versions
00154 #       less than 2.6
00155 # 0.4.8 Some Miro "release" date values are not valid. Override with the current date.
00156 # 0.4.9 The ffmpeg SVN (e.g. SVN-r20151) is now outputting additional metadata, skip metadata that cannot be
00157 #       processed.
00158 # 0.5.0 Correct the addition of adding hostnames to videometadata records and the use of relative paths when
00159 #       there is no Videos Storage Group.
00160 #       Added more informative error messages when trying to connect to the MythTV data base
00161 # 0.5.1 Fixed the config "all" options for command line -N and -M and config file sections [watch_then_copy]
00162 #       and [mythvideo_only].
00163 #       Changed return codes from True to 0 and False to 1.
00164 #       Added display of the directories that will be used by MiroBridge and whether they are storage groups.
00165 #       Added file name sanitising logic plus a config file variable to add characters to be replaced by '_'.
00166 #       The config file variable 'file name_char_filter' is required by users who save MiroBridge files on a
00167 #       file system which only supports MS-Windows file naming conventions.
00168 # 0.5.2 Convert to new python bindings and replace all direct mysql data base calls. See ticket #7264
00169 #       Remove the requirement for the MySQLdb python library.
00170 #       Remove all seek table processing as it is not used due to issues with some video file types. This code had been
00171 #       previously disable but the code and related mythcommflag related code has been removed entirely.
00172 #       Initialized new videometadata fields 'releasedate' and 'hash'.
00173 # 0.5.3 Fixed Exception messages
00174 # 0.5.4 Add the command line option (-i) to import an OPML file that was exported from Miro on a different PC.
00175 #       This new option allows configuring Miro channels on a separate PC then importing those channels on
00176 #       a Myth backend without a keyboard. No Miro GUI needs to run on the MythTV backend.
00177 # 0.5.5 Fixed bug #8051 - creating hash value for non-SG Miro video files had an incorrect variable and missing
00178 #       library import.
00179 # 0.5.6 Fixed an abort and subsequent process hang when displaying a critical Miro start-up error
00180 # 0.5.7 The "DeletesFollowLinks" setting is incompatible with MiroBridge processings. A check
00181 #       and termination of MiroBridge with an appropriate error message has been added.
00182 #       Added better system error messages when an IOError exception occurs
00183 # 0.5.8 Add support for Miro version 3.0
00184 # 0.5.9 Update for changes in Python Bindings
00185 # 0.6.0 Fixed a issue when a Miro video's metadata does not have a title. It was being re-added and the
00186 #       database title fields in several records was being left empty.
00187 # 0.6.1 Modifications to support MythTV python bindings changes
00188 # 0.6.2 Trapped possible unicode errors which would hang the MiroBridge process
00189 # 0.6.3 Pull hostname from python bindings instead of socket libraries
00190 # 0.6.4 MythTV python bindings changes
00191 # 0.6.5 Added support for Miro v3.5.x
00192 #       Small internal document changes
00193 # 0.6.6 Fixed screenshot code due to changes in ffmpeg. First
00194 #       noticed in Ubuntu 10.10 (ffmepg v 0.6-4:0.6-2ubuntu6)
00195 # 0.6.7 Added support for Miro v4.0.2 or higher
00196 #       Integrate with the new metadata functionality in recordings. Now users can specify graphics for
00197 #           Miro Channels or set the Miro Channel name to match an entry in ttvdb.com and MythTV will
00198 #           download the artwork automatically.
00199 #       Automatically convert any copied Miro videos with an inetref of '99999999' which was only required
00200 #           for the defunct Jamu script. The category is changed to 'Video Cast" and  the inetref is removed
00201 #           unless there is a matching Recording rule.
00202 #       Silenced verbose output from ffmpeg when creating a screenshot
00203 #       Fixed delOldRecording abort when a Channel sends two videos with identical published date and time.
00204 #           All starttimes are now unique.
00205 #       Fixed aborts caused by bad metadata in Miro (videoFilename)
00206 #       Fixed a minor bug when a video is marked as watched within Miro but was not being removed from
00207 #           "Watched Recordings" until the video expired
00208 #       Removed creation of "folder.png" graphics when creating directories as that is no longer used
00209 #           by MythVideo
00210 #       Fixed the options "-h, --help" command line display
00211 # 0.6.8 Sometimes Miro metadata has no video filename. Skip these invalid videos.
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 # Common function imports
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 # Set command line options and arguments
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 # Global variables
00283 opts, args = parser.parse_args() # Command line arguments and options
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={} # The gobal dictionary is only populated with the current hosts storage group entries
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                     # The MythVideo Miro directory structure flat or channel subdirectories
00311 download_sleeptime = float(60)    # The default value for the time to wait for auto downloading to start
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        # Used with the (-t) test environment option
00320 imagemagick = True
00321 mythcommflag_recordings = u'%s -c %%s -s "%%s" --rebuild' # or u'mythcommflag -f "%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 # Initalize Report Statistics:
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 # Sub class sys.stdout and sys.stderr as a utf8 stream. Deals with print and stdout unicode issues
00357 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
00358 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
00359 
00360 # Create logger
00361 logger = logging.getLogger(u"mirobridge")
00362 logger.setLevel(logging.DEBUG)
00363 # Create console handler and set level to debug
00364 ch = logging.StreamHandler()
00365 ch.setLevel(logging.DEBUG)
00366 # Create formatter
00367 formatter = logging.Formatter(u"%(asctime)s - %(name)s - %(levelname)s - %(message)s")
00368 # Add formatter to ch
00369 ch.setFormatter(formatter)
00370 # Add ch to logger
00371 logger.addHandler(ch)
00372 
00373 # The pyparsing python library must be installed version 1.5.0 or higher
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 # Find out if the MythTV python bindings can be accessed and instances can be created
00389 try:
00390     #If the MythTV python interface is found, we can insert data directly to MythDB or
00391     #get the directories to store poster, fanart, banner and episode graphics.
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         #Create an instance of each: MythDB, MythVideo
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 # Find out if the Miro python bindings can be accessed and instances can be created
00423 try:
00424     # Initialize locale as required for Miro v3.x
00425     try:
00426         # Setup gconf_name early on so we can load config values
00427         from miro.plat import utils
00428         utils.initialize_locale()
00429     except:
00430         pass
00431 
00432     # Set up gettext before everything else
00433     from miro import config             # New for Miro4 (Changed import location)
00434     from miro import eventloop          # New for Miro4
00435     from miro import gtcache
00436     config.load()                       # New for Miro4
00437     gtcache.init()
00438 
00439     # This fixes some/all problems with Python 2.6 support but should be
00440     # re-addressed when we have a system with Python 2.6 to test with.
00441     # (bug 11262)
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     # Required for Miro 4 as the configuration calls changed location
00454     # and additional Miro 4 specific imports are required
00455     try:
00456         dummy = app.config.get(prefs.APP_VERSION)
00457         # A test to see if this is Miro v4 before the version can be read.
00458         # If there is no exception this is Miro v4
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 # end _can_int
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 # end _displayMessage
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 # end getExtention
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 # end sanitiseFileName()
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 # end useImageMagick()
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         # See if pidFile exists
00588         #
00589         if os.path.exists(pidPath):
00590             #
00591             # Make sure it is not a "stale" pidFile
00592             #
00593             try:
00594                 pid=int(open(pidPath, 'r').read().strip())
00595                 #
00596                 # Check list of running pids, if not running it is stale so
00597                 # overwrite
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             # Write my pid into pidFile to keep multiple copies of program from
00616             # running.
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     # end singleinstance()
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  #end isMiroRunning()
00661 
00662 
00663 # Two routines used for movie title search and matching
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 # end delOldRecorded()
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 # end delRecorded()
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     # Use the MythVideo hashing protocol when the video is in a storage groups
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     # Use a local hashing routine when video is not in a Videos storage group
00723     # For original code: http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes#Python
00724     try:
00725         longlongformat = 'q'  # long long
00726         bytesize = struct.calcsize(longlongformat)
00727         f = open(filename, "rb")
00728         filesize = os.path.getsize(filename)
00729         hash = filesize
00730         if filesize < 65536 * 2:    # Video file is too small
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 #to remain as 64bit number
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):    # Accessing to this video file caused and error
00748         return u''
00749 # end hashFile()
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     # There is a chance that this is already an absolute path
00762     if relpath[0] == u'/':
00763         return relpath
00764 
00765     if not storagegroups.has_key(filetype):
00766         return relpath    # The Videos storage group path does not exist at all the metadata entry is useless
00767 
00768     for directory in storagegroups[filetype]:
00769         abpath = u"%s/%s" % (directory, relpath)
00770         if os.path.isfile(abpath): # The file must actually exist locally
00771             return abpath
00772     else:
00773         return relpath    # The relative path does not exist at all the metadata entry is useless
00774 # end rtnAbsolutePath
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    # Skip any line that has non-utf8 characters in it
00792                 except (UnicodeEncodeError, TypeError):
00793                     dirname = record.dirname  # assume already unicode and pass unchanged
00794 
00795                 # Add a slash if missing to any storage group dirname
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         # Verify that each storage group is an existing local directory
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 # end getStorageGroups
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     # Stop processing if this local host has any storage groups
00820     global localhostname, vid_graphics_dirs, dir_dict, storagegroups, local_only, verbose
00821 
00822     # When there is NO SG for Videos then ALL graphics paths MUST be local paths set in the FE and accessable
00823     # from the backend
00824     if storagegroups.has_key(u'mythvideo'):
00825         local_only = False
00826         # Pick up storage groups first
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                 # Set fall back graphics directory to Videos
00834                 vid_graphics_dirs[key] = storagegroups[u'mythvideo']
00835                 # Set fall back SG graphics directory to Videos
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         # Only use path from MythTV if one was found
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:    # There is the chance that MythTv DB does not have a dir
00876                  logger.error(u"(%s) directory is not set or does not exist(%s)" % (key, dir_dict[key]))
00877 
00878     # Make sure there is a directory set for Videos and other graphics directories on this host
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     # Make sure that there is read/write access to all the directories Miro Bridge uses
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     # Print out the video and image directories that will be used for processing
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     # end getMythtvDirectories()
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             # Add the Channel names to the array of Channels that are to use their item's icon
00959             for option in cfg.options(section):
00960                 channel_icon_override.append(option)
00961             continue
00962         if section == u'watch_only':
00963             # Add the Channel names to the array of Channels that will only not be moved to MythVideo
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             # Add the Channel names to the array of Channels that will be moved to MythVideo only
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             # Add the Channel names to the array of Channels once watched will be copied to MythVideo
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 # end setUserconfig
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              # character reference
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              # named entity
01039              # reescape the reserved characters.
01040              try:
01041                 if text[1:-1] == u"amp":
01042                    text = u"&amp;amp;"
01043                 elif text[1:-1] == u"gt":
01044                    text = u"&amp;gt;"
01045                 elif text[1:-1] == u"lt":
01046                    text = u"&amp;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 # leave as is
01054        return re.sub(u"&#?\w+;", fixup, text)
01055 
01056     details = {}
01057     if not description: # Is there anything to massage
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     # first pass, strip out tags and translate entities
01082     firstPass = (htmlComment | scriptBody | commonHTMLEntity |
01083                  anyTag | anyClose ).transformString(description)
01084 
01085     # first pass leaves many blank lines, collapse these down
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             # probe the nature [...]Rating: 3.0/5 (1 vote cast)
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                     # HD trailers ratings are our of 5 not 10 like MythTV so must be multiplied by two
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     # end massageDescription()
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     # Convert any old videmetadata copied videos changing their inetref and category values.
01145     # Prevents accidental deletions.
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             # Sometimes a channel issues videos with identical publishing (starttime) dates.
01172             # Try to using additiional details to identify the correct oldrecord.
01173                 delOldRecorded((channel_id, data['starttime'], data['endtime'], data['title'],
01174                                 data['subtitle'], data['description'])).delete()
01175             except:
01176                 pass
01177 
01178             # Attempt a clean up for orphaned recorded video files and/or graphics
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             # Attempt a clean up for orphaned MythVideo files and/or graphics from the Default directory
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             # Attempt a clean up for orphaned MythVideo screenshot
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             # Remove any unique cover art graphic files
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     # end getOldrecordedOrphans()
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     # Check if there is a Miro video with an identical start time.
01228     # If there is incremement that start and end times by one until unique.
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     # end getStartEndTimes()
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', ]: # convert graphics to standard jpg and png types
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 # file already exists
01284     try: # Try to remove a broken symbolic link
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:    # Every file but video files copied to support Storage groups
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     # end setSymbolic()
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     # Create the oldrecorded dictionary
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     # end createOldRecordedRecord()
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     # Get any graphics that already exist
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     # Create the recorded dictionary
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     # Create the recordedpropgram dictionary
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     # end  createRecordedRecord()
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     # Get any graphics that already exist
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     # end createVideometadataRecord()
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     # end createChannelRecord(icon)
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      # end checkVideometadataFails()
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     # Check that a MIRO video directory exists
01627     # if not then create the dir and add symbolic link to cover/file or icon
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     # end createMiroMythVideoDirectory()
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     # end createMiroChannelSubdirectory()
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:    # Skip if the video has NOT been watched
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     # end getPlayedMiroVideos()
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: # There may not be any new items but a clean up of existing recorded may be required
01710         items = []
01711     items_copy = list(items)
01712 
01713     # Deal with existing Miro videos already in the MythTV data base
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         # Remove any videos that were marked as viewed within Miro but NOT MythTV
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         # Remove any Miro related "watched" recordings (symlink, recorded records)
01728         # Or remove any Miro related with broken symbolic video links
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             # Remove the database recorded and oldrecorded records
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                     # Attempting to clean up an recorded record and
01742                     # its associated video file (miro symlink)
01743                     rtn = delRecorded((record['chanid'], record['starttime'])).delete()
01744                 except MythError, e:
01745                     pass
01746 
01747                 # Clean up for recorded video files and/or graphics
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: # Attempting to clean up an orphaned oldrecorded record which may or may not exist
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:    # Skip any item already in MythTV data base
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     # Add new Miro unwatched videos to MythTV'd data base
01767     for item in items_copy:
01768         # Do not create records for Miro video files when Miro has a corrupt or missing file name
01769         if item[u'videoFilename'] == None:
01770             continue
01771         # Do not create records for Miro video files that do not exist
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 # Do not create records for Miro video files that do not exist
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']: # Add Cover art link to 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)): # Make sure it does not exist
01801                 if simulation:
01802                     logger.info(u"Simulation: Remove symbolic link(%s)" % (coverart_filename,))
01803                 else:
01804                     try:    # Clean up broken symbolic link
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: # Add Miro screen shot to 'Default' recordings directory
01823             screenshot_recorded = u"%s%s.png" % (vid_graphics_dirs[u'default'], records[0][u'basename'])
01824             if not os.path.isfile(screenshot_recorded): # Make sure it does not exist
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     # updateMythRecorded()
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: # There may not be any new items but a clean up of existing records may be required
01876         items = []
01877     # Check that a MIRO video directory exists
01878     # if not then create the dir and add symbolic link to cover/file or icon
01879     createMiroMythVideoDirectory()
01880 
01881     # Remove any Miro Mythvideo records which the video or graphics paths are broken
01882     records = list(mythdb.searchVideos(category=u'Miro'))
01883     statistics[u'Total_Miro_MythVideos'] = len(records)
01884     for record in records: # Count the Miro-MythVideos that Miro is expiring or has saved
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']): # Only delete video files if they are symlinks
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: # Only delete video files if they are symlinks
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: # An orphaned oldrecorded record may not exist
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                 # Remove video file
01924                 metadata.deleteFile(record[u'filename'], record[u'host'], u'mythvideo')
01925             if record[u'screenshot']: # Remove any associated Screenshot
01926                 metadata.deleteFile(record[u'screenshot'], record[u'host'], u'episodeimagedir')
01927             # Remove any unique cover art graphic files
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: # There may not be any new items to add to MythVideo
01932         return True
01933     # Reread Miro Mythvideo videometadata records
01934     # Remove the matching videometadata record from array of items
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: # Remove any items that are for a Channel that does not get MythVideo records
01950         if filter(is_not_punct_char, item[u'channelTitle'].lower()) in channel_watch_only:
01951             try:    # Some items may have already been removed, let those passed
01952                 items_copy.remove(item)
01953             except ValueError:
01954                 pass
01955     # Add Miro videos that remain in the item list
01956     # If not a flat directory check if title directory exists and add icon symbolic link as coverfile
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']: # If there is no screen shot then create one
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:  # Check for duplicates
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 == []: # Check for an empty array
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     # end updateMythVideo()
02019 
02020 def printStatistics():
02021     global statistics
02022 
02023     # Print statistics
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     # end printStatistics()
02046 
02047 
02048 # Main script processing starts here
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:                    # Display example information
02062         sys.stdout.write(examples_txt+'\n')
02063         sys.exit(0)
02064 
02065     if opts.version:        # Display program information
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     # Verify that Miro is not currently running
02076     if isMiroRunning():
02077         sys.exit(1)
02078 
02079     # Verify that only None or one of the mutually exclusive (-W), (-M) and (-N) options is being used
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     # Set option related global variables
02091     simulation = opts.simulation
02092     verbose = opts.verbose
02093     if opts.hostname:    # Override localhostname if the user specified an hostname
02094         localhostname = opts.hostname
02095 
02096     # Validate settings
02097 
02098     ## Video base directory and current version and revision numbers
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     # Verify Miro version sufficent and Video file configuration correct.
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     # Miro 4.0.1 has a critical bug that effects MiroBridge. Miro must be upgraded.
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     # Verify that the import opml option can be used
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     # Get storage groups
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()    # Initialize all the Video and graphics directory dictionary
02208 
02209     if opts.nosubdirs: # Did the user want a flat MythVideo "Miro" directory structure?
02210         flat = True
02211 
02212     # Get the values in the mirobridge.conf configuration file
02213     setUseroptions()
02214 
02215     if opts.watch_only:
02216         # ALL Miro videos will only be viewed in the MythTV "Watch Recordings" screen
02217         channel_watch_only = [u'all']
02218 
02219     if opts.mythvideo_only: # ALL Miro videos will be copied to MythVideo and removed from Miro
02220         channel_mythvideo_only = {u'all': vid_graphics_dirs[u'mythvideo']+u'Miro/'}
02221 
02222     # Once watched ALL Miro videos will be copied to MythVideo and removed from Miro
02223     if opts.new_watch_copy:
02224         channel_new_watch_copy = {u'all': vid_graphics_dirs[u'mythvideo']+u'Miro/'}
02225 
02226     # Verify that "Mythvideo Only" and "New-Watch-Copy" channels do not clash
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     # Verify that ImageMagick is installed
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     # Verify that the "DeletesFollowLinks" setting is not set to the character '1'
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     # Initialize class with the metadata methods
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:        # All tests passed
02274         metadata.getVideoDetails(u"") # Test that ffmpeg is available
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':    # Add a Miro Channel record - Should only be done once
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     # Mainlogic for all Miro bridge and MythTV
02290     ###########################################
02291 
02292     #
02293     # Start the Miro Front and Backend - This allows mirobridge to execute actions on the Miro backend
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": # Only required for Miro 4
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": # Only required for Miro 4
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) # Let the shutdown processing complete
02312         sys.exit(1)
02313 
02314     if miroConfiguration(prefs.APP_VERSION) > u"4.0": # Only required for Miro 4
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": # Only required for Miro 4
02332         pass
02333     else:
02334         app.movie_data_program_info = app.cli_interpreter.movie_data_program_info
02335 
02336     #
02337     # Attempt to import an opml file
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) # Let the Miro backend process the OPML file before shutting down
02344         except Exception, e:
02345             logger.critical(u"Import of OPML file (%s) failed, error(%s)." % (opts.import_opml, e))
02346             results = 1
02347         # Gracefully close the Miro database and shutdown the Miro Front and Back ends
02348         app.controller.shutdown()
02349         time.sleep(5) # Let the shutdown processing complete
02350         sys.exit(results)
02351 
02352     #
02353     # Optionally Update Miro feeds and
02354     # download any "autodownloadable" videos which are pending
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             # Miro 4 automatically refreshes feeds and downloads
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     # Deal with orphaned oldrecorded records.
02389     # These records indicate that the MythTV user deleted the video from the Watched Recordings screen
02390     # or from MythVideo
02391     # These video items must also be deleted from Miro
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             # Completely remove the video and item information from Miro
02398             app.cli_interpreter.do_mythtv_item_remove([video[u'title'], video[u'subtitle']])
02399 
02400     #
02401     # Collect the set of played Miro video files
02402     #
02403     app.cli_interpreter.videofiles = getPlayedMiroVideos()
02404 
02405     #
02406     # Updated the played status of items
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     # Get the unwatched videos details from Miro
02414     #
02415     app.cli_interpreter.do_mythtv_getunwatched(u'')
02416     unwatched = app.cli_interpreter.videofiles
02417 
02418     #
02419     # Get the watched videos details from Miro
02420     #
02421     app.cli_interpreter.do_mythtv_getwatched(u'')
02422     watched = app.cli_interpreter.videofiles
02423 
02424     #
02425     # Massage empty titles and subtitles from Miro
02426     #
02427     for item in unwatched:
02428         # Deal with empty titles and subtitles from Miro
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         # Deal with empty titles and subtitles from Miro
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     # Remove any duplicate Miro videoes from the unwatched or watched list of Miro videos
02442     # This means that Miro has duplicates due to a Miro/Channel website issue
02443     # These videos should not be added to the MythTV Watch Recordings screen
02444     #
02445     unwatched_copy = []
02446     for item in unwatched:
02447         unwatched_copy.append(item)
02448     for item in unwatched_copy: # Check for a duplicate against already watched Miro videos
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                     # Completely remove this duplicate video and item information from Miro
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: # Check for a duplicate against un-watched Miro videos
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             # Completely remove this duplicate video and item information from Miro
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     # Deal with any Channel videos that are to be copied and removed from Miro
02492     #
02493     copy_items = []
02494     # Copy unwatched and watched Miro videos (all or only selected Channels)
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     # Copy ONLY watched Miro videos (all or only selected Channels)
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         # Create the subdirectories to copy the video into
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         # Copy the Miro video file
02535         # This filename is needed later for deleting in Miro
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:    # Miro video copied into a MythVideo directory
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                 # Gracefully close the Miro database and shutdown the Miro Front and Back ends
02559                 app.controller.shutdown()
02560                 time.sleep(5) # Let the shutdown processing complete
02561                 sys.exit(1)
02562 
02563         # Copy the Channel or item's icon
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         # Get any graphics that already exist
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             # There may already be a Channel icon available
02583             # or it is a symlink which needs to be replaced
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:    # Miro Channel icon copied into a MythVideo directory
02590                         try: # Remove any old symlink file
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                         # Gracefully close the Miro database and shutdown the Miro Front and Back ends
02606                         app.controller.shutdown()
02607                         time.sleep(5) # Let the shutdown processing complete
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         # There may already be a Screenshot available or it is a symlink which needs to be replaced
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:    # Miro Channel icon copied into a MythVideo directory
02634                         try: # Remove any old symlink file
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                         # Gracefully close the Miro database and shutdown the Miro Front and Back ends
02653                         app.controller.shutdown()
02654                         time.sleep(5) # Let the shutdown processing complete
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     # Mark this video item as being copied
02662 
02663         # Completely remove the video and item information from Miro
02664         app.cli_interpreter.do_mythtv_item_remove(save_video_filename)
02665 
02666 
02667     # Gracefully close the Miro database and shutdown the Miro Front and Back ends
02668     app.controller.shutdown()
02669     time.sleep(5) # Let the shutdown processing complete
02670 
02671     #
02672     # Add and delete MythTV (Watch Recordings) Miro recorded records
02673     # Add and remove symlinks for Miro video files
02674     #
02675 
02676     # Check if the user does not want any channels Added to the "Watch Recordings" screen
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     # Add and delete MythVideo records for played Miro Videos
02702     # Add and delete symbolic links to Miro Videos and subdirectories
02703     # Add and delete symbolic links to coverart/Miro icons and Miro screenshots/fanart
02704     #
02705     if len(channel_watch_only): # If the user does not want any channels moved to MythVideo exit
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 # end main
02719 
02720 if __name__ == "__main__":
02721     myapp = singleinstance(u'/tmp/mirobridge.pid')
02722     #
02723     # check is another instance of Miro Bridge running
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends