Spaces:
Sleeping
Sleeping
File size: 22,529 Bytes
a378ff0 63d1eff a378ff0 63d1eff a378ff0 63d1eff a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 e1d0d08 a378ff0 8680a07 7088ddc 8680a07 7088ddc 8680a07 7088ddc 8680a07 a378ff0 77ba701 a378ff0 7088ddc 8680a07 7088ddc 8680a07 7088ddc 8680a07 7088ddc 8680a07 7088ddc 8680a07 7088ddc a378ff0 63d1eff | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 | 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)
|