Added custom timeruler and duration calculation management command

This commit is contained in:
Fergal Moran
2013-04-21 16:53:04 +01:00
parent 6182f26e1e
commit bb6f66163a
18 changed files with 1612 additions and 82 deletions

View File

@@ -0,0 +1 @@
class Mp3FileNotFoundException(Exception): pass

10
core/utils/audio/mp3.py Normal file
View File

@@ -0,0 +1,10 @@
from mutagen.mp3 import MP3
from core.utils.audio import Mp3FileNotFoundException
def mp3_length(source_file):
try:
audio = MP3(source_file)
return audio.info.length
except IOError:
raise Mp3FileNotFoundException("Audio file not found: %s" % source_file)

View File

@@ -124,13 +124,14 @@ AUTHENTICATION_BACKENDS = global_settings.AUTHENTICATION_BACKENDS + (
)
MIDDLEWARE_CLASSES = (
'django.middleware.gzip.GZipMiddleware',
'htmlmin.middleware.HtmlMinifyMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.gzip.GZipMiddleware',
'spa.middleware.uploadify.SWFUploadMiddleware',
#'spa.middleware.sqlprinter.SqlPrintingMiddleware',
#'debug_toolbar.middleware.DebugToolbarMiddleware',
@@ -251,3 +252,5 @@ COMPRESS_CSS_FILTERS = [
import mimetypes
mimetypes.add_type("text/xml", ".plist", False)
HTML_MINIFY = localsettings.HTML_MINIFY

View File

@@ -1,9 +0,0 @@
from shutil import copyfile
from django.core.management.base import NoArgsCommand
import os
from spa.models import Mix
class Command(NoArgsCommand):
def handle_noargs(self, **options):
mixes = Mix.objects.all()

View File

@@ -0,0 +1,26 @@
import os
from django.core.management.base import NoArgsCommand, CommandError
from core.utils.audio import Mp3FileNotFoundException
from core.utils.audio.mp3 import mp3_length
from dss import settings
from spa.models import Mix
class Command(NoArgsCommand):
help = "Updates audio files with their durations"
def handle(self, *args, **options):
try:
candidates = Mix.objects.filter(duration__isnull=True)
for mix in candidates:
try:
print "Finding duration for: %s" % mix.title
length = mp3_length(mix.get_absolute_path())
print "\tLength: %d" % length
mix.duration = length
mix.save()
except Mp3FileNotFoundException, me:
mix.delete()
print me.message
except Exception, ex:
raise CommandError(ex.message)

View File

@@ -1,10 +1,8 @@
import os
from dss import settings
from core.utils.waveform import generate_waveform
from django.core.management.base import NoArgsCommand
from spa.models import Tracklist
from spa.models.Mix import Mix
from spa.models.Release import ReleaseAudio
from django.core.management.base import NoArgsCommand
class Command(NoArgsCommand):
help = "Create tracklists for all mixes"

View File

@@ -12,10 +12,7 @@ class Command(NoArgsCommand):
help = "Generate all outstanding waveforms"
def _generateWaveform(self, mix):
fileName, extension = os.path.splitext(mix.local_file.name)
if extension == "" or extension == ".":
extension = ".mp3"
in_file = '%s/%s%s' % (settings.CACHE_ROOT, mix.uid, extension)
in_file = mix.get_absolute_path()
try:
if os.path.isfile(in_file):
create_waveform_task.delay(in_file=in_file, mix_uid=mix.uid)

View File

@@ -0,0 +1,39 @@
"""
Tightens up response content by removed superflous line breaks and whitespace.
By Doug Van Horn
---- CHANGES ----
v1.1 - 31st May 2011
Cal Leeming [Simplicity Media Ltd]
Modified regex to strip leading/trailing white space from every line, not just those with blank \n.
---- TODO ----
* Ensure whitespace isn't stripped from within <pre> or <code> or <textarea> tags.
"""
import re
class StripWhitespaceMiddleware(object):
"""
Strips leading and trailing whitespace from response content.
"""
def __init__(self):
self.whitespace = re.compile('^\s*\n', re.MULTILINE)
# self.whitespace_lead = re.compile('^\s+', re.MULTILINE)
# self.whitespace_trail = re.compile('\s+$', re.MULTILINE)
def process_response(self, request, response):
if "text" in response['Content-Type']:
if hasattr(self, 'whitespace_lead'):
response.content = self.whitespace_lead.sub('', response.content)
if hasattr(self, 'whitespace_trail'):
response.content = self.whitespace_trail.sub('\n', response.content)
# Uncomment the next line to remove empty lines
if hasattr(self, 'whitespace'):
response.content = self.whitespace.sub('', response.content)
return response
else:
return response

View File

@@ -64,6 +64,12 @@ class Mix(_BaseModel):
self.clean_image('mix_image', Mix)
super(Mix, self).save(force_insert, force_update, using)
def get_absolute_path(self):
fileName, extension = os.path.splitext(self.local_file.name)
if extension == "" or extension == ".":
extension = ".mp3"
return '%s/mixes/%s%s' % (settings.MEDIA_ROOT, self.uid, extension)
def get_absolute_url(self):
return '/mix/%i' % self.id

View File

@@ -1,12 +1,13 @@
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from htmlmin.decorators import not_minified_response
from dss import localsettings
from spa.forms import UserForm
from spa.models import UserProfile
__author__ = 'fergalm'
@not_minified_response
def get_template(request, template_name):
#Temporary hack here to create user profiles for zombie users
if request.user.is_authenticated():
@@ -19,6 +20,7 @@ def get_template(request, template_name):
context_instance=RequestContext(request))
@not_minified_response
def get_template_ex(request, template_name):
html = render_to_response(
'views/%s.html' % template_name,

View File

@@ -99,7 +99,7 @@ div.player-body ul.player-controls a {
}
.waveform img {
width: 793px;
width: 100%;
height: 90px;
}
@@ -179,3 +179,39 @@ img.mix-listing-image {
.footer-button-right {
float: right;
}
.audio-timeline-ruler {
background: lightYellow;
box-shadow: 0 -1px 1em hsl(60, 60%, 84%) inset;
border-radius: 2px;
border: 1px solid #ccc;
font-size: 9px;
margin: 0;
height: 100%;
width: 100%;
padding-right: 1cm;
white-space: nowrap;
}
.audio-timeline-ruler, .audio-timeline-ruler li {
margin: 0;
padding: 0;
list-style: none;
display: inline-block;
}
/* IE6-7 Fix */
.audio-timeline-ruler, .audio-timeline-ruler li {
*display: inline;
}
.audio-timeline-ruler li {
/*width: 2em;*/
text-align: center;
position: relative;
text-shadow: 1px 1px hsl(60, 60%, 84%);
}
.audio-timeline-ruler li:before {
content: '';
position: absolute;
border-left: 1px solid #ccc;
height: .64em;
}

View File

@@ -34,7 +34,9 @@ window.MixListItemView = Backbone.View.extend({
$.each(this.model.get("genre-list"), function (data) {
$('#genre-list', parent.el).append('<a href="/mixes/' + this.slug + '" class="btn btn-mini btn-warning"><i class="icon-tags"></i>&nbsp;' + this.text + '</a>');
});
com.podnoms.player.drawTimeline(
$('#player-timeline-' + id, this.el),
this.model.get('duration'));
return this;
},
mouseOverProfile: function () {

View File

@@ -11,9 +11,9 @@ if (!com) var com = {};
if (!com.podnoms) com.podnoms = {};
soundManager.setup({
url:'/static/bin/sm/',
debugMode:true,
wmode:'transparent'
url: '/static/bin/sm/',
debugMode: true,
wmode: 'transparent'
});
soundManager.usePeakData = false;
@@ -27,34 +27,35 @@ soundManager.useHTML5Audio = true;
com.podnoms.player = {
/*Members*/
currentId:-1,
currentPath:'',
currentSound:null,
waveFormEl:null,
playHeadEl:null,
loadingEl:null,
seekHeadEl:null,
waveFormRect:[-1, -1, -1, -1],
trackLoaded:false,
waveFormTop:-1,
waveFormLeft:-1,
waveFormWidth:-1,
totalLength:-1,
currentPosition:-1,
currentId: -1,
currentPath: '',
currentSound: null,
waveFormEl: null,
playHeadEl: null,
timeLineEl: null,
loadingEl: null,
seekHeadEl: null,
waveFormRect: [-1, -1, -1, -1],
trackLoaded: false,
waveFormTop: -1,
waveFormLeft: -1,
waveFormWidth: -1,
totalLength: -1,
currentPosition: -1,
/*Privates */
_getDurationEstimate:function (oSound) {
_getDurationEstimate: function (oSound) {
if (oSound.instanceOptions.isMovieStar) {
return (oSound.duration);
} else {
return (!oSound._data.metadata || !oSound._data.metadata.data.givenDuration ? (oSound.durationEstimate || 0) : oSound._data.metadata.data.givenDuration);
}
},
_whileLoading:function () {
_whileLoading: function () {
var percentageFinished = (this.currentSound.bytesLoaded / this.currentSound.bytesTotal) * 100;
var percentageWidth = (this.waveFormWidth / 100) * percentageFinished;
this.loadingEl.css('width', percentageWidth);
},
_whilePlaying:function () {
_whilePlaying: function () {
if (!this.trackLoaded) {
this.playButtonEl
.removeClass('play-button-small-loading')
@@ -67,7 +68,7 @@ com.podnoms.player = {
var percentageWidth = (this.waveFormWidth / 100) * percentageFinished;
this.playHeadEl.css('width', percentageWidth);
},
_mouseDown:function (event) {
_mouseDown: function (event) {
console.log("Got mousedown: " + event.pageX);
if (this.currentSound != null) {
this.currentSound.setPosition(
@@ -75,14 +76,14 @@ com.podnoms.player = {
}
$(event.currentTarget).mouseup($.proxy(this._mouseDown, this));
},
_mouseMove:function (event) {
_mouseMove: function (event) {
this.seekHeadEl.show();
this.seekHeadEl.css('left', (event.pageX) + 'px').fadeIn('fast');
},
_mouseLeave:function (event) {
_mouseLeave: function (event) {
this.seekHeadEl.hide();
},
_destroyCurrent:function (success) {
_destroyCurrent: function (success) {
if (this.currentSound != null) {
soundManager.destroySound(this.currentSound.sID);
}
@@ -97,16 +98,17 @@ com.podnoms.player = {
if (success != undefined)
success();
},
_parseOptions:function (options) {
_parseOptions: function (options) {
this.currentId = options.id;
this.waveFormEl = options.waveFormEl;
this.seekHeadEl = options.seekHeadEl;
this.playHeadEl = options.playHeadEl;
this.loadingEl = options.loadingEl;
this.timeLineEl = options.timeLineEl;
this.playButtonEl = options.playButtonEl;
this.currentPath = options.url;
},
_setupParams:function () {
_setupParams: function () {
this.waveFormTop = this.waveFormEl.position().top;
this.waveFormLeft = this.waveFormEl.offset().left;
this.waveFormWidth = this.waveFormEl.width();
@@ -119,14 +121,29 @@ com.podnoms.player = {
this.waveFormEl.mouseout($.proxy(this._mouseLeave, this));
},
/*Methods*/
isPlaying:function () {
isPlaying: function () {
if (this.currentSound != null)
return this.currentSound.playState == 1;
},
isPlayingId:function (id) {
isPlayingId: function (id) {
return this.isPlaying() && this.currentSound.sID == "com.podnoms.player-" + id;
},
setupPlayer:function (options) {
drawTimeline: function (el, duration) {
/*
Assume 10 markers
*/
var markerDuration = duration / 10;
var item = $(document.createElement("li"));
for (var i = 0; i < 10; i++){
var duration = moment.duration(markerDuration * (i+1), "seconds");
var text = duration.hours() != 0 ?
moment(duration).format("HH:mm") :
moment(duration).format("mm:ss");
el.append(item.clone().text(text).css('width', '10%'));
}
},
setupPlayer: function (options) {
this._parseOptions(options);
this._setupParams();
if (this.isPlayingId(options.id)) {
@@ -136,18 +153,18 @@ com.podnoms.player = {
.addClass('play-button-small-pause');
}
},
startPlaying:function (options) {
startPlaying: function (options) {
var ref = this;
var currId = this.currentId;
this._destroyCurrent(function () {
ref.currentSound = soundManager.createSound({
url:ref.currentPath,
id:"com.podnoms.player-" + currId.toString(),
volume:com.podnoms.settings.volume,
whileloading:function () {
url: ref.currentPath,
id: "com.podnoms.player-" + currId.toString(),
volume: com.podnoms.settings.volume,
whileloading: function () {
ref._whileLoading();
},
whileplaying:function () {
whileplaying: function () {
ref._whilePlaying();
}
});
@@ -163,19 +180,19 @@ com.podnoms.player = {
}
});
},
stopPlaying:function () {
stopPlaying: function () {
this._destroyCurrent();
},
playLive:function () {
playLive: function () {
var ref = this;
var args = arguments;
this._destroyCurrent(function () {
ref.currentSound = soundManager.createSound({
id:'com.podnoms.player-live',
url:com.podnoms.settings.liveStreamRoot,
volume:50,
stream:true,
useMovieStar:true
id: 'com.podnoms.player-live',
url: com.podnoms.settings.liveStreamRoot,
volume: 50,
stream: true,
useMovieStar: true
});
if (ref.currentSound) {
ref.currentSound.play();
@@ -187,34 +204,41 @@ com.podnoms.player = {
});
},
play:function () {
play: function () {
this.currentSound.play();
this.playButtonEl
.removeClass('play-button-small-start')
.addClass('play-button-small-loading');
},
pause:function () {
pause: function () {
this.currentSound.pause();
this.playButtonEl
.removeClass('play-button-small-pause')
.addClass('play-button-small-resume');
},
resume:function () {
resume: function () {
this.currentSound.resume();
this.playButtonEl
.removeClass('play-button-small-resume')
.addClass('play-button-small-pause');
},
forward:function (increment) {
forward: function (increment) {
},
back:function (increment) {
back: function (increment) {
},
setPosition:function (position) {
setPosition: function (position) {
},
updateWaveform:function (position) {
updateWaveform: function (position) {
}
};
}
;
com.podnoms.player.timeline = {
setupTimeline: function (options) {
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -81,7 +81,7 @@
<script src="{{ STATIC_URL }}js/libs/tiny_mce/jquery.tinymce.js"></script>
<script src="{{ STATIC_URL }}js/libs/modernizr.js"></script>
<script src="{{ STATIC_URL }}js/libs/backbone/underscore.js"></script>
<script src="{{ STATIC_URL }}js/libs/backbone/moment.js"></script>
<script src="{{ STATIC_URL }}js/libs/moment.js"></script>
<script src="{{ STATIC_URL }}js/libs/ICanHaz.js"></script>
<script src="{{ STATIC_URL }}js/libs/select2.js"></script>
<script src="{{ STATIC_URL }}js/libs/backbone/backbone.js"></script>

View File

@@ -35,12 +35,16 @@
<img id="waveform-image-<%= item.id %>" class="waveform_image"
alt="Audio waveform"
src="<%= item.waveform_url %>">
.
<div class="download-progress-overlay" id="progress-player-<%= item.id %>"></div>
<div class="playhead" id="playhead-player-<%= item.id %>" style="width: 0px"></div>
</div>
</div>
</div>
<div class="row">
<div class="player-timeline" style="height: 12px; border: 1px solid #aaa">
<ul class="audio-timeline-ruler" id="player-timeline-<%= item.id %>"></ul>
</div>
</div>
<div class="row">
<div class="player-footer">
<div class="play-button footer-button">

View File

@@ -10,12 +10,15 @@ com.podnoms.settings = {
streamInfoUrl:'http://{{ LIVE_STREAM_INFO_URL }}',
volume:'{{ DEFAULT_AUDIO_VOLUME }}',
smDebugMode: false,
/** simple helper to take an api JSON object and initialise a player item */
setupPlayer:function (data, id) {
com.podnoms.player.setupPlayer({
id:id,
waveFormEl:$('#waveform-' + id),
playHeadEl:$('#playhead-player-' + id),
loadingEl:$('#progress-player-' + id),
timeLineEl:$('#player-timeline-' + id),
seekHeadEl:$('#player-seekhead'),
playButtonEl:$('#play-pause-button-small-' + id),
url:data.stream_url