diff --git a/audiotranscode/__init__.py b/audiotranscode/__init__.py
index 6962703b..7a17d462 100644
--- a/audiotranscode/__init__.py
+++ b/audiotranscode/__init__.py
@@ -12,6 +12,7 @@
'm4a' : 'audio/m4a',
'wav' : 'audio/wav',
'wma' : 'audio/x-ms-wma',
+ 'cue': '', # FIXME: What should we put here? .cue doesn't actually contain any audio.
}
class Transcoder(object):
@@ -52,13 +53,30 @@ def __init__(self, filetype, command):
self.filetype = filetype
self.mimetype = MimeTypes[filetype]
- def decode(self, filepath, starttime=0):
+ def decode(self, filepath, starttime=0, duration=None):
+ # Fetch audio filepath from cue sheets
+ ext = os.path.splitext(filepath)[1]
+ if ext == '.cue':
+ from cherrymusicserver.cuesheet import Cuesheet
+ cue = Cuesheet(filepath)
+ for cdtext in cue.info[0].cdtext:
+ if cdtext.type == 'FILE':
+ filepath = os.path.join(os.path.dirname(filepath), cdtext.value[0])
+ break
cmd = self.command[:]
- if 'INPUT' in cmd:
- cmd[cmd.index('INPUT')] = filepath
if 'STARTTIME' in cmd:
hours, minutes, seconds = starttime//3600, starttime//60%60, starttime%60
- cmd[cmd.index('STARTTIME')] = '%d:%d:%d' % (hours, minutes, seconds)
+ # Seconds should include decimals so that multi-track files don't
+ # accidentally include the last second of the previous track.
+ cmd[cmd.index('STARTTIME')] = '%d:%d:%f' % (hours, minutes, seconds)
+ if 'DURATION' in cmd:
+ # FIXME: We should remove the duration flag instead of working around it like this.
+ if duration is None:
+ duration = 100000
+ hours, minutes, seconds = duration//3600, duration//60%60, duration%60
+ cmd[cmd.index('DURATION')] = '%d:%d:%f' % (hours, minutes, seconds)
+ if 'INPUT' in cmd:
+ cmd[cmd.index('INPUT')] = filepath
return subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=Transcoder.devnull
@@ -107,6 +125,7 @@ class AudioTranscode:
Decoder('aac' , ['faad', '-w', 'INPUT']),
Decoder('m4a' , ['faad', '-w', 'INPUT']),
Decoder('wav' , ['cat', 'INPUT']),
+ Decoder('cue' , ['ffmpeg', '-ss', 'STARTTIME', '-t', 'DURATION', '-i', 'INPUT', '-f', 'wav', '-']),
]
def __init__(self,debug=False):
@@ -125,7 +144,7 @@ def _filetype(self, filepath):
if '.' in filepath:
return filepath.lower()[filepath.rindex('.')+1:]
- def _decode(self, filepath, decoder=None, starttime=0):
+ def _decode(self, filepath, decoder=None, starttime=0, duration=None):
if not os.path.exists(filepath):
filepath = os.path.abspath(filepath)
raise DecodeError('File not Found! Cannot decode "file" %s'%filepath)
@@ -139,7 +158,7 @@ def _decode(self, filepath, decoder=None, starttime=0):
break
if self.debug:
print(decoder)
- return decoder.decode(filepath, starttime=starttime)
+ return decoder.decode(filepath, starttime=starttime, duration=duration)
def _encode(self, audio_format, decoder_process, bitrate=None,encoder=None):
if not audio_format in self.availableEncoderFormats():
@@ -166,11 +185,11 @@ def transcode(self, in_file, out_file, bitrate=None):
fh.close()
def transcodeStream(self, filepath, newformat, bitrate=None,
- encoder=None, decoder=None, starttime=0):
+ encoder=None, decoder=None, starttime=0, duration=None):
decoder_process = None
encoder_process = None
try:
- decoder_process = self._decode(filepath, decoder, starttime=starttime)
+ decoder_process = self._decode(filepath, decoder, starttime=starttime, duration=duration)
encoder_process = self._encode(newformat, decoder_process,bitrate=bitrate,encoder=encoder)
while encoder_process.poll() == None:
data = encoder_process.stdout.read(AudioTranscode.READ_BUFFER)
diff --git a/cherrymusicserver/cherrymodel.py b/cherrymusicserver/cherrymodel.py
index a83a5a25..317d9026 100644
--- a/cherrymusicserver/cherrymodel.py
+++ b/cherrymusicserver/cherrymodel.py
@@ -163,10 +163,40 @@ def listdir(self, dirpath, filterstr=''):
musicentry.count_subfolders_and_files()
return musicentries
+ @classmethod
+ def addCueSheet(cls, filepath, list):
+ from cherrymusicserver.cuesheet import Cuesheet
+ from cherrymusicserver.metainfo import getCueSongInfo
+ cue = Cuesheet(filepath)
+ audio_filepath = None
+ for cdtext in cue.info[0].cdtext:
+ if cdtext.type == 'FILE':
+ # Set the actual filepath from the FILE field
+ audio_filepath = os.path.join(os.path.dirname(filepath), cdtext.value[0])
+ break
+ if audio_filepath is None or not os.path.exists(audio_filepath):
+ log.info(_("Could not find a valid audio file path in cue sheet '%(filepath)s'"), {'filepath': filepath})
+ return
+ for track_n, track in enumerate(cue.tracks, 1):
+ starttime = track.get_start_time()
+ # We need to know the length of the audio file to get the duration of the last track.
+ nexttrack = cue.get_next(track)
+ metainfo = getCueSongInfo(filepath, cue, track_n)
+ if nexttrack:
+ track.nextstart = nexttrack.get_start_time()
+ duration = track.get_length()
+ elif metainfo and metainfo.length:
+ duration = metainfo.length
+ else:
+ duration = None
+ list.append(MusicEntry(strippath(filepath), starttime = starttime, duration = duration, metainfo = metainfo))
+
@classmethod
def addMusicEntry(cls, fullpath, list):
if os.path.isfile(fullpath):
- if CherryModel.isplayable(fullpath):
+ if CherryModel.iscuesheet(fullpath):
+ CherryModel.addCueSheet(fullpath, list)
+ elif CherryModel.isplayable(fullpath):
list.append(MusicEntry(strippath(fullpath)))
else:
list.append(MusicEntry(strippath(fullpath), dir=True))
@@ -313,6 +343,10 @@ def isplayable(cls, filename):
is_empty_file = os.path.getsize(CherryModel.abspath(filename)) == 0
return is_supported_ext and not is_empty_file
+ @staticmethod
+ def iscuesheet(filename):
+ ext = os.path.splitext(filename)[1]
+ return ext.lower() == '.cue'
def strippath(path):
if path.startswith(cherry.config['media.basedir']):
@@ -325,7 +359,7 @@ class MusicEntry:
# check if there are playable meadia files or other folders inside
MAX_SUB_FILES_ITER_COUNT = 100
- def __init__(self, path, compact=False, dir=False, repr=None, subdircount=0, subfilescount=0):
+ def __init__(self, path, compact=False, dir=False, repr=None, subdircount=0, subfilescount=0, starttime=None, duration=None, metainfo=None):
self.path = path
self.compact = compact
self.dir = dir
@@ -336,6 +370,10 @@ def __init__(self, path, compact=False, dir=False, repr=None, subdircount=0, sub
self.subfilescount = subfilescount
# True when the exact amount of files is too big and is estimated
self.subfilesestimate = False
+ # Times for start and length of the track
+ self.starttime = starttime
+ self.duration = duration
+ self.metainfo = metainfo
def count_subfolders_and_files(self):
if self.dir:
@@ -381,6 +419,9 @@ def to_dict(self):
return {'type': 'file',
'urlpath': urlpath,
'path': self.path,
+ 'starttime': self.starttime,
+ 'duration': self.duration,
+ 'metainfo': self.metainfo.dict() if self.metainfo is not None else None,
'label': simplename}
def __repr__(self):
diff --git a/cherrymusicserver/cuesheet.py b/cherrymusicserver/cuesheet.py
new file mode 100644
index 00000000..5fd757b9
--- /dev/null
+++ b/cherrymusicserver/cuesheet.py
@@ -0,0 +1,130 @@
+# Python cuesheet parsing
+# Copyright (C) 2009-2014 Jon Bergli Heier
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+
+import re
+
+cdtext_re = {
+ 'REM': r'^(REM) (.+)$',
+ 'PERFORMER': r'^(PERFORMER) "?(.+?)"?$',
+ 'TITLE': r'^(TITLE) "?(.+?)"?$',
+ #'FILE': r'^(FILE) "?(.+?)"? (BINARY|MOTOROLA|AIFF|WAVE|MP3)$',
+ # XXX: The above line is the only correct one according to the spec, but some people
+ # seem to think that putting stuff like FLAC here instead is a good idea.
+ 'FILE': r'^(FILE) "?(.+?)"? (\w+)$',
+ 'TRACK': r'^(TRACK) (\d+) (AUDIO|CDG|MODE1/2048|MODE1/2352|MODE2/2336|MODE2/2352|CDI/2336|CDI2352)$',
+ 'INDEX': r'^(INDEX) (\d+) (\d+):(\d+):(\d+)$',
+ 'FLAGS': r'^((?:DCP|4CH|PRE|SCMS) ?){1,4}$',
+ 'ISRC': r'^(ISRC) (\w{5}\d{7})$',
+ 'SONGWRITER': r'^(SONGWRITER) "?(.+?)"?$',
+ 'CATALOG': r'^(CATALOG) (\d{13})$',
+}
+
+for k, v in cdtext_re.items():
+ cdtext_re[k] = re.compile(v)
+
+class CDText(object):
+ def __init__(self, str):
+ name = str.split()[0]
+ self.re = cdtext_re[name]
+ l = self.parse(str)
+ self.type, self.value = l[0], l[1:]
+ if type(self.value) == tuple and len(self.value) == 1:
+ self.value = self.value[0]
+
+ def __repr__(self):
+ return '' % (self.type, self.value)
+
+ def __str__(self):
+ return repr(self)
+
+ def parse(self, str):
+ r = self.re.match(str)
+ if not r:
+ return None, None
+ return r.groups()
+
+class FieldDescriptor(object):
+ def __init__(self, field):
+ self.field = field
+
+ def __get__(self, instance, owner):
+ def find(name):
+ for l in instance.cdtext:
+ if l.type == name:
+ return l
+ cdtext = find(self.field)
+ return cdtext.value if cdtext else None
+
+class Track(object):
+ def __init__(self):
+ self.cdtext = []
+ self.abs_tot, self.abs_end, self.nextstart = 0, 0, None
+
+ def add(self, cdtext):
+ self.cdtext.append(cdtext)
+
+ def set_abs_tot(self, tot):
+ self.abs_tot = tot
+
+ def set_abs_end(self, end):
+ self.abs_end
+
+ def get_start_time(self):
+ index = self.index
+ return int(index[1])*60 + int(index[2]) + float(index[3])/75
+
+ def get_length(self):
+ return self.nextstart - self.get_start_time()
+
+for f in cdtext_re.keys():
+ setattr(Track, f.lower(), FieldDescriptor(f))
+
+class Cuesheet(object):
+ def __init__(self, filename = None, fileobj = None):
+ if not fileobj and filename:
+ fileobj = open(filename, 'rb')
+ if fileobj:
+ self.parse(fileobj)
+
+ def parse(self, f):
+ info = []
+ tracks = []
+ track = Track()
+ info.append(track)
+ if not f.read(3) == b'\xef\xbb\xbf':
+ f.seek(0)
+ for line in f:
+ line = line.strip()
+ line = line.decode('utf-8')
+ if not len(line):
+ continue
+ cdtext = CDText(line)
+ if cdtext.type == 'TRACK':
+ track = Track()
+ tracks.append(track)
+ track.add(cdtext)
+ self.info = info
+ self.tracks = tracks
+
+ def get_next(self, track):
+ found = False
+ for i in self.tracks:
+ if found:
+ return i
+ elif i == track:
+ found = True
+ return None
diff --git a/cherrymusicserver/httphandler.py b/cherrymusicserver/httphandler.py
index 1dc43c1a..d9ede2b2 100644
--- a/cherrymusicserver/httphandler.py
+++ b/cherrymusicserver/httphandler.py
@@ -252,14 +252,18 @@ def trans(self, newformat, *path, **params):
path = codecs.decode(codecs.encode(path, 'latin1'), 'utf-8')
fullpath = os.path.join(cherry.config['media.basedir'], path)
- starttime = int(params.pop('starttime', 0))
+ starttime = float(params.pop('starttime', 0))
+ if 'duration' in params:
+ duration = float(params.pop('duration'))
+ else:
+ duration = None
transcoder = audiotranscode.AudioTranscode()
mimetype = transcoder.mimeType(newformat)
cherrypy.response.headers["Content-Type"] = mimetype
try:
return transcoder.transcodeStream(fullpath, newformat,
- bitrate=bitrate, starttime=starttime)
+ bitrate=bitrate, starttime=starttime, duration=duration)
except audiotranscode.TranscodeError as e:
raise cherrypy.HTTPError(404, e.value)
trans.exposed = True
diff --git a/cherrymusicserver/metainfo.py b/cherrymusicserver/metainfo.py
index 238152fe..662b6a0d 100644
--- a/cherrymusicserver/metainfo.py
+++ b/cherrymusicserver/metainfo.py
@@ -30,7 +30,7 @@
#
from cherrymusicserver import log
-import sys
+import sys, os
from tinytag import TinyTag
@@ -50,7 +50,30 @@ def dict(self):
'length': self.length
}
+def getCueSongInfo(filepath, cue, track_n):
+ info = cue.info[0]
+ artist = info.performer or '-'
+ album = info.title or '-'
+ title = '-'
+ track = cue.tracks[track_n-1]
+ artist = track.performer or artist
+ title = track.title or title
+ if track_n < len(cue.tracks):
+ track.nextstart = cue.get_next(track).get_start_time()
+ audiolength = track.get_length()
+ else:
+ try:
+ audiofilepath = os.path.join(os.path.dirname(filepath), info.file[0])
+ tag = TinyTag.get(audiofilepath)
+ except Exception:
+ audiolength = None
+ log.warn(_("Couldn't get length of '%s', setting 0"), audiofilepath)
+ else:
+ audiolength = tag.duration - track.get_start_time()
+ return Metainfo(artist, album, title, track_n, audiolength)
+
def getSongInfo(filepath):
+ ext = os.path.splitext(filepath)[1]
try:
tag = TinyTag.get(filepath)
except LookupError:
diff --git a/cherrymusicserver/test/test_httphandler.py b/cherrymusicserver/test/test_httphandler.py
index 420ae4b5..ba18823f 100644
--- a/cherrymusicserver/test/test_httphandler.py
+++ b/cherrymusicserver/test/test_httphandler.py
@@ -313,7 +313,7 @@ def test_trans(self):
httphandler.HTTPHandler(config).trans('newformat', 'path', bitrate=111)
- transcoder.transcodeStream.assert_called_with(expectPath, 'newformat', bitrate=111, starttime=0)
+ transcoder.transcodeStream.assert_called_with(expectPath, 'newformat', bitrate=111, starttime=0, duration=None)
if __name__ == "__main__":
diff --git a/res/dist/cherrymusic.dist.js b/res/dist/cherrymusic.dist.js
index 94d54c1a..bd7d8bf7 100644
--- a/res/dist/cherrymusic.dist.js
+++ b/res/dist/cherrymusic.dist.js
@@ -1100,7 +1100,7 @@ $(document).on('click.bs.alert.data-api',dismiss,Alert.prototype.close)}(window.
ManagedPlaylist.prototype={_init:function(playlist,playlistManager){var self=this;this.playlistSelector=self._createNewPlaylistContainer();for(var i=0;iul.playlist-container-list").sortable({axis:"y",delay:150,helper:'clone',update:function(e,ui){self.jplayerplaylist.scan();}});$(self.playlistSelector+">ul.playlist-container-list").disableSelection();$(this.playlistSelector).bind('requestPlay',function(event,playlistSelector){self.playlistManager.setPlayingPlaylist(self.playlistManager.htmlid2plid(playlistSelector));});$(this.playlistSelector).bind('removedItem',function(event,playlistSelector){self.setSaved(false);});$(this.playlistSelector).bind('sortedItems',function(event,playlistSelector){self.setSaved(false);});$(this.playlistSelector).bind('addedItem',function(event,playlistSelector){self.setSaved(false);});},setSaved:function(issaved){this.saved=issaved;this.playlistManager.refreshCommands();this.playlistManager.refreshTabs();},wasSaved:function(){return this.saved;},_createNewPlaylistContainer:function(){var playlistContainerParent=this.playlistManager.cssSelectorPlaylistContainerParent;var id=this.playlistManager.plid2htmlid(this.id);$(playlistContainerParent).append('');return'#'+id;},getCanonicalPlaylist:function(){var canonical=[];for(var i=0;iul.playlist-container-list").sortable({axis:"y",delay:150,helper:'clone',update:function(e,ui){self.jplayerplaylist.scan();}});$(self.playlistSelector+">ul.playlist-container-list").disableSelection();$(this.playlistSelector).bind('requestPlay',function(event,playlistSelector){self.playlistManager.setPlayingPlaylist(self.playlistManager.htmlid2plid(playlistSelector));});$(this.playlistSelector).bind('removedItem',function(event,playlistSelector){self.setSaved(false);});$(this.playlistSelector).bind('sortedItems',function(event,playlistSelector){self.setSaved(false);});$(this.playlistSelector).bind('addedItem',function(event,playlistSelector){self.setSaved(false);});},setSaved:function(issaved){this.saved=issaved;this.playlistManager.refreshCommands();this.playlistManager.refreshTabs();},wasSaved:function(){return this.saved;},_createNewPlaylistContainer:function(){var playlistContainerParent=this.playlistManager.cssSelectorPlaylistContainerParent;var id=this.playlistManager.plid2htmlid(this.id);$(playlistContainerParent).append('');return'#'+id;},getCanonicalPlaylist:function(){var canonical=[];for(var i=0;iwasplayermost){wasplayermost=this.jplayerplaylist.playlist[i].wasPlayed;}}
@@ -1138,20 +1138,21 @@ var otherId=this.managedPlaylists[i '+availablejPlayerFormats[i]+' @ '+transurl);}}}
+return;}else{for(var i=0;i '+availablejPlayerFormats[i]+' @ '+transurl);}}}
if(formats.length==0){window.console.log('no suitable encoder available! Try installing vorbis-tools or lame!');return;}}
-return track;},addSong:function(path,title,plid,animate){"use strict";var self=this;if(typeof animate==='undefined'){animate=true;}
-var track={title:title,url:path,wasPlayed:0,}
+return track;},addSong:function(path,title,metainfo,starttime,duration,plid,animate){"use strict";var self=this;if(typeof animate==='undefined'){animate=true;}
+var track={title:title,url:path,starttime:starttime,duration:duration,wasPlayed:0,}
var playlist;if(plid){playlist=this.getPlaylistById(plid);}
if(typeof playlist=='undefined'){playlist=this.getEditingPlaylist();}
playlist.addTrack(track,animate);if(!jPlayerIsPlaying()&&playlist.jplayerplaylist.playlist.length==1){if(userOptions.misc.autoplay_on_add){playlist.makeThisPlayingPlaylist();playlist.jplayerplaylist.play(0);}else{playlist.jplayerplaylist.select(0);}}
-var success=function(data){var metainfo=$.parseJSON(data);var any_info_received=false;if(metainfo.length){track.duration=metainfo.length;any_info_received=true;}
+var any_info_received=false;if(metainfo.length){track.duration=metainfo.length;any_info_received=true;}
if(metainfo.title.length>0&&metainfo.artist.length>0){track.title=metainfo.artist+' - '+metainfo.title;if(metainfo.track.length>0){track.title=metainfo.track+' '+track.title;if(metainfo.track.length<2){track.title='0'+track.title;}}
any_info_received=true;}
-if(any_info_received){self.getEditingPlaylist().jplayerplaylist._refresh(true);}}
-window.setTimeout(function(){api('getsonginfo',{'path':decodeURIComponent(path)},success,errorFunc('error getting song metainfo'),true);},1000);},clearPlaylist:function(){"use strict";this.getEditingPlaylist().remove();if(this.getEditingPlaylist()==this.getPlayingPlaylist()){$(this.cssSelectorjPlayer).jPlayer("clearMedia");}
+if(any_info_received){self.getEditingPlaylist().jplayerplaylist._refresh(true);}},clearPlaylist:function(){"use strict";this.getEditingPlaylist().remove();if(this.getEditingPlaylist()==this.getPlayingPlaylist()){$(this.cssSelectorjPlayer).jPlayer("clearMedia");}
return false;},displayCurrentSong:function(){var pl=this.getPlayingPlaylist();if(typeof pl==='undefined'){return;}
var jPlaylist=pl.jplayerplaylist;var songtitle='';var tabtitle='CherryMusic';if(typeof this.jPlayerInstance!=='undefined'){var currentTitle=this.jPlayerInstance.data().jPlayer.status.media.title;if(typeof currentTitle!=='undefined'){songtitle=currentTitle;tabtitle=currentTitle+' | CherryMusic';}}
$('.cm-songtitle').html(songtitle);$('title').text(tabtitle);},rememberPlaylist:function(){"use strict";var self=this;var canonicalPlaylists=[]
@@ -1189,12 +1190,15 @@ this.render();$(cssSelector).off('click');$(cssSelector).on('click','.list-dir',
$('#changeAlbumArt').modal('show');});$(cssSelector).on('click','.mb-view-list-enable',view_list_enable);$(cssSelector).on('click','.mb-view-cover-enable',view_cover_enable);$(cssSelector).on('click','.addAllToPlaylist',function(){if(isplaylist){var pl=playlistManager.newPlaylist([],playlistlabel);}else{var pl=playlistManager.getEditingPlaylist();}
MediaBrowser.static._addAllToPlaylist($(this),pl.id);if(isplaylist){pl.setSaved(true);}
$(this).blur();return false;});}
-MediaBrowser.static={_renderList:function(l,listview){"use strict";var self=this;var html="";$.each(l,function(i,e){switch(e.type){case'dir':html+=MediaBrowser.static._renderDirectory(e,listview);break;case'file':html+=MediaBrowser.static._renderFile(e);break;case'compact':html+=MediaBrowser.static._renderCompactDirectory(e);break;case'playlist':html+=MediaBrowser.static._renderPlaylist(e);break;default:window.console.log('cannot render unknown type '+e.type);}});return html;},_renderMessage:function(msg){var template=templateLoader.cached('mediabrowser-message');return Mustache.render(template,{message:msg});},_renderFile:function(json){var template=templateLoader.cached('mediabrowser-file');var template_data={fileurl:json.urlpath,fullpath:json.path,label:json.label,};return Mustache.render(template,template_data);},_renderDirectory:function(json,listview){var template=templateLoader.cached('mediabrowser-directory');var template_data={isrootdir:json.path&&!json.path.indexOf('/')>0,dirpath:json.path,label:json.label,listview:listview,maychangecoverart:!!isAdmin,coverarturl:encodeURIComponent(JSON.stringify({'directory':json.path})),directoryname:encodeURIComponent(json.path),foldercount:json.foldercount,showfoldercount:json.foldercount>0,filescount:json.filescount,showfilescount:json.filescount>0,filescountestimate:json.filescountestimate,};return Mustache.render(template,template_data);},_renderPlaylist:function(e){var template=templateLoader.cached('mediabrowser-playlist');var template_data={playlistid:e['plid'],isowner:e.owner,candelete:e.owner||isAdmin,playlistlabel:e['title'],encodedplaylistlabel:encodeURI(e['title']),username:e['username'],age:time2text(e['age']),username_color:userNameToColor(e.username),publicchecked:e['public']?'checked="checked"':'',publiclabelclass:e['public']?'label-success':'label-default',};return Mustache.render(template,template_data);},_renderCompactDirectory:function(json){var template=templateLoader.cached('mediabrowser-compact');var template_data={filepath:json.urlpath,filter:json.label,filterUPPER:json.label.toUpperCase(),};return Mustache.render(template,template_data);},addThisTrackToPlaylist:function(){"use strict"
-playlistManager.addSong($(this).attr("path"),$(this).attr("title"));$(this).blur();return false;},_addAllToPlaylist:function($source,plid){"use strict";$source.find('li .musicfile').each(function(){playlistManager.addSong($(this).attr("path"),$(this).attr("title"),plid);});},albumArtLoader:function(cssSelector){"use strict";var winheight=$(window).height();var scrolled_down=$(cssSelector).scrollTop();var preload_threshold=50;$(cssSelector).find('.list-dir-albumart.unloaded').each(function(idx){var img_pos=$(this).position().top;var above_screen=img_poswinheight+scrolled_down+preload_threshold;if(!above_screen&&!below_screen){$(this).find('img').attr('src','api/fetchalbumart/?data='+$(this).attr('search-data'));$(this).removeClass('unloaded');}});$(cssSelector).find('.meta-info.unloaded').each(function(idx){var track_pos=$(this).parent().position().top;var above_screen=track_poswinheight+scrolled_down+preload_threshold;if(!above_screen&&!below_screen){var self=this;var path_url_enc=$(this).attr('path');var success=function(data){$(self).show();var metainfo=$.parseJSON(data);if(metainfo.artist.length>0&&metainfo.title.length>0){$(self).find('.meta-info-artist').text(metainfo.artist);$(self).find('.meta-info-title').text(metainfo.title);if(metainfo.track.length>0){if(metainfo.track.length<2){metainfo.track='0'+metainfo.track;}
+MediaBrowser.static={_renderList:function(l,listview){"use strict";var self=this;var html="";$.each(l,function(i,e){switch(e.type){case'dir':html+=MediaBrowser.static._renderDirectory(e,listview);break;case'file':html+=MediaBrowser.static._renderFile(e);break;case'compact':html+=MediaBrowser.static._renderCompactDirectory(e);break;case'playlist':html+=MediaBrowser.static._renderPlaylist(e);break;default:window.console.log('cannot render unknown type '+e.type);}});return html;},_renderMessage:function(msg){var template=templateLoader.cached('mediabrowser-message');return Mustache.render(template,{message:msg});},_renderFile:function(json){var template=templateLoader.cached('mediabrowser-file');var template_data={fileurl:json.urlpath,fullpath:json.path,label:json.label,starttime:json.starttime,duration:json.duration,metainfo:json.metainfo};return Mustache.render(template,template_data);},_renderDirectory:function(json,listview){var template=templateLoader.cached('mediabrowser-directory');var template_data={isrootdir:json.path&&!json.path.indexOf('/')>0,dirpath:json.path,label:json.label,listview:listview,maychangecoverart:!!isAdmin,coverarturl:encodeURIComponent(JSON.stringify({'directory':json.path})),directoryname:encodeURIComponent(json.path),foldercount:json.foldercount,showfoldercount:json.foldercount>0,filescount:json.filescount,showfilescount:json.filescount>0,filescountestimate:json.filescountestimate,};return Mustache.render(template,template_data);},_renderPlaylist:function(e){var template=templateLoader.cached('mediabrowser-playlist');var template_data={playlistid:e['plid'],isowner:e.owner,candelete:e.owner||isAdmin,playlistlabel:e['title'],username:e['username'],age:time2text(e['age']),username_color:userNameToColor(e.username),publicchecked:e['public']?'checked="checked"':'',publiclabelclass:e['public']?'label-success':'label-default',};return Mustache.render(template,template_data);},_renderCompactDirectory:function(json){var template=templateLoader.cached('mediabrowser-compact');var template_data={filepath:json.urlpath,filter:json.label,filterUPPER:json.label.toUpperCase(),};return Mustache.render(template,template_data);},getMetaInfo:function(el){var metainfo={track:$(el).find('.meta-info-track').text(),artist:$(el).find('.meta-info-artist').text(),title:$(el).find('.meta-info-title').text(),length:$(el).attr('duration')};return metainfo;},addThisTrackToPlaylist:function(){"use strict"
+playlistManager.addSong($(this).attr("path"),$(this).attr("title"),MediaBrowser.static.getMetaInfo(this),$(this).attr("starttime"),$(this).attr("duration"));$(this).blur();return false;},_addAllToPlaylist:function($source,plid){"use strict";$source.find('li .musicfile').each(function(){playlistManager.addSong($(this).attr("path"),$(this).attr("title"),MediaBrowser.static.getMetaInfo(this),$(this).attr("starttime"),$(this).attr("duration"),plid);});},albumArtLoader:function(cssSelector){"use strict";var winheight=$(window).height();var scrolled_down=$(cssSelector).scrollTop();var preload_threshold=50;$(cssSelector).find('.list-dir-albumart.unloaded').each(function(idx){var img_pos=$(this).position().top;var above_screen=img_poswinheight+scrolled_down+preload_threshold;if(!above_screen&&!below_screen){$(this).find('img').attr('src','api/fetchalbumart/?data='+$(this).attr('search-data'));$(this).removeClass('unloaded');}});$(cssSelector).find('.meta-info.unloaded').each(function(idx){var track_pos=$(this).parent().position().top;var above_screen=track_poswinheight+scrolled_down+preload_threshold;if(!above_screen&&!below_screen){var self=this;var path_url_enc=$(this).attr('path');var success=function(data){$(self).show();var metainfo=$.parseJSON(data);if(metainfo.artist.length>0&&metainfo.title.length>0){$(self).find('.meta-info-artist').text(metainfo.artist);$(self).find('.meta-info-title').text(metainfo.title);if(metainfo.track&&typeof metainfo.track==="number")
+metainfo.track=metainfo.track.toString();if(metainfo.track.length>0){if(metainfo.track.length<2){metainfo.track='0'+metainfo.track;}
$(self).find('.meta-info-track').text(metainfo.track);}
$(self).parent().find('.simplelabel').hide();}
-if(metainfo.length){$(self).find('.meta-info-length').text('('+jPlayerPlaylist.prototype._formatTime(metainfo.length)+')');}}
-$(this).removeClass('unloaded');api('getsonginfo',{'path':decodeURIComponent(path_url_enc)},success,errorFunc('error getting song metainfo'),true);}});},};var browser=detectBrowser();if(['msie','safari'].indexOf(browser)!=-1){var encoderPreferenceOrder=['mp3','ogg'];}else{var encoderPreferenceOrder=['ogg','mp3'];}
+if(metainfo.length){$(self).parent().attr('duration',metainfo.length);$(self).find('.meta-info-length').text('('+jPlayerPlaylist.prototype._formatTime(metainfo.length)+')');}}
+$(this).removeClass('unloaded');api('getsonginfo',{'path':decodeURIComponent(path_url_enc)},success,errorFunc('error getting song metainfo'),true);}});$(cssSelector).find('.meta-info.preloaded').each(function(idx){$(this).removeClass('preloaded');var track=$(this).find('.meta-info-track').text();if(track.length>0){if(track.length<2){track='0'+track;}
+$(this).find('.meta-info-track').text(track);}
+var length=$(this).find('.meta-info-length').text();if(length.length>0){$(this).find('.meta-info-length').text('('+jPlayerPlaylist.prototype._formatTime(length)+')');}});},};var browser=detectBrowser();if(['msie','safari'].indexOf(browser)!=-1){var encoderPreferenceOrder=['mp3','ogg'];}else{var encoderPreferenceOrder=['ogg','mp3'];}
var SERVER_CONFIG={};var availableEncoders=undefined;var availablejPlayerFormats=['mp3','ogg'];var availableDecoders=undefined;var transcodingEnabled=undefined;var userOptions=undefined;var isAdmin=undefined;var loggedInUserName=undefined;var REMEMBER_PLAYLIST_INTERVAL=3000;var CHECK_MUSIC_PLAYING_INTERVAL=2000;var HEARTBEAT_INTERVAL_MS=30*1000;var playlistSelector='.jp-playlist';var executeAfterConfigLoaded=[]
function api(){"use strict";var action=arguments[0];var has_data=!(typeof arguments[1]==='function');var data={};if(has_data){data=arguments[1];}
var successfunc=arguments[has_data?2:1];var errorfunc=arguments[has_data?3:2];var completefunc=arguments[has_data?4:3];if(!successfunc)successfunc=function(){};if(!completefunc)completefunc=function(){};var successFuncWrapper=function(successFunc){return function handler(json){var result=$.parseJSON(json);if(result.flash){successNotify(result.flash);}
@@ -1242,11 +1246,11 @@ function ord(c)
function showPlaylists(sortby,filterby){"use strict";var success=function(data){var addressAndPort=getAddrPort();var value_before=$('.playlist-filter-input').val();new MediaBrowser('.search-results',data,'Playlist browser',false,{showPlaylistPanel:true});$('.playlist-filter-input').val(value_before);};var error=errorFunc('error loading external playlists');busy('.search-results').hide().fadeIn('fast');api('showplaylists',{'sortby':sortby,'filterby':filterby},success,error,function(){busy('.search-results').fadeOut('fast')});}
function changePlaylist(plid,attrname,value){window.console.log(plid);window.console.log(attrname);window.console.log(value);busy('#playlist-panel').hide().fadeIn('fast');api('changeplaylist',{'plid':plid,'attribute':attrname,'value':value},function(){showPlaylists();},errorFunc('error changing playlist attribute'),function(){busy('#playlist-panel').fadeOut('fast')});}
function confirmDeletePlaylist(id,title){$('#deletePlaylistConfirmButton').off();$('#deletePlaylistConfirmButton').on('click',function(){busy('#playlist-panel').hide().fadeIn('fast');api('deleteplaylist',{'playlistid':id},false,errorFunc('error deleting playlist'),function(){busy('#playlist-panel').fadeOut('fast')});$('#dialog').fadeOut('fast');showPlaylists();});$('#deleteplaylistmodalLabel').html(Mustache.render('Really delete Playlist "{{title}}"',{title:title}));$('#deleteplaylistmodal').modal('show');}
-function loadPlaylist(playlistid,playlistlabel){var success=function(data){var tracklist=data;var pl=playlistManager.newPlaylist([],playlistlabel);var animate=false;for(var i=0;i 0){
if(metainfo.track.length < 2){
metainfo.track = '0' + metainfo.track;
@@ -402,6 +416,7 @@ MediaBrowser.static = {
}
// show length anyway, if it was detemined.
if(metainfo.length){
+ $(self).parent().attr('duration', metainfo.length);
$(self).find('.meta-info-length').text(
'('+jPlayerPlaylist.prototype._formatTime(metainfo.length) + ')'
);
@@ -410,7 +425,9 @@ MediaBrowser.static = {
$(this).removeClass('unloaded');
api(
'getsonginfo',
- {'path': decodeURIComponent(path_url_enc)},
+ {
+ 'path': decodeURIComponent(path_url_enc)
+ },
success,
errorFunc('error getting song metainfo'),
true
@@ -418,6 +435,27 @@ MediaBrowser.static = {
}
}
);
+ $(cssSelector).find('.meta-info.preloaded').each(
+ function(idx) {
+ $(this).removeClass('preloaded');
+ /* FIXME: Modified copy-paste of the success function above. */
+ var track = $(this).find('.meta-info-track').text();
+ if(track.length > 0){
+ if(track.length < 2){
+ track = '0' + track;
+ }
+ $(this).find('.meta-info-track').text(
+ track
+ );
+ }
+ var length = $(this).find('.meta-info-length').text();
+ if(length.length > 0) {
+ $(this).find('.meta-info-length').text(
+ '('+jPlayerPlaylist.prototype._formatTime(length)+')'
+ );
+ }
+ }
+ );
},
}
diff --git a/res/js/playlistmanager.js b/res/js/playlistmanager.js
index c5f5a025..8365c6c6 100644
--- a/res/js/playlistmanager.js
+++ b/res/js/playlistmanager.js
@@ -104,6 +104,7 @@ ManagedPlaylist.prototype = {
var elem = this.jplayerplaylist.playlist[i];
var track = {
title : elem.title,
+ starttime: elem.starttime,
duration : elem.duration,
url: elem.url,
}
@@ -667,10 +668,14 @@ PlaylistManager.prototype = {
var self = this;
var path = track.url;
var title = track.title;
+ var starttime = track.starttime;
+ var duration = track.duration;
var ext = getFileTypeByExt(path);
var track = {
title: title,
wasPlayed : 0,
+ starttime: starttime,
+ duration: duration
}
var forced_bitrate = userOptions.media.force_transcode_to_bitrate;
var formats = [];
@@ -697,6 +702,10 @@ PlaylistManager.prototype = {
formats.push(availablejPlayerFormats[i]);
var transurl = SERVER_CONFIG.transcode_path + availablejPlayerFormats[i] + '/' + path;
transurl += '?bitrate=' + forced_bitrate;
+ if(track.starttime)
+ transurl += '&starttime=' + track.starttime;
+ if(track.duration)
+ transurl += '&duration=' + track.duration;
track[ext2jPlayerFormat(availablejPlayerFormats[i])] = transurl;
window.console.log('added live transcoding '+ext+' --> '+availablejPlayerFormats[i]+' @ '+transurl);
}
@@ -709,7 +718,7 @@ PlaylistManager.prototype = {
}
return track;
},
- addSong : function(path, title, plid, animate){
+ addSong : function(path, title, metainfo, starttime, duration, plid, animate){
"use strict";
var self = this;
if(typeof animate === 'undefined'){
@@ -718,6 +727,8 @@ PlaylistManager.prototype = {
var track = {
title: title,
url: path,
+ starttime: starttime,
+ duration: duration,
wasPlayed : 0,
}
var playlist;
@@ -738,37 +749,26 @@ PlaylistManager.prototype = {
playlist.jplayerplaylist.select(0);
}
}
- var success = function(data){
- var metainfo = $.parseJSON(data);
- var any_info_received = false;
- if (metainfo.length) {
- track.duration = metainfo.length;
- any_info_received = true;
- }
- // only show id tags if at least artist and title are known
- if (metainfo.title.length > 0 && metainfo.artist.length > 0) {
- track.title = metainfo.artist+' - '+metainfo.title;
- if(metainfo.track.length > 0){
- track.title = metainfo.track + ' ' + track.title;
- if(metainfo.track.length < 2){
- track.title = '0' + track.title;
- }
+ var any_info_received = false;
+ if (metainfo.length) {
+ track.duration = metainfo.length;
+ any_info_received = true;
+ }
+ // only show id tags if at least artist and title are known
+ if (metainfo.title.length > 0 && metainfo.artist.length > 0) {
+ track.title = metainfo.artist+' - '+metainfo.title;
+ if(metainfo.track.length > 0){
+ track.title = metainfo.track + ' ' + track.title;
+ if(metainfo.track.length < 2){
+ track.title = '0' + track.title;
}
- any_info_received = true;
- }
- if(any_info_received){
- //only rerender playlist if it would visually change
- self.getEditingPlaylist().jplayerplaylist._refresh(true);
}
+ any_info_received = true;
+ }
+ if(any_info_received){
+ //only rerender playlist if it would visually change
+ self.getEditingPlaylist().jplayerplaylist._refresh(true);
}
- // WORKAROUND: delay the meta-data fetching, so that a request
- // for the actual audio data comes through frist
- window.setTimeout(
- function(){
- api('getsonginfo', {'path': decodeURIComponent(path)}, success, errorFunc('error getting song metainfo'), true);
- },
- 1000
- );
},
clearPlaylist : function(){
"use strict";
diff --git a/res/templates/mediabrowser-file.html b/res/templates/mediabrowser-file.html
index e9e79483..bbcc5daf 100644
--- a/res/templates/mediabrowser-file.html
+++ b/res/templates/mediabrowser-file.html
@@ -1,16 +1,18 @@
-
+
+ {{^metainfo}}
{{label}}
-
-
- -
-
+ {{/metainfo}}
+
+ {{metainfo.track}}
+ {{metainfo.artist}} - {{metainfo.title}}
+ {{metainfo.length}}
{{fullpath}}
-
\ No newline at end of file
+