How Video and Audio Messages Are Handled Differently from Standard Articles in wechat-article-exporter
Video and audio messages trigger specialized parsing logic that extracts media URLs from JavaScript globals, downloads the binary assets to a local assets/ folder, and rewrites the DOM to embed native <video> and audio elements, whereas standard articles only require downloading static images and rewriting their src attributes.
The wechat-article/wechat-article-exporter repository processes WeChat content by categorizing posts into distinct types—standard articles, video sharing messages, and embedded media—and applying specific extraction strategies for each. While standard articles rely on simple DOM scraping, multimedia content demands execution of script fragments to locate transient URL data and subsequent DOM manipulation to ensure offline playback.
Standard Articles: Static Image Extraction
For standard articles, the exporter processes only static visual assets. The system queries all image elements using document.querySelectorAll('img') and extracts background image URLs via regular expression matching against inline style attributes.
Each discovered image is downloaded using downloadAssetWithProxy, stored under the assets/ directory within the ZIP package, and its src attribute is rewritten to point to the local relative path. The resulting HTML contains no external references and requires no additional media tags to render correctly offline.
Video Sharing Messages: Script Evaluation and Video Injection
Video sharing messages follow a distinct detection and processing pipeline. The exporter identifies these messages by searching for the #js_common_share_desc wrapper element and the .js_video_channel_container > #js_mpvedio container within the HTML structure.
To retrieve the video metadata, the parser executes JavaScript extracted from the page source. It evaluates code blocks containing window.videoContentNoEncode to obtain the description text, and window.__mpVideoTransInfo alongside window.__mpVideoCoverUrl to retrieve the highest-quality video stream URL and poster image. According to the source code in utils/index.ts, this extraction relies on regex matching followed by eval() to populate the global window object temporarily.
// utils/index.ts – video share handling
const videoContentMatchResult = html.match(
/(?<code>var\s+videoContentNoEncode\s*=\s*window\.a_value_which_never_exists\s*\|\|\s*(?<value>'[^']+'))/s
);
if (videoContentMatchResult?.groups?.value) {
const code = 'window.videoContentNoEncode = ' + videoContentMatchResult.groups.value;
eval(code);
let desc = (window as any).videoContentNoEncode;
desc = desc.replace(/\r/g, '').replace(/\n/g, '<br>');
$js_common_share_desc.innerHTML = desc;
}
After downloading both the video file and poster image to assets/, the exporter injects a native <video> element into the DOM. This element references the local assets via src and poster attributes, ensuring the content plays without network connectivity.
// utils/index.ts – video injection
const div = document.createElement('div');
div.innerHTML = `<video src="${videoURLMap.get(videoUrl)}"
poster="${videoURLMap.get(poster)}"
controls style="width:100%;height:100%"></video>`;
$js_mpvedio.appendChild(div);
Embedded Audio: Custom Element Transformation
Embedded audio content uses WeChat's proprietary <mp-common-mpaudio> custom elements. The exporter detects these tags and processes each by generating a unique UUID to track the asset pair, then extracting the cover image URL from the cover attribute and the audio stream URL constructed from voice_encode_fileid.
As implemented in utils/index.ts, the system downloads both resources using a concurrent pool, then mutates the original DOM elements by updating their cover and src attributes to reference the local files. The custom element remains in the document structure, but now functions as a standalone offline player.
// utils/index.ts – audio handling
const mpAudioEls = $jsArticleContent.querySelectorAll<HTMLElement>('mp-common-mpaudio');
if (mpAudioEls.length) {
const audioResourceDownloadFn = async (asset: AudioResource, proxy: string) => {
const audioData = await downloadAssetWithProxy<Blob>(asset.url, proxy, false, 10);
const uuid = asset.uuid;
const ext = mime.getExtension(audioData.type);
zip.file(`assets/${uuid}.${ext}`, audioData);
const targetEl = Array.from(mpAudioEls).find(el => el.getAttribute('data-uuid') === uuid)!;
if (asset.type === 'cover') {
targetEl.setAttribute('cover', `./assets/${uuid}.${ext}`);
} else {
targetEl.setAttribute('src', `./assets/${uuid}.${ext}`);
}
return audioData.size;
};
}
Embedded Video: IFrame Replacement with Native Players
Articles containing embedded video utilize <iframe class="video_iframe"> elements that reference external players. The exporter locates these iframes and correlates them with metadata stored in window.__videoPageInfos, a global array containing cover URLs and transcode information for each video.
The system downloads the cover image and the first available transcode URL (typically the highest quality) into assets/, then constructs a videoURLMap to associate remote URLs with local file paths. Each iframe is replaced with a <div> containing a standard HTML5 <video> tag pointing to the downloaded assets, eliminating external dependencies and enabling offline playback.
// utils/index.ts – embedded video handling
const videoPageInfosMatchResult = html.match(/(?<code>var videoPageInfos = \[.+?window.__videoPageInfos = videoPageInfos;)/s);
if (videoPageInfosMatchResult?.groups?.code) {
eval(videoPageInfosMatchResult.groups.code);
const videoPageInfos: VideoPageInfo[] = (window as any).__videoPageInfos;
const urls = videoPageInfos.flatMap(info => [
info.cover_url,
...(info.is_mp_video === 1 && info.mp_video_trans_info.length ? [info.mp_video_trans_info[0].url] : [])
]);
await pool.downloads<string>(urls, resourceDownloadFn);
const videoIframes = $jsArticleContent.querySelectorAll('iframe.video_iframe');
videoIframes.forEach(iframe => {
const mpvid = iframe.getAttribute('data-mpvid');
const info = videoPageInfos.find(v => v.video_id === mpvid);
if (info) {
const div = document.createElement('div');
div.innerHTML = `<video src="${videoURLMap.get(info.mp_video_trans_info[0]?.url)}"
poster="${videoURLMap.get(info.cover_url)}"
controls style="width:100%;height:100%"></video>`;
iframe.replaceWith(div);
}
});
}
Summary
- Standard articles require only image scraping via
querySelectorAll('img')andsrcattribute rewriting to achieve offline functionality. - Video sharing messages necessitate JavaScript evaluation to extract
window.__mpVideoTransInfo, downloading the highest quality video and poster before injecting a<video>element into#js_mpvedio. - Embedded audio preserves the
<mp-common-mpaudio>custom element but updates itssrcandcoverattributes to point to locally downloaded assets tracked by UUID. - Embedded video replaces
<iframe>elements entirely with native<video>tags after parsingwindow.__videoPageInfosand downloading the associated transcode files. - All media assets are stored in the
assets/directory viautils/download/Exporter.tsand referenced through relative paths in the final HTML package.
Frequently Asked Questions
Why does the exporter use eval() on the page's JavaScript?
The WeChat article interface embeds critical media URLs within script tags as global variable assignments rather than HTML attributes. The exporter uses eval() to execute these specific code fragments—such as window.__mpVideoTransInfo and window.videoContentNoEncode—within the Node.js context, temporarily populating the global window object to extract the transient URLs before downloading begins.
How does the exporter ensure offline playback of embedded media?
For audio content, the system updates the original <mp-common-mpaudio> element's src and cover attributes to reference local files in assets/. For video content, it replaces external <iframe> elements entirely with native <video> tags that specify src and poster attributes pointing to downloaded files, eliminating all network dependencies.
What is the difference between video sharing messages and embedded mp-video?
Video sharing messages are standalone posts containing a single video detected via #js_common_share_desc and processed using window.videoContentNoEncode for descriptions. Embedded mp-video elements appear as <iframe> tags within standard articles, requiring parsing of window.__videoPageInfos to correlate iframes with their metadata and transcode URLs before replacement with native players.
Where are the downloaded media files stored in the exported package?
All binary assets—including images, audio files, and video streams—are downloaded via downloadAssetWithProxy and stored in the assets/ folder within the generated ZIP archive. The utils/download/Exporter.ts module handles this packaging, while the HTML rewriting logic updates all references to use relative paths like ./assets/{uuid}.{ext}.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →