FLExText Concatenator

Combine SIL FieldWorks interlinear .flextext exports — either as a corpus of separate texts, or as one combined text with a paragraph per source.

Windows · macOS · Linux No installation required ffmpeg bundled AGPL-3.0

Download

macOS Apple Silicon (arm64)
macOS 13 Ventura or later
Download
Windows 64-bit (x86_64)
Windows 10 / 11
Download
Linux 64-bit (amd64)
AppImage — runs on most distros
Download

Downloads are on the GitHub releases page — pick the newest release and the file for your platform.  •  All releases →

The two modes

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.

Corpus — individual texts, one file

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>.

Nothing is discarded. Time offsets, 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.

Combined Text — one text in FLEx

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.

By default this discards audio segmentation permanently — phrase time offsets, 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:

OptionWhat 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.

First Run

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.

  1. Unzip the download and drag flextext-concat.app to Applications (or anywhere you like).
  2. Double-click it. macOS will refuse to open it, or report that the app is damaged (error −47). This is Gatekeeper, not a real problem with the file.
  3. Open System Settings → Privacy & Security, scroll to the Security section, and click Open Anyway next to the message about flextext-concat.
  4. Double-click the app again and confirm Open. It will launch normally from then on.
  1. Run flextext-concat-windows-x86_64.exe.
  2. Windows SmartScreen will show “Windows protected your PC”.
  3. Click More info, then Run anyway.
The single-file build unpacks itself on each launch, so the first start takes a few seconds longer than later ones.
  1. Make the AppImage executable:
    chmod +x flextext-concat-linux-amd64.AppImage
  2. Run it:
    ./flextext-concat-linux-amd64.AppImage
If it will not start, your distro may need FUSE: sudo apt install libfuse2 on Debian/Ubuntu.

The interface

ControlWhat 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.
A file that cannot be parsed is reported and left out of the list rather than silently added, and the merge itself skips unreadable files and tells you which. One bad file among fifty never loses the batch.

Pairing texts with recordings

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.

ActionWhat 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.

When the names have nothing in common — audio titled in a different language from the text, like 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.
One recording can be suggested for several texts, because a working corpus holds many dated exports of the same text. That is fine while you choose which to include, but combining two texts that share a recording joins that audio twice. The app warns before it does.

Sorting

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.

Simple sort

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.

Advanced (regex) sort

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.

Save Settings… writes everything — sort setup and both modes' options — to a commented YAML file you can edit by hand. Loading applies each field independently, so one bad value never stops the rest of the file from loading.

Audio segmentation

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.

Discard (default)

All timing is removed. Offsets measured against separate recordings have no meaning once the texts are merged, so leaving them in would be misleading.

Shift onto one concatenated recording

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.
Without a matched recording, durations are estimated. A .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.

A note on line numbers (segnum)

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.

ChoiceWhen 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.

FAQ

Will this modify my original files?

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.

Can it read ELAN .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.

My text imports into FLEx completely blank.

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 asks “Duplicate text found” over and over.

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.

Can I combine a file that already contains several texts?

Yes. Corpus mode passes all of them through; combined mode gives each one its own paragraph.

What about morphemes and word analyses?

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.

Do I need ffmpeg, or anything else installed?

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.

Can I use the merge engine without the GUI?

Yes — flextext.py has no Qt dependency and can be imported on its own or driven from a script.