feat: virtcam
This commit is contained in:
parent
82c0dde162
commit
5ab5309c2b
|
@ -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")
|
||||
-- }}}
|
||||
|
|
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)
|
||||
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
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