Sync subtitles

From Rosetta Code
Task
Sync subtitles
You are encouraged to solve this task according to the task description, using any language you may know.

Sync subtitles

Sync subtitles

Sometimes, when you download a movie, the subtitles are out of sync with the audio in this movie. Video players like VLC solve the problem, but it must be done manually.

Make a program that takes the "movie.srt" file, and synchronizes the subtitles "n" seconds.

  • Try to fast-forward the subtitles by 9 seconds.
  • Try rolling back the subtitles by 9 seconds.

Take the excerpt from the following subtitles file as an example:


movie.srt

1
00:01:31,550 --> 00:01:36,347
Four billion years ago,
the first marine life forms.

2
00:01:36,555 --> 00:01:42,019
First came the fish...then slowly
other life forms evolved.

3
00:01:42,144 --> 00:01:43,979
Therefore, our ancestors...

4
00:01:43,979 --> 00:01:45,898
...came from fish.

5
00:01:46,232 --> 00:01:47,608
Everyone, come and see this.

6
00:01:47,733 --> 00:01:50,361
Cretaceous Tyrannosaurus.

7
00:01:50,736 --> 00:01:52,488
Ferocious!

8
00:01:58,035 --> 00:01:58,869
Red,

9
00:01:59,203 --> 00:02:00,079
Pong!

10
00:02:02,540 --> 00:02:03,999
Isn't this a gecko?

11
00:02:04,416 --> 00:02:07,419
How else can I get a 15 ton T-Rex in here?

12
00:02:07,503 --> 00:02:11,048
With our advanced technology, I shrank it down.
References

Amazing Hopper

#include <basico.h>

algoritmo
    
    fd=0
    abrir para leer ( "movie.srt", fd )
    
    iterar 
         s="", t1=0, t2=0, s1=0, s2=0
         usando '1000', leer línea desde 'fd', mover a 's'
         
         cuando ( "-->", está exactamente en 's' ){
              [1:8] obtener de 's', ---copiar en 's1'---
              convertir a segundos, sumarle '9', convertir a hora, guardar en 't1'
              [18:25] obtener de 's', ---copiar en 's2'--- 
              convertir a segundos, sumarle '9', convertir a hora, guardar en 't2'
              
              transformar(s1,t1,s), guardar en 's'
              transformar(s2,t2,s), guardar en 's'

         }
         imprimir ( s, "\n")
         
    mientras ' no sea fin de archivo (fd ) '
    
    cerrar archivo 'fd'
    
terminar
Output:
$ hopper3 basica/modsrt.bas > movie_corrected.srt
$ cat movie_corrected.srt
1  
00:01:40,550 --> 00:01:45,347
Four billion years ago,
the first marine life forms.

2
00:01:45,555 --> 00:01:51,019
First came the fish...then slowly
other life forms evolved.

3
00:01:51,144 --> 00:01:52,979
Therefore, our ancestors...

4
00:01:52,979 --> 00:01:54,898
...came from fish.

5
00:01:55,232 --> 00:01:56,608
Everyone, come and see this.

6
00:01:56,733 --> 00:01:59,361
Cretaceous Tyrannosaurus.

7
00:01:59,736 --> 00:02:01,488
Ferocious!

8
00:02:07,035 --> 00:02:07,869
Red,

9
00:02:08,203 --> 00:02:09,079
Pong!

10
00:02:11,540 --> 00:02:12,999
Isn't this a gecko?

11
00:02:13,416 --> 00:02:16,419
How else can I get a 15 ton T-Rex in here?

12
00:02:16,503 --> 00:02:20,048
With our advanced technology, I shrank it down.

BASIC

BASIC256

Translation of: FreeBASIC
f = freefile
print "After fast-forwarding 9 seconds:" + chr(10)
call syncSubtitles("movie.srt", "movie_corrected.srt", 9)
open f, "movie_corrected.srt"
while not eof(f)
	linea = readline (f)
	print linea;
end while
close

print chr(10) + chr(10)
print "After rolling-back 9 seconds:" + chr(10)
call syncSubtitles("movie.srt", "movie_corrected2.srt", -9)
open f, "movie_corrected2.srt"
while not eof(f)
	linea = readline (f)
	print linea;
end while
close
end

function addSeconds (timestring, secs)
	hh = int(mid(timestring, 1, 2))
	mm = int(mid(timestring, 4, 2))
	ss = int(mid(timestring, 7, 2)) + secs
	ttt = int(mid(timestring, 10, 3))

	while ss < 0
		ss = ss + 60
		mm = mm - 1
	end while
	while mm < 0
		mm = mm + 60
		hh = hh - 1
	end while
	while hh < 0
		hh = hh + 24
	end while

	mm = mm + ss \ 60
	hh = hh + mm \ 60
	ss = ss mod 60
	mm = mm mod 60
	hh = hh mod 24
	return right("0" + string(hh), 2) + ":" + right("0" + string(mm), 2) + ":" + right("0" + string(ss), 2) + "," + right("000" + string(ttt), 3)
end function

subroutine syncSubtitles (fileIn, fileOut, secs)
	fmt = "hh:MM:ss,ttt"
	f1 = freefile
	open f1, fileOut
	f2 = freefile
	open f2, fileIn
	while not eof(f2)
		linea = readline (f2)
		if instr(linea, "-->") > 0 then
			pio = mid(linea, 1, 12)
			pio = addSeconds(pio, secs)
			fin = mid(linea, 18, 12)
			fin = addSeconds(fin, secs)
			write f1, pio; " --> "; fin + chr(10)
		else
			write f1, linea
		end if
	end while
	close
end subroutine

Chipmunk Basic

Translation of: FreeBASIC
Works with: Chipmunk Basic version 3.6.4
100 cls
110 print "After fast-forwarding 9 seconds:";chr$(10)
120 syncsubtitles("movie.srt","movie_corrected.srt",9)
130 open "movie_corrected.srt" for input as #1
140 while not eof(1)
150   line input #1,linea$
160   print linea$
170 wend
180 close #1
190 print
200 print "After rolling-back 9 seconds:";chr$(10)
210 syncsubtitles("movie.srt","movie_corrected2.srt",-9)
220 open "movie_corrected2.srt" for input as #1
230 while not eof(1)
240   line input #1,linea$
250   print linea$
260 wend
270 close #1
280 end
290 sub addseconds$(timestr$,secs)
300   hh = val(mid$(timestr$,1,2))
310   mm = val(mid$(timestr$,4,2))
320   ss = val(mid$(timestr$,7,2))+secs
330   ttt = val(mid$(timestr$,10,3))
340   while ss < 0
350     ss = ss+60
360     mm = mm-1
370   wend
380   while mm < 0
390     mm = mm+60
400     hh = hh-1
410   wend
420   while hh < 0
430     hh = hh+24
440   wend
450   mm = mm+int(ss/60)
460   hh = hh+int(mm/60)
470   ss = ss mod 60
480   mm = mm mod 60
490   hh = hh mod 24
500   addseconds$ = right$("0"+str$(hh),2)+":"+right$("0"+str$(mm),2)+":"+right$("0"+str$(ss),2)+","+right$("000"+str$(ttt),3)
510 end sub
520 sub syncsubtitles(filein$,fileout$,secs)
530   fmt$ = "hh:MM:ss,ttt"
540   open fileout$ for output as #1
550   open filein$ for input as #2
560   while not eof(2)
570     line input #2,linea$
580     if instr(linea$,"-->") > 0 then
590       pio$ = mid$(linea$,1,12)
600       pio$ = addseconds$(pio$,secs)
610       fin$ = mid$(linea$,18,12)
620       fin$ = addseconds$(fin$,secs)
630       print #1,pio$;" --> ";fin$
640     else
650       print #1,linea$
660     endif
670   wend
680   close #2
690   close #1
700 end sub

FreeBASIC

Translation of: Wren
Function addSeconds(timeStr As String, secs As Integer) As String
    Dim As Integer hh, mm, ss, ttt
    hh = Val(Mid(timeStr, 1, 2))
    mm = Val(Mid(timeStr, 4, 2))
    ss = Val(Mid(timeStr, 7, 2)) + secs
    ttt = Val(Mid(timeStr, 10, 3))
    
    While ss < 0
        ss += 60
        mm -= 1
    Wend
    While mm < 0
        mm += 60
        hh -= 1
    Wend
    While hh < 0
        hh += 24
    Wend
    
    mm += ss \ 60
    hh += mm \ 60
    ss Mod= 60
    mm Mod= 60
    hh Mod= 24
    Return Right("0" & Str(hh), 2) & ":" & Right("0" & Str(mm), 2) & ":" & _
    Right("0" & Str(ss), 2) & "," & Right("000" & Str(ttt), 3)
End Function

Sub syncSubtitles(fileIn As String, fileOut As String, secs As Integer)
    Dim As String linea, pio, fin, fmt
    Dim As Ubyte f1, f2
    
    fmt = "hh:MM:ss,ttt"
    f1 = Freefile
    Open fileOut For Output As #f1
    f2 = Freefile
    Open fileIn For Input As #f2
    While Not Eof(f2)
        Line Input #f2, linea
        If Instr(linea, "-->") > 0 Then
            pio = Mid(linea, 1, 12)
            pio = addSeconds(pio, secs)
            fin = Mid(linea, 18, 12)
            fin = addSeconds(fin, secs)
            Print #f1, pio; " --> "; fin
        Else
            Print #f1, linea
        End If
    Wend
    Close #f2, #f1
End Sub

Dim As String linea
Dim As Ubyte f = Freefile

Print !"After fast-forwarding 9 seconds:\n"
syncSubtitles("movie.srt", "movie_corrected.srt", 9)
Open "movie_corrected.srt" For Input As #f
While Not Eof(f)
    Line Input #f, linea
    Print linea
Wend
Close #f

Print !"\n\nAfter rolling-back 9 seconds:\n"
syncSubtitles("movie.srt", "movie_corrected2.srt", -9)
Open "movie_corrected2.srt" For Input As #f
While Not Eof(f)
    Line Input #f, linea
    Print linea
Wend
Close #f

Sleep
Output:
Same as Wren entry.

Gambas

Translation of: FreeBASIC
Function addSeconds(timeStr As String, secs As Integer) As String 

  Dim hh As Integer, mm As Integer, ss As Integer, ttt As Integer
  hh = Val(Mid(timeStr, 1, 2)) 
  mm = Val(Mid(timeStr, 4, 2)) 
  ss = Val(Mid(timeStr, 7, 2)) + secs 
  ttt = Val(Mid(timeStr, 10, 3)) 
  
  While ss < 0 
    ss += 60 
    mm -= 1 
  Wend 
  While mm < 0 
    mm += 60 
    hh -= 1 
  Wend 
  While hh < 0 
    hh += 24 
  Wend 
  
  mm += ss \ 60 
  hh += mm \ 60 
  ss = ss Mod 60 
  mm = mm Mod 60 
  hh = hh Mod 24 
  Return Right("0" & Str(hh), 2) & ":" & Right("0" & Str(mm), 2) & ":" & Right("0" & Str(ss), 2) & "," & Right("000" & Str(ttt), 3) 

End Function 

Sub syncSubtitles(fileIn As String, fileOut As String, secs As Integer) 

  Dim linea As String, pio As String, fin As String, fmt As String
  Dim f1 As File, f2 As File
  
  fmt = "hh:MM:ss,ttt" 
  f1 = Open fileOut For Output
  f2 = Open fileIn For Input 
  While Not Eof(f2) 
    Line Input #f2, linea 
    If InStr(linea, "-->") > 0 Then 
      pio = Mid(linea, 1, 12) 
      pio = addSeconds(pio, secs) 
      fin = Mid(linea, 18, 12) 
      fin = addSeconds(fin, secs) 
      Print #f1, pio; " --> "; fin 
    Else 
      Print #f1, linea 
    End If 
  Wend 
  Close #f1
  Close #f2

End Sub 

