Task 814: .WMA File Format
Task 814: .WMA File Format
File Format Specifications for .WMA
The .WMA (Windows Media Audio) file format is a proprietary audio container developed by Microsoft, utilizing the Advanced Systems Format (ASF) as its underlying structure. ASF serves as an extensible container for multimedia data, primarily audio in the case of .WMA files, supporting streaming and local playback. Detailed specifications are outlined in Microsoft's ASF documentation, which describes the object-based architecture, including headers, data packets, and metadata. Key references include the Microsoft Learn documentation on ASF file structure and related format overviews, emphasizing backward-compatible extensions for audio codecs like WMA.
1. List of Properties Intrinsic to the .WMA File Format
Based on the ASF container specifications relevant to .WMA audio files, the following is a comprehensive list of intrinsic properties that can be extracted from the file structure. These include header fields, stream descriptors, and metadata, derived from mandatory and optional objects within the ASF header. Properties are grouped by object type for clarity:
File Properties Object:
- File ID (128-bit GUID)
- File Size (64-bit integer, in bytes)
- Creation Date (64-bit integer, FILETIME timestamp from January 1, 1601)
- Data Packets Count (64-bit integer)
- Play Duration (64-bit integer, in 100-nanosecond units)
- Send Duration (64-bit integer, in 100-nanosecond units)
- Preroll (64-bit integer, in milliseconds)
- Flags (32-bit integer, e.g., broadcast flag, seekable flag)
- Minimum Data Packet Size (32-bit integer, in bytes)
- Maximum Data Packet Size (32-bit integer, in bytes)
- Maximum Bitrate (32-bit integer, in bits per second)
Stream Properties Object (Audio-Specific for .WMA):
- Stream Type GUID (128-bit GUID, e.g., audio stream: F8699E40-5B4D-11CF-A8FD-00805F5C442B)
- Error Correction Type GUID (128-bit GUID)
- Time Offset (64-bit integer, in 100-nanosecond units)
- Stream Number (16-bit integer)
- Stream Flags (16-bit integer, e.g., key frame properties)
- Audio Format Tag (16-bit integer, e.g., 0x161 for WMA v2)
- Number of Channels (16-bit integer)
- Samples Per Second (32-bit integer, sample rate in Hz)
- Average Bytes Per Second (32-bit integer)
- Block Alignment (16-bit integer, in bytes)
- Bits Per Sample (16-bit integer)
Content Description Object (Metadata):
- Title (Unicode string)
- Author (Unicode string)
- Copyright (Unicode string)
- Description (Unicode string)
- Rating (Unicode string)
These properties are intrinsic to the file format and can be parsed from the binary structure without external dependencies. Additional extended metadata (e.g., genre, album) may appear in optional objects like the Extended Content Description Object, but the list above focuses on core, commonly present fields.
2. Two Direct Download Links for .WMA Files
- https://filesamples.com/samples/audio/wma/Symphony No.6 (1st movement).wma
- https://getsamplefiles.com/download/wma/sample-1.wma
These links provide sample .WMA audio files for testing purposes.
3. HTML/JavaScript for Drag-and-Drop .WMA File Property Dump
The following is a self-contained HTML page with embedded JavaScript that enables drag-and-drop functionality for a .WMA file. Upon dropping the file, it reads the binary content using FileReader, parses the ASF structure, extracts the listed properties, and displays them on the screen. This can be embedded in a blog post (e.g., Ghost platform) as raw HTML.
4. Python Class for .WMA File Handling
The following Python class uses the struct module to read and decode .WMA files, extracting and printing the properties. It also includes a basic write method to create a simple .WMA file header (for demonstration; full writing requires audio data encoding, which is omitted for conciseness).
import struct
import uuid
import datetime
class WMAParser:
def __init__(self, filename):
self.filename = filename
self.properties = {}
def read_and_decode(self):
with open(self.filename, 'rb') as f:
data = f.read()
offset = 0
header_guid = self._read_guid(data, offset)
if header_guid != uuid.UUID('3026b275-8e66-cf11-a6d9-00aa0062ce6c'):
raise ValueError("Not a valid WMA/ASF file")
offset += 16
header_size, = struct.unpack('<Q', data[offset:offset+8])
offset += 8
num_objects, = struct.unpack('<I', data[offset:offset+4])
offset += 6 # Skip reserved
for _ in range(num_objects):
obj_guid = self._read_guid(data, offset)
offset += 16
obj_size, = struct.unpack('<Q', data[offset:offset+8])
offset += 8
if obj_guid == uuid.UUID('a1dcab8c-47a9-cf11-8ee4-00c00c205365'): # File Properties
self.properties['file_id'] = self._read_guid(data, offset)
self.properties['file_size'], = struct.unpack('<Q', data[offset+16:offset+24])
creation_time, = struct.unpack('<Q', data[offset+24:offset+32])
self.properties['creation_date'] = datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=creation_time / 10)
self.properties['data_packets_count'], = struct.unpack('<Q', data[offset+32:offset+40])
self.properties['play_duration'] = struct.unpack('<Q', data[offset+40:offset+48])[0] / 10000 # ms
self.properties['send_duration'] = struct.unpack('<Q', data[offset+48:offset+56])[0] / 10000 # ms
self.properties['preroll'], = struct.unpack('<Q', data[offset+56:offset+64])
self.properties['flags'], = struct.unpack('<I', data[offset+64:offset+68])
self.properties['min_packet_size'], = struct.unpack('<I', data[offset+68:offset+72])
self.properties['max_packet_size'], = struct.unpack('<I', data[offset+72:offset+76])
self.properties['max_bitrate'], = struct.unpack('<I', data[offset+76:offset+80])
elif obj_guid == uuid.UUID('9107dcb7-b7a9-cf11-8ee6-00c00c205365'): # Stream Properties
self.properties['stream_type_guid'] = self._read_guid(data, offset)
self.properties['error_correction_guid'] = self._read_guid(data, offset+16)
self.properties['time_offset'], = struct.unpack('<Q', data[offset+32:offset+40])
type_data_len, = struct.unpack('<I', data[offset+40:offset+44])
err_data_len, = struct.unpack('<I', data[offset+44:offset+48])
self.properties['stream_number'] = struct.unpack('<H', data[offset+48:offset+50])[0] & 0x7F
self.properties['stream_flags'], = struct.unpack('<H', data[offset+50:offset+52])
offset += 52
self.properties['audio_format_tag'], = struct.unpack('<H', data[offset:offset+2])
self.properties['channels'], = struct.unpack('<H', data[offset+2:offset+4])
self.properties['samples_per_second'], = struct.unpack('<I', data[offset+4:offset+8])
self.properties['avg_bytes_per_second'], = struct.unpack('<I', data[offset+8:offset+12])
self.properties['block_alignment'], = struct.unpack('<H', data[offset+12:offset+14])
self.properties['bits_per_sample'], = struct.unpack('<H', data[offset+14:offset+16])
elif obj_guid == uuid.UUID('3326b275-8e66-cf11-a6d9-00aa0062ce6c'): # Content Description
lengths = struct.unpack('<HHHHH', data[offset:offset+10])
offset += 10
self.properties['title'] = data[offset:offset+lengths[0]].decode('utf-16-le')
offset += lengths[0]
self.properties['author'] = data[offset:offset+lengths[1]].decode('utf-16-le')
offset += lengths[1]
self.properties['copyright'] = data[offset:offset+lengths[2]].decode('utf-16-le')
offset += lengths[2]
self.properties['description'] = data[offset:offset+lengths[3]].decode('utf-16-le')
offset += lengths[3]
self.properties['rating'] = data[offset:offset+lengths[4]].decode('utf-16-le')
offset += obj_size - 24 # Advance
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, output_filename):
# Basic stub for writing a minimal header (omits full data packet creation)
header = b''
# Construct minimal ASF header with file properties (example values)
# This is illustrative; full implementation requires audio encoding
with open(output_filename, 'wb') as f:
f.write(header) # Placeholder for complete write logic
def _read_guid(self, data, offset):
return uuid.UUID(bytes_le=data[offset:offset+16])
# Usage example:
# parser = WMAParser('sample.wma')
# parser.read_and_decode()
# parser.print_properties()
# parser.write('output.wma')
5. Java Class for .WMA File Handling
The following Java class uses DataInputStream to read and decode .WMA files, extracting and printing the properties. It includes a write method stub for creating a file.
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.UUID;
import java.util.Date;
public class WMAParser {
private String filename;
private java.util.Map<String, Object> properties = new java.util.HashMap<>();
public WMAParser(String filename) {
this.filename = filename;
}
public void readAndDecode() throws IOException {
try (FileInputStream fis = new FileInputStream(filename);
DataInputStream dis = new DataInputStream(fis)) {
byte[] buffer = new byte[(int) new File(filename).length()];
dis.readFully(buffer);
ByteBuffer bb = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN);
UUID headerGuid = readGuid(bb, 0);
if (!headerGuid.toString().equals("3026b275-8e66-cf11-a6d9-00aa0062ce6c")) {
throw new IllegalArgumentException("Not a valid WMA/ASF file");
}
int offset = 16;
long headerSize = bb.getLong(offset);
offset += 8;
int numObjects = bb.getInt(offset);
offset += 6; // Skip reserved
for (int i = 0; i < numObjects; i++) {
UUID objGuid = readGuid(bb, offset);
offset += 16;
long objSize = bb.getLong(offset);
offset += 8;
if (objGuid.toString().equals("a1dcab8c-47a9-cf11-8ee4-00c00c205365")) { // File Properties
properties.put("file_id", readGuid(bb, offset));
properties.put("file_size", bb.getLong(offset + 16));
long creationTime = bb.getLong(offset + 24);
properties.put("creation_date", new Date((creationTime / 10000) - 11644473600000L)); // FILETIME to Date
properties.put("data_packets_count", bb.getLong(offset + 32));
properties.put("play_duration", bb.getLong(offset + 40) / 10000.0); // ms
properties.put("send_duration", bb.getLong(offset + 48) / 10000.0); // ms
properties.put("preroll", bb.getLong(offset + 56));
properties.put("flags", bb.getInt(offset + 64));
properties.put("min_packet_size", bb.getInt(offset + 68));
properties.put("max_packet_size", bb.getInt(offset + 72));
properties.put("max_bitrate", bb.getInt(offset + 76));
} else if (objGuid.toString().equals("9107dcb7-b7a9-cf11-8ee6-00c00c205365")) { // Stream Properties
properties.put("stream_type_guid", readGuid(bb, offset));
properties.put("error_correction_guid", readGuid(bb, offset + 16));
properties.put("time_offset", bb.getLong(offset + 32));
int typeDataLen = bb.getInt(offset + 40);
int errDataLen = bb.getInt(offset + 44);
properties.put("stream_number", bb.getShort(offset + 48) & 0x7F);
properties.put("stream_flags", bb.getShort(offset + 50));
offset += 52;
properties.put("audio_format_tag", bb.getShort(offset));
properties.put("channels", bb.getShort(offset + 2));
properties.put("samples_per_second", bb.getInt(offset + 4));
properties.put("avg_bytes_per_second", bb.getInt(offset + 8));
properties.put("block_alignment", bb.getShort(offset + 12));
properties.put("bits_per_sample", bb.getShort(offset + 14));
} else if (objGuid.toString().equals("3326b275-8e66-cf11-a6d9-00aa0062ce6c")) { // Content Description
short[] lengths = {bb.getShort(offset), bb.getShort(offset + 2), bb.getShort(offset + 4), bb.getShort(offset + 6), bb.getShort(offset + 8)};
offset += 10;
properties.put("title", readString(bb, offset, lengths[0]));
offset += lengths[0];
properties.put("author", readString(bb, offset, lengths[1]));
offset += lengths[1];
properties.put("copyright", readString(bb, offset, lengths[2]));
offset += lengths[2];
properties.put("description", readString(bb, offset, lengths[3]));
offset += lengths[3];
properties.put("rating", readString(bb, offset, lengths[4]));
}
offset += (int) (objSize - 24);
}
}
}
public void printProperties() {
for (java.util.Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String outputFilename) throws IOException {
// Basic stub for writing (placeholder for full implementation)
try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
// Write minimal header bytes here
}
}
private UUID readGuid(ByteBuffer bb, int offset) {
byte[] guidBytes = new byte[16];
bb.position(offset);
bb.get(guidBytes);
return UUID.nameUUIDFromBytes(guidBytes); // Note: This is simplified; actual GUID parsing may need byte order adjustment
}
private String readString(ByteBuffer bb, int offset, int length) {
bb.position(offset);
byte[] strBytes = new byte[length];
bb.get(strBytes);
return new String(strBytes, "UTF-16LE");
}
// Usage example:
// public static void main(String[] args) throws IOException {
// WMAParser parser = new WMAParser("sample.wma");
// parser.readAndDecode();
// parser.printProperties();
// parser.write("output.wma");
// }
}
Note: UUID parsing in Java requires careful byte order handling; the code uses a simplified approach for demonstration.
6. JavaScript Class for .WMA File Handling
The following JavaScript class reads a .WMA file using Node.js fs module (for console environment), decodes properties, and prints them. It includes a write stub.
const fs = require('fs');
class WMAParser {
constructor(filename) {
this.filename = filename;
this.properties = {};
}
readAndDecode() {
const data = fs.readFileSync(this.filename);
const view = new DataView(data.buffer);
let offset = 0;
const headerGUID = this._readGUID(view, offset);
if (headerGUID !== '3026b275-8e66-cf11-a6d9-00aa0062ce6c') {
throw new Error('Not a valid WMA/ASF file');
}
offset += 16;
const headerSize = this._readQWord(view, offset);
offset += 8;
const numObjects = view.getUint32(offset, true);
offset += 6; // Skip reserved
for (let i = 0; i < numObjects; i++) {
const objGUID = this._readGUID(view, offset);
offset += 16;
const objSize = this._readQWord(view, offset);
offset += 8;
if (objGUID === 'a1dcab8c-47a9-cf11-8ee4-00c00c205365') { // File Properties
this.properties.file_id = this._readGUID(view, offset);
this.properties.file_size = this._readQWord(view, offset + 16);
const creationTime = this._readQWord(view, offset + 24);
this.properties.creation_date = new Date((creationTime / 10000) - 11644473600000); // FILETIME to JS Date
this.properties.data_packets_count = this._readQWord(view, offset + 32);
this.properties.play_duration = this._readQWord(view, offset + 40) / 10000; // ms
this.properties.send_duration = this._readQWord(view, offset + 48) / 10000; // ms
this.properties.preroll = this._readQWord(view, offset + 56);
this.properties.flags = view.getUint32(offset + 64, true);
this.properties.min_packet_size = view.getUint32(offset + 68, true);
this.properties.max_packet_size = view.getUint32(offset + 72, true);
this.properties.max_bitrate = view.getUint32(offset + 76, true);
} else if (objGUID === '9107dcb7-b7a9-cf11-8ee6-00c00c205365') { // Stream Properties
this.properties.stream_type_guid = this._readGUID(view, offset);
this.properties.error_correction_guid = this._readGUID(view, offset + 16);
this.properties.time_offset = this._readQWord(view, offset + 32);
const typeDataLen = view.getUint32(offset + 40, true);
const errDataLen = view.getUint32(offset + 44, true);
this.properties.stream_number = view.getUint16(offset + 48, true) & 0x7F;
this.properties.stream_flags = view.getUint16(offset + 50, true);
offset += 52;
this.properties.audio_format_tag = view.getUint16(offset, true);
this.properties.channels = view.getUint16(offset + 2, true);
this.properties.samples_per_second = view.getUint32(offset + 4, true);
this.properties.avg_bytes_per_second = view.getUint32(offset + 8, true);
this.properties.block_alignment = view.getUint16(offset + 12, true);
this.properties.bits_per_sample = view.getUint16(offset + 14, true);
} else if (objGUID === '3326b275-8e66-cf11-a6d9-00aa0062ce6c') { // Content Description
const lengths = [view.getUint16(offset, true), view.getUint16(offset + 2, true), view.getUint16(offset + 4, true), view.getUint16(offset + 6, true), view.getUint16(offset + 8, true)];
offset += 10;
this.properties.title = this._readString(view, offset, lengths[0]);
offset += lengths[0];
this.properties.author = this._readString(view, offset, lengths[1]);
offset += lengths[1];
this.properties.copyright = this._readString(view, offset, lengths[2]);
offset += lengths[2];
this.properties.description = this._readString(view, offset, lengths[3]);
offset += lengths[3];
this.properties.rating = this._readString(view, offset, lengths[4]);
}
offset += Number(objSize - 24n);
}
}
printProperties() {
console.log(this.properties);
}
write(outputFilename) {
// Basic stub for writing
fs.writeFileSync(outputFilename, Buffer.from([])); // Placeholder
}
_readGUID(view, offset) {
const bytes = [];
for (let i = 0; i < 16; i++) bytes.push(view.getUint8(offset + i).toString(16).padStart(2, '0'));
return `${bytes.slice(0,4).reverse().join('')}-${bytes.slice(4,6).reverse().join('')}-${bytes.slice(6,8).reverse().join('')}-${bytes.slice(8,10).join('')}-${bytes.slice(10).join('')}`.toLowerCase();
}
_readQWord(view, offset) {
return view.getBigUint64(offset, true);
}
_readString(view, offset, length) {
let str = '';
for (let i = 0; i < length; i += 2) str += String.fromCharCode(view.getUint16(offset + i, true));
return str;
}
}
// Usage example:
// const parser = new WMAParser('sample.wma');
// parser.readAndDecode();
// parser.printProperties();
// parser.write('output.wma');
7. C++ Class for .WMA File Handling
The following C++ class uses std::ifstream to read and decode .WMA files, extracting and printing properties. It includes a write stub. Compile with C++11 or later.
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iomanip>
#include <map>
#include <cstdint>
class WMAParser {
private:
std::string filename;
std::map<std::string, std::string> properties;
std::string readGUID(std::ifstream& file) {
uint32_t data1;
uint16_t data2, data3;
uint8_t data4[8];
file.read(reinterpret_cast<char*>(&data1), 4);
file.read(reinterpret_cast<char*>(&data2), 2);
file.read(reinterpret_cast<char*>(&data3), 2);
file.read(reinterpret_cast<char*>(data4), 8);
std::stringstream ss;
ss << std::hex << std::setfill('0') << std::setw(8) << data1 << "-"
<< std::setw(4) << data2 << "-" << std::setw(4) << data3 << "-"
<< std::setw(2) << static_cast<int>(data4[0]) << std::setw(2) << static_cast<int>(data4[1]) << "-";
for (int i = 2; i < 8; i++) ss << std::setw(2) << static_cast<int>(data4[i]);
return ss.str();
}
uint64_t readQWord(std::ifstream& file) {
uint64_t value;
file.read(reinterpret_cast<char*>(&value), 8);
return value;
}
std::string readString(std::ifstream& file, uint16_t length) {
std::vector<char> buffer(length);
file.read(buffer.data(), length);
std::wstring wstr(reinterpret_cast<wchar_t*>(buffer.data()), length / 2);
return std::string(wstr.begin(), wstr.end());
}
public:
WMAParser(const std::string& fn) : filename(fn) {}
void readAndDecode() {
std::ifstream file(filename, std::ios::binary);
if (!file) throw std::runtime_error("Cannot open file");
std::string headerGuid = readGUID(file);
if (headerGuid != "75b22630-668e-11cf-a6d9-00aa0062ce6c") { // Note: byte order adjusted
throw std::runtime_error("Not a valid WMA/ASF file");
}
uint64_t headerSize = readQWord(file);
uint32_t numObjects;
file.read(reinterpret_cast<char*>(&numObjects), 4);
file.seekg(2, std::ios::cur); // Skip reserved
for (uint32_t i = 0; i < numObjects; ++i) {
std::string objGuid = readGUID(file);
uint64_t objSize = readQWord(file);
auto pos = file.tellg();
if (objGuid == "a1dcab8c-47a9-11cf-8ee4-00c00c205365") { // File Properties, note byte order
properties["file_id"] = readGUID(file);
properties["file_size"] = std::to_string(readQWord(file));
uint64_t creationTime = readQWord(file);
properties["creation_date"] = std::to_string(creationTime); // Raw for simplicity
properties["data_packets_count"] = std::to_string(readQWord(file));
properties["play_duration"] = std::to_string(readQWord(file) / 10000);
properties["send_duration"] = std::to_string(readQWord(file) / 10000);
properties["preroll"] = std::to_string(readQWord(file));
uint32_t flags, minPkt, maxPkt, maxBit;
file.read(reinterpret_cast<char*>(&flags), 4);
file.read(reinterpret_cast<char*>(&minPkt), 4);
file.read(reinterpret_cast<char*>(&maxPkt), 4);
file.read(reinterpret_cast<char*>(&maxBit), 4);
properties["flags"] = std::to_string(flags);
properties["min_packet_size"] = std::to_string(minPkt);
properties["max_packet_size"] = std::to_string(maxPkt);
properties["max_bitrate"] = std::to_string(maxBit);
} else if (objGuid == "9107dcb7-b7a9-11cf-8ee6-00c00c205365") { // Stream Properties
properties["stream_type_guid"] = readGUID(file);
properties["error_correction_guid"] = readGUID(file);
properties["time_offset"] = std::to_string(readQWord(file));
uint32_t typeLen, errLen;
file.read(reinterpret_cast<char*>(&typeLen), 4);
file.read(reinterpret_cast<char*>(&errLen), 4);
uint16_t streamNum, flags;
file.read(reinterpret_cast<char*>(&streamNum), 2);
file.read(reinterpret_cast<char*>(&flags), 2);
properties["stream_number"] = std::to_string(streamNum & 0x7F);
properties["stream_flags"] = std::to_string(flags);
uint16_t formatTag, channels, blockAlign, bitsSample;
uint32_t samplesSec, avgBytes;
file.read(reinterpret_cast<char*>(&formatTag), 2);
file.read(reinterpret_cast<char*>(&channels), 2);
file.read(reinterpret_cast<char*>(&samplesSec), 4);
file.read(reinterpret_cast<char*>(&avgBytes), 4);
file.read(reinterpret_cast<char*>(&blockAlign), 2);
file.read(reinterpret_cast<char*>(&bitsSample), 2);
properties["audio_format_tag"] = std::to_string(formatTag);
properties["channels"] = std::to_string(channels);
properties["samples_per_second"] = std::to_string(samplesSec);
properties["avg_bytes_per_second"] = std::to_string(avgBytes);
properties["block_alignment"] = std::to_string(blockAlign);
properties["bits_per_sample"] = std::to_string(bitsSample);
} else if (objGuid == "3326b275-8e66-11cf-a6d9-00aa0062ce6c") { // Content Description
uint16_t lengths[5];
for (int j = 0; j < 5; j++) file.read(reinterpret_cast<char*>(&lengths[j]), 2);
properties["title"] = readString(file, lengths[0]);
properties["author"] = readString(file, lengths[1]);
properties["copyright"] = readString(file, lengths[2]);
properties["description"] = readString(file, lengths[3]);
properties["rating"] = readString(file, lengths[4]);
}
file.seekg(pos + (std::streampos)(objSize - 24));
}
file.close();
}
void printProperties() {
for (const auto& prop : properties) {
std::cout << prop.first << ": " << prop.second << std::endl;
}
}
void write(const std::string& outputFilename) {
std::ofstream out(outputFilename, std::ios::binary);
if (!out) return;
// Placeholder for header writing
out.close();
}
};
// Usage example:
// int main() {
// WMAParser parser("sample.wma");
// parser.readAndDecode();
// parser.printProperties();
// parser.write("output.wma");
// return 0;
// }
Note: GUID strings are adjusted for little-endian reading; full error handling and big-endian adjustments may be needed for robustness.