短いビデオのコピーを抽出するのはとても簡単であることが判明

結婚10年後に西湖へ旅行

       春風が吹いて10マイルも経つと、羊飼いも小麦もすべて青くなっています。春はいつも人々に心地よい気持ちを与えてくれますが、私は妻と結婚して10年になるため、今年の3月は特に違います。二人は子供たちを隠して贅沢な休暇を取り、西湖を再訪して13年前のアイスキャンディー屋を見つけた(当時同僚だった彼女に最も高価な8元のアイスクリームを買ってあげた) )そして、13年前に売ったアイスクリーム屋さんを探しに、小豆のキーホルダーのおじさん(彼女は私に緑豆のキーホルダーをくれました。純粋な友情です)は、13年前に私が座っていたのと同じ椅子に座りに行きました。ロマンチックな思い出に浸っていると、長い間連絡を取っていなかった友人から、安吉の大珠海で会うという知らせが突然届きました。故郷では家の前や裏に竹があるととても平和だと思っていましたが、山や平野にある竹にも独特の味わいがあることが分かりました。子どもたちのグループが芝生の上でサッカーをしています。見てください、子どもたちはとても楽しんでいます。

好奇心はこれから始まる

チャット中に、友人が Qingdou という小さなプログラムを教えてくれました。ビデオのコピーを抽出する機能に惹かれました。 Douyin や Kuaishou などの短いビデオへのリンクをコピーするだけで、ビデオのコピーを抽出できます。好奇心に駆られて、私たちは探検の旅を始めました。始めるのは簡単でも、手放すのは難しいとは思いもしませんでした。

簡単に考えた結果、次の 3 つのステップに分かれる一般的なプロセスが決定されました。

ビデオファイルを抽出 -> 音声分離 -> 音声をテキストに変換します。それから私は楽しくコーディングを始めました。現実はすぐに私に大きな衝撃を与えました。そして、30 年間私の心に残っている四川の古いことわざが現実になりました。「軽く言えば、それは光のラッシュのようなものです(四川弁は読むと面白そうに聞こえます)」。最初の困難は、共有リンクに基づいてビデオをダウンロードし、さまざまな一般的なプラットフォームをサポートする方法です。しばらく試した後、「これはやりたくなかった」と諦めました。その後、URL に基づいてビデオをダウンロードするためのインターフェイスを提供するプラットフォームがたくさんあることを偶然発見したので、それを使用しました。サードパーティのインターフェイス。

ビデオリンクを使用すると、それをローカルにダウンロードし(ただし、簡単な場所に落とし穴がある可能性があります)、コードを直接入力して、ファイルによって生成されたInputStreamを返すのが簡単です。

public InputStream run(MediaDownloadReq req) {
        //根据url获取视频流
        InputStream videoInputStream = null;
        try {
            String newName = "video-"+String.format("%s-%s", System.currentTimeMillis(), UUID.randomUUID().toString())+"."+req.getTargetFileSuffix();

            File folder = new File(tempPath);
            if (!folder.exists()) {
                folder.mkdir();
            }
            File file = HttpUtil.downloadFileFromUrl(req.getUrl(), new File(tempPath +"" + newName+""), new StreamProgress() {
                // 开始下载
                @Override
                public void start() {
                    log.info("Start download file...");
                }
                // 每隔 10% 记录一次日志
                @Override
                public void progress(long total) {
                    //log.info("Download file progress: {} ", total);
                }
                @Override
                public void finish() {
                    log.info("Download file success!");
                }
            });
            videoInputStream = new FileInputStream(file);
            file.delete();
        } catch (Exception e) {
            log.error("获取视频流失败  req ={}", req.getUrl(), e);
            throw new BusinessException(ErrorCodeEnum.DOWNLOAD_VIDEO_ERROR.code(), "获取视频流失败");
        }
        return videoInputStream;
    }

次に、javacv を使用して音声を分離します。分離された音声は FFmpegFrameRecorder を通じて収集されます。コードを直接アップロードすることもできます。

public ExtractAudioRes run(ExtractAudioReq req)  throws Exception {

        long current = System.currentTimeMillis();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        //音频记录器,extractAudio:表示文件路径,2:表示两声道
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputStream, 2);

        recorder.setAudioOption("crf", "0");
        recorder.setAudioQuality(0);
        //比特率
        recorder.setAudioBitrate(256000);
        //采样率
        //recorder.setSampleRate(16000);
        recorder.setSampleRate(8000);
        recorder.setFormat(req.getAudioFormat());
        //音频编解码
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_PCM_S16LE);
        //开始记录
        recorder.start();
    
        //读取视频信息 
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(req.getVideoInputStream());
        grabber.setSampleRate(8000);
        //FFmpegLogCallback.set(); 调试日志
        // 设置采集器构造超时时间(单位微秒,1秒=1000000微秒)
        grabber.setOption("stimeout", String.valueOf(TimeUnit.MINUTES.toMicros(30L)));
        grabber.start();
        recorder.setAudioChannels(grabber.getAudioChannels());
        Frame f;
        Long audioTime = grabber.getLengthInTime() / 1000/ 1000;
        current = System.currentTimeMillis();
        //获取音频样本,并且用recorder记录
        while ((f = grabber.grabSamples()) != null) {
            recorder.record(f);
        }
        grabber.stop();
        recorder.close();

        ExtractAudioRes extractAudioRes = new ExtractAudioRes(outputStream.toByteArray(),  audioTime, outputStream.size() /1024);
        extractAudioRes.setFormat(req.getAudioFormat());

        return extractAudioRes;
    }

夜明け前の嵐

これを書いたとき、私は勝利が東の赤い雲の下に現れようとしている赤い太陽のようなものだと思っていました。それはすでに、1 つの使用例が完璧であり、2 番目の使用例が完璧だったということです。音声テキスト変換ステージの準備をしていましたが、最後の 1 つのテストが失敗しました。この目的を達成するために、長期にわたるデバッグが始まりました。

1. http ダウンロードとファイルの保存 - 解析失敗 - avformat_find_stream_info() エラー: ストリーム情報が見つかりませんでした。

2. ブラウザもファイルの保存に失敗しました。

3. Thunder のダウンロードと解析も失敗しました。

...

Douyin で保存されたファイルが正常に解析されたとき、サードパーティのインターフェイスから返されたビデオ エンコーディングに問題があるのではないかと疑い始めました。その疑いはさらに裏付けられました。しかし、WeChat アプレット saveVideoToPhotosAlbum を使用して保存されたファイルは正常に解析できます...私は自分自身を疑い始めました。そこで、さまざまなパラメータがランダムに調整されるようになりました。数えきれないほどの失敗を経て、私は、ダウンロードした javaCV を解析できない場合でも、自分でダウンロードした javaCV をいつでも解析できるという大胆なアイデアを思いつきました。案の定。上記のコードは 1 行変更されています。


//FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(req.getVideoInputStream());
// 直接传url 
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(req.getUrl());

次のステップでは、抽出された音声ファイルに基づいて Tencent Cloud の ars インターフェイスを呼び出します。以前、Openai のインターフェースを使って社内の財務ロボットを実装したときに、音声入力をテキストに変換するインターフェースを作成して、それをそのまま入れただけでOKでした。 1 文のインターフェイスは次のように呼び出されます。1 分以上かかる場合は、長い音声インターフェイスを呼び出してください。 (注: ワンセンテンス インターフェイスは同期的に戻り、長い音声は非同期コールバックです)

    /**
     * @param audioRecognitionReq
     * @description: 语音转文字
     * @author: jijunjian
     * @date: 11/21/23 09:48
     * @param: [bytes]
     * @return: java.lang.String
     */
    @Override
    public String run(AudioRecognitionReq audioRecognitionReq) {

        log.info("一句话语音语音转文字开始");
        AsrClient client = new AsrClient(cred,  "");
        SentenceRecognitionRequest req = new SentenceRecognitionRequest();
        req.setSourceType(1L);
        req.setVoiceFormat(audioRecognitionReq.getFormat());
        req.setEngSerViceType("16k_zh");
        String base64Encrypted = BaseEncoding.base64().encode(audioRecognitionReq.getBytes());
        req.setData(base64Encrypted);
        req.setDataLen(Integer.valueOf(audioRecognitionReq.getBytes().length).longValue());

        String text = "";
        try {
            SentenceRecognitionResponse resp = client.SentenceRecognition(req);
            log.info("语音转文字结果:{}", JSONUtil.toJsonStr(resp));
            text = resp.getResult();
            if (Strings.isNotBlank(text)){
                return text;
            }
            return "无内容";
        } catch (TencentCloudSDKException e) {
            log.error("语音转文字失败:{}",e);
            throw new BusinessException(AUDIO_RECOGNIZE_ERROR.code(), "语音转文字异常,请重试");
        }
    }

