Skip to content

Instantly share code, notes, and snippets.

@ePirat
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ePirat/58f3f00a47dc3ea21880 to your computer and use it in GitHub Desktop.
Save ePirat/58f3f00a47dc3ea21880 to your computer and use it in GitHub Desktop.
Ice PlaylistParser
#!/usr/bin/php
<?php
if (php_sapi_name() == "cli") {
// If this is executed via CLI, parse options and stuff
// Helper
function report_error($e) {
// Get the current script name
$our_name = (($v =strrchr(__FILE__, DIRECTORY_SEPARATOR)) == FALSE)
? __FILE__
: $v;
$our_name = substr($our_name, 1, (strrpos($our_name, '.')-1));
echo("ERROR: {$e}\n\n");
echo("Syntax:\t{$our_name} -i <Input File> [OPTIONS]\n");
echo("Options:\n");
echo("\t-m\tLimit output to specified mountpoint names (comma separated)\n");
echo("\t-l\tThe locale to use for time parsing\n");
echo("\t \t(should be the same that Icecast is using)\n");
echo("\t-v\tVerbose output\n");
die();
}
// Options
$shortopts = "i:"; // Input Filename, use '-' for stdin
$shortopts .= "m:"; // Limit output to specified mountspoints (comma separated)
$shortopts .= "l:"; // The locale to use, defaults to the systems locale
$shortopts .= "v"; // Verbose output
$options = getopt($shortopts);
$verbose = isset($options['v']);
$infile = (empty($options['i']))
? report_error("No input file given!")
: $options['i'];
$mounts = (empty($options['m']))
? NULL
: explode(',', $options['m']);
$locale = (empty($options['l']))
? NULL
: $options['l'];
// Gather Info
try {
$parser = new PlaylistParser($locale, $infile);
} catch (PlaylistParserException $e) {
if ($verbose)
report_error("[{$e->getCode()}] {$e->getMessage()}\n{$e->getDetails()}");
else
report_error("[{$e->getCode()}] {$e->getMessage()}");
}
$list = $parser->getPlaylistByMount($mounts, true);
// Clear up the parser, as it has some big internal array
unset($parser);
// Iterate over the mounts
foreach ($list as $m => $v) {
echo("\nMountpoint Stats for: {$m}\n");
// Iterate over the days
foreach ($v as $key => $value) {
$hours = 0;
$songs = 0;
echo("Date: {$key}\n");
// Iterate over the hours
foreach ($value as $hour => $logs) {
$hours++;
$count = count($logs);
$songs += $count;
echo("{$hour}:00 - {$count} songs played!\n");
}
$avg = ($songs/$hours);
echo("Average: {$avg} songs per hour!\n");
}
}
}
class PlaylistParserException extends Exception
{
var $details;
// Redefine the exception so message isn't optional
public function __construct($message, $code, $details = '') {
$this->details = $details;
parent::__construct($message, $code, NULL);
}
public function getDetails()
{
return $this->details;
}
// custom string representation of object
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
}
/**
* Icecast Playlist Log Parser
* Expected Input format example:
* 26/Feb/2015:10:05:45 +0000|/radio|0| - Definitive
*/
class PlaylistParser
{
var $playlist;
public function __construct($locale = NULL, $file = NULL)
{
// Initialize the playlist array
$this->playlist = array();
// Try to set the right locale
if (setlocale(LC_TIME, $locale) === FALSE) {
// There was an error changing the locale
throw new PlaylistParserException("Failed changing locale to '{$locale}'", 1);
}
// If no file given, we are done here
if (is_null($file))
return;
// If a file is fiven, parse it
$this->parseFile($file);
}
/* Parses an Icecast playlist file
* This does the same as providing a filename when
* initializing the Object
*/
public function parseFile($file)
{
// Keep track of line number for error reporting
$lineno = 1;
$handle = @fopen($file, "r");
if ($handle) {
while (($line = fgets($handle)) !== FALSE) {
// Parse each line
$line = $this->_parseLine($line, $lineno);
// Add them to the final array
$this->playlist[] = $line;
// Finally, count up the line
$lineno++;
}
//print_r($this->playlist);
} else {
// If we can't open the file, throw a exception
$e = (is_null($e = error_get_last())) ? '' : $e['message'];
throw new PlaylistParserException("Failed opening playlist file '{$file}'", 2, $e);
}
}
private function _parseLine($line, $lineno)
{
// Parse the string into it's components
list($time, $mount, $listeners, $meta) = explode('|', $line, 4);
// Check if all compinents are there
if (!isset($time, $mount, $listeners, $meta)) {
throw new PlaylistParserException("Failed parsing playlist file: " .
"Invalid Argument Number (line {$lineno})", 11);
}
// Parse the Date
$utc = new DateTimeZone('UTC');
$date = DateTime::createFromFormat("d/M/Y:H:i:s O", $time, $utc);
// Check if parsing failed
if ($t === FALSE) {
throw new PlaylistParserException("Failed parsing playlist file: " .
"Invalid Timestamp (line {$lineno})", 10);
}
// Construct return array
return array('date' => $date, 'mount' => $mount, 'listeners' => $listeners, 'meta' => $meta);
}
public function getPlaylistByMount($mount = NULL, $group_hours = FALSE)
{
if (count($this->playlist) === 0) {
return FALSE;
}
$arr = array();
// Make an array with mount as index
foreach ($this->playlist as $k => $v) {
if ($group_hours) {
// Group by Date and Hours of Day
$datestring = $v['date']->format('Y-m-d');
$hour = $v['date']->format('H');
$arr[$v['mount']][$datestring][$hour][] = $v;
} else {
$arr[$v['mount']][] = $v;
}
}
// Make $mount an array in case it is a string
$mount = (is_string($mount)) ? array($mount) : $mount;
return (is_null($mount))
? $arr
: array_intersect_key($arr, array_flip($mount));
}
}
?>
$ ./parse_playlist.php -i ./playlist.log
Mountpoint Stats for: /radio
Date: 2015-02-26
10:00 - 12 songs played!
11:00 - 16 songs played!
12:00 - 13 songs played!
Average: 13.666666666667 songs per hour!
Mountpoint Stats for: /foooo
Date: 2015-02-26
12:00 - 1 songs played!
Average: 1 songs per hour!
@ePirat
Copy link
Author

ePirat commented Feb 27, 2015

Syntax: parse_playlist -i <Input File> [OPTIONS]
Options:
    -m  Limit output to specified mountpoint names (comma separated)
    -l  The locale to use for time parsing
        (should be the same that Icecast is using)
    -v  Verbose output

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment