sonilo-video-kit 0.1.0__tar.gz
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.
- sonilo_video_kit-0.1.0/.gitignore +8 -0
- sonilo_video_kit-0.1.0/LICENSE +21 -0
- sonilo_video_kit-0.1.0/PKG-INFO +144 -0
- sonilo_video_kit-0.1.0/README.md +128 -0
- sonilo_video_kit-0.1.0/examples/score_video.py +19 -0
- sonilo_video_kit-0.1.0/pyproject.toml +26 -0
- sonilo_video_kit-0.1.0/src/sonilo_video_kit/__init__.py +20 -0
- sonilo_video_kit-0.1.0/src/sonilo_video_kit/_ducking_api.py +496 -0
- sonilo_video_kit-0.1.0/src/sonilo_video_kit/_ffmpeg.py +465 -0
- sonilo_video_kit-0.1.0/src/sonilo_video_kit/duck.py +344 -0
- sonilo_video_kit-0.1.0/src/sonilo_video_kit/errors.py +44 -0
- sonilo_video_kit-0.1.0/src/sonilo_video_kit/generate.py +27 -0
- sonilo_video_kit-0.1.0/src/sonilo_video_kit/loudness.py +28 -0
- sonilo_video_kit-0.1.0/src/sonilo_video_kit/mix.py +166 -0
- sonilo_video_kit-0.1.0/tests/__init__.py +0 -0
- sonilo_video_kit-0.1.0/tests/test_duck.py +24 -0
- sonilo_video_kit-0.1.0/tests/test_ducking_api.py +166 -0
- sonilo_video_kit-0.1.0/tests/test_errors.py +30 -0
- sonilo_video_kit-0.1.0/tests/test_ffmpeg_media.py +53 -0
- sonilo_video_kit-0.1.0/tests/test_generate.py +30 -0
- sonilo_video_kit-0.1.0/tests/test_loudness.py +38 -0
- sonilo_video_kit-0.1.0/tests/test_mix.py +45 -0
- sonilo_video_kit-0.1.0/tests/test_probe_video.py +28 -0
- sonilo_video_kit-0.1.0/tests/test_public_surface.py +18 -0
- sonilo_video_kit-0.1.0/tests/test_run_process.py +26 -0
- sonilo_video_kit-0.1.0/tests/test_smoke.py +5 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sonilo AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sonilo-video-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Video helpers for the Sonilo API: generate a soundtrack and mix it into your video with ffmpeg
|
|
5
|
+
Project-URL: Repository, https://github.com/sonilo-ai/sonilo-python
|
|
6
|
+
Author: Sonilo AI
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai,ducking,ffmpeg,music,sonilo,soundtrack,video
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: sonilo<0.4,>=0.3
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
14
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# sonilo-video-kit
|
|
18
|
+
|
|
19
|
+
Video helpers for the [Sonilo](https://sonilo.com) API: generate a soundtrack
|
|
20
|
+
for a video and mix it in locally with ffmpeg. Python ≥ 3.9.
|
|
21
|
+
|
|
22
|
+
Requires `ffmpeg` + `ffprobe` on your PATH (macOS: `brew install ffmpeg`,
|
|
23
|
+
Debian/Ubuntu: `apt-get install ffmpeg`) — or pass `ffmpeg_path`/`ffprobe_path`
|
|
24
|
+
to any function.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install sonilo sonilo-video-kit
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`sonilo` (the core client) is a required dependency — it is installed
|
|
33
|
+
automatically alongside the kit, but is shown here for clarity.
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from sonilo_video_kit import generate_music_for_video, mix_with_video
|
|
39
|
+
|
|
40
|
+
track = generate_music_for_video("./clip.mp4", prompt="upbeat, energetic") # uses SONILO_API_KEY
|
|
41
|
+
|
|
42
|
+
mix_with_video(
|
|
43
|
+
video="./clip.mp4",
|
|
44
|
+
audio=track.audio,
|
|
45
|
+
output="./clip.scored.mp4",
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Loudness-matched mixing
|
|
50
|
+
|
|
51
|
+
By default the kit measures the loudness (LUFS) of your video's own audio and
|
|
52
|
+
of the generated music, then sits the music 4 LU below the original — so
|
|
53
|
+
dialogue stays intelligible without hand-tuning. The final file is normalized
|
|
54
|
+
to −14 LUFS (streaming-platform delivery level) with a −1 dBFS peak limiter.
|
|
55
|
+
The delivery-normalize boost is capped at +12 dB; attenuation (bringing an
|
|
56
|
+
overly loud render down to target) is uncapped.
|
|
57
|
+
|
|
58
|
+
- `music_volume` (0–1, default 0.5): 0.5 is the matched level; each step of
|
|
59
|
+
0.25 shifts ±6 dB (full range ±12 dB). 0 mutes the music.
|
|
60
|
+
- `original_volume` (0–1, default 1): absolute — 1 keeps the original exactly
|
|
61
|
+
as recorded, 0 removes it entirely.
|
|
62
|
+
- `loudness_match=False` switches both knobs to plain absolute gains.
|
|
63
|
+
- `normalize=False` skips the delivery-loudness pass.
|
|
64
|
+
|
|
65
|
+
If loudness measurement fails (exotic codecs, unreadable audio), the kit
|
|
66
|
+
silently falls back to absolute-gain behavior rather than failing your render.
|
|
67
|
+
|
|
68
|
+
## Ducking
|
|
69
|
+
|
|
70
|
+
`mix_with_video` sits the music at a fixed level under the original audio.
|
|
71
|
+
`duck_music_under_speech` goes further: it rides the music down whenever
|
|
72
|
+
someone speaks and back up in the gaps.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from sonilo_video_kit import duck_music_under_speech
|
|
76
|
+
|
|
77
|
+
duck_music_under_speech(
|
|
78
|
+
video="./interview.mp4",
|
|
79
|
+
audio=track.audio,
|
|
80
|
+
output="./interview.ducked.mp4",
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Unlike `mix_with_video`, which is entirely local and free, this calls the
|
|
85
|
+
Sonilo ducking API and is **billed on your video's duration**. The kit
|
|
86
|
+
uploads only the video's extracted audio track — your picture never leaves
|
|
87
|
+
the machine and is copied into the result untouched. The API sets the
|
|
88
|
+
ducking curve itself (speech gate, duck depth, −14 LUFS delivery, −1 dBTP
|
|
89
|
+
ceiling), so there are no volume knobs to pass.
|
|
90
|
+
|
|
91
|
+
Requirements are enforced locally, before anything is uploaded or charged:
|
|
92
|
+
the video must have an audio track and a real picture, it must run no longer
|
|
93
|
+
than **360 s**, `output` must carry a file extension and live in a directory
|
|
94
|
+
that already exists and is writable, and your picture must be
|
|
95
|
+
stream-copyable into `output`'s container. The ducked audio is always written
|
|
96
|
+
as **AAC**, so `output` must be a container that can carry AAC — `.webm`
|
|
97
|
+
(Vorbis/Opus only) is rejected before the API call whatever your picture
|
|
98
|
+
codec. Both the **360 s** limit and the amount billed are measured on the
|
|
99
|
+
**picture**, not the container (a video whose audio outlives its picture is
|
|
100
|
+
billed for the picture's length). Any failure raises before the API is called;
|
|
101
|
+
the kit never quietly falls back to an un-ducked mix. Use `mix_with_video` for
|
|
102
|
+
silent or longer videos.
|
|
103
|
+
|
|
104
|
+
### Nothing you have paid for is thrown away
|
|
105
|
+
|
|
106
|
+
The API charges when the job is **submitted**, and the task then runs to
|
|
107
|
+
completion server-side whatever happens to your process — so every failure
|
|
108
|
+
after that point keeps the mix you paid for reachable. Transient failures are
|
|
109
|
+
retried with backoff (a 5xx while polling, a dropped connection, a 503 from the
|
|
110
|
+
storage host mid-download).
|
|
111
|
+
|
|
112
|
+
If a failure after submit is **not** locally recoverable — the poll fails
|
|
113
|
+
terminally, the download can't complete, or the wait times out — the raised
|
|
114
|
+
`VideoKitError` names the **task id** and tells you the charge was already made
|
|
115
|
+
and the task is still finishing server-side. **No local rescue file is written
|
|
116
|
+
in this case.** Recover by polling `GET /v1/tasks/<task_id>` yourself until it
|
|
117
|
+
reports `succeeded`, then download the `output_url` it returns: that re-fetches
|
|
118
|
+
the mix you already paid for, instead of calling `duck_music_under_speech`
|
|
119
|
+
again and being billed twice.
|
|
120
|
+
|
|
121
|
+
If instead a final, purely-local step fails *after* the API call — remuxing
|
|
122
|
+
the ducked audio onto your picture, or placing the file at `output` (e.g. the
|
|
123
|
+
disk fills mid-mux) — the kit saves the downloaded ducked audio to
|
|
124
|
+
`<output>.ducked.wav` and raises a `VideoKitError` naming that path, so you can
|
|
125
|
+
fix the local problem and finish locally. A rescue never overwrites an earlier
|
|
126
|
+
one (`<output>.ducked.1.wav`, …), and `output` is always placed atomically.
|
|
127
|
+
|
|
128
|
+
## Errors
|
|
129
|
+
|
|
130
|
+
`VideoKitError` (invalid arguments, unreadable video), `FfmpegNotFoundError`
|
|
131
|
+
(ffmpeg/ffprobe missing — message includes install hints), `FfmpegError`
|
|
132
|
+
(ffmpeg failed — carries `exit_code` and `stderr_tail`), `DuckingFailedError`
|
|
133
|
+
(the ducking API accepted the job but could not finish it — carries `code`
|
|
134
|
+
and `refunded`). Errors from the Sonilo API pass through as the `sonilo`
|
|
135
|
+
package's typed errors.
|
|
136
|
+
|
|
137
|
+
`refunded` reports what the server said **at the moment the task was polled**,
|
|
138
|
+
not a final verdict: the backend marks a task failed before it reverses the
|
|
139
|
+
charge (and retries a failed reversal), so `refunded: False` means the reversal
|
|
140
|
+
had not landed yet, not that you were definitely billed.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# sonilo-video-kit
|
|
2
|
+
|
|
3
|
+
Video helpers for the [Sonilo](https://sonilo.com) API: generate a soundtrack
|
|
4
|
+
for a video and mix it in locally with ffmpeg. Python ≥ 3.9.
|
|
5
|
+
|
|
6
|
+
Requires `ffmpeg` + `ffprobe` on your PATH (macOS: `brew install ffmpeg`,
|
|
7
|
+
Debian/Ubuntu: `apt-get install ffmpeg`) — or pass `ffmpeg_path`/`ffprobe_path`
|
|
8
|
+
to any function.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install sonilo sonilo-video-kit
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`sonilo` (the core client) is a required dependency — it is installed
|
|
17
|
+
automatically alongside the kit, but is shown here for clarity.
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from sonilo_video_kit import generate_music_for_video, mix_with_video
|
|
23
|
+
|
|
24
|
+
track = generate_music_for_video("./clip.mp4", prompt="upbeat, energetic") # uses SONILO_API_KEY
|
|
25
|
+
|
|
26
|
+
mix_with_video(
|
|
27
|
+
video="./clip.mp4",
|
|
28
|
+
audio=track.audio,
|
|
29
|
+
output="./clip.scored.mp4",
|
|
30
|
+
)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Loudness-matched mixing
|
|
34
|
+
|
|
35
|
+
By default the kit measures the loudness (LUFS) of your video's own audio and
|
|
36
|
+
of the generated music, then sits the music 4 LU below the original — so
|
|
37
|
+
dialogue stays intelligible without hand-tuning. The final file is normalized
|
|
38
|
+
to −14 LUFS (streaming-platform delivery level) with a −1 dBFS peak limiter.
|
|
39
|
+
The delivery-normalize boost is capped at +12 dB; attenuation (bringing an
|
|
40
|
+
overly loud render down to target) is uncapped.
|
|
41
|
+
|
|
42
|
+
- `music_volume` (0–1, default 0.5): 0.5 is the matched level; each step of
|
|
43
|
+
0.25 shifts ±6 dB (full range ±12 dB). 0 mutes the music.
|
|
44
|
+
- `original_volume` (0–1, default 1): absolute — 1 keeps the original exactly
|
|
45
|
+
as recorded, 0 removes it entirely.
|
|
46
|
+
- `loudness_match=False` switches both knobs to plain absolute gains.
|
|
47
|
+
- `normalize=False` skips the delivery-loudness pass.
|
|
48
|
+
|
|
49
|
+
If loudness measurement fails (exotic codecs, unreadable audio), the kit
|
|
50
|
+
silently falls back to absolute-gain behavior rather than failing your render.
|
|
51
|
+
|
|
52
|
+
## Ducking
|
|
53
|
+
|
|
54
|
+
`mix_with_video` sits the music at a fixed level under the original audio.
|
|
55
|
+
`duck_music_under_speech` goes further: it rides the music down whenever
|
|
56
|
+
someone speaks and back up in the gaps.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from sonilo_video_kit import duck_music_under_speech
|
|
60
|
+
|
|
61
|
+
duck_music_under_speech(
|
|
62
|
+
video="./interview.mp4",
|
|
63
|
+
audio=track.audio,
|
|
64
|
+
output="./interview.ducked.mp4",
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Unlike `mix_with_video`, which is entirely local and free, this calls the
|
|
69
|
+
Sonilo ducking API and is **billed on your video's duration**. The kit
|
|
70
|
+
uploads only the video's extracted audio track — your picture never leaves
|
|
71
|
+
the machine and is copied into the result untouched. The API sets the
|
|
72
|
+
ducking curve itself (speech gate, duck depth, −14 LUFS delivery, −1 dBTP
|
|
73
|
+
ceiling), so there are no volume knobs to pass.
|
|
74
|
+
|
|
75
|
+
Requirements are enforced locally, before anything is uploaded or charged:
|
|
76
|
+
the video must have an audio track and a real picture, it must run no longer
|
|
77
|
+
than **360 s**, `output` must carry a file extension and live in a directory
|
|
78
|
+
that already exists and is writable, and your picture must be
|
|
79
|
+
stream-copyable into `output`'s container. The ducked audio is always written
|
|
80
|
+
as **AAC**, so `output` must be a container that can carry AAC — `.webm`
|
|
81
|
+
(Vorbis/Opus only) is rejected before the API call whatever your picture
|
|
82
|
+
codec. Both the **360 s** limit and the amount billed are measured on the
|
|
83
|
+
**picture**, not the container (a video whose audio outlives its picture is
|
|
84
|
+
billed for the picture's length). Any failure raises before the API is called;
|
|
85
|
+
the kit never quietly falls back to an un-ducked mix. Use `mix_with_video` for
|
|
86
|
+
silent or longer videos.
|
|
87
|
+
|
|
88
|
+
### Nothing you have paid for is thrown away
|
|
89
|
+
|
|
90
|
+
The API charges when the job is **submitted**, and the task then runs to
|
|
91
|
+
completion server-side whatever happens to your process — so every failure
|
|
92
|
+
after that point keeps the mix you paid for reachable. Transient failures are
|
|
93
|
+
retried with backoff (a 5xx while polling, a dropped connection, a 503 from the
|
|
94
|
+
storage host mid-download).
|
|
95
|
+
|
|
96
|
+
If a failure after submit is **not** locally recoverable — the poll fails
|
|
97
|
+
terminally, the download can't complete, or the wait times out — the raised
|
|
98
|
+
`VideoKitError` names the **task id** and tells you the charge was already made
|
|
99
|
+
and the task is still finishing server-side. **No local rescue file is written
|
|
100
|
+
in this case.** Recover by polling `GET /v1/tasks/<task_id>` yourself until it
|
|
101
|
+
reports `succeeded`, then download the `output_url` it returns: that re-fetches
|
|
102
|
+
the mix you already paid for, instead of calling `duck_music_under_speech`
|
|
103
|
+
again and being billed twice.
|
|
104
|
+
|
|
105
|
+
If instead a final, purely-local step fails *after* the API call — remuxing
|
|
106
|
+
the ducked audio onto your picture, or placing the file at `output` (e.g. the
|
|
107
|
+
disk fills mid-mux) — the kit saves the downloaded ducked audio to
|
|
108
|
+
`<output>.ducked.wav` and raises a `VideoKitError` naming that path, so you can
|
|
109
|
+
fix the local problem and finish locally. A rescue never overwrites an earlier
|
|
110
|
+
one (`<output>.ducked.1.wav`, …), and `output` is always placed atomically.
|
|
111
|
+
|
|
112
|
+
## Errors
|
|
113
|
+
|
|
114
|
+
`VideoKitError` (invalid arguments, unreadable video), `FfmpegNotFoundError`
|
|
115
|
+
(ffmpeg/ffprobe missing — message includes install hints), `FfmpegError`
|
|
116
|
+
(ffmpeg failed — carries `exit_code` and `stderr_tail`), `DuckingFailedError`
|
|
117
|
+
(the ducking API accepted the job but could not finish it — carries `code`
|
|
118
|
+
and `refunded`). Errors from the Sonilo API pass through as the `sonilo`
|
|
119
|
+
package's typed errors.
|
|
120
|
+
|
|
121
|
+
`refunded` reports what the server said **at the moment the task was polled**,
|
|
122
|
+
not a final verdict: the backend marks a task failed before it reverses the
|
|
123
|
+
charge (and retries a failed reversal), so `refunded: False` means the reversal
|
|
124
|
+
had not landed yet, not that you were definitely billed.
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Usage: SONILO_API_KEY=sk_... python examples/score_video.py clip.mp4 "upbeat, energetic"
|
|
2
|
+
|
|
3
|
+
Generates a soundtrack for a video and mixes it in locally with ffmpeg.
|
|
4
|
+
Requires ffmpeg + ffprobe on PATH.
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from sonilo_video_kit import generate_music_for_video, mix_with_video
|
|
9
|
+
|
|
10
|
+
video = sys.argv[1] if len(sys.argv) > 1 else "clip.mp4"
|
|
11
|
+
prompt = sys.argv[2] if len(sys.argv) > 2 else "cinematic orchestral score"
|
|
12
|
+
output = "clip.scored.mp4"
|
|
13
|
+
|
|
14
|
+
track = generate_music_for_video(video, prompt=prompt)
|
|
15
|
+
|
|
16
|
+
out = mix_with_video(video=video, audio=track.audio, output=output)
|
|
17
|
+
|
|
18
|
+
title = f' — "{track.title}"' if track.title else ""
|
|
19
|
+
print(f"Saved {out}{title}")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sonilo-video-kit"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Video helpers for the Sonilo API: generate a soundtrack and mix it into your video with ffmpeg"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "Sonilo AI" }]
|
|
13
|
+
dependencies = ["sonilo>=0.3,<0.4"]
|
|
14
|
+
keywords = ["sonilo", "video", "music", "ffmpeg", "soundtrack", "ducking", "ai"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Repository = "https://github.com/sonilo-ai/sonilo-python"
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = ["pytest>=8", "respx>=0.21"]
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/sonilo_video_kit"]
|
|
24
|
+
|
|
25
|
+
[tool.pytest.ini_options]
|
|
26
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Video helpers for the Sonilo API (ffmpeg-based)."""
|
|
2
|
+
from .duck import (
|
|
3
|
+
MAX_DUCKED_MIX_BYTES, MAX_DUCKING_DURATION_SECONDS, duck_music_under_speech,
|
|
4
|
+
)
|
|
5
|
+
from .errors import (
|
|
6
|
+
DuckingFailedError, FfmpegError, FfmpegNotFoundError, VideoKitError,
|
|
7
|
+
)
|
|
8
|
+
from .generate import VideoMusicClient, generate_music_for_video
|
|
9
|
+
from .loudness import (
|
|
10
|
+
DELIVERY_TARGET_LUFS, FALLBACK_MUSIC_LUFS, GAP_BELOW_VOICE_LU, OUTPUT_CEILING_DBFS,
|
|
11
|
+
)
|
|
12
|
+
from .mix import mix_with_video
|
|
13
|
+
|
|
14
|
+
__all__ = sorted([
|
|
15
|
+
"DELIVERY_TARGET_LUFS", "DuckingFailedError", "FALLBACK_MUSIC_LUFS", "FfmpegError",
|
|
16
|
+
"FfmpegNotFoundError", "GAP_BELOW_VOICE_LU", "MAX_DUCKED_MIX_BYTES",
|
|
17
|
+
"MAX_DUCKING_DURATION_SECONDS", "OUTPUT_CEILING_DBFS", "VideoKitError",
|
|
18
|
+
"VideoMusicClient", "duck_music_under_speech", "generate_music_for_video",
|
|
19
|
+
"mix_with_video",
|
|
20
|
+
])
|