Simpler timelapse
This commit is contained in:
parent
b90cbb2b6c
commit
6c31eeb6b0
98
main.go
98
main.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
// Standard
|
// Standard
|
||||||
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -101,27 +102,6 @@ func sortFiles(a, b string) int { // {{{
|
|||||||
return 0
|
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) { // {{{
|
func filesToSequenceFile(fname string, files []string) { // {{{
|
||||||
out, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
out, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -144,43 +124,47 @@ func updateEXIFTimestamp(files []string) { // {{{
|
|||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
t := filenameToTime(f, true)
|
t := filenameToTime(f, true)
|
||||||
fmt.Printf("exif for %s\n", f)
|
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()
|
exiftool.Run()
|
||||||
}
|
}
|
||||||
} // }}}
|
} // }}}
|
||||||
func timelapse(date string, files []string) { // {{{
|
func timelapse(files []string) { // {{{
|
||||||
sequenceFile := fmt.Sprintf("/tmp/timelapse/sequence_%s", date)
|
sequenceFile := fmt.Sprintf("/tmp/timelapse_sequence")
|
||||||
videoFile := fmt.Sprintf("/tmp/timelapse/video_%s.mp4", date)
|
text := `drawtext=text='%{metadata\:ImageDescription}':fontsize=30:x=10:y=10:fontcolor=#71b045:bordercolor=black:borderw=1`
|
||||||
text := `drawtext=text='%{metadata\:DateTimeDigitized}':fontsize=30:x=10:y=10:fontcolor=#71b045:bordercolor=black:borderw=1`
|
|
||||||
|
|
||||||
filesToSequenceFile(sequenceFile, files)
|
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)
|
os.Remove(outFilename)
|
||||||
ffmpeg := exec.Command("ffmpeg", "-safe", "0", "-f", "concat", "-i", fname, "-c", "copy", outFilename)
|
ffmpeg := exec.Command("ffmpeg", "-r", framerate, "-safe", "0", "-f", "concat", "-i", sequenceFile, "-filter_complex", text, "-c:v", "libx264", "-crf", "23", "-progress", "-", "-nostats", outFilename)
|
||||||
out, err := ffmpeg.CombinedOutput()
|
|
||||||
|
progress, err := ffmpeg.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s\n", out)
|
|
||||||
panic(err)
|
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() {
|
func main() {
|
||||||
parseCommandLine()
|
parseCommandLine()
|
||||||
if version {
|
if version {
|
||||||
@ -190,21 +174,5 @@ func main() {
|
|||||||
|
|
||||||
files := findFiles(startDir)
|
files := findFiles(startDir)
|
||||||
slices.SortStableFunc(files, sortFiles)
|
slices.SortStableFunc(files, sortFiles)
|
||||||
sortedDays := separateDays(files)
|
timelapse(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)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user