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

Implemented updates for backend usage #4

Merged
merged 35 commits into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
97a926b
Added Icons
tsundDEV Nov 14, 2022
82126f1
fix: [backend/single-instance] Backend improvements
Nov 14, 2022
e4f1191
feat: Added Chakra | Added Theme | Added Layout
tsundDEV Nov 14, 2022
f019190
dev: [backend/single-instance] Updates for Python script
Nov 14, 2022
4c2314d
FEAT: navbar UPDATED: landing page
ceriddenn Nov 15, 2022
279fc11
feat: upload page added addded a few other things
ceriddenn Nov 15, 2022
fb793d2
feat: Added Footer + Responsive Nav + Home Hero x2
tsundDEV Nov 15, 2022
91f5c76
Merge !ceri additition to @tsunddev additions in branch 'web'
tsundDEV Nov 15, 2022
10c0006
fix: Added links to nav and index, upload page linting and background…
tsundDEV Nov 15, 2022
42b6adf
fix/clean: upload page removed unused
tsundDEV Nov 15, 2022
13e5492
feat: implementing next-auth
ceriddenn Nov 15, 2022
e026d54
fix: app.tsx
ceriddenn Nov 15, 2022
773a4f8
chore: clean up
tsundDEV Nov 15, 2022
2982c73
feat: Upload redone | Global Context | Enabled Main feat
tsundDEV Nov 15, 2022
2f54aa2
feat:Added avatarUrl support
ceriddenn Nov 15, 2022
edad9c5
feat:Added avatarUrl support
ceriddenn Nov 15, 2022
a3f79e6
feat: Merged sort
tsundDEV Nov 15, 2022
ab857c1
Created merge
tsundDEV Nov 15, 2022
4cd8273
Fixed Unsolved Merge
tsundDEV Nov 15, 2022
745a441
fix: linting
tsundDEV Nov 15, 2022
7fcec45
feat: [backend/single-instance] Improvements for createFootage logic
Nov 15, 2022
79f7b69
chore: [backend/single-instance] Merge in master
Nov 15, 2022
cc00f6d
chore: [backend/single-instance] Merge in web branch changes
Nov 15, 2022
61f7a06
ci: [backend/single-instance] Implement CODEOWNERS
Nov 15, 2022
da38d2b
chore: [backend/single-instance] CODEOWNERS & prettier clean up
Nov 15, 2022
aa9f06c
chore: [backend/single-instance] EOF clean up
Nov 15, 2022
f97f7b4
fix: [backend/single-instance] Observation improvements
Nov 15, 2022
685b13f
Update packages/eslint-config-custom/package.json
Huskydog9988 Nov 15, 2022
3d829d6
Update packages/tsconfig/package.json
Huskydog9988 Nov 15, 2022
08a5217
Update packages/ui/package.json
Huskydog9988 Nov 15, 2022
8664eaf
chore: some opinionated vscode settings
Huskydog9988 Nov 16, 2022
e1d3700
chore: some recommended vscode extensions
Huskydog9988 Nov 16, 2022
0a4b5d9
chore: add editorconfig
Huskydog9988 Nov 16, 2022
bad243f
Merge pull request #5 from waldo-vision/feat/dx
Huskydog9988 Nov 16, 2022
441afbc
Merge branch 'master' into backend/single-instance
Nov 16, 2022
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
24 changes: 24 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.

# More details are here: https://help.github.com/articles/about-codeowners/

# The '*' pattern is global owners.

# Order is important. The last matching pattern has the most precedence.
# The folders are ordered as follows:

# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.

# Global rule:
* @waldo-vision/team-leads

# Backend project:
/apps/backend/** @b3kN

# Web project:
/apps/web/** @tsundDEV

# Desktop project:
/apps/desktop/** @tsundDEV
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ yarn-error.log*

# idea
.idea

# videos
*.mp4
3 changes: 2 additions & 1 deletion .prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ module.exports = {
singleQuote: true,
printWidth: 80,
tabWidth: 2,
endOfLine: 'lf'
endOfLine: 'lf',
arrowParens: 'avoid'
};
11 changes: 7 additions & 4 deletions apps/backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
HOST=127.0.0.1
PORT=8001
PORT=8002
WEB_PORT=3001

DB_URL="mongodb://root:example@127.0.0.1:27017/waldo-footage?authSource=admin"
FOOTAGE_COLLECTION_NAME=footage
CLIPS_COLLECTION_NAME=clips
# Linux
FS_LOCATION="/home/{{user}}/waldo-footage"
# Windows
# FS_LOCATION="C:\Users\{{user}}\waldo-footage""
DB_URL="mongodb://root:example@127.0.0.1:27017/waldo-footage?authSource=admin"
216 changes: 216 additions & 0 deletions apps/backend/autoClip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
"""
Huskydog9988 marked this conversation as resolved.
Show resolved Hide resolved
auto_clip.py
Usage: python auto_clip.py {Input Folder} {Output Directory} {(Optional) Use Time (0 or 1)}
Takes in an input video file and finds kills based on kill feed
Video file must be 1920 x 1080 @ 60FPS
Video should contain gameplay of Counter Strike: Global Offensive
"""

import cv2
import numpy as np
import sys
import os
import datetime

# global vars
fc = 0

nlines = 0
plines = 0

decCount = 0

pois = []

# Require 2 options to running program
assert len(sys.argv) >= 3, "needs file input and destination"

inFile = sys.argv[1]
outDir = sys.argv[2]

# Require input file and output directory exist
assert os.path.isfile(inFile), f'input file {inFile} doesn\'t exist'
assert os.path.isdir(outDir), "output directory doesn't exist"

# allow optional flag to save clips in folders named after the time they ocour in the video
useTime = False
if len(sys.argv) > 3 and sys.argv[3].isnumeric() and int(sys.argv[3]) > 0:
useTime = True

# Creating a VideoCapture object to read the video
cap = cv2.VideoCapture(inFile)

# make sure video is the correct frame rate
framRate = cap.get(cv2.CAP_PROP_FPS)
# assert int(framRate) == 60, "Video must be 60FPS"

showParse = False

# count the number of red lines found in a portion of an image
# input image must be 3 pixles wide and in BGR format
def countLines(im):
assert im.shape[1] == 3

streak = 0
breakS = 0
lineC = 0

# for each horizontal line
for y in range(im.shape[0]):
# if 2 pixels in a row are incorrect
if breakS >= 2:
# print("Break check...")
breakS = 0

# increment line count if there were enough pixles in a row
if streak > 15:
# print("Count streak..")
lineC += 1

streak = 0

# check if pixel to left is the same color
# Used to check for deaths because the are a solid color and not an outlined box
if abs(int(im[y,1,2]) - int(im[y,0,2])) < 20:
breakS += 1
continue

# check if the pixel is roughly the correct color of red
if im[y,1,2] > 150 and im[y,1,1] < 10 and im[y,1,0] < 50:
streak += 1
breakS = 0

# if wrong color cont count towards streak
else:
breakS += 1

# Add a line to the count if the loop ends saying there is a streak
if breakS > 15:
name = os.path.join(os.getcwd(), outDir, "frame" + str(y) + ".png")
cv2.imwrite(name, im)
lineC += 1

return lineC

# Loop until the end of the video
while (cap.isOpened()):
# Capture frame-by-frame
ret, frame = cap.read()

if frame is None:
break

# view = cv2.resize(view, (1280, 720), fx = 0, fy = 0, interpolation = cv2.INTER_CUBIC)

# area of video to analyze must be 3 pixels wide
# this area is the right edge of the kill feed
frame_crop = frame[70:300, 1909:1912]

# count the number of kill feed edges
nlines = countLines(frame_crop)

# Display kills to user
# print(f'Kills on Screen: {nlines}, Kills Counted: {len(pois)}\t\t\r', end='', flush=True)

decCount += 1

# check curent number of lines vs previous frames number of lines
if nlines < plines:
# keep track of the last time the number of edges decreased
decCount = 0

# if number of lines has increased add frame number to list to save later
if nlines > plines and decCount > 10:
# print("Increase")
pois.append(fc)

plines = nlines

# shown to user
view = cv2.resize(frame, (1280, 720), fx = 0, fy = 0, interpolation = cv2.INTER_CUBIC)
view = cv2.putText(view, f'Kills Counted: {len(pois)}', (530, 450), cv2.FONT_HERSHEY_COMPLEX, 0.75, (0,0,255), 1, cv2.LINE_AA)

if showParse:
parseView = view.copy()
parseView[parseView[:,:,2] <= 110 ] = [0,0,0]
parseView[parseView[:,:,1] >= 20 ] = [0,0,0]
parseView[parseView[:,:,0] >= 40 ] = [0,0,0]
cv2.imshow('Parser Vision', parseView)

# Display the frames to user
# cv2.imshow('Area or Intrest', frame)
# cv2.imshow('Normal Gameplay', view)

# define q as the exit button
if cv2.waitKey(1) & 0xFF == ord('q'):
break

fc += 1

# release the video capture object
cap.release()
# Closes all the windows currently opened.
cv2.destroyAllWindows()

# prints for refrence
# print("\n")
# print(pois)
# print(f'\n----------\nSaving {len(pois)} Clip{"s" if len(pois) != 1 else ""}')

# how to save a frame modified from video_to_photos
# only works with 1920x1080 video
def save_frames(file_name, out_dir, start=0, end=-1):
cap = cv2.VideoCapture(file_name)

assert cap, "path to file must be valid"

if not os.path.exists(out_dir):
os.makedirs(out_dir)

if end == -1:
end = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

cap.set(cv2.CAP_PROP_POS_FRAMES, start)

for fno in range(start, end):
success, frame = cap.read()
assert success, "frame not read correctly"

frame = frame[428:652, 848:1072]

name = os.path.join(os.getcwd(), out_dir, "frame" + str(fno + 1) + ".png")
cv2.imwrite(name, frame)

cap.release()

# get folder names in outdir
subfolders = [ f.name for f in os.scandir(outDir) if f.is_dir() ]
max = 0

if not useTime:
# make new folder name larger than any folder in curent directory
for folder in subfolders:
if folder.isnumeric() and int(folder) >= max:
max = int(folder) + 1

# save all clips found earlier
for x in range(len(pois)):
assert pois[x] >= 55

# print(f'Saving Clip {x + 1}\t\r', end='', flush=True)

# save folder based on time clip is in respective video
# using this name scheme may result in duplicate folder names across multiple different videos
# useful for saving info to tell to user
if useTime:
s = pois[x] / 60
time = datetime.timedelta(seconds = s)
# print(time)
save_frames(inFile, os.path.join(outDir, str(time).replace(":", "_")), pois[x] - 55, pois[x] + 5)

# save folder name based on other folders in dir
else:
save_frames(inFile, os.path.join(outDir , str(max + x)), pois[x] - 55, pois[x] + 5)\

# print("All Clips Saved!\t\t\t\t")
sys.exit()
20 changes: 14 additions & 6 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"description": "Back end application to process videos based on YouTube URL for AI/ML model usage.",
"scripts": {
"db": "docker compose up",
"dev": "ts-node-dev --respawn --pretty --transpile-only src/index.ts",
"start": "ts-node dist/index.js",
"dev": "cross-env NODE_ENV=dev ts-node-dev --respawn --pretty --transpile-only src/index.ts",
"start": "cross-env NODE_ENV=production ts-node dist/index.js",
"build": "tsc",
"lint": "eslint src/**/*.{ts,tsx}"
"lint": "eslint src/**/*.{ts,tsx}",
"lint:fix": "eslint src/**/*.{ts,tsx} --fix"
},
"keywords": [],
"dependencies": {
Expand All @@ -18,17 +19,24 @@
"mime": "^3.0.0",
"mongoose": "^6.7.1",
"uuid": "^9.0.0",
"ytdl-core": "^4.11.2"
"ytdl-core": "^4.11.2",
"body-parser": "^1.20.1",
"fs-extra": "^10.1.0"
},
"devDependencies": {
"@types/cors": "^2.8.12",
"@types/express": "^4.17.14",
"@types/mime": "^3.0.1",
"@types/node": "^18.11.9",
"@types/uuid": "^8.3.4",
"@types/fs-extra": "^9.0.13",
"@types/swagger-ui-express": "^4.1.3",
"eslint-config-custom": "workspace:*",
"ts-node-dev": "^2.0.0",
"tsconfig": "workspace:*",
"typescript": "^4.8.4"
"ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0",
"typescript": "^4.8.4",
"swagger-ui-express": "^4.6.0",
"cross-env": "^7.0.3"
}
}
20 changes: 7 additions & 13 deletions apps/backend/src/controllers/clip.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { Request, Response } from 'express';
import { v4 as uuidv4, validate } from 'uuid';
import { v4 as uuidv4 } from 'uuid';
import {
Clip,
ClipInput,
ClipDocument,
ClipZod,
ClipZodSchema,
ClipRetrieveZod,
ClipRetrieveSchema,
} from '../models/clip.interface';
import { defaultEndpointsFactory, z, createHttpError } from 'express-zod-api';
Expand All @@ -23,7 +18,7 @@ export const createClip = defaultEndpointsFactory.build({
uuid: z.string().uuid(),
}),
output: ClipZodSchema,
handler: async ({ input: { uuid }, options, logger }) => {
handler: async ({ input: { uuid } }) => {
const uniqueId = uuidv4();

// TODO: Implement logic to store clips to storage directory named after the Footage ID.
Expand Down Expand Up @@ -58,7 +53,7 @@ export const downloadClipById = fileStreamingEndpointsFactory.build({
output: z.object({
uuid: z.string(),
}),
handler: async ({ input: { uuid }, options, logger }) => ({
handler: async ({ input: { uuid } }) => ({
// most functionality is in the streamingEndPointFactory file.. see ../factories/fileStreamingEndpointsFactory.
uuid: uuid,
}),
Expand All @@ -78,12 +73,11 @@ export const getClip = defaultEndpointsFactory.build({
// the error doesn't cause any problems with operations.
}),
output: ClipRetrieveSchema,
handler: async ({ input: { uuid }, options, logger }) => {
const clipResult: any[] = [];
handler: async ({ input: { uuid } }) => {
const clipResult = [];
if (uuid) {
const clip = await Clip.findOne({ uuid });
if (clip === null) {
console.log('error');
throw createHttpError(
404,
'No clip document with the UUID provided could be found.',
Expand All @@ -95,7 +89,7 @@ export const getClip = defaultEndpointsFactory.build({
if (allClips == null) {
throw createHttpError(404, 'No clip documents could be found.');
}
allClips.forEach((doc, index) => {
allClips.forEach(doc => {
clipResult.push(doc);
});
}
Expand All @@ -115,7 +109,7 @@ export const deleteClip = defaultEndpointsFactory.build({
uuid: z.string().uuid().optional(),
}),
output: z.object({ message: z.string() }),
handler: async ({ input: { uuid }, options, logger }) => {
handler: async ({ input: { uuid } }) => {
const deleteResult = await Clip.deleteOne({ uuid });

if (deleteResult.deletedCount === 0) {
Expand Down
Loading