From 3ec56f4a6600f7b8d4dbead12c03bfdfc43e7044 Mon Sep 17 00:00:00 2001 From: Fabrice Quenneville Date: Wed, 24 Apr 2024 01:18:24 -0400 Subject: [PATCH] * Rectified a bug where MediaLibrary would encounter an error due to a missing directory * Enhanced clarity by refining comments * Improved code readability through cleanup and organization * Ensured consistency and enhanced readability by adjusting variable names --- mediacurator/library/medialibrary.py | 179 ++++++++++------------- mediacurator/library/tools.py | 57 ++++---- mediacurator/library/video.py | 206 +++++++++++++-------------- mediacurator/mediacurator.py | 66 ++++----- 4 files changed, 237 insertions(+), 271 deletions(-) diff --git a/mediacurator/library/medialibrary.py b/mediacurator/library/medialibrary.py index f4b197d..21de744 100644 --- a/mediacurator/library/medialibrary.py +++ b/mediacurator/library/medialibrary.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -''' This is the library object who holds the information about the workspace and all the videos in it.''' +''' This module defines the MediaLibrary class, which manages information about the workspace and all videos in it.''' from pathlib import Path @@ -9,184 +9,159 @@ from .tools import deletefile import colorama colorama.init() +cgreen = colorama.Fore.GREEN +creset = colorama.Fore.RESET + class MediaLibrary(): - ''' This is the library object who holds the information about the workspace and all the videos in it. ''' - - def __init__(self, files = False, directories = False, inputs = ["any"], filters = [], verbose = False): + '''This class manages information about the workspace and all videos in it.''' + + def __init__(self, files=False, directories=False, inputs=["any"], filters=[], verbose=False): ''' - This is the library object who holds the information about the workspace and all the videos in it. + Initializes a MediaLibrary instance with provided parameters. Args: - files = False : A list of video files - directories = False : A list of directories containing videos directly or in the subdirectories - inputs = ["any"] : A list of filters to keep when browsing the directories - filters = [] : A list of filters to apply to the videos - verbose = False : A list of print options - Returns: + files (list or False): A list of video files. + directories (list or False): A list of directories containing videos directly or in subdirectories. + inputs (list): A list of filters to keep when browsing the directories. + filters (list): A list of filters to apply to the videos. + verbose (bool): A flag to enable verbose output. ''' if not files and not directories: return - self.directories = None - self.inputs = inputs - self.filters = filters - self.videos = dict() + self.directories = None + self.inputs = inputs + self.filters = filters + self.videos = dict() if files: for filepath in files: - self.videos[filepath] = Video(filepath, verbose = verbose) + self.videos[filepath] = Video(filepath, verbose=verbose) if directories: - self.directories = directories - self.load_directories(verbose = verbose) + self.directories = directories + self.load_directories(verbose=verbose) - self.filter_videos(verbose = verbose) + self.filter_videos(verbose=verbose) def __str__(self): - ''' Returns a sting representations about the current instance of the MediaLibrary object - - Args: + ''' + Returns a string representation of the MediaLibrary instance. Returns: - String : Information about what the MediaLibrary is tracking + str: Information about the MediaLibrary instance. ''' + text = "" if self.directories: - text = f"MediaCurator is watching the following directories: " + text += f"MediaCurator is watching the following directories: " text += '\n '.join(map(str, self.directories)) + '\n' text += f"MediaCurator is tracking {len(self.videos)} video files" return text - def load_directories(self, verbose = False): + def load_directories(self, verbose=False): ''' - Scan folders for video files respecting the inputs requested by the user - Save them to the videos dictionary + Scans folders for video files respecting the inputs requested by the user and saves them to the videos dictionary. Args: - verbose = False : A list of print options - - Returns: + verbose (bool): A flag to enable verbose output. ''' - print(f"{colorama.Fore.GREEN}Scanning files in {', '.join(map(str, self.directories))} for videos{colorama.Fore.RESET}") + print(f"{cgreen}Scanning files in {', '.join(map(str, self.directories))} for videos{creset}") videolist = [] for directory in self.directories: path = Path(directory) - # get all video filetypes - if "wmv" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[wW][mM][vV]")) - if "avi" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[aA][vV][iI]")) - if "mkv" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[mM][kK][vV]")) - if "mp4" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[mM][pP]4")) - if "m4v" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[mM]4[vV]")) - if "flv" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[fF][lL][vV]")) - if "mpg" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[mM][pP][gG]")) - if "mpeg" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[mM][pP][eE][gG]")) - if "vid" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[vV][iI][dD]")) - if "vob" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[vV][oO][bB]")) - if "divx" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[dD][iI][vV][xX]")) - if "ogm" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[oO][gG][mM]")) - if "webm" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: - videolist += list(path.rglob("*.[wW][eE][bB][mM]")) + # Get all video filetypes + for ext in ["wmv", "avi", "mkv", "mp4", "m4v", "flv", "mpg", "mpeg", "vid", "vob", "divx", "ogm", "webm"]: + if ext in self.inputs or "any" in self.inputs or len(self.inputs) < 1: + videolist += list(path.rglob(f"*.[{ext.upper()}{ext.lower()}]")) # Remove folders videolist_tmp = videolist videolist = [video for video in videolist_tmp if video.is_file()] # Map it all to the videos dictionary as initiated Video objects - print(f"{colorama.Fore.GREEN}Analazing {len(videolist)} videos in {', '.join(map(str, self.directories))}{colorama.Fore.RESET}") + print(f"{cgreen}Analyzing {len(videolist)} videos in {', '.join(map(str, self.directories))}{creset}") iteration = 0 for video in videolist: if verbose: iteration += 1 - print(f'{int((iteration / len(videolist )* 100))}% complete', end='\r') + print(f'{int((iteration / len(videolist) * 100))}% complete', end='\r') - self.videos[video] = Video(video, verbose = verbose) + self.videos[video] = Video(video, verbose=verbose) - def filter_videos(self, verbose = False): - ''' Mark useless videos in the videos dictionary (default is useful) + def filter_videos(self, verbose=False): + ''' + Marks videos for operation in the videos dictionary (default is not to operate). Args: - verbose = False : A list of print options - - Returns: + verbose (bool): A flag to enable verbose output. ''' - print(f"{colorama.Fore.GREEN}Filtering {len(self.videos)} videos for the requested parameters{colorama.Fore.RESET}") + print(f"{cgreen}Filtering {len(self.videos)} videos for the requested parameters{creset}") for filepath in self.videos: - # filter for filetypes + # Filter for filetypes if len([filtr for filtr in self.filters if filtr in ["old", "mpeg4", "mpeg", "wmv3", "wmv", "h264", "hevc", "x265", "av1"]]) > 0: - useful = False + operate = False if "old" in self.filters and self.videos[filepath].codec not in ["hevc", "av1"]: - useful = True + operate = True if ("mpeg4" in self.filters or "mpeg" in self.filters) and self.videos[filepath].codec in ["mpeg4", "msmpeg4v3"]: - useful = True + operate = True if "mpeg" in self.filters and self.videos[filepath].codec in ["mpeg1video"]: - useful = True + operate = True if ("wmv3" in self.filters or "wmv" in self.filters) and self.videos[filepath].codec in ["wmv3"]: - useful = True + operate = True if "h264" in self.filters and self.videos[filepath].codec in ["h264"]: - useful = True + operate = True if ("hevc" in self.filters or "x265" in self.filters) and self.videos[filepath].codec in ["hevc"]: - useful = True + operate = True if "av1" in self.filters and self.videos[filepath].codec in ["av1"]: - useful = True - self.videos[filepath].useful = useful + operate = True + self.videos[filepath].operate = operate - # keep video if useful and user wants to also filter by selected resolutions - if self.videos[filepath].useful and len([filtr for filtr in self.filters if filtr in ["lowres", "hd", "subsd", "sd", "720p", "1080p", "uhd"]]) > 0: - useful = False + # Keep video for operation if specified resolution + if self.videos[filepath].operate and len([filtr for filtr in self.filters if filtr in ["lowres", "hd", "subsd", "sd", "720p", "1080p", "uhd"]]) > 0: + operate = False if "subsd" in self.filters and self.videos[filepath].definition in ["subsd"]: - useful = True + operate = True if "sd" in self.filters and self.videos[filepath].definition in ["sd"]: - useful = True + operate = True if "720p" in self.filters and self.videos[filepath].definition in ["720p"]: - useful = True + operate = True if "1080p" in self.filters and self.videos[filepath].definition in ["1080p"]: - useful = True + operate = True if "uhd" in self.filters and self.videos[filepath].definition in ["uhd"]: - useful = True + operate = True if "lowres" in self.filters and self.videos[filepath].definition in ["subsd", "sd"]: - useful = True + operate = True if "hd" in self.filters and self.videos[filepath].definition in ["720p", "1080p", "uhd"]: - useful = True - self.videos[filepath].useful = useful + operate = True + self.videos[filepath].operate = operate - # keep video if useful and user wants to also filter when there is an ffmpeg errors - if self.videos[filepath].useful and len([filtr for filtr in self.filters if filtr in ["fferror"]]) > 0: - useful = False + # Keep video for operation if ffmpeg error exists + if self.videos[filepath].operate and len([filtr for filtr in self.filters if filtr in ["fferror"]]) > 0: + operate = False if self.videos[filepath].error: - useful = True - self.videos[filepath].useful = useful - + operate = True + self.videos[filepath].operate = operate - print(f"{colorama.Fore.GREEN}Found {len([filepath for filepath in self.videos if self.videos[filepath].useful])} videos for the requested parameters{colorama.Fore.RESET}") + print(f"{cgreen}Found {len([filepath for filepath in self.videos if self.videos[filepath].operate])} videos for the requested parameters{creset}") - def unwatch(self, filepath, delete = False): - ''' remove a video from the index and delete it if requested + def unwatch(self, filepath, delete=False): + ''' + Removes a video from the index and deletes it if requested. Args: - filepath : A string containing the full filepath - delete = False : Delete the file as well as removing it from the library - + filepath (str): The full filepath of the video. + delete (bool): If True, delete the file as well as removing it from the library. + Returns: - boolean : Operation success + bool: True if operation successful, False otherwise. ''' if delete: @@ -197,4 +172,4 @@ class MediaLibrary(): return video except KeyError: pass - return False \ No newline at end of file + return False diff --git a/mediacurator/library/tools.py b/mediacurator/library/tools.py index 3591b5b..7387c6a 100644 --- a/mediacurator/library/tools.py +++ b/mediacurator/library/tools.py @@ -10,14 +10,16 @@ import sys import colorama colorama.init() +cgreen = colorama.Fore.GREEN +cyellow = colorama.Fore.YELLOW +cred = colorama.Fore.RED +creset = colorama.Fore.RESET def load_arguments(): '''Get/load command parameters - Args: - Returns: - arguments: A dictionary of lists of the options passed by the user + dict: A dictionary containing lists of options passed by the user ''' arguments = { "directories": list(), @@ -29,12 +31,12 @@ def load_arguments(): } for arg in sys.argv: - # Confirm with the user that he selected to delete found files + # Confirm with the user that they selected to delete found files if "-del" in arg: print( - f"{colorama.Fore.YELLOW}WARNING: Delete option selected!{colorama.Fore.RESET}") - if not user_confirm(f"Are you sure you wish to delete all found results after selected operations are succesfull ? [Y/N] ?", color="yellow"): - print(f"{colorama.Fore.GREEN}Exiting!{colorama.Fore.RESET}") + f"{cyellow}WARNING: Delete option selected!{creset}") + if not user_confirm(f"Are you sure you wish to delete all found results after selected operations are successful? [Y/N] ?", color="yellow"): + print(f"{cgreen}Exiting!{creset}") exit() elif "-in:" in arg: arguments["inputs"] += arg[4:].split(",") @@ -53,19 +55,17 @@ def load_arguments(): def detect_ffmpeg(): - '''Returns the version of ffmpeg that is installed or false - - Args: + '''Returns the version of ffmpeg that is installed or False Returns: - String : The version number of the installed FFMPEG - False : The failure of retreiving the version number + str: The version number of the installed FFMPEG + False: If version retrieval failed ''' try: txt = subprocess.check_output( ['ffmpeg', '-version'], stderr=subprocess.STDOUT).decode() if "ffmpeg version" in txt: - # Strip the useless text and + # Strip the useless text return txt.split(' ')[2] except: pass @@ -73,19 +73,19 @@ def detect_ffmpeg(): def user_confirm(question, color=False): - '''Returns the user answer to a yes or no question + '''Returns the user's answer to a yes or no question Args: - question : A String containing the user question - color : A String containing the prefered color for a question (reg/yellow) + question (str): The user question + color (str, optional): The preferred color for a question (red/yellow) Returns: - Bool : Positive or negative return to the user question + bool: True for a positive response, False otherwise ''' if color == "yellow": - print(f"{colorama.Fore.YELLOW}{question} {colorama.Fore.RESET}", end='') + print(f"{cyellow}{question} {creset}", end='') answer = input() elif color == "red": - print(f"{colorama.Fore.RED}{question} {colorama.Fore.RESET}", end='') + print(f"{cred}{question} {creset}", end='') answer = input() else: answer = input(f"{question} ") @@ -98,32 +98,31 @@ def user_confirm(question, color=False): def deletefile(filepath): - '''Delete a file, Returns a boolean + '''Delete a file and return a boolean indicating success Args: - filepath : A string containing the full filepath + filepath (str): The full filepath Returns: - Bool : The success of the operation + bool: True if successful, False otherwise ''' try: os.remove(filepath) except OSError: - print(f"{colorama.Fore.RED}Error deleting {filepath}{colorama.Fore.RESET}") + print(f"{cred}Error deleting {filepath}{creset}") return False - print(f"{colorama.Fore.GREEN}Successfully deleted {filepath}{colorama.Fore.RESET}") + print(f"{cgreen}Successfully deleted {filepath}{creset}") return True def findfreename(filepath, attempt=0): - ''' Given a filepath it will try to find a free filename by appending to the name. - First trying as passed in argument, then adding [HEVC] to the end and if all fail [HEVC](#). + '''Find a free filename by appending [HEVC] or [HEVC](#) to the name if necessary Args: - filepath : A string containing the full filepath - attempt : The number of times we have already tryed + filepath (str): The full filepath + attempt (int, optional): The number of attempts made Returns: - filepath : The first free filepath we found + str: The first free filepath found ''' attempt += 1 filename = str(filepath)[:str(filepath).rindex(".")] diff --git a/mediacurator/library/video.py b/mediacurator/library/video.py index af27f53..36c8b87 100644 --- a/mediacurator/library/video.py +++ b/mediacurator/library/video.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -'''Its a video!''' +'''This module defines the Video class, which contains information and methods for video files.''' from .tools import deletefile, findfreename import subprocess @@ -8,71 +8,73 @@ import os import colorama colorama.init() +cyellow = colorama.Fore.YELLOW +cred = colorama.Fore.RED +creset = colorama.Fore.RESET + class Video(): '''Contains the information and methods of a video file.''' - def __init__(self, filepath, useful = True, verbose = False): - '''Contains the information and methods of a video file. + def __init__(self, filepath, operate=True, verbose=False): + '''Initializes a Video instance with provided parameters. Args: - filepath : A string containing the full filepath - useful : A boolean marking if the video is to be operated on - verbose = False : A list of print options - - Returns: + filepath (str): The full filepath of the video. + operate (bool): A flag indicating if the video is to be operated on. + verbose (bool): A flag indicating whether to enable verbose output. ''' + + # Initialize attributes self.path = None self.filename_origin = None self.filesize = None self.filename_new = None self.filename_tmp = None - self.useful = None + self.operate = None self.codec = None self.error = None self.definition = None self.width = None self.height = None - #Breaking down the full path in its components + # Break down the full path into its components if os.name == 'nt': - self.path = str(filepath)[:str(filepath).rindex("\\") + 1] - self.filename_origin = str(filepath)[str(filepath).rindex("\\") + 1:] + self.path = str(filepath)[:str(filepath).rindex("\\") + 1] + self.filename_origin = str(filepath)[str(filepath).rindex("\\") + 1:] else: - self.path = str(filepath)[:str(filepath).rindex("/") + 1] - self.filename_origin = str(filepath)[str(filepath).rindex("/") + 1:] + self.path = str(filepath)[:str(filepath).rindex("/") + 1] + self.filename_origin = str(filepath)[str(filepath).rindex("/") + 1:] if not os.path.exists(filepath): - self.error = f"FileNotFoundError: [Errno 2] No such file or directory: '{filepath}'" - self.useful = useful - + self.error = f"FileNotFoundError: [Errno 2] No such file or directory: '{filepath}'" + self.operate = operate else: - # Marking useful is user manually set it. - self.useful = useful + # Mark the video for operation if specified manually by the user + self.operate = operate - #Gathering information on the video - self.filesize = self.detect_filesize(filepath) - self.error = self.detect_fferror(filepath) - self.codec = self.detect_codec(filepath) + # Gather information on the video + self.filesize = self.detect_filesize(filepath) + self.error = self.detect_fferror(filepath) + self.codec = self.detect_codec(filepath) try: self.width, self.height = self.detect_resolution(filepath) - self.definition = self.detect_definition( - width = self.width, - height = self.height ) + self.definition = self.detect_definition( + width=self.width, + height=self.height + ) except: self.width, self.height = False, False - self.definition = False + self.definition = False if self.error and verbose: - print(f"{colorama.Fore.RED}There seams to be an error with \"{filepath}\"{colorama.Fore.RESET}") - print(f"{colorama.Fore.RED} {self.error}{colorama.Fore.RESET}") + print(f"{cred}There seems to be an error with \"{filepath}\"{creset}") + print(f"{cred} {self.error}{creset}") def __str__(self): - '''Returns a short formated string about the video - - Args: + '''Returns a short formatted string about the video. Returns: - String : A short formated string about the video + str: A short formatted string about the video. ''' if type(self.error) is str and "FileNotFoundError" in self.error: @@ -80,49 +82,41 @@ class Video(): text = f"{self.codec} - " - # If the first character of the definition is not a number (ie UHD and not 720p) upper it + # If the first character of the definition is not a number (e.g., UHD and not 720p), upper it if self.definition and self.definition[0] and not self.definition[0].isnumeric(): text += f"{self.definition.upper()}: ({self.width}x{self.height}) - " else: text += f"{self.definition}: ({self.width}x{self.height}) - " - # Return the size in mb or gb if more than 1024 mb + # Return the size in MB or GB if more than 1024 MB if self.filesize >= 1024: - text += f"{self.filesize / 1024 :.2f} gb - " + text += f"{self.filesize / 1024 :.2f} GB - " else: - text += f"{self.filesize} mb - " + text += f"{self.filesize} MB - " text += f"'{self.path + self.filename_origin}'" - if self.error: - text += f"{colorama.Fore.RED}\nErrors:{colorama.Fore.RESET}" + text += f"{cred}\nErrors:{creset}" for err in self.error.splitlines(): - text += f"{colorama.Fore.RED}\n {err}{colorama.Fore.RESET}" - + text += f"{cred}\n {err}{creset}" return text - __repr__ = __str__ def fprint(self): - '''Returns a long formated string about the video - - Args: + '''Returns a long formatted string about the video. Returns: - String : A long formated string about the video + str: A long formatted string about the video. ''' - if type(self.error) is str and "FileNotFoundError" in self.error: return self.error text = f"{self.path + self.filename_origin}\n" - #text += f" Useful: {self.useful}\n" - # If the first character of the definition is not a number (ie UHD and not 720p) upper it if self.definition and self.definition[0] and not self.definition[0].isnumeric(): text += f" Definition: {self.definition.upper()}: ({self.width}x{self.height})\n" else: @@ -130,34 +124,31 @@ class Video(): text += f" Codec: {self.codec}\n" - # Return the size in mb or gb if more than 1024 mb + # Return the size in MB or GB if more than 1024 MB if self.filesize >= 1024: - text += f" size: {self.filesize / 1024 :.2f} gb" + text += f" Size: {self.filesize / 1024 :.2f} GB" else: - text += f" size: {self.filesize} mb" + text += f" Size: {self.filesize} MB" if self.error: - text += f"{colorama.Fore.RED}\n Errors:{colorama.Fore.RESET}" + text += f"{cred}\n Errors:{creset}" for err in self.error.splitlines(): - text += f"{colorama.Fore.RED}\n {err}{colorama.Fore.RESET}" - + text += f"{cred}\n {err}{creset}" return text - - def convert(self, vcodec = "x265", acodec = False, extension = "mkv", verbose = False): + def convert(self, vcodec="x265", acodec=False, extension="mkv", verbose=False): ''' - Convert to original file to the requested format / codec - verbose will print ffmpeg's output + Converts the original file to the requested format / codec. Args: - vcodec = "x265" : The new video codec, supports av1 or x265 - acodec = False : Currently not enabled, will be for audio codecs - extension = "mkv" : A string containing the new video container format and file extention - verbose = False : A boolean enabling verbosity - + vcodec (str): The new video codec, supports av1 or x265. + acodec (bool): Currently not enabled, will be for audio codecs. + extension (str): The new video container format and file extension. + verbose (bool): A flag enabling verbosity. + Returns: - boolean : Operation success + bool: True if operation successful, False otherwise. ''' # Setting new filename @@ -178,18 +169,18 @@ class Video(): else: newfilename = str(newfilename)[str(newfilename).rindex("/") + 1:] - self.filename_tmp = newfilename + self.filename_tmp = newfilename - # Settting ffmpeg + # Setting ffmpeg args = ['ffmpeg', '-i', self.path + self.filename_origin] - # conversion options + # Conversion options if vcodec == "av1": args += ['-c:v', 'libaom-av1', '-strict', 'experimental'] elif vcodec == "x265" or vcodec == "hevc": args += ['-c:v', 'libx265'] args += ['-max_muxing_queue_size', '1000'] - # conversion output + # Conversion output args += [self.path + self.filename_tmp] try: @@ -200,10 +191,10 @@ class Video(): except subprocess.CalledProcessError as e: deletefile(self.path + self.filename_tmp) self.filename_tmp = "" - print(f"{colorama.Fore.RED}Conversion failed {e}{colorama.Fore.RESET}") + print(f"{cred}Conversion failed {e}{creset}") return False except KeyboardInterrupt: - print(f"{colorama.Fore.YELLOW}Conversion cancelled, cleaning up...{colorama.Fore.RESET}") + print(f"{cyellow}Conversion cancelled, cleaning up...{creset}") deletefile(self.path + self.filename_tmp) self.filename_tmp = "" exit() @@ -211,89 +202,89 @@ class Video(): try: os.chmod(f"{self.path}{self.filename_tmp}", 0o777) except PermissionError: - print(f"{colorama.Fore.RED}PermissionError on: '{self.path}{self.filename_tmp}'{colorama.Fore.RESET}") + print(f"{cred}PermissionError on: '{self.path}{self.filename_tmp}'{creset}") self.filename_new = self.filename_tmp self.filename_tmp = "" return True @staticmethod def detect_fferror(filepath): - '''Returns a string with the detected errors + '''Returns a string with the detected errors. Args: - filepath : A string containing the full filepath - + filepath (str): The full filepath of the video. + Returns: - String : The errors that have been found / happened - False : The lack of errors + str: The errors that have been found/happened. + False: The lack of errors. ''' try: - args = ["ffprobe","-v","error",str(filepath)] + args = ["ffprobe", "-v", "error", str(filepath)] output = subprocess.check_output(args, stderr=subprocess.STDOUT) output = output.decode().strip() if len(output) > 0: return output except (subprocess.CalledProcessError, IndexError): - return f'{colorama.Fore.RED}There seams to be a "subprocess.CalledProcessError" error with \"{filepath}\"{colorama.Fore.RESET}' + return f'{cred}There seems to be a "subprocess.CalledProcessError" error with \"{filepath}\"{creset}' return False - @staticmethod def detect_codec(filepath): - '''Returns a string with the detected codec + '''Returns a string with the detected codec. Args: - filepath : A string containing the full filepath + filepath (str): The full filepath of the video. + Returns: - String : The codec that has been detected - False : An error in the codec fetching process + str: The codec that has been detected. + False: An error in the codec fetching process. ''' output = False try: args = ["ffprobe", "-v", "quiet", "-select_streams", "v:0", "-show_entries", "stream=codec_name", "-of", "default=noprint_wrappers=1:nokey=1", str(filepath)] output = subprocess.check_output(args, stderr=subprocess.STDOUT) - - # decoding from binary, stripping whitespace, keep only last line - # in case ffmprobe added error messages over the requested information + # Decoding from binary, stripping whitespace, keep only last line + # in case ffprobe added error messages over the requested information output = output.decode().strip() except (subprocess.CalledProcessError, IndexError): return False return output - @staticmethod def detect_resolution(filepath): - '''Returns a list with the detected width(0) and height(1) + '''Returns a list with the detected width(0) and height(1). Args: - filepath : A string containing the full filepath + filepath (str): The full filepath of the video. + Returns: - List : the detected width(0) and height(1) - False : An error in the codec fetching process + List: The detected width(0) and height(1). + False: An error in the resolution fetching process. ''' try: - args = ["ffprobe","-v","quiet","-select_streams","v:0", "-show_entries","stream=width,height","-of","csv=s=x:p=0",str(filepath)] + args = ["ffprobe", "-v", "quiet", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of", "csv=s=x:p=0", str(filepath)] output = subprocess.check_output(args, stderr=subprocess.STDOUT) - - # decoding from binary, stripping whitespace, keep only last line - # in case ffmprobe added error messages over the requested information + # Decoding from binary, stripping whitespace, keep only last line + # in case ffprobe added error messages over the requested information output = output.decode().strip() - - # See if we got convertable data + # See if we got convertible data output = [int(output.split("x")[0]), int(output.split("x")[1])] except (subprocess.CalledProcessError, IndexError): return False return output[0], output[1] @staticmethod - def detect_definition(filepath = False, width = False, height = False): - '''Returns a string with the detected definition corrected for dead space + def detect_definition(filepath=False, width=False, height=False): + '''Returns a string with the detected definition corrected for dead space. Args: - filepath : A string containing the full filepath + filepath (str): A string containing the full filepath. + width (int): The width of the video. + height (int): The height of the video. + Returns: - List : The classified definition in width(0) and height(1) - False : An error in the process + str: The classified definition in width(0) and height(1). + False: An error in the process. ''' if filepath: width, height = Video.detect_resolution(filepath) @@ -312,13 +303,14 @@ class Video(): @staticmethod def detect_filesize(filepath): - '''Returns an integer with size in mb + '''Returns an integer with size in MB. Args: - filepath : A string containing the full filepath + filepath (str): A string containing the full filepath. + Returns: - Integer : The filesize in mb - False : An error in the process + int: The filesize in MB. + False: An error in the process. ''' try: size = int(os.path.getsize(filepath) / 1024 / 1024) diff --git a/mediacurator/mediacurator.py b/mediacurator/mediacurator.py index be64519..be68bc4 100755 --- a/mediacurator/mediacurator.py +++ b/mediacurator/mediacurator.py @@ -22,16 +22,23 @@ except ModuleNotFoundError: from library.medialibrary import MediaLibrary from library.tools import detect_ffmpeg, user_confirm, load_arguments - +# Import colorama for colored output import colorama colorama.init() +# Define color codes for colored output +ccyan = colorama.Fore.CYAN +cblue = colorama.Fore.BLUE +cgreen = colorama.Fore.GREEN +cred = colorama.Fore.RED +creset = colorama.Fore.RESET + def main(): ''' - MediaCurator's main function - + MediaCurator's main function + Args: - + Returns: ''' @@ -39,14 +46,14 @@ def main(): # confirm that the command has enough parameters if len(sys.argv) < 2: - print(f"{colorama.Fore.RED}ERROR: Command not understood, please see documentation.{colorama.Fore.RESET}") + print(f"{cred}ERROR: Command not understood, please see documentation.{creset}") # confirm that ffmpeg in indeed installed ffmpeg_version = detect_ffmpeg() if not ffmpeg_version: - print(f"{colorama.Fore.RED}No ffmpeg version detected{colorama.Fore.RESET}") + print(f"{cred}No ffmpeg version detected{creset}") exit() - print(f"{colorama.Fore.BLUE}ffmpeg version detected: {ffmpeg_version}{colorama.Fore.RESET}") + print(f"{cblue}ffmpeg version detected: {ffmpeg_version}{creset}") # Get/load command parameters arguments = load_arguments() @@ -57,27 +64,26 @@ def main(): elif len(arguments["directories"]) > 0: medialibrary = MediaLibrary(directories = arguments["directories"], inputs = arguments["inputs"], filters = arguments["filters"]) else: - print(f"{colorama.Fore.RED}ERROR: No files or directories selected.{colorama.Fore.RESET}") + print(f"{cred}ERROR: No files or directories selected.{creset}") return - + # Actions if sys.argv[1] == "list": - # Pulling list of marked videos / original keys for the medialibrary.videos dictionary - keylist = [filepath for filepath in medialibrary.videos if medialibrary.videos[filepath].useful] + keylist = [filepath for filepath in medialibrary.videos if medialibrary.videos[filepath].operate] keylist.sort() for filepath in keylist: - if medialibrary.videos[filepath].useful: + if medialibrary.videos[filepath].operate: if "formated" in arguments["printop"] or "verbose" in arguments["printop"]: if medialibrary.videos[filepath].error: - print(f"{colorama.Fore.RED}{medialibrary.videos[filepath].fprint()}{colorama.Fore.RESET}") + print(f"{cred}{medialibrary.videos[filepath].fprint()}{creset}") else: print(medialibrary.videos[filepath].fprint()) else: if medialibrary.videos[filepath].error: - print(f"{colorama.Fore.RED}{medialibrary.videos[filepath]}{colorama.Fore.RESET}") + print(f"{cred}{medialibrary.videos[filepath]}{creset}") else: print(medialibrary.videos[filepath]) @@ -85,21 +91,20 @@ def main(): if "-del" in sys.argv: medialibrary.unwatch(filepath, delete = True) elif sys.argv[1] == "test": - # Pulling list of marked videos / original keys for the medialibrary.videos dictionary - keylist = [filepath for filepath in medialibrary.videos if medialibrary.videos[filepath].useful] + keylist = [filepath for filepath in medialibrary.videos if medialibrary.videos[filepath].operate] keylist.sort() for filepath in keylist: - if medialibrary.videos[filepath].useful: + if medialibrary.videos[filepath].operate: if "formated" in arguments["printop"] or "verbose" in arguments["printop"]: if medialibrary.videos[filepath].error: - print(f"{colorama.Fore.RED}{medialibrary.videos[filepath].fprint()}{colorama.Fore.RESET}") + print(f"{cred}{medialibrary.videos[filepath].fprint()}{creset}") else: print(medialibrary.videos[filepath].fprint()) else: if medialibrary.videos[filepath].error: - print(f"{colorama.Fore.RED}{medialibrary.videos[filepath]}{colorama.Fore.RESET}") + print(f"{cred}{medialibrary.videos[filepath]}{creset}") else: print(medialibrary.videos[filepath]) @@ -111,7 +116,7 @@ def main(): counter = 0 # Pulling list of marked videos / original keys for the medialibrary.videos dictionary - keylist = [filepath for filepath in medialibrary.videos if medialibrary.videos[filepath].useful] + keylist = [filepath for filepath in medialibrary.videos if medialibrary.videos[filepath].operate] keylist.sort() for filepath in keylist: @@ -123,19 +128,19 @@ def main(): vcodec = "x265" # Verbosing - print(f"{colorama.Fore.GREEN}****** Starting conversion {counter} of {len(keylist)}: '{colorama.Fore.CYAN}{medialibrary.videos[filepath].filename_origin}{colorama.Fore.GREEN}' from {colorama.Fore.CYAN}{medialibrary.videos[filepath].codec}{colorama.Fore.GREEN} to {colorama.Fore.CYAN}{vcodec}{colorama.Fore.GREEN}...{colorama.Fore.RESET}") - print(f"{colorama.Fore.CYAN}Original file:{colorama.Fore.RESET}") + print(f"{cgreen}****** Starting conversion {counter} of {len(keylist)}: '{ccyan}{medialibrary.videos[filepath].filename_origin}{cgreen}' from {ccyan}{medialibrary.videos[filepath].codec}{cgreen} to {ccyan}{vcodec}{cgreen}...{creset}") + print(f"{ccyan}Original file:{creset}") if "formated" in arguments["printop"] or "verbose" in arguments["printop"]: print(medialibrary.videos[filepath].fprint()) else: print(medialibrary.videos[filepath]) - print(f"{colorama.Fore.GREEN}Converting please wait...{colorama.Fore.RESET}", end="\r") + print(f"{cgreen}Converting please wait...{creset}", end="\r") # Converting if medialibrary.videos[filepath].convert(verbose = "verbose" in arguments["printop"]): # Mark the job as done - medialibrary.videos[filepath].useful = False + medialibrary.videos[filepath].operate = False # Scan the new video newfpath = medialibrary.videos[filepath].path + medialibrary.videos[filepath].filename_new @@ -143,17 +148,16 @@ def main(): medialibrary.videos[newfpath] = Video(newfpath, verbose = "verbose" in arguments["printop"]) # Verbose - print(f"{colorama.Fore.GREEN}Successfully converted '{medialibrary.videos[filepath].filename_origin}'{colorama.Fore.CYAN}({medialibrary.videos[filepath].filesize}mb){colorama.Fore.GREEN} to '{medialibrary.videos[newfpath].filename_origin}'{colorama.Fore.CYAN}({medialibrary.videos[newfpath].filesize}mb){colorama.Fore.GREEN}, {colorama.Fore.CYAN}new file:{colorama.Fore.RESET}") - + print(f"{cgreen}Successfully converted '{medialibrary.videos[filepath].filename_origin}'{ccyan}({medialibrary.videos[filepath].filesize}mb){cgreen} to '{medialibrary.videos[newfpath].filename_origin}'{ccyan}({medialibrary.videos[newfpath].filesize}mb){cgreen}, {ccyan}new file:{creset}") if "formated" in arguments["printop"] or "verbose" in arguments["printop"]: if medialibrary.videos[newfpath].error: - print(f"{colorama.Fore.RED}{medialibrary.videos[newfpath].fprint()}{colorama.Fore.RESET}") + print(f"{cred}{medialibrary.videos[newfpath].fprint()}{creset}") else: print(medialibrary.videos[newfpath].fprint()) else: if medialibrary.videos[newfpath].error: - print(f"{colorama.Fore.RED}{medialibrary.videos[newfpath]}{colorama.Fore.RESET}") + print(f"{cred}{medialibrary.videos[newfpath]}{creset}") else: print(medialibrary.videos[newfpath]) @@ -161,9 +165,5 @@ def main(): if "-del" in sys.argv: medialibrary.unwatch(filepath, delete = True) - - - - if __name__ == '__main__': - main() \ No newline at end of file + main()