contain video stream of bitrate 1000k and audio stream of bitrate 64k and the
second variant stream will contain video stream of bitrate 256k and audio
stream of bitrate 32k. Here, two media playlist with file names out_0.m3u8 and
-out_1.m3u8 will be created.
+out_1.m3u8 will be created. If you want something meaningful text instead of indexes
+in result names, you may specify names for each or some of the variants
+as in the following example.
+
+
+@example
+ffmpeg -re -i in.ts -b:v:0 1000k -b:v:1 256k -b:a:0 64k -b:a:1 32k \
+ -map 0:v -map 0:a -map 0:v -map 0:a -f hls -var_stream_map "v:0,a:0,name:my_hd v:1,a:1,name:my_sd" \
+ http://example.com/live/out_%v.m3u8
+@end example
+
+This example creates two hls variant streams as in the previous one.
+But here, the two media playlist with file names out_my_hd.m3u8 and
+out_my_sd.m3u8 will be created.
+
@example
ffmpeg -re -i in.ts -b:v:0 1000k -b:v:1 256k -b:a:0 64k \
-map 0:v -map 0:a -map 0:v -f hls -var_stream_map "v:0 a:0 v:1" \
char *agroup; /* audio group name */
char *ccgroup; /* closed caption group name */
char *baseurl;
+ char *varname; // variant name
} VariantStream;
typedef struct ClosedCaptionsStream {
return;
}
+static int replace_str_data_in_filename(char **s, const char *filename, char placeholder, const char *datastring)
+{
+ const char *p;
+ char *new_filename;
+ char c;
+ int addchar_count;
+ int found_count = 0;
+ AVBPrint buf;
+
+ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ p = filename;
+ for (;;) {
+ c = *p;
+ if (c == '\0')
+ break;
+ if (c == '%' && *(p+1) == '%') // %%
+ addchar_count = 2;
+ else if (c == '%' && *(p+1) == placeholder) {
+ av_bprintf(&buf, "%s", datastring);
+ p += 2;
+ addchar_count = 0;
+ found_count ++;
+ } else
+ addchar_count = 1;
+
+ if (addchar_count > 0) {
+ av_bprint_append_data(&buf, p, addchar_count);
+ p += addchar_count;
+ }
+ }
+ if (!av_bprint_is_complete(&buf)) {
+ av_bprint_finalize(&buf, NULL);
+ return -1;
+ }
+ if (av_bprint_finalize(&buf, &new_filename) < 0 || !new_filename)
+ return -1;
+ *s = new_filename;
+ return found_count;
+}
+
static int replace_int_data_in_filename(char **s, const char *filename, char placeholder, int64_t number)
{
const char *p;
}
- while (segment) {
+ /* if %v is present in the file's directory
+ * all segment belongs to the same variant, so do it only once before the loop*/
+ if (dirname && av_stristr(dirname, "%v")) {
char * r_dirname = dirname;
-
- /* if %v is present in the file's directory */
- if (dirname && av_stristr(dirname, "%v")) {
-
+ if (!vs->varname) {
if (replace_int_data_in_filename(&r_dirname, dirname, 'v', segment->var_stream_idx) < 1) {
ret = AVERROR(EINVAL);
goto fail;
}
- av_free(dirname);
- dirname = r_dirname;
+ } else {
+ if (replace_str_data_in_filename(&r_dirname, dirname, 'v', vs->varname) < 1) {
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
}
+ av_free(dirname);
+ dirname = r_dirname;
+ }
+
+ while (segment) {
av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n",
segment->filename);
path_size = (hls->use_localtime_mkdir ? 0 : strlen(dirname)) + strlen(segment->filename) + 1;
return ret;
}
-static int format_name(const char *buf, char **s, int index)
+static int format_name(const char *buf, char **s, int index, const char *varname)
{
const char *proto, *dir;
char *orig_buf_dup = NULL, *mod_buf_dup = NULL;
return ret;
}
- if (replace_int_data_in_filename(s, orig_buf_dup, 'v', index) < 1) {
- ret = AVERROR(EINVAL);
- goto fail;
+ if (!varname) {
+ if (replace_int_data_in_filename(s, orig_buf_dup, 'v', index) < 1) {
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+ } else {
+ if (replace_str_data_in_filename(s, orig_buf_dup, 'v', varname) < 1) {
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
}
proto = avio_find_protocol_name(orig_buf_dup);
(!av_strncasecmp(val, "1", strlen("1"))));
hls->has_default_key = 1;
continue;
+ } else if (av_strstart(keyval, "name:", &val)) {
+ vs->varname = av_strdup(val);
+ if (!vs->varname)
+ return AVERROR(ENOMEM);
+ continue;
} else if (av_strstart(keyval, "agroup:", &val)) {
vs->agroup = av_strdup(val);
if (!vs->agroup)
av_freep(&vs->language);
av_freep(&vs->ccgroup);
av_freep(&vs->baseurl);
+ av_freep(&vs->varname);
}
}
for (i = 0; i < hls->nb_varstreams; i++) {
vs = &hls->var_streams[i];
- vs->m3u8_name = av_strdup(s->url);
- if (!vs->m3u8_name ) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
- ret = format_name(s->url, i, vs->m3u8_name);
+ ret = format_name(s->url, &vs->m3u8_name, i, vs->varname);
if (ret < 0)
goto fail;
}
}
if (hls->segment_filename) {
- basename_size = strlen(hls->segment_filename) + 1;
- vs->basename = av_malloc(basename_size);
- if (!vs->basename) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
-
- av_strlcpy(vs->basename, hls->segment_filename, basename_size);
- ret = format_name(vs->basename, basename_size, i);
+ ret = format_name(hls->segment_filename, &vs->basename, i, vs->varname);
if (ret < 0)
goto fail;
+ basename_size = strlen(vs->basename) + 1;
} else {
if (hls->flags & HLS_SINGLE_FILE) {
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
fmp4_init_filename_len);
if (hls->nb_varstreams > 1) {
if (av_stristr(vs->fmp4_init_filename, "%v")) {
- format_name(vs->fmp4_init_filename, fmp4_init_filename_len, i);
+ av_freep(&vs->fmp4_init_filename);
+ format_name(hls->fmp4_init_filename, &vs->fmp4_init_filename, i, vs->varname);
} else {
ret = append_postfix(vs->fmp4_init_filename, fmp4_init_filename_len, i);
}
*p = '\0';
if ( hls->subtitle_filename ) {
- strcpy(vs->vtt_m3u8_name, hls->subtitle_filename);
- ret = format_name(vs->vtt_m3u8_name, vtt_basename_size, i);
+ av_freep(&vs->vtt_m3u8_name);
+ ret = format_name(hls->subtitle_filename, &vs->vtt_m3u8_name, i, vs->varname);
if (ret < 0)
goto fail;
} else {
av_freep(&vs->agroup);
av_freep(&vs->ccgroup);
av_freep(&vs->baseurl);
+ av_freep(&vs->varname);
if (vs->avf)
avformat_free_context(vs->avf);
if (vs->vtt_avf)