feat: virtcam
This commit is contained in:
parent
82c0dde162
commit
5ab5309c2b
|
@ -774,7 +774,8 @@ awful.rules.rules = {
|
||||||
"Wpa_gui",
|
"Wpa_gui",
|
||||||
"veromix",
|
"veromix",
|
||||||
"st-256color",
|
"st-256color",
|
||||||
"xtightvncviewer" },
|
"xtightvncviewer"
|
||||||
|
},
|
||||||
|
|
||||||
-- Note that the name property shown in xprop might be set slightly after creation of the client
|
-- Note that the name property shown in xprop might be set slightly after creation of the client
|
||||||
-- and the name shown there might not match defined rules here.
|
-- and the name shown there might not match defined rules here.
|
||||||
|
@ -883,4 +884,5 @@ awful.spawn.with_shell("bluetoothctl power on")
|
||||||
awful.spawn.with_shell("echo DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS > ~/.cron.env")
|
awful.spawn.with_shell("echo DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS > ~/.cron.env")
|
||||||
awful.spawn.with_shell("echo DISPLAY=$DISPLAY >> ~/.cron.env")
|
awful.spawn.with_shell("echo DISPLAY=$DISPLAY >> ~/.cron.env")
|
||||||
awful.spawn.with_shell("grep -F 0 /sys/class/power_supply/ACAD/online && sudo light -S 30")
|
awful.spawn.with_shell("grep -F 0 /sys/class/power_supply/ACAD/online && sudo light -S 30")
|
||||||
|
awful.spawn.with_shell("cd ~/dotfiles/gui/virtcam && lua virtcam.lua")
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
82
gui/virtcam/httpserver.lua
Normal file
82
gui/virtcam/httpserver.lua
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#!/usr/bin/env lua
|
||||||
|
--[[
|
||||||
|
requirement: sudo luarocks install http json-lua
|
||||||
|
A simple HTTP server
|
||||||
|
If a request is not a HEAD method, then reply with "Hello world!"
|
||||||
|
Usage: lua examples/server_hello.lua [<port>]
|
||||||
|
]]
|
||||||
|
|
||||||
|
local http_server = require "http.server"
|
||||||
|
local http_headers = require "http.headers"
|
||||||
|
local json = require "JSON"
|
||||||
|
|
||||||
|
function NewHttpServer(host, port, handlers)
|
||||||
|
local function reply(myserver, stream) -- luacheck: ignore 212
|
||||||
|
-- Read in headers
|
||||||
|
local req_headers = assert(stream:get_headers())
|
||||||
|
local req_method = req_headers:get ":method"
|
||||||
|
local req_path = req_headers:get ":path"
|
||||||
|
|
||||||
|
-- Log request to stdout
|
||||||
|
assert(io.stdout:write(string.format('[%s] "%s %s HTTP/%g" "%s" "%s"\n',
|
||||||
|
os.date("%d/%b/%Y:%H:%M:%S %z"),
|
||||||
|
req_method or "",
|
||||||
|
req_headers:get(":path") or "",
|
||||||
|
stream.connection.version,
|
||||||
|
req_headers:get("referer") or "-",
|
||||||
|
req_headers:get("user-agent") or "-"
|
||||||
|
)))
|
||||||
|
|
||||||
|
-- Handle request
|
||||||
|
local status = 404
|
||||||
|
local res_body = { message = "Resource Not Found" }
|
||||||
|
local handle_key = string.format("%s %s", req_method, req_path)
|
||||||
|
local handler = handlers[handle_key]
|
||||||
|
if handler then
|
||||||
|
local req_body = nil
|
||||||
|
if req_method == "POST" or req_method == "PUT" then
|
||||||
|
req_body = json:decode(stream:get_body_as_string(0))
|
||||||
|
end
|
||||||
|
status, res_body = handler(req_body, req_headers)
|
||||||
|
status = status or 200
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build response headers
|
||||||
|
local res_headers = http_headers.new()
|
||||||
|
res_headers:append(":status", tostring(status))
|
||||||
|
res_headers:append("content-type", "application/json")
|
||||||
|
res_headers:append("Access-Control-Allow-Origin", "*")
|
||||||
|
-- Send headers to client; end the stream immediately if this was a HEAD request
|
||||||
|
assert(stream:write_headers(res_headers, req_method == "HEAD"))
|
||||||
|
if req_method ~= "HEAD" then
|
||||||
|
-- Send body, ending the stream
|
||||||
|
assert(stream:write_chunk(json:encode(res_body), true))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local myserver = assert(http_server.listen {
|
||||||
|
host = host or "localhost";
|
||||||
|
port = port;
|
||||||
|
onstream = reply;
|
||||||
|
onerror = function(myserver, context, op, err, errno) -- luacheck: ignore 212
|
||||||
|
local msg = op .. " on " .. tostring(context) .. " failed"
|
||||||
|
if err then
|
||||||
|
msg = msg .. ": " .. tostring(err)
|
||||||
|
end
|
||||||
|
assert(io.stderr:write(msg, "\n"))
|
||||||
|
end;
|
||||||
|
})
|
||||||
|
assert(myserver:listen())
|
||||||
|
do
|
||||||
|
local bound_port = select(3, myserver:localname())
|
||||||
|
assert(io.stderr:write(string.format("Now listening on port %d\n", bound_port)))
|
||||||
|
end
|
||||||
|
assert(myserver:loop())
|
||||||
|
return myserver
|
||||||
|
end
|
||||||
|
|
||||||
|
-- NewHttpServer("127.0.0.1", 10200, {
|
||||||
|
-- ["POST /test"] = function(body, _)
|
||||||
|
-- return 200, { message = body }
|
||||||
|
-- end,
|
||||||
|
-- })
|
|
@ -31,14 +31,20 @@ while true; do
|
||||||
PID=$(actualwebcam)
|
PID=$(actualwebcam)
|
||||||
echo piping actual webcam, press ENTER to start recording
|
echo piping actual webcam, press ENTER to start recording
|
||||||
read C
|
read C
|
||||||
echo recording, press [q] to replay
|
echo recording for 3 seconds
|
||||||
ffmpeg -y -i /dev/video22 ~/Video/seq.mp4 >/dev/null 2>&1
|
ffmpeg -y -i /dev/video22 ~/Video/seq.mp4 >/dev/null 2>&1 </dev/null &
|
||||||
|
RECORD_PID=$!
|
||||||
|
sleep 3
|
||||||
|
kill -s INT $RECORD_PID
|
||||||
echo recording stopped
|
echo recording stopped
|
||||||
|
wait "$RECORD_PID" || true
|
||||||
ffmpeg -y -i ~/Video/seq.mp4 -vf reverse ~/Video/reversed.mp4 >/dev/null 2>&1
|
ffmpeg -y -i ~/Video/seq.mp4 -vf reverse ~/Video/reversed.mp4 >/dev/null 2>&1
|
||||||
echo reversed clip created
|
echo reversed clip created
|
||||||
ffmpeg -y -i ~/Video/seq.mp4 -i ~/Video/reversed.mp4 -filter_complex "[0:v] [1:v] concat=n=2:v=1 [v]" -map "[v]" ~/Video/loop.mp4 >/dev/null 2>&1
|
ffmpeg -y -i ~/Video/seq.mp4 -i ~/Video/reversed.mp4 -filter_complex "[0:v] [1:v] concat=n=2:v=1 [v]" -map "[v]" ~/Video/loop.mp4 >/dev/null 2>&1
|
||||||
echo looping clip created
|
echo looping clip created
|
||||||
|
pactl set-source-mute @DEFAULT_SOURCE@ 1
|
||||||
kill -s INT $PID
|
kill -s INT $PID
|
||||||
echo webcam stopped, enter looping mode, press [q] to resume webcam
|
echo webcam stopped, enter looping mode, press [q] to resume webcam
|
||||||
ffmpeg -re -stream_loop -1 -i ~/Video/loop.mp4 -f v4l2 -vcodec rawvideo -pix_fmt yuyv422 -framerate 30 /dev/video21 >/dev/null 2>&1
|
ffmpeg -re -stream_loop -1 -i ~/Video/loop.mp4 -f v4l2 -vcodec rawvideo -pix_fmt yuyv422 -framerate 30 /dev/video21 >/dev/null 2>&1
|
||||||
|
pactl set-source-mute @DEFAULT_SOURCE@ 0
|
||||||
done
|
done
|
113
gui/virtcam/virtcam.lua
Normal file
113
gui/virtcam/virtcam.lua
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
local httpserver = require "httpserver"
|
||||||
|
|
||||||
|
local function run(cmd)
|
||||||
|
print(cmd)
|
||||||
|
local proc = io.popen(cmd .. " >/tmp/virtcam.log 2>&1")
|
||||||
|
assert(proc)
|
||||||
|
assert(proc:close())
|
||||||
|
end
|
||||||
|
|
||||||
|
run("sudo modprobe v4l2loopback -r -f")
|
||||||
|
run("sudo modprobe v4l2loopback video_nr=21 card_label=virtcam exclusive_caps=1")
|
||||||
|
|
||||||
|
Ffmpeg = { cmd = "", proc = nil, name = "", started = false }
|
||||||
|
|
||||||
|
function Ffmpeg:new(o)
|
||||||
|
o = o or {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
function Ffmpeg:start()
|
||||||
|
assert(self.proc == nil)
|
||||||
|
self.proc = io.popen(self.cmd .. " >/tmp/virtcam.log 2>&1", "w")
|
||||||
|
assert(self.proc)
|
||||||
|
print(self.name .. " started")
|
||||||
|
self.started = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function Ffmpeg:stop()
|
||||||
|
assert(self.proc)
|
||||||
|
self.proc:write("q")
|
||||||
|
self.proc:flush()
|
||||||
|
assert(self.proc:close())
|
||||||
|
self.proc = nil
|
||||||
|
print(self.name .. " stopped")
|
||||||
|
self.started = false
|
||||||
|
end
|
||||||
|
|
||||||
|
Actualcam = Ffmpeg:new {
|
||||||
|
name = "actualcam",
|
||||||
|
cmd = "ffmpeg -i /dev/video0 -vcodec copy -f v4l2 /dev/video21",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Recorder = Ffmpeg:new {
|
||||||
|
name = "recorder",
|
||||||
|
cmd = "ffmpeg -y -i /dev/video21 ~/Video/seq.mp4",
|
||||||
|
}
|
||||||
|
|
||||||
|
Looper = Ffmpeg:new {
|
||||||
|
name = "looper",
|
||||||
|
cmd = "ffmpeg -re -stream_loop -1 -i ~/Video/loop.mp4 -f v4l2 -vcodec rawvideo -pix_fmt yuyv422 -framerate 30 /dev/video21 ",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local all = { Actualcam, Recorder, Looper }
|
||||||
|
|
||||||
|
local function run_only(the_one)
|
||||||
|
for _, fp in ipairs(all) do
|
||||||
|
if fp ~= the_one and fp.started then
|
||||||
|
fp:stop()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if the_one and not the_one.started then
|
||||||
|
print("try to start")
|
||||||
|
the_one:start()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local stage = "stopped"
|
||||||
|
|
||||||
|
NewHttpServer("127.0.0.1", 10200, {
|
||||||
|
["POST /prepare"] = function(_, _)
|
||||||
|
if stage == "stopped" then
|
||||||
|
run("pactl set-source-mute @DEFAULT_SOURCE@ 1")
|
||||||
|
run_only(Actualcam)
|
||||||
|
stage = "actual"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
["POST /actual"] = function(_, _)
|
||||||
|
if stage ~= "actual" then
|
||||||
|
run("pactl set-source-mute @DEFAULT_SOURCE@ 0")
|
||||||
|
run_only(Actualcam)
|
||||||
|
stage = "actual"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
["POST /fake"] = function(_, _)
|
||||||
|
if stage ~= "fake" then
|
||||||
|
run("pactl set-source-mute @DEFAULT_SOURCE@ 1")
|
||||||
|
run_only(Actualcam)
|
||||||
|
Recorder:start()
|
||||||
|
run("sleep 3")
|
||||||
|
Recorder:stop()
|
||||||
|
run("ffmpeg -y -i ~/Video/seq.mp4 -vf reverse ~/Video/reversed.mp4")
|
||||||
|
run("ffmpeg -y -i ~/Video/seq.mp4 -i ~/Video/reversed.mp4 -filter_complex '[0:v] [1:v] concat=n=2:v=1 [v]' -map '[v]' ~/Video/loop.mp4")
|
||||||
|
run_only(Looper)
|
||||||
|
stage = "fake"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
["POST /stop"] = function(_, _)
|
||||||
|
if stage ~= "stopped" then
|
||||||
|
run_only(nil)
|
||||||
|
stage = "stopped"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
["POST /toggle-mute"] = function(_, _)
|
||||||
|
run("pactl set-source-mute @DEFAULT_SOURCE@ toggle")
|
||||||
|
end,
|
||||||
|
["POST /mute"] = function(_, _)
|
||||||
|
run("pactl set-source-mute @DEFAULT_SOURCE@ 1")
|
||||||
|
end,
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user