PHOTOBOOTH
THE WEB IS YOUR
SMILE!
$whoami
Diana Rodríguez
Google Developer Expert
Auth0 Ambassador
Microsoft MVP
Developer Advocate @ Twitch/AWS
🏡 https://superdi.dev
🐦 @cotufa82
<likes> Food, Infrastructure, Food, Vue.js, Food, Travelling, IOT, Steven Universe </likes>
$whoami-NOT
Efectos De La Cultura DevOps
La adopción de la cultura DevOps, herramientas y practicas ágiles de ingeniería tienen, por sobre todas las cosas, el lindo efecto de incrementar la colaboración entre los roles de desarrollo y operaciones.
Uno de los principales problemas del pasado (pero también hoy en día en algunas realidades) es que el equipo de desarrollo no estaba interesado en la operación y el mantenimiento de un sistema una vez que se entregó al equipo de operaciones, mientras que este último no estaba realmente consciente de los objetivos de negocio, y por lo tanto, reacios a satisfacer las necesidades operativas del sistema (también denominados “caprichos de los desarrolladores”)
Vue.js Docs
Ola Ke Ase?
-
Capturar stream de una webcam
-
Snap Pic con countdown (5seg)
-
Auto mode (extraemos canvas cada segundo, cuando it detecta una sonrisa, se detiene y.. snaps!)
-
Drawer para las capturas con y sin filtros
-
Click para Download
-
Compartir por SMS
TOOLS 🧰
- Vonage Video API
- Vonage Communications API (SMS)
- Azure App Service
- Azure Cognitive Services
TALK CODE TO ME
import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
Vue.config.productionTip = false
console.log(process.env)
new Vue({
vuetify,
render: h => h(App)
}).$mount('#app')
main.js
data: () => ({
//
opentok_api_key: ENV.OPENTOK_API_KEY ? ENV.OPENTOK_API_KEY : "",
opentok_session_id: ENV.OPENTOK_SESSION_ID ? ENV.OPENTOK_SESSION_ID : "",
opentok_token: ENV.OPENTOK_TOKEN ? ENV.OPENTOK_TOKEN : "",
azure_face_api_subscription_key: ENV.AZURE_FACE_API_SUBSCRIPTION_KEY
? ENV.AZURE_FACE_API_SUBSCRIPTION_KEY
: "",
azure_face_api_endpoint: ENV.AZURE_FACE_API_ENDPOINT
? ENV.AZURE_FACE_API_ENDPOINT
: "",
………….
}),
<div id="publisher">
<v-overlay :absolute="true" :value="counter != 6">
<div style="font-size:150px;">{{ counter }}</div>
</v-overlay>
</div>
async initializeSession() {
var session = OT.initSession(this.opentok_api_key,
this.opentok_session_id);
this.publisher = OT.initPublisher("publisher",{insertMode: "append",
width: 400,height: 300
},
handleError
);
// Connect to the session
session.connect(this.opentok_token, error => {
// If the connection is successful, initialize a publisher and publish to the session
if (error) {
handleError(error);
} else {
session.publish(this.publisher, handleError);
}
});
}
<v-card-actions>
<v-btn @click="analyze()" v-if="manual == true" text class="analyze">
<v-img :src="require('./assets/snap.png')" contain height="50" />
</v-btn>
</v-card-actions>
analyze() {
let imageData = this.publisher.getImgData();
let blob = this.dataURItoBlob("data:image/png;base64," + imageData);
//Evaluates image emotion in azure cognitive face service
const xhr = new XMLHttpRequest();
xhr.open(
"POST",
`${this.azure_face_api_endpoint}face/v1.0/detect?returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=emotion`
);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
let response = xhr.response[0];
if (response !== null && response !== undefined) {
............
//Emotion is present, so we evaluate happiness factor (between 0 and 1) if happiness is > 0.5 we take the snap
if (
response.faceAttributes.emotion.happiness !== null &&
response.faceAttributes.emotion.happiness !== undefined
) {
if (response.faceAttributes.emotion.happiness >= 0.5) {
//take the snap and put it in image array
this.images.push({
id: this.images.length + 1,
dataurl: "data:image/png;base64," + image
});
} else {
this.snackbar = true;
this.snackbar_message =
"Smiling is a requirement. Smile and we take your photo";
}
}
}
..............
};
xhr.responseType = "json";
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.setRequestHeader(
"Ocp-Apim-Subscription-Key",
this.azure_face_api_subscription_key
);
xhr.send(blob);
}, 6000);
}
## App.vue
<v-btn
@click="downloadImages()"
style="background-color:inherit !important; padding: 0 !important;"
>
# MANY LINES AFTER....
forceFileDownload(index, place) {
let imgs = null;
if (place == undefined) imgs = this.images;
else imgs = this.filteredImages;
let image_file = this.dataURItoBlob(imgs[index - 1].dataurl);
const url = window.URL.createObjectURL(image_file);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "snap_" + index + ".png"); //or any other extension
link.click();
},
downloadImages() {
for (let i = 0; i < this.images.length; i++) {
this.forceFileDownload(this.images[i].id);
}
for (let f = 0; f < this.filteredImages.length; f++) {
this.forceFileDownload(this.filteredImages[f].id, "filtered");
}
},
# re module is for regular expression ops
import nexmo, re, base64
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from dotenv import load_dotenv
from os import environ, makedirs
from os.path import join, dirname, abspath, exists
from datetime import datetime
app = Flask(__name__, static_url_path="", static_folder="dist")
CORS(app)
# 10mb size is allowed
app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024
# Get env vars from file
envpath = join(dirname(__file__), "./.env")
load_dotenv(envpath)
# Needed for uploading files
basedir = abspath(dirname(__file__))
client = nexmo.Client(
key=environ.get("NEXMO_API_KEY"), secret=environ.get("NEXMO_API_SECRET")
)
# Verify if snaps directory exists, if not then create it
if not exists("{}/dist/snaps".format(basedir)):
makedirs("{}/dist/snaps".format(basedir))
@app.route("/", methods=["GET"])
def home():
return send_from_directory("./dist/", "index.html")
@app.route("/send-mms", methods=["POST"])
def send_mms():
params = request.get_json() or request.form or request.args
if "phone" and "image" in params:
# Get the base64 image
image = re.sub(r"^data:image\/png;base64,", "", params["image"])
image = bytes(image, "utf-8")
phone = params["phone"]
# Get the current timestamp
filename_prefix = datetime.utcnow().isoformat()
filename = "{phone}-{prefix}.png".format(phone=phone, prefix=filename_prefix)
# Save image
imagedir = "{}/dist/snaps".format(basedir)
# Create the binary file in the snaps directory
imagefile = open(
"{imagedir}/{filename}".format(imagedir=imagedir, filename=filename), "wb"
)
# Write the bytes
imagefile.write(base64.decodebytes(image))
# Close file
imagefile.close()
# Send sms
response = client.send_message(
{
"from": environ.get("NEXMO_NUMBER"),
"to": phone,
"text": "Opentok-Nexmo, Your snap is ready: {site_url}/snaps/{filename}".format(
site_url=environ.get("SITE_URL"), filename=filename
),
}
)
if response["messages"][0]["status"] == "0":
return jsonify({"status": "success", "message": "All OK"}), 200
else:
return (
jsonify(
{
"status": "error",
"message": "Message failed with error: "
+ response["messages"][0]["error-text"],
}
),
200,
)
else:
return (
jsonify(
{
"status": "error",
"message": "Required params (phone, image) not provided",
}
),
200,
)
# re module is for regular expression ops
import nexmo, re, base64
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from dotenv import load_dotenv
from os import environ, makedirs
from os.path import join, dirname, abspath, exists
from datetime import datetime
app = Flask(__name__, static_url_path="", static_folder="dist")
CORS(app)
# 10mb size is allowed
app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024
# Get env vars from file
envpath = join(dirname(__file__), "./.env")
load_dotenv(envpath)
# Needed for uploading files
basedir = abspath(dirname(__file__))
client = nexmo.Client(
key=environ.get("NEXMO_API_KEY"), secret=environ.get("NEXMO_API_SECRET")
)
# Verify if snaps directory exists, if not then create it
if not exists("{}/dist/snaps".format(basedir)):
makedirs("{}/dist/snaps".format(basedir))
@app.route("/", methods=["GET"])
def home():
return send_from_directory("./dist/", "index.html")
@app.route("/send-mms", methods=["POST"])
def send_mms():
params = request.get_json() or request.form or request.args
if "phone" and "image" in params:
# Get the base64 image
image = re.sub(r"^data:image\/png;base64,", "", params["image"])
image = bytes(image, "utf-8")
phone = params["phone"]
# Get the current timestamp
filename_prefix = datetime.utcnow().isoformat()
filename = "{phone}-{prefix}.png".format(phone=phone, prefix=filename_prefix)
# Save image
imagedir = "{}/dist/snaps".format(basedir)
# Create the binary file in the snaps directory
imagefile = open(
"{imagedir}/{filename}".format(imagedir=imagedir, filename=filename), "wb"
)
# Write the bytes
imagefile.write(base64.decodebytes(image))
# Close file
imagefile.close()
# Send sms
response = client.send_message(
{
"from": environ.get("NEXMO_NUMBER"),
"to": phone,
"text": "Opentok-Nexmo, Your snap is ready: {site_url}/snaps/{filename}".format(
site_url=environ.get("SITE_URL"), filename=filename
),
}
)
if response["messages"][0]["status"] == "0":
return jsonify({"status": "success", "message": "All OK"}), 200
else:
return (
jsonify(
{
"status": "error",
"message": "Message failed with error: "
+ response["messages"][0]["error-text"],
}
),
200,
)
else:
return (
jsonify(
{
"status": "error",
"message": "Required params (phone, image) not provided",
}
),
200,
)
DEPLOYMENT
DEMO!
https://opentok-nexmo.azurewebsites.net
https://github.com/opentok-community/opentok-photobooth
MUCHAS GRACIAS!!
@cotufa82
https://superdi.dev
https://slides.com/superdiana/smile-jump
Smile ES
By Super Diana
Smile ES
A photobooth built with Vonage communication and video APIs!
- 680