Improved error detection and verbosity handling

This commit is contained in:
Fabrice Quenneville 2020-11-19 23:00:48 -05:00
parent 2e3bddf12b
commit 8df660019d
4 changed files with 147 additions and 84 deletions

View File

@ -17,7 +17,7 @@ pip install -r requirements.txt
``` ```
## Usage ## 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 ",," > for multiple files or filenames use double comma separated values ",,"
@ -25,12 +25,13 @@ default options are:
-in:any -in:any
-filters: -filters:
-out:mkv,x265 -out:mkv,x265
-print:list
Examples: Examples:
```bash ```bash
./curator.py list -filters:old -dir:/mnt/media/ >> ../medlist.txt ./curator.py list -filters:old -print:formated -dir:/mnt/media/ >> ../medlist.txt
./curator.py convert -del -filters:mpeg4 -out:x265,mkv -dir:"/mnt/media/Movies/" ./curator.py convert -del -filters:mpeg4 -out:av1,mp4 -dir:"/mnt/media/Movies/"
./curator.py convert -del -verbose -in:avi,mpg -dir:/mnt/media/ ./curator.py convert -del -in:avi,mpg -print:formated,verbose -dir:/mnt/media/
``` ```

View File

@ -6,7 +6,7 @@
ex: ex:
./converter.py list -in:any -filters:old -dir:/mnt/media/ >> ../medlist.txt ./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 -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 import sys
@ -38,6 +38,7 @@ def main():
inputs = [] inputs = []
filters = [] filters = []
outputs = [] outputs = []
printop = []
for arg in sys.argv: for arg in sys.argv:
# Confirm with the user that he selected to delete found files # Confirm with the user that he selected to delete found files
@ -52,6 +53,8 @@ def main():
filters += arg[9:].split(",") filters += arg[9:].split(",")
elif "-out:" in arg: elif "-out:" in arg:
outputs += arg[5:].split(",") outputs += arg[5:].split(",")
elif "-print:" in arg:
printop += arg[7:].split(",")
elif "-files:" in arg: elif "-files:" in arg:
files += arg[7:].split(",,") files += arg[7:].split(",,")
elif "-dir:" in arg: elif "-dir:" in arg:
@ -63,14 +66,23 @@ def main():
elif len(directories) > 0: elif len(directories) > 0:
medialibrary = MediaLibrary(directories = directories, inputs = inputs, filters = filters) medialibrary = MediaLibrary(directories = directories, inputs = inputs, filters = filters)
else: 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 # Actions
if sys.argv[1] == "list": if sys.argv[1] == "list":
for filepath in medialibrary.videos: for filepath in medialibrary.videos:
if medialibrary.videos[filepath].useful: 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 # TODO delete file when -del
elif sys.argv[1] == "test": elif sys.argv[1] == "test":
print(medialibrary) print(medialibrary)
@ -94,21 +106,37 @@ def main():
# Verbosing # 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.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(f"{BColors.OKCYAN}Original file:{BColors.ENDC}")
print(medialibrary.videos[filepath]) if "formated" in printop or "verbose" in printop:
print(f"{BColors.OKGREEN}Converting please wait...{BColors.ENDC}") print(medialibrary.videos[filepath].fprint())
else:
print(medialibrary.videos[filepath])
print(f"{BColors.OKGREEN}Converting please wait...{BColors.ENDC}", end="\r")
# Converting # Converting
if medialibrary.videos[filepath].convert(): if medialibrary.videos[filepath].convert(verbose = "verbose" in printop):
# Mark the job as done # Mark the job as done
medialibrary.videos[filepath].useful = False medialibrary.videos[filepath].useful = False
# Scan the new video # Scan the new video
newfpath = medialibrary.videos[filepath].path + medialibrary.videos[filepath].filename_new newfpath = medialibrary.videos[filepath].path + medialibrary.videos[filepath].filename_new
medialibrary.videos[newfpath] = Video(newfpath)
# Vervose medialibrary.videos[newfpath] = Video(newfpath, verbose = "verbose" in printop)
# 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(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 marked for deletion delete and unwatch the video
if "-del" in sys.argv: if "-del" in sys.argv:

View File

@ -21,13 +21,13 @@ class MediaLibrary():
inputs = [] inputs = []
filters = [] 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. This is the library object who holds the information about the workspace and all the videos in it.
''' '''
if files: if files:
for filepath in files: for filepath in files:
self.videos[filepath] = Video(filepath) self.videos[filepath] = Video(filepath, verbose = verbose)
elif directories: elif directories:
self.directories = directories self.directories = directories
@ -37,7 +37,7 @@ class MediaLibrary():
self.inputs = inputs self.inputs = inputs
self.filters = filters self.filters = filters
self.load_videos() self.load_videos(verbose = verbose)
self.filter_videos() self.filter_videos()
@ -54,7 +54,7 @@ class MediaLibrary():
return text return text
def load_videos(self): def load_videos(self, verbose = False):
''' '''
Scan folders for video files respecting the inputs requested by the user Scan folders for video files respecting the inputs requested by the user
Save them to the videos dictionary Save them to the videos dictionary
@ -93,7 +93,8 @@ class MediaLibrary():
if "-verbose" in sys.argv: if "-verbose" in sys.argv:
iteration += 1 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)
self.videos[video] = Video(video, verbose = verbose)
def filter_videos(self): def filter_videos(self):
''' '''

View File

@ -24,7 +24,100 @@ class Video():
width = int() width = int()
height = 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 Convert to original file to the requested format / codec
''' '''
@ -58,7 +151,7 @@ class Video():
try: try:
if "-verbose" in sys.argv: if verbose:
subprocess.call(args) subprocess.call(args)
else: else:
txt = subprocess.check_output(args, stderr=subprocess.STDOUT) txt = subprocess.check_output(args, stderr=subprocess.STDOUT)
@ -79,66 +172,6 @@ class Video():
return True 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 @staticmethod
def detect_fferror(filepath): def detect_fferror(filepath):
try: 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 = subprocess.check_output(args, stderr=subprocess.STDOUT)
output = output.decode().strip().splitlines() output = output.decode().strip()
if len(output) > 1: if len(output) > 0:
return output[0:-1] return output
except (subprocess.CalledProcessError, IndexError): except (subprocess.CalledProcessError, IndexError):
return f'{BColors.FAIL}There seams to be a "subprocess.CalledProcessError" error with \"{filepath}\"{BColors.ENDC}' return f'{BColors.FAIL}There seams to be a "subprocess.CalledProcessError" error with \"{filepath}\"{BColors.ENDC}'
return False return False