Jiangxz commited on
Commit
4e62860
·
verified ·
1 Parent(s): 1c3e56f

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +234 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ # 財政部財政資訊中心 江信宗
3
+
4
+ import gradio as gr
5
+ from googleapiclient.discovery import build
6
+ import os
7
+ import re
8
+ import requests
9
+ import io
10
+ import json
11
+
12
+ custom_css = """
13
+ .center-aligned {
14
+ text-align: center !important;
15
+ color: #ff4081;
16
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
17
+ margin-bottom: 0 !important;
18
+ }
19
+ .input-background {
20
+ background-color: #B7E0FF !important;
21
+ padding: 15px !important;
22
+ border-radius: 10px !important;
23
+ }
24
+ .tabs-background {
25
+ background-color: #FFF5E4 !important;
26
+ padding: 0px !important;
27
+ border-radius: 10px !important;
28
+ margin: 0 !important;
29
+ }
30
+ .output-background {
31
+ background-color: #FFF4B5 !important;
32
+ padding: 15px !important;
33
+ border-radius: 10px !important;
34
+ }
35
+ .output-text-size textarea {
36
+ font-size: 18px !important;
37
+ }
38
+ .gen-button {
39
+ border-radius: 10px !important;
40
+ background-color: #ff4081 !important;
41
+ color: white !important;
42
+ font-weight: bold !important;
43
+ transition: all 0.3s ease !important;
44
+ }
45
+ .gen-button:hover {
46
+ background-color: #f50057 !important;
47
+ transform: scale(1.05);
48
+ }
49
+ .clear-button {
50
+ border-radius: 10px !important;
51
+ background-color: #333333 !important;
52
+ color: white !important;
53
+ font-weight: bold !important;
54
+ transition: all 0.3s ease !important;
55
+ }
56
+ .clear-button:hover {
57
+ background-color: #000000 !important;
58
+ transform: scale(1.05);
59
+ }
60
+ """
61
+
62
+ def check_description(video_id, api_key):
63
+ sponsored_keywords = ["折扣碼", "贊助廠商", "優惠碼", "專屬優惠", "預約表單", "賣場連結", "合作夥伴", "合作廠商"]
64
+ try:
65
+ youtube = build('youtube', 'v3', developerKey=api_key)
66
+ request = youtube.videos().list(
67
+ part="snippet",
68
+ id=video_id
69
+ )
70
+ response = request.execute()
71
+ if 'items' in response and len(response['items']) > 0:
72
+ description = response['items'][0]['snippet']['description'].lower().strip()
73
+ for keyword in sponsored_keywords:
74
+ if keyword.lower() in description:
75
+ return True
76
+ return False
77
+ except Exception as e:
78
+ print(f"獲取 YouTube 說明欄資訊時發生錯誤:{str(e)}")
79
+ return False
80
+
81
+ def extract_video_id(url):
82
+ match = re.search(r'(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\n?]+)', url)
83
+ if match:
84
+ return match.group(1)
85
+ return None
86
+
87
+ def seconds_to_minutes_seconds(seconds):
88
+ minutes = int(seconds // 60)
89
+ remaining_seconds = int(seconds % 60)
90
+ return f"{minutes}分{remaining_seconds}秒"
91
+
92
+ def format_segments(segments):
93
+ formatted_segments = []
94
+ for i, segment in enumerate(segments, 1):
95
+ start_time = seconds_to_minutes_seconds(segment[0])
96
+ end_time = seconds_to_minutes_seconds(segment[1])
97
+ formatted_segments.append(f"【廣告{i}時間軸】{start_time}至{end_time}")
98
+ return "\n".join(formatted_segments)
99
+
100
+ def process_input(youtube_url, txt_file, api_key):
101
+ if not api_key:
102
+ api_key = os.getenv("YOUR_API_TOKEN")
103
+ if youtube_url:
104
+ video_id = extract_video_id(youtube_url)
105
+ if video_id:
106
+ api_url = f"https://sponsor.ajay.app/api/skipSegments?videoID={video_id}"
107
+ try:
108
+ response = requests.get(api_url)
109
+ if response.status_code == 404:
110
+ # 使用 YouTube Data API 檢查說明欄
111
+ if check_description(video_id, api_key):
112
+ return "查核結果:影片內嵌贊助廣告,再請人工複查贊助事實。"
113
+ else:
114
+ return "查核結果:未找到贊助廣告片段。請使用地端影片分析程式查驗。"
115
+ response.raise_for_status()
116
+ data = response.json()
117
+
118
+ if data:
119
+ segments = [item['segment'] for item in data]
120
+ formatted_output = format_segments(segments)
121
+ return f"{formatted_output}\n查核結果:影片內嵌贊助廣告,再請人工複查贊助事實。"
122
+ else:
123
+ # 使用 YouTube Data API 檢查說明欄
124
+ if check_description(video_id):
125
+ return "查核結果:影片內嵌贊助廣告,再請人工複查贊助事實。"
126
+ else:
127
+ return "查核結果:未找到贊助廣告片段。請人工複查是否有贊助內容。"
128
+ except requests.RequestException as e:
129
+ return f"請求發生錯誤:{str(e)}。請稍後再試或人工複查。"
130
+ else:
131
+ return "無效的 YouTube URL。請確保輸入正確的 YouTube 影片網址。"
132
+ elif txt_file is not None:
133
+ try:
134
+ if isinstance(txt_file, str):
135
+ with open(txt_file, 'r', encoding='utf-8') as file:
136
+ content = file.read()
137
+ elif hasattr(txt_file, 'name'):
138
+ with open(txt_file.name, 'r', encoding='utf-8') as file:
139
+ content = file.read()
140
+ else:
141
+ return "無法讀取上傳的文件。請確保上傳了有效的文本文件。"
142
+ gr.Info("檔案內容每列僅一個網址,以換行分隔下一個網址。")
143
+ urls = content.strip().split('\n')
144
+ results = []
145
+ for url in urls:
146
+ url = url.strip()
147
+ result = f"《URL》{url}\n"
148
+ video_id = extract_video_id(url)
149
+ if video_id:
150
+ api_url = f"https://sponsor.ajay.app/api/skipSegments?videoID={video_id}"
151
+ try:
152
+ response = requests.get(api_url)
153
+ if response.status_code == 404:
154
+ result += "查核結果:未找到贊助廣告片段。請使用地端影片分析程式查驗。"
155
+ else:
156
+ response.raise_for_status()
157
+ data = response.json()
158
+ if data:
159
+ segments = [item['segment'] for item in data]
160
+ formatted_output = format_segments(segments)
161
+ result += f"{formatted_output}\n查核結果:影片內嵌贊助廣告,再請人工複查贊助事實。"
162
+ else:
163
+ result += "查核結果:未找到贊助廣告片段。請人工複查是否有贊助內容。"
164
+ except requests.RequestException as e:
165
+ result += f"請求發生錯誤:{str(e)}。請稍後再試或人工複查。"
166
+ else:
167
+ result += "無效的 YouTube URL。請確保輸入正確的 YouTube 影片網址。"
168
+ results.append(result)
169
+ return "\n\n".join(results)
170
+ except Exception as e:
171
+ return f"處理文件時發生錯誤:{str(e)}。請確保上傳了有效的文本文件。"
172
+ else:
173
+ gr.Warning("請輸入 YouTube 網址或上傳純文字檔!!")
174
+ return "請輸入 YouTube 網址或上傳純文字檔!!"
175
+
176
+ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
177
+ gr.Markdown("""
178
+ # 💰 YouTuber 營銷贊助稽查 - 財政部財政資訊中心 🏷️
179
+ > ### **※ 建構租稅公平,開拓更多可能性,自動化稽查 YouTuber 業配及銷售收益情形,系統布署:江信宗,適用稅目:綜合所得稅與營業稅。**
180
+ """, elem_classes="center-aligned")
181
+
182
+ def clear_youtube_input():
183
+ return gr.update(value="")
184
+
185
+ with gr.Tabs(elem_classes="tabs-background") as tabs:
186
+ with gr.TabItem("YouTube 網址"):
187
+ youtube_input = gr.Textbox(label="請輸入 YouTube 網址:", autofocus=True, max_lines=2, elem_classes="input-background")
188
+ with gr.TabItem("整批網址稽查"):
189
+ file_input = gr.File(label="上傳純文字檔:", file_types=[".csv", ".txt"], elem_classes="input-background")
190
+ with gr.TabItem("API Key"):
191
+ api_key_input = gr.Textbox(type="password", label="請輸入您的 API Key:", placeholder="YouTube Data API v3 Key", elem_classes="input-background")
192
+
193
+ tabs.change(fn=clear_youtube_input, inputs=None, outputs=youtube_input)
194
+
195
+ with gr.Row():
196
+ submit_btn = gr.Button("傳送", variant="primary", scale=2, elem_classes="gen-button")
197
+ clear_btn = gr.Button("清除", variant="secondary", scale=1, elem_classes="clear-button")
198
+ output = gr.Textbox(label="稽查結果:", max_lines=20, elem_classes="output-background output-text-size")
199
+ gr.HTML(
200
+ """
201
+ <span style="font-size: 20px; color: black; font-weight:bold;">※ 影片或音訊檔建議使用</span><a href="https://huggingface.co/spaces/Jiangxz/Meeting_Minutes" style="font-size: 20px; color: red; font-weight:bold;">財資凊彩歐北寫</a><span style="font-size: 20px; color: black; font-weight:bold;">轉譯後,透過LLMs分析內容判斷營銷或贊助與否。</span>
202
+ """)
203
+ submit_btn.click(fn=process_input, inputs=[youtube_input, file_input, api_key_input], outputs=output)
204
+ def clear_inputs():
205
+ gr.Info("已成功清除所有內容。")
206
+ return "", None, ""
207
+ clear_btn.click(
208
+ fn=clear_inputs,
209
+ inputs=None,
210
+ outputs=[youtube_input, file_input, output]
211
+ )
212
+
213
+ gr.HTML("""
214
+ <script>
215
+ document.addEventListener('click', function(e) {
216
+ if (e.target && e.target.textContent === '清除') {
217
+ var fileInputs = document.querySelectorAll('input[type="file"]');
218
+ fileInputs.forEach(function(input) {
219
+ input.value = '';
220
+ });
221
+ var uploadTexts = document.querySelectorAll('.file-preview');
222
+ uploadTexts.forEach(function(text) {
223
+ text.textContent = '';
224
+ });
225
+ }
226
+ });
227
+ </script>
228
+ """)
229
+
230
+ if __name__ == "__main__":
231
+ if "SPACE_ID" in os.environ:
232
+ iface.launch()
233
+ else:
234
+ iface.launch(share=True, show_api=False)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ requests
3
+ google-api-python-client