Введение
На днях у меня появилась потребность сохранить видео лекции с некоторого сайта, которой такой функциональности не представляет, а причиной этой необходимости послужило то, что после определенного времени с момента оплаты видео становится не доступным.
Анализ
Первое что пришло мне в голову это записать видео с экрана, например через QuickTime, но в таком случае нужно все их просмотреть а это по времени достаточно долго и этот вариант мне не понравился.
Следующим шагом было исследовать html код страницы, и там я нашел интересную ссылку:

Далее я попробовал подставить эту ссылку в адресную строку браузера и в результате скачался файл master.m3u8 следующего содержания:
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=768000,CODECS="mp4a.40.2, avc1.640028",RESOLUTION=640x360
https://vh-04.getcourse.ru/player/89cc32eeb182080b719abfc7f106aaaf/5b7008b4e8fab9852419a5c00e4aa618/media/360.m3u8?sid=sid&host=vh-04&cdn=1&cdn-second=0&integros-s3=1&akamai-defence=0&v=2:0:1:1
#EXT-X-STREAM-INF:BANDWIDTH=1024000,CODECS="mp4a.40.2, avc1.640028",RESOLUTION=853x480
https://vh-04.getcourse.ru/player/89cc32eeb182080b719abfc7f106aaaf/5b7008b4e8fab9852419a5c00e4aa618/media/480.m3u8?sid=sid&host=vh-04&cdn=1&cdn-second=0&integros-s3=1&akamai-defence=0&v=2:0:1:1
#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS="mp4a.40.2, avc1.640028",RESOLUTION=1280x720
https://vh-04.getcourse.ru/player/89cc32eeb182080b719abfc7f106aaaf/5b7008b4e8fab9852419a5c00e4aa618/media/720.m3u8?sid=sid&host=vh-04&cdn=1&cdn-second=0&integros-s3=1&akamai-defence=0&v=2:0:1:1
#EXT-X-STREAM-INF:BANDWIDTH=4096000,CODECS="mp4a.40.2, avc1.640028",RESOLUTION=1920x1080
https://vh-04.getcourse.ru/player/89cc32eeb182080b719abfc7f106aaaf/5b7008b4e8fab9852419a5c00e4aa618/media/1080.m3u8?sid=sid&host=vh-04&cdn=1&cdn-second=0&integros-s3=1&akamai-defence=0&v=2:0:1:1
Как можно увидеть этот файл содержит еще набор ссылок, и судя по их названию они обозначают разрешение видео, которое загружается.
Я решил посмотреть что из себя представляет файл 720.m3u8, для этого я его скачал, перейдя по соответствующей ссылке:
#EXTM3U
#EXT-X-TARGETDURATION:11
#EXT-X-ALLOW-CACHE:YES
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.160000,
https://getcourse-cdn-a1a5df3e.cdn.integros.com/ch/89cc32eeb182080b719abfc7f106aaaf/5b7008b4e8fab9852419a5c00e4aa618/720/0.ts?sid=sid&host=vh-04
#EXTINF:10.080000,
https://getcourse-cdn-a1a5df3e.cdn.integros.com/ch/89cc32eeb182080b719abfc7f106aaaf/5b7008b4e8fab9852419a5c00e4aa618/720/1.ts?sid=sid&host=vh-04
#EXTINF:10.080000,
https://getcourse-cdn-a1a5df3e.cdn.integros.com/ch/89cc32eeb182080b719abfc7f106aaaf/5b7008b4e8fab9852419a5c00e4aa618/720/2.ts?sid=sid&host=vh-04
#EXTINF:10.080000,
...
#EXTINF:8.800000,
https://getcourse-cdn-a1a5df3e.cdn.integros.com/ch/89cc32eeb182080b719abfc7f106aaaf/5b7008b4e8fab9852419a5c00e4aa618/720/119.ts?sid=sid&host=vh-04
#EXT-X-ENDLIST
Если присмотреться внимательней то можно увидеть что все ссылки содержат *.ts, после того как я их скачал, оказалось что это как раз куски данного урока.
После небольшого поиска что-же такое m3u8 файл, я выяснил, что это формат для хранения плейлистов.
Теперь был вопрос как же его сохранить локально.
Сохранение видео из m3u8
Первое что я нашел в Интернете предлагало это сделать через VLC.
Для этого нужно зайти в Файл -> Конвертировать/Передавать
, после чего можно увидеть следующее окно:

В указывается исходный файл плейлиста, профиль сохранения и выходной файл. После чего нажимается кнопка Сохранить и процесс начинается (надо отметить, что процесс не быстрый).
Альтернативное сохранение
После того как таким способом я сохранил пару видео, я начал думать как оптимизировать процесс. Тут меня посетила мысль, что можно просто попробовать скачать все эти маленькие файлы и объединить их в один.
Для этого я написал небольшую утилитку на Go, для обработки файла *.m3u8 и объединения всех маленьких кусков из него в один файл.
Алгоритм работы ее достаточно прост: сначала я перехожу по заданной ссылке на m3u8, затем построчно обхожу его и если строка содержит URL кусочка, я скачиваю его и результат сразу пишу в выходной файл.
Код который это делает следующий:
// received file from server
resp, err := http.Get(inputUrl)
if err != nil {
log.Fatal("Download error: ", err)
}
defer resp.Body.Close()
// create output file
f, err := os.Create(outputFile)
if err != nil {
log.Fatal("Download error: ", err)
}
defer f.Close()
// read server response line by line
scanner := bufio.NewScanner(resp.Body)
i := 0
for scanner.Scan() {
l := scanner.Text()
// if line contains url address
if strings.HasPrefix(l, "http") {
// download file part
part, err := downloadFilePart(l)
if err != nil {
log.Fatal("Download part error: ", err)
}
// write part to output file
if _, err = f.Write(part); err != nil {
log.Fatal("Write part to output file: ", err)
}
log.Printf("Download part %d\n", i)
i++
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
Функция downloadFilePart
отвечает именно за скачивание файла и вывод его в байтовый массив:
func downloadFilePart(url string) ([]byte, error) {
result := make([]byte, 0)
resp, err := http.Get(url)
if err != nil {
return result, err
}
if result, err = ioutil.ReadAll(resp.Body); err != nil {
return result, err
}
return result, err
}
Такой метод полностью себя оправдал и оказался самый быстрый.
Заключение
В конечном счете я не только скачал все нужные лекции, но еще и создал утилиту для скачки и сохранения *.m3u8 файлов.
Для пользователей Winodws готовую сборку можно взять тут.