Task 033: .APE File Format
Task 033: .APE File Format
1. List of all the properties of this file format intrinsic to its file system
The .APE file format (Monkey's Audio) is a lossless audio format with a structured layout. The intrinsic properties are derived from the file's descriptor and header structures, which describe the file's metadata, audio format, compression, and layout. These are the key properties (with their data types and descriptions):
- cID: String (4 bytes, always 'MAC ') - File identifier.
- nVersion: Unsigned 16-bit integer - Version of the format (multiplied by 1000, e.g., 3990 for version 3.99).
- nDescriptorBytes: Unsigned 32-bit integer - Size of the descriptor structure in bytes (allows for future expansion).
- nHeaderBytes: Unsigned 32-bit integer - Size of the APE header in bytes.
- nSeekTableBytes: Unsigned 32-bit integer - Size of the seek table in bytes (optional; number of entries = nSeekTableBytes / 4).
- nHeaderDataBytes: Unsigned 32-bit integer - Size of the original header data bytes (e.g., from WAV file).
- nAPEFrameDataBytes: Unsigned 32-bit integer - Low 32 bits of the total APE frame data size.
- nAPEFrameDataBytesHigh: Unsigned 32-bit integer - High 32 bits of the total APE frame data size (for files >4GB).
- nTerminatingDataBytes: Unsigned 32-bit integer - Size of terminating data bytes (optional, includes tags).
- cFileMD5: Array of 8-bit bytes (16 bytes) - MD5 hash of the file content (calculated specifically; see notes in source for out-of-order computation).
- nCompressionLevel: Unsigned 16-bit integer - Compression level (1000=fast, 2000=normal, 3000=high, 4000=extra high, 5000=insane).
- nFormatFlags: Unsigned 16-bit integer - Format flags (bitmask; obsolete flags include 1=8-bit, 4=peak level, 8=24-bit, 16=seek elements, 32=create WAV header on decompression).
- nBlocksPerFrame: Unsigned 32-bit integer - Number of audio blocks per frame.
- nFinalFrameBlocks: Unsigned 32-bit integer - Number of audio blocks in the final frame.
- nTotalFrames: Unsigned 32-bit integer - Total number of frames in the file.
- nBitsPerSample: Unsigned 16-bit integer - Bits per sample (typically 16, 24, or 32).
- nChannels: Unsigned 16-bit integer - Number of channels (1=mono, 2=stereo).
- nSampleRate: Unsigned 32-bit integer - Sample rate in Hz (e.g., 44100).
Additional intrinsic characteristics (not direct fields but derived from the format):
- Seek table: Optional array of 32-bit unsigned integers (one per frame) for seek offsets.
- Frame data: Compressed audio frames following the header, using range coding, IIR filtering, and channel correlation.
- Tags: Optional APEv2 or ID3 tags at the end.
- MD5 calculation: Computed on the file excluding the tag, with special handling for header placement.
- File layout: Descriptor + Header + Seek Table + Header Data + Frame Data + Terminating Data + Tag.
These properties define the file's structure and are essential for parsing, seeking, and decompressing.
2. Two direct download links for files of format .APE
- https://trac.ffmpeg.org/raw-attachment/ticket/452/ape24bit.ape (24-bit APE sample from FFmpeg ticket for testing 24-bit support)
- https://trac.ffmpeg.org/raw-attachment/ticket/9816/sample.ape (another 24-bit APE sample from FFmpeg ticket for noise issue testing)
3. Ghost blog embedded html javascript for drag n drop .APE file dump
Below is an HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post (or any HTML-enabled blog). It creates a drag-and-drop area. When a .APE file is dropped, it parses the file using FileReader and DataView, extracts the properties, and dumps them to the screen in a pre element.
4. Python class for .APE file
Below is a Python class that can open a .APE file, decode (parse) the header, read and print the properties, and write a new .APE file with the same properties (copying the rest of the data as is for simplicity, since full encoding is beyond scope).
import struct
import hashlib
import os
class APEFile:
def __init__(self, filename):
self.filename = filename
self.properties = {}
self.data = b''
self.parse()
def parse(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
offset = 0
self.properties['cID'] = struct.unpack_from('<4s', self.data, offset)[0].decode('ascii')
offset += 4
self.properties['nVersion'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
offset += 2 # Padding for old versions
self.properties['nDescriptorBytes'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nHeaderBytes'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nSeekTableBytes'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nHeaderDataBytes'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nAPEFrameDataBytes'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nAPEFrameDataBytesHigh'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nTerminatingDataBytes'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['cFileMD5'] = ':'.join(f'{b:02x}' for b in self.data[offset:offset+16])
offset += 16
# APE_HEADER
self.properties['nCompressionLevel'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
self.properties['nFormatFlags'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
self.properties['nBlocksPerFrame'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nFinalFrameBlocks'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nTotalFrames'] = struct.unpack_from('<I', self.data, offset)[0]
offset += 4
self.properties['nBitsPerSample'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
self.properties['nChannels'] = struct.unpack_from('<H', self.data, offset)[0]
offset += 2
self.properties['nSampleRate'] = struct.unpack_from('<I', self.data, offset)[0]
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, new_filename):
# For simplicity, write the same data (header + rest); in a full impl, recompute MD5 if changed
with open(new_filename, 'wb') as f:
f.write(self.data)
# Example usage
# ape = APEFile('example.ape')
# ape.print_properties()
# ape.write('new.ape')
5. Java class for .APE file
Below is a Java class that can open a .APE file, decode (parse) the header, read and print the properties, and write a new .APE file with the same properties (copying the rest).
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
public class APEFile {
private String filename;
private Map<String, Object> properties = new HashMap<>();
private byte[] data;
public APEFile(String filename) throws IOException {
this.filename = filename;
parse();
}
private void parse() throws IOException {
File file = new File(filename);
data = new byte[(int) file.length()];
try (FileInputStream fis = new FileInputStream(file)) {
fis.read(data);
}
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int offset = 0;
byte[] idBytes = new byte[4];
bb.position(offset);
bb.get(idBytes);
properties.put("cID", new String(idBytes));
offset += 4;
properties.put("nVersion", bb.getShort(offset));
offset += 2;
offset += 2; // Padding
properties.put("nDescriptorBytes", bb.getInt(offset));
offset += 4;
properties.put("nHeaderBytes", bb.getInt(offset));
offset += 4;
properties.put("nSeekTableBytes", bb.getInt(offset));
offset += 4;
properties.put("nHeaderDataBytes", bb.getInt(offset));
offset += 4;
properties.put("nAPEFrameDataBytes", bb.getInt(offset));
offset += 4;
properties.put("nAPEFrameDataBytesHigh", bb.getInt(offset));
offset += 4;
properties.put("nTerminatingDataBytes", bb.getInt(offset));
offset += 4;
byte[] md5 = new byte[16];
bb.position(offset);
bb.get(md5);
String md5Str = "";
for (byte b : md5) {
md5Str += String.format("%02x:", b);
}
properties.put("cFileMD5", md5Str.substring(0, md5Str.length() - 1));
offset += 16;
// APE_HEADER
properties.put("nCompressionLevel", bb.getShort(offset));
offset += 2;
properties.put("nFormatFlags", bb.getShort(offset));
offset += 2;
properties.put("nBlocksPerFrame", bb.getInt(offset));
offset += 4;
properties.put("nFinalFrameBlocks", bb.getInt(offset));
offset += 4;
properties.put("nTotalFrames", bb.getInt(offset));
offset += 4;
properties.put("nBitsPerSample", bb.getShort(offset));
offset += 2;
properties.put("nChannels", bb.getShort(offset));
offset += 2;
properties.put("nSampleRate", bb.getInt(offset));
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String newFilename) throws IOException {
try (FileOutputStream fos = new FileOutputStream(newFilename)) {
fos.write(data);
}
}
// Example usage
// public static void main(String[] args) throws IOException {
// APEFile ape = new APEFile("example.ape");
// ape.printProperties();
// ape.write("new.ape");
// }
}
6. Javascript class for .APE file
Below is a JavaScript class that can "open" a .APE file (via File object), decode the header, read and print the properties to console, and "write" (generate a new Blob with the same data and trigger download).
class APEFile {
constructor(file) {
this.file = file;
this.properties = {};
this.data = null;
this.parse();
}
parse() {
const reader = new FileReader();
reader.onload = (event) => {
this.data = event.target.result;
const view = new DataView(this.data);
let offset = 0;
this.properties.cID = String.fromCharCode(view.getUint8(offset++), view.getUint8(offset++), view.getUint8(offset++), view.getUint8(offset++));
this.properties.nVersion = view.getUint16(offset, true); offset += 2;
offset += 2; // Padding
this.properties.nDescriptorBytes = view.getUint32(offset, true); offset += 4;
this.properties.nHeaderBytes = view.getUint32(offset, true); offset += 4;
this.properties.nSeekTableBytes = view.getUint32(offset, true); offset += 4;
this.properties.nHeaderDataBytes = view.getUint32(offset, true); offset += 4;
this.properties.nAPEFrameDataBytes = view.getUint32(offset, true); offset += 4;
this.properties.nAPEFrameDataBytesHigh = view.getUint32(offset, true); offset += 4;
this.properties.nTerminatingDataBytes = view.getUint32(offset, true); offset += 4;
let md5 = [];
for (let i = 0; i < 16; i++) {
md5.push(view.getUint8(offset++).toString(16).padStart(2, '0'));
}
this.properties.cFileMD5 = md5.join(':');
// APE_HEADER
this.properties.nCompressionLevel = view.getUint16(offset, true); offset += 2;
this.properties.nFormatFlags = view.getUint16(offset, true); offset += 2;
this.properties.nBlocksPerFrame = view.getUint32(offset, true); offset += 4;
this.properties.nFinalFrameBlocks = view.getUint32(offset, true); offset += 4;
this.properties.nTotalFrames = view.getUint32(offset, true); offset += 4;
this.properties.nBitsPerSample = view.getUint16(offset, true); offset += 2;
this.properties.nChannels = view.getUint16(offset, true); offset += 2;
this.properties.nSampleRate = view.getUint32(offset, true);
this.printProperties();
};
reader.readAsArrayBuffer(this.file);
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${value}`);
}
}
write(newFilename) {
if (this.data) {
const blob = new Blob([this.data], { type: 'audio/ape' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = newFilename || 'new.ape';
a.click();
URL.revokeObjectURL(url);
}
}
}
// Example usage
// const input = document.getElementById('fileInput'); // <input type="file">
// input.addEventListener('change', (e) => {
// const ape = new APEFile(e.target.files[0]);
// // Later: ape.write('new.ape');
// });
7. C class for .APE file
C doesn't have classes, so below is a struct with functions to open, parse, print properties, and write (copy the file as is).
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
char cID[5];
uint16_t nVersion;
uint32_t nDescriptorBytes;
uint32_t nHeaderBytes;
uint32_t nSeekTableBytes;
uint32_t nHeaderDataBytes;
uint32_t nAPEFrameDataBytes;
uint32_t nAPEFrameDataBytesHigh;
uint32_t nTerminatingDataBytes;
uint8_t cFileMD5[16];
uint16_t nCompressionLevel;
uint16_t nFormatFlags;
uint32_t nBlocksPerFrame;
uint32_t nFinalFrameBlocks;
uint32_t nTotalFrames;
uint16_t nBitsPerSample;
uint16_t nChannels;
uint32_t nSampleRate;
uint8_t* data;
size_t data_size;
} APEFile;
APEFile* ape_open(const char* filename) {
APEFile* ape = malloc(sizeof(APEFile));
FILE* f = fopen(filename, "rb");
fseek(f, 0, SEEK_END);
ape->data_size = ftell(f);
fseek(f, 0, SEEK_SET);
ape->data = malloc(ape->data_size);
fread(ape->data, 1, ape->data_size, f);
fclose(f);
int offset = 0;
memcpy(ape->cID, ape->data + offset, 4); ape->cID[4] = '\0'; offset += 4;
memcpy(&ape->nVersion, ape->data + offset, 2); offset += 2;
offset += 2; // Padding
memcpy(&ape->nDescriptorBytes, ape->data + offset, 4); offset += 4;
memcpy(&ape->nHeaderBytes, ape->data + offset, 4); offset += 4;
memcpy(&ape->nSeekTableBytes, ape->data + offset, 4); offset += 4;
memcpy(&ape->nHeaderDataBytes, ape->data + offset, 4); offset += 4;
memcpy(&ape->nAPEFrameDataBytes, ape->data + offset, 4); offset += 4;
memcpy(&ape->nAPEFrameDataBytesHigh, ape->data + offset, 4); offset += 4;
memcpy(&ape->nTerminatingDataBytes, ape->data + offset, 4); offset += 4;
memcpy(ape->cFileMD5, ape->data + offset, 16); offset += 16;
// APE_HEADER
memcpy(&ape->nCompressionLevel, ape->data + offset, 2); offset += 2;
memcpy(&ape->nFormatFlags, ape->data + offset, 2); offset += 2;
memcpy(&ape->nBlocksPerFrame, ape->data + offset, 4); offset += 4;
memcpy(&ape->nFinalFrameBlocks, ape->data + offset, 4); offset += 4;
memcpy(&ape->nTotalFrames, ape->data + offset, 4); offset += 4;
memcpy(&ape->nBitsPerSample, ape->data + offset, 2); offset += 2;
memcpy(&ape->nChannels, ape->data + offset, 2); offset += 2;
memcpy(&ape->nSampleRate, ape->data + offset, 4);
return ape;
}
void ape_print_properties(APEFile* ape) {
printf("cID: %s\n", ape->cID);
printf("nVersion: %u\n", ape->nVersion);
printf("nDescriptorBytes: %u\n", ape->nDescriptorBytes);
printf("nHeaderBytes: %u\n", ape->nHeaderBytes);
printf("nSeekTableBytes: %u\n", ape->nSeekTableBytes);
printf("nHeaderDataBytes: %u\n", ape->nHeaderDataBytes);
printf("nAPEFrameDataBytes: %u\n", ape->nAPEFrameDataBytes);
printf("nAPEFrameDataBytesHigh: %u\n", ape->nAPEFrameDataBytesHigh);
printf("nTerminatingDataBytes: %u\n", ape->nTerminatingDataBytes);
printf("cFileMD5: ");
for (int i = 0; i < 16; i++) printf("%02x%s", ape->cFileMD5[i], (i < 15 ? ":" : "\n"));
printf("nCompressionLevel: %u\n", ape->nCompressionLevel);
printf("nFormatFlags: %u\n", ape->nFormatFlags);
printf("nBlocksPerFrame: %u\n", ape->nBlocksPerFrame);
printf("nFinalFrameBlocks: %u\n", ape->nFinalFrameBlocks);
printf("nTotalFrames: %u\n", ape->nTotalFrames);
printf("nBitsPerSample: %u\n", ape->nBitsPerSample);
printf("nChannels: %u\n", ape->nChannels);
printf("nSampleRate: %u\n", ape->nSampleRate);
}
void ape_write(APEFile* ape, const char* new_filename) {
FILE* f = fopen(new_filename, "wb");
fwrite(ape->data, 1, ape->data_size, f);
fclose(f);
}
void ape_free(APEFile* ape) {
free(ape->data);
free(ape);
}
// Example usage
// int main() {
// APEFile* ape = ape_open("example.ape");
// ape_print_properties(ape);
// ape_write(ape, "new.ape");
// ape_free(ape);
// return 0;
// }