Extract audio and cue sheet from an NRG audio CD image.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

647 lines
17KB

  1. use std::error::Error;
  2. use std::fmt;
  3. use std::fs::File;
  4. use std::io::{self, Read, Seek, SeekFrom};
  5. use std::mem;
  6. #[derive(Debug)]
  7. pub enum NrgError {
  8. Io(io::Error),
  9. NrgFormat,
  10. NrgFormatV1,
  11. NrgChunkId,
  12. }
  13. impl fmt::Display for NrgError {
  14. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  15. match *self {
  16. NrgError::Io(ref err) => err.fmt(f),
  17. NrgError::NrgFormat => write!(f, "NRG format unknown."),
  18. NrgError::NrgFormatV1 => write!(f, "NRG v1 format is not handled."),
  19. NrgError::NrgChunkId => write!(f, "NRG chunk ID unknown."),
  20. }
  21. }
  22. }
  23. impl Error for NrgError {
  24. fn description(&self) -> &str {
  25. match *self {
  26. NrgError::Io(ref err) => err.description(),
  27. NrgError::NrgFormat => "NRG format",
  28. NrgError::NrgFormatV1 => "NRG format v1",
  29. NrgError::NrgChunkId => "NRG chunk ID",
  30. }
  31. }
  32. fn cause(&self) -> Option<&Error> {
  33. match *self {
  34. NrgError::Io(ref err) => Some(err),
  35. NrgError::NrgFormat => None,
  36. NrgError::NrgFormatV1 => None,
  37. NrgError::NrgChunkId => None,
  38. }
  39. }
  40. }
  41. impl From<io::Error> for NrgError {
  42. fn from(err: io::Error) -> NrgError {
  43. NrgError::Io(err)
  44. }
  45. }
  46. #[derive(Debug)]
  47. pub struct NrgMetadata {
  48. pub file_size: u64,
  49. pub nrg_version: u8,
  50. pub chunk_offset: u64,
  51. pub cuex_chunk: Option<NrgCuex>,
  52. pub daox_chunk: Option<NrgDaox>,
  53. pub sinf_chunk: Option<NrgSinf>,
  54. pub mtyp_chunk: Option<NrgMtyp>,
  55. }
  56. impl NrgMetadata {
  57. fn new() -> NrgMetadata {
  58. NrgMetadata {
  59. file_size: 0,
  60. nrg_version: 0,
  61. chunk_offset: 0,
  62. cuex_chunk: None,
  63. daox_chunk: None,
  64. sinf_chunk: None,
  65. mtyp_chunk: None,
  66. }
  67. }
  68. }
  69. impl fmt::Display for NrgMetadata {
  70. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  71. try!(write!(f, "Image size: {} Bytes\n\
  72. NRG format version: {}\n\
  73. First chunk offset: {}",
  74. self.file_size,
  75. self.nrg_version,
  76. self.chunk_offset,
  77. ));
  78. match self.cuex_chunk {
  79. None => {},
  80. Some(ref chunk) => try!(write!(f, "\n\n\
  81. {}", chunk)),
  82. }
  83. match self.daox_chunk {
  84. None => {},
  85. Some(ref chunk) => try!(write!(f, "\n\n\
  86. {}", chunk)),
  87. }
  88. match self.sinf_chunk {
  89. None => {},
  90. Some(ref chunk) => try!(write!(f, "\n\n\
  91. {}", chunk)),
  92. }
  93. match self.mtyp_chunk {
  94. None => {},
  95. Some(ref chunk) => try!(write!(f, "\n\n\
  96. {}", chunk)),
  97. }
  98. Ok(())
  99. }
  100. }
  101. #[derive(Debug)]
  102. pub struct NrgCuex {
  103. pub size: u32,
  104. pub tracks: Vec<NrgCuexTrack>,
  105. }
  106. impl NrgCuex {
  107. fn new() -> NrgCuex {
  108. NrgCuex {
  109. size: 0,
  110. tracks: Vec::new(),
  111. }
  112. }
  113. }
  114. impl fmt::Display for NrgCuex {
  115. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  116. try!(write!(f, "Chunk ID: CUEX\n\
  117. Chunk description: Cue Sheet\n\
  118. Chunk size: {} Bytes", self.size));
  119. if self.tracks.is_empty() {
  120. try!(write!(f, "\nNo CUEX tracks!"));
  121. } else {
  122. for track in &self.tracks {
  123. try!(write!(f, "\n\
  124. Track:\n\
  125. {}", track));
  126. }
  127. }
  128. Ok(())
  129. }
  130. }
  131. #[derive(Debug)]
  132. pub struct NrgCuexTrack {
  133. mode: u8,
  134. track_number: u8,
  135. index_number: u8,
  136. padding: u8,
  137. position_sectors: i32,
  138. }
  139. impl NrgCuexTrack {
  140. fn new() -> NrgCuexTrack {
  141. NrgCuexTrack {
  142. mode: 0,
  143. track_number: 0,
  144. index_number: 0,
  145. padding: 0,
  146. position_sectors: 0,
  147. }
  148. }
  149. }
  150. impl fmt::Display for NrgCuexTrack {
  151. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  152. try!(writeln!(f, "\tMode: 0x{:02X}", self.mode));
  153. try!(write!(f, "\tTrack number: "));
  154. if self.track_number == 0 {
  155. try!(writeln!(f, "0 (lead-in area)"));
  156. } else if self.track_number == 0xAA {
  157. try!(writeln!(f, "0xAA (lead-out area)"));
  158. } else {
  159. try!(writeln!(f, "{}", self.track_number));
  160. }
  161. try!(writeln!(f, "\tIndex number: {}", self.index_number));
  162. if self.padding != 0 {
  163. try!(writeln!(f, "\tPadding: {} (Warning: should be 0!)",
  164. self.padding));
  165. }
  166. // Audio CDs are played at a 75 sectors per second rate:
  167. let position_seconds: f64 = (self.position_sectors as f64) / 75.0;
  168. write!(f, "\tPosition: {} sectors ({:.2} seconds)",
  169. self.position_sectors, position_seconds)
  170. }
  171. }
  172. #[derive(Debug)]
  173. pub struct NrgDaox {
  174. pub size: u32,
  175. pub size2: u32,
  176. pub upc: String,
  177. pub padding: u8,
  178. pub toc_type: u16,
  179. pub first_track: u8,
  180. pub last_track: u8,
  181. pub tracks: Vec<NrgDaoxTrack>,
  182. }
  183. impl NrgDaox {
  184. fn new() -> NrgDaox {
  185. NrgDaox {
  186. size: 0,
  187. size2: 0,
  188. upc: String::new(),
  189. padding: 0,
  190. toc_type: 0,
  191. first_track: 0,
  192. last_track: 0,
  193. tracks: Vec::new(),
  194. }
  195. }
  196. }
  197. impl fmt::Display for NrgDaox {
  198. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  199. try!(writeln!(f, "Chunk ID: DAOX\n\
  200. Chunk description: DAO (Disc At Once) Information\n\
  201. Chunk size: {} Bytes\n\
  202. Chunk size 2: {}\n\
  203. UPC: \"{}\"",
  204. self.size,
  205. self.size2,
  206. self.upc));
  207. if self.padding != 0 {
  208. try!(writeln!(f, "Padding: {} (Warning: should be 0!)",
  209. self.padding));
  210. }
  211. try!(write!(f, "TOC type: 0x{:04X}\n\
  212. First track in the session: {}\n\
  213. Last track in the session: {}",
  214. self.toc_type,
  215. self.first_track,
  216. self.last_track));
  217. if self.tracks.is_empty() {
  218. try!(write!(f, "\nNo DAOX tracks!"));
  219. } else {
  220. let mut i = 1;
  221. for track in &self.tracks {
  222. try!(write!(f, "\n\
  223. Track {:02}:\n\
  224. {}", i, track));
  225. i += 1;
  226. }
  227. }
  228. Ok(())
  229. }
  230. }
  231. #[derive(Debug)]
  232. pub struct NrgDaoxTrack {
  233. isrc: String,
  234. sector_size: u16,
  235. data_mode: u16,
  236. unknown: u16,
  237. index0: u64,
  238. index1: u64,
  239. track_end: u64,
  240. }
  241. impl NrgDaoxTrack {
  242. fn new() -> NrgDaoxTrack {
  243. NrgDaoxTrack {
  244. isrc: String::new(),
  245. sector_size: 0,
  246. data_mode: 0,
  247. unknown: 0,
  248. index0: 0,
  249. index1: 0,
  250. track_end: 0,
  251. }
  252. }
  253. }
  254. impl fmt::Display for NrgDaoxTrack {
  255. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  256. try!(writeln!(f, "\tISRC: \"{}\"\n\
  257. \tSector size in the image file: {} Bytes\n\
  258. \tMode of the data in the image file: 0x{:04X}",
  259. self.isrc,
  260. self.sector_size,
  261. self.data_mode));
  262. if self.unknown != 0x0001 {
  263. try!(writeln!(f, "\tUnknown field: 0x{:04X} \
  264. (Warning: should be 0x0001!)",
  265. self.unknown));
  266. }
  267. write!(f, "\tIndex0 (Pre-gap): {} Bytes\n\
  268. \tIndex1 (Start of track): {} Bytes\n\
  269. \tEnd of track + 1: {} Bytes",
  270. self.index0,
  271. self.index1,
  272. self.track_end)
  273. }
  274. }
  275. #[derive(Debug)]
  276. pub struct NrgSinf {
  277. pub size: u32,
  278. pub nb_tracks: u32,
  279. }
  280. impl NrgSinf {
  281. fn new() -> NrgSinf {
  282. NrgSinf {
  283. size: 0,
  284. nb_tracks: 0,
  285. }
  286. }
  287. }
  288. impl fmt::Display for NrgSinf {
  289. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  290. write!(f, "Chunk ID: SINF\n\
  291. Chunk description: Session Information\n\
  292. Chunk size: {} Bytes\n\
  293. Number of tracks in the session: {}",
  294. self.size,
  295. self.nb_tracks)
  296. }
  297. }
  298. #[derive(Debug)]
  299. pub struct NrgMtyp {
  300. pub size: u32,
  301. pub unknown: u32,
  302. }
  303. impl NrgMtyp {
  304. fn new() -> NrgMtyp {
  305. NrgMtyp {
  306. size: 0,
  307. unknown: 0,
  308. }
  309. }
  310. }
  311. impl fmt::Display for NrgMtyp {
  312. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  313. write!(f, "Chunk ID: MTYP\n\
  314. Chunk description: Media Type (?)\n\
  315. Chunk size: {} Bytes\n\
  316. Unknown field: 0x{:04X}",
  317. self.size,
  318. self.unknown)
  319. }
  320. }
  321. pub fn parse_nrg_metadata(img_name: String) -> Result<NrgMetadata, NrgError> {
  322. let mut nm = NrgMetadata::new();
  323. // Open the image file
  324. let mut fd = try!(File::open(img_name));
  325. // Get the file size
  326. nm.file_size = try!(fd.seek(SeekFrom::End(0)));
  327. // Get the NRG format from the footer
  328. nm.nrg_version = try!(read_nrg_version(&mut fd, nm.file_size));
  329. if nm.nrg_version != 2 {
  330. // We handle only NRG v2
  331. return Err(NrgError::NrgFormatV1);
  332. }
  333. // Read the first chunk offset
  334. nm.chunk_offset = try!(read_u64(&mut fd));
  335. // Read all the chunks
  336. try!(fd.seek(SeekFrom::Start(nm.chunk_offset)));
  337. try!(read_nrg_chunks(&mut fd, &mut nm));
  338. Ok(nm)
  339. }
  340. /// Determines the NRG format of an open NRG image `fd` of file `file_size`.
  341. ///
  342. /// The offset is left after the main chunk ID, therefore the calling function
  343. /// can read the first data chunk's offset (32 bits for NRG v1 or 64 bits for
  344. /// NRG v2) directly without seeking.
  345. fn read_nrg_version(fd: &mut File, file_size: u64) -> Result<u8, NrgError> {
  346. if file_size < 12 {
  347. // Input file too small
  348. return Err(NrgError::NrgFormat);
  349. }
  350. // In NRG v2, the main footer is on the last 12 bytes
  351. try!(fd.seek(SeekFrom::End(-12)));
  352. let chunk_id = try!(read_nrg_chunk_id(fd));
  353. if chunk_id == "NER5" {
  354. return Ok(2); // NRG v2
  355. }
  356. // In NRG v1, the main footer is on the last 8 bytes; since we just read 4
  357. // bytes after seeking 12 bytes before the end, the offset is right
  358. let chunk_id = try!(read_nrg_chunk_id(fd));
  359. if chunk_id == "NERO" {
  360. return Ok(1); // NRG v1
  361. }
  362. Err(NrgError::NrgFormat) // Unknown format
  363. }
  364. /// Reads all the available NRG chunks.
  365. ///
  366. /// Returns the number of chunks read.
  367. fn read_nrg_chunks(fd: &mut File, nm: &mut NrgMetadata) -> Result<u16, NrgError> {
  368. let mut nread = 0u16;
  369. loop {
  370. let chunk_id = try!(read_nrg_chunk_id(fd));
  371. nread += 1;
  372. match chunk_id.as_ref() {
  373. "END!" => break,
  374. "CUEX" => { nm.cuex_chunk = Some(try!(read_nrg_cuex(fd))); },
  375. "DAOX" => { nm.daox_chunk = Some(try!(read_nrg_daox(fd))); },
  376. "CDTX" => { try!(skip_unhandled_chunk(fd, &chunk_id)); },
  377. "ETN2" => { try!(skip_unhandled_chunk(fd, &chunk_id)); },
  378. "SINF" => { nm.sinf_chunk = Some(try!(read_nrg_sinf(fd))); },
  379. "MTYP" => { nm.mtyp_chunk = Some(try!(read_nrg_mtyp(fd))); },
  380. "DINF" => { try!(skip_unhandled_chunk(fd, &chunk_id)); },
  381. "TOCT" => { try!(skip_unhandled_chunk(fd, &chunk_id)); },
  382. "RELO" => { try!(skip_unhandled_chunk(fd, &chunk_id)); },
  383. _ => { println!("{}", chunk_id); return Err(NrgError::NrgChunkId); }, //fixme
  384. }
  385. }
  386. Ok(nread)
  387. }
  388. /// Reads an NRG chunk ID (i.e. a 4-byte string) from `fd`.
  389. fn read_nrg_chunk_id(fd: &File) -> Result<String, NrgError> {
  390. read_sized_string(fd, 4)
  391. }
  392. /// Reads a String of size `size` from `fd`.
  393. fn read_sized_string(fd: &File, size: u64) -> Result<String, NrgError> {
  394. let mut handle = fd.take(size);
  395. let mut string = String::new();
  396. try!(handle.read_to_string(&mut string));
  397. Ok(string)
  398. }
  399. /// Reads a 64-bit unsigned integer from `fd`.
  400. fn read_u64(fd: &mut File) -> Result<u64, NrgError> {
  401. let mut buf = [0u8; 8];
  402. try!(fd.read_exact(&mut buf));
  403. let i: u64;
  404. unsafe {
  405. i = mem::transmute(buf);
  406. }
  407. Ok(u64::from_be(i))
  408. }
  409. /// Reads a 32-bit unsigned integer from `fd`.
  410. fn read_u32(fd: &mut File) -> Result<u32, NrgError> {
  411. let mut buf = [0u8; 4];
  412. try!(fd.read_exact(&mut buf));
  413. let i: u32;
  414. unsafe {
  415. i = mem::transmute(buf);
  416. }
  417. Ok(u32::from_be(i))
  418. }
  419. /// Reads a 16-bit unsigned integer from `fd`.
  420. fn read_u16(fd: &mut File) -> Result<u16, NrgError> {
  421. let mut buf = [0u8; 2];
  422. try!(fd.read_exact(&mut buf));
  423. let i: u16;
  424. unsafe {
  425. i = mem::transmute(buf);
  426. }
  427. Ok(u16::from_be(i))
  428. }
  429. /// Reads an unsigned byte from `fd`.
  430. fn read_u8(fd: &mut File) -> Result<u8, NrgError> {
  431. let mut buf = [0u8; 1];
  432. try!(fd.read_exact(&mut buf));
  433. Ok(buf[0])
  434. }
  435. fn skip_unhandled_chunk(fd: &mut File, chunk_id: &str) -> Result<(), NrgError> {
  436. let chunk_size = try!(read_u32(fd));
  437. try!(fd.seek(SeekFrom::Current(chunk_size as i64)));
  438. // fixme: lib should'nt print!
  439. println!("Skipping unhandled chunk: {} ({} bytes)", chunk_id, chunk_size);
  440. Ok(())
  441. }
  442. /// Reads the NRG Cue Sheet chunk (CUEX).
  443. ///
  444. /// The CUEX is constituted of the following data:
  445. ///
  446. /// - 4 B: Chunk size (in bytes): size to be read *after* this chunk size
  447. /// (should be a multiple of 8)
  448. ///
  449. /// - one or more pairs of 8-byte blocks composed of:
  450. /// + 1 B: Mode (values found: 0x01 for audio; 0x21 for non
  451. /// copyright-protected audio; 0x41 for data)
  452. /// + 1 B: Track number (BCD coded; 0xAA for the lead-out area)
  453. /// + 1 B: Index number (probably BCD coded): 0 or 1
  454. /// + 1 B: Unknown (padding?), always 0
  455. /// + 4 B: Position in sectors (signed integer value)
  456. ///
  457. /// - one last block like the ones above, for the lead-out area (optional?)
  458. fn read_nrg_cuex(fd: &mut File) -> Result<NrgCuex, NrgError> {
  459. let mut chunk = NrgCuex::new();
  460. chunk.size = try!(read_u32(fd));
  461. let mut bytes_read = 0;
  462. // Read all the 8-byte track info
  463. while bytes_read < chunk.size {
  464. chunk.tracks.push(try!(read_nrg_cuex_track(fd)));
  465. bytes_read += 8;
  466. }
  467. assert_eq!(bytes_read, chunk.size);
  468. Ok(chunk)
  469. }
  470. /// Reads a track from the NRG cue sheet.
  471. fn read_nrg_cuex_track(fd: &mut File) -> Result<NrgCuexTrack, NrgError> {
  472. let mut track = NrgCuexTrack::new();
  473. track.mode = try!(read_u8(fd));
  474. track.track_number = try!(read_u8(fd));
  475. track.index_number = try!(read_u8(fd));
  476. track.padding = try!(read_u8(fd));
  477. track.position_sectors = try!(read_u32(fd)) as i32;
  478. Ok(track)
  479. }
  480. /// Reads the NRG Disc-At-Once Information chunk (DAOX).
  481. ///
  482. /// The DAOX is constituted of the following data:
  483. ///
  484. /// - 4 B: Chunk size (in bytes)
  485. /// - 4 B: Chunk size again, sometimes little endian
  486. /// - 13 B: UPC (text) or null bytes
  487. /// - 1 B: Unknown (padding?), always 0
  488. /// - 2 B: TOC type
  489. /// - 1 B: First track in the session
  490. /// - 1 B: Last track in the session
  491. ///
  492. /// Followed by one or more groups of 42-byte blocks composed of:
  493. /// - 12 B: ISRC (text) or null bytes
  494. /// - 2 B: Sector size in the image file (bytes)
  495. /// - 2 B: Mode of the data in the image file
  496. /// - 2 B: Unknown (should always be 0x0001)
  497. /// - 8 B: Index0 (Pre-gap) (bytes)
  498. /// - 8 B: Index1 (Start of track) (bytes)
  499. /// - 8 B: End of track + 1 (bytes)
  500. fn read_nrg_daox(fd: &mut File) -> Result<NrgDaox, NrgError> {
  501. let mut chunk = NrgDaox::new();
  502. chunk.size = try!(read_u32(fd));
  503. let mut bytes_read = 0;
  504. chunk.size2 = try!(read_u32(fd));
  505. bytes_read += 4; // 32 bits
  506. chunk.upc = try!(read_sized_string(fd, 13));
  507. bytes_read += 13;
  508. chunk.padding = try!(read_u8(fd));
  509. bytes_read += 1;
  510. chunk.toc_type = try!(read_u16(fd));
  511. bytes_read += 2;
  512. chunk.first_track = try!(read_u8(fd));
  513. chunk.last_track = try!(read_u8(fd));
  514. bytes_read += 2;
  515. // Read all the 42-byte track info
  516. while bytes_read < chunk.size {
  517. chunk.tracks.push(try!(read_nrg_daox_track(fd)));
  518. bytes_read += 42;
  519. }
  520. assert_eq!(bytes_read, chunk.size);
  521. Ok(chunk)
  522. }
  523. /// Reads a track from the NRG DAO Information.
  524. fn read_nrg_daox_track(fd: &mut File) -> Result<NrgDaoxTrack, NrgError> {
  525. let mut track = NrgDaoxTrack::new();
  526. track.isrc = try!(read_sized_string(fd, 12));
  527. track.sector_size = try!(read_u16(fd));
  528. track.data_mode = try!(read_u16(fd));
  529. track.unknown = try!(read_u16(fd));
  530. track.index0 = try!(read_u64(fd));
  531. track.index1 = try!(read_u64(fd));
  532. track.track_end = try!(read_u64(fd));
  533. Ok(track)
  534. }
  535. /// Reads the NRG Session Information chunk (SINF).
  536. fn read_nrg_sinf(fd: &mut File) -> Result<NrgSinf, NrgError> {
  537. let mut chunk = NrgSinf::new();
  538. chunk.size = try!(read_u32(fd));
  539. chunk.nb_tracks = try!(read_u32(fd));
  540. Ok(chunk)
  541. }
  542. /// Reads the Media Type (?) chunk (MTYP).
  543. fn read_nrg_mtyp(fd: &mut File) -> Result<NrgMtyp, NrgError> {
  544. let mut chunk = NrgMtyp::new();
  545. chunk.size = try!(read_u32(fd));
  546. chunk.unknown = try!(read_u32(fd));
  547. Ok(chunk)
  548. }