musicfit 0.1.2 → 0.2.1

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # musicfit
2
2
 
3
- Fit a music track to a video cut. musicfit makes the track exactly as long as the video by cutting or looping whole bars, lands its drop on the second you pick, ends it on a beat, ducks it under a voice and sets its loudness.
3
+ Fit a music track to a video cut. musicfit makes the track exactly as long as the video by cutting or looping whole bars, lands its drop on the second you pick, ends it on a beat, ducks it under a voice and sets its loudness. Give it the video too and it lays the track under it.
4
4
 
5
5
  ```bash
6
6
  npx musicfit@latest track.mp3 --length 32 --hit 27.5 --fps 30
@@ -12,12 +12,22 @@ AI music models ignore the length you ask for. Ask for 30 seconds and you may ge
12
12
 
13
13
  The track can come from any music model, and [Wiro](https://wiro.ai/explore/music-generation) has several.
14
14
 
15
+ ## Requirements
16
+
17
+ - Node.js 22 or newer.
18
+ - ffmpeg 4.4 or newer, Ubuntu 22.04's 4.4.2 included. musicfit does not use ffprobe, and needs no H.264 encoder, as `--video` copies the picture as it is. The filters it uses are ffmpeg's own, and so is the AAC encoder. `brew install ffmpeg` and `winget install ffmpeg` give a new enough one, and so does `sudo apt install ffmpeg` on Ubuntu 22.04, Debian 12 or newer. Set `FFMPEG_PATH` to use another build, as a path or a name on your PATH. Before any work, a run checks that ffmpeg starts and answers as ffmpeg; one that does not stops the run with what the system or the program said, and how to get one. One that gives no answer in two minutes, as on a machine short of memory, stops the run too, and the message says to run it again. It does not check the version number ffmpeg gives, so an ffmpeg older than 4.4 goes ahead, though no test has run on one. When the system is short of open files, processes or memory to start ffmpeg, the run says so and what to do.
19
+ - Linux, where it is tested: on Ubuntu 22.04 with its ffmpeg 4.4.2, on Ubuntu 24.04 with its ffmpeg 6.1 and on GitHub's ubuntu-latest runner, on every push and before every release. This release has also run on Ubuntu 26.04 with ffmpeg 8.0. It is written for macOS and Windows as well, but it has not been run on either.
20
+ - Room on the disk for the files of a run and those of the run before it, as the earlier files stay until the new ones are in place. `fitted.wav` and `mix.wav` take 17 MB each for every minute of the fit, and `video.mp4` as much as the video's picture and about 2 MB a minute of sound. Until a run with `--video` ends, it also keeps up to four AAC encodings of that sound, about 2 MB a minute each, in its hidden folder in the out folder, or next to it when the out folder is not there yet.
21
+ - Memory that grows with the length of the fit and of the track, and with that of a voice or of the video's own sound, which are read to their end even where they run on past the fit: a 10 minute fit of a 10 minute track took 0.9 to 1.7 GB, and a 20 second fit with a voice took 0.2 GB, or 0.8 GB when the voice ran on 10 minutes past it (see [Limits](#limits)).
22
+ - A run needs no network access, no browser and no API key: musicfit reads files on your disk, and nothing leaves your machine. Installing it needs the network, and `npx musicfit@latest` asks npm for the newest version each time it runs. There are no npm dependencies: the beat tracking is plain JavaScript.
23
+
15
24
  ## What you get
16
25
 
17
26
  ```
18
27
  musicfit-track/
19
28
  fitted.wav the track, exactly --length long, 48 kHz stereo, at -14 LUFS
20
29
  mix.wav with --voice: the voice over the ducked music
30
+ video.mp4 with --video: the video with the track under it, its picture as it was
21
31
  cues.json every beat and bar, the hit, the seams and the fade, in seconds and frames
22
32
  seams.png the track, the result and what went where, like the picture above
23
33
  FIT.md what was done and where to listen, for people and agents
@@ -25,8 +35,9 @@ musicfit-track/
25
35
 
26
36
  - Cuts and loops are made only on bar lines, in whole bars, between bars that sound alike, and keep the track's four-bar phrases whole where they can. Each seam is a 30 ms crossfade that ends just before the downbeat, so the new bar's first hit plays whole, and the hits on either side are lined up to the millisecond.
