11package org .schabi .newpipe .streams ;
22
3+ import android .graphics .Bitmap ;
4+
35import org .schabi .newpipe .extractor .stream .StreamInfo ;
46import org .schabi .newpipe .streams .Mp4DashReader .Hdlr ;
57import org .schabi .newpipe .streams .Mp4DashReader .Mdia ;
1012import org .schabi .newpipe .streams .Mp4DashReader .TrunEntry ;
1113import org .schabi .newpipe .streams .io .SharpStream ;
1214
15+ import java .io .ByteArrayOutputStream ;
1316import java .io .IOException ;
1417import java .nio .ByteBuffer ;
1518import java .nio .charset .StandardCharsets ;
1619import java .util .ArrayList ;
1720
1821/**
22+ * MP4 muxer that builds a standard MP4 file from DASH fragmented MP4 sources.
23+ *
1924 * @author kapodamy
25+ *
26+ * @implNote See <a href="https://atomicparsley.sourceforge.net/mpeg-4files.html">
27+ * https://atomicparsley.sourceforge.net/mpeg-4files.html</a> for information on
28+ * the MP4 file format and its specification.
2029 */
2130public class Mp4FromDashWriter {
2231 private static final int EPOCH_OFFSET = 2082844800 ;
@@ -53,8 +62,10 @@ public class Mp4FromDashWriter {
5362 private final ArrayList <Integer > compatibleBrands = new ArrayList <>(5 );
5463
5564 private final StreamInfo streamInfo ;
65+ private final Bitmap thumbnail ;
5666
5767 public Mp4FromDashWriter (final StreamInfo streamInfo ,
68+ final Bitmap thumbnail ,
5869 final SharpStream ... sources ) throws IOException {
5970 for (final SharpStream src : sources ) {
6071 if (!src .canRewind () && !src .canRead ()) {
@@ -63,6 +74,7 @@ public Mp4FromDashWriter(final StreamInfo streamInfo,
6374 }
6475
6576 this .streamInfo = streamInfo ;
77+ this .thumbnail = thumbnail ;
6678 sourceTracks = sources ;
6779 readers = new Mp4DashReader [sourceTracks .length ];
6880 readersChunks = new Mp4DashChunk [readers .length ];
@@ -946,10 +958,23 @@ private void makeUdta() throws IOException {
946958 writeMetaItem ("©day" , date );
947959 }
948960
961+
962+
963+ if (thumbnail != null ) {
964+ final ByteArrayOutputStream baos = new ByteArrayOutputStream ();
965+ thumbnail .compress (Bitmap .CompressFormat .PNG , 100 , baos );
966+ final byte [] imgBytes = baos .toByteArray ();
967+ baos .close ();
968+ // 0x0000000E = PNG type indicator for 'data' box (0x0D = JPEG)
969+ writeMetaCover (imgBytes , 0x0000000E );
970+
971+ }
972+
949973 // fix lengths
950974 lengthFor (startIlst );
951975 lengthFor (startMeta );
952976 lengthFor (startUdta );
977+
953978 }
954979
955980 /**
@@ -997,9 +1022,9 @@ private void writeMetaItem(final String keyStr, final String value) throws IOExc
9971022 /**
9981023 * Create a minimal hdlr box for the meta container.
9991024 * The boxsize is fixed (33 bytes) as no name is provided.
1025+ * @return byte array with the hdlr box
10001026 */
10011027 private byte [] makeMetaHdlr () {
1002-
10031028 final ByteBuffer buf = ByteBuffer .allocate (33 );
10041029 buf .putInt (33 );
10051030 buf .putInt (0x68646C72 ); // "hdlr"
@@ -1011,6 +1036,52 @@ private byte[] makeMetaHdlr() {
10111036 return buf .array ();
10121037 }
10131038
1039+ /**
1040+ * Helper to write cover image inside the 'udta' box.
1041+ *
1042+ * <pre>
1043+ * [size][key] [data_box]
1044+ * data_box = [size]["data"][type(4bytes)][locale(4bytes)=0][payload]
1045+ * </pre>
1046+ *
1047+ * @param imageData image byte data
1048+ * @param dataType type indicator: 0x0000000E = PNG, 0x0000000D = JPEG
1049+ * @throws IOException
1050+ */
1051+ private void writeMetaCover (final byte [] imageData , final int dataType ) throws IOException {
1052+ if (imageData == null || imageData .length == 0 ) {
1053+ return ;
1054+ }
1055+
1056+ final byte [] keyBytes = "covr" .getBytes (StandardCharsets .ISO_8859_1 );
1057+
1058+ // data box: 4(size) + 4("data") + 4(type) + 4(locale) + payload
1059+ final int dataBoxSize = 16 + imageData .length ;
1060+ final int itemBoxSize = 8 + dataBoxSize ;
1061+
1062+ final ByteBuffer buf = ByteBuffer .allocate (itemBoxSize );
1063+ buf .putInt (itemBoxSize );
1064+
1065+ // key (4 chars)
1066+ if (keyBytes .length == 4 ) {
1067+ buf .put (keyBytes );
1068+ } else {
1069+ final byte [] kb = new byte [4 ];
1070+ System .arraycopy (keyBytes , 0 , kb , 0 , Math .min (keyBytes .length , 4 ));
1071+ buf .put (kb );
1072+ }
1073+
1074+ // data box
1075+ buf .putInt (dataBoxSize );
1076+ buf .putInt (0x64617461 ); // "data"
1077+ buf .putInt (dataType ); // type indicator: 0x0000000E = PNG, 0x0000000D = JPEG
1078+ buf .putInt (0x00000000 ); // locale
1079+ buf .put (imageData );
1080+
1081+ auxWrite (buf .array ());
1082+ }
1083+
1084+
10141085 static class TablesInfo {
10151086 int stts ;
10161087 int stsc ;
0 commit comments