The M3UG filter language¶
M3UG describes which songs belong in a playlist and how to order them. This is the language reference; start with the Quickstart to make your first playlist. For tag names and units, see Tags; for running genplis, see Usage.
How M3UG files work¶
A .m3ug file defines one playlist. Favorites.m3ug produces Favorites.m3u
in the same directory when songs match. Each filter applies to the whole
collection you scan, not just the folder containing the filter.
The file contains two kinds of lines:
- Rules select songs by their tags. Ordinary rules must all match;
@ORand@NOTexpress alternatives and exclusions. - Directives control the result:
@SORT,@DEDUPE,@LIMIT, and@MAX-DURATIONorder and narrow the selection.@INCLUDEreuses another file, and@NO-PLAYLISTmarks a shared fragment.
An empty file has no rules and matches every song. See Generated playlists for output behavior.
File syntax¶
A rule has three parts, separated by spaces or tabs:
tag operator value
For example:
genre ~ "Heavy Metal"
*rating >= 4
- Use one rule or directive per line in a UTF-8 text file. A byte-order mark and either LF or CRLF line endings are accepted.
- Blank lines and leading/trailing whitespace are ignored. Values can
contain spaces, as in
artist = DANCE WITH THE DEAD. - Put comments on their own lines, starting with
#after any indentation. There are no inline comments:genre = Rock # favoritesearches for the literal valueRock # favorite. - Names beginning with
@are case-insensitive. Unknown names, malformed rules, and invalid directive arguments stop the run with a file and line number. Rule modifiers cannot be applied to directives.
Special tag names¶
Use tag names as shown by file inspection.
Names always ignore case: Artist and artist are the same.
A leading * explicitly selects a calculated value:
| Name | Meaning |
|---|---|
artist, genre, year, etc. |
Embedded metadata |
*duration, *filename, *is_lossless, etc. |
Calculated audio properties |
*rating |
Normalized 0–5 rating, currently calculated from fmps_rating |
Unprefixed audio-property names such as duration remain supported, but an
embedded tag with the same name takes precedence. Unprefixed rating uses
the raw tag when present and otherwise falls back to the calculated rating.
Use *rating when you want the normalized scale, even if a raw rating also
exists. Files with only a raw rating have no calculated rating; see
Rating.
The prefix works in rules, tag sets, sort keys, dedupe keys, and per-tag
limits. It is not a wildcard: *artist is missing because artist is not
a calculated property. See Calculated audio properties
for the available names and units.
Values and types¶
| Written as | Interpreted as |
|---|---|
Metal or Heavy Metal |
Text; bare values may contain spaces |
"Heavy Metal" or 'Heavy Metal' |
Text with the surrounding quotes removed |
1980 |
Integer |
4.5 |
Decimal number |
true or false |
Boolean for = and != |
Quote text, not numbers, when the distinction matters. track = 7
matches a numeric value or the string "7"; track = "7" requires text.
Quotes always make a string, even for "true" or a duration such as "10m".
They also preserve spaces inside the quotes.
Numbers must use digits, optionally followed by a dot and more digits.
Forms such as -1, +7, .5, 5., 1e3, and 0x10 are text, not
numbers. Numeric comparisons reject them.
Quoted strings support \", \', and \\. Other backslashes are preserved,
so regexes such as \d+ stay readable. Text operators (~, ^, $, %)
treat their value as text, including bare true, false, or digits.
Operators¶
| Operator | Meaning | Example |
|---|---|---|
= |
Equals | genre = Metal |
!= |
Does not equal | genre != Pop |
~ |
Contains | genre ~ Metal |
^ |
Starts with | title ^ Live |
$ |
Ends with | title $ Remix |
% |
Regular-expression search | *filename % \.mp3$ |
<, <= |
Less than / at most | duration < 5m |
>, >= |
Greater than / at least | year >= 1980 |
These are the only operator spellings. Equality accepts text, numbers, or booleans; text operators need string tag values; numeric comparisons accept numbers and numeric strings, but not booleans.
Text and case¶
Text matching ignores case unless the rule starts with @CASE:
@CASE artist = "DANCE WITH THE DEAD"
genre = Metal matches exactly Metal, ignoring case. genre ~ Metal
also matches Heavy Metal and Metalcore. Use is_lossless = true for a
boolean property; text operators don't match numbers or booleans.
% searches anywhere in a value rather than requiring the whole value to
match. Use regex anchors when needed: genre % ^Metal matches a prefix.
Regexes are case-insensitive unless @CASE is used; inline regex flags can
override that. Invalid patterns are reported with a file and line number.
Numeric comparisons¶
Write an unquoted number, as in year > 1980, not year > "1980".
Numeric strings in tags are converted, so track > 7 matches "8" as
well as 8. Missing or non-numeric values never pass a numeric comparison.
@CASE is not allowed on numeric comparisons.
Multi-value rules¶
Use brackets and | to list alternative tags or values:
[genre | style] = [Metal | Rock]
[artist | albumartist] = ["DANCE WITH THE DEAD" | Perturbator]
[*filename | comment] ~ [Live | Bootleg]
A positive test passes if any tag/value pair matches. != passes only
when no pair is equal: [genre | style] != [Pop | Rap] rejects a song
if either tag has either exact value. The same any/all behavior applies when
a song's tag itself has several values; see Multi-valued tags.
Numeric comparisons allow several tags but only one value:
[year | original_year] >= 1980 is valid; rating > [3 | 4] is not.
@CASE applies to the whole rule.
Set syntax has a few boundaries:
- A set must be unquoted and contain at least one
|.[Untitled]alone is literal text, not a one-item set. - Quote literal brackets or pipes when needed:
title = "[Intro | Outro]"is one string, andartist = ["AC|DC" | "A|B"]is a two-value set. - Tag names within a set are unquoted and contain no spaces. Values use the normal types above. Empty elements and nested unquoted sets are errors.
Missing values¶
An absent or unknown tag does not match positive tests such as =, ~, or
>=. It does match !=: genre != Pop includes songs without a genre.
With tag sets, missing tags contribute no values to compare.
Zero, false, and an empty string are real values: track = 0,
is_lossless = false, and comment = "" can match. Ignored or oversized
tags behave as missing; see Missing or discarded tags.
Duration literals¶
Rules using only duration or *duration accept readable durations as well
as plain seconds. @MAX-DURATION accepts the same forms.
| Form | Meaning |
|---|---|
600 |
600 seconds |
30s, 10m, 2h, 2d |
Seconds, minutes, hours, days |
2m15s, 1h30m |
Combined units |
1:30 |
90 seconds (mm:ss) |
1:30:00 |
90 minutes (h:mm:ss) |
For example, *duration >= 10m selects songs at least ten minutes long.
Units must be lowercase. In colon forms, seconds must be 0–59; minutes must
also be 0–59 in h:mm:ss, but may exceed 59 in mm:ss.
Quotes prevent conversion: duration = "10m" compares text. For other tags,
10m is ordinary text, so a numeric test such as bitrate > 10m is invalid.
Combining rules¶
Ordinary rules are joined with AND. @OR joins its rule to the previous
rule as an alternative; consecutive @OR lines extend that group:
genre = Metal
@OR genre = Rock
*rating >= 4
This means (genre = Metal OR genre = Rock) AND *rating >= 4. A new ordinary
rule starts a new AND group. @OR cannot be the first rule, and there are no
parentheses or nested OR groups.
@NOT negates only the rule on its line, not a group:
genre ~ Metal
@NOT title ~ Live
You can combine @OR, @NOT, and @CASE in any order before a rule.
Directives don't end an OR group: placing @SORT title between
genre = Metal and @OR genre = Rock would not change their grouping.
Be careful with missing values: @NOT *rating >= 4 includes songs with no
calculated rating, because their comparison fails and is then negated.
*rating < 4 excludes those songs.
Ordering¶
Use one @SORT line with comma-separated keys, highest priority first:
@SORT *rating desc, artist, album, disc, track, title
Each key optionally takes asc (the default) or desc. Later keys break
ties on earlier ones. Some useful choices:
| Directive | Order |
|---|---|
@SORT title |
Title, A–Z |
@SORT year desc |
Newest first |
@SORT artist, album, disc, track, title |
Album order |
@SORT duration |
Shortest first |
@SORT filename |
Absolute file path |
@SORT |
Directory-walk order, overriding included and configured defaults |
Without @SORT, the configured sort applies; its default
is filename. filename and *filename sort by absolute path, not the
relative path written into the playlist. Calculated names such as *rating
work as keys just as they do in rules.
Numeric-looking values sort numerically (2 before 10); other text sorts
case-insensitively. Multi-valued tags use their first non-empty value. Text
sorting is not locale-aware, and digits within text are not sorted as numbers.
Missing sort values¶
Missing or empty values sort last for that key, in either direction.
Equal or jointly missing values move the comparison to the next key; a
missing lower-priority key never overrides a higher-priority difference.
For example, with @SORT artist, album, track, a song without a track number
still stays with its album. Complete ties keep directory-walk order.
Random order¶
| Directive | Effect |
|---|---|
@SORT random |
Shuffle all matching songs |
@SORT random 42 |
Shuffle reproducibly with a fixed seed |
@SORT artist, random 7 |
Sort artists, then shuffle within each artist |
random must be the last key and cannot take asc or desc. Its optional
seed is a non-negative integer. Without a seed, the shuffle changes each run;
with preceding keys, only equal-key groups are shuffled.
Removing duplicates¶
@DEDUPE keeps the first entry after sorting for each combination of
listed tag values:
@DEDUPE artist, title
This removes repeated artist/title pairs. Choose your sort order to decide
which copy wins. Keys use the same normalization as sorting,
including the first non-empty value for multi-valued tags; calculated keys
such as *duration are allowed too.
Entries missing any key are always kept. Dedupe happens before playlist
limits, so removed duplicates don't consume a cap. Use @DEDUPE with no
keys to disable a default inherited through @INCLUDE.
Playlist limits¶
After matching, genplis sorts → removes duplicates → applies limits. Limits narrow the final playlist; they don't change which rules a song matches.
| Directive | Effect |
|---|---|
@LIMIT 100 |
Keep at most 100 songs |
@LIMIT 3 PER artist |
Keep at most three songs per artist |
@MAX-DURATION 1h30m |
Stop when the next song would exceed 90 minutes |
The three limits can be combined. Counts must be non-negative integers;
0 disables the corresponding cap. Use at most one of each form in a file,
just as @SORT and @DEDUPE each take one line.
For per-tag limits, PER ignores case and the key uses the same
normalization as sorting. A calculated key is allowed, for example
@LIMIT 3 PER *rating. Songs missing that key, or with an empty value, are
excluded while the cap is active. Songs skipped by this cap don't consume
the total count or duration budget; selection continues with the next song.
For duration limits, unknown durations count as zero. If the first selected song alone exceeds the cap, it is still included. Otherwise, selection stops rather than looking for a shorter song that might fit.
Including other files¶
@INCLUDE reuses another file's rules and default directives:
@INCLUDE Base.m3ug
*rating >= 4
The included rules and this file's rules must all match. Their OR groups
remain separate: an @OR here cannot join a rule inside Base.m3ug.
Rule modifiers cannot be applied to @INCLUDE.
Paths follow these rules:
- Relative paths start at the directory of the file containing
@INCLUDE, not the current working directory. Absolute paths and~work too. - Quote paths with spaces:
@INCLUDE "shared/Short songs.m3ug". - An existing exact path wins. Otherwise, if the path doesn't end in
.m3ug, genplis tries appending it:@INCLUDE Basecan loadBase.m3ug. Any readable filter file can be included; the extension is a convention. - Includes may nest up to 32 levels. Missing or unreadable files, directories, cycles, excessive depth, and syntax errors stop the run. Errors identify the including file, line, and path.
Directive merging¶
Included directives are defaults. The including file's own directive
always wins, regardless of line position. For example, if Base.m3ug sets
@SORT artist and @LIMIT 10, this file inherits the sort but limits the
playlist to five entries:
@LIMIT 5
@INCLUDE Base.m3ug
When including several files, later includes win per directive. Nested
includes resolve their own defaults before passing them to the parent. A
local @SORT with no keys, @DEDUPE with no keys, or a limit of 0 can
explicitly disable the corresponding inherited setting.
Files without a playlist¶
Mark a reusable fragment with @NO-PLAYLIST:
@NO-PLAYLIST
genre ~ Metal
The file is parsed for includes but doesn't match songs directly or generate
its own playlist. An existing .m3u is left untouched. This flag takes no
arguments, applies only to its own file, and is never inherited by files
that include it.
Examples¶
The example gallery contains complete filters you can copy. For a more tailored playlist, combine rules and directives:
# An hour of highly rated metal, with some artist variety
genre ~ Metal
*rating >= 4
@SORT random
@DEDUPE artist, title
@LIMIT 3 PER artist
@MAX-DURATION 1h
Both rules must match. The results are shuffled, duplicate artist/title pairs
are removed, and selection then respects the per-artist and duration caps.
For other common patterns, use year >= 1980 with year < 1990 for 1980s
releases, or *is_lossless = true for lossless files. See
Common tags and Rating before choosing
fields for your collection.
Testing and debugging filters¶
First inspect a song's tags to check its names and values. Then choose the tool for your question:
| Option | Answers | Work performed |
|---|---|---|
--show-rules |
How was the filter parsed? | Rules only; no audio or cache access |
--trace SONG |
Does this song match? | Evaluate one song; no full selection simulation |
--trace-full SONG |
Will this song reach the playlist? | Simulate ordering, dedupe, and limits across the collection when needed |
All three modes leave playlists untouched. --filter selects a specific
filter; without it, genplis discovers filters under the collection path.
Show parsed rules¶
genplis --show-rules --filter ~/Music/Favorites.m3ug
The tree shows included files inline, resolved include paths, original rule values, and effective directives. Includes appear before local rules because the tree follows evaluation order. A directive-only include has no match test, but its defaults still apply.
Trace one file¶
genplis ~/Music --trace ~/Music/song.mp3 --filter ~/Music/Favorites.m3ug
A trace might contain this excerpt:
PASS artist = "Test Artist" (line 3)
tag artist = ['Test Artist'] -> 'Test Artist'
FAIL rating >= 4 (line 4)
missing tag rating (no rating tag)
SKIP genre ~ Metal (line 5) (short-circuited: an earlier AND child failed)
PASS and FAIL show evaluated rules; SKIP means the result was already
decided, so that rule wasn't tested. Traces identify missing values,
calculated rating sources, and case-sensitive rules. They also show the
effective playlist inputs, but don't simulate the full selection.
Simulate exact selection¶
genplis ~/Music --trace-full ~/Music/song.mp3 --filter ~/Music/Favorites.m3ug
This adds the song's sorted position, any dedupe winner or cap failure, and
the final result: IN PLAYLIST with a position, or NOT IN PLAYLIST with a
reason. A song that fails the rules is reported as NO MATCH. Excluded songs,
songs outside the collection, and @NO-PLAYLIST filters aren't simulated.
Both trace modes may update the tag cache: --trace loads only the target;
--trace-full may scan the collection. Add -q to keep the requested tree
or trace without progress chatter; -qq suppresses all normal output.
For normal playlist generation, see Usage.