Разработчик
0,0
рейтинг
17 сентября 2010 в 04:00

Администрирование → Сохраняем видео из Flash Player 10.2 или unlink нам не помеха

Я, как, вероятно, и многие другие пользователи Linux, привык сохранять видео с сайтов вроде YouTube, копируя временные файлы, создаваемые Adobe Flash. Примерно так:
$ cp /tmp/FlashIBmQCU video.flv

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

…То есть решил посмотреть список открытых плагином файлов. Для начала нам нужен PID процесса, в котором хостится плагин. Пользуюсь я Firefox-ом, так что искать будем просто:
$ ps x | grep firefox
 9800 ?        S      0:00 /bin/sh /usr/lib/firefox-3.6.9/firefox
 9805 ?        S      0:00 /bin/sh /usr/lib/firefox-3.6.9/run-mozilla.sh /usr/lib/firefox-3.6.9/firefox-bin
 9809 ?        Sl    14:58 /usr/lib/firefox-3.6.9/firefox-bin
10099 ?        Sl     4:10 /usr/lib/firefox-3.6.9/plugin-container /usr/lib/mozilla/plugins/libflashplayer.so 9809 plugin
26199 pts/13   S+     0:00 grep firefox

Как видно, Mozilla запускает плагин в отдельном процессе и его PID — 10099. Теперь список открытых файлов можно посмотреть командой lsof:
$ lsof -p 10099
COMMAND     PID    USER   FD   TYPE             DEVICE     SIZE    NODE NAME
# поскипано 152 строки
plugin-co 10099 rooslan  mem    REG               8,21    26048    2656 /usr/lib/gconv/gconv-modules.cache
plugin-co 10099 rooslan  mem    REG               8,21      343  106080 /usr/lib/locale/ru_RU.utf8/LC_IDENTIFICATION
plugin-co 10099 rooslan    0r   CHR                1,3             3419 /dev/null
plugin-co 10099 rooslan    1w  FIFO                0,6             9649 pipe
plugin-co 10099 rooslan    2w  FIFO                0,6             9649 pipe
plugin-co 10099 rooslan    3u  unix 0xffff88007304a3c0           287192 socket
plugin-co 10099 rooslan    4r  0000                0,7        0      32 anon_inode
plugin-co 10099 rooslan    5w  unix 0xffff8800c5425e40           287277 socket
plugin-co 10099 rooslan    6r  unix 0xffff8800c5424b00           287278 socket
plugin-co 10099 rooslan    7w  FIFO                0,6           287279 pipe
plugin-co 10099 rooslan    8r  FIFO                0,6           287279 pipe
plugin-co 10099 rooslan    9w  FIFO                0,6           287280 pipe
plugin-co 10099 rooslan   10u  FIFO                0,6           287280 pipe
plugin-co 10099 rooslan   11u  FIFO                0,6           287281 pipe
plugin-co 10099 rooslan   12u  FIFO                0,6           287281 pipe
plugin-co 10099 rooslan   13u  unix 0xffff88007304a100           287284 socket
plugin-co 10099 rooslan   14u   REG               8,24   376832 1409239 /home/rooslan/.mozilla/firefox/xxxxxxxx.default/cert8.db
plugin-co 10099 rooslan   15w   REG               8,24    16384 1409240 /home/rooslan/.mozilla/firefox/xxxxxxxx.default/key3.db
plugin-co 10099 rooslan   16u   REG               8,23   494641      16 /tmp/FlashXXlm7mcU (deleted)
plugin-co 10099 rooslan   17u  FIFO                0,6           404625 pipe
plugin-co 10099 rooslan   18u  FIFO                0,6           404625 pipe
plugin-co 10099 rooslan   19r  FIFO                0,6           404626 pipe
plugin-co 10099 rooslan   20w  FIFO                0,6           404626 pipe
plugin-co 10099 rooslan   21r  unix 0xffff880015a2b9c0           404630 socket

