1use crate::error::Result;
6use crate::ExifTool;
7use std::io::{self, Write};
8
9pub struct ReplShell {
11 exiftool: ExifTool,
12 verbose: bool,
13 show_help_on_start: bool,
14}
15
16impl ReplShell {
17 pub fn new(exiftool: ExifTool) -> Self {
19 Self {
20 exiftool,
21 verbose: false,
22 show_help_on_start: true,
23 }
24 }
25
26 pub fn verbose(mut self, yes: bool) -> Self {
28 self.verbose = yes;
29 self
30 }
31
32 pub fn show_help_on_start(mut self, yes: bool) -> Self {
34 self.show_help_on_start = yes;
35 self
36 }
37
38 pub fn run(&self) -> Result<()> {
40 let stdin = io::stdin();
41 let mut stdout = io::stdout();
42
43 if self.show_help_on_start {
44 self.print_help(&mut stdout)?;
45 }
46
47 writeln!(stdout, "ExifTool REPL v{}", env!("CARGO_PKG_VERSION"))?;
48 writeln!(stdout, "输入 'help' 查看帮助,输入 'quit' 退出。\n")?;
49
50 loop {
51 write!(stdout, "exiftool> ")?;
52 stdout.flush()?;
53
54 let mut line = String::new();
55 if stdin.read_line(&mut line).is_err() {
56 break;
57 }
58
59 let input = line.trim();
60 if input.is_empty() {
61 continue;
62 }
63
64 match self.process_command(input) {
65 Ok(should_quit) => {
66 if should_quit {
67 writeln!(stdout, "再见!")?;
68 break;
69 }
70 }
71 Err(e) => {
72 writeln!(stdout, "错误: {}", e)?;
73 }
74 }
75 }
76
77 Ok(())
78 }
79
80 fn process_command(&self, input: &str) -> Result<bool> {
82 let parts: Vec<&str> = input.split_whitespace().collect();
83 if parts.is_empty() {
84 return Ok(false);
85 }
86
87 let cmd = parts[0].to_lowercase();
88 let args = &parts[1..];
89
90 match cmd.as_str() {
91 "quit" | "exit" | "q" => Ok(true),
92 "help" | "h" => {
93 self.print_help(&mut io::stdout())?;
94 Ok(false)
95 }
96 "read" | "r" => {
97 if args.is_empty() {
98 println!("用法: read <文件路径> [标签名]");
99 } else {
100 self.cmd_read(args)?;
101 }
102 Ok(false)
103 }
104 "write" | "w" => {
105 if args.len() < 3 {
106 println!("用法: write <文件路径> <标签名> <值>");
107 } else {
108 self.cmd_write(args)?;
109 }
110 Ok(false)
111 }
112 "delete" | "d" => {
113 if args.len() < 2 {
114 println!("用法: delete <文件路径> <标签名>");
115 } else {
116 self.cmd_delete(args)?;
117 }
118 Ok(false)
119 }
120 "batch" | "b" => {
121 if args.is_empty() {
122 println!("用法: batch <文件路径1> [<文件路径2> ...]");
123 } else {
124 self.cmd_batch(args)?;
125 }
126 Ok(false)
127 }
128 "tags" | "t" => {
129 self.cmd_list_tags()?;
130 Ok(false)
131 }
132 "verbose" | "v" => {
133 println!("详细模式: {}", self.verbose);
134 Ok(false)
135 }
136 "version" => {
137 match self.exiftool.version() {
138 Ok(ver) => println!("ExifTool 版本: {}", ver),
139 Err(e) => println!("无法获取版本: {}", e),
140 }
141 Ok(false)
142 }
143 _ => {
144 println!("未知命令: {}", cmd);
145 println!("输入 'help' 查看可用命令");
146 Ok(false)
147 }
148 }
149 }
150
151 fn cmd_read(&self, args: &[&str]) -> Result<()> {
153 let path = args[0];
154
155 if args.len() > 1 {
156 let tag_name = args[1];
158 match self.exiftool.read_tag::<String, _, _>(path, tag_name) {
159 Ok(value) => println!("{}: {}", tag_name, value),
160 Err(e) => println!("读取标签失败: {}", e),
161 }
162 } else {
163 match self.exiftool.query(path).execute() {
165 Ok(metadata) => {
166 println!("文件: {}", path);
167 for (key, value) in metadata.iter() {
168 println!(" {}: {}", key, value);
169 }
170 }
171 Err(e) => println!("读取元数据失败: {}", e),
172 }
173 }
174
175 Ok(())
176 }
177
178 fn cmd_write(&self, args: &[&str]) -> Result<()> {
180 let path = args[0];
181 let tag = args[1];
182 let value = args[2..].join(" ");
183
184 match self
185 .exiftool
186 .write(path)
187 .tag(tag, &value)
188 .overwrite_original(true)
189 .execute()
190 {
191 Ok(_) => println!("写入成功: {} = {}", tag, value),
192 Err(e) => println!("写入失败: {}", e),
193 }
194
195 Ok(())
196 }
197
198 fn cmd_delete(&self, args: &[&str]) -> Result<()> {
200 let path = args[0];
201 let tag = args[1];
202
203 match self
204 .exiftool
205 .write(path)
206 .delete(tag)
207 .overwrite_original(true)
208 .execute()
209 {
210 Ok(_) => println!("删除标签成功: {}", tag),
211 Err(e) => println!("删除标签失败: {}", e),
212 }
213
214 Ok(())
215 }
216
217 fn cmd_batch(&self, args: &[&str]) -> Result<()> {
219 let paths: Vec<&str> = args.to_vec();
220
221 match self.exiftool.query_batch(&paths).execute() {
222 Ok(results) => {
223 for (path, metadata) in results {
224 println!("\n文件: {}", path.display());
225 if self.verbose {
226 for (key, value) in metadata.iter() {
227 println!(" {}: {}", key, value);
228 }
229 } else {
230 if let Some(make) = metadata.get("Make") {
232 println!(" 制造商: {}", make);
233 }
234 if let Some(model) = metadata.get("Model") {
235 println!(" 型号: {}", model);
236 }
237 if let Some(date) = metadata.get("DateTimeOriginal") {
238 println!(" 拍摄时间: {}", date);
239 }
240 }
241 }
242 }
243 Err(e) => println!("批量读取失败: {}", e),
244 }
245
246 Ok(())
247 }
248
249 fn cmd_list_tags(&self) -> Result<()> {
251 println!("常用标签:");
252 println!(" EXIF: Make, Model, DateTimeOriginal, ImageWidth, ImageHeight");
253 println!(" IPTC: Keywords, Caption-Abstract, City, Country");
254 println!(" XMP: Title, Creator, Description, Rights");
255 println!(" GPS: GPSLatitude, GPSLongitude, GPSAltitude");
256 println!("\n使用 'read <文件> <标签名>' 读取特定标签");
257 Ok(())
258 }
259
260 fn print_help(&self, writer: &mut dyn Write) -> io::Result<()> {
262 writeln!(writer, "\n=== ExifTool REPL 帮助 ===\n")?;
263 writeln!(writer, "命令列表:")?;
264 writeln!(writer, " read, r <路径> [标签] 读取文件元数据")?;
265 writeln!(writer, " write, w <路径> <标签> <值> 写入标签")?;
266 writeln!(writer, " delete, d <路径> <标签> 删除标签")?;
267 writeln!(writer, " batch, b <路径1> ... 批量处理")?;
268 writeln!(writer, " tags, t 列出常用标签")?;
269 writeln!(writer, " verbose, v 切换详细模式")?;
270 writeln!(writer, " version 显示版本")?;
271 writeln!(writer, " help, h 显示帮助")?;
272 writeln!(writer, " quit, exit, q 退出\n")?;
273 Ok(())
274 }
275}
276
277pub fn run_repl(exiftool: ExifTool) -> Result<()> {
279 let shell = ReplShell::new(exiftool);
280 shell.run()
281}