Musik/musik.py

175 lines
4.3 KiB
Python
Raw Normal View History

2018-09-12 13:04:00 -04:00
#!/usr/bin/env python3
"""
Music streaming.
"""
2018-09-13 14:22:27 -04:00
import os
2019-01-11 11:27:25 -05:00
import re
2018-09-13 14:22:27 -04:00
import json
2018-09-14 08:02:24 -04:00
import random
2018-09-12 13:04:00 -04:00
import subprocess
2018-09-13 14:22:27 -04:00
from urllib import parse
2018-09-12 13:04:00 -04:00
from flask import Flask, Response, render_template, send_file
import mutagen
2018-09-12 13:04:00 -04:00
2018-09-13 14:22:27 -04:00
MUSIC_DIR = "/mnt/music/Music"
2018-09-14 08:02:24 -04:00
MUSIC_EXT = ['flac', 'mp3', 'wav', 'm4a']
2018-09-12 13:04:00 -04:00
FFMPEG_CMD = [
'ffmpeg', '-y',
'-loglevel', 'panic',
'-i', '',
'-codec:a', 'libopus',
'-b:a', '64k',
'-f', 'opus',
'-'
]
class Track:
def __init__(self, filepath=None, d=None):
if d:
self._from_dict(d)
return
if filepath.endswith("mp3"):
m = mutagen.mp3.EasyMP3(filepath)
else:
m = mutagen.File(filepath)
self.title = m.get('title', [''])[0]
self.artist = m.get('artist', [''])[0]
self.ablum = m.get('album', [''])[0]
self.date = m.get('date', [''])[0]
self.length = str(int(m.info.length) // 60) + ":"
self.length += str(int(m.info.length) % 60)
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):
"""Walks the music directory and builds a library of tracks."""
print("Building library")
tracks = []
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)
track = Track(filepath)
tracks.append(track)
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([t.__dict__ for t in tracks]))
return tracks
2019-01-11 11:27:25 -05:00
2018-09-12 13:04:00 -04:00
app = Flask(__name__)
tracks = init_library()
2018-09-12 13:04:00 -04:00
@app.route('/')
def index():
"""Main index page."""
nav_items = list(set(t.artist for t in tracks))
2018-09-13 14:22:27 -04:00
nav_items.sort()
nav_items = [item + '/' for item in nav_items]
cd = "/"
return render_template('index.html', **locals())
2018-09-12 13:04:00 -04:00
2018-09-13 14:22:27 -04:00
@app.route('/stream/<path:track>')
2018-09-12 13:04:00 -04:00
def stream(track):
"""View for the raw audio file."""
2019-01-11 11:27:25 -05:00
track = parse.unquote(track)
2018-09-13 14:22:27 -04:00
path = os.path.join(MUSIC_DIR, track)
path = os.path.abspath(path)
if not path.startswith(MUSIC_DIR):
return "False"
if not os.path.isfile(path):
return "False"
FFMPEG_CMD[5] = path
2018-09-12 13:04:00 -04:00
def generate():
with subprocess.Popen(FFMPEG_CMD, stdout=subprocess.PIPE) as proc:
data = proc.stdout.read(1024)
while data:
yield data
data = proc.stdout.read(1024)
return Response(generate(), mimetype="audio/ogg")
2018-09-13 14:22:27 -04:00
@app.route('/get_dir/<path:directory>/')
2019-01-11 11:27:25 -05:00
@app.route('/get_dir/')
def get_dir(directory=""):
2018-09-13 14:22:27 -04:00
"""Returns the contents of the requested directory."""
directory = directory.replace("DOTDOT", "..")
directory = os.path.join(MUSIC_DIR, directory)
directory = os.path.abspath(directory)
if not directory.startswith(MUSIC_DIR):
return "False"
if not os.path.isdir(directory):
return "False"
nav_items = os.listdir(directory)
nav_items.sort()
if directory != MUSIC_DIR:
nav_items = [".."] + nav_items
nav_items_new = []
for item in nav_items:
if os.path.isdir(os.path.join(directory, item)):
item += '/'
nav_items_new.append(item)
nav_items_new = [directory.replace(MUSIC_DIR, '') + '/'] + nav_items_new
return json.dumps(nav_items_new)
2018-09-14 08:02:24 -04:00
@app.route('/get_shuffle')
def shuffle():
"""Returns a randomly selected track from the library."""
2018-09-14 08:02:24 -04:00
item = random.choice(os.listdir(MUSIC_DIR))
path = os.path.join(MUSIC_DIR, item)
n = 0
while not item.rpartition('.')[2] in MUSIC_EXT:
n += 1
2018-09-14 08:02:24 -04:00
item = random.choice(os.listdir(path))
if os.path.isdir(os.path.join(path, item)):
path = os.path.join(path, item)
if n == 5:
item = random.choice(os.listdir(MUSIC_DIR))
path = os.path.join(MUSIC_DIR, item)
n = 0
path = os.path.join(path, item)
2018-09-14 08:02:24 -04:00
return path.replace(MUSIC_DIR, '')
@app.route('/album_cover/<path:cover>')
def album_cover(cover):
"""View for the raw audio file."""
path = os.path.join(MUSIC_DIR, cover)
path = os.path.abspath(path)
if not path.startswith(MUSIC_DIR):
return "False"
if not os.path.isfile(path):
return "False"
return send_file(path)
2018-09-12 13:04:00 -04:00
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5150)