Change video format to webm

- Change default allowed filesize to 10mb
- Change video format for posts to webm
pull/4/head
trivernis 5 years ago
parent 269b892cc4
commit ebeaf3f549

@ -64,7 +64,7 @@ maxQueryComplexity = 5000
imageFormat = "png" imageFormat = "png"
# the max file size for uploading in bytes # the max file size for uploading in bytes
maxFileSize = 5_242_880 maxFileSize = 10_485_760
# Configuration for the api rate limit # Configuration for the api rate limit
[api.rateLimit] [api.rateLimit]

@ -10,6 +10,7 @@ import globals from "./globals";
const toArray = require("stream-to-array"); const toArray = require("stream-to-array");
const ffmpegPath = require("@ffmpeg-installer/ffmpeg").path;
const dataDirName = "data"; const dataDirName = "data";
interface IUploadConfirmation { interface IUploadConfirmation {
@ -49,6 +50,7 @@ export class UploadManager {
constructor() { constructor() {
this.dataDir = path.join(globals.getPublicDir(), dataDirName); this.dataDir = path.join(globals.getPublicDir(), dataDirName);
ffmpeg.setFfmpegPath(ffmpegPath);
} }
/** /**
@ -79,6 +81,7 @@ export class UploadManager {
const filePath = path.join(this.dataDir, fileBasename); const filePath = path.join(this.dataDir, fileBasename);
let image = sharp(data) let image = sharp(data)
.resize(width, height, { .resize(width, height, {
background: "#00000000",
fit, fit,
}) })
.normalise(); .normalise();
@ -103,23 +106,29 @@ export class UploadManager {
* @param width * @param width
*/ */
public async processAndStoreVideo(data: Buffer, width: number = 720): Promise<string> { public async processAndStoreVideo(data: Buffer, width: number = 720): Promise<string> {
return new Promise(async (resolve) => { return new Promise(async (resolve, reject) => {
const fileBasename = UploadManager.getCrypticFileName() + ".mp4"; try {
await fsx.ensureDir(this.dataDir); const fileBasename = UploadManager.getCrypticFileName() + ".webm";
const filePath = path.join(this.dataDir, fileBasename); await fsx.ensureDir(this.dataDir);
const videoFileStream = new ReadableStreamBuffer({ const filePath = path.join(this.dataDir, fileBasename);
chunkSize: 2048, const tempFile = filePath + ".tmp";
frequency: 10, await fsx.writeFile(tempFile, data);
}); const video = ffmpeg(tempFile);
videoFileStream.put(data); video
const video = ffmpeg(videoFileStream); .size(`${width}x?`)
video .toFormat("webm")
.on("end", () => { .on("end", async () => {
resolve(`/${dataDirName}/${fileBasename}`); await fsx.unlink(tempFile);
}) resolve(`/${dataDirName}/${fileBasename}`);
.size(`${width}x?`) })
.toFormat("libx264") .on("error", async (err) => {
.output(filePath); await fsx.unlink(tempFile);
reject(err);
})
.save(filePath);
} catch (err) {
reject(err);
}
}); });
} }

@ -106,6 +106,22 @@ export class Post extends Model<Post> {
return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.DOWNVOTE).length; return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.DOWNVOTE).length;
} }
/**
* Returns the media description object of the post
*/
public get media() {
const url = this.getDataValue("mediaUrl");
if (url) {
const type = url.endsWith(".webm") ? "VIDEO" : "IMAGE";
return {
type,
url,
};
} else {
return null;
}
}
/** /**
* Toggles the vote of the user. * Toggles the vote of the user.
* @param userId * @param userId

@ -9,11 +9,11 @@ import * as fsx from "fs-extra";
import * as status from "http-status"; import * as status from "http-status";
import * as path from "path"; import * as path from "path";
import globals from "../lib/globals"; import globals from "../lib/globals";
import {Group, User} from "../lib/models"; import {Group, Post, User} from "../lib/models";
import {is} from "../lib/regex";
import Route from "../lib/Route"; import Route from "../lib/Route";
import {UploadManager} from "../lib/UploadManager"; import {UploadManager} from "../lib/UploadManager";
const ffmpegPath = require("@ffmpeg-installer/ffmpeg").path;
const dataDirName = "data"; const dataDirName = "data";
interface IUploadConfirmation { interface IUploadConfirmation {
@ -59,7 +59,6 @@ export class UploadRoute extends Route {
super(); super();
this.router = Router(); this.router = Router();
this.dataDir = path.join(this.publicPath, dataDirName); this.dataDir = path.join(this.publicPath, dataDirName);
ffmpeg.setFfmpegPath(ffmpegPath);
this.uploadManager = new UploadManager(); this.uploadManager = new UploadManager();
} }
@ -80,6 +79,8 @@ export class UploadRoute extends Route {
uploadConfirmation = await this.uploadProfilePicture(req); uploadConfirmation = await this.uploadProfilePicture(req);
} else if (req.files.groupPicture) { } else if (req.files.groupPicture) {
uploadConfirmation = await this.uploadGroupPicture(req); uploadConfirmation = await this.uploadGroupPicture(req);
} else if (req.files.postMedia) {
uploadConfirmation = await this.uploadPostMedia(req);
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
uploadConfirmation = { uploadConfirmation = {
@ -187,4 +188,49 @@ export class UploadRoute extends Route {
success, success,
}; };
} }
/**
* Uploads a media file for a post
* @param request
*/
private async uploadPostMedia(request: any) {
let error: string;
let fileName: string;
let success = false;
const postId = request.body.postId;
const postMedia = request.files.postMedia as UploadedFile;
if (postId) {
try {
const post = await Post.findByPk(postId);
if (post.authorId === request.session.userId) {
if (is.image(postMedia.mimetype)) {
fileName = await this.uploadManager.processAndStoreImage(postMedia.data, 1080, 720, "contain");
} else if (is.video(postMedia.mimetype)) {
fileName = await this.uploadManager.processAndStoreVideo(postMedia.data, 1080);
} else {
error = "Wrong type of file provided";
}
if (fileName) {
post.mediaUrl = fileName;
await post.save();
success = true;
}
} else {
error = "You are not the author of the post";
}
} catch (err) {
error = err.message;
globals.logger.error(err.message);
globals.logger.debug(err.stack);
}
} else {
error = "No post Id provided";
}
return {
error,
fileName,
success,
};
}
} }

@ -145,27 +145,13 @@ export class MutationResolver extends BaseResolver {
* @param activityId * @param activityId
* @param request * @param request
*/ */
public async createPost({content, activityId, file}: { content: string, activityId?: number, file: FileUpload }, public async createPost({content, activityId}: { content: string, activityId?: number},
request: any): Promise<Post> { request: any): Promise<Post> {
this.ensureLoggedIn(request); this.ensureLoggedIn(request);
if (content.length > 2048) { if (content.length > 2048) {
throw new GraphQLError("Content too long."); throw new GraphQLError("Content too long.");
} }
const post = await dataaccess.createPost(content, request.session.userId, activityId); const post = await dataaccess.createPost(content, request.session.userId, activityId);
if (file) {
let fileUrl: string;
if (is.video(file.mimetype)) {
const fileBuffer = await this.uploadManager.streamToBuffer(file.createReadStream());
fileUrl = await this.uploadManager.processAndStoreVideo(fileBuffer);
} else if (is.image(file.mimetype)) {
const fileBuffer = await this.uploadManager.streamToBuffer(file.createReadStream());
fileUrl = await this.uploadManager.processAndStoreImage(fileBuffer);
} else {
throw new InvalidFileError(file.mimetype);
}
post.mediaUrl = fileUrl;
await post.save();
}
globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post); globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post);
return post; return post;
} }

@ -85,7 +85,7 @@ type Mutation {
sendMessage(chatId: ID!, content: String!): ChatMessage sendMessage(chatId: ID!, content: String!): ChatMessage
"create a post that can belong to an activity" "create a post that can belong to an activity"
createPost(content: String!, activityId: ID, file: Upload): Post! createPost(content: String!, activityId: ID): Post!
"delete the post for a given post id" "delete the post for a given post id"
deletePost(postId: ID!): Boolean! deletePost(postId: ID!): Boolean!

Loading…
Cancel
Save