def files_unrar(abs_path, ext=None): """ Unzip rar files Arguments: abs_path {string} -- Path to the directory containg rar files """ stderr = "" exts = ext if isinstance(ext, str) else tuple(ext) for rar_file in files_find_ext(abs_path, "rar"): files = [] if (exts): process = Popen(["unrar", "lb", rar_file], stdout=PIPE, stderr=PIPE) stdout, stderr = process.communicate() if len(stderr) > 1: break files = stdout.decode("UTF-8").split("\n") files = [f for f in files if f.endswith(exts)] if (files or exts == None): infomsg("Found RAR archive in source directory, extract it", "Postprocessing", (rar_file, )) process = Popen(["unrar", "x", "-o+", rar_file, abs_path], stdout=PIPE, stderr=PIPE) stderr = process.communicate()[1] if len(stderr) > 1: break if len(stderr) > 1: errmsg("Unrar archive failed with the following error message") print(stderr) exit()
def main(): """ Name: VS-Handbrake (Part of the VS-Package) Summary: After handbrake finished the convertion process, the script is called and handles the right naming for the video file and relocates it according to its video type (episode or movie). """ ## Get the shell arguments args = argparse.Namespace() parser = argparse.ArgumentParser(description='Naming and locate script for Handbrake docker container') parser.add_argument('-f','--file', help='Path to the video file', required=True) args = parser.parse_args() args.script_dir = cur_dir args.scope = scope_get() ## Check whether the file exists if not os.path.isfile(args.file): errmsg("File does not exist, check the path", "Postprocessing") ## Parse the config config_file = os.path.join(cur_dir, "config.txt") cfg = parse_cfg(config_file, "vs-handbrake", "docker") ## Initialize the logging init_logging(args, cfg) ## Print the date and the file infomsg("-" * 35, "Postprocessing") infomsg("Handbrake finished converting file", "Postprocessing", (args.file,)) ## Check for the source file, continue if convert file doesnt exist args = get_convert_source_path(args) ## Check which media type it is and process it accordingly processing_file(cfg, args)
def movie_get_releases(args): """ Get all new "releases" sorted by their category for the passed interval. All changelog files of mounts are filtered by the date. Arguments: args {Namespace} -- Namespace containing all arguments. Returns: list -- List of categories and their items. """ ## Get all mounts of the main volume volume_path = os.path.join(os.sep, "volume1") mounts = [m for m in os.listdir(volume_path) if isdir(join(volume_path, m))] mounts = [join(volume_path, m) for m in mounts if "@" not in m] since = datetime.today() - timedelta(days=args.interval) since = since.replace(hour=0, minute=0, second=0, microsecond=0) ## Get all items per category (mount) items, invalid = ([] for _ in range(2)) for mount in mounts: changelog = os.path.join(mount, "changelog.txt") if not os.path.isfile(changelog): invalid.append(mount) continue with open(changelog, 'r') as f: cat_items = f.readlines() cat_items = [i.replace('\n', '').split(',') for i in cat_items] cat_items = [i[1] for i in cat_items if datetime.strptime(i[0], "%Y-%m-%d") > since] cat_items = sorted(list(set(cat_items))) if cat_items: items.append((mount, cat_items)) debugmsg("Releases for mount", "Mounts", (mount, ','.join(cat_items))) infomsg("Skipped following mounts due to no changelog file", "Mounts", (','.join(invalid),)) return sorted(items)
def post_processing(args, cfg): """ Post processing. Arguments: args {Namespace} -- Namespace containing all shell arguments. cfg {Namespace} -- Namespace containing all configurations. """ ## Initialize the logging init_logging(args, cfg) debugmsg("-" * 35, "Postprocessing") ## If torrent is a single file create a directory and copy that file abs_path = files_fix_single(args) ## If there are RAR files extract them into the top directory files_unrar(abs_path, cfg.extensions) ## Import all non-compressed video files source_files = files_find_ext(abs_path, cfg.extensions) for source in source_files: (source_host, root_host, root) = scope_map_path(cfg, args, source) infomsg("Add source file to SynoIndex database", "Postprocessing", (source_host.split(os.sep)[-1], )) client(args.scope, cfg.port, source_host) ## Write changelog file for notification service write_changelog_file(source, source_host, root) ## Copy video file to handbrake if it's configured if (cfg.handbrake): for source in source_files: (source_host, root_host, _) = scope_map_path(cfg, args, source) copy_file_to_handbrake(args, cfg, source, source_host, root_host)
def main(): ## Parse all shell arguments args = parse_arguments() ## Initialize the logging init_notification_log(args) ## Get all users and mail address of the current system args.receivers = mail_get_addresses(args) ## Get all categories and the corresponding items categories = movie_get_releases(args) if not categories: infomsg("No new items for any category", "Notification") exit() ## Create a html-structure and add all categories and items content = html_add_header(args) for category in categories: category_name = os.path.basename(category[0]) content = html_add_category(content, category_name) content = html_add_items(content, category[0], category[1]) content = html_add_closing(args, content) ## Send the mail via sendmail (postfix) command mail_send(args, content)
def mail_get_addresses(args): """ Get all users and mail address of the current system Arguments: args {Namespace} -- Namespace containing all arguments. Returns: string -- List of tuples with users and mail addresses """ ## Get the username, real name and the mail address of each user admin = getpass.getuser() user_file = os.path.join(os.sep, "volume1", "homes", admin, "users.csv") user_mails = os.path.join(args.scriptdir, "user_mails.sh") p = Popen(["/bin/bash", user_mails, admin], stdout=PIPE, stderr=PIPE) stderr = p.communicate()[1] if p.returncode != 0: errmsg("Could not get user mail addresses", (stderr, )) exit() try: with open(user_file, 'r', encoding='utf-8', errors='replace') as f: users = f.read() except EnvironmentError: errmsg("Could not read the user mail addresses file", (user_file, )) exit() os.remove(user_file) ## Parse user names and separate the admin users = ("".join(i for i in users if ord(i) < 128)).split("\n") users = [u.replace("\n", "").replace("\"", "") for u in users] users = [u.replace(" (user)", "").replace(" (admin)", "") for u in users] users = list(filter(None, users)) fs_admin = users_get_selection(2, False) admin = [u for u in users if u.split(',')[0] == fs_admin] users = list(set(users) - set(admin)) ## Get the mail tuples fs_admin = users_get_selection(2, False) user_mails = [ "{} <{}>".format(u.split(",")[1], u.split(",")[2]) for u in users ] admin_mails = [ "{} <{}>".format(a.split(",")[1], a.split(",")[2]) for a in admin ] mails = admin_mails if args.receivers == "admin" else user_mails if args.receivers == "all": mails = admin_mails + user_mails infomsg("Selected list of mail addresses by receivers argument", "Mail", (",".join(mails), )) return mails
def client(scope, port, source_host, output_host=None, original_host=None, original_mode=0): ## Get the URL to the Syno-Index server and add the arguments url = client_get_url(scope, port) if not output_host: query_vars = {'source_host': source_host} else: query_vars = { "source_host": source_host, "output_host": output_host, "original_host": original_host } query_vars["original_mode"] = original_mode ## Call the url and get the answer of the server url = url + urlencode(query_vars) infomsg(" Source", "SynoClient", (source_host, )) infomsg(" Handbrake Output", "SynoClient", (output_host, )) infomsg(" Original", "SynoClient", (original_host, )) infomsg(" Original mode", "SynoClient", (original_mode, )) try: contents = urlopen(url).read() debugmsg("SynoIndex-Server answered with", "SynoClient", (contents.decode("UTF-8"), )) except urllib.error.URLError: errmsg( "Server is not started yet, start the Triggered Task with the right mode" ) exit()
def naming_episode(args, cfg): ## Get the delimiter of the video filename delimiter = delimiter_get(args.file) ## Get all information about the episode and season path = os.path.abspath(os.path.join(args.file, os.pardir)) series = Namespace(file=args.file, path=path, delim=delimiter, original=args.source_host, series_path=args.root_host) series = analyze_series(cfg, series) ## Move the file back to the original path file_name = "%s.%s.%s%s" % (series.name, series.episode, series.resolution, series.extension) file_dst = os.path.join(series.dst, file_name) infomsg("The new file path is", "Naming", (file_dst, )) return file_dst
def mail_send(args, content): """ Send the mail via sendmail (postfix) command Arguments: args {Namespace} -- Namespace containing all arguments. content {string} -- HTML content as string. """ infomsg("Start sending the individual mails", "Mail") for receiver in args.receivers: message = MIMEMultipart('alternative') message['From'] = "{} <{}>".format(args.name, args.address) message['To'] = receiver if args.date: td = datetime.today() current_day = datetime.strftime( datetime(td.year, td.month, td.day), "%d %B %Y") message['Subject'] = '{subject} since {date}'.format( subject=args.subject, date=current_day) else: message['Subject'] = '{subject}'.format(subject=args.subject) message.attach(MIMEText(content, 'html')) p = Popen(["/usr/sbin/sendmail", "-t", "-oi"], stdin=PIPE) stderr = p.communicate(message.as_bytes())[1] if p.returncode != 0: infomsg("sendmail returned with non-zero return value", "Mail", stderr) infomsg("Send mail", "Mail", (receiver, ))
def processing_file(cfg, args): ## Check the file type file_dst = None if any(file_type in args.root_host.split(os.sep) for file_type in cfg.series): infomsg("Type: Episode", "Postprocessing") file_dst = naming_episode(args, cfg) elif any(file_type in args.root_host.split(os.sep) for file_type in cfg.movies): infomsg("Type: Movie", "Postprocessing") file_dst = naming_movie(args) else: exit("Error: Unsupported type of converted file") ## Delete the corresponding convert and watch file and add to synoindex if file_dst: msg = "Add converted file to synoindex and {} original file".format(switch_original(cfg.original)) infomsg(msg, "Postprocessing") client(args.scope, cfg.port, file_dst, args.output_host, args.source_host, cfg.original) watch_file = scope_reverse_map_path(cfg, args, args.watch_host) debugmsg("Delete watch file", "Postprocessing", (watch_file,)) try: os.remove(watch_file) except FileNotFoundError: errmsg("Could not find watch file at", "Postprocessing", (watch_file,)); exit() debugmsg("Delete convert file", "Postprocessing", (args.convert_path,)) os.remove(args.convert_path)
def naming_movie(args): ## Get the delimiter of the video filename delimiter = delimiter_get(args.source_host) ## Get all information about the episode and season path = os.path.abspath(os.path.join(args.file, os.pardir)) movie = Namespace(file=args.file, path=path, delim=delimiter, original=args.source_host, movies_path=args.root_host) movie = analyze_movie(movie) ## Move the file back to the original path if (movie.resolution == "2160p"): file_name = "%s.%s%s" % (movie.name, movie.resolution, movie.extension) else: file_name = "%s%s" % (movie.name, movie.extension) file_dst = os.path.join(movie.dst, file_name) infomsg("The new file path is", "Naming", (file_dst, )) return file_dst
def parse_strlist(strlist, paths=False): ''' Parse a stringlist which may be a list of paths ''' try: strlist = list(filter(None, strlist.split(','))) strlist = [s.strip() for s in strlist] except ValueError: errmsg("Invalid string list in config") exit() if not strlist: errmsg("Invalid string list in config") exit() if paths: paths = [p.rstrip(os.sep).lstrip() for p in strlist] non_paths = [p for p in paths if not os.path.isdir(p)] paths = [p for p in paths if os.path.isdir(p)] if not paths: infomsg("Config contains list of non-existent file paths", "Parsing", (paths, )) if non_paths: infomsg("Config contains path which does not exist", "Parsing", (non_paths, )) return paths return strlist
def copy_file_to_handbrake(args, cfg, source, source_host, root_host): """ Copy file to the handbrake watch directory and change owner. Arguments: args {Namespace} -- Namespace containing all shell arguments. cfg {Namespace} -- Namespace containing all configurations. source {string} -- Path to the source within docker container. source_host {string} -- Path to the source file on the host system. root_host {string} -- Path to the top mount containing the file. """ ## Get all media info about the file video_info = ffprobe_file(source) codec = video_info["video_codec"] resolution = video_info["resolutionY"] debugmsg("Analyse the video file for codec and resolution", "Mediainfo", (resolution, codec)) ## Check whether it is one codec of the config is present if codec not in cfg.codecs: infomsg("Codec is not watched in file", "Postprocessing", (source, codec)) return ## Only copy files which match no exclude string if any(exclude in source for exclude in cfg.exclude): infomsg("Source file excluded for handbrake by config", "Postprocessing", (source, )) return ## Switch the watch directory depending on the 4K mode and the resolution watch_host = os.path.join(cfg.handbrake, "watch") if (cfg.hb_4k == 1 and int(resolution) > 2000): infomsg("4K mode enabled - file is copied to separate watch directory", "Postprocessing", (watch_host, )) watch_host = os.path.join(cfg.handbrake, "watch2") if not os.path.isdir(watch_host): create_path_directories(watch_host) os.chown(watch_host, cfg.host_admin[0], cfg.host_admin[1]) ## Copy the video file to the handbrake watch directory infomsg("Copying file to handbrake watch directory", "Postprocessing", (watch_host, )) watch_file = file_copy(source, watch_host, args) if not watch_file: errmsg("Could not copy file to handbrake watch directory", "Postprocessing", (watch_host, )) return infomsg("Finished copying file", "Postprocessing", (watch_file, )) ## Convert the output file into host system path output_file = os.path.join(cfg.handbrake, "output", os.path.basename(watch_file)) output_host = scope_map_path(cfg, args, output_file)[0] if output_host == -1: errmsg("Could not get the host path of file", "Postprocessing", (output_file)) return ## Convert the watch file into host system path watch_file = os.path.join(watch_host, os.path.basename(watch_file)) watch_host = scope_map_path(cfg, args, watch_file)[0] if watch_host == -1: errmsg("Could not get the host path of watch file", "Postprocessing", (watch_file)) return ## Write the convert file with all necessary information write_convert_file(cfg, source, source_host, root_host, output_host, watch_host)
def html_add_items(content, category, items): """ Add all items of a single category as html div-blocks. Arguments: content {string} -- HTML content as string. category {string} -- Path of the current category. items {list} -- List of items of the category. Returns: string -- HTML content with all items added. """ ## Intialite IMDBPy access = imdb.IMDb() history = [] for movie_name in items: ## Add information whether its 4K or not f4k = ["4K", "2160p", "UHD", "HDR"] format_4k = True if any(u for u in f4k if u in movie_name) else False ## Series name correction season = "" if (os.path.basename(category) != "Filme"): movie_name, season = movie_name.replace(category, "").split(os.sep)[1:3] movie_name = movie_name.replace("oe", "ö").replace("ue", "ü").replace("ae", "ä") season = "- {} ".format(season) movie_season = "{}{}".format(movie_name, season) ## Movie name correction else: movie_name = movie_get_name(category, os.path.dirname(movie_name)) movie_name = movie_name.replace("oe", "ö").replace("ue", "ü").replace("ae", "ä") ## Check for duplicates if (os.path.basename(category) == "Filme" and movie_name in history): infomsg("Skip entry due to duplicate", "HTML", (movie_name,)) continue elif (os.path.basename(category) != "Filme" and movie_season in history): infomsg("Skip entry due to duplicate", "HTML", (movie_season,)) continue ## Search for the movie/series and add the item to the html code search_results = access.search_movie(movie_name) if search_results: movieID = search_results[0].movieID movie = access.get_movie(movieID) movie_url = access.get_imdbURL(movie) content = html_add_item(content, movie, season, movie_name, movie_url, format_4k) infomsg("Added imdb entry (with image) for item", "HTML", (movie_name, movie, season)) else: content = html_add_item(content, None, season, movie_name, None, format_4k) infomsg("Add non-imdb entry for item", "HTML", (movie_name, season)) if (os.path.basename(category) == "Filme"): history.append(movie_name) else: history.append(movie_season) content = content + '<p> </p>' return content