Search
Write a publication
Pull to refresh

Comments 27

PinnedPinned comments

Обновление!
- Исправлено множество багов
- Добавлено еще больше популярных форматов
- Исправление вывода
- Улучшенный детальный анализ

В скором будущем перепишу с ffprobe, потому что он работает крайне медленно, и добавлю несколько важных фишек.

Только текущая папка (без рекурсии):

mdur -r

Неожиданно!

Не работает, если в имени файла больше одной точки. Например - Love.Death.and.Robots.S04E10.For.He.Can.Creep.mkv

Нет. дело не в точках, извиняюсь...

Внутри каталога запускать нельзя, выходит?

Исправил... Теперь точно работает!

Так работает:

/mnt/d2/media/music$ mdur "/mnt/d2/media/music/Milk 'n Blues - 2015 - Milk'n Blues/"

Общая длительность: 00:37:08 Файлов: 10

А так - нет:

/mnt/d2/media/music/Milk 'n Blues - 2015 - Milk'n Blues$ mdur

Не найдено поддерживаемых медиафайлов в каталоге '/mnt/d2/media/music/Milk 'n Blues - 2015 - Milk'n Blues'

И правда, боюсь почему-то не засчитывает файлы в данном каталоге...
Как освобожусь на недели обязательно исправлю!

Исправлено!

Успешно обработано файлов: 1103

Анализ медиафайлов в: /mnt/power/media/movies
Сортировка: по длительности
Файлов: 1103, Общая длительность: 2040:54:10

во первых оно потеряло почти сотню файлов где-то по пути (пять из них я знаю куда делись, это форматы которые ваш скрипт не признаёт, а остальные я хз).
во вторых когда имеешь файлопомойку на 50 тер хочется таки видеть уже не в часах а в днях.. месяцах.. годах..

[werwolf@home] ~  
❯ tree /mnt/power/media/ | tail -n 1      
11352 directories, 217270 files

а в целом респект, хорошая задумка и относительно неплохая реализация, башевать такое это мы любим (спасибо что не `npm run` или `cargo build`).

во первых оно потеряло почти сотню файлов где-то по пути

Извините...
Сам заметил эту проблему, но к сожалению эта проблема именно в ffprobe. (не считая 5 файлов, с этим порешаю)
Да сам замечал не точности в ffmpeg, но я вообще хз как это можно исправить(

во вторых когда имеешь файлопомойку на 50 тер хочется таки видеть уже не в часах а в днях.. месяцах.. годах..

Спасибо за идею! Не думал, что и правда у кого есть настолько большие данные)))
Думаю добавить это как отдельный параметр, но опять же как будет хоть капелька свободного времени T-T

а в целом респект, хорошая задумка и относительно неплохая реализация, башевать такое это мы любим (спасибо что не npm run или cargo build).

Очень очень благодарен за поддержку! (Если честно ожидал 0-ой надобности)

но кому охота вручную складывать часы и минуты из 200 файлов?

А разве просто просмотр свойств файлов в файловом менеджере не показывает общую продолжительность если выделены только медиафайлы?
Или Вы видео тоже в консоли смотрите?

А разве просто просмотр свойств файлов в файловом менеджере не показывает общую продолжительность если выделены только медиафайлы?

Это муторно выделять нужные файлы (а если у тебя их больше 1000? Как у одного из других комментаторов) , да и не у всех показывается длительность (зависит от диспетчера файлов).
И да, большинство видео я смотрю в консоли))

Это муторно выделять нужные файлы

Да вроде в файловых менеджерах и функции "выделить всё" и поиска по типам имеются.

да и не у всех показывается длительность (зависит от диспетчера файлов)

Такое есть, да.
Ещё можно закинуть в плейлист проигрывателя (для проигрывателей, поддерживающих плейлисты), тоже общую длительность покажет.

И да, большинство видео я смотрю в консоли))

В ASCII output надеюсь? :D :D

В ASCII output надеюсь? :D :D

Бывало дело))) =D

Бывало дело))) =D

Наш человек! ^_^=b

Уберите тег "DevOps" из статьи

Кроме ffprobe есть еще mediainfo. Может она лучше будет, или хотя бы использовать ее когда ffbrobe сфейлился.

Соглашусь, буду переписывать на другую утилиту

Попросил гпт переделать с помощью mediainfo. Вроде работает, но это не точно, не очень понял зачем это нужно вообще.

$ mdurmi ~/tmp
Общая длительность: 00:07:36
 Файлов: 11

