Last active
August 10, 2024 07:08
If you have the black flicker at the top of a screen recording because of OBS and the screen getting out-of-sync, this script will attempt to fill in the (literal) blanks using the previous frame. This will cause shearing and may also produce slight artifacts and errors, but it's a lot better than the giant black flicker.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
""" | |
If you have the black flicker at the top of a screen recording because of OBS and the screen getting out-of-sync, this | |
script will attempt to fill in the (literal) blanks using the previous frame. This will cause shearing and may also | |
produce slight artifacts and errors, but it's a lot better than the giant black flicker. | |
""" | |
import cv2, time, datetime | |
import numpy as np | |
def process_video(input_path, output_path, n=5): | |
cap = cv2.VideoCapture(input_path) | |
if not cap.isOpened(): | |
print("Error: Could not open input video!") | |
return | |
# Get video properties | |
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
video_fps = cap.get(cv2.CAP_PROP_FPS) | |
fourcc = cv2.VideoWriter_fourcc(*'XVID') | |
out = cv2.VideoWriter(output_path, fourcc, video_fps, (frame_width, frame_height)) | |
if not out.isOpened(): | |
print("Error: Could not open output video!") | |
return | |
# Initialize the black pixel counter buffer | |
black_pixel_counter = np.zeros((frame_height, frame_width), dtype=int) | |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
frame_counter = 0 | |
ret, prev_frame = cap.read() | |
start_timestamp = prev_second = prev_frame_timestamp = time.time() | |
duration = eta = 0 | |
prev_second_frames = fps = 0 | |
percentage = 0.0 | |
while ret: | |
ret, curr_frame = cap.read() | |
if not ret: | |
break | |
curr_frame_timestamp = time.time() | |
if curr_frame_timestamp - prev_second > 1: | |
percentage = round((frame_counter/total_frames)*100, 2) | |
duration = datetime.timedelta(seconds=int(curr_frame_timestamp-start_timestamp)) | |
fps = frame_counter - prev_second_frames | |
eta = datetime.timedelta(seconds=int((total_frames-frame_counter)/fps)) | |
prev_second = curr_frame_timestamp | |
prev_second_frames = frame_counter | |
# Create a black_mask where the current frame is black (#000000) | |
black_mask = np.all(curr_frame == [0, 0, 0], axis=-1) | |
# Update the black pixel counter buffer | |
black_pixel_counter[black_mask] += 1 | |
black_pixel_counter[~black_mask] = 0 | |
# Create a black_mask for pixels that have been black for less than or equal to n frames | |
mask = black_mask & (black_pixel_counter <= n) | |
# Replace black pixels with pixels from the previous frame | |
curr_frame[mask] = prev_frame[mask] | |
out.write(curr_frame) | |
print(f'\r {percentage}% (#{frame_counter}/{total_frames} @{fps}/s, {duration}~{eta}) ', end='\r') | |
prev_frame = curr_frame | |
prev_frame_timestamp = curr_frame_timestamp | |
frame_counter += 1 | |
cap.release() | |
out.release() | |
print("\nProcessing complete.") | |
if __name__ == '__main__': | |
import sys, os | |
try: | |
if len(sys.argv)<=1: | |
print('Usage: ./remove_black_flicker.py inputvideo [outputvideo]') | |
elif len(sys.argv)==2: | |
process_video(sys.argv[1], f'{os.path.dirname(sys.argv[1])}/unflickered_{os.path.basename(sys.argv[1])}') | |
elif len(sys.argv)>=3: | |
process_video(sys.argv[1], sys.argv[2]) | |
except KeyboardInterrupt: | |
print('\nProcessing cancelled!') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment