diff --git a/config.py b/config.py index f4aa1b7..c7e6725 100644 --- a/config.py +++ b/config.py @@ -7,8 +7,11 @@ # A list of MythTV recording directories, full paths in quotes, separated by commas mythtv_recording_dirs = ["/pool/mythtv/recordings-kids/"] +# Can be "symlink", "hardlink" +target_type = "symlink" + # Path to write symlinks -symlinks_dir = "/pool/myth2kodi/recordings/" +destination_dir = "/pool/myth2kodi/recordings/" # API key for TheTVDB ttvdb_key = "" diff --git a/myth2kodi.py b/myth2kodi.py index 24d5845..0aee8f2 100644 --- a/myth2kodi.py +++ b/myth2kodi.py @@ -9,7 +9,7 @@ Description: A script for generating a library of MythTV show recordings for Kodi(XBMC). The key feature of this script is that "Specials" (episodes with the same series title, but missing show and episode info) are grouped together under the - **same series** for easy navigation in Kodi. To generate the library, recordings are symlinked, and metadata and + **same series** for easy navigation in Kodi. To generate the library, recordings are linked, and metadata and image links (posters, fanart, and banners) for each series are pulled from either TheTVDB or TheMovieDB depending on the "inetref" value in MythTV. Commercial detection is done with comskip. --------------------------- @@ -18,6 +18,7 @@ import httplib import os +import shutil import xml.etree.cElementTree as ET from lxml import etree as ET2 import xml.dom.minidom as dom @@ -61,15 +62,15 @@ parser.add_argument('--add-all', dest='add_all', action='store_true', default=False, help='Add all MythTV recordings that are missing.') parser.add_argument('--show-status', dest='show_status', action='store_true', default=False, - help='Print the output status showing total and new series, episodes, and specials. This will not write any new symlinks or files.') + help='Print the output status showing total and new series, episodes, and specials. This will not write any new links or files.') parser.add_argument('--comskip', dest='comskip', action='store', metavar='', help="Full path to file name of MythTV recording, used to comskip just a single recording.") parser.add_argument('--comskip-all', dest='comskip_all', action='store_true', default=False, - help='Run comskip on all video files found recursively in the "symlinks_dir" path from config.py.') + help='Run comskip on all video files found recursively in the "destination_dir" path from config.py.') parser.add_argument('--comskip-off', dest='comskip_off', action='store_true', default=False, help='Turn off comskip when adding a single recording with --add.') parser.add_argument('--comskip-status', dest='comskip_status', action='store_true', default=False, - help='Report sym-linked recordings with missing comskip files.') + help='Report linked recordings with missing comskip files.') parser.add_argument('--add-match-title', dest='add_match_title', action='store', metavar='', help='Process only recordings with titles that contain the given query.') parser.add_argument('--add-match-programid', dest='add_match_programid', action='store', metavar='<programid match', @@ -91,7 +92,7 @@ parser.add_argument('--refresh-nfos', dest='refresh_nfos', action='store_true', default=False, help='Refresh nfo files. Can be combined with --add to refresh specific nfo file.') parser.add_argument('--clean', dest='clean', action='store_true', default=False, - help='Perform cleanup operations on symlink directory.') + help='Perform cleanup operations on destination directory.') # TODO: handle arguments refresh nfos # TODO: clean up symlinks, nfo files, and directories when MythTV recordings are deleted @@ -406,27 +407,29 @@ def new_series_from_ttvdb(title, title_safe, inetref, category, directory): fanart_text = series.find('fanart').text if poster_text is None: log.warning('Poster image info could not be retrieved') + else: + log.info('Downloading poster...') + poster_url = ttvdb_banners_url + series.find('poster').text + if not download_file(poster_url, os.path.join(directory, 'poster.jpg')): + return False + if banner_text is None: log.warning('Banner image info could not be retrieved') + else: + log.info('Downloading banner...') + banner_url = ttvdb_banners_url + series.find('banner').text + if not download_file(banner_url, os.path.join(directory, 'banner.jpg')): + return False + if fanart_text is None: log.warning('Fanart image info could not be retrieved') - if poster_text is None or banner_text is None or fanart_text is None: - return False - - poster_url = ttvdb_banners_url + series.find('poster').text - banner_url = ttvdb_banners_url + series.find('banner').text - fanart_url = ttvdb_banners_url + series.find('fanart').text - - log.info('Downloading poster...') - if not download_file(poster_url, os.path.join(directory, 'poster.jpg')): - return False - - log.info('Downloading banner...') - if not download_file(banner_url, os.path.join(directory, 'banner.jpg')): - return False + else: + log.info('Downloading fanart...') + fanart_url = ttvdb_banners_url + series.find('fanart').text + if not download_file(fanart_url, os.path.join(directory, 'fanart.jpg')): + return False - log.info('Downloading fanart...') - if not download_file(fanart_url, os.path.join(directory, 'fanart.jpg')): + if poster_text is None and banner_text is None and fanart_text is None: return False return True @@ -557,7 +560,7 @@ def print_config(): print ' hostname: ' + unicode(config.hostname) print ' host_port: ' + unicode(config.host_port) print ' myth_recording_dirs: ' + unicode(config.mythtv_recording_dirs) - print ' symlinks_dir: ' + unicode(config.symlinks_dir) + print ' destination_dir: ' + unicode(config.destination_dir) print ' ttvdb_key: ' + unicode(config.ttvdb_key) print ' ttvdb_zips_dir: ' + unicode(config.ttvdb_zips_dir) print ' tmdb_key: ' + unicode(config.tmdb_key) @@ -642,7 +645,7 @@ def comskip_all(): print('No missing comskip files were found.') else: log.info('Running comskip on ' + str(count) + ' recordings with missing comskip files...') - for root, dirs, files in os.walk(config.symlinks_dir): + for root, dirs, files in os.walk(config.destination_dir): # print root # path = root.split('/') # print path @@ -656,7 +659,7 @@ def comskip_all(): def comskip_status(return_missing_count=False): comskip_missing_lib = [] - for root, dirs, files in os.walk(config.symlinks_dir): + for root, dirs, files in os.walk(config.destination_dir): # print root # path = root.split('/') # print path @@ -695,10 +698,10 @@ def clean(): cleaned_file_exists = os.path.exists(cleaned_file) if args.clean is True or not cleaned_file_exists: if args.clean is True: - log.info('Cleaning symlink directory per argument --clean') + log.info('Cleaning destination directory per argument --clean') else: - log.info('cleaned file was not found, will now perform a cleaning operation on the symlink directory...') - for root, dirs, files in os.walk(config.symlinks_dir): + log.info('cleaned file was not found, will now perform a cleaning operation on the destination directory...') + for root, dirs, files in os.walk(config.destination_dir): for file in files: # print os.path.join(root, file) result = re.sub(r'(.*) - [\d]{4}-[\d]{2}-[\d]{2}(.*)', r'\1\2', file) @@ -717,7 +720,6 @@ def read_recordings(): """ global log - print '' series_lib = [] series_new_lib = [] episode_count = 0 @@ -727,7 +729,7 @@ def read_recordings(): image_error_list = [] updated_nfos_lib = [] - # make sure all files in the symlink directory are in the right format + # make sure all files in the destination directory are in the right format clean() recording_list = get_recording_list() @@ -869,7 +871,7 @@ def read_recordings(): episode_count += 1 # set target link dir - target_link_dir = os.path.join(config.symlinks_dir, title_safe) + target_link_dir = os.path.join(config.destination_dir, title_safe) link_file = os.path.join(target_link_dir, episode_name) + file_extension # print 'LINK FILE = ' + link_file @@ -887,8 +889,8 @@ def read_recordings(): # skip if link already exists (unless we're updating nfo files) if os.path.exists(link_file) or os.path.islink(link_file): if args.show_status is False and args.refresh_nfos is False: - print 'Symlink already exists: ' + link_file - log.info('Symlink already exists: ' + link_file) + print 'Link already exists: ' + link_file + log.info('Link already exists: ' + link_file) if args.add is not None and args.refresh_nfos is False: continue @@ -904,8 +906,8 @@ def read_recordings(): if source_dir is None: # could not find file! - # print ("Cannot create symlink for " + episode_name + ", no valid source directory. Skipping.") - log.error('Cannot create symlink for ' + episode_name + ', no valid source directory. Skipping.') + # print ("Cannot create link for " + episode_name + ", no valid source directory. Skipping.") + log.error('Cannot create link for ' + episode_name + ', no valid source directory. Skipping.') continue # this is a new recording (or we're just refreshing nfo files), so check if we're just checking the status for now @@ -930,7 +932,7 @@ def read_recordings(): # branch on inetref type if args.show_status is False: result = False - generic_inetref = ('ttvdvb' not in inetref or 'tmdb' not in inetref) + generic_inetref = ('ttvdb' not in inetref and 'tmdb' not in inetref) if generic_inetref is True: log.warning('Inetref provided is neither TTVDB or TMDB... trying each to find a match...') @@ -959,12 +961,15 @@ def read_recordings(): log.warning('Link file is: ' + link_file) # continue - # create symlink + # create link # print "Linking " + source_file + " ==> " + link_file if args.show_status is False and args.import_recording_list is None and args.refresh_nfos is False: if not os.path.exists(link_file) or not os.path.islink(link_file): log.info('Linking ' + source_file + ' ==> ' + link_file) - os.symlink(source_file, link_file) + if config.target_type == "symlink": + os.symlink(source_file, link_file) + elif config.target_type == "hardlink": + os.link(source_file, link_file) # write the episode nfo if args.show_status is False or args.refresh_nfos is True: @@ -1020,7 +1025,7 @@ def read_recordings(): if args.show_status is True: if len(series_new_lib) > 0 or len(episode_new_lib) > 0 or len(special_new_lib) > 0: print '' - print ' THESE SYMLINKS ARE NOT YET CREATED:' + print ' THESE LINKS ARE NOT YET CREATED:' print ' ----------------------------------' if len(series_new_lib) > 0: print ' New Series:'