Created
August 1, 2022 21:55
-
-
Save tigrouind/8cd1564568b7582feb776ad21edc8fff to your computer and use it in GitHub Desktop.
Synchronize subtitles of a given movie based on another movie
This file contains hidden or 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
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