@kolbo/kolbo-code-linux-x64 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/bin/kolbo +0 -0
  2. package/package.json +14 -0
  3. package/skills/frontend-design/SKILL.md +42 -0
  4. package/skills/kolbo/SKILL.md +216 -0
  5. package/skills/remotion-best-practices/SKILL.md +61 -0
  6. package/skills/remotion-best-practices/rules/3d.md +86 -0
  7. package/skills/remotion-best-practices/rules/animations.md +27 -0
  8. package/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  9. package/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  10. package/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
  11. package/skills/remotion-best-practices/rules/assets.md +78 -0
  12. package/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
  13. package/skills/remotion-best-practices/rules/audio.md +169 -0
  14. package/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
  15. package/skills/remotion-best-practices/rules/can-decode.md +81 -0
  16. package/skills/remotion-best-practices/rules/charts.md +120 -0
  17. package/skills/remotion-best-practices/rules/compositions.md +154 -0
  18. package/skills/remotion-best-practices/rules/display-captions.md +184 -0
  19. package/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  20. package/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
  21. package/skills/remotion-best-practices/rules/fonts.md +152 -0
  22. package/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  23. package/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  24. package/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
  25. package/skills/remotion-best-practices/rules/gifs.md +141 -0
  26. package/skills/remotion-best-practices/rules/images.md +134 -0
  27. package/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
  28. package/skills/remotion-best-practices/rules/light-leaks.md +73 -0
  29. package/skills/remotion-best-practices/rules/lottie.md +70 -0
  30. package/skills/remotion-best-practices/rules/maps.md +412 -0
  31. package/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
  32. package/skills/remotion-best-practices/rules/measuring-text.md +140 -0
  33. package/skills/remotion-best-practices/rules/parameters.md +109 -0
  34. package/skills/remotion-best-practices/rules/sequencing.md +118 -0
  35. package/skills/remotion-best-practices/rules/sfx.md +30 -0
  36. package/skills/remotion-best-practices/rules/subtitles.md +36 -0
  37. package/skills/remotion-best-practices/rules/tailwind.md +11 -0
  38. package/skills/remotion-best-practices/rules/text-animations.md +20 -0
  39. package/skills/remotion-best-practices/rules/timing.md +179 -0
  40. package/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
  41. package/skills/remotion-best-practices/rules/transitions.md +197 -0
  42. package/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
  43. package/skills/remotion-best-practices/rules/trimming.md +51 -0
  44. package/skills/remotion-best-practices/rules/videos.md +171 -0
  45. package/skills/remotion-best-practices/rules/voiceover.md +99 -0
  46. package/skills/video-production/SKILL.md +152 -0
  47. package/skills/youtube-clipper/SKILL.md +187 -0
@@ -0,0 +1,100 @@
1
+ import {
2
+ AbsoluteFill,
3
+ interpolate,
4
+ useCurrentFrame,
5
+ useVideoConfig,
6
+ } from 'remotion';
7
+
8
+ const COLOR_BG = '#ffffff';
9
+ const COLOR_TEXT = '#000000';
10
+ const FULL_TEXT = 'From prompt to motion graphics. This is Remotion.';
11
+ const PAUSE_AFTER = 'From prompt to motion graphics.';
12
+ const FONT_SIZE = 72;
13
+ const FONT_WEIGHT = 700;
14
+ const CHAR_FRAMES = 2;
15
+ const CURSOR_BLINK_FRAMES = 16;
16
+ const PAUSE_SECONDS = 1;
17
+
18
+ // Ideal composition size: 1280x720
19
+
20
+ const getTypedText = ({
21
+ frame,
22
+ fullText,
23
+ pauseAfter,
24
+ charFrames,
25
+ pauseFrames,
26
+ }: {
27
+ frame: number;
28
+ fullText: string;
29
+ pauseAfter: string;
30
+ charFrames: number;
31
+ pauseFrames: number;
32
+ }): string => {
33
+ const pauseIndex = fullText.indexOf(pauseAfter);
34
+ const preLen =
35
+ pauseIndex >= 0 ? pauseIndex + pauseAfter.length : fullText.length;
36
+
37
+ let typedChars = 0;
38
+ if (frame < preLen * charFrames) {
39
+ typedChars = Math.floor(frame / charFrames);
40
+ } else if (frame < preLen * charFrames + pauseFrames) {
41
+ typedChars = preLen;
42
+ } else {
43
+ const postPhase = frame - preLen * charFrames - pauseFrames;
44
+ typedChars = Math.min(
45
+ fullText.length,
46
+ preLen + Math.floor(postPhase / charFrames),
47
+ );
48
+ }
49
+ return fullText.slice(0, typedChars);
50
+ };
51
+
52
+ const Cursor: React.FC<{
53
+ frame: number;
54
+ blinkFrames: number;
55
+ symbol?: string;
56
+ }> = ({frame, blinkFrames, symbol = '\u258C'}) => {
57
+ const opacity = interpolate(
58
+ frame % blinkFrames,
59
+ [0, blinkFrames / 2, blinkFrames],
60
+ [1, 0, 1],
61
+ {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'},
62
+ );
63
+
64
+ return <span style={{opacity}}>{symbol}</span>;
65
+ };
66
+
67
+ export const MyAnimation = () => {
68
+ const frame = useCurrentFrame();
69
+ const {fps} = useVideoConfig();
70
+
71
+ const pauseFrames = Math.round(fps * PAUSE_SECONDS);
72
+
73
+ const typedText = getTypedText({
74
+ frame,
75
+ fullText: FULL_TEXT,
76
+ pauseAfter: PAUSE_AFTER,
77
+ charFrames: CHAR_FRAMES,
78
+ pauseFrames,
79
+ });
80
+
81
+ return (
82
+ <AbsoluteFill
83
+ style={{
84
+ backgroundColor: COLOR_BG,
85
+ }}
86
+ >
87
+ <div
88
+ style={{
89
+ color: COLOR_TEXT,
90
+ fontSize: FONT_SIZE,
91
+ fontWeight: FONT_WEIGHT,
92
+ fontFamily: 'sans-serif',
93
+ }}
94
+ >
95
+ <span>{typedText}</span>
96
+ <Cursor frame={frame} blinkFrames={CURSOR_BLINK_FRAMES} />
97
+ </div>
98
+ </AbsoluteFill>
99
+ );
100
+ };
@@ -0,0 +1,103 @@
1
+ import {loadFont} from '@remotion/google-fonts/Inter';
2
+ import React from 'react';
3
+ import {AbsoluteFill, spring, useCurrentFrame, useVideoConfig} from 'remotion';
4
+
5
+ /*
6
+ * Highlight a word in a sentence with a spring-animated wipe effect.
7
+ */
8
+
9
+ // Ideal composition size: 1280x720
10
+
11
+ const COLOR_BG = '#ffffff';
12
+ const COLOR_TEXT = '#000000';
13
+ const COLOR_HIGHLIGHT = '#A7C7E7';
14
+ const FULL_TEXT = 'This is Remotion.';
15
+ const HIGHLIGHT_WORD = 'Remotion';
16
+ const FONT_SIZE = 72;
17
+ const FONT_WEIGHT = 700;
18
+ const HIGHLIGHT_START_FRAME = 30;
19
+ const HIGHLIGHT_WIPE_DURATION = 18;
20
+
21
+ const {fontFamily} = loadFont();
22
+
23
+ const Highlight: React.FC<{
24
+ word: string;
25
+ color: string;
26
+ delay: number;
27
+ durationInFrames: number;
28
+ }> = ({word, color, delay, durationInFrames}) => {
29
+ const frame = useCurrentFrame();
30
+ const {fps} = useVideoConfig();
31
+
32
+ const highlightProgress = spring({
33
+ fps,
34
+ frame,
35
+ config: {damping: 200},
36
+ delay,
37
+ durationInFrames,
38
+ });
39
+ const scaleX = Math.max(0, Math.min(1, highlightProgress));
40
+
41
+ return (
42
+ <span style={{position: 'relative', display: 'inline-block'}}>
43
+ <span
44
+ style={{
45
+ position: 'absolute',
46
+ left: 0,
47
+ right: 0,
48
+ top: '50%',
49
+ height: '1.05em',
50
+ transform: `translateY(-50%) scaleX(${scaleX})`,
51
+ transformOrigin: 'left center',
52
+ backgroundColor: color,
53
+ borderRadius: '0.18em',
54
+ zIndex: 0,
55
+ }}
56
+ />
57
+ <span style={{position: 'relative', zIndex: 1}}>{word}</span>
58
+ </span>
59
+ );
60
+ };
61
+
62
+ export const MyAnimation = () => {
63
+ const highlightIndex = FULL_TEXT.indexOf(HIGHLIGHT_WORD);
64
+ const hasHighlight = highlightIndex >= 0;
65
+ const preText = hasHighlight ? FULL_TEXT.slice(0, highlightIndex) : FULL_TEXT;
66
+ const postText = hasHighlight
67
+ ? FULL_TEXT.slice(highlightIndex + HIGHLIGHT_WORD.length)
68
+ : '';
69
+
70
+ return (
71
+ <AbsoluteFill
72
+ style={{
73
+ backgroundColor: COLOR_BG,
74
+ alignItems: 'center',
75
+ justifyContent: 'center',
76
+ fontFamily,
77
+ }}
78
+ >
79
+ <div
80
+ style={{
81
+ color: COLOR_TEXT,
82
+ fontSize: FONT_SIZE,
83
+ fontWeight: FONT_WEIGHT,
84
+ }}
85
+ >
86
+ {hasHighlight ? (
87
+ <>
88
+ <span>{preText}</span>
89
+ <Highlight
90
+ word={HIGHLIGHT_WORD}
91
+ color={COLOR_HIGHLIGHT}
92
+ delay={HIGHLIGHT_START_FRAME}
93
+ durationInFrames={HIGHLIGHT_WIPE_DURATION}
94
+ />
95
+ <span>{postText}</span>
96
+ </>
97
+ ) : (
98
+ <span>{FULL_TEXT}</span>
99
+ )}
100
+ </div>
101
+ </AbsoluteFill>
102
+ );
103
+ };
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: assets
3
+ description: Importing images, videos, audio, and fonts into Remotion
4
+ metadata:
5
+ tags: assets, staticFile, images, fonts, public
6
+ ---
7
+
8
+ # Importing assets in Remotion
9
+
10
+ ## The public folder
11
+
12
+ Place assets in the `public/` folder at your project root.
13
+
14
+ ## Using staticFile()
15
+
16
+ You MUST use `staticFile()` to reference files from the `public/` folder:
17
+
18
+ ```tsx
19
+ import { Img, staticFile } from "remotion";
20
+
21
+ export const MyComposition = () => {
22
+ return <Img src={staticFile("logo.png")} />;
23
+ };
24
+ ```
25
+
26
+ The function returns an encoded URL that works correctly when deploying to subdirectories.
27
+
28
+ ## Using with components
29
+
30
+ **Images:**
31
+
32
+ ```tsx
33
+ import { Img, staticFile } from "remotion";
34
+
35
+ <Img src={staticFile("photo.png")} />;
36
+ ```
37
+
38
+ **Videos:**
39
+
40
+ ```tsx
41
+ import { Video } from "@remotion/media";
42
+ import { staticFile } from "remotion";
43
+
44
+ <Video src={staticFile("clip.mp4")} />;
45
+ ```
46
+
47
+ **Audio:**
48
+
49
+ ```tsx
50
+ import { Audio } from "@remotion/media";
51
+ import { staticFile } from "remotion";
52
+
53
+ <Audio src={staticFile("music.mp3")} />;
54
+ ```
55
+
56
+ **Fonts:**
57
+
58
+ ```tsx
59
+ import { staticFile } from "remotion";
60
+
61
+ const fontFamily = new FontFace("MyFont", `url(${staticFile("font.woff2")})`);
62
+ await fontFamily.load();
63
+ document.fonts.add(fontFamily);
64
+ ```
65
+
66
+ ## Remote URLs
67
+
68
+ Remote URLs can be used directly without `staticFile()`:
69
+
70
+ ```tsx
71
+ <Img src="https://example.com/image.png" />
72
+ <Video src="https://remotion.media/video.mp4" />
73
+ ```
74
+
75
+ ## Important notes
76
+
77
+ - Remotion components (`<Img>`, `<Video>`, `<Audio>`) ensure assets are fully loaded before rendering
78
+ - Special characters in filenames (`#`, `?`, `&`) are automatically encoded
@@ -0,0 +1,198 @@
1
+ ---
2
+ name: audio-visualization
3
+ description: Audio visualization patterns - spectrum bars, waveforms, bass-reactive effects
4
+ metadata:
5
+ tags: audio, visualization, spectrum, waveform, bass, music, audiogram, frequency
6
+ ---
7
+
8
+ # Audio Visualization in Remotion
9
+
10
+ ## Prerequisites
11
+
12
+ ```bash
13
+ npx remotion add @remotion/media-utils
14
+ ```
15
+
16
+ ## Loading Audio Data
17
+
18
+ Use `useWindowedAudioData()` (https://www.remotion.dev/docs/use-windowed-audio-data) to load audio data:
19
+
20
+ ```tsx
21
+ import { useWindowedAudioData } from "@remotion/media-utils";
22
+ import { staticFile, useCurrentFrame, useVideoConfig } from "remotion";
23
+
24
+ const frame = useCurrentFrame();
25
+ const { fps } = useVideoConfig();
26
+
27
+ const { audioData, dataOffsetInSeconds } = useWindowedAudioData({
28
+ src: staticFile("podcast.wav"),
29
+ frame,
30
+ fps,
31
+ windowInSeconds: 30,
32
+ });
33
+ ```
34
+
35
+ ## Spectrum Bar Visualization
36
+
37
+ Use `visualizeAudio()` (https://www.remotion.dev/docs/visualize-audio) to get frequency data for bar charts:
38
+
39
+ ```tsx
40
+ import { useWindowedAudioData, visualizeAudio } from "@remotion/media-utils";
41
+ import { staticFile, useCurrentFrame, useVideoConfig } from "remotion";
42
+
43
+ const frame = useCurrentFrame();
44
+ const { fps } = useVideoConfig();
45
+
46
+ const { audioData, dataOffsetInSeconds } = useWindowedAudioData({
47
+ src: staticFile("music.mp3"),
48
+ frame,
49
+ fps,
50
+ windowInSeconds: 30,
51
+ });
52
+
53
+ if (!audioData) {
54
+ return null;
55
+ }
56
+
57
+ const frequencies = visualizeAudio({
58
+ fps,
59
+ frame,
60
+ audioData,
61
+ numberOfSamples: 256,
62
+ optimizeFor: "speed",
63
+ dataOffsetInSeconds,
64
+ });
65
+
66
+ return (
67
+ <div style={{ display: "flex", alignItems: "flex-end", height: 200 }}>
68
+ {frequencies.map((v, i) => (
69
+ <div
70
+ key={i}
71
+ style={{
72
+ flex: 1,
73
+ height: `${v * 100}%`,
74
+ backgroundColor: "#0b84f3",
75
+ margin: "0 1px",
76
+ }}
77
+ />
78
+ ))}
79
+ </div>
80
+ );
81
+ ```
82
+
83
+ - `numberOfSamples` must be power of 2 (32, 64, 128, 256, 512, 1024)
84
+ - Values range 0-1; left of array = bass, right = highs
85
+ - Use `optimizeFor: "speed"` for Lambda or high sample counts
86
+
87
+ **Important:** When passing `audioData` to child components, also pass the `frame` from the parent. Do not call `useCurrentFrame()` in each child - this causes discontinuous visualization when children are inside `<Sequence>` with offsets.
88
+
89
+ ## Waveform Visualization
90
+
91
+ Use `visualizeAudioWaveform()` (https://www.remotion.dev/docs/media-utils/visualize-audio-waveform) with `createSmoothSvgPath()` (https://www.remotion.dev/docs/media-utils/create-smooth-svg-path) for oscilloscope-style displays:
92
+
93
+ ```tsx
94
+ import {
95
+ createSmoothSvgPath,
96
+ useWindowedAudioData,
97
+ visualizeAudioWaveform,
98
+ } from "@remotion/media-utils";
99
+ import { staticFile, useCurrentFrame, useVideoConfig } from "remotion";
100
+
101
+ const frame = useCurrentFrame();
102
+ const { width, fps } = useVideoConfig();
103
+ const HEIGHT = 200;
104
+
105
+ const { audioData, dataOffsetInSeconds } = useWindowedAudioData({
106
+ src: staticFile("voice.wav"),
107
+ frame,
108
+ fps,
109
+ windowInSeconds: 30,
110
+ });
111
+
112
+ if (!audioData) {
113
+ return null;
114
+ }
115
+
116
+ const waveform = visualizeAudioWaveform({
117
+ fps,
118
+ frame,
119
+ audioData,
120
+ numberOfSamples: 256,
121
+ windowInSeconds: 0.5,
122
+ dataOffsetInSeconds,
123
+ });
124
+
125
+ const path = createSmoothSvgPath({
126
+ points: waveform.map((y, i) => ({
127
+ x: (i / (waveform.length - 1)) * width,
128
+ y: HEIGHT / 2 + (y * HEIGHT) / 2,
129
+ })),
130
+ });
131
+
132
+ return (
133
+ <svg width={width} height={HEIGHT}>
134
+ <path d={path} fill="none" stroke="#0b84f3" strokeWidth={2} />
135
+ </svg>
136
+ );
137
+ ```
138
+
139
+ ## Bass-Reactive Effects
140
+
141
+ Extract low frequencies for beat-reactive animations:
142
+
143
+ ```tsx
144
+ const frequencies = visualizeAudio({
145
+ fps,
146
+ frame,
147
+ audioData,
148
+ numberOfSamples: 128,
149
+ optimizeFor: "speed",
150
+ dataOffsetInSeconds,
151
+ });
152
+
153
+ const lowFrequencies = frequencies.slice(0, 32);
154
+ const bassIntensity =
155
+ lowFrequencies.reduce((sum, v) => sum + v, 0) / lowFrequencies.length;
156
+
157
+ const scale = 1 + bassIntensity * 0.5;
158
+ const opacity = Math.min(0.6, bassIntensity * 0.8);
159
+ ```
160
+
161
+ ## Volume-Based Waveform
162
+
163
+ Use `getWaveformPortion()` (https://www.remotion.dev/docs/get-waveform-portion) when you need simplified volume data instead of frequency spectrum:
164
+
165
+ ```tsx
166
+ import { getWaveformPortion } from "@remotion/media-utils";
167
+ import { useCurrentFrame, useVideoConfig } from "remotion";
168
+
169
+ const frame = useCurrentFrame();
170
+ const { fps } = useVideoConfig();
171
+ const currentTimeInSeconds = frame / fps;
172
+
173
+ const waveform = getWaveformPortion({
174
+ audioData,
175
+ startTimeInSeconds: currentTimeInSeconds,
176
+ durationInSeconds: 5,
177
+ numberOfSamples: 50,
178
+ });
179
+
180
+ // Returns array of { index, amplitude } objects (amplitude: 0-1)
181
+ waveform.map((bar) => (
182
+ <div key={bar.index} style={{ height: bar.amplitude * 100 }} />
183
+ ));
184
+ ```
185
+
186
+ ## Postprocessing
187
+
188
+ Low frequencies naturally dominate. Apply logarithmic scaling for visual balance:
189
+
190
+ ```tsx
191
+ const minDb = -100;
192
+ const maxDb = -30;
193
+
194
+ const scaled = frequencies.map((value) => {
195
+ const db = 20 * Math.log10(value);
196
+ return (db - minDb) / (maxDb - minDb);
197
+ });
198
+ ```
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: audio
3
+ description: Using audio and sound in Remotion - importing, trimming, volume, speed, pitch
4
+ metadata:
5
+ tags: audio, media, trim, volume, speed, loop, pitch, mute, sound, sfx
6
+ ---
7
+
8
+ # Using audio in Remotion
9
+
10
+ ## Prerequisites
11
+
12
+ First, the @remotion/media package needs to be installed.
13
+ If it is not installed, use the following command:
14
+
15
+ ```bash
16
+ npx remotion add @remotion/media
17
+ ```
18
+
19
+ ## Importing Audio
20
+
21
+ Use `<Audio>` from `@remotion/media` to add audio to your composition.
22
+
23
+ ```tsx
24
+ import { Audio } from "@remotion/media";
25
+ import { staticFile } from "remotion";
26
+
27
+ export const MyComposition = () => {
28
+ return <Audio src={staticFile("audio.mp3")} />;
29
+ };
30
+ ```
31
+
32
+ Remote URLs are also supported:
33
+
34
+ ```tsx
35
+ <Audio src="https://remotion.media/audio.mp3" />
36
+ ```
37
+
38
+ By default, audio plays from the start, at full volume and full length.
39
+ Multiple audio tracks can be layered by adding multiple `<Audio>` components.
40
+
41
+ ## Trimming
42
+
43
+ Use `trimBefore` and `trimAfter` to remove portions of the audio. Values are in frames.
44
+
45
+ ```tsx
46
+ const { fps } = useVideoConfig();
47
+
48
+ return (
49
+ <Audio
50
+ src={staticFile("audio.mp3")}
51
+ trimBefore={2 * fps} // Skip the first 2 seconds
52
+ trimAfter={10 * fps} // End at the 10 second mark
53
+ />
54
+ );
55
+ ```
56
+
57
+ The audio still starts playing at the beginning of the composition - only the specified portion is played.
58
+
59
+ ## Delaying
60
+
61
+ Wrap the audio in a `<Sequence>` to delay when it starts:
62
+
63
+ ```tsx
64
+ import { Sequence, staticFile } from "remotion";
65
+ import { Audio } from "@remotion/media";
66
+
67
+ const { fps } = useVideoConfig();
68
+
69
+ return (
70
+ <Sequence from={1 * fps}>
71
+ <Audio src={staticFile("audio.mp3")} />
72
+ </Sequence>
73
+ );
74
+ ```
75
+
76
+ The audio will start playing after 1 second.
77
+
78
+ ## Volume
79
+
80
+ Set a static volume (0 to 1):
81
+
82
+ ```tsx
83
+ <Audio src={staticFile("audio.mp3")} volume={0.5} />
84
+ ```
85
+
86
+ Or use a callback for dynamic volume based on the current frame:
87
+
88
+ ```tsx
89
+ import { interpolate } from "remotion";
90
+
91
+ const { fps } = useVideoConfig();
92
+
93
+ return (
94
+ <Audio
95
+ src={staticFile("audio.mp3")}
96
+ volume={(f) =>
97
+ interpolate(f, [0, 1 * fps], [0, 1], { extrapolateRight: "clamp" })
98
+ }
99
+ />
100
+ );
101
+ ```
102
+
103
+ The value of `f` starts at 0 when the audio begins to play, not the composition frame.
104
+
105
+ ## Muting
106
+
107
+ Use `muted` to silence the audio. It can be set dynamically:
108
+
109
+ ```tsx
110
+ const frame = useCurrentFrame();
111
+ const { fps } = useVideoConfig();
112
+
113
+ return (
114
+ <Audio
115
+ src={staticFile("audio.mp3")}
116
+ muted={frame >= 2 * fps && frame <= 4 * fps} // Mute between 2s and 4s
117
+ />
118
+ );
119
+ ```
120
+
121
+ ## Speed
122
+
123
+ Use `playbackRate` to change the playback speed:
124
+
125
+ ```tsx
126
+ <Audio src={staticFile("audio.mp3")} playbackRate={2} /> {/* 2x speed */}
127
+ <Audio src={staticFile("audio.mp3")} playbackRate={0.5} /> {/* Half speed */}
128
+ ```
129
+
130
+ Reverse playback is not supported.
131
+
132
+ ## Looping
133
+
134
+ Use `loop` to loop the audio indefinitely:
135
+
136
+ ```tsx
137
+ <Audio src={staticFile("audio.mp3")} loop />
138
+ ```
139
+
140
+ Use `loopVolumeCurveBehavior` to control how the frame count behaves when looping:
141
+
142
+ - `"repeat"`: Frame count resets to 0 each loop (default)
143
+ - `"extend"`: Frame count continues incrementing
144
+
145
+ ```tsx
146
+ <Audio
147
+ src={staticFile("audio.mp3")}
148
+ loop
149
+ loopVolumeCurveBehavior="extend"
150
+ volume={(f) => interpolate(f, [0, 300], [1, 0])} // Fade out over multiple loops
151
+ />
152
+ ```
153
+
154
+ ## Pitch
155
+
156
+ Use `toneFrequency` to adjust the pitch without affecting speed. Values range from 0.01 to 2:
157
+
158
+ ```tsx
159
+ <Audio
160
+ src={staticFile("audio.mp3")}
161
+ toneFrequency={1.5} // Higher pitch
162
+ />
163
+ <Audio
164
+ src={staticFile("audio.mp3")}
165
+ toneFrequency={0.8} // Lower pitch
166
+ />
167
+ ```
168
+
169
+ Pitch shifting only works during server-side rendering, not in the Remotion Studio preview or in the `<Player />`.