diff --git a/README.md b/README.md index c4b9676..417420a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ pip install -r requirements.txt ``` ## Usage -./curator.py [list,convert] [-del] [-verbose] [-in:any,avi,mkv,wmv,mpg,mp4,m4v,flv,vid] [-filters:fferror,old,lowres,hd,720p,1080p,uhd,mpeg,mpeg4,x264,wmv3,wmv] [-out:mkv/mp4,x265/av1] [-dir/-files:"/mnt/media/",,"/mnt/media2/"] +./curator.py [list,convert] [-del] [-in:any,avi,mkv,wmv,mpg,mp4,m4v,flv,vid] [-filters:fferror,old,lowres,hd,720p,1080p,uhd,mpeg,mpeg4,x264,wmv3,wmv] [-out:mkv/mp4,x265/av1] [-print:list,formated,verbose] [-dir/-files:"/mnt/media/",,"/mnt/media2/"] > for multiple files or filenames use double comma separated values ",," @@ -25,12 +25,13 @@ default options are: -in:any -filters: -out:mkv,x265 +-print:list Examples: ```bash -./curator.py list -filters:old -dir:/mnt/media/ >> ../medlist.txt -./curator.py convert -del -filters:mpeg4 -out:x265,mkv -dir:"/mnt/media/Movies/" -./curator.py convert -del -verbose -in:avi,mpg -dir:/mnt/media/ +./curator.py list -filters:old -print:formated -dir:/mnt/media/ >> ../medlist.txt +./curator.py convert -del -filters:mpeg4 -out:av1,mp4 -dir:"/mnt/media/Movies/" +./curator.py convert -del -in:avi,mpg -print:formated,verbose -dir:/mnt/media/ ``` diff --git a/mediacurator/curator.py b/mediacurator/curator.py index 4db52fb..730bec5 100755 --- a/mediacurator/curator.py +++ b/mediacurator/curator.py @@ -6,7 +6,7 @@ ex: ./converter.py list -in:any -filters:old -dir:/mnt/media/ >> ../medlist.txt ./converter.py convert -del -in:any -filters:mpeg4 -out:x265,mkv -dir:"/mnt/media/Movies/" - ./converter.py convert -del -verbose -in:avi,mpg -dir:/mnt/media/ + ./converter.py convert -del -in:avi,mpg -dir:/mnt/media/ ''' import sys @@ -38,6 +38,7 @@ def main(): inputs = [] filters = [] outputs = [] + printop = [] for arg in sys.argv: # Confirm with the user that he selected to delete found files @@ -52,6 +53,8 @@ def main(): filters += arg[9:].split(",") elif "-out:" in arg: outputs += arg[5:].split(",") + elif "-print:" in arg: + printop += arg[7:].split(",") elif "-files:" in arg: files += arg[7:].split(",,") elif "-dir:" in arg: @@ -63,14 +66,23 @@ def main(): elif len(directories) > 0: medialibrary = MediaLibrary(directories = directories, inputs = inputs, filters = filters) else: - print(f"{BColors.FAIL}ERROR: No files or directories selected.{BColors.FAIL}") + print(f"{BColors.FAIL}ERROR: No files or directories selected.{BColors.ENDC}") # Actions if sys.argv[1] == "list": for filepath in medialibrary.videos: if medialibrary.videos[filepath].useful: - print(medialibrary.videos[filepath]) + if "formated" in printop or "verbose" in printop: + if medialibrary.videos[filepath].error: + print(f"{BColors.FAIL}{medialibrary.videos[filepath].fprint()}{BColors.ENDC}") + else: + print(medialibrary.videos[filepath].fprint()) + else: + if medialibrary.videos[filepath].error: + print(f"{BColors.FAIL}{medialibrary.videos[filepath]}{BColors.ENDC}") + else: + print(medialibrary.videos[filepath]) # TODO delete file when -del elif sys.argv[1] == "test": print(medialibrary) @@ -94,21 +106,37 @@ def main(): # Verbosing print(f"{BColors.OKGREEN}****** Starting conversion {counter} of {len(keylist)}: '{BColors.OKCYAN}{medialibrary.videos[filepath].filename_origin}{BColors.OKGREEN}' from {BColors.OKCYAN}{medialibrary.videos[filepath].codec}{BColors.OKGREEN} to {BColors.OKCYAN}{vcodec}{BColors.OKGREEN}...{BColors.ENDC}") print(f"{BColors.OKCYAN}Original file:{BColors.ENDC}") - print(medialibrary.videos[filepath]) - print(f"{BColors.OKGREEN}Converting please wait...{BColors.ENDC}") + if "formated" in printop or "verbose" in printop: + print(medialibrary.videos[filepath].fprint()) + else: + print(medialibrary.videos[filepath]) + + print(f"{BColors.OKGREEN}Converting please wait...{BColors.ENDC}", end="\r") # Converting - if medialibrary.videos[filepath].convert(): + if medialibrary.videos[filepath].convert(verbose = "verbose" in printop): # Mark the job as done medialibrary.videos[filepath].useful = False # Scan the new video newfpath = medialibrary.videos[filepath].path + medialibrary.videos[filepath].filename_new - medialibrary.videos[newfpath] = Video(newfpath) + + medialibrary.videos[newfpath] = Video(newfpath, verbose = "verbose" in printop) - # Vervose + # Verbose print(f"{BColors.OKGREEN}Successfully converted '{medialibrary.videos[filepath].filename_origin}'{BColors.OKCYAN}({medialibrary.videos[filepath].filesize}mb){BColors.OKGREEN} to '{medialibrary.videos[newfpath].filename_origin}'{BColors.OKCYAN}({medialibrary.videos[newfpath].filesize}mb){BColors.OKGREEN}, {BColors.OKCYAN}new file:{BColors.ENDC}") - print(medialibrary.videos[newfpath]) + + + if "formated" in printop or "verbose" in printop: + if medialibrary.videos[newfpath].error: + print(f"{BColors.FAIL}{medialibrary.videos[newfpath].fprint()}{BColors.ENDC}") + else: + print(medialibrary.videos[newfpath].fprint()) + else: + if medialibrary.videos[newfpath].error: + print(f"{BColors.FAIL}{medialibrary.videos[newfpath]}{BColors.ENDC}") + else: + print(medialibrary.videos[newfpath]) # if marked for deletion delete and unwatch the video if "-del" in sys.argv: diff --git a/mediacurator/library/medialibrary.py b/mediacurator/library/medialibrary.py index c5486f2..5eabef0 100644 --- a/mediacurator/library/medialibrary.py +++ b/mediacurator/library/medialibrary.py @@ -21,13 +21,13 @@ class MediaLibrary(): inputs = [] filters = [] - def __init__(self, files = False, directories = False, inputs = ["any"], filters = []): + 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. ''' if files: for filepath in files: - self.videos[filepath] = Video(filepath) + self.videos[filepath] = Video(filepath, verbose = verbose) elif directories: self.directories = directories @@ -37,7 +37,7 @@ class MediaLibrary(): self.inputs = inputs self.filters = filters - self.load_videos() + self.load_videos(verbose = verbose) self.filter_videos() @@ -54,7 +54,7 @@ class MediaLibrary(): return text - def load_videos(self): + def load_videos(self, verbose = False): ''' Scan folders for video files respecting the inputs requested by the user Save them to the videos dictionary @@ -93,7 +93,8 @@ class MediaLibrary(): if "-verbose" in sys.argv: iteration += 1 print(f'{int((iteration / len(videolist )* 100))}% complete', end='\r') - self.videos[video] = Video(video) + + self.videos[video] = Video(video, verbose = verbose) def filter_videos(self): ''' diff --git a/mediacurator/library/video.py b/mediacurator/library/video.py index b0990bc..325da36 100644 --- a/mediacurator/library/video.py +++ b/mediacurator/library/video.py @@ -24,7 +24,100 @@ class Video(): width = int() height = int() - def convert(self, vcodec = "x265", acodec = False, extension = "mkv"): + + + def __init__(self, filepath, useful = True, verbose = False): + ''' + ''' + + #Breaking down the full path in its components + self.path = str(filepath)[:str(filepath).rindex("/") + 1] + self.filename_origin = str(filepath)[str(filepath).rindex("/") + 1:] + + # Marking useful is user manually set it. + self.useful = useful + + #Gathering 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 ) + except: + self.width, self.height = False, False + self.definition = False + + if self.error and verbose: + print(f"{BColors.FAIL}There seams to be an error with \"{filepath}\"{BColors.ENDC}") + print(f"{BColors.FAIL} {self.error}{BColors.ENDC}") + + def __str__(self): + ''' + Building and returning formated information about the video file + ''' + text = f"{self.codec} - " + + # If the first character of the definition is not a number (ie UHD and not 720p) upper it + if 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 + if self.filesize >= 1024: + text += f"{self.filesize / 1024 :.2f} gb - " + else: + text += f"{self.filesize} mb - " + + text += f"'{self.path + self.filename_origin}'" + + + if self.error: + text += f"{BColors.FAIL}\nErrors:{BColors.ENDC}" + for err in self.error.splitlines(): + text += f"{BColors.FAIL}\n {err}{BColors.ENDC}" + + + return text + + + __repr__ = __str__ + + def fprint(self): + ''' + Building and returning formated information about the video file + ''' + + 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[0] and not self.definition[0].isnumeric(): + text += f" Definition: {self.definition.upper()}: ({self.width}x{self.height})\n" + else: + text += f" Definition: {self.definition}: ({self.width}x{self.height})\n" + + text += f" Codec: {self.codec}\n" + + # Return the size in mb or gb if more than 1024 mb + if self.filesize >= 1024: + text += f" size: {self.filesize / 1024 :.2f} gb" + else: + text += f" size: {self.filesize} mb" + + if self.error: + text += f"{BColors.FAIL}\n Errors:{BColors.ENDC}" + for err in self.error.splitlines(): + text += f"{BColors.FAIL}\n {err}{BColors.ENDC}" + + + return text + + + def convert(self, vcodec = "x265", acodec = False, extension = "mkv", verbose = False): ''' Convert to original file to the requested format / codec ''' @@ -58,7 +151,7 @@ class Video(): try: - if "-verbose" in sys.argv: + if verbose: subprocess.call(args) else: txt = subprocess.check_output(args, stderr=subprocess.STDOUT) @@ -77,66 +170,6 @@ class Video(): self.filename_new = self.filename_tmp self.filename_tmp = "" return True - - - def __init__(self, filepath, useful = True): - ''' - ''' - - #Breaking down the full path in its components - self.path = str(filepath)[:str(filepath).rindex("/") + 1] - self.filename_origin = str(filepath)[str(filepath).rindex("/") + 1:] - - # Marking useful is user manually set it. - self.useful = useful - - #Gathering 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 ) - except: - self.width, self.height = False, False - self.definition = False - - if self.error and "-verbose" in sys.argv: - print(f"{BColors.FAIL}There seams to be an error with \"{filepath}\"{BColors.ENDC}") - print(f"{BColors.FAIL} {self.error}{BColors.ENDC}") - - def __str__(self): - ''' - Building and returning formated information about the video file - ''' - - 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[0] and not self.definition[0].isnumeric(): - text += f" Definition: {self.definition.upper()}: ({self.width}x{self.height})\n" - else: - text += f" Definition: {self.definition}: ({self.width}x{self.height})\n" - - text += f" Codec: {self.codec}\n" - - # Return the size in mb or gb if more than 1024 mb - if self.filesize >= 1024: - text += f" size: {self.filesize / 1024 :.2f} gb" - else: - text += f" size: {self.filesize} mb" - - if self.error: - text += f"{BColors.FAIL}\n Errors: {', '.join(map(str, self.error))}{BColors.ENDC}" - - - return text - - - __repr__ = __str__ @@ -161,11 +194,11 @@ class Video(): @staticmethod def detect_fferror(filepath): try: - args = ["ffprobe","-v","error","-select_streams","v:0", "-show_entries","stream=width,height","-of","csv=s=x:p=0",str(filepath)] + args = ["ffprobe","-v","error",str(filepath)] output = subprocess.check_output(args, stderr=subprocess.STDOUT) - output = output.decode().strip().splitlines() - if len(output) > 1: - return output[0:-1] + output = output.decode().strip() + if len(output) > 0: + return output except (subprocess.CalledProcessError, IndexError): return f'{BColors.FAIL}There seams to be a "subprocess.CalledProcessError" error with \"{filepath}\"{BColors.ENDC}' return False