|
MythTV
0.26-pre
|
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;" 01043 elif text[1:-1] == u"gt": 01044 text = u"&gt;" 01045 elif text[1:-1] == u"lt": 01046 text = u"&lt;" 01047 else: 01048 logger.info(u"%s" % text[1:-1]) 01049 text = unichr(htmlentitydefs.name2codepoint[text[1:-1]]) 01050 except KeyError: 01051 logger.warn(u"Remove HTML or XML character references: keyerror") 01052 pass 01053 return text # 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
1.7.6.1