The lightweight framework Bottle seems to be popular, so I will make a cloud music player for practice. The finished product is listed in Bitbucket, so only the main points will be explained. The operation sample is available at here. By the way, I have confirmed the operation with Python 2.6.
It's convenient that once you buy it, you can download it from any device such as iTunes or Amazon Cloud Player and play it. However, as a CD purchase kitchen, I still want to play it with the sound source file at hand. Google Music can upload files by itself, but the Japanese music industry is inflexible Therefore, it will be difficult to operate a similar service in Japan for the time being. Therefore, I always wanted to create my own cloud music player, so I will take this opportunity to implement it.
Another option is to build a streaming server using Subsonic or Icecast. While this is easy and many players can play it, it has the disadvantage of incurring a processing load because the sound source is encoded on the server side. (In fact, I used to use this method until now, but there is a problem with the song selection operation.) This time, we'll leave most of the playback to the jQuery plugin jPlayer, an HTML5-based media player. I don't know if the lightweight framework Bottle comes into play because the processing part of blunt Python is just to list playlists, but I think it's just right for studying.
Bottle allows you to create web apps with a really simple syntax.
au.py
# -*- coding: utf-8 -*-
from bottle import route, run #It seems to be common to retrieve only the necessary methods (?)
@route('/hello') #Specify the target path to execute the following function
def hello(): #The function name can be anything
return 'Hello World!' #Contents to be output to the browser
if __name__ == "__main__": #Note that if you do not block it, an error will occur when you execute it from Apache described later.
run(host='0.0.0.0', port=8080, debug=True, reloader=True)
For the argument of run ()
Execute the above script from the command line.
python au.py
If you access http: // server address: 8080 / hello from your browser, you should see "Hello World!".
Now, let's implement the desired cloud music player from here. Bottle's template engine is Django-like, so experienced Django users should be happy to use it easily. Detailed specifications can be found in the Original Document.
au.py
from bottle import route, template, TEMPLATE_PATH
import os
ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) #Absolute path to the folder where this script is located
TEMPLATE_PATH.insert(0, ROOT_PATH+'/templates') #Specify the folder to store the template file
@route('/')
def index():
return template('player.html') #You can also pass parameters following the template file
In the template, we will prepare jPlayer immediately. Since the appearance is secondary, CSS uses [Blue Monday] that comes with jPlayer, and the HTML part is an audio player with a playlist on this site. I was allowed to refer to!
Also, the intersection of each template is grouped into a separate template file with ```% rebase ()`
. Note that this has a different format than Django. Please note that the jplayer.m3u.js
and init.js
passed in the parameters are scripts that you will create later.
templates/player.html
% rebase('base.html', css=['jplayer.blue.monday.css'], js=['jquery.jplayer.min.js', 'jplayer.playlist.min.js', 'jplayer.m3u.js', 'init.js']) <!--Since parameters can be passed after the template file, specify the CSS and JS files to be additionally read.-->
<div id="jquery_jplayer_N" class="jp-jplayer"></div>
<div id="jp_container_N" class="jp-audio">
<div class="jp-type-playlist">
<div class="jp-gui jp-interface">
<ul class="jp-controls">
<li><a href="javascript:;" class="jp-previous" tabindex="1">previous</a></li>
<li><a href="javascript:;" class="jp-play" tabindex="1">play</a></li>
<li><a href="javascript:;" class="jp-pause" tabindex="1">pause</a></li>
<li><a href="javascript:;" class="jp-next" tabindex="1">next</a></li>
<li><a href="javascript:;" class="jp-stop" tabindex="1">stop</a></li>
<li><a href="javascript:;" class="jp-mute" tabindex="1" title="mute">mute</a></li>
<li><a href="javascript:;" class="jp-unmute" tabindex="1" title="unmute">unmute</a></li>
<li><a href="javascript:;" class="jp-volume-max" tabindex="1" title="max volume">max volume</a></li>
</ul>
<div class="jp-progress">
<div class="jp-seek-bar">
<div class="jp-play-bar"></div>
</div>
</div>
<div class="jp-volume-bar">
<div class="jp-volume-bar-value"></div>
</div>
<div class="jp-time-holder">
<div class="jp-current-time"></div>
<div class="jp-duration"></div>
</div>
<ul class="jp-toggles">
<li><a href="javascript:;" class="jp-shuffle" tabindex="1" title="shuffle">shuffle</a></li>
<li><a href="javascript:;" class="jp-shuffle-off" tabindex="1" title="shuffle off">shuffle off</a></li>
<li><a href="javascript:;" class="jp-repeat" tabindex="1" title="repeat">repeat</a></li>
<li><a href="javascript:;" class="jp-repeat-off" tabindex="1" title="repeat off">repeat off</a></li>
</ul>
</div>
<div class="jp-playlist">
<ul>
<li></li>
</ul>
</div>
</div>
</div>
<div id="m3u_list"></div>
Next, write the template file for the common part.
templates/base.html
<!DOCTYPE html>
<html>
<head>
<title>AuPy - Croud Music Player</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
% for item in css: <!--Read the CSS file passed as a parameter-->
<link rel="stylesheet" type="text/css" href="css/{{item}}">
% end
% for item in js: <!--Read the JS file also passed as a parameter-->
<script type="text/javascript" src="js/{{item}}"></script>
% end
</head>
<body>
{{!base}} <!--Here is the contents of the calling template file-->
</body>
</html>
At this stage, I can't run jPlayer yet.
Implement a plugin that will set the jPlayer playlist when you pass the m3u / m3u8 file. It can be implemented as a jQuery plugin by writing it in the format below.
;(function($) {
$.fn.loadm3u = function (){
//Processing content
}
})(jQuery);
This time, we will create a plug-in that assumes the following usage.
$().loadm3u(m3u file path,Server-side music folder path,Path to be replaced(option))
The plug-in has a function to automatically replace the path specified in advance. As a result, the m3u / m3u8 files spit out by foobar2000 etc. can be used as they are in the cloud music player.
#### **`js/jplayer.m3u.js`**
;(function($) { $.fn.loadm3u = function (filepath, server_music_dir, local_music_dir){ $.get(filepath, function(data){ //Get the m3u file passed as an argument var data_array = data.split(/\r\n|\r|\n/); //Split with line breaks var playlists = []; for (var i = 0; i < data_array.length; i++){ //Process line by line if(data_array[i] != ""){ if (typeof local_music_dir != "undefined") { data_array[i] = data_array[i].replace(local_music_dir+"\", "") //Remove the path to be replaced } unix_dir_path = data_array[i].replace(/\/g, "/") //Correct backslash to slash title = unix_dir_path.split("/"); //Split with a slash playlists.push({"title":title[title.length-1], "free":true, "mp3":server_music_dir+"/"+unix_dir_path}); //Store the file name and file path of the sound source in the list } }
$("#jquery_jplayer_N").jPlayer("destroy"); //Initialize jPlayer
var Playlist = new jPlayerPlaylist( //Playlists and options to load into jPlayer
{
jPlayer: "#jquery_jplayer_N",
cssSelectorAncestor: "#jp_container_N"
}, playlists, {
supplied: "mp3" //Supported audio file formats
}
);
});
}
})(jQuery);
In addition, although only mp3 is specified in the above script, it seems that it will be played without problems even if you pass a music file such as ogg just for the file path. flac didn't work because the browser doesn't support playback.
## Generate JS file for initialization in Python
First, prepare a script to describe the settings collectively.
#### **`setting.py`**
```py
#! -*- coding: utf-8 -*-
import os
SERVER_MUSIC_ADDR = os.path.expandvars('audio') #Relative to music folders/Absolute URL
SERVER_PLAYLIST_ADDR = os.path.expandvars('playlist') #Relative to playlist folders/Absolute URL
SERVER_PLAYLIST_DIR = os.path.expandvars('/home/example/aupy/playlist') #Absolute path of playlist folder
LOCAL_MUSIC_DIR = os.path.expandvars('C:\Users\example\Music') #Absolute path of the local music folder to be replaced
Next, list the files in the playlist folder on the server, add them to `` <div id =" m3u_list "> </ div>
`, and when the user clicks on the item, load it into the plugin you just created. Output the JS file to be set in Python.
au.py
from bottle import route, response
from setting import * #Read the configuration file
@route('/js/init.js') #Hijack the request for this JS file
def initjs():
if LOCAL_MUSIC_DIR:
local_music_dir = ', "'+LOCAL_MUSIC_DIR.replace('\\', '\\\\')+'"' #Set the replacement target path
else:
local_music_dir = ''
output = '$(function(){\n' #Start writing output contents
files = os.listdir(SERVER_PLAYLIST_DIR) #List files in playlist folder
files.sort()
flg = True
for file in files:
if file.startswith('.'): #.To skip htaccess etc.
continue
id = file.translate(None, '.')
output += '$("#m3u_list").append("<a href=\\"#\\" id=\\"m3u_file_'+id+'\\" class=\\"list-group-item\\">'+file+'</a>");\n' #Add playlist file to HTML
output += '$("#m3u_file_'+id+'").click(function(){ $().loadm3u("'+SERVER_PLAYLIST_ADDR+'/'+file+'", "'+SERVER_MUSIC_ADDR+'"'+local_music_dir+'); });\n' #Click trigger to pass m3u file to plugin
if flg:
output += '$().loadm3u("'+SERVER_PLAYLIST_ADDR+'/'+file+'", "'+SERVER_MUSIC_ADDR+'"'+local_music_dir+');\n' #Automatically load the first playlist
flg = False
output += '\n});'
response.content_type = 'text/javascript' #Set MIME Type to JS
return output
Am I the only one who can obfuscate the code at once by implementing the #string operation?
Finally, upload the music file and playlist to the server and the player is complete. Requests for these static files can be accepted by Bottle as shown below, but it is more stable if they are properly accepted by Apache, which will be described later.
au.py
from bottle import route, static_file
@route('/css/<filename>')
def css(filename):
return static_file(filename, root=ROOT_PATH+'/css')
@route('/js/<filename>')
def js(filename):
return static_file(filename, root=ROOT_PATH+'/js')
@route('/audio/<filepath:path>')
def audio(filepath):
return static_file(filepath, root=ROOT_PATH+'/audio')
@route('/playlist/<filename>')
def playlist(filename):
return static_file(filename, root=ROOT_PATH+'/playlist'
It is copyright-friendly to make music files accessible to anyone, so set a password. Basic authentication can also be applied to Bottle. This time, we will get the authentication information from `` `.htpasswd``` which is the password file for Apache Basic authentication. However, if you enable both Apache Basic authentication, it will not work properly, so be sure to disable either one.
au.py
from bottle import route, auth_basic
from crypt import crypt
def check(user, passwd): #Create your own password check function(In other words, it can also be used for colander authentication)
for line in open(ROOT_PATH+'/.htpasswd', 'r'): #.Get line by line from htpasswd
htpasswd = line[:-1].split(':') #Separate username and encrypted password
if user is not None and htpasswd[1] is None: #.Enable authentication even if only the user name is set
if user == htpasswd[0]:
return True
elif user is not None and passwd is not None:
if user == htpasswd[0] and crypt(passwd, htpasswd[1][0:2]) == htpasswd[1]: #Match username and encrypted password
return True
return False
@route('/')
@auth_basic(check) #Add to the request requesting authentication
def index():
return template('player.html')
The server function of Bottle is difficult to stabilize, so it is better to limit it to development only. This time, I will introduce an example of calling from Apache, but it depends greatly on the environment, so please use it as a reference. First, create an adapter in the WSGI file that will be called from Apache and passed to Bottle.
adapter.wsgi
import sys, os
sys.path.append(os.path.dirname(os.path.abspath(__file__))) #Pass the path of the folder where the script is located
import bottle, au #Load the Bottle itself and the app
application = bottle.default_app()
Next, write the following in a file suitable for your environment, such as Apache's httpd.conf
or wsgi.conf
, or other virtual host configuration file.
httpd.conf
WSGIScriptAlias /aupy /home/example/aupy/adapter.wsgi #Specify adapter
<Directory "/home/example/aupy/">
Options ExecCGI Indexes FollowSymlinks
AllowOverride All #.Enable htaccess
Order deny,allow
Allow from all
</Directory>
Alias /aupy/audio /home/example/aupy/audio
<Directory "/home/example/aupy/audio">
AllowOverride All
Order deny,allow
Allow from all
</Directory>
Alias /aupy/playlist /home/example/aupy/playlist
<Directory "/home/example/aupy/playlist">
AllowOverride All
Order deny,allow
Allow from all
</Directory>
Also, if you apply Basic authentication to the music folder, it seems that it will not start playing correctly on Android Chrome, so as a countermeasure for direct access, I compromised with restrictions by referrer or user agent (specify a specific model).
.htaccess
SetEnvIf Referer "^http://example.com.*" allowReferer #Allow access from cloud music player
SetEnvIf User-Agent "example" allowReferer #Allowed for certain UAs
order deny,allow
deny from all
allow from env=allowReferer
I forgot to write the important thing. Since I am a Western music HM kitchen, there is no problem, but I think the general public will want to play music with Japanese file names. Unfortunately I didn't know how to route multibyte URLs in Bottle so please let me know if you can. Apache can handle multi-byte URLs by introducing mod_encoding.
Introducing the commands executed in CentOS 6.2. Please note that the path etc. differs depending on the distribution. First, download the necessary files from the console.
wget http://webdav.todo.gr.jp/download/mod_encoding-20021209.tar.gz
wget http://iij.dl.sourceforge.jp/webdav/13905/mod_encoding.c.apache2.20040616
wget http://www.aconus.com/~oyaji/faq/mod_encoding.c-apache2.2-20060520.patch
Extract the downloaded file and apply the patch.
tar zxvf mod_encoding-20021209.tar.gz
cd mod_encoding-20021209/
./configure --with-apxs=/usr/sbin/apxs
cp ../mod_encoding.c.apache2.20040616 mod_encoding.c
patch -p0 < ../mod_encoding.c-apache2.2-20060520.patch
Install the included iconv_hook library.
cd lib
./configure
make
sudo make install
sudo vi /etc/ld.so.conf
/usr/local/Add lib
ldconfig
Finally, install mod_encoding.
cd ../
make
gcc -shared -o mod_encoding.so mod_encoding.o -Wc,-Wall -L/usr/local/lib -Llib -liconv_hook
sudo make install
Add the following to the Apache configuration file.
httpd.conf
<IfModule mod_encoding.c>
EncodingEngine On
SetServerEncoding UTF-8
DefaultClientEncoding UTF-8 CP932 EUCJP-MS
AddClientEncoding "RMA/*" CP932
AddClientEncoding "xdwin9x/" CP932
AddClientEncoding "cadaver/" UTF-8 EUCJP-MS
AddClientEncoding "Mozilla/" EUCJP-MS
</IfModule>
Finally, restart Apache with sudo service httpd restart
and you should be able to access URLs containing multibytes correctly.
How was the introduction of Bottle? By all means, even geeks should try to make one cloud music player for each family!
Recommended Posts