M4 Hardware Transcoding: Converting 234 WMV/RM Videos to MP4
M4 Hardware Transcoding: Converting 234 WMV/RM Videos to MP4

I had 234 legacy WMV/RM videos on a NAS: about 42.8GB and 78.5 hours in total. The goal was to create compatible MP4 copies without touching the source directory, while keeping quality, size, power use, and restart safety under control.
The final choice was h264_videotoolbox -q:v 50. On a representative 720p clip it ran roughly twice as fast as x264, produced a similar file size, and reduced the sustained CPU load and fan noise dramatically.
Inventory before conversion
Inspect the actual codecs, dimensions, and duration before choosing one command for the whole archive:
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,r_frame_rate,duration,bit_rate \
-of default=noprint_wrappers=1 sample.wmvThe archive included WMV3, RealVideo 4, several resolutions, and different audio layouts. A complete scan identifies missing audio, suspicious durations, and codec outliers before a long batch job begins.
Benchmark real content
Use a 60-second segment and compare size, speed, SSIM, and CPU load:
ffmpeg -ss 200 -t 60 -i sample.wmv \
-c:v libx264 -preset medium -crf 27 \
-c:a aac -b:a 96k -movflags +faststart \
-y /tmp/x264.mp4
ffmpeg -ss 200 -t 60 -i sample.wmv \
-c:v h264_videotoolbox -q:v 50 \
-c:a aac -b:a 96k -movflags +faststart \
-y /tmp/videotoolbox.mp4
ffmpeg -i sample.wmv -i /tmp/videotoolbox.mp4 \
-lavfi "ssim" -f null -The representative 720p result was:
| Encoder | 60-second size | Speed | SSIM | Load |
|---|---|---|---|---|
| x264 CRF 27 | 12.1MB | 7.2x | 0.939 | High CPU load |
| H.264 VideoToolbox q50 | 11.5MB | 15.6x | 0.931 | Mostly Media Engine |
HEVC VideoToolbox produced a larger file for this material and tested settings, so it was rejected. That is a workload-specific result, not a universal rule: always benchmark your own input.
VideoToolbox quality values also move in the opposite direction from x264 CRF. Higher -q:v means higher quality and larger output, while lower CRF means higher quality for x264.
What the M4 actually accelerates
The pipeline was not entirely GPU-accelerated:
WMV3 / RV40 software decode
↓
H.264 encode through VideoToolbox and the Apple Media Engine
↓
AAC encode and MP4 muxingApple has no WMV3/RV40 hardware decoder, but decoding these low-resolution files is relatively cheap. Moving the expensive encode stage to the dedicated Media Engine is what made the long job quiet.
Resumable batch script
#!/bin/bash
set -u
SRC="/Volumes/NAS/legacy-video"
DST="/Volumes/NAS/legacy-video-mp4"
LOG="/tmp/video-convert.log"
mkdir -p "$DST"
name="$1"
base="${name%.*}"
out="$DST/${base}.mp4"
tmp="$out.part"
if [ -f "$out" ]; then
echo "$(date '+%F %T') SKIP $name" >> "$LOG"
exit 0
fi
rm -f "$tmp"
if nice -n 10 ffmpeg -v error -nostdin -i "$SRC/$name" \
-map 0:v:0 -map 0:a:0? \
-c:v h264_videotoolbox -q:v 50 \
-c:a aac -b:a 96k \
-movflags +faststart -f mp4 -y "$tmp" 2>> "$LOG"; then
mv "$tmp" "$out"
echo "$(date '+%F %T') OK $name" >> "$LOG"
else
rm -f "$tmp"
echo "$(date '+%F %T') FAIL $name" >> "$LOG"
fiThe explicit -f mp4 is required because an output ending in .part does not tell ffmpeg which container to use. Writing a temporary file and renaming only after success prevents an interrupted file from looking complete.
Run two jobs in parallel and track structured status lines:
find "$SRC" -maxdepth 1 -type f \
\( -iname '*.wmv' -o -iname '*.rm' \) -print0 |
xargs -0 -n 1 -P 2 basename |
xargs -n 1 -P 2 /tmp/convert-one.sh
grep -c ' OK ' /tmp/video-convert.log
grep -c ' FAIL ' /tmp/video-convert.log
find "$DST" -name '*.part' -printFor filenames that may contain newlines, pass complete NUL-delimited paths directly instead of using the readable basename pipeline above.
Validate more than the exit code
Old WMV streams often contain damaged packets. ffmpeg may report bit-consumption, spectral-RLE, or invalid-packet warnings while successfully skipping a bad frame and completing the file.
Compare source and output durations, inspect the beginning/middle/end of samples, and probe the output:
ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1 "$DST/example.mp4"Before restarting an interrupted job, make sure old workers have exited, remove stale .part files, and rotate or clear the log so old failures do not distort the new progress count.
Takeaways
- Benchmark representative clips before launching a large batch.
- VideoToolbox uses the dedicated Apple Media Engine; it is not synonymous with general-purpose GPU compute.
- CRF and VideoToolbox quality values have opposite directions.
- A reliable batch job needs resume logic, temporary-output isolation, structured logs, and content validation.
- Source warnings are not automatically conversion failures, but every output still needs duration and playback checks.
All 234 videos were ultimately written to a new MP4 directory while the source remained read-only. For a many-hour home archive job, the hardware path was a much better operational fit than keeping all CPU cores saturated.
