【源码分享】视频加水印工具

阿甘 18天前 275

功能介绍

视频添加水印/二维码

TODO

  • 水印/二维码 可拖动范围限制:只允许在主窗口画布内拖动

  • 水印/二维码 跟随主窗体移动

  • 视频预览这个没想好怎么做,如果利用第三方工具播放,就不方便获取视频的显示的区域在哪,因为视频会被缩放。可以考虑使用 ffmpeg 视频流之类的

预览

20240830004000149-image

源码

源码

https://gitea.iioio.com:3000/Crimson/VideoWatermark

主窗体:

import win.ui;
/*DSG{{*/
mainForm = win.form(text="视频添加二维码"; right=519; bottom=759; bgcolor=16777215)
mainForm.add(
    custom={cls="custom"; text="自定义控件"; left=10; top=76; right=510; bottom=668; border=1; clip=1; db=1; dl=1; dr=1; dt=1; z=4};
    edit={cls="edit"; left=10; top=673; right=510; bottom=750; db=1; dl=1; dr=1; edge=1; multiline=1; readonly=1; vscroll=1; z=5};
    importButton={cls="plus"; text="导入视频"; left=14; top=28; right=113; bottom=58; bgcolor=-5197169; border={color=-16777216; radius=3; width=1}; dl=0.03; dr=0.78; dt=1; font=LOGFONT(h=-13); forecolor=16777215; z=1};
    qrcodeButton={cls="plus"; text="导入二维码"; left=130; top=28; right=229; bottom=58; bgcolor=-5197169; border={color=-16777216; radius=3; width=1}; dl=0.25; dr=0.56; dt=1; font=LOGFONT(h=-13); forecolor=16777215; notify=1; z=2};
    saveButton={cls="plus"; text="保存格式"; left=408; top=28; right=507; bottom=58; bgcolor=-5197169; border={color=-16777216; radius=3; width=1}; dl=0.78; dr=0.03; dt=1; font=LOGFONT(h=-13); forecolor=5881621; notify=1; z=3}
)
/*}}*/

mainForm.importButton.skin({
    foreground={
        active=0xFFFFBAB7;
        default=0xFFFFFFFF        
    };
})

mainForm.qrcodeButton.skin({
    foreground={
        active=0xFFFFBAB7;
        default=0xFFFFFFFF        
    };
})

mainForm.saveButton.skin({
    foreground={
        active=0xFFFFBAB7;
        default=0xFF15BF59        
    };
})

import console;
import gdip;
import fsys.dlg;
import process.ffmpeg;

var qrcodeForm; // qrcode winform
var videoPath;
var qrcodePath;
var scaleRatio; // 定义一个全局变量来保存缩放比例
var offsetX; // 视频在 custom 内 x 偏移量
var offsetY; // 视频在 custom 内 y 偏移量

showVideo = function(videoDir){
    var customWidth = mainForm.custom.width;
    var customHeight = mainForm.custom.height;
    var img = gdip.image(videoDir + "0001.png");
    var imgWidth = img.width;
    var imgHeight = img.height;
    
    var aspectRatio = imgWidth / imgHeight;
    var customAspectRatio = customWidth / customHeight;
    
    var newImgWidth, newImgHeight;
    
    if (aspectRatio > customAspectRatio) {
        newImgWidth = customWidth;
        newImgHeight = customWidth / aspectRatio;
        scaleRatio = customWidth / imgWidth; // 保存缩放比例
    } else {
        newImgHeight = customHeight;
        newImgWidth = customHeight * aspectRatio;
        scaleRatio = customHeight / imgHeight; // 保存缩放比例
    }
    
    offsetX = (customWidth - newImgWidth) / 2;
    offsetY = (customHeight - newImgHeight) / 2;
    
    mainForm.custom.add(
        videoDisplay = {
            cls = "plus";
            left = offsetX;
            top = offsetY;
            right = offsetX + newImgWidth;
            bottom = offsetY + newImgHeight;
            background = videoDir + "0001.png";
            z = 6
        }
    );
}

mainForm.importButton.oncommand = function(id,event){
    var videoFilePath = fsys.dlg.open("MP4 files (*.mp4)|*.mp4|其他格式 (*.*)|*||");
    if(videoFilePath){
        var videoFileDetails = io.splitpath(videoFilePath);
        var tempDir = io._exedir+"temp\"+videoFileDetails.name+"\";
        var tempFilePath = tempDir + videoFileDetails.file;
        if(!io.exist(tempDir)){
            io.createDir(tempDir)
        }
        fsys.copy(videoFilePath, tempFilePath);
        videoPath = tempFilePath;
        var ffmpegCommand = `-i `++tempFilePath++` -vf "select=eq(n\,0)" -vframes 1 -y `++tempDir++`0001.png`;
        mainForm.edit.print("ffmpeg command: ", ffmpegCommand);
        var ffmpegProcess = process.ffmpeg(, ffmpegCommand);
        ffmpegProcess.logResponse(mainForm.edit);
        
        ffmpegProcess.onResponseEnd = function(){
            showVideo(tempDir);
        }
    }
}

mainForm.qrcodeButton.oncommand = function(id,event){
    var qrcodeFilePath = fsys.dlg.open("*.png|*.png|其他格式 (*.*)|*||");
    if(qrcodeFilePath){
        var qrcodeFileDetails = io.splitpath(qrcodeFilePath);
        var tempDir = io._exedir+"temp\"+qrcodeFileDetails.name+"\";
        var tempFilePath = tempDir + qrcodeFileDetails.file;
        if(!io.exist(tempDir)){
            io.createDir(tempDir)
        }
        fsys.copy(qrcodeFilePath, tempFilePath);
        qrcodePath = tempFilePath;
        var customWidth = mainForm.custom.width;
        var customHeight = mainForm.custom.height;
        var img = gdip.image(qrcodeFilePath);
        var imgWidth = img.width;
        var imgHeight = img.height;
        
        if(imgWidth > customWidth or imgHeight > customWidth){
            var aspectRatio = imgWidth / imgHeight;
            var customAspectRatio = customWidth / customHeight;
            
            var newImgWidth, newImgHeight;
            
            if (aspectRatio > customAspectRatio) {
                newImgWidth = customWidth / 2;
                newImgHeight = customWidth / 2 / aspectRatio;
            } else {
                newImgHeight = customHeight / 2;
                newImgWidth = customHeight / 2 * aspectRatio;
            }
            imgWidth = newImgWidth;
            imgHeight = newImgHeight;
        }
        
        if(qrcodeForm){
            qrcodeForm.close();
        }
        qrcodeForm = mainForm.loadForm("\dlg\qrcode.aardio");
        publish("getQrcode", qrcodeFilePath, imgWidth, imgHeight);
        qrcodeForm.show();
        qrcodeForm.wndproc = function(hwnd,message,wParam,lParam){
            import mouse
            select(message) {
                case 0x201/*_WM_LBUTTONDOWN*/{
                    //点击左键移动窗体
                    qrcodeForm.hitCaption()
                }
                case 0x46/*_WM_WINDOWPOSCHANGING*/{
                    // 窗口位置即将改变时
                }
                case 0x47/*_WM_WINDOWPOSCHANGED*/{
                    // 窗口位置已经改变时
                }
                case 0x232/*_WM_EXITSIZEMOVE*/{
                    // 用户停止拖动窗口或调整窗口大小时
                }
            }
        }
    }
}

