Task 369: .M3U8 File Format

Task 369: .M3U8 File Format

File Format Specifications for .M3U8

The .M3U8 file format is a UTF-8 encoded extension of the M3U playlist format, specifically designed for HTTP Live Streaming (HLS). It is defined in IETF RFC 8216 as a text-based playlist file that contains URIs to media segments or other playlists, along with metadata tags starting with "#EXT". Playlists can be Media Playlists (listing media segments for playback) or Master Playlists (listing variant streams for adaptive bitrate). The format supports live streaming, VOD, encryption, and alternate renditions. Files must not contain a BOM, use NFC normalization, and avoid UTF-8 control characters except CR and LF. URIs can be absolute or relative, and lines are either blank (ignored), comments (# not followed by EXT), tags (#EXT...), or URIs.

  1. List of all properties of this file format intrinsic to its file system:

Based on the specification, the intrinsic properties refer to the structural elements and tags defined in the format. These include encoding, line structure, and all possible tags with their attributes. Here's a comprehensive list:

  • Encoding: UTF-8 (required), with no Byte Order Mark (BOM). All text must be normalized to Unicode Normalization Form C (NFC).
  • File Structure: Text file with lines terminated by CRLF or LF. Lines can be: URIs (pointing to media segments or sub-playlists), blank (ignored), comments (starting with # but not #EXT), or tags (starting with #EXT, case-sensitive).
  • MIME Type: application/vnd.apple.mpegurl or audio/mpegurl.
  • Extension: .m3u8 (or .m3u for non-UTF-8, but .m3u8 implies UTF-8).
  • Playlist Types: Media Playlist (contains media segment URIs) or Master Playlist (contains variant playlist URIs); mixed invalid.
  • Attribute Lists: Comma-separated pairs in tags (e.g., ATTR="value"), with types: decimal-integer, hexadecimal-sequence, decimal-floating-point, signed-decimal-floating-point, quoted-string, enumerated-string, decimal-resolution.
  • Basic Tags (applicable to both Media and Master Playlists):
  • #EXTM3U: Marks the file as extended M3U; must be first line. No attributes.
  • #EXT-X-VERSION:<n>: Protocol version (n = decimal-integer, e.g., 7). Required for compatibility.
  • Media Segment Tags (apply to individual segments in Media Playlists):
  • #EXTINF:<duration>,[<title>]: Segment duration (decimal-floating-point seconds) and optional title (UTF-8 text).
  • #EXT-X-BYTERANGE:<n>[@<o>]: Byte range sub-segment (n = length, o = optional offset).
  • #EXT-X-DISCONTINUITY: Marks a discontinuity (e.g., format change). No attributes.
  • #EXT-X-KEY:<attribute-list>: Encryption info. Attributes: METHOD (NONE/AES-128/SAMPLE-AES), URI (key location), IV (hex init vector), KEYFORMAT (quoted-string), KEYFORMATVERSIONS (quoted versions).
  • #EXT-X-MAP:<attribute-list>: Media Initialization Section. Attributes: URI (required), BYTERANGE (optional quoted range).
  • #EXT-X-PROGRAM-DATE-TIME:<date-time-msec>: Absolute date/time for segment (ISO 8601 format, e.g., 2025-09-29T12:00:00Z).
  • Media Playlist Tags (apply to entire Media Playlists):
  • #EXT-X-TARGETDURATION:: Max segment duration (s = integer seconds).
  • #EXT-X-MEDIA-SEQUENCE:<number>: Starting sequence number (number = integer).
  • #EXT-X-DISCONTINUITY-SEQUENCE:<number>: Discontinuity counter (number = integer).
  • #EXT-X-ENDLIST: Ends the playlist (no more segments). No attributes.
  • #EXT-X-PLAYLIST-TYPE:<type>: Type (EVENT or VOD).
  • #EXT-X-I-FRAMES-ONLY: Indicates I-frames only. No attributes.
  • Master Playlist Tags (apply to Master Playlists):
  • #EXT-X-MEDIA:<attribute-list>: Alternate rendition. Attributes: TYPE (AUDIO/VIDEO/SUBTITLES/CLOSED-CAPTIONS), GROUP-ID, NAME, DEFAULT, AUTOSELECT, FORCED, LANGUAGE, ASSOC-LANGUAGE, URI, INSTREAM-ID, CHARACTERISTICS, CHANNELS.
  • #EXT-X-STREAM-INF:<attribute-list>: Variant stream. Attributes: BANDWIDTH (required), AVERAGE-BANDWIDTH, CODECS, RESOLUTION, FRAME-RATE, HDCP-LEVEL, AUDIO, VIDEO, SUBTITLES, CLOSED-CAPTIONS.
  • #EXT-X-I-FRAME-STREAM-INF:<attribute-list>: I-frame variant. Similar to EXT-X-STREAM-INF but with URI required.
  • #EXT-X-SESSION-DATA:<attribute-list>: Session data. Attributes: DATA-ID, VALUE or URI, LANGUAGE.
  • #EXT-X-SESSION-KEY:<attribute-list>: Session-wide key. Same attributes as EXT-X-KEY.
  • Media or Master Playlist Tags:
  • #EXT-X-INDEPENDENT-SEGMENTS: Indicates independent segments. No attributes.
  • #EXT-X-START:<attribute-list>: Suggested start point. Attributes: TIME-OFFSET (signed-float seconds), PRECISE (YES/NO).
  1. Two direct download links for .M3U8 files:
  1. Ghost blog embedded HTML JavaScript for drag-and-drop .M3U8 file dump:
M3U8 Property Dumper
Drag and drop .M3U8 file here

Python class for .M3U8 handling:

import os

class M3U8Handler:
    def __init__(self):
        self.properties = {
            'encoding': 'UTF-8',
            'tags': [],
            'uris': [],
            'version': 'Not specified',
            'target_duration': 'Not specified',
            # Add placeholders for other properties from the list
            'media_sequence': 'Not specified',
            'playlist_type': 'Not specified',
            # etc.
        }

    def load(self, filepath):
        if not os.path.exists(filepath) or not filepath.endswith('.m3u8'):
            raise ValueError("Invalid .M3U8 file")
        with open(filepath, 'r', encoding='utf-8') as f:
            content = f.read()
        self.parse(content)

    def parse(self, content):
        lines = content.splitlines()
        for line in lines:
            line = line.strip()
            if line.startswith('#EXT'):
                self.properties['tags'].append(line)
                if line.startswith('#EXT-X-VERSION:'):
                    self.properties['version'] = line.split(':')[1]
                elif line.startswith('#EXT-X-TARGETDURATION:'):
                    self.properties['target_duration'] = line.split(':')[1]
                elif line.startswith('#EXT-X-MEDIA-SEQUENCE:'):
                    self.properties['media_sequence'] = line.split(':')[1]
                elif line.startswith('#EXT-X-PLAYLIST-TYPE:'):
                    self.properties['playlist_type'] = line.split(':')[1]
                # Add parsing for other tags as needed
            elif line and not line.startswith('#'):
                self.properties['uris'].append(line)

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def save(self, filepath):
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write('#EXTM3U\n')  # Basic header
            for tag in self.properties['tags']:
                f.write(tag + '\n')
            for uri in self.properties['uris']:
                f.write(uri + '\n')

# Example usage:
# handler = M3U8Handler()
# handler.load('example.m3u8')
# handler.print_properties()
# handler.save('output.m3u8')

This class loads a file, decodes/parses tags and URIs, extracts key properties, prints them to console, and can write a new .M3U8 file with the parsed content.

  1. Java class for .M3U8 handling:
import java.io.*;
import java.util.*;

public class M3U8Handler {
    private Map<String, Object> properties = new HashMap<>();

    public M3U8Handler() {
        properties.put("encoding", "UTF-8");
        properties.put("tags", new ArrayList<String>());
        properties.put("uris", new ArrayList<String>());
        properties.put("version", "Not specified");
        properties.put("target_duration", "Not specified");
        properties.put("media_sequence", "Not specified");
        properties.put("playlist_type", "Not specified");
        // Add more as needed
    }

    public void load(String filepath) throws IOException {
        if (!filepath.endsWith(".m3u8")) {
            throw new IllegalArgumentException("Invalid .M3U8 file");
        }
        StringBuilder content = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filepath), "UTF-8"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
        }
        parse(content.toString());
    }

    private void parse(String content) {
        String[] lines = content.split("\n");
        List<String> tags = (List<String>) properties.get("tags");
        List<String> uris = (List<String>) properties.get("uris");
        for (String line : lines) {
            line = line.trim();
            if (line.startsWith("#EXT")) {
                tags.add(line);
                if (line.startsWith("#EXT-X-VERSION:")) {
                    properties.put("version", line.split(":")[1]);
                } else if (line.startsWith("#EXT-X-TARGETDURATION:")) {
                    properties.put("target_duration", line.split(":")[1]);
                } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE:")) {
                    properties.put("media_sequence", line.split(":")[1]);
                } else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:")) {
                    properties.put("playlist_type", line.split(":")[1]);
                }
                // Add more parsing
            } else if (!line.isEmpty() && !line.startsWith("#")) {
                uris.add(line);
            }
        }
    }

    public void printProperties() {
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void save(String filepath) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filepath), "UTF-8"))) {
            writer.write("#EXTM3U\n");
            List<String> tags = (List<String>) properties.get("tags");
            for (String tag : tags) {
                writer.write(tag + "\n");
            }
            List<String> uris = (List<String>) properties.get("uris");
            for (String uri : uris) {
                writer.write(uri + "\n");
            }
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     M3U8Handler handler = new M3U8Handler();
    //     handler.load("example.m3u8");
    //     handler.printProperties();
    //     handler.save("output.m3u8");
    // }
}

