En route vers le Lac de l'Ouest après dix ans de mariage
Dix milles après la brise printanière, tous les bergers et le blé sont verts. Le printemps met toujours les gens à l'aise, et ce mois de mars est particulièrement différent car je suis marié à ma femme depuis dix ans. Tous deux ont pris une journée de congé luxueuse, cachant leurs enfants, et sont retournés au Lac de l'Ouest pour retrouver le magasin de glaces d'il y a 13 ans (ils lui ont acheté la glace la plus chère - 8 yuans - pour celle qui était une collègue à l'époque. ), et pour retrouver le magasin de glaces qui vendait il y a 13 ans. L'oncle du porte-clés aux haricots rouges (elle m'a offert un porte-clés aux haricots mungo - pure amitié), est allée s'asseoir sur le même tabouret sur lequel je m'étais assis il y a 13 ans. Alors que j'étais plongé dans des souvenirs romantiques, une personne que je n'avais pas contactée depuis longtemps est arrivée. Un de mes amis a soudainement reçu la nouvelle que nous nous rencontrions à Dazhuhai, Anji. Je pensais que c'était très paisible d'avoir des bambous devant et derrière ma maison dans ma ville natale. Il s'avère que les bambous partout dans les montagnes et les plaines ont aussi une saveur unique. Un groupe d’enfants joue au football sur l’herbe. Regardez, les enfants s’amusent tellement.
La curiosité commence désormais
En discutant, un ami m'a montré un petit programme appelé Qingdou. La fonction d'extraction de copie vidéo m'a attiré. Copiez simplement un lien vers une courte vidéo telle que Douyin ou Kuaishou pour extraire la copie vidéo. Poussés par la curiosité, nous avons commencé un voyage d’exploration. Je n’aurais jamais pensé qu’il serait facile de démarrer mais difficile de lâcher prise.
Après quelques réflexions simples, le processus général a été déterminé, qui est divisé en trois étapes :
Extraire les fichiers vidéo -> Séparation audio -> Audio en texte. Ensuite, j'ai commencé à coder avec bonheur. La réalité m’a vite frappé, et le vieux proverbe du Sichuan qui m’accompagne depuis 30 ans s’est réalisé : pour le dire légèrement, c’est comme un éclair de lumière (le dialecte du Sichuan semble intéressant quand on le lit). La première difficulté est : comment télécharger des vidéos basées sur des liens partagés et prendre en charge diverses plateformes communes. Après avoir essayé pendant un moment, j'ai abandonné. Après tout, je « ne voulais pas faire ça ». Plus tard, j'ai découvert par hasard qu'il existait de nombreuses plates-formes de ce type qui fournissaient des interfaces pour télécharger des vidéos basées sur des URL, alors j'ai simplement utilisé le interface tierce.
Avec le lien vidéo, il est simple de la télécharger localement (il peut cependant y avoir des pièges à des endroits simples), de saisir directement le code et de renvoyer l'InputStream généré par le fichier.
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;
}
Utilisez ensuite javacv pour séparer l'audio. Il n'y a rien de spécial à ce sujet. L'audio séparé est collecté via FFmpegFrameRecorder. Téléchargez également directement le code.
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;
}
tempête avant l'aube
Quand j'ai écrit ceci, je pensais que la victoire était comme le soleil rouge qui était sur le point d'émerger sous les nuages rouges à l'est. Un cas d'utilisation était déjà parfait, et le deuxième cas d'utilisation était parfait. se préparait pour une étape de synthèse vocale, le dernier test a échoué. À cette fin, une longue série de débogages a commencé.
1. Téléchargement et sauvegarde du fichier http - échec de l'analyse - erreur avformat_find_stream_info() : impossible de trouver les informations sur le flux ;
2. Le navigateur n'a pas non plus réussi à enregistrer le fichier ;
3. Le téléchargement et l'analyse de Thunder ont également échoué ;
...
J'ai commencé à soupçonner qu'il y avait un problème avec l'encodage vidéo renvoyé par l'interface tierce ; lorsque le fichier enregistré par Douyin a été analysé avec succès, mes soupçons ont été confirmés. Mais les fichiers enregistrés à l'aide de l'applet WeChat saveVideoToPhotosAlbum peuvent être analysés avec succès... J'ai commencé à douter de moi. Ainsi, divers paramètres ont commencé à être ajustés de manière aléatoire. Après d'innombrables échecs, j'ai eu une idée audacieuse. Si vous ne parvenez pas à analyser celui que j'ai téléchargé, vous pouvez toujours analyser le javaCV que vous avez téléchargé vous-même. Assez sur. Le code ci-dessus a modifié une ligne.
//FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(req.getVideoInputStream());
// 直接传url
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(req.getUrl());
L'étape suivante consiste à appeler l'interface ars de Tencent Cloud en fonction des fichiers audio extraits. Lorsque j'ai déjà utilisé l'interface d'Openai pour implémenter un robot financier interne, j'ai écrit une interface pour convertir la saisie vocale en texte, je l'ai simplement prise et insérée et tout s'est bien passé. L'interface d'une phrase est appelée comme suit. Si cela prend plus d'une minute, appelez simplement l'interface vocale longue. (Remarque : l'interface à une phrase est renvoyée de manière synchrone et la voix longue est un rappel asynchrone)
/**
* @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(), "语音转文字异常,请重试");
}
}
Le discours long en texte est similaire. code affiché comme ci-dessous
/**
* @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 sans fin
Au début, j'étais juste par curiosité pour l'extraction de rédaction, mais je n'ai jamais pensé que je ne pourrais pas m'arrêter de l'écrire. Le backend était implémenté, et c'était un peu regrettable s'il n'y avait pas de présentation front-end. ma femme pour m'aider à créer une interface utilisateur ; et j'ai créé un petit programme simple... après un certain temps, il était enfin en ligne. Les étudiants intéressés peuvent scanner le code pour en faire l'expérience.
Nom du mini-programme : utilitaire de synthèse vocale ;
Mini programme QR code : Aucun code QR n'est autorisé, voici un lien pour une expérience rapide
Linus a pris sur lui d'empêcher les développeurs du noyau de remplacer les tabulations par des espaces. Son père est l'un des rares dirigeants capables d'écrire du code, son deuxième fils est directeur du département de technologie open source et son plus jeune fils est un noyau open source. contributeur. Robin Li : Le langage naturel deviendra un nouveau langage de programmation universel. Le modèle open source prendra de plus en plus de retard sur Huawei : il faudra 1 an pour migrer complètement 5 000 applications mobiles couramment utilisées vers Java, qui est le langage le plus enclin . vulnérabilités tierces. L'éditeur de texte riche Quill 2.0 a été publié avec des fonctionnalités, une fiabilité et des développeurs. L'expérience a été grandement améliorée. Bien que l'ouverture soit terminée, Meta Llama 3 a été officiellement publié. la source de Laoxiangji n'est pas le code, les raisons derrière cela sont très réconfortantes. Google a annoncé une restructuration à grande échelle.