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/README.md
CHANGED
|
@@ -47,7 +47,7 @@ mediasnacks <command> <args>
|
|
|
47
47
|
- `flattendir`: Moves unique files to the top dir and deletes empty dirs
|
|
48
48
|
- `qdir` Sequentially runs all *.sh files in a folder
|
|
49
49
|
- `seqcheck` Finds missing sequence number
|
|
50
|
-
- `
|
|
50
|
+
- `openrand` Opens a random file
|
|
51
51
|
- `play` Plays filtered playlist with mpv
|
|
52
52
|
|
|
53
53
|
|
package/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { avif } from './src/avif.js'
|
|
2
|
+
export { countframes } from './src/countframes.js'
|
|
3
|
+
export { detectdups } from './src/detectdups.js'
|
|
4
|
+
export { dlaudio } from './src/dlaudio.js'
|
|
5
|
+
export { dlvideo } from './src/dlvideo.js'
|
|
6
|
+
export { dropdups } from './src/dropdups.js'
|
|
7
|
+
export { edgespic } from './src/edgespic.js'
|
|
8
|
+
export { frameseq } from './src/frameseq.js'
|
|
9
|
+
export { hev1tohvc1 } from './src/hev1tohvc1.js'
|
|
10
|
+
export { moov2front } from './src/moov2front.js'
|
|
11
|
+
export { png } from './src/png.js'
|
|
12
|
+
export { play } from './src/play.js'
|
|
13
|
+
export { prores } from './src/prores.js'
|
|
14
|
+
export { qdir } from './src/qdir.js'
|
|
15
|
+
export { openrand, pickRandomFile } from './src/openrand.js'
|
|
16
|
+
export { resize } from './src/resize.js'
|
|
17
|
+
export { seqcheck } from './src/seqcheck.js'
|
|
18
|
+
export { sqcrop } from './src/sqcrop.js'
|
|
19
|
+
export { ssim } from './src/ssim.js'
|
|
20
|
+
export { unemoji } from './src/unemoji.js'
|
|
21
|
+
export { vsplit } from './src/vsplit.js'
|
|
22
|
+
export { vtrim } from './src/vtrim.js'
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"name": "mediasnacks",
|
|
3
|
+
"version": "0.27.0",
|
|
4
|
+
"description": "Utilities for optimizing and preparing videos and images",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Eric Fortis",
|
|
7
|
+
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
9
|
"mediasnacks": "src/cli.js"
|
|
10
10
|
},
|
|
@@ -14,8 +14,16 @@
|
|
|
14
14
|
"postinstall": "node install-zsh-completions.js",
|
|
15
15
|
"dev-install": "npm i -g . --ignore-scripts=false"
|
|
16
16
|
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./index.js",
|
|
20
|
+
"types": "./index.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
17
23
|
"files": [
|
|
18
24
|
"src",
|
|
25
|
+
"index.js",
|
|
26
|
+
"index.d.js",
|
|
19
27
|
"install-zsh-completions.js"
|
|
20
28
|
]
|
|
21
29
|
}
|
package/src/avif.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import { join, basename, dirname } from 'node:path'
|
|
3
2
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
3
|
import { replaceExt, lstat } from './utils/fs-utils.js'
|
|
@@ -7,22 +6,22 @@ import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
|
|
|
7
6
|
|
|
8
7
|
const HELP = `
|
|
9
8
|
SYNOPSIS
|
|
10
|
-
mediasnacks avif [-y | --overwrite] [--
|
|
9
|
+
mediasnacks avif [-y | --overwrite] [--outdir=<dir>] <images>
|
|
11
10
|
|
|
12
11
|
DESCRIPTION
|
|
13
12
|
Converts images to AVIF.
|
|
14
13
|
|
|
15
14
|
EXAMPLES
|
|
16
15
|
mediasnacks avif -y '*.png'
|
|
17
|
-
mediasnacks avif --
|
|
16
|
+
mediasnacks avif --outdir=foo/ 'a/**/*.png'
|
|
18
17
|
`.trim()
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
async function main() {
|
|
20
|
+
export default async function main() {
|
|
22
21
|
await assertUserHasFFmpeg()
|
|
23
22
|
|
|
24
23
|
const { values, files } = await parseOptions({
|
|
25
|
-
|
|
24
|
+
outdir: { type: 'string', default: '' },
|
|
26
25
|
overwrite: { short: 'y', type: 'boolean' },
|
|
27
26
|
help: { short: 'h', type: 'boolean' },
|
|
28
27
|
})
|
|
@@ -37,27 +36,24 @@ async function main() {
|
|
|
37
36
|
|
|
38
37
|
console.log('AVIF…')
|
|
39
38
|
for (const file of files)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
try {
|
|
40
|
+
await avif({
|
|
41
|
+
file,
|
|
42
|
+
outFile: join(values.outdir || dirname(file), replaceExt(basename(file), 'avif')),
|
|
43
|
+
overwrite: values.overwrite
|
|
44
|
+
})
|
|
45
|
+
console.log(file)
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error(err?.message || err)
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
async function avif({ file, outFile, overwrite }) {
|
|
52
|
+
export async function avif({ file, outFile, overwrite = false }) {
|
|
48
53
|
const stAvif = lstat(outFile)
|
|
54
|
+
if (!overwrite && stAvif?.isFile()) throw new Error(`output file exists but --overwrite=false. ${file}`)
|
|
55
|
+
if (stAvif?.mtimeMs > lstat(file)?.mtimeMs) throw new Error(`avif is newer. ${file}`)
|
|
49
56
|
|
|
50
|
-
if (!overwrite && stAvif?.isFile()) {
|
|
51
|
-
console.log('(skipped: output file exists but --overwrite=false)', file)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
if (stAvif?.mtimeMs > lstat(file)?.mtimeMs) {
|
|
55
|
-
console.log('(skipped: avif is newer)', file)
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// TODO fix transparent PNGs
|
|
60
|
-
console.log(file)
|
|
61
57
|
await ffmpeg([
|
|
62
58
|
'-y',
|
|
63
59
|
'-i', file,
|
|
@@ -66,8 +62,3 @@ async function avif({ file, outFile, overwrite }) {
|
|
|
66
62
|
outFile
|
|
67
63
|
])
|
|
68
64
|
}
|
|
69
|
-
|
|
70
|
-
main().catch(err => {
|
|
71
|
-
console.error(err.message)
|
|
72
|
-
process.exit(1)
|
|
73
|
-
})
|
package/src/cli.js
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { join } from 'node:path'
|
|
4
|
-
import { styleText } from 'node:util'
|
|
5
4
|
import { spawn } from 'node:child_process'
|
|
5
|
+
import { styleText } from 'node:util'
|
|
6
6
|
import pkgJSON from '../package.json' with { type: 'json' }
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
const COMMANDS = {
|
|
10
|
-
avif: ['avif.js', 'Converts images to AVIF'],
|
|
11
|
-
png: ['png.
|
|
12
|
-
sqcrop: ['sqcrop.js', 'Square crops images\n'],
|
|
13
|
-
|
|
14
|
-
resize: ['resize.js', 'Resizes videos or images'],
|
|
15
|
-
edgespic: ['edgespic.js', 'Extracts first and last frames'],
|
|
16
|
-
frameseq: ['frameseq.js', 'Converts video to sequence of PNGs'],
|
|
17
|
-
countframes: ['countframes.js', 'Counts frames in a video'],
|
|
18
|
-
ssim: ['ssim.js', 'Computes SSIM between two images'],
|
|
19
|
-
gif: ['gif.sh', 'Video to GIF\n'],
|
|
20
|
-
|
|
21
|
-
detectdups: ['detectdups.js', 'Detects duplicate frames in a video'],
|
|
22
|
-
dropdups: ['dropdups.js', 'Removes duplicate frames in a video'],
|
|
23
|
-
framediff: ['framediff.sh', 'Plays a video of adjacent frames diff'],
|
|
24
|
-
hev1tohvc1: ['hev1tohvc1.js', 'Fixes video thumbnails not rendering on macOS Finder'],
|
|
25
|
-
moov2front: ['moov2front.js', 'Rearranges .mov and .mp4 metadata for fast-start streaming'],
|
|
26
|
-
vconcat: ['vconcat.sh', 'Concatenates videos'],
|
|
27
|
-
vdiff: ['vdiff.sh', 'Plays a video with the difference of two videos'],
|
|
28
|
-
vsplit: ['vsplit.js', 'Splits a video into multiple clips from CSV timestamps'],
|
|
29
|
-
vtrim: ['vtrim.js', 'Trims video from start to end time'],
|
|
30
|
-
prores: ['prores.js', 'Converts video to ProRes\n'],
|
|
31
|
-
|
|
32
|
-
flattendir: ['flattendir.sh', 'Moves all files to top dir and deletes dirs'],
|
|
33
|
-
qdir: ['qdir.js', 'Sequentially runs all *.sh files in a folder'],
|
|
34
|
-
seqcheck: ['seqcheck.js', 'Finds missing sequence number'],
|
|
35
|
-
|
|
36
|
-
play: ['play.js', 'Plays filtered playlist with mpv\n'],
|
|
37
|
-
|
|
38
|
-
dlaudio: ['dlaudio.
|
|
39
|
-
dlvideo: ['dlvideo.
|
|
40
|
-
|
|
41
|
-
unemoji: ['unemoji.
|
|
42
|
-
rmcover: ['rmcover.sh', 'Removes cover art'],
|
|
10
|
+
avif: ['./avif.js', 'Converts images to AVIF'],
|
|
11
|
+
png: ['./png.js', 'Optimizes PNG images with oxipng'],
|
|
12
|
+
sqcrop: ['./sqcrop.js', 'Square crops images\n'],
|
|
13
|
+
|
|
14
|
+
resize: ['./resize.js', 'Resizes videos or images'],
|
|
15
|
+
edgespic: ['./edgespic.js', 'Extracts first and last frames'],
|
|
16
|
+
frameseq: ['./frameseq.js', 'Converts video to sequence of PNGs'],
|
|
17
|
+
countframes: ['./countframes.js', 'Counts frames in a video'],
|
|
18
|
+
ssim: ['./ssim.js', 'Computes SSIM between two images'],
|
|
19
|
+
gif: ['./gif.sh', 'Video to GIF\n'],
|
|
20
|
+
|
|
21
|
+
detectdups: ['./detectdups.js', 'Detects duplicate frames in a video'],
|
|
22
|
+
dropdups: ['./dropdups.js', 'Removes duplicate frames in a video'],
|
|
23
|
+
framediff: ['./framediff.sh', 'Plays a video of adjacent frames diff'],
|
|
24
|
+
hev1tohvc1: ['./hev1tohvc1.js', 'Fixes video thumbnails not rendering on macOS Finder'],
|
|
25
|
+
moov2front: ['./moov2front.js', 'Rearranges .mov and .mp4 metadata for fast-start streaming'],
|
|
26
|
+
vconcat: ['./vconcat.sh', 'Concatenates videos'],
|
|
27
|
+
vdiff: ['./vdiff.sh', 'Plays a video with the difference of two videos'],
|
|
28
|
+
vsplit: ['./vsplit.js', 'Splits a video into multiple clips from CSV timestamps'],
|
|
29
|
+
vtrim: ['./vtrim.js', 'Trims video from start to end time'],
|
|
30
|
+
prores: ['./prores.js', 'Converts video to ProRes\n'],
|
|
31
|
+
|
|
32
|
+
flattendir: ['./flattendir.sh', 'Moves all files to top dir and deletes dirs'],
|
|
33
|
+
qdir: ['./qdir.js', 'Sequentially runs all *.sh files in a folder'],
|
|
34
|
+
seqcheck: ['./seqcheck.js', 'Finds missing sequence number'],
|
|
35
|
+
openrand: ['./openrand.js', 'Opens a random file (macOS only)'],
|
|
36
|
+
play: ['./play.js', 'Plays filtered playlist with mpv\n'],
|
|
37
|
+
|
|
38
|
+
dlaudio: ['./dlaudio.js', 'yt-dlp best audio'],
|
|
39
|
+
dlvideo: ['./dlvideo.js', 'yt-dlp best video\n'],
|
|
40
|
+
|
|
41
|
+
unemoji: ['./unemoji.js', 'Removes emojis from filenames'],
|
|
42
|
+
rmcover: ['./rmcover.sh', 'Removes cover art'],
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export function commandsSummary() {
|
|
@@ -57,7 +57,7 @@ ${commandsSummary().map(([cmd, desc]) =>
|
|
|
57
57
|
`.trim()
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
function main() {
|
|
60
|
+
async function main() {
|
|
61
61
|
const [, , opt, ...args] = process.argv
|
|
62
62
|
|
|
63
63
|
if (opt === '-v' || opt === '--version') {
|
|
@@ -78,10 +78,16 @@ function main() {
|
|
|
78
78
|
process.exit(1)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
const cmd =
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const cmd = COMMANDS[opt][0]
|
|
82
|
+
if (cmd.endsWith('.js'))
|
|
83
|
+
(await import(cmd)).default()
|
|
84
|
+
else
|
|
85
|
+
spawn(join(import.meta.dirname, cmd), args, { stdio: 'inherit' })
|
|
86
|
+
.on('exit', process.exit)
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
if (import.meta.main)
|
|
87
|
-
main()
|
|
90
|
+
main().catch(err => {
|
|
91
|
+
console.error(err?.message || err)
|
|
92
|
+
process.exit(1)
|
|
93
|
+
})
|
package/src/countframes.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
2
|
import { assertUserHasFFmpeg } from './utils/subprocess.js'
|
|
5
3
|
import { videoAttrs } from './utils/videoAttrs.js'
|
|
@@ -24,7 +22,7 @@ EXAMPLES
|
|
|
24
22
|
`.trim()
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
async function main() {
|
|
25
|
+
export default async function main() {
|
|
28
26
|
await assertUserHasFFmpeg()
|
|
29
27
|
|
|
30
28
|
const { values, files } = await parseOptions({
|
|
@@ -40,16 +38,16 @@ async function main() {
|
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
const { fps, start, end } = values
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
41
|
+
const video = files[0]
|
|
42
|
+
if (!video) throw new Error('No video file specified')
|
|
45
43
|
|
|
46
|
-
const n = await countframes(
|
|
44
|
+
const n = await countframes({ video, fps, start, end })
|
|
47
45
|
console.log(String(n))
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
|
|
51
|
-
export async function countframes(
|
|
52
|
-
const v = await videoAttrs(
|
|
49
|
+
export async function countframes({ video, fps, start, end }) {
|
|
50
|
+
const v = await videoAttrs(video)
|
|
53
51
|
const videoDuration = parseFloat(v.duration || 0)
|
|
54
52
|
const startSecs = start ? parseTimecode(start) : 0
|
|
55
53
|
const endSecs = end ? parseTimecode(end) : videoDuration
|
|
@@ -57,10 +55,3 @@ export async function countframes(file, fps, start, end) {
|
|
|
57
55
|
const actualFps = fps ? Number(fps) : eval(v.r_frame_rate)
|
|
58
56
|
return Math.ceil(durationLimit * actualFps)
|
|
59
57
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (import.meta.main)
|
|
63
|
-
main().catch(err => {
|
|
64
|
-
console.error(err.message)
|
|
65
|
-
process.exit(1)
|
|
66
|
-
})
|
package/src/detectdups.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
2
|
import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
|
|
5
3
|
import { videoAttrs } from './utils/videoAttrs.js'
|
|
@@ -31,7 +29,7 @@ SEE ALSO
|
|
|
31
29
|
`.trim()
|
|
32
30
|
|
|
33
31
|
|
|
34
|
-
async function main() {
|
|
32
|
+
export default async function main() {
|
|
35
33
|
await assertUserHasFFmpeg()
|
|
36
34
|
|
|
37
35
|
const { values, files } = await parseOptions({
|
|
@@ -45,13 +43,11 @@ async function main() {
|
|
|
45
43
|
return
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
if (files.length !== 1)
|
|
49
|
-
throw new Error('Invalid input file. One video file must be specified. See mediasnacks detectdups --help')
|
|
50
|
-
|
|
51
|
-
const v = await videoAttrs(files[0])
|
|
46
|
+
if (files.length !== 1) throw new Error('Invalid input file. One video file must be specified. See mediasnacks detectdups --help')
|
|
52
47
|
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const video = files[0]
|
|
49
|
+
const v = await videoAttrs(video)
|
|
50
|
+
if (v.codec_type !== 'video') throw new Error('Invalid input file. Must be a video.')
|
|
55
51
|
|
|
56
52
|
const vDur = Number(v.duration)
|
|
57
53
|
|
|
@@ -67,7 +63,7 @@ async function main() {
|
|
|
67
63
|
if (isNaN(duration) || duration < 1) throw new Error(`Invalid --duration value: ${values.duration}`)
|
|
68
64
|
if ((seek + duration) > vDur) throw new Error(`Invalid analysis range. Exceeds video duration: ${vDur}`)
|
|
69
65
|
|
|
70
|
-
const dups = await detectdups(files[0], seek, duration)
|
|
66
|
+
const dups = await detectdups({ video: files[0], seek, duration })
|
|
71
67
|
const h = deltaHistogram(dups)
|
|
72
68
|
const report = {
|
|
73
69
|
n: maxFreqKey(h),
|
|
@@ -80,12 +76,12 @@ async function main() {
|
|
|
80
76
|
console.log(JSON.stringify(report, null, 2))
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
export async function detectdups(video, seek, duration) {
|
|
79
|
+
export async function detectdups({ video, seek, duration }) {
|
|
84
80
|
const { stderr } = await ffmpeg([
|
|
85
81
|
'-v', 'info',
|
|
86
82
|
'-stats',
|
|
87
|
-
'-ss', seek,
|
|
88
|
-
'-t', duration,
|
|
83
|
+
seek ? ['-ss', seek] : [],
|
|
84
|
+
duration ? ['-t', duration] : [],
|
|
89
85
|
'-i', video,
|
|
90
86
|
'-vf', [
|
|
91
87
|
'tblend=all_mode=difference',
|
|
@@ -93,7 +89,7 @@ export async function detectdups(video, seek, duration) {
|
|
|
93
89
|
'showinfo',
|
|
94
90
|
].join(','),
|
|
95
91
|
'-f', 'null', '-',
|
|
96
|
-
])
|
|
92
|
+
].flat())
|
|
97
93
|
|
|
98
94
|
const reNearBlackFrames = /n:\s*(\d+).*?mean:\[0].*?stdev:\[([0-9.]+)]/
|
|
99
95
|
const dupFrames = []
|
|
@@ -133,10 +129,3 @@ function maxFreqKey(histogram) {
|
|
|
133
129
|
? Number(maxKey)
|
|
134
130
|
: null
|
|
135
131
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (import.meta.main)
|
|
139
|
-
main().catch(err => {
|
|
140
|
-
console.error(err.message || err)
|
|
141
|
-
process.exit(1)
|
|
142
|
-
})
|
package/src/dlaudio.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parseOptions } from './utils/parseOptions.js'
|
|
2
|
+
import { runSilently } from './utils/subprocess.js'
|
|
3
|
+
import { unemoji } from './unemoji.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const HELP = `
|
|
7
|
+
SYNOPSIS
|
|
8
|
+
mediasnacks dlaudio <url>
|
|
9
|
+
|
|
10
|
+
DESCRIPTION
|
|
11
|
+
yt-dlp best m4a
|
|
12
|
+
`.trim()
|
|
13
|
+
|
|
14
|
+
export default async function main() {
|
|
15
|
+
const { values, positionals } = await parseOptions({
|
|
16
|
+
help: { short: 'h', type: 'boolean' }
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
if (values.help || !positionals[0]) {
|
|
20
|
+
console.log(HELP)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const f = await dlaudio(positionals[0])
|
|
25
|
+
console.log(f)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function dlaudio(url) {
|
|
29
|
+
const f = (await runSilently('yt-dlp', [
|
|
30
|
+
'--print', 'filename',
|
|
31
|
+
'--no-simulate',
|
|
32
|
+
'-o', '%(title)s.%(ext)s',
|
|
33
|
+
'-f', 'bestaudio[ext=m4a]/bestaudio',
|
|
34
|
+
url
|
|
35
|
+
])).stdout.trim()
|
|
36
|
+
|
|
37
|
+
return await unemoji(f) || f
|
|
38
|
+
}
|
package/src/dlvideo.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parseOptions } from './utils/parseOptions.js'
|
|
2
|
+
import { run } from './utils/subprocess.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const HELP = `
|
|
6
|
+
SYNOPSIS
|
|
7
|
+
mediasnacks dlvideo <url>
|
|
8
|
+
|
|
9
|
+
DESCRIPTION
|
|
10
|
+
yt-dlp best mp4
|
|
11
|
+
`.trim()
|
|
12
|
+
|
|
13
|
+
export default async function main() {
|
|
14
|
+
const { values, positionals } = await parseOptions({
|
|
15
|
+
help: { short: 'h', type: 'boolean' }
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
if (values.help || !positionals[0]) {
|
|
19
|
+
console.log(HELP)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await dlvideo(positionals[0])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function dlvideo(url) {
|
|
27
|
+
await run('yt-dlp', [
|
|
28
|
+
'-o', '%(title)s.%(ext)s',
|
|
29
|
+
'-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4',
|
|
30
|
+
url
|
|
31
|
+
])
|
|
32
|
+
}
|
package/src/dropdups.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import { resolve, parse, format } from 'node:path'
|
|
4
|
-
|
|
5
2
|
import { parseOptions } from './utils/parseOptions.js'
|
|
6
3
|
import { ffmpeg, assertUserHasFFmpeg, run } from './utils/subprocess.js'
|
|
7
4
|
import { ProresProfiles } from './prores.js'
|
|
@@ -30,7 +27,7 @@ EXAMPLES
|
|
|
30
27
|
`.trim()
|
|
31
28
|
|
|
32
29
|
|
|
33
|
-
async function main() {
|
|
30
|
+
export default async function main() {
|
|
34
31
|
await assertUserHasFFmpeg()
|
|
35
32
|
|
|
36
33
|
const { values, files } = await parseOptions({
|
|
@@ -55,7 +52,7 @@ async function main() {
|
|
|
55
52
|
await dropdups(resolve(file), dupFrameNum)
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
async function dropdups(video, dupFrameNum) {
|
|
55
|
+
export async function dropdups(video, dupFrameNum) {
|
|
59
56
|
await run('ffmpeg', [
|
|
60
57
|
'-v', 'error',
|
|
61
58
|
'-stats',
|
|
@@ -77,8 +74,3 @@ function makeOutputPath(video) {
|
|
|
77
74
|
? format({ dir, name: `${name}.dedup`, ext: '.mov' })
|
|
78
75
|
: format({ dir, name, ext: '.mov' })
|
|
79
76
|
}
|
|
80
|
-
|
|
81
|
-
main().catch(err => {
|
|
82
|
-
console.error(err.message || err)
|
|
83
|
-
process.exit(1)
|
|
84
|
-
})
|
package/src/edgespic.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import { basename, extname, join, parse } from 'node:path'
|
|
4
2
|
|
|
5
3
|
import { mkDir } from './utils/fs-utils.js'
|
|
4
|
+
import { videoAttrs } from './utils/videoAttrs.js'
|
|
6
5
|
import { parseOptions } from './utils/parseOptions.js'
|
|
7
6
|
import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
|
|
8
|
-
import { videoAttrs } from './utils/videoAttrs.js'
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
const HELP = `
|
|
@@ -24,7 +22,7 @@ EXAMPLES
|
|
|
24
22
|
`.trim()
|
|
25
23
|
|
|
26
24
|
|
|
27
|
-
async function main() {
|
|
25
|
+
export default async function main() {
|
|
28
26
|
await assertUserHasFFmpeg()
|
|
29
27
|
|
|
30
28
|
const { values, files } = await parseOptions({
|
|
@@ -38,14 +36,11 @@ async function main() {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
const width = Number(values['width'])
|
|
41
|
-
if (width <= 0 || !Number.isInteger(width))
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!files.length)
|
|
45
|
-
throw new Error('No video files specified')
|
|
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')
|
|
46
41
|
|
|
47
42
|
const outDir = join(parse(files[0]).dir, 'edgespic')
|
|
48
|
-
await mkDir(outDir)
|
|
43
|
+
await mkDir(outDir)
|
|
49
44
|
|
|
50
45
|
console.log('Extracting edge frames…')
|
|
51
46
|
for (const file of files)
|
|
@@ -53,7 +48,7 @@ async function main() {
|
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
|
|
56
|
-
async function edgespic(video, width, outDir) {
|
|
51
|
+
export async function edgespic(video, width, outDir) {
|
|
57
52
|
const { r_frame_rate } = await videoAttrs(video)
|
|
58
53
|
const name = basename(video, extname(video))
|
|
59
54
|
|
|
@@ -74,9 +69,3 @@ async function edgespic(video, width, outDir) {
|
|
|
74
69
|
join(outDir, `${name}_last.png`)
|
|
75
70
|
])
|
|
76
71
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
main().catch(err => {
|
|
80
|
-
console.error(err.message)
|
|
81
|
-
process.exit(1)
|
|
82
|
-
})
|
package/src/frameseq.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import { basename, extname, join, parse } from 'node:path'
|
|
4
2
|
|
|
5
3
|
import { mkDir } from './utils/fs-utils.js'
|
|
@@ -29,13 +27,14 @@ EXAMPLES
|
|
|
29
27
|
`.trim()
|
|
30
28
|
|
|
31
29
|
|
|
32
|
-
async function main() {
|
|
30
|
+
export default async function main() {
|
|
33
31
|
await assertUserHasFFmpeg()
|
|
34
32
|
|
|
35
33
|
const { values, files } = await parseOptions({
|
|
36
|
-
fps: { type: 'string', default: '' },
|
|
34
|
+
fps: { short: 'f', type: 'string', default: '' },
|
|
37
35
|
start: { short: 's', type: 'string', default: '' },
|
|
38
36
|
end: { short: 'e', type: 'string', default: '' },
|
|
37
|
+
outdir: { type: 'string', default: '' },
|
|
39
38
|
help: { short: 'h', type: 'boolean' }
|
|
40
39
|
})
|
|
41
40
|
|
|
@@ -44,21 +43,21 @@ async function main() {
|
|
|
44
43
|
return
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const { fps, start, end } = values
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
46
|
+
const { fps, start, end, outdir } = values
|
|
47
|
+
const video = files[0]
|
|
48
|
+
if (!video) throw new Error('No video files specified')
|
|
50
49
|
if (fps && isNaN(parseFloat(fps))) throw new Error('Invalid --fps')
|
|
51
50
|
if (start && isNaN(parseFloat(start))) throw new Error('Invalid --start')
|
|
52
51
|
if (end && isNaN(parseFloat(end))) throw new Error('Invalid --end')
|
|
53
52
|
|
|
54
|
-
const nFrames = await countframes(
|
|
53
|
+
const nFrames = await countframes({ video, fps, start, end })
|
|
55
54
|
const pad = String(nFrames).length
|
|
56
|
-
await frameseq(
|
|
55
|
+
await frameseq({ video, fps, start, end, pad, outdir })
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
async function frameseq(video, fps, start, end, pad) {
|
|
58
|
+
export async function frameseq({ video, fps, start, end, pad, outdir }) {
|
|
60
59
|
const name = basename(video, extname(video))
|
|
61
|
-
const outDir = join(parse(video).dir, name)
|
|
60
|
+
const outDir = outdir || join(parse(video).dir, name)
|
|
62
61
|
await mkDir(outDir)
|
|
63
62
|
await ffmpeg([
|
|
64
63
|
start ? ['-ss', start] : [],
|
|
@@ -68,8 +67,3 @@ async function frameseq(video, fps, start, end, pad) {
|
|
|
68
67
|
join(outDir, `${name}_%0${pad}d.png`)
|
|
69
68
|
].flat())
|
|
70
69
|
}
|
|
71
|
-
|
|
72
|
-
main().catch(err => {
|
|
73
|
-
console.error(err.message)
|
|
74
|
-
process.exit(1)
|
|
75
|
-
})
|
package/src/hev1tohvc1.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import { parseOptions } from './utils/parseOptions.js'
|
|
4
2
|
import { uniqueFilenameFor, overwrite } from './utils/fs-utils.js'
|
|
5
3
|
import { ffmpeg, assertUserHasFFmpeg } from './utils/subprocess.js'
|
|
@@ -17,7 +15,7 @@ DESCRIPTION
|
|
|
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({
|
|
@@ -29,21 +27,22 @@ async function main() {
|
|
|
29
27
|
return
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
if (!files.length)
|
|
33
|
-
throw new Error(HELP)
|
|
30
|
+
if (!files.length) throw new Error('Missing input file(s)')
|
|
34
31
|
|
|
35
32
|
for (const file of files)
|
|
36
|
-
|
|
33
|
+
try {
|
|
34
|
+
await hev1tohvc1(file)
|
|
35
|
+
console.log(file)
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error(err?.message || err)
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
async function hev1tohvc1(file) {
|
|
42
|
+
export async function hev1tohvc1(file) {
|
|
40
43
|
const v = await videoAttrs(file)
|
|
41
|
-
if (v.codec_tag_string !== 'hev1') {
|
|
42
|
-
console.log('(skipped: non hev1)', file)
|
|
43
|
-
return
|
|
44
|
-
}
|
|
44
|
+
if (v.codec_tag_string !== 'hev1') throw new Error(`non hev1 ${file}`)
|
|
45
45
|
|
|
46
|
-
console.log(file)
|
|
47
46
|
const tmp = uniqueFilenameFor(file)
|
|
48
47
|
await ffmpeg([
|
|
49
48
|
'-i', file,
|
|
@@ -53,9 +52,3 @@ async function hev1tohvc1(file) {
|
|
|
53
52
|
])
|
|
54
53
|
await overwrite(tmp, file)
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
main().catch(err => {
|
|
59
|
-
console.error(err.message)
|
|
60
|
-
process.exit(1)
|
|
61
|
-
})
|