This Java class opens a file, decodes/parses, prints properties to console, and writes a new file.

  1. JavaScript class for .M3U8 handling:
class M3U8Handler {
    constructor() {
        this.properties = {
            encoding: 'UTF-8',
            tags: [],
            uris: [],
            version: 'Not specified',
            target_duration: 'Not specified',
            media_sequence: 'Not specified',
            playlist_type: 'Not specified',
            // Add more
        };
    }

    load(content) {  // Pass content as string (e.g., from FileReader)
        this.parse(content);
    }

    parse(content) {
        const lines = content.split(/\r?\n/);
        for (let line of lines) {
            line = line.trim();
            if (line.startsWith('#EXT')) {
                this.properties.tags.push(line);
                if (line.startsWith('#EXT-X-VERSION:')) {
                    this.properties.version = line.split(':')[1];
                } else if (line.startsWith('#EXT-X-TARGETDURATION:')) {
                    this.properties.target_duration = line.split(':')[1];
                } else if (line.startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
                    this.properties.media_sequence = line.split(':')[1];
                } else if (line.startsWith('#EXT-X-PLAYLIST-TYPE:')) {
                    this.properties.playlist_type = line.split(':')[1];
                }
                // Add more
            } else if (line && !line.startsWith('#')) {
                this.properties.uris.push(line);
            }
        }
    }

