Skip to content

Commit 93b9ca6

Browse files
committed
Add more metadata
1 parent 761fe4f commit 93b9ca6

File tree

1 file changed

+71
-45
lines changed

1 file changed

+71
-45
lines changed

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

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.schabi.newpipe.streams;
22

33
import static org.schabi.newpipe.MainActivity.DEBUG;
4+
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
45

56
import android.graphics.Bitmap;
67
import android.util.Log;
@@ -9,6 +10,8 @@
910
import androidx.annotation.NonNull;
1011
import androidx.annotation.Nullable;
1112

13+
import org.schabi.newpipe.extractor.localization.DateWrapper;
14+
import org.schabi.newpipe.extractor.stream.SongMetadata;
1215
import org.schabi.newpipe.extractor.stream.StreamInfo;
1316
import org.schabi.newpipe.streams.WebMReader.Cluster;
1417
import org.schabi.newpipe.streams.WebMReader.Segment;
@@ -40,12 +43,18 @@
4043
* </p>
4144
* <ul>
4245
* <li>FLAC: <a href="https://www.rfc-editor.org/rfc/rfc9639">RFC 9639</a></li>
46+
* <li>
47+
* Vorbis: <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html">Vorbis I</a>.
48+
* <br>
49+
* Vorbis uses FLAC picture blocks for embedding cover art in the metadata.
50+
* </li>
4351
* <li>Opus: All specs can be found at <a href="https://opus-codec.org/docs/">
4452
* https://opus-codec.org/docs/</a>.
4553
* <a href="https://datatracker.ietf.org/doc/html/rfc7845.html">RFC7845</a>
4654
* defines the Ogg encapsulation for Opus streams, i.e.the container format and metadata.
55+
* <br>
56+
* Opus uses multiple Vorbis I features, e.g. the comment header format for metadata.
4757
* </li>
48-
* <li>Vorbis: <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html">Vorbis I</a></li>
4958
* </ul>
5059
*
5160
* @author kapodamy
@@ -352,8 +361,8 @@ private int makePacketHeader(final long granPos, @NonNull final ByteBuffer buffe
352361
* @see <a href="https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2">
353362
* Vorbis I 4.2. Header decode and decode setup</a> and
354363
* <a href="https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-820005">
355-
* Vorbis 5. comment field and header specification</a>
356-
* for VORBIS metadata header format
364+
* Vorbis I 5. comment field and header specification</a>
365+
* for VORBIS metadata header format. Vorbis I 5. lists all the possible metadata tags.
357366
*
358367
* @return the metadata header as a byte array, or null if the codec is not supported
359368
* for metadata generation
@@ -364,38 +373,62 @@ private byte[] makeCommentHeader() {
364373
Log.d(TAG, "Downloading media with codec ID " + webmTrack.codecId);
365374
}
366375

367-
if ("A_OPUS".equals(webmTrack.codecId)) {
368-
final var metadata = new ArrayList<Pair<String, String>>();
369-
if (streamInfo != null) {
370-
metadata.add(Pair.create("COMMENT", streamInfo.getUrl()));
371-
metadata.add(Pair.create("GENRE", streamInfo.getCategory()));
372-
metadata.add(Pair.create("ARTIST", streamInfo.getUploaderName()));
373-
metadata.add(Pair.create("TITLE", streamInfo.getName()));
374-
metadata.add(Pair.create("DATE", streamInfo
375-
.getUploadDate()
376-
.getLocalDateTime()
377-
.format(DateTimeFormatter.ISO_DATE)));
378-
if (thumbnail != null) {
379-
metadata.add(makeFlacPictureTag(thumbnail));
380-
}
376+
final var metadata = new ArrayList<Pair<String, String>>();
377+
if (streamInfo != null) {
378+
final SongMetadata songMetadata = streamInfo.getSongMetadata();
379+
// metadata that can be present in the stream info and the song metadata.
380+
// Use the song metadata if available, otherwise fallback to stream info.
381+
metadata.add(Pair.create("COMMENT", streamInfo.getUrl()));
382+
metadata.add(Pair.create("GENRE", streamInfo.getCategory()));
383+
final String artist = songMetadata != null && !isNullOrEmpty(songMetadata.artist)
384+
? songMetadata.artist : streamInfo.getUploaderName();
385+
metadata.add(Pair.create("ARTIST", artist));
386+
final String title = songMetadata != null && !isNullOrEmpty(songMetadata.title)
387+
? songMetadata.title : streamInfo.getName();
388+
metadata.add(Pair.create("TITLE", title));
389+
final DateWrapper date = songMetadata != null && songMetadata.releaseDate != null
390+
? songMetadata.releaseDate : streamInfo.getUploadDate();
391+
metadata.add(Pair.create("DATE", date.getLocalDateTime()
392+
.format(DateTimeFormatter.ISO_DATE)));
393+
// Additional metadata that is only present in the song metadata
394+
if (songMetadata != null) {
395+
metadata.add(Pair.create("ALBUM", songMetadata.album));
396+
if (songMetadata.track != SongMetadata.TRACK_UNKNOWN) {
397+
// TRACKNUMBER is suggested in Vorbis spec,
398+
// but TRACK is more commonly used in practice
399+
metadata.add(Pair.create("TRACKNUMBER", String.valueOf(songMetadata.track)));
400+
metadata.add(Pair.create("TRACK", String.valueOf(songMetadata.track)));
401+
}
402+
metadata.add(Pair.create("PERFORMER", String.join(", ", songMetadata.performer)));
403+
metadata.add(Pair.create("ORGANIZATION", songMetadata.label));
404+
metadata.add(Pair.create("COPYRIGHT", songMetadata.copyright));
381405
}
382-
383-
if (DEBUG) {
384-
Log.d(TAG, "Creating metadata header with this data:");
385-
metadata.forEach(p -> Log.d(TAG, p.first + "=" + p.second));
406+
// Add thumbnail as cover art at the end because it is the largest metadata entry
407+
if (thumbnail != null) {
408+
metadata.add(makeFlacPictureTag(thumbnail));
386409
}
410+
}
411+
412+
if (DEBUG) {
413+
Log.d(TAG, "Creating metadata header with this data:");
414+
metadata.forEach(p -> Log.d(TAG, p.first + "=" + p.second));
415+
}
387416

388-
return makeOpusTagsHeader(metadata);
417+
if ("A_OPUS".equals(webmTrack.codecId)) {
418+
// See RFC7845 5.2: https://datatracker.ietf.org/doc/html/rfc7845.html#section-5.2
419+
final byte[] identificationHeader = new byte[]{
420+
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
421+
0x00, 0x00, 0x00, 0x00, // vendor (aka. Encoder) string of length 0
422+
};
423+
return makeCommentHeader(metadata, identificationHeader);
389424
} else if ("A_VORBIS".equals(webmTrack.codecId)) {
390425
// See https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2
391-
// for the Vorbis comment header format
392-
// TODO: add Vorbis metadata: same as Opus, but with the Vorbis comment header format
393-
return new byte[]{
426+
final byte[] identificationHeader = new byte[]{
394427
0x03, // packet type for Vorbis comment header
395428
0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, // "vorbis" binary string
396-
0x00, 0x00, 0x00, 0x00, // writing application string size (not present)
397-
0x00, 0x00, 0x00, 0x00 // additional tags count (zero means no tags)
429+
0x00, 0x00, 0x00, 0x00, // vendor (aka. Encoder) string of length 0
398430
};
431+
return makeCommentHeader(metadata, identificationHeader);
399432
}
400433

401434
// not implemented for the desired codec
@@ -405,12 +438,12 @@ private byte[] makeCommentHeader() {
405438
/**
406439
* This creates a single metadata tag for use in opus metadata headers. It contains the four
407440
* byte string length field and includes the string as-is. This cannot be used independently,
408-
* but must follow a proper "OpusTags" header.
441+
* but must follow a proper Comment header.
409442
*
410443
* @param pair A key-value pair in the format "KEY=some value"
411444
* @return The binary data of the encoded metadata tag
412445
*/
413-
private static byte[] makeOpusMetadataTag(final Pair<String, String> pair) {
446+
private static byte[] makeVorbisMetadataTag(final Pair<String, String> pair) {
414447
final var keyValue = pair.first.toUpperCase() + "=" + pair.second.trim();
415448

416449
final var bytes = keyValue.getBytes();
@@ -486,24 +519,21 @@ private static Pair<String, String> makeFlacPictureTag(final Bitmap bitmap) {
486519
}
487520

488521
/**
489-
* This returns a complete "OpusTags" header, created from the provided metadata tags.
490-
* <p>
491-
* You probably want to use makeOpusMetadata(), which uses this function to create
492-
* a header with sensible metadata filled in.
493-
*
494-
* @ImplNote See <a href="https://datatracker.ietf.org/doc/html/rfc7845.html#section-5.2">
495-
* RFC7845 5.2</a>
522+
* This returns a complete Comment header, created from the provided metadata tags.
496523
*
497524
* @param keyValueLines A list of pairs of the tags. This can also be though of as a mapping
498525
* from one key to multiple values.
526+
* @param identificationHeader the identification header for the codec,
527+
* which is required to be prefixed to the comment header.
499528
* @return The binary header
500529
*/
501-
private static byte[] makeOpusTagsHeader(final List<Pair<String, String>> keyValueLines) {
530+
private static byte[] makeCommentHeader(final List<Pair<String, String>> keyValueLines,
531+
final byte[] identificationHeader) {
502532
final var tags = keyValueLines
503533
.stream()
504-
.filter(p -> !p.second.isBlank())
505-
.map(OggFromWebMWriter::makeOpusMetadataTag)
506-
.collect(Collectors.toUnmodifiableList());
534+
.filter(p -> p.second != null && !p.second.isBlank())
535+
.map(OggFromWebMWriter::makeVorbisMetadataTag)
536+
.toList();
507537

508538
final var tagsBytes = tags.stream().collect(Collectors.summingInt(arr -> arr.length));
509539

@@ -512,11 +542,7 @@ private static byte[] makeOpusTagsHeader(final List<Pair<String, String>> keyVal
512542

513543
final var head = ByteBuffer.allocate(byteCount);
514544
head.order(ByteOrder.LITTLE_ENDIAN);
515-
// See RFC7845 5.2: https://datatracker.ietf.org/doc/html/rfc7845.html#section-5.2
516-
head.put(new byte[]{
517-
0x4F, 0x70, 0x75, 0x73, 0x54, 0x61, 0x67, 0x73, // "OpusTags" binary string
518-
0x00, 0x00, 0x00, 0x00, // vendor (aka. Encoder) string of length 0
519-
});
545+
head.put(identificationHeader);
520546
head.putInt(tags.size()); // 4 bytes for tag count
521547
tags.forEach(head::put); // dynamic amount of tag bytes
522548

0 commit comments

Comments
 (0)