No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

render.py 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. #!/usr/bin/env python3
  2. # Render image to Oscilloscope XY vector audio
  3. #
  4. # ----------------------------------------------------------------------------
  5. # Copyright (c) 2024 Thomas Buck (thomas@xythobuz.de)
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # See <http://www.gnu.org/licenses/>.
  18. # ----------------------------------------------------------------------------
  19. import sys
  20. import wave
  21. import pyaudio
  22. from PIL import Image
  23. samplerate = 44100 #192000
  24. default_duration = 5.0
  25. def read_image(image):
  26. print("image: width={} height={} total={}".format(image.width, image.height, image.width * image.height))
  27. # resize coordinates for conversion to amplitude values
  28. max_len = max(image.width, image.height)
  29. fact = 32767 / max_len
  30. sw, sh = int(image.width * fact), int(image.height * fact)
  31. print("amplitude: width={} height={}".format(sw, sh))
  32. data = bytearray()
  33. for x in range(0, image.width):
  34. for y in range(0, image.height):
  35. if image.getpixel((x, y))[3] > 127:
  36. xc, yc = int((x - (image.width / 2)) * fact), int((y - (image.height / 2)) * fact)
  37. data.extend(yc.to_bytes(2, byteorder="little", signed=True))
  38. data.extend(xc.to_bytes(2, byteorder="little", signed=True))
  39. return data
  40. def play_waveform(data):
  41. pa = pyaudio.PyAudio()
  42. # int16
  43. stream = pa.open(format=pa.get_format_from_width(2),
  44. channels=2,
  45. rate=samplerate,
  46. output=True)
  47. stream.write(data, int(len(data) / 4), True)
  48. stream.stop_stream()
  49. stream.close()
  50. pa.terminate()
  51. def write_waveform(data):
  52. with wave.open("out.wav", "w") as f:
  53. f.setnchannels(2)
  54. f.setsampwidth(2)
  55. f.setframerate(samplerate)
  56. f.writeframes(data)
  57. def main():
  58. if len(sys.argv) <= 1:
  59. print("Usage:")
  60. print("\t" + sys.argv[0] + " image.png [seconds]")
  61. sys.exit(1)
  62. if len(sys.argv) >= 3:
  63. duration = float(sys.argv[2])
  64. else:
  65. duration = default_duration
  66. with Image.open(sys.argv[1]) as image:
  67. wave = read_image(image)
  68. samplecount = int(len(wave) / 2 / 2) # stereo, int16
  69. drawrate = samplerate / samplecount
  70. drawcount = drawrate * duration
  71. print("len={} samples={} drawrate={:.2f} count={:.2f}".format(len(wave), samplecount, drawrate, drawcount))
  72. data = bytearray()
  73. for n in range(0, int(drawcount)):
  74. data.extend(wave)
  75. print("len={}".format(len(data)))
  76. #play_waveform(bytes(data))
  77. write_waveform(bytes(data))
  78. if __name__ == "__main__":
  79. main()