Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91628bd5d8 | ||
|
|
b10b64bf57 | ||
|
|
749c34be9f | ||
|
|
8cfdfab985 | ||
|
|
ef25f8a31e | ||
|
|
2a0551a08a | ||
|
|
0b80419f15 | ||
|
|
0dc81117aa | ||
|
|
49b29d72a7 | ||
|
|
21ece238ff | ||
|
|
f6ba3f2daa | ||
|
|
bb0d3cb59a | ||
|
|
ca9b6d6c5c | ||
|
|
3103ad2bfe | ||
|
|
eab3998ad0 | ||
|
|
a3dfd3a8e0 | ||
|
|
f1c3087775 | ||
|
|
1be91ed3f2 | ||
|
|
fd83c4f229 | ||
|
|
de99221ad5 | ||
|
|
6892ce56ac | ||
|
|
41cea6f62e | ||
|
|
4bbffa97df | ||
|
|
614f8abfef | ||
|
|
14289b5fd1 | ||
|
|
4164beff1c | ||
|
|
9b3ab486de | ||
|
|
232a49814a | ||
|
|
6c61f0b135 | ||
|
|
c572cec253 | ||
|
|
d4941f2a5f | ||
|
|
bf5ec2f65f | ||
|
|
f8e21584b6 | ||
|
|
3cba83f84b | ||
|
|
dcb4255d7e | ||
|
|
9fc3c0dc2f | ||
|
|
a78830b48e | ||
|
|
949fbadcdc | ||
|
|
12c9e63b13 | ||
|
|
157b230702 | ||
|
|
c69299d659 | ||
|
|
285d630770 | ||
|
|
b9318092f4 | ||
|
|
905c361d52 | ||
|
|
4443abbc49 | ||
|
|
dabb36ad93 | ||
|
|
2bc8736fd9 | ||
|
|
e9b3b09cc2 | ||
|
|
ca337c32b4 | ||
|
|
24b8bd7c85 | ||
|
|
3ad75a441d | ||
|
|
f006e9be8d | ||
|
|
03f3ba8008 | ||
|
|
96a44eb7bf | ||
|
|
006782fe3d | ||
|
|
ff3e95bbf7 | ||
|
|
4b95a37e65 | ||
|
|
38c661b3a8 | ||
|
|
0d6e4f6a66 | ||
|
|
1ad2219f1c | ||
|
|
dfcdd289c3 | ||
|
|
32f5f2cca9 | ||
|
|
24bfe9f3e8 | ||
|
|
004667dc99 | ||
|
|
9d785dc781 | ||
|
|
cbba5a7af0 | ||
|
|
29b29ee349 | ||
|
|
9ad53e09af | ||
|
|
c9278991c9 | ||
|
|
729de48934 | ||
|
|
7476bff5fb | ||
|
|
1e9eae8d9a | ||
|
|
8113a53381 | ||
|
|
72833686f1 | ||
|
|
096c21f105 | ||
|
|
181f66357b | ||
|
|
a54fbc483c | ||
|
|
92d5a002d3 | ||
|
|
f9184903d7 | ||
|
|
91cde6ce7b | ||
|
|
186a4587c7 | ||
|
|
6049acb1f3 | ||
|
|
2d2ebf313c | ||
|
|
3d329dcb52 | ||
|
|
06854fc34f | ||
|
|
e01e14d866 | ||
|
|
3dfd251ebb | ||
|
|
dcea807f77 | ||
|
|
87d83ff33a | ||
|
|
1d31cbdf0d | ||
|
|
e05b27b8dc | ||
|
|
7111bd208e | ||
|
|
04a80280da | ||
|
|
3bda092140 | ||
|
|
9086820479 | ||
|
|
d1da57aedc | ||
|
|
6ded12c566 | ||
|
|
70352566a7 | ||
|
|
cf5cc86588 | ||
|
|
e41db49ab8 | ||
|
|
1b7effafee | ||
|
|
69e9e0b0bf | ||
|
|
89624df411 | ||
|
|
d1a7405211 | ||
|
|
040f8c7c20 | ||
|
|
6d7acabf4c | ||
|
|
45a8b42157 | ||
|
|
8785be24b7 | ||
|
|
cc0812540c | ||
|
|
5cf38ca4f7 | ||
|
|
7e4395c30e | ||
|
|
598d3aeda2 | ||
|
|
012dbf81f7 | ||
|
|
f869def12e | ||
|
|
31f7666337 | ||
|
|
9e339acbca | ||
|
|
8f8054a299 | ||
|
|
f7021eec4c | ||
|
|
c124153da4 | ||
|
|
706c2f921e | ||
|
|
de1d66bcb9 | ||
|
|
4502ca8e80 | ||
|
|
32a66fe5e8 | ||
|
|
e1251aafdb | ||
|
|
587494068c | ||
|
|
7a4d90a47a | ||
|
|
d06b587d33 | ||
|
|
eef70e434b | ||
|
|
b39da3ee01 | ||
|
|
e07c4e0d8c | ||
|
|
2f41ba6f77 | ||
|
|
bf95af0f22 | ||
|
|
2e15847f86 | ||
|
|
5992e85dc8 | ||
|
|
24d416b869 | ||
|
|
5dbf368c4b | ||
|
|
7d56fe105f | ||
|
|
e9327aa18c | ||
|
|
df56e079de | ||
|
|
8c5bfbd187 | ||
|
|
2613e74f97 | ||
|
|
9a7fb96357 | ||
|
|
37f9dfed92 | ||
|
|
68c1544808 | ||
|
|
2b3d3c5824 | ||
|
|
efea87a3ea | ||
|
|
977785fb10 | ||
|
|
4e113e62c0 | ||
|
|
5080b2d781 | ||
|
|
5cfd6d1edb | ||
|
|
27ae4d8ab0 | ||
|
|
3db33302ec | ||
|
|
f2910d48e0 | ||
|
|
cf0f8892e2 | ||
|
|
4d22e172ff | ||
|
|
8874a55b0f | ||
|
|
24b703a875 | ||
|
|
8b8f5b5c40 | ||
|
|
eac81136d2 | ||
|
|
d1e27b43ea | ||
|
|
105dcb7094 | ||
|
|
c0a16efdc1 | ||
|
|
2800c54743 | ||
|
|
2a24e8abcb | ||
|
|
37ee746ebb | ||
|
|
7ee6bfe855 | ||
|
|
40f57a8754 | ||
|
|
e0da462223 | ||
|
|
47a9fc4292 | ||
|
|
03fe5158db | ||
|
|
72be6b480d | ||
|
|
a8964dcc1f | ||
|
|
732e91ee42 | ||
|
|
27da080ce6 | ||
|
|
075d06b108 | ||
|
|
95dc17ffcd | ||
|
|
408b53f8b4 | ||
|
|
3ef68a297a | ||
|
|
3e9b3711dc | ||
|
|
a1cc9ad1f0 | ||
|
|
29e8aa4020 | ||
|
|
777aff403f | ||
|
|
4b3b702459 | ||
|
|
893e6b40a7 | ||
|
|
a85d780020 | ||
|
|
34439699ae | ||
|
|
64b63142b1 | ||
|
|
cee1ab000b | ||
|
|
3ff98770c1 | ||
|
|
244203463d | ||
|
|
b6f7940b10 | ||
|
|
75312602aa | ||
|
|
75977128f0 | ||
|
|
eafde6c677 | ||
|
|
da0598baef | ||
|
|
35ba5e2f7c | ||
|
|
49258d6dbe | ||
|
|
5a081e4f00 | ||
|
|
4feae472e9 | ||
|
|
4e83239258 | ||
|
|
c4cccf44a5 | ||
|
|
64e7cbcc62 | ||
|
|
dd86e4f317 | ||
|
|
4db285a875 | ||
|
|
939d1ba091 | ||
|
|
0fe8d486d9 | ||
|
|
a3cb02af5c | ||
|
|
45a6b8452c | ||
|
|
9d594cc640 | ||
|
|
59e41ae1ac | ||
|
|
c6ed16465b | ||
|
|
8f14b36f5a | ||
|
|
b6c2491e3b | ||
|
|
8e31d04d90 | ||
|
|
bf93fbb357 | ||
|
|
c064b244db | ||
|
|
0280610e96 | ||
|
|
4363623c45 | ||
|
|
c960914ec3 | ||
|
|
9ecc80b443 | ||
|
|
3e146de0a2 | ||
|
|
bee54c39dc | ||
|
|
623d138d60 | ||
|
|
76befc1249 | ||
|
|
51251b9fb0 | ||
|
|
8c45076bb6 | ||
|
|
7d683ef399 | ||
|
|
e4da3822b1 | ||
|
|
12c4cd77c5 | ||
|
|
a611cbb942 | ||
|
|
f946813ccb | ||
|
|
49fca1b839 | ||
|
|
54cb4a2180 | ||
|
|
9954e3b11e | ||
|
|
82692b0ddc | ||
|
|
9d4fdec12f | ||
|
|
ed72c995ef | ||
|
|
66c77d1157 | ||
|
|
40c322ad47 | ||
|
|
83f1e0d713 | ||
|
|
2d89044bd3 | ||
|
|
dc4d24c2b9 | ||
|
|
d5fb20c524 | ||
|
|
7e92e8bfe8 | ||
|
|
efdcfcef97 | ||
|
|
574ee2a46f | ||
|
|
ec4d048905 | ||
|
|
b063099b2a | ||
|
|
2937dac4c3 | ||
|
|
7c283a1805 | ||
|
|
309c0dcda3 | ||
|
|
b35cc01035 | ||
|
|
6e79a5402e | ||
|
|
a989f8daaf | ||
|
|
7880d24b29 | ||
|
|
fdc8bbf72d | ||
|
|
005e188d38 | ||
|
|
adcc3e9b98 | ||
|
|
5fe201da25 | ||
|
|
974f7bd0df | ||
|
|
780ae7cd4f | ||
|
|
50e568b84c | ||
|
|
1ce993051e | ||
|
|
69406343ee | ||
|
|
1c33b8acb2 | ||
|
|
5e77436d39 | ||
|
|
e26308a05b | ||
|
|
c16ee3186f | ||
|
|
fedeeab561 | ||
|
|
bfcaabecfa | ||
|
|
606fa6f6d5 | ||
|
|
6a8d8bf53d | ||
|
|
1f81cba706 | ||
|
|
5db7b242aa | ||
|
|
0b7f65e227 | ||
|
|
2f758af097 | ||
|
|
f64320a464 | ||
|
|
3e87ef6426 | ||
|
|
acb75fa02d | ||
|
|
ea4ecae27c | ||
|
|
a8556a729b | ||
|
|
068df3ef2d | ||
|
|
b304139db2 | ||
|
|
df2aae5169 | ||
|
|
351ac4ec7d | ||
|
|
12e40291c0 | ||
|
|
8af7d51159 | ||
|
|
84ada716ac | ||
|
|
cbcc89be9c | ||
|
|
73a5e11b9b | ||
|
|
194baaeb56 | ||
|
|
469259d663 | ||
|
|
f3db69d975 | ||
|
|
0914cb71ad | ||
|
|
0ae2806eb4 | ||
|
|
adcfe699c2 | ||
|
|
e5048f98b6 | ||
|
|
e6c6338266 | ||
|
|
1f03c8cb8c | ||
|
|
69f5249788 | ||
|
|
3a1f1c946b | ||
|
|
d88745af6e | ||
|
|
709d917f0c | ||
|
|
918386bdc1 | ||
|
|
a8c0fadf95 | ||
|
|
6dc7b8f246 | ||
|
|
71f6f0bee4 | ||
|
|
a00afb61c0 | ||
|
|
5dbe6c5f36 | ||
|
|
16732aa5b3 | ||
|
|
3d2f1437e4 | ||
|
|
fbe721c860 | ||
|
|
7383db60b0 | ||
|
|
53ccc903da | ||
|
|
9d1f9f35e5 | ||
|
|
c1f522ff54 | ||
|
|
b345571a63 | ||
|
|
f29ee6165f | ||
|
|
ec6432cc5f | ||
|
|
8c917667b6 | ||
|
|
941434b8d8 | ||
|
|
2d0632adf8 | ||
|
|
f1afaf641a | ||
|
|
743116a733 | ||
|
|
8e77cf25d9 | ||
|
|
7d33e03943 | ||
|
|
0c44666c89 | ||
|
|
ddaa746807 | ||
|
|
760e1ffe1d | ||
|
|
15b4024715 | ||
|
|
918112a793 | ||
|
|
4ee200a81c | ||
|
|
e37eba49ff | ||
|
|
6de8e3bd1f | ||
|
|
3a9781c4f8 | ||
|
|
a60b9211d2 | ||
|
|
777fb1d5d1 | ||
|
|
8e9110f42e | ||
|
|
c80137e059 | ||
|
|
2768e1dadb | ||
|
|
2fbba01577 | ||
|
|
e7c536ea31 | ||
|
|
1734c0569a | ||
|
|
a5bef89123 | ||
|
|
d8aa73d26e | ||
|
|
791409d5e5 | ||
|
|
01bf89907d | ||
|
|
8e73c7e95e | ||
|
|
088bd18adb | ||
|
|
2e8c7ec225 | ||
|
|
9340a74371 | ||
|
|
5998de610b | ||
|
|
dfabff3846 | ||
|
|
76a7a3bad5 | ||
|
|
a3fa97dd52 | ||
|
|
1d2a41129c | ||
|
|
956298128d | ||
|
|
e6892d66b8 | ||
|
|
6ef22cf578 | ||
|
|
3e6f6edf7e | ||
|
|
81c5b96ed7 | ||
|
|
6f6d202c99 | ||
|
|
2fc389c3ad | ||
|
|
05951aa7da | ||
|
|
bb8e4621f5 | ||
|
|
04e9ab5ce4 | ||
|
|
1089a40943 | ||
|
|
68c3a069ba | ||
|
|
80b9652f7a | ||
|
|
569e07949f | ||
|
|
ffa9534549 | ||
|
|
c539993387 | ||
|
|
8a572f96d5 | ||
|
|
24cb3508e8 | ||
|
|
3f34c57e31 | ||
|
|
4c618daa90 | ||
|
|
cd057370e1 | ||
|
|
6263912655 | ||
|
|
af247275cf | ||
|
|
1198c29dac | ||
|
|
169603d3ff | ||
|
|
dc7eecebc6 | ||
|
|
0dd4087d5d | ||
|
|
6ecf87fc60 | ||
|
|
ebcf1482f8 | ||
|
|
50bcf60893 | ||
|
|
38efbd63ea | ||
|
|
50bcad8b77 | ||
|
|
cfffb219ae | ||
|
|
382d7be50a | ||
|
|
f43dc36a37 | ||
|
|
38e7fa07d2 | ||
|
|
e261c20819 | ||
|
|
3a66e672d3 | ||
|
|
2aada930e3 | ||
|
|
d87f4407a0 | ||
|
|
be5a114f6a | ||
|
|
32b212c7b6 | ||
|
|
76c8e3a12f | ||
|
|
16f7a361c3 | ||
|
|
634b87307f | ||
|
|
1d4fbbdba3 | ||
|
|
65579e9cbf | ||
|
|
49dc029c43 | ||
|
|
08174d8db2 | ||
|
|
5199242a68 | ||
|
|
725dd3220c | ||
|
|
10dc56f6ea | ||
|
|
cc2abe93a6 | ||
|
|
0c6717090c | ||
|
|
f5a2252b29 | ||
|
|
02efb6f415 | ||
|
|
5b4c6e50bc | ||
|
|
9cc46a71cb | ||
|
|
be1673b00a | ||
|
|
b6130e77ff | ||
|
|
4180c710cd | ||
|
|
ab3e70b4db | ||
|
|
d90e408d50 | ||
|
|
6c87ce0879 | ||
|
|
b7b4e38f62 | ||
|
|
480175d70f | ||
|
|
bee99ca6ff | ||
|
|
5c01720567 | ||
|
|
262f45c8bc | ||
|
|
22bb17b2fd | ||
|
|
3a3afe14bf | ||
|
|
01f058a482 | ||
|
|
d899ef158e | ||
|
|
39d64f7ba7 | ||
|
|
f148eb5a7b | ||
|
|
297e2f1c0c | ||
|
|
e818744d81 | ||
|
|
ceedfae993 | ||
|
|
e13563770d | ||
|
|
a659019d1a | ||
|
|
ba71927d53 | ||
|
|
04fed31eac | ||
|
|
ebaa8fac01 | ||
|
|
2ec45cd1b6 | ||
|
|
700bd1e3ef | ||
|
|
c9e9f7a735 | ||
|
|
aea4dc8724 | ||
|
|
12d5007b90 | ||
|
|
8970e73f75 | ||
|
|
1ba006b24f | ||
|
|
4a58f16637 | ||
|
|
436b876b24 | ||
|
|
a770ab7f69 | ||
|
|
806acaf445 | ||
|
|
c653567cc1 | ||
|
|
8fee8f86a2 | ||
|
|
59a4b0e650 | ||
|
|
834a3df0bc | ||
|
|
c41b104997 | ||
|
|
7028b05856 | ||
|
|
2d22a04391 | ||
|
|
baa587028b | ||
|
|
2b51dc3e5b | ||
|
|
9f8278ea8f | ||
|
|
56b9c754f5 | ||
|
|
5c4f5ef3f0 | ||
|
|
8c924896c5 | ||
|
|
2c2f0044b9 | ||
|
|
874e9085a7 | ||
|
|
e791d6646b | ||
|
|
3019b0218c | ||
|
|
6900e140d5 | ||
|
|
911c1b2bfa | ||
|
|
f4587462cf | ||
|
|
cac1faa8ac | ||
|
|
9525bae5a3 | ||
|
|
dbcfd109f6 | ||
|
|
f95d8b6210 | ||
|
|
4dacf02ef9 | ||
|
|
3e803b6a03 | ||
|
|
7a7f507781 | ||
|
|
e0b9b616ce | ||
|
|
4476bd8a13 | ||
|
|
5aa3775c77 | ||
|
|
edf0cd36df | ||
|
|
0279121d77 | ||
|
|
8774e537dc | ||
|
|
0514eeac03 | ||
|
|
a074945394 | ||
|
|
a26d2217d4 | ||
|
|
200d769003 | ||
|
|
48aa245914 | ||
|
|
ada8ffccf9 | ||
|
|
bca4e78e9a | ||
|
|
7d3027e056 | ||
|
|
c406fda288 | ||
|
|
8ff9a982b6 | ||
|
|
f2c205be99 | ||
|
|
862aa2d3f0 | ||
|
|
8bae05cfe2 | ||
|
|
de9c3f4d74 | ||
|
|
c12e19349e | ||
|
|
afb70f11a8 | ||
|
|
85259ca00c | ||
|
|
1a55008cd5 | ||
|
|
2bf4820dc6 | ||
|
|
e597a08a66 | ||
|
|
5dc76803d6 | ||
|
|
80f7e998ae | ||
|
|
df7b90e367 | ||
|
|
2e3c9da650 | ||
|
|
89c03c4455 | ||
|
|
c5e4bc275c | ||
|
|
86f5d8128d | ||
|
|
9186634c60 | ||
|
|
039782ad2e | ||
|
|
bf6d6c3796 | ||
|
|
b4e5c812ce | ||
|
|
122a1666ca | ||
|
|
5258803cd7 | ||
|
|
9486a321c6 | ||
|
|
bff1826bf5 | ||
|
|
19323ac957 | ||
|
|
aeece7b98c | ||
|
|
a73760989c | ||
|
|
eded4d172f | ||
|
|
496b96b4f7 | ||
|
|
f54fa2e56c | ||
|
|
2929773c10 | ||
|
|
c43d83be20 | ||
|
|
b6547de82c | ||
|
|
290150603e | ||
|
|
03c57bf67d | ||
|
|
42ff739cea | ||
|
|
92e1833def | ||
|
|
957bd2adeb | ||
|
|
8525f05f29 | ||
|
|
b67f323215 | ||
|
|
284a96d5c7 | ||
|
|
a84f88c5a5 | ||
|
|
2b0ebfa354 | ||
|
|
d9d3937a0f | ||
|
|
7d4cfe43ad | ||
|
|
f7c8b742e8 | ||
|
|
861539e8b5 | ||
|
|
225f434718 | ||
|
|
6bdacffa2e | ||
|
|
a3c5dd91a1 | ||
|
|
d43c17238c | ||
|
|
f3376a83d6 | ||
|
|
30c223711d | ||
|
|
ab1fda3a0e | ||
|
|
83c9b8fb7a | ||
|
|
e997591e70 | ||
|
|
ebd15489b0 | ||
|
|
51e8a8300f | ||
|
|
80dbed8055 | ||
|
|
53c9a7368d | ||
|
|
5d894f006a | ||
|
|
d8f27658f8 | ||
|
|
6e8409d203 | ||
|
|
3e4256602f | ||
|
|
071fcf6ea2 | ||
|
|
4a77046c7c | ||
|
|
0359e2d2a1 | ||
|
|
623a3044fb | ||
|
|
072997736c | ||
|
|
b91c24bf8f | ||
|
|
11af9bb953 | ||
|
|
a976403edc | ||
|
|
98ce5a4a59 | ||
|
|
7e3d2f6611 | ||
|
|
72393be6d6 |
@@ -0,0 +1,7 @@
|
||||
README.md
|
||||
docs/
|
||||
.gitignore
|
||||
debug
|
||||
config/
|
||||
*.pyc
|
||||
.git
|
||||
@@ -0,0 +1 @@
|
||||
github: blakeblackshear
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: Bug report or Support request
|
||||
about: ''
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what your issue is.
|
||||
|
||||
**Version of frigate**
|
||||
Output from `/version`
|
||||
|
||||
**Config file**
|
||||
Include your full config file wrapped in triple back ticks.
|
||||
```yaml
|
||||
config here
|
||||
```
|
||||
|
||||
**Frigate container logs**
|
||||
```
|
||||
Include relevant log output here
|
||||
```
|
||||
|
||||
**Frigate stats**
|
||||
```json
|
||||
Output from frigate's /stats endpoint
|
||||
```
|
||||
|
||||
**FFprobe from your camera**
|
||||
|
||||
Run the following command and paste output below
|
||||
```
|
||||
ffprobe <stream_url>
|
||||
```
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Computer Hardware**
|
||||
- OS: [e.g. Ubuntu, Windows]
|
||||
- Install method: [e.g. Addon, Docker Compose, Docker Command]
|
||||
- Virtualization: [e.g. Proxmox, Virtualbox]
|
||||
- Coral Version: [e.g. USB, PCIe, None]
|
||||
- Network Setup: [e.g. Wired, WiFi]
|
||||
|
||||
**Camera Info:**
|
||||
- Manufacturer: [e.g. Dahua]
|
||||
- Model: [e.g. IPC-HDW5231R-ZE]
|
||||
- Resolution: [e.g. 720p]
|
||||
- FPS: [e.g. 5]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,11 @@
|
||||
.DS_Store
|
||||
*.pyc
|
||||
debug
|
||||
.vscode
|
||||
config/config.yml
|
||||
models
|
||||
*.mp4
|
||||
*.db
|
||||
frigate/version.py
|
||||
web/build
|
||||
web/node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
/*!
|
||||
Copyright (c) 2017 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
@@ -1 +0,0 @@
|
||||
/*! algoliasearch-lite.umd.js | 4.8.4 | © Algolia, inc. | https://github.com/algolia/algoliasearch-client-javascript */
|
||||
@@ -1 +0,0 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[30],{179:function(n,i,o){"use strict";o.r(i);var r=o(55);for(var t in r)["default"].indexOf(t)<0&&function(n){o.d(i,n,(function(){return r[n]}))}(t)}}]);
|
||||
@@ -1 +0,0 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[31],{117:function(e,t,a){"use strict";a.r(t);var n=a(0),o=a.n(n),l=a(110);t.default=function(){return o.a.createElement(l.a,{title:"Page Not Found"},o.a.createElement("main",{className:"container margin-vert--xl"},o.a.createElement("div",{className:"row"},o.a.createElement("div",{className:"col col--6 col--offset-3"},o.a.createElement("h1",{className:"hero__title"},"Page Not Found"),o.a.createElement("p",null,"We could not find what you were looking for."),o.a.createElement("p",null,"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))}}}]);
|
||||
@@ -1 +0,0 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[6],{75:function(e,t,r){"use strict";r.r(t),r.d(t,"frontMatter",(function(){return i})),r.d(t,"metadata",(function(){return c})),r.d(t,"toc",(function(){return u})),r.d(t,"default",(function(){return s}));var n=r(3),a=r(7),o=(r(0),r(99)),i={id:"web",title:"Web Interface"},c={unversionedId:"usage/web",id:"usage/web",isDocsHomePage:!1,title:"Web Interface",description:"Frigate comes bundled with a simple web ui that supports the following:",source:"@site/docs/usage/web.md",slug:"/usage/web",permalink:"/frigate/usage/web",editUrl:"https://github.com/blakeblackshear/frigate/edit/master/docs/docs/usage/web.md",version:"current",sidebar:"docs",previous:{title:"Integration with Home Assistant",permalink:"/frigate/usage/home-assistant"},next:{title:"HTTP API",permalink:"/frigate/usage/api"}},u=[],l={toc:u};function s(e){var t=e.components,r=Object(a.a)(e,["components"]);return Object(o.b)("wrapper",Object(n.a)({},l,r,{components:t,mdxType:"MDXLayout"}),Object(o.b)("p",null,"Frigate comes bundled with a simple web ui that supports the following:"),Object(o.b)("ul",null,Object(o.b)("li",{parentName:"ul"},"Show cameras"),Object(o.b)("li",{parentName:"ul"},"Browse events"),Object(o.b)("li",{parentName:"ul"},"Mask helper")))}s.isMDXComponent=!0},99:function(e,t,r){"use strict";r.d(t,"a",(function(){return p})),r.d(t,"b",(function(){return m}));var n=r(0),a=r.n(n);function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function c(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?i(Object(r),!0).forEach((function(t){o(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):i(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function u(e,t){if(null==e)return{};var r,n,a=function(e,t){if(null==e)return{};var r,n,a={},o=Object.keys(e);for(n=0;n<o.length;n++)r=o[n],t.indexOf(r)>=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n<o.length;n++)r=o[n],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var l=a.a.createContext({}),s=function(e){var t=a.a.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):c(c({},t),e)),r},p=function(e){var t=s(e.components);return a.a.createElement(l.Provider,{value:t},e.children)},b={inlineCode:"code",wrapper:function(e){var t=e.children;return a.a.createElement(a.a.Fragment,{},t)}},f=a.a.forwardRef((function(e,t){var r=e.components,n=e.mdxType,o=e.originalType,i=e.parentName,l=u(e,["components","mdxType","originalType","parentName"]),p=s(r),f=n,m=p["".concat(i,".").concat(f)]||p[f]||b[f]||o;return r?a.a.createElement(m,c(c({ref:t},l),{},{components:r})):a.a.createElement(m,c({ref:t},l))}));function m(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var o=r.length,i=new Array(o);i[0]=f;var c={};for(var u in t)hasOwnProperty.call(t,u)&&(c[u]=t[u]);c.originalType=e,c.mdxType="string"==typeof e?e:n,i[1]=c;for(var l=2;l<o;l++)i[l]=r[l];return a.a.createElement.apply(null,i)}return a.a.createElement.apply(null,r)}f.displayName="MDXCreateElement"}}]);
|
||||
@@ -1,31 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="generator" content="Docusaurus v2.0.0-alpha.70">
|
||||
<link rel="alternate" type="application/rss+xml" href="/frigate/blog/rss.xml" title="Frigate Blog RSS Feed">
|
||||
<link rel="alternate" type="application/atom+xml" href="/frigate/blog/atom.xml" title="Frigate Blog Atom Feed">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="Frigate" href="/frigate/opensearch.xml"><title data-react-helmet="true">Page Not Found | Frigate</title><meta data-react-helmet="true" property="og:title" content="Page Not Found | Frigate"><meta data-react-helmet="true" name="twitter:card" content="summary_large_image"><meta data-react-helmet="true" name="docsearch:language" content="en"><meta data-react-helmet="true" name="docsearch:docusaurus_tag" content="default"><link data-react-helmet="true" rel="shortcut icon" href="/frigate/img/favicon.ico"><link data-react-helmet="true" rel="preconnect" href="https://BH4D9OD16A-dsn.algolia.net" crossorigin="anonymous"><link rel="stylesheet" href="/frigate/styles.4dd8d972.css">
|
||||
<link rel="preload" href="/frigate/styles.0df63e1c.js" as="script">
|
||||
<link rel="preload" href="/frigate/runtime~main.85a22073.js" as="script">
|
||||
<link rel="preload" href="/frigate/main.b6b2d1f0.js" as="script">
|
||||
<link rel="preload" href="/frigate/1.d4a988ac.js" as="script">
|
||||
<link rel="preload" href="/frigate/2.cbe00df1.js" as="script">
|
||||
<link rel="preload" href="/frigate/28.fabd8c68.js" as="script">
|
||||
<link rel="preload" href="/frigate/31.3f82c6fa.js" as="script">
|
||||
<link rel="preload" href="/frigate/935f2afb.06dae20f.js" as="script">
|
||||
</head>
|
||||
<body>
|
||||
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}()</script><div id="__docusaurus">
|
||||
<nav aria-label="Skip navigation links"><button type="button" tabindex="0" class="skipToContent_11B0">Skip to main content</button></nav><nav class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><div aria-label="Navigation bar toggle" class="navbar__toggle" role="button" tabindex="0"><svg aria-label="Menu" width="30" height="30" viewBox="0 0 30 30" role="img" focusable="false"><title>Menu</title><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></div><a class="navbar__brand" href="/frigate/"><img src="/frigate/img/logo.svg" alt="Frigate" class="themedImage_YANc themedImage--light_3CMI navbar__logo"><img src="/frigate/img/logo-dark.svg" alt="Frigate" class="themedImage_YANc themedImage--dark_3ARp navbar__logo"><strong class="navbar__title">Frigate</strong></a><a class="navbar__item navbar__link" href="/frigate/">Docs</a></div><div class="navbar__items navbar__items--right"><a href="https://github.com/blakeblackshear/frigate" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">GitHub</a><div class="react-toggle react-toggle--disabled displayOnlyInLargeViewport_2N3Q"><div class="react-toggle-track"><div class="react-toggle-track-check"><span class="toggle_3NWk">🌜</span></div><div class="react-toggle-track-x"><span class="toggle_3NWk">🌞</span></div></div><div class="react-toggle-thumb"></div><input type="checkbox" disabled="" aria-label="Dark mode toggle" class="react-toggle-screenreader-only"></div><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><div class="DocSearch-Button-Container"><svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></div></button></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div><div class="navbar-sidebar"><div class="navbar-sidebar__brand"><a class="navbar__brand" href="/frigate/"><img src="/frigate/img/logo.svg" alt="Frigate" class="themedImage_YANc themedImage--light_3CMI navbar__logo"><img src="/frigate/img/logo-dark.svg" alt="Frigate" class="themedImage_YANc themedImage--dark_3ARp navbar__logo"><strong class="navbar__title">Frigate</strong></a></div><div class="navbar-sidebar__items"><div class="menu"><ul class="menu__list"><li class="menu__list-item"><a class="menu__link" href="/frigate/">Docs</a></li><li class="menu__list-item"><a href="https://github.com/blakeblackshear/frigate" target="_blank" rel="noopener noreferrer" class="menu__link">GitHub</a></li></ul></div></div></div></nav><div class="main-wrapper"><main class="container margin-vert--xl"><div class="row"><div class="col col--6 col--offset-3"><h1 class="hero__title">Page Not Found</h1><p>We could not find what you were looking for.</p><p>Please contact the owner of the site that linked you to the original URL and let them know their link is broken.</p></div></div></main></div><footer class="footer footer--dark"><div class="container"><div class="row footer__links"><div class="col footer__col"><h4 class="footer__title">Community</h4><ul class="footer__items"><li class="footer__item"><a href="https://github.com/blakeblackshear/frigate" target="_blank" rel="noopener noreferrer" class="footer__link-item">GitHub</a></li><li class="footer__item"><a href="https://github.com/blakeblackshear/frigate/discussions" target="_blank" rel="noopener noreferrer" class="footer__link-item">Discussions</a></li></ul></div></div><div class="footer__bottom text--center"><div class="footer__copyright">Copyright © 2021 Blake Blackshear</div></div></div></footer></div>
|
||||
<script src="/frigate/styles.0df63e1c.js"></script>
|
||||
<script src="/frigate/runtime~main.85a22073.js"></script>
|
||||
<script src="/frigate/main.b6b2d1f0.js"></script>
|
||||
<script src="/frigate/1.d4a988ac.js"></script>
|
||||
<script src="/frigate/2.cbe00df1.js"></script>
|
||||
<script src="/frigate/28.fabd8c68.js"></script>
|
||||
<script src="/frigate/31.3f82c6fa.js"></script>
|
||||
<script src="/frigate/935f2afb.06dae20f.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[12],{159:function(e,t,r){"use strict";r.r(t),t.default=r.p+"assets/images/diagram-3101f1674015822e176773924caae31a.png"},81:function(e,t,r){"use strict";r.r(t),r.d(t,"frontMatter",(function(){return i})),r.d(t,"metadata",(function(){return c})),r.d(t,"toc",(function(){return s})),r.d(t,"default",(function(){return u}));var n=r(3),o=r(7),a=(r(0),r(99)),i={id:"how-it-works",title:"How Frigate Works",sidebar_label:"How it works"},c={unversionedId:"how-it-works",id:"how-it-works",isDocsHomePage:!1,title:"How Frigate Works",description:"Frigate is designed to minimize resource and maximize performance by only looking for objects when and where it is necessary",source:"@site/docs/how-it-works.md",slug:"/how-it-works",permalink:"/frigate/how-it-works",editUrl:"https://github.com/blakeblackshear/frigate/edit/master/docs/docs/how-it-works.md",version:"current",sidebar_label:"How it works",sidebar:"docs",previous:{title:"Frigate",permalink:"/frigate/"},next:{title:"Recommended hardware",permalink:"/frigate/hardware"}},s=[],l={toc:s};function u(e){var t=e.components,i=Object(o.a)(e,["components"]);return Object(a.b)("wrapper",Object(n.a)({},l,i,{components:t,mdxType:"MDXLayout"}),Object(a.b)("p",null,"Frigate is designed to minimize resource and maximize performance by only looking for objects when and where it is necessary"),Object(a.b)("p",null,Object(a.b)("img",{alt:"Diagram",src:r(159).default})),Object(a.b)("ol",null,Object(a.b)("li",{parentName:"ol"},"Look for Motion"),Object(a.b)("li",{parentName:"ol"},"Calculate Detection Regions"),Object(a.b)("li",{parentName:"ol"},"Run Object Detection")))}u.isMDXComponent=!0},99:function(e,t,r){"use strict";r.d(t,"a",(function(){return p})),r.d(t,"b",(function(){return m}));var n=r(0),o=r.n(n);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function c(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?i(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):i(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function s(e,t){if(null==e)return{};var r,n,o=function(e,t){if(null==e)return{};var r,n,o={},a=Object.keys(e);for(n=0;n<a.length;n++)r=a[n],t.indexOf(r)>=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n<a.length;n++)r=a[n],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var l=o.a.createContext({}),u=function(e){var t=o.a.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):c(c({},t),e)),r},p=function(e){var t=u(e.components);return o.a.createElement(l.Provider,{value:t},e.children)},f={inlineCode:"code",wrapper:function(e){var t=e.children;return o.a.createElement(o.a.Fragment,{},t)}},b=o.a.forwardRef((function(e,t){var r=e.components,n=e.mdxType,a=e.originalType,i=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),p=u(r),b=n,m=p["".concat(i,".").concat(b)]||p[b]||f[b]||a;return r?o.a.createElement(m,c(c({ref:t},l),{},{components:r})):o.a.createElement(m,c({ref:t},l))}));function m(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var a=r.length,i=new Array(a);i[0]=b;var c={};for(var s in t)hasOwnProperty.call(t,s)&&(c[s]=t[s]);c.originalType=e,c.mdxType="string"==typeof e?e:n,i[1]=c;for(var l=2;l<a;l++)i[l]=r[l];return o.a.createElement.apply(null,i)}return o.a.createElement.apply(null,r)}b.displayName="MDXCreateElement"}}]);
|
||||
@@ -1 +0,0 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[13],{84:function(e){e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","isLast":true,"docsSidebars":{"docs":[{"collapsed":true,"type":"category","label":"Frigate","items":[{"type":"link","label":"Features","href":"/frigate/"},{"type":"link","label":"How it works","href":"/frigate/how-it-works"},{"type":"link","label":"Recommended hardware","href":"/frigate/hardware"},{"type":"link","label":"Installation","href":"/frigate/installation"},{"type":"link","label":"Troubleshooting and FAQ","href":"/frigate/troubleshooting"}]},{"collapsed":true,"type":"category","label":"Configuration","items":[{"type":"link","label":"Configuration","href":"/frigate/configuration/index"},{"type":"link","label":"Cameras","href":"/frigate/configuration/cameras"},{"type":"link","label":"Optimizing performance","href":"/frigate/configuration/optimizing"},{"type":"link","label":"Detectors","href":"/frigate/configuration/detectors"},{"type":"link","label":"Reducing false positives","href":"/frigate/configuration/false_positives"},{"type":"link","label":"Available objects","href":"/frigate/configuration/objects"},{"type":"link","label":"Advanced","href":"/frigate/configuration/advanced"}]},{"collapsed":true,"type":"category","label":"Usage","items":[{"type":"link","label":"Home Assistant","href":"/frigate/usage/home-assistant"},{"type":"link","label":"Web Interface","href":"/frigate/usage/web"},{"type":"link","label":"HTTP API","href":"/frigate/usage/api"},{"type":"link","label":"MQTT","href":"/frigate/usage/mqtt"}]},{"collapsed":true,"type":"category","label":"Development","items":[{"type":"link","label":"Contributing","href":"/frigate/contributing"}]}]},"permalinkToSidebar":{"/frigate/configuration/advanced":"docs","/frigate/configuration/cameras":"docs","/frigate/configuration/detectors":"docs","/frigate/configuration/false_positives":"docs","/frigate/configuration/index":"docs","/frigate/configuration/objects":"docs","/frigate/configuration/optimizing":"docs","/frigate/contributing":"docs","/frigate/hardware":"docs","/frigate/how-it-works":"docs","/frigate/":"docs","/frigate/installation":"docs","/frigate/troubleshooting":"docs","/frigate/usage/api":"docs","/frigate/usage/home-assistant":"docs","/frigate/usage/mqtt":"docs","/frigate/usage/web":"docs"}}')}}]);
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2020 Blake Blackshear
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,59 @@
|
||||
default_target: amd64_frigate
|
||||
|
||||
COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1)
|
||||
|
||||
version:
|
||||
echo "VERSION='0.8.0-$(COMMIT_HASH)'" > frigate/version.py
|
||||
|
||||
web:
|
||||
docker build --tag frigate-web --file docker/Dockerfile.web web/
|
||||
|
||||
amd64_wheels:
|
||||
docker build --tag blakeblackshear/frigate-wheels:1.0.0-amd64 --file docker/Dockerfile.wheels .
|
||||
|
||||
amd64_ffmpeg:
|
||||
docker build --tag blakeblackshear/frigate-ffmpeg:1.1.0-amd64 --file docker/Dockerfile.ffmpeg.amd64 .
|
||||
|
||||
amd64_frigate: version web
|
||||
docker build --tag frigate-base --build-arg ARCH=amd64 --build-arg FFMPEG_VERSION=1.1.0 --build-arg WHEELS_VERSION=1.0.0 --file docker/Dockerfile.base .
|
||||
docker build --tag frigate --file docker/Dockerfile.amd64 .
|
||||
|
||||
amd64_all: amd64_wheels amd64_ffmpeg amd64_frigate
|
||||
|
||||
amd64nvidia_wheels:
|
||||
docker build --tag blakeblackshear/frigate-wheels:1.0.0-amd64nvidia --file docker/Dockerfile.wheels .
|
||||
|
||||
amd64nvidia_ffmpeg:
|
||||
docker build --tag blakeblackshear/frigate-ffmpeg:1.0.0-amd64nvidia --file docker/Dockerfile.ffmpeg.amd64nvidia .
|
||||
|
||||
amd64nvidia_frigate: version web
|
||||
docker build --tag frigate-base --build-arg ARCH=amd64nvidia --build-arg FFMPEG_VERSION=1.0.0 --build-arg WHEELS_VERSION=1.0.0 --file docker/Dockerfile.base .
|
||||
docker build --tag frigate --file docker/Dockerfile.amd64nvidia .
|
||||
|
||||
amd64nvidia_all: amd64nvidia_wheels amd64nvidia_ffmpeg amd64nvidia_frigate
|
||||
|
||||
aarch64_wheels:
|
||||
docker build --tag blakeblackshear/frigate-wheels:1.0.0-aarch64 --file docker/Dockerfile.wheels .
|
||||
|
||||
aarch64_ffmpeg:
|
||||
docker build --tag blakeblackshear/frigate-ffmpeg:1.0.0-aarch64 --file docker/Dockerfile.ffmpeg.aarch64 .
|
||||
|
||||
aarch64_frigate: version web
|
||||
docker build --tag frigate-base --build-arg ARCH=aarch64 --build-arg FFMPEG_VERSION=1.0.0 --build-arg WHEELS_VERSION=1.0.0 --file docker/Dockerfile.base .
|
||||
docker build --tag frigate --file docker/Dockerfile.aarch64 .
|
||||
|
||||
armv7_all: armv7_wheels armv7_ffmpeg armv7_frigate
|
||||
|
||||
armv7_wheels:
|
||||
docker build --tag blakeblackshear/frigate-wheels:1.0.0-armv7 --file docker/Dockerfile.wheels .
|
||||
|
||||
armv7_ffmpeg:
|
||||
docker build --tag blakeblackshear/frigate-ffmpeg:1.0.0-armv7 --file docker/Dockerfile.ffmpeg.armv7 .
|
||||
|
||||
armv7_frigate: version web
|
||||
docker build --tag frigate-base --build-arg ARCH=armv7 --build-arg FFMPEG_VERSION=1.0.0 --build-arg WHEELS_VERSION=1.0.0 --file docker/Dockerfile.base .
|
||||
docker build --tag frigate --file docker/Dockerfile.armv7 .
|
||||
|
||||
armv7_all: armv7_wheels armv7_ffmpeg armv7_frigate
|
||||
|
||||
.PHONY: web
|
||||
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 781 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,93 @@
|
||||
import os
|
||||
from statistics import mean
|
||||
import multiprocessing as mp
|
||||
import numpy as np
|
||||
import datetime
|
||||
from frigate.edgetpu import LocalObjectDetector, EdgeTPUProcess, RemoteObjectDetector, load_labels
|
||||
|
||||
my_frame = np.expand_dims(np.full((300,300,3), 1, np.uint8), axis=0)
|
||||
labels = load_labels('/labelmap.txt')
|
||||
|
||||
######
|
||||
# Minimal same process runner
|
||||
######
|
||||
# object_detector = LocalObjectDetector()
|
||||
# tensor_input = np.expand_dims(np.full((300,300,3), 0, np.uint8), axis=0)
|
||||
|
||||
# start = datetime.datetime.now().timestamp()
|
||||
|
||||
# frame_times = []
|
||||
# for x in range(0, 1000):
|
||||
# start_frame = datetime.datetime.now().timestamp()
|
||||
|
||||
# tensor_input[:] = my_frame
|
||||
# detections = object_detector.detect_raw(tensor_input)
|
||||
# parsed_detections = []
|
||||
# for d in detections:
|
||||
# if d[1] < 0.4:
|
||||
# break
|
||||
# parsed_detections.append((
|
||||
# labels[int(d[0])],
|
||||
# float(d[1]),
|
||||
# (d[2], d[3], d[4], d[5])
|
||||
# ))
|
||||
# frame_times.append(datetime.datetime.now().timestamp()-start_frame)
|
||||
|
||||
# duration = datetime.datetime.now().timestamp()-start
|
||||
# print(f"Processed for {duration:.2f} seconds.")
|
||||
# print(f"Average frame processing time: {mean(frame_times)*1000:.2f}ms")
|
||||
|
||||
|
||||
def start(id, num_detections, detection_queue, event):
|
||||
object_detector = RemoteObjectDetector(str(id), '/labelmap.txt', detection_queue, event)
|
||||
start = datetime.datetime.now().timestamp()
|
||||
|
||||
frame_times = []
|
||||
for x in range(0, num_detections):
|
||||
start_frame = datetime.datetime.now().timestamp()
|
||||
detections = object_detector.detect(my_frame)
|
||||
frame_times.append(datetime.datetime.now().timestamp()-start_frame)
|
||||
|
||||
duration = datetime.datetime.now().timestamp()-start
|
||||
object_detector.cleanup()
|
||||
print(f"{id} - Processed for {duration:.2f} seconds.")
|
||||
print(f"{id} - FPS: {object_detector.fps.eps():.2f}")
|
||||
print(f"{id} - Average frame processing time: {mean(frame_times)*1000:.2f}ms")
|
||||
|
||||
######
|
||||
# Separate process runner
|
||||
######
|
||||
# event = mp.Event()
|
||||
# detection_queue = mp.Queue()
|
||||
# edgetpu_process = EdgeTPUProcess(detection_queue, {'1': event}, 'usb:0')
|
||||
|
||||
# start(1, 1000, edgetpu_process.detection_queue, event)
|
||||
# print(f"Average raw inference speed: {edgetpu_process.avg_inference_speed.value*1000:.2f}ms")
|
||||
|
||||
####
|
||||
# Multiple camera processes
|
||||
####
|
||||
camera_processes = []
|
||||
|
||||
events = {}
|
||||
for x in range(0, 10):
|
||||
events[str(x)] = mp.Event()
|
||||
detection_queue = mp.Queue()
|
||||
edgetpu_process_1 = EdgeTPUProcess(detection_queue, events, 'usb:0')
|
||||
edgetpu_process_2 = EdgeTPUProcess(detection_queue, events, 'usb:1')
|
||||
|
||||
for x in range(0, 10):
|
||||
camera_process = mp.Process(target=start, args=(x, 300, detection_queue, events[str(x)]))
|
||||
camera_process.daemon = True
|
||||
camera_processes.append(camera_process)
|
||||
|
||||
start_time = datetime.datetime.now().timestamp()
|
||||
|
||||
for p in camera_processes:
|
||||
p.start()
|
||||
|
||||
for p in camera_processes:
|
||||
p.join()
|
||||
|
||||
duration = datetime.datetime.now().timestamp()-start_time
|
||||
print(f"Total - Processed for {duration:.2f} seconds.")
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<id>https://blakeblackshear.github.io/blog</id>
|
||||
<title>Frigate Blog</title>
|
||||
<updated>2015-10-25T23:29:00.000Z</updated>
|
||||
<generator>https://github.com/jpmonette/feed</generator>
|
||||
<link rel="alternate" href="https://blakeblackshear.github.io/blog"/>
|
||||
<subtitle>Frigate Blog</subtitle>
|
||||
<icon>https://blakeblackshear.github.io/img/favicon.ico</icon>
|
||||
</feed>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Frigate Blog</title>
|
||||
<link>https://blakeblackshear.github.io/blog</link>
|
||||
<description>Frigate Blog</description>
|
||||
<lastBuildDate>Sun, 25 Oct 2015 23:29:00 GMT</lastBuildDate>
|
||||
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
|
||||
<generator>https://github.com/jpmonette/feed</generator>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -1 +0,0 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[21],{92:function(e,t,r){"use strict";r.r(t),r.d(t,"frontMatter",(function(){return c})),r.d(t,"metadata",(function(){return i})),r.d(t,"toc",(function(){return u})),r.d(t,"Highlight",(function(){return p})),r.d(t,"default",(function(){return d}));var n=r(3),o=r(7),a=(r(0),r(99)),c={id:"mdx",title:"Powered by MDX"},i={unversionedId:"mdx",id:"mdx",isDocsHomePage:!1,title:"Powered by MDX",description:"You can write JSX and use React components within your Markdown thanks to MDX.",source:"@site/docs/mdx.md",slug:"/mdx",permalink:"/frigate/mdx",editUrl:"https://github.com/blakeblackshear/frigate/edit/master/docs/docs/mdx.md",version:"current"},u=[],p=function(e){var t=e.children,r=e.color;return Object(a.b)("span",{style:{backgroundColor:r,borderRadius:"2px",color:"#fff",padding:"0.2rem"}},t)},l={toc:u,Highlight:p};function d(e){var t=e.components,r=Object(o.a)(e,["components"]);return Object(a.b)("wrapper",Object(n.a)({},l,r,{components:t,mdxType:"MDXLayout"}),Object(a.b)("p",null,"You can write JSX and use React components within your Markdown thanks to ",Object(a.b)("a",Object(n.a)({parentName:"p"},{href:"https://mdxjs.com/"}),"MDX"),"."),Object(a.b)(p,{color:"#25c2a0",mdxType:"Highlight"},"Docusaurus green")," and ",Object(a.b)(p,{color:"#1877F2",mdxType:"Highlight"},"Facebook blue")," are my favorite colors.",Object(a.b)("p",null,"I can write ",Object(a.b)("strong",{parentName:"p"},"Markdown")," alongside my ",Object(a.b)("em",{parentName:"p"},"JSX"),"!"))}d.isMDXComponent=!0},99:function(e,t,r){"use strict";r.d(t,"a",(function(){return d})),r.d(t,"b",(function(){return b}));var n=r(0),o=r.n(n);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function c(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?c(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):c(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function u(e,t){if(null==e)return{};var r,n,o=function(e,t){if(null==e)return{};var r,n,o={},a=Object.keys(e);for(n=0;n<a.length;n++)r=a[n],t.indexOf(r)>=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n<a.length;n++)r=a[n],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var p=o.a.createContext({}),l=function(e){var t=o.a.useContext(p),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},d=function(e){var t=l(e.components);return o.a.createElement(p.Provider,{value:t},e.children)},s={inlineCode:"code",wrapper:function(e){var t=e.children;return o.a.createElement(o.a.Fragment,{},t)}},f=o.a.forwardRef((function(e,t){var r=e.components,n=e.mdxType,a=e.originalType,c=e.parentName,p=u(e,["components","mdxType","originalType","parentName"]),d=l(r),f=n,b=d["".concat(c,".").concat(f)]||d[f]||s[f]||a;return r?o.a.createElement(b,i(i({ref:t},p),{},{components:r})):o.a.createElement(b,i({ref:t},p))}));function b(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var a=r.length,c=new Array(a);c[0]=f;var i={};for(var u in t)hasOwnProperty.call(t,u)&&(i[u]=t[u]);i.originalType=e,i.mdxType="string"==typeof e?e:n,c[1]=i;for(var p=2;p<a;p++)c[p]=r[p];return o.a.createElement.apply(null,c)}return o.a.createElement.apply(null,r)}f.displayName="MDXCreateElement"}}]);
|
||||
@@ -1 +0,0 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[22],{93:function(e,t,r){"use strict";r.r(t),r.d(t,"frontMatter",(function(){return i})),r.d(t,"metadata",(function(){return c})),r.d(t,"toc",(function(){return u})),r.d(t,"default",(function(){return l}));var n=r(3),o=r(7),a=(r(0),r(99)),i={id:"howtos",title:"Community Guides",sidebar_label:"Community Guides"},c={unversionedId:"usage/howtos",id:"usage/howtos",isDocsHomePage:!1,title:"Community Guides",description:"Communitiy Guides/How-To's",source:"@site/docs/usage/howtos.md",slug:"/usage/howtos",permalink:"/frigate/usage/howtos",editUrl:"https://github.com/blakeblackshear/frigate/edit/master/docs/docs/usage/howtos.md",version:"current",sidebar_label:"Community Guides"},u=[{value:"Communitiy Guides/How-To's",id:"communitiy-guideshow-tos",children:[]}],s={toc:u};function l(e){var t=e.components,r=Object(o.a)(e,["components"]);return Object(a.b)("wrapper",Object(n.a)({},s,r,{components:t,mdxType:"MDXLayout"}),Object(a.b)("h2",{id:"communitiy-guideshow-tos"},"Communitiy Guides/How-To's"),Object(a.b)("ul",null,Object(a.b)("li",{parentName:"ul"},"Best Camera AI Person & Object Detection - How to Setup Frigate w/ Home Assistant - digiblurDIY ",Object(a.b)("a",Object(n.a)({parentName:"li"},{href:"https://youtu.be/V8vGdoYO6-Y"}),"YouTube")," - ",Object(a.b)("a",Object(n.a)({parentName:"li"},{href:"https://www.digiblur.com/2021/05/how-to-setup-frigate-home-assistant.html"}),"Article")),Object(a.b)("li",{parentName:"ul"},"Even More Free Local Object Detection with Home Assistant - Frigate Install - Everything Smart Home ",Object(a.b)("a",Object(n.a)({parentName:"li"},{href:"https://youtu.be/pqDCEZSVeRk"}),"YouTube")),Object(a.b)("li",{parentName:"ul"},"Home Assistant Frigate integration for local image recognition - KPeyanski ",Object(a.b)("a",Object(n.a)({parentName:"li"},{href:"https://youtu.be/Q2UT78lFQpo"}),"YouTube")," - ",Object(a.b)("a",Object(n.a)({parentName:"li"},{href:"https://peyanski.com/home-assistant-frigate-integration/"}),"Article"))))}l.isMDXComponent=!0},99:function(e,t,r){"use strict";r.d(t,"a",(function(){return p})),r.d(t,"b",(function(){return f}));var n=r(0),o=r.n(n);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function c(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?i(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):i(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function u(e,t){if(null==e)return{};var r,n,o=function(e,t){if(null==e)return{};var r,n,o={},a=Object.keys(e);for(n=0;n<a.length;n++)r=a[n],t.indexOf(r)>=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n<a.length;n++)r=a[n],t.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}var s=o.a.createContext({}),l=function(e){var t=o.a.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):c(c({},t),e)),r},p=function(e){var t=l(e.components);return o.a.createElement(s.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return o.a.createElement(o.a.Fragment,{},t)}},b=o.a.forwardRef((function(e,t){var r=e.components,n=e.mdxType,a=e.originalType,i=e.parentName,s=u(e,["components","mdxType","originalType","parentName"]),p=l(r),b=n,f=p["".concat(i,".").concat(b)]||p[b]||m[b]||a;return r?o.a.createElement(f,c(c({ref:t},s),{},{components:r})):o.a.createElement(f,c({ref:t},s))}));function f(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var a=r.length,i=new Array(a);i[0]=b;var c={};for(var u in t)hasOwnProperty.call(t,u)&&(c[u]=t[u]);c.originalType=e,c.mdxType="string"==typeof e?e:n,i[1]=c;for(var s=2;s<a;s++)i[s]=r[s];return o.a.createElement.apply(null,i)}return o.a.createElement.apply(null,r)}b.displayName="MDXCreateElement"}}]);
|
||||
@@ -1,48 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="generator" content="Docusaurus v2.0.0-alpha.70">
|
||||
<link rel="alternate" type="application/rss+xml" href="/frigate/blog/rss.xml" title="Frigate Blog RSS Feed">
|
||||
<link rel="alternate" type="application/atom+xml" href="/frigate/blog/atom.xml" title="Frigate Blog Atom Feed">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="Frigate" href="/frigate/opensearch.xml"><title data-react-helmet="true">nVidia hardware decoder | Frigate</title><meta data-react-helmet="true" name="twitter:card" content="summary_large_image"><meta data-react-helmet="true" name="docsearch:language" content="en"><meta data-react-helmet="true" name="docsearch:version" content="current"><meta data-react-helmet="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-react-helmet="true" property="og:title" content="nVidia hardware decoder | Frigate"><meta data-react-helmet="true" name="description" content="Certain nvidia cards include a hardware decoder, which can greatly improve the"><meta data-react-helmet="true" property="og:description" content="Certain nvidia cards include a hardware decoder, which can greatly improve the"><meta data-react-helmet="true" property="og:url" content="https://blakeblackshear.github.io/frigate/configuration/nvdec"><link data-react-helmet="true" rel="shortcut icon" href="/frigate/img/favicon.ico"><link data-react-helmet="true" rel="preconnect" href="https://BH4D9OD16A-dsn.algolia.net" crossorigin="anonymous"><link data-react-helmet="true" rel="canonical" href="https://blakeblackshear.github.io/frigate/configuration/nvdec"><link rel="stylesheet" href="/frigate/styles.4dd8d972.css">
|
||||
<link rel="preload" href="/frigate/styles.0df63e1c.js" as="script">
|
||||
<link rel="preload" href="/frigate/runtime~main.85a22073.js" as="script">
|
||||
<link rel="preload" href="/frigate/main.b6b2d1f0.js" as="script">
|
||||
<link rel="preload" href="/frigate/1.d4a988ac.js" as="script">
|
||||
<link rel="preload" href="/frigate/2.cbe00df1.js" as="script">
|
||||
<link rel="preload" href="/frigate/28.fabd8c68.js" as="script">
|
||||
<link rel="preload" href="/frigate/31.3f82c6fa.js" as="script">
|
||||
<link rel="preload" href="/frigate/935f2afb.06dae20f.js" as="script">
|
||||
<link rel="preload" href="/frigate/17896441.da8a454f.js" as="script">
|
||||
<link rel="preload" href="/frigate/57316f1e.8b05c337.js" as="script">
|
||||
</head>
|
||||
<body>
|
||||
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}()</script><div id="__docusaurus">
|
||||
<nav aria-label="Skip navigation links"><button type="button" tabindex="0" class="skipToContent_11B0">Skip to main content</button></nav><nav class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><div aria-label="Navigation bar toggle" class="navbar__toggle" role="button" tabindex="0"><svg aria-label="Menu" width="30" height="30" viewBox="0 0 30 30" role="img" focusable="false"><title>Menu</title><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></div><a class="navbar__brand" href="/frigate/"><img src="/frigate/img/logo.svg" alt="Frigate" class="themedImage_YANc themedImage--light_3CMI navbar__logo"><img src="/frigate/img/logo-dark.svg" alt="Frigate" class="themedImage_YANc themedImage--dark_3ARp navbar__logo"><strong class="navbar__title">Frigate</strong></a><a class="navbar__item navbar__link" href="/frigate/">Docs</a></div><div class="navbar__items navbar__items--right"><a href="https://github.com/blakeblackshear/frigate" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">GitHub</a><div class="react-toggle react-toggle--disabled displayOnlyInLargeViewport_2N3Q"><div class="react-toggle-track"><div class="react-toggle-track-check"><span class="toggle_3NWk">🌜</span></div><div class="react-toggle-track-x"><span class="toggle_3NWk">🌞</span></div></div><div class="react-toggle-thumb"></div><input type="checkbox" disabled="" aria-label="Dark mode toggle" class="react-toggle-screenreader-only"></div><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><div class="DocSearch-Button-Container"><svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></div></button></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div><div class="navbar-sidebar"><div class="navbar-sidebar__brand"><a class="navbar__brand" href="/frigate/"><img src="/frigate/img/logo.svg" alt="Frigate" class="themedImage_YANc themedImage--light_3CMI navbar__logo"><img src="/frigate/img/logo-dark.svg" alt="Frigate" class="themedImage_YANc themedImage--dark_3ARp navbar__logo"><strong class="navbar__title">Frigate</strong></a></div><div class="navbar-sidebar__items"><div class="menu"><ul class="menu__list"><li class="menu__list-item"><a class="menu__link" href="/frigate/">Docs</a></li><li class="menu__list-item"><a href="https://github.com/blakeblackshear/frigate" target="_blank" rel="noopener noreferrer" class="menu__link">GitHub</a></li></ul></div></div></div></nav><div class="main-wrapper"><div class="docPage_vMrn"><main class="docMainContainer_2iGs"><div class="container padding-vert--lg docItemWrapper_1bxp"><div class="row"><div class="col docItemCol_U38p"><div class="docItemContainer_a7m4"><article><header><h1 class="docTitle_Oumm">nVidia hardware decoder</h1></header><div class="markdown"><p>Certain nvidia cards include a hardware decoder, which can greatly improve the
|
||||
performance of video decoding. In order to use NVDEC, a special build of
|
||||
ffmpeg with NVDEC support is required. The special docker architecture 'amd64nvidia'
|
||||
includes this support for amd64 platforms. An aarch64 for the Jetson, which
|
||||
also includes NVDEC may be added in the future.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_prK2" id="docker-setup"></a>Docker setup<a class="hash-link" href="#docker-setup" title="Direct link to heading">#</a></h2><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_prK2" id="requirements"></a>Requirements<a class="hash-link" href="#requirements" title="Direct link to heading">#</a></h3><p><a href="https://www.nvidia.com/en-us/drivers/unix/" target="_blank" rel="noopener noreferrer">nVidia closed source driver</a> required to access NVDEC.
|
||||
<a href="https://github.com/NVIDIA/nvidia-docker" target="_blank" rel="noopener noreferrer">nvidia-docker</a> required to pass NVDEC to docker.</p><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_prK2" id="setting-up-docker-compose"></a>Setting up docker-compose<a class="hash-link" href="#setting-up-docker-compose" title="Direct link to heading">#</a></h3><p>In order to pass NVDEC, the docker engine must be set to <code>nvidia</code> and the environment variables
|
||||
<code>NVIDIA_VISIBLE_DEVICES=all</code> and <code>NVIDIA_DRIVER_CAPABILITIES=compute,utility,video</code> must be set.</p><p>In a docker compose file, these lines need to be set:</p><div class="mdxCodeBlock_1zKU"><div class="codeBlockContent_actS"><div tabindex="0" class="prism-code language-undefined codeBlock_tuNs thin-scrollbar"><div class="codeBlockLines_3uvA" style="color:#bfc7d5;background-color:#292d3e"><div class="token-line" style="color:#bfc7d5"><span class="token plain">services:</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> frigate:</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> ...</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> image: blakeblackshear/frigate:stable-amd64nvidia</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> runtime: nvidia</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> environment:</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> - NVIDIA_VISIBLE_DEVICES=all</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> - NVIDIA_DRIVER_CAPABILITIES=compute,utility,video</span></div></div></div><button type="button" aria-label="Copy code to clipboard" class="copyButton_2GIj">Copy</button></div></div><h3><a aria-hidden="true" tabindex="-1" class="anchor enhancedAnchor_prK2" id="setting-up-the-configuration-file"></a>Setting up the configuration file<a class="hash-link" href="#setting-up-the-configuration-file" title="Direct link to heading">#</a></h3><p>In your frigate config.yml, you'll need to set ffmpeg to use the hardware decoder.
|
||||
The decoder you choose will depend on the input video.</p><p>A list of supported codecs (you can use <code>ffmpeg -decoders | grep cuvid</code> in the container to get a list)</p><div class="mdxCodeBlock_1zKU"><div class="codeBlockContent_actS"><div tabindex="0" class="prism-code language-undefined codeBlock_tuNs thin-scrollbar"><div class="codeBlockLines_3uvA" style="color:#bfc7d5;background-color:#292d3e"><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... h263_cuvid Nvidia CUVID H263 decoder (codec h263)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... h264_cuvid Nvidia CUVID H264 decoder (codec h264)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... hevc_cuvid Nvidia CUVID HEVC decoder (codec hevc)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... mjpeg_cuvid Nvidia CUVID MJPEG decoder (codec mjpeg)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... mpeg1_cuvid Nvidia CUVID MPEG1VIDEO decoder (codec mpeg1video)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... mpeg2_cuvid Nvidia CUVID MPEG2VIDEO decoder (codec mpeg2video)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... mpeg4_cuvid Nvidia CUVID MPEG4 decoder (codec mpeg4)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... vc1_cuvid Nvidia CUVID VC1 decoder (codec vc1)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... vp8_cuvid Nvidia CUVID VP8 decoder (codec vp8)</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> V..... vp9_cuvid Nvidia CUVID VP9 decoder (codec vp9)</span></div></div></div><button type="button" aria-label="Copy code to clipboard" class="copyButton_2GIj">Copy</button></div></div><p>For example, for H265 video (hevc), you'll select <code>hevc_cuvid</code>. Add
|
||||
<code>-c:v hevc_cuvid</code> to your ffmpeg input arguments:</p><div class="mdxCodeBlock_1zKU"><div class="codeBlockContent_actS"><div tabindex="0" class="prism-code language-undefined codeBlock_tuNs thin-scrollbar"><div class="codeBlockLines_3uvA" style="color:#bfc7d5;background-color:#292d3e"><div class="token-line" style="color:#bfc7d5"><span class="token plain">ffmpeg:</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> input_args:</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> ...</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> - -c:v</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> - hevc_cuvid</span></div></div></div><button type="button" aria-label="Copy code to clipboard" class="copyButton_2GIj">Copy</button></div></div><p>If everything is working correctly, you should see a significant improvement in performance.
|
||||
Verify that hardware decoding is working by running <code>nvidia-smi</code>, which should show the ffmpeg
|
||||
processes:</p><div class="mdxCodeBlock_1zKU"><div class="codeBlockContent_actS"><div tabindex="0" class="prism-code language-undefined codeBlock_tuNs thin-scrollbar"><div class="codeBlockLines_3uvA" style="color:#bfc7d5;background-color:#292d3e"><div class="token-line" style="color:#bfc7d5"><span class="token plain">+-----------------------------------------------------------------------------+</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| NVIDIA-SMI 455.38 Driver Version: 455.38 CUDA Version: 11.1 |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">|-------------------------------+----------------------+----------------------+</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| | | MIG M. |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">|===============================+======================+======================|</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 0 GeForce GTX 166... Off | 00000000:03:00.0 Off | N/A |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 38% 41C P2 36W / 125W | 2082MiB / 5942MiB | 5% Default |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| | | N/A |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">+-------------------------------+----------------------+----------------------+</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block">
|
||||
</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">+-----------------------------------------------------------------------------+</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| Processes: |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| GPU GI CI PID Type Process name GPU Memory |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| ID ID Usage |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">|=============================================================================|</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 0 N/A N/A 12737 C ffmpeg 249MiB |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 0 N/A N/A 12751 C ffmpeg 249MiB |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 0 N/A N/A 12772 C ffmpeg 249MiB |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 0 N/A N/A 12775 C ffmpeg 249MiB |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 0 N/A N/A 12800 C ffmpeg 249MiB |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 0 N/A N/A 12811 C ffmpeg 417MiB |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">| 0 N/A N/A 12827 C ffmpeg 417MiB |</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain">+-----------------------------------------------------------------------------+</span></div></div></div><button type="button" aria-label="Copy code to clipboard" class="copyButton_2GIj">Copy</button></div></div><p>To further improve performance, you can set ffmpeg to skip frames in the output,
|
||||
using the fps filter:</p><div class="mdxCodeBlock_1zKU"><div class="codeBlockContent_actS"><div tabindex="0" class="prism-code language-undefined codeBlock_tuNs thin-scrollbar"><div class="codeBlockLines_3uvA" style="color:#bfc7d5;background-color:#292d3e"><div class="token-line" style="color:#bfc7d5"><span class="token plain"> output_args:</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> - -filter:v</span></div><div class="token-line" style="color:#bfc7d5"><span class="token plain"> - fps=fps=5</span></div></div></div><button type="button" aria-label="Copy code to clipboard" class="copyButton_2GIj">Copy</button></div></div><p>This setting, for example, allows Frigate to consume my 10-15fps camera streams on
|
||||
my relatively low powered Haswell machine with relatively low cpu usage.</p></div></article><div class="margin-vert--xl"><div class="row"><div class="col"><a href="https://github.com/blakeblackshear/frigate/edit/master/docs/docs/configuration/nvdec.md" target="_blank" rel="noreferrer noopener"><svg fill="currentColor" height="1.2em" width="1.2em" preserveAspectRatio="xMidYMid meet" role="img" viewBox="0 0 40 40" class="iconEdit_2LL7"><g><path d="m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"></path></g></svg>Edit this page</a></div></div></div><div class="margin-vert--lg"><nav class="pagination-nav" aria-label="Blog list page navigation"><div class="pagination-nav__item"></div><div class="pagination-nav__item pagination-nav__item--next"></div></nav></div></div></div><div class="col col--3"><div class="tableOfContents_2xL- thin-scrollbar"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#docker-setup" class="table-of-contents__link">Docker setup</a><ul><li><a href="#requirements" class="table-of-contents__link">Requirements</a></li><li><a href="#setting-up-docker-compose" class="table-of-contents__link">Setting up docker-compose</a></li><li><a href="#setting-up-the-configuration-file" class="table-of-contents__link">Setting up the configuration file</a></li></ul></li></ul></div></div></div></div></main></div></div><footer class="footer footer--dark"><div class="container"><div class="row footer__links"><div class="col footer__col"><h4 class="footer__title">Community</h4><ul class="footer__items"><li class="footer__item"><a href="https://github.com/blakeblackshear/frigate" target="_blank" rel="noopener noreferrer" class="footer__link-item">GitHub</a></li><li class="footer__item"><a href="https://github.com/blakeblackshear/frigate/discussions" target="_blank" rel="noopener noreferrer" class="footer__link-item">Discussions</a></li></ul></div></div><div class="footer__bottom text--center"><div class="footer__copyright">Copyright © 2021 Blake Blackshear</div></div></div></footer></div>
|
||||
<script src="/frigate/styles.0df63e1c.js"></script>
|
||||
<script src="/frigate/runtime~main.85a22073.js"></script>
|
||||
<script src="/frigate/main.b6b2d1f0.js"></script>
|
||||
<script src="/frigate/1.d4a988ac.js"></script>
|
||||
<script src="/frigate/2.cbe00df1.js"></script>
|
||||
<script src="/frigate/28.fabd8c68.js"></script>
|
||||
<script src="/frigate/31.3f82c6fa.js"></script>
|
||||
<script src="/frigate/935f2afb.06dae20f.js"></script>
|
||||
<script src="/frigate/17896441.da8a454f.js"></script>
|
||||
<script src="/frigate/57316f1e.8b05c337.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
FROM frigate-base
|
||||
LABEL maintainer "blakeb@blakeshome.com"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
# Install packages for apt repo
|
||||
RUN apt-get -qq update \
|
||||
&& apt-get -qq install --no-install-recommends -y \
|
||||
# ffmpeg runtime dependencies
|
||||
libgomp1 \
|
||||
# runtime dependencies
|
||||
libopenexr24 \
|
||||
libgstreamer1.0-0 \
|
||||
libgstreamer-plugins-base1.0-0 \
|
||||
libopenblas-base \
|
||||
libjpeg-turbo8 \
|
||||
libpng16-16 \
|
||||
libtiff5 \
|
||||
libdc1394-22 \
|
||||
## Tensorflow lite
|
||||
&& pip3 install https://github.com/google-coral/pycoral/releases/download/release-frogfish/tflite_runtime-2.5.0-cp38-cp38-linux_aarch64.whl \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& (apt-get autoremove -y; apt-get autoclean -y)
|
||||
@@ -0,0 +1,18 @@
|
||||
FROM frigate-base
|
||||
LABEL maintainer "blakeb@blakeshome.com"
|
||||
|
||||
# By default, use the i965 driver
|
||||
ENV LIBVA_DRIVER_NAME=i965
|
||||
# Install packages for apt repo
|
||||
RUN apt-get -qq update \
|
||||
&& apt-get -qq install --no-install-recommends -y \
|
||||
# ffmpeg dependencies
|
||||
libgomp1 \
|
||||
# VAAPI drivers for Intel hardware accel
|
||||
libva-drm2 libva2 libmfx1 i965-va-driver vainfo intel-media-va-driver mesa-va-drivers \
|
||||
## Tensorflow lite
|
||||
&& wget -q https://github.com/google-coral/pycoral/releases/download/release-frogfish/tflite_runtime-2.5.0-cp38-cp38-linux_x86_64.whl \
|
||||
&& python3.8 -m pip install tflite_runtime-2.5.0-cp38-cp38-linux_x86_64.whl \
|
||||
&& rm tflite_runtime-2.5.0-cp38-cp38-linux_x86_64.whl \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& (apt-get autoremove -y; apt-get autoclean -y)
|
||||
@@ -0,0 +1,47 @@
|
||||
FROM frigate-base
|
||||
LABEL maintainer "blakeb@blakeshome.com"
|
||||
|
||||
# Install packages for apt repo
|
||||
RUN apt-get -qq update \
|
||||
&& apt-get -qq install --no-install-recommends -y \
|
||||
# ffmpeg dependencies
|
||||
libgomp1 \
|
||||
## Tensorflow lite
|
||||
&& wget -q https://github.com/google-coral/pycoral/releases/download/release-frogfish/tflite_runtime-2.5.0-cp38-cp38-linux_x86_64.whl \
|
||||
&& python3.8 -m pip install tflite_runtime-2.5.0-cp38-cp38-linux_x86_64.whl \
|
||||
&& rm tflite_runtime-2.5.0-cp38-cp38-linux_x86_64.whl \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& (apt-get autoremove -y; apt-get autoclean -y)
|
||||
|
||||
|
||||
# nvidia layer (see https://gitlab.com/nvidia/container-images/cuda/blob/master/dist/11.1/ubuntu20.04-x86_64/base/Dockerfile)
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES compute,utility,video
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gnupg2 curl ca-certificates && \
|
||||
curl -fsSL https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/7fa2af80.pub | apt-key add - && \
|
||||
echo "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64 /" > /etc/apt/sources.list.d/cuda.list && \
|
||||
echo "deb https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu2004/x86_64 /" > /etc/apt/sources.list.d/nvidia-ml.list && \
|
||||
apt-get purge --autoremove -y curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV CUDA_VERSION 11.1.1
|
||||
|
||||
# For libraries in the cuda-compat-* package: https://docs.nvidia.com/cuda/eula/index.html#attachment-a
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
cuda-cudart-11-1=11.1.74-1 \
|
||||
cuda-compat-11-1 \
|
||||
&& ln -s cuda-11.1 /usr/local/cuda && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Required for nvidia-docker v1
|
||||
RUN echo "/usr/local/nvidia/lib" >> /etc/ld.so.conf.d/nvidia.conf && \
|
||||
echo "/usr/local/nvidia/lib64" >> /etc/ld.so.conf.d/nvidia.conf
|
||||
|
||||
ENV PATH /usr/local/nvidia/bin:/usr/local/cuda/bin:${PATH}
|
||||
ENV LD_LIBRARY_PATH /usr/local/nvidia/lib:/usr/local/nvidia/lib64
|
||||
|
||||
# nvidia-container-runtime
|
||||
ENV NVIDIA_VISIBLE_DEVICES all
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES compute,utility,video
|
||||
ENV NVIDIA_REQUIRE_CUDA "cuda>=11.1 brand=tesla,driver>=418,driver<419 brand=tesla,driver>=440,driver<441 brand=tesla,driver>=450,driver<451"
|
||||
@@ -0,0 +1,24 @@
|
||||
FROM frigate-base
|
||||
LABEL maintainer "blakeb@blakeshome.com"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
# Install packages for apt repo
|
||||
RUN apt-get -qq update \
|
||||
&& apt-get -qq install --no-install-recommends -y \
|
||||
# ffmpeg runtime dependencies
|
||||
libgomp1 \
|
||||
# runtime dependencies
|
||||
libopenexr24 \
|
||||
libgstreamer1.0-0 \
|
||||
libgstreamer-plugins-base1.0-0 \
|
||||
libopenblas-base \
|
||||
libjpeg-turbo8 \
|
||||
libpng16-16 \
|
||||
libtiff5 \
|
||||
libdc1394-22 \
|
||||
libaom0 \
|
||||
libx265-179 \
|
||||
## Tensorflow lite
|
||||
&& pip3 install https://github.com/google-coral/pycoral/releases/download/release-frogfish/tflite_runtime-2.5.0-cp38-cp38-linux_armv7l.whl \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& (apt-get autoremove -y; apt-get autoclean -y)
|
||||
@@ -0,0 +1,58 @@
|
||||
ARG ARCH=amd64
|
||||
ARG WHEELS_VERSION
|
||||
ARG FFMPEG_VERSION
|
||||
FROM blakeblackshear/frigate-wheels:${WHEELS_VERSION}-${ARCH} as wheels
|
||||
FROM blakeblackshear/frigate-ffmpeg:${FFMPEG_VERSION}-${ARCH} as ffmpeg
|
||||
FROM frigate-web as web
|
||||
|
||||
FROM ubuntu:20.04
|
||||
LABEL maintainer "blakeb@blakeshome.com"
|
||||
|
||||
COPY --from=ffmpeg /usr/local /usr/local/
|
||||
|
||||
COPY --from=wheels /wheels/. /wheels/
|
||||
|
||||
ENV FLASK_ENV=development
|
||||
# ENV FONTCONFIG_PATH=/etc/fonts
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
# Install packages for apt repo
|
||||
RUN apt-get -qq update \
|
||||
&& apt-get upgrade -y \
|
||||
&& apt-get -qq install --no-install-recommends -y \
|
||||
gnupg wget unzip tzdata nginx libnginx-mod-rtmp \
|
||||
&& apt-get -qq install --no-install-recommends -y \
|
||||
python3-pip \
|
||||
&& pip3 install -U /wheels/*.whl \
|
||||
&& APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn apt-key adv --fetch-keys https://packages.cloud.google.com/apt/doc/apt-key.gpg \
|
||||
&& echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" > /etc/apt/sources.list.d/coral-edgetpu.list \
|
||||
&& echo "libedgetpu1-max libedgetpu/accepted-eula select true" | debconf-set-selections \
|
||||
&& apt-get -qq update && apt-get -qq install --no-install-recommends -y \
|
||||
libedgetpu1-max=15.0 \
|
||||
&& rm -rf /var/lib/apt/lists/* /wheels \
|
||||
&& (apt-get autoremove -y; apt-get autoclean -y)
|
||||
|
||||
RUN pip3 install \
|
||||
peewee_migrate \
|
||||
zeroconf \
|
||||
voluptuous
|
||||
|
||||
COPY nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# get model and labels
|
||||
COPY labelmap.txt /labelmap.txt
|
||||
RUN wget -q https://github.com/google-coral/test_data/raw/master/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite -O /edgetpu_model.tflite
|
||||
RUN wget -q https://github.com/google-coral/test_data/raw/master/ssdlite_mobiledet_coco_qat_postprocess.tflite -O /cpu_model.tflite
|
||||
|
||||
WORKDIR /opt/frigate/
|
||||
ADD frigate frigate/
|
||||
ADD migrations migrations/
|
||||
|
||||
COPY --from=web /opt/frigate/build web/
|
||||
|
||||
COPY run.sh /run.sh
|
||||
RUN chmod +x /run.sh
|
||||
|
||||
EXPOSE 5000
|
||||
EXPOSE 1935
|
||||
|
||||
CMD ["/run.sh"]
|
||||
@@ -0,0 +1,474 @@
|
||||
# inspired by:
|
||||
# https://github.com/collelog/ffmpeg/blob/master/4.3.1-alpine-rpi4-arm64v8.Dockerfile
|
||||
# https://github.com/mmastrac/ffmpeg-omx-rpi-docker/blob/master/Dockerfile
|
||||
# https://github.com/jrottenberg/ffmpeg/pull/158/files
|
||||
# https://github.com/jrottenberg/ffmpeg/pull/239
|
||||
FROM ubuntu:20.04 AS base
|
||||
|
||||
WORKDIR /tmp/workdir
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y
|
||||
|
||||
FROM base as build
|
||||
|
||||
ENV FFMPEG_VERSION=4.3.1 \
|
||||
AOM_VERSION=v1.0.0 \
|
||||
FDKAAC_VERSION=0.1.5 \
|
||||
FREETYPE_VERSION=2.5.5 \
|
||||
FRIBIDI_VERSION=0.19.7 \
|
||||
KVAZAAR_VERSION=1.2.0 \
|
||||
LAME_VERSION=3.100 \
|
||||
LIBPTHREAD_STUBS_VERSION=0.4 \
|
||||
LIBVIDSTAB_VERSION=1.1.0 \
|
||||
LIBXCB_VERSION=1.13.1 \
|
||||
XCBPROTO_VERSION=1.13 \
|
||||
OGG_VERSION=1.3.2 \
|
||||
OPENCOREAMR_VERSION=0.1.5 \
|
||||
OPUS_VERSION=1.2 \
|
||||
OPENJPEG_VERSION=2.1.2 \
|
||||
THEORA_VERSION=1.1.1 \
|
||||
VORBIS_VERSION=1.3.5 \
|
||||
VPX_VERSION=1.8.0 \
|
||||
WEBP_VERSION=1.0.2 \
|
||||
X264_VERSION=20170226-2245-stable \
|
||||
X265_VERSION=3.1.1 \
|
||||
XAU_VERSION=1.0.9 \
|
||||
XORG_MACROS_VERSION=1.19.2 \
|
||||
XPROTO_VERSION=7.0.31 \
|
||||
XVID_VERSION=1.3.4 \
|
||||
LIBZMQ_VERSION=4.3.2 \
|
||||
SRC=/usr/local
|
||||
|
||||
ARG FREETYPE_SHA256SUM="5d03dd76c2171a7601e9ce10551d52d4471cf92cd205948e60289251daddffa8 freetype-2.5.5.tar.gz"
|
||||
ARG FRIBIDI_SHA256SUM="3fc96fa9473bd31dcb5500bdf1aa78b337ba13eb8c301e7c28923fea982453a8 0.19.7.tar.gz"
|
||||
ARG LIBVIDSTAB_SHA256SUM="14d2a053e56edad4f397be0cb3ef8eb1ec3150404ce99a426c4eb641861dc0bb v1.1.0.tar.gz"
|
||||
ARG OGG_SHA256SUM="e19ee34711d7af328cb26287f4137e70630e7261b17cbe3cd41011d73a654692 libogg-1.3.2.tar.gz"
|
||||
ARG OPUS_SHA256SUM="77db45a87b51578fbc49555ef1b10926179861d854eb2613207dc79d9ec0a9a9 opus-1.2.tar.gz"
|
||||
ARG THEORA_SHA256SUM="40952956c47811928d1e7922cda3bc1f427eb75680c3c37249c91e949054916b libtheora-1.1.1.tar.gz"
|
||||
ARG VORBIS_SHA256SUM="6efbcecdd3e5dfbf090341b485da9d176eb250d893e3eb378c428a2db38301ce libvorbis-1.3.5.tar.gz"
|
||||
ARG XVID_SHA256SUM="4e9fd62728885855bc5007fe1be58df42e5e274497591fec37249e1052ae316f xvidcore-1.3.4.tar.gz"
|
||||
ARG LIBZMQ_SHA256SUM="02ecc88466ae38cf2c8d79f09cfd2675ba299a439680b64ade733e26a349edeb v4.3.2.tar.gz"
|
||||
|
||||
|
||||
ARG LD_LIBRARY_PATH=/opt/ffmpeg/lib
|
||||
ARG MAKEFLAGS="-j2"
|
||||
ARG PKG_CONFIG_PATH="/opt/ffmpeg/share/pkgconfig:/opt/ffmpeg/lib/pkgconfig:/opt/ffmpeg/lib64/pkgconfig"
|
||||
ARG PREFIX=/opt/ffmpeg
|
||||
ARG LD_LIBRARY_PATH="/opt/ffmpeg/lib:/opt/ffmpeg/lib64:/usr/lib64:/usr/lib:/lib64:/lib"
|
||||
|
||||
|
||||
RUN buildDeps="autoconf \
|
||||
automake \
|
||||
cmake \
|
||||
curl \
|
||||
bzip2 \
|
||||
libexpat1-dev \
|
||||
g++ \
|
||||
gcc \
|
||||
git \
|
||||
gperf \
|
||||
libtool \
|
||||
make \
|
||||
nasm \
|
||||
perl \
|
||||
pkg-config \
|
||||
python \
|
||||
libssl-dev \
|
||||
yasm \
|
||||
linux-headers-raspi2 \
|
||||
libomxil-bellagio-dev \
|
||||
zlib1g-dev" && \
|
||||
apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ${buildDeps}
|
||||
## opencore-amr https://sourceforge.net/projects/opencore-amr/
|
||||
RUN \
|
||||
DIR=/tmp/opencore-amr && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://versaweb.dl.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-${OPENCOREAMR_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## x264 http://www.videolan.org/developers/x264.html
|
||||
RUN \
|
||||
DIR=/tmp/x264 && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://download.videolan.org/pub/videolan/x264/snapshots/x264-snapshot-${X264_VERSION}.tar.bz2 | \
|
||||
tar -jx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared --enable-pic --disable-cli && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### x265 http://x265.org/
|
||||
RUN \
|
||||
DIR=/tmp/x265 && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://download.videolan.org/pub/videolan/x265/x265_${X265_VERSION}.tar.gz | \
|
||||
tar -zx && \
|
||||
cd x265_${X265_VERSION}/build/linux && \
|
||||
sed -i "/-DEXTRA_LIB/ s/$/ -DCMAKE_INSTALL_PREFIX=\${PREFIX}/" multilib.sh && \
|
||||
sed -i "/^cmake/ s/$/ -DENABLE_CLI=OFF/" multilib.sh && \
|
||||
export CXXFLAGS="${CXXFLAGS} -fPIC" && \
|
||||
./multilib.sh && \
|
||||
make -C 8bit install && \
|
||||
rm -rf ${DIR}
|
||||
### libogg https://www.xiph.org/ogg/
|
||||
RUN \
|
||||
DIR=/tmp/ogg && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/ogg/libogg-${OGG_VERSION}.tar.gz && \
|
||||
echo ${OGG_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libogg-${OGG_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libopus https://www.opus-codec.org/
|
||||
RUN \
|
||||
DIR=/tmp/opus && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://archive.mozilla.org/pub/opus/opus-${OPUS_VERSION}.tar.gz && \
|
||||
echo ${OPUS_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f opus-${OPUS_VERSION}.tar.gz && \
|
||||
autoreconf -fiv && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libvorbis https://xiph.org/vorbis/
|
||||
RUN \
|
||||
DIR=/tmp/vorbis && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/vorbis/libvorbis-${VORBIS_VERSION}.tar.gz && \
|
||||
echo ${VORBIS_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libvorbis-${VORBIS_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libtheora http://www.theora.org/
|
||||
RUN \
|
||||
DIR=/tmp/theora && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/theora/libtheora-${THEORA_VERSION}.tar.gz && \
|
||||
echo ${THEORA_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libtheora-${THEORA_VERSION}.tar.gz && \
|
||||
curl -sL 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' -o config.guess && \
|
||||
curl -sL 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub && \
|
||||
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libvpx https://www.webmproject.org/code/
|
||||
RUN \
|
||||
DIR=/tmp/vpx && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://codeload.github.com/webmproject/libvpx/tar.gz/v${VPX_VERSION} | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-vp8 --enable-vp9 --enable-vp9-highbitdepth --enable-pic --enable-shared \
|
||||
--disable-debug --disable-examples --disable-docs --disable-install-bins && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libwebp https://developers.google.com/speed/webp/
|
||||
RUN \
|
||||
DIR=/tmp/vebp && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${WEBP_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libmp3lame http://lame.sourceforge.net/
|
||||
RUN \
|
||||
DIR=/tmp/lame && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://versaweb.dl.sourceforge.net/project/lame/lame/$(echo ${LAME_VERSION} | sed -e 's/[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z-]*\)/\1.\2/')/lame-${LAME_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" --enable-shared --enable-nasm --disable-frontend && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### xvid https://www.xvid.com/
|
||||
RUN \
|
||||
DIR=/tmp/xvid && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xvid.org/downloads/xvidcore-${XVID_VERSION}.tar.gz && \
|
||||
echo ${XVID_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx -f xvidcore-${XVID_VERSION}.tar.gz && \
|
||||
cd xvidcore/build/generic && \
|
||||
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### fdk-aac https://github.com/mstorsjo/fdk-aac
|
||||
RUN \
|
||||
DIR=/tmp/fdk-aac && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://github.com/mstorsjo/fdk-aac/archive/v${FDKAAC_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
autoreconf -fiv && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared --datadir="${DIR}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## openjpeg https://github.com/uclouvain/openjpeg
|
||||
RUN \
|
||||
DIR=/tmp/openjpeg && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
export CFLAGS="${CFLAGS} -DPNG_ARM_NEON_OPT=0" && \
|
||||
cmake -DBUILD_THIRDPARTY:BOOL=ON -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## freetype https://www.freetype.org/
|
||||
RUN \
|
||||
DIR=/tmp/freetype && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.gz && \
|
||||
echo ${FREETYPE_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f freetype-${FREETYPE_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## libvstab https://github.com/georgmartius/vid.stab
|
||||
RUN \
|
||||
DIR=/tmp/vid.stab && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/georgmartius/vid.stab/archive/v${LIBVIDSTAB_VERSION}.tar.gz && \
|
||||
echo ${LIBVIDSTAB_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f v${LIBVIDSTAB_VERSION}.tar.gz && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## fridibi https://www.fribidi.org/
|
||||
RUN \
|
||||
DIR=/tmp/fribidi && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/fribidi/fribidi/archive/${FRIBIDI_VERSION}.tar.gz && \
|
||||
echo ${FRIBIDI_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f ${FRIBIDI_VERSION}.tar.gz && \
|
||||
sed -i 's/^SUBDIRS =.*/SUBDIRS=gen.tab charset lib bin/' Makefile.am && \
|
||||
./bootstrap --no-config --auto && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j1 && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## kvazaar https://github.com/ultravideo/kvazaar
|
||||
RUN \
|
||||
DIR=/tmp/kvazaar && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/ultravideo/kvazaar/archive/v${KVAZAAR_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f v${KVAZAAR_VERSION}.tar.gz && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/aom && \
|
||||
git clone --branch ${AOM_VERSION} --depth 1 https://aomedia.googlesource.com/aom ${DIR} ; \
|
||||
cd ${DIR} ; \
|
||||
rm -rf CMakeCache.txt CMakeFiles ; \
|
||||
mkdir -p ./aom_build ; \
|
||||
cd ./aom_build ; \
|
||||
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DBUILD_SHARED_LIBS=1 ..; \
|
||||
make ; \
|
||||
make install ; \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libxcb (and supporting libraries) for screen capture https://xcb.freedesktop.org/
|
||||
RUN \
|
||||
DIR=/tmp/xorg-macros && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive//individual/util/util-macros-${XORG_MACROS_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f util-macros-${XORG_MACROS_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/xproto && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive/individual/proto/xproto-${XPROTO_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f xproto-${XPROTO_VERSION}.tar.gz && \
|
||||
curl -sL 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' -o config.guess && \
|
||||
curl -sL 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libXau && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive/individual/lib/libXau-${XAU_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libXau-${XAU_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libpthread-stubs && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libxcb-proto && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
|
||||
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libxcb && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/libxcb-${LIBXCB_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libxcb-${LIBXCB_VERSION}.tar.gz && \
|
||||
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libzmq https://github.com/zeromq/libzmq/
|
||||
RUN \
|
||||
DIR=/tmp/libzmq && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/zeromq/libzmq/archive/v${LIBZMQ_VERSION}.tar.gz && \
|
||||
echo ${LIBZMQ_SHA256SUM} | sha256sum --check && \
|
||||
tar -xz --strip-components=1 -f v${LIBZMQ_VERSION}.tar.gz && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make check && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## ffmpeg https://ffmpeg.org/
|
||||
RUN \
|
||||
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
|
||||
curl -sLO https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \
|
||||
tar -jx --strip-components=1 -f ffmpeg-${FFMPEG_VERSION}.tar.bz2
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
|
||||
./configure \
|
||||
--disable-debug \
|
||||
--disable-doc \
|
||||
--disable-ffplay \
|
||||
--enable-shared \
|
||||
--enable-avresample \
|
||||
--enable-libopencore-amrnb \
|
||||
--enable-libopencore-amrwb \
|
||||
--enable-gpl \
|
||||
--enable-libfreetype \
|
||||
--enable-libvidstab \
|
||||
--enable-libmp3lame \
|
||||
--enable-libopus \
|
||||
--enable-libtheora \
|
||||
--enable-libvorbis \
|
||||
--enable-libvpx \
|
||||
--enable-libwebp \
|
||||
--enable-libxcb \
|
||||
--enable-libx265 \
|
||||
--enable-libxvid \
|
||||
--enable-libx264 \
|
||||
--enable-nonfree \
|
||||
--enable-openssl \
|
||||
--enable-libfdk_aac \
|
||||
--enable-postproc \
|
||||
--enable-small \
|
||||
--enable-version3 \
|
||||
--enable-libzmq \
|
||||
--extra-libs=-ldl \
|
||||
--prefix="${PREFIX}" \
|
||||
--enable-libopenjpeg \
|
||||
--enable-libkvazaar \
|
||||
--enable-libaom \
|
||||
--extra-libs=-lpthread \
|
||||
# --enable-omx \
|
||||
# --enable-omx-rpi \
|
||||
# --enable-mmal \
|
||||
--enable-v4l2_m2m \
|
||||
--enable-neon \
|
||||
--extra-cflags="-I${PREFIX}/include" \
|
||||
--extra-ldflags="-L${PREFIX}/lib" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
make tools/zmqsend && cp tools/zmqsend ${PREFIX}/bin/ && \
|
||||
make distclean && \
|
||||
hash -r && \
|
||||
cd tools && \
|
||||
make qt-faststart && cp qt-faststart ${PREFIX}/bin/
|
||||
|
||||
## cleanup
|
||||
RUN \
|
||||
ldd ${PREFIX}/bin/ffmpeg | grep opt/ffmpeg | cut -d ' ' -f 3 | xargs -i cp {} /usr/local/lib/ && \
|
||||
for lib in /usr/local/lib/*.so.*; do ln -s "${lib##*/}" "${lib%%.so.*}".so; done && \
|
||||
cp ${PREFIX}/bin/* /usr/local/bin/ && \
|
||||
cp -r ${PREFIX}/share/ffmpeg /usr/local/share/ && \
|
||||
LD_LIBRARY_PATH=/usr/local/lib ffmpeg -buildconf && \
|
||||
cp -r ${PREFIX}/include/libav* ${PREFIX}/include/libpostproc ${PREFIX}/include/libsw* /usr/local/include && \
|
||||
mkdir -p /usr/local/lib/pkgconfig && \
|
||||
for pc in ${PREFIX}/lib/pkgconfig/libav*.pc ${PREFIX}/lib/pkgconfig/libpostproc.pc ${PREFIX}/lib/pkgconfig/libsw*.pc; do \
|
||||
sed "s:${PREFIX}:/usr/local:g" <"$pc" >/usr/local/lib/pkgconfig/"${pc##*/}"; \
|
||||
done
|
||||
|
||||
FROM base AS release
|
||||
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64:/usr/lib:/usr/lib64:/lib:/lib64
|
||||
|
||||
CMD ["--help"]
|
||||
ENTRYPOINT ["ffmpeg"]
|
||||
|
||||
COPY --from=build /usr/local /usr/local/
|
||||
|
||||
# Run ffmpeg with -c:v h264_v4l2m2m to enable HW accell for decoding on raspberry pi4 64-bit
|
||||
@@ -0,0 +1,468 @@
|
||||
# inspired by:
|
||||
# https://github.com/collelog/ffmpeg/blob/master/4.3.1-alpine-rpi4-arm64v8.Dockerfile
|
||||
# https://github.com/jrottenberg/ffmpeg/pull/158/files
|
||||
# https://github.com/jrottenberg/ffmpeg/pull/239
|
||||
FROM ubuntu:20.04 AS base
|
||||
|
||||
WORKDIR /tmp/workdir
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y
|
||||
|
||||
FROM base as build
|
||||
|
||||
ENV FFMPEG_VERSION=4.3.1 \
|
||||
AOM_VERSION=v1.0.0 \
|
||||
FDKAAC_VERSION=0.1.5 \
|
||||
FREETYPE_VERSION=2.5.5 \
|
||||
FRIBIDI_VERSION=0.19.7 \
|
||||
KVAZAAR_VERSION=1.2.0 \
|
||||
LAME_VERSION=3.100 \
|
||||
LIBPTHREAD_STUBS_VERSION=0.4 \
|
||||
LIBVIDSTAB_VERSION=1.1.0 \
|
||||
LIBXCB_VERSION=1.13.1 \
|
||||
XCBPROTO_VERSION=1.13 \
|
||||
OGG_VERSION=1.3.2 \
|
||||
OPENCOREAMR_VERSION=0.1.5 \
|
||||
OPUS_VERSION=1.2 \
|
||||
OPENJPEG_VERSION=2.1.2 \
|
||||
THEORA_VERSION=1.1.1 \
|
||||
VORBIS_VERSION=1.3.5 \
|
||||
VPX_VERSION=1.8.0 \
|
||||
WEBP_VERSION=1.0.2 \
|
||||
X264_VERSION=20170226-2245-stable \
|
||||
X265_VERSION=3.1.1 \
|
||||
XAU_VERSION=1.0.9 \
|
||||
XORG_MACROS_VERSION=1.19.2 \
|
||||
XPROTO_VERSION=7.0.31 \
|
||||
XVID_VERSION=1.3.4 \
|
||||
LIBZMQ_VERSION=4.3.2 \
|
||||
SRC=/usr/local
|
||||
|
||||
ARG FREETYPE_SHA256SUM="5d03dd76c2171a7601e9ce10551d52d4471cf92cd205948e60289251daddffa8 freetype-2.5.5.tar.gz"
|
||||
ARG FRIBIDI_SHA256SUM="3fc96fa9473bd31dcb5500bdf1aa78b337ba13eb8c301e7c28923fea982453a8 0.19.7.tar.gz"
|
||||
ARG LIBVIDSTAB_SHA256SUM="14d2a053e56edad4f397be0cb3ef8eb1ec3150404ce99a426c4eb641861dc0bb v1.1.0.tar.gz"
|
||||
ARG OGG_SHA256SUM="e19ee34711d7af328cb26287f4137e70630e7261b17cbe3cd41011d73a654692 libogg-1.3.2.tar.gz"
|
||||
ARG OPUS_SHA256SUM="77db45a87b51578fbc49555ef1b10926179861d854eb2613207dc79d9ec0a9a9 opus-1.2.tar.gz"
|
||||
ARG THEORA_SHA256SUM="40952956c47811928d1e7922cda3bc1f427eb75680c3c37249c91e949054916b libtheora-1.1.1.tar.gz"
|
||||
ARG VORBIS_SHA256SUM="6efbcecdd3e5dfbf090341b485da9d176eb250d893e3eb378c428a2db38301ce libvorbis-1.3.5.tar.gz"
|
||||
ARG XVID_SHA256SUM="4e9fd62728885855bc5007fe1be58df42e5e274497591fec37249e1052ae316f xvidcore-1.3.4.tar.gz"
|
||||
ARG LIBZMQ_SHA256SUM="02ecc88466ae38cf2c8d79f09cfd2675ba299a439680b64ade733e26a349edeb v4.3.2.tar.gz"
|
||||
|
||||
|
||||
ARG LD_LIBRARY_PATH=/opt/ffmpeg/lib
|
||||
ARG MAKEFLAGS="-j2"
|
||||
ARG PKG_CONFIG_PATH="/opt/ffmpeg/share/pkgconfig:/opt/ffmpeg/lib/pkgconfig:/opt/ffmpeg/lib64/pkgconfig"
|
||||
ARG PREFIX=/opt/ffmpeg
|
||||
ARG LD_LIBRARY_PATH="/opt/ffmpeg/lib:/opt/ffmpeg/lib64:/usr/lib64:/usr/lib:/lib64:/lib"
|
||||
|
||||
|
||||
RUN buildDeps="autoconf \
|
||||
automake \
|
||||
cmake \
|
||||
curl \
|
||||
bzip2 \
|
||||
libexpat1-dev \
|
||||
g++ \
|
||||
gcc \
|
||||
git \
|
||||
gperf \
|
||||
libtool \
|
||||
make \
|
||||
nasm \
|
||||
perl \
|
||||
pkg-config \
|
||||
python \
|
||||
libssl-dev \
|
||||
yasm \
|
||||
libva-dev \
|
||||
libmfx-dev \
|
||||
zlib1g-dev" && \
|
||||
apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ${buildDeps}
|
||||
## opencore-amr https://sourceforge.net/projects/opencore-amr/
|
||||
RUN \
|
||||
DIR=/tmp/opencore-amr && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://versaweb.dl.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-${OPENCOREAMR_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## x264 http://www.videolan.org/developers/x264.html
|
||||
RUN \
|
||||
DIR=/tmp/x264 && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://download.videolan.org/pub/videolan/x264/snapshots/x264-snapshot-${X264_VERSION}.tar.bz2 | \
|
||||
tar -jx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared --enable-pic --disable-cli && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### x265 http://x265.org/
|
||||
RUN \
|
||||
DIR=/tmp/x265 && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://download.videolan.org/pub/videolan/x265/x265_${X265_VERSION}.tar.gz | \
|
||||
tar -zx && \
|
||||
cd x265_${X265_VERSION}/build/linux && \
|
||||
sed -i "/-DEXTRA_LIB/ s/$/ -DCMAKE_INSTALL_PREFIX=\${PREFIX}/" multilib.sh && \
|
||||
sed -i "/^cmake/ s/$/ -DENABLE_CLI=OFF/" multilib.sh && \
|
||||
./multilib.sh && \
|
||||
make -C 8bit install && \
|
||||
rm -rf ${DIR}
|
||||
### libogg https://www.xiph.org/ogg/
|
||||
RUN \
|
||||
DIR=/tmp/ogg && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/ogg/libogg-${OGG_VERSION}.tar.gz && \
|
||||
echo ${OGG_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libogg-${OGG_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libopus https://www.opus-codec.org/
|
||||
RUN \
|
||||
DIR=/tmp/opus && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://archive.mozilla.org/pub/opus/opus-${OPUS_VERSION}.tar.gz && \
|
||||
echo ${OPUS_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f opus-${OPUS_VERSION}.tar.gz && \
|
||||
autoreconf -fiv && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libvorbis https://xiph.org/vorbis/
|
||||
RUN \
|
||||
DIR=/tmp/vorbis && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/vorbis/libvorbis-${VORBIS_VERSION}.tar.gz && \
|
||||
echo ${VORBIS_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libvorbis-${VORBIS_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libtheora http://www.theora.org/
|
||||
RUN \
|
||||
DIR=/tmp/theora && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/theora/libtheora-${THEORA_VERSION}.tar.gz && \
|
||||
echo ${THEORA_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libtheora-${THEORA_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libvpx https://www.webmproject.org/code/
|
||||
RUN \
|
||||
DIR=/tmp/vpx && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://codeload.github.com/webmproject/libvpx/tar.gz/v${VPX_VERSION} | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-vp8 --enable-vp9 --enable-vp9-highbitdepth --enable-pic --enable-shared \
|
||||
--disable-debug --disable-examples --disable-docs --disable-install-bins && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libwebp https://developers.google.com/speed/webp/
|
||||
RUN \
|
||||
DIR=/tmp/vebp && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${WEBP_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libmp3lame http://lame.sourceforge.net/
|
||||
RUN \
|
||||
DIR=/tmp/lame && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://versaweb.dl.sourceforge.net/project/lame/lame/$(echo ${LAME_VERSION} | sed -e 's/[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z-]*\)/\1.\2/')/lame-${LAME_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" --enable-shared --enable-nasm --disable-frontend && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### xvid https://www.xvid.com/
|
||||
RUN \
|
||||
DIR=/tmp/xvid && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xvid.org/downloads/xvidcore-${XVID_VERSION}.tar.gz && \
|
||||
echo ${XVID_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx -f xvidcore-${XVID_VERSION}.tar.gz && \
|
||||
cd xvidcore/build/generic && \
|
||||
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### fdk-aac https://github.com/mstorsjo/fdk-aac
|
||||
RUN \
|
||||
DIR=/tmp/fdk-aac && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://github.com/mstorsjo/fdk-aac/archive/v${FDKAAC_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
autoreconf -fiv && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared --datadir="${DIR}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## openjpeg https://github.com/uclouvain/openjpeg
|
||||
RUN \
|
||||
DIR=/tmp/openjpeg && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
cmake -DBUILD_THIRDPARTY:BOOL=ON -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## freetype https://www.freetype.org/
|
||||
RUN \
|
||||
DIR=/tmp/freetype && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.gz && \
|
||||
echo ${FREETYPE_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f freetype-${FREETYPE_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## libvstab https://github.com/georgmartius/vid.stab
|
||||
RUN \
|
||||
DIR=/tmp/vid.stab && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/georgmartius/vid.stab/archive/v${LIBVIDSTAB_VERSION}.tar.gz && \
|
||||
echo ${LIBVIDSTAB_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f v${LIBVIDSTAB_VERSION}.tar.gz && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## fridibi https://www.fribidi.org/
|
||||
RUN \
|
||||
DIR=/tmp/fribidi && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/fribidi/fribidi/archive/${FRIBIDI_VERSION}.tar.gz && \
|
||||
echo ${FRIBIDI_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f ${FRIBIDI_VERSION}.tar.gz && \
|
||||
sed -i 's/^SUBDIRS =.*/SUBDIRS=gen.tab charset lib bin/' Makefile.am && \
|
||||
./bootstrap --no-config --auto && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j1 && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## kvazaar https://github.com/ultravideo/kvazaar
|
||||
RUN \
|
||||
DIR=/tmp/kvazaar && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/ultravideo/kvazaar/archive/v${KVAZAAR_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f v${KVAZAAR_VERSION}.tar.gz && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/aom && \
|
||||
git clone --branch ${AOM_VERSION} --depth 1 https://aomedia.googlesource.com/aom ${DIR} ; \
|
||||
cd ${DIR} ; \
|
||||
rm -rf CMakeCache.txt CMakeFiles ; \
|
||||
mkdir -p ./aom_build ; \
|
||||
cd ./aom_build ; \
|
||||
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DBUILD_SHARED_LIBS=1 ..; \
|
||||
make ; \
|
||||
make install ; \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libxcb (and supporting libraries) for screen capture https://xcb.freedesktop.org/
|
||||
RUN \
|
||||
DIR=/tmp/xorg-macros && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive//individual/util/util-macros-${XORG_MACROS_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f util-macros-${XORG_MACROS_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/xproto && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive/individual/proto/xproto-${XPROTO_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f xproto-${XPROTO_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libXau && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive/individual/lib/libXau-${XAU_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libXau-${XAU_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libpthread-stubs && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libxcb-proto && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
|
||||
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libxcb && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/libxcb-${LIBXCB_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libxcb-${LIBXCB_VERSION}.tar.gz && \
|
||||
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libzmq https://github.com/zeromq/libzmq/
|
||||
RUN \
|
||||
DIR=/tmp/libzmq && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/zeromq/libzmq/archive/v${LIBZMQ_VERSION}.tar.gz && \
|
||||
echo ${LIBZMQ_SHA256SUM} | sha256sum --check && \
|
||||
tar -xz --strip-components=1 -f v${LIBZMQ_VERSION}.tar.gz && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make check && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## ffmpeg https://ffmpeg.org/
|
||||
RUN \
|
||||
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
|
||||
curl -sLO https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \
|
||||
tar -jx --strip-components=1 -f ffmpeg-${FFMPEG_VERSION}.tar.bz2
|
||||
|
||||
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
|
||||
./configure \
|
||||
--disable-debug \
|
||||
--disable-doc \
|
||||
--disable-ffplay \
|
||||
--enable-shared \
|
||||
--enable-avresample \
|
||||
--enable-libopencore-amrnb \
|
||||
--enable-libopencore-amrwb \
|
||||
--enable-gpl \
|
||||
--enable-libfreetype \
|
||||
--enable-libvidstab \
|
||||
--enable-libmfx \
|
||||
--enable-libmp3lame \
|
||||
--enable-libopus \
|
||||
--enable-libtheora \
|
||||
--enable-libvorbis \
|
||||
--enable-libvpx \
|
||||
--enable-libwebp \
|
||||
--enable-libxcb \
|
||||
--enable-libx265 \
|
||||
--enable-libxvid \
|
||||
--enable-libx264 \
|
||||
--enable-nonfree \
|
||||
--enable-openssl \
|
||||
--enable-libfdk_aac \
|
||||
--enable-postproc \
|
||||
--enable-small \
|
||||
--enable-version3 \
|
||||
--enable-libzmq \
|
||||
--extra-libs=-ldl \
|
||||
--prefix="${PREFIX}" \
|
||||
--enable-libopenjpeg \
|
||||
--enable-libkvazaar \
|
||||
--enable-libaom \
|
||||
--extra-libs=-lpthread \
|
||||
--enable-vaapi \
|
||||
--extra-cflags="-I${PREFIX}/include" \
|
||||
--extra-ldflags="-L${PREFIX}/lib" && \
|
||||
make && \
|
||||
make install && \
|
||||
make tools/zmqsend && cp tools/zmqsend ${PREFIX}/bin/ && \
|
||||
make distclean && \
|
||||
hash -r && \
|
||||
cd tools && \
|
||||
make qt-faststart && cp qt-faststart ${PREFIX}/bin/
|
||||
|
||||
## cleanup
|
||||
RUN \
|
||||
ldd ${PREFIX}/bin/ffmpeg | grep opt/ffmpeg | cut -d ' ' -f 3 | xargs -i cp {} /usr/local/lib/ && \
|
||||
for lib in /usr/local/lib/*.so.*; do ln -s "${lib##*/}" "${lib%%.so.*}".so; done && \
|
||||
cp ${PREFIX}/bin/* /usr/local/bin/ && \
|
||||
cp -r ${PREFIX}/share/ffmpeg /usr/local/share/ && \
|
||||
LD_LIBRARY_PATH=/usr/local/lib ffmpeg -buildconf && \
|
||||
cp -r ${PREFIX}/include/libav* ${PREFIX}/include/libpostproc ${PREFIX}/include/libsw* /usr/local/include && \
|
||||
mkdir -p /usr/local/lib/pkgconfig && \
|
||||
for pc in ${PREFIX}/lib/pkgconfig/libav*.pc ${PREFIX}/lib/pkgconfig/libpostproc.pc ${PREFIX}/lib/pkgconfig/libsw*.pc; do \
|
||||
sed "s:${PREFIX}:/usr/local:g" <"$pc" >/usr/local/lib/pkgconfig/"${pc##*/}"; \
|
||||
done
|
||||
|
||||
FROM base AS release
|
||||
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64:/usr/lib:/usr/lib64:/lib:/lib64
|
||||
|
||||
CMD ["--help"]
|
||||
ENTRYPOINT ["ffmpeg"]
|
||||
|
||||
COPY --from=build /usr/local /usr/local/
|
||||
|
||||
RUN \
|
||||
apt-get update -y && \
|
||||
apt-get install -y --no-install-recommends libva-drm2 libva2 i965-va-driver mesa-va-drivers && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
@@ -0,0 +1,549 @@
|
||||
# inspired by https://github.com/jrottenberg/ffmpeg/blob/master/docker-images/4.3/ubuntu1804/Dockerfile
|
||||
|
||||
# ffmpeg - http://ffmpeg.org/download.html
|
||||
#
|
||||
# From https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu
|
||||
#
|
||||
# https://hub.docker.com/r/jrottenberg/ffmpeg/
|
||||
#
|
||||
#
|
||||
|
||||
FROM nvidia/cuda:11.1-devel-ubuntu20.04 AS devel-base
|
||||
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES compute,utility,video
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
WORKDIR /tmp/workdir
|
||||
|
||||
RUN apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y
|
||||
|
||||
FROM nvidia/cuda:11.1-runtime-ubuntu20.04 AS runtime-base
|
||||
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES compute,utility,video
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
WORKDIR /tmp/workdir
|
||||
|
||||
RUN apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 libxcb-shape0-dev && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y
|
||||
|
||||
|
||||
FROM devel-base as build
|
||||
|
||||
ENV NVIDIA_HEADERS_VERSION=9.1.23.1
|
||||
|
||||
ENV FFMPEG_VERSION=4.3.1 \
|
||||
AOM_VERSION=v1.0.0 \
|
||||
FDKAAC_VERSION=0.1.5 \
|
||||
FREETYPE_VERSION=2.5.5 \
|
||||
FRIBIDI_VERSION=0.19.7 \
|
||||
KVAZAAR_VERSION=1.2.0 \
|
||||
LAME_VERSION=3.100 \
|
||||
LIBPTHREAD_STUBS_VERSION=0.4 \
|
||||
LIBVIDSTAB_VERSION=1.1.0 \
|
||||
LIBXCB_VERSION=1.13.1 \
|
||||
XCBPROTO_VERSION=1.13 \
|
||||
OGG_VERSION=1.3.2 \
|
||||
OPENCOREAMR_VERSION=0.1.5 \
|
||||
OPUS_VERSION=1.2 \
|
||||
OPENJPEG_VERSION=2.1.2 \
|
||||
THEORA_VERSION=1.1.1 \
|
||||
VORBIS_VERSION=1.3.5 \
|
||||
VPX_VERSION=1.8.0 \
|
||||
WEBP_VERSION=1.0.2 \
|
||||
X264_VERSION=20170226-2245-stable \
|
||||
X265_VERSION=3.1.1 \
|
||||
XAU_VERSION=1.0.9 \
|
||||
XORG_MACROS_VERSION=1.19.2 \
|
||||
XPROTO_VERSION=7.0.31 \
|
||||
XVID_VERSION=1.3.4 \
|
||||
LIBZMQ_VERSION=4.3.2 \
|
||||
LIBSRT_VERSION=1.4.1 \
|
||||
LIBARIBB24_VERSION=1.0.3 \
|
||||
LIBPNG_VERSION=1.6.9 \
|
||||
SRC=/usr/local
|
||||
|
||||
ARG FREETYPE_SHA256SUM="5d03dd76c2171a7601e9ce10551d52d4471cf92cd205948e60289251daddffa8 freetype-2.5.5.tar.gz"
|
||||
ARG FRIBIDI_SHA256SUM="3fc96fa9473bd31dcb5500bdf1aa78b337ba13eb8c301e7c28923fea982453a8 0.19.7.tar.gz"
|
||||
ARG LIBVIDSTAB_SHA256SUM="14d2a053e56edad4f397be0cb3ef8eb1ec3150404ce99a426c4eb641861dc0bb v1.1.0.tar.gz"
|
||||
ARG OGG_SHA256SUM="e19ee34711d7af328cb26287f4137e70630e7261b17cbe3cd41011d73a654692 libogg-1.3.2.tar.gz"
|
||||
ARG OPUS_SHA256SUM="77db45a87b51578fbc49555ef1b10926179861d854eb2613207dc79d9ec0a9a9 opus-1.2.tar.gz"
|
||||
ARG THEORA_SHA256SUM="40952956c47811928d1e7922cda3bc1f427eb75680c3c37249c91e949054916b libtheora-1.1.1.tar.gz"
|
||||
ARG VORBIS_SHA256SUM="6efbcecdd3e5dfbf090341b485da9d176eb250d893e3eb378c428a2db38301ce libvorbis-1.3.5.tar.gz"
|
||||
ARG XVID_SHA256SUM="4e9fd62728885855bc5007fe1be58df42e5e274497591fec37249e1052ae316f xvidcore-1.3.4.tar.gz"
|
||||
ARG LIBZMQ_SHA256SUM="02ecc88466ae38cf2c8d79f09cfd2675ba299a439680b64ade733e26a349edeb v4.3.2.tar.gz"
|
||||
ARG LIBARIBB24_SHA256SUM="f61560738926e57f9173510389634d8c06cabedfa857db4b28fb7704707ff128 v1.0.3.tar.gz"
|
||||
|
||||
|
||||
ARG LD_LIBRARY_PATH=/opt/ffmpeg/lib
|
||||
ARG MAKEFLAGS="-j2"
|
||||
ARG PKG_CONFIG_PATH="/opt/ffmpeg/share/pkgconfig:/opt/ffmpeg/lib/pkgconfig:/opt/ffmpeg/lib64/pkgconfig"
|
||||
ARG PREFIX=/opt/ffmpeg
|
||||
ARG LD_LIBRARY_PATH="/opt/ffmpeg/lib:/opt/ffmpeg/lib64"
|
||||
|
||||
|
||||
RUN buildDeps="autoconf \
|
||||
automake \
|
||||
cmake \
|
||||
curl \
|
||||
bzip2 \
|
||||
libexpat1-dev \
|
||||
g++ \
|
||||
gcc \
|
||||
git \
|
||||
gperf \
|
||||
libtool \
|
||||
make \
|
||||
nasm \
|
||||
perl \
|
||||
pkg-config \
|
||||
python \
|
||||
libssl-dev \
|
||||
yasm \
|
||||
zlib1g-dev" && \
|
||||
apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ${buildDeps}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/nv-codec-headers && \
|
||||
git clone https://github.com/FFmpeg/nv-codec-headers ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
git checkout n${NVIDIA_HEADERS_VERSION} && \
|
||||
make PREFIX="${PREFIX}" && \
|
||||
make install PREFIX="${PREFIX}" && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## opencore-amr https://sourceforge.net/projects/opencore-amr/
|
||||
RUN \
|
||||
DIR=/tmp/opencore-amr && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://versaweb.dl.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-${OPENCOREAMR_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## x264 http://www.videolan.org/developers/x264.html
|
||||
RUN \
|
||||
DIR=/tmp/x264 && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://download.videolan.org/pub/videolan/x264/snapshots/x264-snapshot-${X264_VERSION}.tar.bz2 | \
|
||||
tar -jx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared --enable-pic --disable-cli && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### x265 http://x265.org/
|
||||
RUN \
|
||||
DIR=/tmp/x265 && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://download.videolan.org/pub/videolan/x265/x265_${X265_VERSION}.tar.gz | \
|
||||
tar -zx && \
|
||||
cd x265_${X265_VERSION}/build/linux && \
|
||||
sed -i "/-DEXTRA_LIB/ s/$/ -DCMAKE_INSTALL_PREFIX=\${PREFIX}/" multilib.sh && \
|
||||
sed -i "/^cmake/ s/$/ -DENABLE_CLI=OFF/" multilib.sh && \
|
||||
./multilib.sh && \
|
||||
make -C 8bit install && \
|
||||
rm -rf ${DIR}
|
||||
### libogg https://www.xiph.org/ogg/
|
||||
RUN \
|
||||
DIR=/tmp/ogg && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/ogg/libogg-${OGG_VERSION}.tar.gz && \
|
||||
echo ${OGG_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libogg-${OGG_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libopus https://www.opus-codec.org/
|
||||
RUN \
|
||||
DIR=/tmp/opus && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://archive.mozilla.org/pub/opus/opus-${OPUS_VERSION}.tar.gz && \
|
||||
echo ${OPUS_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f opus-${OPUS_VERSION}.tar.gz && \
|
||||
autoreconf -fiv && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libvorbis https://xiph.org/vorbis/
|
||||
RUN \
|
||||
DIR=/tmp/vorbis && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/vorbis/libvorbis-${VORBIS_VERSION}.tar.gz && \
|
||||
echo ${VORBIS_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libvorbis-${VORBIS_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libtheora http://www.theora.org/
|
||||
RUN \
|
||||
DIR=/tmp/theora && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/theora/libtheora-${THEORA_VERSION}.tar.gz && \
|
||||
echo ${THEORA_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libtheora-${THEORA_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libvpx https://www.webmproject.org/code/
|
||||
RUN \
|
||||
DIR=/tmp/vpx && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://codeload.github.com/webmproject/libvpx/tar.gz/v${VPX_VERSION} | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-vp8 --enable-vp9 --enable-vp9-highbitdepth --enable-pic --enable-shared \
|
||||
--disable-debug --disable-examples --disable-docs --disable-install-bins && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libwebp https://developers.google.com/speed/webp/
|
||||
RUN \
|
||||
DIR=/tmp/vebp && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${WEBP_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### libmp3lame http://lame.sourceforge.net/
|
||||
RUN \
|
||||
DIR=/tmp/lame && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://versaweb.dl.sourceforge.net/project/lame/lame/$(echo ${LAME_VERSION} | sed -e 's/[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z-]*\)/\1.\2/')/lame-${LAME_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" --enable-shared --enable-nasm --disable-frontend && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### xvid https://www.xvid.com/
|
||||
RUN \
|
||||
DIR=/tmp/xvid && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xvid.org/downloads/xvidcore-${XVID_VERSION}.tar.gz && \
|
||||
echo ${XVID_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx -f xvidcore-${XVID_VERSION}.tar.gz && \
|
||||
cd xvidcore/build/generic && \
|
||||
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
### fdk-aac https://github.com/mstorsjo/fdk-aac
|
||||
RUN \
|
||||
DIR=/tmp/fdk-aac && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://github.com/mstorsjo/fdk-aac/archive/v${FDKAAC_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
autoreconf -fiv && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared --datadir="${DIR}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## openjpeg https://github.com/uclouvain/openjpeg
|
||||
RUN \
|
||||
DIR=/tmp/openjpeg && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
cmake -DBUILD_THIRDPARTY:BOOL=ON -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## freetype https://www.freetype.org/
|
||||
RUN \
|
||||
DIR=/tmp/freetype && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.gz && \
|
||||
echo ${FREETYPE_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f freetype-${FREETYPE_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## libvstab https://github.com/georgmartius/vid.stab
|
||||
RUN \
|
||||
DIR=/tmp/vid.stab && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/georgmartius/vid.stab/archive/v${LIBVIDSTAB_VERSION}.tar.gz && \
|
||||
echo ${LIBVIDSTAB_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f v${LIBVIDSTAB_VERSION}.tar.gz && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## fridibi https://www.fribidi.org/
|
||||
RUN \
|
||||
DIR=/tmp/fribidi && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/fribidi/fribidi/archive/${FRIBIDI_VERSION}.tar.gz && \
|
||||
echo ${FRIBIDI_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f ${FRIBIDI_VERSION}.tar.gz && \
|
||||
sed -i 's/^SUBDIRS =.*/SUBDIRS=gen.tab charset lib bin/' Makefile.am && \
|
||||
./bootstrap --no-config --auto && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j1 && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
## kvazaar https://github.com/ultravideo/kvazaar
|
||||
RUN \
|
||||
DIR=/tmp/kvazaar && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/ultravideo/kvazaar/archive/v${KVAZAAR_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f v${KVAZAAR_VERSION}.tar.gz && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/aom && \
|
||||
git clone --branch ${AOM_VERSION} --depth 1 https://aomedia.googlesource.com/aom ${DIR} ; \
|
||||
cd ${DIR} ; \
|
||||
rm -rf CMakeCache.txt CMakeFiles ; \
|
||||
mkdir -p ./aom_build ; \
|
||||
cd ./aom_build ; \
|
||||
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DBUILD_SHARED_LIBS=1 ..; \
|
||||
make ; \
|
||||
make install ; \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libxcb (and supporting libraries) for screen capture https://xcb.freedesktop.org/
|
||||
RUN \
|
||||
DIR=/tmp/xorg-macros && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive//individual/util/util-macros-${XORG_MACROS_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f util-macros-${XORG_MACROS_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/xproto && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive/individual/proto/xproto-${XPROTO_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f xproto-${XPROTO_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libXau && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive/individual/lib/libXau-${XAU_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libXau-${XAU_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libpthread-stubs && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libxcb-proto && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
|
||||
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libxcb && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/libxcb-${LIBXCB_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libxcb-${LIBXCB_VERSION}.tar.gz && \
|
||||
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libzmq https://github.com/zeromq/libzmq/
|
||||
RUN \
|
||||
DIR=/tmp/libzmq && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/zeromq/libzmq/archive/v${LIBZMQ_VERSION}.tar.gz && \
|
||||
echo ${LIBZMQ_SHA256SUM} | sha256sum --check && \
|
||||
tar -xz --strip-components=1 -f v${LIBZMQ_VERSION}.tar.gz && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make check && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libsrt https://github.com/Haivision/srt
|
||||
RUN \
|
||||
DIR=/tmp/srt && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/Haivision/srt/archive/v${LIBSRT_VERSION}.tar.gz && \
|
||||
tar -xz --strip-components=1 -f v${LIBSRT_VERSION}.tar.gz && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libpng
|
||||
RUN \
|
||||
DIR=/tmp/png && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
git clone https://git.code.sf.net/p/libpng/code ${DIR} -b v${LIBPNG_VERSION} --depth 1 && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make check && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libaribb24
|
||||
RUN \
|
||||
DIR=/tmp/b24 && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/nkoriyama/aribb24/archive/v${LIBARIBB24_VERSION}.tar.gz && \
|
||||
echo ${LIBARIBB24_SHA256SUM} | sha256sum --check && \
|
||||
tar -xz --strip-components=1 -f v${LIBARIBB24_VERSION}.tar.gz && \
|
||||
autoreconf -fiv && \
|
||||
./configure CFLAGS="-I${PREFIX}/include -fPIC" --prefix="${PREFIX}" && \
|
||||
make && \
|
||||
make install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## ffmpeg https://ffmpeg.org/
|
||||
RUN \
|
||||
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
|
||||
curl -sLO https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \
|
||||
tar -jx --strip-components=1 -f ffmpeg-${FFMPEG_VERSION}.tar.bz2
|
||||
|
||||
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
|
||||
./configure \
|
||||
--disable-debug \
|
||||
--disable-doc \
|
||||
--disable-ffplay \
|
||||
--enable-shared \
|
||||
--enable-avresample \
|
||||
--enable-libopencore-amrnb \
|
||||
--enable-libopencore-amrwb \
|
||||
--enable-gpl \
|
||||
--enable-libfreetype \
|
||||
--enable-libvidstab \
|
||||
--enable-libmp3lame \
|
||||
--enable-libopus \
|
||||
--enable-libtheora \
|
||||
--enable-libvorbis \
|
||||
--enable-libvpx \
|
||||
--enable-libwebp \
|
||||
--enable-libxcb \
|
||||
--enable-libx265 \
|
||||
--enable-libxvid \
|
||||
--enable-libx264 \
|
||||
--enable-nonfree \
|
||||
--enable-openssl \
|
||||
--enable-libfdk_aac \
|
||||
--enable-postproc \
|
||||
--enable-small \
|
||||
--enable-version3 \
|
||||
--enable-libzmq \
|
||||
--extra-libs=-ldl \
|
||||
--prefix="${PREFIX}" \
|
||||
--enable-libopenjpeg \
|
||||
--enable-libkvazaar \
|
||||
--enable-libaom \
|
||||
--extra-libs=-lpthread \
|
||||
--enable-libsrt \
|
||||
--enable-libaribb24 \
|
||||
--enable-nvenc \
|
||||
--enable-cuda \
|
||||
--enable-cuvid \
|
||||
--enable-libnpp \
|
||||
--extra-cflags="-I${PREFIX}/include -I${PREFIX}/include/ffnvcodec -I/usr/local/cuda/include/" \
|
||||
--extra-ldflags="-L${PREFIX}/lib -L/usr/local/cuda/lib64 -L/usr/local/cuda/lib32/" && \
|
||||
make && \
|
||||
make install && \
|
||||
make tools/zmqsend && cp tools/zmqsend ${PREFIX}/bin/ && \
|
||||
make distclean && \
|
||||
hash -r && \
|
||||
cd tools && \
|
||||
make qt-faststart && cp qt-faststart ${PREFIX}/bin/
|
||||
|
||||
## cleanup
|
||||
RUN \
|
||||
LD_LIBRARY_PATH="${PREFIX}/lib:${PREFIX}/lib64:${LD_LIBRARY_PATH}" ldd ${PREFIX}/bin/ffmpeg | grep opt/ffmpeg | cut -d ' ' -f 3 | xargs -i cp {} /usr/local/lib/ && \
|
||||
for lib in /usr/local/lib/*.so.*; do ln -s "${lib##*/}" "${lib%%.so.*}".so; done && \
|
||||
cp ${PREFIX}/bin/* /usr/local/bin/ && \
|
||||
cp -r ${PREFIX}/share/* /usr/local/share/ && \
|
||||
LD_LIBRARY_PATH=/usr/local/lib ffmpeg -buildconf && \
|
||||
cp -r ${PREFIX}/include/libav* ${PREFIX}/include/libpostproc ${PREFIX}/include/libsw* /usr/local/include && \
|
||||
mkdir -p /usr/local/lib/pkgconfig && \
|
||||
for pc in ${PREFIX}/lib/pkgconfig/libav*.pc ${PREFIX}/lib/pkgconfig/libpostproc.pc ${PREFIX}/lib/pkgconfig/libsw*.pc; do \
|
||||
sed "s:${PREFIX}:/usr/local:g; s:/lib64:/lib:g" <"$pc" >/usr/local/lib/pkgconfig/"${pc##*/}"; \
|
||||
done
|
||||
|
||||
|
||||
|
||||
FROM runtime-base AS release
|
||||
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64
|
||||
|
||||
CMD ["--help"]
|
||||
ENTRYPOINT ["ffmpeg"]
|
||||
|
||||
# copy only needed files, without copying nvidia dev files
|
||||
COPY --from=build /usr/local/bin /usr/local/bin/
|
||||
COPY --from=build /usr/local/share /usr/local/share/
|
||||
COPY --from=build /usr/local/lib /usr/local/lib/
|
||||
COPY --from=build /usr/local/include /usr/local/include/
|
||||
|
||||
# Let's make sure the app built correctly
|
||||
# Convenient to verify on https://hub.docker.com/r/jrottenberg/ffmpeg/builds/ console output
|
||||
@@ -0,0 +1,490 @@
|
||||
# inspired by:
|
||||
# https://github.com/collelog/ffmpeg/blob/master/4.3.1-alpine-rpi4-arm64v8.Dockerfile
|
||||
# https://github.com/mmastrac/ffmpeg-omx-rpi-docker/blob/master/Dockerfile
|
||||
# https://github.com/jrottenberg/ffmpeg/pull/158/files
|
||||
# https://github.com/jrottenberg/ffmpeg/pull/239
|
||||
FROM ubuntu:20.04 AS base
|
||||
|
||||
WORKDIR /tmp/workdir
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y
|
||||
|
||||
FROM base as build
|
||||
|
||||
ENV FFMPEG_VERSION=4.3.1 \
|
||||
AOM_VERSION=v1.0.0 \
|
||||
FDKAAC_VERSION=0.1.5 \
|
||||
FREETYPE_VERSION=2.5.5 \
|
||||
FRIBIDI_VERSION=0.19.7 \
|
||||
KVAZAAR_VERSION=1.2.0 \
|
||||
LAME_VERSION=3.100 \
|
||||
LIBPTHREAD_STUBS_VERSION=0.4 \
|
||||
LIBVIDSTAB_VERSION=1.1.0 \
|
||||
LIBXCB_VERSION=1.13.1 \
|
||||
XCBPROTO_VERSION=1.13 \
|
||||
OGG_VERSION=1.3.2 \
|
||||
OPENCOREAMR_VERSION=0.1.5 \
|
||||
OPUS_VERSION=1.2 \
|
||||
OPENJPEG_VERSION=2.1.2 \
|
||||
THEORA_VERSION=1.1.1 \
|
||||
VORBIS_VERSION=1.3.5 \
|
||||
VPX_VERSION=1.8.0 \
|
||||
WEBP_VERSION=1.0.2 \
|
||||
X264_VERSION=20170226-2245-stable \
|
||||
X265_VERSION=3.1.1 \
|
||||
XAU_VERSION=1.0.9 \
|
||||
XORG_MACROS_VERSION=1.19.2 \
|
||||
XPROTO_VERSION=7.0.31 \
|
||||
XVID_VERSION=1.3.4 \
|
||||
LIBZMQ_VERSION=4.3.3 \
|
||||
SRC=/usr/local
|
||||
|
||||
ARG FREETYPE_SHA256SUM="5d03dd76c2171a7601e9ce10551d52d4471cf92cd205948e60289251daddffa8 freetype-2.5.5.tar.gz"
|
||||
ARG FRIBIDI_SHA256SUM="3fc96fa9473bd31dcb5500bdf1aa78b337ba13eb8c301e7c28923fea982453a8 0.19.7.tar.gz"
|
||||
ARG LIBVIDSTAB_SHA256SUM="14d2a053e56edad4f397be0cb3ef8eb1ec3150404ce99a426c4eb641861dc0bb v1.1.0.tar.gz"
|
||||
ARG OGG_SHA256SUM="e19ee34711d7af328cb26287f4137e70630e7261b17cbe3cd41011d73a654692 libogg-1.3.2.tar.gz"
|
||||
ARG OPUS_SHA256SUM="77db45a87b51578fbc49555ef1b10926179861d854eb2613207dc79d9ec0a9a9 opus-1.2.tar.gz"
|
||||
ARG THEORA_SHA256SUM="40952956c47811928d1e7922cda3bc1f427eb75680c3c37249c91e949054916b libtheora-1.1.1.tar.gz"
|
||||
ARG VORBIS_SHA256SUM="6efbcecdd3e5dfbf090341b485da9d176eb250d893e3eb378c428a2db38301ce libvorbis-1.3.5.tar.gz"
|
||||
ARG XVID_SHA256SUM="4e9fd62728885855bc5007fe1be58df42e5e274497591fec37249e1052ae316f xvidcore-1.3.4.tar.gz"
|
||||
|
||||
|
||||
ARG LD_LIBRARY_PATH=/opt/ffmpeg/lib
|
||||
ARG MAKEFLAGS="-j2"
|
||||
ARG PKG_CONFIG_PATH="/opt/ffmpeg/share/pkgconfig:/opt/ffmpeg/lib/pkgconfig:/opt/ffmpeg/lib64/pkgconfig:/opt/vc/lib/pkgconfig"
|
||||
ARG PREFIX=/opt/ffmpeg
|
||||
ARG LD_LIBRARY_PATH="/opt/ffmpeg/lib:/opt/ffmpeg/lib64:/usr/lib64:/usr/lib:/lib64:/lib:/opt/vc/lib"
|
||||
|
||||
|
||||
RUN buildDeps="autoconf \
|
||||
automake \
|
||||
cmake \
|
||||
curl \
|
||||
bzip2 \
|
||||
libexpat1-dev \
|
||||
g++ \
|
||||
gcc \
|
||||
git \
|
||||
gperf \
|
||||
libtool \
|
||||
make \
|
||||
nasm \
|
||||
perl \
|
||||
pkg-config \
|
||||
python \
|
||||
sudo \
|
||||
libssl-dev \
|
||||
yasm \
|
||||
linux-headers-raspi2 \
|
||||
libomxil-bellagio-dev \
|
||||
libx265-dev \
|
||||
libaom-dev \
|
||||
zlib1g-dev" && \
|
||||
apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends ${buildDeps}
|
||||
## opencore-amr https://sourceforge.net/projects/opencore-amr/
|
||||
RUN \
|
||||
DIR=/tmp/opencore-amr && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://versaweb.dl.sourceforge.net/project/opencore-amr/opencore-amr/opencore-amr-${OPENCOREAMR_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## x264 http://www.videolan.org/developers/x264.html
|
||||
RUN \
|
||||
DIR=/tmp/x264 && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://download.videolan.org/pub/videolan/x264/snapshots/x264-snapshot-${X264_VERSION}.tar.bz2 | \
|
||||
tar -jx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared --enable-pic --disable-cli && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
# ### x265 http://x265.org/
|
||||
# RUN \
|
||||
# DIR=/tmp/x265 && \
|
||||
# mkdir -p ${DIR} && \
|
||||
# cd ${DIR} && \
|
||||
# curl -sL https://download.videolan.org/pub/videolan/x265/x265_${X265_VERSION}.tar.gz | \
|
||||
# tar -zx && \
|
||||
# cd x265_${X265_VERSION}/build/linux && \
|
||||
# sed -i "/-DEXTRA_LIB/ s/$/ -DCMAKE_INSTALL_PREFIX=\${PREFIX}/" multilib.sh && \
|
||||
# sed -i "/^cmake/ s/$/ -DENABLE_CLI=OFF/" multilib.sh && \
|
||||
# # export CXXFLAGS="${CXXFLAGS} -fPIC" && \
|
||||
# ./multilib.sh && \
|
||||
# make -C 8bit install && \
|
||||
# rm -rf ${DIR}
|
||||
### libogg https://www.xiph.org/ogg/
|
||||
RUN \
|
||||
DIR=/tmp/ogg && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/ogg/libogg-${OGG_VERSION}.tar.gz && \
|
||||
echo ${OGG_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libogg-${OGG_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libopus https://www.opus-codec.org/
|
||||
RUN \
|
||||
DIR=/tmp/opus && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://archive.mozilla.org/pub/opus/opus-${OPUS_VERSION}.tar.gz && \
|
||||
echo ${OPUS_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f opus-${OPUS_VERSION}.tar.gz && \
|
||||
autoreconf -fiv && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libvorbis https://xiph.org/vorbis/
|
||||
RUN \
|
||||
DIR=/tmp/vorbis && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/vorbis/libvorbis-${VORBIS_VERSION}.tar.gz && \
|
||||
echo ${VORBIS_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libvorbis-${VORBIS_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libtheora http://www.theora.org/
|
||||
RUN \
|
||||
DIR=/tmp/theora && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xiph.org/releases/theora/libtheora-${THEORA_VERSION}.tar.gz && \
|
||||
echo ${THEORA_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f libtheora-${THEORA_VERSION}.tar.gz && \
|
||||
curl -sL 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' -o config.guess && \
|
||||
curl -sL 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub && \
|
||||
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libvpx https://www.webmproject.org/code/
|
||||
RUN \
|
||||
DIR=/tmp/vpx && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://codeload.github.com/webmproject/libvpx/tar.gz/v${VPX_VERSION} | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-vp8 --enable-vp9 --enable-vp9-highbitdepth --enable-pic --enable-shared \
|
||||
--disable-debug --disable-examples --disable-docs --disable-install-bins && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libwebp https://developers.google.com/speed/webp/
|
||||
RUN \
|
||||
DIR=/tmp/vebp && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${WEBP_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### libmp3lame http://lame.sourceforge.net/
|
||||
RUN \
|
||||
DIR=/tmp/lame && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://versaweb.dl.sourceforge.net/project/lame/lame/$(echo ${LAME_VERSION} | sed -e 's/[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z-]*\)/\1.\2/')/lame-${LAME_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" --enable-shared --enable-nasm --disable-frontend && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### xvid https://www.xvid.com/
|
||||
RUN \
|
||||
DIR=/tmp/xvid && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO http://downloads.xvid.org/downloads/xvidcore-${XVID_VERSION}.tar.gz && \
|
||||
echo ${XVID_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx -f xvidcore-${XVID_VERSION}.tar.gz && \
|
||||
cd xvidcore/build/generic && \
|
||||
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
### fdk-aac https://github.com/mstorsjo/fdk-aac
|
||||
RUN \
|
||||
DIR=/tmp/fdk-aac && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://github.com/mstorsjo/fdk-aac/archive/v${FDKAAC_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
autoreconf -fiv && \
|
||||
./configure --prefix="${PREFIX}" --enable-shared --datadir="${DIR}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## openjpeg https://github.com/uclouvain/openjpeg
|
||||
RUN \
|
||||
DIR=/tmp/openjpeg && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sL https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz | \
|
||||
tar -zx --strip-components=1 && \
|
||||
export CFLAGS="${CFLAGS} -DPNG_ARM_NEON_OPT=0" && \
|
||||
cmake -DBUILD_THIRDPARTY:BOOL=ON -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## freetype https://www.freetype.org/
|
||||
RUN \
|
||||
DIR=/tmp/freetype && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.gz && \
|
||||
echo ${FREETYPE_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f freetype-${FREETYPE_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## libvstab https://github.com/georgmartius/vid.stab
|
||||
RUN \
|
||||
DIR=/tmp/vid.stab && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/georgmartius/vid.stab/archive/v${LIBVIDSTAB_VERSION}.tar.gz && \
|
||||
echo ${LIBVIDSTAB_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f v${LIBVIDSTAB_VERSION}.tar.gz && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
## fridibi https://www.fribidi.org/
|
||||
RUN \
|
||||
DIR=/tmp/fribidi && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/fribidi/fribidi/archive/${FRIBIDI_VERSION}.tar.gz && \
|
||||
echo ${FRIBIDI_SHA256SUM} | sha256sum --check && \
|
||||
tar -zx --strip-components=1 -f ${FRIBIDI_VERSION}.tar.gz && \
|
||||
sed -i 's/^SUBDIRS =.*/SUBDIRS=gen.tab charset lib bin/' Makefile.am && \
|
||||
./bootstrap --no-config --auto && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j1 && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## kvazaar https://github.com/ultravideo/kvazaar
|
||||
RUN \
|
||||
DIR=/tmp/kvazaar && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/ultravideo/kvazaar/archive/v${KVAZAAR_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f v${KVAZAAR_VERSION}.tar.gz && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
# RUN \
|
||||
# DIR=/tmp/aom && \
|
||||
# git clone --branch ${AOM_VERSION} --depth 1 https://aomedia.googlesource.com/aom ${DIR} ; \
|
||||
# cd ${DIR} ; \
|
||||
# rm -rf CMakeCache.txt CMakeFiles ; \
|
||||
# mkdir -p ./aom_build ; \
|
||||
# cd ./aom_build ; \
|
||||
# cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DBUILD_SHARED_LIBS=1 ..; \
|
||||
# make ; \
|
||||
# make install ; \
|
||||
# rm -rf ${DIR}
|
||||
|
||||
## libxcb (and supporting libraries) for screen capture https://xcb.freedesktop.org/
|
||||
RUN \
|
||||
DIR=/tmp/xorg-macros && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive//individual/util/util-macros-${XORG_MACROS_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f util-macros-${XORG_MACROS_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/xproto && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive/individual/proto/xproto-${XPROTO_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f xproto-${XPROTO_VERSION}.tar.gz && \
|
||||
curl -sL 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' -o config.guess && \
|
||||
curl -sL 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libXau && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://www.x.org/archive/individual/lib/libXau-${XAU_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libXau-${XAU_VERSION}.tar.gz && \
|
||||
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libpthread-stubs && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libxcb-proto && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
|
||||
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/libxcb && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://xcb.freedesktop.org/dist/libxcb-${LIBXCB_VERSION}.tar.gz && \
|
||||
tar -zx --strip-components=1 -f libxcb-${LIBXCB_VERSION}.tar.gz && \
|
||||
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## libzmq https://github.com/zeromq/libzmq/
|
||||
RUN \
|
||||
DIR=/tmp/libzmq && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
curl -sLO https://github.com/zeromq/libzmq/archive/v${LIBZMQ_VERSION}.tar.gz && \
|
||||
tar -xz --strip-components=1 -f v${LIBZMQ_VERSION}.tar.gz && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix="${PREFIX}" && \
|
||||
make -j $(nproc) && \
|
||||
# make check && \
|
||||
make -j $(nproc) install && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## userland https://github.com/raspberrypi/userland
|
||||
RUN \
|
||||
DIR=/tmp/userland && \
|
||||
mkdir -p ${DIR} && \
|
||||
cd ${DIR} && \
|
||||
git clone --depth 1 https://github.com/raspberrypi/userland.git . && \
|
||||
./buildme && \
|
||||
rm -rf ${DIR}
|
||||
|
||||
## ffmpeg https://ffmpeg.org/
|
||||
RUN \
|
||||
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
|
||||
curl -sLO https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \
|
||||
tar -jx --strip-components=1 -f ffmpeg-${FFMPEG_VERSION}.tar.bz2
|
||||
|
||||
RUN \
|
||||
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
|
||||
./configure \
|
||||
--disable-debug \
|
||||
--disable-doc \
|
||||
--disable-ffplay \
|
||||
--enable-shared \
|
||||
--enable-avresample \
|
||||
--enable-libopencore-amrnb \
|
||||
--enable-libopencore-amrwb \
|
||||
--enable-gpl \
|
||||
--enable-libfreetype \
|
||||
--enable-libvidstab \
|
||||
--enable-libmp3lame \
|
||||
--enable-libopus \
|
||||
--enable-libtheora \
|
||||
--enable-libvorbis \
|
||||
--enable-libvpx \
|
||||
--enable-libwebp \
|
||||
--enable-libxcb \
|
||||
--enable-libx265 \
|
||||
--enable-libxvid \
|
||||
--enable-libx264 \
|
||||
--enable-nonfree \
|
||||
--enable-openssl \
|
||||
--enable-libfdk_aac \
|
||||
--enable-postproc \
|
||||
--enable-small \
|
||||
--enable-version3 \
|
||||
--enable-libzmq \
|
||||
--extra-libs=-ldl \
|
||||
--prefix="${PREFIX}" \
|
||||
--enable-libopenjpeg \
|
||||
--enable-libkvazaar \
|
||||
--enable-libaom \
|
||||
--extra-libs=-lpthread \
|
||||
--enable-omx \
|
||||
--enable-omx-rpi \
|
||||
--enable-mmal \
|
||||
--enable-v4l2_m2m \
|
||||
--enable-neon \
|
||||
--extra-cflags="-I${PREFIX}/include" \
|
||||
--extra-ldflags="-L${PREFIX}/lib" && \
|
||||
make -j $(nproc) && \
|
||||
make -j $(nproc) install && \
|
||||
make tools/zmqsend && cp tools/zmqsend ${PREFIX}/bin/ && \
|
||||
make distclean && \
|
||||
hash -r && \
|
||||
cd tools && \
|
||||
make qt-faststart && cp qt-faststart ${PREFIX}/bin/
|
||||
|
||||
## cleanup
|
||||
RUN \
|
||||
ldd ${PREFIX}/bin/ffmpeg | grep opt/ffmpeg | cut -d ' ' -f 3 | xargs -i cp {} /usr/local/lib/ && \
|
||||
# copy userland lib too
|
||||
ldd ${PREFIX}/bin/ffmpeg | grep opt/vc | cut -d ' ' -f 3 | xargs -i cp {} /usr/local/lib/ && \
|
||||
for lib in /usr/local/lib/*.so.*; do ln -s "${lib##*/}" "${lib%%.so.*}".so; done && \
|
||||
cp ${PREFIX}/bin/* /usr/local/bin/ && \
|
||||
cp -r ${PREFIX}/share/ffmpeg /usr/local/share/ && \
|
||||
LD_LIBRARY_PATH=/usr/local/lib ffmpeg -buildconf && \
|
||||
cp -r ${PREFIX}/include/libav* ${PREFIX}/include/libpostproc ${PREFIX}/include/libsw* /usr/local/include && \
|
||||
mkdir -p /usr/local/lib/pkgconfig && \
|
||||
for pc in ${PREFIX}/lib/pkgconfig/libav*.pc ${PREFIX}/lib/pkgconfig/libpostproc.pc ${PREFIX}/lib/pkgconfig/libsw*.pc; do \
|
||||
sed "s:${PREFIX}:/usr/local:g" <"$pc" >/usr/local/lib/pkgconfig/"${pc##*/}"; \
|
||||
done
|
||||
|
||||
FROM base AS release
|
||||
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64:/usr/lib:/usr/lib64:/lib:/lib64
|
||||
|
||||
RUN \
|
||||
apt-get -yqq update && \
|
||||
apt-get install -yq --no-install-recommends libx265-dev libaom-dev && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y
|
||||
|
||||
CMD ["--help"]
|
||||
ENTRYPOINT ["ffmpeg"]
|
||||
|
||||
COPY --from=build /usr/local /usr/local/
|
||||
@@ -0,0 +1,9 @@
|
||||
ARG NODE_VERSION=14.0
|
||||
|
||||
FROM node:${NODE_VERSION}
|
||||
|
||||
WORKDIR /opt/frigate
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install && npm run build
|
||||
@@ -0,0 +1,41 @@
|
||||
FROM ubuntu:20.04 as build
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get -qq update \
|
||||
&& apt-get -qq install -y \
|
||||
python3 \
|
||||
python3-dev \
|
||||
wget \
|
||||
# opencv dependencies
|
||||
build-essential cmake git pkg-config libgtk-3-dev \
|
||||
libavcodec-dev libavformat-dev libswscale-dev libv4l-dev \
|
||||
libxvidcore-dev libx264-dev libjpeg-dev libpng-dev libtiff-dev \
|
||||
gfortran openexr libatlas-base-dev libssl-dev\
|
||||
libtbb2 libtbb-dev libdc1394-22-dev libopenexr-dev \
|
||||
libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev \
|
||||
# scipy dependencies
|
||||
gcc gfortran libopenblas-dev liblapack-dev cython
|
||||
|
||||
RUN wget -q https://bootstrap.pypa.io/get-pip.py -O get-pip.py \
|
||||
&& python3 get-pip.py "pip==20.2.4"
|
||||
|
||||
RUN pip3 install scikit-build
|
||||
|
||||
RUN pip3 wheel --wheel-dir=/wheels \
|
||||
opencv-python-headless \
|
||||
numpy \
|
||||
imutils \
|
||||
scipy \
|
||||
psutil \
|
||||
Flask \
|
||||
paho-mqtt \
|
||||
PyYAML \
|
||||
matplotlib \
|
||||
click \
|
||||
setproctitle \
|
||||
peewee
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=build /wheels /wheels
|
||||
@@ -0,0 +1,42 @@
|
||||
# Camera Specific Configuration
|
||||
Frigate should work with most RTSP cameras and h264 feeds such as Dahua.
|
||||
|
||||
## RTMP Cameras
|
||||
The input parameters need to be adjusted for RTMP cameras
|
||||
```yaml
|
||||
ffmpeg:
|
||||
input_args:
|
||||
- -avoid_negative_ts
|
||||
- make_zero
|
||||
- -fflags
|
||||
- nobuffer
|
||||
- -flags
|
||||
- low_delay
|
||||
- -strict
|
||||
- experimental
|
||||
- -fflags
|
||||
- +genpts+discardcorrupt
|
||||
- -use_wallclock_as_timestamps
|
||||
- '1'
|
||||
```
|
||||
|
||||
## Blue Iris RTSP Cameras
|
||||
You will need to remove `nobuffer` flag for Blue Iris RTSP cameras
|
||||
```yaml
|
||||
ffmpeg:
|
||||
input_args:
|
||||
- -avoid_negative_ts
|
||||
- make_zero
|
||||
- -flags
|
||||
- low_delay
|
||||
- -strict
|
||||
- experimental
|
||||
- -fflags
|
||||
- +genpts+discardcorrupt
|
||||
- -rtsp_transport
|
||||
- tcp
|
||||
- -stimeout
|
||||
- "5000000"
|
||||
- -use_wallclock_as_timestamps
|
||||
- "1"
|
||||
```
|
||||
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 6.0 MiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,10 @@
|
||||
# How Frigate Works
|
||||
Frigate is designed to minimize resource and maximize performance by only looking for objects when and where it is necessary
|
||||
|
||||

|
||||
|
||||
## 1. Look for Motion
|
||||
|
||||
## 2. Calculate Detection Regions
|
||||
|
||||
## 3. Run Object Detection
|
||||
|
Before Width: | Height: | Size: 781 KiB After Width: | Height: | Size: 781 KiB |
@@ -0,0 +1,71 @@
|
||||
# Notification examples
|
||||
|
||||
Here are some examples of notifications for the HomeAssistant android companion app:
|
||||
```yaml
|
||||
automation:
|
||||
|
||||
- alias: When a person enters a zone named yard
|
||||
trigger:
|
||||
platform: mqtt
|
||||
topic: frigate/events
|
||||
conditions:
|
||||
- "{{ trigger.payload_json['after']['label'] == 'person' }}"
|
||||
- "{{ 'yard' in trigger.payload_json['after']['entered_zones'] }}"
|
||||
action:
|
||||
- service: notify.mobile_app_pixel_3
|
||||
data_template:
|
||||
message: "A {{trigger.payload_json['after']['label']}} has entered the yard."
|
||||
data:
|
||||
image: "https://url.com/api/frigate/notifications/{{trigger.payload_json['after']['id']}}.jpg"
|
||||
tag: "{{trigger.payload_json['after']['id']}}"
|
||||
|
||||
- alias: When a person leaves a zone named yard
|
||||
trigger:
|
||||
platform: mqtt
|
||||
topic: frigate/events
|
||||
conditions:
|
||||
- "{{ trigger.payload_json['after']['label'] == 'person' }}"
|
||||
- "{{ 'yard' in trigger.payload_json['before']['current_zones'] }}"
|
||||
- "{{ not 'yard' in trigger.payload_json['after']['current_zones'] }}"
|
||||
action:
|
||||
- service: notify.mobile_app_pixel_3
|
||||
data_template:
|
||||
message: "A {{trigger.payload_json['after']['label']}} has left the yard."
|
||||
data:
|
||||
image: "https://url.com/api/frigate/notifications/{{trigger.payload_json['after']['id']}}/thumbnail.jpg"
|
||||
tag: "{{trigger.payload_json['after']['id']}}"
|
||||
|
||||
- alias: Notify for dogs in the front with a high top score
|
||||
trigger:
|
||||
platform: mqtt
|
||||
topic: frigate/events
|
||||
conditions:
|
||||
- "{{ trigger.payload_json['after']['label'] == 'dog' }}"
|
||||
- "{{ trigger.payload_json['after']['camera'] == 'front' }}"
|
||||
- "{{ trigger.payload_json['after']['top_score'] > 0.98 }}"
|
||||
action:
|
||||
- service: notify.mobile_app_pixel_3
|
||||
data_template:
|
||||
message: 'High confidence dog detection.'
|
||||
data:
|
||||
image: "https://url.com/api/frigate/notifications/{{trigger.payload_json['after']['id']}}/thumbnail.jpg"
|
||||
tag: "{{trigger.payload_json['after']['id']}}"
|
||||
```
|
||||
|
||||
If you are using telegram, you can fetch the image directly from Frigate:
|
||||
```yaml
|
||||
automation:
|
||||
- alias: Notify of events
|
||||
trigger:
|
||||
platform: mqtt
|
||||
topic: frigate/events
|
||||
action:
|
||||
- service: notify.telegram_full
|
||||
data_template:
|
||||
message: 'A {{trigger.payload_json["after"]["label"]}} was detected.'
|
||||
data:
|
||||
photo:
|
||||
# this url should work for addon users
|
||||
- url: 'http://ccab4aaf-frigate:5000/api/events/{{trigger.payload_json["after"]["id"]}}/thumbnail.jpg'
|
||||
caption : 'A {{trigger.payload_json["after"]["label"]}} was detected on {{ trigger.payload_json["after"]["camera"] }} camera'
|
||||
```
|
||||
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,105 @@
|
||||
# nVidia hardware decoder (NVDEC)
|
||||
|
||||
Certain nvidia cards include a hardware decoder, which can greatly improve the
|
||||
performance of video decoding. In order to use NVDEC, a special build of
|
||||
ffmpeg with NVDEC support is required. The special docker architecture 'amd64nvidia'
|
||||
includes this support for amd64 platforms. An aarch64 for the Jetson, which
|
||||
also includes NVDEC may be added in the future.
|
||||
|
||||
## Docker setup
|
||||
|
||||
### Requirements
|
||||
[nVidia closed source driver](https://www.nvidia.com/en-us/drivers/unix/) required to access NVDEC.
|
||||
[nvidia-docker](https://github.com/NVIDIA/nvidia-docker) required to pass NVDEC to docker.
|
||||
|
||||
### Setting up docker-compose
|
||||
|
||||
In order to pass NVDEC, the docker engine must be set to `nvidia` and the environment variables
|
||||
`NVIDIA_VISIBLE_DEVICES=all` and `NVIDIA_DRIVER_CAPABILITIES=compute,utility,video` must be set.
|
||||
|
||||
In a docker compose file, these lines need to be set:
|
||||
```
|
||||
services:
|
||||
frigate:
|
||||
...
|
||||
image: blakeblackshear/frigate:stable-amd64nvidia
|
||||
runtime: nvidia
|
||||
environment:
|
||||
- NVIDIA_VISIBLE_DEVICES=all
|
||||
- NVIDIA_DRIVER_CAPABILITIES=compute,utility,video
|
||||
```
|
||||
|
||||
### Setting up the configuration file
|
||||
|
||||
In your frigate config.yml, you'll need to set ffmpeg to use the hardware decoder.
|
||||
The decoder you choose will depend on the input video.
|
||||
|
||||
A list of supported codecs (you can use `ffmpeg -decoders | grep cuvid` in the container to get a list)
|
||||
```
|
||||
V..... h263_cuvid Nvidia CUVID H263 decoder (codec h263)
|
||||
V..... h264_cuvid Nvidia CUVID H264 decoder (codec h264)
|
||||
V..... hevc_cuvid Nvidia CUVID HEVC decoder (codec hevc)
|
||||
V..... mjpeg_cuvid Nvidia CUVID MJPEG decoder (codec mjpeg)
|
||||
V..... mpeg1_cuvid Nvidia CUVID MPEG1VIDEO decoder (codec mpeg1video)
|
||||
V..... mpeg2_cuvid Nvidia CUVID MPEG2VIDEO decoder (codec mpeg2video)
|
||||
V..... mpeg4_cuvid Nvidia CUVID MPEG4 decoder (codec mpeg4)
|
||||
V..... vc1_cuvid Nvidia CUVID VC1 decoder (codec vc1)
|
||||
V..... vp8_cuvid Nvidia CUVID VP8 decoder (codec vp8)
|
||||
V..... vp9_cuvid Nvidia CUVID VP9 decoder (codec vp9)
|
||||
```
|
||||
|
||||
For example, for H265 video (hevc), you'll select `hevc_cuvid`. Add
|
||||
`-c:v hevc_covid` to your ffmpeg input arguments:
|
||||
|
||||
```
|
||||
ffmpeg:
|
||||
input_args:
|
||||
...
|
||||
- -c:v
|
||||
- hevc_cuvid
|
||||
```
|
||||
|
||||
If everything is working correctly, you should see a significant improvement in performance.
|
||||
Verify that hardware decoding is working by running `nvidia-smi`, which should show the ffmpeg
|
||||
processes:
|
||||
|
||||
```
|
||||
+-----------------------------------------------------------------------------+
|
||||
| NVIDIA-SMI 455.38 Driver Version: 455.38 CUDA Version: 11.1 |
|
||||
|-------------------------------+----------------------+----------------------+
|
||||
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
|
||||
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|
||||
| | | MIG M. |
|
||||
|===============================+======================+======================|
|
||||
| 0 GeForce GTX 166... Off | 00000000:03:00.0 Off | N/A |
|
||||
| 38% 41C P2 36W / 125W | 2082MiB / 5942MiB | 5% Default |
|
||||
| | | N/A |
|
||||
+-------------------------------+----------------------+----------------------+
|
||||
|
||||
+-----------------------------------------------------------------------------+
|
||||
| Processes: |
|
||||
| GPU GI CI PID Type Process name GPU Memory |
|
||||
| ID ID Usage |
|
||||
|=============================================================================|
|
||||
| 0 N/A N/A 12737 C ffmpeg 249MiB |
|
||||
| 0 N/A N/A 12751 C ffmpeg 249MiB |
|
||||
| 0 N/A N/A 12772 C ffmpeg 249MiB |
|
||||
| 0 N/A N/A 12775 C ffmpeg 249MiB |
|
||||
| 0 N/A N/A 12800 C ffmpeg 249MiB |
|
||||
| 0 N/A N/A 12811 C ffmpeg 417MiB |
|
||||
| 0 N/A N/A 12827 C ffmpeg 417MiB |
|
||||
+-----------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
To further improve performance, you can set ffmpeg to skip frames in the output,
|
||||
using the fps filter:
|
||||
|
||||
```
|
||||
output_args:
|
||||
- -filter:v
|
||||
- fps=fps=5
|
||||
```
|
||||
|
||||
This setting, for example, allows Frigate to consume my 10-15fps camera streams on
|
||||
my relatively low powered Haswell machine with relatively low cpu usage.
|
||||
|
||||
|
After Width: | Height: | Size: 73 KiB |
@@ -0,0 +1,15 @@
|
||||
import faulthandler; faulthandler.enable()
|
||||
import sys
|
||||
import threading
|
||||
|
||||
threading.current_thread().name = "frigate"
|
||||
|
||||
from frigate.app import FrigateApp
|
||||
|
||||
cli = sys.modules['flask.cli']
|
||||
cli.show_server_banner = lambda *x: None
|
||||
|
||||
if __name__ == '__main__':
|
||||
frigate_app = FrigateApp()
|
||||
|
||||
frigate_app.start()
|
||||
@@ -0,0 +1,258 @@
|
||||
import json
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
import os
|
||||
from logging.handlers import QueueHandler
|
||||
from typing import Dict, List
|
||||
import sys
|
||||
import signal
|
||||
|
||||
import yaml
|
||||
from peewee_migrate import Router
|
||||
from playhouse.sqlite_ext import SqliteExtDatabase
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
||||
from frigate.edgetpu import EdgeTPUProcess
|
||||
from frigate.events import EventProcessor, EventCleanup
|
||||
from frigate.http import create_app
|
||||
from frigate.log import log_process, root_configurer
|
||||
from frigate.models import Event
|
||||
from frigate.mqtt import create_mqtt_client
|
||||
from frigate.object_processing import TrackedObjectProcessor
|
||||
from frigate.record import RecordingMaintainer
|
||||
from frigate.stats import StatsEmitter, stats_init
|
||||
from frigate.video import capture_camera, track_camera
|
||||
from frigate.watchdog import FrigateWatchdog
|
||||
from frigate.zeroconf import broadcast_zeroconf
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FrigateApp():
|
||||
def __init__(self):
|
||||
self.stop_event = mp.Event()
|
||||
self.config: FrigateConfig = None
|
||||
self.detection_queue = mp.Queue()
|
||||
self.detectors: Dict[str, EdgeTPUProcess] = {}
|
||||
self.detection_out_events: Dict[str, mp.Event] = {}
|
||||
self.detection_shms: List[mp.shared_memory.SharedMemory] = []
|
||||
self.log_queue = mp.Queue()
|
||||
self.camera_metrics = {}
|
||||
|
||||
def set_environment_vars(self):
|
||||
for key, value in self.config.environment_vars.items():
|
||||
os.environ[key] = value
|
||||
|
||||
def ensure_dirs(self):
|
||||
for d in [RECORD_DIR, CLIPS_DIR, CACHE_DIR]:
|
||||
if not os.path.exists(d) and not os.path.islink(d):
|
||||
logger.info(f"Creating directory: {d}")
|
||||
os.makedirs(d)
|
||||
else:
|
||||
logger.debug(f"Skipping directory: {d}")
|
||||
|
||||
tmpfs_size = self.config.clips.tmpfs_cache_size
|
||||
if tmpfs_size:
|
||||
logger.info(f"Creating tmpfs of size {tmpfs_size}")
|
||||
rc = os.system(f"mount -t tmpfs -o size={tmpfs_size} tmpfs {CACHE_DIR}")
|
||||
if rc != 0:
|
||||
logger.error(f"Failed to create tmpfs, error code: {rc}")
|
||||
|
||||
def init_logger(self):
|
||||
self.log_process = mp.Process(target=log_process, args=(self.log_queue,), name='log_process')
|
||||
self.log_process.daemon = True
|
||||
self.log_process.start()
|
||||
root_configurer(self.log_queue)
|
||||
|
||||
def init_config(self):
|
||||
config_file = os.environ.get('CONFIG_FILE', '/config/config.yml')
|
||||
self.config = FrigateConfig(config_file=config_file)
|
||||
|
||||
for camera_name in self.config.cameras.keys():
|
||||
# create camera_metrics
|
||||
self.camera_metrics[camera_name] = {
|
||||
'camera_fps': mp.Value('d', 0.0),
|
||||
'skipped_fps': mp.Value('d', 0.0),
|
||||
'process_fps': mp.Value('d', 0.0),
|
||||
'detection_enabled': mp.Value('i', 1),
|
||||
'detection_fps': mp.Value('d', 0.0),
|
||||
'detection_frame': mp.Value('d', 0.0),
|
||||
'read_start': mp.Value('d', 0.0),
|
||||
'ffmpeg_pid': mp.Value('i', 0),
|
||||
'frame_queue': mp.Queue(maxsize=2),
|
||||
}
|
||||
|
||||
def check_config(self):
|
||||
for name, camera in self.config.cameras.items():
|
||||
assigned_roles = list(set([r for i in camera.ffmpeg.inputs for r in i.roles]))
|
||||
if not camera.clips.enabled and 'clips' in assigned_roles:
|
||||
logger.warning(f"Camera {name} has clips assigned to an input, but clips is not enabled.")
|
||||
elif camera.clips.enabled and not 'clips' in assigned_roles:
|
||||
logger.warning(f"Camera {name} has clips enabled, but clips is not assigned to an input.")
|
||||
|
||||
if not camera.record.enabled and 'record' in assigned_roles:
|
||||
logger.warning(f"Camera {name} has record assigned to an input, but record is not enabled.")
|
||||
elif camera.record.enabled and not 'record' in assigned_roles:
|
||||
logger.warning(f"Camera {name} has record enabled, but record is not assigned to an input.")
|
||||
|
||||
if not camera.rtmp.enabled and 'rtmp' in assigned_roles:
|
||||
logger.warning(f"Camera {name} has rtmp assigned to an input, but rtmp is not enabled.")
|
||||
elif camera.rtmp.enabled and not 'rtmp' in assigned_roles:
|
||||
logger.warning(f"Camera {name} has rtmp enabled, but rtmp is not assigned to an input.")
|
||||
|
||||
def set_log_levels(self):
|
||||
logging.getLogger().setLevel(self.config.logger.default)
|
||||
for log, level in self.config.logger.logs.items():
|
||||
logging.getLogger(log).setLevel(level)
|
||||
|
||||
if not 'werkzeug' in self.config.logger.logs:
|
||||
logging.getLogger('werkzeug').setLevel('ERROR')
|
||||
|
||||
def init_queues(self):
|
||||
# Queues for clip processing
|
||||
self.event_queue = mp.Queue()
|
||||
self.event_processed_queue = mp.Queue()
|
||||
|
||||
# Queue for cameras to push tracked objects to
|
||||
self.detected_frames_queue = mp.Queue(maxsize=len(self.config.cameras.keys())*2)
|
||||
|
||||
def init_database(self):
|
||||
self.db = SqliteExtDatabase(self.config.database.path)
|
||||
|
||||
# Run migrations
|
||||
del(logging.getLogger('peewee_migrate').handlers[:])
|
||||
router = Router(self.db)
|
||||
router.run()
|
||||
|
||||
models = [Event]
|
||||
self.db.bind(models)
|
||||
|
||||
def init_stats(self):
|
||||
self.stats_tracking = stats_init(self.camera_metrics, self.detectors)
|
||||
|
||||
def init_web_server(self):
|
||||
self.flask_app = create_app(self.config, self.db, self.stats_tracking, self.detected_frames_processor)
|
||||
|
||||
def init_mqtt(self):
|
||||
self.mqtt_client = create_mqtt_client(self.config, self.camera_metrics)
|
||||
|
||||
def start_detectors(self):
|
||||
model_shape = (self.config.model.height, self.config.model.width)
|
||||
for name in self.config.cameras.keys():
|
||||
self.detection_out_events[name] = mp.Event()
|
||||
shm_in = mp.shared_memory.SharedMemory(name=name, create=True, size=self.config.model.height*self.config.model.width*3)
|
||||
shm_out = mp.shared_memory.SharedMemory(name=f"out-{name}", create=True, size=20*6*4)
|
||||
self.detection_shms.append(shm_in)
|
||||
self.detection_shms.append(shm_out)
|
||||
|
||||
for name, detector in self.config.detectors.items():
|
||||
if detector.type == 'cpu':
|
||||
self.detectors[name] = EdgeTPUProcess(name, self.detection_queue, self.detection_out_events, model_shape, 'cpu', detector.num_threads)
|
||||
if detector.type == 'edgetpu':
|
||||
self.detectors[name] = EdgeTPUProcess(name, self.detection_queue, self.detection_out_events, model_shape, detector.device, detector.num_threads)
|
||||
|
||||
def start_detected_frames_processor(self):
|
||||
self.detected_frames_processor = TrackedObjectProcessor(self.config, self.mqtt_client, self.config.mqtt.topic_prefix,
|
||||
self.detected_frames_queue, self.event_queue, self.event_processed_queue, self.stop_event)
|
||||
self.detected_frames_processor.start()
|
||||
|
||||
def start_camera_processors(self):
|
||||
model_shape = (self.config.model.height, self.config.model.width)
|
||||
for name, config in self.config.cameras.items():
|
||||
camera_process = mp.Process(target=track_camera, name=f"camera_processor:{name}", args=(name, config, model_shape,
|
||||
self.detection_queue, self.detection_out_events[name], self.detected_frames_queue,
|
||||
self.camera_metrics[name]))
|
||||
camera_process.daemon = True
|
||||
self.camera_metrics[name]['process'] = camera_process
|
||||
camera_process.start()
|
||||
logger.info(f"Camera processor started for {name}: {camera_process.pid}")
|
||||
|
||||
def start_camera_capture_processes(self):
|
||||
for name, config in self.config.cameras.items():
|
||||
capture_process = mp.Process(target=capture_camera, name=f"camera_capture:{name}", args=(name, config,
|
||||
self.camera_metrics[name]))
|
||||
capture_process.daemon = True
|
||||
self.camera_metrics[name]['capture_process'] = capture_process
|
||||
capture_process.start()
|
||||
logger.info(f"Capture process started for {name}: {capture_process.pid}")
|
||||
|
||||
def start_event_processor(self):
|
||||
self.event_processor = EventProcessor(self.config, self.camera_metrics, self.event_queue, self.event_processed_queue, self.stop_event)
|
||||
self.event_processor.start()
|
||||
|
||||
def start_event_cleanup(self):
|
||||
self.event_cleanup = EventCleanup(self.config, self.stop_event)
|
||||
self.event_cleanup.start()
|
||||
|
||||
def start_recording_maintainer(self):
|
||||
self.recording_maintainer = RecordingMaintainer(self.config, self.stop_event)
|
||||
self.recording_maintainer.start()
|
||||
|
||||
def start_stats_emitter(self):
|
||||
self.stats_emitter = StatsEmitter(self.config, self.stats_tracking, self.mqtt_client, self.config.mqtt.topic_prefix, self.stop_event)
|
||||
self.stats_emitter.start()
|
||||
|
||||
def start_watchdog(self):
|
||||
self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event)
|
||||
self.frigate_watchdog.start()
|
||||
|
||||
def start(self):
|
||||
self.init_logger()
|
||||
try:
|
||||
try:
|
||||
self.init_config()
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing config: {e}")
|
||||
self.log_process.terminate()
|
||||
sys.exit(1)
|
||||
self.set_environment_vars()
|
||||
self.ensure_dirs()
|
||||
self.check_config()
|
||||
self.set_log_levels()
|
||||
self.init_queues()
|
||||
self.init_database()
|
||||
self.init_mqtt()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.log_process.terminate()
|
||||
sys.exit(1)
|
||||
self.start_detectors()
|
||||
self.start_detected_frames_processor()
|
||||
self.start_camera_processors()
|
||||
self.start_camera_capture_processes()
|
||||
self.init_stats()
|
||||
self.init_web_server()
|
||||
self.start_event_processor()
|
||||
self.start_event_cleanup()
|
||||
self.start_recording_maintainer()
|
||||
self.start_stats_emitter()
|
||||
self.start_watchdog()
|
||||
# self.zeroconf = broadcast_zeroconf(self.config.mqtt.client_id)
|
||||
|
||||
def receiveSignal(signalNumber, frame):
|
||||
self.stop()
|
||||
sys.exit()
|
||||
|
||||
signal.signal(signal.SIGTERM, receiveSignal)
|
||||
|
||||
self.flask_app.run(host='127.0.0.1', port=5001, debug=False)
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
logger.info(f"Stopping...")
|
||||
self.stop_event.set()
|
||||
|
||||
self.detected_frames_processor.join()
|
||||
self.event_processor.join()
|
||||
self.event_cleanup.join()
|
||||
self.recording_maintainer.join()
|
||||
self.stats_emitter.join()
|
||||
self.frigate_watchdog.join()
|
||||
|
||||
for detector in self.detectors.values():
|
||||
detector.stop()
|
||||
|
||||
while len(self.detection_shms) > 0:
|
||||
shm = self.detection_shms.pop()
|
||||
shm.close()
|
||||
shm.unlink()
|
||||
@@ -0,0 +1,3 @@
|
||||
CLIPS_DIR = '/media/frigate/clips'
|
||||
RECORD_DIR = '/media/frigate/recordings'
|
||||
CACHE_DIR = '/tmp/cache'
|
||||
@@ -0,0 +1,226 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
import os
|
||||
import queue
|
||||
import threading
|
||||
import signal
|
||||
from abc import ABC, abstractmethod
|
||||
from multiprocessing.connection import Connection
|
||||
from setproctitle import setproctitle
|
||||
from typing import Dict
|
||||
|
||||
import numpy as np
|
||||
import tflite_runtime.interpreter as tflite
|
||||
from tflite_runtime.interpreter import load_delegate
|
||||
|
||||
from frigate.util import EventsPerSecond, SharedMemoryFrameManager, listen
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def load_labels(path, encoding='utf-8'):
|
||||
"""Loads labels from file (with or without index numbers).
|
||||
Args:
|
||||
path: path to label file.
|
||||
encoding: label file encoding.
|
||||
Returns:
|
||||
Dictionary mapping indices to labels.
|
||||
"""
|
||||
with open(path, 'r', encoding=encoding) as f:
|
||||
lines = f.readlines()
|
||||
if not lines:
|
||||
return {}
|
||||
|
||||
if lines[0].split(' ', maxsplit=1)[0].isdigit():
|
||||
pairs = [line.split(' ', maxsplit=1) for line in lines]
|
||||
return {int(index): label.strip() for index, label in pairs}
|
||||
else:
|
||||
return {index: line.strip() for index, line in enumerate(lines)}
|
||||
|
||||
class ObjectDetector(ABC):
|
||||
@abstractmethod
|
||||
def detect(self, tensor_input, threshold = .4):
|
||||
pass
|
||||
|
||||
class LocalObjectDetector(ObjectDetector):
|
||||
def __init__(self, tf_device=None, num_threads=3, labels=None):
|
||||
self.fps = EventsPerSecond()
|
||||
if labels is None:
|
||||
self.labels = {}
|
||||
else:
|
||||
self.labels = load_labels(labels)
|
||||
|
||||
device_config = {"device": "usb"}
|
||||
if not tf_device is None:
|
||||
device_config = {"device": tf_device}
|
||||
|
||||
edge_tpu_delegate = None
|
||||
|
||||
if tf_device != 'cpu':
|
||||
try:
|
||||
logger.info(f"Attempting to load TPU as {device_config['device']}")
|
||||
edge_tpu_delegate = load_delegate('libedgetpu.so.1.0', device_config)
|
||||
logger.info("TPU found")
|
||||
self.interpreter = tflite.Interpreter(
|
||||
model_path='/edgetpu_model.tflite',
|
||||
experimental_delegates=[edge_tpu_delegate])
|
||||
except ValueError:
|
||||
logger.info("No EdgeTPU detected.")
|
||||
raise
|
||||
else:
|
||||
self.interpreter = tflite.Interpreter(
|
||||
model_path='/cpu_model.tflite', num_threads=num_threads)
|
||||
|
||||
self.interpreter.allocate_tensors()
|
||||
|
||||
self.tensor_input_details = self.interpreter.get_input_details()
|
||||
self.tensor_output_details = self.interpreter.get_output_details()
|
||||
|
||||
def detect(self, tensor_input, threshold=.4):
|
||||
detections = []
|
||||
|
||||
raw_detections = self.detect_raw(tensor_input)
|
||||
|
||||
for d in raw_detections:
|
||||
if d[1] < threshold:
|
||||
break
|
||||
detections.append((
|
||||
self.labels[int(d[0])],
|
||||
float(d[1]),
|
||||
(d[2], d[3], d[4], d[5])
|
||||
))
|
||||
self.fps.update()
|
||||
return detections
|
||||
|
||||
def detect_raw(self, tensor_input):
|
||||
self.interpreter.set_tensor(self.tensor_input_details[0]['index'], tensor_input)
|
||||
self.interpreter.invoke()
|
||||
boxes = np.squeeze(self.interpreter.get_tensor(self.tensor_output_details[0]['index']))
|
||||
label_codes = np.squeeze(self.interpreter.get_tensor(self.tensor_output_details[1]['index']))
|
||||
scores = np.squeeze(self.interpreter.get_tensor(self.tensor_output_details[2]['index']))
|
||||
|
||||
detections = np.zeros((20,6), np.float32)
|
||||
for i, score in enumerate(scores):
|
||||
detections[i] = [label_codes[i], score, boxes[i][0], boxes[i][1], boxes[i][2], boxes[i][3]]
|
||||
|
||||
return detections
|
||||
|
||||
def run_detector(name: str, detection_queue: mp.Queue, out_events: Dict[str, mp.Event], avg_speed, start, model_shape, tf_device, num_threads):
|
||||
threading.current_thread().name = f"detector:{name}"
|
||||
logger = logging.getLogger(f"detector.{name}")
|
||||
logger.info(f"Starting detection process: {os.getpid()}")
|
||||
setproctitle(f"frigate.detector.{name}")
|
||||
listen()
|
||||
|
||||
stop_event = mp.Event()
|
||||
def receiveSignal(signalNumber, frame):
|
||||
stop_event.set()
|
||||
|
||||
signal.signal(signal.SIGTERM, receiveSignal)
|
||||
signal.signal(signal.SIGINT, receiveSignal)
|
||||
|
||||
frame_manager = SharedMemoryFrameManager()
|
||||
object_detector = LocalObjectDetector(tf_device=tf_device, num_threads=num_threads)
|
||||
|
||||
outputs = {}
|
||||
for name in out_events.keys():
|
||||
out_shm = mp.shared_memory.SharedMemory(name=f"out-{name}", create=False)
|
||||
out_np = np.ndarray((20,6), dtype=np.float32, buffer=out_shm.buf)
|
||||
outputs[name] = {
|
||||
'shm': out_shm,
|
||||
'np': out_np
|
||||
}
|
||||
|
||||
while True:
|
||||
if stop_event.is_set():
|
||||
break
|
||||
|
||||
try:
|
||||
connection_id = detection_queue.get(timeout=5)
|
||||
except queue.Empty:
|
||||
continue
|
||||
input_frame = frame_manager.get(connection_id, (1,model_shape[0],model_shape[1],3))
|
||||
|
||||
if input_frame is None:
|
||||
continue
|
||||
|
||||
# detect and send the output
|
||||
start.value = datetime.datetime.now().timestamp()
|
||||
detections = object_detector.detect_raw(input_frame)
|
||||
duration = datetime.datetime.now().timestamp()-start.value
|
||||
outputs[connection_id]['np'][:] = detections[:]
|
||||
out_events[connection_id].set()
|
||||
start.value = 0.0
|
||||
|
||||
avg_speed.value = (avg_speed.value*9 + duration)/10
|
||||
|
||||
class EdgeTPUProcess():
|
||||
def __init__(self, name, detection_queue, out_events, model_shape, tf_device=None, num_threads=3):
|
||||
self.name = name
|
||||
self.out_events = out_events
|
||||
self.detection_queue = detection_queue
|
||||
self.avg_inference_speed = mp.Value('d', 0.01)
|
||||
self.detection_start = mp.Value('d', 0.0)
|
||||
self.detect_process = None
|
||||
self.model_shape = model_shape
|
||||
self.tf_device = tf_device
|
||||
self.num_threads = num_threads
|
||||
self.start_or_restart()
|
||||
|
||||
def stop(self):
|
||||
self.detect_process.terminate()
|
||||
logging.info("Waiting for detection process to exit gracefully...")
|
||||
self.detect_process.join(timeout=30)
|
||||
if self.detect_process.exitcode is None:
|
||||
logging.info("Detection process didnt exit. Force killing...")
|
||||
self.detect_process.kill()
|
||||
self.detect_process.join()
|
||||
|
||||
def start_or_restart(self):
|
||||
self.detection_start.value = 0.0
|
||||
if (not self.detect_process is None) and self.detect_process.is_alive():
|
||||
self.stop()
|
||||
self.detect_process = mp.Process(target=run_detector, name=f"detector:{self.name}", args=(self.name, self.detection_queue, self.out_events, self.avg_inference_speed, self.detection_start, self.model_shape, self.tf_device, self.num_threads))
|
||||
self.detect_process.daemon = True
|
||||
self.detect_process.start()
|
||||
|
||||
class RemoteObjectDetector():
|
||||
def __init__(self, name, labels, detection_queue, event, model_shape):
|
||||
self.labels = load_labels(labels)
|
||||
self.name = name
|
||||
self.fps = EventsPerSecond()
|
||||
self.detection_queue = detection_queue
|
||||
self.event = event
|
||||
self.shm = mp.shared_memory.SharedMemory(name=self.name, create=False)
|
||||
self.np_shm = np.ndarray((1,model_shape[0],model_shape[1],3), dtype=np.uint8, buffer=self.shm.buf)
|
||||
self.out_shm = mp.shared_memory.SharedMemory(name=f"out-{self.name}", create=False)
|
||||
self.out_np_shm = np.ndarray((20,6), dtype=np.float32, buffer=self.out_shm.buf)
|
||||
|
||||
def detect(self, tensor_input, threshold=.4):
|
||||
detections = []
|
||||
|
||||
# copy input to shared memory
|
||||
self.np_shm[:] = tensor_input[:]
|
||||
self.event.clear()
|
||||
self.detection_queue.put(self.name)
|
||||
result = self.event.wait(timeout=10.0)
|
||||
|
||||
# if it timed out
|
||||
if result is None:
|
||||
return detections
|
||||
|
||||
for d in self.out_np_shm:
|
||||
if d[1] < threshold:
|
||||
break
|
||||
detections.append((
|
||||
self.labels[int(d[0])],
|
||||
float(d[1]),
|
||||
(d[2], d[3], d[4], d[5])
|
||||
))
|
||||
self.fps.update()
|
||||
return detections
|
||||
|
||||
def cleanup(self):
|
||||
self.shm.unlink()
|
||||
self.out_shm.unlink()
|
||||
@@ -0,0 +1,306 @@
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import subprocess as sp
|
||||
import threading
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
import psutil
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
||||
from frigate.models import Event
|
||||
|
||||
from peewee import fn
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EventProcessor(threading.Thread):
|
||||
def __init__(self, config, camera_processes, event_queue, event_processed_queue, stop_event):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = 'event_processor'
|
||||
self.config = config
|
||||
self.camera_processes = camera_processes
|
||||
self.cached_clips = {}
|
||||
self.event_queue = event_queue
|
||||
self.event_processed_queue = event_processed_queue
|
||||
self.events_in_process = {}
|
||||
self.stop_event = stop_event
|
||||
|
||||
def refresh_cache(self):
|
||||
cached_files = os.listdir(CACHE_DIR)
|
||||
|
||||
files_in_use = []
|
||||
for process in psutil.process_iter():
|
||||
try:
|
||||
if process.name() != 'ffmpeg':
|
||||
continue
|
||||
|
||||
flist = process.open_files()
|
||||
if flist:
|
||||
for nt in flist:
|
||||
if nt.path.startswith(CACHE_DIR):
|
||||
files_in_use.append(nt.path.split('/')[-1])
|
||||
except:
|
||||
continue
|
||||
|
||||
for f in cached_files:
|
||||
if f in files_in_use or f in self.cached_clips:
|
||||
continue
|
||||
|
||||
camera = '-'.join(f.split('-')[:-1])
|
||||
start_time = datetime.datetime.strptime(f.split('-')[-1].split('.')[0], '%Y%m%d%H%M%S')
|
||||
|
||||
ffprobe_cmd = " ".join([
|
||||
'ffprobe',
|
||||
'-v',
|
||||
'error',
|
||||
'-show_entries',
|
||||
'format=duration',
|
||||
'-of',
|
||||
'default=noprint_wrappers=1:nokey=1',
|
||||
f"{os.path.join(CACHE_DIR,f)}"
|
||||
])
|
||||
p = sp.Popen(ffprobe_cmd, stdout=sp.PIPE, shell=True)
|
||||
(output, err) = p.communicate()
|
||||
p_status = p.wait()
|
||||
if p_status == 0:
|
||||
duration = float(output.decode('utf-8').strip())
|
||||
else:
|
||||
logger.info(f"bad file: {f}")
|
||||
os.remove(os.path.join(CACHE_DIR,f))
|
||||
continue
|
||||
|
||||
self.cached_clips[f] = {
|
||||
'path': f,
|
||||
'camera': camera,
|
||||
'start_time': start_time.timestamp(),
|
||||
'duration': duration
|
||||
}
|
||||
|
||||
if len(self.events_in_process) > 0:
|
||||
earliest_event = min(self.events_in_process.values(), key=lambda x:x['start_time'])['start_time']
|
||||
else:
|
||||
earliest_event = datetime.datetime.now().timestamp()
|
||||
|
||||
# if the earliest event exceeds the max seconds, cap it
|
||||
max_seconds = self.config.clips.max_seconds
|
||||
if datetime.datetime.now().timestamp()-earliest_event > max_seconds:
|
||||
earliest_event = datetime.datetime.now().timestamp()-max_seconds
|
||||
|
||||
for f, data in list(self.cached_clips.items()):
|
||||
if earliest_event-90 > data['start_time']+data['duration']:
|
||||
del self.cached_clips[f]
|
||||
os.remove(os.path.join(CACHE_DIR,f))
|
||||
|
||||
def create_clip(self, camera, event_data, pre_capture, post_capture):
|
||||
# get all clips from the camera with the event sorted
|
||||
sorted_clips = sorted([c for c in self.cached_clips.values() if c['camera'] == camera], key = lambda i: i['start_time'])
|
||||
|
||||
while len(sorted_clips) == 0 or sorted_clips[-1]['start_time'] + sorted_clips[-1]['duration'] < event_data['end_time']+post_capture:
|
||||
logger.debug(f"No cache clips for {camera}. Waiting...")
|
||||
time.sleep(5)
|
||||
self.refresh_cache()
|
||||
# get all clips from the camera with the event sorted
|
||||
sorted_clips = sorted([c for c in self.cached_clips.values() if c['camera'] == camera], key = lambda i: i['start_time'])
|
||||
|
||||
playlist_start = event_data['start_time']-pre_capture
|
||||
playlist_end = event_data['end_time']+post_capture
|
||||
playlist_lines = []
|
||||
for clip in sorted_clips:
|
||||
# clip ends before playlist start time, skip
|
||||
if clip['start_time']+clip['duration'] < playlist_start:
|
||||
continue
|
||||
# clip starts after playlist ends, finish
|
||||
if clip['start_time'] > playlist_end:
|
||||
break
|
||||
playlist_lines.append(f"file '{os.path.join(CACHE_DIR,clip['path'])}'")
|
||||
# if this is the starting clip, add an inpoint
|
||||
if clip['start_time'] < playlist_start:
|
||||
playlist_lines.append(f"inpoint {int(playlist_start-clip['start_time'])}")
|
||||
# if this is the ending clip, add an outpoint
|
||||
if clip['start_time']+clip['duration'] > playlist_end:
|
||||
playlist_lines.append(f"outpoint {int(playlist_end-clip['start_time'])}")
|
||||
|
||||
clip_name = f"{camera}-{event_data['id']}"
|
||||
ffmpeg_cmd = [
|
||||
'ffmpeg',
|
||||
'-y',
|
||||
'-protocol_whitelist',
|
||||
'pipe,file',
|
||||
'-f',
|
||||
'concat',
|
||||
'-safe',
|
||||
'0',
|
||||
'-i',
|
||||
'-',
|
||||
'-c',
|
||||
'copy',
|
||||
'-movflags',
|
||||
'+faststart',
|
||||
f"{os.path.join(CLIPS_DIR, clip_name)}.mp4"
|
||||
]
|
||||
|
||||
p = sp.run(ffmpeg_cmd, input="\n".join(playlist_lines), encoding='ascii', capture_output=True)
|
||||
if p.returncode != 0:
|
||||
logger.error(p.stderr)
|
||||
return False
|
||||
return True
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.stop_event.is_set():
|
||||
logger.info(f"Exiting event processor...")
|
||||
break
|
||||
|
||||
try:
|
||||
event_type, camera, event_data = self.event_queue.get(timeout=10)
|
||||
except queue.Empty:
|
||||
if not self.stop_event.is_set():
|
||||
self.refresh_cache()
|
||||
continue
|
||||
|
||||
logger.debug(f"Event received: {event_type} {camera} {event_data['id']}")
|
||||
self.refresh_cache()
|
||||
|
||||
if event_type == 'start':
|
||||
self.events_in_process[event_data['id']] = event_data
|
||||
|
||||
if event_type == 'end':
|
||||
clips_config = self.config.cameras[camera].clips
|
||||
|
||||
if not event_data['false_positive']:
|
||||
clip_created = False
|
||||
if clips_config.enabled and (clips_config.objects is None or event_data['label'] in clips_config.objects):
|
||||
clip_created = self.create_clip(camera, event_data, clips_config.pre_capture, clips_config.post_capture)
|
||||
|
||||
Event.create(
|
||||
id=event_data['id'],
|
||||
label=event_data['label'],
|
||||
camera=camera,
|
||||
start_time=event_data['start_time'],
|
||||
end_time=event_data['end_time'],
|
||||
top_score=event_data['top_score'],
|
||||
false_positive=event_data['false_positive'],
|
||||
zones=list(event_data['entered_zones']),
|
||||
thumbnail=event_data['thumbnail'],
|
||||
has_clip=clip_created,
|
||||
has_snapshot=event_data['has_snapshot'],
|
||||
)
|
||||
del self.events_in_process[event_data['id']]
|
||||
self.event_processed_queue.put((event_data['id'], camera))
|
||||
|
||||
class EventCleanup(threading.Thread):
|
||||
def __init__(self, config: FrigateConfig, stop_event):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = 'event_cleanup'
|
||||
self.config = config
|
||||
self.stop_event = stop_event
|
||||
self.camera_keys = list(self.config.cameras.keys())
|
||||
|
||||
def expire(self, media):
|
||||
## Expire events from unlisted cameras based on the global config
|
||||
if media == 'clips':
|
||||
retain_config = self.config.clips.retain
|
||||
file_extension = 'mp4'
|
||||
update_params = {'has_clip': False}
|
||||
else:
|
||||
retain_config = self.config.snapshots.retain
|
||||
file_extension = 'jpg'
|
||||
update_params = {'has_snapshot': False}
|
||||
|
||||
distinct_labels = (Event.select(Event.label)
|
||||
.where(Event.camera.not_in(self.camera_keys))
|
||||
.distinct())
|
||||
|
||||
# loop over object types in db
|
||||
for l in distinct_labels:
|
||||
# get expiration time for this label
|
||||
expire_days = retain_config.objects.get(l.label, retain_config.default)
|
||||
expire_after = (datetime.datetime.now() - datetime.timedelta(days=expire_days)).timestamp()
|
||||
# grab all events after specific time
|
||||
expired_events = (
|
||||
Event.select()
|
||||
.where(Event.camera.not_in(self.camera_keys),
|
||||
Event.start_time < expire_after,
|
||||
Event.label == l.label)
|
||||
)
|
||||
# delete the media from disk
|
||||
for event in expired_events:
|
||||
media_name = f"{event.camera}-{event.id}"
|
||||
media = Path(f"{os.path.join(CLIPS_DIR, media_name)}.{file_extension}")
|
||||
media.unlink(missing_ok=True)
|
||||
# update the clips attribute for the db entry
|
||||
update_query = (
|
||||
Event.update(update_params)
|
||||
.where(Event.camera.not_in(self.camera_keys),
|
||||
Event.start_time < expire_after,
|
||||
Event.label == l.label)
|
||||
)
|
||||
update_query.execute()
|
||||
|
||||
## Expire events from cameras based on the camera config
|
||||
for name, camera in self.config.cameras.items():
|
||||
if media == 'clips':
|
||||
retain_config = camera.clips.retain
|
||||
else:
|
||||
retain_config = camera.snapshots.retain
|
||||
# get distinct objects in database for this camera
|
||||
distinct_labels = (Event.select(Event.label)
|
||||
.where(Event.camera == name)
|
||||
.distinct())
|
||||
|
||||
# loop over object types in db
|
||||
for l in distinct_labels:
|
||||
# get expiration time for this label
|
||||
expire_days = retain_config.objects.get(l.label, retain_config.default)
|
||||
expire_after = (datetime.datetime.now() - datetime.timedelta(days=expire_days)).timestamp()
|
||||
# grab all events after specific time
|
||||
expired_events = (
|
||||
Event.select()
|
||||
.where(Event.camera == name,
|
||||
Event.start_time < expire_after,
|
||||
Event.label == l.label)
|
||||
)
|
||||
# delete the grabbed clips from disk
|
||||
for event in expired_events:
|
||||
media_name = f"{event.camera}-{event.id}"
|
||||
media = Path(f"{os.path.join(CLIPS_DIR, media_name)}.{file_extension}")
|
||||
media.unlink(missing_ok=True)
|
||||
# update the clips attribute for the db entry
|
||||
update_query = (
|
||||
Event.update(update_params)
|
||||
.where( Event.camera == name,
|
||||
Event.start_time < expire_after,
|
||||
Event.label == l.label)
|
||||
)
|
||||
update_query.execute()
|
||||
|
||||
def run(self):
|
||||
counter = 0
|
||||
while(True):
|
||||
if self.stop_event.is_set():
|
||||
logger.info(f"Exiting event cleanup...")
|
||||
break
|
||||
|
||||
# only expire events every 10 minutes, but check for stop events every 10 seconds
|
||||
time.sleep(10)
|
||||
counter = counter + 1
|
||||
if counter < 60:
|
||||
continue
|
||||
counter = 0
|
||||
|
||||
self.expire('clips')
|
||||
self.expire('snapshots')
|
||||
|
||||
# drop events from db where has_clip and has_snapshot are false
|
||||
delete_query = (
|
||||
Event.delete()
|
||||
.where( Event.has_clip == False,
|
||||
Event.has_snapshot == False)
|
||||
)
|
||||
delete_query.execute()
|
||||
@@ -0,0 +1,268 @@
|
||||
import base64
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from functools import reduce
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from flask import (Blueprint, Flask, Response, current_app, jsonify,
|
||||
make_response, request)
|
||||
from peewee import SqliteDatabase, operator, fn, DoesNotExist
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from frigate.models import Event
|
||||
from frigate.stats import stats_snapshot
|
||||
from frigate.util import calculate_region
|
||||
from frigate.version import VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
bp = Blueprint('frigate', __name__)
|
||||
|
||||
def create_app(frigate_config, database: SqliteDatabase, stats_tracking, detected_frames_processor):
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.before_request
|
||||
def _db_connect():
|
||||
database.connect()
|
||||
|
||||
@app.teardown_request
|
||||
def _db_close(exc):
|
||||
if not database.is_closed():
|
||||
database.close()
|
||||
|
||||
app.frigate_config = frigate_config
|
||||
app.stats_tracking = stats_tracking
|
||||
app.detected_frames_processor = detected_frames_processor
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
return app
|
||||
|
||||
@bp.route('/')
|
||||
def is_healthy():
|
||||
return "Frigate is running. Alive and healthy!"
|
||||
|
||||
@bp.route('/events/summary')
|
||||
def events_summary():
|
||||
has_clip = request.args.get('has_clip', type=int)
|
||||
has_snapshot = request.args.get('has_snapshot', type=int)
|
||||
|
||||
clauses = []
|
||||
|
||||
if not has_clip is None:
|
||||
clauses.append((Event.has_clip == has_clip))
|
||||
|
||||
if not has_snapshot is None:
|
||||
clauses.append((Event.has_snapshot == has_snapshot))
|
||||
|
||||
if len(clauses) == 0:
|
||||
clauses.append((1 == 1))
|
||||
|
||||
groups = (
|
||||
Event
|
||||
.select(
|
||||
Event.camera,
|
||||
Event.label,
|
||||
fn.strftime('%Y-%m-%d', fn.datetime(Event.start_time, 'unixepoch', 'localtime')).alias('day'),
|
||||
Event.zones,
|
||||
fn.COUNT(Event.id).alias('count')
|
||||
)
|
||||
.where(reduce(operator.and_, clauses))
|
||||
.group_by(
|
||||
Event.camera,
|
||||
Event.label,
|
||||
fn.strftime('%Y-%m-%d', fn.datetime(Event.start_time, 'unixepoch', 'localtime')),
|
||||
Event.zones
|
||||
)
|
||||
)
|
||||
|
||||
return jsonify([e for e in groups.dicts()])
|
||||
|
||||
@bp.route('/events/<id>')
|
||||
def event(id):
|
||||
try:
|
||||
return model_to_dict(Event.get(Event.id == id))
|
||||
except DoesNotExist:
|
||||
return "Event not found", 404
|
||||
|
||||
@bp.route('/events/<id>/thumbnail.jpg')
|
||||
def event_snapshot(id):
|
||||
format = request.args.get('format', 'ios')
|
||||
thumbnail_bytes = None
|
||||
try:
|
||||
event = Event.get(Event.id == id)
|
||||
thumbnail_bytes = base64.b64decode(event.thumbnail)
|
||||
except DoesNotExist:
|
||||
# see if the object is currently being tracked
|
||||
try:
|
||||
for camera_state in current_app.detected_frames_processor.camera_states.values():
|
||||
if id in camera_state.tracked_objects:
|
||||
tracked_obj = camera_state.tracked_objects.get(id)
|
||||
if not tracked_obj is None:
|
||||
thumbnail_bytes = tracked_obj.get_jpg_bytes()
|
||||
except:
|
||||
return "Event not found", 404
|
||||
|
||||
if thumbnail_bytes is None:
|
||||
return "Event not found", 404
|
||||
|
||||
# android notifications prefer a 2:1 ratio
|
||||
if format == 'android':
|
||||
jpg_as_np = np.frombuffer(thumbnail_bytes, dtype=np.uint8)
|
||||
img = cv2.imdecode(jpg_as_np, flags=1)
|
||||
thumbnail = cv2.copyMakeBorder(img, 0, 0, int(img.shape[1]*0.5), int(img.shape[1]*0.5), cv2.BORDER_CONSTANT, (0,0,0))
|
||||
ret, jpg = cv2.imencode('.jpg', thumbnail)
|
||||
thumbnail_bytes = jpg.tobytes()
|
||||
|
||||
response = make_response(thumbnail_bytes)
|
||||
response.headers['Content-Type'] = 'image/jpg'
|
||||
return response
|
||||
|
||||
@bp.route('/events')
|
||||
def events():
|
||||
limit = request.args.get('limit', 100)
|
||||
camera = request.args.get('camera')
|
||||
label = request.args.get('label')
|
||||
zone = request.args.get('zone')
|
||||
after = request.args.get('after', type=int)
|
||||
before = request.args.get('before', type=int)
|
||||
has_clip = request.args.get('has_clip', type=int)
|
||||
has_snapshot = request.args.get('has_snapshot', type=int)
|
||||
|
||||
clauses = []
|
||||
|
||||
if camera:
|
||||
clauses.append((Event.camera == camera))
|
||||
|
||||
if label:
|
||||
clauses.append((Event.label == label))
|
||||
|
||||
if zone:
|
||||
clauses.append((Event.zones.cast('text') % f"*\"{zone}\"*"))
|
||||
|
||||
if after:
|
||||
clauses.append((Event.start_time >= after))
|
||||
|
||||
if before:
|
||||
clauses.append((Event.start_time <= before))
|
||||
|
||||
if not has_clip is None:
|
||||
clauses.append((Event.has_clip == has_clip))
|
||||
|
||||
if not has_snapshot is None:
|
||||
clauses.append((Event.has_snapshot == has_snapshot))
|
||||
|
||||
if len(clauses) == 0:
|
||||
clauses.append((1 == 1))
|
||||
|
||||
events = (Event.select()
|
||||
.where(reduce(operator.and_, clauses))
|
||||
.order_by(Event.start_time.desc())
|
||||
.limit(limit))
|
||||
|
||||
return jsonify([model_to_dict(e) for e in events])
|
||||
|
||||
@bp.route('/config')
|
||||
def config():
|
||||
return jsonify(current_app.frigate_config.to_dict())
|
||||
|
||||
@bp.route('/version')
|
||||
def version():
|
||||
return VERSION
|
||||
|
||||
@bp.route('/stats')
|
||||
def stats():
|
||||
stats = stats_snapshot(current_app.stats_tracking)
|
||||
return jsonify(stats)
|
||||
|
||||
@bp.route('/<camera_name>/<label>/best.jpg')
|
||||
def best(camera_name, label):
|
||||
if camera_name in current_app.frigate_config.cameras:
|
||||
best_object = current_app.detected_frames_processor.get_best(camera_name, label)
|
||||
best_frame = best_object.get('frame')
|
||||
if best_frame is None:
|
||||
best_frame = np.zeros((720,1280,3), np.uint8)
|
||||
else:
|
||||
best_frame = cv2.cvtColor(best_frame, cv2.COLOR_YUV2BGR_I420)
|
||||
|
||||
crop = bool(request.args.get('crop', 0, type=int))
|
||||
if crop:
|
||||
box = best_object.get('box', (0,0,300,300))
|
||||
region = calculate_region(best_frame.shape, box[0], box[1], box[2], box[3], 1.1)
|
||||
best_frame = best_frame[region[1]:region[3], region[0]:region[2]]
|
||||
|
||||
height = int(request.args.get('h', str(best_frame.shape[0])))
|
||||
width = int(height*best_frame.shape[1]/best_frame.shape[0])
|
||||
|
||||
best_frame = cv2.resize(best_frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
||||
ret, jpg = cv2.imencode('.jpg', best_frame)
|
||||
response = make_response(jpg.tobytes())
|
||||
response.headers['Content-Type'] = 'image/jpg'
|
||||
return response
|
||||
else:
|
||||
return "Camera named {} not found".format(camera_name), 404
|
||||
|
||||
@bp.route('/<camera_name>')
|
||||
def mjpeg_feed(camera_name):
|
||||
fps = int(request.args.get('fps', '3'))
|
||||
height = int(request.args.get('h', '360'))
|
||||
draw_options = {
|
||||
'bounding_boxes': request.args.get('bbox', type=int),
|
||||
'timestamp': request.args.get('timestamp', type=int),
|
||||
'zones': request.args.get('zones', type=int),
|
||||
'mask': request.args.get('mask', type=int),
|
||||
'motion_boxes': request.args.get('motion', type=int),
|
||||
'regions': request.args.get('regions', type=int),
|
||||
}
|
||||
if camera_name in current_app.frigate_config.cameras:
|
||||
# return a multipart response
|
||||
return Response(imagestream(current_app.detected_frames_processor, camera_name, fps, height, draw_options),
|
||||
mimetype='multipart/x-mixed-replace; boundary=frame')
|
||||
else:
|
||||
return "Camera named {} not found".format(camera_name), 404
|
||||
|
||||
@bp.route('/<camera_name>/latest.jpg')
|
||||
def latest_frame(camera_name):
|
||||
draw_options = {
|
||||
'bounding_boxes': request.args.get('bbox', type=int),
|
||||
'timestamp': request.args.get('timestamp', type=int),
|
||||
'zones': request.args.get('zones', type=int),
|
||||
'mask': request.args.get('mask', type=int),
|
||||
'motion_boxes': request.args.get('motion', type=int),
|
||||
'regions': request.args.get('regions', type=int),
|
||||
}
|
||||
if camera_name in current_app.frigate_config.cameras:
|
||||
# max out at specified FPS
|
||||
frame = current_app.detected_frames_processor.get_current_frame(camera_name, draw_options)
|
||||
if frame is None:
|
||||
frame = np.zeros((720,1280,3), np.uint8)
|
||||
|
||||
height = int(request.args.get('h', str(frame.shape[0])))
|
||||
width = int(height*frame.shape[1]/frame.shape[0])
|
||||
|
||||
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
||||
|
||||
ret, jpg = cv2.imencode('.jpg', frame)
|
||||
response = make_response(jpg.tobytes())
|
||||
response.headers['Content-Type'] = 'image/jpg'
|
||||
return response
|
||||
else:
|
||||
return "Camera named {} not found".format(camera_name), 404
|
||||
|
||||
def imagestream(detected_frames_processor, camera_name, fps, height, draw_options):
|
||||
while True:
|
||||
# max out at specified FPS
|
||||
time.sleep(1/fps)
|
||||
frame = detected_frames_processor.get_current_frame(camera_name, draw_options)
|
||||
if frame is None:
|
||||
frame = np.zeros((height,int(height*16/9),3), np.uint8)
|
||||
|
||||
width = int(height*frame.shape[1]/frame.shape[0])
|
||||
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
ret, jpg = cv2.imencode('.jpg', frame)
|
||||
yield (b'--frame\r\n'
|
||||
b'Content-Type: image/jpeg\r\n\r\n' + jpg.tobytes() + b'\r\n\r\n')
|
||||
@@ -0,0 +1,77 @@
|
||||
# adapted from https://medium.com/@jonathonbao/python3-logging-with-multiprocessing-f51f460b8778
|
||||
import logging
|
||||
import threading
|
||||
import os
|
||||
import signal
|
||||
import queue
|
||||
import multiprocessing as mp
|
||||
from logging import handlers
|
||||
from setproctitle import setproctitle
|
||||
|
||||
|
||||
def listener_configurer():
|
||||
root = logging.getLogger()
|
||||
console_handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(name)-30s %(levelname)-8s: %(message)s')
|
||||
console_handler.setFormatter(formatter)
|
||||
root.addHandler(console_handler)
|
||||
root.setLevel(logging.INFO)
|
||||
|
||||
def root_configurer(queue):
|
||||
h = handlers.QueueHandler(queue)
|
||||
root = logging.getLogger()
|
||||
root.addHandler(h)
|
||||
root.setLevel(logging.INFO)
|
||||
|
||||
def log_process(log_queue):
|
||||
stop_event = mp.Event()
|
||||
def receiveSignal(signalNumber, frame):
|
||||
stop_event.set()
|
||||
|
||||
signal.signal(signal.SIGTERM, receiveSignal)
|
||||
signal.signal(signal.SIGINT, receiveSignal)
|
||||
|
||||
threading.current_thread().name = f"logger"
|
||||
setproctitle("frigate.logger")
|
||||
listener_configurer()
|
||||
while True:
|
||||
if stop_event.is_set() and log_queue.empty():
|
||||
break
|
||||
try:
|
||||
record = log_queue.get(timeout=5)
|
||||
except queue.Empty:
|
||||
continue
|
||||
logger = logging.getLogger(record.name)
|
||||
logger.handle(record)
|
||||
|
||||
# based on https://codereview.stackexchange.com/a/17959
|
||||
class LogPipe(threading.Thread):
|
||||
def __init__(self, log_name, level):
|
||||
"""Setup the object with a logger and a loglevel
|
||||
and start the thread
|
||||
"""
|
||||
threading.Thread.__init__(self)
|
||||
self.daemon = False
|
||||
self.logger = logging.getLogger(log_name)
|
||||
self.level = level
|
||||
self.fdRead, self.fdWrite = os.pipe()
|
||||
self.pipeReader = os.fdopen(self.fdRead)
|
||||
self.start()
|
||||
|
||||
def fileno(self):
|
||||
"""Return the write file descriptor of the pipe
|
||||
"""
|
||||
return self.fdWrite
|
||||
|
||||
def run(self):
|
||||
"""Run the thread, logging everything.
|
||||
"""
|
||||
for line in iter(self.pipeReader.readline, ''):
|
||||
self.logger.log(self.level, line.strip('\n'))
|
||||
|
||||
self.pipeReader.close()
|
||||
|
||||
def close(self):
|
||||
"""Close the write end of the pipe.
|
||||
"""
|
||||
os.close(self.fdWrite)
|
||||
@@ -0,0 +1,16 @@
|
||||
from peewee import *
|
||||
from playhouse.sqlite_ext import *
|
||||
|
||||
|
||||
class Event(Model):
|
||||
id = CharField(null=False, primary_key=True, max_length=30)
|
||||
label = CharField(index=True, max_length=20)
|
||||
camera = CharField(index=True, max_length=20)
|
||||
start_time = DateTimeField()
|
||||
end_time = DateTimeField()
|
||||
top_score = FloatField()
|
||||
false_positive = BooleanField()
|
||||
zones = JSONField()
|
||||
thumbnail = TextField()
|
||||
has_clip = BooleanField(default=True)
|
||||
has_snapshot = BooleanField(default=True)
|
||||
@@ -0,0 +1,85 @@
|
||||
import cv2
|
||||
import imutils
|
||||
import numpy as np
|
||||
from frigate.config import MotionConfig
|
||||
|
||||
|
||||
class MotionDetector():
|
||||
def __init__(self, frame_shape, config: MotionConfig):
|
||||
self.config = config
|
||||
self.frame_shape = frame_shape
|
||||
self.resize_factor = frame_shape[0]/config.frame_height
|
||||
self.motion_frame_size = (config.frame_height, config.frame_height*frame_shape[1]//frame_shape[0])
|
||||
self.avg_frame = np.zeros(self.motion_frame_size, np.float)
|
||||
self.avg_delta = np.zeros(self.motion_frame_size, np.float)
|
||||
self.motion_frame_count = 0
|
||||
self.frame_counter = 0
|
||||
resized_mask = cv2.resize(config.mask, dsize=(self.motion_frame_size[1], self.motion_frame_size[0]), interpolation=cv2.INTER_LINEAR)
|
||||
self.mask = np.where(resized_mask==[0])
|
||||
|
||||
def detect(self, frame):
|
||||
motion_boxes = []
|
||||
|
||||
gray = frame[0:self.frame_shape[0], 0:self.frame_shape[1]]
|
||||
|
||||
# resize frame
|
||||
resized_frame = cv2.resize(gray, dsize=(self.motion_frame_size[1], self.motion_frame_size[0]), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
# TODO: can I improve the contrast of the grayscale image here?
|
||||
|
||||
# convert to grayscale
|
||||
# resized_frame = cv2.cvtColor(resized_frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# mask frame
|
||||
resized_frame[self.mask] = [255]
|
||||
|
||||
# it takes ~30 frames to establish a baseline
|
||||
# dont bother looking for motion
|
||||
if self.frame_counter < 30:
|
||||
self.frame_counter += 1
|
||||
else:
|
||||
# compare to average
|
||||
frameDelta = cv2.absdiff(resized_frame, cv2.convertScaleAbs(self.avg_frame))
|
||||
|
||||
# compute the average delta over the past few frames
|
||||
# higher values mean the current frame impacts the delta a lot, and a single raindrop may
|
||||
# register as motion, too low and a fast moving person wont be detected as motion
|
||||
cv2.accumulateWeighted(frameDelta, self.avg_delta, self.config.delta_alpha)
|
||||
|
||||
# compute the threshold image for the current frame
|
||||
# TODO: threshold
|
||||
current_thresh = cv2.threshold(frameDelta, self.config.threshold, 255, cv2.THRESH_BINARY)[1]
|
||||
|
||||
# black out everything in the avg_delta where there isnt motion in the current frame
|
||||
avg_delta_image = cv2.convertScaleAbs(self.avg_delta)
|
||||
avg_delta_image = cv2.bitwise_and(avg_delta_image, current_thresh)
|
||||
|
||||
# then look for deltas above the threshold, but only in areas where there is a delta
|
||||
# in the current frame. this prevents deltas from previous frames from being included
|
||||
thresh = cv2.threshold(avg_delta_image, self.config.threshold, 255, cv2.THRESH_BINARY)[1]
|
||||
|
||||
# dilate the thresholded image to fill in holes, then find contours
|
||||
# on thresholded image
|
||||
thresh = cv2.dilate(thresh, None, iterations=2)
|
||||
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
cnts = imutils.grab_contours(cnts)
|
||||
|
||||
# loop over the contours
|
||||
for c in cnts:
|
||||
# if the contour is big enough, count it as motion
|
||||
contour_area = cv2.contourArea(c)
|
||||
if contour_area > self.config.contour_area:
|
||||
x, y, w, h = cv2.boundingRect(c)
|
||||
motion_boxes.append((int(x*self.resize_factor), int(y*self.resize_factor), int((x+w)*self.resize_factor), int((y+h)*self.resize_factor)))
|
||||
|
||||
if len(motion_boxes) > 0:
|
||||
self.motion_frame_count += 1
|
||||
if self.motion_frame_count >= 10:
|
||||
# only average in the current frame if the difference persists for a bit
|
||||
cv2.accumulateWeighted(resized_frame, self.avg_frame, self.config.frame_alpha)
|
||||
else:
|
||||
# when no motion, just keep averaging the frames together
|
||||
cv2.accumulateWeighted(resized_frame, self.avg_frame, self.config.frame_alpha)
|
||||
self.motion_frame_count = 0
|
||||
|
||||
return motion_boxes
|
||||
@@ -0,0 +1,131 @@
|
||||
import logging
|
||||
import threading
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def create_mqtt_client(config: FrigateConfig, camera_metrics):
|
||||
mqtt_config = config.mqtt
|
||||
|
||||
def on_clips_command(client, userdata, message):
|
||||
payload = message.payload.decode()
|
||||
logger.debug(f"on_clips_toggle: {message.topic} {payload}")
|
||||
|
||||
camera_name = message.topic.split('/')[-3]
|
||||
command = message.topic.split('/')[-1]
|
||||
|
||||
clips_settings = config.cameras[camera_name].clips
|
||||
|
||||
if payload == 'ON':
|
||||
if not clips_settings.enabled:
|
||||
logger.info(f"Turning on clips for {camera_name} via mqtt")
|
||||
clips_settings._enabled = True
|
||||
elif payload == 'OFF':
|
||||
if clips_settings.enabled:
|
||||
logger.info(f"Turning off clips for {camera_name} via mqtt")
|
||||
clips_settings._enabled = False
|
||||
else:
|
||||
logger.warning(f"Received unsupported value at {message.topic}: {payload}")
|
||||
|
||||
if command == "set":
|
||||
state_topic = f"{message.topic[:-4]}/state"
|
||||
client.publish(state_topic, payload, retain=True)
|
||||
|
||||
def on_snapshots_command(client, userdata, message):
|
||||
payload = message.payload.decode()
|
||||
logger.debug(f"on_snapshots_toggle: {message.topic} {payload}")
|
||||
|
||||
camera_name = message.topic.split('/')[-3]
|
||||
command = message.topic.split('/')[-1]
|
||||
|
||||
snapshots_settings = config.cameras[camera_name].snapshots
|
||||
|
||||
if payload == 'ON':
|
||||
if not snapshots_settings.enabled:
|
||||
logger.info(f"Turning on snapshots for {camera_name} via mqtt")
|
||||
snapshots_settings._enabled = True
|
||||
elif payload == 'OFF':
|
||||
if snapshots_settings.enabled:
|
||||
logger.info(f"Turning off snapshots for {camera_name} via mqtt")
|
||||
snapshots_settings._enabled = False
|
||||
else:
|
||||
logger.warning(f"Received unsupported value at {message.topic}: {payload}")
|
||||
|
||||
if command == "set":
|
||||
state_topic = f"{message.topic[:-4]}/state"
|
||||
client.publish(state_topic, payload, retain=True)
|
||||
|
||||
def on_detect_command(client, userdata, message):
|
||||
payload = message.payload.decode()
|
||||
logger.debug(f"on_detect_toggle: {message.topic} {payload}")
|
||||
|
||||
camera_name = message.topic.split('/')[-3]
|
||||
command = message.topic.split('/')[-1]
|
||||
|
||||
detect_settings = config.cameras[camera_name].detect
|
||||
|
||||
if payload == 'ON':
|
||||
if not camera_metrics[camera_name]["detection_enabled"].value:
|
||||
logger.info(f"Turning on detection for {camera_name} via mqtt")
|
||||
camera_metrics[camera_name]["detection_enabled"].value = True
|
||||
detect_settings._enabled = True
|
||||
elif payload == 'OFF':
|
||||
if camera_metrics[camera_name]["detection_enabled"].value:
|
||||
logger.info(f"Turning off detection for {camera_name} via mqtt")
|
||||
camera_metrics[camera_name]["detection_enabled"].value = False
|
||||
detect_settings._enabled = False
|
||||
else:
|
||||
logger.warning(f"Received unsupported value at {message.topic}: {payload}")
|
||||
|
||||
if command == "set":
|
||||
state_topic = f"{message.topic[:-4]}/state"
|
||||
client.publish(state_topic, payload, retain=True)
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
threading.current_thread().name = "mqtt"
|
||||
if rc != 0:
|
||||
if rc == 3:
|
||||
logger.error("MQTT Server unavailable")
|
||||
elif rc == 4:
|
||||
logger.error("MQTT Bad username or password")
|
||||
elif rc == 5:
|
||||
logger.error("MQTT Not authorized")
|
||||
else:
|
||||
logger.error("Unable to connect to MQTT: Connection refused. Error code: " + str(rc))
|
||||
|
||||
logger.info("MQTT connected")
|
||||
client.publish(mqtt_config.topic_prefix+'/available', 'online', retain=True)
|
||||
|
||||
client = mqtt.Client(client_id=mqtt_config.client_id)
|
||||
client.on_connect = on_connect
|
||||
client.will_set(mqtt_config.topic_prefix+'/available', payload='offline', qos=1, retain=True)
|
||||
|
||||
# register callbacks
|
||||
for name in config.cameras.keys():
|
||||
client.message_callback_add(f"{mqtt_config.topic_prefix}/{name}/clips/#", on_clips_command)
|
||||
client.message_callback_add(f"{mqtt_config.topic_prefix}/{name}/snapshots/#", on_snapshots_command)
|
||||
client.message_callback_add(f"{mqtt_config.topic_prefix}/{name}/detect/#", on_detect_command)
|
||||
|
||||
if not mqtt_config.user is None:
|
||||
client.username_pw_set(mqtt_config.user, password=mqtt_config.password)
|
||||
try:
|
||||
client.connect(mqtt_config.host, mqtt_config.port, 60)
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to connect to MQTT server: {e}")
|
||||
raise
|
||||
|
||||
client.loop_start()
|
||||
|
||||
for name in config.cameras.keys():
|
||||
client.publish(f"{mqtt_config.topic_prefix}/{name}/clips/state", 'ON' if config.cameras[name].clips.enabled else 'OFF', retain=True)
|
||||
client.publish(f"{mqtt_config.topic_prefix}/{name}/snapshots/state", 'ON' if config.cameras[name].clips.enabled else 'OFF', retain=True)
|
||||
client.publish(f"{mqtt_config.topic_prefix}/{name}/detect/state", 'ON' if config.cameras[name].clips.enabled else 'OFF', retain=True)
|
||||
|
||||
client.subscribe(f"{mqtt_config.topic_prefix}/+/clips/#")
|
||||
client.subscribe(f"{mqtt_config.topic_prefix}/+/snapshots/#")
|
||||
client.subscribe(f"{mqtt_config.topic_prefix}/+/detect/#")
|
||||
|
||||
return client
|
||||
@@ -0,0 +1,547 @@
|
||||
import copy
|
||||
import base64
|
||||
import datetime
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from collections import Counter, defaultdict
|
||||
from statistics import mean, median
|
||||
from typing import Callable, Dict
|
||||
|
||||
import cv2
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
from frigate.config import FrigateConfig, CameraConfig
|
||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
||||
from frigate.edgetpu import load_labels
|
||||
from frigate.util import SharedMemoryFrameManager, draw_box_with_label, calculate_region
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PATH_TO_LABELS = '/labelmap.txt'
|
||||
|
||||
LABELS = load_labels(PATH_TO_LABELS)
|
||||
cmap = plt.cm.get_cmap('tab10', len(LABELS.keys()))
|
||||
|
||||
COLOR_MAP = {}
|
||||
for key, val in LABELS.items():
|
||||
COLOR_MAP[val] = tuple(int(round(255 * c)) for c in cmap(key)[:3])
|
||||
|
||||
def on_edge(box, frame_shape):
|
||||
if (
|
||||
box[0] == 0 or
|
||||
box[1] == 0 or
|
||||
box[2] == frame_shape[1]-1 or
|
||||
box[3] == frame_shape[0]-1
|
||||
):
|
||||
return True
|
||||
|
||||
def is_better_thumbnail(current_thumb, new_obj, frame_shape) -> bool:
|
||||
# larger is better
|
||||
# cutoff images are less ideal, but they should also be smaller?
|
||||
# better scores are obviously better too
|
||||
|
||||
# if the new_thumb is on an edge, and the current thumb is not
|
||||
if on_edge(new_obj['box'], frame_shape) and not on_edge(current_thumb['box'], frame_shape):
|
||||
return False
|
||||
|
||||
# if the score is better by more than 5%
|
||||
if new_obj['score'] > current_thumb['score']+.05:
|
||||
return True
|
||||
|
||||
# if the area is 10% larger
|
||||
if new_obj['area'] > current_thumb['area']*1.1:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
class TrackedObject():
|
||||
def __init__(self, camera, camera_config: CameraConfig, frame_cache, obj_data):
|
||||
self.obj_data = obj_data
|
||||
self.camera = camera
|
||||
self.camera_config = camera_config
|
||||
self.frame_cache = frame_cache
|
||||
self.current_zones = []
|
||||
self.entered_zones = set()
|
||||
self.false_positive = True
|
||||
self.top_score = self.computed_score = 0.0
|
||||
self.thumbnail_data = None
|
||||
self.frame = None
|
||||
self.previous = self.to_dict()
|
||||
|
||||
# start the score history
|
||||
self.score_history = [self.obj_data['score']]
|
||||
|
||||
def _is_false_positive(self):
|
||||
# once a true positive, always a true positive
|
||||
if not self.false_positive:
|
||||
return False
|
||||
|
||||
threshold = self.camera_config.objects.filters[self.obj_data['label']].threshold
|
||||
if self.computed_score < threshold:
|
||||
return True
|
||||
return False
|
||||
|
||||
def compute_score(self):
|
||||
scores = self.score_history[:]
|
||||
# pad with zeros if you dont have at least 3 scores
|
||||
if len(scores) < 3:
|
||||
scores += [0.0]*(3 - len(scores))
|
||||
return median(scores)
|
||||
|
||||
def update(self, current_frame_time, obj_data):
|
||||
significant_update = False
|
||||
self.obj_data.update(obj_data)
|
||||
# if the object is not in the current frame, add a 0.0 to the score history
|
||||
if self.obj_data['frame_time'] != current_frame_time:
|
||||
self.score_history.append(0.0)
|
||||
else:
|
||||
self.score_history.append(self.obj_data['score'])
|
||||
# only keep the last 10 scores
|
||||
if len(self.score_history) > 10:
|
||||
self.score_history = self.score_history[-10:]
|
||||
|
||||
# calculate if this is a false positive
|
||||
self.computed_score = self.compute_score()
|
||||
if self.computed_score > self.top_score:
|
||||
self.top_score = self.computed_score
|
||||
self.false_positive = self._is_false_positive()
|
||||
|
||||
if not self.false_positive:
|
||||
# determine if this frame is a better thumbnail
|
||||
if (
|
||||
self.thumbnail_data is None
|
||||
or is_better_thumbnail(self.thumbnail_data, self.obj_data, self.camera_config.frame_shape)
|
||||
):
|
||||
self.thumbnail_data = {
|
||||
'frame_time': self.obj_data['frame_time'],
|
||||
'box': self.obj_data['box'],
|
||||
'area': self.obj_data['area'],
|
||||
'region': self.obj_data['region'],
|
||||
'score': self.obj_data['score']
|
||||
}
|
||||
significant_update = True
|
||||
|
||||
# check zones
|
||||
current_zones = []
|
||||
bottom_center = (self.obj_data['centroid'][0], self.obj_data['box'][3])
|
||||
# check each zone
|
||||
for name, zone in self.camera_config.zones.items():
|
||||
contour = zone.contour
|
||||
# check if the object is in the zone
|
||||
if (cv2.pointPolygonTest(contour, bottom_center, False) >= 0):
|
||||
# if the object passed the filters once, dont apply again
|
||||
if name in self.current_zones or not zone_filtered(self, zone.filters):
|
||||
current_zones.append(name)
|
||||
self.entered_zones.add(name)
|
||||
|
||||
# if the zones changed, signal an update
|
||||
if not self.false_positive and set(self.current_zones) != set(current_zones):
|
||||
significant_update = True
|
||||
|
||||
self.current_zones = current_zones
|
||||
return significant_update
|
||||
|
||||
def to_dict(self, include_thumbnail: bool = False):
|
||||
return {
|
||||
'id': self.obj_data['id'],
|
||||
'camera': self.camera,
|
||||
'frame_time': self.obj_data['frame_time'],
|
||||
'label': self.obj_data['label'],
|
||||
'top_score': self.top_score,
|
||||
'false_positive': self.false_positive,
|
||||
'start_time': self.obj_data['start_time'],
|
||||
'end_time': self.obj_data.get('end_time', None),
|
||||
'score': self.obj_data['score'],
|
||||
'box': self.obj_data['box'],
|
||||
'area': self.obj_data['area'],
|
||||
'region': self.obj_data['region'],
|
||||
'current_zones': self.current_zones.copy(),
|
||||
'entered_zones': list(self.entered_zones).copy(),
|
||||
'thumbnail': base64.b64encode(self.get_thumbnail()).decode('utf-8') if include_thumbnail else None
|
||||
}
|
||||
|
||||
def get_thumbnail(self):
|
||||
if self.thumbnail_data is None or not self.thumbnail_data['frame_time'] in self.frame_cache:
|
||||
ret, jpg = cv2.imencode('.jpg', np.zeros((175,175,3), np.uint8))
|
||||
|
||||
jpg_bytes = self.get_jpg_bytes(timestamp=False, bounding_box=False, crop=True, height=175)
|
||||
|
||||
if jpg_bytes:
|
||||
return jpg_bytes
|
||||
else:
|
||||
ret, jpg = cv2.imencode('.jpg', np.zeros((175,175,3), np.uint8))
|
||||
return jpg.tobytes()
|
||||
|
||||
def get_jpg_bytes(self, timestamp=False, bounding_box=False, crop=False, height=None):
|
||||
if self.thumbnail_data is None:
|
||||
return None
|
||||
|
||||
best_frame = cv2.cvtColor(self.frame_cache[self.thumbnail_data['frame_time']], cv2.COLOR_YUV2BGR_I420)
|
||||
|
||||
if bounding_box:
|
||||
thickness = 2
|
||||
color = COLOR_MAP[self.obj_data['label']]
|
||||
|
||||
# draw the bounding boxes on the frame
|
||||
box = self.thumbnail_data['box']
|
||||
draw_box_with_label(best_frame, box[0], box[1], box[2], box[3], self.obj_data['label'], f"{int(self.thumbnail_data['score']*100)}% {int(self.thumbnail_data['area'])}", thickness=thickness, color=color)
|
||||
|
||||
if crop:
|
||||
box = self.thumbnail_data['box']
|
||||
region = calculate_region(best_frame.shape, box[0], box[1], box[2], box[3], 1.1)
|
||||
best_frame = best_frame[region[1]:region[3], region[0]:region[2]]
|
||||
|
||||
if height:
|
||||
width = int(height*best_frame.shape[1]/best_frame.shape[0])
|
||||
best_frame = cv2.resize(best_frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
||||
|
||||
if timestamp:
|
||||
time_to_show = datetime.datetime.fromtimestamp(self.thumbnail_data['frame_time']).strftime("%m/%d/%Y %H:%M:%S")
|
||||
size = cv2.getTextSize(time_to_show, cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, thickness=2)
|
||||
text_width = size[0][0]
|
||||
desired_size = max(150, 0.33*best_frame.shape[1])
|
||||
font_scale = desired_size/text_width
|
||||
cv2.putText(best_frame, time_to_show, (5, best_frame.shape[0]-7), cv2.FONT_HERSHEY_SIMPLEX,
|
||||
fontScale=font_scale, color=(255, 255, 255), thickness=2)
|
||||
|
||||
ret, jpg = cv2.imencode('.jpg', best_frame)
|
||||
if ret:
|
||||
return jpg.tobytes()
|
||||
else:
|
||||
return None
|
||||
|
||||
def zone_filtered(obj: TrackedObject, object_config):
|
||||
object_name = obj.obj_data['label']
|
||||
|
||||
if object_name in object_config:
|
||||
obj_settings = object_config[object_name]
|
||||
|
||||
# if the min area is larger than the
|
||||
# detected object, don't add it to detected objects
|
||||
if obj_settings.min_area > obj.obj_data['area']:
|
||||
return True
|
||||
|
||||
# if the detected object is larger than the
|
||||
# max area, don't add it to detected objects
|
||||
if obj_settings.max_area < obj.obj_data['area']:
|
||||
return True
|
||||
|
||||
# if the score is lower than the threshold, skip
|
||||
if obj_settings.threshold > obj.computed_score:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# Maintains the state of a camera
|
||||
class CameraState():
|
||||
def __init__(self, name, config, frame_manager):
|
||||
self.name = name
|
||||
self.config = config
|
||||
self.camera_config = config.cameras[name]
|
||||
self.frame_manager = frame_manager
|
||||
self.best_objects: Dict[str, TrackedObject] = {}
|
||||
self.object_counts = defaultdict(lambda: 0)
|
||||
self.tracked_objects: Dict[str, TrackedObject] = {}
|
||||
self.frame_cache = {}
|
||||
self.zone_objects = defaultdict(lambda: [])
|
||||
self._current_frame = np.zeros(self.camera_config.frame_shape_yuv, np.uint8)
|
||||
self.current_frame_lock = threading.Lock()
|
||||
self.current_frame_time = 0.0
|
||||
self.motion_boxes = []
|
||||
self.regions = []
|
||||
self.previous_frame_id = None
|
||||
self.callbacks = defaultdict(lambda: [])
|
||||
|
||||
def get_current_frame(self, draw_options={}):
|
||||
with self.current_frame_lock:
|
||||
frame_copy = np.copy(self._current_frame)
|
||||
frame_time = self.current_frame_time
|
||||
tracked_objects = {k: v.to_dict() for k,v in self.tracked_objects.items()}
|
||||
motion_boxes = self.motion_boxes.copy()
|
||||
regions = self.regions.copy()
|
||||
|
||||
frame_copy = cv2.cvtColor(frame_copy, cv2.COLOR_YUV2BGR_I420)
|
||||
# draw on the frame
|
||||
if draw_options.get('bounding_boxes'):
|
||||
# draw the bounding boxes on the frame
|
||||
for obj in tracked_objects.values():
|
||||
thickness = 2
|
||||
color = COLOR_MAP[obj['label']]
|
||||
|
||||
if obj['frame_time'] != frame_time:
|
||||
thickness = 1
|
||||
color = (255,0,0)
|
||||
|
||||
# draw the bounding boxes on the frame
|
||||
box = obj['box']
|
||||
draw_box_with_label(frame_copy, box[0], box[1], box[2], box[3], obj['label'], f"{int(obj['score']*100)}% {int(obj['area'])}", thickness=thickness, color=color)
|
||||
|
||||
if draw_options.get('regions'):
|
||||
for region in regions:
|
||||
cv2.rectangle(frame_copy, (region[0], region[1]), (region[2], region[3]), (0,255,0), 2)
|
||||
|
||||
if draw_options.get('zones'):
|
||||
for name, zone in self.camera_config.zones.items():
|
||||
thickness = 8 if any([name in obj['current_zones'] for obj in tracked_objects.values()]) else 2
|
||||
cv2.drawContours(frame_copy, [zone.contour], -1, zone.color, thickness)
|
||||
|
||||
if draw_options.get('mask'):
|
||||
mask_overlay = np.where(self.camera_config.motion.mask==[0])
|
||||
frame_copy[mask_overlay] = [0,0,0]
|
||||
|
||||
if draw_options.get('motion_boxes'):
|
||||
for m_box in motion_boxes:
|
||||
cv2.rectangle(frame_copy, (m_box[0], m_box[1]), (m_box[2], m_box[3]), (0,0,255), 2)
|
||||
|
||||
if draw_options.get('timestamp'):
|
||||
time_to_show = datetime.datetime.fromtimestamp(frame_time).strftime("%m/%d/%Y %H:%M:%S")
|
||||
cv2.putText(frame_copy, time_to_show, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, fontScale=.8, color=(255, 255, 255), thickness=2)
|
||||
|
||||
return frame_copy
|
||||
|
||||
def finished(self, obj_id):
|
||||
del self.tracked_objects[obj_id]
|
||||
|
||||
def on(self, event_type: str, callback: Callable[[Dict], None]):
|
||||
self.callbacks[event_type].append(callback)
|
||||
|
||||
def update(self, frame_time, current_detections, motion_boxes, regions):
|
||||
self.current_frame_time = frame_time
|
||||
self.motion_boxes = motion_boxes
|
||||
self.regions = regions
|
||||
# get the new frame
|
||||
frame_id = f"{self.name}{frame_time}"
|
||||
current_frame = self.frame_manager.get(frame_id, self.camera_config.frame_shape_yuv)
|
||||
|
||||
current_ids = current_detections.keys()
|
||||
previous_ids = self.tracked_objects.keys()
|
||||
removed_ids = list(set(previous_ids).difference(current_ids))
|
||||
new_ids = list(set(current_ids).difference(previous_ids))
|
||||
updated_ids = list(set(current_ids).intersection(previous_ids))
|
||||
|
||||
for id in new_ids:
|
||||
new_obj = self.tracked_objects[id] = TrackedObject(self.name, self.camera_config, self.frame_cache, current_detections[id])
|
||||
|
||||
# call event handlers
|
||||
for c in self.callbacks['start']:
|
||||
c(self.name, new_obj, frame_time)
|
||||
|
||||
for id in updated_ids:
|
||||
updated_obj = self.tracked_objects[id]
|
||||
significant_update = updated_obj.update(frame_time, current_detections[id])
|
||||
|
||||
if significant_update:
|
||||
# ensure this frame is stored in the cache
|
||||
if updated_obj.thumbnail_data['frame_time'] == frame_time and frame_time not in self.frame_cache:
|
||||
self.frame_cache[frame_time] = np.copy(current_frame)
|
||||
|
||||
# call event handlers
|
||||
for c in self.callbacks['update']:
|
||||
c(self.name, updated_obj, frame_time)
|
||||
|
||||
for id in removed_ids:
|
||||
# publish events to mqtt
|
||||
removed_obj = self.tracked_objects[id]
|
||||
if not 'end_time' in removed_obj.obj_data:
|
||||
removed_obj.obj_data['end_time'] = frame_time
|
||||
for c in self.callbacks['end']:
|
||||
c(self.name, removed_obj, frame_time)
|
||||
|
||||
# TODO: can i switch to looking this up and only changing when an event ends?
|
||||
# maintain best objects
|
||||
for obj in self.tracked_objects.values():
|
||||
object_type = obj.obj_data['label']
|
||||
# if the object's thumbnail is not from the current frame
|
||||
if obj.false_positive or obj.thumbnail_data['frame_time'] != self.current_frame_time:
|
||||
continue
|
||||
if object_type in self.best_objects:
|
||||
current_best = self.best_objects[object_type]
|
||||
now = datetime.datetime.now().timestamp()
|
||||
# if the object is a higher score than the current best score
|
||||
# or the current object is older than desired, use the new object
|
||||
if (is_better_thumbnail(current_best.thumbnail_data, obj.thumbnail_data, self.camera_config.frame_shape)
|
||||
or (now - current_best.thumbnail_data['frame_time']) > self.camera_config.best_image_timeout):
|
||||
self.best_objects[object_type] = obj
|
||||
for c in self.callbacks['snapshot']:
|
||||
c(self.name, self.best_objects[object_type], frame_time)
|
||||
else:
|
||||
self.best_objects[object_type] = obj
|
||||
for c in self.callbacks['snapshot']:
|
||||
c(self.name, self.best_objects[object_type], frame_time)
|
||||
|
||||
# update overall camera state for each object type
|
||||
obj_counter = Counter()
|
||||
for obj in self.tracked_objects.values():
|
||||
if not obj.false_positive:
|
||||
obj_counter[obj.obj_data['label']] += 1
|
||||
|
||||
# report on detected objects
|
||||
for obj_name, count in obj_counter.items():
|
||||
if count != self.object_counts[obj_name]:
|
||||
self.object_counts[obj_name] = count
|
||||
for c in self.callbacks['object_status']:
|
||||
c(self.name, obj_name, count)
|
||||
|
||||
# expire any objects that are >0 and no longer detected
|
||||
expired_objects = [obj_name for obj_name, count in self.object_counts.items() if count > 0 and not obj_name in obj_counter]
|
||||
for obj_name in expired_objects:
|
||||
self.object_counts[obj_name] = 0
|
||||
for c in self.callbacks['object_status']:
|
||||
c(self.name, obj_name, 0)
|
||||
for c in self.callbacks['snapshot']:
|
||||
c(self.name, self.best_objects[obj_name], frame_time)
|
||||
|
||||
# cleanup thumbnail frame cache
|
||||
current_thumb_frames = set([obj.thumbnail_data['frame_time'] for obj in self.tracked_objects.values() if not obj.false_positive])
|
||||
current_best_frames = set([obj.thumbnail_data['frame_time'] for obj in self.best_objects.values()])
|
||||
thumb_frames_to_delete = [t for t in self.frame_cache.keys() if not t in current_thumb_frames and not t in current_best_frames]
|
||||
for t in thumb_frames_to_delete:
|
||||
del self.frame_cache[t]
|
||||
|
||||
with self.current_frame_lock:
|
||||
self._current_frame = current_frame
|
||||
if not self.previous_frame_id is None:
|
||||
self.frame_manager.delete(self.previous_frame_id)
|
||||
self.previous_frame_id = frame_id
|
||||
|
||||
class TrackedObjectProcessor(threading.Thread):
|
||||
def __init__(self, config: FrigateConfig, client, topic_prefix, tracked_objects_queue, event_queue, event_processed_queue, stop_event):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = "detected_frames_processor"
|
||||
self.config = config
|
||||
self.client = client
|
||||
self.topic_prefix = topic_prefix
|
||||
self.tracked_objects_queue = tracked_objects_queue
|
||||
self.event_queue = event_queue
|
||||
self.event_processed_queue = event_processed_queue
|
||||
self.stop_event = stop_event
|
||||
self.camera_states: Dict[str, CameraState] = {}
|
||||
self.frame_manager = SharedMemoryFrameManager()
|
||||
|
||||
def start(camera, obj: TrackedObject, current_frame_time):
|
||||
self.event_queue.put(('start', camera, obj.to_dict()))
|
||||
|
||||
def update(camera, obj: TrackedObject, current_frame_time):
|
||||
after = obj.to_dict()
|
||||
message = { 'before': obj.previous, 'after': after, 'type': 'new' if obj.previous['false_positive'] else 'update' }
|
||||
self.client.publish(f"{self.topic_prefix}/events", json.dumps(message), retain=False)
|
||||
obj.previous = after
|
||||
|
||||
def end(camera, obj: TrackedObject, current_frame_time):
|
||||
snapshot_config = self.config.cameras[camera].snapshots
|
||||
event_data = obj.to_dict(include_thumbnail=True)
|
||||
event_data['has_snapshot'] = False
|
||||
if not obj.false_positive:
|
||||
message = { 'before': obj.previous, 'after': obj.to_dict(), 'type': 'end' }
|
||||
self.client.publish(f"{self.topic_prefix}/events", json.dumps(message), retain=False)
|
||||
# write snapshot to disk if enabled
|
||||
if snapshot_config.enabled:
|
||||
jpg_bytes = obj.get_jpg_bytes(
|
||||
timestamp=snapshot_config.timestamp,
|
||||
bounding_box=snapshot_config.bounding_box,
|
||||
crop=snapshot_config.crop,
|
||||
height=snapshot_config.height
|
||||
)
|
||||
with open(os.path.join(CLIPS_DIR, f"{camera}-{obj.obj_data['id']}.jpg"), 'wb') as j:
|
||||
j.write(jpg_bytes)
|
||||
event_data['has_snapshot'] = True
|
||||
self.event_queue.put(('end', camera, event_data))
|
||||
|
||||
def snapshot(camera, obj: TrackedObject, current_frame_time):
|
||||
mqtt_config = self.config.cameras[camera].mqtt
|
||||
if mqtt_config.enabled:
|
||||
jpg_bytes = obj.get_jpg_bytes(
|
||||
timestamp=mqtt_config.timestamp,
|
||||
bounding_box=mqtt_config.bounding_box,
|
||||
crop=mqtt_config.crop,
|
||||
height=mqtt_config.height
|
||||
)
|
||||
self.client.publish(f"{self.topic_prefix}/{camera}/{obj.obj_data['label']}/snapshot", jpg_bytes, retain=True)
|
||||
|
||||
def object_status(camera, object_name, status):
|
||||
self.client.publish(f"{self.topic_prefix}/{camera}/{object_name}", status, retain=False)
|
||||
|
||||
for camera in self.config.cameras.keys():
|
||||
camera_state = CameraState(camera, self.config, self.frame_manager)
|
||||
camera_state.on('start', start)
|
||||
camera_state.on('update', update)
|
||||
camera_state.on('end', end)
|
||||
camera_state.on('snapshot', snapshot)
|
||||
camera_state.on('object_status', object_status)
|
||||
self.camera_states[camera] = camera_state
|
||||
|
||||
# {
|
||||
# 'zone_name': {
|
||||
# 'person': {
|
||||
# 'camera_1': 2,
|
||||
# 'camera_2': 1
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
self.zone_data = defaultdict(lambda: defaultdict(lambda: {}))
|
||||
|
||||
def get_best(self, camera, label):
|
||||
# TODO: need a lock here
|
||||
camera_state = self.camera_states[camera]
|
||||
if label in camera_state.best_objects:
|
||||
best_obj = camera_state.best_objects[label]
|
||||
best = best_obj.thumbnail_data.copy()
|
||||
best['frame'] = camera_state.frame_cache.get(best_obj.thumbnail_data['frame_time'])
|
||||
return best
|
||||
else:
|
||||
return {}
|
||||
|
||||
def get_current_frame(self, camera, draw_options={}):
|
||||
return self.camera_states[camera].get_current_frame(draw_options)
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.stop_event.is_set():
|
||||
logger.info(f"Exiting object processor...")
|
||||
break
|
||||
|
||||
try:
|
||||
camera, frame_time, current_tracked_objects, motion_boxes, regions = self.tracked_objects_queue.get(True, 10)
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
camera_state = self.camera_states[camera]
|
||||
|
||||
camera_state.update(frame_time, current_tracked_objects, motion_boxes, regions)
|
||||
|
||||
# update zone counts for each label
|
||||
# for each zone in the current camera
|
||||
for zone in self.config.cameras[camera].zones.keys():
|
||||
# count labels for the camera in the zone
|
||||
obj_counter = Counter()
|
||||
for obj in camera_state.tracked_objects.values():
|
||||
if zone in obj.current_zones and not obj.false_positive:
|
||||
obj_counter[obj.obj_data['label']] += 1
|
||||
|
||||
# update counts and publish status
|
||||
for label in set(list(self.zone_data[zone].keys()) + list(obj_counter.keys())):
|
||||
# if we have previously published a count for this zone/label
|
||||
zone_label = self.zone_data[zone][label]
|
||||
if camera in zone_label:
|
||||
current_count = sum(zone_label.values())
|
||||
zone_label[camera] = obj_counter[label] if label in obj_counter else 0
|
||||
new_count = sum(zone_label.values())
|
||||
if new_count != current_count:
|
||||
self.client.publish(f"{self.topic_prefix}/{zone}/{label}", new_count, retain=False)
|
||||
# if this is a new zone/label combo for this camera
|
||||
else:
|
||||
if label in obj_counter:
|
||||
zone_label[camera] = obj_counter[label]
|
||||
self.client.publish(f"{self.topic_prefix}/{zone}/{label}", obj_counter[label], retain=False)
|
||||
|
||||
# cleanup event finished queue
|
||||
while not self.event_processed_queue.empty():
|
||||
event_id, camera = self.event_processed_queue.get()
|
||||
self.camera_states[camera].finished(event_id)
|
||||
@@ -0,0 +1,149 @@
|
||||
import copy
|
||||
import datetime
|
||||
import itertools
|
||||
import multiprocessing as mp
|
||||
import random
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from scipy.spatial import distance as dist
|
||||
|
||||
from frigate.config import DetectConfig
|
||||
from frigate.util import draw_box_with_label
|
||||
|
||||
|
||||
class ObjectTracker():
|
||||
def __init__(self, config: DetectConfig):
|
||||
self.tracked_objects = {}
|
||||
self.disappeared = {}
|
||||
self.max_disappeared = config.max_disappeared
|
||||
|
||||
def register(self, index, obj):
|
||||
rand_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
||||
id = f"{obj['frame_time']}-{rand_id}"
|
||||
obj['id'] = id
|
||||
obj['start_time'] = obj['frame_time']
|
||||
self.tracked_objects[id] = obj
|
||||
self.disappeared[id] = 0
|
||||
|
||||
def deregister(self, id):
|
||||
del self.tracked_objects[id]
|
||||
del self.disappeared[id]
|
||||
|
||||
def update(self, id, new_obj):
|
||||
self.disappeared[id] = 0
|
||||
self.tracked_objects[id].update(new_obj)
|
||||
|
||||
def match_and_update(self, frame_time, new_objects):
|
||||
# group by name
|
||||
new_object_groups = defaultdict(lambda: [])
|
||||
for obj in new_objects:
|
||||
new_object_groups[obj[0]].append({
|
||||
'label': obj[0],
|
||||
'score': obj[1],
|
||||
'box': obj[2],
|
||||
'area': obj[3],
|
||||
'region': obj[4],
|
||||
'frame_time': frame_time
|
||||
})
|
||||
|
||||
# update any tracked objects with labels that are not
|
||||
# seen in the current objects and deregister if needed
|
||||
for obj in list(self.tracked_objects.values()):
|
||||
if not obj['label'] in new_object_groups:
|
||||
if self.disappeared[obj['id']] >= self.max_disappeared:
|
||||
self.deregister(obj['id'])
|
||||
else:
|
||||
self.disappeared[obj['id']] += 1
|
||||
|
||||
if len(new_objects) == 0:
|
||||
return
|
||||
|
||||
# track objects for each label type
|
||||
for label, group in new_object_groups.items():
|
||||
current_objects = [o for o in self.tracked_objects.values() if o['label'] == label]
|
||||
current_ids = [o['id'] for o in current_objects]
|
||||
current_centroids = np.array([o['centroid'] for o in current_objects])
|
||||
|
||||
# compute centroids of new objects
|
||||
for obj in group:
|
||||
centroid_x = int((obj['box'][0]+obj['box'][2]) / 2.0)
|
||||
centroid_y = int((obj['box'][1]+obj['box'][3]) / 2.0)
|
||||
obj['centroid'] = (centroid_x, centroid_y)
|
||||
|
||||
if len(current_objects) == 0:
|
||||
for index, obj in enumerate(group):
|
||||
self.register(index, obj)
|
||||
return
|
||||
|
||||
new_centroids = np.array([o['centroid'] for o in group])
|
||||
|
||||
# compute the distance between each pair of tracked
|
||||
# centroids and new centroids, respectively -- our
|
||||
# goal will be to match each new centroid to an existing
|
||||
# object centroid
|
||||
D = dist.cdist(current_centroids, new_centroids)
|
||||
|
||||
# in order to perform this matching we must (1) find the
|
||||
# smallest value in each row and then (2) sort the row
|
||||
# indexes based on their minimum values so that the row
|
||||
# with the smallest value is at the *front* of the index
|
||||
# list
|
||||
rows = D.min(axis=1).argsort()
|
||||
|
||||
# next, we perform a similar process on the columns by
|
||||
# finding the smallest value in each column and then
|
||||
# sorting using the previously computed row index list
|
||||
cols = D.argmin(axis=1)[rows]
|
||||
|
||||
# in order to determine if we need to update, register,
|
||||
# or deregister an object we need to keep track of which
|
||||
# of the rows and column indexes we have already examined
|
||||
usedRows = set()
|
||||
usedCols = set()
|
||||
|
||||
# loop over the combination of the (row, column) index
|
||||
# tuples
|
||||
for (row, col) in zip(rows, cols):
|
||||
# if we have already examined either the row or
|
||||
# column value before, ignore it
|
||||
if row in usedRows or col in usedCols:
|
||||
continue
|
||||
|
||||
# otherwise, grab the object ID for the current row,
|
||||
# set its new centroid, and reset the disappeared
|
||||
# counter
|
||||
objectID = current_ids[row]
|
||||
self.update(objectID, group[col])
|
||||
|
||||
# indicate that we have examined each of the row and
|
||||
# column indexes, respectively
|
||||
usedRows.add(row)
|
||||
usedCols.add(col)
|
||||
|
||||
# compute the column index we have NOT yet examined
|
||||
unusedRows = set(range(0, D.shape[0])).difference(usedRows)
|
||||
unusedCols = set(range(0, D.shape[1])).difference(usedCols)
|
||||
|
||||
# in the event that the number of object centroids is
|
||||
# equal or greater than the number of input centroids
|
||||
# we need to check and see if some of these objects have
|
||||
# potentially disappeared
|
||||
if D.shape[0] >= D.shape[1]:
|
||||
for row in unusedRows:
|
||||
id = current_ids[row]
|
||||
|
||||
if self.disappeared[id] >= self.max_disappeared:
|
||||
self.deregister(id)
|
||||
else:
|
||||
self.disappeared[id] += 1
|
||||
# if the number of input centroids is greater
|
||||
# than the number of existing object centroids we need to
|
||||
# register each new input centroid as a trackable object
|
||||
else:
|
||||
for col in unusedCols:
|
||||
self.register(col, group[col])
|
||||
@@ -0,0 +1,208 @@
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
import os
|
||||
import subprocess as sp
|
||||
import sys
|
||||
from unittest import TestCase, main
|
||||
|
||||
import click
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from frigate.config import FRIGATE_CONFIG_SCHEMA, FrigateConfig
|
||||
from frigate.edgetpu import LocalObjectDetector
|
||||
from frigate.motion import MotionDetector
|
||||
from frigate.object_processing import COLOR_MAP, CameraState
|
||||
from frigate.objects import ObjectTracker
|
||||
from frigate.util import (DictFrameManager, EventsPerSecond,
|
||||
SharedMemoryFrameManager, draw_box_with_label)
|
||||
from frigate.video import (capture_frames, process_frames,
|
||||
start_or_restart_ffmpeg)
|
||||
|
||||
logging.basicConfig()
|
||||
logging.root.setLevel(logging.DEBUG)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def get_frame_shape(source):
|
||||
ffprobe_cmd = " ".join([
|
||||
'ffprobe',
|
||||
'-v',
|
||||
'panic',
|
||||
'-show_error',
|
||||
'-show_streams',
|
||||
'-of',
|
||||
'json',
|
||||
'"'+source+'"'
|
||||
])
|
||||
p = sp.Popen(ffprobe_cmd, stdout=sp.PIPE, shell=True)
|
||||
(output, err) = p.communicate()
|
||||
p_status = p.wait()
|
||||
info = json.loads(output)
|
||||
|
||||
video_info = [s for s in info['streams'] if s['codec_type'] == 'video'][0]
|
||||
|
||||
if video_info['height'] != 0 and video_info['width'] != 0:
|
||||
return (video_info['height'], video_info['width'], 3)
|
||||
|
||||
# fallback to using opencv if ffprobe didnt succeed
|
||||
video = cv2.VideoCapture(source)
|
||||
ret, frame = video.read()
|
||||
frame_shape = frame.shape
|
||||
video.release()
|
||||
return frame_shape
|
||||
|
||||
class ProcessClip():
|
||||
def __init__(self, clip_path, frame_shape, config: FrigateConfig):
|
||||
self.clip_path = clip_path
|
||||
self.camera_name = 'camera'
|
||||
self.config = config
|
||||
self.camera_config = self.config.cameras['camera']
|
||||
self.frame_shape = self.camera_config.frame_shape
|
||||
self.ffmpeg_cmd = [c['cmd'] for c in self.camera_config.ffmpeg_cmds if 'detect' in c['roles']][0]
|
||||
self.frame_manager = SharedMemoryFrameManager()
|
||||
self.frame_queue = mp.Queue()
|
||||
self.detected_objects_queue = mp.Queue()
|
||||
self.camera_state = CameraState(self.camera_name, config, self.frame_manager)
|
||||
|
||||
def load_frames(self):
|
||||
fps = EventsPerSecond()
|
||||
skipped_fps = EventsPerSecond()
|
||||
current_frame = mp.Value('d', 0.0)
|
||||
frame_size = self.camera_config.frame_shape_yuv[0] * self.camera_config.frame_shape_yuv[1]
|
||||
ffmpeg_process = start_or_restart_ffmpeg(self.ffmpeg_cmd, logger, sp.DEVNULL, frame_size)
|
||||
capture_frames(ffmpeg_process, self.camera_name, self.camera_config.frame_shape_yuv, self.frame_manager,
|
||||
self.frame_queue, fps, skipped_fps, current_frame)
|
||||
ffmpeg_process.wait()
|
||||
ffmpeg_process.communicate()
|
||||
|
||||
def process_frames(self, objects_to_track=['person'], object_filters={}):
|
||||
mask = np.zeros((self.frame_shape[0], self.frame_shape[1], 1), np.uint8)
|
||||
mask[:] = 255
|
||||
motion_detector = MotionDetector(self.frame_shape, mask, self.camera_config.motion)
|
||||
|
||||
object_detector = LocalObjectDetector(labels='/labelmap.txt')
|
||||
object_tracker = ObjectTracker(self.camera_config.detect)
|
||||
process_info = {
|
||||
'process_fps': mp.Value('d', 0.0),
|
||||
'detection_fps': mp.Value('d', 0.0),
|
||||
'detection_frame': mp.Value('d', 0.0)
|
||||
}
|
||||
stop_event = mp.Event()
|
||||
model_shape = (self.config.model.height, self.config.model.width)
|
||||
|
||||
process_frames(self.camera_name, self.frame_queue, self.frame_shape, model_shape,
|
||||
self.frame_manager, motion_detector, object_detector, object_tracker,
|
||||
self.detected_objects_queue, process_info,
|
||||
objects_to_track, object_filters, mask, stop_event, exit_on_empty=True)
|
||||
|
||||
def top_object(self, debug_path=None):
|
||||
obj_detected = False
|
||||
top_computed_score = 0.0
|
||||
def handle_event(name, obj, frame_time):
|
||||
nonlocal obj_detected
|
||||
nonlocal top_computed_score
|
||||
if obj.computed_score > top_computed_score:
|
||||
top_computed_score = obj.computed_score
|
||||
if not obj.false_positive:
|
||||
obj_detected = True
|
||||
self.camera_state.on('new', handle_event)
|
||||
self.camera_state.on('update', handle_event)
|
||||
|
||||
while(not self.detected_objects_queue.empty()):
|
||||
camera_name, frame_time, current_tracked_objects, motion_boxes, regions = self.detected_objects_queue.get()
|
||||
if not debug_path is None:
|
||||
self.save_debug_frame(debug_path, frame_time, current_tracked_objects.values())
|
||||
|
||||
self.camera_state.update(frame_time, current_tracked_objects, motion_boxes, regions)
|
||||
|
||||
self.frame_manager.delete(self.camera_state.previous_frame_id)
|
||||
|
||||
return {
|
||||
'object_detected': obj_detected,
|
||||
'top_score': top_computed_score
|
||||
}
|
||||
|
||||
def save_debug_frame(self, debug_path, frame_time, tracked_objects):
|
||||
current_frame = cv2.cvtColor(self.frame_manager.get(f"{self.camera_name}{frame_time}", self.camera_config.frame_shape_yuv), cv2.COLOR_YUV2BGR_I420)
|
||||
# draw the bounding boxes on the frame
|
||||
for obj in tracked_objects:
|
||||
thickness = 2
|
||||
color = (0,0,175)
|
||||
|
||||
if obj['frame_time'] != frame_time:
|
||||
thickness = 1
|
||||
color = (255,0,0)
|
||||
else:
|
||||
color = (255,255,0)
|
||||
|
||||
# draw the bounding boxes on the frame
|
||||
box = obj['box']
|
||||
draw_box_with_label(current_frame, box[0], box[1], box[2], box[3], obj['id'], f"{int(obj['score']*100)}% {int(obj['area'])}", thickness=thickness, color=color)
|
||||
# draw the regions on the frame
|
||||
region = obj['region']
|
||||
draw_box_with_label(current_frame, region[0], region[1], region[2], region[3], 'region', "", thickness=1, color=(0,255,0))
|
||||
|
||||
cv2.imwrite(f"{os.path.join(debug_path, os.path.basename(self.clip_path))}.{int(frame_time*1000000)}.jpg", current_frame)
|
||||
|
||||
@click.command()
|
||||
@click.option("-p", "--path", required=True, help="Path to clip or directory to test.")
|
||||
@click.option("-l", "--label", default='person', help="Label name to detect.")
|
||||
@click.option("-t", "--threshold", default=0.85, help="Threshold value for objects.")
|
||||
@click.option("-s", "--scores", default=None, help="File to save csv of top scores")
|
||||
@click.option("--debug-path", default=None, help="Path to output frames for debugging.")
|
||||
def process(path, label, threshold, scores, debug_path):
|
||||
clips = []
|
||||
if os.path.isdir(path):
|
||||
files = os.listdir(path)
|
||||
files.sort()
|
||||
clips = [os.path.join(path, file) for file in files]
|
||||
elif os.path.isfile(path):
|
||||
clips.append(path)
|
||||
|
||||
json_config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'cameras': {
|
||||
'camera': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'path.mp4', 'global_args': '', 'input_args': '', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1920,
|
||||
'width': 1080
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = []
|
||||
for c in clips:
|
||||
logger.info(c)
|
||||
frame_shape = get_frame_shape(c)
|
||||
|
||||
json_config['cameras']['camera']['height'] = frame_shape[0]
|
||||
json_config['cameras']['camera']['width'] = frame_shape[1]
|
||||
json_config['cameras']['camera']['ffmpeg']['inputs'][0]['path'] = c
|
||||
|
||||
config = FrigateConfig(config=FRIGATE_CONFIG_SCHEMA(json_config))
|
||||
|
||||
process_clip = ProcessClip(c, frame_shape, config)
|
||||
process_clip.load_frames()
|
||||
process_clip.process_frames(objects_to_track=[label])
|
||||
|
||||
results.append((c, process_clip.top_object(debug_path)))
|
||||
|
||||
if not scores is None:
|
||||
with open(scores, 'w') as writer:
|
||||
for result in results:
|
||||
writer.write(f"{result[0]},{result[1]['top_score']}\n")
|
||||
|
||||
positive_count = sum(1 for result in results if result[1]['object_detected'])
|
||||
print(f"Objects were detected in {positive_count}/{len(results)}({positive_count/len(results)*100:.2f}%) clip(s).")
|
||||
|
||||
if __name__ == '__main__':
|
||||
process()
|
||||
@@ -0,0 +1,125 @@
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import subprocess as sp
|
||||
import threading
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
import psutil
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SECONDS_IN_DAY = 60 * 60 * 24
|
||||
|
||||
def remove_empty_directories(directory):
|
||||
# list all directories recursively and sort them by path,
|
||||
# longest first
|
||||
paths = sorted(
|
||||
[x[0] for x in os.walk(RECORD_DIR)],
|
||||
key=lambda p: len(str(p)),
|
||||
reverse=True,
|
||||
)
|
||||
for path in paths:
|
||||
# don't delete the parent
|
||||
if path == RECORD_DIR:
|
||||
continue
|
||||
if len(os.listdir(path)) == 0:
|
||||
os.rmdir(path)
|
||||
|
||||
class RecordingMaintainer(threading.Thread):
|
||||
def __init__(self, config: FrigateConfig, stop_event):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = 'recording_maint'
|
||||
self.config = config
|
||||
self.stop_event = stop_event
|
||||
|
||||
def move_files(self):
|
||||
recordings = [d for d in os.listdir(RECORD_DIR) if os.path.isfile(os.path.join(RECORD_DIR, d)) and d.endswith(".mp4")]
|
||||
|
||||
files_in_use = []
|
||||
for process in psutil.process_iter():
|
||||
try:
|
||||
if process.name() != 'ffmpeg':
|
||||
continue
|
||||
flist = process.open_files()
|
||||
if flist:
|
||||
for nt in flist:
|
||||
if nt.path.startswith(RECORD_DIR):
|
||||
files_in_use.append(nt.path.split('/')[-1])
|
||||
except:
|
||||
continue
|
||||
|
||||
for f in recordings:
|
||||
if f in files_in_use:
|
||||
continue
|
||||
|
||||
camera = '-'.join(f.split('-')[:-1])
|
||||
start_time = datetime.datetime.strptime(f.split('-')[-1].split('.')[0], '%Y%m%d%H%M%S')
|
||||
|
||||
ffprobe_cmd = " ".join([
|
||||
'ffprobe',
|
||||
'-v',
|
||||
'error',
|
||||
'-show_entries',
|
||||
'format=duration',
|
||||
'-of',
|
||||
'default=noprint_wrappers=1:nokey=1',
|
||||
f"{os.path.join(RECORD_DIR,f)}"
|
||||
])
|
||||
p = sp.Popen(ffprobe_cmd, stdout=sp.PIPE, shell=True)
|
||||
(output, err) = p.communicate()
|
||||
p_status = p.wait()
|
||||
if p_status == 0:
|
||||
duration = float(output.decode('utf-8').strip())
|
||||
else:
|
||||
logger.info(f"bad file: {f}")
|
||||
os.remove(os.path.join(RECORD_DIR,f))
|
||||
continue
|
||||
|
||||
directory = os.path.join(RECORD_DIR, start_time.strftime('%Y-%m/%d/%H'), camera)
|
||||
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
file_name = f"{start_time.strftime('%M.%S.mp4')}"
|
||||
|
||||
os.rename(os.path.join(RECORD_DIR,f), os.path.join(directory,file_name))
|
||||
|
||||
def expire_files(self):
|
||||
delete_before = {}
|
||||
for name, camera in self.config.cameras.items():
|
||||
delete_before[name] = datetime.datetime.now().timestamp() - SECONDS_IN_DAY*camera.record.retain_days
|
||||
|
||||
for p in Path('/media/frigate/recordings').rglob("*.mp4"):
|
||||
if not p.parent.name in delete_before:
|
||||
continue
|
||||
if p.stat().st_mtime < delete_before[p.parent.name]:
|
||||
p.unlink(missing_ok=True)
|
||||
|
||||
def run(self):
|
||||
counter = 0
|
||||
self.expire_files()
|
||||
while(True):
|
||||
if self.stop_event.is_set():
|
||||
logger.info(f"Exiting recording maintenance...")
|
||||
break
|
||||
|
||||
# only expire events every 10 minutes, but check for new files every 10 seconds
|
||||
time.sleep(10)
|
||||
counter = counter + 1
|
||||
if counter > 60:
|
||||
self.expire_files()
|
||||
remove_empty_directories(RECORD_DIR)
|
||||
counter = 0
|
||||
|
||||
self.move_files()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.version import VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def stats_init(camera_metrics, detectors):
|
||||
stats_tracking = {
|
||||
'camera_metrics': camera_metrics,
|
||||
'detectors': detectors,
|
||||
'started': int(time.time())
|
||||
}
|
||||
return stats_tracking
|
||||
|
||||
def stats_snapshot(stats_tracking):
|
||||
camera_metrics = stats_tracking['camera_metrics']
|
||||
stats = {}
|
||||
|
||||
total_detection_fps = 0
|
||||
|
||||
for name, camera_stats in camera_metrics.items():
|
||||
total_detection_fps += camera_stats['detection_fps'].value
|
||||
stats[name] = {
|
||||
'camera_fps': round(camera_stats['camera_fps'].value, 2),
|
||||
'process_fps': round(camera_stats['process_fps'].value, 2),
|
||||
'skipped_fps': round(camera_stats['skipped_fps'].value, 2),
|
||||
'detection_fps': round(camera_stats['detection_fps'].value, 2),
|
||||
'pid': camera_stats['process'].pid,
|
||||
'capture_pid': camera_stats['capture_process'].pid
|
||||
}
|
||||
|
||||
stats['detectors'] = {}
|
||||
for name, detector in stats_tracking["detectors"].items():
|
||||
stats['detectors'][name] = {
|
||||
'inference_speed': round(detector.avg_inference_speed.value * 1000, 2),
|
||||
'detection_start': detector.detection_start.value,
|
||||
'pid': detector.detect_process.pid
|
||||
}
|
||||
stats['detection_fps'] = round(total_detection_fps, 2)
|
||||
|
||||
stats['service'] = {
|
||||
'uptime': (int(time.time()) - stats_tracking['started']),
|
||||
'version': VERSION
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
class StatsEmitter(threading.Thread):
|
||||
def __init__(self, config: FrigateConfig, stats_tracking, mqtt_client, topic_prefix, stop_event):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = 'frigate_stats_emitter'
|
||||
self.config = config
|
||||
self.stats_tracking = stats_tracking
|
||||
self.mqtt_client = mqtt_client
|
||||
self.topic_prefix = topic_prefix
|
||||
self.stop_event = stop_event
|
||||
|
||||
def run(self):
|
||||
time.sleep(10)
|
||||
while True:
|
||||
if self.stop_event.is_set():
|
||||
logger.info(f"Exiting watchdog...")
|
||||
break
|
||||
stats = stats_snapshot(self.stats_tracking)
|
||||
self.mqtt_client.publish(f"{self.topic_prefix}/stats", json.dumps(stats), retain=False)
|
||||
time.sleep(self.config.mqtt.stats_interval)
|
||||
@@ -0,0 +1,342 @@
|
||||
import json
|
||||
from unittest import TestCase, main
|
||||
import voluptuous as vol
|
||||
from frigate.config import FRIGATE_CONFIG_SCHEMA, FrigateConfig
|
||||
|
||||
class TestConfig(TestCase):
|
||||
def setUp(self):
|
||||
self.minimal = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920
|
||||
}
|
||||
}
|
||||
}
|
||||
def test_empty(self):
|
||||
FRIGATE_CONFIG_SCHEMA({})
|
||||
|
||||
def test_minimal(self):
|
||||
FRIGATE_CONFIG_SCHEMA(self.minimal)
|
||||
|
||||
def test_config_class(self):
|
||||
FrigateConfig(config=self.minimal)
|
||||
|
||||
def test_inherit_tracked_objects(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'objects': {
|
||||
'track': ['person', 'dog']
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920
|
||||
}
|
||||
}
|
||||
}
|
||||
frigate_config = FrigateConfig(config=config)
|
||||
assert('dog' in frigate_config.cameras['back'].objects.track)
|
||||
|
||||
def test_override_tracked_objects(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'objects': {
|
||||
'track': ['person', 'dog']
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920,
|
||||
'objects': {
|
||||
'track': ['cat']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
frigate_config = FrigateConfig(config=config)
|
||||
assert('cat' in frigate_config.cameras['back'].objects.track)
|
||||
|
||||
def test_default_object_filters(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'objects': {
|
||||
'track': ['person', 'dog']
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920
|
||||
}
|
||||
}
|
||||
}
|
||||
frigate_config = FrigateConfig(config=config)
|
||||
assert('dog' in frigate_config.cameras['back'].objects.filters)
|
||||
|
||||
def test_inherit_object_filters(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'objects': {
|
||||
'track': ['person', 'dog'],
|
||||
'filters': {
|
||||
'dog': {
|
||||
'threshold': 0.7
|
||||
}
|
||||
}
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920
|
||||
}
|
||||
}
|
||||
}
|
||||
frigate_config = FrigateConfig(config=config)
|
||||
assert('dog' in frigate_config.cameras['back'].objects.filters)
|
||||
assert(frigate_config.cameras['back'].objects.filters['dog'].threshold == 0.7)
|
||||
|
||||
def test_override_object_filters(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920,
|
||||
'objects': {
|
||||
'track': ['person', 'dog'],
|
||||
'filters': {
|
||||
'dog': {
|
||||
'threshold': 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
frigate_config = FrigateConfig(config=config)
|
||||
assert('dog' in frigate_config.cameras['back'].objects.filters)
|
||||
assert(frigate_config.cameras['back'].objects.filters['dog'].threshold == 0.7)
|
||||
|
||||
def test_ffmpeg_params(self):
|
||||
config = {
|
||||
'ffmpeg': {
|
||||
'input_args': ['-re']
|
||||
},
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920,
|
||||
'objects': {
|
||||
'track': ['person', 'dog'],
|
||||
'filters': {
|
||||
'dog': {
|
||||
'threshold': 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
frigate_config = FrigateConfig(config=config)
|
||||
assert('-re' in frigate_config.cameras['back'].ffmpeg_cmds[0]['cmd'])
|
||||
|
||||
def test_inherit_clips_retention(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'clips': {
|
||||
'retain': {
|
||||
'default': 20,
|
||||
'objects': {
|
||||
'person': 30
|
||||
}
|
||||
}
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920
|
||||
}
|
||||
}
|
||||
}
|
||||
frigate_config = FrigateConfig(config=config)
|
||||
assert(frigate_config.cameras['back'].clips.retain.objects['person'] == 30)
|
||||
|
||||
def test_roles_listed_twice_throws_error(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'clips': {
|
||||
'retain': {
|
||||
'default': 20,
|
||||
'objects': {
|
||||
'person': 30
|
||||
}
|
||||
}
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] },
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video2', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920
|
||||
}
|
||||
}
|
||||
}
|
||||
self.assertRaises(vol.MultipleInvalid, lambda: FrigateConfig(config=config))
|
||||
|
||||
def test_zone_matching_camera_name_throws_error(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'clips': {
|
||||
'retain': {
|
||||
'default': 20,
|
||||
'objects': {
|
||||
'person': 30
|
||||
}
|
||||
}
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920,
|
||||
'zones': {
|
||||
'back': {
|
||||
'coordinates': '1,1,1,1,1,1'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.assertRaises(vol.MultipleInvalid, lambda: FrigateConfig(config=config))
|
||||
|
||||
def test_clips_should_default_to_global_objects(self):
|
||||
config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'clips': {
|
||||
'retain': {
|
||||
'default': 20,
|
||||
'objects': {
|
||||
'person': 30
|
||||
}
|
||||
}
|
||||
},
|
||||
'objects': {
|
||||
'track': ['person', 'dog']
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920,
|
||||
'clips': {
|
||||
'enabled': True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
config = FrigateConfig(config=config)
|
||||
assert(config.cameras['back'].clips.objects is None)
|
||||
|
||||
def test_role_assigned_but_not_enabled(self):
|
||||
json_config = {
|
||||
'mqtt': {
|
||||
'host': 'mqtt'
|
||||
},
|
||||
'cameras': {
|
||||
'back': {
|
||||
'ffmpeg': {
|
||||
'inputs': [
|
||||
{ 'path': 'rtsp://10.0.0.1:554/video', 'roles': ['detect', 'rtmp'] },
|
||||
{ 'path': 'rtsp://10.0.0.1:554/record', 'roles': ['record'] }
|
||||
]
|
||||
},
|
||||
'height': 1080,
|
||||
'width': 1920
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config = FrigateConfig(config=json_config)
|
||||
ffmpeg_cmds = config.cameras['back'].ffmpeg_cmds
|
||||
assert(len(ffmpeg_cmds) == 1)
|
||||
assert(not 'clips' in ffmpeg_cmds[0]['roles'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(verbosity=2)
|
||||