MythTV 0.25-pre
jamu.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # -*- coding: UTF-8 -*-
00003 # ----------------------
00004 # Name: jamu.py      Just.Another.Metadata.Utility
00005 # Python Script
00006 # Author:     R.D. Vaughan
00007 # Purpose:     This python script is intended to perform a variety of utility functions on mythvideo
00008 #           metadata and the associated video files.
00009 #
00010 #           The primary movie source for graphics and data is themoviedb.com wiki.
00011 #           The primary TV Series source for graphics and data is thetvdb.com wiki.
00012 #           Users of this script are encouraged to populate both themoviedb.com and thetvdb.com
00013 #           with posters, fan art and banners and meta data. The richer the source the more valuable
00014 #           the script.
00015 #           This script uses the python module tvdb_api.py (v0.6DEV or higher) found at
00016 #           http://pypi.python.org/pypi?%3Aaction=search&term=tvnamer&submit=search thanks
00017 #           to the authors of this excellent module.
00018 #           The tvdb_api.py module uses the full access XML api published by thetvdb.com see:
00019 #           http://thetvdb.com/wiki/index.php?title=Programmers_API
00020 #           This python script's functionality is enhanced if you have installed "tvnamer.py" created by
00021 #           "dbr/Ben" who is also the author of the "tvdb_api.py" module.
00022 #           "tvnamer.py" is used to rename avi files with series/episode information found at
00023 #           thetvdb.com
00024 #           Python access to the tmdb api started with a module from dbr/Ben and then enhanced for
00025 #           Jamu's needs.
00026 #           The routines to select video files was copied and modified from tvnamer.py mentioned above.
00027 #           The routine "_save_video_metadata_to_mythdb" has been taken and modified from
00028 #           "find_meta.py" author Pekka Jääskeläinen.
00029 #           The routine "_addCastGenre" was taken and modified from "tvdb-bulk-update.py" by
00030 #           author David Shilvock <davels@telus.net>.
00031 #
00032 # Command line examples:
00033 # See help (-u and -h) options
00034 #
00035 # License:Creative Commons GNU GPL v2
00036 # (http://creativecommons.org/licenses/GPL/2.0/)
00037 #-------------------------------------
00038 __title__ ="JAMU - Just.Another.Metadata.Utility";
00039 __author__="R.D.Vaughan"
00040 __purpose__='''
00041 This python script is intended to perform a variety of utility functions on mythvideo metadata
00042 and the associated video files.
00043 
00044 The primary movie source for graphics and data is themoviedb.com wiki.
00045 The primary TV Series source for graphics and data is thetvdb.com wiki.
00046 Users of this script are encouraged to populate both themoviedb.com and thetvdb.com with posters,
00047 fan art and banners and meta data. The richer the source the more valuable the script.
00048 '''
00049 
00050 __version__=u"v0.8.0"
00051  # 0.1.0 Initial development
00052  # 0.2.0 Inital beta release
00053  # 0.3.0 Add mythvideo metadata updating including movie graphics through
00054  #       the use of tmdb.pl when the perl script exists
00055  # 0.3.1 Add mythvideo meta data add and update functionality. Intend use for
00056  #       maintenance cron jobs.
00057  #       Increase integration with mythtvideo download meta data and MythUI
00058  #       Added the ability to movie video files while maintaining the metadata
00059  # 0.3.2 Fixed bug where some poster downloads were unnecessary
00060  #       Fixed bug where the mythtv database was updated for no reason
00061  #       Fixed bug in jamu-example.conf "min_poster_size" variable had '=' not ':'
00062  #       Fixed bug where a unicode URL would abort the script
00063  #       Using ffmpeg added setting accurate video length in minutes. A hack but
00064  #       lacked python method to find audio/video properties.
00065  # 0.3.3 Add logic to skip any video with a inetref of '99999999'. Meta data and
00066  #       graphics are all manually entered and should not be altered by Jamu.
00067  #       Currently used for any meta data that you do not want modified by Jamu.
00068  #       Fixed issues with filenames containing Unicode characters.
00069  # 0.3.4 Added logic to skip any secondary source meta data plot less than 10 words.
00070  #       Properly initialized a new record so warning messages do not display.
00071  #       In plot meta data replace line-feeds with a space (e.g. Space Cowboys
00072  #       plot contains line-feeds). Mythvideo does not expect line-feeds in a plot.
00073  #       Significant improvements in combining meta data between primary and
00074  #       secondary data sources.
00075  #       Remove 'tmdb.pl' calls and use the tmdb api directly.
00076  #       Added detection of broken symbolic links and fixed those links.
00077  #       Fixed inconsistencies in graphics file extentions (as received from the
00078  #       sources), made all extentions lowercase and changed ".jpeg" to ".jpg".
00079  # 0.3.5 Fixed bug when themoviedb.com times out from an api request.
00080  #       A few documentation corrections.
00081  #       Fixed a bug with utf8 directory names.
00082  #       Added code to not abort script when themoviedb.com has problems. The issue
00083  #       is reported but the scripts continues processing.
00084  #       Added option "-W" to download graphics for Scheduled and Recorded videos.
00085  #       Change the "-J" Janitor function to avoid deleting graphics for Scheduled
00086  #       and Recorded videos.
00087  #       Fixed bug where a TMDB Poster image was not found when it was really
00088  #       available.
00089  # 0.3.6 Fixed bug when searching themoviedb.com for a movie by title or
00090  #       alternate title.
00091  #       Increased accuracy of non-interactive TMDB movie searching and matching.
00092  #       Set up for transition to TMDB's beta v2.1 api which adds language support.
00093  #       Corrected Watched Recording graphic file naming convention for movies.
00094  #       If interactive mode is selected but an exact match is found for a movie
00095  #       then the exact match is chosen and no interative session is initiated.
00096  #       Added additional messages when access to MythTV python bindings has issues.
00097  # 0.3.7 Removed some redundant code.
00098  #       Sync up with v1.0 of tvdb_api and new way to assign tvdb api key
00099  #       Added an option (-MG) to allow Jamu best guessing at a video's inetref
00100  #       number. To guess accurately the video file name must be very close to
00101  #       those found on tmdb or imdb and tvdb web sites.
00102  #       Remove all use of the MythVideo.py "pruneMetadata" routine as it deletes
00103  #       records from the Mythvideo table for all video files with relative file
00104  #       paths.
00105  #       Jamu will skip processing any videometadata which is using a Storage group.
00106  #       Jamu will now restrict itself to updating only videometadata records whose
00107  #       video files reside on the current host machine. In the case where a user
00108  #       has multiple backends jamu must run on each of those backends.
00109  #       The Janitor option (-MJ) now checks if the users has set the plugins
00110  #       MythGallery, MythGame and MythMusic to use the same graphics directories as
00111  #       MythVideo. If they share directories the Janitor option will exit
00112  #       without removing any graphics files. Messages indicating which directories
00113  #       are in conflict will be displayed.
00114  #       Added the detection of video or graphics on an NFS mount exiting jamu without
00115  #       any processing and displaying a message why this has been done. A new option
00116  #       for NFS (-MN) will allow a user to override this check and jamu will continue
00117  #       processing.
00118  #       Fixed a bug when TMDB does not have a 'year' for a movie (e.g. 'Bambi')
00119  #       Added compatibility with or without the MythTV.py Ticket #6678
00120  #       Fixed a bug when ffmpeg cannot find the true length in minutes of a video
00121  #       Cleaned up documenation consistency with Warning and Error messages.
00122  #       Added to the existing TV episode video file renaming (-MF) option.
00123  #       Now movie video files can also be renamed to the format "title (year)"
00124  #       e.g. "The Duchess (2008)". If tmdb.com has no year for the movie then only
00125  #       the movie title will be used when renaming. Any existing metadata is
00126  #       preserved.
00127  # 0.3.8 Made changes to sync up with MythTV trunk change set [r21138].
00128  #       Now handles TVDB's change from a 5 digit inetref number to 6 digits.
00129  # 0.3.9 Check accessability (Read and Write) to directories and files before
00130  #       including them in files/directories to process.
00131  #       Add the ability to process Storage Groups for all Videos and graphics.
00132  #       Jamu now uses MythVideo.py binding's Genre and Cast routines
00133  #       Fixed a unicode bug with file paths.
00134  #       Fixed a unicode bug with some URLs containing UTF8 characters.
00135  #       Fixed a bug were a bad image file could avbort the script.
00136  #       Changed all subdirectory cover art to a copied graphic file "folder.jpg/png"
00137  #       to conform to the Storage Group standard. This also works for local subdirs.
00138  #       Fixed a bug where a TV series with out a season specific poster or
00139  #       banner would get repeatedly download.
00140  # 0.4.0 Removed a few lines of debugging code which should never have been left in a
00141  #       distrubuted version.
00142  #       Fixed the check that confirms that all Video and graphic directories are
00143  #       read and writable.
00144  #       Fixed a bug where under rare circumstances a graphic would be repeatedly
00145  #       downloaded.
00146  #       Made the installation of the python IMDbPy library manditory.
00147  #       For all movies IMDB numbers will be used instead of converting to TMDB
00148  #       numbers. This is done to maintain consistency with MythVideo movie inetref
00149  #       numbers.
00150  # 0.4.1 Fixed an obscure video file rename (-F option) error
00151  # 0.4.2 Fixed a bug where bad data for either TMDB or TVDB would abort script
00152  # 0.4.3 Recent changes in the MythVideo UI graphic hunts (cover art and fanart)
00153  #       have made Jamu's creation of "folder.xxx" graphics redundant. This
00154  #       feature has been turned off in Jamu. There is a new user option
00155  #       "folderart" that can reactivate this feature through the Jamu
00156  #       configuration file.
00157  # 0.4.4 Changes to assist SG image hunting Jamu now adds the suffix "_coverart,
00158  #       fanart, _banner, _screenshot" respectively to downloaded graphics.
00159  #       With the use of a graphic suffix the requirement for unique graphics
00160  #       directories is gone. The check has been removed.
00161  # 0.4.5 Fixed a bug where lowercase tv video filenames caused graphics files to
00162  #       also be lowercase which can cause graphics to be downloaded twice.
00163  #       Fixed a bug in graphics file name creation for a TV season.
00164  #       Added checks for compatible python library versions of xml and MySQLdb
00165  # 0.4.6 Fixed a bug where a bad IMDB number in TMDB caused an abort.
00166  # 0.4.7 Fixed a bug where a 'recordedprogram' record is not properly paired with a
00167  #       'recorded' record. This results in no "airdate" information being available
00168  #       and a script abort. An airdate year of u'0000' will be assumed.
00169  #       Fix an abort bug when IMDB is having service problems and a list of
00170  #       movies cannot be retrieved.
00171  # 0.4.8 Fixed a bug in a -MJ option check that removing graphics would not
00172  #       conflict with graphic directories for non-Mythvideo plugins.
00173  # 0.4.9 Combine the video file extentions found in the "videotypes" table with those
00174  #       in Jamu to avoid possible issues in the (-MJ) option and to have tighter
00175  #       integration with MythVideo user file extention settings.
00176  # 0.5.0 Fixed a bug where a filename containing invalid characters caused an abort.
00177  #       Such invalid filenames are now skipped with an appropriate message.
00178  #       Added to the -MW option the fetching of graphics from TVDB and TMDB for
00179  #       videos added by Miro Bridge to either Watched Recordings or MythVideo.
00180  #       If Miro Bridge is not being used no additional processing is performed.
00181  #       Two new sections ([mb_tv] and [mb_movies]) were added to the Jamu
00182  #       configuration file to accomodate this new functionality.
00183  #       The jamu configuration file now has a default name and location of
00184  #       "~/.mythtv/jamu.conf". This can be overridden with the command line option.
00185  #       This has been done so Jamu can better support Mythbuntu.
00186  #       Removed code that was required until ticket #6678 was committed with
00187  #       change set [21191]
00188  #       Filtered out checks for video run length on iso, img ... etc potentially
00189  #       large video files due to processing overhead especially on NFS mounts.
00190  #       With the -MW option skip any recordings who's recgroup is "Deleted"
00191  #       Fixed an abort where a TVDB TV series exists for a language but does not
00192  #       have a series name in other languages.
00193  # 0.5.1 Fixed an abort when a user specifies secondary source input parameters
00194  #       that cannot be parsed from the file name. This
00195  #       covers secondary sources for metadata and graphics.
00196  #       Fixed an abort when thetvdb.com cannot be contact due to network or
00197  #       site issues.
00198  #       Added detection of erroneous graphics file downloads that are actually HTML
00199  #       due to source Web site issues. Jamu's (-MJ) janitor option also detects,
00200  #       deletes these files and repairs the MythVideo record if necessary.
00201  #       For the -MW option any downloaded graphics names will use the title of the
00202  #       recorded program instead of that found on sources like TVDB and TMDB. This
00203  #       resolves Watch Recordings image hunt issues when Schedule Direct uses a
00204  #       different program title then is on either TVDB or TMDB.
00205  #       Fixed an obscure bug where TVDB returns empty graphics URLs along with
00206  #       proper URLs. The empty URLs are now ignored.
00207  #       Fixed a bug when a language was specified and there were no graphics
00208  #       for the specified language none were returned/downloaded. This even when
00209  #       graphics for other languages were available. Now if there are no selected
00210  #       language graphics English graphics are the fall back and if there are no
00211  #       English graphics then any available graphics will be returned.
00212  # 0.5.2 Fixed an abort when trying to add a storage group graphics without a
00213  #       proper file path.
00214  # 0.5.3 Fixed a bug where the filemarkup table is not cleaned up if Jamu renames
00215  #       a Miro movie trailer video file that the user wants to keep in MythVideo.
00216  #       Fixed a bug with Miro video file renaming of Miro Movie trailers
00217  #       for the same movie but which had different file extentions.
00218  # 0.5.4 Conform to changeset 22104 setting of SG graphics directories to default to SG Videos if not configured.
00219  # 0.5.5 Deal with TV Series and Movie titles with a "/" forward slash in their name e.g. "Face/Off"
00220  # 0.5.6 Correct an issue when a user has a mixture of local and SG video records in MythVideo. Jamu was
00221  #       adding a hostname when the video had an absolute path. This caused issues with playback.
00222  #       Added more informative error messages when TMDB is returning bad xml responses.
00223  #       Fixed an error in the graphic file naming convention when graphics share the same download directory.
00224  # 0.5.7 Remove the override of the TVDB graphics URL to the mirror site. See Kobe's comment:
00225  #       http://forums.thetvdb.com/viewtopic.php?f=4&t=2161#p9089
00226  # 0.5.8 The issue fixed in v0.5.5 with invalid file name creation did not fully cover TV shows It does now.
00227  # 0.5.9 Changed permissions checks on video directories to only require RW for the destination directories
00228  #       involved in the move. With this change if a user requested a file rename (-F) option and the video
00229  #       file does not have RW access the rename will be ignored.
00230  #       Uses that have their Video directories set to access and read-only can now use Jamu.
00231  #       Added a stdout display of the directories that Jamu will use for processing. This information may help
00232  #       users resolve issues. The display happens ONLY when the -V (verbose) option is used.
00233  # 0.6.0 Changed The Janitor -J option to deal with graphics associated with a VIDEO_TS directory.
00234  #       Stopped Jamu from processing any files in a "VIDEO_TS" directory as it was leading to multiple
00235  #       MythVideo entires for *.VOB files. Jamu does not process multi-part videos.
00236  #       Added the use of PID files to prevent two instances of the same Jamu -M options running at the same
00237  #       time. This deals with issues when a meta data source is off line for an extended
00238  #       period of time and Jamu runs as a cronjob. Options effected are (-M, -MW and -MG).
00239  #       Change to have jamu use the TMDB Movie title as is done in MythVideo rather than the file name.
00240  #       Fixed a bug when TMDB genres are filtered and none remain they were still being added. This bug was
00241  #       spotted and correct by Mathieu Brabant (thanks).
00242  #       Added the ability for a user to filter additional characters from a file name this is important for
00243  #       users using MS-Windows file systems as a CIFS mount.
00244  # 0.6.1 Added directory name parsing support for TV series title, series number and episode numbers. Patch
00245  #       contributed by Mitko Haralanov (thanks).
00246  # 0.6.2 Added updating the 'homepage', 'releasedata' and 'hash' fields in the videometadata table
00247  #       is the field exists. These fields is only present in trunk.
00248  #       Properly initalize the homepage, hash, releasedate fields when adding a new videometadata record.
00249  # 0.6.3 Convert to new python bindings and replace all direct mysql data base calls. See ticket #7264
00250  #       Remove the requirement for the MySQLdb python library.
00251  #       Removed the folder icon symlink code as it is redundant with MythVideo internal functionality.
00252  #       The 'folderart' option is no longer support on a jamu.conf file and will be ignored if present.
00253  #       Fixed a bug where a FE video directory was set but there were no FE image directories.
00254  #       If there were local SG images directories set they were being used. This is an invalid
00255  #       configuration that should have caused an error.
00256  # 0.6.4 Added a new option (-R) to allow just interactively populate the video reference numbers from
00257  #       TVDB and TMDB without any meta data downloads. After that the user runs Jamu with the -M option
00258  #       and the meta data and images are downloaded.
00259  #       Added to the interactive interface the ability to select a reference number of '99999999'
00260  #       which effectively tells jamu to ignore the specific video from all further processing.
00261  #       Changed the return code from 1 to 0 when Jamu exits without processing if there is already an
00262  #       instance of Jamu running. This was causing issues for Mythbuntu when TVDB or TMDB was down for an
00263  #       extended period.
00264  #       Added a new jamu.conf section [ignore-directory] to list Video sub-directories that Jamu should
00265  #       ignore.
00266  #       Change Jamu's import of tvdb and tmdb api libraries to use the installed versions found with the
00267  #       MythTV python bindings.
00268  #       Changed Jamu to use the tmdb API v2.0 python library
00269  #       Jamu will always use the TMDB reference number over the IMDB number but still supports IMDB#s
00270  #       Jamu interactive sessions for movies now lists the TMDB#s instead of IMDB#s
00271  #       Jamu will convert any IMDB#s to TMDB#s when themoviedb.org includes that movie. This is in line
00272  #       with MythVideo changes. Graphics for the movie will also be renamed so they do not need to be
00273  #       re-downloaded.
00274  #       Add the production countries for movies when TMDB provides that information.
00275  #       Adjusted the -MW option to add a " Season 1" to any downloaded image filename for TV shows.
00276  #       This must be done to make sure that TV shows like "24" do not clash with a movie's TMDB# like
00277  #       (Kill Bill Vol.1) which is "24".
00278  #       Added message display for exceptions where the message may enhance problem analysis.
00279  #       Removed logic which checked that a TV episode was using Season graphics rather than Series graphics.
00280  #       Unfortunately there was a chance that the a Series's graphics could clobber a movie with the same TMDB#
00281  #       as the series title (e.g. the movie Kill Bill Vol.1 and the TV series 24). A positive is that a number
00282  #       of redundant TV Series images can be removed through the jamu -MJ option.
00283  #       Improved the -MW options detection of TV series when the EPG data does not include a subtitle. Users
00284  #       can add the specific TVDB numbers to the 'series_name_override' section of the jamu.conf file.
00285  #       Australian users had mentioned this as an issue, previously the TV series was always being mistaken
00286  #       for a movie.
00287  #       Jamu will now download the top rated TV Series season coverart and banner images. This enhancement
00288  #       matches MythVideo processing.
00289  # 0.6.5 Small fix related to the bindings changes.
00290  # 0.6.6 Fixed Exception messages
00291  #       Change all occurances of 'mythbeconn.host' to 'mythbeconn.hostname' to be consistent with bindings
00292  # 0.6.7 Fixed the (-J) janitor option from removing the Mirobridge default images when they are not being used
00293  # 0.6.8 Fixed a (-J) janitor option statistics error due to skipping Mirbridge default images
00294  # 0.6.9 Fixed an abort when IMDBpy returns movie matches with incomplete data
00295  #       Fixed an abort where an IMDB# was being used instead of a TMDB#
00296  #       Fixed an abort when a storage directory name caused an UnicodeEncodeError or TypeError exception
00297  # 0.7.0 Fixed an (-MW) option abort when a recorded program or upcoming program did not have a title
00298  # 0.7.1 Fixed a bug where movies with punctutation ("Mr. Magoo") were not finding matches
00299  #       Fixed bug with interactive mode when a user enters a reference number directly rather than
00300  #       making a list selection
00301  #       These bugs were both identified by Edi Iten (thanks)
00302  # 0.7.2 Fixed a bug where an inetref field was not properly initialized and caused an abort. Ticket #8243
00303  # 0.7.3 Fixed a bug where a user selected TMDB# was not being used.
00304  #       Minor change to fuzzy matching of a file named parsed title with those from TMDB and TVDB.
00305  # 0.7.4 Update for changes in Python bindings
00306  # 0.7.5 Added the TMDB MovieRating as videometadata table "rating" field
00307  # 0.7.6 Modifications to support MythTV python bindings changes
00308  # 0.7.7 Pull hostname from python bindings instead of socket libraries
00309  # 0.7.8 Replace uses of MythVideo.getVideo()
00310  # 0.7.9 Deal with jamu.conf entries that have unicode characters
00311  #       Replace 'xml' module version check with generic Python version, to correct failure in Python 2.7
00312  # 0.8.0 Fixed a bug which caused jamu to crash due to an extra unicode conversion introduced in 0.7.9.
00313  #       See also #9637.
00314 
00315 
00316 usage_txt=u'''
00317 JAMU - Just.Another.Metadata.Utility is a versatile utility for downloading graphics and meta data
00318 for both movies and TV Series information from themoviedb.com wiki and thetvdb.com wiki. In addition
00319 the MythTV data base is updated with the downloaded information.
00320 Here are the main uses for this utility:
00321 MythTV users should review the Jamu wiki page at http://www.mythtv.org/wiki/Jamu for details.
00322 
00323 1) Simple command line invocation to display or download data from thetvdb.com.
00324    Data can be one or more of: Posters/Cover art, Banners, Fan art,
00325    Episode Images and Episode meta data. use the command "jamu -e | less" to see
00326    command line examples.
00327 2) Mass downloads of data matching your video files. **
00328    This typically done once to download the information for your video collection.
00329 3) Automated maintenance of the information in your video collection. **
00330 4) The creation of video file names which can be used to set the file name of your recorded TV shows.
00331    File names can be formated to the users preference with information like series name, season number,
00332    episode number and episode name. MythTV users may find this valuable as part of a user job
00333    that is spawned automatically by mythbackend when recording is finished.
00334 5) Jamu's modules can be imported into your own python scripts to create enhanced functionality.
00335 6) With the installation of free ImageMagick's utilities (specifically 'mogrify') you can resize
00336    graphics when they are downloaded.
00337 7) Update the MythTV data base with links to posters, banners, fanart and episode images and optionally
00338    download missing graphics if they exist. This feature can be used for mass updates and regular
00339    maintenance.
00340 
00341 '''
00342 
00343 examples_txt=u'''
00344 MythTV users should review the Jamu wiki page at http://www.mythtv.org/wiki/Jamu for details.
00345 These examples are primarily for non-MythTV users of Jamu.
00346 
00347 jamu command line examples:
00348 NOTE: Included here are simple examples of jamu in action.
00349       Please review jamu_README for advise on how to get the most out of jamu.
00350 
00351 ( Display a TV series top rated poster fanart and banner URLs)
00352 > jamu -tS PBF "Sanctuary"
00353 poster:http://www.thetvdb.com/banners/posters/80159-1.jpg
00354 fanart:http://www.thetvdb.com/banners/fanart/original/80159-2.jpg
00355 banner:http://www.thetvdb.com/banners/graphical/80159-g2.jpg
00356 
00357 ( Display the URL for a TV series episode )
00358 > jamu -tS I "Fringe" 1 5
00359 filename:http://www.thetvdb.com/banners/episodes/82066-391049.jpg
00360 
00361 ( Display poster, fanart and banner graphics for a TV series but limited to two per type in a season )
00362 > jamu -S PBF -m 2 "24" 4
00363 poster:http://www.thetvdb.com/banners/seasons/76290-4-3.jpg
00364 poster:http://www.thetvdb.com/banners/seasons/76290-4.jpg
00365 fanart:http://www.thetvdb.com/banners/fanart/original/76290-1.jpg
00366 fanart:http://www.thetvdb.com/banners/fanart/original/76290-2.jpg
00367 banner:http://www.thetvdb.com/banners/seasonswide/76290-4.jpg
00368 banner:http://www.thetvdb.com/banners/seasonswide/76290-4-3.jpg
00369 
00370 ( Display a file name string (less file extention and directory path) for a TV episode )
00371 > jamu -F "24" 4 3
00372 24 - S04E03 - Day 4: 9:00 A.M.-10:00 A.M.
00373 
00374 > jamu -F "24" "Day 4: 9:00 A.M.-10:00 A.M."
00375 24 - S04E03 - Day 4: 9:00 A.M.-10:00 A.M.
00376 
00377 ( Using SID number instead of series name )
00378 > jamu -F 76290 4 3
00379 24 - S04E03 - Day 4: 9:00 A.M.-10:00 A.M.
00380 
00381 ( Simulate a dry run for the download of a TV series top rated poster and fanart )
00382 > jamu -sdtS PF "Fringe"
00383 Simulation download of URL(http://www.thetvdb.com/banners/posters/82066-6.jpg) to File(~/Pictures/Poster - 82066-6.jpg)
00384 Get_Poster downloading successfully processed
00385 Simulation download of URL(http://www.thetvdb.com/banners/fanart/original/82066-11.jpg) to File(~/Pictures/Fanart - 82066-11.jpg)
00386 Get_Fanart downloading successfully processed
00387 
00388 ( Download the Episode meta data and episode image for a video file whose file name contains the series and season/episode numbers)
00389 > jamu -dS EI "~/Pictures/Fringe - S01E01.mkv"
00390 Episode meta data and/or images downloads successfully processed
00391 > ls -ls
00392 total 2
00393 60 -rw-r--r-- 1 user user 53567 2009-03-12 22:05 Fringe - S01E01 - Pilot.jpg
00394  4 -rw-r--r-- 1 user user  1059 2009-03-12 22:05 Fringe - S01E01 - Pilot.meta
00395  4 -rw-r--r-- 1 user user   811 2009-03-12 13:22 Fringe - S01E01.mkv
00396 
00397 ( Display Episode meta data for a TV series )
00398 > jamu -S E "24" 5 3
00399 series:24
00400 seasonnumber:5
00401 episodenumber:3
00402 episodename:Day 5: 9:00 A.M.-10:00 A.M.
00403 rating:None
00404 overview:Jack conceals himself inside the airport hanger and surveys the Russian separatists, feeding information to Curtis and his assault team.
00405 The terrorists begin executing hostages in an attempt to make Logan cave into their demands.
00406 Martha discovers that all traces of her conversation with Palmer may not have been erased.
00407 director:Brad Turner
00408 writer:Manny Coto
00409 gueststars:John Gleeson Connolly, V.J. Foster, David Dayan Fisher, Taylor Nichols, Steve Edwards, Taras Los, Joey Munguia, Reggie Jordan, Lou Richards, Karla Zamudio
00410 imdb_id:None
00411 filename:http://www.thetvdb.com/banners/episodes/76290-306117.jpg
00412 epimgflag:None
00413 language:en
00414 firstaired:2006-01-16
00415 lastupdated:1197942225
00416 productioncode:5AFF03
00417 id:306117
00418 seriesid:76290
00419 seasonid:10067
00420 absolute_number:None
00421 combined_season:5
00422 combined_episodenumber:4.0
00423 dvd_season:5
00424 dvd_discid:None
00425 dvd_chapter:None
00426 dvd_episodenumber:4.0
00427 
00428 ( Specify a user defined configuration file to set most of the configuration variables )
00429 > jamu -C "~/.jamu/jamu.conf" -S P "Supernatural"
00430 poster:http://www.thetvdb.com/banners/posters/78901-3.jpg
00431 poster:http://www.thetvdb.com/banners/posters/78901-1.jpg
00432 
00433 ( Display in alphabetical order the state of all configuration variables )
00434 > jamu -f
00435 allgraphicsdir (~/Pictures)
00436 bannerdir (None)
00437 config_file (False)
00438 data_flags (None)
00439 debug_enabled (False)
00440 download (False)
00441 ... lots of configuration variables ...
00442 video_dir (None)
00443 video_file_exts (['3gp', 'asf', 'asx', 'avi', 'mkv', 'mov', 'mp4', 'mpg', 'qt', 'rm', 'swf', 'wmv', 'm2ts', 'evo', 'ts', 'img', 'iso'])
00444 with_ep_name (%(series)s - S%(seasonnumber)02dE%(episodenumber)02d - %(episodename)s.%(ext)s)
00445 without_ep_name (%(series)s - S%(seasonnumber)02dE%(episodenumber)02d.%(ext)s)
00446 '''
00447 
00448 # System modules
00449 import sys, os, re, locale, subprocess, locale, ConfigParser, urllib, codecs, shutil, datetime, fnmatch, string
00450 from datetime import date
00451 from optparse import OptionParser
00452 from socket import gethostbyname
00453 import tempfile, struct
00454 import logging
00455 
00456 class OutStreamEncoder(object):
00457     """Wraps a stream with an encoder"""
00458     def __init__(self, outstream, encoding=None):
00459         self.out = outstream
00460         if not encoding:
00461             self.encoding = sys.getfilesystemencoding()
00462         else:
00463             self.encoding = encoding
00464 
00465     def write(self, obj):
00466         """Wraps the output stream, encoding Unicode strings with the specified encoding"""
00467         if isinstance(obj, unicode):
00468             try:
00469                 self.out.write(obj.encode(self.encoding))
00470             except IOError:
00471                 pass
00472         else:
00473             try:
00474                 self.out.write(obj)
00475             except IOError:
00476                 pass
00477 
00478     def __getattr__(self, attr):
00479         """Delegate everything but write to the stream"""
00480         return getattr(self.out, attr)
00481 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
00482 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
00483 
00484 if sys.version_info <= (2,5):
00485     print '''JAMU requires Python 2.5 or newer to run.'''
00486     sys.exit(1)
00487 
00488 import xml.etree.cElementTree as ElementTree
00489 
00490 
00491 # Find out if the MythTV python bindings can be accessed and instances can be created
00492 try:
00493     '''If the MythTV python interface is found, we can insert data directly to MythDB or
00494     get the directories to store poster, fanart, banner and episode graphics.
00495     '''
00496     from MythTV import MythDB, Video, MythVideo, MythBE, MythError, MythLog, RecordedProgram
00497     from MythTV.database import DBData
00498     mythdb = None
00499     mythvideo = None
00500     mythbeconn = None
00501     try:
00502         '''Create an instance of each: MythDB, MythVideo
00503         '''
00504         MythLog._setlevel('none') # Some non option -M cannot have any logging on stdout
00505         mythdb = MythDB()
00506         mythvideo = MythVideo(mythdb)
00507         MythLog._setlevel('important,general')
00508     except MythError, e:
00509         print u'\n! Warning - %s' % e.args[0]
00510         filename = os.path.expanduser("~")+'/.mythtv/config.xml'
00511         if not os.path.isfile(filename):
00512             print u'\n! Warning - A correctly configured (%s) file must exist\n' % filename
00513         else:
00514             print u'\n! Warning - Check that (%s) is correctly configured\n' % filename
00515     except Exception, e:
00516         print u"\n! Warning - Creating an instance caused an error for one of: MythDB or MythVideo, error(%s)\n" % e
00517     localhostname = mythdb.gethostname()
00518     try:
00519         MythLog._setlevel('none') # Some non option -M cannot have any logging on stdout
00520         mythbeconn = MythBE(backend=localhostname, db=mythdb)
00521         MythLog._setlevel('important,general')
00522     except MythError, e:
00523         print u'\nWith any -M option Jamu must be run on a MythTV backend'
00524         print u'! Warning - %s' % e.args[0]
00525         mythbeconn = None
00526 except Exception, e:
00527     print u"\n! Warning - MythTV python bindings could not be imported, error(%s)\n" % e
00528     mythdb = None
00529     mythvideo = None
00530     mythbeconn = None
00531 
00532 
00533 # Verify that tvdb_api.py, tvdb_ui.py and tvdb_exceptions.py are available
00534 try:
00535     # thetvdb.com specific modules
00536     import MythTV.ttvdb.tvdb_ui as tvdb_ui
00537     # from tvdb_api import Tvdb
00538     import MythTV.ttvdb.tvdb_api as tvdb_api
00539     from MythTV.ttvdb.tvdb_exceptions import (tvdb_error, tvdb_shownotfound, tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_episodenotfound, tvdb_attributenotfound, tvdb_userabort)
00540 
00541     # verify version of tvdbapi to make sure it is at least 1.0
00542     if tvdb_api.__version__ < '1.0':
00543         print "\nYour current installed tvdb_api.py version is (%s)\n" % tvdb_api.__version__
00544         raise
00545 except Exception, e:
00546     print '''
00547 The modules tvdb_api.py (v1.0.0 or greater), tvdb_ui.py, tvdb_exceptions.py and cache.py.
00548 They should have been installed along with the MythTV python bindings.
00549 Error(%s)
00550 ''' % e
00551     sys.exit(1)
00552 
00553 
00554 try:
00555     import MythTV.tmdb.tmdb_api as tmdb_api
00556     from MythTV.tmdb.tmdb_exceptions import (TmdBaseError, TmdHttpError, TmdXmlError, TmdbUiAbort, TmdbMovieOrPersonNotFound,)
00557 except Exception, e:
00558     sys.stderr.write('''
00559 The subdirectory "tmdb" containing the modules tmdb_api.py (v0.1.3 or greater), tmdb_ui.py,
00560 tmdb_exceptions.py must have been installed with the MythTV python bindings.
00561 Error:(%s)
00562 ''' %  e)
00563     sys.exit(1)
00564 
00565 if tmdb_api.__version__ < '0.1.3':
00566     sys.stderr.write("\n! Error: Your current installed tmdb_api.py version is (%s)\nYou must at least have version (0.1.3) or higher.\n" % tmdb_api.__version__)
00567     sys.exit(1)
00568 
00569 
00570 imdb_lib = True
00571 try:            # Check if the installation is equiped to directly search IMDB for movies
00572     import imdb
00573 except ImportError, e:
00574     sys.stderr.write("\n! Error: To search for movies movies the IMDbPy library must be installed."\
00575         "Check your installation's repository or check the following link."\
00576         "from (http://imdbpy.sourceforge.net/?page=download)\nError:(%s)\n" % e)
00577     sys.exit(1)
00578 
00579 if imdb_lib:
00580     if imdb.__version__ < "3.8":
00581         sys.stderr.write("\n! Error: You version the IMDbPy library (%s) is too old. You must use version 3.8 of higher." % imdb.__version__)
00582         sys.stderr.write("Check your installation's repository or check the following link."\
00583             "from (http://imdbpy.sourceforge.net/?page=download)\n")
00584         sys.exit(1)
00585 
00586 class VideoTypes( DBData ):
00587     _table = 'videotypes'
00588     _where = 'intid=%s'
00589     _setwheredat = 'self.intid,'
00590     _logmodule = 'Python VideoType'
00591     def __str__(self):
00592         return "<VideoTypes '%s'>" % self.extension
00593     def __repr__(self):
00594         return str(self).encode('utf-8')
00595     def __init__(self, id=None, ext=None, db=None):
00596         if id is not None:
00597             DBData.__init__(self, data=(id,), db=db)
00598         elif ext is not None:
00599             self.__dict__['_where'] = 'extension=%s'
00600             self.__dict__['_wheredat'] = 'self.extension,'
00601             DBData.__init__(self, data=(ext,), db=db)
00602         else:
00603             DBData.__init__(self, None, db=db)
00604 # end VideoTypes()
00605 
00606 def isValidPosixFilename(name, NAME_MAX=255):
00607     """Checks for a valid POSIX filename
00608 
00609     Filename: a name consisting of 1 to {NAME_MAX} bytes used to name a file.
00610         The characters composing the name may be selected from the set of
00611         all character values excluding the slash character and the null byte.
00612         The filenames dot and dot-dot have special meaning.
00613         A filename is sometimes referred to as a "pathname component".
00614 
00615     name: (base)name of the file
00616     NAME_MAX: is defined in limits.h (implementation-defined constants)
00617               Maximum number of bytes in a filename
00618               (not including terminating null).
00619               Minimum Acceptable Value: {_POSIX_NAME_MAX}
00620               _POSIX_NAME_MAX: Maximum number of bytes in a filename
00621                                (not including terminating null).
00622                                Value: 14
00623 
00624     More information on http://www.opengroup.org/onlinepubs/009695399/toc.htm
00625     """
00626     return 1<=len(name)<= NAME_MAX and "/" not in name and "\000" not in name
00627 # end isValidPosixFilename()
00628 
00629 
00630 # Two routines used for movie title search and matching
00631 def is_punct_char(char):
00632     '''check if char is punctuation char
00633     return True if char is punctuation
00634     return False if char is not punctuation
00635     '''
00636     return char in string.punctuation
00637 
00638 def is_not_punct_char(char):
00639     '''check if char is not punctuation char
00640     return True if char is not punctuation
00641     return False if chaar is punctuation
00642     '''
00643     return not is_punct_char(char)
00644 
00645 def _getExtention(URL):
00646     """Get the graphic file extension from a URL
00647     return the file extention from the URL
00648     """
00649     (dirName, fileName) = os.path.split(URL)
00650     (fileBaseName, fileExtension)=os.path.splitext(fileName)
00651     return fileExtension[1:]
00652 # end getExtention
00653 
00654 def _getFileList(dst):
00655     ''' Create an array of fully qualified file names
00656     return an array of file names
00657     '''
00658     file_list = []
00659     names = []
00660 
00661     try:
00662         for directory in dst:
00663             try:
00664                 directory = unicode(directory, 'utf8')
00665             except (UnicodeEncodeError, TypeError):
00666                 pass
00667             for filename in os.listdir(directory):
00668                 names.append(os.path.join(directory, filename))
00669     except OSError, e:
00670         sys.stderr.write(u"\n! Error: Getting a list of files for directory (%s)\nThis is most likely a 'Permission denied' error\nError:(%s)\n\n" % (dst, e))
00671         return file_list
00672 
00673     for video_file in names:
00674         if os.path.isdir(video_file):
00675             new_files = _getFileList([video_file])
00676             for new_file in new_files:
00677                 file_list.append(new_file)
00678         else:
00679             file_list.append(video_file)
00680     return file_list
00681 # end _getFileList
00682 
00683 
00684 class singleinstance(object):
00685     '''
00686     singleinstance - based on Windows version by Dragan Jovelic this is a Linux
00687                      version that accomplishes the same task: make sure that
00688                      only a single instance of an application is running.
00689     '''
00690 
00691     def __init__(self, pidPath):
00692         '''
00693         pidPath - full path/filename where pid for running application is to be
00694                   stored.  Often this is ./var/<pgmname>.pid
00695         '''
00696         from os import kill
00697         self.pidPath=pidPath
00698         #
00699         # See if pidFile exists
00700         #
00701         if os.path.exists(pidPath):
00702             #
00703             # Make sure it is not a "stale" pidFile
00704             #
00705             try:
00706                 pid=int(open(pidPath, 'r').read().strip())
00707                 #
00708                 # Check list of running pids, if not running it is stale so
00709                 # overwrite
00710                 #
00711                 try:
00712                     kill(pid, 0)
00713                     pidRunning = 1
00714                 except OSError:
00715                     pidRunning = 0
00716                 if pidRunning:
00717                     self.lasterror=True
00718                 else:
00719                     self.lasterror=False
00720             except:
00721                 self.lasterror=False
00722         else:
00723             self.lasterror=False
00724 
00725         if not self.lasterror:
00726             #
00727             # Write my pid into pidFile to keep multiple copies of program from
00728             # running.
00729             #
00730             fp=open(pidPath, 'w')
00731             fp.write(str(os.getpid()))
00732             fp.close()
00733 
00734     def alreadyrunning(self):
00735         return self.lasterror
00736 
00737     def __del__(self):
00738         if not self.lasterror:
00739             import os
00740             os.unlink(self.pidPath)
00741     # end singleinstance()
00742 
00743 
00744 # Global variables
00745 graphicsDirectories = {'banner': u'bannerdir', 'screenshot': u'episodeimagedir', 'coverfile': u'posterdir', 'fanart': u'fanartdir'}
00746 dir_dict={'posterdir': "VideoArtworkDir", 'bannerdir': 'mythvideo.bannerDir', 'fanartdir': 'mythvideo.fanartDir', 'episodeimagedir': 'mythvideo.screenshotDir', 'mythvideo': 'VideoStartupDir'}
00747 storagegroupnames = {u'Videos': u'mythvideo', u'Coverart': u'posterdir', u'Banners': u'bannerdir', u'Fanart': u'fanartdir', u'Screenshots': u'episodeimagedir'}
00748 storagegroups={u'mythvideo': [], u'posterdir': [], u'bannerdir': [], u'fanartdir': [], u'episodeimagedir': []} # The gobal dictionary is only populated with the current hosts storage group entries
00749 image_extensions = ["png", "jpg", "bmp"]
00750 
00751 def getStorageGroups():
00752     '''Populate the storage group dictionary with the local host's storage groups.
00753     return nothing
00754     '''
00755     records = mythdb.getStorageGroup(hostname=localhostname)
00756     for record in records:
00757         # Only include Video, coverfile, banner, fanart, screenshot and trailers storage groups
00758         if record.groupname in storagegroupnames.keys():
00759             dirname = record.dirname
00760             try:
00761                 dirname = unicode(record.dirname, 'utf8')
00762             except (UnicodeDecodeError):
00763                 sys.stderr.write(u"\n! Error: The local Storage group (%s) directory contained\ncharacters that caused a UnicodeDecodeError. This storage group has been rejected.'\n" % (record['groupname']))
00764                 continue    # Skip any line that has non-utf8 characters in it
00765             except (UnicodeEncodeError, TypeError):
00766                 pass
00767             # Strip the trailing slash so it is consistent with all other directory paths in Jamu
00768             if dirname[-1:] == u'/':
00769                 storagegroups[storagegroupnames[record.groupname]].append(dirname[:-1])
00770             else:
00771                 storagegroups[storagegroupnames[record.groupname]].append(dirname)
00772         continue
00773 
00774     any_storage_group = False
00775     tmp_storagegroups = dict(storagegroups)
00776     for key in tmp_storagegroups.keys():
00777         if len(tmp_storagegroups[key]):
00778             any_storage_group = True
00779         else:
00780             del storagegroups[key]    # Remove empty SG directory arrays
00781     if any_storage_group:
00782         # Verify that each storage group is an existing local directory
00783         storagegroup_ok = True
00784         for key in storagegroups.keys():
00785             for directory in storagegroups[key]:
00786                 if not os.access(directory, os.F_OK):
00787                     sys.stderr.write(u"\n! Error: The local Storage group (%s) directory (%s) does not exist\n" % (key, directory))
00788                     storagegroup_ok = False
00789         if not storagegroup_ok:
00790             sys.exit(1)
00791 # end getStorageGroups
00792 
00793 
00794 def _can_int(x):
00795     """Takes a string, checks if it is numeric.
00796     >>> _can_int("2")
00797     True
00798     >>> _can_int("A test")
00799     False
00800     """
00801     if x == None:
00802         return False
00803     try:
00804         int(x)
00805     except ValueError:
00806         return False
00807     else:
00808         return True
00809 # end _can_int
00810 
00811 class BaseUI:
00812     """Default non-interactive UI, which auto-selects first results
00813     """
00814     def __init__(self, config, log):
00815         self.config = config
00816         self.log = log
00817 
00818     def selectSeries(self, allSeries):
00819         return allSeries[0]
00820 
00821     def selectMovieOrPerson(self, allElements):
00822         return makeDict([allElements[0]])
00823 
00824 # Local variable
00825 video_type = u''
00826 UI_title = u''
00827 UI_search_language = u''
00828 UI_selectedtitle = u''
00829 # List of language from http://www.thetvdb.com/api/0629B785CE550C8D/languages.xml
00830 # Hard-coded here as it is realtively static, and saves another HTTP request, as
00831 # recommended on http://thetvdb.com/wiki/index.php/API:languages.xml
00832 UI_langid_dict = {u'da': u'10', 'fi': u'11', u'nl': u'13', u'de': u'14', u'it': u'15', u'es': u'16', u'fr': u'17', u'pl': u'18', u'hu': u'19', u'el': u'20', u'tr': u'21', u'ru': u'22', u'he': u'24', u'ja': u'25', u'pt': u'26', u'zh': u'27', u'cs': u'28', u'sl': u'30', u'hr': u'31', u'ko': u'32', u'en': '7', u'sv': u'8', u'no': u'9',}
00833 
00834 class jamu_ConsoleUI(BaseUI):
00835     """Interactively allows the user to select a show or movie from a console based UI
00836     """
00837 
00838     def _displaySeries(self, allSeries_array):
00839         """Helper function, lists series with corresponding ID
00840         """
00841         if video_type == u'IMDB':
00842             URL = u'http://www.imdb.com/title/tt'
00843             URL2 = u'http://www.imdb.com/find?s=all&q='+urllib.quote_plus(UI_title.encode("utf-8"))+'&x=0&y=0'
00844             reftype = u'IMDB'
00845         elif video_type == u'TMDB':
00846             URL = u'http://themoviedb.org/movie/'
00847             URL2 = u'http://themoviedb.org/'
00848             reftype = u'TMDB'
00849         else: # TVDB
00850             URL = u'http://thetvdb.com/index.php?tab=series&id=%s&lid=%s'
00851             URL2 = u'http://thetvdb.com/?tab=advancedsearch'
00852             reftype = u'thetvdb'
00853             tmp_title = u''
00854 
00855         allSeries={}
00856         for index in range(len(allSeries_array)):
00857             allSeries[allSeries_array[index]['name']] = allSeries_array[index]
00858         tmp_names = allSeries.keys()
00859         tmp_names.sort()
00860         most_likely = []
00861 
00862         # Find any TV Shows or Movies who's titles start with the video's title
00863         for name in tmp_names:
00864             if filter(is_not_punct_char, name.lower()).startswith(filter(is_not_punct_char, UI_title.lower())):
00865                 most_likely.append(name)
00866 
00867         # IMDB can return titles that are a movies foriegn title. The titles that do not match
00868         # the requested title need to be added to the end of the most likely titles list.
00869         if video_type == u'IMDB' and len(most_likely):
00870             for name in tmp_names:
00871                 try:
00872                     dummy = most_likely.index(name)
00873                 except ValueError:
00874                     most_likely.append(name)
00875 
00876         names = []
00877         # Remove any name that does not start with a title like the TV Show/Movie (except for IMDB)
00878         if len(most_likely):
00879             for likely in most_likely:
00880                 names.append(likely)
00881         else:
00882             names = tmp_names
00883 
00884         if not video_type == u'IMDB':
00885             names.sort()
00886 
00887         # reorder the list of series and sid's
00888         new_array=[]
00889         for key in names: # list all search results
00890             new_array.append(allSeries[key])
00891 
00892         # If there is only one to select and it is an exact match then return with no interface display
00893         if len(new_array) == 1:
00894             if filter(is_not_punct_char, allSeries_array[0]['name'].lower()) == filter(is_not_punct_char, UI_title.lower()):
00895                 return new_array
00896 
00897         # Add the ability to select the skip inetref of '99999999'
00898         new_array.append( {'sid': '99999999', 'name': u'User choses to ignore video'} )
00899         names.append(u'User choses to ignore video')
00900 
00901         i_show=0
00902         for key in names: # list all search results
00903             i_show+=1 # Start at more human readable number 1 (not 0)
00904             if key == u'User choses to ignore video':
00905                 print u"% 2s -> %s # %s" % (
00906                     i_show,
00907                     '99999999', "Set this video to be ignored by Jamu with a reference number of '99999999'"
00908                 )
00909                 continue
00910             if video_type != u'IMDB' and video_type != u'TMDB':
00911                 tmp_URL = URL % (allSeries[key]['sid'], UI_langid_dict[UI_search_language])
00912                 print u"% 2s -> %s # %s" % (
00913                     i_show,
00914                     key, tmp_URL
00915                 )
00916             else:
00917                 print u"% 2s -> %s # %s%s" % (
00918                     i_show,
00919                     key, URL,
00920                     allSeries[key]['sid']
00921                 )
00922         print u"Direct search of %s # %s" % (
00923             reftype,
00924             URL2
00925         )
00926         return new_array
00927 
00928     def selectSeries(self, allSeries):
00929         global UI_selectedtitle
00930         UI_selectedtitle = u''
00931         allSeries = self._displaySeries(allSeries)
00932 
00933         # Check for an automatic choice
00934         if len(allSeries) <= 2:
00935             for series in allSeries:
00936                 if filter(is_not_punct_char, series['name'].lower()) == filter(is_not_punct_char, UI_title.lower()):
00937                     UI_selectedtitle = series['name']
00938                     return series
00939 
00940         display_total = len(allSeries)
00941 
00942         if video_type == u'IMDB':
00943             reftype = u'IMDB #'
00944             refsize = 7
00945             refformat = u"%07d"
00946         elif video_type == u'TMDB':
00947             reftype = u'TMDB #'
00948             refsize = 5
00949             refformat = u"%05d"
00950         else:
00951             reftype = u'Series id'
00952             refsize = 5
00953             refformat = u"%6d"    # Attempt to have the most likely TV/Movies at the top of the list
00954 
00955         while True: # return breaks this loop
00956             try:
00957                 print u'Enter choice ("Enter" key equals first selection (1)) or input the %s directly, ? for help):' % reftype
00958                 ans = raw_input()
00959             except KeyboardInterrupt:
00960                 raise tvdb_userabort(u"User aborted (^c keyboard interupt)")
00961             except EOFError:
00962                 raise tvdb_userabort(u"User aborted (EOF received)")
00963 
00964             self.log.debug(u'Got choice of: %s' % (ans))
00965             try:
00966                 if ans == '':
00967                     selected_id = 0
00968                 else:
00969                     selected_id = int(ans) - 1 # The human entered 1 as first result, not zero
00970             except ValueError: # Input was not number
00971                 if ans == u"q":
00972                     self.log.debug(u'Got quit command (q)')
00973                     raise tvdb_userabort(u"User aborted ('q' quit command)")
00974                 elif ans == u"?":
00975                     print u"## Help"
00976                     print u"# Enter the number that corresponds to the correct video."
00977                     print u"# Enter the %s number for the %s." % (reftype, video_type)
00978                     print u"# ? - this help"
00979                     print u"# q - abort"
00980                 else:
00981                     self.log.debug(u'Unknown keypress %s' % (ans))
00982             else:
00983                 self.log.debug(u'Trying to return ID: %d' % (selected_id))
00984                 try:
00985                     UI_selectedtitle = allSeries[selected_id]['name']
00986                     return allSeries[selected_id]
00987                 except IndexError:
00988                     if len(ans) == refsize and reftype != u'Series id':
00989                         UI_selectedtitle = u''
00990                         return {'name': u'User input', 'sid': ans}
00991                     elif reftype == u'Series id':
00992                         if len(ans) >= refsize:
00993                             UI_selectedtitle = u''
00994                             return {'name': u'User input', 'sid': ans}
00995                     self.log.debug(u'Invalid number entered!')
00996                     print u'Invalid number (%d) input! A directly entered %s must be a full %d zero padded digits (e.g. 905 should be entered as %s)' % (selected_id, reftype, refsize, refformat % 905)
00997                     UI_selectedtitle = u''
00998                     self._displaySeries(allSeries)
00999             #end try
01000         #end while not valid_input
01001 
01002 def _useImageMagick(cmd):
01003     """ Process graphics files using ImageMagick's utility 'mogrify'.
01004     >>> _useImageMagick('-resize 50% "poster.jpg"')
01005     >>> 0
01006     >>> -1
01007     """
01008     return subprocess.call(u'mogrify %s > /dev/null' % cmd, shell=True)
01009 # end verifyImageMagick
01010 
01011 # Call a execute a command line process
01012 def callCommandLine(command):
01013     '''Call a command line script or program. Display any errors
01014     return all stdoutput as a string
01015     '''
01016     p = subprocess.Popen(command, shell=True, bufsize=4096, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
01017 
01018     while 1:
01019         data = p.stderr.readline()
01020         if len(data):
01021             sys.stderr.write(u'%s\n' % data)
01022         if data == '' and p.poll() != None:
01023             break
01024 
01025     returned_data=''
01026     while 1:
01027         data = p.stdout.readline()
01028         if len(data):
01029             returned_data+=data
01030         if data == '' and p.poll() != None:
01031             break
01032     return returned_data
01033 # end callCommandLine
01034 
01035 
01036 # All functionality associated with configuration options
01037 class Configuration(object):
01038     """Set defaults, apply user configuration options, validate configuration settings and display the
01039     settings.
01040     To view all available options run:
01041     >>> config = Configuration()
01042     >>> config.displayOptions()
01043     """
01044     def __init__(self, interactive = False, debug = False):
01045         """Initialize default configuration settings
01046         """
01047         self.config = {}
01048         # Set all default variables
01049         self.config['interactive'] = interactive
01050         self.config['debug_enabled'] = debug
01051         self.config['flags_options'] = False
01052         self.config['local_language'] = u'en'
01053         self.config['simulation'] = False
01054         self.config['toprated'] = False
01055         self.config['download'] = False
01056         self.config['nokeys'] = False
01057         self.config['maximum'] = None
01058         self.config['user_config'] = None
01059         self.config['overwrite'] = False
01060         self.config['update'] = False
01061         self.config['mythtvdir'] = False
01062         self.config['hd_dvd'] = ' HD - On DVD'     # Used for HD DVD collection zero length video files
01063         self.config['dvd'] = ' - On DVD'        # Used for DVD collection zero length video files
01064 
01065         self.config['video_dir'] = None
01066         self.config['recursive'] = True
01067         self.config['series_name'] = None
01068         self.config['sid'] = None
01069         self.config['season_num'] = None
01070         self.config['episode_num'] = None
01071         self.config['episode_name'] = None
01072         self.config['ret_filename'] = False
01073 
01074         # Flags for which data to perform actions on
01075         self.config['get_poster'] = False
01076         self.config['get_banner'] = False
01077         self.config['get_fanart'] = False
01078         self.config['get_ep_image'] = False
01079         self.config['get_ep_meta'] = False
01080         self.config['data_flags'] = ''
01081         self.config['tmdb_genre_filter'] = ['action film', 'adventure film', 'comedy', 'crime film', 'disaster film', 'documentary film', 'drama film', 'eastern', 'environmental', 'fantasy film', 'historical film', 'horror film', 'musical film', 'mystery', 'mystery film', 'road movie', 'science fiction film', 'sport', 'thriller', 'western', 'film noir', 'cult movie', 'neo-noir', 'guy movie',]
01082 
01083         self.config['log'] = self._initLogger() # Setups the logger (self.log.debug() etc)
01084 
01085         # The default format of the file names (with and without episode names)
01086         self.config['with_ep_name'] = u'%(series)s - S%(seasonnumber)02dE%(episodenumber)02d - %(episodename)s.%(ext)s'
01087         self.config['without_ep_name'] = u'%(series)s - S%(seasonnumber)02dE%(episodenumber)02d.%(ext)s'
01088         self.config['ep_metadata'] = self.config['with_ep_name']
01089 
01090         # The default format of the graphics file names (with and without seasons and/or episode names)
01091         # The default is to use the URL's filename from thetvdb.com
01092         self.config['g_defaultname']=True
01093         # e.g. "Fringe - 01.jpg"
01094         self.config['g_series'] = u'%(series)s - %(seq)s.%(ext)s'
01095         # e.g. "SG-1 - 07-02.jpg"
01096         self.config['g_season'] = u'%(series)s - %(seasonnumber)02d-%(seq)s.%(ext)s'
01097 
01098         # Set default configuration variables
01099         # Start - Variables the user can override through option "-u" with their own file of variables
01100         self.config['allgraphicsdir'] = os.getcwd()
01101         self.config['posterdir'] = None
01102         self.config['bannerdir'] = None
01103         self.config['fanartdir'] = None
01104         self.config['episodeimagedir'] = None
01105         self.config['metadatadir'] = None
01106         self.config['mythtvmeta'] = False
01107         self.config['myth_secondary_sources'] = {}
01108         self.config['posterresize'] = False
01109         self.config['fanartresize'] = False
01110         self.config['min_poster_size'] = 400
01111         self.config['image_library'] = False
01112         self.config['ffmpeg'] = True
01113         self.config['folderart'] = False
01114         self.config['metadata_exclude_as_update_trigger'] = ['intid', 'season', 'episode', 'showlevel', 'filename', 'coverfile', 'childid', 'browse', 'playcommand', 'trailer', 'host', 'screenshot', 'banner', 'fanart']
01115         self.config['filename_char_filter'] = u"/%\000"
01116         self.config['ignore-directory'] = []
01117 
01118 
01119         # Dictionaries for Miro Bridge metadata downlods
01120         self.config['mb_tv_channels'] = {}
01121         self.config['mb_movies'] = {}
01122 
01123         # Episode data keys that you want to display or download.
01124         # This includes the order that you want them display or in the downloaded file.
01125         self.config['ep_include_data'] = [u'series', u'seasonnumber', u'episodenumber', u'episodename', u'rating', u'overview', u'director', u'writer', u'cast', u'gueststars', u'imdb_id', u'filename', u'epimgflag', u'language', u'runtime', u'firstaired', u'genres', u'lastupdated', u'productioncode', u'id', u'seriesid', u'seasonid', u'absolute_number', u'combined_season', u'combined_episodenumber', u'dvd_season', u'dvd_discid', u'dvd_chapter', u'dvd_episodenumber']
01126 
01127         self.config['config_file'] = False
01128         self.config['series_name_override'] = False
01129         self.config['ep_name_massage'] = False
01130         self.config['video_file_exts'] = [u'3gp', u'asf', u'asx', u'avi', u'mkv', u'mov', u'mp4', u'mpg', u'qt', u'rm', u'swf', u'wmv', u'm2ts', u'ts', u'evo', u'img', u'iso']
01131 
01132 
01133         # Regex pattern strings used to check for season number from directory names
01134         self.config['season_dir_pattern'] = [
01135             # foo_01 ????
01136             re.compile(u'''^.+?[ \._\-]([0-9]+)[^\\/]*$''', re.UNICODE),
01137             # foo_S01 ????
01138             re.compile(u'''^.+?[ \._\-][Ss]([0-9]+)[^\\/]*$''', re.UNICODE),
01139             # 01 ????
01140             re.compile(u'''([0-9]+)[^\\/]*$''', re.UNICODE),
01141             # s01 ????
01142             re.compile(u'''[Ss]([0-9]+)[^\\/]*$''', re.UNICODE),
01143             ]
01144 
01145 
01146         # Set default regex pattern strings used to extract series name , season and episode numbers for file name
01147         self.config['name_parse'] = [
01148             # foo_[s01]_[e01]
01149             re.compile(u'''^(.+?)[ \._\-]\[[Ss]([0-9]+?)\]_\[[Ee]([0-9]+?)\]?[^\\/]*$''', re.UNICODE),
01150             # foo.1x09*
01151             re.compile(u'''^(.+?)[ \._\-]\[?([0-9]+)x([0-9]+)[^\\/]*$''', re.UNICODE),
01152             # foo.s01.e01, foo.s01_e01
01153             re.compile(u'''^(.+?)[ \._\-][Ss]([0-9]+)[\.\- ]?[Ee]([0-9]+)[^\\/]*$''' , re.UNICODE),
01154             # foo.103*
01155             re.compile(u'''^(.+)[ \._\-]([0-9]{1})([0-9]{2})[\._ -][^\\/]*$''' , re.UNICODE),
01156             # foo.0103*
01157             re.compile(u'''^(.+)[ \._\-]([0-9]{2})([0-9]{2,3})[\._ -][^\\/]*$''' , re.UNICODE),
01158         ]
01159 
01160         # regex strings to parse folder names for TV series title, season and episode numbers
01161         self.config['fullname_parse_season_episode_translation'] = {u'slash': u'\\', u'season': u'Season', u'episode': u'Episode'}
01162         self.config['fullname_parse_regex'] = [
01163             # Title/Season 1/s01e01 Subtitle
01164             u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
01165                    u'''(?P<seasno>[0-9]+)/[Ss][0-9]+[Ee](?P<epno>[0-9]+).+$''',
01166             # Title/Season 1/1x01 Subtitle
01167             u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
01168                    u'''(?P<seasno>[0-9]+)/(?:(?P=seasno))[Xx](?P<epno>[0-9]+).+$''',
01169             # Title/Season 1/01 Subtitle
01170             u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
01171                    u'''(?P<seasno>[0-9]+)/(?P<epno>[0-9]+).+$''',
01172             # Title/Season 1/Title s01e01 Subtitle
01173             u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
01174                    u'''(?P<seasno>[0-9]+)/(?:(?P=seriesname))%(slash)s [Ss][0-9]+'''+
01175                    u'''[Ee](?P<epno>[0-9]+).+$''',
01176             # Title/Season 1/Title 1x01 Subtitle
01177             u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
01178                    u'''(?P<seasno>[0-9]+)/(?:(?P=seriesname))%(slash)s (?:(?P=seasno))'''+
01179                    u'''[Xx](?P<epno>[0-9]+).+$''',
01180             # Title/Season 1/Episode 1 Subtitle
01181             u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
01182                    u'''(?P<seasno>[0-9]+)/%(episode)s%(slash)s (?P<epno>[0-9]+).+$''',
01183             # Title/Season 1/Season 1 Episode 1 Subtitle
01184             u'''^.+?/(?P<seriesname>[^/]+)/%(season)s%(slash)s '''+
01185                    u'''(?P<seasno>[0-9]+)/%(season)s%(slash)s (?:(?P=seasno))%(slash)s '''+
01186                    u'''%(episode)s%(slash)s (?P<epno>[0-9]+).+$''',
01187             # Title Season 1/01 Subtitle
01188             u'''^.+?/(?P<seriesname>[^/]+)%(slash)s %(season)s%(slash)s (?P<seasno>[0-9]+)'''+
01189                    u'''/(?P<epno>[0-9]+).+$''',
01190             # Title Season 1/s01e01 Subtitle
01191             u'''^.*?/(?P<seriesname>[^/]+)%(slash)s %(season)s%(slash)s (?P<seasno>[0-9]+)'''+
01192                    u'''/[Ss][0-9]+[Ee](?P<epno>[0-9]+).+''',
01193             # Title Season 1/1x01 Subtitle
01194             u'''^.*?/(?P<seriesname>[^/]+)%(slash)s %(season)s%(slash)s (?P<seasno>[0-9]+)'''+
01195                    u'''/(?:(?P=seasno))[Xx](?P<epno>[0-9]+).+$''',
01196             # Title Season 1/Title s01e01 Subtitle
01197             u'''^.*?/(?P<seriesname>[^/]+)%(slash)s %(season)s%(slash)s (?P<seasno>[0-9]+)'''+
01198                    u'''/(?:(?P=seriesname))%(slash)s [Ss][0-9]+[Ee](?P<epno>[0-9]+).+$''',
01199             # Title Season 1/Title 1x01 Subtitle
01200             u'''^.*?/(?P<seriesname>[^/]+)%(slash)s %(season)s%(slash)s (?P<seasno>[0-9]+)'''+
01201                    u'''/(?:(?P=seriesname))%(slash)s (?:(?P=seasno))[Xx](?P<epno>[0-9]+).+$''',
01202             # Title Season 1/Episode 1 Subtitle
01203             u'''^.*?/(?P<seriesname>[^/]+)%(slash)s %(season)s%(slash)s (?P<seasno>[0-9]+)'''+
01204                    u'''/%(episode)s%(slash)s (?P<epno>[0-9]+).+$''',
01205             # Title Season 1/Season 1 Episode 1 Subtitle
01206             u'''^.*?/(?P<seriesname>[^/]+)%(slash)s %(season)s%(slash)s (?P<seasno>[0-9]+)'''+
01207                    u'''/%(season)s%(slash)s (?:(?P=seasno))%(slash)s %(episode)s%(slash)s (?P<epno>[0-9]+).+$'''
01208             ]
01209 
01210         # Initalize a valriable used by the -MW option
01211         self.program_seriesid = None
01212         self.config[u'file_move_flag'] = False
01213 
01214     # end __init__
01215 
01216     # Local variable
01217     data_flags_table={ 'P': 'get_poster', 'B': 'get_banner', 'F': 'get_fanart', 'I': 'get_ep_image', 'E': 'get_ep_meta'}
01218 
01219 
01220     def _initLogger(self):
01221         """Sets up a logger using the logging module, returns a log object
01222         """
01223         logger = logging.getLogger(u"jamu")
01224         formatter = logging.Formatter(u'%(asctime)s) %(levelname)s %(message)s')
01225 
01226         hdlr = logging.StreamHandler(sys.stdout)
01227 
01228         hdlr.setFormatter(formatter)
01229         logger.addHandler(hdlr)
01230 
01231         if self.config['debug_enabled']:
01232             logger.setLevel(logging.DEBUG)
01233         else:
01234             logger.setLevel(logging.WARNING)
01235         return logger
01236     #end initLogger
01237 
01238     def setUseroptions(self, useroptions):
01239         """    Change variables through a user supplied configuration file
01240         return False and exit the script if there are issues with the configuration file values
01241         """
01242         if useroptions[0]=='~':
01243             useroptions=os.path.expanduser("~")+useroptions[1:]
01244         if os.path.isfile(useroptions) == False:
01245             sys.stderr.write(
01246                 "\n! Error: The specified user configuration file (%s) is not a file\n" % useroptions
01247             )
01248             sys.exit(1)
01249         cfg = ConfigParser.SafeConfigParser()
01250         cfg.readfp(codecs.open(useroptions, "r", "utf8"))
01251         for section in cfg.sections():
01252             if section[:5] == 'File ':
01253                 self.config['config_file'] = section[5:]
01254                 continue
01255             if section == 'variables':
01256                 # Change variables per user config file
01257                 for option in cfg.options(section):
01258                     if option == 'video_file_exts' or option == 'tmdb_genre_filter' or option == 'metadata_exclude_as_update_trigger':
01259                         tmp_list = (cfg.get(section, option).rstrip()).split(',')
01260                         for i in range(len(tmp_list)): tmp_list[i] = (tmp_list[i].strip()).lower()
01261                         self.config[option] = tmp_list
01262                         continue
01263                     if option == 'filename_char_filter':
01264                         for char in cfg.get(section, option):
01265                             self.config['filename_char_filter']+=char
01266                         continue
01267                     if option == 'translate':
01268                         s_e = (cfg.get(section, option).rstrip()).split(',')
01269                         if not len(s_e) == 2:
01270                             continue
01271                         for index in range(len(s_e)):
01272                             s_e[index] = s_e[index].strip()
01273                         self.config['fullname_parse_season_episode_translation'] = {u'slash': u'\\', u'season': s_e[0], u'episode': s_e[1]}
01274                         continue
01275 
01276                     # Ignore user settings for Myth Video and graphics file directories
01277                     # when the MythTV metadata option (-M) is selected
01278                     if self.config['mythtvmeta'] and option in ['posterdir', 'bannerdir', 'fanartdir', 'episodeimagedir', 'mythvideo']:
01279                         continue
01280                     self.config[option] = cfg.get(section, option)
01281                 continue
01282             if section == 'regex':
01283                 # Change variables per user config file
01284                 for option in cfg.options(section):
01285                     self.config['name_parse'].append(re.compile(cfg.get(section, option), re.UNICODE))
01286                 continue
01287             if section == 'ignore-directory':
01288                 # Video directories to be excluded from Jamu processing
01289                 for option in cfg.options(section):
01290                     self.config['ignore-directory'].append(cfg.get(section, option))
01291                 continue
01292             if section =='series_name_override':
01293                 overrides = {}
01294                 for option in cfg.options(section):
01295                     overrides[option] = cfg.get(section, option)
01296                 if len(overrides) > 0:
01297                     self.config['series_name_override'] = overrides
01298                 continue
01299             if section =='ep_name_massage':
01300                 massage = {}
01301                 for option in cfg.options(section):
01302                     tmp =cfg.get(section, option).split(',')
01303                     if len(tmp)%2 and len(cfg.get(section, option)) != 0:
01304                         sys.stderr.write(u"\n! Error: For (%s) 'ep_name_massage' values must be in pairs\n" % option)
01305                         sys.exit(1)
01306                     tmp_array=[]
01307                     i=0
01308                     while i != len(tmp):
01309                         tmp[i] = tmp[i].strip()
01310                         tmp[i+1] = tmp[i+1].strip()
01311                         tmp_array.append([tmp[i].replace('"',''), tmp[i+1].replace('"','')])
01312                         i+=2
01313                     massage[option]=tmp_array
01314                 if len(massage) > 0:
01315                     self.config['ep_name_massage'] = massage
01316                 continue
01317             if section == 'ep_metadata_to_download':
01318                 if len(cfg.options(section)):
01319                     if cfg.options(section)[0] == 'ep_include_data':
01320                         tmp=cfg.get(section, cfg.options(section)[0])
01321                         overrides=tmp.split(',')
01322                         for index in range(len(overrides)):
01323                             x = overrides[index].replace(' ','')
01324                             if len(x) != 0:
01325                                 overrides[index]=x
01326                             else:
01327                                 del overrides[index]
01328                     self.config['ep_include_data']=overrides
01329                 continue
01330             if section == 'data_flags':
01331                 if len(cfg.options(section)):
01332                     for option in cfg.options(section):
01333                         if cfg.get(section, option).lower() != 'False'.lower():
01334                             for key in self.data_flags_table.keys():
01335                                 if option == self.data_flags_table[key]:
01336                                     self.config[option] = True
01337                 continue
01338             for sec in ['movies-secondary-sources', 'tv-secondary-sources']:
01339                 if section == sec:
01340                     secondary = {}
01341                     for option in cfg.options(section):
01342                         secondary[option] = cfg.get(section, option)
01343                     if len(secondary) > 0:
01344                         self.config['myth_secondary_sources'][sec[:sec.index('-')]] = secondary
01345                 continue
01346             if section == u'mb_tv':
01347                 # Add the channel names and their corresponding thetvdb.com id numbers
01348                 for option in cfg.options(section):
01349                     self.config['mb_tv_channels'][filter(is_not_punct_char, option.lower())] = [cfg.get(section, option), u'']
01350                 continue
01351             if section == u'mb_movies':
01352                 # Add the channel names for movie trailer Channels
01353                 for option in cfg.options(section):
01354                     self.config['mb_movies'][filter(is_not_punct_char, option.lower())] = cfg.get(section, option)
01355                 continue
01356 
01357         # Expand any home directories that are not fully qualified
01358         dirs_to_check= [u'bannerdir', u'episodeimagedir', u'metadatadir', u'posterdir', u'video_dir', u'fanartdir']
01359         for item in dirs_to_check:
01360             if self.config[item]:
01361                 if item == u'metadatadir' and not self.config[item]:
01362                     continue
01363                 if self.config[item][0]=='~':
01364                     self.config[item]=os.path.expanduser("~")+self.config[item][1:]
01365     # end setUserconfig
01366 
01367     def displayOptions(self):
01368         """ Display all of the configuration values. This is used to verify that the user has the
01369         variables set as they want before running jamu live.
01370         """
01371         keys=self.config.keys()
01372         keys.sort()
01373 
01374 ################### Used to create the example configuration file "jamu-example-conf"
01375 #        for key in keys:    # Used to create the example configuration file "jamu-example-conf"
01376 #            print "#%s: %s" % (key, self.config[key])
01377 #        sys.exit(0)
01378 ##################
01379 
01380         for key in keys:
01381             if key == 'log':    # Do not display the logger instance it is irrelevant for display
01382                 continue
01383             try:
01384                 if key == 'name_parse':
01385                     print u"%s (%d items)" % (key, len(self.config[key]))
01386                 else:
01387                     print u"%s (%s)" % (key, str(self.config[key]))
01388             except:
01389                 try:
01390                     print u"%s (%d items)" % (key, len(self.config[key]))
01391                 except:
01392                     print u"%s:" % key, self.config[key]
01393     # end set_Userconfig
01394 
01395     def changeVariable(self, key, value):
01396         """Change any configuration variable - caution no validation is preformed
01397         """
01398         self.config[key]=value
01399     # end changeVariable
01400 
01401 
01402     def _checkNFS(self, dirs, ext_filter):
01403         '''Check if any of the files are on NFS shares. If they are then the user must be warned.
01404         return True if there are at least one file is on a NFS share.
01405         return False if no graphic files are on an NFS share.
01406         '''
01407         tmp_dirs = []
01408         for d in dirs:        # Get rid of Null directories
01409             if d:
01410                 tmp_dirs.append(d)
01411         dirs = tmp_dirs
01412 
01413         global localhostname, graphicsDirectories
01414         try:
01415             localip = gethostbyname(localhostname) # Get the local hosts IP address
01416         except Exception, e:
01417             sys.stderr.write("\n! Error: There is no valid address-to-host mapping for the host (%s)\nThe Jamu Janitor (-MJ) option cannot be used while this issue remains un-resolved.\nError:(%s)\n" % (localhostname, e))
01418             sys.exit(1)
01419 
01420         # Get all curently mounted NFS shares
01421         tmp_mounts = callCommandLine("mount -l | grep '//'").split('\n')
01422         nfs = []
01423         for mount in tmp_mounts:
01424             mount.rstrip()
01425             parts = mount.split(' ')
01426             tmparray=[P for P in parts]
01427             if tmparray[0].startswith('//'):        # Is this a NFS share definition
01428                 if not tmparray[0].startswith(u'//%s' % localip) and not tmparray[0].startswith(u'//%s' % localhostname):
01429                     nfs.append(tmparray[2])            # Add an NFS mount name
01430 
01431         if not len(nfs):    # Check if there are any NFS mounts
01432             return False
01433 
01434         # Check if any of the directories have files on an NFS share
01435         for directory in dirs:    # Check the base directories first
01436             for mount in nfs:
01437                 if os.path.realpath(directory).startswith(mount):
01438                     return True
01439         for directory in dirs:    # Check the actual files
01440             file_list = _getFileList([directory])
01441             if not len(file_list):
01442                 continue
01443             tmp_list = []
01444             for fle in file_list: # Make a copy of file_list
01445                 tmp_list.append(fle)
01446             for g_file in tmp_list:        # Cull the list removing dirs and non-extention files
01447                 if os.path.isdir(g_file):
01448                     file_list.remove(g_file)
01449                     continue
01450                 g_ext = _getExtention(g_file)
01451                 if not g_ext.lower() in ext_filter:
01452                     file_list.remove(g_file)
01453                     continue
01454             for filename in file_list:        # Actually check each file against the NFS mounts
01455                 for mount in nfs:
01456                     if os.path.realpath(filename).startswith(mount):
01457                         return True
01458         return False
01459     # end _checkNFS
01460 
01461 
01462     def _getMythtvDirectories(self):
01463         """Get all graphics directories found in the MythTV DB and change their corresponding
01464         configuration values.  /media/video:/media/virtual/VB_Share/Review
01465         """
01466         # Stop processing if this local host has any storage groups
01467         global localhostname, storagegroups
01468         # Make sure Jamu is being run on a MythTV backend
01469         if not mythbeconn:
01470             sys.stderr.write(u"\n! Error: Jamu must be run on a MythTV backend. Local host (%s) is not a MythTV backend.\n" % localhostname)
01471             sys.exit(1)
01472 
01473         global dir_dict
01474         for key in dir_dict.keys():
01475             graphics_dir = mythdb.settings[localhostname][dir_dict[key]]
01476             # Only use path from MythTV if one was found
01477             self.config[key] = []
01478             if key == 'mythvideo' and graphics_dir:
01479                 tmp_directories = graphics_dir.split(':')
01480                 if len(tmp_directories):
01481                     for i in range(len(tmp_directories)):
01482                         tmp_directories[i] = tmp_directories[i].strip()
01483                         if tmp_directories[i] != '':
01484                             if os.access(tmp_directories[i], os.F_OK):
01485                                 self.config[key].append(tmp_directories[i])
01486                                 continue
01487                             else:
01488                                  sys.stderr.write(u"\n! Warning: MythTV video directory (%s) does not exist.\n" % (tmp_directories[i]))
01489                                  continue
01490 
01491             if key != 'mythvideo' and graphics_dir:
01492                 if os.path.os.access(graphics_dir, os.F_OK):
01493                     self.config[key] = [graphics_dir]
01494                 else:
01495                     sys.stderr.write(u"\n! Warning: MythTV (%s) directory (%s) does not exist.\n" % (key, graphics_dir))
01496 
01497         # Save the FE path settings local to this backend
01498         self.config['localpaths'] = {}
01499         for key in dir_dict.keys():
01500             self.config['localpaths'][key] = []
01501             local_paths = []
01502             if len(self.config[key]):
01503                 self.config['localpaths'][key] = list(self.config[key])
01504 
01505         # If there is a Videos SG then there is always a Graphics SG using Videos as a fallback
01506         getStorageGroups()
01507         for key in dir_dict.keys():
01508             if key == 'episodeimagedir' or key == 'mythvideo':
01509                 continue
01510             if storagegroups.has_key(u'mythvideo') and not storagegroups.has_key(key):
01511                 storagegroups[key] = list(storagegroups[u'mythvideo'])        # Set fall back
01512 
01513         # Use Storage Groups as the priority but append any FE directory settings that
01514         # are local to this BE but are not already used as a storage group
01515         if storagegroups.has_key(u'mythvideo'):
01516             for key in storagegroups.keys():
01517                 self.config[key] = list(storagegroups[key])
01518                 for k in self.config['localpaths'][key]:
01519                     if not k in self.config[key]:
01520                         self.config[key].append(k)    # Add any FE settings local directories not already included
01521                     else:
01522                         if key == 'mythvideo':
01523                             sys.stdout.write(u"\n! Warning: You have a front end video directory path that is a duplicate of this backend's 'Videos' storage group.\nFront end directory (%s)\nThe Front end setting has been ignored.\nThis Front end video directory will cause duplicate entires in MythVideo.\n" % (k))
01524                         else:
01525                             sys.stdout.write(u"\n! Info: You have a front end directory path that is a duplicate of this backend's storage group.\nFront end directory (%s)\nThe Front end setting has been ignored.\n" % (k))
01526                     continue
01527 
01528         # Make sure there is a directory set for Videos and other graphics directories on this host
01529         exists = True
01530         for key in dir_dict.keys():
01531             if key == 'episodeimagedir': # Jamu does nothing with Screenshots
01532                 continue
01533             # The fall back graphics SG is the Videos SG directory as of changeset 22104
01534             if storagegroups.has_key(u'mythvideo') and not len(self.config[key]):
01535                 self.config[key] = storagegroups[u'mythvideo']
01536             if not len(self.config[key]):
01537                 sys.stderr.write(u"\n! Error: There must be a directory for Videos and each graphic type. The (%s) directory is missing.\n" % (key))
01538                 exists = False
01539         if not exists:
01540             sys.exit(1)
01541 
01542         # Make sure that the directory set for Videos and other graphics directories have the proper permissions
01543         accessable = True
01544         for key in dir_dict.keys():
01545             for directory in self.config[key]:
01546                 if key == 'episodeimagedir': # Jamu does nothing with Screenshots
01547                     continue
01548                 if key == 'mythvideo':
01549                     if not os.access(directory, os.F_OK | os.R_OK):
01550                         sys.stderr.write(u"\n! Error: This video directory must have read access for Jamu to function.\nThere is a permissions issue with (%s).\n" % (directory, ))
01551                         accessable = False
01552                     continue
01553                 if not os.access(directory, os.F_OK | os.R_OK | os.W_OK):
01554                     sys.stderr.write(u"\n! Error: The (%s) directory (%s) must be read/writable for Jamu to function.\n" % (key, directory, ))
01555                     accessable = False
01556         if not accessable:
01557             sys.exit(1)
01558 
01559         # Print out the video and image directories that will be used for processing
01560         if self.config['mythtv_verbose']:
01561             dir_types={'posterdir': "Cover art  ", 'bannerdir': 'Banners    ', 'fanartdir': 'Fan art    ', 'episodeimagedir': 'Screenshots', 'mythvideo': 'Video      '}
01562             sys.stdout.write(u"\n==========================================================================================\n")
01563             sys.stdout.write(u"Listed below are the types and base directories Jamu will use for processing.\nThe list reflects your current configuration for the '%s' back end\nand whether a directory is a 'SG' (storage group) or not.\n" % localhostname)
01564             sys.stdout.write(u"Note: All directories are from settings in the MythDB specific to hostname (%s).\n" % localhostname)
01565             sys.stdout.write(u"Note: Screenshot directories are not listed as Jamu does not process Screenshots.\n")
01566             sys.stdout.write(u"------------------------------------------------------------------------------------------\n")
01567             for key in dir_dict.keys():
01568                 if key == 'episodeimagedir':
01569                     continue
01570                 for directory in self.config[key]:
01571                     sg_flag = 'NO '
01572                     if storagegroups.has_key(key):
01573                         if directory in storagegroups[key]:
01574                             sg_flag = 'YES'
01575                     sys.stdout.write(u"Type: %s - SG-%s - Directory: (%s)\n" % (dir_types[key], sg_flag, directory))
01576             sys.stdout.write(u"------------------------------------------------------------------------------------------\n")
01577             sys.stdout.write(u"If a directory you set from a separate Front end is not displayed it means\nthat the directory is not accessible from this backend OR\nyou must add the missing directories using the Front end on this Back end.\nFront end settings are host machine specific.\n")
01578             sys.stdout.write(u"==========================================================================================\n\n")
01579 
01580         if self.config[u'file_move_flag']:    # verify the destination directory in a move is read/writable
01581             index = 0
01582             accessable = True
01583             for arg in self.args:
01584                 if index % 2 == 0:
01585                     index+=1
01586                     continue
01587                 if not os.access(arg, os.F_OK):
01588                     for dirct in self.config['mythvideo']:
01589                         if arg.startswith(dirct):
01590                             if not os.access(dirct, os.F_OK | os.R_OK | os.W_OK):
01591                                 sys.stderr.write(u"! Error: Your move destination root MythVideo directory (%s) must be read/writable for Jamu to function.\n\n" % (dirct, ))
01592                                 accessable = False
01593                             break
01594                     else:
01595                         sys.stderr.write(u"! Error: Your move destination directory (%s) must be a MythVideo directory OR a subdirectory of a MythVideo directory.\n\n" % (arg, ))
01596                         accessable = False
01597                 elif not os.access(arg, os.F_OK | os.R_OK | os.W_OK):
01598                     sys.stderr.write(u"! Error: Your move destination directory (%s) must be read/writable for Jamu to function.\n\n" % (arg, ))
01599                     accessable = False
01600                 index+=1
01601             if not accessable:
01602                 sys.exit(1)
01603 
01604         # Check if any Video files are on a NFS shares
01605         if not self.config['mythtvNFS']:    # Maybe the NFS check is to be skipped
01606             if self._checkNFS(self.config['mythvideo'], self.config['video_file_exts']):
01607                 sys.stderr.write(u"\n! Error: Your video files reside on a NFS mount.\nIn the case where you have more than one MythTV backend using the same directories to store either video files\nor graphics any Jamu's option (-M) can adversly effect your MythTV database by mistakenly adding videos\nfor other backends or with the Janitor (-J) option mistakenly remove graphics files.\n\nIf you only have one backend or do not mix the Video or graphic file directories between backends and still want to use\nJamu add the options (N) to your option string e.g. (-MJN), which will skip this check.\n\n")
01608                 sys.exit(1)
01609     # end _getMythtvDirectories
01610 
01611 
01612     def _JanitorConflicts(self):
01613         '''Verify that there are no conflict between the graphics directories of MythVideo and
01614         other MythTV plugins. Write an warning message if a conflict is found.
01615         return True when there is a conflict
01616         return False when there is no conflict
01617         '''
01618         # Except for the plugins below no other plugins have non-theme graphics
01619         # MythGallery:
01620         #     Table 'settings' fields 'GalleryDir', 'GalleryImportDirs', 'GalleryThumbnailLocation'
01621         # MythGame:
01622         #    Table 'settings' fields 'mythgame.screenshotDir', 'mythgame.fanartDir', 'mythgame.boxartDir'
01623         # MythMusic:
01624         #    Table 'settings' fields 'MusicLocation'
01625         global graphicsDirectories, localhostname
01626         tablefields = ['GalleryDir', 'GalleryImportDirs', 'GalleryThumbnailLocation', 'mythgame.screenshotDir', 'mythgame.fanartDir', 'mythgame.boxartDir', 'MusicLocation', 'ScreenShotPath']
01627         returnvalue = False    # Initalize as no conflicts
01628         for field in tablefields:
01629             tmp_setting = mythdb.settings[localhostname][field]
01630             if not tmp_setting:
01631                 continue
01632             settings = tmp_setting.split(':')    # Account for multiple dirs per setting
01633             if not len(settings):
01634                 continue
01635             for setting in settings:
01636                 for directory in graphicsDirectories.keys():
01637                     if not self.config[graphicsDirectories[directory]]:
01638                         continue
01639                     # As the Janitor processes subdirectories matching must be a starts with check
01640                     for direc in self.config[graphicsDirectories[directory]]:
01641                         if os.path.realpath(setting).startswith(os.path.realpath(direc)):
01642                             sys.stderr.write(u"\n! Error - The (%s) directory (%s) conflicts\nwith the MythVideo (%s) directory (%s).\nThe Jamu Janitor (-MJ) option cannot be used.\n\n" % (field, setting, direc, self.config[graphicsDirectories[directory]]) )
01643                             returnvalue = True
01644         return returnvalue
01645     # end _JanitorConflicts
01646 
01647 
01648     def _addMythtvUserFileTypes(self):
01649         """Add video file types to the jamu list from the "videotypes" table
01650         """
01651         # Get videotypes table field names:
01652         try:
01653             records = VideoTypes.getAllEntries(mythdb)
01654         except MythError, e:
01655             sys.stderr.write(u"\n! Error: Reading videotypes MythTV table: %s\n" % e.args[0])
01656             return False
01657 
01658         for record in records:
01659             # Remove any extentions that are in Jamu's list but the user wants ignore
01660             if record.f_ignore:
01661                 if record.extension in self.config['video_file_exts']:
01662                     self.config['video_file_exts'].remove(record.extension)
01663                 if record.extension.lower() in self.config['video_file_exts']:
01664                     self.config['video_file_exts'].remove(record.extension.lower())
01665             else: # Add extentions that are not in the Jamu list
01666                 if not record.extension in self.config['video_file_exts']:
01667                     self.config['video_file_exts'].append(record.extension)
01668         # Make sure that all video file extensions are lower case
01669         for index in range(len(self.config['video_file_exts'])):
01670             self.config['video_file_exts'][index] = self.config['video_file_exts'][index].lower()
01671     # end _addMythtvUserFileTypes()
01672 
01673 
01674     def validate_setVariables(self, args):
01675         """Validate the contents of specific configuration variables
01676         return False and exit the script if an invalid configuation value is found
01677         """
01678         # Fix all variables which were changed by a users configuration files
01679         # to 'None', 'False' and 'True' literals back to their intended values
01680         keys=self.config.keys()
01681         types={'None': None, 'False': False, 'True': True}
01682         for key in keys:
01683             for literal in types.keys():
01684                 if self.config[key] == literal:
01685                     self.config[key] = types[literal]
01686 
01687         # Compile regex strings to parse folder names for TV series title, season and episode numbers
01688         self.config['fullname_parse'] = []
01689         for index in range(len(self.config['fullname_parse_regex'])):
01690             self.config['fullname_parse'].append(re.compile(self.config['fullname_parse_regex'][index] % self.config['fullname_parse_season_episode_translation'], re.UNICODE))
01691 
01692         if self.config['mythtvmeta']:
01693             if mythdb == None or mythvideo == None:
01694                 sys.stderr.write(u"\n! Error: The MythTV python interface is not installed or Cannot connect to MythTV Backend. MythTV meta data cannot be updated\n\n")
01695                 sys.exit(1)
01696             try:
01697                 import Image
01698                 self.config['image_library'] = Image
01699             except Exception, e:
01700                 sys.stderr.write(u"""\n! Error: Python Imaging Library is required for figuring out the sizes of
01701 the fetched poster images.
01702 
01703 In Debian/Ubuntu it is packaged as 'python-imaging'.
01704 http://www.pythonware.com/products/pil/\nError:(%s)\n""" % e)
01705                 sys.exit(1)
01706 
01707         if not _can_int(self.config['min_poster_size']):
01708             sys.stderr.write(u"\n! Error: The poster minimum value must be an integer (%s)\n" % self.config['min_poster_size'])
01709             sys.exit(1)
01710         else:
01711             self.config['min_poster_size'] = int(self.config['min_poster_size'])
01712 
01713         if self.config['maximum'] != None:
01714             if _can_int(self.config['maximum']) == False:
01715                 sys.stderr.write(u"\n! Error: Maximum option is not an integer (%s)\n" % self.config['maximum'])
01716                 sys.exit(1)
01717 
01718         # Detect if this is a move request
01719         self.config[u'file_move_flag'] = False
01720         if len(args) != 0:
01721             if os.path.isfile(args[0]) or os.path.isdir(args[0]) or args[0][-1:] == '*':
01722                 self.config[u'file_move_flag'] = True
01723                 self.args = list(args)
01724 
01725         if self.config['mythtvdir']:
01726             if mythdb == None or mythvideo == None:
01727                 sys.stderr.write(u"\n! Error: MythTV python interface is not available\n")
01728                 sys.exit(1)
01729         if self.config['mythtvdir'] or self.config['mythtvmeta']:
01730             self._addMythtvUserFileTypes() # add user filetypes from the "videotypes" table
01731             self._getMythtvDirectories()
01732         if self.config['mythtvjanitor']: # Check for graphic directory conflicts with other plugins
01733             if self._JanitorConflicts():
01734                 sys.exit(1)
01735             if not self.config['mythtvNFS']:
01736                 global graphicsDirectories, image_extensions
01737                 dirs = []
01738                 for key in graphicsDirectories:
01739                     if key != u'screenshot':
01740                         for directory in self.config[graphicsDirectories[key]]:
01741                             dirs.append(directory)
01742                 # Check if any Graphics files are on NFS shares
01743                 if self._checkNFS(dirs, image_extensions):
01744                     sys.stderr.write(u"\n! Error: Your metadata graphics reside on a NFS mount.\nIn the case where you have more than one MythTV backend using the same directories to store your graphics\nthe Jamu's Janitor option (-MJ) will be destructive removing graphics used by the other backend(s).\n\nIf you only have one backend or do not mix the graphics directories between backends and still want to use\nJamu's Janitor use the options (-MJN) which will skip this check.\n\n")
01745                     sys.exit(1)
01746 
01747         if self.config['posterresize'] != False or self.config['fanartresize'] != False:
01748             if _useImageMagick("-version"):
01749                 sys.stderr.write(u"\n! Error: ImageMagick is not installed, graphics cannot be resized. posterresize(%s), fanartresize(%s)\n" % (str(self.config['posterresize']), str(self.config['fanartresize'])))
01750                 sys.exit(1)
01751 
01752         if self.config['mythtvmeta'] and len(args) == 0:
01753             args=['']
01754 
01755         if len(args) == 0:
01756             sys.stderr.write(u"\n! Error: At least a video directory, SID or season name must be supplied\n")
01757             sys.exit(1)
01758 
01759         if os.path.isfile(args[0]) or os.path.isdir(args[0]) or args[0][-1:] == '*':
01760             self.config['video_dir'] = []
01761             for arg in args:
01762                 self.config['video_dir'].append(unicode(arg,'utf8'))
01763         elif not self.config['mythtvmeta']:
01764             if _can_int(args[0]) and len(args[0]) >= 5:
01765                 self.config['sid'] = unicode(args[0], 'utf8') # There is still a chance that this is a series name "90210"
01766             else:
01767                 if self.config['series_name_override']:
01768                     if self.config['series_name_override'].has_key(args[0].lower()):
01769                         self.config['sid'] = unicode((self.config['series_name_override'][args[0].lower()]).strip(), 'utf8')
01770                     else:
01771                         self.config['series_name'] = unicode(args[0].strip(), 'utf8')
01772                 else:
01773                     self.config['series_name'] = unicode(args[0].strip(), 'utf8')
01774             if len(args) != 1:
01775                 if len(args) > 3:
01776                     sys.stderr.write("\n! Error: Too many arguments (%d), maximum is three.\n" % len(args))
01777                     print "! args:", args
01778                     sys.exit(1)
01779                 if len(args) == 3 and _can_int(args[1]) and _can_int(args[2]):
01780                     self.config['season_num'] = args[1]
01781                     self.config['episode_num'] = args[2]
01782                 elif len(args) == 3:
01783                     sys.stderr.write(u"\n! Error: Season name(%s), season number(%s), episode number (%s) combination is invalid\n" % (args[0], args[1], args[2]))
01784                     sys.exit(1)
01785                 elif len(args) == 2 and _can_int(args[1]):
01786                     self.config['season_num'] = args[1]
01787                 else:
01788                     if self.config['ep_name_massage']:
01789                         if self.config['ep_name_massage'].has_key(self.config['series_name']):
01790                             tmp_ep_name=args[1].strip()
01791                             tmp_array=self.config['ep_name_massage'][self.config['series_name']]
01792                             for pair in tmp_array:
01793                                 tmp_ep_name = tmp_ep_name.replace(pair[0],pair[1])
01794                             self.config['episode_name'] = unicode(tmp_ep_name, 'utf8')
01795                         else:
01796                             self.config['episode_name'] = unicode(args[1].strip(), 'utf8')
01797                     else:
01798                         self.config['episode_name'] = unicode(args[1].strip(), 'utf8')
01799 
01800         # List of language from http://www.thetvdb.com/api/0629B785CE550C8D/languages.xml
01801         # Hard-coded here as it is realtively static, and saves another HTTP request, as
01802         # recommended on http://thetvdb.com/wiki/index.php/API:languages.xml
01803         valid_languages = ["da", "fi", "nl", "de", "it", "es", "fr","pl", "hu","el","tr", "ru","he","ja","pt","zh","cs","sl", "hr","ko","en","sv","no"]
01804 
01805         # Validate language as specified by user
01806         if self.config['local_language']:
01807             if not self.config['local_language'] in valid_languages:
01808                 valid_langs = ''
01809                 for lang in valid_languages: valid_langs+= lang+', '
01810                 valid_langs=valid_langs[:-2]
01811                 sys.stderr.write(u"\n! Error: Specified language(%s) must match one of the following languages supported by thetvdb.com wiki:\n (%s)\n" % (self.config['local_language'], valid_langs))
01812                 sys.exit(1)
01813         global UI_search_language
01814         UI_search_language = self.config['local_language']
01815 
01816         if self.config['data_flags']:
01817             for data_type in self.config['data_flags']:
01818                 if self.data_flags_table.has_key(data_type):
01819                     self.config[self.data_flags_table[data_type]]=True
01820     # end validate_setVariables
01821 
01822     def __repr__(self):
01823         """Return a copy of the configuration variables
01824         """
01825         return self.config
01826     #end __repr__
01827 # end class Configuration
01828 
01829 
01830 class Tvdatabase(object):
01831     """Process direct thetvdb.com requests
01832     """
01833     def __init__(self, configuration):
01834         """Retrieve all configuration options and get an instance of tvdb_api which is used to
01835         access thetvdb.com wiki.
01836         """
01837         self.config = configuration
01838         cache_dir=u"/tmp/tvdb_api_%s/" % os.geteuid()
01839         if self.config['interactive']:
01840             self.config['tvdb_api'] = tvdb_api.Tvdb(banners=True, debug=self.config['debug_enabled'], interactive=True,  select_first=False, cache=cache_dir, actors = True, language = self.config['local_language'], custom_ui=jamu_ConsoleUI, apikey="0BB856A59C51D607")  # thetvdb.com API key requested by MythTV)
01841         else:
01842             self.config['tvdb_api'] = tvdb_api.Tvdb(banners=True, debug = self.config['debug_enabled'], cache = cache_dir, actors = True, language = self.config['local_language'], apikey="0BB856A59C51D607")  # thetvdb.com API key requested by MythTV)
01843 
01844     # Local variables
01845     # High level dictionay keys for select graphics URL(s)
01846     fanart_key=u'fanart'
01847     banner_key=u'series'
01848     poster_key=u'poster'
01849     season_key=u'season'
01850     # Lower level dictionay keys for select graphics URL(s)
01851     poster_series_key=u'680x1000'
01852     poster_season_key=u'season'
01853     fanart_hires_key=u'1920x1080'
01854     fanart_lowres_key=u'1280x720'
01855     banner_series_key=u'graphical'
01856     banner_season_key=u'seasonwide'
01857     # Type of graphics being requested
01858     poster_type=u'poster'
01859     fanart_type=u'fanart'
01860     banner_type=u'banner'
01861     ep_image_type=u'filename'
01862 
01863     def sanitiseFileName(self, name):
01864         '''Take a file name and change it so that invalid or problematic characters are substituted with a "_"
01865         return a sanitised valid file name
01866         '''
01867         if name == None or name == u'':
01868             return u'_'
01869         for char in self.config['filename_char_filter']:
01870             name = name.replace(char, u'_')
01871         if name[0] == u'.':
01872             name = u'_'+name[1:]
01873         return name
01874     # end sanitiseFileName()
01875 
01876 
01877     def _getSeriesBySid(self, sid):
01878         """Lookup a series via it's sid
01879         return tvdb_api Show instance
01880         """
01881         seriesid = u'sid:' + sid
01882         if not self.corrections.has_key(seriesid):
01883             self._getShowData(sid)
01884             self.corrections[seriesid] = sid
01885         return self.shows[sid]
01886     tvdb_api.Tvdb.series_by_sid = _getSeriesBySid
01887     # end _getSeriesBySid
01888 
01889     def _searchforSeries(self, sid_or_name):
01890         """Get TV series data by sid or series name
01891         return None if the TV show was not found
01892         return an tvdb_api instance of the TV show data if it was found
01893         """
01894         if self.config['sid']:
01895             show = self.config['tvdb_api'].series_by_sid(self.config['sid'])
01896             if len(show) != 0:
01897                 self.config['series_name']=show[u'seriesname']
01898             return show
01899         else:
01900             if self.config['series_name_override']:
01901                 if self.config['series_name_override'].has_key(sid_or_name.lower()):
01902                     self.config['sid'] = (self.config['series_name_override'][sid_or_name.lower()])
01903                     show = self.config['tvdb_api'].series_by_sid(self.config['sid'])
01904                     if len(show) != 0:
01905                         self.config['series_name'] = show[u'seriesname']
01906                     return show
01907                 else:
01908                     show = self.config['tvdb_api'][sid_or_name]
01909                     if len(show) != 0:
01910                         self.config['series_name'] = show[u'seriesname']
01911                     return show
01912             else:
01913                 show = self.config['tvdb_api'][sid_or_name]
01914                 if len(show) != 0:
01915                     self.config['series_name'] = show[u'seriesname']
01916                 return show
01917     # end _searchforSeries
01918 
01919     def verifySeriesExists(self):
01920         """Verify that a:
01921         Series or
01922         Series and Season or
01923         Series and Season and Episode number or
01924         Series and Episode name
01925         passed by the user exists on thetvdb.com
01926         return False and display an appropriate error if the TV data was not found
01927         return an tvdb_api instance of the TV show/season/episode data if it was found
01928         """
01929         sid=self.config['sid']
01930         series_name=self.config['series_name']
01931         season=self.config['season_num']
01932         episode=self.config['episode_num']
01933         episode_name=self.config['episode_name']
01934         try:
01935             self.config['log'].debug(u'Checking for series(%s), sid(%s), season(%s), episode(%s), episode name(%s)' % (series_name, sid, season, episode, episode_name))
01936             if episode_name:        # Find an exact match for the series and episode name
01937                 series_sid=''
01938                 if sid:
01939                     seriesfound=self._searchforSeries(sid).search(episode_name)
01940                 else:
01941                     seriesfound=self._searchforSeries(series_name).search(episode_name)
01942                 if len(seriesfound) != 0:
01943                     for ep in seriesfound:
01944                         if ep['seriesid'] == '999999999':
01945                             self.config['sid'] = ep['seriesid']
01946                             return(ep)
01947                         if (ep['episodename'].lower()).startswith(episode_name.lower()):
01948                             if len(ep['episodename']) > (len(episode_name)+1):
01949                                 # Skip episodes the are not part of a set of (1), (2) ... etc
01950                                 if ep['episodename'][len(episode_name):len(episode_name)+2] != ' (':
01951                                     continue
01952                                 series_sid = ep['seriesid']
01953                                 self.config['sid'] = ep['seriesid']
01954                                 self.config['season_num'] = ep['seasonnumber']
01955                                 self.config['episode_num'] = ep['episodenumber']
01956                                 return(ep)
01957                             else: # Exact match
01958                                 series_sid = ep['seriesid']
01959                                 self.config['sid'] = ep['seriesid']
01960                                 self.config['season_num'] = ep['seasonnumber']
01961                                 self.config['episode_num'] = ep['episodenumber']
01962                                 return(ep)
01963                 raise tvdb_episodenotfound
01964             # Search for the series or series & season or series & season & episode
01965             elif season:
01966                 if episode:     # series & season & episode
01967                     seriesfound=self._searchforSeries(series_name)[int(season)][int(episode)]
01968                     if seriesfound['seriesid'] == '999999999':
01969                         return(seriesfound)
01970                     self.config['sid'] = seriesfound['seriesid']
01971                     self.config['episode_name'] = seriesfound['episodename']
01972                 else:                            # series & season
01973                     seriesfound=self._searchforSeries(series_name)[int(season)]
01974             else:
01975                 seriesfound=self._searchforSeries(series_name)    # Series only
01976         except tvdb_shownotfound:
01977             # No such show found.
01978             # Use the show-name from the files name, and None as the ep name
01979             if series_name:
01980                 sys.stderr.write(u"\n! Warning: Series (%s) not found\n" % (
01981                     series_name )
01982                 )
01983             else:
01984                 sys.stderr.write(u"\n! Warning: Series TVDB number (%s) not found\n" % (
01985                     sid )
01986                 )
01987             return(False)
01988         except (tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_attributenotfound):
01989             # The season, episode or name wasn't found, but the show was.
01990             # Use the corrected show-name, but no episode name.
01991             if series_name == None:
01992                 series_name = sid
01993             if episode:
01994                 sys.stderr.write(u"\n! Warning: For Series (%s), season (%s) or Episode (%s) not found \n"
01995                  % (series_name, season, episode )
01996                 )
01997             elif episode_name:
01998                 sys.stderr.write(u"\n! Warning: For Series (%s), Episode (%s) not found \n"
01999                  % (series_name, episode_name )
02000                 )
02001             else:
02002                 sys.stderr.write(u"\n! Warning: For Series (%s), season (%s) not found \n" % (
02003                     series_name, season)
02004                 )
02005             return(False)
02006         except tvdb_error, errormsg:
02007             # Error communicating with thetvdb.com
02008             if sid: # Maybe the 5 digit number was a series name (e.g. 90210)
02009                 self.config['series_name']=self.config['sid']
02010                 self.config['sid'] = None
02011                 return self.verifySeriesExists()
02012             sys.stderr.write(
02013                 u"\n! Warning: Error contacting www.thetvdb.com:\n%s\n" % (errormsg)
02014             )
02015             return(False)
02016         except tvdb_userabort, errormsg:
02017             # User aborted selection (q or ^c)
02018             print "\n", errormsg
02019             return(False)
02020         else:
02021             return(seriesfound)
02022     # end verifySeriesExists
02023 
02024     def _resizeGraphic(self, filename, resize):
02025         """Resize a graphics file
02026         return False and display an error message if the graphics resizing failed
02027         return True if the resize was succcessful
02028         """
02029         if self.config['simulation']:
02030             sys.stdout.write(
02031                 u"Simulation resize command (mogrify -resize %s %s)\n" % (resize, filename)
02032             )
02033             return(True)
02034         if _useImageMagick('-resize %s "%s"' % (resize, filename)):
02035             sys.stderr.write(
02036                 u'\n! Warning: Resizing failed command (mogrify -resize %s "%s")\n' % (resize, filename)
02037             )
02038             return(False)
02039         return True
02040     # end _resizeGraphic
02041 
02042     def _downloadURL(self, url, OutputFileName):
02043         """Download the specified graphic file from a URL
02044         return False if no file was downloaded
02045         return True if a file was successfully downloaded
02046         """
02047         # Only download a file if it does not exist or the option overwrite is selected
02048         if not self.config['overwrite'] and os.path.isfile(OutputFileName):
02049             return False
02050 
02051         if self.config['simulation']:
02052             sys.stdout.write(
02053                 u"Simulation download of URL(%s) to File(%s)\n" % (url, OutputFileName)
02054             )
02055             return(True)
02056 
02057         org_url = url
02058         tmp_URL = url.replace("http://", "")
02059         url = "http://"+urllib.quote(tmp_URL.encode("utf-8"))
02060 
02061         try:
02062             dat = urllib.urlopen(url).read()
02063         except IOError, e:
02064             sys.stderr.write( u"\n! Warning: Download IOError on URL for Filename(%s)\nOrginal URL(%s)\nIOError urllib.quote URL(%s)\nError:(%s)\n" % (OutputFileName, org_url, url, e))
02065             return False
02066 
02067         try:
02068             target_socket = open(OutputFileName, "wb")
02069             target_socket.write(dat)
02070             target_socket.close()
02071         except IOError, e:
02072             sys.stderr.write( u"\n! Warning: Download IOError for Filename(%s), may be the directory is invalid\nError:(%s)\n" % (OutputFileName, e))
02073             return False
02074 
02075         # Verify that the downloaded file was NOT HTML instead of the intended file
02076         try:
02077             p = subprocess.Popen(u'file "%s"' % OutputFileName, shell=True, bufsize=4096, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
02078         except Exception, e:
02079             sys.stderr.write( u"\n! Warning: Download Exception for Filename(%s)\nError:(%s)\n" % (OutputFileName, e))
02080             return False
02081         except:
02082             return False
02083         data = p.stdout.readline()
02084         try:
02085             data = data.encode('utf8')
02086         except UnicodeDecodeError:
02087             data = unicode(data,'utf8')
02088         index = data.find(u'HTML document text')
02089         if index == -1:
02090             return True
02091         else:
02092             os.remove(OutputFileName) # Delete the useless HTML text
02093             if self.config['mythtv_verbose']:
02094                 sys.stderr.write( u"\n! Warning: The web site may be having issues.\nURL (%s)\nReturned a file containing HTML\n(%s).\nThe bad downloaded file was removed.\n" % (url, OutputFileName))
02095             return False
02096     # end _downloadURL
02097 
02098     def _setGraphicsFileNameFormat(self):
02099         """Return a file name format (e.g. seriesname - episode name.extention)
02100         return a filename format string
02101         """
02102         if self.config['g_defaultname']:
02103             return u'%(url)s.%(ext)s'
02104         cfile={}
02105         cfile['seriesid']=self.config['sid']
02106         cfile['series'] = self.sanitiseFileName(self.config['series_name'])
02107         if cfile['series'] != self.config['series_name']:
02108             self.config['g_series'] = self.config['g_series'].replace(self.config['series_name'], cfile['series'])
02109         if self.config['season_num']:
02110             cfile['seasonnumber']=int(self.config['season_num'])
02111         else:
02112             cfile['seasonnumber']=0
02113         if self.config['episode_num']:
02114             cfile['episodenumber']=int(self.config['episode_num'])
02115         else:
02116             cfile['episodenumber']=0
02117         cfile['episodename']=self.config['episode_name']
02118         cfile['seq']=u'%(seq)02d'
02119         cfile['ext']=u'%(ext)s'
02120 
02121         if self.config['season_num']:
02122             return self.config['g_season'] % cfile
02123 
02124         return self.config['g_series'] % cfile
02125     # end _setGraphicsFileNameFormat
02126 
02127     def _downloadGraphics(self, urls, mythtv=False):
02128         """Download graphic file(s) from a URL list (string of one or more URLs separated by a CR
02129         character)
02130         return None   is the string of urls has no urls
02131         return False  if the any of the urls are corrupt
02132         return file name of the LAST file downloaded (special for MythTV data base updates)
02133         """
02134         global graphicsDirectories
02135 
02136         if urls == None: return None
02137         if urls == '': return None
02138         tmp_list=urls.split('\n')
02139         url_list=[]
02140         for x in tmp_list:
02141             x = x.rstrip()
02142             if x != '':
02143                 url_list.append(x)
02144         if not len(url_list):
02145             return None            # There were no URLs in the list
02146         url_dict={}
02147         for x in url_list:
02148             try:
02149                 self.config['log'].debug(u'Checking for a key in (%s)' % (x))
02150                 i = x.index(':')
02151             except:
02152                 sys.stderr.write(
02153                     u"\n! Warning: URL list does not have a graphics type key(%s)\n" % (x)
02154                 )
02155                 return(False)
02156             if url_dict.has_key(x[:i]):
02157                 temp_array = [x[i+1:],'']
02158                 url_dict[x[:i]].append(temp_array)# Collect a list of the same graphics type of URLs
02159             else: # The first URL of a new graphics type. Also URL replacement code left in place just in case
02160                 url_dict[x[:i]]=[[(x[i+1:]).replace(u"http://www.thetvdb.com",u"http://www.thetvdb.com"),'']]
02161 
02162         unique_dir={u'poster': ['posterdir', True], u'banner': ['bannerdir', True], u'fanart': ['fanartdir', True], u'filename': ['episodeimagedir', True]}
02163         # If a graphics directory was not specified then default to the 'allgraphics' directory
02164         if not self.config['posterdir']: self.config['posterdir'] = self.config['allgraphicsdir']
02165         if not self.config['bannerdir']: self.config['bannerdir'] = self.config['allgraphicsdir']
02166         if not self.config['fanartdir']: self.config['fanartdir'] = self.config['allgraphicsdir']
02167         if not self.config['episodeimagedir']: self.config['episodeimagedir'] = self.config['allgraphicsdir']
02168 
02169         # Check if any of the downloaded graphics will share the same directory
02170         for key in unique_dir.keys():
02171             for k in unique_dir.keys():
02172                 if key != k:
02173                     if self.config[unique_dir[key][0]] == self.config[unique_dir[k][0]]:
02174                         unique_dir[key][1] = False
02175                         break
02176 
02177         dirs={u'poster': self.config['posterdir'], u'banner': self.config['bannerdir'],
02178             u'fanart': self.config['fanartdir'], u'filename': self.config['episodeimagedir']}
02179 
02180         # Figure out filenaming convention
02181         file_format = self._setGraphicsFileNameFormat()
02182 
02183         # Set the graphics fully qualified filenames matched to a URL
02184         for URLtype in url_dict:
02185             if mythtv:
02186                 if self.absolutepath:
02187                     if URLtype == 'poster':
02188                         tmpgraphicdir = graphicsDirectories['coverfile']
02189                     else:
02190                         tmpgraphicdir = graphicsDirectories[URLtype]
02191                     if not len(self.config['localpaths'][tmpgraphicdir]):
02192                         return None
02193                     else:
02194                         directory = self.config['localpaths'][tmpgraphicdir][0]
02195                 else:
02196                     directory = dirs[URLtype][0]
02197             else:
02198                 directory = dirs[URLtype]
02199             seq_num = 0
02200             for url in url_dict[URLtype]:
02201                 (dirName, fileName) = os.path.split(url[0])
02202                 (fileBaseName, fileExtension)=os.path.splitext(fileName)
02203                 fileBaseName = self.sanitiseFileName(fileBaseName)
02204                 # Fix file extentions in all caps or 4 character JPEG extentions
02205                 fileExtension = fileExtension.lower()
02206                 if fileExtension == '.jpeg':
02207                     fileExtension = '.jpg'
02208                 cfile={u'url': fileBaseName, u'seq': seq_num, u'ext': fileExtension[1:]}
02209                 if not isValidPosixFilename(self.config['series_name']):
02210                     if file_format.startswith(self.config['series_name']):
02211                         file_format = file_format.replace(self.config['series_name'], self.sanitiseFileName(self.config['series_name']))
02212                 cfile['series'] = self.sanitiseFileName(self.config['series_name'])
02213                 cfile['seriesid'] = self.config['sid']
02214 
02215                 if URLtype != 'filename':
02216                     if unique_dir[URLtype][1]:
02217                         url_dict[URLtype][seq_num][1] = directory+'/'+file_format % cfile
02218                     else:
02219                         if mythtv:
02220                             url_dict[URLtype][seq_num][1] = directory+'/'+file_format % cfile
02221                         else:
02222                             url_dict[URLtype][seq_num][1] = directory+'/'+URLtype.capitalize()+' - '+file_format % cfile
02223                 else:
02224                     if self.config['season_num']:
02225                         cfile['seasonnumber']=int(self.config['season_num'])
02226                     else:
02227                         cfile['seasonnumber'] = 0
02228                     if self.config['episode_num']:
02229                         cfile['episodenumber']=int(self.config['episode_num'])
02230                     else:
02231                         cfile['episodenumber'] = 0
02232                     cfile['episodename'] = self.sanitiseFileName(self.config['episode_name'])
02233                     url_dict[URLtype][seq_num][1] = directory+'/'+self.config['ep_metadata'] % cfile
02234                 seq_num+=1
02235 
02236         # Download the graphics and resize if requested - Ignore download or resize issues!
02237         failed_download = False
02238         for URLtype in url_dict:
02239             seq_num=0
02240             for pairs in url_dict[URLtype]:
02241                 if self._downloadURL(pairs[0], pairs[1]):
02242                     if URLtype == u'poster' and self.config['posterresize']:
02243                         self._resizeGraphic(pairs[1], self.config['posterresize'])
02244                     elif URLtype == u'fanart' and self.config['fanartresize']:
02245                         self._resizeGraphic(pairs[1], self.config['fanartresize'])
02246                 elif not os.path.isfile(pairs[1]): # Check if the file already was downloaded
02247                     failed_download = True # The download failed
02248                     if self.config['mythtv_verbose']:
02249                         sys.stderr.write(u'\nA graphics file failed to be downloaded. A file issue or a corrupt (HTML) file.(%s)\n' % pairs[1])
02250                 seq_num+=1
02251                 if self.config['maximum']:    # Has the maximum number of graphics been downloaded?
02252                     if seq_num == int(self.config['maximum']):
02253                         break
02254         if failed_download:
02255             return None
02256         else:
02257             return pairs[1]                # The name of the LAST graphics successfully downloaded
02258     # end _downloadGraphics
02259 
02260     def getGraphics(self, graphics_type):
02261         """Retrieve Poster or Fan Art or Banner or Episode image graphics URL(s)
02262         return None if no graphics URLs were found
02263         return a string of URLs
02264         """
02265         banners=u'_banners'
02266         series_name=self.config['series_name']
02267         season=self.config['season_num']
02268         episode=self.config['episode_num']
02269         episode_name=self.config['episode_name']
02270         lang=self.config['local_language']
02271         graphics=[]
02272 
02273         try:
02274             if self.config['sid']:
02275                 URLs = self.config['tvdb_api'].ttvdb_parseBanners(self.config['sid'])
02276             else:
02277                 URLs = self.config['tvdb_api'].ttvdb_parseBanners(self.config['tvdb_api']._nameToSid(series_name))
02278         except Exception, e:
02279             return None
02280 
02281         if graphics_type == self.fanart_type: # Series fanart graphics
02282             if not len(URLs[u'fanart']):
02283                 return None
02284             for url in URLs[u'fanart']:
02285                 graphics.append(url)
02286         elif season == None and episode == None and episode_name == None:
02287             if not len(URLs[u'series']):
02288                 return None
02289             if graphics_type == self.banner_type: # Series Banners
02290                 for url in URLs[u'series']:
02291                     graphics.append(url)
02292             else: # Series Posters
02293                 for url in URLs[u'poster']:
02294                     graphics.append(url)
02295         else:
02296             if not len(URLs[u'season']):
02297                 return None
02298             if graphics_type == self.banner_type: # Season Banners
02299                 season_banners=[]
02300                 for url in URLs[u'season']:
02301                     if url[u'bannertype2'] == u'seasonwide' and url[u'season'] == season:
02302                         season_banners.append(url)
02303                 if not len(season_banners):
02304                     return None
02305                 graphics = season_banners
02306             else: # Season Posters
02307                 season_posters=[]
02308                 for url in URLs[u'season']:
02309                     if url[u'bannertype2'] == u'season' and url[u'season'] == season:
02310                         season_posters.append(url)
02311                 if not len(season_posters):
02312                     return None
02313                 graphics = season_posters
02314 
02315         graphicsURLs=u''
02316         if self.config['nokeys'] and not self.config['download']:
02317             key_tag=u''
02318         else:
02319             key_tag=graphics_type+u':'
02320 
02321         count = 0
02322         wasanythingadded = 0
02323         anyotherlanguagegraphics=u''
02324         englishlanguagegraphics=u''
02325         for URL in graphics:
02326             if graphics_type == 'filename':
02327                 if URL[graphics_type] == None:
02328                     continue
02329             if lang:        # Is there a language to filter URLs on?
02330                 if lang == URL['language']:
02331                     if graphics_type != self.ep_image_type:
02332                         graphicsURLs+=key_tag+URL['_bannerpath']+'\n'
02333                     else:
02334                         graphicsURLs+=key_tag+URL[graphics_type]+'\n'
02335                 else: # Check for fall back graphics in case there are no selected language graphics
02336                     if u'en' == URL['language']:
02337                         if graphics_type != self.ep_image_type:
02338                             englishlanguagegraphics+=key_tag+URL['_bannerpath']+'\n'
02339                         else:
02340                             englishlanguagegraphics+=key_tag+URL[graphics_type]+'\n'
02341                     else:
02342                         if graphics_type != self.ep_image_type:
02343                             anyotherlanguagegraphics+=key_tag+URL['_bannerpath']+'\n'
02344                         else:
02345                             anyotherlanguagegraphics+=key_tag+URL[graphics_type]+'\n'
02346             else:
02347                 if graphics_type != self.ep_image_type:
02348                     graphicsURLs+=key_tag+URL['_bannerpath']+'\n'
02349                 else:
02350                     graphicsURLs+=key_tag+URL[graphics_type]+'\n'
02351             if wasanythingadded == len(graphicsURLs):
02352                 continue
02353             wasanythingadded = len(graphicsURLs)
02354             count+=1
02355             if self.config['maximum']:    # Has the maximum number of graphics been downloaded?
02356                 if count == int(self.config['maximum']):
02357                     break
02358 
02359         if not len(graphicsURLs):
02360             if len(englishlanguagegraphics): # Fall back to English graphics
02361                 graphicsURLs = englishlanguagegraphics
02362             elif len(anyotherlanguagegraphics):  # Fall back-back to any available graphics
02363                 graphicsURLs = anyotherlanguagegraphics
02364 
02365         if self.config['debug_enabled']:
02366             print "\nGraphics:\n", graphicsURLs
02367 
02368         if not len(graphicsURLs):            # Are there any graphics?
02369             return None
02370 
02371         if len(graphicsURLs) == 1 and graphicsURLs[0] == graphics_type+':':
02372             return None                        # Due to the language filter there may not be any URLs
02373 
02374         return(graphicsURLs)
02375     # end get_graphics
02376 
02377     def getTopRatedGraphics(self, graphics_type):
02378         """Retrieve only the top rated series Poster, Fan Art and Banner graphics URL(s)
02379         return None if no top rated graphics URLs were found
02380         return a string of top rated URLs
02381         """
02382         if graphics_type == u'filename':
02383             self.config['log'].debug(u'! There are no such thing as top rated Episode image URLs')
02384             return None
02385         toprated=None
02386         series_name=self.config['series_name']
02387         keys=self.config['nokeys']
02388         if self._searchforSeries(series_name)[graphics_type] != None:
02389             if keys and not self.config['download']:
02390                 toprated=(self._searchforSeries(series_name)[graphics_type])+'\n'
02391             else:
02392                 toprated=(u'%s:%s\n' % (graphics_type, self._searchforSeries(series_name)[graphics_type]))
02393         return toprated
02394     # end getTopRatedGraphics
02395 
02396     def _downloadEpisodeData(self,ep_data):
02397         """Down load episode meta data and episode image graphics
02398         return True whether or not there was episode data processed
02399         """
02400         if not len(ep_data):
02401             return True             # There were no episode data in the list
02402         ep_data_list=[]                # An array of episode meta data
02403 
02404         first_key=self.config['ep_include_data'][0]+':'
02405         key_size=len(first_key)
02406 
02407         while len(ep_data):            # Grab each episode's set of meta data
02408             try:
02409                 self.config['log'].debug(u'Parse out the episode data from an episode meta dats string')
02410                 end = ep_data[key_size:].index(first_key)
02411                 ep_data_list.append(ep_data[:end+key_size])
02412                 ep_data=ep_data[end+key_size:]
02413             except:
02414                 ep_data_list.append(ep_data)
02415                 break
02416 
02417         if not self.config['metadatadir']:
02418             self.config['metadatadir'] = os.getcwd()
02419 
02420         # Process each episode's meta data
02421         for episode in ep_data_list:
02422             tmp_data = episode.split('\n')
02423             for i in range(len(tmp_data)):
02424                 tmp_data[i] = tmp_data[i].rstrip()# Remove \n characters from the end of each record
02425             tmp_dict ={}
02426             for data in tmp_data:
02427                 try:
02428                     self.config['log'].debug(u'Checking for key in episode meta data')
02429                     tmp_dict[data[:data.index(':')]] = data[data.index(':')+1:]
02430                 except ValueError:
02431                     continue
02432             tmp_dict['ext']='meta'
02433             tmp_dict['seq']=0
02434             for key in ['seasonnumber', 'episodenumber']:
02435                 if tmp_dict.has_key(key):
02436                     tmp_dict[key] = int(tmp_dict[key])
02437             if not tmp_dict.has_key(u'episodename'):
02438                 tmp_dict[u'episodename'] = u''
02439             filename="%s/%s" % (self.config['metadatadir'],self.config['ep_metadata'] % tmp_dict)
02440             image_filename = None
02441             if     self.config['get_ep_image'] and tmp_dict.has_key('filename'):
02442                 url= tmp_dict['filename']
02443                 if url != 'None':
02444                     if not self.config['episodeimagedir']:
02445                         self.config['episodeimagedir'] = self.config['allgraphicsdir']
02446                     (dirName, fileName) = os.path.split(url)
02447                     (fileBaseName, fileExtension)=os.path.splitext(fileName)
02448                     tmp_dict[u'ext']=fileExtension[1:]
02449                     image_filename = "%s/%s" % (self.config['episodeimagedir'], self.config['ep_metadata'] % tmp_dict)
02450             # Only download a file if it does not exist or the option overwrite is selected
02451             # or the option update is selected and the local meta data file is
02452             # older than the episode data on thetvdb.com wiki
02453             outofdate = False
02454             if self.config['update'] and tmp_dict.has_key('lastupdated') and os.path.isfile(filename):
02455                 if  int(tmp_dict['lastupdated']) > int(os.path.getmtime(filename)):
02456                     outofdate= True
02457 
02458             if not self.config['overwrite'] and not outofdate:
02459                 if self.config['get_ep_meta'] and self.config['get_ep_image']:
02460                     if image_filename:
02461                         if os.path.isfile(filename) and os.path.isfile(image_filename):
02462                             continue
02463                     else:
02464                         if os.path.isfile(filename):
02465                             continue
02466                 elif self.config['get_ep_meta']:
02467                     if os.path.isfile(filename):
02468                         continue
02469                 elif self.config['get_ep_image'] and tmp_dict.has_key('filename'):
02470                     url= tmp_dict['filename']
02471                     if url != 'None':
02472                         if os.path.isfile(image_filename):
02473                             continue
02474                     else:
02475                         continue
02476                 else:
02477                     continue
02478 
02479             if self.config['simulation']:
02480                 if self.config['get_ep_image'] and tmp_dict.has_key('filename'):
02481                     self.config['log'].debug(u'Simulate downloading an episode image')
02482                     url= tmp_dict['filename']
02483                     if url != 'None':
02484                         sys.stdout.write(u"Simulation create episode image file(%s)\n" % image_filename)
02485                 if self.config['get_ep_meta']:
02486                     sys.stdout.write(
02487                         u"Simulation create meta data file(%s)\n" % filename
02488                     )
02489                 continue
02490 
02491             if self.config['get_ep_image'] and tmp_dict.has_key('filename'):
02492                 if tmp_dict['filename'] != 'None':
02493                     self._downloadGraphics('filename:'+tmp_dict['filename'])
02494 
02495             # Write out an episode meta data file
02496             if     self.config['get_ep_meta']:
02497                 fHandle = codecs.open(filename, 'w', 'utf8')
02498                 fHandle.write(episode)
02499                 fHandle.close()
02500 
02501         return True
02502     # end _downloadEpisodeData
02503 
02504     def _changeToCommas(self,meta_data):
02505         """Remove '|' and replace with commas
02506         return the modified text
02507         """
02508         if not meta_data: return meta_data
02509         meta_data = (u'|'.join([d for d in meta_data.split('| ') if d]))
02510         return (u', '.join([d for d in meta_data.split(u'|') if d]))
02511     # end _changeToCommas
02512 
02513     def _changeAmp(self, text):
02514         """Change &amp; values to ASCII equivalents
02515         return the modified text
02516         """
02517         if not text: return text
02518         text = text.replace("&quot;", u"'").replace("\r\n", u" ")
02519         text = text.replace(r"\'", u"'")
02520         return text
02521     # end _changeAmp
02522 
02523     def getSeriesEpisodeData(self):
02524         """Get Series Episode meta data. This can be one specific episode or all of a seasons episodes
02525         or all episodes for an entire series.
02526         return an empy sting of no episode meta data was found
02527         reurn a string containing key value pairs of episode meta data
02528         """
02529         sid=self.config['sid']
02530         series_name=self.config['series_name']
02531         season_num=self.config['season_num']
02532         episode_num=self.config['episode_num']
02533         episode_name=self.config['episode_name']
02534 
02535         # Get Cast members
02536         tmp_cast={}
02537         cast_members=''
02538         try:
02539             tmp_cast = self._searchforSeries(series_name)[u'_actors']
02540         except:
02541             cast_members=''
02542         if len(tmp_cast):
02543             cast_members=''
02544             for cast in tmp_cast:
02545                 if cast['name'] is not None:
02546                     cast_members+=(cast['name']+u', ').encode('utf8')
02547             if cast_members != '':
02548                 try:
02549                     cast_members = cast_members[:-2].encode('utf8')
02550                 except UnicodeDecodeError:
02551                     cast_members = unicode(cast_members[:-2],'utf8')
02552                 cast_members = self._changeAmp(cast_members)
02553                 cast_members = self._changeToCommas(cast_members)
02554                 cast_members=cast_members.replace('\n',' ')
02555 
02556         # Get genre(s)
02557         genres=''
02558         try:
02559             genres_string = self._searchforSeries(series_name)[u'genre'].encode('utf8')
02560         except:
02561             genres_string=''
02562         if genres_string != None and genres_string != '':
02563             genres = self._changeAmp(genres_string)
02564             genres = self._changeToCommas(genres)
02565 
02566         seasons=self._searchforSeries(series_name).keys()    # Get the seasons for this series
02567         episodes_metadata=u''
02568         for season in seasons:
02569             if season_num:                        # If a season was specified skip other seasons
02570                 if season != int(season_num):
02571                     continue
02572             episodes=self._searchforSeries(series_name)[season].keys()# Get the episodes for this season
02573             for episode in episodes:    # If an episode was specified skip other episodes
02574                 if episode_num:
02575                     if episode != int(episode_num):
02576                         continue
02577                 ep_data={}
02578                 if sid:                    # Ouput the full series name
02579                     try:
02580                         ep_data["series"]=self._searchforSeries(sid)[u'seriesname'].encode('utf8')
02581                     except AttributeError:
02582                         return u''
02583                 else:
02584                     try:
02585                         ep_data["series"]=self._searchforSeries(series_name)[u'seriesname'].encode('utf8')
02586                     except AttributeError:
02587                         return u''
02588                 available_keys=self._searchforSeries(series_name)[season][episode].keys()
02589                 tmp=u''
02590                 ep_data[u'gueststars']=''
02591                 for key in available_keys:
02592                     if self._searchforSeries(series_name)[season][episode][key] == None:
02593                         continue
02594                     # Massage meta data
02595                     text = self._searchforSeries(series_name)[season][episode][key]
02596                     text = self._changeAmp(text)
02597                     text = self._changeToCommas(text)
02598                     ep_data[key.lower()]=text.replace('\n',' ')
02599                 for key in self.config['ep_include_data']:    # Select and sort the required meta data
02600                     if ep_data.has_key(key):
02601                         if key == u'gueststars':
02602                             if ep_data[key] == '':
02603                                 tmp+=u'Cast:%s\n' % cast_members
02604                             else:
02605                                 if (len(ep_data[key]) > 128) and not ep_data[key].count(','):
02606                                     tmp+=u'Cast:%s\n' % cast_members
02607                                 else:
02608                                     tmp+=u'Cast:%s, %s\n' % (cast_members, ep_data[key])
02609                             continue
02610                         try:
02611                             tmp+=u'%s:%s\n' % (key, ep_data[key])
02612                         except UnicodeDecodeError:
02613                             tmp+=u'%s:%s\n' % (key, unicode(ep_data[key], "utf8"))
02614                 tmp+=u'Runtime:%s\n' % self._searchforSeries(series_name)[u'runtime']
02615                 if genres != '':
02616                     tmp+=u'Genres:%s\n' % genres
02617                 if len(tmp) > 0:
02618                     episodes_metadata+=tmp
02619         return episodes_metadata
02620     # end Getseries_episode_data
02621 
02622     def returnFilename(self):
02623         """Return a single file name (excluding file extension and directory), limited by the current
02624         variables (sid, season name, season number ... etc). Typically used when writing a meta file
02625         or naming/renaming a video file after a TV show recording.
02626         return False and out put an error if there not either a series id (SID) or series name
02627         return False and out put an error if there proper episode information (numbers or name)
02628         return False if the option (-MGF) used and there is not exact TV series name match
02629         return a specific episode filename
02630         """
02631         sid=self.config['sid']
02632         series_name=self.config['series_name']
02633         season_num=self.config['season_num']
02634         episode_num=self.config['episode_num']
02635         episode_name=self.config['episode_name']
02636 
02637         if not sid and not series_name:
02638             sys.stderr.write(
02639                 u"\n! Warning: There must be at least series name or SID to request a filename\n"
02640             )
02641             return False
02642 
02643         if season_num and episode_num:
02644             pass
02645         elif not episode_name:
02646             sys.stderr.write(
02647                 u'\n! Error: There must be at least "season and episode numbers" or "episode name" to request a filename\n'
02648             )
02649             sys.exit(1)
02650 
02651         # Special logic must be used if the (-MG) guessing option has been requested
02652         if not self.config['sid'] and self.config['mythtv_guess']:
02653             try:
02654                 allmatchingseries = self.config['tvdb_api']._getSeries(self.config['series_name'])
02655             except Exception, e:
02656                 sys.stderr.write(u"\nErrors while trying to contact thetvbd.com for Series (%s)\ntherefore a file rename is not possible. error(%s)\n\n" % (self.config['series_name'], e))
02657                 return False
02658             if filter(is_not_punct_char, allmatchingseries['name'].lower()) == filter(is_not_punct_char, self.config['series_name'].lower()):
02659                 self.config['sid'] = allmatchingseries['sid']
02660                 self.config['series_name'] = allmatchingseries['name']
02661             else:
02662                 sys.stderr.write(u"\nGuessing could not find an exact match on tvdb for Series (%s)\ntherefore a file rename is not possible.\n\n" % self.config['series_name'])
02663                 return False
02664 
02665         episode = self.verifySeriesExists()
02666 
02667         if not episode:            # Make sure an episode was found
02668             sys.stderr.write(
02669                 u'\n! Error: The episode was not found for series(%s), Episode name(%s)\n' % (series_name, episode_name)
02670             )
02671             sys.exit(1)
02672 
02673         sid=self.config['sid']
02674 
02675         if UI_selectedtitle and (self.config['mythtv_inetref'] or self.config['mythtv_ref_num']):
02676             self.config['series_name'] = UI_selectedtitle
02677 
02678         series_name=self.config['series_name']
02679         season_num=self.config['season_num']
02680         episode_num=self.config['episode_num']
02681         episode_name=self.config['episode_name']
02682 
02683         tmp_dict ={'series': series_name, 'seasonnumber': season_num, 'episodenumber': episode_num, 'episodename': episode_name, 'sid': sid    }
02684 
02685         tmp_dict['ext']=''
02686         for key in ['seasonnumber', 'episodenumber']:
02687             if tmp_dict.has_key(key):
02688                 tmp_dict[key] = int(tmp_dict[key])
02689 
02690         return self.sanitiseFileName(u"%s" % (self.config['ep_metadata'] % tmp_dict)[:-1])
02691     # end returnFilename
02692 
02693     def processTVdatabaseRequests(self):
02694         """Process the data/download requests as indicated by the variables
02695         return None if the series/season/episode does not exist
02696         return None if there is no data to process for the request actions
02697         return a string for display or further processing that satisfies the reqested actions
02698         """
02699         if self.verifySeriesExists():# Getting a filename is a single event nothing else is returned
02700             if self.config['ret_filename']:
02701                 return self.returnFilename()
02702         else:
02703             return None
02704 
02705         types={'get_fanart': self.fanart_type, 'get_poster': self.poster_type, 'get_banner': self.banner_type}
02706         if self.config['toprated']:
02707             typegetGraphics=self.getTopRatedGraphics
02708         else:
02709             typegetGraphics=self.getGraphics
02710         results=u''
02711         if self.verifySeriesExists():
02712             if self.config['download']:    # Deal only with graphics display or downloads
02713                 for key in types.keys():
02714                     if key == 'get_ep_image': # Ep image downloads processed below
02715                         continue
02716                     if self.config[key]:
02717                             if self._downloadGraphics(typegetGraphics(types[key])):
02718                                 sys.stdout.write(
02719                                     u"%s downloading successfully processed\n" % key.title()
02720                                 )
02721             else:
02722                 url_string=u''
02723                 for key in types.keys():
02724                     if self.config[key]:
02725                         string=typegetGraphics(types[key])
02726                         if string != None:
02727                             url_string+=string
02728                 if url_string != '':
02729                     results+=url_string    # Add graphic URLs to returned results
02730 
02731             # Should episode meta data or episode image be processed?
02732             if self.config['get_ep_meta'] or self.config['get_ep_image']:
02733                 if self.config['download']:    # Deal only with episode data display or download
02734                     if self._downloadEpisodeData(self.getSeriesEpisodeData()):
02735                         sys.stdout.write(
02736                             u"Episode meta data and/or images downloads successfully processed\n"
02737                         )
02738                 else:
02739                     eps_string = self.getSeriesEpisodeData()
02740                     if eps_string != '':
02741                         results+=eps_string    # Add episode meta data to returned results
02742         else:
02743             return None
02744 
02745         if results != u'':
02746             if results[len(results)-1] == '\n':
02747                 return results[:len(results)-1]
02748             else:
02749                 return results
02750         else:
02751             return None
02752     # end processTVdatabaseRequests
02753 
02754     def __repr__(self):    # Just a place holder
02755         return self.config
02756     # end __repr__
02757 
02758 # end Tvdatabase
02759 
02760 
02761 class VideoFiles(Tvdatabase):
02762     """Process all video file and/or directories containing video files. These TV Series video
02763     files must be named so that a "series name or sid" and/or "season and episode number"
02764     can be extracted from the video file name. It is best to have renamed the TV series video files with
02765     tvnamer before using these files with jamu. Any video file without season and episode numbers is
02766     assumed to be a movie.    Files that do not match the previously described criterion will be skipped.
02767     tvnamer can be found at:
02768     http://pypi.python.org/pypi?%3Aaction=search&term=tvnamer&submit=search
02769     """
02770     def __init__(self, configuration):
02771         """Retrieve the configuration options
02772         """
02773         super(VideoFiles, self).__init__(configuration)
02774     # end __init__
02775 
02776     image_extensions = ["png", "jpg", "bmp"]
02777 
02778     def _findFiles(self, args, recursive = False, verbose = False):
02779         """
02780         Takes a file name or folder path and grabs files inside them. Does not recurse
02781         more than one level (if a folder is supplied, it will list files within),
02782         unless recurse is True, in which case it will recursively find all files.
02783         return an array of file names
02784         """
02785         allfiles = []
02786 
02787         for cfile in args: # Directories must exist and be both readable and writable
02788             if os.path.isdir(cfile) and not os.access(cfile, os.F_OK | os.R_OK):
02789                 sys.stderr.write(u"\n! Error: Video directory (%s) does not exist or the permissions are not at least readable. Skipping this directory.\n" % (cfile))
02790                 continue
02791             ignore = False
02792             if os.path.isdir(cfile):
02793                 for directory in self.config['ignore-directory']: # ignore directory list
02794                     if not cfile.startswith(directory):
02795                         continue
02796                     ignore = True
02797             if ignore: # Skip this directory
02798                 continue
02799             if os.path.isdir(cfile):
02800                 index = cfile.find(u'VIDEO_TS')
02801                 if index != -1:
02802                     sys.stderr.write(u"\n! Warning: Jamu does not process multi-part video files, video directory (%s).\nSkipping this directory. Use MythVideo to retrieve meta data for these video files.\n" % (cfile))
02803                     continue
02804                 try:
02805                     cfile = unicode(cfile, u'utf8')
02806                 except (UnicodeEncodeError, TypeError):
02807                     pass
02808                 for sf in os.listdir(cfile):
02809                     try:
02810                         newpath = os.path.join(cfile, sf)
02811                     except:
02812                         sys.stderr.write(u"\n! Error: This video file cannot be processed skipping:\n")
02813                         sys.stderr.write(sf)
02814                         sys.stderr.write(u"\nIt may be advisable to rename this file and try again.\n\n")
02815                         continue
02816                     if os.path.isfile(newpath):
02817                         allfiles.append(newpath)
02818                     else:
02819                         if recursive:
02820                             allfiles.extend(
02821                                 self._findFiles([newpath], recursive = recursive, verbose = verbose)
02822                             )
02823                         #end if recursive
02824                     #end if isfile
02825                 #end for sf
02826             elif self.config[u'file_move_flag'] and not os.access(cfile, os.F_OK | os.R_OK | os.W_OK):
02827                 sys.stderr.write(u"\n! Error: The Video file (%s) to be moved must have the read and write permissions. Skipping this video file.\n" % (cfile))
02828             elif os.path.isfile(cfile) and os.access(cfile, os.F_OK | os.R_OK):
02829                 allfiles.append(cfile) # Files must exist and be at least readable
02830             #end if isdir
02831         #end for cfile
02832         return allfiles
02833     #end findFiles
02834 
02835 
02836     def _processNames(self, names, verbose=False, movies=False):
02837         """
02838         Takes list of names, runs them though the self.config['name_parse'] regex parsing strings
02839         to extract series name, season and episode numbers. Non-video files are skipped.
02840         return an array of dictionaries containing series name, season and episode numbers, file path             and full filename and file extention.
02841         """
02842         allEps = []
02843         for f in names:
02844             filepath, filename = os.path.split( f )
02845             filename, ext = os.path.splitext( filename )
02846 
02847             # Remove leading . from extension
02848             ext = ext.replace(u".", u"", 1)
02849             self.config['log'].debug(u'Checking for a valid video filename extension')
02850             if not ext.lower() in self.config[u'video_file_exts']:
02851                 for key in self.image_extensions:
02852                     if key == ext:
02853                         break
02854                 else:
02855                     sys.stderr.write(u"\n! Warning: Skipping non-video file name: (%s)\n" % (f))
02856                 continue
02857 
02858             match = None
02859             for r in self.config['name_parse']:
02860                 match = r.match(filename)
02861                 if match: break
02862             # If the filename does not match the default regular
02863             # expressions, try to match the file path + filename with the
02864             # extended fullpath regular expression so we can extract the
02865             # needed information out of the pathname
02866             if not match:
02867                 for r in self.config['fullname_parse']:
02868                     match = r.match(os.path.join(filepath, filename))
02869                     if match: break
02870 
02871             categories=''
02872             if match:
02873                 self.config['log'].debug(u'matched reg:%s'%match.re.pattern)
02874                 seriesname, seasno, epno = match.groups()
02875 
02876                 #remove ._- characters from name (- removed only if next to end of line)
02877                 seriesname = re.sub("[\._]|\-(?=$)", " ", seriesname).strip()
02878                 seasno, epno = int(seasno), int(epno)
02879 
02880                 if self.config['series_name_override']:
02881                     if self.config['series_name_override'].has_key(seriesname.lower()):
02882                         if len((self.config['series_name_override'][seriesname.lower()]).strip()) == 7:
02883                             categories+=u', Movie'
02884                             movie = filename
02885                             if movie.endswith(self.config['hd_dvd']):
02886                                 movie = movie.replace(self.config['hd_dvd'], '')
02887                                 categories+=u', DVD'
02888                                 categories+=u', HD'
02889                             else:
02890                                 if movie.endswith(self.config['dvd']):
02891                                     movie = movie.replace(self.config['dvd'], '')
02892                                     categories+=u', DVD'
02893                             movie = re.sub("[\._]|\-(?=$)", " ", movie).strip()
02894                             try:
02895                                 allEps.append({ 'file_seriesname':movie,
02896                                         'seasno':0,
02897                                         'epno':0,
02898                                         'filepath':filepath,
02899                                         'filename':filename,
02900                                         'ext':ext,
02901                                         'categories': categories
02902                                         })
02903                             except UnicodeDecodeError:
02904                                 allEps.append({ 'file_seriesname':unicode(movie,'utf8'),
02905                                         'seasno':0,
02906                                         'epno':0,
02907                                         'filepath':unicode(filepath,'utf8'),
02908                                         'filename':unicode(filename,'utf8'),
02909                                         'ext':unicode(ext,'utf8'),
02910                                         'categories': categories
02911                                         })
02912 
02913                 categories+=u', TV Series'
02914                 try:
02915                     allEps.append({ 'file_seriesname':seriesname,
02916                             'seasno':seasno,
02917                             'epno':epno,
02918                             'filepath':filepath,
02919                             'filename':filename,
02920                             'ext':ext,
02921                             'categories': categories
02922                             })
02923                 except UnicodeDecodeError:
02924                     allEps.append({ 'file_seriesname':unicode(seriesname,'utf8'),
02925                             'seasno':seasno,
02926                             'epno':epno,
02927                             'filepath':unicode(filepath,'utf8'),
02928                             'filename':unicode(filename,'utf8'),
02929                             'ext':unicode(ext,'utf8'),
02930                             'categories': categories
02931                             })
02932             else:
02933                 if movies: # Account for " - On DVD" and " HD - On DVD" extra text on file names
02934                     categories+=u', Movie'
02935                     movie = filename
02936 
02937                     if movie.endswith(self.config['hd_dvd']):
02938                         movie = movie.replace(self.config['hd_dvd'], '')
02939                         categories+=u', DVD'
02940                         categories+=u', HD'
02941                     else:
02942                         if movie.endswith(self.config['dvd']):
02943                             movie = movie.replace(self.config['dvd'], '')
02944                             categories+=u', DVD'
02945                     movie = re.sub("[\._]|\-(?=$)", " ", movie).strip()
02946                     try:
02947                         allEps.append({ 'file_seriesname':movie,
02948                                         'seasno':0,
02949                                         'epno':0,
02950                                         'filepath':filepath,
02951                                         'filename':filename,
02952                                         'ext':ext,
02953                                         'categories': categories
02954                                         })
02955                     except UnicodeDecodeError:
02956                         allEps.append({ 'file_seriesname':unicode(movie,'utf8'),
02957                                         'seasno':0,
02958                                         'epno':0,
02959                                         'filepath':unicode(filepath,'utf8'),
02960                                         'filename':unicode(filename,'utf8'),
02961                                         'ext':unicode(ext,'utf8'),
02962                                         'categories': categories
02963                                         })
02964                 else:
02965                     sys.stderr.write(u"\n! Warning: Skipping invalid name: %s\n" % (f))
02966             #end for r
02967         #end for f
02968 
02969         return allEps
02970     #end processNames
02971 
02972 
02973     def processFileOrDirectory(self):
02974         '''This routine is NOT used for MythTV meta data processing.
02975         If directory path has been specified then create a list of files that qualify as video
02976         files / including recursed directories.
02977         Then parse the list of file names to determine (series, season number, ep number and ep name).
02978         Skip any video file that cannot be parsed for sufficient info.
02979         Loop through the list:
02980             > Check if the series, season, ... exists, skip with debug message if none found
02981             > Set variable with proper info: sid, series, season and episode numbers
02982             > Process the file's information per the variable to get graphics and or meta data
02983         return False and an error message and exist the script if there are no video files to process
02984         return None when all processing was complete
02985         return a string of file names if the "Filename" process option was True
02986         '''
02987         filenames=''
02988         allFiles = self._findFiles(self.config['video_dir'], self.config['recursive'] , verbose = self.config['debug_enabled'])
02989         validFiles = self._processNames(allFiles, verbose = self.config['debug_enabled'])
02990 
02991         if len(validFiles) == 0:
02992             sys.stderr.write(u"\n! Error: No valid video files found\n")
02993             sys.exit(1)
02994 
02995         path_flag = self.config['metadatadir']
02996         for cfile in validFiles:
02997             sys.stdout.write(u"# Processing %(file_seriesname)s (season: %(seasno)d, episode %(epno)d)\n" % (cfile))
02998             self.config['sid']=None
02999             self.config['episode_name'] = None
03000             self.config['series_name']=cfile['file_seriesname']
03001             self.config['season_num']=u"%d" % cfile['seasno']
03002             self.config['episode_num']=u"%d" % cfile['epno']
03003             if not path_flag:    # If no metaddata directory specified then default to the video file dir
03004                 self.config['metadatadir'] = cfile['filepath']
03005             if self.verifySeriesExists():
03006                 self.config['log'].debug(u"Found series(%s) season(%s) episode(%s)" % (self.config['series_name'], self.config['season_num'], self.config['episode_num']))
03007                 if self.config['ret_filename']:
03008                     returned = self.processTVdatabaseRequests()
03009                     if returned != None and returned != False:
03010                         filenames+=returned+'\n'
03011                 else:
03012                     self.processTVdatabaseRequests()
03013             else:
03014                 sys.stderr.write(u"\n! Warning: Did not find series(%s) season(%s) episode(%s)\n" % (self.config['series_name'], self.config['season_num'], self.config['episode_num']))
03015             self.config['log'].debug("# Done")
03016         if len(filenames) == 0:
03017             return None
03018         else:
03019             return filenames[:-1] # drop the last '\n'
03020     # end processFileOrDirectory
03021 
03022     def __repr__(self):    # Just a place holder
03023         return self.config
03024     # end __repr__
03025 
03026 # end VideoFiles
03027 
03028 
03029 class MythTvMetaData(VideoFiles):
03030     """Process all mythvideo video files, update the video files associated MythTV meta data.
03031     Download graphics for those video files from either thetvdb.com or themovie.com. Video file names
03032     for TV episodes must series name, season and episode numbers. The video file's movie name must be
03033     an exact match with a movie title in themoviedb.com or the MythTV database must have an entry for
03034     the video file with a TMDB or an IMDB number (db field 'intref').
03035     """
03036     def __init__(self, configuration):
03037         """Retrieve the configuration options
03038         """
03039         super(MythTvMetaData, self).__init__(configuration)
03040     # end __init__
03041 
03042     # Local variables
03043     # A dictionary of meta data keys and initialized values
03044     global graphicsDirectories
03045     movie_file_format=u"%s/%s.%s"
03046     graphic_suffix = {u'coverfile': u'_coverart', u'fanart': u'_fanart', u'banner': u'_banner'}
03047     graphic_name_suffix = u"%s/%s%s.%s"
03048     graphic_name_season_suffix = u"%s/%s Season %d%s.%s"
03049 
03050 
03051     def _getSubtitle(self, cfile):
03052         '''Get the MythTV subtitle (episode name)
03053         return None
03054         return episode name string
03055         '''
03056         self.config['sid']=None
03057         self.config['episode_name'] = None
03058         self.config['series_name']=cfile['file_seriesname']
03059         self.config['season_num']=u"%d" % cfile['seasno']
03060         self.config['episode_num']=u"%d" % cfile['epno']
03061         self.verifySeriesExists()
03062         return self.config['episode_name']
03063     # end _getSubtitle
03064 
03065 
03066     def hashFile(self, name):
03067         '''Create metadata hash values for mythvideo files
03068         return a hash value
03069         return u'' if the was an error with the video file or the video file length was zero bytes
03070         '''
03071         filename = self.rtnRelativePath(name, u'mythvideo')
03072         # Use the MythVideo hashing protocol when the video is in a storage groups
03073         if filename[0] != u'/':
03074             hash_value = mythbeconn.getHash(filename, u'Videos')
03075             if hash_value == u'NULL':
03076                 return u''
03077             else:
03078                 return hash_value
03079 
03080         # Use a local hashing routine when video is not in a Videos storage group
03081         # For original code: http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes#Python
03082         try:
03083             longlongformat = 'q'  # long long
03084             bytesize = struct.calcsize(longlongformat)
03085             f = open(name, "rb")
03086             filesize = os.path.getsize(name)
03087             hash = filesize
03088             if filesize < 65536 * 2:    # Video file is too small
03089                    return u''
03090             for x in range(65536/bytesize):
03091                     buffer = f.read(bytesize)
03092                     (l_value,)= struct.unpack(longlongformat, buffer)
03093                     hash += l_value
03094                     hash = hash & 0xFFFFFFFFFFFFFFFF #to remain as 64bit number
03095             f.seek(max(0,filesize-65536),0)
03096             for x in range(65536/bytesize):
03097                     buffer = f.read(bytesize)
03098                     (l_value,)= struct.unpack(longlongformat, buffer)
03099                     hash += l_value
03100                     hash = hash & 0xFFFFFFFFFFFFFFFF
03101             f.close()
03102             returnedhash =  "%016x" % hash
03103             return returnedhash
03104 
03105         except(IOError):    # Accessing to this video file caused and error
03106             return u''
03107     # end hashFile()
03108 
03109     def rtnRelativePath(self, abpath, filetype):
03110         '''Check if there is a Storage Group for the file type (video, coverfile, banner, fanart, screenshot)
03111         and form an apprioriate relative path and file name.
03112         return a relative path and file name
03113         return an absolute path and file name if there is no storage group for the file type
03114         '''
03115         if abpath == None:
03116             return abpath
03117 
03118         # There is a chance that this is already a relative path or there is no Storage group for file type
03119         if not len(storagegroups):
03120             return abpath
03121         if not storagegroups.has_key(filetype) or abpath[0] != '/':
03122             return abpath
03123 
03124         # The file must already be in one of the directories specified by the file type's storage group
03125         for directory in storagegroups[filetype]:
03126             if abpath.startswith(directory):
03127                 return abpath[len(directory)+1:]
03128         else:
03129             return abpath
03130     # end rtnRelativePath
03131 
03132     def rtnAbsolutePath(self, relpath, filetype):
03133         '''Check if there is a Storage Group for the file type (mythvideo, coverfile, banner, fanart,
03134         screenshot)    and form an appropriate absolute path and file name.
03135         return an absolute path and file name
03136         return the relpath sting if the file does not actually exist in the absolute path location
03137         '''
03138         if relpath == None or relpath == u'':
03139             return relpath
03140 
03141         # There is a chance that this is already an absolute path
03142         if relpath[0] == u'/':
03143             return relpath
03144 
03145         if self.absolutepath:
03146             if not len(self.config['localpaths'][filetype]):
03147                 return relpath
03148             directories = self.config['localpaths'][filetype]
03149         else:
03150             directories = self.config[filetype]
03151 
03152         for directory in directories:
03153             abpath = u"%s/%s" % (directory, relpath)
03154             if os.path.isfile(abpath): # The file must actually exist locally
03155                 return abpath
03156         else:
03157             return relpath    # The relative path does not exist at all the metadata entry is useless
03158     # end rtnAbsolutePath
03159 
03160 
03161     def removeCommonWords(self, title):
03162         '''Remove common words from a title
03163         return title striped of common words
03164         '''
03165         if not title:
03166             return u' '
03167         wordList = [u'the ', u'a ', u'  '] # common word list. Leave double space as the last value.
03168         title = title.lower()
03169         for word in wordList:
03170             title = title.replace(word, u'')
03171         if not title:
03172             return u' '
03173         return filter(is_not_punct_char, title.strip())
03174     # end removeCommonWords()
03175 
03176 
03177     def _getTmdbIMDB(self, title, watched=False, IMDB=False, rtnyear=False):
03178         '''Find and exact match of the movie name with what's on themoviedb.com
03179         If IMDB is True return an imdb#
03180         If rtnyear is True return IMDB# and the movie year in a dictionary
03181         return False (no matching movie found)
03182         return imdb# and/or tmdb#
03183         '''
03184         global video_type, UI_title
03185         UI_title = title.replace(self.config[u'hd_dvd'], u'')
03186         UI_title = UI_title.replace(self.config[u'dvd'], u'')
03187 
03188         if UI_title[-1:] == ')': # Get rid of the (XXXX) year from the movie title
03189             tmp_title = UI_title[:-7].lower()
03190         else:
03191             tmp_title = UI_title.lower()
03192 
03193         if self.config['series_name_override']:
03194             if self.config['series_name_override'].has_key(tmp_title):
03195                 return (self.config['series_name_override'][tmp_title]).strip()
03196 
03197         TMDB_movies=[]
03198         IMDB_movies=[]
03199         user_tmdb = False
03200 
03201         while True:
03202             try:
03203                 if IMDB:
03204                     results = [self.config['tmdb_api'].searchIMDB(IMDB)]
03205                 elif user_tmdb:
03206                     results = self.config['tmdb_api'].searchTMDB(user_tmdb)
03207                     if rtnyear:
03208                         if results.has_key('releasedate'):
03209                             return {'name': "%s (%s)" % (results['title'], results['releasedate'][:4]), u'sid': results[u'inetref']}
03210                         else:
03211                             return {'name': "%s" % (results['title'], ), u'sid': results[u'inetref']}
03212                     else:
03213                         return results['inetref']
03214                 else:
03215                     results = self.config['tmdb_api'].searchTitle(tmp_title)
03216             except TmdbMovieOrPersonNotFound, e:
03217                 results = [{}]
03218             except Exception, errormsg:
03219                 self._displayMessage(u"themoviedb.com error for Movie(%s) invalid data error (%s)" % (title, errormsg))
03220                 return False
03221             except:
03222                 self._displayMessage(u"themoviedb.com error for Movie(%s)" % title)
03223                 return False
03224 
03225             # Check if user's interactive response (Skip, selection, input #)
03226             if len(results[0]) and self.config['interactive']:
03227                 if results[0].has_key('userResponse'):
03228                     # Check if the user selected a specific movie from the list
03229                     if results[0]['userResponse'] == 'User selected':
03230                         if rtnyear:
03231                             if results[0].has_key('released'):
03232                                 data = {'name': "%s (%s)" % (results[0]['name'], results[0]['released'][:4]), u'sid': results[0][u'id']}
03233                             else:
03234                                 data = {'name': "%s" % (results[0]['name'], ), u'sid': results[0][u'id']}
03235                             return data
03236                         else:
03237                             return results[0]['id']
03238                     # Check if the user has entered a TMDB number themselves
03239                     if results[0]['userResponse'] == 'User input':
03240                         user_tmdb = results[0]['id']
03241                         continue
03242                 # Check if the user wants this video to be ignored by Jamu from now on
03243                 if results[0]['id'] == '99999999':
03244                     if rtnyear:
03245                         return False
03246                     else:
03247                         return results[0]['id']
03248             break
03249 
03250         if IMDB: # This is required to allow graphic file searching both by a TMDB and IMDB numbers
03251             if len(results[0]):
03252                 if results[0].has_key('imdb_id'):
03253                     return results[0]['imdb_id'][2:]
03254                 else:
03255                     return False
03256             else:
03257                 return False
03258 
03259         if UI_title[-1:] == ')':
03260             name = UI_title[:-7].lower() # Just the movie title
03261             year = UI_title[-5:-1] # The movie release year
03262         else:
03263             name = tmp_title.lower()
03264             year = ''
03265         name = name.strip().replace('  ', ' ')
03266 
03267         if len(results[0]):
03268             for movie in results:
03269                 if self.removeCommonWords(movie['name']) == self.removeCommonWords(name):
03270                     if not year:
03271                         if movie.has_key('released'):
03272                             TMDB_movies.append({'name': "%s (%s)" % (movie['name'], movie['released'][:4]), u'sid': movie[u'id']})
03273                         else:
03274                             TMDB_movies.append({'name': "%s" % (movie['name'], ), u'sid': movie[u'id']})
03275                         continue
03276                     if movie.has_key(u'released'):
03277                         if movie['released'][:4] == year:
03278                             if rtnyear:
03279                                 return {'name': "%s (%s)" % (movie['name'], movie['released'][:4]), u'sid': movie[u'id']}
03280                             else:
03281                                 return movie[u'id']
03282                         TMDB_movies.append({'name': "%s (%s)" % (movie['name'], movie['released'][:4]), u'sid': movie[u'id']})
03283                         continue
03284                     else:
03285                         TMDB_movies.append({'name': "%s" % (movie['name'], ), u'sid': movie[u'id']})
03286                         continue
03287                 elif movie.has_key('alternative_name'):
03288                     if self.removeCommonWords(movie['alternative_name']) == self.removeCommonWords(name):
03289                         if not year:
03290                             if movie.has_key('released'):
03291                                 TMDB_movies.append({'name': "%s (%s)" % (movie['alternative_name'], movie['released'][:4]), u'sid': movie[u'id']})
03292                             else:
03293                                 TMDB_movies.append({'name': "%s" % (movie['alternative_name'], ), u'sid': movie[u'id']})
03294                             continue
03295                         if movie.has_key(u'released'):
03296                             if movie['released'][:4] == year:
03297                                 if rtnyear:
03298                                     return {'name': "%s (%s)" % (movie['alternative_name'], movie['released'][:4]), u'sid': movie[u'id']}
03299                                 else:
03300                                     return movie['id']
03301                             TMDB_movies.append({'name': "%s (%s)" % (movie['alternative_name'], movie['released'][:4]), u'sid': movie[u'id']})
03302                             continue
03303                         else:
03304                             TMDB_movies.append({'name': "%s" % (movie['alternative_name'], ), u'sid': movie[u'id']})
03305                             continue
03306 
03307         # When there is only one match but NO year to confirm then it is OK to assume an exact match
03308         if len(TMDB_movies) == 1 and year == '':
03309             if rtnyear:
03310                 return TMDB_movies[0]
03311             else:
03312                 return TMDB_movies[0][u'sid']
03313 
03314         if imdb_lib:    # Can a imdb.com search be done?
03315             imdb_access = imdb.IMDb()
03316             movies_found = []
03317             try:
03318                 movies_found = imdb_access.search_movie(tmp_title.encode("ascii", 'ignore'))
03319             except Exception:
03320                 return False
03321             if not len(movies_found):
03322                 return False
03323             tmp_movies={}
03324             for movie in movies_found: # Get rid of duplicates
03325                 try: # Protect against bad data from IMDBpy
03326                     if movie.has_key('year'):
03327                         temp =  {imdb_access.get_imdbID(movie): u"%s (%s)" % (movie['title'], movie['year'])}
03328                     else:
03329                         temp =  {imdb_access.get_imdbID(movie): movie['title']}
03330                 except Exception:
03331                     return False
03332                 if tmp_movies.has_key(temp.keys()[0]):
03333                     continue
03334                 tmp_movies[temp.keys()[0]] = temp[temp.keys()[0]]
03335             for movie in tmp_movies:
03336                 if tmp_movies[movie][:-7].lower() == name or self.removeCommonWords(tmp_movies[movie][:-7]) == self.removeCommonWords(name):
03337                     if year:
03338                         if tmp_movies[movie][-5:-1] == year:
03339                             if rtnyear:
03340                                 return {'name': tmp_movies[movie], u'sid': movie}
03341                             else:
03342                                 return u"%07d" % int(movie) # Pad out IMDB# with leading zeroes
03343                 IMDB_movies.append({'name': tmp_movies[movie], u'sid': movie})
03344 
03345         if len(IMDB_movies) == 1: # If this is the only choice and titles matched then auto pick it
03346             if self.removeCommonWords(IMDB_movies[0]['name'][:-7]) == self.removeCommonWords(name):
03347                 if rtnyear:
03348                     return IMDB_movies[0]
03349                 else:
03350                     return u"%07d" % int(IMDB_movies[0][u'sid'])
03351 
03352         # Does IMDB list this movie?
03353         if len(IMDB_movies) == 0:
03354             return False
03355 
03356         # Did the user want an interactive interface?
03357         if not self.config['interactive']:
03358             return False
03359 
03360         # Force only an IMDB look up for a movie
03361         movies = IMDB_movies
03362         video_type=u'IMDB'
03363 
03364         ui = jamu_ConsoleUI(config = self.config, log = self.config['log'])
03365         try:
03366             inetref = ui.selectSeries(movies)
03367         except tvdb_userabort:
03368             if video_type==u'IMDB' or len(IMDB_movies) == 0:
03369                 self._displayMessage(u"1-No selection made for Movie(%s)" % title)
03370                 return False
03371             movies = IMDB_movies
03372             video_type=u'IMDB'
03373             try:
03374                 inetref = ui.selectSeries(movies)
03375             except tvdb_userabort:
03376                 self._displayMessage(u"2-No selection made for Movie(%s)" % title)
03377                 return False
03378 
03379         if inetref.has_key('sid'):
03380             if _can_int(inetref['sid']):
03381                 if inetref['sid'] == '99999999':
03382                     return inetref['sid']
03383                 if rtnyear:
03384                     if inetref['name'] == u'User input':
03385                         try:
03386                             data = imdb_access.get_movie(inetref['sid'])
03387                             if data.has_key('long imdb title'):
03388                                 return {'name': data['long imdb title'], u'sid': inetref['sid']}
03389                             elif data.has_key('title'):
03390                                 return {'name': data['title'], u'sid': inetref['sid']}
03391                             else:
03392                                 return False
03393                         except imdb._exceptions.IMDbDataAccessError:
03394                             return False
03395                     else:
03396                         return inetref
03397                 else:
03398                     return u"%07d" % int(inetref['sid']) # Pad out IMDB# with leading zeroes
03399             else:
03400                 return False
03401         else:
03402             return False
03403     # end _getTmdbIMDB
03404 
03405     def _getTmdbGraphics(self, cfile, graphic_type, watched=False):
03406         '''Download either a movie Poster or Fanart
03407         return None
03408         return full qualified path and filename of downloaded graphic
03409         '''
03410         if graphic_type == u'-P':
03411             graphic_name = u'poster'
03412             key_type = u'coverart'
03413             rel_type = u'coverfile'
03414         else:
03415             graphic_name = u'fanart'
03416             key_type = u'fanart'
03417             rel_type = key_type
03418 
03419         self.config['series_name']=cfile['file_seriesname']
03420         try:
03421             if len(cfile['inetref']) == 7: # IMDB number
03422                 results = self.config['tmdb_api'].searchIMDB(cfile['inetref'])
03423             else:
03424                 results = self.config['tmdb_api'].searchTMDB(cfile['inetref'])
03425         except TmdbMovieOrPersonNotFound, e:
03426             self._displayMessage(u"0-tmdb %s for Movie not found(%s)(%s)" % (graphic_name, cfile['filename'], cfile['inetref']))
03427             return None
03428         except Exception, e:
03429             self._displayMessage(u"themoviedb.com error for Movie(%s) graphics(%s), error(%s)" % (cfile['file_seriesname'], graphic_name, e))
03430             return None
03431 
03432         if results != None:
03433             if not results.has_key(key_type):
03434                 self._displayMessage(u"1-tmdb %s for Movie not found(%s)(%s)" % (graphic_name, cfile['filename'], cfile['inetref']))
03435                 return None
03436         else:
03437             self._displayMessage(u"1b-tmdb %s for Movie not found(%s)(%s)" % (graphic_name, cfile['filename'], cfile['inetref']))
03438             return None
03439 
03440         graphic_file = (results[key_type].split(u','))[0].strip() # Only want the first image URL
03441 
03442         self.config['g_defaultname']=False
03443         self.config['toprated'] = True
03444         self.config['nokeys'] = False
03445 
03446         self.config['sid']=None
03447         if watched:
03448             if self.program_seriesid == None:
03449                 self.config['g_series'] = self.sanitiseFileName(cfile['file_seriesname'])+u' Season 1'+self.graphic_suffix[rel_type]+u'.%(ext)s'
03450             else:
03451                 self.config['g_series'] = self.sanitiseFileName(self.program_seriesid)+self.graphic_suffix[rel_type]+u'.%(ext)s'
03452         else:
03453             self.config['g_series'] = cfile['inetref']+self.graphic_suffix[rel_type]+u'.%(ext)s'
03454         if graphic_type == '-P':
03455             g_type = u'poster'
03456         else:
03457             g_type = u'fanart'
03458 
03459         self.config['season_num']= None    # Needed to get graphics named in 'g_series' format
03460 
03461         self.config['overwrite'] = True # Force overwriting any existing graphic file
03462 
03463         tmp_URL = graphic_file.replace(u"http://", u"")
03464         graphic_file = u"http://"+urllib.quote(tmp_URL.encode("utf-8"))
03465         value = self._downloadGraphics(u"%s:%s" % (g_type, graphic_file), mythtv=True)
03466 
03467         self.config['overwrite'] = False # Turn off overwriting
03468 
03469         if value == None:
03470             self._displayMessage(u"2-tmdb %s for Movie not found(%s)(%s)" % (graphic_name, cfile['filename'], cfile['inetref']))
03471             return None
03472         else:
03473             return self.rtnRelativePath(value, graphicsDirectories[rel_type])
03474     # end _getTmdbGraphics
03475 
03476     def _getSecondarySourceGraphics(self, cfile, graphic_type, watched=False):
03477         '''Download from secondary source such as movieposter.com
03478         return None
03479         return full qualified path and filename of downloaded graphic
03480         '''
03481         if not len(self.config['myth_secondary_sources']):
03482             return None
03483 
03484         if graphic_type == u'coverfile':
03485             graphic_type = u'poster'
03486             rel_type = u'coverfile'
03487 
03488         if cfile['seasno'] == 0 and cfile['epno'] == 0:
03489             if not self.config['myth_secondary_sources'].has_key('movies'):
03490                 return None
03491             if self.config['myth_secondary_sources']['movies'].has_key(graphic_type):
03492                 source = self.config['myth_secondary_sources']['movies'][graphic_type]
03493                 if source.find(u'%(imdb)s') != -1:
03494                     if len(cfile['inetref']) != 7:
03495                         try:
03496                             results = self.config['tmdb_api'].searchTMDB(cfile['inetref'])
03497                         except TmdbMovieOrPersonNotFound, e:
03498                             self._displayMessage(u"\n! Warning: Secondary themoviedb.com error for Movie(%s) graphics(%s), error(%s)" % (cfile['file_seriesname'], graphic_type, e))
03499                             return None
03500                         except Exception, e:
03501                             self._displayMessage(u"\n! Warning: Secondary themoviedb.com error for Movie(%s) graphics(%s), error(%s)" % (cfile['file_seriesname'], graphic_type, e))
03502                             return None
03503                         if results == None:
03504                             return None
03505                         if not results.has_key('imdb'):
03506                             self._displayMessage(u"\n! Warning: themoviedb.com wiki does not have an IMDB number to search a secondary source (%s)\nfor the movie (%s) inetref (%s).\n" % (source , cfile['filename'], cfile['inetref']))
03507                             return None
03508                         cfile['imdb'] = results['imdb']
03509                     else:
03510                         cfile['imdb'] = cfile['inetref']
03511             else:
03512                 return None
03513         else:
03514             if not self.config['myth_secondary_sources'].has_key('tv'):
03515                 return None
03516             if self.config['myth_secondary_sources']['tv'].has_key(graphic_type):
03517                 source = self.config['myth_secondary_sources']['tv'][graphic_type]
03518             else:
03519                 return None
03520 
03521         self.config['series_name']=cfile['file_seriesname']
03522 
03523         if self.config['simulation']:
03524             sys.stdout.write(u"Simulating - downloading Secondary Source graphic (%s)\n" % cfile['file_seriesname'])
03525             return u"Simulated Secondary Source graphic filename place holder"
03526 
03527         # Test that the secondary's required data has been passed
03528         try:
03529             command = source % cfile
03530         except:
03531             self._displayMessage(u"Graphics Secondary source command:\n%s\nRequired information is not available. Here are the variables that are available:\n%s\n" % (source, cfile))
03532             return None
03533 
03534         tmp_files = callCommandLine(command)
03535         if tmp_files == '':
03536             self._displayMessage(u"\n! Warning: Source (%s)\n could not find (%s) for (%s)(%s)\n" % (source % cfile, graphic_type, cfile['filename'], cfile['inetref']))
03537             return None
03538 
03539         tmp_array=tmp_files.split('\n')
03540         if tmp_array[0].startswith(u'Failed'):
03541             self._displayMessage(u"\n! Warning: Source (%s)\nfailed to download (%s) for (%s)(%s)\n" % (source % cfile, graphic_type, cfile['filename'], cfile['inetref']))
03542             return None
03543 
03544         if tmp_array[0].startswith(u'file://'):
03545             tmp_files=tmp_array[0].replace(u'file://', u'')
03546             if not os.path.isfile(tmp_files):
03547                 sys.stderr.write(u'\n! Error: The graphic file does not exist (%s)\n' % tmp_files)
03548                 sys.exit(1)
03549 
03550             # Fix file extentions in all caps or 4 character JPEG extentions
03551             fileExtension = (_getExtention(tmp_files)).lower()
03552             if fileExtension == u'jpeg':
03553                 fileExtension = u'jpg'
03554             if watched:
03555                 if self.program_seriesid == None:
03556                     filename = u'%s/%s%s.%s' % (self.config['posterdir'][0], self.sanitiseFileName(cfile['file_seriesname']), self.graphic_suffix[rel_type], fileExtension)
03557                 else:
03558                     filename = u'%s/%s%s.%s' % (self.config['posterdir'][0], self.sanitiseFileName(self.program_seriesid), self.graphic_suffix[rel_type], fileExtension)
03559             else:
03560                 filename = u'%s/%s%s.%s' % (self.config['posterdir'][0], cfile['inetref'], self.graphic_suffix[rel_type], fileExtension)
03561 
03562             if os.path.isfile(filename): # This may be the same small file or worse then current
03563                 try:
03564                     (width, height) = self.config['image_library'].open(filename).size
03565                     (width2, height2) = self.config['image_library'].open(tmp_files).size
03566                     if width >= width2:
03567                         os.remove(tmp_files)
03568                         return None
03569                 except IOError:
03570                     return None
03571 
03572             # Verify that the downloaded file was NOT HTML instead of the intended file
03573             if self._checkValidGraphicFile(tmp_files, graphicstype=u'', vidintid=False) == False:
03574                 os.remove(tmp_files) # Delete the useless HTML text
03575                 return None
03576             shutil.copy2(tmp_files, filename)
03577             os.remove(tmp_files)
03578             self.num_secondary_source_graphics_downloaded+=1
03579             return self.rtnRelativePath(filename, graphicsDirectories[rel_type])
03580         else:
03581             graphic_file = tmp_array[0]
03582 
03583             self.config['g_defaultname']=False
03584             self.config['toprated'] = True
03585             self.config['nokeys'] = False
03586 
03587             self.config['sid']=None
03588             if watched:
03589                 if self.program_seriesid == None:
03590                     self.config['g_series'] = self.sanitiseFileName(cfile['file_seriesname'])+self.graphic_suffix[rel_type]+'.%(ext)s'
03591                 else:
03592                     self.config['g_series'] = self.sanitiseFileName(self.program_seriesid)+self.graphic_suffix[rel_type]+'.%(ext)s'
03593             else:
03594                 self.config['g_series'] = self.sanitiseFileName(cfile['inetref'])+self.graphic_suffix[rel_type]+'.%(ext)s'
03595             g_type = graphic_type
03596 
03597             self.config['season_num']= None    # Needed to get graphics named in 'g_series' format
03598 
03599             self.config['overwrite'] = True # Force overwriting any existing graphic file
03600 
03601             tmp_URL = graphic_file.replace(u"http://", u"")
03602             graphic_file = u"http://"+urllib.quote(tmp_URL.encode("utf-8"))
03603             value = self._downloadGraphics(u"%s:%s" % (g_type, graphic_file), mythtv=True)
03604 
03605             self.config['overwrite'] = False # Turn off overwriting
03606             if value == None:
03607                 self._displayMessage(u"Secondary source %s not found(%s)(%s)" % (graphic_file, cfile['filename'], cfile['inetref']))
03608                 return None
03609             else:
03610                 self.num_secondary_source_graphics_downloaded+=1
03611                 return self.rtnRelativePath(value, graphicsDirectories[rel_type])
03612     # end _getSecondarySourceGraphics
03613 
03614     def combineMetaData(self, available_metadata, meta_dict, vid_type=False):
03615         ''' Combine the current data with new meta data from primary or secondary sources
03616         return combinted meta data dictionary
03617         '''
03618         # Combine meta data
03619         for key in meta_dict.keys():
03620             if key in self.config['metadata_exclude_as_update_trigger']:
03621                 continue
03622             else:
03623                 if key == 'inetref' and available_metadata[key] != meta_dict[key]:
03624                     available_metadata[key] = meta_dict[key]
03625                     continue
03626                 if key == 'releasedate' and available_metadata[key] != meta_dict[key]:
03627                     available_metadata[key] = meta_dict[key]
03628                     continue
03629                 if key == 'userrating' and available_metadata[key] == 0.0:
03630                     available_metadata[key] = meta_dict[key]
03631                     continue
03632                 if key == 'length' and available_metadata[key] == 0:
03633                     available_metadata[key] = meta_dict[key]
03634                     continue
03635                 if key == 'rating' and (available_metadata[key] == 'NR' or available_metadata[key] == 'Unknown'):
03636                     available_metadata[key] = meta_dict[key]
03637                     continue
03638                 if key == 'year' and available_metadata[key] == 1895:
03639                     available_metadata[key] = meta_dict[key]
03640                     continue
03641                 if key == 'category' and available_metadata[key] == 0:
03642                     available_metadata[key] = meta_dict[key]
03643                     continue
03644                 if key == 'inetref' and available_metadata[key] == '00000000':
03645                     available_metadata[key] = meta_dict[key]
03646                     continue
03647                 if key == 'title':
03648                     available_metadata[key] = meta_dict[key]
03649                     continue
03650                 if vid_type and key == 'subtitle': # There are no subtitles in movies
03651                     continue
03652                 if key == 'plot': # Remove any line-feeds from the plot. Mythvideo does not expect them.
03653                     meta_dict[key] = meta_dict[key].replace('\n', ' ')
03654                 if (vid_type and key == 'plot') and (meta_dict[key].find('@') != -1 or len(meta_dict[key].split(' ')) < 10):
03655                     continue
03656                 if vid_type and key == 'plot':
03657                     if available_metadata[key] != None:
03658                         if len(available_metadata[key].split(' ')) < 10 and len(meta_dict[key].split(' ')) > 10:
03659                             available_metadata[key] = meta_dict[key]
03660                             continue
03661                 if not available_metadata.has_key(key): # Mainly for Genre, Cast and Countries
03662                     available_metadata[key] = meta_dict[key]
03663                     continue
03664                 if available_metadata[key] == None or available_metadata[key] == '' or available_metadata[key] == 'None' or available_metadata[key] == 'Unknown':
03665                     available_metadata[key] = meta_dict[key]
03666                     continue
03667         return available_metadata
03668     # end combineMetaData
03669 
03670 
03671     def _getSecondarySourceMetadata(self, cfile, available_metadata):
03672         '''Download meta data from secondary source
03673         return available_metadata (returns the current metadata unaltered)
03674         return dictionary of combined meta data
03675         '''
03676         if not len(self.config['myth_secondary_sources']):
03677             return available_metadata
03678 
03679         if cfile['seasno'] == 0 and cfile['epno'] == 0:
03680             if not self.config['myth_secondary_sources'].has_key('movies'):
03681                 return available_metadata
03682             movie = True
03683             if self.config['myth_secondary_sources']['movies'].has_key('metadata'):
03684                 source = self.config['myth_secondary_sources']['movies']['metadata']
03685                 if source.find(u'%(imdb)s') != -1:
03686                     if len(cfile['inetref']) != 7:
03687                         try:
03688                             results = self.config['tmdb_api'].searchTMDB(cfile['inetref'])
03689                         except TmdbMovieOrPersonNotFound, e:
03690                             self._displayMessage(u"Secondary metadata themoviedb.com error for Movie(%s), error(%s)" % (cfile['file_seriesname'], e))
03691                             return available_metadata
03692                         except Exception, e:
03693                             self._displayMessage(u"Secondary metadata themoviedb.com error for Movie(%s), error(%s)" % (cfile['file_seriesname'], e))
03694                             return available_metadata
03695                         if results == None:
03696                             return available_metadata
03697                         if not results.has_key('imdb'):
03698                             self._displayMessage(u"No IMDB number for meta data secondary source (%s)\nfor the movie (%s) inetref (%s) in themoviedb.com wiki.\n" % (source, cfile['filename'], cfile['inetref']))
03699                             return available_metadata
03700                         cfile['imdb'] = results['imdb']
03701                     else:
03702                         cfile['imdb'] = cfile['inetref']
03703             else:
03704                 return available_metadata
03705         else:
03706             if not self.config['myth_secondary_sources'].has_key('tv'):
03707                 return available_metadata
03708             movie = False
03709             if self.config['myth_secondary_sources']['tv'].has_key('metadata'):
03710                 source = self.config['myth_secondary_sources']['tv']['metadata']
03711             else:
03712                 return available_metadata
03713 
03714         # Test that the secondary's required data has been passed
03715         try:
03716             command = source % cfile
03717         except:
03718             self._displayMessage(u"Metadata Secondary source command:\n%s\nRequired information is not available. Here are the variables that are available:\n%s\n" % (source, cfile))
03719             return available_metadata
03720 
03721         self.config['series_name']=cfile['file_seriesname']
03722 
03723         tmp_files=u''
03724         tmp_files = (callCommandLine(command)).decode("utf8")
03725         if tmp_files == '':
03726             self._displayMessage(u"1-Secondary source (%s)\ndid not find(%s)(%s) meta data dictionary cannot be returned" % (source % cfile, cfile['filename'], cfile['inetref']))
03727             return available_metadata
03728 
03729         meta_dict={}
03730         tmp_array=tmp_files.split('\n')
03731         for element in tmp_array:
03732             element = (element.rstrip('\n')).strip()
03733             if element == '' or element == None:
03734                 continue
03735             try:
03736                 index = element.index(':')
03737             except:
03738                 continue
03739             key = element[:index].lower()
03740             data = element[index+1:]
03741             if data == None or data == '':
03742                 continue
03743             if key == u'inetref' and len(cfile['inetref']) == 7:
03744                 meta_dict[key] = cfile['inetref']
03745                 continue
03746             data = self._changeAmp(data)
03747             data = self._changeToCommas(data)
03748             if key == 'year':
03749                 try:
03750                     meta_dict[key] = int(data)
03751                 except:
03752                     continue
03753                 continue
03754             if key == 'userrating':
03755                 try:
03756                     meta_dict[key] = float(data)
03757                 except:
03758                     continue
03759                 continue
03760             if key == 'runtime':
03761                 try:
03762                     meta_dict['length'] = long(data)
03763                 except:
03764                     continue
03765                 continue
03766             if key == 'movierating':
03767                 meta_dict['rating'] = data
03768                 continue
03769             if key == 'plot':
03770                 try:
03771                     if len(data.split(' ')) < 10: # Skip plots that are less than 10 words
03772                         continue
03773                 except:
03774                     pass
03775             if key == 'trailer':
03776                 continue
03777             if key == 'releasedate':
03778                 try:
03779                     meta_dict[key] = datetime.datetime.strptime(data,'%Y-%m-%d').date()
03780                 except ValueError:
03781                     pass
03782                 continue
03783             meta_dict[key] = data
03784         if not len(meta_dict):
03785             self._displayMessage(u"2-Secondary source (%s)\n did not find(%s)(%s) meta data dictionary cannot be returned" % (source % cfile, cfile['filename'], cfile['inetref']))
03786             return available_metadata
03787 
03788         # Combine meta data
03789         available_metadata = self.combineMetaData(available_metadata, meta_dict, vid_type=movie)
03790         self.num_secondary_source_metadata_downloaded+=1
03791         return available_metadata
03792     # end _getSecondarySourceMetadata
03793 
03794     def _getTmdbMetadata(self, cfile, available_metadata):
03795         '''Download a movie's meta data and massage the genres string
03796         return results for secondary sources when no primary source meta data
03797         return dictionary of metadata combined with data from a secondary source
03798         '''
03799         try:
03800             if len(cfile['inetref']) == 7: # IMDB number
03801                 meta_dict = self.config['tmdb_api'].searchIMDB(cfile['inetref'])
03802             else:
03803                 meta_dict = self.config['tmdb_api'].searchTMDB(cfile['inetref'])
03804         except TmdbMovieOrPersonNotFound, e:
03805             self._displayMessage(u"0-tmdb Movie not found(%s)(%s) meta data dictionary cannot be returned" % (cfile['filename'], cfile['inetref']))
03806             return self._getSecondarySourceMetadata(cfile, available_metadata)
03807         except Exception, e:
03808             self._displayMessage(u"themoviedb.com error for Movie(%s)(%s) meta data dictionary cannot be returned, error(%s)" % (cfile['filename'], cfile['inetref'], e))
03809             return self._getSecondarySourceMetadata(cfile, available_metadata)
03810 
03811         if meta_dict == None:
03812             self._displayMessage(u"1-tmdb Movie not found(%s)(%s) meta data dictionary cannot be returned" % (cfile['filename'], cfile['inetref']))
03813             return self._getSecondarySourceMetadata(cfile, available_metadata)
03814 
03815         keys = meta_dict.keys()
03816 
03817         for key in keys:
03818             data = meta_dict[key]
03819             if not data:
03820                 continue
03821             if key == 'homepage':
03822                 continue
03823             data = self._changeAmp(data)
03824             data = self._changeToCommas(data)
03825             if key == 'genres':
03826                 genres=''
03827                 genre_array = data.split(',')
03828                 for i in range(len(genre_array)):
03829                     genre_array[i] = (genre_array[i].strip()).lower()
03830                     if genre_array[i] in self.config['tmdb_genre_filter']:
03831                         genres+=genre_array[i].title()+','
03832                 if genres == '':
03833                     meta_dict[key] = u''
03834                     continue
03835                 else:
03836                     meta_dict[key] = genres[:-1]
03837             if key == 'trailer':
03838                 continue
03839             if key == 'year':
03840                 try:
03841                     meta_dict[key] = int(data)
03842                 except:
03843                     pass
03844                 continue
03845             if key == 'userrating':
03846                 try:
03847                     meta_dict[key] = float(data)
03848                 except:
03849                     pass
03850                 continue
03851             if key == 'url':
03852                 meta_dict['homepage'] = data
03853                 continue
03854             if key == 'releasedate':
03855                 try:
03856                     meta_dict[key] = datetime.datetime.strptime(data,'%Y-%m-%d').date()
03857                 except ValueError:
03858                     del meta_dict[key]
03859                 continue
03860             if key == 'runtime':
03861                 try:
03862                     meta_dict['length'] = long(data)
03863                 except:
03864                     pass
03865                 continue
03866             if key == 'movierating':
03867                 meta_dict['rating'] = data
03868                 continue
03869         if meta_dict.has_key('rating'):
03870             if meta_dict['rating'] == '':
03871                 meta_dict['rating'] = 'Unknown'
03872 
03873         if len(meta_dict):
03874             if available_metadata['hash'] == u'' or available_metadata['hash'] == None:
03875                 filename = u'%s/%s.%s' % (cfile['filepath'], cfile['filename'], cfile['ext'])
03876                 meta_dict['hash'] = self.hashFile(filename)
03877             available_metadata = self.combineMetaData(available_metadata, meta_dict, vid_type=True)
03878             return self._getSecondarySourceMetadata(cfile, available_metadata)
03879         else:
03880             self._displayMessage(u"2-tmdb Movie not found(%s)(%s) meta data dictionary cannot be returned" % (cfile['filename'], cfile['inetref']))
03881             return self._getSecondarySourceMetadata(cfile, available_metadata)
03882     # end _getTmdbMetadata
03883 
03884     def _getTvdbGraphics(self, cfile, graphic_type, toprated=False, watched=False):
03885         '''Download either a TV Series Poster, Banner, Fanart or Episode image
03886         return None
03887         return full qualified path and filename of downloaded graphic
03888         '''
03889         rel_type = graphic_type
03890         if graphic_type == u'coverfile':
03891             graphic_type = u'poster'
03892         elif graphic_type == u'poster':
03893             rel_type =u'coverfile'
03894 
03895         self.config['g_defaultname']=False
03896         self.config['toprated'] = toprated
03897         self.config['nokeys'] = False
03898         self.config['maximum'] = u'1'
03899 
03900         if watched:
03901             self.config['sid']=cfile['inetref']
03902         else:
03903             self.config['sid']=None
03904         self.config['episode_name'] = None
03905         self.config['series_name']=cfile['file_seriesname']
03906         if not watched:
03907             self.config['season_num']=u"%d" % cfile['seasno']
03908             self.config['episode_num']=u"%d" % cfile['epno']
03909 
03910         # Special logic must be used if the (-MG) guessing option has been requested
03911         if not self.config['sid'] and self.config['mythtv_guess']:
03912             try:
03913                 allmatchingseries = self.config['tvdb_api']._getSeries(self.config['series_name'])
03914             except Exception, e:
03915                 self._displayMessage(u"tvdb Series not found(%s) or connection issues with thetvdb.com web site.\nError:(%s)\n" % (cfile['filename'], e))
03916                 return None
03917             if filter(is_not_punct_char, allmatchingseries['name'].lower()) == filter(is_not_punct_char,cfile['file_seriesname'].lower()):
03918                 self.config['sid'] = allmatchingseries['sid']
03919                 self.config['series_name'] = allmatchingseries['name']
03920                 cfile['file_seriesname'] = allmatchingseries['name']
03921             else:
03922                 sys.stderr.write(u"\nGuessing could not find an exact match on tvdb for Series (%s)\ntherefore a graphics cannot be downloaded\n\n" % cfile['filename'])
03923                 return None
03924         else:
03925             if not self.verifySeriesExists():
03926                 self._displayMessage(u"tvdb Series not found(%s)" % cfile['filename'])
03927                 return None
03928 
03929         if watched:
03930             if self.program_seriesid == None:
03931                 self.config['g_series'] = self.sanitiseFileName(cfile['file_seriesname'])+u' Season 1'+self.graphic_suffix[rel_type]+u'.%(ext)s'
03932                 self.config['g_season'] = self.sanitiseFileName(cfile['file_seriesname'])+u' Season %(seasonnumber)d'+self.graphic_suffix[rel_type]+u'.%(ext)s'
03933             else:
03934                 self.config['g_series'] = self.sanitiseFileName(self.program_seriesid)+self.graphic_suffix[rel_type]+u'.%(ext)s'
03935                 self.config['g_season'] = self.sanitiseFileName(self.program_seriesid)+u' Season %(seasonnumber)d'+self.graphic_suffix[rel_type]+u'.%(ext)s'
03936         else:
03937         # TV Series ALWAYS need the ' Season' in the file name incase the show name could clobber a Movie image
03938         # Season X is used so that a real season image is not overritten. It will be renamed later.
03939             self.config['g_series'] = self.sanitiseFileName(self.config['series_name'])+u' Season X'+self.graphic_suffix[rel_type]+u'.%(ext)s'
03940             self.config['g_season'] = self.sanitiseFileName(self.config['series_name'])+u' Season %(seasonnumber)d'+self.graphic_suffix[rel_type]+u'.%(ext)s'
03941         if toprated:
03942             typegetGraphics=self.getTopRatedGraphics
03943             self.config['season_num']= None    # Needed to get toprated graphics named in 'g_series' format
03944         else:
03945             typegetGraphics=self.getGraphics
03946 
03947         self.config['overwrite'] = True # Force overwriting any existing graphic file
03948         value = self._downloadGraphics(typegetGraphics(graphic_type), mythtv=True)
03949         self.config['overwrite'] = False # Turn off overwriting
03950         if value == None:
03951             return None
03952         else:
03953             return self.rtnRelativePath(value, graphicsDirectories[rel_type])
03954     # end _getTvdbGraphics
03955 
03956     def _getTvdbMetadata(self, cfile, available_metadata):
03957         '''Download thetvdb.com meta data
03958         return what was input or results from a secondary source
03959         return dictionary of metadata
03960         '''
03961         global video_type, UI_title
03962         video_type=u'TV series'
03963         UI_title = cfile['file_seriesname']
03964 
03965         meta_dict={}
03966         self.config['nokeys'] = False
03967         self.config['sid']=None
03968         self.config['episode_name'] = None
03969         self.config['series_name']=cfile['file_seriesname']
03970         self.config['season_num']=u"%d" % cfile['seasno']
03971         self.config['episode_num']=u"%d" % cfile['epno']
03972         if self.config['series_name_override']:
03973             if self.config['series_name_override'].has_key(cfile['file_seriesname'].lower()):
03974                 self.config['sid'] = (self.config['series_name_override'][cfile['file_seriesname'].lower()]).strip()
03975 
03976         # Special logic must be used if the (-MG) guessing option has been requested
03977         if not self.config['sid'] and self.config['mythtv_guess']:
03978             try:
03979                 allmatchingseries = self.config['tvdb_api']._getSeries(self.config['series_name'])
03980             except Exception, e:
03981                 self._displayMessage(u"tvdb Series not found(%s) or there are connection problems with thetvdb.com\nError(%s)" % (cfile['filename'], e))
03982                 return None
03983             if filter(is_not_punct_char, allmatchingseries['name'].lower()) == filter(is_not_punct_char,cfile['file_seriesname'].lower()):
03984                 self.config['sid'] = allmatchingseries['sid']
03985                 self.config['series_name'] = allmatchingseries['name']
03986                 cfile['file_seriesname'] = allmatchingseries['name']
03987             else:
03988                 sys.stderr.write(u"\nGuessing could not find an exact match on tvdb for Series (%s)\ntherefore a meta data dictionary cannot be returned\n\n" % cfile['filename'])
03989                 return False
03990         else:
03991             if not self.verifySeriesExists():
03992                 self._displayMessage(u"tvdb Series not found(%s) meta data dictionary cannot be returned" % cfile['filename'])
03993                 return self._getSecondarySourceMetadata(cfile, available_metadata)
03994 
03995         if self.config['sid'] == '99999999':
03996             if not self.config['interactive']:
03997                 return self._getSecondarySourceMetadata(cfile, available_metadata)
03998             else:
03999                 return {'sid': self.config['sid'], 'title': cfile['file_seriesname']}
04000 
04001         meta_dict={}
04002         tmp_array=(self.getSeriesEpisodeData()).split('\n')
04003 
04004         for element in tmp_array:
04005             element = (element.rstrip('\n')).strip()
04006             if element == '':
04007                 continue
04008             index = element.index(':')
04009             key = element[:index].lower()
04010             data = element[index+1:]
04011             if data == None:
04012                 continue
04013             if key == 'series':
04014                 meta_dict['title'] = data
04015                 continue
04016             if key == 'seasonnumber':
04017                 try:
04018                     meta_dict['season'] = int(data)
04019                 except:
04020                     pass
04021                 continue
04022             if key == 'episodenumber':
04023                 try:
04024                     meta_dict['episode'] = int(data)
04025                 except:
04026                     pass
04027                 continue
04028             if key == 'episodename':
04029                 meta_dict['subtitle'] = data
04030                 continue
04031             if key == u'overview':
04032                 meta_dict['plot'] = data
04033                 continue
04034             if key == u'director' and data == 'None':
04035                 meta_dict['director'] = ''
04036                 continue
04037             if key == u'firstaired' and len(data) > 4:
04038                 try:
04039                     meta_dict['year'] = int(data[:4])
04040                 except:
04041                     pass
04042                 meta_dict['firstaired'] = data
04043                 try:
04044                     meta_dict['releasedate'] = datetime.datetime.strptime(data,'%Y-%m-%d').date()
04045                 except ValueError:
04046                     pass
04047                 continue
04048             if key == 'year':
04049                 try:
04050                     meta_dict['year'] = int(data)
04051                 except:
04052                     pass
04053                 continue
04054             if key == 'seriesid':
04055                 meta_dict['inetref'] = data
04056                 meta_dict[key] = data
04057                 continue
04058             if key == 'rating':
04059                 try:
04060                     meta_dict['userrating'] = float(data)
04061                 except:
04062                     pass
04063                 continue
04064             if key == 'filename':# This "episodeimage URL clashed with the video file name and ep image
04065                 continue        #  is not used yet. So skip fixes the db video filename from being wiped.
04066             if key == 'runtime':
04067                 try:
04068                     meta_dict['length'] = long(data)
04069                 except:
04070                     pass
04071                 continue
04072             meta_dict[key] = data
04073 
04074         if len(meta_dict):
04075             if not meta_dict.has_key('director'):
04076                 meta_dict['director'] = u''
04077             meta_dict['rating'] = u'TV Show'
04078             # URL to TVDB web site episode web page for this series
04079             for url_data in [u'seriesid', u'seasonid', u'id']:
04080                 if not url_data in meta_dict.keys():
04081                     break
04082             else:
04083                 meta_dict['homepage'] = u'http://www.thetvdb.com/?tab=episode&seriesid=%s&seasonid=%s&id=%s' % (meta_dict['seriesid'], meta_dict['seasonid'], meta_dict['id'])
04084             if available_metadata['hash'] == u'' or available_metadata['hash'] == None:
04085                 filename = u'%s/%s.%s' % (cfile['filepath'], cfile['filename'], cfile['ext'])
04086                 meta_dict['hash'] = self.hashFile(filename)
04087             available_metadata = self.combineMetaData(available_metadata, meta_dict, vid_type=False)
04088             return self._getSecondarySourceMetadata(cfile, available_metadata)
04089         else:
04090             self._displayMessage(u"tvdb Series found (%s) but no meta data for dictionary" % cfile['filename'])
04091             return self._getSecondarySourceMetadata(cfile, available_metadata)
04092     # end _getTvdbMetadata
04093 
04094     def _make_db_ready(self, text):
04095         '''Prepare text for inclusion into a DB
04096         return None
04097         return data base ready text
04098         '''
04099         if not text: return text
04100         try:
04101             text = text.replace(u'\u2013', u"-")
04102             text = text.replace(u'\u2014', u"-")
04103             text = text.replace(u'\u2018', u"'")
04104             text = text.replace(u'\u2019', u"'")
04105             text = text.replace(u'\u2026', u"...")
04106             text = text.replace(u'\u201c', u'"')
04107             text = text.replace(u'\u201d', u'"')
04108         except UnicodeDecodeError:
04109             pass
04110 
04111         return text
04112     # end make_db_ready
04113 
04114     def _addCastGenreCountry(self, data_string, vim, cast_genres_type):
04115         '''From a comma delimited string of cast members, genres or countries add the ones
04116         not already in the myth db and update the video's meta data
04117         return True when successfull
04118         return False if failed
04119         '''
04120         if data_string == '':
04121             return True
04122         data = data_string.split(',')
04123         for i in range(len(data)):
04124             data[i]=data[i].strip()
04125         try:
04126             data.remove('')
04127         except:
04128             pass
04129 
04130         if cast_genres_type == 'genres':
04131             for item in data:
04132                 vim.genre.add(item)
04133         elif cast_genres_type == 'cast':
04134             for item in data:
04135                 vim.cast.add(item)
04136         elif cast_genres_type == 'countries':
04137             for item in data:
04138                 vim.country.add(item)
04139 
04140         return True
04141     # end _addCastGenreCountry()
04142 
04143     # Local variables
04144     errors = []
04145     new_names = []
04146 
04147     def _moveDirectoryTree(self, src, dst, symlinks=False, ignore=None):
04148         '''Move a directory tree from a given source to a given destination. Subdirectories will be
04149         created and synbolic links will be recreated in the new destination.
04150         return an array of two arrays. Names of files/directories moved and Errors found
04151         '''
04152         wild_card = False
04153         org_src = src
04154         if src[-1:] == '*':
04155             wild_card = True
04156             (src, fileName) = os.path.split(src)
04157             try:
04158                 names = os.listdir(unicode(src, 'utf8'))
04159             except (UnicodeEncodeError, TypeError):
04160                 names = os.listdir(src)
04161         else:
04162             if os.path.isfile(src):
04163                 (src, fileName) = os.path.split(src)
04164                 names = [fileName]
04165             else:
04166                 try:
04167                     names = os.listdir(unicode(src, 'utf8'))
04168                 except (UnicodeEncodeError, TypeError):
04169                     names = os.listdir(src)
04170 
04171         if ignore is not None:
04172             ignored_names = ignore(src, names)
04173         else:
04174             ignored_names = set()
04175 
04176         try:
04177             if self.config['simulation']:
04178                 sys.stdout.write(u"Simulation creating subdirectories for file move (%s)\n" % dst)
04179             else:
04180                 self._displayMessage(u"Creating subdirectories for file move (%s)\n" % dst)
04181                 os.makedirs(dst)        # Some of the subdirectories may already exist
04182         except OSError:
04183             pass
04184 
04185         for name in names:
04186             if name in ignored_names:
04187                 continue
04188             srcname = os.path.join(src, name)
04189             dstname = os.path.join(dst, name)
04190 
04191             if not os.access(srcname, os.F_OK | os.R_OK | os.W_OK): # Skip any file that is not RW able
04192                 sys.stderr.write(u"\n! Error: The Source video directory or file (%s) must have read and write permissions for be moved. File or directory has been skipped\n" % (srcname))
04193                 continue
04194             try:
04195                 if symlinks and os.path.islink(srcname):
04196                     linkto = os.readlink(srcname)
04197                     if self.config['simulation']:
04198                         sys.stdout.write(u"Simulation recreating symbolic link linkto:\n(%s) destination name:\n(%s)\n" % (linkto, dstname))
04199                     else:
04200                         os.symlink(linkto, dstname)
04201                         self._displayMessage(u"Recreating symbolic link linkto:\n(%s) destination name:\n(%s)\n" % (linkto, dstname))
04202                     self.num_symbolic_links+=1
04203                 elif os.path.isdir(srcname):
04204                         if wild_card:
04205                             self._displayMessage(u"Wildcard skipping subdirectory (%s)\n" % srcname)
04206                             continue
04207                         self.num_created_video_subdirectories+=1
04208                         self._displayMessage(u"Move subdirectory (%s)\n" % srcname)
04209                         self._moveDirectoryTree(srcname, dstname, symlinks, ignore)
04210                 else:
04211                     if self.config['simulation']:
04212                         if wild_card:
04213                             if srcname.startswith(org_src[:-1]):
04214                                 sys.stdout.write(u"Simulation move wild card file from\n(%s) to\n(%s)\n" % (srcname, dstname))
04215                                 self.num_moved_video_files+=1
04216                                 self.new_names.append(dstname)
04217                             else:
04218                                 self._displayMessage(u"Simulation of wildcard skipping file(%s)" % (srcname,))
04219                         else:
04220                             sys.stdout.write(u"Simulation move file from\n(%s) to\n(%s)\n" % (srcname, dstname))
04221                             self.num_moved_video_files+=1
04222                             self.new_names.append(dstname)
04223                     else:
04224                         if wild_card:
04225                             if srcname.startswith(org_src[:-1]):
04226                                 self._displayMessage(u"Move wild card file from\n(%s) to\n(%s)\n" % (srcname, dstname))
04227                                 shutil.move(srcname, dstname)
04228                                 self.num_moved_video_files+=1
04229                                 self.new_names.append(dstname)
04230                             else:
04231                                 self._displayMessage(u"Wildcard skipping file(%s)" % (srcname,))
04232                         else:
04233                             self._displayMessage(u"Move file from\n(%s) to\n(%s)\n" % (srcname, dstname))
04234                             shutil.move(srcname, dstname)
04235                             self.num_moved_video_files+=1
04236                             self.new_names.append(dstname)
04237                 # XXX What about devices, sockets etc.?
04238             except (IOError, os.error), why:
04239                 self.errors.append([srcname, dstname, str(why)])
04240             # catch the Error from the recursive move tree so that we can
04241             # continue with other files
04242             except:
04243                 self.errors.append([src, dst, u"Unknown error"])
04244 
04245         return [self.new_names, self.errors]
04246     # end _moveDirectoryTree
04247 
04248     # local variable for move stats
04249     num_moved_video_files=0
04250     num_created_video_subdirectories=0
04251     num_symbolic_links=0
04252 
04253     def _moveVideoFiles(self, target_destination_array):
04254         """Copy files or directories to a destination directory.
04255         If the -F filename option is set then rename TV series during the move process. The move will
04256         be interactive for identifying a movie's IMDB number or TV series if the -i option was also set.
04257         If there is a problem error message are displayed and the script exists. After processing
04258         print a statistics report.
04259         return a array of video file dictionaries to update in Mythvideo data base
04260         """
04261         global UI_selectedtitle
04262         # Validate that the targets and destinations actually exist.
04263         count=1
04264         for file_dir in target_destination_array:
04265             if os.access(file_dir, os.F_OK | os.R_OK):
04266                 if count % 2 == 0:
04267                     # Destinations must all be directories
04268                     if not os.path.isdir(file_dir):
04269                         sys.stderr.write(u"\n! Error: Destinations must all be directories.\nThis destination is not a directory (%s)\n" % (file_dir,))
04270                         sys.exit(1)
04271                     else:
04272                         tmp_dir = file_dir
04273                         for directory in self.config['mythvideo']:
04274                             dummy_dir = file_dir.replace(directory, u'')
04275                             if dummy_dir != tmp_dir:
04276                                 break
04277                         else:
04278                             sys.stderr.write(u"\n! Error: Destinations must all be a mythvideo directory or subdirectory.\nThis destination (%s) is not one of the Mythvideo directories(%s)\n" % (file_dir, self.config['mythvideo'], ))
04279                             sys.exit(1)
04280                 # Verify that a target file is really a video file.
04281                 if file_dir[-1:] != '*': # Skip wildcard file name targets
04282                     if os.access(file_dir, os.F_OK | os.R_OK):    # Confirm that the file actually exists
04283                         if not os.path.isdir(file_dir):
04284                             ext = _getExtention(file_dir)
04285                             for tmp_ext in self.config['video_file_exts']:
04286                                 if ext.lower() == tmp_ext:
04287                                     break
04288                             else:
04289                                 sys.stderr.write(u"\n! Error: Target files must be video files(%s).\nSupported video file extentions(%s)\n" % (file_dir, self.config['video_file_exts'],))
04290                                 sys.exit(1)
04291                     count+=1
04292 
04293         # Stats counters
04294         num_renamed_files = 0
04295         num_mythdb_updates = 0
04296 
04297         i = 0
04298         video_files_to_process=[]
04299         cfile_array=[]
04300         while i < len(target_destination_array):
04301             src = target_destination_array[i]
04302             wild_card = False
04303             if src[-1:] == u'*':
04304                 org_src = src
04305                 wild_card = True
04306                 (src, fileName) = os.path.split(src)
04307             dst = target_destination_array[i+1]
04308             self.errors = []
04309             self.new_names = []
04310             if wild_card:
04311                 results = self._moveDirectoryTree(org_src, dst, symlinks=False, ignore=None)
04312             else:
04313                 results = self._moveDirectoryTree(src, dst, symlinks=False, ignore=None)
04314             if len(results[1]):            # Check if there are any errors
04315                 sys.stderr.write(u"\n! Warning: There were errors during moving, with these directories/files\n")
04316                 for error in results[1]:
04317                     sys.stderr.write(u'\n! Warning: Source(%s), Destination(%s), Reason:(%s)\n' % (error[0], error[1], error[2]))
04318             tmp_cfile_array=[]
04319             for name in results[0]:
04320                 file_name = os.path.join(dst, name)
04321                 if os.path.isdir(file_name):
04322                     for dictionary in self._processNames(_getFileList([file_name]), verbose = self.config['debug_enabled'], movies=True):
04323                         tmp_cfile_array.append(dictionary)
04324                 else:
04325                     for dictionary in self._processNames([file_name], verbose = self.config['debug_enabled'], movies=True):
04326                         tmp_cfile_array.append(dictionary)
04327 
04328             # Is the source directory within a mythvideo directory? If it is,
04329             # update existing mythdb records else add the record as you already have the inetref
04330             for directory in self.config['mythvideo']:
04331                 if src.startswith(directory):
04332                     for cfile in tmp_cfile_array:
04333                         tmp_path = src+cfile['filepath'].replace(dst, u'')
04334                         video_file = self.rtnRelativePath(self.movie_file_format % (tmp_path, cfile['filename'], cfile['ext']), 'mythvideo')
04335                         tmp_filename = self.rtnRelativePath(self.movie_file_format % (cfile['filepath'], cfile['filename'], cfile['ext']), 'mythvideo')
04336                         try:
04337                             metadata = mythvideo.searchVideos(exactfile=video_file).next()
04338                         except StopIteration:
04339                             try:
04340                                 metadata = mythvideo.searchVideos(exactfile=self.movie_file_format % (tmp_path, cfile['filename'], cfile['ext']), host=localhostname.lower()).next()
04341                             except StopIteration:
04342                                 continue
04343 
04344                         if tmp_filename[0] == '/':
04345                             host = u''
04346                             self.absolutepath = True
04347                         else:
04348                             host = localhostname.lower()
04349                             self.absolutepath = False
04350 
04351                         if self.config['simulation']:
04352                             sys.stdout.write(u"Simulation Mythdb update for old file:\n(%s) new:\n(%s)\n" % (video_file, tmp_filename))
04353                         else:
04354                             self._displayMessage(u"Mythdb update for old file:\n(%s) new:\n(%s)\n" % (video_file, tmp_filename))
04355                             metadata.update({'filename': tmp_filename, 'host': host})
04356                         num_mythdb_updates += 1
04357                     break
04358             else:
04359                 pass
04360             cfile_array.extend(tmp_cfile_array)
04361             i+=2        # Increment by 2 because array is int pairs of target and destination
04362 
04363         # Attempt to rename the video file
04364         if self.config['ret_filename']:
04365             for index in range(len(cfile_array)):
04366                 cfile = cfile_array[index]
04367                 if self.config['mythtv_inetref'] or self.config['mythtv_ref_num']:
04368                     sys.stdout.write(u"\nAttempting to rename video filename (%s)\n" % cfile['file_seriesname'])
04369                 if  cfile['seasno'] == 0 and cfile['epno'] == 0: # File rename for a movie
04370                     sid = None
04371                     new_filename = u''
04372                     if self.config['series_name_override']:
04373                         if self.config['series_name_override'].has_key(cfile['file_seriesname'].lower()):
04374                             sid = self.config['series_name_override'][cfile['file_seriesname'].lower()]
04375                     if not sid:
04376                         data = self._getTmdbIMDB(cfile['file_seriesname'], rtnyear=True)
04377                         if data:
04378                             sid = data[u'sid']
04379                             if data[u'sid'] == '99999999': # The user chose to ignore this video
04380                                 continue
04381                             new_filename = self.sanitiseFileName(data[u'name'])
04382                         else:
04383                             continue
04384                     else:
04385                         imdb_access = imdb.IMDb()
04386                         try:
04387                             data = imdb_access.get_movie(sid)
04388                             if data.has_key('long imdb title'):
04389                                 new_filename = data['long imdb title']
04390                             elif data.has_key('title'):
04391                                 new_filename = self.sanitiseFileName(namedata['title'])
04392                             else:
04393                                 continue
04394                         except imdb._exceptions.IMDbDataAccessError:
04395                             continue
04396 
04397                     if not sid:    # Cannot find this movie skip the renaming
04398                         continue
04399                     inetref = sid
04400                     if not new_filename:
04401                         continue
04402                     else:
04403                         cfile_array[index]['file_seriesname'] = new_filename
04404                 else:    # File rename for a TV Series Episode
04405                     UI_selectedtitle = u''
04406                     new_filename = u''
04407                     self.config['sid'] = None
04408                     self.config['series_name'] = cfile['file_seriesname']
04409                     if self.config['series_name_override']:
04410                         if self.config['series_name_override'].has_key(cfile['file_seriesname'].lower()):
04411                             self.config['sid'] = self.config['series_name_override'][cfile['file_seriesname'].lower()]
04412                             self.config['series_name'] = None
04413                     self.config['season_num'] = u"%d" % cfile['seasno']
04414                     self.config['episode_num'] = u"%d" % cfile['epno']
04415                     self.config['episode_name'] = None
04416                     new_filename = self.returnFilename()
04417                     inetref = self.config['sid']
04418                     if inetref == '99999999': # User chose to ignore this video
04419                         continue
04420 
04421                 if new_filename:
04422                     if new_filename == cfile['filename']: # The file was already named to standard format
04423                         self._displayMessage(u"File is already the correct name(%s)\n" % cfile['filename'])
04424                         continue
04425                     video_file = self.movie_file_format % (cfile['filepath'], cfile['filename'], cfile['ext'])
04426                     tmp_filename = self.movie_file_format % (cfile['filepath'], new_filename, cfile['ext'])
04427                     if self.config['simulation']:
04428                         sys.stdout.write(u"Simulation file renamed from(%s) to(%s)\n" % (video_file, tmp_filename))
04429                     else:
04430                         if not os.access(video_file, os.F_OK | os.R_OK | os.W_OK):
04431                             sys.stdout.write(u"Cannot rename this file as it does not have read/write permissions set (%s)\n" % video_file)
04432                             continue
04433                         self._displayMessage(u"File renamed from(%s) to(%s)\n" % (video_file, tmp_filename))
04434                         os.rename(video_file, tmp_filename)
04435                     num_renamed_files+=1
04436                     video_file = self.rtnRelativePath(self.movie_file_format % (cfile['filepath'], cfile['filename'], cfile['ext']), 'mythvideo')
04437                     tmp_filename = self.rtnRelativePath(self.movie_file_format % (cfile['filepath'], new_filename, cfile['ext']), 'mythvideo')
04438 
04439                     try:
04440                         metadata = mythvideo.searchVideos(exactfile=video_file).next()
04441                     except StopIteration:
04442                         try:
04443                             metadata = mythvideo.searchVideos(exactfile=self.movie_file_format % (cfile['filepath'], cfile['filename'], cfile['ext']), host=localhostname.lower()).next()
04444                         except StopIteration:
04445                             metadata = Video(db=mythvideo)
04446 
04447                     if tmp_filename[0] == '/':
04448                         host = u''
04449                         self.absolutepath = True
04450                     else:
04451                         host = localhostname.lower()
04452                         self.absolutepath = False
04453 
04454                     if metadata.intid:
04455                         if self.config['simulation']:
04456                             sys.stdout.write(u"Simulation Mythdb update for renamed file(%s)\n" % (tmp_filename))
04457                         else:
04458                             self._displayMessage(u"Mythdb update for renamed file(%s)\n" % (tmp_filename))
04459                             metadata.update({'filename': tmp_filename, 'host': host})
04460                     else:
04461                         if self.config['simulation']:
04462                             sys.stdout.write(u"Simulation Mythdb add for renamed file(%s)\n" % (tmp_filename))
04463                         else:
04464                             self._displayMessage(u"Adding Mythdb record for file(%s)\n" % (tmp_filename))
04465                             metadata.title = cfile['file_seriesname']
04466                             metadata.filename = tmp_filename
04467                             metadata.host = host
04468                             metadata.inetref = inetref
04469                             metadata.create()
04470 
04471                     cfile_array[index]['filename'] = new_filename
04472 
04473         if self.config['simulation']:
04474             sys.stdout.write(u'\n---------Simulated Statistics---------------')
04475         sys.stdout.write('\n--------------Move Statistics---------------\nNumber of subdirectories ............(% 5d)\nNumber of files moved ...............(% 5d)\nNumber of symbolic links recreated...(% 5d)\nNumber of renamed TV-eps or movies.. (% 5d)\nNumber of Myth database updates .... (% 5d)\n--------------------------------------------\n\n' % (self.num_created_video_subdirectories, self.num_moved_video_files, self.num_symbolic_links, num_renamed_files, num_mythdb_updates))
04476 
04477         return cfile_array
04478     # end _moveVideoFiles
04479 
04480     def _displayMessage(self, message):
04481         """Displays messages through stdout. Usually used with MythTv metadata updates in -V
04482         verbose mode.
04483         returns nothing
04484         """
04485         if message[-1:] != '\n':
04486             message+='\n'
04487         if self.config['mythtv_verbose']:
04488             sys.stdout.write(message)
04489     # end _displayMessage
04490 
04491     def _findMissingInetref(self):
04492         '''Find any video file without a Mythdb record or without an inetref number
04493         return None if there are no new video files
04494         return a array of dictionary information on each video file that qualifies for processing
04495         '''
04496         directories=self.config['mythvideo']
04497 
04498         if not len(directories):
04499             sys.stderr.write(u"\n! Error: There must be a video directory specified in MythTv\n")
04500             sys.exit(1)
04501 
04502         allFiles = self._findFiles(directories, self.config['recursive'] , verbose = self.config['debug_enabled'])
04503         validFiles = self._processNames(allFiles, verbose = self.config['debug_enabled'], movies=True)
04504         if len(validFiles) == 0:    # Is there video files to process?
04505             return None
04506 
04507         missing_list=[]
04508         for cfile in validFiles:
04509             try:
04510                 videopath = self.movie_file_format % (cfile['filepath'], cfile['filename'], cfile['ext'])
04511             except UnicodeDecodeError:
04512                 videopath = os.path.join(unicode(cfile['filepath'],'utf8'), unicode(cfile['filename'],'utf8')+u'.'+cfile['ext'])
04513 
04514             # Find the MythTV meta data
04515             try:
04516                 metadata = mythvideo.searchVideos(exactfile=videopath).next()
04517             except StopIteration:
04518                 try:
04519                     if not self.config['video_dir']:
04520                         metadata = mythvideo.searchVideos(exactfile=self.rtnRelativePath(videopath, 'mythvideo'), host=localhostname.lower()).next()
04521                         if _can_int(metadata.inetref) and (metadata.inetref != u'00000000'):
04522                             continue
04523                 except StopIteration:
04524                     pass
04525 
04526             missing_list.append(cfile)
04527 
04528         return missing_list
04529     # end _findMissingInetref
04530 
04531     def _checkValidGraphicFile(self, filename, graphicstype=u'', vidintid=False):
04532         '''Verify that a graphics file is not really an HTML file
04533         return True if it is a graphics file
04534         return False if it is an HTML file
04535         '''
04536         # Verify that the graphics file is NOT HTML instead of the intended graphics file
04537         try:
04538             p = subprocess.Popen(u'file "%s"' % filename, shell=True, bufsize=4096, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
04539         except:
04540             # There is something wrong with the file but do NOT say it is invalid just in case!
04541             return True
04542         data = p.stdout.readline()
04543         try:
04544             data = data.encode('utf8')
04545         except UnicodeDecodeError:
04546             data = unicode(data,'utf8')
04547         index = data.find(u'HTML document text')
04548         if index == -1:
04549             return True
04550         elif self.config['simulation']:
04551             sys.stdout.write(
04552                 u"Simulation deleting bad graphics file (%s) as it is really HTML\n" % (filename, )
04553             )
04554             if vidintid:
04555                 sys.stdout.write(
04556                     u"and the MythVideo record was corrected for the graphic reference.\n"
04557                 )
04558             return False
04559         else:
04560             os.remove(filename) # Delete the useless HTML text
04561             sys.stderr.write( u"\n! Warning: The graphics file (%s) is actually HTML and not the intended file type.\nDuring the original file download the web site had issues. The bad downloaded file was removed.\n" % (filename))
04562             if vidintid:
04563                 repair = {}
04564                 if graphicstype == u'coverfile':
04565                     repair[graphicstype] = u'No Cover'
04566                 else:
04567                     repair[graphicstype] = u''
04568                 Video(vidintid, db=mythvideo).update(repair)
04569             return False
04570     # end _checkValidGraphicFile()
04571 
04572 
04573     def _graphicsCleanup(self):
04574         '''Match the graphics in the mythtv graphics directories with the ones specified by the
04575         mythvideometa records. Remove any graphics that are not referenced at least once. Print a
04576         report.
04577         '''
04578         global localhostname
04579         num_total = 0
04580         num_deleted = 1
04581         num_new_total = 2
04582         stats = {'coverfile': [0,0,0], 'banner': [0,0,0], 'fanart': [0,0,0]}
04583 
04584         graphics_file_dict={}
04585         all_graphics_file_list=[]
04586         for directory in graphicsDirectories.keys():
04587             if directory == 'screenshot':
04588                 continue
04589             file_list = _getFileList(self.config[graphicsDirectories[directory]])
04590             if not len(file_list):
04591                 graphics_file_dict[directory] = []
04592                 continue
04593             for g_file in list(file_list):        # Cull the list removing dirs and non-graphics files
04594                 if os.path.isdir(g_file):
04595                     file_list.remove(g_file)
04596                     continue
04597                 g_ext = _getExtention(g_file)
04598                 if not g_ext in self.image_extensions:
04599                     file_list.remove(g_file)
04600                     continue
04601             for filel in file_list:
04602                 if not filel in all_graphics_file_list:
04603                     all_graphics_file_list.append(filel)
04604             graphics_file_dict[directory] = file_list
04605 
04606         for key in graphicsDirectories.keys():    # Set initial totals
04607             if key == 'screenshot':
04608                 continue
04609             stats[key][num_total] = len(graphics_file_dict[key])
04610 
04611         # Start reading videometadata records to remove their graphics from the image orphan list
04612         try:
04613             records = mythvideo.searchVideos()
04614         except MythError, e:
04615             sys.stderr.write(u"\n! Error: Reading all videometadata records: %s\n" % e.args[0])
04616             return
04617 
04618         atleast_one_video_file = False
04619         if records:
04620             for record in records:
04621                 atleast_one_video_file = True
04622                 meta_dict = {'host': record.host, 'coverfile': record.coverfile, 'banner': record.banner, 'fanart': record.fanart, 'filename': record.filename, 'intid': record.intid, 'inetref': record.inetref, }
04623                 # Skip any videometadata record that is not for this host
04624                 if meta_dict['host'] != u'' and meta_dict['host'] != None:
04625                     if meta_dict['host'].lower() != localhostname.lower():
04626                         continue
04627                 # Start removing any graphics in this videometadata record
04628                 for key in meta_dict.keys():
04629                     if key in ['host','filename','intid', 'inetref']:
04630                         continue
04631                     if meta_dict[key] in [None, u'', u'None', u'No Cover', u'Unknown']:
04632                         continue
04633 
04634                     # Deal with videometadata record using storage groups
04635                     if meta_dict['filename'] != None:
04636                         if meta_dict['filename'][0] == u'/':
04637                             self.absolutepath = True
04638                         else:
04639                             self.absolutepath = False
04640                     if meta_dict[key][0] != '/':
04641                         meta_dict[key] = self.rtnAbsolutePath(meta_dict[key], graphicsDirectories[key])
04642                         if meta_dict[key][0] != '/': # There is not a storage group for this relative file name
04643                             continue
04644 
04645                     # Deal with TV series level graphics
04646                     (dirName, fileName) = os.path.split(meta_dict[key])
04647                     (fileBaseName, fileExtension)=os.path.splitext(fileName)
04648                     index = fileBaseName.find(u' Season ')
04649                     intid = meta_dict['intid']
04650 
04651                     if index != -1: # Is this a TV Series episode?
04652                         if meta_dict[key] in graphics_file_dict[key]:
04653                             if self._checkValidGraphicFile(meta_dict[key], graphicstype=key, vidintid=intid) == True:
04654                                 graphics_file_dict[key].remove(meta_dict[key])
04655                                 all_graphics_file_list.remove(meta_dict[key])
04656                     # This logic is specific to Movies and videos with a '99999999' inetref numbers
04657                     elif fileName.startswith(meta_dict['inetref']+u'_') or fileName.startswith(meta_dict['inetref']+u'.') or meta_dict['inetref'] == '99999999':
04658                         if meta_dict[key] in graphics_file_dict[key]:
04659                             if self._checkValidGraphicFile(meta_dict[key], graphicstype=key, vidintid=intid) == True:
04660                                 graphics_file_dict[key].remove(meta_dict[key])
04661                                 all_graphics_file_list.remove(meta_dict[key])
04662 
04663         if not atleast_one_video_file:
04664             sys.stderr.write(u"\n! Error: Janitor - did not find any video files to process so skipping\nimage clean up to protect your image files, in case this is a configuration or NFS error.\nIf you do not use MythVideo then the Janitor option (-MJ) is not of value to you on this MythTV back end.\n")
04665             return
04666         # end reading videometadata records to remove their graphics from the image orphan list
04667 
04668         # Get Scheduled and Recorded program list
04669         programs = self._getScheduledRecordedProgramList()
04670 
04671         # Remove Scheduled and Recorded program's graphics files from the delete list
04672         if programs:
04673             for field in graphicsDirectories.keys():
04674                 if field == 'screenshot':
04675                     continue
04676                 remove=[]
04677                 for graphic in graphics_file_dict[field]:
04678                     (dirName, fileName) = os.path.split(graphic)
04679                     (fileBaseName, fileExtension)=os.path.splitext(fileName)
04680                     for program in programs:
04681                         if fileBaseName.lower().startswith(program['title'].lower()+u' '):
04682                             remove.append(graphic)
04683                             break
04684                         if not isValidPosixFilename(program['title']) and program['seriesid'] != u'':
04685                             if fileBaseName.lower().startswith(program['seriesid'].lower()):
04686                                 remove.append(graphic)
04687                                 break
04688                 for rem in remove:
04689                     if self._checkValidGraphicFile(rem, graphicstype=u'', vidintid=False) == True:
04690                         graphics_file_dict[field].remove(rem)
04691                         try:
04692                             all_graphics_file_list.remove(rem)
04693                         except ValueError, e:
04694                             pass
04695 
04696         # Do not remove the MiroBridge default image files even if they are not currently being used
04697         for filel in list(all_graphics_file_list):
04698             if filel.endswith('mirobridge_coverart.jpg'):
04699                 all_graphics_file_list.remove(filel)
04700                 continue
04701             if filel.endswith('mirobridge_banner.jpg'):
04702                 all_graphics_file_list.remove(filel)
04703                 continue
04704             if filel.endswith('mirobridge_fanart.jpg'):
04705                 all_graphics_file_list.remove(filel)
04706                 continue
04707 
04708         for key in graphicsDirectories.keys():    # Set deleted files totals
04709             if key == 'screenshot':
04710                 continue
04711             file_list = list(graphics_file_dict[key])
04712             for filel in file_list:
04713                 if not filel in all_graphics_file_list:
04714                     graphics_file_dict[key].remove(filel)
04715             stats[key][num_deleted] = len(graphics_file_dict[key])
04716 
04717         # Delete all graphics files still on the delete list
04718         for filel in all_graphics_file_list:
04719             if self.config['simulation']:
04720                 sys.stdout.write(
04721                     u"Simulation deleting (%s)\n" % (filel)
04722                 )
04723             else:
04724                 try:
04725                     os.remove(filel)
04726                 except OSError:
04727                     pass
04728                 self._displayMessage(u"(%s) Has been deleted\n" % (filel))
04729 
04730         for key in graphicsDirectories.keys():    # Set new files totals
04731             if key == 'screenshot':
04732                 continue
04733             stats[key][num_new_total] = stats[key][num_total] - stats[key][num_deleted]
04734 
04735         if self.config['simulation']:
04736             sys.stdout.write(u'\n\n------------Simulated Statistics---------------')
04737         sys.stdout.write(u'\n--------------Janitor Statistics---------------\n')
04738         stat_type = ['total', 'deleted', 'new total']
04739         for index in range(len(stat_type)):
04740             for key in graphicsDirectories.keys():    # Print stats
04741                 if key == 'screenshot':
04742                     continue
04743                 if key == 'coverfile':
04744                     g_type = 'posters'
04745                 else:
04746                     g_type = key+'s'
04747                 sys.stdout.write(u'% 9s % 7s ......................(% 5d)\n' % (stat_type[index], g_type, stats[key][index], ))
04748 
04749         for key in graphicsDirectories.keys():    # Print stats
04750             if key == 'screenshot':
04751                 continue
04752             if not len(graphics_file_dict[key]):
04753                 continue
04754             if key == 'coverfile':
04755                 g_type = 'poster'
04756             else:
04757                 g_type = key
04758             sys.stdout.write(u'\n----------------Deleted %s files---------------\n' % g_type)
04759             for graphic in graphics_file_dict[key]:
04760                 sys.stdout.write('%s\n' % graphic)
04761         return
04762     # end _graphicsCleanup
04763 
04764     def _getVideoLength(self, videofilename):
04765         '''Using ffmpeg (if it can be found) get the duration of the video
04766         return False if either ffmpeg cannot be found or the file is not a video
04767         return video lenght in minutes
04768         '''
04769         if not self.config['ffmpeg']:
04770             return False
04771 
04772         # Filter out specific file types due to potential negative processing overhead
04773         if _getExtention(videofilename) in [u'iso', u'img', u'VIDEO_TS', u'm2ts', u'vob']:
04774             return False
04775 
04776         p = subprocess.Popen(u'ffmpeg -i "%s"' % (videofilename), shell=True, bufsize=4096, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
04777 
04778         ffmpeg_found = True
04779         while 1:
04780             data = p.stderr.readline()
04781             if data.endswith('not found\n'):
04782                 ffmpeg_found = False
04783                 break
04784             if data.startswith('  Duration:'):
04785                 break
04786             if data == '' and p.poll() != None:
04787                 break
04788 
04789         if ffmpeg_found == False:
04790             self.config['ffmpeg'] = False
04791             return False
04792         elif data:
04793             time = (data[data.index(':')+1: data.index('.')]).strip()
04794             return (60*(int(time[:2]))+(int(time[3:5])))
04795         else:
04796             return False
04797     # end _getVideoLength
04798 
04799 
04800     def _getMiroVideometadataRecords(self):
04801         """Fetches all videometadata records with an inetref of '99999999' and a category of 'Miro'. If the
04802         videometadata record has a host them it must match the lower-case of the locahostname.
04803         aborts if processing failed
04804         return and array of matching videometadata dictionary records
04805         """
04806         global localhostname
04807         intids = []
04808         try:
04809             records = mythvideo.searchVideos(category=u'Miro', custom=(('inetref=%s','99999999'),))
04810         except MythError, e:
04811             sys.stderr.write(u"\n! Error: Reading all Miro videometadata records: %s\n" % e.args[0])
04812             sys.exit(1)
04813         if records:
04814             for record in records:
04815                 intids.append(record.intid)
04816 
04817         videometadatarecords=[]
04818         if len(intids):
04819             for intid in intids:
04820                 vidrec = Video(intid, db=mythvideo)
04821                 if vidrec[u'host'] != u'' and vidrec[u'host'] != None:
04822                     if vidrec[u'host'].lower() != localhostname.lower():
04823                         continue
04824                 videometadatarecords.append(vidrec)
04825 
04826             return videometadatarecords
04827         else:
04828             return None
04829         # end _getMiroVideometadataRecords()
04830 
04831     def _getExtraMiroDetails(self, mythvideorec, vidtype):
04832         '''Find the extra details required for Miro MythVideo record processing
04833         return a dictionary of details required for processing
04834         '''
04835         extradata = {}
04836         extradata[u'intid'] = [mythvideorec[u'intid']]
04837         if vidtype == u'movies':
04838             extradata[u'tv'] = False
04839         else:
04840             extradata[u'tv'] = True
04841 
04842         for key in [u'coverfile', u'banner', u'fanart', ]:
04843             extradata[key] = True    # Set each graphics type as if it has already been downloaded
04844             if mythvideorec[key] == None or mythvideorec[key] == u'No Cover' or mythvideorec[key] == u'':
04845                 extradata[key] = False
04846                 continue
04847             elif key == u'coverfile': # Look for undersized coverart
04848                 if mythvideorec[u'filename'][0] == u'/':
04849                     self.absolutepath = True
04850                 else:
04851                     self.absolutepath = False
04852                 filename = self.rtnAbsolutePath(mythvideorec[key], graphicsDirectories[key])
04853                 try:
04854                     (width, height) = self.config['image_library'].open(filename).size
04855                     if width < self.config['min_poster_size']:
04856                         extradata[key] = False
04857                         continue
04858                 except IOError:
04859                     extradata[key] = False
04860                     continue
04861                 continue
04862             else: # Check if the default graphics are being used
04863                 if mythvideorec[key].endswith(u'mirobridge_banner.jpg'):
04864                     extradata[key] = False
04865                 if mythvideorec[key].endswith(u'mirobridge_fanart.jpg'):
04866                     extradata[key] = False
04867                 continue
04868 
04869         if vidtype == u'movies': # Data specific to Movie Trailers
04870             if mythvideorec[u'filename'][0] == u'/':
04871                 self.absolutepath = True
04872             else:
04873                 self.absolutepath = False
04874             extradata[u'filename'] = mythvideorec[u'filename']
04875             extradata[u'pathfilename'] = self.rtnAbsolutePath(mythvideorec[u'filename'], u'mythvideo')
04876             if os.path.islink(extradata[u'pathfilename']):
04877                 extradata[u'symlink'] = True
04878             else:
04879                 extradata[u'symlink'] = False
04880             moviename = mythvideorec['subtitle']
04881             if not moviename:
04882                 moviename = ''
04883             else:
04884                 index = moviename.find(self.config[u'mb_movies'][filter(is_not_punct_char, mythvideorec[u'title'].lower())])
04885                 if index != -1:
04886                     moviename = moviename[:index].strip()
04887             extradata[u'moviename'] = moviename
04888             extradata[u'inetref'] = False
04889             if not moviename == None and not moviename == '':
04890                 lastyear = int(datetime.datetime.now().strftime(u"%Y"))
04891                 years = []
04892                 i = 0
04893                 while i < 5: # Check for a Movie that will be released this year or the next four years
04894                     years.append(u"%d" % ((lastyear+i)))
04895                     i+=1
04896                 imdb_access = imdb.IMDb()
04897                 movies_found = []
04898                 try:
04899                     movies_found = imdb_access.search_movie(moviename.encode("ascii", 'ignore'))
04900                 except Exception:
04901                     pass
04902                 tmp_movies={}
04903                 for movie in movies_found: # Get rid of duplicates
04904                     if movie.has_key('year'):
04905                         temp =  {imdb_access.get_imdbID(movie): u"%s (%s)" % (movie['title'], movie['year'])}
04906                         if tmp_movies.has_key(temp.keys()[0]):
04907                             continue
04908                         tmp_movies[temp.keys()[0]] = temp[temp.keys()[0]]
04909                 for year in years:
04910                     for movie in tmp_movies:
04911                         if filter(is_not_punct_char, tmp_movies[movie][:-7].lower()) == filter(is_not_punct_char, moviename.lower()) and tmp_movies[movie][-5:-1] == year:
04912                             extradata[u'inetref'] = u"%07d" % int(movie)
04913                             extradata[u'moviename'] = tmp_movies[movie]
04914                             extradata[u'year'] = year
04915                             break
04916                     if extradata[u'inetref']:
04917                         break
04918         return extradata
04919     # end _getExtraMiroDetails()
04920 
04921     def updateMiroVideo(self, program):
04922         '''Update the information in a Miro/MythVideo record
04923         return nothing
04924         '''
04925         global localhostname, graphicsDirectories
04926 
04927         mirodetails = program[u'miro']
04928 
04929         for intid in mirodetails[u'intid']:
04930             changed_fields = {}
04931             for key in graphicsDirectories.keys():
04932                 if key == u'screenshot':
04933                     continue
04934                 if mirodetails[key] != True and mirodetails[key] != False and mirodetails[key] != None and mirodetails[key] != u'Simulated Secondary Source graphic filename place holder':
04935                     # A graphics was downloaded
04936                     changed_fields[key] = mirodetails[key]
04937 
04938             if not mirodetails[u'tv'] and not mirodetails[u'symlink'] and os.access(mirodetails[u'pathfilename'], os.F_OK | os.R_OK | os.W_OK):
04939                 changed_fields[u'inetref'] = mirodetails[u'inetref']
04940                 changed_fields[u'subtitle'] = u''
04941                 changed_fields[u'year'] = mirodetails[u'year']
04942                 changed_fields[u'banner'] = u''
04943                 (dirName, fileName) = os.path.split(mirodetails[u'pathfilename'])
04944                 (fileBaseName, fileExtension) = os.path.splitext(fileName)
04945                 try:
04946                     dir_list = os.listdir(unicode(dirName, 'utf8'))
04947                 except (UnicodeEncodeError, TypeError):
04948                     dir_list = os.listdir(dirName)
04949                 index = 1
04950                 while index != 0:
04951                     filename = self.sanitiseFileName(u'%s - Trailer %d' % (mirodetails[u'moviename'], index))
04952                     fullfilename = u'%s/%s%s' % (dirName, filename, fileExtension)
04953                     for flenme in dir_list:
04954                         if fnmatch.fnmatch(flenme.lower(), u'%s.*' % filename.lower()):
04955                             break
04956                     else:
04957                         changed_fields[u'title'] = filename
04958                         if self.config['simulation']:
04959                             sys.stdout.write(
04960                             u"Simulation rename Miro-MythTV movie trailer from (%s) to (%s)\n" % (mirodetails[u'pathfilename'], fullfilename))
04961                         else:
04962                             os.rename(mirodetails[u'pathfilename'], fullfilename)
04963                         changed_fields[u'filename'] = self.rtnRelativePath(fullfilename, u'mythvideo')
04964                         if changed_fields[u'filename'][0] != u'/':
04965                             changed_fields[u'host'] = localhostname.lower()
04966                         else:    # Deal with the whole mixing Video SG and local with SG graphics mess
04967                             for key in graphicsDirectories.keys():
04968                                 if key == u'screenshot' or not changed_fields.has_key(key):
04969                                     continue
04970                                 if changed_fields[key][0] == u'/':
04971                                     continue
04972                                 else:
04973                                     changed_fields.remove(key)
04974                         break
04975                     index+=1
04976 
04977             if len(changed_fields):
04978                 if self.config['simulation']:
04979                     if program['subtitle']:
04980                         sys.stdout.write(
04981                         u"Simulation MythTV DB update for Miro video (%s - %s)\n" % (program['title'], program['subtitle']))
04982                     else:
04983                         sys.stdout.write(
04984                         u"Simulation MythTV DB update for Miro video (%s)\n" % (program['title'],))
04985                 else:
04986                     Video(intid, db=mythvideo).update(changed_fields)
04987     # end updateMiroVideo()
04988 
04989     def _getScheduledRecordedProgramList(self):
04990         '''Find all Scheduled and Recorded programs
04991         return array of found programs, if none then empty array is returned
04992         '''
04993         global localhostname
04994         programs=[]
04995 
04996         # Get pending recordings
04997         try:
04998             progs = mythbeconn.getUpcomingRecordings()
04999         except MythError, e:
05000             sys.stderr.write(u"\n! Error: Getting Upcoming Recordings list: %s\n" % e.args[0])
05001             return programs
05002 
05003         for prog in progs:
05004             record={}
05005             if prog.title == None:
05006                 continue
05007             record['title'] = prog.title
05008             record['subtitle'] = prog.subtitle
05009             record['seriesid'] = prog.seriesid
05010 
05011             if record['subtitle'] and prog.airdate != None:
05012                 record['originalairdate'] = prog.airdate.year
05013             else:
05014                 if prog.year != '0':
05015                     record['originalairdate'] = prog.year
05016                 elif prog.airdate != None:
05017                     record['originalairdate'] = prog.airdate.year
05018             for program in programs:    # Skip duplicates
05019                 if program['title'] == record['title']:
05020                     break
05021             else:
05022                 programs.append(record)
05023 
05024         # Get recorded records
05025         try:
05026             recordedlist = list(mythdb.searchRecorded(hostname=localhostname))
05027         except MythError, e:
05028             sys.stderr.write(u"\n! Error: Getting recorded programs list: %s\n" % e.args[0])
05029             return programs
05030 
05031         if not recordedlist:
05032             return programs
05033 
05034         recordedprogram = {}
05035         for recordedRecord in recordedlist:
05036             if recordedRecord.recgroup == u'Deleted':
05037                 continue
05038             recorded = {}
05039             if recordedRecord.title == None:
05040                 continue
05041             if recordedRecord.chanid == 9999:
05042                 recorded[u'miro_tv'] = True
05043             recorded[u'title'] = recordedRecord.title
05044             recorded[u'subtitle'] = recordedRecord.subtitle
05045             recorded[u'seriesid'] = recordedRecord.seriesid
05046             for program in programs:    # Skip duplicates
05047                 if program['title'] == recorded['title']:
05048                     break
05049             else:
05050                 programs.append(recorded)
05051                 # Get Release year for recorded movies
05052                 # Get Recorded videos recordedprogram / airdate
05053                 try:
05054                     recordedDetails = dict(RecordedProgram.fromRecorded(recordedRecord))
05055                 except MythError, e:
05056                     sys.stderr.write(u"\n! Error: Getting recordedprogram table record: %s\n" % e.args[0])
05057                     continue
05058                 if not len(recordedDetails):
05059                     continue
05060                 if not recordedDetails['subtitle']:
05061                     recordedprogram[recordedDetails['title']]= u'%d' % recordedDetails['airdate']
05062 
05063         # Add release year to recorded movies
05064         for program in programs:
05065             if recordedprogram.has_key(program['title']):
05066                 program['originalairdate'] = recordedprogram[program['title']]
05067 
05068 
05069         # Add real names to mb_tv if they are among the recorded videos
05070         if len(self.config['mb_tv_channels']):
05071             for program in programs:
05072                 programtitle = filter(is_not_punct_char, program[u'title'].lower())
05073                 if programtitle in self.config['mb_tv_channels'].keys():
05074                     self.config['mb_tv_channels'][programtitle][1] = program[u'title']
05075 
05076         # Check that each program has an original airdate
05077         for program in programs:
05078             if not program.has_key('originalairdate'):
05079                 program['originalairdate'] = u'0000' # Set the original airdate to zero (unknown)
05080 
05081         # If there are any Miro TV or movies to process then add them to the list
05082         if len(self.config['mb_tv_channels']) or len(self.config['mb_movies']):
05083             miromythvideorecs = self._getMiroVideometadataRecords()
05084             if miromythvideorecs:
05085                 # Create array used to check for duplicates
05086                 duplicatekeys = {}
05087                 i = 0
05088                 for program in programs:
05089                     programtitle = filter(is_not_punct_char, program[u'title'].lower())
05090                     if programtitle in self.config['mb_tv_channels'].keys():
05091                         if not program[u'title'] in duplicatekeys:
05092                             duplicatekeys[program[u'title']] = i
05093                     elif programtitle in self.config['mb_movies'].keys():
05094                         moviename = program['subtitle']
05095                         if not moviename:
05096                             moviename = ''
05097                         else:
05098                             index = moviename.find(self.config['mb_movies'][programtitle])
05099                             if index != -1:
05100                                 moviename = moviename[:index].strip()
05101                         if not moviename in duplicatekeys:
05102                             duplicatekeys[moviename] = i
05103                     i+=1
05104 
05105                 for record in miromythvideorecs:
05106                     program = {}
05107                     program[u'title'] = record[u'title']
05108                     program[u'subtitle'] = record[u'subtitle']
05109                     program[u'originalairdate'] = record[u'year']
05110                     recordtitle = filter(is_not_punct_char, record[u'title'].lower())
05111                     if recordtitle in self.config['mb_tv_channels'].keys():
05112                         if not record[u'title'] in duplicatekeys.keys():
05113                             program[u'miro'] = self._getExtraMiroDetails(record, u'tv')
05114                             duplicatekeys[program[u'title']] = len(programs)
05115                             programs.append(program)
05116                             self.config['mb_tv_channels'][recordtitle][1] = record[u'title']
05117                         elif programs[duplicatekeys[program[u'title']]].has_key(u'miro'):
05118                             programs[duplicatekeys[program[u'title']]][u'miro'][u'intid'].append(record[u'intid'])
05119                         else:
05120                             programs[duplicatekeys[program[u'title']]][u'miro'] = self._getExtraMiroDetails(record, u'tv')
05121                     elif recordtitle in self.config['mb_movies'].keys():
05122                         moviename = record['subtitle']
05123                         if not moviename:
05124                             moviename = ''
05125                         else:
05126                             index = moviename.find(self.config['mb_movies'][filter(is_not_punct_char, program[u'title'].lower())])
05127                             if index != -1:
05128                                 moviename = moviename[:index].strip()
05129                         if not moviename in duplicatekeys.keys():
05130                             program[u'miro'] = self._getExtraMiroDetails(record, u'movies')
05131                             if program[u'miro'][u'inetref']:
05132                                 duplicatekeys[moviename] = len(programs)
05133                                 programs.append(program)
05134                         elif programs[duplicatekeys[moviename]].has_key(u'miro'):
05135                             programs[duplicatekeys[moviename]][u'miro'][u'intid'].append(record[u'intid'])
05136                         else:
05137                             program[u'miro'] = self._getExtraMiroDetails(record, u'movies')
05138                             if program[u'miro'][u'inetref']:
05139                                 programs[duplicatekeys[moviename]][u'miro'] = self._getExtraMiroDetails(record, u'movies')
05140 
05141         # Check that each program has seriesid
05142         for program in programs:
05143             if not program.has_key('seriesid'):
05144                 program['seriesid'] = u''     # Set an empty seriesid - Generall only for Miro Videos
05145             if program['seriesid'] == None:
05146                 program['seriesid'] = u''     # Set an empty seriesid
05147 
05148         return programs
05149     # end _getScheduledRecordedProgramList
05150 
05151 
05152     def _getScheduledRecordedTVGraphics(self, program, graphics_type):
05153         '''Get TV show graphics for Scheduled and Recorded TV programs
05154         return None if no graphics found
05155         return fullpath and filename of downloaded graphics file
05156         '''
05157         if graphics_type == 'coverfile':
05158             graphics_type = 'poster'
05159 
05160         self.config['sid'] = None
05161         if self.config['series_name_override']:
05162             if self.config['series_name_override'].has_key(program['title'].lower()):
05163                 self.config['sid'] = self.config['series_name_override'][program['title'].lower()]
05164         # Find out if there are any Series level graphics available
05165         self.config['toprated'] = True
05166         self.config['episode_name'] = None
05167         self.config['series_name'] = program['title']
05168         self.config['season_num'] = None
05169         self.config['episode_num'] = None
05170 
05171         series_graphics = self.getGraphics(graphics_type)
05172 
05173         if series_graphics != None:
05174             cfile = { 'file_seriesname': program['title'],
05175                     'inetref': self.config['sid'],
05176                     'seasno': self.config['season_num'],
05177                     'epno': self.config['episode_num'],
05178                     'filepath':u'',
05179                     'filename': program['title'],
05180                     'ext':u'',
05181                     'categories':u''
05182             }
05183             return self._getTvdbGraphics(cfile, graphics_type, toprated=True, watched=True)
05184         return None
05185     # end _getScheduledRecordedTVGraphics
05186 
05187     def _downloadScheduledRecordedGraphics(self):
05188         '''Get Scheduled and Recorded programs and Miro vidoes get their graphics if not already
05189         downloaded
05190         return (nothing is returned)
05191         '''
05192         global localhostname
05193 
05194         # Initialize reporting stats
05195         total_progs_checked = 0
05196         total_posters_found = 0
05197         total_banners_found = 0
05198         total_fanart_found = 0
05199         total_posters_downloaded = 0
05200         total_banners_downloaded = 0
05201         total_fanart_downloaded = 0
05202         total_miro_tv = 0
05203         total_miro_movies = 0
05204 
05205         programs = self._getScheduledRecordedProgramList()
05206 
05207         if not len(programs): # Is there any programs to process?
05208             return
05209 
05210         # Add any Miro Bridge mb_tv dictionary items to 'series_name_override' dictionary
05211         if not self.config['series_name_override'] and len(self.config['mb_tv_channels']):
05212             self.config['series_name_override'] = {}
05213         for miro_tv_key in self.config['mb_tv_channels'].keys():
05214             if self.config['mb_tv_channels'][miro_tv_key][0]:
05215                 self.config['series_name_override'][self.config['mb_tv_channels'][miro_tv_key][1].lower()] = self.config['mb_tv_channels'][miro_tv_key][0]
05216 
05217         total_progs_checked = len(programs)
05218 
05219         # Get totals of Miro TV shows and movies that will be processed
05220         for program in programs:
05221             if program.has_key(u'miro'):
05222                 if not program[u'miro'][u'tv']:
05223                     total_miro_movies+=1
05224                 else:
05225                     total_miro_tv+=1
05226             elif program.has_key(u'miro_tv'):
05227                 if filter(is_not_punct_char, program[u'title'].lower()) in self.config['mb_movies'].keys():
05228                     total_miro_movies+=1
05229                 else:
05230                     total_miro_tv+=1
05231 
05232         # Prossess all TV shows and Movies
05233         for program in programs:
05234             program['need'] = False    # Initalize that this program does not need graphic(s) downloaded
05235             mirodetails = None
05236             program_override_tv = False
05237             # Check if a subtitle-less program is really a TV show with an override. This compensates for
05238             # poor EPG data sources (as has been reported from at least Australia)
05239             if not program['subtitle'] and program['title'].lower() in self.config['series_name_override']:
05240                 try:
05241                     result = self._searchforSeries(program['title'])
05242                     program_override_tv = True
05243                 except Exception, e:
05244                     pass
05245 
05246             # Even movies get the ' Season' added to the image names so that movie such as '1408' do not clash
05247             # with TMDB#ed image names
05248             pattern = u'%s Season*.*'
05249             if not program.has_key(u'miro'):
05250                 if program['subtitle'] or program_override_tv:
05251                     graphics_name = program['title']
05252                 else:
05253                     if not int(program['originalairdate']):
05254                         graphics_name = program['title']
05255                     else:
05256                         graphics_name = "%s (%s)" % (program['title'], program['originalairdate'])
05257             else:
05258                 mirodetails = program[u'miro']
05259                 if mirodetails[u'tv']:
05260                     graphics_name = program['title']
05261                 else:
05262                     graphics_name = mirodetails[u'inetref']
05263 
05264             self.absolutepath = False        # All Scheduled Recorded and Miro videos start in the SG "Default"
05265 
05266             # Search for graphics that are already downloaded
05267             for directory in graphicsDirectories.keys():
05268                 if directory == 'screenshot':    # There is no downloading of screenshots required
05269                     program[directory] = True
05270                     continue
05271                 if directory == 'banner' and not program['subtitle']: # No banners for movies
05272                     program[directory] = True
05273                     continue
05274                 elif mirodetails:
05275                     if not mirodetails[u'tv'] and directory == 'banner': # No banners for movies
05276                         program[directory] = True
05277                         continue
05278                 if not mirodetails:
05279                     filename = program['title']
05280                 elif mirodetails[u'tv']:
05281                     filename = program['title']
05282                 else:
05283                     filename = mirodetails[u'inetref']
05284 
05285                 # Deal with TV series names that would generate invalid file names for images TV and movies
05286                 self.program_seriesid = None
05287                 if not isValidPosixFilename(filename) and program['seriesid'] != u'':
05288                     filename = program['seriesid']
05289                     self.program_seriesid = program['seriesid']
05290 
05291                 # Actual check for existing graphics
05292                 for dirct in self.config[graphicsDirectories[directory]]:
05293                     try:
05294                         dir_list = os.listdir(unicode(dirct, 'utf8'))
05295                     except (UnicodeEncodeError, TypeError):
05296                         dir_list = os.listdir(dirct)
05297                     for flenme in dir_list:
05298                         if fnmatch.fnmatch(flenme.lower(), (pattern % filename).lower()):
05299                             program[directory] = True
05300                             if directory == 'coverfile':
05301                                 total_posters_found +=1
05302                             elif directory == 'banner':
05303                                 total_banners_found +=1
05304                             else:
05305                                 total_fanart_found +=1
05306                             if mirodetails: # Update the Miro MythVideo records with any existing graphics
05307                                 mirodetails[directory] = self.rtnRelativePath(u'%s/%s' % (dirct, flenme), directory)
05308                             break
05309                     else:
05310                         continue
05311                     break
05312                 else:
05313                     program['need'] = True
05314                     program[directory] = False
05315 
05316             # Check if there are any graphics to download
05317             if not program['need']:
05318                 if not mirodetails:
05319                     filename = program['title']
05320                 elif mirodetails[u'tv']:
05321                     filename = program['title']
05322                 else:
05323                     filename = mirodetails[u'moviename']
05324                 self._displayMessage("All Graphics already downloaded for [%s]" % filename)
05325                 if mirodetails: # Update the Miro MythVideo records with any new graphics
05326                     self.updateMiroVideo(program)
05327                 continue
05328 
05329             if not mirodetails:
05330                 # It is more efficient to find inetref of movie once
05331                 if not program['subtitle'] and not program_override_tv:
05332                     if not program.has_key('inetref'): # Was the inetref number already found?
05333                         inetref = self._getTmdbIMDB(graphics_name, watched=True)
05334                         if not inetref:
05335                             self._displayMessage("No movie inetref [%s]" % graphics_name)
05336                             # Fake subtitle as this may be a TV series without a subtitle
05337                             program['subtitle']=' '
05338                         else:
05339                             self._displayMessage("Found movie inetref (%s),[%s]" % (inetref, graphics_name))
05340                             program['inetref'] = inetref
05341             elif not mirodetails[u'tv']:
05342                 program['inetref'] = mirodetails[u'inetref']
05343 
05344             # Download missing graphics
05345             for key in graphicsDirectories.keys():
05346                 if program[key]:    # Check if this type of graphic is already downloaded
05347                     continue
05348                 miromovieflag = False
05349                 if mirodetails:
05350                     if not mirodetails[u'tv']:
05351                         miromovieflag = True
05352                 # This is a TV episode or Miro TV show
05353                 if (program['subtitle'] or program_override_tv) and not miromovieflag:
05354                     results = self._getScheduledRecordedTVGraphics(program, key)
05355                     if results:
05356                         if not mirodetails:
05357                             filename = program['title']
05358                         elif mirodetails[u'tv']:
05359                             filename = program['title']
05360                         else:
05361                             filename = mirodetails[u'moviename']
05362                         if key == 'coverfile':
05363                             total_posters_downloaded +=1
05364                         elif key == 'banner':
05365                             total_banners_downloaded +=1
05366                         elif key == 'fanart':
05367                             total_fanart_downloaded +=1
05368                         if mirodetails:    # Save the filename for storing later
05369                             mirodetails[key] = results
05370                     else:
05371                         self._displayMessage("TV Series - No (%s) for [%s]" % (key, program['title']))
05372                 else: # This is a movie
05373                     title = program['title']
05374                     filename = program['title']
05375                     if miromovieflag:
05376                         title = mirodetails[u'inetref']
05377                         filename = mirodetails[u'inetref']
05378                     cfile = { 'file_seriesname': title,
05379                             'inetref': program['inetref'],
05380                             'seasno': 0,
05381                             'epno': 0,
05382                             'filepath':u'',
05383                             'filename': filename,
05384                             'ext':u'',
05385                             'categories':u''
05386                     }
05387                     if key == 'coverfile':
05388                         g_type = '-P'
05389                     else:
05390                         g_type = '-B'
05391                     results = self._getTmdbGraphics(cfile, g_type, watched=True)
05392                     if not results:
05393                         results = self._getSecondarySourceGraphics(cfile, key, watched=True)
05394                     if results:
05395                         if key == 'coverfile':
05396                             total_posters_downloaded +=1
05397                         elif key == 'fanart':
05398                             total_fanart_downloaded +=1
05399                         if mirodetails:    # Save the filename for storing later
05400                             mirodetails[key] = results
05401                     else:
05402                         if not mirodetails:
05403                             filename = program['title']
05404                         else:
05405                             filename = mirodetails[u'moviename']
05406                         self._displayMessage("No (%s) for [%s]" % (key, filename))
05407 
05408             if mirodetails: # Update the Miro MythVideo records with any new graphics
05409                 self.updateMiroVideo(program)
05410 
05411         # Print statistics
05412         sys.stdout.write(u'\n-----Scheduled & Recorded Statistics-------\nNumber of Scheduled & Recorded ......(% 5d)\nNumber of Fanart graphics found .....(% 5d)\nNumber of Poster graphics found .....(% 5d)\nNumber of Banner graphics found .....(% 5d)\nNumber of Fanart graphics downloaded (% 5d)\nNumber of Poster graphics downloaded (% 5d)\nNumber of Banner graphics downloaded (% 5d)\nNumber of Miro TV Shows ............ (% 5d)\nNumber of Miro Movie Trailers ...... (% 5d)\n' % (total_progs_checked, total_fanart_found, total_posters_found, total_banners_found, total_fanart_downloaded, total_posters_downloaded, total_banners_downloaded, total_miro_tv, total_miro_movies))
05413 
05414         if len(programs):
05415             sys.stdout.write(u'\n-------------Scheduled & Recorded----------\n')
05416             for program in programs:
05417                 if not program.has_key(u'miro'):
05418                     if program.has_key(u'miro_tv'):
05419                         if filter(is_not_punct_char, program[u'title'].lower()) in self.config['mb_movies'].keys():
05420                             sys.stdout.write(u'Miro Movie Trailer: %s\n' % (program['title'], ))
05421                         else:
05422                             sys.stdout.write(u'Miro TV Show: %s\n' % (program['title'], ))
05423                     else:
05424                         if program['subtitle']:
05425                             sys.stdout.write(u'%s\n' % (program['title'], ))
05426                         else:
05427                             if program['originalairdate'] != u'0000':
05428                                 sys.stdout.write(u'%s\n' % ("%s (%s)" % (program['title'], program['originalairdate'])))
05429                             else:
05430                                 sys.stdout.write(u'%s\n' % (program['title'], ))
05431                 elif program[u'miro'][u'tv']:
05432                     sys.stdout.write(u'Miro TV Show: %s\n' % (program['title'], ))
05433                 else:
05434                     sys.stdout.write(u'Miro Movie Trailer: %s\n' % (program[u'miro'][u'moviename'], ))
05435         return
05436     # end _downloadScheduledRecordedGraphics()
05437 
05438 
05439     def findFileInDir(self, filename, directories, suffix=None, fuzzy_match=False):
05440         '''Find if a file is in any of the specified directories. An exact match or a variation.
05441         return False - File not found in directories
05442         return True - Absolute file name and path
05443         '''
05444         (dirName, fileName) = os.path.split(filename)
05445         (fileBaseName, fileExtension) = os.path.splitext(fileName)
05446         if fuzzy_match: # Match even when the names are not exactly the same by removing punctuation
05447             for dirct in directories:
05448                 try:
05449                     dir_list = os.listdir(unicode(dirct, 'utf8'))
05450                 except (UnicodeEncodeError, TypeError):
05451                     dir_list = os.listdir(dirct)
05452                 match_list = []
05453                 for file_name in dir_list:
05454                     match_list.append(filter(is_not_punct_char, file_name.lower()))
05455                 if suffix:
05456                     if fileBaseName.find(suffix) == -1:
05457                         file_path = filter(is_not_punct_char, (u"%s%s%s" % (fileBaseName, suffix, fileExtension)).lower())
05458                         file_path2 = filter(is_not_punct_char, (u"%s%s" % (fileBaseName, fileExtension)).lower())
05459                     else:
05460                         file_path = filter(is_not_punct_char, (u"%s%s" % (fileBaseName, fileExtension)).lower())
05461                         file_path2 = filter(is_not_punct_char, (u"%s%s" % (fileBaseName.replace(suffix, u''), fileExtension)).lower())
05462                     if file_path in match_list:
05463                         return u'%s/%s' % (dirct, dir_list[match_list.index(file_path)])
05464                     if file_path2 in match_list:
05465                         return u'%s/%s' % (dirct, dir_list[match_list.index(file_path2)])
05466                     continue
05467                 else:
05468                     file_path = filter(is_not_punct_char, (u"%s%s" % (fileBaseName, fileExtension)).lower())
05469                     if file_path in match_list:
05470                         return u'%s/%s' % (dirct, dir_list[match_list.index(file_path)])
05471             else:
05472                 return False
05473         else: # Find an exact match
05474             for directory in directories:
05475                 if filename[0] != u'/' and dirName != u'':
05476                     dir_name = u"%s/%s" % (directory, dirName)
05477                 else:
05478                     dir_name = directory
05479                 if suffix:
05480                     if fileBaseName.find(suffix) == -1:
05481                         file_path = u"%s/%s%s%s" % (dir_name, fileBaseName, suffix, fileExtension)
05482                         file_path2 = u'%s/%s' % (dir_name, fileName)
05483                     else:
05484                         file_path = u'%s/%s' % (dir_name, fileName)
05485                         file_path2 = u'%s/%s' % (dir_name, fileName.replace(suffix, u''))
05486                     if os.path.isfile(file_path):
05487                         return file_path
05488                     if os.path.isfile(file_path2):
05489                         return file_path2
05490                     continue
05491                 else:
05492                     file_path = u'%s/%s' % (dir_name, fileName)
05493                     if os.path.isfile(file_path):
05494                         return file_path
05495             else:
05496                 return False
05497     # end findFileInDir()
05498 
05499 
05500     # Local Variables
05501     num_secondary_source_graphics_downloaded=0
05502     num_secondary_source_metadata_downloaded=0
05503 
05504     def processMythTvMetaData(self):
05505         '''Check each video file in the mythvideo directories download graphics files and meta data then
05506         update MythTV data base meta data with any new information.
05507         '''
05508         # Verify that the proper fields are present
05509         db_version = mythdb.settings.NULL.DBSchemaVer
05510         field_names = mythvideo.tablefields['videometadata']
05511         for field in ['season', 'episode', 'coverfile', 'screenshot', 'banner', 'fanart']:
05512             if not field in field_names:
05513                 sys.stderr.write(u"\n! Error: Your MythTv data base scheme version (%s) does not have the necessary fields at least (%s) is missing\n\n" % (db_version, field))
05514                 sys.exit(1)
05515 
05516         # Initailize and instance to the TMDB api
05517         apikey = "c27cb71cff5bd76e1a7a009380562c62"
05518         if self.config['interactive']:
05519             # themoviedb.org api key given by Travis Bell for Mythtv
05520             self.config['tmdb_api'] = tmdb_api.MovieDb(apikey,
05521                 mythtv = True,
05522                 interactive = True,
05523                 select_first = False,
05524                 debug = self.config['debug_enabled'],
05525                 custom_ui = None,
05526                 language = self.config['local_language'],
05527                 search_all_languages = True,)
05528         else:
05529             self.config['tmdb_api'] = tmdb_api.MovieDb(apikey,
05530                 mythtv = True,
05531                 interactive = False,
05532                 select_first = False,
05533                 debug = self.config['debug_enabled'],
05534                 language = self.config['local_language'],
05535                 search_all_languages = True,)
05536 
05537         # If there were directories specified move them and update the MythTV db meta data accordingly
05538         if self.config['video_dir']:
05539             if len(self.config['video_dir']) % 2 == 0:
05540                 validFiles = self._moveVideoFiles(self.config['video_dir'])
05541                 self.config[u'file_move_flag'] = False
05542             else:
05543                 sys.stderr.write(u"\n! Error: When specifying target (file or directory) to move to a destination (directory) they must always be in pairs (target and destination directory).\nYou specified an uneven number of variables (%d) for target and destination pairs.\nVariable count (%s)\n" % (len(self.config['video_dir']), self.config['video_dir']))
05544                 sys.exit(1)
05545 
05546         # Check if only missing inetref video's should be processed
05547         if self.config['mythtv_inetref'] or self.config['mythtv_ref_num']:
05548             validFiles = self._findMissingInetref()
05549             if validFiles == None:
05550                 sys.stderr.write(u"\n! Warning: There were no missing interef video files found.\n\n")
05551                 sys.exit(0)
05552             elif not len(validFiles):
05553                 sys.stderr.write(u"\n! Warning: There were no missing interef video files found.\n\n")
05554                 sys.exit(0)
05555 
05556         # Check if this is a Scheduled and Recorded graphics download request
05557         if self.config['mythtv_watched']:
05558             self._downloadScheduledRecordedGraphics()
05559             sys.exit(0)
05560 
05561         # Check if this is just a Janitor (clean up unused graphics files) request
05562         if self.config['mythtvjanitor']:
05563             self._graphicsCleanup()
05564             sys.exit(0)
05565 
05566         directories=self.config['mythvideo']
05567 
05568         if not len(directories):
05569             sys.stderr.write(u"\n! Error: There must be a video directory specified in MythTv\n")
05570             sys.exit(1)
05571 
05572         # Set statistics
05573         num_processed=0
05574         num_fanart_downloads=0
05575         num_posters_downloads=0
05576         num_banners_downloads=0
05577         num_episode_metadata_downloads=0
05578         num_movies_using_imdb_numbers=0
05579         num_symlinks_created=0
05580         num_mythdb_updates=0
05581         num_posters_below_min_size=0
05582         videos_with_small_posters=[]
05583         videos_using_imdb_numbers=[]
05584         videos_updated_metadata=[]
05585         missing_inetref=[]
05586 
05587         sys.stdout.write(u'Mythtv video database maintenance start: %s\n' % (datetime.datetime.now()).strftime("%Y-%m-%d %H:%M"))
05588 
05589         if not self.config['video_dir'] and not self.config['mythtv_inetref'] and not self.config['mythtv_ref_num']:
05590             allFiles = self._findFiles(directories, self.config['recursive'] , verbose = self.config['debug_enabled'])
05591             validFiles = self._processNames(allFiles, verbose = self.config['debug_enabled'], movies=True)
05592 
05593         if not len(validFiles):
05594             sys.stderr.write(u"\n! Error: No valid video files found\n")
05595             sys.exit(1)
05596 
05597         tv_series_season_format=u"%s/%s Season %d.%s"
05598         tv_series_format=u"%s/%s.%s"
05599         for cfile in validFiles:
05600             self._displayMessage(u"\nNow processing video file (%s)(%s)(%s)\n" % (cfile['filename'], cfile['seasno'], cfile['epno']))
05601             num_processed+=1
05602 
05603             # Find the MythTV meta data
05604             videopath = tv_series_format % (cfile['filepath'], cfile['filename'], cfile['ext'])
05605             try:
05606                 metadata = mythvideo.searchVideos(exactfile=videopath).next()
05607             except StopIteration:
05608                 try:
05609                     metadata = mythvideo.searchVideos(exactfile=self.rtnRelativePath(videopath, u'mythvideo'), host=localhostname.lower()).next()
05610                 except StopIteration:
05611                     # create new empty entry
05612                     if not self.config['interactive'] and not self.config['mythtv_guess']:
05613                         continue
05614                     sys.stdout.write(u"\n\nEntry does not exist in MythDB.  Adding (%s).\n" % cfile['filename'])
05615                     metadata = Video().create({'title':cfile['file_seriesname'], 'filename':''})
05616 
05617             if (metadata.category == 'none') and (metadata.year == 1895):
05618                 sys.stdout.write(u"\n\nEntry is set to default values.\nUpdating (%s).\n" % cfile['filename'])
05619                 filename = self.rtnRelativePath(videopath, u'mythvideo')
05620                 if filename.startswith('/'):
05621                     metadata.update({'filename': filename, u'host': u''})
05622                 else:
05623                     metadata.update({'filename': filename, u'host': localhostname.lower()})
05624 
05625             if (cfile['seasno'] == 0) and (cfile['epno'] == 0):
05626                 movie = True
05627             else:
05628                 movie = False
05629 
05630             # Get a dictionary of the existing meta data plus a copy for update comparison
05631             meta_dict={}
05632             vim = Video(metadata.intid, db=mythvideo)
05633             for key in vim.keys():
05634                 meta_dict[key] = vim[key]
05635 
05636             # Fix a metadata record that has an incorrectly initialized inetref number value
05637             if meta_dict['inetref'] == None:
05638                 meta_dict['inetref'] = u'00000000'
05639             available_metadata = dict(meta_dict)
05640 
05641             available_metadata['season']=cfile['seasno']
05642             available_metadata['episode']=cfile['epno']
05643 
05644             if available_metadata['title'] == u'':
05645                 available_metadata['title'] = cfile['file_seriesname']
05646 
05647             # Set whether a video file is stored in a Storage Group or not
05648             if available_metadata['filename'][0] == u'/':
05649                 self.absolutepath = True
05650             else:
05651                 self.absolutepath = False
05652 
05653             # There must be an Internet reference number. Get one for new records.
05654             if _can_int(meta_dict['inetref']) and not meta_dict['inetref'] == u'00000000' and not meta_dict['inetref'] == '':
05655                 if meta_dict['inetref'] == '99999999': # Records that are not updated by Jamu
05656                     continue
05657                 inetref = meta_dict['inetref']
05658                 cfile['inetref'] = meta_dict['inetref']
05659             else:
05660                 if movie:
05661                     if not self.config['interactive'] and not self.config['mythtv_guess']:
05662                         sys.stderr.write(u'\n! Warning: Skipping "%s" as there is no TMDB or IMDB number for this movie.\nUse interactive option (-I) or (-R) to select the TMDB or IMDB number.\n\n' % (cfile['file_seriesname']))
05663                         continue
05664                     inetref = self._getTmdbIMDB(available_metadata['title'])
05665                     cfile['inetref'] = inetref
05666                     if not inetref:
05667                         self._displayMessage(u"themoviedb.com does not recognize the movie (%s) - Cannot update metadata - skipping\n" % available_metadata['title'])
05668                         missing_inetref.append(available_metadata['title'])
05669                         continue
05670                     # Only update the reference number
05671                     if self.config['mythtv_ref_num'] or inetref == '99999999':
05672                         Video(metadata.intid, db=mythvideo).update({'inetref': inetref})
05673                         num_mythdb_updates+=1
05674                         videos_updated_metadata.append(cfile['filename'])
05675                         self._displayMessage(u"\nReference number (%s) added for (%s) \n" % (inetref, cfile['filename']))
05676                         continue
05677                 else:
05678                     copy = {}
05679                     for key in available_metadata.keys():
05680                         copy[key] = available_metadata[key]
05681                     tmp_dict = self._getTvdbMetadata(cfile, copy)
05682                     if not tmp_dict:
05683                         self._displayMessage(u"thetvdb.com does not recognize the Season(%d) Episode(%d) for video file(%s) - skipping\n" % (cfile['seasno'], cfile['epno'], videopath))
05684                         missing_inetref.append(available_metadata['title'])
05685                         continue
05686                     inetref = tmp_dict['inetref']
05687                     available_metadata['title'] = tmp_dict['title']
05688                     cfile['file_seriesname'] = tmp_dict['title']
05689                     # Only update the reference number and title
05690                     if self.config['mythtv_ref_num'] or inetref == '99999999':
05691                         if inetref == u'99999999':
05692                             Video(metadata.intid, db=mythvideo).update({'inetref': inetref})
05693                         else:
05694                             Video(metadata.intid, db=mythvideo).update({'inetref': inetref, 'title': tmp_dict['title']})
05695                         num_mythdb_updates+=1
05696                         videos_updated_metadata.append(cfile['filename'])
05697                         self._displayMessage(u"\nReference number (%s) added for (%s) \n" % (inetref, cfile['filename']))
05698                         continue
05699                 cfile['inetref'] = inetref
05700                 available_metadata['inetref'] = inetref
05701 
05702             if (meta_dict['subtitle'] == None or meta_dict['subtitle'] == '') and not movie:
05703                 tmp_subtitle = self._getSubtitle(cfile)
05704                 if tmp_subtitle == None:
05705                     self._displayMessage(u"thetvdb.com does not recognize the Season(%d) Episode(%d) for video file(%s) - skipping\n" % (cfile['seasno'], cfile['epno'], videopath))
05706                     continue
05707                 else:
05708                     available_metadata['subtitle'] = tmp_subtitle
05709                     available_metadata['title'] = self.config['series_name']
05710                     cfile['file_seriesname'] = self.config['series_name']
05711 
05712             # Check if current inetref is a IMDB#
05713             # If so then check it can be changed to tmdb#
05714             # If it can be changed then rename any graphics and update meta data
05715             if movie and len(inetref) == 7:
05716                 self._displayMessage(u"%s has IMDB# (%s)" % (available_metadata['title'], inetref))
05717                 num_movies_using_imdb_numbers+=1
05718                 videos_using_imdb_numbers.append(u"%s has IMDB# (%s)" % (available_metadata['title'], inetref))
05719                 movie_data = self._getTmdbMetadata(cfile, dict(available_metadata))
05720                 if movie_data.has_key('inetref'):
05721                     if available_metadata['inetref'] != movie_data['inetref']:
05722                         available_metadata['inetref'] = movie_data['inetref']
05723                         inetref = movie_data['inetref']
05724                         cfile['inetref'] = movie_data['inetref']
05725                         for graphic_type in ['coverfile', 'banner', 'fanart']: # Rename graphics files
05726                             if available_metadata[graphic_type] == None or available_metadata[graphic_type] == '':
05727                                 continue
05728                             graphic_file = self.rtnAbsolutePath(available_metadata[graphic_type], graphicsDirectories[graphic_type])
05729                             if os.path.isfile(graphic_file):
05730                                 filepath, filename = os.path.split(graphic_file)
05731                                 filename, ext = os.path.splitext( filename )
05732                                 ext = ext[1:]
05733                                 if self.config['simulation']:
05734                                     sys.stdout.write(
05735                                         u"Simulation renaming (%s) to (%s)\n" % (graphic_file, tv_series_format % (filepath, inetref+self.graphic_suffix[graphic_type], ext))
05736                                     )
05737                                 else:
05738                                     dest = tv_series_format % (filepath, inetref+self.graphic_suffix[graphic_type], ext)
05739                                     try:
05740                                         if not os.path.isfile(dest):
05741                                             os.rename(graphic_file, dest)
05742                                     except IOError, e:
05743                                         sys.stderr.write(
05744                                             u"Renaming image file (%s) to (%s) failed, error(%s)\n" % (graphic_file, dest, e))
05745 
05746                                     self._displayMessage(u"Renamed (%s) to (%s)\n" % (graphic_file, tv_series_format % (filepath, inetref+self.graphic_suffix[graphic_type], ext)))
05747                                 available_metadata[graphic_type]= self.rtnRelativePath(dest,  graphicsDirectories[graphic_type])
05748 
05749             ###############################################################################
05750             # START of metadata Graphics logic - Checking, downloading, renaming
05751             ###############################################################################
05752             for graphic_type in ['coverfile', 'banner', 'fanart']:
05753                 ###############################################################################
05754                 # START of MOVIE graphics updating
05755                 ###############################################################################
05756                 # Check that there are local graphics path for abs path video
05757                 # An abs path video can only use the FE specified graphic directories
05758                 if self.absolutepath:
05759                     if not len(self.config['localpaths'][graphicsDirectories[graphic_type]]):
05760                         continue
05761                     graphicsdirs = self.config['localpaths'][graphicsDirectories[graphic_type]]
05762                 else:
05763                     graphicsdirs = self.config[graphicsDirectories[graphic_type]]
05764                 if movie:
05765                     if graphic_type == 'banner':
05766                         continue
05767                     if graphic_type == 'coverfile':
05768                         g_type = '-P'
05769                     else:
05770                         g_type = '-B'
05771                     need_graphic = True
05772                     undersized_graphic = False
05773                     for ext in self.image_extensions:
05774                         for graphicsdir in graphicsdirs:
05775                             filename = self.findFileInDir(u"%s.%s" % (inetref, ext), [graphicsdir], suffix=self.graphic_suffix[graphic_type])
05776 
05777                             if filename:
05778                                 available_metadata[graphic_type]=self.rtnRelativePath(filename,  graphicsDirectories[graphic_type])
05779                                 if graphic_type == 'coverfile':
05780                                     try:
05781                                         (width, height) = self.config['image_library'].open(filename).size
05782                                         if width < self.config['min_poster_size']:
05783                                             num_posters_below_min_size+=1
05784                                             videos_with_small_posters.append(cfile['filename'])
05785                                             undersized_graphic = True
05786                                             break
05787                                     except IOError:
05788                                         undersized_graphic = True
05789                                         break
05790                                 need_graphic = False
05791                                 break
05792                         if not need_graphic:
05793                             break
05794 
05795                     if need_graphic == True:
05796                         dummy_graphic = self._getTmdbGraphics(cfile, g_type)
05797 
05798                         # Try secondary source if themoviedb.com did not have graphicrecord['title']
05799                         if dummy_graphic == None or undersized_graphic == True:
05800                             dummy_graphic = self._getSecondarySourceGraphics(cfile, graphic_type)
05801 
05802                         if dummy_graphic != None:
05803                             available_metadata[graphic_type] = self.rtnRelativePath(dummy_graphic,  graphicsDirectories[graphic_type])
05804                             if graphic_type == 'fanart':
05805                                 self._displayMessage(u"Movie - Added fan art for(%s)" % cfile['filename'])
05806                                 num_fanart_downloads+=1
05807                             else:
05808                                 self._displayMessage(u"Movie - Added a poster for(%s)" % cfile['filename'])
05809                                 num_posters_downloads+=1
05810                     continue
05811                     # END of Movie graphics updates ###############################################
05812                 else:
05813                     ###############################################################################
05814                     # START of TV Series graphics updating
05815                     ###############################################################################
05816                     need_graphic = False
05817                     new_format = True # Initialize that a graphics file NEEDS a new format
05818                     # Check if an existing TV series graphic file is in the old naming format
05819                     if available_metadata[graphic_type] != None and available_metadata[graphic_type] != 'No Cover' and available_metadata[graphic_type] != '':
05820                         graphic_file = self.rtnAbsolutePath(available_metadata[graphic_type], graphicsDirectories[graphic_type])
05821                         filepath, filename = os.path.split(graphic_file)
05822                         filename, ext = os.path.splitext( filename )
05823                         if filename.find(u' Season ') != -1:
05824                             new_format = False
05825                     else:
05826                         need_graphic = True
05827                     if need_graphic or new_format: # Graphic does not exist or is in an old format
05828                         for ext in self.image_extensions:
05829                             for graphicsdir in graphicsdirs:
05830                                 filename=self.findFileInDir(u"%s Season %d.%s" % (self.sanitiseFileName(available_metadata['title']), available_metadata['season'], ext), [graphicsdir], suffix=self.graphic_suffix[graphic_type], fuzzy_match=True)
05831                                 if filename:
05832                                     available_metadata[graphic_type]=self.rtnRelativePath(filename,  graphicsDirectories[graphic_type])
05833                                     need_graphic = False
05834                                     if graphic_type == 'coverfile':
05835                                         try:
05836                                             (width, height) = self.config['image_library'].open(filename).size
05837                                             if width < self.config['min_poster_size']:
05838                                                 num_posters_below_min_size+=1
05839                                                 videos_with_small_posters.append(cfile['filename'])
05840                                                 break
05841                                         except IOError:
05842                                             undersized_graphic = True
05843                                             break
05844                                     break
05845                             if not need_graphic:
05846                                 break
05847                         else:
05848                             graphic_file = self.rtnAbsolutePath(available_metadata[graphic_type], graphicsDirectories[graphic_type])
05849                             if not graphic_file == None:
05850                                 graphic_file = self.findFileInDir(graphic_file, [graphicsdir], suffix=self.graphic_suffix[graphic_type], fuzzy_match=True)
05851                             if graphic_file == None:
05852                                 need_graphic = True
05853                             if not need_graphic: # Have graphic but may be using an old naming convention
05854                                 must_rename = False
05855                                 season_missing = False
05856                                 suffix_missing = False
05857                                 if graphic_file.find(u' Season ') == -1: # Check for Season
05858                                     must_rename = True
05859                                     season_missing = True
05860                                 if graphic_file.find(self.graphic_suffix[graphic_type]) == -1:
05861                                     must_rename = True
05862                                     suffix_missing = True
05863                                 if must_rename:
05864                                     filepath, filename = os.path.split(graphic_file)
05865                                     baseFilename, ext = os.path.splitext( filename )
05866                                     baseFilename = self.sanitiseFileName(baseFilename)
05867                                     if season_missing and suffix_missing:
05868                                         newFilename = u"%s/%s Season %d%s%s" % (filepath, baseFilename, available_metadata['season'], self.graphic_suffix[graphic_type], ext)
05869                                     elif suffix_missing:
05870                                         newFilename = u"%s/%s%s%s" % (filepath, baseFilename, self.graphic_suffix[graphic_type], ext)
05871                                     elif season_missing:
05872                                         baseFilename = baseFilename.replace(self.graphic_suffix[graphic_type], u'')
05873                                         newFilename = u"%s/%s Season %d%s%s" % (filepath, baseFilename.replace(u' Season X', u''), available_metadata['season'], self.graphic_suffix[graphic_type], ext)
05874                                     if self.config['simulation']:
05875                                         sys.stdout.write(
05876                                             u"Simulation renaming (%s) to (%s)\n" % (graphic_file, newFilename)
05877                                         )
05878                                     else:
05879                                         os.rename(graphic_file, newFilename)
05880                                     available_metadata[graphic_type]= self.rtnRelativePath(newFilename,  graphicsDirectories[graphic_type])
05881                                 else:
05882                                     available_metadata[graphic_type]= self.rtnRelativePath(graphic_file,  graphicsDirectories[graphic_type])
05883                             else: # Must see if a graphic is on thetvdb wiki
05884                                 if graphic_type == 'coverfile' or graphic_type == 'banner':
05885                                     available_metadata[graphic_type] = self.rtnRelativePath(self._getTvdbGraphics(cfile, graphic_type),  graphicsDirectories[graphic_type])
05886                                     if available_metadata[graphic_type] == None:
05887                                         tmp = self._getTvdbGraphics(cfile, graphic_type, toprated=True)
05888                                         if tmp!= None:
05889                                             tmp_fullfilename = self.rtnAbsolutePath(tmp, graphicsDirectories[graphic_type])
05890                                             filepath, filename = os.path.split(tmp_fullfilename)
05891                                             baseFilename, ext = os.path.splitext( filename )
05892                                             baseFilename = self.sanitiseFileName(baseFilename)
05893                                             baseFilename = baseFilename.replace(self.graphic_suffix[graphic_type], u'')
05894                                             newFilename = u"%s/%s Season %d%s%s" % (filepath, baseFilename.replace(u' Season X', u''), available_metadata['season'], self.graphic_suffix[graphic_type], ext)
05895                                             if self.config['simulation']:
05896                                                 sys.stdout.write(
05897                                                     u"Simulation rename (%s) to (%s)\n" % (tmp_fullfilename,newFilename)
05898                                                 )
05899                                             else:
05900                                                 self._displayMessage(u"Rename existing graphic %s for series (%s)" % (graphic_type, available_metadata['title']))
05901                                                 try:
05902                                                     os.rename(tmp_fullfilename, newFilename)
05903                                                     if graphic_type == 'coverfile':
05904                                                         self._displayMessage("1-Added a poster for(%s)" % cfile['filename'])
05905                                                         num_posters_downloads+=1
05906                                                     else:
05907                                                         self._displayMessage("1-Added a banner for(%s)" % cfile['filename'])
05908                                                         num_banners_downloads+=1
05909                                                     available_metadata[graphic_type] = self.rtnRelativePath(newFilename,  graphicsDirectories[graphic_type])
05910                                                 except IOError, e:
05911                                                     sys.stderr.write(
05912                                                         u"IOError coping (%s) to (%s)\nError:(%s)\n" % (tmp_fullfilename, newFilename, e))
05913                                         else: # Try a secondary source
05914                                             dummy = self._getSecondarySourceGraphics(cfile, graphic_type)
05915                                             if dummy:
05916                                                 if graphic_type == 'coverfile':
05917                                                     self._displayMessage(u"1-Secondary source poster for(%s)" % cfile['filename'])
05918                                                     num_posters_downloads+=1
05919                                                 else:
05920                                                     self._displayMessage(u"1-Secondary source banner for(%s)" % cfile['filename'])
05921                                                     num_banners_downloads+=1
05922                                                 available_metadata[graphic_type] = self.rtnRelativePath(dummy,  graphicsDirectories[graphic_type])
05923                                 else: # download fanart
05924                                     tmp = self.rtnAbsolutePath(self._getTvdbGraphics(cfile, graphic_type, toprated=True), graphicsDirectories['fanart'])
05925                                     if tmp!= None:
05926                                         filepath, filename = os.path.split(tmp)
05927                                         baseFilename, ext = os.path.splitext( filename )
05928                                         baseFilename = self.sanitiseFileName(baseFilename)
05929                                         baseFilename = baseFilename.replace(self.graphic_suffix[graphic_type], u'')
05930                                         newFilename = u"%s/%s Season %d%s%s" % (filepath, baseFilename.replace(u' Season X', u''), available_metadata['season'], self.graphic_suffix[graphic_type], ext)
05931                                         if self.config['simulation']:
05932                                             sys.stdout.write(
05933                                                 u"Simulation fanart rename (%s) to (%s)\n" % (tmp, newFilename)
05934                                             )
05935                                         else:
05936                                             try:
05937                                                 os.rename(self.rtnAbsolutePath(tmp, graphicsDirectories[graphic_type]), newFilename)
05938                                                 available_metadata['fanart'] = self.rtnRelativePath(newFilename,  graphicsDirectories['fanart'])
05939                                                 num_fanart_downloads+=1
05940                                             except IOError, e:
05941                                                 sys.stderr.write(
05942                                                     u"IOError coping (%s) to (%s)\nError:(%s)\n" % (self.rtnAbsolutePath(tmp, graphicsDirectories[graphic_type]), newFilename, e))
05943                                     else: # Try a secondary source
05944                                         dummy = self._getSecondarySourceGraphics(cfile, graphic_type)
05945                                         if dummy:
05946                                             available_metadata['fanart'] = self.rtnRelativePath(dummy,  graphicsDirectories['fanart'])
05947                                             num_fanart_downloads+=1
05948                     # END of TV Series graphics updating
05949             ###############################################################################
05950             # END of metadata Graphics logic - Checking, downloading, renaming
05951             ###############################################################################
05952 
05953              ###############################################################################
05954             # START of metadata text logic - Checking, downloading, renaming
05955             ###############################################################################
05956             # Clean up meta data code
05957             if movie:
05958                 if available_metadata['rating'] == 'TV Show':
05959                     available_metadata['rating'] = 'NR'
05960 
05961             # Check if any meta data needs updating
05962             metadata_update = True
05963             for key in available_metadata.keys():
05964                 if key in self.config['metadata_exclude_as_update_trigger']:
05965                     continue
05966                 else:
05967                     if key == 'rating' and (available_metadata[key] == 'NR' or available_metadata[key] ==  '' or available_metadata[key] == 'Unknown'):
05968                         self._displayMessage(
05969                         u"At least (%s) needs updating\n" % (key))
05970                         break
05971                     if key == 'userrating' and available_metadata[key] == 0.0:
05972                         self._displayMessage(
05973                         u"At least (%s) needs updating\n" % (key))
05974                         break
05975                     if key == 'length' and available_metadata[key] == 0:
05976                         self._displayMessage(
05977                         u"At least (%s) needs updating\n" % (key))
05978                         break
05979                     if key == 'category' and available_metadata[key] == 0:
05980                         self._displayMessage(
05981                         u"At least (%s) needs updating\n" % (key))
05982                         break
05983                     if key == 'year' and (available_metadata[key] == 0 or available_metadata[key] == 1895):
05984                         self._displayMessage(
05985                         u"At least (%s) needs updating\n" % (key))
05986                         break
05987                     if movie and key == 'subtitle': # There are no subtitles in movies
05988                         continue
05989                     if movie and key == 'plot' and available_metadata[key] != None:
05990                         if len(available_metadata[key].split(' ')) < 10:
05991                             self._displayMessage(
05992                             u"The plot is less than 10 words check if a better plot exists\n")
05993                             break
05994                     if key == 'releasedate' and (available_metadata[key] == None or available_metadata[key] == date(1,1,1)):
05995                         self._displayMessage(
05996                         u"At least (%s) needs updating\n" % (key))
05997                         break
05998                     if key == 'hash' and (available_metadata['hash'] == u'' or available_metadata['hash'] == None):
05999                         if (os.path.getsize(u'%s/%s.%s' % (cfile['filepath'], cfile['filename'], cfile['ext'])) < 65536 * 2):
06000                             continue
06001                         self._displayMessage(
06002                         u"At least (%s) needs updating\n" % (key))
06003                         break
06004                     if available_metadata[key] == None or available_metadata[key] == '' or available_metadata[key] == 'None' or available_metadata[key] == 'Unknown':
06005                         self._displayMessage(
06006                         u"At least (%s) needs updating\n" % (key))
06007                         break
06008             else:
06009                 metadata_update = False
06010                 if not movie and not len(available_metadata['inetref']) >= 5:
06011                     self._displayMessage(
06012                     u"At least (%s) needs updating\n" % ('inetref'))
06013                     metadata_update = True
06014                 # Find the video file's real duration in minutes
06015                 try:
06016                     length = self._getVideoLength(u'%s/%s.%s' % (cfile['filepath'], cfile['filename'], cfile['ext'], ))
06017                 except:
06018                     length = False
06019                 if length:
06020                     if length != available_metadata['length']:
06021                         self._displayMessage(u"Video file real length (%d) minutes needs updating\n" % (length))
06022                         metadata_update = True
06023 
06024             # Fetch meta data
06025             genres_cast={'genres': u'', 'cast': u''}
06026             if metadata_update:
06027                 copy = dict(available_metadata)
06028                 if movie:
06029                     tmp_dict = self._getTmdbMetadata(cfile, copy)
06030                 else:
06031                     tmp_dict = self._getTvdbMetadata(cfile, copy)
06032                 num_episode_metadata_downloads+=1
06033                 # Update meta data
06034                 if tmp_dict:
06035                     for key in ['genres', 'cast', 'countries']:
06036                         if tmp_dict.has_key(key):
06037                             genres_cast[key] = tmp_dict[key]
06038                     for key in available_metadata.keys():
06039                         if key in self.config['metadata_exclude_as_update_trigger']:
06040                             continue
06041                         else:
06042                             if not tmp_dict.has_key(key):
06043                                 continue
06044                             if key == 'userrating' and available_metadata[key] == 0.0:
06045                                 available_metadata[key] = tmp_dict[key]
06046                                 continue
06047                             if key == 'length':
06048                                 try:
06049                                     length = self._getVideoLength(u'%s/%s.%s' %(cfile['filepath'], cfile['filename'], cfile['ext'], ))
06050                                 except:
06051                                     length = False
06052                                 if length:
06053                                     available_metadata['length'] = length
06054                                 else:
06055                                     available_metadata[key] = tmp_dict[key]
06056                                 continue
06057                             available_metadata[key] = tmp_dict[key]
06058 
06059             # Fix fields that must be prepared for insertion into data base
06060             available_metadata['title'] = self._make_db_ready(available_metadata['title'])
06061             available_metadata['director'] = self._make_db_ready(available_metadata['director'])
06062             available_metadata['plot'] = self._make_db_ready(available_metadata['plot'])
06063             if available_metadata['year'] == 0:
06064                 available_metadata['year'] = 1895
06065             if available_metadata['coverfile'] == None:
06066                 available_metadata['coverfile'] = u'No Cover'
06067             if len(genres_cast['genres']) and available_metadata['category'] == 'none':
06068                 try:
06069                     genres = genres_cast['genres'][:genres_cast['genres'].index(',')]
06070                 except:
06071                     genres = genres_cast['genres']
06072                 available_metadata['category'] = genres
06073                 self._displayMessage(u"Category added for (%s)(%s)" % (available_metadata['title'], available_metadata['category']))
06074 
06075             # Make sure graphics relative/absolute paths are set PROPERLY based
06076             # on the 'filename' field being a relative or absolute path. A filename with an absolite path
06077             # CAN ONLY have graphics baed on absolute paths.
06078             # A filename with a relative path can have mixed absolute and relative path graphic files
06079             if available_metadata[u'filename'][0] == u'/':
06080                 available_metadata[u'host'] = u''
06081                 for key in [u'coverfile', u'banner', u'fanart']:
06082                     if available_metadata[key] != None and available_metadata[key] != u'No Cover' and available_metadata[key] != u'':
06083                         if available_metadata[key][0] != u'/':
06084                             tmp = self.rtnAbsolutePath(available_metadata[key], graphicsDirectories[key])
06085                             if tmp[0] != u'/':
06086                                 if key == u'coverfile':
06087                                     available_metadata[key] = u'No Cover'
06088                                 else:
06089                                     available_metadata[key] = u''
06090             else:
06091                 available_metadata[u'host'] = localhostname.lower()
06092 
06093             ###############################################################################
06094             # END of metadata text logic - Checking, downloading, renaming
06095             ###############################################################################
06096 
06097             ###############################################################################
06098             # START of metadata updating the MythVideo record when graphics or text has changed
06099             ###############################################################################
06100             # Check if any new information was found
06101             if not self.config['overwrite']:
06102                 for key in available_metadata.keys():
06103                     if available_metadata[key] != meta_dict[key]:
06104                         if available_metadata[key] == u'' and meta_dict[key] == None:
06105                             continue
06106                         if available_metadata[key] == u'' and meta_dict[key] == u'Unknown':
06107                             continue
06108                         try:
06109                             self._displayMessage(
06110                             u"1-At least (%s)'s value(%s) has changed new(%s)(%s) old(%s)(%s)\n" % (cfile['filename'], key, available_metadata[key], type(available_metadata[key]), meta_dict[key], type(meta_dict[key])))
06111                         except:
06112                             self._displayMessage(
06113                             u"2-At least (%s)'s value(%s) has changed new(%s) old(%s)\n" % (cfile['filename'], key, type(available_metadata[key]), type(meta_dict[key])))
06114                         break
06115                 else:
06116                     self._displayMessage(
06117                         u"Nothing to update for video file(%s)\n" % cfile['filename']
06118                     )
06119                     continue
06120 
06121             if self.config['simulation']:
06122                 sys.stdout.write(
06123                     u"Simulation MythTV DB update for video file(%s)\n" % cfile['filename']
06124                 )
06125                 for key in available_metadata.keys():
06126                     print key,"        ", available_metadata[key]
06127                 for key in genres_cast.keys():
06128                     sys.stdout.write(u"Key(%s):(%s)\n" % (key, genres_cast[key]))
06129                 else:
06130                     sys.stdout.write('\n')
06131             else:
06132                 # Clean up a few fields before updating Mythdb
06133                 if available_metadata['showlevel'] == 0:    # Allows mythvideo to display this video
06134                     available_metadata['showlevel'] = 1
06135                 Video(metadata.intid, db=mythvideo).update(available_metadata)
06136                 num_mythdb_updates+=1
06137                 videos_updated_metadata.append(cfile['filename'])
06138                 for key in ['genres', 'cast', 'countries']:
06139                     if key == 'genres' and len(cfile['categories']):
06140                         genres_cast[key]+=cfile['categories']
06141                     if genres_cast.has_key(key):
06142                         self._addCastGenreCountry( genres_cast[key], Video(metadata.intid, db=mythvideo), key)
06143                 self._displayMessage(
06144                     u"Updated Mythdb for video file(%s)\n" % cfile['filename']
06145                 )
06146             ###############################################################################
06147             # END of metadata updating the MythVideo record when graphics or text has changed
06148             ###############################################################################
06149 
06150         sys.stdout.write(u"\nMythtv video database maintenance ends at  : %s\n" % (datetime.datetime.now()).strftime("%Y-%m-%d %H:%M"))
06151 
06152         # Print statistics
06153         sys.stdout.write(u'\n------------------Statistics---------------\nNumber of video files processed .....(% 5d)\nNumber of Fanart graphics downloaded (% 5d)\nNumber of Poster graphics downloaded (% 5d)\nNumber of Banner graphics downloaded (% 5d)\nNumber of 2nd source graphics downld (% 5d)\nNumber of metadata downloads.........(% 5d)\nNumber of 2nd source metadata found .(% 5d)\nNumber of symbolic links created.....(% 5d)\nNumber of Myth database updates......(% 5d)\nNumber of undersized posters ........(% 5d)\nNumber of Movies using IMDB numbers .(% 5d)\n' % (num_processed, num_fanart_downloads, num_posters_downloads, num_banners_downloads, self.num_secondary_source_graphics_downloaded, num_episode_metadata_downloads, self.num_secondary_source_metadata_downloaded, num_symlinks_created, num_mythdb_updates, num_posters_below_min_size, num_movies_using_imdb_numbers))
06154 
06155         if len(videos_updated_metadata):
06156             sys.stdout.write(u'\n--------------Updated Video Files----------\n' )
06157             for videofile in videos_updated_metadata:
06158                 sys.stdout.write(u'%s\n' % videofile)
06159         if len(missing_inetref):
06160             sys.stdout.write(u'\n----------------No Inetref Found-----------\n' )
06161             for videofile in missing_inetref:
06162                 sys.stdout.write(u'%s\n' % videofile)
06163         if len(videos_with_small_posters):
06164             sys.stdout.write(u'\n---------------Under sized Poster----------\n' )
06165             for videofile in videos_with_small_posters:
06166                 sys.stdout.write(u'%s\n' % videofile)
06167         if len(videos_using_imdb_numbers):
06168             sys.stdout.write(u'\n---------------Movies with IMDB#s----------\n' )
06169             for videofile in videos_using_imdb_numbers:
06170                 sys.stdout.write(u'%s\n' % videofile)
06171         return None
06172     # end processMythTvMetaData
06173 
06174     def __repr__(self):    # Just a place holder
06175         return self.config
06176     # end __repr__
06177 
06178 # end MythTvMetaData
06179 
06180 def simple_example():
06181     """Simple example of using jamu
06182     Displays the poster graphics URL(s) and episode meta data for the TV series Sanctuary, season 1
06183     episode 3
06184     returns None if there was no data found for the request TV series
06185     returns False if there is no TV series as specified
06186     returns a string with poster URLs and episode meta data
06187     """
06188     # Get an instance of the variable configuration information set to default values
06189     configuration = Configuration(interactive = True, debug = False)
06190 
06191     # Set the type of data to be returned
06192     configuration.changeVariable('get_poster', True)
06193     configuration.changeVariable('get_ep_meta', True)
06194 
06195     # Validate specific variables and set the TV series information
06196     configuration.validate_setVariables(['Sanctuary', '1', '3'])
06197 
06198     # Get an instance of the tvdb process function and fetch the data
06199     process = Tvdatabase(configuration.config)
06200     results = process.processTVdatabaseRequests()
06201 
06202     if results != None and results != False:        # Print the returned data string to the stdout
06203         print process.processTVdatabaseRequests().encode('utf8')
06204 # end simple_example
06205 
06206 
06207 def main():
06208     """Support jamu from the command line
06209     returns True
06210     """
06211     parser = OptionParser(usage=u"%prog usage: jamu -hbueviflstdnmoCRFUDSGN [parameters]\n <series name/SID or 'series/SID and season number' or 'series/SID and season number and episode number' or 'series/SID and episode name' or video file/directory paired with destination directory'>")
06212 
06213     parser.add_option(  "-b", "--debug", action="store_true", default=False, dest="debug",
06214                         help=u"Show debugging info")
06215     parser.add_option(  "-u", "--usage", action="store_true", default=False, dest="usage",
06216                         help=u"Display the six main uses for this jamu")
06217     parser.add_option(  "-e", "--examples", action="store_true", default=False, dest="examples",
06218                         help=u"Display examples for executing the jamu script")
06219     parser.add_option(  "-v", "--version", action="store_true", default=False, dest="version",
06220                         help=u"Display version and author information")
06221     parser.add_option(  "-i", "--interactive", action="store_true", default=False, dest="interactive",
06222                         help=u"Interactive mode allows selection of a specific Series from a series list")
06223     parser.add_option(  "-f", "--flags_options", action="store_true", default=False,dest="flags_options",
06224                         help=u"Display all variables and settings then exit")
06225     parser.add_option(  "-l", "--language", metavar="LANGUAGE", default=u'en', dest="language",
06226                         help=u"Select data that matches the specified language fall back to english if nothing found (e.g. 'es' Español, 'de' Deutsch ... etc)")
06227     parser.add_option(  "-s", "--simulation", action="store_true", default=False, dest="simulation",
06228                         help=u"Simulation (dry run), no downloads are performed or data bases altered")
06229     parser.add_option(  "-t", "--toprated", action="store_true", default=False, dest="toprated",
06230                         help=u"Only display/download the top rated TV Series graphics")
06231     parser.add_option(  "-d", "--download", action="store_true", default=False, dest="download",
06232                         help=u"Download and save the graphics and/or meta data")
06233     parser.add_option(  "-n", "--nokeys", action="store_true", default=False, dest="nokeys",
06234                         help=u"Do not add data type keys to data values when displaying data")
06235     parser.add_option(  "-m", "--maximum", metavar="MAX", default=None, dest="maximum",
06236                         help=u"Limit the number of graphics per type downloaded.           e.g. --maximum=6")
06237     parser.add_option(  "-o", "--overwrite", action="store_true", default=False, dest="overwrite",
06238                         help=u"Overwrite any matching files already downloaded")
06239     parser.add_option(  "-C", "--user_config", metavar="FILE", default="", dest="user_config",
06240                         help=u"User specified configuration variables.                     e.g --user_config='~/.jamu/jamu.conf'")
06241     parser.add_option(  "-F", "--filename", action="store_true", default=False, dest="ret_filename",
06242                         help=u"Display a formated filename for an episode")
06243     parser.add_option(  "-U", "--update", action="store_true", default=False, dest="update",
06244                         help=u"Update a meta data file if local episode meta data is older than what is available on thetvdb.com")
06245     parser.add_option(  "-D", "--mythtvdir", action="store_true", default=False, dest="mythtvdir",
06246                         help=u"Store graphic files into the MythTV DB specified dirs")
06247     parser.add_option(  "-M", "--mythtvmeta", action="store_true", default=False, dest="mythtvmeta",
06248                         help=u"Add/update TV series episode or movie meta data in MythTV DB")
06249     parser.add_option(  "-V", "--mythtv_verbose", action="store_true", default=False, dest="mythtv_verbose",
06250                         help=u"Display verbose messages when performing MythTV metadata maintenance")
06251     parser.add_option(  "-J", "--mythtvjanitor", action="store_true", default=False, dest="mythtvjanitor",
06252                         help=u"Remove unused graphics (poster, fanart, banners) with the graphics janitor. Any graphics not associated with atleast one MythTV video file record is delected.")
06253     parser.add_option(  "-N", "--mythtvNFS", action="store_true", default=False, dest="mythtvNFS",
06254                         help=u"This option overrides Jamu's restrictions on processing NFS mounted Video and/or graphic files.")
06255     parser.add_option(  "-I", "--mythtv_inetref", action="store_true", default=False, dest="mythtv_inetref",
06256                         help=u"Find and interactively update any missing Interent reference numbers e.g. IMDB. This option is ONLY active if the -M option is also selected.")
06257     parser.add_option(  "-W", "--mythtv_watched", action="store_true", default=False, dest="mythtv_watched",
06258                         help=u"Download graphics for Scheduled and Recorded videos. This option is ONLY active if the -M option is also selected.")
06259     parser.add_option(  "-G", "--mythtv_guess", action="store_true", default=False, dest="mythtv_guess",
06260                         help=u"Guess at the inetref for a video. This option is ONLY active if the -M option is also selected.")
06261     parser.add_option(  "-S", "--selected_data", metavar="TYPES", default=None, dest="selected_data",
06262                         help=u"Select one of more data types to display or download, P-poster, B-Banner, F-Fanart, E-Episode data, I-Episode Image. e.g. --selected_data=PBFEI gets all types of data")
06263     parser.add_option(  "-R", "--mythtv_ref_num", action="store_true", default=False, dest="mythtv_ref_num",
06264                         help=u"Start an interactive session that ONLY adds the TVDB/TMDB reference numbers to when missing. No meta data or images will be concurrently downloaded.")
06265 
06266     opts, series_season_ep = parser.parse_args()
06267 
06268     if opts.debug:
06269         print "opts", opts
06270         print "\nargs", series_season_ep
06271 
06272     # Set the default configuration values
06273     if opts.mythtv_inetref or opts.mythtv_ref_num:
06274         opts.interactive = True
06275     configuration = Configuration(interactive = opts.interactive, debug = opts.debug)
06276 
06277     if opts.usage:                    # Display usage information
06278         sys.stdout.write(usage_txt+'\n')
06279         sys.exit(0)
06280 
06281     if opts.examples:                    # Display example information
06282         sys.stdout.write(examples_txt+'\n')
06283         sys.exit(0)
06284 
06285     if opts.version == True:        # Display program information
06286         sys.stdout.write(u"\nTitle: (%s); Version: (%s); Author: (%s)\n%s\n" % (
06287         __title__, __version__, __author__, __purpose__ ))
06288         sys.exit(0)
06289 
06290     # Verify that only one instance of the following options is running at any one time
06291     # Options (-M, -MW and -MG)
06292     options = u''
06293     if opts.mythtvmeta:
06294         options+=u'M'
06295     else:
06296         MythLog._setlevel('none') # There cannot be any logging messages with non -M options
06297     if opts.mythtvmeta and opts.mythtv_watched:
06298         options+=u'W'
06299     if opts.mythtvmeta and opts.mythtv_guess:
06300         options+=u'G'
06301     if opts.mythtvmeta and opts.mythtvjanitor:  # No instance check with the janitor option
06302         options+=u'J'
06303     if opts.mythtvmeta and opts.mythtv_inetref: # No instance check with the interactive mode option
06304         options+=u'I'
06305     if options in [u'M', u'MW', u'MG']:
06306         jamu_instance = singleinstance(u'/tmp/Jamu_%s_instance.pid' % options)
06307         #
06308         # check is another instance of Jamu is running
06309         #
06310         if jamu_instance.alreadyrunning():
06311             print u'\n! Error: An instance of Jamu (-%s) is already running only one instance can run at a time.\nOne of the meta data sources may be off-line or very slow.\n' % options
06312             sys.exit(0)
06313 
06314     # Message the user that they are using incompatible options with the -MW option
06315     if opts.mythtvmeta and opts.mythtv_watched and (opts.mythtv_inetref or opts.interactive):
06316         print u'\n! Error: There us no Interactive mode (-I or -i) for the Jamu (-MW) option.\nPlease change your options and try again.\n'
06317         sys.exit(1)
06318 
06319     # Message the user that they are using incompatible options -R and -I or -i
06320     if opts.mythtvmeta and opts.mythtv_ref_num and opts.mythtv_inetref:
06321         print u'\n! Error: The (-R) and (-I) options are mutually exclusive.\nPlease change your options and try again.\n'
06322         sys.exit(1)
06323 
06324     # Apply any command line switches
06325     configuration.changeVariable('local_language', opts.language)
06326     configuration.changeVariable('simulation', opts.simulation)
06327     configuration.changeVariable('toprated', opts.toprated)
06328     configuration.changeVariable('download', opts.download)
06329     configuration.changeVariable('nokeys', opts.nokeys)
06330     configuration.changeVariable('maximum', opts.maximum)
06331     configuration.changeVariable('overwrite', opts.overwrite)
06332     configuration.changeVariable('ret_filename', opts.ret_filename)
06333     configuration.changeVariable('update', opts.update)
06334     configuration.changeVariable('mythtvdir', opts.mythtvdir)
06335     configuration.changeVariable('mythtvmeta', opts.mythtvmeta)
06336     configuration.changeVariable('mythtv_inetref', opts.mythtv_inetref)
06337     configuration.changeVariable('mythtv_ref_num', opts.mythtv_ref_num)
06338     configuration.changeVariable('mythtv_watched', opts.mythtv_watched)
06339     configuration.changeVariable('mythtv_guess', opts.mythtv_guess)
06340     configuration.changeVariable('mythtv_verbose', opts.mythtv_verbose)
06341     configuration.changeVariable('mythtvjanitor', opts.mythtvjanitor)
06342     configuration.changeVariable('mythtvNFS', opts.mythtvNFS)
06343     configuration.changeVariable('data_flags', opts.selected_data)
06344 
06345     # Check if the user wants to change options via a configuration file
06346     if opts.user_config != '':    # Did the user want to override the default config file name/location
06347         configuration.setUseroptions(opts.user_config)
06348     else:
06349         default_config = u"%s/%s" % (os.path.expanduser(u"~"), u".mythtv/jamu.conf")
06350         if os.path.isfile(default_config):
06351             configuration.setUseroptions(default_config)
06352         else:
06353             print u"\nThere was no default Jamu configuration file found (%s)\n" % default_config
06354 
06355     if opts.flags_options:                # Display option variables
06356         if len(series_season_ep):
06357             configuration.validate_setVariables(series_season_ep)
06358         else:
06359             configuration.validate_setVariables(['FAKE SERIES NAME','FAKE EPISODE NAME'])
06360         configuration.displayOptions()
06361         sys.exit(0)
06362 
06363     # Validate specific variables
06364     configuration.validate_setVariables(series_season_ep)
06365 
06366     if configuration.config['mythtvmeta']:
06367         process = MythTvMetaData(configuration.config)
06368         process.processMythTvMetaData()
06369     elif configuration.config['video_dir']:
06370         process = VideoFiles(configuration.config)
06371         results = process.processFileOrDirectory()
06372         if results != None and results != False:
06373             print process.processFileOrDirectory().encode('utf8')
06374     else:
06375         process = Tvdatabase(configuration.config)
06376         results = process.processTVdatabaseRequests()
06377         if results != None and results != False:
06378             print process.processTVdatabaseRequests().encode('utf8')
06379     return True
06380 # end main
06381 
06382 if __name__ == "__main__":
06383     main()
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends