首页 > 其他分享 >短视频配音原来如此简单

短视频配音原来如此简单

时间:2024-04-07 09:11:07浏览次数:26  
标签:视频 cos return 音频 req complexAudioReq 配音 new 原来如此

 

没有Ai不会写代码了

       前两天淘宝购买的IDEA copilot插件的账号不能用,没有Ai的加持感觉不会写代码了。于是启用了尘封好久的通义灵码,也可能是用法不对,总感觉没有 copilot智能,毕竟廖胜于无嘛... 看着又在一行行自动生成的代码,陷入了沉思:我们是Ai的工具,还是Ai是我们工具。最近密集的面试过程中,发现大部分人也没在用,甚至都没听过这样的工具。《劝学》中有云:君子生非异也,善假于物。可能,对我这样普普通通的程序员而言,三位一体全方位拥抱,学习,改造这些工具方是良策。前一篇文章《短视频文案提取的简单实现》提到的文案提取功能,其实也是借住一些工具简单实现了,今天再来聊一聊短视频配音的简单实现。

 

 

探索配音实现

一开始看轻抖小程序上的配音功能,有停顿,有多音字,有语速等配置,顿时感觉挺有意思的。10前年做外卖配送系统时,为了方便提醒配送员抢单,用科大讯飞的TTS实现了订单语音播报,但只是简单的朗读而已。摸索了一番后,了解腾讯云已经有相关TTS接口了,看到腾讯云已经提供的能力时,我感觉基本就是调用一个接口就基本ok了, 隐约看到了我自己在小程序上实现了智能配音功能。事实证明,真是纸上得来终觉浅,绝知此事要躬行。

 

语音合同的核心接口比较简单,就两接口

  • 基础合成(156字以内) - 同步返回
  • 长语音合成(10万字以内)- 异步返回

代码实现上使用策略模式处理不同字数的场景,使用Spring Event 统一同步与异步处理逻辑,音频文件上传到cos方便下载,前端使用setTimeout 轮询查询,核心类图如下:

有了通义灵码的辅助,三下五除二,便以迅雷不及掩耳之势就写好了基础代码。这里贴下基础合成语音的代码,长语音合成就是加了一个回调url的地址,返回的是任务id, 通过回调拿到语音文件的临时地址。

/**
 * @Author: JJ
 * @CreateTime: 2023-11-21  09:49
 * @Description: 腾讯云tts - 一句话接口(150字以下)
 */
@Component
@Slf4j
public class SentenceTtsProcessor implements TtsProcessor {

    private static Credential cred = new Credential(AppConstant.Tencent.asrSecretId, AppConstant.Tencent.asrSecretKey);

    /**
     * @param complexAudioReq
     * @description: tts
     * @author: JJ
     * @date: 11/21/23 09:48
     * @param: [bytes]
     * @return: java.lang.String
     */
    @Override
    public TtsRes run(ComplexAudioReq complexAudioReq) {

        String reqId = complexAudioReq.getRequestId();
        //如果为这。生成uuid
        if (Strings.isBlank(reqId)){
            reqId = UUID.randomUUID().toString();
        }
        log.info("tts - 基础合成 {}", reqId);
        // 实例化一个http选项,可选的,没有特殊需求可以跳过
        HttpProfile httpProfile = new HttpProfile();
        httpProfile.setEndpoint("tts.tencentcloudapi.com");
        // 实例化一个client选项,可选的,没有特殊需求可以跳过
        ClientProfile clientProfile = new ClientProfile();
        clientProfile.setHttpProfile(httpProfile);
        // 实例化要请求产品的client对象,clientProfile是可选的
        TtsClient client = new TtsClient(cred, "ap-shanghai", clientProfile);
        // 实例化一个请求对象,每个接口都会对应一个request对象
        TextToVoiceRequest req = new TextToVoiceRequest();
        req.setText(complexAudioReq.getTtsText());
        req.setSessionId(reqId);
        req.setVolume(complexAudioReq.getVolume().floatValue());
        req.setSpeed(complexAudioReq.getSpeed().floatValue());
        req.setProjectId(88L);
        req.setModelType(1L);
        req.setVoiceType(complexAudioReq.getVoiceTypeId());
        req.setPrimaryLanguage(1L);
        req.setEnableSubtitle(false);
        req.setEmotionCategory(EmotionMap.getEmotion(complexAudioReq.getEmotionCategory()));
        req.setEmotionIntensity(complexAudioReq.getEmotionIntensity());

        try {
            // 返回的resp是一个TextToVoiceResponse的实例,与请求对象对应
            TextToVoiceResponse resp = client.TextToVoice(req);
            log.info("tts - 基础合成完成 SessionId={},req={}", reqId, resp.getRequestId());
            TtsRes ttsRes = TtsRes.builder()
                    .ttsType(TtsTypeEnum.SENTENCE.code())
                    .requestId(resp.getRequestId())
                    .data(resp.getAudio())
                    .build();

            return ttsRes;
        } catch (TencentCloudSDKException e) {
            log.error("一句话tts失败:{}",e);
            throw new BusinessException(SENTENCE_TTS_ERROR.code(), SENTENCE_TTS_ERROR.desc());
        }
    }

