Built using Tauri, it processes your entire listening history, as generated by Spotify, and generates visualized user trends and stats, giving you an extended view into your music tastes. A more complete Spotify Wrapped.
yarn install
yarn tauri dev
The user uploads their extended history zip file they get from Spotify directly into the application. The app then processes each JSON file in the zip and generates its own set of processed data, saved on the user's filesystem. Then the user can navigate through the various pages showing yearly or monthly stats.
The application displays aggregated information such as total number of tracks/time listened over time, along with a list of Top Tracks/Artists/Albums. The user can also see specific stats about a track or an artist, such as first/total listens, discovery date, etc
The frontend is written using Vue.js. It uses vue-router for application routing and navigation, and pinia for state management, caching any data of previously visited pages in the store.
The backend, or data processeing layer is written using Rust, and utilizes the Tauri filesystem and store plugins to keep track of the processed data.
This is the most important layer of this project. Through this step, the raw listening history data is processed to precompute the yearly and monthly statistics, and stored in the user's filesystem for easy access. Since listening history doesn't change, this only needs to be done once, and the data is then read directly from the processed JSON files.
The first step is to take each zip file and get the relevant information (track, artist, album, timestamp, etc) using the serde framework deserialization. I save this data in the user's filesystem and update the Tauri store to keep track of the processing status of the raw files.
The data processing is completely asynchronous. After the user uploads the zip file, the data is transmited to the Rust side, which takes care of going through each individual file one by one and extracting and aggregating the relevant data. If at any point this processing is interrupted (application crashes or user quits), the process resumes automatically on next application restart, picking up where it left off.
The processing step starts by taking each raw data file and building new data structures for monthly track/artist/album entries. These generated data structures are saved to the system as processed files for each month of each year. For example, a raw file containing listening data from Aug 2024 to Feb 2025 will generate sets of processed files for Aug 2024, Sept 2024, Oct 2024 all the way to Feb 2025. If any processed files already exist for a certain month, the code reads the existing files and merges the two sets together. This ensures that each raw file can be processed in isolation regardless of its contents, and enables the system to be able to pause and resume processing. I use the Tauri store to keep track the processing status for each raw file. To make the processing of each file transactional, I also save the processed files as temp files, and only once an entire raw file is processed and all temp files successfully saved, I move them to the real files.
After the monthly processed files are all generated, I go through them again, month-by-month, and generate the monthly and yearly aggregated data for each set. This preprocessing also uses the Tauri store and temp files to ensure it can be paused and resumed at any point. Once generated, these files are directly read by the UI to display the top items data.
NOTE: This was the initial implementation of the data processing in JavaScript, that was later translated to Rust, and made safer and more robust, with status tracking and crash recovery. Since then, I have realized that instead of using JSON files, I should be using a relational database engine, something simple like SQLite, which is best when we need to query, filter, sort or aggregate data. My exact use-case. Even the temp file approach was solving a problem that transactions solve better. I have future work logged to switch over this file-based implementation to a database.