This is a quick and dirty (even the acronym) perl script I use to time-shift/stretch idx/sub subtitle files.
#!/usr/bin/perl
###########################################################################################
#
# Idx/Sub Subtitle File Time Adjust (ISFiTA) v0.01
#
# Author: Tsan-Kuang Lee
# Date: Jan 2, 2006
#
# What does it do?
#
# A video file may have different subtitle files, which not only differ in format
# but also differ in their timestamps. Usually this is because the creators of
# the subtitle files are syncing their subtitles to different video files (of the
# same content, but some with, say, trailors, and others without).
#
# For example, there are two video files: v1 and v2. Let's say v1 has 2 minutes more
# trailors from the beginning before the main content. Subtitle creators make s1 for
# v1 and s2 for v2.
#
# However, we only have v1 and s2 at hand. We want s2 to sync with v1.
#
# If s2 is in plain text subtitle format, e.g. SRT, TXT, SSA, etc. Many excellent
# utilities have been written, such as: SubCreator, http://www.radioactivepages.com/
# ISFiTA deals with idx/sub format.
#
#
# How to use it?
#
# ISFiTA simply reads the idx file (it's a plain text file) and recalculate the
# timestamps, according to the parameters we provide. We'll need to provide the
# several parameters. Here's what you would usually input at the command prompt:
#
# % perl ISFiTA-v0.01.pl 3 film0.idx 00:03:55:735 01:32:53:067 film.idx 00:03:34:535 01:32:32:067
#
# Here's what they mean:
#
# "%" is the command prompt;
#
# "perl ISFiTA-v0.01.pl" launches this script;
#
# "3" is the index code of the language you want to deal with. If you open the idx file
# with a text editor, you would see something like "id: zh, index: 3". zh is the language
# code for Chinese (Zhong-wen), and "3" is what we want here.
#
# "film0.idx" is the original idx file we want to read from.
#
# "00:03:55:735" is the timestamp (the last three digits are in milliseconds) of one
# specific subtitle line in "filem0.idx". Let's say it maps to the line "Let the show begin!".
# Many tools let you see which line "00:03:55:735" maps to, e.g. VobSub. (Of course, you need
# to have film0.sub for VobSub to display the image.)
#
# "01:32:53:067" maps to another line in "film0.idx", say "See you next time!".
#
# "film.idx" is the target file we want to write to.
#
# "00:03:34:535" is the target timestamp for "Let the show begin!". You can get this target
# timestamp from watching the video file, or from a correctly synced subtitle file.
#
# "01:32:32:067" is the target timestamp for "See you next time!".
#
# "Let the show begin!" doesn't have to be the first line in the whole subtitle; likewise,
# "See you next time!" doesn't have to be the last. However, the more apart they are from
# each other, the more accuracy of caculation we get, since the minor time-offs will be
# divided into ignorably small differences.
#
# Don't forget to provide film.sub to complete the idx/sub pair. Just use the original sub and
# correctly rename it.
#
# About
#
# This is a quick and dirty perl script (even its acronym sucks) I use to time-shift/stretch
# idx/sub subtitle files. Do whatever you want with it, at your own risk. If you do improve
# it or rewrite it, I encourage you to share it with the public. Authors of free utilities have
# my highest respects.
#
# Todos for you
#
# Here are some suggestions if you want to contribute something to the Open Source world, or the
# freeware world:
#
# SubCreator has much more sophisticated transformations. For example, you can decide the time
# range in which you want to adjust the time code.
#
# Obviously Perl is not user-friendly for most people. You may want to re-write it in another
# language and compile it into an excuteable.
#
# GUI makes it more approachable.
#
# Bug the authors of VobSub, SubCreator, etc. to include idx/sub time adjust/stretch functions
# into their utilities.
#
#
###########################################################################################
# Command line arguments
unless(@ARGV == 7) {
die "Usage: $0 lang_index in_file in_start in_end out_file out_start out_end\n(timestamp format: hh:mm:ss:mis)\n"
}
($lang_index, $in_file, $in_start_timestamp, $in_end_timestamp, $out_file, $out_start_timestamp, $out_end_timestamp) = @ARGV;
# Open files
unless(open INFILE, "<", $in_file) {
die "Couldn't open input file $in_file: $!; aborting";
}
unless(open OUTFILE, ">", $out_file) {
die "Couldn't open output file $out_file: $!; aborting";
}
# calculate necessaary transformation(shift, slope)
# simple linear transformation formula:
#
# new_bx = (ax - a1) * (b2 - b1) / (a2 - a1) + b1, where
# a1 = in_start;
a2 = in_end
# b1 = out_start;
b2 = out_en
# ax = old_time_point
$shift_milliseconds = & convert_to_milliseconds($out_start_timestamp);
$slope = ( & convert_to_milliseconds($out_end_timestamp) - $shift_milliseconds) / ( & convert_to_milliseconds($in_end_timestamp) - & convert_to_milliseconds($in_start_timestamp));
$in_start_milliseconds = & convert_to_milliseconds($in_start_timestamp);
# start processing
$section = "header";
while () {
if ($section eq "header" || $section eq "other_lang") {
# check language id
# id: zh, index: 2
if ($_ = ~m / ^ id: * . + , index: * (. + ) $ / ) {
if ($1 eq $lang_index) {
$section = "processing";
print "Processing $_";
} else {
$section = "other_lang";
}
}
}
elsif($section eq "processing") {
# stop when another language id tag appears
if ($_ = ~m / ^ id: * . + , index: * (. + ) $ / ) {
$section = "other_lang";
}
elsif($_ = ~m / ^ (timestamp: )(\d\ d: \d\ d: \d\ d: \d\ d\ d)(, . + ) $ / s)
# only process lines with timestamps, e.g.
# timestamp: 01: 22: 20: 634, filepos: 00011 a000 {
$_ = $1. & calculate_new_timestamp($2).$3;
}
}
print OUTFILE $_;
}
sub calculate_new_timestamp {
my($old_timestamp) = @_;
$new_milliseconds = ( & convert_to_milliseconds($old_timestamp) - $in_start_milliseconds) * $slope + $shift_milliseconds;
return & convert_to_timestamp($new_milliseconds);
}
sub convert_to_milliseconds {
my($string) = @_;
$string = ~m / (\d + ): (\d + ): (\d + ): (\d + ) / ;
$milliseconds = $4 + $3 * 1000 + $2 * 1000 * 60 + $1 * 1000 * 60 * 60;
return $milliseconds;
}
sub convert_to_timestamp {
my($milliseconds) = @_;
# Although perl supports integer division, the scope of "use integer"
# is global and that would cause slope calculation become integer.
# Therefore, we use redundant division as below.floor() is possible but
# we don 't use it here for cross-distribution compatibility
#(some perl distributions don 't have that package installed)
my $res_h = $milliseconds % (60 * 60 * 1000); my $hour = ($milliseconds - $res_h) / (1000 * 60 * 60); my $res_m = $res_h % (1000 * 60); my $min = ($res_h - $res_m) / (1000 * 60); my $res_s = $res_m % 1000; my $sec = ($res_m - $res_s) / 1000; my $ms = $res_s;
return sprintf("%02d:%02d:%02d:%03d", $hour, $min, $sec, $ms);
}
Leave a Comment
All comments are moderated before publication to prevent spam and scams.