Public Sub Main()  
  
  Dim linea As String 
  Dim f As File
  
  Print "After fast-forwarding 9 seconds:\n" 
  syncSubtitles("movie.srt", "movie_corrected.srt", 9) 
  f = Open "movie_corrected.srt" For Input
  While Not Eof(f) 
    Line Input #f, linea 
    Print linea 
  Wend 
  Close #f 
  
  Print "\n\nAfter rolling-back 9 seconds:\n" 
  syncSubtitles("movie.srt", "movie_corrected2.srt", -9) 
  f = Open "movie_corrected2.srt" For Input
  While Not Eof(f) 
    Line Input #f, linea 
    Print linea 
  Wend 
  Close #f
  
End
Output:
Same as FreeBASIC entry.

PureBasic

Translation of: FreeBASIC
Procedure.s addSeconds(timeStr.s, secs)
  Protected hours, minutes, seconds, milliseconds
  Protected.s hh, mm, ss, ttt
  
  hh = Mid(timeStr, 1, 2)
  hours = Val(hh)
  mm = Mid(timeStr, 4, 2)
  minutes = Val(mm)
  ss = Mid(timeStr, 7, 2)
  seconds = Val(ss)
  seconds + secs
  ttt = Mid(timeStr, 10, 3)
  
  If seconds < 0
    seconds + 60
    minutes - 1
  EndIf
  If minutes < 0
    minutes + 60
    hours - 1
  EndIf
  If hours < 0
    hours + 24
  EndIf
  ProcedureReturn Str(hours) + ":" + Str(minutes) + ":" + Str(seconds) + "," + ttt
EndProcedure

Procedure syncSubtitles(fileIn.s, fileOut.s, secs)
  Protected line.s, pio.s, fin.s
  OpenFile(0, fileIn)
  CreateFile(1, fileOut)
  While Not Eof(0)
    line = ReadString(0)
    If FindString(line, "-->", 1)
      pio = Mid(line, 1, 12)
      fin = Mid(line, 18, 12)
      pio = addSeconds(pio, secs)
      fin = addSeconds(fin, secs)
      WriteStringN(1, pio + " --> " + fin)
    Else
      WriteStringN(1, line)
    EndIf
  Wend
  CloseFile(0)
  CloseFile(1)
EndProcedure

