Скачиваем аудио вконтакте через клиентский js или расширение файлов .m3u8

    Как все начиналось...


    Как всегда, зависая вконтакте, я решил скачать пару новых аудиозаписей на комп. Но меня ждало разочарование: аудиозаписи возвращались в каком-то странном формате: m3u8. Этот формат даже vlc media pleyer не воспроизводил, и я стал думать, что делать…

    Что собственно то делать?


    Погуглив, что это собственно за формат такой .m3u8, я понял, что это аудио в формате .m3u. Отлично, скачиваем этот файл .m3u8, открываем с помощью текстового редактора и видим примерно вот такой текст:

    Текст
    #EXTM3U
    #EXT-X-TARGETDURATION:3
    #EXT-X-ALLOW-CACHE:YES
    #EXT-X-PLAYLIST-TYPE:VOD
    #EXT-X-VERSION:3
    #EXT-X-MEDIA-SEQUENCE:1
    #EXTINF:1.000,
    6cfGpgIDcrZjA.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXTINF:2.000,
    c2d2tpKzIsYzM.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXTINF:3.000,
    a3fWlvLDwmZD4.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXTINF:3.000,
    edeWZhKTUnazE.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXTINF:3.000,
    9df2NqJzcmZj0.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXT-X-ENDLIST
    


    Дальше понимаем, что

    9df2NqJzcmZj0.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh

    — путь к аудиозаписи. То есть надо подставить к каждому пути хост и, скорее всего, это аудио станет проигрываемым. Быстренько набросав на питоне пару строк кода реализуем это:

    Код
    import re
    text='''#EXTM3U
    #EXT-X-TARGETDURATION:3
    #EXT-X-ALLOW-CACHE:YES
    #EXT-X-PLAYLIST-TYPE:VOD
    #EXT-X-VERSION:3
    #EXT-X-MEDIA-SEQUENCE:1
    #EXTINF:1.000,
    6cfGpgIDcrZjA.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXTINF:2.000,
    c2d2tpKzIsYzM.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXTINF:3.000,
    a3fWlvLDwmZD4.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXTINF:3.000,
    edeWZhKTUnazE.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXTINF:3.000,
    9df2NqJzcmZj0.ts?extra=0F4d1n-wWV6igsS5Ji7x6gYIbtU_aRzsiByqvrumv4W1iznLLoiC552LnsmyKeuuOtw70WTqfYdDCir-nmlL3VlLR9i2Y6IPOudQxWPbZjlslXE7prmIvdLyoLxb3A9NFnHo2KR5NStPg1sk6ZVXrYBh
    #EXT-X-ENDLIST'''
    host='https://cs9-5v4.vkuseraudio.net/p16/d5fce44eae6dbc/'
    al = re.findall('\n.+?\.ts\?extra\=.+?\n',text)
    for r in al:
        text=text.replace(r,'\n'+host+r.strip('\n')+'\n')
    
    print(text)
    input()
    


    и получаем уже играбельный .m3u файл.

    НО! Проблема в том, что:

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

    Поняв все это я решил написать программу на клиентском js, чтобы ее можно было исполнять в командной строке. Вот что у меня получилось:

    Программа
    class music_get{
        constructor(){
            this.el=document.getElementsByClassName("audio_row");
            this.str_param=[];
            this.json=[];
            this.last_len=this.el.length;
            this.make_str(this.el);    
            this.load_audio_to_json(0,this.str_param[0]);
        }
        parse(element_){
            //функция, возвращающая id аудио, который надо передать в запросе, чтобы получить ссылку.
            //let i=JSON.parse(element_.attributes['data-audio'].nodeValue),s1=i[13].split("/");
            //return i[1]+"_"+i[0]+"_"+s1[2]+"_"+s1[s1.length-2];
            let i = AudioUtils.asObject(JSON.parse(element_.getAttribute('data-audio')));
            return i.fullId+"_"+i.actionHash+"_"+i.urlHash;
        }
        encode_url(t) {
            //функция, декодирующая ссылку на аудиозапись
            let c = {v:(t)=> { return t.split('').reverse().join('')},r: (t, e) => {t = t.split('');for (let i, o = _ + _, a = t.length; a--; ) ~(i = o.indexOf(t[a])) && (t[a] = o.substr(i - e, 1));return t.join('')},
                 s: (t,e)=> { let i = t.length;if (i) { let o = function(t, e) {let i = t.length,o = [];if (i) {let a = i;for (e = Math.abs(e); a--; ) e = (i * (a + 1) ^ e + a) % i,o[a] = e }return o}(t, e), a = 0;for (t = t.split(''); ++a < i; ) t[a] = t.splice(o[i - 1 - a], 1, t[a]) [0];t = t.join('')}return t},
                 i:(t, e)=> {return c.s(t, e ^ vk.id)},x: (t, e)=> {let i = [];return e = e.charCodeAt(0),each(t.split(''), (t, o) => {i.push(String.fromCharCode(o.charCodeAt(0) ^ e))}),i.join('')}
            },_ = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789+/=',h=(t)=>{ if (!t || t.length % 4 == 1) return !1;for (var e, i, o = 0, a = 0, s = ''; i = t.charAt(a++); ) ~(i = _.indexOf(i)) && (e = o % 4 ? 64 * e + i : i, o++ % 4) && (s += String.fromCharCode(255 & e >> ( - 2 * o & 6)));return s};
            if ((!window.wbopen || !~(window.open + '').indexOf('wbopen')) && ~t.indexOf('audio_api_unavailable')) { 
            let e = t.split('?extra=')[1].split('#'),i=''===e[1]?'':h(e[1]);
            if (e = h(e[0]), 'string' != typeof i || !e) return t;for (var o, a, s = (i = i ? i.split(String.fromCharCode(9))  : []).length; s--; ) {if (o = (a = i[s].split(String.fromCharCode(11))).splice(0, 1, e) [0], !c[o]) return t; e = c[o].apply(null, a)}if (e && 'http' === e.substr(0, 4)) return e}return t
        }
        end(){
            //для каждой аудиозаписи в html код добавляем кнопку
            each(this.json,(_,item)=>{
                let els = document.querySelectorAll('[data-full-id="'+item.fullId+'"]')[0];
                if(els.children[0].children[6].children.length===3)return;
                els.children[0].children[6].innerHTML+="<div onclick='new music_download().download(this);' style='float:right;height:40px;width:40px;background:url(/doc472427950_504561254) no-repeat 5px 5px;'></div>"            
                els.children[0].children[6].children[2].attributes.info=item;
                
            });
        }
        make_str(mass){
            //функция, добавляющая в массив str_param строки с id аудио, которые будут передаваться в запросе.
            each(mass,(i,e)=>{
                if(Math.floor(i/10)===i/10)
                    this.str_param.push(this.parse(e));
                else
                    this.str_param[this.str_param.length-1]+=","+this.parse(e);
            });
        }
        load_audio_to_json(i,l){
            //посылаем запрос на сервер вк, в котором в ответ приходит массив с аудио,
            //каждый элемент которого мы добавляем в массив this.json
            ajax.post("/al_audio.php",{act:'reload_audio',al:'1',ids:l},{onDone:(a)=>{
                //each - функция, которая есть на сайте vk.com - похожа на array.forEach
                each(a,(_,c)=>{
                    c=AudioUtils.asObject(c);
                    //ну естественно декодируем ссылку, как же без этого)
                    c.url = this.encode_url(c.url);
                    this.json.push(c);
                });
                //рекурсия
                if(this.str_param.length-1===i) this.end();
                else this.load_audio_to_json(i+1,this.str_param[i+1]);
    
            }});
        }
        _update_scroll(){    
            //функция, вызывающаяся при скролле страницы.
            if(this.el.length===this.last_len)return;
            
            let c = this.el.length,offset=c-this.last_len;
            this.last_len=c;
            let arr = Array.from(this.el).splice(-offset);
            this._load_button(arr);
            
        }
        _load_button(list){
            //функция, которая подгружает новые кнопки.
            let leng=this.str_param.length-1;
            this.make_str(list);
            this.load_audio_to_json(leng,this.str_param[leng]);
        }
    } 
    class music_download{
        //constructor(){}
        download(e){    
            this.info = e.attributes.info;
            //если формат аудио - .mp3, то просто открываем ссылку в новом окне
            if(this.info.url.indexOf(".mp3?")!==-1)
                window.open(this.info.url);
            else 
                //с недавнего времени вк стало поддерживать формат .m3u8, который является аудиоплейлистом(текстом), 
                //в котором содержатся ссылки на промежутки аудио .ts, но ссылки без хоста. 
                //Исправим это следуюшей функцией response:
                fetch(this.info.url).then((e)=>e.text().then((e)=>this.response(e)));
        }
        response(data){
            let alls = data.match(/\n.+?\.ts\?/ig), host=this.info.url.split("index.m3u8")[0];
            each(alls,(_,e)=>data=data.replace(e,"\n"+host+e.replace('\n','')));
            //скачиваем полученный файл
            this.download_data(this.info.title.replace(/[-\/\\:*?"<>|]/gim,'')+".m3u8",data);
        
        }
        download_data(f_n, t) {
            let e = document.createElement('a');
            e.setAttribute('href', //'data:text/plain;charset=utf-8,'
                    'data:text/html;base64,'+ btoa(t));
            e.setAttribute('download', f_n);
            e.style.display = 'none';
            document.body.appendChild(e);
            e.click();
            document.body.removeChild(e);
        }
    
    }
    var mus = new music_get();
    //функция скролла
    window.onscroll=()=>mus._update_scroll();

    В результате получается примерно вот, что:


    Дальше, скачав все аудио в одну папку, я написал следующий код на питоне, чтобы конвертировать все аудио .m3u8 в .ts:

    Код
    import requests,re,os
    def convert_mp3(f):
        z=open(f).read()
        con=list(map(lambda e: e.rstrip('\n').rstrip('#EXT-X-ENDLIST').rstrip("\n") if '#EXTM3U' not in e else '' ,re.split('#EXTINF:\d+.\d+,\n',z)))
        z = b''
        for r in con:
            if(r==''):continue;
            z+=requests.get(r).content
        open(f.strip(".m3u8")+".ts",'bw').write(z)
    z=set()
    for file in os.listdir():
        if file.endswith(".m3u8"):
            z.add(file)
            convert_mp3(file)
    z=',\n'.join(z)
    input(f"Файлы:{z} переконвертированны.\nНажмите Enter, чтобы выйти!")
    


    Впринципе, можно попытаться соединить отрывки .ts на js и потом скачать весь файл, но у меня не получилось (

    P.S. у кого получится — пишите в комментарии
    P.P.S. Забыл сказать, что яндекс.браузер до сих пор возвращает ссылки на .mp3)

    Update:


    Как правильно заметил nokimaro можно скачивать сразу mp3:
    код
    class music_get{
        constructor(){
            this.el=document.getElementsByClassName("audio_row");
            this.s=[];
            this.j=[];
            this.last_len=this.el.length;
            this.make_str(this.el);    
            this.load_audio_to_json(0,this.s[0]);
        }
        parse(element_){
            //функция, возвращающая id аудио, который надо передать в запросе, чтобы получить ссылку.
            //let i=JSON.parse(element_.attributes['data-audio'].nodeValue),s1=i[13].split("/");
            //return i[1]+"_"+i[0]+"_"+s1[2]+"_"+s1[s1.length-2];
            let i = AudioUtils.asObject(JSON.parse(element_.getAttribute('data-audio')));
            return i.fullId+"_"+i.actionHash+"_"+i.urlHash;
        }
        encode_url(t) {
            //функция, декодирующая ссылку на аудиозапись
            let c = {v:(t)=> { return t.split('').reverse().join('')},r: (t, e) => {t = t.split('');for (let i, o = _ + _, a = t.length; a--; ) ~(i = o.indexOf(t[a])) && (t[a] = o.substr(i - e, 1));return t.join('')},
                 s: (t,e)=> { let i = t.length;if (i) { let o = function(t, e) {let i = t.length,o = [];if (i) {let a = i;for (e = Math.abs(e); a--; ) e = (i * (a + 1) ^ e + a) % i,o[a] = e }return o}(t, e), a = 0;for (t = t.split(''); ++a < i; ) t[a] = t.splice(o[i - 1 - a], 1, t[a]) [0];t = t.join('')}return t},
                 i:(t, e)=> {return c.s(t, e ^ vk.id)},x: (t, e)=> {let i = [];return e = e.charCodeAt(0),each(t.split(''), (t, o) => {i.push(String.fromCharCode(o.charCodeAt(0) ^ e))}),i.join('')}
            },_ = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789+/=',h=(t)=>{ if (!t || t.length % 4 == 1) return !1;for (var e, i, o = 0, a = 0, s = ''; i = t.charAt(a++); ) ~(i = _.indexOf(i)) && (e = o % 4 ? 64 * e + i : i, o++ % 4) && (s += String.fromCharCode(255 & e >> ( - 2 * o & 6)));return s};
            if ((!window.wbopen || !~(window.open + '').indexOf('wbopen')) && ~t.indexOf('audio_api_unavailable')) { 
            let e = t.split('?extra=')[1].split('#'),i=''===e[1]?'':h(e[1]);
            if (e = h(e[0]), 'string' != typeof i || !e) return t;for (var o, a, s = (i = i ? i.split(String.fromCharCode(9))  : []).length; s--; ) {if (o = (a = i[s].split(String.fromCharCode(11))).splice(0, 1, e) [0], !c[o]) return t; e = c[o].apply(null, a)}if (e && 'http' === e.substr(0, 4)) return e}return t
        }
        end(){
            //для каждой аудиозаписи в html код добавляем кнопку
            each(this.j,(_,i)=>{
                let e = document.querySelectorAll('[data-full-id="'+i.fullId+'"]')[0],q=e.children[0].children[6];
                if(q.children.length===3)return;
                q.innerHTML+="<a href='"+this._g(i)+"' target='_blank' style='float:right;height:40px;width:40px;background:url(/doc472427950_504561254) no-repeat 5px 5px;'></a>"            
                
            });
        }
    	_g(info){    
    		if(info.url.indexOf(".mp3?")!==-1)
    			return info.url;
    		else 
    			return info.url.replace("/index.m3u8",".mp3").replace(/\/\w{11}\//,'/');
    	}
        make_str(mass){
            //функция, добавляющая в массив s строки с id аудио, которые будут передаваться в запросе.
            each(mass,(i,e)=>{
                if(Math.floor(i/10)===i/10)
                    this.s.push(this.parse(e));
                else
                    this.s[this.s.length-1]+=","+this.parse(e);
            });
        }
        load_audio_to_json(i,l){
            //посылаем запрос на сервер вк, в котором в ответ приходит массив с аудио,
            //каждый элемент которого мы добавляем в массив this.j
            ajax.post("/al_audio.php",{act:'reload_audio',al:'1',ids:l},{onDone:(a)=>{
                //each - функция, которая есть на сайте vk.com - похожа на array.forEach
                each(a,(_,c)=>{
                    c=AudioUtils.asObject(c);
                    //ну естественно декодируем ссылку, как же без этого)
                    c.url = this.encode_url(c.url);
                    this.j.push(c);
                });
                //рекурсия
                if(this.s.length-1===i) this.end();
                else this.load_audio_to_json(i+1,this.s[i+1]);
    
            }});
        }
        _update_scroll(){    
            //функция, вызывающаяся при скролле страницы.
            if(this.el.length===this.last_len)return;
            
            let c = this.el.length,offset=c-this.last_len;
            this.last_len=c;
            let arr = Array.from(this.el).splice(-offset);
            this._load_button(arr);
            
        }
        _load_button(list){
            //функция, которая подгружает новые кнопки.
            let leng=this.s.length-1;
            this.make_str(list);
            this.load_audio_to_json(leng,this.s[leng]);
        }
    } 
    
    
    //функция скролла
    var m = new music_get();
    window.onscroll=()=>m._update_scroll();
    
    

    сжатый код:
    eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('8 p=7(){5.y=O.1a("1b");5.s=[];5.j=[];5.C=5.y.o;5.J(5.y);5.D(0,5.s[0])};p.q.E=7(a){a=P.Q(1c.E(a.1d("R-1e")));n a.S+"T"+a.1f+"T"+a.1g};p.q.U=7(a){8 b={v:7(a){n a.t("").1h().F("")},r:7(a,b){a=a.t("");A(8 k=1i 0,e=d+d,c=a.o;c--;)~(k=e.B(a[c]))&&(a[c]=e.V(k-b,1));n a.F("")},s:7(a,b){8 c=a.o;u(c){8 e=b,d=a.o,f=[];u(d){8 g=d;A(e=W.1j(e);g--;)e=(d*(g+1)^e+g)%d,f[g]=e}e=0;A(a=a.t("");++e<c;)a[e]=a.K(f[c-1-e],1,a[e])[0];a=a.F("")}n a},i:7(a,d){n b.s(a,d^1k.X)},x:7(a,d){8 b=[];n d=d.Y(0),G(a.t(""),7(a,c){b.L(H.I(c.Y(0)^d))}),b.F("")}},d="1l+/=",c=7(a){u(!a||1==a.o%4)n!1;A(8 b,c,e=0,g=0,f="";c=a.1m(g++);)~(c=d.B(c))&&(b=e%4?1n*b+c:c,e++%4)&&(f+=H.I(1o&b>>(-2*e&6)));n f};u((!M.Z||!~(M.1p+"").B("Z"))&&~a.B("1q")){8 f=a.t("?1r=")[1].t("#"),g=""===f[1]?"":c(f[1]);u(f=c(f[0]),"1s"!=1t g||!f)n a;A(8 h,l=(g=g?g.t(H.I(9)):[]).o;l--;){u(h=(c=g[l].t(H.I(11))).K(0,1,f)[0],!b[h])n a;f=b[h].1u(1v,c)}u(f&&"1w"===f.V(0,4))n f}n a};p.q.12=7(){8 a=5;G(5.j,7(b,d){8 c=O.1x(\'[R-1y-X="\'+d.S+\'"]\')[0].N[0].N[6];3!==c.N.o&&(c.1z+="<a 1A=\'"+a.13(d)+"\' 1B=\'1C\' 1D=\'1E:1F;1G:14;1H:14;1I:z(/1J) 1K-1L 15 15;\'></a>")})};p.q.13=7(a){n-1!==a.z.B(".16?")?a.z:a.z.17("/1M.1N",".16").17(/\\/\\w{11}\\//,"/")};p.q.J=7(a){8 b=5;G(a,7(a,c){W.1O(a/10)===a/10?b.s.L(b.E(c)):b.s[b.s.o-1]+=","+b.E(c)})};p.q.D=7(a,b){8 d=5;1P.1Q("/1R.1S",{1T:"1U",1V:"1",1W:b},{1X:7(b){G(b,7(a,b){b=P.Q(b);b.z=d.U(b.z);d.j.L(b)});d.s.o-1===a?d.12():d.D(a+1,d.s[a+1])}})};p.q.18=7(){u(5.y.o!==5.C){8 a=5.y.o,b=a-5.C;5.C=a;a=1Y.1Z(5.y).K(-b);5.19(a)}};p.q.19=7(a){8 b=5.s.o-1;5.J(a);5.D(b,5.s[b])};8 m=20 p;M.21=7(){n m.18()};',62,126,'|||||this||function|var|||||||||||||||return|length|music_get|prototype|||split|if||||el|url|for|indexOf|last_len|load_audio_to_json|parse|join|each|String|fromCharCode|make_str|splice|push|window|children|document|AudioUtils|asObject|data|fullId|_|encode_url|substr|Math|id|charCodeAt|wbopen|||end|_g|40px|5px|mp3|replace|_update_scroll|_load_button|getElementsByClassName|audio_row|JSON|getAttribute|audio|actionHash|urlHash|reverse|void|abs|vk|abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN0PQRSTUVWXYZO123456789|charAt|64|255|open|audio_api_unavailable|extra|string|typeof|apply|null|http|querySelectorAll|full|innerHTML|href|target|_blank|style|float|right|height|width|background|doc472427950_504561254|no|repeat|index|m3u8|floor|ajax|post|al_audio|php|act|reload_audio|al|ids|onDone|Array|from|new|onscroll'.split('|'),0,{}))

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

    Как вы скачиваете музыку с вконтакте?

    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

      +3
      Можно было так не усложнять, и пока-что есть возможность из ссылки на файл m3u8 получить ссылку на mp3.
        +3
        Это не какой-то странный формат, это — hls, http live streaming.
          0
          m3u8 воспроизводят и VLC и несколько десятков других популярных плееров (включая, кажется, даже стандартный проигрыватель windows)
            +1
            m3u/m3u8 использовался как стандарт для плейлистов winamp-а еще в далеких нулевых, если ничего не путаю
            0

            В мобильной версии сайта выдаются ссылки на mp3.

              0

              Вообще во многих мобильных версиях чего угодно допускаются всяческие поблажки в "безопасности". Взять тот же авито, который прячет номер телефона в картинке в обычной версии сайта. Но в мобильной эта картинка внезапно становится ссылкой с "href=tel:...". На юле, кажется, тоже самое, но точно не помню. Думаю у каждого найдется ещё по примеру подобных вещей.

              +1

              Попробуйте такой вариант с ffmpeg:


              ffmpeg -i https://example.org/stream.m3u8 -c copy out.ts

              Ну а если нужен mp3 то выше уже подсказали как его получить.

                0
                Писал своё приложение для загрузки этих вот потоковых m3u8-ts, но с аудио вк возник затык с тем, чтобы из ts перегнать в mp3 без пережатия. FFmpeg отказывается это делать.
                  0
                  я конвертировал это с помощью vlc media pleyer'a
                    0
                    Как это сделать без перекодирования?
                    0

                    В ts звук вроде кодируется в AAC поэтому в mp3 без перекодирования не перегнать.

                      0
                      Вроде бы контейнер не накладывает ограничений на формат сжатия содержимого.
                      Mediainfo про ts, скачанные с вк, говорит, что внутри mp3.
                        0

                        Надо смотреть что пишет ffmpeg. Провёл эксперимент. Загнал mp3 в ts и обратно в mp3 без перекодирования (-c copy). Всё нормально прошло.

                          +1
                          Да, мой косяк. Но всё интереснее на самом деле. Я проверял на одном треке и не обратил внимание, что скачанный ts толком не проигрывается, один скрежет. Попробовал скачать другие — всё хорошо, и ffmpeg в mp3 их перегоняет без вопросов.
                          В чем же разница? А в том, что у первого в m3u8 перед половиной фрагментов идёт строчка
                          #EXT-X-KEY:METHOD=AES-128,URI="https://psv4.vkuseraudio.net/c521401/u26908367/ad39a2fde/audios/595ebdc0ccd6/key.pub?extra=bqGmxzhqMNM8OyEz93EpIygTSFPBPGmJTRHCGQg_b6IGBFTRLAdQlqCRpyEne_OOklLhA4nT5xhefbCh29ACP1bkfOl4Nwwc24qCEdELbZt1oK2NgIhZz-LNSDIuXSPqxf5256HuVwrJDlx2a_0" (по ссылке файл с ключом)
                          А перед второй половиной #EXT-X-KEY:METHOD=NONE
                          Судя по всему половина фрагментов шифруется. Причем с чередованием, один фрагмент шифрованный, следующий нет.
                            –1
                            А есть какая-то библиотека для расшифровки AES-128 с помощью файла ключа? А то вдруг эту «фичу» с вытаскиванием mp3 уберут, хотелось бы сделать более надёжное решение.

                            На самом деле, там ещё нужна перепаковка, если мы хотим из ts mp3 получить, как я понял, почитав статью на Вики.

                            Но у меня проблемы возникли ещё раньше: почему-то при попытке создать Blob из массива других Blob-ов с целью склеить фрагметы потока получается какая-то шляпа очень маленького размера в 1,65 Кб (хотя это совершенно штатная возможность, если верить документации и StackOverflow). Каждый фрагмент трека, если что, весит 127-130 Кб, поэтому я вообще в душе не понимаю, что происходит.

                            Вот мой код если что
                            if (url.match(/\.m3u8$/)) {
                                var xhr = new XMLHttpRequest()
                                xhr.open('GET', url, true)
                                xhr.onload = function() {
                                    var data = xhr.responseText.split(/\n/)
                                    var base = ''
                                    var parts = []
                                    var p = []
                                    data.forEach(function(el) {
                                        var m = el.match(/URI="([^"]+)"/)
                                        if (m) {
                                            base = m[1].split(/\//).slice(0, -1).join('/')
                                        } else {
                                            m = el.match(/\.ts\??/)
                                            if (m) parts.push(el.slice(0, -1))
                                        }
                                    })
                                    //console.log(base)
                                    function fetchNext() {
                                        var req = new XMLHttpRequest()
                                        req.open('GET', base + '/' + parts[index], true)
                                        req.responseType = 'blob'
                                        req.onload = function() {
                                            p[index] = req.response
                                            index++
                                            //console.log(req.response)
                                            if (index < parts.length) fetchNext()
                                            else saveStream()
                                        }
                                        req.send(null)
                                    }
                            
                                    var index = 0
                                    fetchNext()
                            
                                    function saveStream() {
                                        var blob = new Blob(parts, {type : 'audio/m2ts'})
                                        var url = URL.createObjectURL(blob)
                                        var a = document.createElement('a')
                                        a.download = getFileName().split('.')[0] + '.ts'
                                        a.href = url
                                        document.body.appendChild(a)
                                        a.click()
                                        document.body.removeChild(a)
                                    }
                                }
                                xhr.send(null)
                            }

                    –2

                    А в чем смысл? Любое расширение браузера спокойно скачивает в mp3.

                      +1
                      Смысл в том, чтобы незнающие люди поняли, КАК эти расширения работают
                      0
                      А чего в тегах Python делает?
                        0
                        >P.P.S. Забыл сказать, что яндекс.браузер до сих пор возвращает ссылки на .mp3)

                        Я дебажил код и это зависит от UserAgent параметра в HTTP header. Тоесть если вы поставите такойже как у яндекс.браузера то будет возвращать тоже mp3 ссылку, но думаю это временное решение так я делал эмулируя FireFox и спустя неделю это перестало работать и стали приходить ссылки m3u8 так что скорее всего и для яндекс.браузера скоро будут m3u8.

                        Я вот не могу понять две вещи
                        1) если я правильно понимаю этот m3u8 указывает на аудиофайл разделенный на куски. В каком формате этот файл? в mp3 или чтото другое? Как соберать файл по этим кускам?

                        2) ктото понял что за шифрование AES-128? там первым параметром приходит какойто ключ и ко всем последующим ссылкам которые есть в m3u8 добавляються около 60 символов видимо это и есть защита сайта.

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

                        Самое читаемое