Task 368: .MCU File Format

Task 368: .MCU File Format

File Format Specifications for .M3U

The .M3U file format is a de facto standard for multimedia playlists, originally developed for MP3 files but now used for various audio and video media, including streams. It has no formal specification but is derived from plain text formats. The extended M3U variant introduces directives starting with "#EXT" for metadata.

  1. List of Properties Intrinsic to the .M3U File Format

Based on the format's structure, the intrinsic properties include file metadata, structural elements, and supported directives/tags. These are specific to the format itself and how it is handled in file systems:

  • File Extension: .m3u (basic, local encoding) or .m3u8 (UTF-8 encoded variant, often for streaming).
  • MIME Type: audio/x-mpegurl or audio/mpegurl.
  • Encoding: Local system default for .m3u; UTF-8 for .m3u8.
  • Structure: Plain text file with line-separated entries; each media entry can be a local path (absolute/relative), URL, or preceded by metadata directives. Line endings typically CR LF (\r\n), but LF (\n) may work on some systems.
  • Header Directive: #EXTM3U (required for extended format, first line).
  • Track Information Directive: #EXTINF:duration,title (duration in seconds, -1 for streams; optional key-value attributes like logo="image.jpg").
  • Playlist Title Property: #PLAYLIST:title (overall playlist name, used in IPTV).
  • Group Name Property: #EXTGRP:groupname (for categorizing entries).
  • Album Title Property: #EXTALB:albumtitle (album metadata).
  • Album Artist Property: #EXTART:artist (album artist metadata).
  • Genre Property: #EXTGENRE:genre (album genre metadata).
  • Album Mode Directive: #EXTM3A (indicates playlist for tracks/chapters in a single file).
  • File Size Property: #EXTBYT:bytes (size in bytes for entries).
  • Binary Data Directive: #EXTBIN (indicates binary data follows, rare).
  • Album Art URL Property: #EXTALBUMARTURL:url (link to album artwork).
  • Comments: Lines starting with # (not a directive, for notes; avoid conflicts with extended tags).
  • Media Entries: Paths or URLs (no specific property name, core content).

These properties define the format's capabilities for metadata and organization.

  1. Two Direct Download Links for .M3U Files
  1. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .M3U Parser

This is an embeddable HTML snippet with JavaScript for a Ghost blog (or any HTML-compatible platform). It creates a drop zone where users can drag and drop a .M3U file, parses it, extracts the properties listed above, and dumps them to the screen.

Drag and drop a .M3U file here
  1. Python Class for .M3U Handling

This class opens, reads, parses, prints properties, and can write a new .M3U file.

import os

class M3UHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {
            'extension': '.m3u',
            'mime_type': 'audio/x-mpegurl',
            'encoding': 'utf-8',  # Assume UTF-8; adjust if needed
            'structure': 'Plain text lines',
            'header': '',
            'extinf': [],
            'playlist': '',
            'extgrp': [],
            'extalb': '',
            'extart': '',
            'extgenre': '',
            'extm3a': '',
            'extbyt': [],
            'extbin': '',
            'extalbumarturl': '',
            'comments': [],
            'media_entries': []
        }

    def read_and_decode(self):
        try:
            with open(self.filepath, 'r', encoding=self.properties['encoding']) as f:
                content = f.read()
            lines = content.splitlines()
            for line in lines:
                line = line.strip()
                if line.startswith('#EXTM3U'):
                    self.properties['header'] = line
                elif line.startswith('#EXTINF:'):
                    self.properties['extinf'].append(line)
                elif line.startswith('#PLAYLIST:'):
                    self.properties['playlist'] = line
                elif line.startswith('#EXTGRP:'):
                    self.properties['extgrp'].append(line)
                elif line.startswith('#EXTALB:'):
                    self.properties['extalb'] = line
                elif line.startswith('#EXTART:'):
                    self.properties['extart'] = line
                elif line.startswith('#EXTGENRE:'):
                    self.properties['extgenre'] = line
                elif line.startswith('#EXTM3A'):
                    self.properties['extm3a'] = line
                elif line.startswith('#EXTBYT:'):
                    self.properties['extbyt'].append(line)
                elif line.startswith('#EXTBIN'):
                    self.properties['extbin'] = line
                elif line.startswith('#EXTALBUMARTURL:'):
                    self.properties['extalbumarturl'] = line
                elif line.startswith('#') and not line.startswith('#EXT'):
                    self.properties['comments'].append(line)
                elif line:
                    self.properties['media_entries'].append(line)
        except Exception as e:
            print(f"Error reading file: {e}")

    def print_properties(self):
        for key, value in self.properties.items():
            if isinstance(value, list):
                print(f"{key}: {value if value else 'None'}")
            else:
                print(f"{key}: {value or 'None'}")

    def write(self, new_filepath):
        try:
            with open(new_filepath, 'w', encoding=self.properties['encoding']) as f:
                if self.properties['header']:
                    f.write(self.properties['header'] + '\n')
                if self.properties['playlist']:
                    f.write(self.properties['playlist'] + '\n')
                if self.properties['extalb']:
                    f.write(self.properties['extalb'] + '\n')
                if self.properties['extart']:
                    f.write(self.properties['extart'] + '\n')
                if self.properties['extgenre']:
                    f.write(self.properties['extgenre'] + '\n')
                if self.properties['extm3a']:
                    f.write(self.properties['extm3a'] + '\n')
                if self.properties['extbin']:
                    f.write(self.properties['extbin'] + '\n')
                if self.properties['extalbumarturl']:
                    f.write(self.properties['extalbumarturl'] + '\n')
                for comment in self.properties['comments']:
                    f.write(comment + '\n')
                for grp in self.properties['extgrp']:
                    f.write(grp + '\n')
                for i in range(max(len(self.properties['extinf']), len(self.properties['extbyt']), len(self.properties['media_entries']))):
                    if i < len(self.properties['extinf']):
                        f.write(self.properties['extinf'][i] + '\n')
                    if i < len(self.properties['extbyt']):
                        f.write(self.properties['extbyt'][i] + '\n')
                    if i < len(self.properties['media_entries']):
                        f.write(self.properties['media_entries'][i] + '\n')
            print(f"File written to {new_filepath}")
        except Exception as e:
            print(f"Error writing file: {e}")

# Example usage:
# handler = M3UHandler('example.m3u')
# handler.read_and_decode()
# handler.print_properties()
# handler.write('new.m3u')
  1. Java Class for .M3U Handling

This class opens, reads, parses, prints properties, and can write a new .M3U file.

import java.io.*;
import java.util.*;

public class M3UHandler {
    private String filepath;
    private Map<String, Object> properties;

    public M3UHandler(String filepath) {
        this.filepath = filepath;
        this.properties = new HashMap<>();
        properties.put("extension", ".m3u");
        properties.put("mime_type", "audio/x-mpegurl");
        properties.put("encoding", "UTF-8");
        properties.put("structure", "Plain text lines");
        properties.put("header", "");
        properties.put("extinf", new ArrayList<String>());
        properties.put("playlist", "");
        properties.put("extgrp", new ArrayList<String>());
        properties.put("extalb", "");
        properties.put("extart", "");
        properties.put("extgenre", "");
        properties.put("extm3a", "");
        properties.put("extbyt", new ArrayList<String>());
        properties.put("extbin", "");
        properties.put("extalbumarturl", "");
        properties.put("comments", new ArrayList<String>());
        properties.put("media_entries", new ArrayList<String>());
    }

    public void readAndDecode() {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filepath), (String) properties.get("encoding")))) {
            String line;
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (line.startsWith("#EXTM3U")) {
                    properties.put("header", line);
                } else if (line.startsWith("#EXTINF:")) {
                    ((List<String>) properties.get("extinf")).add(line);
                } else if (line.startsWith("#PLAYLIST:")) {
                    properties.put("playlist", line);
                } else if (line.startsWith("#EXTGRP:")) {
                    ((List<String>) properties.get("extgrp")).add(line);
                } else if (line.startsWith("#EXTALB:")) {
                    properties.put("extalb", line);
                } else if (line.startsWith("#EXTART:")) {
                    properties.put("extart", line);
                } else if (line.startsWith("#EXTGENRE:")) {
                    properties.put("extgenre", line);
                } else if (line.startsWith("#EXTM3A")) {
                    properties.put("extm3a", line);
                } else if (line.startsWith("#EXTBYT:")) {
                    ((List<String>) properties.get("extbyt")).add(line);
                } else if (line.startsWith("#EXTBIN")) {
                    properties.put("extbin", line);
                } else if (line.startsWith("#EXTALBUMARTURL:")) {
                    properties.put("extalbumarturl", line);
                } else if (line.startsWith("#") && !line.startsWith("#EXT")) {
                    ((List<String>) properties.get("comments")).add(line);
                } else if (!line.isEmpty()) {
                    ((List<String>) properties.get("media_entries")).add(line);
                }
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }

    public void printProperties() {
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            if (entry.getValue() instanceof List) {
                List<?> list = (List<?>) entry.getValue();
                System.out.println(entry.getKey() + ": " + (list.isEmpty() ? "None" : list));
            } else {
                System.out.println(entry.getKey() + ": " + (entry.getValue().equals("") ? "None" : entry.getValue()));
            }
        }
    }

    public void write(String newFilepath) {
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFilepath), (String) properties.get("encoding")))) {
            if (!((String) properties.get("header")).isEmpty()) {
                writer.write((String) properties.get("header") + "\n");
            }
            if (!((String) properties.get("playlist")).isEmpty()) {
                writer.write((String) properties.get("playlist") + "\n");
            }
            if (!((String) properties.get("extalb")).isEmpty()) {
                writer.write((String) properties.get("extalb") + "\n");
            }
            if (!((String) properties.get("extart")).isEmpty()) {
                writer.write((String) properties.get("extart") + "\n");
            }
            if (!((String) properties.get("extgenre")).isEmpty()) {
                writer.write((String) properties.get("extgenre") + "\n");
            }
            if (!((String) properties.get("extm3a")).isEmpty()) {
                writer.write((String) properties.get("extm3a") + "\n");
            }
            if (!((String) properties.get("extbin")).isEmpty()) {
                writer.write((String) properties.get("extbin") + "\n");
            }
            if (!((String) properties.get("extalbumarturl")).isEmpty()) {
                writer.write((String) properties.get("extalbumarturl") + "\n");
            }
            for (String comment : (List<String>) properties.get("comments")) {
                writer.write(comment + "\n");
            }
            for (String grp : (List<String>) properties.get("extgrp")) {
                writer.write(grp + "\n");
            }
            List<String> extinf = (List<String>) properties.get("extinf");
            List<String> extbyt = (List<String>) properties.get("extbyt");
            List<String> media = (List<String>) properties.get("media_entries");
            int maxLen = Math.max(extinf.size(), Math.max(extbyt.size(), media.size()));
            for (int i = 0; i < maxLen; i++) {
                if (i < extinf.size()) writer.write(extinf.get(i) + "\n");
                if (i < extbyt.size()) writer.write(extbyt.get(i) + "\n");
                if (i < media.size()) writer.write(media.get(i) + "\n");
            }
            System.out.println("File written to " + newFilepath);
        } catch (IOException e) {
            System.out.println("Error writing file: " + e.getMessage());
        }
    }

    // Example usage:
    // public static void main(String[] args) {
    //     M3UHandler handler = new M3UHandler("example.m3u");
    //     handler.readAndDecode();
    //     handler.printProperties();
    //     handler.write("new.m3u");
    // }
}
  1. JavaScript Class for .M3U Handling

This class (ES6) opens (via FileReader for browser/node), parses, prints properties to console, and can write (browser: download blob; node: fs).

class M3UHandler {
  constructor(filepath) {
    this.filepath = filepath;
    this.properties = {
      extension: '.m3u',
      mime_type: 'audio/x-mpegurl',
      encoding: 'utf-8',
      structure: 'Plain text lines',
      header: '',
      extinf: [],
      playlist: '',
      extgrp: [],
      extalb: '',
      extart: '',
      extgenre: '',
      extm3a: '',
      extbyt: [],
      extbin: '',
      extalbumarturl: '',
      comments: [],
      media_entries: []
    };
  }

