From 8448beeb6210062eef2f016c669b0d9b73449fce Mon Sep 17 00:00:00 2001 From: audiodef Date: Tue, 6 Mar 2018 20:31:29 -0500 Subject: [PATCH] Adding media-sound/boodler - a super fun soundscape tool --- media-sound/boodler/Manifest | 5 + media-sound/boodler/boodler-2.0.4-r1.ebuild | 111 +++++++ media-sound/boodler/files/boodle-ui-qt.py | 316 ++++++++++++++++++++ media-sound/boodler/files/boodler | 86 ++++++ media-sound/boodler/metadata.xml | 12 + 5 files changed, 530 insertions(+) create mode 100644 media-sound/boodler/Manifest create mode 100644 media-sound/boodler/boodler-2.0.4-r1.ebuild create mode 100644 media-sound/boodler/files/boodle-ui-qt.py create mode 100644 media-sound/boodler/files/boodler create mode 100644 media-sound/boodler/metadata.xml diff --git a/media-sound/boodler/Manifest b/media-sound/boodler/Manifest new file mode 100644 index 0000000..948f67f --- /dev/null +++ b/media-sound/boodler/Manifest @@ -0,0 +1,5 @@ +AUX boodle-ui-qt.py 12114 SHA256 b2a0ea4dc175e7e175671bfca48bafe44a0bd37801babcd7e23e492ad34130a4 SHA512 c2770590a888c8e6183bd173ed484bf487e678679d60e2b8de3335cdc78e04eae9e381f831c6c66be6f3b7ceb4002cd46d4f4598d55f3f76bc686a54487610cc WHIRLPOOL 9aade76ecaccebe4146851e92015388348d0fdc0c467932e142cefb08b01706784d0d72857889a69418f90547328e184a415473a44ef894d6531c17abf8d7333 +AUX boodler 2156 SHA256 b2f5fbc14dc152e78fdaca0b23020a8df03956c1987ce379df8cf082937177a1 SHA512 88b4e9ae3f916c02ce76a1f7db9ade5b0a24558d3167e16de051f8e1e3cf734b3845b30b9ac26baa231d34b2a0e73c28ce613f11ae14f606495e6619e489f499 WHIRLPOOL 9466af082362125d5adf69143e964cba1bebe608bc3ed668d7da84d51f5e580588141126a53561d43ac0cd9f650e8c7cbbf38a738229a00a5549a830ca687c98 +DIST Boodler-2.0.4.tar.gz 310801 SHA256 430ca92103203442b1f4c5067ea7f6dc0dbced534bf02a28ed7a8e358aaa55f2 SHA512 d48eb55e552f157e5ec7c456a17f70ec81a7b30fdbe7c7097163c3ee9ae33d726011aecfaecc8c47de8c358eb3b0f46dc0d94fcbe56efa4225e8a2e94e485215 WHIRLPOOL 467dfd1ae8d4d845b5c853cdea5f892c09e7d80c3fb4a571dc5faa509e47586451fb22ed7446537c9363827c89f28343a3831a8f9f02851e4289cc292b1aca5f +EBUILD boodler-2.0.4-r1.ebuild 3191 SHA256 3dc47b0043868b78a1c54259c99b24748de6d58994f163a7ce74970ce75f610f SHA512 7afc523a2aac265254d115eab4886c3e163d5c58dba10e847afe7852f72e5dea9d003411b32a50612d4b319b1e4e161831cd847bc56e2116f7e60cf4a19bf8c2 WHIRLPOOL 43ae7da35a550e0952819a84ecec5a6ea3cc29d85583c2e8db233798c54af5e0c37c6ebde62c473b34fd5b5b1db79233e50a743f961061c0944d6406430c2721 +MISC metadata.xml 457 SHA256 dde8c156d6ec0e6f3dbe3625c2e905d72d736e68f2a86160bd1d84eb5f90b72a SHA512 023ae1900518cb71d67045a02fa33e8b66d4ef1de2ebd842919cb3299baf84aad54d8f47ca865351b65ee484e21a2ad90a789b659275d634e5c15f30d24ce3b5 WHIRLPOOL 8343875e6be7e0f54a02cd4cf7dea695922757158bb1540e56411be392db8d9c1a78101ec2c97880d711477a133a5a7666c9e6ee28cbf13aed9d5559210f237b diff --git a/media-sound/boodler/boodler-2.0.4-r1.ebuild b/media-sound/boodler/boodler-2.0.4-r1.ebuild new file mode 100644 index 0000000..fe7baee --- /dev/null +++ b/media-sound/boodler/boodler-2.0.4-r1.ebuild @@ -0,0 +1,111 @@ +# Copyright 1999-2018 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +EAPI=6 +PYTHON_COMPAT=( python{2_5,2_6,2_7} ) +inherit distutils-r1 bash-completion-r1 + +DESCRIPTION="Tool for creating soundscapes -- continuous, infinitely varying streams of sound" +HOMEPAGE="http://boodler.org/" +RESTRICT="mirror" +SRC_URI="http://boodler.org/dl/Boodler-${PV}.tar.gz" +LICENSE="LGPL-2 GPL-2 public-domain" +SLOT="0" +KEYWORDS="~amd64 ~x86" +IUSE="alsa bash-completion coreaudio doc intmath jack lame qt4 shout vorbis" +REQUIRED_USE="shout? ( vorbis )" + +RDEPEND="alsa? ( >=media-libs/alsa-lib-1.0.17a ) + jack? ( >=media-libs/bio2jack-0.9 ) + lame? ( >=media-sound/lame-3.98.2-r1 ) + shout? ( >=media-libs/libshout-2.1 ) + vorbis? ( >=media-libs/libvorbis-1.2.1_rc1-r2 )" +DEPEND="${RDEPEND} + qt4? ( >=dev-python/PyQt4-4.7.3[X] )" + +S=${WORKDIR}/Boodler-${PV} + +HTML_DOCS=( doc/ ) + +python_prepare_all() { + # fix bash completion script for new file names without extension in 2.0.4 + cp "${FILESDIR}/boodler" "${T}"/boodler || die + sed -i -e s/\.py//g "${T}"/boodler || die + + distutils-r1_python_prepare_all +} + +python_prepare() { + if use qt4 ; then + # copy to tmp dir so it can be modified later + cp "${FILESDIR}"/boodle-ui-qt.py \ + "${T}"/${EPYTHON}/boodle-ui-qt || die + + # fix up the command name which was changed in boodler-2.0.4 + sed -i -e "s/\"boodler.py\"/\"boodler\"/" \ + "${T}"/${EPYTHON}/boodle-ui-qt || die + fi + + distutils-r1_python_prepare +} + +python_configure_all() { + local defdriver + local with + local without + + # set up a default audio driver (not daemon) according to USE flags. + # it does not appear to work in setup.cfg so there is a workaround below. + if use coreaudio ; then + defdriver=macosx + elif use alsa ; then + defdriver=alsa + else + defdriver=oss + fi + + # this ugly code enables/disables the output drivers + # oss seems to be needed for boodler.py --list-drivers to work + with="${with}oss," + use alsa && with="${with}alsa," || without="${without}alsa," + use coreaudio && with="${with}macosx,osxaq," \ + || without="${without}macosx,osxaq," + without="${without}esd," + use jack && with="${with}jackb," || without="${without}jackb," + use lame && with="${with}lame," || without="${without}lame," + use shout && with="${with}shout," || without="${without}shout," + use vorbis && with="${with}vorbis," || without="${without}vorbis," + + # move the original setup.cfg out of the way as a backup to check syntax + mv setup.cfg setup.cfg.orig || die + + # fill the setup.cfg with the values + cat > setup.cfg <<-EOF + [build_scripts] + default_driver=${defdriver} + [build_ext] + with-drivers=${with} + without-drivers=${without} + intmath=$(use intmath && echo 1 || echo 0) + EOF + + mydistutilargs=( --default-driver ${defdriver} ) +} + +python_install() { + # a pyqt4 gui addon for boodler downloaded from the official site + # http://boodler.org/dl/etc/boodle-ui-qt.py + if use qt4 ; then + python_doscript "${T}"/${EPYTHON}/boodle-ui-qt + fi + + distutils-r1_python_install +} + +src_install() { + distutils-r1_src_install + + # a bash completion addon script downloaded from the official site + # http://boodler.org/dl/etc/bash_completion.d/boodler + dobashcomp "${T}/boodler" +} diff --git a/media-sound/boodler/files/boodle-ui-qt.py b/media-sound/boodler/files/boodle-ui-qt.py new file mode 100644 index 0000000..0e35f77 --- /dev/null +++ b/media-sound/boodler/files/boodle-ui-qt.py @@ -0,0 +1,316 @@ +#!/usr/bin/python + +# Boodler: a programmable soundscape tool +# Designed by Andrew Plotkin +# For more information, see +# +# This Python script ("boodle-ui-qt.py") is licensed under the GNU +# Library General Public License (LGPL). +# Author: Tuukka Hastrup +# +# You should have received a copy of the GNU Library General Public License +# along with this program. (It should be a document entitled "LGPL".) +# If not, see the web URL above, or write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import sys +import subprocess +import os, signal + +from boopak import collect # you'll need Boodler on Python's library path + +# same as in boodler.py +if 'darwin' in sys.platform.lower(): + Default_Relative_Data_Dir = 'Library/Application Support/Boodler' +else: + Default_Relative_Data_Dir = '.boodler' + +basedir = None # XXX opts.basedir +if not basedir: + basedir = os.environ.get('BOODLER_DATA') +if not basedir: + basedir = os.path.join(os.environ.get('HOME'), Default_Relative_Data_Dir) + + +# collect the list of agents + +l = collect.PackageCollection(basedir=basedir) + +pkgs = l.list_all_current_packages() + +agents = [] + +for pkgname,_vers in sorted(pkgs): + pkg = l.load(pkgname) + for resname in sorted(pkg.resources.keys()): + res = pkg.resources.get(resname) + if res.get_one("boodler.use") == "agent": + agents += [(pkgname, resname)] + + +def play(agent): + boodler = ["boodler.py", "%s/%s" % agent] + return boodler + +def textplay(agent): + print "Playing %s/%s" % agent, + + pkg = l.load(agent[0]) + res = pkg.resources.get(agent[1]) + print '"%s"' % res.get_one("dc.title"), "... ", + + sys.stdout.flush() + + boodler = subprocess.Popen(play(agent), stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + try: + boodler.communicate() + if boodler.returncode == 0: + print "Done." + else: + print "Boodler failed with exit code %s." % boodler.returncode + except KeyboardInterrupt: + print "\nInterrupted" + os.kill(boodler.pid, signal.SIGTERM) + try: + boodler.wait() + except KeyboardInterrupt: + os.kill(boodler.pid, signal.SIGKILL) + +def textmain(): + for agent in agents: + textplay(agent) + +def qtmain(): + from PyQt4 import QtCore, QtGui # you'll need PyQt4 (python-qt4) + + def index2rowdata(index): + rowdata = (index.sibling(index.row(), 0).data().toString(), + index.sibling(index.row(), 1).data().toString(), + index.sibling(index.row(), 2).data().toString(), + ) + + return rowdata + + class Window(QtGui.QWidget): + def __init__(self, app): + self.boodler = None + + QtGui.QWidget.__init__(self) + + self.connect(app, QtCore.SIGNAL('aboutToQuit()'), self.stopPlay) + + # proxy model for sorting and filtering + self.proxyModel = QtGui.QSortFilterProxyModel() + self.proxyModel.setDynamicSortFilter(True) + self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + self.proxyModel.setFilterKeyColumn(2) + + # widgets for now playing agent + self.nowPlayingGroupBox = QtGui.QGroupBox("Now playing") + self.nowPlayingLabel = QtGui.QLabel("Double-click a soundscape to play\n") + self.nowPlayingButton = QtGui.QPushButton(self.style().standardIcon(QtGui.QStyle.SP_MediaStop), "&Stop") + self.nowPlayingButton.setEnabled(False) + + # signals for now playing agent + self.connect(self.nowPlayingButton, QtCore.SIGNAL('clicked()'), self.stopPlay) + + # widgets for selected agent + self.selectedGroupBox = QtGui.QGroupBox("Selected soundscape") + self.selectedLabel = QtGui.QLabel("Click a soundscape to view details\n") + self.selectedInfoLabel = QtGui.QLabel("Internal name:\nLicense:\nSource:") + self.selectedButton = QtGui.QPushButton(self.style().standardIcon(QtGui.QStyle.SP_MediaPlay), "&Play") + self.selectedButton.setEnabled(False) + + # signals for now playing agent + self.connect(self.selectedButton, QtCore.SIGNAL('clicked()'), self.playButtonClicked) + + # view widgets + self.proxyGroupBox = QtGui.QGroupBox("Available soundscapes") + + self.proxyView = QtGui.QTreeView() + self.proxyView.setRootIsDecorated(False) + self.proxyView.setAlternatingRowColors(True) + self.proxyView.setModel(self.proxyModel) + self.proxyView.setSortingEnabled(True) + self.proxyView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) + self.proxyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.proxyView.setAllColumnsShowFocus(True) + + # filter pattern widgets + self.filterPatternLineEdit = QtGui.QLineEdit() + self.filterPatternLabel = QtGui.QLabel("F&ind:") + self.filterPatternLabel.setBuddy(self.filterPatternLineEdit) + + # filter pattern signals + self.connect(self.filterPatternLineEdit, + QtCore.SIGNAL('textChanged(const QString &)'), + self.filterRegExpChanged) + + self.connect(self.proxyView.selectionModel(), + QtCore.SIGNAL('currentRowChanged(const QModelIndex &, const QModelIndex &)'), + self.itemSelected) + self.connect(self.proxyView, QtCore.SIGNAL('activated(const QModelIndex &)'), self.itemActivated) + + # now playing layout + nowPlayingLayout = QtGui.QHBoxLayout() + nowPlayingLayout.addWidget(self.nowPlayingButton) + nowPlayingLayout.addWidget(self.nowPlayingLabel, 1) + self.nowPlayingGroupBox.setLayout(nowPlayingLayout) + + # selected layout + selectedLayout = QtGui.QVBoxLayout() + selectedLayoutH = QtGui.QHBoxLayout() + selectedLayoutH.addWidget(self.selectedButton) + selectedLayoutH.addWidget(self.selectedLabel, 1) + selectedLayout.addLayout(selectedLayoutH) + selectedLayout.addWidget(self.selectedInfoLabel) + self.selectedGroupBox.setLayout(selectedLayout) + + # available agents layout + filterPatternLayout = QtGui.QHBoxLayout() + filterPatternLayout.addWidget(self.filterPatternLabel) + filterPatternLayout.addWidget(self.filterPatternLineEdit) + + proxyLayout = QtGui.QVBoxLayout() + proxyLayout.addWidget(self.proxyView) + proxyLayout.addLayout(filterPatternLayout) + self.proxyGroupBox.setLayout(proxyLayout) + + # main layout + mainLayout = QtGui.QVBoxLayout() + mainLayout.addWidget(self.nowPlayingGroupBox) + mainLayout.addWidget(self.selectedGroupBox) + mainLayout.addWidget(self.proxyGroupBox) + self.setLayout(mainLayout) + + # set top-level window properties + self.setWindowTitle("Boodle UI") + self.resize(500, 450) + + # set initial state + self.proxyView.sortByColumn(2, QtCore.Qt.AscendingOrder) + self.filterPatternLineEdit.setText("") + + def setSourceModel(self, model): + self.proxyModel.setSourceModel(model) + + def filterRegExpChanged(self): + regExp = QtCore.QRegExp(self.filterPatternLineEdit.text(), QtCore.Qt.CaseInsensitive, QtCore.QRegExp.Wildcard) + self.proxyModel.setFilterRegExp(regExp) + + def itemSelected(self, currentIndex, _previousIndex): + if not currentIndex.isValid(): + return # keep the latest selection, if any + row = index2rowdata(currentIndex) + self.lastSelectedRow = row + self.selectedButton.setEnabled(True) + self.updateSelected(row) + + def itemActivated(self, index): + row = index2rowdata(index) + self.startPlay((str(row[0]), str(row[1]))) + self.updateNowPlaying(row) + + def playButtonClicked(self): + row = self.lastSelectedRow + self.startPlay((str(row[0]), str(row[1]))) + self.updateNowPlaying(row) + + def updateNowPlaying(self, row): + pkg = l.load(str(row[0])) + creator = pkg.metadata.get_one("dc.creator") + pkgtitle = pkg.metadata.get_one("dc.title") + + msg = '"%s"\nby %s from "%s"' % (row[2], creator, pkgtitle) + self.nowPlayingLabel.setText(msg) + + def updateSelected(self, row): + pkg = l.load(str(row[0])) + creator = pkg.metadata.get_one("dc.creator") + pkgtitle = pkg.metadata.get_one("dc.title") + license = pkg.metadata.get_one("dc.license") + source = pkg.metadata.get_one("dc.source") + + msg = '"%s"\nby %s from "%s"' % (row[2], creator, pkgtitle) + self.selectedLabel.setText(msg) + + info = 'Internal name: %s/%s' % (row[0], row[1]) + info += '\nLicense: %s' % license + info += '\nSource: %s' % source + self.selectedInfoLabel.setText(info) + + def startPlay(self, agent): + self.stopPlay() + + self.boodler = QtCore.QProcess(self) + self.connect(self.boodler, QtCore.SIGNAL('error(QProcess::ProcessError)'), self.playError) + self.connect(self.boodler, QtCore.SIGNAL('finished(int,QProcess::ExitStatus)'), self.playFinished) + + cmd = play(agent) + print "Launching: %s" % ' '.join(cmd) + self.boodler.start(cmd[0], cmd[1:]) + + self.nowPlayingButton.setEnabled(True) + + def stopPlay(self): + if self.boodler: + self.disconnect(self.boodler, QtCore.SIGNAL('error(QProcess::ProcessError)'), self.playError) + self.disconnect(self.boodler, QtCore.SIGNAL('finished(int, QProcess::ExitStatus)'), self.playFinished) + if self.boodler.state() != QtCore.QProcess.NotRunning: + self.boodler.terminate() + if not self.boodler.waitForFinished(5000): + self.boodler.kill() + self.boodler = None + + self.nowPlayingButton.setEnabled(False) + + def playError(self, error): + if error == QtCore.QProcess.FailedToStart: + print "Boodler failed to start: %s" % self.boodler.errorString() + elif error == QtCore.QProcess.Crashed: + print "Boodler crashed with exit code %s." % self.boodler.exitCode() + else: + print "Boodler failed with error %s." % error + + self.stopPlay() + + def playFinished(self, exitCode, exitStatus): + if exitCode != 0: + print self.boodler.readAllStandardError() + print "Boodler failed with exit code %s." % exitCode + else: + print "Boodler done playing." + + self.stopPlay() + + def createAgentModel(parent): + model = QtGui.QStandardItemModel(0, 3, parent) + + model.setHeaderData(0, QtCore.Qt.Horizontal, QtCore.QVariant("Package")) + model.setHeaderData(1, QtCore.Qt.Horizontal, QtCore.QVariant("Resource")) + model.setHeaderData(2, QtCore.Qt.Horizontal, QtCore.QVariant("Title")) + + def standardItem(x): + if x is None: + return QtGui.QStandardItem("") + else: + return QtGui.QStandardItem(x) + + for agent in reversed(agents): + pkg = l.load(agent[0]) + res = pkg.resources.get(agent[1]) + title = res.get_one("dc.title") + model.appendRow(map(standardItem, [agent[0], agent[1], title])) + + return model + + app = QtGui.QApplication(sys.argv) + window = Window(app) + window.setSourceModel(createAgentModel(window)) + window.show() + sys.exit(app.exec_()) + + +qtmain() diff --git a/media-sound/boodler/files/boodler b/media-sound/boodler/files/boodler new file mode 100644 index 0000000..7fc205e --- /dev/null +++ b/media-sound/boodler/files/boodler @@ -0,0 +1,86 @@ +# /etc/bash_completion.d/boodler #v3 +mgr=boodle-mgr.py + +#types="agent" # only agents +types="agent\|sound" # + +typefilter=".*\[\(${types}\)\].*" + + +#20091113 - v3 - typefilter introduction +#20091113 - v2 - cleanup, documentation, bugfix, substring version +#20091113 - v1 - initial working version + +# does simple bash completion for boodler.py in via wildcard or a two-step process +# type +# boodler.py # to get package completion +# boodler.py name.me.example/ # for agent completion +# boodler.py exam* # get substring matching completion + + +_boodlelib() +{ + # case insensitive matches + if `shopt -q nocasematch`; then + oldnocasematchset=true ; else oldnocasematchset=false ; + fi + shopt -s nocasematch # FIXME - TRAP ME + + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + # packages list (have to remove the last line 'xx packages installed...') + packages=`$mgr -- list 2>/dev/null | sed "$ s/.*//"` + + + case "$cur" in + *\*) # WILDCARD - long, thorough search (package/agent completion) + cur="${cur%\*}" # removing the wildcard + agents="" + for p in $packages; do + ags=`$mgr contents "$p" 2>/dev/null | grep -e "${typefilter}" | awk '{print $1}'` + #echo ags_${ags}_ags + for ag in $ags; do + pag="$p/$ag" + if [[ "$pag" == *${cur}* ]] ; then + #echo "$pag" == *${cur}* + + agents+="$pag " + fi + done + done + + COMPREPLY=( ${agents} ) + ;; + */*) # AGENT completion + p="${cur%\/}" # removing the trailing slash + + ags=`$mgr contents "$p" 2>/dev/null | grep -e "${typefilter}" | awk '{print $1}'` + for ag in $ags; do + # echo found $ag + agents+="$p/$ag " + done + + opts="$agents" + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + ;; + *) # PACKAGE completion + # match first on packages (with slashes at the end) + opts=`echo "$packages" | sed "s/\([a-zA-Z0-9]\)$/\1\//"` + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + ;; + esac + + # clean-up + if ${oldnocasematchset} ; then + shopt -s nocasematch + else + shopt -u nocasematch + fi + + return 0 +} + +complete -o nospace -F _boodlelib boodler.py diff --git a/media-sound/boodler/metadata.xml b/media-sound/boodler/metadata.xml new file mode 100644 index 0000000..f74ecde --- /dev/null +++ b/media-sound/boodler/metadata.xml @@ -0,0 +1,12 @@ + + + + + webmaster@gentoostudio.org + Gentoo Studio/Damien Moody + + + reduces the number of floating-point operations at the cost of less accurate volume fading + Enable shoutcast support + +