Skip to content

Instantly share code, notes, and snippets.

@tigrouind
Created August 1, 2022 21:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tigrouind/8cd1564568b7582feb776ad21edc8fff to your computer and use it in GitHub Desktop.
Save tigrouind/8cd1564568b7582feb776ad21edc8fff to your computer and use it in GitHub Desktop.
Synchronize subtitles of a given movie based on another movie
import cv2
import numpy
import math
import srt
import datetime
vidA = cv2.VideoCapture('movieA.mkv') #reference
vidB = cv2.VideoCapture('movieB.mkv') #to synchronize
with open('subtitles.srt', 'r') as f: #from fileA.mkv
subs = list(srt.parse(f.read()))
seek = 1000 #adjustment is max [-500;500] frames
midseek = seek//2
posA = 0
posB = 0
permaOffset = 0
queue = []
for s in subs:
for offset in [[math.floor(s.start.total_seconds()*25),0], [math.floor(s.end.total_seconds()*25),1]]:
frameA = offset[0]
frameB = math.floor(offset[0]*25/24)-540-midseek+permaOffset #assume 25->24 fps conversion
#get reference frame from movieA
while posA < (frameA+1):
success,imageA = vidA.read()
posA = posA + 1
imageA = cv2.resize(imageA, (720//2, 304//2))
imageA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
brightness = numpy.average(imageA)
imageA = cv2.equalizeHist(imageA)
#fill queue
while posB < (frameB+seek):
success,imageB = vidB.read()
posB = posB + 1
if posB > frameB:
imageB = imageB[92:640, 0:1280] #crop
imageB = cv2.resize(imageB, (720//2, 304//2))
#might do other adjustments as well (eg: zoom)
#...
imageB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)
imageB = cv2.equalizeHist(imageB)
queue.append(imageB)
if len(queue) > seek:
queue.pop(0)
#search best matching frame
best = 0
max = 0
bestImage = None
for x in [-1] + list(range(seek)):
if x==-1:
earlyExit = True
x = midseek
else:
earlyExit = False
imageB = queue[x]
#try to align both frames before compare
imageCrop = imageB[50:304-50, 50:720-50] #crop
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(cv2.matchTemplate(imageA, imageCrop, cv2.TM_CCORR_NORMED))
offsetx = max_loc[0]-50
offsety = max_loc[1]-50
imageB = cv2.warpAffine(imageB, numpy.float32([[1, 0, offsetx],[0, 1, offsety]]), (imageB.shape[1], imageB.shape[0]))
res = cv2.PSNR(imageA, imageB)
if res > max: #take best result
max = res
best = x
bestImage = imageB
#if frame already get a good score or if brightness is too low, don't bother searching
if (earlyExit and res > 10.0) or brightness < 40:
break
#adjust start-end timings
target = datetime.timedelta(seconds=(frameB+best)/25)
if offset[1]==0:
s.start = target
else:
s.end = target
print (s.index, offset, int(max), int(brightness), permaOffset, best-midseek)
permaOffset += best-midseek
#make sure offset stay in acceptable range
if permaOffset>400:
permaOffset = 400
if permaOffset<-400:
permaOffset = -400
#save subtitles
with open('out.srt', 'w') as f:
f.write(srt.compose(subs))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment