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
- Wikipedia: SubRip
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
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
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
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
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
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
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++
#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
# 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
#! /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
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
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.