Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal : Podcast - Alarm - Timer #67

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions app/libs/LastRSS.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
<?php
/*
======================================================================
lastRSS 0.9.1

Simple yet powerfull PHP class to parse RSS files.

by Vojtech Semecky, webmaster @ webdot . cz

Latest version, features, manual and examples:
http://lastrss.webdot.cz/

----------------------------------------------------------------------
LICENSE

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License (GPL)
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

To read the license please visit http://www.gnu.org/copyleft/gpl.html
======================================================================
*/

/**
* lastRSS
* Simple yet powerfull PHP class to parse RSS files.
*/
class lastRSS {
// -------------------------------------------------------------------
// Public properties
// -------------------------------------------------------------------
var $default_cp = 'UTF-8';
var $CDATA = 'content';
var $cp = '';
var $items_limit = 0;
var $stripHTML = False;
var $date_format = '';

// -------------------------------------------------------------------
// Private variables
// -------------------------------------------------------------------
var $channeltags = array ('title', 'link', 'description', 'language', 'copyright', 'managingEditor', 'webMaster', 'lastBuildDate', 'updated', 'rating', 'docs');
var $itemtags = array('title', 'link', 'description', 'author', 'category', 'comments', 'enclosure', 'guid', 'pubDate', 'source', 'summary');
var $imagetags = array('title', 'url', 'link', 'width', 'height');
var $textinputtags = array('title', 'description', 'name', 'link');

// -------------------------------------------------------------------
// Parse RSS file and returns associative array.
// -------------------------------------------------------------------
function Get ($rss_url) {
// If CACHE ENABLED
if ($this->cache_dir != '') {
$cache_file = $this->cache_dir . '/rsscache_' . md5($rss_url);
$timedif = @(time() - filemtime($cache_file));
if ($timedif < $this->cache_time) {
// cached file is fresh enough, return cached array
$result = unserialize(join('', file($cache_file)));
// set 'cached' to 1 only if cached file is correct
if ($result) $result['cached'] = 1;
} else {
// cached file is too old, create new
$result = $this->Parse($rss_url);
$serialized = serialize($result);
if ($f = @fopen($cache_file, 'w')) {
fwrite ($f, $serialized, strlen($serialized));
fclose($f);
}
if ($result) $result['cached'] = 0;
}
}
// If CACHE DISABLED >> load and parse the file directly
else {
$result = $this->Parse($rss_url);
if ($result) $result['cached'] = 0;
}
// return result
return $result;
}

// -------------------------------------------------------------------
// Modification of preg_match(); return trimed field with index 1
// from 'classic' preg_match() array output
// -------------------------------------------------------------------
function my_preg_match ($pattern, $subject) {
// start regullar expression
preg_match($pattern, $subject, $out);
// if there is some result... process it and return it
if(isset($out['value'])) {
// Process CDATA (if present)
if ($this->CDATA == 'content') { // Get CDATA content (without CDATA tag)
$out['value'] = strtr($out['value'], array('<![CDATA['=>'', ']]>'=>''));
} elseif ($this->CDATA == 'strip') { // Strip CDATA
$out['value'] = strtr($out['value'], array('<![CDATA['=>'', ']]>'=>''));
}

// If code page is set convert character encoding to required
if ($this->cp != '')
//$out[1] = $this->MyConvertEncoding($this->rsscp, $this->cp, $out[1]);
$out['value'] = iconv($this->rsscp, $this->cp.'//TRANSLIT', $out['value']);
// Return result
return trim($out['value']);
} else {
// if there is NO result, return empty string
return '';
}
}

// -------------------------------------------------------------------
// Replace HTML entities &something; by real characters
// -------------------------------------------------------------------
function unhtmlentities ($string) {
// Get HTML entities table
$trans_tbl = get_html_translation_table (HTML_ENTITIES, ENT_QUOTES);
// Flip keys<==>values
$trans_tbl = array_flip ($trans_tbl);
// Add support for &apos; entity (missing in HTML_ENTITIES)
$trans_tbl += array('&apos;' => "'");
// Replace entities by values
return strtr ($string, $trans_tbl);
}

// -------------------------------------------------------------------
// Parse() is private method used by Get() to load and parse RSS file.
// Don't use Parse() in your scripts - use Get($rss_file) instead.
// -------------------------------------------------------------------
function Parse ($rss_url) {
// Open and load RSS file
if ($f = @fopen($rss_url, 'r')) {
$rss_content = '';
while (!feof($f)) {
$rss_content .= fgets($f, 4096);
}
fclose($f);

// Parse document encoding
$result['encoding'] = $this->my_preg_match("'encoding=[\'\"](?P<value>.*?)[\'\"]'si", $rss_content);
// if document codepage is specified, use it
if ($result['encoding'] != '')
{ $this->rsscp = $result['encoding']; } // This is used in my_preg_match()
// otherwise use the default codepage
else
{ $this->rsscp = $this->default_cp; } // This is used in my_preg_match()

// Parse CHANNEL of FEED info
preg_match("'<channel.*?>(.*?)</channel>'si", $rss_content, $out_channel);
if(!$out_channel)
preg_match("'<feed.*?>(.*?)</feed>'si", $rss_content, $out_channel);

foreach($this->channeltags as $channeltag)
{
$temp = $this->my_preg_match("'<$channeltag.*?>(?P<value>.*?)</$channeltag>'si", $out_channel[1]);
if ($temp != '') $result[$channeltag] = $temp; // Set only if not empty
}
// If date_format is specified and lastBuildDate is valid
if ($this->date_format != '' && ((($timestamp = strtotime($result['lastBuildDate'])) !==-1) || ($timestamp = strtotime($result['updated'])) !==-1)) {
// convert lastBuildDate to specified date format
$result['lastBuildDate'] = date($this->date_format, $timestamp);
}

// Parse TEXTINPUT info
preg_match("'<textinput(|[^>]*[^/])>(.*?)</textinput>'si", $rss_content, $out_textinfo);
// This a little strange regexp means:
// Look for tag <textinput> with or without any attributes, but skip truncated version <textinput /> (it's not beggining tag)
if (isset($out_textinfo[2])) {
foreach($this->textinputtags as $textinputtag) {
$temp = $this->my_preg_match("'<$textinputtag.*?>(?P<value>.*?)</$textinputtag>'si", $out_textinfo[2]);
if ($temp != '') $result['textinput_'.$textinputtag] = $temp; // Set only if not empty
}
}
// Parse IMAGE info
preg_match("'<image.*?>(.*?)</image>'si", $rss_content, $out_imageinfo);
if (isset($out_imageinfo[1])) {
foreach($this->imagetags as $imagetag) {
$temp = $this->my_preg_match("'<$imagetag.*?>(?P<value>.*?)</$imagetag>'si", $out_imageinfo[1]);
if ($temp != '') $result['image_'.$imagetag] = $temp; // Set only if not empty
}
}
// Parse ITEMS
preg_match_all("'<item(| .*?)>(.*?)</item>'si", $rss_content, $items);
if(!$items[2])
preg_match_all("'<entry(| .*?)>(.*?)</entry>'si", $rss_content, $items);

$rss_items = $items[2];
$i = 0;
$result['items'] = array(); // create array even if there are no items
foreach($rss_items as $rss_item) {
// If number of items is lower then limit: Parse one item
if ($i < $this->items_limit || $this->items_limit == 0) {
foreach($this->itemtags as $itemtag) {
$temp = '';
//try a self closing tag with ENCLOSURE and LINK
if($itemtag == "enclosure" || $itemtag == "link")
$temp = $this->my_preg_match("'<" . $itemtag . "[^>]+(href|url)=\"(?P<value>[^>\"]+)\"[^>]*type=\"audio[^>]+/>'si", $rss_item);

if($temp == '') //it's not a self closing tag with a link inside
$temp = $this->my_preg_match("'<$itemtag.*?>(?P<value>.*?)</$itemtag>'si", $rss_item);

if ($temp != '')
$result['items'][$i][$itemtag] = $temp; // Set only if not empty
}

// Strip HTML tags and other bullshit from DESCRIPTION
if ($this->stripHTML && $result['items'][$i]['description'])
$result['items'][$i]['description'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['description'])));

// Strip HTML tags and other bullshit from SUMMARY
if ($this->stripHTML && $result['items'][$i]['summary'])
$result['items'][$i]['summary'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['summary'])));

// Strip HTML tags and other bullshit from TITLE
if ($this->stripHTML && $result['items'][$i]['title'])
$result['items'][$i]['title'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['title'])));

// If date_format is specified and pubDate is valid
if ($this->date_format != '' && ((($timestamp = strtotime($result['items'][$i]['pubDate'])) !==-1) || (($timestamp = strtotime($result['items'][$i]['published'])) !==-1))) {
// convert pubDate to specified date format
$result['items'][$i]['pubDate'] = date($this->date_format, $timestamp);
}
// Item counter
$i++;
}
}

$result['items_count'] = $i;
return $result;
}
else // Error in opening return False
{
return False;
}
}
}

?>
71 changes: 68 additions & 3 deletions app/libs/runeaudio.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
// Predefined MPD & SPOP Response messages
// define("MPD_GREETING", "OK MPD 0.18.0\n");
// define("SPOP_GREETING", "spop 0.0.1\n");

function openMpdSocket($path, $type = null)
// connection types: 0 = normal (blocking), 1 = burst mode (blocking), 2 = burst mode 2 (non blocking)
{
Expand Down Expand Up @@ -1177,7 +1176,8 @@ function runelog($title, $data = null, $function_name = null)
} else {
$function_name = '';
}
if ($debug_level !== '0') {
if ($debug_level !== '0')
{
if(is_array($data) OR is_object($data)) {
if (is_array($data)) error_log($function_name.'### '.$title.' ### $data type = array',0);
if (is_object($data)) error_log($function_name.'### '.$title.' ### $data type = object',0);
Expand Down Expand Up @@ -2917,6 +2917,69 @@ function deleteRadio($mpd,$redis,$data)
return $return;
}

// podcast management
function addPodcast($mpd, $redis, $data)
{
if ($data->label !== '' && $data->url !== '') {
//Retrieve podcast information
$rss = new lastRSS;
$rss->cache_dir = '/tmp';
$rss->cache_time = 3600; // one hour

if ($rs = $rss->get($data->url))
{
// store podcast record in redis
//$redis->hSet('podcasts', $data->label, $data->url);
$podcast = null;
$podcast->title = $rs['title'];
$podcast->description = $rs['description'] ?: "";
$podcast->url = $data->url;
$podcast->image = $rs['image_url'];

$redis->hSet('podcasts', $data->label, json_encode($podcast));
//$redis->hSet('podcasts', $data->label, $data->url);
// create new file
$file = '/mnt/MPD/Podcast/'.$data->label.'.pod';
$newpls = "[podcast]\n";
$newpls .= "NumberOfEntries=1\n";
$newpls .= "File1=".$data->url."\n";
$newpls .= "Title1=".$data->label;
// Commit changes to .pod file
$fp = fopen($file, 'w');
$return = fwrite($fp, $newpls);
fclose($fp);
}
else
{
$return = false;
}
} else {
$return = false;
}
return $return;
}

function deletePodcast($mpd,$redis,$data)
{
if ($data->label !== '') {
//debug
runelog('deletePodcast (data)', $data);
// delete .pod file
$label = $data->label;
$file = '/mnt/MPD/Podcast/'.$data->label.'.pod';
runelog('deletePodcast (label)', $label);
runelog('deletePodcast (file)', $file);
$return = unlink($file);
if ($return) {
// delete podcast record in redis
$redis->hDel('podcasts', $label);
}
} else {
$return = false;
}
return $return;
}

function ui_notify($title = null, $text, $type = null, $permanotice = null)
{
if (is_object($permanotice)) {
Expand Down Expand Up @@ -3058,6 +3121,8 @@ function ui_libraryHome($redis)
// Webradios
$webradios = count($redis->hKeys('webradios'));
// runelog('webradios: ',$webradios);
//Podcasts
$podcasts = countDirs('/mnt/MPD/Podcast');
// Dirble
$proxy = $redis->hGetall('proxy');
$dirblecfg = $redis->hGetAll('dirble');
Expand All @@ -3079,7 +3144,7 @@ function ui_libraryHome($redis)
// runelog('bookmarks: ',$bookmarks);
// $jsonHome = json_encode(array_merge($bookmarks, array(0 => array('networkMounts' => $networkmounts)), array(0 => array('USBMounts' => $usbmounts)), array(0 => array('webradio' => $webradios)), array(0 => array('Dirble' => $dirble->amount)), array(0 => array('ActivePlayer' => $activePlayer))));
// $jsonHome = json_encode(array_merge($bookmarks, array(0 => array('networkMounts' => $networkmounts)), array(0 => array('USBMounts' => $usbmounts)), array(0 => array('webradio' => $webradios)), array(0 => array('Spotify' => $spotify)), array(0 => array('Dirble' => $dirble->amount)), array(0 => array('ActivePlayer' => $activePlayer))));
$jsonHome = json_encode(array('bookmarks' => $bookmarks, 'localStorages' => $localStorages, 'networkMounts' => $networkmounts, 'USBMounts' => $usbmounts, 'webradio' => $webradios, 'Spotify' => $spotify, 'Dirble' => $dirble->amount, 'ActivePlayer' => $activePlayer));
$jsonHome = json_encode(array('bookmarks' => $bookmarks, 'localStorages' => $localStorages, 'networkMounts' => $networkmounts, 'USBMounts' => $usbmounts, 'webradio' => $webradios, 'podcast' => $podcasts, 'Spotify' => $spotify, 'Dirble' => $dirble->amount, 'ActivePlayer' => $activePlayer));
// Encode UI response
runelog('libraryHome JSON: ', $jsonHome);
ui_render('library', $jsonHome);
Expand Down
4 changes: 3 additions & 1 deletion app/templates/footer.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@
<?php endif; ?>
<script src="<?=$this->asset('/js/vendor/pnotify.custom.min.js')?>"></script>
<script src="<?=$this->asset('/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js')?>"></script>
<script src="<?=$this->asset('/js/vendor/openwebapp.js')?>"></script>
<script src="<?=$this->asset('/js/vendor/openwebapp.js')?>"></script>
<script src="<?=$this->asset('/js/vendor/jquery.clockpicker.min.js')?>"></script>
<script src="<?=$this->asset('/js/vendor/jquery.bootstrap-touchspin.min.js')?>"></script>
3 changes: 3 additions & 0 deletions app/templates/header.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="msapplication-tap-highlight" content="no" />
<link rel="stylesheet" href="<?=$this->asset('/css/runeui.css')?>">
<link rel="stylesheet" href="<?=$this->asset('/css/jquery.clockpicker.min.css')?>">
<link rel="stylesheet" href="<?=$this->asset('/css/jquery.bootstrap-touchspin.min.css')?>">
<link rel="shortcut icon" href="<?=$this->asset('/img/favicon.ico')?>">
<link rel="apple-touch-icon" sizes="57x57" href="<?=$this->asset('/img/apple-touch-icon-57x57.png')?>">
<link rel="apple-touch-icon" sizes="114x114" href="<?=$this->asset('/img/apple-touch-icon-114x114.png')?>">
Expand Down Expand Up @@ -72,6 +74,7 @@
<ul class="dropdown-menu" role="menu" aria-labelledby="menu-settings">
<li class="<?=$this->uri(1, '', 'active')?>"><a href="/"><i class="fa fa-play"></i> Playback</a></li>
<li class="<?=$this->uri(1, 'sources', 'active')?>"><a href="/sources/"><i class="fa fa-folder-open"></i> Sources</a></li>
<li class="<?=$this->uri(1, 'wakeup', 'active')?>"><a href="/wakeup/"><i class="fa fa-bell"></i> Wake-up !</a></li>
<li class="<?=$this->uri(1, 'mpd', 'active')?>"><a href="/mpd/"><i class="fa fa-cogs"></i> MPD</a></li>
<li class="<?=$this->uri(1, 'settings', 'active')?>"><a href="/settings/"><i class="fa fa-wrench"></i> Settings</a></li>
<li class="<?=$this->uri(1, 'network', 'active')?>"><a href="/network/"><i class="fa fa-sitemap"></i> Network</a></li>
Expand Down
Loading