PrivacyStrip (and Media.StripLocation, Media.StripDeviceIds, onemedia strip) writes a copy of a media file without location and without
device identifiers. This page states exactly what is looked for, what is done to it, and what is not covered, so that a result can be checked.
Contract
- Default output is a new file.
PrivacyStrip.SaveAs(in, out, options)leaves the input alone;Savereplaces the file atomically (temporary file, then rename);SaveInPlaceoverwrites only the changed ranges and works only when nothing moves (ISO base media: always). - Media payload is never touched, with one exception, stated here so it can be checked: in an MPEG transport stream the AVCHD camera data (MDPM) sits inside the H.264 SEI messages of the video, so stripping its position entries changes bytes of the video packets (see MDPM below). Everywhere else audio and video samples are copied by range and the per-track payload hash of the output equals the input's. (Telemetry and text tracks are data/subtitle tracks, and their samples change by design.)
- ISO base media (MP4, MOV, 3GP, M4A, fragmented MP4): nothing moves. Every change keeps its length: a field is overwritten with zeros, a whole atom
or item becomes a
freebox of its own size. No chunk offset,moofoffset orsidxentry changes.EditPlan.IsSameLayoutis true. - Make and model stay. Camera make/model/software, firmware version, and orientation are not touched.
- The report is complete or says why not.
StripReport.Entrieslists each field or telemetry stream that was neutralised: category, system, name, track, the byte range in the original (for telemetry that occurs in many samples: first to last occurrence), the number of occurrences and bytes, and the action.NotRemovedlists what was found and left, with the reason.IsCompleteis true when it is empty. The CLI exits 3 when it is not. - Categories.
StripCategories.LocationandStripCategories.DeviceIds(flags); the default is both. CLI:--location,--device-ids.
Actions
| Action | Meaning |
|---|---|
ConvertedToPadding | the atom/item is now a free box of the same size, payload zeroed |
Zeroed | the value bytes were overwritten with zeros; headers and lengths stay |
Overwritten | the bytes were overwritten with a neutral value that is not zero and the field's key was changed so readers skip it: 0xFF in MDPM entries (zeros could join into a start code), spaces in text, and in DJI protobuf samples zeros plus the key changed to field number 15 |
Rewritten | an XMP packet was re-serialised without the properties and padded with spaces to its old length |
Removed | a tag writer removed the key (non-ISO formats; the tag area is rewritten) |
What is stripped, by system
ISO base media
| Where | Location | Device IDs |
|---|---|---|
udta ©xyz | whole atom | |
udta loci (3GPP TS 26.244 §8.3) and the same in an iTunes item list | whole atom | |
mdta key table items whose key name contains location, iso6709, gps, latitude, longitude, coordinates, geotag | whole item | |
mdta/ilst items whose key name contains serial, uniqueid, content.identifier, owner, deviceid, imei, udid, cameraid | whole item | |
udta GoPro atoms LENS, CAME, MUID, GUMI, BCID | whole atom | |
udta GPMF device-level KLV: CASN, MUID, LINF, CINF | value zeroed, key renamed EMPT | |
udta QVMI, MVTG, RMKN, SIGM, Exif: embedded Exif (found with ExifBlock) | GPS directory emptied (count, entries, link and every GPS value zero) | BodySerialNumber, LensSerialNumber, CameraOwnerName, ImageUniqueID, CameraSerialNumber, MakerNote: value zero, tag id changed to 0xFFFF |
XMP (uuid BE7ACFCB…, XMP_): any property or attribute whose name contains gps, latitude, longitude, location, geo, coordinates, or is the word lat, lon, lng; photoshop:City/State/Country; Iptc4xmpCore:Location/CountryCode | removed | serial, uniqueid, owner, deviceid, imei, udid, exif:ImageUniqueID removed |
gpmd track (GoPro GPMF) | GPS5, GPS9, GPSU, GPSF, GPSP, GPSA, GPRI, GLPI, LNED: value zeroed, key renamed EMPT | CASN, MUID, LINF, CINF likewise |
camm track | packet types 4 (local position), 5 (position), 6 (GPS): payload zeroed, type set to 0xFFFF | |
mebx track | atoms whose key (from the sample entry's key table) contains the location words above, including nested detected-face groups: value zeroed, local id set to 0 | atoms whose key names a serial/id |
djmd track (DJI protobuf, protocols in the table of DjmdDecoder) | the position message (latitude, longitude, units), abs_alt, rel_alt: value overwritten with zeros (a varint as 80 … 00, the same length), key changed to field 15 of the same wire type | the aircraft serial number (path 1-1-5): zeros, key changed to field 15. The model string stays |
subtitle and text tracks in ISO files (tx3g, text, wvtt, stpp, DJI per-frame text) | text rules below: overwritten with spaces | serial-style keys |
GoPro settings block (start of mdat before the first sample, and fdsc samples) | the position string by value | lens serial (48 bytes), camera-serial hash (16), camera serial (15) after the FIRM string; and every value read from a removed field, by value |
"By value": after the labelled fields are read, their values (serials ≥ 6 bytes, the media id, the position text, facts-layer serial and location readings)
are searched for in every data track that no decoder handles and in the unreferenced settings block. Each occurrence is zeroed and reported
(System = the track's sample entry or mdat-gap).
Text rules (ISO text tracks; scan only elsewhere)
Applied to the bytes of each subtitle/text sample, in order, then overlapping matches merged and blanked with spaces (the sample keeps its length):
key: valueorkey = value, the key a whole word, case-insensitive:latitude,longitude,lat,lon,lng,altitude,alt,abs_alt,rel_alt,gps_altwith a numeric value (key through value);latitudeandlongitudemay also be followed by spaces and the number alone (latitude 47.62);serial_number,serialnumber,serial number,serial,snwith a word of at least four characters (device IDs).GPS(…)orHOME(…)(spaces allowed before the bracket) whose contents are only digits,. , - + ; space m M.- ISO 6709 strings,
±DD[.d]±DDD[.d][±alt]/(also the minute and second forms with 4/6 and 5/7 digits), the closing/required. - Two decimals with at least three fraction digits, the first within ±90 and the second within ±180, separated by up to six of: space, comma,
semicolon, tab, line break, a degree sign, and a lone hemisphere letter
N S E W(47.62° N, 122.34° W). A number that is the tail of a longer number or word, or followed by a second point (1.2.345), does not count. - Degrees, minutes and optional seconds with marks (
47°37'14.8"N,47° 37' 14.8" N; UTF-8 degree sign, ASCII or prime marks), with a hemisphere letter when one follows.
WebVTT cues in ISO files (wvtt) are read past their box headers (vttc, payl, ...), which would otherwise glue to the first word of a cue. Text
that starts with a UTF-16 byte order mark (3GPP timed text may be UTF-16) is read as UTF-16 and blanked with UTF-16 spaces.
Ordinary subtitle text (times, 1.5, version numbers, the word "latitude" without a value) is left alone; the tests list the cases. A pair of
decimals that is not a position (for instance 12.345 6.789 in a caption) is blanked all the same: the rule prefers removing to missing. Standalone
subtitle files (.srt and others) are not opened by the strip.
In Matroska/WebM, ASF, AVI and the other non-ISO formats a subtitle block lies inside a structure whose checksum or size a same-length edit would have to
maintain, so a track whose text matches the rules is reported in NotRemoved (text:subtitle text, with the first block's range) and not edited.
MPEG transport streams (AVCHD camera data)
AVCHD camcorders repeat their camera data in an H.264 SEI message (type 5, UUID 17ee8c60-f84d-11d9-8cd6-0800200c9a66, marker MDPM) at the start of
each group of pictures. The strip reads the file once in 1 MiB blocks (constant memory; 188-, 192- and 204-byte packets are detected from five sync
bytes), follows each H.264 PID, looks in the first 8 KiB of every PES packet for SEI NAL units, removes emulation prevention bytes to find the entries
(tag plus four data bytes), and overwrites each position entry with 0xFF (tag byte included) in place in the transport packets. Position tags are
0xB0–0xC8 and 0xCA–0xCC (GPS version, latitude and longitude with references, altitude, time, status, mode, DOP, speed, track, map datum, date).
- The video payload changes, by necessity. Only the bytes of those entries differ (the tests and the verification count them); every other byte of the
file, including the other bytes of the same NAL units and every TS header, is copied. Decoded pictures are identical:
ffmpeg -f framemd5of the input and the output are equal (checked on real AVCHD streams). - 0xFF, not zero: zeros next to zeros could form a start code or need an escape. Zero bytes that are followed by an emulation prevention byte (
00 00 03) stay as they are so the escape remains valid; that leaves at most those zero bytes of an entry. - No MDPM serial number field is documented in the public tag descriptions, so none is stripped; maker and model (tag 0xE0 and the Sony model entries) stay. A recorder that stores such data elsewhere is not covered.
- An SEI message that runs past the first 8 KiB of its PES packet is not searched: the report lists it in
NotRemoved. MDPM in an MP4/MOV (some cameras write it there) is not looked for. - Bytes dropped or inserted in a recording move the packets after them off the packet grid; the scan then looks for the next run of sync bytes one packet apart and goes on, so the packets after the damage are searched too.
- Only H.264 video PIDs are followed; MPEG-2 video and other PIDs are not inspected.
Other formats
Matroska, ID3, APE, Vorbis comments, RIFF INFO, ASF: every native tag whose key contains one of the words above (location, gps, latitude,
longitude, serial, uniqueid, owner, …; the whole tokens lat, lon, lng, gnss, sn; RIFF IARL; ID3 TOWN) is removed through the tag
writer for that format (docs/tag-writing.md describes what it rewrites). In Matroska the removal reaches every copy: tags aimed at a track, chapter,
edition or attachment, and SimpleTags nested under another. The report lists them as Removed. A Matroska Tags element that no SeekHead entry points to
(after the last Cluster) is invisible to the tag layer; the strip finds it by hopping over the top-level elements and lists it in NotRemoved. Where the tag writer refuses a file, the tags are listed in NotRemoved. Position or serial data inside
the streams of these formats is not inspected, except subtitle text (above).
Layout of these edits (measured on the test files). The Matroska tag writer turns a removed SimpleTag into a Void element of the same size and
fixes the checksum of its parent, so a Matroska strip keeps the layout (IsSameLayout; SaveInPlace works). The ID3v2 writer keeps the tag's size when
the remaining frames fit and zeroes the rest as padding, so an MP3 strip keeps the layout too; for that the strip passes no extra padding. Other writers
(Vorbis, RIFF, APE, ASF) rewrite their tag area and may move bytes: SaveInPlace then refuses with PaddingRequired, leaving the file untouched
(on the review's test files FLAC, Ogg Opus, WAV and MP3 fitted and matched SaveAs byte for byte; a chained Ogg refused). (Frames removed from the end of an
ID3 tag used to stay in the file as stale bytes inside the padding; the writer now notices and clears them.)
Neutral values
Zero everywhere. A zero latitude/longitude/altitude is what a reader then shows for the stripped GPMF/CAMM samples where the stream is still read (CAMM
packets are renamed to an unassigned type, GPMF keys to EMPT, mebx atoms to local id 0, so ExifTool and OneMedia stop reporting the streams).
Not covered (and what the report says)
Found by the review (docs/strip-review.md) and left on purpose or for lack of a layout; none of these is reported unless said so:
Names the key rules do not know: a custom
mdtakey such ascom.example.position,unit_idorhardware.id; ID3TXXX/GEOB/PRIVframes with unlisted descriptions; RIFF INFOISRC/IENG, BWFbextoriginator fields; Matroska track names and titles.Free text: a comment, title or description that contains a coordinate or serial (ISO
©cmt/©des, XMPdc:description, VorbisCOMMENT, ID3COMM, ID3v1 comment, MatroskaTitle).Positions written as decimal comma (
47,6205 -122,3493), degrees with plain spaces (N 47 37 14.8), full-width digits, UTM/MGRS, plus codes, geo URIs and addresses in subtitle text.Placements the tag layer does not read: a
udtaoutsidemoov/trak,udtaormetainside atraf, anxmlbox inmeta, upper-case©XYZ/LOCI, a GPS IFD reached from the Exif IFD instead of IFD0, an Exif block in an unlisted atom or auuidbox (Canon CMT), bytes appended after the last box, vendor atoms (gpsNMEA, Samsung trailers), unknown data tracks (Sonyrtmd; the report names them in its notes).XMP packets that are not UTF-8: reported in
NotRemoved.A chained Ogg file: only the first logical stream's comments are read and edited; a later link's comments stay.
Text that matches none of the text rules (a place name in a subtitle, a position written in words, text in a script the rules do not know). Subtitle blocks in Matroska and the other non-ISO formats are only reported, not edited. Standalone subtitle files.
DJI's
dbgidebug track (undocumented layout): not inspected; the report lists it inNotRemoved. Fields ofdjmdmessages the decoder does not map (GPS time, home point, quaternions): not touched; a protocol name not in the decoder's table is reported inNotRemoved.The names in a
keysbox and inmebxsample entries stay (a name is not a value); values are gone.Sidecar files next to the video (DJI
.SRT, Sony clip XML, XMP): the strip works on one file.Position or serial data the strip does not know a name for. The rules are the tables in
StripRules; unknown vendor atoms stay.Free-text fields (title, comment, description) are not scanned for coordinates.
Thumbnails/cover art images with their own Exif are not opened.
Filesystem timestamps and the file name.
Container dates (
mvhd/tkhdcreation time) stay: dates are a separate unit.Matroska, ASF, RIFF, ID3: only tag fields (see above).
Verification method
tools/strip/verify-strip.py <original> <stripped> reads the secrets from the original with ExifTool (-n -ee -a -G3): every sample coordinate, every
serial/unique-id/content-id/owner value; builds their forms (UTF-8 and UTF-16 text, 4/5/6-decimal text, ISO 6709, float64 LE/BE, int32 pairs at 1e7 and
1e6 scale, hex ids); searches the stripped file's bytes; lists the tags ExifTool still shows whose names say location/serial/id with a non-neutral value;
the lines MediaInfo shows; and compares per-stream SHA-256 of ffprobe -show_data_hash for audio and video. tools/strip/run-corpus.sh runs a folder.
--ffmpeg adds the framemd5 comparison (for MDPM, where packet bytes differ by design); tools/strip/make-mdpm-real.py turns the date entries of a real camcorder recording into position-tagged entries so there is something to strip while the stream still decodes.
tools/strip/make-samples.sh writes small samples in several tag systems, with text tracks, with ffmpeg; tools/gen-strip-fixtures.py writes the committed synthetic fixture
(testdata/strip/strip-mixed.mp4), a sparse multi-GiB variant (--big) and the fuzz seeds.
Memory: planning reads the movie structure, the tag atoms and the telemetry samples (one packet at a time); media payload is copied by range. A 4.5 GiB synthetic file stripped with 43 MB peak resident memory.