Google+ Followers

2018年6月2日 星期六

[Python] Line Chatbot 開發筆記 - Echo Service @ Ubuntu 18.04

LineChatbot01

入口:https://developers.line.me/en/
文件:https://developers.line.me/en/docs/messaging-api/overview/

登入開發者後,先來個 Add new provider ,此例為 StudyLineChatbot ,接著選擇 Messaging API,填寫完基本資料後,再按幾下就創建了

其中,重要的資訊:
  • Channel secret
  • Channel access token (long-lived)
而 Channel access token (long-lived) 預設為空,要按個 Issue 來產生一組。

接著,Webhook URL 要填寫對接的 API 位置,該 API 必須提供 https 服務,另外再把 Auto-reply message 關閉。如此一來就完成 Line Chatbot 的設定。

接著談談 Webhook URL 的實作方式,此例用 Python 的 Flask 和 line-bot-sdk-python 完工,簡短程式 hello.py 如下(其實就是 https://github.com/line/line-bot-sdk-python README 範例程式,多增加一點輸出訊息):

from flask import Flask, request, abort

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)

line_bot_api = LineBotApi('YOUR_LINE_CHATBOT Channel access token (long-lived)')
handler = WebhookHandler('YOUR_LINE_CHATBOT Channel secret')

@app.route("/", methods=['POST'])
def callback():
    print("[INFO] Get request:")
    print(request.__dict__)
    print()

    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']
    # get request body as text
    body = request.get_data(as_text=True)
    print("[INFO] Request Body: ---\n"+body+"\n---\n")

    # handle webhook body
    try:
        events = handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    print("[INFO] Handle Events \n")
    for event in events:
        if event is None:
            continue
        if not isinstance(event, MessageEvent):
            continue
        if not isinstance(event.message, TextMessage):
            continue
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=event.message.text)
        )
    return '{}'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text)
    )

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)


然後,選擇在 Ubuntu server 上,透過 nginx 跟 uWSGI 設定。

Nginx:

location / {

try_files $uri @chatbot;
}

location @chatbot {
include uwsgi_params;
uwsgi_pass unix:/tmp/chatbot-uwsgi.sock;
}


uWSGI:

$ vim /etc/systemd/system/chatbot.uwsgi.service
[Unit]
Description=uWSGI Emperor
After=syslog.target

[Service]
ExecStart=/usr/local/bin/uwsgi --ini /path/project/chatbot-uwsgi.ini
RuntimeDirectory=uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target

$ vim /path/project/chatbot-uwsgi.ini
[uwsgi]
base = /path/project/

app = hello
module = %(app)
home = %(base)/venv
pythonpath = %(base)
callable = app

socket = /tmp/%n.sock
chmod-socket = 666
logto = /var/log/uwsgi/%n.log


後續都統一靠以下招數更新:

$ sudo systemctl restart chatbot.uwsgi

以及 hello.py 輸出的資料和運行結果,都可以在 /var/log/uwsgi/chatbot-uwsgi.log 找到:

[INFO] Get request:
{'environ': {'QUERY_STRING': '', 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'application/json;charset=UTF-8', 'CONTENT_LENGTH': '237', 'REQUEST_URI': '/', 'PATH_INFO': '/', 'DOCUMENT_ROOT': '/etc/nginx/html', 'SERVER_PROTOCOL': 'HTTP/1.1', 'REQUEST_SCHEME': 'https', 'HTTPS': 'on', 'REMOTE_ADDR': '###.###.###.###', 'REMOTE_PORT': '34994', 'SERVER_PORT': '443', 'SERVER_NAME': 'chatbot.example.com', 'HTTP_X_LINE_SIGNATURE': '#####################', 'HTTP_CONTENT_TYPE': 'application/json;charset=UTF-8', 'HTTP_CONTENT_LENGTH': '237', 'HTTP_HOST': 'chatbot.example.com', 'HTTP_ACCEPT': '*/*', 'HTTP_USER_AGENT': 'LineBotWebhook/1.0', 'wsgi.input': <uwsgi._Input object at 0x7f0d8a4477c8>, 'wsgi.file_wrapper': <built-in function uwsgi_sendfile>, 'wsgi.version': (1, 0), 'wsgi.errors': <_io.TextIOWrapper name=2 mode='w' encoding='UTF-8'>, 'wsgi.run_once': False, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.url_scheme': 'https', 'uwsgi.version': b'2.0.17', 'uwsgi.node': b'###########', 'werkzeug.request': <Request 'https://chatbot.example.com/' [POST]>}, 'shallow': False, 'view_args': {}, 'url_rule': <Rule '/' (POST, OPTIONS) -> callback>, 'url': 'https://chatbot.example.com/'}

[INFO] Request Body: ---
{"events":[{"type":"message","replyToken":"###############","source":{"userId":"##############","type":"user"},"timestamp":###########,"message":{"type":"text","id":"###########","text":"HelloWorld"}}]}
---

2018年5月14日 星期一

iOS App 開發筆記 - 關於 case sensitive 檔案系統與 CocoaPods 和 Ruby/Gems 的處理

某天重灌 macOS 時,把主硬碟弄成 case sensitive ,想說自己都很熟悉 linux server 的行為,殊不知替自己埋了很多坑。首先,如果用 CocoaPods 維護套件時,別人的程式碼不見得會依照此規範,同理在 Ruby/Gems 更是如此。

解法?先切一個 partition 不區分大小寫吧!在把 Xcode build code 的暫存區也移至該 partition 來解!

未來還是推薦用 case insensitive 的 partition 吧 Orz 想搞龜毛還是用另一區,千萬別要跟青春過意不去啊。

將 Xcode build 臨時區移走:

$ mv ~/Library/Developer/Xcode/DerivedData/ ~/Library/Developer/Xcode/DerivedData-bak/
$ mkdir /Volumes/Data/XcodeDerivedData
$ ln -s /Volumes/Data/XcodeDerivedData ~/Library/Developer/Xcode/DerivedData


如此一來,關於 Xcode 與 CocoaPods 的大小寫問題應當可以解了,但...如果 build code 過程還有搭配 ruby script 做事,很抱歉,請自己改 ruby 了 Orz 例如:

require 'CFPropertyList'

很抱歉,在 Ruby 的 CFPropertyList 套件中,他實際是小寫的 Orz 請看:

https://github.com/ckruse/CFPropertyList/tree/master/lib

最重要的,這類還可能搞壞 gem 的管理狀態,這時解法就有點痛苦了,絕對不是三兩句就解掉的。

2018年5月9日 星期三

Alexa Skill 開發筆記 - 使用 AWS Lambda 與 OAUTH2 帳號連結

公司的產品是 WiFi Display 設備,結合智慧音箱後,透過音控請裝置執行動作,如播放影片。在此就順便紀錄這些開發過程。此處不會提及 OATUH2 開發項目。

首先,Alexa Skill 是個滿好玩的點子,說穿了就像 Apple TV 可以安裝 Apps 的概念,他是個市集,可以讓開發者提供更多智慧音箱的技能擴充,大約是 2015 年開始的。上頭也可以有簡單的變現機制,可以參考:Alexa開發者也可以賺錢了!看亞馬遜怎麼開啟語音應用的全新獲利模式

首先,沒有 Alexa device 也可以開發,善用模擬器即可。開發流程:

  1. 建立 Alexa Developer 帳號(過程就是建立 Amazon 帳號)
  2. 建立 AWS 帳號(綁定信用卡,有免費額度可善用)
  3. 使用模擬器服務 - https://echosim.io/


01-skill01

02-skill02

先在 Alexa Developer 先建立一個 Skill project,此例是 Video ,接著就要填寫 AWS Lambda 的位置,就切換到 AWS Lambda 創建流程,而 Alexa Skill 預設是提供 English (US) 的語系,參照 To create a Lambda function 第三步有強調 AWS Region 的部分:
Make sure you’ve selected the N.Virginia for English (US) skills or the EU (Ireland) region for English (UK) and German skills. The region is displayed in the upper right corner. Providing your Lambda function in the correct region prevents latency issues.
就要在 N.Virginia 創建 AWS Lambda function,不然亂建立根本無法互動。建立完 AWS Lambda function 後,替他增加 Alexa Skill Kit 和 Alexa Smart Home ,並且設定它的 Skill ID,並且把對應的程式碼上傳,且要記得發布出去!

03-aws01

04-aws02

05-aws03

如此一來,此 Skill 作者基本上已經可以測試了,而測試的條件包括要有一個 Alexa 音控裝置,這時就要靠模擬器,可以先到 https://echosim.io/ 登入 Amazon 帳號,即可獲得一台虛擬器。

15-simulator

接著,若你人在美國或是擁有美國的 iTunes / Google play 帳號可以下載得到 Amazon Alexa app ,那就直接用,不然只好跟我一樣用用網頁版: https://alexa.amazon.com/spa/index.html ,只要有先把 Amazon 帳號配對過 Alexa 音箱就可以正常切換到 Skill 頁面,點選右上角的 Your skill 即可切換到 Dev skill 來查看,並把自己開發的 skill 給 enable,過程就會觸發 OAUTH2 服務帳號的綁定並授權 Skill 服務使用等等,後續的過程先挑選哪個硬體裝置要被智慧音箱控制(AWS Lambda 要實作 device discovery 等機制),接著再挑哪個智慧音箱來搭配,整個過程用瀏覽器開發工具查看到 api response:

  • https://alexa.amazon.com/api/phoenix/discovery
    • 得知哪些硬體裝置要被控制
  • https://alexa.amazon.com/api/devices-v2/device
    • 得知你的帳號有哪些智慧音箱裝置


06-skill03

07-skill04

08-skill05

09-skill06

10-skill07

11-skill08

12-skill09

最後,自己測試完後,可以再開啟 Beta Test 機制,邀請其他人來測試。而要開啟 Beta Test 只需要把資料填一填,並處於 ready for submission 即可(送審前一步),關鍵的地方就是補一補 icon、描述、甚至一些網址等等,若只要一直內測就可以先亂填

13-skill10

14-skill11

最後,有問題真的要好好看文件 XD 而 AWS Lambda 則可以善用 Cloud Watch 看 logs ,以此確保真的有連線

2018年5月7日 星期一

處理 Google Cloud Messaging for Android (GCM) token 重複問題 (canonical_ids)

事情是這樣的,拖了很久才開始處理這段 XD 拖到連 GCM 都要下線了:

As of April 10, 2018, Google has deprecated GCM. The GCM server and client APIs are deprecated and will be removed as soon as April 11, 2019. Migrate GCM apps to Firebase Cloud Messaging (FCM), which inherits the reliable and scalable GCM infrastructure, plus many new features. See the migration guide to learn more.

為何要處理?主因是一隻 android mobile device 很有可能因為隱私狀態改變、重新安裝軟體後,導致 GCM token 變更,如果當年沒有設計 global uuid 來辨識移動裝置的話(像是綁個人帳號、用 wifi mac_address 等),那 token 肯定會收到一卡車多,這時要發送訊息時,就可能出現多個 token 對應到同一位使用者,造成用戶被 Push  轟炸 (這些在 Firebase 設計理念上都有改善了,例如可以單純對全部 android user 發訊息等等),特別是 QA 常常安裝反安裝程式...

原先我們依賴著 wifi mac_address ,但在某版 Android 發現他是可變的 Orz (忘了是隱私提升還是某OEM廠的bug) 對於維護 GCM token ,就只剩下一條路:試著對他發送訊息,檢查回應是不是有變化。

連續動作:

$check_failed_db_ids = array();
$tokens = array(
array( 'id' => 'db record id', 'token' => 'GCM token'),
// ...
);

$payload = array(
'registration_ids' => array(),
'dry_run' => true,
'data' => array(
'body' => 'test'
),
);

while(count($tokens) > 0) {
//
// Step 1: init
//
$payload['registration_ids'] = array();
$current_task = array_splice($tokens, 0, 900);
foreach($current_task as $items) {
array_push($payload['registration_ids'], $items['token']);
}

//
// Step 2: call gcm api
//
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://android.googleapis.com/gcm/send");
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Authorization: key=".$api_key,
'Content-Type: application/json'
));
curl_setopt($ch, CURLOPT_POST , true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_VERBOSE, true);
$ret = curl_exec($ch);
curl_close($ch);

$ret_json = @json_decode($ret, true);
if (isset($ret_json['results'])) {
$echo_cnt = count($ret_json['results']);
for ($i=0;$i<$echo_cnt; ++$i) {
// 通常有 error 都可以考慮去掉了;當有 registration_id 則是代表已轉換 token
// 完整範例跟解釋:https://developers.google.com/cloud-messaging/http#example-responses
if (isset($ret_json['results'][$i]['error']) || isset($ret_json['results'][$i]['registration_id'])) {
array_push($check_failed_db_ids, $i);

}
}
}

//
// Step 3: update db record & reset $check_failed_db_ids
//
// ...

}

2018年4月20日 星期五

[SEO] Google search console 與使用 wget 建立 sitemap @ macOS 10.13.14

最近公司換了網域,導流量過程中需要安置個 sitemap 來增加曝光,就先查了一下 Google 牌的定義:

https://support.google.com/webmasters/answer/183668?hl=zh-Hant

不錯不錯,可以提交純文字,那就搭配個 wget 吧!

$ time wget --spider --recursive --no-verbose --output-file=log.txt https://example.com/
...
real 87m25.965s
user 0m1.836s
sys 0m4.081s


真久,接著再靠 grep 跟 awk 即可:

$ grep -op "URL:http[s]://\(.*\) " ~/log.txt | awk '{print(substr($1,5));}' > sitemap.txt

搞定!

2018年4月3日 星期二

[PHP] 微信開放平台開發筆記 - WeChat Web 應用與 OAuth2 client

今年才感到 Wechat app 崛起,得知用戶已破10億,甚至微信支付交易筆數早已超車支付寶1.5倍,已乃覺三十里 Orz 想要用境外身份申請,才發現很多都辦不成,像是境外要用公司身份才行,甚至騰訊公開平台的管理者都還得綁有中國銀行卡(金融卡)才搞得定 <囧> 多虧對岸同事的幫忙,可以小試身手一下。

騰訊公開平台的 OAuth2 文件: https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN

需要的留意的是騰訊公開平台的 Web 應用只允許填入一個 hostname/domain 作為 callback handler ,所以,也有人想得很快,透過在統一地方處理完畢後,在拋去其他地方,如:https://github.com/HADB/GetWeixinCode

在此利用一樣的架構,不過是在 backend 處理 XD 主要是服務常分成 production/alpha/dev site,就把它統一建構在 Production site ,但 Dev site 要登入時,跳去 production site 處理後,再把資料回拋到 dev site(當然也可以申請多個 WeChat Web 應用,每個 site 擁有自己的 Web app)

整個過程都算順利,唯一不太順的地方是 OAuth state 這個參數的使用,原先埋了要跳去其他地方的 callback url 或其他資訊,但測了幾輪發現 state 內若有一些敏感資訊(&)時,回來都會出錯,於是就多加個 base64_encode 來解了(也可以考慮把必要的資料儲存在 cookie)

發動處:

<?php

$init_url = 'https://open.weixin.qq.com/connect/qrconnect?'. http_build_query( array(
        'scope' => 'snsapi_login,snsapi_userinfo',
        'response_type' => 'code',
        'redirect_uri' => 'https://auth.my-domain/wechat/callback',
        'state' => base64_encode( http_build_query(array(
                'give_me_code' => 1,
                'callback' => 'https://service.my-domain/wechat/handle-code',
        )) ),
        'appid' => $weixin_app_id,
));


負責處理 WeChat OAuth2 回傳資料(code):https://auth.my-domain/wechat/callback

<?php

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/oauth2/access_token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'appid' => $weixin_app_id,
'secret' => $weixin_app_secret,
'code' => $code,
'grant_type' => 'authorization_code',
)));
$ret = @json_decode(curl_exec($ch), true);
             
$state = $_GET['state'];
$state_param = array();
if (!empty($state)) {
$state = @base64_decode($state);
parse_str($state, $state_param);
}

isset($state_param['callback']) && (
preg_match('#http[s]://(.*?)\.my-domain/#i', $state_param['callback'], $match)
) ) {
if (!empty($_GET['code'])) {
if (isset($state_param['give_me_code']) && $state_param['give_me_code'] == '1')
header('Location: '.$state_param['callback'].'?'.http_build_query(array('code' => $_GET['code'])) );
// ...
} else {
header('Location: '.$state_param['callback'].'?'.http_build_query(array('error' => 'no code')) );
}
return;
}


處理 code:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/oauth2/access_token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'appid' => $weixin_app_id,
'secret' => $weixin_app_secret,
'code' => $ret_code,
'grant_type' => 'authorization_code',
)));

$ret = @json_decode(curl_exec($ch), true);

if (isset($ret['access_token']) && isset($ret['refresh_token']) && isset($ret['openid']) && isset($ret['unionid'])) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/userinfo');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'access_token' => $ret['access_token'],
'openid' => $ret['openid'],
)));
$profile_ret = @json_decode(curl_exec($ch), true);
}

// $profile_ret['nickname']
// $profile_ret['headimgurl']


原先在 callback handler 上,順便做完 code 檢驗,但後來覺得這樣要傳出去的資料太多(且感到不安),所以還是統一把 code 拋出去,缺點是其他服務就必須都紀錄 WeChat Web 應用的資料(app_id/secret_key)等等,但可以避免受到 DNS 攻擊而把用戶的 access_token 給了出去。

[Android] 使用 Chrome Browser - Web Inspector 查看 App WebView 行為

之前用了一陣子,然後又忘了,還是寫一下筆記:

  1. 要把 Android app 內使用的 Webview 開啟 setWebContentsDebuggingEnabled
    • WebView.setWebContentsDebuggingEnabled(true);
  2. Android phone 也要開啟 developer mode 和 adb debug
  3. 使用 Chrome browser -> 開發人員選項 -> more tools -> remote devices

順利的話,就會看到手機彈跳出一些視窗,按一按授權就看在 chrome browser 上追蹤 App 內 webview 的行為,像是追蹤其他國家內的網頁資源有被有被 ban 掉 XD

請直接參考:https://developers.google.com/web/tools/chrome-devtools/remote-debugging/?hl=zh-tw

未來可以規劃 release apk 不開啟 setWebContentsDebuggingEnabled ,而 dev apk 再開啟即可。

2018年4月1日 星期日

[Linux] Let's Encrypt 與 Wildcard 憑證,申請免費 SSL 憑證/HTTPS服務 @ Ubuntu 14.04

*.changyy.org

之前看強者同事已經幫公司網域申請完畢了,我才想到自己的網站一直沒打通 XD 趁個週日練一下吧!

看一下 Let's encrypt 網站簡介:https://letsencrypt.org/docs/client-options/
建議用 Certbot !

挑了一下 Nginx 與 Ubuntu server:

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx


然後就只要指道幾令即可:

$ sudo certbot --manual certonly  -d *.changyy.org --server https://acme-v02.api.letsencrypt.org/directory

過程會要求你添加 DNS TXT record,添加完再按 Enter 驗證。

最後產出:

/etc/letsencrypt/live/changyy.org/fullchain.pem
/etc/letsencrypt/live/changyy.org/privkey.pem


來個 /etc/nginx/conf.d/ssl.changyy.org.conf

server {
    listen       80;
    listen  443 ssl;
    ssl_certificate /etc/letsencrypt/live/changyy.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/changyy.org/privkey.pem;

    server_name  ssl.changyy.org;

    access_log  /var/log/nginx/ssl.changyy.org-access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.php index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}


收工!若是使用 Cloudflare 託管 DNS 的話,也可以透過 Certbot 一口氣都做完事喔:

Welcome to certbot-dns-cloudflare’s documentation!

另外,對我而言,這個服務最想用的就是 private ip server 啦!像是 NAS 等等的,只要透過手動設定即可搞定!

2018年3月10日 星期六

[Python] Google Voice Kit 與 Raspberry Pi 3

AIY4

上一次 Pi 1 開機已經想不起來了,大概是 2013 年的事吧,那年在研究 Raspbmc / Airplay 吧,大概是配置的 SDCard 太慢了,再加上已經長期投資 Linode 後,就漸漸把機器擺進防潮箱了,最近則是因為智慧喇吧的興起,想挑一款產品試試,想著想著,就想到去年就曾看到 Google Voice Kit ,而當初買不下去是因為美國才賣 10 美金,但台灣賣超過 30 美金,更別說美國那邊可是 35 美金含 Pi 3 的方案啊 XD