Длительность  Имя файла
----------  ---------
00:00:05    /home/ubuntu/tmp/1111/videos/IMG_4820.MP4
00:00:11    /home/ubuntu/tmp/1111/videos/IMG_4683.MP4
00:00:13    /home/ubuntu/tmp/1111/videos/m2-res_1280p (36).mp4
00:00:12    /home/ubuntu/tmp/1111/videos/IMG_7808.MOV
00:00:09    /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-18-28.mp4
00:00:10    /home/ubuntu/tmp/1111/videos/123.flv
00:01:28    /home/ubuntu/tmp/1111/videos/Встретились два одиночества #скорая #скораяпомощь #первыекторядом #смп #03 #скорая помощь #103 [YgwvtN3bG_Q].webm
00:02:45    /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-18-43.mp4
00:00:17    /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-19-01.mp4
00:00:10    /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-19-05.mp4
00:01:56    /home/ubuntu/tmp/1111/videos/IMG_1385.MOV

$ mdurmi -v -f -s d ~/tmp
Найдено файлов: 11
Читаю: /home/ubuntu/tmp/1111/videos/IMG_4820.MP4
Читаю: /home/ubuntu/tmp/1111/videos/IMG_4683.MP4
Читаю: /home/ubuntu/tmp/1111/videos/m2-res_1280p (36).mp4
Читаю: /home/ubuntu/tmp/1111/videos/IMG_7808.MOV
Читаю: /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-18-28.mp4
Читаю: /home/ubuntu/tmp/1111/videos/123.flv
Читаю: /home/ubuntu/tmp/1111/videos/Встретились два одиночества #скорая #скораяпомощь #первыекторядом #смп #03 #скорая помощь #103 [YgwvtN3bG_Q].webm
Читаю: /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-18-43.mp4
Читаю: /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-19-01.mp4
Читаю: /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-19-05.mp4
Читаю: /home/ubuntu/tmp/1111/videos/IMG_1385.MOV
Общая длительность: 00:07:36
 Файлов: 11

Длительность  Формат  Имя файла
----------  ------        ---------
00:02:45    MPEG-4        /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-18-43.mp4
00:01:56    MPEG-4        /home/ubuntu/tmp/1111/videos/IMG_1385.MOV
00:01:28    WebM          /home/ubuntu/tmp/1111/videos/Встретились два одиночества #скорая #скораяпомощь #первыекторядом #смп #03 #скорая помощь #103 [YgwvtN3bG_Q].webm
00:00:17    MPEG-4        /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-19-01.mp4
00:00:13    MPEG-4        /home/ubuntu/tmp/1111/videos/m2-res_1280p (36).mp4
00:00:12    MPEG-4        /home/ubuntu/tmp/1111/videos/IMG_7808.MOV
00:00:11    MPEG-4        /home/ubuntu/tmp/1111/videos/IMG_4683.MP4
00:00:10    Flash Video   /home/ubuntu/tmp/1111/videos/123.flv
00:00:10    MPEG-4        /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-19-05.mp4
00:00:09    MPEG-4        /home/ubuntu/tmp/1111/videos/video_2025-08-01_11-18-28.mp4
00:00:05    MPEG-4        /home/ubuntu/tmp/1111/videos/IMG_4820.MP4
#!/usr/bin/env bash
set -uo pipefail

VERSION="0.7"
RECURSIVE=true
VERBOSE=false
SHOW_FORMAT=false
SORT_KEY=""
TYPES=""
MI_TIMEOUT="10s"

print_help() {
  cat <<EOF
mdurmi v$VERSION — суммирует длительность медиафайлов через mediainfo.

Использование:
  mdurmi [опции] [ПУТЬ]
Опции:
  -r    только текущая папка (без рекурсии)
  -s d  сортировка по длительности (desc)
  -t l  фильтр по расширениям: mp4,mov,mp3
  -f    показывать формат (контейнер)
  -v    подробный режим
  -h    помощь
EOF
}

