mediasnacks 0.25.0 → 0.27.0
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.
- package/README.md +1 -1
- package/index.js +22 -0
- package/package.json +14 -6
- package/src/avif.js +18 -27
- package/src/cli.js +45 -39
- package/src/countframes.js +6 -15
- package/src/detectdups.js +10 -21
- package/src/dlaudio.js +38 -0
- package/src/dlvideo.js +32 -0
- package/src/dropdups.js +2 -10
- package/src/edgespic.js +6 -17
- package/src/frameseq.js +10 -16
- package/src/hev1tohvc1.js +11 -18
- package/src/moov2front.js +12 -21
- package/src/{random.js → openrand.js} +8 -10
- package/src/play.js +2 -8
- package/src/png.js +34 -0
- package/src/prores.js +5 -11
- package/src/qdir.js +1 -10
- package/src/resize.js +23 -28
- package/src/seqcheck.js +16 -18
- package/src/sqcrop.js +17 -25
- package/src/ssim.js +1 -9
- package/src/unemoji.js +79 -0
- package/src/utils/fs-utils.js +1 -1
- package/src/utils/parseOptions.js +1 -0
- package/src/vsplit.js +2 -8
- package/src/vtrim.js +7 -10
- package/src/dlaudio.sh +0 -4
- package/src/dlvideo.sh +0 -4
- package/src/png.sh +0 -20
- package/src/unemoji.sh +0 -30
package/src/moov2front.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
|
|
3
2
|
import { uniqueFilenameFor, overwrite } from './utils/fs-utils.js'
|
|
4
3
|
import { parseOptions } from './utils/parseOptions.js'
|
|
@@ -20,7 +19,7 @@ NOTES
|
|
|
20
19
|
`.trim()
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
async function main() {
|
|
22
|
+
export default async function main() {
|
|
24
23
|
await assertUserHasFFmpeg()
|
|
25
24
|
|
|
26
25
|
const { values, files } = await parseOptions({
|
|
@@ -32,25 +31,23 @@ async function main() {
|
|
|
32
31
|
return
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
if (!files.length)
|
|
36
|
-
throw new Error(HELP)
|
|
34
|
+
if (!files.length) throw new Error('Missing input file(s)')
|
|
37
35
|
|
|
38
36
|
console.log('Optimizing video for progressive download…')
|
|
39
37
|
for (const file of files)
|
|
40
|
-
|
|
38
|
+
try {
|
|
39
|
+
await moov2front(file)
|
|
40
|
+
console.log(file)
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error(err?.message || err)
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
async function moov2front(file) {
|
|
44
|
-
if (!/\.(mp4|mov)$/i.test(file)) {
|
|
45
|
-
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
if (await moovIsBeforeMdat(file)) {
|
|
49
|
-
console.log('(skipped: no changes needed)', file)
|
|
50
|
-
return
|
|
51
|
-
}
|
|
47
|
+
export async function moov2front(file) {
|
|
48
|
+
if (!/\.(mp4|mov)$/i.test(file)) throw new Error(`not mp4/mov. ${file}`)
|
|
49
|
+
if (await moovIsBeforeMdat(file)) throw new Error(`no changes needed. ${file}`)
|
|
52
50
|
|
|
53
|
-
console.log(file)
|
|
54
51
|
const tmp = uniqueFilenameFor(file)
|
|
55
52
|
await ffmpeg([
|
|
56
53
|
'-hide_banner',
|
|
@@ -71,9 +68,3 @@ async function moovIsBeforeMdat(file) {
|
|
|
71
68
|
const firstMatchedAtom = stderr.match(/type:'(moov|mdat)'/)?.[1]
|
|
72
69
|
return firstMatchedAtom === 'moov'
|
|
73
70
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
main().catch(err => {
|
|
77
|
-
console.error(err.message)
|
|
78
|
-
process.exit(1)
|
|
79
|
-
})
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { join } from 'node:path'
|
|
3
2
|
import { spawn } from 'node:child_process'
|
|
4
3
|
import { readdirSync } from 'node:fs'
|
|
@@ -7,13 +6,13 @@ import { parseOptions } from './utils/parseOptions.js'
|
|
|
7
6
|
|
|
8
7
|
const HELP = `
|
|
9
8
|
SYNOPSIS
|
|
10
|
-
mediasnacks
|
|
9
|
+
mediasnacks openrand [-r | --recursive]
|
|
11
10
|
|
|
12
11
|
DESCRIPTION
|
|
13
12
|
Opens a random file in the current working directory
|
|
14
13
|
`.trim()
|
|
15
14
|
|
|
16
|
-
async function main() {
|
|
15
|
+
export default async function main() {
|
|
17
16
|
if (process.platform !== 'darwin')
|
|
18
17
|
throw new Error('Error: This command is only supported on macOS.')
|
|
19
18
|
|
|
@@ -27,17 +26,16 @@ async function main() {
|
|
|
27
26
|
return
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
openrand('.', values.recursive)
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
function
|
|
32
|
+
export function openrand(dir = '.', recursive = false) {
|
|
33
|
+
spawn('open', [pickRandomFile(dir, recursive)])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function pickRandomFile(dir = '.', recursive = false) {
|
|
34
37
|
const files = readdirSync(dir, { withFileTypes: true, recursive })
|
|
35
38
|
.filter(entry => !entry.name.startsWith('.') && entry.isFile())
|
|
36
39
|
.map(entry => join(entry.parentPath, entry.name))
|
|
37
40
|
return files[Math.floor(Math.random() * files.length)]
|
|
38
41
|
}
|
|
39
|
-
|
|
40
|
-
main().catch(err => {
|
|
41
|
-
console.error(err.message)
|
|
42
|
-
process.exit(1)
|
|
43
|
-
})
|
package/src/play.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { spawn } from 'node:child_process'
|
|
3
2
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
3
|
import { findFiles } from './utils/fs-utils.js'
|
|
@@ -17,7 +16,7 @@ EXAMPLE
|
|
|
17
16
|
`.trim()
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
async function main() {
|
|
19
|
+
export default async function main() {
|
|
21
20
|
const { values, positionals } = await parseOptions({
|
|
22
21
|
recursive: { short: 'r', type: 'boolean', default: true },
|
|
23
22
|
help: { short: 'h', type: 'boolean' }
|
|
@@ -41,7 +40,7 @@ async function main() {
|
|
|
41
40
|
play(files)
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
function play(files) {
|
|
43
|
+
export function play(files) {
|
|
45
44
|
const mpv = spawn('mpv', ['--playlist=-'], {
|
|
46
45
|
detached: true,
|
|
47
46
|
stdio: ['pipe', 'ignore', 'ignore']
|
|
@@ -57,8 +56,3 @@ function play(files) {
|
|
|
57
56
|
process.exit(1)
|
|
58
57
|
})
|
|
59
58
|
}
|
|
60
|
-
|
|
61
|
-
main().catch(err => {
|
|
62
|
-
console.error(err.message)
|
|
63
|
-
process.exit(1)
|
|
64
|
-
})
|
package/src/png.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { parseOptions } from './utils/parseOptions.js'
|
|
2
|
+
import { run } from './utils/subprocess.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const HELP = `
|
|
6
|
+
SYNOPSIS
|
|
7
|
+
mediasnacks png <img1> [img2 ...]
|
|
8
|
+
|
|
9
|
+
DESCRIPTION
|
|
10
|
+
Losslessly optimizes PNG images with oxipng at max level.
|
|
11
|
+
|
|
12
|
+
EXAMPLE
|
|
13
|
+
mediasnacks png *.png
|
|
14
|
+
`.trim()
|
|
15
|
+
|
|
16
|
+
export default async function main() {
|
|
17
|
+
const { values, positionals } = await parseOptions({
|
|
18
|
+
help: { short: 'h', type: 'boolean' }
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
if (values.help || !positionals[0]) {
|
|
22
|
+
console.log(HELP)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await png(...positionals)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function png(...images) {
|
|
30
|
+
await run('oxipng', [
|
|
31
|
+
'--opt', 'max',
|
|
32
|
+
...images
|
|
33
|
+
])
|
|
34
|
+
}
|
package/src/prores.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { resolve, parse, join } from 'node:path'
|
|
3
2
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
3
|
import { assertUserHasFFmpeg, run } from './utils/subprocess.js'
|
|
5
4
|
|
|
6
5
|
export const ProresProfiles = new class {
|
|
7
|
-
|
|
6
|
+
// https://github.com/oyvindln/vhs-decode/wiki/ProRes-The-Definitive-FFmpeg-Guide#profiles-can-be-the-following
|
|
8
7
|
profiles = {
|
|
9
8
|
// 10-bit color depth
|
|
10
9
|
0: '422 Proxy',
|
|
@@ -50,7 +49,7 @@ EXAMPLES
|
|
|
50
49
|
`.trim()
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
async function main() {
|
|
52
|
+
export default async function main() {
|
|
54
53
|
await assertUserHasFFmpeg()
|
|
55
54
|
|
|
56
55
|
const { values, files } = await parseOptions({
|
|
@@ -75,10 +74,11 @@ async function main() {
|
|
|
75
74
|
const { name, dir } = parse(video)
|
|
76
75
|
const output = join(dir, `${name}.prores.mov`)
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
const { profile, start, end } = values
|
|
78
|
+
await prores({ video, profile, start, end, output })
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
async function prores(video, start, end,
|
|
81
|
+
export async function prores({ video, profile, start, end, output }) {
|
|
82
82
|
await run('ffmpeg', [
|
|
83
83
|
'-v', 'error',
|
|
84
84
|
'-stats',
|
|
@@ -89,9 +89,3 @@ async function prores(video, start, end, profile, output) {
|
|
|
89
89
|
output
|
|
90
90
|
].flat())
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
if (import.meta.main)
|
|
94
|
-
main().catch(err => {
|
|
95
|
-
console.error(err.message || err)
|
|
96
|
-
process.exit(1)
|
|
97
|
-
})
|
package/src/qdir.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import { join } from 'node:path'
|
|
4
2
|
import { spawn } from 'node:child_process'
|
|
5
3
|
import { readdir, writeFile, unlink, rename } from 'node:fs/promises'
|
|
@@ -30,7 +28,7 @@ function newExt(exitCode) {
|
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
|
|
33
|
-
async function main() {
|
|
31
|
+
export default async function main() {
|
|
34
32
|
const { values, positionals } = await parseOptions({
|
|
35
33
|
help: { short: 'h', type: 'boolean' }
|
|
36
34
|
})
|
|
@@ -98,10 +96,3 @@ async function runShell(scriptPath) {
|
|
|
98
96
|
function sleep(ms) {
|
|
99
97
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
100
98
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (import.meta.main)
|
|
104
|
-
main().catch(err => {
|
|
105
|
-
console.error(err.message || err)
|
|
106
|
-
process.exit(1)
|
|
107
|
-
})
|
package/src/resize.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { join } from 'node:path'
|
|
3
2
|
import { rename } from 'node:fs/promises'
|
|
4
3
|
|
|
@@ -10,7 +9,7 @@ import { videoAttrs } from './utils/videoAttrs.js'
|
|
|
10
9
|
|
|
11
10
|
const HELP = `
|
|
12
11
|
SYNOPSIS
|
|
13
|
-
mediasnacks resize [--width=<num>] [--height=<num>] [-y | --overwrite] [--
|
|
12
|
+
mediasnacks resize [--width=<num>] [--height=<num>] [-y | --overwrite] [--outdir=<dir>] <files>
|
|
14
13
|
|
|
15
14
|
DESCRIPTION
|
|
16
15
|
Resizes videos and images. The aspect ratio is preserved when only one dimension is specified.
|
|
@@ -20,7 +19,7 @@ EXAMPLES
|
|
|
20
19
|
mediasnacks resize -y --width 480 'dir-a/**/*.png' 'dir-b/**/*.mp4'
|
|
21
20
|
|
|
22
21
|
Output directory (-o)
|
|
23
|
-
mediasnacks resize --height 240 --
|
|
22
|
+
mediasnacks resize --height 240 --outdir /tmp/out video.mov
|
|
24
23
|
|
|
25
24
|
OPTIONS
|
|
26
25
|
--width and --height are -2 by default:
|
|
@@ -29,13 +28,13 @@ OPTIONS
|
|
|
29
28
|
`.trim()
|
|
30
29
|
|
|
31
30
|
|
|
32
|
-
async function main() {
|
|
31
|
+
export default async function main() {
|
|
33
32
|
await assertUserHasFFmpeg()
|
|
34
33
|
|
|
35
34
|
const { values, files } = await parseOptions({
|
|
36
35
|
width: { type: 'string', default: '-2' },
|
|
37
36
|
height: { type: 'string', default: '-2' },
|
|
38
|
-
|
|
37
|
+
outdir: { type: 'string', default: '' },
|
|
39
38
|
overwrite: { short: 'y', type: 'boolean' },
|
|
40
39
|
help: { short: 'h', type: 'boolean' },
|
|
41
40
|
})
|
|
@@ -57,31 +56,33 @@ async function main() {
|
|
|
57
56
|
|
|
58
57
|
console.log('Resizing…')
|
|
59
58
|
for (const file of files)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
try {
|
|
60
|
+
await resize({
|
|
61
|
+
file,
|
|
62
|
+
outFile: join(values.outdir, file), // TODO basename ?
|
|
63
|
+
overwrite: values.overwrite,
|
|
64
|
+
width,
|
|
65
|
+
height,
|
|
66
|
+
})
|
|
67
|
+
console.log(file)
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
console.error(err?.message || err)
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
|
|
70
|
-
async function resize({ file, outFile, overwrite, width, height }) {
|
|
75
|
+
export async function resize({ file, outFile, overwrite, width, height }) {
|
|
71
76
|
const v = await videoAttrs(file)
|
|
72
77
|
if (width === v.width && height === v.height
|
|
73
78
|
|| width < 0 && height === v.height
|
|
74
|
-
|| height < 0 && width === v.width
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
79
|
+
|| height < 0 && width === v.width
|
|
80
|
+
)
|
|
81
|
+
throw new Error(`no changes needed. ${file}`)
|
|
78
82
|
|
|
79
|
-
if (!overwrite && isFile(outFile))
|
|
80
|
-
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
+
if (!overwrite && isFile(outFile))
|
|
84
|
+
throw new Error(`output file exists but --overwrite=false. ${file}`)
|
|
83
85
|
|
|
84
|
-
console.log(file)
|
|
85
86
|
const tmp = uniqueFilenameFor(file)
|
|
86
87
|
await ffmpeg([
|
|
87
88
|
'-i', file,
|
|
@@ -91,9 +92,3 @@ async function resize({ file, outFile, overwrite, width, height }) {
|
|
|
91
92
|
])
|
|
92
93
|
await rename(tmp, outFile)
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
main().catch(err => {
|
|
97
|
-
console.error(err.message)
|
|
98
|
-
process.exit(1)
|
|
99
|
-
})
|
package/src/seqcheck.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { parseArgs } from 'node:util'
|
|
3
2
|
import { readdirSync } from 'node:fs'
|
|
4
3
|
|
|
4
|
+
const LEFT_DELIM = '_'
|
|
5
|
+
const RIGHT_DELIM = '.'
|
|
5
6
|
|
|
6
7
|
const HELP = `
|
|
7
8
|
SYNOPSIS
|
|
@@ -11,17 +12,17 @@ DESCRIPTION
|
|
|
11
12
|
Find missing numbered files in a sequence.
|
|
12
13
|
|
|
13
14
|
OPTIONS
|
|
14
|
-
-ld, --left-delimiter <str> Delimiter before the number (default: "
|
|
15
|
-
-rd, --right-delimiter <str> Delimiter after the number (default: "
|
|
15
|
+
-ld, --left-delimiter <str> Delimiter before the number (default: "${LEFT_DELIM}")
|
|
16
|
+
-rd, --right-delimiter <str> Delimiter after the number (default: "${RIGHT_DELIM}")
|
|
16
17
|
-h, --help
|
|
17
18
|
`.trim()
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
function main() {
|
|
21
|
+
export default function main() {
|
|
21
22
|
const { values, positionals } = parseArgs({
|
|
22
23
|
options: {
|
|
23
|
-
'left-delimiter': { type: 'string', default:
|
|
24
|
-
'right-delimiter': { type: 'string', default:
|
|
24
|
+
'left-delimiter': { type: 'string', default: LEFT_DELIM },
|
|
25
|
+
'right-delimiter': { type: 'string', default: RIGHT_DELIM },
|
|
25
26
|
help: { short: 'h', type: 'boolean' },
|
|
26
27
|
},
|
|
27
28
|
allowPositionals: true,
|
|
@@ -32,18 +33,19 @@ function main() {
|
|
|
32
33
|
return
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
values['left-delimiter'],
|
|
38
|
-
values['right-delimiter'])
|
|
39
|
-
|
|
40
|
-
const missing = findMissingNumbers(seq)
|
|
36
|
+
const dir = positionals[0] || process.cwd()
|
|
37
|
+
const missing = seqcheck(dir, values['left-delimiter'], values['right-delimiter'])
|
|
41
38
|
if (missing.length)
|
|
42
39
|
console.log('Missing:', missing)
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
export function
|
|
46
|
-
const
|
|
42
|
+
export function seqcheck(dir, leftDelim = LEFT_DELIM, rightDelim = RIGHT_DELIM) {
|
|
43
|
+
const seq = extractSeqNums(readdirSync(dir), leftDelim, rightDelim)
|
|
44
|
+
return findMissingNumbers(seq)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function extractSeqNums(names, leftDelim, rightDelim) {
|
|
48
|
+
const pattern = new RegExp(escapeRegex(leftDelim) + '(\\d+)' + escapeRegex(rightDelim))
|
|
47
49
|
const seq = []
|
|
48
50
|
for (const name of names) {
|
|
49
51
|
const match = name.match(pattern)
|
|
@@ -66,7 +68,3 @@ export function findMissingNumbers(seq) {
|
|
|
66
68
|
function escapeRegex(str) {
|
|
67
69
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
68
70
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (import.meta.main)
|
|
72
|
-
main()
|
package/src/sqcrop.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import { join } from 'node:path'
|
|
4
2
|
import { rename } from 'node:fs/promises'
|
|
5
3
|
|
|
@@ -10,18 +8,18 @@ import { parseOptions } from './utils/parseOptions.js'
|
|
|
10
8
|
|
|
11
9
|
const HELP = `
|
|
12
10
|
SYNOPSIS
|
|
13
|
-
mediasnacks sqcrop [-y | --overwrite] [--
|
|
11
|
+
mediasnacks sqcrop [-y | --overwrite] [--outdir=<dir>] <images>
|
|
14
12
|
|
|
15
13
|
DESCRIPTION
|
|
16
14
|
Square crops images
|
|
17
15
|
`.trim()
|
|
18
16
|
|
|
19
17
|
|
|
20
|
-
async function main() {
|
|
18
|
+
export default async function main() {
|
|
21
19
|
await assertUserHasFFmpeg()
|
|
22
20
|
|
|
23
21
|
const { values, files } = await parseOptions({
|
|
24
|
-
|
|
22
|
+
outdir: { type: 'string', default: '' },
|
|
25
23
|
overwrite: { short: 'y', type: 'boolean' },
|
|
26
24
|
help: { short: 'h', type: 'boolean' },
|
|
27
25
|
})
|
|
@@ -36,26 +34,25 @@ async function main() {
|
|
|
36
34
|
|
|
37
35
|
console.log('Cropping…')
|
|
38
36
|
for (const file of files)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
try {
|
|
38
|
+
await sqcrop({
|
|
39
|
+
file,
|
|
40
|
+
outFile: join(values.outdir, file),
|
|
41
|
+
overwrite: values.overwrite
|
|
42
|
+
})
|
|
43
|
+
console.log(file)
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.error(err?.message || err)
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
async function sqcrop({ file, outFile, overwrite }) {
|
|
50
|
+
export async function sqcrop({ file, outFile, overwrite }) {
|
|
47
51
|
const stOut = lstat(outFile)
|
|
48
52
|
|
|
49
|
-
if (!overwrite && stOut?.isFile()) {
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
if (stOut?.mtimeMs > lstat(file)?.mtimeMs) {
|
|
54
|
-
console.log('(skipped: outputFile is newer)', file)
|
|
55
|
-
return
|
|
56
|
-
}
|
|
53
|
+
if (!overwrite && stOut?.isFile()) throw new Error(`output file exists but --overwrite=false. ${file}`)
|
|
54
|
+
if (stOut?.mtimeMs > lstat(file)?.mtimeMs) throw new Error(`outputFile is newer. ${file}`)
|
|
57
55
|
|
|
58
|
-
console.log(file)
|
|
59
56
|
const tmp = uniqueFilenameFor(file)
|
|
60
57
|
await ffmpeg([
|
|
61
58
|
'-v', 'error',
|
|
@@ -66,8 +63,3 @@ async function sqcrop({ file, outFile, overwrite }) {
|
|
|
66
63
|
])
|
|
67
64
|
await rename(tmp, outFile)
|
|
68
65
|
}
|
|
69
|
-
|
|
70
|
-
main().catch(err => {
|
|
71
|
-
console.error(err.message)
|
|
72
|
-
process.exit(1)
|
|
73
|
-
})
|
package/src/ssim.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { ffmpeg } from './utils/subprocess.js'
|
|
3
2
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
3
|
|
|
@@ -12,7 +11,7 @@ DESCRIPTION
|
|
|
12
11
|
`.trim()
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
async function main() {
|
|
14
|
+
export default async function main() {
|
|
16
15
|
const { values, positionals } = await parseOptions({
|
|
17
16
|
help: { short: 'h', type: 'boolean' }
|
|
18
17
|
})
|
|
@@ -41,10 +40,3 @@ export async function ssim(img1, img2) {
|
|
|
41
40
|
throw new Error(`Could not parse SSIM output:\n${stderr}`)
|
|
42
41
|
return parseFloat(match[1])
|
|
43
42
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (import.meta.main)
|
|
47
|
-
main().catch(err => {
|
|
48
|
-
console.error(err.message)
|
|
49
|
-
process.exit(1)
|
|
50
|
-
})
|
package/src/unemoji.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { rename } from 'node:fs/promises'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { dirname, basename, join } from 'node:path'
|
|
4
|
+
import { parseOptions } from './utils/parseOptions.js'
|
|
5
|
+
import { findFiles } from './utils/fs-utils.js'
|
|
6
|
+
|
|
7
|
+
const EMOJI_RE = new RegExp(
|
|
8
|
+
'[' +
|
|
9
|
+
'\u{1F600}-\u{1F64F}' + // Emoticons
|
|
10
|
+
'\u{1F300}-\u{1F5FF}' + // Misc Symbols and Pictographs
|
|
11
|
+
'\u{1F680}-\u{1F6FF}' + // Transport and Map
|
|
12
|
+
'\u{2600}-\u{26FF}' + // Misc symbols
|
|
13
|
+
'\u{2700}-\u{27BF}' + // Dingbats
|
|
14
|
+
'\u{1F900}-\u{1F9FF}' + // Supplemental Symbols and Pictographs
|
|
15
|
+
'\u{1FA70}-\u{1FAFF}' + // Symbols and Pictographs Extended-A
|
|
16
|
+
'\u{1F1E6}-\u{1F1FF}' + // Regional Indicator Symbols
|
|
17
|
+
']',
|
|
18
|
+
'gu'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const HELP = `
|
|
22
|
+
SYNOPSIS
|
|
23
|
+
mediasnacks unemoji [-r | --recursive] <dir>
|
|
24
|
+
|
|
25
|
+
DESCRIPTION
|
|
26
|
+
Removes emoji from filenames in the current directory.
|
|
27
|
+
Does not overwrite files.
|
|
28
|
+
|
|
29
|
+
OPTIONS
|
|
30
|
+
-r, --recursive
|
|
31
|
+
`.trim()
|
|
32
|
+
|
|
33
|
+
export default async function main() {
|
|
34
|
+
const { values, positionals } = await parseOptions({
|
|
35
|
+
help: { short: 'h', type: 'boolean' },
|
|
36
|
+
recursive: { short: 'r', type: 'boolean' }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (values.help) {
|
|
40
|
+
console.log(HELP)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (positionals.length !== 1) throw new Error('Only one dir is accepted')
|
|
45
|
+
|
|
46
|
+
const files = findFiles({
|
|
47
|
+
dir: positionals[0],
|
|
48
|
+
regex: EMOJI_RE,
|
|
49
|
+
recursive: values.recursive,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
for (const file of files)
|
|
53
|
+
try {
|
|
54
|
+
const newpath = await unemoji(file)
|
|
55
|
+
if (newpath)
|
|
56
|
+
console.log(`Renaming: ${file} -> ${newpath}`)
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.error(err?.message || err)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function unemoji(file) {
|
|
64
|
+
const dir = dirname(file)
|
|
65
|
+
const base = basename(file)
|
|
66
|
+
const newbase = base.replace(EMOJI_RE, '')
|
|
67
|
+
.normalize('NFKC')
|
|
68
|
+
.replace(/\s+/g, ' ')
|
|
69
|
+
.replace(/\s+\./g, '.')
|
|
70
|
+
.trim()
|
|
71
|
+
if (base === newbase)
|
|
72
|
+
return null
|
|
73
|
+
|
|
74
|
+
const newpath = join(dir, newbase)
|
|
75
|
+
if (existsSync(newpath)) throw new Error(`Skipping (exists): ${file} -> ${newpath}`)
|
|
76
|
+
|
|
77
|
+
await rename(file, newpath)
|
|
78
|
+
return newpath
|
|
79
|
+
}
|
package/src/utils/fs-utils.js
CHANGED
|
@@ -34,7 +34,7 @@ export async function mkDir(path) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export function findFiles({ dir, regex, recursive, ignoredDirs }) {
|
|
37
|
+
export function findFiles({ dir, regex, recursive, ignoredDirs = [] }) {
|
|
38
38
|
return readdirSync(dir, { withFileTypes: true, recursive })
|
|
39
39
|
.filter(entry =>
|
|
40
40
|
entry.isFile()
|
package/src/vsplit.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { readFileSync } from 'node:fs'
|
|
3
2
|
import { resolve, parse, join } from 'node:path'
|
|
4
3
|
|
|
@@ -35,7 +34,7 @@ SEE ALSO
|
|
|
35
34
|
`.trim()
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
async function main() {
|
|
37
|
+
export default async function main() {
|
|
39
38
|
await assertUserHasFFmpeg()
|
|
40
39
|
|
|
41
40
|
const { values, files } = await parseOptions({
|
|
@@ -79,7 +78,7 @@ function parseCSV(csvPath) {
|
|
|
79
78
|
return clips
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
async function vsplit(videoPath, clips) {
|
|
81
|
+
export async function vsplit(videoPath, clips) {
|
|
83
82
|
const { dir, name, ext } = parse(videoPath)
|
|
84
83
|
const seqLen = Math.log10(clips.length) + 1 | 0
|
|
85
84
|
|
|
@@ -97,8 +96,3 @@ async function vsplit(videoPath, clips) {
|
|
|
97
96
|
])
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
|
-
|
|
101
|
-
main().catch(err => {
|
|
102
|
-
console.error(err.message || err)
|
|
103
|
-
process.exit(1)
|
|
104
|
-
})
|
package/src/vtrim.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { resolve, parse } from 'node:path'
|
|
3
2
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
3
|
import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
|
|
@@ -14,14 +13,13 @@ DESCRIPTION
|
|
|
14
13
|
OPTIONS
|
|
15
14
|
-s, --start <time> Start time (e.g. 10, 00:00:10, 1:23.5). Default: beginning.
|
|
16
15
|
-e, --end <time> End time (e.g. 30, 00:00:30, 2:45.0). Default: end of video.
|
|
17
|
-
-h, --help
|
|
18
16
|
|
|
19
17
|
SEE ALSO
|
|
20
18
|
mediasnacks vsplit
|
|
21
19
|
`.trim()
|
|
22
20
|
|
|
23
21
|
|
|
24
|
-
async function main() {
|
|
22
|
+
export default async function main() {
|
|
25
23
|
await assertUserHasFFmpeg()
|
|
26
24
|
|
|
27
25
|
const { values, files } = await parseOptions({
|
|
@@ -39,10 +37,14 @@ async function main() {
|
|
|
39
37
|
throw new Error('No video specified. See mediasnacks vtrim --help')
|
|
40
38
|
|
|
41
39
|
for (const file of files)
|
|
42
|
-
await vtrim(
|
|
40
|
+
await vtrim({
|
|
41
|
+
video: resolve(file),
|
|
42
|
+
start: values.start,
|
|
43
|
+
end: values.end
|
|
44
|
+
})
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
async function vtrim(video, start, end) {
|
|
47
|
+
export async function vtrim({ video, start, end }) {
|
|
46
48
|
const { dir, name, ext } = parse(video)
|
|
47
49
|
await ffmpeg([
|
|
48
50
|
'-v', 'error',
|
|
@@ -54,8 +56,3 @@ async function vtrim(video, start, end) {
|
|
|
54
56
|
resolve(dir, `${name}.trim${ext}`)
|
|
55
57
|
].flat())
|
|
56
58
|
}
|
|
57
|
-
|
|
58
|
-
main().catch(err => {
|
|
59
|
-
console.error(err.message || err)
|
|
60
|
-
process.exit(1)
|
|
61
|
-
})
|