You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何根据起始与结束Unix时间从M3U8 HLS播放列表提取片段?

从HLS M3U8播放列表中按Unix时间范围提取片段的实现方案

Python 实现

思路

  1. 解析M3U8文件时保留核心头部标签(如#EXTM3U、#EXT-X-VERSION等),这些是播放列表的必要结构。
  2. 针对每个媒体片段,通过#EXT-X-PROGRAM-DATE-TIME标签获取起始ISO时间,转换为毫秒级Unix时间戳;通过#EXTINF标签获取片段时长,计算出片段结束时间(起始时间 + 时长*1000)。
  3. 对比片段时间范围与目标起始/结束时间,筛选出所有与目标范围有交集的片段,连同对应标签一起保留。
  4. 输出由符合条件的片段组成的新M3U8文件,确保播放兼容性。

代码实现

import datetime

def parse_iso_to_unix_ms(iso_str):
    # 将ISO8601时间字符串转换为毫秒级Unix时间戳
    dt = datetime.datetime.fromisoformat(iso_str.replace('Z', '+00:00'))
    return int(dt.timestamp() * 1000)

def extract_m3u8_segment_by_time(input_m3u8, output_m3u8, start_ms, end_ms):
    with open(input_m3u8, 'r', encoding='utf-8') as f_in, open(output_m3u8, 'w', encoding='utf-8') as f_out:
        current_segment_start = None
        current_segment_duration = None
        is_in_segment = False
        
        # 遍历处理每一行
        for line in f_in:
            line = line.strip()
            if not line:
                continue
            
            # 写入核心头部标签
            if line.startswith('#EXTM3U') or line.startswith('#EXT-X-VERSION') or line.startswith('#EXT-X-TARGETDURATION'):
                f_out.write(line + '\n')
                continue
            
            # 解析片段起始时间
            if line.startswith('#EXT-X-PROGRAM-DATE-TIME:'):
                iso_time = line.split(':', 1)[1]
                current_segment_start = parse_iso_to_unix_ms(iso_time)
                continue
            
            # 解析片段时长
            if line.startswith('#EXTINF:'):
                duration_str = line.split(',')[0].split(':')[1]
                current_segment_duration = float(duration_str)
                is_in_segment = True
                continue
            
            # 处理媒体片段URL
            if is_in_segment and current_segment_start is not None:
                segment_end = current_segment_start + int(current_segment_duration * 1000)
                # 筛选与目标时间范围有交集的片段
                if not (segment_end < start_ms or current_segment_start > end_ms):
                    f_out.write(f'#EXTINF:{current_segment_duration},\n')
                    f_out.write(f'#EXT-X-PROGRAM-DATE-TIME:{datetime.datetime.fromtimestamp(current_segment_start/1000, datetime.timezone.utc).isoformat().replace("+00:00", "Z")}\n')
                    f_out.write(line + '\n')
                is_in_segment = False
                current_segment_start = None
                current_segment_duration = None
        
        # 写入播放列表结束标签
        f_out.write('#EXT-X-ENDLIST\n')

# 使用示例
if __name__ == '__main__':
    INPUT_FILE = 'input.m3u8'
    OUTPUT_FILE = 'output.m3u8'
    START_MS = 1667112116900
    END_MS = 1667112133040
    extract_m3u8_segment_by_time(INPUT_FILE, OUTPUT_FILE, START_MS, END_MS)

关键说明

  • parse_iso_to_unix_ms函数完成ISO时间到毫秒级Unix时间戳的转换,匹配用户提供的时间格式。
  • 筛选逻辑采用交集判断:只要片段结束时间不早于目标起始时间,且片段起始时间不晚于目标结束时间,就保留该片段。
  • 输出的M3U8保留了必要的结构标签,确保能被标准HLS播放器识别。

C++ 实现

思路

  1. 逐行读取M3U8文件,识别并保留核心头部标签。
  2. 解析EXT-X-PROGRAM-DATE-TIME的ISO8601时间字符串,转换为毫秒级Unix时间戳(依赖C++20的<chrono>和<date>库简化时间处理)。
  3. 解析EXTINF的时长信息,计算每个片段的结束时间。
  4. 对比片段时间范围与目标时间,筛选符合条件的片段并写入新文件。

代码实现

#include <iostream>
#include <fstream>
#include <string>
#include <chrono>
#include <date/date.h>
#include <algorithm>
#include <sstream>

using namespace std;
using namespace date;
using namespace chrono;

// 将ISO8601时间字符串转换为毫秒级Unix时间戳
int64_t parse_iso_to_unix_ms(const string& iso_str) {
    sys_seconds tp;
    istringstream ss(iso_str);
    ss >> parse("%Y-%m-%dT%H:%M:%SZ", tp);
    return duration_cast<milliseconds>(tp.time_since_epoch()).count();
}

// 将毫秒级Unix时间戳转换为ISO8601字符串
string unix_ms_to_iso(int64_t ms) {
    sys_time<milliseconds> tp{milliseconds{ms}};
    return format("%Y-%m-%dT%H:%M:%SZ", tp);
}

// 去除字符串首尾空格
string trim(const string& s) {
    auto start = s.begin();
    while (start != s.end() && isspace(*start)) {
        start++;
    }
    auto end = s.end();
    do {
        end--;
    } while (distance(start, end) > 0 && isspace(*end));
    return string(start, end + 1);
}

void extract_m3u8_segment_by_time(const string& input_path, const string& output_path, int64_t start_ms, int64_t end_ms) {
    ifstream in_file(input_path);
    ofstream out_file(output_path);
    if (!in_file.is_open() || !out_file.is_open()) {
        cerr << "Failed to open file" << endl;
        return;
    }

    string line;
    int64_t current_start_ms = -1;
    double current_duration = 0.0;
    bool in_segment = false;

    while (getline(in_file, line)) {
        string trimmed_line = trim(line);
        if (trimmed_line.empty()) continue;

        // 写入核心头部标签
        if (trimmed_line.substr(0, 7) == "#EXTM3U" || trimmed_line.substr(0, 15) == "#EXT-X-VERSION" || trimmed_line.substr(0, 22) == "#EXT-X-TARGETDURATION") {
            out_file << line << endl;
            continue;
        }

        // 解析片段起始时间
        if (trimmed_line.substr(0, 24) == "#EXT-X-PROGRAM-DATE-TIME:") {
            string iso_time = trimmed_line.substr(24);
            current_start_ms = parse_iso_to_unix_ms(iso_time);
            continue;
        }

        // 解析片段时长
        if (trimmed_line.substr(0, 8) == "#EXTINF:") {
            size_t comma_pos = trimmed_line.find(',');
            if (comma_pos != string::npos) {
                string duration_str = trimmed_line.substr(8, comma_pos - 8);
                current_duration = stod(duration_str);
                in_segment = true;
            }
            continue;
        }

        // 处理媒体片段URL
        if (in_segment && current_start_ms != -1) {
            int64_t segment_end_ms = current_start_ms + static_cast<int64_t>(current_duration * 1000);
            // 筛选与目标时间范围有交集的片段
            if (!(segment_end_ms < start_ms || current_start_ms > end_ms)) {
                out_file << "#EXTINF:" << current_duration << ",\n";
                out_file << "#EXT-X-PROGRAM-DATE-TIME:" << unix_ms_to_iso(current_start_ms) << "\n";
                out_file << line << "\n";
            }
            in_segment = false;
            current_start_ms = -1;
            current_duration = 0.0;
        }
    }

    // 写入播放列表结束标签
    out_file << "#EXT-X-ENDLIST\n";

    in_file.close();
    out_file.close();
}

int main() {
    string input_file = "input.m3u8";
    string output_file = "output.m3u8";
    int64_t start_ms = 1667112116900;
    int64_t end_ms = 1667112133040;
    extract_m3u8_segment_by_time(input_file, output_file, start_ms, end_ms);
    return 0;
}

关键说明

  • 代码依赖C20的<date>库,该库已被纳入部分主流编译器的C20标准实现中;若使用的编译器未支持,可手动引入对应的头文件实现时间解析功能。
  • 时间交集判断逻辑与Python版本完全一致,确保跨语言筛选结果一致。
  • 处理文件时保留原始行格式(仅去除首尾空格),避免破坏M3U8的兼容性。

内容的提问来源于stack exchange,提问作者AanisNoor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 02:01:12