Commit 32500dae by kk

初建项目

parent 0d52c19c
...@@ -41,3 +41,21 @@ ExifToolResult r = exiftool.addSubject("333.jpg", "contains-synthetic-performer" ...@@ -41,3 +41,21 @@ ExifToolResult r = exiftool.addSubject("333.jpg", "contains-synthetic-performer"
ExifToolResult v = exiftool.execute("-ver"); ExifToolResult v = exiftool.execute("-ver");
if (!v.isSuccess()) System.err.println(v.stderr()); if (!v.isSuccess()) System.err.println(v.stderr());
``` ```
## 批量处理
给一批文件打**相同**标签/参数,只启动**一个** exiftool 进程(exiftool 冷启动慢,批量务必走这里,不要循环调 `addSubject`):
```java
List<String> files = Arrays.asList("a.jpg", "b.jpg", "c.jpg");
// 便捷:批量追加同一个 Subject
ExifToolResult r = exiftool.addSubjectBatch(files, "contains-synthetic-performer");
// 通用:对每个文件应用相同选项
ExifToolResult r2 = exiftool.executeBatch(
Arrays.asList("-overwrite_original", "-XMP-dc:Subject+=foo"),
files);
```
文件数量大导致命令行超长时,内部自动改用 exiftool 的 `-@ 参数文件`(临时文件, UTF-8),规避 Windows/Linux 命令行长度上限,调用方无感知。返回单个 `ExifToolResult`,任一文件出错时 `isSuccess()` 为 false、`stderr()` 含逐文件错误。
...@@ -42,6 +42,9 @@ public class ExifTool { ...@@ -42,6 +42,9 @@ public class ExifTool {
private static final String RESOURCE_ROOT = "exiftool"; private static final String RESOURCE_ROOT = "exiftool";
/** 批量参数拼成命令行的长度阈值,超过则改用 -@ 参数文件,规避 OS 命令行长度上限。 */
private static final int ARG_LINE_THRESHOLD = 30000;
/** 缓存解析出的可执行文件路径,懒加载,双检锁保证线程安全。 */ /** 缓存解析出的可执行文件路径,懒加载,双检锁保证线程安全。 */
private static volatile Path resolvedExecutable; private static volatile Path resolvedExecutable;
...@@ -112,6 +115,84 @@ public class ExifTool { ...@@ -112,6 +115,84 @@ public class ExifTool {
return Arrays.asList("-overwrite_original", "-XMP-dc:Subject+=" + value, file); return Arrays.asList("-overwrite_original", "-XMP-dc:Subject+=" + value, file);
} }
// ----- 批量处理(方案 A:一条命令处理多文件,仅 1 个进程)-----
/**
* 批量执行:对一批文件应用<b>相同</b>的 exiftool 选项,单个进程处理全部。
*
* <p>参数量小时直接走命令行;拼接长度超过 {@link #ARG_LINE_THRESHOLD} 时
* 自动改用 exiftool 的 {@code -@ 参数文件}(临时文件, UTF-8),规避
* Windows/Linux 的命令行长度上限。
*
* @param options 应用到每个文件的选项,如 {@code ["-overwrite_original", "-XMP-dc:Subject+=foo"]}
* @param files 文件路径列表,不能为空
* @return 单次执行结果;任一文件出错时 exitCode 非零,stderr 含逐文件错误信息
*/
public ExifToolResult executeBatch(List<String> options, List<String> files) {
List<String> combined = combineBatch(options, files);
long approxLen = 0;
for (String s : combined) {
approxLen += s.length() + 3; // 预留引号/分隔符
}
if (approxLen > ARG_LINE_THRESHOLD) {
return executeViaArgFile(combined);
}
return execute(combined);
}
/**
* 便捷方法:给一批文件的 XMP-dc:Subject 追加<b>同一个</b>值,单进程完成。
* 等价于 {@code exiftool -overwrite_original -XMP-dc:Subject+=<value> f1 f2 ...}。
*/
public ExifToolResult addSubjectBatch(List<String> files, String value) {
return executeBatch(
Arrays.asList("-overwrite_original", "-XMP-dc:Subject+=" + value),
files);
}
/** 合并选项与文件为一个参数列表(抽出以便单测);files 为空时抛异常。 */
static List<String> combineBatch(List<String> options, List<String> files) {
if (files == null || files.isEmpty()) {
throw new IllegalArgumentException("批量文件列表不能为空");
}
List<String> combined = new ArrayList<String>();
if (options != null) {
combined.addAll(options);
}
combined.addAll(files);
return combined;
}
/** 把参数写入临时 -@ 参数文件后执行,用完删除。 */
private ExifToolResult executeViaArgFile(List<String> args) {
Path argFile = null;
try {
argFile = Files.createTempFile("exiftool-args-", ".txt");
writeArgFile(argFile, args);
return execute(Arrays.asList("-@", argFile.toString()));
} catch (IOException e) {
throw new ExifToolException("写入 exiftool 参数文件失败", e);
} finally {
if (argFile != null) {
try {
Files.deleteIfExists(argFile);
} catch (IOException ignored) {
// 临时文件删除失败不影响结果。
}
}
}
}
/** 每个参数一行写入文件(UTF-8),供 exiftool -@ 读取。 */
static void writeArgFile(Path file, List<String> args) throws IOException {
StringBuilder sb = new StringBuilder();
for (String a : args) {
sb.append(a).append('\n');
}
Files.write(file, sb.toString().getBytes(StandardCharsets.UTF_8));
}
// ----- 可执行文件解析与释放 ----- // ----- 可执行文件解析与释放 -----
/** 返回(必要时先释放并缓存)当前平台的 exiftool 可执行文件路径。 */ /** 返回(必要时先释放并缓存)当前平台的 exiftool 可执行文件路径。 */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment