Before everything , I know that my question is already posted thousand times but none of them concern me since i cant identify my problem.
So ,I am building a PHP rest api to get a put request from the client (angular), this last can update a file inside the form as well as some text inputs , all of this is sent as a form-data in this code :
var fileFormData = new FormData();
$http({
method: 'PUT',
url: host + '/produit/' + $scope.product.id,
data: fileFormData,
transformRequest: angular.identity,
headers: {
'Content-Type': undefined
}
}).then(function successCallback(response) {}, function errorCallback(response) {});
On the server-side I have to get the response type and then parse the data here's the full code with help from here
$method = $_SERVER['REQUEST_METHOD'];
if ($method=="PUT"){
global $_PUT;
/* PUT data comes in on the stdin stream */
$putdata = fopen("php://input", "r");
/* Open a file for writing */
// $fp = fopen("myputfile.ext", "w");
$raw_data = '';
/* Read the data 1 KB at a time
and write to the file */
while ($chunk = fread($putdata, 1024))
$raw_data .= $chunk;
/* Close the streams */
fclose($putdata);
// Fetch content and determine boundary
$boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));
if(empty($boundary)){
parse_str($raw_data,$data);
$GLOBALS[ '_PUT' ] = $data;
return;
}
// Fetch each part
$parts = array_slice(explode($boundary, $raw_data), 1);
$data = array();
foreach ($parts as $part) {
// If this is the last part, break
if ($part == "--\r\n") break;
// Separate content from headers
$part = ltrim($part, "\r\n");
list($raw_headers, $body) = explode("\r\n\r\n", $part, 2);
// Parse the headers list
$raw_headers = explode("\r\n", $raw_headers);
$headers = array();
foreach ($raw_headers as $header) {
list($name, $value) = explode(':', $header);
$headers[strtolower($name)] = ltrim($value, ' ');
}
// Parse the Content-Disposition to get the field name, etc.
if (isset($headers['content-disposition'])) {
$filename = null;
$tmp_name = null;
preg_match(
'/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/',
$headers['content-disposition'],
$matches
);
list(, $type, $name) = $matches;
//Parse File
if( isset($matches[4]) )
{
//if labeled the same as previous, skip
if( isset( $_FILES[ $matches[ 2 ] ] ) )
{
continue;
}
//get filename
$filename = $matches[4];
//get tmp name
$filename_parts = pathinfo( $filename );
$tmp_name = tempnam( ini_get('upload_tmp_dir'), $filename_parts['filename']);
//populate $_FILES with information, size may be off in multibyte situation
$_FILES[ $matches[ 2 ] ] = array(
'error'=>0,
'name'=>$filename,
'tmp_name'=>$tmp_name,
'size'=>strlen( $body ),
'type'=>$value
);
//place in temporary directory
file_put_contents($tmp_name, $body);
}
//Parse Field
else
{
$data[$name] = substr($body, 0, strlen($body) - 2);
}
}
}
$GLOBALS[ '_PUT' ] = $data;
$input= array_merge($_PUT, $_FILES);
}
and then I have to execute the query and move the file form the temp folder
$sql = "update `$table` set $set where id=$key"; //works
if(isset($name)){ //to check if file exist
chmod($tmpname,0666); //since that's the answer for most of question that are similar to mine
echo substr(sprintf('%o', fileperms($tmpname)), -4); //returns 0666
echo $tmpname." ".$name; //works too
if(move_uploaded_file($tmpname, "/home/****/****/uploads/produits/".$name))
echo "moved";
else
echo "not moved"; // and I always get this
Can you explain to me please whats wrong ?
PS:
I checked the /tmp folder the files I uploaded are still there.
The log is clean and I used this in the top of my code
ini_set('display_errors', true);
error_reporting(E_ALL);
Update :
I use this to get file params from the $input var
$values = array_map(function ($value) use ($link) {
global $method;
if ($value === null)
return null;
if (gettype($value) === "array"){
if ($method=="PUT" || $method=="POST"){
global $tmpname,$name;
$tmpname=$value['tmp_name'];
$name=uniqid().$value['name'];
$value=$name;
}
}
return mysqli_real_escape_string($link, (string) $value);
}, array_values($input));
Related
I have a csv file like this:-
+------+------+------------------------+
| name | mark | url |
+------+------+------------------------+
| ABCD | 5 | http://www.example.org |
| BCD | -2 | http://www.example.com |
| CD | 4 | htt://www.c.com |
+------+------+------------------------+
It contains a header for name, mark and url. I am using PHP to convert the data from csv to json. I want to add validation before converting it into json like for the name it should be in UTF-8, the mark should be a positive number and between 0 to 5 and the url should be valid. If the row passes all validation then it gets stored in a errorless.json and if any row has any issues then in error.json with a comment what was wrong. PHP code for csv to json:-
$fh = fopen("names.csv", "r");
$csvdata = array();
while (($row = fgetcsv($fh, 0, ",")) !== FALSE) {
$csvdata[] = $row;
}
$fp = fopen('data.json', 'w');
fwrite($fp, json_encode($csvdata));
fclose($fp);
I wanted to know how can i add these validations for converting the data. I am new to these concepts and unable to think of a way to do it. I would be highly grateful if anyone can help me.
Here is a shell of what you would do:
// to make life easier on yourself create
// a function that checks one row of data
// to make sure it is valid
// if a row is found to be invalid, the
// function will add an `error` field to the
// array explaining the validation error
function validateRow(&$data) {
if (!array_key_exists('mark', $data) && ((int)$data['mark']) >= 0 && ((int)$data['mark']) <= 5) {
$data['error'] = "No 'mark' value found between 0 and 5";
return false;
}
// do validation for $data['name']
// do validation for $data['url']
return true;
}
$fh = fopen("names.csv", "r");
$csvdata = array();
while (($row = fgetcsv($fh, 0, ",")) !== FALSE) {
$csvdata[] = $row;
}
$header = $csvdata[0];
$n = count($header);
$errorless = array();
$haserrors = array();
// here is where we convert the CSV data into
// an associative array / map of key value
// pairs by treating each row and the header
// row as parallel arrays. Start with index 1
// in the for loop to skip over the header row
// in csvdata
for ($row = 1; $row < count($csvdata); ++$row) {
$data = array();
for ($x = 0; $x < $n; ++$x) {
$data[$header[$x]] = $csvdata[$row][$x];
}
// if we encounter no errors,
// add data to the errorless array
if (validateRow($data)) {
$errorless[] = $data;
}
else {
$haserrors[] = $data;
}
}
// you will want to do a parallel write
// for the error.json file
$fp = fopen('errorless.json', 'w');
fwrite($fp, json_encode($errorless));
fclose($fp);
You should use something called JSON schema. You define how your document should be formed. Then your document can be validated automatically.
In case you were looking for another concrete implementation of CSV validation libraries, you should check out packagist.org in the first place.
This may help you,
The standard JSON format doesn't explicitly support file comments. RFC 4627 application/json, It's a lightweight format for storing and transferring data. If the comment is truly important, you can include it as another data field like comments
Input
$ cat test.csv
name,mark,url
ABCD,5,http://www.example.org
BCD,-2,http://www.example.com
CD,4,htt://www.c.com
Script
<?php
function validate_url($url)
{
return in_array(parse_url($url, PHP_URL_SCHEME),array('http','https')) && filter_var($url, FILTER_VALIDATE_URL);
}
function validate_mark($val)
{
return ($val >= 0 && $val <= 5);
}
function errors($field)
{
$errors = array(
'url' => 'URL should be valid',
'mark' => 'Mark should be between 0 to 5'
);
return ( isset($errors[$field]) ? $errors[$field] : "Unknown");
}
function csv2array_with_validation($filename, $delimiter = ",")
{
$header = $row = $c_row = $output = $val_func = array();
if (($handle = fopen($filename, 'r')) !== FALSE)
{
while (($row = fgetcsv($handle, 0, $delimiter)) !== FALSE)
{
if (empty($header))
{
$header = array_map('strtolower', $row);
foreach ($header as $e)
{
$val_func[$e] = function_exists('validate_' . $e);
}
continue;
}
$c_row = array_combine($header, $row);
$index = 'error_less'; $errors = array();
foreach ($c_row as $e => $v)
{
if ($val_func[$e])
{
if (!call_user_func('validate_' . $e, $v))
{
$index = 'error';
$errors[$e] = errors($e);
}
}
}
/*
If the comment is truly important,
you can include it as another data field like errors,
comment below part if you do not wish to create new field (errors) in
json file
*/
if(!empty($errors))
{
$c_row['errors'] = $errors;
}
$output[$index][] = $c_row;
}
fclose($handle);
}
return $output;
}
$output = csv2array_with_validation('test.csv');
// Write error.json
if (isset($output['error']) && !empty($output['error']))
{
file_put_contents('error.json', json_encode($output['error'], JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK));
}
// Write errorless.json
if (isset($output['error_less']) && !empty($output['error_less']))
{
file_put_contents('error_less.json', json_encode($output['error_less'], JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK));
}
?>
Output
$ php test.php
$ cat error.json
[
{
"name": "BCD",
"mark": -2,
"url": "http:\/\/www.example.com",
"errors": {
"mark": "Mark should be between 0 to 5"
}
},
{
"name": "CD",
"mark": 4,
"url": "htt:\/\/www.c.com",
"errors": {
"url": "URL should be valid"
}
}
]
$ cat error_less.json
[
{
"name": "ABCD",
"mark": 5,
"url": "http:\/\/www.example.org"
}
]
I'm having a headache trying to figure this out.
My folder structure is as follows:
index.php
helpers:
API.php
helpers.php
assets:
products.csv
debug:
debug_info.txt
My index file looks as follows:
<?php
require_once 'helpers/API.php';
file_put_contents('debug/debug_info.txt', "New request started");
if (in_array($_GET['action'],array('insertOrder','updateOrder'))){
$date = date(DATE_RFC2822);
$api = new API();
file_put_contents('debug/debug_info.txt', "New: {$_GET['action']} at {$date}\n", FILE_APPEND | LOCK_EX);
file_put_contents('debug/debug_info.txt', "This is the product " . $api->getOrder());
}
API.php
<?php
class API {
private $order;
private $product_table;
function __construct(){
$this->order = $this->setOrder();
$this->product_table = $this->setProductTable();
}
public function setOrder(){return $this->readJSON();}
public function setProductTable(){return $this->readProductsCSV(__DIR__ . '/../assets/products.csv');}
public function getOrder(){return $this->order;}
public function getProductsTable(){return $this->product_table;}
private function readJSON(){
$stream = fopen('php://input', 'rb');
$json = stream_get_contents($stream);
fclose($stream);
return print_r(json_decode($json, true), true);
}
private function readProductsCSV($csv = '', $delimiter = ','){
if (!file_exists($csv) || !is_readable($csv)){
return "Someone f*cked up -_-";
}
$header = NULL;
$data = array();
if (($handle = fopen($csv, 'r')) !== false){
while (($row = fgetcsv($csv, 100, $delimiter)) !== false){
if (!$header)
$header = $row;
else if($row[0] != ''){
$row = array_merge(array_slice($row,0,2), array_filter(array_slice($row, 2)));
$sku = $row[0];
$data[$sku]['productCode'] = $row[1];
$data[$sku]['Description'] = $row[2];
}
}
fclose($handle);
}
array_change_key_case($data, CASE_LOWER);
return print_r($data, true);
}
}
When I use file_put_contents('debug/debug_info.txt', $api->getOrder()); I get the data correctly ( I have to comment all the product_table parts for it to work tho ).
But I can't get the CSV file no matter what I do.
I've ran file_exists() && is_readable ( and they passed ) but still nothing.
If I declare the function readProductsCSV in the index.php it works.. but it seems using it as a method bugs everything.
Could someone please help me?
Logic bugs:
if (($handle = fopen($csv, 'r')) !== false){
^---your csv file handle
while (($row = fgetcsv($csv, 100, $delimiter)) !== false){
^---your csv filename, which SHOULD be the handle
Since you're trying to use a string as a filehandle, you get a boolean false back from fgetcsv() for failure, that false terminates the while loop, and $data stays an empty array.
I have a SQL file which i created from another database (named as test) on my localhost and now i want to insert this data into another database ( named as server_db) via PHP Script .
I tried and my PHP Script is working fine and creating the tables into server_db database.
But values in those tables are not inserting ..... Please Help
My PHP Code is given below
<?php
class Executer {
public $path="";
public function execute($path){
// MySql connectivity
$link = mysql_connect("localhost","root","");
mysql_select_db("server_db");
//file content
$content = file_get_contents($path);
//remove the comments
$lines = explode("\n",$content);
$content = '';
foreach($lines as $line){
$line = trim($line);
if( $line && !$this->startsWith($line,'--') ){
$content .= $line . "\n";
}
}
//convert data into array of queries
$content = explode(";", $content);
//run the query
$total = $sucess=0;
foreach($content as $command){
if(trim($command)){
$success = (mysql_query($command)==false ? 0 : 1);
}
}
}
public function startsWith($string, $sym_com){
$length = strlen($sym_com);
return (substr($string, 0, $length) === $sym_com);
}
} $path = "C:/xampp/htdocs/final/downloads/server_database_file.sql";
execute($path);
I think you need to check your SQL text file encoding. because the line delimiter for each encoding is not always "\n". You can try change with "\r"
If you on localhost you can use exec function with mysqldump
exec('mysqldump server_database > C:/xampp/htdocs/final/downloads/server_database_file.sql')
Try this. Just wrote it up, realizing I didn't have a function for this. You need to verify that ; is the last character of a line, exploding by ; can lead to false mid-data splits. Below approach simply buffers the lines up until it finds a terminating ;, then inserts them into an array and resets the buffer.
function parse_sql_file($filepath) {
$queries = [];
$sql_query = [];
$lines = file($filepath);
foreach($lines as $line) {
$line = trim($line);
// This is a comment: move on, nothing to see here.
if (substr($line, 0, 2) == '--') continue;
$sql_query[] = $line;
// We found a terminator: do the needful.
if (substr($line, -1) == ';') {
$queries[] = trim( implode("\n", $sql_query) );
$sql_query = [];
}
}
return $queries;
}
$queries = parse_sql_file('my.sql');
var_dump($queries);
I'm trying to build a small CMS using CodeIgniter, and I need to be able to dynamically update some variables within the application/config.php
So far I did:
private function update_file ($file, $var, $var_name) {
$start_tag = "<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');\n";
if (file_exists($file)) {
require_once ($file);
$updated_array = array_merge($$var_name, $var);
$data = $start_tag."\$".$var_name." = ".var_export($updated_array, true).";";
file_put_contents($file, $data);
} else {
return false;
}
}
Everything works just fine! The result in the config.php file will be:
<?php ...;
$config = array (
'base_url' => '',
...
...
);
But what if I would like to maintain the original config.php file format with comments, spaces and
separated declared $config['key'] = 'value' ... ?
Is that possible ?
EDIT:
Thank you for your answers, very precious.
I found a slightly different solution for my needs, performing a preg_replace on the return of file_get_contents() and then write back on the file the new resulting string. File maintains the exact original clean format.
private function update_file ($file, $var, $var_name) {
if (file_exists($file)) {
require_once ($file);
$contents = file_get_contents($file);
$updated_array = array_merge($$var_name, $var);
$search = array();
$replace = array();
foreach($$var_name as $key => $val) {
$pattern = '/\$'.$var_name.'\[\\\''.$key.'\\\'\]\s+=\s+[^\;]+/';
$replace_string = "\$".$var_name."['".$key."'] = ".var_export($updated_array[$key], true);
array_push($search, $pattern);
array_push($replace, $replace_string);
}
$new_contents = preg_replace($search, $replace, $contents);
write_file($file, $new_contents);
}
Maybe it requires some slight performance improvements. But this is my baseline idea.
create the keys with empty values
$config['base_url'] = '';
then set them inside any of your controllers.
This works best if you store the values in the db, and initialize them in MY_Controller.
$this->config->set_item('base_url', 'value');
It is possible. I can't find the code , but once i have written something like that. Whole idea was based on tokenizing template file and substitute values in an array, preserving key order, line numbers and comments from the template.
[+] Found it. It's purpose was to fill values from template that looked like this (it was much bigger of course):
<?php
$_CFG = array(
// DB section
'db_host' => 'localhost',
'db_user' => 'root',
'db_pass' => '',
'db_name' => 'test',
// Site specific
'lang' => array('pl','en'),
'admin' => 'admin#example.com',
);
And the code that was doing all the magic:
$tokens = token_get_all(file_get_contents('tpl/config.php'));
$level = -1;
$buffer = '';
$last_key = 0;
$iteration = 0;
foreach($tokens as $t){
if($t === ')'){
$iteration = 0;
$last_key = 0;
$level--;
}
if(is_array($t)){
if($t[0] == T_ARRAY && strtolower($t[1]) === 'array')
$level++;
if($t[0] == T_CONSTANT_ENCAPSED_STRING){
if($last_key){
if($level){
if(isset($new_config[$last_key][$iteration])){
$buffer .= var_export($new_config[$last_key][$iteration], TRUE);
}
else
$buffer .= 'null';
$iteration++;
}
else{
if(isset($new_config[$last_key]))
$buffer .= var_export($new_config[$last_key], TRUE);
else
$buffer .= 'null';
$last_key = 0;
}
}
else{
$buffer .= $t[1];
$last_key = trim($t[1],"'");
}
}
else
$buffer .= $t[1];
}
else
$buffer .= $t;
}
file_put_contents('config.php',$buffer);
Is it possible to pull track info from an audio stream using PHP? I've done some digging and the closest function I can find is stream_get_transports but my host doesn't support http transports via fsockopen() so I'll have to do some more tinkering to see what else that function returns.
Currently, I'm trying to pull artist and track metadata from an AOL stream.
This is a SHOUTcast stream, and yes it is possible. It has absolutely nothing to do with ID3 tags. I wrote a script awhile ago to do this, but can't find it anymore. Just last week I helped another guy who had a fairly complete script to do the same thing, but I can't just post the source to it, as it isn't mine. I will however get you in touch with him, if you e-mail me at brad#musatcha.com.
Anyway, here's how to do it yourself:
The first thing you need to do is connect to the server directly. Don't use HTTP. Well, you could probably use cURL, but it will likely be much more hassle than its worth. You connect to it with fsockopen() (doc). Make sure to use the correct port. Also note that many web hosts will block a lot of ports, but you can usually use port 80. Fortunately, all of the AOL-hosted SHOUTcast streams use port 80.
Now, make your request just like your client would.
GET /whatever HTTP/1.0
But, before sending <CrLf><CrLf>, include this next header!
Icy-MetaData:1
That tells the server that you want metadata. Now, send your pair of <CrLf>.
Ok, the server will respond with a bunch of headers and then start sending you data. In those headers will be an icy-metaint:8192 or similar. That 8192 is the meta interval. This is important, and really the only value you need. It is usually 8192, but not always, so make sure to actually read this value!
Basically it means, you will get 8192 bytes of MP3 data and then a chunk of meta, followed by 8192 bytes of MP3 data, followed by a chunk of meta.
Read 8192 bytes of data (make sure you are not including the header in this count), discard them, and then read the next byte. This byte is the first byte of meta data, and indicates how long the meta data is. Take the value of this byte (the actual byte with ord() (doc)), and multiply it by 16. The result is the number of bytes to read for metadata. Read those number of bytes into a string variable for you to work with.
Next, trim the value of this variable. Why? Because the string is padded with 0x0 at the end (to make it fit evenly into a multiple of 16 bytes), and trim() (doc) takes care of that for us.
You will be left with something like this:
StreamTitle='Awesome Trance Mix - DI.fm';StreamUrl=''
I'll let you pick your method of choice for parsing this. Personally I'd probably just split with a limit of 2 on ;, but beware of titles that contain ;. I'm not sure what the escape character method is. A little experimentation should help you.
Don't forget to disconnect from the server when you're done with it!
There are lots of SHOUTcast MetaData references out there. This is a good one: http://www.smackfu.com/stuff/programming/shoutcast.html
Check this out: https://gist.github.com/fracasula/5781710
It's a little gist with a PHP function that lets you extract MP3 metadata (StreamTitle) from a streaming URL.
Usually the streaming server puts an icy-metaint header in the response which tells us how often the metadata is sent in the stream. The function checks for that response header and, if present, it replaces the interval parameter with it.
Otherwise the function calls the streaming URL respecting your interval and, if any metadata isn't present, then it tries again through recursion starting from the offset parameter.
<?php
/**
* Please be aware. This gist requires at least PHP 5.4 to run correctly.
* Otherwise consider downgrading the $opts array code to the classic "array" syntax.
*/
function getMp3StreamTitle($streamingUrl, $interval, $offset = 0, $headers = true)
{
$needle = 'StreamTitle=';
$ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36';
$opts = [
'http' => [
'method' => 'GET',
'header' => 'Icy-MetaData: 1',
'user_agent' => $ua
]
];
if (($headers = get_headers($streamingUrl))) {
foreach ($headers as $h) {
if (strpos(strtolower($h), 'icy-metaint') !== false && ($interval = explode(':', $h)[1])) {
break;
}
}
}
$context = stream_context_create($opts);
if ($stream = fopen($streamingUrl, 'r', false, $context)) {
$buffer = stream_get_contents($stream, $interval, $offset);
fclose($stream);
if (strpos($buffer, $needle) !== false) {
$title = explode($needle, $buffer)[1];
return substr($title, 1, strpos($title, ';') - 2);
} else {
return getMp3StreamTitle($streamingUrl, $interval, $offset + $interval, false);
}
} else {
throw new Exception("Unable to open stream [{$streamingUrl}]");
}
}
var_dump(getMp3StreamTitle('http://str30.creacast.com/r101_thema6', 19200));
I hope this helps!
Thanks a lot for the code fra_casula. Here is a slightly simplified version running on PHP <= 5.3 (the original is targeted at 5.4). It also reuses the same connection resource.
I removed the exception because of my own needs, returning false if nothing is found instead.
private function getMp3StreamTitle($steam_url)
{
$result = false;
$icy_metaint = -1;
$needle = 'StreamTitle=';
$ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36';
$opts = array(
'http' => array(
'method' => 'GET',
'header' => 'Icy-MetaData: 1',
'user_agent' => $ua
)
);
$default = stream_context_set_default($opts);
$stream = fopen($steam_url, 'r');
if($stream && ($meta_data = stream_get_meta_data($stream)) && isset($meta_data['wrapper_data'])){
foreach ($meta_data['wrapper_data'] as $header){
if (strpos(strtolower($header), 'icy-metaint') !== false){
$tmp = explode(":", $header);
$icy_metaint = trim($tmp[1]);
break;
}
}
}
if($icy_metaint != -1)
{
$buffer = stream_get_contents($stream, 300, $icy_metaint);
if(strpos($buffer, $needle) !== false)
{
$title = explode($needle, $buffer);
$title = trim($title[1]);
$result = substr($title, 1, strpos($title, ';') - 2);
}
}
if($stream)
fclose($stream);
return $result;
}
This is the C# code for getting the metadata using HttpClient:
public async Task<string> GetMetaDataFromIceCastStream(string url)
{
m_httpClient.DefaultRequestHeaders.Add("Icy-MetaData", "1");
var response = await m_httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
m_httpClient.DefaultRequestHeaders.Remove("Icy-MetaData");
if (response.IsSuccessStatusCode)
{
IEnumerable<string> headerValues;
if (response.Headers.TryGetValues("icy-metaint", out headerValues))
{
string metaIntString = headerValues.First();
if (!string.IsNullOrEmpty(metaIntString))
{
int metadataInterval = int.Parse(metaIntString);
byte[] buffer = new byte[metadataInterval];
using (var stream = await response.Content.ReadAsStreamAsync())
{
int numBytesRead = 0;
int numBytesToRead = metadataInterval;
do
{
int n = stream.Read(buffer, numBytesRead, 10);
numBytesRead += n;
numBytesToRead -= n;
} while (numBytesToRead > 0);
int lengthOfMetaData = stream.ReadByte();
int metaBytesToRead = lengthOfMetaData * 16;
byte[] metadataBytes = new byte[metaBytesToRead];
var bytesRead = await stream.ReadAsync(metadataBytes, 0, metaBytesToRead);
var metaDataString = System.Text.Encoding.UTF8.GetString(metadataBytes);
return metaDataString;
}
}
}
}
return null;
}
UPDATE:
This is an update with a more appropriate solution to the question. The original post is also provided below for information.
The script in this post, after some error correction, works and extracts the stream title using PHP:
PHP script to extract artist & title from Shoutcast/Icecast stream.
I had to make a couple of changes, because the echo statements at the end were throwing an error. I added two print_r() statements after the function, and $argv[1] in the call so you can pass the URL to it from the command line.
<?php
define('CRLF', "\r\n");
class streaminfo{
public $valid = false;
public $useragent = 'Winamp 2.81';
protected $headers = array();
protected $metadata = array();
public function __construct($location){
$errno = $errstr = '';
$t = parse_url($location);
$sock = fsockopen($t['host'], $t['port'], $errno, $errstr, 5);
$path = isset($t['path'])?$t['path']:'/';
if ($sock){
$request = 'GET '.$path.' HTTP/1.0' . CRLF .
'Host: ' . $t['host'] . CRLF .
'Connection: Close' . CRLF .
'User-Agent: ' . $this->useragent . CRLF .
'Accept: */*' . CRLF .
'icy-metadata: 1'.CRLF.
'icy-prebuffer: 65536'.CRLF.
(isset($t['user'])?'Authorization: Basic '.base64_encode($t['user'].':'.$t['pass']).CRLF:'').
'X-TipOfTheDay: Winamp "Classic" rulez all of them.' . CRLF . CRLF;
if (fwrite($sock, $request)){
$theaders = $line = '';
while (!feof($sock)){
$line = fgets($sock, 4096);
if('' == trim($line)){
break;
}
$theaders .= $line;
}
$theaders = explode(CRLF, $theaders);
foreach ($theaders as $header){
$t = explode(':', $header);
if (isset($t[0]) && trim($t[0]) != ''){
$name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
array_shift($t);
$value = trim(implode(':', $t));
if ($value != ''){
if (is_numeric($value)){
$this->headers[$name] = (int)$value;
}else{
$this->headers[$name] = $value;
}
}
}
}
if (!isset($this->headers['icymetaint'])){
$data = ''; $metainterval = 512;
while(!feof($sock)){
$data .= fgetc($sock);
if (strlen($data) >= $metainterval) break;
}
$this->print_data($data);
$matches = array();
preg_match_all('/([\x00-\xff]{2})\x0\x0([a-z]+)=/i', $data, $matches, PREG_OFFSET_CAPTURE);
preg_match_all('/([a-z]+)=([a-z0-9\(\)\[\]., ]+)/i', $data, $matches, PREG_SPLIT_NO_EMPTY);
echo '<pre>';var_dump($matches);echo '</pre>';
$title = $artist = '';
foreach ($matches[0] as $nr => $values){
$offset = $values[1];
$length = ord($values[0]{0}) +
(ord($values[0]{1}) * 256)+
(ord($values[0]{2}) * 256*256)+
(ord($values[0]{3}) * 256*256*256);
$info = substr($data, $offset + 4, $length);
$seperator = strpos($info, '=');
$this->metadata[substr($info, 0, $seperator)] = substr($info, $seperator + 1);
if (substr($info, 0, $seperator) == 'title') $title = substr($info, $seperator + 1);
if (substr($info, 0, $seperator) == 'artist') $artist = substr($info, $seperator + 1);
}
$this->metadata['streamtitle'] = $artist . ' - ' . $title;
}else{
$metainterval = $this->headers['icymetaint'];
$intervals = 0;
$metadata = '';
while(1){
$data = '';
while(!feof($sock)){
$data .= fgetc($sock);
if (strlen($data) >= $metainterval) break;
}
//$this->print_data($data);
$len = join(unpack('c', fgetc($sock))) * 16;
if ($len > 0){
$metadata = str_replace("\0", '', fread($sock, $len));
break;
}else{
$intervals++;
if ($intervals > 100) break;
}
}
$metarr = explode(';', $metadata);
foreach ($metarr as $meta){
$t = explode('=', $meta);
if (isset($t[0]) && trim($t[0]) != ''){
$name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
array_shift($t);
$value = trim(implode('=', $t));
if (substr($value, 0, 1) == '"' || substr($value, 0, 1) == "'"){
$value = substr($value, 1);
}
if (substr($value, -1) == '"' || substr($value, -1) == "'"){
$value = substr($value, 0, -1);
}
if ($value != ''){
$this->metadata[$name] = $value;
}
}
}
}
fclose($sock);
$this->valid = true;
}else echo 'unable to write.';
}else echo 'no socket '.$errno.' - '.$errstr.'.';
print_r($theaders);
print_r($metadata);
}
public function print_data($data){
$data = str_split($data);
$c = 0;
$string = '';
echo "<pre>\n000000 ";
foreach ($data as $char){
$string .= addcslashes($char, "\n\r\0\t");
$hex = dechex(join(unpack('C', $char)));
if ($c % 4 == 0) echo ' ';
if ($c % (4*4) == 0 && $c != 0){
foreach (str_split($string) as $s){
//echo " $string\n";
if (ord($s) < 32 || ord($s) > 126){
echo '\\'.ord($s);
}else{
echo $s;
}
}
echo "\n";
$string = '';
echo str_pad($c, 6, '0', STR_PAD_LEFT).' ';
}
if (strlen($hex) < 1) $hex = '00';
if (strlen($hex) < 2) $hex = '0'.$hex;
echo $hex.' ';
$c++;
}
echo " $string\n</pre>";
}
public function __get($name){
if (isset($this->metadata[$name])){
return $this->metadata[$name];
}
if (isset($this->headers[$name])){
return $this->headers[$name];
}
return null;
}
}
$t = new streaminfo($argv[1]); // get metadata
/*
echo "Meta Interval: ".$t->icymetaint;
echo "\n";
echo 'Current Track: '.$t->streamtitle;
*/
?>
With the updated code, it prints the arrays of header and streamtitle info. If you only want the now_playing track, then comment out the two print_r() statements, and uncomment the echo statements at the end.
#Example: run this command:
php getstreamtitle.php http://162.244.80.118:3066
#and the result is...
Array
(
[0] => HTTP/1.0 200 OK
[1] => icy-notice1:<BR>This stream requires Winamp<BR>
[2] => icy-notice2:SHOUTcast DNAS/posix(linux x64) v2.6.0.750<BR>
[3] => Accept-Ranges:none
[4] => Access-Control-Allow-Origin:*
[5] => Cache-Control:no-cache,no-store,must-revalidate,max-age=0
[6] => Connection:close
[7] => icy-name:
[8] => icy-genre:Old Time Radio
[9] => icy-br:24
[10] => icy-sr:22050
[11] => icy-url:http://horror-theatre.com
[12] => icy-pub:1
[13] => content-type:audio/mpeg
[14] => icy-metaint:8192
[15] => X-Clacks-Overhead:GNU Terry Pratchett
[16] =>
)
StreamTitle='501026TooHotToLive';
Here is the original post using python and vlc
The PHP solution kept searching but never returned a response for me.
This is not PHP as requested, but may help others looking for a way to extract the 'now_playing' info from live streams.
If you only want the 'now_playing' info, you can edit the script to return that.
The python script extracts the metadata (including the 'now_playing' track) using VLC. You need VLC and the python libraries: sys, telnetlib, os, time and socket.
#!/usr/bin/python
# coding: utf-8
import sys, telnetlib, os, time, socket
HOST = "localhost"
password = "admin"
port = "4212"
def check_port():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
res = sock.connect_ex((HOST, int(port)))
sock.close()
return res == 0
def checkstat():
if not check_port():
os.popen('vlc --no-audio --intf telnet --telnet-password admin --quiet 2>/dev/null &')
while not check_port():
time.sleep(.1)
def docmd(cmd):
tn = telnetlib.Telnet(HOST, port)
tn.read_until(b"Password: ")
tn.write(password.encode('utf-8') + b"\n")
tn.read_until(b"> ")
tn.write(cmd.encode('utf-8') + b"\n")
ans=tn.read_until(">".encode("utf-8"))[0:-3]
return(ans)
tn.close()
def nowplaying(playing):
npstart=playing.find('now_playing')
mystr=playing[npstart:]
npend=mystr.find('\n')
return mystr[:npend]
def metadata(playing):
fstr='+----'
mstart=playing.find(fstr)
mend=playing.find(fstr,mstart+len(fstr))
return playing[mstart:mend+len(fstr)]
checkstat()
docmd('add '+sys.argv[1])
playing=""
count=0
while not 'now_playing:' in playing:
time.sleep(.5)
playing=docmd('info')
count+=1
if count>9:
break
if playing == "":
print("--Timeout--")
else:
print(metadata(playing))
docmd('shutdown')
Example, extract metadata from Crypt Theater Station:
./radiometatdata.py http://107.181.227.250:8026
Response:
+----[ Meta data ]
|
| title: *CRYPT THEATER*
| filename: 107.181.227.250:8026
| genre: Old Time Radio
| now_playing: CBS Radio Mystery Theatre - A Ghostly Game of Death
|
+----