#!/usr/bin/env python3 """ Music streaming. """ import os import re import json import random import subprocess from urllib import parse from flask import Flask, Response, render_template, send_file import mutagen MUSIC_DIR = "/mnt/music/Music" MUSIC_EXT = ['flac', 'mp3', 'wav', 'm4a'] 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 app = Flask(__name__) tracks = init_library() @app.route('/') def index(): """Main index page.""" nav_items = list(set(t.artist for t in tracks)) nav_items.sort() nav_items = [item + '/' for item in nav_items] cd = "/" return render_template('index.html', **locals()) @app.route('/stream/') def stream(track): """View for the raw audio file.""" track = parse.unquote(track) 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 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") @app.route('/get_dir//') @app.route('/get_dir/') def get_dir(directory=""): """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) @app.route('/get_shuffle') def shuffle(): """Returns a randomly selected track from the library.""" 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 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) return path.replace(MUSIC_DIR, '') @app.route('/album_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) if __name__ == "__main__": app.run(host='0.0.0.0', port=5150)