From 74935c3f277faf83501dc499ebf640e84f8fb72b Mon Sep 17 00:00:00 2001 From: Luca Benvenuto <20568758+lucabenvenuto@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:18:17 -0300 Subject: [PATCH 1/3] create Dockerfile, add pugixml as dependency when building --- Dockerfile | 56 +++++++++++++++++++++++++++++++++++ Makefile | 2 +- config | 2 ++ ngx_morpheus_internal.cpp | 2 +- stream_with_scte35_events.mpd | 28 ++++++++++++++++++ 5 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 Dockerfile create mode 100644 stream_with_scte35_events.mpd diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b1bdd8b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,56 @@ +# Stage 1: Build the dynamic module +FROM nginx:1.24.0 AS builder + +RUN apt-get update && apt-get install -y \ + build-essential \ + libpcre3-dev \ + zlib1g-dev \ + libssl-dev \ + wget \ + && rm -rf /var/lib/apt/lists/* + +# Download nginx source matching the base image version +RUN wget http://nginx.org/download/nginx-1.24.0.tar.gz \ + && tar -xzf nginx-1.24.0.tar.gz \ + && rm nginx-1.24.0.tar.gz + +# Remove -Werror from nginx build system — it breaks C++ compilation +RUN sed -i 's/ -Werror//' /nginx-1.24.0/auto/cc/gcc + +COPY . /morpheus/ + +# Download pugixml v1.15 source from the official repository. +# Runs after COPY so it works regardless of whether local pugixml/ files are present. +RUN mkdir -p /morpheus/pugixml \ + && wget -qO /morpheus/pugixml/pugixml.cpp https://raw.githubusercontent.com/zeux/pugixml/v1.15/src/pugixml.cpp \ + && wget -qO /morpheus/pugixml/pugixml.hpp https://raw.githubusercontent.com/zeux/pugixml/v1.15/src/pugixml.hpp \ + && wget -qO /morpheus/pugixml/pugiconfig.hpp https://raw.githubusercontent.com/zeux/pugixml/v1.15/src/pugiconfig.hpp + +WORKDIR /nginx-1.24.0 +RUN ./configure \ + --with-ld-opt="-lstdc++" \ + --with-cc-opt="-Wno-write-strings" \ + --with-compat \ + --add-dynamic-module=/morpheus \ + && make modules + + +# Stage 2: Production image +FROM nginx:1.24.0 + +# Copy the compiled module +COPY --from=builder /nginx-1.24.0/objs/ngx_http_morpheus_module.so /etc/nginx/modules/ + +# Copy nginx configuration and MIME types +COPY nginx_morpheus.conf /etc/nginx/nginx.conf +COPY mime.types /etc/nginx/mime.types + +# nginx_morpheus.conf includes /usr/share/nginx/modules/*.conf which doesn't +# exist in the Docker image (unlike Ubuntu package installs); create it empty +RUN mkdir -p /usr/share/nginx/modules/ + +EXPOSE 80 + +# /dev/shm is a tmpfs mounted at container runtime, so its subdirectories +# cannot be created at build time — create them before starting nginx +CMD ["/bin/sh", "-c", "mkdir -p /dev/shm/nginx/client_temp && exec nginx -g 'daemon off;'"] diff --git a/Makefile b/Makefile index 46b5592..9de2fab 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CC = g++ CFLAGS = -Wall -W -g -SRCS = ngx_morpheus_internal.cpp morpheus_main.cpp +SRCS = ngx_morpheus_internal.cpp morpheus_main.cpp pugixml.cpp OBJS = $(SRCS:.cpp=.o) all: morphdriver diff --git a/config b/config index 15270d1..95d34f7 100644 --- a/config +++ b/config @@ -2,11 +2,13 @@ ngx_addon_name=ngx_http_morpheus_module MORPHEUS_SRCS=" \ $ngx_addon_dir/ngx_http_morpheus_module.cpp \ $ngx_addon_dir/ngx_morpheus_internal.cpp \ + $ngx_addon_dir/pugixml/pugixml.cpp \ " if test -n "$ngx_module_link"; then ngx_module_type=HTTP ngx_module_name=ngx_http_morpheus_module + ngx_module_incs="$ngx_addon_dir $ngx_addon_dir/pugixml" ngx_module_srcs="$MORPHEUS_SRCS" . auto/module else diff --git a/ngx_morpheus_internal.cpp b/ngx_morpheus_internal.cpp index 4f07c38..943259d 100644 --- a/ngx_morpheus_internal.cpp +++ b/ngx_morpheus_internal.cpp @@ -11,7 +11,7 @@ #include const std::unordered_map MANIFEST_URLS = { - {1, "https://dash.akamaized.net/dashif/ad-insertion-testcase1/batch2/real/b/ad-insertion-testcase1.mpd"}, + {1, "http://localhost:3000/api/list-mpd?vasturl=http://localhost:3000/samples/dash-alt-mpd/vast-sample.xm"}, {2, "https://dash.akamaized.net/dashif/ad-insertion-testcase1/batch2/real/b/ad-insertion-testcase1.mpd"}, {3, "https://dash.akamaized.net/dashif/ad-insertion-testcase1/batch2/real/b/ad-insertion-testcase1.mpd"} }; diff --git a/stream_with_scte35_events.mpd b/stream_with_scte35_events.mpd new file mode 100644 index 0000000..a83bce8 --- /dev/null +++ b/stream_with_scte35_events.mpd @@ -0,0 +1,28 @@ + + + https://dash.akamaized.net/akamai/bbb_30fps/ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From ff0cb483b8a34d0b5ea601c7f6b4b580c7213bfb Mon Sep 17 00:00:00 2001 From: Luca Benvenuto <20568758+lucabenvenuto@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:13:08 -0300 Subject: [PATCH 2/3] add missing 'l' at the end of url --- ngx_morpheus_internal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ngx_morpheus_internal.cpp b/ngx_morpheus_internal.cpp index 943259d..280380d 100644 --- a/ngx_morpheus_internal.cpp +++ b/ngx_morpheus_internal.cpp @@ -11,7 +11,7 @@ #include const std::unordered_map MANIFEST_URLS = { - {1, "http://localhost:3000/api/list-mpd?vasturl=http://localhost:3000/samples/dash-alt-mpd/vast-sample.xm"}, + {1, "http://localhost:3000/api/list-mpd?vasturl=http://localhost:3000/samples/dash-alt-mpd/vast-sample.xml"}, {2, "https://dash.akamaized.net/dashif/ad-insertion-testcase1/batch2/real/b/ad-insertion-testcase1.mpd"}, {3, "https://dash.akamaized.net/dashif/ad-insertion-testcase1/batch2/real/b/ad-insertion-testcase1.mpd"} }; From 1eaf4bb00a8861ad19fb46eaef2bbb1e7394f829 Mon Sep 17 00:00:00 2001 From: Luca Benvenuto <20568758+lucabenvenuto@users.noreply.github.com> Date: Mon, 27 Apr 2026 12:11:11 -0300 Subject: [PATCH 3/3] support parsing scte35:SegmentationDescriptor into DASH OverlayEvent, unify scte-to-overlay and scte-to-alternative into a single flow --- Makefile | 8 +- morpheus_main.cpp | 9 +- ngx_http_morpheus_module.cpp | 18 +-- ngx_morpheus_internal.cpp | 273 ++++++++++++++++++++++++++++------- 4 files changed, 233 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index 9de2fab..9aa003c 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,14 @@ CC = g++ CFLAGS = -Wall -W -g -SRCS = ngx_morpheus_internal.cpp morpheus_main.cpp pugixml.cpp +SRCS = ngx_morpheus_internal.cpp morpheus_main.cpp OBJS = $(SRCS:.cpp=.o) all: morphdriver -morphdriver: $(OBJS) cxxopts.hpp - $(CC) $(CFLAGS) $(SRCS) -o $@ +morphdriver: $(OBJS) pugixml.o cxxopts.hpp + $(CC) $(CFLAGS) $(OBJS) pugixml.o -o $@ clean: - rm -f *.o morphdriver + rm -f ngx_morpheus_internal.o morpheus_main.o morphdriver diff --git a/morpheus_main.cpp b/morpheus_main.cpp index eaa6a58..dfd65a3 100644 --- a/morpheus_main.cpp +++ b/morpheus_main.cpp @@ -7,12 +7,11 @@ extern "C" { using namespace std; -void morph_process(const char* encmpd, const char* drmconf, const char* iframesmpd, const bool alternativeconf); +void morph_process(const char* encmpd, const char* drmconf, const char* iframesmpd); int main (int argc, char *argv[]) { string inmpd_o, drmconf_o, iframesmpd_o; - bool alternativeconf_o = false; try { @@ -22,7 +21,6 @@ int main (int argc, char *argv[]) { ("n", "encoder mpd file", cxxopts::value()) ("i", "iframes track mpd file", cxxopts::value()) ("d", "ckm encrypt context response xml file", cxxopts::value()) - ("a", "alterantive insertion") ("h,help", "Print this help") ; @@ -43,8 +41,6 @@ int main (int argc, char *argv[]) { if (result.count("d")) drmconf_o = result["d"].as().c_str(); - if (result.count("a")) - alternativeconf_o = true; } catch (const cxxopts::OptionException& e) { @@ -55,9 +51,8 @@ int main (int argc, char *argv[]) { const char* inmpd = inmpd_o.length() ? inmpd_o.c_str() : NULL; const char* drmconf = drmconf_o.length() ? drmconf_o.c_str() : NULL; const char* iframesmpd = iframesmpd_o.length() ? iframesmpd_o.c_str() : NULL; - const bool alternativeconf = alternativeconf_o; - morph_process(inmpd, drmconf, iframesmpd, alternativeconf); + morph_process(inmpd, drmconf, iframesmpd); return EXIT_SUCCESS; } diff --git a/ngx_http_morpheus_module.cpp b/ngx_http_morpheus_module.cpp index 7bb6c6a..7fac46f 100644 --- a/ngx_http_morpheus_module.cpp +++ b/ngx_http_morpheus_module.cpp @@ -72,7 +72,7 @@ static char *ngx_http_morpheus_merge_loc_conf(ngx_conf_t *cf, static ngx_int_t ngx_http_morpheus_init(ngx_conf_t *cf); static char *ngx_http_morpheus(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -void morph_process(const char* encmpd, const char* drmconf, const char* iframesmpd, const bool alternativeconf); +void morph_process(const char* encmpd, const char* drmconf, const char* iframesmpd); static ngx_conf_bitmask_t ngx_http_morpheus_methods_mask[] = { { ngx_string("off"), NGX_HTTP_DAV_OFF }, @@ -248,12 +248,11 @@ ngx_http_morpheus_put_handler(ngx_http_request_t *r) { size_t root; time_t date; - ngx_str_t *temp, path, mode_value; + ngx_str_t *temp, path; ngx_uint_t status; ngx_file_info_t fi; ngx_ext_rename_file_t ext; ngx_http_morpheus_loc_conf_t *dlcf; - bool scte_to_alternative = false; if (r->request_body == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, @@ -290,18 +289,7 @@ ngx_http_morpheus_put_handler(ngx_http_request_t *r) temp = &r->request_body->temp_file->file.name; - #define SCTE_TO_ALT_MODE "scte-to-alternative" - if (r->args.len > 0) { - if (ngx_http_arg(r, (u_char *) "mode", 4, &mode_value) == NGX_OK) { - if (mode_value.len == (sizeof(SCTE_TO_ALT_MODE) - 1) && - ngx_strncmp(mode_value.data, SCTE_TO_ALT_MODE, sizeof(SCTE_TO_ALT_MODE) - 1) == 0) - { - scte_to_alternative = true; - } - } - } - - morph_process((const char*)temp->data, NULL, NULL, scte_to_alternative); + morph_process((const char*)temp->data, NULL, NULL); if (ngx_file_info(path.data, &fi) == NGX_FILE_ERROR) { status = NGX_HTTP_CREATED; diff --git a/ngx_morpheus_internal.cpp b/ngx_morpheus_internal.cpp index 280380d..5ee3140 100644 --- a/ngx_morpheus_internal.cpp +++ b/ngx_morpheus_internal.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include "pugixml.hpp" #include @@ -16,6 +19,175 @@ const std::unordered_map MANIFEST_URLS = { {3, "https://dash.akamaized.net/dashif/ad-insertion-testcase1/batch2/real/b/ad-insertion-testcase1.mpd"} }; +#define BANNER_AD_URL "http://localhost:3001/image/html?template_id=17306275-3762-45f9-9a86-c262b8925963&city=montevideo&gender=male&hobbies=football%2Cmusic&restrictions=diabetic&price=UYU200&favourite_colors=red%2Cblue%2Cblack" +#define SKYSCRAPER_AD_URL "http://localhost:3001/image/html?template_id=cda83e2d-0cd9-44f6-b1d5-d9c0c62cc203&city=montevideo&gender=male&hobbies=football%2Cmusic&restrictions=diabetic%2Cceliac&favourite_colors=red%2Cblue%2Cblack&favourite_food=chivito" +#define LSHAPE_RIGHT_AD_URL "http://localhost:3001/image/html?template_id=7822830e-10ff-449f-a5e6-f92f7899f442&gender=male&country=uruguay&restrictions=celiac&hobbies=gaming%2Cfootball%2Ctravelling&colors=black%2Cpink" +#define LSHAPE_LEFT_AD_URL "http://localhost:3001/image/html?template_id=1ac069f1-0437-4a9d-861b-e29ad552c842&gender=male&country=uruguay&hobbies=gaming%2Cfootball%2Ctravelling&colors=black%2Cpink&age=27&social_media=%40qualabs" + +static std::string fmt_double(double v) { + char buf[32]; + std::snprintf(buf, sizeof(buf), "%g", v); + return buf; +} + +struct vec2 { double x, y; }; +struct squeeze_cfg { bool active; double pct; const char* origin; }; +struct shape_cfg { const char* url; int z; vec2 viewport, size, top_left; squeeze_cfg squeeze; }; + +static const shape_cfg SHAPE_CONFIGS[] = { + // URL Z VIEWPORT SIZE TOPLEFT SQUEEZE + { BANNER_AD_URL, 1, {1920, 1080}, {1920, 324}, {0, 756}, {false, 0.0, ""} }, + { SKYSCRAPER_AD_URL, 1, {1920, 1080}, {480, 1080}, {0, 0}, {false, 0.0, ""} }, + { LSHAPE_RIGHT_AD_URL, -1, {1.0,1.0}, {1.0,1.0}, {0.0,0.0}, {true, 0.6, "top left"} }, + { LSHAPE_LEFT_AD_URL, -1, {1.0,1.0}, {1.0,1.0}, {0.0,0.0}, {true, 0.6, "top right"} }, +}; + +static int parse_shape(const char* upid_text) { + char buf[256] = {}; + std::strncpy(buf, upid_text, sizeof(buf) - 1); + + // trim leading whitespace + char* p = buf; + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') ++p; + + // trim trailing whitespace and '&' + char* end = p + std::strlen(p) - 1; + while (end > p && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r' || *end == '&')) + *end-- = '\0'; + + // normalize to lowercase + for (char* c = p; *c; ++c) + *c = (char)std::tolower((unsigned char)*c); + + if (std::strncmp(p, "shape=", 6) != 0) return -1; + const char* val = p + 6; + + if (std::strcmp(val, "banner") == 0) return 0; + if (std::strcmp(val, "skyscraper") == 0) return 1; + if (std::strcmp(val, "lshape-right") == 0) return 2; + if (std::strcmp(val, "lshape-left") == 0) return 3; + return -1; +} + +struct overlay_ev { + bool is_start; + uint32_t seg_id; + uint64_t ptime; + uint64_t dur; + int shape_idx; +}; + +void morph_overlay(pugi::xml_document& mpddoc) { + pugi::xml_node mpd = mpddoc.child("MPD"); + + for (pugi::xml_node period : mpd.children("Period")) { + pugi::xml_node scte_stream; + for (pugi::xml_node es : period.children("EventStream")) { + std::string scheme = es.attribute("schemeIdUri").value(); + if (scheme.find("scte35") != std::string::npos) { + scte_stream = es; + break; + } + } + if (!scte_stream) continue; + + uint64_t timescale = scte_stream.attribute("timescale").as_ullong(1000); + std::vector events; + + for (pugi::xml_node event : scte_stream.children("Event")) { + uint64_t ptime = event.attribute("presentationTime").as_ullong(0); + uint64_t dur = event.attribute("duration").as_ullong(0); + + pugi::xml_node section = event.child("scte35:SpliceInfoSection"); + if (!section) continue; + pugi::xml_node descriptor = section.child("scte35:SegmentationDescriptor"); + if (!descriptor) continue; + + int type_id = descriptor.attribute("segmentationTypeId").as_int(0); + + const char* seg_id_str = descriptor.attribute("segmentationEventId").value(); + uint32_t seg_id = seg_id_str[0] + ? (uint32_t)std::strtoul(seg_id_str, nullptr, 0) + : event.attribute("id").as_uint(0); + + if (type_id == 56) { + const char* upid_text = nullptr; + for (pugi::xml_node upid : descriptor.children("scte35:SegmentationUpid")) { + if (upid.attribute("segmentationUpidType").as_int(0) == 14) { + upid_text = upid.child_value(); + break; + } + } + if (!upid_text || !upid_text[0]) { + std::cerr << "morpheus: overlay start event id=" << seg_id << " missing UPID type 14, skipping\n"; + continue; + } + int shape_idx = parse_shape(upid_text); + if (shape_idx < 0) { + std::cerr << "morpheus: overlay start event id=" << seg_id << " unknown shape, skipping\n"; + continue; + } + events.push_back({true, seg_id, ptime, dur, shape_idx}); + + } else if (type_id == 57) { + events.push_back({false, seg_id, ptime, 0, -1}); + } + } + + pugi::xml_node new_stream = period.append_child("EventStream"); + new_stream.append_attribute("schemeIdUri").set_value("urn:scte:dash:scte214-events"); + new_stream.append_attribute("timescale").set_value((unsigned long long)timescale); + + for (const overlay_ev& oe : events) { + pugi::xml_node ev = new_stream.append_child("Event"); + ev.append_attribute("presentationTime").set_value((unsigned long long)oe.ptime); + + if (oe.is_start) { + if (oe.dur > 0) + ev.append_attribute("duration").set_value((unsigned long long)oe.dur); + ev.append_attribute("id").set_value((unsigned long)oe.seg_id); + + const shape_cfg& cfg = SHAPE_CONFIGS[oe.shape_idx]; + + pugi::xml_node overlay = ev.append_child("OverlayEvent"); + overlay.append_attribute("uri").set_value(cfg.url); + overlay.append_attribute("mimeType").set_value("text/html"); + overlay.append_attribute("earliestResolutionTime").set_value("0"); + overlay.append_attribute("loop").set_value("false"); + overlay.append_attribute("mode").set_value("start"); + overlay.append_attribute("z").set_value(cfg.z); + + overlay.append_child("Viewport") + .append_attribute("x").set_value(fmt_double(cfg.viewport.x).c_str()); + overlay.child("Viewport") + .append_attribute("y").set_value(fmt_double(cfg.viewport.y).c_str()); + + overlay.append_child("Size") + .append_attribute("x").set_value(fmt_double(cfg.size.x).c_str()); + overlay.child("Size") + .append_attribute("y").set_value(fmt_double(cfg.size.y).c_str()); + + overlay.append_child("TopLeft") + .append_attribute("x").set_value(fmt_double(cfg.top_left.x).c_str()); + overlay.child("TopLeft") + .append_attribute("y").set_value(fmt_double(cfg.top_left.y).c_str()); + + if (cfg.squeeze.active) { + pugi::xml_node sq = overlay.append_child("SqueezeCurrent"); + sq.append_attribute("percentage").set_value(fmt_double(cfg.squeeze.pct).c_str()); + sq.append_attribute("origin").set_value(cfg.squeeze.origin); + } + } else { + pugi::xml_node overlay = ev.append_child("OverlayEvent"); + overlay.append_attribute("mode").set_value("stop"); + overlay.append_attribute("refId").set_value((unsigned long)oe.seg_id); + } + } + + period.remove_child(scte_stream); + } +} + #ifdef __cplusplus extern "C" { #endif @@ -88,52 +260,59 @@ void morph_drm(pugi::xml_document& mpddoc, const char* drmconf) { void morph_alternative(pugi::xml_document& mpddoc) { pugi::xml_node mpd = mpddoc.child("MPD"); - + for (pugi::xml_node period : mpd.children("Period")) { for (pugi::xml_node event_stream : period.children("EventStream")) { - + std::string scheme_id = event_stream.attribute("schemeIdUri").value(); - if (scheme_id == "urn:scte:scte35:2013:xml") { - - event_stream.attribute("schemeIdUri").set_value("urn:mpeg:dash:event:alternativeMPD:replace:2025"); - - if (!event_stream.attribute("xmlns")) { - event_stream.append_attribute("xmlns").set_value(""); - } - - for (pugi::xml_node event : event_stream.children("Event")) { - - uint64_t presentationTime = event.attribute("presentationTime").as_ullong(0); - uint64_t duration = event.attribute("duration").as_ullong(0); - - pugi::xml_node scte_section = event.child("scte35:SpliceInfoSection"); - if (scte_section) { - pugi::xml_node splice_insert = scte_section.child("scte35:SpliceInsert"); - - if (splice_insert) { - int splice_event_id = splice_insert.attribute("spliceEventId").as_int(1); - - pugi::xml_node break_duration = splice_insert.child("scte35:BreakDuration"); - - uint64_t scte_duration = break_duration.attribute("duration").as_ullong(0); - - event.remove_child(scte_section); - - pugi::xml_node replace_presentation = event.append_child("ReplacePresentation"); - - auto it = MANIFEST_URLS.find(splice_event_id); - if (it == MANIFEST_URLS.end()) { - throw std::runtime_error("Manifest URL not found for spliceEventId: " + std::to_string(splice_event_id)); - } - std::string manifest_url = it->second; - - replace_presentation.append_attribute("url").set_value(manifest_url.c_str()); - replace_presentation.append_attribute("earliestResolutionTimeOffset").set_value(std::to_string(presentationTime).c_str()); - replace_presentation.append_attribute("returnOffset").set_value(std::to_string(duration).c_str()); - replace_presentation.append_attribute("maxDuration").set_value(std::to_string(scte_duration).c_str()); - replace_presentation.append_attribute("clip").set_value("false"); - replace_presentation.append_attribute("startAtPlayhead").set_value("false"); + if (scheme_id != "urn:scte:scte35:2013:xml") continue; + + // Only claim streams that actually contain SpliceInsert events + bool has_splice_insert = false; + for (pugi::xml_node ev : event_stream.children("Event")) { + pugi::xml_node sec = ev.child("scte35:SpliceInfoSection"); + if (sec && sec.child("scte35:SpliceInsert")) { has_splice_insert = true; break; } + } + if (!has_splice_insert) continue; + + event_stream.attribute("schemeIdUri").set_value("urn:mpeg:dash:event:alternativeMPD:replace:2025"); + + if (!event_stream.attribute("xmlns")) { + event_stream.append_attribute("xmlns").set_value(""); + } + + for (pugi::xml_node event : event_stream.children("Event")) { + + uint64_t presentationTime = event.attribute("presentationTime").as_ullong(0); + uint64_t duration = event.attribute("duration").as_ullong(0); + + pugi::xml_node scte_section = event.child("scte35:SpliceInfoSection"); + if (scte_section) { + pugi::xml_node splice_insert = scte_section.child("scte35:SpliceInsert"); + + if (splice_insert) { + int splice_event_id = splice_insert.attribute("spliceEventId").as_int(1); + + pugi::xml_node break_duration = splice_insert.child("scte35:BreakDuration"); + + uint64_t scte_duration = break_duration.attribute("duration").as_ullong(0); + + event.remove_child(scte_section); + + pugi::xml_node replace_presentation = event.append_child("ReplacePresentation"); + + auto it = MANIFEST_URLS.find(splice_event_id); + if (it == MANIFEST_URLS.end()) { + throw std::runtime_error("Manifest URL not found for spliceEventId: " + std::to_string(splice_event_id)); } + std::string manifest_url = it->second; + + replace_presentation.append_attribute("url").set_value(manifest_url.c_str()); + replace_presentation.append_attribute("earliestResolutionTimeOffset").set_value(std::to_string(presentationTime).c_str()); + replace_presentation.append_attribute("returnOffset").set_value(std::to_string(duration).c_str()); + replace_presentation.append_attribute("maxDuration").set_value(std::to_string(scte_duration).c_str()); + replace_presentation.append_attribute("clip").set_value("false"); + replace_presentation.append_attribute("startAtPlayhead").set_value("false"); } } } @@ -141,11 +320,7 @@ void morph_alternative(pugi::xml_document& mpddoc) { } } -void morph_process(const char* encmpd, const char* drmconf, const char* iframesmpd, const bool alternativeconf) { - /* if iframes track mpd exists add its' AdaptationSet first - * then if drmconf exists, add drm pieces - * then do the other modifications to the mpd - */ +void morph_process(const char* encmpd, const char* drmconf, const char* iframesmpd) { pugi::xml_document doc; doc.load_file((const char*)encmpd); @@ -155,8 +330,8 @@ void morph_process(const char* encmpd, const char* drmconf, const char* iframesm if (drmconf) morph_drm(doc, drmconf); - if(alternativeconf) - morph_alternative(doc); + morph_alternative(doc); + morph_overlay(doc); pugi::xml_node mpd = doc.child("MPD");