22 __title__ =
"mirobridge - Maintains Miro's Video files with MythTV";
23 __author__=
"R.D.Vaughan"
25 This python script is intended to synchronise Miro's video files with MythTV's "Watch Recordings" and MythVideo.
27 The source of all video files are from those downloaded my Miro.
28 The source of all meta data for the video files is from the Miro data base.
29 The source of all cover art and screen shots are from those downloaded and maintained by Miro.
30 Miro v2.0.3 or later must already be installed and configured and capable of downloading videos.
216 For examples, please see the Mirobridge's wiki page at http://www.mythtv.org/wiki/MiroBridge
220 import sys, os, re, locale, subprocess, locale, ConfigParser, codecs, shutil, struct
221 import fnmatch, string, time, logging, traceback, platform, fnmatch, ConfigParser
222 from datetime
import timedelta
223 from optparse
import OptionParser
224 from socket
import gethostbyname
226 import htmlentitydefs
229 parser = OptionParser(usage=
u"%prog usage: mirobridge -huevstdociVHSCWM [parameters]\n")
231 parser.add_option(
"-e",
"--examples", action=
"store_true", default=
False, dest=
"examples",
232 help=
u"Display examples for executing the mirobridge script")
233 parser.add_option(
"-v",
"--version", action=
"store_true", default=
False, dest=
"version",
234 help=
u"Display version and author information")
235 parser.add_option(
"-s",
"--simulation", action=
"store_true", default=
False, dest=
"simulation",
236 help=
u"Simulation (dry run), no files are copied, symlinks created or MythTV data "\
237 u"bases altered. If option (-n) is NOT specified Miro auto downloads WILL take "\
238 u"place. See option (-n) help for details.")
239 parser.add_option(
"-t",
"--testenv", action=
"store_true", default=
False, dest=
"testenv",
240 help=
u"Test that the local environment can run all mirobridge functionality")
241 parser.add_option(
"-n",
"--no_autodownload", action=
"store_true", default=
False, dest=
"no_autodownload",
242 help=
u"Do not perform Miro Channel updates, video expiry and auto-downloadings. "\
243 u"Default is to perform all perform all Channel maintenance features.")
244 parser.add_option(
"-o",
"--nosubdirs", action=
"store_true", default=
False, dest=
"nosubdirs",
245 help=
u"Organise MythVideo's Miro directory WITHOUT Miro channel subdirectories. "\
246 u"The default is to have Channel subdirectories.")
247 parser.add_option(
"-c",
"--channel", metavar=
"CHANNEL_ID:CHANNEL_NUM", default=
"", dest=
"channel",
248 help=
u'Specifies the channel id that is used for Miros unplayed recordings. Enter '\
249 u'as "xxxx:yyy". Default is 9999:999. Be warned that once you change the '\
250 u'default channel_id "9999" you must always use this option!')
251 parser.add_option(
"-i",
"--import_opml", metavar=
"OPMLFILEPATH", default=
"", dest=
"import_opml",
252 help=
u'Import Miro exported OPML file containing Channel configurations. File '\
253 u'name must be a fully qualified path. This option is exclusive to Miro '\
254 u'v2.5.x and higher.')
255 parser.add_option(
"-V",
"--verbose", action=
"store_true", default=
False, dest=
"verbose",
256 help=
u"Display verbose messages when processing")
257 parser.add_option(
"-H",
"--hostname", metavar=
"HOSTNAME", default=
"", dest=
"hostname",
258 help=
u"MythTV Backend hostname mirobridge is to up date")
259 parser.add_option(
"-S",
"--sleeptime", metavar=
"SLEEP_DELAY_SECONDS", default=
"", dest=
"sleeptime",
260 help=
u"The amount of seconds to wait for an auto download to start.\nThe default "\
261 u"is 60 seconds, but this may need to be adjusted for slower Internet connections.")
262 parser.add_option(
"-C",
"--addchannel", metavar=
"ICONFILE_PATH", default=
"OFF", dest=
"addchannel",
263 help=
u'Add a Miro Channel record to MythTV. This gets rid of the "#9999 #9999" '\
264 u'on the Watch Recordings screen and replaces it with the usual\nthe channel '\
265 u'number and channel name.\nThe default if not overridden by the (-c) option '\
266 u'is channel number 999.\nIf a filename and path is supplied it will be set '\
267 u'as the channels icon. Make sure your override channel number is NOT one of '\
268 u'your current MythTV channel numbers.\nThis option is typically only used '\
269 u'once as there can only be one Miro channel record at a time.')
270 parser.add_option(
"-N",
"--new_watch_copy", action=
"store_true", default=
False, dest=
"new_watch_copy",
271 help=
u'For ALL Miro Channels: Use the "Watch Recording" screen to watch new Miro '\
272 u'downloads then once watched copy the videos, icons, screen shot and metadata '\
273 u'to MythVideo. Once coping is complete delete the video from Miro.\nThis option '\
274 u'overrides any "mirobridge.conf" settings.')
275 parser.add_option(
"-W",
"--watch_only", action=
"store_true", default=
False, dest=
"watch_only",
276 help=
u'For ALL Miro Channels: Only use "Watch Recording" never move any Miro videos '\
277 u'to MythVideo.\nThis option overrides any "mirobridge.conf" settings.')
278 parser.add_option(
"-M",
"--mythvideo_only", action=
"store_true", default=
False, dest=
"mythvideo_only",
279 help=
u'For ALL Miro Channel videos: Copy newly downloaded Miro videos to MythVideo '\
280 u'and removed from Miro. These Miro videos never appear in the MythTV "Watch '\
281 u'Recording" screen.\nThis option overrides any "mirobridge.conf" settings.')
285 opts, args = parser.parse_args()
287 dir_dict = {
u'posterdir':
u"VideoArtworkDir",
u'bannerdir':
u'mythvideo.bannerDir',
288 u'fanartdir':
u'mythvideo.fanartDir',
u'episodeimagedir':
u'mythvideo.screenshotDir',
289 u'mythvideo':
u'VideoStartupDir'}
290 vid_graphics_dirs = {
u'default' :
u'',
u'mythvideo':
u'',
u'posterdir' :
u'',
291 u'bannerdir':
u'',
u'fanartdir':
u'',
u'episodeimagedir':
u'',}
292 key_trans = {
u'filename' :
u'mythvideo',
u'coverfile':
u'posterdir',
293 u'banner' :
u'bannerdir',
u'fanart' :
u'fanartdir',
294 u'screenshot':
u'episodeimagedir'}
296 graphic_suffix = {
u'default' :
u'',
u'mythvideo':
u'',
u'posterdir':
u'_coverart',
297 u'bannerdir':
u'_banner',
u'fanartdir':
u'_fanart',
u'episodeimagedir':
u'_screenshot',}
298 graphic_path_suffix =
u"%s%s%s.%s"
299 graphic_name_suffix =
u"%s%s.%s"
301 storagegroupnames = {
u'Default' :
u'default',
u'Videos' :
u'mythvideo',
302 u'Coverart':
u'posterdir',
u'Banners' :
u'bannerdir',
303 u'Fanart' :
u'fanartdir',
u'Screenshots':
u'episodeimagedir'}
305 image_extensions = [
u"png",
u"jpg",
u"bmp"]
311 symlink_filename_format =
u"%s - %s"
313 download_sleeptime = float(60)
314 channel_icon_override = []
315 channel_watch_only = []
316 channel_mythvideo_only = {}
317 channel_new_watch_copy = {}
320 test_environment =
False
321 requirements_are_met =
True
323 mythcommflag_recordings =
u'%s -c %%s -s "%%s" --rebuild'
324 mythcommflag_videos =
u'%s --rebuild --video "%%s"'
325 filename_char_filter =
u"/%\000"
326 emptyTitle =
u'_NO_TITLE_From_Miro'
327 emptySubTitle =
u'_NO_SUBTITLE_From_Miro'
331 statistics = {
u'WR_watched': 0,
u'Miro_marked_watch_seen': 0,
332 u'Miro_videos_deleted': 0,
u'Miros_videos_downloaded': 0,
333 u'Miros_MythVideos_video_removed': 0,
u'Miros_MythVideos_added': 0,
334 u'Miros_MythVideos_copied': 0,
u'Total_unwatched': 0,
335 u'Total_Miro_expiring': 0,
u'Total_Miro_MythVideos': 0}
338 class OutStreamEncoder(object):
339 """Wraps a stream with an encoder
341 def __init__(self, outstream, encoding=None):
344 self.encoding = sys.getfilesystemencoding()
346 self.encoding = encoding
348 def write(self, obj):
349 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
350 if isinstance(obj, unicode):
351 self.out.write(obj.encode(self.encoding))
355 def __getattr__(self, attr):
356 """Delegate everything but write to the stream"""
357 return getattr(self.out, attr)
359 sys.stdout = OutStreamEncoder(sys.stdout,
'utf8')
360 sys.stderr = OutStreamEncoder(sys.stderr,
'utf8')
363 logger = logging.getLogger(
u"mirobridge")
364 logger.setLevel(logging.DEBUG)
366 ch = logging.StreamHandler()
367 ch.setLevel(logging.DEBUG)
369 formatter = logging.Formatter(
u"%(asctime)s - %(name)s - %(levelname)s - %(message)s")
371 ch.setFormatter(formatter)
373 logger.addHandler(ch)
377 from pyparsing
import *
379 if pyparsing.__version__ <
"1.5.0":
380 logger.critical(
u"The python library 'pyparsing' must be at version 1.5.0 or higher. "\
381 u"Your version is v%s" % pyparsing.__version__)
384 logger.critical(
u"The python library 'pyparsing' must be installed and be version 1.5.0 or "\
385 u"higher, error(%s)" % e)
387 logger.info(
u"Using python library 'pyparsing' version %s" % pyparsing.__version__)
394 from MythTV
import OldRecorded, Recorded, RecordedProgram, Record, Channel, \
395 MythDB, Video, MythVideo, MythBE, MythError, MythLog
396 from MythTV.database
import DBDataWrite
397 from MythTV.utility
import datetime
404 print u'\n! Warning - %s' % e.args[0]
405 filename = os.path.expanduser(
"~")+
'/.mythtv/config.xml'
406 if not os.path.isfile(filename):
407 logger.critical(
u'A correctly configured (%s) file must exist\n' % filename)
409 logger.critical(
u'Check that (%s) is correctly configured\n' % filename)
412 logger.critical(
u"Creating an instance caused an error for one of: MythDB or "\
413 u"MythVideo, error(%s)\n" % e)
415 localhostname = mythdb.gethostname()
417 mythbeconn = MythBE(backend=localhostname, db=mythdb)
419 logger.critical(
u'MiroBridge must be run on a MythTV backend, error(%s)' % e.args[0])
422 logger.critical(
u"MythTV python bindings could not be imported, error(%s)" % e)
430 from miro.plat
import utils
431 utils.initialize_locale()
436 from miro
import config
437 from miro
import eventloop
438 from miro
import gtcache
445 if sys.version_info[0] == 2
and sys.version_info[1] > 5:
446 import miro.feedparser
447 import miro.storedatabase
448 sys.modules[
'feedparser'] = miro.feedparser
449 sys.modules[
'storedatabase'] = miro.storedatabase
451 from miro
import prefs
452 from miro
import startup
454 from miro.frontends.cli.events
import EventHandler
459 dummy = app.config.get(prefs.APP_VERSION)
462 eventloop.setup_config_watcher()
463 from miro
import signals
464 from miro
import messages
465 from miro
import eventloop
466 from miro
import feed
467 from miro
import workerprocess
468 from miro.frontends.cli.application
import InfoUpdaterCallbackList
469 from miro.frontends.cli.application
import InfoUpdater
470 from miro.plat.renderers.gstreamerrenderer
import movie_data_program_info
471 miroConfiguration = app.config.get
472 from miro
import controller
473 app.controller = controller.Controller()
475 miroConfiguration = config.get
479 logger.critical(
u"Importing Miro functions has an issue. Miro must be installed "\
480 u"and functional, error(%s)", e)
483 logger.info(
u"Miro Bridge version %s with Miro version %s" % \
484 (__version__, miroConfiguration(prefs.APP_VERSION)))
485 if miroConfiguration(prefs.APP_VERSION) <
u"2.0.3":
486 logger.critical((
u"Your version of Miro (v%s) is not recent enough. Miro v2.0.3 is "\
487 u"the minimum and it is preferred that you upgrade to Miro v2.5.2 or "\
488 u"later.") % miroConfiguration(prefs.APP_VERSION))
492 if miroConfiguration(prefs.APP_VERSION) <
u"2.5.2":
493 logger.info(
"Using mirobridge_interpreter_2_0_3")
495 elif miroConfiguration(prefs.APP_VERSION) <
u"3.0":
496 logger.info(
"Using mirobridge_interpreter_2_5_2")
498 elif miroConfiguration(prefs.APP_VERSION) <
u"3.5":
499 logger.info(
"Using mirobridge_interpreter_3_0_0")
501 elif miroConfiguration(prefs.APP_VERSION) <
u"4.0":
502 logger.info(
"Using mirobridge_interpreter_3_5_0")
505 logger.info(
"Using mirobridge_interpreter_4_0_2")
509 logger.critical(
u"Importing mirobridge functions has failed. At least a 'mirobridge_interpreter' "\
510 u"file that matches your Miro version must be in the subdirectory 'mirobridge'.\n'"\
511 u"e.g. mirobridge_interpreter_2_0_3.py', 'mirobridge_interpreter_2_5_2.py' ... etc, "\
516 """Takes a string, checks if it is numeric.
519 >>> _can_int("A test")
532 def displayMessage(message):
533 """Displays messages through stdout. Usually used with option (-V) verbose mode.
541 def getExtention(filename):
542 """Get the graphic file extension from a filename
543 return the file extension from the filename
545 (dirName, fileName) = os.path.split(filename)
546 (fileBaseName, fileExtension)=os.path.splitext(fileName)
547 return fileExtension[1:]
551 def sanitiseFileName(name):
552 '''Take a file name and change it so that invalid or problematic characters are substituted with a "_"
553 return a sanitised valid file name
555 global filename_char_filter
556 if name ==
None or name ==
u'':
558 for char
in filename_char_filter:
559 name = name.replace(char,
u'_')
566 def useImageMagick(cmd):
567 """ Process graphics files using ImageMagick's utility 'mogrify'.
568 >>> useImageMagick('convert screenshot.jpg -resize 50% screenshot.png')
572 return subprocess.call(
u'%s > /dev/null' % cmd, shell=
True)
575 class singleinstance(object):
577 singleinstance - based on Windows version by Dragan Jovelic this is a Linux
578 version that accomplishes the same task: make sure that
579 only a single instance of an application is running.
582 def __init__(self, pidPath):
584 pidPath - full path/filename where pid for running application is to be
585 stored. Often this is ./var/<pgmname>.pid
592 if os.path.exists(pidPath):
597 pid=
int(
open(pidPath,
'r').read().strip())
616 if not self.lasterror:
621 fp=
open(pidPath,
'w')
622 fp.write(str(os.getpid()))
625 def alreadyrunning(self):
626 return self.lasterror
629 if not self.lasterror:
631 os.unlink(self.pidPath)
635 '''Check if Miro is running. Only one can be running at the same time.
636 return True if Miro us already running
637 return False if Miro is NOT running
640 p = subprocess.Popen(
u'ps aux | grep "miro.real"', shell=
True, bufsize=4096,
641 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
647 data = p.stdout.readline()
651 data = unicode(data,
'utf8')
652 except (UnicodeEncodeError, TypeError):
654 if data.find(
u'grep') != -1:
657 if data.find(
u'miro.real') != -1:
658 logger.critical(
u"Miro is already running and therefore Miro Bridge should not be run:")
659 logger.critical(
u"(%s)" % data)
668 '''check if char is punctuation char
669 return True if char is punctuation
670 return False if char is not punctuation
672 return char
in string.punctuation
675 '''check if char is not punctuation char
676 return True if char is not punctuation
677 return False if char is punctuation
682 def delOldRecorded(chanid, starttime, endtime, title, \
683 subtitle, description):
685 This routine is not supported in the native python bindings as MiroBridge
686 uses the oldrecorded table outside of its original intent.
689 sql_cmd =
u"""DELETE FROM `mythconverg`.`oldrecorded`
690 WHERE `oldrecorded`.`chanid` = '%s'
691 AND `oldrecorded`.`starttime` = '%s'
692 AND `oldrecorded`.`endtime` = '%s';"""
693 sql_del_a_record(sql_cmd % (chanid, set_del_datatime(starttime),
694 set_del_datatime(endtime)))
698 def delRecorded(chanid, starttime):
699 '''Just delete a recorded record. Never abort as sometimes a record
703 sql_cmd =
u"""DELETE FROM `mythconverg`.`recorded`
704 WHERE `recorded`.`chanid` = %s
705 AND `recorded`.`starttime` = '%s';"""
706 sql_del_a_record(sql_cmd % (chanid, set_del_datatime(starttime)))
710 def set_del_datatime(rec_time):
711 ''' Set the SQL datetime so that the delRecorded and delOldrecorded
712 methods use UTC datetime values.
713 return rec_time datetime string
716 return rec_time.astimezone(
717 rec_time.UTCTZ()).strftime(
"%Y-%m-%d %H:%M:%S")
720 def sql_del_a_record(sql_cmd):
722 cursor = mythdb.cursor()
723 cursor.execute(sql_cmd)
729 def hashFile(filename):
730 '''Create metadata hash values for mythvideo files
732 return u'' if the was an error with the video file or the video file length was zero bytes
735 if filename[0] !=
u'/':
736 hash_value = mythbeconn.getHash(filename,
u'Videos')
737 if hash_value ==
u'NULL':
746 bytesize = struct.calcsize(longlongformat)
747 f =
open(filename,
"rb")
748 filesize = os.path.getsize(filename)
750 if filesize < 65536 * 2:
752 for x
in range(65536/bytesize):
753 buffer = f.read(bytesize)
754 (l_value,)= struct.unpack(longlongformat, buffer)
756 hash = hash & 0xFFFFFFFFFFFFFFFF
757 f.seek(max(0,filesize-65536),0)
758 for x
in range(65536/bytesize):
759 buffer = f.read(bytesize)
760 (l_value,)= struct.unpack(longlongformat, buffer)
762 hash = hash & 0xFFFFFFFFFFFFFFFF
764 returnedhash =
"%016x" % hash
772 def rtnAbsolutePath(relpath, filetype=u'mythvideo'):
773 '''Check if there is a Storage Group for the file type (mythvideo, coverfile, banner, fanart,
774 screenshot) and form an appropriate absolute path and file name.
775 return an absolute path and file name
776 return the relpath sting if the file does not actually exist in the absolute path location
778 if relpath ==
None or relpath ==
u'':
782 if relpath[0] ==
u'/':
785 if not storagegroups.has_key(filetype):
788 for directory
in storagegroups[filetype]:
789 abpath =
u"%s/%s" % (directory, relpath)
790 if os.path.isfile(abpath):
797 def getStorageGroups():
798 '''Populate the storage group dictionary with the host's storage groups.
799 return False if there is an error
801 records = mythdb.getStorageGroup(hostname=localhostname)
803 for record
in records:
804 if record.groupname
in storagegroupnames.keys():
806 dirname = unicode(record.dirname,
'utf8')
807 except (UnicodeDecodeError):
808 logger.error((
u"The local Storage group (%s) directory contained\ncharacters "\
809 u"that caused a UnicodeDecodeError. This storage group has been "\
810 u"rejected.") % (record.groupname))
812 except (UnicodeEncodeError, TypeError):
813 dirname = record.dirname
816 if dirname[-1:] ==
u'/':
817 storagegroups[storagegroupnames[record.groupname]] = dirname
819 storagegroups[storagegroupnames[record.groupname]] = dirname+
u'/'
822 if len(storagegroups):
824 storagegroup_ok =
True
825 for key
in storagegroups.keys():
826 if not os.path.isdir(storagegroups[key]):
827 logger.critical(
u"The Storage group (%s) directory (%s) does not exist" % \
828 (key, storagegroups[key]))
829 storagegroup_ok =
False
830 if not storagegroup_ok:
836 Get all video and graphics directories found in the MythTV DB and add them to the dictionary.
837 Ignore any MythTV Frontend setting when there is already a storage group configured.
840 global localhostname, vid_graphics_dirs, dir_dict, storagegroups, local_only, verbose
844 if storagegroups.has_key(
u'mythvideo'):
847 for key
in storagegroups.keys():
848 vid_graphics_dirs[key] = storagegroups[key]
849 for key
in dir_dict.keys():
850 if key ==
u'default' or key ==
u'mythvideo':
852 if not storagegroups.has_key(key):
854 vid_graphics_dirs[key] = storagegroups[
u'mythvideo']
856 storagegroups[key] = storagegroups[
u'mythvideo']
859 if storagegroups.has_key(
u'default'):
860 vid_graphics_dirs[
u'default'] = storagegroups[
u'default']
863 logger.warning(
u'There is no "Videos" Storage Group set so ONLY MythTV Frontend local '\
864 u'paths for videos and graphics that are accessable from this MythTV '\
865 u'Backend can be used.')
867 for key
in dir_dict.keys():
868 if vid_graphics_dirs[key]:
870 graphics_dir = mythdb.settings[localhostname][dir_dict[key]]
872 if key ==
u'mythvideo':
874 tmp_directories = graphics_dir.split(
u':')
875 if len(tmp_directories):
876 for i
in range(len(tmp_directories)):
877 tmp_directories[i] = tmp_directories[i].strip()
878 if tmp_directories[i] !=
u'':
879 if os.path.exists(tmp_directories[i]):
880 if tmp_directories[i][-1] !=
u'/':
881 tmp_directories[i]+=
u'/'
882 vid_graphics_dirs[key] = tmp_directories[i]
885 logger.error(
u"MythVideo video directory (%s) does not exist(%s)" % \
886 (key, tmp_directories[i]))
888 logger.error(
u"MythVideo video directory (%s) is not set" % (key, ))
890 if key !=
u'mythvideo':
891 if graphics_dir
and os.path.exists(graphics_dir):
892 if graphics_dir[-1] !=
u'/':
894 vid_graphics_dirs[key] = graphics_dir
896 logger.error(
u"(%s) directory is not set or does not exist(%s)" % (key, dir_dict[key]))
900 for key
in vid_graphics_dirs.keys():
901 if not vid_graphics_dirs[key]:
902 logger.critical(
u"There must be a directory for Videos and each graphics type "\
903 u"the (%s) directory is missing." % (key))
910 for key
in vid_graphics_dirs.keys():
911 if not os.access(vid_graphics_dirs[key], os.F_OK | os.R_OK | os.W_OK):
912 logger.critical(
u"\nEvery Video and graphics directory must be read/writable for "\
913 u"Miro Bridge to function. There is a permissions issue with (%s)." % \
914 (vid_graphics_dirs[key], ))
921 dir_types={
'posterdir' :
"Cover art ",
'bannerdir':
'Banners ',
922 'fanartdir' :
'Fan art ',
'episodeimagedir':
'Screenshots',
923 'mythvideo':
'Video ',
'default':
'Default '}
924 sys.stdout.write(
u"""
925 ==========================================================================================
926 Listed below are the types and base directories that will be use for processing.
927 The list reflects your current configuration for the '%s' back end
928 and whether a directory is a 'SG' (storage group) or not.
929 Note: All directories are from settings in the MythDB specific to hostname (%s).
930 ------------------------------------------------------------------------------------------
931 """ % (localhostname, localhostname))
932 for key
in vid_graphics_dirs.keys():
934 if storagegroups.has_key(key):
935 if vid_graphics_dirs[key] == storagegroups[key]:
937 sys.stdout.write(
u"Type: %s - SG-%s - Directory: (%s)\n" % \
938 (dir_types[key], sg_flag, vid_graphics_dirs[key]))
940 u"""------------------------------------------------------------------------------------------
941 If a directory you set from a separate Front end is not displayed it means
942 that the directory is not accessible from this backend OR
943 you must add the missing directories using the Front end on this Back end.
944 Front end settings are host machine specific.
945 ==========================================================================================
951 """ Change variables through a user supplied configuration file
952 abort the script if there are issues with the configuration file values
954 global simulation, verbose, channel_icon_override, channel_watch_only, channel_mythvideo_only
955 global vid_graphics_dirs, tv_channels, movie_trailers, filename_char_filter
957 useroptions=
u"~/.mythtv/mirobridge.conf"
959 if useroptions[0]==
u'~':
960 useroptions=os.path.expanduser(
u"~")+useroptions[1:]
961 if os.path.isfile(useroptions) ==
False:
963 u"There was no mirobridge configuration file found (%s)" % useroptions)
966 cfg = ConfigParser.SafeConfigParser()
967 cfg.read(useroptions)
968 for section
in cfg.sections():
969 if section[:5] ==
u'File ':
971 if section ==
u'variables':
972 for option
in cfg.options(section):
973 if option ==
u'filename_char_filter':
974 for char
in cfg.get(section, option):
975 filename_char_filter+=char
977 if section ==
u'icon_override':
979 for option
in cfg.options(section):
980 channel_icon_override.append(option)
982 if section ==
u'watch_only':
984 for option
in cfg.options(section):
985 if option ==
u'all miro channels':
986 channel_watch_only = [
u'all']
989 channel_watch_only.append(filter(is_not_punct_char, option.lower()))
991 if section ==
u'mythvideo_only':
993 for option
in cfg.options(section):
994 if option ==
u'all miro channels':
995 channel_mythvideo_only[
u'all'] = cfg.get(section, option)
998 channel_mythvideo_only[filter(is_not_punct_char, option.lower())] = \
999 cfg.get(section, option)
1000 for key
in channel_mythvideo_only.keys():
1001 if not channel_mythvideo_only[key].startswith(vid_graphics_dirs[
u'mythvideo']):
1002 logger.critical((
u"All Mythvideo only configuration (%s) directories (%s) must "\
1003 u"be a subdirectory of the MythVideo base directory (%s).") % \
1004 (key, channel_mythvideo_only[key],
1005 vid_graphics_dirs[
u'mythvideo']))
1007 if channel_mythvideo_only[key][-1] !=
u'/':
1008 channel_mythvideo_only[key]+=
u'/'
1010 if section ==
u'watch_then_copy':
1012 for option
in cfg.options(section):
1013 if option ==
u'all miro channels':
1014 channel_new_watch_copy[
u'all'] = cfg.get(section, option)
1017 channel_new_watch_copy[filter(is_not_punct_char, option.lower())] =\
1018 cfg.get(section, option)
1019 for key
in channel_new_watch_copy.keys():
1020 if not channel_new_watch_copy[key].startswith(vid_graphics_dirs[
u'mythvideo']):
1021 logger.critical((
u"All 'new->watch->copy' channel (%s) directory (%s) must "\
1022 u"be a subdirectory of the MythVideo base directory (%s).") % \
1023 (key, channel_new_watch_copy[key],
1024 vid_graphics_dirs[
u'mythvideo']))
1026 if channel_new_watch_copy[key][-1] !=
u'/':
1027 channel_new_watch_copy[key]+=
u'/'
1032 '''Massage the Miro description removing all HTML.
1033 return the massaged description
1037 """Removes HTML or XML character references
1038 and entities from a text string.
1039 @param text The HTML (or XML) source text.
1040 @return The plain text, as a Unicode string, if necessary.
1042 2008-01-03: input only unicode characters string.
1043 http://effbot.org/zone/re-sub.htm#unescape-html
1047 if text[:2] ==
u"&#":
1050 if text[:3] ==
u"&#x":
1051 return unichr(
int(text[3:-1], 16))
1053 return unichr(
int(text[2:-1]))
1055 logger.warn(
u"Remove HTML or XML character references: Value Error")
1061 if text[1:-1] ==
u"amp":
1063 elif text[1:-1] ==
u"gt":
1065 elif text[1:-1] ==
u"lt":
1068 logger.info(
u"%s" % text[1:-1])
1069 text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
1071 logger.warn(
u"Remove HTML or XML character references: keyerror")
1074 return re.sub(
u"&#?\w+;", fixup, text)
1079 details[
u'plot'] = description
1084 director_text =
u'Director: '
1085 director_re = re.compile(director_text, re.UNICODE)
1086 ratings_text =
u'Rating: '
1087 ratings_re = re.compile(ratings_text, re.UNICODE)
1089 removeText = replaceWith(
"")
1090 scriptOpen,scriptClose = makeHTMLTags(
u"script")
1091 scriptBody = scriptOpen + SkipTo(scriptClose) + scriptClose
1092 scriptBody.setParseAction(removeText)
1094 anyTag,anyClose = makeHTMLTags(Word(alphas,alphanums+
u":_"))
1095 anyTag.setParseAction(removeText)
1096 anyClose.setParseAction(removeText)
1097 htmlComment.setParseAction(removeText)
1099 commonHTMLEntity.setParseAction(replaceHTMLEntity)
1102 firstPass = (htmlComment | scriptBody | commonHTMLEntity |
1103 anyTag | anyClose ).transformString(description)
1106 repeatedNewlines = LineEnd() + OneOrMore(LineEnd())
1107 repeatedNewlines.setParseAction(replaceWith(
u"\n\n"))
1108 secondPass = repeatedNewlines.transformString(firstPass)
1109 text = secondPass.replace(
u"Link to Catalog\n ",
u'')
1110 text = unescape(text)
1113 text_lines = text.split(
u'\n')
1114 for index
in range(len(text_lines)):
1115 text_lines[index] = text_lines[index].rstrip()
1118 text_lines = [text.replace(
u'\n',
u' ')]
1122 for text
in text_lines:
1123 if len(director_re.findall(text)):
1124 details[
u'director'] = text.replace(director_text,
u'')
1127 if len(ratings_re.findall(text)):
1128 data = text[text.index(ratings_text):].replace(ratings_text,
u'')
1130 number = data[:data.index(
u'/')]
1133 details[
u'userrating'] = float(number) * 2
1135 details[
u'userrating'] = 0.0
1137 details[
u'userrating'] = 0.0
1138 text = text[:text.index(ratings_text)]
1140 description+=text+
u' '
1142 description = text_lines[0].replace(
u"[...]Rating:",
u"[...] Rating:")
1145 details[
u'plot'] = description.rstrip()
1153 """Retrieves the Miro oldrecorded records for localhostname. Match them against the Miro recorded
1154 records and identify any orphaned oldrecorded records. Those records mean a MythTV user deleted the
1155 Miro video from the Watched Recordings screen or from MythVideo. Delete the orphaned records from
1156 MythTV plus any left over graphic files.
1157 return array of oldrecorded dictionary records which are orphaned
1158 return None if there are no orphaned oldrecorded records
1160 global simulation, verbose, channel_id, localhostname, vid_graphics_dirs, statistics
1161 global channel_icon_override
1162 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
1166 metadata.convertOldMiroVideos()
1168 recorded_array = list(mythdb.searchRecorded(chanid=channel_id, hostname=localhostname))
1169 oldrecorded_array = list(mythdb.searchOldRecorded(chanid=channel_id, ))
1170 videometadata = list(mythdb.searchVideos(category=
u'Miro'))
1173 for record
in oldrecorded_array:
1174 for recorded
in recorded_array:
1175 if recorded[
u'starttime'] == record[
u'starttime']
and recorded[
u'endtime'] == \
1179 for video
in videometadata:
1180 if video[
u'title'] == record[
u'title']
and video[
u'subtitle'] == record[
u'subtitle']:
1183 orphans.append(record)
1185 for data
in orphans:
1187 logger.info(
u"Simulation: Remove orphaned oldrecorded record (%s - %s)" % \
1188 (data[
u'title'], data[
u'subtitle']))
1193 delOldRecorded(channel_id, data[
'starttime'],
1194 data[
'endtime'], data[
'title'],
1195 data[
'subtitle'], data[
'description'])
1200 metadata.cleanupVideoAndGraphics(
u'%s%s_%s.%s' % \
1201 (vid_graphics_dirs[
u'default'], channel_id,
1202 data[
u'starttime'].strftime(
'%Y%m%d%H%M%S'),
u'png'))
1205 metadata.cleanupVideoAndGraphics(
u'%s%s - %s.%s' % \
1206 (vid_graphics_dirs[
u'default'], data[
u'title'],
1207 data[
u'subtitle'],
u'png'))
1210 metadata.cleanupVideoAndGraphics(
u'%s%s - %s%s.%s' % \
1211 (vid_graphics_dirs[
u'episodeimagedir'], data[
u'title'],
1212 data[
u'subtitle'], graphic_suffix[
u'episodeimagedir'],
u'png'))
1215 if data[
u'title'].lower()
in channel_icon_override:
1216 metadata.cleanupVideoAndGraphics(
u'%s%s - %s%s.%s' % \
1217 (vid_graphics_dirs[
u'posterdir'], data[
u'title'],
1218 data[
u'subtitle'], graphic_suffix[
u'posterdir'],
u'png'))
1220 displayMessage(
u"Removed orphaned Miro video and graphics files (%s - %s)" % \
1221 (data[
u'title'], data[
u'subtitle']))
1228 '''Calculate a videos start and end times and isotime for the recorded file name.
1229 return an array of initialised values if either duration or downloadedTime is invalid
1230 return an array of the video's start, end times and isotime
1232 starttime = datetime.now()
1234 start_end = [starttime.strftime(
'%Y-%m-%d %H:%M:%S'),
1235 starttime.strftime(
'%Y-%m-%d %H:%M:%S'),
1236 starttime.strftime(
'%Y%m%d%H%M%S')]
1238 if downloadedTime !=
None:
1240 dummy = downloadedTime.strftime(
'%Y-%m-%d')
1242 downloadedTime = datetime.now()
1243 end = downloadedTime+timedelta(seconds=duration)
1244 start_end[0] = downloadedTime.strftime(
'%Y-%m-%d %H:%M:%S')
1245 start_end[1] = end.strftime(
'%Y-%m-%d %H:%M:%S')
1246 start_end[2] = downloadedTime.strftime(
'%Y%m%d%H%M%S')
1251 if not len(list(mythdb.searchOldRecorded(chanid=channel_id, starttime=start_end[0]))):
1253 starttime = starttime + timedelta(0,1)
1254 end = end + timedelta(0,1)
1255 start_end[0] = starttime.strftime(
'%Y-%m-%d %H:%M:%S')
1256 start_end[1] = end.strftime(
'%Y-%m-%d %H:%M:%S')
1257 start_end[2] = starttime.strftime(
'%Y%m%d%H%M%S')
1263 def setSymbolic(filename, storagegroupkey, symbolic_name, allow_symlink=False):
1264 '''Convert the file into a symbolic name according to it's storage group (there may be
1265 no storage group for the key). Check if a symbolic link exists and replace the link with a copy of
1266 the file. except for video files. Abort if the file does not exist.
1267 return the symbolic link to the file
1269 global simulation, verbose, storagegroups, vid_graphics_dirs
1270 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
1273 if not os.path.isfile(filename):
1274 logger.error(
u"The file (%s) must exist to create a symbolic link" % filename)
1277 ext = getExtention(filename)
1278 if ext.lower() ==
u'm4v':
1282 if ext.lower()
in [
u'gif',
u'jpeg',
u'JPG', ]:
1286 if storagegroupkey
in storagegroups.keys()
and storagegroupkey ==
u'default':
1287 sym_filepath = graphic_path_suffix % (storagegroups[storagegroupkey], symbolic_name,
1288 graphic_suffix[storagegroupkey], ext)
1289 sym_filename = graphic_name_suffix % (symbolic_name, graphic_suffix[storagegroupkey], ext)
1290 elif storagegroupkey
in storagegroups.keys()
and not local_only:
1291 sym_filepath = graphic_path_suffix % (storagegroups[storagegroupkey], symbolic_name,
1292 graphic_suffix[storagegroupkey], ext)
1293 sym_filename = graphic_name_suffix % (symbolic_name, graphic_suffix[storagegroupkey], ext)
1295 sym_filepath = graphic_path_suffix % (vid_graphics_dirs[storagegroupkey], symbolic_name,
1296 graphic_suffix[storagegroupkey], ext)
1297 sym_filename = sym_filepath
1300 if os.path.isfile(os.path.realpath(sym_filepath)):
1303 if os.path.isfile(os.path.realpath(sym_filepath))
and not os.path.islink(sym_filepath):
1306 os.remove(sym_filepath)
1312 logger.info(
u"Simulation: Used file (%s) to create symlink as (%s)" % \
1313 (filename, sym_filepath))
1315 logger.info(
u"Simulation: Used file (%s) copy as (%s)" % (filename, sym_filepath))
1320 os.symlink(filename, sym_filepath)
1321 displayMessage(
u"Used file (%s) to created symlink (%s)" % (filename, sym_filepath))
1325 useImageMagick(
u'convert "%s" "%s"' % (filename, sym_filepath))
1326 displayMessage(
u"Convert and copy Miro file (%s) to file (%s)" % \
1327 (filename, sym_filepath))
1329 shutil.copy2(filename, sym_filepath)
1330 displayMessage(
u"Copied Miro file (%s) to file (%s)" % (filename, sym_filepath))
1332 logger.critical((
u"Trying to copy the Miro file (%s) to the file (%s).\nError(%s)"\
1333 u"\nThis maybe a permissions error (mirobridge.py does not have "\
1334 u"permission to write to the directory).") % \
1335 (filename ,sym_filepath, e))
1338 logger.critical((
u"Trying to create the Miro file (%s) symlink (%s).\nError(%s)\nThis "\
1339 u"maybe a permissions error (mirobridge.py does not have permission to "\
1340 u"write to the directory).") % (sym_filepath, e))
1348 '''Using the details from a Miro item record create a MythTV oldrecorded record
1349 return an array of MythTV of a full initialised oldrecorded record dictionaries
1351 global localhostname, simulation, verbose, storagegroups, ffmpeg, channel_id
1356 tmp_oldrecorded[
u'chanid'] = channel_id
1357 tmp_oldrecorded[
u'starttime'] = start
1358 tmp_oldrecorded[
u'endtime'] = end
1359 tmp_oldrecorded[
u'title'] = item[
u'channelTitle']
1360 tmp_oldrecorded[
u'subtitle'] = item[
u'title']
1361 tmp_oldrecorded[
u'category'] =
u'Miro'
1362 tmp_oldrecorded[
u'station'] =
u'MIRO'
1363 tmp_oldrecorded[
u'inetref'] = inetref
1364 tmp_oldrecorded[
u'season'] = item[
u'season']
1365 tmp_oldrecorded[
u'episode'] = item[
u'episode']
1370 tmp_oldrecorded[
u'description'] = item[
u'description']
1371 return tmp_oldrecorded
1376 '''Using the details from a Miro item record create a MythTV recorded record
1377 return an array of MythTV full initialised recorded and recordedprogram record dictionaries
1379 global localhostname, simulation, verbose, storagegroups, ffmpeg, channel_id
1382 tmp_recordedprogram={}
1385 graphics = metadata.getMetadata(sanitiseFileName(item[
u'channelTitle']))
1387 ffmpeg_details = metadata.getVideoDetails(item[
u'videoFilename'])
1388 start_end =
getStartEndTimes(ffmpeg_details[
u'duration'], item[
u'downloadedTime'])
1390 if item[
u'releasedate'] ==
None:
1391 item[
u'releasedate'] = item[
u'downloadedTime']
1393 dummy = item[
u'releasedate'].strftime(
'%Y-%m-%d')
1395 item[
u'releasedate'] = item[
u'downloadedTime']
1398 tmp_recorded[
u'chanid'] = channel_id
1399 tmp_recorded[
u'starttime'] = start_end[0]
1400 tmp_recorded[
u'endtime'] = start_end[1]
1401 tmp_recorded[
u'title'] = item[
u'channelTitle']
1402 tmp_recorded[
u'subtitle'] = item[
u'title']
1403 tmp_recorded[
u'season'] = item[
u'season']
1404 tmp_recorded[
u'episode'] = item[
u'episode']
1405 tmp_recorded[
u'inetref'] = graphics[
u'inetref']
1410 print u"Channel title(%s) subtitle(%s)" % (item[
u'channelTitle'], item[
u'title'])
1411 print u"The 'massageDescription()' function could not remove HTML and XML tags from:"
1412 print u"Description (%s)\n\n" % item[
u'description']
1413 tmp_recorded[
u'description'] = item[
u'description']
1414 tmp_recorded[
u'category'] =
u'Miro'
1415 tmp_recorded[
u'hostname'] = localhostname
1416 tmp_recorded[
u'lastmodified'] = tmp_recorded[
u'endtime']
1417 tmp_recorded[
u'filesize'] = item[
u'size']
1418 if item[
u'releasedate'] !=
None:
1419 tmp_recorded[
u'originalairdate'] = item[
u'releasedate'].strftime(
'%Y-%m-%d')
1421 basename =
setSymbolic(item[
u'videoFilename'],
u'default',
u"%s_%s" % \
1422 (channel_id, start_end[2]), allow_symlink=
True)
1423 if basename !=
None:
1424 tmp_recorded[
u'basename'] = basename
1426 logger.critical(
u"The file (%s) must exist to create a recorded record" % \
1427 item[
u'videoFilename'])
1430 tmp_recorded[
u'progstart'] = start_end[0]
1431 tmp_recorded[
u'progend'] = start_end[1]
1434 tmp_recordedprogram[
u'chanid'] = channel_id
1435 tmp_recordedprogram[
u'starttime'] = start_end[0]
1436 tmp_recordedprogram[
u'endtime'] = start_end[1]
1437 tmp_recordedprogram[
u'title'] = item[
u'channelTitle']
1438 tmp_recordedprogram[
u'subtitle'] = item[
u'title']
1442 tmp_recordedprogram[
u'description'] = item[
u'description']
1444 tmp_recordedprogram[
u'category'] =
u"Miro"
1445 tmp_recordedprogram[
u'category_type'] =
u"series"
1446 if item[
u'releasedate'] !=
None:
1447 tmp_recordedprogram[
u'airdate'] = item[
u'releasedate'].strftime(
'%Y')
1448 tmp_recordedprogram[
u'originalairdate'] = item[
u'releasedate'].strftime(
'%Y-%m-%d')
1449 tmp_recordedprogram[
u'stereo'] = ffmpeg_details[
u'stereo']
1450 tmp_recordedprogram[
u'hdtv'] = ffmpeg_details[
u'hdtv']
1451 tmp_recordedprogram[
u'audioprop'] = ffmpeg_details[
u'audio']
1452 tmp_recordedprogram[
u'videoprop'] = ffmpeg_details[
u'video']
1454 return [tmp_recorded, tmp_recordedprogram,
1460 '''Using the details from a Miro item create a MythTV videometadata record
1461 return an dictionary of MythTV an initialised videometadata record
1463 global localhostname, simulation, verbose, storagegroups, ffmpeg, channel_id
1464 global vid_graphics_dirs, channel_icon_override, flat, image_extensions
1467 ffmpeg_details = metadata.getVideoDetails(item[
u'videoFilename'])
1468 start_end =
getStartEndTimes(ffmpeg_details[
u'duration'], item[
u'downloadedTime'])
1472 sympath+=
u"/%s" % item[
u'channelTitle']
1475 graphics = metadata.getMetadata(sanitiseFileName(item[
u'channelTitle']))
1479 videometadata[
u'title'] = item[
u'channelTitle']
1480 videometadata[
u'subtitle'] = item[
u'title']
1481 videometadata[
u'inetref'] = graphics[
u'inetref']
1482 videometadata[
u'season'] = item[
u'season']
1483 videometadata[
u'episode'] = item[
u'episode']
1489 print u"MythVideo-Channel title(%s) subtitle(%s)" % \
1490 (item[
u'channelTitle'], item[
u'title'])
1491 print u"The 'massageDescription()' function could not remove HTML and XML tags from:"
1492 print u"Description (%s)\n\n" % item[
u'description']
1493 details = {
u'plot': item[
u'description']}
1495 for key
in details.keys():
1496 videometadata[key] = details[key]
1498 if item[
u'releasedate'] ==
None:
1499 item[
u'releasedate'] = item[
u'downloadedTime']
1501 dummy = item[
u'releasedate'].strftime(
'%Y-%m-%d')
1503 item[
u'releasedate'] = item[
u'downloadedTime']
1505 if item[
u'releasedate'] !=
None:
1506 videometadata[
u'year'] = item[
u'releasedate'].strftime(
'%Y')
1507 videometadata[
u'releasedate'] = item[
u'releasedate'].strftime(
'%Y-%m-%d')
1508 videometadata[
u'length'] = ffmpeg_details[
u'duration']/60
1509 if item.has_key(
u'copied'):
1510 videometadata[
u'category'] =
u'Video Cast'
1512 videometadata[
u'category'] =
u'Miro'
1514 if not item.has_key(
u'copied'):
1515 videofile =
setSymbolic(item[
u'videoFilename'],
u'mythvideo',
"%s/%s - %s" % \
1516 (sympath, sanitiseFileName(item[
u'channelTitle']),
1517 sanitiseFileName(item[
u'title'])), allow_symlink=
True)
1518 if videofile !=
None:
1519 videometadata[
u'filename'] = videofile
1520 if not local_only
and videometadata[
u'filename'][0] !=
u'/':
1521 videometadata[
u'host'] = localhostname.lower()
1523 logger.critical(
u"The file (%s) must exist to create a videometadata record" % \
1524 item[
u'videoFilename'])
1527 videometadata[
u'filename'] = item[
u'videoFilename']
1528 if not local_only
and videometadata[
u'filename'][0] !=
u'/':
1529 videometadata[
u'host'] = localhostname.lower()
1531 videometadata[
u'hash'] = hashFile(videometadata[
u'filename'])
1533 if not item.has_key(
u'copied'):
1534 if graphics[
'coverart']:
1535 videometadata[
u'coverfile'] = graphics[
'coverart']
1536 elif item[
u'channel_icon']
and not item[
u'channelTitle'].lower()
in channel_icon_override:
1537 filename =
setSymbolic(item[
u'channel_icon'],
u'posterdir',
u"%s" % \
1538 (sanitiseFileName(item[
u'channelTitle'])))
1539 if filename !=
None:
1540 videometadata[
u'coverfile'] = filename
1542 if item[
u'item_icon']:
1543 filename =
setSymbolic(item[
u'item_icon'],
u'posterdir',
u"%s - %s" % \
1544 (sanitiseFileName(item[
u'channelTitle']),
1545 sanitiseFileName(item[
u'title'])))
1546 if filename !=
None:
1547 videometadata[
u'coverfile'] = filename
1549 videometadata[
u'coverfile'] = item[
u'channel_icon']
1551 if not item.has_key(
u'copied'):
1552 if item[
u'screenshot']:
1553 filename =
setSymbolic(item[
u'screenshot'],
u'episodeimagedir',
u"%s - %s" % \
1554 (sanitiseFileName(item[
u'channelTitle']),
1555 sanitiseFileName(item[
u'title'])))
1556 if filename !=
None:
1557 videometadata[
u'screenshot'] = filename
1559 if item[
u'screenshot']:
1560 videometadata[
u'screenshot'] = item[
u'screenshot']
1562 if graphics[
'banner']:
1563 videometadata[
u'banner'] = graphics[
'banner']
1564 if graphics[
'fanart']:
1565 videometadata[
u'fanart'] = graphics[
'fanart']
1568 graphics[
u'inetref'])]
1574 Create the optional Miro "channel" record as it makes the Watch Recordings screen look better.
1575 return True if the record was created and written successfully
1576 abort if the processing failed
1578 global localhostname, simulation, verbose, vid_graphics_dirs
1581 if not os.path.isfile(icon):
1582 logger.critical((
u'The Miro channel icon file (%s) does not exist.\nThe variable '\
1583 u'needs to be a fully qualified file name and path.\ne.g. '\
1584 u'./mirobridge.py -C "/path to the channel icon/miro_channel_icon.'\
1589 data[
'chanid'] = channel_id
1590 data[
'channum'] = str(channel_num)
1591 data[
'freqid'] = str(channel_num)
1592 data[
'atsc_major_chan'] =
int(channel_num)
1596 data[
'callsign'] =
u'Miro'
1597 data[
'name'] =
u'Miro'
1598 data[
'last_record'] = datetime.now().strftime(
'%Y-%m-%d %H:%M:%S')
1601 logger.info(
u"Simulation: Create Miro channel record channel_id(%d) and channel_num(%d)" % \
1602 (channel_id, channel_num))
1603 logger.info(
u"Simulation: Channel icon file(%s)" % (icon))
1607 except MythError, e:
1608 logger.critical((
u"Failed writing the Miro channel record. Most likely the Channel "\
1609 u"Id and number already exists.\nUse MythTV set up program "\
1610 u"(mythtv-setup) to alter or remove the offending channel.\nYou "\
1611 u"specified Channel ID (%d) and Channel Number (%d), error(%s)") % \
1612 (channel_id, channel_num, e.args[0]))
1620 Verify that the real path exists for both video and graphics for this MythVideo Miro record.
1621 return False if there were no failures
1622 return True if a failure was found
1624 global localhostname, verbose, vid_graphics_dirs, key_trans
1626 for field
in key_trans.keys():
1627 if not record[field]:
1629 if record[field] ==
u'No Cover':
1631 if not record[
u'host']
or record[field][0] ==
u'/':
1632 if not os.path.isfile(record[field]):
1635 if not os.path.isfile(vid_graphics_dirs[key_trans[field]]+record[field]):
1642 '''If the "Miro" directory does not exist in MythVideo then create it.
1643 abort if there is an issue creating the directory
1645 global localhostname, vid_graphics_dirs, storagegroups, channel_id, flat, simulation, verbose
1650 miro_path = vid_graphics_dirs[
u'mythvideo']+miro
1651 if not os.path.isdir(miro_path):
1654 logger.info(
u"Simulation: Create Miro Mythvideo directory (%s)" % (miro_path,))
1659 logger.critical((
u"Create Miro Mythvideo directory (%s).\nError(%s)\n" \
1660 u"This may be due to a permissions error.") % \
1664 logger.critical((
u"Creation of MythVideo 'Miro' directory (%s) failed.\nError(%s)"\
1665 u"\nThis may be due to a permissions error.") % (miro_path, e))
1670 '''Create the Miro Channel subdirectory in MythVideo
1671 abort if the subdirectory cannot be made
1673 global localhostname, vid_graphics_dirs, storagegroups, channel_id, flat, simulation, verbose
1676 path =
u"%s%s/%s" % (vid_graphics_dirs[
u'mythvideo'], miro,
1677 sanitiseFileName(item[
u'channelTitle']))
1680 logger.info(
u"Simulation: Make subdirectory(%s)" % (path))
1682 if not os.path.isdir(path):
1686 logger.critical((
u"Creation of MythVideo 'Miro' subdirectory path (%s) failed."\
1687 u"\nError(%s)\nThis may be due to a permissions error.") % \
1694 '''From the MythTV database recorded records identify all "played" Miro Video files.
1695 return None if there were either no Miro recorded records or none that were in "watched" status
1696 return an array of subtitles of those Miro video files that were "watched"
1698 global localhostname, vid_graphics_dirs, storagegroups, verbose, channel_id, statistics
1701 recorded = list(mythdb.searchRecorded(chanid=channel_id, hostname=localhostname))
1702 for record
in recorded:
1703 if record[
u'watched'] == 0:
1706 filenames.append(os.path.realpath(storagegroups[
u'default']+record[
u'basename']))
1707 statistics[
u'WR_watched']+=1
1709 logger.info(
u"Miro video file has been removed (%s) outside of mirobridge\nError(%s)" % \
1710 (storagegroups[
u'default']+record[
u'basename'], e))
1712 displayMessage(
u"Miro video (%s) (%s) has been marked as watched in MythTV." % \
1713 (record[
u'title'], record[
u'subtitle']))
1722 Add and delete MythTV (Watch Recordings) Miro recorded records. Add and delete symbolic links
1723 to coverart/Miro icons.
1724 Abort if processing failed
1725 return True if processing was successful
1727 global localhostname, vid_graphics_dirs, storagegroups, channel_id, simulation, imagemagick
1728 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
1732 items_copy = list(items)
1735 recorded = list(mythdb.searchRecorded(chanid=channel_id, hostname=localhostname))
1736 for record
in recorded:
1737 if storagegroups.has_key(
u'default'):
1738 sym_filepath =
u"%s%s" % (storagegroups[
u'default'], record[
u'basename'])
1740 sym_filepath =
u"%s%s" % (vid_graphics_dirs[
u'default'], record[
u'basename'])
1744 if item[
u'channelTitle'] == record[
u'title']
and item[
u'title'] == record[
u'subtitle']:
1750 if record[
u'watched'] == 1
or not os.path.isfile(os.path.realpath(sym_filepath)):
1753 displayMessage(
u"Removing watched Miro recording (%s) (%s)" % \
1754 (record[
u'title'], record[
u'subtitle']))
1757 logger.info(
u"Simulation: Remove recorded/recordedprogram/oldrecorded records and "\
1758 u"associated Miro Video file for chanid(%s), starttime(%s)" % \
1759 (record[
'chanid'], record[
'starttime']))
1764 rtn = delRecorded(record[
'chanid'], record[
'starttime'])
1765 except MythError, e:
1769 metadata.cleanupVideoAndGraphics(
u'%s%s_%s.%s' % \
1770 (vid_graphics_dirs[
u'default'], record[
'chanid'],
1771 record[
u'starttime'].strftime(
'%Y%m%d%H%M%S'),
u'png'))
1774 rtn = delOldRecorded(record[
'chanid'], record[
'starttime'],
1775 record[
'endtime'], record[
'title'],
1776 record[
'subtitle'], record[
'description'])
1777 except Exception, e:
1780 recorded = list(mythdb.searchRecorded(chanid=channel_id, hostname=localhostname))
1781 for record
in recorded:
1783 if item[
u'channelTitle'] == record[
u'title']
and item[
u'title'] == record[
u'subtitle']:
1784 items_copy.remove(item)
1788 for item
in items_copy:
1790 if item[
u'videoFilename'] ==
None:
1793 if not os.path.isfile(os.path.realpath(item[
u'videoFilename'])):
1795 if not os.path.isfile(os.path.realpath(item[
u'videoFilename'])):
1800 logger.info(
u"Simulation: Added recorded and recordedprogram records for "\
1801 u"(%s - %s)" % (item[
u'channelTitle'], item[
u'title'],))
1804 Recorded().create(records[0])
1805 RecordedProgram().create(records[1])
1806 OldRecorded().create(records[2])
1807 except MythError, e:
1808 logger.warning(
u"Inserting recorded/recordedprogram/oldrecorded records "\
1809 u"non-critical error (%s) for (%s - %s)" % \
1810 (e.args[0], item[
u'channelTitle'], item[
u'title']))
1812 logger.critical(
u"Creation of recorded/recordedprogram/oldrecorded record data for "\
1813 u"(%s - %s)" % (item[
u'channelTitle'], item[
u'title'],))
1816 if item[
u'channel_icon']:
1817 ext = getExtention(item[
u'channel_icon'])
1818 coverart_filename =
u"%s%s%s.%s" % (vid_graphics_dirs[
u'posterdir'],
1819 sanitiseFileName(item[
u'channelTitle']),
1820 graphic_suffix[
u'posterdir'], ext)
1821 if not os.path.isfile(os.path.realpath(coverart_filename)):
1823 logger.info(
u"Simulation: Remove symbolic link(%s)" % (coverart_filename,))
1826 os.remove(coverart_filename)
1830 logger.info(
u"Simulation: Create icon file(%s) cover art file(%s)" % \
1831 (item[
u'channel_icon'], coverart_filename))
1834 shutil.copy2(item[
u'channel_icon'], coverart_filename)
1835 displayMessage((
u"Copied a Miro Channel Icon file (%s) to MythTV as "\
1836 u"file (%s).") % (item[
u'channel_icon'], coverart_filename))
1838 logger.critical((
u"Copying an icon file(%s) to coverart file(%s) failed."\
1839 u"\nError(%s)\nThis may be due to a permissions error.") % \
1840 (item[
u'channel_icon'], coverart_filename, e))
1843 if item[
u'screenshot']
and imagemagick:
1844 screenshot_recorded =
u"%s%s.png" % (vid_graphics_dirs[
u'default'], records[0][
u'basename'])
1845 if not os.path.isfile(screenshot_recorded):
1847 logger.info(
u"Simulation: Create screenshot file(%s) as(%s)" % \
1848 (item[
u'screenshot'], screenshot_recorded))
1853 demensions = takeScreenShot(item[
u'videoFilename'],
1854 screenshot_recorded,
1856 just_demensions=
False)
1860 demensions =
u"-size %s" % demensions
1861 useImageMagick(
u'convert "%s" %s "%s"' % \
1862 (item[
u'screenshot'], demensions,
1863 screenshot_recorded))
1864 displayMessage((
u"Used a Miro Channel screenshot file (%s) to\ncreate "\
1865 u"using ImageMagick the MythTV Watch Recordings screen "\
1866 u"shot file\n(%s).") % \
1867 (item[
u'screenshot'], screenshot_recorded))
1869 logger.critical((
u"Creating screenshot file(%s) as(%s) failed.\nError"\
1870 u"(%s)\nThis may be due to a permissions error.") % \
1871 (item[
u'screenshot'], screenshot_recorded, e))
1874 screenshot_recorded =
u"%s%s.png" % \
1875 (vid_graphics_dirs[
u'default'], records[0][
u'basename'])
1877 takeScreenShot(item[
u'videoFilename'], screenshot_recorded, size_limit=
True)
1885 '''Add and delete MythVideo records for played Miro Videos. Add and delete symbolic links
1886 to Miro Videos, to coverart/Miro icons, banners and Miro screenshots and fanart.
1887 NOTE: banner and fanart graphics were provided with the script and are used only if present.
1888 Abort if processing failed
1889 return True if processing was successful
1891 global localhostname, vid_graphics_dirs, storagegroups, channel_id, flat, simulation, verbose
1892 global channel_watch_only, statistics
1893 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
1903 records = list(mythdb.searchVideos(category=
u'Miro'))
1904 statistics[
u'Total_Miro_MythVideos'] = len(records)
1905 for record
in records:
1906 if record[
u'filename'][0] ==
u'/':
1907 if os.path.islink(record[
u'filename'])
and os.path.isfile(record[
u'filename']):
1908 statistics[
u'Total_Miro_expiring']+=1
1909 elif record[
u'host']
and storagegroups.has_key(
u'mythvideo'):
1910 if os.path.islink(storagegroups[
u'mythvideo']+record[
u'filename'])
and \
1911 os.path.isfile(storagegroups[
u'mythvideo']+record[
u'filename']):
1912 statistics[
u'Total_Miro_expiring']+=1
1913 for record
in records:
1916 if os.path.islink(record[
u'filename']):
1917 if not record[
u'host']
or record[
u'filename'][0] ==
'/':
1918 if not os.path.isfile(record[
u'filename']):
1921 if not os.path.isfile(vid_graphics_dirs[key_trans[field]]+record[
u'filename']):
1924 if not os.path.isfile(record[
u'filename']):
1928 logger.info(
u"Simulation: DELETE videometadata for intid = %s" % \
1929 (record[
u'intid'],))
1930 logger.info(
u"Simulation: DELETE oldrecorded for title(%s), subtitle(%s)" % \
1931 (record[
u'title'], record[
u'subtitle']))
1933 rtn =
Video(record[
u'intid'], db=mythdb).delete()
1935 for oldrecorded
in mythdb.searchOldRecorded(title=record[
u'title'],
1936 subtitle=record[
u'subtitle'] ):
1937 rtn = delOldRecorded(channel_id,
1938 oldrecorded[
'starttime'],
1939 oldrecorded[
'endtime'],
1940 oldrecorded[
'title'],
1941 oldrecorded[
'subtitle'],
1942 oldrecorded[
'description'])
1943 except Exception, e:
1945 statistics[
u'Total_Miro_MythVideos']-=1
1947 metadata.deleteFile(record[
u'filename'], record[
u'host'],
u'mythvideo')
1948 if record[
u'screenshot']:
1949 metadata.deleteFile(record[
u'screenshot'], record[
u'host'],
u'episodeimagedir')
1951 if record[
u'title'].lower()
in channel_icon_override:
1952 metadata.deleteFile(record[
u'coverfile'], record[
u'host'],
u'posterdir')
1958 items_copy = list(items)
1959 records = list(mythdb.searchVideos(category=
u'Miro'))
1960 for record
in records:
1962 if item[
u'channelTitle'] == record[
u'title']
and item[
u'title'] == record[
u'subtitle']:
1964 items_copy.remove(item)
1966 logger.info((
u"Video (%s - %s) was found multiple times in list of (watched "\
1967 u"and/or saved) items from Miro - skipping") % \
1968 (item[
u'channelTitle'], item[
u'title']))
1973 if filter(is_not_punct_char, item[
u'channelTitle'].lower())
in channel_watch_only:
1975 items_copy.remove(item)
1980 for item
in items_copy:
1981 if not flat
and not item.has_key(
u'copied'):
1983 if not item[
u'screenshot']:
1984 screenshot_mythvideo =
u"%s%s - %s%s.jpg" % \
1985 (vid_graphics_dirs[
u'episodeimagedir'],
1986 sanitiseFileName(item[
u'channelTitle']),
1987 sanitiseFileName(item[
u'title']),
1988 graphic_suffix[
u'episodeimagedir'])
1990 result = takeScreenShot(item[
u'videoFilename'], screenshot_mythvideo, size_limit=
False)
1994 item[
u'screenshot'] = screenshot_mythvideo
1996 videometadata = tmp_array[0]
1997 oldrecorded = tmp_array[1]
1999 logger.info(
u"Simulation: Create videometadata record for (%s - %s)" % \
2000 (item[
u'channelTitle'], item[
u'title']))
2002 if not local_only
and videometadata[
u'filename'][0] !=
u'/':
2003 intid = list(mythdb.searchVideos(exactfile=videometadata[
u'filename'],
2004 host=localhostname.lower()))
2006 intid = list(mythdb.searchVideos(exactfile=videometadata[
u'filename']))
2010 intid =
Video(db=mythdb).create(videometadata).intid
2011 except MythError, e:
2012 logger.critical(
u"Adding Miro video to MythVideo (%s - %s) failed for (%s)." % \
2013 (item[
u'channelTitle'], item[
u'title'], e.args[0]))
2015 if not item.has_key(
u'copied'):
2017 OldRecorded().create(oldrecorded)
2018 except MythError, e:
2019 logger.critical(
u"Failed writing the oldrecorded record for(%s)" % (e.args[0]))
2021 if videometadata[
u'filename'][0] ==
u'/':
2022 cmd = mythcommflag_videos % videometadata[
u'filename']
2023 elif videometadata[
u'host']
and storagegroups[
u'mythvideo']:
2024 cmd = mythcommflag_videos % \
2025 ((storagegroups[
u'mythvideo']+videometadata[
u'filename']))
2026 statistics[
u'Miros_MythVideos_added']+=1
2027 statistics[
u'Total_Miro_expiring']+=1
2028 statistics[
u'Total_Miro_MythVideos']+=1
2029 displayMessage(
u"Added Miro video to MythVideo (%s - %s)" % \
2030 (videometadata[
u'title'], videometadata[
u'subtitle']))
2032 sys.stdout.write(
u'')
2034 u"""Skipped adding a duplicate Miro video to MythVideo:
2036 Sometimes a Miro channel has the same video downloaded multiple times.
2037 This is a Miro/Channel web site issue and often rectifies itself overtime.
2038 """ % (videometadata[
u'title'], videometadata[
u'subtitle']))
2047 sys.stdout.write(
u"""
2049 -------------------Statistics--------------------
2050 Number of Watch Recording's watched...... (% 5d)
2051 Number of Miro videos marked as seen..... (% 5d)
2052 Number of Miro videos deleted............ (% 5d)
2053 Number of New Miro videos downloaded..... (% 5d)
2054 Number of Miro/MythVideo's removed....... (% 5d)
2055 Number of Miro/MythVideo's added......... (% 5d)
2056 Number of Miro videos copies to MythVideo (% 5d)
2057 -------------------------------------------------
2058 Total Unwatched Miro/Watch Recordings.... (% 5d)
2059 Total Miro/MythVideo videos to expire.... (% 5d)
2060 Total Miro/MythVideo videos.............. (% 5d)
2061 -------------------------------------------------
2063 """ % (statistics[
u'WR_watched'], statistics[
u'Miro_marked_watch_seen'],
2064 statistics[
u'Miro_videos_deleted'], statistics[
u'Miros_videos_downloaded'],
2065 statistics[
u'Miros_MythVideos_video_removed'], statistics[
u'Miros_MythVideos_added'],
2066 statistics[
u'Miros_MythVideos_copied'], statistics[
u'Total_unwatched'],
2067 statistics[
u'Total_Miro_expiring'], statistics[
u'Total_Miro_MythVideos'], ))
2073 """Support mirobridge from the command line
2076 global localhostname, simulation, verbose, storagegroups, ffmpeg, channel_id, channel_num
2077 global flat, download_sleeptime, channel_watch_only, channel_mythvideo_only, channel_new_watch_copy
2078 global vid_graphics_dirs, imagemagick, statistics, requirements_are_met
2079 global graphic_suffix, graphic_path_suffix, graphic_name_suffix
2080 global mythcommflag_recordings, mythcommflag_videos
2081 global local_only, metadata
2082 global parser, opts, args
2085 sys.stdout.write(examples_txt+
'\n')
2089 sys.stdout.write(
u"\nTitle: (%s); Version: description(%s); Author: (%s)\n%s\n" % (
2090 __title__, __version__, __author__, __purpose__ ))
2094 test_environment =
True
2096 test_environment =
False
2104 if opts.new_watch_copy: x+=1
2105 if opts.watch_only: x+=1
2106 if opts.mythvideo_only: x+=1
2107 if opts.import_opml: x+=1
2109 logger.critical(
u"The (-W), (-M), (-N) and (-i) options are mutually exclusive, "\
2110 u"so only one can be specified at a time.")
2114 simulation = opts.simulation
2115 verbose = opts.verbose
2117 localhostname = opts.hostname
2122 base_video_dir = miroConfiguration(prefs.MOVIES_DIRECTORY)
2123 miro_version_rev =
u"%s r%s" % (miroConfiguration(prefs.APP_VERSION),
2124 miroConfiguration(prefs.APP_REVISION_NUM))
2126 displayMessage(
u"Miro Version (%s)" % (miro_version_rev))
2127 displayMessage(
u"Base Miro Video Directory (%s)" % (base_video_dir,))
2131 if not os.path.isdir(base_video_dir):
2132 logger.critical(
u"The Miro Videos directory (%s) does not exist." % str(base_video_dir))
2133 if test_environment:
2134 requirements_are_met =
False
2138 if miroConfiguration(prefs.APP_VERSION) <
u"2.0.3":
2139 logger.critical((
u"The installed version of Miro (%s) is too old. It must be at least "\
2140 u"v2.0.3 or higher.") % miroConfiguration(prefs.APP_VERSION))
2141 if test_environment:
2142 requirements_are_met =
False
2147 if miroConfiguration(prefs.APP_VERSION) ==
u"4.0.1":
2148 logger.critical((
u"The installed version of Miro (%s) must be upgraded to Miro version "\
2149 u"4.0.2 or higher.") % miroConfiguration(prefs.APP_VERSION))
2150 if test_environment:
2151 requirements_are_met =
False
2156 if opts.import_opml:
2157 if miroConfiguration(prefs.APP_VERSION) <
u"2.5.2":
2158 logger.critical(
u"The OPML import option requires Miro v2.5.2 or higher your Miro "\
2159 u"(%s) is too old." % miroConfiguration(prefs.APP_VERSION))
2160 if test_environment:
2161 requirements_are_met =
False
2164 if not os.path.isfile(opts.import_opml):
2165 logger.critical(
u"The OPML import file (%s) does not exist" % opts.import_opml)
2166 if test_environment:
2167 requirements_are_met =
False
2170 if len(opts.import_opml) > 5:
2171 if not opts.import_opml[:-4] !=
'.opml':
2172 logger.critical(
u"The OPML import file (%s) must had a file extention of '.opml'" % \
2174 if test_environment:
2175 requirements_are_met =
False
2179 logger.critical(
u"The OPML import file (%s) must had a file extention of '.opml'" % \
2181 if test_environment:
2182 requirements_are_met =
False
2187 if getStorageGroups() ==
False:
2188 logger.critical(
u"Retrieving storage groups from the MythTV data base failed")
2189 if test_environment:
2190 requirements_are_met =
False
2193 elif not u'default' in storagegroups.keys():
2194 logger.critical(
u"There must be a 'Default' storage group")
2195 if test_environment:
2196 requirements_are_met =
False
2201 channel = opts.channel.split(
u':')
2202 if len(channel) != 2:
2203 logger.critical((
u"The Channel (%s) must be in the format xxx:yyy with x and "\
2204 u"y all numeric.") % str(opts.channel))
2205 if test_environment:
2206 requirements_are_met =
False
2209 elif not _can_int(channel[0])
or not _can_int(channel[1]):
2210 logger.critical(
u"The Channel_id (%s) and Channel_num (%s) must be numeric." % \
2211 (channel[0], channel[1]))
2212 if test_environment:
2213 requirements_are_met =
False
2217 channel_id =
int(channel[0])
2218 channel_num =
int(channel[1])
2221 if not _can_int(opts.sleeptime):
2222 logger.critical(
u"Auto-download sleep time (%s) must be numeric." % str(opts.sleeptime))
2223 if test_environment:
2224 requirements_are_met =
False
2228 download_sleeptime = float(opts.sleeptime)
2240 channel_watch_only = [
u'all']
2242 if opts.mythvideo_only:
2243 channel_mythvideo_only = {
u'all': vid_graphics_dirs[
u'mythvideo']+
u'Miro/'}
2246 if opts.new_watch_copy:
2247 channel_new_watch_copy = {
u'all': vid_graphics_dirs[
u'mythvideo']+
u'Miro/'}
2250 if len(channel_mythvideo_only)
and len(channel_new_watch_copy):
2251 for key
in channel_mythvideo_only.keys():
2252 if key
in channel_new_watch_copy.keys():
2253 logger.critical((
u'The Miro Channel (%s) cannot be used as both a "Mythvideo '\
2254 u'Only" and "New-Watch-Copy" channel.') % key)
2255 if test_environment:
2256 requirements_are_met =
False
2261 ret = useImageMagick(
u"convert -version")
2262 if ret < 0
or ret > 1:
2263 logger.critical(
u"ImageMagick must be installed, graphics cannot be resized or "\
2264 u"converted to the required graphics format (e.g. jpg and or png)")
2265 if test_environment:
2266 requirements_are_met =
False
2271 if mythdb.settings.NULL.DeletesFollowLinks ==
'1':
2272 logger.critical(
u'The MythTV back end setting "Follow symbolic links when deleting '\
2273 u'files" is checked and it is incompatible with MiroBridge processing. '\
2274 u'It must be unchecked it to use MiroBridge.\nTo uncheck this setting '\
2275 u'start "mythtv-setup" or with Mythbuntu start "MythTV Backend Setup" '\
2276 u'and then General->Miscellaneous Settings and uncheck the "Follow '\
2277 u'symbolic links when deleting files" setting')
2278 if test_environment:
2279 requirements_are_met =
False
2284 metadata = MetaData(mythdb,
2297 metadata.getVideoDetails(
u"")
2298 if ffmpeg
and requirements_are_met:
2299 logger.info(
u"The environment test passed !\n\n")
2302 logger.critical(
u"The environment test FAILED. See previously displayed error messages!")
2305 if opts.addchannel !=
u'OFF':
2307 logger.info(
u"The Miro Channel record has been successfully created !\n\n")
2318 displayMessage(
u"Starting Miro Frontend and Backend")
2319 startup.initialize(miroConfiguration(prefs.THEME_NAME))
2320 if miroConfiguration(prefs.APP_VERSION) >
u"4.0":
2321 app.info_updater = InfoUpdater()
2322 app.cli_events = EventHandler()
2323 app.cli_events.connect_to_signals()
2325 if miroConfiguration(prefs.APP_VERSION) >
u"4.0":
2326 startup.install_first_time_handler(app.cli_events.handle_first_time)
2329 app.cli_events.startup_event.wait()
2330 if app.cli_events.startup_failure:
2331 logger.critical(
u"Starting Miro Frontend and Backend failed: (%s)\n(%s)" % \
2332 (app.cli_events.startup_failure[0], app.cli_events.startup_failure[1]))
2333 app.controller.shutdown()
2337 if miroConfiguration(prefs.APP_VERSION) >
u"4.0":
2338 app.movie_data_program_info = movie_data_program_info
2339 messages.FrontendStarted().send_to_backend()
2341 app.cli_interpreter = MiroInterpreter()
2343 app.cli_interpreter.verbose =
True
2345 app.cli_interpreter.verbose =
False
2346 app.cli_interpreter.simulation = opts.simulation
2347 app.cli_interpreter.videofiles = []
2348 app.cli_interpreter.downloading =
False
2349 app.cli_interpreter.icon_cache_dir = miroConfiguration(prefs.ICON_CACHE_DIRECTORY)
2350 app.cli_interpreter.imagemagick = imagemagick
2351 app.cli_interpreter.statistics = statistics
2352 if miroConfiguration(prefs.APP_VERSION) <
u"2.5.0":
2353 app.renderer = app.cli_interpreter
2354 elif miroConfiguration(prefs.APP_VERSION) >
u"4.0":
2357 app.movie_data_program_info = app.cli_interpreter.movie_data_program_info
2362 if opts.import_opml:
2365 app.cli_interpreter.do_mythtv_import_opml(opts.import_opml)
2367 except Exception, e:
2368 logger.critical(
u"Import of OPML file (%s) failed, error(%s)." % (opts.import_opml, e))
2371 app.controller.shutdown()
2379 if not opts.no_autodownload:
2381 app.cli_interpreter.verbose =
False
2382 app.cli_interpreter.do_mythtv_getunwatched(
u'')
2383 before_download = len(app.cli_interpreter.videofiles)
2385 app.cli_interpreter.verbose =
True
2386 if miroConfiguration(prefs.APP_VERSION) <
u"4.0":
2388 app.cli_interpreter.do_mythtv_update_autodownload(
u'')
2389 time.sleep(download_sleeptime)
2392 app.cli_interpreter.do_mythtv_check_downloading(
u'')
2393 if app.cli_interpreter.downloading:
2398 time.sleep(download_sleeptime)
2404 app.cli_interpreter.verbose =
False
2405 app.cli_interpreter.do_mythtv_getunwatched(
u'')
2406 after_download = len(app.cli_interpreter.videofiles)
2407 statistics[
u'Miros_videos_downloaded'] = after_download - before_download
2409 app.cli_interpreter.verbose =
True
2416 if len(videostodelete):
2417 displayMessage(
u"Starting Miro delete of videos deleted in the MythTV "\
2418 u"Watched Recordings screen.")
2419 for video
in videostodelete:
2421 app.cli_interpreter.do_mythtv_item_remove([video[
u'title'], video[
u'subtitle']])
2431 if app.cli_interpreter.videofiles:
2432 displayMessage(
u"Starting Miro update of watched MythTV videos")
2433 app.cli_interpreter.do_mythtv_updatewatched(
u'')
2438 app.cli_interpreter.do_mythtv_getunwatched(
u'')
2439 unwatched = app.cli_interpreter.videofiles
2444 app.cli_interpreter.do_mythtv_getwatched(
u'')
2445 watched = app.cli_interpreter.videofiles
2450 for item
in unwatched:
2452 if not item[
u'channelTitle']:
2453 item[
u'channelTitle'] = emptyTitle
2454 if not item[
u'title']:
2455 item[
u'title'] = emptySubTitle
2456 for item
in watched:
2458 if not item[
u'channelTitle']:
2459 item[
u'channelTitle'] = emptyTitle
2460 if not item[
u'title']:
2461 item[
u'title'] = emptySubTitle
2469 for item
in unwatched:
2470 unwatched_copy.append(item)
2471 for item
in unwatched_copy:
2473 if item[
u'channelTitle'] == x[
u'channelTitle']
and item[
u'title'] == x[
u'title']:
2475 unwatched.remove(item)
2477 app.cli_interpreter.do_mythtv_item_remove(item[
u'videoFilename'])
2478 displayMessage((
u"Skipped adding a duplicate Miro video to the MythTV "\
2479 u"Watch Recordings screen (%s - %s) which is already in "\
2480 u"MythVideo.\nSometimes a Miro channel has the same video "\
2481 u"downloaded multiple times.\nThis is a Miro/Channel web "\
2482 u"site issue and often rectifies itself overtime.") % \
2483 (item[
u'channelTitle'], item[
u'title']))
2487 for item
in unwatched_copy:
2490 if item[
u'channelTitle'] == x[
u'channelTitle']
and item[
u'title'] == x[
u'title']:
2493 for x
in duplicates:
2494 if item[
u'channelTitle'] == x[
u'channelTitle']
and item[
u'title'] == x[
u'title']:
2497 duplicates.append(item)
2499 for duplicate
in duplicates:
2501 unwatched.remove(duplicate)
2503 app.cli_interpreter.do_mythtv_item_remove(duplicate[
u'videoFilename'])
2504 displayMessage((
u"Skipped adding a Miro video to the MythTV Watch Recordings "\
2505 u"screen (%s - %s) as there are duplicate 'new' video items."\
2506 u"\nSometimes a Miro channel has the same video downloaded "\
2507 u"multiple times.\nThis is a Miro/Channel web site issue and "\
2508 u"often rectifies itself overtime.") % \
2509 (duplicate[
u'channelTitle'], duplicate[
u'title']))
2518 if u'all' in channel_mythvideo_only.keys():
2519 for array
in [watched, unwatched]:
2521 copy_items.append(item)
2522 elif len(channel_mythvideo_only):
2523 for array
in [watched, unwatched]:
2525 if filter(is_not_punct_char, video[
u'channelTitle'].lower())
in \
2526 channel_mythvideo_only.keys():
2527 copy_items.append(video)
2529 if u'all' in channel_new_watch_copy.keys():
2530 for video
in watched:
2531 copy_items.append(video)
2532 elif len(channel_new_watch_copy):
2533 for video
in watched:
2534 if filter(is_not_punct_char, video[
u'channelTitle'].lower())
in \
2535 channel_new_watch_copy.keys():
2536 copy_items.append(video)
2538 channels_to_copy = {}
2539 for key
in channel_mythvideo_only.keys():
2540 channels_to_copy[key] = channel_mythvideo_only[key]
2541 for key
in channel_new_watch_copy.keys():
2542 channels_to_copy[key] = channel_new_watch_copy[key]
2544 for video
in copy_items:
2545 if channels_to_copy.has_key(
'all'):
2546 copy_dir =
u"%s%s/" % (channels_to_copy[
'all'], sanitiseFileName(video[
u'channelTitle']))
2548 copy_dir = channels_to_copy[filter(is_not_punct_char, video[
u'channelTitle'].lower())]
2551 if not os.path.isdir(copy_dir):
2553 logger.info(
u"Simulation: Creating the MythVideo directory (%s)." % (copy_dir))
2555 os.makedirs(copy_dir)
2559 save_video_filename = video[
u'videoFilename']
2560 ext = getExtention(video[
u'videoFilename'])
2561 if ext.lower() ==
u'm4v':
2563 filepath =
u"%s%s - %s.%s" % (copy_dir, sanitiseFileName(video[
u'channelTitle']),
2564 sanitiseFileName(video[
u'title']), ext)
2566 logger.info(
u"Simulation: Copying the Miro video (%s) to the MythVideo directory (%s)." % \
2567 (video[
u'videoFilename'], filepath))
2570 shutil.copy2(video[
u'videoFilename'], filepath)
2571 statistics[
u'Miros_MythVideos_copied']+=1
2572 if u'mythvideo' in storagegroups.keys()
and not local_only:
2573 video[
u'videoFilename'] = filepath.replace(storagegroups[
u'mythvideo'],
u'')
2575 video[
u'videoFilename'] = filepath
2576 except Exception, e:
2577 logger.critical((
u"Copying the Miro video (%s) to the MythVideo directory (%s)."\
2578 u"\n This maybe a permissions error (mirobridge.py does "\
2579 u"not have permission to write to the directory), error(%s)") % \
2580 (video[
u'videoFilename'], filepath, e))
2582 app.controller.shutdown()
2587 if video[
u'channel_icon']
and not video[
u'channelTitle'].lower()
in channel_icon_override:
2590 if video[
u'item_icon']:
2591 video[
u'channel_icon'] = video[
u'item_icon']
2593 graphics = metadata.getMetadata(sanitiseFileName(video[
u'channelTitle']))
2594 if video[
u'channel_icon']
and graphics[
'coverart'] ==
u'':
2595 ext = getExtention(video[
u'channel_icon'])
2596 if video[
u'channelTitle'].lower()
in channel_icon_override:
2597 filepath =
u"%s%s - %s%s.%s" % (vid_graphics_dirs[
u'posterdir'],
2598 sanitiseFileName(video[
u'channelTitle']),
2599 sanitiseFileName(video[
u'title']),
2600 graphic_suffix[
u'posterdir'], ext)
2602 filepath =
u"%s%s%s.%s" % (vid_graphics_dirs[
u'posterdir'],
2603 sanitiseFileName(video[
u'channelTitle']),
2604 graphic_suffix[
u'posterdir'], ext)
2607 if not os.path.isfile(filepath)
or os.path.islink(filepath):
2609 logger.info((
u"Simulation: Copying the Channel Icon (%s) to the poster "\
2610 u"directory (%s).") % (video[
u'channel_icon'], filepath))
2617 shutil.copy2(video[
u'channel_icon'], filepath)
2618 if u'posterdir' in storagegroups.keys()
and not local_only:
2619 video[
u'channel_icon'] = filepath.replace(storagegroups[
u'posterdir'],
u'')
2621 video[
u'channel_icon'] = filepath
2622 except Exception, e:
2623 logger.critical((
u"Copying the Channel Icon (%s) to the poster directory "\
2624 u"(%s).\n This maybe a permissions error "\
2625 u"(mirobridge.py does not have permission to write to the "\
2626 u"directory), error(%s)") % \
2627 (video[
u'channel_icon'], filepath, e))
2629 app.controller.shutdown()
2633 if u'posterdir' in storagegroups.keys()
and not local_only:
2634 video[
u'channel_icon'] = filepath.replace(storagegroups[
u'posterdir'],
u'')
2636 video[
u'channel_icon'] = filepath
2638 video[
u'channel_icon'] = graphics[
'coverart']
2641 if video[
u'screenshot']:
2642 ext = getExtention(video[
u'screenshot'])
2643 filepath =
u"%s%s - %s%s.%s" % (vid_graphics_dirs[
u'episodeimagedir'],
2644 sanitiseFileName(video[
u'channelTitle']),
2645 sanitiseFileName(video[
u'title']),
2646 graphic_suffix[
u'episodeimagedir'], ext)
2650 if not os.path.isfile(filepath)
or os.path.islink(filepath):
2651 if video[
u'screenshot']:
2653 logger.info((
u"Simulation: Copying the Screenshot (%s) to the Screenshot "\
2654 u"directory (%s).") % (video[
u'screenshot'], filepath))
2661 shutil.copy2(video[
u'screenshot'], filepath)
2662 displayMessage(
u"Copied Miro screenshot file (%s) to MythVideo (%s)" % \
2663 (video[
u'screenshot'], filepath))
2664 if u'episodeimagedir' in storagegroups.keys()
and not local_only:
2665 video[
u'screenshot'] = filepath.replace(storagegroups[
u'episodeimagedir'],
2668 video[
u'screenshot'] = filepath
2669 except Exception, e:
2670 logger.critical((
u"Copying the Screenshot (%s) to the Screenshot directory "\
2671 u"(%s).\n This maybe a permissions error "\
2672 u"(mirobridge.py does not have permission to write to the "\
2673 u"directory), error(%s)") % \
2674 (video[
u'screenshot'], filepath, e))
2676 app.controller.shutdown()
2679 elif video[
u'screenshot']:
2680 if u'episodeimagedir' in storagegroups.keys()
and not local_only:
2681 video[
u'screenshot'] = filepath.replace(storagegroups[
u'episodeimagedir'],
u'')
2683 video[
u'screenshot'] = filepath
2684 video[
u'copied'] =
True
2687 app.cli_interpreter.do_mythtv_item_remove(save_video_filename)
2691 app.controller.shutdown()
2700 if channel_mythvideo_only.has_key(
u'all'):
2701 for video
in unwatched:
2702 watched.append(video)
2705 if len(channel_mythvideo_only):
2707 for video
in unwatched:
2708 if not filter(is_not_punct_char, video[
u'channelTitle'].lower())
in \
2709 channel_mythvideo_only.keys():
2710 unwatched_copy.append(video)
2712 watched.append(video)
2713 unwatched = unwatched_copy
2715 statistics[
u'Total_unwatched'] = len(unwatched)
2716 if not len(unwatched):
2717 displayMessage(
u"There are no Miro unwatched video items to add as MythTV Recorded videos.")
2719 logger.critical(
u"Updating MythTV Recording with Miro video files failed." % \
2720 str(base_video_dir))
2728 if len(channel_watch_only):
2729 if channel_watch_only[0].lower() ==
u'all':
2733 if not len(watched):
2734 displayMessage(
u"There are no Miro watched items to add to MythVideo")
2736 logger.critical(
u"Updating MythVideo with Miro video files failed.")
2743 if __name__ ==
"__main__":
2744 myapp = singleinstance(
u'/tmp/mirobridge.pid')
2748 if myapp.alreadyrunning():
2749 print u'\nMiro Bridge is already running only one instance can run at a time\n\n'
2753 displayMessage(
u"Miro Bridge Processing completed")