Compare commits
2 Commits
43efcfc0bf
...
a4af2a1fd5
Author | SHA1 | Date | |
---|---|---|---|
a4af2a1fd5 | |||
1d9037310e |
|
@ -4,7 +4,7 @@ Stream some music.
|
||||||
## Requirements
|
## Requirements
|
||||||
Python 3.6+
|
Python 3.6+
|
||||||
FFmpeg compiled with `--enable-libopus`
|
FFmpeg compiled with `--enable-libopus`
|
||||||
Python packages: `flask gunicorn mutagen`
|
Python packages: `flask gunicorn mutagen Flask-RESTful`
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
1. Get on the floor
|
1. Get on the floor
|
||||||
|
|
81
musik.py
81
musik.py
|
@ -3,13 +3,13 @@
|
||||||
Music streaming.
|
Music streaming.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import subprocess
|
import subprocess
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
from flask import Flask, Response, render_template, send_file
|
from flask import Flask, Response, render_template, send_file
|
||||||
|
from flask_restful import reqparse, abort, Api, Resource
|
||||||
import mutagen
|
import mutagen
|
||||||
|
|
||||||
MUSIC_DIR = "/mnt/music/Music"
|
MUSIC_DIR = "/mnt/music/Music"
|
||||||
|
@ -27,7 +27,8 @@ FFMPEG_CMD = [
|
||||||
class Track:
|
class Track:
|
||||||
def __init__(self, filepath=None, d=None):
|
def __init__(self, filepath=None, d=None):
|
||||||
if d:
|
if d:
|
||||||
self._from_dict(d)
|
for attr, value in d.items():
|
||||||
|
setattr(self, attr, value)
|
||||||
return
|
return
|
||||||
|
|
||||||
if filepath.endswith("mp3"):
|
if filepath.endswith("mp3"):
|
||||||
|
@ -36,20 +37,12 @@ class Track:
|
||||||
m = mutagen.File(filepath)
|
m = mutagen.File(filepath)
|
||||||
self.title = m.get('title', [''])[0]
|
self.title = m.get('title', [''])[0]
|
||||||
self.artist = m.get('artist', [''])[0]
|
self.artist = m.get('artist', [''])[0]
|
||||||
self.ablum = m.get('album', [''])[0]
|
self.album = m.get('album', [''])[0]
|
||||||
self.date = m.get('date', [''])[0]
|
self.date = m.get('date', [''])[0]
|
||||||
self.length = str(int(m.info.length) // 60) + ":"
|
self.length = str(int(m.info.length) // 60) + ":"
|
||||||
self.length += str(int(m.info.length) % 60)
|
self.length += str(int(m.info.length) % 60)
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
|
|
||||||
def _from_dict(self, d):
|
|
||||||
self.title = d.get('title')
|
|
||||||
self.artist = d.get('artist')
|
|
||||||
self.ablum = d.get('album')
|
|
||||||
self.date = d.get('date')
|
|
||||||
self.length = d.get('length')
|
|
||||||
self.filepath = d.get('filepath')
|
|
||||||
|
|
||||||
|
|
||||||
def build_library(root_dir):
|
def build_library(root_dir):
|
||||||
"""Walks the music directory and builds a library of tracks."""
|
"""Walks the music directory and builds a library of tracks."""
|
||||||
|
@ -64,6 +57,7 @@ def build_library(root_dir):
|
||||||
tracks.append(track)
|
tracks.append(track)
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
|
||||||
def init_library():
|
def init_library():
|
||||||
"""Loads the library from file, or builds a new one if one isn't found."""
|
"""Loads the library from file, or builds a new one if one isn't found."""
|
||||||
if os.path.isfile("library.json"):
|
if os.path.isfile("library.json"):
|
||||||
|
@ -73,23 +67,78 @@ def init_library():
|
||||||
else:
|
else:
|
||||||
tracks = build_library(MUSIC_DIR)
|
tracks = build_library(MUSIC_DIR)
|
||||||
with open("library.json", "w") as file:
|
with open("library.json", "w") as file:
|
||||||
file.write(json.dumps([t.__dict__ for t in tracks]))
|
file.write(json.dumps([vars(t) for t in tracks]))
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
api = Api(app)
|
||||||
tracks = init_library()
|
tracks = init_library()
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Main index page."""
|
"""Main index page."""
|
||||||
nav_items = list(set(t.artist for t in tracks))
|
artist = list(set(t.artist for t in tracks))
|
||||||
nav_items.sort()
|
artist.sort()
|
||||||
nav_items = [item + '/' for item in nav_items]
|
|
||||||
cd = "/"
|
|
||||||
return render_template('index.html', **locals())
|
return render_template('index.html', **locals())
|
||||||
|
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument('artist')
|
||||||
|
parser.add_argument('album')
|
||||||
|
parser.add_argument('track')
|
||||||
|
|
||||||
|
def validate_select_args(args):
|
||||||
|
"""
|
||||||
|
If a track is specified, both artist and album must also be specified.
|
||||||
|
If an album is specified, the artist must also be specified.
|
||||||
|
"""
|
||||||
|
if args.get('track'):
|
||||||
|
if not args.get('artist') or not args.get('album'):
|
||||||
|
abort(400, message="Artist and album must also be specified.")
|
||||||
|
elif args.get('album'):
|
||||||
|
if not args.get('artist'):
|
||||||
|
abort(400, message="Artist must also be specified.")
|
||||||
|
elif not args.get('artist'):
|
||||||
|
abort(400, message="You must specify at least an artist.")
|
||||||
|
|
||||||
|
class Selection(Resource):
|
||||||
|
def get(self):
|
||||||
|
global tracks
|
||||||
|
args = parser.parse_args()
|
||||||
|
print(args)
|
||||||
|
validate_select_args(args)
|
||||||
|
|
||||||
|
if args.get('track'):
|
||||||
|
track = [t for t in tracks
|
||||||
|
if (t.title == args.get('track') and
|
||||||
|
t.album == args.get('album') and
|
||||||
|
t.artist == args.get('artist'))]
|
||||||
|
if not track:
|
||||||
|
abort(404, message="Track does not exist.")
|
||||||
|
else:
|
||||||
|
return vars(track[0])
|
||||||
|
|
||||||
|
elif args.get('album'):
|
||||||
|
tracks = [t for t in tracks
|
||||||
|
if (t.album == args.get('album') and
|
||||||
|
t.artist == args.get('artist'))]
|
||||||
|
if not tracks:
|
||||||
|
abort(404, message="Album does not exist.")
|
||||||
|
else:
|
||||||
|
return [t.title for t in tracks]
|
||||||
|
|
||||||
|
elif args.get('artist'):
|
||||||
|
albums = list(set(t.album for t in tracks
|
||||||
|
if t.artist == args.get('artist')))
|
||||||
|
if not albums:
|
||||||
|
abort(404, message="Artist does not exist.")
|
||||||
|
else:
|
||||||
|
return sorted(albums)
|
||||||
|
|
||||||
|
api.add_resource(Selection, '/select')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/stream/<path:track>')
|
@app.route('/stream/<path:track>')
|
||||||
def stream(track):
|
def stream(track):
|
||||||
"""View for the raw audio file."""
|
"""View for the raw audio file."""
|
||||||
|
|
|
@ -21,16 +21,41 @@ img {
|
||||||
#navigationContainer {
|
#navigationContainer {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 100%;
|
||||||
border-top: 2px solid #ccc;
|
border-top: 2px solid #ccc;
|
||||||
border-bottom: 2px solid #ccc;
|
border-bottom: 2px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
#currentDirectory {
|
#selectArtistContainer {
|
||||||
padding-left: 0.5em;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navItems {
|
#selectArtist {
|
||||||
list-style-type: none;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectAlbumContainer {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectAlbum {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectTrackContainer {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectTrack {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#playerContainer {
|
#playerContainer {
|
||||||
|
|
|
@ -10,12 +10,22 @@
|
||||||
<div id="globalContainer">
|
<div id="globalContainer">
|
||||||
<div id="titleContainer"><h1>Musik</h1></div>
|
<div id="titleContainer"><h1>Musik</h1></div>
|
||||||
<div id="navigationContainer">
|
<div id="navigationContainer">
|
||||||
<div id="currentDirectory">{{ cd }}</div>
|
<div id="selectArtistContainer">
|
||||||
<ul id="navItems">
|
<select id="selectArtist" size="2">
|
||||||
{% for item in nav_items %}
|
{% for artist in artists %}
|
||||||
<li><a href="javascript:void(0);" onclick="navigate('{{ item }}')">{{ item }}</a></li>
|
<!--<li><a href="javascript:void(0);" onclick="navigate('{{ item }}')">{{ item }}</a></li>-->
|
||||||
{% endfor %}
|
<option>{{ artist }}</option>
|
||||||
</ul>
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="selectAlbumContainer">
|
||||||
|
<select id="selectAlbum" size="2">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="selectTrackContainer">
|
||||||
|
<select id="selectTrack" size="2">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="playerContainer">
|
<div id="playerContainer">
|
||||||
<span id="albumCover"><img src=""/></span>
|
<span id="albumCover"><img src=""/></span>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user