Skip to content

Instantly share code, notes, and snippets.

@Lampe2020
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.
#!/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