Podcast Hosting Platform Python

👤 Sharing: AI
```python
import os
import hashlib
import datetime
from flask import Flask, render_template, request, redirect, url_for, send_from_directory
from werkzeug.utils import secure_filename

app = Flask(__name__)

# Configuration
UPLOAD_FOLDER = 'uploads'  # Directory to store podcast files
ALLOWED_EXTENSIONS = {'mp3', 'wav', 'ogg'}  # Allowed file types
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # Max file size: 16MB

# Ensure the upload directory exists
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

# Simple in-memory podcast database (replace with a proper database in a real application)
podcasts = []

def allowed_file(filename):
    """Check if the filename has an allowed extension."""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def generate_unique_filename(filename):
    """Generate a unique filename using timestamp and hashing."""
    timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    file_extension = filename.rsplit('.', 1)[1].lower()
    filename_without_extension = filename.rsplit('.', 1)[0]  # Extract name
    unique_hash = hashlib.md5((filename_without_extension + timestamp).encode()).hexdigest()
    return f"{unique_hash}_{timestamp}.{file_extension}" # Append original extension

@app.route('/')
def index():
    """Display the list of podcasts."""
    return render_template('index.html', podcasts=podcasts)  # Changed 'podcast' to 'podcasts'


@app.route('/upload', methods=['GET', 'POST'])
def upload_podcast():
    """Handle podcast file uploads."""
    if request.method == 'POST':
        # check if the post request has the file part
        if 'podcast_file' not in request.files:
            return 'No file part'
        file = request.files['podcast_file']
        # If the user does not select a file, the browser submits an
        # empty file without a filename.
        if file.filename == '':
            return 'No selected file'
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            unique_filename = generate_unique_filename(filename)
            file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
            file.save(file_path)

            # Store podcast information (in memory for now)
            title = request.form.get('title') or filename  # Use form title or filename
            description = request.form.get('description') or "No description provided."

            podcast_data = {
                'id': len(podcasts) + 1,  # Simple ID generation
                'title': title,
                'description': description,
                'filename': unique_filename,
                'file_path': file_path, # Store the path
            }
            podcasts.append(podcast_data)

            return redirect(url_for('index'))  # Redirect to the main page after upload
    return render_template('upload.html')  # Show the upload form


@app.route('/uploads/<filename>')
def download_file(filename):
    """Serve the uploaded podcast file."""
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

@app.route('/podcast/<int:podcast_id>')
def podcast_detail(podcast_id):
    """Display details of a specific podcast and provide a link to download."""
    podcast = next((p for p in podcasts if p['id'] == podcast_id), None)  # Find podcast by ID
    if podcast:
        return render_template('podcast_detail.html', podcast=podcast)
    else:
        return "Podcast not found", 404


if __name__ == '__main__':
    app.run(debug=True)


```

**Explanation:**

1.  **Imports:**
    *   `os`: Used for file system operations (creating directories, joining paths).
    *   `hashlib`: Used to generate unique filenames.
    *   `datetime`: Used to generate timestamps for unique filenames.
    *   `flask`: The web framework. Includes `Flask` class, `render_template` for rendering HTML, `request` for handling incoming requests, `redirect` for redirecting to other URLs, `url_for` for generating URLs based on route names, and `send_from_directory` for serving static files.
    *   `werkzeug.utils.secure_filename`:  A function to sanitize filenames to prevent security vulnerabilities.

2.  **Configuration:**
    *   `UPLOAD_FOLDER`:  The directory where uploaded podcast files will be stored (set to `'uploads'`).
    *   `ALLOWED_EXTENSIONS`: A set of allowed file extensions (mp3, wav, ogg).
    *   `app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER`: Configures the Flask app with the upload folder.
    *   `app.config['MAX_CONTENT_LENGTH']`: Sets a limit on the maximum file size that can be uploaded (16MB).
    *   `os.makedirs(UPLOAD_FOLDER, exist_ok=True)`: Creates the `UPLOAD_FOLDER` directory if it doesn't exist already. `exist_ok=True` prevents an error if the directory already exists.

3.  **Helper Functions:**
    *   `allowed_file(filename)`: Checks if the uploaded file's extension is in the `ALLOWED_EXTENSIONS` set.  Returns `True` if it is, `False` otherwise. This is a basic security check.
    *   `generate_unique_filename(filename)`:  Generates a unique filename to avoid collisions and potential security issues.  It combines a hash of the original filename (without the extension) and the timestamp. The original file extension is preserved.

4.  **Routes:**
    *   `/` (index):
        *   `@app.route('/')`:  Defines the route for the homepage.
        *   `index()`:  Renders the `index.html` template, passing the `podcasts` list (our podcast "database") to the template.
    *   `/upload`:
        *   `@app.route('/upload', methods=['GET', 'POST'])`:  Defines the route for the podcast upload page, handling both GET (displaying the form) and POST (handling the upload) requests.
        *   `upload_podcast()`:
            *   `if request.method == 'POST':`:  Checks if the request method is POST (meaning a file is being submitted).
            *   `if 'podcast_file' not in request.files:`: Checks if a file was actually uploaded with the name 'podcast\_file'.
            *   `file = request.files['podcast_file']`: Gets the uploaded file from the request.
            *   `if file.filename == '':`:  Checks if the user submitted an empty file (no filename).
            *   `if file and allowed_file(file.filename):`:  Calls `allowed_file` to check the file extension.
            *   `filename = secure_filename(file.filename)`: Sanitizes the filename using `secure_filename` to prevent path traversal vulnerabilities.
            *   `unique_filename = generate_unique_filename(filename)`: Generates a unique filename to avoid overwriting existing files.
            *   `file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)`: Constructs the full path to save the file.
            *   `file.save(file_path)`: Saves the file to the `UPLOAD_FOLDER`.
            *   `title = request.form.get('title') or filename`: Retrieves the title of the podcast from the form. If no title is provided in the form, it defaults to the original filename.
            *   `description = request.form.get('description') or "No description provided."`: Retrieves the podcast description from the form or sets a default description.
            *   Creates a `podcast_data` dictionary containing podcast information like ID, title, description, filename and file path.
            *   `podcasts.append(podcast_data)`:  Adds the podcast information to the `podcasts` list (our in-memory database).
            *   `return redirect(url_for('index'))`: Redirects the user back to the homepage after a successful upload.
            *   `return render_template('upload.html')`: If it's a GET request, renders the `upload.html` template to display the upload form.
    *   `/uploads/<filename>`:
        *   `@app.route('/uploads/<filename>')`:  Defines a route that takes a filename as a parameter.
        *   `download_file(filename)`:  Serves the file with the given filename from the `UPLOAD_FOLDER` using `send_from_directory`.  This allows users to download the podcast file.
    *   `/podcast/<int:podcast_id>`:
        * `@app.route('/podcast/<int:podcast_id>')`: Defines a route that takes an integer podcast ID as a parameter.
        * `podcast_detail(podcast_id)`:  Fetches the podcast data using `podcast_id`, renders the `podcast_detail.html` template to display the details of the podcast.

5.  **Main Execution:**
    *   `if __name__ == '__main__':`: This is a standard Python idiom.  The code inside this block will only execute when the script is run directly (not when it's imported as a module).
    *   `app.run(debug=True)`: Starts the Flask development server in debug mode. Debug mode provides helpful error messages and automatically reloads the server when you make changes to the code.  **Important:**  Do not use `debug=True` in a production environment.

**HTML Templates (example):**

Create the following files in a `templates` directory within the same directory as your Python script:

*   **templates/index.html:**

```html
<!DOCTYPE html>
<html>
<head>
    <title>Podcast Hosting</title>
</head>
<body>
    <h1>Podcast List</h1>
    <ul>
        {% for podcast in podcasts %}
            <li><a href="{{ url_for('podcast_detail', podcast_id=podcast.id) }}">{{ podcast.title }}</a></li>
        {% endfor %}
    </ul>
    <p><a href="{{ url_for('upload_podcast') }}">Upload New Podcast</a></p>
</body>
</html>
```

*   **templates/upload.html:**

```html
<!DOCTYPE html>
<html>
<head>
    <title>Upload Podcast</title>
</head>
<body>
    <h1>Upload a New Podcast</h1>
    <form method="post" enctype="multipart/form-data">
        <label for="title">Title:</label><br>
        <input type="text" id="title" name="title"><br><br>

        <label for="description">Description:</label><br>
        <textarea id="description" name="description" rows="4" cols="50"></textarea><br><br>

        <input type="file" name="podcast_file"><br><br>
        <input type="submit" value="Upload">
    </form>
    <p><a href="{{ url_for('index') }}">Back to Podcast List</a></p>
</body>
</html>
```

*   **templates/podcast_detail.html:**

```html
<!DOCTYPE html>
<html>
<head>
    <title>{{ podcast.title }} - Podcast Details</title>
</head>
<body>
    <h1>{{ podcast.title }}</h1>
    <p>{{ podcast.description }}</p>
    <p>
        <a href="{{ url_for('download_file', filename=podcast.filename) }}">Download Podcast</a>
    </p>
    <p><a href="{{ url_for('index') }}">Back to Podcast List</a></p>
</body>
</html>
```

**How to Run:**

1.  **Install Flask:**  `pip install flask`
2.  **Create a `templates` directory:** Create a folder named `templates` in the same directory as your Python script.
3.  **Create the HTML files:**  Create the `index.html`, `upload.html`, and `podcast_detail.html` files inside the `templates` directory, copying the content from above.
4.  **Run the script:** `python your_script_name.py`
5.  **Open in your browser:** Go to `http://127.0.0.1:5000/` in your web browser.

**Key Improvements and Explanations:**

*   **File Upload Handling:**  Uses `werkzeug.utils.secure_filename` to sanitize filenames, preventing security vulnerabilities like path traversal.  This is crucial for any file upload application.
*   **Unique Filenames:** Implements a `generate_unique_filename` function using `hashlib` and `datetime` to create unique filenames. This avoids filename collisions (overwriting files) and makes it more difficult for attackers to guess file names.  The unique ID and timestamp make the names virtually guaranteed to be unique.
*   **File Extension Validation:**  The `allowed_file` function checks the file extension against a whitelist of allowed extensions.  This prevents users from uploading arbitrary files (e.g., executable code) to your server.
*   **Error Handling:** The code includes basic error handling (e.g., checking for a file being submitted).
*   **In-Memory "Database":** Uses a simple list `podcasts` to store podcast data.  **Important:** In a real application, you would replace this with a proper database (e.g., SQLite, PostgreSQL, MySQL).
*   **Flask Framework:** Uses Flask for routing, handling requests, and rendering templates.
*   **Templates:** Uses Jinja2 templates (Flask's default templating engine) to separate the presentation logic from the Python code.  This makes the code more maintainable.
*   **URL Generation:** Uses `url_for` to generate URLs based on the route names.  This makes the code more robust (if you change a route, you don't have to update all the links manually).
*   **Download Route:**  The `/uploads/<filename>` route serves the uploaded files, allowing users to download them.
*   **Podcast Detail Page:** Added a detailed view for each podcast with its title, description, and a download link.
*   **File Path Storage:** Now stores the file path in the podcast data, which could be useful for future features or refactoring.
*   **More Robust `podcast_detail`:** The `podcast_detail` view now handles the case where a podcast ID does not exist, returning a 404 error.

**Security Considerations (Important):**

*   **Input Validation:** Always validate and sanitize user input.
*   **Authentication/Authorization:** For a real application, implement user authentication and authorization to control who can upload and manage podcasts.
*   **Database Security:**  If you use a database, follow security best practices for your chosen database system (e.g., parameterized queries to prevent SQL injection).
*   **Storage Security:** Secure the `UPLOAD_FOLDER` directory to prevent unauthorized access.
*   **Content Security Policy (CSP):**  Consider using CSP headers to mitigate cross-site scripting (XSS) attacks.
*   **Regular Security Audits:** Regularly review your code for security vulnerabilities.

This example provides a solid foundation for building a podcast hosting platform with Python and Flask.  Remember to address the security considerations before deploying your application to a production environment.  Using a real database and implementing user authentication are the next logical steps.
👁️ Viewed: 5

Comments