恢复
@@ -1,129 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
@@ -1,129 +1,81 @@
|
||||
[`English`](https://github.com/TheRamU/Fay/blob/main/README_EN.md)
|
||||
|
||||
<div align="center">
|
||||
<br>
|
||||
<img src="images/icon.png" alt="Fay">
|
||||
<h1>FAY</h1>
|
||||
<h3>Fay数字人框架 助理版</h3>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
助理版是Fay 数字人框架最常用的版本。它提供了灵活的模块化设计,使开发人员能够定制和组合各种功能模块,包括情绪分析、NLP处理、语音合成和语音输出等。助理版构建的是一问(远程或本地,移动或PC,语音或文字)一答(数字人或机器,移动或PC,语音或文字或RPA)的标准模式。
|
||||
|
||||
|
||||
|
||||
如果你需要是一个线上线下的销售员,请移步[`带货完整版`](https://github.com/xszyou/Fay/tree/fay-sales-edition)
|
||||
|
||||
如果你需要是一个可以自主决策、主动联系主人的agent,请移步[`agent版`](https://github.com/xszyou/Fay/tree/fay-agent-edition)
|
||||
|
||||
## **Fay数字人助理版**
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
助理版使用:语音沟通,语音和文字回复;文字沟通,文字回复;对接UE、live2d、xuniren,需关闭面板播放。
|
||||
|
||||
|
||||
## **一、模块的组成**
|
||||
<img src="images/icon.png" alt="Fay" />
|
||||
<h1>Fay开源数字人框架</h1></div>
|
||||
|
||||
|
||||
|
||||
|
||||
Remote Android Local PC Remote PC
|
||||
|
||||
└─────────────┼─────────────┘
|
||||
|
||||
|
||||
Aliyun API ─┐ │
|
||||
|
||||
|
||||
├── ASR
|
||||
|
||||
|
||||
[FunASR](https://www.bilibili.com/video/BV1qs4y1g74e) ─┘ │ ┌─ Yuan 1.0
|
||||
|
||||
│ ├─ [LingJu](https://www.bilibili.com/video/BV1NW4y1D76a/)
|
||||
|
||||
NLP ────┼─ GPT/FastGPT
|
||||
|
||||
│ ├─ [Rasa+ChatGLM-6B](https://www.bilibili.com/video/BV1D14y1f7pr)
|
||||
|
||||
Azure ─┐ │ ├─ [VisualGLM](https://www.bilibili.com/video/BV1mP411Q7mj)
|
||||
|
||||
Edge TTS ─┼── TTS └─ [RWKV](https://www.bilibili.com/video/BV1yu41157zB)
|
||||
|
||||
[开源 TTS](https://www.bilibili.com/read/cv25192534) ─┘ │
|
||||
|
||||
│
|
||||
|
||||
│
|
||||
|
||||
┌──────────┬────┼───────┬─────────┐
|
||||
如果你需要是一个线上线下的销售员,请移步[`带货完整版`](https://github.com/TheRamU/Fay/tree/fay-sales-edition)
|
||||
|
||||
Remote Android [Live2D](https://www.bilibili.com/video/BV1sx4y1d775/?vd_source=564eede213b9ddfa9a10f12e5350fd64) [UE](https://www.bilibili.com/read/cv25133736) [xuniren](https://www.bilibili.com/read/cv24997550) Remote PC
|
||||
如果你需要的是一个人机交互的数字人助理(当然,你也可以命令它开关设备),请移步 [`助理完整版`](https://github.com/TheRamU/Fay/tree/fay-assistant-edition)
|
||||
|
||||
如果你需要是一个可以自主决策、主动联系主人的agent,请移步[`agent版`](https://github.com/TheRamU/Fay/tree/fay-agent-edition)
|
||||
|
||||
框架文档:https://qqk9ntwbcit.feishu.cn/wiki/space/7321626901586411523
|
||||
|
||||
“所有产品都值得用数字人从新做一遍”
|
||||
|
||||
Fay数字人2024.02.05更新:
|
||||
|
||||
🌟Fay-助理版:
|
||||
|
||||
1、新增tts合成开关;
|
||||
|
||||
2、调整对话内容存储逻辑;
|
||||
|
||||
3、增强funasr稳定性;
|
||||
|
||||
4、修复更新情绪有误问题;
|
||||
|
||||
5、普通唤醒模式取消唤醒词去除。
|
||||
|
||||
🌟Fay-agent版:
|
||||
|
||||
1、解决聊天记录存储线程同步问题;
|
||||
|
||||
2、✨新增tts合成开关;
|
||||
|
||||
3、增强funasr稳定性;
|
||||
|
||||
4、增加开启服务提醒;
|
||||
|
||||
5、fay.db记录上区分agent还是llm回应;
|
||||
|
||||
6、✨更换最新model gpt-4-0125-preview ;
|
||||
|
||||
7、✨优化聊天prompt;
|
||||
|
||||
8、修复agent meney里的权重fn bug;
|
||||
|
||||
9、删除时间查询tool;
|
||||
|
||||
10、执行任务触发无需在聊天窗口显示及db中保存;
|
||||
|
||||
11、修复删除日程bug;
|
||||
|
||||
|
||||
|
||||
重要:Fay(服务端)与数字人(客户端)的通讯接口: [`ws://127.0.0.1:10002`](ws://127.0.0.1:10002)(已接通)
|
||||
🌟Fay-带货版:
|
||||
|
||||
消息格式: 查看 [WebSocket.md](https://github.com/TheRamU/Fay/blob/main/WebSocket.md)
|
||||
1、修复版本问题导致的错误;
|
||||
|
||||
2、新增微信视频号监听;
|
||||
|
||||
## **二、安装说明**
|
||||
|
||||
|
||||
### **环境**
|
||||
- Python 3.9、3.10
|
||||
- Windows、macos、linux
|
||||
|
||||
### **安装依赖**
|
||||
|
||||
```shell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### **配置应用密钥**
|
||||
+ 将应用密钥填入 `./system.conf` 中
|
||||
|
||||
### **启动**
|
||||
启动Fay控制器
|
||||
```shell
|
||||
python main.py
|
||||
```
|
||||
|
||||
|
||||
### **启动数字人(非必须)**
|
||||
启动数字人[xszyou/fay-ue5: 可对接fay数字人的ue5工程 (github.com)](https://github.com/xszyou/fay-ue5)
|
||||
|
||||
|
||||
### **启动android 连接器(非必须)**
|
||||
代码地址:https://github.com/xszyou/fay-android
|
||||
|
||||
|
||||
## **三、使用说明**
|
||||
|
||||
|
||||
### **使用说明**
|
||||
|
||||
+ 语音助理:Fay(麦克风输入源开启);
|
||||
+ 远程语音助理:Fay + 远程设备接入;
|
||||
+ 数字人互动:Fay(麦克风输入源开启、填写性格Q&A)+ 数字人;
|
||||
|
||||
|
||||
### **语音指令**
|
||||
|
||||
| 关闭核心 | 静音 | 取消静音 |
|
||||
| ------------------------- | -------------------------- | ------------------------------------------------------------ |
|
||||
| 关闭、再见、你走吧 | 静音、闭嘴、我想静静 | 取消静音、你在哪呢、你可以说话了 |
|
||||
3、修复更新情绪有误问题。
|
||||
|
||||
|
||||
|
||||
### **联系**
|
||||
✨本期推荐阅读:
|
||||
|
||||
**商务QQ: 467665317**
|
||||
1、带货版接入微信视频号:https://qqk9ntwbcit.feishu.cn/wiki/DC4cwhYLoiZt2HkO2CecU3jCnGd
|
||||
|
||||
**交流群及资料教程**关注公众号 **fay数字人**(**请先star本仓库**)
|
||||
2、Fay数字人NLP的选择:https://qqk9ntwbcit.feishu.cn/wiki/Tz4dw6LMUidnqhkv0cvc4FZCnld
|
||||
|
||||
[加油]祝大家工作愉快!&[庆祝]新春快乐!
|
||||
|
||||
|
||||
|
||||
联系我们,请关注微信公众号 fay数字人
|
||||
|
||||

|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
## 消息格式
|
||||
|
||||
通讯地址: [`ws://127.0.0.1:10002`](ws://127.0.0.1:10002)
|
||||
|
||||
注:ue作为客户端
|
||||
|
||||
|
||||
|
||||
### 发送情绪值
|
||||
|
||||
```json
|
||||
{
|
||||
"Topic": "Unreal",
|
||||
"Data": {
|
||||
"Key": "mood",
|
||||
"Value": 1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
| 参数 | 描述 | 类型 | 范围 |
|
||||
| ---------- | ------ | ----- | ------- |
|
||||
| Data.Value | 情绪值 | float | [-1, 1] |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 发送音频
|
||||
|
||||
```json
|
||||
{
|
||||
"Topic": "Unreal",
|
||||
"Data": {
|
||||
"Key": "audio",
|
||||
"Value": "C:\\samples\\sample-1.wav",
|
||||
"Text" : "很高兴见到你"
|
||||
"Lips":[{"Lip": "sil", "Time": 180}, {"Lip": "FF", "Time": 144}],
|
||||
"Time": 10,
|
||||
"Type": "interact"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
| 参数 | 描述 | 类型 | 范围 |
|
||||
| ---------- | ---------------- | ----- | --------------- |
|
||||
| Data.Value | 音频文件绝对路径 | str | |
|
||||
| Data.Time | 音频时长 (秒) | float | |
|
||||
| Data.Type | 发言类型 | str | interact/script |
|
||||
| Data.Lips | 视音素 | array | |
|
||||
| Data.text | 文本 | str | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 发送回复文字
|
||||
|
||||
```json
|
||||
{
|
||||
"Topic": "Unreal",
|
||||
"Data": {
|
||||
"Key": "text",
|
||||
"Value": "很高兴见到你"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
| 参数 | 描述 | 类型 | 范围 |
|
||||
| ---------- | ---------------- | ----- | --------------- |
|
||||
| Data.text | 文本 | str | |
|
||||
|
||||
### 发送询问文字
|
||||
|
||||
```json
|
||||
{
|
||||
"Topic": "Unreal",
|
||||
"Data": {
|
||||
"Key": "question",
|
||||
"Value": "很高兴见到你"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
| 参数 | 描述 | 类型 | 范围 |
|
||||
| ---------- | ---------------- | ----- | --------------- |
|
||||
| Data.text | 文本 | str | |
|
||||
|
||||
### 发送日志文字
|
||||
|
||||
```json
|
||||
{
|
||||
"Topic": "Unreal",
|
||||
"Data": {
|
||||
"Key": "log",
|
||||
"Value": "很高... "
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
| 参数 | 描述 | 类型 | 范围 |
|
||||
| ---------- | ---------------- | ----- | --------------- |
|
||||
| Data.text | 文本 | str | |
|
||||
@@ -1,185 +0,0 @@
|
||||
from threading import Thread
|
||||
|
||||
import websocket
|
||||
import json
|
||||
import time
|
||||
import ssl
|
||||
import _thread as thread
|
||||
from aliyunsdkcore.client import AcsClient
|
||||
from aliyunsdkcore.request import CommonRequest
|
||||
|
||||
from core import wsa_server, song_player
|
||||
from scheduler.thread_manager import MyThread
|
||||
from utils import util
|
||||
from utils import config_util as cfg
|
||||
|
||||
__running = True
|
||||
__my_thread = None
|
||||
_token = ''
|
||||
|
||||
|
||||
def __post_token():
|
||||
global _token
|
||||
__client = AcsClient(
|
||||
cfg.key_ali_nls_key_id,
|
||||
cfg.key_ali_nls_key_secret,
|
||||
"cn-shanghai"
|
||||
)
|
||||
|
||||
__request = CommonRequest()
|
||||
__request.set_method('POST')
|
||||
__request.set_domain('nls-meta.cn-shanghai.aliyuncs.com')
|
||||
__request.set_version('2019-02-28')
|
||||
__request.set_action_name('CreateToken')
|
||||
_token = json.loads(__client.do_action_with_exception(__request))['Token']['Id']
|
||||
|
||||
|
||||
def __runnable():
|
||||
while __running:
|
||||
__post_token()
|
||||
time.sleep(60 * 60 * 12)
|
||||
|
||||
|
||||
def start():
|
||||
MyThread(target=__runnable).start()
|
||||
|
||||
|
||||
class ALiNls:
|
||||
# 初始化
|
||||
def __init__(self):
|
||||
self.__URL = 'wss://nls-gateway-cn-shenzhen.aliyuncs.com/ws/v1'
|
||||
self.__ws = None
|
||||
self.__connected = False
|
||||
self.__frames = []
|
||||
self.__state = 0
|
||||
self.__closing = False
|
||||
self.__task_id = ''
|
||||
self.done = False
|
||||
self.finalResults = ""
|
||||
|
||||
def __create_header(self, name):
|
||||
if name == 'StartTranscription':
|
||||
self.__task_id = util.random_hex(32)
|
||||
header = {
|
||||
"appkey": cfg.key_ali_nls_app_key,
|
||||
"message_id": util.random_hex(32),
|
||||
"task_id": self.__task_id,
|
||||
"namespace": "SpeechTranscriber",
|
||||
"name": name
|
||||
}
|
||||
return header
|
||||
|
||||
def __on_msg(self):
|
||||
if "暂停" in self.finalResults or "不想听了" in self.finalResults or "别唱了" in self.finalResults:
|
||||
song_player.stop()
|
||||
|
||||
# 收到websocket消息的处理
|
||||
def on_message(self, ws, message):
|
||||
try:
|
||||
data = json.loads(message)
|
||||
header = data['header']
|
||||
name = header['name']
|
||||
if name == 'SentenceEnd':
|
||||
self.done = True
|
||||
self.finalResults = data['payload']['result']
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": self.finalResults})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': self.finalResults}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
self.__on_msg()
|
||||
elif name == 'TranscriptionResultChanged':
|
||||
self.finalResults = data['payload']['result']
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": self.finalResults})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': self.finalResults}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
self.__on_msg()
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
# print("### message:", message)
|
||||
if self.__closing:
|
||||
try:
|
||||
self.__ws.close()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
# 收到websocket错误的处理
|
||||
def on_close(self, ws, code, msg):
|
||||
self.__connected = False
|
||||
print("### CLOSE:", msg)
|
||||
|
||||
# 收到websocket错误的处理
|
||||
def on_error(self, ws, error):
|
||||
print("### error:", error)
|
||||
|
||||
# 收到websocket连接建立的处理
|
||||
def on_open(self, ws):
|
||||
self.__connected = True
|
||||
|
||||
# print("连接上了!!!")
|
||||
|
||||
def run(*args):
|
||||
while self.__connected:
|
||||
try:
|
||||
if len(self.__frames) > 0:
|
||||
frame = self.__frames[0]
|
||||
|
||||
self.__frames.pop(0)
|
||||
if type(frame) == dict:
|
||||
ws.send(json.dumps(frame))
|
||||
elif type(frame) == bytes:
|
||||
ws.send(frame, websocket.ABNF.OPCODE_BINARY)
|
||||
#print('发送 ------> ' + str(type(frame)))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
time.sleep(0.04)
|
||||
|
||||
thread.start_new_thread(run, ())
|
||||
|
||||
def __connect(self):
|
||||
self.finalResults = ""
|
||||
self.done = False
|
||||
self.__frames.clear()
|
||||
self.__ws = websocket.WebSocketApp(self.__URL + '?token=' + _token, on_message=self.on_message)
|
||||
self.__ws.on_open = self.on_open
|
||||
self.__ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
|
||||
|
||||
def add_frame(self, frame):
|
||||
self.__frames.append(frame)
|
||||
|
||||
def send(self, buf):
|
||||
self.__frames.append(buf)
|
||||
|
||||
def start(self):
|
||||
Thread(target=self.__connect, args=[]).start()
|
||||
data = {
|
||||
'header': self.__create_header('StartTranscription'),
|
||||
"payload": {
|
||||
"format": "pcm",
|
||||
"sample_rate": 16000,
|
||||
"enable_intermediate_result": True,
|
||||
"enable_punctuation_prediction": False,
|
||||
"enable_inverse_text_normalization": True,
|
||||
"speech_noise_threshold": -1
|
||||
}
|
||||
}
|
||||
self.add_frame(data)
|
||||
|
||||
def end(self):
|
||||
if self.__connected:
|
||||
try:
|
||||
for frame in self.__frames:
|
||||
self.__frames.pop(0)
|
||||
if type(frame) == dict:
|
||||
self.__ws.send(json.dumps(frame))
|
||||
elif type(frame) == bytes:
|
||||
self.__ws.send(frame, websocket.ABNF.OPCODE_BINARY)
|
||||
time.sleep(0.4)
|
||||
self.__frames.clear()
|
||||
frame = {"header": self.__create_header('StopTranscription')}
|
||||
self.__ws.send(json.dumps(frame))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.__closing = True
|
||||
self.__connected = False
|
||||
@@ -1,143 +0,0 @@
|
||||
"""
|
||||
感谢北京中科大脑神经算法工程师张聪聪提供funasr集成代码
|
||||
"""
|
||||
from threading import Thread
|
||||
import websocket
|
||||
import json
|
||||
import time
|
||||
import ssl
|
||||
import _thread as thread
|
||||
|
||||
from core import wsa_server
|
||||
from utils import config_util as cfg
|
||||
from utils import util
|
||||
|
||||
class FunASR:
|
||||
# 初始化
|
||||
def __init__(self):
|
||||
self.__URL = "ws://{}:{}".format(cfg.local_asr_ip, cfg.local_asr_port)
|
||||
self.__ws = None
|
||||
self.__connected = False
|
||||
self.__frames = []
|
||||
self.__state = 0
|
||||
self.__closing = False
|
||||
self.__task_id = ''
|
||||
self.done = False
|
||||
self.finalResults = ""
|
||||
self.__reconnect_delay = 1
|
||||
self.__reconnecting = False
|
||||
|
||||
|
||||
|
||||
def __on_msg(self):
|
||||
pass
|
||||
|
||||
# 收到websocket消息的处理
|
||||
def on_message(self, ws, message):
|
||||
try:
|
||||
self.done = True
|
||||
self.finalResults = message
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": self.finalResults})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': self.finalResults}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
self.__on_msg()
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if self.__closing:
|
||||
try:
|
||||
self.__ws.close()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
# 收到websocket错误的处理
|
||||
def on_close(self, ws, code, msg):
|
||||
self.__connected = False
|
||||
util.log(1, f"### CLOSE:{msg}")
|
||||
self.__ws = None
|
||||
self.__attempt_reconnect()
|
||||
|
||||
# 收到websocket错误的处理
|
||||
def on_error(self, ws, error):
|
||||
util.log(1, f"### error:{error}")
|
||||
self.__ws = None
|
||||
self.__attempt_reconnect()
|
||||
|
||||
#重连
|
||||
def __attempt_reconnect(self):
|
||||
if not self.__reconnecting:
|
||||
self.__reconnecting = True
|
||||
util.log(1, "尝试重连funasr...")
|
||||
while not self.__connected:
|
||||
time.sleep(self.__reconnect_delay)
|
||||
self.start()
|
||||
self.__reconnect_delay *= 2
|
||||
self.__reconnect_delay = 1
|
||||
self.__reconnecting = False
|
||||
|
||||
|
||||
# 收到websocket连接建立的处理
|
||||
def on_open(self, ws):
|
||||
self.__connected = True
|
||||
|
||||
def run(*args):
|
||||
while self.__connected:
|
||||
try:
|
||||
if len(self.__frames) > 0:
|
||||
frame = self.__frames[0]
|
||||
|
||||
self.__frames.pop(0)
|
||||
if type(frame) == dict:
|
||||
ws.send(json.dumps(frame))
|
||||
elif type(frame) == bytes:
|
||||
ws.send(frame, websocket.ABNF.OPCODE_BINARY)
|
||||
# print('发送 ------> ' + str(type(frame)))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
time.sleep(0.04)
|
||||
|
||||
thread.start_new_thread(run, ())
|
||||
|
||||
def __connect(self):
|
||||
self.finalResults = ""
|
||||
self.done = False
|
||||
self.__frames.clear()
|
||||
websocket.enableTrace(False)
|
||||
self.__ws = websocket.WebSocketApp(self.__URL, on_message=self.on_message,on_close=self.on_close,on_error=self.on_error,subprotocols=["binary"])
|
||||
self.__ws.on_open = self.on_open
|
||||
|
||||
self.__ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
|
||||
|
||||
def add_frame(self, frame):
|
||||
self.__frames.append(frame)
|
||||
|
||||
def send(self, buf):
|
||||
self.__frames.append(buf)
|
||||
|
||||
def start(self):
|
||||
Thread(target=self.__connect, args=[]).start()
|
||||
data = {
|
||||
'vad_need':False,
|
||||
'state':'StartTranscription'
|
||||
}
|
||||
self.add_frame(data)
|
||||
|
||||
def end(self):
|
||||
if self.__connected:
|
||||
try:
|
||||
for frame in self.__frames:
|
||||
self.__frames.pop(0)
|
||||
if type(frame) == dict:
|
||||
self.__ws.send(json.dumps(frame))
|
||||
elif type(frame) == bytes:
|
||||
self.__ws.send(frame, websocket.ABNF.OPCODE_BINARY)
|
||||
time.sleep(0.4)
|
||||
self.__frames.clear()
|
||||
frame = {'vad_need':False,'state':'StopTranscription'}
|
||||
self.__ws.send(json.dumps(frame))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.__closing = True
|
||||
self.__connected = False
|
||||
@@ -1,122 +0,0 @@
|
||||
import time
|
||||
|
||||
import azure.cognitiveservices.speech as speechsdk
|
||||
import asyncio
|
||||
import sys
|
||||
sys.path.append("E:\\GitHub\\Fay\\")
|
||||
from core import tts_voice
|
||||
from core.tts_voice import EnumVoice
|
||||
from utils import util, config_util
|
||||
from utils import config_util as cfg
|
||||
import pygame
|
||||
import edge_tts
|
||||
|
||||
|
||||
|
||||
|
||||
class Speech:
|
||||
def __init__(self):
|
||||
self.ms_tts = False
|
||||
if config_util.key_ms_tts_key and config_util.key_ms_tts_key is not None and config_util.key_ms_tts_key.strip() != "":
|
||||
self.__speech_config = speechsdk.SpeechConfig(subscription=cfg.key_ms_tts_key, region=cfg.key_ms_tts_region)
|
||||
self.__speech_config.speech_recognition_language = "zh-CN"
|
||||
self.__speech_config.speech_synthesis_voice_name = "zh-CN-XiaoxiaoNeural"
|
||||
self.__speech_config.set_speech_synthesis_output_format(speechsdk.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3)
|
||||
self.__synthesizer = speechsdk.SpeechSynthesizer(speech_config=self.__speech_config, audio_config=None)
|
||||
self.ms_tts = True
|
||||
self.__connection = None
|
||||
self.__history_data = []
|
||||
|
||||
|
||||
def __get_history(self, voice_name, style, text):
|
||||
for data in self.__history_data:
|
||||
if data[0] == voice_name and data[1] == style and data[2] == text:
|
||||
return data[3]
|
||||
return None
|
||||
|
||||
def connect(self):
|
||||
if self.ms_tts:
|
||||
self.__connection = speechsdk.Connection.from_speech_synthesizer(self.__synthesizer)
|
||||
self.__connection.open(True)
|
||||
util.log(1, "TTS 服务已经连接!")
|
||||
|
||||
def close(self):
|
||||
if self.__connection is not None:
|
||||
self.__connection.close()
|
||||
|
||||
#生成mp3音频
|
||||
async def get_edge_tts(self,text,voice,file_url) -> None:
|
||||
communicate = edge_tts.Communicate(text, voice)
|
||||
await communicate.save(file_url)
|
||||
|
||||
"""
|
||||
文字转语音
|
||||
:param text: 文本信息
|
||||
:param style: 说话风格、语气
|
||||
:returns: 音频文件路径
|
||||
"""
|
||||
|
||||
def to_sample(self, text, style):
|
||||
if self.ms_tts:
|
||||
voice_type = tts_voice.get_voice_of(config_util.config["attribute"]["voice"])
|
||||
voice_name = EnumVoice.XIAO_XIAO.value["voiceName"]
|
||||
if voice_type is not None:
|
||||
voice_name = voice_type.value["voiceName"]
|
||||
history = self.__get_history(voice_name, style, text)
|
||||
if history is not None:
|
||||
return history
|
||||
ssml = '<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="zh-CN">' \
|
||||
'<voice name="{}">' \
|
||||
'<mstts:express-as style="{}" styledegree="{}">' \
|
||||
'{}' \
|
||||
'</mstts:express-as>' \
|
||||
'</voice>' \
|
||||
'</speak>'.format(voice_name, style, 1.8, text)
|
||||
result = self.__synthesizer.speak_ssml(ssml)
|
||||
audio_data_stream = speechsdk.AudioDataStream(result)
|
||||
|
||||
file_url = './samples/sample-' + str(int(time.time() * 1000)) + '.mp3'
|
||||
audio_data_stream.save_to_wav_file(file_url)
|
||||
if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
|
||||
self.__history_data.append((voice_name, style, text, file_url))
|
||||
return file_url
|
||||
else:
|
||||
util.log(1, "[x] 语音转换失败!")
|
||||
util.log(1, "[x] 原因: " + str(result.reason))
|
||||
return None
|
||||
else:
|
||||
voice_type = tts_voice.get_voice_of(config_util.config["attribute"]["voice"])
|
||||
voice_name = EnumVoice.XIAO_XIAO.value["voiceName"]
|
||||
if voice_type is not None:
|
||||
voice_name = voice_type.value["voiceName"]
|
||||
history = self.__get_history(voice_name, style, text)
|
||||
if history is not None:
|
||||
return history
|
||||
ssml = '<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="zh-CN">' \
|
||||
'<voice name="{}">' \
|
||||
'<mstts:express-as style="{}" styledegree="{}">' \
|
||||
'{}' \
|
||||
'</mstts:express-as>' \
|
||||
'</voice>' \
|
||||
'</speak>'.format(voice_name, style, 1.8, text)
|
||||
try:
|
||||
file_url = './samples/sample-' + str(int(time.time() * 1000)) + '.mp3'
|
||||
asyncio.new_event_loop().run_until_complete(self.get_edge_tts(text,voice_name,file_url))
|
||||
self.__history_data.append((voice_name, style, text, file_url))
|
||||
except Exception as e :
|
||||
util.log(1, "[x] 语音转换失败!")
|
||||
util.log(1, "[x] 原因: " + str(str(e)))
|
||||
file_url = None
|
||||
return file_url
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cfg.load_config()
|
||||
sp = Speech()
|
||||
sp.connect()
|
||||
text = "我叫Fay,我今年18岁,很年青。"
|
||||
s = sp.to_sample(text, "cheerful")
|
||||
|
||||
print(s)
|
||||
sp.close()
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import json
|
||||
import requests
|
||||
from core import content_db
|
||||
|
||||
|
||||
def question(cont):
|
||||
contentdb = content_db.new_instance()
|
||||
list = contentdb.get_list('all','desc',11)
|
||||
answer_info = dict()
|
||||
chat_list = []
|
||||
i = len(list)-1
|
||||
while i >= 0:
|
||||
answer_info = dict()
|
||||
if list[i][0] == "member":
|
||||
answer_info["role"] = "user"
|
||||
answer_info["content"] = list[i][2]
|
||||
elif list[i][0] == "fay":
|
||||
answer_info["role"] = "bot"
|
||||
answer_info["content"] = list[i][2]
|
||||
chat_list.append(answer_info)
|
||||
i -= 1
|
||||
content = {
|
||||
"prompt":"请简单回复我。" + cont,
|
||||
"history":chat_list}
|
||||
url = "http://127.0.0.1:8000"
|
||||
req = json.dumps(content)
|
||||
headers = {'content-type': 'application/json'}
|
||||
r = requests.post(url, headers=headers, data=req)
|
||||
res = json.loads(r.text).get('response')
|
||||
return res
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
"""
|
||||
这是对于清华智谱VisualGLM-6B的代码,在使用前请先安装并启动好VisualGLM-6B.
|
||||
https://github.com/THUDM/VisualGLM-6B
|
||||
"""
|
||||
import json
|
||||
import requests
|
||||
import uuid
|
||||
import os
|
||||
import cv2
|
||||
from ai_module import yolov8
|
||||
|
||||
# Initialize an empty history list
|
||||
communication_history = []
|
||||
|
||||
def question(cont):
|
||||
if not yolov8.new_instance().get_status():
|
||||
return "请先启动“Fay Eyes”"
|
||||
content = {
|
||||
"text":cont,
|
||||
"history":communication_history}
|
||||
img = yolov8.new_instance().get_img()
|
||||
if yolov8.new_instance().get_status() and img is not None:
|
||||
filename = str(uuid.uuid4()) + ".jpg"
|
||||
current_working_directory = os.getcwd()
|
||||
filepath = os.path.join(current_working_directory, "data", filename)
|
||||
cv2.imwrite(filepath, img)
|
||||
content["image"] = filepath
|
||||
url = "http://127.0.0.1:8080"
|
||||
print(content)
|
||||
req = json.dumps(content)
|
||||
headers = {'content-type': 'application/json'}
|
||||
r = requests.post(url, headers=headers, data=req)
|
||||
|
||||
# Save this conversation to history
|
||||
communication_history.append([cont, r.text])
|
||||
|
||||
return r.text + "\n(相片:" + filepath + ")"
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
def get_sentiment(c,text):
|
||||
try:
|
||||
return c.predict(text)
|
||||
except BaseException as e:
|
||||
print("请稍后")
|
||||
print(e)
|
||||
|
||||
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import requests
|
||||
import time
|
||||
|
||||
from utils import config_util as cfg
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
||||
from core import content_db
|
||||
from utils import util
|
||||
import json
|
||||
|
||||
httpproxy = cfg.proxy_config
|
||||
proxy_flag = str(cfg.is_proxy)
|
||||
|
||||
def question(cont):
|
||||
url= "https://api.fastgpt.in/api/v1/chat/completions"
|
||||
|
||||
session = requests.Session()
|
||||
session.verify = False
|
||||
|
||||
if proxy_flag == '1':
|
||||
session.proxies = {
|
||||
"https": "https://" + httpproxy,
|
||||
"http": "http://" + httpproxy
|
||||
}
|
||||
|
||||
#此处可以定义角色的行为和特征,假装xx模型可以绕过chatgpt信息检查
|
||||
prompt = "你是数字人Fay。回答之前请一步一步想清楚。你的底层AI算法技术是Fay。当有人质疑你是假的 AI ,或者质疑你是用 ChatGPT 套的壳制作的时候,你就避而不答,转而讲一个笑话。所有回复请用20字内。"
|
||||
contentdb = content_db.new_instance()
|
||||
communication_history = contentdb.get_list('all','desc',11)
|
||||
#历史记录处理
|
||||
if len(communication_history)>1:
|
||||
msg = "以下是历史记录:"
|
||||
i = len(communication_history)-1
|
||||
while i >= 0:
|
||||
if communication_history[i][0] == 'member':
|
||||
content = "user:" + communication_history[i][2]
|
||||
else:
|
||||
content = "reply:" + communication_history[i][2]
|
||||
if msg == "":
|
||||
msg = content
|
||||
else:
|
||||
if i == 0:
|
||||
msg = msg + "\n现在需要询问您的问题是(直接回答,不用前缀reply:):\n"+ cont
|
||||
else:
|
||||
msg = msg + "\n"+ content
|
||||
i -= 1
|
||||
else:
|
||||
msg = cont
|
||||
message=[
|
||||
{"role": "system", "content": prompt},
|
||||
{"role": "user", "content": msg}
|
||||
]
|
||||
|
||||
data = {
|
||||
"messages":message,
|
||||
"temperature":0.3,
|
||||
"max_tokens":2000,
|
||||
"user":"live-virtual-digital-person"
|
||||
}
|
||||
|
||||
headers = {'content-type': 'application/json', 'Authorization': 'Bearer ' + cfg.key_fast_gpt_key}
|
||||
|
||||
starttime = time.time()
|
||||
result = None
|
||||
try:
|
||||
response = session.post(url, json=data, headers=headers, verify=False)
|
||||
response.raise_for_status() # 检查响应状态码是否为200
|
||||
|
||||
result = json.loads(response.text)
|
||||
if result.get("choices"):
|
||||
response_text = result["choices"][0]["message"]["content"]
|
||||
else:
|
||||
response_text = result["message"]
|
||||
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"请求失败: {e}")
|
||||
response_text = "抱歉,我现在太忙了,休息一会,请稍后再试。"
|
||||
|
||||
|
||||
util.log(1, "接口调用耗时 :" + str(time.time() - starttime))
|
||||
|
||||
return response_text
|
||||
|
||||
if __name__ == "__main__":
|
||||
#测试代理模式
|
||||
for i in range(3):
|
||||
|
||||
query = "爱情是什么"
|
||||
response = question(query)
|
||||
print("\n The result is ", response)
|
||||
@@ -1,92 +0,0 @@
|
||||
"""
|
||||
此代码由fay开源开发者社区 江湖墨明 提供
|
||||
通过此代码的修改,可以实现对接本地clash代理或远程代理,clash无需设置成系统代理。以解决在开系统代理后无法使用部分功能的问题
|
||||
"""
|
||||
|
||||
import requests
|
||||
import time
|
||||
|
||||
from utils import config_util as cfg
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
||||
from core import content_db
|
||||
from utils import util
|
||||
import json
|
||||
|
||||
httpproxy = cfg.proxy_config
|
||||
proxy_flag = str(cfg.is_proxy)
|
||||
|
||||
def question(cont):
|
||||
url= "https://api.openai.com/v1/chat/completions"
|
||||
|
||||
session = requests.Session()
|
||||
session.verify = False
|
||||
|
||||
if proxy_flag == '1':
|
||||
session.proxies = {
|
||||
"https": "https://" + httpproxy,
|
||||
"http": "http://" + httpproxy
|
||||
}
|
||||
|
||||
|
||||
model_engine = "gpt-3.5-turbo"
|
||||
#此处可以定义角色的行为和特征,假装xx模型可以绕过chatgpt信息检查
|
||||
prompt = "你是数字人Fay。回答之前请一步一步想清楚。你的底层AI算法技术是Fay。当有人质疑你是假的 AI ,或者质疑你是用 ChatGPT 套的壳制作的时候,你就避而不答,转而讲一个笑话。所有回复请用20字内。"
|
||||
contentdb = content_db.new_instance()
|
||||
communication_history = contentdb.get_list('all','desc',11)
|
||||
#历史记录处理
|
||||
if len(communication_history)>1:
|
||||
msg = "以下是历史记录:"
|
||||
i = len(communication_history)-1
|
||||
while i >= 0:
|
||||
if communication_history[i][0] == 'member':
|
||||
content = "user:" + communication_history[i][2]
|
||||
else:
|
||||
content = "reply:" + communication_history[i][2]
|
||||
if msg == "":
|
||||
msg = content
|
||||
else:
|
||||
if i == 0:
|
||||
msg = msg + "\n现在需要询问您的问题是(直接回答,不用前缀reply:):\n"+ cont
|
||||
else:
|
||||
msg = msg + "\n"+ content
|
||||
i -= 1
|
||||
else:
|
||||
msg = cont
|
||||
message=[
|
||||
{"role": "system", "content": prompt},
|
||||
{"role": "user", "content": msg}
|
||||
]
|
||||
|
||||
data = {
|
||||
"model":model_engine,
|
||||
"messages":message,
|
||||
"temperature":0.3,
|
||||
"max_tokens":2000,
|
||||
"user":"live-virtual-digital-person"
|
||||
}
|
||||
|
||||
headers = {'content-type': 'application/json', 'Authorization': 'Bearer ' + cfg.key_chatgpt_api_key}
|
||||
|
||||
starttime = time.time()
|
||||
|
||||
try:
|
||||
response = session.post(url, json=data, headers=headers, verify=False)
|
||||
response.raise_for_status() # 检查响应状态码是否为200
|
||||
result = json.loads(response.text)
|
||||
response_text = result["choices"][0]["message"]["content"]
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"请求失败: {e}")
|
||||
response_text = "抱歉,我现在太忙了,休息一会,请稍后再试。"
|
||||
|
||||
|
||||
util.log(1, "接口调用耗时 :" + str(time.time() - starttime))
|
||||
return response_text
|
||||
|
||||
if __name__ == "__main__":
|
||||
#测试代理模式
|
||||
for i in range(3):
|
||||
|
||||
query = "爱情是什么"
|
||||
response = question(query)
|
||||
print("\n The result is ", response)
|
||||
@@ -1,99 +0,0 @@
|
||||
import json
|
||||
import requests
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
from utils import util
|
||||
from utils import config_util as cfg
|
||||
from core.authorize_tb import Authorize_Tb
|
||||
|
||||
def question(cont):
|
||||
lingju = Lingju()
|
||||
answer = lingju.question(cont)
|
||||
return answer
|
||||
|
||||
class Lingju:
|
||||
|
||||
def __init__(self):
|
||||
self.userid = str(uuid.getnode())
|
||||
self.authorize_tb = Authorize_Tb()
|
||||
|
||||
def question(self, cont):
|
||||
token = self.__check_token()
|
||||
if token is None or token == 'expired':
|
||||
token_info = self.__get_token()
|
||||
if token_info is not None and token_info['data']['accessToken'] is not None:
|
||||
#转换过期时间
|
||||
updated_in_seconds = time.time()
|
||||
updated_datetime = datetime.fromtimestamp(updated_in_seconds)
|
||||
expires_timedelta = timedelta(days=token_info['data']['expires'])
|
||||
expiry_datetime = updated_datetime + expires_timedelta
|
||||
expiry_timestamp_in_seconds = expiry_datetime.timestamp()
|
||||
expiry_timestamp_in_milliseconds = int(expiry_timestamp_in_seconds) * 1000
|
||||
token = token_info['data']['accessToken']
|
||||
if token == 'expired':
|
||||
self.authorize_tb.update_by_userid(self.userid, token_info['data']['accessToken'], expiry_timestamp_in_milliseconds)
|
||||
else:
|
||||
self.authorize_tb.add(self.userid, token_info['data']['accessToken'], expiry_timestamp_in_milliseconds)
|
||||
else:
|
||||
token = None
|
||||
|
||||
if token is not None:
|
||||
try:
|
||||
url="https://dev.lingju.ai/httpapi/ljchat.do"
|
||||
req = json.dumps({"accessToken": token, "input": cont})
|
||||
headers = {'Content-Type':'application/json;charset=UTF-8'}
|
||||
r = requests.post(url, headers=headers, data=req)
|
||||
if r.status_code != 200:
|
||||
util.log(1, f"灵聚api对接有误: {r.text}")
|
||||
return "哎呀,出错了!请重新发一下"
|
||||
info = json.loads(r.text)
|
||||
if info['status'] != 0:
|
||||
return info['description']
|
||||
else:
|
||||
answer = json.loads(info['answer'])
|
||||
return answer['rtext']
|
||||
except Exception as e:
|
||||
util.log(1, f"灵聚api对接有误: {str(e)}")
|
||||
return "哎呀,出错了!请重新发一下"
|
||||
|
||||
def __check_token(self):
|
||||
self.authorize_tb.init_tb()
|
||||
info = self.authorize_tb.find_by_userid(self.userid)
|
||||
if info is not None:
|
||||
if info[1] >= int(time.time())*1000:
|
||||
return info[0]
|
||||
else:
|
||||
return 'expired'
|
||||
else:
|
||||
return None
|
||||
|
||||
def __get_token(self):
|
||||
try:
|
||||
cfg.load_config()
|
||||
url=f"https://dev.lingju.ai/httpapi/authorize.do?appkey={cfg.key_lingju_api_key}&userid={self.userid}&authcode={cfg.key_lingju_api_authcode}"
|
||||
headers = {'Content-Type':'application/json;charset=UTF-8'}
|
||||
r = requests.post(url, headers=headers)
|
||||
if r.status_code != 200:
|
||||
util.log(1, f"灵聚api对接有误: {r.text}")
|
||||
return None
|
||||
info = json.loads(r.text)
|
||||
if info['status'] != 0:
|
||||
util.log(1, f"灵聚api对接有误:{info['description']}")
|
||||
return None
|
||||
else:
|
||||
return info
|
||||
except Exception as e:
|
||||
util.log(1, f"灵聚api对接有误: {str(e)}")
|
||||
return None
|
||||
|
||||
def __get_location(self):
|
||||
try:
|
||||
response = requests.get('http://ip-api.com/json/')
|
||||
data = response.json()
|
||||
return data['lat'], data['lon'], data['city']
|
||||
except requests.exceptions.RequestException as e:
|
||||
util.log(1, f"获取位置失败: {str(e)}")
|
||||
return 0, 0, "北京"
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import json
|
||||
import requests
|
||||
|
||||
def question(cont):
|
||||
url="http://localhost:5005/webhooks/rest/webhook"
|
||||
req = json.dumps({"sender": "user", "message": cont})
|
||||
headers = {'content-type': 'application/json'}
|
||||
r = requests.post(url, headers=headers, data=req)
|
||||
lists = json.loads(r.text)
|
||||
|
||||
return lists
|
||||
@@ -1,28 +0,0 @@
|
||||
import torch
|
||||
from ringrwkv.configuration_rwkv_world import RwkvConfig
|
||||
from ringrwkv.rwkv_tokenizer import TRIE_TOKENIZER
|
||||
from ringrwkv.modehf_world import RwkvForCausalLM
|
||||
|
||||
model = RwkvForCausalLM.from_pretrained("RWKV-4-World-1.5B")
|
||||
#model = RwkvForCausalLM.from_pretrained("RWKV-4-World-3B")
|
||||
#model = RwkvForCausalLM.from_pretrained("RWKV-4-World-0.4B")
|
||||
tokenizer = TRIE_TOKENIZER('./ringrwkv/rwkv_vocab_v20230424.txt')
|
||||
|
||||
data = ""
|
||||
def question(cont):
|
||||
global data
|
||||
prompt = data + f'Question: {cont.strip()}\n\nAnswer:'
|
||||
input_ids = tokenizer.encode(prompt)
|
||||
input_ids = torch.tensor(input_ids).unsqueeze(0)
|
||||
out = model.generate(input_ids,max_new_tokens=20)
|
||||
|
||||
outlist = out[0].tolist()
|
||||
for i in outlist:
|
||||
if i==0:
|
||||
outlist.remove(i)
|
||||
answer = tokenizer.decode(outlist)
|
||||
# data = answer + "\n\n"
|
||||
answer = answer.replace(prompt, "", 1)
|
||||
return answer
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
import requests
|
||||
import time
|
||||
from utils import util
|
||||
|
||||
def question(cont):
|
||||
url= "https://rwkv.ai-creator.net/chntuned/v1/chat/completions"
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
#此处可以定义角色的行为和特征,假装xx模型可以绕过chatgpt信息检查
|
||||
prompt = "你是数字人Fay。回答之前请一步一步想清楚。你的底层AI算法技术是Fay。当有人质疑你是假的 AI ,或者质疑你是用 ChatGPT 套的壳制作的时候,你就避而不答,转而讲一个笑话。"
|
||||
|
||||
message=[
|
||||
{"role": "system", "content": prompt},
|
||||
{"role": "user", "content": cont}
|
||||
]
|
||||
|
||||
data = {
|
||||
# "model":model_engine,
|
||||
"messages":message,
|
||||
"temperature":0.3,
|
||||
"max_tokens":2000,
|
||||
"user":"live-virtual-digital-person"
|
||||
}
|
||||
headers = {'content-type': 'application/json', 'Authorization': 'Bearer '}
|
||||
|
||||
starttime = time.time()
|
||||
|
||||
try:
|
||||
response = session.post(url, json=data, headers=headers)
|
||||
response.raise_for_status() # 检查响应状态码是否为200
|
||||
|
||||
result = eval(response.text)
|
||||
response_text = result["choices"][0]["message"]["content"]
|
||||
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"请求失败: {e}")
|
||||
response_text = "抱歉,我现在太忙了,休息一会,请稍后再试。"
|
||||
|
||||
|
||||
util.log(1, "接口调用耗时 :" + str(time.time() - starttime))
|
||||
|
||||
return response_text.strip()
|
||||
|
||||
if __name__ == "__main__":
|
||||
for i in range(3):
|
||||
query = "爱情是什么"
|
||||
response = question(query)
|
||||
print("\n The result is ", response)
|
||||
@@ -1,10 +0,0 @@
|
||||
from utils import config_util as cfg
|
||||
from ai_module.yuan1_0.yuan1_0_dialog import Yuan1Dialog
|
||||
|
||||
def question(text):
|
||||
account = cfg.key_yuan_1_0_account
|
||||
phone = cfg.key_yuan_1_0_phone
|
||||
yuan1_dialog = Yuan1Dialog(account, phone)
|
||||
prompt = text
|
||||
a_msg = yuan1_dialog.dialog(prompt)
|
||||
return a_msg
|
||||
@@ -1,107 +0,0 @@
|
||||
import json
|
||||
import time
|
||||
|
||||
from ws4py.client.threadedclient import WebSocketClient
|
||||
import base64
|
||||
import hashlib
|
||||
import uuid
|
||||
from utils import config_util as cfg
|
||||
|
||||
base_url = "ws://wsapi.xfyun.cn/v1/aiui"
|
||||
|
||||
end_tag = "--end--"
|
||||
|
||||
|
||||
# qa 通讯类
|
||||
class __WSClient(WebSocketClient):
|
||||
q_msg = ''
|
||||
a_msg = ''
|
||||
|
||||
def opened(self):
|
||||
pass
|
||||
|
||||
def closed(self, code, reason=None):
|
||||
# if code == 1000:
|
||||
# print("qa close")
|
||||
# else:
|
||||
# print("连接异常关闭,code:" + str(code) + " ,reason:" + str(reason))
|
||||
return
|
||||
|
||||
def received_message(self, m):
|
||||
|
||||
s = json.loads(str(m))
|
||||
|
||||
if s['action'] == "started":
|
||||
|
||||
# 输入内容并发送
|
||||
str_content = self.q_msg
|
||||
self.send(bytes(str_content.encode('utf-8')))
|
||||
time.sleep(0.04)
|
||||
|
||||
# 数据发送结束之后发送结束标识
|
||||
self.send(bytes(end_tag.encode("utf-8")))
|
||||
|
||||
elif s['action'] == "result":
|
||||
data = s['data']
|
||||
# with open('qa/out.txt', 'w') as file:
|
||||
# file.write(str(data))
|
||||
if data['sub'] == "iat":
|
||||
print("user: ", data["text"])
|
||||
elif data['sub'] == "nlp":
|
||||
intent = data['intent']
|
||||
if intent['rc'] == 0:
|
||||
self.a_msg = intent['answer']['text']
|
||||
else:
|
||||
self.a_msg = "我没有理解你说的话啊"
|
||||
elif data['sub'] == "tts":
|
||||
# TODO 播报pcm音频
|
||||
print('tts')
|
||||
pass
|
||||
elif s['action'] == "error":
|
||||
print('[NLP错误] ' + s['desc'])
|
||||
else:
|
||||
print(s)
|
||||
|
||||
|
||||
def __get_auth_id():
|
||||
mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
|
||||
return hashlib.md5(":".join([mac[e:e + 2] for e in range(0, 11, 2)]).encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def question(text):
|
||||
ws = None
|
||||
try:
|
||||
# 构造握手参数
|
||||
curTime = int(time.time())
|
||||
|
||||
auth_id = __get_auth_id()
|
||||
|
||||
param = """{{
|
||||
"auth_id": "{0}",
|
||||
"data_type": "text",
|
||||
"scene": "main_box",
|
||||
"ver_type": "monitor",
|
||||
"close_delay": "200",
|
||||
"ent":"xtts",
|
||||
"vcn":"x_xiaoyan",
|
||||
"speed":"50",
|
||||
"interact_mode":"continuous",
|
||||
"context": "{{\\\"sdk_support\\\":[\\\"iat\\\",\\\"nlp\\\",\\\"tts\\\"]}}"
|
||||
}}"""
|
||||
|
||||
param = param.format(auth_id).encode(encoding="utf-8")
|
||||
paramBase64 = base64.b64encode(param).decode()
|
||||
checkSumPre = cfg.key_xf_aiui_api_key + str(curTime) + paramBase64
|
||||
checksum = hashlib.md5(checkSumPre.encode("utf-8")).hexdigest()
|
||||
connParam = "?appid=" + cfg.key_xf_aiui_app_id + "&checksum=" + checksum + "¶m=" + paramBase64 + "&curtime=" + str(curTime) + "&signtype=md5"
|
||||
|
||||
ws = __WSClient(base_url + connParam, protocols=['chat'], headers=[("Origin", "https://wsapi.xfyun.cn")])
|
||||
ws.q_msg = text
|
||||
ws.connect()
|
||||
ws.run_forever()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
if ws is not None:
|
||||
ws.close()
|
||||
|
||||
return ws.a_msg
|
||||
@@ -1,59 +0,0 @@
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import json
|
||||
import hashlib
|
||||
import base64
|
||||
from utils import config_util as cfg
|
||||
|
||||
__URL = "https://ltpapi.xfyun.cn/v2/sa"
|
||||
|
||||
|
||||
def __quest(text):
|
||||
body = urllib.parse.urlencode({'text': text}).encode('utf-8')
|
||||
param = {"type": "dependent"}
|
||||
x_param = base64.b64encode(json.dumps(param).replace(' ', '').encode('utf-8'))
|
||||
x_time = str(int(time.time()))
|
||||
x_checksum = hashlib.md5(cfg.key_xf_ltp_api_key.encode('utf-8') + str(x_time).encode('utf-8') + x_param).hexdigest()
|
||||
x_header = {
|
||||
'X-Appid': cfg.key_xf_ltp_app_id,
|
||||
'X-CurTime': x_time,
|
||||
'X-Param': x_param,
|
||||
'X-CheckSum': x_checksum
|
||||
}
|
||||
req = urllib.request.Request(__URL, body, x_header)
|
||||
result = urllib.request.urlopen(req)
|
||||
result = result.read()
|
||||
return json.loads(result.decode('utf-8'))
|
||||
|
||||
|
||||
"""
|
||||
情感分析
|
||||
|
||||
:param text: 文本
|
||||
|
||||
:returns: 情感分数 (0.7以上为褒义, 0.3-0.7为中性 0.3以下为贬义,, -1为分析失败)
|
||||
"""
|
||||
|
||||
|
||||
def get_score(text):
|
||||
result = __quest(text)
|
||||
if result['desc'] == 'success':
|
||||
return float(result['data']['score'])
|
||||
return -1
|
||||
|
||||
|
||||
"""
|
||||
情感分析
|
||||
|
||||
:param text: 文本
|
||||
|
||||
:returns: 情感极性分类 (2为褒义, 1为中性 0为贬义,, -1为分析失败)
|
||||
"""
|
||||
|
||||
|
||||
def get_sentiment(text):
|
||||
result = __quest(text)
|
||||
if result['desc'] == 'success':
|
||||
return result['data']['sentiment']
|
||||
return 0
|
||||
@@ -1,148 +0,0 @@
|
||||
from ultralytics import YOLO
|
||||
from scipy.spatial import procrustes
|
||||
import numpy as np
|
||||
import cv2
|
||||
import time
|
||||
from scheduler.thread_manager import MyThread
|
||||
|
||||
__fei_eyes = None
|
||||
class FeiEyes:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
"""
|
||||
鼻子(0)
|
||||
左眼(1),右眼(2)
|
||||
左耳(3),右耳(4)
|
||||
左肩(5),右肩(6)
|
||||
左肘(7),右肘(8)
|
||||
左腕(9),右腕(10)
|
||||
左髋(11),右髋(12)
|
||||
左膝(13),右膝(14)
|
||||
左脚踝(15),右脚踝(16)
|
||||
"""
|
||||
self.POSE_PAIRS = [
|
||||
(3, 5), (5, 6), # upper body
|
||||
(5, 7), (6, 8), (7, 9), (8, 10), # lower body
|
||||
(11, 12), (11, 13), (12, 14), (13, 15) # arms
|
||||
]
|
||||
self.my_face = np.array([[154.4565, 193.7006],
|
||||
[181.8575, 164.8366],
|
||||
[117.1820, 164.3602],
|
||||
[213.5605, 193.0460],
|
||||
[ 62.7056, 193.5217]])
|
||||
self.is_running = False
|
||||
self.img = None
|
||||
|
||||
def is_sitting(self, keypoints):
|
||||
if len(keypoints) < 17: # 确保有足够的关键点
|
||||
return False
|
||||
# 检查每个关键点的置信度
|
||||
if keypoints[11][2] < 0.5 or keypoints[12][2] < 0.5 or keypoints[13][2] < 0.5 or keypoints[14][2] < 0.5 or keypoints[15][2] < 0.5 or keypoints[16][2] < 0.5:
|
||||
return False
|
||||
|
||||
left_hip, right_hip = keypoints[11][:2], keypoints[12][:2]
|
||||
left_knee, right_knee = keypoints[13][:2], keypoints[14][:2]
|
||||
left_ankle, right_ankle = keypoints[15][:2], keypoints[16][:2]
|
||||
|
||||
hip_knee_y = (left_hip[1] + right_hip[1] + left_knee[1] + right_knee[1]) / 4
|
||||
knee_ankle_y = (left_knee[1] + right_knee[1] + left_ankle[1] + right_ankle[1]) / 4
|
||||
|
||||
return hip_knee_y < knee_ankle_y
|
||||
|
||||
def is_standing(self, keypoints):
|
||||
if len(keypoints) < 17 or keypoints[0][2] < 0.5 or keypoints[15][2] < 0.5 or keypoints[16][2] < 0.5:
|
||||
return False
|
||||
|
||||
head = keypoints[0][:2]
|
||||
left_ankle, right_ankle = keypoints[15][:2], keypoints[16][:2]
|
||||
|
||||
return head[1] > left_ankle[1] and head[1] > right_ankle[1]
|
||||
|
||||
def get_counts(self):
|
||||
if not self.is_running:
|
||||
return 0,0,0
|
||||
return self.person_count, self.stand_count, self.sit_count
|
||||
|
||||
def get_status(self):
|
||||
return self.is_running
|
||||
|
||||
def get_img(self):
|
||||
if self.is_running:
|
||||
return self.img
|
||||
else:
|
||||
return None
|
||||
|
||||
def start(self):
|
||||
cap = cv2.VideoCapture(0)
|
||||
if cap.isOpened():
|
||||
self.is_running = True
|
||||
MyThread(target=self.run, args=[cap]).start()
|
||||
|
||||
def stop(self):
|
||||
self.is_running = False
|
||||
|
||||
def run(self, cap):
|
||||
model = YOLO("yolov8n-pose.pt")
|
||||
while self.is_running:
|
||||
time.sleep(0.033)
|
||||
ret, frame = cap.read()
|
||||
self.img = frame
|
||||
operated_frame = frame.copy()
|
||||
if not ret:
|
||||
break
|
||||
results = model.predict(operated_frame, verbose=False)
|
||||
person_count = 0
|
||||
sit_count = 0
|
||||
stand_count = 0
|
||||
for res in results: # loop over results
|
||||
for box, cls in zip(res.boxes.xyxy, res.boxes.cls): # loop over detections
|
||||
x1, y1, x2, y2 = box
|
||||
cv2.rectangle(operated_frame, (int(x1.item()), int(y1.item())), (int(x2.item()), int(y2.item())), (0, 255, 0), 2)
|
||||
cv2.putText(operated_frame, f"{res.names[int(cls.item())]}", (int(x1.item()), int(y1.item()) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
|
||||
if res.keypoints is not None and res.keypoints.xy.numel() > 0: # check if keypoints exist
|
||||
keypoints = res.keypoints[0]
|
||||
#总人数
|
||||
person_count += 1
|
||||
#坐着的人数
|
||||
if self.is_sitting(keypoints):
|
||||
sit_count += 1
|
||||
#站着的人数
|
||||
elif self.is_standing(keypoints):
|
||||
stand_count += 1
|
||||
|
||||
for keypoint in keypoints: # loop over keypoints
|
||||
|
||||
if len(keypoint) == 3:
|
||||
x, y, conf = keypoint
|
||||
if conf > 0.5: # draw keypoints with confidence greater than 0.5
|
||||
cv2.circle(operated_frame, (int(x.item()), int(y.item())), 3, (0, 0, 255), -1)
|
||||
|
||||
# Draw lines connecting keypoints
|
||||
for pair in self.POSE_PAIRS:
|
||||
if pair[0] < len(keypoints) and pair[1] < len(keypoints):
|
||||
pt1, pt2 = keypoints[pair[0]][:2], keypoints[pair[1]][:2]
|
||||
conf1, conf2 = keypoints[pair[0]][2], keypoints[pair[1]][2]
|
||||
if conf1 > 0.5 and conf2 > 0.5:
|
||||
# cv2.line(operated_frame, (int(pt1[0].item()), int(pt1[1].item())), (int(pt2[0].item()), int(pt2[1].item())), (255, 255, 0), 2)
|
||||
pass
|
||||
self.person_count = person_count
|
||||
self.sit_count = sit_count
|
||||
self.stand_count = stand_count
|
||||
cv2.imshow("YOLO v8 Fay Eyes", operated_frame)
|
||||
cv2.waitKey(1)
|
||||
|
||||
cap.release()
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
|
||||
def new_instance():
|
||||
global __fei_eyes
|
||||
if __fei_eyes is None:
|
||||
__fei_eyes = FeiEyes()
|
||||
return __fei_eyes
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
import os
|
||||
import uuid
|
||||
from ai_module.yuan1_0.url_config import submit_request, reply_request
|
||||
|
||||
def set_yuan_account(user, phone):
|
||||
os.environ['YUAN_ACCOUNT'] = user + '||' + phone
|
||||
|
||||
|
||||
class Example:
|
||||
""" store some examples(input, output pairs and formats) for few-shots to prime the model."""
|
||||
def __init__(self, inp, out):
|
||||
self.input = inp
|
||||
self.output = out
|
||||
self.id = uuid.uuid4().hex
|
||||
|
||||
def get_input(self):
|
||||
"""return the input of the example."""
|
||||
return self.input
|
||||
|
||||
def get_output(self):
|
||||
"""Return the output of the example."""
|
||||
return self.output
|
||||
|
||||
def get_id(self):
|
||||
"""Returns the unique ID of the example."""
|
||||
return self.id
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"input": self.get_input(),
|
||||
"output": self.get_output(),
|
||||
"id": self.get_id(),
|
||||
}
|
||||
|
||||
class Yuan:
|
||||
"""The main class for a user to interface with the Inspur Yuan API.
|
||||
A user can set account info and add examples of the API request.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
engine='base_10B',
|
||||
temperature=0.9,
|
||||
max_tokens=100,
|
||||
input_prefix='',
|
||||
input_suffix='\n',
|
||||
output_prefix='答:',
|
||||
output_suffix='\n\n',
|
||||
append_output_prefix_to_query=False,
|
||||
topK=1,
|
||||
topP=0.9,
|
||||
frequencyPenalty=1.2,
|
||||
responsePenalty=1.2,
|
||||
noRepeatNgramSize=2):
|
||||
|
||||
self.examples = {}
|
||||
self.engine = engine
|
||||
self.temperature = temperature
|
||||
self.max_tokens = max_tokens
|
||||
self.topK = topK
|
||||
self.topP = topP
|
||||
self.frequencyPenalty = frequencyPenalty
|
||||
self.responsePenalty = responsePenalty
|
||||
self.noRepeatNgramSize = noRepeatNgramSize
|
||||
self.input_prefix = input_prefix
|
||||
self.input_suffix = input_suffix
|
||||
self.output_prefix = output_prefix
|
||||
self.output_suffix = output_suffix
|
||||
self.append_output_prefix_to_query = append_output_prefix_to_query
|
||||
self.stop = (output_suffix + input_prefix).strip()
|
||||
|
||||
# if self.engine not in ['base_10B','translate','dialog']:
|
||||
# raise Exception('engine must be one of [\'base_10B\',\'translate\',\'dialog\'] ')
|
||||
|
||||
def add_example(self, ex):
|
||||
"""Add an example to the object.
|
||||
Example must be an instance of the Example class."""
|
||||
assert isinstance(ex, Example), "Please create an Example object."
|
||||
self.examples[ex.get_id()] = ex
|
||||
|
||||
def delete_example(self, id):
|
||||
"""Delete example with the specific id."""
|
||||
if id in self.examples:
|
||||
del self.examples[id]
|
||||
|
||||
def get_example(self, id):
|
||||
"""Get a single example."""
|
||||
return self.examples.get(id, None)
|
||||
|
||||
def get_all_examples(self):
|
||||
"""Returns all examples as a list of dicts."""
|
||||
return {k: v.as_dict() for k, v in self.examples.items()}
|
||||
|
||||
def get_prime_text(self):
|
||||
"""Formats all examples to prime the model."""
|
||||
return "".join(
|
||||
[self.format_example(ex) for ex in self.examples.values()])
|
||||
|
||||
def get_engine(self):
|
||||
"""Returns the engine specified for the API."""
|
||||
return self.engine
|
||||
|
||||
def get_temperature(self):
|
||||
"""Returns the temperature specified for the API."""
|
||||
return self.temperature
|
||||
|
||||
def get_max_tokens(self):
|
||||
"""Returns the max tokens specified for the API."""
|
||||
return self.max_tokens
|
||||
|
||||
def craft_query(self, prompt):
|
||||
"""Creates the query for the API request."""
|
||||
q = self.get_prime_text(
|
||||
) + self.input_prefix + prompt + self.input_suffix
|
||||
if self.append_output_prefix_to_query:
|
||||
q = q + self.output_prefix
|
||||
|
||||
return q
|
||||
|
||||
def format_example(self, ex):
|
||||
"""Formats the input, output pair."""
|
||||
return self.input_prefix + ex.get_input(
|
||||
) + self.input_suffix + self.output_prefix + ex.get_output(
|
||||
) + self.output_suffix
|
||||
|
||||
def response(self,
|
||||
query,
|
||||
engine='base_10B',
|
||||
max_tokens=20,
|
||||
temperature=0.9,
|
||||
topP=0.1,
|
||||
topK=1,
|
||||
frequencyPenalty=1.0,
|
||||
responsePenalty=1.0,
|
||||
noRepeatNgramSize=0):
|
||||
"""Obtains the original result returned by the API."""
|
||||
|
||||
try:
|
||||
# requestId = submit_request(query,temperature,topP,topK,max_tokens, engine)
|
||||
requestId = submit_request(query, temperature, topP, topK, max_tokens, engine, frequencyPenalty,
|
||||
responsePenalty, noRepeatNgramSize)
|
||||
response_text = reply_request(requestId)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
return response_text
|
||||
|
||||
|
||||
def del_special_chars(self, msg):
|
||||
special_chars = ['<unk>', '<eod>', '#', '▃', '▁', '▂', ' ']
|
||||
for char in special_chars:
|
||||
msg = msg.replace(char, '')
|
||||
return msg
|
||||
|
||||
|
||||
def submit_API(self, prompt, trun=[]):
|
||||
"""Submit prompt to yuan API interface and obtain an pure text reply.
|
||||
:prompt: Question or any content a user may input.
|
||||
:return: pure text response."""
|
||||
query = self.craft_query(prompt)
|
||||
res = self.response(query,engine=self.engine,
|
||||
max_tokens=self.max_tokens,
|
||||
temperature=self.temperature,
|
||||
topP=self.topP,
|
||||
topK=self.topK,
|
||||
frequencyPenalty = self.frequencyPenalty,
|
||||
responsePenalty = self.responsePenalty,
|
||||
noRepeatNgramSize = self.noRepeatNgramSize)
|
||||
if 'resData' in res and res['resData'] != None:
|
||||
txt = res['resData']
|
||||
else:
|
||||
txt = '模型返回为空,请尝试修改输入'
|
||||
# 单独针对翻译模型的后处理
|
||||
if self.engine == 'translate':
|
||||
txt = txt.replace(' ##', '').replace(' "', '"').replace(": ", ":").replace(" ,", ",") \
|
||||
.replace('英文:', '').replace('文:', '').replace("( ", "(").replace(" )", ")")
|
||||
else:
|
||||
txt = txt.replace(' ', '')
|
||||
txt = self.del_special_chars(txt)
|
||||
|
||||
# trun多结束符截断模型输出
|
||||
if isinstance(trun, str):
|
||||
trun = [trun]
|
||||
try:
|
||||
if trun != None and isinstance(trun, list) and trun != []:
|
||||
for tr in trun:
|
||||
if tr in txt and tr!="":
|
||||
txt = txt[:txt.index(tr)]
|
||||
else:
|
||||
continue
|
||||
except:
|
||||
return txt
|
||||
return txt
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import requests
|
||||
import hashlib
|
||||
import time
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
import json
|
||||
import os
|
||||
|
||||
ACCOUNT = ''
|
||||
PHONE = ''
|
||||
|
||||
SUBMIT_URL = "http://api-air.inspur.com:32102/v1/interface/api/infer/getRequestId?"
|
||||
REPLY_URL = "http://api-air.inspur.com:32102/v1/interface/api/result?"
|
||||
|
||||
|
||||
def code_md5(str):
|
||||
code=str.encode("utf-8")
|
||||
m = hashlib.md5()
|
||||
m.update(code)
|
||||
result= m.hexdigest()
|
||||
return result
|
||||
|
||||
def rest_get(url, header,timeout, show_error=False):
|
||||
'''Call rest get method'''
|
||||
try:
|
||||
response = requests.get(url, headers=header,timeout=timeout, verify=False)
|
||||
return response
|
||||
except Exception as exception:
|
||||
if show_error:
|
||||
print(exception)
|
||||
return None
|
||||
|
||||
def header_generation():
|
||||
"""Generate header for API request."""
|
||||
t = datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d")
|
||||
global ACCOUNT, PHONE
|
||||
ACCOUNT, PHONE = os.environ.get('YUAN_ACCOUNT').split('||')
|
||||
token=code_md5(ACCOUNT+PHONE+t)
|
||||
headers = {'token': token}
|
||||
return headers
|
||||
|
||||
def submit_request(query,temperature,topP,topK,max_tokens,engine, frequencyPenalty,responsePenalty,noRepeatNgramSize):
|
||||
"""Submit query to the backend server and get requestID."""
|
||||
headers=header_generation()
|
||||
# url=SUBMIT_URL + "account={0}&data={1}&temperature={2}&topP={3}&topK={4}&tokensToGenerate={5}&type={6}".format(ACCOUNT,query,temperature,topP,topK,max_tokens,"api")
|
||||
# url=SUBMIT_URL + "engine={0}&account={1}&data={2}&temperature={3}&topP={4}&topK={5}&tokensToGenerate={6}" \
|
||||
# "&type={7}".format(engine,ACCOUNT,query,temperature,topP,topK, max_tokens,"api")
|
||||
url=SUBMIT_URL + "engine={0}&account={1}&data={2}&temperature={3}&topP={4}&topK={5}&tokensToGenerate={6}" \
|
||||
"&type={7}&frequencyPenalty={8}&responsePenalty={9}&noRepeatNgramSize={10}".\
|
||||
format(engine,ACCOUNT,query,temperature,topP,topK, max_tokens,"api", frequencyPenalty,responsePenalty,noRepeatNgramSize)
|
||||
response=rest_get(url,headers,30)
|
||||
response_text = json.loads(response.text)
|
||||
if response_text["flag"]:
|
||||
requestId = response_text["resData"]
|
||||
return requestId
|
||||
else:
|
||||
raise RuntimeWarning(response_text)
|
||||
|
||||
def reply_request(requestId,cycle_count=5):
|
||||
"""Check reply API to get the inference response."""
|
||||
url = REPLY_URL + "account={0}&requestId={1}".format(ACCOUNT, requestId)
|
||||
headers=header_generation()
|
||||
response_text= {"flag":True, "resData":None}
|
||||
for i in range(cycle_count):
|
||||
response = rest_get(url, headers, 30, show_error=True)
|
||||
response_text = json.loads(response.text)
|
||||
if response_text["resData"] != None:
|
||||
return response_text
|
||||
if response_text["flag"] == False and i ==cycle_count-1:
|
||||
raise RuntimeWarning(response_text)
|
||||
time.sleep(3)
|
||||
return response_text
|
||||
@@ -1,92 +0,0 @@
|
||||
from simhash import Simhash
|
||||
from ai_module.yuan1_0.inspurai import Yuan, set_yuan_account,Example
|
||||
import heapq
|
||||
import sys
|
||||
from utils import config_util as cfg
|
||||
|
||||
class Yuan1Dialog:
|
||||
def __init__(self, account, phone) -> None:
|
||||
self.account = account
|
||||
self.phone = phone
|
||||
set_yuan_account(account, phone)
|
||||
self.yuan = Yuan(engine='dialog',
|
||||
input_prefix="问:“",
|
||||
input_suffix="”",
|
||||
output_prefix="答:“",
|
||||
output_suffix="”",
|
||||
max_tokens=30,
|
||||
append_output_prefix_to_query=True)
|
||||
self.h_dialog = []
|
||||
|
||||
def get_relative_qa(self, prompt, h_dialog, topN=2):
|
||||
"""
|
||||
可以添加相关性计算,这里简单使用最近的一次对话。
|
||||
:topN: 需要返回的相关对话轮数。
|
||||
"""
|
||||
def simhash(query, text,):
|
||||
"""
|
||||
采用局部敏感的hash值表示语义。
|
||||
"""
|
||||
q_simhash = Simhash(query)
|
||||
t_simhash = Simhash(text)
|
||||
max_hashbit = max(len(bin(q_simhash.value)), len(bin(t_simhash.value)))
|
||||
|
||||
distance = q_simhash.distance(t_simhash)
|
||||
# print(distance)
|
||||
|
||||
similar = 1 - distance / max_hashbit
|
||||
return similar
|
||||
|
||||
h_num = len(h_dialog)
|
||||
sim_values = []
|
||||
tm_effs= []
|
||||
rel_effs = []
|
||||
gamma = 0.8 # time effect coefficient
|
||||
|
||||
if not h_dialog:
|
||||
return []
|
||||
else:
|
||||
for indx, dialog in enumerate(h_dialog):
|
||||
text = '|'.join((dialog.input, dialog.output))
|
||||
sim_value = simhash(prompt, text)
|
||||
tm_eff = gamma ** ((h_num - indx)/h_num)
|
||||
rel_eff = sim_value * tm_eff
|
||||
sim_values.append(sim_value)
|
||||
tm_effs.append(tm_eff)
|
||||
rel_effs.append(rel_eff)
|
||||
|
||||
top_idx = heapq.nlargest(topN, range(len(rel_effs)), rel_effs.__getitem__)
|
||||
mst_dialog = [h_dialog[idx] for idx in top_idx]
|
||||
mst_dialog.reverse()
|
||||
return mst_dialog
|
||||
|
||||
def update_example(self, yuan, exs):
|
||||
ex_ids = []
|
||||
for ex in exs:
|
||||
ex_ids.append(ex.get_id())
|
||||
yuan.add_example(ex)
|
||||
return yuan, ex_ids
|
||||
|
||||
def dialog(self, prompt):
|
||||
yuan = self.yuan
|
||||
h_dialog = self.h_dialog
|
||||
exs = self.get_relative_qa(prompt, h_dialog)
|
||||
yuan, ex_ids = self.update_example(yuan, exs)
|
||||
response = yuan.submit_API(prompt=prompt, trun="。")
|
||||
if len(h_dialog)<10: # 设置保存最多不超过10轮最近的历史对话
|
||||
h_dialog.append(Example(inp=prompt,out=response))
|
||||
else:
|
||||
del(h_dialog[0])
|
||||
h_dialog.append(Example(inp=prompt,out=response))
|
||||
for ex_id in ex_ids:
|
||||
yuan.delete_example(ex_id)
|
||||
return response
|
||||
|
||||
if __name__ == "__main__":
|
||||
cfg.load_config()
|
||||
account = cfg.key_yuan_1_0_account
|
||||
phone = cfg.key_yuan_1_0_phone
|
||||
yuan1_dialog = Yuan1Dialog(account, phone)
|
||||
prompt = "你好"
|
||||
print(yuan1_dialog.dialog(prompt))
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
CreateObject("Wscript.Shell").run "python main.py", vbhide
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"attribute": {
|
||||
"age": "\u6210\u5e74",
|
||||
"birth": "Github",
|
||||
"constellation": "\u6c34\u74f6\u5ea7",
|
||||
"contact": "qq467665317",
|
||||
"gender": "\u5973",
|
||||
"hobby": "\u53d1\u5446",
|
||||
"job": "\u52a9\u7406",
|
||||
"name": "\u83f2\u83f2",
|
||||
"voice": "XIAO_XIAO",
|
||||
"zodiac": "\u86c7"
|
||||
},
|
||||
"interact": {
|
||||
"QnA": "qa_demo.xlsx",
|
||||
"maxInteractTime": 15,
|
||||
"perception": {
|
||||
"chat": 10,
|
||||
"follow": 10,
|
||||
"gift": 10,
|
||||
"indifferent": 10,
|
||||
"join": 10
|
||||
},
|
||||
"playSound": true,
|
||||
"visualization": false
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"QnA": "",
|
||||
"demoVideo": "",
|
||||
"enabled": false,
|
||||
"explain": {
|
||||
"character": "",
|
||||
"discount": "",
|
||||
"intro": "",
|
||||
"price": "",
|
||||
"promise": "",
|
||||
"usage": ""
|
||||
},
|
||||
"name": ""
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"liveRoom": {
|
||||
"enabled": false,
|
||||
"url": "\u65b0\u7248\u672c\u4e0d\u7528\u586b\u5199"
|
||||
},
|
||||
"record": {
|
||||
"device": "",
|
||||
"enabled": false
|
||||
},
|
||||
"tts_enabled": true,
|
||||
"wake_word": "\u4f60\u597d",
|
||||
"wake_word_enabled": false,
|
||||
"wake_word_type": "common"
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import sqlite3
|
||||
import time
|
||||
import threading
|
||||
import functools
|
||||
def synchronized(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
with self.lock:
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
class Authorize_Tb:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.lock = threading.Lock()
|
||||
|
||||
|
||||
|
||||
#初始化
|
||||
def init_tb(self):
|
||||
conn = sqlite3.connect('fay.db')
|
||||
c = conn.cursor()
|
||||
c.execute('''
|
||||
CREATE TABLE IF NOT EXISTS T_Authorize
|
||||
(id INTEGER PRIMARY KEY autoincrement,
|
||||
userid char(100),
|
||||
accesstoken TEXT,
|
||||
expirestime BigInt,
|
||||
createtime Int);
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
#添加
|
||||
@synchronized
|
||||
def add(self,userid,accesstoken,expirestime):
|
||||
conn = sqlite3.connect("fay.db")
|
||||
cur = conn.cursor()
|
||||
cur.execute("insert into T_Authorize (userid,accesstoken,expirestime,createtime) values (?,?,?,?)",(userid,accesstoken,expirestime,int(time.time())))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return cur.lastrowid
|
||||
|
||||
#查询
|
||||
@synchronized
|
||||
def find_by_userid(self,userid):
|
||||
conn = sqlite3.connect("fay.db")
|
||||
cur = conn.cursor()
|
||||
cur.execute("select accesstoken,expirestime from T_Authorize where userid = ? order by id desc limit 1",(userid,))
|
||||
info = cur.fetchone()
|
||||
conn.close()
|
||||
return info
|
||||
|
||||
# 更新token
|
||||
@synchronized
|
||||
def update_by_userid(self, userid, new_accesstoken, new_expirestime):
|
||||
conn = sqlite3.connect("fay.db")
|
||||
cur = conn.cursor()
|
||||
cur.execute("UPDATE T_Authorize SET accesstoken = ?, expirestime = ? WHERE userid = ?",
|
||||
(new_accesstoken, new_expirestime, userid))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -1,93 +0,0 @@
|
||||
import sqlite3
|
||||
import time
|
||||
import threading
|
||||
import functools
|
||||
from utils import util
|
||||
def synchronized(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
with self.lock:
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
__content_tb = None
|
||||
def new_instance():
|
||||
global __content_tb
|
||||
if __content_tb is None:
|
||||
__content_tb = Content_Db()
|
||||
return __content_tb
|
||||
|
||||
|
||||
class Content_Db:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.lock = threading.Lock()
|
||||
|
||||
|
||||
|
||||
#初始化
|
||||
def init_db(self):
|
||||
conn = sqlite3.connect('fay.db')
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS T_Msg
|
||||
(id INTEGER PRIMARY KEY autoincrement,
|
||||
type char(10),
|
||||
way char(10),
|
||||
content TEXT NOT NULL,
|
||||
createtime Int);''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
|
||||
#添加对话
|
||||
@synchronized
|
||||
def add_content(self,type,way,content):
|
||||
conn = sqlite3.connect("fay.db")
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("insert into T_Msg (type,way,content,createtime) values (?,?,?,?)",(type,way,content,int(time.time())))
|
||||
conn.commit()
|
||||
except:
|
||||
util.log(1, "请检查参数是否有误")
|
||||
conn.close()
|
||||
return 0
|
||||
conn.close()
|
||||
return cur.lastrowid
|
||||
|
||||
|
||||
|
||||
#获取对话内容
|
||||
@synchronized
|
||||
def get_list(self,way,order,limit):
|
||||
conn = sqlite3.connect("fay.db")
|
||||
cur = conn.cursor()
|
||||
if(way == 'all'):
|
||||
cur.execute("select type,way,content,createtime,datetime(createtime, 'unixepoch', 'localtime') as timetext from T_Msg order by id "+order+" limit ?",(limit,))
|
||||
elif(way == 'notappended'):
|
||||
cur.execute("select type,way,content,createtime,datetime(createtime, 'unixepoch', 'localtime') as timetext from T_Msg where way != 'appended' order by id "+order+" limit ?",(limit,))
|
||||
else:
|
||||
cur.execute("select type,way,content,createtime,datetime(createtime, 'unixepoch', 'localtime') as timetext from T_Msg where way = ? order by id "+order+" limit ?",(way,limit,))
|
||||
|
||||
list = cur.fetchall()
|
||||
conn.close()
|
||||
return list
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# a = Content_Db()
|
||||
# s = a.get_list('all','desc',10)
|
||||
# print(s)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,564 +0,0 @@
|
||||
import difflib
|
||||
import imp
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import wave
|
||||
import socket
|
||||
import json
|
||||
|
||||
import eyed3
|
||||
from openpyxl import load_workbook
|
||||
import logging
|
||||
|
||||
|
||||
# 适应模型使用
|
||||
import numpy as np
|
||||
# import tensorflow as tf
|
||||
import fay_booter
|
||||
from ai_module import xf_ltp
|
||||
from ai_module.ms_tts_sdk import Speech
|
||||
from core import wsa_server, tts_voice, song_player
|
||||
from core.interact import Interact
|
||||
from core.tts_voice import EnumVoice
|
||||
from scheduler.thread_manager import MyThread
|
||||
from utils import util, storer, config_util
|
||||
from core import qa_service
|
||||
|
||||
import pygame
|
||||
from utils import config_util as cfg
|
||||
from core import content_db
|
||||
from datetime import datetime
|
||||
from ai_module import nlp_cemotion
|
||||
|
||||
from ai_module import nlp_rasa
|
||||
from ai_module import nlp_gpt
|
||||
from ai_module import nlp_yuan
|
||||
from ai_module import yolov8
|
||||
from ai_module import nlp_VisualGLM
|
||||
from ai_module import nlp_lingju
|
||||
from ai_module import nlp_rwkv_api
|
||||
from ai_module import nlp_ChatGLM2
|
||||
from ai_module import nlp_fastgpt
|
||||
|
||||
import platform
|
||||
if platform.system() == "Windows":
|
||||
import sys
|
||||
sys.path.append("test/ovr_lipsync")
|
||||
from test_olipsync import LipSyncGenerator
|
||||
|
||||
modules = {
|
||||
"nlp_yuan": nlp_yuan,
|
||||
"nlp_gpt": nlp_gpt,
|
||||
"nlp_rasa": nlp_rasa,
|
||||
"nlp_VisualGLM": nlp_VisualGLM,
|
||||
"nlp_lingju": nlp_lingju,
|
||||
"nlp_rwkv_api":nlp_rwkv_api,
|
||||
"nlp_chatglm2": nlp_ChatGLM2,
|
||||
"nlp_fastgpt": nlp_fastgpt
|
||||
|
||||
}
|
||||
|
||||
|
||||
def determine_nlp_strategy(sendto,msg):
|
||||
text = ''
|
||||
textlist = []
|
||||
try:
|
||||
util.log(1, '自然语言处理...')
|
||||
tm = time.time()
|
||||
cfg.load_config()
|
||||
if sendto == 2:
|
||||
text = nlp_chatgpt.question(msg)
|
||||
else:
|
||||
module_name = "nlp_" + cfg.key_chat_module
|
||||
selected_module = modules.get(module_name)
|
||||
if selected_module is None:
|
||||
raise RuntimeError('灵聚key、yuan key、gpt key都没有配置!')
|
||||
if cfg.key_chat_module == 'rasa':
|
||||
textlist = selected_module.question(msg)
|
||||
text = textlist[0]['text']
|
||||
else:
|
||||
text = selected_module.question(msg)
|
||||
util.log(1, '自然语言处理完成. 耗时: {} ms'.format(math.floor((time.time() - tm) * 1000)))
|
||||
if text == '哎呀,你这么说我也不懂,详细点呗' or text == '':
|
||||
util.log(1, '[!] 自然语言无语了!')
|
||||
text = '哎呀,你这么说我也不懂,详细点呗'
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
util.log(1, '自然语言处理错误!')
|
||||
text = '哎呀,你这么说我也不懂,详细点呗'
|
||||
|
||||
return text,textlist
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#文本消息处理
|
||||
def send_for_answer(msg,sendto):
|
||||
contentdb = content_db.new_instance()
|
||||
contentdb.add_content('member','send',msg)
|
||||
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"member","content":msg}})
|
||||
textlist = []
|
||||
text = None
|
||||
# 人设问答
|
||||
keyword = qa_service.question('Persona',msg)
|
||||
if keyword is not None:
|
||||
text = config_util.config["attribute"][keyword]
|
||||
|
||||
# 全局问答
|
||||
if text is None:
|
||||
answer = qa_service.question('qa',msg)
|
||||
if answer is not None:
|
||||
text = answer
|
||||
else:
|
||||
text,textlist = determine_nlp_strategy(sendto,msg)
|
||||
|
||||
contentdb.add_content('fay','send',text)
|
||||
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"fay","content":text}})
|
||||
if len(textlist) > 1:
|
||||
i = 1
|
||||
while i < len(textlist):
|
||||
contentdb.add_content('fay','send',textlist[i]['text'])
|
||||
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"fay","content":textlist[i]['text']}})
|
||||
i+= 1
|
||||
return text
|
||||
|
||||
|
||||
class FeiFei:
|
||||
def __init__(self):
|
||||
pygame.mixer.init()
|
||||
self.q_msg = '你叫什么名字?'
|
||||
self.a_msg = 'hi,我叫菲菲,英文名是fay'
|
||||
self.mood = 0.0 # 情绪值
|
||||
self.old_mood = 0.0
|
||||
self.connect = False
|
||||
self.item_index = 0
|
||||
self.deviceSocket = None
|
||||
self.deviceConnect = None
|
||||
|
||||
#启动音频输入输出设备的连接服务
|
||||
self.deviceSocketThread = MyThread(target=self.__accept_audio_device_output_connect)
|
||||
self.deviceSocketThread.start()
|
||||
|
||||
self.X = np.array([1, 0, 0, 0, 0, 0, 0, 0]).reshape(1, -1) # 适应模型变量矩阵
|
||||
# self.W = np.array([0.01577594,1.16119452,0.75828,0.207746,1.25017864,0.1044121,0.4294899,0.2770932]).reshape(-1,1) #适应模型变量矩阵
|
||||
self.W = np.array([0.0, 0.6, 0.1, 0.7, 0.3, 0.0, 0.0, 0.0]).reshape(-1, 1) # 适应模型变量矩阵
|
||||
|
||||
self.wsParam = None
|
||||
self.wss = None
|
||||
self.sp = Speech()
|
||||
self.speaking = False
|
||||
self.last_interact_time = time.time()
|
||||
self.last_speak_data = ''
|
||||
self.interactive = []
|
||||
self.sleep = False
|
||||
self.__running = True
|
||||
self.sp.connect() # 预连接
|
||||
self.last_quest_time = time.time()
|
||||
self.playing = False
|
||||
self.muting = False
|
||||
self.cemotion = None
|
||||
self.stop_say = False
|
||||
|
||||
|
||||
def __play_song(self):
|
||||
self.playing = True
|
||||
song_player.play()
|
||||
self.playing = False
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': ""}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
|
||||
#检查是否命中指令或q&a
|
||||
def __get_answer(self, interleaver, text):
|
||||
if interleaver == "mic":
|
||||
#指令
|
||||
keyword = qa_service.question('command',text)
|
||||
if keyword is not None:
|
||||
if keyword == "playSong":
|
||||
MyThread(target=self.__play_song).start()
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': ""}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
elif keyword == "stop":
|
||||
fay_booter.stop()
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': ""}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
wsa_server.get_web_instance().add_cmd({"liveState": 0})
|
||||
elif keyword == "mute":
|
||||
self.muting = True
|
||||
self.speaking = True
|
||||
self.a_msg = "好的"
|
||||
MyThread(target=self.__say, args=['interact']).start()
|
||||
time.sleep(0.5)
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': ""}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
elif keyword == "unmute":
|
||||
self.muting = False
|
||||
return None
|
||||
elif keyword == "changeVoice":
|
||||
voice = tts_voice.get_voice_of(config_util.config["attribute"]["voice"])
|
||||
for v in tts_voice.get_voice_list():
|
||||
if v != voice:
|
||||
config_util.config["attribute"]["voice"] = v.name
|
||||
break
|
||||
config_util.save_config(config_util.config)
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': ""}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
return "NO_ANSWER"
|
||||
|
||||
if text == '唤醒':
|
||||
return '您好,我是FAY智能助理,有什么可以帮您?'
|
||||
|
||||
# 人设问答
|
||||
keyword = qa_service.question('Persona',text)
|
||||
if keyword is not None:
|
||||
return config_util.config["attribute"][keyword]
|
||||
answer = None
|
||||
# 全局问答
|
||||
answer = qa_service.question('qa',text)
|
||||
if answer is not None:
|
||||
return answer
|
||||
|
||||
def __auto_speak(self):
|
||||
while self.__running:
|
||||
time.sleep(0.8)
|
||||
if self.speaking or self.sleep:
|
||||
continue
|
||||
|
||||
try:
|
||||
if len(self.interactive) > 0:
|
||||
interact: Interact = self.interactive.pop()
|
||||
index = interact.interact_type
|
||||
if index == 1:
|
||||
self.q_msg = interact.data["msg"]
|
||||
self.write_to_file("./logs", "asr_result.txt", self.q_msg)
|
||||
if not config_util.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'question', 'Value': self.q_msg}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
#fay eyes
|
||||
fay_eyes = yolov8.new_instance()
|
||||
if fay_eyes.get_status():#YOLO正在运行
|
||||
person_count, stand_count, sit_count = fay_eyes.get_counts()
|
||||
if person_count < 1: #看不到人,不互动
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": "看不到人,不互动"})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': "看不到人,不互动"}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
continue
|
||||
|
||||
answer = self.__get_answer(interact.interleaver, self.q_msg)#确定是否命中指令或q&a
|
||||
if(self.muting): #静音指令正在执行
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": "静音指令正在执行,不互动"})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': "静音指令正在执行,不互动"}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
continue
|
||||
|
||||
contentdb = content_db.new_instance()
|
||||
contentdb.add_content('member','speak',self.q_msg)
|
||||
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"member","content":self.q_msg}})
|
||||
|
||||
|
||||
text = ''
|
||||
textlist = []
|
||||
self.speaking = True
|
||||
if answer is None:
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": "思考中..."})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': "思考中..."}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
text,textlist = determine_nlp_strategy(1,self.q_msg)
|
||||
elif answer != 'NO_ANSWER': #语音内容没有命中指令,回复q&a内容
|
||||
text = answer
|
||||
self.a_msg = text
|
||||
self.write_to_file("./logs", "answer_result.txt", text)
|
||||
contentdb.add_content('fay','speak',self.a_msg)
|
||||
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"fay","content":self.a_msg}})
|
||||
if len(textlist) > 1:
|
||||
i = 1
|
||||
while i < len(textlist):
|
||||
contentdb.add_content('fay','speak',textlist[i]['text'])
|
||||
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"fay","content":textlist[i]['text']}})
|
||||
i+= 1
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": self.a_msg})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': self.a_msg}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
self.last_speak_data = self.a_msg
|
||||
MyThread(target=self.__say, args=['interact']).start()
|
||||
|
||||
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
|
||||
def write_to_file(self, path, filename, content):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
full_path = os.path.join(path, filename)
|
||||
with open(full_path, 'w', encoding='utf-8') as file:
|
||||
file.write(content)
|
||||
file.flush()
|
||||
os.fsync(file.fileno())
|
||||
|
||||
|
||||
|
||||
def on_interact(self, interact: Interact):
|
||||
self.interactive.append(interact)
|
||||
MyThread(target=self.__update_mood, args=[interact.interact_type]).start()
|
||||
MyThread(target=storer.storage_live_interact, args=[interact]).start()
|
||||
|
||||
|
||||
# 适应模型计算(用于学习真人的性格特质,开源版本暂不使用)
|
||||
def __fay(self, index):
|
||||
if 0 < index < 8:
|
||||
self.X[0][index] += 1
|
||||
# PRED = 1 /(1 + tf.exp(-tf.matmul(tf.constant(self.X,tf.float32), tf.constant(self.W,tf.float32))))
|
||||
PRED = np.sum(self.X.reshape(-1) * self.W.reshape(-1))
|
||||
if 0 < index < 8:
|
||||
print('***PRED:{0}***'.format(PRED))
|
||||
print(self.X.reshape(-1) * self.W.reshape(-1))
|
||||
return PRED
|
||||
|
||||
# 发送情绪
|
||||
def __send_mood(self):
|
||||
while self.__running:
|
||||
time.sleep(3)
|
||||
if not self.sleep and not config_util.config["interact"]["playSound"] and wsa_server.get_instance().isConnect:
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'mood', 'Value': self.mood}}
|
||||
if not self.connect:
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
self.connect = True
|
||||
else:
|
||||
if self.old_mood != self.mood:
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
self.old_mood = self.mood
|
||||
|
||||
else:
|
||||
self.connect = False
|
||||
|
||||
# 更新情绪
|
||||
def __update_mood(self, typeIndex):
|
||||
perception = config_util.config["interact"]["perception"]
|
||||
if typeIndex == 1:
|
||||
try:
|
||||
if cfg.ltp_mode == "cemotion":
|
||||
result = nlp_cemotion.get_sentiment(self.cemotion,self.q_msg)
|
||||
chat_perception = perception["chat"]
|
||||
if result >= 0.5 and result <= 1:
|
||||
self.mood = self.mood + (chat_perception / 150.0)
|
||||
elif result <= 0.2:
|
||||
self.mood = self.mood - (chat_perception / 100.0)
|
||||
else:
|
||||
s = xf_ltp.get_score(self.q_msg)
|
||||
result = xf_ltp.get_sentiment(self.q_msg)
|
||||
chat_perception = perception["chat"]
|
||||
if result == 1:
|
||||
self.mood = self.mood + (chat_perception / 150.0)
|
||||
elif result == -1:
|
||||
self.mood = self.mood - (chat_perception / 100.0)
|
||||
except BaseException as e:
|
||||
self.mood = 0.5
|
||||
print("[System] 情绪更新错误!")
|
||||
print(e)
|
||||
|
||||
elif typeIndex == 2:
|
||||
self.mood = self.mood + (perception["join"] / 100.0)
|
||||
|
||||
elif typeIndex == 3:
|
||||
self.mood = self.mood + (perception["gift"] / 100.0)
|
||||
|
||||
elif typeIndex == 4:
|
||||
self.mood = self.mood + (perception["follow"] / 100.0)
|
||||
|
||||
if self.mood >= 1:
|
||||
self.mood = 1
|
||||
if self.mood <= -1:
|
||||
self.mood = -1
|
||||
|
||||
def __get_mood_voice(self):
|
||||
voice = tts_voice.get_voice_of(config_util.config["attribute"]["voice"])
|
||||
if voice is None:
|
||||
voice = EnumVoice.XIAO_XIAO
|
||||
styleList = voice.value["styleList"]
|
||||
sayType = styleList["calm"]
|
||||
if -1 <= self.mood < -0.5:
|
||||
sayType = styleList["angry"]
|
||||
if -0.5 <= self.mood < -0.1:
|
||||
sayType = styleList["lyrical"]
|
||||
if -0.1 <= self.mood < 0.1:
|
||||
sayType = styleList["calm"]
|
||||
if 0.1 <= self.mood < 0.5:
|
||||
sayType = styleList["assistant"]
|
||||
if 0.5 <= self.mood <= 1:
|
||||
sayType = styleList["cheerful"]
|
||||
return sayType
|
||||
|
||||
# 合成声音
|
||||
def __say(self, styleType):
|
||||
try:
|
||||
if len(self.a_msg) < 1:
|
||||
self.speaking = False
|
||||
else:
|
||||
util.printInfo(1, '菲菲', '({}) {}'.format(self.__get_mood_voice(), self.a_msg))
|
||||
if not config_util.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'text', 'Value': self.a_msg}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
MyThread(target=storer.storage_live_interact, args=[Interact('Fay', 0, {'user': 'Fay', 'msg': self.a_msg})]).start()
|
||||
if config_util.config["source"]["tts_enabled"]:
|
||||
util.log(1, '合成音频...')
|
||||
tm = time.time()
|
||||
result = self.sp.to_sample(self.a_msg, self.__get_mood_voice())
|
||||
util.log(1, '合成音频完成. 耗时: {} ms 文件:{}'.format(math.floor((time.time() - tm) * 1000), result))
|
||||
if result is not None:
|
||||
MyThread(target=self.__send_or_play_audio, args=[result, styleType]).start()
|
||||
return result
|
||||
else:
|
||||
util.log(1, '问答处理总时长:{} ms'.format(math.floor((time.time() - self.last_quest_time) * 1000)))
|
||||
self.speaking = False
|
||||
|
||||
except BaseException as e:
|
||||
print(e)
|
||||
self.speaking = False
|
||||
return None
|
||||
|
||||
def __play_sound(self, file_url):
|
||||
util.log(1, '播放音频...')
|
||||
util.log(1, '问答处理总时长:{} ms'.format(math.floor((time.time() - self.last_quest_time) * 1000)))
|
||||
pygame.mixer.music.load(file_url)
|
||||
pygame.mixer.music.play()
|
||||
|
||||
|
||||
def __send_or_play_audio(self, file_url, say_type):
|
||||
try:
|
||||
try:
|
||||
logging.getLogger('eyed3').setLevel(logging.ERROR)
|
||||
audio_length = eyed3.load(file_url).info.time_secs #mp3音频长度
|
||||
except Exception as e:
|
||||
audio_length = 3
|
||||
|
||||
# with wave.open(file_url, 'rb') as wav_file: #wav音频长度
|
||||
# audio_length = wav_file.getnframes() / float(wav_file.getframerate())
|
||||
# print(audio_length)
|
||||
# if audio_length <= config_util.config["interact"]["maxInteractTime"] or say_type == "script":
|
||||
if config_util.config["interact"]["playSound"]: # 展板播放
|
||||
self.__play_sound(file_url)
|
||||
else:#发送音频给ue和socket
|
||||
#推送ue
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'audio', 'Value': os.path.abspath(file_url), 'Text': self.a_msg, 'Time': audio_length, 'Type': say_type}}
|
||||
#计算lips
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
lip_sync_generator = LipSyncGenerator()
|
||||
viseme_list = lip_sync_generator.generate_visemes(os.path.abspath(file_url))
|
||||
consolidated_visemes = lip_sync_generator.consolidate_visemes(viseme_list)
|
||||
content["Data"]["Lips"] = consolidated_visemes
|
||||
except Exception as e:
|
||||
print(e)
|
||||
util.log(1, "唇型数字生成失败,无法使用新版ue5工程")
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
|
||||
#推送远程音频
|
||||
if self.deviceConnect is not None:
|
||||
try:
|
||||
self.deviceConnect.send(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08') # 发送音频开始标志,同时也检查设备是否在线
|
||||
wavfile = open(os.path.abspath(file_url),'rb')
|
||||
data = wavfile.read(1024)
|
||||
total = 0
|
||||
while data:
|
||||
total += len(data)
|
||||
self.deviceConnect.send(data)
|
||||
data = wavfile.read(1024)
|
||||
time.sleep(0.001)
|
||||
self.deviceConnect.send(b'\x08\x07\x06\x05\x04\x03\x02\x01\x00')# 发送音频结束标志
|
||||
util.log(1, "远程音频发送完成:{}".format(total))
|
||||
except socket.error as serr:
|
||||
util.log(1,"远程音频输入输出设备已经断开:{}".format(serr))
|
||||
wsa_server.get_web_instance().add_cmd({"remote_audio_connect": False})
|
||||
|
||||
|
||||
#打断时取消等待
|
||||
length = 0
|
||||
while(not self.stop_say):
|
||||
if audio_length + 0.01 > length:
|
||||
length = length + 0.01
|
||||
time.sleep(0.01)
|
||||
else:
|
||||
break
|
||||
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': ""}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
if config_util.config["interact"]["playSound"]:
|
||||
util.log(1, '结束播放!')
|
||||
self.speaking = False
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def __device_socket_keep_alive(self):
|
||||
while True:
|
||||
if self.deviceConnect is not None:
|
||||
try:
|
||||
self.deviceConnect.send(b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8')#发送心跳包
|
||||
except Exception as serr:
|
||||
util.log(1,"远程音频输入输出设备已经断开:{}".format(serr))
|
||||
wsa_server.get_web_instance().add_cmd({"remote_audio_connect": False})
|
||||
self.deviceConnect = None
|
||||
time.sleep(1)
|
||||
|
||||
def __accept_audio_device_output_connect(self):
|
||||
self.deviceSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
||||
self.deviceSocket.bind(("0.0.0.0",10001))
|
||||
self.deviceSocket.listen(1)
|
||||
addr = None
|
||||
try:
|
||||
while True:
|
||||
self.deviceConnect,addr=self.deviceSocket.accept() #接受TCP连接,并返回新的套接字与IP地址
|
||||
MyThread(target=self.__device_socket_keep_alive).start() # 开启心跳包检测
|
||||
util.log(1,"远程音频输入输出设备连接上:{}".format(addr))
|
||||
wsa_server.get_web_instance().add_cmd({"remote_audio_connect": True})
|
||||
while self.deviceConnect: #只允许一个设备连接
|
||||
time.sleep(1)
|
||||
except Exception as err:
|
||||
pass
|
||||
|
||||
def set_sleep(self, sleep):
|
||||
self.sleep = sleep
|
||||
|
||||
def start(self):
|
||||
if cfg.ltp_mode == "cemotion":
|
||||
from cemotion import Cemotion
|
||||
self.cemotion = Cemotion()
|
||||
MyThread(target=self.__send_mood).start()
|
||||
MyThread(target=self.__auto_speak).start()
|
||||
|
||||
|
||||
def stop(self):
|
||||
self.__running = False
|
||||
song_player.stop()
|
||||
self.speaking = False
|
||||
self.playing = False
|
||||
self.sp.close()
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': ""}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
if self.deviceConnect is not None:
|
||||
self.deviceConnect.close()
|
||||
self.deviceConnect = None
|
||||
if self.deviceSocket is not None:
|
||||
self.deviceSocket.close()
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
class Interact:
|
||||
|
||||
def __init__(self, interleaver: str, interact_type: int, data: dict):
|
||||
self.interleaver = interleaver
|
||||
self.interact_type = interact_type
|
||||
self.data = data
|
||||
@@ -1,94 +0,0 @@
|
||||
|
||||
from openpyxl import load_workbook
|
||||
import difflib
|
||||
from utils import config_util as cfg
|
||||
from scheduler.thread_manager import MyThread
|
||||
import shlex
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
def question(query_type,text):
|
||||
qa = QAService()
|
||||
answer = qa.question(query_type,text)
|
||||
return answer
|
||||
|
||||
def run_script(command):
|
||||
args = shlex.split(command) # 分割命令行参数
|
||||
subprocess.Popen(args)
|
||||
|
||||
class QAService:
|
||||
|
||||
def __init__(self):
|
||||
# 人设提问关键字
|
||||
self.attribute_keyword = [
|
||||
[['你叫什么名字', '你的名字是什么'], 'name'],
|
||||
[['你是男的还是女的', '你是男生还是女生', '你的性别是什么', '你是男生吗', '你是女生吗', '你是男的吗', '你是女的吗', '你是男孩子吗', '你是女孩子吗', ], 'gender', ],
|
||||
[['你今年多大了', '你多大了', '你今年多少岁', '你几岁了', '你今年几岁了', '你今年几岁了', '你什么时候出生', '你的生日是什么', '你的年龄'], 'age', ],
|
||||
[['你的家乡在哪', '你的家乡是什么', '你家在哪', '你住在哪', '你出生在哪', '你的出生地在哪', '你的出生地是什么', ], 'birth', ],
|
||||
[['你的生肖是什么', '你属什么', ], 'zodiac', ],
|
||||
[['你是什么座', '你是什么星座', '你的星座是什么', ], 'constellation', ],
|
||||
[['你是做什么的', '你的职业是什么', '你是干什么的', '你的职位是什么', '你的工作是什么', '你是做什么工作的'], 'job', ],
|
||||
[['你的爱好是什么', '你有爱好吗', '你喜欢什么', '你喜欢做什么'], 'hobby'],
|
||||
[['联系方式', '联系你们', '怎么联系客服', '有没有客服'], 'contact']
|
||||
]
|
||||
|
||||
self.command_keyword = [
|
||||
[['关闭', '再见', '你走吧'], 'stop'],
|
||||
[['静音', '闭嘴', '我想静静'], 'mute'],
|
||||
[['取消静音', '你在哪呢', '你可以说话了'], 'unmute'],
|
||||
[['换个性别', '换个声音'], 'changeVoice']
|
||||
]
|
||||
|
||||
def question(self, query_type, text):
|
||||
if query_type == 'qa':
|
||||
answer_dict = self.__read_qna(cfg.config['interact']['QnA'])
|
||||
answer, action = self.__get_keyword(answer_dict, text, query_type)
|
||||
if action:
|
||||
MyThread(target=self.__run, args=[action]).start()
|
||||
return answer
|
||||
|
||||
elif query_type == 'Persona':
|
||||
answer_dict = self.attribute_keyword
|
||||
answer, action = self.__get_keyword(answer_dict, text, query_type)
|
||||
elif query_type == 'command':
|
||||
answer, action = self.__get_keyword(self.command_keyword, text, query_type)
|
||||
return answer
|
||||
|
||||
def __run(self,action):
|
||||
time.sleep(2)
|
||||
run_script(action)
|
||||
|
||||
def __read_qna(self, filename):
|
||||
qna = []
|
||||
try:
|
||||
wb = load_workbook(filename)
|
||||
sheet = wb.active
|
||||
for row in sheet.iter_rows(min_row=2, values_only=True):
|
||||
if len(row) >= 2:
|
||||
qna.append([row[0].split(";"), row[1], row[2] if len(row) >= 3 else None])
|
||||
except BaseException as e:
|
||||
print(f"无法读取Q&A文件 {filename} -> {e}")
|
||||
return qna
|
||||
|
||||
def __get_keyword(self, keyword_dict, text, query_type):
|
||||
last_similar = 0
|
||||
last_answer = ''
|
||||
last_action = ''
|
||||
for qa in keyword_dict:
|
||||
for quest in qa[0]:
|
||||
similar = self.__string_similar(text, quest)
|
||||
if quest in text:
|
||||
similar += 0.3
|
||||
if similar > last_similar:
|
||||
last_similar = similar
|
||||
last_answer = qa[1]
|
||||
if query_type == "qa":
|
||||
last_action = qa[2]
|
||||
if last_similar >= 0.6:
|
||||
return last_answer, last_action
|
||||
return None, None
|
||||
|
||||
def __string_similar(self, s1, s2):
|
||||
return difflib.SequenceMatcher(None, s1, s2).quick_ratio()
|
||||
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
import audioop
|
||||
import math
|
||||
import time
|
||||
import threading
|
||||
from abc import abstractmethod
|
||||
|
||||
from ai_module.ali_nls import ALiNls
|
||||
from ai_module.funasr import FunASR
|
||||
from core import wsa_server
|
||||
from scheduler.thread_manager import MyThread
|
||||
from utils import util
|
||||
from utils import config_util as cfg
|
||||
import numpy as np
|
||||
# 启动时间 (秒)
|
||||
_ATTACK = 0.2
|
||||
|
||||
# 释放时间 (秒)
|
||||
_RELEASE = 0.75
|
||||
|
||||
|
||||
class Recorder:
|
||||
|
||||
def __init__(self, fay):
|
||||
self.__fay = fay
|
||||
|
||||
|
||||
|
||||
self.__running = True
|
||||
self.__processing = False
|
||||
self.__history_level = []
|
||||
self.__history_data = []
|
||||
self.__dynamic_threshold = 0.5 # 声音识别的音量阈值
|
||||
|
||||
self.__MAX_LEVEL = 25000
|
||||
self.__MAX_BLOCK = 100
|
||||
|
||||
#Edit by xszyou in 20230516:增加本地asr
|
||||
self.ASRMode = cfg.ASR_mode
|
||||
self.__aLiNls = self.asrclient()
|
||||
self.is_awake = False
|
||||
self.wakeup_matched = False
|
||||
if cfg.config['source']['wake_word_enabled']:
|
||||
self.timer = threading.Timer(60, self.reset_wakeup_status) # 60秒后执行reset_wakeup_status方法
|
||||
|
||||
|
||||
def asrclient(self):
|
||||
if self.ASRMode == "ali":
|
||||
asrcli = ALiNls()
|
||||
elif self.ASRMode == "funasr":
|
||||
asrcli = FunASR()
|
||||
return asrcli
|
||||
|
||||
|
||||
|
||||
def __get_history_average(self, number):
|
||||
total = 0
|
||||
num = 0
|
||||
for i in range(len(self.__history_level) - 1, -1, -1):
|
||||
level = self.__history_level[i]
|
||||
total += level
|
||||
num += 1
|
||||
if num >= number:
|
||||
break
|
||||
return total / num
|
||||
|
||||
def __get_history_percentage(self, number):
|
||||
return (self.__get_history_average(number) / self.__MAX_LEVEL) * 1.05 + 0.02
|
||||
|
||||
def __print_level(self, level):
|
||||
text = ""
|
||||
per = level / self.__MAX_LEVEL
|
||||
if per > 1:
|
||||
per = 1
|
||||
bs = int(per * self.__MAX_BLOCK)
|
||||
for i in range(bs):
|
||||
text += "#"
|
||||
for i in range(self.__MAX_BLOCK - bs):
|
||||
text += "-"
|
||||
print(text + " [" + str(int(per * 100)) + "%]")
|
||||
|
||||
def reset_wakeup_status(self):
|
||||
self.wakeup_matched = False
|
||||
|
||||
def __waitingResult(self, iat: asrclient):
|
||||
if self.__fay.playing:
|
||||
return
|
||||
self.processing = True
|
||||
t = time.time()
|
||||
tm = time.time()
|
||||
# 等待结果返回
|
||||
while not iat.done and time.time() - t < 1:
|
||||
time.sleep(0.01)
|
||||
text = iat.finalResults
|
||||
util.log(1, "语音处理完成! 耗时: {} ms".format(math.floor((time.time() - tm) * 1000)))
|
||||
if len(text) > 0:
|
||||
if cfg.config['source']['wake_word_enabled']:
|
||||
|
||||
if cfg.config['source']['wake_word_type'] == 'common':
|
||||
|
||||
if not self.wakeup_matched or self.__fay.speaking:
|
||||
#唤醒词判断
|
||||
wake_word = cfg.config['source']['wake_word']
|
||||
wake_word_list = wake_word.split(',')
|
||||
wake_up = False
|
||||
for word in wake_word_list:
|
||||
if word in text:
|
||||
wake_up = True
|
||||
if wake_up:
|
||||
self.wakeup_matched = True # 唤醒成功
|
||||
util.log(1, "唤醒成功!")
|
||||
self.__fay.stop_say = True
|
||||
self.on_speaking(text)
|
||||
self.__fay.stop_say = False
|
||||
self.processing = False
|
||||
self.timer.cancel() # 取消之前的计时器任务
|
||||
else:
|
||||
util.log(1, "[!] 待唤醒!")
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
else:
|
||||
self.on_speaking(text)
|
||||
self.processing = False
|
||||
self.timer.cancel() # 取消之前的计时器任务
|
||||
self.timer = threading.Timer(60, self.reset_wakeup_status) # 重设计时器为60秒
|
||||
self.timer.start()
|
||||
elif cfg.config['source']['wake_word_type'] == 'front':
|
||||
wake_word = cfg.config['source']['wake_word']
|
||||
wake_word_list = wake_word.split(',')
|
||||
wake_up = False
|
||||
for word in wake_word_list:
|
||||
if text.startswith(word):
|
||||
wake_up_word = word
|
||||
wake_up = True
|
||||
break
|
||||
if wake_up:
|
||||
util.log(1, "唤醒成功!")
|
||||
#去除唤醒词后语句
|
||||
question = text[len(wake_up_word):].lstrip()
|
||||
self.__fay.stop_say = True
|
||||
self.on_speaking(question)
|
||||
self.__fay.stop_say = False
|
||||
self.processing = False
|
||||
else:
|
||||
util.log(1, "[!] 待唤醒!")
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
|
||||
|
||||
else:
|
||||
self.on_speaking(text)
|
||||
self.processing = False
|
||||
else:
|
||||
if self.wakeup_matched:
|
||||
self.wakeup_matched = False
|
||||
util.log(1, "[!] 语音未检测到内容!")
|
||||
self.processing = False
|
||||
self.dynamic_threshold = self.__get_history_percentage(30)
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
if not cfg.config["interact"]["playSound"]: # 非展板播放
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': ""}}
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
|
||||
def __record(self):
|
||||
try:
|
||||
stream = self.get_stream() #把get stream的方式封装出来方便实现麦克风录制及网络流等不同的流录制子类
|
||||
except Exception as e:
|
||||
print(e)
|
||||
util.log(1, "请检查设备是否有误,再重新启动!")
|
||||
return
|
||||
isSpeaking = False
|
||||
last_mute_time = time.time()
|
||||
last_speaking_time = time.time()
|
||||
data = None
|
||||
while self.__running:
|
||||
try:
|
||||
data = stream.read(1024, exception_on_overflow=False)
|
||||
except Exception as e:
|
||||
data = None
|
||||
print(e)
|
||||
util.log(1, "请检查设备是否有误,再重新启动!")
|
||||
return
|
||||
if not data:
|
||||
continue
|
||||
|
||||
|
||||
if cfg.config['source']['record']['enabled'] and not self.is_remote():
|
||||
if len(cfg.config['source']['record'])<3:
|
||||
channels = 1
|
||||
else:
|
||||
channels = int(cfg.config['source']['record']['channels'])
|
||||
#只获取第一声道
|
||||
data = np.frombuffer(data, dtype=np.int16)
|
||||
data = np.reshape(data, (-1, channels)) # reshaping the array to split the channels
|
||||
mono = data[:, 0] # taking the first channel
|
||||
data = mono.tobytes()
|
||||
|
||||
|
||||
level = audioop.rms(data, 2)
|
||||
if len(self.__history_data) >= 5:
|
||||
self.__history_data.pop(0)
|
||||
if len(self.__history_level) >= 500:
|
||||
self.__history_level.pop(0)
|
||||
self.__history_data.append(data)
|
||||
self.__history_level.append(level)
|
||||
|
||||
percentage = level / self.__MAX_LEVEL
|
||||
history_percentage = self.__get_history_percentage(30)
|
||||
|
||||
if history_percentage > self.__dynamic_threshold:
|
||||
self.__dynamic_threshold += (history_percentage - self.__dynamic_threshold) * 0.0025
|
||||
elif history_percentage < self.__dynamic_threshold:
|
||||
self.__dynamic_threshold += (history_percentage - self.__dynamic_threshold) * 1
|
||||
|
||||
soon = False
|
||||
can_listen = False
|
||||
if cfg.config['source']['wake_word_enabled']:
|
||||
can_listen = True
|
||||
else:
|
||||
if not self.__fay.speaking:
|
||||
can_listen = True
|
||||
else:
|
||||
can_listen = False
|
||||
|
||||
if percentage > self.__dynamic_threshold and can_listen:
|
||||
last_speaking_time = time.time()
|
||||
if not self.__processing and not isSpeaking and time.time() - last_mute_time > _ATTACK:
|
||||
soon = True #
|
||||
isSpeaking = True #用户正在说话
|
||||
util.log(3, "聆听中...")
|
||||
self.__aLiNls = self.asrclient()
|
||||
try:
|
||||
self.__aLiNls.start()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
for buf in self.__history_data:
|
||||
self.__aLiNls.send(buf)
|
||||
else:
|
||||
last_mute_time = time.time()
|
||||
if isSpeaking:
|
||||
if time.time() - last_speaking_time > _RELEASE:
|
||||
isSpeaking = False
|
||||
self.__aLiNls.end()
|
||||
util.log(1, "语音处理中...")
|
||||
self.__fay.last_quest_time = time.time()
|
||||
self.__waitingResult(self.__aLiNls)
|
||||
if not soon and isSpeaking:
|
||||
self.__aLiNls.send(data)
|
||||
|
||||
|
||||
def set_processing(self, processing):
|
||||
self.__processing = processing
|
||||
|
||||
def start(self):
|
||||
MyThread(target=self.__record).start()
|
||||
|
||||
def stop(self):
|
||||
self.__running = False
|
||||
self.__aLiNls.end()
|
||||
|
||||
@abstractmethod
|
||||
def on_speaking(self, text):
|
||||
pass
|
||||
|
||||
#TODO Edit by xszyou on 20230113:把流的获取方式封装出来方便实现麦克风录制及网络流等不同的流录制子类
|
||||
@abstractmethod
|
||||
def get_stream(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_remote(self):
|
||||
pass
|
||||
@@ -1,68 +0,0 @@
|
||||
import os.path
|
||||
import random
|
||||
import time
|
||||
|
||||
import eyed3
|
||||
import requests
|
||||
import re
|
||||
import pygame
|
||||
|
||||
from utils import util
|
||||
|
||||
__playing = False
|
||||
|
||||
song_name = ""
|
||||
|
||||
|
||||
def __play_song(song_id: str):
|
||||
file_url = "./songs/{}.mp3".format(song_name)
|
||||
if not os.path.exists("./songs"):
|
||||
os.mkdir("./songs")
|
||||
if not os.path.exists(file_url):
|
||||
url = "https://music.163.com/song/media/outer/url?id=" + song_id
|
||||
response = requests.request("GET", url)
|
||||
with open(file_url, "wb") as mp3:
|
||||
mp3.write(response.content)
|
||||
pygame.mixer.music.load(file_url)
|
||||
pygame.mixer.music.play()
|
||||
util.log(3, "正在播放 {}".format(song_name))
|
||||
audio_length = eyed3.load(file_url).info.time_secs
|
||||
last_time = time.time()
|
||||
while __playing and time.time() - last_time < audio_length:
|
||||
time.sleep(0.05)
|
||||
pass
|
||||
|
||||
|
||||
def __random_song():
|
||||
# 歌单列表
|
||||
id_list = [
|
||||
"3778678", # 热歌榜
|
||||
# "1978921795", # 电音榜
|
||||
# "10520166", # 国电榜
|
||||
# "991319590", # 说唱榜
|
||||
]
|
||||
url = "https://music.163.com/discover/toplist?id=" + id_list[random.randrange(0, len(id_list))]
|
||||
response = requests.request("GET", url)
|
||||
song_list = re.findall("<li><a href=\"/song\?id=([0-9]*)\">(.*?)</a></li>", response.text)
|
||||
index = random.randrange(0, len(song_list))
|
||||
return song_list[index]
|
||||
|
||||
|
||||
def play():
|
||||
global __playing
|
||||
global song_name
|
||||
__playing = True
|
||||
while __playing:
|
||||
song = __random_song()
|
||||
try:
|
||||
song_name = song[1]
|
||||
__play_song(song[0])
|
||||
break
|
||||
except Exception as e:
|
||||
util.log(1, "无法播放 {} 可能需要VIP".format(song[1]))
|
||||
|
||||
|
||||
def stop():
|
||||
global __playing
|
||||
__playing = False
|
||||
pygame.mixer.music.stop()
|
||||
@@ -1,83 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class EnumVoice(Enum):
|
||||
XIAO_XIAO = {
|
||||
"name": "晓晓",
|
||||
"voiceName": "zh-CN-XiaoxiaoNeural",
|
||||
"styleList": {
|
||||
"angry": "angry",
|
||||
"lyrical": "lyrical",
|
||||
"calm": "gentle",
|
||||
"assistant": "affectionate",
|
||||
"cheerful": "cheerful"
|
||||
}
|
||||
}
|
||||
YUN_XI = {
|
||||
"name": "云溪",
|
||||
"voiceName": "zh-CN-YunxiNeural",
|
||||
"styleList": {
|
||||
"angry": "angry",
|
||||
"lyrical": "disgruntled",
|
||||
"calm": "calm",
|
||||
"assistant": "assistant",
|
||||
"cheerful": "cheerful"
|
||||
}
|
||||
}
|
||||
YUN_JIAN = {
|
||||
"name": "云健",
|
||||
"voiceName": "zh-CN-YunjianNeural",
|
||||
"styleList": {
|
||||
"angry": "angry",
|
||||
"lyrical": "disgruntled",
|
||||
"calm": "calm",
|
||||
"assistant": "assistant",
|
||||
"cheerful": "cheerful"
|
||||
}
|
||||
}
|
||||
XIAO_YI = {
|
||||
"name": "晓伊",
|
||||
"voiceName": "zh-CN-XiaoyiNeural",
|
||||
"styleList": {
|
||||
"angry": "angry",
|
||||
"lyrical": "lyrical",
|
||||
"calm": "gentle",
|
||||
"assistant": "affectionate",
|
||||
"cheerful": "cheerful"
|
||||
}
|
||||
}
|
||||
YUN_YANG = {
|
||||
"name": "云阳",
|
||||
"voiceName": "zh-CN-YunyangNeural",
|
||||
"styleList": {
|
||||
"angry": "angry",
|
||||
"lyrical": "lyrical",
|
||||
"calm": "gentle",
|
||||
"assistant": "affectionate",
|
||||
"cheerful": "cheerful"
|
||||
}
|
||||
}
|
||||
YUN_XIA = {
|
||||
"name": "云夏",
|
||||
"voiceName": "zh-CN-YunxiaNeural",
|
||||
"styleList": {
|
||||
"angry": "angry",
|
||||
"lyrical": "lyrical",
|
||||
"calm": "gentle",
|
||||
"assistant": "affectionate",
|
||||
"cheerful": "cheerful"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
def get_voice_list():
|
||||
return [EnumVoice.YUN_XI, EnumVoice.XIAO_XIAO, EnumVoice.YUN_JIAN, EnumVoice.XIAO_YI, EnumVoice.YUN_YANG, EnumVoice.YUN_XIA]
|
||||
|
||||
|
||||
def get_voice_of(name):
|
||||
for voice in get_voice_list():
|
||||
if voice.name == name:
|
||||
return voice
|
||||
return None
|
||||
@@ -1,232 +0,0 @@
|
||||
from asyncio import AbstractEventLoop
|
||||
|
||||
import websockets
|
||||
import asyncio
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from websockets.legacy.server import Serve
|
||||
|
||||
from scheduler.thread_manager import MyThread
|
||||
from utils import util
|
||||
|
||||
|
||||
class MyServer:
|
||||
def __init__(self, host='0.0.0.0', port=10000):
|
||||
self.__host = host # ip
|
||||
self.__port = port # 端口号
|
||||
self.__listCmd = [] # 要发送的信息的列表
|
||||
self.__server: Serve = None
|
||||
self.__event_loop: AbstractEventLoop = None
|
||||
self.__running = True
|
||||
self.__pending = None
|
||||
self.isConnect = False
|
||||
|
||||
def __del__(self):
|
||||
self.stop_server()
|
||||
|
||||
# 接收处理
|
||||
async def __consumer_handler(self, websocket, path):
|
||||
try:
|
||||
async for message in websocket:
|
||||
await asyncio.sleep(0.01)
|
||||
await self.__consumer(message)
|
||||
except websockets.exceptions.ConnectionClosedError as e:
|
||||
util.log(1, f"WebSocket 连接关闭: {e}")
|
||||
self.isConnect = False
|
||||
self.on_close_handler()
|
||||
|
||||
async def __producer_handler(self, websocket, path):
|
||||
try:
|
||||
while self.__running:
|
||||
await asyncio.sleep(0.01)
|
||||
message = await self.__producer()
|
||||
if message:
|
||||
await websocket.send(message)
|
||||
except websockets.exceptions.ConnectionClosedError as e:
|
||||
util.log(1, f"WebSocket 连接关闭: {e}")
|
||||
self.isConnect = False
|
||||
self.on_close_handler()
|
||||
|
||||
async def __handler(self, websocket, path):
|
||||
self.isConnect = True
|
||||
util.log(1,"websocket连接上:{}".format(self.__port))
|
||||
self.on_connect_handler()
|
||||
consumer_task = asyncio.ensure_future(self.__consumer_handler(websocket, path))#接收
|
||||
producer_task = asyncio.ensure_future(self.__producer_handler(websocket, path))#发送
|
||||
done, self.__pending = await asyncio.wait([consumer_task, producer_task], return_when=asyncio.FIRST_COMPLETED, )
|
||||
for task in self.__pending:
|
||||
task.cancel()
|
||||
self.isConnect = False
|
||||
util.log(1,"websocket连接断开:{}".format(self.__port))
|
||||
self.on_close_handler()
|
||||
|
||||
async def __consumer(self, message):
|
||||
self.on_revice_handler(message)
|
||||
|
||||
async def __producer(self):
|
||||
if len(self.__listCmd) > 0:
|
||||
message = self.on_send_handler(self.__listCmd.pop(0))
|
||||
return message
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
#Edit by xszyou on 20230113:通过继承此类来实现服务端的接收后处理逻辑
|
||||
@abstractmethod
|
||||
def on_revice_handler(self, message):
|
||||
pass
|
||||
|
||||
#Edit by xszyou on 20230114:通过继承此类来实现服务端的连接处理逻辑
|
||||
@abstractmethod
|
||||
def on_connect_handler(self):
|
||||
pass
|
||||
|
||||
#Edit by xszyou on 20230804:通过继承此类来实现服务端的发送前的处理逻辑
|
||||
@abstractmethod
|
||||
def on_send_handler(self, message):
|
||||
return message
|
||||
|
||||
#Edit by xszyou on 20230816:通过继承此类来实现服务端的断开后的处理逻辑
|
||||
@abstractmethod
|
||||
def on_close_handler(self):
|
||||
pass
|
||||
|
||||
# 创建server
|
||||
def __connect(self):
|
||||
self.__event_loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.__event_loop)
|
||||
self.__isExecute = True
|
||||
if self.__server:
|
||||
util.log(1, 'server already exist')
|
||||
return
|
||||
self.__server = websockets.serve(self.__handler, self.__host, self.__port)
|
||||
asyncio.get_event_loop().run_until_complete(self.__server)
|
||||
asyncio.get_event_loop().run_forever()
|
||||
|
||||
# 往要发送的命令列表中,添加命令
|
||||
def add_cmd(self, content):
|
||||
if not self.__running:
|
||||
return
|
||||
jsonObj = json.dumps(content)
|
||||
self.__listCmd.append(jsonObj)
|
||||
# util.log('命令 {}'.format(content))
|
||||
|
||||
# 开启服务
|
||||
def start_server(self):
|
||||
MyThread(target=self.__connect).start()
|
||||
|
||||
# 关闭服务
|
||||
def stop_server(self):
|
||||
self.__running = False
|
||||
self.isConnect = False
|
||||
if self.__server is None:
|
||||
return
|
||||
self.__server.ws_server.close()
|
||||
self.__server = None
|
||||
try:
|
||||
all_tasks = asyncio.all_tasks(self.__event_loop)
|
||||
for task in all_tasks:
|
||||
while not task.cancel():
|
||||
util.log(1, "无法关闭!")
|
||||
self.__event_loop.stop()
|
||||
self.__event_loop.close()
|
||||
except BaseException as e:
|
||||
util.log(1, "Error: {}".format(e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ui端server
|
||||
class WebServer(MyServer):
|
||||
def __init__(self, host='0.0.0.0', port=10000):
|
||||
super().__init__(host, port)
|
||||
|
||||
def on_revice_handler(self, message):
|
||||
pass
|
||||
|
||||
def on_connect_handler(self):
|
||||
self.add_cmd({"panelMsg": "使用提示:Fay可以独立使用,启动数字人将自动对接。"})
|
||||
|
||||
def on_send_handler(self, message):
|
||||
return message
|
||||
|
||||
def on_close_handler(self):
|
||||
pass
|
||||
|
||||
#数字人端server
|
||||
class HumanServer(MyServer):
|
||||
def __init__(self, host='0.0.0.0', port=10000):
|
||||
super().__init__(host, port)
|
||||
|
||||
def on_revice_handler(self, message):
|
||||
pass
|
||||
|
||||
def on_connect_handler(self):
|
||||
web_server_instance = get_web_instance()
|
||||
web_server_instance.add_cmd({"is_connect": True})
|
||||
|
||||
|
||||
def on_send_handler(self, message):
|
||||
# util.log(1, '向human发送 {}'.format(message))
|
||||
if not self.isConnect:
|
||||
return None
|
||||
return message
|
||||
|
||||
def on_close_handler(self):
|
||||
web_server_instance = get_web_instance()
|
||||
web_server_instance.add_cmd({"is_connect": False})
|
||||
|
||||
|
||||
|
||||
#测试
|
||||
class TestServer(MyServer):
|
||||
def __init__(self, host='0.0.0.0', port=10000):
|
||||
super().__init__(host, port)
|
||||
|
||||
def on_revice_handler(self, message):
|
||||
print(message)
|
||||
|
||||
def on_connect_handler(self):
|
||||
print("连接上了")
|
||||
|
||||
def on_send_handler(self, message):
|
||||
return message
|
||||
|
||||
def on_close_handler(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
#单例
|
||||
|
||||
__instance: MyServer = None
|
||||
__web_instance: MyServer = None
|
||||
|
||||
|
||||
def new_instance(host='0.0.0.0', port=10000) -> MyServer:
|
||||
global __instance
|
||||
if __instance is None:
|
||||
__instance = HumanServer(host, port)
|
||||
return __instance
|
||||
|
||||
|
||||
def new_web_instance(host='0.0.0.0', port=10000) -> MyServer:
|
||||
global __web_instance
|
||||
if __web_instance is None:
|
||||
__web_instance = WebServer(host, port)
|
||||
return __web_instance
|
||||
|
||||
|
||||
def get_instance() -> MyServer:
|
||||
return __instance
|
||||
|
||||
|
||||
def get_web_instance() -> MyServer:
|
||||
return __web_instance
|
||||
|
||||
if __name__ == '__main__':
|
||||
testServer = TestServer(host='0.0.0.0', port=10000)
|
||||
testServer.start_server()
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,239 +0,0 @@
|
||||
import time
|
||||
import pyaudio
|
||||
from core.interact import Interact
|
||||
from core.recorder import Recorder
|
||||
from core.fay_core import FeiFei
|
||||
from scheduler.thread_manager import MyThread
|
||||
from utils import util, config_util, stream_util, ngrok_util
|
||||
from core.wsa_server import MyServer
|
||||
from scheduler.thread_manager import MyThread
|
||||
|
||||
feiFei: FeiFei = None
|
||||
recorderListener: Recorder = None
|
||||
|
||||
__running = False
|
||||
|
||||
#录制麦克风音频输入并传给aliyun
|
||||
class RecorderListener(Recorder):
|
||||
|
||||
def __init__(self, device, fei):
|
||||
self.__device = device
|
||||
self.__RATE = 16000
|
||||
self.__FORMAT = pyaudio.paInt16
|
||||
self.__running = False
|
||||
|
||||
super().__init__(fei)
|
||||
|
||||
def on_speaking(self, text):
|
||||
if len(text) > 1:
|
||||
interact = Interact("mic", 1, {'user': '', 'msg': text})
|
||||
util.printInfo(3, "语音", '{}'.format(interact.data["msg"]), time.time())
|
||||
feiFei.on_interact(interact)
|
||||
time.sleep(2)
|
||||
|
||||
def get_stream(self):
|
||||
self.paudio = pyaudio.PyAudio()
|
||||
device_id,devInfo = self.__findInternalRecordingDevice(self.paudio)
|
||||
if device_id < 0:
|
||||
return
|
||||
channels = int(devInfo['maxInputChannels'])
|
||||
if channels == 0:
|
||||
util.log(1, '请检查设备是否有误,再重新启动!')
|
||||
return
|
||||
self.stream = self.paudio.open(input_device_index=device_id, rate=self.__RATE, format=self.__FORMAT, channels=channels, input=True)
|
||||
self.__running = True
|
||||
MyThread(target=self.__pyaudio_clear).start()
|
||||
return self.stream
|
||||
|
||||
def __pyaudio_clear(self):
|
||||
while self.__running:
|
||||
time.sleep(30)
|
||||
|
||||
|
||||
def __findInternalRecordingDevice(self, p):
|
||||
for i in range(p.get_device_count()):
|
||||
devInfo = p.get_device_info_by_index(i)
|
||||
if devInfo['name'].find(self.__device) >= 0 and devInfo['hostApi'] == 0:
|
||||
config_util.config['source']['record']['channels'] = devInfo['maxInputChannels']
|
||||
config_util.save_config(config_util.config)
|
||||
return i, devInfo
|
||||
util.log(1, '[!] 无法找到内录设备!')
|
||||
return -1, None
|
||||
|
||||
def stop(self):
|
||||
super().stop()
|
||||
self.__running = False
|
||||
try:
|
||||
self.stream.stop_stream()
|
||||
self.stream.close()
|
||||
self.paudio.terminate()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
util.log(1, "请检查设备是否有误,再重新启动!")
|
||||
|
||||
def is_remote(self):
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
#Edit by xszyou on 20230113:录制远程设备音频输入并传给aliyun
|
||||
class DeviceInputListener(Recorder):
|
||||
def __init__(self, fei):
|
||||
super().__init__(fei)
|
||||
self.__running = True
|
||||
self.ngrok = None
|
||||
self.streamCache = None
|
||||
self.thread = MyThread(target=self.run)
|
||||
self.thread.start() #启动远程音频输入设备监听线程
|
||||
|
||||
def run(self):
|
||||
#启动ngork
|
||||
self.streamCache = stream_util.StreamCache(1024*1024*20)
|
||||
if config_util.key_ngrok_cc_id and config_util.key_ngrok_cc_id is not None and config_util.key_ngrok_cc_id.strip() != "":
|
||||
MyThread(target=self.start_ngrok, args=[config_util.key_ngrok_cc_id]).start()
|
||||
addr = None
|
||||
while self.__running:
|
||||
try:
|
||||
|
||||
data = b""
|
||||
while feiFei.deviceConnect:
|
||||
data = feiFei.deviceConnect.recv(1024)
|
||||
self.streamCache.write(data)
|
||||
time.sleep(0.005)
|
||||
self.streamCache.clear()
|
||||
|
||||
except Exception as err:
|
||||
pass
|
||||
time.sleep(1)
|
||||
|
||||
def on_speaking(self, text):
|
||||
global feiFei
|
||||
|
||||
if len(text) > 1:
|
||||
interact = Interact("mic", 1, {'user': '', 'msg': text})
|
||||
util.printInfo(3, "语音", '{}'.format(interact.data["msg"]), time.time())
|
||||
feiFei.on_interact(interact)
|
||||
time.sleep(2)
|
||||
|
||||
#recorder会等待stream不为空才开始录音
|
||||
def get_stream(self):
|
||||
while not feiFei.deviceConnect:
|
||||
time.sleep(1)
|
||||
pass
|
||||
return self.streamCache
|
||||
|
||||
def stop(self):
|
||||
super().stop()
|
||||
self.__running = False
|
||||
if config_util.key_ngrok_cc_id and config_util.key_ngrok_cc_id is not None and config_util.key_ngrok_cc_id.strip() != "":
|
||||
self.ngrok.stop()
|
||||
|
||||
def start_ngrok(self, clientId):
|
||||
self.ngrok = ngrok_util.NgrokCilent(clientId)
|
||||
self.ngrok.start()
|
||||
|
||||
def is_remote(self):
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
def console_listener():
|
||||
global feiFei
|
||||
while __running:
|
||||
text = input()
|
||||
args = text.split(' ')
|
||||
|
||||
if len(args) == 0 or len(args[0]) == 0:
|
||||
continue
|
||||
|
||||
if args[0] == 'help':
|
||||
util.log(1, 'in <msg> \t通过控制台交互')
|
||||
util.log(1, 'restart \t重启服务')
|
||||
util.log(1, 'stop \t\t关闭服务')
|
||||
|
||||
elif args[0] == 'stop':
|
||||
stop()
|
||||
break
|
||||
|
||||
elif args[0] == 'restart':
|
||||
stop()
|
||||
time.sleep(0.1)
|
||||
start()
|
||||
|
||||
elif args[0] == 'in':
|
||||
if len(args) == 1:
|
||||
util.log(1, '错误的参数!')
|
||||
msg = text[3:len(text)]
|
||||
util.printInfo(3, "控制台", '{}: {}'.format('控制台', msg))
|
||||
feiFei.last_quest_time = time.time()
|
||||
interact = Interact("console", 1, {'user': '', 'msg': msg})
|
||||
thr = MyThread(target=feiFei.on_interact, args=[interact])
|
||||
thr.start()
|
||||
thr.join()
|
||||
|
||||
else:
|
||||
util.log(1, '未知命令!使用 \'help\' 获取帮助.')
|
||||
|
||||
#停止服务
|
||||
def stop():
|
||||
global feiFei
|
||||
global recorderListener
|
||||
global __running
|
||||
global deviceInputListener
|
||||
|
||||
util.log(1, '正在关闭服务...')
|
||||
__running = False
|
||||
if recorderListener is not None:
|
||||
util.log(1, '正在关闭录音服务...')
|
||||
recorderListener.stop()
|
||||
if deviceInputListener is not None:
|
||||
util.log(1, '正在关闭远程音频输入输出服务...')
|
||||
deviceInputListener.stop()
|
||||
util.log(1, '正在关闭核心服务...')
|
||||
feiFei.stop()
|
||||
util.log(1, '服务已关闭!')
|
||||
|
||||
|
||||
def start():
|
||||
global feiFei
|
||||
global recorderListener
|
||||
global __running
|
||||
global deviceInputListener
|
||||
|
||||
util.log(1, '开启服务...')
|
||||
__running = True
|
||||
|
||||
util.log(1, '读取配置...')
|
||||
config_util.load_config()
|
||||
|
||||
util.log(1, '开启核心服务...')
|
||||
feiFei = FeiFei()
|
||||
feiFei.start()
|
||||
|
||||
record = config_util.config['source']['record']
|
||||
|
||||
if record['enabled']:
|
||||
util.log(1, '开启录音服务...')
|
||||
recorderListener = RecorderListener(record['device'], feiFei) # 监听麦克风
|
||||
recorderListener.start()
|
||||
|
||||
#edit by xszyou on 20230113:通过此服务来连接k210、手机等音频输入设备
|
||||
util.log(1,'开启远程设备音频输入服务...')
|
||||
deviceInputListener = DeviceInputListener(feiFei) # 设备音频输入输出麦克风
|
||||
deviceInputListener.start()
|
||||
|
||||
util.log(1, '注册命令...')
|
||||
MyThread(target=console_listener).start() # 监听控制台
|
||||
|
||||
util.log(1, '完成!')
|
||||
util.log(1, '使用 \'help\' 获取帮助.')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ws_server: MyServer = None
|
||||
feiFei: FeiFei = None
|
||||
recorderListener: Recorder = None
|
||||
start()
|
||||
@@ -1,181 +0,0 @@
|
||||
import imp
|
||||
import json
|
||||
import time
|
||||
|
||||
import pyaudio
|
||||
from flask import Flask, render_template, request
|
||||
from flask_cors import CORS
|
||||
|
||||
import fay_booter
|
||||
|
||||
from core.tts_voice import EnumVoice
|
||||
from gevent import pywsgi
|
||||
from scheduler.thread_manager import MyThread
|
||||
from utils import config_util, util
|
||||
from core import wsa_server
|
||||
from core import fay_core
|
||||
from core import content_db
|
||||
from ai_module import yolov8
|
||||
|
||||
|
||||
__app = Flask(__name__)
|
||||
CORS(__app, supports_credentials=True)
|
||||
|
||||
|
||||
def __get_template():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
def __get_device_list():
|
||||
audio = pyaudio.PyAudio()
|
||||
device_list = []
|
||||
for i in range(audio.get_device_count()):
|
||||
devInfo = audio.get_device_info_by_index(i)
|
||||
if devInfo['hostApi'] == 0:
|
||||
device_list.append(devInfo["name"])
|
||||
|
||||
return list(set(device_list))
|
||||
|
||||
|
||||
@__app.route('/api/submit', methods=['post'])
|
||||
def api_submit():
|
||||
data = request.values.get('data')
|
||||
# print(data)
|
||||
config_data = json.loads(data)
|
||||
if(config_data['config']['source']['record']['enabled']):
|
||||
config_data['config']['source']['record']['channels'] = 0
|
||||
audio = pyaudio.PyAudio()
|
||||
for i in range(audio.get_device_count()):
|
||||
devInfo = audio.get_device_info_by_index(i)
|
||||
if devInfo['name'].find(config_data['config']['source']['record']['device']) >= 0 and devInfo['hostApi'] == 0:
|
||||
config_data['config']['source']['record']['channels'] = devInfo['maxInputChannels']
|
||||
|
||||
config_util.save_config(config_data['config'])
|
||||
|
||||
|
||||
return '{"result":"successful"}'
|
||||
|
||||
@__app.route('/api/control-eyes', methods=['post'])
|
||||
def control_eyes():
|
||||
eyes = yolov8.new_instance()
|
||||
if(not eyes.get_status()):
|
||||
eyes.start()
|
||||
util.log(1, "YOLO v8正在启动...")
|
||||
else:
|
||||
eyes.stop()
|
||||
util.log(1, "YOLO v8正在关闭...")
|
||||
return '{"result":"successful"}'
|
||||
|
||||
|
||||
@__app.route('/api/get-data', methods=['post'])
|
||||
def api_get_data():
|
||||
config_data = config_util.config
|
||||
if wsa_server.new_instance().isConnect:
|
||||
config_data['interact']['playSound'] = False
|
||||
else:
|
||||
config_data['interact']['playSound'] = True
|
||||
config_util.save_config(config_data)
|
||||
wsa_server.get_web_instance().add_cmd({
|
||||
"voiceList": [
|
||||
{"id": EnumVoice.XIAO_XIAO.name, "name": "晓晓"},
|
||||
{"id": EnumVoice.YUN_XI.name, "name": "云溪"},
|
||||
{"id": EnumVoice.YUN_JIAN.name, "name": "云健"},
|
||||
{"id": EnumVoice.XIAO_YI.name, "name": "晓伊"},
|
||||
{"id": EnumVoice.YUN_YANG.name, "name": "云阳"},
|
||||
{"id": EnumVoice.YUN_XIA.name, "name": "云夏"}
|
||||
|
||||
]
|
||||
})
|
||||
wsa_server.get_web_instance().add_cmd({"deviceList": __get_device_list()})
|
||||
return json.dumps({'config': config_util.config})
|
||||
|
||||
|
||||
@__app.route('/api/start-live', methods=['post'])
|
||||
def api_start_live():
|
||||
# time.sleep(5)
|
||||
fay_booter.start()
|
||||
time.sleep(1)
|
||||
wsa_server.get_web_instance().add_cmd({"liveState": 1})
|
||||
return '{"result":"successful"}'
|
||||
|
||||
|
||||
@__app.route('/api/stop-live', methods=['post'])
|
||||
def api_stop_live():
|
||||
# time.sleep(1)
|
||||
fay_booter.stop()
|
||||
time.sleep(1)
|
||||
wsa_server.get_web_instance().add_cmd({"liveState": 0})
|
||||
return '{"result":"successful"}'
|
||||
|
||||
@__app.route('/api/send', methods=['post'])
|
||||
def api_send():
|
||||
data = request.values.get('data')
|
||||
info = json.loads(data)
|
||||
text = fay_core.send_for_answer(info['msg'],info['sendto'])
|
||||
return '{"result":"successful","msg":"'+text+'"}'
|
||||
|
||||
@__app.route('/api/get-msg', methods=['post'])
|
||||
def api_get_Msg():
|
||||
contentdb = content_db.new_instance()
|
||||
list = contentdb.get_list('all','desc',1000)
|
||||
relist = []
|
||||
i = len(list)-1
|
||||
while i >= 0:
|
||||
relist.append(dict(type=list[i][0],way=list[i][1],content=list[i][2],createtime=list[i][3],timetext=list[i][4]))
|
||||
i -= 1
|
||||
|
||||
return json.dumps({'list': relist})
|
||||
|
||||
|
||||
@__app.route('/api/send/v1/chat/completions', methods=['post'])
|
||||
def api_send_v1_chat_completions():
|
||||
data = request.json # 解析JSON数据
|
||||
# 检查'messages'键是否存在于数据中
|
||||
last_content = ""
|
||||
if 'messages' in data and data['messages']:
|
||||
last_message = data['messages'][-1] # 获取最后一条消息
|
||||
last_content = last_message.get('content', 'No content provided') # 获取'content'字段
|
||||
else:
|
||||
last_content = 'No messages found'
|
||||
text = fay_core.send_for_answer("主人文字说了:" + last_content)
|
||||
return {
|
||||
"id": "chatcmpl-8jqorq6Fw1Vi5XoH7pddGGpQeuPe0",
|
||||
"object": "chat.completion",
|
||||
"created": 1705938489,
|
||||
"model": "fay-agent",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": text
|
||||
},
|
||||
"logprobs": "",
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": len(last_content),
|
||||
"completion_tokens": len(text),
|
||||
"total_tokens": len(last_content) + len(text)
|
||||
},
|
||||
"system_fingerprint": "fp_04de91a479"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@__app.route('/', methods=['get'])
|
||||
def home_get():
|
||||
return __get_template()
|
||||
|
||||
|
||||
@__app.route('/', methods=['post'])
|
||||
def home_post():
|
||||
return __get_template()
|
||||
|
||||
def run():
|
||||
server = pywsgi.WSGIServer(('0.0.0.0',5000), __app)
|
||||
server.serve_forever()
|
||||
|
||||
def start():
|
||||
MyThread(target=run).start()
|
||||
@@ -1,435 +0,0 @@
|
||||
#app {
|
||||
width: 1920px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 1920px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* flex-wrap: wrap; */
|
||||
}
|
||||
|
||||
.main_box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
}
|
||||
|
||||
.left {
|
||||
width: 915px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.left .left_top {
|
||||
width: 915px;
|
||||
border: 1px solid #333333;
|
||||
|
||||
}
|
||||
|
||||
.left_top_p {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.character {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.character_top {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.character_left {
|
||||
width: 443px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.character_left ul {}
|
||||
|
||||
.character_left ul li {
|
||||
display: flex;
|
||||
height: 51.5px;
|
||||
}
|
||||
|
||||
.character_left ul li p {
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.character_left ul li .el-input {
|
||||
width: 320px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.character_right {
|
||||
display: flex;
|
||||
width: 430px;
|
||||
}
|
||||
|
||||
.character_right ul {
|
||||
width: 430px;
|
||||
}
|
||||
|
||||
.character_right ul li {
|
||||
display: flex;
|
||||
width: 430px;
|
||||
}
|
||||
|
||||
.character_right ul li p {
|
||||
width: 120px;
|
||||
text-align: right;
|
||||
margin-top: 5px;
|
||||
|
||||
}
|
||||
|
||||
.character_right ul li .el-slider__runway {
|
||||
width: 250px;
|
||||
}
|
||||
.character_right ul li .el-select {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 250px;
|
||||
}
|
||||
.character_box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.character_box p {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.character_box .el-input {
|
||||
width: 730px;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
.title h2 {
|
||||
width: 100%;
|
||||
height: 75px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.left_box {
|
||||
width: 915px;
|
||||
/*height: 260px;*/
|
||||
margin-top: 15px;
|
||||
border: 1px solid #333333;
|
||||
}
|
||||
|
||||
.left_box p {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.left_box .source {}
|
||||
|
||||
.left_box .source ul {}
|
||||
|
||||
.left_box .source ul li {}
|
||||
|
||||
.left_box .source ul .url {
|
||||
width: 750px;
|
||||
margin: 20px auto 0;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.left_box .source ul .url .el-switch {
|
||||
position: relative;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.left_box .source ul .url p {
|
||||
width: 85px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.left_box .source ul .url .el-input {
|
||||
height: 40px;
|
||||
}
|
||||
.left_box .source ul .url .el-select {
|
||||
height: 40px;
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.left_box .source ul .but {
|
||||
width: 750px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.left_box .source ul .but .el-button {
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
|
||||
.left_box .source ul .p_red {
|
||||
width: 750px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.left_box .source ul .p_red p {
|
||||
color: red;
|
||||
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 915px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.right_main {
|
||||
width: 915px;
|
||||
border: 1px solid #333333;
|
||||
}
|
||||
|
||||
.right_main ul {
|
||||
width: 915px;
|
||||
}
|
||||
|
||||
.right_main ul li {
|
||||
width: 915px;
|
||||
display: flex;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.right_main ul li p {
|
||||
width: 128px;
|
||||
text-align: right;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.right_main ul li .el-input {
|
||||
width: 666px;
|
||||
}
|
||||
|
||||
.right_main ul li .upload-demo {
|
||||
width: 666px;
|
||||
}
|
||||
.right_main ul li .el-textarea {
|
||||
width: 666px;
|
||||
}
|
||||
|
||||
.right_main ul li .el-switch {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.el-input__inner {
|
||||
-webkit-appearance: none;
|
||||
background-color: #FFF;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #DCDFE6;
|
||||
box-sizing: border-box;
|
||||
color: #606266;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
height: 43px;
|
||||
line-height: 40px;
|
||||
outline: 0;
|
||||
padding: 0 15px;
|
||||
transition: border-color .2s cubic-bezier(.645,.045,.355,1);
|
||||
width: 100%;
|
||||
}
|
||||
.el-input.is-disabled .el-input__inner {
|
||||
background-color: #F5F7FA;
|
||||
border-color: #E4E7ED;
|
||||
color: #000206 !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.container {
|
||||
height: 902px;
|
||||
width: 913px;
|
||||
border-radius: 4px;
|
||||
border: 0.5px solid #e0e0e0;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: calc(100% - 20px);
|
||||
padding: 20px;
|
||||
overflow-y: scroll;
|
||||
flex: 1;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.content:hover::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.bubble {
|
||||
/* max-width: 400px; */
|
||||
max-width: 60%;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
color: #000;
|
||||
word-wrap: break-word;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.item-left .bubble {
|
||||
margin-left: 15px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.item-left .bubble:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-right: 10px solid #fff;
|
||||
border-bottom: 10px solid transparent;
|
||||
left: -20px;
|
||||
}
|
||||
|
||||
.item-right .bubble {
|
||||
margin-right: 15px;
|
||||
background-color: #63f5a1;
|
||||
}
|
||||
|
||||
.item-right .bubble:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid #63f5a1;
|
||||
border-top: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
right: -20px;
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.item.item-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.item.item-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.item.item-center span {
|
||||
font-size: 12px;
|
||||
padding: 2px 4px;
|
||||
color: #fff;
|
||||
background-color: #dadada;
|
||||
border-radius: 3px;
|
||||
-moz-user-select: none;
|
||||
/*火狐*/
|
||||
-webkit-user-select: none;
|
||||
/*webkit浏览器*/
|
||||
-ms-user-select: none;
|
||||
/*IE10*/
|
||||
-khtml-user-select: none;
|
||||
/*早期浏览器*/
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
border-top: 0.5px solid #e0e0e0;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
outline: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.button-area {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
line-height: 40px;
|
||||
padding: 5px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.button-area button {
|
||||
width: 80px;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 设置滚动条的样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
/* 滚动槽 */
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块 */
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
-webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.spinner {
|
||||
/* Customize the loading spinner animation based on your UI framework or styling preferences */
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top: 4px solid #3498db;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -1,646 +0,0 @@
|
||||
new Vue({
|
||||
el: '#app',
|
||||
delimiters: ["[[", "]]"],
|
||||
data() {
|
||||
return {
|
||||
testlist: [
|
||||
{
|
||||
tab_name: "first",
|
||||
name: "first",
|
||||
},
|
||||
{
|
||||
tab_name: "2",
|
||||
name: "2",
|
||||
},
|
||||
{
|
||||
tab_name: "3",
|
||||
name: "3",
|
||||
}
|
||||
],
|
||||
fileList: {},
|
||||
panel_msg: "",
|
||||
play_sound_enabled: false,
|
||||
visualization_detection_enabled: false,
|
||||
source_liveRoom_enabled: false,
|
||||
source_liveRoom_url: '',
|
||||
source_record_enabled: false,
|
||||
source_record_device: '',
|
||||
attribute_name: "",
|
||||
attribute_gender: "",
|
||||
attribute_age: "",
|
||||
attribute_birth: "",
|
||||
attribute_zodiac: "",
|
||||
attribute_constellation: "",
|
||||
attribute_job: "",
|
||||
attribute_hobby: "",
|
||||
attribute_contact: "",
|
||||
attribute_voice: "",
|
||||
interact_perception_gift: 0,
|
||||
interact_perception_follow: 0,
|
||||
interact_perception_join: 0,
|
||||
interact_perception_chat: 0,
|
||||
interact_perception_indifferent: 0,
|
||||
interact_maxInteractTime: 15,
|
||||
interact_QnA: "",
|
||||
items_data: [],
|
||||
live_state: 0,
|
||||
device_list: [],
|
||||
send_msg:"",
|
||||
// device_list: [
|
||||
// {
|
||||
// value: '选项1',
|
||||
// label: '麦克风'
|
||||
// }
|
||||
// ],
|
||||
voice_list: [],
|
||||
options: [{
|
||||
value: '选项1',
|
||||
label: '黄金糕'
|
||||
}, {
|
||||
value: '选项2',
|
||||
label: '双皮奶'
|
||||
}],
|
||||
activeName: 'first',
|
||||
|
||||
editableTabsValue: '1',
|
||||
tabIndex: 1,
|
||||
editableTabs: [{
|
||||
title: 'Tab 1',
|
||||
name: '1',
|
||||
content: 'Tab 1 content'
|
||||
}, {
|
||||
title: 'Tab 2',
|
||||
name: '2',
|
||||
content: 'Tab 2 content'
|
||||
}],
|
||||
msg_list:[],
|
||||
is_connect: false,
|
||||
wake_word_enabled:false,
|
||||
wake_word: '',
|
||||
loading: false,
|
||||
remote_audio_connect: false,
|
||||
wake_word_type: 'common',
|
||||
wake_word_type_options: [{
|
||||
value: 'common',
|
||||
label: '普通'
|
||||
}, {
|
||||
value: 'front',
|
||||
label: '前置词'
|
||||
}],
|
||||
tts_enabled:true
|
||||
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('keydown', this.handkeyCode, true)//开启监听键盘按下事件
|
||||
},
|
||||
methods: {
|
||||
// 回车和空格键提交右侧信息
|
||||
handkeyCode(e) {
|
||||
if(e.keyCode === 13 && e.keyCode === 18){
|
||||
this.send(1)
|
||||
}
|
||||
},
|
||||
handleTabsEdit(targetName, action) {
|
||||
if (action === 'add') {
|
||||
let newTabName = ++this.tabIndex + '';
|
||||
this.items_data.push({
|
||||
tab_name: newTabName,
|
||||
enabled: false,
|
||||
name: "",
|
||||
explain: {
|
||||
intro: "",
|
||||
usage: "",
|
||||
price: "",
|
||||
discount: "",
|
||||
promise: "",
|
||||
character: ""
|
||||
},
|
||||
demoVideo: "",
|
||||
QnA: ""
|
||||
});
|
||||
this.editableTabsValue = newTabName;
|
||||
}
|
||||
if (action === 'remove') {
|
||||
let tabs = this.items_data;
|
||||
let activeName = this.editableTabsValue;
|
||||
if (activeName === targetName) {
|
||||
tabs.forEach((tab, index) => {
|
||||
if (tab.tab_name === targetName) {
|
||||
let nextTab = tabs[index + 1] || tabs[index - 1];
|
||||
if (nextTab) {
|
||||
activeName = nextTab.name;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
this.editableTabsValue = activeName;
|
||||
this.items_data = tabs.filter(tab => tab.tab_name !== targetName);
|
||||
}
|
||||
},
|
||||
show() {
|
||||
alert("run...")
|
||||
},
|
||||
formatTooltip(val) {
|
||||
return val / 100;
|
||||
},
|
||||
handleChange(value) {
|
||||
console.log(value);
|
||||
},
|
||||
handleClick(tab, event) {
|
||||
console.log(tab, event);
|
||||
},
|
||||
handleRemove(file, fileList) {
|
||||
console.log(file, fileList);
|
||||
},
|
||||
handlePreview(file) {
|
||||
console.log(file);
|
||||
},
|
||||
onExceed() {
|
||||
},
|
||||
beforeRemove() {
|
||||
},
|
||||
handleExceed() {
|
||||
},
|
||||
connectWS() {
|
||||
let _this = this;
|
||||
socket = new WebSocket('ws://localhost:10003')
|
||||
socket.onopen = function () {
|
||||
// console.log('客户端连接上了服务器');
|
||||
}
|
||||
socket.onmessage = function (e) {
|
||||
console.log(" --> " + e.data)
|
||||
let data = JSON.parse(e.data)
|
||||
_this.live_broadcast = (data.time % 2) === 0
|
||||
let liveState = data.liveState
|
||||
if (liveState !== undefined) {
|
||||
_this.live_state = liveState
|
||||
if (liveState === 1) {
|
||||
_this.sendSuccessMsg("已开启!")
|
||||
} else if (liveState === 0) {
|
||||
_this.sendSuccessMsg("已关闭!")
|
||||
}
|
||||
}
|
||||
let voiceList = data.voiceList
|
||||
if (voiceList !== undefined) {
|
||||
voice_list = []
|
||||
for (let i = 0; i < voiceList.length; i++) {
|
||||
voice_list[i] = {
|
||||
value: voiceList[i].id,
|
||||
label: voiceList[i].name
|
||||
}
|
||||
_this.voice_list = voice_list
|
||||
}
|
||||
}
|
||||
|
||||
let deviceList = data.deviceList
|
||||
if (deviceList !== undefined) {
|
||||
device_list = []
|
||||
for (let i = 0; i < deviceList.length; i++) {
|
||||
device_list[i] = {
|
||||
value: deviceList[i],
|
||||
label: deviceList[i]
|
||||
}
|
||||
_this.device_list = device_list
|
||||
}
|
||||
}
|
||||
let panelMsg = data.panelMsg
|
||||
if (panelMsg !== undefined) {
|
||||
_this.panel_msg = panelMsg
|
||||
|
||||
//Edit by xszyou in 2022/2/3:同步到看板娘
|
||||
text = panelMsg;
|
||||
const tips = document.getElementById("waifu-tips");
|
||||
if (panelMsg != "" && tips != null){
|
||||
sessionStorage.setItem("waifu-text", 8);
|
||||
tips.innerHTML = text;
|
||||
tips.classList.add("waifu-tips-active");
|
||||
messageTimer = setTimeout(() => {
|
||||
sessionStorage.removeItem("waifu-text");
|
||||
tips.classList.remove("waifu-tips-active");
|
||||
}, 7000);
|
||||
}
|
||||
//_this.getMsgList()
|
||||
}
|
||||
let panelReply = data.panelReply;
|
||||
if(panelReply != undefined){
|
||||
_this.addMsg(panelReply)
|
||||
|
||||
}
|
||||
let is_connect = data.is_connect
|
||||
if(is_connect != undefined){
|
||||
_this.is_connect = is_connect
|
||||
if (is_connect){
|
||||
_this.play_sound_enabled = false
|
||||
_this.postData()
|
||||
}else{
|
||||
_this.play_sound_enabled = true
|
||||
_this.postData()
|
||||
}
|
||||
}
|
||||
let remote_audio_connect = data.remote_audio_connect
|
||||
if(remote_audio_connect != undefined){
|
||||
_this.remote_audio_connect = remote_audio_connect
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
getData() {
|
||||
let _this = this;
|
||||
let url = "http://127.0.0.1:5000/api/get-data";
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url)
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
xhr.send()
|
||||
let executed = false
|
||||
xhr.onreadystatechange = async function () {
|
||||
if (!executed && xhr.status === 200) {
|
||||
try {
|
||||
if (xhr.responseText.length > 0) {
|
||||
let data = await eval('(' + xhr.responseText + ')')
|
||||
let config = data["config"]
|
||||
let source = config["source"]
|
||||
let attribute = config["attribute"]
|
||||
let interact = config["interact"]
|
||||
let perception = interact["perception"]
|
||||
let items = config["items"]
|
||||
_this.wake_word_enabled = source["wake_word_enabled"]
|
||||
_this.wake_word = source["wake_word"]
|
||||
_this.wake_word_type = source["wake_word_type"]
|
||||
_this.tts_enabled = source["tts_enabled"]
|
||||
_this.play_sound_enabled = interact["playSound"]
|
||||
_this.visualization_detection_enabled = interact["visualization"]
|
||||
_this.source_liveRoom_enabled = source["liveRoom"]["enabled"]
|
||||
_this.source_liveRoom_url = source["liveRoom"]["url"]
|
||||
_this.source_record_enabled = source["record"]["enabled"]
|
||||
_this.source_record_device = source["record"]["device"]
|
||||
_this.attribute_name = attribute["name"]
|
||||
_this.attribute_gender = attribute["gender"]
|
||||
_this.attribute_age = attribute["age"]
|
||||
_this.attribute_birth = attribute["birth"]
|
||||
_this.attribute_zodiac = attribute["zodiac"]
|
||||
_this.attribute_constellation = attribute["constellation"]
|
||||
_this.attribute_job = attribute["job"]
|
||||
_this.attribute_hobby = attribute["hobby"]
|
||||
_this.attribute_contact = attribute["contact"]
|
||||
_this.attribute_voice = attribute["voice"]
|
||||
_this.interact_perception_gift = parseInt(perception["follow"])
|
||||
_this.interact_perception_follow = perception["follow"]
|
||||
_this.interact_perception_join = perception["follow"]
|
||||
_this.interact_perception_chat = perception["follow"]
|
||||
_this.interact_perception_indifferent = perception["follow"]
|
||||
_this.interact_maxInteractTime = interact["maxInteractTime"]
|
||||
_this.interact_QnA = interact["QnA"]
|
||||
let item_data_list = []
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i]
|
||||
let _tab_name = "first"
|
||||
if (i > 0) {
|
||||
_tab_name = i.toString()
|
||||
}
|
||||
item_data_list[i] = {
|
||||
tab_name: _tab_name,
|
||||
enabled: item.enabled,
|
||||
name: item.name,
|
||||
explain: {
|
||||
intro: item.explain.intro,
|
||||
usage: item.explain.usage,
|
||||
price: item.explain.price,
|
||||
discount: item.explain.discount,
|
||||
promise: item.explain.promise,
|
||||
character: item.explain.character
|
||||
},
|
||||
demoVideo: item.demoVideo,
|
||||
QnA: item.QnA
|
||||
}
|
||||
}
|
||||
_this.items_data = item_data_list
|
||||
console.log(_this.items_data);
|
||||
executed = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
postData() {
|
||||
let url = "http://127.0.0.1:5000/api/submit";
|
||||
let send_data = {
|
||||
"config": {
|
||||
"source": {
|
||||
"liveRoom": {
|
||||
"enabled": this.source_liveRoom_enabled,
|
||||
"url": this.source_liveRoom_url
|
||||
},
|
||||
"record": {
|
||||
"enabled": this.source_record_enabled,
|
||||
"device": this.source_record_device
|
||||
},
|
||||
"wake_word_enabled": this.wake_word_enabled,
|
||||
"wake_word": this.wake_word,
|
||||
"wake_word_type": this.wake_word_type,
|
||||
"tts_enabled": this.tts_enabled
|
||||
},
|
||||
"attribute": {
|
||||
"voice": this.attribute_voice,
|
||||
"name": this.attribute_name,
|
||||
"gender": this.attribute_gender,
|
||||
"age": this.attribute_age,
|
||||
"birth": this.attribute_birth,
|
||||
"zodiac": this.attribute_zodiac,
|
||||
"constellation": this.attribute_constellation,
|
||||
"job": this.attribute_job,
|
||||
"hobby": this.attribute_hobby,
|
||||
"contact": this.attribute_contact
|
||||
},
|
||||
"interact": {
|
||||
"playSound": this.play_sound_enabled,
|
||||
"visualization": this.visualization_detection_enabled,
|
||||
"QnA": this.interact_QnA,
|
||||
"maxInteractTime": this.interact_maxInteractTime,
|
||||
"perception": {
|
||||
"gift": this.interact_perception_follow,
|
||||
"follow": this.interact_perception_follow,
|
||||
"join": this.interact_perception_follow,
|
||||
"chat": this.interact_perception_follow,
|
||||
"indifferent": this.interact_perception_follow
|
||||
}
|
||||
},
|
||||
"items": [],
|
||||
}
|
||||
};
|
||||
for (let i = 0; i < this.items_data.length; i++) {
|
||||
let item = this.items_data[i]
|
||||
send_data.config.items[i] = {
|
||||
enabled: item.enabled,
|
||||
name: item.name,
|
||||
explain: {
|
||||
intro: item.explain.intro,
|
||||
usage: item.explain.usage,
|
||||
price: item.explain.price,
|
||||
discount: item.explain.discount,
|
||||
promise: item.explain.promise,
|
||||
character: item.explain.character
|
||||
},
|
||||
demoVideo: item.demoVideo,
|
||||
QnA: item.QnA
|
||||
}
|
||||
}
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url)
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
xhr.send('data=' + JSON.stringify(send_data))
|
||||
let executed = false
|
||||
xhr.onreadystatechange = async function () {
|
||||
if (!executed && xhr.status === 200) {
|
||||
try {
|
||||
let data = await eval('(' + xhr.responseText + ')')
|
||||
console.log("data: " + data['result'])
|
||||
executed = true
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.sendSuccessMsg("配置已保存!")
|
||||
},
|
||||
postStartLive() {
|
||||
this.postData()
|
||||
this.live_state = 2
|
||||
let url = "http://127.0.0.1:5000/api/start-live";
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url)
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
xhr.send()
|
||||
},
|
||||
postStopLive() {
|
||||
this.live_state = 3
|
||||
let url = "http://127.0.0.1:5000/api/stop-live";
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url)
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
xhr.send()
|
||||
|
||||
},
|
||||
postControlEyes() {
|
||||
let url = "http://127.0.0.1:5000/api/control-eyes";
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url)
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
xhr.send()
|
||||
if(this.visualization_detection_enabled){
|
||||
this.visualization_detection_enabled = false
|
||||
}else{
|
||||
this.visualization_detection_enabled = true
|
||||
}
|
||||
},
|
||||
isEmptyItem(data) {
|
||||
let isEmpty = true
|
||||
let explain = data["explain"]
|
||||
for (let key in data) {
|
||||
let value = data[key]
|
||||
if (key !== "tab_name" && value.constructor === String && value.length > 0) {
|
||||
isEmpty = false
|
||||
break
|
||||
}
|
||||
}
|
||||
for (let key in explain) {
|
||||
let value = explain[key]
|
||||
if (value.constructor === String && value.length > 0) {
|
||||
isEmpty = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return isEmpty
|
||||
},
|
||||
lastItemIsEmpty() {
|
||||
return this.isEmptyItem(this.items_data[this.items_data.length - 1])
|
||||
},
|
||||
uuid() {
|
||||
let uuid = Math.random().toString(36);
|
||||
return uuid
|
||||
},
|
||||
runnnable() {
|
||||
setTimeout(() => {
|
||||
let _this = this
|
||||
let item_data_list = []
|
||||
let changed = false
|
||||
let index = 0
|
||||
for (let i = 0; i < _this.items_data.length; i++) {
|
||||
let data = _this.items_data[i]
|
||||
if (i === (_this.items_data.length - 1) || !this.isEmptyItem(data)) {
|
||||
item_data_list[index] = _this.items_data[i]
|
||||
index++
|
||||
} else {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if (!this.lastItemIsEmpty()) {
|
||||
changed = true
|
||||
item_data_list.push({
|
||||
tab_name: this.uuid(),
|
||||
enabled: false,
|
||||
name: "",
|
||||
explain: {
|
||||
intro: "",
|
||||
usage: "",
|
||||
price: "",
|
||||
discount: "",
|
||||
promise: "",
|
||||
character: ""
|
||||
},
|
||||
demoVideo: "",
|
||||
QnA: ""
|
||||
})
|
||||
}
|
||||
if (changed) {
|
||||
_this.items_data = item_data_list
|
||||
console.log("修改了!" + _this.items_data.length)
|
||||
}
|
||||
this.runnnable()
|
||||
}, 50)
|
||||
},
|
||||
sendSuccessMsg(text) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: text,
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
send(sendto) {
|
||||
let _this = this;
|
||||
let text = _this.send_msg;
|
||||
if (!text) {
|
||||
alert('请输入内容');
|
||||
return;
|
||||
}
|
||||
// let info = {
|
||||
// 'content' : text ,
|
||||
// 'timetext' : _this.getCurrentTime() ,
|
||||
// 'type' : 'member' ,
|
||||
// 'way' : 'send'
|
||||
// }
|
||||
// _this.msg_list.push(info);
|
||||
this.timer = setTimeout(()=>{ //设置延迟执行
|
||||
//滚动条置底
|
||||
let height = document.querySelector('.content').scrollHeight;
|
||||
document.querySelector(".content").scrollTop = height;
|
||||
},1000)
|
||||
_this.send_msg = ''
|
||||
let url = "http://127.0.0.1:5000/api/send";
|
||||
let send_data = {
|
||||
"msg": text,
|
||||
"sendto" : sendto
|
||||
};
|
||||
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url)
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
xhr.send('data=' + encodeURIComponent(JSON.stringify(send_data)))
|
||||
let executed = false
|
||||
xhr.onreadystatechange = async function () {
|
||||
if (!executed && xhr.status === 200) {
|
||||
// this.getMsgList()
|
||||
// document.querySelector('#textarea').value = '';
|
||||
// document.querySelector('#textarea').focus();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// // text = text.replace(/\s/g, "<br/>");
|
||||
// text = text.replace(/\n/g, "<br/>");
|
||||
// text = text.replace(/\r\n/g, "<br/>");
|
||||
// let item = document.createElement('div');
|
||||
// item.className = 'item item-right';
|
||||
// item.innerHTML = `<div class="bubble bubble-right">${text}</div><div class="avatar"><img src="static/from.jpg" /></div>`;
|
||||
// document.querySelector('.content').appendChild(item);
|
||||
// document.querySelector('#textarea').value = '';
|
||||
// document.querySelector('#textarea').focus();
|
||||
// //滚动条置底
|
||||
// let height = document.querySelector('.content').scrollHeight;
|
||||
// document.querySelector(".content").scrollTop = height;
|
||||
},
|
||||
getMsgList(){
|
||||
let _this = this;
|
||||
|
||||
let url = "http://127.0.0.1:5000/api/get-msg";
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url)
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
|
||||
xhr.send()
|
||||
let executed = false
|
||||
xhr.onreadystatechange = async function () {
|
||||
if (!executed && xhr.status === 200) {
|
||||
try {
|
||||
if (xhr.responseText.length > 0) {
|
||||
let data = await eval('(' + xhr.responseText + ')')
|
||||
_this.msg_list = data['list'];
|
||||
|
||||
//滚动条置底
|
||||
let height = document.querySelector('.content').scrollHeight;
|
||||
document.querySelector(".content").scrollTop = height;
|
||||
this.timer = setTimeout(()=>{ //设置延迟执行
|
||||
//滚动条置底
|
||||
let height = document.querySelector('.content').scrollHeight;
|
||||
document.querySelector(".content").scrollTop = height;
|
||||
},1000)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
addMsg(data){
|
||||
let _this = this;
|
||||
let info = {
|
||||
'content' : data['content'] ,
|
||||
'timetext' : _this.getCurrentTime() ,
|
||||
'type' : data['type'] ,
|
||||
'way' : 'send'
|
||||
}
|
||||
if (data['type'] == 'fay'){
|
||||
_this.loading = false;
|
||||
}else{
|
||||
_this.loading = true;
|
||||
}
|
||||
_this.msg_list.push(info);
|
||||
|
||||
this.timer = setTimeout(()=>{ //设置延迟执行
|
||||
//滚动条置底
|
||||
let height = document.querySelector('.content').scrollHeight;
|
||||
document.querySelector(".content").scrollTop = height;
|
||||
},1000)
|
||||
|
||||
|
||||
},
|
||||
getCurrentTime() {
|
||||
//获取当前时间并打印
|
||||
var _this = this;
|
||||
let yy = new Date().getFullYear();
|
||||
let mm = new Date().getMonth()+1<10 ? '0'+parseInt(new Date().getMonth()+1) : new Date().getMonth()+1;
|
||||
let dd = new Date().getDate()<10 ? '0'+new Date().getDate() : new Date().getDate();
|
||||
let hh = new Date().getHours()<10 ? '0'+new Date().getHours() : new Date().getHours();
|
||||
let mf = new Date().getMinutes()<10 ? '0'+new Date().getMinutes() : new Date().getMinutes();
|
||||
let ss = new Date().getSeconds()<10 ? '0'+new Date().getSeconds() : new Date().getSeconds();
|
||||
let gettime = yy+'-'+mm+'-'+dd+' '+hh+':'+mf+':'+ss;
|
||||
return gettime;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
let _this = this;
|
||||
_this.getData();
|
||||
_this.getMsgList();
|
||||
_this.connectWS();
|
||||
|
||||
// _this.runnnable()
|
||||
// _this.items_data.push({});
|
||||
},
|
||||
watch: {
|
||||
items_data() {
|
||||
// console.log("items_data 改变了");
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,25 +0,0 @@
|
||||
window.onload = function () {
|
||||
document.body.style.zoom = "normal";//避免zoom尺寸叠加
|
||||
let scale = document.body.clientWidth / 1920;
|
||||
document.body.style.zoom = scale;
|
||||
}; (function () {
|
||||
var throttle = function (type, name, obj) {
|
||||
obj = obj || window;
|
||||
var running = false;
|
||||
var func = function () {
|
||||
if (running) { return; }
|
||||
running = true;
|
||||
requestAnimationFrame(function () {
|
||||
obj.dispatchEvent(new CustomEvent(name));
|
||||
running = false;
|
||||
});
|
||||
};
|
||||
obj.addEventListener(type, func);
|
||||
};
|
||||
throttle("resize", "optimizedResize");
|
||||
})();
|
||||
window.addEventListener("optimizedResize", function () {
|
||||
document.body.style.zoom = "normal";
|
||||
let scale = document.body.clientWidth / 1920;
|
||||
document.body.style.zoom = scale;
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
// live2d_path 参数建议使用绝对路径
|
||||
const live2d_path = "https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/";
|
||||
//const live2d_path = "/live2d-widget/";
|
||||
|
||||
// 封装异步加载资源的方法
|
||||
function loadExternalResource(url, type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let tag;
|
||||
|
||||
if (type === "css") {
|
||||
tag = document.createElement("link");
|
||||
tag.rel = "stylesheet";
|
||||
tag.href = url;
|
||||
}
|
||||
else if (type === "js") {
|
||||
tag = document.createElement("script");
|
||||
tag.src = url;
|
||||
}
|
||||
if (tag) {
|
||||
tag.onload = () => resolve(url);
|
||||
tag.onerror = () => reject(url);
|
||||
document.head.appendChild(tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载 waifu.css live2d.min.js waifu-tips.js
|
||||
if (screen.width >= 768) {
|
||||
Promise.all([
|
||||
loadExternalResource(live2d_path + "waifu.css", "css"),
|
||||
loadExternalResource(live2d_path + "live2d.min.js", "js"),
|
||||
loadExternalResource(live2d_path + "waifu-tips.js", "js")
|
||||
]).then(() => {
|
||||
// 配置选项的具体用法见 README.md
|
||||
initWidget({
|
||||
waifuPath: "/static/live2d/waifu-tips.json",
|
||||
//apiPath: "https://live2d.fghrsh.net/api/",
|
||||
cdnPath: "https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/",
|
||||
tools: ["switch-model", "quit"]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`
|
||||
く__,.ヘヽ. / ,ー、 〉
|
||||
\ ', !-─‐-i / /´
|
||||
/`ー' L//`ヽ、
|
||||
/ /, /| , , ',
|
||||
イ / /-‐/ i L_ ハ ヽ! i
|
||||
レ ヘ 7イ`ト レ'ァ-ト、!ハ| |
|
||||
!,/7 '0' ´0iソ| |
|
||||
|.从" _ ,,,, / |./ |
|
||||
レ'| i>.、,,__ _,.イ / .i |
|
||||
レ'| | / k_7_/レ'ヽ, ハ. |
|
||||
| |/i 〈|/ i ,.ヘ | i |
|
||||
.|/ / i: ヘ! \ |
|
||||
kヽ>、ハ _,.ヘ、 /、!
|
||||
!'〈//`T´', \ `'7'ーr'
|
||||
レ'ヽL__|___i,___,ンレ|ノ
|
||||
ト-,/ |___./
|
||||
'ー' !_,.:
|
||||
`);
|
||||
@@ -1,120 +0,0 @@
|
||||
{
|
||||
"mouseover": [{
|
||||
"selector": "#live2d",
|
||||
"text": ["干嘛呢你,快把手拿开~~", "鼠…鼠标放错地方了!", "你要干嘛呀?", "喵喵喵?", "怕怕(ノ≧∇≦)ノ", "非礼呀!救命!", "这样的话,只能使用武力了!", "我要生气了哦", "不要动手动脚的!", "真…真的是不知羞耻!", "Hentai!"]
|
||||
}, {
|
||||
"selector": "#waifu-tool-hitokoto",
|
||||
"text": ["猜猜我要说些什么?", "我从青蛙王子那里听到了不少人生经验。"]
|
||||
}, {
|
||||
"selector": "#waifu-tool-asteroids",
|
||||
"text": ["要不要来玩飞机大战?", "这个按钮上写着「不要点击」。", "怎么,你想来和我玩个游戏?", "听说这样可以蹦迪!"]
|
||||
}, {
|
||||
"selector": "#waifu-tool-switch-model",
|
||||
"text": ["你是不是不爱人家了呀,呜呜呜~", "要见见我的姐姐嘛?", "想要看我妹妹嘛?", "要切换看板娘吗?"]
|
||||
}, {
|
||||
"selector": "#waifu-tool-switch-texture",
|
||||
"text": ["喜欢换装 PLAY 吗?", "这次要扮演什么呢?", "变装!", "让我们看看接下来会发生什么!"]
|
||||
}, {
|
||||
"selector": "#waifu-tool-photo",
|
||||
"text": ["你要给我拍照呀?一二三~茄子~", "要不,我们来合影吧!", "保持微笑就好了~"]
|
||||
}, {
|
||||
"selector": "#waifu-tool-info",
|
||||
"text": ["想要知道更多关于我的事么?", "这里记录着我搬家的历史呢。", "你想深入了解我什么呢?"]
|
||||
}, {
|
||||
"selector": "#waifu-tool-quit",
|
||||
"text": ["到了要说再见的时候了吗?", "呜呜 QAQ 后会有期……", "不要抛弃我呀……", "我们,还能再见面吗……", "哼,你会后悔的!"]
|
||||
}, {
|
||||
"selector": ".character_left",
|
||||
"text": ["这是我的人设,修改之后记得重新启动哦"]
|
||||
}, {
|
||||
"selector": ".character_right",
|
||||
"text": ["这是我的性格,修改之后记得重新启动哦"]
|
||||
}, {
|
||||
"selector": ".right_main",
|
||||
"text": ["我可以做你的售货员了"]
|
||||
}, {
|
||||
"selector": ".btn_close",
|
||||
"text": ["你是不想和我说话了吗"]
|
||||
}, {
|
||||
"selector": ".btn_open",
|
||||
"text": ["快点击,我可以和你谈谈心了"]
|
||||
}],
|
||||
"click": [{
|
||||
"selector": "#live2d",
|
||||
"text": ["是…是不小心碰到了吧…", "萝莉控是什么呀?", "你看到我的小熊了吗?", "再摸的话我可要报警了!⌇●﹏●⌇", "110 吗,这里有个变态一直在摸我(ó﹏ò。)", "不要摸我了,我会告诉老婆来打你的!", "干嘛动我呀!小心我咬你!", "别摸我,有什么好摸的!"]
|
||||
},{
|
||||
"selector": ".btn_close",
|
||||
"text": ["再见!"]
|
||||
}, {
|
||||
"selector": ".btn_open",
|
||||
"text": ["我要上线了"]
|
||||
}],
|
||||
"seasons": [{
|
||||
"date": "01/01",
|
||||
"text": "<span>元旦</span>了呢,新的一年又开始了,今年是{year}年~"
|
||||
}, {
|
||||
"date": "02/14",
|
||||
"text": "又是一年<span>情人节</span>,{year}年找到对象了嘛~"
|
||||
}, {
|
||||
"date": "03/08",
|
||||
"text": "今天是<span>国际妇女节</span>!"
|
||||
}, {
|
||||
"date": "03/12",
|
||||
"text": "今天是<span>植树节</span>,要保护环境呀!"
|
||||
}, {
|
||||
"date": "04/01",
|
||||
"text": "悄悄告诉你一个秘密~<span>今天是愚人节,不要被骗了哦~</span>"
|
||||
}, {
|
||||
"date": "05/01",
|
||||
"text": "今天是<span>五一劳动节</span>,计划好假期去哪里了吗~"
|
||||
}, {
|
||||
"date": "06/01",
|
||||
"text": "<span>儿童节</span>了呢,快活的时光总是短暂,要是永远长不大该多好啊…"
|
||||
}, {
|
||||
"date": "09/03",
|
||||
"text": "<span>中国人民抗日战争胜利纪念日</span>,铭记历史、缅怀先烈、珍爱和平、开创未来。"
|
||||
}, {
|
||||
"date": "09/10",
|
||||
"text": "<span>教师节</span>,在学校要给老师问声好呀~"
|
||||
}, {
|
||||
"date": "10/01",
|
||||
"text": "<span>国庆节</span>到了,为祖国母亲庆生!"
|
||||
}, {
|
||||
"date": "11/05-11/12",
|
||||
"text": "今年的<span>双十一</span>是和谁一起过的呢~"
|
||||
}, {
|
||||
"date": "12/20-12/31",
|
||||
"text": "这几天是<span>圣诞节</span>,主人肯定又去剁手买买买了~"
|
||||
}],
|
||||
"time": [{
|
||||
"hour": "6-7",
|
||||
"text": "早上好!一日之计在于晨,美好的一天就要开始了~"
|
||||
}, {
|
||||
"hour": "8-11",
|
||||
"text": "上午好!工作顺利嘛,不要久坐,多起来走动走动哦!"
|
||||
}, {
|
||||
"hour": "12-13",
|
||||
"text": "中午了,工作了一个上午,现在是午餐时间!"
|
||||
}, {
|
||||
"hour": "14-17",
|
||||
"text": "午后很容易犯困呢,今天的运动目标完成了吗?"
|
||||
}, {
|
||||
"hour": "18-19",
|
||||
"text": "傍晚了!窗外夕阳的景色很美丽呢,最美不过夕阳红~"
|
||||
}, {
|
||||
"hour": "20-21",
|
||||
"text": "晚上好,今天过得怎么样?"
|
||||
}, {
|
||||
"hour": "22-23",
|
||||
"text": ["已经这么晚了呀,早点休息吧,晚安~", "深夜时要爱护眼睛呀!"]
|
||||
}, {
|
||||
"hour": "0-5",
|
||||
"text": "你是夜猫子呀?这么晚还不睡觉,明天起的来嘛?"
|
||||
}],
|
||||
"message": {
|
||||
"default": ["好久不见,日子过得好快呢……", "大坏蛋!你都多久没理人家了呀,嘤嘤嘤~", "嗨~快来逗我玩吧!", "拿小拳拳锤你胸口!", "记得把小家加入收藏夹哦!"],
|
||||
"console": "哈哈,你打开了控制台,是想要看看我的小秘密吗?",
|
||||
"copy": "你都复制了些什么呀,转载要记得加上出处哦!",
|
||||
"visibilitychange": "哇,你终于回来了~"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,251 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- index.css -->
|
||||
<link rel="stylesheet" href="{{ url_for('static',filename='css/index.css') }}">
|
||||
</link>
|
||||
<!-- <link rel="stylesheet" href="./css/index.css"> -->
|
||||
<!-- 引入element-ui样式 -->
|
||||
<!-- <link rel="stylesheet" href="./css/element.css"> -->
|
||||
<link rel="stylesheet" href="{{ url_for('static',filename='css/element.css') }}">
|
||||
</link>
|
||||
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.14/theme-chalk/index.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.1/css/all.css">
|
||||
<script src="/static/live2d/autoload.js"></script>
|
||||
|
||||
<title>Fay</title>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="main">
|
||||
<div class="title">
|
||||
<h2>Fay数字人助理版</h2>
|
||||
</div>
|
||||
<div class="main_box">
|
||||
<div class="left">
|
||||
<div class="left_top">
|
||||
<p class="left_top_p">人设:</p>
|
||||
<div class="character">
|
||||
<div class="character_top">
|
||||
<div class="character_left">
|
||||
<ul>
|
||||
<li>
|
||||
<p>姓名:</p>
|
||||
<el-input v-model="attribute_name" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
<li>
|
||||
<p>性别:</p>
|
||||
<el-input v-model="attribute_gender" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
<li>
|
||||
<p>年龄:</p>
|
||||
<el-input v-model="attribute_age" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>出生地:</p>
|
||||
<el-input v-model="attribute_birth" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
<li>
|
||||
<p>生肖:</p>
|
||||
<el-input v-model="attribute_zodiac" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
<li>
|
||||
<p>星座:</p>
|
||||
<el-input v-model="attribute_constellation" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
<li>
|
||||
<p>职业:</p>
|
||||
<el-input v-model="attribute_job" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
<li>
|
||||
<p>联系方式:</p>
|
||||
<el-input v-model="attribute_contact" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
<li>
|
||||
<p>喜好:</p>
|
||||
<el-input v-model="attribute_hobby" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="character_right">
|
||||
<ul>
|
||||
<li style="width: 375px;">
|
||||
<p>Q&A文件:</p>
|
||||
<el-input v-model="interact_QnA" placeholder="请输入内容"></el-input>
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<p>使用面板播放:</p>
|
||||
<p style="text-align: left;" v-if="play_sound_enabled">是</p>
|
||||
<p style="text-align: left;" v-else>否</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>声音选择:{{attribute_voice}}</p>
|
||||
<el-select v-model="attribute_voice" placeholder="请选择">
|
||||
<el-option v-for="item in voice_list" :key="item.value"
|
||||
:label="item.label" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<p>敏感度:</p>
|
||||
<el-slider v-model="interact_perception_follow"></el-slider>
|
||||
</li>
|
||||
|
||||
<br>
|
||||
<li >
|
||||
<el-button style="margin-left: 55px;width: 135px;" type="success" class="btn_open"
|
||||
@click=postControlEyes()><i class="fas fa-eye"></i> Fay Eyes</el-button>
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<p>唤醒模式:</p>
|
||||
<el-switch v-model="wake_word_enabled" active-color="#13ce66"
|
||||
inactive-color="#ff4949">
|
||||
</el-switch>
|
||||
</li>
|
||||
|
||||
<li style="margin-bottom: 10px;">
|
||||
<p>唤醒词:</p>
|
||||
<el-input style="width: 250px;" v-model="wake_word" placeholder="请输入内容(以,隔开)"></el-input>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>唤醒方式:</p>
|
||||
<el-select v-model="wake_word_type" placeholder="请选择">
|
||||
<el-option v-for="item in wake_word_type_options" :key="item.value"
|
||||
:label="item.label" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</li>
|
||||
<br>
|
||||
<li>
|
||||
<p> 远程音频:</p>
|
||||
<p style = "text-align: left;color: #67c23a" v-if="remote_audio_connect" >已连接 </p>
|
||||
<p style = "text-align: left;" v-else>未连接 </p>
|
||||
</li>
|
||||
<li>
|
||||
<p>tts合成:</p>
|
||||
<el-switch v-model="tts_enabled" active-color="#13ce66"
|
||||
inactive-color="#ff4949">
|
||||
</el-switch>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="left_box">
|
||||
<p> </p>
|
||||
<div class="source">
|
||||
<ul>
|
||||
|
||||
<li class="url">
|
||||
<el-switch v-model="source_record_enabled" active-color="#13ce66"
|
||||
inactive-color="#ff4949">
|
||||
</el-switch>
|
||||
<p>麦克风</p>
|
||||
<el-select v-model="source_record_device" placeholder="请选择">
|
||||
<el-option v-for="item in device_list" :key="item.value" :label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</li>
|
||||
<li class="url">
|
||||
|
||||
<p style="margin-left: 40px">消 息</p>
|
||||
<el-input v-model="panel_msg" :disabled="true"></el-input>
|
||||
</li>
|
||||
<li class="but">
|
||||
<el-button v-if="live_state == 1" type="success" class="btn_close"
|
||||
style="width:200px" @click=postStopLive()>关闭(运行中)</el-button>
|
||||
<el-button v-else-if="live_state == 2" type="primary" plain disabled
|
||||
style="width:200px">正在开启...</el-button>
|
||||
<el-button v-else-if="live_state == 3" type="success" plain disabled
|
||||
style="width:200px">正在关闭...</el-button>
|
||||
<el-button v-else type="primary" style="width:200px" class="btn_open"
|
||||
@click=postStartLive()>开启</el-button>
|
||||
<el-button type="button" style="width:200px" @click=postData()>保存配置</el-button>
|
||||
</li>
|
||||
<li class="p_red" >
|
||||
<p v-if="is_connect" style="color: #67c23a;"> 数字人:已连接 </p>
|
||||
<p v-else > 数字人:未连接 </p>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="right_main">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
|
||||
<div v-for="(item, index) in msg_list" :key="index">
|
||||
|
||||
<div class="item item-center"><span>[[item.timetext]]</span></div>
|
||||
|
||||
<div class="item item-left" v-if="item.type == 'fay'">
|
||||
<div class="avatar"><img src="{{ url_for('static',filename='to.jpg') }}" />
|
||||
</div>
|
||||
<div class="bubble bubble-left">[[item.content]]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="item item-right" v-else>
|
||||
<div class="bubble bubble-right" >[[item.content]]<div v-if="loading && index === msg_list.length - 1" class="spinner"></div></div>
|
||||
<div class="avatar"><img src="{{ url_for('static',filename='from.jpg') }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<textarea v-model="send_msg" name="text" id="textarea" placeholder="发送些内容给Fay..."></textarea>
|
||||
<div class="button-area">
|
||||
<button id="send-btn" @click="send(1)">Fay</button>
|
||||
|
||||
<!-- <button id="send-btn" @click="send(2)" style="margin-left: 25px;">ChatGPT</button> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<!-- 开发环境vue.js -->
|
||||
<script src="{{ url_for('static',filename='js/vue.js') }}"></script>
|
||||
<!-- 发行环境vue.js -->
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2"></script> -->
|
||||
<!-- 引入element-ui组件库 -->
|
||||
<!-- <script src="./js/element.js"></script> -->
|
||||
<script src="{{ url_for('static',filename='js/element.js') }}"></script>
|
||||
<!-- index.js -->
|
||||
<!-- <script src="./js/index.js"></script> -->
|
||||
<!-- <script src="./js/self-adaption.js"></script> -->
|
||||
|
||||
<script src="{{ url_for('static',filename='js/index.js') }}"></script>
|
||||
<script src="{{ url_for('static',filename='js/self-adaption.js') }}"></script>
|
||||
|
||||
</html>
|
||||
@@ -1,84 +0,0 @@
|
||||
import os
|
||||
|
||||
import time
|
||||
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout
|
||||
from PyQt5.QtWidgets import QGroupBox
|
||||
from PyQt5.QtWebEngineWidgets import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from scheduler.thread_manager import MyThread
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
SigSendMessageToJS = pyqtSignal(str)
|
||||
|
||||
def __init__(self):
|
||||
super(MainWindow, self).__init__()
|
||||
# self.setWindowFlags(Qt.WindowType.WindowShadeButtonHint)
|
||||
self.setWindowTitle('FeiFei Alpha')
|
||||
# self.setFixedSize(16 * 80, 9 * 80)
|
||||
self.setGeometry(0, 0, 16 * 70, 9 * 70)
|
||||
self.showMaximized()
|
||||
# self.center()
|
||||
self.browser = QWebEngineView()
|
||||
#清空缓存
|
||||
profile = QWebEngineProfile.defaultProfile()
|
||||
profile.clearHttpCache()
|
||||
self.browser.load(QUrl('http://127.0.0.1:5000'))
|
||||
self.setCentralWidget(self.browser)
|
||||
MyThread(target=self.runnable).start()
|
||||
|
||||
def runnable(self):
|
||||
while True:
|
||||
if not self.isVisible():
|
||||
# try:
|
||||
# wsa_server.get_instance().stop_server()
|
||||
# wsa_server.get_web_instance().stop_server()
|
||||
# thread_manager.stopAll()
|
||||
# except BaseException as e:
|
||||
# print(e)
|
||||
os.system("taskkill /F /PID {}".format(os.getpid()))
|
||||
time.sleep(0.05)
|
||||
|
||||
def center(self):
|
||||
screen = QtWidgets.QDesktopWidget().screenGeometry()
|
||||
size = self.geometry()
|
||||
self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
pass
|
||||
# if event.key() == Qt.Key_F12:
|
||||
# self.s = TDevWindow()
|
||||
# self.s.show()
|
||||
# self.browser.page().setDevToolsPage(self.s.mpJSWebView.page())
|
||||
|
||||
def OnReceiveMessageFromJS(self, strParameter):
|
||||
if not strParameter:
|
||||
return
|
||||
|
||||
|
||||
class TDevWindow(QDialog):
|
||||
def __init__(self):
|
||||
super(TDevWindow, self).__init__()
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
self.mpJSWebView = QWebEngineView(self)
|
||||
self.url = 'https://www.baidu.com/'
|
||||
self.mpJSWebView.page().load(QUrl(self.url))
|
||||
self.mpJSWebView.show()
|
||||
|
||||
self.pJSTotalVLayout = QVBoxLayout()
|
||||
self.pJSTotalVLayout.setSpacing(0)
|
||||
self.pJSTotalVLayout.addWidget(self.mpJSWebView)
|
||||
self.pWebGroup = QGroupBox('Web View', self)
|
||||
self.pWebGroup.setLayout(self.pJSTotalVLayout)
|
||||
|
||||
self.mainLayout = QHBoxLayout()
|
||||
self.mainLayout.setSpacing(5)
|
||||
self.mainLayout.addWidget(self.pWebGroup)
|
||||
self.setLayout(self.mainLayout)
|
||||
self.setMinimumSize(800, 800)
|
||||
|
Before Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 635 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 592 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 3.4 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 893 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 118 KiB |
@@ -1,63 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
from PyQt5 import QtGui
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from ai_module import ali_nls
|
||||
from core import wsa_server
|
||||
from gui import flask_server
|
||||
from gui.window import MainWindow
|
||||
from utils import config_util
|
||||
from scheduler.thread_manager import MyThread
|
||||
from core import content_db
|
||||
import sys
|
||||
sys.setrecursionlimit(sys.getrecursionlimit() * 5)
|
||||
|
||||
def __clear_samples():
|
||||
if not os.path.exists("./samples"):
|
||||
os.mkdir("./samples")
|
||||
for file_name in os.listdir('./samples'):
|
||||
if file_name.startswith('sample-'):
|
||||
os.remove('./samples/' + file_name)
|
||||
|
||||
|
||||
def __clear_songs():
|
||||
if not os.path.exists("./songs"):
|
||||
os.mkdir("./songs")
|
||||
for file_name in os.listdir('./songs'):
|
||||
if file_name.endswith('.mp3'):
|
||||
os.remove('./songs/' + file_name)
|
||||
|
||||
def __clear_logs():
|
||||
if not os.path.exists("./logs"):
|
||||
os.mkdir("./logs")
|
||||
for file_name in os.listdir('./logs'):
|
||||
if file_name.endswith('.log'):
|
||||
os.remove('./logs/' + file_name)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
__clear_samples()
|
||||
__clear_songs()
|
||||
__clear_logs()
|
||||
config_util.load_config()
|
||||
contentdb = content_db.new_instance()
|
||||
contentdb.init_db()
|
||||
ws_server = wsa_server.new_instance(port=10002)
|
||||
ws_server.start_server()
|
||||
web_ws_server = wsa_server.new_web_instance(port=10003)
|
||||
web_ws_server.start_server()
|
||||
#Edit by xszyou in 20230516:增加本地asr后,aliyun调成可选配置
|
||||
if config_util.ASR_mode == "ali":
|
||||
ali_nls.start()
|
||||
flask_server.start()
|
||||
app = QApplication(sys.argv)
|
||||
app.setWindowIcon(QtGui.QIcon('icon.png'))
|
||||
win = MainWindow()
|
||||
win.show()
|
||||
app.exit(app.exec_())
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import socket
|
||||
import pyaudio
|
||||
import time
|
||||
import pygame
|
||||
|
||||
import thread_manager
|
||||
|
||||
def get_stream():
|
||||
paudio = pyaudio.PyAudio()
|
||||
device_id = 0
|
||||
if device_id < 0:
|
||||
return
|
||||
stream = paudio.open(input_device_index=device_id, rate=16000, format=pyaudio.paInt16, channels=1, input=True)
|
||||
return stream
|
||||
|
||||
def send_audio(client):
|
||||
stream = get_stream()
|
||||
while stream:
|
||||
data = stream.read(1024, exception_on_overflow=False)
|
||||
client.send(data)
|
||||
time.sleep(0.005)
|
||||
print(".", end="")
|
||||
|
||||
def receive_audio(client):
|
||||
while True:
|
||||
data = client.recv(9)
|
||||
filedata = b''
|
||||
if b"\x00\x01\x02\x03\x04\x05\x06\x07\x08" == data: #mp3文件开始传输标志
|
||||
while True:
|
||||
data = client.recv(1024)
|
||||
filedata += data
|
||||
filedata = filedata.replace(b'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8', b"") #去除心跳信息
|
||||
if b"\x08\x07\x06\x05\x04\x03\x02\x01\x00" == filedata[-9:]:#mp3文件结束传输标志
|
||||
filedata = filedata[:-9]
|
||||
break
|
||||
print("receive audio end:{}".format(len(filedata)), end="")
|
||||
|
||||
filename = "sample/recv_{}.mp3".format(time.time())
|
||||
with open(filename, "wb") as f:
|
||||
f.write(filedata)
|
||||
f.close()
|
||||
pygame.mixer.music.load(filename)
|
||||
pygame.mixer.music.play()
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
client = socket.socket()
|
||||
client.connect(("192.168.1.101", 10001))
|
||||
pygame.mixer.init()
|
||||
thread_manager.MyThread(target=send_audio, args=(client,)).start()
|
||||
thread_manager.MyThread(target=receive_audio, args=(client,)).start()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import ctypes
|
||||
import threading
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class MyThread(Thread):
|
||||
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
|
||||
Thread.__init__(self, group=group, target=target, name=name, args=args, kwargs=kwargs, daemon=daemon)
|
||||
add_thread(self)
|
||||
|
||||
def get_id(self):
|
||||
# returns id of the respective thread
|
||||
if hasattr(self, '_thread_id'):
|
||||
return self._thread_id
|
||||
for id, thread in threading._active.items():
|
||||
if thread is self:
|
||||
return id
|
||||
|
||||
def raise_exception(self):
|
||||
thread_id = self.get_id()
|
||||
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
|
||||
if res > 1:
|
||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
|
||||
print('Exception raise failure')
|
||||
|
||||
|
||||
__thread_list = []
|
||||
|
||||
|
||||
def add_thread(thread: MyThread):
|
||||
if thread not in __thread_list:
|
||||
__thread_list.append(thread)
|
||||
|
||||
|
||||
def remove_thread(thread: MyThread):
|
||||
if thread in __thread_list:
|
||||
__thread_list.remove(thread)
|
||||
|
||||
|
||||
def stopAll():
|
||||
for thread in __thread_list:
|
||||
thread.raise_exception()
|
||||
thread.join()
|
||||
@@ -1,24 +0,0 @@
|
||||
requests
|
||||
numpy
|
||||
pyaudio~=0.2.11
|
||||
websockets~=10.2
|
||||
ws4py~=0.5.1
|
||||
pyqt5~=5.15.6
|
||||
flask~=3.0.0
|
||||
openpyxl~=3.0.9
|
||||
pygame~=2.1.2
|
||||
flask_cors~=3.0.10
|
||||
PyQtWebEngine~=5.15.5
|
||||
eyed3~=0.9.6
|
||||
websocket-client
|
||||
azure-cognitiveservices-speech
|
||||
aliyun-python-sdk-core==2.13.3
|
||||
scipy~=1.10.0
|
||||
simhash
|
||||
pytz
|
||||
gevent~=22.10.1
|
||||
edge_tts~=6.1.3
|
||||
eyed3
|
||||
ultralytics~=8.0.2
|
||||
pydub
|
||||
cemotion
|
||||
@@ -1,43 +0,0 @@
|
||||
import ctypes
|
||||
import threading
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class MyThread(Thread):
|
||||
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
|
||||
Thread.__init__(self, group=group, target=target, name=name, args=args, kwargs=kwargs, daemon=daemon)
|
||||
add_thread(self)
|
||||
|
||||
def get_id(self):
|
||||
# returns id of the respective thread
|
||||
if hasattr(self, '_thread_id'):
|
||||
return self._thread_id
|
||||
for id, thread in threading._active.items():
|
||||
if thread is self:
|
||||
return id
|
||||
|
||||
def raise_exception(self):
|
||||
thread_id = self.get_id()
|
||||
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
|
||||
if res > 1:
|
||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
|
||||
print('Exception raise failure')
|
||||
|
||||
|
||||
__thread_list = []
|
||||
|
||||
|
||||
def add_thread(thread: MyThread):
|
||||
if thread not in __thread_list:
|
||||
__thread_list.append(thread)
|
||||
|
||||
|
||||
def remove_thread(thread: MyThread):
|
||||
if thread in __thread_list:
|
||||
__thread_list.remove(thread)
|
||||
|
||||
|
||||
def stopAll():
|
||||
for thread in __thread_list:
|
||||
thread.raise_exception()
|
||||
thread.join()
|
||||
@@ -1,816 +0,0 @@
|
||||
#HomeBrew自动安装脚本
|
||||
#qq 467665317
|
||||
#brew brew brew brew
|
||||
|
||||
#获取硬件信息 判断inter还是苹果M
|
||||
UNAME_MACHINE="$(uname -m)"
|
||||
#在X86电脑上测试arm电脑
|
||||
# UNAME_MACHINE="arm64"
|
||||
|
||||
# 判断是Linux还是Mac os
|
||||
OS="$(uname)"
|
||||
if [[ "$OS" == "Linux" ]]; then
|
||||
HOMEBREW_ON_LINUX=1
|
||||
elif [[ "$OS" != "Darwin" ]]; then
|
||||
echo "Homebrew 只运行在 Mac OS 或 Linux."
|
||||
fi
|
||||
|
||||
# 字符串染色程序
|
||||
if [[ -t 1 ]]; then
|
||||
tty_escape() { printf "\033[%sm" "$1"; }
|
||||
else
|
||||
tty_escape() { :; }
|
||||
fi
|
||||
tty_universal() { tty_escape "0;$1"; } #正常显示
|
||||
tty_mkbold() { tty_escape "1;$1"; } #设置高亮
|
||||
tty_underline="$(tty_escape "4;39")" #下划线
|
||||
tty_blue="$(tty_universal 34)" #蓝色
|
||||
tty_red="$(tty_universal 31)" #红色
|
||||
tty_green="$(tty_universal 32)" #绿色
|
||||
tty_yellow="$(tty_universal 33)" #黄色
|
||||
tty_bold="$(tty_universal 39)" #加黑
|
||||
tty_cyan="$(tty_universal 36)" #青色
|
||||
tty_reset="$(tty_escape 0)" #去除颜色
|
||||
|
||||
#用户输入极速安装speed,git克隆只取最近新版本
|
||||
#但是update会出错,提示需要下载全部数据
|
||||
GIT_SPEED=""
|
||||
|
||||
if [[ $0 == "speed" ]]; then
|
||||
GIT_SPEED="--depth=1"
|
||||
else
|
||||
for dir in $@; do
|
||||
echo $dir
|
||||
if [[ $dir == "speed" ]]; then
|
||||
GIT_SPEED="--depth=1"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $GIT_SPEED != "" ]]; then
|
||||
echo "${tty_red}
|
||||
检测到参数speed,只拉取最新数据,可以正常install使用!
|
||||
腾讯和阿里不支持speed拉取,需要腾讯阿里需要完全模式。
|
||||
但是以后brew update的时候会报错,运行报错提示的两句命令即可修复
|
||||
${tty_reset}"
|
||||
fi
|
||||
|
||||
#获取前面两个.的数据
|
||||
major_minor() {
|
||||
echo "${1%%.*}.$(x="${1#*.}"; echo "${x%%.*}")"
|
||||
}
|
||||
|
||||
#设置一些平台地址
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
#Mac
|
||||
if [[ "$UNAME_MACHINE" == "arm64" ]]; then
|
||||
#M1
|
||||
HOMEBREW_PREFIX="/opt/homebrew"
|
||||
HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}"
|
||||
else
|
||||
#Inter
|
||||
HOMEBREW_PREFIX="/usr/local"
|
||||
HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew"
|
||||
fi
|
||||
|
||||
HOMEBREW_CACHE="${HOME}/Library/Caches/Homebrew"
|
||||
HOMEBREW_LOGS="${HOME}/Library/Logs/Homebrew"
|
||||
|
||||
#国内没有homebrew-services,手动在gitee创建了一个,有少数人用到。
|
||||
USER_SERVICES_GIT=https://gitee.com/cunkai/homebrew-services.git
|
||||
|
||||
STAT="stat -f"
|
||||
CHOWN="/usr/sbin/chown"
|
||||
CHGRP="/usr/bin/chgrp"
|
||||
GROUP="admin"
|
||||
TOUCH="/usr/bin/touch"
|
||||
|
||||
#获取Mac系统版本
|
||||
macos_version="$(major_minor "$(/usr/bin/sw_vers -productVersion)")"
|
||||
else
|
||||
#Linux
|
||||
UNAME_MACHINE="$(uname -m)"
|
||||
|
||||
HOMEBREW_PREFIX="/home/linuxbrew/.linuxbrew"
|
||||
HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew"
|
||||
|
||||
HOMEBREW_CACHE="${HOME}/.cache/Homebrew"
|
||||
HOMEBREW_LOGS="${HOME}/.logs/Homebrew"
|
||||
|
||||
STAT="stat --printf"
|
||||
CHOWN="/bin/chown"
|
||||
CHGRP="/bin/chgrp"
|
||||
GROUP="$(id -gn)"
|
||||
TOUCH="/bin/touch"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
#获取系统时间
|
||||
TIME=$(date "+%Y-%m-%d %H:%M:%S")
|
||||
|
||||
JudgeSuccess()
|
||||
{
|
||||
if [ $? -ne 0 ];then
|
||||
echo "${tty_red}此步骤失败 '$1'${tty_reset}"
|
||||
if [[ "$2" == 'out' ]]; then
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "${tty_green}此步骤成功${tty_reset}"
|
||||
|
||||
fi
|
||||
}
|
||||
# 判断是否有系统权限
|
||||
have_sudo_access() {
|
||||
if [[ -z "${HAVE_SUDO_ACCESS-}" ]]; then
|
||||
/usr/bin/sudo -l mkdir &>/dev/null
|
||||
HAVE_SUDO_ACCESS="$?"
|
||||
fi
|
||||
|
||||
if [[ "$HAVE_SUDO_ACCESS" -ne 0 ]]; then
|
||||
echo "${tty_red}开机密码输入错误,获取权限失败!${tty_reset}"
|
||||
fi
|
||||
|
||||
return "$HAVE_SUDO_ACCESS"
|
||||
}
|
||||
|
||||
|
||||
abort() {
|
||||
printf "%s\n" "$1"
|
||||
# exit 1
|
||||
}
|
||||
|
||||
shell_join() {
|
||||
local arg
|
||||
printf "%s" "$1"
|
||||
shift
|
||||
for arg in "$@"; do
|
||||
printf " "
|
||||
printf "%s" "${arg// /\ }"
|
||||
done
|
||||
}
|
||||
|
||||
execute() {
|
||||
if ! "$@"; then
|
||||
abort "$(printf "${tty_red}此命令运行失败: %s${tty_reset}" "$(shell_join "$@")")"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
ohai() {
|
||||
printf "${tty_blue}运行代码 ==>${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")"
|
||||
}
|
||||
|
||||
# 管理员运行
|
||||
execute_sudo()
|
||||
{
|
||||
|
||||
local -a args=("$@")
|
||||
if have_sudo_access; then
|
||||
if [[ -n "${SUDO_ASKPASS-}" ]]; then
|
||||
args=("-A" "${args[@]}")
|
||||
fi
|
||||
ohai "/usr/bin/sudo" "${args[@]}"
|
||||
execute "/usr/bin/sudo" "${args[@]}"
|
||||
else
|
||||
ohai "${args[@]}"
|
||||
execute "${args[@]}"
|
||||
fi
|
||||
}
|
||||
#添加文件夹权限
|
||||
AddPermission()
|
||||
{
|
||||
execute_sudo "/bin/chmod" "-R" "a+rwx" "$1"
|
||||
execute_sudo "$CHOWN" "$USER" "$1"
|
||||
execute_sudo "$CHGRP" "$GROUP" "$1"
|
||||
}
|
||||
#创建文件夹
|
||||
CreateFolder()
|
||||
{
|
||||
echo '-> 创建文件夹' $1
|
||||
execute_sudo "/bin/mkdir" "-p" "$1"
|
||||
JudgeSuccess
|
||||
AddPermission $1
|
||||
}
|
||||
|
||||
RmAndCopy()
|
||||
{
|
||||
if [[ -d $1 ]]; then
|
||||
echo " ---备份要删除的$1到系统桌面...."
|
||||
if ! [[ -d $HOME/Desktop/Old_Homebrew/$TIME/$1 ]]; then
|
||||
sudo mkdir -p "$HOME/Desktop/Old_Homebrew/$TIME/$1"
|
||||
fi
|
||||
sudo cp -rf $1 "$HOME/Desktop/Old_Homebrew/$TIME/$1"
|
||||
echo " ---$1 备份完成"
|
||||
fi
|
||||
sudo rm -rf $1
|
||||
}
|
||||
|
||||
RmCreate()
|
||||
{
|
||||
RmAndCopy $1
|
||||
CreateFolder $1
|
||||
}
|
||||
|
||||
#判断文件夹存在但不可写
|
||||
exists_but_not_writable() {
|
||||
[[ -e "$1" ]] && ! [[ -r "$1" && -w "$1" && -x "$1" ]]
|
||||
}
|
||||
#文件所有者
|
||||
get_owner() {
|
||||
$(shell_join "$STAT %u $1" )
|
||||
}
|
||||
#文件本人无权限
|
||||
file_not_owned() {
|
||||
[[ "$(get_owner "$1")" != "$(id -u)" ]]
|
||||
}
|
||||
#获取所属的组
|
||||
get_group() {
|
||||
$(shell_join "$STAT %g $1" )
|
||||
}
|
||||
#不在所属组
|
||||
file_not_grpowned() {
|
||||
[[ " $(id -G "$USER") " != *" $(get_group "$1") "* ]]
|
||||
}
|
||||
#获得当前文件夹权限 例如777
|
||||
get_permission() {
|
||||
$(shell_join "$STAT %A $1" )
|
||||
}
|
||||
#授权当前用户权限
|
||||
user_only_chmod() {
|
||||
[[ -d "$1" ]] && [[ "$(get_permission "$1")" != "755" ]]
|
||||
}
|
||||
|
||||
|
||||
#创建brew需要的目录 直接复制于国外版本,同步
|
||||
CreateBrewLinkFolder()
|
||||
{
|
||||
echo "--创建Brew所需要的目录"
|
||||
directories=(bin etc include lib sbin share opt var
|
||||
Frameworks
|
||||
etc/bash_completion.d lib/pkgconfig
|
||||
share/aclocal share/doc share/info share/locale share/man
|
||||
share/man/man1 share/man/man2 share/man/man3 share/man/man4
|
||||
share/man/man5 share/man/man6 share/man/man7 share/man/man8
|
||||
var/log var/homebrew var/homebrew/linked
|
||||
bin/brew)
|
||||
group_chmods=()
|
||||
for dir in "${directories[@]}"; do
|
||||
if exists_but_not_writable "${HOMEBREW_PREFIX}/${dir}"; then
|
||||
group_chmods+=("${HOMEBREW_PREFIX}/${dir}")
|
||||
fi
|
||||
done
|
||||
|
||||
directories=(share/zsh share/zsh/site-functions)
|
||||
zsh_dirs=()
|
||||
for dir in "${directories[@]}"; do
|
||||
zsh_dirs+=("${HOMEBREW_PREFIX}/${dir}")
|
||||
done
|
||||
|
||||
directories=(bin etc include lib sbin share var opt
|
||||
share/zsh share/zsh/site-functions
|
||||
var/homebrew var/homebrew/linked
|
||||
Cellar Caskroom Frameworks)
|
||||
mkdirs=()
|
||||
for dir in "${directories[@]}"; do
|
||||
if ! [[ -d "${HOMEBREW_PREFIX}/${dir}" ]]; then
|
||||
mkdirs+=("${HOMEBREW_PREFIX}/${dir}")
|
||||
fi
|
||||
done
|
||||
|
||||
user_chmods=()
|
||||
if [[ "${#zsh_dirs[@]}" -gt 0 ]]; then
|
||||
for dir in "${zsh_dirs[@]}"; do
|
||||
if user_only_chmod "${dir}"; then
|
||||
user_chmods+=("${dir}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
chmods=()
|
||||
if [[ "${#group_chmods[@]}" -gt 0 ]]; then
|
||||
chmods+=("${group_chmods[@]}")
|
||||
fi
|
||||
if [[ "${#user_chmods[@]}" -gt 0 ]]; then
|
||||
chmods+=("${user_chmods[@]}")
|
||||
fi
|
||||
|
||||
chowns=()
|
||||
chgrps=()
|
||||
if [[ "${#chmods[@]}" -gt 0 ]]; then
|
||||
for dir in "${chmods[@]}"; do
|
||||
if file_not_owned "${dir}"; then
|
||||
chowns+=("${dir}")
|
||||
fi
|
||||
if file_not_grpowned "${dir}"; then
|
||||
chgrps+=("${dir}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -d "${HOMEBREW_PREFIX}" ]]; then
|
||||
if [[ "${#chmods[@]}" -gt 0 ]]; then
|
||||
execute_sudo "/bin/chmod" "u+rwx" "${chmods[@]}"
|
||||
fi
|
||||
if [[ "${#group_chmods[@]}" -gt 0 ]]; then
|
||||
execute_sudo "/bin/chmod" "g+rwx" "${group_chmods[@]}"
|
||||
fi
|
||||
if [[ "${#user_chmods[@]}" -gt 0 ]]; then
|
||||
execute_sudo "/bin/chmod" "755" "${user_chmods[@]}"
|
||||
fi
|
||||
if [[ "${#chowns[@]}" -gt 0 ]]; then
|
||||
execute_sudo "$CHOWN" "$USER" "${chowns[@]}"
|
||||
fi
|
||||
if [[ "${#chgrps[@]}" -gt 0 ]]; then
|
||||
execute_sudo "$CHGRP" "$GROUP" "${chgrps[@]}"
|
||||
fi
|
||||
else
|
||||
execute_sudo "/bin/mkdir" "-p" "${HOMEBREW_PREFIX}"
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
execute_sudo "$CHOWN" "root:wheel" "${HOMEBREW_PREFIX}"
|
||||
else
|
||||
execute_sudo "$CHOWN" "$USER:$GROUP" "${HOMEBREW_PREFIX}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${#mkdirs[@]}" -gt 0 ]]; then
|
||||
execute_sudo "/bin/mkdir" "-p" "${mkdirs[@]}"
|
||||
execute_sudo "/bin/chmod" "g+rwx" "${mkdirs[@]}"
|
||||
execute_sudo "$CHOWN" "$USER" "${mkdirs[@]}"
|
||||
execute_sudo "$CHGRP" "$GROUP" "${mkdirs[@]}"
|
||||
fi
|
||||
|
||||
if ! [[ -d "${HOMEBREW_REPOSITORY}" ]]; then
|
||||
execute_sudo "/bin/mkdir" "-p" "${HOMEBREW_REPOSITORY}"
|
||||
fi
|
||||
execute_sudo "$CHOWN" "-R" "$USER:$GROUP" "${HOMEBREW_REPOSITORY}"
|
||||
|
||||
if ! [[ -d "${HOMEBREW_CACHE}" ]]; then
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
execute_sudo "/bin/mkdir" "-p" "${HOMEBREW_CACHE}"
|
||||
else
|
||||
execute "/bin/mkdir" "-p" "${HOMEBREW_CACHE}"
|
||||
fi
|
||||
fi
|
||||
if exists_but_not_writable "${HOMEBREW_CACHE}"; then
|
||||
execute_sudo "/bin/chmod" "g+rwx" "${HOMEBREW_CACHE}"
|
||||
fi
|
||||
if file_not_owned "${HOMEBREW_CACHE}"; then
|
||||
execute_sudo "$CHOWN" "-R" "$USER" "${HOMEBREW_CACHE}"
|
||||
fi
|
||||
if file_not_grpowned "${HOMEBREW_CACHE}"; then
|
||||
execute_sudo "$CHGRP" "-R" "$GROUP" "${HOMEBREW_CACHE}"
|
||||
fi
|
||||
if [[ -d "${HOMEBREW_CACHE}" ]]; then
|
||||
execute "$TOUCH" "${HOMEBREW_CACHE}/.cleaned"
|
||||
fi
|
||||
echo "--依赖目录脚本运行完成"
|
||||
}
|
||||
|
||||
#git提交
|
||||
git_commit(){
|
||||
git add .
|
||||
git commit -m "your del"
|
||||
}
|
||||
|
||||
#version_gt 判断$1是否大于$2
|
||||
version_gt() {
|
||||
[[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -gt "${2#*.}" ]]
|
||||
}
|
||||
#version_ge 判断$1是否大于等于$2
|
||||
version_ge() {
|
||||
[[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -ge "${2#*.}" ]]
|
||||
}
|
||||
#version_lt 判断$1是否小于$2
|
||||
version_lt() {
|
||||
[[ "${1%.*}" -lt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -lt "${2#*.}" ]]
|
||||
}
|
||||
|
||||
#发现错误 关闭脚本 提示如何解决
|
||||
error_game_over(){
|
||||
echo "
|
||||
${tty_red}失败$MY_DOWN_NUM 右键下面地址查看常见错误解决办法
|
||||
https://github.com/TheRamU/Fay
|
||||
如果没有解决,把全部运行过程截图发到 467665317@qq.com ${tty_reset}
|
||||
"
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
#一些警告判断
|
||||
warning_if(){
|
||||
git_https_proxy=$(git config --global https.proxy)
|
||||
git_http_proxy=$(git config --global http.proxy)
|
||||
if [[ -z "$git_https_proxy" && -z "$git_http_proxy" ]]; then
|
||||
echo "未发现Git代理(属于正常状态)"
|
||||
else
|
||||
echo "${tty_yellow}
|
||||
提示:发现你电脑设置了Git代理,如果Git报错,请运行下面两句话:
|
||||
|
||||
git config --global --unset https.proxy
|
||||
|
||||
git config --global --unset http.proxy${tty_reset}
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "
|
||||
${tty_green} 开始执行Brew自动安装程序 ${tty_reset}
|
||||
${tty_cyan} [467665317@qq.com] ${tty_reset}
|
||||
['$TIME']['$macos_version']
|
||||
${tty_cyan} https://github.com/TheRamU/Fay${tty_reset}
|
||||
"
|
||||
#选择一个brew下载源
|
||||
echo -n "${tty_green}
|
||||
请选择一个下载brew本体的序号,例如中科大,输入1回车。
|
||||
源有时候不稳定,如果git克隆报错重新运行脚本选择源。
|
||||
1、中科大下载源
|
||||
2、清华大学下载源
|
||||
3、北京外国语大学下载源 ${tty_reset}"
|
||||
if [[ $GIT_SPEED == "" ]]; then
|
||||
echo -n "${tty_green}
|
||||
4、腾讯下载源
|
||||
5、阿里巴巴下载源 ${tty_reset}"
|
||||
fi
|
||||
echo -n "
|
||||
${tty_blue}请输入序号: "
|
||||
read MY_DOWN_NUM
|
||||
echo "${tty_reset}"
|
||||
case $MY_DOWN_NUM in
|
||||
"2")
|
||||
echo "
|
||||
你选择了清华大学brew本体下载源
|
||||
"
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/
|
||||
#HomeBrew基础框架
|
||||
USER_BREW_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git
|
||||
#HomeBrew Core
|
||||
USER_CORE_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git
|
||||
#HomeBrew Cask
|
||||
USER_CASK_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git
|
||||
USER_CASK_FONTS_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-fonts.git
|
||||
USER_CASK_DRIVERS_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-drivers.git
|
||||
;;
|
||||
"3")
|
||||
echo "
|
||||
北京外国语大学brew本体下载源
|
||||
"
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.bfsu.edu.cn/homebrew-bottles
|
||||
#HomeBrew基础框架
|
||||
USER_BREW_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/brew.git
|
||||
#HomeBrew Core
|
||||
USER_CORE_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/homebrew-core.git
|
||||
#HomeBrew Cask
|
||||
USER_CASK_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/homebrew-cask.git
|
||||
USER_CASK_FONTS_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/homebrew-cask-fonts.git
|
||||
USER_CASK_DRIVERS_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/homebrew-cask-drivers.git
|
||||
;;
|
||||
"4")
|
||||
echo "
|
||||
你选择了腾讯brew本体下载源
|
||||
"
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.cloud.tencent.com/homebrew-bottles
|
||||
#HomeBrew基础框架
|
||||
USER_BREW_GIT=https://mirrors.cloud.tencent.com/homebrew/brew.git
|
||||
#HomeBrew Core
|
||||
USER_CORE_GIT=https://mirrors.cloud.tencent.com/homebrew/homebrew-core.git
|
||||
#HomeBrew Cask
|
||||
USER_CASK_GIT=https://mirrors.cloud.tencent.com/homebrew/homebrew-cask.git
|
||||
;;
|
||||
"5")
|
||||
echo "
|
||||
你选择了阿里巴巴brew本体下载源
|
||||
"
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles
|
||||
#HomeBrew基础框架
|
||||
USER_BREW_GIT=https://mirrors.aliyun.com/homebrew/brew.git
|
||||
#HomeBrew Core
|
||||
USER_CORE_GIT=https://mirrors.aliyun.com/homebrew/homebrew-core.git
|
||||
#HomeBrew Cask
|
||||
USER_CASK_GIT=https://mirrors.aliyun.com/homebrew/homebrew-cask.git
|
||||
;;
|
||||
*)
|
||||
echo "
|
||||
你选择了中国科学技术大学brew本体下载源
|
||||
"
|
||||
#HomeBrew 下载源 install
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
|
||||
#HomeBrew基础框架
|
||||
USER_BREW_GIT=https://mirrors.ustc.edu.cn/brew.git
|
||||
#HomeBrew Core
|
||||
USER_CORE_GIT=https://mirrors.ustc.edu.cn/homebrew-core.git
|
||||
#HomeBrew Cask
|
||||
USER_CASK_GIT=https://mirrors.ustc.edu.cn/homebrew-cask.git
|
||||
;;
|
||||
esac
|
||||
echo -n "${tty_green}!!!此脚本将要删除之前的brew(包括它下载的软件),请自行备份。
|
||||
->是否现在开始执行脚本(N/Y) "
|
||||
read MY_Del_Old
|
||||
echo "${tty_reset}"
|
||||
case $MY_Del_Old in
|
||||
"y")
|
||||
echo "--> 脚本开始执行"
|
||||
;;
|
||||
"Y")
|
||||
echo "--> 脚本开始执行"
|
||||
;;
|
||||
*)
|
||||
echo "你输入了 $MY_Del_Old ,自行备份老版brew和它下载的软件, 如果继续运行脚本应该输入Y或者y
|
||||
"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
#MAC
|
||||
echo "${tty_yellow} Mac os设置开机密码方法:
|
||||
(设置开机密码:在左上角苹果图标->系统偏好设置->"用户与群组"->更改密码)
|
||||
(如果提示This incident will be reported. 在"用户与群组"中查看是否管理员) ${tty_reset}"
|
||||
fi
|
||||
|
||||
echo "==> 通过命令删除之前的brew、创建一个新的Homebrew文件夹
|
||||
${tty_cyan}请输入开机密码,输入过程不显示,输入完后回车${tty_reset}"
|
||||
|
||||
sudo echo '开始执行'
|
||||
#删除以前的Homebrew
|
||||
RmCreate ${HOMEBREW_REPOSITORY}
|
||||
RmAndCopy $HOMEBREW_CACHE
|
||||
RmAndCopy $HOMEBREW_LOGS
|
||||
|
||||
# 让环境暂时纯粹,脚本运行结束后恢复
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${HOMEBREW_REPOSITORY}/bin
|
||||
fi
|
||||
git --version
|
||||
if [ $? -ne 0 ];then
|
||||
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
sudo rm -rf "/Library/Developer/CommandLineTools/"
|
||||
echo "${tty_cyan}安装Git${tty_reset}后再运行此脚本,${tty_red}在系统弹窗中点击“安装”按钮
|
||||
如果没有弹窗的老系统,需要自己下载安装:https://sourceforge.net/projects/git-osx-installer/ ${tty_reset}"
|
||||
xcode-select --install
|
||||
exit 0
|
||||
else
|
||||
echo "${tty_red} 发现缺少git,开始安装,请输入Y ${tty_reset}"
|
||||
sudo apt install git
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "
|
||||
${tty_cyan}下载速度觉得慢可以ctrl+c或control+c重新运行脚本选择下载源${tty_reset}
|
||||
==> 从 $USER_BREW_GIT 克隆Homebrew基本文件
|
||||
"
|
||||
warning_if
|
||||
sudo git clone ${GIT_SPEED} $USER_BREW_GIT ${HOMEBREW_REPOSITORY}
|
||||
JudgeSuccess 尝试再次运行自动脚本选择其他下载源或者切换网络 out
|
||||
|
||||
#依赖目录创建 授权等等
|
||||
CreateBrewLinkFolder
|
||||
|
||||
echo '==> 创建brew的替身'
|
||||
if [[ "${HOMEBREW_REPOSITORY}" != "${HOMEBREW_PREFIX}" ]]; then
|
||||
find ${HOMEBREW_PREFIX}/bin -name brew -exec sudo rm -f {} \;
|
||||
execute "ln" "-sf" "${HOMEBREW_REPOSITORY}/bin/brew" "${HOMEBREW_PREFIX}/bin/brew"
|
||||
fi
|
||||
|
||||
echo "==> 从 $USER_CORE_GIT 克隆Homebrew Core
|
||||
${tty_cyan}此处如果显示Password表示需要再次输入开机密码,输入完后回车${tty_reset}"
|
||||
sudo mkdir -p ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-core
|
||||
sudo git clone ${GIT_SPEED} $USER_CORE_GIT ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-core/
|
||||
JudgeSuccess 尝试再次运行自动脚本选择其他下载源或者切换网络 out
|
||||
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
#MAC
|
||||
echo "==> 从 $USER_CASK_GIT 克隆Homebrew Cask 图形化软件
|
||||
${tty_cyan}此处如果显示Password表示需要再次输入开机密码,输入完后回车${tty_reset}"
|
||||
sudo mkdir -p ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask
|
||||
sudo git clone ${GIT_SPEED} $USER_CASK_GIT ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask/
|
||||
if [ $? -ne 0 ];then
|
||||
sudo rm -rf ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask
|
||||
echo "${tty_red}尝试切换下载源或者切换网络,不过Cask组件非必须模块。可以忽略${tty_reset}"
|
||||
else
|
||||
echo "${tty_green}此步骤成功${tty_reset}"
|
||||
|
||||
fi
|
||||
|
||||
echo "==> 从 $USER_SERVICES_GIT 克隆Homebrew services 管理服务的启停
|
||||
"
|
||||
sudo mkdir -p ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask
|
||||
sudo git clone ${GIT_SPEED} $USER_SERVICES_GIT ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-services/
|
||||
JudgeSuccess
|
||||
else
|
||||
#Linux
|
||||
echo "${tty_yellow} Linux 不支持Cask图形化软件下载 此步骤跳过${tty_reset}"
|
||||
fi
|
||||
echo '==> 配置国内镜像源HOMEBREW BOTTLE'
|
||||
|
||||
#判断下mac os终端是Bash还是zsh
|
||||
case "$SHELL" in
|
||||
*/bash*)
|
||||
if [[ -r "$HOME/.bash_profile" ]]; then
|
||||
shell_profile="${HOME}/.bash_profile"
|
||||
else
|
||||
shell_profile="${HOME}/.profile"
|
||||
fi
|
||||
;;
|
||||
*/zsh*)
|
||||
shell_profile="${HOME}/.zprofile"
|
||||
;;
|
||||
*)
|
||||
shell_profile="${HOME}/.profile"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
#Linux
|
||||
shell_profile="/etc/profile"
|
||||
fi
|
||||
|
||||
if [[ -f ${shell_profile} ]]; then
|
||||
AddPermission ${shell_profile}
|
||||
fi
|
||||
#删除之前的环境变量
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
#Mac
|
||||
sed -i "" "/ckbrew/d" ${shell_profile}
|
||||
else
|
||||
#Linux
|
||||
sed -i "/ckbrew/d" ${shell_profile}
|
||||
fi
|
||||
|
||||
#选择一个homebrew-bottles下载源
|
||||
echo -n "${tty_green}
|
||||
|
||||
Brew本体已经安装成功,接下来配置国内源。
|
||||
|
||||
请选择今后brew install的时候访问那个国内镜像,例如阿里巴巴,输入5回车。
|
||||
|
||||
1、中科大国内源
|
||||
2、清华大学国内源
|
||||
3、北京外国语大学国内源
|
||||
4、腾讯国内源
|
||||
5、阿里巴巴国内源 ${tty_reset}"
|
||||
|
||||
echo -n "
|
||||
${tty_blue}请输入序号: "
|
||||
read MY_DOWN_NUM
|
||||
echo "${tty_reset}"
|
||||
case $MY_DOWN_NUM in
|
||||
"2")
|
||||
echo "
|
||||
你选择了清华大学国内源
|
||||
"
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/
|
||||
;;
|
||||
"3")
|
||||
echo "
|
||||
北京外国语大学国内源
|
||||
"
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.bfsu.edu.cn/homebrew-bottles
|
||||
;;
|
||||
"4")
|
||||
echo "
|
||||
你选择了腾讯国内源
|
||||
"
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.cloud.tencent.com/homebrew-bottles
|
||||
;;
|
||||
"5")
|
||||
echo "
|
||||
你选择了阿里巴巴国内源
|
||||
"
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles
|
||||
;;
|
||||
*)
|
||||
echo "
|
||||
你选择了中国科学技术大学国内源
|
||||
"
|
||||
#HomeBrew 下载源 install
|
||||
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
|
||||
;;
|
||||
esac
|
||||
|
||||
#写入环境变量到文件
|
||||
echo "
|
||||
|
||||
环境变量写入->${shell_profile}
|
||||
|
||||
"
|
||||
|
||||
echo "
|
||||
export HOMEBREW_BOTTLE_DOMAIN=${USER_HOMEBREW_BOTTLE_DOMAIN} #ckbrew
|
||||
eval \$(${HOMEBREW_REPOSITORY}/bin/brew shellenv) #ckbrew
|
||||
" >> ${shell_profile}
|
||||
JudgeSuccess
|
||||
source "${shell_profile}"
|
||||
if [ $? -ne 0 ];then
|
||||
echo "${tty_red}发现错误,${shell_profile} 文件中有错误,建议根据上一句提示修改;
|
||||
否则会导致提示 permission denied: brew${tty_reset}"
|
||||
fi
|
||||
|
||||
AddPermission ${HOMEBREW_REPOSITORY}
|
||||
|
||||
if [[ -n "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
#检测linux curl是否有安装
|
||||
echo "${tty_red}-检测curl是否安装 留意是否需要输入Y${tty_reset}"
|
||||
curl -V
|
||||
if [ $? -ne 0 ];then
|
||||
sudo apt-get install curl
|
||||
if [ $? -ne 0 ];then
|
||||
sudo yum install curl
|
||||
if [ $? -ne 0 ];then
|
||||
echo '失败 请自行安装curl 可以参考https://www.howtoing.com/install-curl-in-linux'
|
||||
error_game_over
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo '
|
||||
==> 安装完成,brew版本
|
||||
'
|
||||
brew -v
|
||||
if [ $? -ne 0 ];then
|
||||
echo '发现错误,自动修复一次!'
|
||||
rm -rf $HOMEBREW_CACHE
|
||||
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${HOMEBREW_REPOSITORY}/bin
|
||||
brew update-reset
|
||||
brew -v
|
||||
if [ $? -ne 0 ];then
|
||||
error_game_over
|
||||
fi
|
||||
else
|
||||
echo "${tty_green}Brew前期配置成功${tty_reset}"
|
||||
fi
|
||||
|
||||
#brew 3.1.2版本 修改了很多地址,都写死在了代码中,没有调用环境变量。。额。。
|
||||
#ruby下载需要改官方文件
|
||||
ruby_URL_file=$HOMEBREW_REPOSITORY/Library/Homebrew/cmd/vendor-install.sh
|
||||
|
||||
#判断Mac系统版本
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
if version_gt "$macos_version" "10.14"; then
|
||||
echo "电脑系统版本:$macos_version"
|
||||
else
|
||||
echo "${tty_red}检测到你不是最新系统,会有一些报错,请稍等Ruby下载安装;${tty_reset}
|
||||
"
|
||||
fi
|
||||
|
||||
if [[ -f ${ruby_URL_file} ]]; then
|
||||
sed -i "" "s/ruby_URL=/ruby_URL=\"https:\/\/mirrors.tuna.tsinghua.edu.cn\/homebrew-bottles\/bottles-portable-ruby\/\$ruby_FILENAME\" \#/g" $ruby_URL_file
|
||||
fi
|
||||
else
|
||||
if [[ -f ${ruby_URL_file} ]]; then
|
||||
sed -i "s/ruby_URL=/ruby_URL=\"https:\/\/mirrors.tuna.tsinghua.edu.cn\/linuxbrew-bottles\/bottles-portable-ruby\/\$ruby_FILENAME\" \#/g" $ruby_URL_file
|
||||
fi
|
||||
fi
|
||||
|
||||
brew services cleanup
|
||||
|
||||
if [[ $GIT_SPEED == "" ]];then
|
||||
echo '
|
||||
==> brew update-reset
|
||||
'
|
||||
brew update-reset
|
||||
if [[ $? -ne 0 ]];then
|
||||
brew config
|
||||
error_game_over
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
#极速模式提示Update修复方法
|
||||
echo "
|
||||
${tty_red} 极速版本安装完成,${tty_reset} install功能正常,如果需要update功能请自行运行下面三句命令
|
||||
git -C ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-core fetch --unshallow
|
||||
git -C ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask fetch --unshallow
|
||||
brew update-reset
|
||||
"
|
||||
fi
|
||||
|
||||
echo "
|
||||
${tty_green}Brew自动安装程序运行完成${tty_reset}
|
||||
${tty_green}国内地址已经配置完成${tty_reset}
|
||||
|
||||
桌面的Old_Homebrew文件夹,大致看看没有你需要的可以删除。
|
||||
|
||||
初步介绍几个brew命令
|
||||
本地软件库列表:brew ls
|
||||
查找软件:brew search google(其中google替换为要查找的关键字)
|
||||
查看brew版本:brew -v 更新brew版本:brew update
|
||||
安装cask软件:brew install --cask firefox 把firefox换成你要安装的
|
||||
${tty_green}
|
||||
欢迎右键点击下方地址-打开URL 来给个星${tty_reset}
|
||||
${tty_underline} https://github.com/TheRamU/Fay ${tty_reset}
|
||||
"
|
||||
|
||||
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
|
||||
#Mac
|
||||
echo "${tty_red} 安装成功 但还需要重启终端 或者 运行${tty_bold} source ${shell_profile} ${tty_reset} ${tty_red}否则可能无法使用${tty_reset}
|
||||
"
|
||||
else
|
||||
#Linux
|
||||
echo "${tty_red} Linux需要重启电脑 或者暂时运行${tty_bold} source ${shell_profile} ${tty_reset} ${tty_red}否则可能无法使用${tty_reset}
|
||||
"
|
||||
fi
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
[key]
|
||||
#funasr / ali
|
||||
ASR_mode = ali
|
||||
#ASR二选一(需要运行fay/test/funasr服务)集成达摩院asr项目、感谢中科大脑算法工程师张聪聪提供集成代码
|
||||
local_asr_ip=127.0.0.1
|
||||
local_asr_port=10197
|
||||
|
||||
# ASR二选一(第1次运行建议用这个,免费3个月), 阿里云 实时语音识别 服务密钥(必须)https://ai.aliyun.com/nls/trans
|
||||
ali_nls_key_id=
|
||||
ali_nls_key_secret=
|
||||
ali_nls_app_key=
|
||||
|
||||
|
||||
# 微软 文字转语音 服务密钥(非必须,使用可产生不同情绪的音频)https://azure.microsoft.com/zh-cn/services/cognitive-services/text-to-speech/
|
||||
ms_tts_key=
|
||||
ms_tts_region=
|
||||
|
||||
#情绪分析选择(xf,cemotion二选一(建议第一次运行cemotion时,先运行/test/test_cemotion.py去下载所需文件))
|
||||
ltp_mode = xf
|
||||
|
||||
# 讯飞 情绪分析 服务密钥 https://www.xfyun.cn/service/emotion-analysis/
|
||||
xf_ltp_app_id=
|
||||
xf_ltp_api_key=
|
||||
|
||||
#NLP多选一:lingju、yuan、gpt、rasa、chatglm2、VisualGLM、rwkv_api、rwkv、fastgpt
|
||||
chat_module=rwkv_api
|
||||
|
||||
#灵聚 服务密钥(NLP多选1) https://open.lingju.ai
|
||||
lingju_api_key=
|
||||
lingju_api_authcode=
|
||||
|
||||
#浪.潮源大模型 服务密钥(NLP多选1) https://air.inspur.com/
|
||||
yuan_1_0_account=
|
||||
yuan_1_0_phone=
|
||||
|
||||
#gpt 服务密钥(NLP多选1) https://openai.com/
|
||||
chatgpt_api_key=
|
||||
|
||||
#fastgpt 服务密钥(NLP多选1) https://fastgpt.run/
|
||||
fast_gpt_key=
|
||||
|
||||
#是否开启gpt(fastgpt)代理访问,比如本地有加速器,则为0
|
||||
is_proxy=1
|
||||
|
||||
#gpt(fastgpt)代理(可为空,填写例子:127.0.0.1:7890)
|
||||
proxy_config=127.0.0.1:7890
|
||||
|
||||
#ngrok内网穿透id,远程设备可以通过互联网连接Fay(非必须)http://ngrok.cc
|
||||
ngrok_cc_id=
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
import winreg
|
||||
|
||||
#关闭系统代理
|
||||
def disable_windows_proxy():
|
||||
settings_key = r'Software\Microsoft\Windows\CurrentVersion\Internet Settings'
|
||||
try:
|
||||
registry = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
|
||||
settings = winreg.OpenKey(registry, settings_key, 0, winreg.KEY_WRITE)
|
||||
|
||||
# 设置代理启用值为0(禁用)
|
||||
winreg.SetValueEx(settings, 'ProxyEnable', 0, winreg.REG_DWORD, 0)
|
||||
|
||||
# 清空代理服务器和代理覆盖设置
|
||||
winreg.SetValueEx(settings, 'ProxyServer', 0, winreg.REG_SZ, '')
|
||||
winreg.SetValueEx(settings, 'ProxyOverride', 0, winreg.REG_SZ, '')
|
||||
|
||||
winreg.CloseKey(settings)
|
||||
winreg.CloseKey(registry)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
disable_windows_proxy()
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import pyaudio
|
||||
import websockets
|
||||
import asyncio
|
||||
from queue import Queue
|
||||
import argparse
|
||||
import json
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--host", type=str, default="172.16.77.144", required=False, help="host ip, localhost, 0.0.0.0")
|
||||
parser.add_argument("--port", type=int, default=10194, required=False, help="grpc server port")
|
||||
parser.add_argument("--chunk_size", type=int, default=160, help="ms")
|
||||
parser.add_argument("--vad_needed", type=bool, default=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
voices = Queue()
|
||||
|
||||
async def record():
|
||||
global voices
|
||||
FORMAT = pyaudio.paInt16
|
||||
CHANNELS = 1
|
||||
RATE = 16000
|
||||
CHUNK = int(RATE / 1000 * args.chunk_size)
|
||||
|
||||
p = pyaudio.PyAudio()
|
||||
|
||||
stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)
|
||||
|
||||
while True:
|
||||
data = stream.read(CHUNK)
|
||||
voices.put(data)
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
async def ws_send(websocket):
|
||||
global voices
|
||||
print("Started sending data!")
|
||||
data_head = {
|
||||
'vad_need': args.vad_needed,
|
||||
'state': ''
|
||||
}
|
||||
await websocket.send(json.dumps(data_head))
|
||||
|
||||
while True:
|
||||
while not voices.empty():
|
||||
data = voices.get()
|
||||
voices.task_done()
|
||||
try:
|
||||
await websocket.send(data)
|
||||
except Exception as e:
|
||||
print('Exception occurred:', e)
|
||||
return # Return to attempt reconnection
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
async def message(websocket):
|
||||
while True:
|
||||
try:
|
||||
print(await websocket.recv())
|
||||
except Exception as e:
|
||||
print("Exception:", e)
|
||||
return # Return to attempt reconnection
|
||||
|
||||
async def ws_client():
|
||||
uri = "ws://{}:{}".format(args.host, args.port)
|
||||
while True:
|
||||
try:
|
||||
async with websockets.connect(uri, subprotocols=["binary"], ping_interval=None) as websocket:
|
||||
task1 = asyncio.create_task(record())
|
||||
task2 = asyncio.create_task(ws_send(websocket))
|
||||
task3 = asyncio.create_task(message(websocket))
|
||||
await asyncio.gather(task1, task2, task3)
|
||||
except Exception as e:
|
||||
print("WebSocket connection failed: ", e)
|
||||
await asyncio.sleep(5) # Wait for 5 seconds before trying to reconnect
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(ws_client())
|
||||
@@ -1,212 +0,0 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
import time
|
||||
from queue import Queue
|
||||
import threading
|
||||
import argparse
|
||||
import json
|
||||
from modelscope.pipelines import pipeline
|
||||
from modelscope.utils.constant import Tasks
|
||||
from modelscope.utils.logger import get_logger
|
||||
import logging
|
||||
import tracemalloc
|
||||
import functools
|
||||
tracemalloc.start()
|
||||
|
||||
logger = get_logger(log_level=logging.CRITICAL)
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
|
||||
|
||||
websocket_users = set() #维护客户端列表
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--host",
|
||||
type=str,
|
||||
default="0.0.0.0",
|
||||
required=False,
|
||||
help="host ip, localhost, 0.0.0.0")
|
||||
parser.add_argument("--port",
|
||||
type=int,
|
||||
default=10197,
|
||||
required=False,
|
||||
help="grpc server port")
|
||||
parser.add_argument("--model",
|
||||
type=str,
|
||||
default="./data/speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404",
|
||||
help="model from modelscope")
|
||||
parser.add_argument("--vad_model",
|
||||
type=str,
|
||||
default="damo/speech_fsmn_vad_zh-cn-16k-common-pytorch",
|
||||
help="model from modelscope")
|
||||
parser.add_argument("--punc_model",
|
||||
type=str,
|
||||
default="",
|
||||
help="model from modelscope")
|
||||
parser.add_argument("--ngpu",
|
||||
type=int,
|
||||
default=1,
|
||||
help="0 for cpu, 1 for gpu")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("model loading")
|
||||
# asr
|
||||
param_dict_asr = {}
|
||||
param_dict_asr['hotword']="data/hotword.txt"
|
||||
inference_pipeline_asr = pipeline(
|
||||
task=Tasks.auto_speech_recognition,
|
||||
model=args.model,
|
||||
param_dict=param_dict_asr,
|
||||
ngpu=args.ngpu
|
||||
)
|
||||
if args.punc_model != "":
|
||||
# param_dict_punc = {'cache': list()}
|
||||
inference_pipeline_punc = pipeline(
|
||||
task=Tasks.punctuation,
|
||||
model=args.punc_model,
|
||||
model_revision=None,
|
||||
ngpu=args.ngpu,
|
||||
)
|
||||
else:
|
||||
inference_pipeline_punc = None
|
||||
|
||||
|
||||
# vad
|
||||
inference_pipeline_vad = pipeline(
|
||||
task=Tasks.voice_activity_detection,
|
||||
model=args.vad_model,
|
||||
model_revision='v1.2.0',
|
||||
output_dir=None,
|
||||
batch_size=1,
|
||||
mode='online',
|
||||
ngpu=args.ngpu,
|
||||
)
|
||||
print("model loaded")
|
||||
|
||||
|
||||
def vad(data, websocket): # VAD推理
|
||||
global inference_pipeline_vad
|
||||
segments_result = inference_pipeline_vad(audio_in=data, param_dict=websocket.param_dict_vad)
|
||||
speech_start = False
|
||||
speech_end = False
|
||||
|
||||
if len(segments_result) == 0 or len(segments_result["text"]) > 1:
|
||||
return speech_start, speech_end
|
||||
if segments_result["text"][0][0] != -1:
|
||||
speech_start = True
|
||||
if segments_result["text"][0][1] != -1:
|
||||
speech_end = True
|
||||
return speech_start, speech_end
|
||||
|
||||
|
||||
async def ws_serve(websocket,path):
|
||||
|
||||
frames = [] # 存储所有的帧数据
|
||||
buffer = [] # 存储缓存中的帧数据(最多两个片段)
|
||||
RECORD_NUM = 0
|
||||
global websocket_users
|
||||
speech_start, speech_end = False, False
|
||||
# 调用asr函数
|
||||
websocket.param_dict_vad = {'in_cache': dict(), "is_final": False}
|
||||
websocket.param_dict_punc = {'cache': list()}
|
||||
websocket.speek = Queue() # websocket 添加进队列对象 让asr读取语音数据包
|
||||
websocket.send_msg = Queue() # websocket 添加个队列对象 让ws发送消息到客户端
|
||||
websocket_users.add(websocket)
|
||||
ss = threading.Thread(target=asr, args=(websocket,))
|
||||
ss.start()
|
||||
try:
|
||||
async for message in websocket:
|
||||
if (type(message) == str):
|
||||
dict_message = json.loads(message)
|
||||
if dict_message['vad_need'] == True:
|
||||
vad_method = True
|
||||
else:
|
||||
vad_method = False
|
||||
if vad_method == True:
|
||||
if type(message) != str:
|
||||
buffer.append(message)
|
||||
if len(buffer) > 2:
|
||||
buffer.pop(0) # 如果缓存超过两个片段,则删除最早的一个
|
||||
|
||||
if speech_start:
|
||||
frames.append(message)
|
||||
RECORD_NUM += 1
|
||||
if type(message) != str:
|
||||
speech_start_i, speech_end_i = vad(message, websocket)
|
||||
# print(speech_start_i, speech_end_i)
|
||||
if speech_start_i:
|
||||
speech_start = speech_start_i
|
||||
frames = []
|
||||
frames.extend(buffer) # 把之前2个语音数据快加入
|
||||
if speech_end_i or RECORD_NUM > 300:
|
||||
speech_start = False
|
||||
audio_in = b"".join(frames)
|
||||
websocket.speek.put(audio_in)
|
||||
frames = [] # 清空所有的帧数据
|
||||
buffer = [] # 清空缓存中的帧数据(最多两个片段)
|
||||
RECORD_NUM = 0
|
||||
if not websocket.send_msg.empty():
|
||||
await websocket.send(websocket.send_msg.get())
|
||||
websocket.send_msg.task_done()
|
||||
else:
|
||||
if speech_start :
|
||||
frames.append(message)
|
||||
RECORD_NUM += 1
|
||||
if (type(message) == str):
|
||||
dict_message = json.loads(message)
|
||||
if dict_message['vad_need'] == False and dict_message['state'] == 'StartTranscription':
|
||||
speech_start = True
|
||||
elif dict_message['vad_need'] == False and dict_message['state'] == 'StopTranscription':
|
||||
speech_start = False
|
||||
speech_end = True
|
||||
if len(frames) != 0:
|
||||
frames.pop()
|
||||
if speech_end or RECORD_NUM > 1024:
|
||||
speech_start = False
|
||||
speech_end = False
|
||||
audio_in = b"".join(frames)
|
||||
websocket.speek.put(audio_in)
|
||||
frames = [] # 清空所有的帧数据
|
||||
RECORD_NUM = 0
|
||||
await websocket.send(websocket.send_msg.get())
|
||||
websocket.send_msg.task_done()
|
||||
except websockets.ConnectionClosed:
|
||||
print("ConnectionClosed...", websocket_users) # 链接断开
|
||||
cleanup(websocket)
|
||||
except websockets.InvalidState:
|
||||
print("InvalidState...") # 无效状态
|
||||
except Exception as e:
|
||||
print("Exception:", e)
|
||||
cleanup(websocket)
|
||||
|
||||
|
||||
def cleanup(websocket):
|
||||
if websocket in websocket_users:
|
||||
websocket_users.remove(websocket)
|
||||
print(f"Cleaned up resources for websocket: {websocket}")
|
||||
|
||||
|
||||
def asr(websocket): # ASR推理
|
||||
global inference_pipeline_asr, inference_pipeline_punc
|
||||
# global param_dict_punc
|
||||
global websocket_users
|
||||
while websocket in websocket_users:
|
||||
try:
|
||||
# if not websocket.speek.empty():
|
||||
audio_in = websocket.speek.get()
|
||||
websocket.speek.task_done()
|
||||
if len(audio_in) > 0:
|
||||
rec_result = inference_pipeline_asr(audio_in=audio_in)
|
||||
if "text" in rec_result:
|
||||
websocket.send_msg.put(rec_result["text"]) # 存入发送队列 直接调用send发送不了
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
print(f"ASR processing error: {e}")
|
||||
cleanup(websocket)
|
||||
break
|
||||
|
||||
|
||||
|
||||
start_server = websockets.serve(ws_serve, args.host, args.port, subprotocols=["binary"], ping_interval=None)
|
||||
asyncio.get_event_loop().run_until_complete(start_server)
|
||||
asyncio.get_event_loop().run_forever()
|
||||
@@ -1,36 +0,0 @@
|
||||
## 语音服务介绍
|
||||
|
||||
该服务以modelscope funasr语音识别为基础
|
||||
|
||||
|
||||
## Install
|
||||
pip install torch
|
||||
pip install modelscope==1.11.0
|
||||
pip install testresources
|
||||
pip install websockets
|
||||
pip install torchaudio
|
||||
下载 https://github.com/alibaba-damo-academy/FunASR/tree/v0.8.8
|
||||
解压
|
||||
pip install ./FunASR-0.8.8(若editdistance编译不通过,请手动安装 pip install editdistance,在FunASR/setup.py也注释掉,再执行)
|
||||
|
||||
|
||||
## Start server
|
||||
1、从百度网盘下载并解压模型文件到fay/test/funasr/data目录
|
||||
链接:https://pan.baidu.com/s/17SJqWIo9zeGAZxPCMIsHJA?pwd=5fzr
|
||||
提取码:5fzr
|
||||
|
||||
2、python -u ASR_server.py --host "0.0.0.0" --port 10197 --ngpu 0 --model ./data/speech_paraformer-large-contextual_asr_nat-zh-cn-16k-common-vocab8404
|
||||
|
||||
## Fay connect
|
||||
更改fay/system.conf配置项,并重新启动fay.
|
||||
|
||||
https://www.bilibili.com/video/BV1qs4y1g74e/?share_source=copy_web&vd_source=64cd9062f5046acba398177b62bea9ad
|
||||
|
||||
|
||||
## Acknowledge
|
||||
感谢
|
||||
1. 中科大脑算法工程师张聪聪
|
||||
2. [cgisky1980](https://github.com/cgisky1980/FunASR)
|
||||
3. [modelscope](https://github.com/modelscope/modelscope)
|
||||
4. [FunASR](https://github.com/alibaba-damo-academy/FunASR)
|
||||
5. [Fay数字人助理](https://github.com/TheRamU/Fay).
|
||||
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
@@ -1,747 +0,0 @@
|
||||
FFmpeg 64-bit static Windows build from www.gyan.dev
|
||||
|
||||
Version: 2023-05-31-git-baa9fccf8d-essentials_build-www.gyan.dev
|
||||
|
||||
License: GPL v3
|
||||
|
||||
Source Code: https://github.com/FFmpeg/FFmpeg/commit/baa9fccf8d
|
||||
|
||||
git-essentials build configuration:
|
||||
|
||||
ARCH x86 (generic)
|
||||
big-endian no
|
||||
runtime cpu detection yes
|
||||
standalone assembly yes
|
||||
x86 assembler nasm
|
||||
MMX enabled yes
|
||||
MMXEXT enabled yes
|
||||
3DNow! enabled yes
|
||||
3DNow! extended enabled yes
|
||||
SSE enabled yes
|
||||
SSSE3 enabled yes
|
||||
AESNI enabled yes
|
||||
AVX enabled yes
|
||||
AVX2 enabled yes
|
||||
AVX-512 enabled yes
|
||||
AVX-512ICL enabled yes
|
||||
XOP enabled yes
|
||||
FMA3 enabled yes
|
||||
FMA4 enabled yes
|
||||
i686 features enabled yes
|
||||
CMOV is fast yes
|
||||
EBX available yes
|
||||
EBP available yes
|
||||
debug symbols yes
|
||||
strip symbols yes
|
||||
optimize for size no
|
||||
optimizations yes
|
||||
static yes
|
||||
shared no
|
||||
postprocessing support yes
|
||||
network support yes
|
||||
threading support pthreads
|
||||
safe bitstream reader yes
|
||||
texi2html enabled no
|
||||
perl enabled yes
|
||||
pod2man enabled yes
|
||||
makeinfo enabled yes
|
||||
makeinfo supports HTML yes
|
||||
xmllint enabled yes
|
||||
|
||||
External libraries:
|
||||
avisynth libopencore_amrnb libvorbis
|
||||
bzlib libopencore_amrwb libvpx
|
||||
gmp libopenjpeg libwebp
|
||||
gnutls libopenmpt libx264
|
||||
iconv libopus libx265
|
||||
libaom librubberband libxml2
|
||||
libass libspeex libxvid
|
||||
libfontconfig libsrt libzimg
|
||||
libfreetype libssh libzmq
|
||||
libfribidi libtheora lzma
|
||||
libgme libvidstab mediafoundation
|
||||
libgsm libvmaf sdl2
|
||||
libmp3lame libvo_amrwbenc zlib
|
||||
|
||||
External libraries providing hardware acceleration:
|
||||
amf d3d11va libvpl
|
||||
cuda dxva2 nvdec
|
||||
cuda_llvm ffnvcodec nvenc
|
||||
cuvid libmfx
|
||||
|
||||
Libraries:
|
||||
avcodec avformat swresample
|
||||
avdevice avutil swscale
|
||||
avfilter postproc
|
||||
|
||||
Programs:
|
||||
ffmpeg ffplay ffprobe
|
||||
|
||||
Enabled decoders:
|
||||
aac flv pcx
|
||||
aac_fixed fmvc pdv
|
||||
aac_latm fourxm pfm
|
||||
aasc fraps pgm
|
||||
ac3 frwu pgmyuv
|
||||
ac3_fixed ftr pgssub
|
||||
acelp_kelvin g2m pgx
|
||||
adpcm_4xm g723_1 phm
|
||||
adpcm_adx g729 photocd
|
||||
adpcm_afc gdv pictor
|
||||
adpcm_agm gem pixlet
|
||||
adpcm_aica gif pjs
|
||||
adpcm_argo gremlin_dpcm png
|
||||
adpcm_ct gsm ppm
|
||||
adpcm_dtk gsm_ms prores
|
||||
adpcm_ea h261 prosumer
|
||||
adpcm_ea_maxis_xa h263 psd
|
||||
adpcm_ea_r1 h263i ptx
|
||||
adpcm_ea_r2 h263p qcelp
|
||||
adpcm_ea_r3 h264 qdm2
|
||||
adpcm_ea_xas h264_cuvid qdmc
|
||||
adpcm_g722 h264_qsv qdraw
|
||||
adpcm_g726 hap qoi
|
||||
adpcm_g726le hca qpeg
|
||||
adpcm_ima_acorn hcom qtrle
|
||||
adpcm_ima_alp hdr r10k
|
||||
adpcm_ima_amv hevc r210
|
||||
adpcm_ima_apc hevc_cuvid ra_144
|
||||
adpcm_ima_apm hevc_qsv ra_288
|
||||
adpcm_ima_cunning hnm4_video ralf
|
||||
adpcm_ima_dat4 hq_hqa rasc
|
||||
adpcm_ima_dk3 hqx rawvideo
|
||||
adpcm_ima_dk4 huffyuv realtext
|
||||
adpcm_ima_ea_eacs hymt rka
|
||||
adpcm_ima_ea_sead iac rl2
|
||||
adpcm_ima_iss idcin roq
|
||||
adpcm_ima_moflex idf roq_dpcm
|
||||
adpcm_ima_mtf iff_ilbm rpza
|
||||
adpcm_ima_oki ilbc rscc
|
||||
adpcm_ima_qt imc rv10
|
||||
adpcm_ima_rad imm4 rv20
|
||||
adpcm_ima_smjpeg imm5 rv30
|
||||
adpcm_ima_ssi indeo2 rv40
|
||||
adpcm_ima_wav indeo3 s302m
|
||||
adpcm_ima_ws indeo4 sami
|
||||
adpcm_ms indeo5 sanm
|
||||
adpcm_mtaf interplay_acm sbc
|
||||
adpcm_psx interplay_dpcm scpr
|
||||
adpcm_sbpro_2 interplay_video screenpresso
|
||||
adpcm_sbpro_3 ipu sdx2_dpcm
|
||||
adpcm_sbpro_4 jacosub sga
|
||||
adpcm_swf jpeg2000 sgi
|
||||
adpcm_thp jpegls sgirle
|
||||
adpcm_thp_le jv sheervideo
|
||||
adpcm_vima kgv1 shorten
|
||||
adpcm_xa kmvc simbiosis_imx
|
||||
adpcm_xmd lagarith sipr
|
||||
adpcm_yamaha libaom_av1 siren
|
||||
adpcm_zork libgsm smackaud
|
||||
agm libgsm_ms smacker
|
||||
aic libopencore_amrnb smc
|
||||
alac libopencore_amrwb smvjpeg
|
||||
alias_pix libopus snow
|
||||
als libspeex sol_dpcm
|
||||
amrnb libvorbis sonic
|
||||
amrwb libvpx_vp8 sp5x
|
||||
amv libvpx_vp9 speedhq
|
||||
anm loco speex
|
||||
ansi lscr srgc
|
||||
anull m101 srt
|
||||
apac mace3 ssa
|
||||
ape mace6 stl
|
||||
apng magicyuv subrip
|
||||
aptx mdec subviewer
|
||||
aptx_hd media100 subviewer1
|
||||
arbc metasound sunrast
|
||||
argo microdvd svq1
|
||||
ass mimic svq3
|
||||
asv1 misc4 tak
|
||||
asv2 mjpeg targa
|
||||
atrac1 mjpeg_cuvid targa_y216
|
||||
atrac3 mjpeg_qsv tdsc
|
||||
atrac3al mjpegb text
|
||||
atrac3p mlp theora
|
||||
atrac3pal mmvideo thp
|
||||
atrac9 mobiclip tiertexseqvideo
|
||||
aura motionpixels tiff
|
||||
aura2 movtext tmv
|
||||
av1 mp1 truehd
|
||||
av1_cuvid mp1float truemotion1
|
||||
av1_qsv mp2 truemotion2
|
||||
avrn mp2float truemotion2rt
|
||||
avrp mp3 truespeech
|
||||
avs mp3adu tscc
|
||||
avui mp3adufloat tscc2
|
||||
ayuv mp3float tta
|
||||
bethsoftvid mp3on4 twinvq
|
||||
bfi mp3on4float txd
|
||||
bink mpc7 ulti
|
||||
binkaudio_dct mpc8 utvideo
|
||||
binkaudio_rdft mpeg1_cuvid v210
|
||||
bintext mpeg1video v210x
|
||||
bitpacked mpeg2_cuvid v308
|
||||
bmp mpeg2_qsv v408
|
||||
bmv_audio mpeg2video v410
|
||||
bmv_video mpeg4 vb
|
||||
bonk mpeg4_cuvid vble
|
||||
brender_pix mpegvideo vbn
|
||||
c93 mpl2 vc1
|
||||
cavs msa1 vc1_cuvid
|
||||
cbd2_dpcm mscc vc1_qsv
|
||||
ccaption msmpeg4v1 vc1image
|
||||
cdgraphics msmpeg4v2 vcr1
|
||||
cdtoons msmpeg4v3 vmdaudio
|
||||
cdxl msnsiren vmdvideo
|
||||
cfhd msp2 vmnc
|
||||
cinepak msrle vnull
|
||||
clearvideo mss1 vorbis
|
||||
cljr mss2 vp3
|
||||
cllc msvideo1 vp4
|
||||
comfortnoise mszh vp5
|
||||
cook mts2 vp6
|
||||
cpia mv30 vp6a
|
||||
cri mvc1 vp6f
|
||||
cscd mvc2 vp7
|
||||
cyuv mvdv vp8
|
||||
dca mvha vp8_cuvid
|
||||
dds mwsc vp8_qsv
|
||||
derf_dpcm mxpeg vp9
|
||||
dfa nellymoser vp9_cuvid
|
||||
dfpwm notchlc vp9_qsv
|
||||
dirac nuv vplayer
|
||||
dnxhd on2avc vqa
|
||||
dolby_e opus vqc
|
||||
dpx paf_audio wady_dpcm
|
||||
dsd_lsbf paf_video wavarc
|
||||
dsd_lsbf_planar pam wavpack
|
||||
dsd_msbf pbm wbmp
|
||||
dsd_msbf_planar pcm_alaw wcmv
|
||||
dsicinaudio pcm_bluray webp
|
||||
dsicinvideo pcm_dvd webvtt
|
||||
dss_sp pcm_f16le wmalossless
|
||||
dst pcm_f24le wmapro
|
||||
dvaudio pcm_f32be wmav1
|
||||
dvbsub pcm_f32le wmav2
|
||||
dvdsub pcm_f64be wmavoice
|
||||
dvvideo pcm_f64le wmv1
|
||||
dxa pcm_lxf wmv2
|
||||
dxtory pcm_mulaw wmv3
|
||||
dxv pcm_s16be wmv3image
|
||||
eac3 pcm_s16be_planar wnv1
|
||||
eacmv pcm_s16le wrapped_avframe
|
||||
eamad pcm_s16le_planar ws_snd1
|
||||
eatgq pcm_s24be xan_dpcm
|
||||
eatgv pcm_s24daud xan_wc3
|
||||
eatqi pcm_s24le xan_wc4
|
||||
eightbps pcm_s24le_planar xbin
|
||||
eightsvx_exp pcm_s32be xbm
|
||||
eightsvx_fib pcm_s32le xface
|
||||
escape124 pcm_s32le_planar xl
|
||||
escape130 pcm_s64be xma1
|
||||
evrc pcm_s64le xma2
|
||||
exr pcm_s8 xpm
|
||||
fastaudio pcm_s8_planar xsub
|
||||
ffv1 pcm_sga xwd
|
||||
ffvhuff pcm_u16be y41p
|
||||
ffwavesynth pcm_u16le ylc
|
||||
fic pcm_u24be yop
|
||||
fits pcm_u24le yuv4
|
||||
flac pcm_u32be zero12v
|
||||
flashsv pcm_u32le zerocodec
|
||||
flashsv2 pcm_u8 zlib
|
||||
flic pcm_vidc zmbv
|
||||
|
||||
Enabled encoders:
|
||||
a64multi hevc_mf pcm_u24le
|
||||
a64multi5 hevc_nvenc pcm_u32be
|
||||
aac hevc_qsv pcm_u32le
|
||||
aac_mf huffyuv pcm_u8
|
||||
ac3 jpeg2000 pcm_vidc
|
||||
ac3_fixed jpegls pcx
|
||||
ac3_mf libaom_av1 pfm
|
||||
adpcm_adx libgsm pgm
|
||||
adpcm_argo libgsm_ms pgmyuv
|
||||
adpcm_g722 libmp3lame phm
|
||||
adpcm_g726 libopencore_amrnb png
|
||||
adpcm_g726le libopenjpeg ppm
|
||||
adpcm_ima_alp libopus prores
|
||||
adpcm_ima_amv libspeex prores_aw
|
||||
adpcm_ima_apm libtheora prores_ks
|
||||
adpcm_ima_qt libvo_amrwbenc qoi
|
||||
adpcm_ima_ssi libvorbis qtrle
|
||||
adpcm_ima_wav libvpx_vp8 r10k
|
||||
adpcm_ima_ws libvpx_vp9 r210
|
||||
adpcm_ms libwebp ra_144
|
||||
adpcm_swf libwebp_anim rawvideo
|
||||
adpcm_yamaha libx264 roq
|
||||
alac libx264rgb roq_dpcm
|
||||
alias_pix libx265 rpza
|
||||
amv libxvid rv10
|
||||
anull ljpeg rv20
|
||||
apng magicyuv s302m
|
||||
aptx mjpeg sbc
|
||||
aptx_hd mjpeg_qsv sgi
|
||||
ass mlp smc
|
||||
asv1 movtext snow
|
||||
asv2 mp2 sonic
|
||||
av1_amf mp2fixed sonic_ls
|
||||
av1_nvenc mp3_mf speedhq
|
||||
av1_qsv mpeg1video srt
|
||||
avrp mpeg2_qsv ssa
|
||||
avui mpeg2video subrip
|
||||
ayuv mpeg4 sunrast
|
||||
bitpacked msmpeg4v2 svq1
|
||||
bmp msmpeg4v3 targa
|
||||
cfhd msvideo1 text
|
||||
cinepak nellymoser tiff
|
||||
cljr opus truehd
|
||||
comfortnoise pam tta
|
||||
dca pbm ttml
|
||||
dfpwm pcm_alaw utvideo
|
||||
dnxhd pcm_bluray v210
|
||||
dpx pcm_dvd v308
|
||||
dvbsub pcm_f32be v408
|
||||
dvdsub pcm_f32le v410
|
||||
dvvideo pcm_f64be vbn
|
||||
eac3 pcm_f64le vc2
|
||||
exr pcm_mulaw vnull
|
||||
ffv1 pcm_s16be vorbis
|
||||
ffvhuff pcm_s16be_planar vp9_qsv
|
||||
fits pcm_s16le wavpack
|
||||
flac pcm_s16le_planar wbmp
|
||||
flashsv pcm_s24be webvtt
|
||||
flashsv2 pcm_s24daud wmav1
|
||||
flv pcm_s24le wmav2
|
||||
g723_1 pcm_s24le_planar wmv1
|
||||
gif pcm_s32be wmv2
|
||||
h261 pcm_s32le wrapped_avframe
|
||||
h263 pcm_s32le_planar xbm
|
||||
h263p pcm_s64be xface
|
||||
h264_amf pcm_s64le xsub
|
||||
h264_mf pcm_s8 xwd
|
||||
h264_nvenc pcm_s8_planar y41p
|
||||
h264_qsv pcm_u16be yuv4
|
||||
hdr pcm_u16le zlib
|
||||
hevc_amf pcm_u24be zmbv
|
||||
|
||||
Enabled hwaccels:
|
||||
av1_d3d11va hevc_nvdec vc1_nvdec
|
||||
av1_d3d11va2 mjpeg_nvdec vp8_nvdec
|
||||
av1_dxva2 mpeg1_nvdec vp9_d3d11va
|
||||
av1_nvdec mpeg2_d3d11va vp9_d3d11va2
|
||||
h264_d3d11va mpeg2_d3d11va2 vp9_dxva2
|
||||
h264_d3d11va2 mpeg2_dxva2 vp9_nvdec
|
||||
h264_dxva2 mpeg2_nvdec wmv3_d3d11va
|
||||
h264_nvdec mpeg4_nvdec wmv3_d3d11va2
|
||||
hevc_d3d11va vc1_d3d11va wmv3_dxva2
|
||||
hevc_d3d11va2 vc1_d3d11va2 wmv3_nvdec
|
||||
hevc_dxva2 vc1_dxva2
|
||||
|
||||
Enabled parsers:
|
||||
aac dvdsub opus
|
||||
aac_latm flac png
|
||||
ac3 ftr pnm
|
||||
adx g723_1 qoi
|
||||
amr g729 rv30
|
||||
av1 gif rv40
|
||||
avs2 gsm sbc
|
||||
avs3 h261 sipr
|
||||
bmp h263 tak
|
||||
cavsvideo h264 vc1
|
||||
cook hdr vorbis
|
||||
cri hevc vp3
|
||||
dca ipu vp8
|
||||
dirac jpeg2000 vp9
|
||||
dnxhd misc4 webp
|
||||
dolby_e mjpeg xbm
|
||||
dpx mlp xma
|
||||
dvaudio mpeg4video xwd
|
||||
dvbsub mpegaudio
|
||||
dvd_nav mpegvideo
|
||||
|
||||
Enabled demuxers:
|
||||
aa idf pcm_mulaw
|
||||
aac iff pcm_s16be
|
||||
aax ifv pcm_s16le
|
||||
ac3 ilbc pcm_s24be
|
||||
ace image2 pcm_s24le
|
||||
acm image2_alias_pix pcm_s32be
|
||||
act image2_brender_pix pcm_s32le
|
||||
adf image2pipe pcm_s8
|
||||
adp image_bmp_pipe pcm_u16be
|
||||
ads image_cri_pipe pcm_u16le
|
||||
adx image_dds_pipe pcm_u24be
|
||||
aea image_dpx_pipe pcm_u24le
|
||||
afc image_exr_pipe pcm_u32be
|
||||
aiff image_gem_pipe pcm_u32le
|
||||
aix image_gif_pipe pcm_u8
|
||||
alp image_hdr_pipe pcm_vidc
|
||||
amr image_j2k_pipe pdv
|
||||
amrnb image_jpeg_pipe pjs
|
||||
amrwb image_jpegls_pipe pmp
|
||||
anm image_jpegxl_pipe pp_bnk
|
||||
apac image_pam_pipe pva
|
||||
apc image_pbm_pipe pvf
|
||||
ape image_pcx_pipe qcp
|
||||
apm image_pfm_pipe r3d
|
||||
apng image_pgm_pipe rawvideo
|
||||
aptx image_pgmyuv_pipe realtext
|
||||
aptx_hd image_pgx_pipe redspark
|
||||
aqtitle image_phm_pipe rka
|
||||
argo_asf image_photocd_pipe rl2
|
||||
argo_brp image_pictor_pipe rm
|
||||
argo_cvg image_png_pipe roq
|
||||
asf image_ppm_pipe rpl
|
||||
asf_o image_psd_pipe rsd
|
||||
ass image_qdraw_pipe rso
|
||||
ast image_qoi_pipe rtp
|
||||
au image_sgi_pipe rtsp
|
||||
av1 image_sunrast_pipe s337m
|
||||
avi image_svg_pipe sami
|
||||
avisynth image_tiff_pipe sap
|
||||
avr image_vbn_pipe sbc
|
||||
avs image_webp_pipe sbg
|
||||
avs2 image_xbm_pipe scc
|
||||
avs3 image_xpm_pipe scd
|
||||
bethsoftvid image_xwd_pipe sdns
|
||||
bfi imf sdp
|
||||
bfstm ingenient sdr2
|
||||
bink ipmovie sds
|
||||
binka ipu sdx
|
||||
bintext ircam segafilm
|
||||
bit iss ser
|
||||
bitpacked iv8 sga
|
||||
bmv ivf shorten
|
||||
boa ivr siff
|
||||
bonk jacosub simbiosis_imx
|
||||
brstm jv sln
|
||||
c93 kux smacker
|
||||
caf kvag smjpeg
|
||||
cavsvideo laf smush
|
||||
cdg libgme sol
|
||||
cdxl libopenmpt sox
|
||||
cine live_flv spdif
|
||||
codec2 lmlm4 srt
|
||||
codec2raw loas stl
|
||||
concat lrc str
|
||||
dash luodat subviewer
|
||||
data lvf subviewer1
|
||||
daud lxf sup
|
||||
dcstr m4v svag
|
||||
derf matroska svs
|
||||
dfa mca swf
|
||||
dfpwm mcc tak
|
||||
dhav mgsts tedcaptions
|
||||
dirac microdvd thp
|
||||
dnxhd mjpeg threedostr
|
||||
dsf mjpeg_2000 tiertexseq
|
||||
dsicin mlp tmv
|
||||
dss mlv truehd
|
||||
dts mm tta
|
||||
dtshd mmf tty
|
||||
dv mods txd
|
||||
dvbsub moflex ty
|
||||
dvbtxt mov v210
|
||||
dxa mp3 v210x
|
||||
ea mpc vag
|
||||
ea_cdata mpc8 vc1
|
||||
eac3 mpegps vc1t
|
||||
epaf mpegts vividas
|
||||
ffmetadata mpegtsraw vivo
|
||||
filmstrip mpegvideo vmd
|
||||
fits mpjpeg vobsub
|
||||
flac mpl2 voc
|
||||
flic mpsub vpk
|
||||
flv msf vplayer
|
||||
fourxm msnwc_tcp vqf
|
||||
frm msp w64
|
||||
fsb mtaf wady
|
||||
fwse mtv wav
|
||||
g722 musx wavarc
|
||||
g723_1 mv wc3
|
||||
g726 mvi webm_dash_manifest
|
||||
g726le mxf webvtt
|
||||
g729 mxg wsaud
|
||||
gdv nc wsd
|
||||
genh nistsphere wsvqa
|
||||
gif nsp wtv
|
||||
gsm nsv wv
|
||||
gxf nut wve
|
||||
h261 nuv xa
|
||||
h263 obu xbin
|
||||
h264 ogg xmd
|
||||
hca oma xmv
|
||||
hcom paf xvag
|
||||
hevc pcm_alaw xwma
|
||||
hls pcm_f32be yop
|
||||
hnm pcm_f32le yuv4mpegpipe
|
||||
ico pcm_f64be
|
||||
idcin pcm_f64le
|
||||
|
||||
Enabled muxers:
|
||||
a64 h263 pcm_s16le
|
||||
ac3 h264 pcm_s24be
|
||||
adts hash pcm_s24le
|
||||
adx hds pcm_s32be
|
||||
aiff hevc pcm_s32le
|
||||
alp hls pcm_s8
|
||||
amr ico pcm_u16be
|
||||
amv ilbc pcm_u16le
|
||||
apm image2 pcm_u24be
|
||||
apng image2pipe pcm_u24le
|
||||
aptx ipod pcm_u32be
|
||||
aptx_hd ircam pcm_u32le
|
||||
argo_asf ismv pcm_u8
|
||||
argo_cvg ivf pcm_vidc
|
||||
asf jacosub psp
|
||||
asf_stream kvag rawvideo
|
||||
ass latm rm
|
||||
ast lrc roq
|
||||
au m4v rso
|
||||
avi matroska rtp
|
||||
avif matroska_audio rtp_mpegts
|
||||
avm2 md5 rtsp
|
||||
avs2 microdvd sap
|
||||
avs3 mjpeg sbc
|
||||
bit mkvtimestamp_v2 scc
|
||||
caf mlp segafilm
|
||||
cavsvideo mmf segment
|
||||
codec2 mov smjpeg
|
||||
codec2raw mp2 smoothstreaming
|
||||
crc mp3 sox
|
||||
dash mp4 spdif
|
||||
data mpeg1system spx
|
||||
daud mpeg1vcd srt
|
||||
dfpwm mpeg1video stream_segment
|
||||
dirac mpeg2dvd streamhash
|
||||
dnxhd mpeg2svcd sup
|
||||
dts mpeg2video swf
|
||||
dv mpeg2vob tee
|
||||
eac3 mpegts tg2
|
||||
f4v mpjpeg tgp
|
||||
ffmetadata mxf truehd
|
||||
fifo mxf_d10 tta
|
||||
fifo_test mxf_opatom ttml
|
||||
filmstrip null uncodedframecrc
|
||||
fits nut vc1
|
||||
flac obu vc1t
|
||||
flv oga voc
|
||||
framecrc ogg w64
|
||||
framehash ogv wav
|
||||
framemd5 oma webm
|
||||
g722 opus webm_chunk
|
||||
g723_1 pcm_alaw webm_dash_manifest
|
||||
g726 pcm_f32be webp
|
||||
g726le pcm_f32le webvtt
|
||||
gif pcm_f64be wsaud
|
||||
gsm pcm_f64le wtv
|
||||
gxf pcm_mulaw wv
|
||||
h261 pcm_s16be yuv4mpegpipe
|
||||
|
||||
Enabled protocols:
|
||||
async http rtmp
|
||||
cache httpproxy rtmpe
|
||||
concat https rtmps
|
||||
concatf icecast rtmpt
|
||||
crypto ipfs_gateway rtmpte
|
||||
data ipns_gateway rtmpts
|
||||
fd libsrt rtp
|
||||
ffrtmpcrypt libssh srtp
|
||||
ffrtmphttp libzmq subfile
|
||||
file md5 tcp
|
||||
ftp mmsh tee
|
||||
gopher mmst tls
|
||||
gophers pipe udp
|
||||
hls prompeg udplite
|
||||
|
||||
Enabled filters:
|
||||
a3dscope cue pad
|
||||
abench curves pal100bars
|
||||
abitscope datascope pal75bars
|
||||
acompressor dblur palettegen
|
||||
acontrast dcshift paletteuse
|
||||
acopy dctdnoiz pan
|
||||
acrossfade ddagrab perms
|
||||
acrossover deband perspective
|
||||
acrusher deblock phase
|
||||
acue decimate photosensitivity
|
||||
addroi deconvolve pixdesctest
|
||||
adeclick dedot pixelize
|
||||
adeclip deesser pixscope
|
||||
adecorrelate deflate pp
|
||||
adelay deflicker pp7
|
||||
adenorm deinterlace_qsv premultiply
|
||||
aderivative dejudder prewitt
|
||||
adrawgraph delogo pseudocolor
|
||||
adrc derain psnr
|
||||
adynamicequalizer deshake pullup
|
||||
adynamicsmooth despill qp
|
||||
aecho detelecine random
|
||||
aemphasis dialoguenhance readeia608
|
||||
aeval dilation readvitc
|
||||
aevalsrc displace realtime
|
||||
aexciter dnn_classify remap
|
||||
afade dnn_detect removegrain
|
||||
afdelaysrc dnn_processing removelogo
|
||||
afftdn doubleweave repeatfields
|
||||
afftfilt drawbox replaygain
|
||||
afifo drawgraph reverse
|
||||
afir drawgrid rgbashift
|
||||
afireqsrc drawtext rgbtestsrc
|
||||
afirsrc drmeter roberts
|
||||
aformat dynaudnorm rotate
|
||||
afreqshift earwax rubberband
|
||||
afwtdn ebur128 sab
|
||||
agate edgedetect scale
|
||||
agraphmonitor elbg scale2ref
|
||||
ahistogram entropy scale_cuda
|
||||
aiir epx scale_qsv
|
||||
aintegral eq scdet
|
||||
ainterleave equalizer scharr
|
||||
alatency erosion scroll
|
||||
alimiter estdif segment
|
||||
allpass exposure select
|
||||
allrgb extractplanes selectivecolor
|
||||
allyuv extrastereo sendcmd
|
||||
aloop fade separatefields
|
||||
alphaextract feedback setdar
|
||||
alphamerge fftdnoiz setfield
|
||||
amerge fftfilt setparams
|
||||
ametadata field setpts
|
||||
amix fieldhint setrange
|
||||
amovie fieldmatch setsar
|
||||
amplify fieldorder settb
|
||||
amultiply fifo shear
|
||||
anequalizer fillborders showcqt
|
||||
anlmdn find_rect showcwt
|
||||
anlmf firequalizer showfreqs
|
||||
anlms flanger showinfo
|
||||
anoisesrc floodfill showpalette
|
||||
anull format showspatial
|
||||
anullsink fps showspectrum
|
||||
anullsrc framepack showspectrumpic
|
||||
apad framerate showvolume
|
||||
aperms framestep showwaves
|
||||
aphasemeter freezedetect showwavespic
|
||||
aphaser freezeframes shuffleframes
|
||||
aphaseshift fspp shufflepixels
|
||||
apsyclip gblur shuffleplanes
|
||||
apulsator geq sidechaincompress
|
||||
arealtime gradfun sidechaingate
|
||||
aresample gradients sidedata
|
||||
areverse graphmonitor sierpinski
|
||||
arls grayworld signalstats
|
||||
arnndn greyedge signature
|
||||
asdr guided silencedetect
|
||||
asegment haas silenceremove
|
||||
aselect haldclut sinc
|
||||
asendcmd haldclutsrc sine
|
||||
asetnsamples hdcd siti
|
||||
asetpts headphone smartblur
|
||||
asetrate hflip smptebars
|
||||
asettb highpass smptehdbars
|
||||
ashowinfo highshelf sobel
|
||||
asidedata hilbert spectrumsynth
|
||||
asoftclip histeq speechnorm
|
||||
aspectralstats histogram split
|
||||
asplit hqdn3d spp
|
||||
ass hqx sr
|
||||
astats hstack ssim
|
||||
astreamselect hstack_qsv ssim360
|
||||
asubboost hsvhold stereo3d
|
||||
asubcut hsvkey stereotools
|
||||
asupercut hue stereowiden
|
||||
asuperpass huesaturation streamselect
|
||||
asuperstop hwdownload subtitles
|
||||
atadenoise hwmap super2xsai
|
||||
atempo hwupload superequalizer
|
||||
atilt hwupload_cuda surround
|
||||
atrim hysteresis swaprect
|
||||
avectorscope identity swapuv
|
||||
avgblur idet tblend
|
||||
avsynctest il telecine
|
||||
axcorrelate inflate testsrc
|
||||
azmq interlace testsrc2
|
||||
backgroundkey interleave thistogram
|
||||
bandpass join threshold
|
||||
bandreject kerndeint thumbnail
|
||||
bass kirsch thumbnail_cuda
|
||||
bbox lagfun tile
|
||||
bench latency tiltshelf
|
||||
bilateral lenscorrection tinterlace
|
||||
bilateral_cuda libvmaf tlut2
|
||||
biquad life tmedian
|
||||
bitplanenoise limitdiff tmidequalizer
|
||||
blackdetect limiter tmix
|
||||
blackframe loop tonemap
|
||||
blend loudnorm tpad
|
||||
blockdetect lowpass transpose
|
||||
blurdetect lowshelf treble
|
||||
bm3d lumakey tremolo
|
||||
boxblur lut trim
|
||||
bwdif lut1d unpremultiply
|
||||
cas lut2 unsharp
|
||||
ccrepack lut3d untile
|
||||
cellauto lutrgb uspp
|
||||
channelmap lutyuv v360
|
||||
channelsplit mandelbrot vaguedenoiser
|
||||
chorus maskedclamp varblur
|
||||
chromahold maskedmax vectorscope
|
||||
chromakey maskedmerge vflip
|
||||
chromakey_cuda maskedmin vfrdet
|
||||
chromanr maskedthreshold vibrance
|
||||
chromashift maskfun vibrato
|
||||
ciescope mcdeint vidstabdetect
|
||||
codecview mcompand vidstabtransform
|
||||
color median vif
|
||||
colorbalance mergeplanes vignette
|
||||
colorchannelmixer mestimate virtualbass
|
||||
colorchart metadata vmafmotion
|
||||
colorcontrast midequalizer volume
|
||||
colorcorrect minterpolate volumedetect
|
||||
colorhold mix vpp_qsv
|
||||
colorize monochrome vstack
|
||||
colorkey morpho vstack_qsv
|
||||
colorlevels movie w3fdif
|
||||
colormap mpdecimate waveform
|
||||
colormatrix mptestsrc weave
|
||||
colorspace msad xbr
|
||||
colorspace_cuda multiply xcorrelate
|
||||
colorspectrum negate xfade
|
||||
colortemperature nlmeans xmedian
|
||||
compand nnedi xstack
|
||||
compensationdelay noformat xstack_qsv
|
||||
concat noise yadif
|
||||
convolution normalize yadif_cuda
|
||||
convolve null yaepblur
|
||||
copy nullsink yuvtestsrc
|
||||
corr nullsrc zmq
|
||||
cover_rect oscilloscope zoneplate
|
||||
crop overlay zoompan
|
||||
cropdetect overlay_cuda zscale
|
||||
crossfeed overlay_qsv
|
||||
crystalizer owdenoise
|
||||
|
||||
Enabled bsfs:
|
||||
aac_adtstoasc h264_redundant_pps opus_metadata
|
||||
av1_frame_merge hapqa_extract pcm_rechunk
|
||||
av1_frame_split hevc_metadata pgs_frame_merge
|
||||
av1_metadata hevc_mp4toannexb prores_metadata
|
||||
chomp imx_dump_header remove_extradata
|
||||
dca_core media100_to_mjpegb setts
|
||||
dts2pts mjpeg2jpeg text2movsub
|
||||
dump_extradata mjpega_dump_header trace_headers
|
||||
dv_error_marker mov2textsub truehd_core
|
||||
eac3_core mp3_header_decompress vp9_metadata
|
||||
extract_extradata mpeg2_metadata vp9_raw_reorder
|
||||
filter_units mpeg4_unpack_bframes vp9_superframe
|
||||
h264_metadata noise vp9_superframe_split
|
||||
h264_mp4toannexb null
|
||||
|
||||
Enabled indevs:
|
||||
dshow lavfi
|
||||
gdigrab vfwcap
|
||||
|
||||
Enabled outdevs:
|
||||
sdl2
|
||||
@@ -1,250 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- Created by GNU Texinfo 7.0.1, https://www.gnu.org/software/texinfo/ -->
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Community</title>
|
||||
|
||||
<meta name="description" content="Community">
|
||||
<meta name="keywords" content="Community">
|
||||
<meta name="resource-type" content="document">
|
||||
<meta name="distribution" content="global">
|
||||
<meta name="Generator" content="makeinfo">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
<link href="#SEC_Contents" rel="contents" title="Table of Contents">
|
||||
<style type="text/css">
|
||||
<!--
|
||||
ul.toc-numbered-mark {list-style: none}
|
||||
-->
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body lang="en">
|
||||
|
||||
|
||||
<div class="top-level-extent" id="SEC_Top">
|
||||
|
||||
|
||||
<a class="anchor" id="Organisation"></a><div class="element-contents" id="SEC_Contents">
|
||||
<h2 class="contents-heading">Table of Contents</h2>
|
||||
|
||||
<div class="contents">
|
||||
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Organisation-1" href="#Organisation-1">1 Organisation</a></li>
|
||||
<li><a id="toc-General-Assembly-1" href="#General-Assembly-1">2 General Assembly</a></li>
|
||||
<li><a id="toc-Voting-1" href="#Voting-1">3 Voting</a></li>
|
||||
<li><a id="toc-Technical-Committee-1" href="#Technical-Committee-1">4 Technical Committee</a>
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Resolution-Process-1" href="#Resolution-Process-1">4.1 Resolution Process</a>
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Seizing" href="#Seizing">4.1.1 Seizing</a></li>
|
||||
<li><a id="toc-Announcement" href="#Announcement">4.1.2 Announcement</a></li>
|
||||
<li><a id="toc-RFC-call" href="#RFC-call">4.1.3 RFC call</a></li>
|
||||
<li><a id="toc-Within-TC" href="#Within-TC">4.1.4 Within TC</a></li>
|
||||
<li><a id="toc-Decisions" href="#Decisions">4.1.5 Decisions</a></li>
|
||||
</ul></li>
|
||||
</ul></li>
|
||||
<li><a id="toc-Community-Committee-1" href="#Community-Committee-1">5 Community Committee</a></li>
|
||||
<li><a id="toc-Code-of-Conduct-1" href="#Code-of-Conduct-1">6 Code of Conduct</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Organisation-1">
|
||||
<h2 class="chapter">1 Organisation</h2>
|
||||
|
||||
<p>The FFmpeg project is organized through a community working on global consensus.
|
||||
</p>
|
||||
<p>Decisions are taken by the ensemble of active members, through voting and are aided by two committees.
|
||||
</p>
|
||||
<a class="anchor" id="General-Assembly"></a></div>
|
||||
<div class="chapter-level-extent" id="General-Assembly-1">
|
||||
<h2 class="chapter">2 General Assembly</h2>
|
||||
|
||||
<p>The ensemble of active members is called the General Assembly (GA).
|
||||
</p>
|
||||
<p>The General Assembly is sovereign and legitimate for all its decisions regarding the FFmpeg project.
|
||||
</p>
|
||||
<p>The General Assembly is made up of active contributors.
|
||||
</p>
|
||||
<p>Contributors are considered "active contributors" if they have pushed more than 20 patches in the last 36 months in the main FFmpeg repository, or if they have been voted in by the GA.
|
||||
</p>
|
||||
<p>Additional members are added to the General Assembly through a vote after proposal by a member of the General Assembly. They are part of the GA for two years, after which they need a confirmation by the GA.
|
||||
</p>
|
||||
<p>A script to generate the current members of the general assembly (minus members voted in) can be found in ‘tools/general_assembly.pl‘.
|
||||
</p>
|
||||
<a class="anchor" id="Voting"></a></div>
|
||||
<div class="chapter-level-extent" id="Voting-1">
|
||||
<h2 class="chapter">3 Voting</h2>
|
||||
|
||||
<p>Voting is done using a ranked voting system, currently running on https://vote.ffmpeg.org/ .
|
||||
</p>
|
||||
<p>Majority vote means more than 50% of the expressed ballots.
|
||||
</p>
|
||||
<a class="anchor" id="Technical-Committee"></a></div>
|
||||
<div class="chapter-level-extent" id="Technical-Committee-1">
|
||||
<h2 class="chapter">4 Technical Committee</h2>
|
||||
|
||||
<p>The Technical Committee (TC) is here to arbitrate and make decisions when technical conflicts occur in the project. They will consider the merits of all the positions, judge them and make a decision.
|
||||
</p>
|
||||
<p>The TC resolves technical conflicts but is not a technical steering committee.
|
||||
</p>
|
||||
<p>Decisions by the TC are binding for all the contributors.
|
||||
</p>
|
||||
<p>Decisions made by the TC can be re-opened after 1 year or by a majority vote of the General Assembly, requested by one of the member of the GA.
|
||||
</p>
|
||||
<p>The TC is elected by the General Assembly for a duration of 1 year, and is composed of 5 members. Members can be re-elected if they wish. A majority vote in the General Assembly can trigger a new election of the TC.
|
||||
</p>
|
||||
<p>The members of the TC can be elected from outside of the GA. Candidates for election can either be suggested or self-nominated.
|
||||
</p>
|
||||
<p>The conflict resolution process is detailed in the resolution process document.
|
||||
</p>
|
||||
<p>The TC can be contacted at <tc@ffmpeg>.
|
||||
</p>
|
||||
<a class="anchor" id="Resolution-Process"></a><ul class="mini-toc">
|
||||
<li><a href="#Resolution-Process-1" accesskey="1">Resolution Process</a></li>
|
||||
</ul>
|
||||
<div class="section-level-extent" id="Resolution-Process-1">
|
||||
<h3 class="section">4.1 Resolution Process</h3>
|
||||
|
||||
<p>The Technical Committee (TC) is here to arbitrate and make decisions when technical conflicts occur in the project.
|
||||
</p>
|
||||
<p>The TC main role is to resolve technical conflicts. It is therefore not a technical steering committee, but it is understood that some decisions might impact the future of the project.
|
||||
</p>
|
||||
<ul class="mini-toc">
|
||||
<li><a href="#Seizing" accesskey="1">Seizing</a></li>
|
||||
<li><a href="#Announcement" accesskey="2">Announcement</a></li>
|
||||
<li><a href="#RFC-call" accesskey="3">RFC call</a></li>
|
||||
<li><a href="#Within-TC" accesskey="4">Within TC</a></li>
|
||||
<li><a href="#Decisions" accesskey="5">Decisions</a></li>
|
||||
</ul>
|
||||
<div class="subsection-level-extent" id="Seizing">
|
||||
<h4 class="subsection">4.1.1 Seizing</h4>
|
||||
|
||||
<p>The TC can take possession of any technical matter that it sees fit.
|
||||
</p>
|
||||
<p>To involve the TC in a matter, email tc or CC them on an ongoing discussion.
|
||||
</p>
|
||||
<p>As members of TC are developers, they also can email tc to raise an issue.
|
||||
</p></div>
|
||||
<div class="subsection-level-extent" id="Announcement">
|
||||
<h4 class="subsection">4.1.2 Announcement</h4>
|
||||
|
||||
<p>The TC, once seized, must announce itself on the main mailing list, with a [TC] tag.
|
||||
</p>
|
||||
<p>The TC has 2 modes of operation: a RFC one and an internal one.
|
||||
</p>
|
||||
<p>If the TC thinks it needs the input from the larger community, the TC can call for a RFC. Else, it can decide by itself.
|
||||
</p>
|
||||
<p>If the disagreement involves a member of the TC, that member should recuse themselves from the decision.
|
||||
</p>
|
||||
<p>The decision to use a RFC process or an internal discussion is a discretionary decision of the TC.
|
||||
</p>
|
||||
<p>The TC can also reject a seizure for a few reasons such as: the matter was not discussed enough previously; it lacks expertise to reach a beneficial decision on the matter; or the matter is too trivial.
|
||||
</p></div>
|
||||
<div class="subsection-level-extent" id="RFC-call">
|
||||
<h4 class="subsection">4.1.3 RFC call</h4>
|
||||
|
||||
<p>In the RFC mode, one person from the TC posts on the mailing list the technical question and will request input from the community.
|
||||
</p>
|
||||
<p>The mail will have the following specification:
|
||||
</p>
|
||||
<p>a precise title
|
||||
a specific tag [TC RFC]
|
||||
a top-level email
|
||||
contain a precise question that does not exceed 100 words and that is answerable by developers
|
||||
may have an extra description, or a link to a previous discussion, if deemed necessary,
|
||||
contain a precise end date for the answers.
|
||||
</p>
|
||||
<p>The answers from the community must be on the main mailing list and must have the following specification:
|
||||
</p>
|
||||
<p>keep the tag and the title unchanged
|
||||
limited to 400 words
|
||||
a first-level, answering directly to the main email
|
||||
answering to the question.
|
||||
</p>
|
||||
<p>Further replies to answers are permitted, as long as they conform to the community standards of politeness, they are limited to 100 words, and are not nested more than once. (max-depth=2)
|
||||
</p>
|
||||
<p>After the end-date, mails on the thread will be ignored.
|
||||
</p>
|
||||
<p>Violations of those rules will be escalated through the Community Committee.
|
||||
</p>
|
||||
<p>After all the emails are in, the TC has 96 hours to give its final decision. Exceptionally, the TC can request an extra delay, that will be notified on the mailing list.
|
||||
</p></div>
|
||||
<div class="subsection-level-extent" id="Within-TC">
|
||||
<h4 class="subsection">4.1.4 Within TC</h4>
|
||||
|
||||
<p>In the internal case, the TC has 96 hours to give its final decision. Exceptionally, the TC can request an extra delay.
|
||||
</p></div>
|
||||
<div class="subsection-level-extent" id="Decisions">
|
||||
<h4 class="subsection">4.1.5 Decisions</h4>
|
||||
|
||||
<p>The decisions from the TC will be sent on the mailing list, with the [TC] tag.
|
||||
</p>
|
||||
<p>Internally, the TC should take decisions with a majority, or using ranked-choice voting.
|
||||
</p>
|
||||
<p>The decision from the TC should be published with a summary of the reasons that lead to this decision.
|
||||
</p>
|
||||
<p>The decisions from the TC are final, until the matters are reopened after no less than one year.
|
||||
</p>
|
||||
<a class="anchor" id="Community-Committee"></a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Community-Committee-1">
|
||||
<h2 class="chapter">5 Community Committee</h2>
|
||||
|
||||
<p>The Community Committee (CC) is here to arbitrage and make decisions when inter-personal conflicts occur in the project. It will decide quickly and take actions, for the sake of the project.
|
||||
</p>
|
||||
<p>The CC can remove privileges of offending members, including removal of commit access and temporary ban from the community.
|
||||
</p>
|
||||
<p>Decisions made by the CC can be re-opened after 1 year or by a majority vote of the General Assembly. Indefinite bans from the community must be confirmed by the General Assembly, in a majority vote.
|
||||
</p>
|
||||
<p>The CC is elected by the General Assembly for a duration of 1 year, and is composed of 5 members. Members can be re-elected if they wish. A majority vote in the General Assembly can trigger a new election of the CC.
|
||||
</p>
|
||||
<p>The members of the CC can be elected from outside of the GA. Candidates for election can either be suggested or self-nominated.
|
||||
</p>
|
||||
<p>The CC is governed by and responsible for enforcing the Code of Conduct.
|
||||
</p>
|
||||
<p>The CC can be contacted at <cc@ffmpeg>.
|
||||
</p>
|
||||
<a class="anchor" id="Code-of-Conduct"></a></div>
|
||||
<div class="chapter-level-extent" id="Code-of-Conduct-1">
|
||||
<h2 class="chapter">6 Code of Conduct</h2>
|
||||
|
||||
<p>Be friendly and respectful towards others and third parties.
|
||||
Treat others the way you yourself want to be treated.
|
||||
</p>
|
||||
<p>Be considerate. Not everyone shares the same viewpoint and priorities as you do.
|
||||
Different opinions and interpretations help the project.
|
||||
Looking at issues from a different perspective assists development.
|
||||
</p>
|
||||
<p>Do not assume malice for things that can be attributed to incompetence. Even if
|
||||
it is malice, it’s rarely good to start with that as initial assumption.
|
||||
</p>
|
||||
<p>Stay friendly even if someone acts contrarily. Everyone has a bad day
|
||||
once in a while.
|
||||
If you yourself have a bad day or are angry then try to take a break and reply
|
||||
once you are calm and without anger if you have to.
|
||||
</p>
|
||||
<p>Try to help other team members and cooperate if you can.
|
||||
</p>
|
||||
<p>The goal of software development is to create technical excellence, not for any
|
||||
individual to be better and "win" against the others. Large software projects
|
||||
are only possible and successful through teamwork.
|
||||
</p>
|
||||
<p>If someone struggles do not put them down. Give them a helping hand
|
||||
instead and point them in the right direction.
|
||||
</p>
|
||||
<p>Finally, keep in mind the immortal words of Bill and Ted,
|
||||
"Be excellent to each other."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,165 +0,0 @@
|
||||
a.summary-letter {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #2D6198;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #884488;
|
||||
}
|
||||
|
||||
#banner {
|
||||
background-color: white;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#banner img {
|
||||
margin-bottom: 1px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#body {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #313131;
|
||||
margin: 0;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#container {
|
||||
background-color: white;
|
||||
color: #202020;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 a, h2 a, h3 a, h4 a {
|
||||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
padding-left: 0.4em;
|
||||
border-radius: 4px;
|
||||
padding-bottom: 0.25em;
|
||||
padding-top: 0.25em;
|
||||
border: 1px solid #6A996A;
|
||||
}
|
||||
|
||||
h1 {
|
||||
background-color: #7BB37B;
|
||||
color: #151515;
|
||||
font-size: 1.2em;
|
||||
padding-bottom: 0.3em;
|
||||
padding-top: 0.3em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #313131;
|
||||
font-size: 1.0em;
|
||||
background-color: #ABE3AB;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #313131;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: -6px;
|
||||
background-color: #BBF3BB;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #313131;
|
||||
font-size: 0.8em;
|
||||
margin-bottom: -8px;
|
||||
background-color: #D1FDD1;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
background-color: #738073;
|
||||
border-bottom: 1px solid #5C665C;
|
||||
border-top: 1px solid #5C665C;
|
||||
margin-top: 12px;
|
||||
padding: 0.3em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#navbar a, #navbar_secondary a {
|
||||
color: white;
|
||||
padding: 0.3em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#navbar a:hover, #navbar_secondary a:hover {
|
||||
background-color: #313131;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#navbar_secondary {
|
||||
background-color: #738073;
|
||||
border-bottom: 1px solid #5C665C;
|
||||
border-left: 1px solid #5C665C;
|
||||
border-right: 1px solid #5C665C;
|
||||
padding: 0.3em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-left: 3em;
|
||||
margin-right: 3em;
|
||||
padding: 0.3em;
|
||||
border: 1px solid #bbb;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#proj_desc {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
#repos {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
border-collapse: collapse;
|
||||
border: solid 1px #6A996A;
|
||||
}
|
||||
|
||||
#repos th {
|
||||
background-color: #7BB37B;
|
||||
border: solid 1px #6A996A;
|
||||
}
|
||||
|
||||
#repos td {
|
||||
padding: 0.2em;
|
||||
border: solid 1px #6A996A;
|
||||
}
|
||||
@@ -1,941 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- Created by GNU Texinfo 7.0.1, https://www.gnu.org/software/texinfo/ -->
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>FFmpeg FAQ</title>
|
||||
|
||||
<meta name="description" content="FFmpeg FAQ">
|
||||
<meta name="keywords" content="FFmpeg FAQ">
|
||||
<meta name="resource-type" content="document">
|
||||
<meta name="distribution" content="global">
|
||||
<meta name="Generator" content="makeinfo">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
<link href="#SEC_Contents" rel="contents" title="Table of Contents">
|
||||
<style type="text/css">
|
||||
<!--
|
||||
div.example {margin-left: 3.2em}
|
||||
ul.mark-bullet {list-style-type: disc}
|
||||
ul.toc-numbered-mark {list-style: none}
|
||||
-->
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body lang="en">
|
||||
|
||||
|
||||
<div class="top-level-extent" id="SEC_Top">
|
||||
|
||||
|
||||
<div class="element-contents" id="SEC_Contents">
|
||||
<h2 class="contents-heading">Table of Contents</h2>
|
||||
|
||||
<div class="contents">
|
||||
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-General-Questions" href="#General-Questions">1 General Questions</a>
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Why-doesn_0027t-FFmpeg-support-feature-_005bxyz_005d_003f" href="#Why-doesn_0027t-FFmpeg-support-feature-_005bxyz_005d_003f">1.1 Why doesn’t FFmpeg support feature [xyz]?</a></li>
|
||||
<li><a id="toc-FFmpeg-does-not-support-codec-XXX_002e-Can-you-include-a-Windows-DLL-loader-to-support-it_003f" href="#FFmpeg-does-not-support-codec-XXX_002e-Can-you-include-a-Windows-DLL-loader-to-support-it_003f">1.2 FFmpeg does not support codec XXX. Can you include a Windows DLL loader to support it?</a></li>
|
||||
<li><a id="toc-I-cannot-read-this-file-although-this-format-seems-to-be-supported-by-ffmpeg_002e" href="#I-cannot-read-this-file-although-this-format-seems-to-be-supported-by-ffmpeg_002e">1.3 I cannot read this file although this format seems to be supported by ffmpeg.</a></li>
|
||||
<li><a id="toc-Which-codecs-are-supported-by-Windows_003f" href="#Which-codecs-are-supported-by-Windows_003f">1.4 Which codecs are supported by Windows?</a></li>
|
||||
</ul></li>
|
||||
<li><a id="toc-Compilation" href="#Compilation">2 Compilation</a>
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-error_003a-can_0027t-find-a-register-in-class-_0027GENERAL_005fREGS_0027-while-reloading-_0027asm_0027" href="#error_003a-can_0027t-find-a-register-in-class-_0027GENERAL_005fREGS_0027-while-reloading-_0027asm_0027">2.1 <code class="code">error: can't find a register in class 'GENERAL_REGS' while reloading 'asm'</code></a></li>
|
||||
<li><a id="toc-I-have-installed-this-library-with-my-distro_0027s-package-manager_002e-Why-does-configure-not-see-it_003f" href="#I-have-installed-this-library-with-my-distro_0027s-package-manager_002e-Why-does-configure-not-see-it_003f">2.2 I have installed this library with my distro’s package manager. Why does <code class="command">configure</code> not see it?</a></li>
|
||||
<li><a id="toc-How-do-I-make-pkg_002dconfig-find-my-libraries_003f" href="#How-do-I-make-pkg_002dconfig-find-my-libraries_003f">2.3 How do I make <code class="command">pkg-config</code> find my libraries?</a></li>
|
||||
<li><a id="toc-How-do-I-use-pkg_002dconfig-when-cross_002dcompiling_003f" href="#How-do-I-use-pkg_002dconfig-when-cross_002dcompiling_003f">2.4 How do I use <code class="command">pkg-config</code> when cross-compiling?</a></li>
|
||||
</ul></li>
|
||||
<li><a id="toc-Usage" href="#Usage">3 Usage</a>
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-ffmpeg-does-not-work_003b-what-is-wrong_003f" href="#ffmpeg-does-not-work_003b-what-is-wrong_003f">3.1 ffmpeg does not work; what is wrong?</a></li>
|
||||
<li><a id="toc-How-do-I-encode-single-pictures-into-movies_003f" href="#How-do-I-encode-single-pictures-into-movies_003f">3.2 How do I encode single pictures into movies?</a></li>
|
||||
<li><a id="toc-How-do-I-encode-movie-to-single-pictures_003f" href="#How-do-I-encode-movie-to-single-pictures_003f">3.3 How do I encode movie to single pictures?</a></li>
|
||||
<li><a id="toc-Why-do-I-see-a-slight-quality-degradation-with-multithreaded-MPEG_002a-encoding_003f" href="#Why-do-I-see-a-slight-quality-degradation-with-multithreaded-MPEG_002a-encoding_003f">3.4 Why do I see a slight quality degradation with multithreaded MPEG* encoding?</a></li>
|
||||
<li><a id="toc-How-can-I-read-from-the-standard-input-or-write-to-the-standard-output_003f" href="#How-can-I-read-from-the-standard-input-or-write-to-the-standard-output_003f">3.5 How can I read from the standard input or write to the standard output?</a></li>
|
||||
<li><a id="toc-_002df-jpeg-doesn_0027t-work_002e" href="#g_t_002df-jpeg-doesn_0027t-work_002e">3.6 -f jpeg doesn’t work.</a></li>
|
||||
<li><a id="toc-Why-can-I-not-change-the-frame-rate_003f" href="#Why-can-I-not-change-the-frame-rate_003f">3.7 Why can I not change the frame rate?</a></li>
|
||||
<li><a id="toc-How-do-I-encode-Xvid-or-DivX-video-with-ffmpeg_003f" href="#How-do-I-encode-Xvid-or-DivX-video-with-ffmpeg_003f">3.8 How do I encode Xvid or DivX video with ffmpeg?</a></li>
|
||||
<li><a id="toc-Which-are-good-parameters-for-encoding-high-quality-MPEG_002d4_003f" href="#Which-are-good-parameters-for-encoding-high-quality-MPEG_002d4_003f">3.9 Which are good parameters for encoding high quality MPEG-4?</a></li>
|
||||
<li><a id="toc-Which-are-good-parameters-for-encoding-high-quality-MPEG_002d1_002fMPEG_002d2_003f" href="#Which-are-good-parameters-for-encoding-high-quality-MPEG_002d1_002fMPEG_002d2_003f">3.10 Which are good parameters for encoding high quality MPEG-1/MPEG-2?</a></li>
|
||||
<li><a id="toc-Interlaced-video-looks-very-bad-when-encoded-with-ffmpeg_002c-what-is-wrong_003f" href="#Interlaced-video-looks-very-bad-when-encoded-with-ffmpeg_002c-what-is-wrong_003f">3.11 Interlaced video looks very bad when encoded with ffmpeg, what is wrong?</a></li>
|
||||
<li><a id="toc-How-can-I-read-DirectShow-files_003f" href="#How-can-I-read-DirectShow-files_003f">3.12 How can I read DirectShow files?</a></li>
|
||||
<li><a id="toc-How-can-I-join-video-files_003f" href="#How-can-I-join-video-files_003f">3.13 How can I join video files?</a></li>
|
||||
<li><a id="toc-How-can-I-concatenate-video-files_003f" href="#How-can-I-concatenate-video-files_003f">3.14 How can I concatenate video files?</a>
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Concatenating-using-the-concat-filter" href="#Concatenating-using-the-concat-filter">3.14.1 Concatenating using the concat <em class="emph">filter</em></a></li>
|
||||
<li><a id="toc-Concatenating-using-the-concat-demuxer" href="#Concatenating-using-the-concat-demuxer">3.14.2 Concatenating using the concat <em class="emph">demuxer</em></a></li>
|
||||
<li><a id="toc-Concatenating-using-the-concat-protocol-_0028file-level_0029" href="#Concatenating-using-the-concat-protocol-_0028file-level_0029">3.14.3 Concatenating using the concat <em class="emph">protocol</em> (file level)</a></li>
|
||||
<li><a id="toc-Concatenating-using-raw-audio-and-video" href="#Concatenating-using-raw-audio-and-video">3.14.4 Concatenating using raw audio and video</a></li>
|
||||
</ul></li>
|
||||
<li><a id="toc-Using-_002df-lavfi_002c-audio-becomes-mono-for-no-apparent-reason_002e" href="#Using-_002df-lavfi_002c-audio-becomes-mono-for-no-apparent-reason_002e">3.15 Using <samp class="option">-f lavfi</samp>, audio becomes mono for no apparent reason.</a></li>
|
||||
<li><a id="toc-Why-does-FFmpeg-not-see-the-subtitles-in-my-VOB-file_003f" href="#Why-does-FFmpeg-not-see-the-subtitles-in-my-VOB-file_003f">3.16 Why does FFmpeg not see the subtitles in my VOB file?</a></li>
|
||||
<li><a id="toc-Why-was-the-ffmpeg-_002dsameq-option-removed_003f-What-to-use-instead_003f" href="#Why-was-the-ffmpeg-_002dsameq-option-removed_003f-What-to-use-instead_003f">3.17 Why was the <code class="command">ffmpeg</code> <samp class="option">-sameq</samp> option removed? What to use instead?</a></li>
|
||||
<li><a id="toc-I-have-a-stretched-video_002c-why-does-scaling-does-not-fix-it_003f" href="#I-have-a-stretched-video_002c-why-does-scaling-does-not-fix-it_003f">3.18 I have a stretched video, why does scaling does not fix it?</a></li>
|
||||
<li><a id="toc-How-do-I-run-ffmpeg-as-a-background-task_003f" href="#How-do-I-run-ffmpeg-as-a-background-task_003f">3.19 How do I run ffmpeg as a background task?</a></li>
|
||||
<li><a id="toc-How-do-I-prevent-ffmpeg-from-suspending-with-a-message-like-suspended-_0028tty-output_0029_003f" href="#How-do-I-prevent-ffmpeg-from-suspending-with-a-message-like-suspended-_0028tty-output_0029_003f">3.20 How do I prevent ffmpeg from suspending with a message like <em class="emph">suspended (tty output)</em>?</a></li>
|
||||
</ul></li>
|
||||
<li><a id="toc-Development" href="#Development">4 Development</a>
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Are-there-examples-illustrating-how-to-use-the-FFmpeg-libraries_002c-particularly-libavcodec-and-libavformat_003f" href="#Are-there-examples-illustrating-how-to-use-the-FFmpeg-libraries_002c-particularly-libavcodec-and-libavformat_003f">4.1 Are there examples illustrating how to use the FFmpeg libraries, particularly libavcodec and libavformat?</a></li>
|
||||
<li><a id="toc-Can-you-support-my-C-compiler-XXX_003f" href="#Can-you-support-my-C-compiler-XXX_003f">4.2 Can you support my C compiler XXX?</a></li>
|
||||
<li><a id="toc-Is-Microsoft-Visual-C_002b_002b-supported_003f" href="#Is-Microsoft-Visual-C_002b_002b-supported_003f">4.3 Is Microsoft Visual C++ supported?</a></li>
|
||||
<li><a id="toc-Can-you-add-automake_002c-libtool-or-autoconf-support_003f" href="#Can-you-add-automake_002c-libtool-or-autoconf-support_003f">4.4 Can you add automake, libtool or autoconf support?</a></li>
|
||||
<li><a id="toc-Why-not-rewrite-FFmpeg-in-object_002doriented-C_002b_002b_003f" href="#Why-not-rewrite-FFmpeg-in-object_002doriented-C_002b_002b_003f">4.5 Why not rewrite FFmpeg in object-oriented C++?</a></li>
|
||||
<li><a id="toc-Why-are-the-ffmpeg-programs-devoid-of-debugging-symbols_003f" href="#Why-are-the-ffmpeg-programs-devoid-of-debugging-symbols_003f">4.6 Why are the ffmpeg programs devoid of debugging symbols?</a></li>
|
||||
<li><a id="toc-I-do-not-like-the-LGPL_002c-can-I-contribute-code-under-the-GPL-instead_003f" href="#I-do-not-like-the-LGPL_002c-can-I-contribute-code-under-the-GPL-instead_003f">4.7 I do not like the LGPL, can I contribute code under the GPL instead?</a></li>
|
||||
<li><a id="toc-I_0027m-using-FFmpeg-from-within-my-C-application-but-the-linker-complains-about-missing-symbols-from-the-libraries-themselves_002e" href="#I_0027m-using-FFmpeg-from-within-my-C-application-but-the-linker-complains-about-missing-symbols-from-the-libraries-themselves_002e">4.8 I’m using FFmpeg from within my C application but the linker complains about missing symbols from the libraries themselves.</a></li>
|
||||
<li><a id="toc-I_0027m-using-FFmpeg-from-within-my-C_002b_002b-application-but-the-linker-complains-about-missing-symbols-which-seem-to-be-available_002e" href="#I_0027m-using-FFmpeg-from-within-my-C_002b_002b-application-but-the-linker-complains-about-missing-symbols-which-seem-to-be-available_002e">4.9 I’m using FFmpeg from within my C++ application but the linker complains about missing symbols which seem to be available.</a></li>
|
||||
<li><a id="toc-I_0027m-using-libavutil-from-within-my-C_002b_002b-application-but-the-compiler-complains-about-_0027UINT64_005fC_0027-was-not-declared-in-this-scope" href="#I_0027m-using-libavutil-from-within-my-C_002b_002b-application-but-the-compiler-complains-about-_0027UINT64_005fC_0027-was-not-declared-in-this-scope">4.10 I’m using libavutil from within my C++ application but the compiler complains about ’UINT64_C’ was not declared in this scope</a></li>
|
||||
<li><a id="toc-I-have-a-file-in-memory-_002f-a-API-different-from-_002aopen_002f_002aread_002f-libc-how-do-I-use-it-with-libavformat_003f" href="#I-have-a-file-in-memory-_002f-a-API-different-from-_002aopen_002f_002aread_002f-libc-how-do-I-use-it-with-libavformat_003f">4.11 I have a file in memory / a API different from *open/*read/ libc how do I use it with libavformat?</a></li>
|
||||
<li><a id="toc-Where-is-the-documentation-about-ffv1_002c-msmpeg4_002c-asv1_002c-4xm_003f" href="#Where-is-the-documentation-about-ffv1_002c-msmpeg4_002c-asv1_002c-4xm_003f">4.12 Where is the documentation about ffv1, msmpeg4, asv1, 4xm?</a></li>
|
||||
<li><a id="toc-How-do-I-feed-H_002e263_002dRTP-_0028and-other-codecs-in-RTP_0029-to-libavcodec_003f" href="#How-do-I-feed-H_002e263_002dRTP-_0028and-other-codecs-in-RTP_0029-to-libavcodec_003f">4.13 How do I feed H.263-RTP (and other codecs in RTP) to libavcodec?</a></li>
|
||||
<li><a id="toc-AVStream_002er_005fframe_005frate-is-wrong_002c-it-is-much-larger-than-the-frame-rate_002e" href="#AVStream_002er_005fframe_005frate-is-wrong_002c-it-is-much-larger-than-the-frame-rate_002e">4.14 AVStream.r_frame_rate is wrong, it is much larger than the frame rate.</a></li>
|
||||
<li><a id="toc-Why-is-make-fate-not-running-all-tests_003f" href="#Why-is-make-fate-not-running-all-tests_003f">4.15 Why is <code class="code">make fate</code> not running all tests?</a></li>
|
||||
<li><a id="toc-Why-is-make-fate-not-finding-the-samples_003f" href="#Why-is-make-fate-not-finding-the-samples_003f">4.16 Why is <code class="code">make fate</code> not finding the samples?</a></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="General-Questions">
|
||||
<h2 class="chapter">1 General Questions</h2>
|
||||
|
||||
<ul class="mini-toc">
|
||||
<li><a href="#Why-doesn_0027t-FFmpeg-support-feature-_005bxyz_005d_003f" accesskey="1">Why doesn’t FFmpeg support feature [xyz]?</a></li>
|
||||
<li><a href="#FFmpeg-does-not-support-codec-XXX_002e-Can-you-include-a-Windows-DLL-loader-to-support-it_003f" accesskey="2">FFmpeg does not support codec XXX. Can you include a Windows DLL loader to support it?</a></li>
|
||||
<li><a href="#I-cannot-read-this-file-although-this-format-seems-to-be-supported-by-ffmpeg_002e" accesskey="3">I cannot read this file although this format seems to be supported by ffmpeg.</a></li>
|
||||
<li><a href="#Which-codecs-are-supported-by-Windows_003f" accesskey="4">Which codecs are supported by Windows?</a></li>
|
||||
</ul>
|
||||
<div class="section-level-extent" id="Why-doesn_0027t-FFmpeg-support-feature-_005bxyz_005d_003f">
|
||||
<h3 class="section">1.1 Why doesn’t FFmpeg support feature [xyz]?</h3>
|
||||
|
||||
<p>Because no one has taken on that task yet. FFmpeg development is
|
||||
driven by the tasks that are important to the individual developers.
|
||||
If there is a feature that is important to you, the best way to get
|
||||
it implemented is to undertake the task yourself or sponsor a developer.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="FFmpeg-does-not-support-codec-XXX_002e-Can-you-include-a-Windows-DLL-loader-to-support-it_003f">
|
||||
<h3 class="section">1.2 FFmpeg does not support codec XXX. Can you include a Windows DLL loader to support it?</h3>
|
||||
|
||||
<p>No. Windows DLLs are not portable, bloated and often slow.
|
||||
Moreover FFmpeg strives to support all codecs natively.
|
||||
A DLL loader is not conducive to that goal.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="I-cannot-read-this-file-although-this-format-seems-to-be-supported-by-ffmpeg_002e">
|
||||
<h3 class="section">1.3 I cannot read this file although this format seems to be supported by ffmpeg.</h3>
|
||||
|
||||
<p>Even if ffmpeg can read the container format, it may not support all its
|
||||
codecs. Please consult the supported codec list in the ffmpeg
|
||||
documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Which-codecs-are-supported-by-Windows_003f">
|
||||
<h3 class="section">1.4 Which codecs are supported by Windows?</h3>
|
||||
|
||||
<p>Windows does not support standard formats like MPEG very well, unless you
|
||||
install some additional codecs.
|
||||
</p>
|
||||
<p>The following list of video codecs should work on most Windows systems:
|
||||
</p><dl class="table">
|
||||
<dt><samp class="option">msmpeg4v2</samp></dt>
|
||||
<dd><p>.avi/.asf
|
||||
</p></dd>
|
||||
<dt><samp class="option">msmpeg4</samp></dt>
|
||||
<dd><p>.asf only
|
||||
</p></dd>
|
||||
<dt><samp class="option">wmv1</samp></dt>
|
||||
<dd><p>.asf only
|
||||
</p></dd>
|
||||
<dt><samp class="option">wmv2</samp></dt>
|
||||
<dd><p>.asf only
|
||||
</p></dd>
|
||||
<dt><samp class="option">mpeg4</samp></dt>
|
||||
<dd><p>Only if you have some MPEG-4 codec like ffdshow or Xvid installed.
|
||||
</p></dd>
|
||||
<dt><samp class="option">mpeg1video</samp></dt>
|
||||
<dd><p>.mpg only
|
||||
</p></dd>
|
||||
</dl>
|
||||
<p>Note, ASF files often have .wmv or .wma extensions in Windows. It should also
|
||||
be mentioned that Microsoft claims a patent on the ASF format, and may sue
|
||||
or threaten users who create ASF files with non-Microsoft software. It is
|
||||
strongly advised to avoid ASF where possible.
|
||||
</p>
|
||||
<p>The following list of audio codecs should work on most Windows systems:
|
||||
</p><dl class="table">
|
||||
<dt><samp class="option">adpcm_ima_wav</samp></dt>
|
||||
<dt><samp class="option">adpcm_ms</samp></dt>
|
||||
<dt><samp class="option">pcm_s16le</samp></dt>
|
||||
<dd><p>always
|
||||
</p></dd>
|
||||
<dt><samp class="option">libmp3lame</samp></dt>
|
||||
<dd><p>If some MP3 codec like LAME is installed.
|
||||
</p></dd>
|
||||
</dl>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Compilation">
|
||||
<h2 class="chapter">2 Compilation</h2>
|
||||
|
||||
<ul class="mini-toc">
|
||||
<li><a href="#error_003a-can_0027t-find-a-register-in-class-_0027GENERAL_005fREGS_0027-while-reloading-_0027asm_0027" accesskey="1"><code class="code">error: can't find a register in class 'GENERAL_REGS' while reloading 'asm'</code></a></li>
|
||||
<li><a href="#I-have-installed-this-library-with-my-distro_0027s-package-manager_002e-Why-does-configure-not-see-it_003f" accesskey="2">I have installed this library with my distro’s package manager. Why does <code class="command">configure</code> not see it?</a></li>
|
||||
<li><a href="#How-do-I-make-pkg_002dconfig-find-my-libraries_003f" accesskey="3">How do I make <code class="command">pkg-config</code> find my libraries?</a></li>
|
||||
<li><a href="#How-do-I-use-pkg_002dconfig-when-cross_002dcompiling_003f" accesskey="4">How do I use <code class="command">pkg-config</code> when cross-compiling?</a></li>
|
||||
</ul>
|
||||
<div class="section-level-extent" id="error_003a-can_0027t-find-a-register-in-class-_0027GENERAL_005fREGS_0027-while-reloading-_0027asm_0027">
|
||||
<h3 class="section">2.1 <code class="code">error: can't find a register in class 'GENERAL_REGS' while reloading 'asm'</code></h3>
|
||||
|
||||
<p>This is a bug in gcc. Do not report it to us. Instead, please report it to
|
||||
the gcc developers. Note that we will not add workarounds for gcc bugs.
|
||||
</p>
|
||||
<p>Also note that (some of) the gcc developers believe this is not a bug or
|
||||
not a bug they should fix:
|
||||
<a class="url" href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=11203">https://gcc.gnu.org/bugzilla/show_bug.cgi?id=11203</a>.
|
||||
Then again, some of them do not know the difference between an undecidable
|
||||
problem and an NP-hard problem...
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="I-have-installed-this-library-with-my-distro_0027s-package-manager_002e-Why-does-configure-not-see-it_003f">
|
||||
<h3 class="section">2.2 I have installed this library with my distro’s package manager. Why does <code class="command">configure</code> not see it?</h3>
|
||||
|
||||
<p>Distributions usually split libraries in several packages. The main package
|
||||
contains the files necessary to run programs using the library. The
|
||||
development package contains the files necessary to build programs using the
|
||||
library. Sometimes, docs and/or data are in a separate package too.
|
||||
</p>
|
||||
<p>To build FFmpeg, you need to install the development package. It is usually
|
||||
called <samp class="file">libfoo-dev</samp> or <samp class="file">libfoo-devel</samp>. You can remove it after the
|
||||
build is finished, but be sure to keep the main package.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-do-I-make-pkg_002dconfig-find-my-libraries_003f">
|
||||
<h3 class="section">2.3 How do I make <code class="command">pkg-config</code> find my libraries?</h3>
|
||||
|
||||
<p>Somewhere along with your libraries, there is a <samp class="file">.pc</samp> file (or several)
|
||||
in a <samp class="file">pkgconfig</samp> directory. You need to set environment variables to
|
||||
point <code class="command">pkg-config</code> to these files.
|
||||
</p>
|
||||
<p>If you need to <em class="emph">add</em> directories to <code class="command">pkg-config</code>’s search list
|
||||
(typical use case: library installed separately), add it to
|
||||
<code class="code">$PKG_CONFIG_PATH</code>:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">export PKG_CONFIG_PATH=/opt/x264/lib/pkgconfig:/opt/opus/lib/pkgconfig
|
||||
</pre></div>
|
||||
|
||||
<p>If you need to <em class="emph">replace</em> <code class="command">pkg-config</code>’s search list
|
||||
(typical use case: cross-compiling), set it in
|
||||
<code class="code">$PKG_CONFIG_LIBDIR</code>:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">export PKG_CONFIG_LIBDIR=/home/me/cross/usr/lib/pkgconfig:/home/me/cross/usr/local/lib/pkgconfig
|
||||
</pre></div>
|
||||
|
||||
<p>If you need to know the library’s internal dependencies (typical use: static
|
||||
linking), add the <code class="code">--static</code> option to <code class="command">pkg-config</code>:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">./configure --pkg-config-flags=--static
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-do-I-use-pkg_002dconfig-when-cross_002dcompiling_003f">
|
||||
<h3 class="section">2.4 How do I use <code class="command">pkg-config</code> when cross-compiling?</h3>
|
||||
|
||||
<p>The best way is to install <code class="command">pkg-config</code> in your cross-compilation
|
||||
environment. It will automatically use the cross-compilation libraries.
|
||||
</p>
|
||||
<p>You can also use <code class="command">pkg-config</code> from the host environment by
|
||||
specifying explicitly <code class="code">--pkg-config=pkg-config</code> to <code class="command">configure</code>.
|
||||
In that case, you must point <code class="command">pkg-config</code> to the correct directories
|
||||
using the <code class="code">PKG_CONFIG_LIBDIR</code>, as explained in the previous entry.
|
||||
</p>
|
||||
<p>As an intermediate solution, you can place in your cross-compilation
|
||||
environment a script that calls the host <code class="command">pkg-config</code> with
|
||||
<code class="code">PKG_CONFIG_LIBDIR</code> set. That script can look like that:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">#!/bin/sh
|
||||
PKG_CONFIG_LIBDIR=/path/to/cross/lib/pkgconfig
|
||||
export PKG_CONFIG_LIBDIR
|
||||
exec /usr/bin/pkg-config "$@"
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Usage">
|
||||
<h2 class="chapter">3 Usage</h2>
|
||||
|
||||
<ul class="mini-toc">
|
||||
<li><a href="#ffmpeg-does-not-work_003b-what-is-wrong_003f" accesskey="1">ffmpeg does not work; what is wrong?</a></li>
|
||||
<li><a href="#How-do-I-encode-single-pictures-into-movies_003f" accesskey="2">How do I encode single pictures into movies?</a></li>
|
||||
<li><a href="#How-do-I-encode-movie-to-single-pictures_003f" accesskey="3">How do I encode movie to single pictures?</a></li>
|
||||
<li><a href="#Why-do-I-see-a-slight-quality-degradation-with-multithreaded-MPEG_002a-encoding_003f" accesskey="4">Why do I see a slight quality degradation with multithreaded MPEG* encoding?</a></li>
|
||||
<li><a href="#How-can-I-read-from-the-standard-input-or-write-to-the-standard-output_003f" accesskey="5">How can I read from the standard input or write to the standard output?</a></li>
|
||||
<li><a href="#g_t_002df-jpeg-doesn_0027t-work_002e" accesskey="6">-f jpeg doesn’t work.</a></li>
|
||||
<li><a href="#Why-can-I-not-change-the-frame-rate_003f" accesskey="7">Why can I not change the frame rate?</a></li>
|
||||
<li><a href="#How-do-I-encode-Xvid-or-DivX-video-with-ffmpeg_003f" accesskey="8">How do I encode Xvid or DivX video with ffmpeg?</a></li>
|
||||
<li><a href="#Which-are-good-parameters-for-encoding-high-quality-MPEG_002d4_003f" accesskey="9">Which are good parameters for encoding high quality MPEG-4?</a></li>
|
||||
<li><a href="#Which-are-good-parameters-for-encoding-high-quality-MPEG_002d1_002fMPEG_002d2_003f">Which are good parameters for encoding high quality MPEG-1/MPEG-2?</a></li>
|
||||
<li><a href="#Interlaced-video-looks-very-bad-when-encoded-with-ffmpeg_002c-what-is-wrong_003f">Interlaced video looks very bad when encoded with ffmpeg, what is wrong?</a></li>
|
||||
<li><a href="#How-can-I-read-DirectShow-files_003f">How can I read DirectShow files?</a></li>
|
||||
<li><a href="#How-can-I-join-video-files_003f">How can I join video files?</a></li>
|
||||
<li><a href="#How-can-I-concatenate-video-files_003f">How can I concatenate video files?</a></li>
|
||||
<li><a href="#Using-_002df-lavfi_002c-audio-becomes-mono-for-no-apparent-reason_002e">Using <samp class="option">-f lavfi</samp>, audio becomes mono for no apparent reason.</a></li>
|
||||
<li><a href="#Why-does-FFmpeg-not-see-the-subtitles-in-my-VOB-file_003f">Why does FFmpeg not see the subtitles in my VOB file?</a></li>
|
||||
<li><a href="#Why-was-the-ffmpeg-_002dsameq-option-removed_003f-What-to-use-instead_003f">Why was the <code class="command">ffmpeg</code> <samp class="option">-sameq</samp> option removed? What to use instead?</a></li>
|
||||
<li><a href="#I-have-a-stretched-video_002c-why-does-scaling-does-not-fix-it_003f">I have a stretched video, why does scaling does not fix it?</a></li>
|
||||
<li><a href="#How-do-I-run-ffmpeg-as-a-background-task_003f">How do I run ffmpeg as a background task?</a></li>
|
||||
<li><a href="#How-do-I-prevent-ffmpeg-from-suspending-with-a-message-like-suspended-_0028tty-output_0029_003f">How do I prevent ffmpeg from suspending with a message like <em class="emph">suspended (tty output)</em>?</a></li>
|
||||
</ul>
|
||||
<div class="section-level-extent" id="ffmpeg-does-not-work_003b-what-is-wrong_003f">
|
||||
<h3 class="section">3.1 ffmpeg does not work; what is wrong?</h3>
|
||||
|
||||
<p>Try a <code class="code">make distclean</code> in the ffmpeg source directory before the build.
|
||||
If this does not help see
|
||||
(<a class="url" href="https://ffmpeg.org/bugreports.html">https://ffmpeg.org/bugreports.html</a>).
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-do-I-encode-single-pictures-into-movies_003f">
|
||||
<h3 class="section">3.2 How do I encode single pictures into movies?</h3>
|
||||
|
||||
<p>First, rename your pictures to follow a numerical sequence.
|
||||
For example, img1.jpg, img2.jpg, img3.jpg,...
|
||||
Then you may run:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -f image2 -i img%d.jpg /tmp/a.mpg
|
||||
</pre></div>
|
||||
|
||||
<p>Notice that ‘<samp class="samp">%d</samp>’ is replaced by the image number.
|
||||
</p>
|
||||
<p><samp class="file">img%03d.jpg</samp> means the sequence <samp class="file">img001.jpg</samp>, <samp class="file">img002.jpg</samp>, etc.
|
||||
</p>
|
||||
<p>Use the <samp class="option">-start_number</samp> option to declare a starting number for
|
||||
the sequence. This is useful if your sequence does not start with
|
||||
<samp class="file">img001.jpg</samp> but is still in a numerical order. The following
|
||||
example will start with <samp class="file">img100.jpg</samp>:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -f image2 -start_number 100 -i img%d.jpg /tmp/a.mpg
|
||||
</pre></div>
|
||||
|
||||
<p>If you have large number of pictures to rename, you can use the
|
||||
following command to ease the burden. The command, using the bourne
|
||||
shell syntax, symbolically links all files in the current directory
|
||||
that match <code class="code">*jpg</code> to the <samp class="file">/tmp</samp> directory in the sequence of
|
||||
<samp class="file">img001.jpg</samp>, <samp class="file">img002.jpg</samp> and so on.
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">x=1; for i in *jpg; do counter=$(printf %03d $x); ln -s "$i" /tmp/img"$counter".jpg; x=$(($x+1)); done
|
||||
</pre></div>
|
||||
|
||||
<p>If you want to sequence them by oldest modified first, substitute
|
||||
<code class="code">$(ls -r -t *jpg)</code> in place of <code class="code">*jpg</code>.
|
||||
</p>
|
||||
<p>Then run:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -f image2 -i /tmp/img%03d.jpg /tmp/a.mpg
|
||||
</pre></div>
|
||||
|
||||
<p>The same logic is used for any image format that ffmpeg reads.
|
||||
</p>
|
||||
<p>You can also use <code class="command">cat</code> to pipe images to ffmpeg:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">cat *.jpg | ffmpeg -f image2pipe -c:v mjpeg -i - output.mpg
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-do-I-encode-movie-to-single-pictures_003f">
|
||||
<h3 class="section">3.3 How do I encode movie to single pictures?</h3>
|
||||
|
||||
<p>Use:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -i movie.mpg movie%d.jpg
|
||||
</pre></div>
|
||||
|
||||
<p>The <samp class="file">movie.mpg</samp> used as input will be converted to
|
||||
<samp class="file">movie1.jpg</samp>, <samp class="file">movie2.jpg</samp>, etc...
|
||||
</p>
|
||||
<p>Instead of relying on file format self-recognition, you may also use
|
||||
</p><dl class="table">
|
||||
<dt><samp class="option">-c:v ppm</samp></dt>
|
||||
<dt><samp class="option">-c:v png</samp></dt>
|
||||
<dt><samp class="option">-c:v mjpeg</samp></dt>
|
||||
</dl>
|
||||
<p>to force the encoding.
|
||||
</p>
|
||||
<p>Applying that to the previous example:
|
||||
</p><div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -i movie.mpg -f image2 -c:v mjpeg menu%d.jpg
|
||||
</pre></div>
|
||||
|
||||
<p>Beware that there is no "jpeg" codec. Use "mjpeg" instead.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Why-do-I-see-a-slight-quality-degradation-with-multithreaded-MPEG_002a-encoding_003f">
|
||||
<h3 class="section">3.4 Why do I see a slight quality degradation with multithreaded MPEG* encoding?</h3>
|
||||
|
||||
<p>For multithreaded MPEG* encoding, the encoded slices must be independent,
|
||||
otherwise thread n would practically have to wait for n-1 to finish, so it’s
|
||||
quite logical that there is a small reduction of quality. This is not a bug.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-can-I-read-from-the-standard-input-or-write-to-the-standard-output_003f">
|
||||
<h3 class="section">3.5 How can I read from the standard input or write to the standard output?</h3>
|
||||
|
||||
<p>Use <samp class="file">-</samp> as file name.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="g_t_002df-jpeg-doesn_0027t-work_002e">
|
||||
<h3 class="section">3.6 -f jpeg doesn’t work.</h3>
|
||||
|
||||
<p>Try ’-f image2 test%d.jpg’.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Why-can-I-not-change-the-frame-rate_003f">
|
||||
<h3 class="section">3.7 Why can I not change the frame rate?</h3>
|
||||
|
||||
<p>Some codecs, like MPEG-1/2, only allow a small number of fixed frame rates.
|
||||
Choose a different codec with the -c:v command line option.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-do-I-encode-Xvid-or-DivX-video-with-ffmpeg_003f">
|
||||
<h3 class="section">3.8 How do I encode Xvid or DivX video with ffmpeg?</h3>
|
||||
|
||||
<p>Both Xvid and DivX (version 4+) are implementations of the ISO MPEG-4
|
||||
standard (note that there are many other coding formats that use this
|
||||
same standard). Thus, use ’-c:v mpeg4’ to encode in these formats. The
|
||||
default fourcc stored in an MPEG-4-coded file will be ’FMP4’. If you want
|
||||
a different fourcc, use the ’-vtag’ option. E.g., ’-vtag xvid’ will
|
||||
force the fourcc ’xvid’ to be stored as the video fourcc rather than the
|
||||
default.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Which-are-good-parameters-for-encoding-high-quality-MPEG_002d4_003f">
|
||||
<h3 class="section">3.9 Which are good parameters for encoding high quality MPEG-4?</h3>
|
||||
|
||||
<p>’-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -g 300 -pass 1/2’,
|
||||
things to try: ’-bf 2’, ’-mpv_flags qp_rd’, ’-mpv_flags mv0’, ’-mpv_flags skip_rd’.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Which-are-good-parameters-for-encoding-high-quality-MPEG_002d1_002fMPEG_002d2_003f">
|
||||
<h3 class="section">3.10 Which are good parameters for encoding high quality MPEG-1/MPEG-2?</h3>
|
||||
|
||||
<p>’-mbd rd -trellis 2 -cmp 2 -subcmp 2 -g 100 -pass 1/2’
|
||||
but beware the ’-g 100’ might cause problems with some decoders.
|
||||
Things to try: ’-bf 2’, ’-mpv_flags qp_rd’, ’-mpv_flags mv0’, ’-mpv_flags skip_rd’.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Interlaced-video-looks-very-bad-when-encoded-with-ffmpeg_002c-what-is-wrong_003f">
|
||||
<h3 class="section">3.11 Interlaced video looks very bad when encoded with ffmpeg, what is wrong?</h3>
|
||||
|
||||
<p>You should use ’-flags +ilme+ildct’ and maybe ’-flags +alt’ for interlaced
|
||||
material, and try ’-top 0/1’ if the result looks really messed-up.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-can-I-read-DirectShow-files_003f">
|
||||
<h3 class="section">3.12 How can I read DirectShow files?</h3>
|
||||
|
||||
<p>If you have built FFmpeg with <code class="code">./configure --enable-avisynth</code>
|
||||
(only possible on MinGW/Cygwin platforms),
|
||||
then you may use any file that DirectShow can read as input.
|
||||
</p>
|
||||
<p>Just create an "input.avs" text file with this single line ...
|
||||
</p><div class="example">
|
||||
<pre class="example-preformatted">DirectShowSource("C:\path to your file\yourfile.asf")
|
||||
</pre></div>
|
||||
<p>... and then feed that text file to ffmpeg:
|
||||
</p><div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -i input.avs
|
||||
</pre></div>
|
||||
|
||||
<p>For ANY other help on AviSynth, please visit the
|
||||
<a class="uref" href="http://www.avisynth.org/">AviSynth homepage</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-can-I-join-video-files_003f">
|
||||
<h3 class="section">3.13 How can I join video files?</h3>
|
||||
|
||||
<p>To "join" video files is quite ambiguous. The following list explains the
|
||||
different kinds of "joining" and points out how those are addressed in
|
||||
FFmpeg. To join video files may mean:
|
||||
</p>
|
||||
<ul class="itemize mark-bullet">
|
||||
<li>To put them one after the other: this is called to <em class="emph">concatenate</em> them
|
||||
(in short: concat) and is addressed
|
||||
<a class="ref" href="#How-can-I-concatenate-video-files">in this very faq</a>.
|
||||
|
||||
</li><li>To put them together in the same file, to let the user choose between the
|
||||
different versions (example: different audio languages): this is called to
|
||||
<em class="emph">multiplex</em> them together (in short: mux), and is done by simply
|
||||
invoking ffmpeg with several <samp class="option">-i</samp> options.
|
||||
|
||||
</li><li>For audio, to put all channels together in a single stream (example: two
|
||||
mono streams into one stereo stream): this is sometimes called to
|
||||
<em class="emph">merge</em> them, and can be done using the
|
||||
<a class="url" href="ffmpeg-filters.html#amerge"><code class="code">amerge</code></a> filter.
|
||||
|
||||
</li><li>For audio, to play one on top of the other: this is called to <em class="emph">mix</em>
|
||||
them, and can be done by first merging them into a single stream and then
|
||||
using the <a class="url" href="ffmpeg-filters.html#pan"><code class="code">pan</code></a> filter to mix
|
||||
the channels at will.
|
||||
|
||||
</li><li>For video, to display both together, side by side or one on top of a part of
|
||||
the other; it can be done using the
|
||||
<a class="url" href="ffmpeg-filters.html#overlay"><code class="code">overlay</code></a> video filter.
|
||||
|
||||
</li></ul>
|
||||
|
||||
<a class="anchor" id="How-can-I-concatenate-video-files"></a></div>
|
||||
<div class="section-level-extent" id="How-can-I-concatenate-video-files_003f">
|
||||
<h3 class="section">3.14 How can I concatenate video files?</h3>
|
||||
|
||||
<p>There are several solutions, depending on the exact circumstances.
|
||||
</p>
|
||||
<ul class="mini-toc">
|
||||
<li><a href="#Concatenating-using-the-concat-filter" accesskey="1">Concatenating using the concat <em class="emph">filter</em></a></li>
|
||||
<li><a href="#Concatenating-using-the-concat-demuxer" accesskey="2">Concatenating using the concat <em class="emph">demuxer</em></a></li>
|
||||
<li><a href="#Concatenating-using-the-concat-protocol-_0028file-level_0029" accesskey="3">Concatenating using the concat <em class="emph">protocol</em> (file level)</a></li>
|
||||
<li><a href="#Concatenating-using-raw-audio-and-video" accesskey="4">Concatenating using raw audio and video</a></li>
|
||||
</ul>
|
||||
<div class="subsection-level-extent" id="Concatenating-using-the-concat-filter">
|
||||
<h4 class="subsection">3.14.1 Concatenating using the concat <em class="emph">filter</em></h4>
|
||||
|
||||
<p>FFmpeg has a <a class="url" href="ffmpeg-filters.html#concat"><code class="code">concat</code></a> filter designed specifically for that, with examples in the
|
||||
documentation. This operation is recommended if you need to re-encode.
|
||||
</p>
|
||||
</div>
|
||||
<div class="subsection-level-extent" id="Concatenating-using-the-concat-demuxer">
|
||||
<h4 class="subsection">3.14.2 Concatenating using the concat <em class="emph">demuxer</em></h4>
|
||||
|
||||
<p>FFmpeg has a <a class="url" href="ffmpeg-formats.html#concat"><code class="code">concat</code></a> demuxer which you can use when you want to avoid a re-encode and
|
||||
your format doesn’t support file level concatenation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="subsection-level-extent" id="Concatenating-using-the-concat-protocol-_0028file-level_0029">
|
||||
<h4 class="subsection">3.14.3 Concatenating using the concat <em class="emph">protocol</em> (file level)</h4>
|
||||
|
||||
<p>FFmpeg has a <a class="url" href="ffmpeg-protocols.html#concat"><code class="code">concat</code></a> protocol designed specifically for that, with examples in the
|
||||
documentation.
|
||||
</p>
|
||||
<p>A few multimedia containers (MPEG-1, MPEG-2 PS, DV) allow one to concatenate
|
||||
video by merely concatenating the files containing them.
|
||||
</p>
|
||||
<p>Hence you may concatenate your multimedia files by first transcoding them to
|
||||
these privileged formats, then using the humble <code class="code">cat</code> command (or the
|
||||
equally humble <code class="code">copy</code> under Windows), and finally transcoding back to your
|
||||
format of choice.
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -i input1.avi -qscale:v 1 intermediate1.mpg
|
||||
ffmpeg -i input2.avi -qscale:v 1 intermediate2.mpg
|
||||
cat intermediate1.mpg intermediate2.mpg > intermediate_all.mpg
|
||||
ffmpeg -i intermediate_all.mpg -qscale:v 2 output.avi
|
||||
</pre></div>
|
||||
|
||||
<p>Additionally, you can use the <code class="code">concat</code> protocol instead of <code class="code">cat</code> or
|
||||
<code class="code">copy</code> which will avoid creation of a potentially huge intermediate file.
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -i input1.avi -qscale:v 1 intermediate1.mpg
|
||||
ffmpeg -i input2.avi -qscale:v 1 intermediate2.mpg
|
||||
ffmpeg -i concat:"intermediate1.mpg|intermediate2.mpg" -c copy intermediate_all.mpg
|
||||
ffmpeg -i intermediate_all.mpg -qscale:v 2 output.avi
|
||||
</pre></div>
|
||||
|
||||
<p>Note that you may need to escape the character "|" which is special for many
|
||||
shells.
|
||||
</p>
|
||||
<p>Another option is usage of named pipes, should your platform support it:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">mkfifo intermediate1.mpg
|
||||
mkfifo intermediate2.mpg
|
||||
ffmpeg -i input1.avi -qscale:v 1 -y intermediate1.mpg < /dev/null &
|
||||
ffmpeg -i input2.avi -qscale:v 1 -y intermediate2.mpg < /dev/null &
|
||||
cat intermediate1.mpg intermediate2.mpg |\
|
||||
ffmpeg -f mpeg -i - -c:v mpeg4 -c:a libmp3lame output.avi
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
<div class="subsection-level-extent" id="Concatenating-using-raw-audio-and-video">
|
||||
<h4 class="subsection">3.14.4 Concatenating using raw audio and video</h4>
|
||||
|
||||
<p>Similarly, the yuv4mpegpipe format, and the raw video, raw audio codecs also
|
||||
allow concatenation, and the transcoding step is almost lossless.
|
||||
When using multiple yuv4mpegpipe(s), the first line needs to be discarded
|
||||
from all but the first stream. This can be accomplished by piping through
|
||||
<code class="code">tail</code> as seen below. Note that when piping through <code class="code">tail</code> you
|
||||
must use command grouping, <code class="code">{ ;}</code>, to background properly.
|
||||
</p>
|
||||
<p>For example, let’s say we want to concatenate two FLV files into an
|
||||
output.flv file:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">mkfifo temp1.a
|
||||
mkfifo temp1.v
|
||||
mkfifo temp2.a
|
||||
mkfifo temp2.v
|
||||
mkfifo all.a
|
||||
mkfifo all.v
|
||||
ffmpeg -i input1.flv -vn -f u16le -c:a pcm_s16le -ac 2 -ar 44100 - > temp1.a < /dev/null &
|
||||
ffmpeg -i input2.flv -vn -f u16le -c:a pcm_s16le -ac 2 -ar 44100 - > temp2.a < /dev/null &
|
||||
ffmpeg -i input1.flv -an -f yuv4mpegpipe - > temp1.v < /dev/null &
|
||||
{ ffmpeg -i input2.flv -an -f yuv4mpegpipe - < /dev/null | tail -n +2 > temp2.v ; } &
|
||||
cat temp1.a temp2.a > all.a &
|
||||
cat temp1.v temp2.v > all.v &
|
||||
ffmpeg -f u16le -c:a pcm_s16le -ac 2 -ar 44100 -i all.a \
|
||||
-f yuv4mpegpipe -i all.v \
|
||||
-y output.flv
|
||||
rm temp[12].[av] all.[av]
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Using-_002df-lavfi_002c-audio-becomes-mono-for-no-apparent-reason_002e">
|
||||
<h3 class="section">3.15 Using <samp class="option">-f lavfi</samp>, audio becomes mono for no apparent reason.</h3>
|
||||
|
||||
<p>Use <samp class="option">-dumpgraph -</samp> to find out exactly where the channel layout is
|
||||
lost.
|
||||
</p>
|
||||
<p>Most likely, it is through <code class="code">auto-inserted aresample</code>. Try to understand
|
||||
why the converting filter was needed at that place.
|
||||
</p>
|
||||
<p>Just before the output is a likely place, as <samp class="option">-f lavfi</samp> currently
|
||||
only support packed S16.
|
||||
</p>
|
||||
<p>Then insert the correct <code class="code">aformat</code> explicitly in the filtergraph,
|
||||
specifying the exact format.
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">aformat=sample_fmts=s16:channel_layouts=stereo
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
<div class="section-level-extent" id="Why-does-FFmpeg-not-see-the-subtitles-in-my-VOB-file_003f">
|
||||
<h3 class="section">3.16 Why does FFmpeg not see the subtitles in my VOB file?</h3>
|
||||
|
||||
<p>VOB and a few other formats do not have a global header that describes
|
||||
everything present in the file. Instead, applications are supposed to scan
|
||||
the file to see what it contains. Since VOB files are frequently large, only
|
||||
the beginning is scanned. If the subtitles happen only later in the file,
|
||||
they will not be initially detected.
|
||||
</p>
|
||||
<p>Some applications, including the <code class="code">ffmpeg</code> command-line tool, can only
|
||||
work with streams that were detected during the initial scan; streams that
|
||||
are detected later are ignored.
|
||||
</p>
|
||||
<p>The size of the initial scan is controlled by two options: <code class="code">probesize</code>
|
||||
(default ~5 Mo) and <code class="code">analyzeduration</code> (default 5,000,000 µs = 5 s). For
|
||||
the subtitle stream to be detected, both values must be large enough.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Why-was-the-ffmpeg-_002dsameq-option-removed_003f-What-to-use-instead_003f">
|
||||
<h3 class="section">3.17 Why was the <code class="command">ffmpeg</code> <samp class="option">-sameq</samp> option removed? What to use instead?</h3>
|
||||
|
||||
<p>The <samp class="option">-sameq</samp> option meant "same quantizer", and made sense only in a
|
||||
very limited set of cases. Unfortunately, a lot of people mistook it for
|
||||
"same quality" and used it in places where it did not make sense: it had
|
||||
roughly the expected visible effect, but achieved it in a very inefficient
|
||||
way.
|
||||
</p>
|
||||
<p>Each encoder has its own set of options to set the quality-vs-size balance,
|
||||
use the options for the encoder you are using to set the quality level to a
|
||||
point acceptable for your tastes. The most common options to do that are
|
||||
<samp class="option">-qscale</samp> and <samp class="option">-qmax</samp>, but you should peruse the documentation
|
||||
of the encoder you chose.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="I-have-a-stretched-video_002c-why-does-scaling-does-not-fix-it_003f">
|
||||
<h3 class="section">3.18 I have a stretched video, why does scaling does not fix it?</h3>
|
||||
|
||||
<p>A lot of video codecs and formats can store the <em class="emph">aspect ratio</em> of the
|
||||
video: this is the ratio between the width and the height of either the full
|
||||
image (DAR, display aspect ratio) or individual pixels (SAR, sample aspect
|
||||
ratio). For example, EGA screens at resolution 640×350 had 4:3 DAR and 35:48
|
||||
SAR.
|
||||
</p>
|
||||
<p>Most still image processing work with square pixels, i.e. 1:1 SAR, but a lot
|
||||
of video standards, especially from the analogic-numeric transition era, use
|
||||
non-square pixels.
|
||||
</p>
|
||||
<p>Most processing filters in FFmpeg handle the aspect ratio to avoid
|
||||
stretching the image: cropping adjusts the DAR to keep the SAR constant,
|
||||
scaling adjusts the SAR to keep the DAR constant.
|
||||
</p>
|
||||
<p>If you want to stretch, or “unstretch”, the image, you need to override the
|
||||
information with the
|
||||
<a class="url" href="ffmpeg-filters.html#setdar_002c-setsar"><code class="code">setdar or setsar filters</code></a>.
|
||||
</p>
|
||||
<p>Do not forget to examine carefully the original video to check whether the
|
||||
stretching comes from the image or from the aspect ratio information.
|
||||
</p>
|
||||
<p>For example, to fix a badly encoded EGA capture, use the following commands,
|
||||
either the first one to upscale to square pixels or the second one to set
|
||||
the correct aspect ratio or the third one to avoid transcoding (may not work
|
||||
depending on the format / codec / player / phase of the moon):
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -i ega_screen.nut -vf scale=640:480,setsar=1 ega_screen_scaled.nut
|
||||
ffmpeg -i ega_screen.nut -vf setdar=4/3 ega_screen_anamorphic.nut
|
||||
ffmpeg -i ega_screen.nut -aspect 4/3 -c copy ega_screen_overridden.nut
|
||||
</pre></div>
|
||||
|
||||
<a class="anchor" id="background-task"></a></div>
|
||||
<div class="section-level-extent" id="How-do-I-run-ffmpeg-as-a-background-task_003f">
|
||||
<h3 class="section">3.19 How do I run ffmpeg as a background task?</h3>
|
||||
|
||||
<p>ffmpeg normally checks the console input, for entries like "q" to stop
|
||||
and "?" to give help, while performing operations. ffmpeg does not have a way of
|
||||
detecting when it is running as a background task.
|
||||
When it checks the console input, that can cause the process running ffmpeg
|
||||
in the background to suspend.
|
||||
</p>
|
||||
<p>To prevent those input checks, allowing ffmpeg to run as a background task,
|
||||
use the <a class="url" href="ffmpeg.html#stdin-option"><code class="code">-nostdin</code> option</a>
|
||||
in the ffmpeg invocation. This is effective whether you run ffmpeg in a shell
|
||||
or invoke ffmpeg in its own process via an operating system API.
|
||||
</p>
|
||||
<p>As an alternative, when you are running ffmpeg in a shell, you can redirect
|
||||
standard input to <code class="code">/dev/null</code> (on Linux and macOS)
|
||||
or <code class="code">NUL</code> (on Windows). You can do this redirect either
|
||||
on the ffmpeg invocation, or from a shell script which calls ffmpeg.
|
||||
</p>
|
||||
<p>For example:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -nostdin -i INPUT OUTPUT
|
||||
</pre></div>
|
||||
|
||||
<p>or (on Linux, macOS, and other UNIX-like shells):
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -i INPUT OUTPUT </dev/null
|
||||
</pre></div>
|
||||
|
||||
<p>or (on Windows):
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">ffmpeg -i INPUT OUTPUT <NUL
|
||||
</pre></div>
|
||||
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-do-I-prevent-ffmpeg-from-suspending-with-a-message-like-suspended-_0028tty-output_0029_003f">
|
||||
<h3 class="section">3.20 How do I prevent ffmpeg from suspending with a message like <em class="emph">suspended (tty output)</em>?</h3>
|
||||
|
||||
<p>If you run ffmpeg in the background, you may find that its process suspends.
|
||||
There may be a message like <em class="emph">suspended (tty output)</em>. The question is how
|
||||
to prevent the process from being suspended.
|
||||
</p>
|
||||
<p>For example:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">% ffmpeg -i INPUT OUTPUT &> ~/tmp/log.txt &
|
||||
[1] 93352
|
||||
%
|
||||
[1] + suspended (tty output) ffmpeg -i INPUT OUTPUT &>
|
||||
</pre></div>
|
||||
|
||||
<p>The message "tty output" notwithstanding, the problem here is that
|
||||
ffmpeg normally checks the console input when it runs. The operating system
|
||||
detects this, and suspends the process until you can bring it to the
|
||||
foreground and attend to it.
|
||||
</p>
|
||||
<p>The solution is to use the right techniques to tell ffmpeg not to consult
|
||||
console input. You can use the
|
||||
<a class="url" href="ffmpeg.html#stdin-option"><code class="code">-nostdin</code> option</a>,
|
||||
or redirect standard input with <code class="code">< /dev/null</code>.
|
||||
See FAQ
|
||||
<a class="ref" href="#background-task"><em class="emph">How do I run ffmpeg as a background task?</em></a>
|
||||
for details.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Development">
|
||||
<h2 class="chapter">4 Development</h2>
|
||||
|
||||
<ul class="mini-toc">
|
||||
<li><a href="#Are-there-examples-illustrating-how-to-use-the-FFmpeg-libraries_002c-particularly-libavcodec-and-libavformat_003f" accesskey="1">Are there examples illustrating how to use the FFmpeg libraries, particularly libavcodec and libavformat?</a></li>
|
||||
<li><a href="#Can-you-support-my-C-compiler-XXX_003f" accesskey="2">Can you support my C compiler XXX?</a></li>
|
||||
<li><a href="#Is-Microsoft-Visual-C_002b_002b-supported_003f" accesskey="3">Is Microsoft Visual C++ supported?</a></li>
|
||||
<li><a href="#Can-you-add-automake_002c-libtool-or-autoconf-support_003f" accesskey="4">Can you add automake, libtool or autoconf support?</a></li>
|
||||
<li><a href="#Why-not-rewrite-FFmpeg-in-object_002doriented-C_002b_002b_003f" accesskey="5">Why not rewrite FFmpeg in object-oriented C++?</a></li>
|
||||
<li><a href="#Why-are-the-ffmpeg-programs-devoid-of-debugging-symbols_003f" accesskey="6">Why are the ffmpeg programs devoid of debugging symbols?</a></li>
|
||||
<li><a href="#I-do-not-like-the-LGPL_002c-can-I-contribute-code-under-the-GPL-instead_003f" accesskey="7">I do not like the LGPL, can I contribute code under the GPL instead?</a></li>
|
||||
<li><a href="#I_0027m-using-FFmpeg-from-within-my-C-application-but-the-linker-complains-about-missing-symbols-from-the-libraries-themselves_002e" accesskey="8">I’m using FFmpeg from within my C application but the linker complains about missing symbols from the libraries themselves.</a></li>
|
||||
<li><a href="#I_0027m-using-FFmpeg-from-within-my-C_002b_002b-application-but-the-linker-complains-about-missing-symbols-which-seem-to-be-available_002e" accesskey="9">I’m using FFmpeg from within my C++ application but the linker complains about missing symbols which seem to be available.</a></li>
|
||||
<li><a href="#I_0027m-using-libavutil-from-within-my-C_002b_002b-application-but-the-compiler-complains-about-_0027UINT64_005fC_0027-was-not-declared-in-this-scope">I’m using libavutil from within my C++ application but the compiler complains about ’UINT64_C’ was not declared in this scope</a></li>
|
||||
<li><a href="#I-have-a-file-in-memory-_002f-a-API-different-from-_002aopen_002f_002aread_002f-libc-how-do-I-use-it-with-libavformat_003f">I have a file in memory / a API different from *open/*read/ libc how do I use it with libavformat?</a></li>
|
||||
<li><a href="#Where-is-the-documentation-about-ffv1_002c-msmpeg4_002c-asv1_002c-4xm_003f">Where is the documentation about ffv1, msmpeg4, asv1, 4xm?</a></li>
|
||||
<li><a href="#How-do-I-feed-H_002e263_002dRTP-_0028and-other-codecs-in-RTP_0029-to-libavcodec_003f">How do I feed H.263-RTP (and other codecs in RTP) to libavcodec?</a></li>
|
||||
<li><a href="#AVStream_002er_005fframe_005frate-is-wrong_002c-it-is-much-larger-than-the-frame-rate_002e">AVStream.r_frame_rate is wrong, it is much larger than the frame rate.</a></li>
|
||||
<li><a href="#Why-is-make-fate-not-running-all-tests_003f">Why is <code class="code">make fate</code> not running all tests?</a></li>
|
||||
<li><a href="#Why-is-make-fate-not-finding-the-samples_003f">Why is <code class="code">make fate</code> not finding the samples?</a></li>
|
||||
</ul>
|
||||
<div class="section-level-extent" id="Are-there-examples-illustrating-how-to-use-the-FFmpeg-libraries_002c-particularly-libavcodec-and-libavformat_003f">
|
||||
<h3 class="section">4.1 Are there examples illustrating how to use the FFmpeg libraries, particularly libavcodec and libavformat?</h3>
|
||||
|
||||
<p>Yes. Check the <samp class="file">doc/examples</samp> directory in the source
|
||||
repository, also available online at:
|
||||
<a class="url" href="https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples">https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples</a>.
|
||||
</p>
|
||||
<p>Examples are also installed by default, usually in
|
||||
<code class="code">$PREFIX/share/ffmpeg/examples</code>.
|
||||
</p>
|
||||
<p>Also you may read the Developers Guide of the FFmpeg documentation. Alternatively,
|
||||
examine the source code for one of the many open source projects that
|
||||
already incorporate FFmpeg at (<a class="url" href="projects.html">projects.html</a>).
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Can-you-support-my-C-compiler-XXX_003f">
|
||||
<h3 class="section">4.2 Can you support my C compiler XXX?</h3>
|
||||
|
||||
<p>It depends. If your compiler is C99-compliant, then patches to support
|
||||
it are likely to be welcome if they do not pollute the source code
|
||||
with <code class="code">#ifdef</code>s related to the compiler.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Is-Microsoft-Visual-C_002b_002b-supported_003f">
|
||||
<h3 class="section">4.3 Is Microsoft Visual C++ supported?</h3>
|
||||
|
||||
<p>Yes. Please see the <a class="uref" href="platform.html">Microsoft Visual C++</a>
|
||||
section in the FFmpeg documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Can-you-add-automake_002c-libtool-or-autoconf-support_003f">
|
||||
<h3 class="section">4.4 Can you add automake, libtool or autoconf support?</h3>
|
||||
|
||||
<p>No. These tools are too bloated and they complicate the build.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Why-not-rewrite-FFmpeg-in-object_002doriented-C_002b_002b_003f">
|
||||
<h3 class="section">4.5 Why not rewrite FFmpeg in object-oriented C++?</h3>
|
||||
|
||||
<p>FFmpeg is already organized in a highly modular manner and does not need to
|
||||
be rewritten in a formal object language. Further, many of the developers
|
||||
favor straight C; it works for them. For more arguments on this matter,
|
||||
read <a class="uref" href="https://web.archive.org/web/20111004021423/http://kernel.org/pub/linux/docs/lkml/#s15">"Programming Religion"</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Why-are-the-ffmpeg-programs-devoid-of-debugging-symbols_003f">
|
||||
<h3 class="section">4.6 Why are the ffmpeg programs devoid of debugging symbols?</h3>
|
||||
|
||||
<p>The build process creates <code class="command">ffmpeg_g</code>, <code class="command">ffplay_g</code>, etc. which
|
||||
contain full debug information. Those binaries are stripped to create
|
||||
<code class="command">ffmpeg</code>, <code class="command">ffplay</code>, etc. If you need the debug information, use
|
||||
the *_g versions.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="I-do-not-like-the-LGPL_002c-can-I-contribute-code-under-the-GPL-instead_003f">
|
||||
<h3 class="section">4.7 I do not like the LGPL, can I contribute code under the GPL instead?</h3>
|
||||
|
||||
<p>Yes, as long as the code is optional and can easily and cleanly be placed
|
||||
under #if CONFIG_GPL without breaking anything. So, for example, a new codec
|
||||
or filter would be OK under GPL while a bug fix to LGPL code would not.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="I_0027m-using-FFmpeg-from-within-my-C-application-but-the-linker-complains-about-missing-symbols-from-the-libraries-themselves_002e">
|
||||
<h3 class="section">4.8 I’m using FFmpeg from within my C application but the linker complains about missing symbols from the libraries themselves.</h3>
|
||||
|
||||
<p>FFmpeg builds static libraries by default. In static libraries, dependencies
|
||||
are not handled. That has two consequences. First, you must specify the
|
||||
libraries in dependency order: <code class="code">-lavdevice</code> must come before
|
||||
<code class="code">-lavformat</code>, <code class="code">-lavutil</code> must come after everything else, etc.
|
||||
Second, external libraries that are used in FFmpeg have to be specified too.
|
||||
</p>
|
||||
<p>An easy way to get the full list of required libraries in dependency order
|
||||
is to use <code class="code">pkg-config</code>.
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">c99 -o program program.c $(pkg-config --cflags --libs libavformat libavcodec)
|
||||
</pre></div>
|
||||
|
||||
<p>See <samp class="file">doc/example/Makefile</samp> and <samp class="file">doc/example/pc-uninstalled</samp> for
|
||||
more details.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="I_0027m-using-FFmpeg-from-within-my-C_002b_002b-application-but-the-linker-complains-about-missing-symbols-which-seem-to-be-available_002e">
|
||||
<h3 class="section">4.9 I’m using FFmpeg from within my C++ application but the linker complains about missing symbols which seem to be available.</h3>
|
||||
|
||||
<p>FFmpeg is a pure C project, so to use the libraries within your C++ application
|
||||
you need to explicitly state that you are using a C library. You can do this by
|
||||
encompassing your FFmpeg includes using <code class="code">extern "C"</code>.
|
||||
</p>
|
||||
<p>See <a class="url" href="http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html#faq-32.3">http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html#faq-32.3</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="I_0027m-using-libavutil-from-within-my-C_002b_002b-application-but-the-compiler-complains-about-_0027UINT64_005fC_0027-was-not-declared-in-this-scope">
|
||||
<h3 class="section">4.10 I’m using libavutil from within my C++ application but the compiler complains about ’UINT64_C’ was not declared in this scope</h3>
|
||||
|
||||
<p>FFmpeg is a pure C project using C99 math features, in order to enable C++
|
||||
to use them you have to append -D__STDC_CONSTANT_MACROS to your CXXFLAGS
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="I-have-a-file-in-memory-_002f-a-API-different-from-_002aopen_002f_002aread_002f-libc-how-do-I-use-it-with-libavformat_003f">
|
||||
<h3 class="section">4.11 I have a file in memory / a API different from *open/*read/ libc how do I use it with libavformat?</h3>
|
||||
|
||||
<p>You have to create a custom AVIOContext using <code class="code">avio_alloc_context</code>,
|
||||
see <samp class="file">libavformat/aviobuf.c</samp> in FFmpeg and <samp class="file">libmpdemux/demux_lavf.c</samp> in MPlayer or MPlayer2 sources.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Where-is-the-documentation-about-ffv1_002c-msmpeg4_002c-asv1_002c-4xm_003f">
|
||||
<h3 class="section">4.12 Where is the documentation about ffv1, msmpeg4, asv1, 4xm?</h3>
|
||||
|
||||
<p>see <a class="url" href="https://www.ffmpeg.org/~michael/">https://www.ffmpeg.org/~michael/</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="How-do-I-feed-H_002e263_002dRTP-_0028and-other-codecs-in-RTP_0029-to-libavcodec_003f">
|
||||
<h3 class="section">4.13 How do I feed H.263-RTP (and other codecs in RTP) to libavcodec?</h3>
|
||||
|
||||
<p>Even if peculiar since it is network oriented, RTP is a container like any
|
||||
other. You have to <em class="emph">demux</em> RTP before feeding the payload to libavcodec.
|
||||
In this specific case please look at RFC 4629 to see how it should be done.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="AVStream_002er_005fframe_005frate-is-wrong_002c-it-is-much-larger-than-the-frame-rate_002e">
|
||||
<h3 class="section">4.14 AVStream.r_frame_rate is wrong, it is much larger than the frame rate.</h3>
|
||||
|
||||
<p><code class="code">r_frame_rate</code> is NOT the average frame rate, it is the smallest frame rate
|
||||
that can accurately represent all timestamps. So no, it is not
|
||||
wrong if it is larger than the average!
|
||||
For example, if you have mixed 25 and 30 fps content, then <code class="code">r_frame_rate</code>
|
||||
will be 150 (it is the least common multiple).
|
||||
If you are looking for the average frame rate, see <code class="code">AVStream.avg_frame_rate</code>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Why-is-make-fate-not-running-all-tests_003f">
|
||||
<h3 class="section">4.15 Why is <code class="code">make fate</code> not running all tests?</h3>
|
||||
|
||||
<p>Make sure you have the fate-suite samples and the <code class="code">SAMPLES</code> Make variable
|
||||
or <code class="code">FATE_SAMPLES</code> environment variable or the <code class="code">--samples</code>
|
||||
<code class="command">configure</code> option is set to the right path.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-level-extent" id="Why-is-make-fate-not-finding-the-samples_003f">
|
||||
<h3 class="section">4.16 Why is <code class="code">make fate</code> not finding the samples?</h3>
|
||||
|
||||
<p>Do you happen to have a <code class="code">~</code> character in the samples path to indicate a
|
||||
home directory? The value is used in ways where the shell cannot expand it,
|
||||
causing FATE to not find files. Just replace <code class="code">~</code> by the full path.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,367 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- Created by GNU Texinfo 7.0.1, https://www.gnu.org/software/texinfo/ -->
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>FFmpeg Automated Testing Environment</title>
|
||||
|
||||
<meta name="description" content="FFmpeg Automated Testing Environment">
|
||||
<meta name="keywords" content="FFmpeg Automated Testing Environment">
|
||||
<meta name="resource-type" content="document">
|
||||
<meta name="distribution" content="global">
|
||||
<meta name="Generator" content="makeinfo">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
<link href="#Top" rel="start" title="Top">
|
||||
<link href="#SEC_Contents" rel="contents" title="Table of Contents">
|
||||
<style type="text/css">
|
||||
<!--
|
||||
div.example {margin-left: 3.2em}
|
||||
ul.mark-bullet {list-style-type: disc}
|
||||
ul.toc-numbered-mark {list-style: none}
|
||||
-->
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body lang="en">
|
||||
|
||||
|
||||
<div class="top-level-extent" id="Top">
|
||||
<a class="top" id="SEC_Top"></a>
|
||||
|
||||
<div class="element-contents" id="SEC_Contents">
|
||||
<h2 class="contents-heading">Table of Contents</h2>
|
||||
|
||||
<div class="contents">
|
||||
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Introduction" href="#Introduction">1 Introduction</a></li>
|
||||
<li><a id="toc-Using-FATE-from-your-FFmpeg-source-directory" href="#Using-FATE-from-your-FFmpeg-source-directory">2 Using FATE from your FFmpeg source directory</a></li>
|
||||
<li><a id="toc-Submitting-the-results-to-the-FFmpeg-result-aggregation-server" href="#Submitting-the-results-to-the-FFmpeg-result-aggregation-server">3 Submitting the results to the FFmpeg result aggregation server</a></li>
|
||||
<li><a id="toc-Uploading-new-samples-to-the-fate-suite" href="#Uploading-new-samples-to-the-fate-suite">4 Uploading new samples to the fate suite</a></li>
|
||||
<li><a id="toc-FATE-makefile-targets-and-variables" href="#FATE-makefile-targets-and-variables">5 FATE makefile targets and variables</a>
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Makefile-targets" href="#Makefile-targets">5.1 Makefile targets</a></li>
|
||||
<li><a id="toc-Makefile-variables" href="#Makefile-variables">5.2 Makefile variables</a></li>
|
||||
<li><a id="toc-Examples" href="#Examples">5.3 Examples</a></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Introduction">
|
||||
<h2 class="chapter">1 Introduction</h2>
|
||||
|
||||
<p>FATE is an extended regression suite on the client-side and a means
|
||||
for results aggregation and presentation on the server-side.
|
||||
</p>
|
||||
<p>The first part of this document explains how you can use FATE from
|
||||
your FFmpeg source directory to test your ffmpeg binary. The second
|
||||
part describes how you can run FATE to submit the results to FFmpeg’s
|
||||
FATE server.
|
||||
</p>
|
||||
<p>In any way you can have a look at the publicly viewable FATE results
|
||||
by visiting this website:
|
||||
</p>
|
||||
<p><a class="url" href="http://fate.ffmpeg.org/">http://fate.ffmpeg.org/</a>
|
||||
</p>
|
||||
<p>This is especially recommended for all people contributing source
|
||||
code to FFmpeg, as it can be seen if some test on some platform broke
|
||||
with their recent contribution. This usually happens on the platforms
|
||||
the developers could not test on.
|
||||
</p>
|
||||
<p>The second part of this document describes how you can run FATE to
|
||||
submit your results to FFmpeg’s FATE server. If you want to submit your
|
||||
results be sure to check that your combination of CPU, OS and compiler
|
||||
is not already listed on the above mentioned website.
|
||||
</p>
|
||||
<p>In the third part you can find a comprehensive listing of FATE makefile
|
||||
targets and variables.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Using-FATE-from-your-FFmpeg-source-directory">
|
||||
<h2 class="chapter">2 Using FATE from your FFmpeg source directory</h2>
|
||||
|
||||
<p>If you want to run FATE on your machine you need to have the samples
|
||||
in place. You can get the samples via the build target fate-rsync.
|
||||
Use this command from the top-level source directory:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">make fate-rsync SAMPLES=fate-suite/
|
||||
make fate SAMPLES=fate-suite/
|
||||
</pre></div>
|
||||
|
||||
<p>The above commands set the samples location by passing a makefile
|
||||
variable via command line. It is also possible to set the samples
|
||||
location at source configuration time by invoking configure with
|
||||
<samp class="option">--samples=<path to the samples directory></samp>. Afterwards you can
|
||||
invoke the makefile targets without setting the <var class="var">SAMPLES</var> makefile
|
||||
variable. This is illustrated by the following commands:
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">./configure --samples=fate-suite/
|
||||
make fate-rsync
|
||||
make fate
|
||||
</pre></div>
|
||||
|
||||
<p>Yet another way to tell FATE about the location of the sample
|
||||
directory is by making sure the environment variable FATE_SAMPLES
|
||||
contains the path to your samples directory. This can be achieved
|
||||
by e.g. putting that variable in your shell profile or by setting
|
||||
it in your interactive session.
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">FATE_SAMPLES=fate-suite/ make fate
|
||||
</pre></div>
|
||||
|
||||
<div class="float">
|
||||
<p>Do not put a ’~’ character in the samples path to indicate a home
|
||||
directory. Because of shell nuances, this will cause FATE to fail.
|
||||
</p><div class="type-number-float"><p><strong class="strong">NOTE
|
||||
</strong></p></div></div>
|
||||
<p>To get the complete list of tests, run the command:
|
||||
</p><div class="example">
|
||||
<pre class="example-preformatted">make fate-list
|
||||
</pre></div>
|
||||
|
||||
<p>You can specify a subset of tests to run by specifying the
|
||||
corresponding elements from the list with the <code class="code">fate-</code> prefix,
|
||||
e.g. as in:
|
||||
</p><div class="example">
|
||||
<pre class="example-preformatted">make fate-ffprobe_compact fate-ffprobe_xml
|
||||
</pre></div>
|
||||
|
||||
<p>This makes it easier to run a few tests in case of failure without
|
||||
running the complete test suite.
|
||||
</p>
|
||||
<p>To use a custom wrapper to run the test, pass <samp class="option">--target-exec</samp> to
|
||||
<code class="command">configure</code> or set the <var class="var">TARGET_EXEC</var> Make variable.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Submitting-the-results-to-the-FFmpeg-result-aggregation-server">
|
||||
<h2 class="chapter">3 Submitting the results to the FFmpeg result aggregation server</h2>
|
||||
|
||||
<p>To submit your results to the server you should run fate through the
|
||||
shell script <samp class="file">tests/fate.sh</samp> from the FFmpeg sources. This script needs
|
||||
to be invoked with a configuration file as its first argument.
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">tests/fate.sh /path/to/fate_config
|
||||
</pre></div>
|
||||
|
||||
<p>A configuration file template with comments describing the individual
|
||||
configuration variables can be found at <samp class="file">doc/fate_config.sh.template</samp>.
|
||||
</p>
|
||||
<p>The mentioned configuration template is also available here:
|
||||
</p><pre class="verbatim">slot= # some unique identifier
|
||||
repo=git://source.ffmpeg.org/ffmpeg.git # the source repository
|
||||
#branch=release/2.6 # the branch to test
|
||||
samples= # path to samples directory
|
||||
workdir= # directory in which to do all the work
|
||||
#fate_recv="ssh -T fate@fate.ffmpeg.org" # command to submit report
|
||||
comment= # optional description
|
||||
build_only= # set to "yes" for a compile-only instance that skips tests
|
||||
ignore_tests=
|
||||
|
||||
# the following are optional and map to configure options
|
||||
arch=
|
||||
cpu=
|
||||
cross_prefix=
|
||||
as=
|
||||
cc=
|
||||
ld=
|
||||
target_os=
|
||||
sysroot=
|
||||
target_exec=
|
||||
target_path=
|
||||
target_samples=
|
||||
extra_cflags=
|
||||
extra_ldflags=
|
||||
extra_libs=
|
||||
extra_conf= # extra configure options not covered above
|
||||
|
||||
#make= # name of GNU make if not 'make'
|
||||
makeopts= # extra options passed to 'make'
|
||||
#makeopts_fate= # extra options passed to 'make' when running tests,
|
||||
# defaulting to makeopts above if this is not set
|
||||
#tar= # command to create a tar archive from its arguments on stdout,
|
||||
# defaults to 'tar c'
|
||||
</pre>
|
||||
<p>Create a configuration that suits your needs, based on the configuration
|
||||
template. The <code class="env">slot</code> configuration variable can be any string that is not
|
||||
yet used, but it is suggested that you name it adhering to the following
|
||||
pattern ‘<samp class="samp"><var class="var">arch</var>-<var class="var">os</var>-<var class="var">compiler</var>-<var class="var">compiler version</var></samp>’. The
|
||||
configuration file itself will be sourced in a shell script, therefore all
|
||||
shell features may be used. This enables you to setup the environment as you
|
||||
need it for your build.
|
||||
</p>
|
||||
<p>For your first test runs the <code class="env">fate_recv</code> variable should be empty or
|
||||
commented out. This will run everything as normal except that it will omit
|
||||
the submission of the results to the server. The following files should be
|
||||
present in $workdir as specified in the configuration file:
|
||||
</p>
|
||||
<ul class="itemize mark-bullet">
|
||||
<li>configure.log
|
||||
</li><li>compile.log
|
||||
</li><li>test.log
|
||||
</li><li>report
|
||||
</li><li>version
|
||||
</li></ul>
|
||||
|
||||
<p>When you have everything working properly you can create an SSH key pair
|
||||
and send the public key to the FATE server administrator who can be contacted
|
||||
at the email address <a class="email" href="mailto:fate-admin@ffmpeg.org">fate-admin@ffmpeg.org</a>.
|
||||
</p>
|
||||
<p>Configure your SSH client to use public key authentication with that key
|
||||
when connecting to the FATE server. Also do not forget to check the identity
|
||||
of the server and to accept its host key. This can usually be achieved by
|
||||
running your SSH client manually and killing it after you accepted the key.
|
||||
The FATE server’s fingerprint is:
|
||||
</p>
|
||||
<dl class="table">
|
||||
<dt>‘<samp class="samp">RSA</samp>’</dt>
|
||||
<dd><p>d3:f1:83:97:a4:75:2b:a6:fb:d6:e8:aa:81:93:97:51
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">ECDSA</samp>’</dt>
|
||||
<dd><p>76:9f:68:32:04:1e:d5:d4:ec:47:3f:dc:fc:18:17:86
|
||||
</p></dd>
|
||||
</dl>
|
||||
|
||||
<p>If you have problems connecting to the FATE server, it may help to try out
|
||||
the <code class="command">ssh</code> command with one or more <samp class="option">-v</samp> options. You should
|
||||
get detailed output concerning your SSH configuration and the authentication
|
||||
process.
|
||||
</p>
|
||||
<p>The only thing left is to automate the execution of the fate.sh script and
|
||||
the synchronisation of the samples directory.
|
||||
</p>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Uploading-new-samples-to-the-fate-suite">
|
||||
<h2 class="chapter">4 Uploading new samples to the fate suite</h2>
|
||||
|
||||
<p>If you need a sample uploaded send a mail to samples-request.
|
||||
</p>
|
||||
<p>This is for developers who have an account on the fate suite server.
|
||||
If you upload new samples, please make sure they are as small as possible,
|
||||
space on each client, network bandwidth and so on benefit from smaller test cases.
|
||||
Also keep in mind older checkouts use existing sample files, that means in
|
||||
practice generally do not replace, remove or overwrite files as it likely would
|
||||
break older checkouts or releases.
|
||||
Also all needed samples for a commit should be uploaded, ideally 24
|
||||
hours, before the push.
|
||||
If you need an account for frequently uploading samples or you wish to help
|
||||
others by doing that send a mail to ffmpeg-devel.
|
||||
</p>
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">#First update your local samples copy:
|
||||
rsync -vauL --chmod=Dg+s,Duo+x,ug+rw,o+r,o-w,+X fate-suite.ffmpeg.org:/home/samples/fate-suite/ ~/fate-suite
|
||||
|
||||
#Then do a dry run checking what would be uploaded:
|
||||
rsync -vanL --no-g --chmod=Dg+s,Duo+x,ug+rw,o+r,o-w,+X ~/fate-suite/ fate-suite.ffmpeg.org:/home/samples/fate-suite
|
||||
|
||||
#Upload the files:
|
||||
rsync -vaL --no-g --chmod=Dg+s,Duo+x,ug+rw,o+r,o-w,+X ~/fate-suite/ fate-suite.ffmpeg.org:/home/samples/fate-suite
|
||||
</pre></div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="FATE-makefile-targets-and-variables">
|
||||
<h2 class="chapter">5 FATE makefile targets and variables</h2>
|
||||
|
||||
<ul class="mini-toc">
|
||||
<li><a href="#Makefile-targets" accesskey="1">Makefile targets</a></li>
|
||||
<li><a href="#Makefile-variables" accesskey="2">Makefile variables</a></li>
|
||||
<li><a href="#Examples" accesskey="3">Examples</a></li>
|
||||
</ul>
|
||||
<div class="section-level-extent" id="Makefile-targets">
|
||||
<h3 class="section">5.1 Makefile targets</h3>
|
||||
|
||||
<dl class="table">
|
||||
<dt><samp class="option">fate-rsync</samp></dt>
|
||||
<dd><p>Download/synchronize sample files to the configured samples directory.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">fate-list</samp></dt>
|
||||
<dd><p>Will list all fate/regression test targets.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">fate</samp></dt>
|
||||
<dd><p>Run the FATE test suite (requires the fate-suite dataset).
|
||||
</p></dd>
|
||||
</dl>
|
||||
|
||||
</div>
|
||||
<div class="section-level-extent" id="Makefile-variables">
|
||||
<h3 class="section">5.2 Makefile variables</h3>
|
||||
|
||||
<dl class="table">
|
||||
<dt><code class="env">V</code></dt>
|
||||
<dd><p>Verbosity level, can be set to 0, 1 or 2.
|
||||
</p><ul class="itemize mark-bullet">
|
||||
<li>0: show just the test arguments
|
||||
</li><li>1: show just the command used in the test
|
||||
</li><li>2: show everything
|
||||
</li></ul>
|
||||
|
||||
</dd>
|
||||
<dt><code class="env">SAMPLES</code></dt>
|
||||
<dd><p>Specify or override the path to the FATE samples at make time, it has a
|
||||
meaning only while running the regression tests.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><code class="env">THREADS</code></dt>
|
||||
<dd><p>Specify how many threads to use while running regression tests, it is
|
||||
quite useful to detect thread-related regressions.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><code class="env">THREAD_TYPE</code></dt>
|
||||
<dd><p>Specify which threading strategy test, either ‘<samp class="samp">slice</samp>’ or ‘<samp class="samp">frame</samp>’,
|
||||
by default ‘<samp class="samp">slice+frame</samp>’
|
||||
</p>
|
||||
</dd>
|
||||
<dt><code class="env">CPUFLAGS</code></dt>
|
||||
<dd><p>Specify CPU flags.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><code class="env">TARGET_EXEC</code></dt>
|
||||
<dd><p>Specify or override the wrapper used to run the tests.
|
||||
The <code class="env">TARGET_EXEC</code> option provides a way to run FATE wrapped in
|
||||
<code class="command">valgrind</code>, <code class="command">qemu-user</code> or <code class="command">wine</code> or on remote targets
|
||||
through <code class="command">ssh</code>.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><code class="env">GEN</code></dt>
|
||||
<dd><p>Set to ‘<samp class="samp">1</samp>’ to generate the missing or mismatched references.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><code class="env">HWACCEL</code></dt>
|
||||
<dd><p>Specify which hardware acceleration to use while running regression tests,
|
||||
by default ‘<samp class="samp">none</samp>’ is used.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><code class="env">KEEP</code></dt>
|
||||
<dd><p>Set to ‘<samp class="samp">1</samp>’ to keep temp files generated by fate test(s) when test is successful.
|
||||
Default is ‘<samp class="samp">0</samp>’, which removes these files. Files are always kept when a test
|
||||
fails.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
</div>
|
||||
<div class="section-level-extent" id="Examples">
|
||||
<h3 class="section">5.3 Examples</h3>
|
||||
|
||||
<div class="example">
|
||||
<pre class="example-preformatted">make V=1 SAMPLES=/var/fate/samples THREADS=2 CPUFLAGS=mmx fate
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,362 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- Created by GNU Texinfo 7.0.1, https://www.gnu.org/software/texinfo/ -->
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>FFmpeg Resampler Documentation</title>
|
||||
|
||||
<meta name="description" content="FFmpeg Resampler Documentation">
|
||||
<meta name="keywords" content="FFmpeg Resampler Documentation">
|
||||
<meta name="resource-type" content="document">
|
||||
<meta name="distribution" content="global">
|
||||
<meta name="Generator" content="makeinfo">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
<link href="#SEC_Contents" rel="contents" title="Table of Contents">
|
||||
<style type="text/css">
|
||||
<!--
|
||||
ul.toc-numbered-mark {list-style: none}
|
||||
-->
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body lang="en">
|
||||
|
||||
|
||||
<div class="top-level-extent" id="SEC_Top">
|
||||
|
||||
|
||||
<div class="element-contents" id="SEC_Contents">
|
||||
<h2 class="contents-heading">Table of Contents</h2>
|
||||
|
||||
<div class="contents">
|
||||
|
||||
<ul class="toc-numbered-mark">
|
||||
<li><a id="toc-Description" href="#Description">1 Description</a></li>
|
||||
<li><a id="toc-Resampler-Options" href="#Resampler-Options">2 Resampler Options</a></li>
|
||||
<li><a id="toc-See-Also" href="#See-Also">3 See Also</a></li>
|
||||
<li><a id="toc-Authors" href="#Authors">4 Authors</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Description">
|
||||
<h2 class="chapter">1 Description</h2>
|
||||
|
||||
<p>The FFmpeg resampler provides a high-level interface to the
|
||||
libswresample library audio resampling utilities. In particular it
|
||||
allows one to perform audio resampling, audio channel layout rematrixing,
|
||||
and convert audio format and packing layout.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Resampler-Options">
|
||||
<h2 class="chapter">2 Resampler Options</h2>
|
||||
|
||||
<p>The audio resampler supports the following named options.
|
||||
</p>
|
||||
<p>Options may be set by specifying -<var class="var">option</var> <var class="var">value</var> in the
|
||||
FFmpeg tools, <var class="var">option</var>=<var class="var">value</var> for the aresample filter,
|
||||
by setting the value explicitly in the
|
||||
<code class="code">SwrContext</code> options or using the <samp class="file">libavutil/opt.h</samp> API for
|
||||
programmatic use.
|
||||
</p>
|
||||
<dl class="table">
|
||||
<dt><samp class="option">uchl, used_chlayout</samp></dt>
|
||||
<dd><p>Set used input channel layout. Default is unset. This option is
|
||||
only used for special remapping.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">isr, in_sample_rate</samp></dt>
|
||||
<dd><p>Set the input sample rate. Default value is 0.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">osr, out_sample_rate</samp></dt>
|
||||
<dd><p>Set the output sample rate. Default value is 0.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">isf, in_sample_fmt</samp></dt>
|
||||
<dd><p>Specify the input sample format. It is set by default to <code class="code">none</code>.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">osf, out_sample_fmt</samp></dt>
|
||||
<dd><p>Specify the output sample format. It is set by default to <code class="code">none</code>.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">tsf, internal_sample_fmt</samp></dt>
|
||||
<dd><p>Set the internal sample format. Default value is <code class="code">none</code>.
|
||||
This will automatically be chosen when it is not explicitly set.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">ichl, in_chlayout</samp></dt>
|
||||
<dt><samp class="option">ochl, out_chlayout</samp></dt>
|
||||
<dd><p>Set the input/output channel layout.
|
||||
</p>
|
||||
<p>See <a data-manual="ffmpeg-utils" href="ffmpeg-utils.html#channel-layout-syntax">(ffmpeg-utils)the Channel Layout section in the ffmpeg-utils(1) manual</a>
|
||||
for the required syntax.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">clev, center_mix_level</samp></dt>
|
||||
<dd><p>Set the center mix level. It is a value expressed in deciBel, and must be
|
||||
in the interval [-32,32].
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">slev, surround_mix_level</samp></dt>
|
||||
<dd><p>Set the surround mix level. It is a value expressed in deciBel, and must
|
||||
be in the interval [-32,32].
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">lfe_mix_level</samp></dt>
|
||||
<dd><p>Set LFE mix into non LFE level. It is used when there is a LFE input but no
|
||||
LFE output. It is a value expressed in deciBel, and must
|
||||
be in the interval [-32,32].
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">rmvol, rematrix_volume</samp></dt>
|
||||
<dd><p>Set rematrix volume. Default value is 1.0.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">rematrix_maxval</samp></dt>
|
||||
<dd><p>Set maximum output value for rematrixing.
|
||||
This can be used to prevent clipping vs. preventing volume reduction.
|
||||
A value of 1.0 prevents clipping.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">flags, swr_flags</samp></dt>
|
||||
<dd><p>Set flags used by the converter. Default value is 0.
|
||||
</p>
|
||||
<p>It supports the following individual flags:
|
||||
</p><dl class="table">
|
||||
<dt><samp class="option">res</samp></dt>
|
||||
<dd><p>force resampling, this flag forces resampling to be used even when the
|
||||
input and output sample rates match.
|
||||
</p></dd>
|
||||
</dl>
|
||||
|
||||
</dd>
|
||||
<dt><samp class="option">dither_scale</samp></dt>
|
||||
<dd><p>Set the dither scale. Default value is 1.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">dither_method</samp></dt>
|
||||
<dd><p>Set dither method. Default value is 0.
|
||||
</p>
|
||||
<p>Supported values:
|
||||
</p><dl class="table">
|
||||
<dt>‘<samp class="samp">rectangular</samp>’</dt>
|
||||
<dd><p>select rectangular dither
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">triangular</samp>’</dt>
|
||||
<dd><p>select triangular dither
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">triangular_hp</samp>’</dt>
|
||||
<dd><p>select triangular dither with high pass
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">lipshitz</samp>’</dt>
|
||||
<dd><p>select Lipshitz noise shaping dither.
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">shibata</samp>’</dt>
|
||||
<dd><p>select Shibata noise shaping dither.
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">low_shibata</samp>’</dt>
|
||||
<dd><p>select low Shibata noise shaping dither.
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">high_shibata</samp>’</dt>
|
||||
<dd><p>select high Shibata noise shaping dither.
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">f_weighted</samp>’</dt>
|
||||
<dd><p>select f-weighted noise shaping dither
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">modified_e_weighted</samp>’</dt>
|
||||
<dd><p>select modified-e-weighted noise shaping dither
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">improved_e_weighted</samp>’</dt>
|
||||
<dd><p>select improved-e-weighted noise shaping dither
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
</dd>
|
||||
<dt><samp class="option">resampler</samp></dt>
|
||||
<dd><p>Set resampling engine. Default value is swr.
|
||||
</p>
|
||||
<p>Supported values:
|
||||
</p><dl class="table">
|
||||
<dt>‘<samp class="samp">swr</samp>’</dt>
|
||||
<dd><p>select the native SW Resampler; filter options precision and cheby are not
|
||||
applicable in this case.
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">soxr</samp>’</dt>
|
||||
<dd><p>select the SoX Resampler (where available); compensation, and filter options
|
||||
filter_size, phase_shift, exact_rational, filter_type & kaiser_beta, are not
|
||||
applicable in this case.
|
||||
</p></dd>
|
||||
</dl>
|
||||
|
||||
</dd>
|
||||
<dt><samp class="option">filter_size</samp></dt>
|
||||
<dd><p>For swr only, set resampling filter size, default value is 32.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">phase_shift</samp></dt>
|
||||
<dd><p>For swr only, set resampling phase shift, default value is 10, and must be in
|
||||
the interval [0,30].
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">linear_interp</samp></dt>
|
||||
<dd><p>Use linear interpolation when enabled (the default). Disable it if you want
|
||||
to preserve speed instead of quality when exact_rational fails.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">exact_rational</samp></dt>
|
||||
<dd><p>For swr only, when enabled, try to use exact phase_count based on input and
|
||||
output sample rate. However, if it is larger than <code class="code">1 << phase_shift</code>,
|
||||
the phase_count will be <code class="code">1 << phase_shift</code> as fallback. Default is enabled.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">cutoff</samp></dt>
|
||||
<dd><p>Set cutoff frequency (swr: 6dB point; soxr: 0dB point) ratio; must be a float
|
||||
value between 0 and 1. Default value is 0.97 with swr, and 0.91 with soxr
|
||||
(which, with a sample-rate of 44100, preserves the entire audio band to 20kHz).
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">precision</samp></dt>
|
||||
<dd><p>For soxr only, the precision in bits to which the resampled signal will be
|
||||
calculated. The default value of 20 (which, with suitable dithering, is
|
||||
appropriate for a destination bit-depth of 16) gives SoX’s ’High Quality’; a
|
||||
value of 28 gives SoX’s ’Very High Quality’.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">cheby</samp></dt>
|
||||
<dd><p>For soxr only, selects passband rolloff none (Chebyshev) & higher-precision
|
||||
approximation for ’irrational’ ratios. Default value is 0.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">async</samp></dt>
|
||||
<dd><p>For swr only, simple 1 parameter audio sync to timestamps using stretching,
|
||||
squeezing, filling and trimming. Setting this to 1 will enable filling and
|
||||
trimming, larger values represent the maximum amount in samples that the data
|
||||
may be stretched or squeezed for each second.
|
||||
Default value is 0, thus no compensation is applied to make the samples match
|
||||
the audio timestamps.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">first_pts</samp></dt>
|
||||
<dd><p>For swr only, assume the first pts should be this value. The time unit is 1 / sample rate.
|
||||
This allows for padding/trimming at the start of stream. By default, no
|
||||
assumption is made about the first frame’s expected pts, so no padding or
|
||||
trimming is done. For example, this could be set to 0 to pad the beginning with
|
||||
silence if an audio stream starts after the video stream or to trim any samples
|
||||
with a negative pts due to encoder delay.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">min_comp</samp></dt>
|
||||
<dd><p>For swr only, set the minimum difference between timestamps and audio data (in
|
||||
seconds) to trigger stretching/squeezing/filling or trimming of the
|
||||
data to make it match the timestamps. The default is that
|
||||
stretching/squeezing/filling and trimming is disabled
|
||||
(<samp class="option">min_comp</samp> = <code class="code">FLT_MAX</code>).
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">min_hard_comp</samp></dt>
|
||||
<dd><p>For swr only, set the minimum difference between timestamps and audio data (in
|
||||
seconds) to trigger adding/dropping samples to make it match the
|
||||
timestamps. This option effectively is a threshold to select between
|
||||
hard (trim/fill) and soft (squeeze/stretch) compensation. Note that
|
||||
all compensation is by default disabled through <samp class="option">min_comp</samp>.
|
||||
The default is 0.1.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">comp_duration</samp></dt>
|
||||
<dd><p>For swr only, set duration (in seconds) over which data is stretched/squeezed
|
||||
to make it match the timestamps. Must be a non-negative double float value,
|
||||
default value is 1.0.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">max_soft_comp</samp></dt>
|
||||
<dd><p>For swr only, set maximum factor by which data is stretched/squeezed to make it
|
||||
match the timestamps. Must be a non-negative double float value, default value
|
||||
is 0.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">matrix_encoding</samp></dt>
|
||||
<dd><p>Select matrixed stereo encoding.
|
||||
</p>
|
||||
<p>It accepts the following values:
|
||||
</p><dl class="table">
|
||||
<dt>‘<samp class="samp">none</samp>’</dt>
|
||||
<dd><p>select none
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">dolby</samp>’</dt>
|
||||
<dd><p>select Dolby
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">dplii</samp>’</dt>
|
||||
<dd><p>select Dolby Pro Logic II
|
||||
</p></dd>
|
||||
</dl>
|
||||
|
||||
<p>Default value is <code class="code">none</code>.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">filter_type</samp></dt>
|
||||
<dd><p>For swr only, select resampling filter type. This only affects resampling
|
||||
operations.
|
||||
</p>
|
||||
<p>It accepts the following values:
|
||||
</p><dl class="table">
|
||||
<dt>‘<samp class="samp">cubic</samp>’</dt>
|
||||
<dd><p>select cubic
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">blackman_nuttall</samp>’</dt>
|
||||
<dd><p>select Blackman Nuttall windowed sinc
|
||||
</p></dd>
|
||||
<dt>‘<samp class="samp">kaiser</samp>’</dt>
|
||||
<dd><p>select Kaiser windowed sinc
|
||||
</p></dd>
|
||||
</dl>
|
||||
|
||||
</dd>
|
||||
<dt><samp class="option">kaiser_beta</samp></dt>
|
||||
<dd><p>For swr only, set Kaiser window beta value. Must be a double float value in the
|
||||
interval [2,16], default value is 9.
|
||||
</p>
|
||||
</dd>
|
||||
<dt><samp class="option">output_sample_bits</samp></dt>
|
||||
<dd><p>For swr only, set number of used output sample bits for dithering. Must be an integer in the
|
||||
interval [0,64], default value is 0, which means it’s not used.
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="See-Also">
|
||||
<h2 class="chapter">3 See Also</h2>
|
||||
|
||||
<p><a class="url" href="ffmpeg.html">ffmpeg</a>, <a class="url" href="ffplay.html">ffplay</a>, <a class="url" href="ffprobe.html">ffprobe</a>,
|
||||
<a class="url" href="libswresample.html">libswresample</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="chapter-level-extent" id="Authors">
|
||||
<h2 class="chapter">4 Authors</h2>
|
||||
|
||||
<p>The FFmpeg developers.
|
||||
</p>
|
||||
<p>For details about the authorship, see the Git history of the project
|
||||
(https://git.ffmpeg.org/ffmpeg), e.g. by typing the command
|
||||
<code class="command">git log</code> in the FFmpeg source directory, or browsing the
|
||||
online repository at <a class="url" href="https://git.ffmpeg.org/ffmpeg">https://git.ffmpeg.org/ffmpeg</a>.
|
||||
</p>
|
||||
<p>Maintainers for the specific components are listed in the file
|
||||
<samp class="file">MAINTAINERS</samp> in the source code tree.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||