script.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. #!/usr/bin/env python
  2. import argparse
  3. import errno
  4. from math import floor
  5. import ffmpeg
  6. import sys
  7. WAVES_BACKGROUND_IMAGE_RATIO = 0.2
  8. WAVES_IMAGE_RATIO = 0.15
  9. def main():
  10. parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, add_help=True)
  11. parser.add_argument("--audio",
  12. help="input audio filename", required=True)
  13. parser.add_argument("--background",
  14. help="visualization background filename", required=True)
  15. parser.add_argument("--output",
  16. help="output video filename", required=True)
  17. parser.add_argument("--vis-color", nargs='*',
  18. help="colors for visualization waveforms", required=False, default=["0xffffff"])
  19. parser.add_argument("--background-color",
  20. help="backgroundcolor for visualization waveforms", required=False, default="0x000000")
  21. args, _ = parser.parse_known_args()
  22. duration = get_audio_duration(args.audio)
  23. (bg_height, bg_width) = get_image_resolution(args.background)
  24. waves_height = floor(bg_height * WAVES_IMAGE_RATIO)
  25. waves_background_height = floor(bg_height * WAVES_IMAGE_RATIO)
  26. # Compile the waves and a background color
  27. stream = ffmpeg.input(args.audio)
  28. vis_colors = "|".join(args.vis_color)
  29. vid_stream = (
  30. stream
  31. .filter("showwaves", s="%dx%d" % (bg_width, waves_height), mode="cline", colors=vis_colors)
  32. .filter("format", "rgba")
  33. .filter("colorchannelmixer", aa=0.9)
  34. )
  35. background_stream = (
  36. ffmpeg.input("color=c=%s:s=%dx%d:d=%ss" % (args.background_color, bg_width, waves_background_height, duration), f="lavfi")
  37. .filter("format", "rgba")
  38. .filter("colorchannelmixer", aa=0.5)
  39. )
  40. waves_center_offset = floor((waves_background_height - waves_height)/2)
  41. viz = ffmpeg.filter([background_stream, vid_stream], 'overlay', 0, waves_center_offset)
  42. waves_background_center_offset = floor((bg_height - waves_background_height)/2)
  43. # Overlay the waves stream on top of our static image
  44. vid_stream = ffmpeg.filter([ffmpeg.input(args.background), viz], 'overlay', 0, waves_background_center_offset)
  45. ffmpeg.output(stream.audio, vid_stream, args.output).run()
  46. # Get image resolution using ffprobe
  47. def get_image_resolution(image_filename):
  48. metadata = get_metadata(image_filename)
  49. height = metadata["streams"][0]["height"]
  50. width = metadata["streams"][0]["width"]
  51. return (height, width)
  52. # Get audio duration using ffprobe
  53. def get_audio_duration(audio_filename):
  54. metadata = get_metadata(audio_filename)
  55. return metadata["format"]["duration"]
  56. def get_metadata(filename):
  57. metadata = ffmpeg.probe(filename)
  58. return metadata
  59. if __name__ == "__main__":
  60. try:
  61. main()
  62. except KeyboardInterrupt:
  63. # The user asked the program to exit
  64. sys.exit(1)
  65. except IOError as e:
  66. # When this program is used in a shell pipeline and an earlier program in
  67. # the pipeline is terminated, we'll receive an EPIPE error. This is normal
  68. # and just an indication that we should exit after processing whatever
  69. # input we've received -- we don't consume standard input so we can just
  70. # exit cleanly in that case.
  71. if e.errno != errno.EPIPE:
  72. raise
  73. # We still exit with a non-zero exit code though in order to propagate the
  74. # error code of the earlier process that was terminated.
  75. sys.exit(1)
  76. sys.exit(0)