Skip to content

Instantly share code, notes, and snippets.

@ITotalJustice
Created November 9, 2021 18:01
Show Gist options
  • Save ITotalJustice/9f51329d57dee259217d9f68d7b00f8f to your computer and use it in GitHub Desktop.
Save ITotalJustice/9f51329d57dee259217d9f68d7b00f8f to your computer and use it in GitHub Desktop.
crapi hls parser
#include "hlsparse.hpp"
#include "../../utils/logger.hpp"
#ifndef NDEBUG
#include <iostream>
#endif
#include <cassert>
#include <ranges>
#include <algorithm>
namespace hls {
static constexpr std::string_view MASTER_MAGIC = "#EXTM3U";
static constexpr std::string_view META_MAGIC = "#EXT-X-STREAM-INF:";
// SOURCE: https://www.bfilipek.com/2018/07/string-view-perf-followup.html
static auto splitSV(std::string_view strv, char delim) -> std::vector<std::string_view> {
std::vector<std::string_view> output;
std::size_t first{};
while (first < strv.size()) {
const auto second = strv.find_first_of(delim, first);
if (first != second) {
output.emplace_back(strv.substr(first, second - first));
}
if (second == std::string_view::npos) {
break;
}
first = second + 1;
}
return output;
}
static constexpr auto CHAR_INT_MIX = 48;
static constexpr auto CHAR_INT_MAX = 57;
static constexpr inline auto IsInt(const char a) {
return (a >= CHAR_INT_MIX) && (a <= CHAR_INT_MAX);
}
static auto ToUint(std::string_view view) {
std::uint32_t result{};
std::size_t max{};
for (std::size_t i = 0; i < view.size(); ++i) {
if (!IsInt(view[i])) {
break;
}
++max;
}
for (std::size_t mul10 = 1; max > 0; --max) {
result += (view[max - 1] - CHAR_INT_MIX) * mul10;
mul10 *= 10;
}
return result;
}
// todo:
static auto ParseMeta(std::string_view meta, Info& info) -> void {
static constexpr std::string_view tag_res{"RESOLUTION="};
// find res
if (auto pos = meta.find(tag_res); pos != meta.npos) {
auto sub = meta.substr(pos + tag_res.size(), meta.find(','));
if (auto split = splitSV(sub, 'x'); split.size() == 2) {
info.w = ToUint(split[0]);
info.h = ToUint(split[1]);
} else {
LOG("[HLS] failed to split\n");
}
} else {
LOG("[HLS] missing res\n");
}
}
auto ParseMaster(std::string_view master) -> std::vector<Info> {
if (!master.starts_with(MASTER_MAGIC)) {
LOG("[HLS] master has invalid magic!\n");
return {};
}
// skip magic + newline
auto data = master.substr(MASTER_MAGIC.size() + 1);
// split data by lines
auto lines = splitSV(data, '\n');
// some hls will have gaps between the first stream with more metadata
// or empty spaces, so seek to the first stream (if any).
auto it = std::ranges::find_if(lines, [](const auto& a){
return a.starts_with(META_MAGIC);
});
// if we reach the end, then no streams were found.
if (it == lines.end()) {
LOG("[HLS] no streams found with meta magic\n");
return {};
}
// check if we had to seek past the first entry, if so, erease the rest.
if (it > lines.begin()) {
lines.erase(lines.begin(), it);
}
std::vector<Info> entries;
// process 2 lines at a time because they are split into [META, URL] pairs.
for (std::size_t i = 0; (i + 1) < lines.size(); i += 2) {
const auto meta = lines[i];
const auto url = lines[i + 1];
// basic test to see if the data is valid-ish.
if (!meta.starts_with(META_MAGIC) || !url.starts_with("https://")) {
LOG("HLS-ERROR invalid data at entry %lu\n", i);
break;
}
// #ifndef NDEBUG
// std::cout << meta << "\nURL: " << url << "\n\n";
// #endif
Info info;
ParseMeta(meta, info);
info.url = url;
assert(info.url.starts_with("https://"));
entries.emplace_back(std::move(info));
}
std::ranges::sort(entries, std::greater_equal{}, &Info::h);
return entries;
}
} // namespace hls
// simple m3u8 master parser made for CR streams.
// i looked at the m3u8 spec a while ago and iirc CR does stuff
// that is not defined in the spec, like some of the field entries
// didn't exist in the spec.
// this was a lonnng time ago though however so i might be forgetting.
// anyway, because of this, this parser will only work with CR master
// hls stuff.
#pragma once
#include <vector>
#include <string>
#include <string_view>
namespace hls {
struct Info {
unsigned bandwidth; //req
unsigned average_bandwidth; //opt
std::vector<std::string> codecs; //req
unsigned w;
unsigned h;
unsigned program_id;
float frame_rate;
std::string url;
};
auto ParseMaster(std::string_view master) -> std::vector<Info>;
} // namespace hls
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment