Combine SIL FieldWorks interlinear .flextext exports —
either as a corpus of separate texts, or as one combined text with a
paragraph per source.
Downloads are on the GitHub releases page — pick the newest release and the file for your platform. • All releases →
Both modes read the same files and respect the order you arrange them in. The difference is what FLEx sees when you import the result.
Every source text stays its own text in FLEx. The output is one
<document> holding one <interlinear-text>
per source, each keeping its own title, <languages> and
<media-files>.
media-file
links, speaker names and media lists all survive exactly as they were. Each
text's media-file reference points at a <media>
entry inside its own text, so merging never breaks the link.
All the sources become a single FLEx text under a title you supply. Each
source text becomes one <paragraph>; every line from that
text becomes a <phrase> inside it. Sources that already had
several paragraphs are flattened into one.
media-file links, speaker names and the
<media-files> list. The app warns you before it does, and
only when the files you loaded actually contain any. See
Audio segmentation for the alternative, which keeps the
timing. Your source files are never modified either way.
Options in this mode:
| Option | What it does |
|---|---|
| Title + language | Required. Becomes the new text's
<item type="title">. The language list is filled
from the writing systems found in your files. |
| Source titles as notes | Adds each source text's title as a note on its paragraph's first line, so you can see where each original text begins. A paragraph cannot carry a title of its own, which is why it attaches to a phrase. |
| Line numbers (segnum) | Remove (default), renumber continuously, or keep each text's original numbers. See below. |
| Strip audio timestamp notes | Removes notes whose entire content is a timestamp like
audio ~0:00.000–0:02.421. Notes that mix real
commentary with a timestamp are left alone. |
Because the app is not signed with a platform certificate, each operating system will warn you before it runs for the first time. This is expected — the steps below take about 30 seconds and only need to be done once per downloaded copy.
flextext-concat.app to
Applications (or anywhere you like).flextext-concat-windows-x86_64.exe.chmod +x flextext-concat-linux-amd64.AppImage./flextext-concat-linux-amd64.AppImagesudo apt install libfuse2 on Debian/Ubuntu.
| Control | What it does |
|---|---|
| Load Folder… | Replaces the list with every .flextext file in a
folder (only .flextext — stray .xml
files are ignored, and ELAN .eaf is not supported).
With Include subfolders ticked it searches the
whole tree beneath it. |
| Add Files… | Adds individual files to the existing list. |
| Drag and drop | Drop files — or whole folders — onto the list from your file manager. |
| The file list | Shows each file's name, the text's title, its phrase count, and 🔊 if it carries audio segmentation. Hover for the full path. Drag rows to reorder; the list order is the order texts are written. |
| Remove Selected / Clear All | Take files back out of the list. Shift+Click and Ctrl/Cmd+Click select several at once. |
| Combine as | Corpus or Combined Text, with that mode's options below it. |
| Output | Where to write the result. Suggested for you when you load a folder. |
| Combine Files | Runs the merge on a background thread, with a progress bar and a Cancel button. Nothing is written if you cancel. |
| Ctrl+Shift+D | Shows a diagnostic panel: Python, Qt and PyYAML versions, and what is currently loaded. |
In combined mode the file list is a two-column table: the text on the left, its recording on the right. The row order is the order texts are written and audio is joined, so the two can never disagree.
| Action | What it does |
|---|---|
| Drag in the left column | Reorders texts. Each recording travels with its text. |
| Drag in the right column | Moves a recording to a different text, to fix a wrong pairing. |
| Double-click a recording | Pick a different file for that row. |
| Drop audio onto a row | Assigns it to that text. Dropping .flextext files
adds them to the list instead. |
| Match Audio | Re-suggests a recording for every row. |
| Unmatched audio pane | Every recording found that no text is using. Drag one onto a text's row to pair them. Add Audio Files… brings recordings in from anywhere on disk. |
| Audio Folder… | Where to look for recordings. By default this is each text's own folder; set it when your audio lives elsewhere. |
Matching is fuzzy, because filenames in the field rarely line up exactly.
It strips export timestamps and processing suffixes, folds case and
punctuation, and scores on shared words — so
Crocodile Woman.flextext finds
2021-06-21 Barnabas Doi Crocodile Woman.m4a.
Eti Makan Sabun.flextext
beside Eti eats soap.wav — a recording is
suggested only when it is the only one in the text's folder, and it
is marked with ? for you to confirm. When a folder holds
several recordings and none resemble the text's name, the app does not
guess: they appear in the Unmatched audio pane instead,
and you drag the right one onto the text.
The order of the list is the order the texts appear in the output, so sorting matters. Drag rows by hand, or use one of the two sort modes.
Sort by filename (alphabetical or numerical), by the text's
title read from inside the file, or by date created, modified
or accessed. Numerical order treats digits as numbers, so
text2 sorts before text10.
Stack several regex layers. Layer 1 is the primary key; each lower layer breaks ties within groups that are equal above it. Each layer has its own capture group, sort-as mode (natural text, numeric, alphabetical) and direction. Patterns are validated as you type.
Some .flextext files carry timing: each phrase has
begin-time-offset and end-time-offset in
milliseconds, a media-file pointing at a
<media> entry, and sometimes a speaker name. Corpus mode
keeps all of it untouched. Combined mode offers two choices.
All timing is removed. Offsets measured against separate recordings have no meaning once the texts are merged, so leaving them in would be misleading.
Instead of discarding the timing, each text's offsets are moved forward by the total length of the texts before it, plus a gap. The result lines up with the source audio joined end to end — so you can open the combined text against the combined audio in ELAN.
If you let the app join the recordings too, it measures each one while writing them, so the offsets are exact and the audio is guaranteed to be in the same order as the text. That is the recommended route. Turn joining off to combine the text only; the app then estimates each text's length from its last annotation, which is exact for continuously annotated texts and approximate for ELAN-style ones.
The gap defaults to 1005 ms — 500 ms silence, a 5 ms click, 500 ms silence — matching the Audio Concatenator, so files joined by either app stay interchangeable. Set it to 0 for gapless.
| A text that… | What happens |
|---|---|
| has a recording and segmentation | Its offsets are shifted onto the joined timeline. Exact. |
| has a recording but no segmentation | Its lines are spread evenly across that recording, so it is usable in ELAN — ELAN cannot show an annotation without a time slot. Approximate, and reported as such. The timeline still advances by the recording's full length, so everything after it stays aligned. |
| has no recording | It is not in the joined audio, so its lines are written without timing rather than pointing at audio that is not there, and it takes up no room in the timeline. The texts around it stay aligned. |
.flextext file does not record how long its audio is, so the
app falls back to the end of the last annotation. That is exact for a text
annotated continuously from zero, but not for ELAN-style
annotation, which marks utterances and leaves silence unannotated: audio
after the final utterance is invisible, and later texts drift early by that
much, cumulatively. Match a recording to make it exact. The app names the
texts affected.
Each source text numbers its own lines from 1, so combining several texts raises the question of what the numbering should become. It matters less than it looks.
FLEx ignores segnum when importing. Its
importer contains an explicit no-op for it, because there is no field on a
segment to store it in — the value is written on export from the
reference FLEx computes for the screen. So whatever you choose here changes
nothing about how FLEx numbers the imported text.
| Choice | When to use it |
|---|---|
| Remove (default) | Let FLEx number the text itself. Leaves no stale numbering for other tools to misread. |
| Renumber continuously | For tools other than FLEx that do read segnum. Lines
that have none are counted but not given one, so the sequence can
have gaps — you are told if that happens. |
| Keep | Leave each source text's original numbers, restarting at 1 per paragraph. |
reference-label, which FLEx does store, is never
touched by any of these.
No. Every mode reads the sources and writes a new file, and the app refuses to write its output over any loaded source — even the same file reached by a different path. If a combined text is not what you wanted, re-run in corpus mode.
.eaf files?Not yet. An .eaf is XML, but its tiers are free-form —
supporting it would need a manual tier-to-writing-system mapping step. Export
from ELAN as FLExText (.flextext) instead.
Almost always missing writing systems. A .flextext import will
not create them — the writing systems used by your texts must already
exist in the target project, and must be enabled in Interlinear Configuration
for the data to show.
FLEx matches on each text's GUID. Re-importing a corpus of texts you exported before will prompt once per text, and the dialog does not say which text it means. Tick Strip text GUIDs in corpus mode to import everything as new texts instead. Combined mode always strips them, because it is a new text by definition.
Yes. Corpus mode passes all of them through; combined mode gives each one its own paragraph.
They are copied through untouched. The app only ever removes or appends
within a phrase — it never rebuilds one — because interlinear items
appear both before and after the <words> element and
reordering them would corrupt the text.
No — the downloads bundle ffmpeg. It is used only to measure and join recordings in combined mode; corpus mode and text-only combining are pure XML. Running from source needs PySide6, plus pydub and numpy for audio and PyYAML for settings; without ffmpeg on your PATH the app still runs and estimates durations from annotations instead.
Yes — flextext.py has no Qt dependency and can be imported
on its own or driven from a script.