flutter中使用ffmpeg_kit_flutter依赖 完成水印功能并保存到本地

介绍

本篇文章使用了ffmpeg_kit_flutter,Image,image_picker,path_provider等依赖,主要实现了给视频,图片添加水印的功能,该添加水印不会随着视频,图片的格式,分辨率的变化而变化。在此之前在网上查找过相关资料没有找到相应的文章,为此本人自己发表一下该相关的文章吧。---------本人能力不是特别好,有能力的可以试着调一下,主要提供一下大概方向把

提前需要准备的方法

其中的ui和img是这样的。


import 'dart:ui' as ui;

import 'package:image/image.dart' as img;

制作水印图片的方法

该方法中的位置设置都是设置好的,位置是右下角(该是获取的手机屏幕的分辨率来调整的)



static Future<Uint8List?> _getWatermark({String? id, String? name}) async {
    var imgWidth = ui.window.physicalSize.width;
    var pictureRecorder = ui.PictureRecorder();
    var canvas = Canvas(pictureRecorder);
    var images = await getImage("" //assets的图片路径
    );
    var width = images.width;
    Paint _linePaint = Paint()..color = const Color.fromRGBO(0, 0, 0, 1);
    // 绘制图片
    canvas.drawImage(images, Offset(imgWidth - width - 21, 0),
        _linePaint); // 这个Offset是值可以自己算(0,0起点开始,中间的话就是画布宽度-2*图片的宽度):图片的宽就是分辨率的宽。
    // 绘制文字
    ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
      textAlign: TextAlign.right,
      fontWeight: FontWeight.w500,
      fontStyle: FontStyle.normal,
      fontSize: 24.0.px,
    ));
    pb.pushStyle(ui.TextStyle(color: Colors.white));
    pb.addText("$name:$id");

    ///计算数据的长度
    var textPainter = TextPainter(
      text: TextSpan(
        text: "$name:$id",
        style: TextStyle(fontSize: 24.px, fontWeight: FontWeight.w500),
      ),
      textDirection: TextDirection.ltr,
      textAlign: TextAlign.left,
      textScaleFactor: 1.0,
    );
    textPainter.layout();
    var textImgWidth = textPainter.width;
    // 设置文本的宽度约束
    var pc = ui.ParagraphConstraints(width: imgWidth - 23);
    ui.Paragraph paragraph = pb.build()..layout(pc);
    canvas.drawParagraph(paragraph, Offset(-5, images.height.toDouble() + 20));

    ///判断名称的长度
    if (imgWidth - 23 <= textImgWidth) {
      picture = await pictureRecorder
          .endRecording()
          .toImage(imgWidth.toInt() - 10, (images.height + 102)); //设置生成图片的宽和高

      var pngImageBytes =
          await picture.toByteData(format: ui.ImageByteFormat.png);
      return pngImageBytes?.buffer.asUint8List();
    } else {
      picture = await pictureRecorder
          .endRecording()
          .toImage(imgWidth.toInt() - 10, (images.height + 79)); //设置生成图片的宽和高
      var pngImageBytes =
          await picture.toByteData(format: ui.ImageByteFormat.png);
      return pngImageBytes?.buffer.asUint8List();
    }
  }

  //图片转换
  static Future<ui.Image> getImage(String asset) async {
    ByteData data = await rootBundle.load(asset);
    var codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    var fi = await codec.getNextFrame();
    return fi.image;
  }

图片添加水印

给图片添加水印使用到了ffmpeg的命令,除此之外还需要获取图片的分辨率,之后保存到本地。

该代码是ffmpeg的主要命令,其中的url,_imagePath分别是网络图片的路径跟水印图片,

${appDocDir.path}/${time}.png呢是图片保存的地址。
String command = "-i " +
          url +
          " -i " +
          " $_imagePath " +
          " -filter_complex [1:v]scale=${width * imageWH}:${height * imageWH}[wm];[0:v][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10 -qscale:v 1 " +
          " ${appDocDir.path}/${time}.png";

这个是完整的图片添加水印代码:(文章中的_getCurrentTime是写的一个获取当前时间的代码)

 static var picture;//这个属性是接收水印图片的大小
 static Future<String?> _saveNetworkImage(String url,
      {String? userId, String? nickName}) async {
    //userId跟nickName是我需要水印的id和名称
    if (userId != null && nickName != null) {
      //该是获取缓存临时路径
      var appDocDir = await getTemporaryDirectory();
      //该方法是使用了画笔生成的图
      Uint8List? pngBytes = await _getWatermark(id: userId, name: nickName);
      //自己创建的水印图片的路径
      String _imagePath = appDocDir.path + '/syimage.png'; //水印图片
      File file = File(_imagePath);
      //将水印图片保存到_imagePath 中
      await file.writeAsBytes(pngBytes!);
      //该方法是获取图片的分辨率
      var getNetworkImageSize = await _getNetworkImageSize(url);
      //水印图片的宽高
      var width = picture.width;
      var height = picture.height;
      //网络图片的宽高
      var imageWidth = getNetworkImageSize.width;
      var imageHeight = getNetworkImageSize.height;
      //获取最小的宽高比的值,用来调整水印的大小
      var imageWH = imageWidth / width > imageHeight / height
          ? imageHeight / height
          : imageWidth / width;

      var time = _getCurrentTime();
      ///ffmpeg命令生成图片水印
      String command = "-i " +
          url +
          " -i " +
          " $_imagePath " +
          " -filter_complex [1:v]scale=${width * imageWH}:${height * imageWH}[wm];[0:v][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10 -qscale:v 1 " +
          " ${appDocDir.path}/${time}.png";

      //执行ffmpeg命令,并继续其他操作
      await FFmpegKit.executeAsync(command, (session) async {
        final returnCode = await session.getReturnCode();
        //ffmpeg命令执行是否成功。returnCode = 0成功:1失败
        if (ReturnCode.isSuccess(returnCode)) {
          //保存到本地---自己在网上查
        } else {
          //ffmpeg命令执行失败
        }
      });
    } else {
        //如果没有传递userId和nickName直接保存
    }
  }

视频添加水印

给视频添加水印跟着图片呢是差不多的,步骤都是一样的,ffmpag命令也没有差太多,可以说是一样的,接下来上ffmpeg命令代码:

String command = "-i " +
          savePath +
          " -i " +
          " $_imagePath " +
          "  -filter_complex [1:v]scale=${width * imageWH}:${height * imageWH}[wm];[0:v][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10 " +
          //这一行好像是提高视频的质量的,不是特别清楚,自己可以看看
          "  -acodec copy  -q:v 0  -q:a 0 " +
          "  ${appDocDir.path}/${time}.mp4";

接下来就是完整的给视频添加水印了:

视频添加水印是需要获取视频的分辨率的,使用到的方法是VideoPlayerController。

static Future<String?> _saveNetworkVideoFile(String videoUrl,
      {String? userId, String? nickName}) async {
    var appDocDir = await getTemporaryDirectory();
    String savePath = "${appDocDir.path}/${_getCurrentTime()}.mp4";
    debugPrint("---------resultsavePath=$savePath");
    //下载视频videoUrl是视频路径,savePath是将视频保存到的地址
    await Dio().download(videoUrl, savePath, onReceiveProgress: (count, total) {
      debugPrint("---------${(count / total * 100).toStringAsFixed(0)}%");
    });

    if (userId != null && nickName != null) {
      var videoWidth;
      var videoHeight;
      final File localVideoFile = File(savePath);

      // 初始化VideoPlayerController
      VideoPlayerController _controller =
          VideoPlayerController.file(localVideoFile);

      // 确保视频初始化完成
      await _controller.initialize();
      // 视频加载完元数据后,可以通过value.format.description获取宽高
      if (_controller.value.isInitialized) {
        videoWidth = _controller.value.size.width.toInt();
        videoHeight = _controller.value.size.height.toInt();
        print('Local Video Resolution: $videoWidth x $videoHeight');
      }

      var width = picture.width;
      var height = picture.height;
      var imageWH = videoWidth / width > videoHeight / height
          ? videoHeight / height
          : videoWidth / width;

      var time = _getCurrentTime();
      Uint8List? pngBytes = await _getWatermark(id: userId, name: nickName);
      String _imagePath = appDocDir.path + '/syimage.png'; //水印图片
      File file = File(_imagePath);
      file.writeAsBytes(pngBytes!);

      ///ffmpeg命令生成图片水印
      String command = "-i " +
          savePath +
          " -i " +
          " $_imagePath " +
          "  -filter_complex [1:v]scale=${width * imageWH}:${height * imageWH}[wm];[0:v][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10 " +
          "  -acodec copy  -q:v 0  -q:a 0 " +
          "  ${appDocDir.path}/${time}.mp4";

      await FFmpegKit.executeAsync(command, (session) async {
        final returnCode = await session.getReturnCode();
        if (ReturnCode.isSuccess(returnCode)) {
          //ffmpeg命令执行成功,保存到本地----自己上网找
        } else {
          //ffmpeg命令执行失败
        }
      });
    } else {
        //没有userId和nickName,直接保存到本地(不添加水印)
    }
  }

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
首先,需要在pubspec.yaml文件file_saver插件的依赖: ```yaml dependencies: file_saver: ^2.0.3 ``` 然后,在需要保存图片的地方,可以使用如下代码: ```dart import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:file_saver/file_saver.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:flutter/services.dart'; class SaveImagePage extends StatefulWidget { final String imageUrl; SaveImagePage({Key key, @required this.imageUrl}) : super(key: key); @override _SaveImagePageState createState() => _SaveImagePageState(); } class _SaveImagePageState extends State<SaveImagePage> { Uint8List imageBytes; bool isSaving = false; @override void initState() { super.initState(); loadImage(); } Future<void> loadImage() async { final ByteData imageData = await NetworkAssetBundle(Uri.parse(widget.imageUrl)).load(""); setState(() { imageBytes = imageData.buffer.asUint8List(); }); } Future<void> saveImage() async { setState(() { isSaving = true; }); final result = await ImageGallerySaver.saveImage(imageBytes); setState(() { isSaving = false; }); if (result != null) { // 保存成功后,分享到QQ FileSaver.instance.shareFile(result['filePath'], 'image/*', text: '分享图片'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Save Image'), ), body: imageBytes != null ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.memory( imageBytes, width: MediaQuery.of(context).size.width, fit: BoxFit.contain, ), SizedBox(height: 20), RaisedButton( onPressed: isSaving ? null : saveImage, child: isSaving ? CircularProgressIndicator( valueColor: AlwaysStoppedAnimation<Color>(Colors.white), ) : Text('Save and Share'), ), ], ) : Center( child: CircularProgressIndicator(), ), ); } } ``` 上面的代码,我们首先使用`NetworkAssetBundle`图片,然后在`saveImage`方法,调用`ImageGallerySaver.saveImage`方法将图片保存到相册,并返回保存成功后的结果。最后,我们调用`FileSaver.instance.shareFile`方法将图片分享到QQ。 需要注意的是,为了能够进行文件分享,我们需要在AndroidManifest.xml文件以下权限: ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> ``` 同时,还需要在AndroidManifest.xml文件以下provider: ```xml <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ``` 其,file_paths.xml文件内容如下: ```xml <?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="."/> </paths> ``` 最后,在调用`FileSaver.instance.shareFile`方法时,需要传递正确的MIME类型,例如分享图片时,MIME类型应该为'image/*'。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值