2025-09-10 11:52:05 EDT

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.

Simple (same folder, same basename)

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"
done

With language code in the filename (helps Jellyfin label it)

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

Notes

2025-09-10 11:56:35 EDT

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.

Usage

#!/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

Notes

This should slot right into your Jellyfin workflow.

2025-09-10 11:59:12 EDT

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; done

That’s the whole script condensed to a one-liner for direct CLI use 🚀