OpenConsole()
PrintN("After fast-forwarding 9 seconds:" + #CRLF$)
syncSubtitles("movie.srt", "movie_corrected.srt", 9)
OpenFile(0, "movie_corrected.srt")
While Not Eof(0)
  PrintN(ReadString(0))
Wend
CloseFile(0)

PrintN(#CRLF$ + #CRLF$ + "After rolling-back 9 seconds:" + #CRLF$)
syncSubtitles("movie.srt", "movie_corrected2.srt", -9)
OpenFile(0, "movie_corrected2.srt")
While Not Eof(0)
  PrintN(ReadString(0))
Wend
CloseFile(0)

PrintN(#CRLF$ + "Press ENTER to exit"): Input()
CloseConsole()
Output:
Same as FreeBASIC entry.

QB64

Dim As String linea
f = FreeFile

Print "After fast-forwarding 9 seconds:"; Chr$(10)
Call syncSubtitles("movie.srt", "movie_corrected.srt", 9)
Open "movie_corrected.srt" For Input As #f
While Not EOF(f)
    Line Input #f, linea
    Print linea
Wend
Close #f

Print Chr$(10); Chr$(10); "After rolling-back 9 seconds:"; Chr$(10)
Call syncSubtitles("movie.srt", "movie_corrected2.srt", -9)
Open "movie_corrected2.srt" For Input As #f
While Not EOF(f)
    Line Input #f, linea
    Print linea
Wend
Close #f

Function addSeconds$ (timeStr As String, secs As Integer)
    Dim As Integer hh, mm, ss, ttt
    hh = Val(Mid$(timeStr, 1, 2))
    mm = Val(Mid$(timeStr, 4, 2))
    ss = Val(Mid$(timeStr, 7, 2)) + secs
    ttt = Val(Mid$(timeStr, 10, 3))

    While ss < 0
        ss = ss + 60
        mm = mm - 1
    Wend
    While mm < 0
        mm = mm + 60
        hh = hh - 1
    Wend
    While hh < 0
        hh = hh + 24
    Wend

    mm = mm + ss \ 60
    hh = hh + mm \ 60
    ss = ss Mod 60
    mm = mm Mod 60
    hh = hh Mod 24
    addSeconds$ = Right$("0" + Str$(hh), 2) + ":" + Right$("0" + Str$(mm), 2) + ":" + Right$("0" + Str$(ss), 2) + "," + Right$("000" + Str$(ttt), 3)
End Function

Sub syncSubtitles (fileIn As String, fileOut As String, secs As Integer)
    Dim As String linea, pio, fin, fmt
    Dim As Integer f1, f2

    fmt = "hh:MM:ss,ttt"
    f1 = FreeFile
    Open fileOut For Output As #f1
    f2 = FreeFile
    Open fileIn For Input As #f2
    While Not EOF(f2)
        Line Input #f2, linea
        If InStr(linea, "-->") > 0 Then
            pio = Mid$(linea, 1, 12)
            pio = addSeconds$(pio, secs)
            fin = Mid$(linea, 18, 12)
            fin = addSeconds$(fin, secs)
            Print #f1, pio; " --> "; fin
        Else
            Print #f1, linea
        End If
    Wend
    Close #f2, #f1
End Sub

Yabasic

Translation of: FreeBASIC
print "After fast-forwarding 9 seconds:\n"
syncSubtitles("movie.srt", "movie_corrected.srt", 9)
f = open("movie_corrected.srt")
while not eof(f)
    line input #f linea$
    print linea$
wend
close #f

print "\n\nAfter rolling-back 9 seconds:\n"
syncSubtitles("movie.srt", "movie_corrected2.srt", -9)
f = open("movie_corrected2.srt")
while not eof(f)
    line input #f linea$
    print linea$
wend
close #f
end

sub addSeconds$ (timeStr$, secs)
    hh = val(mid$(timeStr$, 1, 2))
    mm = val(mid$(timeStr$, 4, 2))
    ss = val(mid$(timeStr$, 7, 2)) + secs
    ttt = val(mid$(timeStr$, 10, 3))

    while ss < 0
        ss = ss + 60
        mm = mm - 1
    wend
    while mm < 0
        mm = mm + 60
        hh = hh - 1
    wend
    while hh < 0
        hh = hh + 24
    wend

    mm = mm + int(ss / 60)
    hh = hh + int(mm / 60)
    ss = mod(ss, 60)
    mm = mod(mm, 60)
    hh = mod(hh, 24)
    return right$("0" + str$(hh), 2) + ":" + right$("0" + str$(mm), 2) + ":" + right$("0" + str$(ss), 2) + "," + right$("000" + str$(ttt), 3)
end sub

sub syncSubtitles (fileIn$, fileOut$, secs)
    fmt$ = "hh:MM:ss,ttt"
	nl$ = chr$(10)
    open fileOut$ for writing as #1
    open fileIn$ for reading as #2
    while not eof(2)
        line input #2 linea$
        if (instr(linea$, "-->") > 0) then
            pio$ = mid$(linea$, 1, 12)
            pio$ = addSeconds$(pio$, secs)
            fin$ = mid$(linea$, 18, 12)
            fin$ = addSeconds$(fin$, secs)
            print #1, pio$ + " --> " + fin$ + nl$;
        else
            print #1, linea$ + nl$;
        fi
    wend
    close #2
    close #1
end sub
Output:
Same as FreeBASIC entry.

C++

Translation of: FreeBASIC
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <ctime>

using namespace std;

string addSeconds(string timeStr, int secs) {
	int hours, minutes, seconds, milliseconds;
	sscanf(timeStr.c_str(), "%d:%d:%d,%d", &hours, &minutes, &seconds, &milliseconds);
	int total_seconds = hours * 3600 + minutes * 60 + seconds + secs;
	hours = total_seconds / 3600;
	total_seconds %= 3600;
	minutes = total_seconds / 60;
	seconds = total_seconds % 60;
	char buffer[13];
	sprintf(buffer, "%02d:%02d:%02d,%03d", hours, minutes, seconds, milliseconds);
	return string(buffer);
}

void syncSubtitles(string fileIn, string fileOut, int secs) {
	ifstream fin(fileIn);
	ofstream fout(fileOut);
	string line;
	while (getline(fin, line)) {
		if (line.find("-->") != string::npos) {
			string start = line.substr(0, 12);
			string end = line.substr(17, 12);
			start = addSeconds(start, secs);
			end = addSeconds(end, secs);
			fout << start << " --> " << end << "\n";
		} else {
			fout << line << "\n";
		}
	}
	fin.close();
	fout.close();
}

int main() {
	cout << "After fast-forwarding 9 seconds:\n\n";
	syncSubtitles("movie.srt", "movie_corrected.srt", 9);
	ifstream f("movie_corrected.srt");
	string line;
	while (getline(f, line)) {
		cout << line << "\n";
	}
	f.close();
	
	cout << "\n\nAfter rolling-back 9 seconds:\n\n";
	syncSubtitles("movie.srt", "movie_corrected2.srt", -9);
	ifstream f2("movie_corrected2.srt");
	while (getline(f2, line)) {
		cout << line << "\n";
	}
	f2.close();
	
	return 0;
}
Output:
Same as FreeBASIC entry.

COBOL

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SubtitleSync.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT INPUT-FILE ASSIGN TO "movie.srt".
           SELECT OUTPUT-FILE ASSIGN TO "movie_corrected.srt".
       DATA DIVISION.
       FILE SECTION.
       FD INPUT-FILE.
       01 IN-REC PIC X(100).
       FD OUTPUT-FILE.
       01 OUT-REC PIC X(100).
       WORKING-STORAGE SECTION.
       01 WS-SECS PIC S9(4) COMP VALUE 0.
       01 WS-LINE PIC X(100) VALUE SPACES.
       01 WS-START-TIME PIC X(12) VALUE SPACES.
       01 WS-END-TIME PIC X(12) VALUE SPACES.
       01 WS-HH PIC 99 VALUE 0.
       01 WS-MM PIC 99 VALUE 0.
       01 WS-SS PIC 99 VALUE 0.
       01 WS-TTT PIC 999 VALUE 0.
       PROCEDURE DIVISION.
           DISPLAY "After fast-forwarding 9 seconds:"
           MOVE 9 TO WS-SECS
           OPEN INPUT INPUT-FILE
           OPEN OUTPUT OUTPUT-FILE
           PERFORM syncSubtitles
           CLOSE INPUT-FILE
           CLOSE OUTPUT-FILE
           DISPLAY "After rolling-back 9 seconds:"
           MOVE -9 TO WS-SECS
           OPEN INPUT INPUT-FILE
           OPEN OUTPUT OUTPUT-FILE
           PERFORM syncSubtitles
           CLOSE INPUT-FILE
           CLOSE OUTPUT-FILE
           STOP RUN.
           
       syncSubtitles SECTION.
           READ INPUT-FILE AT END GO TO CLOSE-FILES
               IF WS-LINE(1:3) = "-->" THEN
                   MOVE WS-LINE(1:12) TO WS-START-TIME
                   MOVE WS-LINE(18:12) TO WS-END-TIME
                   PERFORM addSeconds
                   MOVE WS-START-TIME TO WS-LINE(1:12)
                   PERFORM addSeconds
                   MOVE WS-END-TIME TO WS-LINE(18:12)
                   STRING WS-START-TIME DELIMITED BY SIZE " --> " 
       DELIMITED BY SIZE WS-END-TIME DELIMITED BY SIZE INTO OUT-REC
                   WRITE OUT-REC
               ELSE
                   MOVE WS-LINE TO OUT-REC
                   WRITE OUT-REC
               END-IF
           END-READ
           CLOSE INPUT-FILE
           CLOSE OUTPUT-FILE
           EXIT.
           
       addSeconds SECTION.
           MOVE WS-START-TIME(1:2) TO WS-HH
           MOVE WS-START-TIME(4:2) TO WS-MM
           MOVE WS-START-TIME(7:2) TO WS-SS
           MOVE WS-START-TIME(10:3) TO WS-TTT
           ADD WS-SECS TO WS-SS
           IF WS-SS < 0
               ADD 60 TO WS-SS
               SUBTRACT 1 FROM WS-MM
           END-IF
           IF WS-MM < 0
               ADD 60 TO WS-MM
               SUBTRACT 1 FROM WS-HH
           END-IF
           IF WS-HH < 0
               ADD 24 TO WS-HH
           END-IF
           DIVIDE WS-SS BY 60 GIVING WS-MM REMAINDER WS-SS
           DIVIDE WS-MM BY 60 GIVING WS-HH REMAINDER WS-MM
           MOVE WS-HH TO WS-START-TIME(1:2)
           MOVE WS-MM TO WS-START-TIME(4:2)
           MOVE WS-SS TO WS-START-TIME(7:2)
           MOVE WS-TTT TO WS-START-TIME(10:3)
           EXIT.
       CLOSE-FILES SECTION.
           CLOSE INPUT-FILE
           CLOSE OUTPUT-FILE
           EXIT.

Java

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;

public final class SyncSubtitles {

	public static void main(String[] args) throws IOException {
		System.out.println("After fast-forwarding by 9 seconds:" + System.lineSeparator());
		syncSubtitles("./movie.srt", "./movie_amended.srt", 9);
		Files.lines(Path.of("./movie_amended.srt")).forEach(System.out::println);
		System.out.println();
		
		System.out.println("After rolling back by 9 seconds:" + System.lineSeparator());
		syncSubtitles("./movie.srt", "./movie_amended.srt", -9);
		Files.lines(Path.of("./movie_amended.srt")).forEach(System.out::println);
	}
	
	private static void syncSubtitles(String sourceFile, String targetFile, int seconds) throws IOException {
		final String arrow = " --> ";
		try ( BufferedReader reader = Files.newBufferedReader(Path.of(sourceFile));
			BufferedWriter writer = Files.newBufferedWriter(Path.of(targetFile)) ) {
			for ( String line = reader.readLine(); line != null; line = reader.readLine() ) {
				final int index = line.strip().indexOf(arrow);
		    	if ( index < 0 ) {
		    		writer.write(line + System.lineSeparator());		    		
		    	} else {		    		
		    		String startTime = addSeconds(line.substring(0, index), seconds);
		    		String finishTime = addSeconds(line.substring(index + arrow.length()), seconds);
		    		writer.write(startTime + arrow + finishTime + System.lineSeparator());				
		    	}		    		
			}
		}		
	}
	
	private static String addSeconds(String time, int seconds) {
		LocalTime original = LocalTime.parse(time.replace(',', '.')); // convert time to ISO-8601 standard
		LocalTime adjusted = original.plus(seconds, ChronoUnit.SECONDS);
		return adjusted.toString().replace('.', ','); // convert time back from ISO-8601 standard
	}

}
Output:
Same as the Wren example.

jq

Works with gojq, the Go implementation of jq

The following program has been written for gojq, the Go implementation of jq, and will almost surely need adapting for use with other versions of jq.

Usage

To fast-forward the subtitles by 9 seconds:

< movie.srt gojq -nRr --argjson seconds 9 -f sync-subtitles.jq

where sync-subtitles.jq is the name of the file containing the jq program. To roll-back, specify `seconds` as a negative number, e.g.:

< movie.srt gojq -nRr --argjson seconds -9 -f  sync-subtitles.jq
def syncSubtitles($secs):
  def fmt: "%H:%M:%S";
  def adjust: strptime(fmt) | .[5] += $seconds | strftime(fmt);

  if ($secs|type) != "number" then "The number of seconds must be specified as an integer" | error end
  | inputs as $line
  | ($line
     | capture("^(?<start>[^,]*),(?<startms>[0-9]*) *--> *(?<finish>[^,]*),(?<finishms>[0-9]*)")
     | "\(.start|adjust),\(.startms) --> \(.finish|adjust),\(.finishms)" )
  // $line ;

if $seconds > 0 then
  "Fast-forwarding \($seconds) seconds" | debug
  | syncSubtitles($seconds)
elif $seconds == 0 then
  "No resynchronization is needed" | debug
else
  "Rolling-back \(-$seconds) seconds" | debug
  | syncSubtitles($seconds)
end
Output:

As shown elsewhere on this page.

Julia

using Dates
    
function syncsubtitles(intext::AbstractString, sec::Integer, msec::Integer)
    outtext, fmt = "", dateformat"HH:MM:SS,sss" 
    deltatime = Dates.Second(sec) + Dates.Millisecond(msec)
    for line in split(intext, r"\r?\n")
        if !isempty(line) && length(begin times = split(line, " --> ") end) == 2
            start, stop = DateTime.(times, fmt) .+ deltatime
            line = join(Dates.format.((start, stop), fmt), " ==> ")
        end
        outtext *= line * "\n"
    end
    return outtext
end

function syncsubtitles(infile, outfile::AbstractString, sec, msec = 0)
    outs = open(outfile, "w")
    write(outs, syncsubtitles(read(infile, String), sec, msec))
    close(outs)
end

println("After fast-forwarding 9 seconds:\n")
syncsubtitles("movie.srt", "movie_corrected.srt", 9)
println(read("movie_corrected.srt", String))
println("After rolling-back 9 seconds:\n")
syncsubtitles("movie.srt", "movie_corrected2.srt", -9)
println(read("movie_corrected2.srt", String))
Output:
After fast-forwarding 9 seconds:

1
00:01:40,550 ==> 00:01:45,347
Four billion years ago,
the first marine life forms.

2
00:01:45,555 ==> 00:01:51,019
First came the fish...then slowly
other life forms evolved.

3
00:01:51,144 ==> 00:01:52,979
Therefore, our ancestors...

4
00:01:52,979 ==> 00:01:54,898
...came from fish.

5
00:01:55,232 ==> 00:01:56,608
Everyone, come and see this.

6
00:01:56,733 ==> 00:01:59,361
Cretaceous Tyrannosaurus.

7
00:01:59,736 ==> 00:02:01,488
Ferocious!

8
00:02:07,035 ==> 00:02:07,869
Red,

9
00:02:08,203 ==> 00:02:09,079
Pong!

10
00:02:11,540 ==> 00:02:12,999
Isn't this a gecko?

11
00:02:13,416 ==> 00:02:16,419
How else can I get a 15 ton T-Rex in here?

12
00:02:16,503 ==> 00:02:20,048
With our advanced technology, I shrank it down.


After rolling-back 9 seconds:

1
00:01:22,550 ==> 00:01:27,347
Four billion years ago,
the first marine life forms.

2
00:01:27,555 ==> 00:01:33,019
First came the fish...then slowly
other life forms evolved.

3
00:01:33,144 ==> 00:01:34,979
Therefore, our ancestors...

4
00:01:34,979 ==> 00:01:36,898
...came from fish.

5
00:01:37,232 ==> 00:01:38,608
Everyone, come and see this.

6
00:01:38,733 ==> 00:01:41,361
Cretaceous Tyrannosaurus.

7
00:01:41,736 ==> 00:01:43,488
Ferocious!

8
00:01:49,035 ==> 00:01:49,869
Red,

9
00:01:50,203 ==> 00:01:51,079
Pong!

10
00:01:53,540 ==> 00:01:54,999
Isn't this a gecko?

11
00:01:55,416 ==> 00:01:58,419
How else can I get a 15 ton T-Rex in here?

12
00:01:58,503 ==> 00:02:02,048
With our advanced technology, I shrank it down.

Perl

Translation of: Raku
# 20241005 Perl programming solution

use strict;
use warnings;
use Time::Piece;
use Time::Seconds;

my @lines = <STDIN>; 
for my $seconds (9, -9) {
   print "Original subtitle adjusted by ".sprintf("%+d", $seconds)." seconds.\n";
   for my $line (@lines) {
      if ($line =~ /(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})/) {
         my $start = adjust_time($1, $seconds);
         my $end   = adjust_time($2, $seconds);
         $line =~ s/\d{2}:\d{2}:\d{2},\d{3} --> \d{2}:\d{2}:\d{2},\d{3}/$start --> $end/;
         print $line
      } else {
         print $line  
      }
   }
   print "\n"  
}

sub adjust_time {
    my ($time, $seconds) = @_;
    my ($time_str, $milliseconds_str) = split /,/, $time;
    my ($hh, $mm, $ss) = split /:/, $time_str;
    my $t = Time::Piece->strptime("$hh:$mm:$ss", "%H:%M:%S");
    $t += $seconds;
    sprintf("%02d:%02d:%02d,%03d",$t->hour,$t->min,$t->sec,$milliseconds_str);
}

You may Attempt This Online!

Phix

Needed a bugfix to adjust_timedate() for time-only handling, in that a y,m,d of 0,0,0 pre-dates the introduction of the Gregorian calendar. There was also a floor(milliseconds) which should have been/is now round(milliseconds). Fixes can be grabbed from github if needed.

You could of course use sequence lines = read_lines("movie.srt") and write_lines("movie_corrected.srt",lines) for a file-based solution, but obviously that would not be compatible with JavaScript in a browser window.

requires("1.0.6") -- (a time-only handling bugfix in adjust_timedate)

constant movie_srt = `
1
00:01:31,550 --> 00:01:36,347
Four billion years ago,
00:01:36,555 --> 00:01:42,019
00:01:42,144 --> 00:01:43,979
00:01:43,979 --> 00:01:45,898
00:01:46,232 --> 00:01:47,608
00:01:47,733 --> 00:01:50,361
00:01:50,736 --> 00:01:52,488
00:01:58,035 --> 00:01:58,869
00:01:59,203 --> 00:02:00,079
00:02:02,540 --> 00:02:03,999
00:02:04,416 --> 00:02:07,419
00:02:07,503 --> 00:02:11,048
With our advanced technology, I shrank it down.
`,
sep = ` --> `,
fmt = `hh:mm:ss,ms`
include timedate.e

for line in split(movie_srt,'\n') do
    if match(sep,line) then
        line = split(line,sep)
        for i,t in line do
            timedate td = parse_date_string(t,{fmt}) 
            line[i] = format_timedate(adjust_timedate(td,9),fmt)
        end for
        line = join(line,sep)
    end if
    printf(1,"%s\n",line)
end for
Output:
1
00:01:40,550 --> 00:01:45,347
Four billion years ago,
00:01:45,555 --> 00:01:51,019
00:01:51,144 --> 00:01:52,979
00:01:52,979 --> 00:01:54,898
00:01:55,232 --> 00:01:56,608
00:01:56,733 --> 00:01:59,361
00:01:59,736 --> 00:02:01,488
00:02:07,035 --> 00:02:07,869
00:02:08,203 --> 00:02:09,079
00:02:11,540 --> 00:02:12,999
00:02:13,416 --> 00:02:16,419
00:02:16,503 --> 00:02:20,048
With our advanced technology, I shrank it down.

Python

Library: datetime
Works with: Python version 3.x
Translation of: FreeBASIC
#! /usr/bin/env python3

import datetime

def add_seconds(time_str, secs):
    time_format = "%H:%M:%S,%f"
    time_obj = datetime.datetime.strptime(time_str, time_format)
    delta = datetime.timedelta(seconds=secs)
    new_time_obj = time_obj + delta
    new_time_str = new_time_obj.strftime(time_format)[:-3]  # remove the last 3 digits of microseconds
    return new_time_str

def sync_subtitles(file_in, file_out, secs):
    with open(file_in, 'r') as fin, open(file_out, 'w') as fout:
        for line in fin:
            if '-->' in line:
                start_time, end_time = line.strip().split(' --> ')
                start_time = add_seconds(start_time, secs)
                end_time = add_seconds(end_time, secs)
                fout.write(f"{start_time} --> {end_time}\n")
            else:
                fout.write(line)

print("After fast-forwarding 9 seconds:\n")
sync_subtitles("movie.srt", "movie_corrected.srt", 9)
with open("movie_corrected.srt", 'r') as f:
    print(f.read())

print("\n\nAfter rolling-back 9 seconds:\n")
sync_subtitles("movie.srt", "movie_corrected2.srt", -9)
with open("movie_corrected2.srt", 'r') as f:
    print(f.read())
Output:
Same as FreeBASIC entry.

Raku

Brutal

# 20240621 Raku programming solution

sub MAIN() {
   my @lines = $*IN.lines;
   for 9, -9 -> $seconds {
      say "Original subtitle adjusted by {$seconds.fmt('%+d')} seconds.";
      for @lines -> $line {
         if $line ~~ /(\d ** 2 ':' \d ** 2 ':' \d ** 2 ',' \d ** 3) ' --> ' (\d ** 2 ':' \d ** 2 ':' \d ** 2 ',' \d ** 3)/ {
            my $start = adjust-time($0.Str, $seconds);
            my $end   = adjust-time($1.Str, $seconds);
            my $adjusted = $line;
            $adjusted ~~ s/\d ** 2 ':' \d ** 2 ':' \d ** 2 ',' \d ** 3 ' --> ' \d ** 2 ':' \d ** 2 ':' \d ** 2 ',' \d ** 3/$start ~ ' --> ' ~ $end/.Str;
            say $adjusted
         } else {
            say $line;
         }
      }
      say()
   }
}

sub adjust-time($time, $seconds) {
   my ($time_str, $milliseconds_str) = $time.split(',');
   my (\hh, \mm, \ss) = $time_str.split(':')>>.Int;
   my $milliseconds = $milliseconds_str.Int;
   my $datetime = DateTime.new(:year,     :month,      :day, 
                               :hour(hh), :minute(mm), :second(ss));
   given $datetime .= later(:seconds($seconds)) {
      return sprintf('%02d:%02d:%02d,%03d',.hour,.minute,.second,$milliseconds)
   }
}

You may Attempt This Online!

Less brutal

# 20240622 Raku programming solution

grammar SRT { # loc.gov/preservation/digital/formats/fdd/fdd000569.shtml
   token TOP { ^ <subtitle>+ % \n $ }
   token subtitle { <index> \n <timecode> \n <text> \n? }
   token index { \d+ }
   token timecode { <timestamp> ' --> ' <timestamp> }
   token timestamp { \d ** 2 ':' \d ** 2 ':' \d ** 2 ',' \d ** 3 }
   token text { <line>+ % \n }
   token line { <-[\n]>+ }
}

class SRT::Actions { has @.subtitles;

   method TOP($/) {
      @.subtitles = $<subtitle>».made;
      make @.subtitles
   }

   method subtitle($/) {
      make {
         index => $<index>.Str,
         start => $<timecode><timestamp>[0].made,
         end   => $<timecode><timestamp>[1].made,
         text  => $<text>.made,
      }
   }

   method timestamp($/) { make $/.Str }

   method text($/) { make $<line>.join("\n") }
}

class SubtitleAdjuster {

    method adjust-time($time, $seconds) {
       my ($time-str, $milliseconds-str) = $time.split(',');
       my (\hh, \mm, \ss) = $time-str.split(':')>>.Int;
       my \mls            = $milliseconds-str.Int;
       my $datetime = DateTime.new( :year, :month, :day,
                                    :hour(hh), :minute(mm), :second(ss));
       given $datetime .= later(:seconds($seconds)) {
          return sprintf('%02d:%02d:%02d,%03d', .hour, .minute, .second, mls)
       }
   }

   method adjust-subtitles(@subtitles, Int $seconds) {
      @subtitles.map({
         $_<start> = self.adjust-time($_<start>, $seconds);
         $_<end>   = self.adjust-time($_<end>, $seconds);
         $_;
      });
   }

   method format-srt(@subtitles) {
      @subtitles.map({
         $_<index> ~ "\n"
         ~ $_<start> ~ " --> " ~ $_<end> ~ "\n"
         ~ $_<text> ~ "\n"
      }).join("\n");
   }
}

my $srt-content = $*IN.slurp;
my $parsed = SRT.parse($srt-content, :actions(SRT::Actions.new));
my @subtitles = $parsed.made;

my $adjuster = SubtitleAdjuster.new;

for 9, -9 -> \N {
   my @adjusted-subtitles = $adjuster.adjust-subtitles(@subtitles.deepmap(*.clone), N);
   say "Original subtitle adjusted by {N.fmt('%+d')} seconds.";
   say $adjuster.format-srt(@adjusted-subtitles);
}

You may Attempt This Online!

Rust

Library: chrono
Library: lazy_static
Library: regex
use chrono::{NaiveTime, TimeDelta};
use lazy_static::lazy_static;
use regex::Regex;
use std::env;
use std::fs::File;
use std::io::{self, BufRead};
use std::io::{stdout, Write};
use std::path::Path;

lazy_static! {
    static ref RE: Regex =
        Regex::new(r"(\d{2}:\d{2}:\d{2},\d{3})\s+-->\s+(\d{2}:\d{2}:\d{2},\d{3})").unwrap();
    static ref FORMAT: String = String::from("%H:%M:%S,%3f");
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args: Vec<String> = env::args().collect();

    match args.len() {
        3 => {
            let path = &args[1];
            let secs: i64 = args[2].parse()?;
            let mut lock = stdout().lock();
            for line in sync_file(path, secs)? {
                writeln!(lock, "{line}").unwrap()
            }
        }
        _ => println!("usage: subrip-sync <filename> <seconds>"),
    }

    Ok(())
}

fn sync_file<P>(path: P, secs: i64) -> Result<impl Iterator<Item = String>, io::Error>
where
    P: AsRef<Path>,
{
    let file = File::open(path)?;
    let reader = io::BufReader::new(file);
    Ok(sync_lines(reader.lines().flatten(), secs))
}

fn sync_lines(lines: impl Iterator<Item = String>, secs: i64) -> impl Iterator<Item = String> {
    let delta = TimeDelta::new(secs, 0).unwrap();
    lines.map(move |line| {
        if let Some(groups) = RE.captures(&line) {
            format(&groups[1], &groups[2], &delta)
        } else {
            line
        }
    })
}

fn format(start: &str, stop: &str, delta: &TimeDelta) -> String {
    format!(
        "{} --> {}",
        (NaiveTime::parse_from_str(start, &FORMAT).unwrap() + *delta).format(&FORMAT),
        (NaiveTime::parse_from_str(stop, &FORMAT).unwrap() + *delta).format(&FORMAT)
    )
}
Output:
$ subrip-sync movie.srt 9 > movie_plus_9.srt
$ cat movie_plus_9.srt
1
00:01:40,550 --> 00:01:45,347
Four billion years ago,
the first marine life forms.

2
00:01:45,555 --> 00:01:51,019
First came the fish...then slowly
other life forms evolved.

3
00:01:51,144 --> 00:01:52,979
Therefore, our ancestors...

4
00:01:52,979 --> 00:01:54,898
...came from fish.

5
00:01:55,232 --> 00:01:56,608
Everyone, come and see this.

6
00:01:56,733 --> 00:01:59,361
Cretaceous Tyrannosaurus.

7
00:01:59,736 --> 00:02:01,488
Ferocious!

8
00:02:07,035 --> 00:02:07,869
Red,

9
00:02:08,203 --> 00:02:09,079
Pong!

10
00:02:11,540 --> 00:02:12,999
Isn't this a gecko?

11
00:02:13,416 --> 00:02:16,419
How else can I get a 15 ton T-Rex in here?

12
00:02:16,503 --> 00:02:20,048
With our advanced technology, I shrank it down.

Wren

Library: Wren-date
Library: Wren-ioutil
import "./date" for Date
import "./ioutil" for File, FileUtil

var syncSubtitles = Fn.new { |fileIn, fileOut, secs|
    var nl = FileUtil.lineBreak
    var fmt = "hh|:|MM|:|ss|,|ttt"
    var f = File.create(fileOut)
    for (line in FileUtil.readLines(fileIn)) {
        if (line.contains("-->")) {
            var start = line[0..11]
            var startDate = Date.parse(start, fmt).addSeconds(secs)
            start = startDate.format(fmt)
            var end = line[17..28]
            var endDate = Date.parse(end, fmt).addSeconds(secs)
            end = endDate.format(fmt)
            f.writeBytes(start + " --> " + end + nl)
         } else {
            f.writeBytes(line + nl)
         }
    }
    f.close()
}

System.print("After fast-forwarding 9 seconds:\n")
syncSubtitles.call("movie.srt", "movie_corrected.srt", 9)
System.print(File.read("movie_corrected.srt"))
System.print("After rolling-back 9 seconds:\n")
syncSubtitles.call("movie.srt", "movie_corrected2.srt", -9)
System.print(File.read("movie_corrected2.srt"))
Output:
After fast-forwarding 9 seconds:

1
00:01:40,550 --> 00:01:45,347
Four billion years ago,
the first marine life forms.

2
00:01:45,555 --> 00:01:51,019
First came the fish...then slowly
other life forms evolved.

3
00:01:51,144 --> 00:01:52,979
Therefore, our ancestors...

4
00:01:52,979 --> 00:01:54,898
...came from fish.

5
00:01:55,232 --> 00:01:56,608
Everyone, come and see this.

6
00:01:56,733 --> 00:01:59,361
Cretaceous Tyrannosaurus.

7
00:01:59,736 --> 00:02:01,488
Ferocious!

8
00:02:07,035 --> 00:02:07,869
Red,

9
00:02:08,203 --> 00:02:09,079
Pong!

10
00:02:11,540 --> 00:02:12,999
Isn't this a gecko?

11
00:02:13,416 --> 00:02:16,419
How else can I get a 15 ton T-Rex in here?

12
00:02:16,503 --> 00:02:20,048
With our advanced technology, I shrank it down.


After rolling-back 9 seconds:

1
00:01:22,550 --> 00:01:27,347
Four billion years ago,
the first marine life forms.

2
00:01:27,555 --> 00:01:33,019
First came the fish...then slowly
other life forms evolved.

3
00:01:33,144 --> 00:01:34,979
Therefore, our ancestors...

4
00:01:34,979 --> 00:01:36,898
...came from fish.

5
00:01:37,232 --> 00:01:38,608
Everyone, come and see this.

6
00:01:38,733 --> 00:01:41,361
Cretaceous Tyrannosaurus.

7
00:01:41,736 --> 00:01:43,488
Ferocious!

8
00:01:49,035 --> 00:01:49,869
Red,

9
00:01:50,203 --> 00:01:51,079
Pong!

10
00:01:53,540 --> 00:01:54,999
Isn't this a gecko?

11
00:01:55,416 --> 00:01:58,419
How else can I get a 15 ton T-Rex in here?

12
00:01:58,503 --> 00:02:02,048
With our advanced technology, I shrank it down.

XPL0

Runs on on Raspberry Pi like this: sync <movie.srt

proc Sync(Offset);                      \Show sync-d movie file
int  Offset;
int  Char, Time, Digit;
[loop   [Char:= ChIn(1);                \pass characters up to minutes time
        if Char = \EOF\$1A then quit;
        ChOut(0, Char);
        if Char = ^: then               \get time in seconds
            [Time:= IntIn(1) * 60;      \convert minutes to seconds
            Time:= Time + IntIn(1);     \add seconds
            Time:= Time + Offset;
            Digit:= Time/600;           \show offset Time
            Time:= rem(0);
            IntOut(0, Digit);
            Digit:= Time/60;
            Time:= rem(0);
            IntOut(0, Digit);
            ChOut(0, ^:);
            Digit:= Time/10;
            Time:= rem(0);
            IntOut(0, Digit);
            IntOut(0, Time);
            ChOUt(0, ^,);
            ];
        ];
];

[if not Rerun then      \Rerun and Restart used to reset redirected input file
    [Text(0, "After fast-forwarding 9 seconds:^j^j");
    Sync(9);
    Restart;
    ];
Text(0, "^j^jAfter rolling-back 9 seconds:^j^j");
Sync(-9);
]
Output:
Exactly the same as Wren example.