shithub: treason

Download patch

ref: 3d30208102fc4f2c19bad785d20a3201fa32879f
parent: d0d3099ce3193cfa50905cf5557f29c522ad4c1d
author: Sigrid Solveig Haflínudóttir <[email protected]>
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++;
 				}
 			}