Skip to content

Commit

Permalink
Merge 9ccd709 into 38d7a60
Browse files Browse the repository at this point in the history
  • Loading branch information
ejhumphrey committed Dec 20, 2016
2 parents 38d7a60 + 9ccd709 commit 4eed903
Show file tree
Hide file tree
Showing 16 changed files with 222 additions and 21 deletions.
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,24 @@ Here is a rough projection of the timeline for progress on the Open-MIC project,

![Open-MIC Roadmap - v1.2](https://github.com/cosmir/open-mic/raw/master/docs/img/roadmap.png "Open-MIC Roadmap - v1.2")

## Running the components

Here are instructions for running the different parts of the system.
## Running the annotation machinery locally

### Content Annotation System
The easiest way to get started is to run the demo at the commandline:

```
$ ./run_demo.sh
```

This will start the backend server (CMS), upload a few audio files, and begin serving the audio annotation tool locally. By default this will appear at [http://localhost:8000/docs/annotator.html](http://localhost:8000/docs/annotator.html).

**Notes**:
- It is **strongly** recommended that a private / incognito session is used for demo purposes. Caching behavior in normal browser sessions can cause significant headaches.
- For some reason, loading the audio seems to get "stuck" on occasion. To unblock it, manually proceed to an audio file's URL once the server is running, e.g. [http://localhost:8080/api/v0.1/audio/740c835f-a23d-41ef-b84c-0cd1de4edfa5](http://localhost:8080/api/v0.1/audio/740c835f-a23d-41ef-b84c-0cd1de4edfa5).

Alternatively, instructions for running the different parts of the system are listed below.

### Content Management System

See the [ReadMe](https://github.com/cosmir/open-mic/blob/master/backend_server/README.md) for details on running the backend web server.

Expand Down
2 changes: 1 addition & 1 deletion audio-annotator
4 changes: 2 additions & 2 deletions backend_server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ At this point, the endpoints should be live via localhost (default deployment is

```
$ curl -X GET localhost:8080/annotation/taxonomy
$ curl -F "audio=@some_file.mp3" localhost:8080/audio/upload
$ curl -F "audio=@some_file.mp3" localhost:8080/audio
```

### Deploying to App Engine
Expand Down Expand Up @@ -82,7 +82,7 @@ You can then poke the endpoints as one would expect:

```
$ curl -X GET http://<PROJECT_ID>.appspot.com/annotation/taxonomy
$ curl -F "audio=@some_file.mp3" http://<PROJECT_ID>/audio/upload
$ curl -F "audio=@some_file.mp3" http://<PROJECT_ID>/audio
```


Expand Down
7 changes: 6 additions & 1 deletion backend_server/local_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
"backend": "local",
"local_dir": "tmp"
},
"database": {
"audio-db": {
"backend": "local",
"filepath": "tmp/database-file.json",
"mode": "a"
},
"annotation-db": {
"backend": "local",
"filepath": "tmp/annot-database-file.json",
"mode": "a"
}
}
77 changes: 63 additions & 14 deletions backend_server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
import datetime
from flask import Flask, request, Response
from flask import send_file
from flask_cors import CORS
import io
import json
import logging
import mimetypes
import random
import requests
import os

Expand All @@ -40,6 +42,11 @@
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)

# TODO: One of the following
# - Whitelist localhost and `SOURCE` below.
# - Use AppEngine for delivery of the annotator HTML.
CORS(app)

# Set the cloud backend
# TODO: This should be controlled by `app.yaml`, right?
CLOUD_CONFIG = os.path.join(os.path.dirname(__file__), 'gcloud_config.json')
Expand Down Expand Up @@ -81,7 +88,7 @@ def audio_upload():
# Keep things like extension, storage platform, mimetype, etc.
dbase = pybackend.database.Database(
project_id=app.config['cloud']['project_id'],
**app.config['cloud']['database'])
**app.config['cloud']['audio-db'])
record = dict(filepath=filepath,
created=str(datetime.datetime.now()))
dbase.put(uri, record)
Expand All @@ -105,7 +112,7 @@ def audio_download(uri):

dbase = pybackend.database.Database(
project_id=app.config['cloud']['project_id'],
**app.config['cloud']['database'])
**app.config['cloud']['audio-db'])

entity = dbase.get(uri)
if entity is None:
Expand Down Expand Up @@ -141,14 +148,20 @@ def annotation_submit():
-X POST localhost:8080/annotation/submit \
-d '{"message":"Hello Data"}'
"""

if request.headers['Content-Type'] == 'application/json':
app.logger.info("Received Annotation:\n{}"
.format(json.dumps(request.json, indent=2)))
# obj = json.loads(request.data)
# Do a thing with the annotation
# Return some progress stats?
data = json.dumps(dict(message='Success!'))
status = 200

db = pybackend.database.Database(
project_id=app.config['cloud']['project_id'],
**app.config['cloud']['annotation-db'])
uri = str(pybackend.utils.uuid(json.dumps(request.json)))
record = dict(created=str(datetime.datetime.now()), **request.json)
db.put(uri, record)
else:
status = 400
data = json.dumps(dict(message='Invalid Content-Type; '
Expand All @@ -160,26 +173,62 @@ def annotation_submit():
return resp


def get_taxonomy():
tax_url = ("https://raw.githubusercontent.com/cosmir/open-mic/"
"ejh_20161119_iss8_webannot/data/instrument_taxonomy_v0.json")
res = requests.get(tax_url)
values = []
try:
schema = res.json()
values = schema['tag_open_mic_instruments']['value']['enum']
except BaseException as derp:
app.logger.error("Failed loading taxonomy: {}".format(derp))

return values


@app.route('/api/v0.1/annotation/taxonomy', methods=['GET'])
def annotation_taxonomy():
"""
To fetch data at this endpoint:
$ curl -X GET localhost:8080/annotation/taxonomy
"""
instruments = get_taxonomy()
status = 200 if instruments else 400

TODO: Clean this up per @alastair's feedback.
resp = Response(json.dumps(instruments), status=status)
resp.headers['Link'] = SOURCE
return resp


@app.route('/api/v0.1/task', methods=['GET'])
def next_task():
"""
data = json.dumps(dict(message='Resource not found'))
status = 404
To fetch data at this endpoint:
tax_url = ("https://raw.githubusercontent.com/marl/jams/master/jams/"
"schemata/namespaces/tag/medleydb_instruments.json")
res = requests.get(tax_url)
if res.text:
data = json.loads(res.text)
status = 200
$ curl -X GET localhost:8080/task
"""
audio_url = "http://localhost:8080/api/v0.1/audio/{}"

resp = Response(data, status=status)
db = pybackend.database.Database(
project_id=app.config['cloud']['project_id'],
**app.config['cloud']['audio-db'])

random_uri = random.choice(list(db.keys()))

task = dict(feedback="none",
visualization=random.choice(['waveform', 'spectrogram']),
proximityTag=[],
annotationTag=get_taxonomy(),
url=audio_url.format(random_uri),
numRecordings='?',
recordingIndex=random_uri,
tutorialVideoURL="https://www.youtube.com/embed/Bg8-83heFRM",
alwaysShowTags=True)
data = json.dumps(dict(task=task))
app.logger.debug("Returning:\n{}".format(data))
resp = Response(data)
resp.headers['Link'] = SOURCE
return resp

Expand Down
4 changes: 4 additions & 0 deletions backend_server/pybackend/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ def delete(self, key):
if key in self._collection:
self._collection.pop(key)

def keys(self):
"""Returns an iterator over the keys in the Client."""
return self._collection.keys()


class GClient(object):
"""Thin wrapper for gcloud's DataStore client.
Expand Down
1 change: 1 addition & 0 deletions backend_server/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
install_requires=[
'audioread >= 2.0.0',
'Flask >= 0.11.1',
'Flask-Cors >= 3.0.2',
'requests',
'protobuf',
'googleapis-common-protos',
Expand Down
5 changes: 5 additions & 0 deletions backend_server/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,8 @@ def test_annotation_submit(app):
def test_annotation_taxonomy(app):
r = app.get('/api/v0.1/annotation/taxonomy')
assert r.status_code == requests.status_codes.codes.OK


def test_task_get(app):
r = app.get('/api/v0.1/task')
assert r.status_code == requests.status_codes.codes.OK
17 changes: 17 additions & 0 deletions backend_server/tests/test_pybackend_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ def test_LocalClient_append(json_file):
assert db.get(key2) == rec2


@pytest.fixture()
def sample_client(json_file):
key = 'a'
exp_rec = dict(x=1, y='13')
collec = {key: exp_rec}
with open(json_file, 'w') as fp:
json.dump(collec, fp)

return D.LocalClient('my-project', filepath=json_file, mode=D.READ)


def test_LocalClient_keys(sample_client):
keys = list(sample_client.keys())
assert len(keys) == 1
assert sample_client.get(keys[0]) == dict(x=1, y='13')


def test_GClient___init__():
assert D.GClient('my-proj')

Expand Down
Binary file added data/audio/267508__mickleness__3nf.ogg
Binary file not shown.
Binary file added data/audio/273427__darshak-bhatti__harmony.ogg
Binary file not shown.
Binary file added data/audio/345515__furbyguy__strings-piano.ogg
Binary file not shown.
Binary file added data/audio/55248__crescendo__asdsdasd.ogg
Binary file not shown.
Binary file added data/audio/58239__crescendo__strings75960.ogg
Binary file not shown.
80 changes: 80 additions & 0 deletions docs/annotator.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html>
<head>
<title>Open-MIC Challenge</title>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" href="../audio-annotator/static/css/materialize.min.css">
<link rel="stylesheet" type="text/css" href="../audio-annotator/static/css/urban-ears.css">

<script type="text/javascript" src="../audio-annotator/static/js/lib/jquery-2.2.3.min.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/lib/materialize.min.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/lib/wavesurfer.min.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/lib/wavesurfer.spectrogram.min.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/colormap/colormap.min.js"></script>

<script type="text/javascript" src="../audio-annotator/static/js/src/message.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/src/wavesurfer.regions.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/src/wavesurfer.drawer.extended.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/src/wavesurfer.labels.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/src/hidden_image.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/src/components.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/src/annotation_stages.js"></script>
<script type="text/javascript" src="../audio-annotator/static/js/src/main.js" defer></script>
</head>
<body>
<div class="row header">
<div class="col s12">
<div class="divider"></div>
</div>
<div class="col s6">
<div class="left">
<h5>Recording <span id="recording-index"></span> of 9</h5>
<h6 class="left">approx. <span id="time-remaining"></span> minutes remaining</h6>
</div>
</div>
<div class="col s6 ">
<div class="right">
<h5>Open-MIC Challenge</h5>
<a class="waves-effect waves-light modal-trigger right" href="#modal1">Tutorial Video</a>
</div>
</div>
<div class="col s12">
<div class="divider bottom"></div>
</div>
</div>
<!-- Modal Structure -->
<div id="modal1" class="modal">
<div class="modal-content">
<h4>Tutorial Video</h4>
<div class="videowrapper">
<iframe id="tutorial-video" width="100%" height="100%" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<div class="modal-footer">
<a href="#!" class=" modal-action modal-close waves-effect waves-red btn-flat">Close</a>
</div>
</div>
<div class="row prompt">
<h4 class="center">Highlight &amp; Tag Each Sound</h4>
<h6 class="center">Click and drag to create a new annotation. Listen for sound events and tag them from the list under the audio recording.</h6>
</div>
<div class="annotation">
<div class="labels"></div>
<div class="audio_visual"></div>
<div class="play_bar"></div>
<div class="hidden_img"></div>
<div class="creation_stage_container"></div>
<div class="submit_container"></div>
</div>
<script>
// Change me! Is there an easy way to config this?
var dataUrl = 'http://localhost:8080/api/v0.1/task';
var postUrl = 'http://localhost:8080/api/v0.1/annotation/submit';

$(document).ready(function(){
$('.modal-trigger').leanModal();
});
</script>
</body>
</html>
27 changes: 27 additions & 0 deletions run_demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

# Start the backend server
python backend_server/main.py --port 8080 --local --debug &
CMS_PID=$!
sleep 4s

# Ingest the audio data
curl -F "audio=@data/audio/267508__mickleness__3nf.ogg" \
localhost:8080/api/v0.1/audio

curl -F "audio=@data/audio/345515__furbyguy__strings-piano.ogg" \
localhost:8080/api/v0.1/audio

sleep 1s
python -m http.server &
HTTP_PID=$!

sleep 1s
# Wait
echo "\n\nAnnotator serving at: http://localhost:8000/docs/annotator.html"
echo "NOTE: Use a private / incognito session to avoid weird cache behavior."
read -n 1 -p " >> Press any key to exit."

# Clean-up
kill $CMS_PID;
kill $HTTP_PID;

0 comments on commit 4eed903

Please sign in to comment.