/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with mpv. If not, see <http://www.gnu.org/licenses/>. */#include<stdlib.h>#include<stdbool.h>#include<string.h>#include<inttypes.h>#include"mpv_talloc.h"#include"misc/bstr.h"#include"common/common.h"#include"common/tags.h"#include"cue.h"#define SECS_PER_CUE_FRAME (1.0/75.0)enumcue_command{CUE_ERROR=-1,// not a valid CUE command, or an unknown extensionCUE_EMPTY,// line with whitespace onlyCUE_UNUSED,// valid CUE command, but ignored by this codeCUE_FILE,CUE_TRACK,CUE_INDEX,CUE_TITLE,CUE_PERFORMER,};staticconststruct{enumcue_commandcommand;constchar*text;}cue_command_strings[]={{CUE_FILE,"FILE"},{CUE_TRACK,"TRACK"},{CUE_INDEX,"INDEX"},{CUE_TITLE,"TITLE"},{CUE_UNUSED,"CATALOG"},{CUE_UNUSED,"CDTEXTFILE"},{CUE_UNUSED,"FLAGS"},{CUE_UNUSED,"ISRC"},{CUE_PERFORMER,"PERFORMER"},{CUE_UNUSED,"POSTGAP"},{CUE_UNUSED,"PREGAP"},{CUE_UNUSED,"REM"},{CUE_UNUSED,"SONGWRITER"},{CUE_UNUSED,"MESSAGE"},{-1},};staticenumcue_commandread_cmd(structbstr*data,structbstr*out_params){structbstrline=bstr_strip_linebreaks(bstr_getline(*data,data));line=bstr_lstrip(line);if(line.len==0)returnCUE_EMPTY;for(intn=0;cue_command_strings[n].command!=-1;n++){structbstrname=bstr0(cue_command_strings[n].text);if(bstr_startswith(line,name)){structbstrrest=bstr_cut(line,name.len);if(rest.len&&!strchr(WHITESPACE,rest.start[0]))continue;if(out_params)*out_params=rest;returncue_command_strings[n].command;}}returnCUE_ERROR;}staticbooleat_char(structbstr*data,charch){if(data->len&&data->start[0]==ch){*data=bstr_cut(*data,1);returntrue;}else{returnfalse;}}staticchar*read_quoted(void*talloc_ctx,structbstr*data){*data=bstr_lstrip(*data);if(!eat_char(data,'"'))returnNULL;intend=bstrchr(*data,'"');if(end<0)returnNULL;structbstrres=bstr_splice(*data,0,end);*data=bstr_cut(*data,end+1);returnbstrto0(talloc_ctx,res);}// Read a 2 digit unsigned decimal integer.// Return -1 on failure.staticintread_int_2(structbstr*data){*data=bstr_lstrip(*data);if(data->len&&data->start[0]=='-')return-1;structbstrs=*data;intres=(int)bstrtoll(s,&s,10);if(data->len==s.len||data->len-s.len>2)return-1;*data=s;returnres;}staticdoubleread_time(structbstr*data){structbstrs=*data;boolok=true;doublet1=read_int_2(&s);ok=eat_char(&s,':')&&ok;doublet2=read_int_2(&s);ok=eat_char(&s,':')&&ok;doublet3=read_int_2(&s);ok=ok&&t1>=0&&t2>=0&&t3>=0;returnok?t1*60.0+t2+t3*SECS_PER_CUE_FRAME:0;}staticstructbstrskip_utf8_bom(structbstrdata){returnbstr_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.boolmp_probe_cue(structbstrdata){boolvalid=false;data=skip_utf8_bom(data);for(;;){enumcue_commandcmd=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)returnfalse;if(cmd!=CUE_EMPTY)valid=true;}returnvalid;}structcue_file*mp_parse_cue(structbstrdata){structcue_file*f=talloc_zero(NULL,structcue_file);f->tags=talloc_zero(f,structmp_tags);data=skip_utf8_bom(data);char*filename=NULL;// Global metadata, and copied into new tracks.structcue_trackproto_track={0};structcue_track*cur_track=NULL;while(data.len){structbstrparam;intcmd=read_cmd(&data,&param);switch(cmd){caseCUE_ERROR:talloc_free(f);returnNULL;caseCUE_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,structmp_tags);break;}caseCUE_TITLE:caseCUE_PERFORMER:{staticconstchar*metanames[]={[CUE_TITLE]="title",[CUE_PERFORMER]="performer",};structmp_tags*tags=cur_track?cur_track->tags:f->tags;mp_tags_set_bstr(tags,bstr0(metanames[cmd]),param);break;}caseCUE_INDEX:{inttype=read_int_2(&param);doubletime=read_time(&param);if(cur_track){if(type==1){cur_track->start=time;cur_track->filename=filename;}elseif(type==0){cur_track->pregap_start=time;}}break;}caseCUE_FILE:// NOTE: FILE comes before TRACK, so don't use cur_track->filenamefilename=read_quoted(f,&param);break;}}returnf;}intmp_check_embedded_cue(structcue_file*f){char*fn0=f->tracks[0].filename;for(intn=1;n<f->num_tracks;n++){char*fn=f->tracks[n].filename;// both filenames have the same address (including NULL)if(fn0==fn)continue;// only one filename is NULL, or the strings don't matchif(!fn0||!fn||strcmp(fn0,fn)!=0)return-1;}return0;}