feat: virtcam

This commit is contained in:
Klesh Wong 2022-07-25 11:48:34 +08:00
parent 82c0dde162
commit 5ab5309c2b
4 changed files with 206 additions and 3 deletions

View File

@ -774,7 +774,8 @@ awful.rules.rules = {
"Wpa_gui",
"veromix",
"st-256color",
"xtightvncviewer" },
"xtightvncviewer"
},
-- 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.
@ -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 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("cd ~/dotfiles/gui/virtcam && lua virtcam.lua")
-- }}}

View 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,
-- })

View File

@ -31,14 +31,20 @@ while true; do
PID=$(actualwebcam)
echo piping actual webcam, press ENTER to start recording
read C
echo recording, press [q] to replay
ffmpeg -y -i /dev/video22 ~/Video/seq.mp4 >/dev/null 2>&1
echo recording for 3 seconds
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
wait "$RECORD_PID" || true
ffmpeg -y -i ~/Video/seq.mp4 -vf reverse ~/Video/reversed.mp4 >/dev/null 2>&1
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
echo looping clip created
pactl set-source-mute @DEFAULT_SOURCE@ 1
kill -s INT $PID
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
pactl set-source-mute @DEFAULT_SOURCE@ 0
done

113
gui/virtcam/virtcam.lua Normal file
View 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,
})