    printProperties() {
        for (let key in this.properties) {
            console.log(`${key}: ${JSON.stringify(this.properties[key])}`);
        }
    }

    save() {  // Returns string for writing (e.g., via Blob)
        let output = '#EXTM3U\n';
        for (let tag of this.properties.tags) {
            output += tag + '\n';
        }
        for (let uri of this.properties.uris) {
            output += uri + '\n';
        }
        return output;
    }
}

// Example usage:
// const handler = new M3U8Handler();
// handler.load(someM3U8ContentString);
// handler.printProperties();
// const savedContent = handler.save();
// // Use savedContent to download or write

This JS class takes content (e.g., from file read), parses, prints to console, and returns a string for writing.

  1. C++ class for .M3U8 handling:
#include #include #include #include #include #include class M3U8Handler { private: std::map properties; std::vector tags; std::vector uris; public: M3U8Handler() { properties["encoding"] = "UTF-8"; properties["version"] = "Not specified"; properties["target_duration"] = "Not specified"; properties["media_sequence"] = "Not specified"; properties["playlist_type"] = "Not specified"; // Add more } void load(const std::string& filepath) { if (filepath.substr(filepath.find_last_of(".") + 1) != "m3u8") { throw std::invalid_argument("Invalid .M3U8 file"); } std::ifstream file(filepath); if (!file.is_open()) { throw std::runtime_error("Could not open file"); } std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); parse(content); } void parse(const std::string& content) { std::istringstream iss(content); std::string line; while (std::getline(iss, line)) { line.erase(0, line.find_first_not_of(" \t")); line.erase(line.find_last_not_of(" \t") + 1); if (line.rfind("#EXT", 0) == 0) { tags.push_back(line); if (line.rfind("#EXT-X-VERSION:", 0) == 0) { properties["version"] = line.substr(15); } else if (line.rfind("#EXT-X-TARGETDURATION:", 0) == 0) { properties["target_duration"] = line.substr(22); } else if (line.rfind("#EXT-X-MEDIA-SEQUENCE:", 0) == 0) { properties["media_sequence"] = line.substr(22); } else if (line.rfind("#EXT-X-PLAYLIST-TYPE:", 0) == 0) { properties["playlist_type"] = line.substr(21); } // Add more } else if (!line.empty() && line[0] != '#') { uris.push_back(line); } } } void printProperties() const { for (const auto& prop : properties) { std::cout << prop.first << ": " << prop.second << std::endl; } std::cout << "tags: "; for (const auto& tag : tags) { std::cout << tag << " "; } std::cout << std::endl; std::cout << "uris: "; for (const auto& uri : uris) { std::cout << uri << " "; } std::cout << std::endl; } void save(const std::string& filepath) const { std::ofstream file(filepath); if (!file.is_open()) { throw std::runtime_error("Could not open file for writing"); } file << "#EXTM3U\n"; for (const auto& tag : tags) { file << tag << "\n"; } for (const auto& uri : uris) { file << uri << "\n"; } } }; // Example usage: // int main() { // M3U8Handler handler; // try { // handler.load("example.m3u8"); // handler.printProperties(); // handler.save("output.m3u8"); // } catch (const std::exception& e) { // std::cerr << e.what() << std::endl; // } // return 0; // }