Initial import.

This commit is contained in:
Kyle Isom 2022-02-22 21:14:47 -08:00
commit a31c696733
10 changed files with 850 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.jpg
*.avi
*.zip

24
README.md Normal file
View File

@ -0,0 +1,24 @@
lifecam
=======
I was reading a [blog post](https://writings.stephenwolfram.com/2019/02/seeking-the-productive-life-some-details-of-my-personal-infrastructure/) by Stephen Wolfram:
> But when I go to events like trade shows I typically bring a tiny
> camera with me, that takes a picture every half-minute. If Im
> wearing one of those lanyard name tags Ill typically clip the
> camera on the top of the name tag, among other things putting it at
> an ideal height to capture name tags of people I meet. When I write
> my personal trip report Ill typically review the pictures, and
> sometimes copy a few into my trip notebook.
I thought it would be cool to build something like this, and maybe use
the photos to do some interesting photo analysis. I picked up an
[OpenMV H7 R2](https://www.sparkfun.com/products/18982) with a
[wide-angle lens](https://www.sparkfun.com/products/16778). I wrote a
[quick little program](main.py) to take a picture every thirty seconds
and write it to an image directory. This code collects those images
into a video.
I haven't used this for anything yet on account of not having a
case. I might try to replicate this with an OAK-D for the inside of my
house.

34
go.mod Normal file
View File

@ -0,0 +1,34 @@
module git.wntrmute.dev/kyle/lifecam
go 1.17
require (
git.sr.ht/~kisom/goutils v1.5.3
github.com/disintegration/imaging v1.6.2
github.com/icza/mjpeg v0.0.0-20210726201846-5ff75d3c479f
github.com/minio/minio-go/v7 v7.0.23
)
require (
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/gopherjs/gopherjs v0.0.0-20220221023154-0b2280d3ff96 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/klauspost/compress v1.13.5 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/minio/md5-simd v1.1.0 // indirect
github.com/minio/sha256-simd v0.1.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/rs/xid v1.2.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/smartystreets/assertions v1.2.1 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/text v0.3.3 // indirect
gopkg.in/ini.v1 v1.57.0 // indirect
)

149
go.sum Normal file
View File

@ -0,0 +1,149 @@
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4=
git.sr.ht/~kisom/goutils v1.5.3 h1:Q0BI2Q8/gs5uYdi2e2op4uRt9hTLt5zgH9HdGDcx4pM=
git.sr.ht/~kisom/goutils v1.5.3/go.mod h1:azq1B7nq8lmva1FWeuw+tZ6xdJZCtJIlFskB3N2v9E4=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
github.com/cloudflare/cfssl v1.5.0/go.mod h1:sPPkBS5L8l8sRc/IOO1jG51Xb34u+TYhL6P//JdODMQ=
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20220221023154-0b2280d3ff96 h1:QJq7UBOuoynsywLk+aC75rC2Cbi2+lQRDaLaizhA+fA=
github.com/gopherjs/gopherjs v0.0.0-20220221023154-0b2280d3ff96/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/icza/mjpeg v0.0.0-20210726201846-5ff75d3c479f h1:907GjB0sqx3+XKOz5tSeAi8Z9ED7WwQU9hXoclDp+jg=
github.com/icza/mjpeg v0.0.0-20210726201846-5ff75d3c479f/go.mod h1:Eja3x31oRrEOzl6ihhsxY23gXaTYWLP3Gwj5nMAJ7m0=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
github.com/minio/minio-go/v7 v7.0.23 h1:NleyGQvAn9VQMU+YHVrgV4CX+EPtxPt/78lHOOTncy4=
github.com/minio/minio-go/v7 v7.0.23/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2DAn1ou4+Do=
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v1.2.1 h1:bKNHfEv7tSIjZ8JbKaFjzFINljxG4lzZvmHUnElzOIg=
github.com/smartystreets/assertions v1.2.1/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
github.com/zmap/zcrypto v0.0.0-20200513165325-16679db567ff/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4=
github.com/zmap/zcrypto v0.0.0-20200911161511-43ff0ea04f21/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4=
github.com/zmap/zlint/v2 v2.2.1/go.mod h1:ixPWsdq8qLxYRpNUTbcKig3R7WgmspsHGLhCCs6rFAM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

73
lifecam.go Normal file
View File

@ -0,0 +1,73 @@
package main
import (
"flag"
"log"
"os"
"path/filepath"
"git.sr.ht/~kisom/goutils/config"
"git.sr.ht/~kisom/goutils/die"
"git.wntrmute.dev/kyle/lifecam/lifecam"
)
var defaultConfigFile = config.DefaultConfigPath("lifecam", "lifecam.conf")
func main() {
var err error
var minio *lifecam.Server
clean := flag.Bool("c", false, "remove files after zipping")
cfgFile := flag.String("f", defaultConfigFile, "path to config file")
upload := flag.Bool("u", false, "upload zipfile to S3")
flag.Parse()
if *cfgFile != "" {
err := config.LoadFile(*cfgFile)
if err != nil {
if !os.IsNotExist(err) {
die.If(err)
}
} else {
log.Println("read config file", *cfgFile)
}
}
if *upload {
minio, err = lifecam.NewMinio()
die.If(err)
}
paths := flag.Args()
if len(paths) == 0 {
if defaultPath := config.Get("DEFAULT_IMAGE_DIR"); defaultPath != "" {
paths = append(paths, defaultPath)
} else {
log.Println("nothing to do")
}
}
var frameMap map[int]lifecam.FrameList
for _, path := range paths {
log.Println("loading all frames in", path)
path = filepath.Clean(path)
frameMap, err = lifecam.LoadDir(path, frameMap)
die.If(err)
}
for _, fl := range frameMap {
pkg, err := fl.Package()
die.If(err)
err = lifecam.PackageZip(pkg, *clean)
die.If(err)
if *upload {
path, err := pkg.Frames.Name()
die.If(err)
n, err := minio.Upload(path, lifecam.MimeZip)
die.If(err)
log.Printf("uploaded %s [%d bytes]", path, n)
}
}
log.Println("complete")
}

29
lifecam/image.go Normal file
View File

@ -0,0 +1,29 @@
package lifecam
import (
"image"
"os"
"github.com/disintegration/imaging"
)
func getImageBounds(img image.Image) (w, h int) {
edges := img.Bounds().Max
return edges.X, edges.Y
}
func getImageSize(path string) (w, h int, err error) {
file, err := os.Open(path)
if err != nil {
return
}
defer file.Close()
img, err := imaging.Decode(file, imaging.AutoOrientation(true))
if err != nil {
return 0, 0, err
}
w, h = getImageBounds(img)
return w, h, nil
}

212
lifecam/mjpeg.go Normal file
View File

@ -0,0 +1,212 @@
package lifecam
import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/icza/mjpeg"
)
const (
fps = 1
)
type Frame struct {
Session int
Frame int
Path string
Width, Height int
}
func (f *Frame) String() string {
return fmt.Sprintf("[FRAME] S: %02d F: %04d P: %s",
f.Session, f.Frame, f.Path)
}
func FrameFromPath(path string) (*Frame, error) {
names := strings.Split(path, "-")
if len(names) != 3 {
return nil, fmt.Errorf("lifecam: %s isn't a lifecam frame", path)
}
f := &Frame{
Path: path,
}
var err error
f.Session, err = strconv.Atoi(names[1])
if err != nil {
return nil, err
}
names[2] = strings.TrimSuffix(names[2], filepath.Ext(names[2]))
f.Frame, err = strconv.Atoi(names[2])
if err != nil {
return nil, err
}
f.Width, f.Height, err = getImageSize(path)
if err != nil {
return nil, err
}
return f, nil
}
var (
ErrEmptyFrameList = errors.New("lifecam: empty framelist")
ErrFrameListSequence = errors.New("lifecam: framelist out of sequence")
ErrFrameListSession = errors.New("lifecam: mismatched sessions in framelist")
ErrFrameDimensions = errors.New("lifecam: frames have mismatched dimensions")
)
type FrameList []*Frame
func (fl FrameList) Check() error {
if len(fl) == 0 {
return ErrEmptyFrameList
}
sort.Sort(fl)
for i := 1; i < len(fl); i++ {
if fl[i].Frame <= fl[i-1].Frame {
return ErrFrameListSequence
}
if fl[i].Session != fl[i-1].Session {
return ErrFrameListSession
}
if fl[i].Width != fl[i-1].Width {
return ErrFrameDimensions
}
if fl[i].Height != fl[i-1].Height {
return ErrFrameDimensions
}
}
return nil
}
func (fl FrameList) String() string {
if len(fl) == 0 {
return "empty framelist"
}
return fmt.Sprintf("S: %d F: %d", fl[0].Session, len(fl))
}
func (fl FrameList) Len() int {
return len(fl)
}
func (fl FrameList) Less(i, j int) bool {
return fl[i].Frame < fl[j].Frame
}
func (fl FrameList) Swap(i, j int) {
fl[i], fl[j] = fl[j], fl[i]
}
func (fl FrameList) Name() (string, error) {
fi, err := os.Stat(fl[0].Path)
if err != nil {
panic(err.Error())
}
ts := fi.ModTime().Format("2006-01-02_1534")
return fmt.Sprintf("lifecam_%s_%02d_%04d.avi",
ts, fl[0].Session, len(fl)), nil
}
func LoadDir(imageDir string, frameMap map[int]FrameList) (map[int]FrameList, error) {
if frameMap == nil {
frameMap = map[int]FrameList{}
}
dir, err := os.ReadDir(imageDir)
if err != nil {
return nil, err
}
for _, dirent := range dir {
path := filepath.Join(imageDir, dirent.Name())
fi, err := os.Stat(path)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
continue
}
f, err := FrameFromPath(path)
if err != nil {
return nil, err
}
frameMap[f.Session] = append(frameMap[f.Session], f)
}
return frameMap, nil
}
func (fl FrameList) ToMJPEG(outPath string) error {
err := fl.Check()
if err != nil {
return err
}
width := int32(fl[0].Width)
height := int32(fl[0].Height)
aviFile, err := mjpeg.New(outPath, width, height, fps)
if err != nil {
return err
}
defer aviFile.Close()
for _, frame := range fl {
img, err := ioutil.ReadFile(frame.Path)
if err != nil {
return err
}
if err = aviFile.AddFrame(img); err != nil {
return err
}
}
log.Printf("encoded %s", outPath)
return aviFile.Close()
}
type Package struct {
Frames FrameList
Video string
}
func (fl FrameList) Package() (*Package, error) {
path, err := fl.Name()
if err != nil {
return nil, err
}
err = fl.ToMJPEG(path)
if err != nil {
return nil, err
}
return &Package{
Frames: fl,
Video: path,
}, nil
}

81
lifecam/s3.go Normal file
View File

@ -0,0 +1,81 @@
package lifecam
import (
"context"
"fmt"
"log"
"path/filepath"
"strings"
"git.sr.ht/~kisom/goutils/config"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
const MimeZip = "application/zip"
type Server struct {
client *minio.Client
bucket string
}
func NewMinio() (*Server, error) {
var fields []string
server := config.Get("MINIO_SERVER")
if server == "" {
fields = append(fields, "MINIO_SERVER")
}
access := config.Get("MINIO_ACCESS")
if access == "" {
fields = append(fields, "MINIO_ACCESS")
}
secret := config.Get("MINIO_SECRET")
if secret == "" {
fields = append(fields, "MINIO_SECRET")
}
bucket := config.Get("MINIO_BUCKET")
if bucket == "" {
fields = append(fields, "MINIO_BUCKET")
}
srv := &Server{
bucket: bucket,
}
if len(fields) != 0 {
err := fmt.Errorf("lifecam: missing fields in S3 config: %s",
strings.Join(fields, ", "))
return nil, err
}
var err error
srv.client, err = minio.New(server, &minio.Options{
Creds: credentials.NewStaticV4(access, secret, ""),
Secure: true,
})
if err != nil {
return nil, err
}
return srv, nil
}
func (srv *Server) Upload(path, contentType string) (int64, error) {
log.Printf("uploading %s to minio", path)
ui, err := srv.client.FPutObject(
context.Background(),
srv.bucket,
filepath.Base(path),
path,
minio.PutObjectOptions{
ContentType: contentType,
})
if err != nil {
return 0, err
}
return ui.Size, nil
}

158
lifecam/zip.go Normal file
View File

@ -0,0 +1,158 @@
package lifecam
import (
"archive/zip"
"io"
"log"
"os"
"path/filepath"
"strings"
)
func zipName(p string, top bool) string {
if top {
return filepath.Base(p)
}
return filepath.Join("frames", filepath.Base(p))
}
func pathHeader(p string, top bool) (*zip.FileHeader, error) {
fi, err := os.Stat(p)
if err != nil {
return nil, err
}
h, err := zip.FileInfoHeader(fi)
if err != nil {
return nil, err
}
h.Name = zipName(p, top)
h.Method = zip.Deflate
return h, nil
}
type zipFile struct {
path string
file *os.File
zw *zip.Writer
err error
clean bool
zipped []string
}
func (zf *zipFile) Close() error {
zf.zw.Close()
err := zf.file.Close()
if zf.err != nil {
os.Remove(zf.path)
}
if zf.clean {
zf.Clean()
}
return err
}
func (zf *zipFile) Clean() (err error) {
targets := zf.zipped[:]
for i := range targets {
err = os.Remove(targets[i])
if err != nil {
zf.zipped = zf.zipped[i:]
break
}
log.Println("removed", targets[i])
zf.zipped = zf.zipped[1:]
}
if len(zf.zipped) != 0 {
log.Printf("failed to remove %s",
strings.Join(zf.zipped, " "))
return err
}
return nil
}
func (zf *zipFile) addPath(path string, top bool) error {
var (
f *os.File
h *zip.FileHeader
w io.Writer
)
h, zf.err = pathHeader(path, top)
if zf.err != nil {
return zf.err
}
log.Println("zipping", h.Name)
w, zf.err = zf.zw.CreateHeader(h)
if zf.err != nil {
return zf.err
}
f, zf.err = os.Open(path)
if zf.err != nil {
return zf.err
}
defer f.Close()
_, zf.err = io.Copy(w, f)
if zf.err != nil {
return zf.err
}
zf.zipped = append(zf.zipped, path)
return nil
}
func (zf *zipFile) AddVideo(path string) error {
return zf.addPath(path, true)
}
func (zf *zipFile) AddFrame(path string) error {
return zf.addPath(path, false)
}
func newZipFile(path string, clean bool) (*zipFile, error) {
zf := &zipFile{path: path, clean: clean}
zf.file, zf.err = os.Create(path)
if zf.err != nil {
return nil, zf.err
}
zf.zw = zip.NewWriter(zf.file)
return zf, nil
}
func PackageZip(p *Package, clean bool) (err error) {
path := strings.TrimSuffix(p.Video,
filepath.Ext(p.Video)) + ".zip"
zf, err := newZipFile(path, clean)
if err != nil {
return err
}
defer zf.Close()
err = zf.AddVideo(p.Video)
if err != nil {
return err
}
for _, frame := range p.Frames {
err = zf.AddFrame(frame.Path)
if err != nil {
return err
}
}
zf.zw.Close()
if clean {
err = zf.Clean()
}
return err
}

87
main.py Normal file
View File

@ -0,0 +1,87 @@
# Hello World Example
#
# Welcome to the OpenMV IDE! Click on the green run arrow button below to run the script!
import sensor
import image
import pyb
import time
import uos
blue = pyb.LED(3)
green = pyb.LED(2)
sleep_cycle = 30
def reset():
sensor.reset() # Reset and initialize the sensor.
sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565 (or GRAYSCALE)
sensor.set_framesize(sensor.VGA) # Set frame size to QVGA (320x240)
sensor.skip_frames(time = 2000) # Wait for settings take effect.
# if the fs is <= 1M in size, it's probably flash and we don't want to
# write images.
def sd_is_mounted():
st = uos.statvfs('.')
sz = st[1] * st[2]
return sz > 1048576
def image_path(sess, imno):
return 'images/image-%02d-%04d.jpg' % (sess, imno)
def check_dir():
try:
uos.stat('images')
except OSError:
uos.mkdir('images')
def find_session():
sess = -1
for dir in uos.listdir('images'):
try:
_, imsess, _ = dir.split('-')
except ValueError:
continue
imsess = int(imsess)
if imsess > sess:
sess = imsess
return sess + 1
def snapshot(sess, imno):
path = image_path(sess, imno)
green.on()
img = sensor.snapshot()
green.off()
img.save(path)
print('wrote path', path)
return imno + 1
def cycle(cycle, sess, imno):
while cycle > 0:
if cycle % 5 == 0:
print('blue on')
blue.on()
if cycle % 5 == 4:
print('blue off')
blue.off()
time.sleep(1)
cycle -= 1
return snapshot(sess, imno)
def main():
reset()
if not sd_is_mounted():
return
check_dir()
sess = find_session()
imno = 0
print('session', sess)
while(True):
print('start cycle', imno)
imno = cycle(sleep_cycle, sess, imno)
if __name__ == '__main__':
main()