  async readAndDecode() {
    // For browser: assume filepath is a File object; for Node: use fs
    let content;
    if (typeof window === 'undefined') { // Node.js
      const fs = require('fs');
      try {
        content = fs.readFileSync(this.filepath, this.properties.encoding);
      } catch (e) {
        console.error('Error reading file:', e);
        return;
      }
    } else { // Browser
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = (e) => {
          content = e.target.result;
          this.parse(content);
          resolve();
        };
        reader.onerror = reject;
        reader.readAsText(this.filepath); // filepath should be File object
      });
    }
    this.parse(content);
  }

  parse(content) {
    const lines = content.split(/\r?\n/);
    lines.forEach(line => {
      line = line.trim();
      if (line.startsWith('#EXTM3U')) this.properties.header = line;
      else if (line.startsWith('#EXTINF:')) this.properties.extinf.push(line);
      else if (line.startsWith('#PLAYLIST:')) this.properties.playlist = line;
      else if (line.startsWith('#EXTGRP:')) this.properties.extgrp.push(line);
      else if (line.startsWith('#EXTALB:')) this.properties.extalb = line;
      else if (line.startsWith('#EXTART:')) this.properties.extart = line;
      else if (line.startsWith('#EXTGENRE:')) this.properties.extgenre = line;
      else if (line.startsWith('#EXTM3A')) this.properties.extm3a = line;
      else if (line.startsWith('#EXTBYT:')) this.properties.extbyt.push(line);
      else if (line.startsWith('#EXTBIN')) this.properties.extbin = line;
      else if (line.startsWith('#EXTALBUMARTURL:')) this.properties.extalbumarturl = line;
      else if (line.startsWith('#') && !line.startsWith('#EXT')) this.properties.comments.push(line);
      else if (line) this.properties.media_entries.push(line);
    });
  }

  printProperties() {
    Object.entries(this.properties).forEach(([key, value]) => {
      if (Array.isArray(value)) {
        console.log(`${key}: ${value.length > 0 ? value : 'None'}`);
      } else {
        console.log(`${key}: ${value || 'None'}`);
      }
    });
  }

  write(newFilepath) {
    let content = '';
    if (this.properties.header) content += this.properties.header + '\n';
    if (this.properties.playlist) content += this.properties.playlist + '\n';
    if (this.properties.extalb) content += this.properties.extalb + '\n';
    if (this.properties.extart) content += this.properties.extart + '\n';
    if (this.properties.extgenre) content += this.properties.extgenre + '\n';
    if (this.properties.extm3a) content += this.properties.extm3a + '\n';
    if (this.properties.extbin) content += this.properties.extbin + '\n';
    if (this.properties.extalbumarturl) content += this.properties.extalbumarturl + '\n';
    this.properties.comments.forEach(c => content += c + '\n');
    this.properties.extgrp.forEach(g => content += g + '\n');
    const maxLen = Math.max(this.properties.extinf.length, this.properties.extbyt.length, this.properties.media_entries.length);
    for (let i = 0; i < maxLen; i++) {
      if (i < this.properties.extinf.length) content += this.properties.extinf[i] + '\n';
      if (i < this.properties.extbyt.length) content += this.properties.extbyt[i] + '\n';
      if (i < this.properties.media_entries.length) content += this.properties.media_entries[i] + '\n';
    }
    if (typeof window === 'undefined') { // Node.js
      const fs = require('fs');
      try {
        fs.writeFileSync(newFilepath, content, this.properties.encoding);
        console.log(`File written to ${newFilepath}`);
      } catch (e) {
        console.error('Error writing file:', e);
      }
    } else { // Browser: trigger download
      const blob = new Blob([content], { type: 'text/plain' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = newFilepath || 'new.m3u';
      a.click();
      URL.revokeObjectURL(url);
      console.log('File downloaded as new.m3u');
    }
  }
}

// Example usage (Node):
// const handler = new M3UHandler('example.m3u');
// handler.readAndDecode();
// handler.printProperties();
// handler.write('new.m3u');

// Browser: pass File object instead of string filepath.
  1. C "Class" (Struct with Functions) for .M3U Handling

In C, we use a struct and functions. This opens, reads, parses, prints to console, and writes a new .M3U file. Assumes UTF-8; uses stdio for I/O.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINES 1000
#define MAX_LINE_LEN 1024

typedef struct {
    char *filepath;
    char *extension;
    char *mime_type;
    char *encoding;
    char *structure;
    char *header;
    char **extinf;
    int extinf_count;
    char *playlist;
    char **extgrp;
    int extgrp_count;
    char *extalb;
    char *extart;
    char *extgenre;
    char *extm3a;
    char **extbyt;
    int extbyt_count;
    char *extbin;
    char *extalbumarturl;
    char **comments;
    int comments_count;
    char **media_entries;
    int media_entries_count;
} M3UHandler;

M3UHandler* create_m3u_handler(const char *filepath) {
    M3UHandler *handler = (M3UHandler*)malloc(sizeof(M3UHandler));
    handler->filepath = strdup(filepath);
    handler->extension = ".m3u";
    handler->mime_type = "audio/x-mpegurl";
    handler->encoding = "UTF-8";
    handler->structure = "Plain text lines";
    handler->header = NULL;
    handler->extinf = (char**)malloc(MAX_LINES * sizeof(char*));
    handler->extinf_count = 0;
    handler->playlist = NULL;
    handler->extgrp = (char**)malloc(MAX_LINES * sizeof(char*));
    handler->extgrp_count = 0;
    handler->extalb = NULL;
    handler->extart = NULL;
    handler->extgenre = NULL;
    handler->extm3a = NULL;
    handler->extbyt = (char**)malloc(MAX_LINES * sizeof(char*));
    handler->extbyt_count = 0;
    handler->extbin = NULL;
    handler->extalbumarturl = NULL;
    handler->comments = (char**)malloc(MAX_LINES * sizeof(char*));
    handler->comments_count = 0;
    handler->media_entries = (char**)malloc(MAX_LINES * sizeof(char*));
    handler->media_entries_count = 0;
    return handler;
}

void read_and_decode(M3UHandler *handler) {
    FILE *file = fopen(handler->filepath, "r");
    if (!file) {
        printf("Error opening file: %s\n", handler->filepath);
        return;
    }
    char line[MAX_LINE_LEN];
    while (fgets(line, sizeof(line), file)) {
        char *trimmed = strtok(line, "\r\n");
        if (!trimmed) continue;
        if (strstr(trimmed, "#EXTM3U") == trimmed) {
            handler->header = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTINF:") == trimmed) {
            handler->extinf[handler->extinf_count++] = strdup(trimmed);
        } else if (strstr(trimmed, "#PLAYLIST:") == trimmed) {
            handler->playlist = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTGRP:") == trimmed) {
            handler->extgrp[handler->extgrp_count++] = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTALB:") == trimmed) {
            handler->extalb = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTART:") == trimmed) {
            handler->extart = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTGENRE:") == trimmed) {
            handler->extgenre = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTM3A") == trimmed) {
            handler->extm3a = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTBYT:") == trimmed) {
            handler->extbyt[handler->extbyt_count++] = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTBIN") == trimmed) {
            handler->extbin = strdup(trimmed);
        } else if (strstr(trimmed, "#EXTALBUMARTURL:") == trimmed) {
            handler->extalbumarturl = strdup(trimmed);
        } else if (trimmed[0] == '#' && strstr(trimmed, "#EXT") != trimmed) {
            handler->comments[handler->comments_count++] = strdup(trimmed);
        } else if (strlen(trimmed) > 0) {
            handler->media_entries[handler->media_entries_count++] = strdup(trimmed);
        }
    }
    fclose(file);
}

void print_properties(M3UHandler *handler) {
    printf("extension: %s\n", handler->extension);
    printf("mime_type: %s\n", handler->mime_type);
    printf("encoding: %s\n", handler->encoding);
    printf("structure: %s\n", handler->structure);
    printf("header: %s\n", handler->header ? handler->header : "None");
    printf("extinf: ");
    if (handler->extinf_count == 0) printf("None\n");
    else {
        for (int i = 0; i < handler->extinf_count; i++) printf("%s ", handler->extinf[i]);
        printf("\n");
    }
    printf("playlist: %s\n", handler->playlist ? handler->playlist : "None");
    printf("extgrp: ");
    if (handler->extgrp_count == 0) printf("None\n");
    else {
        for (int i = 0; i < handler->extgrp_count; i++) printf("%s ", handler->extgrp[i]);
        printf("\n");
    }
    printf("extalb: %s\n", handler->extalb ? handler->extalb : "None");
    printf("extart: %s\n", handler->extart ? handler->extart : "None");
    printf("extgenre: %s\n", handler->extgenre ? handler->extgenre : "None");
    printf("extm3a: %s\n", handler->extm3a ? handler->extm3a : "None");
    printf("extbyt: ");
    if (handler->extbyt_count == 0) printf("None\n");
    else {
        for (int i = 0; i < handler->extbyt_count; i++) printf("%s ", handler->extbyt[i]);
        printf("\n");
    }
    printf("extbin: %s\n", handler->extbin ? handler->extbin : "None");
    printf("extalbumarturl: %s\n", handler->extalbumarturl ? handler->extalbumarturl : "None");
    printf("comments: ");
    if (handler->comments_count == 0) printf("None\n");
    else {
        for (int i = 0; i < handler->comments_count; i++) printf("%s ", handler->comments[i]);
        printf("\n");
    }
    printf("media_entries: ");
    if (handler->media_entries_count == 0) printf("None\n");
    else {
        for (int i = 0; i < handler->media_entries_count; i++) printf("%s ", handler->media_entries[i]);
        printf("\n");
    }
}

void write_m3u(M3UHandler *handler, const char *new_filepath) {
    FILE *file = fopen(new_filepath, "w");
    if (!file) {
        printf("Error opening file for write: %s\n", new_filepath);
        return;
    }
    if (handler->header) fprintf(file, "%s\n", handler->header);
    if (handler->playlist) fprintf(file, "%s\n", handler->playlist);
    if (handler->extalb) fprintf(file, "%s\n", handler->extalb);
    if (handler->extart) fprintf(file, "%s\n", handler->extart);
    if (handler->extgenre) fprintf(file, "%s\n", handler->extgenre);
    if (handler->extm3a) fprintf(file, "%s\n", handler->extm3a);
    if (handler->extbin) fprintf(file, "%s\n", handler->extbin);
    if (handler->extalbumarturl) fprintf(file, "%s\n", handler->extalbumarturl);
    for (int i = 0; i < handler->comments_count; i++) fprintf(file, "%s\n", handler->comments[i]);
    for (int i = 0; i < handler->extgrp_count; i++) fprintf(file, "%s\n", handler->extgrp[i]);
    int max_count = handler->extinf_count > handler->extbyt_count ? handler->extinf_count : handler->extbyt_count;
    max_count = max_count > handler->media_entries_count ? max_count : handler->media_entries_count;
    for (int i = 0; i < max_count; i++) {
        if (i < handler->extinf_count) fprintf(file, "%s\n", handler->extinf[i]);
        if (i < handler->extbyt_count) fprintf(file, "%s\n", handler->extbyt[i]);
        if (i < handler->media_entries_count) fprintf(file, "%s\n", handler->media_entries[i]);
    }
    fclose(file);
    printf("File written to %s\n", new_filepath);
}

void free_m3u_handler(M3UHandler *handler) {
    free(handler->filepath);
    if (handler->header) free(handler->header);
    for (int i = 0; i < handler->extinf_count; i++) free(handler->extinf[i]);
    free(handler->extinf);
    if (handler->playlist) free(handler->playlist);
    for (int i = 0; i < handler->extgrp_count; i++) free(handler->extgrp[i]);
    free(handler->extgrp);
    if (handler->extalb) free(handler->extalb);
    if (handler->extart) free(handler->extart);
    if (handler->extgenre) free(handler->extgenre);
    if (handler->extm3a) free(handler->extm3a);
    for (int i = 0; i < handler->extbyt_count; i++) free(handler->extbyt[i]);
    free(handler->extbyt);
    if (handler->extbin) free(handler->extbin);
    if (handler->extalbumarturl) free(handler->extalbumarturl);
    for (int i = 0; i < handler->comments_count; i++) free(handler->comments[i]);
    free(handler->comments);
    for (int i = 0; i < handler->media_entries_count; i++) free(handler->media_entries[i]);
    free(handler->media_entries);
    free(handler);
}

// Example usage:
// int main() {
//     M3UHandler *handler = create_m3u_handler("example.m3u");
//     read_and_decode(handler);
//     print_properties(handler);
//     write_m3u(handler, "new.m3u");
//     free_m3u_handler(handler);
//     return 0;
// }