mediac 1.5.2 → 1.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +8 -5
  2. package/cmd/cmd_compress.js +146 -145
  3. package/cmd/cmd_dcim.js +45 -45
  4. package/cmd/cmd_decode.js +24 -16
  5. package/cmd/cmd_moveup.js +97 -96
  6. package/cmd/cmd_prefix.js +159 -267
  7. package/cmd/cmd_remove.js +269 -219
  8. package/cmd/cmd_rename.js +356 -125
  9. package/cmd/cmd_shared.js +191 -77
  10. package/cmd/cmd_zipu.js +540 -150
  11. package/index.js +1 -1
  12. package/labs/cjk_demo.js +38 -30
  13. package/labs/download_urls.js +44 -36
  14. package/{scripts → labs}/file_cli.js +41 -41
  15. package/labs/file_organize.js +41 -32
  16. package/labs/fs_demo.js +15 -7
  17. package/labs/gdh_gpt35.js +95 -0
  18. package/labs/gdh_gpt4.js +94 -0
  19. package/labs/git_date_header_001.js +127 -0
  20. package/labs/git_date_header_002.js +88 -0
  21. package/labs/make_thumbs.js +65 -57
  22. package/labs/merge_dir.js +106 -0
  23. package/labs/merge_dir_cli.js +107 -0
  24. package/{scripts → labs}/path_test.js +3 -3
  25. package/labs/pic_exif_rename.js +15 -7
  26. package/labs/print_unicode.js +79 -0
  27. package/labs/strftime.js +75 -67
  28. package/labs/string_format.js +56 -48
  29. package/{scripts → labs}/test.js +21 -21
  30. package/{scripts → labs}/unicode_test.js +21 -14
  31. package/labs/yargs_demo.js +17 -7
  32. package/{scripts → labs}/zip_test.js +24 -24
  33. package/lib/core.js +143 -25
  34. package/lib/cue-extractor.js +88 -0
  35. package/lib/cue-parse.js +249 -0
  36. package/lib/cue-split.js +100 -0
  37. package/lib/debug.js +75 -75
  38. package/lib/encoding.js +70 -42
  39. package/lib/exif.js +114 -113
  40. package/lib/file.js +178 -123
  41. package/lib/hanzi_common_3500.txt +1 -0
  42. package/lib/hanzi_common_7000.txt +1 -0
  43. package/lib/hanzi_common_japanese.txt +1 -0
  44. package/lib/hanzi_complex.txt +1 -0
  45. package/lib/hanzi_rarely.txt +1 -0
  46. package/lib/helper.js +149 -92
  47. package/lib/notes.txt +12 -0
  48. package/lib/path-merge.js +80 -0
  49. package/lib/tools.js +13 -13
  50. package/lib/unicode.js +76 -43
  51. package/lib/walk.js +7 -7
  52. package/package.json +34 -17
  53. package/scripts/media_cli.js +177 -162
  54. package/cmd/cmd_fixname.js +0 -254
  55. package/lib/messy_hanzi.txt +0 -1
  56. package/lib/unicode_data.js +0 -40
  57. package/lib/unicode_data.json +0 -6
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  MediaCli is a multimedia file processing tool that utilizes ffmpeg and exiftool, among others, to compress/convert/rename/delete/organize media files, including images, videos, and audio.
4
4
 
5
- created at 2021.07
5
+ created at 2021.07, updated at 2024.04.07
6
6
 
7
7
  ## Installation
8
8
 
@@ -41,15 +41,18 @@ Commands:
41
41
  lder [aliases: mu]
42
42
  media_cli.js prefix <input> [output] Rename files by append dir name or str
43
43
  ing [aliases: pf, px]
44
-
45
- Positionals:
46
- input Input folder that contains files [string]
44
+ media_cli.js fixname <input> [output] Fix filenames (fix messy, clean, conve
45
+ rt tc to sc) [aliases: fn, fxn]
46
+ media_cli.js zipu <input> [output] Smart unzip command (auto detect encod
47
+ ing) [aliases: zipunicode]
48
+ media_cli.js decode <strings...> Decode text with messy or invalid char
49
+ s [aliases: dc]
47
50
 
48
51
  Options:
49
52
  --version Show version number [boolean]
50
53
  -h, --help Show help [boolean]
51
54
 
52
- Media Cli: Image/Raw/Video filename processing utilities
55
+ MediaCli is a multimedia file processing tool.
53
56
  Copyright 2021-2025 @ Zhang Xiaoke
54
57
 
55
58
  ```
@@ -1,36 +1,36 @@
1
1
  /*
2
2
  * File: cmd_compress.js
3
- * Created: 2024-03-15 20:34:49
4
- * Modified: 2024-03-23 11:51:09
3
+ * Created: 2024-03-15 20:42:41 +0800
4
+ * Modified: 2024-04-09 22:13:38 +0800
5
5
  * Author: mcxiaoke (github@mcxiaoke.com)
6
6
  * License: Apache License 2.0
7
7
  */
8
8
 
9
9
 
10
- import assert from "assert";
11
- import chalk from 'chalk';
12
- import * as cliProgress from "cli-progress";
13
- import dayjs from "dayjs";
14
- import exif from 'exif-reader';
15
- import fs from 'fs-extra';
16
- import inquirer from "inquirer";
17
- import { cpus } from "os";
18
- import pMap from 'p-map';
19
- import path from "path";
20
- import sharp from "sharp";
21
- import * as log from '../lib/debug.js';
22
- import * as mf from '../lib/file.js';
23
- import * as helper from '../lib/helper.js';
24
- import { compressImage } from "./cmd_shared.js";
25
- export { aliases, builder, command, describe, handler };
10
+ import assert from "assert"
11
+ import chalk from 'chalk'
12
+ import * as cliProgress from "cli-progress"
13
+ import dayjs from "dayjs"
14
+ import exif from 'exif-reader'
15
+ import fs from 'fs-extra'
16
+ import inquirer from "inquirer"
17
+ import { cpus } from "os"
18
+ import pMap from 'p-map'
19
+ import path from "path"
20
+ import sharp from "sharp"
21
+ import * as log from '../lib/debug.js'
22
+ import * as mf from '../lib/file.js'
23
+ import * as helper from '../lib/helper.js'
24
+ import { compressImage } from "./cmd_shared.js"
25
+ export { aliases, builder, command, describe, handler }
26
26
 
27
27
  const command = "compress <input> [output]"
28
28
  const aliases = ["cs", "cps"]
29
29
  const describe = 'Compress input images to target size'
30
30
 
31
- const QUALITY_DEFAULT = 86;
31
+ const QUALITY_DEFAULT = 86
32
32
  const SIZE_DEFAULT = 2048 // in kbytes
33
- const WIDTH_DEFAULT = 6000;
33
+ const WIDTH_DEFAULT = 6000
34
34
 
35
35
  const builder = function addOptions(ya, helpOrVersionSet) {
36
36
  return ya.option("purge", {
@@ -81,28 +81,28 @@ const builder = function addOptions(ya, helpOrVersionSet) {
81
81
  }
82
82
 
83
83
  const handler = async function cmdCompress(argv) {
84
- const testMode = !argv.doit;
85
- const logTag = "cmdCompress";
86
- const root = path.resolve(argv.input);
87
- assert.strictEqual("string", typeof root, "root must be string");
84
+ const testMode = !argv.doit
85
+ const logTag = "cmdCompress"
86
+ const root = path.resolve(argv.input)
87
+ assert.strictEqual("string", typeof root, "root must be string")
88
88
  if (!root || !(await fs.pathExists(root))) {
89
- log.error(logTag, `Invalid Input: '${root}'`);
90
- throw new Error(`Invalid Input: ${root}`);
89
+ log.error(logTag, `Invalid Input: '${root}'`)
90
+ throw new Error(`Invalid Input: ${root}`)
91
91
  }
92
92
  if (!testMode) {
93
- log.fileLog(`Root:${root}`, logTag);
94
- log.fileLog(`Argv:${JSON.stringify(argv)}`, logTag);
93
+ log.fileLog(`Root:${root}`, logTag)
94
+ log.fileLog(`Argv:${JSON.stringify(argv)}`, logTag)
95
95
  }
96
- log.show(logTag, argv);
97
- const override = argv.override || false;
98
- const quality = argv.quality || QUALITY_DEFAULT;
99
- const minFileSize = (argv.size || SIZE_DEFAULT) * 1024;
100
- const maxWidth = argv.width || WIDTH_DEFAULT;
101
- const purgeOnly = argv.purgeOnly || false;
102
- const purgeSource = argv.purge || false;
103
- log.show(`${logTag} input:`, root);
96
+ log.show(logTag, argv)
97
+ const override = argv.override || false
98
+ const quality = argv.quality || QUALITY_DEFAULT
99
+ const minFileSize = (argv.size || SIZE_DEFAULT) * 1024
100
+ const maxWidth = argv.width || WIDTH_DEFAULT
101
+ const purgeOnly = argv.purgeOnly || false
102
+ const purgeSource = argv.purge || false
103
+ log.show(`${logTag} input:`, root)
104
104
 
105
- const RE_THUMB = /Z4K|P4K|M4K|feature|web|thumb$/i;
105
+ const RE_THUMB = /Z4K|P4K|M4K|feature|web|thumb$/i
106
106
  const walkOpts = {
107
107
  needStats: true,
108
108
  entryFilter: (f) =>
@@ -110,17 +110,17 @@ const handler = async function cmdCompress(argv) {
110
110
  && !RE_THUMB.test(f.path)
111
111
  && f.stats.size > minFileSize
112
112
  && helper.isImageFile(f.path)
113
- };
114
- log.showGreen(logTag, `Walking files ...`);
115
- let files = await mf.walk(root, walkOpts);
116
- if (!files || files.length == 0) {
117
- log.showYellow(logTag, "no files found, abort.");
118
- return;
119
113
  }
120
- log.show(logTag, `total ${files.length} files found (all)`);
121
- if (files.length == 0) {
122
- log.showYellow("Nothing to do, abort.");
123
- return;
114
+ log.showGreen(logTag, `Walking files ...`)
115
+ let files = await mf.walk(root, walkOpts)
116
+ if (!files || files.length === 0) {
117
+ log.showYellow(logTag, "no files found, abort.")
118
+ return
119
+ }
120
+ log.show(logTag, `total ${files.length} files found (all)`)
121
+ if (files.length === 0) {
122
+ log.showYellow("Nothing to do, abort.")
123
+ return
124
124
  }
125
125
  const confirmFiles = await inquirer.prompt([
126
126
  {
@@ -129,14 +129,14 @@ const handler = async function cmdCompress(argv) {
129
129
  default: false,
130
130
  message: chalk.bold.green(`Press y to continue processing...`),
131
131
  },
132
- ]);
132
+ ])
133
133
  if (!confirmFiles.yes) {
134
- log.showYellow("Will do nothing, aborted by user.");
135
- return;
134
+ log.showYellow("Will do nothing, aborted by user.")
135
+ return
136
136
  }
137
- const needBar = files.length > 9999 && !log.isVerbose();
138
- log.showGreen(logTag, `preparing compress arguments...`);
139
- let startMs = Date.now();
137
+ const needBar = files.length > 9999 && !log.isVerbose()
138
+ log.showGreen(logTag, `preparing compress arguments...`)
139
+ let startMs = Date.now()
140
140
  const addArgsFunc = async (f, i) => {
141
141
  return {
142
142
  ...f,
@@ -147,44 +147,44 @@ const handler = async function cmdCompress(argv) {
147
147
  maxWidth,
148
148
  }
149
149
  }
150
- files = await Promise.all(files.map(addArgsFunc));
150
+ files = await Promise.all(files.map(addArgsFunc))
151
151
  files.forEach((t, i) => {
152
- t.bar1 = bar1;
153
- t.needBar = needBar;
154
- });
155
- needBar && bar1.start(files.length, 0);
152
+ t.bar1 = bar1
153
+ t.needBar = needBar
154
+ })
155
+ needBar && bar1.start(files.length, 0)
156
156
  let tasks = await pMap(files, preCompress, { concurrency: cpus().length * 4 })
157
- needBar && bar1.update(files.length);
158
- needBar && bar1.stop();
159
- log.info(logTag, "before filter: ", tasks.length);
160
- const total = tasks.length;
161
- tasks = tasks.filter((t) => t?.dst);
162
- const skipped = total - tasks.length;
163
- log.info(logTag, "after filter: ", tasks.length);
157
+ needBar && bar1.update(files.length)
158
+ needBar && bar1.stop()
159
+ log.info(logTag, "before filter: ", tasks.length)
160
+ const total = tasks.length
161
+ tasks = tasks.filter((t) => t?.dst)
162
+ const skipped = total - tasks.length
163
+ log.info(logTag, "after filter: ", tasks.length)
164
164
  if (skipped > 0) {
165
165
  log.showYellow(logTag, `${skipped} thumbs skipped`)
166
166
  }
167
- if (tasks.length == 0) {
168
- log.showYellow("Nothing to do, abort.");
169
- return;
167
+ if (tasks.length === 0) {
168
+ log.showYellow("Nothing to do, abort.")
169
+ return
170
170
  }
171
171
  tasks.forEach((t, i) => {
172
- t.total = tasks.length;
173
- t.index = i;
174
- t.bar1 = null;
175
- t.needBar = false;
176
- });
172
+ t.total = tasks.length
173
+ t.index = i
174
+ t.bar1 = null
175
+ t.needBar = false
176
+ })
177
177
  log.show(logTag, `in ${helper.humanTime(startMs)} tasks:`)
178
178
  tasks.slice(-1).forEach(t => {
179
- log.show(helper._omit(t, "stats", "bar1"));
179
+ log.show(helper._omit(t, "stats", "bar1"))
180
180
  })
181
- log.info(logTag, argv);
181
+ log.info(logTag, argv)
182
182
  testMode && log.showYellow("++++++++++ TEST MODE (DRY RUN) ++++++++++")
183
183
 
184
184
  if (purgeOnly) {
185
185
  log.showYellow("+++++ PURGE ONLY (NO COMPRESS) +++++")
186
- await purgeSrcFiles(tasks);
187
- return;
186
+ await purgeSrcFiles(tasks)
187
+ return
188
188
  }
189
189
  const answer = await inquirer.prompt([
190
190
  {
@@ -195,63 +195,62 @@ const handler = async function cmdCompress(argv) {
195
195
  `Are you sure to compress ${tasks.length} files? \n[Apply to files bigger than ${minFileSize / 1024}K, target long width is ${maxWidth}] \n${purgeSource ? "(Attention: you choose to delete original file!)" : "(Will keep original file)"}`
196
196
  ),
197
197
  },
198
- ]);
198
+ ])
199
199
 
200
200
  if (!answer.yes) {
201
- log.showYellow("Will do nothing, aborted by user.");
202
- return;
201
+ log.showYellow("Will do nothing, aborted by user.")
202
+ return
203
203
  }
204
204
 
205
205
  if (testMode) {
206
206
  log.showYellow(logTag, `[DRY RUN], no thumbs generated.`)
207
207
  } else {
208
- startMs = Date.now();
208
+ startMs = Date.now()
209
209
  log.showGreen(logTag, 'startAt', dayjs().format())
210
- tasks.forEach(t => t.startMs = startMs);
211
- tasks = await pMap(tasks, compressImage, { concurrency: cpus().length / 2 });
212
- const okTasks = tasks.filter(t => t?.done);
213
- const failedTasks = tasks.filter(t => t?.errorFlag && !t.done);
210
+ tasks.forEach(t => t.startMs = startMs)
211
+ tasks = await pMap(tasks, compressImage, { concurrency: cpus().length / 2 })
212
+ const okTasks = tasks.filter(t => t?.done)
213
+ const failedTasks = tasks.filter(t => t?.errorFlag && !t.done)
214
214
  log.showGreen(logTag, `${okTasks.length} files compressed in ${helper.humanTime(startMs)}`)
215
215
  log.showGreen(logTag, 'endAt', dayjs().format(), helper.humanTime(startMs))
216
216
  if (failedTasks.length > 0) {
217
- log.showYellow(logTag, `${okTasks.length} tasks are failed`);
218
- const failedContent = failedTasks.map(t => t.src).join('\n');
219
- const failedLogFile = path.join(root, `mediac_compress_failed_list_${dayjs().format("YYYYMMDDHHmmss")}.txt`);
220
- await fs.writeFile(failedLogFile, failedContent);
221
- const clickablePath = failedLogFile.split(path.sep).join("/");
222
- log.showYellow(logTag, `failed filenames: file:///${clickablePath}`);
217
+ log.showYellow(logTag, `${okTasks.length} tasks are failed`)
218
+ const failedContent = failedTasks.map(t => t.src).join('\n')
219
+ const failedLogFile = path.join(root, `mediac_compress_failed_list_${dayjs().format("YYYYMMDDHHmmss")}.txt`)
220
+ await fs.writeFile(failedLogFile, failedContent)
221
+ const clickablePath = failedLogFile.split(path.sep).join("/")
222
+ log.showYellow(logTag, `failed filenames: file:///${clickablePath}`)
223
223
  }
224
224
  if (purgeSource) {
225
- await purgeSrcFiles(tasks);
225
+ await purgeSrcFiles(tasks)
226
226
  }
227
227
  }
228
228
  }
229
229
 
230
230
 
231
- let compressLastUpdatedAt = 0;
232
- const bar1 = new cliProgress.SingleBar({ etaBuffer: 300 }, cliProgress.Presets.shades_classic);
231
+ let compressLastUpdatedAt = 0
232
+ const bar1 = new cliProgress.SingleBar({ etaBuffer: 300 }, cliProgress.Presets.shades_classic)
233
233
  // 文心一言注释 20231206
234
234
  // 准备压缩图片的参数,并进行相应的处理
235
235
  async function preCompress(f, options = {}) {
236
236
  const logTag = 'PreCompress'
237
- // log.debug("prepareCompressArgs options:", options); // 打印日志,显示选项参数
238
- const maxWidth = options.maxWidth || 6000; // 获取最大宽度限制,默认为6000
239
- let fileSrc = path.resolve(f.path); // 解析源文件路径
240
- const [dir, base, ext] = helper.pathSplit(fileSrc); // 将路径分解为目录、基本名和扩展名
241
- const fileDstTmp = path.join(dir, `_TMP_${base}.jpg`);
242
- let fileDst = path.join(dir, `${base}_Z4K.jpg`); // 构建目标文件路径,添加压缩后的文件名后缀
243
- fileSrc = path.resolve(fileSrc); // 解析源文件路径(再次确认)
244
- fileDst = path.resolve(fileDst); // 解析目标文件路径(再次确认)
237
+ const maxWidth = f.maxWidth || 6000 // 获取最大宽度限制,默认为6000
238
+ let fileSrc = path.resolve(f.path) // 解析源文件路径
239
+ const [dir, base, ext] = helper.pathSplit(fileSrc) // 将路径分解为目录、基本名和扩展名
240
+ const fileDstTmp = path.join(dir, `_TMP_${base}.jpg`)
241
+ let fileDst = path.join(dir, `${base}_Z4K.jpg`) // 构建目标文件路径,添加压缩后的文件名后缀
242
+ fileSrc = path.resolve(fileSrc) // 解析源文件路径(再次确认)
243
+ fileDst = path.resolve(fileDst) // 解析目标文件路径(再次确认)
245
244
 
246
- const timeNow = Date.now();
245
+ const timeNow = Date.now()
247
246
  if (timeNow - compressLastUpdatedAt > 2 * 1000) {
248
- f.needBar && f.bar1.update(f.index);
249
- compressLastUpdatedAt = timeNow;
247
+ f.needBar && f.bar1.update(f.index)
248
+ compressLastUpdatedAt = timeNow
250
249
  }
251
250
 
252
251
  if (await fs.pathExists(fileDst)) {
253
252
  // 如果目标文件已存在,则进行相应的处理
254
- log.info(logTag, "exists:", fileDst);
253
+ log.info(logTag, "exists:", fileDst)
255
254
  return {
256
255
  ...f,
257
256
  width: 0,
@@ -262,18 +261,18 @@ async function preCompress(f, options = {}) {
262
261
  dstExists: true,
263
262
  shouldSkip: true,
264
263
  skipReason: 'DST EXISTS',
265
- };
264
+ }
266
265
  }
267
266
  try {
268
- const st = await fs.stat(fileSrc);
269
- const m = await sharp(fileSrc).metadata();
267
+ const st = await fs.stat(fileSrc)
268
+ const m = await sharp(fileSrc).metadata()
270
269
  try {
271
270
  if (m?.exif) {
272
- const md = exif(m.exif)?.Image;
271
+ const md = exif(m.exif)?.Image
273
272
  if (md && (md.Copyright?.includes("mediac")
274
273
  || md.Software?.includes("mediac")
275
274
  || md.Artist?.includes("mediac"))) {
276
- log.info(logTag, "skip:", fileDst);
275
+ log.info(logTag, "skip:", fileDst)
277
276
  return {
278
277
  ...f,
279
278
  width: 0,
@@ -282,49 +281,51 @@ async function preCompress(f, options = {}) {
282
281
  dst: fileDst,
283
282
  shouldSkip: true,
284
283
  skipReason: 'MEDIAC MAKE',
285
- };
284
+ }
286
285
  }
287
286
  }
288
287
  } catch (error) {
289
- log.warn(logTag, "exif", error.message, fileSrc);
290
- log.fileLog(`ExifErr: <${fileSrc}> ${error.message}`, logTag);
288
+ log.warn(logTag, "exif", error.message, fileSrc)
289
+ log.fileLog(`ExifErr: <${fileSrc}> ${error.message}`, logTag)
291
290
  }
292
291
 
293
- const nw =
294
- m.width > m.height ? maxWidth : Math.round((maxWidth * m.width) / m.height);
295
- const nh = Math.round((nw * m.height) / m.width);
292
+ const newWidth =
293
+ m.width > m.height ? maxWidth : Math.round((maxWidth * m.width) / m.height)
294
+ const newHeight = Math.round((newWidth * m.height) / m.width)
296
295
 
297
- const dw = nw > m.width ? m.width : nw;
298
- const dh = nh > m.height ? m.height : nh;
299
- if (f.total < 9999) {
296
+ const dstWidth = newWidth > m.width ? m.width : newWidth
297
+ const dstHeight = newHeight > m.height ? m.height : newHeight
298
+ if (f.total < 1000 || f.index > f.total - 1000) {
300
299
  log.show(logTag, `${f.index}/${f.total}`,
301
- helper.pathShort(fileSrc, 32),
302
- `${m.width}x${m.height}=>${dw}x${dh} ${helper.humanSize(st.size)}`
303
- );
300
+ helper.pathShort(fileSrc),
301
+ `${m.width}x${m.height}=>${dstWidth}x${dstHeight} ${helper.humanSize(st.size)}`
302
+ )
304
303
  }
305
304
  log.fileLog(`Pre: ${f.index}/${f.total} <${fileSrc}> ` +
306
- `${dw}x${dh}) ${m.format} ${helper.humanSize(st.size)}`, logTag);
305
+ `${dstWidth}x${dstHeight}) ${m.format} ${helper.humanSize(st.size)}`, logTag)
307
306
  return {
308
307
  ...f,
309
- width: dw,
310
- height: dh,
308
+ srcWidth: m.width,
309
+ srcHeight: m.height,
310
+ width: dstWidth,
311
+ height: dstHeight,
311
312
  src: fileSrc,
312
313
  dst: fileDst,
313
314
  tmpDst: fileDstTmp,
314
- };
315
+ }
315
316
  } catch (error) {
316
- log.warn(logTag, "sharp", error.message, fileSrc);
317
- log.fileLog(`SharpErr: ${f.index} <${fileSrc}> sharp:${error.message}`, logTag);
317
+ log.warn(logTag, "sharp", error.message, fileSrc)
318
+ log.fileLog(`SharpErr: ${f.index} <${fileSrc}> sharp:${error.message}`, logTag)
318
319
  }
319
320
  }
320
321
 
321
322
 
322
323
  async function purgeSrcFiles(results) {
323
- const logTag = "Purge";
324
- const toDelete = results.filter(t => t?.src && t.dstExists && t.dst);
325
- const total = toDelete?.length ?? 0;
324
+ const logTag = "Purge"
325
+ const toDelete = results.filter(t => t?.src && t.dstExists && t.dst)
326
+ const total = toDelete?.length ?? 0
326
327
  if (total <= 0) {
327
- return;
328
+ return
328
329
  }
329
330
  const answer = await inquirer.prompt([
330
331
  {
@@ -335,26 +336,26 @@ async function purgeSrcFiles(results) {
335
336
  `Are you sure to delete ${total} original files?`
336
337
  ),
337
338
  },
338
- ]);
339
+ ])
339
340
  if (!answer.yes) {
340
- log.showYellow("Will do nothing, aborted by user.");
341
- return;
341
+ log.showYellow("Will do nothing, aborted by user.")
342
+ return
342
343
  }
343
344
  const deletecFunc = async (td, index) => {
344
- const srcExists = await fs.pathExists(td.src);
345
- const dstExists = await fs.pathExists(td.dst);
345
+ const srcExists = await fs.pathExists(td.src)
346
+ const dstExists = await fs.pathExists(td.dst)
346
347
  log.info(logTag, `Check S=${srcExists} D=${dstExists} ${helper.pathShort(td.src)}`)
347
348
  // 确认文件存在,确保不会误删除
348
349
  if (!(srcExists && dstExists)) {
349
- return;
350
+ return
350
351
  }
351
- await fs.pathExists(td.tmpDst) && await fs.remove(td.tmpDst);
352
- await helper.safeRemove(td.src);
353
- log.showYellow(logTag, `SafeDel: ${index}/${total} ${helper.pathShort(td.src)}`);
354
- log.fileLog(`SafeDel: <${td.dst}>`, logTag);
355
- return td.src;
352
+ await fs.pathExists(td.tmpDst) && await fs.remove(td.tmpDst)
353
+ await helper.safeRemove(td.src)
354
+ log.showYellow(logTag, `SafeDel: ${index}/${total} ${helper.pathShort(td.src)}`)
355
+ log.fileLog(`SafeDel: <${td.src}>`, logTag)
356
+ return td.src
356
357
  }
357
358
  const deleted = await pMap(toDelete, deletecFunc, { concurrency: cpus().length * 8 })
358
- log.showCyan(logTag, `${deleted.filter(Boolean).length} files are safely removed`);
359
+ log.showCyan(logTag, `${deleted.filter(Boolean).length} files are safely removed`)
359
360
 
360
361
  }
package/cmd/cmd_dcim.js CHANGED
@@ -1,25 +1,25 @@
1
1
  /*
2
2
  * File: cmd_dcim.js
3
- * Created: 2024-03-16 21:04:01
4
- * Modified: 2024-03-23 11:51:18
3
+ * Created: 2024-03-20 13:43:17 +0800
4
+ * Modified: 2024-04-09 22:13:39 +0800
5
5
  * Author: mcxiaoke (github@mcxiaoke.com)
6
6
  * License: Apache License 2.0
7
7
  */
8
8
 
9
- import chalk from 'chalk';
10
- import fs from 'fs-extra';
11
- import inquirer from "inquirer";
12
- import path from "path";
9
+ import chalk from 'chalk'
10
+ import fs from 'fs-extra'
11
+ import inquirer from "inquirer"
12
+ import path from "path"
13
13
 
14
- import { renameFiles } from "./cmd_shared.js";
14
+ import { renameFiles } from "./cmd_shared.js"
15
15
 
16
- import * as log from '../lib/debug.js';
17
- import * as exif from '../lib/exif.js';
18
- import * as helper from '../lib/helper.js';
16
+ import * as log from '../lib/debug.js'
17
+ import * as exif from '../lib/exif.js'
18
+ import * as helper from '../lib/helper.js'
19
19
 
20
- const LOG_TAG = "DcimR";
20
+ const LOG_TAG = "DcimR"
21
21
 
22
- export { aliases, builder, command, describe, handler };
22
+ export { aliases, builder, command, describe, handler }
23
23
 
24
24
  const command = "dcimr <input> [options]"
25
25
  const aliases = ["dm", "dcim"]
@@ -72,20 +72,20 @@ const builder = function addOptions(ya, helpOrVersionSet) {
72
72
 
73
73
 
74
74
  const handler = async function cmdRename(argv) {
75
- log.show(LOG_TAG, argv);
76
- const root = path.resolve(argv.input);
75
+ log.show(LOG_TAG, argv)
76
+ const root = path.resolve(argv.input)
77
77
  if (!(await fs.pathExists(root))) {
78
- log.error(`Invalid Input: '${root}'`);
78
+ log.error(`Invalid Input: '${root}'`)
79
79
  throw new Error(`Invalid Input: '${root}'`)
80
80
  }
81
- const testMode = !argv.doit;
82
- const fastMode = argv.fast || false;
81
+ const testMode = !argv.doit
82
+ const fastMode = argv.fast || false
83
83
  // action: rename media file by exif date
84
- const startMs = Date.now();
85
- log.show(LOG_TAG, `Input: ${root}`);
86
- let files = await exif.listMedia(root);
87
- const fileCount = files.length;
88
- log.show(LOG_TAG, `Total ${files.length} media files found`);
84
+ const startMs = Date.now()
85
+ log.show(LOG_TAG, `Input: ${root}`)
86
+ let files = await exif.listMedia(root)
87
+ const fileCount = files.length
88
+ log.show(LOG_TAG, `Total ${files.length} media files found`)
89
89
 
90
90
  const confirmFiles = await inquirer.prompt([
91
91
  {
@@ -94,55 +94,55 @@ const handler = async function cmdRename(argv) {
94
94
  default: false,
95
95
  message: chalk.bold.green(`Press y to continue processing...`),
96
96
  },
97
- ]);
97
+ ])
98
98
  if (!confirmFiles.yes) {
99
- log.showYellow("Will do nothing, aborted by user.");
100
- return;
99
+ log.showYellow("Will do nothing, aborted by user.")
100
+ return
101
101
  }
102
- log.show(LOG_TAG, `Processing files, reading EXIF data...`);
103
- files = await exif.parseFiles(files, { fastMode });
102
+ log.show(LOG_TAG, `Processing files, reading EXIF data...`)
103
+ files = await exif.parseFiles(files, { fastMode })
104
104
  log.show(
105
105
  LOG_TAG,
106
106
  `Total ${files.length} media files parsed`,
107
107
  fastMode ? "(FastMode)" : ""
108
- );
109
- files = exif.buildNames(files);
110
- const [validFiles, skippedBySize, skippedByDate] = exif.checkFiles(files);
111
- files = validFiles;
108
+ )
109
+ files = exif.buildNames(files)
110
+ const [validFiles, skippedBySize, skippedByDate] = exif.checkFiles(files)
111
+ files = validFiles
112
112
  if (fileCount - files.length > 0) {
113
113
  log.warn(
114
114
  LOG_TAG,
115
115
  `Total ${fileCount - files.length} media files skipped`
116
- );
116
+ )
117
117
  }
