CUEParser.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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(): CUEParser("")
  37. {
  38. }
  39. CUEParser::CUEParser(const char *cue_sheet):
  40. m_cue_sheet(cue_sheet)
  41. {
  42. restart();
  43. }
  44. void CUEParser::restart()
  45. {
  46. m_parse_pos = m_cue_sheet;
  47. memset(&m_track_info, 0, sizeof(m_track_info));
  48. }
  49. const CUETrackInfo *CUEParser::next_track()
  50. {
  51. // Previous track info is needed to track file offset
  52. uint32_t prev_track_start = m_track_info.track_start;
  53. uint32_t prev_sector_length = get_sector_length(m_track_info.file_mode, m_track_info.track_mode); // Defaults to 2352 before first track
  54. bool got_track = false;
  55. bool got_data = false;
  56. bool got_pause = false; // true if a period of silence (INDEX 00) was encountered for a track
  57. while(!(got_track && got_data) && start_line())
  58. {
  59. if (strncasecmp(m_parse_pos, "FILE ", 5) == 0)
  60. {
  61. const char *p = read_quoted(m_parse_pos + 5, m_track_info.filename, sizeof(m_track_info.filename));
  62. m_track_info.file_mode = parse_file_mode(skip_space(p));
  63. m_track_info.file_offset = 0;
  64. m_track_info.track_mode = CUETrack_AUDIO;
  65. prev_track_start = 0;
  66. prev_sector_length = get_sector_length(m_track_info.file_mode, m_track_info.track_mode);
  67. }
  68. else if (strncasecmp(m_parse_pos, "TRACK ", 6) == 0)
  69. {
  70. const char *track_num = skip_space(m_parse_pos + 6);
  71. char *endptr;
  72. m_track_info.track_number = strtoul(track_num, &endptr, 10);
  73. m_track_info.track_mode = parse_track_mode(skip_space(endptr));
  74. m_track_info.sector_length = get_sector_length(m_track_info.file_mode, m_track_info.track_mode);
  75. m_track_info.unstored_pregap_length = 0;
  76. m_track_info.data_start = 0;
  77. m_track_info.track_start = 0;
  78. got_track = true;
  79. got_data = false;
  80. got_pause = false;
  81. }
  82. else if (strncasecmp(m_parse_pos, "PREGAP ", 7) == 0)
  83. {
  84. // Unstored pregap, which offsets the data start on CD but does not
  85. // affect the offset in data file.
  86. const char *time_str = skip_space(m_parse_pos + 7);
  87. m_track_info.unstored_pregap_length = parse_time(time_str);
  88. }
  89. else if (strncasecmp(m_parse_pos, "INDEX ", 6) == 0)
  90. {
  91. const char *index_str = skip_space(m_parse_pos + 6);
  92. char *endptr;
  93. int index = strtoul(index_str, &endptr, 10);
  94. const char *time_str = skip_space(endptr);
  95. uint32_t time = parse_time(time_str);
  96. if (index == 0)
  97. {
  98. // Stored pregap that is present both on CD and in data file
  99. m_track_info.track_start = time;
  100. got_pause = true;
  101. }
  102. else if (index == 1)
  103. {
  104. // Data content of the track
  105. m_track_info.data_start = time;
  106. got_data = true;
  107. }
  108. }
  109. next_line();
  110. }
  111. if (got_data && !got_pause)
  112. {
  113. m_track_info.track_start = m_track_info.data_start;
  114. m_track_info.data_start += m_track_info.unstored_pregap_length;
  115. }
  116. if (got_track && got_data)
  117. {
  118. m_track_info.file_offset += (uint64_t)(m_track_info.track_start - prev_track_start) * prev_sector_length;
  119. return &m_track_info;
  120. }
  121. else
  122. {
  123. return nullptr;
  124. }
  125. }
  126. bool CUEParser::start_line()
  127. {
  128. // Skip initial whitespace
  129. while (isspace(*m_parse_pos))
  130. {
  131. m_parse_pos++;
  132. }
  133. return *m_parse_pos != '\0';
  134. }
  135. void CUEParser::next_line()
  136. {
  137. // Find end of current line
  138. const char *p = m_parse_pos;
  139. while (*p != '\n' && *p != '\0')
  140. {
  141. p++;
  142. }
  143. // Skip any linefeeds
  144. while (*p == '\n' || *p == '\r')
  145. {
  146. p++;
  147. }
  148. m_parse_pos = p;
  149. }
  150. const char *CUEParser::skip_space(const char *p) const
  151. {
  152. while (isspace(*p)) p++;
  153. return p;
  154. }
  155. const char *CUEParser::read_quoted(const char *src, char *dest, int dest_size)
  156. {
  157. // Search for starting quote
  158. while (*src != '"')
  159. {
  160. if (*src == '\0' || *src == '\n')
  161. {
  162. // Unexpected end of line / file
  163. dest[0] = '\0';
  164. return src;
  165. }
  166. src++;
  167. }
  168. src++;
  169. // Copy text until ending quote
  170. int len = 0;
  171. while (*src != '"' && *src != '\0' && *src != '\n')
  172. {
  173. if (len < dest_size - 1)
  174. {
  175. dest[len++] = *src;
  176. }
  177. src++;
  178. }
  179. dest[len] = '\0';
  180. if (*src == '"') src++;
  181. return src;
  182. }
  183. uint32_t CUEParser::parse_time(const char *src)
  184. {
  185. char *endptr;
  186. uint32_t minutes = strtoul(src, &endptr, 10);
  187. if (*endptr == ':') endptr++;
  188. uint32_t seconds = strtoul(endptr, &endptr, 10);
  189. if (*endptr == ':') endptr++;
  190. uint32_t frames = strtoul(endptr, &endptr, 10);
  191. return frames + 75 * (seconds + 60 * minutes);
  192. }
  193. CUEFileMode CUEParser::parse_file_mode(const char *src)
  194. {
  195. if (strncasecmp(src, "BIN", 3) == 0)
  196. return CUEFile_BINARY;
  197. else if (strncasecmp(src, "MOTOROLA", 8) == 0)
  198. return CUEFile_MOTOROLA;
  199. else if (strncasecmp(src, "MP3", 3) == 0)
  200. return CUEFile_MP3;
  201. else if (strncasecmp(src, "WAV", 3) == 0)
  202. return CUEFile_WAVE;
  203. else if (strncasecmp(src, "AIFF", 4) == 0)
  204. return CUEFile_AIFF;
  205. else
  206. return CUEFile_BINARY; // Default to binary mode
  207. }
  208. CUETrackMode CUEParser::parse_track_mode(const char *src)
  209. {
  210. if (strncasecmp(src, "AUDIO", 5) == 0)
  211. return CUETrack_AUDIO;
  212. else if (strncasecmp(src, "CDG", 3) == 0)
  213. return CUETrack_CDG;
  214. else if (strncasecmp(src, "MODE1/2048", 10) == 0)
  215. return CUETrack_MODE1_2048;
  216. else if (strncasecmp(src, "MODE1/2352", 10) == 0)
  217. return CUETrack_MODE1_2352;
  218. else if (strncasecmp(src, "MODE2/2048", 10) == 0)
  219. return CUETrack_MODE2_2048;
  220. else if (strncasecmp(src, "MODE2/2324", 10) == 0)
  221. return CUETrack_MODE2_2324;
  222. else if (strncasecmp(src, "MODE2/2336", 10) == 0)
  223. return CUETrack_MODE2_2336;
  224. else if (strncasecmp(src, "MODE2/2352", 10) == 0)
  225. return CUETrack_MODE2_2352;
  226. else if (strncasecmp(src, "CDI/2336", 8) == 0)
  227. return CUETrack_CDI_2336;
  228. else if (strncasecmp(src, "CDI/2352", 8) == 0)
  229. return CUETrack_CDI_2352;
  230. else
  231. return CUETrack_MODE1_2048; // Default to data track
  232. }
  233. uint32_t CUEParser::get_sector_length(CUEFileMode filemode, CUETrackMode trackmode)
  234. {
  235. if (filemode == CUEFile_BINARY || filemode == CUEFile_MOTOROLA)
  236. {
  237. switch (trackmode)
  238. {
  239. case CUETrack_AUDIO: return 2352;
  240. case CUETrack_CDG: return 2448;
  241. case CUETrack_MODE1_2048: return 2048;
  242. case CUETrack_MODE1_2352: return 2352;
  243. case CUETrack_MODE2_2048: return 2048;
  244. case CUETrack_MODE2_2324: return 2324;
  245. case CUETrack_MODE2_2336: return 2336;
  246. case CUETrack_MODE2_2352: return 2352;
  247. case CUETrack_CDI_2336: return 2336;
  248. case CUETrack_CDI_2352: return 2352;
  249. default: return 2048;
  250. }
  251. }
  252. else
  253. {
  254. return 0;
  255. }
  256. }