import fs from 'fs'; import os from 'os'; import path from 'path'; import crypto from 'crypto'; import FFmpeg, { Video } from '../node-ffmpeg'; import { Resolution } from '../UGCPublishTypeDef'; export class Exif { // Constants used for the Orientation Exif tag. public static ORIENTATION_UNDEFINED = 0; public static ORIENTATION_NORMAL = 1; public static ORIENTATION_FLIP_HORIZONTAL = 2; // left right reversed mirror public static ORIENTATION_ROTATE_180 = 3; public static ORIENTATION_FLIP_VERTICAL = 4; // upside down mirror // flipped about top-left <--> bottom-right axis public static ORIENTATION_TRANSPOSE = 5; public static ORIENTATION_ROTATE_90 = 6; // rotate 90 cw to right it // flipped about top-right <--> bottom-left axis public static ORIENTATION_TRANSVERSE = 7; public static ORIENTATION_ROTATE_270 = 8; // rotate 270 to right it } export class Rotate { public static ROTATE_90 = '-vf transpose=1'; // 顺时针旋转90° public static ROTATE_180 = '-vf rotate=PI'; // 顺时针旋转180° public static ROTATE_270 = '-vf transpose=2'; // 顺时针旋转270° public static getValue(orientation: number) { if (orientation == Exif.ORIENTATION_ROTATE_90) return Rotate.ROTATE_90; if (orientation == Exif.ORIENTATION_ROTATE_180) return Rotate.ROTATE_180; if (orientation == Exif.ORIENTATION_ROTATE_270) return Rotate.ROTATE_270; } } export async function GetGifInfo(ffmpegPath: string | undefined, file: string) { let f = await new FFmpeg(file, ffmpegPath); let w: number = (f.metadata as any).video.resolution.w; let h: number = (f.metadata as any).video.resolution.h; let size = fs.statSync(file).size; return [{ file: file, fileSize: size, width: w, height: h, }]; } /** * 计算md5值 * @param file */ export function GetMd5(file: string): Promise { return new Promise((resolve, reject) => { let hash = crypto.createHash('md5'); let stream = fs.createReadStream(file); stream.on('data', (buffer) => { hash.update(buffer); }); stream.on('end', () => { resolve(hash.digest('hex').toUpperCase()); }); stream.on('error', err => { reject(err); }) }); } /** * 计算文件流的Md5 * */ export async function GetMd5Buffer(buffer: Uint8Array): Promise { if (buffer == undefined || buffer.length == 0){ return null; } return crypto.createHash('md5').update(buffer).digest('hex').toUpperCase(); } /** * 计算md5值 * @param buf */ export function GetMd5FromBuffer(buf: Buffer) { // let hash = new Md5(); let hash = crypto.createHash('md5'); hash.update(buf); return hash.digest('hex'); } /** 计算准确的分辨率 */ export async function MakeResulotionsDefinite(ffmpegPath: string | undefined, file: string, resolutions: Resolution[]) { if (resolutions.length == 0) return []; let f = await new FFmpeg(file, ffmpegPath); let width: number = (f.metadata as any).video.resolution.w; let height: number = (f.metadata as any).video.resolution.h; let res = resolutions.map((resolution, i) => { let dstW = width; let dstH = height; if (resolution.name == 'Original' || resolution.name == 'original') { ; } else { if (resolution.height != null) { dstH = parseInt(resolution.height + ''); dstW = parseInt(dstH * width / height + ''); } else if (resolution.scale != null) { dstH = parseInt(resolution.scale * height + ''); dstW = parseInt(resolution.scale * width + ''); } } return { w: dstW, h: dstH }; }); return res; } /** * 图片转码 * @param file * @param resolutions */ export async function TransformImage(ffmpegPath: string | undefined, file: string, resolutions: Resolution[]) { if (resolutions.length == 0) return []; let tempdir = path.join(os.tmpdir(), 'wl_temp/'); try { let stats = fs.statSync(tempdir); if (!stats.isDirectory()) { fs.mkdirSync(tempdir); } } catch (e) { fs.mkdirSync(tempdir); } let buf = fs.readFileSync(file); let bufsize = buf.length; let orientation = getOrientation(buf); let rotateStr = Rotate.getValue(orientation); let f = await new FFmpeg(file, ffmpegPath); let width: number = (f.metadata as any).video.resolution.w; let height: number = (f.metadata as any).video.resolution.h; if (orientation == Exif.ORIENTATION_ROTATE_90 || orientation == Exif.ORIENTATION_ROTATE_270) { let temp = width; width = height; height = temp; } let arr = file.split(/\/|\\/); let basename = arr[arr.length - 1].split('.')[0]; return await Promise.all(resolutions.map(async (resolution, index) => { let dstW = width; let dstH = height; let fileName = `temp_${basename}_${index}.jpg`; let targetFileName = `temp_${basename}_${index}_1.jpg`; let targetPath:string; if (resolution.name == 'Original' || resolution.name == 'original') { targetPath = file; return { name: resolution.name, file: targetPath, fileSize: bufsize, width: width, height: height } } else { targetPath = tempdir + targetFileName; if (resolution.height != null) { dstH = parseInt(resolution.height + ''); dstW = parseInt(dstH * width / height + ''); } else if (resolution.scale != null) { dstH = parseInt(resolution.scale * height + ''); dstW = parseInt(resolution.scale * width + ''); } let imgSize = `${dstW}x${dstH}`; await new Promise((resolve, reject) => { f.fnExtractFrameToJPG(tempdir, { number: 1, size: imgSize, file_name: fileName, padding_color: 'jpg-only', quality: 2, rotate: rotateStr }, (err, files) => { if (err) reject(err); else resolve(files); }); }); let size = fs.statSync(tempdir + targetFileName).size; return { name: resolution.name, file: tempdir + targetFileName, fileSize: size, width: dstW, height: dstH, }; } })); } /** * 获取封面图片的路径 * @param file */ export async function GetCoverImage(ffmpegPath: string | undefined, file: string): Promise { //let platform = os.platform(); let video = await new FFmpeg(file, ffmpegPath); let tempdir: string = path.join(os.tmpdir(), 'wl_temp/'); try { let stats = fs.statSync(tempdir); if (!stats.isDirectory()) { fs.mkdirSync(tempdir); } } catch (e) { fs.mkdirSync(tempdir); } try { let files = await new Promise((resolve, reject) => { video.fnExtractFrameToJPG(tempdir, { number: 1, file_name: 'temp_cover.jpg', padding_color: 'jpg-only', quality: 2 }, (err, files) => { if (err) { reject(err); } else { resolve(files); } }); }); return path.join(tempdir, 'temp_cover_1.jpg'); //return fs.readFileSync(tempdir + '/temp_cover_1.jpg'); } catch (err) { console.error(err); throw err; } } export function getOrientation(dataBuf: Buffer): number { let dataView = new DataView(dataBuf.buffer); let length = dataBuf.byteLength; let orientation = 0; let littleEndian: boolean = false; let app1Start: number | undefined; let ifdStart: number | undefined; let offset = 0; let needBytes = 2; if (offset + needBytes < length) { // Only handle JPEG image (start by 0xFFD8) let tag = dataView.getUint16(offset, false); if (tag == 0xFFD8) { // SOI offset = 2; needBytes = 2; while (offset + needBytes < length) { tag = dataView.getUint16(offset, false); if (tag == 0xFFD9) { // EOI break; } else if (tag == 0xFFDA) { // SOS break; } else if (tag == 0xFFE1) { app1Start = offset; break; } else { // get tag length; offset += 2; needBytes = 2; if (offset + needBytes < length) { let tag_len = dataView.getUint16(offset, false); offset += tag_len; } } } } } if (app1Start) { let exifIDCode = app1Start + 4; let tiffOffset = app1Start + 10; if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') { let endianness = dataView.getUint16(tiffOffset); littleEndian = endianness === 0x4949; if (littleEndian || endianness === 0x4D4D /* bigEndian */) { if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) { let firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian); if (firstIFDOffset >= 0x00000008) { ifdStart = tiffOffset + firstIFDOffset; } } } } } if (ifdStart) { length = dataView.getUint16(ifdStart, littleEndian); for (let i = 0; i < length; i++) { offset = ifdStart + i * 12 + 2; if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) { // 8 is the offset of the current tag's value offset += 8; // Get the original orientation value orientation = dataView.getUint16(offset, littleEndian); break; } } } return orientation; } function getStringFromCharCode(dataView: DataView, start: number, length: number) { var str = ''; var i; for (i = start, length += start; i < length; i++) { str += String.fromCharCode(dataView.getUint8(i)); } return str; }