Skip to content

Commit 65cd451

Browse files
committed
feat: add originalPath support with split functionality
- Add extractOriginalPath function with delimiter-based splitting - Support cross-platform path normalization (Windows \ to /) - Enable directory-based photo grouping and path component extraction - Add comprehensive tests for path splitting and stacking scenarios - Update documentation with originalPath examples and usage guide This allows users to group photos by directory structure and extract specific path components using the same split syntax as originalFileName.
1 parent 9a9165d commit 65cd451

3 files changed

Lines changed: 416 additions & 11 deletions

File tree

docs/features/custom-criteria.md

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ You can use any of these keys in your criteria:
3636
| Key | Description |
3737
| ------------------ | ------------------------------ |
3838
| `originalFileName` | Original filename of the asset |
39+
| `originalPath` | Original path of the asset |
3940
| `localDateTime` | Local capture time |
4041
| `fileCreatedAt` | File creation time |
4142
| `fileModifiedAt` | File modification time |
@@ -60,6 +61,25 @@ For example, with a file named `IMG_1234~edit.jpg`:
6061
1. Split on `~` and `.` gives `["IMG_1234", "edit", "jpg"]`
6162
2. Using `index: 0` selects `"IMG_1234"`
6263

64+
For paths, you can split by directory separators:
65+
66+
```json
67+
{
68+
"key": "originalPath",
69+
"split": {
70+
"delimiters": ["/"],
71+
"index": 2
72+
}
73+
}
74+
```
75+
76+
For a path like `photos/2023/vacation/IMG_001.jpg`:
77+
78+
1. Split on `/` gives `["photos", "2023", "vacation", "IMG_001.jpg"]`
79+
2. Using `index: 2` selects `"vacation"`
80+
81+
Note: The `originalPath` splitter automatically normalizes Windows-style backslashes (`\`) to forward slashes (`/`).
82+
6383
## Delta Configuration
6484

6585
The `delta` configuration allows for flexible time matching:
@@ -109,32 +129,44 @@ This is useful for:
109129
]
110130
```
111131

112-
### Combined Criteria
132+
### Directory-Based Grouping
113133

114134
```json
115135
[
116136
{
117-
"key": "originalFileName",
137+
"key": "originalPath",
118138
"split": {
119-
"delimiters": ["~", "."],
120-
"index": 0
139+
"delimiters": ["/"],
140+
"index": 2
121141
}
122-
},
142+
}
143+
]
144+
```
145+
146+
This will group photos by their directory name (e.g., all photos in the "vacation" directory will be grouped together).
147+
148+
### Combined Path and Time Criteria
149+
150+
```json
151+
[
123152
{
124-
"key": "localDateTime",
125-
"delta": {
126-
"milliseconds": 1000
153+
"key": "originalPath",
154+
"split": {
155+
"delimiters": ["/"],
156+
"index": 2
127157
}
128158
},
129159
{
130-
"key": "fileCreatedAt",
160+
"key": "localDateTime",
131161
"delta": {
132-
"milliseconds": 5000
162+
"milliseconds": 1000
133163
}
134164
}
135165
]
136166
```
137167

168+
This will group photos that are both in the same directory and taken within 1 second of each other.
169+
138170
## Best Practices
139171

140172
1. **Start Simple:**

pkg/stacker/criteria.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func applyCriteria(asset utils.TAsset, criteria []utils.TCriteria) ([]string, er
112112
return extractTimeWithDelta(a.LocalDateTime, c.Delta)
113113
},
114114
"originalFileName": extractOriginalFileName,
115-
"originalPath": func(a utils.TAsset, _ utils.TCriteria) (string, error) { return a.OriginalPath, nil },
115+
"originalPath": extractOriginalPath,
116116
"ownerId": func(a utils.TAsset, _ utils.TCriteria) (string, error) { return a.OwnerID, nil },
117117
"type": func(a utils.TAsset, _ utils.TCriteria) (string, error) { return a.Type, nil },
118118
"updatedAt": func(a utils.TAsset, c utils.TCriteria) (string, error) {
@@ -177,6 +177,40 @@ func extractOriginalFileName(asset utils.TAsset, c utils.TCriteria) (string, err
177177
return baseName, nil
178178
}
179179

180+
/**************************************************************************************************
181+
** extractOriginalPath extracts and processes the original path from an asset according
182+
** to the provided criteria. If the criteria include split parameters (delimiters and an
183+
** index), the path is split by those delimiters, and the part at the specified index
184+
** is returned. The function handles both forward slashes and backslashes as path
185+
** separators by always normalizing them to forward slashes.
186+
**
187+
** @param asset - The utils.TAsset from which to extract the original path.
188+
** @param c - The utils.TCriteria containing potential split parameters.
189+
** @return string - The processed original path (potentially split).
190+
** @return error - An error if the split index is out of range for the resulting parts,
191+
** or nil otherwise.
192+
**************************************************************************************************/
193+
func extractOriginalPath(asset utils.TAsset, c utils.TCriteria) (string, error) {
194+
// Always normalize path separators to forward slashes
195+
path := strings.ReplaceAll(asset.OriginalPath, "\\", "/")
196+
197+
if c.Split != nil && len(c.Split.Delimiters) > 0 {
198+
parts := []string{path}
199+
for _, delim := range c.Split.Delimiters {
200+
temp := []string{}
201+
for _, part := range parts {
202+
temp = append(temp, strings.Split(part, delim)...)
203+
}
204+
parts = temp
205+
}
206+
if c.Split.Index < 0 || c.Split.Index >= len(parts) {
207+
return "", fmt.Errorf("split index %d out of range for %q", c.Split.Index, path)
208+
}
209+
path = parts[c.Split.Index]
210+
}
211+
return path, nil
212+
}
213+
180214
/**************************************************************************************************
181215
** boolToString converts a boolean value to its string representation. It returns "true"
182216
** for a true input and "false" for a false input.

0 commit comments

Comments
 (0)