長い音声をテキストに変換する場合も同様です。コードは以下のように表示されます

    /**
     * @param audioRecognitionReq
     * @description: 语音转文字
     * @author: jijunjian
     * @date: 11/21/23 09:48
     * @param: [bytes]
     * @return: java.lang.String
     */
    @Override
    public String run(AudioRecognitionReq audioRecognitionReq) {

        log.info("极速语音转文字开始");
        Credential credential = Credential.builder().secretId(AppConstant.Tencent.asrSecretId).secretKey(AppConstant.Tencent.asrSecretKey).build();
        String text = "";
        try {

            FlashRecognizer recognizer = SpeechClient.newFlashRecognizer(AppConstant.Tencent.arsAppId, credential);
            byte[] data = null;
            if (audioRecognitionReq.getBytes() != null){
                data = audioRecognitionReq.getBytes();
            }else {
                //根据文件路径获取识别语音数据 以后再实现
            }

            //传入识别语音数据同步获取结果
            FlashRecognitionRequest recognitionRequest = FlashRecognitionRequest.initialize();
            recognitionRequest.setEngineType("16k_zh");
            recognitionRequest.setFirstChannelOnly(1);
            recognitionRequest.setVoiceFormat(audioRecognitionReq.getFormat());
            recognitionRequest.setSpeakerDiarization(0);
            recognitionRequest.setFilterDirty(0);
            recognitionRequest.setFilterModal(0);
            recognitionRequest.setFilterPunc(0);
            recognitionRequest.setConvertNumMode(1);
            recognitionRequest.setWordInfo(1);
            FlashRecognitionResponse response = recognizer.recognize(recognitionRequest, data);


            if (SuccessCode.equals(response.getCode())){
                text = response.getFlashResult().get(0).getText();
                return text;
            }
            log.info("极速语音转文字失败:{}", JSONUtil.toJsonStr(response));
            throw new BusinessException(AUDIO_RECOGNIZE_ERROR.code(), "极速语音转换失败,请重试");
        } catch (Exception e) {
            log.error("语音转文字失败:{}",e);
            throw new BusinessException(AUDIO_RECOGNIZE_ERROR.code(), "极速语音转文字异常,请重试");
        }
    }

    /**
     * @param req
     * @description: filter 根据参数选
     * @author: jijunjian
     * @date: 3/3/24 18:54
     * @param:
     * @return:
     */
    @Override
    public Boolean filter(AudioRecognitionReq req) {
        if (req.getAudioTime() == null || req.getAudioTime() >= AppConstant.Tencent.Max_Audio_Len || req.getAudioSize() >= AppConstant.Tencent.Max_Audio_Size){
            return true;
        }
        return false;
    }

終わりなく終わる

最初は興味本位でコピーライティングの抽出をしていたのですが、まさかバックエンドが実装されていて、フロントエンドのプレゼンテーションがないとちょっと残念な気がして、質問させていただきました。妻に UI の構築を手伝ってもらい、私は簡単な小さなプログラムを作成しました。しばらくして、ついにオンラインになりました。興味のある学生はコードをスキャンして体験できます。

ミニ プログラム名: テキスト読み上げユーティリティ。

ミニ プログラム QR コード:  QR コードは使用できません。簡単な体験のためのリンクは次のとおりです。

 

 

 

ライナスは、カーネル開発者がタブをスペースに置き換えるのを防ぐことに自ら取り組みました。 彼の父親はコードを書くことができる数少ないリーダーの 1 人であり、次男はオープンソース テクノロジー部門のディレクターであり、末息子はオープンソース コアです。寄稿者Robin Li: 自然言語 新しいユニバーサル プログラミング言語になるでしょう。オープン ソース モデルは Huawei にますます後れをとっていきます 。一般的に使用されている 5,000 のモバイル アプリケーションを Honmeng に完全に移行するには 1 年かかります。 リッチテキスト エディタ Quill 2.0 リリースされ、機能、信頼性、開発者は「恨みを取り除く ために握手を交わしました。 Laoxiangji のソースはコードではありませんが、その背後にある理由は非常に心温まるものです。Googleは大規模な組織再編を発表しました。
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/jijunjian/blog/11054025
おすすめ