Skip to content

Commit 650ea25

Browse files
committed
Add thumbnail / cover art to mp4 downloads
1 parent f332e23 commit 650ea25

File tree

3 files changed

+75
-3
lines changed

3 files changed

+75
-3
lines changed

app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.schabi.newpipe.streams;
22

3+
import android.graphics.Bitmap;
4+
35
import org.schabi.newpipe.extractor.stream.StreamInfo;
46
import org.schabi.newpipe.streams.Mp4DashReader.Hdlr;
57
import org.schabi.newpipe.streams.Mp4DashReader.Mdia;
@@ -10,13 +12,20 @@
1012
import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry;
1113
import org.schabi.newpipe.streams.io.SharpStream;
1214

15+
import java.io.ByteArrayOutputStream;
1316
import java.io.IOException;
1417
import java.nio.ByteBuffer;
1518
import java.nio.charset.StandardCharsets;
1619
import 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
*/
2130
public 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;

app/src/main/java/us/shandian/giga/postprocessing/M4aNoDash.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ boolean test(SharpStream... sources) throws IOException {
3030

3131
@Override
3232
int process(SharpStream out, SharpStream... sources) throws IOException {
33-
Mp4FromDashWriter muxer = new Mp4FromDashWriter(this.streamInfo, sources[0]);
33+
Mp4FromDashWriter muxer = new Mp4FromDashWriter(
34+
this.streamInfo, this.thumbnail, sources[0]);
3435
muxer.setMainBrand(0x4D344120);// binary string "M4A "
3536
muxer.parseSources();
3637
muxer.selectTracks(0);

app/src/main/java/us/shandian/giga/postprocessing/Mp4FromDashMuxer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class Mp4FromDashMuxer extends Postprocessing {
1616

1717
@Override
1818
int process(SharpStream out, SharpStream... sources) throws IOException {
19-
Mp4FromDashWriter muxer = new Mp4FromDashWriter(this.streamInfo, sources);
19+
Mp4FromDashWriter muxer = new Mp4FromDashWriter(this.streamInfo, this.thumbnail, sources);
2020
muxer.parseSources();
2121
muxer.selectTracks(0, 0);
2222
muxer.build(out);

0 commit comments

Comments
 (0)