diff --git a/.gitignore b/.gitignore
index 0ee461e..8038d71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,15 +1,16 @@
+config/config.py
+backend/db/id2igid.db
+
+# ignore
__pycache__
tmp
config/session.json
config/traceback.json
-config/config.py
-backend/db/id2igid.db
-
+## testing files
test
testfiles
test.py
-
-# for sljh
+## for sljh
utils/sljh
PictureMaker/sljh.py
interface/sljh.py
diff --git a/PictureMaker/default.py b/PictureMaker/default.py
index 4fbc6da..402dc38 100644
--- a/PictureMaker/default.py
+++ b/PictureMaker/default.py
@@ -1,73 +1,77 @@
-import datetime
import os
import hashlib
import time
+from typing import List
from playwright.sync_api import sync_playwright
-from jinja2 import Template
+from jinja2 import Environment, FileSystemLoader
-from config.config import TMP
+from config.config import TMP, TZINFO
+from utils.err import easyExceptionHandler
# configuration
-TIMEZONE = 8
-IMAGE_WIDTH = 1280
-IMAGE_HEIGHT = 1280
+IMAGE_WIDTH = 1080
+IMAGE_HEIGHT = 1350
+TEMPLATE_DIR = "./PictureMaker/templates"
+TARGET_COMMENT_SYSTEM = True # set this to False if your platform not have comment system
-def render(text, filename, width=IMAGE_WIDTH, height=IMAGE_HEIGHT, font_size=60, text_color="black", margin=50) -> int:
+def render(post_context:dict) -> tuple[list[str], int]:
err = 0
browser = None
page = None
context = None
+
+ fnlist = []
try:
with sync_playwright() as p:
+ # open a browser
browser = p.chromium.launch(headless=True)
context = browser.new_context()
page = context.new_page()
- # template
- template = Template("{{ text }}")
- processed_text = template.render(text=text)
- html_content = f"""
-
-
-
-
-
- {processed_text}
-
-
-"""
- page.set_content(html_content)
+ # render template
+ env = Environment(loader=FileSystemLoader(TEMPLATE_DIR))
+ template = env.get_template('index.jinja2')
+ main = {
+ "id": post_context["id"],
+ "content": post_context["content"]["text"],
+ "time": post_context["metadata"]["create_time"].astimezone(tz=TZINFO).strftime("%Y-%m-%d %H:%M:%S")
+ }
+ ## (optional) parent article of comment
+ parent = None
+ if TARGET_COMMENT_SYSTEM:
+ from backend.utils import ld_interface
+ parent_id = post_context["metadata"]["parent"]
+ if parent_id is not None:
+ parent_context = ld_interface.inf.get(index=parent_id, media=False)
+ parent = {
+ "id": parent_context["id"],
+ "content": parent_context["content"]["text"]
+ }
+
+ rendered_html = template.render(main=main, parent=parent)
+ #print(rendered_html)
+
+ page.set_content(rendered_html)
# set window size
- page.set_viewport_size({"width": width, "height": height})
-
- # screenshot
- page.screenshot(path=filename)
+ page.set_viewport_size({"width": IMAGE_WIDTH, "height": IMAGE_HEIGHT})
+
+ # screen shot
+ time.sleep(0.1)
+ while True:
+ filename = TMP + hashlib.sha512( str(time.time()).encode() ).hexdigest() + ".jpg"
+ filename = os.path.abspath(filename)
+ page.screenshot(path=filename)
+ fnlist.append(filename)
+
+ result = page.evaluate("scrollPage()")
+ if result == 1:
+ break
except Exception as e:
# exception
- print(e)
+ easyExceptionHandler(e)
err = 1
finally:
if page:
@@ -80,10 +84,10 @@ def render(text, filename, width=IMAGE_WIDTH, height=IMAGE_HEIGHT, font_size=60,
try: browser.close()
except: pass
- return err
+ return fnlist, err
-def gen(context:dict) -> str | None:
+def gen(context:dict) -> List[str]:
"""
Generate a image that has the content of the post in it.
@@ -96,16 +100,14 @@ def gen(context:dict) -> str | None:
"""
# data preparation
- content = context["content"]["text"]
+ # content = context["content"]["text"]
# generate image
- filename = TMP + hashlib.sha512( str(time.time()).encode() ).hexdigest() + ".jpg"
- filename = os.path.abspath(filename)
- err = render(content, filename, width=IMAGE_WIDTH, height=IMAGE_HEIGHT)
+ files, err = render(context)
if err:
return None
- return filename
+ return files
def gen_caption(context:dict) -> str:
@@ -119,8 +121,9 @@ def gen_caption(context:dict) -> str:
str: caption.
"""
- caption = f"""[TCIVS Niming #{context["id"]}]
-{context["content"]["text"]}
+ caption = f"""中工匿名#{context["id"]} 🔧 {context["content"]["text"]}
+
+發布匿名貼文 > niming.tcivs.live
"""
return caption
\ No newline at end of file
diff --git a/PictureMaker/templates/index.jinja2 b/PictureMaker/templates/index.jinja2
new file mode 100644
index 0000000..a0f0562
--- /dev/null
+++ b/PictureMaker/templates/index.jinja2
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+ {% if parent is not none %}
+
+ Re: #{{ parent.id }} {{ parent.content }}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TODO b/TODO
index d1579f9..9346bc4 100644
--- a/TODO
+++ b/TODO
@@ -1,7 +1,14 @@
-[ ] 改善Traceback的收集
-[ ] GIF Support
+[ ] 處理因為ig媒體畫面比例固定,但是使用者圖片畫面比例不固定導致的問題
+ 看要不要幫使用者的媒體填充畫面到正確的比例
[ ] api: ID查IGID,IGID反查ID
[ ] api: 返回錯誤處理紀錄
+[ ] Protobuf 重新定義
+[ ] 改善Traceback的收集
+[ ] 隊列本地檔案存儲
+
[V] 本地儲存ID對IGID表
+[V] Webp Support
+[V] GIF Support
+[V] 使用者文章太長,溢出換頁機制
[ ] 測試
\ No newline at end of file
diff --git a/backend/processor.py b/backend/processor.py
index 34bd3ad..b2f2f32 100644
--- a/backend/processor.py
+++ b/backend/processor.py
@@ -11,8 +11,12 @@ from backend.utils import ld_interface
from backend.utils import ld_picturemaker
from backend.utils import fileProcessor
-def clean(file_list):
+def clean(file_list:list[str]):
for f in file_list:
+ # instagram thumbnail file
+ if f.endswith(".mp4"):
+ try: os.remove(f+".jpg")
+ except: pass
try: os.remove(f)
except: pass
@@ -39,11 +43,11 @@ def upload(aid:int) -> Tuple[str, int]:
article["content"]["media"] = []
# 合成文字圖
- proma_file = ld_picturemaker.picture_maker.gen(article)
- if proma_file is None:
+ proma_file:list = ld_picturemaker.picture_maker.gen(article)
+ if len(proma_file) == 0: # no file returned
clean(tmp_path)
return "Error while generating proma_file", 1
- tmp_path = [proma_file] + tmp_path
+ tmp_path = proma_file + tmp_path
# 送交 IG 上傳
if not DEBUG:
diff --git a/backend/utils/fileProcessor.py b/backend/utils/fileProcessor.py
index 7293508..8d36c72 100644
--- a/backend/utils/fileProcessor.py
+++ b/backend/utils/fileProcessor.py
@@ -14,6 +14,8 @@ from utils.err import easyExceptionHandler
register_heif_opener()
+# converters
+## image
def image_conventer(filename:str, binary: bytes) -> int:
try:
fio = io.BytesIO(binary)
@@ -25,7 +27,7 @@ def image_conventer(filename:str, binary: bytes) -> int:
easyExceptionHandler(e)
return 1
-
+## video (and gif)
def read_output(pipe, q):
""" 用於非阻塞讀取 ffmpeg 的 stdout """
while True:
@@ -44,12 +46,27 @@ def video_conventor(filename:str, oriFormat:str, binary:bytes) -> int:
f.write(binary)
# ffmpeg process
- process:subprocess.Popen = (
- ffmpeg
- .input(tmpfile, format=oriFormat)
- .output(filename, format='mp4')
- .run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True)
- )
+ if oriFormat == "gif": # gif process
+ process:subprocess.Popen = (
+ ffmpeg
+ .input(tmpfile, format='gif')
+ .output(
+ filename, format='mp4',
+ movflags='faststart',
+ pix_fmt='yuv420p',
+ vf='scale=trunc(iw/2)*2:trunc(ih/2)*2,fps=15',
+ crf=28,
+ preset='medium'
+ )
+ .run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True)
+ )
+ else:
+ process:subprocess.Popen = (
+ ffmpeg
+ .input(tmpfile, format=oriFormat)
+ .output(filename, format='mp4')
+ .run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True)
+ )
process.wait()
@@ -60,13 +77,13 @@ def video_conventor(filename:str, oriFormat:str, binary:bytes) -> int:
except Exception as e:
easyExceptionHandler(e)
return 1
-
+# file_writer
def file_writer(filename:str, binary:bytes):
with open(filename, "wb") as f:
f.write(binary)
-
+# main
def file_saver(ftype:str, binary:bytes) -> Tuple[str, int]:
"""
ftype -> minetype
@@ -87,13 +104,13 @@ def file_saver(ftype:str, binary:bytes) -> Tuple[str, int]:
if not ( ftype == "image/jpg" or ftype == "image/webp" or \
ftype == "video/mp4" ):
# 轉圖片
- if ftype.startswith("image"):
+ if ftype.startswith("image") and ftype != "image/gif":
opt = os.path.abspath(os.path.join(TMP, filename+".jpg"))
err = image_conventer(opt, binary)
if err: # 發生錯誤
return "File convert error", 1
# 轉影片
- elif ftype.startswith("video"):
+ elif ftype.startswith("video") or ftype == "image/gif":
opt = os.path.abspath(os.path.join(TMP, filename+".mp4"))
err = video_conventor(opt, ext, binary)
if err:
diff --git a/config/config.py.example b/config/config.py.example
index 4fe9e52..7acedfa 100644
--- a/config/config.py.example
+++ b/config/config.py.example
@@ -1,7 +1,10 @@
+import datetime
####################
# General config #
####################
TMP = "./tmp/"
+TIMEZONE = +8
+TZINFO = datetime.timezone(datetime.timedelta(hours=TIMEZONE))
####################
# Frontend config #
@@ -39,6 +42,7 @@ FILE_MINE_TYPE = {
"video/mp4": "mp4",
"video/quicktime": "mov",
"video/hevc": "hevc",
+ "image/gif": "gif" # convert gif to mp4
}
####################
diff --git a/interface/example.py b/interface/example.py
index 2da3a1c..c803fdf 100644
--- a/interface/example.py
+++ b/interface/example.py
@@ -26,32 +26,34 @@ def get(index:int, media:bool=True) -> dict | None:
# get data
## fake server in localhost
### where is the api of web application?
- res = requests.get("http://localhost:5000/article/%d?media_count=1"%index)
+ res = requests.get("http://localhost:5000/article/%d?media_count=2"%index)
if res.status_code != 200:
return None
rj = res.json()
- media = []
+ media_arr = []
# get media
if media:
for m in rj["media"]:
_m = requests.get(m)
if _m.status_code == 200:
- media.append(io.BytesIO(_m.content))
+ media_arr.append(io.BytesIO(_m.content))
# return
result = {
"id": rj["id"],
"metadata": {
- "create_time": rj["create_time"],
+ "create_time": datetime.datetime.fromtimestamp(timestamp=rj["create_time"]),
"author": "",
"tags": [],
- "category": ""
+ "category": "",
+ # ext
+ "parent": rj["parent"] # parent id
},
"content": {
"text": rj["content"],
- "media": media
+ "media": media_arr
}
}
@@ -65,7 +67,9 @@ an_example_of_context = {
"create_time": int | datetime.datetime,
"author": str,
"tags": list[str],
- "category": str
+ "category": str,
+ # ext
+ "parent": int | None # parent id
},
"content": {
"text": str,