Esempio n. 1
0
/*!
  Read a binary CD-TEXT and fill a cdtext struct.

  @param p_cdtext the CD-TEXT object
  @param wdata the data
  @param i_data size of wdata

  @returns 0 on success, non-zero on failure
*/
int
cdtext_data_init(cdtext_t *p_cdtext, uint8_t *wdata, size_t i_data)
{
  uint8_t       *p_data;
  int           j;
  uint8_t       buffer[256];
  uint8_t       tab_buffer[256];
  int           i_buf = 0;
  int           i_block;
  int           i_seq = 0;
  int           i;
  cdtext_blocksize_t blocksize;
  char          *charset = NULL;
  uint8_t       cur_track;

  memset( buffer, 0, sizeof(buffer) );
  memset( tab_buffer, 0, sizeof(buffer) );

  p_data = wdata;
  if (i_data < CDTEXT_LEN_PACK || 0 != i_data % CDTEXT_LEN_PACK) {
    cdio_warn("CD-Text size is too small or not a multiple of pack size");
    return -1;
  }

#if 0
  for(i=0; i < i_data; i++)
    printf("%0x%c", wdata[i], ((i+1) % 18 == 0 ? '\n' : ' '));
#endif


  /* Iterate over blocks */
  i_block = -1;
  while(i_data > 0) {
    cdtext_pack_t pack;
    cdtext_read_pack(&pack, p_data);

    if (i_block != pack.block || i_seq != pack.seq) {
      cdtext_pack_t tpack;
      i_block = pack.block;
      if (i_block >= CDTEXT_NUM_BLOCKS_MAX) {
        cdio_warn("CD-TEXT: Invalid blocknumber %d.\n", i_block);
        return -1;
      }
      p_cdtext->block_i = i_block;
      i_seq = 0;
      memset( &blocksize, 0, CDTEXT_LEN_BLOCKSIZE);

      /* first read block size information for sanity checks and encoding */
      for(i=0; i <= i_data-CDTEXT_LEN_PACK; i+=CDTEXT_LEN_PACK) {

        if (p_data[i+0] == CDTEXT_PACK_BLOCKSIZE) {
          cdtext_read_pack(&tpack, p_data+i);
          switch (tpack.i_track) {
            case 0:
              blocksize.charcode      = tpack.text[0];
              blocksize.i_first_track = tpack.text[1];
              blocksize.i_last_track  = tpack.text[2];
              blocksize.copyright     = tpack.text[3];
              blocksize.i_packs[0]    = tpack.text[4];
              blocksize.i_packs[1]    = tpack.text[5];
              blocksize.i_packs[2]    = tpack.text[6];
              blocksize.i_packs[3]    = tpack.text[7];
              blocksize.i_packs[4]    = tpack.text[8];
              blocksize.i_packs[5]    = tpack.text[9];
              blocksize.i_packs[6]    = tpack.text[10];
              blocksize.i_packs[7]    = tpack.text[11];
              break;
            case 1:
              blocksize.i_packs[8]    = tpack.text[0];
              blocksize.i_packs[9]    = tpack.text[1];
              blocksize.i_packs[10]   = tpack.text[2];
              blocksize.i_packs[11]   = tpack.text[3];
              blocksize.i_packs[12]   = tpack.text[4];
              blocksize.i_packs[13]   = tpack.text[5];
              blocksize.i_packs[14]   = tpack.text[6];
              blocksize.i_packs[15]   = tpack.text[7];
              blocksize.lastseq[0]    = tpack.text[8];
              blocksize.lastseq[1]    = tpack.text[9];
              blocksize.lastseq[2]    = tpack.text[10];
              blocksize.lastseq[3]    = tpack.text[11];
              break;
            case 2:
              blocksize.lastseq[4]    = tpack.text[0];
              blocksize.lastseq[5]    = tpack.text[1];
              blocksize.lastseq[6]    = tpack.text[2];
              blocksize.lastseq[7]    = tpack.text[3];
              blocksize.langcode[0]   = tpack.text[4];
              blocksize.langcode[1]   = tpack.text[5];
              blocksize.langcode[2]   = tpack.text[6];
              blocksize.langcode[3]   = tpack.text[7];
              blocksize.langcode[4]   = tpack.text[8];
              blocksize.langcode[5]   = tpack.text[9];
              blocksize.langcode[6]   = tpack.text[10];
              blocksize.langcode[7]   = tpack.text[11];
              break;
          }
        }
      }

      if(blocksize.i_packs[15] == 3) {
        cdtext_lang_t lcode;
        /* if there were 3 BLOCKSIZE packs */
        /* set copyright */
        p_cdtext->block[i_block].copyright = (0x03 == (blocksize.copyright & 0x03));

        /* set Language */
        lcode = blocksize.langcode[i_block];
        if(lcode <= CDTEXT_LANGUAGE_WALLON ||
          (lcode >= CDTEXT_LANGUAGE_ZULU && lcode <= CDTEXT_LANGUAGE_AMHARIC) )
          p_cdtext->block[i_block].language_code = lcode;
        else
          p_cdtext->block[i_block].language_code = CDTEXT_LANGUAGE_INVALID;

        /* determine encoding */
        switch (blocksize.charcode){
          case CDTEXT_CHARCODE_ISO_8859_1:
            /* default */
            charset = (char *) "ISO-8859-1";
            break;
          case CDTEXT_CHARCODE_ASCII:
            charset = (char *) "ASCII";
            break;
          case CDTEXT_CHARCODE_SHIFT_JIS:
            charset = (char *) "SHIFT_JIS";
            break;
        }

        /* set track numbers */
        p_cdtext->block[i_block].first_track = blocksize.i_first_track;
        p_cdtext->block[i_block].last_track = blocksize.i_last_track;

      } else {
        cdio_warn("CD-TEXT: No blocksize information available for block %d.\n", i_block);
        return -1;
      }

    }

    cdtext_read_pack(&pack, p_data);

#ifndef _CDTEXT_DBCC
    if ( pack.db_chars ) {
      cdio_warn("CD-TEXT: Double-byte characters not supported");
      return -1;
    }
#endif

    cur_track = pack.i_track;

    /* read text packs first */
    j = 0;
    switch (pack.type) {
      case CDTEXT_PACK_GENRE:
        /* If pack.text starts with an unprintable character, it is likely to be the genre_code.
         * While the specification requires the first GENRE pack to start with the 2 byte genre code,
         * it is not specific about the following ones. */
        if (pack.text[0] <= 31) {
          j = 2;
          if (CDTEXT_GENRE_UNUSED == p_cdtext->block[i_block].genre_code)
            p_cdtext->block[i_block].genre_code = CDTEXT_GET_LEN16(pack.text);
        }
      case CDTEXT_PACK_TITLE:
      case CDTEXT_PACK_PERFORMER:
      case CDTEXT_PACK_SONGWRITER:
      case CDTEXT_PACK_COMPOSER:
      case CDTEXT_PACK_ARRANGER:
      case CDTEXT_PACK_MESSAGE:
      case CDTEXT_PACK_DISCID:
      case CDTEXT_PACK_UPC:
        while (j < CDTEXT_LEN_TEXTDATA) {
          /* not terminated */

          if ( i_buf+2 >= sizeof(buffer)) {
            cdio_warn("CD-TEXT: Field too long.");
            return -1;
          }

          /* if the first character is a TAB, copy the buffer */
          if ( i_buf == 0 && CDTEXT_COMPARE_CHAR(&pack.text[j], '\t', pack.db_chars)) {
            memcpy(tab_buffer, buffer, sizeof(tab_buffer));
          }

          if ( ! CDTEXT_COMPARE_CHAR(&pack.text[j], '\0', pack.db_chars)) {
            buffer[i_buf++] = pack.text[j];
            if(pack.db_chars)
              buffer[i_buf++] = pack.text[j+1];
          } else if(i_buf > 0) {
            /* if end of string */

            /* check if the buffer contains only the Tab Indicator */
            if ( CDTEXT_COMPARE_CHAR(buffer, '\t', pack.db_chars) ) {
              if ( cur_track <= blocksize.i_first_track ) {
                cdio_warn("CD-TEXT: Invalid use of Tab Indicator.");
                return -1;
              }
              memcpy(buffer, tab_buffer, sizeof(buffer));
            } else {
              buffer[i_buf++] = 0;
              if(pack.db_chars)
                buffer[i_buf++] = 0;
            }

            switch (pack.type) {
              case CDTEXT_PACK_TITLE:
                cdtext_set(p_cdtext, CDTEXT_FIELD_TITLE, buffer, cur_track, charset);
                break;
              case CDTEXT_PACK_PERFORMER:
                cdtext_set(p_cdtext, CDTEXT_FIELD_PERFORMER, buffer, cur_track, charset);
                break;
              case CDTEXT_PACK_SONGWRITER:
                cdtext_set(p_cdtext, CDTEXT_FIELD_SONGWRITER, buffer, cur_track, charset);
                break;
              case CDTEXT_PACK_COMPOSER:
                cdtext_set(p_cdtext, CDTEXT_FIELD_COMPOSER, buffer, cur_track, charset);
                break;
              case CDTEXT_PACK_ARRANGER:
                cdtext_set(p_cdtext, CDTEXT_FIELD_ARRANGER, buffer, cur_track, charset);
                break;
              case CDTEXT_PACK_MESSAGE:
                cdtext_set(p_cdtext, CDTEXT_FIELD_MESSAGE, buffer, cur_track, charset);
                break;
              case CDTEXT_PACK_DISCID:
                if (cur_track == 0)
                  cdtext_set(p_cdtext, CDTEXT_FIELD_DISCID, buffer, cur_track, NULL);
                break;
              case CDTEXT_PACK_GENRE:
                cdtext_set(p_cdtext, CDTEXT_FIELD_GENRE, buffer, cur_track, "ASCII");
                break;
              case CDTEXT_PACK_UPC:
                if (cur_track == 0)
                  cdtext_set(p_cdtext, CDTEXT_FIELD_UPC_EAN, buffer, cur_track, "ASCII");
                else
                  cdtext_set(p_cdtext, CDTEXT_FIELD_ISRC, buffer, cur_track, "ISO-8859-1");
                break;
            }
            i_buf = 0;
            ++cur_track;

          }
          if (pack.db_chars)
            j+=2;
          else
            j+=1;
        }
        break;
    }
    /* This would be the right place to parse TOC and TOC2 fields. */

    i_seq++;
    i_data-=CDTEXT_LEN_PACK;
    p_data+=CDTEXT_LEN_PACK;
  } /* end of while loop */

  p_cdtext->block_i = 0;
  return 0;
}
Esempio n. 2
0
static bool
parse_tocfile (_img_private_t *cd, const char *psz_cue_name)
{
  /* The below declarations may be common in other image-parse routines. */
  FILE        *fp;
  char         psz_line[MAXLINE];   /* text of current line read in file fp. */
  unsigned int i_line=0;            /* line number in file of psz_line. */
  int          i = -1;              /* Position in tocent. Same as
				       cd->gen.i_tracks - 1 */
  char *psz_keyword, *psz_field, *psz_cue_name_dup;
  cdio_log_level_t log_level = (cd) ? CDIO_LOG_WARN : CDIO_LOG_INFO ;
  cdtext_field_t cdtext_key;

  /* The below declaration(s) may be unique to this image-parse routine. */
  unsigned int i_cdtext_nest = 0;

  if (NULL == psz_cue_name)
    return false;

  psz_cue_name_dup = _cdio_strdup_fixpath(psz_cue_name);
  if (NULL == psz_cue_name_dup)
    return false;

  fp = CDIO_FOPEN (psz_cue_name_dup, "r");
  cdio_free(psz_cue_name_dup);
  if (fp == NULL) {
    cdio_log(log_level, "error opening %s for reading: %s",
	     psz_cue_name, strerror(errno));
    return false;
  }

  if (cd) {
    cd->gen.b_cdtext_error = false;
  }

  while (fgets(psz_line, MAXLINE, fp)) {

    i_line++;

    /* strip comment from line */
    /* todo: // in quoted strings? */
    /* //comment */
    if ((psz_field = strstr (psz_line, "//")))
      *psz_field = '\0';

    if ((psz_keyword = strtok (psz_line, " \t\n\r"))) {
      /* CATALOG "ddddddddddddd" */
      if (0 == strcmp ("CATALOG", psz_keyword)) {
	if (-1 == i) {
	  if (NULL != (psz_field = strtok (NULL, "\"\t\n\r"))) {
	    if (13 != strlen(psz_field)) {
	      cdio_log(log_level,
		       "%s line %d after word CATALOG:",
		       psz_cue_name, i_line);
	      cdio_log(log_level,
		       "Token %s has length %ld. Should be 13 digits.",
		       psz_field, (long int) strlen(psz_field));

	      goto err_exit;
	    } else {
	      /* Check that we have all digits*/
	      unsigned int j;
	      for (j=0; j<13; j++) {
		if (!isdigit((unsigned char) psz_field[j])) {
		    cdio_log(log_level,
			     "%s line %d after word CATALOG:",
			     psz_cue_name, i_line);
		    cdio_log(log_level,
			     "Character \"%c\" at postition %i of token \"%s\""
			     " is not all digits.",
			     psz_field[j], j+1, psz_field);
		    goto err_exit;
		}
	      }
	      if (NULL != cd) cd->psz_mcn = strdup (psz_field);
	    }
	  } else {
	    cdio_log(log_level,
		     "%s line %d after word CATALOG:",
		     psz_cue_name, i_line);
	    cdio_log(log_level, "Expecting 13 digits; nothing seen.");
	    goto err_exit;
	  }
	} else {
	  goto err_exit;
	}

	/* CD_DA | CD_ROM | CD_ROM_XA */
      } else if (0 == strcmp ("CD_DA", psz_keyword)) {
	if (-1 == i) {
	  if (NULL != cd)
	    cd->disc_mode = CDIO_DISC_MODE_CD_DA;
	} else {
	  goto not_in_global_section;
	}
      } else if (0 == strcmp ("CD_ROM", psz_keyword)) {
	if (-1 == i) {
	  if (NULL != cd)
	    cd->disc_mode = CDIO_DISC_MODE_CD_DATA;
	} else {
	  goto not_in_global_section;
	}

      } else if (0 == strcmp ("CD_ROM_XA", psz_keyword)) {
	if (-1 == i) {
	  if (NULL != cd)
	    cd->disc_mode = CDIO_DISC_MODE_CD_XA;
	} else {
	  goto not_in_global_section;
	}

	/* TRACK <track-mode> [<sub-channel-mode>] */
      } else if (0 == strcmp ("TRACK", psz_keyword)) {
	i++;
	if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	  if (0 == strcmp ("AUDIO", psz_field)) {
	    if (NULL != cd) {
	      cd->tocent[i].track_format = TRACK_FORMAT_AUDIO;
	      cd->tocent[i].blocksize    = CDIO_CD_FRAMESIZE_RAW;
	      cd->tocent[i].datasize     = CDIO_CD_FRAMESIZE_RAW;
	      cd->tocent[i].datastart    = 0;
	      cd->tocent[i].endsize      = 0;
	      switch(cd->disc_mode) {
	      case CDIO_DISC_MODE_NO_INFO:
		cd->disc_mode = CDIO_DISC_MODE_CD_DA;
		break;
	      case CDIO_DISC_MODE_CD_DA:
	      case CDIO_DISC_MODE_CD_MIXED:
	      case CDIO_DISC_MODE_ERROR:
		/* Disc type stays the same. */
		break;
	      case CDIO_DISC_MODE_CD_DATA:
	      case CDIO_DISC_MODE_CD_XA:
		cd->disc_mode = CDIO_DISC_MODE_CD_MIXED;
		break;
	      default:
		cd->disc_mode = CDIO_DISC_MODE_ERROR;
	      }

	    }
	  } else if (0 == strcmp ("MODE1", psz_field)) {
	    if (NULL != cd) {
	      cd->tocent[i].track_format = TRACK_FORMAT_DATA;
	      cd->tocent[i].blocksize    = CDIO_CD_FRAMESIZE_RAW;
	      cd->tocent[i].datastart    = CDIO_CD_SYNC_SIZE
		+ CDIO_CD_HEADER_SIZE;
	      cd->tocent[i].datasize     = CDIO_CD_FRAMESIZE;
	      cd->tocent[i].endsize      = CDIO_CD_EDC_SIZE
		+ CDIO_CD_M1F1_ZERO_SIZE + CDIO_CD_ECC_SIZE;
	      switch(cd->disc_mode) {
	      case CDIO_DISC_MODE_NO_INFO:
		cd->disc_mode = CDIO_DISC_MODE_CD_DATA;
		break;
	      case CDIO_DISC_MODE_CD_DATA:
	      case CDIO_DISC_MODE_CD_MIXED:
	      case CDIO_DISC_MODE_ERROR:
		/* Disc type stays the same. */
		break;
	      case CDIO_DISC_MODE_CD_DA:
	      case CDIO_DISC_MODE_CD_XA:
		cd->disc_mode = CDIO_DISC_MODE_CD_MIXED;
		break;
	      default:
		cd->disc_mode = CDIO_DISC_MODE_ERROR;
	      }
	    }
	  } else if (0 == strcmp ("MODE1_RAW", psz_field)) {
	    if (NULL != cd) {
	      cd->tocent[i].track_format = TRACK_FORMAT_DATA;
	      cd->tocent[i].blocksize = CDIO_CD_FRAMESIZE_RAW;
	      cd->tocent[i].datastart = CDIO_CD_SYNC_SIZE
		+ CDIO_CD_HEADER_SIZE;
	      cd->tocent[i].datasize  = CDIO_CD_FRAMESIZE;
	      cd->tocent[i].endsize   = CDIO_CD_EDC_SIZE
		+ CDIO_CD_M1F1_ZERO_SIZE + CDIO_CD_ECC_SIZE;
	      switch(cd->disc_mode) {
	      case CDIO_DISC_MODE_NO_INFO:
		cd->disc_mode = CDIO_DISC_MODE_CD_DATA;
		break;
	      case CDIO_DISC_MODE_CD_DATA:
	      case CDIO_DISC_MODE_CD_MIXED:
	      case CDIO_DISC_MODE_ERROR:
		/* Disc type stays the same. */
		break;
	      case CDIO_DISC_MODE_CD_DA:
	      case CDIO_DISC_MODE_CD_XA:
		cd->disc_mode = CDIO_DISC_MODE_CD_MIXED;
		break;
	      default:
		cd->disc_mode = CDIO_DISC_MODE_ERROR;
	      }
	    }
	  } else if (0 == strcmp ("MODE2", psz_field)) {
	    if (NULL != cd) {
	      cd->tocent[i].track_format = TRACK_FORMAT_XA;
	      cd->tocent[i].datastart = CDIO_CD_SYNC_SIZE
		+ CDIO_CD_HEADER_SIZE;
	      cd->tocent[i].datasize = M2RAW_SECTOR_SIZE;
	      cd->tocent[i].endsize   = 0;
	      switch(cd->disc_mode) {
	      case CDIO_DISC_MODE_NO_INFO:
		cd->disc_mode = CDIO_DISC_MODE_CD_XA;
		break;
	      case CDIO_DISC_MODE_CD_XA:
	      case CDIO_DISC_MODE_CD_MIXED:
	      case CDIO_DISC_MODE_ERROR:
		/* Disc type stays the same. */
		break;
	      case CDIO_DISC_MODE_CD_DA:
	      case CDIO_DISC_MODE_CD_DATA:
		cd->disc_mode = CDIO_DISC_MODE_CD_MIXED;
		break;
	      default:
		cd->disc_mode = CDIO_DISC_MODE_ERROR;
	      }
	    }
	  } else if (0 == strcmp ("MODE2_FORM1", psz_field)) {
	    if (NULL != cd) {
	      cd->tocent[i].track_format = TRACK_FORMAT_XA;
	      cd->tocent[i].datastart = CDIO_CD_SYNC_SIZE
		+ CDIO_CD_HEADER_SIZE;
	      cd->tocent[i].datasize  = CDIO_CD_FRAMESIZE_RAW;
	      cd->tocent[i].endsize   = 0;
	      switch(cd->disc_mode) {
	      case CDIO_DISC_MODE_NO_INFO:
		cd->disc_mode = CDIO_DISC_MODE_CD_XA;
		break;
	      case CDIO_DISC_MODE_CD_XA:
	      case CDIO_DISC_MODE_CD_MIXED:
	      case CDIO_DISC_MODE_ERROR:
		/* Disc type stays the same. */
		break;
	      case CDIO_DISC_MODE_CD_DA:
	      case CDIO_DISC_MODE_CD_DATA:
		cd->disc_mode = CDIO_DISC_MODE_CD_MIXED;
		break;
	      default:
		cd->disc_mode = CDIO_DISC_MODE_ERROR;
	      }
	    }
	  } else if (0 == strcmp ("MODE2_FORM2", psz_field)) {
	    if (NULL != cd) {
	      cd->tocent[i].track_format = TRACK_FORMAT_XA;
	      cd->tocent[i].datastart    = CDIO_CD_SYNC_SIZE
		+ CDIO_CD_HEADER_SIZE + CDIO_CD_SUBHEADER_SIZE;
	      cd->tocent[i].datasize     = CDIO_CD_FRAMESIZE;
	      cd->tocent[i].endsize      = CDIO_CD_SYNC_SIZE
		+ CDIO_CD_ECC_SIZE;
	      switch(cd->disc_mode) {
	      case CDIO_DISC_MODE_NO_INFO:
		cd->disc_mode = CDIO_DISC_MODE_CD_XA;
		break;
	      case CDIO_DISC_MODE_CD_XA:
	      case CDIO_DISC_MODE_CD_MIXED:
	      case CDIO_DISC_MODE_ERROR:
		/* Disc type stays the same. */
		break;
	      case CDIO_DISC_MODE_CD_DA:
	      case CDIO_DISC_MODE_CD_DATA:
		cd->disc_mode = CDIO_DISC_MODE_CD_MIXED;
		break;
	      default:
		cd->disc_mode = CDIO_DISC_MODE_ERROR;
	      }
	    }
	  } else if (0 == strcmp ("MODE2_FORM_MIX", psz_field)) {
	    if (NULL != cd) {
	      cd->tocent[i].track_format = TRACK_FORMAT_XA;
	      cd->tocent[i].datasize     = M2RAW_SECTOR_SIZE;
	      cd->tocent[i].blocksize    = CDIO_CD_FRAMESIZE_RAW;
	      cd->tocent[i].datastart    = CDIO_CD_SYNC_SIZE +
		CDIO_CD_HEADER_SIZE + CDIO_CD_SUBHEADER_SIZE;
	      cd->tocent[i].track_green  = true;
	      cd->tocent[i].endsize      = 0;
	      switch(cd->disc_mode) {
	      case CDIO_DISC_MODE_NO_INFO:
		cd->disc_mode = CDIO_DISC_MODE_CD_XA;
		break;
	      case CDIO_DISC_MODE_CD_XA:
	      case CDIO_DISC_MODE_CD_MIXED:
	      case CDIO_DISC_MODE_ERROR:
		/* Disc type stays the same. */
		break;
	      case CDIO_DISC_MODE_CD_DA:
	      case CDIO_DISC_MODE_CD_DATA:
		cd->disc_mode = CDIO_DISC_MODE_CD_MIXED;
		break;
	      default:
		cd->disc_mode = CDIO_DISC_MODE_ERROR;
	      }
	    }
	  } else if (0 == strcmp ("MODE2_RAW", psz_field)) {
	    if (NULL != cd) {
	      cd->tocent[i].track_format = TRACK_FORMAT_XA;
	      cd->tocent[i].blocksize    = CDIO_CD_FRAMESIZE_RAW;
	      cd->tocent[i].datastart    = CDIO_CD_SYNC_SIZE +
		CDIO_CD_HEADER_SIZE + CDIO_CD_SUBHEADER_SIZE;
	      cd->tocent[i].datasize     = CDIO_CD_FRAMESIZE;
	      cd->tocent[i].track_green  = true;
	      cd->tocent[i].endsize      = 0;
	      switch(cd->disc_mode) {
	      case CDIO_DISC_MODE_NO_INFO:
		cd->disc_mode = CDIO_DISC_MODE_CD_XA;
		break;
	      case CDIO_DISC_MODE_CD_XA:
	      case CDIO_DISC_MODE_CD_MIXED:
	      case CDIO_DISC_MODE_ERROR:
		/* Disc type stays the same. */
		break;
	      case CDIO_DISC_MODE_CD_DA:
	      case CDIO_DISC_MODE_CD_DATA:
		cd->disc_mode = CDIO_DISC_MODE_CD_MIXED;
		break;
	      default:
		cd->disc_mode = CDIO_DISC_MODE_ERROR;
	      }
	    }
	  } else {
	    cdio_log(log_level, "%s line %d after TRACK:",
		     psz_cue_name, i_line);
	    cdio_log(log_level, "'%s' not a valid mode.", psz_field);
	    goto err_exit;
	  }
	}
	if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	  /* \todo: set sub-channel-mode */
#ifdef TODO
	  if (0 == strcmp ("RW", psz_field))
	    ;
	  else if (0 == strcmp ("RW_RAW", psz_field))
	    ;
#endif
	}
	if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	  goto format_error;
	}

	/* track flags */
	/* [NO] COPY | [NO] PRE_EMPHASIS */
      } else if (0 == strcmp ("NO", psz_keyword)) {
	if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	  if (0 == strcmp ("COPY", psz_field)) {
	    if (NULL != cd)
	      cd->tocent[i].flags &= ~CDIO_TRACK_FLAG_COPY_PERMITTED;

	  } else if (0 == strcmp ("PRE_EMPHASIS", psz_field))
	    if (NULL != cd) {
	      cd->tocent[i].flags &= ~CDIO_TRACK_FLAG_PRE_EMPHASIS;
	    }
	} else {
	  goto format_error;
	}
	if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	  goto format_error;
	}
      } else if (0 == strcmp ("COPY", psz_keyword)) {
	if (NULL != cd && i >= 0)
	  cd->tocent[i].flags |= CDIO_TRACK_FLAG_COPY_PERMITTED;
      } else if (0 == strcmp ("PRE_EMPHASIS", psz_keyword)) {
	if (NULL != cd && i >= 0)
	  cd->tocent[i].flags |= CDIO_TRACK_FLAG_PRE_EMPHASIS;
	/* TWO_CHANNEL_AUDIO */
      } else if (0 == strcmp ("TWO_CHANNEL_AUDIO", psz_keyword)) {
	if (NULL != cd && i >= 0)
	  cd->tocent[i].flags &= ~CDIO_TRACK_FLAG_FOUR_CHANNEL_AUDIO;
	/* FOUR_CHANNEL_AUDIO */
      } else if (0 == strcmp ("FOUR_CHANNEL_AUDIO", psz_keyword)) {
	if (NULL != cd && i >= 0)
	  cd->tocent[i].flags |= CDIO_TRACK_FLAG_FOUR_CHANNEL_AUDIO;

	/* ISRC "CCOOOYYSSSSS" */
      } else if (0 == strcmp ("ISRC", psz_keyword)) {
	if (NULL != (psz_field = strtok (NULL, "\"\t\n\r"))) {
	  if (NULL != cd)
	    cd->tocent[i].isrc = strdup(psz_field);
	} else {
	  goto format_error;
	}

	/* SILENCE <length> */
      } else if (0 == strcmp ("SILENCE", psz_keyword)) {
	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	      if (NULL != cd)
		  cd->tocent[i].silence = cdio_mmssff_to_lba (psz_field);
	  } else {
	      goto format_error;
	  }
	  cdio_log(log_level, "%s line %d: SILENCE not fully implimented",
		   psz_cue_name, i_line);

	/* ZERO <length> */
      } else if (0 == strcmp ("ZERO", psz_keyword)) {
	UNIMPLIMENTED_MSG;

	/* [FILE|AUDIOFILE] "<filename>" <start-msf> [<length-msf>] */
      } else if (0 == strcmp ("FILE", psz_keyword)
		 || 0 == strcmp ("AUDIOFILE", psz_keyword)) {
	if (0 <= i) {
	  if (NULL != (psz_field = strtok (NULL, "\"\t\n\r"))) {
	    /* Handle "<filename>" */
	    if (cd) {
	      char *psz_dirname = cdio_dirname(psz_cue_name);
	      char *psz_filename = cdio_abspath(psz_dirname, psz_field);
	      cd->tocent[i].filename = strdup (psz_filename);
	      free(psz_filename);
	      free(psz_dirname);
	      /* To do: do something about reusing existing files. */
	      if (!(cd->tocent[i].data_source = cdio_stdio_new (psz_field))) {
		cdio_log (log_level,
			  "%s line %d: can't open file `%s' for reading",
			   psz_cue_name, i_line, psz_field);
		goto err_exit;
	      }
	    } else {
	      CdioDataSource_t *s = cdio_stdio_new (psz_field);
	      if (!s) {
		cdio_log (log_level,
			  "%s line %d: can't open file `%s' for reading",
			  psz_cue_name, i_line, psz_field);
		goto err_exit;
	      }
	      cdio_stdio_destroy (s);
	    }
	  }

	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	    /* Handle <start-msf> */
	    lba_t i_start_lba =
	      cdio_lsn_to_lba(cdio_mmssff_to_lba (psz_field));
	    if (CDIO_INVALID_LBA == i_start_lba) {
	      cdio_log(log_level, "%s line %d: invalid MSF string %s",
		       psz_cue_name, i_line, psz_field);
	      goto err_exit;
	    }

	    if (NULL != cd) {
	      cd->tocent[i].start_lba = i_start_lba;
	      cdio_lba_to_msf(i_start_lba, &(cd->tocent[i].start_msf));
	    }
	  }
	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	    /* Handle <length-msf> */
	    lba_t lba = cdio_mmssff_to_lba (psz_field);
	    if (CDIO_INVALID_LBA == lba) {
	      cdio_log(log_level, "%s line %d: invalid MSF string %s",
		       psz_cue_name, i_line, psz_field);
	      goto err_exit;
	    }
	    if (cd) {
	      off_t i_size = cdio_stream_stat(cd->tocent[i].data_source);
	      if (lba) {
		if ( (lba * cd->tocent[i].datasize) > i_size) {
		  cdio_log(log_level,
			   "%s line %d: MSF length %s exceeds end of file",
			   psz_cue_name, i_line, psz_field);
		  goto err_exit;
		}
	      } else {
		lba = (lba_t) (i_size / cd->tocent[i].blocksize);
	      }
	      cd->tocent[i].sec_count = lba;
	    }
	  }
	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	    goto format_error;
	  }
	} else {
	  goto not_in_global_section;
	}

	/* DATAFILE "<filename>" #byte-offset <start-msf> */
      } else if (0 == strcmp ("DATAFILE", psz_keyword)) {
	if (0 <= i) {
	  if (NULL != (psz_field = strtok (NULL, "\"\t\n\r"))) {
	    /* Handle <filename> */
	    char *psz_dirname = cdio_dirname(psz_cue_name);
	    char *psz_filename = cdio_abspath(psz_dirname, psz_field);
	    if (cd) {
	      cd->tocent[i].filename = strdup(psz_filename);
	      /* To do: do something about reusing existing files. */
	      if (!(cd->tocent[i].data_source = cdio_stdio_new (psz_field))) {
		cdio_log (log_level,
			  "%s line %d: can't open file `%s' for reading",
			  psz_cue_name, i_line, psz_field);
		free(psz_filename);
		free(psz_dirname);
		goto err_exit;
	      }
	    } else {
	      CdioDataSource_t *s = cdio_stdio_new (psz_filename);
	      if (!s) {
		cdio_log (log_level,
			  "%s line %d: can't open file `%s' for reading",
			  psz_cue_name, i_line, psz_field);
		free(psz_filename);
		free(psz_dirname);
		goto err_exit;
	      }
	      cdio_stdio_destroy (s);
	    }
	    free(psz_filename);
	    free(psz_dirname);
	  }

	  psz_field = strtok (NULL, " \t\n\r");
	  if (psz_field) {
	    /* Handle optional #byte-offset */
	    if ( psz_field[0] == '#') {
	      long int offset;
	      psz_field++;
	      errno = 0;
	      offset = strtol(psz_field, (char **)NULL, 10);
	      if ( (LONG_MIN == offset || LONG_MAX == offset)
		   && 0 != errno ) {
		cdio_log (log_level,
			  "%s line %d: can't convert `%s' to byte offset",
			  psz_cue_name, i_line, psz_field);
		goto err_exit;
	      } else {
		if (NULL != cd) {
		  cd->tocent[i].offset = offset;
		}
	      }
	      psz_field = strtok (NULL, " \t\n\r");
	    }
	  }
	  if (psz_field) {
	    /* Handle start-msf */
	    lba_t lba = cdio_mmssff_to_lba (psz_field);
	    if (CDIO_INVALID_LBA == lba) {
	      cdio_log(log_level, "%s line %d: invalid MSF string %s",
		       psz_cue_name, i_line, psz_field);
	      goto err_exit;
	    }
	    if (cd) {
	      cd->tocent[i].start_lba = lba;
	      cdio_lba_to_msf(cd->tocent[i].start_lba,
			      &(cd->tocent[i].start_msf));
	    }
	  } else {
	    /* No start-msf. */
	    if (cd) {
	      if (i) {
		uint16_t i_blocksize = cd->tocent[i-1].blocksize;
		off_t i_size      =
		  cdio_stream_stat(cd->tocent[i-1].data_source);

		  check_track_is_blocksize_multiple(cd->tocent[i-1].filename,
						    i-1, i_size, i_blocksize);
		/* Append size of previous datafile. */
		cd->tocent[i].start_lba = (lba_t) (cd->tocent[i-1].start_lba +
		  (i_size / i_blocksize));
	      }
	      cd->tocent[i].offset = 0;
	      cd->tocent[i].start_lba += CDIO_PREGAP_SECTORS;
	      cdio_lba_to_msf(cd->tocent[i].start_lba,
			      &(cd->tocent[i].start_msf));
	    }
	  }

	} else {
	  goto not_in_global_section;
	}

	/* FIFO "<fifo path>" [<length>] */
      } else if (0 == strcmp ("FIFO", psz_keyword)) {
	goto unimplimented_error;

	/* START MM:SS:FF */
      } else if (0 == strcmp ("START", psz_keyword)) {
	if (0 <= i) {
	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	    /* todo: line is too long! */
	    if (NULL != cd) {
	      cd->tocent[i].pregap = cd->tocent[i].start_lba;
	      cd->tocent[i].start_lba += cdio_mmssff_to_lba (psz_field);
	      cdio_lba_to_msf(cd->tocent[i].start_lba,
			      &(cd->tocent[i].start_msf));
	    }
	  }

	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	    goto format_error;
	  }
	} else {
	  goto not_in_global_section;
	}

	/* PREGAP MM:SS:FF */
      } else if (0 == strcmp ("PREGAP", psz_keyword)) {
	if (0 <= i) {
	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	    if (NULL != cd)
	      cd->tocent[i].pregap = cdio_mmssff_to_lba (psz_field);
	  } else {
	    goto format_error;
	  }
	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	    goto format_error;
	  }
	} else {
	  goto not_in_global_section;
	}

	  /* INDEX MM:SS:FF */
      } else if (0 == strcmp ("INDEX", psz_keyword)) {
	if (0 <= i) {
	  if (NULL != (psz_field = strtok (NULL, " \t\n\r"))) {
	    if (NULL != cd) {
#if 0
	      if (1 == cd->tocent[i].nindex) {
		cd->tocent[i].indexes[1] = cd->tocent[i].indexes[0];
		cd->tocent[i].nindex++;
	      }
	      cd->tocent[i].indexes[cd->tocent[i].nindex++] =
		cdio_mmssff_to_lba (psz_field) + cd->tocent[i].indexes[0];
#else
	      ;

#endif
	    }
	  } else {
	    goto format_error;
	  }
	  if (NULL != strtok (NULL, " \t\n\r")) {
	    goto format_error;
	  }
	}  else {
	  goto not_in_global_section;
	}

  /* CD_TEXT { ... } */
  /* todo: opening { must be on same line as CD_TEXT */
      } else if (0 == strcmp ("CD_TEXT", psz_keyword)) {
        if (NULL == (psz_field = strtok (NULL, " \t\n\r"))) {
          goto format_error;
        }
        if ( 0 == strcmp( "{", psz_field ) ) {
          i_cdtext_nest++;
        } else {
          cdio_log (log_level,
              "%s line %d: expecting '{'", psz_cue_name, i_line);
          goto err_exit;
        }

      // TODO: implement language mapping
      } else if (0 == strcmp ("LANGUAGE_MAP", psz_keyword)) {
        /* LANGUAGE d { ... } */
      } else if (0 == strcmp ("LANGUAGE", psz_keyword)) {
        /* Language number */
        if (NULL == (psz_field = strtok (NULL, " \t\n\r"))) {
          goto format_error;
        }
        if ( 0 == strcmp( "{", psz_field ) ) {
          i_cdtext_nest++;
        }
      } else if (0 == strcmp ("{", psz_keyword)) {
        i_cdtext_nest++;
      } else if (0 == strcmp ("}", psz_keyword)) {
        if (i_cdtext_nest > 0) i_cdtext_nest--;
      } else if ( CDTEXT_FIELD_INVALID !=
          (cdtext_key = cdtext_is_field (psz_keyword)) ) {
        if (NULL != cd) {
          if (NULL == cd->gen.cdtext) {
            cd->gen.cdtext = cdtext_init ();
            /* until language mapping is implemented ...*/
            cd->gen.cdtext->block[cd->gen.cdtext->block_i].language_code = CDTEXT_LANGUAGE_ENGLISH;
          }
          cdtext_set (cd->gen.cdtext, cdtext_key, (uint8_t*) strtok (NULL, "\"\t\n\r"),
              (-1 == i ? 0 : cd->gen.i_first_track + i),
              "ISO-8859-1");
        }

	/* unrecognized line */
      } else {
	cdio_log(log_level, "%s line %d: warning: unrecognized word: %s",
		 psz_cue_name, i_line, psz_keyword);
	goto err_exit;
      }
    }
  }

  if (NULL != cd) {
    cd->gen.i_tracks = i+1;
    cd->gen.toc_init = true;
  }

  fclose (fp);
  return true;

 unimplimented_error:
  UNIMPLIMENTED_MSG;
  goto err_exit;

 format_error:
  cdio_log(log_level, "%s line %d after word %s",
	   psz_cue_name, i_line, psz_keyword);
  goto err_exit;

 not_in_global_section:
  cdio_log(log_level, "%s line %d: word %s only allowed in global section",
	   psz_cue_name, i_line, psz_keyword);

 err_exit:
  fclose (fp);
  return false;
}