switched database to sqlite. changed api to use query strings. removed library dependency.
This commit is contained in:
parent
38808dba67
commit
a7ae72c82a
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ __pycache__/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*.json
|
*.json
|
||||||
|
*.db
|
||||||
|
|
|
@ -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 Flask-RESTful`
|
Python packages: `flask gunicorn mutagen`
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
1. Get on the floor
|
1. Get on the floor
|
||||||
|
|
204
database.py
Normal file
204
database.py
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
API for interacting with the Musik database.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
import mutagen
|
||||||
|
import mutagen.mp3
|
||||||
|
|
||||||
|
MUSIC_DIR = "/home/iou1name/music/Music"
|
||||||
|
MUSIC_EXT = ['flac', 'mp3', 'wav', 'm4a']
|
||||||
|
DB_LOCK = multiprocessing.Lock()
|
||||||
|
|
||||||
|
def db_execute(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Opens a connection to the app's database and executes the SQL statements
|
||||||
|
passed to this function.
|
||||||
|
"""
|
||||||
|
with sqlite3.connect('musik.db') as con:
|
||||||
|
DB_LOCK.acquire()
|
||||||
|
con.row_factory = sqlite3.Row
|
||||||
|
cur = con.cursor()
|
||||||
|
res = cur.execute(*args, **kwargs)
|
||||||
|
DB_LOCK.release()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def db_execute_unsafe(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
A non-thread-safe version of `db_execute()`. Only use if you're
|
||||||
|
certain there will be no competing threads for the database.
|
||||||
|
"""
|
||||||
|
with sqlite3.connect('musik.db') as con:
|
||||||
|
con.row_factory = sqlite3.Row
|
||||||
|
cur = con.cursor()
|
||||||
|
res = cur.execute(*args, **kwargs)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def db_execute_many(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Same as `db_executei()` except with `cur.executemany()`.
|
||||||
|
"""
|
||||||
|
with sqlite3.connect('musik.db') as con:
|
||||||
|
DB_LOCK.acquire()
|
||||||
|
con.row_factory = sqlite3.Row
|
||||||
|
cur = con.cursor()
|
||||||
|
res = cur.executemany(*args, **kwargs)
|
||||||
|
DB_LOCK.release()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def init_database():
|
||||||
|
"""
|
||||||
|
Initializes the database.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
db_execute_unsafe("SELECT * FROM tracks LIMIT 1").fetchone()
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
db_execute("CREATE TABLE tracks("
|
||||||
|
"filepath TEXT PRIMARY KEY,"
|
||||||
|
"artist TEXT,"
|
||||||
|
"albumartist TEXT,"
|
||||||
|
"date TEXT,"
|
||||||
|
"album TEXT,"
|
||||||
|
"discnumber TEXT,"
|
||||||
|
"tracknumber TEXT,"
|
||||||
|
"title TEXT,"
|
||||||
|
"genre TEXT,"
|
||||||
|
"length TEXT"
|
||||||
|
")"
|
||||||
|
)
|
||||||
|
build_library(MUSIC_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def build_library(root_dir):
|
||||||
|
"""Walks the music directory and builds a library of tracks."""
|
||||||
|
print("Building library")
|
||||||
|
filepaths = []
|
||||||
|
for dir_name, sub_dirs, files in os.walk(root_dir):
|
||||||
|
for file in files:
|
||||||
|
if not os.path.splitext(file)[1][1:] in MUSIC_EXT:
|
||||||
|
continue
|
||||||
|
filepath = os.path.join(root_dir, dir_name, file)
|
||||||
|
filepaths.append(filepath)
|
||||||
|
|
||||||
|
global worker
|
||||||
|
def worker(filepath):
|
||||||
|
"""Worker for multi-processing tracks."""
|
||||||
|
data = read_track(filepath)
|
||||||
|
return data
|
||||||
|
|
||||||
|
with multiprocessing.Pool() as pool:
|
||||||
|
mapping = pool.imap(worker, filepaths)
|
||||||
|
tracks = []
|
||||||
|
prev_percent = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
tracks.append(mapping.next())
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
percent = round(len(tracks) / len(filepaths) * 100, 2)
|
||||||
|
if percent >= prev_percent + 2.5:
|
||||||
|
print(f"{percent}%")
|
||||||
|
prev_percent = percent
|
||||||
|
db_execute_many(
|
||||||
|
"INSERT INTO tracks VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
tracks
|
||||||
|
)
|
||||||
|
print("Done")
|
||||||
|
|
||||||
|
|
||||||
|
def read_track(filepath):
|
||||||
|
"""
|
||||||
|
Reads the specified file and extracts relevant information from it.
|
||||||
|
Returns a tuple.
|
||||||
|
"""
|
||||||
|
if filepath.endswith("mp3"):
|
||||||
|
m = mutagen.mp3.EasyMP3(filepath)
|
||||||
|
else:
|
||||||
|
m = mutagen.File(filepath)
|
||||||
|
artist = m.get('artist', [''])[0]
|
||||||
|
if m.get('albumartist'):
|
||||||
|
albumartist = m.get('albumartist', [''])[0]
|
||||||
|
else:
|
||||||
|
albumartist = m.get('artist', [''])[0]
|
||||||
|
date = m.get('date', [''])[0]
|
||||||
|
album = m.get('album', [''])[0]
|
||||||
|
discnumber = m.get('discnumber', [''])[0]
|
||||||
|
tracknumber = m.get('tracknumber', [''])[0]
|
||||||
|
title = m.get('title', [''])[0]
|
||||||
|
genre = m.get('genre', [''])[0]
|
||||||
|
length = str(int(m.info.length) // 60) + ":"
|
||||||
|
length += str(int(m.info.length) % 60)
|
||||||
|
|
||||||
|
d = locals()
|
||||||
|
d.pop('m')
|
||||||
|
return tuple(d.values())
|
||||||
|
|
||||||
|
|
||||||
|
def insert_track(data):
|
||||||
|
"""
|
||||||
|
Inserts a new item into the `tracks` table. `data` should be a tuple
|
||||||
|
containing an item for every column in the `tracks` table. See the
|
||||||
|
table declarion in `init_database()` for more information.
|
||||||
|
"""
|
||||||
|
db_execute(
|
||||||
|
"INSERT INTO tracks VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
data
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_artist_albums(artist):
|
||||||
|
"""
|
||||||
|
Returns a list of all albums produced by the specified `artist`.
|
||||||
|
"""
|
||||||
|
albums = db_execute(
|
||||||
|
"SELECT DISTINCT album FROM tracks WHERE artist = ? ORDER BY date ASC",
|
||||||
|
(artist,)
|
||||||
|
).fetchall()
|
||||||
|
albums = [row[0] for row in albums]
|
||||||
|
return albums
|
||||||
|
|
||||||
|
def get_album_tracks(artist, album):
|
||||||
|
"""
|
||||||
|
Returns a list of all tracks on the `album` produced by `artist`.
|
||||||
|
"""
|
||||||
|
tracks = db_execute(
|
||||||
|
"SELECT discnumber, tracknumber, title FROM tracks "
|
||||||
|
"WHERE artist = ? AND album = ?"
|
||||||
|
"ORDER BY discnumber ASC, tracknumber ASC",
|
||||||
|
(artist, album)
|
||||||
|
).fetchall()
|
||||||
|
tracks = [tuple(row) for row in tracks]
|
||||||
|
return tracks
|
||||||
|
|
||||||
|
def get_track(artist, album, discnumber, tracknumber):
|
||||||
|
"""
|
||||||
|
Returns a dictionary containing all information about a specific track.
|
||||||
|
"""
|
||||||
|
track = db_execute(
|
||||||
|
"SELECT * FROM tracks "
|
||||||
|
"WHERE artist = ? AND album = ? "
|
||||||
|
"AND discnumber = ? AND tracknumber = ?",
|
||||||
|
(artist, album, discnumber, tracknumber)
|
||||||
|
).fetchone()
|
||||||
|
return dict(track)
|
||||||
|
|
||||||
|
def get_random_track():
|
||||||
|
"""
|
||||||
|
Returns a random track.
|
||||||
|
"""
|
||||||
|
track = db_execute(
|
||||||
|
"SELECT * FROM tracks ORDER BY random() LIMIT 1"
|
||||||
|
).fetchone()
|
||||||
|
return dict(track)
|
||||||
|
|
||||||
|
def get_all_artists():
|
||||||
|
"""
|
||||||
|
Returns a list of all artist names.
|
||||||
|
"""
|
||||||
|
artists = db_execute(
|
||||||
|
"SELECT DISTINCT albumartist FROM tracks ORDER BY artist ASC"
|
||||||
|
)
|
||||||
|
artists = [row[0] for row in artists]
|
||||||
|
return artists
|
266
musik.py
266
musik.py
|
@ -6,228 +6,83 @@ import os
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import subprocess
|
import subprocess
|
||||||
import multiprocessing
|
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
from flask import Flask, Response, render_template, send_file, url_for
|
from flask import Flask, Response, render_template, send_file, url_for, request, abort
|
||||||
from flask_restful import reqparse, abort, Api, Resource
|
|
||||||
import mutagen
|
import database
|
||||||
import mutagen.mp3
|
|
||||||
|
|
||||||
MUSIC_DIR = "/home/iou1name/music/Music"
|
|
||||||
MUSIC_EXT = ['flac', 'mp3', 'wav', 'm4a']
|
|
||||||
FFMPEG_CMD = [
|
FFMPEG_CMD = [
|
||||||
'ffmpeg', '-y',
|
'ffmpeg', '-y',
|
||||||
'-loglevel', 'panic',
|
'-loglevel', 'panic',
|
||||||
'-i', '',
|
'-i', '',
|
||||||
'-codec:a', 'libopus',
|
|
||||||
'-b:a', '64k',
|
'-b:a', '64k',
|
||||||
'-f', 'opus',
|
'-f', 'opus',
|
||||||
'-'
|
'-'
|
||||||
]
|
]
|
||||||
|
|
||||||
class Track:
|
|
||||||
def __init__(self, filepath=None, d=None):
|
|
||||||
if d:
|
|
||||||
for attr, value in d.items():
|
|
||||||
setattr(self, attr, value)
|
|
||||||
return
|
|
||||||
|
|
||||||
if filepath.endswith("mp3"):
|
|
||||||
m = mutagen.mp3.EasyMP3(filepath)
|
|
||||||
else:
|
|
||||||
m = mutagen.File(filepath)
|
|
||||||
self.tracknumber = m.get('tracknumber', [''])[0]
|
|
||||||
self.discnumber = m.get('discnumber', [''])[0]
|
|
||||||
self.title = m.get('title', [''])[0]
|
|
||||||
if m.get('albumartist'):
|
|
||||||
self.artist = m.get('albumartist', [''])[0]
|
|
||||||
else:
|
|
||||||
self.artist = m.get('artist', [''])[0]
|
|
||||||
self.album = m.get('album', [''])[0]
|
|
||||||
self.date = m.get('date', [''])[0]
|
|
||||||
self.genre = m.get('genre', [''])[0]
|
|
||||||
self.length = str(int(m.info.length) // 60) + ":"
|
|
||||||
self.length += str(int(m.info.length) % 60)
|
|
||||||
self.filepath = filepath
|
|
||||||
self.coverart = os.path.join(
|
|
||||||
os.path.dirname(self.filepath), 'folder.jpg')
|
|
||||||
|
|
||||||
|
|
||||||
def build_library(root_dir):
|
|
||||||
"""Walks the music directory and builds a library of tracks."""
|
|
||||||
print("Building library")
|
|
||||||
filepaths = []
|
|
||||||
for dir_name, sub_dirs, files in os.walk(root_dir):
|
|
||||||
for file in files:
|
|
||||||
if not os.path.splitext(file)[1][1:] in MUSIC_EXT:
|
|
||||||
continue
|
|
||||||
filepath = os.path.join(root_dir, dir_name, file)
|
|
||||||
filepaths.append(filepath)
|
|
||||||
|
|
||||||
global worker
|
|
||||||
def worker(filepath):
|
|
||||||
"""Worker for multi-processing tracks."""
|
|
||||||
track = Track(filepath)
|
|
||||||
return track
|
|
||||||
|
|
||||||
with multiprocessing.Pool() as pool:
|
|
||||||
mapping = pool.imap(worker, filepaths)
|
|
||||||
tracks = []
|
|
||||||
prev_percent = 0
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
tracks.append(mapping.next())
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
percent = round(len(tracks) / len(filepaths) * 100, 2)
|
|
||||||
if percent >= prev_percent + 2.5:
|
|
||||||
print(f"{percent}%")
|
|
||||||
prev_percent = percent
|
|
||||||
|
|
||||||
print("Done")
|
|
||||||
return tracks
|
|
||||||
|
|
||||||
|
|
||||||
def init_library():
|
|
||||||
"""Loads the library from file, or builds a new one if one isn't found."""
|
|
||||||
if os.path.isfile("library.json"):
|
|
||||||
with open("library.json", "r") as file:
|
|
||||||
tracks = json.loads(file.read())
|
|
||||||
tracks = [Track(d=d) for d in tracks]
|
|
||||||
else:
|
|
||||||
tracks = build_library(MUSIC_DIR)
|
|
||||||
with open("library.json", "w") as file:
|
|
||||||
file.write(json.dumps([vars(t) for t in tracks]))
|
|
||||||
return tracks
|
|
||||||
|
|
||||||
|
|
||||||
def escape(string):
|
|
||||||
"""Escape things."""
|
|
||||||
string = parse.quote(string, safe='')
|
|
||||||
string = string.replace('&', '%26')
|
|
||||||
return string
|
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
api = Api(app)
|
database.init_database()
|
||||||
tracks = init_library()
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Main index page."""
|
"""Main index page."""
|
||||||
artists = list(set(t.artist for t in tracks))
|
artists = database.get_all_artists()
|
||||||
artists.sort()
|
|
||||||
return render_template('index.html', **locals())
|
return render_template('index.html', **locals())
|
||||||
|
|
||||||
|
|
||||||
parser = reqparse.RequestParser()
|
@app.route('/select')
|
||||||
parser.add_argument('artist')
|
def select():
|
||||||
parser.add_argument('album')
|
"""Retrieve information about an artist, album or track."""
|
||||||
parser.add_argument('track')
|
artist = request.args.get('artist')
|
||||||
|
album = request.args.get('album')
|
||||||
|
discnumber = request.args.get('discnumber')
|
||||||
|
tracknumber = request.args.get('tracknumber')
|
||||||
|
|
||||||
def validate_select_args(args):
|
if tracknumber:
|
||||||
"""
|
result = database.get_track(artist, album, discnumber, tracknumber)
|
||||||
If a track is specified, both artist and album must also be specified.
|
if not result:
|
||||||
If an album is specified, the artist must also be specified.
|
return ("Valid artist, album, discnumber and tracknumber "
|
||||||
"""
|
"parameters must be supplied.")
|
||||||
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()
|
|
||||||
validate_select_args(args)
|
|
||||||
|
|
||||||
if args.get('track'):
|
|
||||||
for track in tracks:
|
|
||||||
if (track.artist == args.get('artist') and
|
|
||||||
track.album == args.get('album') and
|
|
||||||
track.title == args.get('track')):
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
abort(404, message="Track does not exist.")
|
result.pop('filepath')
|
||||||
found = dict(vars(track))
|
elif album:
|
||||||
found.pop('filepath')
|
result = database.get_album_tracks(artist, album)
|
||||||
found['streampath'] = url_for(
|
if not result:
|
||||||
'stream',
|
return "Valid artist and album parameters must be supplied."
|
||||||
artist=escape(track.artist),
|
elif artist:
|
||||||
album=escape(track.album),
|
result = database.get_artist_albums(artist)
|
||||||
track=escape(track.title))
|
if not result:
|
||||||
found['coverart'] = url_for(
|
return "Valid artist parameter must be supplied."
|
||||||
'coverart',
|
else:
|
||||||
artist=escape(track.artist),
|
return "Invalid query."
|
||||||
album=escape(track.album),
|
return json.dumps(result)
|
||||||
track=escape(track.title))
|
|
||||||
return found
|
|
||||||
|
|
||||||
elif args.get('album'):
|
|
||||||
found = []
|
|
||||||
for track in tracks:
|
|
||||||
if (track.artist == args.get('artist') and
|
|
||||||
track.album == args.get('album')):
|
|
||||||
found.append(track)
|
|
||||||
if not found:
|
|
||||||
abort(404, message="Album does not exist.")
|
|
||||||
found = [f"{t.discnumber}.{t.tracknumber} - {t.title}" for t in found]
|
|
||||||
found.sort()
|
|
||||||
return found
|
|
||||||
|
|
||||||
elif args.get('artist'):
|
|
||||||
found = []
|
|
||||||
for track in tracks:
|
|
||||||
if track.artist == args.get('artist'):
|
|
||||||
found.append(track)
|
|
||||||
if not found:
|
|
||||||
abort(404, message="Artist does not exist.")
|
|
||||||
seen = {track.album: track.date for track in found}
|
|
||||||
found = sorted(seen.keys(), key=lambda album: seen[album])
|
|
||||||
return found
|
|
||||||
|
|
||||||
|
|
||||||
class RandomSelection(Resource):
|
@app.route('/select/random')
|
||||||
def get(self):
|
def select_random():
|
||||||
global tracks
|
"""
|
||||||
track = random.choice(tracks)
|
Selects a random track.
|
||||||
found = dict(vars(track))
|
"""
|
||||||
found.pop('filepath')
|
track = database.get_random_track()
|
||||||
found['streampath'] = url_for(
|
track.pop('filepath')
|
||||||
'stream',
|
return json.dumps(track)
|
||||||
artist=track.artist,
|
|
||||||
album=track.album,
|
|
||||||
track=track.title)
|
|
||||||
found['coverart'] = url_for(
|
|
||||||
'coverart',
|
|
||||||
artist=track.artist,
|
|
||||||
album=track.album,
|
|
||||||
track=track.title)
|
|
||||||
return found
|
|
||||||
|
|
||||||
api.add_resource(Selection, '/select')
|
|
||||||
api.add_resource(RandomSelection, '/select/random')
|
|
||||||
api.init_app(app)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/stream/<artist>/<album>/<track>')
|
@app.route('/stream')
|
||||||
def stream(artist, album, track):
|
def stream():
|
||||||
"""View for the raw audio file."""
|
"""View for the raw audio file."""
|
||||||
artist, album, track = map(parse.unquote, (artist, album, track))
|
artist = parse.unquote(request.args.get('artist'))
|
||||||
for t in tracks:
|
album = parse.unquote(request.args.get('album'))
|
||||||
if (t.artist == artist and
|
discnumber = parse.unquote(request.args.get('discnumber'))
|
||||||
t.album == album and
|
tracknumber = parse.unquote(request.args.get('tracknumber'))
|
||||||
t.title == track):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
abort(404, message="Track does not exist.")
|
|
||||||
|
|
||||||
FFMPEG_CMD[5] = t.filepath
|
track = database.get_track(artist, album, discnumber, tracknumber)
|
||||||
|
if not track:
|
||||||
|
return "Track does not exist."
|
||||||
|
|
||||||
|
FFMPEG_CMD[5] = track['filepath']
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
with subprocess.Popen(FFMPEG_CMD, stdout=subprocess.PIPE) as proc:
|
with subprocess.Popen(FFMPEG_CMD, stdout=subprocess.PIPE) as proc:
|
||||||
|
@ -238,20 +93,21 @@ def stream(artist, album, track):
|
||||||
return Response(generate(), mimetype="audio/ogg")
|
return Response(generate(), mimetype="audio/ogg")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/coverart/<artist>/<album>/<track>')
|
@app.route('/coverart')
|
||||||
def coverart(artist, album, track):
|
def coverart():
|
||||||
"""View for the raw audio file."""
|
"""View for the raw audio file."""
|
||||||
artist, album, track = map(parse.unquote, (artist, album, track))
|
artist = parse.unquote(request.args.get('artist'))
|
||||||
for t in tracks:
|
album = parse.unquote(request.args.get('album'))
|
||||||
if (t.artist == artist and
|
discnumber = parse.unquote(request.args.get('discnumber'))
|
||||||
t.album == album and
|
tracknumber = parse.unquote(request.args.get('tracknumber'))
|
||||||
t.title == track):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
abort(404, message="Track does not exist.")
|
|
||||||
|
|
||||||
if os.path.isfile(t.coverart):
|
track = database.get_track(artist, album, discnumber, tracknumber)
|
||||||
return send_file(t.coverart)
|
if not track:
|
||||||
|
return "Track does not exist."
|
||||||
|
|
||||||
|
cover_path = os.path.join(os.path.dirname(track['filepath']), 'folder.jpg')
|
||||||
|
if os.path.isfile(cover_path):
|
||||||
|
return send_file(cover_path)
|
||||||
else:
|
else:
|
||||||
return "No cover art for this track found."
|
return "No cover art for this track found."
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,13 @@ function select_artist(select) {
|
||||||
document.getElementById('albumList').innerHTML = html_str;
|
document.getElementById('albumList').innerHTML = html_str;
|
||||||
document.getElementById('trackList').innerHTML = '';
|
document.getElementById('trackList').innerHTML = '';
|
||||||
};
|
};
|
||||||
httpRequest.open('GET', api_uri + '?artist=' + select.value.replace('&', '%26'), true);
|
let params = {
|
||||||
|
artist: select.value,
|
||||||
|
};
|
||||||
|
let query = Object.keys(params)
|
||||||
|
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
|
||||||
|
.join('&');
|
||||||
|
httpRequest.open('GET', api_uri + '?' + query, true);
|
||||||
httpRequest.send();
|
httpRequest.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +51,18 @@ function select_album(select) {
|
||||||
nav_items = JSON.parse(httpRequest.responseText);
|
nav_items = JSON.parse(httpRequest.responseText);
|
||||||
let html_str = '';
|
let html_str = '';
|
||||||
for (let i = 0; i < nav_items.length; i++) {
|
for (let i = 0; i < nav_items.length; i++) {
|
||||||
html_str += '<option value="' + nav_items[i].replace(/^.*? - /, '') + '">' + nav_items[i] + '</option>';
|
html_str += '<option data-discnumber="' + nav_items[i][0] + '" data-tracknumber="' + nav_items[i][1]+ '" value="' + nav_items[i][2] + '">' + nav_items[i][0] + '.' + nav_items[i][1] + ' - ' + nav_items[i][2] + '</option>';
|
||||||
}
|
}
|
||||||
document.getElementById('trackList').innerHTML = html_str;
|
document.getElementById('trackList').innerHTML = html_str;
|
||||||
};
|
};
|
||||||
httpRequest.open('GET', api_uri + '?artist=' + document.getElementById('artistList').value.replace('&', '%26') + '&album=' + select.value.replace('&', '%26'), true);
|
let params = {
|
||||||
|
artist: document.getElementById('artistList').value,
|
||||||
|
album: select.value,
|
||||||
|
};
|
||||||
|
let query = Object.keys(params)
|
||||||
|
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
|
||||||
|
.join('&');
|
||||||
|
httpRequest.open('GET', api_uri + '?' + query, true);
|
||||||
httpRequest.send();
|
httpRequest.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,13 +76,32 @@ function select_track(select) {
|
||||||
let track = JSON.parse(httpRequest.responseText);
|
let track = JSON.parse(httpRequest.responseText);
|
||||||
change_track(track);
|
change_track(track);
|
||||||
};
|
};
|
||||||
httpRequest.open('GET', api_uri + '?artist=' + document.getElementById('artistList').value.replace('&', '%26') + '&album=' + document.getElementById('albumList').value.replace('&', '%26') + '&track=' + select.value.replace('&', '%26'), true);
|
let params = {
|
||||||
|
artist: document.getElementById('artistList').value,
|
||||||
|
album: document.getElementById('albumList').value,
|
||||||
|
discnumber: select.selectedOptions[0].dataset.discnumber,
|
||||||
|
tracknumber: select.selectedOptions[0].dataset.tracknumber,
|
||||||
|
};
|
||||||
|
let query = Object.keys(params)
|
||||||
|
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
|
||||||
|
.join('&');
|
||||||
|
httpRequest.open('GET', api_uri + '?' + query, true);
|
||||||
httpRequest.send();
|
httpRequest.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
function change_track(track) {
|
function change_track(track) {
|
||||||
|
let params = {
|
||||||
|
artist: track.artist,
|
||||||
|
album: track.album,
|
||||||
|
discnumber: track.discnumber,
|
||||||
|
tracknumber: track.tracknumber,
|
||||||
|
};
|
||||||
|
let query = Object.keys(params)
|
||||||
|
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
|
||||||
|
.join('&');
|
||||||
|
|
||||||
let source = document.getElementById('stream');
|
let source = document.getElementById('stream');
|
||||||
source.src = track.streampath;
|
source.src = document.location.href + '/stream?' + query;
|
||||||
let player = document.getElementById('player');
|
let player = document.getElementById('player');
|
||||||
player.load();
|
player.load();
|
||||||
player.play();
|
player.play();
|
||||||
|
@ -77,5 +109,5 @@ function change_track(track) {
|
||||||
document.getElementById('nowPlayingAlbum').innerHTML = track.album;
|
document.getElementById('nowPlayingAlbum').innerHTML = track.album;
|
||||||
document.getElementById('nowPlayingTitle').innerHTML = track.title;
|
document.getElementById('nowPlayingTitle').innerHTML = track.title;
|
||||||
|
|
||||||
document.getElementById('albumCover').firstChild.src = track.coverart;
|
document.getElementById('albumCover').firstChild.src = document.location.href + '/coverart?' + query;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<link rel="stylesheet" type="text/css" href="/static/musik.css">
|
<link rel="stylesheet" type="text/css" href="/static/musik.css">
|
||||||
<script type="text/javascript" src="/static/musik.js"></script>
|
<script type="text/javascript" src="/static/musik.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const api_uri = "{{ url_for('selection') }}";
|
const api_uri = "{{ url_for('select') }}";
|
||||||
</script>
|
</script>
|
||||||
<script>window.onload = load;</script>
|
<script>window.onload = load;</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user