Всё самое интересное оказалось в конце и сразу перед глазами, но для порядка попробуем отфильтровать открытые процессом обычные (regular) файлы. Вероятно, это можно сделать встроенными средствами lsof, но размеры man lsof быстро отбивают желание читать его для решения такой проходной задачи. Поэтому я предпочёл воспользоваться простым фильтром на AWK:
$ lsof -p 10099 | awk '$4 ~ /^[0-9]+/ && $5 == "REG"'
plugin-co 10099 rooslan   14u   REG               8,24   376832 1409239 /home/rooslan/.mozilla/firefox/xxxxxxxx.default/cert8.db
plugin-co 10099 rooslan   15w   REG               8,24    16384 1409240 /home/rooslan/.mozilla/firefox/xxxxxxxx.default/key3.db
plugin-co 10099 rooslan   16u   REG               8,23   494641      16 /tmp/FlashXXlm7mcU (deleted)

Сразу стало понятно, куда делся наш временный файл: плагин удалил (unlink) ссылку на файл из каталога, но оставил открытым его дескриптор. Таким образом файл перестал быть виден в файловой системе, но не исчез, и окончательно удалён он будет только когда закроется последний ссылающийся на него дескриптор.
Но как нам теперь достать содержимое файла, открытого лишь одним процессом? Очень просто, с помощью файловой системы procfs. Каталог /proc/$PID/fd содержит символьные ссылки на все открытые процессом PID дескрипторы.
$ ls -l /proc/10099/fd
итого 0
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 0 -> /dev/null
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 1 -> pipe:[9649]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 10 -> pipe:[287280]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 11 -> pipe:[287281]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 12 -> pipe:[287281]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 13 -> socket:[287284]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 14 -> /home/rooslan/.mozilla/firefox/xxxxxxxx.default/cert8.db
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 15 -> /home/rooslan/.mozilla/firefox/xxxxxxxx.default/key3.db
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 16 -> /tmp/FlashXXpOdDuF (deleted)
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 17 -> pipe:[396658]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 18 -> pipe:[396658]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 19 -> pipe:[396659]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 2 -> pipe:[9649]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 20 -> pipe:[396659]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 21 -> socket:[396663]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 3 -> socket:[287192]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 4 -> anon_inode:[eventpoll]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 5 -> socket:[287277]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 6 -> socket:[287278]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 7 -> pipe:[287279]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 8 -> pipe:[287279]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 9 -> pipe:[287280]

(Вот, кстати, ещё один способ посмотреть открытые процессом файлы, помимо lsof).
И, хотя readlink возвращает для некоторых из этих ссылок имена несуществующих файлов, из них можно спокойно читать (если права позволяют), чем мы и воспользуемся:
$ cp /proc/10099/fd/16 video.flv

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

UPD
kreon оформил эти действия в виде скрипта (я позволил себе немного модифицировать его, добавив аргумент):
#!/bin/sh
PID=`ps x | grep libflashplayer.so | grep -v grep | awk '{print $1}'`
FD=`lsof -p $PID | grep Flash | awk '{print $4}' | sed 's/u^//'`
cp /proc/$PID/fd/$FD "$1"

Использование:
$ saveflash.sh coolvideo.flv

UPD 2
В комментариях неоднократно указали на нерациональность такого способа и предложили взамен разнообразные программы и плагины браузеров для скачивания видео. Конечно, для скачивания с YouTube удобнее будет воспользоваться ими (хотя надо ещё посмотреть, не поломалась ли у них из-за вышеописанного возможность доставать видео из кеша браузера). Однако все эти плагины заточены на конкретный, пусть и достаточно большой, список видеохостингов. Данный же способ позволяет достать видео практически всегда, если видео загружается по HTTP, а не используется RTMP-стриминг.
Руслан Хайров @khayrov
карма
87,8
рейтинг 0,0
Разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Администрирование

