// Read a 2 digit unsigned decimal integer. // Return -1 on failure. static int read_int_2(struct bstr *data) { *data = bstr_lstrip(*data); if (data->len && data->start[0] == '-') return -1; struct bstr s = *data; int res = (int)bstrtoll(s, &s, 10); if (data->len == s.len || data->len - s.len > 2) return -1; *data = s; return res; }
int af_str2fmt_short(bstr str) { if (bstr_startswith0(str, "0x")) { bstr rest; int fmt = bstrtoll(str, &rest, 16); if (rest.len == 0 && af_fmt_valid(fmt)) return fmt; } for (int i = 0; af_fmtstr_table[i].name; i++) if (!bstrcasecmp0(str, af_fmtstr_table[i].name)) return af_fmtstr_table[i].format; return -1; }
static int demux_open_tv(demuxer_t *demuxer, enum demux_check check) { tvi_handle_t *tvh; const tvi_functions_t *funcs; if (check > DEMUX_CHECK_REQUEST || demuxer->stream->type != STREAMTYPE_TV) return -1; tv_param_t *params = mp_get_config_group(demuxer, demuxer->global, &tv_params_conf); bstr urlparams = bstr0(demuxer->stream->path); bstr channel, input; bstr_split_tok(urlparams, "/", &channel, &input); if (channel.len) { talloc_free(params->channels); params->channel = bstrto0(NULL, channel); } if (input.len) { bstr r; params->input = bstrtoll(input, &r, 0); if (r.len) { MP_ERR(demuxer->stream, "invalid input: '%.*s'\n", BSTR_P(input)); return -1; } } assert(demuxer->priv==NULL); if(!(tvh=tv_begin(params, demuxer->log))) return -1; if (!tvh->functions->init(tvh->priv)) return -1; tvh->demuxer = demuxer; if (!open_tv(tvh)){ tv_uninit(tvh); return -1; } funcs = tvh->functions; demuxer->priv=tvh; struct sh_stream *sh_v = demux_alloc_sh_stream(STREAM_VIDEO); /* get IMAGE FORMAT */ int fourcc; funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FORMAT, &fourcc); if (fourcc == MP_FOURCC_MJPEG) { sh_v->codec->codec = "mjpeg"; } else { sh_v->codec->codec = "rawvideo"; sh_v->codec->codec_tag = fourcc; } /* set FPS and FRAMETIME */ if(!sh_v->codec->fps) { float tmp; if (funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FPS, &tmp) != TVI_CONTROL_TRUE) sh_v->codec->fps = 25.0f; /* on PAL */ else sh_v->codec->fps = tmp; } if (tvh->tv_param->fps != -1.0f) sh_v->codec->fps = tvh->tv_param->fps; /* If playback only mode, go to immediate mode, fail silently */ if(tvh->tv_param->immediate == 1) { funcs->control(tvh->priv, TVI_CONTROL_IMMEDIATE, 0); tvh->tv_param->audio = 0; } /* set width */ funcs->control(tvh->priv, TVI_CONTROL_VID_GET_WIDTH, &sh_v->codec->disp_w); /* set height */ funcs->control(tvh->priv, TVI_CONTROL_VID_GET_HEIGHT, &sh_v->codec->disp_h); demux_add_sh_stream(demuxer, sh_v); demuxer->seekable = 0; /* here comes audio init */ if (tvh->tv_param->audio && funcs->control(tvh->priv, TVI_CONTROL_IS_AUDIO, 0) == TVI_CONTROL_TRUE) { int audio_format; /* yeah, audio is present */ funcs->control(tvh->priv, TVI_CONTROL_AUD_SET_SAMPLERATE, &tvh->tv_param->audiorate); if (funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_FORMAT, &audio_format) != TVI_CONTROL_TRUE) goto no_audio; switch(audio_format) { // This is the only format any of the current inputs generate. case AF_FORMAT_S16: break; default: MP_ERR(tvh, "Audio type '%s' unsupported!\n", af_fmt_to_str(audio_format)); goto no_audio; } struct sh_stream *sh_a = demux_alloc_sh_stream(STREAM_AUDIO); funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_SAMPLERATE, &sh_a->codec->samplerate); int nchannels = sh_a->codec->channels.num; funcs->control(tvh->priv, TVI_CONTROL_AUD_GET_CHANNELS, &nchannels); mp_chmap_from_channels(&sh_a->codec->channels, nchannels); // s16ne mp_set_pcm_codec(sh_a->codec, true, false, 16, BYTE_ORDER == BIG_ENDIAN); demux_add_sh_stream(demuxer, sh_a); MP_VERBOSE(tvh, " TV audio: %d channels, %d bits, %d Hz\n", nchannels, 16, sh_a->codec->samplerate); } no_audio: if(!(funcs->start(tvh->priv))){ // start failed :( tv_uninit(tvh); return -1; } /* set color eq */ tv_set_color_options(tvh, TV_COLOR_BRIGHTNESS, tvh->tv_param->brightness); tv_set_color_options(tvh, TV_COLOR_HUE, tvh->tv_param->hue); tv_set_color_options(tvh, TV_COLOR_SATURATION, tvh->tv_param->saturation); tv_set_color_options(tvh, TV_COLOR_CONTRAST, tvh->tv_param->contrast); if(tvh->tv_param->gain!=-1) if(funcs->control(tvh->priv,TVI_CONTROL_VID_SET_GAIN,&tvh->tv_param->gain)!=TVI_CONTROL_TRUE) MP_WARN(tvh, "Unable to set gain control!\n"); return 0; }
int m_config_parse(m_config_t *config, const char *location, bstr data, char *initial_section, int flags) { m_profile_t *profile = m_config_add_profile(config, initial_section); void *tmp = talloc_new(NULL); int line_no = 0; int errors = 0; bstr_eatstart0(&data, "\xEF\xBB\xBF"); // skip BOM while (data.len) { talloc_free_children(tmp); bool ok = false; line_no++; char loc[512]; snprintf(loc, sizeof(loc), "%s:%d:", location, line_no); bstr line = bstr_strip_linebreaks(bstr_getline(data, &data)); if (!skip_ws(&line)) continue; // Profile declaration if (bstr_eatstart0(&line, "[")) { bstr profilename; if (!bstr_split_tok(line, "]", &profilename, &line)) { MP_ERR(config, "%s missing closing ]\n", loc); goto error; } if (skip_ws(&line)) { MP_ERR(config, "%s unparseable extra characters: '%.*s'\n", loc, BSTR_P(line)); goto error; } profile = m_config_add_profile(config, bstrto0(tmp, profilename)); continue; } bstr_eatstart0(&line, "--"); bstr option = line; while (line.len && (mp_isalnum(line.start[0]) || line.start[0] == '_' || line.start[0] == '-')) line = bstr_cut(line, 1); option.len = option.len - line.len; skip_ws(&line); bstr value = {0}; if (bstr_eatstart0(&line, "=")) { skip_ws(&line); if (line.len && (line.start[0] == '"' || line.start[0] == '\'')) { // Simple quoting, like "value" char term[2] = {line.start[0], 0}; line = bstr_cut(line, 1); if (!bstr_split_tok(line, term, &value, &line)) { MP_ERR(config, "%s unterminated quote\n", loc); goto error; } } else if (bstr_eatstart0(&line, "%")) { // Quoting with length, like %5%value bstr rest; long long len = bstrtoll(line, &rest, 10); if (rest.len == line.len || !bstr_eatstart0(&rest, "%") || len > rest.len) { MP_ERR(config, "%s fixed-length quoting expected - put " "\"quotes\" around the option value if you did not " "intend to use this, but your option value starts " "with '%%'\n", loc); goto error; } value = bstr_splice(rest, 0, len); line = bstr_cut(rest, len); } else { // No quoting; take everything until the comment or end of line int end = bstrchr(line, '#'); value = bstr_strip(end < 0 ? line : bstr_splice(line, 0, end)); line.len = 0; } } if (skip_ws(&line)) { MP_ERR(config, "%s unparseable extra characters: '%.*s'\n", loc, BSTR_P(line)); goto error; } int res; if (profile) { if (bstr_equals0(option, "profile-desc")) { m_profile_set_desc(profile, value); res = 0; } else { res = m_config_set_profile_option(config, profile, option, value); } } else { res = m_config_set_option_ext(config, option, value, flags); } if (res < 0) { MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n", loc, BSTR_P(option), BSTR_P(value)); goto error; } ok = true; error: if (!ok) errors++; if (errors > 16) { MP_ERR(config, "%s: too many errors, stopping.\n", location); break; } } talloc_free(tmp); return 1; }
/* Returns a list of parts, or NULL on parse error. * Syntax (without file header or URI prefix): * url ::= <entry> ( (';' | '\n') <entry> )* * entry ::= <param> ( <param> ',' )* * param ::= [<string> '='] (<string> | '%' <number> '%' <bytes>) */ static struct tl_parts *parse_edl(bstr str) { struct tl_parts *tl = talloc_zero(NULL, struct tl_parts); while (str.len) { if (bstr_eatstart0(&str, "#")) bstr_split_tok(str, "\n", &(bstr){0}, &str); if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";")) continue; struct tl_part p = { .length = -1 }; int nparam = 0; while (1) { bstr name, val; // Check if it's of the form "name=..." int next = bstrcspn(str, "=%,;\n"); if (next > 0 && next < str.len && str.start[next] == '=') { name = bstr_splice(str, 0, next); str = bstr_cut(str, next + 1); } else { const char *names[] = {"file", "start", "length"}; // implied name name = bstr0(nparam < 3 ? names[nparam] : "-"); } if (bstr_eatstart0(&str, "%")) { int len = bstrtoll(str, &str, 0); if (!bstr_startswith0(str, "%") || (len > str.len - 1)) goto error; val = bstr_splice(str, 1, len + 1); str = bstr_cut(str, len + 1); } else { next = bstrcspn(str, ",;\n"); val = bstr_splice(str, 0, next); str = bstr_cut(str, next); } // Interpret parameters. Explicitly ignore unknown ones. if (bstr_equals0(name, "file")) { p.filename = bstrto0(tl, val); } else if (bstr_equals0(name, "start")) { if (!parse_time(val, &p.offset)) goto error; p.offset_set = true; } else if (bstr_equals0(name, "length")) { if (!parse_time(val, &p.length)) goto error; } else if (bstr_equals0(name, "timestamps")) { if (bstr_equals0(val, "chapters")) p.chapter_ts = true; } nparam++; if (!bstr_eatstart0(&str, ",")) break; } if (!p.filename) goto error; MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p); } if (!tl->num_parts) goto error; return tl; error: talloc_free(tl); return NULL; } static struct demuxer *open_source(struct timeline *tl, char *filename) { for (int n = 0; n < tl->num_sources; n++) { struct demuxer *d = tl->sources[n]; if (strcmp(d->stream->url, filename) == 0) return d; } struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global); if (d) { MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d); } else { MP_ERR(tl, "EDL: Could not open source file '%s'.\n", filename); } return d; } static double demuxer_chapter_time(struct demuxer *demuxer, int n) { if (n < 0 || n >= demuxer->num_chapters) return -1; return demuxer->chapters[n].pts; } // Append all chapters from src to the chapters array. // Ignore chapters outside of the given time range. static void copy_chapters(struct demux_chapter **chapters, int *num_chapters, struct demuxer *src, double start, double len, double dest_offset) { for (int n = 0; n < src->num_chapters; n++) { double time = demuxer_chapter_time(src, n); if (time >= start && time <= start + len) { struct demux_chapter ch = { .pts = dest_offset + time - start, .metadata = mp_tags_dup(*chapters, src->chapters[n].metadata), }; MP_TARRAY_APPEND(NULL, *chapters, *num_chapters, ch); } } } // return length of the source in seconds, or -1 if unknown static double source_get_length(struct demuxer *demuxer) { double time; // <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW if (demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH, &time) <= 0) time = -1; return time; } static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer) { if (part->chapter_ts) { double start = demuxer_chapter_time(demuxer, part->offset); double length = part->length; double end = length; if (end >= 0) end = demuxer_chapter_time(demuxer, part->offset + part->length); if (end >= 0 && start >= 0) length = end - start; part->offset = start; part->length = length; } if (!part->offset_set) part->offset = demuxer->start_time; } static void build_timeline(struct timeline *tl, struct tl_parts *parts) { tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1); double starttime = 0; for (int n = 0; n < parts->num_parts; n++) { struct tl_part *part = &parts->parts[n]; struct demuxer *source = open_source(tl, part->filename); if (!source) goto error; resolve_timestamps(part, source); double end_time = source_get_length(source); if (end_time >= 0) end_time += source->start_time; // Unknown length => use rest of the file. If duration is unknown, make // something up. if (part->length < 0) { if (end_time < 0) { MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n", part->filename); end_time = 1; } part->length = end_time - part->offset; } else if (end_time >= 0) { double end_part = part->offset + part->length; if (end_part > end_time) { MP_WARN(tl, "EDL: entry %d uses %f " "seconds, but file has only %f seconds.\n", n, end_part, end_time); } } // Add a chapter between each file. struct demux_chapter ch = { .pts = starttime, .metadata = talloc_zero(tl, struct mp_tags), }; mp_tags_set_str(ch.metadata, "title", part->filename); MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch); // Also copy the source file's chapters for the relevant parts copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset, part->length, starttime); tl->parts[n] = (struct timeline_part) { .start = starttime, .source_start = part->offset, .source = source, }; starttime += part->length; } tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime}; tl->num_parts = parts->num_parts; tl->track_layout = tl->parts[0].source; return; error: tl->num_parts = 0; tl->num_chapters = 0; } // For security, don't allow relative or absolute paths, only plain filenames. // Also, make these filenames relative to the edl source file. static void fix_filenames(struct tl_parts *parts, char *source_path) { struct bstr dirname = mp_dirname(source_path); for (int n = 0; n < parts->num_parts; n++) { struct tl_part *part = &parts->parts[n]; char *filename = mp_basename(part->filename); // plain filename only part->filename = mp_path_join_bstr(parts, dirname, bstr0(filename)); } } static void build_mpv_edl_timeline(struct timeline *tl) { struct priv *p = tl->demuxer->priv; struct tl_parts *parts = parse_edl(p->data); if (!parts) { MP_ERR(tl, "Error in EDL.\n"); return; } MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer); // Source is .edl and not edl:// => don't allow arbitrary paths if (tl->demuxer->stream->uncached_type != STREAMTYPE_EDL) fix_filenames(parts, tl->demuxer->filename); build_timeline(tl, parts); talloc_free(parts); } static int try_open_file(struct demuxer *demuxer, enum demux_check check) { struct priv *p = talloc_zero(demuxer, struct priv); demuxer->priv = p; demuxer->fully_read = true; struct stream *s = demuxer->stream; if (s->uncached_type == STREAMTYPE_EDL) { p->data = bstr0(s->path); return 0; } if (check >= DEMUX_CHECK_UNSAFE) { if (!bstr_equals0(stream_peek(s, strlen(HEADER)), HEADER)) return -1; } p->data = stream_read_complete(s, demuxer, 1000000); if (p->data.start == NULL) return -1; bstr_eatstart0(&p->data, HEADER); return 0; } const struct demuxer_desc demuxer_desc_edl = { .name = "edl", .desc = "Edit decision list", .open = try_open_file, .load_timeline = build_mpv_edl_timeline, };
dvb_state_t *dvb_get_state(stream_t *stream) { if (global_dvb_state != NULL) { return global_dvb_state; } struct mp_log *log = stream->log; struct mpv_global *global = stream->global; stream->priv = mp_get_config_group(stream, stream->global, &stream_dvb_conf); dvb_priv_t *priv = stream->priv; int type, size; char filename[30], *name; dvb_channels_list *list; dvb_card_config_t *cards = NULL, *tmp; dvb_state_t *state = NULL; bstr prog, card; if (!bstr_split_tok(bstr0(stream->path), "@", &card, &prog)) { prog = card; card.len = 0; } if (prog.len) { talloc_free(priv->cfg_prog); priv->cfg_prog = bstrto0(NULL, prog); } if (card.len) { bstr r; priv->cfg_card = bstrtoll(card, &r, 0); if (r.len || priv->cfg_card < 1 || priv->cfg_card > 4) { MP_ERR(stream, "invalid card: '%.*s'\n", BSTR_P(card)); return NULL; } } state = malloc(sizeof(dvb_state_t)); if (state == NULL) return NULL; state->count = 0; state->switching_channel = false; state->stream_used = true; state->cards = NULL; state->fe_fd = state->dvr_fd = -1; for (int i = 0; i < MAX_CARDS; i++) { snprintf(filename, sizeof(filename), "/dev/dvb/adapter%d/frontend0", i); int fd = open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) { mp_verbose(log, "DVB_CONFIG, can't open device %s, skipping\n", filename); continue; } mp_verbose(log, "Opened device %s, FD: %d\n", filename, fd); int* tuner_types = NULL; int num_tuner_types = dvb_get_tuner_types(fd, log, &tuner_types); close(fd); mp_verbose(log, "Frontend device %s offers %d supported delivery systems.\n", filename, num_tuner_types); for (int num_tuner_type=0; num_tuner_type<num_tuner_types; num_tuner_type++) { type = tuner_types[num_tuner_type]; if (type != TUNER_SAT && type != TUNER_TER && type != TUNER_CBL && type != TUNER_ATSC) { mp_verbose(log, "DVB_CONFIG, can't detect tuner type of " "card %d, skipping\n", i); continue; } void *talloc_ctx = talloc_new(NULL); char *conf_file = NULL; if (priv->cfg_file && priv->cfg_file[0]) conf_file = priv->cfg_file; else { switch (type) { case TUNER_TER: conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf.ter"); break; case TUNER_CBL: conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf.cbl"); break; case TUNER_SAT: conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf.sat"); break; case TUNER_ATSC: conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf.atsc"); break; } if (conf_file) { mp_verbose(log, "Ignoring other channels.conf files.\n"); } else { conf_file = mp_find_config_file(talloc_ctx, global, "channels.conf"); } } list = dvb_get_channels(log, priv->cfg_full_transponder, conf_file, type); talloc_free(talloc_ctx); if (list == NULL) continue; size = sizeof(dvb_card_config_t) * (state->count + 1); tmp = realloc(state->cards, size); if (tmp == NULL) { mp_err(log, "DVB_CONFIG, can't realloc %d bytes, skipping\n", size); free(list); continue; } cards = tmp; name = malloc(20); if (name == NULL) { mp_err(log, "DVB_CONFIG, can't realloc 20 bytes, skipping\n"); free(list); free(tmp); continue; } state->cards = cards; state->cards[state->count].devno = i; state->cards[state->count].list = list; state->cards[state->count].type = type; snprintf(name, 20, "DVB-%c card n. %d", type == TUNER_TER ? 'T' : (type == TUNER_CBL ? 'C' : 'S'), state->count + 1); state->cards[state->count].name = name; state->count++; } talloc_free(tuner_types); } if (state->count == 0) { free(state); state = NULL; } global_dvb_state = state; return state; }
static struct bstr read_quoted(struct bstr *data) { *data = bstr_lstrip(*data); if (!eat_char(data, '"')) return (struct bstr) {0}; int end = bstrchr(*data, '"'); if (end < 0) return (struct bstr) {0}; struct bstr res = bstr_splice(*data, 0, end); *data = bstr_cut(*data, end + 1); return res; } // Read a 2 digit unsigned decimal integer. // Return -1 on failure. static int read_int_2(struct bstr *data) { *data = bstr_lstrip(*data); if (data->len && data->start[0] == '-') return -1; struct bstr s = *data; int res = (int)bstrtoll(s, &s, 10); if (data->len == s.len || data->len - s.len > 2) return -1; *data = s; return res; } static double read_time(struct bstr *data) { struct bstr s = *data; bool ok = true; double t1 = read_int_2(&s); ok = eat_char(&s, ':') && ok; double t2 = read_int_2(&s); ok = eat_char(&s, ':') && ok; double t3 = read_int_2(&s); ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0; return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0; } static struct bstr skip_utf8_bom(struct bstr data) { return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data; } // Check if the text in data is most likely CUE data. This is used by the // demuxer code to check the file type. // data is the start of the probed file, possibly cut off at a random point. bool mp_probe_cue(struct bstr data) { bool valid = false; data = skip_utf8_bom(data); for (;;) { enum cue_command cmd = read_cmd(&data, NULL); // End reached. Since the line was most likely cut off, don't use the // result of the last parsing call. if (data.len == 0) break; if (cmd == CUE_ERROR) return false; if (cmd != CUE_EMPTY) valid = true; } return valid; }