I want to find how many bytes per second are used in a mp3 file in order to be able to find the duration of the audio file.
Im using the script below and it shows me some strange values. Im not really familiar with all this audio file things and i would appreciate some help.
function getDuration($file) {
$fp = fopen($file, 'rb');
$size_in_bytes = filesize($file);
fseek($fp, 20);
$rawheader = fread($fp, 16);
$header = unpack('vtype/vchannels/Vsamplerate/Vbytespersec/valignment/vbits', $rawheader);
print_r($header);
$sec = ceil($size_in_bytes/$header['bytespersec']);
return $sec;
}
The output on print_r is
Array ( [type] => 25936 [channels] => 27489 [samplerate] => 1970037078 [bytespersec] => 2110652517 [alignment] => 0 [bits] => 21072 )
So this bytespersec rate (2 110 652 517) is confusing me.
maybe you can use the ffmpeg extension for php?
http://ffmpeg-php.sourceforge.net/doc/api/ffmpeg_movie.php
Related
I'm trying to parse a Binary File in PHP which is an attachment of a Document in a NoSQL DB. However, in my tests, if the size of a file is of 1MB, the unpacking lasts for around 12-15 seconds. The file contains information about speed from a sensor.
The binary file converted to hexadecimal is structured as follow:
BB22 1100 0015 XXXX ...
BB22 1300 0400 20FB 5900 25FB 5910 ... 20FB 5910
BB22 1100 0015 ...
BB22 1300 0400 20FB 5700 25FB 5810 ... 20FB 5912
BB22 1300 0400 20FB 5700 25FB 5810 ... 20FB 5912
...
The marker BB22 1100 contains the sensor specification, while 0015 refers to the size of that information.
The marker BB22 1300 contains other data plus the actual speed from the sensor. The next two bytes 0400 represent the length of that chunk, which is of 1024 bytes.
I'm only interested in the speed which are the values e.g. 5900 5910 5910 5700 5810 ...
My approach is as follow:
$file = fopen($url, 'r', false, authenticationContext($url));
$result = stream_get_contents($file, -1);
fclose($file);
$hex_result = bin2hex($result);
$markerData = 'bb2213';
$sensorDataUnpack= "sspeed"; // signed int16
while(($pos = strpos($hex_result, $markerData, $pos)) !== FALSE){
$pos=$pos+4;
for ($j=4; $j<1028; $j=$j+4) {
$d = unpack($sensorDataUnpack, substr($result, $pos/2+$j+2));
$sensorData[] = $d;
}
}
I converted the results from binary to hexadecimal because it wasn't working for me to get the positions properly. Anyway, I believe this code can be very much improved, any ideas?.
This should be fast, but without test data I wasn't able to test it.
The key points are these:
Open the URL as binary, and use the fread() to help in positioning and in slicing up the data to parts.
Use the unpack both for parsing the headers and the bodies of the entries as well.
Use the asterisk * repeater to quickly parse the big bodies for signed shorts.
Use array_values() to convert the associative array to a simple array with numeric keys (like: 0, 1, 2, ...).
Update: I solved the endianness and bitness problem around the marker comparison by using the "H4" pack format to get a hexa string in big endian order.
$sensorData = array();
$file = fopen($url, 'rb', false, authenticationContext($url));
while (($header = fread($file, 6)) !== false) {
$fields = unpack("H4marker/ssize", $header);
$body = fread($file, $fields["size"] * 2);
if ($body === false) {
throw new Exception("import: data stream unexpectedly ended.");
}
if ($fields["marker"] == "BB221300") {
$data = array_values(unpack("s*", $body));
// Store only every second value.
for ($i = 1; $i < count($data); $i+=2) {
$sensorData[] = $data[$i];
}
}
}
fclose($file);
I have a PHP array, which contains bytes, and the actual bytes represent a ZIP file (which contains multiple files inside the ZIP).
This is returned from a third party API, so I have no choice but to work with this format.
So I'm trying to convert this into the ZIP file.
Example of byte array returned by API:
[bytes] => Array
(
[0] => 37
[1] => 80
[2] => 68
[3] => 70
[4] => 45
[5] => 49
[6] => -46
... continues to close to 20,000
)
I have tried simply getting the browser to return it by creating a complete byte string, and adapting browser headers... using:
foreach($bytes as $byte)
{
$byteString .= $byte;
}
header("Content-type: application/zip");
header("Content-Disposition: inline; filename='test.zip'");
echo $byteString;
This does create a zip file, but it's invalid / corrupted.
I have also tried this which I found elsewhere on Stackoverflow:
$fp = fopen('/myfile.zip', 'wb+');
while(!empty($bytes)) {
$byte1 = array_shift($bytes);
$byte2 = array_shift($bytes);
if(!$byte2) {
$byte2 = 0;
}
fwrite($fp, pack("n*", ($byte1 << 8) + $byte2));
}
close($fp);
But again, the ZIP is created, but it's invalid / corrupted.
Any pointers, most appreciated.
Thanks
With the help of an ascii table it's easy to decode the data you're receiving, and you will see that it starts with
%PDF
which means that the returned data is in PDF format, not zip. Zip files start with PK, the initials of the inventor of the format, the late Phil Katz.
As a general note, knowing about the common file type signatures is quite useful and can save you lots of time.
I'm reading a binary file in PHP:
$File = fopen('simplefile', 'rb');
// Random stuff
fseek($File, $APosition);
for($I = 0; $I < 10; $I ++ ){
var_dump(unpack('V', fread($File, 4)));
}
fclose($File);
However this does not give correct output for that position (it is giving 6357101 not 4294967295 as expected - I've checked using a hex editor).
However, this work and gives the correct values:
$File = fopen('simplefile', 'rb');
// Random stuff
fseek($File, $APosition);
for($I = 0; $I < 10; $I ++ ){
fseek($File, ftell($File)); // What is this line doing?
var_dump(unpack('V', fread($File, 4)));
}
fclose($File);
This however, I don't understand as surely fseek($File, ftell($File)); does absolutely nothing? What is that line doing and how can I read the correct values without errors?
This segment of the file is just 0xFF repeated several hundred times.
I set up a file of all FF bytes, and the loop correctly reads -1 ten times, which is 4294967295 unsigned. I can't reproduce the value you get.
The value you are getting, 6357101, corresponds to the bytes 6D 00 61 00. That is the characters 'ma' encoded in UTF-16, although it could also mean other things.
I think the file or the content of the file is not what you think it is, or you are reading from the wrong place.
fseek($File, ftell($File)); // What is this line doing?
It should do nothing. If it doesn't do nothing I can only speculate that perhaps the file is corrupt on disk, or your copy of PHP is borked, or your computer has gone quietly mad.
If you dont use that line, that mean, you are still reading the first 4 Bytes in Loop. You need do seek.
sometxtfile.txt
aaaabbbbcccc
Without that line it ould be like:
This will return aaaaaaaaaaaa:
$File = fopen('sometxtfile.txt', 'rb');
echo fread($File, 4);
echo fread($File, 4);
echo fread($File, 4);
With that line it is like:
This will return aaaabbbbcccc:
$File = fopen('sometxtfile.txt', 'rb');
echo fread($File, 4);
fseek($File, ftell($File));
echo fread($File, 4);
fseek($File, ftell($File));
echo fread($File, 4);
fseek($File, ftell($File));
See PHP documentation for further info.
For some reason, the zlib.deflate filter doesn't seem to be working with socket pairs generated by stream_socket_pair(). All that can be read from the second socket is the two-byte zlib header, and everything after that is NULL.
Example:
<?php
list($in, $out) = stream_socket_pair(STREAM_PF_UNIX,
STREAM_SOCK_STREAM,
STREAM_IPPROTO_IP);
$params = array('level' => 6, 'window' => 15, 'memory' => 9);
stream_filter_append($in, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
stream_set_blocking($in, 0);
stream_set_blocking($out, 0);
fwrite($in, 'Some big long string.');
$compressed = fread($out, 1024);
var_dump($compressed);
fwrite($in, 'Some big long string, take two.');
$compressed = fread($out, 1024);
var_dump($compressed);
fwrite($in, 'Some big long string - third time is the charm?');
$compressed = fread($out, 1024);
var_dump($compressed);
Output:
string(2) "x�"
string(0) ""
string(0) ""
If I comment out the call to stream_filter_append(), the stream writing/reading functions correctly, with the data being dumped in its entirety all three times, and if I direct the zlib filtered stream into a file instead of through the socket pair, the compressed data is written correctly. So both parts function correctly separately, but not together. Is this a PHP bug that I should report, or an error on my part?
This question is branched from a solution to this related question.
I had worked on the PHP source code and found a fix.
To understand what happens I had traced the code during a
....
for ($i = 0 ; $i < 3 ; $i++) {
fwrite($s[0], ...);
fread($s[1], ...);
fflush($s[0], ...);
fread($s[1], ...);
}
loop and I found that the deflate function is never called with the Z_SYNC_FLUSH flag set because no new data are present into the backets_in brigade.
My fix is to manage the (PSFS_FLAG_FLUSH_INC flag is set AND no iterations are performed on deflate function case) extending the
if (flags & PSFS_FLAG_FLUSH_CLOSE) {
managing FLUSH_INC too:
if (flags & PSFS_FLAG_FLUSH_CLOSE || (flags & PSFS_FLAG_FLUSH_INC && to_be_flushed)) {
This downloadable patch is for debian squeeze version of PHP but the current git version of the file is closer to it so I suppose to port the fix is simply (few lines).
If some side effect arises please contact me.
Looking through the C source code, the problem is that the filter always lets zlib's deflate() function decide how much data to accumulate before producing compressed output. The deflate filter does not create a new data bucket to pass on unless deflate() outputs some data (see line 235) or the PSFS_FLAG_FLUSH_CLOSE flag bit is set (line 250). That's why you only see the header bytes until you close $in; the first call to deflate() outputs the two header bytes, so data->strm.avail_out is 2 and a new bucket is created for these two bytes to pass on.
Note that fflush() does not work because of a known issue with the zlib filter. See: Bug #48725 Support for flushing in zlib stream.
Unfortunately, there does not appear to be a nice work-around to this. I started writing a filter in PHP by extending php_user_filter, but quickly ran into the problem that php_user_filter does not expose the flag bits, only whether flags & PSFS_FLAG_FLUSH_CLOSE (the fourth parameter to the filter() method, a boolean argument commonly named $closing). You would need to modify the C sources yourself to fix Bug #48725. Alternatively, re-write it.
Personally I would consider re-writing it because there seems to be a few eyebrow-raising issues with the code:
status = deflate(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FULL_FLUSH : (flags & PSFS_FLAG_FLUSH_INC ? Z_SYNC_FLUSH : Z_NO_FLUSH)); seems odd because when writing, I don't know why flags would be anything other than PSFS_FLAG_NORMAL. Is it possible to write & flush at the same time? In any case, handling the flags should be done outside of the while loop through the "in" bucket brigade, like how PSFS_FLAG_FLUSH_CLOSE is handled outside of this loop.
Line 221, the memcpy to data->strm.next_in seems to ignore the fact that data->strm.avail_in may be non-zero, so the compressed output might skip some data of a write. See, for example, the following text from the zlib manual:
If not all input can be processed (because there is not enough room in the output buffer), next_in and avail_in are updated and processing will resume at this point for the next call of deflate().
In other words, it is possible that avail_in is non-zero.
The if statement on line 235, if (data->strm.avail_out < data->outbuf_len) should probably be if (data->strm.avail_out) or perhaps if (data->strm.avail_out > 2).
I'm not sure why *bytes_consumed = consumed; isn't *bytes_consumed += consumed;. The example streams at http://www.php.net/manual/en/function.stream-filter-register.php all use += to update $consumed.
EDIT: *bytes_consumed = consumed; is correct. The standard filter implementations all use = rather than += to update the size_t value pointed to by the fifth parameter. Also, even though $consumed += ... on the PHP side effectively translates to += on the size_t (see lines 206 and 231 of ext/standard/user_filters.c), the native filter function is called with either a NULL pointer or a pointer to a size_t set to 0 for the fifth argument (see lines 361 and 452 of main/streams/filter.c).
You need to close the stream after the write to flush it before the data will come in from the read.
list($in, $out) = stream_socket_pair(STREAM_PF_UNIX,
STREAM_SOCK_STREAM,
STREAM_IPPROTO_IP);
$params = array('level' => 6, 'window' => 15, 'memory' => 9);
stream_filter_append($out, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
stream_set_blocking($out, 0);
stream_set_blocking($in, 0);
fwrite($out, 'Some big long string.');
fclose($out);
$compressed = fread($in, 1024);
echo "Compressed:" . bin2hex($compressed) . "<br>\n";
list($in, $out) = stream_socket_pair(STREAM_PF_UNIX,
STREAM_SOCK_STREAM,
STREAM_IPPROTO_IP);
$params = array('level' => 6, 'window' => 15, 'memory' => 9);
stream_filter_append($out, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
stream_set_blocking($out, 0);
stream_set_blocking($in, 0);
fwrite($out, 'Some big long string, take two.');
fclose($out);
$compressed = fread($in, 1024);
echo "Compressed:" . bin2hex($compressed) . "<br>\n";
list($in, $out) = stream_socket_pair(STREAM_PF_UNIX,
STREAM_SOCK_STREAM,
STREAM_IPPROTO_IP);
$params = array('level' => 6, 'window' => 15, 'memory' => 9);
stream_filter_append($out, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
stream_set_blocking($out, 0);
stream_set_blocking($in, 0);
fwrite($out, 'Some big long string - third time is the charm?');
fclose($out);
$compressed = fread($in, 1024);
echo "Compressed:" . bin2hex($compressed) . "<br>\n";
That produces:
Compressed:789c0bcecf4d5548ca4c57c8c9cf4b57282e29cacc4bd70300532b079c
Compressed:789c0bcecf4d5548ca4c57c8c9cf4b57282e29cacc4bd7512849cc4e552829cfd70300b1b50b07
Compressed:789c0bcecf4d5548ca4c57c8c9cf4b57282e29ca0452ba0a25199945290a259940c9cc62202f55213923b128d71e008e4c108c
Also I switched the $in and $out because writing to $in confused me.
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 12.4M 100 12.4M 0 0 4489k 0 0:00:02 0:00:02 --:--:-- 4653k
The above is a CURL output from the command line when download the file. I have captured this using PHP like so, but I am having trouble working out how to use pre_match to extract the percentage done.
$handle = popen('curl -o '.VIDEOPATH.$fileName.'.flv '.$url, 'rb');
while(!feof($handle))
{
$progress = fread($handle, 8192);
//I don't even know what I was attempting here
$pattern = '/(?<Total>[0-9]{1,3}\.[0-9]{1,2})% of (?<Total>.+) at/';
//divide received by total somehow, then times 100
if(preg_match_all($pattern, $progress, $matches)){
fwrite($fh, $matches[0][0]."\r\n");
}
}
How can I do this? Please note, I have no idea what I am doing with the above preg_match_all!
Thanks
Update
Thanks to the help of ylebre. I have this so far.
$handle = popen('curl -o '.VIDEOPATH.$fileName.'.flv '.$url.' 2>&1', 'rb');//make sure its saved to videos
while(!feof($handle))
{
$line = fgets($handle, 4096); // Get a line from the input handle
echo '<br>Line'.$line.'<br>';
$line = preg_replace("/s+/", " ", $line); // replace the double spaces with one
$fields = explode(" ", $line); // split the input on spaces into fields array
echo '<br>Fields: '.$fields[0];
fwrite($fh, $fields[0]); // write a part of the fields array to the output file
}
I get this output to the browser:
Line % Total % Received % Xferd Average Speed Time Time Time Current
Fields:
Line Dload Upload Total Spent Left Speed
Fields:
Line 0 1340k 0 4014 0 0 27342 0 0:00:50 --:--:-- 0:00:50 27342 41 1340k 41 552k 0 0 849k 0 0:00:01 --:--:-- 0:00:01 1088k 100 1340k 100 1340k 0 0 1445k 0 --:--:-- --:--:-- --:--:-- 1711k
Fields:
Line
How do I extract the percentage part only? Maybe CURL can do this by itself - hmm will ask a question on this.
The progress that is showing up is probably updating the information in the same spot, so it will help if you know what you are parsing exactly.
The next step I recommend is taking one line of input, and trying to get the regexp to work on that.
You could also just split the string at the spaces if I'm reading the output correctly. If you start out by replacing all the double spaces into one. After that you can use explode() to get an array with the values, which you can print_r to take a peek what is inside.
This would be something like:
$line = fgets($handle, 4096); // Get a line from the input handle
$line = preg_replace("/s+/", " ", $line); // replace the double spaces with one
$fields = explode(" ", $line); // split the input on spaces into fields array
fwrite($fh, $fields[0]); // write a part of the fields array to the output file
As long as the ordering in the fields remains the same, your resulting array should give you a consistent result.
Hope this helps!
If you have access to PHP 5.3, you can use the CURL_PROGRESSFUNCTION option, which results in a much more elegant solution (no parsing output). Here's an example of how to use it:
function callback($download_size, $downloaded, $upload_size, $uploaded)
{
$percent=$downloaded/$download_size;
// Do something with $percent
}
$ch = curl_init('http://www.example.com');
// Turn off the default progress function
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
// Set up the callback
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, 'callback');
// You'll want to tweak the buffer size. Too small could affect performance. Too large and you don't get many progress callbacks.
curl_setopt($ch, CURLOPT_BUFFERSIZE, 128);
$data = curl_exec($ch);