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; }
struct cue_file *mp_parse_cue(struct bstr data) { struct cue_file *f = talloc_zero(NULL, struct cue_file); f->tags = talloc_zero(f, struct mp_tags); data = skip_utf8_bom(data); char *filename = NULL; // Global metadata, and copied into new tracks. struct cue_track proto_track = {0}; struct cue_track *cur_track = NULL; while (data.len) { struct bstr param; int cmd = read_cmd(&data, ¶m); switch (cmd) { case CUE_ERROR: talloc_free(f); return NULL; case CUE_TRACK: { MP_TARRAY_GROW(f, f->tracks, f->num_tracks); f->num_tracks += 1; cur_track = &f->tracks[f->num_tracks - 1]; *cur_track = proto_track; cur_track->tags = talloc_zero(f, struct mp_tags); break; } case CUE_TITLE: case CUE_PERFORMER: { static const char *metanames[] = { [CUE_TITLE] = "title", [CUE_PERFORMER] = "performer", }; struct mp_tags *tags = cur_track ? cur_track->tags : f->tags; mp_tags_set_bstr(tags, bstr0(metanames[cmd]), param); break; } case CUE_INDEX: { int type = read_int_2(¶m); double time = read_time(¶m); if (cur_track) { if (type == 1) { cur_track->start = time; cur_track->filename = filename; } else if (type == 0) { cur_track->pregap_start = time; } } break; } case CUE_FILE: // NOTE: FILE comes before TRACK, so don't use cur_track->filename filename = read_quoted(f, ¶m); break; } } return f; }
void build_cue_timeline(struct MPContext *mpctx) { void *ctx = talloc_new(NULL); struct bstr data = mpctx->demuxer->file_contents; data = skip_utf8_bom(data); struct cue_track *tracks = NULL; size_t track_count = 0; struct bstr filename = {0}; // Global metadata, and copied into new tracks. struct cue_track proto_track = {0}; struct cue_track *cur_track = &proto_track; while (data.len) { struct bstr param; switch (read_cmd(&data, ¶m)) { case CUE_ERROR: mp_msg(MSGT_CPLAYER, MSGL_ERR, "CUE: error parsing input file!\n"); goto out; case CUE_TRACK: { track_count++; tracks = talloc_realloc(ctx, tracks, struct cue_track, track_count); cur_track = &tracks[track_count - 1]; *cur_track = proto_track; break; } case CUE_TITLE: cur_track->title = read_quoted(¶m); break; case CUE_INDEX: { int type = read_int_2(¶m); double time = read_time(¶m); if (type == 1) { cur_track->start = time; cur_track->filename = filename; } else if (type == 0) { cur_track->pregap_start = time; } break; } case CUE_FILE: // NOTE: FILE comes before TRACK, so don't use cur_track->filename filename = read_quoted(¶m); break; } } if (track_count == 0) { mp_msg(MSGT_CPLAYER, MSGL_ERR, "CUE: no tracks found!\n"); goto out; } // Remove duplicate file entries. This might be too sophisticated, since // CUE files usually use either separate files for every single track, or // only one file for all tracks. struct bstr *files = 0; size_t file_count = 0; for (size_t n = 0; n < track_count; n++) { struct cue_track *track = &tracks[n]; track->source = -1; for (size_t file = 0; file < file_count; file++) { if (bstrcmp(files[file], track->filename) == 0) { track->source = file; break; } } if (track->source == -1) { file_count++; files = talloc_realloc(ctx, files, struct bstr, file_count); files[file_count - 1] = track->filename; track->source = file_count - 1; } }
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; }