transcode non-mp3s
This commit is contained in:
parent
a0d25381c4
commit
99aae4165f
@ -1,16 +1,21 @@
|
||||
import sys
|
||||
import logging
|
||||
import cherrypy
|
||||
import subprocess
|
||||
from time import time
|
||||
from bs4 import BeautifulSoup
|
||||
from pysonic.library import LETTER_GROUPS
|
||||
from pysonic.types import MUSIC_TYPES
|
||||
|
||||
|
||||
logging = logging.getLogger("api")
|
||||
|
||||
|
||||
class PysonicApi(object):
|
||||
def __init__(self, db, library):
|
||||
def __init__(self, db, library, options):
|
||||
self.db = db
|
||||
self.library = library
|
||||
self.options = options
|
||||
|
||||
def response(self, status="ok"):
|
||||
doc = BeautifulSoup('', features='lxml-xml')
|
||||
@ -100,6 +105,9 @@ class PysonicApi(object):
|
||||
root.append(dirtag)
|
||||
|
||||
for item in children:
|
||||
# omit not dirs and media in browser
|
||||
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
||||
continue
|
||||
child = doc.new_tag("child",
|
||||
id=item["id"],
|
||||
parent=directory["id"],
|
||||
@ -132,24 +140,34 @@ class PysonicApi(object):
|
||||
yield doc.prettify()
|
||||
|
||||
@cherrypy.expose
|
||||
def stream_view(self, id, **kwargs):
|
||||
# /rest/stream.view?u=dave&s=rid5h452ag6nmb153r8sjtctk8
|
||||
# &t=dad1e6f7331160ea7f04120c7fbab1c8&v=1.2.0&c=DSub&id=167&maxBitRate=256
|
||||
def stream_view(self, id, maxBitRate="256", **kwargs):
|
||||
maxBitRate = int(maxBitRate)
|
||||
assert maxBitRate >= 32 and maxBitRate <= 320
|
||||
fpath = self.library.get_filepath(id)
|
||||
meta = self.library.get_file_metadata(id)
|
||||
cherrypy.response.headers['Content-Type'] = 'audio/mpeg'
|
||||
|
||||
def content():
|
||||
total = 0
|
||||
with open(fpath, "rb") as f:
|
||||
if self.options.skip_transcode and meta["type"] == "audio/mpeg":
|
||||
def content():
|
||||
with open(fpath, "rb") as f:
|
||||
while True:
|
||||
data = f.read(16 * 1024)
|
||||
if not data:
|
||||
break
|
||||
yield data
|
||||
else:
|
||||
def content():
|
||||
transcode_args = ["ffmpeg", "-i", fpath, "-map", "0:0", "-b:a",
|
||||
"{}k".format(min(maxBitRate, self.options.max_bitrate)),
|
||||
"-v", "0", "-f", "mp3", "-"]
|
||||
logging.info(' '.join(transcode_args))
|
||||
start = time()
|
||||
proc = subprocess.Popen(transcode_args, stdout=subprocess.PIPE)
|
||||
while True:
|
||||
data = f.read(8192)
|
||||
data = proc.stdout.read(16 * 1024)
|
||||
if not data:
|
||||
break
|
||||
total += len(data)
|
||||
yield data
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
logging.info("\nSent {} bytes for {}".format(total, fpath))
|
||||
logging.warning("transcoded {} in {}s".format(id, int(time() - start)))
|
||||
return content()
|
||||
stream_view._cp_config = {'response.stream': True}
|
||||
|
||||
@ -173,8 +191,6 @@ class PysonicApi(object):
|
||||
break
|
||||
total += len(data)
|
||||
yield data
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
logging.info("\nSent {} bytes for {}".format(total, fpath))
|
||||
return content()
|
||||
|
||||
|
@ -16,6 +16,11 @@ def main():
|
||||
parser.add_argument('-d', '--dirs', required=True, nargs='+', help="new music dirs to share")
|
||||
parser.add_argument('-s', '--database-path', default="./db.sqlite", help="path to persistent sqlite database")
|
||||
parser.add_argument('--debug', action="store_true", help="enable development options")
|
||||
|
||||
group = parser.add_argument_group("app options")
|
||||
group.add_argument("--skip-transcode", action="store_true", help="instead of trancoding mp3s, send as-is")
|
||||
group.add_argument("--max-bitrate", type=int, default=320, help="maximum send bitrate")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=logging.INFO if args.debug else logging.WARNING)
|
||||
@ -33,7 +38,7 @@ def main():
|
||||
logging.warning("Libraries: {}".format([i["name"] for i in library.get_libraries()]))
|
||||
logging.warning("Artists: {}".format([i["name"] for i in library.get_artists()]))
|
||||
|
||||
cherrypy.tree.mount(PysonicApi(db, library), '/rest/', {'/': {}})
|
||||
cherrypy.tree.mount(PysonicApi(db, library, args), '/rest/', {'/': {}})
|
||||
cherrypy.config.update({
|
||||
'sessionFilter.on': True,
|
||||
'tools.sessions.on': True,
|
||||
|
@ -52,7 +52,7 @@ class PysonicDatabase(object):
|
||||
|
||||
# Initialize DB
|
||||
if len(cursor.fetchall()) == 0:
|
||||
logging.waring("Initializing database")
|
||||
logging.warning("Initializing database")
|
||||
for query in queries:
|
||||
cursor.execute(query)
|
||||
else:
|
||||
@ -105,7 +105,11 @@ class PysonicDatabase(object):
|
||||
cursor.execute("UPDATE nodes SET metadata=? WHERE id=?;", (json.dumps(metadata), node_id, ))
|
||||
|
||||
def get_metadata(self, node_id):
|
||||
return self.decode_metadata(self.getnode(node_id)["metadata"])
|
||||
keys_in_table = ["title", "album", "artist", "type"]
|
||||
node = self.getnode(node_id)
|
||||
metadata = self.decode_metadata(node["metadata"])
|
||||
metadata.update({item: node[item] for item in ["title", "album", "artist", "type"]})
|
||||
return metadata
|
||||
|
||||
def decode_metadata(self, metadata):
|
||||
if metadata:
|
||||
|
@ -69,14 +69,17 @@ class PysonicLibrary(object):
|
||||
return self.db.getnodes(dirid)
|
||||
|
||||
@memoize
|
||||
def get_filepath(self, fileid):
|
||||
parents = [self.db.getnode(fileid)]
|
||||
def get_filepath(self, nodeid):
|
||||
parents = [self.db.getnode(nodeid)]
|
||||
while parents[-1]['parent'] != -1:
|
||||
parents.append(self.db.getnode(parents[-1]['parent']))
|
||||
root = parents.pop()
|
||||
parents.reverse()
|
||||
return os.path.join(json.loads(root['metadata'])['fspath'], *[i['name'] for i in parents])
|
||||
|
||||
def get_file_metadata(self, nodeid):
|
||||
return self.db.get_metadata(nodeid)
|
||||
|
||||
def get_artist_info(self, item_id):
|
||||
# artist = self.db.getnode(item_id)
|
||||
return {"biography": "placeholder biography",
|
||||
|
@ -4,9 +4,9 @@ import logging
|
||||
import mimetypes
|
||||
from time import time
|
||||
from threading import Thread
|
||||
from pysonic.types import KNOWN_MIMES
|
||||
|
||||
|
||||
KNOWN_MIMES = ["audio/mpeg", "audio/flac", "audio/x-wav", "image/jpeg", "image/png"]
|
||||
logging = logging.getLogger("scanner")
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ class PysonicFilesystemScanner(object):
|
||||
# Create any nodes not found in the db
|
||||
for create in to_create:
|
||||
new_node = self.library.db.addnode(parent["id"], path, create)
|
||||
logging.info("Added", os.path.join(path, create))
|
||||
logging.info("Added {}".format(os.path.join(path, create)))
|
||||
db_entires.append(new_node)
|
||||
|
||||
# Delete any db nodes not found on disk
|
||||
|
4
pysonic/types.py
Normal file
4
pysonic/types.py
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
KNOWN_MIMES = ["audio/mpeg", "audio/flac", "audio/x-wav", "image/jpeg", "image/png"]
|
||||
MUSIC_TYPES = ["audio/mpeg", "audio/flac", "audio/x-wav"]
|
||||
IMAGE_TYPES = ["image/jpeg", "image/png"]
|
10
requirements.txt
Normal file
10
requirements.txt
Normal file
@ -0,0 +1,10 @@
|
||||
beautifulsoup4==4.6.0
|
||||
cheroot==5.8.3
|
||||
CherryPy==11.0.0
|
||||
lxml==3.8.0
|
||||
mutagen==1.38
|
||||
pkg-resources==0.0.0
|
||||
portend==2.1.2
|
||||
pytz==2017.2
|
||||
six==1.10.0
|
||||
tempora==1.8
|
Loading…
Reference in New Issue
Block a user