Initial import.
This commit is contained in:
commit
a31c696733
|
@ -0,0 +1,3 @@
|
||||||
|
*.jpg
|
||||||
|
*.avi
|
||||||
|
*.zip
|
|
@ -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 I’m
|
||||||
|
> wearing one of those lanyard name tags I’ll 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 I’ll 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.
|
|
@ -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
|
||||||
|
)
|
|
@ -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=
|
|
@ -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")
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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()
|
Loading…
Reference in New Issue