CUEParser.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /*
  2. * Simple CUE sheet parser suitable for embedded systems.
  3. *
  4. * Copyright (c) 2023 Rabbit Hole Computing
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. // Refer to e.g. https://www.gnu.org/software/ccd2cue/manual/html_node/CUE-sheet-format.html#CUE-sheet-format
  20. //
  21. // Example of a CUE file:
  22. // FILE "foo bar.bin" BINARY
  23. // TRACK 01 MODE1/2048
  24. // INDEX 01 00:00:00
  25. // TRACK 02 AUDIO
  26. // PREGAP 00:02:00
  27. // INDEX 01 02:47:20
  28. // TRACK 03 AUDIO
  29. // INDEX 00 07:55:58
  30. // INDEX 01 07:55:65
  31. #include "CUEParser.h"
  32. #include <ctype.h>
  33. #include <stdlib.h>
  34. #include <string.h>
  35. #include <strings.h>
  36. CUEParser::CUEParser(const char *cue_sheet):
  37. m_cue_sheet(cue_sheet)
  38. {
  39. restart();
  40. }
  41. void CUEParser::restart()
  42. {
  43. m_parse_pos = m_cue_sheet;
  44. memset(&m_track_info, 0, sizeof(m_track_info));
  45. }
  46. const CUETrackInfo *CUEParser::next_track()
  47. {
  48. bool got_track = false;
  49. bool got_data = false;
  50. while(start_line() && !(got_track && got_data))
  51. {
  52. if (strncasecmp(m_parse_pos, "FILE ", 5) == 0)
  53. {
  54. const char *p = read_quoted(m_parse_pos + 5, m_track_info.filename, sizeof(m_track_info.filename));
  55. m_track_info.file_mode = parse_file_mode(skip_space(p));
  56. }
  57. else if (strncasecmp(m_parse_pos, "TRACK ", 6) == 0)
  58. {
  59. const char *track_num = skip_space(m_parse_pos + 6);
  60. char *endptr;
  61. m_track_info.track_number = strtoul(track_num, &endptr, 10);
  62. m_track_info.track_mode = parse_track_mode(skip_space(endptr));
  63. m_track_info.pregap_start = 0;
  64. m_track_info.unstored_pregap_length = 0;
  65. m_track_info.data_start = 0;
  66. got_track = true;
  67. got_data = false;
  68. }
  69. else if (strncasecmp(m_parse_pos, "PREGAP ", 7) == 0)
  70. {
  71. const char *time_str = skip_space(m_parse_pos + 7);
  72. m_track_info.unstored_pregap_length = parse_time(time_str);
  73. }
  74. else if (strncasecmp(m_parse_pos, "INDEX ", 6) == 0)
  75. {
  76. const char *index_str = skip_space(m_parse_pos + 6);
  77. char *endptr;
  78. int index = strtoul(index_str, &endptr, 10);
  79. const char *time_str = skip_space(endptr);
  80. uint32_t time = parse_time(time_str);
  81. if (index == 0)
  82. {
  83. m_track_info.pregap_start = time;
  84. }
  85. else if (index == 1)
  86. {
  87. m_track_info.data_start = time;
  88. got_data = true;
  89. }
  90. }
  91. next_line();
  92. }
  93. if (got_track && got_data)
  94. return &m_track_info;
  95. else
  96. return nullptr;
  97. }
  98. bool CUEParser::start_line()
  99. {
  100. // Skip initial whitespace
  101. while (isspace(*m_parse_pos))
  102. {
  103. m_parse_pos++;
  104. }
  105. return *m_parse_pos != '\0';
  106. }
  107. void CUEParser::next_line()
  108. {
  109. // Find end of current line
  110. const char *p = m_parse_pos;
  111. while (*p != '\n' && *p != '\0')
  112. {
  113. p++;
  114. }
  115. // Skip any linefeeds
  116. while (*p == '\n' || *p == '\r')
  117. {
  118. p++;
  119. }
  120. m_parse_pos = p;
  121. }
  122. const char *CUEParser::skip_space(const char *p) const
  123. {
  124. while (isspace(*p)) p++;
  125. return p;
  126. }
  127. const char *CUEParser::read_quoted(const char *src, char *dest, int dest_size)
  128. {
  129. // Search for starting quote
  130. while (*src != '"')
  131. {
  132. if (*src == '\0' || *src == '\n')
  133. {
  134. // Unexpected end of line / file
  135. dest[0] = '\0';
  136. return src;
  137. }
  138. src++;
  139. }
  140. src++;
  141. // Copy text until ending quote
  142. int len = 0;
  143. while (*src != '"' && *src != '\0' && *src != '\n')
  144. {
  145. if (len < dest_size - 1)
  146. {
  147. dest[len++] = *src;
  148. }
  149. src++;
  150. }
  151. dest[len] = '\0';
  152. if (*src == '"') src++;
  153. return src;
  154. }
  155. uint32_t CUEParser::parse_time(const char *src)
  156. {
  157. char *endptr;
  158. uint32_t minutes = strtoul(src, &endptr, 10);
  159. if (*endptr == ':') endptr++;
  160. uint32_t seconds = strtoul(endptr, &endptr, 10);
  161. if (*endptr == ':') endptr++;
  162. uint32_t frames = strtoul(endptr, &endptr, 10);
  163. return frames + 75 * (seconds + 60 * minutes);
  164. }
  165. CUEFileMode CUEParser::parse_file_mode(const char *src)
  166. {
  167. if (strncasecmp(src, "BIN", 3) == 0)
  168. return CUEFile_BINARY;
  169. else if (strncasecmp(src, "MOTOROLA", 8) == 0)
  170. return CUEFile_MOTOROLA;
  171. else if (strncasecmp(src, "MP3", 3) == 0)
  172. return CUEFile_MP3;
  173. else if (strncasecmp(src, "WAV", 3) == 0)
  174. return CUEFile_WAVE;
  175. else if (strncasecmp(src, "AIFF", 4) == 0)
  176. return CUEFile_AIFF;
  177. else
  178. return CUEFile_BINARY; // Default to binary mode
  179. }
  180. CUETrackMode CUEParser::parse_track_mode(const char *src)
  181. {
  182. if (strncasecmp(src, "AUDIO", 5) == 0)
  183. return CUETrack_AUDIO;
  184. else if (strncasecmp(src, "CDG", 3) == 0)
  185. return CUETrack_CDG;
  186. else if (strncasecmp(src, "MODE1/2048", 10) == 0)
  187. return CUETrack_MODE1_2048;
  188. else if (strncasecmp(src, "MODE1/2352", 10) == 0)
  189. return CUETrack_MODE1_2352;
  190. else if (strncasecmp(src, "MODE2/2048", 10) == 0)
  191. return CUETrack_MODE2_2048;
  192. else if (strncasecmp(src, "MODE2/2324", 10) == 0)
  193. return CUETrack_MODE2_2324;
  194. else if (strncasecmp(src, "MODE2/2336", 10) == 0)
  195. return CUETrack_MODE2_2336;
  196. else if (strncasecmp(src, "MODE2/2352", 10) == 0)
  197. return CUETrack_MODE2_2352;
  198. else if (strncasecmp(src, "CDI/2336", 8) == 0)
  199. return CUETrack_CDI_2336;
  200. else if (strncasecmp(src, "CDI/2352", 8) == 0)
  201. return CUETrack_CDI_2352;
  202. else
  203. return CUETrack_MODE1_2048; // Default to data track
  204. }