Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kseniia fix #3

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ __pycache__/
/media/
/videos/
/src/app.db
/src/data
54 changes: 54 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
FROM ultravideo/kvazaar

RUN echo 'tzdata tzdata/Areas select Europe' | debconf-set-selections
RUN echo 'tzdata tzdata/Zones/Europe select Paris' | debconf-set-selections
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata && \
apt-get -y install build-essential zlib1g-dev \
libncurses5-dev libgdbm-dev libnss3-dev sqlite3 libsqlite3-dev \
libssl-dev libreadline-dev libffi-dev curl software-properties-common && \
rm -rf /var/lib/apt/lists/*

# RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
# RUN add-apt-repository ppa:deadsnakes/ppa
RUN mkdir /tmp/python-build && \
cd /tmp/python-build && \
curl -fsSL https://www.python.org/ftp/python/3.9.7/Python-3.9.7.tgz | tar zx --strip-components=1
RUN cd /tmp/python-build && \
./configure --enable-loadable-sqlite-extensions --enable-optimizations && \
make -j "$(nproc)"
RUN cd /tmp/python-build && \
make altinstall
RUN ln -s /usr/local/bin/python3.9 /usr/bin/python3.9
RUN python3.9 --version

RUN apt-get update \
&& apt-get install build-essential git curl ffmpeg python3-pip \
zlib1g-dev libfreetype6-dev libjpeg62-dev libpng-dev libmad0-dev libfaad-dev libogg-dev libvorbis-dev libtheora-dev liba52-0.7.4-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavdevice-dev libxv-dev x11proto-video-dev libgl1-mesa-dev x11proto-gl-dev libxvidcore-dev libssl-dev libjack-dev libasound2-dev libpulse-dev libsdl2-dev dvb-apps mesa-utils \
libpq-dev libsm6 libgl1 libxext6 -y

# RUN ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts

WORKDIR /gpac-build
RUN git clone https://github.com/gpac/gpac.git
WORKDIR /gpac-build/gpac
RUN ./configure --static-bin
RUN make
RUN make install

WORKDIR /app

RUN python3.9 -m venv /venv
ENV PATH=/venv/bin:$PATH

COPY requirements.txt /app/
RUN pip install --upgrade setuptools pip
RUN pip install scikit-build
RUN pip install --upgrade cmake
RUN python --version
RUN pip --version
RUN pip install -r requirements.txt

COPY . .

ENTRYPOINT [ "/app/entrypoint.sh" ]
72 changes: 72 additions & 0 deletions encode_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import os
from datetime import datetime, timedelta
from pathlib import Path
from subprocess import Popen, PIPE, DEVNULL, check_call
from tempfile import mkstemp

import numpy as np

duration = 10.0

ffmpeg_cmd = [
'ffmpeg', '-ss', '3.19912',
'-i', '/event_medias/output/output_2023-05-31T20:13:20.036643.mp4',
'-i', '/event_medias/output/output_2023-05-31T20:13:30.041467.mp4',
'-filter_complex', '[0:v][1:v]concat=n=2[outv]',
'-map', '[outv]', '-t', f'{duration}',
'-f', 'rawvideo',
'-pix_fmt', 'yuv420p', '-'
]

ffmpeg_handle = Popen(
ffmpeg_cmd,
stdout=PIPE,
# stderr=DEVNULL
)

resolution = "480x360"
roi_file = None
out_file = "/event_medias/kavazaar_test"
out_path = "/event_medias/kavazaar_test.mp4"


encode_command = [
"kvazaar",
"--input-fps", "30",
"-i", "-",
"--input-res", resolution,
"--preset", "ultrafast",
"--qp", "27" if roi_file is not None else "27",
"-o", str(out_file),
]

if roi_file is not None:
encode_command.extend([
"--roi", roi_file,
])

kvazaar_handle = Popen(
encode_command,
stdin=ffmpeg_handle.stdout,
stderr=PIPE,
)

frames_encoded = 0
total_frames = duration * 30
for line in kvazaar_handle.stderr:
a = line.decode()
if a.startswith("POC"):
frames_encoded += 1

print(a, end="")

kvazaar_handle.wait()
print(f"Kvazaar done {str(out_file)}")

check_call(
[
"MP4Box",
"-add", str(out_file),
"-new", str(out_path)
]
)
11 changes: 11 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

flask --app src db upgrade

if [[ "$DISABLE_READ_AND_FEED" == "1" ]]; then
rq worker & flask --app src run -h 0.0.0.0 -p 7655
else
python read_and_feed.py "$CAMERA_NAME" "$STREAM" & rq worker & flask --app src run -h 0.0.0.0 -p 7655
fi


168 changes: 129 additions & 39 deletions read_and_feed.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from sys import platform
import os
import sys
import cv2
import subprocess
from collections import deque
from datetime import datetime, timedelta
from pathlib import Path
Expand All @@ -10,8 +13,71 @@
load_dotenv()


def main(camera_name="output"):
def save_10_seconds_webcam(output_path, device):
# Open the webcam
cap = cv2.VideoCapture(0) # Use 0 for the default camera

# Check if the camera is opened correctly
if not cap.isOpened():
print("Failed to open the webcam")
return

# Get the frames per second (FPS) of the camera
resolution = os.environ["RESOLUTION"] or "1920x1080"
width, height = [int(x) for x in resolution.split("x")]
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
fps = cap.get(cv2.CAP_PROP_FPS)
# print(f'fps: {fps}')
# cap.set(cv2.CAP_PROP_FPS, 30)

# Calculate the number of frames to capture for 10 seconds
num_frames = int(fps * 10)

# Initialize variables
frame_counter = 0
video_frames = []

while cap.isOpened() and frame_counter < num_frames:
# Read a frame from the camera
ret, frame = cap.read()

if not ret:
break
frame = cv2.flip(frame, 1)

# Add the frame to the list
video_frames.append(frame)

# Increment the frame counter
frame_counter += 1

# Release the camera
cap.release()

# Check if enough frames were captured
if frame_counter < num_frames:
print("Not enough frames available from the webcam")
return

# Get the width and height of the frames
height, width, _ = video_frames[0].shape

# Create a VideoWriter object to save the frames as an MP4 file
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))

# Write the frames to the MP4 file
for frame in video_frames:
out.write(frame)

# Release the VideoWriter
out.release()


def main(camera_name="output", stream=""):
media_dir = (Path(__file__) / ".." / "media" / camera_name).resolve()
media_dir = Path(os.environ.get("MEDIA_DIR", media_dir))
media_dir.mkdir(exist_ok=True, parents=True)
for file in media_dir.glob("*"):
file.unlink()
Expand All @@ -29,57 +95,81 @@ def main(camera_name="output"):
# '-pix_fmt', "yuv420p",
# '-'
# ]
a = Popen(
[
'ffmpeg',
'-f', 'lavfi',
'-i', f'color=c=black:size={resolution}',
'-vf', rf"drawtext=fontsize=100:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:text=%{{n}}",
'-f', "rawvideo",
'-pix_fmt', "yuv420p",
'-'

],
stdout=PIPE,
stderr=DEVNULL
)
print(f"Stream {camera_name}: {stream}")
if stream:
a = None
else:
a = Popen(
[
"ffmpeg",
"-f",
"lavfi",
"-i",
f"color=c=black:size={resolution}",
"-vf",
rf"drawtext=fontsize=100:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:text=%{{n}}",
"-f",
"rawvideo",
"-pix_fmt",
"yuv420p",
"-",
],
stdout=PIPE,
stderr=DEVNULL,
)

i = 0
width, height = [int(x) for x in resolution.split("x")]
segments = deque()
while True:
segment_start_time = datetime.utcnow()
output_file = media_dir / f'output_{segment_start_time.isoformat()}.mp4'
b = Popen(
[
'ffmpeg',
'-s:v', resolution,
'-f', 'rawvideo',
'-pix_fmt', 'yuv420p',
'-r', '30',
'-i', 'pipe:.yuv',
"-c:v", "libx264",
str(output_file),
"-y"
],
stdin=PIPE,
stderr=DEVNULL
)
for _ in range(30 * 10):
f = a.stdout.read(int(width * height * 1.5))
b.stdin.write(f)
b.stdin.close()
output_file = media_dir / f"output_{segment_start_time.isoformat()}.mp4"
if a:
b = Popen(
[
"ffmpeg",
"-s:v",
resolution,
"-f",
"rawvideo",
"-pix_fmt",
"yuv420p",
"-r",
"30",
"-i",
"pipe:.yuv",
"-c:v",
"libx264",
str(output_file),
"-y",
],
stdin=PIPE,
stderr=DEVNULL,
)
for _ in range(30 * 10):
f = a.stdout.read(int(width * height * 1.5))
b.stdin.write(f)
b.stdin.close()
else:
save_10_seconds_webcam(
str(output_file), stream if platform != "darwin" else 0
)

i += 1
segments.append(output_file)
if len(segments) > 60:
segment_to_remove = segments.popleft()
segment_to_remove.unlink()
segment_end_time = datetime.utcnow()
# This is not needed if the input is from actual camera
sleep(10 - (segment_end_time - segment_start_time).total_seconds())
a.stdout.close()
# if (10 - (segment_end_time - segment_start_time).total_seconds()) > 0:
# sleep(10 - (segment_end_time - segment_start_time).total_seconds())

if a:
a.stdout.close()


if __name__ == '__main__':
if __name__ == "__main__":
camera = sys.argv[1] if len(sys.argv) > 1 else "output"
main(camera)
stream = sys.argv[2] if len(sys.argv) > 2 else ""
main(camera, stream)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ toml==0.10.2
typing_extensions==4.5.0
urllib3==1.26.14
Werkzeug==2.2.2
opencv-python-headless
6 changes: 4 additions & 2 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import rq
from flask import Flask
import pathlib
import os
from redis.client import Redis

from flask_sqlalchemy import SQLAlchemy
Expand All @@ -14,8 +15,9 @@
video_storage.mkdir()

app = Flask(__name__)
app.redis = Redis.from_url("redis://")
app.config["SQLALCHEMY_DATABASE_URI"] = f'sqlite:///{(pathlib.Path(__file__) / ".." / "app.db").resolve()}'
redis_url = os.environ["REDIS_URL"] or "redis://"
app.redis = Redis.from_url(redis_url)
app.config["SQLALCHEMY_DATABASE_URI"] = f'sqlite:///{(pathlib.Path(__file__) / ".." / ".." / "data" / "app.db").resolve()}'
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.task_queue = rq.Queue(connection=app.redis)
db = SQLAlchemy(app)
Expand Down
3 changes: 2 additions & 1 deletion src/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ def delete_roi_region(id_):

@app.route("/encode", methods=["POST"])
def start_encoding():
print('start encoding')
data = request.get_json()
roi_id = data.get("roi_id")
print(data)
f = None
if roi_id is not None:
f = roi_storage / roi_id
Expand Down Expand Up @@ -144,7 +146,6 @@ def start_encoding():
"src.encoder.encode",
f, start_point, duration, mkstemp()[1], camera, out_path
)
print(a.get_id())
return {"id": a.get_id()}


Expand Down
Loading