Merge branch 'devel' of dev.sourcefabric.org:airtime into devel
This commit is contained in:
commit
15eae0698b
|
@ -125,6 +125,8 @@ class PlaylistController extends Zend_Controller_Action
|
||||||
private function playlistNoPermission($p_type)
|
private function playlistNoPermission($p_type)
|
||||||
{
|
{
|
||||||
$this->view->error = "You don't have permission to delete selected {$p_type}(s).";
|
$this->view->error = "You don't have permission to delete selected {$p_type}(s).";
|
||||||
|
$this->changePlaylist(null, $p_type);
|
||||||
|
$this->createFullResponse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function playlistUnknownError($e)
|
private function playlistUnknownError($e)
|
||||||
|
@ -221,6 +223,8 @@ class PlaylistController extends Zend_Controller_Action
|
||||||
$this->createFullResponse($obj);
|
$this->createFullResponse($obj);
|
||||||
} catch (PlaylistNoPermissionException $e) {
|
} catch (PlaylistNoPermissionException $e) {
|
||||||
$this->playlistNoPermission($type);
|
$this->playlistNoPermission($type);
|
||||||
|
} catch (BlockNoPermissionException $e) {
|
||||||
|
$this->playlistNoPermission($type);
|
||||||
} catch (PlaylistNotFoundException $e) {
|
} catch (PlaylistNotFoundException $e) {
|
||||||
$this->playlistNotFound($type);
|
$this->playlistNotFound($type);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|
|
@ -192,7 +192,7 @@ class Application_Model_Block implements Application_Model_LibraryEditable
|
||||||
|
|
||||||
$files = array();
|
$files = array();
|
||||||
$sql = <<<"EOT"
|
$sql = <<<"EOT"
|
||||||
SELECT pc.id as id, pc.position, pc.cliplength as length, pc.cuein, pc.cueout, pc.fadein, pc.fadeout, bl.type,
|
SELECT pc.id as id, pc.position, pc.cliplength as length, pc.cuein, pc.cueout, pc.fadein, pc.fadeout, bl.type, f.length as orig_length,
|
||||||
f.id as item_id, f.track_title, f.artist_name as creator, f.file_exists as exists, f.filepath as path FROM cc_blockcontents AS pc
|
f.id as item_id, f.track_title, f.artist_name as creator, f.file_exists as exists, f.filepath as path FROM cc_blockcontents AS pc
|
||||||
LEFT JOIN cc_files AS f ON pc.file_id=f.id
|
LEFT JOIN cc_files AS f ON pc.file_id=f.id
|
||||||
LEFT JOIN cc_block AS bl ON pc.block_id = bl.id
|
LEFT JOIN cc_block AS bl ON pc.block_id = bl.id
|
||||||
|
@ -220,6 +220,10 @@ EOT;
|
||||||
$fades = $this->getFadeInfo($row['position']);
|
$fades = $this->getFadeInfo($row['position']);
|
||||||
$row['fadein'] = $fades[0];
|
$row['fadein'] = $fades[0];
|
||||||
$row['fadeout'] = $fades[1];
|
$row['fadeout'] = $fades[1];
|
||||||
|
|
||||||
|
//format original length
|
||||||
|
$formatter = new LengthFormatter($row['orig_length']);
|
||||||
|
$row['orig_length'] = $formatter->format();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $rows;
|
return $rows;
|
||||||
|
@ -871,11 +875,20 @@ EOT;
|
||||||
*/
|
*/
|
||||||
public static function deleteBlocks($p_ids, $p_userId)
|
public static function deleteBlocks($p_ids, $p_userId)
|
||||||
{
|
{
|
||||||
$leftOver = self::blocksNotOwnedByUser($p_ids, $p_userId);
|
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
|
||||||
if (count($leftOver) == 0) {
|
$user = new Application_Model_User($userInfo->id);
|
||||||
CcBlockQuery::create()->findPKs($p_ids)->delete();
|
$isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
|
||||||
|
|
||||||
|
if (!$isAdminOrPM) {
|
||||||
|
$leftOver = self::blocksNotOwnedByUser($p_ids, $p_userId);
|
||||||
|
|
||||||
|
if (count($leftOver) == 0) {
|
||||||
|
CcBlockQuery::create()->findPKs($p_ids)->delete();
|
||||||
|
} else {
|
||||||
|
throw new BlockNoPermissionException;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new BlockNoPermissionException;
|
CcBlockQuery::create()->findPKs($p_ids)->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,17 +90,21 @@ class Application_Model_Datatables
|
||||||
// we need to go over all items and fix length for playlist
|
// we need to go over all items and fix length for playlist
|
||||||
// in case the playlist contains dynamic block
|
// in case the playlist contains dynamic block
|
||||||
foreach ($results as &$r) {
|
foreach ($results as &$r) {
|
||||||
if ($r['ftype'] == 'playlist') {
|
//this function is also called for Manage Users so in
|
||||||
$pl = new Application_Model_Playlist($r['id']);
|
//this case there will be no 'ftype'
|
||||||
$r['length'] = $pl->getLength();
|
if (isset($r['ftype'])) {
|
||||||
} else if ($r['ftype'] == "block") {
|
if ($r['ftype'] == 'playlist') {
|
||||||
$bl = new Application_Model_Block($r['id']);
|
$pl = new Application_Model_Playlist($r['id']);
|
||||||
if ($bl->isStatic()) {
|
$r['length'] = $pl->getLength();
|
||||||
$r['bl_type'] = 'static';
|
} else if ($r['ftype'] == "block") {
|
||||||
} else {
|
$bl = new Application_Model_Block($r['id']);
|
||||||
$r['bl_type'] = 'dynamic';
|
if ($bl->isStatic()) {
|
||||||
|
$r['bl_type'] = 'static';
|
||||||
|
} else {
|
||||||
|
$r['bl_type'] = 'dynamic';
|
||||||
|
}
|
||||||
|
$r['length'] = $bl->getLength();
|
||||||
}
|
}
|
||||||
$r['length'] = $bl->getLength();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|
|
@ -171,7 +171,8 @@ class Application_Model_Playlist implements Application_Model_LibraryEditable
|
||||||
f.track_title,
|
f.track_title,
|
||||||
f.artist_name AS creator,
|
f.artist_name AS creator,
|
||||||
f.file_exists AS EXISTS,
|
f.file_exists AS EXISTS,
|
||||||
f.filepath AS path
|
f.filepath AS path,
|
||||||
|
f.length AS orig_length
|
||||||
FROM cc_playlistcontents AS pc
|
FROM cc_playlistcontents AS pc
|
||||||
JOIN cc_files AS f ON pc.file_id=f.id
|
JOIN cc_files AS f ON pc.file_id=f.id
|
||||||
WHERE pc.playlist_id = {$this->id}
|
WHERE pc.playlist_id = {$this->id}
|
||||||
|
@ -188,7 +189,8 @@ class Application_Model_Playlist implements Application_Model_LibraryEditable
|
||||||
(ws.name || ': ' || ws.url) AS title,
|
(ws.name || ': ' || ws.url) AS title,
|
||||||
sub.login AS creator,
|
sub.login AS creator,
|
||||||
't'::boolean AS EXISTS,
|
't'::boolean AS EXISTS,
|
||||||
ws.url AS path
|
ws.url AS path,
|
||||||
|
ws.length AS orig_length
|
||||||
FROM cc_playlistcontents AS pc
|
FROM cc_playlistcontents AS pc
|
||||||
JOIN cc_webstream AS ws ON pc.stream_id=ws.id
|
JOIN cc_webstream AS ws ON pc.stream_id=ws.id
|
||||||
LEFT JOIN cc_subjs AS sub ON sub.id = ws.creator_id
|
LEFT JOIN cc_subjs AS sub ON sub.id = ws.creator_id
|
||||||
|
@ -206,7 +208,8 @@ class Application_Model_Playlist implements Application_Model_LibraryEditable
|
||||||
bl.name AS title,
|
bl.name AS title,
|
||||||
sbj.login AS creator,
|
sbj.login AS creator,
|
||||||
't'::boolean AS EXISTS,
|
't'::boolean AS EXISTS,
|
||||||
NULL::text AS path
|
NULL::text AS path,
|
||||||
|
bl.length AS orig_length
|
||||||
FROM cc_playlistcontents AS pc
|
FROM cc_playlistcontents AS pc
|
||||||
JOIN cc_block AS bl ON pc.block_id=bl.id
|
JOIN cc_block AS bl ON pc.block_id=bl.id
|
||||||
JOIN cc_subjs AS sbj ON bl.creator_id=sbj.id
|
JOIN cc_subjs AS sbj ON bl.creator_id=sbj.id
|
||||||
|
@ -240,6 +243,10 @@ SQL;
|
||||||
$fades = $this->getFadeInfo($row['position']);
|
$fades = $this->getFadeInfo($row['position']);
|
||||||
$row['fadein'] = $fades[0];
|
$row['fadein'] = $fades[0];
|
||||||
$row['fadeout'] = $fades[1];
|
$row['fadeout'] = $fades[1];
|
||||||
|
|
||||||
|
//format original length
|
||||||
|
$formatter = new LengthFormatter($row['orig_length']);
|
||||||
|
$row['orig_length'] = $formatter->format();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $rows;
|
return $rows;
|
||||||
|
@ -867,11 +874,19 @@ SQL;
|
||||||
*/
|
*/
|
||||||
public static function deletePlaylists($p_ids, $p_userId)
|
public static function deletePlaylists($p_ids, $p_userId)
|
||||||
{
|
{
|
||||||
$leftOver = self::playlistsNotOwnedByUser($p_ids, $p_userId);
|
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
|
||||||
if (count($leftOver) == 0) {
|
$user = new Application_Model_User($userInfo->id);
|
||||||
CcPlaylistQuery::create()->findPKs($p_ids)->delete();
|
$isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
|
||||||
|
|
||||||
|
if (!$isAdminOrPM) {
|
||||||
|
$leftOver = self::playlistsNotOwnedByUser($p_ids, $p_userId);
|
||||||
|
if (count($leftOver) == 0) {
|
||||||
|
CcPlaylistQuery::create()->findPKs($p_ids)->delete();
|
||||||
|
} else {
|
||||||
|
throw new PlaylistNoPermissionException;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new PlaylistNoPermissionException;
|
CcPlaylistQuery::create()->findPKs($p_ids)->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1549,7 +1549,7 @@ class Application_Model_Show
|
||||||
$sql = $sql." AND ({$exclude})";
|
$sql = $sql." AND ({$exclude})";
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $con->query($sql);
|
$result = $con->query($sql)->fetchAll();
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ if ($item['type'] == 2) {
|
||||||
'id' => $item["id"],
|
'id' => $item["id"],
|
||||||
'cueIn' => $item['cuein'],
|
'cueIn' => $item['cuein'],
|
||||||
'cueOut' => $item['cueout'],
|
'cueOut' => $item['cueout'],
|
||||||
'origLength' => $item['length'])); ?>
|
'origLength' => $item['orig_length'])); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php }?>
|
<?php }?>
|
||||||
|
|
||||||
|
|
|
@ -59,4 +59,6 @@ class EventContractor(Loggable):
|
||||||
try: del self.store[evt.path]
|
try: del self.store[evt.path]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.unexpected_exception(e)
|
self.unexpected_exception(e)
|
||||||
self.logger.info("Unregistering. Left: '%d'" % len(self.store.keys()))
|
# the next line is commented out because it clutters up logging real
|
||||||
|
# bad
|
||||||
|
#self.logger.info("Unregistering. Left: '%d'" % len(self.store.keys()))
|
||||||
|
|
|
@ -105,10 +105,38 @@ class Metadata(Loggable):
|
||||||
# little bit messy. Some of the handling is in m.m.pure while the rest is
|
# little bit messy. Some of the handling is in m.m.pure while the rest is
|
||||||
# here. Also interface is not very consistent
|
# here. Also interface is not very consistent
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def airtime_dict(d):
|
||||||
|
"""
|
||||||
|
Converts mutagen dictionary 'd' into airtime dictionary
|
||||||
|
"""
|
||||||
|
temp_dict = {}
|
||||||
|
for m_key, m_val in d.iteritems():
|
||||||
|
# TODO : some files have multiple fields for the same metadata.
|
||||||
|
# genre is one example. In that case mutagen will return a list
|
||||||
|
# of values
|
||||||
|
assign_val = m_val[0] if isinstance(m_val, list) else m_val
|
||||||
|
temp_dict[ m_key ] = assign_val
|
||||||
|
airtime_dictionary = {}
|
||||||
|
for muta_k, muta_v in temp_dict.iteritems():
|
||||||
|
# We must check if we can actually translate the mutagen key into
|
||||||
|
# an airtime key before doing the conversion
|
||||||
|
if muta_k in mutagen2airtime:
|
||||||
|
airtime_key = mutagen2airtime[muta_k]
|
||||||
|
# Apply truncation in the case where airtime_key is in our
|
||||||
|
# truncation table
|
||||||
|
muta_v = \
|
||||||
|
truncate_to_length(muta_v, truncate_table[airtime_key])\
|
||||||
|
if airtime_key in truncate_table else muta_v
|
||||||
|
airtime_dictionary[ airtime_key ] = muta_v
|
||||||
|
return airtime_dictionary
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_unsafe(path,md):
|
def write_unsafe(path,md):
|
||||||
"""
|
"""
|
||||||
Writes 'md' metadata into 'path' through mutagen
|
Writes 'md' metadata into 'path' through mutagen. Converts all
|
||||||
|
dictionary values to strings because mutagen will not write anything
|
||||||
|
else
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(path): raise BadSongFile(path)
|
if not os.path.exists(path): raise BadSongFile(path)
|
||||||
song_file = mutagen.File(path, easy=True)
|
song_file = mutagen.File(path, easy=True)
|
||||||
|
@ -128,37 +156,13 @@ class Metadata(Loggable):
|
||||||
self.path = fpath
|
self.path = fpath
|
||||||
# TODO : Simplify the way all of these rules are handled right not it's
|
# TODO : Simplify the way all of these rules are handled right not it's
|
||||||
# extremely unclear and needs to be refactored.
|
# extremely unclear and needs to be refactored.
|
||||||
metadata = {}
|
self.__metadata = Metadata.airtime_dict(full_mutagen)
|
||||||
# Load only the metadata avilable in mutagen into metdata
|
|
||||||
for k,v in full_mutagen.iteritems():
|
|
||||||
# Special handling of attributes here
|
|
||||||
if isinstance(v, list):
|
|
||||||
# TODO : some files have multiple fields for the same metadata.
|
|
||||||
# genre is one example. In that case mutagen will return a list
|
|
||||||
# of values
|
|
||||||
metadata[k] = v[0]
|
|
||||||
#if len(v) == 1: metadata[k] = v[0]
|
|
||||||
#else: raise Exception("Unknown mutagen %s:%s" % (k,str(v)))
|
|
||||||
else: metadata[k] = v
|
|
||||||
self.__metadata = {}
|
|
||||||
# Start populating a dictionary of airtime metadata in __metadata
|
|
||||||
for muta_k, muta_v in metadata.iteritems():
|
|
||||||
# We must check if we can actually translate the mutagen key into
|
|
||||||
# an airtime key before doing the conversion
|
|
||||||
if muta_k in mutagen2airtime:
|
|
||||||
airtime_key = mutagen2airtime[muta_k]
|
|
||||||
# Apply truncation in the case where airtime_key is in our
|
|
||||||
# truncation table
|
|
||||||
muta_v = \
|
|
||||||
truncate_to_length(muta_v, truncate_table[airtime_key])\
|
|
||||||
if airtime_key in truncate_table else muta_v
|
|
||||||
self.__metadata[ airtime_key ] = muta_v
|
|
||||||
# Now we extra the special values that are calculated from the mutagen
|
# Now we extra the special values that are calculated from the mutagen
|
||||||
# object itself:
|
# object itself:
|
||||||
for special_key,f in airtime_special.iteritems():
|
for special_key,f in airtime_special.iteritems():
|
||||||
new_val = f(full_mutagen)
|
new_val = f(full_mutagen)
|
||||||
if new_val is not None:
|
if new_val is not None:
|
||||||
self.__metadata[special_key] = f(full_mutagen)
|
self.__metadata[special_key] = new_val
|
||||||
# Finally, we "normalize" all the metadata here:
|
# Finally, we "normalize" all the metadata here:
|
||||||
self.__metadata = mmp.normalized_metadata(self.__metadata, fpath)
|
self.__metadata = mmp.normalized_metadata(self.__metadata, fpath)
|
||||||
# Now we must load the md5:
|
# Now we must load the md5:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import copy
|
import copy
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import hashlib
|
import hashlib
|
||||||
import locale
|
import locale
|
||||||
|
@ -228,34 +229,33 @@ def normalized_metadata(md, original_path):
|
||||||
# Specific rules that are applied in a per attribute basis
|
# Specific rules that are applied in a per attribute basis
|
||||||
format_rules = {
|
format_rules = {
|
||||||
'MDATA_KEY_TRACKNUMBER' : parse_int,
|
'MDATA_KEY_TRACKNUMBER' : parse_int,
|
||||||
'MDATA_KEY_BITRATE' : lambda x: str(int(x) / 1000) + "kbps",
|
|
||||||
'MDATA_KEY_FILEPATH' : lambda x: os.path.normpath(x),
|
'MDATA_KEY_FILEPATH' : lambda x: os.path.normpath(x),
|
||||||
'MDATA_KEY_MIME' : lambda x: x.replace('-','/'),
|
'MDATA_KEY_MIME' : lambda x: x.replace('-','/'),
|
||||||
'MDATA_KEY_BPM' : lambda x: x[0:8],
|
'MDATA_KEY_BPM' : lambda x: x[0:8],
|
||||||
}
|
}
|
||||||
|
|
||||||
new_md = remove_whitespace(new_md)
|
new_md = remove_whitespace(new_md) # remove whitespace fields
|
||||||
|
# Format all the fields in format_rules
|
||||||
new_md = apply_rules_dict(new_md, format_rules)
|
new_md = apply_rules_dict(new_md, format_rules)
|
||||||
new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_TITLE'],
|
# set filetype to audioclip by default
|
||||||
default=no_extension_basename(original_path))
|
|
||||||
new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_CREATOR',
|
|
||||||
'MDATA_KEY_SOURCE'], default=u'')
|
|
||||||
new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_FTYPE'],
|
new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_FTYPE'],
|
||||||
default=u'audioclip')
|
default=u'audioclip')
|
||||||
# In the case where the creator is 'Airtime Show Recorder' we would like to
|
|
||||||
# format the MDATA_KEY_TITLE slightly differently
|
# Try to parse bpm but delete the whole key if that fails
|
||||||
# Note: I don't know why I'm doing a unicode string comparison here
|
if 'MDATA_KEY_BPM' in new_md:
|
||||||
# that part is copied from the original code
|
new_md['MDATA_KEY_BPM'] = parse_int(new_md['MDATA_KEY_BPM'])
|
||||||
|
if new_md['MDATA_KEY_BPM'] is None:
|
||||||
|
del new_md['MDATA_KEY_BPM']
|
||||||
|
|
||||||
if is_airtime_recorded(new_md):
|
if is_airtime_recorded(new_md):
|
||||||
hour,minute,second,name = md['MDATA_KEY_TITLE'].split("-",3)
|
hour,minute,second,name = new_md['MDATA_KEY_TITLE'].split("-",3)
|
||||||
# We assume that MDATA_KEY_YEAR is always given for airtime recorded
|
|
||||||
# shows
|
|
||||||
new_md['MDATA_KEY_TITLE'] = u'%s-%s-%s:%s:%s' % \
|
new_md['MDATA_KEY_TITLE'] = u'%s-%s-%s:%s:%s' % \
|
||||||
(name, new_md['MDATA_KEY_YEAR'], hour, minute, second)
|
(name, new_md['MDATA_KEY_YEAR'], hour, minute, second)
|
||||||
# IMPORTANT: in the original code. MDATA_KEY_FILEPATH would also
|
else:
|
||||||
# be set to the original path of the file for airtime recorded shows
|
# Read title from filename if it does not exist
|
||||||
# (before it was "organized"). We will skip this procedure for now
|
new_md = default_to(dictionary=new_md, keys=['MDATA_KEY_TITLE'],
|
||||||
# because it's not clear why it was done
|
default=no_extension_basename(original_path))
|
||||||
|
|
||||||
return new_md
|
return new_md
|
||||||
|
|
||||||
def organized_path(old_path, root_path, orig_md):
|
def organized_path(old_path, root_path, orig_md):
|
||||||
|
@ -274,15 +274,26 @@ def organized_path(old_path, root_path, orig_md):
|
||||||
if key in dictionary: return len(dictionary[key]) == 0
|
if key in dictionary: return len(dictionary[key]) == 0
|
||||||
else: return True
|
else: return True
|
||||||
# We set some metadata elements to a default "unknown" value because we use
|
# We set some metadata elements to a default "unknown" value because we use
|
||||||
# these fields to create a path hence they cannot be empty
|
# these fields to create a path hence they cannot be empty Here "normal"
|
||||||
|
# means normalized only for organized path
|
||||||
|
|
||||||
|
# MDATA_KEY_BITRATE is in bytes/second i.e. (256000) we want to turn this
|
||||||
|
# into 254kbps
|
||||||
normal_md = default_to_f(orig_md, path_md, unicode_unknown, default_f)
|
normal_md = default_to_f(orig_md, path_md, unicode_unknown, default_f)
|
||||||
|
if normal_md['MDATA_KEY_BITRATE']:
|
||||||
|
formatted = str(int(normal_md['MDATA_KEY_BITRATE']) / 1000)
|
||||||
|
normal_md['MDATA_KEY_BITRATE'] = formatted + 'kbps'
|
||||||
|
else: normal_md['MDATA_KEY_BITRATE'] = unicode_unknown
|
||||||
|
|
||||||
if is_airtime_recorded(normal_md):
|
if is_airtime_recorded(normal_md):
|
||||||
fname = u'%s-%s-%s.%s' % ( normal_md['MDATA_KEY_YEAR'],
|
title_re = re.match("(?P<show>\w+)-(?P<date>\d+-\d+-\d+-\d+:\d+:\d+)$",
|
||||||
normal_md['MDATA_KEY_TITLE'],
|
normal_md['MDATA_KEY_TITLE'])
|
||||||
normal_md['MDATA_KEY_BITRATE'], ext )
|
show_name, = title_re.group('show'),
|
||||||
yyyy, mm, _ = normal_md['MDATA_KEY_YEAR'].split('-',3)
|
date = title_re.group('date').replace(':','-')
|
||||||
path = os.path.join(root_path, yyyy, mm)
|
yyyy, mm, _ = normal_md['MDATA_KEY_YEAR'].split('-',2)
|
||||||
filepath = os.path.join(path,fname)
|
fname_base = '%s-%s-%s.%s' % \
|
||||||
|
(date, show_name, normal_md['MDATA_KEY_BITRATE'], ext)
|
||||||
|
filepath = os.path.join(root_path, yyyy, mm, fname_base)
|
||||||
elif len(normal_md['MDATA_KEY_TRACKNUMBER']) == 0:
|
elif len(normal_md['MDATA_KEY_TRACKNUMBER']) == 0:
|
||||||
fname = u'%s-%s.%s' % (normal_md['MDATA_KEY_TITLE'],
|
fname = u'%s-%s.%s' % (normal_md['MDATA_KEY_TITLE'],
|
||||||
normal_md['MDATA_KEY_BITRATE'], ext)
|
normal_md['MDATA_KEY_BITRATE'], ext)
|
||||||
|
|
|
@ -127,6 +127,8 @@ def main(global_config, api_client_config, log_config,
|
||||||
tt = ToucherThread(path=config['index_path'],
|
tt = ToucherThread(path=config['index_path'],
|
||||||
interval=int(config['touch_interval']))
|
interval=int(config['touch_interval']))
|
||||||
|
|
||||||
|
apiclient.register_component('media-monitor')
|
||||||
|
|
||||||
pyi = manager.pyinotify()
|
pyi = manager.pyinotify()
|
||||||
pyi.loop()
|
pyi.loop()
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
import media.monitor.pure as mmp
|
import media.monitor.pure as mmp
|
||||||
|
from media.monitor.metadata import Metadata
|
||||||
|
|
||||||
class TestMMP(unittest.TestCase):
|
class TestMMP(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -34,23 +35,32 @@ class TestMMP(unittest.TestCase):
|
||||||
for k in def_keys: self.assertEqual( sd[k], 'DEF' )
|
for k in def_keys: self.assertEqual( sd[k], 'DEF' )
|
||||||
|
|
||||||
def test_normalized_metadata(self):
|
def test_normalized_metadata(self):
|
||||||
normal = mmp.normalized_metadata(self.md1,"")
|
# Recorded show test first
|
||||||
self.assertTrue(hasattr(normal['MDATA_KEY_CREATOR'],'startswith'))
|
orig = Metadata.airtime_dict({
|
||||||
self.assertTrue('MDATA_KEY_CREATOR' in normal)
|
'date': [u'2012-08-21'],
|
||||||
self.assertTrue('MDATA_KEY_SOURCE' in normal)
|
'tracknumber': [u'2'],
|
||||||
|
'title': [u'11-29-00-record'],
|
||||||
|
'artist': [u'Airtime Show Recorder']
|
||||||
|
})
|
||||||
|
orga = Metadata.airtime_dict({
|
||||||
|
'date': [u'2012-08-21'],
|
||||||
|
'tracknumber': [u'2'],
|
||||||
|
'artist': [u'Airtime Show Recorder'],
|
||||||
|
'title': [u'record-2012-08-21-11:29:00']
|
||||||
|
})
|
||||||
|
orga['MDATA_KEY_FTYPE'] = u'audioclip'
|
||||||
|
orig['MDATA_KEY_BITRATE'] = u'256000'
|
||||||
|
orga['MDATA_KEY_BITRATE'] = u'256kbps'
|
||||||
|
|
||||||
def test_organized_path(self):
|
old_path = "/home/rudi/recorded/2012-08-21-11:29:00.ogg"
|
||||||
o_path = '/home/rudi/throwaway/ACDC_-_Back_In_Black-sample-64kbps.ogg'
|
normalized = mmp.normalized_metadata(orig, old_path)
|
||||||
normal = mmp.normalized_metadata(self.md1,o_path)
|
print(normalized)
|
||||||
og = mmp.organized_path(o_path,
|
self.assertEqual( orga, normalized )
|
||||||
'/home/rudi/throwaway/fucking_around/watch/',
|
|
||||||
normal)
|
organized_base_name = "2012-08-21-11-29-00-record-256kbps.ogg"
|
||||||
real_path1 = \
|
base = "/srv/airtime/stor/"
|
||||||
u'/home/rudi/throwaway/fucking_around/watch/unknown/unknown/ACDC_-_Back_In_Black-sample-64kbps-64kbps.ogg'
|
organized_path = mmp.organized_path(old_path,base, normalized)
|
||||||
self.assertTrue( 'unknown' in og, True )
|
self.assertEqual(os.path.basename(organized_path), organized_base_name)
|
||||||
self.assertEqual( og, real_path1 ) # TODO : fix this failure
|
|
||||||
# for recorded it should be something like this
|
|
||||||
# ./recorded/2012/07/2012-07-09-17-55-00-Untitled Show-256kbps.ogg
|
|
||||||
|
|
||||||
def test_file_md5(self):
|
def test_file_md5(self):
|
||||||
p = os.path.realpath(__file__)
|
p = os.path.realpath(__file__)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import sys
|
||||||
import pytz
|
import pytz
|
||||||
import signal
|
import signal
|
||||||
import math
|
import math
|
||||||
|
import traceback
|
||||||
|
|
||||||
from configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
|
|
||||||
|
@ -55,16 +56,16 @@ class ShowRecorder(Thread):
|
||||||
|
|
||||||
def __init__ (self, show_instance, show_name, filelength, start_time):
|
def __init__ (self, show_instance, show_name, filelength, start_time):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.logger = logging.getLogger('recorder')
|
self.logger = logging.getLogger('recorder')
|
||||||
self.api_client = api_client(self.logger)
|
self.api_client = api_client(self.logger)
|
||||||
self.filelength = filelength
|
self.filelength = filelength
|
||||||
self.start_time = start_time
|
self.start_time = start_time
|
||||||
self.show_instance = show_instance
|
self.show_instance = show_instance
|
||||||
self.show_name = show_name
|
self.show_name = show_name
|
||||||
self.p = None
|
self.p = None
|
||||||
|
|
||||||
def record_show(self):
|
def record_show(self):
|
||||||
length = str(self.filelength) + ".0"
|
length = str(self.filelength) + ".0"
|
||||||
filename = self.start_time
|
filename = self.start_time
|
||||||
filename = filename.replace(" ", "-")
|
filename = filename.replace(" ", "-")
|
||||||
|
|
||||||
|
@ -73,16 +74,18 @@ class ShowRecorder(Thread):
|
||||||
else:
|
else:
|
||||||
filetype = "ogg";
|
filetype = "ogg";
|
||||||
|
|
||||||
filepath = "%s%s.%s" % (config["base_recorded_files"], filename, filetype)
|
joined_path = os.path.join(config["base_recorded_files"], filename)
|
||||||
|
filepath = "%s.%s" % (joined_path, filetype)
|
||||||
|
|
||||||
br = config["record_bitrate"]
|
br = config["record_bitrate"]
|
||||||
sr = config["record_samplerate"]
|
sr = config["record_samplerate"]
|
||||||
c = config["record_channels"]
|
c = config["record_channels"]
|
||||||
ss = config["record_sample_size"]
|
ss = config["record_sample_size"]
|
||||||
|
|
||||||
#-f:16,2,44100
|
#-f:16,2,44100
|
||||||
#-b:256
|
#-b:256
|
||||||
command = "ecasound -f:%s,%s,%s -i alsa -o %s,%s000 -t:%s" % (ss, c, sr, filepath, br, length)
|
command = "ecasound -f:%s,%s,%s -i alsa -o %s,%s000 -t:%s" % \
|
||||||
|
(ss, c, sr, filepath, br, length)
|
||||||
args = command.split(" ")
|
args = command.split(" ")
|
||||||
|
|
||||||
self.logger.info("starting record")
|
self.logger.info("starting record")
|
||||||
|
@ -144,17 +147,16 @@ class ShowRecorder(Thread):
|
||||||
artist = "Airtime Show Recorder"
|
artist = "Airtime Show Recorder"
|
||||||
|
|
||||||
#set some metadata for our file daemon
|
#set some metadata for our file daemon
|
||||||
recorded_file = mutagen.File(filepath, easy=True)
|
recorded_file = mutagen.File(filepath, easy = True)
|
||||||
recorded_file['title'] = name
|
recorded_file['title'] = name
|
||||||
recorded_file['artist'] = artist
|
recorded_file['artist'] = artist
|
||||||
recorded_file['date'] = md[0]
|
recorded_file['date'] = md[0]
|
||||||
#recorded_file['date'] = md[0].split("-")[0]
|
#recorded_file['date'] = md[0].split("-")[0]
|
||||||
#You cannot pass ints into the metadata of a file. Even tracknumber needs to be a string
|
#You cannot pass ints into the metadata of a file. Even tracknumber needs to be a string
|
||||||
recorded_file['tracknumber'] = unicode(self.show_instance)
|
recorded_file['tracknumber'] = unicode(self.show_instance)
|
||||||
recorded_file.save()
|
recorded_file.save()
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
import traceback
|
|
||||||
top = traceback.format_exc()
|
top = traceback.format_exc()
|
||||||
self.logger.error('Exception: %s', e)
|
self.logger.error('Exception: %s', e)
|
||||||
self.logger.error("traceback: %s", top)
|
self.logger.error("traceback: %s", top)
|
||||||
|
@ -180,20 +182,20 @@ class ShowRecorder(Thread):
|
||||||
class Recorder(Thread):
|
class Recorder(Thread):
|
||||||
def __init__(self, q):
|
def __init__(self, q):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.logger = logging.getLogger('recorder')
|
self.logger = logging.getLogger('recorder')
|
||||||
self.api_client = api_client(self.logger)
|
self.api_client = api_client(self.logger)
|
||||||
self.api_client.register_component("show-recorder")
|
self.sr = None
|
||||||
self.sr = None
|
|
||||||
self.shows_to_record = {}
|
self.shows_to_record = {}
|
||||||
self.server_timezone = ''
|
self.server_timezone = ''
|
||||||
self.queue = q
|
self.queue = q
|
||||||
|
self.loops = 0
|
||||||
|
self.api_client.register_component("show-recorder")
|
||||||
self.logger.info("RecorderFetch: init complete")
|
self.logger.info("RecorderFetch: init complete")
|
||||||
self.loops = 0
|
|
||||||
|
|
||||||
def handle_message(self):
|
def handle_message(self):
|
||||||
if not self.queue.empty():
|
if not self.queue.empty():
|
||||||
message = self.queue.get()
|
message = self.queue.get()
|
||||||
msg = json.loads(message)
|
msg = json.loads(message)
|
||||||
command = msg["event_type"]
|
command = msg["event_type"]
|
||||||
self.logger.info("Received msg from Pypo Message Handler: %s", msg)
|
self.logger.info("Received msg from Pypo Message Handler: %s", msg)
|
||||||
if command == 'cancel_recording':
|
if command == 'cancel_recording':
|
||||||
|
@ -212,8 +214,8 @@ class Recorder(Thread):
|
||||||
shows = m['shows']
|
shows = m['shows']
|
||||||
for show in shows:
|
for show in shows:
|
||||||
show_starts = getDateTimeObj(show[u'starts'])
|
show_starts = getDateTimeObj(show[u'starts'])
|
||||||
show_end = getDateTimeObj(show[u'ends'])
|
show_end = getDateTimeObj(show[u'ends'])
|
||||||
time_delta = show_end - show_starts
|
time_delta = show_end - show_starts
|
||||||
|
|
||||||
temp_shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id'], show[u'name'], m['server_timezone']]
|
temp_shows_to_record[show[u'starts']] = [time_delta, show[u'instance_id'], show[u'name'], m['server_timezone']]
|
||||||
self.shows_to_record = temp_shows_to_record
|
self.shows_to_record = temp_shows_to_record
|
||||||
|
@ -223,10 +225,10 @@ class Recorder(Thread):
|
||||||
tnow = datetime.datetime.utcnow()
|
tnow = datetime.datetime.utcnow()
|
||||||
sorted_show_keys = sorted(self.shows_to_record.keys())
|
sorted_show_keys = sorted(self.shows_to_record.keys())
|
||||||
|
|
||||||
start_time = sorted_show_keys[0]
|
start_time = sorted_show_keys[0]
|
||||||
next_show = getDateTimeObj(start_time)
|
next_show = getDateTimeObj(start_time)
|
||||||
|
|
||||||
delta = next_show - tnow
|
delta = next_show - tnow
|
||||||
s = '%s.%s' % (delta.seconds, delta.microseconds)
|
s = '%s.%s' % (delta.seconds, delta.microseconds)
|
||||||
out = float(s)
|
out = float(s)
|
||||||
|
|
||||||
|
@ -237,36 +239,35 @@ class Recorder(Thread):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def start_record(self):
|
def start_record(self):
|
||||||
if len(self.shows_to_record) != 0:
|
if len(self.shows_to_record) == 0: return None
|
||||||
try:
|
try:
|
||||||
delta = self.get_time_till_next_show()
|
delta = self.get_time_till_next_show()
|
||||||
if delta < 5:
|
if delta < 5:
|
||||||
self.logger.debug("sleeping %s seconds until show", delta)
|
self.logger.debug("sleeping %s seconds until show", delta)
|
||||||
time.sleep(delta)
|
time.sleep(delta)
|
||||||
|
|
||||||
sorted_show_keys = sorted(self.shows_to_record.keys())
|
sorted_show_keys = sorted(self.shows_to_record.keys())
|
||||||
start_time = sorted_show_keys[0]
|
start_time = sorted_show_keys[0]
|
||||||
show_length = self.shows_to_record[start_time][0]
|
show_length = self.shows_to_record[start_time][0]
|
||||||
show_instance = self.shows_to_record[start_time][1]
|
show_instance = self.shows_to_record[start_time][1]
|
||||||
show_name = self.shows_to_record[start_time][2]
|
show_name = self.shows_to_record[start_time][2]
|
||||||
server_timezone = self.shows_to_record[start_time][3]
|
server_timezone = self.shows_to_record[start_time][3]
|
||||||
|
|
||||||
T = pytz.timezone(server_timezone)
|
T = pytz.timezone(server_timezone)
|
||||||
start_time_on_UTC = getDateTimeObj(start_time)
|
start_time_on_UTC = getDateTimeObj(start_time)
|
||||||
start_time_on_server = start_time_on_UTC.replace(tzinfo=pytz.utc).astimezone(T)
|
start_time_on_server = start_time_on_UTC.replace(tzinfo=pytz.utc).astimezone(T)
|
||||||
start_time_formatted = '%(year)d-%(month)02d-%(day)02d %(hour)02d:%(min)02d:%(sec)02d' % \
|
start_time_formatted = '%(year)d-%(month)02d-%(day)02d %(hour)02d:%(min)02d:%(sec)02d' % \
|
||||||
{'year': start_time_on_server.year, 'month': start_time_on_server.month, 'day': start_time_on_server.day, \
|
{'year': start_time_on_server.year, 'month': start_time_on_server.month, 'day': start_time_on_server.day, \
|
||||||
'hour': start_time_on_server.hour, 'min': start_time_on_server.minute, 'sec': start_time_on_server.second}
|
'hour': start_time_on_server.hour, 'min': start_time_on_server.minute, 'sec': start_time_on_server.second}
|
||||||
self.sr = ShowRecorder(show_instance, show_name, show_length.seconds, start_time_formatted)
|
self.sr = ShowRecorder(show_instance, show_name, show_length.seconds, start_time_formatted)
|
||||||
self.sr.start()
|
self.sr.start()
|
||||||
#remove show from shows to record.
|
#remove show from shows to record.
|
||||||
del self.shows_to_record[start_time]
|
del self.shows_to_record[start_time]
|
||||||
#self.time_till_next_show = self.get_time_till_next_show()
|
#self.time_till_next_show = self.get_time_till_next_show()
|
||||||
except Exception, e :
|
except Exception, e :
|
||||||
import traceback
|
top = traceback.format_exc()
|
||||||
top = traceback.format_exc()
|
self.logger.error('Exception: %s', e)
|
||||||
self.logger.error('Exception: %s', e)
|
self.logger.error("traceback: %s", top)
|
||||||
self.logger.error("traceback: %s", top)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Main loop of the thread:
|
Main loop of the thread:
|
||||||
|
@ -284,6 +285,7 @@ class Recorder(Thread):
|
||||||
self.process_recorder_schedule(temp)
|
self.process_recorder_schedule(temp)
|
||||||
self.logger.info("Bootstrap recorder schedule received: %s", temp)
|
self.logger.info("Bootstrap recorder schedule received: %s", temp)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
self.logger.error( traceback.format_exc() )
|
||||||
self.logger.error(e)
|
self.logger.error(e)
|
||||||
|
|
||||||
self.logger.info("Bootstrap complete: got initial copy of the schedule")
|
self.logger.info("Bootstrap complete: got initial copy of the schedule")
|
||||||
|
@ -305,14 +307,15 @@ class Recorder(Thread):
|
||||||
self.process_recorder_schedule(temp)
|
self.process_recorder_schedule(temp)
|
||||||
self.logger.info("updated recorder schedule received: %s", temp)
|
self.logger.info("updated recorder schedule received: %s", temp)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
self.logger.error( traceback.format_exc() )
|
||||||
self.logger.error(e)
|
self.logger.error(e)
|
||||||
try: self.handle_message()
|
try: self.handle_message()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
self.logger.error( traceback.format_exc() )
|
||||||
self.logger.error('Pypo Recorder Exception: %s', e)
|
self.logger.error('Pypo Recorder Exception: %s', e)
|
||||||
time.sleep(PUSH_INTERVAL)
|
time.sleep(PUSH_INTERVAL)
|
||||||
self.loops += 1
|
self.loops += 1
|
||||||
except Exception, e :
|
except Exception, e :
|
||||||
import traceback
|
|
||||||
top = traceback.format_exc()
|
top = traceback.format_exc()
|
||||||
self.logger.error('Exception: %s', e)
|
self.logger.error('Exception: %s', e)
|
||||||
self.logger.error("traceback: %s", top)
|
self.logger.error("traceback: %s", top)
|
||||||
|
|
Loading…
Reference in New Issue