void Export_ID3(FILE *inf, FILE *outf) { struct ID3Tag tag={NULL,0,0,0,0,0,0.0,NULL,0,NULL,NULL,NULL,NULL}; //Validate parameters assert_wrapper((inf != NULL) && (outf != NULL)); assert_wrapper(Lyrics.piececount != 0); //This function is not to be called with an empty Lyrics structure tag.fp=inf; //Store the input file pointer into the ID3 tag structure //Seek to first MPEG frame in input file (after ID3 tag, otherwise presume it's at the beginning of the file) if(Lyrics.verbose) (void) puts("Parsing input MPEG audio file"); if(FindID3Tag(&tag)) //Find start and end of ID3 tag fseek_err(tag.fp,tag.tagend,SEEK_SET); //Seek to the first MP3 frame (immediately after the ID3 tag) else rewind_err(tag.fp); //Rewind file if no ID3 tag is found //Load MPEG information if(GetMP3FrameDuration(&tag) == 0) //Find the sample rate defined in the MP3 frame at the current file position { printf("Error loading MPEG information from file \"%s\"\nAborting",Lyrics.srcfilename); exit_wrapper(1); } //Load any existing ID3 frames (void) ID3FrameProcessor(&tag); //Build output file (void) BuildID3Tag(&tag,outf); //Release memory DestroyID3(&tag); //Release the ID3 structure's memory DestroyOmitID3framelist(Lyrics.nosrctag); //Release ID3 frame omission list }
void Export_SRT(FILE *outf) { struct Lyric_Line *curline=NULL; //Conductor of the lyric line linked list struct Lyric_Piece *temp=NULL; //A conductor for the lyric pieces list unsigned long ctr=1; //The lyric counter assert_wrapper(outf != NULL); //This must not be NULL assert_wrapper(Lyrics.piececount != 0); //This function is not to be called with an empty Lyrics structure if(Lyrics.verbose) printf("\nExporting SRT subtitles to file \"%s\"\n\nWriting subtitles\n",Lyrics.outfilename); //Export the lyric pieces curline=Lyrics.lines; //Point lyric line conductor to first line of lyrics while(curline != NULL) //For each line of lyrics { temp=curline->pieces; //Starting with the first piece of lyric in this line if(Lyrics.verbose) printf("\tSubtitle line %lu: ",ctr); while(temp != NULL) //For each piece of lyric in this line { if(ctr != 1) { //All subtitles after the first are prefixed with two newline characters if(fprintf(outf,"\n\n") < 0) { printf("Error writing double space between subtitles: %s\nAborting\n",strerror(errno)); exit_wrapper(1); } } if((fprintf(outf,"%lu\n",ctr) < 0) || Write_SRT_Timestamp(outf,temp->start) || (fprintf(outf," --> ") < 0) || Write_SRT_Timestamp(outf,temp->start+temp->duration) || (fprintf(outf,"\n%s",temp->lyric) < 0)) { //Write the subtitle number, the start timestamp, the "-->" separator, the end timestamp and the subtitle text printf("Error exporting lyric %lu: %s\nAborting\n",ctr,strerror(errno)); exit_wrapper(2); } if(Lyrics.verbose) printf("'%s'",temp->lyric); temp=temp->next; //Advance to next lyric piece as normal ctr++; }//end while(temp != NULL) curline=curline->next; //Advance to next line of lyrics if(Lyrics.verbose) (void) putchar('\n'); } if(Lyrics.verbose) printf("\nSRT export complete. %lu subtitles written",Lyrics.piececount); }
int ReadSyncEntry(struct VL_Sync_entry *ptr,FILE *inf) { //Portable function to read 16 bytes from the file. Returns nonzero upon error unsigned long buffer=0; unsigned long ftell_result; assert_wrapper((ptr != NULL) && (inf != NULL)); //These must not be NULL //Determine if the next 16 bytes can be read without reaching end of file ftell_result=ftell_err(inf); if(ftell_result +16 > VL.filesize + 8) //If the size of a sync entry would exceed the end of the file as defined in the VL header return 1; //return EOF //Read lyric number and reserved bytes ReadDWORDLE(inf,&buffer); //Read 4 bytes from file (the lyric # and the 2 reserved bytes) ptr->lyric_number=buffer & 0xFF; //Convert to 2 byte integer //Read start char ReadWORDLE(inf,&(ptr->start_char)); //Read 2 bytes from file //Read end char ReadWORDLE(inf,&(ptr->end_char)); //Read 2 bytes from file //Read start time ReadDWORDLE(inf,&(ptr->start_time)); //Read 4 bytes from file //Read end time ReadDWORDLE(inf,&(ptr->end_time)); //Read 4 bytes from file return 0; //return success }
void WriteTextInfoFrame(FILE *outf,const char *frameid,const char *string) { size_t size=0; //Validate input parameters assert_wrapper((outf != NULL) && (frameid != NULL) && (string != NULL)); if(strlen(frameid) != 4) { printf("Error: Attempted export of invalid frame ID \"%s\"\nAborting",frameid); exit_wrapper(1); } if(Lyrics.verbose >= 2) printf("\tWriting frame \"%s\"\n",frameid); size=strlen(string)+1; //The frame payload size is the string and the encoding byte //Write ID3 frame header fwrite_err(frameid,4,1,outf); //Write frame ID WriteDWORDBE(outf,(unsigned long)size); //Write frame size (total frame size-header size) in Big Endian format fputc_err(0xC0,outf); //Write first flag byte (preserve frame for both tag and file alteration) fputc_err(0,outf); //Write second flag byte (no compression, encryption or grouping identity) //Write frame data fputc_err(0,outf); //Write ASCII encoding fwrite_err(string,(size_t)size-1,1,outf); //Write the string }
int SearchOmitID3framelist(struct OmitID3frame *ptr,const char *frameid) { assert_wrapper(frameid != NULL); while(ptr != NULL) { assert_wrapper(ptr->frameid != NULL); if(ptr->frameid[0]=='*') return 2; //Return wildcard match if(strcasecmp(ptr->frameid,frameid) == 0) return 1; //Return exact match ptr=ptr->next; } return 0; //Return no match }
void DisplayID3Tag(char *filename) { char *buffer=NULL; //A buffer to store strings returned from ReadTextInfoFrame() struct ID3Tag tag={NULL,0,0,0,0,0,0.0,NULL,0,NULL,NULL,NULL,NULL}; struct ID3Frame *temp=NULL; //Conductor for the ID3 frames list //Validate parameters if(!filename) return; tag.fp=fopen(filename,"rb"); if(tag.fp == NULL) return; (void) ID3FrameProcessor(&tag); if(tag.id3v1present > 1) //If the ID3v1 tag exists and is populated { (void) puts("ID3v1 tag:"); if(tag.id3v1title != NULL) printf("\tTitle = \"%s\"\n",tag.id3v1title); if(tag.id3v1artist != NULL) printf("\tArtist = \"%s\"\n",tag.id3v1artist); if(tag.id3v1album != NULL) printf("\tAlbum = \"%s\"\n",tag.id3v1album); if(tag.id3v1year != NULL) printf("\tYear = \"%s\"\n",tag.id3v1year); } //Display ID3v2 frames if(tag.frames != NULL) { (void) puts("ID3v2 tag:"); for(temp=tag.frames;temp != NULL;temp=temp->next) { //For each frame that was parsed, check the tags[] array to see if it is a text info frame assert_wrapper(temp->frameid != NULL); if(IsTextInfoID3FrameID(temp->frameid)) { fseek_err(tag.fp,temp->pos,SEEK_SET); //Seek to the ID3 frame in question buffer=ReadTextInfoFrame(tag.fp); //Read and store the text info frame content if(buffer == NULL) printf("\tFrame: %s = Unreadable or Unicode format\n",temp->frameid); else printf("\tFrame: %s = \"%s\"\n",temp->frameid,buffer); free(buffer); //Release text info frame content buffer=NULL; } else printf("\t%s frame present\n",temp->frameid); }//For each frame that was parsed } else if(!tag.id3v1present) (void) puts("No ID3 Tag detected"); DestroyID3(&tag); //Release the ID3 structure's memory (void) fclose(tag.fp); }
char *GrabID3TextFrame(struct ID3Tag *tag,const char *frameid,char *buffer,unsigned long buffersize) { struct ID3Frame *frameptr=NULL; char *string=NULL; //Validate parameters assert_wrapper((tag != NULL) && (tag->fp != NULL) && (tag->frames != NULL) && (frameid != NULL)); if(buffer != NULL) assert_wrapper(buffersize != 0); //Locate the specified frame frameptr=FindID3Frame(tag,frameid); if(frameptr == NULL) //If no match was found return NULL; //return without altering the buffer //Seek to the frame's location in the file if(fseek(tag->fp,frameptr->pos,SEEK_SET) != 0) //If there was a file I/O error seeking return NULL; //return without altering the buffer //Read the text info string string=ReadTextInfoFrame(tag->fp); if(string == NULL) //If there was an error reading the string return NULL; //return without altering the buffer //If performing the logic to buffer the string if(buffer != NULL) { //Verify string will fit in buffer if(strlen(string) + 1 > (size_t)buffersize) //If the string (including NULL terminator) is larger than the buffer string[buffersize-1]='\0'; //truncate the string to fit strcpy(buffer,string); //Copy string to buffer free(string); //Release memory used by the string return buffer; //Return success } //Otherwise return the allocated string return string; }
struct ID3Frame *FindID3Frame(struct ID3Tag *tag,const char *frameid) { struct ID3Frame *ptr=NULL; //Validate parameters assert_wrapper((tag != NULL) && (tag->fp != NULL) && (tag->frames != NULL) && (frameid != NULL)); //Locate the specified frame for(ptr=tag->frames;ptr != NULL;ptr=ptr->next) //For each frame in the linked list if(strcasecmp(ptr->frameid,frameid) == 0) //If this is the frame being searched for return ptr; //Return success return NULL; //If no result was found, return failure }
struct OmitID3frame *AddOmitID3framelist(struct OmitID3frame *ptr,const char *frameid) { struct OmitID3frame *temp; assert_wrapper(frameid != NULL); //Allocate and init new link temp=malloc_err(sizeof(struct OmitID3frame)); temp->frameid=DuplicateString(frameid); //Append link to existing list if applicable temp->next=ptr; //The new link becomes the head and points forward to whatever the head was return temp; //Otherwise return the new linked list head }
void JB_Load(FILE *inf) { size_t maxlinelength; //I will count the length of the longest line (including NULL char/newline) in the //input file so I can create a buffer large enough to read any line into char *buffer; //Will be an array large enough to hold the largest line of text from input file unsigned long index=0; //Used to index within a line of input text unsigned long index2=0; //Used to index within an output buffer char textbuffer[101]={0}; //Allow for a 100 character lyric text unsigned long processedctr=0; //The current line number being processed in the text file unsigned long bufferctr=0; //Ensure textbuffer[] isn't overflowed char notename=0; //Used for parsing note names double timestamp=0.0; //Used to read timestamp char linetype=0; //Is set to one of the following: 1 = lyric, 2 = line break, 3 = end of file unsigned char pitch=0; //Stores the lyric pitch transposed to middle octave int readerrordetected = 0; assert_wrapper(inf != NULL); //This must not be NULL //Find the length of the longest line maxlinelength=FindLongestLineLength(inf,1); //Allocate memory buffer large enough to hold any line in this file buffer=(char *)malloc_err(maxlinelength); (void) fgets_err(buffer,(int)maxlinelength,inf); //Read first line of text, capping it to prevent buffer overflow if(Lyrics.verbose) printf("\nImporting C9C lyrics from file \"%s\"\n\n",Lyrics.infilename); processedctr=0; //This will be set to 1 at the beginning of the main while loop while(!feof(inf) && !readerrordetected) //Until end of file is reached or fgets() returns an I/O error { processedctr++; if(Lyrics.verbose) printf("\tProcessing line %lu\n",processedctr); index = 0; //Read end of file if(strcasestr_spec(buffer,"ENDFILE")) { //A line starting with "ENDFILE" denotes the end of the lyric entries linetype = 3; } //Read the lyric pitch else if(isalpha(buffer[index])) { //A line starting with an alphabetical letter is a normal lyric entry linetype = 1; notename = toupper(buffer[index++]); if(isalpha(buffer[index])) index++; //The first lyric entry seems to repeat the note name pitch=60; //The pitches will be interpreted as ranging from C4 to B4 switch(notename) //Add value of note in current octave { case 'B': pitch+=11; break; case 'A': pitch+=9; break; case 'G': pitch+=7; break; case 'F': pitch+=5; break; case 'E': pitch+=4; break; case 'D': pitch+=2; break; default: break; } if(buffer[index] == '#') { //If the note name is followed by a sharp character, pitch++; //increase the pitch by one half step index++; //Seek past the sharp character } while(buffer[index] != ':') { //Seek to the expected colon character if(buffer[index] == '\0') { //The line ends unexpectedly printf("Error: Invalid lyric entry in line %lu during C9C lyric import (colon missing)\nAborting\n",processedctr); exit_wrapper(1); } index++; } index++; //Seek beyond the colon //Read the lyric text index2=bufferctr=0; while(!isspace(buffer[index])) { //Until whitespace is reached if(buffer[index] == '\0') { //The line ends unexpectedly printf("Error: Invalid lyric entry in line %lu during C9C lyric import (whitespace missing)\nAborting\n",processedctr); exit_wrapper(2); } textbuffer[index2++] = buffer[index++]; //Copy the character to a buffer bufferctr++; if(bufferctr == 100) { //Unexpectedly long lyric reached printf("Error: Invalid lyric entry in line %lu during C9C lyric import (lyric is too long)\nAborting\n",processedctr); exit_wrapper(3); } } textbuffer[index2++] = '\0'; //Terminate the string }//A line starting with an alphabetical letter is a normal lyric entry //Read line break else if(buffer[index] == '-') { //A line starting with "--:S" is the start of a period of silence between lyrics (will be treated as a line break) linetype = 2; } else { //Invalid input printf("Error: Invalid input \"%s\" in line %lu during C9C import\nAborting\n",&(buffer[index]),processedctr); exit_wrapper(4); } //Seek to timestamp while(!isdigit(buffer[index])) { //Until a number (the timestamp) is reached if(buffer[index] == '\0') { //The line ends unexpectedly printf("Error: Invalid line break entry in line %lu during C9C lyric import (timestamp missing)\nAborting\n",processedctr); exit_wrapper(5); } index++; } //Read timestamp if(sscanf(&(buffer[index]), "%20lf", ×tamp) != 1) { //Double floating point value didn't parse printf("Error: Invalid lyric entry in line %lu during C9C lyric import (error parsing timestamp)\nAborting\n",processedctr); exit_wrapper(6); } timestamp *= 1000.0; //Convert to milliseconds //Adjust previous lyric's end position if(Lyrics.lastpiece) { //If there was a previous lyric unsigned long length; assert_wrapper(Lyrics.lastpiece->lyric != NULL); length = (unsigned long)strlen(Lyrics.lastpiece->lyric); Lyrics.lastpiece->duration = timestamp + 0.5 - Lyrics.realoffset - Lyrics.lastpiece->start; //Remember to offset start by realoffset, otherwise Lyrics.lastpiece->start could be the larger operand, causing an overflow if(Lyrics.lastpiece->lyric[length - 1] == '-') { //If the previous lyric ended in a hyphen, the previous lyric lasts all the way up to the start of this one Lyrics.lastpiece->groupswithnext=1; //The previous lyric piece will group with this one } else { //Otherwise space out the lyrics a bit, 1/32 second was suggested if(Lyrics.lastpiece->duration > 31) Lyrics.lastpiece->duration -= 31; //31ms ~= 1 sec/32 } } //Add lyric if(linetype == 1) //If this line defined a new lyric { //Track for pitch changes, enabling Lyrics.pitch_tracking if applicable if((Lyrics.last_pitch != 0) && (Lyrics.last_pitch != pitch)) //There's a pitch change Lyrics.pitch_tracking=1; Lyrics.last_pitch=pitch; //Consider this the last defined pitch if(Lyrics.line_on != 1) //If we're at this point, there should be a line of lyrics in progress CreateLyricLine(); AddLyricPiece(textbuffer,timestamp + 0.5,timestamp + 0.5,pitch,0); //Add lyric with no defined duration } //Add line break else if(linetype == 2) { //If this line defined a line break EndLyricLine(); Lyrics.lastpiece = NULL; //Prevent the first lyric from the new line from altering the previous lyric's duration, which was set by the line break position } //End processing else break; if(fgets(buffer, (int)maxlinelength,inf) == NULL) //Read next line of text, so the EOF condition can be checked, don't exit on EOF readerrordetected = 1; }//while(!feof(inf) && !readerrordetected) free(buffer); //No longer needed, release the memory before exiting function ForceEndLyricLine(); RecountLineVars(Lyrics.lines); //Rebuild line durations since this lyric format required adjusting timestamps after lines were parsed if(Lyrics.verbose) printf("C9C import complete. %lu lyrics loaded\n\n",Lyrics.piececount); }
void SRT_Load(FILE *inf) { char *buffer; //Buffer used to read from input file char *temp=NULL; //Used for string processing unsigned long processedctr=0; //The current line number being processed in the text file size_t maxlinelength; //I will count the length of the longest line (including NULL char/newline) in the //input file so I can create a buffer large enough to read any line into unsigned long startstamp=0,endstamp=0; unsigned long ctr=0; assert_wrapper(inf != NULL); //This must not be NULL //Find the length of the longest line maxlinelength=FindLongestLineLength(inf,1); //Allocate buffers to read file line by line buffer=malloc_err(maxlinelength); //Process each line of input file if(Lyrics.verbose) printf("\nImporting SRT subtitles from file \"%s\"\n\n",Lyrics.infilename); processedctr=0; //This will be set to 1 at the beginning of the main while loop while(fgets(buffer,(int)maxlinelength,inf) != NULL) //Read lines until end of file is reached, don't exit on EOF { processedctr++; //Find first timestamp in this line temp=SeekNextSRTTimestamp(buffer); //Point temp to first timestamp if(temp == NULL) //If there is none, skip this line continue; //Skip processing and read next line startstamp=ConvertSRTTimestamp(&temp,NULL); //Find second timestamp in this line temp=SeekNextSRTTimestamp(temp); //Point temp to second timestamp if(temp == NULL) { if(Lyrics.verbose) printf("Warning: Line #%lu does not contain the ending timestamp. Ignoring\n",processedctr); continue; //Skip processing and read next line } endstamp=ConvertSRTTimestamp(&temp,NULL); //Read next line, which is expected to be the subtitle entry if(fgets(buffer,(int)maxlinelength,inf) == NULL) break; //If another line couldn't be read, exit loop ctr = (unsigned long)strlen(buffer); //Find index of the string's NULL terminator while((ctr > 0) && ((buffer[ctr-1] == '\n') || (buffer[ctr-1] == '\r'))) { //If the string isn't empty and the last character is a newline or carriage return buffer[ctr-1] = '\0'; //Truncate it from the string ctr--; //Track the position of the end of the string } //Add lyric piece as a lyric line CreateLyricLine(); AddLyricPiece(buffer,startstamp,endstamp,PITCHLESS,0); //Write lyric with no defined pitch EndLyricLine(); }//end while(fgets(buffer,maxlinelength,inf) != NULL) ForceEndLyricLine(); //Release memory buffers and return free(buffer); if(Lyrics.verbose) printf("SRT import complete. %lu subtitles loaded\n\n",Lyrics.piececount); }
unsigned long ConvertSRTTimestamp(char **ptr,int *errorstatus) { char *temp=NULL; unsigned int ctr=1; char failed=0; //Boolean: Parsing indicated that ptr didn't point at a valid timestamp, abort char hours[SRTTIMESTAMPMAXFIELDLENGTH+1]={0}; //Allow for pre-defined # of hours chars (and NULL terminator) char minutes[SRTTIMESTAMPMAXFIELDLENGTH+1]={0}; //Allow for pre-defined # of minute chars (and NULL terminator) char seconds[SRTTIMESTAMPMAXFIELDLENGTH+1]={0}; //Allow for pre-defined # of seconds chars (and NULL terminator) char millis[SRTTIMESTAMPMAXFIELDLENGTH+1]={0}; //Allow for pre-defined # of milliseconds chars (and NULL terminator) unsigned int index=0; //index variable into the 3 timestamp strings unsigned long sum=0; long conversion=0; //Will store the integer conversions of each of the 3 timestamp strings if(ptr == NULL) failed=1; else { temp=*ptr; //To improve readability for string parsing if(temp == NULL) failed=1; } //Validate that the string has brackets if((temp != NULL) && !isdigit(temp[0])) //If this character is not a beginning character for a timestamp failed=1; //Parse hours portion of timestamp index=0; while(!failed) { assert_wrapper(temp != NULL); if((temp[ctr] == '\0') || (!isdigit(temp[ctr]) && (temp[ctr] != ':'))) //Numerical char(s) followed by delimiter are expected failed=1; else { if(isdigit(temp[ctr])) //Is a numerical character { if(index > SRTTIMESTAMPMAXFIELDLENGTH) //If more than the defined limit of chars have been parsed failed=2; //This is more than we allow for else hours[index++]=temp[ctr++]; //copy character into hours string, increment indexes } else { //this character is a delimiter ctr++; //seek past the colon break; //break from loop } } } assert_wrapper(index < SRTTIMESTAMPMAXFIELDLENGTH+1); //Ensure that writing the NULL character won't overflow hours[index]='\0'; //Terminate hours string //validate minutes portion of timestamp index=0; while(!failed) { assert_wrapper(temp != NULL); if((temp[ctr] == '\0') || (!isdigit(temp[ctr]) && (temp[ctr] != ':'))) //Numerical char(s) followed by delimiter are expected failed=1; else { if(isdigit(temp[ctr])) //Is a numerical character { if(index > SRTTIMESTAMPMAXFIELDLENGTH) //If more than the defined limit of chars have been parsed failed=2; //This is more than we allow for else minutes[index++]=temp[ctr++]; //copy character into minutes string, increment indexes } else { //this character is a delimiter ctr++; //seek past the colon break; //break from loop } } } assert_wrapper(index < SRTTIMESTAMPMAXFIELDLENGTH+1); //Ensure that writing the NULL character won't overflow minutes[index]='\0'; //Terminate minutes string //validate seconds portion of timestamp index=0; while(!failed) { assert_wrapper(temp != NULL); if((temp[ctr] == '\0') || (!isdigit(temp[ctr]) && (temp[ctr] != ','))) //Numerical char(s) followed by delimiter are expected failed=1; else { if(isdigit(temp[ctr])) //Is a numerical character { if(index > SRTTIMESTAMPMAXFIELDLENGTH) //If more than the defined limit of chars have been parsed failed=2; //This is more than we allow for else seconds[index++]=temp[ctr++]; //copy character into seconds string, increment indexes } else { //this character is a delimiter ctr++; //seek past the colon break; //break from loop } } } assert_wrapper(index < SRTTIMESTAMPMAXFIELDLENGTH+1); //Ensure that writing the NULL character won't overflow seconds[index]='\0'; //Terminate seconds string //validate milliseconds portion of timestamp index=0; while(!failed) { assert_wrapper(temp != NULL); if((temp[ctr] == '\0') || !isdigit(temp[ctr])) //If not a numerical character { if(isspace(temp[ctr]) || (temp[ctr] == '\0'))//If this character is a whitespace/newline/carriage return/NULL character break; //break from loop else failed=1; } else { if(index > SRTTIMESTAMPMAXFIELDLENGTH) //If more than the defined limit of chars have been parsed failed=2; else millis[index++]=temp[ctr++]; //copy character into milliseconds string, increment indexes } } assert_wrapper(index < SRTTIMESTAMPMAXFIELDLENGTH+1); //Ensure that writing the NULL character won't overflow millis[index]='\0'; //Terminate milliseconds string if(failed) //If parsing failed { printf("Error: Invalid timestamp \"%s\"\n",temp); if(errorstatus != NULL) { *errorstatus=1; return 0; } else { (void) puts("Aborting"); exit_wrapper(1); } } assert_wrapper((ptr != NULL) && (temp != NULL)); if((ptr != NULL) && (temp != NULL)) { //Redundant check to satisfy cppcheck *ptr=&(temp[ctr+1]); //Store address of first character following end of timestamp } //Convert hours string to integer and add to sum temp=RemoveLeadingZeroes(hours); if(temp[0] != '0') //If hours is not 0 { conversion=atol(temp); //get integer conversion if(conversion<1) //Values of 0 are errors from atol(), negative values are not allowed for timestamps { (void) puts("Error converting string to integer\nAborting"); free(temp); if(errorstatus != NULL) { *errorstatus=2; return 0; } else exit_wrapper(2); } sum+=conversion*3600000; //one hour is 3600000 milliseconds } free(temp); //Convert minutes string to integer and add to sum temp=RemoveLeadingZeroes(minutes); if(temp[0] != '0') //If minutes is not 0 { conversion=atol(temp); //get integer conversion if(conversion<1) //Values of 0 are errors from atol(), negative values are not allowed for timestamps { (void) puts("Error converting string to integer\nAborting"); free(temp); if(errorstatus != NULL) { *errorstatus=2; return 0; } else exit_wrapper(2); } sum+=conversion*60000; //one minute is 60,000 milliseconds } free(temp); //Convert seconds string to integer and add to sum temp=RemoveLeadingZeroes(seconds); if(temp[0] != '0') //If minutes is not 0 { conversion=atol(temp); //get integer conversion if(conversion<1) //Values of 0 are errors from atol(), negative values are not allowed for timestamps { (void) puts("Error converting string to integer\nAborting"); free(temp); if(errorstatus != NULL) { *errorstatus=3; return 0; } else exit_wrapper(3); } sum+=conversion*1000; //one second is 1,000 milliseconds } free(temp); //Convert milliseconds string to integer and add to sum temp=RemoveLeadingZeroes(millis); if(temp[0] != '0') //If minutes is not 0 { conversion=atol(temp); //get integer conversion if(conversion<1) //Values of 0 are errors from atol(), negative values are not allowed for timestamps { (void) puts("Error converting string to integer\nAborting"); free(temp); if(errorstatus != NULL) { *errorstatus=4; return 0; } else exit_wrapper(4); } sum+=conversion; } free(temp); return sum; }
void Export_VL(FILE *outf) { struct _VLSTRUCT_ *OutVL=NULL; //The prepared VL structure to write to file struct VL_Text_entry *curtext=NULL; //Conductor for the text chunk list struct VL_Text_entry *textnext=NULL; //Used for OutVL deallocation struct VL_Sync_entry *cursync=NULL; //Conductor for the sync chunk list struct VL_Sync_entry *syncnext=NULL; //Used for OutVL deallocation unsigned long ctr=0; long filepos=0; assert_wrapper(outf != NULL); //This must not be NULL assert_wrapper(Lyrics.piececount != 0); //This function is not to be called with an empty Lyrics structure if(Lyrics.verbose) printf("\nExporting VL lyrics to file \"%s\"\n\n",Lyrics.outfilename); //Write VL header: 'V','L','2',0 fputc_err('V',outf); fputc_err('L',outf); fputc_err('2',outf); fputc_err(0,outf); //Pad the next four bytes, which are supposed to hold the size of the file, minus the size of the VL header (8 bytes) WriteDWORDLE(outf,0); if(Lyrics.verbose>=2) (void) puts("Writing text chunk"); //Write Text chunk header: 'T','E','X','T' fputc_err('T',outf); fputc_err('E',outf); fputc_err('X',outf); fputc_err('T',outf); //Pad the next four bytes, which are supposed to hold the size of the header chunk, minus the size of the chunk header (8 bytes) WriteDWORDLE(outf,0); //Convert the Offset string to centiseconds from milliseconds (as this is how the VL format interprets it) if((Lyrics.Offset != NULL)) //If there is an offset { if(strlen(Lyrics.Offset) < 2) //If the offset is 0 or 1 characters long, it will be lost, free the string { free(Lyrics.Offset); Lyrics.Offset=NULL; } else Lyrics.Offset[strlen(Lyrics.Offset)-1]='\0'; //Truncate last digit to effectively divide by ten } //Write 8 Unicode strings, for Title, Artist, Album, Editor, Offset and 3 empty strings WriteUnicodeString(outf,Lyrics.Title); WriteUnicodeString(outf,Lyrics.Artist); WriteUnicodeString(outf,Lyrics.Album); WriteUnicodeString(outf,Lyrics.Editor); WriteUnicodeString(outf,NULL); //The Offset tag should be 0 because all timestamps are written as being absolute WriteUnicodeString(outf,NULL); WriteUnicodeString(outf,NULL); WriteUnicodeString(outf,NULL); //Create sync and lyric structures OutVL=VL_PreWrite(); //Write lyric strings curtext=OutVL->Lyrics; if(Lyrics.verbose) (void) puts("Writing VL file"); while(curtext != NULL) //For each text chunk { assert_wrapper(curtext->text != NULL); if(Lyrics.verbose) printf("\tText chunk entry written: \"%s\"\n",curtext->text); WriteUnicodeString(outf,curtext->text); curtext=curtext->next; //Point to next text chunk entry } //Write padding if necessary ctr=ftell_err(outf)%4; //ctr=# of padding bytes needed to align output file position to a DWORD boundary while(ctr--) //For each byte of padding necessary fputc_err(0,outf); //Go back and write the text chunk size after the text chunk header filepos=ftell_err(outf); //Store file position OutVL->textsize=filepos-16; //File position - VL and Text header sizes = text chunk size fseek_err(outf,12,SEEK_SET); //File position 12 is where the text chunk size is to be stored WriteDWORDLE(outf,OutVL->textsize); //Write text chunk size to file fseek_err(outf,filepos,SEEK_SET); //Seek back to the end of the text chunk (where the Sync chunk will begin) if(Lyrics.verbose>=2) (void) puts("Writing Sync chunk"); //Write Sync chunk header: 'S','Y','N','C' fputc_err('S',outf); fputc_err('Y',outf); fputc_err('N',outf); fputc_err('C',outf); //Pad the next four bytes, which are supposed to hold the size of the sync chunk, minus the size of the chunk header (8 bytes) WriteDWORDLE(outf,0); //Write sync entries cursync=OutVL->Syncs; while(cursync != NULL) { WriteWORDLE(outf,cursync->lyric_number);//Write lyric line number this sync entry pertains to WriteWORDLE(outf,0); //Write reserved Word value WriteWORDLE(outf,cursync->start_char); //Write start offset WriteWORDLE(outf,cursync->end_char); //Write end offset WriteDWORDLE(outf,cursync->start_time); //Write start time WriteDWORDLE(outf,cursync->end_time); //Write end time cursync=cursync->next; } //Go back and write the sync chunk size after the sync chunk header filepos=ftell_err(outf); //Store file position OutVL->syncsize=filepos - OutVL->textsize - 24; //File position - Text chunk size - 3 header sizes = sync chunk size fseek_err(outf,OutVL->textsize+20,SEEK_SET); //File position for sync chunk size is text chunk size +2.5 headers WriteDWORDLE(outf,OutVL->syncsize); //Write sync chunk size to file //Go back and write the VL file size after the VL header, which completes the VL export filepos-=8; //Subtract the VL header size from the total file size fseek_err(outf,4,SEEK_SET); //File posistion for VL file size is after the "VL2\0" string WriteDWORDLE(outf,filepos); //Write the VL file size - header value //Destroy the OutVL structure if(Lyrics.verbose>=2) (void) puts("\tReleasing memory from export VL structure"); curtext=OutVL->Lyrics; //Point conductor at first text chunk entry while(curtext != NULL) //For each text chunk entry { textnext=curtext->next; if(curtext->text != NULL) free(curtext->text); //Free string free(curtext); //Free text chunk structure curtext=textnext; } cursync=OutVL->Syncs; //Point conductor at first sync chunk entry while(cursync != NULL) //For each sync chunk entry { syncnext=cursync->next; free(cursync); //Free sync chunk structure cursync=syncnext; } free(OutVL); if(Lyrics.verbose) printf("\nVL export complete. %lu lyrics written\n",Lyrics.piececount); }
struct _VLSTRUCT_ *VL_PreWrite(void) { struct _VLSTRUCT_ *OutVL=NULL; //Create a VL structure to build the exported VL format char *lyrline=NULL; //A pointer to an array large enough to store the largest line of lyrics char *temp=NULL; unsigned long maxlength=0; //The calculated length of the longest lyric line (excluding null terminator) unsigned long charcount=0; //The running sum of the length of all lyric pieces in a line unsigned long index=0; //The current index into the lyric line, stored with each sync entry unsigned long linenum=0; //The number of the currently-processed lyric line struct Lyric_Piece *curpiece=NULL; //Conductor for lyric piece linked list struct Lyric_Line *curline=NULL; //Conductor for lyric line linked list struct VL_Text_entry *curtext=NULL; //Text entry for current lyric piece struct VL_Sync_entry *cursync=NULL; //Sync entry for current lyric piece struct VL_Sync_entry *newsync=NULL; //Used to build new sync entries to insert into list struct VL_Text_entry *newtext=NULL; //Used to build new text entries to insert into list unsigned long lastendtime=0; //The end time for the last lyric piece. The next lyric's timestamp must be at least 1 more than this static const struct _VLSTRUCT_ empty_VLSTRUCT_; //Auto-initialize all members to 0/NULL if(Lyrics.verbose) (void) puts("Building VL structure"); //Allocate the Export_VL structure OutVL=malloc_err(sizeof(struct _VLSTRUCT_)); *OutVL=empty_VLSTRUCT_; //Reliably initialize all values to 0/NULL //Initialize the Export_VL structure OutVL->numlines=Lyrics.linecount; OutVL->numsyncs=Lyrics.piececount; //Find the length of the longest line of lyrics maxlength=0; curline=Lyrics.lines; //Conductor points to first line of lyrics while(curline != NULL) //For each line of lyrics { charcount=0; //reset count curpiece=curline->pieces; //Conductor points to first lyric in the line while(curpiece != NULL) //For each lyric piece in the line { if(curpiece->lyric != NULL) charcount+=strlen(curpiece->lyric); //Add the length of the lyric curpiece=curpiece->next; //Point to next lyric piece } if(charcount>maxlength) //If this line has more characters than any other so far maxlength=charcount; //store the number of characters curline=curline->next; //Point to next lyric line } //Initialize lyrline lyrline=malloc_err((size_t)(maxlength+1+Lyrics.piececount)); //Add piececount to allow for spacing between each word //Process lyric pieces curline=Lyrics.lines; //Conductor points to first line of lyrics lastendtime=0; linenum=0; //This will be used to index into the lyric array cursync=OutVL->Syncs; //Point conductor to first link in sync chunk list while(curline != NULL) //For each line of lyrics { lyrline[0]='\0'; //Empty the string buffer index=0; //Reset index to 0 curpiece=curline->pieces; //Conductor points to first lyric in the line while(curpiece != NULL) //For each lyric piece in this line { if(curpiece->lyric != NULL) //Only process if there is actual lyric text for this lyric piece { //Allocate a new sync list link newsync=malloc_err(sizeof(struct VL_Sync_entry)); //Build sync entry newsync->lyric_number=linenum; //Initialize lyric number, which should be the lyric line number it refers to newsync->start_char=index; //Initialize sync entry start index if(curpiece->next != NULL) //If there's another lyric piece in this line newsync->end_char=index+strlen(curpiece->lyric)-1; //Find ending offset of this sync entry else newsync->end_char=0xFFFF; //The lyric reaches the end of the line of lyrics newsync->start_time=curpiece->start/10; //VL stores timestamps in 10ms increments newsync->end_time=(curpiece->start+curpiece->duration)/10; if(newsync->end_time <= newsync->start_time) //If the duration of the lyric was less than 10ms (became 0 during the division) newsync->end_time+=1; //add the minimum duration of 10ms if((lastendtime != 0) && (newsync->start_time < lastendtime)) //Ensure that this doesn't overlap with last piece newsync->start_time=lastendtime+1; lastendtime=newsync->end_time; //Store this to prevent overlapping with next piece index=newsync->end_char+1; //Set lyric character index one higher than the end of this sync entry newsync->next=NULL; //This will be the last link in the list //Insert sync entry into linked list if(OutVL->Syncs == NULL) //This is the first sync entry in the list OutVL->Syncs=newsync; else //Last link points forward to this link cursync->next=newsync; cursync=newsync; //This becomes the new last link in the list //Append lyric piece to string and append a space if necessary strcat(lyrline,curpiece->lyric); if((curpiece->next != NULL) && (curpiece->groupswithnext == 0)) { //There is another lyric piece in this line and this piece does not group with it strcat(lyrline," "); index++; //Increment the index to preserve subsequent sync piece placement } } curpiece=curpiece->next; //Point to next lyric piece }//end while(curpiece != NULL) //Make a permanent copy of the combined lyric string and store it in the lyric array temp=DuplicateString(lyrline); //Copy completed lyric line into new string linenum++; //Iterate lyric line number //Allocate new text link newtext=malloc_err(sizeof(struct VL_Text_entry)); //Build new link and insert into list newtext->text=temp; newtext->next=NULL; if(OutVL->Lyrics == NULL) //This is the first text chunk entry OutVL->Lyrics=newtext; else //Last link points forward to this link { assert_wrapper(curtext != NULL); curtext->next=newtext; } curtext=newtext; //This becomes the last link in the list curline=curline->next; //Point to next lyric line if(Lyrics.verbose>=2) printf("\tStored text chunk entry \"%s\"\n",temp); }//end while(curline != NULL) free(lyrline); //No longer needed return OutVL; //Return completed structure }
void VL_Load(FILE *inf) { unsigned long ctr=0; //Generic counter unsigned long start_off=0; //Starting offset of a lyric piece in milliseconds unsigned long end_off=0; //Ending offset of a lyric piece in milliseconds char *temp=NULL; //Pointer for string manipulation struct VL_Text_entry *curtext=NULL; //Conductor for text chunk linked list struct VL_Sync_entry *cursync=NULL; //Conductor for sync chunk linked list unsigned short cur_line_len=0; //The length of the currently line of lyrics unsigned short start_char=0; //The starting character offset for the current sync entry unsigned short end_char=0; //The ending character offset for the current sync entry char groupswithnext=0; //Tracks grouping, passed to AddLyricPiece() assert_wrapper(inf != NULL); //This must not be NULL Lyrics.freestyle_on=1; //VL is a pitch-less format, so import it as freestyle if(Lyrics.verbose) printf("Importing VL lyrics from file \"%s\"\n\n",Lyrics.infilename); //Build the VL storage structure (void) VL_PreLoad(inf,0); //Process offset if(Lyrics.offsetoverride == 0) { if(Lyrics.Offset == NULL) { if(Lyrics.verbose) (void) puts("No offset defined in VL file, applying offset of 0"); Lyrics.realoffset=0; } else if(strcmp(Lyrics.Offset,"0") != 0) { //If the VL file's offset is not zero and the command line offset is not specified assert_wrapper(Lyrics.Offset != NULL); //atol() crashes if NULL is passed to it Lyrics.realoffset=atol(Lyrics.Offset); //convert to number if(Lyrics.realoffset == 0) //atol returns 0 on error { printf("Error converting \"%s\" to integer value\nAborting\n",Lyrics.Offset); exit_wrapper(1); } if(Lyrics.verbose) printf("Applying offset defined in VL file: %ldms\n",Lyrics.realoffset); } //if the VL file's offset is defined as 0, that's what Lyrics.realoffset is initialized to already } if(Lyrics.verbose) (void) puts("Processing lyrics and sync entries"); //Process sync points, adding lyrics to Lyrics structure cursync=VL.Syncs; //Begin with first sync entry while(cursync != NULL) //For each sync point { groupswithnext=0; //Reset this condition start_off=cursync->start_time*10; //VL stores offsets as increments of 10 milliseconds each end_off=cursync->end_time*10; //Validate the lyric line number if(cursync->lyric_number >= VL.numlines) //lyric_number count starts w/ 0 instead of 1 and should never meet/exceed numlines { (void) puts("Error: Invalid line number detected during VL load\nAborting"); exit_wrapper(2); } //Validate the start and end character numbers in the sync entry start_char=cursync->start_char; end_char=cursync->end_char; if(start_char == 0xFFFF) { (void) puts("Error: Sync entry has no valid lyric during VL load\nAborting"); exit_wrapper(3); } //Seek to the correct lyric entry curtext=VL.Lyrics; //Point conductor to first text entry for(ctr=0; ctr<cursync->lyric_number; ctr++) if(curtext->next == NULL) { (void) puts("Error: Unexpected end of text piece linked list\nAborting"); exit_wrapper(4); } else curtext=curtext->next; //Seek forward to next piece cur_line_len = (unsigned short) strlen(curtext->text); //This value will be used several times in the rest of the loop if(start_char >= cur_line_len) //The starting offset cannot be past the end of the line of lyrics { (void) puts("Error: Sync entry has invalid starting offset during VL load\nAborting"); exit_wrapper(5); } if((end_char!=0xFFFF) && (end_char >= cur_line_len)) { //The ending offset cannot be past the end of the line of lyrics (void) puts("Error: Sync entry has invalid ending offset during VL load\nAborting"); exit_wrapper(6); } //Build the lyric based on the start and end character offsets temp=DuplicateString(curtext->text+start_char); //Copy the current text piece into a new string, starting from the character indexed by the sync entry if(Lyrics.verbose>=2) printf("\tProcessing sync entry #%lu: \"%s\"\tstart char=%u\tend char=%u\t\n",ctr,curtext->text,start_char,end_char); if(end_char != 0xFFFF) //If the sync entry ends before the end of the text piece { //"abcdef" strlen=6 st=2,en=4->"cde" if((isspace((unsigned char)temp[end_char-start_char-1])==0) && (isspace((unsigned char)temp[end_char-start_char])==0)) //if this sync entry's text doesn't end in whitespace and the next entry's doesn't begin in whitespace groupswithnext=1; //Allow AddLyricPiece to handle grouping and optional hyphen insertion //I've had to go back and forth on this line, but currently, end_char-start_char seems to mark the location at which to truncate, not the last character to keep before truncating temp[end_char-start_char]='\0'; //Truncate the string as indicated by the sync entry's end index (after the end_char) } //Add lyric to Lyric structure if(cursync->start_char == 0) //If this piece is the beginning of a line of lyrics { //Ensure a line of lyrics isn't already in progress if(Lyrics.line_on == 1) { (void) puts("Error: Lyric lines overlap during VL load\nAborting"); exit_wrapper(7); } if(Lyrics.verbose>=2) (void) puts("New line of lyrics:"); CreateLyricLine(); //Initialize the line } //Add lyric to Lyrics structure. AddLyricPiece(temp,start_off,end_off,PITCHLESS,groupswithnext); //Add the lyric piece to the Lyric structure, no defined pitch free(temp); //Release memory for this temporary string if((end_char == 0xFFFF) || (end_char == cur_line_len)) //If this piece ends a line of lyrics { //Ensure a line of lyrics is in progress if(Lyrics.line_on == 0) { (void) puts("Error: End of lyric line detected when none is started during VL load\nAborting"); exit_wrapper(8); } if(Lyrics.verbose>=2) (void) puts("End line of lyrics"); EndLyricLine(); //End the line } cursync=cursync->next; }//end while(cursync != NULL) ForceEndLyricLine(); if(Lyrics.verbose) printf("VL import complete. %lu lyrics loaded\n",Lyrics.piececount); ReleaseVL(); //Release memory used to build the VL structure }
void ID3_Load(FILE *inf) { //Parses the file looking for an ID3 tag. If found, the first MP3 frame is examined to obtain the sample rate //Then an SYLT frame is searched for within the ID3 tag. If found, synchronized lyrics are imported struct ID3Tag tag={NULL,0,0,0,0,0,0.0,NULL,0,NULL,NULL,NULL,NULL}; struct ID3Frame *frameptr=NULL; struct ID3Frame *frameptr2=NULL; assert_wrapper(inf != NULL); //This must not be NULL tag.fp=inf; if(Lyrics.verbose) printf("Importing ID3 lyrics from file \"%s\"\n\nParsing input MPEG audio file\n",Lyrics.infilename); if(ID3FrameProcessor(&tag) == 0) //Build a list of the ID3 frames { DestroyID3(&tag); //Release the ID3 structure's memory return; //Return if no frames were successfully parsed } //Parse MPEG frame header fseek_err(tag.fp,tag.tagend,SEEK_SET); //Seek to the first MP3 frame (immediately after the ID3 tag) if(GetMP3FrameDuration(&tag) == 0) //Find the sample rate defined in the MP3 frame { DestroyID3(&tag); //Release the ID3 structure's memory return; //Return if the sample rate was not found or was invalid } frameptr=FindID3Frame(&tag,"SYLT"); //Search ID3 frame list for SYLT ID3 frame frameptr2=FindID3Frame(&tag,"USLT"); //Search ID3 frame list for USLT ID3 frame if(frameptr != NULL) { //Perform Synchronized ID3 Lyric import fseek_err(tag.fp,frameptr->pos,SEEK_SET); //Seek to SYLT ID3 frame SYLT_Parse(&tag); } else if(frameptr2 != NULL) { //Perform Unsynchronized ID3 Lyric import (void) puts("\aUnsynchronized ID3 lyric import currently not supported"); exit_wrapper(1); } else { DestroyID3(&tag); //Release the ID3 structure's memory return; //Return if neither lyric frame is present } //Load song tags Lyrics.Title=GrabID3TextFrame(&tag,"TIT2",NULL,0); //Return the defined song title, if it exists Lyrics.Artist=GrabID3TextFrame(&tag,"TPE1",NULL,0); //Return the defined artist, if it exists Lyrics.Album=GrabID3TextFrame(&tag,"TALB",NULL,0); //Return the defined album, if it exists Lyrics.Year=GrabID3TextFrame(&tag,"TYER",NULL,0); //Return the defined year, if it exists if(Lyrics.Title == NULL) //If there was no Title defined in the ID3v2 tag Lyrics.Title=DuplicateString(tag.id3v1title); //Use one defined in the ID3v1 tag if it exists if(Lyrics.Artist == NULL) //If there was no Artist defined in the ID3v2 tag Lyrics.Artist=DuplicateString(tag.id3v1artist); //Use one defined in the ID3v1 tag if it exists if(Lyrics.Album == NULL) //If there was no Album defined in the ID3v2 tag Lyrics.Album=DuplicateString(tag.id3v1album); //Use one defined in the ID3v1 tag if it exists if(Lyrics.Year == NULL) //If there was no Year defined in the ID3v2 tag Lyrics.Year=DuplicateString(tag.id3v1year); //Use one defined in the ID3v1 tag if it exists ForceEndLyricLine(); DestroyID3(&tag); //Release the ID3 structure's memory if(Lyrics.verbose) printf("ID3 import complete. %lu lyrics loaded\n\n",Lyrics.piececount); }
int VL_PreLoad(FILE *inf,char validate) { long ftell_result=0; //Used to store return value from ftell() char buffer[5]= {0}; //Used to read word/doubleword integers from file unsigned long ctr=0; //Generic counter char *temp=NULL; //Temporary pointer for allocated strings struct VL_Sync_entry se= {0,0,0,0,0,NULL}; //Used to store sync entries from file during parsing struct VL_Text_entry *ptr1=NULL; //Used for allocating links for the text chunk list struct VL_Sync_entry *ptr2=NULL; //Used for allocation links for the sync chunk list struct VL_Text_entry *curtext; //Conductor for text chunk linked list struct VL_Sync_entry *cursync; //Conductor for sync chunk linked list assert_wrapper(inf != NULL); //Initialize variables VL.numlines=0; VL.numsyncs=0; VL.filesize=0; VL.textsize=0; VL.syncsize=0; VL.Lyrics=NULL; VL.Syncs=NULL; curtext=NULL; //list starts out empty cursync=NULL; //list starts out empty se.next=NULL; //In terms of linked lists, this always represents the last link if(Lyrics.verbose) (void) puts("Reading VL file header"); //Read file header fread_err(buffer,4,1,inf); //Read 4 bytes, which should be 'V','L','2',0 ReadDWORDLE(inf,&(VL.filesize)); //Read doubleword from file in little endian format if(strncmp(buffer,"VL2",4) != 0) { if(validate) return 1; else { (void) puts("Error: Invalid file header string\nAborting"); exit_wrapper(1); } } //Read text header fread_err(buffer,4,1,inf); //Read 4 bytes, which should be 'T','E','X','T' buffer[4]='\0'; //Add a NULL character to make buffer into a proper string ReadDWORDLE(inf,&(VL.textsize)); //Read doubleword from file in little endian format if(VL.textsize % 4 != 0) { (void) puts("Error: VL spec. violation: Sync chunk is not padded to a double-word alignment"); if(validate) return 2; else { (void) puts("Aborting"); exit_wrapper(2); } } if(strncmp(buffer,"TEXT",5) != 0) { (void) puts("Error: Invalid text header string"); if(validate) return 3; else { (void) puts("Aborting"); exit_wrapper(3); } } //Read sync header, which should be th.textsize+16 bytes into the file fseek_err(inf,VL.textsize+16,SEEK_SET); fread_err(buffer,4,1,inf); //Read 4 bytes, which should be 'S','Y','N','C' buffer[4]='\0'; //Add a NULL character to make buffer into a proper string ReadDWORDLE(inf,&(VL.syncsize)); //Read doubleword from file in little endian format if(strncmp(buffer,"SYNC",5) != 0) { (void) puts("Error: Invalid sync header string"); if(validate) return 4; else { (void) puts("Aborting"); exit_wrapper(4); } } //Validate chunk sizes given in headers (textsize was already validated if the Sync header was correctly read) fseek_err(inf,0,SEEK_END); //Seek to end of file ftell_result = ftell(inf); //Get position of end of file if(ftell_result < 0) { printf("Error determining file length during file size validation: %s\n",strerror(errno)); if(validate) return 5; else { (void) puts("Aborting"); exit_wrapper(5); } } if((unsigned long)ftell_result != VL.filesize+8) //Validate filesize { (void) puts("Error: Filesize does not match size given in file header"); if(validate) return 6; else { (void) puts("Aborting"); exit_wrapper(6); } } if(VL.filesize != VL.textsize + VL.syncsize + 16) //Validate syncsize { (void) puts("Error: Incorrect size given in sync chunk"); if(validate) return 7; else { (void) puts("Aborting"); exit_wrapper(7); } } //Load tags if(Lyrics.verbose) (void) puts("Loading VL tag strings"); fseek_err(inf,16,SEEK_SET); //Seek to the Title tag for(ctr=0; ctr<5; ctr++) //Expecting 5 null terminated unicode strings { temp=ReadUnicodeString(inf); //Allocate array and read Unicode string if(temp[0] != '\0') //If the string wasn't empty, save the tag switch(ctr) { //Look at ctr to determine which tag we have read, and assign to the appropriate tag string: case 0: SetTag(temp,'n',0); break; case 1: SetTag(temp,'s',0); break; case 2: SetTag(temp,'a',0); break; case 3: SetTag(temp,'e',0); break; case 4: //Offset SetTag(temp,'o',0); //Do not multiply the offset by -1 break; default: (void) puts("Unexpected error"); if(validate) return 8; else { (void) puts("Aborting"); exit_wrapper(8); } break; }//end switch(ctr) free(temp); //Free string, a copy of which would have been stored in the Lyrics structure as necessary }//end for(ctr=0;ctr<5;) //Validate the existence of three empty unicode strings for(ctr=0; ctr<3; ctr++) //Expecting 3 empty, null terminated unicode strings if(ParseUnicodeString(inf) != 0) { (void) puts("Error: Reserved string is not empty during load"); if(validate) return 9; else { (void) puts("Aborting"); exit_wrapper(9); } } //Load the lyric strings if(Lyrics.verbose) (void) puts("Loading text chunk entries"); while(1) //Read lyric lines { //Check to see if the Sync Chunk has been reached if((unsigned long)ftell_err(inf) >= VL.textsize + 16) //The Text chunk size + the file and text header sizes is the position of the Sync chunk break; //Read lyric string temp=ReadUnicodeString(inf); ftell_result=ftell_err(inf); //If this string is empty if(temp[0] == '\0') { if(VL.textsize+16 - ftell_result <= 3) //This 0 word value ends within 3 bytes of the sync header (is padding) { free(temp); //Release string as it won't be used break; //text chunk has been read } else { printf("Error: Empty lyric string detected before file position 0x%lX\n",ftell_result); free(temp); if(validate) return 10; else { (void) puts("Aborting"); exit_wrapper(10); } } } if((unsigned long)ftell_result > VL.textsize + 16) //If reading this string caused the file position to cross into Sync chunk { (void) puts("Error: Lyric string overlapped into Sync chunk"); free(temp); if(validate) return 11; else { (void) puts("Aborting"); exit_wrapper(11); } } if(Lyrics.verbose) printf("\tLoading text piece #%lu: \"%s\"\n",VL.numlines,temp); //Allocate link ptr1=malloc_err(sizeof(struct VL_Text_entry)); //Allocate memory for new link //Build link and insert into list ptr1->text=temp; ptr1->next=NULL; if(VL.Lyrics == NULL) //This is the first lyric piece read from the input file VL.Lyrics=ptr1; else { assert_wrapper(curtext != NULL); curtext->next=ptr1; //The end of the list points forward to this new link } curtext=ptr1; //This link is now at the end of the list VL.numlines++; //one more lyric string has been parsed }//end while(1) if(Lyrics.verbose) printf("%lu text chunk entries loaded\n",VL.numlines); //Load the Sync points if(Lyrics.verbose) (void) puts("Loading sync chunk entries"); fseek_err(inf,VL.textsize+16+8,SEEK_SET); //Seek to first sync entry (8 bytes past start of sync header) while(ReadSyncEntry(&se,inf) == 0) //Read all entries { if(se.start_char > se.end_char) { (void) puts("Error: Invalid sync point offset is specified to start after end offset"); if(validate) return 12; else { (void) puts("Aborting"); exit_wrapper(12); } } if(se.lyric_number!=0xFFFF && se.start_char!=0xFFFF && se.start_time!=0xFFFFFFFF) { //This is a valid sync entry //Allocate link ptr2=malloc_err(sizeof(struct VL_Sync_entry)); //Build link and insert into list memcpy(ptr2,&se,sizeof(struct VL_Sync_entry)); //copy structure into newly allocated link if(VL.Syncs == NULL) //This is the first sync piece read from the input file VL.Syncs=ptr2; else //The end of the list points forward to this new link { assert_wrapper(cursync != NULL); cursync->next=ptr2; } cursync=ptr2; //This link is now at the end of the list VL.numsyncs++; //one more sync entry has been parsed } } if(Lyrics.verbose) (void) puts("VL_PreLoad completed"); return 0; }
void SYLT_Parse(struct ID3Tag *tag) { unsigned char frameheader[10]={0}; //This is the ID3 frame header unsigned char syltheader[6]={0}; //This is the SYLT frame header, excluding the terminated descriptor string that follows char *contentdescriptor=NULL; //The null terminated string located after the SYLT frame header char timestampformat=0; //Will be set based on the SYLT header (1= MPEG Frames, 2=Milliseconds) char *string=NULL; //Used to store SYLT strings that are read char *string2=NULL; //Used to build a display version of the string for debug output (minus newline) unsigned char timestamparray[4]={0};//Used to store the timestamp for a lyric string unsigned long timestamp=0; //The timestamp converted to milliseconds unsigned long breakpos; //Will be set to the first byte beyond the SYLT frame unsigned long framesize=0; char groupswithnext=0; //Used for grouping logic char linebreaks=0; //Tracks whether the imported lyrics defined linebreaks (if not, each ID3 lyric should be treated as one line) struct Lyric_Piece *ptr=NULL,*ptr2=NULL; //Used to insert line breaks as necessary struct Lyric_Line *lineptr=NULL; //Used to insert line breaks as necessary unsigned long length=0; //Used to store the length of each string that is read from file unsigned long filepos=0; //Used to track the current file position during SYLT lyric parsing //Validate input assert_wrapper((tag != NULL) && (tag->fp != NULL)); breakpos=ftell_err(tag->fp); //Load and validate ID3 frame header and SYLT frame header fread_err(frameheader,10,1,tag->fp); //Load ID3 frame header fread_err(syltheader,6,1,tag->fp); //Load SYLT frame header if((frameheader[9] & 192) != 0) { (void) puts("ID3 Compression and Encryption are not supported\nAborting"); exit_wrapper(3); } if(syltheader[0] != 0) { (void) puts("Unicode ID3 lyrics are not supported\nAborting"); exit_wrapper(4); } //Load and validate content descriptor string contentdescriptor=ReadString(tag->fp,NULL,0); //Load content descriptor string if(contentdescriptor == NULL) //If the content descriptor string couldn't be read { (void) puts("Damaged Content Descriptor String\nAborting"); exit_wrapper(1); } //Validate timestamp format timestampformat=syltheader[4]; if((timestampformat != 1) && (timestampformat != 2)) { printf("Warning: Invalid timestamp format (%d) specified, ms timing assumed\n",timestampformat); timestampformat=2; //Assume millisecond timing } //Process framesize as a 4 byte Big Endian integer framesize=((unsigned long)frameheader[4]<<24) | ((unsigned long)frameheader[5]<<16) | ((unsigned long)frameheader[6]<<8) | ((unsigned long)frameheader[7]); //Convert to 4 byte integer if(framesize & 0x80808080) //According to the ID3v2 specification, the MSB of each of the 4 bytes defining the tag size must be zero exit_wrapper(5); //If this isn't the case, the size is invalid assert(framesize < 0x80000000); //Redundant assert() to resolve a false positive with Coverity (this assertion will never be triggered because the above exit_wrapper() call would be triggered first) breakpos=breakpos + framesize + 10; //Find the position that is one byte past the end of the SYLT frame if(Lyrics.verbose>=2) printf("SYLT frame info:\n\tFrame size is %lu bytes\n\tEnds after byte 0x%lX\n\tTimestamp format: %s\n\tLanguage: %c%c%c\n\tContent Type %d\n\tContent Descriptor: \"%s\"\n\n",framesize,breakpos-1,timestampformat == 1 ? "MPEG frames" : "Milliseconds",syltheader[1],syltheader[2],syltheader[3],syltheader[5],contentdescriptor != NULL ? contentdescriptor : "(none)"); if(Lyrics.verbose) (void) puts("Parsing SYLT frame:"); free(contentdescriptor); //Release this, it's not going to be used contentdescriptor=NULL; //Load SYLT lyrics filepos=ftell_err(tag->fp); while(filepos < breakpos) //While we haven't reached the end of the SYLT frame { //Load the lyric text string=ReadString(tag->fp,&length,0); //Load SYLT lyric string, save the string length if(string == NULL) { (void) puts("Invalid SYLT lyric string\nAborting"); exit_wrapper(6); } //Load the timestamp if(fread(timestamparray,4,1,tag->fp) != 1) //Read timestamp { (void) puts("Error reading SYLT timestamp\nAborting"); exit_wrapper(7); } filepos+=length+4; //The number of bytes read from the input file during this iteration is the string length and the timestamp length //Process the timestamp as a 4 byte Big Endian integer timestamp=(unsigned long)((timestamparray[0]<<24) | (timestamparray[1]<<16) | (timestamparray[2]<<8) | timestamparray[3]); if(timestampformat == 1) //If this timestamp is in MPEG frames instead of milliseconds timestamp=((double)timestamp * tag->frameduration + 0.5); //Convert to milliseconds, rounding up //Perform line break logic assert(string != NULL); //(check string for NULL again to satisfy cppcheck) if((string[0] == '\r') || (string[0] == '\n')) //If this lyric begins with a newline or carriage return { EndLyricLine(); //End the lyric line before the lyric is added linebreaks=1; //Track that line break character(s) were found in the lyrics } if(Lyrics.verbose >= 2) { string2=DuplicateString(string); //Make a copy of the string for display purposes string2=TruncateString(string2,1); //Remove leading/trailing whitespace, newline chars, etc. printf("Timestamp: 0x%X%X%X%X\t%lu %s\t\"%s\"\t%s",timestamparray[0],timestamparray[1],timestamparray[2],timestamparray[3],timestamp,(timestampformat == 1) ? "MPEG frames" : "Milliseconds",string2,(string[0]=='\n') ? "(newline)\n" : "\n"); free(string2); string2=NULL; } //Perform grouping logic //Handle whitespace at the beginning of the parsed lyric piece as a signal that the piece will not group with previous piece if(isspace(string[0])) if(Lyrics.curline->pieces != NULL) //If there was a previous lyric piece on this line Lyrics.lastpiece->groupswithnext=0; //Ensure it is set to not group with this lyric piece if(isspace(string[strlen(string)-1])) //If the lyric ends in a space groupswithnext=0; else groupswithnext=1; if(Lyrics.line_on == 0) //Ensure that a line phrase is started CreateLyricLine(); //Add lyric piece, during testing, I'll just write it with a duration of 1ms AddLyricPiece(string,timestamp,timestamp+1,PITCHLESS,groupswithnext); //Write lyric with no defined pitch free(string); //Free string if((Lyrics.lastpiece != NULL) && (Lyrics.lastpiece->prev != NULL) && (Lyrics.lastpiece->prev->groupswithnext)) //If this piece groups with the previous piece Lyrics.lastpiece->prev->duration=Lyrics.lastpiece->start-Lyrics.realoffset-Lyrics.lastpiece->prev->start; //Extend previous piece's length to reach this piece, take the current offset into account }//While we haven't reached the end of the SYLT frame //If the imported lyrics did not contain line breaks, they must be inserted manually if(!linebreaks && Lyrics.piececount) { if(Lyrics.verbose) (void) puts("\nImported ID3 lyrics did not contain line breaks, adding..."); ptr=Lyrics.lines->pieces; lineptr=Lyrics.lines; //Point to first line of lyrics (should be the only line) assert_wrapper((lineptr != NULL) && (ptr != NULL)); //This shouldn't be possible if Lyrics.piececount is nonzero if(lineptr->next != NULL) //If there is another line of lyrics defined return; //abort the insertion of automatic line breaks while((ptr != NULL) && (ptr->next != NULL)) //For each lyric { ptr2=ptr->next; //Store pointer to next lyric lineptr=InsertLyricLineBreak(lineptr,ptr2); //Insert a line break between this lyric and the next, line conductor points to new line ptr=ptr2; //lyric conductor points to the next lyric, which is at the beginning of the new line } } }
unsigned long ID3FrameProcessor(struct ID3Tag *ptr) { unsigned char header[10]={0}; //Used to store the ID3 frame header unsigned long filepos=0; int ctr2=0; //Used to validate frame ID struct ID3Frame *temp=NULL; //Used to allocate an ID3Frame link struct ID3Frame *head=NULL; //Used as the head pointer struct ID3Frame *cond=NULL; //Used as the conductor unsigned long ctr=0; //Count the number of frames processed unsigned long framesize=0; //Used to validate frame size char buffer[31]={0}; //Used to read an ID3v1 tag in the absence of an ID3v2 tag static struct ID3Frame emptyID3Frame; //Auto-initialize all members to 0/NULL //Validate input parameter if((ptr == NULL) || (ptr->fp == NULL)) return 0; //Return failure //Find and parse the ID3 header //ID3v1 fseek_err(ptr->fp,-128,SEEK_END); //Seek 128 back from the end of the file, where this tag would exist fread_err(buffer,3,1,ptr->fp); //Read 3 bytes for the "TAG" header buffer[3] = '\0'; //Ensure NULL termination if(strcasecmp(buffer,"TAG") == 0) //If this is an ID3v1 header { if(Lyrics.verbose) (void) puts("Loading ID3v1 tag"); ptr->id3v1present=1; //Track that this tag exists fread_err(buffer,30,1,ptr->fp); //Read the first field in the tag (song title) buffer[30] = '\0'; //Ensure NULL termination if(buffer[0] != '\0') //If the string isn't empty { ptr->id3v1present=2; //Track that this tag is populated ptr->id3v1title=DuplicateString(buffer); if(Lyrics.verbose) (void) puts("\tTitle loaded"); } fread_err(buffer,30,1,ptr->fp); //Read the second field in the tag (artist) buffer[30] = '\0'; //Ensure NULL termination if(buffer[0] != '\0') //If the string isn't empty { ptr->id3v1present=2; //Track that this tag is populated ptr->id3v1artist=DuplicateString(buffer); if(Lyrics.verbose) (void) puts("\tArtist loaded"); } fread_err(buffer,30,1,ptr->fp); //Read the third field in the tag (album) buffer[30] = '\0'; //Ensure NULL termination if(buffer[0] != '\0') //If the string isn't empty { ptr->id3v1present=2; //Track that this tag is populated ptr->id3v1album=DuplicateString(buffer); if(Lyrics.verbose) (void) puts("\tAlbum loaded"); } fread_err(buffer,4,1,ptr->fp); //Read the fourth field in the tag (year) buffer[4]='\0'; //Terminate the buffer to make it a string if(buffer[0] != '\0') //If the string isn't empty { ptr->id3v1present=2; //Track that this tag is populated ptr->id3v1year=DuplicateString(buffer); if(Lyrics.verbose) (void) puts("\tYear loaded"); } } //ID3v2 //Load ID3 frames into linked list rewind_err(ptr->fp); //Rewind file so that the ID3v2 header can be searched for if(FindID3Tag(ptr) == 0) //Locate the ID3 tag return 0; //Return if there was none if(Lyrics.verbose) (void) puts("Loading ID3v2 tag"); fseek_err(ptr->fp,ptr->framestart,SEEK_SET); //Seek to first ID3 frame filepos=ftell_err(ptr->fp); //Record file position of first expected ID3 frame while((filepos >= ptr->framestart) && (filepos < ptr->tagend)) { //While file position is at or after the end of the ID3 header, before or at end of the ID3 tag //Read frame header into buffer if(fread(header,10,1,ptr->fp) != 1) { ptr->frames=head; //Store the linked list into the ID3 tag structure return 0; //Return failure on I/O error } //Validate frame ID for(ctr2=0;ctr2<4;ctr2++) { if(!isupper(header[ctr2]) && !isdigit(header[ctr2])) //If the character is NOT valid for an ID3 frame ID { ptr->frames=head; //Store the linked list into the ID3 tag structure return ctr; //Return the number of frames that were already processed } } //Validate frame end framesize=((unsigned long)header[4]<<24) | ((unsigned long)header[5]<<16) | ((unsigned long)header[6]<<8) | ((unsigned long)header[7]); //Convert to 4 byte integer if(filepos + framesize + 10 > ptr->tagend) //If the defined frame size would cause the frame to extend beyond the end of the ID3 tag { ptr->frames=head; //Store the linked list into the ID3 tag structure return 0; //Return failure } //Initialize an ID3Frame structure temp=malloc_err(sizeof(struct ID3Frame)); //Allocate memory *temp=emptyID3Frame; //Reliably initialize all values to 0/NULL temp->frameid=malloc_err(5); //Allocate 5 bytes for the ID3 frame ID memcpy(temp->frameid,header,4); //Copy the 4 byte ID3 frame ID into the pre-terminated string temp->frameid[4]='\0'; //Terminate the string temp->pos=filepos; //Record the file position of the ID3 frame temp->length=((unsigned long)header[4]<<24) | ((unsigned long)header[5]<<16) | ((unsigned long)header[6]<<8) | ((unsigned long)header[7]); //Record the frame length (defined in header as 4 byte Big Endian integer) //Append ID3Frame link to the list if(head == NULL) //Empty list { head=temp; //Point head to new link cond=temp; //Point conductor to new link } else { assert_wrapper(cond != NULL); //The linked list is expected to not be corrupted cond->next=temp; //Conductor points forward to new link temp->prev=cond; //New link points back to conductor cond=temp; //Conductor points to new link } if(Lyrics.verbose >= 2) printf("\tFrame ID %s loaded\n",temp->frameid); ctr++; //Iterate counter (void) fseek(ptr->fp,framesize,SEEK_CUR); //Seek ahead to the beginning of the next ID3 frame filepos+=framesize + 10; //Update file position }//While file position is at or after the end of the ID3 header, before or at end of the ID3 tag if(Lyrics.verbose) printf("%lu ID3 frames loaded\n\n",ctr); ptr->frames=head; //Store the linked list into the ID3 tag structure return ctr; //Return the number of frames loaded }