Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5649d26077 | ||
|
|
92f923effe | ||
|
|
0d46d548b9 | ||
|
|
062df3f0c3 | ||
|
|
789fb53b8e | ||
|
|
351db5a18f | ||
|
|
aabbd271c8 | ||
|
|
aae8e0171e | ||
|
|
45827a2458 | ||
|
|
726030296f | ||
|
|
6659ab3881 | ||
|
|
c6a103609e | ||
|
|
c6b3f035e5 | ||
|
|
2b0a7e378e | ||
|
|
b75ce909c8 | ||
|
|
229c3f5dab | ||
|
|
ec73094506 | ||
|
|
c7650c9326 | ||
|
|
d94c6d4e72 | ||
|
|
3cc8760733 | ||
|
|
a2f6973495 | ||
|
|
f8648fa651 | ||
|
|
177aa038df | ||
|
|
e0a14ec881 | ||
|
|
9366512f2f | ||
|
|
ea38b8041a | ||
|
|
f1870daf0d | ||
|
|
9722441aad | ||
|
|
9d014087f4 | ||
|
|
83b4038b85 | ||
|
|
1e0a448feb | ||
|
|
fb81de3b36 | ||
|
|
aa4f352301 | ||
|
|
f1a1c2ea45 | ||
|
|
6249bd4163 | ||
|
|
2579dc64ce | ||
|
|
356512270a | ||
|
|
bed27f2b43 | ||
|
|
54013d861b | ||
|
|
ec100210dc | ||
|
|
3ab1acf32c | ||
|
|
8c28266418 | ||
|
|
7f8b8dcb92 | ||
|
|
6dd39811d4 | ||
|
|
35e2138e3e | ||
|
|
239b4e9fe6 | ||
|
|
2fcd0e7e72 | ||
|
|
357347ce3a | ||
|
|
36dc1107fb |
34
README.md
34
README.md
@@ -20,6 +20,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
|
|
||||||
* top
|
* top
|
||||||
* [quickstart](#quickstart)
|
* [quickstart](#quickstart)
|
||||||
|
* [on debian](#on-debian)
|
||||||
* [notes](#notes)
|
* [notes](#notes)
|
||||||
* [status](#status)
|
* [status](#status)
|
||||||
* [bugs](#bugs)
|
* [bugs](#bugs)
|
||||||
@@ -68,6 +69,7 @@ some recommended options:
|
|||||||
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
|
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
|
||||||
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
|
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
|
||||||
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
|
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
|
||||||
|
* the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
|
||||||
* replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
|
* replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
|
||||||
* in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it
|
* in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it
|
||||||
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
|
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
|
||||||
@@ -77,6 +79,19 @@ you may also want these, especially on servers:
|
|||||||
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
|
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
|
||||||
|
|
||||||
|
|
||||||
|
### on debian
|
||||||
|
|
||||||
|
recommended steps to enable audio metadata and thumbnails (from images and videos):
|
||||||
|
|
||||||
|
* as root, run the following:
|
||||||
|
`apt install python3 python3-pip python3-dev ffmpeg`
|
||||||
|
|
||||||
|
* then, as the user which will be running copyparty (so hopefully not root), run this:
|
||||||
|
`python3 -m pip install --user -U Pillow pillow-avif-plugin`
|
||||||
|
|
||||||
|
(skipped `pyheif-pillow-opener` because apparently debian is too old to build it)
|
||||||
|
|
||||||
|
|
||||||
## notes
|
## notes
|
||||||
|
|
||||||
general:
|
general:
|
||||||
@@ -111,7 +126,7 @@ summary: all planned features work! now please enjoy the bloatening
|
|||||||
* ☑ FUSE client (read-only)
|
* ☑ FUSE client (read-only)
|
||||||
* browser
|
* browser
|
||||||
* ☑ tree-view
|
* ☑ tree-view
|
||||||
* ☑ audio player
|
* ☑ audio player (with OS media controls)
|
||||||
* ☑ thumbnails
|
* ☑ thumbnails
|
||||||
* ☑ images using Pillow
|
* ☑ images using Pillow
|
||||||
* ☑ videos using FFmpeg
|
* ☑ videos using FFmpeg
|
||||||
@@ -168,25 +183,28 @@ summary: all planned features work! now please enjoy the bloatening
|
|||||||
## hotkeys
|
## hotkeys
|
||||||
|
|
||||||
the browser has the following hotkeys
|
the browser has the following hotkeys
|
||||||
|
* `B` toggle breadcrumbs / directory tree
|
||||||
* `I/K` prev/next folder
|
* `I/K` prev/next folder
|
||||||
* `P` parent folder
|
* `M` parent folder
|
||||||
* `G` toggle list / grid view
|
* `G` toggle list / grid view
|
||||||
* `T` toggle thumbnails / icons
|
* `T` toggle thumbnails / icons
|
||||||
* when playing audio:
|
* when playing audio:
|
||||||
* `0..9` jump to 10%..90%
|
* `0..9` jump to 10%..90%
|
||||||
* `U/O` skip 10sec back/forward
|
* `U/O` skip 10sec back/forward
|
||||||
* `J/L` prev/next song
|
* `J/L` prev/next song
|
||||||
* `M` play/pause (also starts playing the folder)
|
* `P` play/pause (also starts playing the folder)
|
||||||
|
* when tree-sidebar is open:
|
||||||
|
* `A/D` adjust tree width
|
||||||
* in the grid view:
|
* in the grid view:
|
||||||
* `S` toggle multiselect
|
* `S` toggle multiselect
|
||||||
* `A/D` zoom
|
* shift+`A/D` zoom
|
||||||
|
|
||||||
|
|
||||||
## tree-mode
|
## tree-mode
|
||||||
|
|
||||||
by default there's a breadcrumbs path; you can replace this with a tree-browser sidebar thing by clicking the 🌲
|
by default there's a breadcrumbs path; you can replace this with a tree-browser sidebar thing by clicking the `🌲` or pressing the `B` hotkey
|
||||||
|
|
||||||
click `[-]` and `[+]` to adjust the size, and the `[a]` toggles if the tree should widen dynamically as you go deeper or stay fixed-size
|
click `[-]` and `[+]` (or hotkeys `A`/`D`) to adjust the size, and the `[a]` toggles if the tree should widen dynamically as you go deeper or stay fixed-size
|
||||||
|
|
||||||
|
|
||||||
## thumbnails
|
## thumbnails
|
||||||
@@ -197,6 +215,8 @@ it does static images with Pillow and uses FFmpeg for video files, so you may wa
|
|||||||
|
|
||||||
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
|
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
|
||||||
|
|
||||||
|
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
||||||
|
|
||||||
|
|
||||||
## zip downloads
|
## zip downloads
|
||||||
|
|
||||||
@@ -280,6 +300,8 @@ up2k has saved a few uploads from becoming corrupted in-transfer already; caught
|
|||||||
|
|
||||||
* you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&t=1:20` after the `.../#af-c8960dab`
|
* you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&t=1:20` after the `.../#af-c8960dab`
|
||||||
|
|
||||||
|
* if you are using media hotkeys to switch songs and are getting tired of seeing the OSD popup which Windows doesn't let you disable, consider https://ocv.me/dev/?media-osd-bgone.ps1
|
||||||
|
|
||||||
|
|
||||||
# searching
|
# searching
|
||||||
|
|
||||||
|
|||||||
@@ -48,15 +48,16 @@ you could replace winfsp with [dokan](https://github.com/dokan-dev/dokany/releas
|
|||||||
|
|
||||||
|
|
||||||
# [`dbtool.py`](dbtool.py)
|
# [`dbtool.py`](dbtool.py)
|
||||||
upgrade utility which can show db info and help transfer data between databases, for example when a new version of copyparty recommends to wipe the DB and reindex because it now collects additional metadata during analysis, but you have some really expensive `-mtp` parsers and want to copy over the tags from the old db
|
upgrade utility which can show db info and help transfer data between databases, for example when a new version of copyparty is incompatible with the old DB and automatically rebuilds the DB from scratch, but you have some really expensive `-mtp` parsers and want to copy over the tags from the old db
|
||||||
|
|
||||||
for that example (upgrading to v0.11.0), first move the old db aside, launch copyparty, let it rebuild the db until the point where it starts running mtp (colored messages as it adds the mtp tags), then CTRL-C and patch in the old mtp tags from the old db instead
|
for that example (upgrading to v0.11.20), first launch the new version of copyparty like usual, let it make a backup of the old db and rebuild the new db until the point where it starts running mtp (colored messages as it adds the mtp tags), that's when you hit CTRL-C and patch in the old mtp tags from the old db instead
|
||||||
|
|
||||||
so assuming you have `-mtp` parsers to provide the tags `key` and `.bpm`:
|
so assuming you have `-mtp` parsers to provide the tags `key` and `.bpm`:
|
||||||
|
|
||||||
```
|
```
|
||||||
~/bin/dbtool.py -ls up2k.db
|
cd /mnt/nas/music/.hist
|
||||||
~/bin/dbtool.py -src up2k.db.v0.10.22 up2k.db -cmp
|
~/src/copyparty/bin/dbtool.py -ls up2k.db
|
||||||
~/bin/dbtool.py -src up2k.db.v0.10.22 up2k.db -rm-mtp-flag -copy key
|
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -cmp
|
||||||
~/bin/dbtool.py -src up2k.db.v0.10.22 up2k.db -rm-mtp-flag -copy .bpm -vac
|
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy key
|
||||||
|
~/src/copyparty/bin/dbtool.py -src up2k.*.v3 up2k.db -rm-mtp-flag -copy .bpm -vac
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
det(tf)
|
det(tf)
|
||||||
except:
|
except:
|
||||||
pass
|
pass # mute
|
||||||
finally:
|
finally:
|
||||||
os.unlink(tf)
|
os.unlink(tf)
|
||||||
|
|
||||||
|
|||||||
123
bin/mtag/audio-key-slicing.py
Executable file
123
bin/mtag/audio-key-slicing.py
Executable file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
import keyfinder
|
||||||
|
|
||||||
|
from copyparty.util import fsenc
|
||||||
|
|
||||||
|
"""
|
||||||
|
dep: github/mixxxdj/libkeyfinder
|
||||||
|
dep: pypi/keyfinder
|
||||||
|
dep: ffmpeg
|
||||||
|
|
||||||
|
note: this is a janky edition of the regular audio-key.py,
|
||||||
|
slicing the files at 20sec intervals and keeping 5sec from each,
|
||||||
|
surprisingly accurate but still garbage (446 ok, 69 bad, 13% miss)
|
||||||
|
|
||||||
|
it is fast tho
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_duration():
|
||||||
|
# TODO provide ffprobe tags to mtp as json
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
dur = sp.check_output([
|
||||||
|
"ffprobe",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-show_streams",
|
||||||
|
"-show_format",
|
||||||
|
fsenc(sys.argv[1])
|
||||||
|
])
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
dur = dur.decode("ascii", "replace").split("\n")
|
||||||
|
dur = [x.split("=")[1] for x in dur if x.startswith("duration=")]
|
||||||
|
dur = [float(x) for x in dur if re.match(r"^[0-9\.,]+$", x)]
|
||||||
|
return list(sorted(dur))[-1] if dur else None
|
||||||
|
|
||||||
|
|
||||||
|
def get_segs(dur):
|
||||||
|
# keep first 5s of each 20s,
|
||||||
|
# keep entire last segment
|
||||||
|
ofs = 0
|
||||||
|
segs = []
|
||||||
|
while True:
|
||||||
|
seg = [ofs, 5]
|
||||||
|
segs.append(seg)
|
||||||
|
if dur - ofs < 20:
|
||||||
|
seg[-1] = int(dur - seg[0])
|
||||||
|
break
|
||||||
|
|
||||||
|
ofs += 20
|
||||||
|
|
||||||
|
return segs
|
||||||
|
|
||||||
|
|
||||||
|
def slice(tf):
|
||||||
|
dur = get_duration()
|
||||||
|
dur = min(dur, 600) # max 10min
|
||||||
|
segs = get_segs(dur)
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-nostdin",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-y"
|
||||||
|
]
|
||||||
|
|
||||||
|
for seg in segs:
|
||||||
|
cmd.extend([
|
||||||
|
"-ss", str(seg[0]),
|
||||||
|
"-i", fsenc(sys.argv[1])
|
||||||
|
])
|
||||||
|
|
||||||
|
filt = ""
|
||||||
|
for n, seg in enumerate(segs):
|
||||||
|
filt += "[{}:a:0]atrim=duration={}[a{}]; ".format(n, seg[1], n)
|
||||||
|
|
||||||
|
prev = "a0"
|
||||||
|
for n in range(1, len(segs)):
|
||||||
|
nxt = "b{}".format(n)
|
||||||
|
filt += "[{}][a{}]acrossfade=d=0.5[{}]; ".format(prev, n, nxt)
|
||||||
|
prev = nxt
|
||||||
|
|
||||||
|
cmd.extend([
|
||||||
|
"-filter_complex", filt[:-2],
|
||||||
|
"-map", "[{}]".format(nxt),
|
||||||
|
"-sample_fmt", "s16",
|
||||||
|
tf
|
||||||
|
])
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
# print(cmd)
|
||||||
|
sp.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def det(tf):
|
||||||
|
slice(tf)
|
||||||
|
print(keyfinder.key(tf).camelot())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".flac", delete=False) as f:
|
||||||
|
f.write(b"h")
|
||||||
|
tf = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
det(tf)
|
||||||
|
finally:
|
||||||
|
os.unlink(tf)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,18 +1,54 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import subprocess as sp
|
||||||
import keyfinder
|
import keyfinder
|
||||||
|
|
||||||
|
from copyparty.util import fsenc
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dep: github/mixxxdj/libkeyfinder
|
dep: github/mixxxdj/libkeyfinder
|
||||||
dep: pypi/keyfinder
|
dep: pypi/keyfinder
|
||||||
dep: ffmpeg
|
dep: ffmpeg
|
||||||
|
|
||||||
note: cannot fsenc
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# tried trimming the first/last 5th, bad idea,
|
||||||
|
# misdetects 9a law field (Sphere Caliber) as 10b,
|
||||||
|
# obvious when mixing 9a ghostly parapara ship
|
||||||
|
|
||||||
|
|
||||||
|
def det(tf):
|
||||||
|
# fmt: off
|
||||||
|
sp.check_call([
|
||||||
|
"ffmpeg",
|
||||||
|
"-nostdin",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-y", "-i", fsenc(sys.argv[1]),
|
||||||
|
"-t", "300",
|
||||||
|
"-sample_fmt", "s16",
|
||||||
|
tf
|
||||||
|
])
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
print(keyfinder.key(tf).camelot())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".flac", delete=False) as f:
|
||||||
|
f.write(b"h")
|
||||||
|
tf = f.name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print(keyfinder.key(sys.argv[1]).camelot())
|
det(tf)
|
||||||
except:
|
except:
|
||||||
pass
|
pass # mute
|
||||||
|
finally:
|
||||||
|
os.unlink(tf)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ def main(argv=None):
|
|||||||
+ " (if you crash with codec errors then that is why)"
|
+ " (if you crash with codec errors then that is why)"
|
||||||
)
|
)
|
||||||
|
|
||||||
if WINDOWS and sys.version_info < (3, 6):
|
if sys.version_info < (3, 6):
|
||||||
al.no_scandir = True
|
al.no_scandir = True
|
||||||
|
|
||||||
# signal.signal(signal.SIGINT, sighandler)
|
# signal.signal(signal.SIGINT, sighandler)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (0, 11, 20)
|
VERSION = (0, 11, 28)
|
||||||
CODENAME = "the grid"
|
CODENAME = "the grid"
|
||||||
BUILD_DT = (2021, 6, 20)
|
BUILD_DT = (2021, 6, 28)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|||||||
@@ -693,6 +693,11 @@ class AuthSrv(object):
|
|||||||
self.user = user
|
self.user = user
|
||||||
self.iuser = {v: k for k, v in user.items()}
|
self.iuser = {v: k for k, v in user.items()}
|
||||||
|
|
||||||
|
self.re_pwd = None
|
||||||
|
pwds = [re.escape(x) for x in self.iuser.keys()]
|
||||||
|
if pwds:
|
||||||
|
self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
|
||||||
|
|
||||||
# import pprint
|
# import pprint
|
||||||
# pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
|
# pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import json
|
|||||||
import string
|
import string
|
||||||
import socket
|
import socket
|
||||||
import ctypes
|
import ctypes
|
||||||
import traceback
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
@@ -50,12 +49,21 @@ class HttpCli(object):
|
|||||||
self.tls = hasattr(self.s, "cipher")
|
self.tls = hasattr(self.s, "cipher")
|
||||||
|
|
||||||
self.bufsz = 1024 * 32
|
self.bufsz = 1024 * 32
|
||||||
|
self.hint = None
|
||||||
self.absolute_urls = False
|
self.absolute_urls = False
|
||||||
self.out_headers = {"Access-Control-Allow-Origin": "*"}
|
self.out_headers = {"Access-Control-Allow-Origin": "*"}
|
||||||
|
|
||||||
def log(self, msg, c=0):
|
def log(self, msg, c=0):
|
||||||
|
ptn = self.asrv.re_pwd
|
||||||
|
if ptn and ptn.search(msg):
|
||||||
|
msg = ptn.sub(self.unpwd, msg)
|
||||||
|
|
||||||
self.log_func(self.log_src, msg, c)
|
self.log_func(self.log_src, msg, c)
|
||||||
|
|
||||||
|
def unpwd(self, m):
|
||||||
|
a, b = m.groups()
|
||||||
|
return "=\033[7m {} \033[27m{}".format(self.asrv.iuser[a], b)
|
||||||
|
|
||||||
def _check_nonfatal(self, ex):
|
def _check_nonfatal(self, ex):
|
||||||
return ex.code < 400 or ex.code in [404, 429]
|
return ex.code < 400 or ex.code in [404, 429]
|
||||||
|
|
||||||
@@ -72,6 +80,7 @@ class HttpCli(object):
|
|||||||
"""returns true if connection can be reused"""
|
"""returns true if connection can be reused"""
|
||||||
self.keepalive = False
|
self.keepalive = False
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
self.hint = None
|
||||||
try:
|
try:
|
||||||
headerlines = read_header(self.sr)
|
headerlines = read_header(self.sr)
|
||||||
if not headerlines:
|
if not headerlines:
|
||||||
@@ -130,6 +139,9 @@ class HttpCli(object):
|
|||||||
if v is not None:
|
if v is not None:
|
||||||
self.log("[H] {}: \033[33m[{}]".format(k, v), 6)
|
self.log("[H] {}: \033[33m[{}]".format(k, v), 6)
|
||||||
|
|
||||||
|
if "&" in self.req and "?" not in self.req:
|
||||||
|
self.hint = "did you mean '?' instead of '&'"
|
||||||
|
|
||||||
# split req into vpath + uparam
|
# split req into vpath + uparam
|
||||||
uparam = {}
|
uparam = {}
|
||||||
if "?" not in self.req:
|
if "?" not in self.req:
|
||||||
@@ -169,6 +181,9 @@ class HttpCli(object):
|
|||||||
self.rvol, self.wvol, self.avol = [[], [], []]
|
self.rvol, self.wvol, self.avol = [[], [], []]
|
||||||
self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
|
self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
|
||||||
|
|
||||||
|
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
|
||||||
|
self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
|
||||||
|
|
||||||
ua = self.headers.get("user-agent", "")
|
ua = self.headers.get("user-agent", "")
|
||||||
self.is_rclone = ua.startswith("rclone/")
|
self.is_rclone = ua.startswith("rclone/")
|
||||||
if self.is_rclone:
|
if self.is_rclone:
|
||||||
@@ -199,6 +214,9 @@ class HttpCli(object):
|
|||||||
|
|
||||||
self.log("{}\033[0m, {}".format(str(ex), self.vpath), 3)
|
self.log("{}\033[0m, {}".format(str(ex), self.vpath), 3)
|
||||||
msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
|
msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
|
||||||
|
if self.hint:
|
||||||
|
msg += "hint: {}\r\n".format(self.hint)
|
||||||
|
|
||||||
self.reply(msg.encode("utf-8", "replace"), status=ex.code)
|
self.reply(msg.encode("utf-8", "replace"), status=ex.code)
|
||||||
return self.keepalive
|
return self.keepalive
|
||||||
except Pebkac:
|
except Pebkac:
|
||||||
@@ -623,7 +641,7 @@ class HttpCli(object):
|
|||||||
penalty = 0.7
|
penalty = 0.7
|
||||||
t_idle = t0 - idx.p_end
|
t_idle = t0 - idx.p_end
|
||||||
if idx.p_dur > 0.7 and t_idle < penalty:
|
if idx.p_dur > 0.7 and t_idle < penalty:
|
||||||
m = "rate-limit ({:.1f} sec), cost {:.2f}, idle {:.2f}"
|
m = "rate-limit {:.1f} sec, cost {:.2f}, idle {:.2f}"
|
||||||
raise Pebkac(429, m.format(penalty, idx.p_dur, t_idle))
|
raise Pebkac(429, m.format(penalty, idx.p_dur, t_idle))
|
||||||
|
|
||||||
if "srch" in body:
|
if "srch" in body:
|
||||||
@@ -743,6 +761,12 @@ class HttpCli(object):
|
|||||||
pwd = self.parser.require("cppwd", 64)
|
pwd = self.parser.require("cppwd", 64)
|
||||||
self.parser.drop()
|
self.parser.drop()
|
||||||
|
|
||||||
|
ck, msg = self.get_pwd_cookie(pwd)
|
||||||
|
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
|
||||||
|
self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_pwd_cookie(self, pwd):
|
||||||
if pwd in self.asrv.iuser:
|
if pwd in self.asrv.iuser:
|
||||||
msg = "login ok"
|
msg = "login ok"
|
||||||
dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
|
dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
|
||||||
@@ -753,9 +777,7 @@ class HttpCli(object):
|
|||||||
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
||||||
|
|
||||||
ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
|
ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
|
||||||
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
|
return [ck, msg]
|
||||||
self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
|
|
||||||
return True
|
|
||||||
|
|
||||||
def handle_mkdir(self):
|
def handle_mkdir(self):
|
||||||
new_dir = self.parser.require("name", 512)
|
new_dir = self.parser.require("name", 512)
|
||||||
@@ -1765,16 +1787,27 @@ class HttpCli(object):
|
|||||||
fn = f["name"]
|
fn = f["name"]
|
||||||
rd = f["rd"]
|
rd = f["rd"]
|
||||||
del f["rd"]
|
del f["rd"]
|
||||||
if icur:
|
if not icur:
|
||||||
|
break
|
||||||
|
|
||||||
if vn != dbv:
|
if vn != dbv:
|
||||||
_, rd = vn.get_dbv(rd)
|
_, rd = vn.get_dbv(rd)
|
||||||
|
|
||||||
q = "select w from up where rd = ? and fn = ?"
|
q = "select w from up where rd = ? and fn = ?"
|
||||||
|
r = None
|
||||||
try:
|
try:
|
||||||
r = icur.execute(q, (rd, fn)).fetchone()
|
r = icur.execute(q, (rd, fn)).fetchone()
|
||||||
except:
|
except Exception as ex:
|
||||||
|
if "database is locked" in str(ex):
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
args = s3enc(idx.mem_cur, rd, fn)
|
args = s3enc(idx.mem_cur, rd, fn)
|
||||||
r = icur.execute(q, args).fetchone()
|
r = icur.execute(q, args).fetchone()
|
||||||
|
except:
|
||||||
|
m = "tag list error, {}/{}\n{}"
|
||||||
|
self.log(m.format(rd, fn, min_ex()))
|
||||||
|
break
|
||||||
|
|
||||||
tags = {}
|
tags = {}
|
||||||
f["tags"] = tags
|
f["tags"] = tags
|
||||||
@@ -1784,9 +1817,14 @@ class HttpCli(object):
|
|||||||
|
|
||||||
w = r[0][:16]
|
w = r[0][:16]
|
||||||
q = "select k, v from mt where w = ? and k != 'x'"
|
q = "select k, v from mt where w = ? and k != 'x'"
|
||||||
|
try:
|
||||||
for k, v in icur.execute(q, (w,)):
|
for k, v in icur.execute(q, (w,)):
|
||||||
taglist[k] = True
|
taglist[k] = True
|
||||||
tags[k] = v
|
tags[k] = v
|
||||||
|
except:
|
||||||
|
m = "tag read error, {}/{} [{}]:\n{}"
|
||||||
|
self.log(m.format(rd, fn, w, min_ex()))
|
||||||
|
break
|
||||||
|
|
||||||
if icur:
|
if icur:
|
||||||
taglist = [k for k in vn.flags.get("mte", "").split(",") if k in taglist]
|
taglist = [k for k in vn.flags.get("mte", "").split(",") if k in taglist]
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,19 @@ def parse_ffprobe(txt):
|
|||||||
ret = {} # processed
|
ret = {} # processed
|
||||||
md = {} # raw tags
|
md = {} # raw tags
|
||||||
|
|
||||||
|
is_audio = fmt.get("format_name") in ["mp3", "ogg", "flac", "wav"]
|
||||||
|
if fmt.get("filename", "").split(".")[-1].lower() in ["m4a", "aac"]:
|
||||||
|
is_audio = True
|
||||||
|
|
||||||
|
# if audio file, ensure audio stream appears first
|
||||||
|
if (
|
||||||
|
is_audio
|
||||||
|
and len(streams) > 2
|
||||||
|
and streams[1].get("codec_type") != "audio"
|
||||||
|
and streams[2].get("codec_type") == "audio"
|
||||||
|
):
|
||||||
|
streams = [fmt, streams[2], streams[1]] + streams[3:]
|
||||||
|
|
||||||
have = {}
|
have = {}
|
||||||
for strm in streams:
|
for strm in streams:
|
||||||
typ = strm.get("codec_type")
|
typ = strm.get("codec_type")
|
||||||
@@ -134,9 +147,7 @@ def parse_ffprobe(txt):
|
|||||||
]
|
]
|
||||||
|
|
||||||
if typ == "video":
|
if typ == "video":
|
||||||
if strm.get("DISPOSITION:attached_pic") == "1" or fmt.get(
|
if strm.get("DISPOSITION:attached_pic") == "1" or is_audio:
|
||||||
"format_name"
|
|
||||||
) in ["mp3", "ogg", "flac"]:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
kvm = [
|
kvm = [
|
||||||
@@ -180,7 +191,7 @@ def parse_ffprobe(txt):
|
|||||||
|
|
||||||
k = k[4:].strip()
|
k = k[4:].strip()
|
||||||
v = v.strip()
|
v = v.strip()
|
||||||
if k and v:
|
if k and v and k not in md:
|
||||||
md[k] = [v]
|
md[k] = [v]
|
||||||
|
|
||||||
for k in [".q", ".vq", ".aq"]:
|
for k in [".q", ".vq", ".aq"]:
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class U2idx(object):
|
|||||||
self.timeout = self.args.srch_time
|
self.timeout = self.args.srch_time
|
||||||
|
|
||||||
if not HAVE_SQLITE3:
|
if not HAVE_SQLITE3:
|
||||||
self.log("could not load sqlite3; searchign wqill be disabled")
|
self.log("your python does not have sqlite3; searching will be disabled")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.cur = {}
|
self.cur = {}
|
||||||
@@ -57,6 +57,9 @@ class U2idx(object):
|
|||||||
raise Pebkac(500, min_ex())
|
raise Pebkac(500, min_ex())
|
||||||
|
|
||||||
def get_cur(self, ptop):
|
def get_cur(self, ptop):
|
||||||
|
if not HAVE_SQLITE3:
|
||||||
|
return None
|
||||||
|
|
||||||
cur = self.cur.get(ptop)
|
cur = self.cur.get(ptop)
|
||||||
if cur:
|
if cur:
|
||||||
return cur
|
return cur
|
||||||
@@ -66,7 +69,7 @@ class U2idx(object):
|
|||||||
if not os.path.exists(db_path):
|
if not os.path.exists(db_path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
cur = sqlite3.connect(db_path).cursor()
|
cur = sqlite3.connect(db_path, 2).cursor()
|
||||||
self.cur[ptop] = cur
|
self.cur[ptop] = cur
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
|
|||||||
@@ -653,7 +653,7 @@ class Up2k(object):
|
|||||||
try:
|
try:
|
||||||
parser = MParser(parser)
|
parser = MParser(parser)
|
||||||
except:
|
except:
|
||||||
self.log("invalid argument: " + parser, 1)
|
self.log("invalid argument (could not find program): " + parser, 1)
|
||||||
return
|
return
|
||||||
|
|
||||||
for tag in entags:
|
for tag in entags:
|
||||||
@@ -901,7 +901,7 @@ class Up2k(object):
|
|||||||
except:
|
except:
|
||||||
self.log("WARN: could not list files; DB corrupt?\n" + min_ex())
|
self.log("WARN: could not list files; DB corrupt?\n" + min_ex())
|
||||||
|
|
||||||
elif ver > DB_VER:
|
if (ver or 0) > DB_VER:
|
||||||
m = "database is version {}, this copyparty only supports versions <= {}"
|
m = "database is version {}, this copyparty only supports versions <= {}"
|
||||||
raise Exception(m.format(ver, DB_VER))
|
raise Exception(m.format(ver, DB_VER))
|
||||||
|
|
||||||
|
|||||||
@@ -1030,7 +1030,13 @@ def guess_mime(url, fallback="application/octet-stream"):
|
|||||||
except:
|
except:
|
||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
return MIMES.get(ext) or mimetypes.guess_type(url)[0] or fallback
|
ret = MIMES.get(ext) or mimetypes.guess_type(url)[0] or fallback
|
||||||
|
|
||||||
|
if ";" not in ret:
|
||||||
|
if ret.startswith("text/") or ret.endswith("/javascript"):
|
||||||
|
ret += "; charset=UTF-8"
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def runcmd(*argv):
|
def runcmd(*argv):
|
||||||
|
|||||||
@@ -811,10 +811,12 @@ input.eq_gain {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border-bottom: 1px solid #555;
|
border-bottom: 1px solid #555;
|
||||||
}
|
}
|
||||||
#thumbs {
|
#thumbs,
|
||||||
|
#au_osd_cv {
|
||||||
opacity: .3;
|
opacity: .3;
|
||||||
}
|
}
|
||||||
#griden.on+#thumbs {
|
#griden.on+#thumbs,
|
||||||
|
#au_os_ctl.on+#au_osd_cv {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
#ghead {
|
#ghead {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
<div id="epi" class="logue">{{ logues[1] }}</div>
|
<div id="epi" class="logue">{{ logues[1] }}</div>
|
||||||
|
|
||||||
<h2><a href="?h">control-panel</a></h2>
|
<h2><a href="/?h">control-panel</a></h2>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -222,10 +222,14 @@ var have_webp = null;
|
|||||||
|
|
||||||
|
|
||||||
var mpl = (function () {
|
var mpl = (function () {
|
||||||
|
var have_mctl = 'mediaSession' in navigator && window.MediaMetadata;
|
||||||
|
|
||||||
ebi('op_player').innerHTML = (
|
ebi('op_player').innerHTML = (
|
||||||
'<div><h3>switches</h3><div>' +
|
'<div><h3>switches</h3><div>' +
|
||||||
'<a href="#" class="tgl btn" id="au_preload" tt="start loading the next song near the end for gapless playback">preload</a>' +
|
'<a href="#" class="tgl btn" id="au_preload" tt="start loading the next song near the end for gapless playback">preload</a>' +
|
||||||
'<a href="#" class="tgl btn" id="au_npclip" tt="show buttons for clipboarding the currently playing song">/np clip</a>' +
|
'<a href="#" class="tgl btn" id="au_npclip" tt="show buttons for clipboarding the currently playing song">/np clip</a>' +
|
||||||
|
'<a href="#" class="tgl btn" id="au_os_ctl" tt="os integration (media hotkeys / osd)">os-ctl</a>' +
|
||||||
|
'<a href="#" class="tgl btn" id="au_osd_cv" tt="show album cover in osd">osd-cv</a>' +
|
||||||
'</div></div>' +
|
'</div></div>' +
|
||||||
|
|
||||||
'<div><h3>playback mode</h3><div id="pb_mode">' +
|
'<div><h3>playback mode</h3><div id="pb_mode">' +
|
||||||
@@ -238,7 +242,9 @@ var mpl = (function () {
|
|||||||
var r = {
|
var r = {
|
||||||
"pb_mode": sread('pb_mode') || 'loop-folder',
|
"pb_mode": sread('pb_mode') || 'loop-folder',
|
||||||
"preload": bcfg_get('au_preload', true),
|
"preload": bcfg_get('au_preload', true),
|
||||||
"clip": bcfg_get('au_npclip', false)
|
"clip": bcfg_get('au_npclip', false),
|
||||||
|
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
|
||||||
|
"osd_cv": bcfg_get('au_osd_cv', true),
|
||||||
};
|
};
|
||||||
|
|
||||||
ebi('au_preload').onclick = function (e) {
|
ebi('au_preload').onclick = function (e) {
|
||||||
@@ -254,6 +260,20 @@ var mpl = (function () {
|
|||||||
clmod(ebi('wtoggle'), 'np', r.clip && mp.au);
|
clmod(ebi('wtoggle'), 'np', r.clip && mp.au);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ebi('au_os_ctl').onclick = function (e) {
|
||||||
|
ev(e);
|
||||||
|
r.os_ctl = !r.os_ctl && have_mctl;
|
||||||
|
bcfg_set('au_os_ctl', r.os_ctl);
|
||||||
|
if (!have_mctl)
|
||||||
|
alert('need firefox 82+ or chrome 73+');
|
||||||
|
};
|
||||||
|
|
||||||
|
ebi('au_osd_cv').onclick = function (e) {
|
||||||
|
ev(e);
|
||||||
|
r.osd_cv = !r.osd_cv;
|
||||||
|
bcfg_set('au_osd_cv', r.osd_cv);
|
||||||
|
};
|
||||||
|
|
||||||
function draw_pb_mode() {
|
function draw_pb_mode() {
|
||||||
var btns = QSA('#pb_mode>a');
|
var btns = QSA('#pb_mode>a');
|
||||||
for (var a = 0, aa = btns.length; a < aa; a++) {
|
for (var a = 0, aa = btns.length; a < aa; a++) {
|
||||||
@@ -270,20 +290,85 @@ var mpl = (function () {
|
|||||||
draw_pb_mode();
|
draw_pb_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.pp = function () {
|
||||||
|
if (!r.os_ctl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
navigator.mediaSession.playbackState = mp.au && !mp.au.paused ? "playing" : "paused";
|
||||||
|
};
|
||||||
|
|
||||||
|
r.announce = function () {
|
||||||
|
if (!r.os_ctl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var np = get_np()[0],
|
||||||
|
fns = np.file.split(' - '),
|
||||||
|
artist = (np.circle ? np.circle + ' // ' : '') + (np.artist || (fns.length > 1 ? fns[0] : '')),
|
||||||
|
tags = {
|
||||||
|
title: np.title || fns.slice(-1)[0]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (artist)
|
||||||
|
tags.artist = artist;
|
||||||
|
|
||||||
|
if (np.album)
|
||||||
|
tags.album = np.album;
|
||||||
|
|
||||||
|
if (r.osd_cv) {
|
||||||
|
var files = QSA("#files tr>td:nth-child(2)>a[id]"),
|
||||||
|
cover = null;
|
||||||
|
|
||||||
|
for (var a = 0, aa = files.length; a < aa; a++) {
|
||||||
|
if (/^(cover|folder)\.(jpe?g|png|gif)$/.test(files[a].textContent)) {
|
||||||
|
cover = files[a].getAttribute('href');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cover) {
|
||||||
|
cover += (cover.indexOf('?') === -1 ? '?' : '&') + 'th=j';
|
||||||
|
|
||||||
|
var pwd = get_pwd();
|
||||||
|
if (pwd)
|
||||||
|
cover += '&pw=' + uricom_enc(pwd);
|
||||||
|
|
||||||
|
tags.artwork = [{ "src": cover, type: "image/jpeg" }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.mediaSession.metadata = new MediaMetadata(tags);
|
||||||
|
navigator.mediaSession.setActionHandler('play', playpause);
|
||||||
|
navigator.mediaSession.setActionHandler('pause', playpause);
|
||||||
|
navigator.mediaSession.setActionHandler('seekbackward', function () { seek_au_rel(-10); });
|
||||||
|
navigator.mediaSession.setActionHandler('seekforward', function () { seek_au_rel(10); });
|
||||||
|
navigator.mediaSession.setActionHandler('previoustrack', prev_song);
|
||||||
|
navigator.mediaSession.setActionHandler('nexttrack', next_song);
|
||||||
|
r.pp();
|
||||||
|
};
|
||||||
|
|
||||||
|
r.stop = function () {
|
||||||
|
if (!r.os_ctl || !navigator.mediaSession.metadata)
|
||||||
|
return;
|
||||||
|
|
||||||
|
navigator.mediaSession.metadata = null;
|
||||||
|
navigator.mediaSession.playbackState = "paused";
|
||||||
|
};
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
// extract songs + add play column
|
// extract songs + add play column
|
||||||
function MPlayer() {
|
function MPlayer() {
|
||||||
this.id = Date.now();
|
var r = this;
|
||||||
this.au = null;
|
r.id = Date.now();
|
||||||
this.au_native = null;
|
r.au = null;
|
||||||
this.au_native2 = null;
|
r.au_native = null;
|
||||||
this.au_ogvjs = null;
|
r.au_native2 = null;
|
||||||
this.au_ogvjs2 = null;
|
r.au_ogvjs = null;
|
||||||
this.tracks = {};
|
r.au_ogvjs2 = null;
|
||||||
this.order = [];
|
r.tracks = {};
|
||||||
|
r.order = [];
|
||||||
|
|
||||||
var re_audio = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i,
|
var re_audio = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i,
|
||||||
trs = QSA('#files tbody tr');
|
trs = QSA('#files tbody tr');
|
||||||
@@ -298,32 +383,33 @@ function MPlayer() {
|
|||||||
|
|
||||||
if (m) {
|
if (m) {
|
||||||
var tid = link.getAttribute('id');
|
var tid = link.getAttribute('id');
|
||||||
this.order.push(tid);
|
r.order.push(tid);
|
||||||
this.tracks[tid] = url;
|
r.tracks[tid] = url;
|
||||||
tds[0].innerHTML = '<a id="a' + tid + '" href="#a' + tid + '" class="play">play</a></td>';
|
tds[0].innerHTML = '<a id="a' + tid + '" href="#a' + tid + '" class="play">play</a></td>';
|
||||||
ebi('a' + tid).onclick = ev_play;
|
ebi('a' + tid).onclick = ev_play;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.vol = sread('vol');
|
r.vol = sread('vol');
|
||||||
if (this.vol !== null)
|
if (r.vol !== null)
|
||||||
this.vol = parseFloat(this.vol);
|
r.vol = parseFloat(r.vol);
|
||||||
else
|
else
|
||||||
this.vol = 0.5;
|
r.vol = 0.5;
|
||||||
|
|
||||||
this.expvol = function () {
|
r.expvol = function (v) {
|
||||||
return 0.5 * this.vol + 0.5 * this.vol * this.vol;
|
return 0.5 * v + 0.5 * v * v;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setvol = function (vol) {
|
r.setvol = function (vol) {
|
||||||
this.vol = Math.max(Math.min(vol, 1), 0);
|
r.vol = Math.max(Math.min(vol, 1), 0);
|
||||||
swrite('vol', vol);
|
swrite('vol', vol);
|
||||||
|
r.stopfade(true);
|
||||||
|
|
||||||
if (this.au)
|
if (r.au)
|
||||||
this.au.volume = this.expvol();
|
r.au.volume = r.expvol(r.vol);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.read_order = function () {
|
r.read_order = function () {
|
||||||
var order = [],
|
var order = [],
|
||||||
links = QSA('#files>tbody>tr>td:nth-child(1)>a');
|
links = QSA('#files>tbody>tr>td:nth-child(1)>a');
|
||||||
|
|
||||||
@@ -334,24 +420,71 @@ function MPlayer() {
|
|||||||
|
|
||||||
order.push(tid.slice(1));
|
order.push(tid.slice(1));
|
||||||
}
|
}
|
||||||
this.order = order;
|
r.order = order;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.preload = function (url) {
|
r.fdir = 0;
|
||||||
|
r.fvol = -1;
|
||||||
|
r.ftid = -1;
|
||||||
|
r.ftimer = null;
|
||||||
|
r.fade_in = function () {
|
||||||
|
r.fvol = 0;
|
||||||
|
r.fdir = 0.025;
|
||||||
|
if (r.au) {
|
||||||
|
r.ftid = r.au.tid;
|
||||||
|
r.au.play();
|
||||||
|
mpl.pp();
|
||||||
|
fader();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.fade_out = function () {
|
||||||
|
r.fvol = r.vol;
|
||||||
|
r.fdir = -0.05;
|
||||||
|
r.ftid = r.au.tid;
|
||||||
|
fader();
|
||||||
|
};
|
||||||
|
r.stopfade = function (hard) {
|
||||||
|
clearTimeout(r.ftimer);
|
||||||
|
if (hard)
|
||||||
|
r.ftid = -1;
|
||||||
|
}
|
||||||
|
function fader() {
|
||||||
|
r.stopfade();
|
||||||
|
if (!r.au || r.au.tid !== r.ftid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var done = true;
|
||||||
|
r.fvol += r.fdir;
|
||||||
|
if (r.fvol < 0) {
|
||||||
|
r.fvol = 0;
|
||||||
|
r.au.pause();
|
||||||
|
mpl.pp();
|
||||||
|
}
|
||||||
|
else if (r.fvol > r.vol)
|
||||||
|
r.fvol = r.vol;
|
||||||
|
else
|
||||||
|
done = false;
|
||||||
|
|
||||||
|
r.au.volume = r.expvol(r.fvol);
|
||||||
|
if (!done)
|
||||||
|
setTimeout(fader, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
r.preload = function (url) {
|
||||||
var au = null;
|
var au = null;
|
||||||
if (need_ogv_for(url)) {
|
if (need_ogv_for(url)) {
|
||||||
au = mp.au_ogvjs2;
|
au = mp.au_ogvjs2;
|
||||||
if (!au && window['OGVPlayer']) {
|
if (!au && window['OGVPlayer']) {
|
||||||
au = new OGVPlayer();
|
au = new OGVPlayer();
|
||||||
au.preload = "auto";
|
au.preload = "auto";
|
||||||
this.au_ogvjs2 = au;
|
r.au_ogvjs2 = au;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
au = mp.au_native2;
|
au = mp.au_native2;
|
||||||
if (!au) {
|
if (!au) {
|
||||||
au = new Audio();
|
au = new Audio();
|
||||||
au.preload = "auto";
|
au.preload = "auto";
|
||||||
this.au_native2 = au;
|
r.au_native2 = au;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (au) {
|
if (au) {
|
||||||
@@ -365,39 +498,62 @@ var mp = new MPlayer();
|
|||||||
makeSortable(ebi('files'), mp.read_order.bind(mp));
|
makeSortable(ebi('files'), mp.read_order.bind(mp));
|
||||||
|
|
||||||
|
|
||||||
|
function get_np() {
|
||||||
|
var th = ebi('files').tHead.rows[0].cells,
|
||||||
|
tr = QS('#files tr.play').cells,
|
||||||
|
rv = [],
|
||||||
|
ra = [],
|
||||||
|
rt = {};
|
||||||
|
|
||||||
|
for (var a = 1, aa = th.length; a < aa; a++) {
|
||||||
|
var tv = tr[a].textContent,
|
||||||
|
tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').slice(-1)[0],
|
||||||
|
vis = th[a].className.indexOf('min') === -1;
|
||||||
|
|
||||||
|
if (!tv)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
(vis ? rv : ra).push(tk);
|
||||||
|
rt[tk] = tv;
|
||||||
|
}
|
||||||
|
return [rt, rv, ra];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// toggle player widget
|
// toggle player widget
|
||||||
var widget = (function () {
|
var widget = (function () {
|
||||||
var ret = {},
|
var r = {},
|
||||||
widget = ebi('widget'),
|
widget = ebi('widget'),
|
||||||
wtico = ebi('wtico'),
|
wtico = ebi('wtico'),
|
||||||
nptxt = ebi('nptxt'),
|
nptxt = ebi('nptxt'),
|
||||||
npirc = ebi('npirc'),
|
npirc = ebi('npirc'),
|
||||||
touchmode = false,
|
touchmode = false,
|
||||||
side_open = false,
|
|
||||||
was_paused = true;
|
was_paused = true;
|
||||||
|
|
||||||
ret.open = function () {
|
r.is_open = false;
|
||||||
if (side_open)
|
|
||||||
|
r.open = function () {
|
||||||
|
if (r.is_open)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
widget.className = 'open';
|
widget.className = 'open';
|
||||||
side_open = true;
|
r.is_open = true;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
ret.close = function () {
|
r.close = function () {
|
||||||
if (!side_open)
|
if (!r.is_open)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
widget.className = '';
|
widget.className = '';
|
||||||
side_open = false;
|
r.is_open = false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
ret.toggle = function (e) {
|
r.toggle = function (e) {
|
||||||
ret.open() || ret.close();
|
r.open() || r.close();
|
||||||
ev(e);
|
ev(e);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
ret.paused = function (paused) {
|
r.paused = function (paused) {
|
||||||
if (was_paused != paused) {
|
if (was_paused != paused) {
|
||||||
was_paused = paused;
|
was_paused = paused;
|
||||||
ebi('bplay').innerHTML = paused ? '▶' : '⏸';
|
ebi('bplay').innerHTML = paused ? '▶' : '⏸';
|
||||||
@@ -405,28 +561,22 @@ var widget = (function () {
|
|||||||
};
|
};
|
||||||
wtico.onclick = function (e) {
|
wtico.onclick = function (e) {
|
||||||
if (!touchmode)
|
if (!touchmode)
|
||||||
ret.toggle(e);
|
r.toggle(e);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
npirc.onclick = nptxt.onclick = function (e) {
|
npirc.onclick = nptxt.onclick = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
var th = ebi('files').tHead.rows[0].cells,
|
var irc = this.getAttribute('id') == 'npirc',
|
||||||
tr = QS('#files tr.play').cells,
|
|
||||||
irc = this.getAttribute('id') == 'npirc',
|
|
||||||
ck = irc ? '06' : '',
|
ck = irc ? '06' : '',
|
||||||
cv = irc ? '07' : '',
|
cv = irc ? '07' : '',
|
||||||
m = ck + 'np: ';
|
m = ck + 'np: ',
|
||||||
|
npr = get_np(),
|
||||||
|
npk = npr[1],
|
||||||
|
np = npr[0];
|
||||||
|
|
||||||
for (var a = 1, aa = th.length; a < aa; a++) {
|
for (var a = 0; a < npk.length; a++)
|
||||||
if (th[a].className.indexOf('min') !== -1)
|
m += (npk[a] == 'file' ? '' : npk[a]) + '(' + cv + np[npk[a]] + ck + ') // ';
|
||||||
continue;
|
|
||||||
|
|
||||||
var tv = tr[a].textContent,
|
|
||||||
tk = a == 1 ? '' : th[a].getAttribute('name').split('/').slice(-1)[0];
|
|
||||||
|
|
||||||
m += tk + '(' + cv + tv + ck + ') // ';
|
|
||||||
}
|
|
||||||
|
|
||||||
m += '[' + cv + s2ms(mp.au.currentTime) + ck + '/' + cv + s2ms(mp.au.duration) + ck + ']';
|
m += '[' + cv + s2ms(mp.au.currentTime) + ck + '/' + cv + s2ms(mp.au.duration) + ck + ']';
|
||||||
|
|
||||||
@@ -442,7 +592,7 @@ var widget = (function () {
|
|||||||
document.body.removeChild(o);
|
document.body.removeChild(o);
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
return ret;
|
return r;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
@@ -488,12 +638,15 @@ var pbar = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.drawbuf = function () {
|
r.drawbuf = function () {
|
||||||
|
var bc = r.buf,
|
||||||
|
bctx = bc.ctx;
|
||||||
|
|
||||||
|
bctx.clearRect(0, 0, bc.w, bc.h);
|
||||||
|
|
||||||
if (!mp.au)
|
if (!mp.au)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var bc = r.buf,
|
var sm = bc.w * 1.0 / mp.au.duration,
|
||||||
bctx = bc.ctx,
|
|
||||||
sm = bc.w * 1.0 / mp.au.duration,
|
|
||||||
gk = bc.h + '' + light;
|
gk = bc.h + '' + light;
|
||||||
|
|
||||||
if (gradh != gk) {
|
if (gradh != gk) {
|
||||||
@@ -501,7 +654,6 @@ var pbar = (function () {
|
|||||||
grad = glossy_grad(bc, 85, [35, 40, 37, 35], light ? [45, 56, 50, 45] : [42, 51, 47, 42]);
|
grad = glossy_grad(bc, 85, [35, 40, 37, 35], light ? [45, 56, 50, 45] : [42, 51, 47, 42]);
|
||||||
}
|
}
|
||||||
bctx.fillStyle = grad;
|
bctx.fillStyle = grad;
|
||||||
bctx.clearRect(0, 0, bc.w, bc.h);
|
|
||||||
for (var a = 0; a < mp.au.buffered.length; a++) {
|
for (var a = 0; a < mp.au.buffered.length; a++) {
|
||||||
var x1 = sm * mp.au.buffered.start(a),
|
var x1 = sm * mp.au.buffered.start(a),
|
||||||
x2 = sm * mp.au.buffered.end(a);
|
x2 = sm * mp.au.buffered.end(a);
|
||||||
@@ -511,15 +663,17 @@ var pbar = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
r.drawpos = function () {
|
r.drawpos = function () {
|
||||||
|
var bc = r.buf,
|
||||||
|
pc = r.pos,
|
||||||
|
pctx = pc.ctx;
|
||||||
|
|
||||||
|
pctx.clearRect(0, 0, pc.w, pc.h);
|
||||||
|
|
||||||
if (!mp.au || isNaN(mp.au.duration) || isNaN(mp.au.currentTime))
|
if (!mp.au || isNaN(mp.au.duration) || isNaN(mp.au.currentTime))
|
||||||
return; // not-init || unsupp-codec
|
return; // not-init || unsupp-codec
|
||||||
|
|
||||||
var bc = r.buf,
|
var sm = bc.w * 1.0 / mp.au.duration;
|
||||||
pc = r.pos,
|
|
||||||
pctx = pc.ctx,
|
|
||||||
sm = bc.w * 1.0 / mp.au.duration;
|
|
||||||
|
|
||||||
pctx.clearRect(0, 0, pc.w, pc.h);
|
|
||||||
pctx.fillStyle = light ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)';
|
pctx.fillStyle = light ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)';
|
||||||
for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++)
|
for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++)
|
||||||
pctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
|
pctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
|
||||||
@@ -635,6 +789,11 @@ function seek_au_mul(mul) {
|
|||||||
seek_au_sec(mp.au.duration * mul);
|
seek_au_sec(mp.au.duration * mul);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function seek_au_rel(sec) {
|
||||||
|
if (mp.au)
|
||||||
|
seek_au_sec(mp.au.currentTime + sec);
|
||||||
|
}
|
||||||
|
|
||||||
function seek_au_sec(seek) {
|
function seek_au_sec(seek) {
|
||||||
if (!mp.au)
|
if (!mp.au)
|
||||||
return;
|
return;
|
||||||
@@ -645,9 +804,8 @@ function seek_au_sec(seek) {
|
|||||||
|
|
||||||
mp.au.currentTime = seek;
|
mp.au.currentTime = seek;
|
||||||
|
|
||||||
// ogv.js breaks on .play() during playback
|
if (mp.au.paused)
|
||||||
if (mp.au === mp.au_native)
|
mp.fade_in();
|
||||||
mp.au.play();
|
|
||||||
|
|
||||||
mpui.progress_updater();
|
mpui.progress_updater();
|
||||||
}
|
}
|
||||||
@@ -669,22 +827,29 @@ function next_song(e) {
|
|||||||
}
|
}
|
||||||
function prev_song(e) {
|
function prev_song(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
|
|
||||||
|
if (mp.au && !mp.au.paused && mp.au.currentTime > 3)
|
||||||
|
return seek_au_sec(0);
|
||||||
|
|
||||||
return song_skip(-1);
|
return song_skip(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function playpause(e) {
|
function playpause(e) {
|
||||||
|
// must be event-chain
|
||||||
ev(e);
|
ev(e);
|
||||||
if (mp.au) {
|
if (mp.au) {
|
||||||
if (mp.au.paused)
|
if (mp.au.paused)
|
||||||
mp.au.play();
|
mp.fade_in();
|
||||||
else
|
else
|
||||||
mp.au.pause();
|
mp.fade_out();
|
||||||
|
|
||||||
mpui.progress_updater();
|
mpui.progress_updater();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
play(0);
|
play(0, true);
|
||||||
|
|
||||||
|
mpl.pp();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -693,9 +858,13 @@ function playpause(e) {
|
|||||||
ebi('bplay').onclick = playpause;
|
ebi('bplay').onclick = playpause;
|
||||||
ebi('bprev').onclick = prev_song;
|
ebi('bprev').onclick = prev_song;
|
||||||
ebi('bnext').onclick = next_song;
|
ebi('bnext').onclick = next_song;
|
||||||
ebi('barpos').onclick = function (e) {
|
|
||||||
|
var bar = ebi('barpos');
|
||||||
|
|
||||||
|
bar.onclick = function (e) {
|
||||||
if (!mp.au) {
|
if (!mp.au) {
|
||||||
return play(0);
|
play(0, true);
|
||||||
|
return mp.fade_in();
|
||||||
}
|
}
|
||||||
|
|
||||||
var rect = pbar.buf.can.getBoundingClientRect(),
|
var rect = pbar.buf.can.getBoundingClientRect(),
|
||||||
@@ -703,6 +872,19 @@ function playpause(e) {
|
|||||||
|
|
||||||
seek_au_mul(x * 1.0 / rect.width);
|
seek_au_mul(x * 1.0 / rect.width);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!is_touch)
|
||||||
|
bar.onwheel = function (e) {
|
||||||
|
var dist = Math.sign(e.deltaY) * 10;
|
||||||
|
if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
|
||||||
|
dist = e.deltaY;
|
||||||
|
|
||||||
|
if (!dist || !mp.au)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
seek_au_rel(dist);
|
||||||
|
ev(e);
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
@@ -770,7 +952,12 @@ var mpui = (function () {
|
|||||||
// event from play button next to a file in the list
|
// event from play button next to a file in the list
|
||||||
function ev_play(e) {
|
function ev_play(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
play(this.getAttribute('id').slice(1));
|
|
||||||
|
var fade = !mp.au || mp.au.paused;
|
||||||
|
play(this.getAttribute('id').slice(1), true);
|
||||||
|
if (fade)
|
||||||
|
mp.fade_in();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1009,13 +1196,18 @@ var audio_eq = (function () {
|
|||||||
|
|
||||||
|
|
||||||
// plays the tid'th audio file on the page
|
// plays the tid'th audio file on the page
|
||||||
function play(tid, seek, call_depth) {
|
function play(tid, is_ev, seek, call_depth) {
|
||||||
if (mp.order.length == 0)
|
if (mp.order.length == 0)
|
||||||
return console.log('no audio found wait what');
|
return console.log('no audio found wait what');
|
||||||
|
|
||||||
|
mp.stopfade(true);
|
||||||
|
|
||||||
var tn = tid;
|
var tn = tid;
|
||||||
if ((tn + '').indexOf('f-') === 0)
|
if ((tn + '').indexOf('f-') === 0) {
|
||||||
tn = mp.order.indexOf(tn);
|
tn = mp.order.indexOf(tn);
|
||||||
|
if (tn < 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (tn >= mp.order.length) {
|
if (tn >= mp.order.length) {
|
||||||
if (mpl.pb_mode == 'loop-folder') {
|
if (mpl.pb_mode == 'loop-folder') {
|
||||||
@@ -1054,7 +1246,7 @@ function play(tid, seek, call_depth) {
|
|||||||
}
|
}
|
||||||
else if (window['OGVPlayer']) {
|
else if (window['OGVPlayer']) {
|
||||||
mp.au = mp.au_ogvjs = new OGVPlayer();
|
mp.au = mp.au_ogvjs = new OGVPlayer();
|
||||||
attempt_play = false;
|
attempt_play = is_ev;
|
||||||
mp.au.addEventListener('error', evau_error, true);
|
mp.au.addEventListener('error', evau_error, true);
|
||||||
mp.au.addEventListener('progress', pbar.drawpos);
|
mp.au.addEventListener('progress', pbar.drawpos);
|
||||||
mp.au.addEventListener('ended', next_song);
|
mp.au.addEventListener('ended', next_song);
|
||||||
@@ -1067,7 +1259,7 @@ function play(tid, seek, call_depth) {
|
|||||||
show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
|
show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
|
||||||
|
|
||||||
import_js('/.cpr/deps/ogv.js', function () {
|
import_js('/.cpr/deps/ogv.js', function () {
|
||||||
play(tid, seek, 1);
|
play(tid, false, seek, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -1088,7 +1280,7 @@ function play(tid, seek, call_depth) {
|
|||||||
|
|
||||||
mp.au.tid = tid;
|
mp.au.tid = tid;
|
||||||
mp.au.src = url + (url.indexOf('?') < 0 ? '?cache' : '&cache');
|
mp.au.src = url + (url.indexOf('?') < 0 ? '?cache' : '&cache');
|
||||||
mp.au.volume = mp.expvol();
|
mp.au.volume = mp.expvol(mp.vol);
|
||||||
var oid = 'a' + tid;
|
var oid = 'a' + tid;
|
||||||
setclass(oid, 'play act');
|
setclass(oid, 'play act');
|
||||||
var trs = ebi('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
var trs = ebi('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
||||||
@@ -1124,6 +1316,7 @@ function play(tid, seek, call_depth) {
|
|||||||
|
|
||||||
mpui.progress_updater();
|
mpui.progress_updater();
|
||||||
pbar.drawbuf();
|
pbar.drawbuf();
|
||||||
|
mpl.announce();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
@@ -1178,7 +1371,8 @@ function show_modal(html) {
|
|||||||
|
|
||||||
|
|
||||||
// hide fullscreen message
|
// hide fullscreen message
|
||||||
function unblocked() {
|
function unblocked(e) {
|
||||||
|
ev(e);
|
||||||
var dom = ebi('blocked');
|
var dom = ebi('blocked');
|
||||||
if (dom)
|
if (dom)
|
||||||
dom.parentNode.removeChild(dom);
|
dom.parentNode.removeChild(dom);
|
||||||
@@ -1193,26 +1387,25 @@ function autoplay_blocked(seek) {
|
|||||||
|
|
||||||
var go = ebi('blk_go'),
|
var go = ebi('blk_go'),
|
||||||
na = ebi('blk_na'),
|
na = ebi('blk_na'),
|
||||||
fn = mp.tracks[mp.au.tid].split(/\//).pop();
|
tid = mp.au.tid,
|
||||||
|
fn = mp.tracks[tid].split(/\//).pop();
|
||||||
|
|
||||||
fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
|
fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
|
||||||
|
|
||||||
go.textContent = 'Play "' + fn + '"';
|
go.textContent = 'Play "' + fn + '"';
|
||||||
go.onclick = function (e) {
|
go.onclick = function (e) {
|
||||||
if (e) e.preventDefault();
|
unblocked(e);
|
||||||
unblocked();
|
// chrome 91 may permanently taint on a failed play()
|
||||||
mp.au.play();
|
// depending on win10 settings or something? idk
|
||||||
if (seek)
|
mp.au_native = mp.au_ogvjs = null;
|
||||||
seek_au_sec(seek);
|
play(tid, true, seek);
|
||||||
else
|
mp.fade_in();
|
||||||
mpui.progress_updater();
|
|
||||||
};
|
};
|
||||||
na.onclick = unblocked;
|
na.onclick = unblocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// autoplay linked track
|
function play_linked() {
|
||||||
(function () {
|
|
||||||
var v = location.hash;
|
var v = location.hash;
|
||||||
if (v && v.indexOf('#af-') === 0) {
|
if (v && v.indexOf('#af-') === 0) {
|
||||||
var id = v.slice(2).split('&');
|
var id = v.slice(2).split('&');
|
||||||
@@ -1226,9 +1419,9 @@ function autoplay_blocked(seek) {
|
|||||||
if (!m)
|
if (!m)
|
||||||
return play(id[0]);
|
return play(id[0]);
|
||||||
|
|
||||||
return play(id[0], parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
|
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
|
||||||
}
|
}
|
||||||
})();
|
};
|
||||||
|
|
||||||
|
|
||||||
var thegrid = (function () {
|
var thegrid = (function () {
|
||||||
@@ -1334,35 +1527,57 @@ var thegrid = (function () {
|
|||||||
}
|
}
|
||||||
setsz();
|
setsz();
|
||||||
|
|
||||||
function seltgl(e) {
|
function gclick(e) {
|
||||||
if (e && e.ctrlKey)
|
if (e && e.ctrlKey)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
ev(e);
|
|
||||||
var oth = ebi(this.getAttribute('ref')),
|
var oth = ebi(this.getAttribute('ref')),
|
||||||
td = oth.parentNode.nextSibling,
|
href = this.getAttribute('href'),
|
||||||
|
aplay = ebi('a' + oth.getAttribute('id')),
|
||||||
|
is_img = /\.(gif|jpe?g|png|webp)(\?|$)/i.test(href),
|
||||||
|
in_tree = null,
|
||||||
|
have_sel = QS('#files tr.sel'),
|
||||||
|
td = oth.closest('td').nextSibling,
|
||||||
tr = td.parentNode;
|
tr = td.parentNode;
|
||||||
|
|
||||||
|
if (/\/(\?|$)/.test(href)) {
|
||||||
|
var ta = QSA('#treeul a.hl+ul>li>a+a'),
|
||||||
|
txt = oth.textContent.slice(0, -1);
|
||||||
|
|
||||||
|
for (var a = 0, aa = ta.length; a < aa; a++) {
|
||||||
|
if (ta[a].textContent == txt) {
|
||||||
|
in_tree = ta[a];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.sel) {
|
||||||
td.click();
|
td.click();
|
||||||
this.setAttribute('class', tr.getAttribute('class'));
|
this.setAttribute('class', tr.getAttribute('class'));
|
||||||
}
|
}
|
||||||
|
else if (widget.is_open && aplay)
|
||||||
|
aplay.click();
|
||||||
|
|
||||||
function bgopen(e) {
|
else if (in_tree && !have_sel)
|
||||||
|
in_tree.click();
|
||||||
|
|
||||||
|
else if (!is_img && have_sel)
|
||||||
|
window.open(href, '_blank');
|
||||||
|
|
||||||
|
else return true;
|
||||||
ev(e);
|
ev(e);
|
||||||
var url = this.getAttribute('href');
|
|
||||||
window.open(url, '_blank');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.loadsel = function () {
|
r.loadsel = function () {
|
||||||
if (r.dirty)
|
if (r.dirty)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var ths = QSA('#ggrid>a'),
|
var ths = QSA('#ggrid>a');
|
||||||
have_sel = !!QS('#files tr.sel');
|
|
||||||
|
|
||||||
for (var a = 0, aa = ths.length; a < aa; a++) {
|
for (var a = 0, aa = ths.length; a < aa; a++) {
|
||||||
ths[a].onclick = r.sel ? seltgl : have_sel ? bgopen : null;
|
var tr = ebi(ths[a].getAttribute('ref')).closest('tr');
|
||||||
ths[a].setAttribute('class', ebi(ths[a].getAttribute('ref')).parentNode.parentNode.getAttribute('class'));
|
ths[a].setAttribute('class', tr.getAttribute('class'));
|
||||||
}
|
}
|
||||||
var uns = QS('#ggrid a[ref="unsearch"]');
|
var uns = QS('#ggrid a[ref="unsearch"]');
|
||||||
if (uns)
|
if (uns)
|
||||||
@@ -1393,6 +1608,8 @@ var thegrid = (function () {
|
|||||||
|
|
||||||
if (r.thumbs) {
|
if (r.thumbs) {
|
||||||
ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
|
ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
|
||||||
|
if (href == "#")
|
||||||
|
ihref = '/.cpr/ico/⏏️';
|
||||||
}
|
}
|
||||||
else if (isdir) {
|
else if (isdir) {
|
||||||
ihref = '/.cpr/ico/folder';
|
ihref = '/.cpr/ico/folder';
|
||||||
@@ -1420,6 +1637,11 @@ var thegrid = (function () {
|
|||||||
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
|
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
|
||||||
}
|
}
|
||||||
ebi('ggrid').innerHTML = html.join('\n');
|
ebi('ggrid').innerHTML = html.join('\n');
|
||||||
|
|
||||||
|
var ths = QSA('#ggrid>a');
|
||||||
|
for (var a = 0, aa = ths.length; a < aa; a++)
|
||||||
|
ths[a].onclick = gclick;
|
||||||
|
|
||||||
r.dirty = false;
|
r.dirty = false;
|
||||||
r.bagit();
|
r.bagit();
|
||||||
r.loadsel();
|
r.loadsel();
|
||||||
@@ -1507,26 +1729,32 @@ document.onkeydown = function (e) {
|
|||||||
if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
|
if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || e.isComposing)
|
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var k = (e.code + ''), pos = -1, n;
|
||||||
|
|
||||||
|
if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var k = (e.code + ''), pos = -1;
|
|
||||||
if (k.indexOf('Digit') === 0)
|
if (k.indexOf('Digit') === 0)
|
||||||
pos = parseInt(k.slice(-1)) * 0.1;
|
pos = parseInt(k.slice(-1)) * 0.1;
|
||||||
|
|
||||||
if (pos !== -1)
|
if (pos !== -1)
|
||||||
return seek_au_mul(pos);
|
return seek_au_mul(pos) || true;
|
||||||
|
|
||||||
var n = k == 'KeyJ' ? -1 : k == 'KeyL' ? 1 : 0;
|
if (k == 'KeyJ')
|
||||||
if (n !== 0)
|
return prev_song() || true;
|
||||||
return song_skip(n);
|
|
||||||
|
if (k == 'KeyL')
|
||||||
|
return next_song() || true;
|
||||||
|
|
||||||
if (k == 'KeyP')
|
if (k == 'KeyP')
|
||||||
return playpause();
|
return playpause() || true;
|
||||||
|
|
||||||
n = k == 'KeyU' ? -10 : k == 'KeyO' ? 10 : 0;
|
n = k == 'KeyU' ? -10 : k == 'KeyO' ? 10 : 0;
|
||||||
if (n !== 0)
|
if (n !== 0)
|
||||||
return mp.au ? seek_au_sec(mp.au.currentTime + n) : true;
|
return seek_au_rel(n) || true;
|
||||||
|
|
||||||
n = k == 'KeyI' ? -1 : k == 'KeyK' ? 1 : 0;
|
n = k == 'KeyI' ? -1 : k == 'KeyK' ? 1 : 0;
|
||||||
if (n !== 0)
|
if (n !== 0)
|
||||||
@@ -1536,7 +1764,6 @@ document.onkeydown = function (e) {
|
|||||||
return tree_up();
|
return tree_up();
|
||||||
|
|
||||||
if (k == 'KeyB')
|
if (k == 'KeyB')
|
||||||
//return treectl.hidden ? treectl.show() : treectl.hide();
|
|
||||||
return treectl.hidden ? treectl.entree() : treectl.detree();
|
return treectl.hidden ? treectl.entree() : treectl.detree();
|
||||||
|
|
||||||
if (k == 'KeyG')
|
if (k == 'KeyG')
|
||||||
@@ -1545,6 +1772,14 @@ document.onkeydown = function (e) {
|
|||||||
if (k == 'KeyT')
|
if (k == 'KeyT')
|
||||||
return ebi('thumbs').click();
|
return ebi('thumbs').click();
|
||||||
|
|
||||||
|
if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
|
||||||
|
if (k == 'KeyA')
|
||||||
|
return QS('#twig').click();
|
||||||
|
|
||||||
|
if (k == 'KeyD')
|
||||||
|
return QS('#twobytwo').click();
|
||||||
|
}
|
||||||
|
|
||||||
if (thegrid.en) {
|
if (thegrid.en) {
|
||||||
if (k == 'KeyS')
|
if (k == 'KeyS')
|
||||||
return ebi('gridsel').click();
|
return ebi('gridsel').click();
|
||||||
@@ -2743,8 +2978,10 @@ function reload_mp() {
|
|||||||
mp.au.pause();
|
mp.au.pause();
|
||||||
mp.au = null;
|
mp.au = null;
|
||||||
}
|
}
|
||||||
|
mpl.stop();
|
||||||
widget.close();
|
widget.close();
|
||||||
mp = new MPlayer();
|
mp = new MPlayer();
|
||||||
|
setTimeout(pbar.onresize, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2788,3 +3025,4 @@ function reload_browser(not_mp) {
|
|||||||
reload_browser(true);
|
reload_browser(true);
|
||||||
mukey.render();
|
mukey.render();
|
||||||
msel.render();
|
msel.render();
|
||||||
|
play_linked();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<div>{{ logues[1] }}</div><br />
|
<div>{{ logues[1] }}</div><br />
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h2><a href="{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
<h2><a href="/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -740,9 +740,17 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
function handshakes_permitted() {
|
function handshakes_permitted() {
|
||||||
var lim = multitask ? 1 : 0;
|
var lim = multitask ? 1 : 0;
|
||||||
return lim >=
|
|
||||||
|
if (lim <
|
||||||
st.todo.upload.length +
|
st.todo.upload.length +
|
||||||
st.busy.upload.length;
|
st.busy.upload.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var cd = st.todo.handshake.length ? st.todo.handshake[0].cooldown : 0;
|
||||||
|
if (cd && cd - Date.now() > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hashing_permitted() {
|
function hashing_permitted() {
|
||||||
@@ -1155,6 +1163,15 @@ function up2k_init(subtle) {
|
|||||||
if (rsp.indexOf('<pre>') === 0)
|
if (rsp.indexOf('<pre>') === 0)
|
||||||
rsp = rsp.slice(5);
|
rsp = rsp.slice(5);
|
||||||
|
|
||||||
|
if (rsp.indexOf('rate-limit ') !== -1) {
|
||||||
|
var penalty = rsp.replace(/.*rate-limit /, "").split(' ')[0];
|
||||||
|
console.log("rate-limit: " + penalty);
|
||||||
|
t.cooldown = Date.now() + parseFloat(penalty) * 1000;
|
||||||
|
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
||||||
|
st.todo.handshake.unshift(t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
st.bytes.uploaded += t.size;
|
st.bytes.uploaded += t.size;
|
||||||
if (rsp.indexOf('partial upload exists') !== -1 ||
|
if (rsp.indexOf('partial upload exists') !== -1 ||
|
||||||
rsp.indexOf('file already exists') !== -1) {
|
rsp.indexOf('file already exists') !== -1) {
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ function ev(e) {
|
|||||||
if (e.stopPropagation)
|
if (e.stopPropagation)
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (e.stopImmediatePropagation)
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
e.returnValue = false;
|
e.returnValue = false;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -359,6 +362,15 @@ function get_vpath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function get_pwd() {
|
||||||
|
var pwd = ('; ' + document.cookie).split('; cppwd=');
|
||||||
|
if (pwd.length < 2)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return pwd[1].split(';')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function unix2iso(ts) {
|
function unix2iso(ts) {
|
||||||
return new Date(ts * 1000).toISOString().replace("T", " ").slice(0, -5);
|
return new Date(ts * 1000).toISOString().replace("T", " ").slice(0, -5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,6 @@
|
|||||||
}
|
}
|
||||||
#ggrid>a[href$="/"]:before {
|
#ggrid>a[href$="/"]:before {
|
||||||
content: '📂';
|
content: '📂';
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
margin: -.1em -.4em;
|
|
||||||
text-shadow: 0 0 .1em #000;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -27,8 +22,11 @@
|
|||||||
#ggrid>a:before {
|
#ggrid>a:before {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: -.1em -.4em;
|
padding: .3em 0;
|
||||||
|
margin: -.4em;
|
||||||
text-shadow: 0 0 .1em #000;
|
text-shadow: 0 0 .1em #000;
|
||||||
|
background: linear-gradient(135deg,rgba(255,255,255,0) 50%,rgba(255,255,255,0.2));
|
||||||
|
border-radius: .3em;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,15 @@ cat warks | while IFS= read -r x; do sqlite3 up2k.db "delete from mt where w = '
|
|||||||
# dump all dbs
|
# dump all dbs
|
||||||
find -iname up2k.db | while IFS= read -r x; do sqlite3 "$x" 'select substr(w,1,12), rd, fn from up' | sed -r 's/\|/ \| /g' | while IFS= read -r y; do printf '%s | %s\n' "$x" "$y"; done; done
|
find -iname up2k.db | while IFS= read -r x; do sqlite3 "$x" 'select substr(w,1,12), rd, fn from up' | sed -r 's/\|/ \| /g' | while IFS= read -r y; do printf '%s | %s\n' "$x" "$y"; done; done
|
||||||
|
|
||||||
|
# unschedule mtp scan for all files somewhere under "enc/"
|
||||||
|
sqlite3 -readonly up2k.db 'select substr(up.w,1,16) from up inner join mt on mt.w = substr(up.w,1,16) where rd like "enc/%" and +mt.k = "t:mtp"' > keys; awk '{printf "delete from mt where w = \"%s\" and +k = \"t:mtp\";\n", $0}' <keys | tee /dev/stderr | sqlite3 up2k.db
|
||||||
|
|
||||||
|
# compare metadata key "key" between two databases
|
||||||
|
sqlite3 -readonly up2k.db.key-full 'select w, v from mt where k = "key" order by w' > k1; sqlite3 -readonly up2k.db 'select w, v from mt where k = "key" order by w' > k2; ok=0; ng=0; while IFS='|' read w k2; do k1="$(grep -E "^$w" k1 | sed -r 's/.*\|//')"; [ "$k1" = "$k2" ] && ok=$((ok+1)) || { ng=$((ng+1)); printf '%3s %3s %s\n' "$k1" "$k2" "$(sqlite3 -readonly up2k.db.key-full "select * from up where substr(w,1,16) = '$w'" | sed -r 's/\|/ | /g')"; }; done < <(cat k2); echo "match $ok diff $ng"
|
||||||
|
|
||||||
|
# actually this is much better
|
||||||
|
sqlite3 -readonly up2k.db.key-full 'select w, v from mt where k = "key" order by w' > k1; sqlite3 -readonly up2k.db 'select mt.w, mt.v, up.rd, up.fn from mt inner join up on mt.w = substr(up.w,1,16) where mt.k = "key" order by up.rd, up.fn' > k2; ok=0; ng=0; while IFS='|' read w k2 path; do k1="$(grep -E "^$w" k1 | sed -r 's/.*\|//')"; [ "$k1" = "$k2" ] && ok=$((ok+1)) || { ng=$((ng+1)); printf '%3s %3s %s\n' "$k1" "$k2" "$path"; }; done < <(cat k2); echo "match $ok diff $ng"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## media
|
## media
|
||||||
@@ -157,7 +166,7 @@ dbg.asyncStore.pendingBreakpoints = {}
|
|||||||
about:config >> devtools.debugger.prefs-schema-version = -1
|
about:config >> devtools.debugger.prefs-schema-version = -1
|
||||||
|
|
||||||
# determine server version
|
# determine server version
|
||||||
git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > /dev/shm/revs && cat /dev/shm/revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser,up2k}.js 2>/dev/null | diff -wNarU0 - <(cat /mnt/Users/ed/Downloads/ref/{util,browser,up2k}.js) | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done
|
git pull; git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > ../revs && cat ../{util,browser}.js >../vr && cat ../revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser}.js >../vg 2>/dev/null && diff -wNarU0 ../{vg,vr} | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -200,3 +209,4 @@ mk() { rm -rf /tmp/foo; sudo -u ed bash -c 'mkdir /tmp/foo; echo hi > /tmp/foo/b
|
|||||||
mk && t0="$(date)" && while true; do date -s "$(date '+ 1 hour')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; done; echo "$t0"
|
mk && t0="$(date)" && while true; do date -s "$(date '+ 1 hour')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; done; echo "$t0"
|
||||||
mk && sudo -u ed flock /tmp/foo sleep 40 & sleep 1; ps aux | grep -E 'sleep 40$' && t0="$(date)" && for n in {1..40}; do date -s "$(date '+ 1 day')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; done; echo "$t0"
|
mk && sudo -u ed flock /tmp/foo sleep 40 & sleep 1; ps aux | grep -E 'sleep 40$' && t0="$(date)" && for n in {1..40}; do date -s "$(date '+ 1 day')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; done; echo "$t0"
|
||||||
mk && t0="$(date)" && for n in {1..40}; do date -s "$(date '+ 1 day')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; tar -cf/dev/null /tmp/foo; done; echo "$t0"
|
mk && t0="$(date)" && for n in {1..40}; do date -s "$(date '+ 1 day')"; systemd-tmpfiles --clean; ls -1 /tmp | grep foo || break; tar -cf/dev/null /tmp/foo; done; echo "$t0"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user