MinIO中视频转换为HLS协议并进行AES加密

发布于:2025-06-29 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、视频切片进行AES加密

  1. win10下载FFmpeg D:\ProgramFiles\ffmpeg
    在这里插入图片描述

  2. 把ffmpeg的绝对路径配置进来

package cn.teaching.jlk.module.infra.framework.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class FFmpegPathResolver {

    @Value("${ffmpeg.path.linux:/usr/bin/ffmpeg}")
    private String linuxPath;

    @Value("${ffmpeg.path.win:D:/ffmpeg/bin/ffmpeg.exe}")
    private String windowsPath;

    public String getFFmpegPath() {
        return System.getProperty("os.name").toLowerCase().contains("win")
                ? windowsPath : linuxPath;
    }
}

  1. 从MinIo下载视频到本地,切片完成之后进行AES加密上传到MinIo
import cn.hutool.core.util.HexUtil;
import cn.teaching.jlk.module.infra.framework.file.core.client.FileClient;
import cn.teaching.jlk.module.infra.framework.utils.FFmpegPathResolver;
import jakarta.annotation.Resource;
import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.builder.FFmpegBuilder;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.util.stream.Stream;

// 4. HLS 切片服务 (异步处理)
@Service
@EnableAsync
public class HlsConversionService {

    @Value("${hls.segment-duration}")
    private int segmentDuration;

    @Autowired
    private FileService fileService;

    @Resource
    private FileConfigService fileConfigService;

    @Resource
    private FFmpegPathResolver ffmpegPathResolver;

    @Async
    public void convertToHls(String sourceObject, String videoId, String keyUri) {
        try {
            // 1. 从MinIO下载原始视频到临时文件
            Path tempDir = Files.createTempDirectory("hls_conversion");
            Path inputFile = tempDir.resolve("source.mp4");
            FileClient client = fileConfigService.getMasterFileClient();
            byte[] fileContent = fileService.getFileContent(client.getId(), sourceObject);
            Files.write(inputFile, fileContent);

            // 2. 创建输出目录
            Path outputDir = tempDir.resolve("output");
            Files.createDirectories(outputDir);

            // 3. 创建密钥文件
            Path keyInfoFile = tempDir.resolve("enc.keyinfo");
            Path keyFile = tempDir.resolve("enc.key");
            String hexKey = generateHexKey();

            // 3.1生成 key 文件
            Files.write(keyFile, getKeyBytes());

            // 3.2生成 keyinfo 文件
            String keyInfoContent = keyUri + "\n" + keyFile.toAbsolutePath().toString() + "\n" + hexKey;
            Files.write(keyInfoFile, keyInfoContent.getBytes());


            // 4. 执行FFmpeg转换
            FFmpeg ffmpeg = new FFmpeg(ffmpegPathResolver.getFFmpegPath());
            FFmpegBuilder builder = new FFmpegBuilder()
                    .setInput(inputFile.toString())
                    .addOutput(outputDir.resolve("playlist.m3u8").toString())
                    .setFormat("hls")
                    .addExtraArgs("-hls_time", String.valueOf(segmentDuration))
                    .addExtraArgs("-hls_key_info_file", keyInfoFile.toString())
                    .addExtraArgs("-hls_playlist_type", "vod")
                    .addExtraArgs("-hls_segment_filename", outputDir.resolve("segment_%03d.ts").toString())
                    .addExtraArgs("-hls_flags", "independent_segments")
                    .addExtraArgs("-g", "48")
                    .addExtraArgs("-sc_threshold", "0")
                    .addExtraArgs("-c:v", "libx264")
                    .addExtraArgs("-c:a", "aac")
                    .addExtraArgs("-b:v", "2000k")
                    .addExtraArgs("-b:a", "128k")
                    .done();

            FFmpegExecutor executor = new FFmpegExecutor(ffmpeg);
            executor.createJob(builder).run();

            // 5. 上传切片文件到MinIO
            uploadHlsFiles(outputDir, videoId);

            // 6. 清理临时文件
            FileUtils.deleteDirectory(tempDir.toFile());

        } catch (Exception e) {
            throw new RuntimeException("HLS conversion failed", e);
        }
    }


    /**
     * 获取 AES 密钥
     * @return
     */
    public byte[] getKeyBytes() {
        String hexKey = "a1b2c3d4e5f678901234567890abcdef";
        return HexUtil.decodeHex(hexKey);
    }


    private String generateHexKey() {
        byte[] key = new byte[16];
        new SecureRandom().nextBytes(key);
        StringBuilder sb = new StringBuilder();
        for (byte b : key) {
            sb.append(String.format("%02x", b & 0xFF));
        }
        return sb.toString();
    }

    private void uploadHlsFiles(Path outputDir, String videoId) {
        try (Stream<Path> paths = Files.walk(outputDir)) {
            paths.filter(Files::isRegularFile)
                    .forEach(file -> {
                        String objectName = videoId + "/" + file.getFileName().toString();
                        try (InputStream is = Files.newInputStream(file)) {
                            fileService.createFile(file.getFileName().toString(),objectName, is.readAllBytes(), false);
                        } catch (Exception e) {
                            throw new RuntimeException("HLS file upload failed", e);
                        }
                    });
        } catch (Exception e) {
            throw new RuntimeException("HLS file upload failed", e);
        }
    }
}

二、防下载技术组合拳

  1. MinIO 桶策略
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::video-bucket/*",
      "Condition": {
        "StringNotLike": {
          "aws:Referer": [
            "https://your-domain.com/*"
          ]
        }
      }
    }
  ]
}
  1. Nginx 代理加固
location ~ \.(m3u8|ts|key)$ {
    proxy_pass http://minio-server;
    
    # 关键防御头
    add_header X-Content-Type-Options "nosniff";
    add_header Content-Disposition "inline";
    add_header Content-Security-Policy "default-src 'self'";
    
    # 限制Range请求(防下载工具)
    if ($http_range) {
        return 416;
    }
}
  1. 动态水印叠加
// 使用FFmpeg实时叠加用户专属水印
FFmpegBuilder builder = new FFmpegBuilder()
    .setInput(inputUrl)
    .addOutput(outputUrl)
    .addExtraArgs("-vf", 
        "drawtext=text='User %{userId}': fontcolor=white@0.5: fontsize=24: box=1: boxcolor=black@0.5: x=10: y=10")
    .done();
  1. 禁用右键/快捷键
<template>
  <div @contextmenu.prevent @keydown.prevent="blockShortcuts">
    <video ref="videoEl" controls></video>
  </div>
</template>

<script>
function blockShortcuts(e) {
  const forbiddenKeys = [67, 83, 85]; // C, S, U
  if (e.ctrlKey && forbiddenKeys.includes(e.keyCode)) {
    e.preventDefault();
    alert('禁止操作');
  }
}
</script>