27
37
  - The drop lands on `--hit` to the sample, on the start of its attack. Nothing that comes after the drop in the track plays before it, so it is heard first where you asked.
28
- - The track starts the way it starts, with its own pickup, and ends on its own last hit when it has one and there is room. Otherwise it ends with a fade that starts on a beat and ends on one.
38
+ - The track starts the way it starts, with its own pickup, and ends on its own last hit when it has one and there is room. Otherwise it ends with a fade that starts on a beat and ends on one, and when the music ends before the fit does, FIT.md says how many seconds are silent. A fit too short for the track's opening and its ending both can start later in the track, on a bar, so without `--hit` it can be the track's last bars alone; FIT.md says where it starts.
29
39
  - `--voice` lowers the music 10 dB (`--duck`) wherever the voice speaks, starting 0.15 s before each phrase and coming back 0.5 s after.
40
+ - `--video` lays the result under the video: see [Laying it under a video](#laying-it-under-a-video).
30
41
  - Loudness is measured with ffmpeg's EBU R128 meter and set with one gain; a true-peak limiter holds the peaks under -1 dBTP only when that gain would push them over.
31
42
  - `cues.json` lists `beats`, `bars` and `hits` (the drop and the track's own last hit) as frame numbers at `--fps`, ready for a Remotion `<Sequence from={cues.bars[8]}>`, and the same moments in seconds under `times`. The seams, sections, fade and loudness are in it too:
32
43
 
@@ -46,6 +57,46 @@ musicfit-track/
46
57
 
47
58
  The lists are cut short here; the file has every beat and bar.
48
59
 
60
+ `--analyze` reads a track without fitting it and saves its `cues.json`, `seams.png` and `FIT.md` in `musicfit-track.analysis/` by default. No fit's folder can have that name, so reading a track never replaces a fit, unless both are given the same `--out`: an analysis that ends well in a fit's folder replaces that fit, its `fitted.wav`, `mix.wav` and `video.mp4` too, and says so.
61
+
62
+ ## Laying it under a video
63
+
64
+ Give `--video` the video the track is for, and musicfit also writes `video.mp4`: that video's picture, copied as it is and never encoded again, with the fitted track under it at the loudness target. The video has to be `--length` long to a frame, counted from its first frame, and without `--fps` the frames count at its own frame rate.
65
+
66
+ `--video-sound` says what becomes of the video's own sound:
67
+
68
+ - `keep`, for sound effects. Each place where it plays is set 3 dB louder than the music there, and no quieter than the loudness target, so a place over quiet music or none plays at the target. It goes no louder than keeps its true peaks, with the music's, under -1 dBTP, so the limiter takes nothing off the music. Where that leaves a place short of 3 dB over the music, as a sharp sound or one on a loud drop can be, the music dips under that place alone by what is missing, `--duck` (10 dB) at most. A note names a place that stays short even so.
69
+ - `voice`, for speech. It is set to the loudness target where it speaks, and the music dips `--duck` (10 dB) under it, in `fitted.wav` and `mix.wav` too, as under `--voice`.
70
+ - `drop`, for music of its own. It is left out.
71
+
72
+ With `keep` the music plays as in `fitted.wav` away from the places it dips under, and as it dips, `video.mp4` can come out under the target, a little at the default and more at a loud `--lufs`; a note says so when it is more than half a LU short. In a 29 s launch film whose own sound is five sound effects, measured in `video.mp4`, the logo sound on the drop came out 3.4 dB over the music with the music 7.7 dB down under it, another sound 2.9 dB over with the music 5.9 dB down, and the other three 2.5 to 2.9 dB over with the music 1.1 dB down under one of them; the film came out at -14.4 LUFS, and at -12.1 LUFS with `--lufs -11`.
73
+
74
+ With `--voice` as well, the music dips under the voice as it always does and each place is set over that music, so a place the voice speaks over plays at the loudness target, as loud as the voice, and the music needs no further dip there. The voice's peaks hold no place back: where a place meets the voice a limiter holds the two under -1 dBTP, and takes a little off both for those moments, so `video.mp4` reaches the target. With a voice over that same film, the two sounds it speaks over all the way through came out 10.8 and 8.6 dB over the music as it dips, and the film at -14 LUFS.
75
+
76
+ musicfit cannot tell speech from sound effects or from music, so a video with more than a faint room tone of its own is refused until you choose. Of several sound streams it reads only the first, and a note says how many the video has. When the first is silent or nearly so and a later one is not, the run is refused, with `keep` and `voice` too, as that sound would be left out: give a copy with that sound first, or `--video-sound drop` to go on without it. With `drop` no stream is read and no note comes. A voice the video already holds goes with `--video-sound voice`, not as a copy given to `--voice`, which would lay it in twice; when the video itself is the track or the `--voice` file, its sound is laid in once. `--help` says how loud each part is set, and FIT.md how `video.mp4` came out after the AAC encoding. `video.mp4` carries a comment that says musicfit made it, and names no file.
77
+
78
+ A video is refused before any work when it:
79
+
80
+ - cannot be read, or is cut short or damaged
81
+ - has no picture, or only a still one
82
+ - holds a picture an MP4 cannot take without encoding it again (ProRes, DNxHD, VP8): give an H.264 or HEVC copy
83
+ - is not `--length` long, and the message gives the length to use
84
+ - is a `video.mp4` musicfit made, unless `--video-sound drop` leaves out its sound and the fitted track in it
85
+
86
+ ## What a failed run leaves
87
+
88
+ A run makes its files in a hidden `.musicfit-part-` folder, inside the out folder, or next to it when the out folder is not there yet, and puts them in place only once it has ended well. A run that fails, is refused or is stopped with Ctrl-C, Ctrl-\ or a signal (SIGTERM, SIGHUP, SIGUSR2, SIGALRM or SIGXCPU) leaves the out folder as it was: the earlier run's files stay and nothing is added. As the earlier files stay until the new ones are in place, a run needs room on the disk for both.
89
+
90
+ A run that ends well replaces the earlier run's files and removes those it does not make again, such as the `mix.wav` of a fit with a voice, and a note says which it removed. Two runs that end at the same moment in one folder take turns, and a run that starts while another moves its files in waits for it.
91
+
92
+ The earlier run's files are the ones its `cues.json` lists, while they are as it wrote them: `cues.json` records the size and the SHA-256 of each file it lists, and its own SHA-256, so a file changed since, `cues.json` included, is no longer the earlier run's. A folder musicfit 0.1 wrote has no such record; there a file its `cues.json` lists counts as musicfit's while it looks as 0.1 left it (`fitted.wav` and `mix.wav` as long as the fit, `seams.png` and `FIT.md` as musicfit makes them) and was not changed more than a minute before or after `cues.json`.
93
+
94
+ Any other file under one of musicfit's names (`fitted.wav`, `mix.wav`, `video.mp4`, `cues.json`, `seams.png`, `FIT.md`) is taken for yours: a run that would write over it is refused before any work, and one that would not leaves it alone. A run whose track, voice or video is one of the files it would replace or remove is refused before any work too.
95
+
96
+ A run killed outright (`kill -9`, or the system when memory runs out) cannot tidy up, and its hidden folder stays. A later run that ends well in the same place removes it once the killed run is gone and nothing in it has changed for 15 minutes; one it may not look into stays, and does not fail that run. A run killed in the moment it moved its files in also leaves some of its new files in the out folder, the earlier ones in its hidden folder and a `.musicfit-commit` file: the next run in that folder puts the earlier files back before anything else, and no other run removes that hidden folder. A failed or stopped run that cannot remove its own hidden folder names it in its message, and that folder goes the same way.
97
+
98
+ When another program holds a file open so that it cannot be moved, the run says so, takes its new files out again and puts the earlier ones back. An earlier file it cannot put back waits in the folder the message names, with a note of where each file belongs; no later run removes that folder.
99
+
49
100
  ## Use it with your agent
50
101
 
51
102
  musicfit ships as an [Agent Skill](https://agentskills.io). Install it once and your agent runs it by itself when a video needs music that fits:
@@ -82,20 +133,42 @@ npx musicfit@latest <track> --length <time> [options]
82
133
  --length <time> how long the result is, e.g. 32, 32s, 960f or 1:05
83
134
  --hit <time> where the drop lands in the result
84
135
  --drop <time> where the drop is in the track (default: found)
85
- --fps <n> frame rate for frame numbers, e.g. 30, 25, 29.97 (default: 30)
136
+ --fps <n> frame rate for frame numbers, e.g. 30, 25, 29.97
137
+ (default: the video's with --video, else 30)
86
138
  --voice <file> a voice track on the video's timeline; the music dips under it
87
- --duck <dB> how far the music dips under the voice (default: 10)
139
+ --duck <dB> how far the music dips under the voice (default: 10), and
140
+ the most it dips under the video's own sound with keep
141
+ --video <file> a video to lay the result under: also writes video.mp4,
142
+ the video's picture as it was with this sound under it.
143
+ The video has to be --length long, to a frame
144
+ --video-sound <how>
145
+ what becomes of the video's own sound: keep (sound
146
+ effects: each place it plays set 3 dB louder than the
147
+ music there, between --lufs and 6 dB over it, and no
148
+ louder than keeps its true peaks, with the music's,
149
+ under -1 dBTP; where that leaves it less than 3 dB over
150
+ the music, the music dips under that place alone by as
151
+ much, --duck at most. With --voice the places are set
152
+ over the music as it dips under the voice, and where
153
+ one meets the voice a limiter holds the two under
154
+ -1 dBTP), voice (speech: set to --lufs where it plays,
155
+ and the music dips --duck under it) or drop (music of
156
+ its own: left out). A faint room tone, or a voice with
157
+ no speech found, is only turned down to --lufs if it is
158
+ louder. Without it, a video with more than a faint room
159
+ tone of its own is refused until you choose
88
160
  --lufs <n> loudness target in LUFS (default: -14)
89
161
  --end <kind> auto, natural (the track's own ending) or fade (default: auto)
90
162
  --fade <s> length of the closing fade, in whole beats (default: a bar)
91
163
  --bpm <n> the tempo, when the one found is half or double the real one
92
164
  --meter <n> beats to a bar, 3 or 4 (default: found)
93
- --out <dir> where to save (default: ./musicfit-<track name>)
165
+ --out <dir> where to save (default: ./musicfit-<track name>, or
166
+ ./musicfit-<track name>.analysis with --analyze)
94
167
  --analyze only read the track: its beats, bars, drop and ending
95
168
  --json print cues.json instead of a summary
96
169
  ```
97
170
 
98
- Times are seconds unless they end in `f`, which counts frames at `--fps`. The track can be any file ffmpeg reads, the sound of a video included.
171
+ Times are seconds unless they end in `f`, which counts frames at `--fps`, or with `--video` and no `--fps` at the video's frame rate. The track can be any file ffmpeg reads, the sound of a video included. musicfit reads files, not addresses: a track, a voice or a video given as `https://...` is refused, so download it first.
99
172
 
100
173
  Some runs:
101
174
 
@@ -103,12 +176,12 @@ Some runs:
103
176
  npx musicfit@latest track.mp3 --length 15
104
177
  npx musicfit@latest track.mp3 --length 900f --hit 450f --fps 30 --out public/music
105
178
  npx musicfit@latest track.mp3 --length 1:30 --hit 41 --voice vo.wav --lufs -16
179
+ npx musicfit@latest track.mp3 --length 733f --hit 600f --video film.mp4 --video-sound keep
180
+ npx musicfit@latest track.mp3 --length 32 --video talk.mp4 --video-sound voice
106
181
  npx musicfit@latest track.mp3 --analyze
107
182
  ```
108
183
 
109
- Installed globally (`npm install -g musicfit`), the command is `musicfit`. npx keeps the copy an earlier run fetched, so `npx musicfit@latest` is how you get a newer version.
110
-
111
- It needs Node 22 or newer and ffmpeg (`brew install ffmpeg`, `sudo apt install ffmpeg` or `winget install ffmpeg`). Set `FFMPEG_PATH` to pick the ffmpeg. There are no npm dependencies; the beat tracking is plain JavaScript and nothing leaves your machine.
184
+ Installed globally (`npm install -g musicfit`), the command is `musicfit`. npx keeps the copy an earlier run fetched, so `npx musicfit@latest` is how you get a newer version. What a run needs is under [Requirements](#requirements).
112
185
 
113
186
  ## How it works
114
187
 
@@ -118,7 +191,7 @@ It needs Node 22 or newer and ffmpeg (`brew install ffmpeg`, `sudo apt install f
118
191
  4. Finds the bar lines from where the kicks, the chord changes and the changes in loudness fall, and whether a bar has three beats or four.
119
192
  5. Describes every bar by its harmony, timbre, loudness and rhythm, finds where sections change, the drop (the first time the track reaches its fullest after a quieter stretch, on a phrase line) and how the track ends.
120
193
  6. Lays the result out as slots one bar long, lined up so a slot starts exactly on the hit, and fills them with bars of the track by dynamic programming: playing on costs nothing, a seam costs more the less its two sides sound like bars that follow each other in the track, and more again when it breaks a four-bar phrase or leaves a build before its drop.
121
- 7. Joins the bars with short crossfades, lines up the hits at every seam, fades the head and the tail, ducks the voice, sets the loudness, and writes the files.
194
+ 7. Joins the bars with short crossfades, lines up the hits at every seam, fades the head and the tail, ducks the voice, sets the loudness, and writes the files. With `--video` it reads the video's packets first, to know its length and frame rate and to refuse what it cannot take, and at the end puts the new sound under its picture, which it copies as it is.
122
195
 
123
196
  ## Limits
124
197
 
@@ -130,6 +203,9 @@ It needs Node 22 or newer and ffmpeg (`brew install ffmpeg`, `sudo apt install f
130
203
  - A tempo that changes a lot within the track, or a bar of 2/4 in a 4/4 song, can put a seam a beat off.
131
204
  - Seams follow bars and sound, not words: in a song with singing a cut can fall in the middle of a line. An instrumental fits best.
132
205
  - Lengthening loops bars the track already has, so a 30 second track stretched to 3 minutes repeats, and so does the intro when the drop is asked for much later than the track has it (musicfit says how many times). Ask the music model for a little more than you need.
206
+ - `--video` copies the picture as it is, so a picture an MP4 cannot hold that way (ProRes, DNxHD, VP8) is refused rather than encoded again. Only the picture and the new sound go into `video.mp4`: subtitles and other streams of the video are left out, a note says so of subtitles, and of several sound streams only the first is read.
207
+ - A fit holds its sound in memory a few times over, so memory grows with `--length` and with the length of the track itself: a 10 minute fit of a 10 minute track took 0.9 to 1 GB, 1.2 GB with `--video` and its own sound kept, 1.4 to 1.5 GB with `--voice` and 1.7 GB with a voice and the video's own sound as well, on a desktop with ffmpeg 8 and in a 2 GB container with ffmpeg 4.4. A voice and the video's own sound are read to their end before they are cut to the fit, so memory grows with their length too: a 20 second fit took 0.2 GB with a voice as long as itself, 0.8 GB with one that ran on 10 minutes past it, and 0.6 GB with a video whose own sound ran on 10 minutes past its picture, on the desktop and in the container alike. A run takes a few seconds, and with `--video` of a long film a minute or more, most of it spent encoding the sound as AAC.
208
+ - musicfit cannot tell speech from sound effects or from music, which is why it asks for `--video-sound`. With `voice` the music dips under anything in that sound that stands out, a cough or a door as much as a word, and a hiss or a hum under a quiet voice comes up with it. With `keep` each place is set on its own, so a soft effect and a loud one over the same music come out alike, and a hiss or a hum between them comes up with the places around it.
133
209
  - It does not change the tempo or the pitch, and it does not check whether you may use the track.
134
210
 
135
211
  ## See also
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "musicfit",
3
- "version": "0.1.2",
4
- "description": "Fit a music track to a video cut: exactly the length you need, cut and looped on bar lines, the drop on the second you pick, a fade that ends on a beat, ducked under a voice and at the right loudness.",
3
+ "version": "0.2.1",
4
+ "description": "Fit a music track to a video cut: exactly the length you need, cut and looped on bar lines, the drop on the second you pick, a fade that ends on a beat, ducked under a voice, at the right loudness, and laid under the video when you give it.",
5
5
  "license": "MIT",
6
6
  "author": "Emin Budak",
7
7
  "homepage": "https://github.com/EminBudak/musicfit#readme",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: musicfit
3
- description: Fit a music track to a video before you put it under the video, such as a launch film, a product demo, an ad, a reel or a Remotion or HyperFrames composition. Makes the track exactly as long as the cut by cutting or looping whole bars, lands its drop on a chosen second, ends it with a fade that resolves on a beat, ducks it under a voice-over and sets its loudness, and writes every beat and bar as frame numbers. Use it whenever a track (from a music model, a library or the user) has to fit a video's length, or the cuts should land on the beat.
3
+ description: Fit a music track to a video and lay it under the video, such as a launch film, a product demo, an ad, a reel or a Remotion or HyperFrames composition. Makes the track exactly as long as the cut by cutting or looping whole bars, lands its drop on a chosen second, ends it with a fade that resolves on a beat, ducks it under a voice-over, sets its loudness, writes every beat and bar as frame numbers, and with the video given writes the video with the track under it. Use it whenever a track (from a music model, a library or the user) has to fit a video's length, has to go under a finished video, or the cuts should land on the beat.
4
4
  ---
5
5
 
6
6
  # musicfit
@@ -13,19 +13,33 @@ Music models ignore the length you ask for, and a track cut at the video's last
13
13
  node <skill>/scripts/musicfit.js track.mp3 --length 32 --hit 27.5 --fps 30 --out audio/fit
14
14
  ```
15
15
 
16
- `<skill>` is this skill's folder. The script needs Node 22 or newer and ffmpeg on the machine; it installs nothing and needs no API key. A run takes a few seconds.
16
+ `<skill>` is this skill's folder. The script needs Node 22 or newer and ffmpeg 4.4 or newer on the machine; it installs nothing and needs no API key. A run takes a few seconds, and with a long `--video` a minute or more. A run needs memory, more the longer the fit and the track, and the longer a voice or the video's own sound, which are read to their end even where they run on past the fit: a 10 minute fit of a 10 minute track took 0.9 to 1 GB, 1.2 GB with `--video`, 1.4 to 1.5 GB with `--voice` and 1.7 GB with a voice and the video's own sound, and a 20 second fit took 0.8 GB with a voice that ran on 10 minutes past it.
17
17
 
18
18
  - `--length` is the video's length: seconds (`32`), frames (`960f`, at `--fps`) or minutes (`1:05`).
19
- - `--hit` is the second the drop should land on, for example the logo reveal. Leave it out and the track starts as it starts and ends on its own ending.
19
+ - `--hit` is the second the drop should land on, for example the logo reveal. Leave it out and the track keeps its own ending; a short fit can then start later in the track, so its drop may not play (FIT.md says where it starts). Give `--hit` when the drop has to be heard.
20
20
  - `--drop 41.3` says where the drop is in the track when the one found is not the one you want. `--analyze` lists the candidates first.
21
21
  - `--voice vo.wav` ducks the music under a voice track laid out on the video's timeline and also writes `mix.wav`.
22
+ - `--video film.mp4` also writes `video.mp4`, the video with the fitted track under it: see the next section.
22
23
  - `--fps` should match the composition, so the frame numbers in `cues.json` can be used as they are.
24
+ - `--analyze` saves in `musicfit-<track name>.analysis` by default, never in a fit's folder unless you give both the same `--out`. An analysis given a fit's `--out` replaces that fit when it ends well: `fitted.wav`, `mix.wav` and `video.mp4` are removed. Give `--analyze` its own folder, or leave `--out` out.
25
+
26
+ ## Lay it under a video
27
+
28
+ `--video film.mp4` also writes `video.mp4`: the video's picture as it was, copied and not encoded again, with the fitted track under it at the loudness target. The video has to be `--length` long to a frame; without `--fps` the frames count at its frame rate.
29
+
30
+ A video with more than a faint room tone of its own, a single click included, is refused until `--video-sound` says what becomes of that sound. When you do not know what it is, ask the user:
31
+
32
+ - `keep` for sound effects: each place where it plays is set 3 dB louder than the music there, and no quieter than the loudness target, so a place over quiet music or none plays at the target; it goes no louder than keeps its true peaks, with the music's, under -1 dBTP, so the limiter takes nothing off the music. Where that leaves a place less than 3 dB over the music, as a sharp sound or one on a loud drop can be, the music dips under that place alone by as much as is missing, `--duck` (10 dB) at most, and plays as in `fitted.wav` elsewhere; a note names a place that stays short even so. `video.mp4` can then come out under the target, more at a loud `--lufs`; a note says so when it is more than half a LU short. In a launch film whose own sound was five effects, the logo sound on the drop came out 3.4 dB over the music with the music 7.7 dB down under it, and the film at -14.4 LUFS. With `--voice` as well, the music dips under the voice as it always does and each place is set over that music, so one the voice speaks over plays at the target, as loud as the voice, with no further dip; where a place meets the voice a limiter holds the two under -1 dBTP and `video.mp4` reaches the target.
33
+ - `voice` for speech: set to the loudness target where it speaks, and the music dips `--duck` (10 dB) under it, in `fitted.wav` and `mix.wav` too. With no speech found in it, it is only turned down if it is louder than the target, and a note says so.
34
+ - `drop` for music of its own: left out.
35
+
36
+ A voice the video already holds goes with `--video-sound voice`, not as a copy given to `--voice`, which would lay it in twice. For a launch film from launch-film that should carry your own track, make or plan the film with `--no-music`, or set `"music": false` in its film.json and render it again, so its sound is its effects only; then give its film.mp4 here as `--video` with `--video-sound keep`.
23
37
 
24
38
  ## Use the result
25
39
 
26
40
  1. Read `audio/fit/FIT.md` first. It says what was cut or looped and lists the moments a person should listen to, with their frames.
27
41
  2. Show `seams.png` to the user: the track on top, the fitted result below, every seam and the hit marked.
28
- 3. Put `fitted.wav` (or `mix.wav` with a voice) under the whole video, starting at frame 0. Do not trim or stretch it: it is already exactly `--length`.
42
+ 3. With `--video`, hand over `video.mp4`: that is the video with the track under it. Without it, put `fitted.wav` (or `mix.wav` with a voice) under the whole video, starting at frame 0, and do not trim or stretch it: it is already exactly `--length`. Never say the track is in the video before a file with it exists.
29
43
  4. Time the edit to the music with `cues.json`: `beats` and `bars` are frame numbers at `--fps`, `hits` the frames of the drop and of the track's own last hit, `sections` where the music changes, and `times` has the same moments in seconds. In Remotion that is `<Sequence from={cues.bars[8]}>`; cut scenes on bar frames, small moves on beats.
30
44
  5. Tell the user to listen at the seams and the hit before the film goes out. `FIT.md` lists them with their frames.
31
45
 
@@ -37,4 +51,13 @@ node <skill>/scripts/musicfit.js track.mp3 --length 32 --hit 27.5 --fps 30 --out
37
51
  - A note that some bars play several times means `--hit` is much later than the track's own drop, so its intro repeats. Suggest an earlier `--hit`, or `--drop` with a later candidate.
38
52
  - With no steady beat, `beats` and `bars` in `cues.json` are empty lists. Time the edit by `hits` and `seams` instead.
39
53
  - The track can come from any music model. Ask for an instrumental a little longer than the video: musicfit shortens more gracefully than it lengthens, and its seams follow bars, not sung words.
54
+ - `--video` refuses before any work a video it cannot read, one that is cut short or damaged, one with no picture, one whose picture an MP4 cannot hold without encoding it again (ProRes, DNxHD, VP8: ask for an H.264 or HEVC copy), one that is not `--length` long (the message gives the length to use), and a video musicfit made unless `--video-sound drop` leaves its sound out (else give the video it was made from, which the message names when the video's `cues.json` is beside it).
55
+ - A video with sound of its own is refused until `--video-sound` is given: ask the user whether that sound is sound effects to keep (`keep`), speech (`voice`) or music of its own (`drop`). Read the notes in the summary too: one says when the video's own sound plays for most of its length, and with `keep` one names each place where that sound stays less than 3 dB louder than the music even with the music dipped `--duck` under it, as a sharp sound over the loudest part of the track can. Tell the user; a larger `--duck` lets the music dip further there, and deepens its dip under the `--voice` too when there is one, so no note suggests it then.
56
+ - musicfit reads only a video's first sound stream, and a note says when it has more. When the first is silent or nearly so and a later one is not, the run is refused, with `keep` and `voice` too, as that sound would be left out: ask the user for a copy with that sound first, or give `--video-sound drop` to go on without it. With `drop` no stream is read and no note comes.
57
+ - A note says when a video has subtitle streams: they are not in `video.mp4`, so tell the user.
58
+ - A note that `video.mp4` is short of the loudness target, or over -1 dBTP after the AAC encoding, says how it came out; FIT.md says the same. Tell the user.
59
+ - A run is refused before any work when its out folder holds, under a name the run writes, a file that is not as an earlier musicfit run wrote it there, such as the user's own `video.mp4` beside a copied `cues.json` for a run with `--video`, or a `FIT.md` or `cues.json` the user changed (move it, with the files it lists for a `cues.json`, or give another `--out`); a run that does not write that name leaves the file alone. It is refused too when the track, the voice or the video is one of the files the run would replace or remove.
60
+ - A run that fails or is stopped (Ctrl-C, or a signal such as SIGTERM) leaves its folder as it was, so a fit that worked is never lost to one that did not. As the earlier files stay until the new ones are in place, a run needs room on the disk for both, and a full disk says so. A run that ends well removes the earlier files it does not make again, and a note names them: tell the user when one was a fit's `video.mp4` or `fitted.wav`. A run killed outright after it began writing can leave a hidden `.musicfit-part-` folder, inside the out folder or next to it when the out folder was not there yet, which a later run that ends well in the same place removes once the killed run's process is gone and 15 minutes have passed (one it may not look into stays, and does not fail that run); a run killed in the moment it moved its files in is undone by the next run there, which puts the earlier files back first, and no other run removes its hidden folder. When a failed or stopped run could not remove its hidden folder, its message names it, and a later run removes it the same way. When another program held a file open so that a run could not put every earlier file back, the message names the folder they are in, and its `musicfit-kept.txt` says where each one belongs: tell the user.
61
+ - musicfit reads files: a track, a voice or a video given as an address is refused. Download it first.
62
+ - "musicfit needs ffmpeg" means the run stopped before any work, as it found no ffmpeg, or the one it found or `FFMPEG_PATH` names could not be started or did not answer as ffmpeg when asked its version. The message names that file and gives what the system said, or the first line the program printed and how it ended: tell the user, and ask them to install ffmpeg or to set `FFMPEG_PATH` to one that runs. "musicfit could not start ffmpeg" means the system could not start a program then, as when too many files are open, and "musicfit could not check ffmpeg" that ffmpeg gave no answer in two minutes when asked its version, as on a machine short of memory; the message says what to do.
40
63
  - Never say a track is cleared for use; musicfit does not check rights.
@@ -358,8 +358,11 @@ function estimateTempo(feat, from, to, { bpm: forced } = {}) {
358
358
  const value = comb(acf, bpm);
359
359
  if (value > best.value) best = { bpm, value };
360
360
  }
361
- // Refine to a hundredth of a beat per minute.
362
- for (let bpm = best.bpm - 0.1; bpm <= best.bpm + 0.1; bpm += 0.01) {
361
+ // Refine to a hundredth of a beat per minute, around where the search
362
+ // found it: where the curve has no peak, as a steady tone has none, a
363
+ // bound that moved with the best would climb without end.
364
+ const around = best.bpm;
365
+ for (let bpm = around - 0.1; bpm <= around + 0.1; bpm += 0.01) {
363
366
  const value = comb(acf, bpm);
364
367
  if (value > best.value) best = { bpm, value };
365
368
  }
@@ -931,4 +934,4 @@ function finishEnergy(result, feat, chroma, audible, options) {
931
934
  return result;
932
935
  }
933
936
 
934
- module.exports = { analyze, RATE };
937
+ module.exports = { analyze, RATE, barDistance };