Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image | |
| import qrcode | |
| from googleapiclient.discovery import build | |
| import os | |
| import yaml | |
| from ultralytics import YOLO | |
| import logging | |
| import glob | |
| import re | |
| import time # 確保有導入 time 模組 | |
| import subprocess # 用於執行外部命令 | |
| import threading # 用於處理多線程 | |
| import queue # 用於線程間通信 | |
| import urllib.request # 用於檢查 URL 是否可訪問 | |
| import tempfile # 用於創建臨時文件 | |
| markdown_content = """ | |
| <style> | |
| .title-icon { | |
| display: inline-flex; /* 讓圖片和文字在同一行 */ | |
| align-items: center; /* 垂直居中 */ | |
| font-size: 2em; /* 調整整體大小 */ | |
| } | |
| .title-icon img { | |
| height: 1em; /* 圖片高度,與文字大小相關 */ | |
| margin-right: 0.5em; /* 圖片與文字間距 */ | |
| } | |
| </style> | |
| <div class="title-icon"> | |
| <h1>台灣常見鳥類偵測系統</h1> | |
| </div> | |
| """ | |
| # 設定日誌 | |
| logging.basicConfig( | |
| level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" | |
| ) | |
| # 載入類別名稱 (這部分保持不變) | |
| try: | |
| logging.info("載入類別名稱...") | |
| yaml_path = "multibird_dataset.yaml" # 改成你的路徑 | |
| with open(yaml_path, "r", encoding="utf-8") as f: | |
| yaml_data = yaml.safe_load(f) | |
| class_names = yaml_data["names"] | |
| total_class_names = list(yaml_data["names"].values()) | |
| logging.info(f"成功載入 {len(set(total_class_names))} 個類別") | |
| except Exception as e: | |
| logging.error(f"載入類別名稱時發生錯誤: {str(e)}") | |
| class_names = {} | |
| # 設定 YouTube API (這部分保持不變) | |
| YOUTUBE_API_KEY = os.environ.get("YOUTUBE_API_KEY") | |
| youtube = build("youtube", "v3", developerKey=YOUTUBE_API_KEY) | |
| # 載入 YOLO 模型 (這部分保持不變) | |
| try: | |
| logging.info("開始載入 YOLO 模型...") | |
| model_path = "best.pt" # 0323 187種鳥的版本 | |
| if not os.path.exists(model_path): | |
| raise FileNotFoundError(f"找不到模型文件:{model_path}") | |
| model = YOLO(model_path) | |
| logging.info("YOLO 模型載入成功") | |
| except Exception as e: | |
| logging.error(f"載入模型時發生錯誤:{str(e)}") | |
| raise | |
| # 建立英文鳥名到中文鳥名的對照表 | |
| bird_chinese_names = { | |
| "domestic_goose": "家鵝", | |
| "black_swan": "黑天鵝", | |
| "spot_billed_duck": "花嘴鴨", | |
| "mallard": "綠頭鴨", | |
| "rock_dove": "野鴿", | |
| "spotted_dove": "珠頸斑鳩", | |
| "emerald_dove": "翠翼鳩", | |
| "common_moorhen": "紅冠水雞", | |
| "little_grebe": "小鸊鷉", | |
| "black_crowned_night_heron": "夜鷺", | |
| "malayan_night_heron": "黑冠麻鷺", | |
| "cattle_egret": "黃頭鷺", | |
| "mountain_hawk_eagle": "熊鷹", | |
| "common_kingfisher": "翠鳥", | |
| "muller_barbet": "五色鳥", | |
| "black_drongo": "大卷尾", | |
| "blue_flycatcher": "黑枕藍鶲", | |
| "formosan_blue_magpie": "台灣藍鵲", | |
| "chinese_bulbul": "白頭翁", | |
| "streak_breasted_scimitar_babbler": "小彎嘴", | |
| "black_necklaced_scimitar_babbler": "大彎嘴", | |
| "javan_myna": "家八哥", | |
| "white_vented_myna": "白腹八哥", | |
| "oriental_magpie_robin": "鵲鴝", | |
| "white_rumped_shama": "白腰鵲鴝", | |
| "daurian_redstart": "黃尾鴝", | |
| "eurasian_tree_sparrow": "麻雀", | |
| "white_wagtail": "白鶺鴒", | |
| "black_naped_oriole": "黃鸝", | |
| "maroon_oriole": "朱鸝", | |
| "black_faced_spoonbill": "黑面琵鷺", | |
| "great_egret": "大白鷺", | |
| "intermediate_egret": "中白鷺", | |
| "little_egret": "小白鷺", | |
| "chinese_egret": "唐白鷺", | |
| "grey_heron": "蒼鷺", | |
| "eurasian_spoonbill": "白琵鷺", | |
| "osprey": "魚鷹", | |
| "black_winged_kite": "黑翅鳶", | |
| "black_kite": "黑鳶", | |
| "mountain_eagle": "林鵰", | |
| "eastern_imperial_eagle": "花鵰", | |
| "oriental_honey_buzzard": "東方蜂鷹", | |
| "crested_serpent_eagle": "大冠鷲", | |
| "grey_faced_buzzard": "灰面鵟鷹", | |
| "crested_goshawk": "鳳頭蒼鷹", | |
| "chinese_sparrowhawk": "赤腹鷹", | |
| "besra": "松雀鷹", | |
| "white_tailed_eagle": "白尾海鵰", | |
| "peregrine_falcon": "遊隼", | |
| "ancient_murrelet": "扁嘴海雀", | |
| "tundra_swan": "小天鵝", | |
| "whooper_swan": "大天鵝", | |
| "great_crested_grebe": "鳳頭鸊鷉", | |
| "eared_grebe": "黑頸鸊鷉", | |
| "cotton_pygmy_goose": "棉鴨", | |
| "ferruginous_duck": "白眼潛鴨", | |
| "great_cormorant": "鸕鶿", | |
| "japanese_cormorant": "暗綠背鸕鶿", | |
| "brown_noddy": "褐翅燕鷗", | |
| "brown_headed_gull": "棕頭鷗", | |
| "black_headed_gull": "紅嘴鷗", | |
| "saunders_gull": "黑嘴鷗", | |
| "taiwan_hwamei": "台灣畫眉", | |
| "green_winged_teal": "小水鴨", | |
| "baikal_teal": "花臉鴨", | |
| "garganey": "白眉鴨", | |
| "common_shelduck": "翹鼻麻鴨", | |
| "common_pochard": "紅頭潛鴨", | |
| "anas_falcata": "羅紋鴨", | |
| "gadwall": "赤膀鴨", | |
| "mandarin_duck": "鴛鴦", | |
| "greater_scaup": "斑背潛鴨", | |
| "tufted_duck": "鳳頭潛鴨", | |
| "northern_pintail": "尖尾鴨", | |
| "eurasian_wigeon": "赤頸鴨", | |
| "northern_shoveller": "琵嘴鴨", | |
| "eastern_spot_billed_duck": "東方斑嘴鴨", | |
| "american_wigeon": "美洲赤頸鴨", | |
| "eurasian_coot": "白骨頂", | |
| "black_tailed_gull": "黑尾鷗", | |
| "caspian_gull": "裡海鷗", | |
| "caspian_tern": "紅嘴巨鷗", | |
| "relict_gull": "遺鷗", | |
| "graylag_goose": "灰雁", | |
| "swan_goose": "鴻雁", | |
| "tundra_bean_goose": "短嘴豆雁", | |
| "lesser_white_fronted_goose": "小白額雁", | |
| "taiga_bean_goose": "豆雁", | |
| "greater_white_fronted_goose": "大白額雁", | |
| "red_footed_booby": "紅腳鰹鳥", | |
| "franklin_gull": "富蘭克林鷗", | |
| "smew": "白秋沙", | |
| "brown_headed_thrush": "赤腹鶇", | |
| "grey_backed_thrush": "灰背鶇", | |
| "island_thrush": "島鶇", | |
| "plumbeous_redstart": "鉛色水鶇", | |
| "red_breasted_flycatcher": "紅胸姬鶲", | |
| "fairy_pitta": "八色鳥", | |
| "blue_winged_pitta": "藍翅八色鳥", | |
| "collared_kingfisher": "蒼翡翠", | |
| "ruddy_kingfisher": "赤翡翠", | |
| "dollar_bird": "三寶鳥", | |
| "eurasian_wryneck": "蟻鴷", | |
| "grey_capped_woodpecker": "小啄木", | |
| "japanese_waxwing": "日本太平鳥", | |
| "mikado_pheasant": "帝雉", | |
| "swinhoe_pheasant": "藍腹鷴", | |
| "ring_necked_pheasant": "環頸雉", | |
| "taiwan_bamboo_partridge": "台灣竹雞", | |
| "chestnut_backed_shrike": "栗背伯勞", | |
| "steppe_grey_strike": "草原灰伯勞", | |
| "brown_shrike": "紅尾伯勞", | |
| "long_tailed_shrike": "棕背伯勞", | |
| "taiwan_scimitar_babbler": "台灣彎嘴畫眉", | |
| "rufous_faced_warbler": "棕面鶯", | |
| "taiwan_yuhina": "冠羽畫眉", | |
| "green_backed_tit": "青背山雀", | |
| "yellow_tit": "黃山雀", | |
| "coal_tit": "煤山雀", | |
| "grey_chinned_minivet": "灰喉山椒鳥", | |
| "black_winged_cuckooshrike": "黑翅鵑鵙", | |
| "large_cuckoo_shrike": "大鵑鵙", | |
| "large_billed_crow": "巨嘴鴉", | |
| "olive_backed_pipit": "樹鷚", | |
| "alpine_accentor": "岩鷚", | |
| "eurasian_nuthatch": "茶腹鳾", | |
| "asian_koel": "噪鵑", | |
| "chestnut_bunting": "栗鵐", | |
| "little_bunting": "小鵐", | |
| "black_faced_bunting": "黑臉鵐", | |
| "yellow_throated_bunting": "黃喉鵐", | |
| "oriental_reed_warbler": "東方大葦鶯", | |
| "narcissus_flycatcher": "黃眉姬鶲", | |
| "mugimaki_flycatcher": "鴝姬鶲", | |
| "verditer_flycatcher": "銅藍鶲", | |
| "siberian_rubythroat": "紅喉歌鴝", | |
| "forest_wagtail": "山鶺鴒", | |
| "vinous_breasted_starling": "亞洲輝椋鳥", | |
| "plain_flowerpecker": "純色啄花鳥", | |
| "fire_breasted_flowerpecker": "紅胸啄花鳥", | |
| "taiwan_barbet": "台灣擬啄木", | |
| "collared_scops_owl": "領角鴞", | |
| "brown_hawk_owl": "褐鷹鴞", | |
| "brown_wood_owl": "褐林鴞", | |
| "collared_owlet": "鵂鶹", | |
| "austrialasian_grass_owl": "草鴞", | |
| "tawny_fish_owl": "黃魚鴞", | |
| "short_eared_owl": "短耳鴞", | |
| "hooded_crane": "白頭鶴", | |
| "siberian_crane": "白鶴", | |
| "sandhill_crane": "沙丘鶴", | |
| "common_crane": "灰鶴", | |
| "chinese_pond_heron": "池鷺", | |
| "pacific_reef_heron": "岩鷺", | |
| "purple_heron": "紫鷺", | |
| "yellow_bittern": "黃葦鳽", | |
| "great_bittern": "大麻鳽", | |
| "glossy_ibis": "彩鷺", | |
| "common_kestrel": "紅隼", | |
| "chinese_crested_tern": "中華鳳頭燕鷗", | |
| "white_eared_sibia": "白耳畫眉", | |
| "asian_dowitcher": "半蹼鷸", | |
| "bar_tailed_godwit": "斑尾塍鷸", | |
| "common": "田鷸", | |
| "far_eastern_curlew": "大杓鷸", | |
| "ruddy_turnstone": "翻石鷸", | |
| "little_stint": "小濱鷸", | |
| "curlew_sandpiper": "彎嘴濱鷸", | |
| "dunlin": "黑腹濱鷸", | |
| "spotted_redshank": "鶴鷸", | |
| "greater_painted_snipe": "彩鷸", | |
| "nordmann_greenshank": "諾氏鷸", | |
| "wood_sandpiper": "林鷸", | |
| "terek_sandpiper": "翹嘴鷸", | |
| "sanderling": "三趾濱鷸", | |
| "eurasian_curlew": "白腰杓鷸", | |
| "long_toed_stint": "長趾濱鷸", | |
| "common_greenshank": "青足鷸", | |
| "whimbrel": "中杓鷸", | |
| "marsh_sandpiper": "澤鷸", | |
| "red_necked_phalarope": "紅頸瓣足鷸", | |
| "common_redshank": "紅腳鷸", | |
| "little_ringed_plover": "小環頸鴴", | |
| "kentish_plover": "環頸鴴", | |
| "lesser_sand_plover": "蒙古鴴", | |
| "black_winged_stilt": "高蹺鴴", | |
| "oriental_pratincole": "燕鴴", | |
| "pacific_golden_plover": "太平洋金斑鴴", | |
| "pied_avocet": "反嘴鷸", | |
| "little_curlew": "小杓鷸", | |
| "common_ringed_plover": "劍鴴", | |
| "northern_lapwing": "田鳧", | |
| "grey_headed_lapwing": "灰頭麥雞", | |
| "savanna_nightjar": "南亞夜鷹", | |
| "little_tern": "小燕鷗", | |
| "glareola_maldivarum": "燕鴴", | |
| "barn_swallow": "家燕", | |
| "greater_crested_tern": "鳳頭燕鷗", | |
| "common_tern": "普通燕鷗", | |
| "whiskered_tern": "黑腹燕鷗", | |
| "gull_billed_tern": "鷗嘴燕鷗", | |
| "black_naped_tern": "黑枕燕鷗", | |
| "white_backed_woodpecker": "大赤啄木", | |
| "oriental_stork": "東方白鸛", | |
| "red_collared_dove": "紅鳩", | |
| "black_chinned_fruit_dove": "小綠鳩", | |
| "zebra_dove": "斑馬鳩", | |
| "common_goldeneye": "鵲鴨", | |
| "red_breasted_merganser": "紅胸秋沙", | |
| "scaly_sided_merganser": "中華秋沙", | |
| "great_white_pelican": "白鵜鶘", | |
| "bar_headed_goose": "斑頭雁", | |
| "adelie_penguin": "阿德利企鵝", | |
| "gentoo_penguin": "巴布亞企鵝", | |
| "chinstrap_penguin": "頰帶企鵝", | |
| "macaroni_penguin": "長冠企鵝", | |
| "king_penguin": "國王企鵝", | |
| "tufted_puffin": "簇絨海鸚", | |
| "hoopoe": "戴勝", | |
| "anna_hummingbird": "安氏蜂鳥", | |
| } | |
| # 將yaml中的類別名稱加入bird_chinese_names | |
| for key, value in class_names.items(): | |
| if key not in bird_chinese_names: | |
| bird_chinese_names[key] = value | |
| def search_youtube_video(query): | |
| """搜尋 YouTube 影片""" | |
| try: | |
| # 將英文鳥名轉換為中文鳥名 | |
| if query in bird_chinese_names: | |
| chinese_query = bird_chinese_names[query] | |
| logging.info(f"將英文鳥名 '{query}' 轉換為中文鳥名 '{chinese_query}'") | |
| query = chinese_query | |
| else: | |
| logging.warning(f"未找到 '{query}' 的中文對照名稱,使用原始名稱進行搜尋") | |
| logging.info(f"搜尋 YouTube 影片,關鍵字:{query}") | |
| search_response = ( | |
| youtube.search() | |
| .list( | |
| q=query, | |
| part="id,snippet", | |
| maxResults=1, | |
| type="video", | |
| channelId="UClCaVtB-pYpNOJGiGzQAhNA", # 你的頻道 ID | |
| ) | |
| .execute() | |
| ) | |
| if not search_response["items"]: | |
| logging.warning("未找到相關影片") | |
| return None, None, None | |
| video_id = search_response["items"][0]["id"]["videoId"] | |
| video_url = f"https://www.youtube.com/watch?v={video_id}" | |
| logging.info(f"找到影片:{video_url}") | |
| embed_html = f'<iframe width="100%" height="315" src="https://www.youtube.com/embed/{video_id}" frameborder="0" allowfullscreen></iframe>' | |
| # 生成 QR Code | |
| qr = qrcode.QRCode(version=1, box_size=10, border=5) | |
| qr.add_data(video_url) | |
| qr.make(fit=True) | |
| qr_img = qr.make_image(fill_color="black", back_color="white") | |
| qr_array = np.array(qr_img.convert("RGB")) # 轉換為RGB模式以確保兼容性 | |
| return video_url, embed_html, qr_array | |
| except Exception as e: | |
| logging.error(f"YouTube 搜尋錯誤: {str(e)}") | |
| return None, None, None | |
| def is_valid_youtube_url(url): | |
| """檢查是否為有效的 YouTube URL""" | |
| youtube_regex = ( | |
| r"(https?://)?(www\.)?" | |
| r"(youtube|youtu|youtube-nocookie)\.(com|be)/" | |
| r"(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})" | |
| ) | |
| match = re.match(youtube_regex, url) | |
| return match is not None | |
| def get_youtube_video_id(url): | |
| """從 YouTube URL 中提取視頻 ID""" | |
| youtube_regex = ( | |
| r"(https?://)?(www\.)?" | |
| r"(youtube|youtu|youtube-nocookie)\.(com|be)/" | |
| r"(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})" | |
| ) | |
| match = re.match(youtube_regex, url) | |
| if match: | |
| return match.group(6) | |
| return None | |
| def process_image(input_image): | |
| """處理圖片並進行物件偵測 (這部分保持不變)""" | |
| try: | |
| if input_image is None: | |
| logging.warning("未提供輸入圖片") | |
| return None, "請先上傳圖片", "", None | |
| logging.info("開始處理圖片...") | |
| # 建立輸出目錄 | |
| output_dir = "results" | |
| os.makedirs(output_dir, exist_ok=True) | |
| # 執行物件偵測 | |
| results = model.predict(input_image, save=False) | |
| result = results[0] | |
| # 取得偵測結果 | |
| detected_objects = [] | |
| for box in result.boxes: | |
| cls = int(box.cls[0]) | |
| conf = float(box.conf[0]) | |
| if conf > 0.8: # 信心度閾值 | |
| label = class_names.get(cls, f"類別 {cls}") | |
| detected_objects.append(label) | |
| logging.info(f"檢測到物件:{label},信心度:{conf:.2f}") | |
| # 取得處理後的圖片 | |
| result_image = result.plot() | |
| result_pil = Image.fromarray(result_image) | |
| # 搜尋布萊克獨木舟生態休閒中心 YouTube 影片 | |
| if detected_objects: | |
| video_url, embed_html, qr_image = search_youtube_video(detected_objects[0]) | |
| result_text = f"檢測到: {', '.join(detected_objects)}" | |
| else: | |
| video_url = "偵測該物件的信心度不足,未查詢到相關影片,偵測結果僅供參考" | |
| embed_html = "" | |
| qr_image = None | |
| result_text = "偵測該物件的信心度不足,未查詢到相關影片,偵測結果僅供參考" | |
| return result_pil, video_url, embed_html, qr_image | |
| except Exception as e: | |
| logging.error(f"處理錯誤: {str(e)}") | |
| return None, f"處理錯誤: {str(e)}", "", None | |
| def process_video(input_video): | |
| """處理影片並進行物件偵測""" | |
| try: | |
| if input_video is None: | |
| logging.warning("未提供輸入影片") | |
| return None, "未提供輸入影片", "", None # 修正返回的訊息數量 | |
| logging.info("開始處理影片...") | |
| output_dir = "results" | |
| os.makedirs(output_dir, exist_ok=True) | |
| timestamp = int(time.time()) | |
| run_name = f"predict_{timestamp}" | |
| logging.info(f"使用唯一識別名稱進行偵測: {run_name}") | |
| # 執行物件偵測,並直接處理結果 | |
| results = model( | |
| input_video, save=True, project=output_dir, name=run_name, stream=True | |
| ) | |
| # 儲存處理過的frame | |
| processed_frames = [] | |
| # 取得偵測結果和處理frame | |
| detected_objects = set() | |
| for result in results: | |
| for box in result.boxes: | |
| cls = int(box.cls[0]) | |
| conf = float(box.conf[0]) | |
| if conf > 0.8: | |
| label = class_names.get(cls, f"類別 {cls}") | |
| detected_objects.add(label) | |
| logging.info(f"檢測到物件:{label},信心度:{conf:.2f}") | |
| # 將result.plot()存入processed_frames | |
| processed_frames.append(result.plot()) | |
| # 影片格式轉換和輸出 | |
| predict_folder = os.path.join(output_dir, run_name) | |
| output_video_name = os.path.join(predict_folder, f"output_{timestamp}.mp4") | |
| if not os.path.exists(predict_folder): | |
| os.makedirs(predict_folder) | |
| # 取得影片的基本資訊 (從原始影片取得) | |
| cap = cv2.VideoCapture(input_video) # 改為讀取原始影片 | |
| fps = int(cap.get(cv2.CAP_PROP_FPS)) | |
| width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| cap.release() # 讀取完後就釋放 | |
| # 建立 VideoWriter 物件 (使用 mp4v codec) | |
| fourcc = cv2.VideoWriter_fourcc(*"avc1") | |
| out = cv2.VideoWriter(output_video_name, fourcc, fps, (width, height)) | |
| # 將處理過的 frame 寫入影片 | |
| for frame in processed_frames: | |
| out.write(frame) | |
| out.release() | |
| # 搜尋 YouTube 影片 (其餘部分保持不變) | |
| if detected_objects: | |
| video_url, embed_html, qr_image = search_youtube_video( | |
| list(detected_objects)[0] | |
| ) | |
| result_text = f"檢測到: {', '.join(detected_objects)}" | |
| else: | |
| video_url = "偵測該物件的信心度不足,未查詢到相關影片,偵測結果僅供參考" | |
| embed_html = "" | |
| qr_image = None | |
| result_text = "偵測該物件的信心度不足,未查詢到相關影片,偵測結果僅供參考" | |
| return output_video_name, video_url, embed_html, qr_image # 直接回傳名稱 | |
| except Exception as e: | |
| logging.error(f"影片處理錯誤: {str(e)}") | |
| return None, f"影片處理錯誤: {str(e)}", "", None | |
| def search_channel_videos(query): | |
| """搜尋布萊克獨木舟生態休閒中心頻道的影片""" | |
| try: | |
| logging.info(f"搜尋頻道影片,關鍵字:{query}") | |
| search_response = ( | |
| youtube.search() | |
| .list( | |
| q=query, | |
| part="id,snippet", | |
| maxResults=6, | |
| type="video", | |
| channelId="UClCaVtB-pYpNOJGiGzQAhNA", # 布萊克獨木舟生態休閒中心頻道 ID | |
| order="relevance" | |
| ) | |
| .execute() | |
| ) | |
| results = [] | |
| for item in search_response.get("items", []): | |
| video_id = item["id"]["videoId"] | |
| title = item["snippet"]["title"] | |
| description = item["snippet"]["description"] | |
| published_at = item["snippet"]["publishedAt"] | |
| embed_html = f'<div style="margin-bottom: 20px;"><h3>{title}</h3><p>發布時間:{published_at}</p><iframe width="100%" height="315" src="https://www.youtube.com/embed/{video_id}" frameborder="0" allowfullscreen></iframe><p>{description}</p></div>' | |
| results.append(embed_html) | |
| if not results: | |
| return "<p>查無相關影片</p>" | |
| return "<div style='max-width: 800px; margin: 0 auto;'>" + "".join(results) + "</div>" | |
| except Exception as e: | |
| logging.error(f"頻道影片搜尋錯誤: {str(e)}") | |
| return f"<p>搜尋錯誤: {str(e)}</p>" | |
| # 創建 Gradio 介面 | |
| with gr.Blocks(title="鳥類偵測系統") as demo: | |
| gr.Markdown(markdown_content) | |
| gr.Markdown("### 支援圖片、影片、即時攝影機") | |
| with gr.Tab("圖片偵測"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| image_input = gr.Image(label="上傳圖片", type="numpy") | |
| image_button = gr.Button("開始偵測") | |
| with gr.Column(scale=1): | |
| image_output = gr.Image(label="偵測結果") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| image_video_url = gr.Textbox( | |
| label="YouTube 影片連結", visible=True | |
| ) # 保留但隱藏 | |
| image_video_embed = gr.HTML(label="YouTube 影片") | |
| with gr.Column(scale=1): | |
| image_qr_output = gr.Image(label="QR Code") | |
| image_button.click( | |
| process_image, | |
| inputs=[image_input], | |
| outputs=[image_output, image_video_url, image_video_embed, image_qr_output], | |
| ) | |
| # 新增頻道影片搜尋頁籤 | |
| with gr.Tab("頻道影片搜尋"): | |
| gr.Markdown("### 布萊克獨木舟生態休閒中心頻道影片搜尋") | |
| with gr.Row(): | |
| search_input = gr.Textbox( | |
| label="請輸入關鍵字", | |
| placeholder="輸入要搜尋的關鍵字...", | |
| scale=4 | |
| ) | |
| search_button = gr.Button("搜尋", scale=1) | |
| with gr.Row(): | |
| results_html = gr.HTML( | |
| label="搜尋結果", | |
| value="<p>請輸入關鍵字進行搜尋</p>" | |
| ) | |
| search_button.click( | |
| fn=search_channel_videos, | |
| inputs=[search_input], | |
| outputs=[results_html] | |
| ) | |
| # 影片偵測頁籤 | |
| # with gr.Tab("影片偵測"): | |
| # with gr.Row(): | |
| # with gr.Column(scale=1): | |
| # video_input = gr.Video(label="上傳影片") | |
| # video_button = gr.Button("開始偵測") | |
| # with gr.Column(scale=1): | |
| # video_output = gr.Video( | |
| # label="偵測結果", | |
| # # streaming=True, # 不再需要 streaming | |
| # # autoplay=True # autoplay 根據你的需求決定是否保留 | |
| # ) | |
| # with gr.Row(): | |
| # with gr.Column(scale=1): | |
| # video_video_url = gr.Textbox( | |
| # label="YouTube 影片連結", visible=True | |
| # ) | |
| # video_video_embed = gr.HTML(label="YouTube 影片") | |
| # with gr.Column(scale=1): | |
| # video_qr_output = gr.Image(label="QR Code") | |
| # video_button.click( | |
| # process_video, | |
| # inputs=[video_input], | |
| # outputs=[video_output, video_video_url, video_video_embed, video_qr_output], | |
| # ) | |
| # 啟動應用程式 | |
| if __name__ == "__main__": | |
| logging.info("啟動網頁應用程式...") | |
| demo.launch(share=True, server_name="0.0.0.0", pwa=True) | |