mainForm.saveButton.oncommand = function(id,event){
    var videoRect = mainForm.custom.videoDisplay.getRect();
    var videoScreenRect = win.toScreenRect(mainForm.custom.videoDisplay.hwnd, videoRect);
    var videoLeft = videoScreenRect.left - offsetX;
    var videoTop  = videoScreenRect.top;
    var qrcodeLeft = qrcodeForm.left;
    var qrcodeTop  = qrcodeForm.top;
    var x = (qrcodeLeft - videoLeft) / scaleRatio;
    var y = (qrcodeTop - videoTop) / scaleRatio;
    
    var videoFileDetails = io.splitpath(videoPath);
    var qrcodeFileDetails = io.splitpath(qrcodePath);
    
    var qrcodeCroppedWidth = qrcodeForm.width / scaleRatio;
    var qrcodeCroppedHeight = qrcodeForm.height / scaleRatio;
    var qrcodeOutputPath = io._exedir++"temp\"++ qrcodeFileDetails.name ++ "\" ++ qrcodeFileDetails.name ++ "-output" ++ qrcodeFileDetails.ext;
    
    // ffmpeg -i input.jpg -vf "scale=width:height" output.jpg
    var ffmpegCommand = `-i `++ qrcodePath ++` -vf "scale=`++ qrcodeCroppedWidth ++`:`++ qrcodeCroppedHeight ++`" -y `++qrcodeOutputPath;
    mainForm.edit.print("ffmpeg command: ", ffmpegCommand);
    var ffmpegProcess = process.ffmpeg(, ffmpegCommand);

    ffmpegProcess.logResponse(mainForm.edit);
    ffmpegProcess.onResponseEnd = function(){
        // ffmpeg -i input.mp4 -i overlay.png -filter_complex "overlay=10:10" output.mp4
        var ffmpegCommand = `-i `++ videoPath ++` -i `++ qrcodeOutputPath ++` -filter_complex "overlay=`++ x ++`:`++ y ++ `" -y ` ++ io._exedir ++ videoFileDetails.name ++`-output`++videoFileDetails.ext;
        mainForm.edit.print("ffmpeg command: ", ffmpegCommand);
        var ffmpegProcess = process.ffmpeg(, ffmpegCommand);
        ffmpegProcess.logResponse(mainForm.edit);
        ffmpegProcess.onResponseEnd = function(){
            mainForm.edit.print("完成。");
            mainForm.msgbox("处理完成,在原视频目录下查看 -output后缀")
        }
    }
}
mainForm.show();
return win.loopMessage();

子窗体:

import win.ui;
import win.region.hole;
/*DSG{{*/
var winform = win.form(text="aardio form";right=304;bottom=297;border="none";exmode="none";mode="popup")
winform.add(
plus={cls="plus";left=0;top=0;right=305;bottom=298;ah=1;aw=1;border={color=-65536;width=2};clipBk=false;db=1;dl=1;dr=1;dt=1;transparent=1;z=1}
)
/*}}*/

import console;
import gdip;

subscribe("getQrcode",function(...){
	var qrcodePath, width, height = ...;
	winform.width = width;
	winform.height = height;
	winform.plus.width = width;
	winform.plus.height = height;
	winform.plus.background = qrcodePath;
} )

import win.ui.layered;
win.ui.layered(winform);

import win.ui.simpleWindow;
main = win.ui.simpleWindow(winform,0,0,0);
win.ui.minmax(winform,50,50,500,500);

winform.show();
win.loopMessage();

计划做,但不知道怎么入手的:

想把【水印】放在父窗口内拖动,下图是预想的效果:

由于【水印】窗体用了 win.ui.layered、win.ui.simpleWindow,按照上图的实现方式 直接用 win.setParent(childForm.hwnd, mainForm.hwnd); 会有显示问题,暂时不知道怎么处理。

最新回复 (4)
  • 光庆 17天前
    0 2

  • 阿甘 14天前
    0 3

    process.ffmpeg 没有管理员权限时,无法读取 C 盘目录下的文件。

    规避方案:把源文件复制到有权限访问的目录,缺点--如果源文件很大,这样会增加过多的额外空间占用。

    解决方案(未尝试):参考 bbs.aardio.com 论坛中的一篇文章 任意程序以管理员权限运行


  • Xmzzz 12天前
    0 4

  • 小光芒 10天前
    0 5
    子窗口是 child的就可以啊,开双重缓存
返回