Improved error detection and verbosity handling
This commit is contained in:
parent
2e3bddf12b
commit
8df660019d
@ -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/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
|
||||||
|
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(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:
|
||||||
|
|||||||
@ -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):
|
||||||
'''
|
'''
|
||||||
|
|||||||
@ -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)
|
||||||
@ -77,66 +170,6 @@ class Video():
|
|||||||
self.filename_new = self.filename_tmp
|
self.filename_new = self.filename_tmp
|
||||||
self.filename_tmp = ""
|
self.filename_tmp = ""
|
||||||
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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user