How to return base64 video via API - laravel-5

I have videos that can be seen only if the user is authenticated. Due to which I decided to return videos in base64 format via API. However, I have quickly identified a problem, having noticed that if the file is greater than 1MB the returned data is truncated.
Example code:
$size = Storage::size($video->path);
header("Content-length: $size");
echo base64_encode(Storage::get($video->path));
<video :src="'getVideoSrc()" />
getVideo() {
this.axios.get('video/1').then(result => {
this.video = result.data;
});
},
getVideoSrc() {
return "data:video/mp4;base64," + this.getVideo();
}
How can this be resolved?

I would avoid using base64 encoding as it's not required and only blows out the size of your data stream.
If you're happy delivering the entire video (as opposed to streaming it), you can use something like this client-side.
<video :src="videoSrc">
data: () => ({ videoSrc: '' }),
methods: {
async getVideo() {
const { data } = await axios.get('video/1', { responseType: 'blob' })
this.videoSrc = URL.createObjectURL(data)
}
}
beforeDestroy() {
// clean up
URL.revokeObjectURL(this.videoSrc)
}
FYI, I'm not sure what changes you'd need to make to your server-side but I imagine it would involve omitting base64_encode()

Related

Get image from blob Laravel vue

First of all, this is the start of where I am at as a similar post
Store blob as a file in S3 with Laravel
I am sending a photo from VueJS to Laravel. It is coming as multipart/form-data.
Vue Code:
export default {
emits: ['onClose'],
props: ['isOpen'],
data: function() {
return {
serverOptions: {
process: (fieldName, file, metadata, load, error) => {
const formData = new FormData();
formData.append(fieldName, file, file.name);
axios({
method: "POST",
url: '/chat/room/upload',
data: formData,
headers: {
"Content-Type": "multipart/form-data"
}
})
.then(() => {
load();
})
.catch(() => {
error();
});
}
},
files: [],
};
},
methods: {
handleFilePondInit: function () {
console.log('FilePond has initialized');
// example of instance method call on pond reference
this.$refs.pond.getFiles();
console.log(this.$refs.pond.getFiles());
},
},
Laravel Controller:
public function uploadImage(Request $request)
{
// This is what this is SUPPOSED to do. Grab the file from the frontend
// Bring it here. Store it in S3, return the path with the CDN URL
// Then store that URL into the DB as a message. Once that is done, then
// Broadcast the message to said room.
if ($request->has('upload')) {
$files = $request->get('photo');
$urls = [];
foreach ($files as $file) {
$filename = 'files/' . $file['name'];
// Upload File to s3
Storage::disk('digitalocean')->put($filename, $file['blob']);
Storage::disk('digitalocean')->setVisibility($filename, 'public');
$url = Storage::disk('digitalocean')->url($filename);
$urls[] = $url;
}
return response()->json(['urls' => $urls]);
}
// broadcast(new NewChatMessage($newMessage))->toOthers();
// return $newMessage;
}
First: I want to state that if there is something wrong with the current code, just know its because ive been playing around with this for 3 hours now and been trying anything. I am sure at one point I had it close but somehow screwed it up along the way so I am more looking for fresh eyes to show me my error.
That being said, the other part to take into account is in DevTools under Network I can clearly see the blob and can load it up, I can also see the "upload" item and under there the form data which shows the following
------WebKitFormBoundary7qD7xdmiQO9U1Ko0
Content-Disposition: form-data; name="photo"; filename="6A8B48B4-F546-438E-852E-C24340525C20_1_201_a.jpeg"
Content-Type: image/jpeg
------WebKitFormBoundary7qD7xdmiQO9U1Ko0--
it clearly also shows photo: (binary) so I am completely confused as to what I am doing wrong. The ULTIMATE goal here is to get the image, store it as public in S3/DigitalOcean then grab the public URL to the file and store in the DB.
Any help would be GREATLY appreciated!

Rendering image from API response in NextJS - just downloads base64 file

I am working on a new project and learning ReactJS. I have saved an image in base64 to a MySQL database and I am now trying to do a GET request so the image is shown in the browser instead of it being downloaded, however, although it attempts to download the file, the file is just a file containing the base64 string instead of an actual image.
The file in the database looks like the below (only a snippet of the base64)
data:image/png;base64,iVBORw0KGgoAAAA
The API to fetch the image is as below:
export default async function handler(req : NextApiRequest, res : NextApiResponse) {
const prisma = new PrismaClient()
if (req.method === "GET")
{
const {image_id} = req.query;
const image = await prisma.images.findFirst({
where: {
imageId: parseInt(image_id)
}
});
const decoded = Buffer.from(image.content).toString("base64");
console.log(decoded)
res.status(200).send(decoded);
}
else
{
res.status(405).json(returnAPIErrorObject(405, "Method not supported by API endpoint"));
}
}
I've modified the next.config.js to provide a custom header response which contains the below:
module.exports = {
async headers() {
return [
{
source: '/api/images/:image_id',
headers: [
{
key: 'Content-Type',
value: 'image/png:Base64'
}
]
}
]
}
}
As mentioned, when I go to the URL http://localhost:3000/api/images/4 (4 being the image id) it downloads a file that contains the base64 string that is in the database, instead of showing the image in the browser.
UPDATE
Based on the link in the comment from #sean w it now attempts to display the image but instead of showing the actual picture, it just shows a blank window with a white square as shown in the screenshot below.
My code now looks like the following:
const {image_id} = req.query;
const image = await prisma.images.findFirst({
where: {
imageId: parseInt(image_id)
}
});
const decoded = Buffer.from(image.content, 'base64').toString();
let imageContent = decoded.replace(/^data:image\/png;base64,/, '');
console.log(decoded);
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': imageContent.length
});
res.end(imageContent);
Below is a screenshot showing what actually gets rendered on the page instead of my actual image.
I've figured out the issue, instead of creating the buffer from the database which I think looks like Prisma as the column was a blob was giving me a buffer anyway. , I first extract the base64 string from the DB and remove the data:image/png;base64 from the string and then create a buffer from that string and send that for the response:
const {image_id} = req.query;
const image = await prisma.images.findFirst({
where: {
imageId: parseInt(image_id)
}
});
const decoded = image.content.toString().replace("data:image/png;base64,", "");
const imageResp = new Buffer(decoded, "base64");
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': imageResp.length
});
res.end(imageResp);

