Skip to content

Instantly share code, notes, and snippets.

@sunny
Last active January 28, 2020 14:33
Show Gist options
  • Save sunny/0364252c256a4043bfdc97b68d78d235 to your computer and use it in GitHub Desktop.
Save sunny/0364252c256a4043bfdc97b68d78d235 to your computer and use it in GitHub Desktop.
Cut wavs using ffmpeg
#!/usr/bin/env ruby
def ffprobe_duration(wav)
call(
"ffprobe " \
"'#{wav}' " \
"-show_entries format=duration " \
"-of compact=p=0:nk=1 " \
"-v 0"
)
end
def ffmpeg_silences(wav)
call(
"ffmpeg " \
"-i '#{wav}' " \
"-filter_complex '[0:a]silencedetect=n=-70dB:d=1.5[outa]' " \
"-map '[outa]' " \
"-f s16le " \
"-y /dev/null " \
"2>&1"
)
end
def ffmpeg_cut(wav, new_wav, from, to)
call(
"ffmpeg " \
"-i '#{wav}' " \
"-ss #{from} " \
"-t #{to} " \
"-aq 70 " \
"-y '#{new_wav}' " \
"-v 0 " \
"2>&1"
)
end
def call(command)
# puts "------> #{command}"
`#{command}`
end
# Récupère les arguments passés.
wav = ARGV[0]
wav_dir = ARGV[1] || abort("Usage: #{$0} file dir [start_at] [end_cut]")
start_at = ARGV[2].to_f
end_cut = ARGV[3].to_f
# Appelle `ffprobe` pour avoir le temps total, transformé en chiffre décimal.
duration = ffprobe_duration(wav).to_f
# En soustrayant le temps total aux secondes de fin ça donne le temps à ne pas
# dépasser.
end_at = duration - end_cut
# Appelle ffmpeg en lui passant le nom du fichier, sépare en lignes (`.lines`),
# puis récupère une valeur pour chaque ligne (`.map`).
timestamps = ffmpeg_silences(wav).lines.map do |line|
# Affiche la ligne en cours, avec son retour à la ligne.
# print line
# Si la ligne correspond à "silence_start: …" ou "silence_end: …",
# retourne la valeur trouvée, transformée en chiffre décimal (`.to_f`).
$2.to_f if line =~ /silence_(start|end): (\S+)/
# Sinon, retourne `nil`.
# Retire chaque valeur `nil` qui reste (`.compact`),
# ce qui correspond à des lignes qui ne contiennent pas de valeur de silence.
end.compact
# On ajoute au à la liste des temps le démarrage et la fin afin d'avoir les
# temps qui ne sont PAS des silences quand on les regroupera par paires.
timestamps = [0] + timestamps + [duration]
# Maintenant que `timestamps` contient une liste de chiffres, comme
# `[0, 2.23, 4.555, 12.33, …]`, on les groupe par deux (`each_slice`),
# ce qui donne un début et une fin comme `[[0, 2.23], [4.55, 12.33], …]`.
ranges = timestamps.each_slice(2)
# On retire (`.reject`) les temps qui ont un temps en dehors du début et de
# la fin souhaitée. On ne garde pas non plus les temps qui sont vides, au cas
# où (par exemple `[0, 0]`).
ranges = ranges.reject do |from, to|
to < start_at || end_at < from || to == from
end
# On transforme chaque paire pour s'assurer que chaque temps reste dans les
# bornes. Par exemple si on a gardé `[2.3, 5.0]` mais qu'on démarre à `2.9`,
# on va retourner `[2.9, 5.0]`.
ranges = ranges.map do |from, to|
[[from, start_at].max, [to, end_at].min]
end
# On boucle sur chaque paire de temps en récupérant son index.
ranges.each_with_index do |(from, to), index|
filename = "#{wav_dir}/#{index}.wav"
puts "#{filename}: #{from}-#{to}"
# Découpe un nouveau fichier en indicant son nom et en passant les timestamps.
ffmpeg_cut(wav, filename, from, to)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment