diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/id3v1.rs | 61 | ||||
| -rw-r--r-- | src/id3v2.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 50 | ||||
| -rw-r--r-- | src/options.rs | 2 | 
4 files changed, 105 insertions, 10 deletions
diff --git a/src/id3v1.rs b/src/id3v1.rs new file mode 100644 index 0000000..ccade4a --- /dev/null +++ b/src/id3v1.rs @@ -0,0 +1,61 @@ +use std::fs::File; +use std::io::{self, Read, Seek}; + +#[derive(Copy, Clone, Debug)] +pub enum TagType { +    /// An ID3v1.0 tag +    TAGv10, +    /// An ID3v1.1 enhanced tag (TAG+) in addition to the v1.0 tag +    TAGv11, +    /// An ID3v1.2 extended tag (EXT) in addition to the v1.0 tag +    TAGv12, +} + +pub fn recognise(file: &mut File) -> io::Result<Option<TagType>> { +    let length = file.seek(io::SeekFrom::End(0))?; +    if length < 128 { return Ok(None); } + +    let mut buf4 = [0u8; 4]; + +    // Check whether we have a v1.0 tag +    file.seek(io::SeekFrom::End(-128))?; +    file.read_exact(&mut buf4[0..3])?; +    if &buf4[0..3] == b"TAG" { +        // Check whether we also have a v1.1 tag +        if length >= 128 + 227 { +            file.seek(io::SeekFrom::End(-128 - 227))?; +            file.read_exact(&mut buf4)?; +            if &buf4 == b"TAG+" { +                return Ok(Some(TagType::TAGv11)); +            } +        } + +        // Otherwise, check whether we also have a v1.2 tag +        if length >= 128 + 128 { +            file.seek(io::SeekFrom::End(-128 - 128))?; +            file.read_exact(&mut buf4[0..3])?; +            if &buf4[0..3] == b"EXT" { +                return Ok(Some(TagType::TAGv12)); +            } +        } + +        // Nope, just v1.0 +        Ok(Some(TagType::TAGv10)) +    } else { +        Ok(None) +    } +} + +pub fn remove_tag(file: &mut File, typ: TagType) -> io::Result<()> { +    let tag_len = match typ { +        TagType::TAGv10 => 128, +        TagType::TAGv11 => 128 + 227, +        TagType::TAGv12 => 128 + 128, +    }; + +    let length = file.seek(io::SeekFrom::End(0))?; +    eprintln!("remove_tag: Resizing file from {} to {} bytes", length, length - tag_len); +    file.set_len(length - tag_len)?; + +    Ok(()) +} diff --git a/src/id3v2.rs b/src/id3v2.rs index 19f5e89..b427845 100644 --- a/src/id3v2.rs +++ b/src/id3v2.rs @@ -246,7 +246,7 @@ impl ID3v2 {          let mut header = [0u8; 10];          stream.read_exact(&mut header)?; -        let (id3version, flags, header_size) = parse_id3v2_header(&header).ok_or("Invalid ID3 header".ioerr())?; +        let (id3version, flags, header_size) = parse_id3v2_header(&header).ok_or("Invalid ID3v2 header or no ID3v2 tag found".ioerr())?;          let version_sub = match id3version {              0x0300 => 3, diff --git a/src/main.rs b/src/main.rs index 327daa0..5af6eb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use crate::options::Options;  mod encoding;  mod error; +mod id3v1;  mod id3v2;  mod options; @@ -22,6 +23,10 @@ fn parse_options_into(opt: &mut Options) {          .add_option(&["--assume-utf8"], StoreTrue,                      "Assume that all strings specified in the MP3 as Latin-1 are really UTF-8."); +    ap.refer(&mut opt.remove_v1) +        .add_option(&["--remove-v1"], StoreTrue, +                    "Remove any existing ID3v1.* tags."); +      ap.refer(&mut opt.set_tags.album) .add_option(&["-A", "--album"],  StoreOption, "Set/replace album");      ap.refer(&mut opt.set_tags.artist).add_option(&["-a", "--artist"], StoreOption, "Set/replace artist");      ap.refer(&mut opt.set_tags.title) .add_option(&["-t", "--title"],  StoreOption, "Set/replace title"); @@ -86,8 +91,36 @@ fn modify_tag(tag: &mut ID3v2, new_frame: Frame) -> io::Result<()> {  fn main() -> io::Result<()> {      let options = parse_options(); -    let mut tag = ID3v2::from_stream(&mut File::open(&options.file)?)?; -    // println!("{:?}", tag); +    let open_read = || File::open(&options.file); +    let open_write = || OpenOptions::new().write(true).open(&options.file); + +    let mut tag = { +        let (tag, tag_v1) = { +            let mut file = open_read()?; +            let tag = ID3v2::from_stream(&mut file)?; +            let tag_v1 = id3v1::recognise(&mut file)?; +            (tag, tag_v1) +        }; + +        if options.remove_v1 { +            if let Some(tag_type) = tag_v1 { +                id3v1::remove_tag(&mut open_write()?, tag_type)?; +            } else { +                eprintln!("WARNING: Told to remove ID3v1 tag while none is present."); +            } +        } else { +            if let Some(tag_type) = tag_v1 { +                let descr = match tag_type { +                    id3v1::TagType::TAGv10 => "ID3v1.0", +                    id3v1::TagType::TAGv11 => "ID3v1.1", +                    id3v1::TagType::TAGv12 => "ID3v1.2", +                }; +                eprintln!("WARNING: {} tag found at end of file; use --remove-v1 to remove it.", descr); +            } +        } + +        tag +    };      if options.latin1_as_utf8 {          for frame in &mut tag.frames { @@ -99,19 +132,18 @@ fn main() -> io::Result<()> {          }      } -    if let Some(s) = options.set_tags.album  { modify_tag(&mut tag, Frame::TALB(s))?; } -    if let Some(s) = options.set_tags.artist { modify_tag(&mut tag, Frame::TPE1(s))?; } -    if let Some(s) = options.set_tags.title  { modify_tag(&mut tag, Frame::TIT2(s))?; } -    if let Some(s) = options.set_tags.track  { modify_tag(&mut tag, Frame::TRCK(s))?; } -    if let Some(s) = options.set_tags.year   { modify_tag(&mut tag, Frame::TYER(s))?; } +    if let Some(s) = &options.set_tags.album  { modify_tag(&mut tag, Frame::TALB(s.clone()))?; } +    if let Some(s) = &options.set_tags.artist { modify_tag(&mut tag, Frame::TPE1(s.clone()))?; } +    if let Some(s) = &options.set_tags.title  { modify_tag(&mut tag, Frame::TIT2(s.clone()))?; } +    if let Some(s) = &options.set_tags.track  { modify_tag(&mut tag, Frame::TRCK(s.clone()))?; } +    if let Some(s) = &options.set_tags.year   { modify_tag(&mut tag, Frame::TYER(s.clone()))?; }      print_tag(&tag)?;      // TODO: if -w, then write tags to file (if it fits)      if options.write {          let encoded = tag.encode()?; -        let mut f = OpenOptions::new().write(true).open(&options.file)?; -        f.write_all(&encoded)?; +        open_write()?.write_all(&encoded)?;          println!("Tag written!");      } diff --git a/src/options.rs b/src/options.rs index 5f1dec9..b2223de 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,5 +1,6 @@  pub struct Options {      pub latin1_as_utf8: bool, +    pub remove_v1: bool,      pub file: String,      pub write: bool,      pub set_tags: TagOptions, @@ -18,6 +19,7 @@ impl Default for Options {      fn default() -> Self {          Options {              latin1_as_utf8: false, +            remove_v1: false,              file: String::new(),              write: false,              set_tags: Default::default(),  | 
