extract_audio.sf 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #!/usr/bin/ruby
  2. # Daniel "Trizen" Șuteu
  3. # Date: 21 March 2020
  4. # Edit: 24 October 2023
  5. # https://github.com/trizen
  6. # Extract audio from video files with ffmpeg, using `n` parallel processes.
  7. func ffmpeg_convert(input, output) {
  8. Sys.run("ffmpeg", "-loglevel", "warning", "-i", input, "-vn", "-sn", "-dn", "-c:a", "copy", output)
  9. }
  10. func prepare_file(file, outdir, ext) {
  11. var out = ""
  12. if (file =~ /^(.*)\.\w+\z/s) {|m|
  13. out = (m[0] + "." + ext)
  14. }
  15. else {
  16. out = (file + "." + ext)
  17. }
  18. out = File(outdir, File(out).basename)
  19. if (File(out).rel2abs == File(file).rel2abs) {
  20. return nil
  21. }
  22. return out
  23. }
  24. var threads = 2
  25. var format = 'opus'
  26. var outdir = Dir.cwd
  27. var delete_original = false
  28. func print_help(exit_code=0) {
  29. var name = File(__FILE__).basename
  30. print <<"EOT"
  31. usage: #{name} [options] [files]
  32. options:
  33. -f --format=s : output format (default: #{format})
  34. -d --delete! : delete the original files (default: #{delete_original})
  35. -o --outdir=s : output directory (default: .)
  36. -t --threads=i : number of threads to use (default: #{threads})
  37. example:
  38. #{name} -f opus -t 4 *.webm # extract OPUS audio from WEBM videos
  39. EOT
  40. Sys.exit(exit_code)
  41. }
  42. ARGV.getopt!(
  43. 'f|format=s' => \format,
  44. 't|threads=i' => \threads,
  45. 'o|outdir=s' => \outdir,
  46. 'd|delete!' => \delete_original,
  47. 'h|help' => print_help,
  48. )
  49. outdir.exists || outdir.make_path || die "Can't create directory <<#{outdir}>>: #{$!}"
  50. ARGV || print_help(1)
  51. ARGV.each_slice(threads, {|*slice|
  52. var files = slice.grep { File(_).exists }
  53. var conversions = files.map {|file|
  54. [File(file), prepare_file(file, outdir, format)]
  55. }.grep {
  56. defined(.tail)
  57. }.grep {
  58. !.tail.exists
  59. }
  60. conversions || next
  61. conversions.each_2d {|from,to|
  62. say ":: Converting: '#{from}' -> '#{to}'"
  63. }
  64. var forks = conversions.map_2d {|input, output|
  65. ffmpeg_convert.fork(input, output)
  66. }
  67. forks.each {|t|
  68. say ":: Waiting..."
  69. t.wait
  70. }
  71. conversions.each_2d {|input, output|
  72. output.is_file || next
  73. var output_size = output.size
  74. if (delete_original && (output_size > 0)) {
  75. say "Removing: #{input}"
  76. input.delete || warn "failed to remove: #{$!}"
  77. }
  78. elsif (output_size == 0) { # ffmpeg failed
  79. output.delete # remove empty file
  80. }
  81. }
  82. })