Repository navigation
Conversation
With `-i DURATION[,MIN[,MAX]]`, each scene gets floor(length / DURATION) + 1 images instead of a fixed `-n`, clamped to [MIN, MAX] (default 1, no limit; an empty MIN or MAX uses the default, e.g. `-i 2s,,10`). The images are chosen the same way as with `-n`, including the frame margin. `-n` and `-i` are mutually exclusive, on the command line and in the config file. The API gets `interval`, `min_images` and `max_images` arguments on `save_images()`. `num_images` now defaults to None (3 unless `interval` is set) so that setting both can be rejected, and `min_images`/`max_images` are rejected without `interval`. Closes Breakthrough#280 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Breakthrough
left a comment
There was a problem hiding this comment.
Thanks, this looks great! Appreciate you taking the time to kick this off.
I left a few comments - would like to understand the tradeoffs a bit better between the different CLI approaches we already discussed. I would be okay landing this as-is, so feel free to push back if you think this is the right way to go.
If possible, can you look into how other projects handle cases like this and if there's a standardized/recommended way to specify options such as this on a command line tool? Hopefully the comments and concerns I added are useful, let me know if you have any questions.
| metavar="DURATION[,MIN[,MAX]]", | ||
| default=None, | ||
| type=click.STRING, | ||
| help="Generate one image per DURATION of each scene instead of a fixed number, so longer scenes get more images. Each scene gets floor(length / DURATION) + 1 images, chosen the same way as with -n/--num-images. MIN and MAX limit the number of images per scene (default: 1, no limit). Leave MIN empty to only set MAX (-i 2s,,10). DURATION can be specified in frames (-i 24), in seconds with `s` suffix (-i 1.5s), or timecode (-i 00:00:01.500). Cannot be used with -n/--num-images.{}".format( |
There was a problem hiding this comment.
Do we need to support leaving MIN empty? Can we require it be declared? I'm assuming if we don't specify it just defaults to 1. I'm also curious, can we use nargs here or does that not work because of the subcommands?
E.g. it would be cool if we made -i take 1/2/3 args conditionally using spaces as separators rather than requiring commas. Not sure how much effort that would be though, so happy to punt on this if it becomes a rabbit hole - just curious as to your thoughts.
Context: It's possible in some locales timecodes use commas, so eventually we might need to support that. The key word being eventually though, which is why for today I'd say this is optional, but I do want to explore what options we have and the tradeoffs for each before shipping something.
There was a problem hiding this comment.
Thanks for the questions! I switched -i to space-separated values in 43e6968. Short answers here; the survey of other tools and the tradeoffs are in a comment on the PR below.
Q1, leaving MIN empty: no, we don't need it, so I removed it.
Q2, requiring MIN: yes. MIN and MAX are now positional, so MAX requires MIN. An omitted MIN defaults to 1, as you assumed. To set only a maximum you write -i 2s 1 10, which matches your original 1/2/3-arg shapes.
Q3, nargs: nargs can't do this, and the subcommands aren't the reason.
- Click rejects
nargs=-1on every option (TypeError: nargs=-1 is not supported for options.). - A fixed
nargs=3would force all three values. - The chained subcommands make it worse. In a small chained-group test with
nargs=3,save-images -i 1s 3 list-scenesexits 0 with('1s', '3', 'list-scenes'), solist-scenessilently never runs.
Q4, space-separated 1/2/3 args: it wasn't a rabbit hole, so it's in the update: -i 2s, -i 2s 3, -i 2s 1 10.
- A small
click.Optionsubclass parses-ias usual, then also takes the following arguments while they start with a number. - That's safe here because MIN and MAX are integers and no command or option name starts with a digit. So
save-images -i 1s 3 list-scenesstill runslist-scenes, and-i 1s 3 -o DIRstill sees-o. - Bad values (
1.5,2s, a fourth number,-3) are taken too, so they give an-i/--intervalerror instead of click'sNo such command '1.5'. - On locale commas: with the old comma format,
-i 00:00:01,500was silently read as DURATION00:00:01with MIN 500. It's now an error, and commas stay free for decimal-comma timecodes later.
| duration_secs = max((end - start).seconds, 0.0) | ||
| # The epsilon keeps a scene that is a whole number of intervals long from getting one | ||
| # image less due to floating point error. | ||
| scene_images = max(math.floor(duration_secs / interval_secs + 1e-9) + 1, min_images) |
There was a problem hiding this comment.
Please use a named constant for this (we might have an epsilon value declared elsewhere as well).
There was a problem hiding this comment.
Done in 43e6968: it's now _INTERVAL_COUNT_TOLERANCE: float = 1e-9 at the top of scenedetect/output/image.py, with a comment explaining it. The closest existing constant, MAX_FPS_DELTA in common.py, has the same value, but it's a tolerance for comparing frame rates, and this one is added to a unitless count of intervals, so I gave it its own private name, following _NTSC_DETECTION_TOLERANCE.
Following the review on Breakthrough#588, `save-images -i` now takes `DURATION [MIN [MAX]]` as separate arguments (`-i 2s 1 10`) instead of one comma-separated value, and MAX can only be set together with MIN. Click options can't take a variable number of values, so `IntervalOption` also takes the arguments that follow `-i` while they start with a number. No command or option name does, so a following command or option is left alone. This uses click parser internals that are unchanged in click 8.0 through 8.5; if they are missing, the hook is skipped and the values can still be passed as one quoted argument. Commas are no longer separators, so a timecode with a decimal comma such as `00:00:01,500` is now an error instead of DURATION `00:00:01` with MIN 500. The config file uses the same format (`interval = 2s 1 10`), and a comma gives a hint to separate the values with spaces. The tolerance used when counting intervals per scene is now the named constant `_INTERVAL_COUNT_TOLERANCE`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review! I've pushed 43e6968, which answers the inline comments: How other tools do itThere's no standard for an option that takes a variable number of separate values:
Tradeoffs
Locale commas. Testing the comma format turned up a concrete problem:
What it costs
If you'd rather depend only on public API, I can switch to separate |
Thank you, let's go this route. Sorry I steered you in this direction, I didn't realize it would be so troublesome in practice. |
Following the review decision on Breakthrough#588, `save-images -i/--interval` now takes a single DURATION, and the minimum and maximum number of images per scene are set with separate `--min-images` and `--max-images` options (default 1 and 0 for no limit), e.g. `-i 2s --max-images 10`. They are `min-images`/`max-images` in the config file, where `interval` is now a plain duration. `--min-images`/`--max-images` on the command line are an error unless an interval is in effect (from `-i` or the config file), and `--max-images` must be 0 or at least `--min-images`. If `-n/--num-images` is in effect, `min-images`/`max-images` from the config file are ignored. This removes `IntervalOption` and its click parser hook, along with the help text, config comments and tests for the space-separated values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks! No worries at all, it was worth checking, and trying the variants out made the tradeoffs much clearer. Switched to separate options in 479a545:
I updated the PR description to match. Ready for another look. |
Closes #280
Adds
-i/--intervaltosave-images, as discussed in #280, so the number of images per scene depends on its length:Each scene gets
floor(scene length / DURATION) + 1images, clamped to--min-images/--max-images(default 1 and no limit). The images are chosen the same way as with-n, so the first and last frames are included and the frame margin applies. DURATION takes the same formats as-m/--frame-margin: frames (24), seconds (1.5s), or a timecode (00:00:01.500).Design notes
-i DURATIONtakes one value, and--min-images N/--max-images Nset the limits. Each limit can be set on its own, the options use only public click API (click.IntRange), and each maps to one config key. They have long names only, like--min-out-lengthand--expand. (Single-option variants with comma- and space-separated values were tried and dropped after the review: [cli] Add save-images -i/--interval to base image count on scene length #588 (comment).)--min-imagesmust be at least 1.--max-images 0means no limit and is the default, so a limit from the config file can be removed on the command line.--max-imagesmust be 0 or at least--min-images.interval = 2s,min-images = 1andmax-images = 0in[save-images].-nand-iare mutually exclusive: setting both on the command line is an error, and so is setting bothnum-imagesandintervalin the config file.--min-images/--max-imagesneed an interval: on the command line they are an error unless an interval is in effect, from-ior fromintervalin the config file. If-nis in effect,min-images/max-imagesfrom the config file are ignored, so a config file can keep them for when an interval is used.-noverridesinterval,-ioverridesnum-images, and--min-images/--max-imagesoverridemin-images/max-images. For example, withinterval = 2sandmax-images = 4in the config file,-i 1skeeps the limit of 4 and--max-images 0removes it.save_images()getsinterval,min_imagesandmax_imagesarguments (max_images=Nonemeans no limit). To rejectnum_imagesandintervaltogether, the default fornum_imagesis nowNone, which means 3 unlessintervalis set. Existing calls behave the same.min_imagesandmax_imagesare rejected withoutinterval.$IMAGE_NUMBERpadding and the progress bar total use the actual image counts.save-htmlalready handles a different number of images per scene.-ndoes the same, so I left it alone.Tests
tests/test_output.py: image counts per scene for frame, second and timecode intervals, with and withoutmin_images/max_images, on both the threaded and non-threaded paths; invalid arguments raiseValueError.tests/test_cli.py(in-process):-ialone (frames, seconds, timecode), with--min-images, with--max-images, with both, and with--max-images 0;-nwith-i,--min-images/--max-imageswithout an interval,--max-imagesbelow--min-images,--min-images 0,--max-images -1, an invalid duration, and a duration of 0;interval,min-imagesandmax-images, thenum-imagesconflict, invalid values, and precedence (-i,-n,--min-imagesand--max-imagesover the config file, andmin-images/max-imagesignored without an interval).docs/cli.rstmatches the output ofdocs/generate_cli_docs.pyforsave-images, andscenedetect.cfgand the changelog are updated.🤖 Generated with Claude Code