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 果然收到這兩個影片 : 



沒有留言 :