torrent-online 1.7.8 → 1.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +8 -8
- package/package.json +1 -1
- package/wtui.js +178 -76
- package/node_modules/deep-extend/CHANGELOG.md +0 -46
- package/node_modules/http-parser-js/CHANGELOG.md +0 -14
- package/node_modules/mime/CHANGELOG.md +0 -312
- package/node_modules/string2compact/node_modules/ipaddr.js/Changes.md +0 -20
- package/node_modules/which/CHANGELOG.md +0 -166
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Формат — [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/), версии — [Semantic Versioning](https://semver.org/lang/ru/).
|
|
4
4
|
|
|
5
|
+
## [1.7.10] — 2026-10-04
|
|
6
|
+
**RU:** На странице есть кнопка «Закрыть»: она гасит фоновый сервер и не удаляет кэш. Язык переключается кнопками RU и EN.
|
|
7
|
+
**EN:** The page has a Quit button. It stops the background server and does not delete the cache. The language switches with the RU and EN buttons.
|
|
8
|
+
- ➕ `POST /api/quit` останавливает процесс. Скачанное в `~/Movies/WebTorrent` остаётся
|
|
9
|
+
- ➕ Переключатель языка в шапке и в плеере. Выбор запоминается в браузере. По умолчанию русский
|
|
10
|
+
|
|
11
|
+
## [1.7.9] — 2026-10-04
|
|
12
|
+
**RU:** В браузере звук больше не идёт рывками и не отстаёт от картинки.
|
|
13
|
+
**EN:** In the browser, audio no longer stutters or drifts off the picture.
|
|
14
|
+
- 🐛 Поток резался на MPEG-TS, и каждый кусок обнулял время. Браузер ставил звук не на ту секунду. Теперь фрагменты fMP4 с одной шкалой времени
|
|
15
|
+
- ➕ В релиз снова положен `TorrentOnline.app.zip`: двойной щелчок открывает страницу, окно Терминала не появляется
|
|
16
|
+
|
|
5
17
|
## [1.7.8] — 2026-10-04
|
|
6
18
|
**RU:** `npm i -g` снова доходит до конца. Зависимость `ip-set` больше не вызывает `npx only-allow pnpm` и не роняет установку.
|
|
7
19
|
**EN:** `npm i -g` finishes again. The `ip-set` dependency no longer runs `npx only-allow pnpm` and no longer aborts the install.
|
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ node wtui.js "https://example.com/file.torrent"
|
|
|
47
47
|
| `--port=<N>` | фиксированный порт (иначе авто-поиск 8123..10122) |
|
|
48
48
|
| `--help` | справка |
|
|
49
49
|
|
|
50
|
-
Кэш по умолчанию: `~/Movies/WebTorrent`. В окне он остаётся после
|
|
50
|
+
Кэш по умолчанию: `~/Movies/WebTorrent`. В окне он остаётся после выхода и после кнопки «Закрыть». Стирает его только «Очистить кэш». Язык страницы — кнопки RU и EN (по умолчанию русский). В режиме `--cli` папка по-прежнему удаляется при выходе, если нет `--keep-cache`.
|
|
51
51
|
|
|
52
52
|
## Возможности (v1.4)
|
|
53
53
|
- 🔎 Поиск торрентов прямо в меню: **Rutor.info (по-русски)**, TPB.party (по-английски), опционально 1337x API
|
|
@@ -63,7 +63,7 @@ node wtui.js "https://example.com/file.torrent"
|
|
|
63
63
|
```bash
|
|
64
64
|
node wtui.js --web
|
|
65
65
|
```
|
|
66
|
-
Тот же процесс, тот же порт. В браузере: поиск, поле magnet, кнопка файла `.torrent`, карточки с прогрессом, у каждого файла кнопки VLC и Браузер. VLC играет исходник. Браузер играет тот же поток со звуком: обычная шкала, перемотка и смена дорожки. С телефона в Wi-Fi — через `--lan` (токен в URL).
|
|
66
|
+
Тот же процесс, тот же порт. В браузере: поиск, поле magnet, кнопка файла `.torrent`, карточки с прогрессом, у каждого файла кнопки VLC и Браузер. VLC играет исходник. Браузер играет тот же поток со звуком: обычная шкала, перемотка и смена дорожки. Кнопки RU и EN меняют язык страницы и плеера. «Закрыть» останавливает сервер и не трогает кэш. С телефона в Wi-Fi — через `--lan` (токен в URL).
|
|
67
67
|
|
|
68
68
|
## Нативное приложение (Tauri, macOS)
|
|
69
69
|
Окошко с иконкой, без терминала: над тем же веб-GUI.
|
|
@@ -80,7 +80,7 @@ cd desktop && ./build-mac.sh
|
|
|
80
80
|
xattr -dr com.apple.quarantine /Applications/TorrentOnline.app
|
|
81
81
|
open /Applications/TorrentOnline.app
|
|
82
82
|
```
|
|
83
|
-
|
|
83
|
+
Своя сборка (двойной щелчок открывает страницу в браузере, Terminal не нужен):
|
|
84
84
|
```bash
|
|
85
85
|
./build/app.sh
|
|
86
86
|
open dist/TorrentOnline.app
|
|
@@ -112,7 +112,7 @@ docker run -p 8123:8123 torrent-online node wtui.js "magnet:?xt=..." --lan --no-
|
|
|
112
112
|
- VLC (`/Applications/VLC.app` или в PATH) — не нужен с `--no-vlc`
|
|
113
113
|
|
|
114
114
|
## Частые вопросы
|
|
115
|
-
- **Node не виден из .app** — .app
|
|
115
|
+
- **Node не виден из .app** — .app ищет `node` в `/opt/homebrew/bin` и `/usr/local/bin`. Если нет — `brew install node`.
|
|
116
116
|
- **«Writable stream closed prematurely»** — VLC рвёт пробные коннекты; мы их игнорим.
|
|
117
117
|
- **Порт занят** — авто-поиск в диапазоне 8123..10122 или `--port=`.
|
|
118
118
|
- **Magnet без пиров** — приложение отвалится через 60 с с понятной ошибкой, не будет висеть вечно.
|
|
@@ -158,7 +158,7 @@ node wtui.js "https://example.com/file.torrent"
|
|
|
158
158
|
| `--port=<N>` | fixed port (otherwise auto-scan 8123..10122) |
|
|
159
159
|
| `--help` | help |
|
|
160
160
|
|
|
161
|
-
Default cache: `~/Movies/WebTorrent`. The window keeps it after exit
|
|
161
|
+
Default cache: `~/Movies/WebTorrent`. The window keeps it after exit and after Quit. Only Clear cache deletes it. The page language is the RU and EN buttons (Russian by default). In `--cli` mode the folder is still removed on exit unless `--keep-cache` is set.
|
|
162
162
|
|
|
163
163
|
## Features (v1.4)
|
|
164
164
|
- 🔎 Built-in torrent search: **Rutor.info (Russian)**, TPB.party (English), optional 1337x API
|
|
@@ -174,7 +174,7 @@ Default cache: `~/Movies/WebTorrent`. The window keeps it after exit; the «Оч
|
|
|
174
174
|
```bash
|
|
175
175
|
node wtui.js --web
|
|
176
176
|
```
|
|
177
|
-
Same process, same port. The browser has search, a magnet field, a `.torrent` file button, progress cards, and VLC / Browser buttons on each file. VLC plays the original. The browser plays one stream with sound: a normal timeline, seek, and an audio-track switch. From a phone on your Wi-Fi — use `--lan` (token in URL).
|
|
177
|
+
Same process, same port. The browser has search, a magnet field, a `.torrent` file button, progress cards, and VLC / Browser buttons on each file. VLC plays the original. The browser plays one stream with sound: a normal timeline, seek, and an audio-track switch. The RU and EN buttons switch the page and the player. Quit stops the server and leaves the cache. From a phone on your Wi-Fi — use `--lan` (token in URL).
|
|
178
178
|
|
|
179
179
|
## Native app (Tauri, macOS)
|
|
180
180
|
A window with an icon, no terminal — wraps the same web GUI.
|
|
@@ -191,7 +191,7 @@ The app is unsigned, so macOS adds a quarantine flag — clear it once:
|
|
|
191
191
|
xattr -dr com.apple.quarantine /Applications/TorrentOnline.app
|
|
192
192
|
open /Applications/TorrentOnline.app
|
|
193
193
|
```
|
|
194
|
-
Build it yourself (
|
|
194
|
+
Build it yourself (a double-click opens the page in the browser, no Terminal window):
|
|
195
195
|
```bash
|
|
196
196
|
./build/app.sh
|
|
197
197
|
open dist/TorrentOnline.app
|
|
@@ -223,7 +223,7 @@ docker run -p 8123:8123 torrent-online node wtui.js "magnet:?xt=..." --lan --no-
|
|
|
223
223
|
- VLC (`/Applications/VLC.app` or in PATH) — not needed with `--no-vlc`
|
|
224
224
|
|
|
225
225
|
## FAQ
|
|
226
|
-
- **Node not visible from the .app** — the .app
|
|
226
|
+
- **Node not visible from the .app** — the .app looks for `node` in `/opt/homebrew/bin` and `/usr/local/bin`. If it is missing, run `brew install node`.
|
|
227
227
|
- **"Writable stream closed prematurely"** — VLC drops probe connections; we ignore them.
|
|
228
228
|
- **Port busy** — auto-scan in 8123..10122 or use `--port=`.
|
|
229
229
|
- **Magnet with no peers** — the app fails after 60 s with a clear error instead of hanging forever.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "torrent-online",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Stream .torrent / magnet to VLC or browser — torrent streaming for macOS/Linux without a torrent client. Стрим торрентов в VLC или браузер.",
|
|
6
6
|
"keywords": [
|
package/wtui.js
CHANGED
|
@@ -555,9 +555,13 @@ function watchFolder() {
|
|
|
555
555
|
const WEB_HTML = `<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>TorrentOnline</title><style>
|
|
556
556
|
:root{--bg:#0f1115;--card:#171a21;--line:#232833;--txt:#e8ecf1;--mut:#8a93a3;--acc:#7fd4ff}
|
|
557
557
|
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--txt);font:15px/1.45 -apple-system,'Segoe UI',Roboto,sans-serif}
|
|
558
|
-
header{display:flex;gap:10px;align-items:center;padding:14px 20px;border-bottom:1px solid var(--line);position:sticky;top:0;background:rgba(15,17,21,.92);backdrop-filter:blur(6px)}
|
|
558
|
+
header{display:flex;gap:10px;align-items:center;padding:14px 20px;border-bottom:1px solid var(--line);position:sticky;top:0;background:rgba(15,17,21,.92);backdrop-filter:blur(6px);flex-wrap:wrap}
|
|
559
559
|
header b{font-size:18px}
|
|
560
|
-
#
|
|
560
|
+
#langs{margin-left:auto;display:flex;gap:4px}
|
|
561
|
+
#langs button{min-width:44px;padding:6px 10px}
|
|
562
|
+
#langs button.on{background:#2a3c58;color:#fff;border-color:var(--acc)}
|
|
563
|
+
#clr{color:#ffd7a8;border-color:#5a4630;background:#2a241c}
|
|
564
|
+
#quit{color:#ffb4b4;border-color:#5a3038;background:#2a1c22}
|
|
561
565
|
.wrap{max-width:900px;margin:0 auto;padding:16px}
|
|
562
566
|
.search{display:flex;gap:8px}
|
|
563
567
|
input#q{flex:1;min-width:0;background:#1d222c;border:1px solid #2a3140;color:var(--txt);padding:10px 12px;border-radius:8px;font-size:15px}
|
|
@@ -589,45 +593,57 @@ body.drop{outline:3px dashed #3b82f6;outline-offset:-10px;background:rgba(59,130
|
|
|
589
593
|
h3{color:var(--mut);font-size:14px;margin:18px 0 4px;font-weight:500}
|
|
590
594
|
video{width:100vw;height:100vh;background:#000;display:block;margin:0}
|
|
591
595
|
</style></head><body>
|
|
592
|
-
<header>🦀 <b>TorrentOnline</b><button id=clr type=button>Очистить кэш</button></header>
|
|
596
|
+
<header>🦀 <b>TorrentOnline</b><span id=langs><button type=button id=lang-ru class=on>RU</button><button type=button id=lang-en>EN</button></span><button id=clr type=button>Очистить кэш</button><button id=quit type=button>Закрыть</button></header>
|
|
593
597
|
<div class=wrap>
|
|
594
598
|
<div class=search><input id=q placeholder="Поиск: пацаны 4 сезон · breaking bad" autocomplete=off><button id=go type=button>Искать</button></div>
|
|
595
599
|
<div class=search style="margin-top:8px"><input id=magin placeholder="Вставь magnet:?xt=urn:btih:…" autocomplete=off><button id=mag type=button>Добавить magnet</button><button id=tpath type=button>Файл .torrent</button><input type=file id=tt accept=".torrent,application/x-bittorrent" style=display:none></div>
|
|
596
|
-
<div class=note>Поток идёт сразу, целиком ждать не нужно. У каждого файла кнопки VLC и Браузер. Кэш при выходе не удаляется — только
|
|
600
|
+
<div class=note id=lead>Поток идёт сразу, целиком ждать не нужно. У каждого файла кнопки VLC и Браузер. Кэш при выходе не удаляется — только кнопкой «Очистить кэш». «Закрыть» останавливает сервер и не трогает скачанное.</div>
|
|
597
601
|
<div id=notes></div>
|
|
598
602
|
<div id=results></div>
|
|
599
|
-
<h3>Торренты</h3>
|
|
603
|
+
<h3 id=th>Торренты</h3>
|
|
600
604
|
<div id=torrents></div>
|
|
601
605
|
</div>
|
|
602
606
|
<script>
|
|
603
607
|
var BASE="__BASE__";
|
|
608
|
+
var DICT={
|
|
609
|
+
ru:{phSearch:'Поиск: пацаны 4 сезон · breaking bad',search:'Искать',phMag:'Вставь magnet:?xt=urn:btih:…',addMag:'Добавить magnet',fileBtn:'Файл .torrent',lead:'Поток идёт сразу, целиком ждать не нужно. У каждого файла кнопки VLC и Браузер. Кэш при выходе не удаляется — только кнопкой «Очистить кэш». «Закрыть» останавливает сервер и не трогает скачанное.',clear:'Очистить кэш',quit:'Закрыть',torrents:'Торренты',adding:'добавляю в клиент…',torrent:'торрент',searching:'Ищу…',emptyFind:'Ничего не найдено',searchErr:'Ошибка поиска',needMag:'Нужна ссылка, которая начинается с magnet:',clearAsk:'Удалить скачанные файлы из кэша? Список торрентов тоже очистится.',cleared:'Кэш очищен',clearFail:'Не удалось очистить кэш',uploading:'Загружаю',uploadErr:'Ошибка загрузки',empty:'Пусто. Найди торрент и кликни по строке.',browser:'Браузер',all:'выбрать все',none:'снять все',waitMeta:'ждём метаданные…',peers:'пиры',hint:'Отметь серии галочками — ▶ VLC / ▶ Браузер запустят выбранное. Одна серия стартует автоматически.',del:'✕ удалить',quitAsk:'Остановить сервер? Скачанные файлы останутся в кэше.',quitDone:'Сервер остановлен. Вкладку можно закрыть.',quitFail:'Сервер всё ещё отвечает.',addErr:'Ошибка /api/add','empty-file':'пустой файл','need-source':'нужна ссылка или файл','no-torrent':'нет торрента','no-file':'нет файла','pick-files':'выбери файлы','no-vlc':'VLC не найден'},
|
|
610
|
+
en:{phSearch:'Search: the boys season 4 · breaking bad',search:'Search',phMag:'Paste magnet:?xt=urn:btih:…',addMag:'Add magnet',fileBtn:'.torrent file',lead:'Playback starts right away. You do not wait for the full download. Each file has VLC and Browser buttons. Exit does not delete the cache. Only Clear cache does. Quit stops the server and leaves downloads on disk.',clear:'Clear cache',quit:'Quit',torrents:'Torrents',adding:'adding to the client…',torrent:'torrent',searching:'Searching…',emptyFind:'Nothing found',searchErr:'Search failed',needMag:'The link must start with magnet:',clearAsk:'Delete downloaded files from the cache? The torrent list will be cleared too.',cleared:'Cache cleared',clearFail:'Could not clear the cache',uploading:'Uploading',uploadErr:'Upload failed',empty:'Nothing here yet. Search for a torrent and click a row.',browser:'Browser',all:'select all',none:'select none',waitMeta:'waiting for metadata…',peers:'peers',hint:'Tick the episodes. VLC and Browser play the selection. A single episode starts on its own.',del:'✕ remove',quitAsk:'Stop the server? Downloaded files stay in the cache.',quitDone:'Server stopped. You can close this tab.',quitFail:'The server is still running.',addErr:'/api/add error','empty-file':'empty file','need-source':'a link or a file is required','no-torrent':'no such torrent','no-file':'no such file','pick-files':'pick files first','no-vlc':'VLC was not found'}
|
|
611
|
+
};
|
|
612
|
+
function lang(){try{return localStorage.getItem('to-lang')==='en'?'en':'ru'}catch(e){return 'ru'}}
|
|
613
|
+
function tr(k){var d=DICT[lang()]||DICT.ru;if(d&&d[k]!=null)return d[k];return (DICT.ru&&DICT.ru[k]!=null)?DICT.ru[k]:k}
|
|
614
|
+
function errText(code){if(code==null||code==='')return '';var s=tr(String(code));return s===String(code)?String(code):s}
|
|
615
|
+
function applyLang(){var L=lang();document.documentElement.lang=L;var ruB=document.getElementById('lang-ru'),enB=document.getElementById('lang-en');if(ruB)ruB.className=L==='ru'?'on':'';if(enB)enB.className=L==='en'?'on':'';document.getElementById('clr').textContent=tr('clear');document.getElementById('quit').textContent=tr('quit');document.getElementById('go').textContent=tr('search');document.getElementById('mag').textContent=tr('addMag');document.getElementById('tpath').textContent=tr('fileBtn');document.getElementById('q').placeholder=tr('phSearch');document.getElementById('magin').placeholder=tr('phMag');document.getElementById('lead').textContent=tr('lead');document.getElementById('th').textContent=tr('torrents');}
|
|
604
616
|
function esc(s){return String(s).replace(/[&<>"']/g,function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];});}
|
|
605
617
|
function api(p,body){var o=body?{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(body)}:undefined;return fetch(BASE+p,o).then(function(r){return r.json();});}
|
|
606
618
|
function addTorrent(mag){
|
|
607
619
|
var box=document.getElementById('torrents');
|
|
608
620
|
var dn=(/dn=([^&]*)/.exec(mag)||[,''])[1];try{dn=decodeURIComponent(dn.replace(/\\+/g,' '))}catch(e){}
|
|
609
621
|
var ph=document.createElement('div');ph.className='card';
|
|
610
|
-
ph.innerHTML='<div class=tname>⏳ '+esc(dn||'
|
|
622
|
+
ph.innerHTML='<div class=tname>⏳ '+esc(dn||tr('torrent'))+'</div><div class=tmeta>'+esc(tr('adding'))+'</div>';
|
|
611
623
|
box.prepend(ph);
|
|
612
|
-
api('/api/add',{source:mag}).then(function(d){if(d&&d.error){ph.innerHTML='<div class=tname>⚠ '+esc(d.error)+'</div>';}poll();toTorrents();}).catch(function(e){ph.innerHTML='<div class=tname>⚠
|
|
624
|
+
api('/api/add',{source:mag}).then(function(d){if(d&&d.error){ph.innerHTML='<div class=tname>⚠ '+esc(errText(d.error))+'</div>';}poll();toTorrents();}).catch(function(e){ph.innerHTML='<div class=tname>⚠ '+esc(tr('addErr'))+': '+esc(e&&e.message||e)+'</div>';});
|
|
613
625
|
}
|
|
614
|
-
function doSearch(){var q=document.getElementById('q').value.trim();if(!q)return;if(q.indexOf('magnet:')===0){addTorrent(q);document.getElementById('q').value='';return;}var box=document.getElementById('results');box.innerHTML='<div class=note
|
|
626
|
+
function doSearch(){var q=document.getElementById('q').value.trim();if(!q)return;if(q.indexOf('magnet:')===0){addTorrent(q);document.getElementById('q').value='';return;}var box=document.getElementById('results');box.innerHTML='<div class=note>'+esc(tr('searching'))+'</div>';
|
|
615
627
|
api('/api/search?q='+encodeURIComponent(q)).then(function(d){
|
|
616
628
|
var h='';(d.notes||[]).forEach(function(n){h+='<div class=note>⚠ '+esc(n)+'</div>';});
|
|
617
|
-
if(!d.items.length){box.innerHTML=h+'<div class=note
|
|
629
|
+
if(!d.items.length){box.innerHTML=h+'<div class=note>'+esc(tr('emptyFind'))+'</div>';return;}
|
|
618
630
|
h+=d.items.map(function(it){return '<div class=row data-mag="'+esc(it.mag)+'"><span class=tag>'+esc(it.src)+'</span><span class=rname>'+esc(it.name)+'</span>'+(it.size?'<span class=meta>'+esc(it.size)+'</span>':'')+(it.seeds?'<span class=meta>🌱 '+it.seeds+'</span>':'')+'</div>';}).join('');
|
|
619
631
|
box.innerHTML=h;
|
|
620
632
|
[].forEach.call(box.querySelectorAll('.row'),function(r){r.onclick=function(){addTorrent(r.getAttribute('data-mag'));};});
|
|
621
|
-
}).catch(function(){box.innerHTML='<div class=note
|
|
633
|
+
}).catch(function(){box.innerHTML='<div class=note>'+esc(tr('searchErr'))+'</div>';});}
|
|
622
634
|
document.getElementById('go').onclick=doSearch;
|
|
623
635
|
document.getElementById('q').addEventListener('keydown',function(e){if(e.key==='Enter')doSearch();});
|
|
624
|
-
document.getElementById('mag').onclick=function(){var m=document.getElementById('magin').value.trim();var nb=document.getElementById('notes');if(m.indexOf('magnet:')!==0){nb.innerHTML='<div class=note
|
|
636
|
+
document.getElementById('mag').onclick=function(){var m=document.getElementById('magin').value.trim();var nb=document.getElementById('notes');if(m.indexOf('magnet:')!==0){nb.innerHTML='<div class=note>'+esc(tr('needMag'))+'</div>';return;}nb.innerHTML='';addTorrent(m);document.getElementById('magin').value='';};
|
|
625
637
|
document.getElementById('magin').addEventListener('keydown',function(e){if(e.key==='Enter')document.getElementById('mag').click();});
|
|
626
|
-
document.getElementById('clr').onclick=function(){if(!confirm('
|
|
638
|
+
document.getElementById('clr').onclick=function(){if(!confirm(tr('clearAsk')))return;api('/api/clear-cache',{ok:1}).then(function(){document.getElementById('notes').innerHTML='<div class=note>'+esc(tr('cleared'))+'</div>';poll();}).catch(function(){document.getElementById('notes').innerHTML='<div class=note>'+esc(tr('clearFail'))+'</div>';});};
|
|
639
|
+
document.getElementById('quit').onclick=function(){if(!confirm(tr('quitAsk')))return;var nb=document.getElementById('notes');fetch(BASE+'/api/quit',{method:'POST',headers:{'content-type':'application/json'},body:'{}'}).catch(function(){}).then(function(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}setTimeout(function(){fetch(BASE+'/api/status').then(function(r){nb.innerHTML='<div class=note>'+esc(r.ok?tr('quitFail'):tr('quitDone'))+'</div>';}).catch(function(){nb.innerHTML='<div class=note>'+esc(tr('quitDone'))+'</div>';});},700);});};
|
|
640
|
+
function setLang(code){try{localStorage.setItem('to-lang',code)}catch(e){}applyLang();poll();}
|
|
641
|
+
document.getElementById('lang-ru').onclick=function(){setLang('ru');};
|
|
642
|
+
document.getElementById('lang-en').onclick=function(){setLang('en');};
|
|
627
643
|
document.getElementById('tpath').onclick=function(){document.getElementById('tt').click();};
|
|
628
644
|
document.getElementById('tt').onchange=function(){if(this.files&&this.files[0])uploadFile(this.files[0]);this.value='';};
|
|
629
|
-
function uploadFile(f){var nb=document.getElementById('notes');nb.innerHTML='<div class=note>⏳
|
|
630
|
-
fetch(BASE+'/api/add-file',{method:'POST',headers:{'content-type':'application/octet-stream'},body:f}).then(function(r){return r.json()}).then(function(d){nb.innerHTML=d.error?'<div class=note>⚠ '+esc(d.error)+'</div>':'';poll();toTorrents();}).catch(function(){nb.innerHTML='<div class=note>⚠
|
|
645
|
+
function uploadFile(f){var nb=document.getElementById('notes');nb.innerHTML='<div class=note>⏳ '+esc(tr('uploading'))+' '+esc(f.name)+'…</div>';
|
|
646
|
+
fetch(BASE+'/api/add-file',{method:'POST',headers:{'content-type':'application/octet-stream'},body:f}).then(function(r){return r.json()}).then(function(d){nb.innerHTML=d.error?'<div class=note>⚠ '+esc(errText(d.error))+'</div>':'';poll();toTorrents();}).catch(function(){nb.innerHTML='<div class=note>⚠ '+esc(tr('uploadErr'))+'</div>';});}
|
|
631
647
|
document.addEventListener('dragover',function(e){e.preventDefault();document.body.classList.add('drop');});
|
|
632
648
|
document.addEventListener('dragleave',function(e){if(e.target===document.body||e.relatedTarget===null)document.body.classList.remove('drop');});
|
|
633
649
|
document.addEventListener('drop',function(e){e.preventDefault();document.body.classList.remove('drop');
|
|
@@ -637,27 +653,30 @@ var t=(e.dataTransfer.getData('text/uri-list')||'')+(e.dataTransfer.getData('tex
|
|
|
637
653
|
var mm=t.match(/magnet:\\?[^\\s]+/);if(mm){addTorrent(mm[0]);}
|
|
638
654
|
});
|
|
639
655
|
function render(list){var box=document.getElementById('torrents');
|
|
640
|
-
if(!list.length){box.innerHTML='<div class=note
|
|
656
|
+
if(!list.length){box.innerHTML='<div class=note>'+esc(tr('empty'))+'</div>';return;}
|
|
641
657
|
box.innerHTML=list.map(function(t){
|
|
642
|
-
var files=t.files.map(function(f){return '<div class=fin><label><input type=checkbox data-id='+t.id+' data-i='+f.i+(f.selected?' checked':'')+'> '+esc(f.name)+' <span class=meta>'+esc(f.size)+'</span></label> <button type=button class=play data-act=vlc data-id='+t.id+' data-i='+f.i+'>VLC</button> <button type=button class=play data-act=web data-id='+t.id+' data-i='+f.i+'
|
|
658
|
+
var files=t.files.map(function(f){return '<div class=fin><label><input type=checkbox data-id='+t.id+' data-i='+f.i+(f.selected?' checked':'')+'> '+esc(f.name)+' <span class=meta>'+esc(f.size)+'</span></label> <button type=button class=play data-act=vlc data-id='+t.id+' data-i='+f.i+'>VLC</button> <button type=button class=play data-act=web data-id='+t.id+' data-i='+f.i+'>'+esc(tr('browser'))+'</button></div>';}).join('');
|
|
643
659
|
var first=t.files.filter(function(f){return f.selected;})[0];
|
|
644
660
|
var wi=first?first.i:0;
|
|
645
661
|
var nSel=t.files.filter(function(f){return f.selected;}).length;
|
|
646
662
|
var bar=t.ready?t.progress:0;
|
|
647
|
-
var hint=(t.ready&&nSel===0)?'<div class=note
|
|
648
|
-
return '<div class=card><div class=tname>'+esc(t.name)+'</div><div class=tmeta>'+(t.ready?'⏬ '+t.progress+'% · '+esc(t.speed)+' ·
|
|
663
|
+
var hint=(t.ready&&nSel===0)?'<div class=note>'+esc(tr('hint'))+'</div>':'';
|
|
664
|
+
return '<div class=card><div class=tname>'+esc(t.name)+'</div><div class=tmeta>'+(t.ready?'⏬ '+t.progress+'% · '+esc(t.speed)+' · '+esc(tr('peers'))+' '+t.peers:'⏳ '+esc(tr('waitMeta')))+'</div><div class=bar><div style=width:'+bar+'%></div></div>'+(t.files.length>1?'<div class=picks><button data-act=all data-id='+t.id+'>'+esc(tr('all'))+'</button><button data-act=none data-id='+t.id+'>'+esc(tr('none'))+'</button></div>':'')+'<div class=files>'+files+'</div>'+hint+'<div class=acts><button data-act=vlc data-id='+t.id+'>▶ VLC'+(nSel?' ('+nSel+')':'')+'</button><button data-act=web data-id='+t.id+' data-i='+wi+'>▶ '+esc(tr('browser'))+'</button><button class=del data-act=del data-id='+t.id+'>'+esc(tr('del'))+'</button></div></div>';
|
|
649
665
|
}).join('');
|
|
650
666
|
[].forEach.call(box.querySelectorAll('.fin input'),function(cb){cb.onchange=function(){var id=+cb.getAttribute('data-id');var idx=[].filter.call(box.querySelectorAll('.fin input[data-id="'+id+'"]'),function(x){return x.checked;}).map(function(x){return +x.getAttribute('data-i');});api('/api/select',{id:id,indices:idx});};});
|
|
651
667
|
[].forEach.call(box.querySelectorAll('button[data-act]'),function(b){b.onclick=function(ev){ev.preventDefault();ev.stopPropagation();var id=+b.getAttribute('data-id');var act=b.getAttribute('data-act');
|
|
652
|
-
if(act==='vlc'){var one=b.getAttribute('data-i');var label=b.textContent;b.textContent='…';var body={id:id};if(one!=null&&one!=='')body.index=+one;api('/api/vlc',body).then(function(d){b.textContent=d.error?('⚠ '+
|
|
668
|
+
if(act==='vlc'){var one=b.getAttribute('data-i');var label=b.textContent;b.textContent='…';var body={id:id};if(one!=null&&one!=='')body.index=+one;api('/api/vlc',body).then(function(d){b.textContent=d.error?('⚠ '+errText(d.error).slice(0,80)):label;});}
|
|
653
669
|
if(act==='web'){window.open(BASE+'/view/'+id+'/'+b.getAttribute('data-i'));}
|
|
654
670
|
if(act==='del'){api('/api/remove',{id:id}).then(poll);}
|
|
655
671
|
if(act==='all'||act==='none'){var idx=act==='all'?[].map.call(box.querySelectorAll('.fin input[data-id="'+id+'"]'),function(x){return +x.getAttribute('data-i');}):[];api('/api/select',{id:id,indices:idx}).then(poll);}
|
|
656
672
|
};});
|
|
657
673
|
}
|
|
674
|
+
var pollTimer=null;
|
|
658
675
|
function poll(){api('/api/status').then(render).catch(function(){});}
|
|
659
676
|
function toTorrents(){var el=document.getElementById('torrents');el.scrollIntoView({behavior:'smooth',block:'start'});}
|
|
660
|
-
|
|
677
|
+
applyLang();
|
|
678
|
+
poll();
|
|
679
|
+
pollTimer=setInterval(poll,1500);
|
|
661
680
|
</script></body></html>`;
|
|
662
681
|
|
|
663
682
|
function readBody(req) {
|
|
@@ -672,26 +691,23 @@ function readBody(req) {
|
|
|
672
691
|
const HLS_SEG = 4;
|
|
673
692
|
|
|
674
693
|
function hlsSegName(i) {
|
|
675
|
-
return 'seg-' + String(i).padStart(5, '0') + '.
|
|
694
|
+
return 'seg-' + String(i).padStart(5, '0') + '.m4s';
|
|
676
695
|
}
|
|
677
696
|
|
|
678
697
|
function listHlsSegs(dir) {
|
|
679
698
|
try {
|
|
680
699
|
return fs.readdirSync(dir)
|
|
681
|
-
.filter(n => /^seg-\d+\.
|
|
682
|
-
.map(n => Number(n.slice(4, -
|
|
700
|
+
.filter(n => /^seg-\d+\.m4s$/.test(n))
|
|
701
|
+
.map(n => Number(n.slice(4, -4)))
|
|
683
702
|
.filter(n => Number.isFinite(n))
|
|
684
703
|
.sort((a, b) => a - b);
|
|
685
704
|
} catch { return []; }
|
|
686
705
|
}
|
|
687
706
|
|
|
688
|
-
function hlsSegComplete(dir, index
|
|
707
|
+
function hlsSegComplete(dir, index) {
|
|
708
|
+
// temp_file: готовый фрагмент появляется атомарно, недописанный лежит как .tmp
|
|
689
709
|
const fp = path.join(dir, hlsSegName(index));
|
|
690
|
-
|
|
691
|
-
try { st = fs.statSync(fp); } catch { return false; }
|
|
692
|
-
if (st.size < 188) return false;
|
|
693
|
-
try { if (fs.statSync(path.join(dir, hlsSegName(index + 1))).size > 0) return true; } catch {}
|
|
694
|
-
return !!(job && job.ended && !job.aborted);
|
|
710
|
+
try { return fs.statSync(fp).size > 32; } catch { return false; }
|
|
695
711
|
}
|
|
696
712
|
|
|
697
713
|
async function probeSource(ffprobe, url) {
|
|
@@ -742,13 +758,12 @@ function killHlsJob(job, aborted) {
|
|
|
742
758
|
if (!job || job.aborted || job.ended) return;
|
|
743
759
|
job.aborted = !!aborted;
|
|
744
760
|
try { job.proc.kill('SIGKILL'); } catch {}
|
|
745
|
-
if (!aborted) return;
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
761
|
+
if (!aborted || !job.dir) return;
|
|
762
|
+
try {
|
|
763
|
+
for (const n of fs.readdirSync(job.dir)) {
|
|
764
|
+
if (n.endsWith('.tmp')) fs.rmSync(path.join(job.dir, n));
|
|
765
|
+
}
|
|
766
|
+
} catch {}
|
|
752
767
|
}
|
|
753
768
|
|
|
754
769
|
function jobCovers(job, index) {
|
|
@@ -771,35 +786,40 @@ function ensureHlsJob(hls, key, dir, ffmpeg, url, fromIndex, audioIndex, meta) {
|
|
|
771
786
|
if (pre > 0.05) args.push('-ss', pre.toFixed(3));
|
|
772
787
|
args.push('-i', url);
|
|
773
788
|
if (post > 0.05) args.push('-ss', post.toFixed(3));
|
|
789
|
+
if (target > 0.05) args.push('-output_ts_offset', target.toFixed(3));
|
|
774
790
|
args.push('-map', '0:v:0');
|
|
775
791
|
const hasAudio = meta.tracks.length > 0;
|
|
776
792
|
const a = hasAudio ? Math.max(0, Math.min(audioIndex, meta.tracks.length - 1)) : 0;
|
|
777
793
|
if (hasAudio) args.push('-map', '0:a:' + a);
|
|
778
|
-
//
|
|
779
|
-
//
|
|
794
|
+
// MPEG-TS сбрасывал PTS каждого куска в ноль, браузер ставил звук не на то время.
|
|
795
|
+
// fMP4 держит одну шкалу: картинка и звук без разрывов на границах.
|
|
780
796
|
// VLC по-прежнему открывает исходный файл как есть.
|
|
781
797
|
const big = (meta.height || 0) > 1080 || (meta.width || 0) > 1920;
|
|
782
798
|
const scale = big
|
|
783
799
|
? 'scale=trunc(min(1920\\,iw)/2)*2:trunc(min(1080\\,ih)/2)*2'
|
|
784
800
|
: 'scale=trunc(iw/2)*2:trunc(ih/2)*2';
|
|
801
|
+
const initName = (fs.existsSync(path.join(dir, 'init.mp4')) && fs.statSync(path.join(dir, 'init.mp4')).size > 200)
|
|
802
|
+
? 'init-extra.mp4' : 'init.mp4';
|
|
785
803
|
args.push(
|
|
786
|
-
'-c:v', 'libx264', '-preset', 'ultrafast', '-
|
|
787
|
-
'-profile:v', 'main', '-crf', '22',
|
|
804
|
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-profile:v', 'main', '-crf', '22',
|
|
788
805
|
'-vf', scale + ',format=yuv420p',
|
|
806
|
+
'-g', '1000', '-keyint_min', '1000', '-sc_threshold', '0',
|
|
789
807
|
'-force_key_frames', 'expr:gte(t,n_forced*' + HLS_SEG + ')'
|
|
790
808
|
);
|
|
791
|
-
if (hasAudio) args.push('-c:a', 'aac', '-b:a', '160k', '-ac', '2', '-
|
|
809
|
+
if (hasAudio) args.push('-c:a', 'aac', '-b:a', '160k', '-ac', '2', '-ar', '48000');
|
|
792
810
|
else args.push('-an');
|
|
793
811
|
args.push(
|
|
794
|
-
'-avoid_negative_ts', 'make_zero',
|
|
795
812
|
'-muxdelay', '0', '-muxpreload', '0',
|
|
796
813
|
'-max_muxing_queue_size', '9999',
|
|
797
|
-
'-f', '
|
|
798
|
-
'-
|
|
799
|
-
'-
|
|
800
|
-
'-
|
|
801
|
-
'-
|
|
802
|
-
|
|
814
|
+
'-f', 'hls', '-hls_time', String(HLS_SEG),
|
|
815
|
+
'-hls_playlist_type', 'event',
|
|
816
|
+
'-hls_segment_type', 'fmp4',
|
|
817
|
+
'-hls_fmp4_init_filename', initName,
|
|
818
|
+
'-hls_flags', 'independent_segments+temp_file',
|
|
819
|
+
'-hls_list_size', '0',
|
|
820
|
+
'-start_number', String(fromIndex),
|
|
821
|
+
'-hls_segment_filename', path.join(dir, 'seg-%05d.m4s'),
|
|
822
|
+
path.join(dir, 'ffmpeg.m3u8')
|
|
803
823
|
);
|
|
804
824
|
const proc = spawn(ffmpeg, args, { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
805
825
|
const job = { proc, dir, from: fromIndex, touched: Date.now(), ended: false, aborted: false, err: '' };
|
|
@@ -816,8 +836,9 @@ function ensureHlsJob(hls, key, dir, ffmpeg, url, fromIndex, audioIndex, meta) {
|
|
|
816
836
|
|
|
817
837
|
function vodPlaylist(duration, audio) {
|
|
818
838
|
const n = Math.max(1, Math.ceil(duration / HLS_SEG - 1e-6));
|
|
819
|
-
let pl = '#EXTM3U\n#EXT-X-VERSION:
|
|
839
|
+
let pl = '#EXTM3U\n#EXT-X-VERSION:7\n#EXT-X-TARGETDURATION:' + HLS_SEG + '\n#EXT-X-PLAYLIST-TYPE:VOD\n#EXT-X-INDEPENDENT-SEGMENTS\n';
|
|
820
840
|
const qs = '?a=' + encodeURIComponent(String(audio));
|
|
841
|
+
pl += '#EXT-X-MAP:URI="init.mp4' + qs + '"\n';
|
|
821
842
|
for (let i = 0; i < n; i++) {
|
|
822
843
|
const segDur = Math.min(HLS_SEG, Math.max(0.001, duration - i * HLS_SEG));
|
|
823
844
|
pl += '#EXTINF:' + segDur.toFixed(3) + ',\n' + hlsSegName(i) + qs + '\n';
|
|
@@ -873,6 +894,31 @@ function makeWebServer(client, st, opts, cacheDir, base) {
|
|
|
873
894
|
});
|
|
874
895
|
}
|
|
875
896
|
|
|
897
|
+
const PLAYER_I18N = {
|
|
898
|
+
ru: {
|
|
899
|
+
sound: '▶ со звуком',
|
|
900
|
+
prep: 'готовлю файл к стриму…',
|
|
901
|
+
ffmpeg: 'нужен ffmpeg',
|
|
902
|
+
nodur: 'не вижу длительность файла',
|
|
903
|
+
noplayer: 'плеер браузера не загрузился',
|
|
904
|
+
track: 'дорожка',
|
|
905
|
+
nosub: 'без субтитров',
|
|
906
|
+
sub: 'субтитры',
|
|
907
|
+
peers: 'пиры',
|
|
908
|
+
},
|
|
909
|
+
en: {
|
|
910
|
+
sound: '▶ with sound',
|
|
911
|
+
prep: 'preparing the stream…',
|
|
912
|
+
ffmpeg: 'ffmpeg is required',
|
|
913
|
+
nodur: 'cannot read the duration',
|
|
914
|
+
noplayer: 'the browser player did not load',
|
|
915
|
+
track: 'track',
|
|
916
|
+
nosub: 'no subtitles',
|
|
917
|
+
sub: 'subtitles',
|
|
918
|
+
peers: 'peers',
|
|
919
|
+
},
|
|
920
|
+
};
|
|
921
|
+
|
|
876
922
|
async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
877
923
|
const u = new URL(req.url, 'http://127.0.0.1');
|
|
878
924
|
const ensureSel = (e, i) => { const f = e?.t.files[i]; if (f && !e.sel.has(i)) { e.sel.add(i); try { f.select(); } catch {} } };
|
|
@@ -898,6 +944,17 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
898
944
|
} catch { res.statusCode = 404; res.end('no hls.js'); }
|
|
899
945
|
return;
|
|
900
946
|
}
|
|
947
|
+
if (p === '/api/quit' && req.method === 'POST') {
|
|
948
|
+
res.statusCode = 200;
|
|
949
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
950
|
+
res.setHeader('Connection', 'close');
|
|
951
|
+
res.end(JSON.stringify({ ok: true }));
|
|
952
|
+
setTimeout(() => {
|
|
953
|
+
if (typeof st.quit === 'function') st.quit();
|
|
954
|
+
else process.exit(0);
|
|
955
|
+
}, 150);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
901
958
|
if (p === '/api/search') {
|
|
902
959
|
const q = u.searchParams.get('q') || '';
|
|
903
960
|
const { items, notes } = await searchIndexers(q);
|
|
@@ -912,7 +969,7 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
912
969
|
req.on('error', reject);
|
|
913
970
|
});
|
|
914
971
|
const buf = Buffer.concat(chunks);
|
|
915
|
-
if (!buf.length) return json(400, { error: '
|
|
972
|
+
if (!buf.length) return json(400, { error: 'empty-file' });
|
|
916
973
|
const id = st.nextId++;
|
|
917
974
|
st.pending.push(id);
|
|
918
975
|
try { client.add(buf, { path: cacheDir }); }
|
|
@@ -922,7 +979,7 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
922
979
|
if (p === '/api/add' && req.method === 'POST') {
|
|
923
980
|
const b = await readBody(req);
|
|
924
981
|
const source = String(b.source || '');
|
|
925
|
-
if (!source) return json(400, { error: '
|
|
982
|
+
if (!source) return json(400, { error: 'need-source' });
|
|
926
983
|
const btih = (source.match(/btih:([0-9a-f]{40})/i) || [])[1];
|
|
927
984
|
if (btih) {
|
|
928
985
|
const ex = [...st.torrents.values()].find(e => e.t.infoHash === btih.toLowerCase());
|
|
@@ -951,7 +1008,7 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
951
1008
|
if (p === '/api/select' && req.method === 'POST') {
|
|
952
1009
|
const b = await readBody(req);
|
|
953
1010
|
const e = st.torrents.get(Number(b.id));
|
|
954
|
-
if (!e) return json(404, { error: '
|
|
1011
|
+
if (!e) return json(404, { error: 'no-torrent' });
|
|
955
1012
|
e.t.files.forEach(f => f.deselect());
|
|
956
1013
|
e.sel = new Set((b.indices || []).map(Number));
|
|
957
1014
|
e.sel.forEach(i => e.t.files[i]?.select());
|
|
@@ -960,18 +1017,18 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
960
1017
|
if (p === '/api/vlc' && req.method === 'POST') {
|
|
961
1018
|
const b = await readBody(req);
|
|
962
1019
|
const e = st.torrents.get(Number(b.id));
|
|
963
|
-
if (!e) return json(404, { error: '
|
|
1020
|
+
if (!e) return json(404, { error: 'no-torrent' });
|
|
964
1021
|
let idx;
|
|
965
1022
|
if (b.index != null && b.index !== '' && Number.isFinite(Number(b.index))) {
|
|
966
1023
|
const i = Number(b.index);
|
|
967
|
-
if (!e.t.files[i]) return json(400, { error: '
|
|
1024
|
+
if (!e.t.files[i]) return json(400, { error: 'no-file' });
|
|
968
1025
|
e.sel.add(i);
|
|
969
1026
|
try { e.t.files[i].select(); } catch {}
|
|
970
1027
|
idx = [i];
|
|
971
1028
|
} else idx = [...e.sel];
|
|
972
|
-
if (!idx.length) return json(400, { error: '
|
|
1029
|
+
if (!idx.length) return json(400, { error: 'pick-files' });
|
|
973
1030
|
const vlcBin = detectVLCPath();
|
|
974
|
-
if (!vlcBin) return json(500, { error: '
|
|
1031
|
+
if (!vlcBin) return json(500, { error: 'no-vlc' });
|
|
975
1032
|
const subs = await collectSubtitles(e.t, idx);
|
|
976
1033
|
const urls = idx.map(i => 'http://127.0.0.1:' + st.port + base + '/s/' + e.id + '/' + i);
|
|
977
1034
|
const args = buildVLCArgs({ urls, caching: opts.caching, subs, startAt: 0 });
|
|
@@ -1014,7 +1071,7 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
1014
1071
|
if (mT) {
|
|
1015
1072
|
const e = st.torrents.get(Number(mT[1]));
|
|
1016
1073
|
const f = e?.t.files[Number(mT[2])];
|
|
1017
|
-
if (!f) return json(404, { error: '
|
|
1074
|
+
if (!f) return json(404, { error: 'no-file' });
|
|
1018
1075
|
ensureSel(e, Number(mT[2]));
|
|
1019
1076
|
const ffprobe = detectFFBin('ffprobe');
|
|
1020
1077
|
if (!ffprobe) return json(200, { ffmpeg: false, tracks: [] });
|
|
@@ -1134,7 +1191,38 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
1134
1191
|
res.end(vodPlaylist(meta.duration, a));
|
|
1135
1192
|
return;
|
|
1136
1193
|
}
|
|
1137
|
-
|
|
1194
|
+
if (name === 'init.mp4') {
|
|
1195
|
+
await ensureDir(dir);
|
|
1196
|
+
const initFp = path.join(dir, 'init.mp4');
|
|
1197
|
+
if (!fs.existsSync(initFp) || fs.statSync(initFp).size < 200) {
|
|
1198
|
+
const running = hls.procs.get(key);
|
|
1199
|
+
const job = (running && !running.ended && !running.aborted)
|
|
1200
|
+
? running
|
|
1201
|
+
: ensureHlsJob(hls, key, dir, ffmpeg, url, 0, a, meta);
|
|
1202
|
+
job.touched = Date.now();
|
|
1203
|
+
const deadline = Date.now() + 20000;
|
|
1204
|
+
while (Date.now() < deadline) {
|
|
1205
|
+
try { if (fs.statSync(initFp).size > 200) break; } catch {}
|
|
1206
|
+
if (job.ended && job.err) break;
|
|
1207
|
+
await sleep(100);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
let initBuf = null;
|
|
1211
|
+
try { if (fs.statSync(initFp).size > 200) initBuf = fs.readFileSync(initFp); } catch {}
|
|
1212
|
+
if (!initBuf) {
|
|
1213
|
+
res.statusCode = 503;
|
|
1214
|
+
res.setHeader('Retry-After', '1');
|
|
1215
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
1216
|
+
res.end('init');
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
res.setHeader('Content-Type', 'video/mp4');
|
|
1220
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
1221
|
+
res.setHeader('Content-Length', initBuf.length);
|
|
1222
|
+
res.end(initBuf);
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
const segM = /^seg-(\d+)\.m4s$/.exec(name);
|
|
1138
1226
|
if (!segM) { res.statusCode = 404; res.end('no'); return; }
|
|
1139
1227
|
const index = Number(segM[1]);
|
|
1140
1228
|
const nSeg = Math.max(1, Math.ceil(meta.duration / HLS_SEG - 1e-6));
|
|
@@ -1159,7 +1247,7 @@ async function webReq(req, res, client, st, opts, cacheDir, base) {
|
|
|
1159
1247
|
return;
|
|
1160
1248
|
}
|
|
1161
1249
|
const buf = fs.readFileSync(fp);
|
|
1162
|
-
res.setHeader('Content-Type', 'video/
|
|
1250
|
+
res.setHeader('Content-Type', 'video/mp4');
|
|
1163
1251
|
res.setHeader('Cache-Control', 'no-store');
|
|
1164
1252
|
res.setHeader('Content-Length', buf.length);
|
|
1165
1253
|
res.end(buf);
|
|
@@ -1179,14 +1267,22 @@ select{background:#1b1b1f;color:#ddd;border:1px solid #333;border-radius:6px;pad
|
|
|
1179
1267
|
#tm{font-variant-numeric:tabular-nums;min-width:9em}
|
|
1180
1268
|
video{flex:1;width:100%;min-height:0;background:#000}
|
|
1181
1269
|
#n{color:#9ab}
|
|
1182
|
-
#ov{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.45);cursor:pointer;font-size:28px;color:#fff}
|
|
1183
|
-
|
|
1270
|
+
#ov{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.45);cursor:pointer;font-size:28px;color:#fff}
|
|
1271
|
+
#langs{display:flex;gap:4px}#langs button{background:#1b1b1f;color:#ddd;border:1px solid #333;border-radius:6px;padding:2px 8px;cursor:pointer}#langs button.on{border-color:#7fd4ff;color:#fff}</style>
|
|
1272
|
+
<div class=bar><b>${esc(f.name)}</b><span id=langs><button type=button id=lru>RU</button><button type=button id=len>EN</button></span><select id=a style=display:none></select><select id=s style=display:none></select><span id=tm>00:00 / --:--</span><span id=n></span></div>
|
|
1184
1273
|
<video id=v controls autoplay playsinline></video>
|
|
1185
1274
|
<div id=ov>▶ со звуком</div>
|
|
1186
1275
|
<script src="${base}/hls.min.js"></script>
|
|
1187
1276
|
<script>
|
|
1188
1277
|
var B=${JSON.stringify(base)},ID=${mV[1]},I=${mV[2]};
|
|
1278
|
+
var PK=${JSON.stringify(PLAYER_I18N)};
|
|
1189
1279
|
var v=document.getElementById("v"),aS=document.getElementById("a"),sS=document.getElementById("s"),n=document.getElementById("n"),tm=document.getElementById("tm"),ov=document.getElementById("ov");
|
|
1280
|
+
function LG(){try{return localStorage.getItem("to-lang")==="en"?"en":"ru"}catch(e){return "ru"}}
|
|
1281
|
+
function pt(k){var d=PK[LG()]||PK.ru;return (d&&d[k])||(PK.ru&&PK.ru[k])||k}
|
|
1282
|
+
function paintLang(){var L=LG();document.documentElement.lang=L;var a=document.getElementById("lru"),b=document.getElementById("len");if(a)a.className=L==="ru"?"on":"";if(b)b.className=L==="en"?"on":"";ov.textContent=pt("sound")}
|
|
1283
|
+
document.getElementById("lru").onclick=function(){try{localStorage.setItem("to-lang","ru")}catch(e){}paintLang();fillTracks()};
|
|
1284
|
+
document.getElementById("len").onclick=function(){try{localStorage.setItem("to-lang","en")}catch(e){}paintLang();fillTracks()};
|
|
1285
|
+
paintLang();
|
|
1190
1286
|
v.muted=false;v.volume=1;
|
|
1191
1287
|
ov.onclick=function(){v.muted=false;v.volume=1;v.play().catch(function(){});ov.style.display="none"};
|
|
1192
1288
|
var tries=0,player=null,cues=[],subUrl="",armed=false;
|
|
@@ -1207,27 +1303,29 @@ player.loadSource(u);player.attachMedia(v);
|
|
|
1207
1303
|
player.on(Hls.Events.MANIFEST_PARSED,start);
|
|
1208
1304
|
player.on(Hls.Events.ERROR,function(ev,d){if(!d.fatal)return;if(d.type===Hls.ErrorTypes.NETWORK_ERROR){player.startLoad(v.currentTime||want);return}n.textContent="HLS: "+(d.details||d.type)})}
|
|
1209
1305
|
else{var once=function(){v.removeEventListener("loadedmetadata",once);start()};v.addEventListener("loadedmetadata",once);v.src=u}};
|
|
1306
|
+
var lastD=null,filling=false;
|
|
1307
|
+
function fillTracks(){if(!lastD)return;filling=true;var d=lastD;var prevA=aS.value,prevS=sS.value;var A=d.tracks||[],S=d.subs||[];aS.innerHTML="";sS.innerHTML="";
|
|
1308
|
+
var SI=S.filter(function(t){return ["subrip","ass","ssa","srt","mov_text","webvtt","text"].indexOf(t.codec)>=0}).map(function(t){return t.s});
|
|
1309
|
+
A.forEach(function(t){var o=document.createElement("option");o.value=t.a;o.textContent=(t.lang&&t.lang!=="und"?t.lang:pt("track")+" "+(t.a+1))+(t.title?" — "+t.title:"")+" ["+t.codec+"]";aS.appendChild(o)});
|
|
1310
|
+
if(A.length){aS.value=(prevA!==""&&A.some(function(t){return String(t.a)===prevA}))?prevA:String(A[0].a);aS.style.display="";if(A.length<2)aS.disabled=true}
|
|
1311
|
+
if(SI.length){var o0=document.createElement("option");o0.value="";o0.textContent=pt("nosub");sS.appendChild(o0);
|
|
1312
|
+
S.filter(function(t){return SI.indexOf(t.s)>=0}).forEach(function(t){var o=document.createElement("option");o.value=t.s;o.textContent=(t.lang&&t.lang!=="und"?t.lang:pt("sub")+" "+(t.s+1))+(t.title?" — "+t.title:"");sS.appendChild(o)});sS.value=prevS||"";sS.style.display=""}
|
|
1313
|
+
filling=false}
|
|
1210
1314
|
var load=function(){fetch(B+"/api/tracks/"+ID+"/"+I).then(function(r){return r.json()}).then(function(d){
|
|
1211
|
-
if((d.retry||!(d.duration>0))&&tries++<20){n.textContent="
|
|
1212
|
-
if(!d.ffmpeg){n.textContent="
|
|
1213
|
-
if(!(d.duration>0)){n.textContent="
|
|
1315
|
+
if((d.retry||!(d.duration>0))&&tries++<20){n.textContent=pt("prep");setTimeout(load,2000);return;}
|
|
1316
|
+
if(!d.ffmpeg){n.textContent=pt("ffmpeg");return;}
|
|
1317
|
+
if(!(d.duration>0)){n.textContent=pt("nodur");return;}
|
|
1214
1318
|
var nativeHls=!!v.canPlayType("application/vnd.apple.mpegurl");
|
|
1215
|
-
if(!(window.Hls&&Hls.isSupported())&&!nativeHls){n.textContent="
|
|
1319
|
+
if(!(window.Hls&&Hls.isSupported())&&!nativeHls){n.textContent=pt("noplayer");return;}
|
|
1216
1320
|
tm.textContent="00:00 / "+fmt(d.duration);
|
|
1217
|
-
|
|
1218
|
-
aS.innerHTML="";sS.innerHTML="";
|
|
1219
|
-
var SI=S.filter(function(t){return ["subrip","ass","ssa","srt","mov_text","webvtt","text"].indexOf(t.codec)>=0}).map(function(t){return t.s});
|
|
1220
|
-
A.forEach(function(t){var o=document.createElement("option");o.value=t.a;o.textContent=(t.lang&&t.lang!=="und"?t.lang:"дорожка "+(t.a+1))+(t.title?" — "+t.title:"")+" ["+t.codec+"]";aS.appendChild(o)});
|
|
1221
|
-
if(A.length){aS.value=String(A[0].a);aS.style.display="";if(A.length<2)aS.disabled=true}
|
|
1222
|
-
if(SI.length){var o0=document.createElement("option");o0.value="";o0.textContent="без субтитров";sS.appendChild(o0);
|
|
1223
|
-
S.filter(function(t){return SI.indexOf(t.s)>=0}).forEach(function(t){var o=document.createElement("option");o.value=t.s;o.textContent=(t.lang&&t.lang!=="und"?t.lang:"субтитры "+(t.s+1))+(t.title?" — "+t.title:"");sS.appendChild(o)});sS.value="";sS.style.display=""}
|
|
1321
|
+
lastD=d;fillTracks();
|
|
1224
1322
|
var go=function(keep){var a=aS.options.length?aS.value:0;var t=0;if(keep){t=v.currentTime;if(!isFinite(t)||t<0)t=0}play(B+"/hls/"+ID+"/"+I+"/index.m3u8?a="+encodeURIComponent(a),t)};
|
|
1225
|
-
aS.onchange=function(){go(true)};
|
|
1226
|
-
sS.onchange=function(){var s=sS.value;cues=[];cueDiv.innerHTML="";subUrl=s!==""?B+"/hls/"+ID+"/"+I+"/sub.vtt?si="+encodeURIComponent(s):"";if(subUrl)pollSub()};
|
|
1323
|
+
aS.onchange=function(){if(filling)return;go(true)};
|
|
1324
|
+
sS.onchange=function(){if(filling)return;var s=sS.value;cues=[];cueDiv.innerHTML="";subUrl=s!==""?B+"/hls/"+ID+"/"+I+"/sub.vtt?si="+encodeURIComponent(s):"";if(subUrl)pollSub()};
|
|
1227
1325
|
if(!armed){armed=true;go(false)}
|
|
1228
1326
|
}).catch(function(){if(tries++<20)setTimeout(load,2000)})};
|
|
1229
1327
|
v.addEventListener("playing",function(){n.textContent="";ov.style.display="none";tick()});
|
|
1230
|
-
setInterval(function(){fetch(B+"/api/status").then(function(r){return r.json()}).then(function(list){var t=list.find(function(x){return x.id===ID});if(t&&v.readyState<2)n.textContent=t.speed+" ·
|
|
1328
|
+
setInterval(function(){fetch(B+"/api/status").then(function(r){return r.json()}).then(function(list){var t=list.find(function(x){return x.id===ID});if(t&&v.readyState<2)n.textContent=t.speed+" · "+pt("peers")+" "+t.peers+(t.progress>0?" · "+t.progress+"%":"")}).catch(function(){})},2000);
|
|
1231
1329
|
load();
|
|
1232
1330
|
</script>`);
|
|
1233
1331
|
return;
|
|
@@ -1279,15 +1377,19 @@ async function startWeb(opts, srcArg) {
|
|
|
1279
1377
|
try { server.close(); } catch {}
|
|
1280
1378
|
if (st.hls) for (const h of st.hls.procs.values()) { try { h.proc.kill('SIGKILL'); } catch {} }
|
|
1281
1379
|
try { fs.rmSync(path.join(os.tmpdir(), 'wtui-hls-' + process.pid), { recursive: true, force: true }); } catch {}
|
|
1380
|
+
const bye = setTimeout(() => process.exit(code), 4000);
|
|
1381
|
+
bye.unref();
|
|
1382
|
+
// «Закрыть» и Ctrl+C гасят процесс. Кэш на диске остаётся: destroyStoreOnDestroy выключен.
|
|
1282
1383
|
try { await new Promise(r => client.destroy(r)); } catch {}
|
|
1283
1384
|
process.exit(code);
|
|
1284
1385
|
}
|
|
1386
|
+
st.quit = () => { shutdown(0); };
|
|
1285
1387
|
process.on('SIGINT', () => shutdown(0));
|
|
1286
1388
|
process.on('SIGTERM', () => shutdown(0));
|
|
1287
1389
|
const NET_NOISE = /premature|aborted|EPIPE|ECONNRESET|ETIMEDOUT|socket|stream|duplicate torrent/i;
|
|
1288
1390
|
process.on('unhandledRejection', e => { const m = (e && (e.message || e)) + ''; if (!NET_NOISE.test(m)) console.error('unhandledRejection:', m); });
|
|
1289
1391
|
process.on('uncaughtException', e => { const m = (e && (e.message || e)) + ''; if (NET_NOISE.test(m)) return; console.error('uncaughtException:', m); console.error((e && e.stack || '').split('\\n').slice(0, 8).join('\\n')); });
|
|
1290
|
-
console.log('Ctrl+C
|
|
1392
|
+
console.log('Выход: кнопка «Закрыть» на странице или Ctrl+C. Скачанное остаётся в ' + cacheDir + '. Стирает его только кнопка «Очистить кэш».');
|
|
1291
1393
|
}
|
|
1292
1394
|
|
|
1293
1395
|
// ---- Main ----
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
Changelog
|
|
2
|
-
=========
|
|
3
|
-
|
|
4
|
-
v0.6.0
|
|
5
|
-
------
|
|
6
|
-
|
|
7
|
-
- Updated "devDependencies" versions to fix vulnerability alerts
|
|
8
|
-
- Dropped support of io.js and node.js v0.12.x and lower since new versions of
|
|
9
|
-
"devDependencies" couldn't work with those old node.js versions
|
|
10
|
-
(minimal supported version of node.js now is v4.0.0)
|
|
11
|
-
|
|
12
|
-
v0.5.1
|
|
13
|
-
------
|
|
14
|
-
|
|
15
|
-
- Fix prototype pollution vulnerability (thanks to @mwakerman for the PR)
|
|
16
|
-
- Avoid using deprecated Buffer API (thanks to @ChALkeR for the PR)
|
|
17
|
-
|
|
18
|
-
v0.5.0
|
|
19
|
-
------
|
|
20
|
-
|
|
21
|
-
- Auto-testing provided by Travis CI;
|
|
22
|
-
- Support older Node.JS versions (`v0.11.x` and `v0.10.x`);
|
|
23
|
-
- Removed tests files from npm package.
|
|
24
|
-
|
|
25
|
-
v0.4.2
|
|
26
|
-
------
|
|
27
|
-
|
|
28
|
-
- Fix for `null` as an argument.
|
|
29
|
-
|
|
30
|
-
v0.4.1
|
|
31
|
-
------
|
|
32
|
-
|
|
33
|
-
- Removed test code from <b>npm</b> package
|
|
34
|
-
([see pull request #21](https://github.com/unclechu/node-deep-extend/pull/21));
|
|
35
|
-
- Increased minimal version of Node from `0.4.0` to `0.12.0`
|
|
36
|
-
(because can't run tests on lesser version anyway).
|
|
37
|
-
|
|
38
|
-
v0.4.0
|
|
39
|
-
------
|
|
40
|
-
|
|
41
|
-
- **WARNING!** Broken backward compatibility with `v0.3.x`;
|
|
42
|
-
- Fixed bug with extending arrays instead of cloning;
|
|
43
|
-
- Deep cloning for arrays;
|
|
44
|
-
- Check for own property;
|
|
45
|
-
- Fixed some documentation issues;
|
|
46
|
-
- Strict JS mode.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# HTTP Parser
|
|
2
|
-
|
|
3
|
-
## 0.4.4
|
|
4
|
-
|
|
5
|
-
Made 'maxHeaderSize' configurable.
|
|
6
|
-
|
|
7
|
-
```js
|
|
8
|
-
// Monkey patch before you require http for the first time.
|
|
9
|
-
process.binding('http_parser').HTTPParser = require('http-parser-js').HTTPParser;
|
|
10
|
-
require('http-parser-js').HTTPParser.maxHeaderSize = 1024 * 1024; // 1MB instead of 80kb
|
|
11
|
-
|
|
12
|
-
var http = require('http');
|
|
13
|
-
// ...
|
|
14
|
-
```
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
-
|
|
5
|
-
## [3.0.0](https://github.com/broofa/mime/compare/v2.6.0...v3.0.0) (2021-11-03)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
### ⚠ BREAKING CHANGES
|
|
9
|
-
|
|
10
|
-
* drop support for node < 10.x
|
|
11
|
-
|
|
12
|
-
### Bug Fixes
|
|
13
|
-
|
|
14
|
-
* skypack.dev for direct browser import, fixes [#263](https://github.com/broofa/mime/issues/263) ([41db4c0](https://github.com/broofa/mime/commit/41db4c042ccf50ea7baf3d2160ea37dcca37998d))
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
### update
|
|
18
|
-
|
|
19
|
-
* drop support for node < 10.x ([8857363](https://github.com/broofa/mime/commit/8857363ae0446ed0229b17291cf4483cf801f0d0))
|
|
20
|
-
|
|
21
|
-
## [2.6.0](https://github.com/broofa/mime/compare/v2.5.2...v2.6.0) (2021-11-02)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
### Features
|
|
25
|
-
|
|
26
|
-
* mime-db@1.50.0 ([cef0cc4](https://github.com/broofa/mime/commit/cef0cc484ff6d05ff1e12b54ca3e8b856fbc14d8))
|
|
27
|
-
|
|
28
|
-
### [2.5.2](https://github.com/broofa/mime/compare/v2.5.0...v2.5.2) (2021-02-17)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
### Bug Fixes
|
|
32
|
-
|
|
33
|
-
* update to mime-db@1.46.0, fixes [#253](https://github.com/broofa/mime/issues/253) ([f10e6aa](https://github.com/broofa/mime/commit/f10e6aa62e1356de7e2491d7fb4374c8dac65800))
|
|
34
|
-
|
|
35
|
-
## [2.5.0](https://github.com/broofa/mime/compare/v2.4.7...v2.5.0) (2021-01-16)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### Features
|
|
39
|
-
|
|
40
|
-
* improved CLI ([#244](https://github.com/broofa/mime/issues/244)) ([c8a8356](https://github.com/broofa/mime/commit/c8a8356e3b27f3ef46b64b89b428fdb547b14d5f))
|
|
41
|
-
|
|
42
|
-
### [2.4.7](https://github.com/broofa/mime/compare/v2.4.6...v2.4.7) (2020-12-16)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
### Bug Fixes
|
|
46
|
-
|
|
47
|
-
* update to latest mime-db ([43b09ef](https://github.com/broofa/mime/commit/43b09eff0233eacc449af2b1f99a19ba9e104a44))
|
|
48
|
-
|
|
49
|
-
### [2.4.6](https://github.com/broofa/mime/compare/v2.4.5...v2.4.6) (2020-05-27)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
### Bug Fixes
|
|
53
|
-
|
|
54
|
-
* add cli.js to package.json files ([#237](https://github.com/broofa/mime/issues/237)) ([6c070bc](https://github.com/broofa/mime/commit/6c070bc298fa12a48e2ed126fbb9de641a1e7ebc))
|
|
55
|
-
|
|
56
|
-
### [2.4.5](https://github.com/broofa/mime/compare/v2.4.4...v2.4.5) (2020-05-01)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
### Bug Fixes
|
|
60
|
-
|
|
61
|
-
* fix [#236](https://github.com/broofa/mime/issues/236) ([7f4ecd0](https://github.com/broofa/mime/commit/7f4ecd0d850ed22c9e3bfda2c11fc74e4dde12a7))
|
|
62
|
-
* update to latest mime-db ([c5cb3f2](https://github.com/broofa/mime/commit/c5cb3f2ab8b07642a066efbde1142af1b90c927b))
|
|
63
|
-
|
|
64
|
-
### [2.4.4](https://github.com/broofa/mime/compare/v2.4.3...v2.4.4) (2019-06-07)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
### [2.4.3](https://github.com/broofa/mime/compare/v2.4.2...v2.4.3) (2019-05-15)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
### [2.4.2](https://github.com/broofa/mime/compare/v2.4.1...v2.4.2) (2019-04-07)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
### Bug Fixes
|
|
76
|
-
|
|
77
|
-
* don't use arrow function introduced in 2.4.1 ([2e00b5c](https://github.com/broofa/mime/commit/2e00b5c))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
### [2.4.1](https://github.com/broofa/mime/compare/v2.4.0...v2.4.1) (2019-04-03)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
### Bug Fixes
|
|
85
|
-
|
|
86
|
-
* update MDN and mime-db types ([3e567a9](https://github.com/broofa/mime/commit/3e567a9))
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# [2.4.0](https://github.com/broofa/mime/compare/v2.3.1...v2.4.0) (2018-11-26)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
### Features
|
|
94
|
-
|
|
95
|
-
* Bind exported methods ([9d2a7b8](https://github.com/broofa/mime/commit/9d2a7b8))
|
|
96
|
-
* update to mime-db@1.37.0 ([49e6e41](https://github.com/broofa/mime/commit/49e6e41))
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
### [2.3.1](https://github.com/broofa/mime/compare/v2.3.0...v2.3.1) (2018-04-11)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
### Bug Fixes
|
|
104
|
-
|
|
105
|
-
* fix [#198](https://github.com/broofa/mime/issues/198) ([25ca180](https://github.com/broofa/mime/commit/25ca180))
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# [2.3.0](https://github.com/broofa/mime/compare/v2.2.2...v2.3.0) (2018-04-11)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
### Bug Fixes
|
|
113
|
-
|
|
114
|
-
* fix [#192](https://github.com/broofa/mime/issues/192) ([5c35df6](https://github.com/broofa/mime/commit/5c35df6))
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
### Features
|
|
118
|
-
|
|
119
|
-
* add travis-ci testing ([d64160f](https://github.com/broofa/mime/commit/d64160f))
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
### [2.2.2](https://github.com/broofa/mime/compare/v2.2.1...v2.2.2) (2018-03-30)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
### Bug Fixes
|
|
127
|
-
|
|
128
|
-
* update types files to mime-db@1.32.0 ([85aac16](https://github.com/broofa/mime/commit/85aac16))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
### [2.2.1](https://github.com/broofa/mime/compare/v2.2.0...v2.2.1) (2018-03-30)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
### Bug Fixes
|
|
135
|
-
|
|
136
|
-
* Retain type->extension mappings for non-default types. Fixes [#180](https://github.com/broofa/mime/issues/180) ([b5c83fb](https://github.com/broofa/mime/commit/b5c83fb))
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
# [2.2.0](https://github.com/broofa/mime/compare/v2.1.0...v2.2.0) (2018-01-04)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
### Features
|
|
144
|
-
|
|
145
|
-
* Retain type->extension mappings for non-default types. Fixes [#180](https://github.com/broofa/mime/issues/180) ([10f82ac](https://github.com/broofa/mime/commit/10f82ac))
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# [2.1.0](https://github.com/broofa/mime/compare/v2.0.5...v2.1.0) (2017-12-22)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
### Features
|
|
153
|
-
|
|
154
|
-
* Upgrade to mime-db@1.32.0. Fixes [#185](https://github.com/broofa/mime/issues/185) ([3f775ba](https://github.com/broofa/mime/commit/3f775ba))
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
### [2.0.5](https://github.com/broofa/mime/compare/v2.0.1...v2.0.5) (2017-12-22)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
### Bug Fixes
|
|
162
|
-
|
|
163
|
-
* ES5 support (back to node v0.4) ([f14ccb6](https://github.com/broofa/mime/commit/f14ccb6))
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# Changelog
|
|
168
|
-
|
|
169
|
-
### v2.0.4 (24/11/2017)
|
|
170
|
-
- [**closed**] Switch to mime-score module for resolving extension contention issues. [#182](https://github.com/broofa/mime/issues/182)
|
|
171
|
-
- [**closed**] Update mime-db to 1.31.0 in v1.x branch [#181](https://github.com/broofa/mime/issues/181)
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## v1.5.0 (22/11/2017)
|
|
176
|
-
- [**closed**] need ES5 version ready in npm package [#179](https://github.com/broofa/mime/issues/179)
|
|
177
|
-
- [**closed**] mime-db no trace of iWork - pages / numbers / etc. [#178](https://github.com/broofa/mime/issues/178)
|
|
178
|
-
- [**closed**] How it works in brownser ? [#176](https://github.com/broofa/mime/issues/176)
|
|
179
|
-
- [**closed**] Missing `./Mime` [#175](https://github.com/broofa/mime/issues/175)
|
|
180
|
-
- [**closed**] Vulnerable Regular Expression [#167](https://github.com/broofa/mime/issues/167)
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
### v2.0.3 (25/09/2017)
|
|
185
|
-
*No changelog for this release.*
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
### v1.4.1 (25/09/2017)
|
|
190
|
-
- [**closed**] Issue when bundling with webpack [#172](https://github.com/broofa/mime/issues/172)
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
### v2.0.2 (15/09/2017)
|
|
195
|
-
- [**V2**] fs.readFileSync is not a function [#165](https://github.com/broofa/mime/issues/165)
|
|
196
|
-
- [**closed**] The extension for video/quicktime should map to .mov, not .qt [#164](https://github.com/broofa/mime/issues/164)
|
|
197
|
-
- [**V2**] [v2 Feedback request] Mime class API [#163](https://github.com/broofa/mime/issues/163)
|
|
198
|
-
- [**V2**] [v2 Feedback request] Resolving conflicts over extensions [#162](https://github.com/broofa/mime/issues/162)
|
|
199
|
-
- [**V2**] Allow callers to load module with official, full, or no defined types. [#161](https://github.com/broofa/mime/issues/161)
|
|
200
|
-
- [**V2**] Use "facets" to resolve extension conflicts [#160](https://github.com/broofa/mime/issues/160)
|
|
201
|
-
- [**V2**] Remove fs and path dependencies [#152](https://github.com/broofa/mime/issues/152)
|
|
202
|
-
- [**V2**] Default content-type should not be application/octet-stream [#139](https://github.com/broofa/mime/issues/139)
|
|
203
|
-
- [**V2**] reset mime-types [#124](https://github.com/broofa/mime/issues/124)
|
|
204
|
-
- [**V2**] Extensionless paths should return null or false [#113](https://github.com/broofa/mime/issues/113)
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
### v2.0.1 (14/09/2017)
|
|
209
|
-
- [**closed**] Changelog for v2.0 does not mention breaking changes [#171](https://github.com/broofa/mime/issues/171)
|
|
210
|
-
- [**closed**] MIME breaking with 'class' declaration as it is without 'use strict mode' [#170](https://github.com/broofa/mime/issues/170)
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## v2.0.0 (12/09/2017)
|
|
215
|
-
- [**closed**] woff and woff2 [#168](https://github.com/broofa/mime/issues/168)
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## v1.4.0 (28/08/2017)
|
|
220
|
-
- [**closed**] support for ac3 voc files [#159](https://github.com/broofa/mime/issues/159)
|
|
221
|
-
- [**closed**] Help understanding change from application/xml to text/xml [#158](https://github.com/broofa/mime/issues/158)
|
|
222
|
-
- [**closed**] no longer able to override mimetype [#157](https://github.com/broofa/mime/issues/157)
|
|
223
|
-
- [**closed**] application/vnd.adobe.photoshop [#147](https://github.com/broofa/mime/issues/147)
|
|
224
|
-
- [**closed**] Directories should appear as something other than application/octet-stream [#135](https://github.com/broofa/mime/issues/135)
|
|
225
|
-
- [**closed**] requested features [#131](https://github.com/broofa/mime/issues/131)
|
|
226
|
-
- [**closed**] Make types.json loading optional? [#129](https://github.com/broofa/mime/issues/129)
|
|
227
|
-
- [**closed**] Cannot find module './types.json' [#120](https://github.com/broofa/mime/issues/120)
|
|
228
|
-
- [**V2**] .wav files show up as "audio/x-wav" instead of "audio/x-wave" [#118](https://github.com/broofa/mime/issues/118)
|
|
229
|
-
- [**closed**] Don't be a pain in the ass for node community [#108](https://github.com/broofa/mime/issues/108)
|
|
230
|
-
- [**closed**] don't make default_type global [#78](https://github.com/broofa/mime/issues/78)
|
|
231
|
-
- [**closed**] mime.extension() fails if the content-type is parameterized [#74](https://github.com/broofa/mime/issues/74)
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
### v1.3.6 (11/05/2017)
|
|
236
|
-
- [**closed**] .md should be text/markdown as of March 2016 [#154](https://github.com/broofa/mime/issues/154)
|
|
237
|
-
- [**closed**] Error while installing mime [#153](https://github.com/broofa/mime/issues/153)
|
|
238
|
-
- [**closed**] application/manifest+json [#149](https://github.com/broofa/mime/issues/149)
|
|
239
|
-
- [**closed**] Dynamic adaptive streaming over HTTP (DASH) file extension typo [#141](https://github.com/broofa/mime/issues/141)
|
|
240
|
-
- [**closed**] charsets image/png undefined [#140](https://github.com/broofa/mime/issues/140)
|
|
241
|
-
- [**closed**] Mime-db dependency out of date [#130](https://github.com/broofa/mime/issues/130)
|
|
242
|
-
- [**closed**] how to support plist? [#126](https://github.com/broofa/mime/issues/126)
|
|
243
|
-
- [**closed**] how does .types file format look like? [#123](https://github.com/broofa/mime/issues/123)
|
|
244
|
-
- [**closed**] Feature: support for expanding MIME patterns [#121](https://github.com/broofa/mime/issues/121)
|
|
245
|
-
- [**closed**] DEBUG_MIME doesn't work [#117](https://github.com/broofa/mime/issues/117)
|
|
246
|
-
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
### v1.3.4 (06/02/2015)
|
|
250
|
-
*No changelog for this release.*
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
### v1.3.3 (06/02/2015)
|
|
255
|
-
*No changelog for this release.*
|
|
256
|
-
|
|
257
|
-
---
|
|
258
|
-
|
|
259
|
-
### v1.3.1 (05/02/2015)
|
|
260
|
-
- [**closed**] Consider adding support for Handlebars .hbs file ending [#111](https://github.com/broofa/mime/issues/111)
|
|
261
|
-
- [**closed**] Consider adding support for hjson. [#110](https://github.com/broofa/mime/issues/110)
|
|
262
|
-
- [**closed**] Add mime type for Opus audio files [#94](https://github.com/broofa/mime/issues/94)
|
|
263
|
-
- [**closed**] Consider making the `Requesting New Types` information more visible [#77](https://github.com/broofa/mime/issues/77)
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## v1.3.0 (05/02/2015)
|
|
268
|
-
- [**closed**] Add common name? [#114](https://github.com/broofa/mime/issues/114)
|
|
269
|
-
- [**closed**] application/x-yaml [#104](https://github.com/broofa/mime/issues/104)
|
|
270
|
-
- [**closed**] Add mime type for WOFF file format 2.0 [#102](https://github.com/broofa/mime/issues/102)
|
|
271
|
-
- [**closed**] application/x-msi for .msi [#99](https://github.com/broofa/mime/issues/99)
|
|
272
|
-
- [**closed**] Add mimetype for gettext translation files [#98](https://github.com/broofa/mime/issues/98)
|
|
273
|
-
- [**closed**] collaborators [#88](https://github.com/broofa/mime/issues/88)
|
|
274
|
-
- [**closed**] getting errot in installation of mime module...any1 can help? [#87](https://github.com/broofa/mime/issues/87)
|
|
275
|
-
- [**closed**] should application/json's charset be utf8? [#86](https://github.com/broofa/mime/issues/86)
|
|
276
|
-
- [**closed**] Add "license" and "licenses" to package.json [#81](https://github.com/broofa/mime/issues/81)
|
|
277
|
-
- [**closed**] lookup with extension-less file on Windows returns wrong type [#68](https://github.com/broofa/mime/issues/68)
|
|
278
|
-
|
|
279
|
-
---
|
|
280
|
-
|
|
281
|
-
### v1.2.11 (15/08/2013)
|
|
282
|
-
- [**closed**] Update mime.types [#65](https://github.com/broofa/mime/issues/65)
|
|
283
|
-
- [**closed**] Publish a new version [#63](https://github.com/broofa/mime/issues/63)
|
|
284
|
-
- [**closed**] README should state upfront that "application/octet-stream" is default for unknown extension [#55](https://github.com/broofa/mime/issues/55)
|
|
285
|
-
- [**closed**] Suggested improvement to the charset API [#52](https://github.com/broofa/mime/issues/52)
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
### v1.2.10 (25/07/2013)
|
|
290
|
-
- [**closed**] Mime type for woff files should be application/font-woff and not application/x-font-woff [#62](https://github.com/broofa/mime/issues/62)
|
|
291
|
-
- [**closed**] node.types in conflict with mime.types [#51](https://github.com/broofa/mime/issues/51)
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
### v1.2.9 (17/01/2013)
|
|
296
|
-
- [**closed**] Please update "mime" NPM [#49](https://github.com/broofa/mime/issues/49)
|
|
297
|
-
- [**closed**] Please add semicolon [#46](https://github.com/broofa/mime/issues/46)
|
|
298
|
-
- [**closed**] parse full mime types [#43](https://github.com/broofa/mime/issues/43)
|
|
299
|
-
|
|
300
|
-
---
|
|
301
|
-
|
|
302
|
-
### v1.2.8 (10/01/2013)
|
|
303
|
-
- [**closed**] /js directory mime is application/javascript. Is it correct? [#47](https://github.com/broofa/mime/issues/47)
|
|
304
|
-
- [**closed**] Add mime types for lua code. [#45](https://github.com/broofa/mime/issues/45)
|
|
305
|
-
|
|
306
|
-
---
|
|
307
|
-
|
|
308
|
-
### v1.2.7 (19/10/2012)
|
|
309
|
-
- [**closed**] cannot install 1.2.7 via npm [#41](https://github.com/broofa/mime/issues/41)
|
|
310
|
-
- [**closed**] Transfer ownership to @broofa [#36](https://github.com/broofa/mime/issues/36)
|
|
311
|
-
- [**closed**] it's wrong to set charset to UTF-8 for text [#30](https://github.com/broofa/mime/issues/30)
|
|
312
|
-
- [**closed**] Allow multiple instances of MIME types container [#27](https://github.com/broofa/mime/issues/27)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
### 2.0.1 - 2020-01-DD
|
|
3
|
-
|
|
4
|
-
- add support for deprecated IPv4 compatible IPv6 addresses #142
|
|
5
|
-
- drop node 8 testing, add v14
|
|
6
|
-
- fix parseInt for decimal, octal, hex
|
|
7
|
-
- add support for classful (2,3 octet) IPv4
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
### 2.0.0 - 2019-10-13
|
|
11
|
-
|
|
12
|
-
- use es6 templates instead of concatenated strings
|
|
13
|
-
- lint: update tests with no-var
|
|
14
|
-
- lint: allow no-cond-assign with extra parens
|
|
15
|
-
- es6: replace var with const/let
|
|
16
|
-
- update README with es6 examples #125
|
|
17
|
-
- replace nodeunit with mocha
|
|
18
|
-
- rewrite in JS, drop CoffeeScript
|
|
19
|
-
|
|
20
|
-
### 1.9.1 - 2019-07-03
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# Changes
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
## 2.0.2
|
|
5
|
-
|
|
6
|
-
* Rename bin to `node-which`
|
|
7
|
-
|
|
8
|
-
## 2.0.1
|
|
9
|
-
|
|
10
|
-
* generate changelog and publish on version bump
|
|
11
|
-
* enforce 100% test coverage
|
|
12
|
-
* Promise interface
|
|
13
|
-
|
|
14
|
-
## 2.0.0
|
|
15
|
-
|
|
16
|
-
* Parallel tests, modern JavaScript, and drop support for node < 8
|
|
17
|
-
|
|
18
|
-
## 1.3.1
|
|
19
|
-
|
|
20
|
-
* update deps
|
|
21
|
-
* update travis
|
|
22
|
-
|
|
23
|
-
## v1.3.0
|
|
24
|
-
|
|
25
|
-
* Add nothrow option to which.sync
|
|
26
|
-
* update tap
|
|
27
|
-
|
|
28
|
-
## v1.2.14
|
|
29
|
-
|
|
30
|
-
* appveyor: drop node 5 and 0.x
|
|
31
|
-
* travis-ci: add node 6, drop 0.x
|
|
32
|
-
|
|
33
|
-
## v1.2.13
|
|
34
|
-
|
|
35
|
-
* test: Pass missing option to pass on windows
|
|
36
|
-
* update tap
|
|
37
|
-
* update isexe to 2.0.0
|
|
38
|
-
* neveragain.tech pledge request
|
|
39
|
-
|
|
40
|
-
## v1.2.12
|
|
41
|
-
|
|
42
|
-
* Removed unused require
|
|
43
|
-
|
|
44
|
-
## v1.2.11
|
|
45
|
-
|
|
46
|
-
* Prevent changelog script from being included in package
|
|
47
|
-
|
|
48
|
-
## v1.2.10
|
|
49
|
-
|
|
50
|
-
* Use env.PATH only, not env.Path
|
|
51
|
-
|
|
52
|
-
## v1.2.9
|
|
53
|
-
|
|
54
|
-
* fix for paths starting with ../
|
|
55
|
-
* Remove unused `is-absolute` module
|
|
56
|
-
|
|
57
|
-
## v1.2.8
|
|
58
|
-
|
|
59
|
-
* bullet items in changelog that contain (but don't start with) #
|
|
60
|
-
|
|
61
|
-
## v1.2.7
|
|
62
|
-
|
|
63
|
-
* strip 'update changelog' changelog entries out of changelog
|
|
64
|
-
|
|
65
|
-
## v1.2.6
|
|
66
|
-
|
|
67
|
-
* make the changelog bulleted
|
|
68
|
-
|
|
69
|
-
## v1.2.5
|
|
70
|
-
|
|
71
|
-
* make a changelog, and keep it up to date
|
|
72
|
-
* don't include tests in package
|
|
73
|
-
* Properly handle relative-path executables
|
|
74
|
-
* appveyor
|
|
75
|
-
* Attach error code to Not Found error
|
|
76
|
-
* Make tests pass on Windows
|
|
77
|
-
|
|
78
|
-
## v1.2.4
|
|
79
|
-
|
|
80
|
-
* Fix typo
|
|
81
|
-
|
|
82
|
-
## v1.2.3
|
|
83
|
-
|
|
84
|
-
* update isexe, fix regression in pathExt handling
|
|
85
|
-
|
|
86
|
-
## v1.2.2
|
|
87
|
-
|
|
88
|
-
* update deps, use isexe module, test windows
|
|
89
|
-
|
|
90
|
-
## v1.2.1
|
|
91
|
-
|
|
92
|
-
* Sometimes windows PATH entries are quoted
|
|
93
|
-
* Fixed a bug in the check for group and user mode bits. This bug was introduced during refactoring for supporting strict mode.
|
|
94
|
-
* doc cli
|
|
95
|
-
|
|
96
|
-
## v1.2.0
|
|
97
|
-
|
|
98
|
-
* Add support for opt.all and -as cli flags
|
|
99
|
-
* test the bin
|
|
100
|
-
* update travis
|
|
101
|
-
* Allow checking for multiple programs in bin/which
|
|
102
|
-
* tap 2
|
|
103
|
-
|
|
104
|
-
## v1.1.2
|
|
105
|
-
|
|
106
|
-
* travis
|
|
107
|
-
* Refactored and fixed undefined error on Windows
|
|
108
|
-
* Support strict mode
|
|
109
|
-
|
|
110
|
-
## v1.1.1
|
|
111
|
-
|
|
112
|
-
* test +g exes against secondary groups, if available
|
|
113
|
-
* Use windows exe semantics on cygwin & msys
|
|
114
|
-
* cwd should be first in path on win32, not last
|
|
115
|
-
* Handle lower-case 'env.Path' on Windows
|
|
116
|
-
* Update docs
|
|
117
|
-
* use single-quotes
|
|
118
|
-
|
|
119
|
-
## v1.1.0
|
|
120
|
-
|
|
121
|
-
* Add tests, depend on is-absolute
|
|
122
|
-
|
|
123
|
-
## v1.0.9
|
|
124
|
-
|
|
125
|
-
* which.js: root is allowed to execute files owned by anyone
|
|
126
|
-
|
|
127
|
-
## v1.0.8
|
|
128
|
-
|
|
129
|
-
* don't use graceful-fs
|
|
130
|
-
|
|
131
|
-
## v1.0.7
|
|
132
|
-
|
|
133
|
-
* add license to package.json
|
|
134
|
-
|
|
135
|
-
## v1.0.6
|
|
136
|
-
|
|
137
|
-
* isc license
|
|
138
|
-
|
|
139
|
-
## 1.0.5
|
|
140
|
-
|
|
141
|
-
* Awful typo
|
|
142
|
-
|
|
143
|
-
## 1.0.4
|
|
144
|
-
|
|
145
|
-
* Test for path absoluteness properly
|
|
146
|
-
* win: Allow '' as a pathext if cmd has a . in it
|
|
147
|
-
|
|
148
|
-
## 1.0.3
|
|
149
|
-
|
|
150
|
-
* Remove references to execPath
|
|
151
|
-
* Make `which.sync()` work on Windows by honoring the PATHEXT variable.
|
|
152
|
-
* Make `isExe()` always return true on Windows.
|
|
153
|
-
* MIT
|
|
154
|
-
|
|
155
|
-
## 1.0.2
|
|
156
|
-
|
|
157
|
-
* Only files can be exes
|
|
158
|
-
|
|
159
|
-
## 1.0.1
|
|
160
|
-
|
|
161
|
-
* Respect the PATHEXT env for win32 support
|
|
162
|
-
* should 0755 the bin
|
|
163
|
-
* binary
|
|
164
|
-
* guts
|
|
165
|
-
* package
|
|
166
|
-
* 1st
|