Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,32 @@ verify_ssl = true
name = "pypi"

[packages]
ffmpeg-python = "~=0.2.0"
loguru = "~=0.7.2"
werkzeug = "~=2.3.7"
python-dotenv = "~=1.0.0"
flask = "*"
pydub = "*"
werkzeug = "*"
gunicorn = "*"

[dev-packages]
pytest = "~=7.4.0"
pytest-cov = "~=4.1.0"
black = "~=23.7.0"
flake8 = "~=6.1.0"
mypy = "~=1.5.1"
isort = "~=5.12.0"

[requires]
python_version = "3.12"
python_version = "3.9"

[scripts]
start = "gunicorn --config gunicorn.conf.py wsgi:application"
dev = "flask run --debug"
test = "pytest"
lint = "flake8 ."
format = "black ."
check = "mypy ."
sort = "isort ."

[pipenv]
allow_prereleases = false
154 changes: 0 additions & 154 deletions Pipfile.lock

This file was deleted.

8 changes: 7 additions & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
web: flask run --host=0.0.0.0 --port=8000
web: gunicorn --config gunicorn.conf.py app:app

# Uncomment this `release` process if you are using a database, so that Django's model
# migrations are run as part of app deployment, using Heroku's Release Phase feature:
# https://docs.djangoproject.com/en/5.1/topics/migrations/
# https://devcenter.heroku.com/articles/release-phase
#release: ./manage.py migrate --no-input
7 changes: 3 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

This project is a web application that allows users to upload audio or video files and reverses the audio track for playback. It supports a wide range of file formats that FFmpeg can handle, includes secure file handling, and can be deployed using Flask's built-in server.

[chatgpt](https://chatgpt.com/share/6751dd60-5754-8013-a93b-8d2cf8f5f8ca)

## Features

- **Multiple File Formats**: Supports any audio or video file format that FFmpeg can process.
Expand All @@ -18,6 +16,7 @@ This project is a web application that allows users to upload audio or video fil
- **Web Server**: Flask's built-in development server
- **Storage**: Local file storage for uploads and reversed files
- **Session Management**: Flask's built-in session management
- **Frontend**: HTML, CSS, JavaScript with IndexedDB for local storage

## Prerequisites

Expand Down Expand Up @@ -103,7 +102,7 @@ This project is a web application that allows users to upload audio or video fil
- If `requirements.txt` is not available, manually install:

```bash
pip install flask pydub
pip install flask gunicorn ffmpeg-python werkzeug
```

### 4. Configure Environment Variables (if needed)
Expand Down Expand Up @@ -173,4 +172,4 @@ Logs are stored in `logs/app.log`. For advanced monitoring, consider integrating

## License

This project is licensed under the MIT License.
This project is licensed under the MIT License.
133 changes: 81 additions & 52 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,72 +1,101 @@
import os
from flask import Flask, request, send_file, render_template
from pydub import AudioSegment
from flask import Flask, request, jsonify, send_file, render_template, redirect, url_for, session
import io
import tempfile
import logging
import os
from werkzeug.utils import secure_filename
import uuid
import ffmpeg
from loguru import logger

app = Flask(__name__)
app.logger.setLevel(logging.DEBUG) # Enable debug logging
app.secret_key = 'your_secret_key' # Replace with a secure, random key
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['REVERSED_FOLDER'] = 'static/reversed' # Static folder to serve files
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024 # 500 MB upload limit

# Ensure the directories exist
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['REVERSED_FOLDER'], exist_ok=True)

def generate_unique_filename(filename):
"""Generate a unique filename to prevent collisions on the server."""
unique_id = str(uuid.uuid4())
name, ext = os.path.splitext(filename)
return f"{unique_id}_{secure_filename(name)}{ext}"

@app.route('/')
def index():
def upload_file():
return render_template('index.html')

@app.route('/reverse', methods=['POST'])
def reverse_audio():
@app.route('/upload', methods=['POST'])
def process_file():
if 'file' not in request.files:
return "No file provided", 400

logger.error("No file part in the request")
return jsonify({'error': 'No file part'}), 400

file = request.files['file']
if file.filename == '':
return "No file selected", 400
logger.error("No file selected for uploading")
return jsonify({'error': 'No file selected'}), 400

try:
# Create a temporary directory that won't be deleted immediately
temp_dir = tempfile.mkdtemp()
input_path = os.path.join(temp_dir, 'input' + os.path.splitext(file.filename)[1])
output_path = os.path.join(temp_dir, 'output.mp3')

# Save uploaded file
# Generate unique filename and save uploaded file
input_filename = generate_unique_filename(file.filename)
input_path = os.path.join(app.config['UPLOAD_FOLDER'], input_filename)
file.save(input_path)
app.logger.debug(f"Saved input file to {input_path}")

# Generate output filename
output_filename = f'reversed_{secure_filename(file.filename)}.mp3'
output_path = os.path.join(app.config['REVERSED_FOLDER'], output_filename)

# Use ffmpeg to reverse the audio
ffmpeg.input(input_path).output(output_path, af='areverse').run()

# Clean up the input file
os.remove(input_path)

# Return the reversed audio file directly
return send_file(
output_path,
mimetype='audio/mpeg',
as_attachment=True,
download_name=output_filename
)

except Exception as e:
logger.error(f"Error processing file: {str(e)}")
return jsonify({'error': str(e)}), 500

@app.route('/clear')
def clear_reversed_files():
# Delete user-associated files
user_files = session.get('user_files', [])
for file_path in user_files:
try:
# Process the audio
input_audio = AudioSegment.from_file(input_path)
app.logger.debug("Successfully loaded audio file")

reversed_audio = input_audio.reverse()
app.logger.debug("Successfully reversed audio")

# Export to mp3
reversed_audio.export(output_path, format="mp3")
app.logger.debug(f"Exported reversed audio to {output_path}")

# Read the output file and send it
with open(output_path, 'rb') as f:
output_data = io.BytesIO(f.read())
output_data.seek(0)

# Clean up temporary files
os.remove(input_path)
os.remove(output_path)
os.rmdir(temp_dir)

return send_file(
output_data,
mimetype='audio/mpeg',
as_attachment=True,
download_name='reversed.mp3'
)
if os.path.exists(file_path):
os.remove(file_path)
except Exception as e:
logger.error(f"Error deleting file {file_path}: {e}")

# Clear session data
session.pop('reversed_files', None)
session.pop('user_files', None)

return redirect(url_for('upload_file'))

@app.route('/logout')
def logout():
# Delete user-associated files
user_files = session.get('user_files', [])
for file_path in user_files:
try:
if os.path.exists(file_path):
os.remove(file_path)
except Exception as e:
app.logger.error(f"Error processing audio: {str(e)}")
raise
logger.error(f"Error deleting file {file_path}: {e}")

except Exception as e:
app.logger.error(f"Server error: {str(e)}")
return f"An error occurred: {str(e)}", 500
# Clear session data
session.clear()
return redirect(url_for('upload_file'))

if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8000)
app.run(debug=False, host='0.0.0.0', port=8000)
Binary file added favicon.ico
Binary file not shown.
Loading