Improved ffmpeg conversion script with stream mapping, quality control, and new format support

- Added explicit stream mapping for video (-map 0:v), audio (-map 0:a?), and subtitles (-map 0:s?) to preserve all streams during conversion.
- Removed '-strict experimental' for AV1 as it's no longer necessary for most ffmpeg builds.
- Introduced CRF (Constant Rate Factor) settings for AV1 and x265 for better control over quality and file size.
- Standardized audio and subtitle copying with '-c:a copy' and '-c:s copy' for efficiency.
- Added support for .mov and .ts formats in MediaCurator.
This commit is contained in:
Fabrice Quenneville 2024-10-17 15:38:35 -04:00
parent 1516b6a321
commit 8319851836
2 changed files with 26 additions and 11 deletions

View File

@ -90,6 +90,8 @@ class MediaLibrary():
videolist += list(path.rglob("*.[fF][lL][vV]")) videolist += list(path.rglob("*.[fF][lL][vV]"))
if "mpg" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: if "mpg" in self.inputs or "any" in self.inputs or len(self.inputs) < 1:
videolist += list(path.rglob("*.[mM][pP][gG]")) videolist += list(path.rglob("*.[mM][pP][gG]"))
if "mov" in self.inputs or "any" in self.inputs or len(self.inputs) < 1:
videolist += list(path.rglob("*.[mM][oO][vV]"))
if "mpeg" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: if "mpeg" in self.inputs or "any" in self.inputs or len(self.inputs) < 1:
videolist += list(path.rglob("*.[mM][pP][eE][gG]")) videolist += list(path.rglob("*.[mM][pP][eE][gG]"))
if "vid" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: if "vid" in self.inputs or "any" in self.inputs or len(self.inputs) < 1:
@ -100,6 +102,8 @@ class MediaLibrary():
videolist += list(path.rglob("*.[dD][iI][vV][xX]")) videolist += list(path.rglob("*.[dD][iI][vV][xX]"))
if "ogm" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: if "ogm" in self.inputs or "any" in self.inputs or len(self.inputs) < 1:
videolist += list(path.rglob("*.[oO][gG][mM]")) videolist += list(path.rglob("*.[oO][gG][mM]"))
if "ts" in self.inputs or "any" in self.inputs or len(self.inputs) < 1:
videolist += list(path.rglob("*.[tT][sS]"))
if "webm" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: if "webm" in self.inputs or "any" in self.inputs or len(self.inputs) < 1:
videolist += list(path.rglob("*.[wW][eE][bB][mM]")) videolist += list(path.rglob("*.[wW][eE][bB][mM]"))

View File

@ -15,7 +15,7 @@ cred = colorama.Fore.RED
creset = colorama.Fore.RESET creset = colorama.Fore.RESET
class Video(): class Video():
'''Contains the information and methods of a video file.''' '''Contains the information and methods of a video file.'''
def __init__(self, filepath, operate=True, verbose=False): def __init__(self, filepath, operate=True, verbose=False):
'''Initializes a Video instance with provided parameters. '''Initializes a Video instance with provided parameters.
@ -38,7 +38,7 @@ class Video():
self.definition = None self.definition = None
self.width = None self.width = None
self.height = None self.height = None
# Break down the full path into its components # Break down the full path into its components
if os.name == 'nt': if os.name == 'nt':
self.path = str(filepath)[:str(filepath).rindex("\\") + 1] self.path = str(filepath)[:str(filepath).rindex("\\") + 1]
@ -61,13 +61,13 @@ class Video():
try: try:
self.width, self.height = self.detect_resolution(filepath) self.width, self.height = self.detect_resolution(filepath)
self.definition = self.detect_definition( self.definition = self.detect_definition(
width=self.width, width=self.width,
height=self.height height=self.height
) )
except: except:
self.width, self.height = False, False self.width, self.height = False, False
self.definition = False self.definition = False
if self.error and verbose: if self.error and verbose:
print(f"{cred}There seems to be an error with \"{filepath}\"{creset}") print(f"{cred}There seems to be an error with \"{filepath}\"{creset}")
print(f"{cred} {self.error}{creset}") print(f"{cred} {self.error}{creset}")
@ -81,7 +81,7 @@ class Video():
if type(self.error) is str and "FileNotFoundError" in self.error: if type(self.error) is str and "FileNotFoundError" in self.error:
return self.error return self.error
text = f"{self.codec} - " text = f"{self.codec} - "
# If the first character of the definition is not a number (e.g., 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
@ -95,7 +95,7 @@ class Video():
text += f"{self.filesize / 1024 :.2f} GB - " text += f"{self.filesize / 1024 :.2f} GB - "
else: else:
text += f"{self.filesize} MB - " text += f"{self.filesize} MB - "
text += f"'{self.path + self.filename_origin}'" text += f"'{self.path + self.filename_origin}'"
if self.error: if self.error:
@ -116,7 +116,7 @@ class Video():
if type(self.error) is str and "FileNotFoundError" in self.error: if type(self.error) is str and "FileNotFoundError" in self.error:
return self.error return self.error
text = f"{self.path + self.filename_origin}\n" text = f"{self.path + self.filename_origin}\n"
if self.definition and self.definition[0] and not self.definition[0].isnumeric(): if self.definition and self.definition[0] and not self.definition[0].isnumeric():
@ -174,17 +174,28 @@ class Video():
self.filename_tmp = newfilename self.filename_tmp = newfilename
# Setting ffmpeg # Setting ffmpeg
args = ['ffmpeg', '-i', self.path + self.filename_origin] args = [
'ffmpeg', '-i', self.path + self.filename_origin, '-map', '0:v',
'-map', '0:a?', '-map', '0:s?'
]
# Conversion options # Conversion options
if vcodec == "av1": if vcodec == "av1":
args += ['-c:v', 'libaom-av1', '-strict', 'experimental'] args += ['-c:v', 'libaom-av1']
# Optionally, control quality with a crf value for AV1
args += ['-crf', '30']
elif vcodec == "x265" or vcodec == "hevc": elif vcodec == "x265" or vcodec == "hevc":
args += ['-c:v', 'libx265'] args += ['-c:v', 'libx265']
# Add max_muxing_queue_size to avoid buffer overflows
args += ['-max_muxing_queue_size', '1000'] args += ['-max_muxing_queue_size', '1000']
# Control quality with crf for x265
args += ['-preset', 'medium', '-crf', '28']
# Add mapping for video, audio, and subtitle streams
args += ['-c:a', 'copy', '-c:s', 'copy']
# Conversion output # Conversion output
args += [self.path + self.filename_tmp] args += [self.path + self.filename_tmp]
try: try:
if verbose: if verbose:
subprocess.call(args) subprocess.call(args)
@ -292,7 +303,7 @@ class Video():
width, height = Video.detect_resolution(filepath) width, height = Video.detect_resolution(filepath)
if not width and not height: if not width and not height:
return False return False
if width >= 2160 or height >= 2160: if width >= 2160 or height >= 2160:
return "uhd" return "uhd"
elif width >= 1440 or height >= 1080: elif width >= 1440 or height >= 1080: