美文网首页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