From 18e6e92678d37f24e7f6fa207dcdf5e16c78ac46 Mon Sep 17 00:00:00 2001 From: malinkang Date: Thu, 4 Jul 2024 17:32:59 +0800 Subject: [PATCH] add tongyi speech to text --- .github/workflows/speech_text.yml | 32 +++ scripts/config.py | 41 ---- scripts/notion_helper.py | 25 ++- scripts/podcast.py | 108 +++------- scripts/speech_text.py | 315 ++++++++++++++++++++++++++++++ scripts/utils.py | 77 +++++--- 6 files changed, 446 insertions(+), 152 deletions(-) create mode 100644 .github/workflows/speech_text.yml create mode 100644 scripts/speech_text.py diff --git a/.github/workflows/speech_text.yml b/.github/workflows/speech_text.yml new file mode 100644 index 0000000..be16baa --- /dev/null +++ b/.github/workflows/speech_text.yml @@ -0,0 +1,32 @@ +name: speech to text + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + sync: + name: Sync + runs-on: ubuntu-latest + env: + NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} + NOTION_PAGE: ${{ secrets.NOTION_PAGE }} + COOKIE: ${{ secrets.TONGYI_COOKIE }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: speech to text + run: | + python -u scripts/speech_text.py \ No newline at end of file diff --git a/scripts/config.py b/scripts/config.py index 12c15b9..43217b8 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -38,44 +38,3 @@ "链接": URL, "收听时长": NUMBER, } -{ - "标题": { - "title": [ - { - "type": "text", - "text": {"content": "Vol.224 金色梦乡:你知道人最强大的武器是什么吗?"}, - } - ] - }, - "Description": { - "rich_text": [ - { - "type": "text", - "text": { - "content": "本期节目我们一起读小说《金色梦乡》,作者伊坂幸太郎。\n《金色梦乡》出版于2007年,讲述了平凡的前送货员青柳雅春被突然当作刺杀首相的凶手,遭到政府通缉,同时被媒体炒作网暴,成为“十恶不赦的罪人”,因此唯一的出路只有拼命逃跑,在惊险的跑路中,与警方短兵相接,也得到情义相挺,莫名其妙的命运捉弄中,他能否顺利逃出重围?这个故事的灵感来自于真实历史事件“肯尼迪遇刺案”。\n伊坂幸太郎(1971-),日本作家。2000年以《奥杜邦的祈祷》获得“新潮推理俱乐部奖”,由此跻身文坛,曾五度入围“直木奖”,是公认的“文坛才子”。\n你会听到:\n1、什么是套路?\n2、看“原著”的意义是什么?\n3、《金色梦乡》和伊坂幸太郎简介。\n4、如何理解书中关于美国、摇滚、披头士、刺杀总统等意象?\n5、精彩片段分享。\n6、伊坂幸太郎的作品为什么畅销?怎么理解“人类最后的武器是信任”和标题《金色梦乡》?\n片头曲:靛厂\n片尾曲:Golden Slumbers (Remastered 2009)\n主播:大壹 / 超哥 / 星光" - }, - } - ] - }, - "时间戳": {"number": 1712012400}, - "发布时间": { - "date": {"start": "2024-04-02 07:00:00", "time_zone": "Asia/Shanghai"} - }, - "音频": { - "rich_text": [ - { - "type": "text", - "text": { - "content": "https://jt.ximalaya.com//GKwRINsJ3BQ4An6aiQK-6Qkb-aacv2-48K.m4a?channel=rss&album_id=29887212&track_id=718781905&uid=68693381&jt=https://audio.xmcdn.com/storages/e11f-audiofreehighqps/0B/16/GKwRINsJ3BQ4An6aiQK-6Qkb-aacv2-48K.m4a" - }, - } - ] - }, - "Eid": { - "rich_text": [{"type": "text", "text": {"content": "660b3dad1c3c7de44a82f773"}}] - }, - "时长": {"number": 5169}, - "Podcast": {"relation": [{"id": "87723a05-dd9a-494d-a934-9ff4140fcb21"}]}, - "链接": {"url": "hhttps://www.xiaoyuzhoufm.com/episode/660b3dad1c3c7de44a82f773"}, - "状态": {"status": {"name": "在听"}}, -} diff --git a/scripts/notion_helper.py b/scripts/notion_helper.py index 8e0a29e..fccffee 100644 --- a/scripts/notion_helper.py +++ b/scripts/notion_helper.py @@ -31,6 +31,7 @@ class NotionHelper: "EPISODE_DATABASE_NAME": "Episode", "ALL_DATABASE_NAME": "全部", "AUTHOR_DATABASE_NAME": "Author", + "MINDMAP_DATABASE_NAME": "思维导图", } database_id_dict = {} image_dict = {} @@ -53,6 +54,9 @@ def __init__(self): ) self.all_database_id = self.database_id_dict.get( self.database_name_dict.get("ALL_DATABASE_NAME") + ) + self.mindmap_database_id = self.database_id_dict.get( + self.database_name_dict.get("MINDMAP_DATABASE_NAME") ) def extract_page_id(self, notion_url): @@ -198,7 +202,7 @@ def delete_block(self, block_id): @retry(stop_max_attempt_number=3, wait_fixed=5000) - def query_all(self, database_id, filter): + def query_all_by_filter(self, database_id, filter): results = [] has_more = True start_cursor = None @@ -212,4 +216,21 @@ def query_all(self, database_id, filter): start_cursor = response.get("next_cursor") has_more = response.get("has_more") results.extend(response.get("results")) - return results \ No newline at end of file + return results + + @retry(stop_max_attempt_number=3, wait_fixed=5000) + def query_all(self, database_id): + """获取database中所有的数据""" + results = [] + has_more = True + start_cursor = None + while has_more: + response = self.client.databases.query( + database_id=database_id, + start_cursor=start_cursor, + page_size=100, + ) + start_cursor = response.get("next_cursor") + has_more = response.get("has_more") + results.extend(response.get("results")) + return results diff --git a/scripts/podcast.py b/scripts/podcast.py index 218931b..e5617f9 100644 --- a/scripts/podcast.py +++ b/scripts/podcast.py @@ -9,9 +9,6 @@ from dotenv import load_dotenv load_dotenv() -DOUBAN_API_HOST = os.getenv("DOUBAN_API_HOST", "frodo.douban.com") -DOUBAN_API_KEY = os.getenv("DOUBAN_API_KEY", "0ac44ae016490db2204ce0a042db2916") - from config import ( movie_properties_type_dict, book_properties_type_dict, @@ -110,32 +107,6 @@ def get_episode(pid, timestamp): return results -@retry(stop_max_attempt_number=3, wait_fixed=5000) -def get_history(): - results = [] - url = "https://api.xiaoyuzhoufm.com/v1/episode-played/list-history" - data = { - "limit": 25, - } - loadMoreKey = "" - while loadMoreKey is not None: - if loadMoreKey: - data["loadMoreKey"] = loadMoreKey - resp = requests.post(url, json=data, headers=headers) - if resp.ok: - loadMoreKey = resp.json().get("loadMoreKey") - d = resp.json().get("data") - for item in d: - episode = item.get("episode") - pubDate = pendulum.parse(episode.get("pubDate")).in_tz("UTC").int_timestamp - episode["pubDate"] = pubDate - results.append(episode) - else: - refresh_token() - raise Exception(f"Error {data} {resp.text}") - return results - - def check_podcast(pid): """检查是否已经插入过""" filter = {"property": "Pid", "rich_text": {"equals": pid}} @@ -146,16 +117,6 @@ def check_podcast(pid): return response["results"][0].get("id") -def check_eposide(eid): - """检查是否已经插入过""" - filter = {"property": "Eid", "rich_text": {"equals": eid}} - response = notion_helper.query( - database_id=notion_helper.episode_database_id, filter=filter - ) - if len(response["results"]) > 0: - return response["results"][0].get("id") - - def get_timestamp(id): """检查是否已经插入过""" filter = {"property": "Podcast", "relation": {"contains": id}} @@ -176,17 +137,6 @@ def get_timestamp(id): return 0 -def delete(): - """删除未听的""" - filter = {"property": "状态", "status": {"equals": "未听"}} - results = notion_helper.query_all( - database_id=notion_helper.episode_database_id, filter=filter - ) - for index,result in enumerate(results): - print(f"正在删除第{index+1}个,共{len(results)}个") - notion_helper.delete_block(block_id=result.get("id")) - - def merge_podcast(list1, list2): results = [] results.extend(list1) @@ -198,10 +148,10 @@ def merge_podcast(list1, list2): def insert_podcast(): + refresh_token() list1 = get_mileage() list2 = get_podcast() results = merge_podcast(list1, list2) - dict = {} for index, result in enumerate(results): podcast = {} podcast["播客"] = result.get("title") @@ -211,12 +161,11 @@ def insert_podcast(): podcast["收听时长"] = result.get("playedSeconds", 0) podcast["Description"] = result.get("description") podcast["链接"] = f"https://www.xiaoyuzhoufm.com/podcast/{result.get('pid')}" - if result.get("latestEpisodePubDate"): - podcast["最后更新时间"] = ( - pendulum.parse(result.get("latestEpisodePubDate")) - .in_tz("UTC") - .int_timestamp - ) + podcast["最后更新时间"] = ( + pendulum.parse(result.get("latestEpisodePubDate")) + .in_tz("UTC") + .int_timestamp + ) cover = result.get("image").get("picUrl") podcast["全部"] = [ notion_helper.get_relation_id( @@ -239,36 +188,31 @@ def insert_podcast(): print( f"正在同步 = {result.get('title')},共{len(results)}个播客,当前是第{index+1}个" ) - page_id = check_podcast(pid) - if page_id: - notion_helper.update_page(page_id=page_id, properties=properties) - else: + if not page_id: page_id = notion_helper.create_page( parent=parent, properties=properties, icon=get_icon(cover) ).get("id") - dict[pid] =(page_id, cover) - return dict + else: + notion_helper.update_page(page_id=page_id, properties=properties) + insert_episode(pid, page_id, cover) -def insert_episode(episodes, d): - episodes.sort(key=lambda x: x["pubDate"]) - for index, result in enumerate(episodes): - pid = result.get("pid") - if pid not in d: - continue +def insert_episode(pid, page_id, cover): + timestamp = get_timestamp(page_id) + results = get_episode(pid, timestamp) + results.sort(key=lambda x: x["pubDate"]) + for index, result in enumerate(results): episode = {} episode["标题"] = result.get("title") episode["Description"] = result.get("description") episode["时间戳"] = result.get("pubDate") episode["发布时间"] = result.get("pubDate") episode["音频"] = result.get("media").get("source").get("url") - eid = result.get("eid") - episode["Eid"] = eid - + episode["Eid"] = result.get("eid") episode["时长"] = result.get("duration") episode["喜欢"] = result.get("isPicked") - episode["Podcast"] = [d.get(pid)[0]] + episode["Podcast"] = [page_id] episode["链接"] = f"hhttps://www.xiaoyuzhoufm.com/episode/{result.get('eid')}" status = "未听" if result.get("isFinished"): @@ -278,25 +222,17 @@ def insert_episode(episodes, d): episode["状态"] = status properties = utils.get_properties(episode, book_properties_type_dict) print( - f"正在同步 = {result.get('title')},共{len(episodes)}个Episode,当前是第{index+1}个" + f"正在同步 = {result.get('title')},共{len(results)}个Episode,当前是第{index+1}个" ) parent = { "database_id": notion_helper.episode_database_id, "type": "database_id", } - page_id = check_eposide(eid) - if page_id: - notion_helper.update_page(page_id=page_id, properties=properties) - else: - notion_helper.create_page( - parent=parent, properties=properties, icon=get_icon(d.get(pid)[1]) - ) + notion_helper.create_page( + parent=parent, properties=properties, icon=get_icon(cover) + ) if __name__ == "__main__": notion_helper = NotionHelper() - refresh_token() - d = insert_podcast() - episodes = get_history() - insert_episode(episodes, d) - delete() + insert_podcast() diff --git a/scripts/speech_text.py b/scripts/speech_text.py new file mode 100644 index 0000000..60607b8 --- /dev/null +++ b/scripts/speech_text.py @@ -0,0 +1,315 @@ +import argparse +import json +import os +import pendulum +from retrying import retry +import requests +from notion_helper import NotionHelper +import utils +from dotenv import load_dotenv +import urllib.parse + +load_dotenv() + + +headers = { + "accept": "application/json, text/plain, */*", + "accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", + "content-type": "application/json", + "origin": "https://tongyi.aliyun.com", + "priority": "u=1, i", + "referer": "https://tongyi.aliyun.com/efficiency/doc/transcripts/g2y8qeaoogbxnbeo?source=2", + "sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"macOS"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-site", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "x-b3-sampled": "1", + "x-b3-spanid": "540e0d18e52cdf0d", + "x-b3-traceid": "75a25320c048cde87ea3b710a65d196b", + "x-tw-canary": "", + "x-tw-from": "tongyi", +} + + +def get_dir(): + """获取文件夹""" + results = [] + url = ( + "https://qianwen.biz.aliyun.com/assistant/api/record/dir/list/get?c=tongyi-web" + ) + response = requests.post(url, headers=headers) + if response.status_code == 200: + data = response.json().get("data") + print(response.json()) + for i in data: + results.extend(dir_list(i.get("dir").get("id"))) + else: + print("请求失败:", response.status_code) + return results + + +def dir_list(dir_id): + """获取文件夹内所有的item""" + result = [] + pageNo = 1 + pageSize = 48 + while True: + payload = {"dirIdStr": dir_id, "pageNo": pageNo, "pageSize": pageSize} + url = "https://qianwen.biz.aliyun.com/assistant/api/record/list?c=tongyi-web" + response = requests.post(url, headers=headers, json=payload) + if response.status_code == 200: + batchRecord = response.json().get("data").get("batchRecord") + if len(batchRecord) == 0: + break + for i in batchRecord: + result.extend(i.get("recordList")) + pageNo += 1 + else: + print("请求失败:", response.status_code) + break + return result + + +def get_note(transId): + """暂时不支持子list和表格""" + url = "https://tw-efficiency.biz.aliyun.com/api/doc/getTransDocEdit?c=tongyi-web" + payload = { + "action": "getTransDocEdit", + "version": "1.0", + "transId": transId + } + response = requests.post(url, headers=headers, json=payload) + if response.ok: + note = response.json().get("data").get("content") + print(note) + if note: + data = json.loads(content) + children = [] + for paragraph in data: + type = "paragraph" + skip_outer = False + is_checked = False + rich_text = [] + if isinstance(paragraph, list): + for span in paragraph: + if isinstance(span,dict): + if "list" in span: + type="bulleted_list_item" + isOrdered = span.get("list").get("isOrdered") + isTaskList = span.get("list").get("isTaskList") + if isOrdered: + type="numbered_list_item" + if isTaskList: + type = "to_do" + is_checked = span.get("list").get("isChecked") + if isinstance(span, list): + if(span[0]=='span'): + for i in range(2, len(span)): + bold = False + highlight = False + content = span[i][2] + if "bold" in span[i][1]: + bold = span[i][1].get("bold") + if "highlight" in span[i][1]: + highlight = True + rich_text.append(get_text(content,bold,highlight)) + if span[0]=='tag': + time = utils.format_milliseconds(span[1].get("metadata").get("time")) + rich_text.append({"type": "text","text": {"content": time},"annotations": {"underline": True,"color":"blue"}}) + if span[0]=='img': + skip_outer = True + url = span[1].get("src") + children.append({"type": "image","image": {"type": "external","external": {"url":url}}}) + if skip_outer: + continue + child = {"type": type,type: {"rich_text": rich_text,"color": "default"}} + if type=="to_do": + child[type]["checked"] = is_checked + children.append(child) + return children + else: + print("请求失败") + +def get_text(content,bold=False,highlight=False): + text = {"type": "text","text": {"content": content},"annotations": {"bold": bold}} + if highlight: + text["annotations"]["color"]="red_background" + return text +def get_all_lab_info(transId): + url = "https://tw-efficiency.biz.aliyun.com/api/lab/getAllLabInfo?c=tongyi-web" + payload = { + "action": "getAllLabInfo", + "content": ["labInfo", "labSummaryInfo"], + "transId": transId, + } + response = requests.post(url, headers=headers, json=payload) + mindmap = None + children = [] + if response.status_code == 200: + labInfo = response.json().get("data").get("labCardsMap").get("labInfo") + for i in labInfo: + name = i.get("basicInfo").get("name") + if name == "qa问答": + children.append(utils.get_heading(2,"问题回顾")) + if name =="议程": + children.append(utils.get_heading(2,"章节速览")) + for content in i.get("contents",[]): + for contentValue in content.get("contentValues"): + if name=="思维导图": + mindmap = contentValue.get("json") + if name=="议程": + title = f"{utils.format_milliseconds(contentValue.get('time'))} {contentValue.get('value')}" + children.append(utils.get_heading(3,title)) + summary = contentValue.get("summary") + children.append(utils.get_callout(summary,{"emoji":"💡"})) + if name=="qa问答": + title = contentValue.get("title") + value = contentValue.get("value") + beginTime = contentValue.get("extensions")[0].get("sentenceInfoOfAnswer")[0].get("beginTime") + beginTime = utils.format_milliseconds(beginTime) + title = f"{beginTime} {title}" + children.append(utils.get_heading(3,title)) + children.append(utils.get_callout(value,{"emoji":"💡"})) + return (children, mindmap) + else: + print("请求脑图失败:", response.status_code) + + +def insert_mindmap(block_id, mindmap): + """插入思维导图""" + id = ( + notion_helper.append_blocks( + block_id=block_id, + children=[utils.get_bulleted_list_item(mindmap.get("content"))], + ) + .get("results")[0] + .get("id") + ) + if mindmap.get("children"): + for child in mindmap.get("children"): + insert_mindmap(id, child) + +def get_trans_result(transId): + payload = { + "action": "getTransResult", + "version": "1.0", + "transId":transId, + } + url = "https://tw-efficiency.biz.aliyun.com/api/trans/getTransResult?c=tongyi-web" + response = requests.post(url, headers=headers, json=payload) + if response.status_code == 200: + response_data = response.json() + user_dict = {} + if response_data.get("data").get("tag").get("identify"): + user_info = json.loads().get("user_info") + for key,value in user_info.items(): + user_dict[key] = value.get("name") + + children = [] + for i in json.loads(response_data.get("data").get("result")).get("pg"): + content = "" + name = "" + uid = i.get("ui") + avatar = None + if uid in user_dict: + name = user_dict.get(uid) + avatar = get_author_avatar(name) + else: + name = f"发言人{uid}" + if avatar is None: + avatar = "https://www.notion.so/icons/user_gray.svg" + title = f'{name} {utils.format_milliseconds(i.get("sc")[0].get("bt"))}' + children.append(utils.get_heading(3,title)) + for j in i.get("sc"): + content+=j.get("tc") + children.append(utils.get_callout(content,utils.get_icon(avatar))) + return children + else: + print("请求失败:", response.status_code) + return None + +author_cache = {} +def get_author_avatar(author): + if author in author_cache: + return author_cache[author] + filter = {"property": "标题", "title": {"equals": author}} + r = notion_helper.query(database_id=notion_helper.author_database_id,filter=filter) + if len(r.get("results"))>0: + avatar= r.get("results")[0].get("icon").get("external").get("url") + author_cache[author] = avatar + return avatar + +if __name__ == "__main__": + notion_helper = NotionHelper() + headers["cookie"] = os.getenv("COOKIE") + filter = {"property": "音频转文字", "checkbox": {"equals": False}} + episode_list = notion_helper.query_all_by_filter( + notion_helper.episode_database_id, filter=filter + ) + episode_dict = { + utils.get_property_value(x.get("properties").get("标题")): x + for x in episode_list + } + print(episode_dict.keys()) + results = get_dir() + for i in results: + title = i.get("recordTitle") + print(f"{title} {title in episode_dict}") + children = [] + if title in episode_dict: + print(f"title: {title}") + episode = episode_dict.get(title) + episode_properties = episode.get("properties") + episode_page_id = episode.get("id") + audio_url = utils.get_property_value(episode_properties.get("音频")) + podcast = utils.get_property_value(episode_properties.get("Podcast")) + podcast = utils.get_property_value( + notion_helper.client.pages.retrieve(podcast[0].get("id")) + .get("properties") + .get("播客") + ) + transId = i.get("genRecordId") + cover = episode.get("cover").get("external").get("url") + player_url = f"https://notion-music.malinkang.com/player?url={urllib.parse.quote(audio_url)}&name={urllib.parse.quote(title)}&cover={urllib.parse.quote(cover)}&artist={urllib.parse.quote(podcast)}" + children.append({"type": "embed", "embed": {"url": player_url}}) + info,mindmap = get_all_lab_info(transId) + mindmap_page_id = None + if mindmap: + parent = { + "database_id": notion_helper.mindmap_database_id, + "type": "database_id", + } + properties = {"title": [{"type": "text", "text": {"content": title}}]} + mindmap_page_id = notion_helper.create_page( + parent=parent, + properties=properties, + icon=episode.get("icon"), + ).get("id") + insert_mindmap(mindmap_page_id, mindmap) + mindmap_url = f"https://mindmap.malinkang.com/markmap/{mindmap_page_id.replace('-','')}?token={os.getenv('NOTION_TOKEN')}" + children.append(utils.get_heading(2,"思维导图")) + children.append({"type": "embed", "embed": {"url": mindmap_url}}) + if info: + children.extend(info) + trans = get_trans_result(transId) + if trans: + children.append(utils.get_heading(2,"语音转文字")) + children.extend(trans) + note = get_note(transId) + if note: + children.append(utils.get_heading(2,"笔记")) + children.extend(note) + for i in range(0,len(children)//100+1): + notion_helper.append_blocks(block_id=episode_page_id,children=children[i*100:(i+1)*100]) + properties = {"音频转文字":{"checkbox":True}} + if mindmap_page_id: + properties["思维导图"] = {"relation": [{"id": mindmap_page_id}]} + notion_helper.update_page( + page_id=episode_page_id, + properties=properties, + ) + + diff --git a/scripts/utils.py b/scripts/utils.py index a734a3a..783ee91 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -52,6 +52,42 @@ def get_heading(level, content): } +def get_paragraph(content, bold=False): + return { + "type": "paragraph", + "paragraph": { + "rich_text": [ + { + "type": "text", + "text": { + "content": content[:MAX_LENGTH], + }, + "annotations": {"bold": bold}, + } + ], + "color": "default", + }, + } + +def get_bulleted_list_item(content, bold=False): + return { + "type": "bulleted_list_item", + "bulleted_list_item": { + "rich_text": [ + { + "type": "text", + "text": { + "content": content[:MAX_LENGTH], + }, + "annotations": {"bold": bold}, + } + ], + "color": "default", + }, + } + + + def get_table_of_contents(): """获取目录""" return {"type": "table_of_contents", "table_of_contents": {"color": "default"}} @@ -118,28 +154,8 @@ def get_quote(content): } -def get_callout(content, style, colorStyle, reviewId): +def get_callout(content, icon): # 根据不同的划线样式设置不同的emoji 直线type=0 背景颜色是1 波浪线是2 - emoji = "〰️" - if style == 0: - emoji = "💡" - elif style == 1: - emoji = "⭐" - # 如果reviewId不是空说明是笔记 - if reviewId != None: - emoji = "✍️" - color = "default" - # 根据划线颜色设置文字的颜色 - if colorStyle == 1: - color = "red" - elif colorStyle == 2: - color = "purple" - elif colorStyle == 3: - color = "blue" - elif colorStyle == 4: - color = "green" - elif colorStyle == 5: - color = "yellow" return { "type": "callout", "callout": { @@ -151,8 +167,7 @@ def get_callout(content, style, colorStyle, reviewId): }, } ], - "icon": {"emoji": emoji}, - "color": color, + "icon":icon, }, } @@ -411,3 +426,19 @@ def download_image(url, save_dir="cover"): def upload_cover(url): cover_file = download_image(url) return upload_image("cover", f"{cover_file.split('/')[-1]}", cover_file) + + +def format_milliseconds(milliseconds): + # Convert milliseconds to total seconds + total_seconds = milliseconds // 1000 + + # Calculate hours, minutes, and seconds + hours = total_seconds // 3600 + minutes = (total_seconds % 3600) // 60 + seconds = total_seconds % 60 + + # Format the output string based on the presence of hours + if hours > 0: + return f"{hours:02}:{minutes:02}:{seconds:02}" + else: + return f"{minutes:02}:{seconds:02}"