118
118
  log.show(
119
119
  LOG_TAG,
120
120
  `Total ${fileCount} files processed in ${helper.humanTime(startMs)}`,
121
121
  fastMode ? "(FastMode)" : ""
122
- );
122
+ )
123
123
  if (skippedBySize.length > 0) {
124
124
  log.showYellow(
125
125
  LOG_TAG,
126
126
  `Total ${skippedBySize.length} media files are skipped by size`
127
- );
127
+ )
128
128
  }
129
129
  if (skippedByDate.length > 0) {
130
130
  log.showYellow(
131
131
  LOG_TAG,
132
132
  `Total ${skippedByDate.length} media files are skipped by date`
133
- );
133
+ )
134
134
  }
135
- if (files.length == 0) {
136
- log.showYellow(LOG_TAG, "Nothing to do, exit now.");
137
- return;
135
+ if (files.length === 0) {
136
+ log.showYellow(LOG_TAG, "Nothing to do, exit now.")
137
+ return
138
138
  }
139
139
  log.show(
140
140
  LOG_TAG,
141
141
  `Total ${files.length} media files ready to rename by exif`,
142
142
  fastMode ? "(FastMode)" : ""
143
- );
143
+ )
144
144
  log.show(LOG_TAG, `task sample:`, files.slice(-2))
