顯示具有 python 標籤的文章。 顯示所有文章
顯示具有 python 標籤的文章。 顯示所有文章

2026-10-16

AS1102TL NAS 跑 kokoro 生成日語 mp3 語音檔

參考資料 ----

kokoro


以 mobaXterm 管理員權限登入 AS1102TL

要生成日語,需要 pyopenjtalk,下載並在 NAS 上進行編譯。
 
# 先在 AppCentral 找到 Entware 並安裝,這是 NAS 的套件管理
# 安裝的所有二進位檔、函式庫與標頭檔全都統一存放在 /opt(即 /volume1/.@plugins/AppCentral/entware/opt/)底下。
user$  opkg  update
user$  opkg  install  gcc  make  cmake

# The scripts cygdb, cython and cythonize are installed in '/volume1/.@plugins/AppCentral/python3/bin' which is not on PATH.

user$  export  TMPDIR=/volume1/tmp
user$  ~/.local/bin/pip3  install  cython  unidic-lite  --only-binary=:all:  --no-cache-dir


# 因為 pyopenjtalk 沒有提供適用於 ARM64 Linux + Python 3.12 的現成 Wheel 檔
# 所以要手動下載重新編譯
user$  cd /volume1/
# 建立暫存目錄 tmp
user$  mkdir  tmp
user$  cd  tmp
user$  mkdir  build_pyopenjtalk
user$  cd  build_pyopenjtalk
user$  ~/.local/bin/pip3  download  pyopenjtalk  --no-binary  :all:
# 本筆記下載了 pyopenjtalk-0.4.1.tar.gz
# 解壓縮
user$  tar  -xzf  pyopenjtalk-0.4.1.tar.gz
# 解壓縮的檔案放在 pyopenjtalk-0.4.1 目錄

user$  cd  /volume1/tmp/build_pyopenjtalk/pyopenjtalk-0.4.1/pyopenjtalk

# 這指令會將 openjtalk.pyx 生成 openjtalk.cpp
user$  python3  -m  cython  --cplus  openjtalk.pyx

# 以文字編輯軟體打開 openjtalk.cpp,如:vscode, notepad++...等,將這幾行插入 openjtalk.cpp 的第一列,再存檔
#include <unistd.h>
#include <limits.h>
#ifndef SSIZE_MAX
#define SSIZE_MAX LONG_MAX
#endif pyopenjtalk/openjtalk.cpp

# 可透過 head -n 6 pyopenjtalk/openjtalk.cpp 確認前幾行已包含這些定義。

user$  export  PATH=/opt/bin:$PATH
user$  export  CC=/opt/bin/gcc
user$  export  CXX=/opt/bin/g++
user$  export  TMPDIR=/volume1/tmp

# 這步驟會消耗蠻多時間...請耐心等候
user$  ~/.local/bin/pip3  install  .  --no-build-isolation  --no-cache-dir

# 日語假名/羅馬字轉換工具
user$  ~/.local/bin/pip3  install  jaconv  mojimoji  --no-cache-dir
 


合成日語 tts_jp.py
 
import os
import warnings
import io
import onnxruntime as ort
import soundfile as sf
from kokoro_onnx import Kokoro
from pydub import AudioSegment
from pydub.utils import which
from misaki import ja


# 限制執行緒數量,避免在 1GB RAM NAS 上過載
os.environ["OMP_NUM_THREADS"] = "2"
os.environ["ORT_NUM_THREADS"] = "2"

warnings.filterwarnings("ignore", category=UserWarning, module="joblib")

# 將靜態 ffmpeg 所在目錄放到環境變數 PATH 最前端
FFMPEG_DIR = "/volume1/Web/kokoro/ffmpeg-7.0.2-arm64-static"
os.environ["PATH"] = FFMPEG_DIR + ":" + os.environ.get("PATH", "")

# 明確指定使用支援 libmp3lame 的 ffmpeg 執行檔路徑
AudioSegment.converter = os.path.join(FFMPEG_DIR, "ffmpeg")
AudioSegment.ffmpeg = os.path.join(FFMPEG_DIR, "ffmpeg")


# 檔案路徑設定
MODEL_PATH = "/volume1/Web/kokoro/kokoro-v1.0.onnx"
VOICES_PATH = "/volume1/Web/kokoro/voices-v1.0.bin"
OUTPUT_MP3 = "/volume1/Web/mp3/lee_mood.mp3"


# 目標字串
text_input = "各作品はいくつかの世界設定を共有している。例外もあるが正式な作品のクレジットにはサンライズの共同筆名である矢立肇と、シリーズ第1作を手掛けた富野由悠季の名義を入れることが慣習となっている。"
text_input = "ガンダム(IP)は、映像作品を中心に、ガンプラなどのトイホビー、コンピュータゲーム、専門雑誌やコミックなどの書籍、ネットワークコンテンツ、アミューズメント施設など、さまざまな分野に展開する多角的な戦略を通じて、日本を代表するIPへと成長を遂げている。"

# 初始化 Kokoro ONNX
kokoro = Kokoro(model_path=MODEL_PATH, voices_path=VOICES_PATH)

# 初始化日語音素處理器 (Misaki G2P)
g2p = ja.JAG2P()
res = g2p(text_input)
phonemes = res[0] if isinstance(res, tuple) else res

# 將中文轉為 Kokoro 需要的音素格式
print(f"轉換音素結果: {phonemes}")

# 呼叫 create,傳入音素
print("正在合成語音...")
samples, sample_rate = kokoro.create(
    text=phonemes,
    voice="jf_nezumi",
    speed=1.0,
    is_phonemes=True
)


# 轉存為 MP3
# 先在記憶體中建立輕量 WAV 緩衝區,再由 pydub 調用 ffmpeg 封裝成 MP3
wav_buffer = io.BytesIO()
sf.write(wav_buffer, samples, sample_rate, format="WAV")
wav_buffer.seek(0)

audio_seg = AudioSegment.from_wav(wav_buffer)
audio_seg.export(OUTPUT_MP3, format="mp3", bitrate="192k")

print(f"完成!音訊已輸出至: {OUTPUT_MP3}")
 


相關筆記 ----


2026-10-13

在 ASUSTOR AS1102TL NAS 跑 kokoro tts(text-to-speech) 生成 mp3 語音檔

參考資料 ----

kokoro


以 mobaXterm 管理員權限登入 AS1102TL

因為 NAS AppCentral 的 FFmpeg 並不具生成 mp3 的能力,所以需要另外下載
 
user$  cd  /volume1/Web/kokoro/
user$  wget  https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz
user$  tar  -xf  ffmpeg-release-arm64-static.tar.xz
 


安裝 python3 的套件管理工具 pip
 
user$  curl  -sS  https://bootstrap.pypa.io/get-pip.py  -o  get-pip.py

# 透過本機 python3 執行安裝(建議加上 --user 避免破壞系統預設權限)
user$  python3  get-pip.py  --user

# 因為有加 --user 參數
# 所以 pip 會安裝在 user 家目錄的 ~/.local/bin
 




安裝輕量版的 kokoro-onnx
 
# 怕將 pip 路徑加入環境變數會對 NAS 造成影響,因此暫不加
# 執行 pip 時就必須輸入完整的 pip 路徑
user$   ~/.local/bin/pip3.12  install  --no-cache-dir  kokoro-onnx  soundfile  pydub

# 因為需中文音素支援,所以還要安裝 misaki 的中文模組
user$  ~/.local/bin/pip3.12  install  --no-cache-dir  kokoro-onnx  "misaki[zh]"
 




下載音色庫
 
# 建立一個目錄存放,在本筆記,我放在 /volume1/Web/kokoro

# 下載 ONNX 模型權重 (約 300MB)
user$  wget  -O  kokoro-v1.0.onnx  https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx

# 下載音色庫 voices-v1.0.bin (約 25MB)
user$  wget  -O  voices-v1.0.bin  https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin
 


想知道有哪些音色
 
user$  python3  -c  "from  kokoro_onnx  import  Kokoro;  k=Kokoro('/volume1/Web/kokoro/kokoro-v1.0.onnx', '/volume1/Web/kokoro/voices-v1.0.bin'); print(k.get_voices())"
 

紅框內就是 kokoro 的角色音色,
第一個字母代表語言:
z:中文 (Zho / Chinese)
a:美式英語 (American English)
b:英式英語 (British English)
j:日語 (Japanese)
e:西班牙語 (Español)
f:法語 (Français)
h:印地語 (Hindi)
i:義大利語 (Italian)
p:巴西葡萄牙語 (Portuguese)

第二個字母代表性別,f 是女性(female),m 是男性(male)。

目前 kokoro 的音色列表:
美語:
af_alloy, af_aoede, af_bella, af_heart, af_jessica, af_kore, af_nicole, af_nova, af_river, af_sarah, af_sky
am_adam, am_echo, am_eric, am_fenrir, am_liam, am_michael, am_onyx, am_puck, am_santa
英語:
bf_alice, bf_emma, bf_isabella, bf_lily,
bm_daniel, bm_fable, bm_george, bm_lewis
西班牙語:
ef_dora,
em_alex, em_santa
法語:
ff_siwis
印地語:
hf_alpha, hf_beta
hm_omega, hm_psi
義大利語:
if_sara
im_nicola
日語:
jf_alpha, jf_gongitsune, jf_nezumi, jf_tebukuro
jm_kumo, pf_dora
巴西葡萄牙語:
pm_alex, pm_santa
中文:
zf_xiaobei, zf_xiaoni, zf_xiaoxiao, zf_xiaoyi
zm_yunjian, zm_yunxi, zm_yunxia, zm_yunyang


警告訊息可以不理會,因為 joblib(底層用來處理並行運算的套件)在啟動時會嘗試在共享記憶體目錄(通常是 /dev/shm)建立旗標或暫存檔。在 ASUSTOR ADM 系統下,一般使用者帳號預設對 /dev/shm 沒有寫入權限,因此 joblib 自動降級為單線程序列模式(serial mode)運作。

因為在 1GB RAM 的 NAS 上本來就建議以單線程/序列模式執行以防爆記憶體,這個降級對生成語音完全沒有負面影響。


合成中文 tts_zh.py
 
import os
import warnings
import io
import onnxruntime as ort
import soundfile as sf
from kokoro_onnx import Kokoro
from pydub import AudioSegment
from pydub.utils import which
from misaki import zh


# 限制執行緒數量,避免在 1GB RAM NAS 上過載
os.environ["OMP_NUM_THREADS"] = "2"
os.environ["ORT_NUM_THREADS"] = "2"

warnings.filterwarnings("ignore", category=UserWarning, module="joblib")

# 將靜態 ffmpeg 所在目錄放到環境變數 PATH 最前端
FFMPEG_DIR = "/volume1/Web/kokoro/ffmpeg-7.0.2-arm64-static"
os.environ["PATH"] = FFMPEG_DIR + ":" + os.environ.get("PATH", "")

# 明確指定使用支援 libmp3lame 的 ffmpeg 執行檔路徑
AudioSegment.converter = os.path.join(FFMPEG_DIR, "ffmpeg")
AudioSegment.ffmpeg = os.path.join(FFMPEG_DIR, "ffmpeg")


# 檔案路徑設定
MODEL_PATH = "/volume1/Web/kokoro/kokoro-v1.0.onnx"
VOICES_PATH = "/volume1/Web/kokoro/voices-v1.0.bin"
OUTPUT_MP3 = "/volume1/Web/mp3/lee_mood.mp3"


# 目標字串
text_input = "你現在是怎樣的心情呢?是歡喜悲傷,還是一點點不知名的愁?"


# 初始化 Kokoro ONNX
kokoro = Kokoro(model_path=MODEL_PATH, voices_path=VOICES_PATH)

# 初始化中文音素處理器 (Misaki G2P)
g2p = zh.ZHG2P()
res = g2p(text_input)
phonemes = res[0] if isinstance(res, tuple) else res

# 將中文轉為 Kokoro 需要的音素格式
print(f"轉換音素結果: {phonemes}")

# 呼叫 create,傳入音素 (lang 改用 'en-us' 或省略驗證,is_phonemes=True)
print("正在合成語音...")
samples, sample_rate = kokoro.create(
    text=phonemes,
    voice="zm_yunyang",
    speed=1.0,
    is_phonemes=True
)


# 轉存為 MP3
# 先在記憶體中建立輕量 WAV 緩衝區,再由 pydub 調用 ffmpeg 封裝成 MP3
wav_buffer = io.BytesIO()
sf.write(wav_buffer, samples, sample_rate, format="WAV")
wav_buffer.seek(0)

audio_seg = AudioSegment.from_wav(wav_buffer)
audio_seg.export(OUTPUT_MP3, format="mp3", bitrate="192k")

print(f"完成!音訊已輸出至: {OUTPUT_MP3}")
 


合成美語 tts_en.py
 
import os
import warnings
import io
import onnxruntime as ort
import soundfile as sf
from kokoro_onnx import Kokoro
from pydub import AudioSegment
from pydub.utils import which
# from misaki import zh


# 限制執行緒數量,避免在 1GB RAM NAS 上過載
os.environ["OMP_NUM_THREADS"] = "2"
os.environ["ORT_NUM_THREADS"] = "2"

warnings.filterwarnings("ignore", category=UserWarning, module="joblib")

# 將靜態 ffmpeg 所在目錄放到環境變數 PATH 最前端
FFMPEG_DIR = "/volume1/Web/kokoro/ffmpeg-7.0.2-arm64-static"
os.environ["PATH"] = FFMPEG_DIR + ":" + os.environ.get("PATH", "")

# 明確指定使用支援 libmp3lame 的 ffmpeg 執行檔路徑
AudioSegment.converter = os.path.join(FFMPEG_DIR, "ffmpeg")
AudioSegment.ffmpeg = os.path.join(FFMPEG_DIR, "ffmpeg")


# 檔案路徑設定
MODEL_PATH = "/volume1/Web/kokoro/kokoro-v1.0.onnx"
VOICES_PATH = "/volume1/Web/kokoro/voices-v1.0.bin"
OUTPUT_MP3 = "/volume1/Web/mp3/lee_mood.mp3"


# 目標字串
text_input = "你現在是怎樣的心情呢?是歡喜悲傷,還是一點點不知名的愁?"
text_input = "American professors Robert E. Hall and Michael Woodford have won the 2026 Nobel prize in economics for developing new economic models that help governments and central banks stabilize their economies to minimize the impact of external shocks. "


# 初始化 Kokoro ONNX
kokoro = Kokoro(model_path=MODEL_PATH, voices_path=VOICES_PATH)


# 合成美語語音
print("正在合成美語語音...")
samples, sample_rate = kokoro.create(
    text=text_input,
    voice="af_alloy",      # 美語女聲 (亦可更換為 am_adam, af_sarah, af_nicole 等)
    speed=1.0,
    lang="en-us"           # 指定美語
)


# 轉存為 MP3
# 先在記憶體中建立輕量 WAV 緩衝區,再由 pydub 調用 ffmpeg 封裝成 MP3
wav_buffer = io.BytesIO()
sf.write(wav_buffer, samples, sample_rate, format="WAV")
wav_buffer.seek(0)

audio_seg = AudioSegment.from_wav(wav_buffer)
audio_seg.export(OUTPUT_MP3, format="mp3", bitrate="192k")

print(f"完成!音訊已輸出至: {OUTPUT_MP3}")
 


相關筆記 ----


2026-05-27

【軟體筆記】群暉(Synology) DS223j 設定 MariaDB + Python3 環境

想要在 DS223j 跑 python 排程,因此摸索寫了這篇筆記;雖然 DS223j 底層也是 Linux,不過算是變形的 Linux,有其獨特性,所以本筆記的做法都是以不影響安全性及效率為前提。

老人家的 DS223j 系統已更新至 DSM7.2。


* 登入 DS223j web 管理系統 DSM,NAS 本身也有一些系統程式是以 python2 或 python3 撰寫,所以在【套件中心】會看到 python2 顯示已安裝,而 python3 雖然不是已安裝的狀態,但是若以 ssh 登入 DS223j 查看就會發現出廠時系統就安裝了 Python3.8.15。

因為我的程式要寫連線 DS223j 的 MariaDB,所以需要安裝【pymysql】套件,所以要與系統的 Python3.8.15 分開,另外再安裝 套件中心 的 Python3.9。

* 點選【套件中心】→【所有套件】→ 找到【Python3.9】並點擊安裝



* 以 ssh 連線進入 DS223j,本筆記採用的軟體為 MobaXterm,注意:NAS 和一般 Linux 不同,只允許系統管理者 ssh 連線。
python3.9 的安裝目錄路徑是 /var/packages/Python3.9/target/usr/bin/,執行檔名稱是 python3.9,在下圖中,可以看到套件中心自動為 python3.9 建立了 2 個捷徑,方便我們呼叫執行 python3.9。 


 
# 切換到家目錄
admin@DS223j: cd ~

# 下載並安裝 pip
admin@DS223j:~$ wget  https://bootstrap.pypa.io/pip/3.9/get-pip.py
admin@DS223j:~$ /var/packages/Python3.9/target/usr/bin/python3.9  get-pip.py

# 安裝 pymysql
admin@DS223j:~$ /var/packages/Python3.9/target/usr/bin/python3.9  -m  pip  install  pymysql
 


test.py
 
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import pymysql


# DS223j 預設關閉了 TCP/IP 連線協定
# 所以改用 socket
def get_db_connection_nas():
    """取得 NAS 資料庫連線"""
    # 請根據您的實際設定修改
    connection = pymysql.connect(
        user='資料庫連線帳號',
        password='密碼',
        database='資料庫名',
        unix_socket='/run/mysqld/mysqld.sock',
        charset='utf8',
        cursorclass=pymysql.cursors.DictCursor
    )
    return connection
    
    
def execute_and_print_query(pdo_nas, sql: str):
    print("\n" + "="*60)
    print(f"執行 SQL: {sql}")
    print("="*60)
    
    try:
        with pdo_nas.cursor() as cursor:
            # 執行 SQL 查詢
            cursor.execute(sql)
            rows = cursor.fetchall()
            if rows and len(rows) > 0:
                field_names = list(rows[0].keys())
                print(f"\n欄位: {', '.join(field_names)}")
                print("-"*60)
            
            # 取得記錄總數
            total_rows = len(rows)
            print(f"\n總共 {total_rows} 筆記錄\n")
            
            # 逐筆 print 出來
            for idx, row in enumerate(rows, 1):
                print(f"第 {idx} 筆記錄:")
                for field, value in row.items():
                    print(f"  {field}: {value}")
                print("-"*40)
            
            # 如果沒有記錄
            if total_rows == 0:
                print("查無任何記錄")
            
            return rows
            
    except Exception as e:
        print(f"執行 SQL 時發生錯誤: {e}")
        return None




def main():
    """主程式"""
    try:
        # 建立 mysql 連線
        pdo_nas = get_db_connection_nas()

        # 查詢記錄
        sql = "SELECT * FROM table1"
        result1 = execute_and_print_query(pdo_nas, sql)

        # 寫入紀錄
        sql_insert = f"""INSERT INTO table1
                        (field1, field2, field3)
                        VALUES
                        ('欄位1值', '欄位2值', '欄位3值')"""
                    
        print(f"sql = {sql_insert}")
        pdo_nas.cursor.execute(sql_insert)
        pdo_nas.commit()
        print("成功寫入")

    except Exception as e:
        print(f"發生錯誤: {e}")
        traceback.print_exc()
    


if __name__ == "__main__":
    main()
 


在儘量不更動 DS223j 的前提下,不改變 NAS 的環境變數,執行 test.py
 
# 須完整路徑,但可以是別名(捷徑),所以下面 3 種呼叫方式都行得通
admin@DS223j:/$ /var/packages/Python3.9/target/usr/bin/python3.9 test.py
admin@DS223j:/$ /var/packages/Python3.9/target/usr/bin/python3 test.py
admin@DS223j:/$ /var/packages/Python3.9/target/usr/bin/python test.py

 


2023-05-07

【Python2】計算 起、迄 時刻經過的時間

 
#!/usr/bin/python
#-*- coding:utf-8 -*-

import datetime

tBeginTime = datetime.datetime.now()
sBegin = tBeginTime.strftime("%Y.%m.%d  %H:%M:%S")

...
...

tStopTime = datetime.datetime.now()
sStop = tStopTime.strftime("%Y.%m.%d  %H:%M:%S")

print('開始時刻: '+sBegin+'\n'+
      '結束時刻: '+sStop+'\n'+
      '歷時: '+str(tStopTime-tBeginTime))


# 顯示類似下述
# 開始時間: 2023.05.05  10:19:18
# 結束時間: 2023.05.06  22:46:04
# 歷時: 1 day, 12:26:46.230894
 



2022-08-24

【Python3】FTP_TLS -- 加密連線客戶端應用

注意:Python2.7.18(含) 之後才支援加密連線。
 
 
import sys
from ftplib import FTP_TLS
import ssl


sFTPhost = '主機網址/IP'
sFTPuser = '帳號'
sFTPpwd = '密碼'


# 加密連線
ftps = FTP_TLS(sFTPhost)
# ftps.set_debuglevel(2)  # To show logs
ftps.ssl_version = ssl.PROTOCOL_TLSv1_2    # 調整為您自己的加密協定
ftps.set_pasv(True)
ftps.encoding = 'utf-8'    # 預設是 latin-1 字元, 版本 3.9 之後預設才是 utf-8
ftps.login(sFTPuser, sFTPpwd)

ftps.cwd("/dir1/dir11") # 切換在 server 的目錄
lstFTPfile = ftps.nlst()  # 列出所有檔案
for sFTPfile in lstFTPfile:
    if ('.jpg' in sFTPfile):
        print('sFTPfile = ' + sFTPfile)

        localfile = open(sFTPfile,'wb')   # 本地端要儲存的檔案
        ftps.retrbinary('RETR ' + sFTPfile, localfile.write) # 下載檔案
        localfile.close()


        # 上傳檔案
        localfile = open(sFTPfile,'rb')
        ftps.cwd("/dir1/dir12") # 切換在 server 的目錄
        ftps.storbinary('STOR '+sFTPfile, localfile)

        break
ftps.quit
 


【Python2】FTP 客戶端應用

不確定是不是版本限制,Python2.7 無法加密連線。
 
 
#!/usr/bin/python
#-*- coding:utf-8 -*-

import sys

# FTP
from ftplib import FTP

sFTPhost = '主機網址/IP'
sFTPuser = '帳號'
sFTPpwd = '密碼'


ftp = FTP(sFTPhost, sFTPuser, sFTPpwd)
# ftp = FTP_TLS(sFTPhost, sFTPuser, sFTPpwd)
ftp.cwd("/dir1/dir11") # 切換在 server 的目錄
lstFTPfile = ftp.nlst()  # 列出所有檔案
for sFTPfile in lstFTPfile:
    print 'sFTPfile = ' + sFTPfile
    localfile = open(sFTPfile,'wb')   # 本地端要儲存的檔案
    ftp.retrbinary('RETR ' + sFTPfile, localfile.write) # 下載檔案
    localfile.close()


    # 上傳檔案
    localfile = open(sFTPfile,'rb')
    ftp.cwd("/dir1/dir12") # 切換在 server 的目錄
    ftp.storbinary('STOR '+sFTPfile, localfile)

    break

ftp.quit
 


2022-08-23

【Python】requests 的應用

參考資料 ----

Requests 是一個 Python HTTP 庫,但不是內建,所以要另外安裝
 
#!/usr/bin/python
#-*- coding:utf-8 -*-

import requests

# 最 陽春/基本 的語法
# res = requests.get('https://網址,一定要 https')
# print("res = "+res.text)    # 顯示 server 傳來的字串內容
# 如果網站用的是像 Let's Encrypt 的免費認證
# 造成傳來的字串內容為 An appropriate representation of the requested resource could not be found on this server. This error was generated by Mod_Security. 的錯誤訊息
# 可加下列的 headers
headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0', }
res = requests.get('https://網址,一定要 https', headers=headers)


param = {'參數名1':參數值1, '參數名2':參數值2}
res = requests.get('https://網址,一定要 https', param, headers=headers)    # 網址後帶參數 → https://網址?參數名1=參數值1&參數名2=參數值2



# print(res.json())    # 因為字串內容是 json, 所以這指令可顯示 json 內容

lstDataset = res.json()    # 將 json 內容存入 list
for row in lstDataset:
    print("prod no = "+row['prodno']+", prod name = "+row['prodname'])
 




2022-08-22

【Python3】微軟的 文字轉換語音(Text To Speach, TTS)

參考資料 ----

申請 "微軟語音服務" 請參考最下方的筆記

系統需求:
Python 3.7(含)↑
 
# 目前(2022.08.22) 尚不支援 Ubuntu22
# Ubuntu20 / Linuxmint20 必須安裝這些套件
[user]$ sudo  apt  install  python3-pip  build-essential  libssl-dev  libasound2 wget

[user]$ pip  install  azure-cognitiveservices-speech
 

tts.py
 
import azure.cognitiveservices.speech as speechsdk

speech_config = speechsdk.SpeechConfig(subscription="您申請的金鑰", region="eastasia")    # eastasia 是我在微軟申請服務選取的區域,請置換為您申請的區域
# audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True)    # 即時由喇叭播放
audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=False, filename="king.mp3")    # 存成 mp3

# The language of the voice that speaks.
speech_config.speech_synthesis_voice_name='zh-TW-Yating'    // 語音角色: 雅婷

speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)

# Get text from the console and synthesize to the default speaker.
text = "有一位國王,他擁有一座美麗的花園,花園裡有一棵結著金蘋果的樹。國王派人每天清點樹上的蘋果有幾顆。"

speech_synthesis_result = speech_synthesizer.speak_text_async(text).get()

if speech_synthesis_result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
    print("Speech synthesized for text [{}]".format(text))
elif speech_synthesis_result.reason == speechsdk.ResultReason.Canceled:
    cancellation_details = speech_synthesis_result.cancellation_details
    print("Speech synthesis canceled: {}".format(cancellation_details.reason))
    if cancellation_details.reason == speechsdk.CancellationReason.Error:
        if cancellation_details.error_details:
            print("Error details: {}".format(cancellation_details.error_details))
            print("Did you set the speech resource key and region values?")
           

 


相關筆記 ----

2022-08-01

【Python】格式化數字顯示

 
iFmt = 1234567890.123456

# , 表示要有千位符號
# .4f 表示 要顯示 4 位小數
print(format(iFmt,",.4f"))    # 顯示 1,234,567,890.1235 (小數點部份也會四捨五入)
 

2022-07-15

【Python】日期時間的運算

參考資料 ----

 

import  datetime
from  datetime  import timedelta
import time
from time import sleep

...
...

tBegin = datetime.datetime.now()    # 開始時間
print('tBegin ='+str(tBegin))
# 輸出結果 → tBegin = 2022-07-15 20:16:51.050644

print(tBegin.strftime("%Y.%m.%d  %H:%M:%S 星期 %w, 第 %U 週, 第 %j 日"))    # 格式化日期時間
# 輸出結果 → tBegin = 2022.07.15 20:16:51 星期 5, 第 28 週, 第 196 日
# 注意: 第 1 週是 0, 而且是以 星期日做為一週的第 1 天, 所以實際上是 第 29 週
# 若要以 星期一 做為一週的第 1 天, 則參數為 %W
# %w 參數, 0 表 星期日, 6 表 星期六


