feat(downloads): add explicit content rating support

- Write ITUNESADVISORY for FLAC
- Write rtng for M4A
- Read the corresponding values from local media
This commit is contained in:
Daniel 2026-02-28 02:31:25 +00:00 committed by GitHub
parent fa4da493e6
commit dcf8563254
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -161,6 +161,7 @@ async function readFlacMetadata(file, metadata) {
if (upperKey === 'ALBUM') metadata.album.title = value;
if (upperKey === 'ISRC') metadata.isrc = value;
if (upperKey === 'COPYRIGHT') metadata.copyright = value;
if (upperKey === 'ITUNESADVISORY') metadata.explicit = value === '1';
}
}
}
@ -262,6 +263,9 @@ async function readM4aMetadata(file, metadata) {
const mime = getMimeType(pictureData);
const blob = new Blob([pictureData], { type: mime });
metadata.album.cover = URL.createObjectURL(blob);
} else if (item.type === 'rtng') {
metadata.explicit =
contentLen > 0 && new Uint8Array(view.buffer, contentOffset, contentLen)[0] === 1;
}
}
}
@ -603,6 +607,9 @@ function createVorbisCommentBlock(track) {
if (track.isrc) {
comments.push(['ISRC', track.isrc]);
}
if (track.explicit) {
comments.push(['ITUNESADVISORY', '1']);
}
// Calculate total size
const vendor = VENDOR_STRING;
@ -943,6 +950,9 @@ function createMp4MetadataAtoms(track) {
total: track.album?.numberOfTracks,
};
}
if (track.explicit) {
tags['rtng'] = 1; // 1 = Explicit, 2 = Clean, 0 = Unknown
}
const discNumber = track.volumeNumber ?? track.discNumber;
if (discNumber) {
@ -1086,6 +1096,8 @@ function createMetadataBlock(metadataAtoms) {
for (const [key, value] of Object.entries(tags)) {
if (key === 'trkn' || key === 'disk') {
ilstChildren.push(createIntAtom(key, value));
} else if (key === 'rtng') {
ilstChildren.push(createRatingAtom(value));
} else {
ilstChildren.push(createStringAtom(key, value));
}
@ -1218,6 +1230,42 @@ function createStringAtom(type, value) {
return buf;
}
/**
* Constructs an MP4 `rtng` metadata atom that encodes an explicit-content rating.
*
* @param {number} value - The rating to embed (0 = Unrated, 1 = Explicit, 2 = Clean).
* @returns {Uint8Array} The serialized atom buffer ready to be inserted into metadata.
*/
function createRatingAtom(value) {
const dataSize = 17; // 8 (data atom header) + 8 (flags/null) + Rating
const atomSize = 8 + dataSize;
const buf = new Uint8Array(atomSize);
let offset = 0;
// Wrapper atom (e.g., ©nam)
writeAtomHeader(buf, offset, atomSize, 'rtng');
offset += 8;
// Data atom
writeAtomHeader(buf, offset, dataSize, 'data');
offset += 8;
// Data Type ((21 = Rating) + Locale (0))
buf[offset++] = 0;
buf[offset++] = 0;
buf[offset++] = 0;
buf[offset++] = 21; // Type 21
buf[offset++] = 0;
buf[offset++] = 0;
buf[offset++] = 0;
buf[offset++] = 0;
buf[offset++] = value;
return buf;
}
function createIntAtom(type, value) {
// trkn/disk are special: data is 8 bytes.
// reserved(2) + track(2) + total(2) + reserved(2)