Browse Source

transcode non-mp3s

podcasts
dave 5 years ago
parent
commit
99aae4165f
  1. 46
      pysonic/api.py
  2. 7
      pysonic/daemon.py
  3. 8
      pysonic/database.py
  4. 7
      pysonic/library.py
  5. 4
      pysonic/scanner.py
  6. 4
      pysonic/types.py
  7. 10
      requirements.txt

46
pysonic/api.py

@ -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()

7
pysonic/daemon.py

@ -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,

8
pysonic/database.py

@ -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:

7
pysonic/library.py

@ -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
pysonic/scanner.py

@ -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

@ -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

@ -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…
Cancel
Save