diff --git a/kodi-nfo-feeder.py b/kodi-nfo-feeder.py index 6f7378e..6ae2813 100644 --- a/kodi-nfo-feeder.py +++ b/kodi-nfo-feeder.py @@ -14,7 +14,6 @@ import lxml.builder # TODO Create season subdir if it doesn't exist -# TODO Thread config sections # Exit codes @@ -34,13 +33,15 @@ class CONST(object): {"key": "self_name", "value": "kodi-nfo-feeder"}, {"key": "ignored_target_file_exts", "value": ".jpg, .jpeg, .png, .nfo"}, {"key": "title_regex_search", "value": ""}, - {"key": "title_regex_replace", "value": ""} + {"key": "title_regex_replace", "value": ""}, + {"key": "do_seasons", "value": "yes"} ] CFG_KNOWN_SECTION = [ {"key": "watch_dir", "is_mandatory": True}, {"key": "output_dir", "is_mandatory": True}, {"key": "title_regex_search", "is_mandatory": False}, - {"key": "title_regex_replace", "is_mandatory": False} + {"key": "title_regex_replace", "is_mandatory": False}, + {"key": "do_seasons", "is_mandatory": False} ] CFG_MANDATORY = [section_cfg["key"] for section_cfg in CFG_KNOWN_SECTION if section_cfg["is_mandatory"]] @@ -153,20 +154,32 @@ def validate_config_sections( def setup_watch( - watch_this: str) -> INotify: + csection_name: str, + config_obj: configparser.ConfigParser(), + inotify_obj: INotify) -> bool: + + global wds + + watch_this = config_obj.get(csection, "watch_dir") if not os.path.exists(watch_this): os.makedirs(watch_this, exist_ok=False) - inotify = INotify() watch_flags = flags.MOVED_TO try: - inotify.add_watch(watch_this, watch_flags) - log.debug(f"Watching for files moved to '{watch_this}' ...") + log.debug(f"Watching for '[{csection_name}]' files moved to '{watch_this}' ...") + wd_obj = inotify_obj.add_watch(watch_this, watch_flags) except FileNotFoundError: - log.error(f"Watch directory '{watch_this}' does not exist. Please create it. Exiting 3 ...") + log.error(f"Section '[{csection_name}]' watch directory '{watch_this}' does not exist. Please create it. " + f"Exiting 3 ...") sys.exit(3) else: - return inotify + log.debug(f"Created watch descriptor ID {wd_obj} for '[{csection_name}]' watch directory '{watch_this}'.") + wds[wd_obj] = { + "watch_dir": watch_this, + "output_dir": config.get(csection, "output_dir"), + "section": csection_name + } + return True def generate_nfo( @@ -187,26 +200,24 @@ def generate_nfo( def get_basic_cleaned_title( - section_name: str, + csection_name: str, config_obj: configparser.ConfigParser(), dirty_title: str) -> str: - regex_search_pattern = config_obj.get(section_name, "title_regex_search") - regex_replace_pattern = config_obj.get(section_name, "title_regex_replace") + regex_search_pattern = config_obj.get(csection_name, "title_regex_search") + regex_replace_pattern = config_obj.get(csection_name, "title_regex_replace") if regex_search_pattern: - log.debug(regex_search_pattern) log.debug(f"Doing basic title cleaning ...") pattern = re.compile(regex_search_pattern) clean_title = re.sub(pattern, regex_replace_pattern, dirty_title) log.debug(f"""Title's now "{clean_title}".""") - quit() return clean_title else: return dirty_title def get_season_and_episode( - section_name: str, + csection_name: str, config_obj: configparser.ConfigParser(), raw_file_name: str) -> dict: @@ -216,7 +227,7 @@ def get_season_and_episode( season_episode = re.split("[S|E]", season_ep_str[0]) season = f"Season {season_episode[1]}" title = season_ep_str[1] - basic_cleaned_title = get_basic_cleaned_title(section_name, config_obj, title) + basic_cleaned_title = get_basic_cleaned_title(csection_name, config_obj, title) got_season_and_episode = { "season_str": season, @@ -243,15 +254,16 @@ def get_target_file_list( def move_file_to_target_dir( - section_name: str, + csection_name: str, config_obj: configparser.ConfigParser(), raw_file_name: str, - season_ep_str: dict) -> str: + season_ep_str: dict, + output_dir_name: str) -> str: - this_watch_dir = config_obj.get(section_name, "watch_dir") + this_watch_dir = config_obj.get(csection_name, "watch_dir") source_abs_path = os.path.join(this_watch_dir, raw_file_name) - target_dir = config_obj.get(section_name, "output_dir") + target_dir = output_dir_name target_file_list = get_target_file_list(target_dir) target_file_name = season_ep_str["season_ep_list"][0] @@ -259,7 +271,7 @@ def move_file_to_target_dir( target_file_name_plus_ext = f"{target_file_name}{target_ext}" if target_file_name_plus_ext in target_file_list: - log.debug(f"File name already exists in target dir, incrementing counter ...") + log.debug(f"Intended file name already exists in target dir, incrementing counter ...") episode_minus_counter = target_file_name[:-2] counter = target_file_name[-2:] counter_length = len(counter) @@ -270,10 +282,10 @@ def move_file_to_target_dir( try: log.debug(f"Moving '{source_abs_path}' to '{target_abs_path}' ...") - # shutil.move(source_abs_path, target_abs_path) - except OSError as ose: + shutil.move(source_abs_path, target_abs_path) + except OSError as move_ose: log.error(f"Failed moving file with an OSError:\n" - f"{ose}\n" + f"{move_ose}\n" f"Continuing file watch ...") return "" else: @@ -300,9 +312,9 @@ def write_nfo_to_disk( log.debug(f"Writing NFO data to '{target_abs_path}':\n" f"""{nfo_str.decode("UTF-8")}""") nfo_file.write(nfo_str) - except OSError as ose: + except OSError as nfo_ose: log.error(f"Failed writing NFO file '{target_abs_path}' with an OSError:\n" - f"{ose}\n" + f"{nfo_ose}\n" f"Continuing file watch ...") return False else: @@ -319,38 +331,52 @@ if __name__ == '__main__': f"{CONST.CFG_MANDATORY} set. Exiting 2 ...") sys.exit(2) + inotify = INotify() + wds = {} log.debug(f"Iterating over config sections ...") - for section in config.sections(): - log.debug(f"Processing section '[{section}]' ...") - watch_dir = config.get(section, "watch_dir") - inotify_watch = setup_watch(watch_dir) - output_dir = config.get(section, "output_dir") - try: - os.makedirs(output_dir, exist_ok=True) - except OSError as ose: - log.error(f"Unable to create section '[{section}]' output dir '{output_dir}' with an OSError:\n" - f"{ose}\n" - f"Exiting 4 ...") - sys.exit(4) - else: - while True: - time.sleep(0.2) - for event in inotify_watch.read(): - events = [str(flags) for flags in flags.from_mask(event.mask)] - if "flags.MOVED_TO" in events: - file_name = event.name - log.info(f"File '{file_name}' was moved to watch directory '{watch_dir}', processing ...") + for csection in config.sections(): + log.debug(f"Processing section '[{csection}]' ...") + setup_watch(csection, config, inotify) - season_and_episode = get_season_and_episode(section, config, file_name) - nfo = generate_nfo(season_and_episode["title_str"], file_name) - file_moved_to_target_dir = move_file_to_target_dir( - section, - config, - file_name, - season_and_episode) - if file_moved_to_target_dir: - write_nfo_to_disk( - nfo, - file_moved_to_target_dir, - output_dir) + while True: + time.sleep(0.2) + for event in inotify.read(): + events = [str(flags) for flags in flags.from_mask(event.mask)] + if "flags.MOVED_TO" in events: + file_name = event.name + watch_dir_config = wds[event.wd] + watch_dir = watch_dir_config["watch_dir"] + output_dir = watch_dir_config["output_dir"] + section_name = watch_dir_config["section"] + log.debug(f"Watch dir config: {watch_dir_config}") + log.info(f"File '{file_name}' was moved to watch directory " + f"""'{watch_dir}', processing ...""") + + season_and_episode = get_season_and_episode(section_name, config, file_name) + if config.getboolean(section_name, "do_seasons"): + season_str = season_and_episode["season_str"] + log.debug(f"Changing output to season-specific dir '{season_str}' ...") + output_dir = os.path.join(output_dir, season_str) + + try: + os.makedirs(output_dir, exist_ok=True) + except OSError as ose: + log.error(f"Unable to create section '[{section_name}]' output dir " + f"'{output_dir}' with an OSError:\n" + f"{ose}\n" + f"Exiting 4 ...") + sys.exit(4) + + nfo = generate_nfo(season_and_episode["title_str"], file_name) + file_moved_to_target_dir = move_file_to_target_dir( + section_name, + config, + file_name, + season_and_episode, + output_dir) + if file_moved_to_target_dir: + write_nfo_to_disk( + nfo, + file_moved_to_target_dir, + output_dir)