Skip to content

Commit 6bcd299

Browse files
committed
Initial commit
0 parents  commit 6bcd299

File tree

7 files changed

+449
-0
lines changed

7 files changed

+449
-0
lines changed

Waveform.php

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<?php
2+
/**
3+
*
4+
*
5+
* @author MaximAL
6+
* @since 2016-11-21
7+
* @date 2016-11-21
8+
* @time 19:08
9+
* @link http://maximals.ru
10+
* @link http://sijeko.ru
11+
* @link https://github.com/maximal/audio-waveform-php
12+
* @copyright © MaximAL, Sijeko 2016
13+
*/
14+
15+
namespace maximal\audio;
16+
17+
class Waveform
18+
{
19+
protected $filename;
20+
protected $info;
21+
protected $channels;
22+
protected $samples;
23+
protected $sampleRate;
24+
protected $duration;
25+
26+
public static $linesPerPixel = 8;
27+
public static $samplesPerLine = 512;
28+
29+
// Colors in CSS `rgba(red, green, blue, opacity)` format
30+
public static $color = [95, 95, 95, 0.5];
31+
public static $backgroundColor = [245, 245, 245, 1];
32+
public static $axisColor = [0, 0, 0, 0.1];
33+
34+
35+
public function __construct($filename)
36+
{
37+
$this->filename = $filename;
38+
}
39+
40+
public function getInfo()
41+
{
42+
$out = null;
43+
$ret = null;
44+
exec('sox --i ' . escapeshellarg($this->filename) . ' 2>&1', $out, $ret);
45+
$str = implode('|', $out);
46+
47+
$match = null;
48+
if (preg_match('/Channels?\s*\:\s*(\d+)/ui', $str, $match)) {
49+
$this->channels = intval($match[1]);
50+
}
51+
52+
$match = null;
53+
if (preg_match('/Sample\s*Rate\s*\:\s*(\d+)/ui', $str, $match)) {
54+
$this->sampleRate = intval($match[1]);
55+
}
56+
57+
$match = null;
58+
if (preg_match('/Duration.*[^\d](\d+)\s*samples?/ui', $str, $match)) {
59+
$this->samples = intval($match[1]);
60+
}
61+
62+
if ($this->samples && $this->sampleRate) {
63+
$this->duration = 1.0 * $this->samples / $this->samples;
64+
}
65+
66+
if ($ret !== 0) {
67+
throw new \Exception('Failed to get audio info.' . PHP_EOL . 'Error: ' . implode(PHP_EOL, $out) . PHP_EOL);
68+
}
69+
}
70+
71+
public function getSampleRate()
72+
{
73+
if (!$this->sampleRate) {
74+
$this->getInfo();
75+
}
76+
return $this->sampleRate;
77+
}
78+
79+
public function getChannels()
80+
{
81+
if (!$this->channels) {
82+
$this->getInfo();
83+
}
84+
return $this->channels;
85+
}
86+
87+
public function getSamples()
88+
{
89+
if (!$this->samples) {
90+
$this->getInfo();
91+
}
92+
return $this->samples;
93+
}
94+
95+
public function getDuration()
96+
{
97+
if (!$this->duration) {
98+
$this->getInfo();
99+
}
100+
return $this->duration;
101+
}
102+
103+
104+
public function getWaveform($filename, $width, $height)
105+
{
106+
// Calculating parameters
107+
$needChannels = $this->getChannels() > 1 ? 2 : 1;
108+
$samplesPerPixel = self::$samplesPerLine * self::$linesPerPixel;
109+
$needRate = 1.0 * $width * $samplesPerPixel * $this->getSampleRate() / $this->getSamples();
110+
111+
//if ($needRate > 4000) {
112+
// $needRate = 4000;
113+
//}
114+
115+
// Command text
116+
$command = 'sox ' . escapeshellarg($this->filename) .
117+
' -c ' . $needChannels .
118+
' -r ' . $needRate . ' -e floating-point -t raw -';
119+
120+
//var_dump($command);
121+
122+
$outputs = [
123+
1 => ['pipe', 'w'], // stdout
124+
2 => ['pipe', 'w'], // stderr
125+
];
126+
$pipes = null;
127+
$proc = proc_open($command, $outputs, $pipes);
128+
if (!$proc) {
129+
throw new \Exception('Failed to run sox command');
130+
}
131+
132+
$lines1 = [];
133+
$lines2 = [];
134+
while ($chunk = fread($pipes[1], 4 * $needChannels * self::$samplesPerLine)) {
135+
$data = unpack('f*', $chunk);
136+
$channel1 = [];
137+
$channel2 = [];
138+
foreach ($data as $index => $sample) {
139+
if ($needChannels === 2 && $index % 2 === 0) {
140+
$channel2 []= $sample;
141+
} else {
142+
$channel1 []= $sample;
143+
}
144+
}
145+
$lines1 []= min($channel1);
146+
$lines1 []= max($channel1);
147+
if ($needChannels === 2) {
148+
$lines2 []= min($channel2);
149+
$lines2 []= max($channel2);
150+
}
151+
}
152+
153+
$err = stream_get_contents($pipes[2]);
154+
$ret = proc_close($proc);
155+
156+
if ($ret !== 0) {
157+
throw new \Exception('Failed to run `sox` command. Error:' . PHP_EOL . $err);
158+
}
159+
160+
// Creating image
161+
$img = imagecreatetruecolor($width, $height);
162+
imagesavealpha($img, true);
163+
//if (function_exists('imageantialias')) {
164+
// imageantialias($img, true);
165+
//}
166+
167+
// Colors
168+
$back = self::rgbaToColor($img, self::$backgroundColor);
169+
$color = self::rgbaToColor($img, self::$color);
170+
$axis = self::rgbaToColor($img, self::$axisColor);
171+
imagefill($img, 0, 0, $back);
172+
173+
// Center Ys
174+
$center1 = $needChannels === 2 ? ($height / 2 - 1) / 2 : $height / 2;
175+
$center2 = $needChannels === 2 ? $height - $center1 : null;
176+
177+
// Drawing channel 1
178+
for ($i = 0; $i < count($lines1); $i += 2) {
179+
$min = $lines1[$i];
180+
$max = $lines1[$i+1];
181+
$x = $i / 2 / self::$linesPerPixel;
182+
183+
imageline($img, $x, $center1 - $min * $center1, $x, $center1 - $max * $center1, $color);
184+
}
185+
// Drawing channel 2
186+
for ($i = 0; $i < count($lines2); $i += 2) {
187+
$min = $lines2[$i];
188+
$max = $lines2[$i + 1];
189+
$x = $i / 2 / self::$linesPerPixel;
190+
191+
imageline($img, $x, $center2 - $min * $center1, $x, $center2 - $max * $center1, $color);
192+
}
193+
194+
// Axis
195+
imageline($img, 0, $center1, $width - 1, $center1, $axis);
196+
if ($center2 !== null) {
197+
imageline($img, 0, $center2, $width - 1, $center2, $axis);
198+
}
199+
200+
return imagepng($img, $filename);
201+
}
202+
203+
public static function rgbaToColor($img, $rgba)
204+
{
205+
return imagecolorallocatealpha($img, $rgba[0], $rgba[1], $rgba[2], round((1 - $rgba[3]) * 127));
206+
}
207+
}

