1use std::path::PathBuf;
2use std::ffi::OsString;
3
4use lexopt::prelude::*;
5
6#[derive(Debug, PartialEq, Eq, Default)]
7pub struct Args {
8 pub filename: PathBuf,
9 pub read: bool,
10 pub write: bool,
11 pub with_covers: bool,
12 pub tag_version: Option<id3::Version>,
13 pub in_json: Option<PathBuf>,
14 pub out_json: Option<PathBuf>,
15}
16
17pub fn parse_args<I>(args: I) -> Result<Args, lexopt::Error>
18where
19 I: IntoIterator + 'static,
20 I::Item: Into<OsString>,
21{
22 let mut read = false;
23 let mut write = false;
24 let mut with_covers = false;
25
26 let mut filename_input = None;
27 let mut tag_version = None;
28 let mut in_json = None;
29 let mut out_json = None;
30
31 let mut parser = lexopt::Parser::from_iter(args);
32
33 while let Some(arg) = parser.next()? {
34 match arg {
35 Short('r') | Long("read") => read = true,
36 Short('w') | Long("write") => write = true,
37 Long("with-covers") => with_covers = true,
38
39 Long("tag-version") => {
40 let mut input = parser.value()?;
41 input.make_ascii_lowercase();
42
43 if input == "id3v2.2" {
44 tag_version = Some(id3::Version::Id3v22);
45 } else if input == "id3v2.3" {
46 tag_version = Some(id3::Version::Id3v23);
47 } else if input == "id3v2.4" {
48 tag_version = Some(id3::Version::Id3v24);
49 } else {
50 let error = format!("Unsupported ID3 version: {:?}. Expected ID3v2.{{2,3,4}}", input);
51 return Err(lexopt::Error::Custom(error.into()));
52 }
53 },
54 Value(val) if filename_input.is_none() => {
55 filename_input = Some(PathBuf::from(val));
56 },
57
58 Short('i') | Long("in-json") => {
59 let input = parser.value()?.into();
60 in_json = Some(input);
61 },
62 Short('o') | Long("out-json") => {
63 let input = parser.value()?.into();
64 out_json = Some(input);
65 },
66
67 Short('V') | Long("version") => {
68 println!("id3-json {}", env!("CARGO_PKG_VERSION"));
69 std::process::exit(0);
70 },
71 Long("help") => {
72 print_help();
73 std::process::exit(0);
74 },
75 _ => return Err(arg.unexpected()),
76 }
77 }
78
79 let Some(filename) = filename_input else {
80 let error = String::from("Missing <filename.mp3>");
81 return Err(lexopt::Error::Custom(error.into()));
82 };
83
84 if !read && !write {
85 read = true;
86 }
87
88 Ok(Args { filename, read, write, with_covers, tag_version, in_json, out_json })
89}
90
91fn print_help() {
92 println!("id3-json {}", env!("CARGO_PKG_VERSION"));
93 println!();
94 println!("USAGE:");
95 println!(" id3-json [FLAGS] <music-file.mp3>");
96 println!();
97 println!("FLAGS:");
98 println!(" -r, --read Reads tags from the file and outputs them to STDOUT as JSON,");
99 println!(" or writes them to the file given by --out-json.");
100 println!(" If neither `read` nor `write` are given, will read by default.");
101 println!();
102 println!(" -w, --write Write mode, expects a JSON on STDIN with valid tag values,");
103 println!(" or reads the tags from the file given by --in-json.");
104 println!(" If also given `read`, will print/write the resulting tags afterwards");
105 println!();
106 println!(" --with-covers Also output cover images as base64-encoded data.");
107 println!(" If not set, only cover metadata will be returned.");
108 println!();
109 println!(" -i, --in-json <path/to.json>");
110 println!(" File to read tags from. If not given, uses STDIN");
111 println!();
112 println!(" -o, --out-json <path/to.json>");
113 println!(" File to write tags to. If not given, uses STDOUT");
114 println!();
115 println!(" --tag-version <ID3v2.{{2,3,4}}>");
116 println!(" On write, sets the tags' version to 2.2, 2.3, or 2.4.");
117 println!();
118 println!(" -V, --version Print version information");
119 println!();
120 println!("ARGS:");
121 println!(" <music-file.mp3> Music file to read tags from or write tags to");
122}