後來想通後,把多出來的價碼當作代購吧 Orz 何必為了一點還可以承擔的錢,延遲或放棄學習的機會?時間可是不等人的!總之,就這樣噴了將近快台幣 3000 吧,一張近似裸裝 Pi 3 (13XX) + Google Voice Kit (900) + microSD (300),各別都還要加上運費和轉帳費。(若單純對智慧音箱有興趣的,建議可以直接買 Google Home mini ,在露天代購不用兩千,甚至接近一千五,跟定價 49.99 美金非常接近)

AIY3

安裝硬件跟紙盒還滿快的,15 分鐘內應當可以搞定,接下來是軟體與線上服務的搭配,開通 GOOGLE ASSISTANT API 及 OAuth 認證後,就可以執行非常簡單的 demo 機制。

寫到這,的確可以穩穩體驗一下,但突然很反骨的想自己試試一些“中文”,需要搞定的項目為:
  • 語音輸入
  • 語音播放
  • 語音辨識
  • 語音合成(Text to Speech)
整個流程就是善用資源,哪邊有 code 要翻出來拼裝,像是語音輸入跟播放就用 Google 寫好的 aiy.audio,而語音辨識則是 SpeechRecognition ,語音合成則是 gTTS。比較特別的是語音播放是用 wav 格式,而 gTTS 的語音合成產出是 mp3 格式,所以再透過 pydub 轉成 wav (底層是 ffmpeg)

其中找資料的過程,主要是從 ~/Desktop/check_audio.desktop 進而研究 AIY-projects-python/checkpoints/check_audio.py 的寫法,找出底層實作的方式(command line!):

AIY-projects-python/src/aiy/audio.py
aiy._drivers._player
aiy._drivers._recorder
aiy._drivers._tts

AIY-projects-python/src/aiy/_drivers/_recorder.py
$ arecord --version
arecord: version 1.1.3 by Jaroslav Kysela <[email protected]>

AIY-projects-python/src/aiy/_drivers/_player.py
$ aplay --version
aplay: version 1.1.3 by Jaroslav Kysela <[email protected]>

AIY-projects-python/src/aiy/_drivers/_tts.py
$ pico2wave --usage
Usage: pico2wave [-?] [-w|--wave=filename.wav] [-l|--lang=lang] [-?|--help] [--usage] <words>


如果有興趣想追 GOOGLE ASSISTANT API 的實作,也可以多看看這些:

~/AIY-projects-python/src/examples/voice/assistant_library_with_button_demo.py
~/AIY-projects-python/src/examples/voice/assistant_library_demo.py
~/AIY-projects-python/src/aiy/assistant/library.py


很快就會追到 google.assistant.library,接著發現無法欣賞他如何實作 https://github.com/googlesamples/assistant-sdk-python/issues/77

最後,嘗試中文的 Speech to text 跟 text to speech ,再順便加一點點天氣回應,有興趣再到這邊看看,最大的關鍵點是用 AIY 的元件(arecord / aplay) 跟其他 gTTS / pydub 亂串時,發現有一些 async 狀態(要播放剛剛製成的音訊資料時,發現資料未完全寫入檔案,最後靠 time.sleep(3) 來解掉):

https://github.com/changyy/GoogleVoiceKitStudy/blob/master/detect-and-echo.py

2018年1月27日 星期六

[PHP] 使用 PHP built-in web server 及 PHP CodeIgniter framework

回想起來,我大概斷斷續續用了 CodeIgniter 七年了,最近才準備在本地開發 XD 開發上就在想 node.js 都有 webpack 等工具了,為啥 PHP 開發上還要先架設個 Web server 而感到納悶。

然而,其實 PHP 早已有 built-in web server 了,雖然只是 single-thread 但在開發上已經非常夠用,就來摸一下怎樣整再一起

$ cd project_web_document_root
$ php -S localhost:8000


接著就在 http://localhost:8000 就可以運行了!
然而,許多 web framework 都靠 routing 把 requests 統一集中到一支程式判斷,似乎已經是個非常基本的設計概念,那在 php built-in web server 也是可以的,他可以設定 routing 機制

$ cd project/web
$ cat ../tools/routing.php
$BASE_DIR = __DIR__ . '/../web' ;
if (file_exists($BASE_DIR . $_SERVER['REQUEST_URI']))
return false;
$_SERVER['SCRIPT_NAME'] = '/index.php';
include_once ($BASE_DIR . '/index.php');
$ php -S localhost:8000 ../tools/routing.php
PHP 7.0.27 Development Server started at Tue Jan 23 12:31:32 2018
Listening on http://localhost:8000
Document root is C:\Users\user\Desktop\ci-project\web
Press Ctrl-C to quit.


更多筆記:changyy/codeiginiter-and-php-built-in-web-server

2018年1月20日 星期六

AWS 筆記 - 使用 Ubuntu server 更換 Windows server 2012 帳號狀態

AWS Windows server

公司在三年前在 AWS Beijing 開了一台 Windows server 2012 運行一些套裝軟體,然而,負責的 IT 在整理系統帳號時,把 guest 關閉了。接著發生很多奇妙事件,最恐怖的是機器無法遠端登入了 Orz

一開始想朝更換 Administrator 密碼試試,結果一看過程步驟也不少:
使用 EC2Config 重置 Windows 管理员密码 -
https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/WindowsGuide/ResettingAdminPassword_EC2Config.html
接著,反而朝惡搞的方式進去研究,例如:如何修改機碼啟用 guest 帳號


最後,使用了前一份(一年前版本)的全系統備份,開了一台機器,把他的 C:/Windows/System32/config/SAM 複製出來,覆蓋掉無法登入的機器上,如此一來就搞定了!

情況簡介:
A server: 壞掉的 Windows server 2012
B server: 從 A server 現狀建置 AMI 而開啟的機器
C server: 前一版次備份的 Windows server
D server: Ubuntu 14.04 server
A 計畫:
將 B server 的 C 槽卸下,並掛到 C server 上進行 B server 的 C槽 資料修改,改完後再卸下,重裝到 B server,把 B server 重新啟動,看看是否正常

然而,A 計劃失敗了,在 AWS console 可以看到系統無法正常通過檢驗,於是啟動了 B 計畫

B 計畫:
再次建置乾淨的 B server,將 B server 的 C 槽卸下,也把 C server - C 槽卸下,並一起掛到 D server 上,拿著 C server - C 槽關鍵資訊,直接覆蓋掉 B server - C 槽 資料,修改完後再卸下,重裝到 B server,再把 B server 重新啟動
果真 B 計劃成功了 :p 猜測 A方案 可能因為修改的是 Windows OS level 資訊,修改完可能也改變了 B server - C 槽狀態,以至於重新啟用時反而資訊對不上而無法開機。

AWS Windows server

其他筆記:

  1. Windows 需要 stop 機器,才能卸下指定硬碟,請開啟 “終止保護“ 避免不小心做錯事
  2. Windows 的系統碟(C:)根设备用 /dev/sda1 資訊,卸下後要重新裝上時,記得要用原本的 /dev/sda1 才行
  3. 在 EC2 - EBS 管理介面要卸下某硬碟時,建議把 Name 帶點原本的機器資訊,方便找尋
  4. 在 Ubuntu 要 mount NTFS 都還算簡單,sudo mount /dev/xsgf1 /mnt/Win2012CDisk 
  5. Windows 的帳號資訊擺在 C:\Windows\System32\config\SAM 檔案,可以單純做 cp /mnt/C_server_c_diskt/Windows/System32/config/SAM /mnt/B_server_c_diskt/Windows/System32/config/SAM 
  6. 驗證完畢後,就可以安心地把 A server 先暫停 -> 把系統槽卸下 -> 掛到 Ubuntu server -> mount 起來 -> 覆蓋掉 SAM -> 卸下 -> 掛回 A server -> A server 重新開機