mirror of
https://github.com/happycola233/tchMaterial-parser.git
synced 2026-10-17 05:11:30 +08:00
Fix #43: 建议添加音频下载
Signed-off-by: Mr-Neutr0n <64578610+Mr-Neutr0n@users.noreply.github.com>
This commit is contained in:
@@ -85,6 +85,33 @@ def parse(url: str, bookmarks: bool) -> list[tuple[str, str, str, list[dict]]] |
|
||||
data: dict = response.json()
|
||||
|
||||
# 3. 获取资源标题、下载链接及章节目录
|
||||
def get_audio_info(audio_data: dict, root_title: str | None = None) -> tuple[str, str, str, list[dict]] | None: # 解析教材关联的音频资源(如英语教材听力)
|
||||
# 音频资源的标题存放在 global_title 字典中(键为语言代码,如 zh-CN)
|
||||
title_data = audio_data.get("global_title")
|
||||
audio_title = title_data.get("zh-CN") or title_data.get("en") if isinstance(title_data, dict) else title_data or audio_data.get("title") or audio_data.get("id")
|
||||
title: str = f"{root_title} - {audio_title}" if root_title else audio_title
|
||||
resource_url: str | None = None
|
||||
resource_format = "mp3"
|
||||
|
||||
# 优先选择转码后的 MP3 文件(ti_file_flag 为 href),否则回退到源文件
|
||||
for item in audio_data["ti_items"]:
|
||||
if item.get("ti_file_flag") not in ("href", "source") or item.get("ti_format") != "mp3":
|
||||
continue
|
||||
|
||||
resource_url = item.get("ti_storage") # 获取并构造资源的 URL
|
||||
if resource_url:
|
||||
resource_url = resource_url.replace("cs_path:${ref-path}", "https://r1-ndr-private.ykt.cbern.com.cn")
|
||||
else:
|
||||
resource_url = next((url for url in item.get("ti_storages") or [] if url), None)
|
||||
if resource_url:
|
||||
resource_format = item.get("ti_format") or "mp3"
|
||||
break
|
||||
|
||||
if not resource_url:
|
||||
return None
|
||||
|
||||
return title, resource_url, resource_format, []
|
||||
|
||||
def get_resource_info(resource_data: dict, root_title: str | None = None) -> tuple[str, str, str, list[dict]] | None:
|
||||
title: str = f"{root_title} - {resource_data.get('title') or resource_data.get('id')}" if root_title else resource_data.get("title") or resource_data.get("id")
|
||||
resource_url: str | None = None
|
||||
@@ -221,6 +248,17 @@ def parse(url: str, bookmarks: bool) -> list[tuple[str, str, str, list[dict]]] |
|
||||
if resource_info:
|
||||
resources_info.append(resource_info)
|
||||
|
||||
if content_type == "assets_document": # 教材可能带有配套的音频资源(如英语教材听力)
|
||||
try:
|
||||
audios_resp = session.get(f"https://s-file-1.ykt.cbern.com.cn/zxx/ndrs/resources/{content_id}/relation_audios.json")
|
||||
audios_data: list[dict] = audios_resp.json()
|
||||
for audio in audios_data:
|
||||
audio_info = get_audio_info(audio, data.get("title"))
|
||||
if audio_info:
|
||||
resources_info.append(audio_info)
|
||||
except Exception: # 音频资源不是必需的,获取失败时直接跳过
|
||||
pass
|
||||
|
||||
return resources_info
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import unittest
|
||||
|
||||
from src.tchmaterial_parser import api
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, json_data: object) -> None:
|
||||
self._json = json_data
|
||||
|
||||
def json(self) -> object:
|
||||
return self._json
|
||||
|
||||
|
||||
class FakeSession:
|
||||
def __init__(self, details: dict, audios: list[dict]) -> None:
|
||||
self.details = details
|
||||
self.audios = audios
|
||||
|
||||
def get(self, url: str, *args: tuple, **kwargs: dict) -> FakeResponse:
|
||||
if "relation_audios.json" in url:
|
||||
return FakeResponse(self.audios)
|
||||
return FakeResponse(self.details)
|
||||
|
||||
|
||||
DETAILS = {
|
||||
"id": "book-1",
|
||||
"title": "英语七年级上册",
|
||||
"ti_items": [
|
||||
{
|
||||
"ti_is_source_file": True,
|
||||
"ti_format": "pdf",
|
||||
"ti_storage": "cs_path:${ref-path}/edu_product/esp/assets/book-1.pkg/英语七年级上册.pdf",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
AUDIOS = [
|
||||
{
|
||||
"id": "audio-1",
|
||||
"global_title": {"zh-CN": "1 Starter Section 2 Activity 2"},
|
||||
"ti_items": [
|
||||
{
|
||||
"ti_file_flag": "href",
|
||||
"ti_format": "mp3",
|
||||
"ti_storage": "cs_path:${ref-path}/edu_product/esp/assets/audio-1.t/1.mp3",
|
||||
},
|
||||
{
|
||||
"ti_file_flag": "source",
|
||||
"ti_format": "wav",
|
||||
"ti_storage": "cs_path:${ref-path}/edu_product/esp/assets/audio-1.t/1.wav",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "audio-2",
|
||||
"global_title": {"zh-CN": "2 Starter Section 3 Activity 1"},
|
||||
"ti_items": [
|
||||
{
|
||||
"ti_file_flag": "href",
|
||||
"ti_format": "mp3",
|
||||
"ti_storage": "cs_path:${ref-path}/edu_product/esp/assets/audio-2.t/2.mp3",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class AudioParseTest(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.addCleanup(setattr, api, "session", api.session)
|
||||
|
||||
def parse_book(self) -> list[tuple[str, str, str, list[dict]]] | None:
|
||||
api.session = FakeSession(DETAILS, AUDIOS)
|
||||
url = "https://basic.smartedu.cn/tchMaterial/detail?contentType=assets_document&contentId=book-1&catalogType=tchMaterial&subCatalog=tchMaterial"
|
||||
return api.parse(url, False)
|
||||
|
||||
def test_textbook_with_audio_returns_pdf_and_mp3s(self) -> None:
|
||||
results = self.parse_book()
|
||||
self.assertIsNotNone(results)
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertEqual(results[0][2], "pdf")
|
||||
self.assertEqual(results[1][1], "https://r1-ndr-private.ykt.cbern.com.cn/edu_product/esp/assets/audio-1.t/1.mp3")
|
||||
self.assertEqual(results[1][2], "mp3")
|
||||
self.assertEqual(results[1][0], "英语七年级上册 - 1 Starter Section 2 Activity 2")
|
||||
self.assertEqual(results[2][0], "英语七年级上册 - 2 Starter Section 3 Activity 1")
|
||||
|
||||
def test_textbook_without_audio_returns_only_pdf(self) -> None:
|
||||
api.session = FakeSession(DETAILS, [])
|
||||
url = "https://basic.smartedu.cn/tchMaterial/detail?contentType=assets_document&contentId=book-1&catalogType=tchMaterial&subCatalog=tchMaterial"
|
||||
results = api.parse(url, False)
|
||||
self.assertIsNotNone(results)
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0][2], "pdf")
|
||||
|
||||
def test_audio_fetch_failure_is_ignored(self) -> None:
|
||||
class FailingAudiosSession(FakeSession):
|
||||
def get(self, url: str, *args: tuple, **kwargs: dict) -> FakeResponse:
|
||||
if "relation_audios.json" in url:
|
||||
raise RuntimeError("network error")
|
||||
return super().get(url, *args, **kwargs)
|
||||
|
||||
api.session = FailingAudiosSession(DETAILS, [])
|
||||
url = "https://basic.smartedu.cn/tchMaterial/detail?contentType=assets_document&contentId=book-1&catalogType=tchMaterial&subCatalog=tchMaterial"
|
||||
results = api.parse(url, False)
|
||||
self.assertIsNotNone(results)
|
||||
self.assertEqual(len(results), 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user