美文网首页flutter面
flutter视频加水印(加处理进度条)

flutter视频加水印(加处理进度条)

作者: _诸葛青 | 来源:发表于2022-01-17 10:37 被阅读0次

用到的插件

 dio: ^4.0.1
ffmpeg_kit_flutter:^4.5.1//视频处理操作
image_gallery_saver: ^1.7.1//保存到相册
path_provider: ^2.0.6 //路径查询

注:用ffmpeg_kit_flutter这个的原因是因为项目用到了video_editor它引了。可以用flutter_ffmpeg,一样的。

加图片的命令:
    String command = "-i " +
        _videoInput +
        " -i " +
        _imagePath +
        " -filter_complex overlay=main_w-overlay_w-20:main_h-overlay_h-20 " +
        _videoOutput +
        "";
_videoInput :视频路径
_imagePath :图片路径
_videoOutput :加了水印后的视频存放路径
加文字的命令:
            String command = "-i " +
                  sss! +
                  " -vf " +
                  "drawtext=fontsize=32:fontcolor=red:text='helloWorld':alpha=0.8 " +
                  output! +
                  "";

这里有个小插曲:就是我文字添加不了,老是报没有drawtext:No such filter: 'drawtext',按网上的方法也不行,无奈放弃。那就换一种实现方式。文字和图片合在一起,生成一张新的图片,再通过加图片的命令去操作。

1、获取水印图片(文字加图片)单纯只加图片的,这一步略过

 Future<Uint8List?> _getWatermark({String? personalId}) async {
    var pictureRecorder = new ui.PictureRecorder(); 
    var canvas = new Canvas(pictureRecorder); 
    var images = await getImage(
      'assets/images/collect_live.png',//assets的图片路径
    );
    Paint _linePaint = new Paint();
    // 绘制图片
    canvas.drawImage(images, Offset(32, 0), _linePaint); // 这个Offset是值可以自己算(0,0起点开始,中间的话就是画布宽度-2*图片的宽度):图片的宽就是分辨率的宽。
    // 绘制文字
    ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
        textAlign: TextAlign.center,
        fontWeight: FontWeight.w400,
        fontStyle: FontStyle.normal,
        fontSize: 12.0));
    pb.pushStyle(ui.TextStyle(color: Colors.white));
    pb.addText('抖音号:1231454');
    // 设置文本的宽度约束
    ParagraphConstraints pc = ui.ParagraphConstraints(width: 100);
    ui.Paragraph paragraph = pb.build()..layout(pc);
    canvas.drawParagraph(paragraph, Offset(0, images.height.toDouble() + 5));
    var picture =
        await pictureRecorder.endRecording().toImage(100, 60); //设置生成图片的宽和高
    var pngImageBytes =
        await picture.toByteData(format: ui.ImageByteFormat.png);

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

2、视频加水印并保存到本地操作

    FFmpegKit.executeAsync(
        command,
        (
          Session session,
        ) async {
          final returnCode = await session.getReturnCode();
          if (ReturnCode.isSuccess(returnCode)) {
            final result = await ImageGallerySaver.saveFile(_videoOutput);
            if (result['isSuccess'] == true) {
              Fluttertoast.showToast(msg: '视频已保存到本地');
            } else if (result['isSuccess'] == false) {
              Fluttertoast.showToast(msg: '视频保存失败');
            }
          } else if (ReturnCode.isCancel(returnCode)) {
            print('取消');
          } else {
            print('错误');
            Fluttertoast.showToast(msg: '视频处理错误');
          }
        });

3、还有一个问题就是进度条。下载网络视频到本地的时间,和视频加水印的处理时间。一般是把这两个时间和在一起显示成一个进度。下面会实现一个简单的进度条样式。
网络视频下载进度回调:


image.png

typedef ProgressCallback = void Function(int count, int total);
视频处理的进度回调:


image.png
回调方法可以拿到时间:getTime() 单位是毫秒

