Compare commits

...

6 Commits

19 changed files with 323 additions and 118 deletions

View File

@@ -1,4 +1,4 @@
SHELL:=/nix/store/zicq9x6aznw7x202564p8iy0rm04py6l-nushell-0.89.0/bin/nu
SHELL:=/nix/store/kx4ic1nmgsfh3qhr74vxq7g97pal7mn4-nushell-0.108.0/bin/nu
all: README.md

206
README.md
View File

@@ -18,7 +18,7 @@ Utility commands for working with ffmpeg in nushell.
The `ffmpeg` and `ffprobe` commands are required to be installed and available
in your path; they are not installed for you.
Currently only nushell version 0.89.0 is supported.
Currently only nushell version 0.108.0 is supported.
After that, clone this repository and add the following code to your scripts,
or to your `config.nu` file:
@@ -32,8 +32,9 @@ use <path-to-repository>/filters *
### Commands
| name | usage |
| name | description |
| --------------------- | ---------------------------------------------------------------- |
| ffprobe banner | Print a banner for Nushell with information about the project |
| ffprobe | Run ffprobe on a list of files and return the output as a table. |
| ffprobe dimensions | Get the dimensions of a video stream |
| ffprobe streams | Retrieve all the streams from a list of ffprobe outputs |
@@ -41,76 +42,115 @@ use <path-to-repository>/filters *
| ffprobe streams video | Retrieve all the video streams from a list of ffprobe outputs |
## FFMpeg
### Examples
### Supported Filters
| name | usage |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| crop | Crop the input video to given dimensions. |
| format | Convert the input video to one of the specified pixel formats. Libavfilter will try to pick one that is suitable as input to the next filter. |
| fps | Convert the video to specified constant frame rate by duplicating or dropping frames as necessary. |
| loop | loop video frames |
| overlay | Overlay one video on top of another. |
| settb | Set the timebase to use for the output frames timestamps. It is mainly useful for testing timebase configuration. |
| split | |
| vflip | Flip the input video vertically. |
| xfade | Apply cross fade from one input video stream to another input video stream. The cross fade is applied for specified duration. Both inputs must be constant frame-rate and have the same resolution, pixel format, frame rate and timebase. |
## Examples
```nu
# Return a table from ffprobe
> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4
╭───┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬──────────────╮
# │ streams │ format
├───┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────────────┤
0 ╭───┬────────────┬───────────────────────────────────────────┬─────────┬────────────┬──────────────────┬────────────┬───────┬────────┬─────────────┬──────────────┬─────────────────┬────────────┬──────────────┬─────╮ {record 11
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_string │ codec_tag │ width │ height │ coded_width │ coded_height │ closed_captions │ film_grain │ has_b_frames │ ... │ │ fields} │
├───┼────────────┼───────────────────────────────────────────┼─────────┼────────────┼──────────────────┼────────────┼───────┼────────┼─────────────┼──────────────┼─────────────────┼────────────┼──────────────┼─────┤
0 h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 Main video avc1 0x31637661 1280 720 1280 720 0 0 0 ...
1 aac AAC (Advanced Audio Coding) LC audio mp4a 0x6134706d ...
╰───┴────────────┴───────────────────────────────────────────┴─────────┴──────────────────────────────────────────────────────────────────────┴──────────────┴─────────────────┴────────────┴──────────────┴─────╯
╰───┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────╯
╭───┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
# │ streams │ f
o
r
m
a
t
├───┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────┼───┤
0 ╭───┬────────────┬───────────────────────────────────────────┬───────────────────────────────────────── {
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_str │ ... │ │ r │
ing e
├───┼────────────┼───────────────────────────────────────────┼─────────┼────────────┼───────────────┼─────┤ c
0 h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 Main video avc1 ... o
1 aac AAC (Advanced Audio Coding) LC audio mp4a ... r
╰───┴────────────┴───────────────────────────────────────────┴─────────┴────────────┴───────────────┴─────╯ d
1
2
f
i
e
l
d
s
}
╰───┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────┴───╯
```
```nu
# Use ffprobe on mutliple files at once
> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 https://sample-videos.com/video321/mkv/720/big_buck_bunny_720p_1mb.mkv
╭───┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬──────────────╮
# │ streams │ format
├───┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────────────┤
0 ╭───┬────────────┬───────────────────────────────────────────┬─────────┬────────────┬──────────────────┬────────────┬───────┬────────┬─────────────┬──────────────┬─────────────────┬────────────┬──────────────┬─────╮ {record 11
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_string │ codec_tag │ width │ height │ coded_width │ coded_height │ closed_captions │ film_grain │ has_b_frames │ ... │ │ fields} │
├───┼────────────┼───────────────────────────────────────────┼─────────┼────────────┼──────────────────┼────────────┼───────┼────────┼─────────────┼──────────────┼─────────────────┼────────────┼──────────────┼─────┤
0 h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 Main video avc1 0x31637661 1280 720 1280 720 0 0 0 ...
1 aac AAC (Advanced Audio Coding) LC audio mp4a 0x6134706d ...
╰───┴────────────┴───────────────────────────────────────────┴─────────┴──────────────────────────────────────────────────────────────────────┴──────────────┴─────────────────┴────────────┴──────────────┴─────╯
1 ╭───┬────────────┬─────────────────────────────┬────────────────┬────────────┬──────────────────┬───────────┬───────┬────────┬─────────────┬──────────────┬─────────────────┬────────────┬──────────────┬─────╮ {record 10
# │ codec_name │ codec_long_name profile │ codec_type │ codec_tag_string │ codec_tag │ width │ height │ coded_width │ coded_height │ closed_captions │ film_grain │ has_b_frames │ ... │ │ fields} │
├───┼────────────┼─────────────────────────────┼────────────────┼────────────┼──────────────────┼───────────┼───────┼────────┼─────────────┼──────────────┼─────────────────┼────────────┼──────────────┼─────┤
0 mpeg4 MPEG-4 part 2 Simple Profile video [0][0][0][0] 0x0000 1280 720 1280 720 0 0 0 ...
1 aac AAC (Advanced Audio Coding) LC audio [0][0][0][0] 0x0000 ...
╰───┴────────────┴─────────────────────────────────────────────┴────────────┴──────────────────┴───────────┴───────┴────────┴─────────────┴──────────────┴─────────────────┴────────────┴──────────────┴─────╯
╰───┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────╯
╭───┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
# │ streams │ f
o
r
m
a
t
├───┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────┼───┤
0 ╭───┬────────────┬───────────────────────────────────────────┬───────────────────────────────────────── {
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_str │ ... │ │ r │
ing e
├───┼────────────┼───────────────────────────────────────────┼─────────┼────────────┼───────────────┼─────┤ c
0 h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 Main video avc1 ... o
1 aac AAC (Advanced Audio Coding) LC audio mp4a ... r
╰───┴────────────┴────────────────────────────────────────────────────┴────────────┴───────────────┴─────╯ d
1
2
f
i
e
l
d
s
}
1 ╭───┬────────────┬─────────────────────────────┬────────────────┬────────────┬──────────────────┬───┬─────╮ {
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_string │ c │ ... │ │ r │
o e
d c
e o
c r
_ d
t
a 1
g 1
├───┼────────────┼─────────────────────────────┼────────────────┼────────────┼──────────────────┼───┼─────┤
0 mpeg4 MPEG-4 part 2 Simple Profile video [0][0][0][0] 0 ... f
x i
0 e
0 l
0 d
0 s
1 aac AAC (Advanced Audio Coding) LC audio [0][0][0][0] 0 ... }
x
0
0
0
0
╰───┴────────────┴─────────────────────────────┴────────────────┴────────────┴──────────────────┴───┴─────╯
╰───┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────┴───╯
```
```nu
# Extract the video streams from a video
> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 | streams video
╭───┬────────────┬───────────────────────────────────────────┬─────────┬────────────┬──────────────────┬────────────┬───────┬────────┬─────────────┬──────────────┬─────────────────┬────────────┬──────────────┬─────────────────────┬─────╮
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_string │ codec_tag │ width │ height │ coded_width │ coded_height │ closed_captions │ film_grain │ has_b_frames │ sample_aspect_ratio │ ... │
├────┼────────────┼───────────────────────────────────────────┼─────────┼────────────┼──────────────────┼────────────┼───────┼────────┼─────────────┼──────────────┼─────────────────┼────────────┼──────────────┼─────────────────────┼─────┤
0 h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 Main video avc1 0x31637661 1280 720 1280 720 0 0 0 1:1 ...
╰────┴────────────┴───────────────────────────────────────────┴─────────┴────────────┴──────────────────┴────────────┴───────┴────────┴─────────────┴──────────────┴─────────────────┴────────────┴──────────────┴─────────────────────┴─────╯
╭───┬────────────┬───────────────────────────────────────────┬─────────┬────────────┬──────────────────┬────────┬─────╮
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_string │ codec_ │ ... │
tag
├───┼────────────┼───────────────────────────────────────────┼─────────┼────────────┼──────────────────┼────────┼─────┤
0 h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 Main video avc1 0x3163 ...
7661
╰───┴────────────┴───────────────────────────────────────────┴─────────┴────────────┴──────────────────┴────────┴─────╯
# Extract the audio streams from a video
> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 | streams audio
╭───┬────────────┬─────────────────────────────┬─────────┬────────────┬──────────────────┬────────────┬────────────┬─────────────┬──────────┬────────────────┬─────────────────┬─────────────────┬─────┬──────────────┬────────────────┬─────╮
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_string │ codec_tag │ sample_fmt │ sample_rate │ channels │ channel_layout │ bits_per_sample │ initial_padding │ id │ r_frame_rate │ avg_frame_rate │ ... │
├───┼────────────┼─────────────────────────────┼─────────┼────────────┼──────────────────┼────────────┼────────────┼─────────────┼──────────┼────────────────┼─────────────────┼─────────────────┼─────┼──────────────┼────────────────┼─────┤
1 aac AAC (Advanced Audio Coding) LC audio mp4a 0x6134706d fltp 48000 6 5.1 0 0 0x2 0/0 0/0 ...
╰───┴────────────┴─────────────────────────────┴─────────┴────────────┴──────────────────┴────────────┴────────────┴─────────────┴──────────┴────────────────┴─────────────────┴─────────────────┴─────┴──────────────┴────────────────┴─────╯
╭───┬────────────┬─────────────────────────────┬─────────┬────────────┬──────────────────┬────────────┬─────────┬─────╮
# │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_string │ codec_tag │ sample_ │ ... │
fmt
├───┼────────────┼─────────────────────────────┼─────────┼────────────┼──────────────────┼────────────┼─────────┼─────┤
1 aac AAC (Advanced Audio Coding) LC audio mp4a 0x6134706d fltp ...
╰───┴────────────┴─────────────────────────────┴─────────┴────────────┴──────────────────┴────────────┴─────────┴─────╯
```
```nu
@@ -121,3 +161,59 @@ use <path-to-repository>/filters *
height 720
╰────────┴──────╯
```
## FFMpeg
### Supported Filters
| name | description |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| banner | Print a banner for Nushell with information about the project |
| crop | Crop the input video to given dimensions. |
| format | Convert the input video to one of the specified pixel formats. Libavfilter will try to pick one that is suitable as input to the next filter. |
| fps | Convert the video to specified constant frame rate by duplicating or dropping frames as necessary. |
| overlay | Overlay one video on top of another. |
| settb | Set the timebase to use for the output frames timestamps. It is mainly useful for testing timebase configuration. |
| split | |
| vflip | Flip the input video vertically. |
| vloop | loop video frames Same as the `loop` filter in ffmpeg, but had to be renamed due to collisions with the [nushell builtin](https://www.nushell.sh/commands/docs/loop.html) |
| xfade | Apply cross fade from one input video stream to another input video stream. The cross fade is applied for specified duration. Both inputs must be constant frame-rate and have the same resolution, pixel format, frame rate and timebase. |
### Examples
```nu
# Re-encode the video to 30fps
(
fmpeg cmd ['https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'] []
| fps 30
| ffmpeg run
)
Would run:
╭───┬────────────────────────────────────────────────────────────────────────╮
0 ffmpeg
1 -i
2 https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4
3 -filter_complex
4 fps=fps=30
╰───┴────────────────────────────────────────────────────────────────────────╯
```
```nu
# Re-encode the video to 30fps, specifying input and output streams
(
fmpeg cmd ['https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'] []
| filterchain --input ['0'] --output ['video'] { fps 30 }
| ffmpeg run
)
Would run:
╭───┬────────────────────────────────────────────────────────────────────────╮
0 ffmpeg
1 -i
2 https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4
3 -filter_complex
4 [0]fps=fps=30[video]
╰───┴────────────────────────────────────────────────────────────────────────╯
```

View File

@@ -41,13 +41,8 @@ use <path-to-repository>/filters *
{{ ffprobe }}
## FFMpeg
### Examples
### Supported Filters
{{ filters }}
## Examples
```nu
{{ example "ffprobe" -}}
@@ -65,3 +60,19 @@ use <path-to-repository>/filters *
```nu
{{ example "dimensions" -}}
```
## FFMpeg
### Supported Filters
{{ filters }}
### Examples
```nu
{{ example "convert-to-30-fps" -}}
```
```nu
{{ example "convert-to-30-fps-with-filterchain" -}}
```

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env -S nu -n
use ../ffmpeg.nu;
use ../ffmpeg.nu [filterchain];
use ../filters.nu *;
# Re-encode the video to 30fps
def main []: nothing -> any {
print '# Re-encode the video to 30fps, specifying input and output streams'
print '('
print ' fmpeg cmd ['https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'] []'
print " | filterchain --input ['0'] --output ['video'] { fps 30 }"
print ' | ffmpeg run'
print ')'
print ''
print 'Would run:'
(
ffmpeg cmd ['https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'] []
| filterchain --input ['0'] --output ['video'] { fps 30 }
| ffmpeg cmd to-args
| ['ffmpeg' ...$in]
)
}

24
examples/convert-to-30-fps.nu Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env -S nu -n
use ../ffmpeg.nu;
use ../filters.nu *;
# Re-encode the video to 30fps
def main []: nothing -> any {
print '# Re-encode the video to 30fps'
print '('
print ' fmpeg cmd ['https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'] []'
print ' | fps 30'
print ' | ffmpeg run'
print ')'
print ''
print 'Would run:'
(
ffmpeg cmd ['https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'] []
| fps 30
| ffmpeg cmd to-args
| ['ffmpeg' ...$in]
)
}

View File

@@ -3,8 +3,12 @@
use ../ffprobe ["main" "dimensions" "streams video"];
def main [] {
echo "# Extract the dimensions of a video stream"
echo "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 | streams video | first | dimensions"
ffprobe $'($env.FILE_PWD)/videos/sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'
| streams video | first | dimensions
print "# Extract the dimensions of a video stream"
print "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 | streams video | first | dimensions"
(
ffprobe $'($env.FILE_PWD)/videos/sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'
| streams video
| first
| dimensions
)
}

View File

@@ -3,8 +3,8 @@
use ../ffprobe;
def main [] {
echo "# Use ffprobe on mutliple files at once"
echo "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 https://sample-videos.com/video321/mkv/720/big_buck_bunny_720p_1mb.mkv"
print "# Use ffprobe on mutliple files at once"
print "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 https://sample-videos.com/video321/mkv/720/big_buck_bunny_720p_1mb.mkv"
(
ffprobe
$'($env.FILE_PWD)/videos/sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'

View File

@@ -3,7 +3,7 @@
use ../ffprobe;
def main [] {
echo "# Return a table from ffprobe"
echo "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4"
ffprobe $'($env.FILE_PWD)/videos/sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'
print "# Return a table from ffprobe"
print "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4"
ffprobe $'($env.FILE_PWD)/videos/sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4' | table -e
}

View File

@@ -3,8 +3,8 @@
use ../ffprobe ["main" "streams audio"];
def main [] {
echo "# Extract the audio streams from a video"
echo "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 | streams audio"
print "# Extract the audio streams from a video"
print "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 | streams audio"
ffprobe $'($env.FILE_PWD)/videos/sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'
| streams audio
}

View File

@@ -3,8 +3,8 @@
use ../ffprobe ["main" "streams video"];
def main [] {
echo "# Extract the video streams from a video"
echo "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 | streams video"
print "# Extract the video streams from a video"
print "> ffprobe https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4 | streams video"
ffprobe $'($env.FILE_PWD)/videos/sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4'
| streams video
}

View File

@@ -80,10 +80,16 @@ export def "parse filterchain" [
export def "parse filter" [
]: string -> table<name: string params: table<param: string, value: string>> {
parse --regex '^\s*(?:\[(?<input>[^\s]+)\]\s*)?(?<name>[^=\s\[]+)\s*(?:=(?<params>[^\[\s,;]*)\s*)?(?:\[(?<output>[^\s,;]+)\])?' | first | update params {
parse --regex `(?:(?<param>[^=]+)=)?(?<value>[^:]+):?`
} | update input { split row '][' | filter { is-not-empty }
} | update output { split row '][' | filter { is-not-empty } }
(
parse --regex '^\s*(?:\[(?<input>[^\s]+)\]\s*)?(?<name>[^=\s\[]+)\s*(?:=(?<params>[^\[\s,;]*)\s*)?(?:\[(?<output>[^\s,;]+)\])?'
| first
| update params {
default '' | parse --regex `(?:(?<param>[^=]+)=)?(?<value>[^:]+):?`
}
| update params { update param { if ($in == null) { '' } else { $in} } }
| update input { default '' | split row '][' | where { is-not-empty } }
| update output { default '' | split row '][' | where { is-not-empty } }
)
}
# TODO: Remove export
@@ -95,7 +101,7 @@ def "filterchain to-string" []: table -> string {
$in | each { filter to-string }| str join ','
}
def "filter to-string" []: record<input: list<string> name: string params: table<name: string value: string> output: list<string>> -> string {
def "filter to-string" []: record<input: list<string> name: string params: table<param: string value: any> output: list<string>> -> string {
$in | update input {
str join ']['
} | update output {
@@ -156,7 +162,7 @@ export def complex-filter [
--output (-o): list<string> = []
name: string
params: record = {}
]: nothing -> record<input: list<string> name: string params: table<param: string, value: string> output: list<string>> {
]: nothing -> record<input: list<string> name: string params: table<param: string, value: any> output: list<string>> {
{
input: $input
name: $name
@@ -171,12 +177,13 @@ export def append-complex-filter [
--output (-o): list<string> = []
name: string
params: record = {}
] {
$in | cmd filters append [
(complex-filter --input $input --output $output $name $params)
]
}
]: [record -> record, nothing -> record] {
let before = $in;
let complex_filter = complex-filter --input $input --output $output $name $params;
def is-not-empty []: any -> bool {
is-empty | not $in
if ($before | is-empty) {
$complex_filter
} else {
$before | cmd filters append [$complex_filter]
}
}

26
ffmpeg_test.nu Normal file → Executable file
View File

@@ -1,8 +1,26 @@
#!/usr/bin/env -S nu -n
use std [assert];
use ffmpeg.nu *;
use filters.nu *;
def main [] {
let test_commands = (
scope commands
| where ($it.type == "custom")
and ($it.description | str starts-with "[test]")
and not ($it.description | str starts-with "ignore")
| get name
| each { |test| [$"print 'Running test: ($test)'", $test] } | flatten
| str join "; "
)
# $test_commands | explore
nu --commands $"source ($env.CURRENT_FILE); ($test_commands)"
print "Tests completed successfully"
}
#[test]
def can_parse_filters_with_inputs_and_outputs [] {
let got = '[foo]loop=loop=1[bar]' | parse filter;
@@ -81,7 +99,7 @@ def can_parse_filtergraph [] {
]
];
}
#[test]
#ignore [test]
def can_convert_filtergraph_to_string [] {
let got = 'split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2' | parse filtergraph | filtergraph to-string;
@@ -100,7 +118,7 @@ def boolean_params_are_converted_to_1_or_0 [] {
#[test]
def without_filterchain_chains_are_concated [] {
let got = (cmd ['INPUT'] ['OUTPUT'] | fps 25 | loop 2 1);
let got = (cmd ['INPUT'] ['OUTPUT'] | fps 25 | vloop 2 1);
assert equal $got {
input: ['INPUT']
@@ -140,7 +158,7 @@ def without_filterchain_chains_are_concated [] {
}
#[test]
def filterchain_concats_filters [] {
let got = (cmd ['INPUT'] ['OUTPUT'] | filterchain { fps 25 -i ['in'] | loop 2 1 -o ['out']});
let got = (cmd ['INPUT'] ['OUTPUT'] | filterchain { fps 25 -i ['in'] | vloop 2 1 -o ['out']});
assert equal $got {
input: ['INPUT']
@@ -179,7 +197,7 @@ def filterchain_concats_filters [] {
#[test]
def filterchain_appends_current_filter [] {
let got = (cmd ['INPUT'] ['OUTPUT'] | fps 12 | filterchain { fps 25 -i ['in'] | loop 2 1 -o ['out']});
let got = (cmd ['INPUT'] ['OUTPUT'] | fps 12 | filterchain { fps 25 -i ['in'] | vloop 2 1 -o ['out']});
assert equal $got {
input: ['INPUT']

View File

@@ -31,6 +31,6 @@ export def "streams audio" []: table<streams: table, format: record> -> table {
}
# Get the dimensions of a video stream
export def "dimensions" []: table<streams: table, format: record> -> record<width: int, height: int> {
export def "dimensions" []: record<width: int, height: int> -> record<width: int, height: int> {
$in | select width height
}

View File

@@ -5,9 +5,11 @@ use std [assert];
use ./ffmpeg.nu ["append-complex-filter"]
# loop video frames
export def loop [
--input (-i): list<string>: = []
--output (-o): list<string>: = []
# Same as the `loop` filter in ffmpeg, but
# had to be renamed due to collisions with the [nushell builtin](https://www.nushell.sh/commands/docs/loop.html)
export def "vloop" [
--input (-i): list<string> = []
--output (-o): list<string> = []
loop: int # Set the number of loops. Setting this value to -1 will result in infinite loops. Default is 0.
size: int # Set maximal size in number of frames. Default is 0.
--start (-s): int # Set first frame of loop. Default is 0.
@@ -23,8 +25,8 @@ export def loop [
# Convert the video to specified constant frame rate by duplicating or dropping frames as necessary.
export def fps [
--input (-i): list<string>: = []
--output (-o): list<string>: = []
--input (-i): list<string> = []
--output (-o): list<string> = []
--start-time (-s) # Assume the first PTS should be the given value, in seconds.
# This allows for padding/trimming at the start of stream. By default, no assumption is made about the first frames expected PTS, so no padding or trimming is done. For example, this could be set to 0 to pad the beginning with duplicates of the first frame if a video stream starts after the audio stream or to trim any frames with a negative PTS.
--round (-r): string@fps-round # Timestamp (PTS) rounding method. use completion to view available options.
@@ -63,8 +65,8 @@ def fps-fps [] {
# Set the timebase to use for the output frames timestamps. It is mainly useful for testing timebase configuration.
export def settb [
--input (-i): list<string>: = []
--output (-o): list<string>: = []
--input (-i): list<string> = []
--output (-o): list<string> = []
timebase # The value for tb is an arithmetic expression representing a rational. The expression can contain the constants "AVTB" (the default timebase), "intb" (the input timebase) and "sr" (the sample rate, audio only). Default value is "intb".
] {
(append-complex-filter settb {expr: $timebase} -i $input -o $output)
@@ -81,16 +83,16 @@ def "list to-pipe-separated-string" []: list -> string {
# Convert the input video to one of the specified pixel formats. Libavfilter will try to pick one that is suitable as input to the next filter.
export def format [
--input (-i): list<string>: = []
--output (-o): list<string>: = []
--input (-i): list<string> = []
--output (-o): list<string> = []
--pix-fmts (-p): list<string> # A list of pixel format names
--color-spaces (-c): list<string> # A list of color space names
--color-ranges (-r): list<string> # A list of color range names
] {
(append-complex-filter format {
pix_fmts: ($pix_fmts | list to-pipe-separated-string)
color_spaces: ($color_spaces | list to-pipe-separated-string)
color_ranges: ($color_ranges | list to-pipe-separated-string)
color_spaces: ($color_spaces | default [] | list to-pipe-separated-string)
color_ranges: ($color_ranges | default [] | list to-pipe-separated-string)
} -i $input -o $output)
}
@@ -208,7 +210,7 @@ def xfade-expressions [context: string] {
}
export def split [
--input (-i): list<string>: = []
--input (-i): list<string> = []
output: list<string>
] {
let cmd = $in;

28
filters_test.nu Normal file → Executable file
View File

@@ -2,18 +2,36 @@
use std [assert];
use ./filters.nu [complex-filter loop "parse filter"];
use ./ffmpeg.nu [complex-filter "parse filter"];
use ./filters.nu [vloop];
# #[test]
def main [] {
let test_commands = (
scope commands
| where ($it.type == "custom")
and ($it.description | str starts-with "[test]")
and not ($it.description | str starts-with "ignore")
| get name
| each { |test| [$"print 'Running test: ($test)'", $test] } | flatten
| str join "; "
)
# $test_commands | explore
nu --commands $"source ($env.CURRENT_FILE); ($test_commands)"
print "Tests completed successfully"
}
#[test]
def loop_has_defaults [] {
let got = (loop 10 1);
let got = (vloop 10 1);
let want = (complex-filter 'loop' { loop: 10 size: 1 });
assert equal $got $want;
}
# #[test]
#[test]
def setting_time_sets_start_to-1 [] {
let got = (loop 10 1 -t 0.5);
let got = (vloop 10 1 -t 0.5);
let want = (complex-filter 'loop' {
loop: 10
size: 1

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1712163089,
"narHash": "sha256-Um+8kTIrC19vD4/lUCN9/cU9kcOsD1O1m+axJqQPyMM=",
"lastModified": 1766902085,
"narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fd281bd6b7d3e32ddfa399853946f782553163b5",
"rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4",
"type": "github"
},
"original": {

View File

@@ -20,7 +20,7 @@
hugo
yarn
nushellFull
nushell
];
};
};

View File

@@ -7,9 +7,9 @@ def main [] {
(
help commands
| where command_type == "custom" and name !~ "p(rompt|wd)"
| select name usage
| select name description
| update name { if $in == 'main' { 'ffprobe' } else { $'ffprobe ($in)' } }
| filter { not ($in.name == 'ffprobe' and ($in.usage | is-empty)) }
| where { not ($in.name == 'ffprobe' and ($in.description | is-empty)) }
| to md -p
)
}

View File

@@ -7,8 +7,8 @@ def main [] {
(
help commands
| where command_type == "custom" and name !~ "p(rompt|wd)|^main$"
| select name usage
| update usage {
| select name description
| update description {
str replace -a "\n" " "
}
| to md -p