time.sleep(66)    # 暫停 66 秒
tStop = datetime.datetime.now()    # 結束時間
print("歷時: "+str(tStop - tBegin))
# 輸出結果 → 歷時: 0:01:06.011046


# 當 2 個日期時間運算後, 結果會存入 datedelta 的類別

# 利用 timedelta 進行日期/時間 的 前/後移,
# 往後移, 數字是 正數
# 往前移, 數字是 負數
tStop = tBegin + timedelta(days=1, hours=2, minutes=-10)    # 共有 days, seconds, microseconds, milliseconds, minutes, hours, weeks 這幾種屬性
# 往後移 1 天 又 2 小時, 但 "往前移" 10 分
print('tStop ='+str(tStop))
# 輸出結果 → tStop = 2022-07-16 22:06:51.050644


tSomeday = datetime.datetime(2020, 5, 17, 13, 0, 9)
print('tSomeday = '+str(tSomeday))
# 輸出結果 → tSomeday = 2020-05-17 13:00:09

tSomeday = datetime.datetime(2020, 5, 17)    # 只有日期
print('tSomeday = '+str(tSomeday))
# 輸出結果 → tSomeday = 2020-05-17 00:00:00
 




2022-06-25

【Python2】透過郵局 web service 查詢 3+3(6碼) 郵遞區號

參考資料 ----


郵局提供了 web service 方便 大量且快速查詢郵遞區號。

注意:要申請!要申請!要申請!
1. 開放 個人/團體/企業 申請,但要有固定 IP。
2. 要填寫 web service 系統介接申請書,郵局才會將貴公司 IP 列入白名單,貴公司的查詢程式才能正常運作。
 
#!/usr/bin/python
#-*- coding:utf-8 -*-

import requests
import xml.etree.cElementTree as ET

...
...

def main():
    # sAddr = '高雄市前金區'
    # sAddr = '高雄市前金區光復一街'
    sAddr = '完整的地址'
    sZip = GetZip(sAddr)    # 正式
    # sZip = GetZip(sAddr,0)    # 測試
    if(sZip is None):
        print('查詢失敗')
    else:
        print('sZip = '+sZip)


# 郵局 web service, 取得 3+3 郵遞區號
# 傳入的參數 bTest: 執行函數的模式, 預設為 "正式"=1, 若為 0 時, 則列出傳回的各節點(node) 內容
def GetZip(ss, bTest=1):
    mydata = (''
                '<soap12:envelope xmlns:soap12="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
                    '<soap12:body>'
                        '<getzipcode xmlns="http://tempuri.org/">'
                            '<addrstr>' + ss + '</addrstr>'
                        '</getzipcode>'
                    '</soap12:body>'
                '</soap12:envelope>')
    url = 'http://33wsp.post.gov.tw/lzwzip/tzip33.asmx'
    headers = {'content-type': 'application/soap+xml'}
    response = requests.post(url,data=mydata,headers=headers)
    root = ET.fromstring(response.content)
    sResult = None
    for child in root.iter('*'):
        if(bTest==0):
            print(child.tag, child.text)
        if (child.tag=='{http://tempuri.org/}GetZipCodeResult'):
            if(child.text is None):
                pass
            else:
                if(len(child.text)==6):
                    sResult = child.text
            break
    
    return sResult
 

郵局傳回的內容如下:
 
# 當地址不完整時, 傳回的值為空值 None
('{http://www.w3.org/2003/05/soap-envelope}Envelope', None)
('{http://www.w3.org/2003/05/soap-envelope}Body', None)
('{http://tempuri.org/}GetZipCodeResponse', None)
('{http://tempuri.org/}address', None)


# 當地址完整時
('{http://www.w3.org/2003/05/soap-envelope}Envelope', None)
('{http://www.w3.org/2003/05/soap-envelope}Body', None)
('{http://tempuri.org/}GetZipCodeResponse', None)
('{http://tempuri.org/}GetZipCodeResult', '801002')    # 當有資料成功傳回時, 郵遞區號在這
 


經實驗後,當地址不完整時, web service 會傳回 None 或 3 碼郵遞區號
2022.06.25 測試,之前還會有傳回 3 碼郵區的情況,現在好像只會傳回 None 或 6 碼郵區。

當地址內含有 路/街... 等,即使沒有門牌號碼,郵局 web service 就可能傳回 6 碼郵區,例如:"高雄市前金區光復一街",因此 並不能將 web service 做為判別地址完整性的方法。





2022-06-12

【Python】連線 MS SQL Server

參考資料 ----

微軟官方建議的驅動程式有 2 個 -- pyodbc、pymssql,在本例採用的是 pymssql。
 
#!/usr/bin/python
#-*- coding:utf-8 -*-

import  pymssql

...
...

def  main():
    conn = pymssql.connect(server='主機IP', user='帳戶名', password='密碼', database='資料庫名')
    cursor = conn.cursor(as_dict=True)    # 啟用以欄位名讀出欄位值
    
    # 查詢
    ssql = ("SELECT * FROM 資料表名 \n"
            "    WHERE 1=1 \n"
            "        AND 篩選條件1 \n"
            "        AND 篩選條件2 \n"
            "        AND ...")
    # SQL 語句以 括弧 包起,就可以寫多列
    # 每列最末是讓語句折行
    # 除錯時較易閱讀
    
    cursor.execute(ssql)
    
    # 方法一:
    # 一次只取出一筆記錄
    row = cursor.fetchone()
    while row:
        ff1 = row['欄位名']
        ...
        ...
        row = cursor.fetchone()
        
        
    # 方法二:
    # 一次取出所有記錄,並存入 list 中
    rows = cursor.fetchall()
    print("記錄數:"+str(cursor.rowcount))    # 一定要先 fetchall() 才能知道記錄數, 否則傳回值是 -1
    print("record count = "+str(len(rows)))    # 此法是讀出 list 有多少 element
    for rr in rows:
        ff1 = rr['欄位名']
        ...
        ...
        
        
    # 更新
    ssql = ("UPDATE 資料表名 \n"
            "    SET 欄位1='值1' \n"
            "      欄位2='值2' \n"
            "    WHERE 篩選條件")
    cursor.execute(ssql)
    conn.commit()    # 預設是沒有 auto commit 的
 


2022-04-09

【Python2】整數與浮點數的混合運算

若沒有額外的設定,Python2 只支援整數運算,所以

2328/80  等於 2328//80
 
from __future__ import division    # 這要寫在最前面, 啟用浮點數除法
import math

...
...

i0 = 2328
i1 = 2328 / 80    # 浮點數除法 29.1
i2 = 2328 // 80    # 整數除法 29
i3 = i1 - i2    # 0.1
i4 = int(math.floor(i3 * 80))    # floor 是將浮點數直接捨去小數部份,但會變成 8.0
                        # 所以再以 int() 取整數
print ("i1 = "+str(i1))    # 29.1
print ("i2 = "+str(i2))    # 29
print ("i3 = "+str(i3))    # 0.1
print ("i4 = "+str(i4))    # 8

2020-07-16

【Python】讀出 BIG5 中文的內碼

土法鍊鋼的做法:
 
#!/usr/bin/python
#-*- coding:utf-8 -*-

# 因為程式碼本身是 utf-8, 所以先強制將字串轉碼成 BIG5
strbig5 = "big 中文測試".decode('utf8','ignore').encode('big5','ignore')

ii = 0
while ii<(len(strbig5)-1):
    bCH = False
    s1 = strbig5[ii]
    s2 = ""
    if (ord(s1)>127):
        bCH = True
        s2 = strbig5[ii+1]
        print (s1+s2).decode('big5','ignore').encode('utf8','ignore') + " 的 ASCII :  " + hex(ord(s1)).strip('0x') + hex(ord(s2)).strip('0x') + "\n"
    else:
        print s1 + " 的 ASCII :  " + hex(ord(s1)) + "\n"
    if bCH:
        ii += 1
    
    ii += 1
 

2020-06-05

【Python】FTP client


 
from ftplib import FTP
from ftplib import FTP_TLS    # 如果 FTP server 有支援加密連線

...
...

# 定義 FTP 連線資訊
sFTPhost = 'IP 或 網址'
sFTPport = 20    # 本例因使用預設的 port,故不會用到
sFTPuser = '帳號'
sFTPpwd = '密碼'

# 下載的檔案要存放的位置
local_path = "/case/python/download"

ftp = FTP(sFTPhost, sFTPuser, sFTPpwd)
ftp.cwd("download")    # FTP server 端切換至指定的目錄
lstFTPfile = ftp.nlst()    # 列出所有檔案
for sFTPfile in lstFTPfile:
    # 過濾要下載的檔案,在本例,只下載檔名中有 ".ZIP" 的檔案
    if ('.ZIP' in sFTPfile):
        localfile = open(local_path+'/'+sFTPfile,'wb')    # 本地端要儲存的檔案
        ftp.retrbinary('RETR ' + sFTPfile, localfile.write)    # 下載檔案
        localfile.close()
ftp.quit    # 結束連線
 





2019-10-05

【python 2.7】寄發郵件功能

 
#!/usr/bin/python
#-*- coding:utf-8 -*-

...
...

from smtplib import SMTP  
from smtplib import SMTPRecipientsRefused  
from poplib import POP3
from email.mime.text import MIMEText

...
...

# 定義 mail 相關的變數
smtpserver = '郵件主機 IP 或 網址'
username = '發送郵件的帳號'
password = '發送郵件的密碼'
sMaillist = 'johnber@email.hanshin.com.tw,johnbertw@gmail.com'    # 收件人列表, 以逗點分隔, 中間無空白


# 寄送郵件函式
def SendMail(sender, maillist, title, content):
    # 將收件人分隔開
    lstReceiver = maillist.split(",")

    lstMail = MIMEText(content, 'plain', 'utf-8')    # 寄發純文字格式的信件
    lstMail['From'] = sender
    lstMail['To'] = maillist
    lstMail['Subject'] = title
    # sendSer = SMTP("smtp.gmail.com:587")
    sendSer = SMTP(smtpserver)
    sendSer.ehlo()
    sendSer.starttls()    # 主機有使用 TLS 加密連線設定者適用
    sendSer.login(username, password)
    sendSer.sendmail(username, lstReceiver, lstMail.as_string())

...
...

def main():
    # 信件內容, 以 \n 斷行, 
    sMail = "敬啟者:\n" +
            "...\n" + 
            "...\n" + 
            "with regards"
    # SendMail(使用者帳號(寄件人), 收件人列表, 主旨, 信件內容)
    SendMail(username, sMaillist, 'title', sMail)

...
...

if __name__ == "__main__":
    main()

 

2018-08-11

【Python】CentOS7 安裝 Python3

參考資料 ----
How to install Python 3 on CentOS 7
軟件選集(SCL)軟件庫
How can I make a Red Hat Software Collection persist after a reboot/logout?


想在 CentOS7 使用 Python3 是有點小麻煩的。

