Compare commits

...

1 Commits
v1 ... main

Author SHA1 Message Date
Magnus Åhall
6c31eeb6b0 Simpler timelapse 2024-10-28 14:40:19 +01:00

98
main.go
View File

@ -2,6 +2,7 @@ package main
import (
// Standard
"bufio"
"flag"
"fmt"
"os"
@ -101,27 +102,6 @@ func sortFiles(a, b string) int { // {{{
return 0
} // }}}
func separateDays(files []string) *map[string][]string { // {{{
days := make(map[string][]string, 128)
var t time.Time
var date string
var fnames []string
var found bool
for _, f := range files {
t = filenameToTime(f, true)
date = t.Format("2006-01-02")
fnames, found = days[date]
if !found {
fnames = []string{}
}
fnames = append(fnames, f)
days[date] = fnames
}
return &days
} // }}}
func filesToSequenceFile(fname string, files []string) { // {{{
out, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
@ -144,43 +124,47 @@ func updateEXIFTimestamp(files []string) { // {{{
for _, f := range files {
t := filenameToTime(f, true)
fmt.Printf("exif for %s\n", f)
exiftool := exec.Command("exiftool", "-CreateDate="+t.Format("2006-01-02 15:04"), "-overwrite_original", f)
exiftool := exec.Command("exiftool", "-CreateDate="+t.Format("2006-01-02 15:04"), "-ImageDescription="+t.Format("2006-01-02 15"), "-overwrite_original", f)
exiftool.Run()
}
} // }}}
func timelapse(date string, files []string) { // {{{
sequenceFile := fmt.Sprintf("/tmp/timelapse/sequence_%s", date)
videoFile := fmt.Sprintf("/tmp/timelapse/video_%s.mp4", date)
text := `drawtext=text='%{metadata\:DateTimeDigitized}':fontsize=30:x=10:y=10:fontcolor=#71b045:bordercolor=black:borderw=1`
func timelapse(files []string) { // {{{
sequenceFile := fmt.Sprintf("/tmp/timelapse_sequence")
text := `drawtext=text='%{metadata\:ImageDescription}':fontsize=30:x=10:y=10:fontcolor=#71b045:bordercolor=black:borderw=1`
filesToSequenceFile(sequenceFile, files)
os.Remove(videoFile)
ffmpeg := exec.Command("ffmpeg", "-r", framerate, "-safe", "0", "-f", "concat", "-i", sequenceFile, "-filter_complex", text, "-c:v", "libx264", "-crf", "23", videoFile)
out, err := ffmpeg.CombinedOutput()
if err != nil {
fmt.Printf("%s\n", out)
panic(err)
}
} // }}}
func concatVideos(unsortedDates []string) { // {{{
fname := "/tmp/timelapse/sequence_all"
var videos []string
for _, date := range unsortedDates {
videos = append(videos, "/tmp/timelapse/video_"+date+".mp4")
}
slices.Sort(videos)
filesToSequenceFile(fname, videos)
os.Remove(outFilename)
ffmpeg := exec.Command("ffmpeg", "-safe", "0", "-f", "concat", "-i", fname, "-c", "copy", outFilename)
out, err := ffmpeg.CombinedOutput()
ffmpeg := exec.Command("ffmpeg", "-r", framerate, "-safe", "0", "-f", "concat", "-i", sequenceFile, "-filter_complex", text, "-c:v", "libx264", "-crf", "23", "-progress", "-", "-nostats", outFilename)
progress, err := ffmpeg.StdoutPipe()
if err != nil {
fmt.Printf("%s\n", out)
panic(err)
}
} // }}}
err = ffmpeg.Start()
if err != nil {
panic(err)
}
// Store position to overwrite progress later.
fmt.Print("\x1b[s")
rxp := regexp.MustCompile(`^frame=(\d+)$`)
ffmpegReader := bufio.NewScanner(progress)
var frameStr []string
var frame int
totalNumFrames := len(files)
for ffmpegReader.Scan() {
frameStr = rxp.FindStringSubmatch(ffmpegReader.Text())
if len(frameStr) < 2 {
continue
}
frame, _ = strconv.Atoi(frameStr[1])
fmt.Printf("\x1b[u%.0f%% ", float32(frame) / float32(totalNumFrames) * 100)
}
ffmpeg.Wait()
} // }}}
func main() {
parseCommandLine()
if version {
@ -190,21 +174,5 @@ func main() {
files := findFiles(startDir)
slices.SortStableFunc(files, sortFiles)
sortedDays := separateDays(files)
os.Mkdir("/tmp/timelapse", 0755)
var dates []string
for date, files := range *sortedDays {
fmt.Printf("Create timelapse for %s\n", date)
dates = append(dates, date)
// This is now done when downloading the image from the camera,
// since it takes too damn long time when running this program.
// It is preserved for the times when the source images are missing
// the EXIF data.
// updateEXIFTimestamp(files)
timelapse(date, files)
}
fmt.Println("Assemble day videos to complete timelapse")
concatVideos(dates)
timelapse(files)
}