MyGo 云音乐
用 Go 与 MyGo 写成的第三方网易云音乐桌面客户端。原生界面,没有 WebView;边下边播,无损与 Hi-Res,逐字歌词与「律动」,刘海播放器。
Translation pending
สร้างวิดีโอประวัติศาสตร์ด้วยแผนที่และสคริปต์
Translation pending
AI summary · website and README
MediaJavaScript · HTMLGitHub @Tube2322OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
เอนจิ้นเล่าเรื่องประวัติศาสตร์บนแผนที่ดาวเทียมจริง: ป้อนสคริปต์ฉาก (พิกัด/แผนกล้อง/บทพากย์) เว็บจัดกล้อง หมุด เส้นทาง เอฟเฟกต์ และเสียงพากย์จริงให้เองตามจังหวะ พาท 1 — UX/UI พื้นฐาน • เต็มจอตามขนาดจอจริงเสมอตอนแก้ไข (คอม/มือถือ/แท็บเล็ตขนาดไหนก็ได้) — ไม่ผูกสัดส่วนวิดีโอจนกว่าจะกด "บันทึกวิดีโอ" • แผงนำเข้าสคริปต์ซีน วางข้อมูลทีละแถวหรือไฟล์ Excel • เส้นเวลาฉากด้านล่าง คลิกกระโดดไปแต่ละฉาก สีบอกแผนกล้อง • หมุดไอคอน+ป้ายชื่อ, เส้นทางโค้งพร้อมหัวลูกศรระหว่างฉาก • insert-overlay (กล่องภาพลอย PiP) แยกจาก cut-to-insert (เต็มจอ) • ป้ายแบรนด์/วอเตอร์มาร์ก ตั้งชื่อย่อ+มุมได้Read the full README ↗
เอนจิ้นเล่าเรื่องประวัติศาสตร์บนแผนที่ดาวเทียมจริง: ป้อนสคริปต์ฉาก (พิกัด/แผนกล้อง/บทพากย์) เว็บจัดกล้อง หมุด เส้นทาง เอฟเฟกต์ และเสียงพากย์จริงให้เองตามจังหวะ
พาท 1 — UX/UI พื้นฐาน
พาท 2 — แผนที่จริง
flyTo/easeTo ตามแผนกล้อง 7 แบบ (รวม orbit หมุนกล้องจริงด้วย bearing)maplibregl.Marker และ geojson line layerพาท 3 — เสียงพากย์จริง
server.py (proxy ในเครื่อง เพราะเบราว์เซอร์เรียก edge-tts ตรงไม่ได้);; ในช่องสคริปต์ — แต่ละช่วงพากย์แยกกันแล้วเล่นต่อเนื่อง ซับไตเติลสลับตามจริงgetDisplayMedia + MediaRecorder อัดหน้าจอสด (ต้องเลือกแชร์ "แท็บนี้" ตอนเบราว์เซอร์ถาม) ได้ไฟล์ .webm — เหมาะกับดูตัวอย่างเร็วๆพาท 4 — เลือกสัดส่วนตอนส่งออก + บันทึก/โหลดโปรเจกต์ + เรนเดอร์ฝั่งเซิร์ฟเวอร์
render.py เลือกสัดส่วนแยกผ่าน --aspect 916/169 เหมือนเดิม ไม่เกี่ยวกับตัวเอดิเตอร์เลย.json (สคริปต์+แบรนด์+สัดส่วน) และ auto-save สคริปต์ล่าสุดไว้ใน localStorage ของเบราว์เซอร์ (กันรีเฟรชแล้วหาย)render.py — เรนเดอร์วิดีโอฝั่งเซิร์ฟเวอร์แบบไม่ต้องเฝ้าหน้าจอ: พากย์เสียงจริงทุกฉากล่วงหน้า วัดความยาวจริง แล้วขับ Chromium แบบ headless (Playwright) ให้เล่นตามจังหวะเสียงเป๊ะๆ อัดภาพ แล้วรวมภาพ+เสียงเป็น .mp4 ที่สมบูรณ์ — ไม่มี UI เครื่องมือแก้ไขติดไปในวิดีโอ (ต่างจากปุ่ม "บันทึกวิดีโอ" สด)insert ได้ (แทนต้องมี url รูปพร้อมอยู่แล้ว)พาท 5 — ฟอร์แมต tag ยืดหยุ่น + ไฮไลต์เขตแดนจริง + กล้องเอียง/หมุน
key=value แบบไม่ต้องเรียงลำดับ ควบคู่กับฟอร์แมต column เดิม (ใช้แบบไหนก็ได้ แม้ในไฟล์เดียวกัน) — พิมพ์เฉพาะคีย์ที่จำเป็น ที่เหลือ default ให้tilt= (0-60°) และ bearing= (0-360°) ปรับมุมเอียง/ทิศกล้องต่อฉากได้ตรงๆ แทนที่จะ fix pitch=0 เสมอ — ให้ภาพมุมกล้อง 3D แบบในคลิปตัวอย่าง (Animaps/MapRecap)highlight=country/province/place ไฮไลต์เขตแดนจริงจาก OpenStreetMap (Nominatim reverse geocoding ตรงจากพิกัดฉาก ไม่ใช่ค้นชื่อ จึงแม่นยำ) พร้อมป้ายชื่อเขตขึ้นกลางจอแบบเคลื่อนไหวพาท 6 — ลูกเล่นแบบ AnimateMyMap: War Morph, fill reveal, border trace หลายทิศ, ค้นหาสถานที่
warmorph=RRGGBB:lat1,lng1:lat2,lng2 — พื้นที่ยึดครองค่อยๆเปลี่ยนสีจากฝ่ายแพ้เป็นฝ่ายชนะจริง โดยตัด polygon จริงด้วยเส้นที่กำหนด (ไม่ใช่แค่วาดทับ) ใช้คู่กับ highlightreveal=fade/wipe/split/circular/diamond — รูปแบบการเผยพื้นที่ไฮไลต์ ตัด polygon จริงด้วยหน้ากากที่โตขึ้นตามเวลา (ใช้เทคนิคเดียวกับ warmorph)trace=one/two/tworeverse/four — จำนวนจุดเริ่มลากเส้นขอบเขตพร้อมกัน + เพิ่ม glow ใต้เส้นmainland=on — ตัดเกาะ/ดินแดนไกลออกไปทิ้งตอนคำนวณกรอบกล้องอัตโนมัติ (เช่น ฝรั่งเศสไม่รวมเฟรนช์เกียนา)labelfont / labelsize / labelweight — ปรับฟอนต์/ขนาด/น้ำหนักตัวอักษรป้ายชื่อหมุดเฉพาะฉาก/api/geocode ผ่าน Nominatim forward search) พิมพ์ชื่อแล้วคลิกผลลัพธ์เพื่อแทรกแถวพร้อมพิกัดจริงลงสคริปต์ทันที ไม่ต้องรู้ lat,lng เองแผนกล้อง 7 แบบ: establishing (ภาพเปิดกว้าง) · fly-to (กล้องบินเดินทาง) · push-in (ซูมเข้าถึงจุดหมาย) · zoom-out (ซูมออกเผยภาพรวม) · orbit (กล้องหมุนรอบจุดสนใจ) · cut-to-insert (ตัดเข้าภาพเต็มจอ) · insert-overlay (แทรกภาพลอย PiP)
พาท 7 — แก้อนิเมชันกระตุก ให้ลื่นไหลขึ้น
highlight + กล้องแบบ auto-frame (establishing/fly-to/push-in/zoom-out) จะมีกล้องขยับ 2 รอบซ้อนกัน — รอบแรกจาก moveCamera() ไปที่พิกัดหมุดทันที รอบสองจาก showBoundary() ไปที่กรอบเขตแดนจริงตอนโหลดเสร็จ (มาช้ากว่าเพราะต้องรอ Nominatim) ทำให้กล้องกระตุกเหมือนสะดุดกลางอากาศ — แก้โดยให้ showBoundary() เป็นเจ้าของการขยับกล้องเพียงจุดเดียวสำหรับฉากประเภทนี้easeInOutCubic) ให้ทุกอนิเมชันที่ขับด้วย requestAnimationFrame เอง (ลากเส้นขอบเขต, เผยพื้นที่ไฮไลต์, war morph, ไอคอนพาหนะวิ่งตามเส้นทาง) แทนความเร็วคงที่แบบเส้นตรง — เริ่ม/จบนุ่มนวลกว่าเดิมพาท 8 — ตัดเส้นไฮไลต์ให้อยู่แค่บนแผ่นดิน + เครื่องมือจาก AnimateMyMap Studio
data/land_mask_50m.geojson, ผ่าน shapely ฝั่ง server.py) ก่อนส่งให้หน้าเว็บเสมอ — ผลลัพธ์คือเส้นไฮไลต์แนบชายฝั่งจริงเหมือน AnimateMyMap โดยไม่ต้องพึ่งความแม่นยำของ mapper แต่ละประเทศใน OSMcaption=ข้อความ | captionpos=top/center/bottom — ข้อความลอยกลางจอ ไม่ผูกกับหมุด สำหรับทำหัวข้อ/คำโปรยแยกจากซับไตเติลบทพากย์ (เทียบเท่า "Add Text")geophoto=url:lat,lng — ปักรูปจริง (จาก URL ที่อัปโหลดไว้แล้ว) ลงตามพิกัดจริงบนแผนที่ เป็นการ์ดรูปเด้งเข้า (เทียบเท่า "Add Image")draw=lat,lng;lat,lng;... | drawcolor=RRGGBB — ลากเส้นอิสระตามพิกัดที่พิมพ์เอง ใช้ขีดเส้นทาง/วงล้อม/เน้นจุดที่ไม่ใช่ขอบเขตทางการ (เทียบเท่า "Pen" แบบพิมพ์พิกัดแทนลากเมาส์ เพราะเอนจิ้นนี้เป็นระบบสคริปต์ ไม่ใช่ตัวแก้ไขด้วยเมาส์)persist=N — ให้เขตแดนที่ไฮไลต์ไว้ (พร้อมป้ายชื่อ) ค้างอยู่ต่ออีก N ฉากถัดไปแม้ฉากนั้นไม่ได้ตั้ง highlight เอง ใช้เล่าเรื่องต่อเนื่องในพื้นที่เดิมหลายจังหวะโดยไม่ต้องไฮไลต์ซ้ำ (เทียบเท่า "Keep this group visible")landfill=flag ที่มีอยู่แล้ว, "Pen" ของเขาเป็นฟีเจอร์ปิด (ต้องอัปเกรดแพลน) จึงยังไม่มีอ้างอิงเทียบให้แน่ชัด — เพิ่ม draw= ให้เป็นเวอร์ชันของเราไปก่อนตามแนวคิดเดียวกันพาท 9 — ปรับแต่งก่อนอัด (เสียง/เพลง/จังหวะ/สไตล์) + จัดระเบียบแผงนำเข้าสคริปต์
/api/tts (ตรวจรูปแบบค่าก่อนส่งเข้า edge_tts.Communicate เสมอกันค่าผิดรูปแบบหลุดเข้าไป)render.py --bgm ไฟล์.mp3 --bgm-volume 0.25) จะ mix รวมเข้ากับเสียงพากย์จริงด้วย ffmpeg amix ก่อนรวมเป็นวิดีโอสุดท้ายsceneGap): พักเงียบระหว่างฉากได้ 0-3 วินาที กันเนื้อหาแน่นเกินไปไม่มีจังหวะหายใจ — render.py --gap วินาที แทรกช่วงเงียบจริงในแทร็กเสียงให้ตรงกับที่ browser หยุดรอเช่นกันrender.py --subpos/--subcolor/--subsize/--subweight ให้ตรงกันตอนเรนเดอร์คลิปยาว<details> แยกจากกัน ไม่กางค้างเต็มพื้นที่เหมือนเดิม — เห็นแค่ช่องพิมพ์สคริปต์ + ปุ่ม "แปลงเป็นฉาก" ทันทีที่เปิดแผง, ปุ่มแปลง/ล้างลอยติดขอบล่างเสมอ (sticky) และรายการฉากที่แปลงแล้วจำกัดความสูงมีสกอลของตัวเอง — แก้ปัญหาสคริปต์ยาวๆแล้วต้องเลื่อนเมาส์ไกลกว่าจะเจอปุ่มhide=topbar,timeline ล็อกปุ่มเล่นตอนแก้ไข: เดิมถ้าฉากไหนตั้ง hide=topbar (สำหรับให้วิดีโอที่อัดออกมาไม่ติด UI) มันจะซ่อนแถบควบคุมทันทีแม้ตอนแก้ไข/พรีวิวในเบราว์เซอร์ ทำให้กดเล่น/หยุดไม่ได้อีกเลยถ้าฉากนั้นเป็นฉากที่กำลังเปิดอยู่ — แก้ให้ hide=topbar/timeline มีผลจริงเฉพาะตอนกด "บันทึกวิดีโอ" หรือตอน render.py อัดจริงเท่านั้น ตอนแก้ไขปกติจะเห็นแถบควบคุมเสมอพาท 10 — กล้อง fly-to ซูมตามระยะทางจริง, ไม่ดีฟอลต์ไอคอนพาหนะผิดยุค, พากย์เสียงทั้งคลิปให้เสร็จก่อนเล่น
cam=fly-to ซูมตามเนื้อหาจริง: เดิม fly-to ใช้ zoom 6.2 ตายตัวเสมอ ทำให้จุดสองจุดที่ใกล้กันมากๆ (เช่นในจังหวัดเดียวกัน) กล้องยังถอยไกลเกินพื้นที่จริงเห็นแค่จุดเล็กๆกลางจอ — แก้ให้คำนวณกรอบกล้องจาก map.cameraForBounds ครอบทั้งจุดต้นทาง-ปลายทางจริง (จำกัดช่วง zoom 3-10 กันจุดใกล้กันมากๆซูมเข้าจนแปลก) ใช้เฉพาะตอนไม่มี highlight (ถ้ามี highlight ให้ showBoundary() เป็นเจ้าของกล้องเหมือนเดิม)transport= เดิมดีฟอลต์เป็น plane ทำให้ฉากประวัติศาสตร์ก่อนยุคเครื่องบินมีเครื่องบินบินให้เห็นอย่างผิดยุค — ตอนนี้ไม่ระบุ transport= แปลว่าไม่มีไอคอนวิ่งบนเส้นทางเลย (มีแต่เส้นทาง+ลูกศร) ต้องระบุเองถ้าต้องการไอคอนพาหนะพาท 11 — ลูกเล่นสไตล์ History Toon Lab / Whyhistory: โฟกัสขาวดำ, สีไฮไลต์เอง, ป้ายวงกลม, กล่องแทรกมีเส้นโยง
focus=on: ทำพื้นหลังทั้งจอเป็นขาวดำ (ปรับ raster-saturation ของเลเยอร์ภาพถ่ายดาวเทียมเป็น -1) เหลือแค่เขตที่ไฮไลต์เป็นสีสดจัด (ฟิลด์ทึบขึ้นเป็น ~0.78 จากปกติ 0.18) ให้ความรู้สึกแบบคลิป Whyhistory ที่ไฮไลต์ประเทศเดียวเด่นออกมาจากพื้นหลัง ต้องใช้คู่กับ highlighthighlightcolor=RRGGBB: เปลี่ยนสีไฮไลต์เขตแดน/เส้นขอบ/glow จากเหลืองทองดีฟอลต์เป็นสีที่ต้องการ (เช่นให้แต่ละประเทศในซีรีส์เดียวกันมีสีเป็นเอกลักษณ์ของตัวเอง)badge=flag:xx:lat,lng:ป้ายชื่อ หรือ badge=icon:🏛️:lat,lng:ป้ายชื่อ: ไอคอนวงกลม (ธงชาติจริงผ่าน /api/flag หรืออีโมจิ) พร้อมป้ายชื่อปักตามพิกัดจริง เทียบเท่าไอคอนวงกลม+ป้ายชื่อของ Whyhistorycallout=url:lat,lng:ป้ายชื่อ: วงแหวนสีทองชี้จุดพิกัดจริงบนแผนที่ พร้อมเส้นโยงเรืองแสงไปยังกล่องรูป/ภาพแทรกมุมขวาบน (ตำแหน่งวงแหวนคำนวณสดทุกเฟรมจาก map.project() เหมือนกลไก shade spotlight เดิม เส้นโยงเลยขยับตามกล้องได้จริง) เทียบเท่าการ zoom-in ชี้จุดพร้อมภาพหลักฐานแทรกของคลิปไทย-กัมพูชาพาท 12 — ความแม่นยำเขตแดนทั่วโลก + สวิตช์ตัดชายฝั่ง
country=4, province=6, place=10) ซึ่งคาลิเบรตจากลำดับชั้นการปกครองไทยเป็นหลัก พอใช้กับประเทศอื่นที่โครงสร้างต่างกันเลยได้ระดับผิด — ตอนนี้ไล่ยิงตาม "บันไดค่า zoom" ต่อระดับ แล้วตรวจ addresstype ที่ Nominatim ตอบกลับมาจริงว่าตรงระดับที่ขอไหม แบ่งเป็นชนิดหลัก (เจอแล้วใช้ทันที) กับชนิดสำรอง (ใช้ต่อเมื่อไล่จนหมดแล้วไม่เจอชนิดหลัก)city (เดิมได้ suburb = เขตย่อยไม่ใช่ตัวเมือง), โตเกียวได้ city, ซิดนีย์ระดับ province ได้ state = New South Wales, กรุงเทพระดับ place ได้เขตพระนคร (suburb ซึ่งเป็นระดับเขตจริงของ กทม. ใน OSM)data/land_mask_10m.geojson, ~10MB) ชายฝั่งละเอียดขึ้นประมาณ 10 เท่า — ไฟล์ 10m เก็บทวีปเป็น MultiPolygon ก้อนใหญ่ จึงต้องแตกเป็นรูปหลายเหลี่ยมเดี่ยว 6,837 ชิ้นตอนโหลด ไม่งั้นการกรองด้วย bbox ไม่ช่วยอะไรและตัดช้ามากlandclip=0/1 ได้พาท 13 — แผงนำเข้าสคริปต์ยื่นเต็มจอ, ปุ่มควบคุมย้ายมุมซ้ายบน, แก้ browser cache ค้างโค้ดเก่า
server.py เดิมเสิร์ฟ script.js/styles.css แบบไฟล์ static ธรรมดา ไม่มี header กัน cache และไม่มี version ต่อท้าย URL — เบราว์เซอร์บางตัวจะจำ response เก่าไว้แบบไม่ยอมเช็กซ้ำเลยแม้รีสตาร์ตเซิร์ฟเวอร์ ทำให้แก้โค้ดแล้วเหมือนไม่มีผล (ต้องล้าง cache มือทุกครั้ง) — แก้โดยเสิร์ฟ index.html เองพร้อมแปะ query ?v=<mtime ไฟล์จริง> ต่อท้าย script.js/styles.css อัตโนมัติทุกครั้งที่มีการแก้ไข บวกส่ง Cache-Control: no-store ทุก response กัน browser cache ใดๆค้างอีกพาท 14 — "สร้างฉากง่ายๆ" (จากจุด A ไป B) + เส้นทางวิ่งตามถนนจริง
route=road: เส้นทางระหว่างฉากก่อนหน้ากับฉากนี้วิ่งตามถนนจริง (เรียก OSRM demo server สาธารณะฟรี ไม่ต้องมีคีย์ ผ่าน /api/route ที่ proxy ให้) แทนเส้นโค้งจำลองเดิม — วาดเส้นโค้งจำลองไปก่อนทันที (กันหน้าจอค้างรอเน็ต) แล้วสลับเป็นเส้นถนนจริงเมื่อโหลดเสร็จ ไอคอนพาหนะก็วิ่งตามเส้นถนนจริงนี้ด้วยถ้าตั้ง transport=pointAtFraction) รองรับทั้งเส้นโค้งจำลองเดิมและเส้นถนนจริงที่มีเป็นร้อยจุดในโค้ดเดียวกันพาท 15 — เส้นประวิ่ง, กล้องตามพาหนะ, ความเร็วเดินทางจริง + ขัดความสวยงาม 3 จุด
scene-line-layer) ตอนนี้ไล่สลับ line-dasharray ต่อเนื่องตลอดเวลา (marching ants) แทนเส้นประนิ่งๆเดิม เพราะ MapLibre ไม่มี line-dashoffset ให้ใช้ตรงๆfollow=on: กล้องไล่ตามไอคอนพาหนะไปตลอดเส้นทาง (ซูมค้างที่ระดับ "ติดตาม" ~12 ไม่ใช่ซูมเห็นเส้นทางทั้งหมดนิ่งๆแบบเดิม) ใช้กับ cam=fly-to — แก้บั๊กระหว่างพัฒนา: เดิม map.jumpTo({center}) ที่ขับกล้องทุกเฟรมไปขัดจังหวะ easeTo ที่ตั้งซูมไว้ตอนต้นฉาก ทำให้ซูมค้างกลางทางไม่ถึงเป้าหมาย ต้องระบุ zoom ชัดเจนทุกครั้งที่ jumpTo ด้วยspeed=กม./ชม.: คำนวณเวลาไอคอนเดินทางจากระยะทางจริง/ความเร็วที่ตั้ง ให้ฟีลเร็ว-ช้าสมจริงตามพาหนะ (เครื่องบินเร็วกว่าเดินเท้า) — แก้บั๊กสำคัญ: ถ้าคำนวณตรงๆจะได้เวลาที่ไกลๆกลายเป็นหลักสิบนาทีจริงบนจอ (ทางไกล+ความเร็วต่ำ) จึงจำกัดไม่ให้เกินความยาวฉากที่มีจริงเสมอ (speed มีผลแค่ทำให้ไอคอนไปถึงเร็วกว่าความยาวฉากได้ ไม่ทำให้ฉากยาวขึ้น เพราะเสียงพากย์เป็นตัวกำหนดความยาวฉากจริงอยู่แล้ว)route=road, follow, speed เพิ่มในแผงสร้างฉากง่ายๆด้วย ไม่ต้องพิมพ์ tag เองพาท 16 — แก้ไขฉากที่สร้างไว้ก่อนหน้าได้ (เหมือนตัดต่อวิดีโอ)
sceneRawLines[i] ตรงกับ scenes[i] เป๊ะ กรองบรรทัดที่ parse ไม่ผ่านออกพร้อมกันทั้งคู่ กัน index เพี้ยน) ตอนบันทึกแก้ไขจะประกอบสคริปต์ใหม่จากบรรทัดที่ parse ได้จริงทั้งหมดพาท 17 — พรีเซ็ตสไตล์นำเสนอ 30 แบบ (รวมหมวดผี/ลึกลับใหม่), ค้นภาพประกอบอัตโนมัติ, พากย์เสียงเป็นธรรมชาติขึ้น, กล้องลื่นไหลขึ้น
style=: พรีเซ็ต 30 แบบใน 5 หมวด (สารคดี/วล็อกเดินทาง/ข่าว/มหากาพย์-สงคราม/ผี-ลึกลับ ← หมวดใหม่) แต่ละพรีเซ็ตตั้ง effect (สติ๊กเกอร์ธีม เช่น 👻🕯️🌫️⚡🏚️👑 ที่เพิ่มเข้า EFFECT_GLYPHS), highlightcolor, shade (สปอตไลต์), reveal, captionpos, และ โทนพากย์เสียง (rate/pitch ของ edge-tts) ให้อัตโนมัติ — ทับเฉพาะฟิลด์ที่ผู้ใช้ไม่ได้ตั้งเองใน tag อื่น ไม่ยุ่งกับ cam/highlight ที่เลือกเอง เลือกได้จากดรอปดาวน์ในแผง "สร้างฉากง่ายๆ" หรือพิมพ์ tag เอง/api/imagesearch (server.py) แล้วเติม geophoto=url:lat,lng:ป้ายชื่อ ให้อัตโนมัติgeophoto: เดิมรับแค่ url:lat,lng ใส่ป้ายชื่อต่อท้ายไม่ได้ (parse ล้มเหลวเงียบๆ) — ตอนนี้รับ url:lat,lng:ป้ายชื่อ ได้แบบเดียวกับ badge/callout แล้ว (ป้ายใส่หรือไม่ใส่ก็ได้)style= มีโทนพากย์ของตัวเอง (เช่นหมวดผีพูดช้าลง-เสียงต่ำลงกว่าปกติ, วล็อกพูดไวขึ้น-เสียงสูงขึ้นเล็กน้อย) (2) เพิ่มช่วงหายใจสั้นๆ (180ms) ระหว่างประโยคที่แบ่งด้วย ;; ในฉากเดียวกัน กันเสียงพากย์รัวติดกันไม่เป็นธรรมชาติeaseTo/flyTo ใช้ easing แบบ easeInOutCubic ร่วมกัน (EASE_CINEMATIC) แทนความเร็วคงที่เดิม กันกล้องกระชากตอนเริ่ม/หยุดขยับgeophoto) และป้ายวงกลม (badge) ที่เคยไปทับป้ายชื่อหมุดหลัก (pin label) เวลาอยู่พิกัดเดียวกัน — ยกมาร์กเกอร์ทั้งสองขึ้น 34px ด้วย offsetพาท 18 — Story System: narrative / beat / importance / mood + auto narrative analysis (เพิ่มความสามารถล้วนๆ ไม่กระทบของเดิม)
narrative/beat/mood เป็น auto, importance เป็น medium — สคริปต์เดิมที่ไม่มี 4 คีย์นี้ทำงานเหมือนเดิม 100% (ตรวจแล้ว: camPaceMul=1, holdBonusMs=0, styleMood=null เท่าของเดิมทุกประการ)narrative: hook/context/setup/development/escalation/conflict/reveal/turningpoint/climax/consequence/resolution/conclusion — หน้าที่ของฉากในเรื่อง เป็น metadata สำหรับ auto-analysis + แสดงในพรีวิว ไม่บังคับพฤติกรรมกล้อง/เอฟเฟกต์เองbeat: hook/question/setup/reveal/escalation/conflict/twist/turningpoint/payoff — จังหวะสำคัญของการเล่าเรื่อง; reveal/twist/turningpoint/payoff เสริมเวลาพักก่อนตัดฉากถัดไปให้อัตโนมัติ (holdBonusMs)importance: low/medium/high/critical — คุมแค่ "จังหวะ" (ความเร็วกล้องที่มีอยู่แล้ว + เวลาพักก่อนตัดฉาก) เท่านั้น ไม่เพิ่ม effect/reveal/camera-movement ใหม่ให้เอง ตามที่กำหนดไว้ — critical กล้องขยับช้าลง 30% + พักก่อนตัดฉาก +900ms, high ช้าลง 15%+400ms, low ไวขึ้น 20%, medium (ดีฟอลต์) ไม่เปลี่ยนอะไรเลยmood: calm/curious/mysterious/tension/fear/tragic/epic/hope/shock — คุมจังหวะกล้อง (คูณเพิ่มจาก importance) + โทนพากย์เสียง (rate/pitch) เท่านั้น ผู้ใช้ตั้งเองทับ style= preset's mood เสมอ, ไม่ตั้งแต่มี style= ใช้โทนของ style เดิม (ไม่เปลี่ยนพฤติกรรมเดิม), ไม่มีทั้งคู่ engine เดาจากเนื้อหาเป็นโทนแนะนำเบาๆ"กองทัพเยอรมันบุกโปแลนด์" → narrative=conflict, beat=conflict, importance=high (ทดสอบแล้วตรงตามตัวอย่างที่กำหนด) — เดาไม่ออกจากคำในสคริปต์ ลองเดาจาก cam=battle-map/effect=battle,fire,lightning ต่อ เดาไม่ออกเลยตกไปที่ดีฟอลต์ปลอดภัย ผู้ใช้ระบุเองทับค่าเดาเสมอไม่ว่าฟิลด์ไหนmood= ที่ผู้ใช้ตั้งเอง > โทนจาก style= preset (เดิม) > โทนที่ auto เดาจากเนื้อหา > ไม่มีอะไรเลย (ใช้ค่าเสียงกลางปกติ) — ทั้งหมดยังไหลผ่านฟิลด์ styleMood เดิม ไม่ได้เปลี่ยนจุดเชื่อมกับระบบพากย์เสียงเลยพาท 19 — Camera System: shot / cameraaction / focuspoint / motion / motioncurve + Geographic Camera Continuity (เพิ่มความสามารถล้วนๆ ไม่กระทบของเดิม)
cam เดิมทั้ง 8 แบบ (establishing/fly-to/push-in/zoom-out/orbit/cut-to-insert/insert-overlay/battle-map) ยังทำงานเหมือนเดิมทุกตัว — ตรวจแล้ว: สคริปต์ที่ไม่มีคีย์ใหม่พวกนี้เลยได้ shotZoomOverride=null, camActionMotionMul=1 = ไม่กระทบเลขจริงใดๆ เลย (คีย์ใหม่ resolve เป็น label ดีฟอลต์ตาม cam ไว้แค่โชว์พรีวิว)shot: establishing/wide/medium/close/detail/insert — ระยะภาพ ปรับซูมจาก baseline เดิมของ cam นั้นๆ เฉพาะตอนผู้ใช้ระบุเอง เท่านั้นcameraaction: static/gentle/cinematic/dynamic/urgent/tracking/reveal/drift — ลักษณะการเคลื่อนกล้อง คูณเข้ากับความเร็วกล้อง (0.65×-1.4×)motion: static/slow/normal/fast/accelerate/decelerate — ความเร็วกล้อง คูณรวมกับ cameraaction; accelerate/decelerate เซ็ต motioncurve โดยนัยให้ (ease-in/ease-out) ถ้ายังไม่ได้ตั้งเองmotioncurve: linear/ease-in/ease-out/ease-in-out — รูปแบบเร่ง/ชะลอ แทน easing เริ่มต้นของระบบ (EASE_CINEMATIC) เฉพาะตอนตั้งเองfocuspoint: focuspoint=lat,lng — แยกจุดที่กล้องเล็งออกจากพิกัดหลักของฉากได้ (latlng ยังเป็นพิกัดของหมุด/ไฮไลต์/มาร์กเกอร์อื่นทุกจุดเหมือนเดิม ไม่กระทบ)tilt=/bearing= เอง และพิกัดอยู่ใกล้ฉากก่อนหน้า (< 60กม.) กล้องจะสืบทอดมุมเอียง/ทิศจากฉากก่อนแทนการรีเซ็ตกลับ 0 — ทดสอบแล้ว: ฉากใกล้กันสืบทอด pitch/bearing ถูกต้อง, ฉากไกลกัน (คนละทวีป) รีเซ็ตกลับ 0 ตามพฤติกรรมเดิม; ตั้ง tilt=/bearing= เองเมื่อไหร่ก็ทับค่าสืบทอดเสมอ (ครอบคลุมเฉพาะ path หลักใน moveCamera(), ไม่รวม path ไฮไลต์เขตแดนที่ showBoundary() คุมกล้องเอง)พาท 20 — Scene Connection System: continuity / visualbridge / transition / returnmap (เพิ่มความสามารถล้วนๆ ไม่กระทบของเดิม)
continuity="auto" (heuristic ระยะทางเดิม), visualBridge=false, transition=null→"none", returnMap=false = พฤติกรรมเดิม 100%continuity: on/off — ทับ heuristic ระยะทางเดิมเสมอ (on สืบทอด position/zoom/tilt/bearing ไม่ว่าไกลแค่ไหน, off รีเซ็ตทุกครั้งแม้ใกล้ฉากก่อนหน้า) — ทดสอบแล้ว: off กับฉากใกล้กันรีเซ็ตกลับ 0/0 จริง, on กับฉากไกลกันคนละทวีปสืบทอด pitch/bearing/ซูมถูกต้อง (เพิ่มการสืบทอดซูมจากพาท 19 ที่ยังไม่รวม)visualbridge: on — ฉากที่ไม่ใช่ fly-to ถ้ากระโดดไกลจากฉากก่อนหน้ามาก (≥60กม. ใช้ threshold เดียวกับ continuity) จะซูมออกเห็นทั้งสองจุดสั้นๆ (500ms) ก่อนแล้วค่อยเข้ากล้องจริงของฉากนั้นต่อ (setTimeout ต่อคิว กัน easeTo สองอันชนกันกลางอากาศ) แทนตัดตรงแบบ "รีเซ็ตแผนที่เริ่มใหม่" ตามตัวอย่างในสเปค (เบอร์ลิน→ฝรั่งเศส)transition: cut/fade/dissolve/match/travel/morph/reveal/whip/none — แฟลชสั้นๆ (CSS keyframe ผ่าน #transitionOverlay, ไม่บล็อก JS) กลบรอยตัดฉาก ไม่ระบุ = engine เดาเอง: แผนที่→แผนที่ปกติ = none (ไม่มีอะไรเลย กัน "ห้ามใช้ transition ทุกฉาก"), แผนที่↔ภาพแทรกจริง (cut-to-insert/insert-overlay) = dissolve นุ่มๆอัตโนมัติ, beat=twist/turningpoint = cut เน้นจังหวะ — importance สูงลดความหวือหวาอัตโนมัติ (ทดสอบแล้ว: dissolve ปกติ opacity 0.5, importance=critical เหลือ 0.15)returnmap: on — ติดตาม "สถานะกล้องแผนที่จริงล่าสุด" แยกจากสถานะกล้องทั่วไป (ข้าม insert scene ที่ไม่ใช่ตำแหน่งกล้องแผนที่จริง) ฉากที่กลับมาเป็นแผนที่จะสืบทอด zoom/tilt/bearing จากจุดนั้นแทนเริ่มใหม่ แม้พิกัดจริงของฉากนั้นจะอยู่ไกลคนละทวีปก็ตาม (ทดสอบแล้ว: A push-in zoom10/tilt25 → B insert (คนละทวีป) → C returnmap=on ที่ญี่ปุ่น ได้ zoom10/tilt25 กลับมาถูกต้อง)พาท 21 — Map System: mapmode (เพิ่มความสามารถล้วนๆ ไม่กระทบเครื่องมือแผนที่เดิมเลยแม้แต่ตัวเดียว)
highlight/arrows/warmorph/draw/persist/route/follow/speed/shade/hide/landfill/reveal/trace/mainland/focus/highlightcolor/badge/callout/geophoto/timeline — ไม่ถูกแตะเลยแม้แต่บรรทัดเดียว ตรวจแล้ว: ฉากที่ใช้เครื่องมือเดิมครบ 15 ตัวพร้อมกัน (arrows/warmorph/draw/badge/callout/geophoto/highlight/route/persist/shade/reveal/trace/mainland/focus/highlightcolor) resolve ค่าถูกต้องทุกตัวเหมือนเดิมเป๊ะmapmode: location/journey/movement/expansion/retreat/encirclement/invasion/territorial-change/comparison/before-after/timeline — ป้ายกำกับ "โหมดแผนที่" ของฉาก เป็น metadata ล้วนๆ (คล้าย narrative) ไม่เพิ่ม arrows/highlight/draw/warmorph/route ให้เองเด็ดขาด ตามที่ห้ามไว้ — ผู้ใช้ระบุเองเป็น priority สูงสุดเสมอwarmorph→territorial-change, arrows→movement, route=road→journey, draw→encirclement, narrative=conflict/escalation→invasion) ไม่มีสัญญาณอะไรเลย = location (ดีฟอลต์กลาง) — ทดสอบตรงตามตัวอย่างในสเปคทั้ง 3 ข้อ: "กองทัพเคลื่อนเข้าสู่กรุงปารีส" → movement, "จักรวรรดิขยายอำนาจไปทั่วทะเลเมดิเตอร์เรเนียน" → expansion, "กองทัพถูกล้อมที่เมืองสตาลินกราด" → encirclementพาท 22 — Insert System: insertmode / inserttransition / evidence + ระบบป้องกันการบัง (เพิ่มความสามารถล้วนๆ ไม่กระทบ insert เดิม)
insert= (ข้อความ/URL รูป/รูปอัปโหลด) ยังทำงานเหมือนเดิมทุกจุด ต้องมี cam=cut-to-insert/insert-overlay อยู่ก่อนถึงจะโชว์อะไรเลย — คีย์ใหม่ 3 ตัวไม่มีผลถ้าฉากไม่มี insert= ไม่บังคับให้ทุกฉากต้องมี insert ตามที่ห้ามไว้; geophoto=/callout= ไม่ถูกแตะเลย รักษาพฤติกรรมเดิม 100%insertmode: full/overlay/picture-in-picture/split/background — เลย์เอาต์กล่อง insert ไม่ระบุ = ดีฟอลต์ตาม cam เดิม (cut-to-insert→full, insert-overlay→picture-in-picture) ตรวจแล้ว: ดีฟอลต์ทั้งสอง cam ให้ CSS เหมือนเดิมเป๊ะ (กฎ CSS ใหม่แต่ละอันจงใจเล็งไปที่ element ฝั่งที่ยังซ่อนอยู่สำหรับกรณีดีฟอลต์ จึงไม่มีผลทางสายตาเลย) split/background/overlay คือเลย์เอาต์ใหม่จริง ต้องระบุเองถึงจะเห็นinserttransition: cut/fade/dissolve/match/slide/flash — มีผลเฉพาะจังหวะเข้า/ออก insert เท่านั้น (Map↔Historical Evidence) ทับดีฟอลต์ dissolve อัตโนมัติของพาท 20 แต่ยังแพ้ transition= ทั่วไปถ้าตั้งไว้ด้วย — เพิ่ม 2 แบบใหม่ในตัวเล่นเปลี่ยนฉากเดิม (slide กวาดแถบขาว/ดำผ่านจอ, flash แฟลชขาว) ผ่าน #transitionOverlay ตัวเดิมevidence: photo/painting/document/newspaper/portrait/artifact/archive/map — ป้าย label บอกประเภทหลักฐาน (แสดงเป็นข้อความไทยมุมกล่อง insert) ห้ามสร้าง/เติม URL เองเด็ดขาด ตามที่กำหนด — โชว์เฉพาะตอนมี insert= จริงและระบุ evidence= เอง (ไม่มี = ไม่โชว์อะไรเลย)callout=/badge= ด้วย กล่อง overlay/PiP จะสลับไปฝั่งตรงข้ามอัตโนมัติ (insert-flip) กันชนกล่อง callout เดิม — ทดสอบแล้ว: ฉากมี callout ได้ class insert-flip ถูกต้อง (ขอบเขตที่ทำจริง: ป้องกันระดับโครงสร้าง/เลย์เอาต์ ไม่ใช่ real-time pixel-collision กับหมุด/ลูกศรที่กำลังเคลื่อนไหวบนจอ)พาท 23 — Documentary Editor Intelligence: pace/pacing/density/attention/emphasis/intensity/composition + Auto Documentary Rules (layer บนของทุกระบบเดิม)
camPaceMul/holdBonusMs/transition-peak ที่มีอยู่แล้วเท่านั้น ตรวจแล้ว: สคริปต์ไม่มี 7 คีย์นี้เลยได้ docPaceMul=1, holdBonusMs=0(ไม่บวกเพิ่ม), effectSuppressed=false = พฤติกรรมเดิม 100%pace: very-slow/slow/normal/fast/very-fast — ความไวกล้อง (1.5×-0.55×) narrative=climax เดาเป็น fast อัตโนมัติ (กฎ 11)pacing: build/accelerate/decelerate/release/pause — จังหวะรวม release/pause เสริมเวลาพักก่อนตัดฉาก — หลัง narrative=climax ฉากถัดไปได้ pacing=release อัตโนมัติถ้ายังไม่ตั้งเอง (กฎ 12, เป็น post-process หลัง parse ทั้งสคริปต์เพราะต้องรู้จักฉากก่อนหน้า) — ทดสอบแล้ว: ฉากหลัง climax ได้ docPaceMul ช้าลง+holdBonusMs=500 ถูกต้องdensity: minimal/low/medium/high — ดีฟอลต์ medium; high ลดความเร็วกล้อง (กฎ 4) + เพิ่มเวลาอ่าน + ลด effect ที่มาจาก style preset เท่านั้น ไม่แตะ effect= ที่พิมพ์เองattention: primary/secondary/background — ดีฟอลต์ primary; background ลดความเด่นซับไตเติล (opacity 0.72) + ลด effect ที่มาจาก style preset เหมือน density=highemphasis: none/subtle/medium/strong — คูณเข้ากับ transition ที่ resolve ไว้แล้วเท่านั้น ไม่สร้าง transition เองจากศูนย์ (กันเกินตามกฎ 10) ดีฟอลต์ medium (คูณ 1 ไม่เปลี่ยนอะไร ไม่ใช่ "none" เพื่อกัน dissolve อัตโนมัติของ insert หายไปเงียบๆ) beat=twist/turningpoint เดาเป็น subtle อัตโนมัติ เพดานสูงสุด strong=1.5× ตามที่ห้าม "มากเกินไป"intensity: low/medium/high — ความเข้มข้นกล้อง ดีฟอลต์ medium; climax เดาเป็น high อัตโนมัติ (กฎ 11)composition: auto/left/right/center/top/bottom — left/right ทับ heuristic ป้องกันการบัง insert อัตโนมัติของพาท 22 ได้โดยตรงeffectSource==="user" ไม่ถูกแตะเด็ดขาดไม่ว่ากรณีไหน) — ทดสอบแล้ว: ฉาก style+insert+caption+arrows+highlight (5 อย่าง) + density=high ได้ effectSuppressed=true แต่ฉากที่พิมพ์ effect=fire เองได้ effectRender="fire" เหมือนเดิมเป๊ะพาท 24 — Global Compatibility Rules: Internal Validation + Debug Mode (ตรวจสอบย้อนหลังทุกระบบ พาท 18-23)
xxxValid || guess.xxx || default ทุกจุด) — ตรวจย้อนหลังผ่านแล้วทุกคีย์ใหม่ทั้ง 30+ ตัวparseTagRow ดั้งเดิม (if (!key) continue;) — enum ผิดทุกคีย์ใหม่ตกไปดีฟอลต์ปลอดภัยเสมอ (ไม่มีคีย์ไหน throw หรือทำให้ฉากอื่น render พังไปด้วย) ตรวจแล้ว: ฉากที่มี unknownkey=xyz render ฉากได้ปกติทุกจุดvalidateScript()/validateTagLine() ตรวจ: จำนวนบรรทัด, place/cam/script ขาด, cam ไม่รู้จัก, latlng ผิดรูปแบบ, ;; ว่างเปล่าใน script, คีย์ไม่รู้จัก (info level), column-mode น้อยกว่า 4 คอลัมน์ — ทำงานทุกครั้งที่กด "แปลงเป็นฉาก" แต่ไม่แสดงอะไรเลยใน Production Mode (ดีฟอลต์) ต้องเปิด "โหมดตรวจสอบ (Debug)" เองถึงจะเห็นรายงาน — ทดสอบแล้ว: สคริปต์ 5 บรรทัดมีปัญหา 3 แบบ (ขาด place/latlng ผิด/;; ว่าง) จับได้ครบ 4 รายการ, debug ปิด reportVisible=false แต่ scene ที่ parse ผ่านยัง render 4 ฉากตามปกติ (fail-graceful ไม่กระทบ)\t คั่น ไม่มี key=value เลย) ยัง parse ได้ปกติ 100%, tag mode สคริปต์สมบูรณ์ไม่มีปัญหาแสดง "ไม่พบปัญหาในสคริปต์ล่าสุด" ถูกต้องtransport ดีฟอลต์ "none" เสมอมาตั้งแต่ก่อนพาท 18 แล้ว (ไม่ดีฟอลต์เป็นเครื่องบินกันโชว์พาหนะผิดยุคในฉากประวัติศาสตร์ก่อนมีเครื่องบิน)พาท 25 — Smooth Geographic Navigation + Full Scene Preload / Media Preparation
navigation: auto/smooth/direct (ดีฟอลต์ auto) — เปลี่ยนดีฟอลต์การขยับกล้องระหว่างฉากที่ไกลกันมากให้ "ซูมออก→เผยบริบทภูมิศาสตร์→เดินทาง→ซูมเข้า" แทนการตัดตรงแบบเดิม (เหตุผลชัดเจนตามที่กำหนด: กันภาพกระโดด/เวียนหัว) — ต่อยอด visualbridge= เดิม (พาท 20) ไม่ลบไม่เปลี่ยนชื่อ: visualbridge=on/off ที่ตั้งเองชัดเจนยัง priority สูงสุดเหมือนเดิมเป๊ะ, navigation=direct/smooth ที่ตั้งเองรองลงมา, ไม่ตั้งอะไรเลย (auto) ใช้ดีฟอลต์ใหม่ตาม travelmodetravelmode: local/regional/global — ไม่ใส่ = คำนวณจากระยะทางจริง (haversine) ระหว่างฉากก่อนหน้ากับฉากนี้: local <150กม. ไม่ต้องซูมออก, regional <1500กม. ซูมออกปานกลาง (zoom -0.5, floor 3, 500ms), global ไกลกว่านั้นซูมออกมากขึ้น (zoom -1, floor 2, 650ms) เผยบริบทกว้างขึ้นแต่ยังกันไม่ให้ซูมออกเกินจำเป็น — ทดสอบยืนยันครบ: ฉากใกล้กันไม่ bridge (เหมือนเดิม), ฉากไกลข้ามทวีป ไม่ต้องตั้ง visualbridge=on เอง ก็ bridge อัตโนมัติแล้ว (ดีฟอลต์ใหม่), navigation=direct และ visualbridge=off ยังบังคับตัดตรงได้เหมือนเดิมทุกจุด, cam=fly-to ไม่ผ่านระบบนี้เพราะมีการเดินทางโค้งในตัวอยู่แล้วPromise.all([preloadNarration, preloadImages])) แสดงข้อความสถานะ PREPARING → GENERATING VOICE → IMAGE PREPARATION → READY ระหว่างเตรียมgeophoto/callout/badge=flag:.../insert (ที่เป็นรูป) ทุกฉาก preload ด้วย Image() ผ่าน cache กันโหลดซ้ำถ้ารูปเดิมใช้หลายฉาก — สคริปต์ที่ไม่มีรูปเลย (ส่วนใหญ่) ผ่านทันที ไม่กระทบพฤติกรรมเดิม — ทดสอบแล้ว: ฉากมี geophoto แสดงสถานะ "1/1" ถูกต้อง เล่นจบไม่มี errorttsCache เดิมคีย์ด้วย เนื้อหาข้อความจริง (ไม่ใช่ index ฉาก) อยู่แล้วตั้งแต่ก่อนหน้านี้ — แก้สคริปต์แค่บางฉากจึงได้ cache hit ฉากที่ไม่เปลี่ยนโดยอัตโนมัติ ไม่ต้องแก้อะไรเพิ่ม — ทดสอบยืนยัน: เล่น 3 ฉากแล้วแก้เฉพาะฉาก 2 กด เล่นใหม่ cache โตขึ้นแค่ 1 รายการ (เฉพาะฉากที่แก้) ไม่ใช่ regenerate ทั้ง 3preloadNarration ไม่ถูกลบ/เปลี่ยนชื่อ แค่เพิ่มข้อความสถานะ, narrationLoop/fetchTts/playAudioClip ไม่ถูกแตะเลยพาท 26 — ลากไฟล์รูป/เสียงแบ่งฉากอัตโนมัติ (Drag & Drop Auto-Split)
scene2.jpg มาหลัง scene10.jpg) แล้วไฟล์ที่ 1 → ฉากที่ 1, ไฟล์ที่ 2 → ฉากที่ 2, ... ไฟล์เกินจำนวนฉากถูกข้าม (สถานะแจ้งจำนวนที่ไม่ได้ใช้), ต้องนำเข้าสคริปต์ก่อนถึงจะลากได้ — ลากไฟล์อัพโหลดผ่าน /api/upload เดิม (endpoint เดียวกับปุ่ม "เลือกไฟล์ภาพ" ที่มีอยู่แล้ว) ได้ URL ถาวรในโฟลเดอร์ assets/scene.geophoto.url ของฉากนั้น (ถ้าฉากมี geophoto= เดิมอยู่แล้วจะทับแค่ .url คง lat/lng/label เดิมไว้ ถ้ายังไม่มีจะสร้างใหม่ปักที่พิกัดของฉากเอง) — ใช้ระบบ render/preload รูปเดิมทั้งหมด (setGeoPhoto/collectSceneImageUrls) ไม่ต้องเพิ่มโค้ดแสดงผลใหม่เลยscene.overrideAudioUrl/overrideAudioDuration (ฟิลด์รันไทม์ใหม่ ไม่ใช่ tag) — ตอนเล่นจริง ฉากที่มีไฟล์เสียงอัพโหลดเองจะข้าม TTS ทั้งหมด (ทั้งใน preloadNarration และ narrationLoop) เล่นไฟล์เสียงที่ลากมาเป็นคลิปเดียวทั้งฉากแทน ซับไตเติลคาราโอเกะยังทำงานปกติ (ใช้ความยาวไฟล์เสียงจริงคำนวณจังหวะคำแทนที่จะประมาณจาก TTS) ความยาวไฟล์เสียงถูกวัดจริงมาตั้งเป็น scene.duration เหมือน TTS เดิมทุกจุด (จังหวะกล้อง/ระบบ pacing ทั้งหมดยังทำงานตามปกติ)Audio().duration มีการ์ดกัน edge case ที่เบราว์เซอร์รายงาน Infinity/NaN (ไฟล์เสียงเพี้ยน/สตรีม) ตกไปใช้ความยาวเดิมของฉากแทนเสมอ ไม่ปล่อยให้ค่าพังไปกระทบจังหวะฉากgeophoto=/TTS ปกติทุกจุดเหมือนเดิม 100%/api/tts เลย ฉากที่ 3 (ไม่มีไฟล์) ยังพากย์ TTS ตามปกติ ไม่มี error บน consoleพาท 27 — Smart Documentary Production Import (Core Loop v1)
image=01 / image=01,02 / video=01 / audio=01 — เลข sequence อ้างอิง asset ที่ลากเข้า Smart Import ไม่ใช่ url ตรงๆ (ต่างจาก geophoto= เดิมที่ต้องใส่ url เอง) เก็บใน scene.imageRefs/videoRefs/audioRefs (array, ดีฟอลต์ []) ไม่ใส่เลยสักฉาก = พฤติกรรมเดิม 100% — v1 ใช้แค่ตัวแรกของ list เป็นตัวจับคู่จริง (multi-ref เก็บไว้ในฉากแล้วแต่ยังไม่ทำ multi-asset sequence ต่อฉากตามข้อ 15 ในสเปก)asset_0001, asset_0002, ...) — ห้ามใช้ originalFilename เป็นตัวจับคู่หลักตามข้อ 6 ในสเปก (เก็บไว้แค่โชว์ผู้ใช้ดู) อัพโหลดผ่าน /api/upload เดิม (endpoint เดียวกับพาท 26) ได้ url ถาวรใน assets/ — รูปวัด width/height จริง, วิดีโอ/เสียงวัด duration จริง (มีการ์ดกัน Infinity/NaN เหมือนพาท 26)runAssetMatching, ข้อ 11 ในสเปก): ทำแค่ 2 tier ที่ deterministic 100% ไม่ต้องเดา (ตัด metadata/semantic/date matching ที่ต้องใช้ AI วิเคราะห์เนื้อหาไฟล์จริงออก เพราะ Zero Additional Cost Mode ห้ามเรียกบริการภายนอกเสียเงินและไม่มี local vision model ในโปรเจกต์): 1) Explicit Reference — image=/video=/audio= ตรงกับเลข sequence ของ asset ที่มีจริง ได้ confidence 100% (HIGH) ถ้าระบุเลขแต่หา asset ไม่เจอ = ขึ้นเป็น "ขาดไฟล์" ทันที (ไม่ตกไป fallback เงียบๆ ตามข้อ 21+39 ห้าม silent failure) 2) Sequence Matching — ฉากที่ไม่ได้ระบุเลขเลย ได้ asset ที่เหลือ (ยังไม่ถูกจับคู่) ไปเรียงตามลำดับไฟล์ที่อัพโหลด confidence 75% (MEDIUM) — asset หมดพูลเมื่อไหร่ฉากที่เหลือไม่ได้ asset เพิ่มเลย (กันไม่ให้ยัด video/image ทุกฉากจนกลายเป็น slideshow ตามข้อ 14 "Map-First" โดยไม่ต้องมี logic เลือกเองเลย เพราะพูล asset จริงมักน้อยกว่าจำนวนฉากอยู่แล้ว)พาท 28 — จัดหน้านำเข้าสคริปต์ใหม่เป็นแท็บ (UX cleanup)
<details> ซ้อนกัน 6 ชั้นในหน้าเดียว หาของยาก) — เปลี่ยนเป็นแถบแท็บด้านบน (สร้างฉากง่ายๆ / เขียนสคริปต์ / Smart Import / Excel / โปรเจกต์ / ตรวจสอบ / ตั้งค่า) กดปุ่มไหนแสดงเนื้อหาแท็บนั้น ซ่อนแท็บอื่น แทนการพับ/กางแบบเดิมbuilderDetails/smartImportDetails/ปุ่ม/ช่องกรอกทุกตัว), ปุ่ม "แก้ไข" บนการ์ดฉาก สลับไปแท็บ "สร้างฉากง่ายๆ" ให้อัตโนมัติเหมือนพฤติกรรมเดิมที่เคยกาง <details> ให้เองพาท 29 — MAP-FIRST Documentary Pattern (คู่มือ + ตัวอย่างโหลดได้ ไม่ใช่ระบบใหม่)
mapmode/insertmode/returnmap/density/Anti-Overediting) — ไม่ต้องสร้างระบบ AI ตัดสินใจ visual ใหม่ (ซึ่งจะต้องพึ่ง AI ภายนอกอยู่ดี) แค่ต้องเขียนสคริปต์ตามแพทเทิร์นที่ถูกต้อง จึงเพิ่มแค่คู่มือ + ปุ่มโหลดตัวอย่างสำเร็จรูป แทนการสร้างเอนจินใหม่cam=establishing/fly-to/push-in/orbit) = "MAP ONLY" ดีฟอลต์อยู่แล้ว → ฉากที่ต้องบอกแนวรบใช้ cam=battle-map + arrows= เฉพาะฉากนั้น → ต้องการภาพเล็กมุมจอไม่บังแผนที่ใช้ cam=insert-overlay + insertmode=picture-in-picture (ต่างจาก cut-to-insert ที่ตั้งใจให้เต็มจอ) → ต้องการตัดเต็มจอชั่วครู่แล้วกลับแผนที่ (EXPLAIN→SHOW→RETURN / MAP→MEDIA→MAP) แทรกฉาก cut-to-insert สั้นๆ 1 ฉากคั่นกลาง แล้วฉากแผนที่ที่ตามมาใส่ returnmap=on (กล้องสืบทอดตำแหน่งจากฉากแผนที่ก่อนหน้า ไม่กระโดดเริ่มใหม่ — ระบบเดิมตั้งแต่พาท 20) → ใส่ density=low/minimal ลดความเร็วกล้อง+ลด effect ตกแต่งจาก style preset อัตโนมัติ (เส้น/ลูกศร/insert ที่พิมพ์เองยังโชว์เสมอ เอ็นจินไม่มีสิทธิ์ลบสิ่งที่สั่งเองตรงๆ ตาม priority เดิม)insertMode:"picture-in-picture" ที่ฉาก insert, returnMap:true ที่ฉากสุดท้าย, density:"low" ตามที่ตั้ง) timeline chip แสดงป้ายกำกับถูกต้องครบ ไม่มี error บน consoleพาท 30 — เทสสคริปต์จริง 4 นาที (สงครามแปซิฟิก) + แก้จุดที่เจอ
arrows= เขียนผิดฟอร์แมตแล้วหายเงียบๆ: ตัวเองพิมพ์ arrows=ป้าย:สี:lat,lng;lat,lng (ใช้ ; คั่นจุดเริ่ม/จุดปลาย) ทั้งที่ฟอร์แมตจริงต้องใช้ : คั่น (; มีไว้คั่นระหว่างลูกศรหลายเส้น) — ลูกศรหายไปทั้งเส้นโดยไม่มี error ให้เห็นแม้เปิด Debug Mode เพราะ validateTagLine เดิมไม่เคยเช็ก arrows= เลย เพิ่มการตรวจฟอร์แมต arrows= ใน Debug Mode แล้ว (จำนวนจุด/สี/พิกัดผิดขึ้นเตือนบรรทัดที่ผิดชัดเจน)narrative=rising-action (ไม่มีในระบบ ค่าจริงคือ escalation/conflict/ฯลฯ) และ mood=hopeful (ค่าจริงคือ hope) — ทั้งคู่ตกไปใช้ auto-guess/default เงียบๆ โดยไม่มี warning เลยแม้เปิด Debug Mode ตรวจโค้ดพบว่า validateTagLine เดิม (ตั้งแต่พาท 24 ที่อ้างว่า "ตรวจสอบย้อนหลังทุกระบบ") เช็กแค่ 4 คีย์แรก (place/cam/script/latlng) ไม่เคยครอบคลุมคีย์ enum อีก 20+ ตัวที่เพิ่มมาทีหลัง (narrative/beat/mood/shot/cameraaction/motion/motioncurve/continuity/transition/mapmode/insertmode/inserttransition/evidence/pace/pacing/density/attention/emphasis/intensity/composition/navigation/travelmode) — เพิ่ม generic enum validator (ENUM_KEY_CHECKS) ตัวเดียวครอบคลุมครบทุกคีย์ enum เหล่านี้ ขึ้น warning บอกค่าที่รองรับจริงเมื่อพิมพ์ผิด ไม่กระทบ render ใดๆ (Debug Mode เท่านั้น)insertmode=picture-in-picture ไม่เล็กจริง — ขนาดเท่า overlay เป๊ะ (62-78% ของจอ): ขัดกับชื่อ "PiP" และหลักการ MAP-FIRST (พาท 29) ที่ต้องการภาพเล็กไม่บังแผนที่ ตรวจ CSS พบว่า .insertmode-picture-in-picture ใช้ selector ร่วมกับ .insertmode-overlay มาตั้งแต่แรก ไม่เคยมีขนาดแยกกันจริง — แก้ให้ picture-in-picture เล็กจริง (30% ของจอ แทน 62-78%) แยกขาดจาก overlay (คงขนาดกลาง 58-62% ไว้เหมือนเดิม) ทดสอบแล้วภาพเล็กมุมจอ เห็นแผนที่ข้างหลังชัดเจน ไม่ทับ cut-to-insert/insertmode=full ที่ยังเต็มจอปกติเหมือนเดิม (ทดสอบแยกยืนยันไม่มีการเปลี่ยนแปลง)sec= เป็นแค่ค่าประมาณเริ่มต้นก่อนพากย์เสียงจริง — เมื่อเล่นจริงแล้ว engine จะปรับ ตามความยาวเสียงพากย์จริงเสมอ (ไม่ใช่ตามตัวเลข ที่พิมพ์) ดังนั้นความยาววิดีโอรวมจริงขึ้นกับความยาวบทพากย์ที่เขียน ไม่ใช่ผลรวม ที่ตั้งไว้ — สคริปต์ทดสอบนี้ตั้ง รวม 236 วินาทีแต่เวลาจริงสั้น/ยาวกว่านั้นได้ขึ้นกับความยาวบทพากย์แต่ละฉาก (มีอยู่แล้วตั้งแต่การออกแบบเดิม ไม่ต้องแก้ไข แค่บันทึกไว้ให้เข้าใจตรงกัน)พาท 31 — ดึงภาพประกอบอัตโนมัติทุกฉาก (ขยายจากเดิมที่มีแค่ในแผงสร้างฉากทีละฉาก)
/api/imagesearch (Wikimedia Commons ฟรี) และฟังก์ชัน searchAutoImage() อยู่แล้วตั้งแต่ก่อนหน้านี้ แต่ผูกไว้แค่ใน "สร้างฉากง่ายๆ" (checkbox "ดึงภาพประกอบอัตโนมัติ" ตอนเพิ่มฉากทีละฉากเท่านั้น) — สคริปต์ที่พิมพ์/วางตรงๆ (แท็บเขียนสคริปต์) หรือมาจาก Smart Import ไม่มีทางได้ภาพอัตโนมัติเลยgeophoto=, ไม่ได้จับคู่จาก Smart Import) แล้วค้น Wikimedia Commons ตามชื่อ place= ให้อัตโนมัติ ก่อนเข้าสู่ Full Scene Preload เดิม (พาท 25)cut-to-insert/insert-overlay เสมอ (ต้องใส่ insert= เองเท่านั้น ตามกฎ "ห้ามสร้าง evidence/insert เองอัตโนมัติ" ที่ตกลงไว้ตั้งแต่พาท 21) — ดึงเฉพาะ geophoto ของฉากแผนที่ปกติเท่านั้นplace=) กันค้นซ้ำถ้าหลายฉากใช้สถานที่เดียวกัน (เช่นกลับมาที่เมืองเดิมหลายฉาก) — หาไม่เจอ = ไม่มีรูปเหมือนเดิม ไม่ error ไม่กระทบการเล่นต่อgeophoto=/Smart Import อยู่แล้ว (ข้ามไปเลยไม่ค้นซ้ำ)พาท 32 — เลือกดึงภาพอัตโนมัติ/ใส่เองได้เป็นรายฉาก
autoimage=on / autoimage=off ตั้งได้รายฉาก priority สูงสุดทับ toggle รวมเสมอ (ตรงตามหลัก explicit ผู้ใช้ > ตัวเลือกรวม > ดีฟอลต์ ที่ใช้สม่ำเสมอทั้งระบบ):
autoimage=off — ฉากนี้จะไม่ถูกดึงภาพอัตโนมัติเด็ดขาด แม้เปิด toggle รวมไว้ก็ตาม (ไว้ใส่รูปเองแน่ๆ ไม่ให้ระบบไปแทนที่)autoimage=on — ฉากนี้ถูกดึงภาพอัตโนมัติเสมอ แม้ปิด toggle รวมไว้ก็ตาม (อยากได้ภาพประกอบเฉพาะบางฉากโดยไม่ต้องเปิดทั้งสคริปต์)geophoto=/Smart Import จับคู่ไว้แล้ว หรือเป็นฉาก cut-to-insert/insert-overlay ยังถูกข้ามเสมอไม่ว่า autoimage= จะตั้งอะไรไว้ (ตรรกะเดิมพาท 31 ไม่เปลี่ยน — ป้องกันการเขียนทับรูปที่ผู้ใช้ใส่เองไว้แล้ว)autoimage=on → ฉากนั้นได้ภาพ ฉากอื่นที่ไม่ตั้งอะไรยังว่างเหมือนเดิม (2) toggle รวมเปิด + ฉากหนึ่งใส่ autoimage=off → ฉากนั้นว่างต่อ ฉากอื่นได้ภาพตามปกติ ไม่มี error บน consoleพาท 33 — ดึงภาพประกอบให้ตรงยุคเหตุการณ์ (ไม่ใช่แค่ตรงสถานที่)
year=1941 (ปี ค.ศ.) ตั้งได้รายฉาก — ใช้ระบุปีเหตุการณ์ตรงๆ ให้ค้นภาพแม่นขึ้นyear= เองก็เดาให้อัตโนมัติจากบทพากย์: หาเลขปี ค.ศ. ตรงๆ ในสคริปต์ (เช่น "1941") หรือ พ.ศ. (เช่น "พ.ศ. 2484" แปลงเป็น ค.ศ. อัตโนมัติ = 1941 - 543) — เจอปีก็เอาไปต่อท้ายชื่อสถานที่ตอนค้น Wikimedia Commons ให้เอง ไม่เจอปีเลย = ค้นแค่ชื่อสถานที่เหมือนเดิม (ไม่กระทบพฤติกรรมเดิมถ้าสคริปต์ไม่มีตัวเลขปีอยู่แล้ว)year= เองเสมอสำหรับฉากประวัติศาสตร์ (ยังไม่การันตีตรงยุค 100% เพราะสถานที่ภาษาไทยอาจหาไม่เจอเลยก็ได้ แต่แม่นขึ้นชัดเจนกว่าไม่ใส่)year= แต่สคริปต์มีคำว่า "1941" → เดาปีถูกต้อง ได้รูปเหตุการณ์จริงปี 1941 อัตโนมัติ, ฉาก "ฮิโรชิม่า" ใส่ year=1945 ตรงๆ → ค้นไม่เจอ (ชื่อไทย+ปี) fallback กลับไปได้รูปสถานที่ปัจจุบันแทน (ตัวอย่างข้อจำกัดที่พบจริง) ไม่มี error บน console ทั้งสองกรณีพาท 34 — ปรับขนาดการ์ดภาพประกอบปักพิกัดได้
photosize=120 (px) ปรับขนาดการ์ด geophoto=/รูปจาก Smart Import/วิดีโอปักพิกัด (เป็นสี่เหลี่ยมจัตุรัส) เฉพาะฉากนั้น ไม่ใส่ = ดีฟอลต์ 84px เดิมเป๊ะ ไม่กระทบสคริปต์เก่า.geo-photo-card (รูปนิ่ง) และ .geo-video-card (วิดีโอจาก Smart Import พาท 27) เพราะเป็นการ์ด "ภาพประกอบปักจุด" ชุดเดียวกัน ตั้งค่าเดียวคุมทั้งคู่photosize=180 การ์ดใหญ่ขึ้นชัดเจน ฉากที่ไม่ใส่ยังคง 84px เดิม ไม่มี error บน consoleพาท 35 — Auto Director: เดา cam อัตโนมัติ + Visual Rhythm diagnostic + Visual Makeup label
cam= เอง (rule-based ล้วนๆ ไม่มี AI ภายนอก ไม่สร้าง arrows/highlight/insert ให้เองเด็ดขาด ตามกฎ Global Compatibility Rules พาท 24 ที่ห้ามสร้างหลักฐาน/ข้อมูลเอง)cam= ไม่ใส่ก็ได้แล้ว (เดิมเป็นคีย์บังคับ 3 อย่าง place+cam+script — ไม่ใส่ cam ฉากทั้งบรรทัดถูกข้ามเงียบๆ ทั้งฉาก) ตอนนี้บังคับแค่ place+script — ไม่ใส่ cam หรือสะกดผิด ระบบเดามุมกล้องให้จากคำในบทพากย์ (inferCam): เจอคำเกี่ยวกับรบ/โจมตี/รุกราน/สงคราม/ล้อม → battle-map, เจอคำเกี่ยวกับเดินทาง/เคลื่อนทัพ/มุ่งหน้า → fly-to, เดาไม่ออก → establishing (ดีฟอลต์กลางสุดเดิม) — cam= ที่พิมพ์เองถูกต้องยังชนะเสมอ ไม่กระทบสคริปต์เก่าที่ระบุ cam ครบทุกฉากเลยแม้แต่นิดเดียว (ทดสอบยืนยัน camSource:"user" ทุกฉากเหมือนเดิม)camSource ("user"/"auto") เก็บไว้ในทุกฉากเหมือน narrative/beat/mood — โชว์ในการ์ดพรีวิวเป็น cam:xxx (auto, Director) เฉพาะตอนเดาเท่านั้น โปร่งใสว่าระบบตัดสินใจอะไรไปบ้างsceneVisualMakeup) สรุปว่าฉากนั้นมีองค์ประกอบภาพจริงอะไรบ้าง (MAP / MAP + IMAGE / MAP + VIDEO / MAP + GRAPHIC (ลูกศร) / MAP + INSERT เล็ก ฯลฯ) — เป็นแค่ label สรุปให้ดู ไม่ได้ไปเปลี่ยนการ render ใดๆ ตรงกับคำศัพท์ "Final Visual Decision" ในสเปกข้อ 31checkVisualRhythm) เตือนถ้าฉากติดกันตั้งแต่ 4 ฉากขึ้นไปใช้ cam เดิมซ้ำ หรือมีรูป/วิดีโอประกอบทุกฉากติดกัน (เสี่ยงกลายเป็น slideshow ตามสเปกข้อ 16) — เตือนเฉยๆ ไม่ได้แก้ฉากให้เอง, ไม่เตือนรันของฉาก "ไม่มีรูป" ติดกัน (นั่นคือ MAP-ONLY ที่ถูกต้องตามหลัก MAP-FIRST อยู่แล้ว ไม่ใช่ปัญหา)arrows=/highlight=/insert= จากบทพากย์เด็ดขาด เพราะต้องแต่งพิกัด/หลักฐานขึ้นเองซึ่งขัดกับกฎ "ห้ามสร้างข้อมูล/หลักฐานเอง" ที่ยึดมาตั้งแต่พาท 24 — Auto Director เดาได้แค่ "มุมกล้อง" (การตีความ ไม่ใช่ข้อมูลใหม่) ส่วนที่เหลือ (arrows/highlight/insert) ยังต้องให้ผู้ใช้ระบุพิกัด/เนื้อหาจริงเองเสมอxyz-invalid fallback ไป inferCam แทน establishing เฉยๆ, rhythm diagnostic เตือน 5 ฉากติดกัน cam เดิมถูกต้อง และ 4 ฉากติดกันมีรูปถูกต้อง (ฉากไม่มีรูปคั่นแล้วไม่เตือนย้อนหลัง), สคริปต์เก่าที่ระบุ cam ครบ (สคริปต์สงครามแปซิฟิกพาท 30) camSource เป็น "user" ทุกฉากเหมือนเดิม ไม่มี error บน consoleพาท 36 — เครื่องมือค้นข้อมูลจริงจาก Wikidata (manual, ไม่ auto-apply)
latlng=... | year=...) ไปวางในสคริปต์เอง (รูปแบบเดียวกับปุ่ม "คัดลอกไฟล์" ที่มีอยู่แล้วในแผงอัพโหลดภาพ)/api/wikidata?q=... proxy wbsearchentities+wbgetentities ของ Wikidata (ค้นภาษาไทยก่อน ถ้าไม่เจอลองอังกฤษต่อ) แคชผลลัพธ์ตาม query กันเรียกซ้ำพาท 37 — เพิ่ม 2 ใน 4 API ฟรีที่เสนอไว้ (อีก 2 ทดสอบแล้วไม่พร้อมใช้จริง)
/api/archivesearch (ค้นชื่อ/คำอธิบายคลิป mediatype:movies) + /api/archivefile (ดึงรายชื่อไฟล์ของรายการที่เลือก เลือกไฟล์ .mp4 ที่ไม่ใช่ "Preservation Master" (มักใหญ่เป็น GB) และเล็กสุดที่พอเล่นได้) — UI ใหม่ในแท็บ "สร้างฉากง่ายๆ" ค้นแล้วโชว์ชื่อ+คำอธิบายให้เช็กก่อน กด "ดึงลิงก์เล่นได้" แล้วคัดลอก tag videourl= สำเร็จรูป ทดสอบค้น "pearl harbor newsreel 1941" ได้คลิปข่าวจริงปี 1942 (National Archives) ขนาด 61.8MB เล่นได้จริง/api/audiosearch (endpoint เดิมจากพาท 26 ขยายแค่เติม filetype:audio ในคำค้น ไฟล์เสียงอยู่ namespace เดียวกับรูปอยู่แล้ว) ทดสอบค้น "air raid siren" ได้ไฟล์เสียงไซเรนจริงหลายไฟล์ คัดลอก tag audiourl= ได้ทันทีvideourl=<url> (ปักการ์ดวิดีโอที่ฉากตรงๆ เหมือน geophoto= ไม่ผ่าน asset manifest ของ Smart Import) และ audiourl=<url> (ข้าม TTS ใช้ไฟล์นี้พากย์แทนทั้งฉาก duration ที่ไม่รู้ตอน parse จะถูก probe จริงให้อัตโนมัติก่อนเข้า narrationLoop เสมอ ผ่านฟังก์ชันใหม่ probeExplicitAudioUrls)highlight= เดิมได้ ไม่ implementlibretranslate.com) เรียก /translate แล้วได้ error ทันที ("server could not understand") บ่งชี้ว่า public instance ตอนนี้ต้องใช้ API key/free-tier สมัครเองแล้ว ไม่ใช่ keyless แบบที่ตั้งใจไว้ตอนแรก ไม่ implementพาท 38 — เทสสคริปต์ยาวจริง (สงครามรัสเซีย-ยูเครน 31 ฉาก) + แก้คอขวดที่เจอ 4 จุด
examples/russia-ukraine-10min.txtcallout=/badge=/geophoto=/warmorph=/focuspoint=/draw= เขียนผิดฟอร์แมตแล้วหายเงียบๆ (ปัญหาคลาสเดียวกับ arrows= ที่แก้ไปในพาท 30 แต่คีย์อื่นยังไม่ได้ตรวจ) — เพิ่มการตรวจฟอร์แมตด้วย parser จริงของแต่ละคีย์ใน Debug Mode ทันทีที่เพิ่ม validator ตัวนี้ มันจับ draw= ที่เขียนผิดในสคริปต์เทสของตัวเองได้ทันที (ใช้ : คั่นจุดแทน ;)/api/tts ล้มเหลว ~ครึ่งหนึ่งของคำขอตอนเตรียมเสียงรัวๆ (คือ error 500 ที่เห็นมาตลอดทั้งโปรเจกต์และเคยสรุปว่าเป็น noise ไม่เกี่ยวกับโค้ด — ที่จริงคือ edge-tts ตัดการเชื่อมต่อเมื่อยิงถี่) ทำให้ฉากนั้นไม่มีเสียงในแคช แล้วไปค้างกลางคลิปตอนเล่นจริง — เพิ่ม retry 3 ครั้งพร้อมหน่วงเพิ่มขึ้นใน synthesize() ผลคือ 500 หายเกลี้ยง เหลือ 200 ล้วน.tts-cache/, คีย์ sha1 ของ voice+rate+pitch+ข้อความ) — คนทำคอนเทนต์กดเล่นซ้ำหลายรอบระหว่างแก้สคริปต์ ประโยคที่ไม่ได้แก้ไม่ต้องรอ edge-tts ใหม่ทุกครั้ง (แคชในเบราว์เซอร์เดิมหายทุกครั้งที่รีเฟรช)photosize= ได้เหมือนเดิมต้องรัน server.py (ไม่ใช่ python -m http.server เฉยๆ) เพราะต้องมี endpoint /api/tts สำหรับเสียงพากย์:
python -m pip install -r requirements.txt
python server.py 5173
แล้วเปิด http://localhost:5173 กด "นำเข้าสคริปต์" → "แปลงเป็นฉาก" (มีข้อมูลตัวอย่างในช่องอยู่แล้ว) แล้วกดเล่น
ถ้าเปิดแค่ index.html ตรงๆ หรือรันด้วย http.server ธรรมดา แผนที่/หมุด/สคริปต์ยังใช้ได้ปกติ แต่กดเล่นแล้วจะไม่มีเสียง (fetch /api/tts จะพัง)
ต้องมี server.py รันอยู่ที่ localhost:5173 ก่อน (คนละหน้าต่างเทอร์มินัล) แล้วรัน:
python -m playwright install chromium # ครั้งแรกครั้งเดียว โหลด Chromium ~150MB
python render.py my-script.txt --aspect 916 --out my-video.mp4
my-script.txt คือไฟล์ข้อความสคริปต์ซีน ฟอร์แมตเดียวกับที่วางในกล่อง "นำเข้าสคริปต์" บนเว็บ (เอาไฟล์ที่บันทึกโปรเจกต์ไว้มาแกะ script ออกมาเป็นไฟล์ .txt ก็ได้ หรือพิมพ์ตรงๆ)--aspect 916 แนวตั้ง หรือ 169 แนวนอน--voice th-TH-NiwatNeural เปลี่ยนเป็นเสียงชาย (ดีฟอลต์เสียงหญิง Premwadee), --rate +20% / --pitch -10Hz ปรับความเร็ว/โทนเสียง--gap 1.5 พักเงียบ 1.5 วินาทีระหว่างฉาก--bgm เพลง.mp3 --bgm-volume 0.25 ผสมเพลงพื้นหลัง (วนซ้ำอัตโนมัติจนจบคลิป)--subpos top --subcolor ff3333 --subsize 24 --subweight 800 ปรับสไตล์ซับไตเติลหลักให้ตรงกับที่ตั้งไว้บนเว็บ (ค่าเหล่านี้ไม่ได้ดึงจากเว็บอัตโนมัติ ต้องพิมพ์ค่าที่ต้องการเองตอนรันคำสั่ง)แบบ tag (แนะนำ — ยืดหยุ่น เรียงคีย์ไหนก่อนก็ได้ ไม่ใส่คีย์ไหนก็ default ให้):
place=สุโขทัย | latlng=17.0175,99.7016 | cam=fly-to | script=... | sec=6 | icon=castle | effect=storm | tilt=45 | bearing=200 | highlight=province | insert=...
ใส่แค่คีย์ที่จำเป็นก็พอ เช่น place=... | cam=orbit | script=... | tilt=50
แบบ column เดิม (เรียงตำแหน่งตายตัว ใช้กับไฟล์ Excel/render.py ก็ยังได้):
สถานที่ | lat,lng | แผนกล้อง | สคริปต์เสียง | วินาที | ไอคอน | เอฟเฟกต์ | insert
ใช้ทั้งสองแบบปนกันในไฟล์เดียวได้ (เช็คต่อแถว) — ระบบดูว่าแถวนั้นมี key=value ไหม ถ้ามีใช้ tag-mode ถ้าไม่มีถอยไปใช้ column-mode
city / castle / ship / battle / flag / mountain / camp (ไม่ใส่ = 📍)storm / fire / battle / nonetilt (0-60°) / bearing (0-360°) — ปรับมุมเอียง/ทิศกล้องเฉพาะฉากนั้น ใช้ได้เฉพาะ tag-mode (column-mode ไม่มีช่องให้ใส่)highlight: country / province / place / none — ไฮไลต์เขตแดนจริงจาก OpenStreetMap (Nominatim) ตรงจากพิกัดฉาก พร้อมป้ายชื่อเขต + เส้นขอบลากทีละนิดแบบปากกาวาด (border trace) ใช้ได้เฉพาะ tag-mode เท่านั้น ต้องมีอินเทอร์เน็ตและรัน server.py (ไม่ใช่ http.server เฉยๆ) เพราะเรียก /api/boundary ผ่านเซิร์ฟเวอร์landfill=flag — เติมพื้นที่ไฮไลต์เป็นลายธงชาติจริงแทนสีทึบ (ดึงจาก /api/flag ต้องมี highlight ด้วย)cam=battle-map คู่กับ arrows=ฝ่าย1:RRGGBB:lat,lng:lat,lng;ฝ่าย2:... — ลูกศรเดินทัพหลายเส้นพร้อมกัน แยกสีตามฝ่าย มุมกล้องสไตล์ RTS (เอียงคงที่ ไม่หมุน)transport: plane / car / ship / train / walk — ไอคอนพาหนะวิ่งไปตามเส้นทางตอน cam=fly-to (ดีฟอลต์ plane)shade: on หรือตัวเลข (รัศมี px) — เอฟเฟกต์สปอตไลต์ มืดรอบข้าง เหลือจุดสนใจสว่างhide: รายชื่อคั่นด้วย , จาก pin,boundary,arrow,timeline,topbar,brand — ซ่อนเฉพาะฉากนั้น เช่นอยากได้ภาพแผนที่โล่งๆ ไม่มี UIwarmorph=RRGGBB:lat1,lng1:lat2,lng2 — ต้องมี highlight ด้วย พื้นที่ฝั่งขวาของเส้น (ยืนที่จุด1 หันไปจุด2) เปลี่ยนสีเป็น RRGGBB ทีละนิดreveal=fade/wipe/split/circular/diamond — ต้องมี highlight ด้วย ลักษณะการเผยพื้นที่ (ดีฟอลต์ fade)trace=one/two/tworeverse/four — ต้องมี highlight ด้วย จำนวนจุดเริ่มลากเส้นขอบเขตพร้อมกัน (ดีฟอลต์ one)mainland=on — ต้องมี highlight ด้วย ตัดเกาะ/ดินแดนไกลออกไปทิ้งตอนคำนวณกรอบกล้องlabelfont / labelsize / labelweight — ปรับฟอนต์/ขนาด(px)/น้ำหนัก (เช่น 400,600,900) ของป้ายชื่อหมุดเฉพาะฉากนั้นcut-to-insert / insert-overlay — อัพโหลดภาพของตัวเองได้จากปุ่ม "เลือกไฟล์ภาพ" ในแผงนำเข้าสคริปต์ (เก็บไว้ที่โฟลเดอร์ assets/ ของเครื่องที่รัน server.py) กดที่ path ในลิสต์เพื่อคัดลอกมาวางในคอลัมน์นี้;; เพื่อแบ่งซับไตเติลหลายช่วงในฉากเดียว (ห้ามใช้ | เพราะชนกับตัวคั่นคอลัมน์/tag)<select id="voiceSelect"> เอง (edge-tts รองรับเสียงอื่นอีกเยอะ)render.py ยังไม่ fast-render (ใช้เวลาเท่าความยาววิดีโอจริง) — ถ้าจะเร็วกว่านี้ต้องขับกล้อง/แอนิเมชันแบบ deterministic frame-stepping แทนการเล่นสดhighlight= เรียก Nominatim สาธารณะ (ฟรีแต่จำกัด ~1 req/sec ต่อเครื่อง, cache ไว้แล้วแต่ยังไม่มี retry ถ้า Nominatim ล่ม/บล็อก) — และตั้งแต่พาท 12 ที่ไล่ยิงหลาย zoom ต่อการค้นหนึ่งครั้ง พื้นที่ที่หาระดับตรงไม่เจอง่ายๆจะยิ่งช้าขึ้น (ครั้งละหลายวินาที) แต่ผลถูกแคชไว้แล้วเรียกซ้ำจะเร็วทันทีhighlight= ของตัวเอง — place ครอบคลุมถึงระดับเขต/อำเภอ/เมืองเท่านั้น และหมู่บ้านส่วนใหญ่ใน OSM เก็บเป็นจุดไม่ใช่รูปพื้นที่ จึงไฮไลต์เป็นขอบเขตไม่ได้อยู่ดีmap.setStyle() ของ MapLibre ต้องลงทะเบียน source/layer ที่เราสร้างเองทั้งหมดใหม่ทุกครั้งที่สลับสไตล์ (เขียนโค้ดส่วน map.on("load", ...) ใหม่ให้เรียกซ้ำได้) — งานถัดไปถ้าต้องการscenes[] ผ่านฟิลด์ที่ engine เดิมรู้จักอยู่แล้วเป๊ะ — รูป→scene.geophoto (ทับแค่ .url คง lat/lng/label เดิมถ้ามี, เหมือนพาท 26), เสียง→scene.overrideAudioUrl/overrideAudioDuration (ข้าม TTS เหมือนพาท 26), วิดีโอ→scene.videoAsset ใหม่ (ดูข้อถัดไป) แล้วเรียก renderTimeline()/goToScene(0) ปลายทางเดียวกับ parse ปกติทุกจุด — เล่นได้ทันทีเหมือนนำเข้าสคริปต์แบบเดิมmarkerGeoVideo/geoVideoWrapEl คู่ขนานกับ markerGeoPhoto เดิม (offset คนละตำแหน่งไม่ทับกัน โชว์พร้อมกันได้ถ้าฉากมีทั้งรูปและวิดีโอ) เล่นวนแบบ muted อัตโนมัติเมื่อฉากมี scene.videoAsset — ฟังก์ชันเดิม (setGeoPhoto/goToScene/preload/cache) ไม่ถูกแตะเลยสักบรรทัด แค่เพิ่ม setGeoVideo(scene) เรียกคู่กันในจุดเดียวzeroCostMode — v1 นี้ไม่มีจุดไหนเรียก paid API เลยอยู่แล้ว (matching engine ทำงานจาก id/sequence/reference ล้วนๆ) ตัวแปรนี้เตรียมไว้เป็น guard สำหรับฟีเจอร์เสริมในอนาคตที่อาจต้องพึ่งบริการภายนอกimage=/video=/audio=/geophoto= เลยสักคีย์ — parse ได้ปกติ imageRefs:[] ทุกฉาก ไม่กระทบอะไรเลย, สคริปต์เก่าที่ใช้ geophoto=/พาท 26 drop-zone เดิมก็ยังทำงานคู่ขนานได้ตามปกติ (คนละกลไก ไม่ชนกัน)image=01|video=01|audio=01 ครบ, ฉาก 2 ไม่ระบุเลย, ฉาก 3 ระบุ image=99 ที่ไม่มีจริง) + bulk ingest 2 รูป/1 วิดีโอ/1 เสียง → กดวิเคราะห์ได้ผล: ฉาก 1 ทั้ง 3 ช่อง HIGH 100% (explicit), ฉาก 2 image ได้ MEDIUM 75% (sequence fallback) video/audio ว่างเพราะพูลหมด (ไม่ flood), ฉาก 3 ขึ้น "ขาดไฟล์" ถูกต้อง — override ผ่าน dropdown ทดสอบแล้ว (asset ถูกใช้ซ้ำได้), กดอนุมัติแล้ว scenes[] ได้ geophoto/videoAsset/overrideAudioUrl ตรงตามที่ review ไว้ทุกจุด, เล่นจริงยืนยัน 2 ฉากที่มีไฟล์เสียงข้าม TTS จริง ฉากที่ไม่มีไฟล์ยังพากย์ TTS ปกติ, วิดีโอ marker render ขึ้นจริงคู่กับ geophoto card ไม่ทับกัน, เล่นจบครบ 3 ฉากไม่มี error บน console (มีแค่ noise 500 เดิมที่ไม่เกี่ยวกับโค้ดนี้), ทดสอบสคริปต์เก่าไม่มี asset key เลยแยกต่างหาก ยืนยัน parse ปกติ 100%webkitdirectory), 3-panel workspace เต็มรูปแบบแยกหน้า, Engine Command Library (เอกสาร command ทั้งหมดในตัว), Visual Role tagging (EVIDENCE/CONTEXT/ฯลฯ), multi-asset sequence ต่อฉาก (map→image→video ในฉากเดียวตามลำดับเวลาย่อย), Import Session save/load แยกจากโปรเจกต์หลัก (ตอนนี้ asset manifest ถูกเก็บรวมในไฟล์โปรเจกต์ .json เดิมแทน — โหลดโปรเจกต์แล้ว asset ก็กลับมาเหมือนเดิม), full 25 test-case matrix ตามสเปก (ทดสอบแค่ smoke test ตัวแทนที่ครอบคลุม explicit/fallback/missing/override/reuse/backward-compat)scene.durationsec=sec=sec=sec= รวม 668 วินาที แต่ความยาวจริงที่ได้คือ 340 วินาที เพราะความยาวฉากจริงมาจากความยาวเสียงพากย์เสมอ (พาท 30) — วัดได้ว่าเสียงไทยพูดที่ ~12.9 ตัวอักษรต่อวินาที ดังนั้นถ้าอยากได้คลิป 10 นาทีจริงต้องเขียนบทพากย์ราว 7,700 ตัวอักษร (สคริปต์นี้มี 4,377 ตัวอักษร)|