Skip to content

Commit

Permalink
Merge branch 'audio-playback-tool'
Browse files Browse the repository at this point in the history
  • Loading branch information
MaZderMind committed Feb 9, 2016
2 parents c1cc107 + 5352bfa commit faf3fb7
Showing 1 changed file with 197 additions and 0 deletions.
197 changes: 197 additions & 0 deletions example-scripts/gstreamer/source-nostream-music-from-folder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#!/usr/bin/env python3
import os, sys, gi, signal, random
import argparse, logging, pyinotify

gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, GLib

# init GObject & Co. before importing local classes
GObject.threads_init()
Gst.init([])

class Directory(object):
def __init__(self, path):
self.log = logging.getLogger('Directory')
self.path = path
self.scheduled = False
self.rescan()

self.log.debug('setting up inotify watch for %s', self.path)
wm = pyinotify.WatchManager()
notifier = pyinotify.Notifier(wm,
timeout=10,
default_proc_fun=self.inotify_callback)

wm.add_watch(
self.path,
#pyinotify.ALL_EVENTS,
pyinotify.IN_DELETE | pyinotify.IN_CREATE | pyinotify.IN_MODIFY,
rec=True)

GLib.io_add_watch(
notifier._fd,
GLib.IO_IN,
self.io_callback,
notifier)

def inotify_callback(self, notifier):
self.log.info('inotify callback %s: %s', notifier.maskname, notifier.pathname)
if not self.scheduled:
self.scheduled = True
GLib.timeout_add(100, self.rescan)
return True

def io_callback(self, source, condition, notifier):
notifier.process_events()
while notifier.check_events():
notifier.read_events()
notifier.process_events()

return True

def is_playable_file(self, filepath):
root, ext = os.path.splitext(filepath)
return ext in ['.mp3', '.ogg', '.oga', '.wav', '.m4a', '.flac', 'self.opus']

def rescan(self):
self.log.info('scanning directory %s', self.path)
self.scheduled = False

all_files = []

for root, dirs, files in os.walk(self.path):
files = filter(self.is_playable_file, files)
files = map(lambda f: os.path.join(root, f), files)
files = list(files)

self.log.debug('found directory %s: %u playable file(s)', root, len(files))
all_files.extend(files)

self.log.info('found %u playable files', len(all_files))
self.files = all_files

def get_random_file(self):
return random.choice(self.files)

def get_random_uri(self):
return 'file://'+self.get_random_file()


class LoopSource(object):
def __init__(self, directory):
self.log = logging.getLogger('LoopSource')
self.directory = directory

pipeline = """
audioresample name=join !
audioconvert !
audio/x-raw,format=S16LE,channels=2,layout=interleaved,rate=48000 !
matroskamux !
tcpclientsink host=localhost port=18000
"""

# Parsing Pipeline
self.log.debug('creating pipeline\n%s', pipeline)
self.pipeline = Gst.parse_launch(pipeline)

# Selecting inital URI
inital_uri = self.directory.get_random_uri()
self.log.info('initial track %s', inital_uri)

# Create decoder-element
self.src = Gst.ElementFactory.make('uridecodebin', None)
self.src.set_property('uri', inital_uri);
self.src.connect('pad-added', self.on_pad_added)
self.pipeline.add(self.src)

# Save pad on the Join-Element
self.joinpad = self.pipeline.get_by_name('join').get_static_pad('sink')

# Binding End-of-Stream-Signal on Source-Pipeline
self.pipeline.bus.add_signal_watch()
self.pipeline.bus.connect("message::eos", self.on_eos)
self.pipeline.bus.connect("message::error", self.on_error)

self.log.debug('setting pipeline to playing')
self.pipeline.set_state(Gst.State.PLAYING)

def on_pad_added(self, src, pad):
self.log.debug('new pad on decoder, setting pad-probe')
pad.add_probe(Gst.PadProbeType.EVENT_DOWNSTREAM | Gst.PadProbeType.BLOCK, self.on_pad_event)
if self.joinpad.is_linked():
self.log.debug('unlinking with joinpad')
self.joinpad.unlink(self.joinpad.get_peer())

clock = self.pipeline.get_clock()
if clock:
runtime = clock.get_time() - self.pipeline.get_base_time()
self.log.debug('setting pad offset to pipeline runtime: %sns', runtime)
pad.set_offset(runtime)

self.log.debug('linking with joinpad')
pad.link(self.joinpad)

def on_pad_event(self, pad, info):
event = info.get_event()
self.log.debug('event %s on pad %s', event.type, pad)

if event.type == Gst.EventType.EOS:
self.log.debug('scheduling next track and dropping EOS-Event')
GObject.idle_add(self.next_track)
return Gst.PadProbeReturn.DROP

return Gst.PadProbeReturn.PASS

def next_track(self):
next_uri = self.directory.get_random_uri()
self.log.info('next track %s', next_uri)

self.src.set_state(Gst.State.READY)
self.src.set_property('uri', next_uri);
self.src.set_state(Gst.State.PLAYING)
return False

def on_eos(self, bus, message):
self.log.info('received EOS-Event on bus, exiting')
sys.exit(1)

def on_error(self, bus, message):
self.log.warning('received Error-Event on bus, exiting')
(error, debug) = message.parse_error()
self.log.warning('Error-Details: #%u: %s', error.code, debug)
sys.exit(1)

def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)

parser = argparse.ArgumentParser(description='Voctocore Music-Source')
parser.add_argument('directory')

parser.add_argument('-v|-vv', '--verbose', action='count', default=0,
help="Also print INFO and DEBUG messages.")

args = parser.parse_args()

if args.verbose >= 2:
level = logging.DEBUG
elif args.verbose == 1:
level = logging.INFO
else:
level = logging.WARNING

logging.basicConfig(
level=level,
format='%(levelname)8s %(name)s: %(message)s')

directory = Directory(args.directory)
src = LoopSource(directory)

mainloop = GObject.MainLoop()
try:
mainloop.run()
except KeyboardInterrupt:
print('Terminated via Ctrl-C')


if __name__ == '__main__':
main()

0 comments on commit faf3fb7

Please sign in to comment.