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"
# the max file size for uploading in bytes
maxFileSize = 5_242_880
maxFileSize = 10_485_760
# Configuration for the api rate limit
[api.rateLimit]

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

@ -9,11 +9,11 @@ import * as fsx from "fs-extra";
import * as status from "http-status";
import * as path from "path";
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 {UploadManager} from "../lib/UploadManager";
const ffmpegPath = require("@ffmpeg-installer/ffmpeg").path;
const dataDirName = "data";
interface IUploadConfirmation {
@ -59,7 +59,6 @@ export class UploadRoute extends Route {
super();
this.router = Router();
this.dataDir = path.join(this.publicPath, dataDirName);
ffmpeg.setFfmpegPath(ffmpegPath);
this.uploadManager = new UploadManager();
}
@ -80,6 +79,8 @@ export class UploadRoute extends Route {
uploadConfirmation = await this.uploadProfilePicture(req);
} else if (req.files.groupPicture) {
uploadConfirmation = await this.uploadGroupPicture(req);
} else if (req.files.postMedia) {
uploadConfirmation = await this.uploadPostMedia(req);
} else {
res.status(status.BAD_REQUEST);
uploadConfirmation = {
@ -187,4 +188,49 @@ export class UploadRoute extends Route {
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 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> {
this.ensureLoggedIn(request);
if (content.length > 2048) {
throw new GraphQLError("Content too long.");
}
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);
return post;
}

@ -85,7 +85,7 @@ type Mutation {
sendMessage(chatId: ID!, content: String!): ChatMessage
"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"
deletePost(postId: ID!): Boolean!

Loading…
Cancel
Save