Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/next' into issue-#534
Browse files Browse the repository at this point in the history
  • Loading branch information
simulot committed Nov 22, 2024
2 parents 0acbdde + 6ff997a commit 72d0fdb
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 53 deletions.
5 changes: 5 additions & 0 deletions adapters/folder/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ type ImportFolderOptions struct {

// Folder as tags
FolderAsTags bool

// SessionTag indicates whether to add a session tag to the imported assets.
SessionTag bool
session string // Session tag value
}

func (o *ImportFolderOptions) AddFromFolderFlags(cmd *cobra.Command, parent *cobra.Command) {
Expand Down Expand Up @@ -96,6 +100,7 @@ func (o *ImportFolderOptions) AddFromFolderFlags(cmd *cobra.Command, parent *cob

cmd.Flags().StringSliceVar(&o.Tags, "tag", nil, "Add tags to the imported assets. Can be specified multiple times. Hierarchy is supported using a / separator (e.g. 'tag1/subtag1')")
cmd.Flags().BoolVar(&o.FolderAsTags, "folder-as-tags", false, "Use the folder structure as tags, (ex: the file holiday/summer 2024/file.jpg will have the tag holiday/summer 2024)")
cmd.Flags().BoolVar(&o.SessionTag, "session-tag", false, "Tag uploaded photos with a tag \"{immich-go}/YYYY-MM-DD HH-MM-SS\"")

cliflags.AddInclusionFlags(cmd, &o.InclusionFlags)
exif.AddExifToolFlags(cmd, &o.ExifToolFlags)
Expand Down
45 changes: 27 additions & 18 deletions adapters/folder/readFolder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io/fs"
"path"
"path/filepath"
"sort"
"strings"
"sync"
"time"

"github.com/simulot/immich-go/internal/assets"
"github.com/simulot/immich-go/internal/exif"
Expand Down Expand Up @@ -51,6 +53,10 @@ func NewLocalFiles(ctx context.Context, l *fileevent.Recorder, flags *ImportFold
flags.InfoCollector = filenames.NewInfoCollector(flags.ExifToolFlags.Timezone.TZ, flags.SupportedMedia)
}

if flags.SessionTag {
flags.session = fmt.Sprintf("{immich-go}/%s", time.Now().Format("2006-01-02 15:04:05"))
}

if flags.ExifToolFlags.UseExifTool {
err := exif.NewExifTool(&flags.ExifToolFlags)
if err != nil {
Expand Down Expand Up @@ -221,24 +227,6 @@ func (la *LocalAssetBrowser) parseDir(ctx context.Context, fsys fs.FS, dir strin
a.FromSourceFile = a.UseMetadata(md)
}

// Add tags
if len(la.flags.Tags) > 0 {
for _, t := range la.flags.Tags {
a.AddTag(t)
}
}

// Add folder as tags
if la.flags.FolderAsTags {
t := fsName
if dir != "." {
t = path.Join(t, dir)
}
if t != "" {
a.AddTag(t)
}
}

// check the presence of a XMP file
xmpName, err := checkExistSideCar(fsys, a.File.Name(), ".xmp")
if err == nil && xmpName != "" {
Expand Down Expand Up @@ -281,6 +269,27 @@ func (la *LocalAssetBrowser) parseDir(ctx context.Context, fsys fs.FS, dir strin
continue
}

// Add tags
if len(la.flags.Tags) > 0 {
for _, t := range la.flags.Tags {
a.AddTag(t)
}
}

// Add folder as tags
if la.flags.FolderAsTags {
t := fsName
if dir != "." {
t = path.Join(t, dir)
}
if t != "" {
a.AddTag(t)
}
}

if la.flags.SessionTag {
a.AddTag(la.flags.session)
}
select {
case in <- a:
case <-ctx.Done():
Expand Down
85 changes: 58 additions & 27 deletions adapters/googlePhotos/googlephotos.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package gp

import (
"bytes"
"context"
"fmt"
"io/fs"
"log/slog"
"path"
Expand Down Expand Up @@ -100,6 +102,9 @@ func NewTakeout(ctx context.Context, l *fileevent.Recorder, flags *ImportFlags,
return nil, err
}
}
if flags.SessionTag {
flags.session = fmt.Sprintf("{immich-go}/%s", time.Now().Format("2006-01-02 15:04:05"))
}

if flags.ManageEpsonFastFoto {
g := epsonfastfoto.Group{}
Expand Down Expand Up @@ -173,38 +178,56 @@ func (to *Takeout) passOneFsWalk(ctx context.Context, w fs.FS) error {
}
switch ext {
case ".json":
md, err := fshelper.ReadJSON[GoogleMetaData](w, name)
if err == nil {
switch {
case md.isAsset():
dirCatalog.jsons[base] = md.AsMetadata(fshelper.FSName(w, name)) // Keep metadata
to.log.Log().Debug("Asset JSON", "metadata", md)
to.log.Record(ctx, fileevent.DiscoveredSidecar, fshelper.FSName(w, name), "type", "asset metadata", "title", md.Title)
case md.isAlbum():
to.log.Log().Debug("Album JSON", "metadata", md)
if !to.flags.KeepUntitled && md.Title == "" {
to.log.Record(ctx, fileevent.DiscoveredUnsupported, fshelper.FSName(w, name), "reason", "discard untitled album")
var md *assets.Metadata
b, err := fs.ReadFile(w, name)
if err != nil {
to.log.Record(ctx, fileevent.Error, fshelper.FSName(w, name), "error", err.Error())
return nil
}
if bytes.Contains(b, []byte("immich-go version:")) {
md, err = assets.UnMarshalMetadata(b)
if err != nil {
to.log.Record(ctx, fileevent.DiscoveredUnsupported, fshelper.FSName(w, name), "reason", "unknown JSONfile")
}
md.FileName = base
to.log.Record(ctx, fileevent.DiscoveredSidecar, fshelper.FSName(w, name), "type", "immich-go metadata", "title", md.FileName)
md.File = fshelper.FSName(w, name)
} else {
md, err := fshelper.UnmarshalJSON[GoogleMetaData](b)
if err == nil {
switch {
case md.isAsset():
md := md.AsMetadata(fshelper.FSName(w, name)) // Keep metadata
md.File = fshelper.FSName(w, name)
dirCatalog.jsons[base] = md
to.log.Log().Debug("Asset JSON", "metadata", md)
to.log.Record(ctx, fileevent.DiscoveredSidecar, fshelper.FSName(w, name), "type", "asset metadata", "title", md.FileName)
case md.isAlbum():
to.log.Log().Debug("Album JSON", "metadata", md)
if !to.flags.KeepUntitled && md.Title == "" {
to.log.Record(ctx, fileevent.DiscoveredUnsupported, fshelper.FSName(w, name), "reason", "discard untitled album")
return nil
}
a := to.albums[dir]
a.Title = md.Title
if a.Title == "" {
a.Title = filepath.Base(dir)
}
if e := md.Enrichments; e != nil {
a.Description = e.Text
a.Latitude = e.Latitude
a.Longitude = e.Longitude
}
to.albums[dir] = a
to.log.Record(ctx, fileevent.DiscoveredSidecar, fshelper.FSName(w, name), "type", "album metadata", "title", md.Title)
default:
to.log.Record(ctx, fileevent.DiscoveredUnsupported, fshelper.FSName(w, name), "reason", "unknown JSONfile")
return nil
}
a := to.albums[dir]
a.Title = md.Title
if a.Title == "" {
a.Title = filepath.Base(dir)
}
if e := md.Enrichments; e != nil {
a.Description = e.Text
a.Latitude = e.Latitude
a.Longitude = e.Longitude
}
to.albums[dir] = a
to.log.Record(ctx, fileevent.DiscoveredSidecar, fshelper.FSName(w, name), "type", "album metadata", "title", md.Title)
default:
} else {
to.log.Record(ctx, fileevent.DiscoveredUnsupported, fshelper.FSName(w, name), "reason", "unknown JSONfile")
return nil
}
} else {
to.log.Record(ctx, fileevent.DiscoveredUnsupported, fshelper.FSName(w, name), "reason", "unknown JSONfile")
return nil
}
default:

Expand Down Expand Up @@ -474,6 +497,14 @@ func (to *Takeout) handleDir(ctx context.Context, dir string, gOut chan *assets.

for _, a := range g.Assets {
a.Albums = g.Albums
if to.flags.SessionTag {
a.AddTag(to.flags.session)
}
if to.flags.Tags != nil {
for _, tag := range to.flags.Tags {
a.AddTag(tag)
}
}
}

select {
Expand Down
9 changes: 9 additions & 0 deletions adapters/googlePhotos/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ type ImportFlags struct {

// ManageEpsonFastFoto enables the management of Epson FastFoto files.
ManageEpsonFastFoto bool

// Tags is a list of tags to be added to the imported assets.
Tags []string

// SessionTag indicates whether to add a session tag to the imported assets.
SessionTag bool
session string // Session tag value
}

func (o *ImportFlags) AddFromGooglePhotosFlags(cmd *cobra.Command, parent *cobra.Command) {
Expand All @@ -88,6 +95,8 @@ func (o *ImportFlags) AddFromGooglePhotosFlags(cmd *cobra.Command, parent *cobra
cmd.Flags().BoolVarP(&o.KeepArchived, "include-archived", "a", true, "Import archived Google Photos")
cmd.Flags().BoolVarP(&o.KeepJSONLess, "include-unmatched", "u", false, "Import photos that do not have a matching JSON file in the takeout")
cmd.Flags().Var(&o.BannedFiles, "ban-file", "Exclude a file based on a pattern (case-insensitive). Can be specified multiple times.")
cmd.Flags().StringSliceVar(&o.Tags, "tag", nil, "Add tags to the imported assets. Can be specified multiple times. Hierarchy is supported using a / separator (e.g. 'tag1/subtag1')")
cmd.Flags().BoolVar(&o.SessionTag, "session-tag", false, "Tag uploaded photos with a tag \"{immich-go}/YYYY-MM-DD HH-MM-SS\"")

cliflags.AddInclusionFlags(cmd, &o.InclusionFlags)
exif.AddExifToolFlags(cmd, &o.ExifToolFlags)
Expand Down
16 changes: 8 additions & 8 deletions commands/upload/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,18 +252,18 @@ func (upCmd *UpCmd) handleAsset(ctx context.Context, g *assets.Group, a *assets.
})
}
upCmd.app.Jnl().Record(ctx, fileevent.UploadServerDuplicate, a.File, "reason", advice.Message)
err = upCmd.manageAssetTags(ctx, a)
if err != nil {
return err
}
// err = upCmd.manageAssetTags(ctx, a)
// if err != nil {
// return err
// }

case BetterOnServer: // and manage albums
a.ID = advice.ServerAsset.ID
upCmd.app.Jnl().Record(ctx, fileevent.UploadServerBetter, a.File, "reason", advice.Message)
err = upCmd.manageAssetTags(ctx, a)
if err != nil {
return err
}
// err = upCmd.manageAssetTags(ctx, a)
// if err != nil {
// return err
// }

}

Expand Down
24 changes: 24 additions & 0 deletions docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@

## Release v0.23.0-alpha6 🏗️ Work in progress 🏗️

### New features

**Folder import tags**
Its now possible to assign tags to photos and videos:
```sh
--folder-as-tags Use the folder structure as tags, (ex: the file "holidays/summer 2024/file.jpg" get the tag holidays/summer 2024)
--session-tag Tag uploaded photos with a tag "{immich-go}/YYYY-MM-DD HH-MM-SS"
--tag strings Add tags to the imported assets. Can be specified multiple times. Hierarchy is supported using a / separator (e.g. 'tag1/subtag1')
```

The session tag is useful to identify all photos imported at the same time. It's easy to remove them from the tag screen

**Google photos import tags**

```sh
--session-tag Tag uploaded photos with a tag "{immich-go}/YYYY-MM-DD HH-MM-SS"
--tag strings Add tags to the imported assets. Can be specified multiple times. Hierarchy is supported using a / separator (e.g. 'tag1/subtag1')
```


#### Breaking change since v0.23.0-alpha5
A metadata file is created withe same name as the main file, but with the extension `.json`. The XMP file is left untouched.


### Fixes
* [#533](https://github.com/simulot/immich-go/issues/533) RAW file metadata
The efforts for determining the capture date from the file name are useless.
Expand Down
7 changes: 7 additions & 0 deletions internal/assets/metadata.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package assets

import (
"encoding/json"
"log/slog"
"time"

Expand Down Expand Up @@ -43,3 +44,9 @@ func (m Metadata) LogValue() slog.Value {
func (m Metadata) IsSet() bool {
return m.Description != "" || !m.DateTaken.IsZero() || m.Latitude != 0 || m.Longitude != 0
}

func UnMarshalMetadata(data []byte) (*Metadata, error) {
var m Metadata
err := json.Unmarshal(data, &m)
return &m, err
}
10 changes: 10 additions & 0 deletions internal/fshelper/readjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ func ReadJSON[T any](fsys fs.FS, name string) (*T, error) {

return &object, nil
}

func UnmarshalJSON[T any](b []byte) (*T, error) {
var object T
err := json.Unmarshal(b, &object)
if err != nil {
return nil, err
}

return &object, nil
}

0 comments on commit 72d0fdb

Please sign in to comment.