mirror of
https://github.com/AnimeThemes/animethemes-webm-verifier.git
synced 2026-07-11 01:24:30 +02:00
chore: lint & update python requirement (>=3.14) (#11)
This commit is contained in:
@@ -11,7 +11,7 @@ Test success/failure does **NOT** guarantee acceptance/rejection of submissions.
|
||||
**Requirements:**
|
||||
|
||||
* FFmpeg
|
||||
* Python >= 3.6
|
||||
* Python >= 3.14
|
||||
|
||||
**Install:**
|
||||
|
||||
|
||||
@@ -2,32 +2,29 @@
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
with open('README.md') as f:
|
||||
with open("README.md") as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name='animethemes-webm-verifier',
|
||||
version='1.2.4',
|
||||
author='AnimeThemes',
|
||||
author_email='admin@animethemes.moe',
|
||||
url='https://github.com/AnimeThemes/animethemes-webm-verifier',
|
||||
description='Verify WebM(s) Against AnimeThemes Encoding Standards',
|
||||
name="animethemes-webm-verifier",
|
||||
version="1.3",
|
||||
author="AnimeThemes",
|
||||
author_email="admin@animethemes.moe",
|
||||
url="https://github.com/AnimeThemes/animethemes-webm-verifier",
|
||||
description="Verify WebM(s) Against AnimeThemes Encoding Standards",
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
long_description_content_type="text/markdown",
|
||||
packages=find_packages(),
|
||||
classifiers=[
|
||||
'Intended Audience :: Developers',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: 3.15",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Natural Language :: English",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
python_requires='>=3.6',
|
||||
python_requires=">=3.14",
|
||||
install_requires=[
|
||||
'packaging',
|
||||
"packaging",
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
+26
-26
@@ -16,33 +16,33 @@ def main():
|
||||
"""Verify WebM(s) Against /r/AnimeThemes Encoding Standards"""
|
||||
# Load/Validate Arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='test_webm',
|
||||
description='Verify WebM(s) Against /r/AnimeThemes Encoding Standards',
|
||||
formatter_class=argparse.RawTextHelpFormatter
|
||||
prog="test_webm",
|
||||
description="Verify WebM(s) Against /r/AnimeThemes Encoding Standards",
|
||||
formatter_class=argparse.RawTextHelpFormatter,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'file',
|
||||
nargs='*',
|
||||
default=[f for f in os.listdir('.') if f.endswith('.webm')],
|
||||
"file",
|
||||
nargs="*",
|
||||
default=[f for f in os.listdir(".") if f.endswith(".webm")],
|
||||
type=file_arg_type,
|
||||
help='The WebM(s) to verify'
|
||||
help="The WebM(s) to verify",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--loglevel',
|
||||
nargs='?',
|
||||
default='info',
|
||||
choices=['debug', 'info', 'error'],
|
||||
help='Set logging level'
|
||||
"--loglevel",
|
||||
nargs="?",
|
||||
default="info",
|
||||
choices=["debug", "info", "error"],
|
||||
help="Set logging level",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--groups',
|
||||
nargs='*',
|
||||
"--groups",
|
||||
nargs="*",
|
||||
default=[group.value for group in TestGroup],
|
||||
choices=[group.value for group in TestGroup],
|
||||
help='Select groups of tests to run'
|
||||
help="Select groups of tests to run",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -51,26 +51,26 @@ def main():
|
||||
logging.basicConfig(
|
||||
stream=sys.stdout,
|
||||
level=logging.getLevelName(args.loglevel.upper()),
|
||||
format='%(levelname)s: %(message)s'
|
||||
format="%(levelname)s: %(message)s",
|
||||
)
|
||||
|
||||
# Env Check: Check that dependencies are installed
|
||||
if shutil.which('ffmpeg') is None:
|
||||
logging.error('FFmpeg is required')
|
||||
if shutil.which("ffmpeg") is None:
|
||||
logging.error("FFmpeg is required")
|
||||
sys.exit()
|
||||
|
||||
if shutil.which('ffprobe') is None:
|
||||
logging.error('FFprobe is required')
|
||||
if shutil.which("ffprobe") is None:
|
||||
logging.error("FFprobe is required")
|
||||
sys.exit()
|
||||
|
||||
if not args.file:
|
||||
logging.error('No WebMs to verify')
|
||||
logging.error("No WebMs to verify")
|
||||
sys.exit()
|
||||
|
||||
logging.info('Verifying files...')
|
||||
logging.info("Verifying files...")
|
||||
|
||||
for file in args.file:
|
||||
logging.info('Using file \'%s\'...', file)
|
||||
logging.info("Using file '%s'...", file)
|
||||
|
||||
webm_format = WebmFormat(file)
|
||||
|
||||
@@ -78,7 +78,7 @@ def main():
|
||||
if logging.root.isEnabledFor(logging.DEBUG):
|
||||
webm_format.debug_dump()
|
||||
|
||||
logging.info('Running Tests...')
|
||||
logging.info("Running Tests...")
|
||||
test_loader = unittest.TestLoader()
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
@@ -93,8 +93,8 @@ def main():
|
||||
unittest.TextTestRunner().run(suite)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
logging.error('Exiting after keyboard interrupt')
|
||||
logging.error("Exiting after keyboard interrupt")
|
||||
|
||||
+14
-14
@@ -9,20 +9,20 @@ class TestAudio(TestWebm):
|
||||
# Audio must use the Opus format.
|
||||
def test_audio_codec(self):
|
||||
"""Test if the audio codec is opus"""
|
||||
audio_codec = self.webm_format.get_audio_stream_entry('codec_name')
|
||||
self.assertEqual(audio_codec, 'opus', 'Incorrect audio codec')
|
||||
audio_codec = self.webm_format.get_audio_stream_entry("codec_name")
|
||||
self.assertEqual(audio_codec, "opus", "Incorrect audio codec")
|
||||
|
||||
# Audio must be normalized as described by the AES Streaming Loudness Recommendation.
|
||||
def test_loudness_i(self):
|
||||
"""Test if the average perceptual loudness is near the targeted -16 LUFS"""
|
||||
input_i = float(self.webm_format.get_loudness_entry('input_i'))
|
||||
self.assertTrue(-16.25 <= input_i <= -15.75, 'Unexpected target loudness')
|
||||
input_i = float(self.webm_format.get_loudness_entry("input_i"))
|
||||
self.assertTrue(-16.25 <= input_i <= -15.75, "Unexpected target loudness")
|
||||
|
||||
# Audio must be normalized as described by the AES Streaming Loudness Recommendation.
|
||||
def test_loudness_tp(self):
|
||||
"""Test if the true peak of the audio stream is less than -1.0 dB"""
|
||||
input_tp = float(self.webm_format.get_loudness_entry('input_tp'))
|
||||
self.assertTrue(input_tp <= -1.0, 'Unexpected true peak')
|
||||
input_tp = float(self.webm_format.get_loudness_entry("input_tp"))
|
||||
self.assertTrue(input_tp <= -1.0, "Unexpected true peak")
|
||||
|
||||
# If the source is a DVD or BD release with a source bitrate of >= 320 kbps,
|
||||
# the audio stream must use a bitrate of 320 kbps.
|
||||
@@ -30,26 +30,26 @@ class TestAudio(TestWebm):
|
||||
def test_audio_bitrate(self):
|
||||
"""Test if the audio bitrate is near the targeted 192 kbps or 320 kbps"""
|
||||
# Note: libopus defaults to VBR mode so we will allow for variance
|
||||
audio_bitrate = int(self.webm_format.get_audio_format_entry('bit_rate'))
|
||||
audio_bitrate = int(self.webm_format.get_audio_format_entry("bit_rate"))
|
||||
self.assertTrue(
|
||||
167000 <= audio_bitrate <= 217000 or 295000 <= audio_bitrate <= 345000,
|
||||
'Unexpected audio bitrate'
|
||||
"Unexpected audio bitrate",
|
||||
)
|
||||
|
||||
# Audio must use a sampling rate of 48k.
|
||||
def test_sample_rate(self):
|
||||
"""Test if the sampling rate is 48 kHz"""
|
||||
sample_rate = int(self.webm_format.get_audio_stream_entry('sample_rate'))
|
||||
self.assertEqual(sample_rate, 48000, 'Incorrect sample rate')
|
||||
sample_rate = int(self.webm_format.get_audio_stream_entry("sample_rate"))
|
||||
self.assertEqual(sample_rate, 48000, "Incorrect sample rate")
|
||||
|
||||
# Audio must use a two channel stereo mix.
|
||||
def test_channels(self):
|
||||
"""Test if there exists two audio channels"""
|
||||
channels = int(self.webm_format.get_audio_stream_entry('channels'))
|
||||
self.assertEqual(channels, 2, 'Incorrect audio channels')
|
||||
channels = int(self.webm_format.get_audio_stream_entry("channels"))
|
||||
self.assertEqual(channels, 2, "Incorrect audio channels")
|
||||
|
||||
# Audio must use a two channel stereo mix.
|
||||
def test_layout(self):
|
||||
"""Test if the audio layout is stereo"""
|
||||
channel_layout = self.webm_format.get_audio_stream_entry('channel_layout')
|
||||
self.assertEqual(channel_layout, 'stereo', 'Incorrect audio layout')
|
||||
channel_layout = self.webm_format.get_audio_stream_entry("channel_layout")
|
||||
self.assertEqual(channel_layout, "stereo", "Incorrect audio layout")
|
||||
|
||||
+33
-28
@@ -10,77 +10,82 @@ class TestFormat(TestWebm):
|
||||
# Expectation 1: Index 0 stream is video
|
||||
def test_video_stream(self):
|
||||
"""Test if the video stream is first indexed stream"""
|
||||
first_stream_codec = self.webm_format.webm_format['streams'][0]['codec_type']
|
||||
self.assertEqual(first_stream_codec, 'video', 'First stream is not video')
|
||||
first_stream_codec = self.webm_format.webm_format["streams"][0]["codec_type"]
|
||||
self.assertEqual(first_stream_codec, "video", "First stream is not video")
|
||||
|
||||
# Expectation 2: Index 1 stream is audio
|
||||
def test_audio_stream(self):
|
||||
"""Test if the audio stream is second indexed stream"""
|
||||
second_stream_codec = self.webm_format.webm_format['streams'][1]['codec_type']
|
||||
self.assertEqual(second_stream_codec, 'audio', 'Second stream is not audio')
|
||||
second_stream_codec = self.webm_format.webm_format["streams"][1]["codec_type"]
|
||||
self.assertEqual(second_stream_codec, "audio", "Second stream is not audio")
|
||||
|
||||
# Expectation 3: There exists 1 video stream and 1 audio stream
|
||||
def test_stream_count(self):
|
||||
"""Test if the file has exactly two streams"""
|
||||
streams = len(self.webm_format.webm_format['streams'])
|
||||
self.assertEqual(streams, 2, 'Unexpected number of streams')
|
||||
streams = len(self.webm_format.webm_format["streams"])
|
||||
self.assertEqual(streams, 2, "Unexpected number of streams")
|
||||
|
||||
# Files must use the latest release version of FFmpeg.
|
||||
def test_encoder_name(self):
|
||||
"""Test if the encoder is FFmpeg"""
|
||||
for tag_key in self.webm_format.webm_format['format']['tags']:
|
||||
if tag_key.lower() == 'encoder':
|
||||
encoder = self.webm_format.webm_format['format']['tags'][tag_key]
|
||||
self.assertTrue(encoder.startswith('Lavf'), 'Incorrect Encoder')
|
||||
for tag_key in self.webm_format.webm_format["format"]["tags"]:
|
||||
if tag_key.lower() == "encoder":
|
||||
encoder = self.webm_format.webm_format["format"]["tags"][tag_key]
|
||||
self.assertTrue(encoder.startswith("Lavf"), "Incorrect Encoder")
|
||||
|
||||
# Files must use the latest release version of FFmpeg.
|
||||
def test_encoder_version(self):
|
||||
"""Test if the FFmpeg build is out of date"""
|
||||
for tag_key in self.webm_format.webm_format['format']['tags']:
|
||||
if tag_key.lower() == 'encoder':
|
||||
for tag_key in self.webm_format.webm_format["format"]["tags"]:
|
||||
if tag_key.lower() == "encoder":
|
||||
# FFmpeg versioning doesn't comply with PEP 440 so we can't use packaging.version
|
||||
webm_ffmpeg_version = version.parse(
|
||||
self.webm_format.webm_format['format']['tags'][tag_key].removeprefix('Lavf')
|
||||
self.webm_format.webm_format["format"]["tags"][
|
||||
tag_key
|
||||
].removeprefix("Lavf")
|
||||
)
|
||||
latest_ffmpeg_version = version.parse('61.7.100')
|
||||
latest_ffmpeg_version = version.parse("61.7.100")
|
||||
self.assertTrue(
|
||||
webm_ffmpeg_version >= latest_ffmpeg_version,
|
||||
'Build is out of date'
|
||||
webm_ffmpeg_version >= latest_ffmpeg_version, "Build is out of date"
|
||||
)
|
||||
|
||||
# Files must use the WebM container.
|
||||
def test_file_format(self):
|
||||
"""Test if the file format is webm"""
|
||||
webm_format = self.webm_format.webm_format['format']['format_name']
|
||||
self.assertEqual(webm_format, 'matroska,webm', 'Incorrect file format')
|
||||
webm_format = self.webm_format.webm_format["format"]["format_name"]
|
||||
self.assertEqual(webm_format, "matroska,webm", "Incorrect file format")
|
||||
|
||||
# Files must adhere to our size restrictions.
|
||||
def test_filesize(self):
|
||||
"""Test if the file size violates an approximation of the restrictions"""
|
||||
bit_rate = int(self.webm_format.webm_format['format']['bit_rate'])
|
||||
resolution = int(self.webm_format.get_video_stream_entry('height'))
|
||||
bit_rate = int(self.webm_format.webm_format["format"]["bit_rate"])
|
||||
resolution = int(self.webm_format.get_video_stream_entry("height"))
|
||||
# Linear fit approximation, we just want to flag egregious overall bitrates here
|
||||
self.assertTrue(bit_rate < resolution * 5000 + 683300, 'File size restriction violated')
|
||||
self.assertTrue(
|
||||
bit_rate < resolution * 5000 + 683300, "File size restriction violated"
|
||||
)
|
||||
|
||||
# Files must erase source metadata using -map_metadata -1.
|
||||
def test_metadata(self):
|
||||
"""Test if extraneous source file metadata exists"""
|
||||
found_source_metadata = False
|
||||
|
||||
for stream in self.webm_format.webm_format['streams']:
|
||||
for stream in self.webm_format.webm_format["streams"]:
|
||||
# Tag entry isn't guaranteed to exist, examples found in VP8 encodes
|
||||
for tag_key in stream.get('tags', []):
|
||||
if tag_key.lower() != 'encoder' and tag_key.lower() != 'duration':
|
||||
for tag_key in stream.get("tags", []):
|
||||
if tag_key.lower() != "encoder" and tag_key.lower() != "duration":
|
||||
found_source_metadata = True
|
||||
|
||||
# Tag entry is expected to exist here, haven't found examples where that isn't true
|
||||
for tag_key in self.webm_format.webm_format['format']['tags']:
|
||||
if tag_key.lower() != 'encoder':
|
||||
for tag_key in self.webm_format.webm_format["format"]["tags"]:
|
||||
if tag_key.lower() != "encoder":
|
||||
found_source_metadata = True
|
||||
|
||||
self.assertFalse(found_source_metadata, 'Extraneous source file metadata')
|
||||
self.assertFalse(found_source_metadata, "Extraneous source file metadata")
|
||||
|
||||
# Files must erase source menu data using -map_chapters -1.
|
||||
def test_chapters(self):
|
||||
"""Test if menu data exists"""
|
||||
self.assertTrue(not self.webm_format.webm_format['chapters'], 'Extraneous menu data')
|
||||
self.assertTrue(
|
||||
not self.webm_format.webm_format["chapters"], "Extraneous menu data"
|
||||
)
|
||||
|
||||
@@ -9,15 +9,16 @@ from ._test_video import TestVideo
|
||||
# The enumeration of test groups
|
||||
class TestGroup(Enum):
|
||||
"""An enumeration of test groups"""
|
||||
|
||||
def __new__(cls, value, test_class):
|
||||
obj = object.__new__(cls)
|
||||
obj._value_ = value
|
||||
obj.test_class = test_class
|
||||
return obj
|
||||
|
||||
FORMAT = ('format', TestFormat)
|
||||
VIDEO = ('video', TestVideo)
|
||||
AUDIO = ('audio', TestAudio)
|
||||
FORMAT = ("format", TestFormat)
|
||||
VIDEO = ("video", TestVideo)
|
||||
AUDIO = ("audio", TestAudio)
|
||||
|
||||
@staticmethod
|
||||
def value_of(val):
|
||||
|
||||
+24
-24
@@ -9,60 +9,60 @@ class TestVideo(TestWebm):
|
||||
# Videos must use the VP9 video codec.
|
||||
def test_video_codec(self):
|
||||
"""Test if the video codec is VP9"""
|
||||
video_codec = self.webm_format.get_video_stream_entry('codec_name')
|
||||
self.assertEqual(video_codec, 'vp9', 'Incorrect video codec')
|
||||
video_codec = self.webm_format.get_video_stream_entry("codec_name")
|
||||
self.assertEqual(video_codec, "vp9", "Incorrect video codec")
|
||||
|
||||
# Videos must use the yuv420p pixel format.
|
||||
def test_pix_fmt(self):
|
||||
"""Test if the pixel format is yuv420p"""
|
||||
pix_fmt = self.webm_format.get_video_stream_entry('pix_fmt')
|
||||
self.assertEqual(pix_fmt, 'yuv420p', 'Incorrect pixel format')
|
||||
pix_fmt = self.webm_format.get_video_stream_entry("pix_fmt")
|
||||
self.assertEqual(pix_fmt, "yuv420p", "Incorrect pixel format")
|
||||
|
||||
# Videos must identify colorspace
|
||||
def test_color_space(self):
|
||||
"""Test if the color space is an accepted value"""
|
||||
color_spaces = ['bt709', 'smpte170m', 'bt470bg']
|
||||
color_spaces = ["bt709", "smpte170m", "bt470bg"]
|
||||
|
||||
# Colorspace entries do not exist if not specified by the encoder
|
||||
color_space = self.webm_format.get_video_stream_entry('color_space')
|
||||
color_space = self.webm_format.get_video_stream_entry("color_space")
|
||||
|
||||
self.assertIn(color_space, color_spaces, 'Unexpected color_space')
|
||||
self.assertIn(color_space, color_spaces, "Unexpected color_space")
|
||||
|
||||
# Videos must identify colorspace
|
||||
def test_color_transfer(self):
|
||||
"""Test if the color transfer is an accepted value"""
|
||||
color_transfers = ['bt709', 'smpte170m', 'bt470bg']
|
||||
color_transfers = ["bt709", "smpte170m", "bt470bg"]
|
||||
|
||||
# Colorspace entries do not exist if not specified by the encoder
|
||||
color_transfer = self.webm_format.get_video_stream_entry('color_transfer')
|
||||
color_transfer = self.webm_format.get_video_stream_entry("color_transfer")
|
||||
|
||||
self.assertIn(color_transfer, color_transfers, 'Unexpected color_transfer')
|
||||
self.assertIn(color_transfer, color_transfers, "Unexpected color_transfer")
|
||||
|
||||
# Videos must identify colorspace
|
||||
def test_color_primaries(self):
|
||||
"""Test if the color primaries is an accepted value"""
|
||||
color_primaries = ['bt709', 'smpte170m', 'bt470bg']
|
||||
color_primaries = ["bt709", "smpte170m", "bt470bg"]
|
||||
|
||||
# Colorspace entries do not exist if not specified by the encoder
|
||||
color_primary = self.webm_format.get_video_stream_entry('color_primaries')
|
||||
color_primary = self.webm_format.get_video_stream_entry("color_primaries")
|
||||
|
||||
self.assertIn(color_primary, color_primaries, 'Unexpected color_primaries')
|
||||
self.assertIn(color_primary, color_primaries, "Unexpected color_primaries")
|
||||
|
||||
# Videos must be encoded at the same framerate as the source file.
|
||||
# Motion interpolated videos (60FPS converted) are not allowed.
|
||||
def test_framerate(self):
|
||||
"""Test if the average framerate of the video stream is 23.976 or 29.997 FPS"""
|
||||
expected_frame_rates = [
|
||||
'24000/1001',
|
||||
'2997/125',
|
||||
'23976/1000',
|
||||
'24/1',
|
||||
'30000/1001',
|
||||
'19001/634',
|
||||
'1990/83',
|
||||
'2997/100',
|
||||
'30/1'
|
||||
"24000/1001",
|
||||
"2997/125",
|
||||
"23976/1000",
|
||||
"24/1",
|
||||
"30000/1001",
|
||||
"19001/634",
|
||||
"1990/83",
|
||||
"2997/100",
|
||||
"30/1",
|
||||
]
|
||||
|
||||
avg_frame_rate = self.webm_format.get_video_stream_entry('avg_frame_rate')
|
||||
self.assertIn(avg_frame_rate, expected_frame_rates, 'Unexpected framerate')
|
||||
avg_frame_rate = self.webm_format.get_video_stream_entry("avg_frame_rate")
|
||||
self.assertIn(avg_frame_rate, expected_frame_rates, "Unexpected framerate")
|
||||
|
||||
@@ -13,10 +13,6 @@ class TestWebm(TestCase):
|
||||
:type webm_format: WebmFormat
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
testname,
|
||||
webm_format
|
||||
):
|
||||
def __init__(self, testname, webm_format):
|
||||
super().__init__(testname)
|
||||
self.webm_format = webm_format
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ import os
|
||||
def file_arg_type(arg_value):
|
||||
"""Test if the file is readable and is a WebM"""
|
||||
if not os.access(arg_value, os.R_OK):
|
||||
raise argparse.ArgumentTypeError(f'File \'{arg_value}\' does not exist')
|
||||
if not arg_value.endswith('.webm'):
|
||||
raise argparse.ArgumentTypeError(f'File \'{arg_value}\' is not WebM')
|
||||
raise argparse.ArgumentTypeError(f"File '{arg_value}' does not exist")
|
||||
if not arg_value.endswith(".webm"):
|
||||
raise argparse.ArgumentTypeError(f"File '{arg_value}' is not WebM")
|
||||
return arg_value
|
||||
|
||||
+104
-101
@@ -15,22 +15,22 @@ class WebmFormat:
|
||||
"""
|
||||
|
||||
format_args = [
|
||||
'ffprobe',
|
||||
'-v',
|
||||
'quiet',
|
||||
'-print_format',
|
||||
'json',
|
||||
'-show_streams',
|
||||
'-show_format',
|
||||
'-show_chapters'
|
||||
"ffprobe",
|
||||
"-v",
|
||||
"quiet",
|
||||
"-print_format",
|
||||
"json",
|
||||
"-show_streams",
|
||||
"-show_format",
|
||||
"-show_chapters",
|
||||
]
|
||||
|
||||
def __init__(self, file):
|
||||
self.webm_format = WebmFormat.get_webm_format(file)
|
||||
self.audio_format = WebmFormat.get_audio_format(file)
|
||||
self.loudness_stats = WebmFormat.get_loudness_stats(file)
|
||||
self.video_index = WebmFormat.get_stream_index(self.webm_format, 'video')
|
||||
self.audio_index = WebmFormat.get_stream_index(self.webm_format, 'audio')
|
||||
self.video_index = WebmFormat.get_stream_index(self.webm_format, "video")
|
||||
self.audio_index = WebmFormat.get_stream_index(self.webm_format, "audio")
|
||||
|
||||
# Source 1: WebM Streams/Formats
|
||||
@staticmethod
|
||||
@@ -43,10 +43,10 @@ class WebmFormat:
|
||||
:return: the container format and stream information of the file
|
||||
:rtype: dict
|
||||
"""
|
||||
logging.info('Retrieving WebmM stream/format data...')
|
||||
logging.info("Retrieving WebmM stream/format data...")
|
||||
|
||||
webm_args = WebmFormat.format_args + [file]
|
||||
webm_format = subprocess.check_output(webm_args).decode('utf-8')
|
||||
webm_format = subprocess.check_output(webm_args).decode("utf-8")
|
||||
return json.loads(webm_format)
|
||||
|
||||
# Source 2: Extracted audio stream, needed for verifying audio bitrate
|
||||
@@ -60,29 +60,29 @@ class WebmFormat:
|
||||
:return: the container format and stream information of the extracted audio
|
||||
:rtype: dict
|
||||
"""
|
||||
logging.info('Retrieving extracted audio stream/format data...')
|
||||
logging.info("Retrieving extracted audio stream/format data...")
|
||||
|
||||
ogg_file = f'{file}.ogg'
|
||||
ogg_file = f"{file}.ogg"
|
||||
|
||||
ogg_args = [
|
||||
'ffmpeg',
|
||||
'-v',
|
||||
'quiet',
|
||||
'-i',
|
||||
"ffmpeg",
|
||||
"-v",
|
||||
"quiet",
|
||||
"-i",
|
||||
file,
|
||||
'-vn',
|
||||
'-acodec',
|
||||
'copy',
|
||||
'-f',
|
||||
'ogg',
|
||||
'-y',
|
||||
ogg_file
|
||||
"-vn",
|
||||
"-acodec",
|
||||
"copy",
|
||||
"-f",
|
||||
"ogg",
|
||||
"-y",
|
||||
ogg_file,
|
||||
]
|
||||
|
||||
subprocess.call(ogg_args)
|
||||
|
||||
audio_args = WebmFormat.format_args + [ogg_file]
|
||||
audio_format = subprocess.check_output(audio_args).decode('utf-8')
|
||||
audio_format = subprocess.check_output(audio_args).decode("utf-8")
|
||||
audio_format = json.loads(audio_format)
|
||||
|
||||
os.remove(ogg_file)
|
||||
@@ -101,30 +101,31 @@ class WebmFormat:
|
||||
:return: the loudness stats of the file
|
||||
:rtype: dict
|
||||
"""
|
||||
logging.info('Retrieving loudness data...')
|
||||
logging.info("Retrieving loudness data...")
|
||||
|
||||
loudness_args = [
|
||||
'ffmpeg',
|
||||
'-i',
|
||||
"ffmpeg",
|
||||
"-i",
|
||||
file,
|
||||
'-hide_banner',
|
||||
'-nostats',
|
||||
'-vn',
|
||||
'-sn',
|
||||
'-dn',
|
||||
'-af',
|
||||
'loudnorm=I=-16:LRA=20:TP=-1:dual_mono=true:linear=true:print_format=json',
|
||||
'-f',
|
||||
'null',
|
||||
'NUL'
|
||||
"-hide_banner",
|
||||
"-nostats",
|
||||
"-vn",
|
||||
"-sn",
|
||||
"-dn",
|
||||
"-af",
|
||||
"loudnorm=I=-16:LRA=20:TP=-1:dual_mono=true:linear=true:print_format=json",
|
||||
"-f",
|
||||
"null",
|
||||
"NUL",
|
||||
]
|
||||
|
||||
loudness_output = subprocess.check_output(
|
||||
loudness_args,
|
||||
stderr=subprocess.STDOUT
|
||||
).decode('utf-8').strip()
|
||||
loudness_output = (
|
||||
subprocess.check_output(loudness_args, stderr=subprocess.STDOUT)
|
||||
.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
|
||||
loudness_stats = re.search(r'\{[^}]*\}', loudness_output, re.DOTALL)
|
||||
loudness_stats = re.search(r"\{[^}]*\}", loudness_output, re.DOTALL)
|
||||
return json.loads(loudness_stats.group(0))
|
||||
|
||||
# We expect video at index 0 and audio at index 1,
|
||||
@@ -142,8 +143,8 @@ class WebmFormat:
|
||||
:return: the index of the stream of the given type
|
||||
:rtype: int
|
||||
"""
|
||||
for i in range(len(webm_format['streams'])):
|
||||
codec_type = webm_format['streams'][i]['codec_type']
|
||||
for i in range(len(webm_format["streams"])):
|
||||
codec_type = webm_format["streams"][i]["codec_type"]
|
||||
if codec_type == target_codec_type:
|
||||
return i
|
||||
return 0
|
||||
@@ -157,7 +158,7 @@ class WebmFormat:
|
||||
:return: the value of the named entry for the video stream
|
||||
:rtype: str
|
||||
"""
|
||||
return self.webm_format['streams'][self.video_index].get(entry, '')
|
||||
return self.webm_format["streams"][self.video_index].get(entry, "")
|
||||
|
||||
# Get entry from audio stream
|
||||
def get_audio_stream_entry(self, entry):
|
||||
@@ -168,7 +169,7 @@ class WebmFormat:
|
||||
:return: the value of the named entry for the audio stream
|
||||
:rtype: str
|
||||
"""
|
||||
return self.webm_format['streams'][self.audio_index].get(entry, '')
|
||||
return self.webm_format["streams"][self.audio_index].get(entry, "")
|
||||
|
||||
# Get entry from audio format
|
||||
def get_audio_format_entry(self, entry):
|
||||
@@ -179,7 +180,7 @@ class WebmFormat:
|
||||
:return: the value of the named entry for the audio format
|
||||
:rtype: str
|
||||
"""
|
||||
return self.audio_format['format'].get(entry, '')
|
||||
return self.audio_format["format"].get(entry, "")
|
||||
|
||||
# Get entry from loudness stats
|
||||
def get_loudness_entry(self, entry):
|
||||
@@ -190,89 +191,91 @@ class WebmFormat:
|
||||
:return: the value of the named entry for loudness stats
|
||||
:rtype: str
|
||||
"""
|
||||
return self.loudness_stats.get(entry, '')
|
||||
return self.loudness_stats.get(entry, "")
|
||||
|
||||
# Dump test data
|
||||
def debug_dump(self):
|
||||
"""Log container format and stream information of the file for debugging"""
|
||||
logging.debug('Dumping test data...')
|
||||
logging.debug('video_index: \'%s\'', self.video_index)
|
||||
logging.debug('audio_index: \'%s\'', self.audio_index)
|
||||
logging.debug("Dumping test data...")
|
||||
logging.debug("video_index: '%s'", self.video_index)
|
||||
logging.debug("audio_index: '%s'", self.audio_index)
|
||||
logging.debug(
|
||||
'webm_format[streams][0][codec_type]: \'%s\'',
|
||||
self.webm_format['streams'][0]['codec_type']
|
||||
"webm_format[streams][0][codec_type]: '%s'",
|
||||
self.webm_format["streams"][0]["codec_type"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][1][codec_type]: \'%s\'',
|
||||
self.webm_format['streams'][1]['codec_type']
|
||||
)
|
||||
logging.debug('len(webm_format[streams]): \'%s\'', len(self.webm_format['streams']))
|
||||
logging.debug(
|
||||
'webm_format[format][format_name]: \'%s\'',
|
||||
self.webm_format['format']['format_name']
|
||||
"webm_format[streams][1][codec_type]: '%s'",
|
||||
self.webm_format["streams"][1]["codec_type"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[format][bit_rate]): \'%s\'',
|
||||
self.webm_format['format']['bit_rate']
|
||||
"len(webm_format[streams]): '%s'", len(self.webm_format["streams"])
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][video_index][height]: \'%s\'',
|
||||
self.webm_format['streams'][self.video_index]['height']
|
||||
)
|
||||
logging.debug('webm_format[chapters]: \'%s\'', self.webm_format["chapters"])
|
||||
logging.debug(
|
||||
'webm_format[streams][video_index][codec_name]: \'%s\'',
|
||||
self.webm_format['streams'][self.video_index]['codec_name']
|
||||
"webm_format[format][format_name]: '%s'",
|
||||
self.webm_format["format"]["format_name"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][video_index][pix_fmt]: \'%s\'',
|
||||
self.webm_format['streams'][self.video_index]['pix_fmt']
|
||||
"webm_format[format][bit_rate]): '%s'",
|
||||
self.webm_format["format"]["bit_rate"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][video_index].get(color_space): \'%s\'',
|
||||
self.webm_format['streams'][self.video_index].get('color_space')
|
||||
"webm_format[streams][video_index][height]: '%s'",
|
||||
self.webm_format["streams"][self.video_index]["height"],
|
||||
)
|
||||
logging.debug("webm_format[chapters]: '%s'", self.webm_format["chapters"])
|
||||
logging.debug(
|
||||
"webm_format[streams][video_index][codec_name]: '%s'",
|
||||
self.webm_format["streams"][self.video_index]["codec_name"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][video_index].get(color_transfer): \'%s\'',
|
||||
self.webm_format['streams'][self.video_index].get('color_transfer')
|
||||
"webm_format[streams][video_index][pix_fmt]: '%s'",
|
||||
self.webm_format["streams"][self.video_index]["pix_fmt"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][video_index].get(color_primaries): \'%s\'',
|
||||
self.webm_format['streams'][self.video_index].get('color_primaries')
|
||||
"webm_format[streams][video_index].get(color_space): '%s'",
|
||||
self.webm_format["streams"][self.video_index].get("color_space"),
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][video_index][avg_frame_rate]: \'%s\'',
|
||||
self.webm_format['streams'][self.video_index]['avg_frame_rate']
|
||||
"webm_format[streams][video_index].get(color_transfer): '%s'",
|
||||
self.webm_format["streams"][self.video_index].get("color_transfer"),
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][audio_index][codec_name]: \'%s\'',
|
||||
self.webm_format['streams'][self.audio_index]['codec_name']
|
||||
"webm_format[streams][video_index].get(color_primaries): '%s'",
|
||||
self.webm_format["streams"][self.video_index].get("color_primaries"),
|
||||
)
|
||||
logging.debug(
|
||||
'[loudness_stats] input_i: \'%s\', '
|
||||
'input_lra: \'%s\', '
|
||||
'input_tp: \'%s\', '
|
||||
'input_thresh: \'%s\', '
|
||||
'target_offset: \'%s\'',
|
||||
self.loudness_stats['input_i'],
|
||||
self.loudness_stats['input_lra'],
|
||||
self.loudness_stats['input_tp'],
|
||||
self.loudness_stats['input_thresh'],
|
||||
self.loudness_stats['target_offset'],
|
||||
"webm_format[streams][video_index][avg_frame_rate]: '%s'",
|
||||
self.webm_format["streams"][self.video_index]["avg_frame_rate"],
|
||||
)
|
||||
logging.debug(
|
||||
'audio_format[format][bitrate]: \'%s\'',
|
||||
self.audio_format['format']['bit_rate']
|
||||
"webm_format[streams][audio_index][codec_name]: '%s'",
|
||||
self.webm_format["streams"][self.audio_index]["codec_name"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][audio_index][sample_rate]: \'%s\'',
|
||||
self.webm_format['streams'][self.audio_index]['sample_rate']
|
||||
"[loudness_stats] input_i: '%s', "
|
||||
"input_lra: '%s', "
|
||||
"input_tp: '%s', "
|
||||
"input_thresh: '%s', "
|
||||
"target_offset: '%s'",
|
||||
self.loudness_stats["input_i"],
|
||||
self.loudness_stats["input_lra"],
|
||||
self.loudness_stats["input_tp"],
|
||||
self.loudness_stats["input_thresh"],
|
||||
self.loudness_stats["target_offset"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][audio_index][channels]: \'%s\'',
|
||||
self.webm_format['streams'][self.audio_index]['channels']
|
||||
"audio_format[format][bitrate]: '%s'",
|
||||
self.audio_format["format"]["bit_rate"],
|
||||
)
|
||||
logging.debug(
|
||||
'webm_format[streams][audio_index][channel_layout]: \'%s\'',
|
||||
self.webm_format['streams'][self.audio_index]['channel_layout']
|
||||
"webm_format[streams][audio_index][sample_rate]: '%s'",
|
||||
self.webm_format["streams"][self.audio_index]["sample_rate"],
|
||||
)
|
||||
logging.debug(
|
||||
"webm_format[streams][audio_index][channels]: '%s'",
|
||||
self.webm_format["streams"][self.audio_index]["channels"],
|
||||
)
|
||||
logging.debug(
|
||||
"webm_format[streams][audio_index][channel_layout]: '%s'",
|
||||
self.webm_format["streams"][self.audio_index]["channel_layout"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user