chore: 1.0.0 release
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
version: v2
|
||||
title: Open Kimi PPT Local Export Test
|
||||
size: [960, 540]
|
||||
pages:
|
||||
- pages/01.page
|
||||
@@ -0,0 +1,11 @@
|
||||
pageType: content
|
||||
background: {type: solid, color: "#F7F8FC"}
|
||||
elements:
|
||||
- elementId: title
|
||||
elementType: text
|
||||
bounds: [100, 180, 760, 120]
|
||||
content:
|
||||
align: [center, middle]
|
||||
fontFamily: MiSans
|
||||
text: |
|
||||
<p><span style="font-size:42px;color:#171923;font-weight:700">本地导出验证</span></p>
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
import importlib.util
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SCRIPTS_DIR = Path(__file__).resolve().parents[1] / "scripts"
|
||||
sys.path.insert(0, str(SCRIPTS_DIR))
|
||||
|
||||
SPEC = importlib.util.spec_from_file_location("export_images", SCRIPTS_DIR / "export_images.py")
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
assert SPEC and SPEC.loader
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
|
||||
|
||||
def make_images_zip(path: Path, names=("1.jpeg", "10.jpeg", "2.jpeg")) -> None:
|
||||
# 1x1 white JPEG, the smallest valid payload Pillow can open.
|
||||
import base64
|
||||
|
||||
pixel = base64.b64decode(
|
||||
"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////"
|
||||
"////////////////////////////////////////////2wBDAf//////////////////"
|
||||
"////////////////////////////////////////////wAARCAABAAEDASIAAhEBAxEB"
|
||||
"/8QAFQABAQAAAAAAAAAAAAAAAAAAAAX/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oADAMB"
|
||||
"AAIQAxAAAAGf/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABBQJ//8QAFBEBAAAA"
|
||||
"AAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgB"
|
||||
"AgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAAAAAA"
|
||||
"AAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABCf/8QAFBEBAAAAAAAAAAAA"
|
||||
"AAAAAAAAAP/aAAgBAwEPEBB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEPEBB/"
|
||||
"/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k="
|
||||
)
|
||||
with zipfile.ZipFile(path, "w") as archive:
|
||||
for name in names:
|
||||
archive.writestr(name, pixel)
|
||||
|
||||
|
||||
class ExportImagesTests(unittest.TestCase):
|
||||
def test_page_sort_key_orders_numeric_stems(self):
|
||||
paths = [Path("10.jpeg"), Path("2.jpeg"), Path("cover.jpeg"), Path("1.jpeg")]
|
||||
ordered = sorted(paths, key=MODULE.page_sort_key)
|
||||
self.assertEqual(
|
||||
[path.name for path in ordered],
|
||||
["1.jpeg", "2.jpeg", "10.jpeg", "cover.jpeg"],
|
||||
)
|
||||
|
||||
def test_is_image_zip_accepts_image_entries_only(self):
|
||||
with tempfile.TemporaryDirectory() as name:
|
||||
root = Path(name)
|
||||
good = root / "images.zip"
|
||||
make_images_zip(good)
|
||||
self.assertTrue(MODULE.is_image_zip(good))
|
||||
|
||||
bad = root / "text.zip"
|
||||
with zipfile.ZipFile(bad, "w") as archive:
|
||||
archive.writestr("readme.txt", "hello")
|
||||
self.assertFalse(MODULE.is_image_zip(bad))
|
||||
self.assertFalse(MODULE.is_image_zip(root / "missing.zip"))
|
||||
|
||||
def test_unzip_images_flattens_and_sorts(self):
|
||||
with tempfile.TemporaryDirectory() as name:
|
||||
root = Path(name)
|
||||
archive_path = root / "images.zip"
|
||||
make_images_zip(archive_path, names=("1.jpeg", "10.jpeg", "2.jpeg", "note.txt"))
|
||||
images = MODULE.unzip_images(archive_path, root / "pages")
|
||||
self.assertEqual(
|
||||
[path.name for path in images], ["1.jpeg", "2.jpeg", "10.jpeg"]
|
||||
)
|
||||
|
||||
def test_stitch_overview_grid(self):
|
||||
try:
|
||||
image_cls, draw_cls, image_font = MODULE.ensure_pillow()
|
||||
except MODULE.ExportError:
|
||||
self.skipTest("Pillow is not available")
|
||||
with tempfile.TemporaryDirectory() as name:
|
||||
root = Path(name)
|
||||
images = []
|
||||
for index in range(1, 5):
|
||||
path = root / f"{index}.jpeg"
|
||||
image = image_cls.new("RGB", (320, 180), (index * 40 % 255, 30, 60))
|
||||
image.save(path, "JPEG")
|
||||
images.append(path)
|
||||
overview = MODULE.stitch_overview(
|
||||
images, root / "overview.jpg", image_cls, draw_cls, image_font
|
||||
)
|
||||
self.assertTrue(overview.is_file())
|
||||
with image_cls.open(overview) as result:
|
||||
self.assertEqual(
|
||||
result.width,
|
||||
3 * MODULE.OVERVIEW_THUMB_WIDTH + 4 * MODULE.OVERVIEW_GAP,
|
||||
)
|
||||
rows = 2
|
||||
cell = MODULE.OVERVIEW_LABEL_HEIGHT + 360
|
||||
self.assertEqual(result.height, rows * cell + (rows + 1) * MODULE.OVERVIEW_GAP)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
import importlib.util
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "export_pptx.py"
|
||||
SPEC = importlib.util.spec_from_file_location("export_pptx", SCRIPT)
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
assert SPEC and SPEC.loader
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
|
||||
|
||||
class ExportPptxTests(unittest.TestCase):
|
||||
def test_parse_agent_browser_version(self):
|
||||
self.assertEqual(MODULE.parse_version("agent-browser 0.33.2"), (0, 33, 2))
|
||||
self.assertEqual(MODULE.parse_version("v1.4.0-beta.1"), (1, 4, 0))
|
||||
|
||||
@patch.object(MODULE.subprocess, "run")
|
||||
@patch.object(MODULE.shutil, "which")
|
||||
def test_old_agent_browser_is_upgraded(self, which, run):
|
||||
which.side_effect = ["/bin/agent-browser", "/bin/npm", "/bin/agent-browser"]
|
||||
run.side_effect = [
|
||||
MODULE.subprocess.CompletedProcess([], 0, "agent-browser 0.17.1\n"),
|
||||
MODULE.subprocess.CompletedProcess([], 0, "changed 1 package\n"),
|
||||
MODULE.subprocess.CompletedProcess([], 0, "agent-browser 0.33.2\n"),
|
||||
]
|
||||
self.assertEqual(MODULE.ensure_agent_browser(), "/bin/agent-browser")
|
||||
self.assertEqual(run.call_args_list[1].args[0], [
|
||||
"/bin/npm", "install", "-g", "agent-browser@latest"
|
||||
])
|
||||
|
||||
def test_fade_is_inserted_before_timing(self):
|
||||
source = (
|
||||
b'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
b'<p:sld xmlns:p="urn:test"><p:cSld><p:spTree><p:extLst/>'
|
||||
b'</p:spTree></p:cSld><p:clrMapOvr/><p:timing/><p:extLst/></p:sld>'
|
||||
)
|
||||
result_bytes = MODULE.replace_transition(source, "fade")
|
||||
result = result_bytes.decode("utf-8")
|
||||
self.assertIn("<p:transition", result)
|
||||
self.assertIn("<p:fade/>", result)
|
||||
self.assertGreater(result.index("<p:transition"), result.index("<p:clrMapOvr"))
|
||||
self.assertLess(result.index("<p:transition"), result.index("<p:timing"))
|
||||
MODULE.validate_transition_order(result_bytes, "fade")
|
||||
|
||||
def test_existing_transition_is_replaced_or_removed(self):
|
||||
source = (
|
||||
b'<p:sld xmlns:p="urn:test"><p:cSld/>'
|
||||
b'<p:transition><p:wipe/></p:transition><p:extLst/></p:sld>'
|
||||
)
|
||||
faded = MODULE.replace_transition(source, "fade").decode("utf-8")
|
||||
self.assertNotIn("p:wipe", faded)
|
||||
self.assertEqual(faded.count("<p:transition"), 1)
|
||||
MODULE.validate_transition_order(faded.encode("utf-8"), "fade")
|
||||
cleared = MODULE.replace_transition(source, "none").decode("utf-8")
|
||||
self.assertNotIn("p:transition", cleared)
|
||||
MODULE.validate_transition_order(cleared.encode("utf-8"), "none")
|
||||
|
||||
def test_nested_transition_is_relocated_to_slide_root(self):
|
||||
source = (
|
||||
b'<p:sld xmlns:p="urn:test"><p:cSld><p:spTree>'
|
||||
b'<p:transition><p:fade/></p:transition><p:extLst/>'
|
||||
b'</p:spTree></p:cSld><p:clrMapOvr/><p:extLst/></p:sld>'
|
||||
)
|
||||
result = MODULE.replace_transition(source, "fade")
|
||||
MODULE.validate_transition_order(result, "fade")
|
||||
self.assertEqual(MODULE.root_child_names(result), [
|
||||
"cSld", "clrMapOvr", "transition", "extLst"
|
||||
])
|
||||
|
||||
def test_patch_transitions_preserves_a_valid_zip(self):
|
||||
with tempfile.TemporaryDirectory() as name:
|
||||
deck = Path(name) / "test.pptx"
|
||||
with zipfile.ZipFile(deck, "w", zipfile.ZIP_DEFLATED) as archive:
|
||||
archive.writestr(
|
||||
"[Content_Types].xml",
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
||||
'<Override PartName="/ppt/presentation.xml" '
|
||||
f'ContentType="{MODULE.PPTX_CONTENT_TYPE}"/></Types>',
|
||||
)
|
||||
archive.writestr("ppt/presentation.xml", "<p:presentation xmlns:p=\"urn:test\"/>")
|
||||
archive.writestr(
|
||||
"ppt/slides/slide1.xml",
|
||||
'<p:sld xmlns:p="urn:test"><p:cSld/></p:sld>',
|
||||
)
|
||||
self.assertEqual(MODULE.patch_transitions(deck, "fade"), 1)
|
||||
with zipfile.ZipFile(deck) as archive:
|
||||
self.assertIsNone(archive.testzip())
|
||||
slide = archive.read("ppt/slides/slide1.xml")
|
||||
self.assertIn(b"<p:fade/>", slide)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user