    /**
     * @param req
     * @description: filter 根据参数选
     * @author: JJ
     * @date: 3/3/24 18:54
     * @param:
     * @return:
     */
    @Override
    public Boolean filter(ComplexAudioReq req) {
        // 字数小于150
        if (req.getTtsTextLength() < AppConstant.Tencent.Sentence_TTS_Max_Word_Count){
            return true;
        }
        return false;
    }
}

 

收到合成成功的回调后,发送事件,监听器异步处理。

            //发送异步事件,上传cos
            AudioUploadCosEvent uploadCosEvent = AudioUploadCosEvent.builder()
                    .eventTime(System.currentTimeMillis() / 1000)
                    .recordId(usageRecordEntity.getId())
                    .remote(true)
                    .dataUrl(req.getResultUrl()).build();

            applicationContext.publishEvent(uploadCosEvent);

 

事件监听核心逻辑就是上传cos,并修改合成记录状态,代码非常简单,大致如下:

 /**
     * 音频异步上传cos
     * @param event
     */
    @Async
    @EventListener
    public void audioUploadCos(AudioUploadCosEvent event) {
        
            
            // 上传cos
            InputStream inputStream =  null;
            //根据是否远程走不同的逻辑
            if (event.isRemote()){
               //跟url 下载 生成inputStream
                log.info("开始下载音频{} ", updateModel.getId());
                MediaDownloadReq videoReq = new MediaDownloadReq();
                videoReq.setUrl(event.getDataUrl());
                videoReq.setTargetFileSuffix("wav");
                inputStream = mediaDownloader.run(videoReq);

            }else {
                byte[] decodedBytes = Base64.decode(event.getData());
                inputStream = new ByteArrayInputStream(decodedBytes);
            }

            //上传音频到cos
            String yyyyMM = DateUtils.dateFormatDateTime(new Date(), DateUtils.formatyyyyMM);
            String path = "/lp/audio/"+yyyyMM+"/"+updateModel.getId()+".wav";
            OssUploadResponse ossUploadResponse = OSSFactory.build().upload(inputStream, path);
            // 关闭InputStream
            inputStream.close();
            log.info("上传音频到cos完成{}", ossUploadResponse.getUrl());
            // 修改记录状态

    }

 

 

原来还在半山腰

最近在搞2024团队规划,Boss希望我们能预估到大致人日,之前也有过这样的预估实际上每次都预估偏差都不小,毕竟人日可能都在细节上。最后退而求其次,预估下两个月的人日,没有PRD,没有技术方案大概率预估的人日可能只会在半山腰,就如同配音的功能写到这里,我以为已经“会当临绝顶,一览众山小了”,哪知道一山还有一山高。

第一难就是多音字,要得到正确的发音,就需要明确指出发音与声调。比如对于腾讯云的语音合成接口支持 SSML 标记语言,比如我们需要让“长”发音为“zhang”,就需要做这样的标记。

<speak><phoneme alphabet="py" ph="zhang3">长</phoneme></speak>

 

对于后端而言,这个是简单的,通过pinyin4j可以快速找出一段文本中的多音字及其所有读音,几行代码就解决了。

 

 /**
     * 多音字检测
     * @param req
     * @return
     * @throws Exception
     */
    public List<PolyphoneVo> run(PolyphoneQuery req) {
        //遍历字符串,找出多音字
        char[] chars = req.getTtsText().toCharArray();
        List<PolyphoneVo> polyphoneVoList = new ArrayList<>();
        Set<String> polyphoneWordSet = new HashSet<>();
        for (char c : chars) {
            if((c >= 0x4e00)&&(c <= 0x9fbb)) {
                String[] pinyinList = PinyinHelper.toHanyuPinyinStringArray(c);
                if (pinyinList != null && pinyinList.length > 1 && !polyphoneWordSet.contains(c+"")) {
                    PolyphoneVo polyphoneVo = new PolyphoneVo();
                    polyphoneVo.setWord(c+"");
                    polyphoneVo.setReadList(Arrays.asList(pinyinList));
                    polyphoneVoList.add(polyphoneVo);
                    polyphoneWordSet.add(c+"");
                }
            }
        }
        return polyphoneVoList;
    }

 

   

前端难点在于如何让多音字可以点击以及点击后的交互,以及SSML 标记语言替换。给大家来两张效果图,有兴趣的可以脑补下前端实现。主要两个地方注意下:显示文本的数据结构和正则表达式。

 

行文至此,停顿有了,发音正常了,音频文件也有了,播放音频也正常了,只是进度条着实费了一些神,官方没有进度条的实现,最后用 van-slider 模拟实现了。唯一bug是无法获取正确的音时长,正当一筹莫展时,又有一个问题出现了:mp3格式无法正常在小程序中保存文件,官方文档中的描述确实只支持mp4文件。一番斗争后,觉得这个问题更为重要,于是又开始了摸索。

 

音频转视频的意外收获

mp3转成mp4,还是javaCV,有了之前的视频中分离音频的经历,这次就顺利多了。唯一的问题就是:视频文件的祯没有内容,所以是一片黑。于是想着用一张固定的图片做为祯内容。中间最麻烦的是音频与祯如何同步。最后还是与GPT4进行了一次多轮对话才完美了解决了。 用小程序二维码图做为默认的祯,效果如下图。在转换过程中,又根据FFmpegFrameGrabber.getLengthInTime 获取到了音频进长,顺道解决了前面无法解决的问题。真可谓是“无心插柳柳成荫”。代码与上一篇文章中的文案提取的代码基本雷同,就不贴了。

 

写在最后

 

写到这里,坑算是基本都趟过了。虽说实际所花的时间早已远超之前的计划了,好在出来效果还不错,也顺道补充了一些基本见识,也是不错了。再回来Ai辅助编程的话题,Ai知道的东西很多,会越来越多,如果提升个人思维能力,如何利用AI 估计很快会成为大部分的程序员了必修课了。

 

有兴趣的同学可以扫码体验下小程序。

小程序名称 :智能配音实用工具;

小程序二维码 :

 

标签:视频,cos,return,音频,req,complexAudioReq,配音,new,原来如此
From: https://www.cnblogs.com/jijunjian/p/18118366

相关文章

  • AI 驱动强大是视频转换处理软件
    由AI驱动的视频工具包。增强、转换、录制和编辑视频AI驱动的顶级视频工具包。不论是老旧、低质、噪声或模糊的影片/图像,都能升级至4K,稳定抖动的影片,提升帧率至120/240fps,并能以全面GPU加速进行转换、压缩、录制和编辑4K/8K/HDR影片,实现每一帧都具备影院级视觉效果。......
  • 【包远程安装运行】:SpringBoot+Mysql健身房在线预约管理系统源码+运行视频+开发文档(参
    今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的健身房在线预约管理系统,系统分四个角色,管理员,职工、教练、前台用户,各角色功能如下:管理员:系统管理(角色、权限、菜单等)、职工管理、健身会员管理、会员充值管理、健身项目管理、健身百科管理、健身......
  • 一起感受快手、西瓜视频无水印下载的乐趣,把快乐分享
    在这个信息共享的时代,快乐往往来自于与他人的分享。当我们在视频平台上发现有趣、有料的视频时,内心总会涌起一股分享的冲动。然而,水印的存在却常常成为分享的障碍,让我们在分享快乐时,留下了稍许的遗憾。快乐永无止境,小编一定要冲破水印封锁,把快乐分享更多的人,接下来,就让小编来介......
  • B站、微博视频批量下载,轻松实现离线观看!
    在现代社会,信息的传播和获取变得尤为重要。特别是对于那些热衷于在视频平台学习新知识、探索未知领域的人来说,能够随时随地下载这些平台的视频内容,以便在没有网络的情况下也能继续学习,无疑是提高效率的一大助力。现在小编就为大家演示下如何获取吧!1、我们需要准备一个工具“......
  • 强大专业的 AI 营销内容文案创作工具,支持内容一键生成、自动配图、图文转视频等
    推荐一款AI营销内容创作工具,它可以一键生成多种类型的营销内容,如营销文本、配图和短视频等。它可以智能生成大量的营销文案,可适应国内及海外的各类营销平台的风格,覆盖丰富的产品类型。只需输入关键词,便可快速生成原创的软文,可在各大媒体和自媒体平台发布,极大地提高了创作效率......
  • FFmpeg图片与视频相互转换命令
    FFmpeg图片与视频相互转换命令命令简介该命令可以实现对图片和视频之间的相互转换,即:图片转视频和视频转图片。视频转图片命令的格式ffmpeg-i[输入文件][滤镜参数(可选)][输出文件]输入文件指定你要转哪个视频文件。滤镜参数详见FFmpeg滤镜命令简介,并且可以添加编解码......
  • FFmpeg音视频裁剪和合并命令
    FFmpeg音视频裁剪和合并命令命令简介裁剪音视频和合并音视频。合并命令ffmpeg-i[输入文件1]-i[输入文件2]...[滤镜参数][输出文件]输入文件指明输入你要合并的文件,可以有多个输入文件。滤镜参数可以为合并的文件添加滤镜,详细参数查看ffmpeg滤镜命令中的参数。输......
  • 学浪视频怎么录屏保存
    学浪视频平台现在已经限制录屏了,我们该怎么办,可能对于非专业人士已经没有办法了,但是专门研究视频下载技术的人来说,可以通过逆向分析从而达到学浪视频的下载学浪视频下载众多软件中以小浪助手.exe最为出名大家有需要的自己下载一下链接:https://pan.baidu.com/s/1y7vcqILT......
  • Adobe Premiere Pro 2024 v24.3 (macOS, Windows) - 专业视频编辑软件
    AdobePremierePro2024v24.3(macOS,Windows)-专业视频编辑软件Acrobat、AfterEffects、Animate、Audition、Bridge、CharacterAnimator、Dimension、Dreamweaver、Illustrator、InCopy、InDesign、LightroomClassic、MediaEncoder、Photoshop、PremierePro、Adobe......
  • 基于springboot的大学生综合成绩管理系统3(含源码+sql+视频导入教程+文档+PPT)
    ......