charmbracelet / vhs

Your CLI home video recorder 📼
MIT License
15.21k stars 259 forks source link

Make output relative to tape file #121

Open spenserblack opened 2 years ago

spenserblack commented 2 years ago
# in directory called demo
mkdir -p subdir
cat <<EOF > tape.tape
Output ./subdir/tape.gif
Type "Hello, World!"
Enter
Sleep 5s
EOF
cd ..
vhs ./demo/tape.tape

Currently this usage would write to demo/../subdir/tape.gif instead of demo/subdir/tape.gif, because it seems that the output is currently relative to the current directory, not the tape's directory.

I thought this would be useful because I noticed the output of vhs new demo.tape doesn't work "out of the box", because the examples/ directory must exist. If the output could be relative to the tape file, then examples/demo.tape could use ./demo.gif, and the file would also work "out of the box" as the embedded output of vhs new. Making outputs relative to the tape would make results easier to reproducible.

muesli commented 2 years ago

I understand where you're coming from, but I wonder if that isn't less intuitive behavior 🤔

spenserblack commented 2 years ago

A new keyword definitely shouldn't be added without careful consideration, but what do you think about OutputRelative or something similar?

While it's definitely far more common for paths to be relative to the directory that the process is executed in, not the current file, many tools will have some way to get the file's directory (dirname $0 in Bash, __dir__ in Ruby, etc.). Not arguing for that added complexity, but I'm just saying that there's almost always some way to build paths relative to the currently interpreted file.

I should note that my initial issue is actually that vhs new tape.tape doesn't work immediately unless I either create an examples/ directory or change the path in the new tape. I think it's a bit awkward that vhs new tape.tape might not be enough to be a runnable file, and an additional step might be required. This proposal is inspired by that, as I was thinking of a way to allow examples/demo.tape to continue to work as both an example in this repo and as the source for the new command, and allow the newly created tape to always work without additional steps.

spenserblack commented 2 years ago

I'll note that, while checking out behavior, my initial assumption was that output.gif would be relative to the current directory, and ./output.gif would be relative to the file's directory. Not sure how common that assumption is, though.

rm-dr commented 1 year ago

It isn't possible to get a relative output file if vhs gets input through stdin (like vhs < path.tape), since vhs doesn't "see" that path at all. Tapes with relative outputs must be loaded as an argument, like vhs path.tape.

Here are a few thoughts:

As for vhs new, we'd better fix the fact that the file it creates doesn't work. We could do this by putting relative paths in all the demo files, and cding into the examples directories before giving them to vhs.

spenserblack commented 1 year ago
  • Maybe a cli argument, like --output-relative?

An --out-dir or similar argument could be useful (taking some inspiration from TypeScript).

$ cat file.tape
Output "out.gif"
...

$ vhs --out-dir /path/to/context file.tape
$ ls /path/to/context
out.gif ...

This by itself wouldn't resolve the new tape not working out-of-the-box, but maybe that should be a separate issue. But maybe all of the examples could use --out-dir examples instead of all referencing examples/name.format.

spenserblack commented 1 year ago

because we can't get relative output if we load the file through stdin.

By the way, interpreted languages like Ruby and JavaScript (Node) allow one to reference paths relative to the current file:

require_relative '../path/to/file'
require('../path/to/file')

Both of these languages support interpretation from stdin AFAIK. Right now I think that --out-dir is a better solution (I'm happy to change the issue title 🙂), but it might be worth looking into what these languages/interpreters do when the code referencing relative paths comes from stdin.

Edit: Just to give further context to how other tools resolve relative paths, Node will interpret paths relative to the current file, but use the working directory if interpreting from stdin. For example

$ echo "console.log('hello, world');" > greet.js
$ echo "require('../greet.js');" > subdir/index.js
$ node subdir/index.js
hello, world
$ node < subdir/index.js
(stacktrace)
$ cd subdir
$ node index.js
hello, world
$ node < index.js
hello, world