Sno Station
Sno Station은 AI 에이전트를 하나의 팀으로 묶는 오픈소스 워크스테이션으로, 로컬 머신에서 에이전트 협업을 가능하게 함
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Video intelligence for ffrwd: describers that write what a video shows and says as metadata tracks, and the embeddings a query searches
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Three video describers and one embedder over a file, hosted in wasm: clips
and sounds read the picture and the sound into rows and vectors,
embed/embed_text put the same 384-d space under a row or a search
prompt, embed_clip/embed_clip_text do the same in X-CLIP's 512-d space,
and find.sql ranks one against the other with cos_similarity. Speech is
not this package's job - it composes
ffrwd/whisper
and ffrwd/vad
instead of shipping a third model for it.
Requires ffrwd 0.29, whose ffrwd/wasm is 0.19.1.README 전문 보기 ↗Three video describers and one embedder over a file, hosted in wasm: clips
and sounds read the picture and the sound into rows and vectors,
embed/embed_text put the same 384-d space under a row or a search
prompt, embed_clip/embed_clip_text do the same in X-CLIP's 512-d space,
and find.sql ranks one against the other with cos_similarity. Speech is
not this package's job - it composes
ffrwd/whisper
and ffrwd/vad
instead of shipping a third model for it.
Requires ffrwd 0.30, whose ffrwd/wasm is 0.19.1.
The four modules are built and run:
target/wasm32-wasip2/release/{clips,sounds,embed,embed_clip}.wasm, all
nodes of the ffrwd:[email protected] world, and clips_gpu.wasm, clips' source
built beside ffrwd:gpu. ffrwd install is the whole of the setup.
clips(v, cuts => <rows>) returns
STRUCT(start_t number, end_t number, vector vector)[]: a row a shot,
or a row every ten seconds of a longer one, each leaving when its
stretch ends. cuts is a {shot} row a frame from a detector over the
same picture, ffrwd.shots.simple_detector(v); left out, the whole
input is one shot. No picture leaves.clips_gpu(v, cuts => <rows>, memory DEFAULT 'gpu') returns clips'
rows, the host making each frame the module keeps into the tower's input
and joining the eight, so the module never reads the picture. With
memory => 'gpu' the picture stays on the GPU; each kept frame's centre
square is resized and normalised there, and where the tower's session
reads the device's memory the eight are joined on the device and bound
to it in place. The frame's centre square is resized in one pass where
clips resizes and then crops, and nothing is rounded to 8 bits, so a
vector agrees with clips' to a cosine of 0.994 to 0.9999 on a five-shot
test clip at 1080p and 4K, and ranks shots the same way. cuts comes from
ffrwd.shots.simple_detector_gpu(v) over the same picture. It imports
ffrwd:gpu, so it runs where the sidecar has GPU support, and needs
ffrwd 0.30.sounds(a, threshold DEFAULT 0.3, top DEFAULT 3) returns cue[]: up
to top AudioSet labels a ten-second window, windows half a window
apart, each leaving with its window. No sound leaves.embed(rows) and embed_clip(rows) read cues and write
STRUCT(start_t number, end_t number, vector vector)[], a vector a
cue at the cue's own time. embed_text(prompt) and
embed_clip_text(prompt) are the same models over one prompt, at
compile time, from the same module files.A reader takes the picture and the sound from the source, and the rows
from the call:
ffrwd.describe.clips(f.video[1], ffrwd.shots.simple_detector(f.video[1])).
A described file carries its vectors in its own encoded video packets, as
SEI messages in H.264 and HEVC and a metadata OBU in AV1, written by
ffrwd/index's weave.
The Matroska vector track this package used to write is gone. What that
changes for a caller:
.mkv. MP4 takes the same query.-c copy, a -ss cut and a UNION ALL, which
the track never did.find.sql reads the vectors back with ffrwd.index.records, a sink
the compiler runs while the query compiles. It copies the keyframes
alone: on the file below, 0.19% of the bytes.ffrwd-index read --mp4 film.described.mp4 prints every record and
every space declaration without ffrwd involved.A space declaration says which model made the vectors and which model turns
a search into the same space. Those are two different networks for the clip
space - X-CLIP's video tower wrote the vectors, its text tower embeds the
prompt - and one network reached twice for the text spaces. The URIs and the
digests are this package's own models table, written into the recipe as
the spaces literal weave reads.
Function: ffrwd.describe.clips(), ffrwd.describe.embed_clip(), ffrwd.describe.embed_clip_text()
Model: microsoft/xclip-base-patch16-kinetics-600, exported by us to imbcmdth/xclip-onnx.
License: MIT
Function: ffrwd.describe.embed(), ffrwd.describe.embed_text()
Model: Xenova/all-MiniLM-L6-v2
License: Apache-2.0
Function: ffrwd.describe.sounds()
Model: onnx-community/ast-finetuned-audioset-10-10-0.4593-ONNX
License: BSD-3-Clause
Four, in two pairs. One pair reads a file that has sound, the other a file
that does not, because the dialect has no way to make a stream optional
inside one query: f.audio[1] on a silent source is
STREAM_NOT_FOUND: 'f.audio[1]' does not exist, at compile time, before
anything runs. Splitting the recipe is the whole fix. describe-video-gpu
is describe-video with simple_detector_gpu and clips_gpu.
| recipe | source | writes |
|---|---|---|
describe | has audio | video with vectors woven in, the audio, and two caption tracks |
describe-video | any | video with vectors woven in |
describe-video-gpu | any | describe-video's file, the shots and vectors made on the GPU |
find | described, has audio | the matching spans, video and audio |
find-video | described | the matching spans, video alone |
ffrwd run ffrwd/describe:describe-video -v src=film.mp4 -v dest=film.described.mp4
ffrwd run ffrwd/describe:find-video -v src=film.described.mp4 -v prompt='a car driving at night' -v threshold=0.24 -v dest=clips.mp4
describe writes the AudioSet labels and the transcript as caption tracks
beside the video and the audio. They are text, not vectors: find never
reads them, and they are there to be read by a person or by
unnest(f.cues).
Both columns are written without an alias. An alias on a cue column is the
track's TITLE, and a title is Matroska's: every other container rewrites or
drops it, so ffrwd refuses a titled track anywhere but an .mkv. Untitled,
the same column writes wherever captions do. So:
.mkv: the video, the audio, and two WebVTT subtitle tracks, in SELECT
order - the sound labels first, the transcript second, neither carrying a
name..mp4: the same four streams, the captions as mov_text, in the same
order.Naming them again would cost MP4, which is the container this rewrite was for.
describe declares and weaves three. weave has an input per declared
space, named for it, and the call binds each producer to its space by
name: clip => ..., sound => ..., speech => ....
| space | modality | dims | model | query |
|---|---|---|---|---|
clip | picture | 512 | X-CLIP video tower | X-CLIP text tower |
sound | sound-text | 384 | all-MiniLM-L6-v2 | the same |
speech | speech | 384 | all-MiniLM-L6-v2 | the same |
clip is the one whose two models differ: the video tower wrote the vectors
and the text tower is what a search goes through. sound and speech are
the same embedder over two kinds of text, and they are two spaces rather
than one because a declaration carries one modality, and a reader that
wanted only what was said could not tell them apart under one id.
describe-video declares one. A space declared to weave rides every
keyframe whether a record uses it or not, so a picture-only file described
by the three-space recipe would tell every later reader it carries vectors
it does not have.
describe stream-copies the audio. The packets in the output are the
source's own bytes, the codec is the source's codec, and the file is the
source's size plus what the records cost. That holds even though sounds
and vad read the same audio on the way past: ffrwd opens the source again
for the stream it is copying rather than taking it off the pipe those two
modules read.
The labels and the transcript are read twice, as caption tracks and under
embed, and each is one node: a call written twice in one query is one
call, and its rows go to both readers.
A record carries a numeric space id, and weave hands ids out in the order
its space table declares them, from zero. find does not rely on that. It
joins ffrwd.index.spaces, which reads the declarations out of the file's
first packet, and selects on modality. An id is a position in one
producer's table; a described file outlives the recipe that wrote it, and
may be remuxed, cut, joined to another file, or written by something that is
not this package. modality is a field of the format and means the same
thing in every file. The extra read is one packet.
Sixty seconds of H.264 at 23.976 fps with a pink-noise AAC track, 45266864 bytes. Trimmed output of the real commands, on 2026-09-20, against ffrwd 0.18.0 and every package resolved from the registry.
$ ffrwd run ffrwd/describe:describe -v src=toned.mp4 -v dest=described.mp4
Fifteen records in three spaces: thirteen shots, one sound label, one
transcript cue. 34 seconds to MP4, 29 to Matroska, with the models already
in the cache. The described MP4 is 45297349 bytes, so the records cost the
video stream 29341 bytes, 0.066% of it, and the same in both containers.
Every picture is the source's own: framemd5 over the decoded video agrees
across the source, the described MP4, the described Matroska and the same
query with the weave call taken out, all 1438 frames of it, and every
video packet's presentation time, decode time and duration is the unwoven
query's. The audio is the source's own packets, byte for byte, in the
source's own codec.
$ ffrwd-index read --mp4 described.mp4
{"space":{"id":0,"dims":512,"encoding":"i8","unit_length":false,"modality":"picture","source":0,
"model":"hf:imbcmdth/xclip-onnx@649f3c91b59cd24be316dc505e26eacf5cd00801/video_tower.onnx",
"model_hash":"87a87b51ae52efa549e6e48f3a3b41f4",
"query":"hf:imbcmdth/xclip-onnx@649f3c91b59cd24be316dc505e26eacf5cd00801/text_tower.onnx",
"query_hash":"c14df0fdda1ba530330e278b8fabeb0b","producer":"ffrwd/describe 0.2.0"}}
{"space":{"id":1,"dims":384,"encoding":"i8","unit_length":false,"modality":"sound-text","source":0,
"model":"hf:Xenova/all-MiniLM-L6-v2@751bff37182d3f1213fa05d7196b954e230abad9/onnx/model.onnx",
"model_hash":"759c3cd2b7fe7e93933ad23c4c9181b7",
"query":"hf:Xenova/all-MiniLM-L6-v2@751bff37182d3f1213fa05d7196b954e230abad9/onnx/model.onnx",
"query_hash":"759c3cd2b7fe7e93933ad23c4c9181b7","producer":"ffrwd/describe 0.2.0"}}
{"space":{"id":2,"dims":384,"encoding":"i8","unit_length":false,"modality":"speech","source":0,
"model":"hf:Xenova/all-MiniLM-L6-v2@751bff37182d3f1213fa05d7196b954e230abad9/onnx/model.onnx",
"model_hash":"759c3cd2b7fe7e93933ad23c4c9181b7",
"query":"hf:Xenova/all-MiniLM-L6-v2@751bff37182d3f1213fa05d7196b954e230abad9/onnx/model.onnx",
"query_hash":"759c3cd2b7fe7e93933ad23c4c9181b7","producer":"ffrwd/describe 0.2.0"}}
described.mp4: scan keyframes: 30 of 1438 samples, 84488 of 45297349 bytes read (0.19% of the file), 36 seeks
(each space is one line in the file; wrapped here.)
Then two searches, one aimed at the picture and one at the sound. The same recipe serves both: the branches are told apart by modality, and the ones a prompt does not clear drop out.
$ ffrwd compile -f recipes/find.sql -v src=described.mp4 \
-v prompt='a car driving at night' -v threshold=0.23 -v dest=found.mp4
... trim=start=-0.083:end=3.838 ... trim=start=23.315:end=25.276
... trim=start=31.449:end=38.122 ... trim=start=41.167:end=43.419
... trim=start=46.338:end=53.596 ...
$ ffrwd compile -f recipes/find.sql -v src=described.mkv \
-v prompt='a hiss of static noise' -v threshold=0.30 -v dest=found.mkv
... trim=start=-0.083:end=9.917 ... atrim=start=-0.083:end=9.917 ...
Five spans from the picture prompt, 22.065 seconds cut out of sixty, and the
same five off the .mkv. One span from the sound prompt, 10 seconds, and
it is the sound record: that prompt scores 0.496 against the sound space and
0.014 against the speech space, so only one branch survives the threshold.
The compile of find is 5 seconds on the machine this ran on, most of it
the two text towers loading; find-video, which the picture prompt alone
needs, compiled a described file in 2.8 seconds with one tower. The read of
the file's own records is the keyframe scan above and is not where the time
goes.
The silent pair over the same picture without its audio track: 9 seconds to
describe, thirteen records in the one picture space, and find-video over
the result cuts the same five spans the three-space file gave.
The same three rankings over the binary32 the models produced, before the 8-bit encoding and before the file:
$ ffrwd-index search --rows rows.ndjson --space 1 --query query_clip.json --top 5
{"rank":1,"score":0.247439,"start_t":41.250,"end_t":43.502,"space":1,"record_id":8}
{"rank":2,"score":0.243536,"start_t":31.532,"end_t":38.205,"space":1,"record_id":6}
{"rank":3,"score":0.240230,"start_t":46.421,"end_t":53.679,"space":1,"record_id":10}
{"rank":4,"score":0.238564,"start_t":23.398,"end_t":25.359,"space":1,"record_id":4}
{"rank":5,"score":0.236714,"start_t":0.000,"end_t":3.921,"space":1,"record_id":0}
$ ffrwd-index search --rows rows.ndjson --space 3 --query query_noise.json --top 3
{"rank":1,"score":0.496058,"start_t":0.000,"end_t":10.000,"space":3,"record_id":0}
$ ffrwd-index search --rows rows.ndjson --space 2 --query query_noise.json --top 3
{"rank":1,"score":0.013573,"start_t":30.000,"end_t":36.000,"space":2,"record_id":0}
Out of the file the same three read 0.247323, 0.496017 and 0.014074, in the
same order, with the clip space's five records ranked the same way. The
scores differ in the third or fourth decimal, which is the 8-bit encoding,
and the spans by 83 milliseconds, which is two frames at 23.976 fps: the
rows are on the source's clock, and a span read back out of the file is an
offset from the picture it rides on, against presentation times an encode
with B-frames does not put in coding order. (ffrwd-index numbers its own
rows from one and weave numbers the file's from zero, which is why the
space ids differ between the two halves and why find looks a space up
rather than naming a number.)
For a contributor with the checkout. An installed copy of the package needs none of this: the modules travel in the archive.
cargo build --target wasm32-wasip2 --release
cargo test
Every module is a node written with
ffrwd-node, which carries the
world it is built against, and common/ is the wasi:nn glue the four
share. embed and embed_clip also export values, on bindings of their
own built from wit/av.wit, a copy of the same world. clips-gpu builds
clips' source with its gpu feature on, as clips_gpu.wasm, over the copy
of the sidecar's ffrwd:gpu wit in clips/wit-gpu/.
The X-CLIP ONNX export under build/xclip is scripts/export_xclip.py's
output, pinned to the revision the manifest names.
sh scripts/check.sh
ffrwd.json's test command, so ffrwd publish runs it too. It generates
an eight-second fixture with lavfi, silent and with a noise track, compiles
every recipe, then describes and searches both, checking the pictures
against the source's own and that the file with sound declares all three
spaces. The two compile checks load no model: nothing in describe.sql or
describe-video.sql runs while the query compiles, which is what makes them
checkable on a machine with an empty model cache. The find recipes embed
their prompt at compile time and read a file that has records in it, so they
need the towers and a described fixture, and the script makes one.
Apache-2.0.