完整代码:

  ///保存视频
  _saveVideo(CommunityTopicEntityEntity items) async {
    late final ValueNotifier<double> _notifier = ValueNotifier<double>(0.0);
    showDialog(
      context: context,
      barrierDismissible: true,
      builder: (BuildContext context) {
        return UnconstrainedBox(
          constrainedAxis: Axis.vertical,
          child: SizedBox(
            width: 120,
            child: Dialog(
                insetPadding: EdgeInsets.zero,
                child: Stack(
                  alignment: Alignment.topRight,
                  children: [
                    Container(
                      height: 80,
                      color: AppColors.black_1f00,
                      child: Center(
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            ValueListenableBuilder(
                              valueListenable: _notifier,
                              builder: (context, double value, child) {
                                return Stack(
                                  alignment: Alignment.center,
                                  children: [
                                    Container(
                                        height: 35,
                                        width: 35,
                                        child: CircularProgressIndicator(
                                          value: value,
                                          backgroundColor: Colors.white,
                                          valueColor:
                                              AlwaysStoppedAnimation<Color>(
                                                  AppColors.black_33),
                                        )),
                                    Text(
                                      "${(value * 100).toStringAsFixed(0)}%",
                                      style: TextStyle(
                                        color: Colors.white,
                                        fontSize: 12.0,
                                        fontWeight: FontWeight.bold,
                                      ),
                                    ),
                                  ],
                                );
                              },
                            ),
                            SizedBox(
                              height: 5,
                            ),
                            Text(
                              '正在保存至本地...',
                              style: TextStyle(
                                  color: AppColors.black_33, fontSize: 12),
                            )
                          ],
                        ),
                      ),
                    ),
                    GestureDetector(
                      onTap: () {
                        Navigator.pop(context);
                      },
                      child: Icon(
                        Icons.close,
                        size: 22,
                        color: AppColors.black_33,
                      ),
                    )
                  ],
                )),
          ),
        );
      },
    );
    Uint8List? pngBytes = await _getWatermark();
    Directory _directory = await getTemporaryDirectory();
//临时路径
    String _imagePath = _directory.path + '/image.png'; //水印图片
    String? _videoInput = _directory.path + '/input.mp4'; //处理的视频
    String? _videoOutput = _directory.path + '/output.mp4'; //得到的视频
    File file = File(_imagePath);
    file.writeAsBytes(pngBytes!);
    double _count = 0;
    double _total = 0;
    await Dio().download(items.content!.video!, _videoInput,
        onReceiveProgress: (count, total) {
      if (total != -1) {
        _count = count.toDouble();
        _total = total + items.content!.duration! * 1000; //总时间
        _notifier.value = _count / _total;
        print('total:$total');
        print((count / total * 100).toStringAsFixed(0) + '%');
      }
    });
    String command = "-i " +
        _videoInput +
        " -i " +
        _imagePath +
        " -filter_complex overlay=main_w-overlay_w-20:main_h-overlay_h-20 " +
        _videoOutput +
        "";
    FFmpegKit.executeAsync(
        command,
        (
          Session session,
        ) async {
          final returnCode = await session.getReturnCode();
          if (ReturnCode.isSuccess(returnCode)) {
            final result = await ImageGallerySaver.saveFile(_videoOutput);
            print(result['isSuccess']);
            if (result['isSuccess'] == true) {
              Navigator.pop(context);
              Fluttertoast.showToast(msg: '视频已保存到本地');
            } else if (result['isSuccess'] == false) {
              Fluttertoast.showToast(msg: '视频保存失败');
            }
          } else if (ReturnCode.isCancel(returnCode)) {
            print('取消');
          } else {
            print('错误');
            Fluttertoast.showToast(msg: '视频处理错误');
          }
        },
        null,
        (statistics) {
          _notifier.value = (_count + statistics.getTime().toDouble()) / _total;
        });
  }

  ///获取视频水印
  Future<Uint8List?> _getWatermark({String? personalId}) async {
    var pictureRecorder = new ui.PictureRecorder(); // 图片记录仪
    var canvas = new Canvas(pictureRecorder); //canvas接受一个图片记录仪
    var images = await getImage(
      'assets/images/collect_live.png',
    );
    Paint _linePaint = new Paint();
    // 绘制图片
    canvas.drawImage(images, Offset(32, 0), _linePaint); // 直接画图
    // 绘制文字
    ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle(
        textAlign: TextAlign.center,
        fontWeight: FontWeight.w400,
        fontStyle: FontStyle.normal,
        fontSize: 12.0));
    pb.pushStyle(ui.TextStyle(color: Colors.white));
    pb.addText('喵职号:1231454');
    // 设置文本的宽度约束
    ParagraphConstraints pc = ui.ParagraphConstraints(width: 100);
    ui.Paragraph paragraph = pb.build()..layout(pc);
    canvas.drawParagraph(paragraph, Offset(0, images.height.toDouble() + 5));
    var picture =
        await pictureRecorder.endRecording().toImage(100, 60); //设置生成图片的宽和高
    var pngImageBytes =
        await picture.toByteData(format: ui.ImageByteFormat.png);

    return pngImageBytes?.buffer.asUint8List();
  }

  //图片转换
  Future<ui.Image> getImage(String asset) async {
    ByteData data = await rootBundle.load(asset);
    Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }
简单使用:
  _saveVideo(widget.items!);
说明:代码中
items.content!.video!替换成自己的视频路径。
items.content!.duration!这个是视频的时间。如果是毫秒就就不要*1000了
Color的得自己替换。

相关文章

网友评论

    本文标题:flutter视频加水印(加处理进度条)

    本文链接:https://www.haomeiwen.com/subject/heqchrtx.html