mediasnacks 0.27.0 → 0.29.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/install-zsh-completions.js +3 -3
- package/package.json +1 -1
- package/src/avif.js +17 -29
- package/src/cli.js +14 -17
- package/src/countframes.js +10 -19
- package/src/detectdups.js +10 -17
- package/src/dlaudio.js +5 -9
- package/src/dlvideo.js +4 -8
- package/src/dropdups.js +6 -16
- package/src/edgespic.js +11 -19
- package/src/frameseq.js +13 -21
- package/src/gif.js +43 -0
- package/src/hev1tohvc1.js +10 -21
- package/src/moov2front.js +10 -24
- package/src/openrand.js +11 -15
- package/src/play.js +4 -10
- package/src/png.js +5 -9
- package/src/prores.js +10 -17
- package/src/qdir.js +3 -11
- package/src/resize.js +29 -43
- package/src/seqcheck.js +7 -17
- package/src/sqcrop.js +18 -29
- package/src/ssim.js +4 -12
- package/src/unemoji.js +20 -30
- package/src/utils/parseOptions.js +15 -2
- package/src/utils/subprocess.js +2 -1
- package/src/utils/videoAttrs.js +2 -2
- package/src/vsplit.js +8 -17
- package/src/vtrim.js +7 -15
- package/src/gif.sh +0 -44
|
@@ -25,7 +25,7 @@ function makeScript() {
|
|
|
25
25
|
return `#compdef mediasnacks
|
|
26
26
|
|
|
27
27
|
_mediasnacks_commands=(
|
|
28
|
-
${commandsSummary().map(([cmd, desc]) => `'${cmd}:${desc}'`).join('\n')}
|
|
28
|
+
${commandsSummary().map(([cmd, desc]) => `'${cmd}:${desc.trim()}'`).join('\n')}
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
if (( CURRENT == 2 )); then
|
|
@@ -35,8 +35,8 @@ fi
|
|
|
35
35
|
|
|
36
36
|
local cmd="$words[2]"
|
|
37
37
|
case "$cmd" in
|
|
38
|
-
qdir)
|
|
39
|
-
|
|
38
|
+
qdir|openrand)
|
|
39
|
+
_path_files -/
|
|
40
40
|
;;
|
|
41
41
|
*)
|
|
42
42
|
_files
|
package/package.json
CHANGED
package/src/avif.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join, basename, dirname } from 'node:path'
|
|
2
2
|
import { parseOptions } from './utils/parseOptions.js'
|
|
3
3
|
import { replaceExt, lstat } from './utils/fs-utils.js'
|
|
4
|
-
import { ffmpeg
|
|
4
|
+
import { ffmpeg } from './utils/subprocess.js'
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
const HELP = `
|
|
@@ -14,45 +14,33 @@ DESCRIPTION
|
|
|
14
14
|
EXAMPLES
|
|
15
15
|
mediasnacks avif -y '*.png'
|
|
16
16
|
mediasnacks avif --outdir=foo/ 'a/**/*.png'
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
`
|
|
19
18
|
|
|
20
19
|
export default async function main() {
|
|
21
|
-
await
|
|
22
|
-
|
|
23
|
-
const { values, files } = await parseOptions({
|
|
20
|
+
const { values, files } = await parseOptions(HELP, {
|
|
24
21
|
outdir: { type: 'string', default: '' },
|
|
25
22
|
overwrite: { short: 'y', type: 'boolean' },
|
|
26
|
-
help: { short: 'h', type: 'boolean' },
|
|
27
23
|
})
|
|
28
24
|
|
|
29
|
-
if (values.help) {
|
|
30
|
-
console.log(HELP)
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
|
|
34
25
|
if (!files.length)
|
|
35
|
-
throw
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
console.log(file)
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
console.error(err?.message || err)
|
|
49
|
-
}
|
|
26
|
+
throw 'Invalid input image'
|
|
27
|
+
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
await avif({
|
|
30
|
+
file,
|
|
31
|
+
outFile: join(values.outdir || dirname(file), replaceExt(basename(file), 'avif')),
|
|
32
|
+
overwrite: values.overwrite
|
|
33
|
+
})
|
|
34
|
+
console.log(file)
|
|
35
|
+
}
|
|
50
36
|
}
|
|
51
37
|
|
|
52
38
|
export async function avif({ file, outFile, overwrite = false }) {
|
|
53
39
|
const stAvif = lstat(outFile)
|
|
54
|
-
if (!overwrite
|
|
55
|
-
|
|
40
|
+
if (!overwrite) {
|
|
41
|
+
if (stAvif?.isFile()) throw `output file exists: ${file}`
|
|
42
|
+
if (stAvif?.mtimeMs > lstat(file)?.mtimeMs) throw `avif is newer: ${file}`
|
|
43
|
+
}
|
|
56
44
|
|
|
57
45
|
await ffmpeg([
|
|
58
46
|
'-y',
|
package/src/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ const COMMANDS = {
|
|
|
16
16
|
frameseq: ['./frameseq.js', 'Converts video to sequence of PNGs'],
|
|
17
17
|
countframes: ['./countframes.js', 'Counts frames in a video'],
|
|
18
18
|
ssim: ['./ssim.js', 'Computes SSIM between two images'],
|
|
19
|
-
gif: ['./gif.
|
|
19
|
+
gif: ['./gif.js', 'Video to GIF\n'],
|
|
20
20
|
|
|
21
21
|
detectdups: ['./detectdups.js', 'Detects duplicate frames in a video'],
|
|
22
22
|
dropdups: ['./dropdups.js', 'Removes duplicate frames in a video'],
|
|
@@ -60,27 +60,24 @@ ${commandsSummary().map(([cmd, desc]) =>
|
|
|
60
60
|
async function main() {
|
|
61
61
|
const [, , opt, ...args] = process.argv
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
console.log(HELP)
|
|
69
|
-
return
|
|
70
|
-
}
|
|
63
|
+
switch (opt) {
|
|
64
|
+
case '-v':
|
|
65
|
+
case '--version':
|
|
66
|
+
console.log(pkgJSON.version)
|
|
67
|
+
return
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!Object.hasOwn(COMMANDS, opt)) {
|
|
77
|
-
console.error(`'${opt}' is not a command. See mediasnacks --help\n`)
|
|
78
|
-
process.exit(1)
|
|
69
|
+
case '-h':
|
|
70
|
+
case '--help':
|
|
71
|
+
console.log(HELP)
|
|
72
|
+
return
|
|
79
73
|
}
|
|
80
74
|
|
|
75
|
+
if (!opt) throw HELP
|
|
76
|
+
if (!Object.hasOwn(COMMANDS, opt)) throw `'${opt}' is not a command. See mediasnacks --help\n`
|
|
77
|
+
|
|
81
78
|
const cmd = COMMANDS[opt][0]
|
|
82
79
|
if (cmd.endsWith('.js'))
|
|
83
|
-
(await import(cmd)).default()
|
|
80
|
+
await (await import(cmd)).default()
|
|
84
81
|
else
|
|
85
82
|
spawn(join(import.meta.dirname, cmd), args, { stdio: 'inherit' })
|
|
86
83
|
.on('exit', process.exit)
|
package/src/countframes.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { parseOptions } from './utils/parseOptions.js'
|
|
2
|
-
import { assertUserHasFFmpeg } from './utils/subprocess.js'
|
|
3
2
|
import { videoAttrs } from './utils/videoAttrs.js'
|
|
4
3
|
import { parseTimecode } from './utils/parseTimecode.js'
|
|
5
4
|
|
|
@@ -19,27 +18,20 @@ OPTIONS
|
|
|
19
18
|
EXAMPLES
|
|
20
19
|
mediasnacks countframes --start=1:30.16 --end=60 video.mov
|
|
21
20
|
mediasnacks countframes --fps=12 video.mov
|
|
22
|
-
|
|
21
|
+
`
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
export default async function main() {
|
|
26
|
-
await
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
start: { short: 's', type: 'string', default: '' },
|
|
31
|
-
end: { short: 'e', type: 'string', default: '' },
|
|
32
|
-
help: { short: 'h', type: 'boolean' }
|
|
25
|
+
const { values, files } = await parseOptions(HELP, {
|
|
26
|
+
fps: { type: 'string' },
|
|
27
|
+
start: { short: 's', type: 'string' },
|
|
28
|
+
end: { short: 'e', type: 'string' },
|
|
33
29
|
})
|
|
34
30
|
|
|
35
|
-
if (values.help) {
|
|
36
|
-
console.log(HELP)
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
|
|
40
31
|
const { fps, start, end } = values
|
|
41
32
|
const video = files[0]
|
|
42
|
-
if (!video)
|
|
33
|
+
if (!video)
|
|
34
|
+
throw 'No video file specified'
|
|
43
35
|
|
|
44
36
|
const n = await countframes({ video, fps, start, end })
|
|
45
37
|
console.log(String(n))
|
|
@@ -48,10 +40,9 @@ export default async function main() {
|
|
|
48
40
|
|
|
49
41
|
export async function countframes({ video, fps, start, end }) {
|
|
50
42
|
const v = await videoAttrs(video)
|
|
51
|
-
const
|
|
43
|
+
const duration = parseFloat(v.duration || 0)
|
|
52
44
|
const startSecs = start ? parseTimecode(start) : 0
|
|
53
|
-
const endSecs = end ? parseTimecode(end) :
|
|
54
|
-
const durationLimit = Math.max(0, endSecs - startSecs)
|
|
45
|
+
const endSecs = end ? parseTimecode(end) : duration
|
|
55
46
|
const actualFps = fps ? Number(fps) : eval(v.r_frame_rate)
|
|
56
|
-
return Math.ceil(
|
|
47
|
+
return Math.ceil(Math.max(0, endSecs - startSecs) * actualFps)
|
|
57
48
|
}
|
package/src/detectdups.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseOptions } from './utils/parseOptions.js'
|
|
2
|
-
import { ffmpeg
|
|
2
|
+
import { ffmpeg } from './utils/subprocess.js'
|
|
3
3
|
import { videoAttrs } from './utils/videoAttrs.js'
|
|
4
4
|
|
|
5
5
|
const STDEV_THRESHOLD = 0.2
|
|
@@ -22,32 +22,25 @@ OPTIONS
|
|
|
22
22
|
-s, --seek <sec> Video start time for detection
|
|
23
23
|
-d, --duration <sec> Analyze this many seconds of video
|
|
24
24
|
-v, --verbose
|
|
25
|
-
-h, --help
|
|
26
25
|
|
|
27
26
|
SEE ALSO
|
|
28
27
|
mediasnacks framediff
|
|
29
|
-
|
|
28
|
+
`
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
export default async function main() {
|
|
33
|
-
await
|
|
34
|
-
|
|
35
|
-
const { values, files } = await parseOptions({
|
|
32
|
+
const { values, files } = await parseOptions(HELP, {
|
|
36
33
|
seek: { short: 's', type: 'string', },
|
|
37
34
|
duration: { short: 'd', type: 'string' },
|
|
38
|
-
help: { short: 'h', type: 'boolean' }
|
|
39
35
|
})
|
|
40
36
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (files.length !== 1) throw new Error('Invalid input file. One video file must be specified. See mediasnacks detectdups --help')
|
|
37
|
+
if (files.length !== 1)
|
|
38
|
+
throw 'Invalid input file. One video file must be specified.'
|
|
47
39
|
|
|
48
40
|
const video = files[0]
|
|
49
41
|
const v = await videoAttrs(video)
|
|
50
|
-
if (v.codec_type !== 'video')
|
|
42
|
+
if (v.codec_type !== 'video')
|
|
43
|
+
throw 'Invalid input file. Must be a video.'
|
|
51
44
|
|
|
52
45
|
const vDur = Number(v.duration)
|
|
53
46
|
|
|
@@ -59,9 +52,9 @@ export default async function main() {
|
|
|
59
52
|
? Number(values.duration)
|
|
60
53
|
: vDur > 60 ? 20 : vDur
|
|
61
54
|
|
|
62
|
-
if (isNaN(seek) || seek < 0) throw
|
|
63
|
-
if (isNaN(duration) || duration < 1) throw
|
|
64
|
-
if ((seek + duration) > vDur) throw
|
|
55
|
+
if (isNaN(seek) || seek < 0) throw `Invalid --seek value: ${values.seek}`
|
|
56
|
+
if (isNaN(duration) || duration < 1) throw `Invalid --duration value: ${values.duration}`
|
|
57
|
+
if ((seek + duration) > vDur) throw `Invalid analysis range. Exceeds video duration: ${vDur}`
|
|
65
58
|
|
|
66
59
|
const dups = await detectdups({ video: files[0], seek, duration })
|
|
67
60
|
const h = deltaHistogram(dups)
|
package/src/dlaudio.js
CHANGED
|
@@ -9,17 +9,13 @@ SYNOPSIS
|
|
|
9
9
|
|
|
10
10
|
DESCRIPTION
|
|
11
11
|
yt-dlp best m4a
|
|
12
|
-
|
|
12
|
+
`
|
|
13
13
|
|
|
14
14
|
export default async function main() {
|
|
15
|
-
const { values, positionals } = await parseOptions(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (values.help || !positionals[0]) {
|
|
20
|
-
console.log(HELP)
|
|
21
|
-
return
|
|
22
|
-
}
|
|
15
|
+
const { values, positionals } = await parseOptions(HELP)
|
|
16
|
+
|
|
17
|
+
if (!positionals[0])
|
|
18
|
+
throw 'Missing URL'
|
|
23
19
|
|
|
24
20
|
const f = await dlaudio(positionals[0])
|
|
25
21
|
console.log(f)
|
package/src/dlvideo.js
CHANGED
|
@@ -8,17 +8,13 @@ SYNOPSIS
|
|
|
8
8
|
|
|
9
9
|
DESCRIPTION
|
|
10
10
|
yt-dlp best mp4
|
|
11
|
-
|
|
11
|
+
`
|
|
12
12
|
|
|
13
13
|
export default async function main() {
|
|
14
|
-
const { values, positionals } = await parseOptions(
|
|
15
|
-
help: { short: 'h', type: 'boolean' }
|
|
16
|
-
})
|
|
14
|
+
const { values, positionals } = await parseOptions(HELP)
|
|
17
15
|
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
return
|
|
21
|
-
}
|
|
16
|
+
if (!positionals[0])
|
|
17
|
+
throw 'Missing URL'
|
|
22
18
|
|
|
23
19
|
await dlvideo(positionals[0])
|
|
24
20
|
}
|
package/src/dropdups.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve, parse, format } from 'node:path'
|
|
2
2
|
import { parseOptions } from './utils/parseOptions.js'
|
|
3
|
-
import { ffmpeg,
|
|
3
|
+
import { ffmpeg, run } from './utils/subprocess.js'
|
|
4
4
|
import { ProresProfiles } from './prores.js'
|
|
5
5
|
|
|
6
6
|
|
|
@@ -16,7 +16,6 @@ DESCRIPTION
|
|
|
16
16
|
OPTIONS
|
|
17
17
|
-n, --dup-frame-num <n> Known frame interval to drop.
|
|
18
18
|
Default: n=0, which auto-detects repeated frames (slower)
|
|
19
|
-
-h, --help
|
|
20
19
|
|
|
21
20
|
EXAMPLES
|
|
22
21
|
Use n=2 when every other frame is repeated:
|
|
@@ -24,30 +23,21 @@ EXAMPLES
|
|
|
24
23
|
|
|
25
24
|
Use n=6 if e.g., a 25 fps got upped to 30 fps without interpolation.
|
|
26
25
|
mediasnacks dropdups -n6 vid.mov
|
|
27
|
-
|
|
26
|
+
`
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
export default async function main() {
|
|
31
|
-
await
|
|
32
|
-
|
|
33
|
-
const { values, files } = await parseOptions({
|
|
34
|
-
'dup-frame-num': { short: 'n', type: 'string', default: '' },
|
|
35
|
-
help: { short: 'h', type: 'boolean' },
|
|
30
|
+
const { values, files } = await parseOptions(HELP, {
|
|
31
|
+
'dup-frame-num': { short: 'n', type: 'string' },
|
|
36
32
|
})
|
|
37
33
|
|
|
38
|
-
if (values.help) {
|
|
39
|
-
console.log(HELP)
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
34
|
if (!files.length)
|
|
44
|
-
throw
|
|
35
|
+
throw 'No video specified.'
|
|
45
36
|
|
|
46
37
|
let dupFrameNum = values['dup-frame-num']
|
|
47
38
|
if (dupFrameNum && !Number.isInteger(+dupFrameNum))
|
|
48
|
-
throw
|
|
39
|
+
throw 'Invalid -n. It must be a positive integer.'
|
|
49
40
|
|
|
50
|
-
console.log('Dropping Duplicate Frames…')
|
|
51
41
|
for (const file of files)
|
|
52
42
|
await dropdups(resolve(file), dupFrameNum)
|
|
53
43
|
}
|
package/src/edgespic.js
CHANGED
|
@@ -3,46 +3,38 @@ import { basename, extname, join, parse } from 'node:path'
|
|
|
3
3
|
import { mkDir } from './utils/fs-utils.js'
|
|
4
4
|
import { videoAttrs } from './utils/videoAttrs.js'
|
|
5
5
|
import { parseOptions } from './utils/parseOptions.js'
|
|
6
|
-
import { ffmpeg
|
|
6
|
+
import { ffmpeg } from './utils/subprocess.js'
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
const WIDTH = 640
|
|
10
|
+
|
|
9
11
|
const HELP = `
|
|
10
12
|
SYNOPSIS
|
|
11
|
-
mediasnacks edgespic [--width=<num>] <files>
|
|
13
|
+
mediasnacks edgespic [-w | --width=<num>] <files>
|
|
12
14
|
|
|
13
15
|
DESCRIPTION
|
|
14
16
|
Extracts the first and last frames from each video and saves them to the 'edgepics/' subfolder.
|
|
15
17
|
|
|
16
18
|
OPTIONS
|
|
17
|
-
-w, --width Default:
|
|
19
|
+
-w, --width Default: ${WIDTH} The aspect ratio is preserved.
|
|
18
20
|
|
|
19
21
|
EXAMPLES
|
|
20
22
|
mediasnacks edgespic -w 800 *.mov
|
|
21
23
|
mediasnacks edgespic -w 600 'videos/**/*.mp4'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
`
|
|
24
25
|
|
|
25
26
|
export default async function main() {
|
|
26
|
-
await
|
|
27
|
-
|
|
28
|
-
const { values, files } = await parseOptions({
|
|
29
|
-
'width': { short: 'w', type: 'string', default: '640' },
|
|
30
|
-
help: { short: 'h', type: 'boolean' },
|
|
27
|
+
const { values, files } = await parseOptions(HELP, {
|
|
28
|
+
width: { short: 'w', type: 'string', default: String(WIDTH) }
|
|
31
29
|
})
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const width = Number(values['width'])
|
|
39
|
-
if (width <= 0 || !Number.isInteger(width)) throw new Error('--width must be a positive number')
|
|
40
|
-
if (!files.length) throw new Error('No video files specified')
|
|
31
|
+
const width = Number(values.width)
|
|
32
|
+
if (width <= 0 || !Number.isInteger(width)) throw '--width must be a positive number'
|
|
33
|
+
if (!files.length) throw 'No video files specified'
|
|
41
34
|
|
|
42
35
|
const outDir = join(parse(files[0]).dir, 'edgespic')
|
|
43
36
|
await mkDir(outDir)
|
|
44
37
|
|
|
45
|
-
console.log('Extracting edge frames…')
|
|
46
38
|
for (const file of files)
|
|
47
39
|
await edgespic(file, width, outDir)
|
|
48
40
|
}
|
package/src/frameseq.js
CHANGED
|
@@ -2,7 +2,7 @@ import { basename, extname, join, parse } from 'node:path'
|
|
|
2
2
|
|
|
3
3
|
import { mkDir } from './utils/fs-utils.js'
|
|
4
4
|
import { parseOptions } from './utils/parseOptions.js'
|
|
5
|
-
import { ffmpeg
|
|
5
|
+
import { ffmpeg } from './utils/subprocess.js'
|
|
6
6
|
import { countframes } from './countframes.js'
|
|
7
7
|
|
|
8
8
|
|
|
@@ -24,31 +24,23 @@ EXAMPLES
|
|
|
24
24
|
|
|
25
25
|
Custom framerate, all video duration
|
|
26
26
|
mediasnacks frameseq --fps=12 video.mov
|
|
27
|
-
|
|
27
|
+
`
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
export default async function main() {
|
|
31
|
-
await
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
start: { short: 's', type: 'string', default: '' },
|
|
36
|
-
end: { short: 'e', type: 'string', default: '' },
|
|
31
|
+
const { values, files } = await parseOptions(HELP, {
|
|
32
|
+
fps: { short: 'f', type: 'string' },
|
|
33
|
+
start: { short: 's', type: 'string' },
|
|
34
|
+
end: { short: 'e', type: 'string' },
|
|
37
35
|
outdir: { type: 'string', default: '' },
|
|
38
|
-
help: { short: 'h', type: 'boolean' }
|
|
39
36
|
})
|
|
40
37
|
|
|
41
|
-
if (values.help) {
|
|
42
|
-
console.log(HELP)
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
38
|
const { fps, start, end, outdir } = values
|
|
47
39
|
const video = files[0]
|
|
48
|
-
if (!video) throw
|
|
49
|
-
if (fps && isNaN(parseFloat(fps))) throw
|
|
50
|
-
if (start && isNaN(parseFloat(start))) throw
|
|
51
|
-
if (end && isNaN(parseFloat(end))) throw
|
|
40
|
+
if (!video) throw 'No video files specified'
|
|
41
|
+
if (fps && isNaN(parseFloat(fps))) throw 'Invalid --fps'
|
|
42
|
+
if (start && isNaN(parseFloat(start))) throw 'Invalid --start'
|
|
43
|
+
if (end && isNaN(parseFloat(end))) throw 'Invalid --end'
|
|
52
44
|
|
|
53
45
|
const nFrames = await countframes({ video, fps, start, end })
|
|
54
46
|
const pad = String(nFrames).length
|
|
@@ -57,13 +49,13 @@ export default async function main() {
|
|
|
57
49
|
|
|
58
50
|
export async function frameseq({ video, fps, start, end, pad, outdir }) {
|
|
59
51
|
const name = basename(video, extname(video))
|
|
60
|
-
const
|
|
61
|
-
await mkDir(
|
|
52
|
+
const dir = outdir || join(parse(video).dir, name)
|
|
53
|
+
await mkDir(dir)
|
|
62
54
|
await ffmpeg([
|
|
63
55
|
start ? ['-ss', start] : [],
|
|
64
56
|
end ? ['-to', end] : [],
|
|
65
57
|
'-i', video,
|
|
66
58
|
fps ? ['-vf', `fps=${fps}`] : [],
|
|
67
|
-
join(
|
|
59
|
+
join(dir, `${name}_%0${pad}d.png`)
|
|
68
60
|
].flat())
|
|
69
61
|
}
|
package/src/gif.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { parse, format } from 'node:path'
|
|
2
|
+
import { parseOptions } from './utils/parseOptions.js'
|
|
3
|
+
import { run } from './utils/subprocess.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const FPS = 12
|
|
7
|
+
const WIDTH = 600
|
|
8
|
+
|
|
9
|
+
const HELP = `
|
|
10
|
+
SYNOPSIS
|
|
11
|
+
mediasnacks gif [-f | --fps <number>] [-w | --width <pixels>] <file>
|
|
12
|
+
|
|
13
|
+
DESCRIPTION
|
|
14
|
+
Converts video to GIF
|
|
15
|
+
|
|
16
|
+
OPTIONS
|
|
17
|
+
-f, --fps Default: ${FPS}
|
|
18
|
+
-w, --width Default: ${WIDTH}
|
|
19
|
+
`
|
|
20
|
+
|
|
21
|
+
export default async function main() {
|
|
22
|
+
const { values, files } = await parseOptions(HELP, {
|
|
23
|
+
fps: { short: 'f', type: 'string', default: String(FPS) },
|
|
24
|
+
width: { short: 'w', type: 'string', default: String(WIDTH) },
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (!files.length)
|
|
28
|
+
throw 'Missing input file'
|
|
29
|
+
|
|
30
|
+
await gif(files[0], values.fps, values.width)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function gif(file, fps, width) {
|
|
34
|
+
const { dir, name } = parse(file)
|
|
35
|
+
const outName = format({ dir, name, ext: '.gif' })
|
|
36
|
+
|
|
37
|
+
await run('ffmpeg', [
|
|
38
|
+
'-v', 'error',
|
|
39
|
+
'-i', file,
|
|
40
|
+
'-vf', `fps=${fps},scale=${width}:-1`,
|
|
41
|
+
outName,
|
|
42
|
+
])
|
|
43
|
+
}
|
package/src/hev1tohvc1.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseOptions } from './utils/parseOptions.js'
|
|
2
2
|
import { uniqueFilenameFor, overwrite } from './utils/fs-utils.js'
|
|
3
|
-
import { ffmpeg
|
|
3
|
+
import { ffmpeg } from './utils/subprocess.js'
|
|
4
4
|
import { videoAttrs } from './utils/videoAttrs.js'
|
|
5
5
|
|
|
6
6
|
|
|
@@ -12,36 +12,25 @@ DESCRIPTION
|
|
|
12
12
|
This program fixes video thumbnails not rendering in macOS
|
|
13
13
|
Finder, and fixes video not importable in Final Cut Pro. That’s done
|
|
14
14
|
by changing the container’s sample entry code from HEV1 to HVC1.
|
|
15
|
-
|
|
15
|
+
`
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
export default async function main() {
|
|
19
|
-
await
|
|
19
|
+
const { values, files } = await parseOptions(HELP)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
})
|
|
21
|
+
if (!files.length)
|
|
22
|
+
throw 'Missing input file(s)'
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
await hev1tohvc1(file)
|
|
26
|
+
console.log(file)
|
|
28
27
|
}
|
|
29
|
-
|
|
30
|
-
if (!files.length) throw new Error('Missing input file(s)')
|
|
31
|
-
|
|
32
|
-
for (const file of files)
|
|
33
|
-
try {
|
|
34
|
-
await hev1tohvc1(file)
|
|
35
|
-
console.log(file)
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
console.error(err?.message || err)
|
|
39
|
-
}
|
|
40
28
|
}
|
|
41
29
|
|
|
42
30
|
export async function hev1tohvc1(file) {
|
|
43
31
|
const v = await videoAttrs(file)
|
|
44
|
-
if (v.codec_tag_string !== 'hev1')
|
|
32
|
+
if (v.codec_tag_string !== 'hev1')
|
|
33
|
+
throw `non hev1 ${file}`
|
|
45
34
|
|
|
46
35
|
const tmp = uniqueFilenameFor(file)
|
|
47
36
|
await ffmpeg([
|
package/src/moov2front.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ffmpeg
|
|
1
|
+
import { ffmpeg } from './utils/subprocess.js'
|
|
2
2
|
import { uniqueFilenameFor, overwrite } from './utils/fs-utils.js'
|
|
3
3
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
4
|
|
|
@@ -16,37 +16,23 @@ DESCRIPTION
|
|
|
16
16
|
|
|
17
17
|
NOTES
|
|
18
18
|
Files are overwritten.
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
`
|
|
21
20
|
|
|
22
21
|
export default async function main() {
|
|
23
|
-
await
|
|
22
|
+
const { values, files } = await parseOptions(HELP)
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
})
|
|
24
|
+
if (!files.length)
|
|
25
|
+
throw 'Missing input file(s)'
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
await moov2front(file)
|
|
29
|
+
console.log(file)
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
if (!files.length) throw new Error('Missing input file(s)')
|
|
35
|
-
|
|
36
|
-
console.log('Optimizing video for progressive download…')
|
|
37
|
-
for (const file of files)
|
|
38
|
-
try {
|
|
39
|
-
await moov2front(file)
|
|
40
|
-
console.log(file)
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
console.error(err?.message || err)
|
|
44
|
-
}
|
|
45
31
|
}
|
|
46
32
|
|
|
47
33
|
export async function moov2front(file) {
|
|
48
|
-
if (!/\.(mp4|mov)$/i.test(file)) throw
|
|
49
|
-
if (await moovIsBeforeMdat(file)) throw
|
|
34
|
+
if (!/\.(mp4|mov)$/i.test(file)) throw `not mp4/mov. ${file}`
|
|
35
|
+
if (await moovIsBeforeMdat(file)) throw `no changes needed. ${file}`
|
|
50
36
|
|
|
51
37
|
const tmp = uniqueFilenameFor(file)
|
|
52
38
|
await ffmpeg([
|