#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#define BUFSZ 256
typedef struct Line {
uint32_t on, off;
char *text;
} Line;
typedef struct Subtitles {
int bulk;
int count;
Line *lines;
} Subtitles;
uint32_t
ts2ms(const char *ts)
{
uint32_t ms = 0;
ms += atoi(ts) * 60 * 60 * 1000;
ms += atoi(ts + 3) * 60 * 1000;
ms += atoi(ts + 6) * 1000;
ms += atoi(ts + 9);
return ms;
}
uint32_t
hms2ms(const char *hms)
{
const char *str = hms;
uint32_t ms = 0;
while (*str) {
unsigned long n = strtoul(str, (char **) &str, 10);
switch (*str) {
case 'h': case 'H':
n *= 60;
case 'm': case 'M':
n *= 60;
case 's': case 'S':
n *= 1000;
default:
str++;
case '\0':
break;
}
ms += n;
}
return ms;
}
void
ms2ts(char *buffer, size_t bufsiz, uint32_t ms)
{
unsigned h, m, s;
h = ms / (60 * 60 * 1000);
ms %= 60 * 60 * 1000;
m = ms / (60 * 1000);
ms %= 60 * 1000;
s = ms / (1000);
ms %= 1000;
snprintf(buffer, bufsiz, "%02u:%02u:%02u,%03u", h, m, s, ms);
}
Subtitles *
load_subs(FILE *fp)
{
Subtitles *subs;
char buffer[BUFSZ];
char text[4*BUFSZ];
char *ret = buffer;
subs = malloc(sizeof(*subs));
subs->bulk = 256;
subs->count = 0;
subs->lines = malloc(subs->bulk * sizeof(*subs->lines));
while (1) {
Line line;
/* Discard blank lines; get index or EOF. */
strcpy(buffer, "");
while (ret && strlen(buffer) <= 2)
ret = fgets(buffer, BUFSZ, fp);
if (!ret)
break;
assert(atoi(buffer) == subs->count + 1);
/* Get timestamps. */
fgets(buffer, BUFSZ, fp);
line.on = ts2ms(buffer);
line.off = ts2ms(buffer + 17);
/* Get text. */
fgets(text, BUFSZ, fp);
while (1) {
ret = fgets(buffer, BUFSZ, fp);
if (ret && strlen(buffer) > 2)
strcat(text, buffer);
else
break;
}
line.text = malloc(strlen(text) + 1);
strcpy(line.text, text);
/* Add line to subtitles. */
if (subs->count == subs->bulk) {
subs->bulk += subs->bulk / 2;
subs->lines = realloc(subs->lines, subs->bulk * sizeof(*subs->lines));
}
subs->lines[subs->count++] = line;
}
return subs;
}
void
print_line(FILE *fp, Subtitles *subs, int index)
{
Line *line;
char bufon[13], bufoff[13];
fprintf(fp, "%d\r\n", index + 1);
line = subs->lines + index;
ms2ts(bufon, 13, line->on);
ms2ts(bufoff, 13, line->off);
fprintf(fp, "%s --> %s\r\n", bufon, bufoff);
fprintf(fp, "%s", line->text);
}
void
save_subs(FILE *fp, Subtitles *subs)
{
int i;
for (i = 0; i < subs->count; i++) {
print_line(fp, subs, i);
fprintf(fp, "\r\n");
}
}
void
free_subs(Subtitles **subs)
{
int i;
for (i = 0; i < (*subs)->count; i++)
free((*subs)->lines[i].text);
free((*subs)->lines);
free(*subs);
*subs = NULL;
}
void
transform(Subtitles *subs, double factor, int sign, uint32_t offset)
{
int i;
for (i = 0; i < subs->count; i++) {
subs->lines[i].on = subs->lines[i].on * factor + sign * offset;
subs->lines[i].off = subs->lines[i].off * factor + sign * offset;
}
}
void
sync(Subtitles *subs, int i1, uint32_t t1, int i2, uint32_t t2)
{
double factor;
int32_t shift;
int sign;
uint32_t offset;
uint32_t t1_old = subs->lines[i1-1].on;
uint32_t t2_old = subs->lines[i2-1].on;
factor = ((double) (t2 - t1)) / (t2_old - t1_old);
shift = t1 - t1_old * factor;
fprintf(stderr, "scaled by %g, shifted by %+gs\n", factor, shift / 1e3);
sign = shift < 0 ? -1 : +1;
offset = abs(shift);
transform(subs, factor, sign, offset);
}
int
closest(Subtitles *subs, uint32_t ms)
{
int imin, imax;
imin = 0;
imax = subs->count - 1;
do {
int i = (imin + imax) / 2;
uint32_t sms = subs->lines[i].on;
if (sms < ms)
imin = i + 1;
else if (sms > ms)
imax = i - 1;
else
return i;
} while (imin < imax);
return imin;
}
int
contains(Line *line, char *words[], int nwords)
{
char *str = line->text;
while (nwords--)
if (!(str = strstr(str, *words++)))
return 0;
return 1;
}
int
search(Subtitles *subs, uint32_t ms, char *words[], int nwords)
{
#define SRT_CHECK(I) if (contains(&subs->lines[I], words, nwords)) return I
int i, s, t;
i = closest(subs, ms);
SRT_CHECK(i);
s = 1;
while (i-s > 0 && i+s < subs->count) {
SRT_CHECK(i-s);
SRT_CHECK(i+s);
s++;
}
for (t = i-s; t >= 0; t--)
SRT_CHECK(t);
for (t = i+s; t < subs->count; t++)
SRT_CHECK(t);
return -1;
#undef SRT_CHECK
}
void
usage(FILE *fp)
{
fprintf(fp,
"usage:\n"
" srtsync (-h|--help|help) -- print this help message\n"
" srtsync search TIME [WORD [WORD [...]]] -- search around TIME\n"
" srtsync shift (-TIME|+TIME) -- shift all subtitles by TIME\n"
" srtsync scale FACTOR -- multiply all timestamps by FACTOR\n"
" srtsync sync INDEX TIME INDEX TIME -- linearly sync subtitles\n"
"\n"
);
}
int
main(int argc, char *argv[])
{
Subtitles *subs;
if (argc == 1) {
usage(stderr);
return 1;
}
if (!(strcmp(argv[1], "-h") && strcmp(argv[1], "--help") && strcmp(argv[1], "help"))) {
usage(stdout);
return 0;
}
subs = load_subs(stdin);
if (!strcmp(argv[1], "search") && argc >= 3) {
int i;
i = search(subs, hms2ms(argv[2]), argv + 3, argc - 3);
if (i >= 0) {
print_line(stdout, subs, i);
return 0;
} else {
fprintf(stderr, "not found\n");
return 1;
}
} else if (!strcmp(argv[1], "shift") && argc == 3) {
int sign;
uint32_t offset;
char *hms = argv[2];
switch (*hms) {
case '-':
hms++;
sign = -1;
break;
case '+':
hms++;
default:
sign = +1;
}
offset = hms2ms(hms);
transform(subs, 1, sign, offset);
} else if (!strcmp(argv[1], "scale") && argc == 3) {
double factor = atof(argv[2]);
transform(subs, factor, 0, 0);
} else if (!strcmp(argv[1], "sync") && argc == 6) {
int i1 = atoi(argv[2]);
uint32_t t1 = hms2ms(argv[3]);
int i2 = atoi(argv[4]);
uint32_t t2 = hms2ms(argv[5]);
sync(subs, i1, t1, i2, t2);
} else {
usage(stderr);
return 1;
}
save_subs(stdout, subs);
free_subs(&subs);
return 0;
}