Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Remove Silence from Final Cut Pro clips, automatically, using ffmpeg timecodes and OSX JavaScript Automation - Demo: https://imgur.com/a/Zisav
#!/usr/bin/env osascript -l JavaScript
/**
* Delete silence from Final Cut Pro timeline using a script.
* Demo: https://imgur.com/a/Zisav
*
* This script accepts an ffmpeg silencedetect log as input.
*
* To setup, have fcp running along with your clip selected. Ensure that the
* timecode will start at zero before running this script. That is, if your clip
* is in the middle of your project, create a compound clip first and then enter
* into that clip before running this script.
*
* To generate silence.txt:
* /usr/local/bin/ffmpeg -i $1 -af silencedetect=n=-50dB:d=1 -f s16le -y /dev/null 2>&1 | tee silence.txt
*
* Currently it adds a little margin of silence at the end of each noised clip. You can adjust this below.
* If you adjust the silencedetection dB, then this number may need tweaking.
*
* For example, if your dB filter is higher, you'll filter out more background
* noise, but you might clip the audio you want. Extending the margins can help
* prevent clipping a natural trailing off of the sound you want.
*
* Nate Murray 2017 <nate@natemurray.com>
**/
function run(argv) {
ObjC.import("stdlib");
ObjC.import("Foundation");
console.log(JSON.stringify(argv));
console.log("Start Final Cut Pro and click the matching clip");
// Adjust these to your liking
// One way to test, is comment out the delete section below and visually inspect
const startMargin = 0.100;
const endMargin = 0.000;
if (!Application("Final Cut Pro").running()) {
console.log("Final Cut Pro isn't running");
$.exit(1);
}
// let app = Application("Finder");
let app = Application.currentApplication();
app.includeStandardAdditions = true;
let se = Application("System Events");
function loadFile(path) {
let fm = $.NSFileManager.defaultManager;
let contents = fm.contentsAtPath(path.toString()); // NSData
contents = $.NSString.alloc.initWithDataEncoding(
contents,
$.NSUTF8StringEncoding
);
return ObjC.unwrap(contents);
}
function parseSilenceFile(contents) {
let lines = contents.split("\n");
let silences = [];
let pair = {};
for (let i = 0; i < lines.length; i++) {
let l = lines[i];
// [silencedetect @ 0x7fd895407da0] silence_start: 272.972
// [silencedetect @ 0x7fd895407da0] silence_end: 274.762 | silence_duration: 1.78948
let startReg = /silence_start: (\d+.?\d+)\b/;
let endReg = /silence_end: (\d+.?\d+)\b/;
let startMatch = startReg.exec(l);
let endMatch = endReg.exec(l);
if (startMatch && startMatch.length > 0) {
pair["start"] = startMatch[1];
pair["end"] = null;
}
if (endMatch && endMatch.length > 0) {
pair["end"] = endMatch[1];
if (pair["start"]) {
(pair => silences.push(pair))(pair);
pair = {};
}
}
}
return silences;
}
// parse the silence points
let path = argv[0];
let rawSilenceFileContents = loadFile(path);
let silencePoints = parseSilenceFile(rawSilenceFileContents);
console.log(JSON.stringify(silencePoints));
// activate fcp
let fcp = Application("Final Cut Pro");
fcp.activate();
delay(1.0);
function moveToTimecode(timeInSeconds) {
delay(0.2);
// convert timeInSeconds in decimal to timecode values.
let [seconds, deciseconds] = timeInSeconds.split(".");
let d = new Date(null);
d.setSeconds(parseInt(seconds));
let hourmindays = d.toISOString().substr(11, 8);
let decipart = (60 * parseFloat("0." + deciseconds))
.toString()
.split(".")[0]
.substr(0, 2);
if (decipart.length < 2) {
decipart = "0" + decipart;
}
let timecodekeystroke = (hourmindays + decipart).replace(/:/g, "");
console.log(
" timecodekeystroke ",
timeInSeconds,
hourmindays,
decipart,
timecodekeystroke
);
se.keystroke("p", { using: "control down" });
se.keystroke(timecodekeystroke);
se.keyCode(36); // Press Enter
}
function blade() {
se.keystroke("b", { using: "command down" });
}
// extend edges
silencePoints = silencePoints.map(sp => ({
start: (parseFloat(sp.start) + startMargin).toString(),
end: (parseFloat(sp.end) - endMargin).toString()
}));
for (let i = 0; i < silencePoints.length; i++) {
let sp = silencePoints[i];
console.log(i, JSON.stringify(sp));
delay(0.05);
moveToTimecode(sp.start);
delay(0.05);
blade();
delay(0.05);
moveToTimecode(sp.end);
delay(0.05);
blade();
}
console.log("Deleting Silence");
// Go backwards, becase we're changing the total time as we go
for (let i = silencePoints.length - 1; i > 0; i--) {
let sp = silencePoints[i];
console.log("D", i, JSON.stringify(sp));
moveToTimecode(sp.start);
delay(0.1);
// select current clip
se.keystroke("c");
delay(0.1);
// delete the silence
se.keyCode(51); // Press Delete
delay(0.3);
}
}
@shervinshaikh

