in src/media_tools/mpd.c [1086:1645]
GF_Err gf_m3u8_to_mpd(const char *m3u8_file, const char *base_url,
const char *mpd_file,
u32 reload_count, char *mimeTypeForM3U8Segments, Bool do_import, Bool use_mpd_templates, GF_FileDownload *getter)
{
GF_Err e;
char *sep, *template_base, *template_ext;
u32 i, nb_streams, j, k, template_width, template_idx_start;
Double update_interval;
MasterPlaylist *pl = NULL;
Bool use_template;
Stream *stream;
PlaylistElement *pe, *the_pe, *elt;
FILE *fmpd;
Bool is_end;
u32 max_dur = 0;
e = gf_m3u8_parse_master_playlist(m3u8_file, &pl, base_url);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[M3U8] Failed to parse root playlist '%s', error = %s\n", m3u8_file, gf_error_to_string(e)));
if (pl)
gf_m3u8_master_playlist_del(pl);
pl = NULL;
return e;
}
if (mpd_file == NULL) {
gf_delete_file(m3u8_file);
mpd_file = m3u8_file;
}
the_pe = NULL;
pe = NULL;
i = 0;
assert(pl);
assert(pl->streams);
while ((stream = gf_list_enum(pl->streams, &i))) {
j = 0;
while (NULL != (pe = gf_list_enum(stream->variants, &j))) {
Bool found = GF_FALSE;
char *suburl;
if (!pe->url)
continue;
/* filter out duplicated entries (seen on M6 m3u8) */
for (k=0; k<j-1; ++k) {
PlaylistElement *a_pe = gf_list_get(stream->variants, k);
if (a_pe->url && pe->url && !strcmp(a_pe->url, pe->url)) {
found = GF_TRUE;
break;
}
}
if (found)
continue;
the_pe = pe;
suburl = NULL;
if (!strstr(pe->url, ".m3u8"))
continue; /*not HLS*/
if (strcmp(base_url, pe->url))
suburl = gf_url_concatenate(base_url, pe->url);
if (!suburl || !strcmp(base_url, suburl)) {
if (suburl)
gf_free(suburl);
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD Generator] Not downloading, programs are identical for %s...\n", pe->url));
continue;
}
if (getter && getter->new_session && getter->del_session && getter->get_cache_name) {
e = getter->new_session(getter, suburl);
if (e) {
gf_free(suburl);
pe->load_error = e;
continue;
}
if (e == GF_OK) {
pe->load_error = gf_m3u8_parse_sub_playlist(getter->get_cache_name(getter), &pl, suburl, stream, pe);
}
getter->del_session(getter);
} else { /* for use in MP4Box */
if (strstr(suburl, "://")) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD Generator] Downloading %s...\n", suburl));
e = gf_dm_wget(suburl, "tmp.m3u8", 0, 0, NULL);
if (e == GF_OK) {
e = gf_m3u8_parse_sub_playlist("tmp.m3u8", &pl, suburl, stream, pe);
} else {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD Generator] Download faile for %s\n", suburl));
}
gf_delete_file("tmp.m3u8");
} else {
e = gf_m3u8_parse_sub_playlist(suburl, &pl, suburl, stream, pe);
}
}
gf_free(suburl);
}
if (max_dur < (u32) stream->computed_duration) {
max_dur = (u32) stream->computed_duration;
}
}
is_end = !pl->playlist_needs_refresh;
if (!the_pe) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD Generator] The M3U8 playlist is not correct.\n"));
return GF_BAD_PARAM;
}
update_interval = 0;
/*update interval is set to the duration of the last media file with rules defined in http live streaming RFC section 6.3.4*/
switch (reload_count) {
case 0:
update_interval = the_pe->duration_info;
break;
case 1:
update_interval = (Double)the_pe->duration_info / 2;
break;
case 2:
update_interval = 3 * ((Double)the_pe->duration_info / 2);
break;
default:
update_interval = 3 * the_pe->duration_info;
break;
}
if (is_end || ((the_pe->element_type == TYPE_PLAYLIST) && the_pe->element.playlist.is_ended)) {
update_interval = 0;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD Generator] No need to refresh playlist!\n"));
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD Generator] Playlist will be refreshed every %g seconds, len=%d\n", update_interval, the_pe->duration_info));
}
assert(mpd_file);
fmpd = gf_fopen(mpd_file, "wt");
if (!fmpd) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD Generator] Cannot write to temp file %s!\n", mpd_file));
gf_m3u8_master_playlist_del(pl);
return GF_IO_ERR;
}
fprintf(fmpd, "<MPD type=\"%s\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:hls=\"urn:gpac:hls:aes:mpd:2015\" profiles=\"urn:mpeg:dash:profile:full:2011\"", is_end ? "static" : "dynamic" );
sep = strrchr(m3u8_file, '/');
if (!sep)
sep = strrchr(m3u8_file, '\\');
if (sep)
sep = sep + 1;
else
sep = (char *)m3u8_file;
fprintf(fmpd, " id=\"%s\"", sep);
if (update_interval)
fprintf(fmpd, " minimumUpdatePeriod=\"PT%02.2gS\"", update_interval);
if (is_end)
fprintf(fmpd, " mediaPresentationDuration=\"PT%dS\"", max_dur);
fprintf(fmpd, " minBufferTime=\"PT1.5S\"");
fprintf(fmpd, ">\n");
fprintf(fmpd, " <ProgramInformation moreInformationURL=\"http://gpac.sourceforge.net\">\n");
{
char *title = the_pe->title;
if (!title || strlen(title) < 2)
title = the_pe->url;
fprintf(fmpd, " <Title>%s</Title>\n", title);
}
fprintf(fmpd, " <Source>Generated from URL %s</Source>\n", base_url );
fprintf(fmpd, " <Copyright>Generated by GPAC %s from %s</Copyright>\n", GPAC_FULL_VERSION, base_url);
fprintf(fmpd, " </ProgramInformation>\n");
fprintf(fmpd, " <Period start=\"PT0S\"");
if (is_end) fprintf(fmpd, " duration=\"PT%dS\"", max_dur);
fprintf(fmpd, " >\n");
nb_streams = gf_list_count(pl->streams);
/*check if we use templates*/
template_base = NULL;
template_ext = NULL;
use_template = use_mpd_templates;
template_width = 0;
template_idx_start = 0;
elt = NULL;
for (i=0; i<nb_streams; i++) {
u32 count_variants;
stream = gf_list_get(pl->streams, i);
count_variants = gf_list_count(stream->variants);
for (j=0; j<count_variants; j++) {
u32 count_elements;
pe = gf_list_get(stream->variants, j);
if (pe->element_type != TYPE_PLAYLIST)
continue;
count_elements = gf_list_count(pe->element.playlist.elements);
if (!count_elements)
continue;
if (!template_base && use_template) {
char *sub_url;
elt = gf_list_get(pe->element.playlist.elements, 0);
sub_url = strrchr(elt->url, '/');
if (!sub_url) {
sub_url = elt->url;
} else {
sub_url ++;
}
template_base = gf_strdup(sub_url);
template_ext = strrchr(template_base, '.');
k=0;
while (1) {
if (strchr("0123456789", template_base[k])) {
if (template_ext) {
template_ext[0] = 0;
template_width = (u32) strlen(template_base + k);
template_idx_start = atoi(template_base + k);
template_ext[0] = '.';
}
template_base[k] = 0;
break;
}
k++;
if (!template_base[k]) {
use_template = GF_FALSE;
break;
}
}
}
if (!template_ext) template_ext="";
if (use_template) {
for (k=0; k<count_elements; k++) {
char szURL[GF_MAX_PATH], *sub_url;
elt = gf_list_get(pe->element.playlist.elements, k);
if (template_width == 2)
sprintf(szURL, "%s%02d%s", template_base, template_idx_start + k, template_ext);
else if (template_width == 3)
sprintf(szURL, "%s%03d%s", template_base, template_idx_start + k, template_ext);
else if (template_width == 4)
sprintf(szURL, "%s%04d%s", template_base, template_idx_start + k, template_ext);
else if (template_width == 5)
sprintf(szURL, "%s%05d%s", template_base, template_idx_start + k, template_ext);
else if (template_width == 6)
sprintf(szURL, "%s%06d%s", template_base, template_idx_start + k, template_ext);
else
sprintf(szURL, "%s%d%s", template_base, template_idx_start + k, template_ext);
sub_url = strrchr(elt->url, '/');
if (!sub_url) sub_url = elt->url;
else sub_url ++;
if (strcmp(szURL, sub_url)) {
use_template = GF_FALSE;
break;
}
}
}
}
}
nb_streams = gf_list_count(pl->streams);
for (i=0; i<nb_streams; i++) {
u32 count_variants;
u32 width, height, samplerate, num_channels;
fprintf(fmpd, " <AdaptationSet segmentAlignment=\"true\">\n");
/*if we use templates, put the SegmentTemplate element at the adaptationSet level*/
if (use_template) {
fprintf(fmpd, " <SegmentTemplate");
fprintf(fmpd, " duration=\"%d\"", (u32) pe->duration_info);
if (template_width > 1) {
fprintf(fmpd, " media=\"%s$%%0%ddNumber$%s\"", template_base, template_width, template_ext);
} else {
fprintf(fmpd, " media=\"%s$Number$%s\"", template_base, template_ext);
}
fprintf(fmpd, " startNumber=\"%d\"", template_idx_start);
fprintf(fmpd, "/>\n");
}
if (elt && do_import) {
#ifndef GPAC_DISABLE_MEDIA_IMPORT
GF_Err e;
GF_MediaImporter import;
char *tmp_file = NULL;
elt = gf_list_get(pe->element.playlist.elements, 0);
memset(&import, 0, sizeof(GF_MediaImporter));
import.trackID = 0;
import.flags = GF_IMPORT_PROBE_ONLY;
if (strstr(elt->url, "://") && !strstr(elt->url, "file://")) {
tmp_file = strrchr(elt->url, '/');
if (!tmp_file) tmp_file = strrchr(elt->url, '\\');
if (tmp_file) {
e = gf_dm_wget(elt->url, tmp_file, 0, 0, NULL);
if (e==GF_OK) {
import.in_name = tmp_file;
e = gf_media_import(&import);
}
}
} else {
import.in_name = elt->url;
e = gf_media_import(&import);
}
if (import.nb_tracks > 1) {
for (k=0; k<import.nb_tracks; k++) {
fprintf(fmpd, " <ContentComponent id=\"%d\"", import.tk_info[k].track_num);
if (import.tk_info[k].lang)
fprintf(fmpd, " lang=\"%s\"", gf_4cc_to_str(import.tk_info[k].lang));
switch (import.tk_info[k].type) {
case GF_ISOM_MEDIA_VISUAL:
fprintf(fmpd, " contentType=\"video\"");
break;
case GF_ISOM_MEDIA_AUDIO:
fprintf(fmpd, " contentType=\"audio\"");
break;
default:
fprintf(fmpd, " contentType=\"application\"");
break;
}
fprintf(fmpd, "/>\n");
}
}
if (tmp_file)
gf_delete_file(tmp_file);
#endif
}
/*check if we use templates*/
stream = gf_list_get(pl->streams, i);
count_variants = gf_list_count(stream->variants);
for (j=0; j<count_variants; j++) {
char *base_url;
u32 count_elements;
#ifndef GPAC_DISABLE_MEDIA_IMPORT
Bool import_file = do_import;
#endif
Bool is_aac = GF_FALSE;
char *byte_range_media_file = NULL;
pe = gf_list_get(stream->variants, j);
if (pe->element_type == TYPE_MEDIA) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] NOT SUPPORTED: M3U8 Media\n"));
} else if (pe->load_error) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Error loading playlist element %s: %s\n", pe->url, gf_error_to_string(e) ));
} else if (pe->element_type != TYPE_PLAYLIST) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] NOT SUPPORTED: M3U8 unknown type for %s\n", pe->url));
}
count_elements = gf_list_count(pe->element.playlist.elements);
if (!count_elements)
continue;
base_url = gf_strdup(pe->url);
sep = strrchr(base_url, '/');
if (pe->codecs && (pe->codecs[0] == '\"')) {
u32 len = (u32) strlen(pe->codecs);
strncpy(pe->codecs, pe->codecs+1, len-1);
pe->codecs[len-2] = 0;
}
#ifndef GPAC_DISABLE_MEDIA_IMPORT
if (pe->bandwidth && pe->codecs && pe->width && pe->height) {
import_file = GF_FALSE;
}
#endif
k = 0;
#ifndef GPAC_DISABLE_MEDIA_IMPORT
try_next_segment:
#endif
k++;
elt = gf_list_get(pe->element.playlist.elements, k);
if (!elt)
break;
/*get rid of level 0 aac*/
if (elt && strstr(elt->url, ".aac"))
is_aac = GF_TRUE;
if (is_aac)
fprintf(fmpd, "<!-- \n");
width = pe->width;
height = pe->height;
samplerate = num_channels = 0;
#ifndef GPAC_DISABLE_MEDIA_IMPORT
if (import_file) {
GF_Err e;
GF_MediaImporter import;
char *tmp_file = NULL;
memset(&import, 0, sizeof(GF_MediaImporter));
import.trackID = 0;
import.flags = GF_IMPORT_PROBE_ONLY;
if (strstr(elt->url, "://") && !strstr(elt->url, "file://")) {
tmp_file = strrchr(elt->url, '/');
if (!tmp_file)
tmp_file = strrchr(elt->url, '\\');
if (tmp_file) {
tmp_file++;
e = gf_dm_wget(elt->url, tmp_file, elt->byte_range_start, elt->byte_range_end, NULL);
if (e == GF_OK) {
import.in_name = tmp_file;
}
}
} else {
import.in_name = elt->url;
}
e = gf_media_import(&import);
if (e != GF_OK) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] M3U8 missing Media Element %s (Playlist %s) %s \n", import.in_name, base_url));
goto try_next_segment;
}
if (import.in_name && !pe->bandwidth && pe->duration_info) {
u64 pos = 0;
Double bw;
FILE *t = gf_fopen(import.in_name, "rb");
if (t) {
gf_fseek(t, 0, SEEK_END);
pos = gf_ftell(t);
gf_fclose(t);
}
bw = (Double) pos;
bw *= 8;
bw /= pe->duration_info;
pe->bandwidth = (u32) bw;
}
if (tmp_file)
gf_delete_file(tmp_file);
if (!pe->codecs) {
char szCodecs[1024];
szCodecs[0] = 0;
for (k=0; k<import.nb_tracks; k++) {
if (strlen(import.tk_info[k].szCodecProfile)) {
if (strlen(szCodecs)) strcat(szCodecs, ",");
strcat(szCodecs, import.tk_info[k].szCodecProfile);
}
}
pe->codecs = gf_strdup(szCodecs);
}
for (k=0; k<import.nb_tracks; k++) {
switch (import.tk_info[k].type) {
case GF_ISOM_MEDIA_VISUAL:
width = import.tk_info[k].video_info.width;
height = import.tk_info[k].video_info.height;
break;
case GF_ISOM_MEDIA_AUDIO:
samplerate = import.tk_info[k].audio_info.sample_rate;
num_channels = import.tk_info[k].audio_info.nb_channels;
break;
}
}
}
#endif
fprintf(fmpd, " <Representation id=\"%s\" bandwidth=\"%d\"", sep ? sep+1 : base_url, pe->bandwidth);
/* TODO : if mime-type is still unknown, don't try to add codec information since it would be wrong */
if (!strcmp(M3U8_UNKNOWN_MIME_TYPE, mimeTypeForM3U8Segments)) {
fprintf(fmpd, " mimeType=\"%s\"", mimeTypeForM3U8Segments);
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Unknown mime-type when converting from M3U8 HLS playlist, setting %s\n", mimeTypeForM3U8Segments));
} else {
fprintf(fmpd, " mimeType=\"%s\"", mimeTypeForM3U8Segments);
}
if (pe->codecs)
fprintf(fmpd, " codecs=\"%s\"", pe->codecs);
if (pe->language)
fprintf(fmpd, " lang=\"%s\"", pe->language);
if (width && height) {
fprintf(fmpd, " width=\"%d\" height=\"%d\"", width, height);
}
if (samplerate)
fprintf(fmpd, " audioSamplingRate=\"%d\"", samplerate);
if (use_template) {
if (sep) {
/*keep final '/' */
sep[1] = 0;
fprintf(fmpd, ">\n <BaseURL>%s</BaseURL>\n </Representation>\n", base_url);
} else
fprintf(fmpd, "/>\n");
if (is_aac)
fprintf(fmpd, " -->");
continue;
}
fprintf(fmpd, ">\n");
byte_range_media_file = NULL;
elt = gf_list_get(pe->element.playlist.elements, 0);
if (elt && (elt->byte_range_end || elt->byte_range_start)) {
byte_range_media_file = elt->url;
fprintf(fmpd, " <BaseURL>%s</BaseURL>\n", byte_range_media_file);
} else if (sep) {
sep[1] = 0;
fprintf(fmpd, " <BaseURL>%s</BaseURL>\n", base_url);
}
fprintf(fmpd, " <SegmentList duration=\"%lf\">\n", pe->duration_info);
update_interval = (count_elements - 1) * pe->duration_info * 1000;
for (k=0; k<count_elements; k++) {
u32 cmp = 0;
char *src_url, *seg_url;
elt = gf_list_get(pe->element.playlist.elements, k);
/* remove protocol scheme and try to find the common part in baseURL and segment URL - this avoids copying the entire url */
src_url = strstr(base_url, "://");
if (src_url) src_url += 3;
else src_url = base_url;
seg_url = strstr(elt->url, "://");
if (seg_url) seg_url += 3;
else seg_url = elt->url;
while (src_url[cmp] == seg_url[cmp]) cmp++;
fprintf(fmpd, " <SegmentURL");
if (byte_range_media_file) {
fprintf(fmpd, " mediaRange=\""LLU"-"LLU"\"", elt->byte_range_start, elt->byte_range_end);
if (strcmp(elt->url, byte_range_media_file))
fprintf(fmpd, " media=\"%s\"", elt->url);
} else {
fprintf(fmpd, " media=\"%s\"", cmp ? (seg_url + cmp) : elt->url);
}
if (elt->drm_method != DRM_NONE) {
fprintf(fmpd, " hls:keyMethod=\"aes-128\"");
if (elt->key_uri) {
u32 idx;
fprintf(fmpd, " hls:keyURL=%s hls:keyIV=\"", elt->key_uri);
for (idx=0; idx<16; idx++) {
fprintf(fmpd, "%02x", elt->key_iv[idx]);
}
fprintf(fmpd, "\"");
}
}
fprintf(fmpd, "/>\n");
}
fprintf(fmpd, " </SegmentList>\n");
fprintf(fmpd, " </Representation>\n");
gf_free(base_url);
if (is_aac)
fprintf(fmpd, " -->\n");
}
if (template_base) {
gf_free(template_base);
template_base = NULL;
}
fprintf(fmpd, " </AdaptationSet>\n");
}
fprintf(fmpd, " </Period>\n");
fprintf(fmpd, "</MPD>");
gf_fclose(fmpd);
gf_m3u8_master_playlist_del(pl);
return GF_OK;
}