image2audio.pl 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. #!/usr/bin/perl
  2. # Convert an image to an audio spectrogram.
  3. # Algorithm from:
  4. # https://github.com/alexadam/img-encode/blob/master/v1-python/imgencode.py
  5. # The spectrogram can be viewed in a program, like Audacity.
  6. # Inspired by the hidden message in the movie "Leave the world behind":
  7. # https://www.reddit.com/r/MrRobot/comments/18hnn3q/minor_spoiler_leave_the_world_behind_hidden/
  8. use 5.036;
  9. use Imager;
  10. use Audio::Wav;
  11. use List::Util qw(min max);
  12. use Getopt::Long qw(GetOptions);
  13. my $max_height = 300; # resize images larger than this
  14. my $sample_rate = 44100;
  15. my $bits_sample = 16;
  16. my $frequency_band = $sample_rate / 2; # in Hz
  17. my $channels = 1;
  18. my $duration_factor = 1;
  19. my $output_wav = 'output.wav';
  20. sub help ($code) {
  21. print <<"EOT";
  22. usage: $0 [options] [images]
  23. options:
  24. -o --output=s : output audio file (default: $output_wav)
  25. -f --freq=i : frequency band in Hz (default: $frequency_band)
  26. -d --duration=f : duration multiplication factor (default: $duration_factor)
  27. -b --bits=i : bits sample (default: $bits_sample)
  28. -s --sample=i : sample rate (default: $sample_rate)
  29. -c --channels=i : number of channels (default: $channels)
  30. EOT
  31. exit($code);
  32. }
  33. GetOptions(
  34. 'o|output=s' => \$output_wav,
  35. 'f|frequency=i' => \$frequency_band,
  36. 'd|duration-factor=f' => \$duration_factor,
  37. 'b|bits-sample=i' => \$bits_sample,
  38. 's|sample-rate=i' => \$sample_rate,
  39. 'c|channels=i' => \$channels,
  40. 'h|help' => sub { help(0) },
  41. )
  42. or die("Error in command line arguments");
  43. sub range_map ($value, $in_min, $in_max, $out_min, $out_max) {
  44. ($value - $in_min) * ($out_max - $out_min) / ($in_max - $in_min) + $out_min;
  45. }
  46. sub image2spectrogram ($input_file, $write) {
  47. say "\n:: Processing: $input_file";
  48. my $img = Imager->new(file => $input_file)
  49. or die "Can't open file <<$input_file>> for reading: $!";
  50. my $width = $img->getwidth;
  51. my $height = $img->getheight;
  52. my $duration = $duration_factor * ($width / $height);
  53. say "-> Duration: $duration seconds";
  54. if ($height > $max_height) {
  55. $img = $img->scale(ypixels => $max_height, qtype => 'mixing');
  56. ($width, $height) = ($img->getwidth, $img->getheight);
  57. }
  58. my $min_size = min($width, $height);
  59. $width = int($duration * $min_size);
  60. $height = $min_size;
  61. say "-> Resizing the image to: $width x $height";
  62. $img = $img->scale(xpixels => $width, ypixels => $height, qtype => 'mixing', type => 'nonprop');
  63. my @data;
  64. my $maxFreq = 0;
  65. my $numSamples = int($sample_rate * $duration);
  66. my $samplesPerPixel = $numSamples / $width;
  67. my $C = $frequency_band / $height;
  68. my @img;
  69. foreach my $y (0 .. $height - 1) {
  70. my @line = $img->getscanline(y => $y);
  71. foreach my $pixel (@line) {
  72. my ($R, $G, $B) = $pixel->rgba;
  73. ## push @{$img[$y]}, ((($R + $G + $B) / 3) * 100 / 255)**2;
  74. ## push @{$img[$y]}, ((0.5 * max($R, $G, $B) + 0.5 * min($R, $G, $B)) * 100 / 255)**2;
  75. ## push @{$img[$y]}, (sqrt(0.299 * $R**2 + 0.587 * $G**2 + 0.114 * $B**2) * 100 / 255)**2;
  76. push @{$img[$y]}, ((0.299 * $R + 0.587 * $G + 0.114 * $B) * 100 / 255)**2;
  77. }
  78. }
  79. say "-> Converting the pixels to spectrogram frequencies";
  80. my $tau = 2 * atan2(0, -1);
  81. foreach my $x (0 .. $numSamples - 1) {
  82. my $rez = 0;
  83. my $pixel_x = int($x / $samplesPerPixel);
  84. foreach my $y (0 .. $height - 1) {
  85. my $volume = $img[$y][$pixel_x] || next;
  86. my $freq = sprintf('%.0f', $C * ($height - $y + 1));
  87. $rez += sprintf('%.0f', $volume * cos($freq * $tau * $x / $sample_rate));
  88. }
  89. push @data, $rez;
  90. if (abs($rez) > $maxFreq) {
  91. $maxFreq = abs($rez);
  92. }
  93. }
  94. say "-> Maximum frequency: $maxFreq";
  95. my $max_no = 2**($bits_sample - 1) - 1;
  96. #my $min = min(@data);
  97. #my $max = max(@data);
  98. my $min = -$maxFreq;
  99. my $max = $maxFreq;
  100. foreach my $val (@data) {
  101. ## $write->write(sprintf('%.0f', $max_no * $val / $maxFreq));
  102. $write->write(range_map($val, $min, $max, -$max_no, $max_no));
  103. }
  104. return 1;
  105. }
  106. @ARGV || help(2);
  107. my $details = {
  108. 'bits_sample' => $bits_sample,
  109. 'sample_rate' => $sample_rate,
  110. 'channels' => $channels,
  111. };
  112. my $wav = Audio::Wav->new;
  113. my $write = $wav->write($output_wav, $details);
  114. foreach my $input_img (@ARGV) {
  115. image2spectrogram($input_img, $write);
  116. }
  117. $write->finish();