* 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
This commit is contained in:
parent
44bf4fef04
commit
3ec56f4a66
@ -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()
|
||||
|
||||
class MediaLibrary():
|
||||
''' This is the library object who holds the information about the workspace and all the videos in it. '''
|
||||
cgreen = colorama.Fore.GREEN
|
||||
creset = colorama.Fore.RESET
|
||||
|
||||
def __init__(self, files = False, directories = False, inputs = ["any"], filters = [], verbose = False):
|
||||
class MediaLibrary():
|
||||
'''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"{cgreen}Found {len([filepath for filepath in self.videos if self.videos[filepath].operate])} videos for the requested parameters{creset}")
|
||||
|
||||
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}")
|
||||
|
||||
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:
|
||||
|
||||
@ -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(".")]
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -22,13 +22,20 @@ 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:
|
||||
|
||||
@ -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()
|
||||
Loading…
Reference in New Issue
Block a user