145
- log.info(LOG_TAG, argv);
145
+ log.info(LOG_TAG, argv)
146
146
  testMode && log.showYellow("++++++++++ TEST MODE (DRY RUN) ++++++++++")
147
147
  const answer = await inquirer.prompt([
148
148
  {
@@ -154,16 +154,16 @@ const handler = async function cmdRename(argv) {
154
154
  (fastMode ? " (FastMode)" : "")
155
155
  ),
156
156
  },
157
- ]);
157
+ ])
158
158
  if (answer.yes) {
159
159
  if (testMode) {
160
- log.showYellow(LOG_TAG, `All ${files.length} files, NO file renamed in TEST MODE.`);
160
+ log.showYellow(LOG_TAG, `All ${files.length} files, NO file renamed in TEST MODE.`)
161
161
  }
162
162
  else {
163
- const results = await renameFiles(files);
164
- log.showGreen(LOG_TAG, `All ${results.length} file were renamed.`,);
163
+ const results = await renameFiles(files, false)
164
+ log.showGreen(LOG_TAG, `All ${results.length} file were renamed.`,)
165
165
  }
166
166
  } else {
167
- log.showYellow(LOG_TAG, "Will do nothing, aborted by user.");
167
+ log.showYellow(LOG_TAG, "Will do nothing, aborted by user.")
168
168
  }
169
169
  }