-
Notifications
You must be signed in to change notification settings - Fork 2
feat(uploads): chunked resumable image upload with JP2 ingest pipeline #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
saad-mhmd
wants to merge
7
commits into
main
Choose a base branch
from
feat-72/item-image-upload
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5667aa2
feat(manuscripts): ItemImage technical metadata + path-only image fie…
020699a
feat(uploads): chunked resumable image upload with JP2 ingest pipelin…
8fdd902
fix(manuscripts): delete image files from disk when an ItemImage is d…
bea1bae
fix(uploads): preflight storage writability at session creation (#72)
7c094e5
feat(uploads): resume interrupted sessions instead of 409ing (#72)
d78b926
fix(uploads): sweep orphan temp dirs in cleanup, not just stale sessi…
9364039
fix(uploads): survive concurrent chunk sends and double finalize (#72)
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
apps/manuscripts/migrations/0023_itemimage_checksum_sha256_itemimage_created_and_more.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # Generated by Django 6.0.6 on 2026-07-16 09:51 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('manuscripts', '0022_alter_historicalitem_date'), | ||
| migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='checksum_sha256', | ||
| field=models.CharField(blank=True, default='', max_length=64), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='created', | ||
| field=models.DateTimeField(auto_now_add=True, null=True), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='exif', | ||
| field=models.JSONField(blank=True, null=True), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='height', | ||
| field=models.PositiveIntegerField(blank=True, null=True), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='modified', | ||
| field=models.DateTimeField(auto_now=True, null=True), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='original_path', | ||
| field=models.CharField(blank=True, default='', max_length=255), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='size_bytes', | ||
| field=models.BigIntegerField(blank=True, null=True), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='source_format', | ||
| field=models.CharField(blank=True, default='', max_length=16), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='uploaded_by', | ||
| field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='uploaded_images', to=settings.AUTH_USER_MODEL), | ||
| ), | ||
| migrations.AddField( | ||
| model_name='itemimage', | ||
| name='width', | ||
| field=models.PositiveIntegerField(blank=True, null=True), | ||
| ), | ||
| ] |
17 changes: 17 additions & 0 deletions
17
apps/manuscripts/migrations/0024_merge_itemimage_metadata_msdescarea.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| """Relink the two concurrent 0023 leaves into a single line. | ||
|
|
||
| A rebase left two migrations branching off 0022: this feature's ItemImage | ||
| upload-metadata fields and main's MsDescArea model. They touch different | ||
| tables, so this is a pure ordering merge with no operations. | ||
| """ | ||
|
|
||
| dependencies = [ | ||
| ("manuscripts", "0023_itemimage_checksum_sha256_itemimage_created_and_more"), | ||
| ("manuscripts", "0023_msdescarea"), | ||
| ] | ||
|
|
||
| operations = [] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
most of the images won't have these fields since they'll already be on the image server. so, it doesn't make sense that we store them here. the role of this model is just to hold a reference to the image on the SIPI server while the SIPI server (image server) would be responsible for anything related to the technical image details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe leave this PR open for now, and explore what we can do with a direct connection to SIPI. can we upload images there directly? does the SIPI server allow for direct image uploads and does it support resumability?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do direct connect to SIPI and upload images directly, it won't be resumable, which would be an issue especially for uploading images of multiple GBs (if I remember correctly from one of the meetings, I heard that a single image can reach up to 5GB).
This will also introduce orphan images, as the directly uploaded images will have no row in the ItemImage table.
I've been exploring some options, especially tus (tus.io) that seems to be the most used for resumable file uploads (along with uppy.io UI).
There's tusd, written in Go, it'll run as its own HTTP upload server in a separate container. It seems to be the standard, and specifically recommended for handling file uploads in the GBs.
There's also drf-tus, which is maintained (last release Jan 2026), but it doesn't declare Python 3.14 support, and every chunk would go through the Python stack, which wouldn't be ideal for GB files. So tusd seems to be a better option.
I lean towards tusd, mainly for its ability to handle big sizes. I'd like your opinion.
Some extra info worth noting:
SIPI has conversion to jp2, but it doesn't indicate that it's lossless and it's more prone to fall into OOM error during the conversion of big files, unlike vips' approach (vips streams instead of loading the whole image into memory).