shithub: mcfs

Download patch

ref: ed803b2139a490df5d69f4d82320e8fecd2f7324
parent: f8e5a16a06c337bafb54921804a90fe4229ea3e9
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Wed Sep 9 10:21:44 EDT 2020

remux video streams as IVF

--- /dev/null
+++ b/.gitignore
@@ -1,0 +1,2 @@
+[a0125678vqki].out
+*.[o0125678vqki]
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-# mp4
+# mcfs
+
+Multimedia container file system.
 
 Someday this might end up as a 9p filesystem for various video
 container formats so other programs (vidya players) can identify
--- a/iso.c
+++ b/iso.c
@@ -4,6 +4,7 @@
 
 typedef struct Audio Audio;
 typedef struct Box Box;
+typedef struct Moof Moof;
 typedef struct RunSample RunSample;
 typedef struct SampleToChunk SampleToChunk;
 typedef struct Track Track;
@@ -139,6 +140,11 @@
 			u32int handlertype;
 			char *name;
 		}hdlr;
+
+		struct {
+			int id;
+			int flags;
+		}esds;
 	};
 };
 
@@ -147,6 +153,7 @@
 	u32int size;
 	u32int flags;
 	vlong timeoffset;
+	vlong offset;
 };
 
 struct SampleToChunk {
@@ -155,30 +162,12 @@
 	u32int sdt;
 };
 
-struct Track {
-	u32int handlertype;
-
-	u64int *chunkoffset;
-	u32int numchunks;
-
-	u32int samplesizeeach;
-	u32int *samplesize;
-	u32int numsamples;
-
-	SampleToChunk *stc;
-	u32int numstc;
-
-	Audio audio;
-	Video video;
-
-	int id;
-};
-
 enum {
 	HandlerVideo = 0x76696465u,
 	HandlerAudio = 0x736f756eu,
 
 	FmtMp4a = 0x6d703461u,
+	FmtAv01 = 0x61763031u,
 
 	BoxUuid = 0x75756964u,
 	BoxFtyp = 0x66747970u,
@@ -230,6 +219,36 @@
 
 #define eBread(sz, e) if(Bread(f, d, (sz)) != (sz)){ werrstr(e); goto err; }
 
+struct Moof {
+	Box;
+	Box mfhd;
+	Box tfhd;
+	Box trun;
+};
+
+struct Track {
+	u32int handlertype;
+	u32int timescale;
+
+	u64int *chunkoffset;
+	u32int numchunks;
+
+	u32int samplesizeeach;
+	u32int *samplesize;
+	u32int numsamples;
+
+	SampleToChunk *stc;
+	u32int numstc;
+
+	Moof *moof;
+	int nmoof;
+
+	Audio audio;
+	Video video;
+
+	int id;
+};
+
 static int dflag;
 static int dind;
 static u32int defsamplesize;
@@ -237,7 +256,10 @@
 static Track track;
 static Track *tracks;
 static int ntracks;
+static Biobuf stderr;
 
+static Moof moof;
+
 static int parsebox(Biobuf *f, Box *b, int *eof);
 
 #pragma varargck type "T" u32int
@@ -267,40 +289,40 @@
 	if(dflag == 0)
 		return;
 
-	fprint(2, "%.*s%T\n", dind, ind, b->type);
+	Bprint(&stderr, "%.*s%T\n", dind, ind, b->type);
 
 	/* full box */
 	if(isfullbox(b)){
-		fprint(2, "\t%.*sversion\t%d\n", dind, ind, b->version);
-		fprint(2, "\t%.*sflags\t0x%ux\n", dind, ind, b->flags);
+		Bprint(&stderr, "\t%.*sversion\t%d\n", dind, ind, b->version);
+		Bprint(&stderr, "\t%.*sflags\t0x%ux\n", dind, ind, b->flags);
 	}
 	if(dflag > 1){
-		fprint(2, "\t%.*soffset\t%zd\n", dind, ind, b->offset);
-		fprint(2, "\t%.*sdstart\t%zd\n", dind, ind, b->dstart);
-		fprint(2, "\t%.*sdsize\t%zd\n", dind, ind, b->dsz);
+		Bprint(&stderr, "\t%.*soffset\t%zd\n", dind, ind, b->offset);
+		Bprint(&stderr, "\t%.*sdstart\t%zd\n", dind, ind, b->dstart);
+		Bprint(&stderr, "\t%.*sdsize\t%zd\n", dind, ind, b->dsz);
 	}
 
 	if(b->type == BoxFtyp){
-		fprint(2, "\t%.*sbrand\t%T\n", dind, ind, b->ftyp.brand);
-		fprint(2, "\t%.*sversion\t%d\n", dind, ind, b->ftyp.version);
-		fprint(2, "\t%.*scompat", dind, ind);
+		Bprint(&stderr, "\t%.*sbrand\t%T\n", dind, ind, b->ftyp.brand);
+		Bprint(&stderr, "\t%.*sversion\t%d\n", dind, ind, b->ftyp.version);
+		Bprint(&stderr, "\t%.*scompat", dind, ind);
 		for(i = 0; i < b->ftyp.ncompat; i++)
-			fprint(2, "\t%.*s%T", dind, ind, b->ftyp.compat[i]);
-		fprint(2, "\n");
+			Bprint(&stderr, "\t%.*s%T", dind, ind, b->ftyp.compat[i]);
+		Bprint(&stderr, "\n");
 	}else if(b->type == BoxMdhd){
-		fprint(2, "\t%.*screation\t%zd\n", dind, ind, b->mdhd.creation);
-		fprint(2, "\t%.*smodification\t%zd\n", dind, ind, b->mdhd.modification);
-		fprint(2, "\t%.*stimescale\t%ud\n", dind, ind, b->mdhd.timescale);
-		fprint(2, "\t%.*sduration\t%zd\n", dind, ind, b->mdhd.duration);
+		Bprint(&stderr, "\t%.*screation\t%zd\n", dind, ind, b->mdhd.creation);
+		Bprint(&stderr, "\t%.*smodification\t%zd\n", dind, ind, b->mdhd.modification);
+		Bprint(&stderr, "\t%.*stimescale\t%ud\n", dind, ind, b->mdhd.timescale);
+		Bprint(&stderr, "\t%.*sduration\t%zd\n", dind, ind, b->mdhd.duration);
 	}else if(b->type == BoxMvhd){
-		fprint(2, "\t%.*screation\t%zd\n", dind, ind, b->mvhd.creation);
-		fprint(2, "\t%.*smodification\t%zd\n", dind, ind, b->mvhd.modification);
-		fprint(2, "\t%.*stimescale\t%ud\n", dind, ind, b->mvhd.timescale);
-		fprint(2, "\t%.*sduration\t%zd\n", dind, ind, b->mvhd.duration);
-		fprint(2, "\t%.*srate\t0x%ux\n", dind, ind, b->mvhd.rate);
-		fprint(2, "\t%.*svolume\t0x%ux\n", dind, ind, b->mvhd.volume);
-		fprint(2, "\t%.*snexttrack\t0x%ux\n", dind, ind, b->mvhd.nexttrack);
-		fprint(2, "\t%.*smatrix\t0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux\n", dind, ind,
+		Bprint(&stderr, "\t%.*screation\t%zd\n", dind, ind, b->mvhd.creation);
+		Bprint(&stderr, "\t%.*smodification\t%zd\n", dind, ind, b->mvhd.modification);
+		Bprint(&stderr, "\t%.*stimescale\t%ud\n", dind, ind, b->mvhd.timescale);
+		Bprint(&stderr, "\t%.*sduration\t%zd\n", dind, ind, b->mvhd.duration);
+		Bprint(&stderr, "\t%.*srate\t0x%ux\n", dind, ind, b->mvhd.rate);
+		Bprint(&stderr, "\t%.*svolume\t0x%ux\n", dind, ind, b->mvhd.volume);
+		Bprint(&stderr, "\t%.*snexttrack\t0x%ux\n", dind, ind, b->mvhd.nexttrack);
+		Bprint(&stderr, "\t%.*smatrix\t0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux 0x%ux\n", dind, ind,
 			b->mvhd.matrix[0],
 			b->mvhd.matrix[1],
 			b->mvhd.matrix[2],
@@ -312,99 +334,99 @@
 			b->mvhd.matrix[8]
 		);
 	}else if(b->type == BoxTrex){
-		fprint(2, "\t%.*strackid\t0x%ux\n", dind, ind, b->trex.trackid);
-		fprint(2, "\t%.*sdefsample\n", dind, ind);
-		fprint(2, "\t\t%.*sdescrindex\t0x%ux\n", dind, ind, b->trex.defsample.descrindex);
-		fprint(2, "\t\t%.*sduration\t%ud\n", dind, ind, b->trex.defsample.duration);
-		fprint(2, "\t\t%.*ssize\t0x%ux\n", dind, ind, b->trex.defsample.size);
-		fprint(2, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->trex.defsample.flags);
+		Bprint(&stderr, "\t%.*strackid\t0x%ux\n", dind, ind, b->trex.trackid);
+		Bprint(&stderr, "\t%.*sdefsample\n", dind, ind);
+		Bprint(&stderr, "\t\t%.*sdescrindex\t0x%ux\n", dind, ind, b->trex.defsample.descrindex);
+		Bprint(&stderr, "\t\t%.*sduration\t%ud\n", dind, ind, b->trex.defsample.duration);
+		Bprint(&stderr, "\t\t%.*ssize\t0x%ux\n", dind, ind, b->trex.defsample.size);
+		Bprint(&stderr, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->trex.defsample.flags);
 	}else if(b->type == BoxMfhd){
-		fprint(2, "\t%.*sseqnumber\t%ud\n", dind, ind, b->mfhd.seqnumber);
+		Bprint(&stderr, "\t%.*sseqnumber\t%ud\n", dind, ind, b->mfhd.seqnumber);
 	}else if(b->type == BoxTfhd){
-		fprint(2, "\t%.*strackid\t0x%ux\n", dind, ind, b->tfhd.trackid);
+		Bprint(&stderr, "\t%.*strackid\t%d\n", dind, ind, b->tfhd.trackid);
 		if(b->flags & 1)
-			fprint(2, "\t%.*sbaseoffset\t%zd\n", dind, ind, b->tfhd.baseoffset);
-		fprint(2, "\t%.*sdefsample\n", dind, ind);
+			Bprint(&stderr, "\t%.*sbaseoffset\t%zd\n", dind, ind, b->tfhd.baseoffset);
+		Bprint(&stderr, "\t%.*sdefsample\n", dind, ind);
 		if(b->flags & 2)
-			fprint(2, "\t\t%.*sdescrindex\t0x%ux\n", dind, ind, b->tfhd.defsample.descrindex);
+			Bprint(&stderr, "\t\t%.*sdescrindex\t0x%ux\n", dind, ind, b->tfhd.defsample.descrindex);
 		if(b->flags & 8)
-			fprint(2, "\t\t%.*sduration\t%ud\n", dind, ind, b->tfhd.defsample.duration);
+			Bprint(&stderr, "\t\t%.*sduration\t%ud\n", dind, ind, b->tfhd.defsample.duration);
 		if(b->flags & 16)
-			fprint(2, "\t\t%.*ssize\t0x%ux\n", dind, ind, b->tfhd.defsample.size);
+			Bprint(&stderr, "\t\t%.*ssize\t0x%ux\n", dind, ind, b->tfhd.defsample.size);
 		if(b->flags & 32)
-			fprint(2, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->tfhd.defsample.flags);
+			Bprint(&stderr, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->tfhd.defsample.flags);
 		if(b->flags & 0x10000)
-			fprint(2, "\t%.*sduration is empty\n", dind, ind);
+			Bprint(&stderr, "\t%.*sduration is empty\n", dind, ind);
 		if(b->flags & 0x20000)
-			fprint(2, "\t%.*sdefault base is moof\n", dind, ind);
+			Bprint(&stderr, "\t%.*sdefault base is moof\n", dind, ind);
 	}else if(b->type == BoxTfdt){
-		fprint(2, "\t%.*sdecodetime\t%zd\n", dind, ind, b->tfdt.decodetime);
+		Bprint(&stderr, "\t%.*sdecodetime\t%zd\n", dind, ind, b->tfdt.decodetime);
 	}else if(b->type == BoxTrun){
-		fprint(2, "\t%.*ssamplecount\t%ud\n", dind, ind, b->trun.samplecount);
+		Bprint(&stderr, "\t%.*ssamplecount\t%ud\n", dind, ind, b->trun.samplecount);
 		if(b->flags & 1)
-			fprint(2, "\t%.*sdataoffset\t%d\n", dind, ind, b->trun.dataoffset);
+			Bprint(&stderr, "\t%.*sdataoffset\t%d\n", dind, ind, b->trun.dataoffset);
 		if(b->flags & 2)
-			fprint(2, "\t%.*sfirstsampleflags\t0x%ux\n", dind, ind, b->trun.firstsampleflags);
+			Bprint(&stderr, "\t%.*sfirstsampleflags\t0x%ux\n", dind, ind, b->trun.firstsampleflags);
 		for(u = 0; dflag > 1 && u < b->trun.samplecount; u++){
-			fprint(2, "\t%.*ssamples[%zd]\n", dind, ind, u);
-			if(b->flags & 0x100)
-				fprint(2, "\t\t%.*sduration\t%ud\n", dind, ind, b->trun.samples[u].duration);
-			if(b->flags & 0x200)
-				fprint(2, "\t\t%.*ssize\t%ud\n", dind, ind, b->trun.samples[u].size);
-			if(b->flags & 0x400)
-				fprint(2, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->trun.samples[u].flags);
+			Bprint(&stderr, "\t%.*ssamples[%zd]\n", dind, ind, u);
+			Bprint(&stderr, "\t\t%.*sduration\t%ud\n", dind, ind, b->trun.samples[u].duration);
+			Bprint(&stderr, "\t\t%.*ssize\t%ud\n", dind, ind, b->trun.samples[u].size);
+			Bprint(&stderr, "\t\t%.*sflags\t0x%ux\n", dind, ind, b->trun.samples[u].flags);
 			if(b->flags & 0x800)
-				fprint(2, "\t\t%.*stimeoffset\t%zd\n", dind, ind, b->trun.samples[u].timeoffset);
+				Bprint(&stderr, "\t\t%.*stimeoffset\t%zd\n", dind, ind, b->trun.samples[u].timeoffset);
 		}
 	}else if(b->type == BoxStsd){
-		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stsd.entrycount);
+		Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stsd.entrycount);
 		if(b->stsd.video.format != 0){
-			fprint(2, "\t\t%.*svideo\t%T\n", dind, ind, b->stsd.video.format);
-			fprint(2, "\t\t\t%.*swidth\t%d\n", dind, ind, b->stsd.video.width);
-			fprint(2, "\t\t\t%.*sheight\t%d\n", dind, ind, b->stsd.video.height);
+			Bprint(&stderr, "\t\t%.*svideo\t%T\n", dind, ind, b->stsd.video.format);
+			Bprint(&stderr, "\t\t\t%.*swidth\t%d\n", dind, ind, b->stsd.video.width);
+			Bprint(&stderr, "\t\t\t%.*sheight\t%d\n", dind, ind, b->stsd.video.height);
 		}
 		if(b->stsd.audio.format != 0){
-			fprint(2, "\t\t%.*saudio\t%T\n", dind, ind, b->stsd.audio.format);
-			fprint(2, "\t\t\t%.*schannels\t%d\n", dind, ind, b->stsd.audio.channels);
-			fprint(2, "\t\t\t%.*ssample_rate\t%d\n", dind, ind, b->stsd.audio.samplerate);
+			Bprint(&stderr, "\t\t%.*saudio\t%T\n", dind, ind, b->stsd.audio.format);
+			Bprint(&stderr, "\t\t\t%.*schannels\t%d\n", dind, ind, b->stsd.audio.channels);
+			Bprint(&stderr, "\t\t\t%.*ssample_rate\t%d\n", dind, ind, b->stsd.audio.samplerate);
 		}
 	}else if(b->type == BoxStts){
-		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stts.entrycount);
+		Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stts.entrycount);
 	}else if(b->type == BoxStss){
-		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
+		Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
 	}else if(b->type == BoxStsc){
-		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
+		Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
 		for(u = 0; dflag > 1 && u < b->stsc.entrycount; u++){
-			fprint(2, "\t%.*sentry[%zd]\n", dind, ind, u);
-			fprint(2, "\t\t%.*sfirst_chunk\t%ud\n", dind, ind, b->stsc.entry[u].firstchunk);
-			fprint(2, "\t\t%.*ssamples_per_chunk\t%ud\n", dind, ind, b->stsc.entry[u].samplesperchunk);
-			fprint(2, "\t\t%.*ssample_description_table\t%ud\n", dind, ind, b->stsc.entry[u].sdt);
+			Bprint(&stderr, "\t%.*sentry[%zd]\n", dind, ind, u);
+			Bprint(&stderr, "\t\t%.*sfirst_chunk\t%ud\n", dind, ind, b->stsc.entry[u].firstchunk);
+			Bprint(&stderr, "\t\t%.*ssamples_per_chunk\t%ud\n", dind, ind, b->stsc.entry[u].samplesperchunk);
+			Bprint(&stderr, "\t\t%.*ssample_description_table\t%ud\n", dind, ind, b->stsc.entry[u].sdt);
 		}
 	}else if(b->type == BoxStsz){
-		fprint(2, "\t%.*ssample_size\t%ud\n", dind, ind, b->stsz.samplesizeeach);
-		fprint(2, "\t%.*ssample_count\t%ud\n", dind, ind, b->stsz.samplecount);
+		Bprint(&stderr, "\t%.*ssample_size\t%ud\n", dind, ind, b->stsz.samplesizeeach);
+		Bprint(&stderr, "\t%.*ssample_count\t%ud\n", dind, ind, b->stsz.samplecount);
 		if(dflag > 1 && b->stsz.samplesizeeach == 0){
 			for(u = 0; u < b->stsz.samplecount; u++)
-				fprint(2, "\t%.*ssamplesize[%zd]\t%ud\n", dind, ind, u, b->stsz.samplesize[u]);
+				Bprint(&stderr, "\t%.*ssamplesize[%zd]\t%ud\n", dind, ind, u, b->stsz.samplesize[u]);
 		}
 	}else if(b->type == BoxTkhd){
-		fprint(2, "\t%.*screation_time\t%zd\n", dind, ind, b->tkhd.creattime);
-		fprint(2, "\t%.*smodification_timetime\t%zd\n", dind, ind, b->tkhd.modtime);
-		fprint(2, "\t%.*strack_id\t%ud\n", dind, ind, b->tkhd.trackid);
-		fprint(2, "\t%.*sduration\t%zd\n", dind, ind, b->tkhd.duration);
-		fprint(2, "\t%.*swidth\t%ud\n", dind, ind, b->tkhd.width);
-		fprint(2, "\t%.*sheight\t%ud\n", dind, ind, b->tkhd.height);
+		Bprint(&stderr, "\t%.*screation_time\t%zd\n", dind, ind, b->tkhd.creattime);
+		Bprint(&stderr, "\t%.*smodification_time\t%zd\n", dind, ind, b->tkhd.modtime);
+		Bprint(&stderr, "\t%.*strack_id\t%ud\n", dind, ind, b->tkhd.trackid);
+		Bprint(&stderr, "\t%.*sduration\t%zd\n", dind, ind, b->tkhd.duration);
+		Bprint(&stderr, "\t%.*swidth\t%ud\n", dind, ind, b->tkhd.width);
+		Bprint(&stderr, "\t%.*sheight\t%ud\n", dind, ind, b->tkhd.height);
 	}else if(b->type == BoxHdlr){
-		fprint(2, "\t%.*shandler_type\t%T\n", dind, ind, b->hdlr.handlertype);
-		fprint(2, "\t%.*sname\t%s\n", dind, ind, b->hdlr.name);
+		Bprint(&stderr, "\t%.*shandler_type\t%T\n", dind, ind, b->hdlr.handlertype);
+		Bprint(&stderr, "\t%.*sname\t%s\n", dind, ind, b->hdlr.name);
 	}else if(b->type == BoxStco || b->type == BoxCo64){
-		fprint(2, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
+		Bprint(&stderr, "\t%.*sentry_count\t%ud\n", dind, ind, b->stss.entrycount);
 		for(u = 0; dflag > 1 && u < b->stco_co64.entrycount; u++)
-			fprint(2, "\t%.*schunkoffset[%zd]\t%zd\n", dind, ind, u, b->stco_co64.chunkoffset[u]);
+			Bprint(&stderr, "\t%.*schunkoffset[%zd]\t%zd\n", dind, ind, u, b->stco_co64.chunkoffset[u]);
+	}else if(b->type == BoxEsds){
+		Bprint(&stderr, "\t%.*sid\t%d\n", dind, ind, b->esds.id);
+		Bprint(&stderr, "\t%.*sflags\t0x%x\n", dind, ind, b->esds.flags);
 	}else if(dflag < 2){
-		fprint(2, "\t%.*soffset\t%zd\n", dind, ind, b->offset);
-		fprint(2, "\t%.*sdstart\t%zd\n", dind, ind, b->dstart);
-		fprint(2, "\t%.*sdsize\t%zd\n", dind, ind, b->dsz);
+		Bprint(&stderr, "\t%.*soffset\t%zd\n", dind, ind, b->offset);
+		Bprint(&stderr, "\t%.*sdstart\t%zd\n", dind, ind, b->dstart);
+		Bprint(&stderr, "\t%.*sdsize\t%zd\n", dind, ind, b->dsz);
 	}
 }
 
@@ -415,6 +437,38 @@
 	memmove(&tracks[ntracks++], &track, sizeof(track));
 }
 
+static Track *
+gettrack(int id)
+{
+	Track *t;
+	int i;
+
+	for(t = tracks, i = 0; i < ntracks && t->id != id; i++, t++){
+		fprint(2, "%d != %d\n", t->id, id);
+	}
+	if(i >= ntracks){
+		werrstr("no track %d", id);
+		t = nil;
+	}
+
+	return t;
+}
+
+static void
+addtrun(Box *trun)
+{
+	Track *t;
+	Moof *m;
+
+	if((t = gettrack(moof.tfhd.tfhd.trackid)) == nil)
+		sysfatal("trun without a track");
+
+	t->moof = realloc(t->moof, (t->nmoof+1)*sizeof(Moof));
+	m = &t->moof[t->nmoof++];
+	memmove(&moof.trun, trun, sizeof(moof.trun));
+	memmove(m, &moof, sizeof(moof));
+}
+
 static int srate2mpeg4fi[] = {
 	96000,
 	88200,
@@ -432,51 +486,14 @@
 };
 
 static int
-dumptrack(Biobuf *f, int id)
+dumpstc(Biobuf *f, Biobuf *out, Track *t, u8int *frame)
 {
 	SampleToChunk *stc;
 	u32int si, ch, nextch;
 	u32int samplelast, sample, rawsz, samplesz;
 	u64int o, wo;
-	int i;
 	u8int *raw;
-	Biobuf out;
-	Track *t;
-	u8int frame[7];
 
-	for(t = tracks, i = 0; i < ntracks && t->id != id; i++, t++);
-	if(i >= ntracks){
-		werrstr("no track %d", id);
-		return -1;
-	}
-
-	fprint(
-		2,
-		"track %d: handler=%T numstc=%ud chunks=%ud samples=%ud\n", t->id, t->handlertype, t->numstc, t->numchunks, t->numsamples
-	);
-	if(t->audio.format != 0){
-		fprint(2, "audio: format=%T channels=%d samplerate=%d\n", t->audio.format, t->audio.channels, t->audio.samplerate);
-		if(t->audio.format == FmtMp4a){
-			for(i = 0; i < nelem(srate2mpeg4fi) && srate2mpeg4fi[i] != t->audio.samplerate; i++);
-			if(i >= nelem(srate2mpeg4fi)){
-				werrstr("audio: mpeg4: invalid sample rate %d", t->audio.samplerate);
-				return -1;
-			}
-			frame[0] = 0xff; /* syncword */
-			frame[1] = 0xf1; /* syncword, mpeg4, no crc */
-			frame[2] = 1<<6 | i<<2 | t->audio.channels>>2; /* AAC(LC)??? FIXME, frequency index, channels */
-		}else{
-			werrstr("audio: unknown format %T\n", t->audio.format);
-			return -1;
-		}
-	}
-	if(t->video.format != 0){
-		fprint(2, "video: format=%T resolution=%dx%d\n", t->video.format, t->video.width, t->video.height);
-		//werrstr("video: unknown format %T", t->video.format);
-		//return -1;
-	}
-
-	Binit(&out, 1, OWRITE);
 	raw = nil;
 	rawsz = 0;
 	sample = samplelast = 0;
@@ -515,7 +532,7 @@
 					frame[5] = (samplesz&7)<<5 | 0x1f; /* frame length, fullness */
 					frame[6] = 0xfc; /* fullness, number of frames */
 					samplesz -= 7;
-					if(Bwrite(&out, frame, 7) != 7){ /* EOF */
+					if(Bwrite(out, frame, 7) != 7){ /* EOF */
 						werrstr("eof");
 						break;
 					}
@@ -522,7 +539,7 @@
 					wo += 7;
 					o += 7;
 				}
-				if(Bwrite(&out, raw, samplesz) != samplesz){ /* EOF? */
+				if(Bwrite(out, raw, samplesz) != samplesz){ /* EOF? */
 					werrstr("eof");
 					break;
 				}
@@ -532,10 +549,167 @@
 			samplelast = sample;
 		}
 	}
-	Bterm(&out);
 	free(raw);
 
 	return 0;
+}
+
+static int
+dumptrun(Biobuf *f, Biobuf *out, Track *t, u8int *frame)
+{
+	int i, j;
+	u64int ts;
+	u8int *raw;
+	int maxsz;
+	RunSample *s;
+	Moof *m;
+
+	USED(frame);
+
+	if(t->video.format == 0)
+		sysfatal("trun dump only works with IVF atm"); /* FIXME */
+
+	ts = 0;
+	maxsz = 0;
+	raw = nil;
+	for(i = 0, m = t->moof; i < t->nmoof; i++, m++){
+		for(j = 0; j < m->trun.trun.samplecount; j++){
+			s = &m->trun.trun.samples[j];
+			if(12 + s->size > maxsz){
+				maxsz = 12 + s->size;
+				raw = realloc(raw, maxsz);
+			}
+			raw[0] = s->size;
+			raw[1] = s->size >> 8;
+			raw[2] = s->size >> 16;
+			raw[3] = s->size >> 24;
+			raw[4] = ts;
+			raw[5] = ts >> 8;
+			raw[6] = ts >> 16;
+			raw[7] = ts >> 24;
+			raw[8] = ts >> 32;
+			raw[9] = ts >> 40;
+			raw[10] = ts >> 48;
+			raw[11] = ts >> 56;
+			if(Bseek(f, s->offset, 0) != s->offset){
+				werrstr("couldn't seek to offset %zd", s->offset);
+				return -1;
+			}
+			if(Bread(f, raw+12, s->size) != s->size){
+				werrstr("couldn't read sample (%d bytes)", s->size);
+				return -1;
+			}
+			if(Bwrite(out, raw, 12 + s->size) != 12 + s->size) /* eof? */
+				break;
+			ts += s->duration; /* sample's "time offset" is ignored here */
+		}
+	}
+
+	return 0;
+}
+
+static int
+dumptrack(Biobuf *f, int id)
+{
+	int i, j, res;
+	Biobuf out;
+	u64int dur;
+	Track *t;
+	Moof *m;
+	u8int frame[0x20];
+
+	if((t = gettrack(id)) == nil)
+		return -1;
+
+	if(dflag)
+		Bprint(&stderr, "track %d: handler=%T ", t->id, t->handlertype);
+	if(t->numstc > 0){
+		if(dflag)
+			Bprint(&stderr, "numstc=%ud chunks=%ud samples=%ud\n", t->numstc, t->numchunks, t->numsamples);
+	}else if(t->nmoof > 0){
+		if(dflag)
+			Bprint(&stderr, "numtrun: %d\n", t->nmoof);
+	}else{
+		if(dflag)
+			Bprint(&stderr, "no samples\n");
+		werrstr("track %d has no samples", t->id);
+		return -1;
+	}
+
+	Binit(&out, 1, OWRITE);
+
+	if(t->audio.format != 0){
+		Bprint(&stderr, "audio: format=%T channels=%d samplerate=%d\n", t->audio.format, t->audio.channels, t->audio.samplerate);
+		if(t->audio.format == FmtMp4a){
+			for(i = 0; i < nelem(srate2mpeg4fi) && srate2mpeg4fi[i] != t->audio.samplerate; i++);
+			if(i >= nelem(srate2mpeg4fi)){
+				werrstr("audio: mpeg4: invalid sample rate %d", t->audio.samplerate);
+				return -1;
+			}
+			frame[0] = 0xff; /* syncword */
+			frame[1] = 0xf1; /* syncword, mpeg4, no crc */
+			frame[2] = 1<<6 | i<<2 | t->audio.channels>>2; /* AAC(LC)??? FIXME, frequency index, channels */
+		}else{
+			werrstr("audio: unknown format %T\n", t->audio.format);
+			return -1;
+		}
+	}else if(t->video.format != 0){
+		Bprint(&stderr, "video: format=%T resolution=%dx%d\n", t->video.format, t->video.width, t->video.height);
+		/* video is remuxed into IVF */
+		memset(frame, 0, 0x20);
+		memmove(frame, "DKIF", 4);
+		frame[4] = 0;
+		frame[5] = 0;
+		frame[6] = 0x20;
+		frame[7] = 0;
+		if(t->video.format == FmtAv01){
+			memmove(frame+8, "AV01", 4);
+			frame[12] = t->video.width;
+			frame[13] = t->video.width >> 8;
+			frame[14] = t->video.height;
+			frame[15] = t->video.height >> 8;
+			/* timebase denum */
+			frame[16] = t->timescale;
+			frame[17] = t->timescale >> 8;
+			frame[18] = t->timescale >> 16;
+			frame[19] = t->timescale >> 24;
+			/* timebase num */
+			frame[20] = 1;
+			frame[21] = 0;
+			frame[22] = 0;
+			frame[23] = 0;
+			/* FIXME is it a "number of frames" or "total duration?" */
+			dur = 0;
+			for(i = 0, m = t->moof; i < t->nmoof; i++, m++){
+				for(j = 0; j < m->trun.trun.samplecount; j++){
+					dur += m->trun.trun.samples[j].duration;
+				}
+			}
+			frame[24] = dur;
+			frame[25] = dur >> 8;
+			frame[26] = dur >> 16;
+			frame[27] = dur >> 24;
+			frame[28] = dur >> 32; /* FIXME is it 64 bits? */
+			frame[29] = dur >> 40;
+			frame[30] = dur >> 48;
+			frame[31] = dur >> 56;
+
+			Bwrite(&out, frame, 0x20);
+		}else{
+			werrstr("video: unknown format %T", t->video.format);
+			return -1;
+		}
+	}
+
+	res = -1;
+	if(t->numstc > 0)
+		res = dumpstc(f, &out, t, frame);
+	else if(t->nmoof > 0)
+		res = dumptrun(f, &out, t, frame);
+
+	Bterm(&out);
+
+	return res;
 };
 
 int
@@ -565,7 +739,7 @@
 		memmove(&track.audio, &b->stsd.audio, sizeof(Audio));
 		/* FIXME do we care about the rest? */
 	}else{
-		fprint(2, "SampleEntry: unknown handler type %T\n", track.handlertype);
+		Bprint(&stderr, "SampleEntry: unknown handler type %T\n", track.handlertype);
 	}
 
 	return n;
@@ -579,8 +753,8 @@
 	u8int d[128], *p;
 	Box inner;
 	int i, n, eof;
-	s32int s32i;
-	u64int u;
+	u64int u, off;
+	RunSample *s;
 
 	if(b->type == BoxFtyp){
 		eBread(8, "brand and version");
@@ -601,6 +775,10 @@
 		b->type == BoxMdia || b->type == BoxMinf || b->type == BoxStbl ||
 		b->type == BoxMoof || b->type == BoxTraf || b->type == BoxEdts ||
 		b->type == BoxDinf){
+		if(b->type == BoxMoof){
+			memset(&moof, 0, sizeof(moof));
+			memmove(&moof, b, sizeof(Box));
+		}
 		printbox(b);
 		dind++;
 		for(;;){
@@ -616,6 +794,8 @@
 			memset(&track, 0, sizeof(track));
 		}
 		dind--;
+		if(b->type == BoxMoof)
+			memset(&moof, 0, sizeof(moof));
 	}else if(b->type == BoxMdhd){
 		n = b->version == 0 ? 16 : 28;
 		eBread(n, "short read");
@@ -632,6 +812,7 @@
 		}
 
 		b->mdhd.timescale = bu32(p); p += 4;
+		track.timescale = b->mdhd.timescale; /* it's used for IVF timebase */
 
 		b->mdhd.duration = bu32(p); p += 4;
 		if(b->version == 1){
@@ -683,6 +864,7 @@
 	}else if(b->type == BoxMfhd){
 		eBread(4, "short read");
 		b->mfhd.seqnumber = bu32(d);
+		memmove(&moof.mfhd, b, sizeof(*b));
 		printbox(b);
 	}else if(b->type == BoxTfhd){
 		eBread(4, "track_id");
@@ -708,6 +890,7 @@
 			eBread(4, "default_sample_flags");
 			b->tfhd.defsample.flags = bu32(d);
 		}
+		memmove(&moof.tfhd, b, sizeof(*b));
 		printbox(b);
 	}else if(b->type == BoxTfdt){
 		if(b->version == 1){
@@ -731,32 +914,53 @@
 		}
 		/* FIXME free those */
 		b->trun.samples = calloc(b->trun.samplecount, sizeof(RunSample));
+		off = 0;
 		for(u = 0; u < b->trun.samplecount; u++){
+			s = &b->trun.samples[u];
 			if(b->flags & 0x100){
 				eBread(4, "sample_duration");
-				b->trun.samples[u].duration = bu32(d);
-			}
+				s->duration = bu32(d);
+			}else if(moof.tfhd.flags & 8)
+				s->duration = moof.tfhd.tfhd.defsample.duration;
+
 			if(b->flags & 0x200){
 				eBread(4, "sample_size");
-				b->trun.samples[u].size = bu32(d);
-			}
+				s->size = bu32(d);
+			}else if(moof.tfhd.flags & 16)
+				s->size = moof.tfhd.tfhd.defsample.size;
+
 			if(b->flags & 0x400){
 				eBread(4, "sample_flags");
-				b->trun.samples[u].flags = bu32(d);
-			}
+				s->flags = bu32(d);
+			}else if((b->flags & 4) && u == 0){
+				// FIXME default flags???
+				s->flags = b->trun.firstsampleflags;
+			}else if(moof.tfhd.flags & 32)
+				s->flags = moof.tfhd.tfhd.defsample.flags;
+
 			if(b->flags & 0x800){
 				eBread(4, "sample_composition_time_offset");
-				s32i = bu32(d);
 				if(b->version == 0)
-					b->trun.samples[u].timeoffset = bu32(d);
+					s->timeoffset = bu32(d);
 				else
-					b->trun.samples[u].timeoffset = s32i;
+					s->timeoffset = (s32int)bu32(d);
 			}
+
+			s->offset = b->trun.dataoffset + off;
+			if(moof.tfhd.flags & 1)
+				Bprint(&stderr, "tfhd has base-data-offset-present set, this isn't supported atm\n");
+			else if(moof.tfhd.flags & 0x20000)
+				s->offset += moof.offset + moof.tfhd.tfhd.baseoffset;
+			else
+				Bprint(&stderr, "couldn't calculate sample offset\n");
+			off += s->size;
 		}
+		addtrun(b);
 		printbox(b);
 	}else if(b->type == BoxEsds){
 		eBread(3, "es id");
-		fprint(2, "# es id: %ud\n# es flags: 0x%02x\n", bu16(d+1), d[3]);
+		b->esds.id = bu16(d+1);
+		b->esds.flags = d[3];
 		printbox(b);
 	}else if(b->type == BoxStsd){
 		eBread(4, "entry_count");
@@ -950,7 +1154,7 @@
 static void
 usage(void)
 {
-	fprint(2, "usage: iso [-d] [-t TRACK] FILE\n");
+	Bprint(&stderr, "usage: iso [-d] [-t TRACK] FILE\n");
 	exits("usage");
 }
 
@@ -975,6 +1179,8 @@
 
 	fmtinstall('T', typefmt);
 
+	Binit(&stderr, 2, OWRITE);
+
 	status = nil;
 	for(; *argv && status == nil; argv++){
 		if((f = Bopen(*argv, OREAD)) == nil)
@@ -992,12 +1198,14 @@
 		}
 
 		if(trackdump >= 0 && dumptrack(f, trackdump) != 0){
-			fprint(2, "%s: %r\n", *argv);
+			Bprint(&stderr, "%s: %r\n", *argv);
 			status = "dump";
 		}
 
 		Bterm(f);
 	}
+
+	Bterm(&stderr);
 
 	exits(status);
 }
--- a/mkfile
+++ b/mkfile
@@ -1,8 +1,7 @@
 </$objtype/mkfile
 MAN=/sys/man/1
 
-TARG=\
-	iso\
+TARG=mcfs
 
 BIN=/$objtype/bin
 
@@ -13,6 +12,4 @@
 
 default:V:	all
 
-</sys/src/cmd/mkmany
-
-install:
+</sys/src/cmd/mkone