Link to home
Create AccountLog in
Avatar of Stevie Zakhour
Stevie Zakhour

asked on

Using Flask to Show Photos and Videos Based on Filter

Hi All


I am building a Media Lookup tool using Flask. The frontend consists index.html and results.html, the code are below. The purpose of Media Lookup is it allows the user to enter the order ID and helmet number, push the Filter button and then have the ability to select and download the display .jpg and .mp4 files.

index.html

<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <title>Image Filter</title>
</head>


<body>


    <div class="container mt-4">
        <h2>Media Lookup</h2>
        <form action="{{ url_for('filter_images') }}" method="post">
            <div class="form-group">
                <label for="orderID">Order ID</label>
                <input type="text" name="orderID" class="form-control" value="{{ orderID|default('', true) }}" required>
            </div>
            <div class="form-group">
                <label for="baa_m1">Helmet Number</label>
                <input type="text" name="baa_m1" class="form-control" value="{{ baa_m1|default('', true) }}" required>
            </div>
            <button type="submit" class="btn btn-primary">Filter</button>
        </form>
    </div>


</body>


</html>

Open in new window


results.html

<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <title>Filtered Images</title>
    <style>
        .loading-modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.7);
            z-index: 9999;
        }


        .loading-content {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            text-align: center;
        }


        .loading-content img {
            width: 700px; /* Adjust as needed */
            height: 50px; /* Adjust as needed */
        }


        /* Click */


        .image-checkbox-container {
            position: relative;
            cursor: pointer;
        }


        .hidden-checkbox {
            display: none;
        }


        .overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 100, 0, 0.3);  /* greenish overlay, adjust if needed */
            display: none;
            align-items: center;
            justify-content: center;
            font-size: 30px;
            color: white;
        }


        .image-checkbox-container input:checked + .overlay {
            display: flex;
        }


    </style>
</head>


<body>


<div class="container mt-4">
    <form action="{{ url_for('filter_images') }}" method="post">
        <div class="form-group">
            <label for="orderID">Order ID:</label>
            <input type="text" name="orderID" class="form-control" required>
        </div>
        <div class="form-group">
            <label for="baa_m1">Helmet Number:</label>
            <input type="text" name="baa_m1" class="form-control" required>
        </div>
        <button type="submit" class="btn btn-primary mb-1">Filter</button>
    </form>


    <form id="downloadForm" action="/download_images" method="post">
        <button type="button" class="btn btn-info mb-1" onclick="selectAll()">Select All</button>
        <button type="button" class="btn btn-info mb-1" onclick="deselectAll()">Deselect All</button>
        <button type="submit" class="btn btn-success mb-1" onclick="showLoadingModal()">Download Selected</button>


        <p class="lead">Photo Count: {{ photo_files|length }}</p>
        <p id="selectedPhotoCount">0 Photos Selected</p>
        <div class="row">
      {% for actual_path, download_path in photo_files %}
         <div class="col-md-4">
            <img src="{{ actual_path }}" alt="{{ download_path }}" width="200" class="img-thumbnail">
            <input type="checkbox" name="selected_photos" value="{{ download_path }}" class="hidden-checkbox">
         </div>
      {% endfor %}


        </div>


        <p class="lead">Video Count: {{ video_files|length }}</p>
        <p id="selectedVideoCount">0 Videos Selected</p>
        <div class="row">
            {% for video in video_files %}
            <div class="col-md-3 mb-4 image-checkbox-container">
                <video width="200" height="150" controls>
                    <source src="{{ url_for('send_video', filename=video) }}" type="video/mp4">
                    Your browser does not support the video tag.
                </video>
                <input type="checkbox" name="selected_videos" value="{{ video }}" class="hidden-checkbox">
                <div class="overlay"><i class="fa fa-check"></i></div>
            </div>
            {% endfor %}
        </div>
    </form>
</div>


<div id="loadingModal" class="loading-modal">
    <div class="loading-content">
        <img src="{{ url_for('static', filename='loading.gif') }}" alt="Processing...">
    </div>
</div>


<script>
    function selectAll() {
        document.querySelectorAll('input[name="selected_photos"], input[name="selected_videos"]').forEach(el => el.checked = true);
        updateSelectedCount();
    }


    function deselectAll() {
        document.querySelectorAll('input[name="selected_photos"], input[name="selected_videos"]').forEach(el => el.checked = false);
        updateSelectedCount();
    }


    function showLoadingModal() {
        document.getElementById('loadingModal').style.display = 'block';
    }


    function hideLoadingModal() {
        document.getElementById('loadingModal').style.display = 'none';
    }


    window.onload = hideLoadingModal;


    document.getElementById('downloadForm').addEventListener('submit', function(event) {
        event.preventDefault();
        showLoadingModal();


        fetch("/download_images", {
            method: 'POST',
            body: new FormData(this)
        })
        .then(response => response.blob())
        .then(blob => {
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = "{{ selected_date }}-{{ time_block }}.zip";
            a.click();
            hideLoadingModal();
        })
        .catch(error => {
            console.error('Error:', error);
            hideLoadingModal();
        });
    });


    function updateSelectedCount() {
        var checkedPhotoBoxes = document.querySelectorAll('input[name="selected_photos"]:checked').length;
        var checkedVideoBoxes = document.querySelectorAll('input[name="selected_videos"]:checked').length;


        document.getElementById('selectedPhotoCount').textContent = checkedPhotoBoxes + " Photos Selected";
        document.getElementById('selectedVideoCount').textContent = checkedVideoBoxes + " Videos Selected";
    }


    document.querySelectorAll('.image-checkbox-container').forEach(function(container) {
        container.addEventListener('click', function() {
            var checkbox = container.querySelector('input[type="checkbox"]');
            checkbox.checked = !checkbox.checked;
            updateSelectedCount();
        });
    });
</script>


</body>
</html>

Open in new window

Now, the backend runs in a file called app.py, the code is below.


app.py

from flask import Flask, render_template, request, send_from_directory, Response
from flaskext.mysql import MySQL
import os
import zipfile
import io


app = Flask(__name__)


# Define the base network share path
NETWORK_SHARE_PATH = '\\\\10.10.1.26\media'


# MySQL configurations
app.config['MYSQL_DATABASE_USER'] = 'xx'
app.config['MYSQL_DATABASE_PASSWORD'] = 'xx'
app.config['MYSQL_DATABASE_DB'] = 'xx'
app.config['MYSQL_DATABASE_HOST'] = 'xx'
mysql = MySQL()
mysql.init_app(app)


@app.route('/send_image/photo/<path:filename>')
def send_photo(filename):
    filename = filename.replace('/', '\\')
    return send_from_directory(os.path.join(NETWORK_SHARE_PATH, 'Photos'), filename)


@app.route('/send_image/video/<path:filename>')
def send_video(filename):
    filename = filename.replace('/', '\\')
    return send_from_directory(os.path.join(NETWORK_SHARE_PATH, 'Videos'), filename)


@app.route('/display_images')
def display_images():
    images_from_db = fetch_images_from_database()
    
    web_style_images = [extract_relative_path(image_path) for image_path in images_from_db]


    return render_template('results.html', images=web_style_images)


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


@app.route('/filter_images', methods=['POST'])
def filter_images():
    orderID_var = request.form.get('orderID')
    baa_m1_var = request.form.get('baa_m1')


    conn = mysql.connect()
    cursor = conn.cursor()


    query = """
    SELECT DISTINCT 
            activity_account_id AS baaId,
            sessionId AS sessionId,
            sessiondate AS sessionDate,
            CASE 
            WHEN fms_cap_sessions.hasphotos = 1 THEN CONCAT('\\\\10.10.1.26\\media\\Photos\\000', sessionId, '\\00', activity_account_id)
            ELSE CONCAT('\\\\10.10.1.26\\media\\Photos\\000', sessionId, '\\00', activity_account_id)
        END AS Photos,


        CASE 
            WHEN fms_cap_sessions.hasvideos = 1 THEN CONCAT('\\\\10.10.1.26\\media\\Videos\\000', sessionId, '\\00', activity_account_id) 
            ELSE CONCAT('\\\\10.10.1.26\\media\\Videos\\000', sessionId, '\\00', activity_account_id)
        END AS Videos,
            CASE 
                WHEN fms_cap_sessions.locationid = 1 THEN '1' 
                ELSE '2' 
            END AS Location,
            CONCAT(u.firstname,' ',u.lastname) AS name,
            baa.m1 AS helmetNum,
            u.id
        FROM fms_cap_sessions_ts 
        JOIN fms_cap_sessions ON fms_cap_sessions_ts.sessionId = fms_cap_sessions.id 
        JOIN booking_activity_account baa ON baa.id = activity_account_id
        JOIN users u ON u.id = baa.userID
        WHERE activity_account_id IN (SELECT id FROM booking_activity_account WHERE orderID = %s) 
        AND baa.m1 = %s
    """


    cursor.execute(query, (orderID_var, baa_m1_var))
    data = cursor.fetchall()
    cursor.close()
    conn.close()


    photos_dirs = [row[3] for row in data]
    videos_dirs = [row[4] for row in data]


    photo_files = []
    for photo_dir in photos_dirs:
        if '\0' in photo_dir:
            print(f"Skipping problematic path: {photo_dir}")
            continue
        # Construct the correct network share path
        network_photo_dir = photo_dir.replace(NETWORK_SHARE_PATH, NETWORK_SHARE_PATH)
        photo_files.extend([(os.path.join(extract_relative_path(network_photo_dir), f), os.path.join(network_photo_dir, f)) for f in os.listdir(network_photo_dir) if f.endswith('.jpg')]) 


    video_files = []
    for video_dir in videos_dirs:
        if '\0' in video_dir:
            print(f"Skipping problematic path: {video_dir}")
            continue
        # Construct the correct network share path
        network_video_dir = video_dir.replace(NETWORK_SHARE_PATH, NETWORK_SHARE_PATH)
        video_files.extend([(os.path.join(extract_relative_path(network_video_dir), f), os.path.join(network_video_dir, f)) for f in os.listdir(network_video_dir) if f.endswith('.mp4')])


    return render_template("results.html", photo_files=photo_files, video_files=video_files)


# ... (existing code for download_images)


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000, debug=True)

Open in new window

Just a few notes
1. There is a shared drive, it's network address is \\10.10.1.26\media\

2. There are 2 folders in media, these are Photos and Videos

3. For testing purposes, the shared drive permissions is using Everyone with full read\write access.

4. Inside the Photos folder, there are folders that start session folders, these consists of folders with a numbering system, i.e., 00050955. Within each of these session folders are activity folders, these consists of folders with a similar number system as well, i.e., 00458486. Within the activity folder are  .jpg files - this is the same for the Videos folders except there are .mp4 files within the activity folders.

5. I know the host is set to 0.0.0.0. The intention is to run this locally.

6. The session and activity folders with its numbering system is unique to each order number. In this particular case, if we look at order number 262745, it will show a session folder of 00050955 and a activity folder 00458486.


I have the index.html and results.html sitting in a folder called templates. The templates folder sits in a folder called apps. In this apps folder is the app.py file. When I run app.py, I use cmd by running python app.py. I get an output like the snip below.
User generated image


When I go to 10.10.1.26:5000, I see the following page
User generated image


When I enter a order number and helmet number, I don't see any of the .jpg or .mp4 files, see below.

User generated image
The log shows the following

User generated image

When I use the mysql query to look up data, I see the order id and helmet number correspond to a session and activity id folders, see below.

User generated image


When I go to \\10.10.1.26\media and into the Photo and Video folders, I see there are .jpg and .mp4 files, see below.


Photos
User generated image


Videos

User generated image


Any help is greatly appreciated!

ASKER CERTIFIED SOLUTION
Avatar of Stevie Zakhour
Stevie Zakhour

Link to home
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
See answer
Avatar of Paul Sauvé

why is this question still open 4 hours later?