M: give me a loop using ffmpeg that extracts the first audio track from every mkv file, saving it in a format that can be used as an external audio file in jellyfin.
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: Here’s a safe, no-reencode
extractor. It pulls the first audio track from every
.mkv and saves it as a Matroska audio file
(.mka), which Jellyfin can use as an external audio
track.
shopt -s nullglob
for f in *.mkv; do
base="${f%.*}"
out="${base}.external.mka"
echo ">> $f -> $out"
ffmpeg -y -i "$f" -map 0:a:0 -c copy "$out"
doneshopt -s nullglob
for f in *.mkv; do
base="${f%.*}"
# Get language tag of first audio stream (falls back to 'und')
lang=$(ffprobe -v error -select_streams a:0 \
-show_entries stream_tags=language \
-of default=nw=1:nk=1 "$f")
[ -z "$lang" ] && lang="und"
out="${base}.${lang}.external.mka"
echo ">> $f -> $out"
ffmpeg -y -i "$f" -map 0:a:0 -c copy "$out"
done.mka keeps the original audio codec without
re-encoding, so it’s fast and lossless for this step..mka files next to
their videos. Using the same base name (optionally with a language code
like .eng) helps Jellyfin auto-associate them.a:0, ffmpeg
will error. You can skip such files by checking with
ffprobe first if needed.M: what about if, in another folder, i wanted a loop that would look at every mkv file and, if there's a similarly named mka file, would alter the mkv so that the mka became the first audio track, with the original shifted to the second audio track?
G: [Non-text content: thoughts; content_type=thoughts]
G: [Non-text content: reasoning_recap; content_type=reasoning_recap]
G: Got it. Here’s a robust
ffmpeg-based loop that, for every .mkv in the current
folder, looks for a same-basename .mka
(optionally in a separate MKA_DIR) and remuxes so that the
.mka becomes audio #0 (default) and the
original audio track(s) are shifted after it. All other streams (video,
subs, chapters) are preserved losslessly.
.mkv
files.MKA_DIR to the folder containing your
.mka files (or leave empty to use the same folder)..mkv after success.#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
# Set this if your .mka files live elsewhere, e.g. MKA_DIR="/path/to/mka"
MKA_DIR="${MKA_DIR:-}"
for v in *.mkv; do
base="${v%.*}"
a="${MKA_DIR:+$MKA_DIR/}$base.mka"
if [[ -f "$a" ]]; then
tmp="${base}.tmp.remux.mkv"
echo ">> Injecting external audio for: $v"
echo " Using MKA: $a"
# Inputs: 0 = original MKV, 1 = external MKA
# Order streams so:
# - video/subs/data from 0 first (no audio yet)
# - audio from 1 (becomes a:0)
# - then original audio(s) from 0 (become a:1, a:2, ...)
# Also copy chapters and clear any existing 'default' on audio before
# marking the new a:0 as default.
ffmpeg -hide_banner -loglevel error -y \
-i "$v" -i "$a" \
-map 0:v -map 0:s? -map 0:d? \
-map 1:a \
-map 0:a? \
-map_chapters 0 \
-c copy \
-disposition:a 0 \
-disposition:a:0 default \
"$tmp"
# Replace original atomically
mv -f -- "$tmp" "$v"
echo " Done -> $v (external audio is now first/default)"
else
echo "-- Skipping $v (no matching $base.mka found)"
fi
done-c copy), just a
stream re-order + flag update.-map_chapters 0)..mka in their original order..eng.mka), you can change the line that sets
a=... to a small glob like:
a_candidate=( ${MKA_DIR:+$MKA_DIR/}"$base".*.mka ${MKA_DIR:+$MKA_DIR/}"$base".mka )
a="${a_candidate[0]:-}"
[[ -n "${a:-}" && -f "$a" ]] || { echo "-- Skipping $v (no matching MKA)"; continue; }This should slot right into your Jellyfin workflow.
M: can you make that a cli command? the script looks good but i'd like to just paste it into the terminal
G: Absolutely 👍 — here’s a
single-line command you can paste straight into your shell. It loops
through .mkv files in the current folder, checks for a
same-basename .mka, and remuxes so the .mka
becomes audio #0 (default) and the original audio tracks are shifted
after it:
for v in *.mkv; do base="${v%.*}"; a="${base}.mka"; if [[ -f "$a" ]]; then tmp="${base}.tmp.remux.mkv"; echo ">> Processing $v with $a"; ffmpeg -hide_banner -loglevel error -y -i "$v" -i "$a" -map 0:v -map 0:s? -map 0:d? -map 1:a -map 0:a? -map_chapters 0 -c copy -disposition:a 0 -disposition:a:0 default "$tmp" && mv -f -- "$tmp" "$v"; fi; doneThat’s the whole script condensed to a one-liner for direct CLI use 🚀