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>
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>
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)
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.
When I go to 10.10.1.26:5000, I see the following page
When I enter a order number and helmet number, I don't see any of the .jpg or .mp4 files, see below.
The log shows the following
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.
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.
Videos
Any help is greatly appreciated!
why is this question still open 4 hours later?