Mohammed Shabaj Ahmed
Over the years, I've taken thousands of photos capturing special occasions, travels, and everyday moments. However, storing and retrieving these memories has become increasingly difficult. When I want to find a photo of a specific person, event, or location, I have no reliable way to search by these criteria. As a result, many meaningful moments are becoming harder to revisit simply because I can't locate the images when I want them.
This project set out to solve that problem by creating a lightweight desktop application that allows all my photos to be stored in a single, unstructured folder, while enabling meaningful organisation and retrieval through tagging.
The application allows me to tag each image with custom metadata such as the people present, the event, the location, and even the emotion it captures. With this functionality, I can easily filter images to find exactly what I'm looking for — whether reminiscing about a trip or collecting all the photos of a specific family member.
The project prioritises simplicity, privacy, and offline use. It is not designed for cloud syncing or large-scale deployment, but rather as a personal tool for organising and enjoying memories.
Explore the Full Project
View the complete project on GitHubThis section outlines the design requirements for the photo album application, focusing on what users need from the system and how the system should behave. It is divided into three parts: user requirements that describe the goals from the user's perspective, functional requirements that specify what the system shall do, and non-functional requirements that define the quality attributes of the system.
Initially, I planned to use ExifTool to edit metadata embedded directly within image files. While ExifTool works well for JPEGs and some other formats, it does not provide consistent support across all image types. Given that the photo album project needs to handle a variety of image formats, this approach proved limiting.
To overcome this, I chose a non-destructive, format-independent method: storing custom, searchable metadata in a separate database. This approach offers several advantages:
This decision supports scalability, maintainability, and user convenience.
Although performance was not a primary design goal, practical steps were taken to ensure the application remains responsive when working with large collections of photos. Images are loaded at reduced resolution to conserve memory and speed up rendering. Additionally, pagination was implemented to avoid loading all images at once, which could otherwise cause the application to become sluggish or unresponsive when browsing folders containing hundreds or thousands of files. These optimisations allow the application to remain lightweight while handling real-world datasets efficiently.
These performance decisions were made to ensure the application remains responsive without introducing architectural complexity. They align with the broader design trade-offs: prioritising simplicity, clarity, and ease of use over scalability or cloud-based features.
This project adopts the Model-View-Controller (MVC) architecture to promote modularity, separation of concerns, and long-term maintainability. The decision to use MVC was driven by the need to clearly separate the user interface logic, business logic, and data access, making the codebase easier to understand and extend.
The MVC pattern divides the application into three interconnected components:
The codebase is structured around these components to support the MVC architecture:
database_manager.py
: Manages all database operations, including CRUD actions and filtered image queries.main_window.py
: Implements the overall UI layout and main interface logic using PyQt6.widgets/
: Contains reusable UI components, such as editable dropdowns and custom toast notifications.main_controller.py
: Orchestrates the application logic, including loading image folders, updating metadata, applying filters, and handling user interactions.Each part of the application has a well-defined role, making the system easier to scale and maintain as new features are introduced.
The photo album application was developed using a simple, modular design that reflects its limited scope and focused purpose: to make photo tagging and filtering straightforward and user-friendly. The system was not intended to be scaled or deployed in complex environments, so the emphasis was placed on clarity, maintainability, and ease of use.
This pragmatic and lightweight architecture supports the project’s goal as a personal or small-scale photo organiser, while still allowing for modest future improvements or extensions.
The database schema was chosen to support the structured organisation and retrieval of photos based on associated metadata. The core principles guiding this schema were:
Figure 1: Entity-Relationship Diagram illustrating the relationships between the tables in the database.
Figure 1 presents the Entity-Relationship Diagram (ERD), which shows how the schema is structured. The ImageMetadata
table, which stores core metadata for each image. It is linked to three tagging entities: Person
, GroupTag
, and EmotionTag
, each of which contains unique tag values.
To support many-to-many relationships—where each image can be tagged with multiple people, groups, or emotions, the schema includes three join tables: ImagePerson
, ImageGroup
, and ImageEmotion
. Each join table contains foreign keys that reference the primary keys in ImageMetadata
and their respective tag tables. This design ensures flexibility and consistency while enabling efficient filtering such as “Show all photos of Shabaj or “Find all images tagged as ‘Happy’.”
Stores one row per image with general metadata.
Column | Type | Description |
---|---|---|
id | INTEGER | Primary key |
filename | TEXT | Unique filename of the image |
description | TEXT | User-provided description of the image |
date | TEXT | Date of the image (as string) |
tagged | INTEGER | Boolean flag (1 = tagged, 0 = untagged) |
location.id | INTEGER | Foreign key to `Location.id` |
Stores unique names of individuals who appear in images.
Column | Type | Description |
---|---|---|
id | INTEGER | Primary key |
person_name | TEXT | Unique name of the person |
Join table linking images to people (many-to-many).
Column | Type | Description |
---|---|---|
image_id | INTEGER | Foreign key to `ImageMetadata.id` |
person_id | INTEGER | Foreign key to `Person.id` |
Composite primary key (`image_id`, `person_id`) |
Stores unique group labels such as “Family” or “Friends”.
Column | Type | Description |
---|---|---|
id | INTEGER | Primary key |
group_name | TEXT | Unique group name |
Join table linking images to groups (many-to-many).
Column | Type | Description |
---|---|---|
image_id | INTEGER | Foreign key to `ImageMetadata.id` |
group_id | INTEGER | Foreign key to `GroupTag.id` |
Composite primary key (`image_id`, `group_id`) |
Stores emotion labels associated with photos, e.g. "Happy", "Nostalgic".
Column | Type | Description |
---|---|---|
id | INTEGER | Primary key |
emotion_name | TEXT | Unique emotion name |
Join table linking images to emotions (many-to-many).
Column | Type | Description |
---|---|---|
image_id | INTEGER | Foreign key to `ImageMetadata.id` |
emotion_id | INTEGER | Foreign key to `EmotionTag.id` |
Composite primary key (`image_id`, `emotion_id`) |
Stores structured location data that can be associated with images.
Column | Type | Description |
---|---|---|
id | INTEGER | Primary key |
name | TEXT | Name of the location (e.g., "Hyde Park") |
category | TEXT | Optional category (e.g., "Park", "Museum") |
country | TEXT | Country where the picture was taken |
region | TEXT | Regional subdivision |
city | TEXT | City name |
postcode | TEXT | Postal code of the location |
This table is referenced by the ImageMetadata
table via the location_id
foreign key, allowing each image to be optionally linked to a structured location entry. This enables location-based filtering (e.g., all photos taken in "London" or in "Museums").
While the application meets its core goals, there are a few limitations in the current version and opportunities for future enhancements:
These limitations were consciously accepted in order to keep the initial system lightweight, focused, and easy to maintain. However, addressing them in future iterations could improve usability and extend the system's capabilities without compromising its core values of simplicity and privacy.