Merge branch 'devel' of dev.sourcefabric.org:airtime into devel
Conflicts: airtime_mvc/application/controllers/PreferenceController.php airtime_mvc/application/models/Preference.php
This commit is contained in:
commit
05dc48fa71
4
CREDITS
4
CREDITS
|
@ -2,6 +2,10 @@
|
|||
CREDITS
|
||||
=======
|
||||
|
||||
Version 1.9.0
|
||||
-------------
|
||||
Same as previous version.
|
||||
|
||||
Version 1.8.2
|
||||
-------------
|
||||
Welcome to James Moon!
|
||||
|
|
|
@ -54,7 +54,7 @@ class ApiController extends Zend_Controller_Action
|
|||
* Allows remote client to download requested media file.
|
||||
*
|
||||
* @return void
|
||||
* The given value increased by the increment amount.
|
||||
*
|
||||
*/
|
||||
public function getMediaAction()
|
||||
{
|
||||
|
@ -65,7 +65,7 @@ class ApiController extends Zend_Controller_Action
|
|||
$this->_helper->viewRenderer->setNoRender(true);
|
||||
|
||||
$api_key = $this->_getParam('api_key');
|
||||
$downlaod = $this->_getParam('download');
|
||||
$download = ("true" == $this->_getParam('download'));
|
||||
|
||||
if(!in_array($api_key, $CC_CONFIG["apiKey"]))
|
||||
{
|
||||
|
@ -87,7 +87,6 @@ class ApiController extends Zend_Controller_Action
|
|||
exit;
|
||||
}
|
||||
|
||||
|
||||
// possibly use fileinfo module here in the future.
|
||||
// http://www.php.net/manual/en/book.fileinfo.php
|
||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
|
@ -96,7 +95,12 @@ class ApiController extends Zend_Controller_Action
|
|||
else if ($ext == "mp3")
|
||||
header("Content-Type: audio/mpeg");
|
||||
if ($download){
|
||||
header('Content-Disposition: attachment; filename="'.$media->getName().'"');
|
||||
//path_info breaks up a file path into seperate pieces of informaiton.
|
||||
//We just want the basename which is the file name with the path
|
||||
//information stripped away. We are using Content-Disposition to specify
|
||||
//to the browser what name the file should be saved as.
|
||||
$path_parts = pathinfo($media->getPropelOrm()->getDbFilepath());
|
||||
header('Content-Disposition: attachment; filename="'.$path_parts['basename'].'"');
|
||||
}
|
||||
header("Content-Length: " . filesize($filepath));
|
||||
|
||||
|
@ -408,7 +412,8 @@ class ApiController extends Zend_Controller_Action
|
|||
public function reloadMetadataAction() {
|
||||
global $CC_CONFIG;
|
||||
|
||||
$api_key = $this->_getParam('api_key');
|
||||
$request = $this->getRequest();
|
||||
$api_key = $request->getParam('api_key');
|
||||
if (!in_array($api_key, $CC_CONFIG["apiKey"]))
|
||||
{
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
|
@ -416,8 +421,16 @@ class ApiController extends Zend_Controller_Action
|
|||
exit;
|
||||
}
|
||||
|
||||
$md = $this->_getParam('md');
|
||||
$mode = $this->_getParam('mode');
|
||||
$mode = $request->getParam('mode');
|
||||
$params = $request->getParams();
|
||||
|
||||
$md = array();
|
||||
//extract all file metadata params from the request.
|
||||
foreach ($params as $key => $value) {
|
||||
if (preg_match('/^MDATA_KEY/', $key)) {
|
||||
$md[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mode == "create") {
|
||||
$md5 = $md['MDATA_KEY_MD5'];
|
||||
|
|
|
@ -28,6 +28,7 @@ class LibraryController extends Zend_Controller_Action
|
|||
$this->view->headScript()->appendFile($baseUrl.'/js/jplayer/jquery.jplayer.min.js');
|
||||
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/js/jquery.dataTables.js','text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.pluginAPI.js','text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.fnSetFilteringDelay.js','text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/library/library.js','text/javascript');
|
||||
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/library/advancedsearch.js','text/javascript');
|
||||
|
||||
|
@ -166,7 +167,7 @@ class LibraryController extends Zend_Controller_Action
|
|||
|
||||
$data = $file->getMetadata();
|
||||
|
||||
RabbitMq::SendFileMetaData($data);
|
||||
RabbitMq::SendMessageToMediaMonitor("md_update", $data);
|
||||
|
||||
$this->_helper->redirector('index');
|
||||
}
|
||||
|
|
|
@ -113,8 +113,8 @@ class PlaylistController extends Zend_Controller_Action
|
|||
$this->changePlaylist($pl_id);
|
||||
|
||||
$pl = $this->getPlaylist();
|
||||
$title = $pl->getPLMetaData(UI_MDATA_KEY_TITLE);
|
||||
$desc = $pl->getPLMetaData(UI_MDATA_KEY_DESCRIPTION);
|
||||
$title = $pl->getPLMetaData("dc:title");
|
||||
$desc = $pl->getPLMetaData("dc:description");
|
||||
|
||||
$data = array( 'title' => $title, 'description' => $desc);
|
||||
$form->populate($data);
|
||||
|
@ -130,7 +130,7 @@ class PlaylistController extends Zend_Controller_Action
|
|||
$pl->setName($title);
|
||||
|
||||
if(isset($description)) {
|
||||
$pl->setPLMetaData(UI_MDATA_KEY_DESCRIPTION, $description);
|
||||
$pl->setPLMetaData("dc:description", $description);
|
||||
}
|
||||
|
||||
$this->view->pl = $pl;
|
||||
|
|
|
@ -19,23 +19,24 @@ class PreferenceController extends Zend_Controller_Action
|
|||
|
||||
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/preferences/preferences.js','text/javascript');
|
||||
$this->view->statusMsg = "";
|
||||
|
||||
|
||||
$form = new Application_Form_Preferences();
|
||||
|
||||
|
||||
if ($request->isPost()) {
|
||||
|
||||
|
||||
if ($form->isValid($request->getPost())) {
|
||||
|
||||
$values = $form->getValues();
|
||||
|
||||
Application_Model_Preference::SetHeadTitle($values["preferences_general"]["stationName"], $this->view);
|
||||
Application_Model_Preference::SetDefaultFade($values["preferences_general"]["stationDefaultFade"]);
|
||||
|
||||
Application_Model_Preference::SetHeadTitle($values["preferences_general"]["stationName"], $this->view);
|
||||
Application_Model_Preference::SetDefaultFade($values["preferences_general"]["stationDefaultFade"]);
|
||||
Application_Model_Preference::SetStreamLabelFormat($values["preferences_general"]["streamFormat"]);
|
||||
Application_Model_Preference::SetAllow3rdPartyApi($values["preferences_general"]["thirdPartyApi"]);
|
||||
Application_Model_Preference::SetWatchedDirectory($values["preferences_general"]["watchedFolder"]);
|
||||
|
||||
Application_Model_Preference::SetDoSoundCloudUpload($values["preferences_soundcloud"]["UseSoundCloud"]);
|
||||
Application_Model_Preference::SetDoSoundCloudUpload($values["preferences_soundcloud"]["UseSoundCloud"]);
|
||||
Application_Model_Preference::SetSoundCloudUser($values["preferences_soundcloud"]["SoundCloudUser"]);
|
||||
Application_Model_Preference::SetSoundCloudPassword($values["preferences_soundcloud"]["SoundCloudPassword"]);
|
||||
Application_Model_Preference::SetSoundCloudPassword($values["preferences_soundcloud"]["SoundCloudPassword"]);
|
||||
Application_Model_Preference::SetSoundCloudTags($values["preferences_soundcloud"]["SoundCloudTags"]);
|
||||
Application_Model_Preference::SetSoundCloudGenre($values["preferences_soundcloud"]["SoundCloudGenre"]);
|
||||
Application_Model_Preference::SetSoundCloudTrackType($values["preferences_soundcloud"]["SoundCloudTrackType"]);
|
||||
|
@ -54,11 +55,13 @@ class PreferenceController extends Zend_Controller_Action
|
|||
Application_Model_Preference::SetStationDescription($values["preferences_support"]["Description"]);
|
||||
Application_Model_Preference::SetStationLogo($imagePath);
|
||||
|
||||
$data = array();
|
||||
$data["directory"] = $values["preferences_general"]["watchedFolder"];
|
||||
RabbitMq::SendMessageToMediaMonitor("new_watch", $data);
|
||||
$this->view->statusMsg = "<div class='success'>Preferences updated.</div>";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->supportFeedback = Application_Model_Preference::GetSupportFeedback();
|
||||
$logo = Application_Model_Preference::GetStationLogo();
|
||||
if($logo){
|
||||
|
|
|
@ -33,15 +33,15 @@ class Application_Form_GeneralPreferences extends Zend_Form_SubForm
|
|||
'label' => 'Default Fade:',
|
||||
'required' => false,
|
||||
'filters' => array('StringTrim'),
|
||||
'validators' => array(array('regex', false,
|
||||
array('/^[0-2][0-3]:[0-5][0-9]:[0-5][0-9](\.\d{1,6})?$/',
|
||||
'validators' => array(array('regex', false,
|
||||
array('/^[0-2][0-3]:[0-5][0-9]:[0-5][0-9](\.\d{1,6})?$/',
|
||||
'messages' => 'enter a time 00:00:00{.000000}'))),
|
||||
'value' => $defaultFade,
|
||||
'decorators' => array(
|
||||
'ViewHelper'
|
||||
)
|
||||
));
|
||||
|
||||
|
||||
$stream_format = new Zend_Form_Element_Radio('streamFormat');
|
||||
$stream_format->setLabel('Stream Label:');
|
||||
$stream_format->setMultiOptions(array("Artist - Title",
|
||||
|
@ -58,6 +58,18 @@ class Application_Form_GeneralPreferences extends Zend_Form_SubForm
|
|||
$third_party_api->setValue(Application_Model_Preference::GetAllow3rdPartyApi());
|
||||
$third_party_api->setDecorators(array('ViewHelper'));
|
||||
$this->addElement($third_party_api);
|
||||
|
||||
//Default station fade
|
||||
$this->addElement('text', 'watchedFolder', array(
|
||||
'class' => 'input_text',
|
||||
'label' => 'WatchedFolder:',
|
||||
'required' => false,
|
||||
'filters' => array('StringTrim'),
|
||||
'value' => Application_Model_Preference::GetWatchedDirectory(),
|
||||
'decorators' => array(
|
||||
'ViewHelper'
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -127,5 +127,35 @@ class DateHelper
|
|||
$explode = explode(" ", $p_timestamp);
|
||||
return $explode[1];
|
||||
}
|
||||
|
||||
/* Given a track length in the format HH:MM:SS.mm, we want to
|
||||
* convert this to seconds. This is useful for Liquidsoap which
|
||||
* likes input parameters give in seconds.
|
||||
* For example, 00:06:31.444, should be converted to 391.444 seconds
|
||||
* @param int $p_time
|
||||
* The time interval in format HH:MM:SS.mm we wish to
|
||||
* convert to seconds.
|
||||
* @return int
|
||||
* The input parameter converted to seconds.
|
||||
*/
|
||||
public static function calculateLengthInSeconds($p_time){
|
||||
|
||||
if (2 !== substr_count($p_time, ":")){
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (1 === substr_count($p_time, ".")){
|
||||
list($hhmmss, $ms) = explode(".", $p_time);
|
||||
} else {
|
||||
$hhmmss = $p_time;
|
||||
$ms = 0;
|
||||
}
|
||||
|
||||
list($hours, $minutes, $seconds) = explode(":", $hhmmss);
|
||||
|
||||
$totalSeconds = $hours*3600 + $minutes*60 + $seconds + $ms/1000;
|
||||
|
||||
return $totalSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -393,7 +393,7 @@ class Playlist {
|
|||
->orderByDbPosition()
|
||||
->filterByDbPlaylistId($this->id)
|
||||
->find();
|
||||
|
||||
|
||||
$i = 0;
|
||||
$offset = 0;
|
||||
foreach ($rows as $row) {
|
||||
|
@ -502,7 +502,7 @@ class Playlist {
|
|||
}
|
||||
|
||||
$metadata = $media->getMetadata();
|
||||
$length = $metadata["dcterms:extent"];
|
||||
$length = $metadata['MDATA_KEY_DURATION'];
|
||||
|
||||
if (!is_null($p_clipLength)) {
|
||||
$length = $p_clipLength;
|
||||
|
|
|
@ -36,7 +36,7 @@ class Application_Model_Preference
|
|||
else if(is_null($id)) {
|
||||
$sql = "INSERT INTO cc_pref (keystr, valstr)"
|
||||
." VALUES ('$key', '$value')";
|
||||
}
|
||||
}
|
||||
else {
|
||||
$sql = "INSERT INTO cc_pref (subjid, keystr, valstr)"
|
||||
." VALUES ($id, '$key', '$value')";
|
||||
|
@ -188,6 +188,7 @@ class Application_Model_Preference
|
|||
return $val;
|
||||
}
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
|
||||
public static function SetPhone($phone){
|
||||
Application_Model_Preference::SetValue("phone", $phone);
|
||||
|
@ -350,5 +351,16 @@ class Application_Model_Preference
|
|||
return Application_Model_Preference::GetValue("remindme");
|
||||
}
|
||||
|
||||
=======
|
||||
|
||||
public static function SetWatchedDirectory($directory) {
|
||||
Application_Model_Preference::SetValue("watched_directory", $directory);
|
||||
}
|
||||
|
||||
public static function GetWatchedDirectory() {
|
||||
return Application_Model_Preference::GetValue("watched_directory");
|
||||
}
|
||||
|
||||
>>>>>>> 898cdc64dc65c03d2ed6e3f3344b273df7c0d201
|
||||
}
|
||||
|
||||
|
|
|
@ -40,10 +40,12 @@ class RabbitMq
|
|||
}
|
||||
}
|
||||
|
||||
public static function SendFileMetaData($md)
|
||||
public static function SendMessageToMediaMonitor($event_type, $md)
|
||||
{
|
||||
global $CC_CONFIG;
|
||||
|
||||
$md["event_type"] = $event_type;
|
||||
|
||||
$conn = new AMQPConnection($CC_CONFIG["rabbitmq"]["host"],
|
||||
$CC_CONFIG["rabbitmq"]["port"],
|
||||
$CC_CONFIG["rabbitmq"]["user"],
|
||||
|
|
|
@ -184,7 +184,9 @@ class ScheduleGroup {
|
|||
." st.cue_out,"
|
||||
." st.clip_length,"
|
||||
." st.fade_in,"
|
||||
." st.fade_out"
|
||||
." st.fade_out,"
|
||||
." st.starts,"
|
||||
." st.ends"
|
||||
." FROM $CC_CONFIG[scheduleTable] as st"
|
||||
." LEFT JOIN $CC_CONFIG[showInstances] as si"
|
||||
." ON st.instance_id = si.id"
|
||||
|
@ -676,7 +678,7 @@ class Schedule {
|
|||
$timestamp = strtotime($start);
|
||||
$playlists[$pkey]['source'] = "PLAYLIST";
|
||||
$playlists[$pkey]['x_ident'] = $dx['group_id'];
|
||||
$playlists[$pkey]['subtype'] = '1'; // Just needs to be between 1 and 4 inclusive
|
||||
//$playlists[$pkey]['subtype'] = '1'; // Just needs to be between 1 and 4 inclusive
|
||||
$playlists[$pkey]['timestamp'] = $timestamp;
|
||||
$playlists[$pkey]['duration'] = $dx['clip_length'];
|
||||
$playlists[$pkey]['played'] = '0';
|
||||
|
@ -696,27 +698,24 @@ class Schedule {
|
|||
$scheduleGroup = new ScheduleGroup($playlist["schedule_id"]);
|
||||
$items = $scheduleGroup->getItems();
|
||||
$medias = array();
|
||||
$playlist['subtype'] = '1';
|
||||
foreach ($items as $item)
|
||||
{
|
||||
$storedFile = StoredFile::Recall($item["file_id"]);
|
||||
$uri = $storedFile->getFileUrl();
|
||||
|
||||
// For pypo, a cueout of zero means no cueout
|
||||
$cueOut = "0";
|
||||
if (Schedule::TimeDiff($item["cue_out"], $item["clip_length"]) > 0.001) {
|
||||
$cueOut = Schedule::WallTimeToMillisecs($item["cue_out"]);
|
||||
}
|
||||
$medias[] = array(
|
||||
$starts = Schedule::AirtimeTimeToPypoTime($item["starts"]);
|
||||
$medias[$starts] = array(
|
||||
'row_id' => $item["id"],
|
||||
'id' => $storedFile->getGunid(),
|
||||
'uri' => $uri,
|
||||
'fade_in' => Schedule::WallTimeToMillisecs($item["fade_in"]),
|
||||
'fade_out' => Schedule::WallTimeToMillisecs($item["fade_out"]),
|
||||
'fade_cross' => 0,
|
||||
'cue_in' => Schedule::WallTimeToMillisecs($item["cue_in"]),
|
||||
'cue_out' => $cueOut,
|
||||
'export_source' => 'scheduler'
|
||||
'cue_in' => DateHelper::CalculateLengthInSeconds($item["cue_in"]),
|
||||
'cue_out' => DateHelper::CalculateLengthInSeconds($item["cue_out"]),
|
||||
'export_source' => 'scheduler',
|
||||
'start' => $starts,
|
||||
'end' => Schedule::AirtimeTimeToPypoTime($item["ends"])
|
||||
);
|
||||
}
|
||||
$playlist['medias'] = $medias;
|
||||
|
|
|
@ -63,6 +63,10 @@ class StoredFile {
|
|||
return $this->_file->getDbFtype();
|
||||
}
|
||||
|
||||
public function getPropelOrm(){
|
||||
return $this->_file;
|
||||
}
|
||||
|
||||
public function setFormat($p_format)
|
||||
{
|
||||
$this->_file->setDbFtype($p_format);
|
||||
|
|
|
@ -73,6 +73,19 @@
|
|||
</ul>
|
||||
<?php endif; ?>
|
||||
</dd>
|
||||
<dt id="watchedFolder-label" class="block-display">
|
||||
<label class="required" for="watchedFolder"><?php echo $this->element->getElement('watchedFolder')->getLabel() ?></label>
|
||||
</dt>
|
||||
<dd id="watchedFolder-element" class="block-display">
|
||||
<?php echo $this->element->getElement('watchedFolder') ?>
|
||||
<?php if($this->element->getElement('watchedFolder')->hasErrors()) : ?>
|
||||
<ul class='errors'>
|
||||
<?php foreach($this->element->getElement('watchedFolder')->getMessages() as $error): ?>
|
||||
<li><?php echo $error; ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
</fieldset>
|
||||
|
|
|
@ -175,5 +175,5 @@ $(document).ready(function() {
|
|||
"oLanguage": {
|
||||
"sSearch": ""
|
||||
}
|
||||
});
|
||||
}).fnSetFilteringDelay(350);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
jQuery.fn.dataTableExt.oApi.fnSetFilteringDelay = function ( oSettings, iDelay ) {
|
||||
/*
|
||||
* Inputs: object:oSettings - dataTables settings object - automatically given
|
||||
* integer:iDelay - delay in milliseconds
|
||||
* Usage: $('#example').dataTable().fnSetFilteringDelay(250);
|
||||
* Author: Zygimantas Berziunas (www.zygimantas.com) and Allan Jardine
|
||||
* License: GPL v2 or BSD 3 point style
|
||||
* Contact: zygimantas.berziunas /AT\ hotmail.com
|
||||
*/
|
||||
var
|
||||
_that = this,
|
||||
iDelay = (typeof iDelay == 'undefined') ? 250 : iDelay;
|
||||
|
||||
this.each( function ( i ) {
|
||||
$.fn.dataTableExt.iApiIndex = i;
|
||||
var
|
||||
$this = this,
|
||||
oTimerId = null,
|
||||
sPreviousSearch = null,
|
||||
anControl = $( 'input', _that.fnSettings().aanFeatures.f );
|
||||
|
||||
anControl.unbind( 'keyup' ).bind( 'keyup', function() {
|
||||
var $$this = $this;
|
||||
|
||||
if (sPreviousSearch === null || sPreviousSearch != anControl.val()) {
|
||||
window.clearTimeout(oTimerId);
|
||||
sPreviousSearch = anControl.val();
|
||||
oTimerId = window.setTimeout(function() {
|
||||
$.fn.dataTableExt.iApiIndex = i;
|
||||
_that.fnFilter( anControl.val() );
|
||||
}, iDelay);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
} );
|
||||
return this;
|
||||
}
|
|
@ -14,7 +14,7 @@ echo -e "\n******************************** Install Begin **********************
|
|||
echo -e "\n*** Creating Pypo User ***"
|
||||
python ${SCRIPTPATH}/../python_apps/create-pypo-user.py
|
||||
|
||||
php ${SCRIPTPATH}/airtime-install.php $@
|
||||
php ${SCRIPTPATH}/include/airtime-install.php $@
|
||||
|
||||
echo -e "\n*** Pypo Installation ***"
|
||||
python ${SCRIPTPATH}/../python_apps/pypo/install/pypo-install.py
|
||||
|
@ -26,6 +26,7 @@ echo -e "\n*** Media Monitor Installation ***"
|
|||
python ${SCRIPTPATH}/../python_apps/media-monitor/install/media-monitor-install.py
|
||||
|
||||
sleep 4
|
||||
echo -e "\n*** Verifying your system environment ***"
|
||||
airtime-check-system
|
||||
|
||||
echo -e "\n******************************* Install Complete *******************************"
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
#Cause bash script to exit if any of the installers
|
||||
#return with a non-zero return value.
|
||||
set -e
|
||||
|
||||
# Absolute path to this script
|
||||
SCRIPT=`readlink -f $0`
|
||||
# Absolute directory this script is in
|
||||
|
@ -7,8 +11,6 @@ SCRIPTPATH=`dirname $SCRIPT`
|
|||
|
||||
echo -e "\n******************************* Uninstall Begin ********************************"
|
||||
|
||||
php ${SCRIPTPATH}/airtime-uninstall.php
|
||||
|
||||
echo -e "\n*** Uninstalling Pypo ***"
|
||||
python ${SCRIPTPATH}/../python_apps/pypo/install/pypo-uninstall.py
|
||||
|
||||
|
@ -21,6 +23,9 @@ python ${SCRIPTPATH}/../python_apps/media-monitor/install/media-monitor-uninstal
|
|||
echo -e "\n*** Removing Pypo User ***"
|
||||
python ${SCRIPTPATH}/../python_apps/remove-pypo-user.py
|
||||
|
||||
php ${SCRIPTPATH}/include/airtime-uninstall.php
|
||||
|
||||
|
||||
echo -e "\n****************************** Uninstall Complete ******************************\n"
|
||||
echo "NOTE: To fully remove all Airtime files, you will also have to manually delete"
|
||||
echo " the directories '/srv/airtime'(default storage location of media files)"
|
||||
|
|
|
@ -27,6 +27,7 @@ class AirtimeIni
|
|||
const CONF_FILE_RECORDER = "/etc/airtime/recorder.cfg";
|
||||
const CONF_FILE_LIQUIDSOAP = "/etc/airtime/liquidsoap.cfg";
|
||||
const CONF_FILE_MEDIAMONITOR = "/etc/airtime/media-monitor.cfg";
|
||||
const CONF_FILE_MONIT = "/etc/monit/conf.d/airtime-monit.cfg";
|
||||
|
||||
public static function IniFilesExist()
|
||||
{
|
||||
|
@ -75,10 +76,21 @@ class AirtimeIni
|
|||
exit(1);
|
||||
}
|
||||
if (!copy(__DIR__."/../../python_apps/media-monitor/media-monitor.cfg", AirtimeIni::CONF_FILE_MEDIAMONITOR)){
|
||||
echo "Could not copy MediaMonitor.cfg to /etc/airtime/. Exiting.";
|
||||
echo "Could not copy media-monitor.cfg to /etc/airtime/. Exiting.";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static function CreateMonitFile(){
|
||||
if (!copy(__DIR__."/../../python_apps/monit/airtime-monit.cfg", AirtimeIni::CONF_FILE_MONIT)){
|
||||
echo "Could not copy airtime-monit.cfg to /etc/monit/conf.d/. Exiting.";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static function RemoveMonitFile(){
|
||||
@unlink("/etc/monit/conf.d/airtime-monit.cfg");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function removes /etc/airtime and the configuration
|
||||
|
@ -187,7 +199,6 @@ class AirtimeIni
|
|||
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_PYPO, 'api_key', "'$api_key'");
|
||||
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_RECORDER, 'api_key', "'$api_key'");
|
||||
AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_MEDIAMONITOR, 'api_key', "'$api_key'");
|
||||
AirtimeIni::UpdateIniValue(AirtimeInstall::CONF_DIR_WWW.'/build/build.properties', 'project.home', AirtimeInstall::CONF_DIR_WWW);
|
||||
}
|
||||
|
||||
public static function ReadPythonConfig($p_filename)
|
||||
|
|
|
@ -290,7 +290,7 @@ class AirtimeInstall
|
|||
|
||||
public static function DeleteFilesRecursive($p_path)
|
||||
{
|
||||
$command = "rm -rf $p_path";
|
||||
$command = "rm -rf \"$p_path\"";
|
||||
exec($command);
|
||||
}
|
||||
|
||||
|
@ -336,7 +336,7 @@ class AirtimeInstall
|
|||
public static function UninstallPhpCode()
|
||||
{
|
||||
echo "* Removing PHP code from ".AirtimeInstall::CONF_DIR_WWW.PHP_EOL;
|
||||
exec("rm -rf ".AirtimeInstall::CONF_DIR_WWW);
|
||||
exec('rm -rf "'.AirtimeInstall::CONF_DIR_WWW.'"');
|
||||
}
|
||||
|
||||
public static function InstallBinaries()
|
||||
|
@ -349,7 +349,7 @@ class AirtimeInstall
|
|||
public static function UninstallBinaries()
|
||||
{
|
||||
echo "* Removing Airtime binaries from ".AirtimeInstall::CONF_DIR_BINARIES.PHP_EOL;
|
||||
exec("rm -rf ".AirtimeInstall::CONF_DIR_BINARIES);
|
||||
exec('rm -rf "'.AirtimeInstall::CONF_DIR_BINARIES.'"');
|
||||
}
|
||||
|
||||
public static function DirCheck()
|
||||
|
@ -399,6 +399,6 @@ class AirtimeInstall
|
|||
$path = AirtimeInstall::CONF_DIR_LOG;
|
||||
echo "* Removing logs directory ".$path.PHP_EOL;
|
||||
|
||||
exec("rm -rf $path");
|
||||
exec("rm -rf \"$path\"");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
* Performs a new install (new configs, database install) if a version of Airtime is not found
|
||||
* If the current version is found to be installed the User is presented with the help menu and can choose -r to reinstall.
|
||||
*/
|
||||
set_include_path(__DIR__.'/../airtime_mvc/library' . PATH_SEPARATOR . get_include_path());
|
||||
set_include_path(__DIR__.'/../../airtime_mvc/library' . PATH_SEPARATOR . get_include_path());
|
||||
|
||||
require_once(dirname(__FILE__).'/include/AirtimeIni.php');
|
||||
require_once(dirname(__FILE__).'/include/AirtimeInstall.php');
|
||||
require_once(dirname(__FILE__).'/AirtimeIni.php');
|
||||
require_once(dirname(__FILE__).'/AirtimeInstall.php');
|
||||
require_once(AirtimeInstall::GetAirtimeSrcDir().'/application/configs/constants.php');
|
||||
|
||||
AirtimeInstall::ExitIfNotRoot();
|
||||
|
@ -97,6 +97,8 @@ if ($overwrite) {
|
|||
echo "* Creating INI files".PHP_EOL;
|
||||
AirtimeIni::CreateIniFiles();
|
||||
}
|
||||
AirtimeIni::CreateMonitFile();
|
||||
|
||||
|
||||
AirtimeInstall::InstallPhpCode();
|
||||
AirtimeInstall::InstallBinaries();
|
||||
|
@ -106,6 +108,9 @@ if ($overwrite) {
|
|||
AirtimeIni::UpdateIniFiles();
|
||||
}
|
||||
|
||||
// Update the build.properties file to point to the correct directory.
|
||||
AirtimeIni::UpdateIniValue(AirtimeInstall::CONF_DIR_WWW.'/build/build.properties', 'project.home', AirtimeInstall::CONF_DIR_WWW);
|
||||
|
||||
require_once(AirtimeInstall::GetAirtimeSrcDir().'/application/configs/conf.php');
|
||||
|
||||
echo "* Airtime Version: ".AIRTIME_VERSION.PHP_EOL;
|
|
@ -5,8 +5,8 @@
|
|||
* @license http://www.gnu.org/licenses/gpl.txt
|
||||
*/
|
||||
|
||||
require_once(dirname(__FILE__).'/include/AirtimeIni.php');
|
||||
require_once(dirname(__FILE__).'/include/AirtimeInstall.php');
|
||||
require_once(dirname(__FILE__).'/AirtimeIni.php');
|
||||
require_once(dirname(__FILE__).'/AirtimeInstall.php');
|
||||
// Need to check that we are superuser before running this.
|
||||
AirtimeInstall::ExitIfNotRoot();
|
||||
|
||||
|
@ -69,7 +69,7 @@ if ($dbDeleteFailed) {
|
|||
// Delete the user
|
||||
//------------------------------------------------------------------------
|
||||
echo " * Deleting database user '{$CC_CONFIG['dsn']['username']}'...".PHP_EOL;
|
||||
$command = "echo \"DROP USER IF EXISTS {$CC_CONFIG['dsn']['username']}\" | su postgres -c psql";
|
||||
$command = "echo \"DROP USER IF EXISTS {$CC_CONFIG['dsn']['username']}\" | su postgres -c psql >/dev/null 2>&1";
|
||||
@exec($command, $output, $results);
|
||||
if ($results == 0) {
|
||||
echo " * User '{$CC_CONFIG['dsn']['username']}' deleted.".PHP_EOL;
|
||||
|
@ -88,6 +88,7 @@ if ($results == 0) {
|
|||
AirtimeInstall::RemoveSymlinks();
|
||||
AirtimeInstall::UninstallBinaries();
|
||||
AirtimeInstall::RemoveLogDirectories();
|
||||
AirtimeIni::RemoveMonitFile();
|
||||
|
||||
unlink('/etc/cron.d/airtime-crons');
|
||||
|
|
@ -7,8 +7,9 @@
|
|||
*/
|
||||
|
||||
//Pear classes.
|
||||
set_include_path(__DIR__.'/../airtime_mvc/library/pear' . PATH_SEPARATOR . get_include_path());
|
||||
set_include_path(__DIR__.'/../../airtime_mvc/library/pear' . PATH_SEPARATOR . get_include_path());
|
||||
require_once('DB.php');
|
||||
require_once(dirname(__FILE__).'/AirtimeIni.php');
|
||||
|
||||
if(exec("whoami") != "root"){
|
||||
echo "Must be root user.\n";
|
||||
|
@ -67,19 +68,19 @@ echo "******************************** Update Begin ****************************
|
|||
$version = substr($version, 0, 5);
|
||||
|
||||
if (strcmp($version, "1.7.0") < 0){
|
||||
system("php ".__DIR__."/upgrades/airtime-1.7/airtime-upgrade.php");
|
||||
system("php ".__DIR__."/../upgrades/airtime-1.7/airtime-upgrade.php");
|
||||
}
|
||||
if (strcmp($version, "1.8.0") < 0){
|
||||
system("php ".__DIR__."/upgrades/airtime-1.8/airtime-upgrade.php");
|
||||
system("php ".__DIR__."/../upgrades/airtime-1.8/airtime-upgrade.php");
|
||||
}
|
||||
if (strcmp($version, "1.8.1") < 0){
|
||||
system("php ".__DIR__."/upgrades/airtime-1.8.1/airtime-upgrade.php");
|
||||
system("php ".__DIR__."/../upgrades/airtime-1.8.1/airtime-upgrade.php");
|
||||
}
|
||||
if (strcmp($version, "1.8.2") < 0){
|
||||
system("php ".__DIR__."/upgrades/airtime-1.8.2/airtime-upgrade.php");
|
||||
system("php ".__DIR__."/../upgrades/airtime-1.8.2/airtime-upgrade.php");
|
||||
}
|
||||
if (strcmp($version, "1.9.0") < 0){
|
||||
system("php ".__DIR__."/upgrades/airtime-1.9/airtime-upgrade.php");
|
||||
system("php ".__DIR__."/../upgrades/airtime-1.9/airtime-upgrade.php");
|
||||
}
|
||||
|
||||
|
||||
|
@ -91,13 +92,15 @@ $CC_DBC->query($sql);
|
|||
|
||||
|
||||
echo PHP_EOL."*** Updating Pypo ***".PHP_EOL;
|
||||
passthru("python ".__DIR__."/../python_apps/pypo/install/pypo-install.py");
|
||||
passthru("python ".__DIR__."/../../python_apps/pypo/install/pypo-install.py");
|
||||
|
||||
echo PHP_EOL."*** Updating Recorder ***".PHP_EOL;
|
||||
passthru("python ".__DIR__."/../python_apps/show-recorder/install/recorder-install.py");
|
||||
passthru("python ".__DIR__."/../../python_apps/show-recorder/install/recorder-install.py");
|
||||
|
||||
echo PHP_EOL."*** Starting Media Monitor ***".PHP_EOL;
|
||||
passthru("python ".__DIR__."/../python_apps/media-monitor/install/media-monitor-install.py");
|
||||
echo PHP_EOL."*** Updating Media Monitor ***".PHP_EOL;
|
||||
passthru("python ".__DIR__."/../../python_apps/media-monitor/install/media-monitor-install.py");
|
||||
|
||||
AirtimeIni::CreateMonitFile();
|
||||
|
||||
|
||||
echo "******************************* Update Complete *******************************".PHP_EOL;
|
|
@ -39,7 +39,7 @@ function InstallBinaries()
|
|||
function UninstallBinaries()
|
||||
{
|
||||
echo "* Removing Airtime binaries from ".CONF_DIR_BINARIES.PHP_EOL;
|
||||
exec("rm -rf ".CONF_DIR_BINARIES);
|
||||
exec('rm -rf "'.CONF_DIR_BINARIES.'"');
|
||||
}
|
||||
|
||||
|
||||
|
@ -74,7 +74,7 @@ $pathnames = array("/usr/bin/airtime-pypo-start",
|
|||
|
||||
foreach ($pathnames as $pn){
|
||||
echo "Removing $pn\n";
|
||||
exec("rm -rf ".$pn);
|
||||
exec("rm -rf \"$pn\"");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -31,24 +31,6 @@ def api_client_factory(config):
|
|||
logger.info('API Client "'+config["api_client"]+'" not supported. Please check your config file.\n')
|
||||
sys.exit()
|
||||
|
||||
def recursive_urlencode(d):
|
||||
def recursion(d, base=None):
|
||||
pairs = []
|
||||
|
||||
for key, value in d.items():
|
||||
if hasattr(value, 'values'):
|
||||
pairs += recursion(value, key)
|
||||
else:
|
||||
new_pair = None
|
||||
if base:
|
||||
new_pair = "%s[%s]=%s" % (base, urllib.quote(unicode(key)), urllib.quote(unicode(value)))
|
||||
else:
|
||||
new_pair = "%s=%s" % (urllib.quote(unicode(key)), urllib.quote(unicode(value)))
|
||||
pairs.append(new_pair)
|
||||
return pairs
|
||||
|
||||
return '&'.join(recursion(d))
|
||||
|
||||
class ApiClientInterface:
|
||||
|
||||
# Implementation: optional
|
||||
|
@ -402,11 +384,12 @@ class AirTimeApiClient(ApiClientInterface):
|
|||
response = None
|
||||
try:
|
||||
url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_media_url"])
|
||||
logger.debug(url)
|
||||
|
||||
url = url.replace("%%api_key%%", self.config["api_key"])
|
||||
url = url.replace("%%mode%%", mode)
|
||||
logger.debug(url)
|
||||
|
||||
data = recursive_urlencode(md)
|
||||
data = urllib.urlencode(md)
|
||||
req = urllib2.Request(url, data)
|
||||
|
||||
response = urllib2.urlopen(req).read()
|
||||
|
@ -636,7 +619,7 @@ class ObpApiClient():
|
|||
def get_liquidsoap_data(self, pkey, schedule):
|
||||
playlist = schedule[pkey]
|
||||
data = dict()
|
||||
data["ptype"] = playlist['subtype']
|
||||
#data["ptype"] = playlist['subtype']
|
||||
try:
|
||||
data["user_id"] = playlist['user_id']
|
||||
data["playlist_id"] = playlist['id']
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import sys
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
def create_user(username):
|
||||
|
|
|
@ -10,9 +10,12 @@ import hashlib
|
|||
import json
|
||||
import shutil
|
||||
import math
|
||||
import socket
|
||||
import grp
|
||||
import pwd
|
||||
|
||||
from collections import deque
|
||||
from pwd import getpwnam
|
||||
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
from configobj import ConfigObj
|
||||
|
@ -26,6 +29,8 @@ from kombu.connection import BrokerConnection
|
|||
from kombu.messaging import Exchange, Queue, Consumer, Producer
|
||||
from api_clients import api_client
|
||||
|
||||
from multiprocessing import Process, Lock
|
||||
|
||||
MODE_CREATE = "create"
|
||||
MODE_MODIFY = "modify"
|
||||
MODE_MOVED = "moved"
|
||||
|
@ -54,10 +59,9 @@ list of supported easy tags in mutagen version 1.20
|
|||
['albumartistsort', 'musicbrainz_albumstatus', 'lyricist', 'releasecountry', 'date', 'performer', 'musicbrainz_albumartistid', 'composer', 'encodedby', 'tracknumber', 'musicbrainz_albumid', 'album', 'asin', 'musicbrainz_artistid', 'mood', 'copyright', 'author', 'media', 'length', 'version', 'artistsort', 'titlesort', 'discsubtitle', 'website', 'musicip_fingerprint', 'conductor', 'compilation', 'barcode', 'performer:*', 'composersort', 'musicbrainz_discid', 'musicbrainz_albumtype', 'genre', 'isrc', 'discnumber', 'musicbrainz_trmid', 'replaygain_*_gain', 'musicip_puid', 'artist', 'title', 'bpm', 'musicbrainz_trackid', 'arranger', 'albumsort', 'replaygain_*_peak', 'organization']
|
||||
"""
|
||||
|
||||
class AirtimeNotifier(Notifier):
|
||||
class MetadataExtractor:
|
||||
|
||||
def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, threshold=0, timeout=None):
|
||||
Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, threshold, timeout)
|
||||
def __init__(self):
|
||||
|
||||
self.airtime2mutagen = {\
|
||||
"MDATA_KEY_TITLE": "title",\
|
||||
|
@ -77,50 +81,6 @@ class AirtimeNotifier(Notifier):
|
|||
"MDATA_KEY_COPYRIGHT": "copyright",\
|
||||
}
|
||||
|
||||
schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True)
|
||||
schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem")
|
||||
self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/")
|
||||
channel = self.connection.channel()
|
||||
consumer = Consumer(channel, schedule_queue)
|
||||
consumer.register_callback(self.handle_message)
|
||||
consumer.consume()
|
||||
|
||||
self.logger = logging.getLogger('root')
|
||||
|
||||
def handle_message(self, body, message):
|
||||
# ACK the message to take it off the queue
|
||||
message.ack()
|
||||
|
||||
self.logger.info("Received md from RabbitMQ: " + body)
|
||||
|
||||
try:
|
||||
m = json.loads(message.body)
|
||||
airtime_file = mutagen.File(m['MDATA_KEY_FILEPATH'], easy=True)
|
||||
|
||||
for key in m.keys() :
|
||||
if key in self.airtime2mutagen:
|
||||
value = m[key]
|
||||
if ((value is not None) and (len(str(value)) > 0)):
|
||||
airtime_file[self.airtime2mutagen[key]] = str(value)
|
||||
self.logger.info('setting %s = %s ', key, str(value))
|
||||
|
||||
|
||||
airtime_file.save()
|
||||
except Exception, e:
|
||||
self.logger.error('Trying to save md')
|
||||
self.logger.error('Exception: %s', e)
|
||||
self.logger.error('Filepath %s', m['MDATA_KEY_FILEPATH'])
|
||||
|
||||
class MediaMonitor(ProcessEvent):
|
||||
|
||||
def my_init(self):
|
||||
"""
|
||||
Method automatically called from ProcessEvent.__init__(). Additional
|
||||
keyworded arguments passed to ProcessEvent.__init__() are then
|
||||
delegated to my_init().
|
||||
"""
|
||||
self.api_client = api_client.api_client_factory(config)
|
||||
|
||||
self.mutagen2airtime = {\
|
||||
"title": "MDATA_KEY_TITLE",\
|
||||
"artist": "MDATA_KEY_CREATOR",\
|
||||
|
@ -139,26 +99,7 @@ class MediaMonitor(ProcessEvent):
|
|||
"copyright": "MDATA_KEY_COPYRIGHT",\
|
||||
}
|
||||
|
||||
self.supported_file_formats = ['mp3', 'ogg']
|
||||
self.logger = logging.getLogger('root')
|
||||
self.temp_files = {}
|
||||
self.moved_files = {}
|
||||
self.file_events = deque()
|
||||
|
||||
self.mask = pyinotify.ALL_EVENTS
|
||||
|
||||
self.wm = WatchManager()
|
||||
|
||||
schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True)
|
||||
schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem")
|
||||
connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/")
|
||||
channel = connection.channel()
|
||||
|
||||
def watch_directory(self, directory):
|
||||
return self.wm.add_watch(directory, self.mask, rec=True, auto_add=True)
|
||||
|
||||
def is_parent_directory(self, filepath, directory):
|
||||
return (directory == filepath[0:len(directory)])
|
||||
|
||||
def get_md5(self, filepath):
|
||||
f = open(filepath, 'rb')
|
||||
|
@ -185,6 +126,192 @@ class MediaMonitor(ProcessEvent):
|
|||
|
||||
return length
|
||||
|
||||
def save_md_to_file(self, m):
|
||||
try:
|
||||
airtime_file = mutagen.File(m['MDATA_KEY_FILEPATH'], easy=True)
|
||||
|
||||
for key in m.keys() :
|
||||
if key in self.airtime2mutagen:
|
||||
value = m[key]
|
||||
if ((value is not None) and (len(str(value)) > 0)):
|
||||
airtime_file[self.airtime2mutagen[key]] = str(value)
|
||||
#self.logger.info('setting %s = %s ', key, str(value))
|
||||
|
||||
|
||||
airtime_file.save()
|
||||
except Exception, e:
|
||||
self.logger.error('Trying to save md')
|
||||
self.logger.error('Exception: %s', e)
|
||||
self.logger.error('Filepath %s', m['MDATA_KEY_FILEPATH'])
|
||||
|
||||
def get_md_from_file(self, filepath):
|
||||
md = {}
|
||||
md5 = self.get_md5(filepath)
|
||||
md['MDATA_KEY_MD5'] = md5
|
||||
|
||||
file_info = mutagen.File(filepath, easy=True)
|
||||
attrs = self.mutagen2airtime
|
||||
for key in file_info.keys() :
|
||||
if key in attrs :
|
||||
md[attrs[key]] = file_info[key][0]
|
||||
|
||||
if 'MDATA_KEY_TITLE' not in md:
|
||||
#get rid of file extention from original name, name might have more than 1 '.' in it.
|
||||
original_name = os.path.basename(filepath)
|
||||
original_name = original_name.split(".")[0:-1]
|
||||
original_name = ''.join(original_name)
|
||||
md['MDATA_KEY_TITLE'] = original_name
|
||||
|
||||
#incase track number is in format u'4/11'
|
||||
if 'MDATA_KEY_TRACKNUMBER' in md:
|
||||
if isinstance(md['MDATA_KEY_TRACKNUMBER'], basestring):
|
||||
md['MDATA_KEY_TRACKNUMBER'] = md['MDATA_KEY_TRACKNUMBER'].split("/")[0]
|
||||
|
||||
md['MDATA_KEY_BITRATE'] = file_info.info.bitrate
|
||||
md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate
|
||||
md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length)
|
||||
md['MDATA_KEY_MIME'] = file_info.mime[0]
|
||||
|
||||
if "mp3" in md['MDATA_KEY_MIME']:
|
||||
md['MDATA_KEY_FTYPE'] = "audioclip"
|
||||
elif "vorbis" in md['MDATA_KEY_MIME']:
|
||||
md['MDATA_KEY_FTYPE'] = "audioclip"
|
||||
|
||||
#do this so object can be urlencoded properly.
|
||||
for key in md.keys():
|
||||
if(isinstance(md[key], basestring)):
|
||||
md[key] = md[key].encode('utf-8')
|
||||
|
||||
return md
|
||||
|
||||
|
||||
class AirtimeNotifier(Notifier):
|
||||
|
||||
def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, threshold=0, timeout=None):
|
||||
Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, threshold, timeout)
|
||||
|
||||
schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True)
|
||||
schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem")
|
||||
self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/")
|
||||
channel = self.connection.channel()
|
||||
consumer = Consumer(channel, schedule_queue)
|
||||
consumer.register_callback(self.handle_message)
|
||||
consumer.consume()
|
||||
|
||||
self.logger = logging.getLogger('root')
|
||||
self.api_client = api_client.api_client_factory(config)
|
||||
self.md_manager = MetadataExtractor()
|
||||
self.import_processes = {}
|
||||
self.watched_folders = []
|
||||
|
||||
def handle_message(self, body, message):
|
||||
# ACK the message to take it off the queue
|
||||
message.ack()
|
||||
|
||||
self.logger.info("Received md from RabbitMQ: " + body)
|
||||
m = json.loads(message.body)
|
||||
|
||||
if m['event_type'] == "md_update":
|
||||
self.logger.info("AIRTIME NOTIFIER md update event")
|
||||
self.md_manager.save_md_to_file(m)
|
||||
elif m['event_type'] == "new_watch":
|
||||
self.logger.info("AIRTIME NOTIFIER add watched folder event " + m['directory'])
|
||||
#start a new process to walk through this folder and add the files to Airtime.
|
||||
p = Process(target=self.walk_newly_watched_directory, args=(m['directory'],))
|
||||
p.start()
|
||||
self.import_processes[m['directory']] = p
|
||||
#add this new folder to our list of watched folders
|
||||
self.watched_folders.append(m['directory'])
|
||||
|
||||
def update_airtime(self, d):
|
||||
|
||||
filepath = d['filepath']
|
||||
mode = d['mode']
|
||||
|
||||
data = None
|
||||
md = {}
|
||||
md['MDATA_KEY_FILEPATH'] = filepath
|
||||
|
||||
if (os.path.exists(filepath) and (mode == MODE_CREATE)):
|
||||
mutagen = self.md_manager.get_md_from_file(filepath)
|
||||
md.update(mutagen)
|
||||
data = md
|
||||
elif (os.path.exists(filepath) and (mode == MODE_MODIFY)):
|
||||
mutagen = self.md_manager.get_md_from_file(filepath)
|
||||
md.update(mutagen)
|
||||
data = md
|
||||
elif (mode == MODE_MOVED):
|
||||
mutagen = self.md_manager.get_md_from_file(filepath)
|
||||
md.update(mutagen)
|
||||
data = md
|
||||
elif (mode == MODE_DELETE):
|
||||
data = md
|
||||
|
||||
if data is not None:
|
||||
self.logger.info("Updating Change to Airtime " + filepath)
|
||||
response = None
|
||||
while response is None:
|
||||
response = self.api_client.update_media_metadata(data, mode)
|
||||
time.sleep(5)
|
||||
|
||||
def walk_newly_watched_directory(self, directory):
|
||||
|
||||
for (path, dirs, files) in os.walk(directory):
|
||||
for filename in files:
|
||||
full_filepath = path+"/"+filename
|
||||
self.update_airtime({'filepath': full_filepath, 'mode': MODE_CREATE})
|
||||
|
||||
|
||||
class MediaMonitor(ProcessEvent):
|
||||
|
||||
def my_init(self):
|
||||
"""
|
||||
Method automatically called from ProcessEvent.__init__(). Additional
|
||||
keyworded arguments passed to ProcessEvent.__init__() are then
|
||||
delegated to my_init().
|
||||
"""
|
||||
self.api_client = api_client.api_client_factory(config)
|
||||
self.supported_file_formats = ['mp3', 'ogg']
|
||||
self.logger = logging.getLogger('root')
|
||||
self.temp_files = {}
|
||||
self.moved_files = {}
|
||||
self.file_events = deque()
|
||||
self.mask = pyinotify.ALL_EVENTS
|
||||
self.wm = WatchManager()
|
||||
self.md_manager = MetadataExtractor()
|
||||
|
||||
schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True)
|
||||
schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem")
|
||||
connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/")
|
||||
channel = connection.channel()
|
||||
|
||||
def watch_directory(self, directory):
|
||||
return self.wm.add_watch(directory, self.mask, rec=True, auto_add=True)
|
||||
|
||||
def is_parent_directory(self, filepath, directory):
|
||||
return (directory == filepath[0:len(directory)])
|
||||
|
||||
def set_needed_file_permissions(self, item, is_dir):
|
||||
|
||||
try:
|
||||
omask = os.umask(0)
|
||||
|
||||
uid = pwd.getpwnam('pypo')[2]
|
||||
gid = grp.getgrnam('www-data')[2]
|
||||
|
||||
os.chown(item, uid, gid)
|
||||
|
||||
if is_dir is True:
|
||||
os.chmod(item, 02777)
|
||||
else:
|
||||
os.chmod(item, 0666)
|
||||
|
||||
except Exception, e:
|
||||
self.logger.error("Failed to change file's owner/group/permissions.")
|
||||
self.logger.error(item)
|
||||
finally:
|
||||
os.umask(omask)
|
||||
|
||||
def ensure_dir(self, filepath):
|
||||
directory = os.path.dirname(filepath)
|
||||
|
||||
|
@ -196,21 +323,38 @@ class MediaMonitor(ProcessEvent):
|
|||
finally:
|
||||
os.umask(omask)
|
||||
|
||||
def move_file(self, source, dest):
|
||||
|
||||
try:
|
||||
omask = os.umask(0)
|
||||
os.rename(source, dest)
|
||||
except Exception, e:
|
||||
self.logger.error("failed to move file.")
|
||||
finally:
|
||||
os.umask(omask)
|
||||
|
||||
def create_unique_filename(self, filepath):
|
||||
|
||||
if(os.path.exists(filepath)):
|
||||
file_dir = os.path.dirname(filepath)
|
||||
filename = os.path.basename(filepath).split(".")[0]
|
||||
#will be in the format .ext
|
||||
file_ext = os.path.splitext(filepath)[1]
|
||||
i = 1;
|
||||
while(True):
|
||||
new_filepath = "%s/%s(%s)%s" % (file_dir, filename, i, file_ext)
|
||||
try:
|
||||
if(os.path.exists(filepath)):
|
||||
self.logger.info("Path %s exists", filepath)
|
||||
file_dir = os.path.dirname(filepath)
|
||||
filename = os.path.basename(filepath).split(".")[0]
|
||||
#will be in the format .ext
|
||||
file_ext = os.path.splitext(filepath)[1]
|
||||
i = 1;
|
||||
while(True):
|
||||
new_filepath = '%s/%s(%s)%s' % (file_dir, filename, i, file_ext)
|
||||
self.logger.error("Trying %s", new_filepath)
|
||||
|
||||
if(os.path.exists(new_filepath)):
|
||||
i = i+1;
|
||||
else:
|
||||
filepath = new_filepath
|
||||
if(os.path.exists(new_filepath)):
|
||||
i = i+1;
|
||||
else:
|
||||
filepath = new_filepath
|
||||
break
|
||||
|
||||
except Exception, e:
|
||||
self.logger.error("Exception %s", e)
|
||||
|
||||
return filepath
|
||||
|
||||
|
@ -226,23 +370,39 @@ class MediaMonitor(ProcessEvent):
|
|||
|
||||
#will be in the format .ext
|
||||
file_ext = os.path.splitext(imported_filepath)[1]
|
||||
md = self.get_mutagen_info(imported_filepath)
|
||||
file_ext = file_ext.encode('utf-8')
|
||||
md = self.md_manager.get_md_from_file(imported_filepath)
|
||||
|
||||
path_md = ['MDATA_KEY_TITLE', 'MDATA_KEY_CREATOR', 'MDATA_KEY_SOURCE', 'MDATA_KEY_TRACKNUMBER', 'MDATA_KEY_BITRATE']
|
||||
|
||||
self.logger.info('Getting md')
|
||||
|
||||
for m in path_md:
|
||||
if m not in md:
|
||||
md[m] = 'unknown'
|
||||
md[m] = u'unknown'.encode('utf-8')
|
||||
else:
|
||||
#get rid of any "/" which will interfere with the filepath.
|
||||
if isinstance(md[m], basestring):
|
||||
md[m] = md[m].replace("/", "-")
|
||||
|
||||
self.logger.info(md)
|
||||
|
||||
self.logger.info('Starting filepath creation')
|
||||
|
||||
filepath = None
|
||||
if (md['MDATA_KEY_TITLE'] == 'unknown'):
|
||||
filepath = "%s/%s/%s/%s-%s%s" % (storage_directory, md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], original_name, md['MDATA_KEY_BITRATE'], file_ext)
|
||||
elif(md['MDATA_KEY_TRACKNUMBER'] == 'unknown'):
|
||||
filepath = "%s/%s/%s/%s-%s%s" % (storage_directory, md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
||||
if (md['MDATA_KEY_TITLE'] == u'unknown'.encode('utf-8')):
|
||||
self.logger.info('unknown title')
|
||||
filepath = '%s/%s/%s/%s-%s%s' % (storage_directory.encode('utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], original_name, md['MDATA_KEY_BITRATE'], file_ext)
|
||||
elif(md['MDATA_KEY_TRACKNUMBER'] == u'unknown'.encode('utf-8')):
|
||||
self.logger.info('unknown track number')
|
||||
filepath = '%s/%s/%s/%s-%s%s' % (storage_directory.encode('utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
||||
else:
|
||||
filepath = "%s/%s/%s/%s-%s-%s%s" % (storage_directory, md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TRACKNUMBER'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
||||
self.logger.info('full metadata')
|
||||
filepath = '%s/%s/%s/%s-%s-%s%s' % (storage_directory.encode('utf-8'), md['MDATA_KEY_CREATOR'], md['MDATA_KEY_SOURCE'], md['MDATA_KEY_TRACKNUMBER'], md['MDATA_KEY_TITLE'], md['MDATA_KEY_BITRATE'], file_ext)
|
||||
|
||||
self.logger.info(u'Created filepath: %s', filepath)
|
||||
filepath = self.create_unique_filename(filepath)
|
||||
self.logger.info(u'Unique filepath: %s', filepath)
|
||||
self.ensure_dir(filepath)
|
||||
|
||||
except Exception, e:
|
||||
|
@ -250,63 +410,6 @@ class MediaMonitor(ProcessEvent):
|
|||
|
||||
return filepath
|
||||
|
||||
def get_mutagen_info(self, filepath):
|
||||
md = {}
|
||||
md5 = self.get_md5(filepath)
|
||||
md['MDATA_KEY_MD5'] = md5
|
||||
|
||||
file_info = mutagen.File(filepath, easy=True)
|
||||
attrs = self.mutagen2airtime
|
||||
for key in file_info.keys() :
|
||||
if key in attrs :
|
||||
md[attrs[key]] = file_info[key][0]
|
||||
|
||||
#md['MDATA_KEY_TRACKNUMBER'] = "%02d" % (int(md['MDATA_KEY_TRACKNUMBER']))
|
||||
|
||||
md['MDATA_KEY_BITRATE'] = file_info.info.bitrate
|
||||
md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate
|
||||
md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length)
|
||||
md['MDATA_KEY_MIME'] = file_info.mime[0]
|
||||
|
||||
if "mp3" in md['MDATA_KEY_MIME']:
|
||||
md['MDATA_KEY_FTYPE'] = "audioclip"
|
||||
elif "vorbis" in md['MDATA_KEY_MIME']:
|
||||
md['MDATA_KEY_FTYPE'] = "audioclip"
|
||||
|
||||
return md
|
||||
|
||||
|
||||
def update_airtime(self, d):
|
||||
|
||||
filepath = d['filepath']
|
||||
mode = d['mode']
|
||||
|
||||
data = None
|
||||
md = {}
|
||||
md['MDATA_KEY_FILEPATH'] = filepath
|
||||
|
||||
if (os.path.exists(filepath) and (mode == MODE_CREATE)):
|
||||
mutagen = self.get_mutagen_info(filepath)
|
||||
md.update(mutagen)
|
||||
data = {'md': md}
|
||||
elif (os.path.exists(filepath) and (mode == MODE_MODIFY)):
|
||||
mutagen = self.get_mutagen_info(filepath)
|
||||
md.update(mutagen)
|
||||
data = {'md': md}
|
||||
elif (mode == MODE_MOVED):
|
||||
mutagen = self.get_mutagen_info(filepath)
|
||||
md.update(mutagen)
|
||||
data = {'md': md}
|
||||
elif (mode == MODE_DELETE):
|
||||
data = {'md': md}
|
||||
|
||||
if data is not None:
|
||||
self.logger.info("Updating Change to Airtime")
|
||||
response = None
|
||||
while response is None:
|
||||
response = self.api_client.update_media_metadata(data, mode)
|
||||
time.sleep(5)
|
||||
|
||||
def is_temp_file(self, filename):
|
||||
info = filename.split(".")
|
||||
|
||||
|
@ -334,18 +437,20 @@ class MediaMonitor(ProcessEvent):
|
|||
global plupload_directory
|
||||
#files that have been added through plupload have a placeholder already put in Airtime's database.
|
||||
if not self.is_parent_directory(event.pathname, plupload_directory):
|
||||
md5 = self.get_md5(event.pathname)
|
||||
response = self.api_client.check_media_status(md5)
|
||||
if self.is_audio_file(event.pathname):
|
||||
self.set_needed_file_permissions(event.pathname, event.dir)
|
||||
md5 = self.md_manager.get_md5(event.pathname)
|
||||
response = self.api_client.check_media_status(md5)
|
||||
|
||||
#this file is new, md5 does not exist in Airtime.
|
||||
if(response['airtime_status'] == 0):
|
||||
filepath = self.create_file_path(event.pathname)
|
||||
os.rename(event.pathname, filepath)
|
||||
self.file_events.append({'mode': MODE_CREATE, 'filepath': filepath})
|
||||
#this file is new, md5 does not exist in Airtime.
|
||||
if(response['airtime_status'] == 0):
|
||||
filepath = self.create_file_path(event.pathname)
|
||||
self.move_file(event.pathname, filepath)
|
||||
self.file_events.append({'mode': MODE_CREATE, 'filepath': filepath})
|
||||
|
||||
#immediately add a watch on the new directory.
|
||||
else:
|
||||
self.watch_directory(event.pathname)
|
||||
self.set_needed_file_permissions(event.pathname, event.dir)
|
||||
|
||||
|
||||
def process_IN_MODIFY(self, event):
|
||||
if not event.dir:
|
||||
|
@ -367,6 +472,8 @@ class MediaMonitor(ProcessEvent):
|
|||
|
||||
def process_IN_MOVED_TO(self, event):
|
||||
self.logger.info("%s: %s", event.maskname, event.pathname)
|
||||
#if stuff dropped in stor via a UI move must change file permissions.
|
||||
self.set_needed_file_permissions(event.pathname, event.dir)
|
||||
if not event.dir:
|
||||
if event.cookie in self.temp_files:
|
||||
del self.temp_files[event.cookie]
|
||||
|
@ -380,7 +487,7 @@ class MediaMonitor(ProcessEvent):
|
|||
#file renamed from /tmp/plupload does not have a path in our naming scheme yet.
|
||||
md_filepath = self.create_file_path(event.pathname)
|
||||
#move the file a second time to its correct Airtime naming schema.
|
||||
os.rename(event.pathname, md_filepath)
|
||||
self.move_file(event.pathname, md_filepath)
|
||||
self.file_events.append({'filepath': md_filepath, 'mode': MODE_MOVED})
|
||||
else:
|
||||
self.file_events.append({'filepath': event.pathname, 'mode': MODE_MOVED})
|
||||
|
@ -389,7 +496,7 @@ class MediaMonitor(ProcessEvent):
|
|||
#TODO need to pass in if md5 exists to this file creation function, identical files will just replace current files not have a (1) etc.
|
||||
#file has been most likely dropped into stor folder from an unwatched location. (from gui, mv command not cp)
|
||||
md_filepath = self.create_file_path(event.pathname)
|
||||
os.rename(event.pathname, md_filepath)
|
||||
self.move_file(event.pathname, md_filepath)
|
||||
self.file_events.append({'mode': MODE_CREATE, 'filepath': md_filepath})
|
||||
|
||||
def process_IN_DELETE(self, event):
|
||||
|
@ -402,12 +509,22 @@ class MediaMonitor(ProcessEvent):
|
|||
|
||||
def notifier_loop_callback(self, notifier):
|
||||
|
||||
for watched_directory in notifier.import_processes.keys():
|
||||
process = notifier.import_processes[watched_directory]
|
||||
if not process.is_alive():
|
||||
self.watch_directory(watched_directory)
|
||||
del notifier.import_processes[watched_directory]
|
||||
|
||||
while len(self.file_events) > 0:
|
||||
self.logger.info("Processing a file event update to Airtime.")
|
||||
file_info = self.file_events.popleft()
|
||||
self.update_airtime(file_info)
|
||||
notifier.update_airtime(file_info)
|
||||
|
||||
try:
|
||||
notifier.connection.drain_events(timeout=1)
|
||||
#avoid logging a bunch of timeout messages.
|
||||
except socket.timeout:
|
||||
pass
|
||||
except Exception, e:
|
||||
self.logger.info("%s", e)
|
||||
|
||||
|
|
|
@ -9,19 +9,21 @@
|
|||
# Short-Description: Manage airtime-media-monitor daemon
|
||||
### END INIT INFO
|
||||
|
||||
USERID=pypo
|
||||
GROUPID=pypo
|
||||
NAME=Airtime
|
||||
USERID=root
|
||||
GROUPID=www-data
|
||||
NAME=Airtime\ Media\ Monitor
|
||||
|
||||
DAEMON=/usr/bin/airtime-media-monitor
|
||||
DAEMON=/usr/lib/airtime/media-monitor/airtime-media-monitor
|
||||
PIDFILE=/var/run/airtime-media-monitor.pid
|
||||
|
||||
start () {
|
||||
start () {
|
||||
monit monitor airtime-media-monitor >/dev/null 2>&1
|
||||
start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE --startas $DAEMON
|
||||
}
|
||||
|
||||
stop () {
|
||||
# Send TERM after 5 seconds, wait at most 30 seconds.
|
||||
monit unmonitor airtime-media-monitor >/dev/null 2>&1
|
||||
start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE
|
||||
rm -f $PIDFILE
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ def copy_dir(src_dir, dest_dir):
|
|||
if not (os.path.exists(dest_dir)):
|
||||
print "Copying directory "+os.path.realpath(src_dir)+" to "+os.path.realpath(dest_dir)
|
||||
shutil.copytree(src_dir, dest_dir)
|
||||
|
||||
|
||||
def get_current_script_dir():
|
||||
current_script_dir = os.path.realpath(__file__)
|
||||
index = current_script_dir.rindex('/')
|
||||
|
@ -60,21 +60,18 @@ try:
|
|||
os.system("chown -R pypo:pypo "+config["log_dir"])
|
||||
|
||||
copy_dir("%s/.."%current_script_dir, config["bin_dir"])
|
||||
|
||||
|
||||
print "Setting permissions"
|
||||
os.system("chmod -R 755 "+config["bin_dir"])
|
||||
#os.system("chmod -R 755 "+config["bin_dir"]+"/airtime-media-monitor)
|
||||
os.system("chown -R pypo:pypo "+config["bin_dir"])
|
||||
|
||||
print "Creating symbolic links"
|
||||
os.system("rm -f /usr/bin/airtime-media-monitor")
|
||||
os.system("ln -s "+config["bin_dir"]+"/airtime-media-monitor /usr/bin/")
|
||||
|
||||
print "Installing media-monitor daemon"
|
||||
shutil.copy(config["bin_dir"]+"/airtime-media-monitor-init-d", "/etc/init.d/airtime-media-monitor")
|
||||
|
||||
p = Popen("update-rc.d airtime-media-monitor defaults", shell=True)
|
||||
p = Popen("update-rc.d airtime-media-monitor defaults >/dev/null 2>&1", shell=True)
|
||||
sts = os.waitpid(p.pid, 0)[1]
|
||||
|
||||
|
||||
print "Waiting for processes to start..."
|
||||
p = Popen("/etc/init.d/airtime-media-monitor start", shell=True)
|
||||
sts = os.waitpid(p.pid, 0)[1]
|
||||
|
|
|
@ -12,7 +12,7 @@ if os.geteuid() != 0:
|
|||
PATH_INI_FILE = '/etc/airtime/media-monitor.cfg'
|
||||
|
||||
def remove_path(path):
|
||||
os.system("rm -rf " + path)
|
||||
os.system('rm -rf "%s"' % path)
|
||||
|
||||
def get_current_script_dir():
|
||||
current_script_dir = os.path.realpath(__file__)
|
||||
|
@ -29,7 +29,7 @@ try:
|
|||
|
||||
os.system("/etc/init.d/airtime-media-monitor stop")
|
||||
os.system("rm -f /etc/init.d/airtime-media-monitor")
|
||||
os.system("update-rc.d -f airtime-media-monitor remove")
|
||||
os.system("update-rc.d -f airtime-media-monitor remove >/dev/null 2>&1")
|
||||
|
||||
print "Removing log directories"
|
||||
remove_path(config["log_dir"])
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
set daemon 10 # Poll at 10 second intervals
|
||||
set logfile syslog facility log_daemon
|
||||
|
||||
set httpd port 2812 and use address 127.0.0.1
|
||||
allow localhost
|
||||
allow admin:monit
|
||||
|
||||
check process airtime-playout
|
||||
with pidfile "/var/run/airtime-playout.pid"
|
||||
start program = "/etc/init.d/airtime-playout start" with timeout 10 seconds
|
||||
|
|
|
@ -11,24 +11,31 @@
|
|||
|
||||
USERID=pypo
|
||||
GROUPID=pypo
|
||||
NAME=Airtime
|
||||
NAME=Airtime\ Playout
|
||||
|
||||
DAEMON0=/usr/bin/airtime-playout
|
||||
DAEMON0=/usr/lib/airtime/pypo/bin/airtime-playout
|
||||
PIDFILE0=/var/run/airtime-playout.pid
|
||||
|
||||
DAEMON1=/usr/bin/airtime-liquidsoap
|
||||
DAEMON1=/usr/lib/airtime/pypo/bin/airtime-liquidsoap
|
||||
PIDFILE1=/var/run/airtime-liquidsoap.pid
|
||||
|
||||
start () {
|
||||
start () {
|
||||
monit monitor airtime-playout >/dev/null 2>&1
|
||||
start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE0 --startas $DAEMON0
|
||||
|
||||
monit monitor airtime-liquidsoap >/dev/null 2>&1
|
||||
start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE1 --startas $DAEMON1
|
||||
}
|
||||
|
||||
stop () {
|
||||
# Send TERM after 5 seconds, wait at most 30 seconds.
|
||||
|
||||
monit unmonitor airtime-playout >/dev/null 2>&1
|
||||
start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE0
|
||||
start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE1
|
||||
rm -f $PIDFILE0
|
||||
|
||||
monit unmonitor airtime-liquidsoap >/dev/null 2>&1
|
||||
start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE1
|
||||
rm -f $PIDFILE1
|
||||
}
|
||||
|
||||
|
|
|
@ -81,10 +81,10 @@ try:
|
|||
|
||||
if architecture == '64bit' and natty:
|
||||
print "Installing 64-bit liquidsoap binary (Natty)"
|
||||
shutil.copy("%s/../liquidsoap_bin/liquidsoap-amd64-natty"%current_script_dir, "%s/../liquidsoap_bin/liquidsoap"%current_script_dir)
|
||||
shutil.copy("%s/../liquidsoap_bin/liquidsoap-natty-amd64"%current_script_dir, "%s/../liquidsoap_bin/liquidsoap"%current_script_dir)
|
||||
elif architecture == '32bit' and natty:
|
||||
print "Installing 32-bit liquidsoap binary (Natty)"
|
||||
shutil.copy("%s/../liquidsoap_bin/liquidsoap-i386-natty"%current_script_dir, "%s/../liquidsoap_bin/liquidsoap"%current_script_dir)
|
||||
shutil.copy("%s/../liquidsoap_bin/liquidsoap-natty-i386"%current_script_dir, "%s/../liquidsoap_bin/liquidsoap"%current_script_dir)
|
||||
elif architecture == '64bit' and not natty:
|
||||
print "Installing 64-bit liquidsoap binary"
|
||||
shutil.copy("%s/../liquidsoap_bin/liquidsoap-amd64"%current_script_dir, "%s/../liquidsoap_bin/liquidsoap"%current_script_dir)
|
||||
|
@ -103,16 +103,10 @@ try:
|
|||
os.system("chown -R pypo:pypo "+config["bin_dir"])
|
||||
os.system("chown -R pypo:pypo "+config["cache_base_dir"])
|
||||
|
||||
print "Creating symbolic links"
|
||||
os.system("rm -f /usr/bin/airtime-playout")
|
||||
os.system("ln -s "+config["bin_dir"]+"/bin/airtime-playout /usr/bin/")
|
||||
os.system("rm -f /usr/bin/airtime-liquidsoap")
|
||||
os.system("ln -s "+config["bin_dir"]+"/bin/airtime-liquidsoap /usr/bin/")
|
||||
|
||||
print "Installing pypo daemon"
|
||||
shutil.copy(config["bin_dir"]+"/bin/airtime-playout-init-d", "/etc/init.d/airtime-playout")
|
||||
|
||||
p = Popen("update-rc.d airtime-playout defaults", shell=True)
|
||||
p = Popen("update-rc.d airtime-playout defaults >/dev/null 2>&1", shell=True)
|
||||
sts = os.waitpid(p.pid, 0)[1]
|
||||
|
||||
print "Waiting for processes to start..."
|
||||
|
|
|
@ -12,7 +12,7 @@ if os.geteuid() != 0:
|
|||
PATH_INI_FILE = '/etc/airtime/pypo.cfg'
|
||||
|
||||
def remove_path(path):
|
||||
os.system("rm -rf " + path)
|
||||
os.system('rm -rf "%s"' % path)
|
||||
|
||||
def get_current_script_dir():
|
||||
current_script_dir = os.path.realpath(__file__)
|
||||
|
@ -29,7 +29,7 @@ try:
|
|||
|
||||
os.system("/etc/init.d/airtime-playout stop")
|
||||
os.system("rm -f /etc/init.d/airtime-playout")
|
||||
os.system("update-rc.d -f airtime-playout remove")
|
||||
os.system("update-rc.d -f airtime-playout remove >/dev/null 2>&1")
|
||||
|
||||
print "Removing cache directories"
|
||||
remove_path(config["cache_base_dir"])
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,5 @@
|
|||
|
||||
SUBDIRS = tests
|
||||
DISTFILES = $(wildcard *.in) Makefile ask-liquidsoap.rb ask-liquidsoap.pl \
|
||||
$(wildcard *.liq) extract-replaygain
|
||||
|
||||
|
|
|
@ -9,37 +9,43 @@ my_get_mime = get_mime
|
|||
get_mime = my_get_mime
|
||||
|
||||
%ifdef add_decoder
|
||||
if test_process("which flac") then
|
||||
log(level=3,"Found flac binary: enabling flac external decoder.")
|
||||
flac_p = "flac -d -c - 2>/dev/null"
|
||||
def test_flac(file) =
|
||||
if test_process("which metaflac") then
|
||||
channels = list.hd(get_process_lines("metaflac \
|
||||
--show-channels #{quote(file)} \
|
||||
2>/dev/null"))
|
||||
# If the value is not an int, this returns 0 and we are ok :)
|
||||
int_of_string(channels)
|
||||
else
|
||||
# Try to detect using mime test..
|
||||
mime = get_mime(file)
|
||||
if string.match(pattern="flac",file) then
|
||||
# We do not know the number of audio channels
|
||||
# so setting to -1
|
||||
(-1)
|
||||
# Enable external FLAC decoders. Requires flac binary
|
||||
# in the path for audio decoding and metaflac binary for
|
||||
# metadata. Does not work on Win32. Default: disabled.
|
||||
# Please note that built-in support for FLAC is available
|
||||
# in liquidsoap if compiled and should be preferred over
|
||||
# the external decoder.
|
||||
# @category Liquidsoap
|
||||
def enable_external_flac_decoder() =
|
||||
if test_process("which flac") then
|
||||
log(level=3,"Found flac binary: enabling flac external decoder.")
|
||||
flac_p = "flac -d -c - 2>/dev/null"
|
||||
def test_flac(file) =
|
||||
if test_process("which metaflac") then
|
||||
channels = list.hd(get_process_lines("metaflac \
|
||||
--show-channels #{quote(file)} \
|
||||
2>/dev/null"))
|
||||
# If the value is not an int, this returns 0 and we are ok :)
|
||||
int_of_string(channels)
|
||||
else
|
||||
# All tests failed: no audio decodable using flac..
|
||||
0
|
||||
# Try to detect using mime test..
|
||||
mime = get_mime(file)
|
||||
if string.match(pattern="flac",file) then
|
||||
# We do not know the number of audio channels
|
||||
# so setting to -1
|
||||
(-1)
|
||||
else
|
||||
# All tests failed: no audio decodable using flac..
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
add_decoder(name="FLAC",description="Decode files using the flac \
|
||||
decoder binary.", test=test_flac,flac_p)
|
||||
else
|
||||
log(level=3,"Did not find flac binary: flac decoder disabled.")
|
||||
end
|
||||
add_decoder(name="FLAC",description="Decode files using the flac \
|
||||
decoder binary.", test=test_flac,flac_p)
|
||||
else
|
||||
log(level=3,"Did not find flac binary: flac decoder disabled.")
|
||||
end
|
||||
%endif
|
||||
|
||||
if os.type != "Win32" then
|
||||
if test_process("which metaflac") then
|
||||
log(level=3,"Found metaflac binary: enabling flac external metadata \
|
||||
resolver.")
|
||||
|
@ -55,49 +61,59 @@ if os.type != "Win32" then
|
|||
if list.length(l) >= 1 then
|
||||
list.append([(list.hd(l),"")],l')
|
||||
else
|
||||
l'
|
||||
end
|
||||
l'
|
||||
end
|
||||
end
|
||||
list.fold(f,[],ret)
|
||||
end
|
||||
list.fold(f,[],ret)
|
||||
end
|
||||
add_metadata_resolver("FLAC",flac_meta)
|
||||
else
|
||||
log(level=3,"Did not find metaflac binary: flac metadata resolver disabled.")
|
||||
log(level=3,"Did not find metaflac binary: flac metadata resolver disabled.")
|
||||
end
|
||||
end
|
||||
%endif
|
||||
|
||||
# A list of know extensions and content-type for AAC.
|
||||
# Values from http://en.wikipedia.org/wiki/Advanced_Audio_Coding
|
||||
# TODO: can we register a setting for that ??
|
||||
aac_mimes =
|
||||
["audio/aac", "audio/aacp", "audio/3gpp", "audio/3gpp2", "audio/mp4",
|
||||
"audio/MP4A-LATM", "audio/mpeg4-generic", "audio/x-hx-aac-adts"]
|
||||
aac_filexts = ["m4a", "m4b", "m4p", "m4v",
|
||||
"m4r", "3gp", "mp4", "aac"]
|
||||
%ifdef add_oblivious_decoder
|
||||
# Enable or disable external FAAD (AAC/AAC+/M4A) decoders.
|
||||
# Requires faad binary in the path for audio decoding and
|
||||
# metaflac binary for metadata. Does not work on Win32.
|
||||
# Please note that built-in support for faad is available
|
||||
# in liquidsoap if compiled and should be preferred over
|
||||
# the external decoder.
|
||||
# @category Liquidsoap
|
||||
def enable_external_faad_decoder() =
|
||||
|
||||
# Faad is not very selective so
|
||||
# We are checking only file that
|
||||
# end with a known extension or mime type
|
||||
def faad_test(file) =
|
||||
# Get the file's mime
|
||||
mime = get_mime(file)
|
||||
# Test mime
|
||||
if list.mem(mime,aac_mimes) then
|
||||
true
|
||||
else
|
||||
# Otherwise test file extension
|
||||
ret = string.extract(pattern='\.(.+)$',file)
|
||||
# A list of know extensions and content-type for AAC.
|
||||
# Values from http://en.wikipedia.org/wiki/Advanced_Audio_Coding
|
||||
# TODO: can we register a setting for that ??
|
||||
aac_mimes =
|
||||
["audio/aac", "audio/aacp", "audio/3gpp", "audio/3gpp2", "audio/mp4",
|
||||
"audio/MP4A-LATM", "audio/mpeg4-generic", "audio/x-hx-aac-adts"]
|
||||
aac_filexts = ["m4a", "m4b", "m4p", "m4v",
|
||||
"m4r", "3gp", "mp4", "aac"]
|
||||
|
||||
# Faad is not very selective so
|
||||
# We are checking only file that
|
||||
# end with a known extension or mime type
|
||||
def faad_test(file) =
|
||||
# Get the file's mime
|
||||
mime = get_mime(file)
|
||||
# Test mime
|
||||
if list.mem(mime,aac_mimes) then
|
||||
true
|
||||
else
|
||||
# Otherwise test file extension
|
||||
ret = string.extract(pattern='\.(.+)$',file)
|
||||
if list.length(ret) != 0 then
|
||||
ext = ret["1"]
|
||||
list.mem(ext,aac_filexts)
|
||||
else
|
||||
false
|
||||
end
|
||||
ext = ret["1"]
|
||||
list.mem(ext,aac_filexts)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if os.type != "Win32" then
|
||||
if test_process("which faad") then
|
||||
log(level=3,"Found faad binary: enabling external faad decoder and \
|
||||
metadata resolver.")
|
||||
|
@ -120,15 +136,13 @@ if os.type != "Win32" then
|
|||
0
|
||||
end
|
||||
end
|
||||
%ifdef add_oblivious_decoder
|
||||
add_oblivious_decoder(name="FAAD",description="Decode files using \
|
||||
the faad binary.", test=test_faad, faad_p)
|
||||
%endif
|
||||
def faad_meta(file) =
|
||||
if faad_test(file) then
|
||||
ret = get_process_lines("faad -i \
|
||||
#{quote(file)} 2>&1")
|
||||
# Yea, this is tuff programming (again) !
|
||||
# Yea, this is ugly programming (again) !
|
||||
def get_meta(l,s)=
|
||||
ret = string.extract(pattern="^(\w+):\s(.+)$",s)
|
||||
if list.length(ret) > 0 then
|
||||
|
@ -147,6 +161,7 @@ if os.type != "Win32" then
|
|||
log(level=3,"Did not find faad binary: faad decoder disabled.")
|
||||
end
|
||||
end
|
||||
%endif
|
||||
|
||||
# Standard function for displaying metadata.
|
||||
# Shows artist and title, using "Unknown" when a field is empty.
|
||||
|
@ -189,3 +204,22 @@ def notify_metadata(~urgency="low",~icon="stock_smiley-22",~time=3000,
|
|||
^ ' -t #{time} #{quote(title)} '
|
||||
on_metadata(fun (m) -> system(send^quote(display(m))),s)
|
||||
end
|
||||
|
||||
%ifdef input.external
|
||||
# Stream data from mplayer
|
||||
# @category Source / Input
|
||||
# @param s data URI.
|
||||
# @param ~restart restart on exit.
|
||||
# @param ~restart_on_error restart on exit with error.
|
||||
# @param ~buffer Duration of the pre-buffered data.
|
||||
# @param ~max Maximum duration of the buffered data.
|
||||
def input.mplayer(~id="input.mplayer",
|
||||
~restart=true,~restart_on_error=false,
|
||||
~buffer=0.2,~max=10.,s) =
|
||||
input.external(id=id,restart=restart,
|
||||
restart_on_error=restart_on_error,
|
||||
buffer=buffer,max=max,
|
||||
"mplayer -really-quiet -ao pcm:file=/dev/stdout \
|
||||
-vc null -vo null #{quote(s)} 2>/dev/null")
|
||||
end
|
||||
%endif
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
set("log.file",false)
|
||||
|
||||
echo = fun (x) -> system("echo "^quote(x))
|
||||
|
||||
def test(lbl,f)
|
||||
if f() then echo(lbl) else system("echo fail "^lbl) end
|
||||
end
|
||||
|
||||
test("1",{ 1==1 })
|
||||
test("2",{ 1+1==2 })
|
||||
test("3",{ (-1)+2==1 })
|
||||
test("4",{ (-1)+2 <= 3*2 })
|
||||
test("5",{ true })
|
||||
test("6",{ true and true })
|
||||
test("7",{ 1==1 and 1==1 })
|
||||
test("8",{ (1==1) and (1==1) })
|
||||
test("9",{ true and (-1)+2 <= 3*2 })
|
||||
|
||||
l = [ ("bla",""), ("bli","x"), ("blo","xx"), ("blu","xxx"), ("dix","10") ]
|
||||
echo(l["dix"])
|
||||
test("11",{ 2 == list.length(string.split(separator="",l["blo"])) })
|
||||
|
||||
%ifdef foobarbaz
|
||||
if = if is not a well-formed expression, and we do not care...
|
||||
%endif
|
||||
|
||||
echo("1#{1+1}")
|
||||
echo(string_of(int_of_float(float_of_string(default=13.,"blah"))))
|
||||
|
||||
f=fun(x)->x
|
||||
# Checking that the following is not recursive:
|
||||
f=fun(x)->f(x)
|
||||
print(f(14))
|
|
@ -1,112 +0,0 @@
|
|||
# Check these examples with: liquidsoap --no-libs -i -c typing.liq
|
||||
|
||||
# TODO Throughout this file, parsing locations displayed in error messages
|
||||
# are often much too inaccurate.
|
||||
|
||||
set("log.file",false)
|
||||
|
||||
# Check that some polymorphism is allowed.
|
||||
# id :: (string,'a)->'a
|
||||
def id(a,b)
|
||||
log(a)
|
||||
b
|
||||
end
|
||||
ignore("bla"==id("bla","bla"))
|
||||
ignore(0==id("zero",0))
|
||||
|
||||
# Reporting locations for the next error is non-trivial, because it is about
|
||||
# an instantiation of the type of id. The deep error doesn't have relevant
|
||||
# information about why the int should be a string, the outer one has.
|
||||
# id(0,0)
|
||||
|
||||
# Polymorphism is limited to outer generalizations, this is not system F.
|
||||
# apply :: ((string)->'a)->'a
|
||||
def apply(f)
|
||||
f("bla")
|
||||
# f is not polymorphic, the following is forbidden:
|
||||
# f(0)
|
||||
# f(f)
|
||||
end
|
||||
|
||||
# The level checks forbid abusive generalization.
|
||||
# id' :: ('a)->'a
|
||||
def id'(x)
|
||||
# If one isn't careful about levels/scoping, f2 gets the type ('a)->'b
|
||||
# and so does twisted_id.
|
||||
def f2(y)
|
||||
x
|
||||
end
|
||||
f2(x)
|
||||
end
|
||||
|
||||
# More errors...
|
||||
# 0=="0"
|
||||
# [3,""]
|
||||
|
||||
# Subtyping, functions and lists.
|
||||
f1 = fun () -> 3
|
||||
f2 = fun (a=1) -> a
|
||||
|
||||
# This is OK, l1 is a list of elements of type f1.
|
||||
l1 = [f1,f2]
|
||||
list.iter(fun (f) -> log(string_of(f())), l1)
|
||||
# Forbidden. Indeed, f1 doesn't accept any argument -- although f2 does.
|
||||
# Here the error message may even be too detailed since it goes back to the
|
||||
# definition of l1 and requires that f1 has type (int)->int.
|
||||
# list.iter(fun (f) -> log(string_of(f(42))), l1)
|
||||
|
||||
# Actually, this is forbidden too, but the reason is more complex...
|
||||
# The infered type for the function is ((int)->int)->unit,
|
||||
# and (int)->int is not a subtype of (?int)->int.
|
||||
# There's no most general answer here since (?int)->int is not a
|
||||
# subtype of (int)->int either.
|
||||
# list.iter(fun (f) -> log(string_of(f(42))), [f2])
|
||||
|
||||
# Unlike l1, this is not OK, since we don't leave open subtyping constraints
|
||||
# while infering types.
|
||||
# I hope we can make the inference smarter in the future, without obfuscating
|
||||
# the error messages too much.
|
||||
# The type error here shows the use of all the displayed positions:
|
||||
# f1 has type t1, f2 has type t2, t1 should be <: t2
|
||||
# l2 = [ f2, f1 ]
|
||||
|
||||
# An error where contravariance flips the roles of both sides..
|
||||
# [fun (x) -> x+1, fun (y) -> y^"."]
|
||||
|
||||
# An error without much locations..
|
||||
# TODO An explaination about the missing label would help a lot here.
|
||||
# def f(f)
|
||||
# f(output.icecast.vorbis)
|
||||
# f(output.icecast.mp3)
|
||||
# end
|
||||
|
||||
# This causes an occur-check error.
|
||||
# TODO The printing of the types breaks the sharing of one EVAR
|
||||
# across two types. Here the sharing is actually the origin of the occur-check
|
||||
# error. And it's not easy to understand..
|
||||
# omega = fun (x) -> x(x)
|
||||
|
||||
# Now let's test ad-hoc polymorphism.
|
||||
|
||||
echo = fun(x) -> system("echo #{quote(string_of(x))}")
|
||||
|
||||
echo("bla")
|
||||
echo((1,3.12))
|
||||
echo(1 + 1)
|
||||
echo(1. + 2.14)
|
||||
|
||||
# string is not a Num
|
||||
# echo("bl"+"a")
|
||||
|
||||
echo(1 <= 2)
|
||||
echo((1,2) == (1,3))
|
||||
|
||||
# float <> int
|
||||
# echo(1 == 2.)
|
||||
|
||||
# source is not an Ord
|
||||
# echo(blank()==blank())
|
||||
|
||||
def sum_eq(a,b)
|
||||
a+b == a
|
||||
end
|
|
@ -300,6 +300,25 @@ def server.insert_metadata(~id="",s) =
|
|||
s
|
||||
end
|
||||
|
||||
# Register a command that outputs the RMS of the returned source.
|
||||
# @category Source / Visualization
|
||||
# @param ~id Force the value of the source ID.
|
||||
def server.rms(~id="",s) =
|
||||
x = rms(id=id,s)
|
||||
rms = fst(x)
|
||||
s = snd(x)
|
||||
id = source.id(s)
|
||||
def rms(_) =
|
||||
rms = rms()
|
||||
"#{rms}"
|
||||
end
|
||||
server.register(namespace="#{id}",
|
||||
description="Return the current RMS of the source.",
|
||||
usage="rms",
|
||||
"rms",rms)
|
||||
s
|
||||
end
|
||||
|
||||
# Get the base name of a path.
|
||||
# Implemented using the corresponding shell command.
|
||||
# @category System
|
||||
|
@ -479,59 +498,95 @@ def smart_crossfade (~start_next=5.,~fade_in=3.,~fade_out=3.,
|
|||
end
|
||||
|
||||
# Custom playlist source written using the script language.
|
||||
# Will read directory or playlist, play all files and stop
|
||||
# Will read directory or playlist, play all files and stop.
|
||||
# Returns a pair @(reload,source)@ where @reload@ is a function
|
||||
# of type @(?uri:string)->unit@ used to reload the source and @source@
|
||||
# is the actual source. The reload function can optionally be called
|
||||
# with a new playlist URI. Otherwise, it reloads the previous URI.
|
||||
# @category Source / Input
|
||||
# @param ~id Force the value of the source ID.
|
||||
# @param ~random Randomize playlist content
|
||||
# @param ~on_done Function to execute when the playlist is finished
|
||||
# @param uri Playlist URI
|
||||
def playlist.once(~random=false,~on_done={()},uri)
|
||||
x = ref 0
|
||||
def playlist.custom(files)
|
||||
length = list.length(files)
|
||||
if length == 0 then
|
||||
log("Empty playlist..")
|
||||
fail ()
|
||||
else
|
||||
files =
|
||||
if random then
|
||||
list.sort(fun (x,y) -> int_of_float(random.float()), files)
|
||||
else
|
||||
files
|
||||
end
|
||||
def next () =
|
||||
state = !x
|
||||
file =
|
||||
if state < length then
|
||||
x := state + 1
|
||||
list.nth(files,state)
|
||||
else
|
||||
# Playlist finished
|
||||
def playlist.reloadable(~id="",~random=false,~on_done={()},uri)
|
||||
# A reference to the playlist
|
||||
playlist = ref []
|
||||
# A reference to the uri
|
||||
playlist_uri = ref uri
|
||||
# A reference to know if the source
|
||||
# has been stopped
|
||||
has_stopped = ref false
|
||||
# The next function
|
||||
def next () =
|
||||
file =
|
||||
if list.length(!playlist) > 0 then
|
||||
ret = list.hd(!playlist)
|
||||
playlist := list.tl(!playlist)
|
||||
ret
|
||||
else
|
||||
# Playlist finished
|
||||
if not !has_stopped then
|
||||
on_done ()
|
||||
""
|
||||
end
|
||||
request.create(file)
|
||||
has_stopped := true
|
||||
""
|
||||
end
|
||||
request.dynamic(next)
|
||||
request.create(file)
|
||||
end
|
||||
# Instanciate the source
|
||||
source = request.dynamic(id=id,next)
|
||||
# Get its id.
|
||||
id = source.id(source)
|
||||
# The load function
|
||||
def load_playlist () =
|
||||
files =
|
||||
if test_process("test -d #{quote(!playlist_uri)}") then
|
||||
log(label=id,"playlist is a directory.")
|
||||
get_process_lines("find #{quote(!playlist_uri)} -type f | sort")
|
||||
else
|
||||
playlist = request.create.raw(!playlist_uri)
|
||||
result =
|
||||
if request.resolve(playlist) then
|
||||
playlist = request.filename(playlist)
|
||||
files = playlist.parse(playlist)
|
||||
list.map(snd,files)
|
||||
else
|
||||
log(label=id,"Couldn't read playlist: request resolution failed.")
|
||||
[]
|
||||
end
|
||||
request.destroy(playlist)
|
||||
result
|
||||
end
|
||||
if random then
|
||||
playlist := list.sort(fun (x,y) -> int_of_float(random.float()), files)
|
||||
else
|
||||
playlist := files
|
||||
end
|
||||
end
|
||||
if test_process("test -d #{quote(uri)}") then
|
||||
files = get_process_lines("find #{quote(uri)} -type f | sort")
|
||||
playlist.custom(files)
|
||||
else
|
||||
playlist = request.create.raw(uri)
|
||||
result =
|
||||
if request.resolve(playlist) then
|
||||
playlist = request.filename(playlist)
|
||||
files = playlist.parse(playlist)
|
||||
files = list.map(snd,files)
|
||||
playlist.custom(files)
|
||||
else
|
||||
log("Couldn't read playlist: request resolution failed.")
|
||||
fail ()
|
||||
end
|
||||
request.destroy(playlist)
|
||||
result
|
||||
# The reload function
|
||||
def reload(~uri="") =
|
||||
if uri != "" then
|
||||
playlist_uri := uri
|
||||
end
|
||||
log(label=id,"Reloading playlist with URI #{!playlist_uri}")
|
||||
has_stopped := false
|
||||
load_playlist()
|
||||
end
|
||||
# Load the playlist
|
||||
load_playlist()
|
||||
# Return
|
||||
(reload,source)
|
||||
end
|
||||
|
||||
# Custom playlist source written using the script language.
|
||||
# Will read directory or playlist, play all files and stop
|
||||
# @category Source / Input
|
||||
# @param ~id Force the value of the source ID.
|
||||
# @param ~random Randomize playlist content
|
||||
# @param ~on_done Function to execute when the playlist is finished
|
||||
# @param uri Playlist URI
|
||||
def playlist.once(~id="",~random=false,~on_done={()},uri)
|
||||
snd(playlist.reloadable(id=id,random=random,on_done=on_done,uri))
|
||||
end
|
||||
|
||||
# Mixes two streams, with faded transitions between the state when only the
|
||||
|
@ -588,7 +643,8 @@ def exec_at(~freq=1.,~pred,f)
|
|||
add_timeout(freq,check)
|
||||
end
|
||||
|
||||
# Register the replaygain protocol
|
||||
# Register the replaygain protocol.
|
||||
# @category Liquidsoap
|
||||
def replaygain_protocol(arg,delay)
|
||||
# The extraction program
|
||||
extract_replaygain = "#{configure.libdir}/extract-replaygain"
|
||||
|
|
|
@ -1,46 +1,51 @@
|
|||
###########################################
|
||||
# liquidsoap config file #
|
||||
# Liquidsoap config file #
|
||||
###########################################
|
||||
|
||||
###########################################
|
||||
# Output settings #
|
||||
###########################################
|
||||
output_sound_device = false
|
||||
output_icecast_vorbis = true
|
||||
output_icecast_mp3 = false
|
||||
output_shoutcast = false
|
||||
|
||||
###########################################
|
||||
# general settings #
|
||||
# Logging settings #
|
||||
###########################################
|
||||
|
||||
log_file = "/var/log/airtime/pypo-liquidsoap/<script>.log"
|
||||
log_level = 3
|
||||
|
||||
###########################################
|
||||
# stream settings #
|
||||
# Icecast Stream settings #
|
||||
###########################################
|
||||
icecast_host = "127.0.0.1"
|
||||
icecast_port = 8000
|
||||
icecast_pass = "hackme"
|
||||
|
||||
###########################################
|
||||
# webstream mountpoint names #
|
||||
###########################################
|
||||
# Icecast mountpoint names
|
||||
mount_point_mp3 = "airtime.mp3"
|
||||
mount_point_vorbis = "airtime.ogg"
|
||||
|
||||
###########################################
|
||||
# webstream metadata settings #
|
||||
###########################################
|
||||
# Webstream metadata settings
|
||||
icecast_url = "http://airtime.sourcefabric.org"
|
||||
icecast_description = "Airtime Radio!"
|
||||
icecast_genre = "genre"
|
||||
|
||||
###########################################
|
||||
#liquidsoap output settings #
|
||||
###########################################
|
||||
output_sound_device = false
|
||||
output_icecast_vorbis = true
|
||||
output_icecast_mp3 = false
|
||||
|
||||
|
||||
#audio stream metadata for vorbis/ogg is disabled by default
|
||||
#due to a large number of client media players that disconnect
|
||||
#when the metadata changes to that of a new track. Some versions of
|
||||
#mplayer and VLC have this problem. Enable this option at your
|
||||
#own risk!
|
||||
# Audio stream metadata for vorbis/ogg is disabled by default
|
||||
# due to a number of client media players that disconnect
|
||||
# when the metadata changes to a new track. Some versions of
|
||||
# mplayer and VLC have this problem. Enable this option at your
|
||||
# own risk!
|
||||
output_icecast_vorbis_metadata = false
|
||||
|
||||
###########################################
|
||||
# Shoutcast Stream settings #
|
||||
###########################################
|
||||
shoutcast_host = "127.0.0.1"
|
||||
shoutcast_port = 9000
|
||||
shoutcast_pass = "testing"
|
||||
|
||||
# Webstream metadata settings
|
||||
shoutcast_url = "http://airtime.sourcefabric.org"
|
||||
shoutcast_genre = "genre"
|
||||
|
|
|
@ -7,6 +7,7 @@ set("server.telnet", true)
|
|||
set("server.telnet.port", 1234)
|
||||
|
||||
queue = request.queue(id="queue", length=0.5)
|
||||
queue = cue_cut(queue)
|
||||
queue = audio_to_stereo(queue)
|
||||
|
||||
pypo_data = ref '0'
|
||||
|
@ -57,8 +58,10 @@ end
|
|||
|
||||
if output_icecast_mp3 then
|
||||
out_mp3 = output.icecast(%mp3,
|
||||
host = icecast_host, port = icecast_port,
|
||||
password = icecast_pass, mount = mount_point_mp3,
|
||||
host = icecast_host,
|
||||
port = icecast_port,
|
||||
password = icecast_pass,
|
||||
mount = mount_point_mp3,
|
||||
fallible = true,
|
||||
restart = true,
|
||||
restart_delay = 5,
|
||||
|
@ -69,11 +72,12 @@ if output_icecast_mp3 then
|
|||
end
|
||||
|
||||
if output_icecast_vorbis then
|
||||
|
||||
if output_icecast_vorbis_metadata then
|
||||
out_vorbis = output.icecast(%vorbis,
|
||||
host = icecast_host, port = icecast_port,
|
||||
password = icecast_pass, mount = mount_point_vorbis,
|
||||
host = icecast_host,
|
||||
port = icecast_port,
|
||||
password = icecast_pass,
|
||||
mount = mount_point_vorbis,
|
||||
fallible = true,
|
||||
restart = true,
|
||||
restart_delay = 5,
|
||||
|
@ -86,16 +90,30 @@ if output_icecast_vorbis then
|
|||
#with vlc and mplayer disconnecting at the end of every track
|
||||
s = add(normalize=false, [amplify(0.00001, noise()),s])
|
||||
out_vorbis = output.icecast(%vorbis,
|
||||
host = icecast_host, port = icecast_port,
|
||||
password = icecast_pass, mount = mount_point_vorbis,
|
||||
host = icecast_host,
|
||||
port = icecast_port,
|
||||
password = icecast_pass,
|
||||
mount = mount_point_vorbis,
|
||||
fallible = true,
|
||||
restart = true,
|
||||
restart_delay = 5,
|
||||
url = icecast_url,
|
||||
description = icecast_description,
|
||||
genre = icecast_genre,
|
||||
s)
|
||||
s)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
if output_shoutcast then
|
||||
out_shoutcast = output.shoutcast(%mp3,
|
||||
host = shoutcast_host,
|
||||
port = shoutcast_port,
|
||||
password = shoutcast_pass,
|
||||
fallible = true,
|
||||
restart = true,
|
||||
restart_delay = 5,
|
||||
url = shoutcast_url,
|
||||
genre = shoutcast_genre,
|
||||
s)
|
||||
end
|
||||
|
||||
|
|
|
@ -90,7 +90,6 @@ class Global:
|
|||
print '*****************************************'
|
||||
print '\033[0;32m%s %s\033[m' % ('scheduled at:', str(pkey))
|
||||
print 'cached at : ' + self.cache_dir + str(pkey)
|
||||
print 'subtype: ' + str(playlist['subtype'])
|
||||
print 'played: ' + str(playlist['played'])
|
||||
print 'schedule id: ' + str(playlist['schedule_id'])
|
||||
print 'duration: ' + str(playlist['duration'])
|
||||
|
|
|
@ -37,7 +37,6 @@ from configobj import ConfigObj
|
|||
# custom imports
|
||||
from util import *
|
||||
from api_clients import *
|
||||
from dls import *
|
||||
|
||||
# Set up command-line options
|
||||
parser = OptionParser()
|
||||
|
|
|
@ -4,7 +4,6 @@ import time
|
|||
import logging
|
||||
import logging.config
|
||||
import shutil
|
||||
import pickle
|
||||
import random
|
||||
import string
|
||||
import json
|
||||
|
@ -12,6 +11,8 @@ import telnetlib
|
|||
import math
|
||||
from threading import Thread
|
||||
from subprocess import Popen, PIPE
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
# For RabbitMQ
|
||||
from kombu.connection import BrokerConnection
|
||||
|
@ -98,19 +99,59 @@ class PypoFetch(Thread):
|
|||
logger.error(" * To fix this, you need to set the 'date.timezone' value in your php.ini file and restart apache.")
|
||||
logger.error(" * See this page for more info (v1.7): http://wiki.sourcefabric.org/x/BQBF")
|
||||
logger.error(" * and also the 'FAQ and Support' page underneath it.")
|
||||
|
||||
"""
|
||||
def get_currently_scheduled(self, playlistsOrMedias, str_tnow_s):
|
||||
for key in playlistsOrMedias:
|
||||
start = playlistsOrMedias[key]['start']
|
||||
end = playlistsOrMedias[key]['end']
|
||||
|
||||
if start <= str_tnow_s and str_tnow_s < end:
|
||||
return key
|
||||
|
||||
return None
|
||||
|
||||
def handle_shows_currently_scheduled(self, playlists):
|
||||
logger = logging.getLogger('fetch')
|
||||
|
||||
dtnow = datetime.today()
|
||||
tnow = dtnow.timetuple()
|
||||
str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
|
||||
|
||||
current_pkey = self.get_currently_scheduled(playlists, str_tnow_s)
|
||||
if current_pkey is not None:
|
||||
logger.debug("FOUND CURRENT PLAYLIST %s", current_pkey)
|
||||
# So we have found that a playlist if currently scheduled
|
||||
# even though we just started pypo. Perhaps there was a
|
||||
# system crash. Lets calculate what position in the playlist
|
||||
# we are supposed to be in.
|
||||
medias = playlists[current_pkey]["medias"]
|
||||
current_mkey = self.get_currently_scheduled(medias, str_tnow_s)
|
||||
if current_mkey is not None:
|
||||
mkey_split = map(int, current_mkey.split('-'))
|
||||
media_start = datetime(mkey_split[0], mkey_split[1], mkey_split[2], mkey_split[3], mkey_split[4], mkey_split[5])
|
||||
logger.debug("Found media item that started at %s.", media_start)
|
||||
|
||||
delta = dtnow - media_start #we get a TimeDelta object from this operation
|
||||
logger.info("Starting media item at %d second point", delta.seconds)
|
||||
"""
|
||||
|
||||
"""
|
||||
Process the schedule
|
||||
- Reads the scheduled entries of a given range (actual time +/- "prepare_ahead" / "cache_for")
|
||||
- Saves a serialized file of the schedule
|
||||
- playlists are prepared. (brought to liquidsoap format) and, if not mounted via nsf, files are copied
|
||||
to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss)
|
||||
- runs the cleanup routine, to get rid of unused cashed files
|
||||
- runs the cleanup routine, to get rid of unused cached files
|
||||
"""
|
||||
def process_schedule(self, schedule_data, export_source):
|
||||
def process_schedule(self, schedule_data, export_source, bootstrapping):
|
||||
logger = logging.getLogger('fetch')
|
||||
playlists = schedule_data["playlists"]
|
||||
|
||||
#if bootstrapping:
|
||||
#TODO: possible allow prepare_playlists to handle this.
|
||||
#self.handle_shows_currently_scheduled(playlists)
|
||||
|
||||
self.check_matching_timezones(schedule_data["server_timezone"])
|
||||
|
||||
# Push stream metadata to liquidsoap
|
||||
|
@ -127,9 +168,9 @@ class PypoFetch(Thread):
|
|||
logger.error("Exception %s", e)
|
||||
status = 0
|
||||
|
||||
# Download all the media and put playlists in liquidsoap format
|
||||
# Download all the media and put playlists in liquidsoap "annotate" format
|
||||
try:
|
||||
liquidsoap_playlists = self.prepare_playlists(playlists)
|
||||
liquidsoap_playlists = self.prepare_playlists(playlists, bootstrapping)
|
||||
except Exception, e: logger.error("%s", e)
|
||||
|
||||
# Send the data to pypo-push
|
||||
|
@ -149,7 +190,7 @@ class PypoFetch(Thread):
|
|||
and stored in a playlist folder.
|
||||
file is e.g. 2010-06-23-15-00-00/17_cue_10.132-123.321.mp3
|
||||
"""
|
||||
def prepare_playlists(self, playlists):
|
||||
def prepare_playlists(self, playlists, bootstrapping):
|
||||
logger = logging.getLogger('fetch')
|
||||
|
||||
liquidsoap_playlists = dict()
|
||||
|
@ -170,27 +211,18 @@ class PypoFetch(Thread):
|
|||
try:
|
||||
os.mkdir(self.cache_dir + str(pkey))
|
||||
except Exception, e:
|
||||
pass
|
||||
logger.error(e)
|
||||
|
||||
#logger.debug('*****************************************')
|
||||
#logger.debug('pkey: ' + str(pkey))
|
||||
#logger.debug('cached at : ' + self.cache_dir + str(pkey))
|
||||
#logger.debug('subtype: ' + str(playlist['subtype']))
|
||||
#logger.debug('played: ' + str(playlist['played']))
|
||||
#logger.debug('schedule id: ' + str(playlist['schedule_id']))
|
||||
#logger.debug('duration: ' + str(playlist['duration']))
|
||||
#logger.debug('source id: ' + str(playlist['x_ident']))
|
||||
#logger.debug('*****************************************')
|
||||
|
||||
if int(playlist['played']) == 1:
|
||||
logger.info("playlist %s already played / sent to liquidsoap, so will ignore it", pkey)
|
||||
|
||||
elif int(playlist['subtype']) > 0 and int(playlist['subtype']) < 5:
|
||||
ls_playlist = self.handle_media_file(playlist, pkey)
|
||||
#June 13, 2011: Commented this block out since we are not currently setting this to '1'
|
||||
#on the server side. Currently using a different method to detect if already played - Martin
|
||||
#if int(playlist['played']) == 1:
|
||||
# logger.info("playlist %s already played / sent to liquidsoap, so will ignore it", pkey)
|
||||
|
||||
ls_playlist = self.handle_media_file(playlist, pkey, bootstrapping)
|
||||
|
||||
liquidsoap_playlists[pkey] = ls_playlist
|
||||
except Exception, e:
|
||||
logger.info("%s", e)
|
||||
logger.error("%s", e)
|
||||
return liquidsoap_playlists
|
||||
|
||||
|
||||
|
@ -199,27 +231,47 @@ class PypoFetch(Thread):
|
|||
This handles both remote and local files.
|
||||
Returns an updated ls_playlist string.
|
||||
"""
|
||||
def handle_media_file(self, playlist, pkey):
|
||||
ls_playlist = []
|
||||
|
||||
def handle_media_file(self, playlist, pkey, bootstrapping):
|
||||
logger = logging.getLogger('fetch')
|
||||
for media in playlist['medias']:
|
||||
|
||||
ls_playlist = []
|
||||
|
||||
dtnow = datetime.today()
|
||||
str_tnow_s = dtnow.strftime('%Y-%m-%d-%H-%M-%S')
|
||||
|
||||
sortedKeys = sorted(playlist['medias'].iterkeys())
|
||||
|
||||
for key in sortedKeys:
|
||||
media = playlist['medias'][key]
|
||||
logger.debug("Processing track %s", media['uri'])
|
||||
|
||||
if bootstrapping:
|
||||
start = media['start']
|
||||
end = media['end']
|
||||
|
||||
if end <= str_tnow_s:
|
||||
continue
|
||||
elif start <= str_tnow_s and str_tnow_s < end:
|
||||
#song is currently playing and we just started pypo. Maybe there
|
||||
#was a power outage? Let's restart playback of this song.
|
||||
start_split = map(int, start.split('-'))
|
||||
media_start = datetime(start_split[0], start_split[1], start_split[2], start_split[3], start_split[4], start_split[5])
|
||||
logger.debug("Found media item that started at %s.", media_start)
|
||||
|
||||
delta = dtnow - media_start #we get a TimeDelta object from this operation
|
||||
logger.info("Starting media item at %d second point", delta.seconds)
|
||||
media['cue_in'] = delta.seconds + 10
|
||||
td = timedelta(seconds=10)
|
||||
playlist['start'] = (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S')
|
||||
logger.info("Crash detected, setting playlist to restart at %s", (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S'))
|
||||
|
||||
|
||||
fileExt = os.path.splitext(media['uri'])[1]
|
||||
try:
|
||||
if str(media['cue_in']) == '0' and str(media['cue_out']) == '0':
|
||||
#logger.debug('No cue in/out detected for this file')
|
||||
dst = "%s%s/%s%s" % (self.cache_dir, str(pkey), str(media['id']), str(fileExt))
|
||||
do_cue = False
|
||||
else:
|
||||
#logger.debug('Cue in/out detected')
|
||||
dst = "%s%s/%s_cue_%s-%s%s" % \
|
||||
(self.cache_dir, str(pkey), str(media['id']), str(float(media['cue_in']) / 1000), str(float(media['cue_out']) / 1000), str(fileExt))
|
||||
do_cue = True
|
||||
dst = "%s%s/%s%s" % (self.cache_dir, str(pkey), str(media['id']), str(fileExt))
|
||||
|
||||
# download media file
|
||||
self.handle_remote_file(media, dst, do_cue)
|
||||
self.handle_remote_file(media, dst)
|
||||
|
||||
if True == os.access(dst, os.R_OK):
|
||||
# check filesize (avoid zero-byte files)
|
||||
|
@ -230,11 +282,13 @@ class PypoFetch(Thread):
|
|||
|
||||
if fsize > 0:
|
||||
pl_entry = \
|
||||
'annotate:export_source="%s",media_id="%s",liq_start_next="%s",liq_fade_in="%s",liq_fade_out="%s",schedule_table_id="%s":%s'\
|
||||
% (str(media['export_source']), media['id'], 0, str(float(media['fade_in']) / 1000), \
|
||||
str(float(media['fade_out']) / 1000), media['row_id'],dst)
|
||||
|
||||
#logger.debug(pl_entry)
|
||||
'annotate:export_source="%s",media_id="%s",liq_start_next="%s",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \
|
||||
% (str(media['export_source']), media['id'], 0, \
|
||||
str(float(media['fade_in']) / 1000), \
|
||||
str(float(media['fade_out']) / 1000), \
|
||||
str(float(media['cue_in'])), \
|
||||
str(float(media['cue_out'])), \
|
||||
media['row_id'], dst)
|
||||
|
||||
"""
|
||||
Tracks are only added to the playlist if they are accessible
|
||||
|
@ -248,7 +302,6 @@ class PypoFetch(Thread):
|
|||
entry['show_name'] = playlist['show_name']
|
||||
ls_playlist.append(entry)
|
||||
|
||||
#logger.debug("everything ok, adding %s to playlist", pl_entry)
|
||||
else:
|
||||
logger.warning("zero-size file - skipping %s. will not add it to playlist at %s", media['uri'], dst)
|
||||
|
||||
|
@ -262,51 +315,14 @@ class PypoFetch(Thread):
|
|||
"""
|
||||
Download a file from a remote server and store it in the cache.
|
||||
"""
|
||||
def handle_remote_file(self, media, dst, do_cue):
|
||||
def handle_remote_file(self, media, dst):
|
||||
logger = logging.getLogger('fetch')
|
||||
if do_cue == False:
|
||||
if os.path.isfile(dst):
|
||||
pass
|
||||
#logger.debug("file already in cache: %s", dst)
|
||||
else:
|
||||
logger.debug("try to download %s", media['uri'])
|
||||
self.api_client.get_media(media['uri'], dst)
|
||||
|
||||
if os.path.isfile(dst):
|
||||
pass
|
||||
#logger.debug("file already in cache: %s", dst)
|
||||
else:
|
||||
if os.path.isfile(dst):
|
||||
logger.debug("file already in cache: %s", dst)
|
||||
|
||||
else:
|
||||
logger.debug("try to download and cue %s", media['uri'])
|
||||
|
||||
fileExt = os.path.splitext(media['uri'])[1]
|
||||
dst_tmp = config["tmp_dir"] + "".join([random.choice(string.letters) for i in xrange(10)]) + fileExt
|
||||
self.api_client.get_media(media['uri'], dst_tmp)
|
||||
|
||||
# cue
|
||||
logger.debug("STARTING CUE")
|
||||
debugDst = self.cue_file.cue(dst_tmp, dst, float(media['cue_in']) / 1000, float(media['cue_out']) / 1000)
|
||||
logger.debug(debugDst)
|
||||
logger.debug("END CUE")
|
||||
|
||||
if True == os.access(dst, os.R_OK):
|
||||
try: fsize = os.path.getsize(dst)
|
||||
except Exception, e:
|
||||
logger.error("%s", e)
|
||||
fsize = 0
|
||||
|
||||
if fsize > 0:
|
||||
logger.debug('try to remove temporary file: %s' + dst_tmp)
|
||||
try: os.remove(dst_tmp)
|
||||
except Exception, e:
|
||||
logger.error("%s", e)
|
||||
|
||||
else:
|
||||
logger.warning('something went wrong cueing: %s - using uncued file' + dst)
|
||||
try: os.rename(dst_tmp, dst)
|
||||
except Exception, e:
|
||||
logger.error("%s", e)
|
||||
|
||||
logger.debug("try to download %s", media['uri'])
|
||||
self.api_client.get_media(media['uri'], dst)
|
||||
|
||||
"""
|
||||
Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR"
|
||||
|
@ -354,7 +370,7 @@ class PypoFetch(Thread):
|
|||
# most recent schedule. After that we can just wait for updates.
|
||||
status, schedule_data = self.api_client.get_schedule()
|
||||
if status == 1:
|
||||
self.process_schedule(schedule_data, "scheduler")
|
||||
self.process_schedule(schedule_data, "scheduler", True)
|
||||
logger.info("Bootstrap complete: got initial copy of the schedule")
|
||||
|
||||
loops = 1
|
||||
|
@ -373,6 +389,6 @@ class PypoFetch(Thread):
|
|||
status, schedule_data = self.api_client.get_schedule()
|
||||
|
||||
if status == 1:
|
||||
self.process_schedule(schedule_data, "scheduler")
|
||||
self.process_schedule(schedule_data, "scheduler", False)
|
||||
loops += 1
|
||||
|
||||
|
|
|
@ -97,9 +97,11 @@ class PypoPush(Thread):
|
|||
str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
|
||||
|
||||
for pkey in schedule:
|
||||
plstart = pkey[0:19]
|
||||
plstart = schedule[pkey]['start'][0:19]
|
||||
#plstart = pkey[0:19]
|
||||
|
||||
playedFlag = (pkey in playedItems) and playedItems[pkey].get("played", 0)
|
||||
#playedFlag = (pkey in playedItems) and playedItems[pkey].get("played", 0)
|
||||
playedFlag = False
|
||||
|
||||
if plstart == str_tcoming_s or (plstart < str_tcoming_s and plstart > str_tcoming2_s and not playedFlag):
|
||||
logger.debug('Preparing to push playlist scheduled at: %s', pkey)
|
||||
|
@ -119,7 +121,6 @@ class PypoPush(Thread):
|
|||
schedule_tracker = open(self.schedule_tracker_file, "w")
|
||||
pickle.dump(playedItems, schedule_tracker)
|
||||
schedule_tracker.close()
|
||||
#logger.debug("Wrote schedule to disk: "+str(json.dumps(playedItems)))
|
||||
|
||||
# Call API to update schedule states
|
||||
logger.debug("Doing callback to server to update 'played' status.")
|
||||
|
@ -132,7 +133,6 @@ class PypoPush(Thread):
|
|||
currently_on_air = True
|
||||
else:
|
||||
pass
|
||||
#logger.debug('Empty schedule')
|
||||
|
||||
if not currently_on_air and self.liquidsoap_state_play:
|
||||
logger.debug('Notifying Liquidsoap to stop playback.')
|
||||
|
@ -190,7 +190,6 @@ class PypoPush(Thread):
|
|||
logger.debug('Preparing to push playlist %s' % pkey)
|
||||
for item in playlist:
|
||||
annotate = str(item['annotate'])
|
||||
#logger.debug(annotate)
|
||||
tn.write(('queue.push %s\n' % annotate).encode('latin-1'))
|
||||
tn.write(('vars.show_name %s\n' % item['show_name']).encode('latin-1'))
|
||||
|
||||
|
@ -207,7 +206,6 @@ class PypoPush(Thread):
|
|||
|
||||
def load_schedule_tracker(self):
|
||||
logger = logging.getLogger('push')
|
||||
#logger.debug('load_schedule_tracker')
|
||||
playedItems = dict()
|
||||
|
||||
# create the file if it doesnt exist
|
||||
|
@ -220,7 +218,6 @@ class PypoPush(Thread):
|
|||
except Exception, e:
|
||||
logger.error('Error creating schedule tracker file: %s', e)
|
||||
else:
|
||||
#logger.debug('schedule tracker file exists, opening: ' + self.schedule_tracker_file)
|
||||
try:
|
||||
schedule_tracker = open(self.schedule_tracker_file, "r")
|
||||
playedItems = pickle.load(schedule_tracker)
|
||||
|
@ -241,6 +238,6 @@ class PypoPush(Thread):
|
|||
loops = 0
|
||||
try: self.push('scheduler')
|
||||
except Exception, e:
|
||||
logger.error('Pypo Push Error, exiting: %s', e)
|
||||
logger.error('Pypo Push Exception: %s', e)
|
||||
time.sleep(PUSH_INTERVAL)
|
||||
loops += 1
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
def remove_user(username):
|
||||
|
|
|
@ -11,17 +11,20 @@
|
|||
|
||||
USERID=pypo
|
||||
GROUPID=pypo
|
||||
NAME=Airtime
|
||||
NAME=Airtime\ Show\ Recorder
|
||||
|
||||
DAEMON=/usr/bin/airtime-show-recorder
|
||||
DAEMON=/usr/lib/airtime/show-recorder/airtime-show-recorder
|
||||
PIDFILE=/var/run/airtime-show-recorder.pid
|
||||
|
||||
start () {
|
||||
start () {
|
||||
monit monitor airtime-show-recorder >/dev/null 2>&1
|
||||
start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE --startas $DAEMON
|
||||
}
|
||||
|
||||
stop () {
|
||||
# Send TERM after 5 seconds, wait at most 30 seconds.
|
||||
|
||||
monit unmonitor airtime-show-recorder >/dev/null 2>&1
|
||||
start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE
|
||||
rm -f $PIDFILE
|
||||
}
|
||||
|
|
|
@ -63,14 +63,10 @@ try:
|
|||
os.system("chmod -R 755 "+config["bin_dir"])
|
||||
os.system("chown -R pypo:pypo "+config["bin_dir"])
|
||||
|
||||
print "Creating symbolic links"
|
||||
os.system("rm -f /usr/bin/airtime-show-recorder")
|
||||
os.system("ln -s "+config["bin_dir"]+"/airtime-show-recorder /usr/bin/")
|
||||
|
||||
print "Installing show-recorder daemon"
|
||||
shutil.copy(config["bin_dir"]+"/airtime-show-recorder-init-d", "/etc/init.d/airtime-show-recorder")
|
||||
|
||||
p = Popen("update-rc.d airtime-show-recorder defaults", shell=True)
|
||||
p = Popen("update-rc.d airtime-show-recorder defaults >/dev/null 2>&1", shell=True)
|
||||
sts = os.waitpid(p.pid, 0)[1]
|
||||
|
||||
print "Waiting for processes to start..."
|
||||
|
|
|
@ -12,7 +12,7 @@ if os.geteuid() != 0:
|
|||
PATH_INI_FILE = '/etc/airtime/recorder.cfg'
|
||||
|
||||
def remove_path(path):
|
||||
os.system("rm -rf " + path)
|
||||
os.system('rm -rf "%s"' % path)
|
||||
|
||||
def get_current_script_dir():
|
||||
current_script_dir = os.path.realpath(__file__)
|
||||
|
@ -29,7 +29,7 @@ try:
|
|||
|
||||
os.system("/etc/init.d/airtime-show-recorder stop")
|
||||
os.system("rm -f /etc/init.d/airtime-show-recorder")
|
||||
os.system("update-rc.d -f airtime-show-recorder remove")
|
||||
os.system("update-rc.d -f airtime-show-recorder remove >/dev/null 2>&1")
|
||||
|
||||
print "Removing log directories"
|
||||
remove_path(config["log_dir"])
|
||||
|
|
Loading…
Reference in New Issue