2025年4月15日 星期二

好站 : 最佳 GitHub Copilot 設定

上週在 GitHub Copilot 課程中得知保哥 Will 在 GitHub 上的詳細教學 :


雖然似乎目前也沒用到, 但我瀏覽器開太多頁啦! 關閉前還是先記一下得好, 或許明天就要用也說不定 (就像 Midjourney 收費後我就沒再用了, 但最近看書之後突然想付費來鑽研一下). 

2025年4月14日 星期一

更改 mp3 音訊檔標籤的好工具 : Mp3Tag

周末在測試 Telegram API 傳送 mp3 檔案時發現有些音訊檔傳到 Telegram 時會顯示歌曲名稱與演唱者, 但有些不會, 經詢問 AI 得知原來 Telegram 會去讀取 mp3 檔案 metadata 的 ID3 tag, 如果裡面有設定標題 (Title 欄位) 與表演者 (Artist 欄位), 那就會將其顯示在訊息上, 否則就顯示檔案名稱與 "Unknown artist". 修改 ID3 tag 有一個好用的工具 : Mp3Tag, 我從 XP 時代用到現在, 下載網址 :


執行 Mp3Tag 後按左上角檔案選單的 "變更目錄" : 




選取你的 mp3 檔案所在的目錄 (資料夾) :




這時會顯示該目錄下所有的 mp3/mp4 檔案列表, 點選 mp3 檔的標題 (Title 欄位) 與表演者 (Artist 欄位) 直接在儲存格中編輯文字即可 :




改好後立即生效, 直接關掉 Mp3Tag 程式即可毋須存檔動作. 這樣在任何播放器上都會顯示曲名與歌手姓名了. 

Telegram Bot API 測試筆記索引

因為好用的 LINE Notify 免費服務於 2025-04-01 終止服務, 經評估後決定轉換到 Telegram 平台, 因為其 API 簡單且功能豐富, 轉換成本也不高, 所以就利用春假期間花了兩周測試, 基本上就將之前 ESP32 物聯網與圖書館/股票/技術網站爬蟲都順利改用 Telegram 了.

為了搜尋查閱方便, 將測試過程留下的筆記做成如下之索引 : 



~ 進行中 ~

2025年4月13日 星期日

Python 學習筆記 : 用 Telegram Bot 取代 LINE Notify (八)

本篇旨在將前一篇用 HTTP API 傳送 mp3 音訊的程式碼改成用 Bot 物件來實作. 

本系列之前的文章參考 :


我將 Telegram Bot API 權杖 (token) 與聊天室識別碼 (chat_id) 都存放在環境變數檔 .env 裡面, 利用 dotenv 套件載入後再用 os 模組讀取出來放在 token 與 chat_id 變數裡 : 

>>> from dotenv import load_dotenv   
>>> load_dotenv()    
True 
>>> import os    
>>> token=os.environ.get('TELEGRAM_TOKEN')   
>>> chat_id=os.environ.get('TELEGRAM_ID')   

# load_secrets_py
from dotenv import load_dotenv  
import os  

load_dotenv()    
token=os.environ.get('TELEGRAM_TOKEN')   
chat_id=os.environ.get('TELEGRAM_ID') 

參考 :


Telegram 支援傳送 mp3/m4a 音訊格式, 可在 App 上直接播放. 


11. 利用 Bot 物件傳送音訊 :     

新版 (v20+) 的 python-telegram-bot 套件使用非同步架構, 函式只要有呼叫 Bot 物件之方法都要用 async 修飾, 且要用 await 去呼叫 Bot 物件方法. 關於 Bot 物件用法參考本系列第二篇測試 :



(1). 傳送網路音訊 :   

使用 Bot 物件傳送單一網路影片的程式碼如下 :

# telegram_send_web_audio_bot.py
import asyncio
from telegram import Bot

