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:
parent
1516b6a321
commit
8319851836
@ -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]"))
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user