ref: 3d30208102fc4f2c19bad785d20a3201fa32879f
parent: d0d3099ce3193cfa50905cf5557f29c522ad4c1d
author: Sigrid Solveig Haflínudóttir <ftrvxmtrx@gmail.com>
date: Mon Mar 1 12:37:10 EST 2021
add SRT subtitles support
--- a/main.c
+++ b/main.c
@@ -14,9 +14,12 @@
uvlong trackpref;
+static Streamframe subfr[8];
+static int nsubfr;
static Image *curim;
+static Rectangle videor;
static u8int *curpx;
-static uvlong lastframe;
+static uvlong firstframe, lastframe;
static int audiofd = -1;
static Channel *audiosync;
static Channel *audiofinished;
@@ -60,6 +63,63 @@
}
static void
+drawsub(Stream *s)
+{
+ Streamframe *f;
+ int i, l, h;
+ uvlong ts;
+ Point p;
+
+ if(s == nil)
+ return;
+
+ /* read until we have something to show, but don't go too far */
+ ts = lastframe - firstframe;
+ for(; nsubfr < nelem(subfr) && (nsubfr == 0 || subfr[nsubfr-1].timestamp <= ts); nsubfr++){
+ f = &subfr[nsubfr];
+ if(Sread(s, f) != 0){
+ fprint(2, "%r\n");
+ break;
+ }
+ if(f->nlines == 0)
+ return;
+ }
+
+ /* remove stale text, calculate height of the lines currently displayed */
+ h = 0;
+ for(i = 0; i < nsubfr; i++){
+ f = &subfr[i];
+
+ if(f->timestamp + f->dt < ts){
+ for(l = 0; l < nelem(f->lines) && f->lines[l] != nil; l++)
+ free(f->lines[l]);
+ ts = lastframe - firstframe;
+ memmove(subfr+i, subfr+i+1, sizeof(Streamframe)*(nsubfr-i));
+ nsubfr--;
+ i--;
+ continue;
+ }
+
+ if(f->timestamp <= ts && f->timestamp+f->dt >= ts)
+ h += font->height*(f->nlines + 1); /* one extra for spacing */
+ }
+
+ /* draw */
+ p.y = videor.max.y - h;
+ for(i = 0; i < nsubfr; i++){
+ f = &subfr[i];
+ if(f->timestamp <= ts && f->timestamp+f->dt >= ts){
+ for(l = 0; l < nelem(f->lines) && f->lines[l] != nil; l++){
+ p.x = screen->r.min.x + Dx(screen->r)/2 - stringwidth(font, f->lines[l])/2;
+ stringbg(screen, p, display->white, ZP, font, f->lines[l], display->black, ZP);
+ p.y += font->height;
+ }
+ p.y += font->height; /* spacing */
+ }
+ }
+}
+
+static void
drawframe(Frame *f)
{
uvlong x, end, left;
@@ -67,8 +127,10 @@
int y;
x = nanosec();
- if(lastframe == 0)
+ if(lastframe == 0){
+ firstframe = x;
lastframe = x;
+ }
end = lastframe + f->dt;
if(x+dispdelay >= end){
@@ -127,9 +189,11 @@
else
r = screen->r;
draw(screen, r, curim, nil, ZP);
+ videor.min = r.min;
+ videor.max.x = r.min.x + Dx(clip);
+ videor.max.y = r.min.y + Dy(clip);
if(showinfo)
stringbg(screen, screen->r.min, display->white, ZP, font, info, display->black, ZP);
- flushimage(display, 1);
dispdelay += nanosec() - x;
drop:
@@ -144,7 +208,7 @@
static void
usage(void)
{
- fprint(2, "usage: %s [-a AUDIOFILE] [-t N -t M ...] file.mp4\n", argv0);
+ fprint(2, "usage: %s [-a AUDIOFILE] [-S subtitles] [-t N -t M ...] file.mp4\n", argv0);
threadexitsall("usage");
}
@@ -164,8 +228,8 @@
Keyboardctl *kctl;
Rune key;
Mouse m;
- char *s, *audio, *status;
- Stream *stream, *svideo, *saudio;
+ char *s, *audio, *sub, *status;
+ Stream *stream, *svideo, *saudio, *ssub;
Decoder *d;
int i, j, end, done, forced, res, nstreams;
Alt a[Cnum+1] =
@@ -183,6 +247,7 @@
debug = 0;
audio = nil;
+ sub = nil;
trackpref = 0;
ARGBEGIN{
case 'a':
@@ -192,6 +257,13 @@
}
audio = EARGF(usage());
break;
+ case 'S':
+ if(sub != nil){
+ fprint(2, "only one subtitle file can be specified\n");
+ usage();
+ }
+ sub = EARGF(usage());
+ break;
case 'B':
benchmark++;
break;
@@ -230,9 +302,8 @@
a[Cresize].c = mctl->resizec;
a[Ckeyboard].c = kctl->c;
- saudio = nil;
- if(audio != nil)
- saudio = Sopen(audio, &nstreams);
+ saudio = audio ? Sopen(audio, &nstreams) : nil;
+ ssub = sub ? subopen(sub, &nstreams, &i) : nil;
for(end = i = 0; !end && i < argc; i++){
draw(screen, screen->r, display->black, nil, ZP);
@@ -246,6 +317,8 @@
svideo = stream+j;
else if(stream[j].type == Saudio && saudio == nil && !benchmark)
saudio = stream+j;
+ else if(stream[j].type == Ssubtitles && ssub == nil)
+ ssub = stream+j;
else
Sclose(stream+j);
}
@@ -258,6 +331,7 @@
a[Cframe].c = d->frames;
a[Cplayerdone].c = d->finished;
lastframe = 0;
+ nsubfr = 0;
if(saudio != nil){
audiosync = chancreate(sizeof(void*), 1);
@@ -275,7 +349,7 @@
if(showinfo){
snprint(
info, sizeof(info),
- "decode=%-5lld yuv→rgb=%-3lld display=%-3lld total=%-3lld late=%-3lld %dx%d %s %s %s",
+ "decode=%-5lld yuv→rgb=%-3lld display=%-3lld total=%-3lld late=%-3lld %dx%d %s %s %s %s",
d->decodetime/1000000ULL,
dispdelay/1000000ULL,
yuv→rgb/1000000ULL,
@@ -284,10 +358,13 @@
frame->w, frame->h,
d->info,
d->s->info,
- saudio ? saudio->info : "no audio"
+ saudio ? saudio->info : "no audio",
+ ssub ? ssub->info : "no subtitles"
);
}
drawframe(frame);
+ drawsub(ssub);
+ flushimage(display, 1);
break;
case Cplayerdone:
@@ -327,6 +404,12 @@
if(!forced)
Sclose(saudio);
saudio = nil;
+
+ if(ssub != nil){
+ Sclose(ssub);
+ if(sub != nil)
+ free(ssub);
+ }
Dclose(d);
for(i = 0; i < nstreams; i++)
--- a/misc.c
+++ b/misc.c
@@ -13,6 +13,7 @@
[FmtH264] = "h264",
[FmtMp3] = "mp3",
[FmtOpus] = "opus",
+ [FmtSrt] = "srt",
[FmtVP8] = "vp8",
[FmtVP9] = "vp9",
};
--- a/mkfile
+++ b/mkfile
@@ -27,6 +27,7 @@
stream_audio.$O\
stream_ivf.$O\
stream_mc.$O\
+ stream_sub.$O\
yuv.$O\
default:V: all
--- a/stream.c
+++ b/stream.c
@@ -57,7 +57,9 @@
int
Sread(Stream *s, Streamframe *f)
{
- if(s == nil || s->type < 0)
+ if(s == nil || s->type < 0){
+ werrstr("invalid stream");
return -1;
+ }
return s->ops.read(s, f);
}
--- a/stream.h
+++ b/stream.h
@@ -8,6 +8,7 @@
enum {
Svideo,
Saudio,
+ Ssubtitles,
/* video */
FmtAV1 = 0,
@@ -20,6 +21,8 @@
FmtOpus,
FmtVorbis,
FmtFlac,
+ /* subtitles */
+ FmtSrt,
Numfmt,
};
@@ -27,8 +30,16 @@
struct Streamframe {
uvlong timestamp;
uvlong dt;
- u8int *buf;
- int sz;
+ union {
+ struct { /* video/audio */
+ u8int *buf;
+ int sz;
+ };
+ struct { /* subtitles */
+ char *lines[16];
+ int nlines;
+ };
+ };
};
struct Streamops {
@@ -81,3 +92,6 @@
int ivfopenb(void *bio, Stream *s, int *failed);
int audopenfd(int fd, Stream *s, int *failed);
+int subopenfd(int fd, Stream *s, int *failed);
+
+Stream *subopen(char *path, int *num, int *failed);
--- a/stream_mc.c
+++ b/stream_mc.c
@@ -5,7 +5,7 @@
#include "misc.h"
enum {
- Maxstreams = 2,
+ Maxstreams = 3,
Bufsz = 64*1024,
};
@@ -66,7 +66,7 @@
nil,
};
Stream *s, *streams;
- int nvideo, naudio, sp;
+ int nvideo, naudio, nsub, sp;
char *v[8];
if((pid = mcfs(argv, &p)) < 0)
@@ -75,17 +75,17 @@
return nil;
Binit(&b, p, OREAD);
- for(ns = naudio = nvideo = 0; ns < Maxstreams && (line = Brdstr(&b, '\n', 1)) != nil;){
+ for(ns = naudio = nvideo = nsub = 0; ns < Maxstreams && (line = Brdstr(&b, '\n', 1)) != nil;){
n = tokenize(line, v, nelem(v));
if(trackpref != 0 && (trackpref & (1<<atoi(v[0]))) == 0)
continue;
- if(n > 4 && str2fmt(v[2]) >= 0){
+ if(n >= 3 && str2fmt(v[2]) >= 0){
argv[1] = "-t";
argv[2] = v[0]; /* stream id */
argv[3] = filename;
s = streams+ns;
- if(nvideo < 1 && strcmp(v[1], "video") == 0){
+ if(n >= 5 && nvideo < 1 && strcmp(v[1], "video") == 0){
s->video.w = atoi(v[3]);
s->video.h = atoi(v[4]);
if(mcfs(argv, &sp) > 0){
@@ -99,12 +99,18 @@
ns++;
}
}
- }else if(naudio < 1 && strcmp(v[1], "audio") == 0 && (s->fmt = str2fmt(v[2])) >= 0){
+ }else if(n >= 5 && naudio < 1 && strcmp(v[1], "audio") == 0 && (s->fmt = str2fmt(v[2])) >= 0){
s->audio.nchan = atoi(v[3]);
s->audio.srate = atoi(v[4]);
snprint(s->info, sizeof(s->info), "%s (%d %d)", v[2], s->audio.nchan, s->audio.srate);
if(mcfs(argv, &sp) > 0 && audopenfd(sp, s, failed) == 0){
naudio++;
+ ns++;
+ }
+ }else if(n >= 3 && nsub < 1 && strcmp(v[1], "subtitle") == 0 && (s->fmt = str2fmt(v[2])) >= 0){
+ snprint(s->info, sizeof(s->info), "%s (%s)", v[2], (n > 3 && *v[3]) ? v[3] : "default");
+ if(mcfs(argv, &sp) > 0 && subopenfd(sp, s, failed) == 0){
+ nsub++;
ns++;
}
}