From ae9dd8e6b6bd272a6a1955c8b5e98e3418850df3 Mon Sep 17 00:00:00 2001 From: Domenico Testa Date: Fri, 17 Apr 2020 13:32:43 +0200 Subject: [PATCH] Adding a command line client --- client/client.py | 127 ++++++++++++++++++++++++++++++++++++++++ client/requirements.txt | 3 + worker/separator.py | 4 +- 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 client/client.py create mode 100644 client/requirements.txt diff --git a/client/client.py b/client/client.py new file mode 100644 index 0000000..e2d3b17 --- /dev/null +++ b/client/client.py @@ -0,0 +1,127 @@ +''' +Simple command line client to remove vocals from audio/video tracks. +''' + +import os +import json +import time + +import click +import requests +from pygments import highlight, lexers, formatters + + +API_ENDPOINT_URL = os.getenv('API_ENDPOINT_URL') + + +def api_req(ctx: click.core.Context, method: str, url: str, **kwargs): + method = method.lower() + if method not in ['get', 'post']: + raise ValueError(f'Unsupported HTTP method "{method}"') + + actual_url = ctx.obj['url'](url) + + if ctx.obj['verbose']: + print(f'GET {actual_url}') + + response = getattr(requests, method)(actual_url, **kwargs) + + if ctx.obj['verbose']: + print(f'Server response status: {response.status_code}') + echo_obj(response.json()) + + return response + + +def api_get(ctx: click.core.Context, url: str, **kwargs): + return api_req(ctx, 'GET', url, **kwargs) + + +def api_post(ctx: click.core.Context, url: str, **kwargs): + return api_req(ctx, 'POST', url, **kwargs) + + +def highlight_json(obj): + formatted_json = json.dumps(obj, sort_keys=True, indent=4) + return highlight(formatted_json, lexers.JsonLexer(), formatters.TerminalFormatter()) + + +def echo_obj(obj): + click.echo(highlight_json(obj)) + + +@click.group() +@click.option('--verbose', '-v', is_flag=True, help='Enables verbose output.') +@click.option('--endpoint', '-e', default=API_ENDPOINT_URL, show_default=True) +@click.pass_context +def cli(ctx: click.core.Context, verbose: bool, endpoint: str): + ctx.obj = { + 'verbose': verbose, + 'endpoint': endpoint, + 'url': lambda s: f'{endpoint}{s}' + } + + +@cli.command('version') +@click.pass_context +def get_version(ctx: click.core.Context): + '''Gets the service version''' + response = api_get(ctx, '/') + echo_obj(response.json()) + + +@cli.command('list') +@click.pass_context +def list_jobs(ctx: click.core.Context): + '''List conversion jobs''' + response = api_get(ctx, '/jobs') + echo_obj(response.json()) + + +@cli.command('remove-vocals') +@click.option('--file', type=click.File(mode='rb'), required=True) +@click.option('--output-path', '-o', type=click.Path(writable=True)) +@click.pass_context +def remove_vocals(ctx: click.core.Context, file: click.File, output_path: click.Path): + '''Removes the vocal part from an audio/video file''' + response = api_post(ctx, '/jobs') + job = response.json() + job_id = job['job_id'] + upload_data = job['upload_data'] + files = {'file': file} + + upload_started = time.time() + response = requests.post(upload_data['url'], + data=upload_data['fields'], + files=files) + + upload_time = time.time() - upload_started + print(f"File uploaded in {upload_time}s") + + response = api_post(ctx, f'/jobs/{job_id}/process') + print(f'Processing file with job id {job_id}') + + processing_started = time.time() + + status_changed = False + job = None + while not status_changed: + response = api_get(ctx, f'/jobs/{job_id}') + + job = response.json() + status_changed = job['status'] != 'processing' + time.sleep(5) + + processing_time = time.time() - processing_started + print(f"File processed in {processing_time}s") + + response = requests.get(job['output_url'], stream=True) + with open(output_path, 'wb') as f: + for chunk in response.raw: + f.write(chunk) + + print(f"Saved file to: {output_path}") + + +if __name__ == '__main__': + cli() diff --git a/client/requirements.txt b/client/requirements.txt new file mode 100644 index 0000000..2ce9c1a --- /dev/null +++ b/client/requirements.txt @@ -0,0 +1,3 @@ +click +pygments +requests diff --git a/worker/separator.py b/worker/separator.py index c2a565c..5eb1513 100644 --- a/worker/separator.py +++ b/worker/separator.py @@ -21,6 +21,7 @@ OUTPUT_FILENAME_FORMAT = os.getenv('OUTPUT_FILENAME_FORMAT', '{instrument}.{code QUEUE_NAME = os.getenv('QUEUE_NAME') POLLING_INTERVAL = int(os.getenv('POLLING_INTERVAL', 5)) OUTPUT_BUCKET_NAME = os.getenv('OUTPUT_BUCKET_NAME') +OUTPUT_BUCKET_REGION= os.getenv('OUTPUT_BUCKET_REGION', 'eu-west-1') TRACKS_TABLE_NAME = os.getenv('TRACKS_TABLE_NAME') USE_MULTICHANNEL_WIENER_FILTERING = False @@ -79,6 +80,7 @@ def poll_for_sqs_message(queue_name: str): body = json.loads(message['Body']) job_id = body['job_id'] output_s3_url = f"s3://{OUTPUT_BUCKET_NAME}/track_{job_id}.mp3" + download_url = f'https://{OUTPUT_BUCKET_NAME}.s3-{OUTPUT_BUCKET_REGION}.amazonaws.com/track_{job_id}.mp3' logging.info(f'Start separating {job_id} -> {output_s3_url}') try: @@ -94,7 +96,7 @@ def poll_for_sqs_message(queue_name: str): Key={'id': {'S': job_id}}, AttributeUpdates={ 'status': {'Value': {'S': 'successful'}}, - 'output_url': {'Value': {'S': output_s3_url}} + 'output_url': {'Value': {'S': download_url}} }) except: logging.exception('Processing failed for some reason')