async def telegram_web_audio(token, chat_id, audio_url, caption=None, parse_mode='Markdown'):
    bot=Bot(token=token)
    try:
        await bot.send_audio(
            chat_id=chat_id,
            audio=audio_url,
            caption=caption,
            parse_mode=parse_mode
            )
        print("Audio message sent successfully.")
        return True
    except Exception as e:
        print(f'Telegram API Error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    audio_url='https://yaohuang1966.github.io/media/glorious_days_clip.mp3'
    caption='🎵 一起聽聽這首歌吧'
    asyncio.run(telegram_web_audio(token, chat_id, audio_url, caption))

測試結果如下 :

>>> audio_url='https://yaohuang1966.github.io/media/glorious_days_clip.mp3'   
>>> caption='🎵 一起聽聽這首歌吧'     
>>> asyncio.run(telegram_web_audio(token, chat_id, audio_url, caption))     
Message(audio=Audio(duration=11, file_id='CQACAgQAAxkDAAPGZ_tZsQl1KAm0JUvrQVV8HbuAvBEAAkcIAAJJWt1TD1EgCp7QdWk2BA', file_name='glorious_days_clip.mp3', file_size=176877, file_unique_id='AgADRwgAAkla3VM', mime_type='audio/mpeg'), caption='🎵 一起聽聽這首歌吧', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 13, 9, 15, 11, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, message_id=211, supergroup_chat_created=False)
Audio message sent successfully.
True

查看 App 有收到這首歌 :




使用 Bot 物件傳送多個音訊時須使用 InputMediaAudio 類別來打包這些音訊, 程式碼如下 :

# telegram_send_web_audios_bot.py
import asyncio
from telegram import Bot, InputMediaAudio

async def telegram_web_audios(token, chat_id, media_list, caption_list, parse_mode='Markdown'):
    bot=Bot(token=token)
    media_group=[]
    for url, caption in zip(media_list, caption_list):
        media_group.append(InputMediaAudio(
            media=url,
            caption=caption,
            parse_mode=parse_mode
            )
        )
    try:
        await bot.send_media_group(chat_id=chat_id, media=media_group)
        print('Audio group sent successfully.')
        return True
    except Exception as e:
        print(f'Telegram API Error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    audio_urls=[
        'https://yaohuang1966.github.io/media/glorious_days_clip.mp3',
        'https://yaohuang1966.github.io/media/your_answer_clip.mp3'
        ]
    captions=['光輝歲月', '你的答案']
    asyncio.run(telegram_web_audios(token, chat_id, audio_urls, captions))

測試結果如下 :

>>> audio_urls=[
    'https://yaohuang1966.github.io/media/glorious_days_clip.mp3',
    'https://yaohuang1966.github.io/media/your_answer_clip.mp3'
    ]   
>>> captions=['光輝歲月', '你的答案']    
>>> asyncio.run(telegram_web_audios(token, chat_id, audio_urls, captions))   
(Message(audio=Audio(duration=11, file_id='CQACAgQAAxkDAAPGZ_tZsQl1KAm0JUvrQVV8HbuAvBEAAkcIAAJJWt1TD1EgCp7QdWk2BA', file_name='glorious_days_clip.mp3', file_size=176877, file_unique_id='AgADRwgAAkla3VM', mime_type='audio/mpeg'), caption='光輝歲月', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 13, 13, 1, 1, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13956394088384405', message_id=220, supergroup_chat_created=False), Message(audio=Audio(duration=22, file_id='CQACAgQAAxkDAAPHZ_tbuy8Iwqm0xeFtD-r7m4crAVoAApoHAAK7nN1T6wcAAe_BDvXyNgQ', file_name='your_answer_clip.mp3', file_size=665844, file_unique_id='AgADmgcAAruc3VM', mime_type='audio/mpeg', performer='黃霄雲', title='你的答案'), caption='你的答案', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室ID>, type=<ChatType.PRIVATE>, username='yhhuang1966'), date=datetime.datetime(2025, 4, 13, 13, 1, 1, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13956394088384405', message_id=221, supergroup_chat_created=False))

查看 App 有收到這兩首歌 :




(2). 傳送本機音訊 :   

傳送本機音訊也是呼叫 Bot 物件的 send_audio() 方法, 但要先用 open() 開啟 mp3 檔, 將檔案參考傳給 audio 參數即可 : 

# telegram_send_local_audio_bot.py
import asyncio
from telegram import Bot

async def telegram_local_audio(token, chat_id, audio_path, caption=None, parse_mode='Markdown'):
    bot=Bot(token=token)
    try:
        with open(audio_path, 'rb') as audio_file:
            await bot.send_audio(
                chat_id=chat_id,
                audio=audio_file,
                caption=caption,
                parse_mode=parse_mode
                )
        print("Audio message sent successfully.")
        return True
    except Exception as e:
        print(f'Telegram API Error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    audio_path='your_answer_clip.mp3'
    caption='🎵 一起來聽你的答案'
    asyncio.run(telegram_local_audio(token, chat_id, audio_path, caption

測試結果如下 : 

>>> audio_path='your_answer_clip.mp3'   
>>> caption='🎵 一起來聽你的答案'   
>>> asyncio.run(telegram_local_audio(token, chat_id, audio_path, caption))    
Message(audio=Audio(duration=22, file_id='CQACAgUAAxkDAAPeZ_u7puXuDAXCL8unhK5IRvr4KMUAArwYAAIU-uBXLrY47umaGaU2BA', file_name='your_answer_clip.mp3', file_size=665844, file_unique_id='AgADvBgAAhT64Fc', mime_type='audio/mpeg', performer='黃霄雲', title='你的答案'), caption='🎵 一起來聽你的答案', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 13, 13, 27, 2, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, message_id=222, supergroup_chat_created=False)
Audio message sent successfully.
True

查看 App 有收到這首歌 :




傳送本機多個音訊, 與上面傳送多個網路音訊一樣要用 InputMediaVideo 類別來將各音訊檔打包, 差別只是要先開啟音訊檔, 再將檔案參考打包成 InputMediaAudio 物件串列, 然後於 呼叫 Bot 物件的 send_media_group() 方法時把它傳給 media 參數即可. 完整程式碼如下 : 

# telegram_send_local_audios_bot.py
import asyncio
from telegram import Bot, InputMediaAudio

async def telegram_local_audios(token, chat_id, audio_paths, captions, parse_mode='Markdown'):
    bot=Bot(token=token)
    media=[]  # 儲存用 InputMediaAudio 打包後的音訊
    files=[open(path, 'rb') for path in audio_paths] # 儲存所有開啟的音訊檔案
    try:        
        for i, f in enumerate(files):  # 建立 InputMediaAudio 物件串列
            caption=captions[i] if i < len(captions) else None
            media.append(InputMediaAudio(media=f, caption=caption, parse_mode=parse_mode))
        await bot.send_media_group(chat_id=chat_id, media=media)
        print('Audio group message sent successfully.')
        return True
    except Exception as e:
        print(f'Telegram API Error: {e}')
        return False
    finally:
        for f in files:
            f.close()

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    audio_paths=['glorious_days_clip.mp3', 'your_answer_clip.mp3']
    captions=['🎵 光輝歲月', '🎶 你的答案']
    asyncio.run(telegram_local_audios(token, chat_id, audio_paths, captions))

測試結果如下 : 

>>> audio_paths=['glorious_days_clip.mp3', 'your_answer_clip.mp3']  
>>> captions=['🎵 光輝歲月', '🎶 你的答案']   
>>> asyncio.run(telegram_local_audios(token, chat_id, audio_paths, captions))     
(Message(audio=Audio(duration=11, file_id='CQACAgUAAxUHZ_vd96HtCw6rZqYGWC8G55JmDtsAAhUZAAIU-uBXfuyPNXaOhAABNgQ', file_name='glorious_days_clip.mp3', file_size=179078, file_unique_id='AgADFRkAAhT64Fc', mime_type='audio/mpeg', performer='Beyond', title='光輝歲月'), caption='🎵 光輝歲月', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 13, 15, 53, 27, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13956476862917125', message_id=225, supergroup_chat_created=False), Message(audio=Audio(duration=22, file_id='CQACAgUAAxUHZ_vd91l_mZCQvrteW59nE_FxdxsAAhYZAAIU-uBXMPTgePwhh3c2BA', file_name='your_answer_clip.mp3', file_size=665844, file_unique_id='AgADFhkAAhT64Fc', mime_type='audio/mpeg', performer='黃霄雲', title='你的答案'), caption='🎶 你的答案', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 13, 15, 53, 27, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13956476862917125', message_id=226, supergroup_chat_created=False))
Audio group message sent successfully.
True

查看 App 有收到這兩首歌 :




因為我已經用 MP3Tag 軟體更改光輝歲月的 ID3 Tag, 所以這次會顯示曲名與歌手了. 

2025 年第 15 周記事

本周繼續春假的 Telegram API 測試工作, 經過集中火力密集測試, 目前已能掌握從 LINE Notify 無縫移轉到 Telegram 的方法了, 只是還需要一點時間去修改好幾個爬蟲程式. LINE Notify 要終止服務其實公告大半年了, 但一直沒時間處理, 我都要火燒屁股了才不得不面對, 被逼去處理後才發現似乎也沒那麼棘手.

最近因為買了 Stable Diffusion 的書, 利用懶人包順利在去年買的 MSI 電競桌機上安裝 Stable Diffusion, 開始了我的 AI 繪圖學習旅程, 萬事起頭難, 只要衝過這一關, 後面就容易了. 這台主機三月底已過保, 所以依計畫打算買一顆 2TB SSD 來當 D 碟, 因為接下來做 LLM 與 AI 繪圖需要夠大儲存空間. 

本周最大的新聞莫過於台股在春假休市結束後反應川普的 4/2 關稅解放日衝擊, 果然周一開市馬上幾乎全市場鎖死, 大盤跌幅超過兩千點, 我從沒看過這種一價到底的盛大場面. 由於之前疫情的教訓, 這回我沒衝這麼快, 0056 我是慢慢往下買, 到周三才買了近 20 張, 哪知川普突然說沒實施報復的國家給予 90 天 10% 關稅, 市場因此反彈, 0056 我最低買到 28.1 元而已. 但向來買不到的台積電零股卻成交 20 股, 終於晉身台積電股東, 哈哈. 

Python 學習筆記 : 用 Telegram Bot 取代 LINE Notify (七)

本篇紀錄如何用 Telegram Bot API 透過 HTTP 傳送音訊.  

本系列之前的文章參考 :


我將 Telegram Bot API 權杖與聊天室識別碼都存放在環境變數檔 .env 裡面, 利用 dotenv 套件載入後再用 os 模組讀取出來放在 token 與 chat_id 變數裡 : 

>>> from dotenv import load_dotenv   
>>> load_dotenv()    
True 
>>> import os    
>>> token=os.environ.get('TELEGRAM_TOKEN')   
>>> chat_id=os.environ.get('TELEGRAM_ID')   

# load_secrets_py
from dotenv import load_dotenv  
import os  

load_dotenv()    
token=os.environ.get('TELEGRAM_TOKEN')   
chat_id=os.environ.get('TELEGRAM_ID') 

參考 :


Telegram 支援 mp3 與 m4a 音訊檔案傳送, 可在 Telegram App 上直接播放. 我在 GitHub 上準備了下面兩個 mp3 檔案做為測試用的標本 : 


前者取自 Beyond 的 "光輝歲月" 前 11 秒片段; 後者則是取自黃霄雲 "你的答案" 前 22 秒片段. 可以下載後做為測試傳送本機 mp3 檔案之用. 以下測試將直接使用函式, 不再一步一步地在命令列測試. 


10. 利用 HTTP API 傳送音訊 :     

Telegram Bot API 傳送音訊的 API 是 sendAudio, 作法與傳送影片類似, 但多了 title (音樂標題) 與 performer (演唱者) 這兩個參數. 


(1). 傳送網路音訊 :       

傳送單一個 mp3 音訊的完整程式碼如下 :

# telegram_send_web_audio.py
import requests

def telegram_web_audio(token, chat_id, audio_url, caption=None, parse_mode='Markdown'):
    url=f'https://api.telegram.org/bot{token}/sendAudio'
    data={
        'chat_id': chat_id,
        'audio': audio_url,
        'caption': caption,
        'parse_mode': parse_mode,
        }
    try:
        with requests.post(url, data=data) as r:
            r.raise_for_status()
            result=r.json()
            if result.get('ok'):
                print('Audio message sent successfully.')
                return True
            else:
                print(f'Telegram API Error: {result}')
                return False
    except requests.exceptions.RequestException as e:
        print(f'Request error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    audio_url='https://yaohuang1966.github.io/media/glorious_days_clip.mp3'  
    caption='🎵 一起聽聽這首歌吧'
    telegram_web_audio(token, chat_id, audio_url, caption, title, performer)

首先用 "光輝歲月" 的前奏 mp3 測試 : 

>>> audio_url='https://yaohuang1966.github.io/media/glorious_days_clip.mp3'   
>>> caption='🎵 一起聽聽這首歌吧'    
>>> telegram_web_audio(token, chat_id, audio_url, caption, title, performer)    
Audio message sent successfully.
True

查看 App 有收到這首歌, 但表演者顯示的卻是 'Unknown artist', 歌曲標題顯示的是 mp3 檔案名稱, 這是因為光輝歲月這首歌 mp3 檔的 metadata (ID3 標籤) 裡面的 Title 與 Artist 欄位空白所致 :




改用另一個 mp3 黃霄雲的 "你的答案" 測試 :

>>> audio_url='https://yaohuang1966.github.io/media/your_answer_clip.mp3'   
>>> telegram_web_audio(token, chat_id, audio_url, caption, title, performer)   
Audio message sent successfully.
True

結果曲名與歌手顯示正常, 因為這首歌的 ID3 標籤的 Title 與 Artist 欄位有設定 :




可以用 MP3tag 這個軟體去修改 ID3 標籤 : 




如果要傳送多個音訊, API 就要改用 sendMediaGroup, 每個音訊要先與標題打包成字典串列, 轉成 JSON 字串後傳給 data 字典的 media 參數, 完整程式碼如下 :

# telegram_send_web_audios.py 
import requests

def telegram_web_audios(token, chat_id, media_list, caption_list, parse_mode='Markdown'):
    url=f'https://api.telegram.org/bot{token}/sendMediaGroup'
    media=[{
        'type': 'audio',
        'media': media,
        'caption': caption,
        'parse_mode': parse_mode
        } for media, caption in zip(media_list, caption_list)]
    data={
        'chat_id': chat_id,
        'media': str(media).replace("'", '"')
        }
    try:
        with requests.post(url, data=data) as r:
            r.raise_for_status()  # 若 status_code != 2xx 會拋出例外
            result=r.json()
            if result.get('ok'):
                print('Message sent successfully.')
                return True
            else:
                print(f'Telegram API Error: {result}')
                return False
    except Exception as e:
        print(f'Request error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    audio_urls=[
        'https://yaohuang1966.github.io/media/glorious_days_clip.mp3',
        'https://yaohuang1966.github.io/media/your_answer_clip.mp3'
        ]
    captions=['光輝歲月', '你的答案']
    telegram_web_audios(token, chat_id, audio_urls, captions)

測試結果如下 :

>>> audio_urls=[
        'https://yaohuang1966.github.io/media/glorious_days_clip.mp3',
        'https://yaohuang1966.github.io/media/your_answer_clip.mp3'
        ] 
>>> captions=['光輝歲月', '你的答案']    
>>> telegram_web_audios(token, chat_id, audio_urls, captions)     
Message sent successfully.
True

查看 App 有收到這兩首歌 : 




(2). 傳送本機音訊 :       

傳送本機音訊所用的 API 與網路音訊的一樣, 傳送一個 mp3 檔使用 sendAudio; 傳送多個 mp3 檔使用 sendMidiaGroup, 傳送本機音訊就是多了開檔與關檔動作而已. 

傳送一個音訊檔的程式碼如下 :

# telegram_send_local_audio.py 
import requests

def telegram_local_audio(token, chat_id, audio_path, caption=None, parse_mode='Markdown'):
    url=f'https://api.telegram.org/bot{token}/sendAudio'
    data={
        'chat_id': chat_id,
        'caption': caption,
        'parse_mode': parse_mode,
        }
    try:
        with open(audio_path, 'rb') as f:
            files={'audio': f}
            with requests.post(url, data=data, files=files) as r:
                r.raise_for_status()
                result=r.json()
                if result.get('ok'):
                    print('Message sent successfully.')
                    return True
                else:
                    print(f'Telegram API Error: {result}')
                    return False
    except Exception as e:
        print(f'Request error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    audio_path='your_answer_clip.mp3'
    caption='🎵 一起來聽你的答案'
    telegram_local_audio(token, chat_id, audio_path, caption)

測試結果如下 :

>>> audio_path='your_answer_clip.mp3'   
>>> caption='🎵 一起來聽你的答案'   
>>> telegram_local_audio(token, chat_id, audio_path, caption)    
Message sent successfully.
True

查看 App 果然有收到這首歌 :




傳送多個本機檔案使用的 API 是 sendMediaGroup, 完整程式碼如下 : 

# telegram_send_local_audios.py
import requests

def telegram_local_audios(token, chat_id, audio_paths, captions):
    url=f'https://api.telegram.org/bot{token}/sendMediaGroup'
    files={}  # 儲存檔案物件
    media=[]  # 準備 media payload

    for i, audio_path in enumerate(audio_paths):
        key=f'media{i+1}'
        f=open(audio_path, 'rb')
        files[key]=f
        item={
            'type': 'audio',
            'media': f'attach://{key}'
            }
        if i < len(captions):
            item['caption']=captions[i]
        media.append(item)
    data={
        'chat_id': chat_id,
        'media': str(media).replace("'", '"')  # 轉成 JSON 字串格式
        }
    multipart={
        k: (f.name, f, 'audio/mpeg') for k, f in files.items()
        }
    try:
        with requests.post(url, data=data, files=multipart) as r:
            r.raise_for_status()
            result=r.json()
            if result.get('ok'):
                print('Message sent successfully.')
                return True
            else:
                print(f'Telegram API Error: {result}')
                return False
    except Exception as e:
        print(f'Request error: {e}')
        return False
    finally:
        for f in files.values():
            f.close()

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    audio_paths=['glorious_days_clip.mp3', 'your_answer_clip.mp3']
    captions=['🎵 光輝歲月', '🎶 你的答案']
    telegram_local_audios(token, chat_id, audio_paths, captions)

請注意每個 mp3 音訊檔打包時 type 鍵要設為 audio, 而 multipart 的編碼格式要設為 audio/mpeg, 測試結果如下 : 

>>> audio_paths=['glorious_days_clip.mp3', 'your_answer_clip.mp3']   
>>> captions=['🎵 光輝歲月', '🎶 你的答案']     
>>> telegram_local_audios(token, chat_id, audio_paths, captions)       
Message sent successfully.
True

查看 App 有收到這兩首歌 :



Python 學習筆記 : 用 Telegram Bot 取代 LINE Notify (六)

本篇旨在將前一篇用 HTTP API 傳送影片的程式碼改成用 Bot 物件來實作. 

本系列之前的文章參考 :


我的 Telegram Bot API 權杖與聊天室識別碼都存放在環境變數檔 .env 裡面, 可用 dotenv 套件載入後再用 os 模組讀取出來放在 token 與 chat_id 變數裡 : 

>>> from dotenv import load_dotenv   
>>> load_dotenv()    
True 
>>> import os    
>>> token=os.environ.get('TELEGRAM_TOKEN')   
>>> chat_id=os.environ.get('TELEGRAM_ID')   

# load_secrets_py
from dotenv import load_dotenv  
import os  

load_dotenv()    
token=os.environ.get('TELEGRAM_TOKEN')   
chat_id=os.environ.get('TELEGRAM_ID') 

參考 :


Telegram 雖然支援常見的 mp4/mov/avi/mkv/webm 等影片格式, 但其中以 H.264 編碼的 mp4 支援度最佳, 傳送結果最穩定, mov 格式則要視編碼與音訊格式部分支援, 至於 avi/mkv/webm 格式則不保證能順利傳送, 所以基本上還是以傳送 mp4 影片為主 (H.264 + AAC 格式). 


9. 利用 Bot 物件傳送影片 :     

新版 (v20+) 的 python-telegram-bot 套件使用非同步架構, 所有呼叫 Bot 物件的方法的函式要用 async 修飾, 要用 await 去呼叫方法. 關於 Bot 物件用法參考本系列第二篇測試 :



(1). 傳送網路影片 :  

使用 Bot 物件傳送單一網路影片的程式碼如下 :

# telegram_send_web_video_bot.py
import asyncio
from telegram import Bot
from telegram.error import TelegramError

async def telegram_web_video_bot(token, chat_id, video_url, caption=None, parse_mode='Markdown'):
    bot=Bot(token=token)
    try:
        await bot.send_video(
            chat_id=chat_id,
            video=video_url,
            caption=caption,
            parse_mode=parse_mode
            )
        print('Message sent successfully.')
        return True
    except TelegramError as e:
        print(f'Telegram API Error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    video_url='URL OF VIDEO'  # 例如:https://example.com/sample.mp4
    caption='THE CAPTION'
    asyncio.run(telegram_web_video_bot(token, chat_id, video_url, caption))

結構上比起用 HTTP API 要簡潔許多. 注意, def 前須加 async, 呼叫 bot.send_video() 前要加 await, 執行時要把函式呼叫傳給 asyncio.run() 以非同步方式執行. 

測試如下 : 

>>> import asyncio   
>>> from telegram import Bot    
>>> from telegram.error import TelegramError   
>>> async def telegram_web_video_bot(token, chat_id, video_url, caption=None, parse_mode='Markdown'):
    bot=Bot(token=token)
    try:
        await bot.send_video(
            chat_id=chat_id,
            video=video_url,
            caption=caption,
            parse_mode=parse_mode
            )
        print('Message sent successfully.')
        return True
    except TelegramError as e:
        print(f'Telegram API Error: {e}')
        return False

用 samplelib.com 上的範例影片測試 :

>>> video_url='https://samplelib.com/lib/preview/mp4/sample-5s.mp4'   
>>> caption='範例影片'   
>>> asyncio.run(telegram_web_video_bot(token, chat_id, video_url, caption))   
Message(caption='範例影片', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 12, 15, 19, 42, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, message_id=174, supergroup_chat_created=False, video=Video(api_kwargs={'thumb': {'file_id': 'AAMCBAADGQMAA5pn-c9qwM3evHoXsPcf3ovsgwFAzAACpgcAAu6tHVKvU2k_MzPKIwEAB20AAzYE', 'file_unique_id': 'AQADpgcAAu6tHVJy', 'file_size': 29129, 'width': 320, 'height': 180}}, duration=6, file_id='BAACAgQAAxkDAAOaZ_nPasDN3rx6F7D3H96L7IMBQMwAAqYHAALurR1Sr1NpPzMzyiM2BA', file_name='sample-5s.mp4', file_size=2848208, file_unique_id='AgADpgcAAu6tHVI', height=1080, mime_type='video/mp4', thumbnail=PhotoSize(file_id='AAMCBAADGQMAA5pn-c9qwM3evHoXsPcf3ovsgwFAzAACpgcAAu6tHVKvU2k_MzPKIwEAB20AAzYE', file_size=29129, file_unique_id='AQADpgcAAu6tHVJy', height=180, width=320), width=1920))
Message sent successfully.
True

App 收到所傳送的影片 : 




使用 Bot 物件傳送多個影片時, 須使用 telegram. InputMediaPhoto 類別來打包這些影片, 程式碼如下 : 

# telegram_send_web_videos_bot.py
import asyncio
from telegram import Bot, InputMediaVideo   
from telegram.error import TelegramError

async def telegram_web_videos_bot(token, chat_id, media_list, caption_list, parse_mode='Markdown'):
    bot=Bot(token=token)
    media_group=[
        InputMediaVideo(
            media=url,
            caption=caption,
            parse_mode=parse_mode
            )
        for url, caption in zip(media_list, caption_list)
        ]
    try:
        await bot.send_media_group(chat_id=chat_id, media=media_group)
        print('Videos sent successfully.')
        return True
    except TelegramError as e:
        print(f'Telegram API Error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    video_urls=[
        'https://yaohuang1966.github.io/media/cat.mp4',
        'https://yaohuang1966.github.io/media/remind.mp4'
        ]
    captions=['小喵三兄弟', '明天記得上班']
    asyncio.run(telegram_web_videos_bot(token, chat_id, video_urls, captions))

此處利用 InputMediaVideo 類別與串列生成式將每一個網路 mp4 檔打包後放進串列中, 然後在呼叫 Bot 物件的 send_media_group() 方法時將此媒體串列傳給 media 參數, 測試結果如下 :

>>> import asyncio   
>>> from telegram import Bot, InputMediaVideo   
>>> from telegram.error import TelegramError    
>>> async def telegram_web_videos_bot(token, chat_id, media_list, caption_list, parse_mode='Markdown'):
    bot=Bot(token=token)
    media_group=[
        InputMediaVideo(
            media=url,
            caption=caption,
            parse_mode=parse_mode
            )
        for url, caption in zip(media_list, caption_list)
        ]
    try:
        await bot.send_media_group(chat_id=chat_id, media=media_group)
        print('Videos sent successfully.')
        return True
    except TelegramError as e:
        print(f'Telegram API Error: {e}')
        return False

用目前工作目錄下的兩個 mp4 檔測試 :

>>> video_urls=[
    'https://yaohuang1966.github.io/media/cat.mp4',
    'https://yaohuang1966.github.io/media/remind.mp4'
    ]
>>> captions=['小喵三兄弟', '明天記得上班']   
>>> asyncio.run(telegram_web_videos_bot(token, chat_id, video_urls, captions))  
(Message(caption='小喵三兄弟', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 12, 22, 48, 47, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13955985018210413', message_id=182, supergroup_chat_created=False, video=Video(api_kwargs={'thumb': {'file_id': 'AAMCBAADFQdn-h--aWyJYqCw7AsR1boJCNTdoAACmQcAAhk71FOnbd3lJ4PIGgEAB20AAzYE', 'file_unique_id': 'AQADmQcAAhk71FNy', 'file_size': 9156, 'width': 320, 'height': 149}}, duration=17, file_id='BAACAgQAAxUHZ_ofvmlsiWKgsOwLEdW6CQjU3aAAApkHAAIZO9RTp23d5SeDyBo2BA', file_name='cat.mp4', file_size=3947077, file_unique_id='AgADmQcAAhk71FM', height=596, mime_type='video/mp4', thumbnail=PhotoSize(file_id='AAMCBAADFQdn-h--aWyJYqCw7AsR1boJCNTdoAACmQcAAhk71FOnbd3lJ4PIGgEAB20AAzYE', file_size=9156, file_unique_id='AQADmQcAAhk71FNy', height=149, width=320), width=1280)), Message(caption='明天記得上班', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 12, 22, 48, 47, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13955985018210413', message_id=183, supergroup_chat_created=False, video=Video(api_kwargs={'thumb': {'file_id': 'AAMCBAADGQMAA59n-guWrp2yFseDaNM-6HdKwv0N3QACggcAAkA31FORB6cGxgUTDQEAB20AAzYE', 'file_unique_id': 'AQADggcAAkA31FNy', 'file_size': 18223, 'width': 320, 'height': 320}}, duration=13, file_id='BAACAgQAAxkDAAOfZ_oLlq6dshbHg2jTPuh3SsL9Dd0AAoIHAAJAN9RTkQenBsYFEw02BA', file_name='remind.mp4', file_size=516935, file_unique_id='AgADggcAAkA31FM', height=360, mime_type='video/mp4', thumbnail=PhotoSize(file_id='AAMCBAADGQMAA59n-guWrp2yFseDaNM-6HdKwv0N3QACggcAAkA31FORB6cGxgUTDQEAB20AAzYE', file_size=18223, file_unique_id='AQADggcAAkA31FNy', height=320, width=320), width=360)))
Videos sent successfully.
True

開啟 Telegram App 檢視, 果然收到這兩個影片 : 





(2). 傳送本機影片 :  

我們直接來改寫前一篇測試中用 HTTP API 傳送一個本機影片的 telegram_send_local_video() 函式, 其 Bot 物件版的寫法如下 :

# telegram_send_local_video_bot.py
import asyncio
from telegram import Bot
from telegram.error import TelegramError

async def telegram_local_video_bot(token, chat_id, video_path, caption=None, parse_mode='Markdown'):
    bot=Bot(token=token)
    try:
        with open(video_path, 'rb') as video_file:
            await bot.send_video(
                chat_id=chat_id,
                video=video_file,
                caption=caption,
                parse_mode=parse_mode
                )
        print('Message sent successfully.')
        return True
    except TelegramError as e:
        print(f'Telegram API Error: {e}')
        return False
    except OSError as e:
        print(f'File error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    video_path='VIDEO FILE PATH'  # 例如 './video/cat.mp4'
    caption='THE CAPTION'
    asyncio.run(telegram_local_video_bot(token, chat_id, video_path, caption))

此處使用 open() 開啟影片檔後直接將其傳給 Bot 物件的 send_video() 方法的 video 參數, 測試結果如下 : 

>>> import asyncio   
>>> from telegram import Bot, InputMediaVideo   
>>> from telegram.error import TelegramError    
>>> async def telegram_local_video_bot(token, chat_id, video_path, caption=None, parse_mode='Markdown'):
    bot=Bot(token=token)
    try:
        with open(video_path, 'rb') as video_file:
            await bot.send_video(
                chat_id=chat_id,
                video=video_file,
                caption=caption,
                parse_mode=parse_mode
                )
        print('Message sent successfully.')
        return True
    except TelegramError as e:
        print(f'Telegram API Error: {e}')
        return False
    except OSError as e:
        print(f'File error: {e}')
        return False

使用目前工作目錄下的 cat.mp4 測試 : 

>>> video_path='cat.mp4'   
>>> caption='小喵三兄弟'   
>>> asyncio.run(telegram_local_video_bot(token, chat_id, video_path, caption))     
Message(caption='小喵三兄弟', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 12, 23, 23, 12, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, message_id=185, supergroup_chat_created=False, video=Video(api_kwargs={'thumb': {'file_id': 'AAMCBQADGQMAA7ln-vXgEh0dMrmAyQKmK3XZ1KBo_wAChxUAAhT62FdWAedgDpAVbwEAB20AAzYE', 'file_unique_id': 'AQADhxUAAhT62Fdy', 'file_size': 9156, 'width': 320, 'height': 149}}, duration=17, file_id='BAACAgUAAxkDAAO5Z_r14BIdHTK5gMkCpit12dSgaP8AAocVAAIU-thXVgHnYA6QFW82BA', file_name='cat.mp4', file_size=3947077, file_unique_id='AgADhxUAAhT62Fc', height=596, mime_type='video/mp4', thumbnail=PhotoSize(file_id='AAMCBQADGQMAA7ln-vXgEh0dMrmAyQKmK3XZ1KBo_wAChxUAAhT62FdWAedgDpAVbwEAB20AAzYE', file_size=9156, file_unique_id='AQADhxUAAhT62Fdy', height=149, width=320), width=1280))
Message sent successfully.
True

查看 Telegram App 果然有收到此影片 : 




如果要傳送本機多個影片, 與上面傳送多個網路影片一樣要用到 InputMediaVideo 類別. 其實不論是網路影片還是本機影片, 如果要傳送多個影片都要用 InputMediaVideo 類別來將各影片打包, 差別只是本機影片需要開啟檔案后再打包, 網路影片則是直接將影片網址打包而已. 

下面是將前一篇中的 telegram_local_videos() 改寫為 Bot 物件版的寫法 :

# telegram_send_local_videos_bot.py
import asyncio
from telegram import Bot, InputMediaVideo
from telegram.error import TelegramError

async def telegram_local_videos_bot(token, chat_id, video_paths, captions):
    bot=Bot(token=token)
    media=[]  # 儲存用 InputMediaVideo 打包後的影片
    files=[]  # 儲存開啟的所有影片檔案
    try:
        for i, path in enumerate(video_paths):
            f=open(path, 'rb')
            files.append(f)
            caption=captions[i] if i < len(captions) else None
            media.append(InputMediaVideo(media=f, caption=caption))
        await bot.send_media_group(chat_id=chat_id, media=media)
        print("Message sent successfully.")
        return True
    except TelegramError as e:
        print(f'Telegram API Error: {e}')
        return False
    except OSError as e:
        print(f'File error: {e}')
        return False
    finally:
        for f in files:
            f.close()

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    video_paths=['cat.mp4', 'remind.mp4']
    captions=['小喵三兄弟', '記得明天要上班']
    asyncio.run(telegram_local_videos_bot(token, chat_id, video_paths, captions))

此例與上面傳送多個網路影片不同之處是需要一個 files 串列來儲存開啟過的影片檔案參考, 以便能在傳送完成後記得要關閉那些檔案. 測試結果如下 :

>>> import asyncio   
>>> from telegram import Bot, InputMediaVideo   
>>> from telegram.error import TelegramError    
>>> async def telegram_local_videos_bot(token, chat_id, video_paths, captions):
    bot=Bot(token=token)
    media=[]  # 儲存用 InputMediaVideo 打包後的影片
    files=[]  # 儲存開啟的所有影片檔案
    try:
        for i, path in enumerate(video_paths):
            f=open(path, 'rb')
            files.append(f)
            caption=captions[i] if i < len(captions) else None
            media.append(InputMediaVideo(media=f, caption=caption))
        await bot.send_media_group(chat_id=chat_id, media=media)
        print("Message sent successfully.")
        return True
    except TelegramError as e:
        print(f'Telegram API Error: {e}')
        return False
    except OSError as e:
        print(f'File error: {e}')
        return False
    finally:
        for f in files:
            f.close()

使用目前工作目錄下的 cat.mp4 與 remind.mp4 測試 : 

>>> video_paths=['cat.mp4', 'remind.mp4']   
>>> captions=['小喵三兄弟', '記得明天要上班']   
>>> asyncio.run(telegram_local_videos_bot(token, chat_id, video_paths, captions))   
(Message(caption='小喵三兄弟', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 12, 23, 44, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13956011520727829', message_id=186, supergroup_chat_created=False, video=Video(api_kwargs={'thumb': {'file_id': 'AAMCBQADFQdn-vq_N0rzmlKlGv1uWTvrueGBOQACihUAAhT62Fc0NX6pzVwmHwEAB20AAzYE', 'file_unique_id': 'AQADihUAAhT62Fdy', 'file_size': 9156, 'width': 320, 'height': 149}}, duration=17, file_id='BAACAgUAAxUHZ_r6vzdK85pSpRr9blk767nhgTkAAooVAAIU-thXNDV-qc1cJh82BA', file_name='cat.mp4', file_size=3947077, file_unique_id='AgADihUAAhT62Fc', height=596, mime_type='video/mp4', thumbnail=PhotoSize(file_id='AAMCBQADFQdn-vq_N0rzmlKlGv1uWTvrueGBOQACihUAAhT62Fc0NX6pzVwmHwEAB20AAzYE', file_size=9156, file_unique_id='AQADihUAAhT62Fdy', height=149, width=320), width=1280)), Message(caption='記得明天要上班', channel_chat_created=False, chat=Chat(first_name='<我的名字>', id=<聊天室 ID>, type=<ChatType.PRIVATE>, username='<我的 username>'), date=datetime.datetime(2025, 4, 12, 23, 44, tzinfo=datetime.timezone.utc), delete_chat_photo=False, from_user=User(first_name='twstock', id=7938146214, is_bot=True, username='twstock168_bot'), group_chat_created=False, media_group_id='13956011520727829', message_id=187, supergroup_chat_created=False, video=Video(api_kwargs={'thumb': {'file_id': 'AAMCBQADFQdn-vq_aNXB5SHwb2CU0UwyBjeTBAACiRUAAhT62FeAbDy1aJktFAEAB20AAzYE', 'file_unique_id': 'AQADiRUAAhT62Fdy', 'file_size': 18223, 'width': 320, 'height': 320}}, duration=13, file_id='BAACAgUAAxUHZ_r6v2jVweUh8G9glNFMMgY3kwQAAokVAAIU-thXgGw8tWiZLRQ2BA', file_name='remind.mp4', file_size=516935, file_unique_id='AgADiRUAAhT62Fc', height=360, mime_type='video/mp4', thumbnail=PhotoSize(file_id='AAMCBQADFQdn-vq_aNXB5SHwb2CU0UwyBjeTBAACiRUAAhT62FeAbDy1aJktFAEAB20AAzYE', file_size=18223, file_unique_id='AQADiRUAAhT62Fdy', height=320, width=320), width=360)))
Message sent successfully.
True

查看 Telegram App 果然收到了這兩個影片 : 



2025年4月12日 星期六

Python 學習筆記 : 用 Telegram Bot 取代 LINE Notify (五)

今天繼續來測試 Telegram Bot API 用法, 本篇旨在探討傳送影片到 Telegram 的方法. 

本系列之前的文章參考 :


我的 Telegram Bot API 權杖與聊天室識別碼都存放在環境變數檔 .env 裡面, 可用 dotenv 套件載入後再用 os 模組讀取出來放在 token 與 chat_id 變數裡 : 

>>> from dotenv import load_dotenv   
>>> load_dotenv()    
True 
>>> import os    
>>> token=os.environ.get('TELEGRAM_TOKEN')   
>>> chat_id=os.environ.get('TELEGRAM_ID')   

# load_secrets_py
from dotenv import load_dotenv  
import os  

load_dotenv()    
token=os.environ.get('TELEGRAM_TOKEN')   
chat_id=os.environ.get('TELEGRAM_ID') 

參考 :


Telegram 雖然支援常見的 mp4/mov/avi/mkv/webm 等影片格式, 但其中以 H.264 編碼的 mp4 支援度最佳, 傳送結果最穩定, mov 格式則要視編碼與音訊格式部分支援, 至於 avi/mkv/webm 格式則不保證能順利傳送, 所以基本上還是以傳送 mp4 影片為主 (H.264 + AAC 格式). 


8. 利用 HTTP 的 POST 方法傳送影片 :     

首先匯入 requests 用來負責處理 HTTP 請求與回應 :

>>> import requests  

Telegram Bot API 傳送影片的 HTTP 網址格式如下 :  

https://api.telegram.org/bot{token}/sendVideo   

定義一個嵌入 Telegram Bot 權杖的 API 網址變數 url :

>>> url=f'https://api.telegram.org/bot{token}/sendVideo'    

其中嵌入變數 token 為 Telegram Bot 的權杖. 


(1). 傳送網路影片 :       

首先測試傳送一個網路上的 mp4 影片, 例如 samplelib.com 提供的範例影片 :


參考 :


注意, 必須是直接放在網路上, 網址是以 .mp4 結束的網址才行, Youtube 影片無法直接使用 (要先下載用本機檔案方式傳送), 因為它的連結是一個網頁網址, mp4 影片是嵌在該網頁中, Telegram 無法從網頁網址取出 mp4 網址 (因為有防爬蟲與版權保護). 

接下來製作影片的 data 字典, 必須包含 chat_id (聊天室識別碼) 與 video  (影片網址) 這兩個鍵, 但 caption (標題) 鍵與 parse_mode 鍵則是可有可無 :

>>> data={
    'chat_id': chat_id,
    'video': 'https://samplelib.com/lib/preview/mp4/sample-5s.mp4', 
    'caption': '範例影片'
    }  
>>> data   
{'chat_id': '7742981072', 'video': 'https://samplelib.com/lib/preview/mp4/sample-5s.mp4', 'caption': '範例影片'}

最後呼叫 requests.post() 並將此 data 字典傳給 data 參數即可 :

>>> r=requests.post(url, data=data)   

檢視傳回值裡面有關於此影片的解析度與編碼格式等資訊 :

>>> r.json()  
{'ok': True, 'result': {'message_id': 154, 'from': {'id': 7938146214, 'is_bot': True, 'first_name': 'twstock', 'username': 'twstock168_bot'}, 'chat': {'id': <聊天室ID>, 'first_name': '<我的名字>', 'username': '<我的username>', 'type': 'private'}, 'date': 1744424810, 'video': {'duration': 6, 'width': 1920, 'height': 1080, 'file_name': 'sample-5s.mp4', 'mime_type': 'video/mp4', 'thumbnail': {'file_id': 'AAMCBAADGQMAA5pn-c9qwM3evHoXsPcf3ovsgwFAzAACpgcAAu6tHVKvU2k_MzPKIwEAB20AAzYE', 'file_unique_id': 'AQADpgcAAu6tHVJy', 'file_size': 29129, 'width': 320, 'height': 180}, 'thumb': {'file_id': 'AAMCBAADGQMAA5pn-c9qwM3evHoXsPcf3ovsgwFAzAACpgcAAu6tHVKvU2k_MzPKIwEAB20AAzYE', 'file_unique_id': 'AQADpgcAAu6tHVJy', 'file_size': 29129, 'width': 320, 'height': 180}, 'file_id': 'BAACAgQAAxkDAAOaZ_nPasDN3rx6F7D3H96L7IMBQMwAAqYHAALurR1Sr1NpPzMzyiM2BA', 'file_unique_id': 'AgADpgcAAu6tHVI', 'file_size': 2848208}, 'caption': '範例影片'}}

檢視 Telegram App 果然收到此影片 : 




以上測試之完整程式碼如下 :

# telegram_send_web_video.py
import requests

def telegram_web_video(token, chat_id, video_url, caption=None, parse_mode='Markdown'):
    url=f'https://api.telegram.org/bot{token}/sendVideo'
    data={
        'chat_id': chat_id,
        'video': video_url,
        'caption': caption,
        'parse_mode': parse_mode
        }
    try:
        with requests.post(url, data=data) as r:
            r.raise_for_status()
            result=r.json()
            if result.get('ok'):
                print('Message sent successfully.')
                return True
            else:
                print(f'Telegram API Error: {result}')
                return False
    except requests.exceptions.RequestException as e:
        print(f'Request error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    video_url='URL OF PHOTO'
    caption='THE CAPTION'
    telegram_web_video(token, chat_id, video_url, caption)

如果要傳送多個網路影片, 跟傳送多張圖片一樣, API 要改用 sendMediaGroup, 每個影片要與標題先打包成媒體字典放進串列中, 然後將其轉成 JSON 格式字串放進 data 字典的 media 鍵, 再把 data 字典傳給 requests.post() 的 data 參數, API 網址如下 :

https://api.telegram.org/bot{token}/sendMediaGroup  

我們直接修改 "Python 學習筆記 : 用 Telegram Bot 取代 LINE Notify (三)" 中的 telegram_web_images() 函式成為如下的 telegram_web_videos() : 

# telegram_web_videos.py 

import requests

def telegram_web_videos(token, chat_id, media_list, caption_list):
    url=f'https://api.telegram.org/bot{token}/sendMediaGroup'
    media=[{
            'type': 'video',
            'media': media,
            'caption': caption,
            'parse_mode': 'Markdown'
            }
        for media, caption in zip(media_list, caption_list)
        ]
    data={
        'chat_id': chat_id,
        'media': str(media).replace("'", '"')
        }
    try:
        with requests.post(url, data=data) as r:
            r.raise_for_status()  # 若 status_code != 2xx 會拋出例外
            result=r.json()
            if result.get('ok'):
                print('Message sent successfully.')
                return True
            else:
                print(f'Telegram API Error: {result}')
                return False
    except requests.exceptions.RequestException as e:
        print(f'Request error: {e}')
        return False

if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    video_urls=['https://yaohuang1966.github.io/media/cat.mp4',
                'https://yaohuang1966.github.io/media/remind.mp4']
    captions=['小喵三兄弟', '明天記得上班']
    telegram_web_videos(token, chat_id, video_urls, caption)

這裡我準備了兩個放在 GitHub 上的 mp4 影片來測試 : 


測試結果如下 :

>>> def telegram_web_videos(token, chat_id, media_list, caption_list):
    url=f'https://api.telegram.org/bot{token}/sendMediaGroup'
    media=[{
            'type': 'video',
            'media': media,
            'caption': caption,
            'parse_mode': 'Markdown'
            }
        for media, caption in zip(media_list, caption_list)
        ]
    data={
        'chat_id': chat_id,
        'media': str(media).replace("'", '"')
        }
    try:
        with requests.post(url, data=data) as r:
            r.raise_for_status()  # 若 status_code != 2xx 會拋出例外
            result=r.json()
            if result.get('ok'):
                print('Message sent successfully.')
                return True
            else:
                print(f'Telegram API Error: {result}')
                return False
    except requests.exceptions.RequestException as e:
        print(f'Request error: {e}')
        return False

然後準備兩支影片網址與標題串列 : 

>>> video_urls=['https://yaohuang1966.github.io/media/cat.mp4',
            'https://yaohuang1966.github.io/media/remind.mp4']   
>>> captions=['小喵三兄弟', '明天記得上班']   

呼叫 telegram_web_videos() 傳送這兩張影片 (token, chat_id 已在前面用 dotenv 載入) :

>>> telegram_web_videos(token, chat_id, video_urls, caption)   
Message sent successfully.
True

檢查 Telegram App 果然收到這兩個影片 :




(2). 傳送本機影片 :       

傳送本機上的影片與傳送本機圖片的方法相同 (只是 API 不同, 傳送圖片是 sendPhoto, 傳送影片是 sendVideo), 都是將標題 caption 留在 data 參數中, 將影片檔另外以 files 參數傳送. 影片檔要用 open() 以 rb 模式開啟, 然後將檔案參考設為 files 參數字典中 files 鍵之值, 於呼叫 requests.post() 時把 data 與 files 傳進去即可. 

參考 "Python 學習筆記 : 用 Telegram Bot 取代 LINE Notify (三)" 中的 telegram_local_image() 函式, 將其修改為如下的 telegram_local_video() :

# telegram_send_local_video.py
import requests

def telegram_local_video(token, chat_id, video_path, caption=None, parse_mode='Markdown'):
    url=f'https://api.telegram.org/bot{token}/sendVideo'
    data={
        'chat_id': chat_id,
        'caption': caption,
        'parse_mode': parse_mode
        }
    try:
        with open(video_path, 'rb') as f:
            files={'video': f}        
            with requests.post(url, data=data, files=files) as r:
                r.raise_for_status()
                result=r.json()
                if result.get('ok'):
                    print('Message sent successfully.')
                    return True
                else:
                    print(f'Telegram API Error: {result}')
                    return False
    except requests.exceptions.RequestException as e:
        print(f'Request error: {e}')
        return False
    
if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    video_path='VIDEO FILE PATH'
    caption='THE CAPTION'
    telegram_local_video(token, chat_id, video_path, caption)    

此例 data 字典中僅攜帶聊天室 ID (chat_id) 與影片標題 (caption), 影片檔案參考則是放在 files 字典的 files 鍵裡面, 整個架構與傳送本機圖片一樣, 只是 API 改為 sendVideo 而已. 

>>> def telegram_local_video(token, chat_id, video_path, caption=None, parse_mode='Markdown'):
    url=f'https://api.telegram.org/bot{token}/sendVideo'
    data={
        'chat_id': chat_id,
        'caption': caption,
        'parse_mode': parse_mode
        }
    try:
        with open(video_path, 'rb') as f:
            files={'video': f}        
            with requests.post(url, data=data, files=files) as r:
                r.raise_for_status()
                result=r.json()
                if result.get('ok'):
                    print('Message sent successfully.')
                    return True
                else:
                    print(f'Telegram API Error: {result}')
                    return False
    except requests.exceptions.RequestException as e:
        print(f'Request error: {e}')
        return False 

我將上面 GitHub 中的貓咪影片下載到本機工作目錄下的 cat.mp4, 測試如下 :

>>> video_path='cat.mp4'  
>>> caption='小喵三兄弟'    
>>> telegram_local_video(token, chat_id, video_path, caption)   
Message sent successfully.
True

檢視 Telegram App 果然收到此影片 :




傳送多個媒體不論是網路或本機都是使用 sendMediaGroup API, 網址格式如下 :

https://api.telegram.org/bot{token}/sendMediaGroup     

傳送本機影片的程式架構與傳送多個本機圖片一樣, 也是使用 open() 逐一開啟影片檔, 然後將這些檔案參考與其標題打包成 multipart 媒體字典串列後傳給 requests.post() 的 files 參數送出. 下面直接拿 "Python 學習筆記 : 用 Telegram Bot 取代 LINE Notify (三)" 這篇中用來傳送本機多個圖片檔的 telegram_local_image() 函式來修改為如下的 telegram_local_video() :

# telegram_send_local_video.py
import requests

def telegram_local_videos(token, chat_id, media_list, caption_list):
    url=f'https://api.telegram.org/bot{token}/sendMediaGroup'
    files={}  # 儲存檔案參考的字典
    media=[]  # 儲存 media 字典串列
    for i, video_path in enumerate(media_list): # 將檔案
        key=f'media{i+1}'  # 製作 files 字典的鍵 'media#'
        f=open(video_path, 'rb')  # 開啟檔案
        files[key]=f  # 將檔案參考存入 files 字典
        item={  # media 字典的項目
            'type': 'video',
            'media': f'attach://{key}'
            }        
        if i < len(caption_list): # 若有對應的 caption,則加入
            item['caption']=caption_list[i]  # 加入 caption 鍵
        media.append(item)  # 將媒體字典加入 media 串列
    data={  # 製作 data 參數值
        'chat_id': chat_id,
        'media': str(media).replace("'", '"')  # JSON 字串格式
        }
    multipart={  # 製作 multipart 參數值
        k: (f.name, f, 'video/mp4') for k, f in files.items()
        }
    try:
        with requests.post(url, data=data, files=multipart) as r:
            r.raise_for_status()  # 若 status_code != 2xx 會拋出例外
            result=r.json()
            if result.get('ok'):
                print('Message sent successfully.')
                return True
            else:
                print(f'Telegram API Error: {result}')
                return False
    except requests.exceptions.RequestException as e:
        print(f'Request error: {e}')
        return False
    finally:  # 關閉檔案避免資源泄漏
        for f in files.values():
            f.close()
            
 if __name__ == '__main__':
    token='YOUR TOKEN'
    chat_id='YOUR CHAT ID'
    video_paths=['cat.mp4', 'remind.mp4']
    caption=['小喵三兄弟', '記得明天要上班']
    telegram_local_videos(token, chat_id, video_paths, caption)             

其中黃底色部分就是修改之處, 可見只是將 media 字典的 type 鍵之值改為 video, 而 multipart 字典的媒體格式改為 video/mp4 如此而已.  

我將上面 GitHub 中的貓咪與唐伯虎點秋香影片下載到本機工作目錄下的 cat.mp4 與 remind.mp4, 測試如下 :

>>> def telegram_local_videos(token, chat_id, media_list, caption_list):
    url=f'https://api.telegram.org/bot{token}/sendMediaGroup'
    files={}  # 儲存檔案參考的字典
    media=[]  # 儲存 media 字典串列
    for i, video_path in enumerate(media_list): 
        key=f'media{i+1}'  # 製作 files 字典的鍵 'media#'
        f=open(video_path, 'rb')  # 開啟檔案
        files[key]=f  # 將檔案參考存入 files 字典
        item={  # media 字典的項目
            'type': 'video',
            'media': f'attach://{key}'
            }        
        if i < len(caption_list): # 若有對應的 caption 則加入
            item['caption']=caption_list[i]  # 加入 caption 鍵
        media.append(item)  # 將媒體字典加入 media 串列
    data={  # 製作 data 參數值
        'chat_id': chat_id,
        'media': str(media).replace("'", '"')  # JSON 字串格式
        }
    multipart={  # 製作 multipart 參數值
        k: (f.name, f, 'video/mp4') for k, f in files.items()
        }
    try:
        with requests.post(url, data=data, files=multipart) as r:
            r.raise_for_status()  # 若 status_code != 2xx 會拋出例外
            result=r.json()
            if result.get('ok'):
                print('Message sent successfully.')
                return True
            else:
                print(f'Telegram API Error: {result}')
                return False
    except requests.exceptions.RequestException as e:
        print(f'Request error: {e}')
        return False
    finally:  # 關閉檔案避免資源泄漏
        for f in files.values():
            f.close()

測試結果 :

>>> video_paths=['cat.mp4', 'remind.mp4']   
>>> captions=['小喵三兄弟', '記得明天要上班']    
>>> telegram_local_videos(token, chat_id, video_paths, captions)   
Message sent successfully.
True

查看 API 果然收到這兩個影片 :