Обновить

Комментарии 27

ЗакрепленныеЗакреплённые комментарии

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

В скором будущем перепишу с 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" из статьи

Убрал!

НЛО прилетело и опубликовало эту надпись здесь

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

Попросил гпт переделать с помощью 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.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации