Example #1
0
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;
}
Example #2
0
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, &ltenths ) == 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;
}