from bottle import request, response import bottle import psycopg import random from contextlib import contextmanager from data import midi2wavtool, wavtoolm2midi dbconnstr = "dbname=composer_new_dataset_v3 host=localhost" app = bottle.Bottle() @app.route("/<:re:.*>", method="OPTIONS") def enable_cors_generic_route(): add_cors_headers() @app.hook("after_request") def enable_cors_after_request_hook(): add_cors_headers() def add_cors_headers(): response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS" response.headers["Access-Control-Allow-Headers"] = ( "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token" ) @contextmanager def dbconn(): with psycopg.connect(dbconnstr) as conn: conn.autocommit = True yield conn def setup_tag(cur, tag_value): cur.execute( "INSERT INTO tag_values (value) VALUES (%s) ON CONFLICT DO NOTHING", (tag_value,), ) cur.execute("SELECT id FROM tag_values WHERE value = %s", (tag_value,)) return cur.fetchone()[0] def setup_tags(conn): with conn.cursor() as cur, conn.transaction(): return setup_tag(cur, "Good at Time"), setup_tag(cur, "Bad at Time") @app.post("/rate") def rate_clip(): with dbconn() as conn, conn.cursor() as cur: good_tag_id, bad_tag_id = setup_tags(conn) data = request.json if not isinstance(data, dict): return {"error": "Invalid JSON"} if "id" not in data or "is_good" not in data: return {"error": "Missing fields"} file_id, start = data["id"].split("-") file_id = int(file_id) start = int(start) cur.execute( "INSERT INTO manual_file_tags (file_id, tag_value_id, start) VALUES (%s, %s, %s)", (file_id, good_tag_id if data["is_good"] else bad_tag_id, start), ) return {"success": True} def dedupe_clips(clips): accum = [] for wclip in clips: for note in wavtoolm2midi(wclip): if not all( anote["note"] != note["note"] or anote["offBeat"] <= note["onBeat"] or anote["onBeat"] >= note["offBeat"] for anote in accum ): continue accum.append(note) return midi2wavtool(accum) @app.post("/clip") def get_clip(): with dbconn() as conn, conn.cursor() as cur: good_tag_id, bad_tag_id = setup_tags(conn) def sample(): # choose a random file randval = random.random() cur.execute( "select id from files where rand_order > %s order by rand_order asc limit 1", (randval,), ) if cur.rowcount == 0: return None file_id = cur.fetchone()[0] # choose a random time from the file that hasn't already been tagged cur.execute( """ with starts as ( select distinct start::integer from extracted_clips where file_id = %s ) select start from starts where not exists ( select from manual_file_tags t where file_id = %s and tag_value_id = any(%s) and t.start = starts.start ) order by random() limit 1; """, (file_id, file_id, [good_tag_id, bad_tag_id]), ) if cur.rowcount == 0: return None start = cur.fetchone()[0] # get all extracted clips for the chosen time cur.execute( """ select n.notes, exists ( select from automatic_extracted_clip_tags t join tag_values v on t.tag_value_id = v.id where t.extracted_clip_id = c.id and v.value in ('ML Analyzed Drums', 'Analyzed Drums', 'Flagged Drums') ) as is_drums from extracted_clips c join extracted_clip_notes n on c.id = n.extracted_clip_id where c.file_id = %s and c.start = %s and c.symbolic_length is not null and exists ( select from automatic_extracted_clip_tags t join tag_values v on t.tag_value_id = v.id where t.extracted_clip_id = c.id and v.value = 'Trainable as Main' ) """, (file_id, start), ) rows = cur.fetchall() if len(rows) == 0: return None return { "id": f"{file_id}-{start}", "piano": dedupe_clips([row[0] for row in rows if not row[1]]), "drums": dedupe_clips([row[0] for row in rows if row[1]]), } for _ in range(100): clip = sample() if clip is not None: return clip response.status = 404 return {"error": "No clips available"} app.run(host="0.0.0.0", port=12349, debug=False)