M: look online to check your answer before you respond to these: is there an easy way to carve up MKV files at their chapter marks? if so, is there an easy way to introduce chapter marks into an MKV? and either way, does an MKV file need to be reencoded, or is it a literal container in that it can just pull together various-codec files into a single format?
G: I’ll verify this against current MKVToolNix documentation and distinguish chapter-based splitting, chapter creation, and codec/container behavior.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: Yes—MKVToolNix makes all three operations fairly easy, generally without re-encoding.
In MKVToolNix GUI:
M: i'm sorry, i wanted to specify: i prefer cli tools, ffmpeg being the best place to start but not necessarily necessary if you can come up with a smoother utility.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: For CLI work,
mkvmerge from MKVToolNix is smoother than FFmpeg
for chapter-based MKV operations. FFmpeg is still useful for
examining streams and doing ordinary timestamp cuts, but
mkvmerge directly understands “split at every chapter.”
mkvmerge -o 'output-%02d.mkv' \
--split chapters:all \
input.mkvThat creates:
output-01.mkv
output-02.mkv
output-03.mkv
...
You can use the chapter titles in the filenames:
mkvmerge -o 'output-%c.mkv' \
--split chapters:all \
input.mkv%c is replaced by the first chapter name in each
resulting file. Beware that chapter titles may contain awkward filename
characters.
You can also split only before selected chapters:
mkvmerge -o 'output-%02d.mkv' \
--split chapters:3,7,10 \
input.mkvThe split occurs immediately before the first suitable keyframe at or after the chapter start. Chapter 1 normally begins at zero, so it does not itself create a split. citeturn965308search0
FFmpeg’s probe utility:
ffprobe -v error \
-show_chapters \
-of json \
input.mkvOr MKVToolNix:
mkvmerge --identify input.mkvFor detailed Matroska structure:
mkvinfo input.mkvThe simplest repeatable method is to make a small chapter file and
remux it with mkvmerge.
Create chapters.txt:
CHAPTER01=00:00:00.000
CHAPTER01NAME=Opening
CHAPTER02=00:07:32.500
CHAPTER02NAME=Second section
CHAPTER03=00:18:14.000
CHAPTER03NAME=Final section
Then:
mkvmerge -o output.mkv \
--chapters chapters.txt \
input.mkvThis copies the existing audio, video, subtitle, attachment and other supported contents into a new MKV while inserting the chapter metadata. It does not decode and re-encode the media streams.
For more elaborate chapter structures, languages or nested chapters,
mkvmerge also accepts Matroska chapter XML.
FFmpeg can import chapters through an ffmetadata
file.
Create chapters.ffmeta:
;FFMETADATA1
[CHAPTER]
TIMEBASE=1/1000
START=0
END=452500
title=Opening
[CHAPTER]
TIMEBASE=1/1000
START=452500
END=1094000
title=Second section
[CHAPTER]
TIMEBASE=1/1000
START=1094000
END=1800000
title=Final section
Then:
ffmpeg \
-i input.mkv \
-i chapters.ffmeta \
-map 0 \
-map_chapters 1 \
-c copy \
output.mkvSTART and END are milliseconds here because
TIMEBASE=1/1000.
FFmpeg normally copies chapters from the first input containing them unless chapter mapping is explicitly changed. citeturn635022search10
For merely adding or replacing chapters, I would use
mkvmerge; its chapter text is easier to write because you
only need start times rather than calculating every chapter’s end
time.
No, not for any of those operations. MKV really is a container. Matroska itself is not an audio or video compression format; it contains separately encoded streams such as H.264, H.265, AV1, AAC, Opus, FLAC, subtitles, attachments and chapter metadata. citeturn635022search7turn635022search6
The operative commands are:
-c copyin FFmpeg, or simply using mkvmerge, which remuxes
rather than transcodes.
For example:
ffmpeg -i input.mkv -map 0 -c copy output.mkvThat reads the streams from the old container and writes the same compressed packets into a new container. There is no generational quality loss.
Video usually contains:
A lossless, non-reencoded cut generally has to begin at or near a
keyframe. Therefore, a chapter split may not be frame-perfect when the
chapter timestamp falls between keyframes. mkvmerge
documents that chapter splitting happens before the first keyframe whose
timestamp is equal to or later than the chapter start.
citeturn965308search0
FFmpeg has the same general constraint: accurate stream-copy segmentation depends upon suitable keyframes at the requested split locations. citeturn635022search5
So the practical division is:
For your stated preference, the clean starting toolkit is:
ffprobe # inspect streams and chapters
mkvmerge # combine streams, add chapters and split by chapters
mkvextract # extract tracks, chapters or attachments
mkvpropedit # edit many MKV properties without rebuilding the whole file[Attachment reference unresolved: file-by]
[Attachment reference unresolved: file-and]
M: okay, thinking of a new approach to a project step... help me out here with a plan of attack. assume that i will manually identify timestamps and create a chapter file 'chapter.file'
M: sorry, accidental carriage return... assume that 'chapter.file' is syntactically correct. help me plan how to first merge chapter.file with input.mkv to readytouse.mkv, then to use ffmpeg - with reencoding allowed - to split readytouse.mkv into separate files based on the chapter markings.
G: I’m checking the current command syntax for both tools, especially how FFmpeg exposes chapter timestamps for reliable per-chapter re-encoding.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: That approach is sound. The clean pipeline is:
input.mkv with your chapter
file.ffprobe to extract each chapter’s start and
end times.Assuming chapter.file uses a chapter format accepted by
MKVToolNix:
mkvmerge \
--chapters chapter.file \
-o readytouse.mkv \
input.mkvThis remuxes the source streams rather than re-encoding them.
readytouse.mkv should therefore contain the original video,
audio, subtitles, attachments, and your newly added chapter metadata.
MKVToolNix officially supports importing chapters through
--chapters. citeturn804992search0
You might prefer this ordering for readability; it is equivalent:
mkvmerge -o readytouse.mkv \
--chapters chapter.file \
input.mkvUse:
ffprobe -v error \
-show_chapters \
-of json \
readytouse.mkvFFprobe’s -show_chapters option reports each chapter as
a separate CHAPTER section, including its start time, end
time, and available title metadata.
citeturn804992search1turn804992search3
For a more human-readable check:
ffprobe -v error \
-show_entries chapter=start_time,end_time:chapter_tags=title \
-of default=noprint_wrappers=1 \
readytouse.mkvYou should see something resembling:
start_time=0.000000
end_time=315.500000
TAG:title=Opening
start_time=315.500000
end_time=702.250000
TAG:title=Second section
FFmpeg does not provide a straightforward, documented command
equivalent to MKVToolNix’s --split chapters:all. The
dependable approach is to have ffprobe produce chapter
timing data and run one FFmpeg process per chapter.
This Bash script uses jq:
#!/usr/bin/env bash
set -euo pipefail
input="readytouse.mkv"
output_dir="chapters"
mkdir -p "$output_dir"
ffprobe -v error \
-show_chapters \
-of json \
"$input" |
jq -r '
.chapters[] |
[
(.id // 0),
.start_time,
.end_time,
(.tags.title // ("Chapter " + ((.id // 0) + 1 | tostring)))
] |
@tsv
' |
while IFS=$'\t' read -r chapter_id start end title; do
number=$(printf '%02d' "$((chapter_id + 1))")
# Replace characters that are troublesome in filenames.
safe_title=$(
printf '%s' "$title" |
sed 's#[/\\:*?"<>|]#_#g; s/[[:space:]]\+$//'
)
duration=$(
awk -v start="$start" -v end="$end" \
'BEGIN { printf "%.6f", end - start }'
)
output="${output_dir}/${number} - ${safe_title}.mkv"
echo "Creating: $output"
echo "Range: $start through $end; duration $duration seconds"
ffmpeg -hide_banner \
-ss "$start" \
-i "$input" \
-t "$duration" \
-map 0 \
-map_chapters -1 \
-map_metadata 0 \
-c:v libx264 \
-preset medium \
-crf 18 \
-c:a copy \
-c:s copy \
"$output"
doneSave it as, for example:
split-chapters.sh
Then:
chmod +x split-chapters.sh
./split-chapters.shThe resulting directory should look like:
chapters/
├── 01 - Opening.mkv
├── 02 - Second section.mkv
├── 03 - Final section.mkv
└── ...
This part re-encodes the video:
-c:v libx264That means FFmpeg does not have to move the chapter boundary to a preexisting video keyframe. It can begin the new output at the requested timestamp and create a new keyframe there.
These options control H.264 quality:
-preset medium
-crf 18CRF 18 is ordinarily visually lossless or very close to
it, but it is not mathematically lossless. Smaller CRF numbers increase
quality and file size.
A smaller output could use:
-crf 20A faster conversion could use:
-preset fastThe script currently uses:
-c:a copyThat avoids unnecessary audio quality loss. Audio packet boundaries may not align perfectly with the exact timestamp, although this is generally far less noticeable than non-keyframe video cutting.
For fully re-encoded audio:
-c:a libopus -b:a 160kThus the encoding section would become:
-c:v libx264 \
-preset medium \
-crf 18 \
-c:a libopus \
-b:a 160k \
-c:s copyBecause the output remains MKV, Opus is a natural choice. AAC would also work:
-c:a aac -b:a 192k-c:s copyText or image subtitle streams are retained without transformation. FFmpeg should trim their packets to the selected interval, although subtitle events spanning a chapter boundary can behave imperfectly depending on the subtitle format.
-map_chapters -1Otherwise FFmpeg may copy the complete original chapter table into every resulting file. The individual file does not normally need a chapter marker describing itself.
-map 0This attempts to preserve all streams from the original: video, multiple audio tracks, subtitles, attachments, and other supported streams.
The script uses FFprobe’s chapter id:
number=$(printf '%02d' "$((chapter_id + 1))")Ordinary chapter IDs are usually sequential from zero, but they are not necessarily guaranteed to be. A safer version maintains its own counter:
chapter_number=0
while IFS=$'\t' read -r start end title; do
chapter_number=$((chapter_number + 1))
number=$(printf '%02d' "$chapter_number")
...
doneThat requires changing the jq output to:
jq -r '
.chapters[] |
[
.start_time,
.end_time,
(.tags.title // "Untitled chapter")
] |
@tsv
'I would use the independent counter version in a permanent script.
This is the version I would actually keep:
#!/usr/bin/env bash
set -euo pipefail
input="${1:-readytouse.mkv}"
output_dir="${2:-chapters}"
command -v ffmpeg >/dev/null || {
echo "Error: ffmpeg is not installed." >&2
exit 1
}
command -v ffprobe >/dev/null || {
echo "Error: ffprobe is not installed." >&2
exit 1
}
command -v jq >/dev/null || {
echo "Error: jq is not installed." >&2
exit 1
}
[[ -f "$input" ]] || {
echo "Error: input file not found: $input" >&2
exit 1
}
mkdir -p "$output_dir"
chapter_number=0
while IFS=$'\t' read -r start end title; do
chapter_number=$((chapter_number + 1))
number=$(printf '%02d' "$chapter_number")
safe_title=$(
printf '%s' "$title" |
sed '
s#[/\\:*?"<>|]#_#g
s/^[[:space:]]*//
s/[[:space:]]*$//
'
)
[[ -n "$safe_title" ]] || safe_title="Chapter $number"
duration=$(
awk -v start="$start" -v end="$end" '
BEGIN {
duration = end - start
if (duration <= 0) {
exit 1
}
printf "%.6f", duration
}
'
) || {
echo "Error: invalid chapter duration: $start to $end" >&2
exit 1
}
output="${output_dir}/${number} - ${safe_title}.mkv"
ffmpeg -hide_banner -y \
-ss "$start" \
-i "$input" \
-t "$duration" \
-map 0 \
-map_chapters -1 \
-map_metadata 0 \
-c:v libx264 \
-preset medium \
-crf 18 \
-c:a copy \
-c:s copy \
"$output"
done < <(
ffprobe -v error \
-show_chapters \
-of json \
"$input" |
jq -r '
.chapters[] |
[
.start_time,
.end_time,
(.tags.title // "Untitled chapter")
] |
@tsv
'
)
if (( chapter_number == 0 )); then
echo "Error: no chapters were found in $input" >&2
exit 1
fi
echo "Created $chapter_number chapter files in: $output_dir"Usage:
./split-chapters.sh readytouse.mkv chapter-outputThe entire operation becomes:
mkvmerge -o readytouse.mkv \
--chapters chapter.file \
input.mkv
./split-chapters.sh readytouse.mkv chapter-outputThat gives you exact chapter-defined video boundaries through re-encoding, while retaining the original audio and subtitle streams wherever possible.
M: okay, that makes sense. now let's try a different, gui approach for figuring out the actual chapter points. is there anything in the mkvtoolnix gui or another utility that will allow for realtime scanning and marking of video for mkv?
G: I’ll check current tools with actual video preview and chapter-marker workflows, prioritizing Linux-friendly options and MKV output.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: LosslessCut is probably exactly what you want. MKVToolNix GUI has a chapter editor, but it is essentially a metadata editor: it does not provide an integrated video player for watching in real time and dropping chapter points as scenes pass. Its documented GUI tools are multiplexing, file inspection, chapter editing, and header editing—not video-preview editing. citeturn347962view4
LosslessCut gives you:
input.mkv in LosslessCut.+ to add a marker at the current position.OpeningEpisode 1Commercial breakCreditsA marker in LosslessCut is a timestamp without an end time. It appears as a numbered vertical line on the timeline, which maps nicely to the chapter concept. citeturn347962view0
Useful controls include:
Space Play/pause
Left/Right Seek
, and . Fine/frame seeking
+ Add marker
Backspace Remove selected marker
C Capture a reference screenshot
The precise shortcuts can be viewed and reassigned through
Shift+/. citeturn347962view0
LosslessCut advertises an embedded MKV/MP4 chapter-mark editor and supports importing and exporting segments or markers as MKV chapter marks, text, CSV, CUE, XML, and YouTube chapter formats. citeturn347962view2
The likely direct workflow is:
File
→ Export project
→ MKV chapters
or an equivalent chapter-export entry depending on the installed version.
You would then either:
mkvmerge -o readytouse.mkv \
--chapters chapter.file \
input.mkvLosslessCut can work directly with embedded MKV chapter metadata.
However, for your proposed process, I would still prefer exporting the
chapter information and doing the final remux with
mkvmerge. That leaves you with:
Rather than trying to hit chapter changes perfectly while the video is running:
+ approximately when the transition happens.LosslessCut offers timeline zooming, thumbnails, waveform display, and frame/keyframe navigation, so this should be considerably easier than noting VLC timestamps manually. citeturn347962view2
It can also automatically detect black frames, silent passages, and scene changes. Those detections can become preliminary markers that you review rather than building every boundary from scratch. citeturn347962view2turn347962view3
LosslessCut’s preview relies partly on Chromium’s media support. MKV itself is supported, but playback depends on the codecs inside it. H.264, VP8, VP9, AV1, and hardware-supported H.265 are among the documented options. When the original cannot be previewed directly, LosslessCut can create a temporary FFmpeg-assisted playback file while still applying the final edits to the original source. citeturn347962view2
That means even a strange MKV should usually remain workable without your manually transcoding it first.
input.mkv
↓
Open in LosslessCut
↓
Watch, place markers, and label them
↓
Export markers as chapter.file
↓
mkvmerge --chapters chapter.file
↓
readytouse.mkv
↓
Your FFmpeg chapter-splitting script
This keeps LosslessCut confined to the job it does especially well—visually determining timestamps—while MKVToolNix and FFmpeg handle the reproducible file processing.
[Attachment reference unresolved: file-by]
M: okay, so using ubuntu linux, what's the install process?
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: On Ubuntu, I’d use the official AppImage from LosslessCut’s GitHub releases. It is usually newer than the Snap/Flatpak versions, and the developer specifically recommends trying the GitHub build when sandboxed packages cause problems. citeturn741426search2turn310335search3turn310335search7
Open the official LosslessCut releases page and download:
LosslessCut-linux-x86_64.AppImage
That is the normal choice for an Intel or AMD Ubuntu computer. The project also provides ARM builds for ARM systems. citeturn741426search2turn310335search1
Assuming it landed in ~/Downloads:
cd ~/Downloads
chmod +x LosslessCut-linux-x86_64.AppImage
./LosslessCut-linux-x86_64.AppImageYou can also launch it later by double-clicking the file. AppImages do not undergo a traditional installation; the executable file contains the application and its required bundled components. citeturn310335search4turn310335search16
On Ubuntu 24.04 or newer, install:
sudo apt update
sudo apt install libfuse2t64On Ubuntu 22.04, use:
sudo apt update
sudo apt install libfuse2Ubuntu 24.04 renamed libfuse2 to
libfuse2t64. citeturn310335search0turn310335search8
Then retry:
./LosslessCut-linux-x86_64.AppImageRather than leaving it in Downloads:
mkdir -p ~/Applications
mv ~/Downloads/LosslessCut-linux-x86_64.AppImage ~/Applications/LosslessCut.AppImage
chmod +x ~/Applications/LosslessCut.AppImageRun it with:
~/Applications/LosslessCut.AppImageYou can also place a symlink in your local executable path:
mkdir -p ~/.local/bin
ln -s ~/Applications/LosslessCut.AppImage ~/.local/bin/losslesscutAfter opening a new terminal, you should be able to start it with:
losslesscutUbuntu’s Snap Store has it:
sudo snap install losslesscutThen:
losslesscutThe Snap is easier to maintain automatically, but it can lag behind the GitHub release and may encounter sandbox-related file-access or playback issues. citeturn741426search1turn310335search3turn310335search7
For your project, I would start with the AppImage. It is the least complicated route and avoids Snap permissions becoming an extra variable.
M: i set the image to executable; this is what came out: yudasgoat@Lilypad:~/AppImage$ ./LosslessCut-linux-x86_64.AppImage [345616:0727/131746.670466:FATAL:sandbox/linux/suid/client/setuid_sandbox_host.cc:166] The SUID sandbox helper binary was found, but is not configured correctly. Rather than run without sandboxing I'm aborting now. You need to make sure that /tmp/.mount_Lossle0Q2I8A/chrome-sandbox is owned by root and has mode 4755. Trace/breakpoint trap (core dumped)
G: That error is from Electron’s Chromium sandbox, not the AppImage permissions themselves. I’m checking the current Ubuntu-safe fixes before recommending one.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: That is a known incompatibility between Electron’s Chromium sandbox and some AppImage/Ubuntu configurations. Your executable permission is correct; LosslessCut successfully mounted the AppImage and then Chromium rejected its internal sandbox helper. LosslessCut has an open report for this exact failure. citeturn523134search10turn523134search2
The immediate workaround is:
./LosslessCut-linux-x86_64.AppImage --no-sandboxThat should launch it.
Because you obtained the AppImage from the official LosslessCut
release page, using --no-sandbox is a reasonable practical
workaround, although it disables Chromium’s process sandbox. I would not
use that flag with an AppImage downloaded from an untrusted source.
citeturn523134search4turn523134search8
To avoid typing the option every time, rename the AppImage and create a small launcher:
mv LosslessCut-linux-x86_64.AppImage LosslessCut.AppImage
cat > losslesscut <<'EOF'
#!/usr/bin/env bash
exec "$(dirname "$(readlink -f "$0")")/LosslessCut.AppImage" --no-sandbox "$@"
EOF
chmod +x losslesscutThen launch it from that directory with:
./losslesscutOr make it available system-wide for your account:
mkdir -p ~/.local/bin
ln -s "$PWD/losslesscut" ~/.local/bin/losslesscutAfter opening a new terminal:
losslesscutDo not try to change ownership of this path:
/tmp/.mount_Lossle0Q2I8A/chrome-sandbox
That directory is a temporary AppImage mount. Its name changes each time the application launches, so modifying it would not be a durable installation fix.
The simplest next command is therefore:
./LosslessCut-linux-x86_64.AppImage --no-sandboxM: i'll ask about the shortcuts once i've determined this is a viable methodology. okay, it's up and running and i've done a brief marker insertion set. where did we say i needed to go from here? we hashed it out last night and this morning was spent arguing other stuff, so i'm a bit of a blank whiteboard on the topic.
G: You’re at the “turn rough markers into a reusable chapter file” stage. I’m checking the current LosslessCut export path so I don’t send you hunting through an outdated menu.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: You are currently at the timestamp-identification stage. The plan we settled on was:
Use playback and scrubbing to put a marker at the beginning of every intended chapter.
Make sure:
00:00:00.For now, do not export individual video pieces from LosslessCut. We were using it only as the visual timestamp editor.
LosslessCut currently supports importing and exporting segment information as MKV/MP4 chapter marks, text, CSV, CUE, XML, and other formats. Look under:
File → Export project
Then choose the chapter-oriented export available in your build—ideally MKV chapters or a compatible text/XML chapter format. citeturn864214search12turn864214search14
The goal is to end up with the file we provisionally called:
chapter.file
Before moving onward, open that file in a text editor and confirm that the timestamps and names look sensible.
mkvmerge \
-o readytouse.mkv \
--chapters chapter.file \
input.mkvThat produces:
input.mkv
+ chapter.file
= readytouse.mkv
No video re-encoding occurs at this stage.
ffprobe -v error \
-show_chapters \
-of json \
readytouse.mkvYou should see one entry per intended piece, each with:
start_time
end_time
title
This is the checkpoint before doing any lengthy encoding.
The script we discussed would:
ffprobe.start_time and
end_time.01 - Opening.mkv
02 - First Section.mkv
03 - Second Section.mkv
So the full workflow is:
LosslessCut
↓
Visually place and label markers
↓
Export chapter.file
↓
mkvmerge into readytouse.mkv
↓
ffprobe verification
↓
FFmpeg chapter-splitting script
↓
Separate re-encoded MKV files
Your immediate next action is therefore: finish the marker
set, then inspect LosslessCut’s File → Export project
choices. The exact export choices shown there will determine
whether chapter.file can go directly into
mkvmerge or needs a small format conversion first.
M: what are the keyboard shortcuts for losslesscut? and/or where are the modification made? not to gripe at the guy for making a potentially useful utility, but the ui is relatively obscure compared to the paradigms set over the last couple decades.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: Yes, the UI is unusually icon-heavy and does not consistently follow familiar desktop-menu conventions.
Press:
Shift + /
That opens Keyboard & mouse shortcuts.
You can also reach it through the application menu, generally under:
Help → Keyboard & mouse shortcuts
That dialog is both:
LosslessCut’s own documentation specifically directs users to that dialog, rather than maintaining a fixed external shortcut sheet, because the bindings can change and be customized. citeturn584003search2turn584003search3
Inside the dialog, search for an action, click its existing binding, and press the replacement key combination. Recent versions also let you trigger/test actions directly from the shortcuts page. citeturn584003search0turn584003search13
The most relevant ones are generally:
Space Play / pause
J Play backward or reduce playback speed
K Pause
L Play forward or increase playback speed
Left Seek backward
Right Seek forward
, Previous frame
. Next frame
Shift + / Open keyboard-shortcut editor
Ctrl + Z Undo
Ctrl + Y Redo
The developer explicitly identifies J, K, and L as the playback-speed controls. citeturn584003search11
The exact seek amount attached to the arrow keys and modifier combinations can vary with the current shortcut configuration, so confirm those in your dialog rather than relying on a generic list.
Open the shortcut dialog and search for:
Add cut segment
That action can be assigned to a simple key such as:
M
A recent discussion from the developer recommends exactly that when someone wants Premiere-style “press M while playing to mark this point” behavior. citeturn584003search8
There may also be a distinct action involving markers rather than segments, depending on your installed version. Search the shortcut dialog for:
marker
segment
cut
start
end
The terminology is part of the obscurity here: LosslessCut is primarily organized around export segments, so some operations that visually resemble chapter markers are labeled as segment actions rather than conventional timeline markers.
Click the small baby icon in the lower-left corner to enable Advanced view.
That exposes functions which otherwise seem simply absent, including additional export and segment controls. The project documentation explicitly says to enable Advanced view when you cannot find a function. citeturn584003search3
The export-options toggle is likewise near the Export button at the lower right, rather than in an ordinary persistent preferences menu.
For this project, I would customize a minimal working set:
Space Play/pause
M Add marker or cut segment
, Previous frame
. Next frame
Left Short seek backward
Right Short seek forward
J/K/L Reverse, pause, forward/speed
Then you can watch continuously, tap M near a suspected
boundary, and later use the arrows and frame keys to refine it.
M: [Non-text content: image_asset_pointer; content_type=image_asset_pointer; size_bytes=15606]
thank you... okay, i've got the keys bound and a list. these are the export options, screencapped.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: From those choices, select Timestamps (TSV/TXT).
That exports each segment as a line roughly like:
00:00:00.000 00:07:04.576 Opening
00:07:04.576 00:12:42.145 Second chapter
So it preserves:
That is the best intermediate format for the workflow we planned. LosslessCut’s timestamp export is documented/examples show it using start time, end time, and label on each line. citeturn922559search0
LosslessCut’s “markers” are actually segments without end points. For useful chapter export, you ideally want a set of consecutive segments:
Chapter 1: 00:00 → 05:32
Chapter 2: 05:32 → 11:47
Chapter 3: 11:47 → end
If you created only isolated zero-length markers, the exported TSV may not automatically produce proper chapter ranges. LosslessCut describes markers as segments that simply lack an end time. citeturn922559search14
Check the exported file. If it contains sensible start and end timestamps, you are fine.
Export it and call it something like:
chapters.tsv
Then show me a few lines of its actual contents. We will write a tiny conversion command that transforms it into MKVToolNix’s simple chapter format:
CHAPTER01=00:00:00.000
CHAPTER01NAME=Opening
CHAPTER02=00:07:04.576
CHAPTER02NAME=Second chapter
Only the start time and label need to go into the MKV chapter file; the end of one chapter is implied by the beginning of the next.
[Attachment reference unresolved: file_into]
M: [Empty message]
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: That export is clean and exactly usable:
Start End Name
00:00:00.000 00:01:43.402 intro
00:01:43.402 00:58:20.801 sc1
00:58:20.801 01:49:45.095 sc2
01:49:45.095 02:14:12.356 sc3
Convert it into an MKVToolNix chapter file with:
awk -F '\t' '
NR > 1 {
printf "CHAPTER%02d=%s\n", NR - 1, $1
printf "CHAPTER%02dNAME=%s\n", NR - 1, $3
}
' daachapters.tsv > chapter.fileThe resulting chapter.file will be:
CHAPTER01=00:00:00.000
CHAPTER01NAME=intro
CHAPTER02=00:01:43.402
CHAPTER02NAME=sc1
CHAPTER03=00:58:20.801
CHAPTER03NAME=sc2
CHAPTER04=01:49:45.095
CHAPTER04NAME=sc3
Notice that the final TSV end time, 02:14:12.356, does
not need to appear in the chapter file. A chapter’s end is inferred from
the next chapter’s start, and the last chapter runs to the end of the
media.
Then merge it:
mkvmerge \
-o readytouse.mkv \
--chapters chapter.file \
input.mkvVerify:
ffprobe -v error \
-show_entries chapter=start_time,end_time:chapter_tags=title \
-of default=noprint_wrappers=1 \
readytouse.mkvYou should get four chapter blocks corresponding to
intro, sc1, sc2, and
sc3.
One caveat: your last exported segment ends at
02:14:12.356. If that is also the actual end of
input.mkv, everything is aligned. If the MKV continues
beyond that point, the embedded sc3 chapter will continue
through the remaining footage unless you deliberately add another
chapter or trim the source.
M: yudasgoat@Lilypad:/media/yudasgoat/Terapile/slow-retrieve-zeros/___clearing-with-split$ ffprobe -v error -show_entries chapter=start_time,end_time:chapter_tags=title -of default=noprint_wrappers=1 daa1chapters.mkv start_time=0.000000 end_time=103.402000 TAG:title=intro start_time=103.402000 end_time=3500.801000 TAG:title=sc1 start_time=3500.801000 end_time=6585.095000 TAG:title=sc2 start_time=6585.095000 end_time=8053.356000 TAG:title=sc3
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: That output confirms the chapter merge worked correctly.
You now have four chapters:
intro 0.000 → 103.402 1:43.402
sc1 103.402 → 3500.801 56:37.399
sc2 3500.801 → 6585.095 51:24.294
sc3 6585.095 → 8053.356 24:28.261
So daa1chapters.mkv is ready for the actual
split-and-reencode stage.
A direct Bash loop using ffprobe, jq, and
FFmpeg:
#!/usr/bin/env bash
set -euo pipefail
input="daa1chapters.mkv"
output_dir="daa1-split"
mkdir -p "$output_dir"
chapter_number=0
while IFS=$'\t' read -r start end title; do
chapter_number=$((chapter_number + 1))
number=$(printf '%02d' "$chapter_number")
safe_title=$(
printf '%s' "$title" |
sed 's#[/\\:*?"<>|]#_#g; s/^[[:space:]]*//; s/[[:space:]]*$//'
)
duration=$(
awk -v start="$start" -v end="$end" \
'BEGIN { printf "%.6f", end - start }'
)
output="${output_dir}/${number}-${safe_title}.mkv"
echo
echo "Creating $output"
echo "Start: $start Duration: $duration"
ffmpeg -hide_banner -y \
-ss "$start" \
-i "$input" \
-t "$duration" \
-map 0 \
-map_chapters -1 \
-map_metadata 0 \
-c:v libx264 \
-preset medium \
-crf 18 \
-c:a copy \
-c:s copy \
"$output"
done < <(
ffprobe -v error \
-show_chapters \
-of json \
"$input" |
jq -r '
.chapters[] |
[
.start_time,
.end_time,
(.tags.title // "Untitled")
] |
@tsv
'
)
echo
echo "Created $chapter_number files in $output_dir/"Save it as:
nano split-daa1.shMake it executable and run it:
chmod +x split-daa1.sh
./split-daa1.shExpected output:
daa1-split/
├── 01-intro.mkv
├── 02-sc1.mkv
├── 03-sc2.mkv
└── 04-sc3.mkv
One thing worth checking before launching a potentially long encode:
ffprobe -v error \
-select_streams v:0 \
-show_entries stream=codec_name,width,height,pix_fmt \
-of default=noprint_wrappers=1 \
daa1chapters.mkvThe script above converts the video to H.264 regardless of its current codec. That is broadly compatible, but the source codec, resolution, and bit depth may justify changing the encoder settings before you spend the time.
M: error message: Creating daa1-split/02-.mkv Start: sc1 Duration: 0.000000 Invalid duration for option ss: sc1 Error parsing options for input file daa1chapters.mkv. Error opening input files: Invalid argument
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: The chapter data is getting shifted during the TSV-to-shell parsing. On the second pass, the loop has read:
start = sc1
end = empty
title = empty
That is why FFmpeg receives:
-ss sc1Replace the TSV parsing with one compact JSON object per chapter. This avoids shell field-splitting problems entirely:
#!/usr/bin/env bash
set -euo pipefail
input="daa1chapters.mkv"
output_dir="daa1-split"
mkdir -p "$output_dir"
chapter_number=0
while IFS= read -r chapter_json; do
chapter_number=$((chapter_number + 1))
number=$(printf '%02d' "$chapter_number")
start=$(jq -r '.start_time' <<< "$chapter_json")
end=$(jq -r '.end_time' <<< "$chapter_json")
title=$(jq -r '.tags.title // "Untitled"' <<< "$chapter_json")
safe_title=$(
printf '%s' "$title" |
sed '
s#[/\\:*?"<>|]#_#g
s/^[[:space:]]*//
s/[[:space:]]*$//
'
)
[[ -n "$safe_title" ]] || safe_title="chapter-$number"
duration=$(
awk -v start="$start" -v end="$end" '
BEGIN {
duration = end - start
if (duration <= 0)
exit 1
printf "%.6f", duration
}
'
) || {
echo "Invalid chapter interval: $start to $end" >&2
exit 1
}
output="${output_dir}/${number}-${safe_title}.mkv"
echo
echo "Creating: $output"
echo "Start: $start"
echo "End: $end"
echo "Duration: $duration"
ffmpeg -hide_banner -y \
-ss "$start" \
-i "$input" \
-t "$duration" \
-map 0 \
-map_chapters -1 \
-map_metadata 0 \
-c:v libx264 \
-preset medium \
-crf 18 \
-c:a copy \
-c:s copy \
"$output"
done < <(
ffprobe -v error \
-show_chapters \
-of json \
"$input" |
jq -c '.chapters[]'
)
echo
echo "Created $chapter_number files in $output_dir/"Before rerunning it, remove the malformed partial output:
rm -f 'daa1-split/02-.mkv'Then run the script again.
The opening of the run should now display:
Creating: daa1-split/01-intro.mkv
Start: 0.000000
End: 103.402000
Duration: 103.402000
Creating: daa1-split/02-sc1.mkv
Start: 103.402000
End: 3500.801000
Duration: 3397.399000
The original logic was sound; the fragile part was passing three values through tab-delimited shell input. This version passes each complete chapter as JSON and extracts the fields individually.
M: more error, here's the whole output: yudasgoat@Lilypad:/media/yudasgoat/Terapile/slow-retrieve-zeros/___clearing-with-split$ ./split-test.sh
Creating: daa1-split/01-intro.mkv Start: 0.000000 End: 103.402000
Duration: 103.402000 Input #0, matroska,webm, from 'daa1chapters.mkv':
Metadata: SOFTWARE : VirtualDubMod 1.5.4.1 (build 2178/release)
creation_time : 2026-07-27T17:49:27.000000Z ENCODER : Lavf59.16.100
Duration: 02:14:13.36, start: 0.000000, bitrate: 685 kb/s Chapters:
Chapter #0:0: start 0.000000, end 103.402000 Metadata: title : intro
Chapter #0:1: start 103.402000, end 3500.801000 Metadata: title : sc1
Chapter #0:2: start 3500.801000, end 6585.095000 Metadata: title : sc2
Chapter #0:3: start 6585.095000, end 8053.356000 Metadata: title : sc3
Stream #0:0: Video: h264 (High), yuv420p(progressive), 512x384 [SAR 1:1
DAR 4:3], 29.97 fps, 29.97 tbr, 1k tbn Metadata: ENCODER : Lavc59.18.100
libx264 BPS : 589457 DURATION : 02:14:13.323000000 NUMBER_OF_FRAMES:
241358 NUMBER_OF_BYTES : 593386004 _STATISTICS_WRITING_APP: mkvmerge
v82.0 ('I'm The President') 64-bit _STATISTICS_WRITING_DATE_UTC:
2026-07-27 17:49:27 _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES
NUMBER_OF_BYTES Stream #0:1: Audio: vorbis, 48000 Hz, stereo, fltp
Metadata: _STATISTICS_TAGS: BPS DURATION NUMBER_OF_FRAMES
NUMBER_OF_BYTES BPS : 93454 DURATION : 02:14:12.293000000
NUMBER_OF_FRAMES: 428501 NUMBER_OF_BYTES : 94065284
_STATISTICS_WRITING_APP: mkvmerge v82.0 ('I'm The President') 64-bit
_STATISTICS_WRITING_DATE_UTC: 2026-07-27 17:49:27 ENCODER :
Lavc59.18.100 Stream mapping: Stream #0:0 -> #0:0 (h264 (native)
-> h264 (libx264)) Stream #0:1 -> #0:1 (copy) Press [q] to stop,
[?] for help [libx264 @ 0x6390c4c5dec0] using SAR=1/1 [libx264 @
0x6390c4c5dec0] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX
FMA3 BMI2 AVX2 AVX512 [libx264 @ 0x6390c4c5dec0] profile High, level
3.0, 4:2:0, 8-bit [libx264 @ 0x6390c4c5dec0] 264 - core 164 r3108
31e19f9 - H.264/MPEG-4 AVC codec - Copyleft 2003-2023 - http://www.videolan.org/x264.html
- options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7
psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1
8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2
threads=12 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1
interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2
b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250
keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf
mbtree=1 crf=18.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40
aq=1:1.00 Output #0, matroska, to 'daa1-split/01-intro.mkv': Metadata:
SOFTWARE : VirtualDubMod 1.5.4.1 (build 2178/release) creation_time :
2026-07-27T17:49:27.000000Z encoder : Lavf60.16.100 Stream #0:0: Video:
h264 (H264 / 0x34363248), yuv420p(progressive), 512x384 [SAR 1:1 DAR
4:3], q=2-31, 29.97 fps, 1k tbn Metadata: _STATISTICS_TAGS: BPS DURATION
NUMBER_OF_FRAMES NUMBER_OF_BYTES BPS : 589457 DURATION :
02:14:13.323000000 NUMBER_OF_FRAMES: 241358 NUMBER_OF_BYTES : 593386004
_STATISTICS_WRITING_APP: mkvmerge v82.0 ('I'm The President') 64-bit
_STATISTICS_WRITING_DATE_UTC: 2026-07-27 17:49:27 encoder :
Lavc60.31.102 libx264 Side data: cpb: bitrate max/min/avg: 0/0/0 buffer
size: 0 vbv_delay: N/A Stream #0:1: Audio: vorbis (oV[0][0] / 0x566F),
48000 Hz, stereo, fltp Metadata: _STATISTICS_TAGS: BPS DURATION
NUMBER_OF_FRAMES NUMBER_OF_BYTES BPS : 93454 DURATION :
02:14:12.293000000 NUMBER_OF_FRAMES: 428501 NUMBER_OF_BYTES : 94065284
_STATISTICS_WRITING_APP: mkvmerge v82.0 ('I'm The President') 64-bit
_STATISTICS_WRITING_DATE_UTC: 2026-07-27 17:49:27 ENCODER :
Lavc59.18.100 [out#0/matroska @ 0x6390c4c74fc0] video:9488kB
audio:1393kB subtitle:0kB other streams:0kB global headers:4kB muxing
overhead: 0.690660% frame= 3098 fps=1370 q=-1.0 Lsize= 10957kB
time=00:01:43.39 bitrate= 868.1kbits/s speed=45.7x
[libx264 @ 0x6390c4c5dec0] frame I:40 Avg QP:12.97 size: 18557 [libx264
@ 0x6390c4c5dec0] frame P:1091 Avg QP:17.58 size: 6103 [libx264 @
0x6390c4c5dec0] frame B:1967 Avg QP:21.43 size: 1177 [libx264 @
0x6390c4c5dec0] consecutive B-frames: 3.0% 35.9% 3.4% 57.7% [libx264 @
0x6390c4c5dec0] mb I I16..4: 24.7% 44.9% 30.4% [libx264 @
0x6390c4c5dec0] mb P I16..4: 1.6% 7.4% 1.5% P16..4: 41.8% 14.6% 6.7%
0.0% 0.0% skip:26.4% [libx264 @ 0x6390c4c5dec0] mb B I16..4: 0.3% 0.5%
0.1% B16..8: 23.2% 2.3% 0.4% direct: 4.6% skip:68.5% L0:37.7% L1:40.3%
BI:22.1% [libx264 @ 0x6390c4c5dec0] 8x8 transform intra:63.1%
inter:56.7% [libx264 @ 0x6390c4c5dec0] coded y,uvDC,uvAC intra: 60.4%
71.3% 36.3% inter: 13.4% 14.8% 1.1% [libx264 @ 0x6390c4c5dec0] i16
v,h,dc,p: 49% 15% 11% 24% [libx264 @ 0x6390c4c5dec0] i8
v,h,dc,ddl,ddr,vr,hd,vl,hu: 20% 16% 22% 6% 8% 8% 8% 6% 7% [libx264 @
0x6390c4c5dec0] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 20% 17% 12% 7% 11% 10% 9%
7% 7% [libx264 @ 0x6390c4c5dec0] i8c dc,h,v,p: 46% 20% 22% 11% [libx264
@ 0x6390c4c5dec0] Weighted P-Frames: Y:9.8% UV:7.7% [libx264 @
0x6390c4c5dec0] ref P L0: 67.3% 9.6% 20.7% 2.4% 0.0% [libx264 @
0x6390c4c5dec0] ref B L0: 84.2% 14.9% 0.9% [libx264 @ 0x6390c4c5dec0]
ref B L1: 99.2% 0.8% [libx264 @ 0x6390c4c5dec0] kb/s:751.89 jq: parse
error: Expected value before ',' at line 1, column 3
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: The first chapter encoded successfully. The failure afterward is because FFmpeg consumed the loop’s standard input while it was encoding.
Your loop receives chapter JSON through stdin:
done < <(
ffprobe ... | jq -c '.chapters[]'
)But FFmpeg also monitors stdin for interactive commands such as
q. During the first 103-second encode, it read part of the
remaining JSON stream. The next loop iteration therefore received a
mangled fragment, producing:
jq: parse error
Add -nostdin to the FFmpeg command.
FFmpeg officially documents this option for cases where stdin is being
used for something else. citeturn930100search0turn930100search1
Change:
ffmpeg -hide_banner -y \to:
ffmpeg -hide_banner -nostdin -y \So the relevant section becomes:
ffmpeg -hide_banner -nostdin -y \
-ss "$start" \
-i "$input" \
-t "$duration" \
-map 0 \
-map_chapters -1 \
-map_metadata 0 \
-c:v libx264 \
-preset medium \
-crf 18 \
-c:a copy \
-c:s copy \
"$output"Then rerun:
./split-test.shBecause you used -y, it will overwrite the already
completed 01-intro.mkv and begin again. To avoid repeating
that first encode, either delete it and rerun normally, or add this
immediately before the FFmpeg command:
if [[ -f "$output" ]]; then
echo "Skipping existing file: $output"
continue
fiThe corrected complete loop structure is:
while IFS= read -r chapter_json; do
chapter_number=$((chapter_number + 1))
number=$(printf '%02d' "$chapter_number")
start=$(jq -r '.start_time' <<< "$chapter_json")
end=$(jq -r '.end_time' <<< "$chapter_json")
title=$(jq -r '.tags.title // "Untitled"' <<< "$chapter_json")
safe_title=$(
printf '%s' "$title" |
sed '
s#[/\\:*?"<>|]#_#g
s/^[[:space:]]*//
s/[[:space:]]*$//
'
)
duration=$(
awk -v start="$start" -v end="$end" \
'BEGIN { printf "%.6f", end - start }'
)
output="${output_dir}/${number}-${safe_title}.mkv"
echo
echo "Creating: $output"
echo "Start: $start"
echo "End: $end"
echo "Duration: $duration"
ffmpeg -hide_banner -nostdin -y \
-ss "$start" \
-i "$input" \
-t "$duration" \
-map 0 \
-map_chapters -1 \
-map_metadata 0 \
-c:v libx264 \
-preset medium \
-crf 18 \
-c:a copy \
-c:s copy \
"$output"
done < <(
ffprobe -v error \
-show_chapters \
-of json \
"$input" |
jq -c '.chapters[]'
)So this was not another chapter-format problem. The JSON was correct; FFmpeg simply stole the remaining lines from the loop.
M: it looks to be running; proof of concept proven. let me stew on implementation before i ask you to turn this into a generalized script or series of scripts.
G: Good. The pipeline has now demonstrated all the load-bearing parts:
mkvmerge embeds them correctly.ffprobe reads them correctly.-nostdin fixes the loop interference.So the remaining work is mostly deciding how automated, configurable, and failure-resistant you want the final version to be.