RedHat 的套件政策是:主版本週期定為 10 年,主版本釋出後,所包含的套件都會維持主版本不變;例如:CentOS7 內含的 Python 版本是 2.7,在這 10 年間,Python 的版本就會是 2.7.x 的更新。

Python 因為太好用,所以許多套件將 Python 綁在一起,如果想要移除 Python2,會因為套件的相依性而連帶移除了其他原本不想或不能移除的套件!例如:重要的套件管理 yum。

但是,10 年是會有滄海滄田的變化的,雖然 RedHat / CentOS 的主要對象是企業,企業重視系統的穩定使用,同時也希望在下一個主版本釋出前,可以對新工具 / 新功能進行 開發 / 測試 / 研究 / 實驗。

RedHat 聽到了企業的聲音,推出了 Software Collections(SCL),SCL 包含了新的套件可供安裝,其中 Python3 的版本,目前更新到 3.6。


[root]# vi  /etc/yum.repos.d/CentOS-Base.repo
→ 找到 [extras]
→ 將 enabled 值改成 1
→ :x 存檔離開


[root]# yum  install  centos-release-scl


偷懶,安裝所有以 rh-python36 字首的套件  :-P

[root]# yum  install  rh-python36*


此時 Python2 與 Python3 是 並存 於 CentOS7 內的,這時若查詢系統預設執行的 Python 版本

[root]# python  --version

會回應顯示 python 2.7.5



若要以 Python3 執行,則要先下指令:

[root]# scl  enable  rh-python36  bash

再查詢一次

[root]# python  --version

這次就會回應 python 3.6.3

但因為 SCL  原意就是讓我們可以同時使用 Python2 和 Python3,所以當您 登出 或 重新開機,預設要執行的 Python 版本又會回到 2.7。


若要每一次登入,預設執行的 Python 版本都是 Python3,方法如下(不過 RedHat 也說這是解套方法,他們還沒想出正解):


建立 rh-python36.sh

[root]# vi /etc/profile.d/rh-python36.sh
 
#!/bin/bash
source  scl_source  enable  rh-python36
 
:x 存檔離開

登出系統,再重新登入

[root]# python  --version
系統就會回應

python 3.6.3



方法二:

Python3 會安裝在下述的路徑

/opt/rh/rh-python36/root/bin/python

在您撰寫的 python 程式的第一行指定要執行的 Python 直譯器

例如,您的程式名為 test.py
 
#!/opt/rh/rh-python36/root/bin/python
import sys
import platform

print(platform.python_version())
 

然後,變更 test.py 的屬性為可執行

[root]# chmod  a+x  test.py

這樣,您的程式會變成可執行檔

執行方式為:切換到 test.py 所在的目錄下,直接執行 test.py

[root]# ./test.py

2018-06-26

【Python】CentOS 7 使用 cx_Oracle 連線 Oracle 9i

參考資料 ----
Python Oracle drivers


Python 連線 Oracle 的驅動程式有很多種,因為 CentOS7 預設使用 cx_Oracle,並且其他驅動有涉及到版權規範,因此老人家還是選擇 cx_Oracle


* 以 root 身份登入 CentOS 7


* 到 Oracle 官網,(若無帳號就申請建立一個),登入、下載並安裝  oracle-instantclient11.2-basic.x86_64.rpm,   oracle-instantclient11.2-devel.x86_64.rpm, oracle-instantclient11.2-sqlplus.x86_64.rpm
因為 Oracle 官網常改版,所以自己要花點時間去摸索尋找。


* 新增並編輯 oracle.sh
[root]# vi  /etc/profile.d/oracle.sh
 
ORACLE_HOME="/usr/lib/oracle/11.2/client64"
PATH=$PATH:$ORACLE_HOME/bin
export PATH
LD_LIBRARY_PATH=$ORACLE_HOME/lib:/usr/lib:/usr/local/lib
export LD_LIBRARY_PATH
 
:x 存檔離開

重開機, 讓 oracle.sh 設定生效


新增 Python 的測試程式 oratest.py
內容如下:
 
# -*- coding: utf-8 -*-
import cx_Oracle
conn = cx_Oracle.connect("帳號/密碼@IP/資料庫名", encoding = "UTF-8", nencoding = "UTF-8")
curs = conn.cursor()
curs.execute('SELECT  *  FROM  資料表名')


# 原生方式是以欄位順序取得欄位值
# 例如 row[0], row[1], ...
# 我們要很明確知道 row[0], row[1] 各代表什麼欄位
# 可讀性不高

# 我們可以繞個彎
# 將資料集的欄位讀出並存入 list
columns = [i[0] for i in curs.description]

# 這裡要注意
# row 對欄位名並沒有大小寫分別
# 但因我們以 curs.description 讀出的欄位名是大寫
for row in curs:  
    print 'field1 = ' + row[columns.index('FIELD1')]

conn.close()
 


附帶看一下 curs.description 的內容:

[('FIELD1', , 2, 2, None, None, 0), ('FIELD2', , 8, 8, None, None, 0), ...]




要注意的是,若您的 Python 程式要放入排程跑,則要再加環境變數的設定:

[root]# vi /etc/crontab
 
SHELL=/bin/bash

# 設置 oracle 環境變數
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/lib/oracle/11.2/client64/bin
ORACLE_HOME="/usr/lib/oracle/11.2/client64"
LD_LIBRARY_PATH=/usr/lib/oracle/11.2/client64/lib:/usr/lib:/usr/local/lib

...
...

要執行的 python 程式
 
:x 存檔離開


不然您會發現,crontab 到指定時間有執行,資料卻沒存入 Oracle。