# How Video and Audio Messages Are Handled Differently from Standard Articles in wechat-article-exporter

> Learn how wechat-article-exporter handles video and audio messages differently than standard articles. Discover its specialized parsing and media extraction for richer exports.

- Repository: [公众号文章工具箱/wechat-article-exporter](https://github.com/wechat-article/wechat-article-exporter)
- Tags: internals
- Published: 2026-05-26

---

**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`](https://github.com/wechat-article/wechat-article-exporter/blob/main/utils/index.ts), this extraction relies on regex matching followed by `eval()` to populate the global window object temporarily.

```typescript
// 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.

```typescript
// 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`](https://github.com/wechat-article/wechat-article-exporter/blob/main/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.

```typescript
// 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.

```typescript
// 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')` and `src` attribute 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 its `src` and `cover` attributes to point to locally downloaded assets tracked by UUID.
- **Embedded video** replaces `<iframe>` elements entirely with native `<video>` tags after parsing `window.__videoPageInfos` and downloading the associated transcode files.
- All media assets are stored in the `assets/` directory via [`utils/download/Exporter.ts`](https://github.com/wechat-article/wechat-article-exporter/blob/main/utils/download/Exporter.ts) and 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`](https://github.com/wechat-article/wechat-article-exporter/blob/main/utils/download/Exporter.ts) module handles this packaging, while the HTML rewriting logic updates all references to use relative paths like `./assets/{uuid}.{ext}`.