1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-09-27 22:58:23 +00:00

Use Checkstyle for org.schabi.newpipe.streams as well

This commit is contained in:
wb9688 2020-04-02 15:42:28 +02:00
parent 55480c8290
commit b6c6dc7282
10 changed files with 2131 additions and 2098 deletions

View File

@ -99,7 +99,6 @@ task runCheckstyle(type: Checkstyle) {
exclude '**/R.java' exclude '**/R.java'
exclude '**/BuildConfig.java' exclude '**/BuildConfig.java'
exclude 'main/java/us/shandian/giga/**' exclude 'main/java/us/shandian/giga/**'
exclude 'main/java/org/schabi/newpipe/streams/**'
// empty classpath // empty classpath
classpath = files() classpath = files()

View File

@ -10,13 +10,12 @@ import java.io.InputStream;
* @author kapodamy * @author kapodamy
*/ */
public class DataReader { public class DataReader {
public static final int SHORT_SIZE = 2;
public static final int LONG_SIZE = 8;
public static final int INTEGER_SIZE = 4;
public static final int FLOAT_SIZE = 4;
public final static int SHORT_SIZE = 2; private static final int BUFFER_SIZE = 128 * 1024; // 128 KiB
public final static int LONG_SIZE = 8;
public final static int INTEGER_SIZE = 4;
public final static int FLOAT_SIZE = 4;
private final static int BUFFER_SIZE = 128 * 1024;// 128 KiB
private long position = 0; private long position = 0;
private final SharpStream stream; private final SharpStream stream;
@ -24,7 +23,7 @@ public class DataReader {
private InputStream view; private InputStream view;
private int viewSize; private int viewSize;
public DataReader(SharpStream stream) { public DataReader(final SharpStream stream) {
this.stream = stream; this.stream = stream;
this.readOffset = this.readBuffer.length; this.readOffset = this.readBuffer.length;
} }
@ -74,6 +73,7 @@ public class DataReader {
return value & 0xffffffffL; return value & 0xffffffffL;
} }
public short readShort() throws IOException { public short readShort() throws IOException {
primitiveRead(SHORT_SIZE); primitiveRead(SHORT_SIZE);
return (short) (primitive[0] << 8 | primitive[1]); return (short) (primitive[0] << 8 | primitive[1]);
@ -86,11 +86,11 @@ public class DataReader {
return high << 32 | low; return high << 32 | low;
} }
public int read(byte[] buffer) throws IOException { public int read(final byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length); return read(buffer, 0, buffer.length);
} }
public int read(byte[] buffer, int offset, int count) throws IOException { public int read(final byte[] buffer, int offset, int count) throws IOException {
if (readCount < 0) { if (readCount < 0) {
return -1; return -1;
} }
@ -135,7 +135,7 @@ public class DataReader {
stream.rewind(); stream.rewind();
if ((position - viewSize) > 0) { if ((position - viewSize) > 0) {
viewSize = 0;// drop view viewSize = 0; // drop view
} else { } else {
viewSize += position; viewSize += position;
} }
@ -157,7 +157,7 @@ public class DataReader {
* @param size the size of the view * @param size the size of the view
* @return the view * @return the view
*/ */
public InputStream getView(int size) { public InputStream getView(final int size) {
if (view == null) { if (view == null) {
view = new InputStream() { view = new InputStream() {
@Override @Override
@ -173,12 +173,13 @@ public class DataReader {
} }
@Override @Override
public int read(byte[] buffer) throws IOException { public int read(final byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length); return read(buffer, 0, buffer.length);
} }
@Override @Override
public int read(byte[] buffer, int offset, int count) throws IOException { public int read(final byte[] buffer, final int offset, final int count)
throws IOException {
if (viewSize < 1) { if (viewSize < 1) {
return -1; return -1;
} }
@ -190,7 +191,7 @@ public class DataReader {
} }
@Override @Override
public long skip(long amount) throws IOException { public long skip(final long amount) throws IOException {
if (viewSize < 1) { if (viewSize < 1) {
return 0; return 0;
} }
@ -224,16 +225,18 @@ public class DataReader {
private final short[] primitive = new short[LONG_SIZE]; private final short[] primitive = new short[LONG_SIZE];
private void primitiveRead(int amount) throws IOException { private void primitiveRead(final int amount) throws IOException {
byte[] buffer = new byte[amount]; byte[] buffer = new byte[amount];
int read = read(buffer, 0, amount); int read = read(buffer, 0, amount);
if (read != amount) { if (read != amount) {
throw new EOFException("Truncated stream, missing " + String.valueOf(amount - read) + " bytes"); throw new EOFException("Truncated stream, missing "
+ String.valueOf(amount - read) + " bytes");
} }
for (int i = 0; i < amount; i++) { for (int i = 0; i < amount; i++) {
primitive[i] = (short) (buffer[i] & 0xFF);// the "byte" data type in java is signed and is very annoying // the "byte" data type in java is signed and is very annoying
primitive[i] = (short) (buffer[i] & 0xFF);
} }
} }
@ -256,5 +259,4 @@ public class DataReader {
return readCount < 1; return readCount < 1;
} }
} }

View File

@ -14,7 +14,6 @@ import java.util.NoSuchElementException;
* @author kapodamy * @author kapodamy
*/ */
public class Mp4DashReader { public class Mp4DashReader {
private static final int ATOM_MOOF = 0x6D6F6F66; private static final int ATOM_MOOF = 0x6D6F6F66;
private static final int ATOM_MFHD = 0x6D666864; private static final int ATOM_MFHD = 0x6D666864;
private static final int ATOM_TRAF = 0x74726166; private static final int ATOM_TRAF = 0x74726166;
@ -50,7 +49,6 @@ public class Mp4DashReader {
private static final int HANDLER_SOUN = 0x736F756E; private static final int HANDLER_SOUN = 0x736F756E;
private static final int HANDLER_SUBT = 0x73756274; private static final int HANDLER_SUBT = 0x73756274;
private final DataReader stream; private final DataReader stream;
private Mp4Track[] tracks = null; private Mp4Track[] tracks = null;
@ -68,7 +66,7 @@ public class Mp4DashReader {
Audio, Video, Subtitles, Other Audio, Video, Subtitles, Other
} }
public Mp4DashReader(SharpStream source) { public Mp4DashReader(final SharpStream source) {
this.stream = new DataReader(source); this.stream = new DataReader(source);
} }
@ -78,14 +76,15 @@ public class Mp4DashReader {
} }
box = readBox(ATOM_FTYP); box = readBox(ATOM_FTYP);
brands = parse_ftyp(box); brands = parseFtyp(box);
switch (brands[0]) { switch (brands[0]) {
case BRAND_DASH: case BRAND_DASH:
case BRAND_ISO5:// ¿why not? case BRAND_ISO5:// ¿why not?
break; break;
default: default:
throw new NoSuchElementException( throw new NoSuchElementException(
"Not a MPEG-4 DASH container, major brand is not 'dash' or 'iso5' is " + boxName(brands[0]) "Not a MPEG-4 DASH container, major brand is not 'dash' or 'iso5' is "
+ boxName(brands[0])
); );
} }
@ -98,7 +97,7 @@ public class Mp4DashReader {
switch (box.type) { switch (box.type) {
case ATOM_MOOV: case ATOM_MOOV:
moov = parse_moov(box); moov = parseMoov(box);
break; break;
case ATOM_SIDX: case ATOM_SIDX:
break; break;
@ -117,10 +116,10 @@ public class Mp4DashReader {
tracks[i] = new Mp4Track(); tracks[i] = new Mp4Track();
tracks[i].trak = moov.trak[i]; tracks[i].trak = moov.trak[i];
if (moov.mvex_trex != null) { if (moov.mvexTrex != null) {
for (Trex mvex_trex : moov.mvex_trex) { for (Trex mvexTrex : moov.mvexTrex) {
if (tracks[i].trak.tkhd.trackId == mvex_trex.trackId) { if (tracks[i].trak.tkhd.trackId == mvexTrex.trackId) {
tracks[i].trex = mvex_trex; tracks[i].trex = mvexTrex;
} }
} }
} }
@ -144,7 +143,7 @@ public class Mp4DashReader {
backupBox = box; backupBox = box;
} }
Mp4Track selectTrack(int index) { Mp4Track selectTrack(final int index) {
selectedTrack = index; selectedTrack = index;
return tracks[index]; return tracks[index];
} }
@ -179,7 +178,7 @@ public class Mp4DashReader {
Box traf; Box traf;
while ((traf = untilBox(tmp, ATOM_TRAF)) != null) { while ((traf = untilBox(tmp, ATOM_TRAF)) != null) {
Box tfhd = readBox(ATOM_TFHD); Box tfhd = readBox(ATOM_TFHD);
if (parse_tfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) { if (parseTfhd(tracks[selectedTrack].trak.tkhd.trackId) != null) {
count++; count++;
break; break;
} }
@ -196,7 +195,9 @@ public class Mp4DashReader {
} }
public int[] getBrands() { public int[] getBrands() {
if (brands == null) throw new IllegalStateException("Not parsed"); if (brands == null) {
throw new IllegalStateException("Not parsed");
}
return brands; return brands;
} }
@ -219,7 +220,7 @@ public class Mp4DashReader {
return tracks; return tracks;
} }
public Mp4DashChunk getNextChunk(boolean infoOnly) throws IOException { public Mp4DashChunk getNextChunk(final boolean infoOnly) throws IOException {
Mp4Track track = tracks[selectedTrack]; Mp4Track track = tracks[selectedTrack];
while (stream.available()) { while (stream.available()) {
@ -240,27 +241,31 @@ public class Mp4DashReader {
throw new IOException("moof found without mdat"); throw new IOException("moof found without mdat");
} }
moof = parse_moof(box, track.trak.tkhd.trackId); moof = parseMoof(box, track.trak.tkhd.trackId);
if (moof.traf != null) { if (moof.traf != null) {
if (hasFlag(moof.traf.trun.bFlags, 0x0001)) { if (hasFlag(moof.traf.trun.bFlags, 0x0001)) {
moof.traf.trun.dataOffset -= box.size + 8; moof.traf.trun.dataOffset -= box.size + 8;
if (moof.traf.trun.dataOffset < 0) { if (moof.traf.trun.dataOffset < 0) {
throw new IOException("trun box has wrong data offset, points outside of concurrent mdat box"); throw new IOException("trun box has wrong data offset, "
+ "points outside of concurrent mdat box");
} }
} }
if (moof.traf.trun.chunkSize < 1) { if (moof.traf.trun.chunkSize < 1) {
if (hasFlag(moof.traf.tfhd.bFlags, 0x10)) { if (hasFlag(moof.traf.tfhd.bFlags, 0x10)) {
moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize * moof.traf.trun.entryCount; moof.traf.trun.chunkSize = moof.traf.tfhd.defaultSampleSize
* moof.traf.trun.entryCount;
} else { } else {
moof.traf.trun.chunkSize = (int) (box.size - 8); moof.traf.trun.chunkSize = (int) (box.size - 8);
} }
} }
if (!hasFlag(moof.traf.trun.bFlags, 0x900) && moof.traf.trun.chunkDuration == 0) { if (!hasFlag(moof.traf.trun.bFlags, 0x900)
&& moof.traf.trun.chunkDuration == 0) {
if (hasFlag(moof.traf.tfhd.bFlags, 0x20)) { if (hasFlag(moof.traf.tfhd.bFlags, 0x20)) {
moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration * moof.traf.trun.entryCount; moof.traf.trun.chunkDuration = moof.traf.tfhd.defaultSampleDuration
* moof.traf.trun.entryCount;
} }
} }
} }
@ -272,7 +277,7 @@ public class Mp4DashReader {
if (moof.traf == null) { if (moof.traf == null) {
moof = null; moof = null;
continue;// find another chunk continue; // find another chunk
} }
Mp4DashChunk chunk = new Mp4DashChunk(); Mp4DashChunk chunk = new Mp4DashChunk();
@ -292,17 +297,15 @@ public class Mp4DashReader {
return null; return null;
} }
public static boolean hasFlag(final int flags, final int mask) {
public static boolean hasFlag(int flags, int mask) {
return (flags & mask) == mask; return (flags & mask) == mask;
} }
private String boxName(Box ref) { private String boxName(final Box ref) {
return boxName(ref.type); return boxName(ref.type);
} }
private String boxName(int type) { private String boxName(final int type) {
try { try {
return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8"); return new String(ByteBuffer.allocate(4).putInt(type).array(), "UTF-8");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
@ -323,15 +326,16 @@ public class Mp4DashReader {
return b; return b;
} }
private Box readBox(int expected) throws IOException { private Box readBox(final int expected) throws IOException {
Box b = readBox(); Box b = readBox();
if (b.type != expected) { if (b.type != expected) {
throw new NoSuchElementException("expected " + boxName(expected) + " found " + boxName(b)); throw new NoSuchElementException("expected " + boxName(expected)
+ " found " + boxName(b));
} }
return b; return b;
} }
private byte[] readFullBox(Box ref) throws IOException { private byte[] readFullBox(final Box ref) throws IOException {
// full box reading is limited to 2 GiB, and should be enough // full box reading is limited to 2 GiB, and should be enough
int size = (int) ref.size; int size = (int) ref.size;
@ -342,15 +346,14 @@ public class Mp4DashReader {
int read = size - 8; int read = size - 8;
if (stream.read(buffer.array(), 8, read) != read) { if (stream.read(buffer.array(), 8, read) != read) {
throw new EOFException( throw new EOFException(String.format("EOF reached in box: type=%s offset=%s size=%s",
String.format("EOF reached in box: type=%s offset=%s size=%s", boxName(ref.type), ref.offset, ref.size) boxName(ref.type), ref.offset, ref.size));
);
} }
return buffer.array(); return buffer.array();
} }
private void ensure(Box ref) throws IOException { private void ensure(final Box ref) throws IOException {
long skip = ref.offset + ref.size - stream.position(); long skip = ref.offset + ref.size - stream.position();
if (skip == 0) { if (skip == 0) {
@ -365,7 +368,7 @@ public class Mp4DashReader {
stream.skipBytes((int) skip); stream.skipBytes((int) skip);
} }
private Box untilBox(Box ref, int... expected) throws IOException { private Box untilBox(final Box ref, final int... expected) throws IOException {
Box b; Box b;
while (stream.position() < (ref.offset + ref.size)) { while (stream.position() < (ref.offset + ref.size)) {
b = readBox(); b = readBox();
@ -380,7 +383,7 @@ public class Mp4DashReader {
return null; return null;
} }
private Box untilAnyBox(Box ref) throws IOException { private Box untilAnyBox(final Box ref) throws IOException {
if (stream.position() >= (ref.offset + ref.size)) { if (stream.position() >= (ref.offset + ref.size)) {
return null; return null;
} }
@ -388,17 +391,15 @@ public class Mp4DashReader {
return readBox(); return readBox();
} }
private Moof parseMoof(final Box ref, final int trackId) throws IOException {
private Moof parse_moof(Box ref, int trackId) throws IOException {
Moof obj = new Moof(); Moof obj = new Moof();
Box b = readBox(ATOM_MFHD); Box b = readBox(ATOM_MFHD);
obj.mfhd_SequenceNumber = parse_mfhd(); obj.mfhdSequenceNumber = parseMfhd();
ensure(b); ensure(b);
while ((b = untilBox(ref, ATOM_TRAF)) != null) { while ((b = untilBox(ref, ATOM_TRAF)) != null) {
obj.traf = parse_traf(b, trackId); obj.traf = parseTraf(b, trackId);
ensure(b); ensure(b);
if (obj.traf != null) { if (obj.traf != null) {
@ -409,7 +410,7 @@ public class Mp4DashReader {
return obj; return obj;
} }
private int parse_mfhd() throws IOException { private int parseMfhd() throws IOException {
// version // version
// flags // flags
stream.skipBytes(4); stream.skipBytes(4);
@ -417,11 +418,11 @@ public class Mp4DashReader {
return stream.readInt(); return stream.readInt();
} }
private Traf parse_traf(Box ref, int trackId) throws IOException { private Traf parseTraf(final Box ref, final int trackId) throws IOException {
Traf traf = new Traf(); Traf traf = new Traf();
Box b = readBox(ATOM_TFHD); Box b = readBox(ATOM_TFHD);
traf.tfhd = parse_tfhd(trackId); traf.tfhd = parseTfhd(trackId);
ensure(b); ensure(b);
if (traf.tfhd == null) { if (traf.tfhd == null) {
@ -431,18 +432,18 @@ public class Mp4DashReader {
b = untilBox(ref, ATOM_TRUN, ATOM_TFDT); b = untilBox(ref, ATOM_TRUN, ATOM_TFDT);
if (b.type == ATOM_TFDT) { if (b.type == ATOM_TFDT) {
traf.tfdt = parse_tfdt(); traf.tfdt = parseTfdt();
ensure(b); ensure(b);
b = readBox(ATOM_TRUN); b = readBox(ATOM_TRUN);
} }
traf.trun = parse_trun(); traf.trun = parseTrun();
ensure(b); ensure(b);
return traf; return traf;
} }
private Tfhd parse_tfhd(int trackId) throws IOException { private Tfhd parseTfhd(final int trackId) throws IOException {
Tfhd obj = new Tfhd(); Tfhd obj = new Tfhd();
obj.bFlags = stream.readInt(); obj.bFlags = stream.readInt();
@ -471,31 +472,31 @@ public class Mp4DashReader {
return obj; return obj;
} }
private long parse_tfdt() throws IOException { private long parseTfdt() throws IOException {
int version = stream.read(); int version = stream.read();
stream.skipBytes(3);// flags stream.skipBytes(3); // flags
return version == 0 ? stream.readUnsignedInt() : stream.readLong(); return version == 0 ? stream.readUnsignedInt() : stream.readLong();
} }
private Trun parse_trun() throws IOException { private Trun parseTrun() throws IOException {
Trun obj = new Trun(); Trun obj = new Trun();
obj.bFlags = stream.readInt(); obj.bFlags = stream.readInt();
obj.entryCount = stream.readInt();// unsigned int obj.entryCount = stream.readInt(); // unsigned int
obj.entries_rowSize = 0; obj.entriesRowSize = 0;
if (hasFlag(obj.bFlags, 0x0100)) { if (hasFlag(obj.bFlags, 0x0100)) {
obj.entries_rowSize += 4; obj.entriesRowSize += 4;
} }
if (hasFlag(obj.bFlags, 0x0200)) { if (hasFlag(obj.bFlags, 0x0200)) {
obj.entries_rowSize += 4; obj.entriesRowSize += 4;
} }
if (hasFlag(obj.bFlags, 0x0400)) { if (hasFlag(obj.bFlags, 0x0400)) {
obj.entries_rowSize += 4; obj.entriesRowSize += 4;
} }
if (hasFlag(obj.bFlags, 0x0800)) { if (hasFlag(obj.bFlags, 0x0800)) {
obj.entries_rowSize += 4; obj.entriesRowSize += 4;
} }
obj.bEntries = new byte[obj.entries_rowSize * obj.entryCount]; obj.bEntries = new byte[obj.entriesRowSize * obj.entryCount];
if (hasFlag(obj.bFlags, 0x0001)) { if (hasFlag(obj.bFlags, 0x0001)) {
obj.dataOffset = stream.readInt(); obj.dataOffset = stream.readInt();
@ -524,23 +525,24 @@ public class Mp4DashReader {
return obj; return obj;
} }
private int[] parse_ftyp(Box ref) throws IOException { private int[] parseFtyp(final Box ref) throws IOException {
int i = 0; int i = 0;
int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)]; int[] list = new int[(int) ((ref.offset + ref.size - stream.position() - 4) / 4)];
list[i++] = stream.readInt();// major brand list[i++] = stream.readInt(); // major brand
stream.skipBytes(4);// minor version stream.skipBytes(4); // minor version
for (; i < list.length; i++) for (; i < list.length; i++) {
list[i] = stream.readInt();// compatible brands list[i] = stream.readInt(); // compatible brands
}
return list; return list;
} }
private Mvhd parse_mvhd() throws IOException { private Mvhd parseMvhd() throws IOException {
int version = stream.read(); int version = stream.read();
stream.skipBytes(3);// flags stream.skipBytes(3); // flags
// creation entries_time // creation entries_time
// modification entries_time // modification entries_time
@ -564,7 +566,7 @@ public class Mp4DashReader {
return obj; return obj;
} }
private Tkhd parse_tkhd() throws IOException { private Tkhd parseTkhd() throws IOException {
int version = stream.read(); int version = stream.read();
Tkhd obj = new Tkhd(); Tkhd obj = new Tkhd();
@ -576,17 +578,17 @@ public class Mp4DashReader {
obj.trackId = stream.readInt(); obj.trackId = stream.readInt();
stream.skipBytes(4);// reserved stream.skipBytes(4); // reserved
obj.duration = version == 0 ? stream.readUnsignedInt() : stream.readLong(); obj.duration = version == 0 ? stream.readUnsignedInt() : stream.readLong();
stream.skipBytes(2 * 4);// reserved stream.skipBytes(2 * 4); // reserved
obj.bLayer = stream.readShort(); obj.bLayer = stream.readShort();
obj.bAlternateGroup = stream.readShort(); obj.bAlternateGroup = stream.readShort();
obj.bVolume = stream.readShort(); obj.bVolume = stream.readShort();
stream.skipBytes(2);// reserved stream.skipBytes(2); // reserved
obj.matrix = new byte[9 * 4]; obj.matrix = new byte[9 * 4];
stream.read(obj.matrix); stream.read(obj.matrix);
@ -597,20 +599,20 @@ public class Mp4DashReader {
return obj; return obj;
} }
private Trak parse_trak(Box ref) throws IOException { private Trak parseTrak(final Box ref) throws IOException {
Trak trak = new Trak(); Trak trak = new Trak();
Box b = readBox(ATOM_TKHD); Box b = readBox(ATOM_TKHD);
trak.tkhd = parse_tkhd(); trak.tkhd = parseTkhd();
ensure(b); ensure(b);
while ((b = untilBox(ref, ATOM_MDIA, ATOM_EDTS)) != null) { while ((b = untilBox(ref, ATOM_MDIA, ATOM_EDTS)) != null) {
switch (b.type) { switch (b.type) {
case ATOM_MDIA: case ATOM_MDIA:
trak.mdia = parse_mdia(b); trak.mdia = parseMdia(b);
break; break;
case ATOM_EDTS: case ATOM_EDTS:
trak.edst_elst = parse_edts(b); trak.edstElst = parseEdts(b);
break; break;
} }
@ -620,7 +622,7 @@ public class Mp4DashReader {
return trak; return trak;
} }
private Mdia parse_mdia(Box ref) throws IOException { private Mdia parseMdia(final Box ref) throws IOException {
Mdia obj = new Mdia(); Mdia obj = new Mdia();
Box b; Box b;
@ -633,13 +635,13 @@ public class Mp4DashReader {
ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd); ByteBuffer buffer = ByteBuffer.wrap(obj.mdhd);
byte version = buffer.get(8); byte version = buffer.get(8);
buffer.position(12 + ((version == 0 ? 4 : 8) * 2)); buffer.position(12 + ((version == 0 ? 4 : 8) * 2));
obj.mdhd_timeScale = buffer.getInt(); obj.mdhdTimeScale = buffer.getInt();
break; break;
case ATOM_HDLR: case ATOM_HDLR:
obj.hdlr = parse_hdlr(b); obj.hdlr = parseHdlr(b);
break; break;
case ATOM_MINF: case ATOM_MINF:
obj.minf = parse_minf(b); obj.minf = parseMinf(b);
break; break;
} }
ensure(b); ensure(b);
@ -648,7 +650,7 @@ public class Mp4DashReader {
return obj; return obj;
} }
private Hdlr parse_hdlr(Box ref) throws IOException { private Hdlr parseHdlr(final Box ref) throws IOException {
// version // version
// flags // flags
stream.skipBytes(4); stream.skipBytes(4);
@ -666,10 +668,10 @@ public class Mp4DashReader {
return obj; return obj;
} }
private Moov parse_moov(Box ref) throws IOException { private Moov parseMoov(final Box ref) throws IOException {
Box b = readBox(ATOM_MVHD); Box b = readBox(ATOM_MVHD);
Moov moov = new Moov(); Moov moov = new Moov();
moov.mvhd = parse_mvhd(); moov.mvhd = parseMvhd();
ensure(b); ensure(b);
ArrayList<Trak> tmp = new ArrayList<>((int) moov.mvhd.nextTrackId); ArrayList<Trak> tmp = new ArrayList<>((int) moov.mvhd.nextTrackId);
@ -677,10 +679,10 @@ public class Mp4DashReader {
switch (b.type) { switch (b.type) {
case ATOM_TRAK: case ATOM_TRAK:
tmp.add(parse_trak(b)); tmp.add(parseTrak(b));
break; break;
case ATOM_MVEX: case ATOM_MVEX:
moov.mvex_trex = parse_mvex(b, (int) moov.mvhd.nextTrackId); moov.mvexTrex = parseMvex(b, (int) moov.mvhd.nextTrackId);
break; break;
} }
@ -692,19 +694,19 @@ public class Mp4DashReader {
return moov; return moov;
} }
private Trex[] parse_mvex(Box ref, int possibleTrackCount) throws IOException { private Trex[] parseMvex(final Box ref, final int possibleTrackCount) throws IOException {
ArrayList<Trex> tmp = new ArrayList<>(possibleTrackCount); ArrayList<Trex> tmp = new ArrayList<>(possibleTrackCount);
Box b; Box b;
while ((b = untilBox(ref, ATOM_TREX)) != null) { while ((b = untilBox(ref, ATOM_TREX)) != null) {
tmp.add(parse_trex()); tmp.add(parseTrex());
ensure(b); ensure(b);
} }
return tmp.toArray(new Trex[0]); return tmp.toArray(new Trex[0]);
} }
private Trex parse_trex() throws IOException { private Trex parseTrex() throws IOException {
// version // version
// flags // flags
stream.skipBytes(4); stream.skipBytes(4);
@ -719,7 +721,7 @@ public class Mp4DashReader {
return obj; return obj;
} }
private Elst parse_edts(Box ref) throws IOException { private Elst parseEdts(final Box ref) throws IOException {
Box b = untilBox(ref, ATOM_ELST); Box b = untilBox(ref, ATOM_ELST);
if (b == null) { if (b == null) {
return null; return null;
@ -728,22 +730,22 @@ public class Mp4DashReader {
Elst obj = new Elst(); Elst obj = new Elst();
boolean v1 = stream.read() == 1; boolean v1 = stream.read() == 1;
stream.skipBytes(3);// flags stream.skipBytes(3); // flags
int entryCount = stream.readInt(); int entryCount = stream.readInt();
if (entryCount < 1) { if (entryCount < 1) {
obj.bMediaRate = 0x00010000;// default media rate (1.0) obj.bMediaRate = 0x00010000; // default media rate (1.0)
return obj; return obj;
} }
if (v1) { if (v1) {
stream.skipBytes(DataReader.LONG_SIZE);// segment duration stream.skipBytes(DataReader.LONG_SIZE); // segment duration
obj.MediaTime = stream.readLong(); obj.mediaTime = stream.readLong();
// ignore all remain entries // ignore all remain entries
stream.skipBytes((entryCount - 1) * (DataReader.LONG_SIZE * 2)); stream.skipBytes((entryCount - 1) * (DataReader.LONG_SIZE * 2));
} else { } else {
stream.skipBytes(DataReader.INTEGER_SIZE);// segment duration stream.skipBytes(DataReader.INTEGER_SIZE); // segment duration
obj.MediaTime = stream.readInt(); obj.mediaTime = stream.readInt();
} }
obj.bMediaRate = stream.readInt(); obj.bMediaRate = stream.readInt();
@ -751,7 +753,7 @@ public class Mp4DashReader {
return obj; return obj;
} }
private Minf parse_minf(Box ref) throws IOException { private Minf parseMinf(final Box ref) throws IOException {
Minf obj = new Minf(); Minf obj = new Minf();
Box b; Box b;
@ -762,11 +764,11 @@ public class Mp4DashReader {
obj.dinf = readFullBox(b); obj.dinf = readFullBox(b);
break; break;
case ATOM_STBL: case ATOM_STBL:
obj.stbl_stsd = parse_stbl(b); obj.stblStsd = parseStbl(b);
break; break;
case ATOM_VMHD: case ATOM_VMHD:
case ATOM_SMHD: case ATOM_SMHD:
obj.$mhd = readFullBox(b); obj.mhd = readFullBox(b);
break; break;
} }
@ -777,42 +779,39 @@ public class Mp4DashReader {
} }
/** /**
* this only read the "stsd" box inside * This only reads the "stsd" box inside.
*
* @param ref stbl box
* @return stsd box inside
*/ */
private byte[] parse_stbl(Box ref) throws IOException { private byte[] parseStbl(final Box ref) throws IOException {
Box b = untilBox(ref, ATOM_STSD); Box b = untilBox(ref, ATOM_STSD);
if (b == null) { if (b == null) {
return new byte[0];// this never should happens (missing codec startup data) return new byte[0]; // this never should happens (missing codec startup data)
} }
return readFullBox(b); return readFullBox(b);
} }
class Box { class Box {
int type; int type;
long offset; long offset;
long size; long size;
} }
public class Moof { public class Moof {
int mfhdSequenceNumber;
int mfhd_SequenceNumber;
public Traf traf; public Traf traf;
} }
public class Traf { public class Traf {
public Tfhd tfhd; public Tfhd tfhd;
long tfdt; long tfdt;
public Trun trun; public Trun trun;
} }
public class Tfhd { public class Tfhd {
int bFlags; int bFlags;
public int trackId; public int trackId;
int defaultSampleDuration; int defaultSampleDuration;
@ -821,7 +820,6 @@ public class Mp4DashReader {
} }
class TrunEntry { class TrunEntry {
int sampleDuration; int sampleDuration;
int sampleSize; int sampleSize;
int sampleFlags; int sampleFlags;
@ -833,7 +831,6 @@ public class Mp4DashReader {
} }
public class Trun { public class Trun {
public int chunkDuration; public int chunkDuration;
public int chunkSize; public int chunkSize;
@ -843,10 +840,10 @@ public class Mp4DashReader {
public int entryCount; public int entryCount;
byte[] bEntries; byte[] bEntries;
int entries_rowSize; int entriesRowSize;
public TrunEntry getEntry(int i) { public TrunEntry getEntry(final int i) {
ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entries_rowSize, entries_rowSize); ByteBuffer buffer = ByteBuffer.wrap(bEntries, i * entriesRowSize, entriesRowSize);
TrunEntry entry = new TrunEntry(); TrunEntry entry = new TrunEntry();
if (hasFlag(bFlags, 0x0100)) { if (hasFlag(bFlags, 0x0100)) {
@ -868,7 +865,7 @@ public class Mp4DashReader {
return entry; return entry;
} }
public TrunEntry getAbsoluteEntry(int i, Tfhd header) { public TrunEntry getAbsoluteEntry(final int i, final Tfhd header) {
TrunEntry entry = getEntry(i); TrunEntry entry = getEntry(i);
if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) { if (!hasFlag(bFlags, 0x0100) && hasFlag(header.bFlags, 0x20)) {
@ -892,7 +889,6 @@ public class Mp4DashReader {
} }
public class Tkhd { public class Tkhd {
int trackId; int trackId;
long duration; long duration;
short bVolume; short bVolume;
@ -904,28 +900,24 @@ public class Mp4DashReader {
} }
public class Trak { public class Trak {
public Tkhd tkhd; public Tkhd tkhd;
public Elst edst_elst; public Elst edstElst;
public Mdia mdia; public Mdia mdia;
} }
class Mvhd { class Mvhd {
long timeScale; long timeScale;
long nextTrackId; long nextTrackId;
} }
class Moov { class Moov {
Mvhd mvhd; Mvhd mvhd;
Trak[] trak; Trak[] trak;
Trex[] mvex_trex; Trex[] mvexTrex;
} }
public class Trex { public class Trex {
private int trackId; private int trackId;
int defaultSampleDescriptionIndex; int defaultSampleDescriptionIndex;
int defaultSampleDuration; int defaultSampleDuration;
@ -934,42 +926,36 @@ public class Mp4DashReader {
} }
public class Elst { public class Elst {
public long mediaTime;
public long MediaTime;
public int bMediaRate; public int bMediaRate;
} }
public class Mdia { public class Mdia {
public int mdhdTimeScale;
public int mdhd_timeScale;
public byte[] mdhd; public byte[] mdhd;
public Hdlr hdlr; public Hdlr hdlr;
public Minf minf; public Minf minf;
} }
public class Hdlr { public class Hdlr {
public int type; public int type;
public int subType; public int subType;
public byte[] bReserved; public byte[] bReserved;
} }
public class Minf { public class Minf {
public byte[] dinf; public byte[] dinf;
public byte[] stbl_stsd; public byte[] stblStsd;
public byte[] $mhd; public byte[] mhd;
} }
public class Mp4Track { public class Mp4Track {
public TrackKind kind; public TrackKind kind;
public Trak trak; public Trak trak;
public Trex trex; public Trex trex;
} }
public class Mp4DashChunk { public class Mp4DashChunk {
public InputStream data; public InputStream data;
public Moof moof; public Moof moof;
private int i = 0; private int i = 0;
@ -1002,9 +988,7 @@ public class Mp4DashReader {
} }
public class Mp4DashSample { public class Mp4DashSample {
public TrunEntry info; public TrunEntry info;
public byte[] data; public byte[] data;
} }
} }

View File

@ -17,13 +17,15 @@ import java.util.ArrayList;
* @author kapodamy * @author kapodamy
*/ */
public class Mp4FromDashWriter { public class Mp4FromDashWriter {
private static final int EPOCH_OFFSET = 2082844800;
private final static int EPOCH_OFFSET = 2082844800; private static final short DEFAULT_TIMESCALE = 1000;
private final static short DEFAULT_TIMESCALE = 1000; private static final byte SAMPLES_PER_CHUNK_INIT = 2;
private final static byte SAMPLES_PER_CHUNK_INIT = 2; // ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6
private final static byte SAMPLES_PER_CHUNK = 6;// ffmpeg uses 2, basic uses 1 (with 60fps uses 21 or 22). NewPipe will use 6 private static final byte SAMPLES_PER_CHUNK = 6;
private final static long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;// near 3.999 GiB // near 3.999 GiB
private final static int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024); // 2.2 MiB enough for: 1080p 60fps 00h35m00s private static final long THRESHOLD_FOR_CO64 = 0xFFFEFFFFL;
// 2.2 MiB enough for: 1080p 60fps 00h35m00s
private static final int THRESHOLD_MOOV_LENGTH = (256 * 1024) + (2048 * 1024);
private final long time; private final long time;
@ -48,7 +50,7 @@ public class Mp4FromDashWriter {
private final ArrayList<Integer> compatibleBrands = new ArrayList<>(5); private final ArrayList<Integer> compatibleBrands = new ArrayList<>(5);
public Mp4FromDashWriter(SharpStream... sources) throws IOException { public Mp4FromDashWriter(final SharpStream... sources) throws IOException {
for (SharpStream src : sources) { for (SharpStream src : sources) {
if (!src.canRewind() && !src.canRead()) { if (!src.canRewind() && !src.canRead()) {
throw new IOException("All sources must be readable and allow rewind"); throw new IOException("All sources must be readable and allow rewind");
@ -60,12 +62,12 @@ public class Mp4FromDashWriter {
readersChunks = new Mp4DashChunk[readers.length]; readersChunks = new Mp4DashChunk[readers.length];
time = (System.currentTimeMillis() / 1000L) + EPOCH_OFFSET; time = (System.currentTimeMillis() / 1000L) + EPOCH_OFFSET;
compatibleBrands.add(0x6D703431);// mp41 compatibleBrands.add(0x6D703431); // mp41
compatibleBrands.add(0x69736F6D);// isom compatibleBrands.add(0x69736F6D); // isom
compatibleBrands.add(0x69736F32);// iso2 compatibleBrands.add(0x69736F32); // iso2
} }
public Mp4Track[] getTracksFromSource(int sourceIndex) throws IllegalStateException { public Mp4Track[] getTracksFromSource(final int sourceIndex) throws IllegalStateException {
if (!parsed) { if (!parsed) {
throw new IllegalStateException("All sources must be parsed first"); throw new IllegalStateException("All sources must be parsed first");
} }
@ -92,7 +94,7 @@ public class Mp4FromDashWriter {
} }
} }
public void selectTracks(int... trackIndex) throws IOException { public void selectTracks(final int... trackIndex) throws IOException {
if (done) { if (done) {
throw new IOException("already done"); throw new IOException("already done");
} }
@ -110,7 +112,7 @@ public class Mp4FromDashWriter {
} }
} }
public void setMainBrand(int brand) { public void setMainBrand(final int brand) {
overrideMainBrand = brand; overrideMainBrand = brand;
} }
@ -140,7 +142,7 @@ public class Mp4FromDashWriter {
outStream = null; outStream = null;
} }
public void build(SharpStream output) throws IOException { public void build(final SharpStream output) throws IOException {
if (done) { if (done) {
throw new RuntimeException("already done"); throw new RuntimeException("already done");
} }
@ -153,7 +155,7 @@ public class Mp4FromDashWriter {
// not allowed for very short tracks (less than 0.5 seconds) // not allowed for very short tracks (less than 0.5 seconds)
// //
outStream = output; outStream = output;
long read = 8;// mdat box header size long read = 8; // mdat box header size
long totalSampleSize = 0; long totalSampleSize = 0;
int[] sampleExtra = new int[readers.length]; int[] sampleExtra = new int[readers.length];
int[] defaultMediaTime = new int[readers.length]; int[] defaultMediaTime = new int[readers.length];
@ -165,12 +167,12 @@ public class Mp4FromDashWriter {
tablesInfo[i] = new TablesInfo(); tablesInfo[i] = new TablesInfo();
} }
int single_sample_buffer; int singleSampleBuffer;
if (tracks.length == 1 && tracks[0].kind == TrackKind.Audio) { if (tracks.length == 1 && tracks[0].kind == TrackKind.Audio) {
// near 1 second of audio data per chunk, avoid split the audio stream in large chunks // near 1 second of audio data per chunk, avoid split the audio stream in large chunks
single_sample_buffer = tracks[0].trak.mdia.mdhd_timeScale / 1000; singleSampleBuffer = tracks[0].trak.mdia.mdhdTimeScale / 1000;
} else { } else {
single_sample_buffer = -1; singleSampleBuffer = -1;
} }
@ -187,7 +189,7 @@ public class Mp4FromDashWriter {
} }
read += chunk.moof.traf.trun.chunkSize; read += chunk.moof.traf.trun.chunkSize;
sampleExtra[i] += chunk.moof.traf.trun.chunkDuration;// calculate track duration sampleExtra[i] += chunk.moof.traf.trun.chunkDuration; // calculate track duration
TrunEntry info; TrunEntry info;
while ((info = chunk.getNextSampleInfo()) != null) { while ((info = chunk.getNextSampleInfo()) != null) {
@ -222,8 +224,8 @@ public class Mp4FromDashWriter {
readers[i].rewind(); readers[i].rewind();
if (single_sample_buffer > 0) { if (singleSampleBuffer > 0) {
initChunkTables(tablesInfo[i], single_sample_buffer, single_sample_buffer); initChunkTables(tablesInfo[i], singleSampleBuffer, singleSampleBuffer);
} else { } else {
initChunkTables(tablesInfo[i], SAMPLES_PER_CHUNK_INIT, SAMPLES_PER_CHUNK); initChunkTables(tablesInfo[i], SAMPLES_PER_CHUNK_INIT, SAMPLES_PER_CHUNK);
} }
@ -232,18 +234,18 @@ public class Mp4FromDashWriter {
if (sampleSizeChanges == 1) { if (sampleSizeChanges == 1) {
tablesInfo[i].stsz = 0; tablesInfo[i].stsz = 0;
tablesInfo[i].stsz_default = samplesSize; tablesInfo[i].stszDefault = samplesSize;
} else { } else {
tablesInfo[i].stsz_default = 0; tablesInfo[i].stszDefault = 0;
} }
if (tablesInfo[i].stss == tablesInfo[i].stsz) { if (tablesInfo[i].stss == tablesInfo[i].stsz) {
tablesInfo[i].stss = -1;// for audio tracks (all samples are keyframes) tablesInfo[i].stss = -1; // for audio tracks (all samples are keyframes)
} }
// ensure track duration // ensure track duration
if (tracks[i].trak.tkhd.duration < 1) { if (tracks[i].trak.tkhd.duration < 1) {
tracks[i].trak.tkhd.duration = sampleExtra[i];// this never should happen tracks[i].trak.tkhd.duration = sampleExtra[i]; // this never should happen
} }
} }
@ -251,21 +253,21 @@ public class Mp4FromDashWriter {
boolean is64 = read > THRESHOLD_FOR_CO64; boolean is64 = read > THRESHOLD_FOR_CO64;
// calculate the moov size // calculate the moov size
int auxSize = make_moov(defaultMediaTime, tablesInfo, is64); int auxSize = makeMoov(defaultMediaTime, tablesInfo, is64);
if (auxSize < THRESHOLD_MOOV_LENGTH) { if (auxSize < THRESHOLD_MOOV_LENGTH) {
auxBuffer = ByteBuffer.allocate(auxSize);// cache moov in the memory auxBuffer = ByteBuffer.allocate(auxSize); // cache moov in the memory
} }
moovSimulation = false; moovSimulation = false;
writeOffset = 0; writeOffset = 0;
final int ftyp_size = make_ftyp(); final int ftypSize = makeFtyp();
// reserve moov space in the output stream // reserve moov space in the output stream
if (auxSize > 0) { if (auxSize > 0) {
int length = auxSize; int length = auxSize;
byte[] buffer = new byte[64 * 1024];// 64 KiB byte[] buffer = new byte[64 * 1024]; // 64 KiB
while (length > 0) { while (length > 0) {
int count = Math.min(length, buffer.length); int count = Math.min(length, buffer.length);
outWrite(buffer, count); outWrite(buffer, count);
@ -274,21 +276,22 @@ public class Mp4FromDashWriter {
} }
if (auxBuffer == null) { if (auxBuffer == null) {
outSeek(ftyp_size); outSeek(ftypSize);
} }
// tablesInfo contains row counts // tablesInfo contains row counts
// and after returning from make_moov() will contain those table offsets // and after returning from makeMoov() will contain those table offsets
make_moov(defaultMediaTime, tablesInfo, is64); makeMoov(defaultMediaTime, tablesInfo, is64);
// write tables: stts stsc sbgp // write tables: stts stsc sbgp
// reset for ctts table: sampleCount sampleExtra // reset for ctts table: sampleCount sampleExtra
for (int i = 0; i < readers.length; i++) { for (int i = 0; i < readers.length; i++) {
writeEntryArray(tablesInfo[i].stts, 2, sampleCount[i], defaultSampleDuration[i]); writeEntryArray(tablesInfo[i].stts, 2, sampleCount[i], defaultSampleDuration[i]);
writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stsc_bEntries.length, tablesInfo[i].stsc_bEntries); writeEntryArray(tablesInfo[i].stsc, tablesInfo[i].stscBEntries.length,
tablesInfo[i].stsc_bEntries = null; tablesInfo[i].stscBEntries);
tablesInfo[i].stscBEntries = null;
if (tablesInfo[i].ctts > 0) { if (tablesInfo[i].ctts > 0) {
sampleCount[i] = 1;// the index is not base zero sampleCount[i] = 1; // the index is not base zero
sampleExtra[i] = -1; sampleExtra[i] = -1;
} }
if (tablesInfo[i].sbgp > 0) { if (tablesInfo[i].sbgp > 0) {
@ -300,11 +303,11 @@ public class Mp4FromDashWriter {
outRestore(); outRestore();
} }
outWrite(make_mdat(totalSampleSize, is64)); outWrite(makeMdat(totalSampleSize, is64));
int[] sampleIndex = new int[readers.length]; int[] sampleIndex = new int[readers.length];
int[] sizes = new int[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK]; int[] sizes = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
int[] sync = new int[single_sample_buffer > 0 ? single_sample_buffer : SAMPLES_PER_CHUNK]; int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK];
int written = readers.length; int written = readers.length;
while (written > 0) { while (written > 0) {
@ -312,14 +315,14 @@ public class Mp4FromDashWriter {
for (int i = 0; i < readers.length; i++) { for (int i = 0; i < readers.length; i++) {
if (sampleIndex[i] < 0) { if (sampleIndex[i] < 0) {
continue;// track is done continue; // track is done
} }
long chunkOffset = writeOffset; long chunkOffset = writeOffset;
int syncCount = 0; int syncCount = 0;
int limit; int limit;
if (single_sample_buffer > 0) { if (singleSampleBuffer > 0) {
limit = single_sample_buffer; limit = singleSampleBuffer;
} else { } else {
limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK; limit = sampleIndex[i] == 0 ? SAMPLES_PER_CHUNK_INIT : SAMPLES_PER_CHUNK;
} }
@ -330,7 +333,8 @@ public class Mp4FromDashWriter {
if (sample == null) { if (sample == null) {
if (tablesInfo[i].ctts > 0 && sampleExtra[i] >= 0) { if (tablesInfo[i].ctts > 0 && sampleExtra[i] >= 0) {
writeEntryArray(tablesInfo[i].ctts, 1, sampleCount[i], sampleExtra[i]);// flush last entries writeEntryArray(tablesInfo[i].ctts, 1, sampleCount[i],
sampleExtra[i]); // flush last entries
outRestore(); outRestore();
} }
sampleIndex[i] = -1; sampleIndex[i] = -1;
@ -344,7 +348,8 @@ public class Mp4FromDashWriter {
sampleCount[i]++; sampleCount[i]++;
} else { } else {
if (sampleExtra[i] >= 0) { if (sampleExtra[i] >= 0) {
tablesInfo[i].ctts = writeEntryArray(tablesInfo[i].ctts, 2, sampleCount[i], sampleExtra[i]); tablesInfo[i].ctts = writeEntryArray(tablesInfo[i].ctts, 2,
sampleCount[i], sampleExtra[i]);
outRestore(); outRestore();
} }
sampleCount[i] = 1; sampleCount[i] = 1;
@ -378,7 +383,8 @@ public class Mp4FromDashWriter {
if (is64) { if (is64) {
tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset); tablesInfo[i].stco = writeEntry64(tablesInfo[i].stco, chunkOffset);
} else { } else {
tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1, (int) chunkOffset); tablesInfo[i].stco = writeEntryArray(tablesInfo[i].stco, 1,
(int) chunkOffset);
} }
} }
@ -389,17 +395,17 @@ public class Mp4FromDashWriter {
if (auxBuffer != null) { if (auxBuffer != null) {
// dump moov // dump moov
outSeek(ftyp_size); outSeek(ftypSize);
outStream.write(auxBuffer.array(), 0, auxBuffer.capacity()); outStream.write(auxBuffer.array(), 0, auxBuffer.capacity());
auxBuffer = null; auxBuffer = null;
} }
} }
private Mp4DashSample getNextSample(int track) throws IOException { private Mp4DashSample getNextSample(final int track) throws IOException {
if (readersChunks[track] == null) { if (readersChunks[track] == null) {
readersChunks[track] = readers[track].getNextChunk(false); readersChunks[track] = readers[track].getNextChunk(false);
if (readersChunks[track] == null) { if (readersChunks[track] == null) {
return null;// EOF reached return null; // EOF reached
} }
} }
@ -413,7 +419,7 @@ public class Mp4FromDashWriter {
} }
private int writeEntry64(int offset, long value) throws IOException { private int writeEntry64(final int offset, final long value) throws IOException {
outBackup(); outBackup();
auxSeek(offset); auxSeek(offset);
@ -422,7 +428,8 @@ public class Mp4FromDashWriter {
return offset + 8; return offset + 8;
} }
private int writeEntryArray(int offset, int count, int... values) throws IOException { private int writeEntryArray(final int offset, final int count, final int... values)
throws IOException {
outBackup(); outBackup();
auxSeek(offset); auxSeek(offset);
@ -456,7 +463,8 @@ public class Mp4FromDashWriter {
} }
} }
private void initChunkTables(TablesInfo tables, int firstCount, int succesiveCount) { private void initChunkTables(final TablesInfo tables, final int firstCount,
final int succesiveCount) {
// tables.stsz holds amount of samples of the track (total) // tables.stsz holds amount of samples of the track (total)
int totalSamples = (tables.stsz - firstCount); int totalSamples = (tables.stsz - firstCount);
float chunkAmount = totalSamples / (float) succesiveCount; float chunkAmount = totalSamples / (float) succesiveCount;
@ -473,36 +481,36 @@ public class Mp4FromDashWriter {
} }
// stsc_table_entry = [first_chunk, samples_per_chunk, sample_description_index] // stsc_table_entry = [first_chunk, samples_per_chunk, sample_description_index]
tables.stsc_bEntries = new int[tables.stsc * 3]; tables.stscBEntries = new int[tables.stsc * 3];
tables.stco = remainChunkOffset + 1;// total entrys in chunk offset box tables.stco = remainChunkOffset + 1; // total entrys in chunk offset box
tables.stsc_bEntries[index++] = 1; tables.stscBEntries[index++] = 1;
tables.stsc_bEntries[index++] = firstCount; tables.stscBEntries[index++] = firstCount;
tables.stsc_bEntries[index++] = 1; tables.stscBEntries[index++] = 1;
if (firstCount != succesiveCount) { if (firstCount != succesiveCount) {
tables.stsc_bEntries[index++] = 2; tables.stscBEntries[index++] = 2;
tables.stsc_bEntries[index++] = succesiveCount; tables.stscBEntries[index++] = succesiveCount;
tables.stsc_bEntries[index++] = 1; tables.stscBEntries[index++] = 1;
} }
if (remain) { if (remain) {
tables.stsc_bEntries[index++] = remainChunkOffset + 1; tables.stscBEntries[index++] = remainChunkOffset + 1;
tables.stsc_bEntries[index++] = totalSamples % succesiveCount; tables.stscBEntries[index++] = totalSamples % succesiveCount;
tables.stsc_bEntries[index] = 1; tables.stscBEntries[index] = 1;
} }
} }
private void outWrite(byte[] buffer) throws IOException { private void outWrite(final byte[] buffer) throws IOException {
outWrite(buffer, buffer.length); outWrite(buffer, buffer.length);
} }
private void outWrite(byte[] buffer, int count) throws IOException { private void outWrite(final byte[] buffer, final int count) throws IOException {
writeOffset += count; writeOffset += count;
outStream.write(buffer, 0, count); outStream.write(buffer, 0, count);
} }
private void outSeek(long offset) throws IOException { private void outSeek(final long offset) throws IOException {
if (outStream.canSeek()) { if (outStream.canSeek()) {
outStream.seek(offset); outStream.seek(offset);
writeOffset = offset; writeOffset = offset;
@ -515,12 +523,12 @@ public class Mp4FromDashWriter {
} }
} }
private void outSkip(long amount) throws IOException { private void outSkip(final long amount) throws IOException {
outStream.skip(amount); outStream.skip(amount);
writeOffset += amount; writeOffset += amount;
} }
private int lengthFor(int offset) throws IOException { private int lengthFor(final int offset) throws IOException {
int size = auxOffset() - offset; int size = auxOffset() - offset;
if (moovSimulation) { if (moovSimulation) {
@ -534,7 +542,8 @@ public class Mp4FromDashWriter {
return size; return size;
} }
private int make(int type, int extra, int columns, int rows) throws IOException { private int make(final int type, final int extra, final int columns, final int rows)
throws IOException {
final byte base = 16; final byte base = 16;
int size = columns * rows * 4; int size = columns * rows * 4;
int total = size + base; int total = size + base;
@ -562,14 +571,14 @@ public class Mp4FromDashWriter {
return offset + base; return offset + base;
} }
private void auxWrite(int value) throws IOException { private void auxWrite(final int value) throws IOException {
auxWrite(ByteBuffer.allocate(4) auxWrite(ByteBuffer.allocate(4)
.putInt(value) .putInt(value)
.array() .array()
); );
} }
private void auxWrite(byte[] buffer) throws IOException { private void auxWrite(final byte[] buffer) throws IOException {
if (moovSimulation) { if (moovSimulation) {
writeOffset += buffer.length; writeOffset += buffer.length;
} else if (auxBuffer == null) { } else if (auxBuffer == null) {
@ -579,7 +588,7 @@ public class Mp4FromDashWriter {
} }
} }
private void auxSeek(int offset) throws IOException { private void auxSeek(final int offset) throws IOException {
if (moovSimulation) { if (moovSimulation) {
writeOffset = offset; writeOffset = offset;
} else if (auxBuffer == null) { } else if (auxBuffer == null) {
@ -589,7 +598,7 @@ public class Mp4FromDashWriter {
} }
} }
private void auxSkip(int amount) throws IOException { private void auxSkip(final int amount) throws IOException {
if (moovSimulation) { if (moovSimulation) {
writeOffset += amount; writeOffset += amount;
} else if (auxBuffer == null) { } else if (auxBuffer == null) {
@ -603,27 +612,27 @@ public class Mp4FromDashWriter {
return auxBuffer == null ? (int) writeOffset : auxBuffer.position(); return auxBuffer == null ? (int) writeOffset : auxBuffer.position();
} }
private int makeFtyp() throws IOException {
private int make_ftyp() throws IOException {
int size = 16 + (compatibleBrands.size() * 4); int size = 16 + (compatibleBrands.size() * 4);
if (overrideMainBrand != 0) size += 4; if (overrideMainBrand != 0) {
size += 4;
}
ByteBuffer buffer = ByteBuffer.allocate(size); ByteBuffer buffer = ByteBuffer.allocate(size);
buffer.putInt(size); buffer.putInt(size);
buffer.putInt(0x66747970);// "ftyp" buffer.putInt(0x66747970); // "ftyp"
if (overrideMainBrand == 0) { if (overrideMainBrand == 0) {
buffer.putInt(0x6D703432);// mayor brand "mp42" buffer.putInt(0x6D703432); // mayor brand "mp42"
buffer.putInt(512);// default minor version buffer.putInt(512); // default minor version
} else { } else {
buffer.putInt(overrideMainBrand); buffer.putInt(overrideMainBrand);
buffer.putInt(0); buffer.putInt(0);
buffer.putInt(0x6D703432);// "mp42" compatible brand buffer.putInt(0x6D703432); // "mp42" compatible brand
} }
for (Integer brand : compatibleBrands) { for (Integer brand : compatibleBrands) {
buffer.putInt(brand);// compatible brand buffer.putInt(brand); // compatible brand
} }
outWrite(buffer.array()); outWrite(buffer.array());
@ -631,7 +640,7 @@ public class Mp4FromDashWriter {
return size; return size;
} }
private byte[] make_mdat(long refSize, boolean is64) { private byte[] makeMdat(long refSize, final boolean is64) {
if (is64) { if (is64) {
refSize += 16; refSize += 16;
} else { } else {
@ -640,7 +649,7 @@ public class Mp4FromDashWriter {
ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8) ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8)
.putInt(is64 ? 0x01 : (int) refSize) .putInt(is64 ? 0x01 : (int) refSize)
.putInt(0x6D646174);// mdat .putInt(0x6D646174); // mdat
if (is64) { if (is64) {
buffer.putLong(refSize); buffer.putLong(refSize);
@ -649,7 +658,7 @@ public class Mp4FromDashWriter {
return buffer.array(); return buffer.array();
} }
private void make_mvhd(long longestTrack) throws IOException { private void makeMvhd(final long longestTrack) throws IOException {
auxWrite(new byte[]{ auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x78, 0x6D, 0x76, 0x68, 0x64, 0x01, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x78, 0x6D, 0x76, 0x68, 0x64, 0x01, 0x00, 0x00, 0x00
}); });
@ -662,21 +671,22 @@ public class Mp4FromDashWriter {
); );
auxWrite(new byte[]{ auxWrite(new byte[]{
0x00, 0x01, 0x00, 0x00, 0x01, 0x00,// default volume and rate 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, // default volume and rate
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// reserved values 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved values
// default matrix // default matrix
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00 0x40, 0x00, 0x00, 0x00
}); });
auxWrite(new byte[24]);// predefined auxWrite(new byte[24]); // predefined
auxWrite(ByteBuffer.allocate(4) auxWrite(ByteBuffer.allocate(4)
.putInt(tracks.length + 1) .putInt(tracks.length + 1)
.array() .array()
); );
} }
private int make_moov(int[] defaultMediaTime, TablesInfo[] tablesInfo, boolean is64) throws RuntimeException, IOException { private int makeMoov(final int[] defaultMediaTime, final TablesInfo[] tablesInfo,
final boolean is64) throws RuntimeException, IOException {
int start = auxOffset(); int start = auxOffset();
auxWrite(new byte[]{ auxWrite(new byte[]{
@ -688,21 +698,21 @@ public class Mp4FromDashWriter {
for (int i = 0; i < durations.length; i++) { for (int i = 0; i < durations.length; i++) {
durations[i] = (long) Math.ceil( durations[i] = (long) Math.ceil(
((double) tracks[i].trak.tkhd.duration / tracks[i].trak.mdia.mdhd_timeScale) * DEFAULT_TIMESCALE ((double) tracks[i].trak.tkhd.duration / tracks[i].trak.mdia.mdhdTimeScale)
); * DEFAULT_TIMESCALE);
if (durations[i] > longestTrack) { if (durations[i] > longestTrack) {
longestTrack = durations[i]; longestTrack = durations[i];
} }
} }
make_mvhd(longestTrack); makeMvhd(longestTrack);
for (int i = 0; i < tracks.length; i++) { for (int i = 0; i < tracks.length; i++) {
if (tracks[i].trak.tkhd.matrix.length != 36) { if (tracks[i].trak.tkhd.matrix.length != 36) {
throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i); throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
} }
make_trak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64); makeTrak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
} }
// udta/meta/ilst/©too // udta/meta/ilst/©too
@ -713,17 +723,18 @@ public class Mp4FromDashWriter {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00,
0x1F, (byte) 0xA9, 0x74, 0x6F, 0x6F, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x1F, (byte) 0xA9, 0x74, 0x6F, 0x6F, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65// "NewPipe" binary string 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65 // "NewPipe" binary string
}); });
return lengthFor(start); return lengthFor(start);
} }
private void make_trak(int index, long duration, int defaultMediaTime, TablesInfo tables, boolean is64) throws IOException { private void makeTrak(final int index, final long duration, final int defaultMediaTime,
final TablesInfo tables, final boolean is64) throws IOException {
int start = auxOffset(); int start = auxOffset();
auxWrite(new byte[]{ auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x6B,// trak header 0x00, 0x00, 0x00, 0x00, 0x74, 0x72, 0x61, 0x6B, // trak header
0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03 // tkhd header 0x00, 0x00, 0x00, 0x68, 0x74, 0x6B, 0x68, 0x64, 0x01, 0x00, 0x00, 0x03 // tkhd header
}); });
@ -747,20 +758,20 @@ public class Mp4FromDashWriter {
); );
auxWrite(new byte[]{ auxWrite(new byte[]{
0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73,// edts header 0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73, // edts header
0x00, 0x00, 0x00, 0x1C, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01// elst header 0x00, 0x00, 0x00, 0x1C, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 // elst header
}); });
int bMediaRate; int bMediaRate;
int mediaTime; int mediaTime;
if (tracks[index].trak.edst_elst == null) { if (tracks[index].trak.edstElst == null) {
// is a audio track ¿is edst/elst optional for audio tracks? // is a audio track ¿is edst/elst optional for audio tracks?
mediaTime = 0x00;// ffmpeg set this value as zero, instead of defaultMediaTime mediaTime = 0x00; // ffmpeg set this value as zero, instead of defaultMediaTime
bMediaRate = 0x00010000; bMediaRate = 0x00010000;
} else { } else {
mediaTime = (int) tracks[index].trak.edst_elst.MediaTime; mediaTime = (int) tracks[index].trak.edstElst.mediaTime;
bMediaRate = tracks[index].trak.edst_elst.bMediaRate; bMediaRate = tracks[index].trak.edstElst.bMediaRate;
} }
auxWrite(ByteBuffer auxWrite(ByteBuffer
@ -771,32 +782,33 @@ public class Mp4FromDashWriter {
.array() .array()
); );
make_mdia(tracks[index].trak.mdia, tables, is64, tracks[index].kind == TrackKind.Audio); makeMdia(tracks[index].trak.mdia, tables, is64, tracks[index].kind == TrackKind.Audio);
lengthFor(start); lengthFor(start);
} }
private void make_mdia(Mdia mdia, TablesInfo tablesInfo, boolean is64, boolean isAudio) throws IOException { private void makeMdia(final Mdia mdia, final TablesInfo tablesInfo, final boolean is64,
int start_mdia = auxOffset(); final boolean isAudio) throws IOException {
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61});// mdia int startMdia = auxOffset();
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x61}); // mdia
auxWrite(mdia.mdhd); auxWrite(mdia.mdhd);
auxWrite(make_hdlr(mdia.hdlr)); auxWrite(makeHdlr(mdia.hdlr));
int start_minf = auxOffset(); int startMinf = auxOffset();
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x69, 0x6E, 0x66});// minf auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x6D, 0x69, 0x6E, 0x66}); // minf
auxWrite(mdia.minf.$mhd); auxWrite(mdia.minf.mhd);
auxWrite(mdia.minf.dinf); auxWrite(mdia.minf.dinf);
int start_stbl = auxOffset(); int startStbl = auxOffset();
auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x62, 0x6C});// stbl auxWrite(new byte[]{0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x62, 0x6C}); // stbl
auxWrite(mdia.minf.stbl_stsd); auxWrite(mdia.minf.stblStsd);
// //
// In audio tracks the following tables is not required: ssts ctts // In audio tracks the following tables is not required: ssts ctts
// And stsz can be empty if has a default sample size // And stsz can be empty if has a default sample size
// //
if (moovSimulation) { if (moovSimulation) {
make(0x73747473, -1, 2, 1);// stts make(0x73747473, -1, 2, 1); // stts
if (tablesInfo.stss > 0) { if (tablesInfo.stss > 0) {
make(0x73747373, -1, 1, tablesInfo.stss); make(0x73747373, -1, 1, tablesInfo.stss);
} }
@ -804,7 +816,7 @@ public class Mp4FromDashWriter {
make(0x63747473, -1, 2, tablesInfo.ctts); make(0x63747473, -1, 2, tablesInfo.ctts);
} }
make(0x73747363, -1, 3, tablesInfo.stsc); make(0x73747363, -1, 3, tablesInfo.stsc);
make(0x7374737A, tablesInfo.stsz_default, 1, tablesInfo.stsz); make(0x7374737A, tablesInfo.stszDefault, 1, tablesInfo.stsz);
make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco); make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco);
} else { } else {
tablesInfo.stts = make(0x73747473, -1, 2, 1); tablesInfo.stts = make(0x73747473, -1, 2, 1);
@ -815,23 +827,24 @@ public class Mp4FromDashWriter {
tablesInfo.ctts = make(0x63747473, -1, 2, tablesInfo.ctts); tablesInfo.ctts = make(0x63747473, -1, 2, tablesInfo.ctts);
} }
tablesInfo.stsc = make(0x73747363, -1, 3, tablesInfo.stsc); tablesInfo.stsc = make(0x73747363, -1, 3, tablesInfo.stsc);
tablesInfo.stsz = make(0x7374737A, tablesInfo.stsz_default, 1, tablesInfo.stsz); tablesInfo.stsz = make(0x7374737A, tablesInfo.stszDefault, 1, tablesInfo.stsz);
tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1, tablesInfo.stco); tablesInfo.stco = make(is64 ? 0x636F3634 : 0x7374636F, -1, is64 ? 2 : 1,
tablesInfo.stco);
} }
if (isAudio) { if (isAudio) {
auxWrite(make_sgpd()); auxWrite(makeSgpd());
tablesInfo.sbgp = make_sbgp();// during simulation the returned offset is ignored tablesInfo.sbgp = makeSbgp(); // during simulation the returned offset is ignored
} }
lengthFor(start_stbl); lengthFor(startStbl);
lengthFor(start_minf); lengthFor(startMinf);
lengthFor(start_mdia); lengthFor(startMdia);
} }
private byte[] make_hdlr(Hdlr hdlr) { private byte[] makeHdlr(final Hdlr hdlr) {
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{ ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72,// hdlr 0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72, // hdlr
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// binary string "ISO Media file created in NewPipe (A libre lightweight streaming frontend for Android)." // binary string "ISO Media file created in NewPipe (A libre lightweight streaming frontend for Android)."
@ -846,28 +859,28 @@ public class Mp4FromDashWriter {
buffer.position(12); buffer.position(12);
buffer.putInt(hdlr.type); buffer.putInt(hdlr.type);
buffer.putInt(hdlr.subType); buffer.putInt(hdlr.subType);
buffer.put(hdlr.bReserved);// always is a zero array buffer.put(hdlr.bReserved); // always is a zero array
return buffer.array(); return buffer.array();
} }
private int make_sbgp() throws IOException { private int makeSbgp() throws IOException {
int offset = auxOffset(); int offset = auxOffset();
auxWrite(new byte[] { auxWrite(new byte[] {
0x00, 0x00, 0x00, 0x1C,// box size 0x00, 0x00, 0x00, 0x1C, // box size
0x73, 0x62, 0x67, 0x70,// "sbpg" 0x73, 0x62, 0x67, 0x70, // "sbpg"
0x00, 0x00, 0x00, 0x00,// default box flags 0x00, 0x00, 0x00, 0x00, // default box flags
0x72, 0x6F, 0x6C, 0x6C,// group type "roll" 0x72, 0x6F, 0x6C, 0x6C, // group type "roll"
0x00, 0x00, 0x00, 0x01,// group table size 0x00, 0x00, 0x00, 0x01, // group table size
0x00, 0x00, 0x00, 0x00,// group[0] total samples (to be set later) 0x00, 0x00, 0x00, 0x00, // group[0] total samples (to be set later)
0x00, 0x00, 0x00, 0x01// group[0] description index 0x00, 0x00, 0x00, 0x01 // group[0] description index
}); });
return offset + 0x14; return offset + 0x14;
} }
private byte[] make_sgpd() { private byte[] makeSgpd() {
/* /*
* Sample Group Description Box * Sample Group Description Box
* *
@ -882,26 +895,25 @@ public class Mp4FromDashWriter {
*/ */
ByteBuffer buffer = ByteBuffer.wrap(new byte[] { ByteBuffer buffer = ByteBuffer.wrap(new byte[] {
0x00, 0x00, 0x00, 0x1A,// box size 0x00, 0x00, 0x00, 0x1A, // box size
0x73, 0x67, 0x70, 0x64,// "sgpd" 0x73, 0x67, 0x70, 0x64, // "sgpd"
0x01, 0x00, 0x00, 0x00,// box flags (unknown flag sets) 0x01, 0x00, 0x00, 0x00, // box flags (unknown flag sets)
0x72, 0x6F, 0x6C, 0x6C, // ¿¿group type?? 0x72, 0x6F, 0x6C, 0x6C, // ¿¿group type??
0x00, 0x00, 0x00, 0x02,// ¿¿?? 0x00, 0x00, 0x00, 0x02, // ¿¿??
0x00, 0x00, 0x00, 0x01,// ¿¿?? 0x00, 0x00, 0x00, 0x01, // ¿¿??
(byte)0xFF, (byte)0xFF// ¿¿?? (byte) 0xFF, (byte) 0xFF // ¿¿??
}); });
return buffer.array(); return buffer.array();
} }
class TablesInfo { class TablesInfo {
int stts; int stts;
int stsc; int stsc;
int[] stsc_bEntries; int[] stscBEntries;
int ctts; int ctts;
int stsz; int stsz;
int stsz_default; int stszDefault;
int stss; int stss;
int stco; int stco;
int sbgp; int sbgp;

View File

@ -1,431 +1,428 @@
package org.schabi.newpipe.streams; package org.schabi.newpipe.streams;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.schabi.newpipe.streams.WebMReader.Cluster;
import org.schabi.newpipe.streams.WebMReader.Segment; import org.schabi.newpipe.streams.WebMReader.Cluster;
import org.schabi.newpipe.streams.WebMReader.SimpleBlock; import org.schabi.newpipe.streams.WebMReader.Segment;
import org.schabi.newpipe.streams.WebMReader.WebMTrack; import org.schabi.newpipe.streams.WebMReader.SimpleBlock;
import org.schabi.newpipe.streams.io.SharpStream; import org.schabi.newpipe.streams.WebMReader.WebMTrack;
import org.schabi.newpipe.streams.io.SharpStream;
import java.io.Closeable;
import java.io.IOException; import java.io.Closeable;
import java.nio.ByteBuffer; import java.io.IOException;
import java.nio.ByteOrder; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import androidx.annotation.Nullable;
/**
/** * @author kapodamy
* @author kapodamy */
*/ public class OggFromWebMWriter implements Closeable {
public class OggFromWebMWriter implements Closeable { private static final byte FLAG_UNSET = 0x00;
//private static final byte FLAG_CONTINUED = 0x01;
private static final byte FLAG_UNSET = 0x00; private static final byte FLAG_FIRST = 0x02;
//private static final byte FLAG_CONTINUED = 0x01; private static final byte FLAG_LAST = 0x04;
private static final byte FLAG_FIRST = 0x02;
private static final byte FLAG_LAST = 0x04; private static final byte HEADER_CHECKSUM_OFFSET = 22;
private static final byte HEADER_SIZE = 27;
private final static byte HEADER_CHECKSUM_OFFSET = 22;
private final static byte HEADER_SIZE = 27; private static final int TIME_SCALE_NS = 1000000000;
private final static int TIME_SCALE_NS = 1000000000; private boolean done = false;
private boolean parsed = false;
private boolean done = false;
private boolean parsed = false; private SharpStream source;
private SharpStream output;
private SharpStream source;
private SharpStream output; private int sequenceCount = 0;
private final int streamId;
private int sequence_count = 0; private byte packetFlag = FLAG_FIRST;
private final int STREAM_ID;
private byte packet_flag = FLAG_FIRST; private WebMReader webm = null;
private WebMTrack webmTrack = null;
private WebMReader webm = null; private Segment webmSegment = null;
private WebMTrack webm_track = null; private Cluster webmCluster = null;
private Segment webm_segment = null; private SimpleBlock webmBlock = null;
private Cluster webm_cluster = null;
private SimpleBlock webm_block = null; private long webmBlockLastTimecode = 0;
private long webmBlockNearDuration = 0;
private long webm_block_last_timecode = 0;
private long webm_block_near_duration = 0; private short segmentTableSize = 0;
private final byte[] segmentTable = new byte[255];
private short segment_table_size = 0; private long segmentTableNextTimestamp = TIME_SCALE_NS;
private final byte[] segment_table = new byte[255];
private long segment_table_next_timestamp = TIME_SCALE_NS; private final int[] crc32Table = new int[256];
private final int[] crc32_table = new int[256]; public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target) {
if (!source.canRead() || !source.canRewind()) {
public OggFromWebMWriter(@NonNull SharpStream source, @NonNull SharpStream target) { throw new IllegalArgumentException("source stream must be readable and allows seeking");
if (!source.canRead() || !source.canRewind()) { }
throw new IllegalArgumentException("source stream must be readable and allows seeking"); if (!target.canWrite() || !target.canRewind()) {
} throw new IllegalArgumentException("output stream must be writable and allows seeking");
if (!target.canWrite() || !target.canRewind()) { }
throw new IllegalArgumentException("output stream must be writable and allows seeking");
} this.source = source;
this.output = target;
this.source = source;
this.output = target; this.streamId = (int) System.currentTimeMillis();
this.STREAM_ID = (int) System.currentTimeMillis(); populateCrc32Table();
}
populate_crc32_table();
} public boolean isDone() {
return done;
public boolean isDone() { }
return done;
} public boolean isParsed() {
return parsed;
public boolean isParsed() { }
return parsed;
} public WebMTrack[] getTracksFromSource() throws IllegalStateException {
if (!parsed) {
public WebMTrack[] getTracksFromSource() throws IllegalStateException { throw new IllegalStateException("source must be parsed first");
if (!parsed) { }
throw new IllegalStateException("source must be parsed first");
} return webm.getAvailableTracks();
}
return webm.getAvailableTracks();
} public void parseSource() throws IOException, IllegalStateException {
if (done) {
public void parseSource() throws IOException, IllegalStateException { throw new IllegalStateException("already done");
if (done) { }
throw new IllegalStateException("already done"); if (parsed) {
} throw new IllegalStateException("already parsed");
if (parsed) { }
throw new IllegalStateException("already parsed");
} try {
webm = new WebMReader(source);
try { webm.parse();
webm = new WebMReader(source); webmSegment = webm.getNextSegment();
webm.parse(); } finally {
webm_segment = webm.getNextSegment(); parsed = true;
} finally { }
parsed = true; }
}
} public void selectTrack(final int trackIndex) throws IOException {
if (!parsed) {
public void selectTrack(int trackIndex) throws IOException { throw new IllegalStateException("source must be parsed first");
if (!parsed) { }
throw new IllegalStateException("source must be parsed first"); if (done) {
} throw new IOException("already done");
if (done) { }
throw new IOException("already done"); if (webmTrack != null) {
} throw new IOException("tracks already selected");
if (webm_track != null) { }
throw new IOException("tracks already selected");
} switch (webm.getAvailableTracks()[trackIndex].kind) {
case Audio:
switch (webm.getAvailableTracks()[trackIndex].kind) { case Video:
case Audio: break;
case Video: default:
break; throw new UnsupportedOperationException("the track must an audio or video stream");
default: }
throw new UnsupportedOperationException("the track must an audio or video stream");
} try {
webmTrack = webm.selectTrack(trackIndex);
try { } finally {
webm_track = webm.selectTrack(trackIndex); parsed = true;
} finally { }
parsed = true; }
}
} @Override
public void close() throws IOException {
@Override done = true;
public void close() throws IOException { parsed = true;
done = true;
parsed = true; webmTrack = null;
webm = null;
webm_track = null;
webm = null; if (!output.isClosed()) {
output.flush();
if (!output.isClosed()) { }
output.flush();
} source.close();
output.close();
source.close(); }
output.close();
} public void build() throws IOException {
float resolution;
public void build() throws IOException { SimpleBlock bloq;
float resolution; ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
SimpleBlock bloq; ByteBuffer page = ByteBuffer.allocate(64 * 1024);
ByteBuffer header = ByteBuffer.allocate(27 + (255 * 255));
ByteBuffer page = ByteBuffer.allocate(64 * 1024); header.order(ByteOrder.LITTLE_ENDIAN);
header.order(ByteOrder.LITTLE_ENDIAN); /* step 1: get the amount of frames per seconds */
switch (webmTrack.kind) {
/* step 1: get the amount of frames per seconds */ case Audio:
switch (webm_track.kind) { resolution = getSampleFrequencyFromTrack(webmTrack.bMetadata);
case Audio: if (resolution == 0f) {
resolution = getSampleFrequencyFromTrack(webm_track.bMetadata); throw new RuntimeException("cannot get the audio sample rate");
if (resolution == 0f) { }
throw new RuntimeException("cannot get the audio sample rate"); break;
} case Video:
break; // WARNING: untested
case Video: if (webmTrack.defaultDuration == 0) {
// WARNING: untested throw new RuntimeException("missing default frame time");
if (webm_track.defaultDuration == 0) { }
throw new RuntimeException("missing default frame time"); resolution = 1000f / ((float) webmTrack.defaultDuration
} / webmSegment.info.timecodeScale);
resolution = 1000f / ((float) webm_track.defaultDuration / webm_segment.info.timecodeScale); break;
break; default:
default: throw new RuntimeException("not implemented");
throw new RuntimeException("not implemented"); }
}
/* step 2: create packet with code init data */
/* step 2: create packet with code init data */ if (webmTrack.codecPrivate != null) {
if (webm_track.codecPrivate != null) { addPacketSegment(webmTrack.codecPrivate.length);
addPacketSegment(webm_track.codecPrivate.length); makePacketheader(0x00, header, webmTrack.codecPrivate);
make_packetHeader(0x00, header, webm_track.codecPrivate); write(header);
write(header); output.write(webmTrack.codecPrivate);
output.write(webm_track.codecPrivate); }
}
/* step 3: create packet with metadata */
/* step 3: create packet with metadata */ byte[] buffer = makeMetadata();
byte[] buffer = make_metadata(); if (buffer != null) {
if (buffer != null) { addPacketSegment(buffer.length);
addPacketSegment(buffer.length); makePacketheader(0x00, header, buffer);
make_packetHeader(0x00, header, buffer); write(header);
write(header); output.write(buffer);
output.write(buffer); }
}
/* step 4: calculate amount of packets */
/* step 4: calculate amount of packets */ while (webmSegment != null) {
while (webm_segment != null) { bloq = getNextBlock();
bloq = getNextBlock();
if (bloq != null && addPacketSegment(bloq)) {
if (bloq != null && addPacketSegment(bloq)) { int pos = page.position();
int pos = page.position(); //noinspection ResultOfMethodCallIgnored
//noinspection ResultOfMethodCallIgnored bloq.data.read(page.array(), pos, bloq.dataSize);
bloq.data.read(page.array(), pos, bloq.dataSize); page.position(pos + bloq.dataSize);
page.position(pos + bloq.dataSize); continue;
continue; }
}
// calculate the current packet duration using the next block
// calculate the current packet duration using the next block double elapsedNs = webmTrack.codecDelay;
double elapsed_ns = webm_track.codecDelay;
if (bloq == null) {
if (bloq == null) { packetFlag = FLAG_LAST; // note: if the flag is FLAG_CONTINUED, is changed
packet_flag = FLAG_LAST;// note: if the flag is FLAG_CONTINUED, is changed elapsedNs += webmBlockLastTimecode;
elapsed_ns += webm_block_last_timecode;
if (webmTrack.defaultDuration > 0) {
if (webm_track.defaultDuration > 0) { elapsedNs += webmTrack.defaultDuration;
elapsed_ns += webm_track.defaultDuration; } else {
} else { // hardcoded way, guess the sample duration
// hardcoded way, guess the sample duration elapsedNs += webmBlockNearDuration;
elapsed_ns += webm_block_near_duration; }
} } else {
} else { elapsedNs += bloq.absoluteTimeCodeNs;
elapsed_ns += bloq.absoluteTimeCodeNs; }
}
// get the sample count in the page
// get the sample count in the page elapsedNs = elapsedNs / TIME_SCALE_NS;
elapsed_ns = elapsed_ns / TIME_SCALE_NS; elapsedNs = Math.ceil(elapsedNs * resolution);
elapsed_ns = Math.ceil(elapsed_ns * resolution);
// create header and calculate page checksum
// create header and calculate page checksum int checksum = makePacketheader((long) elapsedNs, header, null);
int checksum = make_packetHeader((long) elapsed_ns, header, null); checksum = calcCrc32(checksum, page.array(), page.position());
checksum = calc_crc32(checksum, page.array(), page.position());
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
header.putInt(HEADER_CHECKSUM_OFFSET, checksum);
// dump data
// dump data write(header);
write(header); write(page);
write(page);
webmBlock = bloq;
webm_block = bloq; }
} }
}
private int makePacketheader(final long granPos, @NonNull final ByteBuffer buffer,
private int make_packetHeader(long gran_pos, @NonNull ByteBuffer buffer, byte[] immediate_page) { final byte[] immediatePage) {
short length = HEADER_SIZE; short length = HEADER_SIZE;
buffer.putInt(0x5367674f);// "OggS" binary string in little-endian buffer.putInt(0x5367674f); // "OggS" binary string in little-endian
buffer.put((byte) 0x00);// version buffer.put((byte) 0x00); // version
buffer.put(packet_flag);// type buffer.put(packetFlag); // type
buffer.putLong(gran_pos);// granulate position buffer.putLong(granPos); // granulate position
buffer.putInt(STREAM_ID);// bitstream serial number buffer.putInt(streamId); // bitstream serial number
buffer.putInt(sequence_count++);// page sequence number buffer.putInt(sequenceCount++); // page sequence number
buffer.putInt(0x00);// page checksum buffer.putInt(0x00); // page checksum
buffer.put((byte) segment_table_size);// segment table buffer.put((byte) segmentTableSize); // segment table
buffer.put(segment_table, 0, segment_table_size);// segment size buffer.put(segmentTable, 0, segmentTableSize); // segment size
length += segment_table_size; length += segmentTableSize;
clearSegmentTable();// clear segment table for next header clearSegmentTable(); // clear segment table for next header
int checksum_crc32 = calc_crc32(0x00, buffer.array(), length); int checksumCrc32 = calcCrc32(0x00, buffer.array(), length);
if (immediate_page != null) { if (immediatePage != null) {
checksum_crc32 = calc_crc32(checksum_crc32, immediate_page, immediate_page.length); checksumCrc32 = calcCrc32(checksumCrc32, immediatePage, immediatePage.length);
buffer.putInt(HEADER_CHECKSUM_OFFSET, checksum_crc32); buffer.putInt(HEADER_CHECKSUM_OFFSET, checksumCrc32);
segment_table_next_timestamp -= TIME_SCALE_NS; segmentTableNextTimestamp -= TIME_SCALE_NS;
} }
return checksum_crc32; return checksumCrc32;
} }
@Nullable @Nullable
private byte[] make_metadata() { private byte[] makeMetadata() {
if ("A_OPUS".equals(webm_track.codecId)) { if ("A_OPUS".equals(webmTrack.codecId)) {
return new byte[]{ return new byte[]{
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73,// "OpusTags" binary string 0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
0x07, 0x00, 0x00, 0x00,// writting application string size 0x07, 0x00, 0x00, 0x00, // writing application string size
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
0x00, 0x00, 0x00, 0x00// additional tags count (zero means no tags) 0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
}; };
} else if ("A_VORBIS".equals(webm_track.codecId)) { } else if ("A_VORBIS".equals(webmTrack.codecId)) {
return new byte[]{ return new byte[]{
0x03,// ???????? 0x03, // ????????
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73,// "vorbis" binary string 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
0x07, 0x00, 0x00, 0x00,// writting application string size 0x07, 0x00, 0x00, 0x00, // writting application string size
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
0x01, 0x00, 0x00, 0x00,// additional tags count (zero means no tags) 0x01, 0x00, 0x00, 0x00, // additional tags count (zero means no tags)
/* /*
// whole file duration (not implemented) // whole file duration (not implemented)
0x44,// tag string size 0x44,// tag string size
0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4F, 0x4E, 0x3D, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30,
0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30 0x30, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
*/ */
0x0F,// tag string size 0x0F, // tag string size
0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D,// "ENCODER=" binary string 0x00, 0x00, 0x00, 0x45, 0x4E, 0x43, 0x4F, 0x44, 0x45, 0x52, 0x3D, // "ENCODER=" binary string
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65,// "NewPipe" binary string 0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65, // "NewPipe" binary string
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00// ???????? 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // ????????
}; };
} }
// not implemented for the desired codec // not implemented for the desired codec
return null; return null;
} }
private void write(ByteBuffer buffer) throws IOException { private void write(final ByteBuffer buffer) throws IOException {
output.write(buffer.array(), 0, buffer.position()); output.write(buffer.array(), 0, buffer.position());
buffer.position(0); buffer.position(0);
} }
@Nullable
private SimpleBlock getNextBlock() throws IOException {
@Nullable SimpleBlock res;
private SimpleBlock getNextBlock() throws IOException {
SimpleBlock res; if (webmBlock != null) {
res = webmBlock;
if (webm_block != null) { webmBlock = null;
res = webm_block; return res;
webm_block = null; }
return res;
} if (webmSegment == null) {
webmSegment = webm.getNextSegment();
if (webm_segment == null) { if (webmSegment == null) {
webm_segment = webm.getNextSegment(); return null; // no more blocks in the selected track
if (webm_segment == null) { }
return null;// no more blocks in the selected track }
}
} if (webmCluster == null) {
webmCluster = webmSegment.getNextCluster();
if (webm_cluster == null) { if (webmCluster == null) {
webm_cluster = webm_segment.getNextCluster(); webmSegment = null;
if (webm_cluster == null) { return getNextBlock();
webm_segment = null; }
return getNextBlock(); }
}
} res = webmCluster.getNextSimpleBlock();
if (res == null) {
res = webm_cluster.getNextSimpleBlock(); webmCluster = null;
if (res == null) { return getNextBlock();
webm_cluster = null; }
return getNextBlock();
} webmBlockNearDuration = res.absoluteTimeCodeNs - webmBlockLastTimecode;
webmBlockLastTimecode = res.absoluteTimeCodeNs;
webm_block_near_duration = res.absoluteTimeCodeNs - webm_block_last_timecode;
webm_block_last_timecode = res.absoluteTimeCodeNs; return res;
}
return res;
} private float getSampleFrequencyFromTrack(final byte[] bMetadata) {
// hardcoded way
private float getSampleFrequencyFromTrack(byte[] bMetadata) { ByteBuffer buffer = ByteBuffer.wrap(bMetadata);
// hardcoded way
ByteBuffer buffer = ByteBuffer.wrap(bMetadata); while (buffer.remaining() >= 6) {
int id = buffer.getShort() & 0xFFFF;
while (buffer.remaining() >= 6) { if (id == 0x0000B584) {
int id = buffer.getShort() & 0xFFFF; return buffer.getFloat();
if (id == 0x0000B584) { }
return buffer.getFloat(); }
}
} return 0f;
}
return 0f;
} private void clearSegmentTable() {
segmentTableNextTimestamp += TIME_SCALE_NS;
private void clearSegmentTable() { packetFlag = FLAG_UNSET;
segment_table_next_timestamp += TIME_SCALE_NS; segmentTableSize = 0;
packet_flag = FLAG_UNSET; }
segment_table_size = 0;
} private boolean addPacketSegment(final SimpleBlock block) {
long timestamp = block.absoluteTimeCodeNs + webmTrack.codecDelay;
private boolean addPacketSegment(SimpleBlock block) {
long timestamp = block.absoluteTimeCodeNs + webm_track.codecDelay; if (timestamp >= segmentTableNextTimestamp) {
return false;
if (timestamp >= segment_table_next_timestamp) { }
return false;
} return addPacketSegment(block.dataSize);
}
return addPacketSegment(block.dataSize);
} private boolean addPacketSegment(int size) {
if (size > 65025) {
private boolean addPacketSegment(int size) { throw new UnsupportedOperationException("page size cannot be larger than 65025");
if (size > 65025) { }
throw new UnsupportedOperationException("page size cannot be larger than 65025");
} int available = (segmentTable.length - segmentTableSize) * 255;
boolean extra = (size % 255) == 0;
int available = (segment_table.length - segment_table_size) * 255;
boolean extra = (size % 255) == 0; if (extra) {
// add a zero byte entry in the table
if (extra) { // required to indicate the sample size is multiple of 255
// add a zero byte entry in the table available -= 255;
// required to indicate the sample size is multiple of 255 }
available -= 255;
} // check if possible add the segment, without overflow the table
if (available < size) {
// check if possible add the segment, without overflow the table return false; // not enough space on the page
if (available < size) { }
return false;// not enough space on the page
} for (; size > 0; size -= 255) {
segmentTable[segmentTableSize++] = (byte) Math.min(size, 255);
for (; size > 0; size -= 255) { }
segment_table[segment_table_size++] = (byte) Math.min(size, 255);
} if (extra) {
segmentTable[segmentTableSize++] = 0x00;
if (extra) { }
segment_table[segment_table_size++] = 0x00;
} return true;
}
return true;
} private void populateCrc32Table() {
for (int i = 0; i < 0x100; i++) {
private void populate_crc32_table() { int crc = i << 24;
for (int i = 0; i < 0x100; i++) { for (int j = 0; j < 8; j++) {
int crc = i << 24; long b = crc >>> 31;
for (int j = 0; j < 8; j++) { crc <<= 1;
long b = crc >>> 31; crc ^= (int) (0x100000000L - b) & 0x04c11db7;
crc <<= 1; }
crc ^= (int) (0x100000000L - b) & 0x04c11db7; crc32Table[i] = crc;
} }
crc32_table[i] = crc; }
}
} private int calcCrc32(int initialCrc, final byte[] buffer, final int size) {
for (int i = 0; i < size; i++) {
private int calc_crc32(int initial_crc, byte[] buffer, int size) { int reg = (initialCrc >>> 24) & 0xff;
for (int i = 0; i < size; i++) { initialCrc = (initialCrc << 8) ^ crc32Table[reg ^ (buffer[i] & 0xff)];
int reg = (initial_crc >>> 24) & 0xff; }
initial_crc = (initial_crc << 8) ^ crc32_table[reg ^ (buffer[i] & 0xff)];
} return initialCrc;
}
return initial_crc; }
}
}

View File

@ -26,18 +26,19 @@ public class SrtFromTtmlWriter {
private int frameIndex = 0; private int frameIndex = 0;
public SrtFromTtmlWriter(SharpStream out, boolean ignoreEmptyFrames) { public SrtFromTtmlWriter(final SharpStream out, final boolean ignoreEmptyFrames) {
this.out = out; this.out = out;
this.ignoreEmptyFrames = ignoreEmptyFrames; this.ignoreEmptyFrames = ignoreEmptyFrames;
} }
private static String getTimestamp(Element frame, String attr) { private static String getTimestamp(final Element frame, final String attr) {
return frame return frame
.attr(attr) .attr(attr)
.replace('.', ',');// SRT subtitles uses comma as decimal separator .replace('.', ','); // SRT subtitles uses comma as decimal separator
} }
private void writeFrame(String begin, String end, StringBuilder text) throws IOException { private void writeFrame(final String begin, final String end, final StringBuilder text)
throws IOException {
writeString(String.valueOf(frameIndex++)); writeString(String.valueOf(frameIndex++));
writeString(NEW_LINE); writeString(NEW_LINE);
writeString(begin); writeString(begin);
@ -49,11 +50,11 @@ public class SrtFromTtmlWriter {
writeString(NEW_LINE); writeString(NEW_LINE);
} }
private void writeString(String text) throws IOException { private void writeString(final String text) throws IOException {
out.write(text.getBytes(charset)); out.write(text.getBytes(charset));
} }
public void build(SharpStream ttml) throws IOException { public void build(final SharpStream ttml) throws IOException {
/* /*
* TTML parser with BASIC support * TTML parser with BASIC support
* multiple CUE is not supported * multiple CUE is not supported
@ -66,25 +67,32 @@ public class SrtFromTtmlWriter {
// parse XML // parse XML
byte[] buffer = new byte[(int) ttml.available()]; byte[] buffer = new byte[(int) ttml.available()];
ttml.read(buffer); ttml.read(buffer);
Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "", Parser.xmlParser()); Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "",
Parser.xmlParser());
StringBuilder text = new StringBuilder(128); StringBuilder text = new StringBuilder(128);
Elements paragraph_list = doc.select("body > div > p"); Elements paragraphList = doc.select("body > div > p");
// check if has frames // check if has frames
if (paragraph_list.size() < 1) return; if (paragraphList.size() < 1) {
return;
}
for (Element paragraph : paragraph_list) { for (Element paragraph : paragraphList) {
text.setLength(0); text.setLength(0);
for (Node children : paragraph.childNodes()) { for (Node children : paragraph.childNodes()) {
if (children instanceof TextNode) if (children instanceof TextNode) {
text.append(((TextNode) children).text()); text.append(((TextNode) children).text());
else if (children instanceof Element && ((Element) children).tagName().equalsIgnoreCase("br")) } else if (children instanceof Element
&& ((Element) children).tagName().equalsIgnoreCase("br")) {
text.append(NEW_LINE); text.append(NEW_LINE);
}
} }
if (ignoreEmptyFrames && text.length() < 1) continue; if (ignoreEmptyFrames && text.length() < 1) {
continue;
}
String begin = getTimestamp(paragraph, "begin"); String begin = getTimestamp(paragraph, "begin");
String end = getTimestamp(paragraph, "end"); String end = getTimestamp(paragraph, "end");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +1,62 @@
package org.schabi.newpipe.streams.io; package org.schabi.newpipe.streams.io;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
/** /**
* based on c# * Based on C#'s Stream class.
*/ */
public abstract class SharpStream implements Closeable { public abstract class SharpStream implements Closeable {
public abstract int read() throws IOException;
public abstract int read() throws IOException;
public abstract int read(byte[] buffer) throws IOException;
public abstract int read(byte buffer[]) throws IOException;
public abstract int read(byte[] buffer, int offset, int count) throws IOException;
public abstract int read(byte buffer[], int offset, int count) throws IOException;
public abstract long skip(long amount) throws IOException;
public abstract long skip(long amount) throws IOException;
public abstract long available();
public abstract long available();
public abstract void rewind() throws IOException;
public abstract void rewind() throws IOException;
public abstract boolean isClosed();
public abstract boolean isClosed();
@Override
@Override public abstract void close();
public abstract void close();
public abstract boolean canRewind();
public abstract boolean canRewind();
public abstract boolean canRead();
public abstract boolean canRead();
public abstract boolean canWrite();
public abstract boolean canWrite();
public boolean canSetLength() {
public boolean canSetLength() { return false;
return false; }
}
public boolean canSeek() {
public boolean canSeek() { return false;
return false; }
}
public abstract void write(byte value) throws IOException;
public abstract void write(byte value) throws IOException;
public abstract void write(byte[] buffer) throws IOException;
public abstract void write(byte[] buffer) throws IOException;
public abstract void write(byte[] buffer, int offset, int count) throws IOException;
public abstract void write(byte[] buffer, int offset, int count) throws IOException;
public void flush() throws IOException {
public void flush() throws IOException { // STUB
// STUB }
}
public void setLength(final long length) throws IOException {
public void setLength(long length) throws IOException { throw new IOException("Not implemented");
throw new IOException("Not implemented"); }
}
public void seek(final long offset) throws IOException {
public void seek(long offset) throws IOException { throw new IOException("Not implemented");
throw new IOException("Not implemented"); }
}
public long length() throws IOException {
public long length() throws IOException { throw new UnsupportedOperationException("Unsupported operation");
throw new UnsupportedOperationException("Unsupported operation"); }
} }
}

View File

@ -6,7 +6,38 @@
<suppress checks="FinalParameters" <suppress checks="FinalParameters"
files="LocalItemListAdapter.java" files="LocalItemListAdapter.java"
lines="220,292"/> lines="220,292"/>
<suppress checks="FinalParameters" <suppress checks="FinalParameters"
files="InfoListAdapter.java" files="InfoListAdapter.java"
lines="253,325"/> lines="253,325"/>
<!-- org.schabi.newpipe.streams -->
<suppress checks="FinalParameters"
files="WebMWriter.java"
lines="423,595"/>
<suppress checks="LineLength"
files="WebMWriter.java"
lines="160,162"/>
<suppress checks="FinalParameters"
files="OggFromWebMWriter.java"
lines="378,420"/>
<suppress checks="LineLength"
files="OggFromWebMWriter.java"
lines="292,296"/>
<suppress checks="FinalParameters"
files="Mp4FromDashWriter.java"
lines="643"/>
<suppress checks="LineLength"
files="Mp4FromDashWriter.java"
lines="677,678,720-724,738,762,848,850-855"/>
<suppress checks="InnerAssignment"
files="Mp4DashReader.java"
lines="190"/>
<suppress checks="FinalParameters"
files="DataReader.java"
lines="46,93"/>
</suppressions> </suppressions>