Skip to content

Commit

Permalink
Merge pull request #2 from patelneel55/frontend-setup
Browse files Browse the repository at this point in the history
Setup frontend architecture and rough UI/UX outline
  • Loading branch information
patelneel55 authored Dec 29, 2020
2 parents 3a57df9 + 6c58897 commit 50494b7
Show file tree
Hide file tree
Showing 94 changed files with 2,550 additions and 25 deletions.
99 changes: 98 additions & 1 deletion backend/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const fs = require('fs');
const path = require('path');
const os = require('os');

const ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(require('@ffmpeg-installer/ffmpeg').path);

const utils = require('./utils.js');
const algolia = require('./algolia.js');
const typesense = require('./typesense.js');
Expand Down Expand Up @@ -125,12 +128,86 @@ async function addSearchRecords(bucketObject) {
})
}

async function makeSearchRequest(queryParams)
{
let tailoredResults = [];
let request_per_page = queryParams.per_page * queryParams.page;
queryParams.page = 1;

while(tailoredResults.length < request_per_page)
{
let searchResult = await typesense.search(
queryParams,
functions.config().memoree.search_host,
functions.config().memoree.search_port,
functions.config().memoree.search_apikey,
functions.config().memoree.search_index
);
if(searchResult.hits.length == 0)
break;

let results = searchResult.hits.map(obj => {
return {
"file_name": obj.document.file_name,
"confidence": obj.document.confidence,
"data": obj.document
}
});

for (const item of results)
{
if(tailoredResults.findIndex((item1) => item1.file_name == item.file_name) == -1)
{
const file_blob = admin.storage().bucket(functions.config().memoree.video_bucket).file(item.file_name.replace(/^.+?[\/]/, ""));
const [url] = await file_blob.getSignedUrl({
version: 'v4',
action: 'read',
expires: Date.now() + (24 * 60 ** 2 * 1000)
});

item['videoURL'] = url;
tailoredResults.push(item);
}
}

queryParams.page++;
}

return tailoredResults;
}

function generateThumbnail(videoURL) {

do {
var fileName = Math.random().toString(36).substring(7);
var filePath = path.join(os.tmpdir(), fileName);
} while(fs.existsSync(filePath + ".png"));

return new Promise((resolve, reject) => {
ffmpeg(videoURL)
.screenshots({
folder: os.tmpdir(),
filename: fileName,
timemarks: [(Math.floor(Math.random() * 15) + 1) + "%"],
size: "500x?"
})
.on('end', () => {
let dataURL = fs.readFileSync(filePath + ".png", 'base64');
fs.unlinkSync(filePath + ".png");
resolve(dataURL);
})
.on('error', (err) => {
reject(err);
})
});
}


// The following functions are triggered when a new entity is added or
// modified in Google Cloud Storage

const runtimeOpts = {
timeoutSeconds: 60,
timeoutSeconds: 90,
memory: '2GB'
}

Expand All @@ -155,3 +232,23 @@ exports.processJson = functions
.onFinalize(async (object) => {
await addSearchRecords(object)
})

exports.search = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
let queryParams = {
"query": req.query.q,
"sortType": req.query.sort || "relevant",
"page": req.query.page || 1,
"per_page": req.query.per_page || 25,
}
let resData = await makeSearchRequest(queryParams);

res.send(resData);
});

exports.generate_thumbnail = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
let videoURL = req.query.video_url;

let imageData = await generateThumbnail(videoURL);

return res.send(imageData);
});
2 changes: 2 additions & 0 deletions backend/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
},
"dependencies": {
"@babel/runtime": "^7.12.1",
"@ffmpeg-installer/ffmpeg": "^1.0.20",
"@google-cloud/video-intelligence": "^3.0.1",
"algoliasearch": "^4.4.0",
"firebase-admin": "^8.13.0",
"firebase-functions": "^3.8.0",
"fluent-ffmpeg": "^2.1.2",
"hashcode": "^1.0.3",
"typesense": "^0.8.0"
},
Expand Down
78 changes: 55 additions & 23 deletions backend/functions/typesense.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,47 @@ exports.save = (records, host, port, apiKey, targetIndex) => {
}
})

if(records.length > 0)
{
// Create search server client
const typesenseClient = new TypeSense.Client({
nodes: [{
host: host,
port: port,
protocol: "http"
}],
apiKey: apiKey,
'connectionTimeoutSeconds': 2
});

// Import records to typesense server, create a new collection if applicable
typesenseClient.collections(targetIndex).documents().import(records, {action: 'upsert'})
.catch(err => {
// Create new index if 404 error was returned
if(err.httpStatus == 404)
{
typesenseClient.collections().create(get_search_server_schema(targetIndex))
.then(res => {
typesenseClient.collections(targetIndex).documents().import(records, {action: 'upsert'});
});
}
else
console.error(err);
})
.then(res => {
errors = []
for(let i = 0;i < res.length;i++)
{
if(!res[i].success)
errors.push(i);
}
if(errors.length != 0)
console.log("Errors: ", errors);
});
}
}

exports.search = (queryParams, host, port, apiKey, targetIndex) => {
// Create search server client
const typesenseClient = new TypeSense.Client({
nodes: [{
Expand All @@ -111,28 +152,19 @@ exports.save = (records, host, port, apiKey, targetIndex) => {
'connectionTimeoutSeconds': 2
});

// Import records to typesense server, create a new collection if applicable
typesenseClient.collections(targetIndex).documents().import(records, {action: 'upsert'})
.catch(err => {
// Create new index if 404 error was returned
if(err.httpStatus == 404)
{
typesenseClient.collections().create(get_search_server_schema(targetIndex))
.then(res => {
typesenseClient.collections(targetIndex).documents().import(records, {action: 'upsert'});
});
}
else
console.error(err);
})
.then(res => {
errors = []
for(let i = 0;i < res.length;i++)
{
if(!res[i].success)
errors.push(i);
}
if(errors.length != 0)
console.log("Errors: ", errors);
let searchParameters = {
'q': queryParams.query,
'page': queryParams.page,
'query_by': 'entity,keywords,text,transcript,file_name',
'per_page': 250,
'max_hits': 'all'
}
if(queryParams.sortType != "relevant")
searchParameters["sort_by"] = queryParams.sortType

return new Promise((resolve, reject) => {
typesenseClient.collections(targetIndex).documents().search(searchParameters)
.then(res => resolve(res))
.catch(err => reject(err));
});
}
2 changes: 1 addition & 1 deletion backend/verify_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
metadata = json.load(json_file)
metadata_video_list = metadata["files"]

local_video_list = find(prefix_path, "-type", "f", "-iregex", ".*\.\(mov\|mp4\|avi\|wmv\|mpeg\|vob\)", _iter=True)
local_video_list = list(find(prefix_path, "-type", "f", "-iregex", ".*\.\(mov\|mp4\|avi\|wmv\|mpeg\|vob\)", _iter=True))
gs_video_list = [val[:-1] for val in gsutil("ls", "-r", os.path.join(gcloud_video_bucket, "**"), _iter=True)]

print("# of files yet to be uploaded:", len(local_video_list) - len(gs_video_list))
Expand Down
46 changes: 46 additions & 0 deletions frontend/memoree_client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
10 changes: 10 additions & 0 deletions frontend/memoree_client/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: 198df796aa80073ef22bdf249e614e2ff33c6895
channel: beta

project_type: app
16 changes: 16 additions & 0 deletions frontend/memoree_client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# memoree_client

A new Flutter project.

## Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)

For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
11 changes: 11 additions & 0 deletions frontend/memoree_client/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java

# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
63 changes: 63 additions & 0 deletions frontend/memoree_client/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 29

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

lintOptions {
disable 'InvalidPackage'
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.memoree_client"
minSdkVersion 16
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}

flutter {
source '../..'
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.memoree_client">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
Loading

0 comments on commit 50494b7

Please sign in to comment.