From 8319851836c5295020b03f8a5e47657c3415f7e6 Mon Sep 17 00:00:00 2001 From: Fabrice Quenneville Date: Thu, 17 Oct 2024 15:38:35 -0400 Subject: [PATCH] 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. --- mediacurator/library/medialibrary.py | 4 ++++ mediacurator/library/video.py | 33 ++++++++++++++++++---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/mediacurator/library/medialibrary.py b/mediacurator/library/medialibrary.py index de4cfee..df5a707 100644 --- a/mediacurator/library/medialibrary.py +++ b/mediacurator/library/medialibrary.py @@ -90,6 +90,8 @@ class MediaLibrary(): 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 "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: videolist += list(path.rglob("*.[mM][pP][eE][gG]")) 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]")) if "ogm" in self.inputs or "any" in self.inputs or len(self.inputs) < 1: 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: videolist += list(path.rglob("*.[wW][eE][bB][mM]")) diff --git a/mediacurator/library/video.py b/mediacurator/library/video.py index 97d07bf..27937bb 100644 --- a/mediacurator/library/video.py +++ b/mediacurator/library/video.py @@ -15,7 +15,7 @@ cred = colorama.Fore.RED creset = colorama.Fore.RESET 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): '''Initializes a Video instance with provided parameters. @@ -38,7 +38,7 @@ class Video(): self.definition = None self.width = None self.height = None - + # Break down the full path into its components if os.name == 'nt': self.path = str(filepath)[:str(filepath).rindex("\\") + 1] @@ -61,13 +61,13 @@ class Video(): try: self.width, self.height = self.detect_resolution(filepath) self.definition = self.detect_definition( - width=self.width, + width=self.width, height=self.height ) except: self.width, self.height = False, False self.definition = False - + if self.error and verbose: print(f"{cred}There seems to be an error with \"{filepath}\"{creset}") print(f"{cred} {self.error}{creset}") @@ -81,7 +81,7 @@ class Video(): if type(self.error) is str and "FileNotFoundError" in self.error: return self.error - + text = f"{self.codec} - " # 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 - " else: text += f"{self.filesize} MB - " - + text += f"'{self.path + self.filename_origin}'" if self.error: @@ -116,7 +116,7 @@ class Video(): if type(self.error) is str and "FileNotFoundError" in self.error: return self.error - + text = f"{self.path + self.filename_origin}\n" if self.definition and self.definition[0] and not self.definition[0].isnumeric(): @@ -174,17 +174,28 @@ class Video(): self.filename_tmp = newfilename # 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 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": args += ['-c:v', 'libx265'] + # Add max_muxing_queue_size to avoid buffer overflows 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 args += [self.path + self.filename_tmp] - + try: if verbose: subprocess.call(args) @@ -292,7 +303,7 @@ class Video(): width, height = Video.detect_resolution(filepath) if not width and not height: return False - + if width >= 2160 or height >= 2160: return "uhd" elif width >= 1440 or height >= 1080: