diff --git a/adapters/googlePhotos/googlephotos.go b/adapters/googlePhotos/googlephotos.go index 87cd9c69..0ce5aa1a 100644 --- a/adapters/googlePhotos/googlephotos.go +++ b/adapters/googlePhotos/googlephotos.go @@ -213,7 +213,7 @@ func (to *Takeout) passOneFsWalk(ctx context.Context, w fs.FS) error { if err == nil { switch { case md.isAsset(): - md := md.AsMetadata(fshelper.FSName(w, name), to.flags.PeopleTag) // Keep metadata + md := md.AsMetadata(fshelper.FSName(w, name), to.flags.PeopleTag, to.flags) // Keep metadata 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, "date", md.DateTaken) @@ -573,6 +573,11 @@ func (to *Takeout) filterOnMetadata(ctx context.Context, a *assets.Asset) fileev a.Close() return fileevent.DiscoveredDiscarded } + if !to.flags.KeepSharedAlbum && a.FromSharedAlbum { + to.logMessage(ctx, fileevent.DiscoveredDiscarded, a, "discarding shared album file") + a.Close() + return fileevent.DiscoveredDiscarded + } if !to.flags.KeepTrashed && a.Trashed { to.logMessage(ctx, fileevent.DiscoveredDiscarded, a, "discarding trashed file") a.Close() diff --git a/adapters/googlePhotos/json.go b/adapters/googlePhotos/json.go index 7c039afa..466fb3ca 100644 --- a/adapters/googlePhotos/json.go +++ b/adapters/googlePhotos/json.go @@ -28,6 +28,7 @@ type GoogleMetaData struct { People []Person `json:"people,omitempty"` // People tags GooglePhotosOrigin struct { FromPartnerSharing googIsPresent `json:"fromPartnerSharing,omitempty"` // true when this is a partner's asset + FromSharedAlbum googIsPresent `json:"fromSharedAlbum,omitempty"` // true when this is from a shared album } `json:"googlePhotosOrigin"` } @@ -78,15 +79,16 @@ func (gmd GoogleMetaData) LogValue() slog.Value { ) } -func (gmd GoogleMetaData) AsMetadata(name fshelper.FSAndName, tagPeople bool) *assets.Metadata { +func (gmd GoogleMetaData) AsMetadata(name fshelper.FSAndName, tagPeople bool, flags *ImportFlags) *assets.Metadata { md := assets.Metadata{ - File: name, - FileName: sanitizedTitle(gmd.Title), - Description: gmd.Description, - Trashed: gmd.Trashed, - Archived: gmd.Archived, - Favorited: gmd.Favorited, - FromPartner: gmd.isPartner(), + File: name, + FileName: sanitizedTitle(gmd.Title), + Description: gmd.Description, + Trashed: gmd.Trashed, + Archived: gmd.Archived, + Favorited: gmd.Favorited, + FromPartner: gmd.isPartner(), + FromSharedAlbum: gmd.isSharedAlbum(), } if gmd.GeoDataExif != nil { md.Latitude, md.Longitude = gmd.GeoDataExif.Latitude, gmd.GeoDataExif.Longitude @@ -106,6 +108,11 @@ func (gmd GoogleMetaData) AsMetadata(name fshelper.FSAndName, tagPeople bool) *a md.AddTag("People/" + p.Name) } } + + if flags.SharedAlbumTag && md.FromSharedAlbum { + md.AddTag("From Shared Album") + } + return &md } @@ -130,6 +137,13 @@ func (gmd *GoogleMetaData) isPartner() bool { return bool(gmd.GooglePhotosOrigin.FromPartnerSharing) } +func (gmd *GoogleMetaData) isSharedAlbum() bool { + if gmd == nil { + return false + } + return bool(gmd.GooglePhotosOrigin.FromSharedAlbum) +} + // Key return an expected unique key for the asset // based on the title and the timestamp func (gmd GoogleMetaData) Key() string { diff --git a/adapters/googlePhotos/options.go b/adapters/googlePhotos/options.go index bc56bd58..8538c589 100644 --- a/adapters/googlePhotos/options.go +++ b/adapters/googlePhotos/options.go @@ -33,6 +33,9 @@ type ImportFlags struct { // KeepPartner determines whether to import photos from the partner's Google Photos account. KeepPartner bool + // KeepSharedAlbum determines whether to import photos from shared albums. + KeepSharedAlbum bool + // KeepUntitled determines whether to include photos from albums without a title in the import process. KeepUntitled bool @@ -79,6 +82,10 @@ type ImportFlags struct { // PeopleTag indicates whether to add a people tag to the imported assets. PeopleTag bool + + // SharedAlbumTag indicates whether to add \"From Shared Album\" tag. + SharedAlbumTag bool + // Timezone TZ *time.Location } @@ -99,6 +106,7 @@ func (o *ImportFlags) AddFromGooglePhotosFlags(cmd *cobra.Command, parent *cobra cmd.Flags().BoolVar(&o.KeepUntitled, "include-untitled-albums", false, "Include photos from albums without a title in the import process") cmd.Flags().BoolVarP(&o.KeepTrashed, "include-trashed", "t", false, "Import photos that are marked as trashed in Google Photos") cmd.Flags().BoolVarP(&o.KeepPartner, "include-partner", "p", true, "Import photos from your partner's Google Photos account") + cmd.Flags().BoolVar(&o.KeepSharedAlbum, "include-shared-album", true, "Import photos from shared albums in Google Photos") cmd.Flags().StringVar(&o.PartnerSharedAlbum, "partner-shared-album", "", "Add partner's photo to the specified album name") 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") @@ -107,6 +115,7 @@ func (o *ImportFlags) AddFromGooglePhotosFlags(cmd *cobra.Command, parent *cobra cmd.Flags().BoolVar(&o.SessionTag, "session-tag", false, "Tag uploaded photos with a tag \"{immich-go}/YYYY-MM-DD HH-MM-SS\"") cmd.Flags().BoolVar(&o.TakeoutTag, "takeout-tag", true, "Tag uploaded photos with a tag \"{takeout}/takeout-YYYYMMDDTHHMMSSZ\"") cmd.Flags().BoolVar(&o.PeopleTag, "people-tag", true, "Tag uploaded photos with tags \"people/name\" found in the JSON file") + cmd.Flags().BoolVar(&o.SharedAlbumTag, "shared-album-tag", true, "Tag photos from shared albums with \"From Shared Album\"") cliflags.AddInclusionFlags(cmd, &o.InclusionFlags) // exif.AddExifToolFlags(cmd, &o.ExifToolFlags) diff --git a/internal/assets/asset.go b/internal/assets/asset.go index 2d7b1c71..3a21b588 100644 --- a/internal/assets/asset.go +++ b/internal/assets/asset.go @@ -39,14 +39,15 @@ type Asset struct { FileSize int // File size in bytes // Metadata for the process and the upload to Immich - CaptureDate time.Time // Date of the capture - Trashed bool // The asset is trashed - Archived bool // The asset is archived - FromPartner bool // the asset comes from a partner - Favorite bool // the asset is marked as favorite - Rating int // the asset is marked with stars - Albums []Album // List of albums the asset is in - Tags []Tag // List of tags the asset is tagged with + CaptureDate time.Time // Date of the capture + Trashed bool // The asset is trashed + Archived bool // The asset is archived + FromPartner bool // the asset comes from a partner + FromSharedAlbum bool // the asset comes from a shared album + Favorite bool // the asset is marked as favorite + Rating int // the asset is marked with stars + Albums []Album // List of albums the asset is in + Tags []Tag // List of tags the asset is tagged with // Information inferred from the original file name NameInfo @@ -101,6 +102,7 @@ func (a *Asset) UseMetadata(md *Metadata) *Metadata { a.Longitude = md.Longitude a.CaptureDate = md.DateTaken a.FromPartner = md.FromPartner + a.FromSharedAlbum = md.FromSharedAlbum a.Trashed = md.Trashed a.Archived = md.Archived a.Favorite = md.Favorited @@ -127,6 +129,7 @@ func (a Asset) LogValue() slog.Value { slog.Bool("Trashed", a.Trashed), slog.Bool("Archived", a.Archived), slog.Bool("FromPartner", a.FromPartner), + slog.Bool("FromSharedAlbum", a.FromSharedAlbum), slog.Bool("Favorite", a.Favorite), slog.Int("Stars", a.Rating), slog.String("Latitude", fmt.Sprintf("%.0f.xxxxx", a.Latitude)), diff --git a/internal/assets/metadata.go b/internal/assets/metadata.go index f3a95a29..7c9f24b6 100644 --- a/internal/assets/metadata.go +++ b/internal/assets/metadata.go @@ -11,20 +11,21 @@ import ( ) type Metadata struct { - File fshelper.FSAndName `json:"-"` // File name and file system that holds the metadata. Could be empty - FileName string `json:"fileName,omitempty"` // File name as presented to users - Latitude float64 `json:"latitude,omitempty"` // GPS - Longitude float64 `json:"longitude,omitempty"` // GPS - FileDate time.Time `json:"fileDate,omitzero"` // Date of the file - DateTaken time.Time `json:"dateTaken,omitzero"` // Date of exposure - Description string `json:"description,omitempty"` // Long description - Albums []Album `json:"albums,omitempty"` // Used to list albums that contain the file - Tags []Tag `json:"tags,omitempty"` // Used to list tags - Rating byte `json:"rating,omitempty"` // 0 to 5 - Trashed bool `json:"trashed,omitempty"` // Flag to indicate if the image has been trashed - Archived bool `json:"archived,omitempty"` // Flag to indicate if the image has been archived - Favorited bool `json:"favorited,omitempty"` // Flag to indicate if the image has been favorited - FromPartner bool `json:"fromPartner,omitempty"` // Flag to indicate if the image is from a partner + File fshelper.FSAndName `json:"-"` // File name and file system that holds the metadata. Could be empty + FileName string `json:"fileName,omitempty"` // File name as presented to users + Latitude float64 `json:"latitude,omitempty"` // GPS + Longitude float64 `json:"longitude,omitempty"` // GPS + FileDate time.Time `json:"fileDate,omitzero"` // Date of the file + DateTaken time.Time `json:"dateTaken,omitzero"` // Date of exposure + Description string `json:"description,omitempty"` // Long description + Albums []Album `json:"albums,omitempty"` // Used to list albums that contain the file + Tags []Tag `json:"tags,omitempty"` // Used to list tags + Rating byte `json:"rating,omitempty"` // 0 to 5 + Trashed bool `json:"trashed,omitempty"` // Flag to indicate if the image has been trashed + Archived bool `json:"archived,omitempty"` // Flag to indicate if the image has been archived + Favorited bool `json:"favorited,omitempty"` // Flag to indicate if the image has been favorited + FromPartner bool `json:"fromPartner,omitempty"` // Flag to indicate if the image is from a partner + FromSharedAlbum bool `json:"fromSharedAlbum,omitempty"` // Flag to indicate if the image is from a shared album } func (m Metadata) LogValue() slog.Value { @@ -46,6 +47,7 @@ func (m Metadata) LogValue() slog.Value { slog.Bool("archived", m.Archived), slog.Bool("favorited", m.Favorited), slog.Bool("fromPartner", m.FromPartner), + slog.Bool("fromSharedAlbum", m.FromSharedAlbum), slog.Any("albums", m.Albums), slog.Any("tags", m.Tags), ) diff --git a/readme.md b/readme.md index aa1480a7..cbe1a3ec 100644 --- a/readme.md +++ b/readme.md @@ -401,6 +401,7 @@ The **from-google-photos** sub-command processes a Google Photos takeout archive | --include-extensions | `all` | Comma-separated list of extension to include. (e.g. .jpg, .heic) | | --include-type | `all` | Single file type to include. (`VIDEO` or `IMAGE`) | | -p, --include-partner | `TRUE` | Import photos from your partner's Google Photos account | +| --include-shared-album | `TRUE` | Import photos from others in shared albums in Google Photos | | -t, --include-trashed | `FALSE` | Import photos that are marked as trashed in Google Photos | | -u, --include-unmatched | `FALSE` | Import photos that do not have a matching JSON file in the takeout | | --include-untitled-albums | `FALSE` | Include photos from albums without a title in the import process | @@ -414,6 +415,7 @@ The **from-google-photos** sub-command processes a Google Photos takeout archive | --tag strings | | Add tags to the imported assets. Can be specified multiple times. Hierarchy is supported using a / separator (e.g. 'tag1/subtag1') | | --takeout-tag | `TRUE` | Tag uploaded photos with a tag "{takeout}/takeout-YYYYMMDDTHHMMSSZ" | | --people-tag | `TRUE` | Tag uploaded photos with tags \"people/name\" found in the JSON file | +| --shared-album-tag | `TRUE` | Tag photos from others in shared albums with \"From Shared Album\". | ## Google Photos Best Practices: