|
| 1 | +^{:kindly/hide-code true |
| 2 | + :clay {:title "Bioscoop, a DSL for FFmpeg" |
| 3 | + :quarto {:author [:danielsz] |
| 4 | + :description "Quickstart for Clojurians" |
| 5 | + :draft false |
| 6 | + :type :post |
| 7 | + :date "2025-10-27" |
| 8 | + :category :clojure |
| 9 | + :tags [:creative_coding]}}} |
| 10 | +(ns bioscoop.quickstart |
| 11 | + (:require [scicloj.kindly.v4.kind :as kind] |
| 12 | + [bioscoop.quickstart :as qs])) |
| 13 | + |
| 14 | +;; This document serves as a quickstart for the Bioscoop library. It is geared toward Clojurians as it assumes knowledge of namespaces, the REPL, etc. |
| 15 | + |
| 16 | +;; Bioscoop is a DSL to program FFmpeg's filtergraphs. Start by requiring the Bioscoop macros and the `bioscoop.built-in` namespace. |
| 17 | + |
| 18 | +(require '[bioscoop.macro :refer [bioscoop defgraph]] |
| 19 | + '[bioscoop.built-in :refer [help]]) |
| 20 | + |
| 21 | +;; Let's build a simple animation. We will start with a black background. The _color_ filter provides that. |
| 22 | + |
| 23 | +(bioscoop (color)) |
| 24 | + |
| 25 | +;; Note that we didn't require _color_. It is available thanks to the `bioscoop.built_in` namespace, alongside all of the other filters. |
| 26 | + |
| 27 | +;; If you need help with the parameters that a filter accepts, type in the REPL: |
| 28 | + |
| 29 | +^:kind/hidden |
| 30 | +(help "color") |
| 31 | + |
| 32 | +;; Bioscoop recognizes the same parameters as FFmpeg, with a caveat. In FFmpeg, some filters define the same parameter twice, once fully spelled out, and once in shorthand form (as if the syntax wasn't terse enough). For example, _w_ and _width_. Bioscoop doesn't accept the shorthand version *by design*. |
| 33 | + |
| 34 | +;; On top of that black canvas, we are going to draw text twice per second, positioned randomly. In order to achieve that, we will refer the _x_ and _y_ coordinates to an expression instead of fixed values. |
| 35 | + |
| 36 | +(bioscoop (drawtext {:text "bioscoop" :fontcolor "white" :x "'mod(random(0)*10000,W-tw)'" :y "'mod(random(1)*10000,H-th)'"})) |
| 37 | + |
| 38 | +;; The bioscoop macro accepts a subset of Clojure. You can use _let_ bindings and all of _clojure.core_. It returns a data structure, the internal representation of a filtergraph. |
| 39 | + |
| 40 | +;; Putting everything together, we can write: |
| 41 | + |
| 42 | +(bioscoop (chain (color {:duration 5}) |
| 43 | + (drawtext {:text "bioscoop" |
| 44 | + :fontcolor "white" |
| 45 | + :x "'mod(random(0)*10000,W-tw)'" |
| 46 | + :y "'mod(random(1)*10000,H-th)'"}) |
| 47 | + (fps {:fps "2"}))) |
| 48 | + |
| 49 | +;; Or, if we need a handle on the filtergraph, we can use _defgraph_ which will intern a Var with a name of our choosing. |
| 50 | + |
| 51 | +(defgraph filtergraph (chain (color {:duration 5}) |
| 52 | + (drawtext {:text "bioscoop" |
| 53 | + :x "'mod(random(0)*10000,W-tw)'" |
| 54 | + :y "'mod(random(1)*10000,H-th)'"}) |
| 55 | + (fps {:fps "2"}))) |
| 56 | + |
| 57 | +;; To convert the internal data structure, or the handle, back to a filtergraph, we use _to-ffmpeg_: |
| 58 | + |
| 59 | +(require '[bioscoop.render :refer [to-ffmpeg]]) |
| 60 | +(to-ffmpeg filtergraph) |
| 61 | + |
| 62 | +;; Finally, the filtergraph is ready to be fed to FFmpeg. While there is a helper in Bioscoop, how and where you run the FFmpeg command is entirely up to you. For example, you can display the animation in the terminal with `ffplay -f lavfi -i` followed by the filtergraph. |
| 63 | +;; |
| 64 | +;; *Note:* FFplay is a companion player that is most often installed together with FFMpeg. |
| 65 | + |
| 66 | +;;  |
| 67 | +;; |
| 68 | +;; And voilà! |
| 69 | + |
| 70 | +;; Content creators often reuse a preamble in their broadcasts. This use case is an excellent opportunity to demonstrate how Bioscoop provides means of composition. |
| 71 | +;; It also demonstrates how you reuse definitions across namespace boundaries. |
| 72 | + |
| 73 | +^:kind/hidden |
| 74 | +(in-ns 'bioscoop.masterpiece) |
| 75 | +^{:kindly/hide-code true :kindly/kind :kind/hidden} |
| 76 | +(clojure.core/refer-clojure) |
| 77 | +(require '[bioscoop.quickstart :refer [filtergraph]] |
| 78 | + '[bioscoop.macro :refer [bioscoop defgraph]] |
| 79 | + '[bioscoop.built-in]) |
| 80 | + |
| 81 | +;; We will simulate the actual video content with a FFMpeg dummy filter, _testsrc_. The intro is our filtergraph referred to in the `bioscoop.quickstart` namespace. |
| 82 | + |
| 83 | +(defgraph masterpiece (testsrc)) |
| 84 | +(bioscoop (compose [filtergraph ["intro"]] |
| 85 | + [masterpiece ["masterpiece"]] |
| 86 | + [["intro"] ["masterpiece"] (concat {:n 2})])) |
| 87 | + |
| 88 | +;;  |
| 89 | +;; |
| 90 | + |
| 91 | +;; And voilà, again! |
| 92 | +;; |
| 93 | +;; What are you going to make? We'll be happy to link to **your** masterpiece in Bioscoop's [gallery](https://github.com/danielsz/bioscoop#gallery). |
| 94 | + |
| 95 | + |
| 96 | + |
0 commit comments