Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 71 additions & 9 deletions app/upload/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"fmt"
"strings"
"sync"
"time"

"github.com/gdamore/tcell/v2"
"github.com/simulot/immich-go/adapters"
Expand Down Expand Up @@ -54,12 +55,21 @@
uc.app.Log().Info("created tag", "tag", tag.Value)
tag.ID = r[0].ID
}
_, err := uc.client.Immich.TagAssets(ctx, tag.ID, ids)
if err != nil {
uc.app.Log().Error("failed to add assets to tag", "err", err, "tag", tag.Value, "assets", len(ids))
return tag, err
const batchSize = 500
total := len(ids)
var err error
for i := 0; i < total; i += batchSize {
end := i + batchSize
if end > total {
end = total
}
_, err = uc.client.Immich.TagAssets(ctx, tag.ID, ids[i:end])
if err != nil {
uc.app.Log().Error("failed to add assets to tag", "err", err, "tag", tag.Value, "assets", len(ids[i:end]))
return tag, err
}
}
uc.app.Log().Info("updated tag", "tag", tag.Value, "assets", len(ids))
uc.app.Log().Info("updated tag", "tag", tag.Value, "assets", total)
return tag, err
}

Expand Down Expand Up @@ -97,16 +107,39 @@
return nil
}
defer func() { uc.finished = true }()
// do waiting operations
uc.albumsCache.Close()
uc.tagsCache.Close()

if uc.DeferTags {
if uc.client.PauseImmichBackgroundJobs {
uc.app.Log().Info("Resuming metadata extraction...")
// Use a background context to ensure the command is sent even if the main context is cancelling
bgCtx := context.Background()
_, err := uc.client.AdminImmich.SendJobCommand(bgCtx, "metadataExtraction", "resume", true)

Check failure on line 116 in app/upload/run.go

View workflow job for this annotation

GitHub Actions / 🔍 Lint Code

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter doesn't like it
Error: app/upload/run.go:116:50: Non-inherited new context, use function like context.WithXXX instead (contextcheck)
_, err := uc.client.AdminImmich.SendJobCommand(bgCtx, "metadataExtraction", "resume", true)

anyway, I don't know how to do something else

Suggested change
_, err := uc.client.AdminImmich.SendJobCommand(bgCtx, "metadataExtraction", "resume", true)
_, err := uc.client.AdminImmich.SendJobCommand(bgCtx, "metadataExtraction", "resume", true) //nolint:contextcheck

Copy link
Author

@Aeroverra Aeroverra Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is different than the original problem you are describing.

I can confirm task 2 does not override the values of task 3. I believe task 2 will simply not add tags read from the metadata if tags have already been applied to the asset.

Running the metadata task first was my way of trying to speed up the process so the tags get applied in immich and immich-go can than apply its tags as soon as possible so the user can than exit the program.

I have now tested this solution a couple times with my large library and it does work but in the event that the import gets stuck or immich tasks deadlock you end up having to reimport the whole library so writing the tags to a file first so it can always pick up would make this implementation more reliable..

That all being said I think I may investigate the immich Code base first to see if the process of applying tags could be updated there as this is turning into a large work around for what may just be a simple PR on that end.

I have also never written in Go until now so this is not my area of expertise and I don't like committing spaghetti or AI slop so I will get back to this in a bit.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Immich used to have (has) issues with upload and modifying metadata using the API.

I think a good suspect it the "ReadXMP" job.
#1245
#1200

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't forgot about this but also haven't looked into it yet. If I forget about this entirely feel free to close but its the holidays so just a little busy

if err != nil {
uc.app.Log().Error("Failed to resume metadata extraction", "err", err)
}
}

uc.app.Log().Info("Waiting for metadata extraction to complete...")
err := uc.waitForMetadataExtraction(ctx)
if err != nil {
uc.app.Log().Error("Failed to wait for metadata extraction", "err", err)
}
uc.app.Log().Info("Metadata extraction complete, applying tags...")
uc.tagsCache.Close()
}

// Resume immich background jobs if requested
err := uc.resumeJobs(ctx)
if err != nil {
return err
}

// do waiting operations
uc.albumsCache.Close()
if !uc.DeferTags {
uc.tagsCache.Close()
}

// Generate FileProcessor report
if uc.app.FileProcessor() != nil {
report := uc.app.FileProcessor().GenerateReport()
Expand Down Expand Up @@ -141,7 +174,11 @@
uc.albumsCache = cache.NewCollectionCache(50, func(album assets.Album, ids []string) (assets.Album, error) {
return uc.saveAlbum(ctx, album, ids)
})
uc.tagsCache = cache.NewCollectionCache(50, func(tag assets.Tag, ids []string) (assets.Tag, error) {
tagCacheSize := 50
if uc.DeferTags {
tagCacheSize = 1 << 30
}
uc.tagsCache = cache.NewCollectionCache(tagCacheSize, func(tag assets.Tag, ids []string) (assets.Tag, error) {
return uc.saveTags(ctx, tag, ids)
})

Expand Down Expand Up @@ -596,3 +633,28 @@
return nil
}
*/

func (uc *UpCmd) waitForMetadataExtraction(ctx context.Context) error {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
jobs, err := uc.client.Immich.GetJobs(ctx)
if err != nil {
return err
}
job, ok := jobs["metadataExtraction"]
if !ok {
// Job not found, assume it's done or not running
return nil
}
if job.JobCounts.Active == 0 && job.JobCounts.Waiting == 0 {
return nil
}
}
}
}
2 changes: 2 additions & 0 deletions app/upload/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type UpCmd struct {
tagsCache *cache.CollectionCache[assets.Tag] // List of tags present on the server
finished bool // the finish task has been run
infoCollector *filenames.InfoCollector // Collects information about the files being processed
DeferTags bool // Defer tagging until metadata extraction is complete
}

func (uc *UpCmd) RegisterFlags(flags *pflag.FlagSet) {
Expand All @@ -85,6 +86,7 @@ func (uc *UpCmd) RegisterFlags(flags *pflag.FlagSet) {
flags.BoolVar(&uc.Overwrite, "overwrite", false, "Always overwrite files on the server with local versions")
flags.StringSliceVar(&uc.Tags, "tag", nil, "Add tags to the imported assets. Can be specified multiple times. Hierarchy is supported using a / separator (e.g. 'tag1/subtag1')")
flags.BoolVar(&uc.SessionTag, "session-tag", false, "Tag uploaded photos with a tag \"{immich-go}/YYYY-MM-DD HH-MM-SS\"")
flags.BoolVar(&uc.DeferTags, "defer-tags", false, "Defer tagging until metadata extraction is complete")

uc.StackOptions.RegisterFlags(flags)
}
Expand Down
13 changes: 7 additions & 6 deletions docs/commands/upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ All upload sub-commands require these connection parameters:

## Tagging and Organization

| Option | Default | Description |
| --------------- | ------------ | -------------------------------------------- |
| `--session-tag` | `false` | Tag with upload session timestamp |
| `--tag` | - | Add custom tags (can be used multiple times) |
| `--device-uuid` | `$LOCALHOST` | Set device identifier |
| Option | Default | Description |
| --------------- | ------------ | ------------------------------------------------------------ |
| `--session-tag` | `false` | Tag with upload session timestamp |
| `--defer-tags` | `false` | Apply tags only after metadata extraction has completed to keep embedded file tags/keywords intact |
| `--tag` | - | Add custom tags (can be used multiple times) |
| `--device-uuid` | `$LOCALHOST` | Set device identifier |

## User Interface

Expand Down Expand Up @@ -270,4 +271,4 @@ immich-go upload from-immich \

- [Configuration Options](../configuration.md)
- [Technical Details](../technical.md)
- [Best Practices](../best-practices.md)
- [Best Practices](../best-practices.md)
3 changes: 3 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ overwrite = false
pause-immich-jobs = true
server = 'https://immich.app'
session-tag = false
defer-tags = false
skip-verify-ssl = false
time-zone = ''

Expand Down Expand Up @@ -473,6 +474,7 @@ upload:
pause-immich-jobs: true
server: https://immich.app
session-tag: false
defer-tags: false
skip-verify-ssl: false
tag: {}
time-zone: ""
Expand Down Expand Up @@ -710,6 +712,7 @@ upload:
"pause-immich-jobs": true,
"server": "https://immich.app",
"session-tag": false,
"defer-tags": false,
"skip-verify-ssl": false,
"tag": {},
"time-zone": ""
Expand Down
1 change: 1 addition & 0 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ The following environment variables can be used to configure `immich-go`.
| `IMMICH_GO_UPLOAD_PAUSE_IMMICH_JOBS` | `--pause-immich-jobs` | `true` | Pause Immich background jobs during upload operations |
| `IMMICH_GO_UPLOAD_SERVER` | `--server` | | Immich server address (example http://your-ip:2283 or https://your-domain) |
| `IMMICH_GO_UPLOAD_SESSION_TAG` | `--session-tag` | `false` | Tag uploaded photos with a tag "{immich-go}/YYYY-MM-DD HH-MM-SS" |
| `IMMICH_GO_UPLOAD_DEFER_TAGS` | `--defer-tags` | `false` | Apply tags only after metadata extraction has finished |
| `IMMICH_GO_UPLOAD_SKIP_VERIFY_SSL` | `--skip-verify-ssl` | `false` | Skip SSL verification |
| `IMMICH_GO_UPLOAD_TAG` | `--tag` | `[]` | Add tags to the imported assets. Can be specified multiple times. Hierarchy is supported using a / separator (e.g. 'tag1/subtag1') |
| `IMMICH_GO_UPLOAD_TIME_ZONE` | `--time-zone` | | Override the system time zone |
Expand Down
1 change: 1 addition & 0 deletions docs/upload-commands-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Tags can be assigned in two main ways:

* **`--folder-as-tags`**: Uses the folder structure to create tags. For example, a file at `Holidays/Summer 2024/photo.jpg` will be tagged `Holidays/Summer 2024`.
* **XMP Metadata**: Tags are also read from XMP sidecar files.
* **`--defer-tags`**: Postpones tag creation until Immich finishes metadata extraction so built-in file tags/keywords aren’t dropped.

`immich-go` creates new tags on the server as needed and efficiently tags assets in batches.

Expand Down
Loading