diff --git a/airtime_mvc/application/forms/SmartPlaylistCriteria.php b/airtime_mvc/application/forms/SmartPlaylistCriteria.php
index a12328de9..e5eed0c5d 100644
--- a/airtime_mvc/application/forms/SmartPlaylistCriteria.php
+++ b/airtime_mvc/application/forms/SmartPlaylistCriteria.php
@@ -9,12 +9,12 @@ class Application_Form_SmartPlaylistCriteria extends Zend_Form_SubForm
$criteriaOptions = array(
0 => "Select criteria",
"album_title" => "Album",
- "artist_name" => "Artist",
"bit_rate" => "Bit Rate",
"bpm" => "Bpm",
"comments" => "Comments",
"composer" => "Composer",
"conductor" => "Conductor",
+ "artist_name" => "Creator",
"disc_number" => "Disc Number",
"genre" => "Genre",
"isrc_number" => "ISRC",
diff --git a/airtime_mvc/public/js/airtime/playlist/smart_playlistbuilder.js b/airtime_mvc/public/js/airtime/playlist/smart_playlistbuilder.js
index 760c2a96f..614f1d3b2 100644
--- a/airtime_mvc/public/js/airtime/playlist/smart_playlistbuilder.js
+++ b/airtime_mvc/public/js/airtime/playlist/smart_playlistbuilder.js
@@ -1,35 +1,11 @@
$(document).ready(function() {
setSmartPlaylistEvents();
-
- $(".playlist_type_help_icon").qtip({
- content: {
- text: "A static playlist will save the criteria and generate the playlist content immediately." +
- "This allows you to edit and view it in the Playlist Builder before adding it to a show.
" +
- "A dynamic playlist will only save the criteria. The playlist content will get generated upon " +
- "adding it to a show. You will not be able to view and edit it in the Playlist Builder."
- },
- hide: {
- delay: 500,
- fixed: true
- },
- style: {
- border: {
- width: 0,
- radius: 4
- },
- classes: "ui-tooltip-dark ui-tooltip-rounded"
- },
- position: {
- my: "left bottom",
- at: "right center"
- },
- })
});
function setSmartPlaylistEvents() {
var form = $('#smart-playlist-form');
- form.find('.criteria_add').live("click", function(){
+ form.find('.criteria_add').live('click', function(){
var div = $('dd[id="sp_criteria-element"]').children('div:visible:last').next();
div.show();
@@ -42,7 +18,7 @@ function setSmartPlaylistEvents() {
removeButtonCheck();
});
- form.find('a[id^="criteria_remove"]').live("click", function(){
+ form.find('a[id^="criteria_remove"]').live('click', function(){
var curr = $(this).parent();
var curr_pos = curr.index();
var list = curr.parent();
@@ -207,6 +183,30 @@ function setupUI() {
applyPlatformOpacityRules: false
});
}
+
+ $(".playlist_type_help_icon").qtip({
+ content: {
+ text: "A static playlist will save the criteria and generate the playlist content immediately." +
+ "This allows you to edit and view it in the Playlist Builder before adding it to a show.
" +
+ "A dynamic playlist will only save the criteria. The playlist content will get generated upon " +
+ "adding it to a show. You will not be able to view and edit it in the Playlist Builder."
+ },
+ hide: {
+ delay: 500,
+ fixed: true
+ },
+ style: {
+ border: {
+ width: 0,
+ radius: 4
+ },
+ classes: "ui-tooltip-dark ui-tooltip-rounded"
+ },
+ position: {
+ my: "left bottom",
+ at: "right center"
+ },
+ });
}
function enableAndShowExtraField(valEle, index) {
diff --git a/python_apps/media-monitor/airtimefilemonitor/replaygain.py b/python_apps/media-monitor/airtimefilemonitor/replaygain.py
new file mode 100644
index 000000000..bcb9cf6a7
--- /dev/null
+++ b/python_apps/media-monitor/airtimefilemonitor/replaygain.py
@@ -0,0 +1,107 @@
+from subprocess import Popen, PIPE
+import re
+import os
+import sys
+import shutil
+import tempfile
+
+def get_process_output(command):
+ """
+ Run subprocess and return stdout
+ """
+ p = Popen(command, shell=True, stdout=PIPE)
+ return p.communicate()[0].strip()
+
+def run_process(command):
+ """
+ Run subprocess and return "return code"
+ """
+ p = Popen(command, shell=True)
+ return os.waitpid(p.pid, 0)[1]
+
+def get_mime_type(file_path):
+ """
+ Attempts to get the mime type but will return prematurely if the process
+ takes longer than 5 seconds. Note that this function should only be called
+ for files which do not have a mp3/ogg/flac extension.
+ """
+
+ return get_process_output("timeout 5 file -b --mime-type %s" % file_path)
+
+def duplicate_file(file_path):
+ """
+ Makes a duplicate of the file and returns the path of this duplicate file.
+ """
+ fsrc = open(file_path, 'r')
+ fdst = tempfile.NamedTemporaryFile(delete=False)
+
+ print "Copying %s to %s" % (file_path, fdst.name)
+
+ shutil.copyfileobj(fsrc, fdst)
+
+ fsrc.close()
+ fdst.close()
+
+ return fdst.name
+
+def calculate_replay_gain(file_path):
+ """
+ This function accepts files of type mp3/ogg/flac and returns a calculated ReplayGain value in dB.
+ If the value cannot be calculated for some reason, then we default to 0 (Unity Gain).
+
+ http://wiki.hydrogenaudio.org/index.php?title=ReplayGain_1.0_specification
+ """
+
+ try:
+ """
+ Making a duplicate is required because the ReplayGain extraction utilities we use
+ make unwanted modifications to the file.
+ """
+
+ search = None
+ temp_file_path = duplicate_file(file_path)
+
+ if re.search(r'mp3$', temp_file_path, re.IGNORECASE) or get_mime_type(temp_file_path) == "audio/mpeg":
+ if run_process("which mp3gain > /dev/null") == 0:
+ out = get_process_output('mp3gain -q "%s" 2> /dev/null' % temp_file_path)
+ search = re.search(r'Recommended "Track" dB change: (.*)', out)
+ else:
+ print "mp3gain not found"
+ #Log warning
+ elif re.search(r'ogg$', temp_file_path, re.IGNORECASE) or get_mime_type(temp_file_path) == "application/ogg":
+ if run_process("which vorbisgain > /dev/null && which ogginfo > /dev/null") == 0:
+ run_process('vorbisgain -q -f "%s" 2>/dev/null >/dev/null' % temp_file_path)
+ out = get_process_output('ogginfo "%s"' % temp_file_path)
+ search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out)
+ else:
+ print "vorbisgain/ogginfo not found"
+ #Log warning
+ elif re.search(r'flac$', temp_file_path, re.IGNORECASE) or get_mime_type(temp_file_path) == "audio/x-flac":
+ if run_process("which metaflac > /dev/null") == 0:
+ out = get_process_output('metaflac --show-tag=REPLAYGAIN_TRACK_GAIN "%s"' % temp_file_path)
+ search = re.search(r'REPLAYGAIN_TRACK_GAIN=(.*) dB', out)
+ else:
+ print "metaflac not found"
+ #Log warning
+ else:
+ pass
+ #Log unknown file type.
+
+ #no longer need the temp, file simply remove it.
+ os.remove(temp_file_path)
+ except Exception, e:
+ print e
+
+ replay_gain = 0
+ if search:
+ matches = search.groups()
+ if len(matches) == 1:
+ replay_gain = matches[0]
+
+ return replay_gain
+
+
+# Example of running from command line:
+# python replay_gain.py /path/to/filename.mp3
+if __name__ == "__main__":
+ print calculate_replay_gain(sys.argv[1])
diff --git a/python_apps/pypo/recorder.py b/python_apps/pypo/recorder.py
index 132a687ba..f63f47a5e 100644
--- a/python_apps/pypo/recorder.py
+++ b/python_apps/pypo/recorder.py
@@ -20,7 +20,14 @@ from threading import Thread
import mutagen
-from api_clients import api_client
+from api_clients import api_client as apc
+
+def api_client(logger):
+ """
+ api_client returns the correct instance of AirtimeApiClient. Although there is only one
+ instance to choose from at the moment.
+ """
+ return apc.AirtimeApiClient(logger)
# loading config file
try:
@@ -30,14 +37,17 @@ except Exception, e:
sys.exit()
def getDateTimeObj(time):
+ # TODO : clean up for this function later.
+ # - use tuples to parse result from split (instead of indices)
+ # - perhaps validate the input before doing dangerous casts?
+ # - rename this function to follow the standard convention
+ # - rename time to something else so that the module name does not get
+ # shadowed
+ # - add docstring to document all behaviour of this function
timeinfo = time.split(" ")
- date = timeinfo[0].split("-")
- time = timeinfo[1].split(":")
-
- date = map(int, date)
- time = map(int, time)
-
- return datetime.datetime(date[0], date[1], date[2], time[0], time[1], time[2], 0, None)
+ date = [ int(x) for x in timeinfo[0].split("-") ]
+ my_time = [ int(x) for x in timeinfo[1].split(":") ]
+ return datetime.datetime(date[0], date[1], date[2], my_time[0], my_time[1], my_time[2], 0, None)
PUSH_INTERVAL = 2