Videos: Extract local time from DateTimeOriginal if possible #2640
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
e65c260656
commit
3403c50c48
|
@ -17,8 +17,9 @@ type Data struct {
|
|||
FileName string `meta:"FileName"`
|
||||
DocumentID string `meta:"BurstUUID,MediaGroupUUID,ImageUniqueID,OriginalDocumentID,DocumentID,DigitalImageGUID"`
|
||||
InstanceID string `meta:"InstanceID,DocumentID"`
|
||||
TakenAt time.Time `meta:"SubSecDateTimeOriginal,SubSecDateTimeCreated,SubSecCreateDate,DateTimeOriginal,CreationDate,CreateDate,MediaCreateDate,ContentCreateDate,DateTimeCreated,DateTime,DateTimeDigitized" xmp:"DateCreated"`
|
||||
TakenAtLocal time.Time `meta:"SubSecDateTimeOriginal,SubSecDateTimeCreated,SubSecCreateDate,DateTimeOriginal,CreationDate,CreateDate,MediaCreateDate,ContentCreateDate,DateTimeCreated,DateTime,DateTimeDigitized"`
|
||||
CreatedAt time.Time `meta:"SubSecCreateDate,CreationDate,CreateDate,MediaCreateDate,ContentCreateDate,TrackCreateDate,MetadataDate"`
|
||||
TakenAt time.Time `meta:"SubSecDateTimeOriginal,SubSecDateTimeCreated,DateTimeOriginal,CreationDate,DateTimeCreated,DateTime,DateTimeDigitized" xmp:"DateCreated"`
|
||||
TakenAtLocal time.Time `meta:"SubSecDateTimeOriginal,SubSecDateTimeCreated,DateTimeOriginal,CreationDate,DateTimeCreated,DateTime,DateTimeDigitized"`
|
||||
TakenGps time.Time `meta:"GPSDateTime,GPSDateStamp"`
|
||||
TakenNs int `meta:"-"`
|
||||
TimeZone string `meta:"-"`
|
||||
|
|
|
@ -33,8 +33,14 @@ func (data *Data) Exiftool(jsonData []byte, originalName string) (err error) {
|
|||
|
||||
j := gjson.GetBytes(jsonData, "@flatten|@join")
|
||||
|
||||
logName := "json file"
|
||||
|
||||
if originalName != "" {
|
||||
logName = clean.Log(filepath.Base(originalName))
|
||||
}
|
||||
|
||||
if !j.IsObject() {
|
||||
return fmt.Errorf("metadata: data is not an object in %s (exiftool)", clean.Log(filepath.Base(originalName)))
|
||||
return fmt.Errorf("metadata: data is not an object in %s (exiftool)", logName)
|
||||
}
|
||||
|
||||
data.json = make(map[string]string)
|
||||
|
@ -46,6 +52,8 @@ func (data *Data) Exiftool(jsonData []byte, originalName string) (err error) {
|
|||
|
||||
if fileName, ok := data.json["FileName"]; ok && fileName != "" && originalName != "" && fileName != originalName {
|
||||
return fmt.Errorf("metadata: original name %s does not match %s (exiftool)", clean.Log(originalName), clean.Log(fileName))
|
||||
} else if fileName != "" && originalName == "" {
|
||||
logName = clean.Log(filepath.Base(fileName))
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(data).Elem()
|
||||
|
@ -185,18 +193,34 @@ func (data *Data) Exiftool(jsonData []byte, originalName string) (err error) {
|
|||
|
||||
hasTimeOffset := false
|
||||
|
||||
// Fallback to GPS timestamp.
|
||||
// Has Media Create Date?
|
||||
if !data.CreatedAt.IsZero() {
|
||||
data.TakenAt = data.CreatedAt
|
||||
}
|
||||
|
||||
// Fallback to GPS UTC Time?
|
||||
if data.TakenAt.IsZero() && data.TakenAtLocal.IsZero() && !data.TakenGps.IsZero() {
|
||||
data.TimeZone = time.UTC.String()
|
||||
data.TakenAt = data.TakenGps.UTC()
|
||||
data.TakenAtLocal = time.Time{}
|
||||
}
|
||||
|
||||
// Check plausibility of the local <> UTC time difference.
|
||||
if !data.TakenAt.IsZero() && !data.TakenAtLocal.IsZero() {
|
||||
if d := data.TakenAt.Sub(data.TakenAtLocal).Abs(); d > time.Hour*27 {
|
||||
log.Warnf("metadata: invalid local time offset %.1fh in %s (exiftool)", d.Hours(), logName)
|
||||
data.TakenAtLocal = data.TakenAt
|
||||
data.TakenAt = data.TakenAt.UTC()
|
||||
}
|
||||
}
|
||||
|
||||
// Has time zone offset?
|
||||
if _, offset := data.TakenAtLocal.Zone(); offset != 0 && !data.TakenAtLocal.IsZero() {
|
||||
hasTimeOffset = true
|
||||
} else if mt, ok := data.json["MIMEType"]; ok && (mt == MimeVideoMP4 || mt == MimeQuicktime) {
|
||||
} else if mt, ok := data.json["MIMEType"]; ok && data.TakenAtLocal.IsZero() && (mt == MimeVideoMP4 || mt == MimeQuicktime) {
|
||||
// Assume default time zone for MP4 & Quicktime videos is UTC.
|
||||
// see https://exiftool.org/TagNames/QuickTime.html
|
||||
log.Debugf("metadata: using UTC as default time zone in %s (%s)", logName, clean.Log(mt))
|
||||
data.TimeZone = time.UTC.String()
|
||||
data.TakenAt = data.TakenAt.UTC()
|
||||
data.TakenAtLocal = time.Time{}
|
||||
|
|
|
@ -732,6 +732,26 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "", data.LensModel)
|
||||
})
|
||||
|
||||
t.Run("MVI_1724.MOV.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/MVI_1724.MOV.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, string(video.CodecAVC), data.Codec)
|
||||
assert.Equal(t, "6s", data.Duration.String())
|
||||
assert.Equal(t, "2022-06-25 06:50:58 +0000 UTC", data.TakenAtLocal.String())
|
||||
assert.Equal(t, "2022-06-25 04:50:58 +0000 UTC", data.TakenAt.String())
|
||||
assert.Equal(t, "", data.TimeZone) // Local Time
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, float32(0), data.Lat)
|
||||
assert.Equal(t, float32(0), data.Lng)
|
||||
assert.Equal(t, "Canon", data.CameraMake)
|
||||
assert.Equal(t, "Canon PowerShot G15", data.CameraModel)
|
||||
assert.Equal(t, "6.1", data.LensModel)
|
||||
})
|
||||
|
||||
t.Run("snow.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/snow.json", "")
|
||||
|
||||
|
@ -845,8 +865,9 @@ func TestJSON(t *testing.T) {
|
|||
// t.Logf("all: %+v", data.json)
|
||||
|
||||
assert.Equal(t, "Jens\r\tMander", data.Artist)
|
||||
assert.Equal(t, "2004-09-23T10:57:57Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "2004-09-23T10:57:57Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "", data.TimeZone)
|
||||
assert.Equal(t, "2004-10-07 20:49:16 +0000 UTC", data.TakenAt.String())
|
||||
assert.Equal(t, "2004-10-07 22:49:16 +0000 UTC", data.TakenAtLocal.String())
|
||||
assert.Equal(t, "This is the title", data.Title)
|
||||
assert.Equal(t, "", data.Keywords.String())
|
||||
assert.Equal(t, "This is a\n\ndescription!", data.Description)
|
||||
|
|
215
internal/meta/testdata/MVI_1724.MOV.json
vendored
Normal file
215
internal/meta/testdata/MVI_1724.MOV.json
vendored
Normal file
|
@ -0,0 +1,215 @@
|
|||
[{
|
||||
"SourceFile": "MVI_1724.MOV",
|
||||
"ExifToolVersion": 12.16,
|
||||
"FileName": "MVI_1724.MOV",
|
||||
"Directory": ".",
|
||||
"FileSize": 26861044,
|
||||
"FileModifyDate": "2022:06:25 06:50:58+02:00",
|
||||
"FileAccessDate": "2022:08:24 14:40:40+02:00",
|
||||
"FileInodeChangeDate": "2022:08:24 14:39:39+02:00",
|
||||
"FilePermissions": 664,
|
||||
"FileType": "MOV",
|
||||
"FileTypeExtension": "MOV",
|
||||
"MIMEType": "video/quicktime",
|
||||
"MajorBrand": "qt ",
|
||||
"MinorVersion": "2007.9.0",
|
||||
"CompatibleBrands": ["qt ","CAEP"],
|
||||
"CompressorVersion": "CanonAVC0010/02.00.00/00.00.00",
|
||||
"ExifByteOrder": "II",
|
||||
"ImageDescription": " ",
|
||||
"Orientation": 1,
|
||||
"ResolutionUnit": 2,
|
||||
"YCbCrPositioning": 2,
|
||||
"ExposureTime": 0.01666666667,
|
||||
"FNumber": 5.6,
|
||||
"SensitivityType": 4,
|
||||
"ExifVersion": "0230",
|
||||
"DateTimeOriginal": "2022:06:25 06:50:58",
|
||||
"ComponentsConfiguration": "1 2 3 0",
|
||||
"CompressedBitsPerPixel": 3,
|
||||
"ShutterSpeedValue": 0.0166740687605754,
|
||||
"ApertureValue": 5.59591869015324,
|
||||
"MaxApertureValue": 1.79470907500311,
|
||||
"Flash": 16,
|
||||
"FocalLength": 6.1,
|
||||
"MacroMode": 2,
|
||||
"SelfTimer": 0,
|
||||
"Quality": 130,
|
||||
"CanonFlashMode": 0,
|
||||
"ContinuousDrive": 2,
|
||||
"FocusMode": 4,
|
||||
"RecordMode": 9,
|
||||
"CanonImageSize": 142,
|
||||
"EasyMode": 1,
|
||||
"DigitalZoom": 0,
|
||||
"Contrast": 0,
|
||||
"Saturation": 0,
|
||||
"Sharpness": 0,
|
||||
"CameraISO": "Auto",
|
||||
"MeteringMode": 3,
|
||||
"FocusRange": 1,
|
||||
"AFPoint": 16390,
|
||||
"CanonExposureMode": 1,
|
||||
"LensType": 65535,
|
||||
"MaxFocalLength": 30.5,
|
||||
"MinFocalLength": 6.1,
|
||||
"FocalUnits": 1000,
|
||||
"MaxAperture": 1.79470907500311,
|
||||
"MinAperture": 8,
|
||||
"FlashActivity": 0,
|
||||
"FlashBits": 0,
|
||||
"FocusContinuous": 1,
|
||||
"AESetting": 0,
|
||||
"ImageStabilization": 260,
|
||||
"ZoomSourceWidth": 4000,
|
||||
"ZoomTargetWidth": 4000,
|
||||
"SpotMeteringMode": 0,
|
||||
"ManualFlashOutput": 0,
|
||||
"AutoISO": 37.7291106898356,
|
||||
"BaseISO": 200,
|
||||
"MeasuredEV": 11.5,
|
||||
"TargetAperture": 5.59591869015324,
|
||||
"TargetExposureTime": 0.0166740687605754,
|
||||
"ExposureCompensation": 0,
|
||||
"WhiteBalance": 0,
|
||||
"SlowShutter": 0,
|
||||
"SequenceNumber": 0,
|
||||
"OpticalZoomCode": 0,
|
||||
"FlashGuideNumber": 0,
|
||||
"FlashExposureComp": 0,
|
||||
"AutoExposureBracketing": 0,
|
||||
"AEBBracketValue": 0,
|
||||
"ControlMode": 1,
|
||||
"FocusDistanceUpper": 0.65,
|
||||
"FocusDistanceLower": 0,
|
||||
"BulbDuration": 0,
|
||||
"CameraType": 250,
|
||||
"AutoRotate": 0,
|
||||
"NDFilter": 0,
|
||||
"SelfTimer2": 0,
|
||||
"FlashOutput": 0,
|
||||
"CanonImageType": "MVI:PowerShot G15 Movie",
|
||||
"CanonFirmwareVersion": "Firmware Version 1.00",
|
||||
"FileNumber": 1551724,
|
||||
"CameraTemperature": 21,
|
||||
"CanonModelID": 53673984,
|
||||
"ThumbnailImageValidArea": "0 159 15 104",
|
||||
"DateStampMode": 0,
|
||||
"MyColorMode": 0,
|
||||
"FirmwareRevision": 16778496,
|
||||
"Categories": 0,
|
||||
"AFAreaMode": 2,
|
||||
"NumAFPoints": 9,
|
||||
"ValidAFPoints": 1,
|
||||
"CanonImageWidth": 1920,
|
||||
"CanonImageHeight": 1080,
|
||||
"AFImageWidth": 4000,
|
||||
"AFImageHeight": 3000,
|
||||
"AFAreaWidths": "720 18 18 18 18 2304 0 -14992 24172",
|
||||
"AFAreaHeights": "540 240 240 240 240 0 19 3739 542",
|
||||
"AFAreaXPositions": "0 18 -18 0 18 18 18 18 18",
|
||||
"AFAreaYPositions": "0 0 240 240 240 240 240 240 240",
|
||||
"AFPointsInFocus": 16,
|
||||
"PrimaryAFPoint": 4,
|
||||
"IntelligentContrast": 0,
|
||||
"ImageUniqueID": "a5a750522f844e7b06168799cbbb9dd5",
|
||||
"FacesDetected": 65535,
|
||||
"TimeZone": 120,
|
||||
"TimeZoneCity": 32766,
|
||||
"DaylightSavings": 60,
|
||||
"AspectRatio": 7,
|
||||
"CroppedImageWidth": 1920,
|
||||
"CroppedImageHeight": 1080,
|
||||
"CroppedImageLeft": 0,
|
||||
"CroppedImageTop": 0,
|
||||
"VRDOffset": 0,
|
||||
"UserComment": "",
|
||||
"FlashpixVersion": "0100",
|
||||
"ColorSpace": 1,
|
||||
"ExifImageWidth": 160,
|
||||
"ExifImageHeight": 120,
|
||||
"InteropIndex": "THM",
|
||||
"InteropVersion": "0100",
|
||||
"RelatedImageWidth": 1920,
|
||||
"RelatedImageHeight": 1080,
|
||||
"SensingMethod": 2,
|
||||
"FileSource": 3,
|
||||
"CustomRendered": 0,
|
||||
"ExposureMode": 0,
|
||||
"DigitalZoomRatio": 1,
|
||||
"SceneCaptureType": 0,
|
||||
"OwnerName": "",
|
||||
"GPSVersionID": "2 3 0 0",
|
||||
"EncodingProcess": 0,
|
||||
"BitsPerSample": 8,
|
||||
"ColorComponents": 3,
|
||||
"YCbCrSubSampling": "2 1",
|
||||
"ThumbnailImage": "(Binary data 9850 bytes, use -b option to extract)",
|
||||
"Make": "Canon",
|
||||
"Model": "Canon PowerShot G15",
|
||||
"UserRating": 0,
|
||||
"Copyright": " ",
|
||||
"Author": " ",
|
||||
"MovieHeaderVersion": 0,
|
||||
"CreateDate": "2022:06:25 04:50:58",
|
||||
"ModifyDate": "2022:06:25 04:50:58",
|
||||
"TimeScale": 24000,
|
||||
"Duration": 6.08941666666667,
|
||||
"PreferredRate": 1,
|
||||
"PreferredVolume": 1,
|
||||
"PreviewTime": 0,
|
||||
"PreviewDuration": 0,
|
||||
"PosterTime": 0,
|
||||
"SelectionTime": 0,
|
||||
"SelectionDuration": 0,
|
||||
"CurrentTime": 0,
|
||||
"NextTrackID": 3,
|
||||
"TrackHeaderVersion": 0,
|
||||
"TrackCreateDate": "2022:06:25 04:50:58",
|
||||
"TrackModifyDate": "2022:06:25 04:50:58",
|
||||
"TrackID": 1,
|
||||
"TrackDuration": 6.08941666666667,
|
||||
"TrackLayer": 0,
|
||||
"TrackVolume": 0,
|
||||
"ImageWidth": 1920,
|
||||
"ImageHeight": 1080,
|
||||
"GraphicsMode": 0,
|
||||
"OpColor": "0 0 0",
|
||||
"CompressorID": "avc1",
|
||||
"SourceImageWidth": 1920,
|
||||
"SourceImageHeight": 1080,
|
||||
"XResolution": 72,
|
||||
"YResolution": 72,
|
||||
"BitDepth": 24,
|
||||
"VideoFrameRate": 23.976023976024,
|
||||
"MatrixStructure": "1 0 0 0 1 0 0 0 1",
|
||||
"MediaHeaderVersion": 0,
|
||||
"MediaCreateDate": "2022:06:25 04:50:58",
|
||||
"MediaModifyDate": "2022:06:25 04:50:58",
|
||||
"MediaTimeScale": 48000,
|
||||
"MediaDuration": 6.08941666666667,
|
||||
"Balance": 0,
|
||||
"HandlerClass": "dhlr",
|
||||
"HandlerType": "alis",
|
||||
"AudioFormat": "sowt",
|
||||
"AudioBitsPerSample": 16,
|
||||
"AudioSampleRate": 48000,
|
||||
"LayoutFlags": 101,
|
||||
"AudioChannels": 2,
|
||||
"MediaDataSize": 26762732,
|
||||
"MediaDataOffset": 98312,
|
||||
"DriveMode": 0,
|
||||
"ISO": 75.4582213796711,
|
||||
"Lens": 6.1,
|
||||
"ShootingMode": 1,
|
||||
"Aperture": 5.6,
|
||||
"ImageSize": "1920 1080",
|
||||
"LensID": 65535,
|
||||
"Megapixels": 2.0736,
|
||||
"ShutterSpeed": 0.01666666667,
|
||||
"AvgBitrate": 35159666,
|
||||
"Rotation": 0,
|
||||
"Lens35efl": 6.1,
|
||||
"FocalLength35efl": 6.1,
|
||||
"LightValue": 11.2927817489393
|
||||
}]
|
Loading…
Reference in New Issue
Block a user