赞
踩
- <!-- Jsoup 解析HTML文本 -->
- <dependency>
- <groupId>org.jsoup</groupId>
- <artifactId>jsoup</artifactId>
- <version>1.11.3</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.4</version>
- </dependency>
- <!-- 主要用JavaCV来处理视频生成 -->
- <dependency>
- <groupId>org.bytedeco</groupId>
- <artifactId>javacv-platform</artifactId>
- <version>1.5.2</version>
- </dependency>
- import org.jsoup.Connection;
- import org.jsoup.Jsoup;
- import org.jsoup.nodes.Document;
- import org.jsoup.nodes.Element;
- import org.jsoup.select.Elements;
- import javax.servlet.http.HttpServletRequest;
-
- /**
- * 根据小红书链接爬取内容
- * @param url
- * @param request
- * @return
- * @throws Exception
- */
- @RequestMapping("/analysis")
- public JsonResp analysis(String url, HttpServletRequest request) throws Exception {
- if (StringUtils.isBlank(url)) {
- return JsonResp.toFail("小红书地址为空");
- }
- if (!url.contains("https://www.xiaohongshu.com") && !url.contains("http://xhslink.com")) {
- return JsonResp.toFail("小红书地址不正确");
- }
- Connection connection = Jsoup.connect(url);
- Connection data = connection.headers(getHeaderMap(request));
- Document doc = data.get();
-
- Map<String, Object> map = new HashMap<>();
- //获取tag是title的所有dom文档
- //标题
- Elements elements = doc.getElementsByTag("title");
- //获取第一个元素
- Element element = elements.get(0);
- //.html是返回html
- String title = element.text();
- map.put("title", title);
- //图片
- Elements elements1 = doc.select("span[class=inner]");
- List<String> list = new ArrayList<>();
- for (Element element1 : elements1) {
- String imageUrl = "http://" + element1.attr("style").replace("background-image:url(//", "").replace(");", "");
- list.add(imageUrl);
- }
- map.put("list", list);
- return JsonResp.ok(map);
- }
-
- /**
- * 小红书请求头设置
- */
- public static Map<String, String> getHeaderMap(HttpServletRequest request) {
- String ip = getIp(request);
- Map<String, String> map = new HashMap<>(new LinkedHashMap<>());
-
- if (StringUtils.isNotEmpty(ip) && !ip.contains("127.0.0.1") && !ip.contains("192.168")) {
- map.put("Accept", "text/html, application/xhtml+xml, image/jxr, */*");
- map.put("Accept-Encoding", "gzip, deflate");
- map.put("x-forwarded-for", ip);
- map.put("Proxy-Client-IP", ip);
- map.put("WL-Proxy-Client-IP", ip);
- map.put("HTTP_CLIENT_IP", ip);
- map.put("HTTP_X_FORWARDED_FOR", ip);
- }
-
- map.put("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
- map.put("accept-encoding", "gzip, deflate, br");
- map.put("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
- map.put("cache-control", "max-age=0");
- map.put("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36");
- return map;
- }
-
- /**
- * 获取客户端ip
- *
- * @param httpServletRequest
- * @return
- */
- public static String getIp(HttpServletRequest httpServletRequest) {
- String ip = httpServletRequest.getHeader("x-forwarded-for");
- if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = httpServletRequest.getHeader("Proxy-Client-IP");
- }
- if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = httpServletRequest.getHeader("WL-Proxy-Client-IP");
- }
- if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = httpServletRequest.getHeader("HTTP_CLIENT_IP");
- }
- if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = httpServletRequest.getHeader("HTTP_X_FORWARDED_FOR");
- }
- if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = httpServletRequest.getRemoteAddr();
- }
-
- ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip.split(",")[0];
- return ip;
- }
JsonResp 是项目里公共的接口返回类型 这里可以直接忽略
- import org.apache.commons.lang3.StringUtils;
- import org.bytedeco.ffmpeg.global.avcodec;
- import org.bytedeco.ffmpeg.global.avutil;
- import org.bytedeco.javacv.*;
- import org.bytedeco.javacv.Frame;
- import org.bytedeco.opencv.global.opencv_core;
- import org.bytedeco.opencv.opencv_core.IplImage;
- import sun.font.FontDesignMetrics;
- import java.awt.*;
- import java.awt.image.BufferedImage;
- import java.io.File;
- import java.math.BigDecimal;
- import java.util.Arrays;
- import java.util.Comparator;
- import java.util.List;
- import java.util.stream.Collectors;
- import static org.bytedeco.opencv.helper.opencv_imgcodecs.cvLoadImage;
-
- /**
- * 视频生成工具类
- *
- * @author fengzi
- * @version 1.0
- * @date 2020/07/03
- */
- public class GenerateVideo {
-
- /**
- * 图片合成视频
- *
- * @param picturesPath 图片文件夹路径
- * @param videoPath 视频存放地址
- * @param second 每隔second秒切换一张图
- * @param audioPath 音频地址
- * @param cover 视频封面 选填
- * @param title 封面标题 选填
- * @param color 标题颜色 选填
- * @param width 视频宽度
- * @param height 视频高度
- * @param audioStart 音频开始位置 秒 选题
- * @throws FrameRecorder.Exception Exception
- */
- public static void createMp4(String picturesPath, String videoPath, int second, String audioPath, File cover, String title, Color color, Integer width, Integer height, Integer audioStart) throws FrameRecorder.Exception {
- FFmpegFrameRecorder recorder = null;
- // int width = 1080;
- // int height = 1440;
- if (width == null) {
- width = 1080;
- }
- if (height == null) {
- height = 1440;
- }
- try {
- int frameRate = 25;
- //读取所有图片
- File file = new File(picturesPath);
- File[] files = file.listFiles();
- if (files == null) {
- files = new File[]{};
- }
- List<File> fileList = Arrays.stream(files).sorted(Comparator.comparing(File::getPath)).collect(Collectors.toList());
- if (cover != null) {
- fileList.add(0, cover);
- }
-
- //视频宽高最好是按照常见的视频的宽高 16:9 或者 9:16
- recorder = new FFmpegFrameRecorder(videoPath, width, height);
- //设置视频编码层模式
- recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
- //设置视频为25帧每秒
- recorder.setFrameRate(frameRate);
- //设置视频图像数据格式
- recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
- recorder.setFormat("mp4");
-
- // 先默认吧,这个应该属于设置视频的处理模式 不可变(固定)音频比特率
- recorder.setAudioOption("crf", "0");
- // 最高质量
- recorder.setAudioQuality(0);
- // 音频比特率
- recorder.setAudioBitrate(192000);
- // 音频采样率
- recorder.setSampleRate(44100);
- // 双通道(立体声)
- recorder.setAudioChannels(2);
- // 音频编/解码器
- recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
- // 关键帧间隔,一般与帧率相同或者是视频帧率的两倍
- recorder.setGopSize(frameRate * 2);
- recorder.start();
- Frame frameImage;
- OpenCVFrameConverter.ToIplImage conveter = new OpenCVFrameConverter.ToIplImage();
- Java2DFrameConverter converter = new Java2DFrameConverter();
- int key = 0;
- for (File f : fileList) {
- String fName = f.getPath();
- if (!fName.contains(".png") && !fName.contains(".jpg")) {
- continue;
- }
- ///图片尺寸调整
- IplImage image = cvLoadImage(fName);
- frameImage = conveter.convert(image);
- BufferedImage read = converter.convert(frameImage);
- int imageType = Java2DFrameConverter.getBufferedImageType(frameImage);
- read = reduceImageScale(read, width, height, imageType);
- if (key == 0 && StringUtils.isNotEmpty(title)) {
- markText(read, title, color);
- }
- key++;
- for (int j = 0; j < frameRate * second; j++) {
- recorder.record(converter.convert(read));
- }
- opencv_core.cvReleaseImage(image);
- }
- if (StringUtils.isEmpty(audioPath)) {
- return;
- }
- System.err.println("------开始录制音频------");
- FrameGrabber audioFrames = new FFmpegFrameGrabber(audioPath);
- audioFrames.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
- audioFrames.start();
-
- //音频开始前的读取出来抛弃掉
- if (audioStart != null && audioStart > 0) {
- for (int j = 0; j <= frameRate * audioStart + frameRate * second; j++) {
- audioFrames.grab();
- }
- }
-
- Frame frameAudio;
- //一秒是25帧 所以要记录25次
- for (int j = 0; j < frameRate * second * key * 1.1; j++) {
- frameAudio = audioFrames.grab();
- recorder.record(frameAudio);
- }
- audioFrames.stop();
- audioFrames.release();
- System.err.println("------结束录制音频------");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- //最后一定要结束并释放资源
- if (recorder != null) {
- recorder.stop();
- recorder.release();
- }
- }
- }
-
- /**
- * 填充图片为png格式,填充部分为透明色
- *
- * @param srcImage 源文件
- * @param destWidth 设置图片宽度
- * @param destHeight 设置图片高度
- * @return BufferedImage
- */
- public static BufferedImage reduceImageScale(final BufferedImage srcImage, int destWidth, int destHeight, int imageType) {
- int oldheight = srcImage.getHeight();
- int oldwidth = srcImage.getWidth();
- if (oldheight == destHeight && oldwidth == destWidth) {
- return srcImage;
- }
- BufferedImage outImage = null;
- try {
- BigDecimal rate = new BigDecimal(destWidth).divide(new BigDecimal(destHeight), 2, BigDecimal.ROUND_HALF_DOWN);
- BigDecimal oldRate = new BigDecimal(oldwidth).divide(new BigDecimal(oldheight), 2, BigDecimal.ROUND_HALF_DOWN);
-
- //新比例比旧的比例高 以高为准
- if (rate.compareTo(oldRate) >= 0) {
- if (oldheight > destHeight) {
- destHeight = oldheight;
- destWidth = new BigDecimal(oldheight).multiply(rate).intValue();
- }
- } else {
- //新比例比旧的比例低 以宽为准
- if (oldwidth > destWidth) {
- destWidth = oldwidth;
- destHeight = new BigDecimal(oldwidth).divide(rate, 0, BigDecimal.ROUND_HALF_DOWN).intValue();
- }
- }
- System.err.println(destWidth + "----" + destHeight);
- outImage = new BufferedImage(destWidth, destHeight, imageType);
- Graphics2D graphics2D = outImage.createGraphics();
- outImage = graphics2D.getDeviceConfiguration().createCompatibleImage(destWidth, destHeight, Transparency.OPAQUE);
- graphics2D.dispose();
- graphics2D = outImage.createGraphics();
-
- // 设置图片居中显示
- graphics2D.drawImage(srcImage, (destWidth - oldwidth) / 2,
- (destHeight - oldheight) / 2, null);
- return outImage;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return srcImage;
- }
-
- /**
- * @param bufImg 图片
- * @param text 最多12个字
- * @param color 颜色
- * @throws Exception 异常
- */
- public static void markText(BufferedImage bufImg, String text, Color color) throws Exception {
- text = filterEmoji(text);
- if (text.length() > 15) {
- text = text.substring(0, 15);
- }
- Font font = new Font("宋体", Font.BOLD, 65);
- Graphics2D g = bufImg.createGraphics();
- g.drawImage(bufImg, bufImg.getWidth(), bufImg.getHeight(), null);
- g.setColor(color);
- g.setFont(font);
- int srcImgWidth = bufImg.getWidth();
- int srcImgHeight = bufImg.getHeight();
-
- FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
- int fontWidth = metrics.charsWidth(text.toCharArray(), 0, text.length());
- int fontHeight = metrics.getHeight();
-
- System.out.println("fontWidth----" + fontWidth + ",fontHeight----" + fontHeight);
- int x = 100;
- if (fontWidth < srcImgWidth) {
- x = (srcImgWidth - fontWidth) / 2;
- }
-
- g.drawString(text, x, (srcImgHeight + fontHeight) / 2 - 15);
- g.dispose();
- }
-
- /**
- * 过滤emoji 或者 其他非文字类型的字符
- *
- * @param source
- * @return
- */
- public static String filterEmoji(String source) {
- if (StringUtils.isBlank(source)) {
- return source;
- }
- StringBuilder buf = null;
- int len = source.length();
- for (int i = 0; i < len; i++) {
- char codePoint = source.charAt(i);
- if (isEmojiCharacter(codePoint)) {
- if (buf == null) {
- buf = new StringBuilder(source.length());
- }
- buf.append(codePoint);
- }
- }
- if (buf == null) {
- return source;
- } else {
- if (buf.length() == len) {
- buf = null;
- return source;
- } else {
- return buf.toString();
- }
- }
- }
-
- private static boolean isEmojiCharacter(char codePoint) {
- return (codePoint == 0x0) || (codePoint == 0x9) || (codePoint == 0xA)
- || (codePoint == 0xD)
- || ((codePoint >= 0x20) && (codePoint <= 0xD7FF))
- || ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))
- || ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF));
- }
- }
videoPath 为视频生成后的地址
该方法可实现图片+文字+音频合成视频
可自定义文本颜色、音频开始位置、图片切换间隔、不同图片尺寸默认上下左右居中不变形
具体参考 https://open.douyin.com 抖音开放平台
主要对接以下功能:
1、账号授权:https://open.douyin.com/platform/doc/6848834666171009035 获取授权码
2、上传视频:https://open.douyin.com/platform/doc/6848798087398295555 获取video_id 下一步创建视频会用到
3、创建视频:https://open.douyin.com/platform/doc/6848798087398328323 创建成功会返回抖音视频id item_id
其他的接口如查询已发布的视频列表 删除视频等可自行查看抖音相关文档
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。