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.

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.

WMA Property Dumper
Drag and drop .WMA file here

    

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.