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.

147 lines
5.0 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 extract the cue sheet from the NRG metadata.
  23. use std::io::Write;
  24. use std::ffi::OsStr;
  25. use std::fs::File;
  26. use std::path::PathBuf;
  27. use ::error::NrgError;
  28. use ::metadata::metadata::NrgMetadata;
  29. use ::metadata::cuex::NrgCuexTrack;
  30. /// Writes the cue sheet for `img_path` into a file.
  31. ///
  32. /// - `img_path` is the name of the input NRG file.
  33. /// - `metadata` is the metadata extracted from `img_path` by nrgrip::metadata.
  34. ///
  35. /// The output file's name will be `img_path`'s base name stripped for its
  36. /// extension (if any), with a ".cue" extension.
  37. pub fn write_cue_sheet(img_path: &str, metadata: &NrgMetadata)
  38. -> Result<(), NrgError> {
  39. // Make sure we have a cue sheet in the metadata
  40. let cuex_tracks = match metadata.cuex_chunk {
  41. None => return Err(NrgError::NoNrgCue),
  42. Some(ref chunk) => &chunk.tracks,
  43. };
  44. // Get the image's base name
  45. let img_name = PathBuf::from(img_path);
  46. let img_name = match img_name.file_name() {
  47. Some(name) => name,
  48. None => return Err(NrgError::FileName(img_path.to_string())),
  49. };
  50. // Set the cue sheet file's name
  51. let mut cue_name = PathBuf::from(img_name);
  52. if cue_name.extension().unwrap_or(OsStr::new("")) == "cue" {
  53. // img_path's extension was already .cue: problem!
  54. return Err(NrgError::FileName("Input and output file are identical"
  55. .to_string()));
  56. }
  57. cue_name.set_extension("cue");
  58. // Set the raw audio file's name
  59. let mut raw_name = cue_name.clone();
  60. raw_name.set_extension("raw");
  61. // Write cue sheet
  62. let mut fd = try!(File::create(cue_name));
  63. try!(writeln!(fd, "FILE \"{}\" BINARY", raw_name.to_string_lossy()));
  64. try!(write_cue_tracks(&mut fd, cuex_tracks));
  65. Ok(())
  66. }
  67. /// Writes a list of cue tracks to `fd`.
  68. fn write_cue_tracks(fd: &mut File, cuex_tracks: &Vec<NrgCuexTrack>)
  69. -> Result<(), NrgError> {
  70. let mut index0_pos = -1; // position of the last index #0 encountered
  71. for track in cuex_tracks {
  72. try!(write_cue_track(fd, track, &mut index0_pos));
  73. }
  74. Ok(())
  75. }
  76. /// Writes a cue track's info to `fd`.
  77. ///
  78. /// `index0_pos` should be negative when this function is first called.
  79. fn write_cue_track(fd: &mut File, track: &NrgCuexTrack, index0_pos: &mut i32)
  80. -> Result<(), NrgError> {
  81. // Ignore lead-in and lead-out areas
  82. if track.track_number == 0 || track.track_number == 0xAA {
  83. return Ok(());
  84. }
  85. // Ignore negative positions. This should happen only for track 1, index 0
  86. // and for the lead-in area (which we already skipped).
  87. if track.position_sectors < 0 {
  88. return Ok(());
  89. }
  90. // Store/skip index0
  91. if track.index_number == 0 {
  92. *index0_pos = track.position_sectors;
  93. return Ok(());
  94. }
  95. // Write track info
  96. try!(writeln!(fd, " TRACK {:02} AUDIO", track.track_number));
  97. // Write index0 if we stored it and it's before the current index's
  98. // position (i.e., it indicates a pre-gap)
  99. if *index0_pos >= 0 && *index0_pos < track.position_sectors {
  100. try!(write_cue_index(fd, 0, *index0_pos));
  101. }
  102. // Reset index0 (even if we didn't write it, because it only applies to the
  103. // current track)
  104. *index0_pos = -1;
  105. // Write current index
  106. write_cue_index(fd, track.index_number, track.position_sectors)
  107. }
  108. /// Writes a cue index's info to `fd`.
  109. fn write_cue_index(fd: &mut File, index: u8, position_sectors: i32)
  110. -> Result<(), NrgError> {
  111. assert!(position_sectors >= 0);
  112. // Audio CDs are played at a 75 sectors per second rate:
  113. let mut seconds: u32 = position_sectors as u32 / 75;
  114. let remaining_sectors = position_sectors % 75;
  115. let minutes = seconds / 60;
  116. seconds %= 60;
  117. try!(writeln!(fd, " INDEX {:02} {:02}:{:02}:{:02}",
  118. index, minutes, seconds, remaining_sectors));
  119. Ok(())
  120. }