diff --git a/.gitignore b/.gitignore index e61afe7..0eb3b71 100755 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,17 @@ .venv/ /venv3-8 -app/__pycache__/* \ No newline at end of file +app/__pycache__/* + +# Arquivos de cache do Python +__pycache__/ +*.py[cod] +*$py.class + +# Cache de build / distribuição +build/ +dist/ +*.egg-info/ + +# Arquivos temporários de testes +.pytest_cache/ diff --git a/README.md b/README.md index 3dc1d2a..c8243b3 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,27 @@ The Split project is coupled to a plugin I created with the name Boratio, in C++ this project is a neural network that removes or separates the voice between the instruments + +## Prerequisite: FFmpeg + +Spleeter uses FFmpeg to decode audio (mp3, wav, etc). Make sure `ffmpeg` is installed and available on your PATH before using `/upload`. + +- Ubuntu/Debian (WSL/Linux): + ``` + sudo apt-get update && sudo apt-get install -y ffmpeg + ``` +- macOS (Homebrew): + ``` + brew install ffmpeg + ``` +- Windows: + - Using winget: `winget install Gyan.FFmpeg` + - Or Chocolatey: `choco install ffmpeg` + - Ensure the `bin` folder (containing `ffmpeg.exe`) is added to your PATH. + +Verify installation: +``` +ffmpeg -version +``` + +Restart the Flask server after installation. diff --git a/app.py b/app.py index 0dd9e39..ab6fbe3 100755 --- a/app.py +++ b/app.py @@ -1,10 +1,14 @@ -from flask import Flask, send_from_directory, make_response -from flask_restful import Resource, Api, reqparse +from flask import Flask, send_from_directory, make_response, request +from flask_restful import Resource, Api from werkzeug.datastructures import FileStorage +from werkzeug.exceptions import BadRequest, RequestEntityTooLarge, ClientDisconnected +from werkzeug.utils import secure_filename from utils import split, ziped import os, json app = Flask(__name__) +# Limit upload size to avoid hanging/parsing huge bodies (can be overridden via env) +app.config['MAX_CONTENT_LENGTH'] = int(os.environ.get('MAX_CONTENT_LENGTH', 64 * 1024 * 1024)) # 64 MB api = Api(app) @api.representation('application/json') @@ -42,36 +46,125 @@ def get(self): class UploadAPI(Resource): def post(self): try: - os.system("rm -rf files/separate/audio") - parse = reqparse.RequestParser() - parse.add_argument('audio', type=FileStorage, location='files') - - args = parse.parse_args() - stream = args['audio'] - - stream.save("files/audio.wav") - - split.separa(2,"files/audio.wav") - #split.separa(4,wav_file) - os.system("rm -rf files/audio.wav") + os.makedirs("files", exist_ok=True) + # Clean previous outputs safely + try: + import shutil + shutil.rmtree("files/separate/audio", ignore_errors=True) + except Exception: + pass + + # Quick header checks before parsing body + te = request.headers.get('Transfer-Encoding', '').lower() + if te == 'chunked': + data = { + "response": "Chunked uploads not supported by this server", + "status": False, + } + return output_json(data, 400) + + if request.mimetype != 'multipart/form-data': + data = { + "response": "Content-Type must be multipart/form-data with field 'audio'", + "status": False, + } + return output_json(data, 400) + + # Accessing request.files may raise parsing errors; handle them explicitly + try: + files = request.files + except RequestEntityTooLarge as e: + data = { + "response": "Uploaded file too large", + "status": False, + "error": str(e), + } + return output_json(data, 413) + except (ClientDisconnected, BadRequest) as e: + data = { + "response": "Failed to read upload (client disconnected or bad request)", + "status": False, + "error": str(e), + } + return output_json(data, 400) + + if 'audio' not in files: + data = { + "response": "Missing file field 'audio' in multipart/form-data", + "status": False, + } + return output_json(data, 400) + + stream: FileStorage = files['audio'] + if not stream or stream.filename == "": + data = { + "response": "Empty file for field 'audio'", + "status": False, + } + return output_json(data, 400) + + # Ensure ffmpeg is available before trying to process + import shutil as _shutil + if _shutil.which('ffmpeg') is None: + data = { + "response": "ffmpeg binary not found. Please install ffmpeg and ensure it is on PATH.", + "status": False, + "error": "MissingDependency: ffmpeg" + } + return output_json(data, 400) + + # Save using the original extension for correct decoding + os.makedirs("files", exist_ok=True) + original_name = secure_filename(stream.filename) + _, ext = os.path.splitext(original_name) + if not ext: + ext = ".wav" + target_path = os.path.join("files", f"audio{ext}") + stream.save(target_path) + + split.separa(2, target_path) + # Clean input file after processing + try: + os.remove(target_path) + except FileNotFoundError: + pass + ziped.zipFilesInDir('files/separate/', 'files/separate/audio/extractFiles.zip', lambda name : 'wav' in name) data = { - "response": "Source separation fineshed", + "response": "Source separation finished", "status": True, } return output_json(data, 200) - + except Exception as err: + app.logger.exception("Upload processing failed") data = { - "response": "Source separation is not fineshed", + "response": "Source separation did not finish", "status": False, "error": repr(err) } - return output_json(data, 400) + return output_json(data, 400) api.add_resource(UploadAPI, '/upload') +# Simple HTML form for manual testing via browser +@app.route('/upload', methods=['GET']) +def upload_form(): + return ( + """ + +