seconds_to_hms() {
  local s=${1%.*}
  s=$((10#$s))
  local h=$((10#$s / 3600))
  local m=$(((10#$s % 3600) / 60))
  local c=$((10#$s % 60))
  printf "%02d:%02d:%02d" "$h" "$m" "$c"
}

require_tools() {
  if ! command -v mediainfo >/dev/null 2>&1; then
    echo "Ошибка: mediainfo не найден" >&2
    exit 127
  fi
}

TIMEOUT_CMD=""
if command -v timeout >/dev/null 2>&1; then
  TIMEOUT_CMD="timeout"
elif command -v gtimeout >/dev/null 2>&1; then
  TIMEOUT_CMD="gtimeout"
fi

run_mi() {
  # $1 = inform, $2 = file
  if [[ -n "$TIMEOUT_CMD" ]]; then
    "$TIMEOUT_CMD" "$MI_TIMEOUT" mediainfo --Inform="$1" "$2" 2>/dev/null | tr -d '\r'
  else
    mediainfo --Inform="$1" "$2" 2>/dev/null | tr -d '\r'
  fi
}

probe_file() {
  local file="$1"
  local dur_ms fmt

  dur_ms=$(run_mi "General;%Duration%" "$file" || true)
  if [[ -z "$dur_ms" || "$dur_ms" == "0" ]]; then
    dur_ms=$(run_mi "Video;%Duration%" "$file" || true)
    if [[ -z "$dur_ms" || "$dur_ms" == "0" ]]; then
      dur_ms=$(run_mi "Audio;%Duration%" "$file" || true)
    fi
  fi

  if $SHOW_FORMAT; then
    fmt=$(run_mi "General;%Format%" "$file" || true)
  else
    fmt=""
  fi

  if [[ -z "$dur_ms" ]] || ! [[ "$dur_ms" =~ ^[0-9]+([.][0-9]+)?$ ]] || [[ "$dur_ms" == "0" ]]; then
    $VERBOSE && echo "  Ошибка: нет валидной длительности: $file" >&2
    echo -e "ERR\t${fmt}\t${file}"
    return 1
  fi

  local dur_s
  dur_s=$(awk -v ms="$dur_ms" 'BEGIN{printf "%.0f", ms/1000}')
  echo -e "${dur_s}\t${fmt}\t${file}"
  return 0
}

while getopts ":rs:t:fvh" opt; do
  case "$opt" in
    r) RECURSIVE=false ;;
    s) SORT_KEY="$OPTARG" ;;
    t) TYPES="$OPTARG" ;;
    f) SHOW_FORMAT=true ;;
    v) VERBOSE=true ;;
    h) print_help; exit 0 ;;
    \?) echo "Неизвестная опция: -$OPTARG" >&2; exit 2 ;;
    :) echo "Опция -$OPTARG требует аргумент" >&2; exit 2 ;;
  esac
done
shift $((OPTIND-1))

TARGET="${1:-.}"
require_tools

# Дефолтные расширения
if [[ -z "$TYPES" ]]; then
  TYPES="mp4,mov,m4v,mkv,avi,flv,webm,mp3,flac,wav,aac,ogg,m4a"
fi

shopt -s nullglob dotglob
declare -a files
skip_suffix=".filepart"

collect_recursive() {
  IFS=, read -r -a exts <<<"$TYPES"
  find_expr=()
  for e in "${exts[@]}"; do
    e="${e#.}"
    find_expr+=(-iname "*.${e}")
    find_expr+=(-o)
  done
  unset 'find_expr[${#find_expr[@]}-1]' || true
  while IFS= read -r -d '' f; do
    [[ "$f" == *"$skip_suffix" ]] && continue
    files+=("$f")
  done < <(find "$TARGET" -type f \( "${find_expr[@]}" \) -print0)
}
collect_shallow() {
  if [[ -d "$TARGET" ]]; then
    IFS=, read -r -a exts <<<"$TYPES"
    for e in "${exts[@]}"; do
      e="${e#.}"
      for f in "$TARGET"/*."$e"; do
        [[ -e "$f" ]] || continue
        [[ "$f" == *"$skip_suffix" ]] && continue
        files+=("$f")
      done
    done
  else
    [[ "$TARGET" == *"$skip_suffix" ]] || files+=("$TARGET")
  fi
}

if $RECURSIVE; then
  collect_recursive
else
  collect_shallow
fi

total_sec=0
count=0
errors=0
results_file=$(mktemp)
trap 'rm -f "$results_file"' EXIT

$VERBOSE && echo "Найдено файлов: ${#files[@]}"

for file in "${files[@]}"; do
  [[ -f "$file" ]] || continue
  $VERBOSE && echo "Читаю: $file"

  line=""
  if ! line=$(probe_file "$file"); then
    ((errors++))
    continue
  fi

  dur_s=$(cut -f1 <<<"$line")
  fmt=$(cut -f2 <<<"$line")
  name=$(cut -f3- <<<"$line")

  total_sec=$((10#$total_sec + 10#$dur_s))
  ((count++))

  printf "%09d\t%s\t%s\n" "$dur_s" "${fmt:-}" "$name" >>"$results_file"
done

if [[ $count -eq 0 ]]; then
  echo "Общая длительность: 00:00:00"
  echo " Файлов: 0"
  [[ $errors -gt 0 ]] && echo " Ошибок: $errors (включи -v)"
  exit 0
fi

# Сортировка по первому числовому полю
if [[ "$SORT_KEY" == "d" ]]; then
  sort -k1,1nr "$results_file" > "$results_file.sorted" && mv "$results_file.sorted" "$results_file"
fi

echo "Общая длительность: $(seconds_to_hms "$total_sec")"
echo " Файлов: $count"
[[ $errors -gt 0 ]] && echo " Ошибок: $errors"

echo

if $SHOW_FORMAT; then
  printf "%-10s  %-12s  %s\n" "Длительность" "Формат" "Имя файла"
  printf "%-10s  %-12s  %s\n" "----------"   "------" "---------"
  while IFS=$'\t' read -r s fmt name; do
    hms=$(seconds_to_hms "$s")
    printf "%-10s  %-12s  %s\n" "$hms" "${fmt:-?}" "$name"
  done < "$results_file"
else
  printf "%-10s  %s\n" "Длительность" "Имя файла"
  printf "%-10s  %s\n" "----------" "---------"
  # читаем только 1-е и 3-е поле (имя), пропуская формат
  while IFS=$'\t' read -r s name; do
    hms=$(seconds_to_hms "$s")
    printf "%-10s  %s\n" "$hms" "$name"
  done < <(cut -f1,3- "$results_file")
fi

Извиняюсь, конечно, что не совсем по теме, но у меня на JS (WSH на Винде, с Висты, вроде), вот такое решение было и сейчас пользуюсь. Я к чему: не сталкивался тогда в Линуксе с подобной проблемой (если скачивал там, на винду перебрасывал сразу), но на Винде длительность видео можно выцепить через метод GetDetailsOf объекта "Shell.Application" ("Описание: возвращается информация, похожая на ту, что можно увидеть в окне свойств файла (папки) в проводнике"). И повторюсь, насколько помню (давно было), из Линукса данные передавались уже с этими свойствами файла. Скрипт своеобразный - рассчитан под нумерованные папки (оцифровывал VHS кассеты изначально, теперь только при скачивании через yt-dlp использую - он умеет разбивать на фрагменты почти хорошо, а с нужными параметрами почти идеально), но легко поправить. Может, без ffprobe можно и на линуксе обойтись? Давно с ним не работал.

На винде и правда можно сразу брать длительность файлов из метаданных файла благодаря их свойств, но на Linux такого нету(

И повторюсь, насколько помню (давно было), из Линукса данные передавались уже с этими свойствами файла

Я поискал, но люди пишут что из-за такой файловой системы метаданные не передаются так же как в Windows.

Может, без ffprobe можно и на линуксе обойтись? Давно с ним не работал.

Зависимость все равно придется ставить (без парсинга файла, для получение метаданных из контейнера, не обойтись), но как я уже понял, ffprobe так себе вариант из-за скорости работы.
В будущем перепишу на более быструю утилиту.

Буквально вчера нужно было уточнить длительность файлов, в Nemo не отображается, в Thunar долго (Свойства -> Аудио -> Информация) и только отдельно по файлам. В Проводнике Windows с подобным намного проще.

Скачал. Заметил достаточно большую задержку при обработке даже с локального диска: в папке 9 файлов, вывод через несколько секунд. Или у меня одного так?

P.S. Полезно, несомненно. В линуксах часто проще самому написать сценарий, нежели искать нужные программы, которых может и не быть вовсе.

Да, задержка из-за ffrobe большая. Буду переписывать

Обновление!
- Исправлено множество багов
- Добавлено еще больше популярных форматов
- Исправление вывода
- Улучшенный детальный анализ

В скором будущем перепишу с ffprobe, потому что он работает крайне медленно, и добавлю несколько важных фишек.

Отпишитесь в комментариях? Скрипт полезен весьма, благодарю!

Как-то писал себе скрипт по перекодированию медиа файлов через ffmpeg, и тоже столкнулся с проблемой определения длины, не помню точно, но как-то решил (какой-то другой способ работал очень медленно, может это как раз ffprobe). Скрипт только на домашнем ПК, а дома я буду через неделю, посмотрю и сравню. Прогресс в итоге я стал определять тупо по данным из лога ffmpeg, там он пишет какой кадр и сколько всего. Да, тоже всë на bash.

Sign up to leave a comment.

Articles