This comment has been minimized.

Copy link

@shervinshaikh shervinshaikh commented Jan 5, 2020

When I run the script, I get nothing in the array for console.log(JSON.stringify(silencePoints));
I tried adjusting the DB, with no luck. What do you think I'm doing wrong?

$ /usr/local/bin/ffmpeg -i $1 -af silencedetect=n=-50dB:d=1 -f s16le -y /dev/null 2>&1 | tee silence.txt
ffmpeg version 4.2.2 Copyright (c) 2000-2019 the FFmpeg developers
built with Apple clang version 11.0.0 (clang-1100.0.33.16)
configuration: --prefix=/usr/local/Cellar/ffmpeg/4.2.2 --enable-shared --enable-pthreads --enable-version3 --enable-avresample --cc=clang --host-cflags='-I/Library/Java/JavaVirtualMachines/adoptopenjdk-13.0.1.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/adoptopenjdk-13.0.1.jdk/Contents/Home/include/darwin -fno-stack-check' --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libbluray --enable-libmp3lame --enable-libopus --enable-librubberband --enable-libsnappy --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librtmp --enable-libspeex --enable-libsoxr --enable-videotoolbox --disable-libjack --disable-indev=jack
libavutil 56. 31.100 / 56. 31.100
libavcodec 58. 54.100 / 58. 54.100
libavformat 58. 29.100 / 58. 29.100
libavdevice 58. 8.100 / 58. 8.100
libavfilter 7. 57.100 / 7. 57.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100
-af: No such file or directory

$ ./final-cut-it-out.js silence.txt
["silence.txt"]
Start Final Cut Pro and click the matching clip
[]
Deleting Silence

@jashmenn

This comment has been minimized.

Copy link
Owner Author

@jashmenn jashmenn commented Jan 8, 2020

@shervinshaikh well, notice that your shell says -af: No such file or directory -- makes me think your ffmpeg isn't parsing the options in the same way as mine. (It happens, ffmpeg is notorious for having incompatible versions.)

I'd try to cat silence.txt and make sure you even have any breakpoints in that file.

@happyharis

This comment has been minimized.

Copy link

@happyharis happyharis commented Feb 12, 2020

What are the options you have parsed? I have the same error.

@happyharis

This comment has been minimized.

Copy link

@happyharis happyharis commented Feb 12, 2020

I found the solution: replace the $1 in the command /usr/local/bin/ffmpeg -i $1 -af silencedetect=n=-50dB:d=1 -f s16le -y /dev/null 2>&1 | tee silence.txt to your file name you want to edit. Thanks for the script 😁

@EnseignantLapro

This comment has been minimized.

Copy link

@EnseignantLapro EnseignantLapro commented Apr 7, 2020

Hi all, with Catalina 10.15.4, osascript is not allowed to send entries (1002)
execution error: Error: Error: osascript n’est pas autorisé à envoyer de saisies. (1002)

How you fix that ?

EDIT Solve : Add Terminal and apple script in security préférence / accessibility and input

@EnseignantLapro

This comment has been minimized.

Copy link

@EnseignantLapro EnseignantLapro commented Apr 7, 2020

If your video is in 30 fps you have to change this line in this script
let decipart = (60 * parseFloat("0." + deciseconds))
to
let decipart = (30 * parseFloat("0." + deciseconds))
and it work great
thanks jashmenn for you work.

@lfccruz

This comment has been minimized.

Copy link

@lfccruz lfccruz commented Apr 10, 2020

It's not working for me :/

Here's what I am doing:

  • I have imported my .mov video to FCPX at the beginning of my timeline;
  • Then I ran the command to generate the silence.txt file passing my video as parameter (no errors are shown, but not much info as well). Please see my silence.txt below.
  • Then I execute the command node final-cut-it-out.js silence.txt (is this the correct way to execute it?).

