From 1d4ff1282b03678f3931f29a787ebd65bfbd7b4e Mon Sep 17 00:00:00 2001 From: locphamtran Date: Sun, 23 Aug 2026 09:11:18 +0700 Subject: [PATCH] =?UTF-8?q?Gap=2013:=20BWF/INFO=20metadata=20cho=20WAV=20e?= =?UTF-8?q?xport=20=E2=80=94=20wav=5Fbwf.patch=5Fbwf=5Fmetadata=20chen=20b?= =?UTF-8?q?ext=20+=20LIST/INFO=20(INAM/IART/ISRC/ICMT)=20truoc=20data=20ch?= =?UTF-8?q?unk,=20giu=20nguyen=20PCM=20bits;=20render=5Fproject(metadata?= =?UTF-8?q?=3D)=20+=20RenderRequest.metadata=20+=20cloud=20render=20task;?= =?UTF-8?q?=20ExportModal=20Title/Artist/ISRC;=20test=5F66=20(178=20passed?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/v1/plugins.py | 3 ++ app/api/v1/projects.py | 4 +- app/core/render_engine.py | 12 ++++- app/core/wav_bwf.py | 88 ++++++++++++++++++++++++++++++++ app/static/js/app.jsx | 33 +++++++++++- app/static/js/app.precompiled.js | 7 +-- app/tasks/worker.py | 4 +- app/templates/index.html | 2 +- tests/test_daw_phase5.py | 79 ++++++++++++++++++++++++++++ 9 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 app/core/wav_bwf.py diff --git a/app/api/v1/plugins.py b/app/api/v1/plugins.py index 144734c..638f8cd 100644 --- a/app/api/v1/plugins.py +++ b/app/api/v1/plugins.py @@ -616,6 +616,8 @@ class RenderRequest(BaseModel): normalize: Optional[bool] = False normalize_target: Optional[str] = "-1dbtp" mono_check: Optional[bool] = False + # Gap 13: BWF/INFO metadata (title/artist/isrc/comment/description/...) + metadata: Optional[dict] = None class FxGuiRequest(BaseModel): @@ -1212,6 +1214,7 @@ def render_project( dither=bool(req.dither), normalize=bool(req.normalize), normalize_target=req.normalize_target or "-1dbtp", + metadata=req.metadata or None, ) # EBU R128 loudness + true peak (D1) — trả kèm response để mastering # panel hiển thị LUFS/dBTP ngay sau export mà không cần đọc lại file. diff --git a/app/api/v1/projects.py b/app/api/v1/projects.py index c5c8290..28c33c4 100644 --- a/app/api/v1/projects.py +++ b/app/api/v1/projects.py @@ -546,6 +546,7 @@ async def cleanup_all_backups(req: CleanupBackupsRequest, current_user: dict = D class RenderProjectRequest(BaseModel): sample_rate: Optional[int] = 44100 + metadata: Optional[dict] = None # Gap 13: BWF/INFO metadata (title/artist/isrc/...) @router.post("/cloud/{project_id}/render") async def render_project_endpoint(project_id: str, req: RenderProjectRequest, current_user: dict = Depends(get_current_user)): @@ -568,7 +569,8 @@ async def render_project_endpoint(project_id: str, req: RenderProjectRequest, cu project_id=project_id, project_name=row["name"], project_json_str=row["data_json"], - sample_rate=req.sample_rate or 44100 + sample_rate=req.sample_rate or 44100, + metadata=req.metadata or None ) return { diff --git a/app/core/render_engine.py b/app/core/render_engine.py index a4eb9c1..294ac53 100644 --- a/app/core/render_engine.py +++ b/app/core/render_engine.py @@ -839,7 +839,9 @@ class PythonRenderEngine: def render_project(self, project_json: dict, output_filepath: str, bit_depth: int = 32, out_sample_rate: int = 0, dither: bool = False, normalize: bool = False, - normalize_target: str = "-1dbtp"): + normalize_target: str = "-1dbtp", + metadata: dict = None): + # Gap 13: BWF/INFO metadata — tham số phụ, không đổi hành vi cũ khi None. with RENDER_LOCK: bpm = project_json["metadata"]["bpm"] time_sig_num = project_json["metadata"].get("time_signature_numerator", 4) @@ -963,4 +965,12 @@ class PythonRenderEngine: out_sr, subtype="PCM_24") else: sf.write(output_filepath, master_buffer.T, out_sr, subtype="FLOAT") + # Gap 13: BWF/INFO chunks (bext + LIST/INFO title/artist/ISRC) — + # chen sau khi ghi PCM, loi metadata khong lam hong file audio. + if metadata: + try: + from app.core.wav_bwf import patch_bwf_metadata + patch_bwf_metadata(output_filepath, metadata) + except Exception as e: + logger.warning("[RenderEngine] BWF metadata skipped: %s", e) return output_filepath diff --git a/app/core/wav_bwf.py b/app/core/wav_bwf.py new file mode 100644 index 0000000..1107133 --- /dev/null +++ b/app/core/wav_bwf.py @@ -0,0 +1,88 @@ +"""Gap 13: BWF/INFO metadata chunks cho WAV export (EBU Tech 3285 + RIFF INFO). + +sf.write chi ghi fmt/data — ham nay chen bext (602 bytes fixed + coding history) +va LIST/INFO (INAM title, IART artist, ISRC, ICMT comment) ngay truoc data chunk +roi ghi lai file. Pure stdlib (struct/datetime). +""" +import datetime +import struct + +_BEXT_FIXED = 602 + + +def _pad(text, length): + raw = text.encode("utf-8", errors="replace") + return (raw[:length] + b"\x00" * length)[:length] + + +def _chunk(fourcc, payload): + pad = b"\x00" if len(payload) % 2 else b"" + return fourcc + struct.pack(" )} +
+
+ + setExportSettings(p => ({ ...p, metaTitle: e.target.value }))} + placeholder="Tên bài" className="w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none" /> +
+
+ + setExportSettings(p => ({ ...p, metaArtist: e.target.value }))} + placeholder="Nghệ sĩ" className="w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none" /> +
+
+ + setExportSettings(p => ({ ...p, metaIsrc: e.target.value.toUpperCase() }))} + placeholder="US-ABC-26-00001" className="w-full bg-[#141414] border border-zinc-800 rounded px-1 py-0.5 text-xs text-zinc-300 focus:outline-none" /> +
+