bool CKaraokeLyricsTextUStar::Load() { // Header parameters CStdString coverimage, bgimage; int bpm = 0, startoffsetms = 0; bool relative = false; // Read the text file std::vector< CStdString > lines = readFile( m_lyricsFile, true ); if ( lines.size() == 0 ) return false; // Clear the lyrics array clearLyrics(); // Parse and validate the header according to // http://ultrastardeluxe.xtremeweb-hosting.net/wiki/doku.php?id=editor:txt_file unsigned int idx = 0; for ( ; idx < lines.size() && lines[idx][0] == '#'; idx++ ) { // Parse into key:value size_t offset = lines[idx].find(':'); if (offset == std::string::npos) { CLog::Log( LOGERROR, "UStar lyric loader: invalid line '%s', no semicolon", lines[idx].c_str() ); return false; } CStdString key = lines[idx].substr(1, offset - 1); CStdString value = lines[idx].substr(offset + 1); if ( key == "TITLE" ) m_songName = value; else if ( key == "ARTIST" ) m_artist = value; else if ( key == "VIDEO" ) { m_videoFile = URIUtils::GetDirectory(m_lyricsFile); m_videoFile = URIUtils::AddFileToFolder(m_videoFile, value); if ( !XFILE::CFile::Exists( m_videoFile ) ) { CLog::Log( LOGERROR, "UStar lyric loader: VIDEO entry is present, but video file %s is not found", m_videoFile.c_str() ); m_videoFile.clear(); } } else if ( key == "COVER" ) coverimage = value; else if ( key == "BACKGROUND" ) bgimage = value; else if ( key == "VIDEOGAP" ) m_videoOffset = atoi( value.c_str() ); else if ( key == "BPM" ) bpm = atoi( value.c_str() ); else if ( key == "GAP" ) startoffsetms = atoi( value.c_str() ); else if ( key == "RELATIVE" ) relative = StringUtils::EqualsNoCase(value, "YES"); else if ( key == "LANGUAGE" || key == "EDITION" || key == "GENRE" || key == "YEAR" || key == "MP3" ) { ; // do nothing } else CLog::Log( LOGWARNING, "UStar lyric loader: unsupported keyword '%s'", key.c_str() ); } // BPM must be defined if ( bpm == 0 ) { CLog::Log( LOGERROR, "UStar lyric loader: BPM is not defined, file is invalid" ); return false; } // Should be more lines if ( idx == lines.size() ) { CLog::Log( LOGERROR, "UStar lyric loader: no lyrics found besides the header" ); return false; } double beatstep = 60.0 / bpm / 4.0; CLog::Log( LOGDEBUG, "UStar lyric loader: found valid lyrics, BPM is %d (%g)", bpm, beatstep ); // Now parse the words/notes part int lyric_flags = 0; for ( ; idx < lines.size() && lines[idx][0] != 'E'; idx++ ) { char type = lines[idx][0]; // A valid type should be followed by space if ( type != 'F' && type != ':' && type != '*' && type != '-' && lines[idx][1] != ' ' ) { CLog::Log( LOGERROR, "UStar lyric loader: invalid line '%s', bad note type or no tail space", lines[idx].c_str() ); return false; } // Parse the numbers in the line into the vector int numbercount = (type == '-') ? 1 : 3; char * p = &(lines[idx][1]); std::vector< int > numbers; while ( numbercount > 0 ) { unsigned int length = 0; // Skip all leading space while ( isspace( *p ) ) p++; // skip non-space while ( p[length] && !isspace( p[length] ) ) { if ( !isdigit( p[length] ) ) { CLog::Log( LOGERROR, "UStar lyric loader: invalid line '%s', bad digit at back-position %d", lines[idx].c_str(), numbercount ); return false; } length++; } p[length++] = '\0'; if ( strlen(p) == 0 ) { CLog::Log( LOGERROR, "UStar lyric loader: invalid line '%s', empty digit at back-position %d", lines[idx].c_str(), numbercount ); return false; } numbers.push_back( atoi( p ) ); // Adjust p p += length; numbercount--; } int notestart_timing = (int)((numbers[0] * beatstep) * 10 + (startoffsetms / 100)); if ( type != '-' ) { // Pitch is not used yet; notelenght will not be used at all //int notelength = numbers[1] * beatstep * 10; //int notepitch = numbers[2]; addLyrics( p, notestart_timing, lyric_flags | LYRICS_CONVERT_UTF8 ); lyric_flags = 0; //CLog::Log( LOGDEBUG, ":: %d %d [%d - %d] %d '%s'", numbers[0], numbers[1], notestart_timing, notelength, notepitch, text ); } else { lyric_flags = CKaraokeLyricsText::LYRICS_NEW_LINE; addLyrics( " ", notestart_timing, lyric_flags | LYRICS_CONVERT_UTF8 ); // If we're relative, adjust to the pause start if ( relative ) startoffsetms += (int)((numbers[0] * beatstep) * 10); //CLog::Log( LOGERROR, ":: [stop] %d [%d]", numbers[0], notestart_timing ); } } // Indicate that lyrics have pitch m_hasPitch = true; return true; }
bool CKaraokeLyricsTextLRC::ParserMultiTime(char *lyricData, unsigned int lyricSize, int timing_correction) { CLog::Log( LOGDEBUG, "LRC lyric loader: parser mult-time lyrics file" ); ParserState state = PARSER_INIT; unsigned int state_offset = 0; unsigned int lyric_flags = 0; std::vector<int> lyric_time(1, -1); int time_num = 0; std::vector<MtLyric> mtline; MtLyric line; int start_offset = 0; unsigned int offset = 0; for ( char * p = lyricData; offset < lyricSize; offset++, p++ ) { // Skip \r if ( *p == 0x0D ) continue; if ( state == PARSER_IN_LYRICS ) { // Lyrics are terminated either by \n or by [ if ( *p == '\n' || *p == '[' ) { // Time must be there if ( lyric_time[0] == -1 ) { CLog::Log( LOGERROR, "LRC lyric loader: lyrics file has no time before lyrics" ); return false; } // Add existing lyrics char current = *p; CStdString text; if ( offset > state_offset ) { // null-terminate string, we saved current char anyway *p = '\0'; text = &lyricData[0] + state_offset; } else text = " "; // add a single space for empty lyric // If this was end of line, set the flags accordingly if ( current == '\n' ) { // Add space after the trailing lyric in lrc text += " "; for ( int i = 0; i <= time_num; i++ ) { line.text = text; line.flags = lyric_flags | LYRICS_CONVERT_UTF8; line.timing = lyric_time[i]; mtline.push_back( line ); } state_offset = -1; lyric_flags = CKaraokeLyricsText::LYRICS_NEW_LINE; state = PARSER_INIT; } else { // No conversion needed as the file should be in UTF8 already for ( int i = 0; i <= time_num; i++ ) { line.text = text; line.flags = lyric_flags | LYRICS_CONVERT_UTF8; line.timing = lyric_time[i]; mtline.push_back( line ); } lyric_flags = 0; state_offset = offset + 1; state = PARSER_IN_TIME; } time_num = 0; lyric_time.resize(1); lyric_time[0] = -1; } } else if ( state == PARSER_IN_TIME ) { // Time is terminated by ] or > if ( *p == ']' || *p == '>' ) { int mins, secs, htenths, ltenths = 0; if ( offset == state_offset ) { CLog::Log( LOGERROR, "LRC lyric loader: empty time" ); return false; // [] - empty time } // null-terminate string char * timestr = &lyricData[0] + state_offset; *p = '\0'; // Now check if this is time field or info tag. Info tags are like [ar:Pink Floyd] char * fieldptr = strchr( timestr, ':' ); if ( timestr[0] >= 'a' && timestr[0] <= 'z' && timestr[1] >= 'a' && timestr[1] <= 'z' && fieldptr ) { // Null-terminate the field name and switch to the field value *fieldptr = '\0'; fieldptr++; while ( isspace( *fieldptr ) ) fieldptr++; // Check the info field if ( !strcmp( timestr, "ar" ) ) m_artist += fieldptr; else if ( !strcmp( timestr, "sr" ) ) { // m_artist += "[CR]" + CStdString( fieldptr ); // Add source to the artist name as a separate line } else if ( !strcmp( timestr, "ti" ) ) m_songName = fieldptr; else if ( !strcmp( timestr, "offset" ) ) { if ( sscanf( fieldptr, "%d", &start_offset ) != 1 ) { CLog::Log( LOGERROR, "LRC lyric loader: invalid [offset:] value '%s'", fieldptr ); return false; // [] - empty time } // Offset is in milliseconds; convert to 1/10 seconds start_offset /= 100; } state_offset = -1; state = PARSER_INIT; continue; } else if ( sscanf( timestr, "%d:%d.%1d%1d", &mins, &secs, &htenths, <enths ) == 4 ) lyric_time[time_num] = mins * 600 + secs * 10 + htenths + MathUtils::round_int( ltenths / 10 ); else if ( sscanf( timestr, "%d:%d.%1d", &mins, &secs, &htenths ) == 3 ) lyric_time[time_num] = mins * 600 + secs * 10 + htenths; else if ( sscanf( timestr, "%d:%d", &mins, &secs ) == 2 ) lyric_time[time_num] = mins * 600 + secs * 10; else { // bad time CLog::Log( LOGERROR, "LRC lyric loader: lyrics file has no proper time field: '%s'", timestr ); return false; } // Correct timing if necessary lyric_time[time_num] += start_offset; lyric_time[time_num] += timing_correction; if ( lyric_time[time_num] < 0 ) lyric_time[time_num] = 0; // Multi-time line if ( *(p + 1) == '[' ) { offset++; p++; state_offset = offset + 1; state = PARSER_IN_TIME; time_num++; lyric_time.push_back(-1); } else { // Set to next char state_offset = offset + 1; state = PARSER_IN_LYRICS; } } } else if ( state == PARSER_INIT ) { // Ignore spaces if ( *p == ' ' || *p == '\t' ) continue; // We're looking for [ or < if ( *p == '[' || *p == '<' ) { // Set to next char state_offset = offset + 1; state = PARSER_IN_TIME; time_num = 0; lyric_time.resize(1); lyric_time[0] = -1; } else if ( *p == '\n' ) { // If we get a newline and we're not paragraph, set it if ( lyric_flags & CKaraokeLyricsText::LYRICS_NEW_LINE ) lyric_flags = CKaraokeLyricsText::LYRICS_NEW_PARAGRAPH; } else { // Everything else is error CLog::Log( LOGERROR, "LRC lyric loader: lyrics file does not start from time" ); return false; } } } unsigned int lyricsNum = mtline.size(); if ( lyricsNum >= 2 ) { for ( unsigned int i = 0; i < lyricsNum - 1; i++ ) { for ( unsigned int j = i + 1; j < lyricsNum; j++ ) { if ( mtline[i].timing > mtline[j].timing ) { line = mtline[i]; mtline[i] = mtline[j]; mtline[j] = line; } } } } for ( unsigned int i=0; i < lyricsNum; i++ ) addLyrics( mtline[i].text, mtline[i].timing, mtline[i].flags ); return true; }