Extraer una copia corta de un vídeo resulta muy fácil

Viajando al Lago del Oeste después de diez años de matrimonio

       Diez millas después de la brisa primaveral, todos los pastores y el trigo están verdes. La primavera siempre hace que la gente se sienta cómoda y este marzo es particularmente diferente porque llevo diez años casado con mi esposa. Los dos se tomaron un lujoso día libre, escondieron a sus hijos y volvieron a visitar el Lago del Oeste para encontrar la tienda de paletas de hace 13 años (le compraron el helado más caro, 8 yuanes, para ella, que era su colega en ese momento). ), y para encontrar la heladería que vendía hace 13 años, el tío del llavero de frijol rojo (me regaló un llavero de frijol mungo - pura amistad), fue a sentarse en el mismo taburete en el que yo me senté hace 13 años. Justo cuando estaba inmerso en recuerdos románticos, apareció una persona con la que no había contactado durante mucho tiempo. Un amigo mío recibió de repente la noticia de que nos encontraríamos en Dazhuhai, Anji. Solía ​​​​pensar que era muy tranquilo tener bambúes delante y detrás de mi casa en mi ciudad natal. Resulta que los bambúes de todas las montañas y llanuras también tienen un sabor único. Un grupo de niños está jugando al fútbol en el césped. Mira, los niños se divierten mucho.

La curiosidad comienza a partir de ahora.

Mientras charlaba, un amigo me mostró un pequeño programa llamado Qingdou. La función de extraer copias de video me atrajo. Simplemente copie un enlace a un video corto como Douyin o Kuaishou para extraer la copia del video. Impulsados ​​por la curiosidad, iniciamos un viaje de exploración. Nunca pensé que sería fácil empezar pero difícil dejarlo ir.

Luego de unas simples reflexiones, se determinó el proceso general, el cual se divide en tres pasos:

Extraiga archivos de video -> Separación de audio -> Audio a texto. Luego comencé a codificar felizmente. La realidad pronto me golpeó fuerte y se hizo realidad el viejo proverbio de Sichuan que ha estado conmigo durante 30 años: Para decirlo a la ligera, es como un rayo de luz (el dialecto de Sichuan suena interesante cuando lo lees). La primera dificultad es: cómo descargar videos basados ​​en enlaces compartidos y admitir varias plataformas comunes. Después de intentarlo por un tiempo, me di por vencido. Después de todo, "no quería hacer esto". Más tarde, descubrí accidentalmente que existen muchas plataformas de este tipo que proporcionan interfaces para descargar videos basadas en URL, así que simplemente usé el interfaz de terceros.

Con el enlace del video, es sencillo descargarlo localmente (sin embargo, puede haber problemas en lugares simples), ingresar directamente el código y devolver el InputStream generado por el archivo.

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;
    }

Luego use javacv para separar el audio. Esto no tiene nada de especial. El audio separado se recopila a través de FFmpegFrameRecorder. También cargue directamente el código.

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;
    }

tormenta antes del amanecer

Cuando escribí esto, pensé que la victoria era como el sol rojo que estaba a punto de emerger bajo las nubes rojas en el este. Ya estaba infinitamente cerca. Un caso de uso era perfecto y el segundo caso de uso era perfecto. Me estaba preparando para una etapa de conversión de voz a texto, la última prueba falló. Con este fin, se inició una prolongada ronda de depuración.

1. Descarga http y guarda el archivo - Error al analizar - Error avformat_find_stream_info(): No se pudo encontrar la información de la transmisión;

2. El navegador tampoco pudo guardar el archivo;

3. La descarga y el análisis de Thunder también fallaron;

...

Comencé a sospechar que hay un problema con la codificación de video devuelta por la interfaz de terceros cuando el archivo guardado de Douyin se analizó exitosamente, mi sospecha se confirmó aún más; Pero los archivos guardados con el subprograma WeChat saveVideoToPhotosAlbum se pueden analizar con éxito... Empecé a dudar de mí mismo. Entonces se comenzaron a ajustar varios parámetros al azar. Después de innumerables fracasos, se me ocurrió una idea audaz. Si no puedes analizar el que descargué, siempre puedes analizar el javaCV que descargaste tú mismo. Bastante seguro. El código anterior ha modificado una línea.


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

El siguiente paso es llamar a la interfaz ars de Tencent Cloud en función de los archivos de audio extraídos. Cuando usé la interfaz de Openai para implementar un robot financiero interno antes, escribí una interfaz para convertir la entrada de voz en texto, simplemente la tomé y la instalé y estuvo bien. La interfaz de una frase se llama de la siguiente manera. Si tarda más de un minuto, simplemente llame a la interfaz de voz larga. (Nota: la interfaz de una oración regresa sincrónicamente y la voz larga es una devolución de llamada asincrónica)

    /**
     * @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(), "语音转文字异常,请重试");
        }
    }

El discurso largo al texto es similar. El código se muestra a continuación.

    /**
     * @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;
    }

fin sin fin

Al principio, solo tenía curiosidad por la extracción de redacción publicitaria, pero nunca pensé que no podría dejar de escribirlo. Se implementó el backend y me sentí un poco arrepentido si no había una presentación frontal. esposa para ayudarme a construir una interfaz de usuario e hice un pequeño programa simple... después de un tiempo, finalmente estaba en línea. Los estudiantes interesados ​​pueden escanear el código para experimentarlo.

Nombre del miniprograma: Utilidad de texto a voz;

Código QR del mini programa:  no se permiten códigos QR, aquí hay un enlace para una experiencia rápida

 

 

 

Linus se encargó de evitar que los desarrolladores del kernel reemplazaran las pestañas con espacios. Su padre es uno de los pocos líderes que puede escribir código, su segundo hijo es el director del departamento de tecnología de código abierto y su hijo menor es un núcleo de código abierto. Colaborador Robin Li: El lenguaje natural se convertirá en un nuevo lenguaje de programación universal. El modelo de código abierto se quedará cada vez más atrás de Huawei: tomará 1 año migrar completamente 5,000 aplicaciones móviles de uso común a Hongmeng, que es el lenguaje más propenso. Vulnerabilidades de terceros. Se lanzó el editor de texto enriquecido Quill 2.0 con características, confiabilidad y experiencia de desarrolladores que Ma Huateng y Zhou Hongyi se dieron la mano para "eliminar los rencores". La fuente de Laoxiangji no es el código, las razones detrás de esto son muy conmovedoras. Google anunció una reestructuración a gran escala.
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/jijunjian/blog/11054025
Recomendado
Clasificación