diff --git a/pkg/dji/dji.go b/pkg/dji/dji.go index ef0c0e5..5d497d2 100644 --- a/pkg/dji/dji.go +++ b/pkg/dji/dji.go @@ -6,18 +6,17 @@ import ( "os" "path/filepath" "regexp" - "strings" "sync" "time" "github.com/dustin/go-humanize" "github.com/fatih/color" "github.com/karrick/godirwalk" + "github.com/konradit/mmt/pkg/media" "github.com/konradit/mmt/pkg/utils" "github.com/minio/minio/pkg/disk" "github.com/rwcarlsen/goexif/exif" "github.com/vbauerster/mpb/v8" - "gopkg.in/djherbis/times.v1" ) func getDeviceNameFromPhoto(path string) (string, error) { //nolint:unused @@ -97,19 +96,11 @@ func (Entrypoint) Import(params utils.ImportParams) (*utils.Result, error) { if !ftype.Regex.MatchString(de.Name()) { continue } - t, err := times.Stat(osPathname) - if err != nil { - return godirwalk.SkipThis - } - d := t.ModTime() - mediaDate := d.Format("02-01-2006") - if strings.Contains(params.DateFormat, "yyyy") && strings.Contains(params.DateFormat, "mm") && strings.Contains(params.DateFormat, "dd") { - mediaDate = d.Format(utils.DateFormatReplacer.Replace(params.DateFormat)) - } + d := media.GetFileTime(osPathname, false, false) + mediaDate := media.GetMediaDate(d, params.DateFormat) // check if is in date range - if d.Before(params.DateRange[0]) || d.After(params.DateRange[1]) { return godirwalk.SkipThis } diff --git a/pkg/gopro/gopro.go b/pkg/gopro/gopro.go index ca1e535..23f636f 100644 --- a/pkg/gopro/gopro.go +++ b/pkg/gopro/gopro.go @@ -18,11 +18,11 @@ import ( "github.com/fatih/color" "github.com/karrick/godirwalk" mErrors "github.com/konradit/mmt/pkg/errors" + "github.com/konradit/mmt/pkg/media" "github.com/konradit/mmt/pkg/utils" "github.com/maja42/goval" "github.com/minio/minio/pkg/disk" "github.com/vbauerster/mpb/v8" - "gopkg.in/djherbis/times.v1" ) /* @@ -159,8 +159,8 @@ folderLoop: continue fileTypeLoop } - d := getFileTime(osPathname, true) - mediaDate := getMediaDate(getFileTime(osPathname, true), params.DateFormat) + d := media.GetFileTime(osPathname, true, true) + mediaDate := media.GetMediaDate(media.GetFileTime(osPathname, true, true), params.DateFormat) if d.Before(params.DateRange[0]) || d.After(params.DateRange[1]) { return godirwalk.SkipThis @@ -336,8 +336,8 @@ func importFromGoProV1(params utils.ImportParams) utils.Result { continue } - d := getFileTime(osPathname, true) - mediaDate := getMediaDate(d, params.DateFormat) + d := media.GetFileTime(osPathname, true, true) + mediaDate := media.GetMediaDate(d, params.DateFormat) if d.Before(params.DateRange[0]) || d.After(params.DateRange[1]) { return godirwalk.SkipThis @@ -550,29 +550,6 @@ func readInfo(inBytes []byte) (*Info, error) { return &gpVersion, nil } -func getFileTime(osPathname string, utcFix bool) time.Time { - var d time.Time - t, err := times.Stat(osPathname) - if err != nil { - log.Fatal(err.Error()) - } - d = t.ModTime() - if utcFix { - zoneName, _ := d.Zone() - newTime := strings.Replace(d.Format(time.UnixDate), zoneName, "UTC", -1) - d, _ = time.Parse(time.UnixDate, newTime) - } - return d -} - -func getMediaDate(d time.Time, dateFormat string) string { - mediaDate := d.Format("02-01-2006") - if strings.Contains(dateFormat, "yyyy") && strings.Contains(dateFormat, "mm") && strings.Contains(dateFormat, "dd") { - mediaDate = d.Format(utils.DateFormatReplacer.Replace(dateFormat)) - } - return mediaDate -} - func parse(folder string, name string, osPathname string, bufferSize int, bar *mpb.Bar, modTime time.Time) error { if _, err := os.Stat(folder); os.IsNotExist(err) { mkdirerr := os.MkdirAll(folder, 0o755) diff --git a/pkg/insta360/insta360.go b/pkg/insta360/insta360.go index 14195ae..f2c02b6 100644 --- a/pkg/insta360/insta360.go +++ b/pkg/insta360/insta360.go @@ -8,17 +8,16 @@ import ( "os" "path/filepath" "regexp" - "strings" "sync" "time" "github.com/dustin/go-humanize" "github.com/fatih/color" "github.com/karrick/godirwalk" + "github.com/konradit/mmt/pkg/media" "github.com/konradit/mmt/pkg/utils" "github.com/minio/minio/pkg/disk" "github.com/vbauerster/mpb/v8" - "gopkg.in/djherbis/times.v1" ) func getDeviceName(manifest string) string { @@ -91,20 +90,11 @@ func (Entrypoint) Import(params utils.ImportParams) (*utils.Result, error) { if !ftype.Regex.MatchString(de.Name()) { continue } - t, err := times.Stat(osPathname) - if err != nil { - return godirwalk.SkipThis - } - d := t.ModTime() - - mediaDate := d.Format("02-01-2006") - if strings.Contains(params.DateFormat, "yyyy") && strings.Contains(params.DateFormat, "mm") && strings.Contains(params.DateFormat, "dd") { - mediaDate = d.Format(utils.DateFormatReplacer.Replace(params.DateFormat)) - } + d := media.GetFileTime(osPathname, false, false) + mediaDate := media.GetMediaDate(d, params.DateFormat) // check if is in date range - if d.Before(params.DateRange[0]) || d.After(params.DateRange[1]) { return godirwalk.SkipThis } diff --git a/pkg/media/dates.go b/pkg/media/dates.go new file mode 100644 index 0000000..ba55c91 --- /dev/null +++ b/pkg/media/dates.go @@ -0,0 +1,181 @@ +package media + +import ( + "bytes" + "fmt" + "io" + "log" + "os" + "path/filepath" + "strings" + "time" + + "github.com/konradit/gopro-utils/telemetry" + "github.com/konradit/mmt/pkg/utils" + "github.com/konradit/mmt/pkg/videomanipulation" + "github.com/rwcarlsen/goexif/exif" + "gopkg.in/djherbis/times.v1" +) + +func getGPSTime(x *exif.Exif, date *time.Time) bool { + var gpsDateTime string + + gpsDateStamp, err := x.Get(exif.GPSDateStamp) + if err != nil { + return false + } + gpsTimeStamp, err := x.Get(exif.GPSTimeStamp) + if err != nil { + return false + } + + gpsD, err := gpsDateStamp.StringVal() + if err != nil { + return false + } + + // convert rational to string + gpsH, _, err := gpsTimeStamp.Rat2(0) + if err != nil { + return false + } + gpsM, _, err := gpsTimeStamp.Rat2(1) + if err != nil { + return false + } + gpsS, _, err := gpsTimeStamp.Rat2(2) + if err != nil { + return false + } + + gpsDateTime = fmt.Sprintf("%s %02d:%02d:%02d", gpsD, gpsH, gpsM, gpsS) + + // parse the string into time + d, err := time.Parse("2006:01:02 15:04:05", gpsDateTime) + if err == nil { + *date = d + return true + } + + return false +} + +func getFileTimeExif(osPathname string, goPro bool) time.Time { + var date time.Time + + if strings.Contains(osPathname, ".WAV") { + osPathname = osPathname[:len(osPathname)-len(filepath.Ext(osPathname))] + ".MP4" + } + + t, err := times.Stat(osPathname) + if err != nil { + log.Fatal(err.Error()) + } + + d := t.ModTime() + + // First search in gps track + if goPro && strings.Contains(osPathname, ".MP4") { + if getTimeFromMP4(osPathname, &date) { + return date + } + } + + f, err := os.Open(osPathname) + if err != nil { + return d + } + defer f.Close() + x, err := exif.Decode(f) + if err != nil { + return d + } + + if getGPSTime(x, &date) { + return date + } + + // define the list of possible tags to extract date from + dateTags := []string{"DateTimeOriginal", "DateTime", "DateTimeDigitized"} + + // loop for each tag and return the first valid date + for _, tag := range dateTags { + // get value of tag from exif + tt, err := x.Get(exif.FieldName(tag)) + if err != nil { + tts, _ := tt.StringVal() + date, err = time.Parse("2006:01:02 15:04:05", tts) + if err != nil { + continue + } + return date + } + } + + return d +} + +func getTimeFromMP4(videoPath string, date *time.Time) bool { + vman := videomanipulation.New() + data, err := vman.ExtractGPMF(videoPath) + if err != nil { + return false + } + + reader := bytes.NewReader(*data) + + lastEvent := &telemetry.TELEM{} + + for { + event, err := telemetry.Read(reader) + if err != nil && err != io.EOF { + return false + } else if err == io.EOF || event == nil { + break + } + + if lastEvent.IsZero() { + *lastEvent = *event + event.Clear() + continue + } + + err = lastEvent.FillTimes(event.Time.Time) + if err != nil { + return false + } + + telems := lastEvent.ShitJson() + for _, telem := range telems { + if telem.Latitude != 0 && telem.Longitude != 0 { + *date = time.UnixMicro(telem.TS) + + return true + } + } + *lastEvent = *event + } + + return false +} + +func GetFileTime(osPathname string, utcFix bool, goPro bool) time.Time { + t := getFileTimeExif(osPathname, goPro) + + if utcFix { + zoneName, _ := t.Zone() + newTime := strings.Replace(t.Format(time.UnixDate), zoneName, "UTC", -1) + t, _ = time.Parse(time.UnixDate, newTime) + } + + return t +} + +func GetMediaDate(d time.Time, dateFormat string) string { + mediaDate := d.Format("02-01-2006") + if strings.Contains(dateFormat, "yyyy") && strings.Contains(dateFormat, "mm") && strings.Contains(dateFormat, "dd") { + mediaDate = d.Format(utils.DateFormatReplacer.Replace(dateFormat)) + } + + return mediaDate +}