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.

157 lines
5.3 KiB

  1. // This file is part of the NRGrip project.
  2. //
  3. // Copyright (c) 2016 Matteo Cypriani <mcy@lm7.fr>
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to
  7. // deal in the Software without restriction, including without limitation the
  8. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9. // sell copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21. // IN THE SOFTWARE.
  22. //! Module to read and store the metadata from an NRG image file.
  23. use std::fs::File;
  24. use std::io::{Seek, SeekFrom};
  25. use ::error::NrgError;
  26. pub mod metadata;
  27. pub mod cuex;
  28. mod daox;
  29. mod sinf;
  30. mod mtyp;
  31. mod readers;
  32. use self::metadata::NrgMetadata;
  33. use self::readers::*;
  34. /// Reads the metadata chunks from an open NRG image file `fd`.
  35. ///
  36. /// `fd`'s offset can be anywhere when this function is called: it will be reset
  37. /// before anything is read.
  38. ///
  39. /// In case of success, `fd`'s offset will be left after the "END!" string of
  40. /// the NRG footer. Otherwise, the offset is undefined and should be reset by
  41. /// the caller if any additional reading operations are to be done.
  42. pub fn read_nrg_metadata(fd: &mut File) -> Result<NrgMetadata, NrgError> {
  43. let mut nm = NrgMetadata::new();
  44. // Get the file size
  45. nm.file_size = try!(fd.seek(SeekFrom::End(0)));
  46. // Get the NRG format from the footer
  47. nm.nrg_version = try!(read_nrg_version(fd, nm.file_size));
  48. if nm.nrg_version != 2 {
  49. // We handle only NRG v2
  50. return Err(NrgError::NrgFormat(
  51. "NRG v1 format is not handled".to_string()));
  52. }
  53. // Read the first chunk offset
  54. nm.chunk_offset = try!(read_u64(fd));
  55. // Read all the chunks
  56. try!(fd.seek(SeekFrom::Start(nm.chunk_offset)));
  57. try!(read_nrg_chunks(fd, &mut nm));
  58. Ok(nm)
  59. }
  60. /// Determines the NRG format of an open NRG image `fd` of file `file_size`.
  61. ///
  62. /// `fd`'s offset at call-time doesn't matter, as this function will seek to
  63. /// read the relevant data.
  64. ///
  65. /// The offset is left after the main chunk ID, therefore the calling function
  66. /// can read the first data chunk's offset (32 bits for NRG v1 or 64 bits for
  67. /// NRG v2) directly without seeking.
  68. pub fn read_nrg_version(fd: &mut File, file_size: u64) -> Result<u8, NrgError> {
  69. if file_size < 12 {
  70. // Input file too small
  71. return Err(NrgError::NrgFormat(
  72. "Input file is to small to be an NRG image".to_string()));
  73. }
  74. // In NRG v2, the main footer is on the last 12 bytes
  75. try!(fd.seek(SeekFrom::End(-12)));
  76. let chunk_id = try!(read_nrg_chunk_id(fd));
  77. if chunk_id == "NER5" {
  78. return Ok(2); // NRG v2
  79. }
  80. // In NRG v1, the main footer is on the last 8 bytes; since we just read 4
  81. // bytes after seeking 12 bytes before the end, the offset is right
  82. let chunk_id = try!(read_nrg_chunk_id(fd));
  83. if chunk_id == "NERO" {
  84. return Ok(1); // NRG v1
  85. }
  86. Err(NrgError::NrgFormat("Unknown format".to_string()))
  87. }
  88. /// Reads all the available NRG chunks.
  89. ///
  90. /// Returns the number of chunks read.
  91. fn read_nrg_chunks(fd: &mut File, nm: &mut NrgMetadata) -> Result<(), NrgError> {
  92. loop {
  93. let chunk_id = try!(read_nrg_chunk_id(fd));
  94. match chunk_id.as_ref() {
  95. "END!" => break,
  96. "CUEX" => { nm.cuex_chunk = Some(try!(cuex::read_nrg_cuex(fd))); },
  97. "DAOX" => { nm.daox_chunk = Some(try!(daox::read_nrg_daox(fd))); },
  98. "CDTX" => {
  99. try!(skip_chunk(fd));
  100. nm.skipped_chunks.push(chunk_id);
  101. },
  102. "ETN2" => {
  103. try!(skip_chunk(fd));
  104. nm.skipped_chunks.push(chunk_id);
  105. },
  106. "SINF" => { nm.sinf_chunk = Some(try!(sinf::read_nrg_sinf(fd))); },
  107. "MTYP" => { nm.mtyp_chunk = Some(try!(mtyp::read_nrg_mtyp(fd))); },
  108. "DINF" => {
  109. try!(skip_chunk(fd));
  110. nm.skipped_chunks.push(chunk_id);
  111. },
  112. "TOCT" => {
  113. try!(skip_chunk(fd));
  114. nm.skipped_chunks.push(chunk_id);
  115. },
  116. "RELO" => {
  117. try!(skip_chunk(fd));
  118. nm.skipped_chunks.push(chunk_id);
  119. },
  120. _ => return Err(NrgError::NrgChunkId(chunk_id)),
  121. }
  122. }
  123. Ok(())
  124. }
  125. /// Reads an NRG chunk ID (i.e. a 4-byte string) from `fd`.
  126. fn read_nrg_chunk_id(fd: &mut File) -> Result<String, NrgError> {
  127. read_sized_string(fd, 4)
  128. }
  129. /// Skips a chunk.
  130. fn skip_chunk(fd: &mut File) -> Result<(), NrgError> {
  131. let chunk_size = try!(read_u32(fd));
  132. try!(fd.seek(SeekFrom::Current(chunk_size as i64)));
  133. Ok(())
  134. }