# Outvoke
[](https://creativecommons.org/publicdomain/zero/1.0/deed.en)
Outvoke serves as a library, a framework, and an internal DSL simultaneously.
If you need Japanese Edition of this README, see [README.ja.md](README.ja.md) (←日本語版README)
## CAUTION
The author, cleemy desu wayo, has tested Outvoke only on Linux.
The first official release, version 0.1, is probably planned for 2026. Version 0.1 focuses on demonstrating the concept, so performance may be limited.
Current versions available as of summer 2025 (e.g., 0.0.99.x) are experimental.
Until version 0.1 is released, specifications may change without notice and features may be removed suddenly.
## Requirements
- Ruby 3.0 or later
## Overview
Outvoke makes it easy to write code that hooks into flowing data streams.
Since Outvoke is an internal DSL, almost all Ruby features are available.
Outvoke is an internal DSL in Ruby, and is basically for people who want to write code in Ruby. However, you may be able to use it practically without going into the linguistic details of Ruby.
Even if Outvoke has some parts that could be called "DSL within a DSL", Outvoke is basically not doing its own parsing, etc. In code that uses Outvoke, code that looks like Ruby is simply Ruby code. In some cases, code may not appear Ruby-like due to its nature as an internal DSL, but that is what an internal DSL is.
Outvoke works both as a library and as a stand-alone command. This README contains a lot of sample code (one-liners) when used as a stand-alone command.
This README does not describe the design philosophy.
The following article, written in Japanese and published in June 2025, is the only cohesive explanation by the author personally as of July 2025, but it covers only a small portion of the functionality.
- Outvokeのことを、なんと説明すればいいだろう|cleemy desu wayo (2025-06-28)
https://note.com/cleemy/n/n01ae9d0583a7
Until version 0.1 is released, the author plans to write the code (excluding comments) of the Outvoke main file ([outvoke.rb](outvoke.rb)), its test code, and sample code without using AI. However, AI is used for translating Japanese text, such as this README and code comments, into English.
The code under [samples/](samples/) in the repository is merely sample code.
## Setup
Put [outvoke.rb](https://gitlab.com/cleemy-desu-wayo/outvoke/-/raw/main/outvoke.rb) in the current directory and give it execute permission.
Launch Outvoke using the `-e` option as follows.
```
$ ./outvoke.rb -e hooksec
```
A new line of output appearing every second signals a successful startup for now.
To exit, press Ctrl + C.
## Options
|option |description|
|-------------------------------------------|---|
|-h, --help |show a list of options (provided by optparse)|
|-v, --version |show version info (shortened with `-q`)|
|-e CODE |use as a one-liner|
|-r LIBRARY |load Ruby library|
|-q, --quiet |quiet mode|
|-w, --wait INTERVAL |wait time for Outvoke's main loop (default is `0.5`)|
|-p, --preset,
--output-mode OUTPUT_MODE |override output presets for all data sources except "err"|
|-V, --var VAR:VALUE |`--var=str:hello` sets `OUTVOKE_VAR["str"]` to `"hello"`|
Note that the specifications for -V/--var have changed in [version 0.0.99.20250816.2](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/f3be1cfcc21e80e5ef9f271b1140ddfd3899a5b3/outvoke.rb) (f3be1cfc).
If there is a conflict with environment variables, the options take precedence.
## Environment Variables
|variable name |description|
|-----------------------|---|
|OUTVOKE_OPT |default options of Outvoke|
|OUTVOKE_QUIET_MODE |quiet mode (enabled if value is `1`)|
|OUTVOKE_WAIT |wait time for Outvoke's main loop (default is `0.5`)|
|OUTVOKE_OUTPUT_MODE |override output presets for all data sources except "err"|
|OUTVOKE_VAR_* |if env `OUTVOKE_VAR_STR` is `"hello"`, `OUTVOKE_VAR["str"]` is set to `"hello"` in Ruby code.|
These are only reflected when you launch Outvoke as a command.
Note that the specifications for OUTVOKE_VAR_* have changed in [version 0.0.99.20250816.2](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/f3be1cfcc21e80e5ef9f271b1140ddfd3899a5b3/outvoke.rb) (f3be1cfc).
Outvoke, an internal DSL of Ruby, is affected by `RUBYOPT` and similar. However, it is meaningless to write Outvoke options in `RUBYOPT`. For such purposes, use `OUTVOKE_OPT`.
## Data Source
In Outvoke, data sources abstract input streams. The original data may not necessarily exist, but a data source allows you to easily access it through a consistent approach without worrying about such details.
|data source name|description|
|----------------|---|
|io |can store arbitrary IO object|
|stdin |receive standard input|
|lo |loopback (does not receive data from external sources)|
|err |loopback for aggregation of error info
(does not receive data from external sources)|
|every-sec |receive date/time info as a string once per second|
|osc-001 |receive OSC messages (default port is `9001`)|
|vrchat-001 |watch VRChat client log file|
There are also `web-001` and `auxin:fifo:1`, but note that these are experimental.
In the following one-liner, the `"stdin"` part is the data source.
```
$ seq 1 40 | ./outvoke.rb -e 'hook("stdin", /2/) { "matched data: #{e}" }'
```
The table above lists the built-in data sources. You can also create your own data source by defining a subclass of `OutvokeDataSource` to fit your needs.
When using the built-in data source "osc-001" for VRChat-related purposes, you may face a limitation because [osc-ruby](https://github.com/aberant/osc-ruby) 1.1.5 does not support the OSC Type Tag String "T" (true value) or "F" (false value). In such cases, hopefully, [this monkey patch](https://gitlab.com/cleemy-desu-wayo/outvoke/-/snippets/4875923) helps. Note that, in [OSC 1.0](https://opensoundcontrol.stanford.edu/spec-1_0.html), the Type Tags "T" and "F" are considered "nonstandard".
## Output Preset
Each data source in Outvoke has `post_procs` for processing the value (`hook_result`) returned by the `Proc` registered using the `hook`. `post_procs` is just an array, and while you can manually register `Proc` using `<<`, there are also convenient built-in presets available, known as output presets.
|preset name |description|
|------------|---|
|terminal |default for many data sources. and this puts String to standard output in a format like:
`[2025-08-12 04:45:48 +0900] hook_result body`|
|warn |default for data source "err". and this puts String using `Kernel.#warn` in a format like:
`[2025-08-12 04:45:48 +0900] hook_result body`|
|nop |no operation|
|jsonl |output to standard output in JSONL format|
|yaml |output to standard output in YAML stream|
|osc |send OSC messages (default port is `9000`)|
In the following one-liner, the output preset for the data source `"every-sec"` is set to `"jsonl"`.
```
$ ./outvoke.rb -e '$outvoke["every-sec"].preset = "jsonl" ; hook("every-sec") { {"sec" => e.sec} }'
```
Assigning a value with `$outvoke.preset =`, you can change the output presets for all data sources except "err" at once (since version 0.0.99.20250817.4).
## Config File
If there is a file `outvoke.conf.rb` in the current directory, Outvoke will first include that file.
For example, if you have a line like the following in `outvoke.conf.rb`, you can specify the directory for VRChat log files to look for.
```
$outvoke["vrchat-001"].log_dir = "#{Dir.home}/.steam/debian-installation/steamapps/compatdata/438100/pfx/drive_c/users/steamuser/AppData/LocalLow/VRChat/VRChat"
```
In [version 0.0.99.20241125](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/a111518f7c74bbf39d304189f108a089458c276e/outvoke.rb) or earlier, it was necessary to specify it as follows:
```
$outvoke.ds["vrchat-001"].log_dir = "..."
```
In even older versions, you would have had to do like this:
```
$outvoke.sources["vrchat-001"].log_dir = "..."
```
## Basic Usage
When you run Outvoke with no arguments, it looks for `main.rb` in the current directory and includes it.
If you run Outvoke by specifying a file name as shown below, it will look for `test.rb` in the current directory and include it.
```
$ ./outvoke.rb test.rb
```
You can write a one-liner using the `-e` option.
For the sake of clarity, this section assumes that you are writing in `main.rb`.
If you write `using OutvokeDSL` at the beginning of `main.rb`, you will be in the mode of internal DSL. In the internal DSL mode, you can write it as casually as writing a configuration file.
Since `main.rb` is interpreted as a Ruby script, you can write complex programs.
The following is an example of `main.rb`.
```
using OutvokeDSL
hook 'every-sec', /22:08:00/
```
The `'every-sec'` part specifies the name of the data source. The `/22:08:00/` part specifies the condition.
Leave it alone after executing Outvoke, and at 10:08 PM, a line that looks like the following should be output.
```
[2024-11-03 22:08:00 +0900] 2024-11-03 22:08:00 +0900
```
Try changing the `/22:08:00/` part to suit your environment.
For the third line with `hook`, it is equivalent to writing:
```
hook 'every-sec', /22:08:00/ do |e|
e.body
end
```
Here is another example.
```
using OutvokeDSL
hook 'every-sec', /05:00:00/ do
spawn 'play', '-q', '-t', 'wav', '-v', '0.6', 'ring.wav', 'repeat', '50'
end
```
At 5:00 AM, `ring.wav` in the current directory is played by the `play` command.
Here is an example of treating multiple data sources:
```
using OutvokeDSL
hook 'every-sec', /..:..:.[02468]/ do |e|
"every-sec: #{e.body}"
end
hook 'stdin' do |e|
"stdin: #{e.body}"
end
```
After preparing the above `main.rb`, run it as follows:
```
$ { sleep 5 ; echo aaa ; sleep 5 ; echo bbb ; sleep 5 ; echo ccc ; } | ./outvoke.rb
```
And you should get output like this:
```
# starting Outvoke 0.1 (version 0.0.99.20240928) ---- 2024-11-03 16:06:22 +0900
# ----
# loading ./main.rb ...
# ----
[2024-11-03 16:06:22 +0900] [outvoke-system] vrchat-001: a new log file was found.
[2024-11-03 16:06:22 +0900] [outvoke-system] vrchat-001: first time log check has done.
[2024-11-03 16:06:24 +0900] every-sec: 2024-11-03 16:06:24 +0900
[2024-11-03 16:06:26 +0900] every-sec: 2024-11-03 16:06:26 +0900
[2024-11-03 16:06:27 +0900] stdin: aaa
[2024-11-03 16:06:28 +0900] every-sec: 2024-11-03 16:06:28 +0900
[2024-11-03 16:06:30 +0900] every-sec: 2024-11-03 16:06:30 +0900
[2024-11-03 16:06:32 +0900] stdin: bbb
[2024-11-03 16:06:32 +0900] every-sec: 2024-11-03 16:06:32 +0900
[2024-11-03 16:06:34 +0900] every-sec: 2024-11-03 16:06:34 +0900
[2024-11-03 16:06:36 +0900] every-sec: 2024-11-03 16:06:36 +0900
[2024-11-03 16:06:37 +0900] stdin: ccc
[2024-11-03 16:06:38 +0900] every-sec: 2024-11-03 16:06:38 +0900
[2024-11-03 16:06:40 +0900] every-sec: 2024-11-03 16:06:40 +0900
[2024-11-03 16:06:42 +0900] every-sec: 2024-11-03 16:06:42 +0900
[2024-11-03 16:06:44 +0900] every-sec: 2024-11-03 16:06:44 +0900
[2024-11-03 16:06:46 +0900] every-sec: 2024-11-03 16:06:46 +0900
[2024-11-03 16:06:48 +0900] every-sec: 2024-11-03 16:06:48 +0900
```
In this example, it helps to think of it like `grep` with multiple input streams.
If that helps, it is possible to make Outvoke work like `grep` by using the `-q` option.
```
$ seq 10 20 | grep 2
12
20
$ seq 10 20 | ./outvoke.rb -q -e 'hook "stdin", /2/'
12
20
```
## With VRChat
In addition to `every-sec` and `stdin`, Outvoke has a built-in data source `vrchat-001`.
The data source `vrchat-001` monitors the log file output by the VRChat client and treats it as like an input stream. This data source only watches the log files output by the VRChat client, so just using it normally does not constitute cheating.
If you have VRChat installed and it has been a long time since VRChat was started, it may take a long time after Outvoke is started before you see the `first time log check has done.` This time can be shortened by restarting VRChat.
Also see the samples below.
- [samples/vrchat_join_log2.rb](samples/vrchat_join_log2.rb) (entry and exit history)
- [samples/vrchat_join_log3.rb](samples/vrchat_join_log3.rb) (entry and exit history, with user ID)
- [samples/2024/vrchat_waittest1.rb](samples/2024/vrchat_waittest1.rb) (pickup and change the interval)
- [samples/2024/vrchat_waittest2.rb](samples/2024/vrchat_waittest2.rb) (pickup and change the interval)
- [samples/2024/vrchat_get_log_file_name.rb](samples/2024/vrchat_get_log_file_name.rb) (usage of e.status)
- [samples/2024/vrchat_multiple_ds1.rb](samples/2024/vrchat_multiple_ds1.rb) (handling multiple data sources)
- [samples/2024/vrchat_multiple_ds2.rb](samples/2024/vrchat_multiple_ds2.rb) (writing an own data source)
- [samples/2024/vrchat_loputs.rb](samples/2024/vrchat_loputs.rb) (basic usage of lo and loputs)
- [samples/2024/webrick_vrchat_instanceinfo.rb](samples/2024/webrick_vrchat_instanceinfo.rb) (deliver JSON data about the instance you are in)
- [samples/2024/webrick_vrchat_instanceinfo.html](samples/2024/webrick_vrchat_instanceinfo.html) (SPA, for how to get it to work, see [here](https://cleemy-desu-wayo.gitlab.io/ov/docs/about_webrick_vrchat_instanceinfo.html))
Videos and Images of Outvoke running in action
- [samples/2025/vrchat_osc_send2.rb](samples/2025/vrchat_osc_send2.rb) alongside OSC message-sending one-liner demos (video): https://x.com/metanagi/status/1944847364961001921
- [samples/2024/vrchat_multiple_ds2.rb](samples/2024/vrchat_multiple_ds2.rb) in action (video): https://x.com/metanagi/status/1802512101111689368
- [samples/2024/webrick_vrchat_instanceinfo.html](samples/2024/webrick_vrchat_instanceinfo.html) in action (screenshot): https://cleemy-desu-wayo.gitlab.io/ov/docs/about_webrick_vrchat_instanceinfo_img001.png
Some explanations and samples may be found at the following:
- https://x.com/hashtag/outvoke
- [cdwact-2023-12] https://note.com/cleemy/n/nf9ea83c0c5e3
- [cdwact-2024-01] https://note.com/cleemy/n/n4ceff128e355
- [cdwact-2024-04] https://note.com/cleemy/n/nf7cce0493fc0
(The rest is currently being written)
## One-liner examples
Even with one-liners, basically all of Ruby's features are available.
Some of the samples provided in this README play the sound. Before you run them, you'll need the following:
1. Put [outvoke.rb](outvoke.rb) to the current directory
2. Set up `maoudamashii-se-system47.wav` to the current directory
(download a wav file from https://maou.audio/se_system47/ )
3. Check to see if sound is played by the play command
```
$ play maoudamashii-se-system47.wav
```
4. Put `outvoke.conf.rb` to the current directory with the following contents
```
def ring(vol = "0.05")
spawn "play", "-v", vol.to_s, "-q", "maoudamashii-se-system47.wav", :err=>"/dev/null"
nil
end
```
5. Check to see if the sound is played once per second with the following one-liner
```
$ ./outvoke.rb -e 'hook "every-sec", /./ do ring ; end'
```
In [version 0.0.99.20240818](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/c3a57d9660e80617bfe2b28099c6f8ad22bb0cfe/outvoke.rb) or later, the following is also possible:
```
$ ./outvoke.rb -e 'hook "every-sec" do ring ; end'
```
You can also write as follows:
```
$ ./outvoke.rb -e 'hook("every-sec"){ring}'
```
In [version 0.0.99.20241110](https://gitlab.com/cleemy-desu-wayo/outvoke/-/blob/c61c4312f72d9e745a3dfb8704783dda2021a854/outvoke.rb) or later, you can also write as follows:
```
$ ./outvoke.rb -e 'hooksec{ring}'
```
### Notes on the one-liners presented here
These one-liners make heavy use of `_1` (numbered parameter).
```
$ ./outvoke.rb -e 'hookvr(/pickup object/i){puts _1.body}'
```
The above is the same as:
```
$ ./outvoke.rb -e 'hookvr(/pickup object/i){|e| puts e.body}'
```
As of November 2024, `hook "vrchat-001"` is the same as `hookvr`, but these one-liners dare not use `hookvr`.
### Slightly complicated alarm clock one-lilners
Now, here are a number of one-liner examples.
Sound three times every 10 seconds between 5:00 AM and 05:10 AM:
```
$ ./outvoke.rb -e 'hook("every-sec", / 05:0[0-9]:[0-5][036]/){ring}'
```
Play the video once every 5 minutes between 05:00 AM and 05:55 AM:
```
$ ./outvoke.rb -e 'hook("every-sec", / 05:[0-5][05]:00/) {spawn "mpv", "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "--really-quiet", :err=>"/dev/null"}'
```
At 22:48, start looping "Hotaru no Hikari" and end at 23:05:
```
$ ./outvoke.rb -e 'hook("every-sec", / 22:48:00/) {spawn "mpv", "https://www.youtube.com/watch?v=OgYWssWn7uQ", "--really-quiet", "--loop", :err=>"/dev/null"} ; hook("every-sec", / 23:05:00/) {spawn "pkill", "mpv", :err=>"/dev/null"}'
```
looping of "Hotaru no Hikari", version that ends after 17 minutes instead of specifying an end time:
```
$ ./outvoke.rb -e 'hook("every-sec", / 22:48:00/) {spawn "mpv", "https://www.youtube.com/watch?v=OgYWssWn7uQ", "--really-quiet", "--loop", :err=>"/dev/null" ; sleep 60 * 17 ; spawn "pkill", "mpv", :err=>"/dev/null"}'
```
Note that in these examples, `pkill mpv` will terminate all mpv.
`Kernel.#spawn` returns PID. PID can be memorized so it's possible to terminate the mpv with pinpoint accuracy:
```
$ ./outvoke.rb -e 'mpv_pid = nil; hook("every-sec", / 22:48:00/) {mpv_pid = spawn "mpv", "https://www.youtube.com/watch?v=OgYWssWn7uQ", "--really-quiet", "--loop", :err=>"/dev/null"} ; hook("every-sec", / 23:05:00/) {spawn "kill", mpv_pid.to_s, :err=>"/dev/null" if mpv_pid}'
```
looping of "Hotaru no Hikari", life-and-death monitoring version:
```
$ ./outvoke.rb -e 'is_music_on = false; hook("every-sec", / 22:48:00/) {is_music_on = true} ; hook("every-sec") {next if (not is_music_on) || IO.popen( "ps aux | grep [O]gYWssWn7uQ").to_a.length != 0 ; spawn "mpv", "https://www.youtube.com/watch?v=O" + "gYWssWn7uQ", "--really-quiet", "--loop", :err=>"/dev/null" ; sleep 0.5 } ; hook("every-sec", / 23:05:00/) {is_music_on = false; spawn "pkill", "mpv", :err=>"/dev/null"}'
```
When 22:48 is reached, just set a flag and then it will start life-and-death monitoring every other second and execute mpv. If you exit mpv by mistake, it will immediately start playing again.
The downside of the one-liner is that the entire source code will be included in the output of `ps aux`. Muddy ingenuity may be required to life-and-death monitoring of the process with `ps aux`. For a case as complex as this, it is better to prepare a file instead of a one-liner.
See also sample [samples/2024/loputs_hotaru_no_hikari.rb](samples/2024/loputs_hotaru_no_hikari.rb) which does something similar. This uses `Process.#detach` and `Process.#kill`.
And now, let's think about the days of the week.
Sound at exactly 05:00 AM, but only between Monday and Friday:
```
$ ./outvoke.rb -e 'hook("every-sec", / 05:00:00/){ring if (1..5).include?(Time.now.wday)}'
```
Instead of `Time.now`, you can also use `_1.status.now`:
```
$ ./outvoke.rb -e 'hook("every-sec", / 05:00:00/){ring if (1..5).include?(_1.status.now.wday)}'
```
In these examples, either is fine, but you should be aware that `Time.now` attempts to get the current time every time it is evaluated, so the date may end up being the next day.
As a general Ruby topic, `include?` can handle days of the week well. For example, whether it falls on Monday, Wednesday, or Friday can be written as follows:
```
[1,3,5].include?(Time.now.wday)
```
### simple VRChat-related one-liners
From here, I will present a number of samples related to VRChat.
Play a sound when someone joins:
```
$ ./outvoke.rb -e 'hook("vrchat-001", /onplayerjoincomplete/i){ring}'
```
`/onplayerjoined/i` is not recommended.
Play a sound when you pick up an object:
```
$ ./outvoke.rb -e 'hook("vrchat-001", /pickup object/i){ring}'
```
Display all video-related logs at all:
```
$ ./outvoke.rb -e 'hook "vrchat-001", /(resolv|video)/i'
```
If you write "resolv", it will match both "resolve" and "resolving".
Display all TopazChat-related logs at all:
```
$ ./outvoke.rb -e 'hook "vrchat-001", /(rtsp|topaz)/i'
```
### Slightly complicated VRChat-related one-liners
Play a sound when someone joins, but no ringing for 90 seconds after you join:
```
$ ./outvoke.rb -e 'hook("vrchat-001", /onplayerjoincomplete/i) {ring if _1.status.elapsed > 90}'
```
You can get the elapsed time since you joined by `_1.status.elapsed`.
This will prevent unnecessary noise immediately after you join an instance with a large number of people.
When you want to output string even without sound:
```
$ ./outvoke.rb -e 'hook("vrchat-001", /onplayerjoincomplete/i) {ring if _1.status.elapsed > 90 ; _1.body}'
```
If you want to output `_1.body`, you can actually use `true`:
```
$ ./outvoke.rb -e 'hook("vrchat-001", /onplayerjoincomplete/i) {ring if _1.status.elapsed > 90 ; true}'
```
Using `1` instead of `true`, for example, is not recommended. It may result in different behavior in the future due to changes in the Outvoke specification. (NOTE: In commit 04be13b4, that change was indeed made)
Whenever a video starts playing in the world, play that video in mpv:
```
$ ./outvoke.rb -e 'hook("vrchat-001", /video playback.*resolve url..(https[-_.a-zA-Z0-9&=?%:\/]*)/i) {spawn "mpv", _1.m[1], "--really-quiet", :err=>"/dev/null"; _1.m[1]}'
```
If an error occurs in a video player in the world, mpv may start multiple times due to retries.
If you dynamically change the arguments passed to `Kernel.#spawn`, you should be aware of the security risks. Avoid passing the entire OS command line as a single string.
```
$ ruby -e 's = "aaa;date" ; spawn "echo", s'
aaa;date
$ ruby -e 's = "aaa;date" ; spawn "echo #{s}"'
aaa
Sat Aug 24 18:30:35 JST 2024
$ ruby -e 's = "aaa\";date;#" ; spawn "echo \"#{s}\""'
aaa
Sat Aug 24 18:30:38 JST 2024
```
Every time a video starts playing in the world, output the URL and title:
```
$ ./outvoke.rb -e 'hookcc("vrchat-001", /video playback.*resolve url..(https[-_.a-zA-Z0-9&=?%:\/]*)/i) {title = IO.popen(["./yt-dlp_linux", "-q", "--get-title", _1.m[1], :err=>"/dev/null"]).each_line.first; loputs "#{_1.m[1]} -- #{title}"} ; hooklo'
```
For information on `hookcc` and multi-threading, see [samples/2024/vrchat_loputs.rb](samples/2024/vrchat_loputs.rb) and [samples/2024/vrchat_loputs_hookcc.rb](samples/2024/vrchat_loputs_hookcc.rb).
`yt-dlp_linux` is a binary of yt-dlp for Linux. The latest version is available from https://github.com/yt-dlp/yt-dlp/releases/.
If you want to know how long it takes to get the title by yt-dlp, you may also do `loputs` at the beginning of the block as follows:
```
$ ./outvoke.rb -e 'hookcc("vrchat-001", /video playback.*resolve url..(https[-_.a-zA-Z0-9&=?%:\/]*)/i) {loputs "#{_1.m[1]} -- hookcc start" ; title = IO.popen(["./yt-dlp_linux", "-q", "--get-title", _1.m[1], :err=>"/dev/null"]).each_line.first; loputs "#{_1.m[1]} -- #{title}"} ; hooklo'
```
Dynamically changing the arguments passed to `IO.popen` has also a security risk. Avoid passing the entire OS command line as a single string. And you should also be cautious about using Kernel.#` or `%x` for security reasons.
(More and more samples to be added)
## About tee command
Although not directly related to Outvoke, it is useful in combination with the tee command if you want to output strings to the terminal while keeping a record of it.
```
$ ./outvoke.rb vrchat_join_log3.rb | tee join_history_$(date "+%Y-%m-%d_%H-%M-%S").txt
```
Of course, it can also be used in one-liner mode.
```
$ ./outvoke.rb -e 'hookcc("vrchat-001", /video playback.*resolve url..(https[-_.a-zA-Z0-9&=?%:\/]*)/i) {title = IO.popen(["./yt-dlp_linux", "-q", "--get-title", _1.m[1], :err=>"/dev/null"]).each_line.first; loputs "#{_1.m[1]} -- #{title}"} ; hooklo' | tee video_history_$(date "+%Y-%m-%d_%H-%M-%S").txt
```
## About options of mpv
In launching mpv from Outvoke, some useful mpv options to know are, for example:
- `--fullscreen` (full screen)
- `--geometry=` (position of window, `--geometry=0:0` for upper left)
- `--loop` (looping)
- `--no-audio` (only video)
- `--no-video` (only audio)
- `--no-keepaspect` (not maintain aspect ratio)
- `--no-osc` (GUI disabled, this OSC is "On Screen Controller", not "OpenSound Control")
- `--really-quiet` (suppress unnecessary output)
- `--volume=` (volume setting, 100 is 100%)
It does not have to be mpv. VLC media player or `firefox`, `chromium`, or `xdg-open` as commands are also useful.