Комментарии (53)

  • 0
    Большое спасибо, работает (Ubuntu 10.04 amd64)
  • 0
    Может достаточно пользоваться плагинами-сохранялками?
    Этот, например, достаточно умный, чтобы брать из кеша, когда оно есть
    • +2
      Во-во. Давно уже не страдаю такими извращениями. Мой выбор — Video DownloadHelper.
      • +27
        Вот посмотрели вы ролик на ютубе, понравился, решили сохранить его к себе на жесткий диск. Video DownloadHelper будет скачивать его заново, а автор предлагает решение без повторного скачивания.
        И… ну это же unix, здесь важен сам процесс.
        • 0
          DownloadHelper может так поступить, если качать начали видео с другим качеством, ведь ютуб их дает несколько. Возможно по-умолчанию настроено скачивание другого качества.
        • +1
          Я сам в Убунте сейчас сижу и мне процесс не важен, важен результат. Если видео просмотрено до конца, то DownloadHelper вытаскивает его из кэша без повторной закачки.
          • +1
            читайте внимательно — из файловой системы стандартными средствами не достать — потому как не видно его в кеше, если использовать новый флешплеер.

            возможно он даже и файл удаляет не при завершении закачки, а при самом ее начале.
            • 0
              возможно он даже и файл удаляет не при завершении закачки, а при самом ее начале.

              Именно так.
    • +13
      Это все прекрасно, но согласитесь, что данный метод можно применить не только для решения этой задачи. Мне показалось, что это больше совокупный обзор базовых команд и особенностей linux.
  • –5
    Расследования, конечно, дело хорошее и нужное. Но почему бы просто не пользовать FlashGot?
  • 0
    Шерлок Холмс однако
  • +28
    Простенько и со вкусом.
    Можно написать sh-скрипт для такого дела.
    Типа такого
    $ cat > saveflash.sh
    #!/bin/sh
    PID=`ps x|grep libflashplayer.so|grep -v grep|awk '{print $1}'`
    FD=`lsof -p $PID|grep Flash|awk '{print $4}'|sed 's/u^//'`
    cp /proc/$PID/fd/$FD ~/video.flv
    # EOF
    ^D
    • 0
      в статью бы добавить, весьма полезное решение.
      • +1
        Done.
    • +1
      Уберем 2 команды :)
      PID=`ps x | awk '/libflashplayer.so\ /{print $1}'`
      • 0
        как насчёт 1 команды — pgrep?
        • 0
          Спасибо. Век живи, век учись, а дураком помрёшь. :)
    • 0
      скрипт не будет работать когда открыто несколько флеш-роликов одновременно или запущено несколько инстансов плагинов.

      Примерно так можно получить список всех дескрипторов:

      pgrep libflashplayer.so | xargs -I PID find /proc/PID/fd -lname '/tmp/Flash*'


      После чего можно копировать все или смотреть mplayer`ом с vdpau акселерацией 8)
  • +20
    к своему стыду, не знал о lsof.
    Все же не перестаю удивляться красоте *nix систем — для решения простых задач (смонтировать образ диска, скопировать раздел, посмотреть список открытых процессом файлов, и т.п.) есть простые решения, не требующие установки какого либо доп. софта.
    • 0
      к моему стыду, я тоже не знал ни осуществовании lsof, ни о том, где хранятся ссылки на открытые процессом дескрипторы.

      Автору спасибо.
    • 0
      Ну, вообще-то lsof в Debian-based дистрибутивах вынесена в отдельный пакет, так что без (до)установки вряд ли просто так обойдётесь.
      • 0
        мм, я просто по своему Arch'у сделал вывод. У меня в стандартной комплектации есть lsof.
      • 0
        В любом случае, достаточно написать одну команду, чтобы установить этот пакет.
  • –4
    Ха, а в Windows у них так не получится, фиг они удалят открытый файл :)
    • 0
      В Windows даже без удаления файла к нему нет доступа пользователю даже на чтение (на NTFS, по крайней мере, пока с правами доступа не поколдуешь).
      • +1
        Unlocker'ом всегда копировал
      • +1
        Дело не в правах файловой системы. При открытии файла в винде процесс может его залочить от чтения и/или записи. Лок на чтение обычно не делают, а вот лок на запись сторонними процессами штука важная. Ну и при желании можно вообще файл не лочить.
  • +1
    Давно хотел узнать способ достать удаленный, но ещё не закрытый файл (безотносительно флеша), только до самостоятельных поисков руки не доходили. Как то так себе это и представлял. Спасибо за готовое решение.
  • +3
    есть еще в репозиториях хорошая утилита clive
    • 0
      а ещё YouTube-DL
      Currently supported sites are video.google.com, youtube, photobucket, dailymotion, and metacafe.

      Жаль и та, и другая написаны на скриптовых языках и требуют соответствующего интерпретатора, а в случае с clive ещё и нескольких недефолтных модулей.
      • +1
        cclive — версия clive написанная на с++.
        • 0
          занятно :)
          cclive использует в свою очередь libquvi, которая реализует выдирание ссылок через Lua скрипты
          непонятно, им что, регэкспов одних мало? %)
  • +1
    Пользуюсь Firebug — показывает что и откуда качается.
  • 0
    А в чём суть такого трюка? Файл остаётся на диске после удаления жёстких ссылок, если он ещё занят процессом?
    • +1
      Даже после удаления последней жесткой ссылки файл может ещё находиться на диске — если до удаления открыть файл на чтение и/или запись, то фс не покажет файл, но он ещё не удален, чем собственно автор и пользуется в статье.
  • 0
    Спасибо, хороший способ. Я же, в свою очередь, использую отличный питоновский скрипт youtube-dl
    • 0
      Актуально для youtube, разумеется :)
      • 0
        Не только, youtube-dl так же работает с Yahoo! Video и Dailymotion. Ещё я им с какого-то непонятного видеохостинга ролик успешно стянул.
  • +1
    А что будет в этом файле если я взял и вовремя просмотра перепрыгнул из начала в конец клипа? Ведь клип при этом скачивается не полностью.
    • 0
      видео с того места, где вы кликнули. HTTP content-range.
  • 0
    замечательная статья, спасибо. По отдельности об этих инструментах знал, а вот до такого необычного применения недодумался.
  • +3
    > awk '$4 ~ /^[0-9]+/ && $5 == «REG»'

    Не ищете в жизни легких путей? ;) Чем уже в данном случае grep не угодил?
    • +2
      Запись на awk явно показывает, что мы работаем с колонками таблицы. На написание этой команды мне понадобилось секунд 15, куда уж легче-то? awk лично для меня привычный инструмент, если кому-то проще одной регуляркой, никто не мешает.
  • –2
    как знакомство с возможностями линукса — статья замечательная, а вот как решение конкретной задачи(сохранить видео из флешплеера) намного проще и удобнее использовать плагины для браузеров(типа downloadhelper).

    еще есть замечательный сервис ru.savefrom.net/ — скармливаете ему ссылку на страницу с видео и получаете прямые линки на видео. правда для варианта достать из кеша видео этот способ не подойдет.
    • 0
      плагины и сервисы не всегда помогают, например видео отсюда presidium.arbitr.ru/ никакими плагинами не скачешь, или в кеше рыться или надо знать прямую ссылку на .mp4 файл, которой в коде страницы нет (я как то нашел такую ссылку окольными путями, но авторам ничего не мешает её засекретить).
      • +1
        в таких редких и тяжелых случаях, я обычно использую снифер хттп запросов и из них беру прямую ссылку на видео. но в большинстве случаев люди качают видео с публичных сервисов типа ютуба, вимео, рутуба итд, а с ними плагины-качалки справляются на ура.
  • 0
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Пожалуйста, перечитайте комментарии и update к топику.
  • 0
    Пользовался юзер-скриптами, но после обновления ютуба всё поломалось, сейчас пользуюсь www.dirpy.com/ кстати недавно тут топик был про него
  • 0
    Теперь, кажется, надо так (по крайней мере у меня только так работает)

    #!/bin/bash
    
    PID=`ps x | grep libflashplayer.so | grep -v grep | awk '{print $1}'`
    FD=`ls /proc/$PID/fd -l | grep tmp | awk '{print $8}'`
    cp /proc/$PID/fd/$FD $1
    
    
  • 0
    сейчас и это не работает

    ps x | grep libflashplayer.so | grep -v grep | awk '{print $1}' — получаю pid процесса
    FD=`ls /proc/$PID/fd — получаю вывод в виде 1 10 2 3 4 5…
    после grep tmp — пусто :(
    опять изменили что то рутюбовцы :(

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.