Task 371: .M4P File Format
Task 371: .M4P File Format
1. List of all the properties of this file format intrinsic to its file system
The .M4P file format is a variant of the MPEG-4 Part 14 (MP4) container, specifically for AAC audio with Apple's FairPlay DRM protection. Its structure is box-based (atoms), with intrinsic properties referring to the metadata fields stored within the file's box hierarchy (e.g., under moov > udta > meta > ilst). These properties include standard audio metadata and DRM-specific fields. Based on specifications from MPEG-4 standards, Apple documentation, and tools like AtomicParsley and Mutagen, here is a comprehensive list of properties (metadata atoms), including their 4-byte codes, names, and descriptions:
- ©nam (Title): The track title (text).
- ©alb (Album): The album name (text).
- ©ART or ©art (Artist): The artist name (text).
- aART (Album Artist): The album artist name (text).
- ©wrt (Composer): The composer name (text).
- ©day (Year): The release year (text).
- ©cmt (Comment): User comments (text).
- desc (Description): Short description, often used in podcasts (text).
- purd (Purchase Date): Date of purchase (text).
- ©grp (Grouping): Content grouping (text).
- ©gen (Genre): Custom genre name (text).
- ©lyr (Lyrics): Song lyrics (text).
- purl (Podcast URL): URL for podcast episode (text, or binary).
- egid (Episode Global Unique ID): Unique ID for podcast episode (text or binary).
- catg (Category): Podcast category (text).
- keyw (Keyword): Podcast keywords (text).
- ©too (Encoded By): Encoder tool name (text).
- cprt (Copyright): Copyright notice (text).
- soal (Album Sort Order): Sort order for album (text).
- soaa (Album Artist Sort Order): Sort order for album artist (text).
- soar (Artist Sort Order): Sort order for artist (text).
- sonm (Title Sort Order): Sort order for title (text).
- soco (Composer Sort Order): Sort order for composer (text).
- sosn (Show Sort Order): Sort order for show name (text).
- tvsh (TV Show Name): TV show name (text).
- ©wrk (Work): Work name (text).
- ©mvn (Movement): Movement name (text).
- cpil (Compilation): Flag indicating part of a compilation (boolean, uint8).
- pgap (Gapless Playback): Flag for gapless playback (boolean, uint8).
- pcst (Podcast): Flag indicating podcast content (boolean, uint8).
- trkn (Track Number): Track number and total tracks (tuple of ints).
- disk (Disk Number): Disk number and total disks (tuple of ints).
- tmpo (BPM): Beats per minute (integer).
- ©mvc (Movement Count): Total movements (integer).
- ©mvi (Movement Index): Movement number (integer).
- shwm (Show Movement): Show movement flag (integer).
- stik (Media Kind): Media type indicator (integer).
- hdvd (HD Video): HD video flag (integer, though .M4P is audio-only).
- rtng (Content Rating/Advisory): Content rating (integer, uint8).
- tves (TV Episode): TV episode number (integer).
- tvsn (TV Season): TV season number (integer).
- covr (Artwork): Cover art image (binary, JPEG or PNG).
- gnre (Genre ID): ID3v1-style genre ID (integer, not recommended; use ©gen instead).
- apID (Apple ID): Purchaser's Apple account email (text, DRM-specific).
- atID (Account Type ID): Account type identifier (integer, DRM-specific).
- cnID (Catalog/Track ID): Track catalog ID (integer, DRM-specific).
- plID (Playlist/Album ID): Album or playlist ID (integer, DRM-specific).
- geID (Genre ID): Numeric genre ID (integer, DRM-specific).
- sfID (Store Front ID): Store country code (integer, DRM-specific).
- cmID (Composer ID): Composer ID (integer, DRM-specific).
- akID (Account Kind): Account kind (integer, DRM-specific).
- ---- (Freeform/Custom): Custom metadata (text or binary; format: ----:mean:name, e.g., com.apple.iTunes:custom_key).
These properties are stored in the file's box structure and are intrinsic to .M4P files, allowing for metadata management without decrypting the audio data. DRM protection is handled via the 'sinf' box (protection information) under the sample description ('stsd'), with scheme_type 'itms' for FairPlay in legacy downloaded files.
2. Two direct download links for files of format .M4P
.M4P files are DRM-protected and typically not freely distributed publicly, as they are licensed content from iTunes/Apple Music (Apple discontinued selling new DRM-protected music in 2009, but older files exist). Extensive searches across web and X did not yield public direct download links for genuine .M4P samples, as they would violate DRM terms. For testing, you can purchase and download .M4P files via iTunes if you have legacy protected content. As alternatives for similar MP4 audio structure (rename to .m4p for testing parsing, but without DRM):
- https://samplelib.com/lib/download/mp4/sample-3s.mp4
- https://file-examples.com/wp-content/uploads/2017/11/file_example_MP4_480_1_5MG.mp4
3. Ghost blog embedded html javascript that allows a user to drag n drop a file of format .M4P and it will dump to screen all these properties
Here is a self-contained HTML page with embedded JavaScript for a drag-and-drop interface. Drop a .M4P file onto the designated area, and it will parse the box structure, extract the metadata properties from 'ilst', and dump them to the screen in a readable format. This can be embedded in a Ghost blog post as raw HTML.
4. Python class that can open any file of format .M4P and decode read and write and print to console all the properties from the above list
Here is a Python class using built-in modules (no external libs needed). It opens a .M4P file, decodes the box structure, reads the properties, prints them to console, and can write the parsed file back (as a copy, without modification).
import struct
import sys
class M4PParser:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
self.buffer = None
def read(self):
with open(self.filepath, 'rb') as f:
self.buffer = f.read()
self._parse_properties()
return self.properties
def _parse_properties(self):
offset = 0
buffer = self.buffer
while offset < len(buffer):
size, type_ = struct.unpack('>I4s', buffer[offset:offset+8])
type_ = type_.decode('utf-8')
offset += 8
if type_ == 'moov':
moov_end = offset + size - 8
while offset < moov_end:
sub_size, sub_type = struct.unpack('>I4s', buffer[offset:offset+8])
sub_type = sub_type.decode('utf-8')
offset += 8
if sub_type == 'udta':
udta_end = offset + sub_size - 8
while offset < udta_end:
meta_size, meta_type = struct.unpack('>I4s', buffer[offset:offset+8])
meta_type = meta_type.decode('utf-8')
offset += 8
if meta_type == 'meta':
offset += 4 # Skip version/flags
meta_end = offset + meta_size - 12
while offset < meta_end:
ilst_size, ilst_type = struct.unpack('>I4s', buffer[offset:offset+8])
ilst_type = ilst_type.decode('utf-8')
offset += 8
if ilst_type == 'ilst':
ilst_end = offset + ilst_size - 8
while offset < ilst_end:
prop_size, prop_type = struct.unpack('>I4s', buffer[offset:offset+8])
prop_type = prop_type.decode('utf-8')
offset += 8
data_size, data_type = struct.unpack('>I4s', buffer[offset:offset+8])
data_type = data_type.decode('utf-8')
offset += 8
if data_type == 'data':
offset += 8 # Skip type/flags/locale
value_length = data_size - 16
value = self._get_value(buffer[offset:offset+value_length], prop_type)
self.properties[prop_type] = value
offset += value_length
else:
offset += data_size - 8
else:
offset += ilst_size - 8
else:
offset += meta_size - 8
else:
offset += sub_size - 8
else:
offset += size - 8
def _get_value(self, data, prop_type):
if prop_type in ['covr']:
return f'Binary data (length: {len(data)})'
elif prop_type in ['trkn', 'disk']:
_, num, total, _ = struct.unpack('>HHH2x', data)
return (num, total)
elif prop_type in ['tmpo', 'rtng', 'stik', 'hdvd', 'tves', 'tvsn']:
return struct.unpack('>I', data)[0]
elif prop_type in ['cpil', 'pgap', 'pcst']:
return bool(struct.unpack('>B', data)[0])
else:
return data.decode('utf-8').strip('\x00')
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, output_path):
with open(output_path, 'wb') as f:
f.write(self.buffer)
# Example usage
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python m4p_parser.py <file.m4p>")
sys.exit(1)
parser = M4PParser(sys.argv[1])
parser.read()
parser.print_properties()
parser.write('output.m4p') # Writes copy
5. Java class that can open any file of format .M4P and decode read and write and print to console all the properties from the above list
Here is a Java class using RandomAccessFile
for parsing. It opens, decodes, reads, prints, and writes a copy.
import java.io.RandomAccessFile;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class M4PParser {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
public M4PParser(String filepath) {
this.filepath = filepath;
}
public void read() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filepath, "r")) {
ByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, raf.length());
buffer.order(ByteOrder.BIG_ENDIAN);
parseProperties(buffer);
}
}
private void parseProperties(ByteBuffer buffer) {
while (buffer.hasRemaining()) {
int size = buffer.getInt();
byte[] typeBytes = new byte[4];
buffer.get(typeBytes);
String type = new String(typeBytes, StandardCharsets.UTF_8);
if ("moov".equals(type)) {
int moovEnd = buffer.position() + size - 8;
while (buffer.position() < moovEnd) {
int subSize = buffer.getInt();
buffer.get(typeBytes);
String subType = new String(typeBytes, StandardCharsets.UTF_8);
if ("udta".equals(subType)) {
int udtaEnd = buffer.position() + subSize - 8;
while (buffer.position() < udtaEnd) {
int metaSize = buffer.getInt();
buffer.get(typeBytes);
String metaType = new String(typeBytes, StandardCharsets.UTF_8);
if ("meta".equals(metaType)) {
buffer.position(buffer.position() + 4); // Skip version/flags
int metaEnd = buffer.position() + metaSize - 12;
while (buffer.position() < metaEnd) {
int ilstSize = buffer.getInt();
buffer.get(typeBytes);
String ilstType = new String(typeBytes, StandardCharsets.UTF_8);
if ("ilst".equals(ilstType)) {
int ilstEnd = buffer.position() + ilstSize - 8;
while (buffer.position() < ilstEnd) {
int propSize = buffer.getInt();
buffer.get(typeBytes);
String propType = new String(typeBytes, StandardCharsets.UTF_8);
int dataSize = buffer.getInt();
buffer.get(typeBytes);
String dataType = new String(typeBytes, StandardCharsets.UTF_8);
if ("data".equals(dataType)) {
buffer.position(buffer.position() + 8); // Skip type/flags/locale
int valueLength = dataSize - 16;
Object value = getValue(buffer, valueLength, propType);
properties.put(propType, value);
} else {
buffer.position(buffer.position() + dataSize - 8);
}
}
} else {
buffer.position(buffer.position() + ilstSize - 8);
}
}
} else {
buffer.position(buffer.position() + metaSize - 8);
}
}
} else {
buffer.position(buffer.position() + subSize - 8);
}
}
} else {
buffer.position(buffer.position() + size - 8);
}
}
}
private Object getValue(ByteBuffer buffer, int length, String propType) {
byte[] data = new byte[length];
buffer.get(data);
if (propType.equals("covr")) {
return "Binary data (length: " + length + ")";
} else if (propType.equals("trkn") || propType.equals("disk")) {
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
bb.position(2);
int num = bb.getShort();
int total = bb.getShort();
return new int[]{num, total};
} else if (propType.equals("tmpo") || propType.equals("rtng") || propType.equals("stik") || propType.equals("hdvd") || propType.equals("tves") || propType.equals("tvsn")) {
return ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).getInt();
} else if (propType.equals("cpil") || propType.equals("pgap") || propType.equals("pcst")) {
return data[0] != 0;
} else {
return new String(data, StandardCharsets.UTF_8).trim();
}
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String outputPath) throws IOException {
try (RandomAccessFile in = new RandomAccessFile(filepath, "r");
RandomAccessFile out = new RandomAccessFile(outputPath, "rw")) {
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
inChannel.transferTo(0, in.length(), outChannel);
}
}
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("Usage: java M4PParser <file.m4p>");
System.exit(1);
}
M4PParser parser = new M4PParser(args[0]);
parser.read();
parser.printProperties();
parser.write("output.m4p");
}
}
6. Javascript class that can open any file of format .M4P and decode read and write and print to console all the properties from the above list
Here is a JavaScript class for Node.js (use node m4p_parser.js <file.m4p>
). It uses fs
to open, decode, read, print to console, and write a copy.
const fs = require('fs');
class M4PParser {
constructor(filepath) {
this.filepath = filepath;
this.properties = {};
this.buffer = null;
}
read() {
this.buffer = fs.readFileSync(this.filepath);
this.parseProperties();
return this.properties;
}
parseProperties() {
const dv = new DataView(this.buffer.buffer);
let offset = 0;
while (offset < this.buffer.length) {
const size = dv.getUint32(offset);
const type = String.fromCharCode(dv.getUint8(offset + 4), dv.getUint8(offset + 5), dv.getUint8(offset + 6), dv.getUint8(offset + 7));
offset += 8;
if (type === 'moov') {
const moovEnd = offset + size - 8;
while (offset < moovEnd) {
const subSize = dv.getUint32(offset);
const subType = String.fromCharCode(dv.getUint8(offset + 4), dv.getUint8(offset + 5), dv.getUint8(offset + 6), dv.getUint8(offset + 7));
offset += 8;
if (subType === 'udta') {
const udtaEnd = offset + subSize - 8;
while (offset < udtaEnd) {
const metaSize = dv.getUint32(offset);
const metaType = String.fromCharCode(dv.getUint8(offset + 4), dv.getUint8(offset + 5), dv.getUint8(offset + 6), dv.getUint8(offset + 7));
offset += 8;
if (metaType === 'meta') {
offset += 4; // Skip version/flags
const metaEnd = offset + metaSize - 12;
while (offset < metaEnd) {
const ilstSize = dv.getUint32(offset);
const ilstType = String.fromCharCode(dv.getUint8(offset + 4), dv.getUint8(offset + 5), dv.getUint8(offset + 6), dv.getUint8(offset + 7));
offset += 8;
if (ilstType === 'ilst') {
const ilstEnd = offset + ilstSize - 8;
while (offset < ilstEnd) {
const propSize = dv.getUint32(offset);
const propType = String.fromCharCode(dv.getUint8(offset + 4), dv.getUint8(offset + 5), dv.getUint8(offset + 6), dv.getUint8(offset + 7));
offset += 8;
const dataSize = dv.getUint32(offset);
const dataType = String.fromCharCode(dv.getUint8(offset + 4), dv.getUint8(offset + 5), dv.getUint8(offset + 6), dv.getUint8(offset + 7));
offset += 8;
if (dataType === 'data') {
offset += 8; // Skip type/flags/locale
const valueLength = dataSize - 16;
const value = this.getValue(dv, offset, valueLength, propType);
this.properties[propType] = value;
offset += valueLength;
} else {
offset += dataSize - 8;
}
}
} else {
offset += ilstSize - 8;
}
}
} else {
offset += metaSize - 8;
}
}
} else {
offset += subSize - 8;
}
}
} else {
offset += size - 8;
}
}
}
getValue(dv, offset, length, propType) {
if (propType === 'covr') {
return `Binary data (length: ${length})`;
} else if (['trkn', 'disk'].includes(propType)) {
return [dv.getUint16(offset + 2), dv.getUint16(offset + 4)];
} else if (['tmpo', 'rtng', 'stik', 'hdvd', 'tves', 'tvsn'].includes(propType)) {
return dv.getUint32(offset);
} else if (['cpil', 'pgap', 'pcst'].includes(propType)) {
return !!dv.getUint8(offset);
} else {
let str = '';
for (let i = 0; i < length; i++) {
str += String.fromCharCode(dv.getUint8(offset + i));
}
return str.trim();
}
}
printProperties() {
console.log(JSON.stringify(this.properties, null, 2));
}
write(outputPath) {
fs.writeFileSync(outputPath, this.buffer);
}
}
if (process.argv.length < 3) {
console.log('Usage: node m4p_parser.js <file.m4p>');
process.exit(1);
}
const parser = new M4PParser(process.argv[2]);
parser.read();
parser.printProperties();
parser.write('output.m4p');
7. C class that can open any file of format .M4P and decode read and write and print to console all the properties from the above list
In C, classes are not native, so here is a struct with functions (equivalent to a class). Compile with gcc m4p_parser.c -o m4p_parser
and run ./m4p_parser <file.m4p>
. It opens, decodes, reads, prints, and writes a copy.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct {
char *filepath;
char **keys;
char **values;
int count;
uint8_t *buffer;
size_t buffer_size;
} M4PParser;
M4PParser* m4p_parser_new(const char *filepath) {
M4PParser *parser = malloc(sizeof(M4PParser));
parser->filepath = strdup(filepath);
parser->keys = NULL;
parser->values = NULL;
parser->count = 0;
parser->buffer = NULL;
parser->buffer_size = 0;
return parser;
}
void m4p_parser_free(M4PParser *parser) {
free(parser->filepath);
for (int i = 0; i < parser->count; i++) {
free(parser->keys[i]);
free(parser->values[i]);
}
free(parser->keys);
free(parser->values);
free(parser->buffer);
free(parser);
}
void read_file(M4PParser *parser) {
FILE *f = fopen(parser->filepath, "rb");
if (!f) {
perror("Failed to open file");
exit(1);
}
fseek(f, 0, SEEK_END);
parser->buffer_size = ftell(f);
fseek(f, 0, SEEK_SET);
parser->buffer = malloc(parser->buffer_size);
fread(parser->buffer, 1, parser->buffer_size, f);
fclose(f);
}
void add_property(M4PParser *parser, const char *key, const char *value) {
parser->keys = realloc(parser->keys, (parser->count + 1) * sizeof(char*));
parser->values = realloc(parser->values, (parser->count + 1) * sizeof(char*));
parser->keys[parser->count] = strdup(key);
parser->values[parser->count] = strdup(value);
parser->count++;
}
uint32_t get_be32(const uint8_t *data) {
return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
}
uint16_t get_be16(const uint8_t *data) {
return (data[0] << 8) | data[1];
}
void parse_properties(M4PParser *parser) {
size_t offset = 0;
while (offset < parser->buffer_size) {
uint32_t size = get_be32(parser->buffer + offset);
char type[5] = {0};
memcpy(type, parser->buffer + offset + 4, 4);
offset += 8;
if (strcmp(type, "moov") == 0) {
size_t moov_end = offset + size - 8;
while (offset < moov_end) {
uint32_t sub_size = get_be32(parser->buffer + offset);
char sub_type[5] = {0};
memcpy(sub_type, parser->buffer + offset + 4, 4);
offset += 8;
if (strcmp(sub_type, "udta") == 0) {
size_t udta_end = offset + sub_size - 8;
while (offset < udta_end) {
uint32_t meta_size = get_be32(parser->buffer + offset);
char meta_type[5] = {0};
memcpy(meta_type, parser->buffer + offset + 4, 4);
offset += 8;
if (strcmp(meta_type, "meta") == 0) {
offset += 4; // Skip version/flags
size_t meta_end = offset + meta_size - 12;
while (offset < meta_end) {
uint32_t ilst_size = get_be32(parser->buffer + offset);
char ilst_type[5] = {0};
memcpy(ilst_type, parser->buffer + offset + 4, 4);
offset += 8;
if (strcmp(ilst_type, "ilst") == 0) {
size_t ilst_end = offset + ilst_size - 8;
while (offset < ilst_end) {
uint32_t prop_size = get_be32(parser->buffer + offset);
char prop_type[5] = {0};
memcpy(prop_type, parser->buffer + offset + 4, 4);
offset += 8;
uint32_t data_size = get_be32(parser->buffer + offset);
char data_type[5] = {0};
memcpy(data_type, parser->buffer + offset + 4, 4);
offset += 8;
if (strcmp(data_type, "data") == 0) {
offset += 8; // Skip type/flags/locale
uint32_t value_length = data_size - 16;
char value_str[1024];
get_value(parser->buffer + offset, value_length, prop_type, value_str);
add_property(parser, prop_type, value_str);
offset += value_length;
} else {
offset += data_size - 8;
}
}
} else {
offset += ilst_size - 8;
}
}
} else {
offset += meta_size - 8;
}
}
} else {
offset += sub_size - 8;
}
}
} else {
offset += size - 8;
}
}
}
void get_value(const uint8_t *data, uint32_t length, const char *prop_type, char *out) {
if (strcmp(prop_type, "covr") == 0) {
sprintf(out, "Binary data (length: %u)", length);
} else if (strcmp(prop_type, "trkn") == 0 || strcmp(prop_type, "disk") == 0) {
uint16_t num = get_be16(data + 2);
uint16_t total = get_be16(data + 4);
sprintf(out, "[%u, %u]", num, total);
} else if (strcmp(prop_type, "tmpo") == 0 || strcmp(prop_type, "rtng") == 0 || strcmp(prop_type, "stik") == 0 ||
strcmp(prop_type, "hdvd") == 0 || strcmp(prop_type, "tves") == 0 || strcmp(prop_type, "tvsn") == 0) {
uint32_t val = get_be32(data);
sprintf(out, "%u", val);
} else if (strcmp(prop_type, "cpil") == 0 || strcmp(prop_type, "pgap") == 0 || strcmp(prop_type, "pcst") == 0) {
sprintf(out, "%s", data[0] ? "true" : "false");
} else {
strncpy(out, (const char*)data, length);
out[length] = '\0';
}
}
void print_properties(M4PParser *parser) {
for (int i = 0; i < parser->count; i++) {
printf("%s: %s\n", parser->keys[i], parser->values[i]);
}
}
void write(M4PParser *parser, const char *output_path) {
FILE *f = fopen(output_path, "wb");
if (!f) {
perror("Failed to write file");
exit(1);
}
fwrite(parser->buffer, 1, parser->buffer_size, f);
fclose(f);
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("Usage: %s <file.m4p>\n", argv[0]);
return 1;
}
M4PParser *parser = m4p_parser_new(argv[1]);
read_file(parser);
parse_properties(parser);
print_properties(parser);
write(parser, "output.m4p");
m4p_parser_free(parser);
return 0;
}