composer.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "maximal/audio-waveform",
3+
"type": "library",
4+
"description": "Audio waveform generator on PHP with SoX",
5+
"keywords": ["audio", "waveform", "generator", "sox"],
6+
"homepage": "https://github.com/maximal/audio-waveform-php#readme",
7+
"license": "MIT",
8+
"authors": [
9+
{
10+
"name": "MaximAL",
11+
"homepage": "http://maximals.ru",
12+
"role": "Author"
13+
}
14+
],
15+
"support": {
16+
"issues": "https://github.com/maximal/audio-waveform-php/issues",
17+
"source": "https://github.com/maximal/audio-waveform-php"
18+
},
19+
"require": {
20+
"php": ">=5.6.0"
21+
},
22+
"autoload": {
23+
"psr-4": {
24+
"maximal\\audio\\": ""
25+
}
26+
}
27+
}

example.png

56 KB
Loading

license.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MIT License
2+
3+
Copyright (c) 2016 MaximAL
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+

readme.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Audio waveform generator on PHP with SoX
2+
3+
![Example result](example.png)
4+
5+
6+
## Install
7+
8+
Install SoX and some of its handlers for different audio formats. For example, on Ubuntu:
9+
10+
```sh
11+
sudo apt install sox libsox-fmt-all
12+
```
13+
14+
Install this package using the [Composer](https://getcomposer.org) `require` command:
15+
16+
```sh
17+
composer require maximal/audio-waveform '~1.0'
18+
```
19+
20+
or add the package name to the `require` section in your `composer.json` file:
21+
```json
22+
"require": {
23+
"maximal/audio-waveform": "~1.0"
24+
}
25+
```
26+
27+
and then run:
28+
```sh
29+
composer update
30+
```
31+
32+
33+
## Use
34+
35+
In your PHP source:
36+
37+
```php
38+
// Include `maximal\audio` namespace
39+
use maximal\audio\Waveform;
40+
41+
// Open an audio file `track.mp3`
42+
$waveform = new Waveform('track.mp3');
43+
44+
// Save its waveform to the `thumbnail.png` image file which size is 1024×512 pixels
45+
$success = $waveform->getWaveform('thumbnail.png', 1024, 512);
46+
```
47+
48+
49+
## Settings
50+
51+
All settings are public static members of `Waveform` class:
52+
* `$linesPerPixel` is the count of lines per each pixel in horizontal axis. Default is `8`.
53+
* `$samplesPerLine` is the count of samples per each line. Default is `512`.
54+
* `$color` is the color of each line. Default is `[95, 95, 95, 0.5]` meaning the dark grey color with 50% opacity.
55+
* `$backgroundColor` is the background color of the waveform file.
56+
Default is `[245, 245, 245, 1]` meaning the light grey opaque background with 100% opacity.
57+
* `$axisColor` is the color of each axis. Default is `[0, 0, 0, 0.15]` meaning the black color with 15% opacity.
58+
59+
60+
## Examples
61+
62+
Red waveform with half-transparent peaks:
63+
```php
64+
$waveform = new Waveform('track.mp3');
65+
Waveform::$color = [255, 0, 0, 0.5];
66+
$success = $waveform->getWaveform('thumbnail.png', 1024, 512);
67+
```
68+
69+
Red waveform and fully transparent background:
70+
```php
71+
$waveform = new Waveform('track.mp3');
72+
Waveform::$color = [255, 0, 0, 0.5];
73+
Waveform::$backgroundColor = [0, 0, 0, 0];
74+
$success = $waveform->getWaveform('thumbnail.png', 1024, 512);
75+
```
76+
77+
## Ubuntu Linux thumbnailer for Nautilus file explorer
78+
79+
1. Install the package to some directory. For example: `/opt/maximal/audio-waveform-php`
80+
81+
2. Place the following code in the file `/usr/share/thumbnailers/waveform.thumbnailer`
82+
```
83+
[Thumbnailer Entry]
84+
Exec=/opt/maximal/audio-waveform-php/thumbnailer.php %i %o %sx%s
85+
MimeType=audio/wave;audio/x-wav;audio/mpeg;audio/ogg
86+
```
87+
88+
This repository contains an example thumbnailer file. See: `usr/share/thumbnailers/waveform.thumbnailer`.
89+
90+
Also you can add other MIME types to the `MimeType` section of your thumbnaler file
91+
as long as they are supportable by [SoX](http://sox.sourceforge.net) utility.
92+
93+
3. Clear thumbnail cache and restart Nautilus:
94+
```sh
95+
rm -rf ~/.thumbnails
96+
nautilus -q
97+
```
98+
99+
4. Since then all your audio files with specified MIME types will be shown in Nautilus using its small waveforms.
100+
By default they are WAV, MP3 and OGG files.
101+
102+
103+
## Contact the author
104+
105+
* Website: http://maximals.ru (Russian)
106+
* Sijeko Company: http://sijeko.ru (web, mobile, desktop applications development and graphic design)
107+
* Personal GitHub: https://github.com/maximal
108+
* Company’s GitHub: https://github.com/sijeko

0 commit comments

Comments
 (0)