Silence.txt file content:
ffmpeg version N-97224-g7104c4dd88-tessus https://evermeet.cx/ffmpeg/ Copyright (c) 2000-2020 the FFmpeg developers
built with Apple clang version 11.0.0 (clang-1100.0.33.17)
configuration: --cc=/usr/bin/clang --prefix=/opt/ffmpeg --extra-version=tessus --enable-avisynth --enable-fontconfig --enable-gpl --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libfreetype --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libmysofa --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopus --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvmaf --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-version3 --pkg-config-flags=--static --disable-ffplay
libavutil 56. 42.102 / 56. 42.102
libavcodec 58. 77.101 / 58. 77.101
libavformat 58. 42.100 / 58. 42.100
libavdevice 58. 9.103 / 58. 9.103
libavfilter 7. 77.101 / 7. 77.101
libswscale 5. 6.101 / 5. 6.101
libswresample 3. 6.100 / 3. 6.100
libpostproc 55. 6.100 / 55. 6.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'IMG_8657.mov':
Metadata:
major_brand : qt
minor_version : 0
compatible_brands: qt
creation_time : 2020-04-10T06:03:44.000000Z
com.apple.quicktime.make: Apple
com.apple.quicktime.model: iPhone XS
com.apple.quicktime.software: 13.2.3
com.apple.quicktime.creationdate: 2020-04-10T06:38:24+0100
Duration: 00:00:17.68, start: 0.000000, bitrate: 15854 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080, 15636 kb/s, 29.97 fps, 29.97 tbr, 600 tbn, 1200 tbc (default)
Metadata:
creation_time : 2020-04-10T06:03:44.000000Z
handler_name : Core Media Video
encoder : H.264
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 187 kb/s (default)
Metadata:
creation_time : 2020-04-10T06:03:44.000000Z
handler_name : Core Media Audio
Stream #0:2(und): Data: none (mebx / 0x7862656D), 23 kb/s (default)
Metadata:
creation_time : 2020-04-10T06:03:44.000000Z
handler_name : Core Media Metadata
Stream #0:3(und): Data: none (mebx / 0x7862656D), 0 kb/s (default)
Metadata:
creation_time : 2020-04-10T06:03:44.000000Z
handler_name : Core Media Metadata
Stream mapping:
Stream #0:1 -> #0:0 (aac (native) -> pcm_s16le (native))
Press [q] to stop, [?] for help
Output #0, s16le, to '/dev/null':
Metadata:
major_brand : qt
minor_version : 0
compatible_brands: qt
com.apple.quicktime.creationdate: 2020-04-10T06:38:24+0100
com.apple.quicktime.make: Apple
com.apple.quicktime.model: iPhone XS
com.apple.quicktime.software: 13.2.3
encoder : Lavf58.42.100
Stream #0:0(und): Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s (default)
Metadata:
creation_time : 2020-04-10T06:03:44.000000Z
handler_name : Core Media Audio
encoder : Lavc58.77.101 pcm_s16le
size= 3048kB time=00:00:17.69 bitrate=1411.2kbits/s speed= 624x
video:0kB audio:3048kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000000%

@JeanSemeur

This comment has been minimized.

Copy link

@JeanSemeur JeanSemeur commented May 4, 2020

If your video is in 30 fps you have to change this line in this script
let decipart = (60 * parseFloat("0." + deciseconds))
to
let decipart = (30 * parseFloat("0." + deciseconds))
and it work great
thanks jashmenn for you work.
Bonjour à vous EnseignantLapro,
Je suis le rédacteur d'un ensemble de documents pour FCP X (martingosset.com) et écris quelques modestes application pour les monteurs FCP X (http://martingosset.com/boomerang-app/). Mais uniquement en Applescript.
J'aimerais vous contacter car je pense que ce petit script en intéresserait plus d'un. Mon idée serait de générer un XML grâce au fichier txt que semble gérer ce script; et plutôt que de faire faire le boulot de coupe et suppression par ce dernier. Je pense que je serais capable de le faire en Applescript que je connais et parce que j'ai bine l'habitude des FCPXML.
C'est sauf que j'ai peur de mal m'y prendre pour installer ce premier script ici présent.
Si vous aviez la gentillesse de m'épauler un peu pour cette partie, je vous offrirais avec grand plaisir mes documents ... en échange et pour ne pas que vous vous sentiez trop exploités ;)
En attendant de vous lire
Martin Gosset

@JeanSemeur

This comment has been minimized.

Copy link

@JeanSemeur JeanSemeur commented May 6, 2020

I finlay choose another way to delimitate those silents parts : with keyword !!!... and wrote a small app in applescript that create an FCPXML... https://vimeo.com/415340397/f28424553d
Thank Jashmaenn
Martin

@hamletcat

This comment has been minimized.

Copy link

@hamletcat hamletcat commented Jul 7, 2020

I finlay choose another way to delimitate those silents parts : with keyword !!!... and wrote a small app in applescript that create an FCPXML... https://vimeo.com/415340397/f28424553d
Thank Jashmaenn
Martin

That looks amazing and is exactly what I need - something to just identify silence in Fcpx. Would you be willing to share your app?

@JeanSemeur

This comment has been minimized.

Copy link

@JeanSemeur JeanSemeur commented Jul 7, 2020

I finlay choose another way to delimitate those silents parts : with keyword !!!... and wrote a small app in applescript that create an FCPXML... https://vimeo.com/415340397/f28424553d
Thank Jashmaenn
Martin

That looks amazing and is exactly what I need - something to just identify silence in Fcpx. Would you be willing to share your app?

Hello hamletcat... this app is now available on my website : (english : http://martingosset.com/boomerang_english/ and french with other app : http://martingosset.com/boomerang-app/)
Thank for your interest...
Martin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.