| import json |
| import os |
| import re |
| import time |
| import pandas as pd |
| import gradio as gr |
| from google import genai |
| from google.genai import types |
| from huggingface_hub import hf_hub_download, HfApi |
|
|
| |
| REPO_ID = "Denny1911/Keywords" |
| FILENAME = "tags.json" |
| HF_TOKEN = os.environ.get("HF_TOKEN") |
|
|
| def load_tags_from_cloud(): |
| """從雲端 Dataset 下載最新 JSON""" |
| try: |
| filepath = hf_hub_download(repo_id=REPO_ID, filename=FILENAME, repo_type="dataset", token=HF_TOKEN) |
| with open(filepath, "r", encoding="utf-8") as f: |
| return json.load(f) |
| except Exception as e: |
| print(f"讀取雲端失敗,使用預設初始標籤。原因: {e}") |
| return { |
| "情緒基調": ["溫柔療癒", "熱血激情"], |
| "視覺畫面": ["璀璨星空"], |
| "適用場景": ["深夜沉思"], |
| "風格曲風": ["華語流行"] |
| } |
|
|
| def save_tags_to_cloud(tags_data): |
| """將更新後的 JSON 直接推回雲端 Dataset 儲存""" |
| with open(FILENAME, "w", encoding="utf-8") as f: |
| json.dump(tags_data, f, ensure_ascii=False, indent=2) |
| |
| try: |
| api = HfApi() |
| api.upload_file( |
| path_or_fileobj=FILENAME, |
| path_in_repo=FILENAME, |
| repo_id=REPO_ID, |
| repo_type="dataset", |
| token=HF_TOKEN |
| ) |
| print("雲端標籤庫同步成功!") |
| except Exception as e: |
| print(f"同步至雲端失敗。原因: {e}") |
|
|
|
|
| |
| |
| |
| def core_analyze_process(artist, song, current_tags): |
| client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY")) |
| |
| prompt = f""" |
| 你是一位專業的音樂與情感分析師。 |
| 請針對用戶提供的【歌曲:{song}】與【歌手:{artist}】,利用聯網搜尋功能找到這首歌的完整背景、歌詞與音樂資訊。 |
| |
| 請嚴格按照以下格式輸出結果(包含兩個區塊): |
| |
| ### 📝 1. 意境與情境描述 |
| [請用 150 字以內,精準、優美地簡述這首歌在描述什麼樣的情境、故事或情感。] |
| |
| ### 🏷️ 2. 統一標籤庫 |
| [請將這首歌的所有相關標籤,全部統一用「英文逗號 ,」隔開,排成一整行輸出,不要分段。] |
| |
| ✨【中英雙語對照輸出規則】✨: |
| 1. 只要輸出了「中文/非英文」的標籤,其後方必須「緊接著」輸出它的英文翻譯,並用逗號隔開。 |
| 2. 如果標籤本身就是數字(如發行年份 2007、速度 135 BPM)或是原本就屬於英文單字(如 Taiwan, 2000s),則保持原樣即可,絕對不要重複翻譯。 |
| |
| 字串中必須包含以下兩大類標籤: |
| 1. 靜態標籤(需遵循雙語對照規則): |
| 歌曲名、專輯或歌曲衍生名、發行年份、年代標籤(如 2000年代, 2000s)、地區、相關影視作品名稱、最著名的副歌第一句歌詞、歌曲速度(如 135 BPM)。 |
| |
| 2. 動態標籤(必須對照下方的【標準資料庫】進行語意對齊): |
| 包含【情緒基調】、【視覺畫面】、【適用場景】、【風格曲風】。 |
| - 語意對齊:如果你想到的詞與下方資料庫中的詞高度相似,必須自動校正並採用資料庫中的詞(採用後一樣要在後面補上英文翻譯)。 |
| - 發現新詞規則:如果你認為現有資料庫無法涵蓋,允许你創造新詞,但必須在「中文新詞」前面加上 `[NEW:類別]` 標記,並在其後方照常輸出英文翻譯。 |
| (例如:創造新曲風寫成 `[NEW:風格曲風]周氏情歌, Chou Style`;創造新情感寫成 `[NEW:情緒基調]青澀回憶, Youthful Memories`)。 |
| |
| 【目前的標準資料庫】(供你對齊與參考語意): |
| {json.dumps(current_tags, ensure_ascii=False)} |
| |
| 正確的【統一標籤庫】中英交錯輸出範例(必須只有一行長字串,不要帶引號): |
| 蒲公英的約定, The Promised Dandelion, 我box忙, On the Run, 2007, 2000年代, 2000s, 台灣, Taiwan, 不能說的秘密, Secret, 一起長大的約定 那樣清晰 打過勾的我相信, The promise of growing up together is so clear, 135 BPM, [NEW:風格曲風]周氏情歌, Chou Style, 溫柔療癒, Gentle and Healing, 璀璨星空, Starry Night, 深夜沉思, Midnight Reflection |
| """ |
|
|
| response = client.models.generate_content( |
| model='gemini-2.5-flash', |
| contents=prompt, |
| config=types.GenerateContentConfig( |
| tools=[types.Tool(google_search=types.GoogleSearch())] |
| ) |
| ) |
| |
| result_text = response.text |
| |
| new_found_tags = re.findall(r'\[NEW:(情緒基調|視覺畫面|適用場景|風格曲風)\]([^,\n]+)', result_text) |
| has_updates = False |
| for category, tag in new_found_tags: |
| tag = tag.strip() |
| if tag not in current_tags[category]: |
| current_tags[category].append(tag) |
| has_updates = True |
| |
| if has_updates: |
| save_tags_to_cloud(current_tags) |
| |
| clean_output = re.sub(r'\[NEW:[^\]]+\]', '', result_text) |
| |
| desc_match = re.search(r'### 📝 1\. 意境與情境描述\s*([\s\S]*?)(?=### 🏷️ 2\. 統一標籤庫|$)', clean_output) |
| tags_match = re.search(r'### 🏷️ 2\. 統一標籤庫\s*([\s\S]*)', clean_output) |
| |
| desc_content = desc_match.group(1).strip() if desc_match else "(未能成功生成情境描述)" |
| tags_content = tags_match.group(1).strip() if tags_match else clean_output |
| |
| return tags_content, desc_content |
|
|
|
|
| |
| |
| |
| def analyze_song_single(artist, song, progress=gr.Progress()): |
| progress(0.1, desc="🔄 正在連線雲端下載歷史標籤庫...") |
| current_tags = load_tags_from_cloud() |
| |
| progress(0.3, desc=f"🔍 正在啟動 Gemini 聯網檢索《{song}》...") |
| tags_content, desc_content = core_analyze_process(artist, song, current_tags) |
| |
| progress(1.0, desc="🎉 單曲分析大功告成!") |
| return tags_content, desc_content |
|
|
|
|
| |
| |
| |
| def analyze_song_batch(file_obj, delay_seconds, progress=gr.Progress()): |
| if file_obj is None: |
| return None, "❌ 請先上傳 Excel 或 CSV 檔案!" |
| |
| try: |
| if file_obj.name.endswith('.csv'): |
| df = pd.read_csv(file_obj.name) |
| else: |
| df = pd.read_excel(file_obj.name) |
| except Exception as e: |
| return None, f"❌ 檔案讀取失敗。原因: {e}" |
| |
| if df.shape[1] < 2: |
| return None, "❌ 檔案格式不符!表格必須至少包含兩欄(左邊歌曲,右邊歌手)。" |
| |
| total_songs = len(df) |
| results = [] |
| |
| progress(0.05, desc="🔄 正在連線雲端下載歷史標籤庫...") |
| current_tags = load_tags_from_cloud() |
| |
| for index, row in df.iterrows(): |
| song = str(row.iloc[0]).strip() |
| artist = str(row.iloc[1]).strip() |
| |
| current_idx = index + 1 |
| progress((index / total_songs), desc=f"🎵 [批次進度 {current_idx}/{total_songs}] 正在分析: {artist} - 《{song}》...") |
| |
| try: |
| |
| tags, _ = core_analyze_process(artist, song, current_tags) |
| |
| results.append({ |
| "歌曲": song, |
| "歌手": artist, |
| "雙語標籤庫": tags |
| }) |
| except Exception as e: |
| results.append({ |
| "歌曲": song, |
| "歌手": artist, |
| "雙語標籤庫": f"分析失敗: {e}" |
| }) |
| |
| if current_idx < total_songs and delay_seconds > 0: |
| for remaining in range(int(delay_seconds), 0, -1): |
| progress((current_idx / total_songs), desc=f"⏳ [防封鎖冷卻中] 已完成第 {current_idx} 首。等待 {remaining} 秒後繼續下一首...") |
| time.sleep(1) |
|
|
| progress(0.95, desc="💾 所有歌曲分析完畢!正在封裝成精簡版 Excel 檔案...") |
| output_df = pd.DataFrame(results) |
| output_filename = "音樂批次分析結果_精簡雙語版.xlsx" |
| output_df.to_excel(output_filename, index=False) |
| |
| progress(1.0, desc="🎉 批次分析全部完成!檔案已生成。") |
| return output_filename, f"✅ 成功處理完成!共計 {total_songs} 首歌曲,請點擊下方按鈕下載僅含三直欄的完美表格。" |
|
|
|
|
| |
| |
| |
| with gr.Blocks(title="🎵 智慧音樂關鍵字大數據分析系統") as demo: |
| gr.Markdown("# 🎵 智慧音樂關鍵字與意境分析系統 (全功能版)") |
| |
| with gr.Tabs(): |
| |
| with gr.Tab("🎯 單曲即時分析"): |
| gr.Markdown("輸入單一歌手與歌名,AI 自動聯網檢索,即時呈現雙語標籤海。") |
| with gr.Row(): |
| with gr.Column(): |
| artist_input = gr.Textbox(label="歌手 / 藝術家", placeholder="例如:周杰倫") |
| song_input = gr.Textbox(label="歌曲名稱", placeholder="例如:蒲公英的約定") |
| with gr.Row(): |
| clear_btn = gr.Button("Clear") |
| submit_btn = gr.Button("Submit", variant="primary") |
| with gr.Column(): |
| gr.Markdown("### 🏷️ 智慧生成雙語標籤庫") |
| output_tags = gr.Markdown() |
| with gr.Accordion("📝 點擊展開:查看 AI 歌曲意境與情境描述", open=False): |
| output_desc = gr.Markdown() |
| |
| submit_btn.click( |
| fn=analyze_song_single, |
| inputs=[artist_input, song_input], |
| outputs=[output_tags, output_desc] |
| ) |
| clear_btn.click( |
| fn=lambda: ("", "", "", ""), |
| inputs=None, |
| outputs=[artist_input, song_input, output_tags, output_desc] |
| ) |
|
|
| |
| with gr.Tab("📂 批次表格上傳分析"): |
| gr.Markdown("### 📊 多筆資料自動化分析") |
| gr.Markdown("請上傳一個 Excel (.xlsx) 或 CSV (.csv) 檔案。第一欄為「歌曲名稱」,第二欄為「歌手/團隊」。") |
| |
| with gr.Row(): |
| with gr.Column(): |
| file_input = gr.File(label="上傳音樂表格檔案", file_types=[".xlsx", ".csv"]) |
| |
| delay_slider = gr.Slider( |
| minimum=0, maximum=30, value=12, step=1, |
| label="⏳ 每筆分析之間的時間間隔 (秒)", |
| info="💡 建議維持 12 秒以上。由於免費版 Gemini 加上聯網搜尋有嚴格的每分鐘次數限制 (Rate Limit),間隔太短會導致後面整排分析失敗。" |
| ) |
| batch_submit_btn = gr.Button("🚀 開始批次自動分析", variant="primary") |
| |
| with gr.Column(): |
| batch_status = gr.Textbox(label="執行狀態報告", placeholder="等待上傳檔案...", interactive=False) |
| file_output = gr.File(label="📥 下載完整分析 Excel 結果報告") |
| |
| batch_submit_btn.click( |
| fn=analyze_song_batch, |
| inputs=[file_input, delay_slider], |
| outputs=[file_output, batch_status] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |