|
MythTV 0.25-pre
|
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 & values to ASCII equivalents 02515 return the modified text 02516 """ 02517 if not text: return text 02518 text = text.replace(""", 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()
1.7.4