How to POST correctly a form that have data and files with VueJS, Axios and Laravel?

I am posting here as a beginner of VueJS and Laravel. I am stuck with a problem that I can't fix by myself after hours of search.
I would like to know how correctly send and get back the inputs of a form (complex data and files).
Here is the submit method of the form:
onSubmit: function () {
var formData = new FormData();
formData.append("data", this.model.data);
formData.append("partData", this.model.partData);
if (this.model.symbolFile != null) {
formData.append("symbolFile", this.model.symbolFile);
}
if (this.model.footprintFile != null) {
formData.append("footprintFile", this.model.footprintFile);
}
axios
.post("/api/updatecomponent", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
// do something with res
// console.log(res);
})
.catch((err) => {
/* catch error*/
console.log(err);
});
},
The variable Data and PartData contains multiple string fields which will be stored in different tables in my database. Example :
Data
{
string Value,
string Tolerance,
string Power
}
Here is the method of the Controller in the server side:
public function updateComponent(Request $req)
{
$data = $req->input('data');
$partData = $req->input('partData');
$symbolFile = $req->file('symbolFile'); // is null if the user did not modify the symbol
$footprintFile = $req->file('symbolFile'); // is null if the user did not modify the footprint
// etc...
}
I am able to get the files, everything work for that and I can store and read them :)
But, the problem is that I am unable to get back properly my Data or PartDat.
When I do :
dd($partData);
I got as result in the console:
"[object Object]"
I am almost sure that I don't use correctly the FormData but after hours of search, I can't find the good way I should gave the Data and PartData to the FormData.
My code was working well for Data and PartData until I add FormData to support the file upload :(
Thank you for your help :)
Here my working code:
Client side:
var formData = new FormData(); // create FormData
formData.append("subcat", this.subcategory);// append simple type data
formData.append("data", JSON.stringify(this.model.data));// append complex type data
axios // send FormData
.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
// do something with res
// console.log(res);
})
.catch((err) => {
/* catch error*/
console.log(err);
});
Server side:
public function createComponent(Request $req)
{
$subcategory = $req->input('subcat'); // get the input parameter named 'subcat' (selected subcategory)
$data = json_decode($req->input('data'), true); // get the input, decode the jason format, force to return the result as an array
}
I hope it will help other peoples :)
Simple solution
let data = new FormData();
data.append('image',file_name.value);
_.each(form_data, (value, key) => {
data.append(key, value)
})
console.log('form data',data);
Now you can get data in laravel controller like:
$request->title
$request->description
$request->file

YouTube search API not filtering embeddable/syndicatable videos

I'm embedding YouTube videos into an app I'm writing, but some of the results are videos that aren't allowed to be played on my site. I've tried setting both videoSyndicated and videoEmbeddable to true in the params but it doesn't seem to fix my problem.
const axios = require('axios');
const ROOT_URL = 'https://www.googleapis.com/youtube/v3/search';
const search = (options, callback) => {
if(!options.key) {
throw new Error('Youtube Search expected key, received undefined');
}
const params = {
type: 'video',
videoEmbeddable: true,
videoSyndicated: true,
part: 'snippet',
key: options.key,
q: options.term,
};
axios.get(ROOT_URL, { params })
.then((response) => {
if(callback) { callback(response.data.items); }
})
.catch((error) => {
console.error(error);
});
};
export default search;`
This looks like a copyright claim by a 3rd party, which happens outside of the YouTube API. You can check this separately by scraping the video page and searching for the copyright text.

Can't get meteor FileCollection package to work

I am (unfortunately) working on a Windows machine so I had to manually add the FileCollection package to my app, but when I run my app, I can access the file collection and file collection methods from the browser console. However, I can't seem to get the event listeners set up on an actual page. (FYI, I am using iron-router for my templating architecture.)
It seems like the code that needs to be called is just not coming in the right order, but I've experimented with where I place the code and nothing seems to make a difference.
The server side code:
// Create a file collection, and enable file upload and download using HTTP
Images = new fileCollection('images',
{ resumable: true, // Enable built-in resumable.js upload support
http: [
{ method: 'get',
path: '/:_id', // this will be at route "/gridfs/images/:_id"
lookup: function (params, query) { // uses express style url params
return { _id: params._id }; // a mongo query mapping url to myFiles
}
}
]
}
);
if (Meteor.isServer) {
// Only publish files owned by this userId, and ignore
// file chunks being used by Resumable.js for current uploads
Meteor.publish('myImages',
function () {
return Images.find({ 'metadata._Resumable': { $exists: false },
'metadata.owner': this.userId });
}
);
// Allow rules for security. Should look familiar!
// Without these, no file writes would be allowed
Images.allow({
remove: function (userId, file) {
// Only owners can delete
if (userId !== file.metadata.owner) {
return false;
} else {
return true;
}
},
// Client file document updates are all denied, implement Methods for that
// This rule secures the HTTP REST interfaces' PUT/POST
update: function (userId, file, fields) {
// Only owners can upload file data
if (userId !== file.metadata.owner) {
return false;
} else {
return true;
}
},
insert: function (userId, file) {
// Assign the proper owner when a file is created
file.metadata = file.metadata || {};
file.metadata.owner = userId;
return true;
}
});
}
The client side code (currently in main.js at the top level of the client dir):
if (Meteor.isClient) {
// This assigns a file upload drop zone to some DOM node
Images.resumable.assignDrop($(".fileDrop"));
// This assigns a browse action to a DOM node
Images.resumable.assignBrowse($(".fileBrowse"));
// When a file is added via drag and drop
Images.resumable.on('fileAdded', function(file) {
// Create a new file in the file collection to upload
Images.insert({
_id : file.uniqueIdentifier, // This is the ID resumable will use
filename : file.fileName,
contentType : file.file.type
}, function(err, _id) {// Callback to .insert
if (err) {throwError('Image upload failed');}
// Once the file exists on the server, start uploading
Images.resumable.upload();
});
});
Images.resumable.on('fileSuccess', function(file) {
var userId = Meteor.userId();
var url = '/gridfs/images/' + file._id;
Meteor.users.update(userId, {
$set : {
"profile.$.imageURL" : url
}
});
Session.set('uploading', false);
});
Images.resumable.on('fileProgress', function(file) {
Session.set('uploading', true);
});
}
I think the issue might be with using IronRouter. I'll assume you are using some layout.html via Iron router and inside it you've added your template for your file table to be shown. (I'm guessing your are following the sampleApp that came with fileCollection.). I had a problem when I did this and it had to do with where I had the code that attached the listeners. The problem is where you have the code "Images.resumable.assignDrop($(".fileDrop"));" in your client file. The way you have it now, that line of code is running before your template is rendered within the layout.html. So the code can not find the DOM element ".fileDrop". To fix this create a layout.js file and use the rendered method like this...
Template.layout.rendered = function(){
Images.resumable.assignDrop($(".fileDrop"));
}

Resources