GitHub: https://github.com/fazi-gondal/Flet-media-library
PyPI: https://pypi.org/project/flet-media-library/
I’ve been building mobile applications with Flet, and one thing becomes obvious pretty quickly when you move beyond basic apps:
Python and Flet are great for building the application itself, but eventually you need to talk to the operating system.
Media handling is a good example.
As soon as an app needs to browse photos, display videos, save downloads to the gallery, record audio, manage files, or react to changes in the device's media library, you start dealing with platform-specific APIs, permissions, scoped storage, MediaStore, PhotoKit, thumbnails, and a lot of edge cases.
I didn't want to solve those problems separately in every Flet project.
So I built flet-media-library.
It is an open-source Flet service extension that gives Flet applications a Python API for working with the device media library on Android and iOS.
The current release is v1.1.2.
The problem I was trying to solve
Imagine you're building a Flet application that downloads videos.
The download itself isn't particularly interesting:
URL
↓
download
↓
temporary file
The real problem starts afterward.
Where should that video go?
Users expect it to appear in something like:
Movies/MyApp/
and show up in the system gallery.
The same problem appears with other applications.
A camera app needs to save captured media.
A voice recorder needs to save recordings to Music.
A media editor needs to read assets and save the output.
A gallery app needs to query thousands of files efficiently.
A file manager may need to rename or move media.
And all of this has to work within the platform's permission and storage model.
That's the part I wanted to make reusable.
What is flet-media-library?
flet-media-library is a Flet Service that exposes media-library functionality directly to Python.
A basic setup looks like this:
import flet as ft
from flet_media_library import MediaLibrary
async def main(page: ft.Page):
media = MediaLibrary()
page.services.append(media)
page.update()
status = await media.request_permissions([
"image",
"video",
])
if not status.all_granted and not status.any_limited:
page.add(ft.Text("Media permission was not granted"))
return
result = await media.get_assets(
media_type="image",
limit=20,
sort_by="date_added",
sort_order="desc",
)
for asset in result.items:
print(asset.display_name)
if __name__ == "__main__":
ft.run(main)
The important part for me is that the application code stays Python.
You don't have to build your application around photo_manager types or write platform-specific media logic throughout your UI code.
What it currently supports
The library covers most of the things you'd expect from a reusable media service.
Permissions
You can check and request permissions for images, videos, and audio independently.
status = await media.check_permissions([
"image",
"video",
"audio",
])
The result contains per-type states such as:
granted
limited
denied
denied_forever
restricted
unknown
That matters on newer Android versions because media permissions are no longer just one big switch.
The project also exposes the limited-access picker and a helper for opening the application settings.
Querying albums and assets
You can query device albums:
albums = await media.get_albums(media_type="image")
for album in albums:
print(album.name, album.asset_count)
And then query assets with pagination and sorting:
page = await media.get_assets(
media_type="video",
limit=30,
offset=0,
sort_by="date_added",
sort_order="desc",
)
The query API supports:
- image, video, audio, or all media
- album filtering
- pagination
- date added
- date modified
- filename
- size
- duration
- MIME filtering where supported
- date-range filtering
That makes it practical for a real gallery rather than just a small demo with ten files.
Thumbnails became more important than I expected
One of the first things you notice when building a media gallery is that loading original files everywhere is a bad idea.
The package provides a normal thumbnail API:
thumb = await media.get_thumbnail(
asset.id,
width=150,
height=150,
quality=85,
)
That works nicely with Flet:
ft.Image(
src_base64=thumb,
width=150,
height=150,
)
But I also added another API:
path = await media.get_thumbnail_path(
asset.id,
width=160,
height=160,
)
This writes the thumbnail into a local cache and returns the path.
You can then use:
ft.Image(
src=path,
width=160,
height=160,
)
The reason for having both is pretty simple.
Base64 is convenient.
But when you're scrolling through a large gallery, continuously moving image data through the Python/Flutter boundary isn't something I wanted to rely on.
So the path-based API is there specifically for gallery-scale rendering.
Saving media to the system gallery
This is probably the part most Flet media applications will care about.
For images:
await media.save_image(
"/path/to/photo.jpg",
file_name="photo.jpg",
relative_path="Pictures/MyApp",
)
For videos:
await media.save_video(
"/path/to/video.mp4",
file_name="video.mp4",
relative_path="Movies/MyApp",
)
The point is that these aren't just application-private files anymore.
They are inserted into the platform's media library.
On Android, the public destinations include locations such as:
DCIM/
Pictures/
Movies/
Music/
depending on the type of media and the API being used.
This is particularly useful for downloaders, camera apps, recorders, editors, and other applications that create media for the user.
Audio exposed an interesting platform gap
While working on this, I ran into one of the reasons native extensions are sometimes unavoidable.
The upstream Flutter media-library functionality I was using didn't provide everything I needed for Android audio saving.
So I added a custom Android Kotlin implementation using MediaStore.
That gives the Python API:
await media.save_audio(
"/path/to/recording.wav",
file_name="voice_note.wav",
relative_path="Music/FletMediaLibrary",
)
This is currently an Android-only capability.
I prefer exposing that limitation explicitly rather than pretending Android and iOS have exactly the same capabilities.
That's also why the library has capability discovery.
Platform capability discovery
You can ask the library what the current platform supports:
capabilities = await media.get_capabilities()
print(capabilities)
The response can tell you things such as:
platform
supports_audio_save
supports_move
supports_rename
supports_copy
supports_mime_filter
supports_limited_access
supports_thumbnail_path
supports_change_notify
android_sdk
So instead of scattering platform checks everywhere, an application can do something like:
if capabilities["supports_rename"]:
# show rename action
For cross-platform Flet applications, that can make the UI much cleaner.
Rename and move are where mobile storage gets interesting
Reading media is one thing.
Changing it is another.
The library provides:
await media.rename_asset(
asset_id,
"new_name.mp4",
)
and:
await media.move_asset(
asset_id,
"Movies/Archive",
)
On Android, these operations are implemented through native media APIs and have to respect the rules introduced by scoped storage.
Depending on the Android version and operation, the system may ask the user to confirm the modification.
That's expected.
One thing I learned while building this is that it's better to work with the platform's security model than to try to work around it.
That's also why the project intentionally avoids requiring broad:
MANAGE_EXTERNAL_STORAGE
access for normal media-library operations.
Live media changes
Another feature that became useful in the demo is change notifications.
Suppose your Flet gallery is open and another application creates a photo.
Your application shouldn't necessarily have to be manually restarted or rebuilt to know something changed.
You can subscribe to changes:
def on_change(event):
print(
event.change_type,
event.asset_id,
event.media_type,
)
media.on_change = on_change
await media.start_change_notify()
The event can represent:
added
modified
removed
other
The other case is intentional.
The underlying platform doesn't always give you perfectly precise change information, so I'd rather expose that honestly than invent precision that isn't really there.
The implementation
The project is packaged as an actual Flet extension.
The Python side exposes the public API.
The Flutter side implements the service.
photo_manager is used as the primary media-library backend for common functionality such as querying, thumbnails, saving and deletion.
Then Android Kotlin fills specific gaps where native handling is needed.
So the important boundary is roughly:
Flet / Python application
↓
MediaLibrary service
↓
Flutter extension
↓
photo_manager
↓
Android native MediaStore where required
The Python package owns its own models such as:
MediaAsset
MediaAlbum
MediaAssetPage
MediaPermissionStatus
MediaChangeEvent
That means the application isn't tightly coupled to the underlying Flutter package.
I think that's an important detail when building an extension that other projects may depend on.
Why not just use filesystem access?
That was one of the questions I kept coming back to during development.
A phone isn't just a normal desktop filesystem.
Modern Android deliberately separates application storage and shared media.
iOS also has its own photo-library model.
So a solution based on:
open(...)
os.listdir(...)
shutil.move(...)
doesn't automatically give you correct integration with the user's gallery.
You also have to think about:
- media indexing
- permissions
- scoped storage
- limited access
- user confirmation
- public media directories
- platform-specific identifiers
- external media changes
That's why I built this around the media-library APIs themselves, rather than treating the problem as generic filesystem access.
The demo application
I also didn't want the repository to contain only a library with a few isolated code snippets.
There is a full demo application in:
examples/media_library_demo/
It acts as both a showcase and a test harness.
It includes:
Gallery
Browse photos, videos, and audio.
Albums
Load actual device albums.
Thumbnails
Render image and video thumbnails.
Playback
Play video and audio from inside the application.
Camera
Capture photos and record videos.
Microphone
Record audio and save it to Android's Music collection.
File management
Move, rename, copy, and delete media.
Live updates
Listen for media-library changes.
Smoke testing
Run a sequence of checks against the core APIs on a real device.
That last part is especially useful for native extensions.
There are a lot of things that can work in a desktop development environment and then behave differently on a real Android device.
A real-world use case: a Flet video downloader
This is probably the easiest way to explain why I wanted this library to exist.
Suppose we're building a Flet video downloader.
The downloader creates:
/cache/download.mp4
Then:
await media.save_video(
"/cache/download.mp4",
file_name="download.mp4",
relative_path="Movies/MyDownloader",
)
Now the file is part of the device media library.
The same service can then be used to query and display it.
So the application doesn't need one system for downloading and another completely separate system for managing gallery content.
The workflow becomes:
Download
↓
Temporary file
↓
MediaLibrary
↓
MediaStore / PhotoKit
↓
System media library
That's the kind of reusable building block I wanted.
Another use case: camera and recording apps
The same thing works for camera applications.
For example:
Camera capture
↓
temporary file
↓
save_image / save_video
↓
system gallery
And for an Android voice recorder:
microphone
↓
WAV recording
↓
save_audio
↓
Music/FletMediaLibrary
The included demo application exercises both flows.
Error handling
Native APIs fail.
Permissions can be denied.
Files can disappear.
Users can cancel system dialogs.
Some operations simply aren't available on a specific platform.
So instead of returning a giant collection of ambiguous values, the package defines typed exceptions such as:
PermissionRequiredError
PermissionDeniedError
UnsupportedError
AssetNotFoundError
AlbumNotFoundError
InvalidArgumentError
PlatformError
For example:
from flet_media_library import UnsupportedError
try:
await media.rename_asset(asset_id, "new_name.mp4")
except UnsupportedError:
print("Rename is not supported here")
This is a small detail, but it makes the API much easier to integrate into an actual application.
What I learned while building it
The interesting part of this project wasn't writing a few methods around a media plugin.
The real work was dealing with the differences between:
Python
Flutter
Android
iOS
MediaStore
PhotoKit
Flet's extension system
and keeping those differences from leaking into every part of the application.
A few things became especially clear during development.
Native capability gaps are normal
A cross-platform Flutter package can cover most of the common functionality and still leave important platform-specific gaps.
That's where an extension can add value.
Mobile storage is not a normal filesystem problem
Especially on newer Android versions.
Trying to treat everything as unrestricted file access leads to the wrong abstraction.
Performance matters in media applications
Generating thumbnails is easy.
Generating thousands of thumbnails efficiently is a different problem.
That's why the path-based thumbnail API was worth adding.
Platform differences should be explicit
Rather than hiding limitations, I prefer capability detection and clear exceptions.
Testing on a real device matters
Permissions, MediaStore behavior, recording paths, confirmation dialogs, and filesystem locations are all areas where a real device tells you much more than a desktop test.
Installing it
The package is available on PyPI:
pip install flet-media-library
Or with uv:
uv add flet-media-library
Then add it to your project:
[project]
dependencies = [
"flet>=1.0.0",
"flet-media-library>=1.1.2",
]
The package includes the Flutter extension so the native integration can be included when building the Flet application.
Project status
The current release is:
flet-media-library 1.1.2
The project is MIT licensed and currently targets Android and iOS.
Recent work has included:
- per-media-type permissions
- limited-access handling
- cached thumbnail paths
- capability discovery
- date-range filtering
- Android audio saving
- Android move and rename support
- improved native concurrency handling
- demo recording fixes
- automated Android demo APK builds
- a larger integration-test/demo harness
There is still work to do.
The project roadmap includes broader device verification, more native integration tests, Dart-side tests around the media backend, and an optional original-file-path API for playback-related scenarios.
Final thoughts
I built flet-media-library because I kept running into the same problem:
Flet makes application development surprisingly productive, but serious mobile applications eventually need native platform capabilities.
Media handling is one of those areas.
Instead of every Flet developer separately figuring out permissions, MediaStore, PhotoKit, thumbnails, public storage, media mutations, and change notifications, I'd rather have those capabilities available as a reusable service.
That's what this project is trying to be.
Not a replacement for Flet.
Not a new UI framework.
Just a practical media-library layer that lets you keep writing your application in Python while still using the native media APIs underneath.
And because it's open source, you can inspect the implementation, use it in your own projects, open issues, and contribute improvements.
If you're building a Flet gallery, video downloader, camera app, audio recorder, media editor, or another media-heavy mobile application, this may save you from rebuilding the same native integration yourself.
Top comments (0)