diff --git a/demos/video-capture-simple.py b/demos/video-capture-simple.py index 086dbaa..1e7f7f9 100755 --- a/demos/video-capture-simple.py +++ b/demos/video-capture-simple.py @@ -71,6 +71,11 @@ def main() -> None: with mss.mss() as sct: monitor = sct.monitors[1] + # Because of how H.264 video stores color information, libx264 requires the video size to be a multiple of + # two. + monitor["width"] = (monitor["width"] // 2) * 2 + monitor["height"] = (monitor["height"] // 2) * 2 + with av.open(FILENAME, "w") as avmux: # The "avmux" object we get back from "av.open" represents the MP4 file. That's a container that holds # the video, as well as possibly audio and more. These are each called "streams". We only create one diff --git a/demos/video-capture.py b/demos/video-capture.py index 18ff9f0..cd7b8ac 100755 --- a/demos/video-capture.py +++ b/demos/video-capture.py @@ -397,6 +397,12 @@ def main() -> None: metavar="LEFT,TOP,RIGHT,BOTTOM", help="region to capture as comma-separated coordinates", ) + parser.add_argument( + "-2", + "--region-crop-to-multiple-of-two", + action=argparse.BooleanOptionalAction, + help="crop the capture region to a multiple of two, as required by some codecs (default: only for libx264 and libx265)", + ) parser.add_argument( "-c", "--codec", @@ -426,6 +432,7 @@ def main() -> None: codec = args.codec filename = args.output duration_secs = args.duration_secs + region_crop_to_multiple_of_two = args.region_crop_to_multiple_of_two with mss.mss() as sct: if args.region: @@ -439,6 +446,17 @@ def main() -> None: else: monitor = sct.monitors[args.monitor] + # Some codecs, such as libx264, require the region to be a multiple of 2, to get the chroma subsampling right. + # Others, such as h264_nvenc, do not; they'll pad to get the subsampling region, and add flags to the stream + # to tell the viewer to crop accordingly. + if region_crop_to_multiple_of_two is None: + # The user didn't specify; choose the default. We haven't tested many codecs, but we know these require + # it (at least, when using 4:2:0 subsampling). + region_crop_to_multiple_of_two = codec in {"libx264", "libx265"} + if region_crop_to_multiple_of_two: + monitor["width"] = (monitor["width"] // 2) * 2 + monitor["height"] = (monitor["height"] // 2) * 2 + # We don't pass the container format to av.open here, so it will choose it based on the extension: .mp4, .mkv, # etc. with av.open(filename, "w") as avmux: