mirror of
https://github.com/9001/copyparty.git
synced 2025-11-06 06:43:53 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae3a01038b | ||
|
|
e47a2a4ca2 | ||
|
|
95ea6d5f78 | ||
|
|
7d290f6b8f | ||
|
|
9db617ed5a | ||
|
|
514456940a | ||
|
|
33feefd9cd | ||
|
|
65e14cf348 | ||
|
|
1d61bcc4f3 | ||
|
|
c38bbaca3c | ||
|
|
246d245ebc | ||
|
|
f269a710e2 | ||
|
|
051998429c | ||
|
|
432cdd640f | ||
|
|
9ed9b0964e | ||
|
|
6a97b3526d | ||
|
|
451d757996 | ||
|
|
f9e9eba3b1 | ||
|
|
2a9a6aebd9 | ||
|
|
adbb6c449e | ||
|
|
3993605324 | ||
|
|
0ae574ec2c | ||
|
|
c56ded828c |
84
README.md
84
README.md
@@ -16,11 +16,6 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [thumbnails](#thumbnails) // [md-viewer](#markdown-viewer) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [ie4](#browser-support)
|
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [thumbnails](#thumbnails) // [md-viewer](#markdown-viewer) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [ie4](#browser-support)
|
||||||
|
|
||||||
|
|
||||||
## breaking changes \o/
|
|
||||||
|
|
||||||
this is the readme for v0.12 which has a different expression for volume permissions (`-v`); see [the v0.11.x readme](https://github.com/9001/copyparty/tree/15b59822112dda56cee576df30f331252fc62628#readme) for stuff regarding the [current stable release](https://github.com/9001/copyparty/releases/tag/v0.11.47)
|
|
||||||
|
|
||||||
|
|
||||||
## readme toc
|
## readme toc
|
||||||
|
|
||||||
* top
|
* top
|
||||||
@@ -41,6 +36,7 @@ this is the readme for v0.12 which has a different expression for volume permiss
|
|||||||
* [uploading](#uploading)
|
* [uploading](#uploading)
|
||||||
* [file-search](#file-search)
|
* [file-search](#file-search)
|
||||||
* [file manager](#file-manager)
|
* [file manager](#file-manager)
|
||||||
|
* [batch rename](#batch-rename)
|
||||||
* [markdown viewer](#markdown-viewer)
|
* [markdown viewer](#markdown-viewer)
|
||||||
* [other tricks](#other-tricks)
|
* [other tricks](#other-tricks)
|
||||||
* [searching](#searching)
|
* [searching](#searching)
|
||||||
@@ -125,30 +121,31 @@ summary: all planned features work! now please enjoy the bloatening
|
|||||||
* ☑ [accounts](#accounts-and-volumes)
|
* ☑ [accounts](#accounts-and-volumes)
|
||||||
* upload
|
* upload
|
||||||
* ☑ basic: plain multipart, ie6 support
|
* ☑ basic: plain multipart, ie6 support
|
||||||
* ☑ up2k: js, resumable, multithreaded
|
* ☑ [up2k](#uploading): js, resumable, multithreaded
|
||||||
* ☑ stash: simple PUT filedropper
|
* ☑ stash: simple PUT filedropper
|
||||||
* ☑ unpost: undo/delete accidental uploads
|
* ☑ unpost: undo/delete accidental uploads
|
||||||
* ☑ symlink/discard existing files (content-matching)
|
* ☑ symlink/discard existing files (content-matching)
|
||||||
* download
|
* download
|
||||||
* ☑ single files in browser
|
* ☑ single files in browser
|
||||||
* ☑ folders as zip / tar files
|
* ☑ [folders as zip / tar files](#zip-downloads)
|
||||||
* ☑ FUSE client (read-only)
|
* ☑ FUSE client (read-only)
|
||||||
* browser
|
* browser
|
||||||
* ☑ navpane (directory tree sidebar)
|
* ☑ navpane (directory tree sidebar)
|
||||||
|
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
|
||||||
* ☑ audio player (with OS media controls)
|
* ☑ audio player (with OS media controls)
|
||||||
* ☑ thumbnails
|
* ☑ image gallery with webm player
|
||||||
|
* ☑ [thumbnails](#thumbnails)
|
||||||
* ☑ ...of images using Pillow
|
* ☑ ...of images using Pillow
|
||||||
* ☑ ...of videos using FFmpeg
|
* ☑ ...of videos using FFmpeg
|
||||||
* ☑ cache eviction (max-age; maybe max-size eventually)
|
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||||
* ☑ image gallery with webm player
|
|
||||||
* ☑ SPA (browse while uploading)
|
* ☑ SPA (browse while uploading)
|
||||||
* if you use the navpane to navigate, not folders in the file list
|
* if you use the navpane to navigate, not folders in the file list
|
||||||
* server indexing
|
* server indexing
|
||||||
* ☑ locate files by contents
|
* ☑ [locate files by contents](#file-search)
|
||||||
* ☑ search by name/path/date/size
|
* ☑ search by name/path/date/size
|
||||||
* ☑ search by ID3-tags etc.
|
* ☑ [search by ID3-tags etc.](#searching)
|
||||||
* markdown
|
* markdown
|
||||||
* ☑ viewer
|
* ☑ [viewer](#markdown-viewer)
|
||||||
* ☑ editor (sure why not)
|
* ☑ editor (sure why not)
|
||||||
|
|
||||||
|
|
||||||
@@ -181,7 +178,7 @@ small collection of user feedback
|
|||||||
* this is an msys2 bug, the regular windows edition of python is fine
|
* this is an msys2 bug, the regular windows edition of python is fine
|
||||||
|
|
||||||
* VirtualBox: sqlite throws `Disk I/O Error` when running in a VM and the up2k database is in a vboxsf
|
* VirtualBox: sqlite throws `Disk I/O Error` when running in a VM and the up2k database is in a vboxsf
|
||||||
* use `--hist` or the `hist` volflag (`-v [...]:chist=/tmp/foo`) to place the db inside the vm instead
|
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db inside the vm instead
|
||||||
|
|
||||||
|
|
||||||
# accounts and volumes
|
# accounts and volumes
|
||||||
@@ -373,6 +370,53 @@ if you have the required permissions, you can cut/paste, rename, and delete file
|
|||||||
you can move files across browser tabs (cut in one tab, paste in another)
|
you can move files across browser tabs (cut in one tab, paste in another)
|
||||||
|
|
||||||
|
|
||||||
|
## batch rename
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
select some files and press F2 to bring up the rename UI
|
||||||
|
|
||||||
|
quick explanation of the buttons,
|
||||||
|
* `[✅ apply rename]` confirms and begins renaming
|
||||||
|
* `[❌ cancel]` aborts and closes the rename window
|
||||||
|
* `[↺ reset]` reverts any filename changes back to the original name
|
||||||
|
* `[decode]` does a URL-decode on the filename, fixing stuff like `&` and `%20`
|
||||||
|
* `[advanced]` toggles advanced mode
|
||||||
|
|
||||||
|
advanced mode: rename files based on rules to decide the new names, based on the original name (regex), or based on the tags collected from the file (artist/title/...), or a mix of both
|
||||||
|
|
||||||
|
in advanced mode,
|
||||||
|
* `[case]` toggles case-sensitive regex
|
||||||
|
* `regex` is the regex pattern to apply to the original filename; any files which don't match will be skipped
|
||||||
|
* `format` is the new filename, taking values from regex capturing groups and/or from file tags
|
||||||
|
* very loosely based on foobar2000 syntax
|
||||||
|
* `presets` lets you save rename rules for later
|
||||||
|
|
||||||
|
available functions:
|
||||||
|
* `$lpad(text, length, pad_char)`
|
||||||
|
* `$rpad(text, length, pad_char)`
|
||||||
|
|
||||||
|
so,
|
||||||
|
|
||||||
|
say you have a file named [`meganeko - Eclipse - 07 Sirius A.mp3`](https://www.youtube.com/watch?v=-dtb0vDPruI) (absolutely fantastic album btw) and the tags are: `Album:Eclipse`, `Artist:meganeko`, `Title:Sirius A`, `tn:7`
|
||||||
|
|
||||||
|
you could use just regex to rename it:
|
||||||
|
* `regex` = `(.*) - (.*) - ([0-9]{2}) (.*)`
|
||||||
|
* `format` = `(3). (1) - (4)`
|
||||||
|
* `output` = `07. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
or you could use just tags:
|
||||||
|
* `format` = `$lpad((tn),2,0). (artist) - (title).(ext)`
|
||||||
|
* `output` = `7. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
or a mix of both:
|
||||||
|
* `regex` = ` - ([0-9]{2}) `
|
||||||
|
* `format` = `(1). (artist) - (title).(ext)`
|
||||||
|
* `output` = `07. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
the metadata keys you can use in the format field are the ones in the file-browser table header (whatever is collected with `-mte` and `-mtp`)
|
||||||
|
|
||||||
|
|
||||||
## markdown viewer
|
## markdown viewer
|
||||||
|
|
||||||

|

|
||||||
@@ -415,9 +459,9 @@ through arguments:
|
|||||||
* `-e2tsr` deletes all existing tags, does a full reindex
|
* `-e2tsr` deletes all existing tags, does a full reindex
|
||||||
|
|
||||||
the same arguments can be set as volume flags, in addition to `d2d` and `d2t` for disabling:
|
the same arguments can be set as volume flags, in addition to `d2d` and `d2t` for disabling:
|
||||||
* `-v ~/music::r:ce2dsa:ce2tsr` does a full reindex of everything on startup
|
* `-v ~/music::r:c,e2dsa:c,e2tsr` does a full reindex of everything on startup
|
||||||
* `-v ~/music::r:cd2d` disables **all** indexing, even if any `-e2*` are on
|
* `-v ~/music::r:c,d2d` disables **all** indexing, even if any `-e2*` are on
|
||||||
* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
|
* `-v ~/music::r:c,d2t` disables all `-e2t*` (tags), does not affect `-e2d*`
|
||||||
|
|
||||||
note:
|
note:
|
||||||
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
|
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
|
||||||
@@ -436,7 +480,7 @@ if you set `--no-hash`, you can enable hashing for specific volumes using flag `
|
|||||||
copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
|
copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
|
||||||
|
|
||||||
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
|
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
|
||||||
* `--hist ~/.cache/copyparty -v ~/music::r:chist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
|
* `--hist ~/.cache/copyparty -v ~/music::r:c,hist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
|
||||||
|
|
||||||
note:
|
note:
|
||||||
* markdown edits are always stored in a local `.hist` subdirectory
|
* markdown edits are always stored in a local `.hist` subdirectory
|
||||||
@@ -447,10 +491,12 @@ note:
|
|||||||
## metadata from audio files
|
## metadata from audio files
|
||||||
|
|
||||||
`-mte` decides which tags to index and display in the browser (and also the display order), this can be changed per-volume:
|
`-mte` decides which tags to index and display in the browser (and also the display order), this can be changed per-volume:
|
||||||
* `-v ~/music::r:cmte=title,artist` indexes and displays *title* followed by *artist*
|
* `-v ~/music::r:c,mte=title,artist` indexes and displays *title* followed by *artist*
|
||||||
|
|
||||||
if you add/remove a tag from `mte` you will need to run with `-e2tsr` once to rebuild the database, otherwise only new files will be affected
|
if you add/remove a tag from `mte` you will need to run with `-e2tsr` once to rebuild the database, otherwise only new files will be affected
|
||||||
|
|
||||||
|
but instead of using `-mte`, `-mth` is a better way to hide tags in the browser: these tags will not be displayed by default, but they still get indexed and become searchable, and users can choose to unhide them in the settings pane
|
||||||
|
|
||||||
`-mtm` can be used to add or redefine a metadata mapping, say you have media files with `foo` and `bar` tags and you want them to display as `qux` in the browser (preferring `foo` if both are present), then do `-mtm qux=foo,bar` and now you can `-mte artist,title,qux`
|
`-mtm` can be used to add or redefine a metadata mapping, say you have media files with `foo` and `bar` tags and you want them to display as `qux` in the browser (preferring `foo` if both are present), then do `-mtm qux=foo,bar` and now you can `-mte artist,title,qux`
|
||||||
|
|
||||||
tags that start with a `.` such as `.bpm` and `.dur`(ation) indicate numeric value
|
tags that start with a `.` such as `.bpm` and `.dur`(ation) indicate numeric value
|
||||||
@@ -471,7 +517,7 @@ copyparty can invoke external programs to collect additional metadata for files
|
|||||||
|
|
||||||
* `-mtp .bpm=~/bin/audio-bpm.py` will execute `~/bin/audio-bpm.py` with the audio file as argument 1 to provide the `.bpm` tag, if that does not exist in the audio metadata
|
* `-mtp .bpm=~/bin/audio-bpm.py` will execute `~/bin/audio-bpm.py` with the audio file as argument 1 to provide the `.bpm` tag, if that does not exist in the audio metadata
|
||||||
* `-mtp key=f,t5,~/bin/audio-key.py` uses `~/bin/audio-key.py` to get the `key` tag, replacing any existing metadata tag (`f,`), aborting if it takes longer than 5sec (`t5,`)
|
* `-mtp key=f,t5,~/bin/audio-key.py` uses `~/bin/audio-key.py` to get the `key` tag, replacing any existing metadata tag (`f,`), aborting if it takes longer than 5sec (`t5,`)
|
||||||
* `-v ~/music::r:cmtp=.bpm=~/bin/audio-bpm.py:cmtp=key=f,t5,~/bin/audio-key.py` both as a per-volume config wow this is getting ugly
|
* `-v ~/music::r:c,mtp=.bpm=~/bin/audio-bpm.py:c,mtp=key=f,t5,~/bin/audio-key.py` both as a per-volume config wow this is getting ugly
|
||||||
|
|
||||||
*but wait, there's more!* `-mtp` can be used for non-audio files as well using the `a` flag: `ay` only do audio files, `an` only do non-audio files, or `ad` do all files (d as in dontcare)
|
*but wait, there's more!* `-mtp` can be used for non-audio files as well using the `a` flag: `ay` only do audio files, `an` only do non-audio files, or `ad` do all files (d as in dontcare)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ some of these rely on libraries which are not MIT-compatible
|
|||||||
|
|
||||||
* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
|
* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
|
||||||
* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
|
* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
|
||||||
|
* [media-hash.py](./media-hash.py) generates checksums for audio and video streams; uses FFmpeg (LGPL or GPL)
|
||||||
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
@@ -18,7 +19,10 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
|
|||||||
|
|
||||||
# usage from copyparty
|
# usage from copyparty
|
||||||
|
|
||||||
`copyparty -e2dsa -e2ts -mtp key=f,audio-key.py -mtp .bpm=f,audio-bpm.py`
|
`copyparty -e2dsa -e2ts` followed by any combination of these:
|
||||||
|
* `-mtp key=f,audio-key.py`
|
||||||
|
* `-mtp .bpm=f,audio-bpm.py`
|
||||||
|
* `-mtp ahash,vhash=f,media-hash.py`
|
||||||
|
|
||||||
* `f,` makes the detected value replace any existing values
|
* `f,` makes the detected value replace any existing values
|
||||||
* the `.` in `.bpm` indicates numeric value
|
* the `.` in `.bpm` indicates numeric value
|
||||||
@@ -29,6 +33,9 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
|
|||||||
## usage with volume-flags
|
## usage with volume-flags
|
||||||
|
|
||||||
instead of affecting all volumes, you can set the options for just one volume like so:
|
instead of affecting all volumes, you can set the options for just one volume like so:
|
||||||
```
|
|
||||||
copyparty -v /mnt/nas/music:/music:r:cmtp=key=f,audio-key.py:cmtp=.bpm=f,audio-bpm.py:ce2dsa:ce2ts
|
`copyparty -v /mnt/nas/music:/music:r:c,e2dsa:c,e2ts` immediately followed by any combination of these:
|
||||||
```
|
|
||||||
|
* `:c,mtp=key=f,audio-key.py`
|
||||||
|
* `:c,mtp=.bpm=f,audio-bpm.py`
|
||||||
|
* `:c,mtp=ahash,vhash=f,media-hash.py`
|
||||||
|
|||||||
73
bin/mtag/media-hash.py
Normal file
73
bin/mtag/media-hash.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
try:
|
||||||
|
from copyparty.util import fsenc
|
||||||
|
except:
|
||||||
|
|
||||||
|
def fsenc(p):
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
dep: ffmpeg
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def det():
|
||||||
|
# fmt: off
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-nostdin",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-i", fsenc(sys.argv[1]),
|
||||||
|
"-f", "framemd5",
|
||||||
|
"-"
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
p = sp.Popen(cmd, stdout=sp.PIPE)
|
||||||
|
# ps = io.TextIOWrapper(p.stdout, encoding="utf-8")
|
||||||
|
ps = p.stdout
|
||||||
|
|
||||||
|
chans = {}
|
||||||
|
for ln in ps:
|
||||||
|
if ln.startswith(b"#stream#"):
|
||||||
|
break
|
||||||
|
|
||||||
|
m = re.match(r"^#media_type ([0-9]): ([a-zA-Z])", ln.decode("utf-8"))
|
||||||
|
if m:
|
||||||
|
chans[m.group(1)] = m.group(2)
|
||||||
|
|
||||||
|
hashers = [hashlib.sha512(), hashlib.sha512()]
|
||||||
|
for ln in ps:
|
||||||
|
n = int(ln[:1])
|
||||||
|
v = ln.rsplit(b",", 1)[-1].strip()
|
||||||
|
hashers[n].update(v)
|
||||||
|
|
||||||
|
r = {}
|
||||||
|
for k, v in chans.items():
|
||||||
|
dg = hashers[int(k)].digest()[:12]
|
||||||
|
dg = base64.urlsafe_b64encode(dg).decode("ascii")
|
||||||
|
r[v[0].lower() + "hash"] = dg
|
||||||
|
|
||||||
|
print(json.dumps(r, indent=4))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
det()
|
||||||
|
except:
|
||||||
|
pass # mute
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -15,8 +15,11 @@
|
|||||||
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
||||||
#
|
#
|
||||||
# enable line-buffering for realtime logging (slight performance cost):
|
# enable line-buffering for realtime logging (slight performance cost):
|
||||||
# modify ExecStart and prefix it with `/bin/stdbuf -oL` like so:
|
# modify ExecStart and prefix it with `/usr/bin/stdbuf -oL` like so:
|
||||||
# ExecStart=/bin/stdbuf -oL /usr/bin/python3 [...]
|
# ExecStart=/usr/bin/stdbuf -oL /usr/bin/python3 [...]
|
||||||
|
# but some systemd versions require this instead (higher performance cost):
|
||||||
|
# inside the [Service] block, add the following line:
|
||||||
|
# Environment=PYTHONUNBUFFERED=x
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=copyparty file server
|
Description=copyparty file server
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from .__init__ import E, WINDOWS, VT100, PY2, unicode
|
|||||||
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
from .util import py_desc, align_tab, IMPLICATIONS
|
from .util import py_desc, align_tab, IMPLICATIONS
|
||||||
|
from .authsrv import re_vol
|
||||||
|
|
||||||
HAVE_SSL = True
|
HAVE_SSL = True
|
||||||
try:
|
try:
|
||||||
@@ -326,7 +327,7 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
||||||
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
||||||
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
||||||
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume state")
|
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)")
|
||||||
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
|
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
|
||||||
ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval")
|
ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval")
|
||||||
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval (0=off)")
|
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval (0=off)")
|
||||||
@@ -341,7 +342,9 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
|
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
|
||||||
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
||||||
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
||||||
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps")
|
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,res,.fps,ahash,vhash")
|
||||||
|
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
|
||||||
|
default=".vq,.aq,vc,ac,res,.fps")
|
||||||
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('appearance options')
|
ap2 = ap.add_argument_group('appearance options')
|
||||||
@@ -396,10 +399,16 @@ def main(argv=None):
|
|||||||
nstrs = []
|
nstrs = []
|
||||||
anymod = False
|
anymod = False
|
||||||
for ostr in al.v or []:
|
for ostr in al.v or []:
|
||||||
|
m = re_vol.match(ostr)
|
||||||
|
if not m:
|
||||||
|
# not our problem
|
||||||
|
nstrs.append(ostr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
src, dst, perms = m.groups()
|
||||||
|
na = [src, dst]
|
||||||
mod = False
|
mod = False
|
||||||
oa = ostr.split(":")
|
for opt in perms.split(":"):
|
||||||
na = oa[:2]
|
|
||||||
for opt in oa[2:]:
|
|
||||||
if re.match("c[^,]", opt):
|
if re.match("c[^,]", opt):
|
||||||
mod = True
|
mod = True
|
||||||
na.append("c," + opt[1:])
|
na.append("c," + opt[1:])
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (0, 12, 8)
|
VERSION = (0, 12, 12)
|
||||||
CODENAME = "fil\033[33med"
|
CODENAME = "fil\033[33med"
|
||||||
BUILD_DT = (2021, 8, 1)
|
BUILD_DT = (2021, 8, 6)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -310,6 +310,12 @@ class VFS(object):
|
|||||||
yield f
|
yield f
|
||||||
|
|
||||||
|
|
||||||
|
if WINDOWS:
|
||||||
|
re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
|
||||||
|
else:
|
||||||
|
re_vol = re.compile(r"^([^:]*):([^:]*):(.*)$")
|
||||||
|
|
||||||
|
|
||||||
class AuthSrv(object):
|
class AuthSrv(object):
|
||||||
"""verifies users against given paths"""
|
"""verifies users against given paths"""
|
||||||
|
|
||||||
@@ -319,11 +325,6 @@ class AuthSrv(object):
|
|||||||
self.warn_anonwrite = warn_anonwrite
|
self.warn_anonwrite = warn_anonwrite
|
||||||
self.line_ctr = 0
|
self.line_ctr = 0
|
||||||
|
|
||||||
if WINDOWS:
|
|
||||||
self.re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
|
|
||||||
else:
|
|
||||||
self.re_vol = re.compile(r"^([^:]*):([^:]*):(.*)$")
|
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
@@ -453,7 +454,7 @@ class AuthSrv(object):
|
|||||||
# list of src:dst:permset:permset:...
|
# list of src:dst:permset:permset:...
|
||||||
# permset is <rwmd>[,username][,username] or <c>,<flag>[=args]
|
# permset is <rwmd>[,username][,username] or <c>,<flag>[=args]
|
||||||
for v_str in self.args.v:
|
for v_str in self.args.v:
|
||||||
m = self.re_vol.match(v_str)
|
m = re_vol.match(v_str)
|
||||||
if not m:
|
if not m:
|
||||||
raise Exception("invalid -v argument: [{}]".format(v_str))
|
raise Exception("invalid -v argument: [{}]".format(v_str))
|
||||||
|
|
||||||
@@ -624,9 +625,11 @@ class AuthSrv(object):
|
|||||||
if k1 in vol.flags:
|
if k1 in vol.flags:
|
||||||
vol.flags[k2] = True
|
vol.flags[k2] = True
|
||||||
|
|
||||||
# default tag-list if unset
|
# default tag cfgs if unset
|
||||||
if "mte" not in vol.flags:
|
if "mte" not in vol.flags:
|
||||||
vol.flags["mte"] = self.args.mte
|
vol.flags["mte"] = self.args.mte
|
||||||
|
if "mth" not in vol.flags:
|
||||||
|
vol.flags["mth"] = self.args.mth
|
||||||
|
|
||||||
# append parsers from argv to volume-flags
|
# append parsers from argv to volume-flags
|
||||||
self._read_volflag(vol.flags, "mtp", self.args.mtp, True)
|
self._read_volflag(vol.flags, "mtp", self.args.mtp, True)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
from copyparty.authsrv import AuthSrv
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
@@ -9,6 +8,7 @@ import threading
|
|||||||
from .broker_util import ExceptionalQueue
|
from .broker_util import ExceptionalQueue
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
from .util import FAKE_MP
|
from .util import FAKE_MP
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
|
||||||
|
|
||||||
class MpWorker(object):
|
class MpWorker(object):
|
||||||
|
|||||||
@@ -1755,7 +1755,7 @@ class HttpCli(object):
|
|||||||
"acct": self.uname,
|
"acct": self.uname,
|
||||||
"perms": json.dumps(perms),
|
"perms": json.dumps(perms),
|
||||||
"taglist": [],
|
"taglist": [],
|
||||||
"tag_order": [],
|
"def_hcols": [],
|
||||||
"have_up2k_idx": ("e2d" in vn.flags),
|
"have_up2k_idx": ("e2d" in vn.flags),
|
||||||
"have_tags_idx": ("e2t" in vn.flags),
|
"have_tags_idx": ("e2t" in vn.flags),
|
||||||
"have_mv": (not self.args.no_mv),
|
"have_mv": (not self.args.no_mv),
|
||||||
@@ -1952,8 +1952,8 @@ class HttpCli(object):
|
|||||||
j2a["logues"] = logues
|
j2a["logues"] = logues
|
||||||
j2a["taglist"] = taglist
|
j2a["taglist"] = taglist
|
||||||
|
|
||||||
if "mte" in vn.flags:
|
if "mth" in vn.flags:
|
||||||
j2a["tag_order"] = json.dumps(vn.flags["mte"].split(","))
|
j2a["def_hcols"] = vn.flags["mth"].split(",")
|
||||||
|
|
||||||
if self.args.css_browser:
|
if self.args.css_browser:
|
||||||
j2a["css"] = self.args.css_browser
|
j2a["css"] = self.args.css_browser
|
||||||
|
|||||||
@@ -174,25 +174,26 @@ class HttpSrv(object):
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if now - (self.tp_time or now) > 300:
|
if now - (self.tp_time or now) > 300:
|
||||||
|
m = "httpserver threadpool died: tpt {:.2f}, now {:.2f}, nthr {}, ncli {}"
|
||||||
|
self.log(self.name, m.format(self.tp_time, now, self.tp_nthr, self.ncli), 1)
|
||||||
|
self.tp_time = None
|
||||||
self.tp_q = None
|
self.tp_q = None
|
||||||
|
|
||||||
if self.tp_q:
|
with self.mutex:
|
||||||
self.tp_q.put((sck, addr))
|
self.ncli += 1
|
||||||
with self.mutex:
|
if self.tp_q:
|
||||||
self.ncli += 1
|
|
||||||
self.tp_time = self.tp_time or now
|
self.tp_time = self.tp_time or now
|
||||||
self.tp_ncli = max(self.tp_ncli, self.ncli + 1)
|
self.tp_ncli = max(self.tp_ncli, self.ncli)
|
||||||
if self.tp_nthr < self.ncli + 4:
|
if self.tp_nthr < self.ncli + 4:
|
||||||
self.start_threads(8)
|
self.start_threads(8)
|
||||||
return
|
|
||||||
|
self.tp_q.put((sck, addr))
|
||||||
|
return
|
||||||
|
|
||||||
if not self.args.no_htp:
|
if not self.args.no_htp:
|
||||||
m = "looks like the httpserver threadpool died; please make an issue on github and tell me the story of how you pulled that off, thanks and dog bless\n"
|
m = "looks like the httpserver threadpool died; please make an issue on github and tell me the story of how you pulled that off, thanks and dog bless\n"
|
||||||
self.log(self.name, m, 1)
|
self.log(self.name, m, 1)
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
self.ncli += 1
|
|
||||||
|
|
||||||
thr = threading.Thread(
|
thr = threading.Thread(
|
||||||
target=self.thr_client,
|
target=self.thr_client,
|
||||||
args=(sck, addr),
|
args=(sck, addr),
|
||||||
|
|||||||
@@ -434,7 +434,15 @@ class MTag(object):
|
|||||||
try:
|
try:
|
||||||
v = getattr(md.info, attr)
|
v = getattr(md.info, attr)
|
||||||
except:
|
except:
|
||||||
continue
|
if k != "ac":
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
v = str(md.info).split(".")[1]
|
||||||
|
if v.startswith("ogg"):
|
||||||
|
v = v[3:]
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
if not v:
|
if not v:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -205,8 +205,8 @@ class ThumbSrv(object):
|
|||||||
try:
|
try:
|
||||||
fun(abspath, tpath)
|
fun(abspath, tpath)
|
||||||
except:
|
except:
|
||||||
msg = "{} failed on {}\n{}"
|
msg = "{} could not create thumbnail of {}\n{}"
|
||||||
self.log(msg.format(fun.__name__, abspath, min_ex()), 3)
|
self.log(msg.format(fun.__name__, abspath, min_ex()), "1;30")
|
||||||
with open(tpath, "wb") as _:
|
with open(tpath, "wb") as _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -286,8 +286,9 @@ class ThumbSrv(object):
|
|||||||
cmd += seek
|
cmd += seek
|
||||||
cmd += [
|
cmd += [
|
||||||
b"-i", fsenc(abspath),
|
b"-i", fsenc(abspath),
|
||||||
|
b"-map", b"0:v:0",
|
||||||
b"-vf", scale,
|
b"-vf", scale,
|
||||||
b"-vframes", b"1",
|
b"-frames:v", b"1",
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
@@ -308,8 +309,9 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
ret, sout, serr = runcmd(cmd)
|
ret, sout, serr = runcmd(cmd)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
msg = ["ff: {}".format(x) for x in serr.split("\n")]
|
m = "FFmpeg failed (probably a corrupt video file):\n"
|
||||||
self.log("FFmpeg failed:\n" + "\n".join(msg), c="1;30")
|
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
|
||||||
|
self.log(m, c="1;30")
|
||||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||||
|
|
||||||
def poke(self, tdir):
|
def poke(self, tdir):
|
||||||
|
|||||||
@@ -1422,9 +1422,10 @@ class Up2k(object):
|
|||||||
if not srem:
|
if not srem:
|
||||||
raise Pebkac(400, "mv: cannot move a mountpoint")
|
raise Pebkac(400, "mv: cannot move a mountpoint")
|
||||||
|
|
||||||
st = bos.stat(sabs)
|
st = bos.lstat(sabs)
|
||||||
if stat.S_ISREG(st.st_mode):
|
if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
|
||||||
return self._mv_file(uname, svp, dvp)
|
with self.mutex:
|
||||||
|
return self._mv_file(uname, svp, dvp)
|
||||||
|
|
||||||
jail = svn.get_dbv(srem)[0]
|
jail = svn.get_dbv(srem)[0]
|
||||||
permsets = [[True, False, True]]
|
permsets = [[True, False, True]]
|
||||||
@@ -1449,7 +1450,8 @@ class Up2k(object):
|
|||||||
raise Pebkac(500, "mv: bug at {}, top {}".format(svpf, svp))
|
raise Pebkac(500, "mv: bug at {}, top {}".format(svpf, svp))
|
||||||
|
|
||||||
dvpf = dvp + svpf[len(svp) :]
|
dvpf = dvp + svpf[len(svp) :]
|
||||||
self._mv_file(uname, svpf, dvpf)
|
with self.mutex:
|
||||||
|
self._mv_file(uname, svpf, dvpf)
|
||||||
|
|
||||||
rmdirs(self.log_func, scandir, True, sabs)
|
rmdirs(self.log_func, scandir, True, sabs)
|
||||||
return "k"
|
return "k"
|
||||||
@@ -1541,13 +1543,15 @@ class Up2k(object):
|
|||||||
self.log("forgetting {}".format(vrem))
|
self.log("forgetting {}".format(vrem))
|
||||||
if wark:
|
if wark:
|
||||||
self.log("found {} in db".format(wark))
|
self.log("found {} in db".format(wark))
|
||||||
if self._relink(wark, ptop, vrem, None):
|
if drop_tags:
|
||||||
drop_tags = False
|
if self._relink(wark, ptop, vrem, None):
|
||||||
|
drop_tags = False
|
||||||
|
|
||||||
if drop_tags:
|
if drop_tags:
|
||||||
q = "delete from mt where w=?"
|
q = "delete from mt where w=?"
|
||||||
cur.execute(q, (wark[:16],))
|
cur.execute(q, (wark[:16],))
|
||||||
self.db_rm(cur, srd, sfn)
|
|
||||||
|
self.db_rm(cur, srd, sfn)
|
||||||
|
|
||||||
reg = self.registry.get(ptop)
|
reg = self.registry.get(ptop)
|
||||||
if reg:
|
if reg:
|
||||||
@@ -1599,7 +1603,7 @@ class Up2k(object):
|
|||||||
# deleting final remaining full copy; swap it with a symlink
|
# deleting final remaining full copy; swap it with a symlink
|
||||||
slabs = list(sorted(links.keys()))[0]
|
slabs = list(sorted(links.keys()))[0]
|
||||||
ptop, rem = links.pop(slabs)
|
ptop, rem = links.pop(slabs)
|
||||||
self.log("linkswap [{}] and [{}]".format(sabs, dabs))
|
self.log("linkswap [{}] and [{}]".format(sabs, slabs))
|
||||||
bos.unlink(slabs)
|
bos.unlink(slabs)
|
||||||
bos.rename(sabs, slabs)
|
bos.rename(sabs, slabs)
|
||||||
self._symlink(slabs, sabs, False)
|
self._symlink(slabs, sabs, False)
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
padding-bottom: 5em;
|
|
||||||
}
|
|
||||||
pre, code, tt {
|
pre, code, tt {
|
||||||
font-family: monospace, monospace;
|
font-family: monospace, monospace;
|
||||||
}
|
}
|
||||||
@@ -810,6 +807,7 @@ input.eq_gain {
|
|||||||
#wrap {
|
#wrap {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
min-height: 90vh;
|
min-height: 90vh;
|
||||||
|
padding-bottom: 5em;
|
||||||
}
|
}
|
||||||
#tree {
|
#tree {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -1081,19 +1079,39 @@ html.light #ggrid a:hover {
|
|||||||
padding: 1em;
|
padding: 1em;
|
||||||
z-index: 765;
|
z-index: 765;
|
||||||
}
|
}
|
||||||
|
html.light #rui {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
#rui div+div {
|
#rui div+div {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
#rui table {
|
#rui table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
#rui td {
|
#rui td+td {
|
||||||
padding: .2em .5em;
|
padding: .2em 0 .2em .5em;
|
||||||
|
}
|
||||||
|
#rn_vadv input {
|
||||||
|
font-family: monospace, monospace;
|
||||||
}
|
}
|
||||||
#rui td+td,
|
#rui td+td,
|
||||||
#rui td input {
|
#rui td input[type="text"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#rn_f.m td:first-child {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#rn_f.m td+td {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
#rn_f .err td {
|
||||||
|
background: #c00;
|
||||||
|
}
|
||||||
|
#rn_f .err input[readonly] {
|
||||||
|
background: #600;
|
||||||
|
color: #fc0;
|
||||||
|
}
|
||||||
#rui input[readonly] {
|
#rui input[readonly] {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #444;
|
background: #444;
|
||||||
@@ -1109,6 +1127,7 @@ html.light #ggrid a:hover {
|
|||||||
#barbuf,
|
#barbuf,
|
||||||
#barpos,
|
#barpos,
|
||||||
#u2conf label,
|
#u2conf label,
|
||||||
|
#rui label,
|
||||||
#ops {
|
#ops {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
<script>
|
<script>
|
||||||
var acct = "{{ acct }}",
|
var acct = "{{ acct }}",
|
||||||
perms = {{ perms }},
|
perms = {{ perms }},
|
||||||
tag_order_cfg = {{ tag_order }},
|
def_hcols = {{ def_hcols|tojson }},
|
||||||
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
||||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||||
have_mv = {{ have_mv|tojson }},
|
have_mv = {{ have_mv|tojson }},
|
||||||
|
|||||||
@@ -1460,6 +1460,105 @@ function play_linked() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function fmt_ren(re, md, fmt) {
|
||||||
|
var ptr = 0;
|
||||||
|
function dive(stop_ch) {
|
||||||
|
var ret = '', ng = 0;
|
||||||
|
while (ptr < fmt.length) {
|
||||||
|
var dbg = fmt.slice(ptr),
|
||||||
|
ch = fmt[ptr++];
|
||||||
|
|
||||||
|
if (ch == '\\') {
|
||||||
|
ret += fmt[ptr++];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == ')' || ch == ']' || ch == stop_ch)
|
||||||
|
return [ng, ret];
|
||||||
|
|
||||||
|
if (ch == '[') {
|
||||||
|
var r2 = dive();
|
||||||
|
if (r2[0] == 0)
|
||||||
|
ret += r2[1];
|
||||||
|
}
|
||||||
|
else if (ch == '(') {
|
||||||
|
var end = fmt.indexOf(')', ptr);
|
||||||
|
if (end < 0)
|
||||||
|
throw 'the ( was never closed: ' + fmt.slice(0, ptr);
|
||||||
|
|
||||||
|
var arg = fmt.slice(ptr, end), v = null;
|
||||||
|
ptr = end + 1;
|
||||||
|
|
||||||
|
if (arg != parseInt(arg))
|
||||||
|
v = md[arg];
|
||||||
|
else {
|
||||||
|
arg = parseInt(arg);
|
||||||
|
if (arg >= re.length)
|
||||||
|
throw 'matching group ' + arg + ' exceeds ' + (re.length - 0);
|
||||||
|
|
||||||
|
v = re[arg];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v !== null && v !== undefined)
|
||||||
|
ret += v;
|
||||||
|
else
|
||||||
|
ng++;
|
||||||
|
}
|
||||||
|
else if (ch == '$') {
|
||||||
|
ch = fmt[ptr++];
|
||||||
|
var end = fmt.indexOf('(', ptr);
|
||||||
|
if (end < 0)
|
||||||
|
throw 'no function name after the $ here: ' + fmt.slice(0, ptr);
|
||||||
|
|
||||||
|
var fun = fmt.slice(ptr - 1, end);
|
||||||
|
ptr = end + 1;
|
||||||
|
|
||||||
|
if (fun == "lpad") {
|
||||||
|
var str = dive(',')[1];
|
||||||
|
var len = dive(',')[1];
|
||||||
|
var chr = dive()[1];
|
||||||
|
if (!len || !chr)
|
||||||
|
throw 'invalid arguments to ' + fun;
|
||||||
|
|
||||||
|
if (!str.length)
|
||||||
|
ng += 1;
|
||||||
|
|
||||||
|
while (str.length < len)
|
||||||
|
str = chr + str;
|
||||||
|
|
||||||
|
ret += str;
|
||||||
|
}
|
||||||
|
else if (fun == "rpad") {
|
||||||
|
var str = dive(',')[1];
|
||||||
|
var len = dive(',')[1];
|
||||||
|
var chr = dive()[1];
|
||||||
|
if (!len || !chr)
|
||||||
|
throw 'invalid arguments to ' + fun;
|
||||||
|
|
||||||
|
if (!str.length)
|
||||||
|
ng += 1;
|
||||||
|
|
||||||
|
while (str.length < len)
|
||||||
|
str += chr;
|
||||||
|
|
||||||
|
ret += str;
|
||||||
|
}
|
||||||
|
else throw 'function not implemented: "' + fun + '"';
|
||||||
|
}
|
||||||
|
else ret += ch;
|
||||||
|
}
|
||||||
|
return [ng, ret];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return [true, dive()[1]];
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return [false, ex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var fileman = (function () {
|
var fileman = (function () {
|
||||||
var bren = ebi('fren'),
|
var bren = ebi('fren'),
|
||||||
bdel = ebi('fdel'),
|
bdel = ebi('fdel'),
|
||||||
@@ -1493,16 +1592,47 @@ var fileman = (function () {
|
|||||||
return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder');
|
return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder');
|
||||||
|
|
||||||
var sel = msel.getsel();
|
var sel = msel.getsel();
|
||||||
if (sel.length !== 1)
|
if (!sel.length)
|
||||||
return toast.err(3, 'select exactly 1 item to rename');
|
return toast.err(3, 'select at least one item to rename');
|
||||||
|
|
||||||
var src = sel[0].vp;
|
var f = [],
|
||||||
if (src.endsWith('/'))
|
base = vsplit(sel[0].vp)[0],
|
||||||
src = src.slice(0, -1);
|
mkeys;
|
||||||
|
|
||||||
var vsp = vsplit(src),
|
for (var a = 0; a < sel.length; a++) {
|
||||||
base = vsp[0],
|
var vp = sel[a].vp;
|
||||||
ofn = uricom_dec(vsp[1])[0];
|
if (vp.endsWith('/'))
|
||||||
|
vp = vp.slice(0, -1);
|
||||||
|
|
||||||
|
var vsp = vsplit(vp);
|
||||||
|
if (base != vsp[0])
|
||||||
|
return toast.err(0, 'bug:\n' + base + '\n' + vsp[0]);
|
||||||
|
|
||||||
|
var vars = ft2dict(ebi(sel[a].id).closest('tr'));
|
||||||
|
mkeys = vars[1].concat(vars[2]);
|
||||||
|
|
||||||
|
var md = vars[0];
|
||||||
|
for (var k in md) {
|
||||||
|
if (!md.hasOwnProperty(k))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
md[k.toLowerCase()] = md[k];
|
||||||
|
k = k.toLowerCase();
|
||||||
|
|
||||||
|
if (k.startsWith('.'))
|
||||||
|
md[k.slice(1)] = md[k];
|
||||||
|
}
|
||||||
|
md.t = md.ext;
|
||||||
|
md.date = md.ts;
|
||||||
|
md.size = md.sz;
|
||||||
|
|
||||||
|
f.push({
|
||||||
|
"src": vp,
|
||||||
|
"ofn": uricom_dec(vsp[1])[0],
|
||||||
|
"md": vars[0],
|
||||||
|
"ok": true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var rui = ebi('rui');
|
var rui = ebi('rui');
|
||||||
if (!rui) {
|
if (!rui) {
|
||||||
@@ -1510,62 +1640,221 @@ var fileman = (function () {
|
|||||||
rui.setAttribute('id', 'rui');
|
rui.setAttribute('id', 'rui');
|
||||||
document.body.appendChild(rui);
|
document.body.appendChild(rui);
|
||||||
}
|
}
|
||||||
var html = [
|
|
||||||
'<h1>rename file</h1>',
|
var html = sel.length > 1 ? ['<div>'] : [
|
||||||
'<div><table>',
|
|
||||||
'<tr><td>old:</td><td><input type="text" id="rn_old" readonly /></td></tr>',
|
|
||||||
'<tr><td>new:</td><td><input type="text" id="rn_new" /></td></tr>',
|
|
||||||
'</table></div>',
|
|
||||||
'<div>',
|
'<div>',
|
||||||
'<button id="rn_dec">url-decode</button>',
|
'<button class="rn_dec" n="0" tt="may fix some cases of broken filenames">url-decode</button>',
|
||||||
'|',
|
'//',
|
||||||
'<button id="rn_reset">↺ reset</button>',
|
'<button class="rn_reset" n="0" tt="reset modified filenames back to the original ones">↺ reset</button>'
|
||||||
'<button id="rn_cancel">❌ cancel</button>',
|
|
||||||
'<button id="rn_apply">✅ apply rename</button>',
|
|
||||||
'</div>',
|
|
||||||
'<div><table>'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var vars = ft2dict(ebi(sel[0].id).closest('tr')),
|
html = html.concat([
|
||||||
keys = vars[1].concat(vars[2]);
|
'<button id="rn_cancel" tt="abort and close this window">❌ cancel</button>',
|
||||||
|
'<button id="rn_apply">✅ apply rename</button>',
|
||||||
vars = vars[0];
|
'<a id="rn_adv" class="tgl btn" href="#" tt="batch / metadata / pattern renaming">advanced</a>',
|
||||||
for (var a = 0; a < keys.length; a++)
|
'<a id="rn_case" class="tgl btn" href="#" tt="case-sensitive regex">case</a>',
|
||||||
html.push('<tr><td>' + esc(keys[a]) + '</td><td><input type="text" readonly value="' + esc(vars[keys[a]]) + '" /></td></tr>');
|
'</div>',
|
||||||
|
'<div id="rn_vadv"><table>',
|
||||||
|
'<tr><td>regex</td><td><input type="text" id="rn_re" tt="regex search pattern to apply to original filenames; capturing groups can be referenced in the format field below like <code>(1)</code> and <code>(2)</code> and so on" /></td></tr>',
|
||||||
|
'<tr><td>format</td><td><input type="text" id="rn_fmt" tt="inspired by foobar2000:$N<code>(title)</code> is replaced by song title,$N<code>[(artist) - ](title)</code> skips the first part if artist is blank$N<code>$lpad((tn),2,0)</code> pads tracknumber to 2 digits" /></td></tr>',
|
||||||
|
'<tr><td>preset</td><td><select id="rn_pre"></select>',
|
||||||
|
'<button id="rn_pdel">❌ delete</button>',
|
||||||
|
'<button id="rn_pnew">💾 save as</button>',
|
||||||
|
'</td></tr>',
|
||||||
|
'</table></div>'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (sel.length == 1)
|
||||||
|
html.push(
|
||||||
|
'<div><table id="rn_f">\n' +
|
||||||
|
'<tr><td>old:</td><td><input type="text" id="rn_old" n="0" readonly /></td></tr>\n' +
|
||||||
|
'<tr><td>new:</td><td><input type="text" id="rn_new" n="0" /></td></tr>');
|
||||||
|
else {
|
||||||
|
html.push(
|
||||||
|
'<div><table id="rn_f" class="m">' +
|
||||||
|
'<tr><td></td><td>new name</td><td>old name</td></tr>');
|
||||||
|
for (var a = 0; a < f.length; a++)
|
||||||
|
html.push(
|
||||||
|
'<tr><td>' +
|
||||||
|
'<button class="rn_dec" n="' + a + '">decode</button>',
|
||||||
|
'<button class="rn_reset" n="' + a + '">↺ reset</button></td>',
|
||||||
|
'<td><input type="text" id="rn_new" n="' + a + '" /></td>' +
|
||||||
|
'<td><input type="text" id="rn_old" n="' + a + '" readonly /></td></tr>');
|
||||||
|
}
|
||||||
html.push('</table></div>');
|
html.push('</table></div>');
|
||||||
rui.innerHTML = html.join('\n');
|
|
||||||
var iold = ebi('rn_old'),
|
|
||||||
inew = ebi('rn_new');
|
|
||||||
|
|
||||||
function rn_reset() {
|
if (sel.length == 1) {
|
||||||
inew.value = iold.value;
|
html.push('<div><p style="margin:.6em 0">tags for the selected file (read-only, just for reference):</p><table>');
|
||||||
inew.focus();
|
for (var a = 0; a < mkeys.length; a++)
|
||||||
inew.setSelectionRange(0, inew.value.lastIndexOf('.'), "forward");
|
html.push('<tr><td>' + esc(mkeys[a]) + '</td><td><input type="text" readonly value="' + esc(f[0].md[mkeys[a]]) + '" /></td></tr>');
|
||||||
|
|
||||||
|
html.push('</table></div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
rui.innerHTML = html.join('\n');
|
||||||
|
for (var a = 0; a < f.length; a++) {
|
||||||
|
var k = '[n="' + a + '"]';
|
||||||
|
f[a].iold = QS('#rn_old' + k);
|
||||||
|
f[a].inew = QS('#rn_new' + k);
|
||||||
|
f[a].inew.value = f[a].iold.value = f[a].ofn;
|
||||||
|
|
||||||
|
(function (a) {
|
||||||
|
f[a].inew.onkeydown = function (e) {
|
||||||
|
rn_ok(a, true);
|
||||||
|
|
||||||
|
if (e.key == 'Escape')
|
||||||
|
return rn_cancel();
|
||||||
|
|
||||||
|
if (e.key == 'Enter')
|
||||||
|
return rn_apply();
|
||||||
|
};
|
||||||
|
QS('.rn_dec' + k).onclick = function () {
|
||||||
|
f[a].inew.value = uricom_dec(f[a].inew.value)[0];
|
||||||
|
};
|
||||||
|
QS('.rn_reset' + k).onclick = function () {
|
||||||
|
rn_reset(a);
|
||||||
|
};
|
||||||
|
})(a);
|
||||||
|
}
|
||||||
|
rn_reset(0);
|
||||||
|
tt.att(rui);
|
||||||
|
|
||||||
|
var adv = bcfg_get('rn_adv', false),
|
||||||
|
cs = bcfg_get('rn_case', false);
|
||||||
|
|
||||||
|
function sadv() {
|
||||||
|
ebi('rn_vadv').style.display = ebi('rn_case').style.display = adv ? '' : 'none';
|
||||||
|
}
|
||||||
|
sadv();
|
||||||
|
|
||||||
|
function rn_ok(n, ok) {
|
||||||
|
f[n].ok = ok;
|
||||||
|
clmod(f[n].inew.closest('tr'), 'err', !ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rn_reset(n) {
|
||||||
|
f[n].inew.value = f[n].iold.value = f[n].ofn;
|
||||||
|
f[n].inew.focus();
|
||||||
|
f[n].inew.setSelectionRange(0, f[n].inew.value.lastIndexOf('.'), "forward");
|
||||||
}
|
}
|
||||||
function rn_cancel() {
|
function rn_cancel() {
|
||||||
rui.parentNode.removeChild(rui);
|
rui.parentNode.removeChild(rui);
|
||||||
}
|
}
|
||||||
|
|
||||||
inew.onkeydown = function (e) {
|
ebi('rn_cancel').onclick = rn_cancel;
|
||||||
|
ebi('rn_apply').onclick = rn_apply;
|
||||||
|
ebi('rn_adv').onclick = function () {
|
||||||
|
adv = !adv;
|
||||||
|
bcfg_set('rn_adv', adv);
|
||||||
|
sadv();
|
||||||
|
};
|
||||||
|
ebi('rn_case').onclick = function () {
|
||||||
|
cs = !cs;
|
||||||
|
bcfg_set('rn_case', cs);
|
||||||
|
};
|
||||||
|
|
||||||
|
var ire = ebi('rn_re'),
|
||||||
|
ifmt = ebi('rn_fmt'),
|
||||||
|
ipre = ebi('rn_pre'),
|
||||||
|
idel = ebi('rn_pdel'),
|
||||||
|
inew = ebi('rn_pnew'),
|
||||||
|
defp = '$lpad((tn),2,0). [(artist) - ](title).(ext)';
|
||||||
|
|
||||||
|
var presets = {};
|
||||||
|
presets[defp] = ['', defp];
|
||||||
|
presets = jread("rn_pre", presets);
|
||||||
|
|
||||||
|
function spresets() {
|
||||||
|
var keys = Object.keys(presets), o;
|
||||||
|
keys.sort();
|
||||||
|
ipre.innerHTML = '<option value=""></option>';
|
||||||
|
for (var a = 0; a < keys.length; a++) {
|
||||||
|
o = mknod('option');
|
||||||
|
o.setAttribute('value', keys[a]);
|
||||||
|
o.textContent = keys[a];
|
||||||
|
ipre.appendChild(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inew.onclick = function () {
|
||||||
|
var name = prompt('provide a name for your new preset', ifmt.value);
|
||||||
|
if (!name)
|
||||||
|
return toast.warn(3, 'aborted');
|
||||||
|
|
||||||
|
presets[name] = [ire.value, ifmt.value];
|
||||||
|
jwrite('rn_pre', presets);
|
||||||
|
spresets();
|
||||||
|
ipre.value = name;
|
||||||
|
};
|
||||||
|
idel.onclick = function () {
|
||||||
|
delete presets[ipre.value];
|
||||||
|
jwrite('rn_pre', presets);
|
||||||
|
spresets();
|
||||||
|
};
|
||||||
|
ipre.oninput = function () {
|
||||||
|
var cfg = presets[ipre.value];
|
||||||
|
if (cfg) {
|
||||||
|
ire.value = cfg[0];
|
||||||
|
ifmt.value = cfg[1];
|
||||||
|
}
|
||||||
|
ifmt.oninput();
|
||||||
|
};
|
||||||
|
spresets();
|
||||||
|
|
||||||
|
ire.onkeydown = ifmt.onkeydown = function (e) {
|
||||||
if (e.key == 'Escape')
|
if (e.key == 'Escape')
|
||||||
return rn_cancel();
|
return rn_cancel();
|
||||||
|
|
||||||
if (e.key == 'Enter')
|
if (e.key == 'Enter')
|
||||||
return rn_apply();
|
return rn_apply();
|
||||||
};
|
};
|
||||||
ebi('rn_cancel').onclick = rn_cancel;
|
|
||||||
ebi('rn_reset').onclick = rn_reset;
|
ire.oninput = ifmt.oninput = function (e) {
|
||||||
ebi('rn_apply').onclick = rn_apply;
|
var ptn = ire.value,
|
||||||
ebi('rn_dec').onclick = function () {
|
fmt = ifmt.value,
|
||||||
inew.value = uricom_dec(inew.value)[0];
|
re = null;
|
||||||
|
|
||||||
|
if (!fmt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (ptn)
|
||||||
|
re = new RegExp(ptn, cs ? 'i' : '');
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return toast.err(5, 'invalid regex:\n' + ex);
|
||||||
|
}
|
||||||
|
toast.hide();
|
||||||
|
|
||||||
|
for (var a = 0; a < f.length; a++) {
|
||||||
|
var m = re ? re.exec(f[a].ofn) : null,
|
||||||
|
ok, txt = '';
|
||||||
|
|
||||||
|
if (re && !m) {
|
||||||
|
txt = 'regex did not match';
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var ret = fmt_ren(m, f[a].md, fmt);
|
||||||
|
ok = ret[0];
|
||||||
|
txt = ret[1];
|
||||||
|
}
|
||||||
|
rn_ok(a, ok);
|
||||||
|
f[a].inew.value = (ok ? '' : 'ERROR: ') + txt;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
iold.value = ofn;
|
|
||||||
rn_reset();
|
|
||||||
|
|
||||||
function rn_apply() {
|
function rn_apply() {
|
||||||
var dst = base + uricom_enc(inew.value, false);
|
while (f.length && (!f[0].ok || f[0].ofn == f[0].inew.value))
|
||||||
|
f.shift();
|
||||||
|
|
||||||
|
if (!f.length) {
|
||||||
|
toast.ok(2, 'rename OK');
|
||||||
|
treectl.goto(get_evpath());
|
||||||
|
return rn_cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.inf(0, 'renaming ' + f.length + ' items\n\n' + f[0].ofn);
|
||||||
|
var dst = base + uricom_enc(f[0].inew.value, false);
|
||||||
|
|
||||||
function rename_cb() {
|
function rename_cb() {
|
||||||
if (this.readyState != XMLHttpRequest.DONE)
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
@@ -1576,15 +1865,16 @@ var fileman = (function () {
|
|||||||
toast.err(9, 'rename failed:\n' + msg);
|
toast.err(9, 'rename failed:\n' + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.ok(2, 'rename OK');
|
|
||||||
treectl.goto(get_evpath());
|
f.shift().inew.value = '( OK )';
|
||||||
rn_cancel();
|
return rn_apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', src + '?move=' + dst, true);
|
xhr.open('GET', f[0].src + '?move=' + dst, true);
|
||||||
xhr.onreadystatechange = rename_cb;
|
xhr.onreadystatechange = rename_cb;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
};
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
r.delete = function (e) {
|
r.delete = function (e) {
|
||||||
@@ -3029,6 +3319,8 @@ var filecols = (function () {
|
|||||||
"q": "quality / bitrate",
|
"q": "quality / bitrate",
|
||||||
"Ac": "audio codec",
|
"Ac": "audio codec",
|
||||||
"Vc": "video codec",
|
"Vc": "video codec",
|
||||||
|
"Ahash": "audio checksum",
|
||||||
|
"Vhash": "video checksum",
|
||||||
"Res": "resolution",
|
"Res": "resolution",
|
||||||
"T": "filetype",
|
"T": "filetype",
|
||||||
"aq": "audio quality / bitrate",
|
"aq": "audio quality / bitrate",
|
||||||
@@ -3040,6 +3332,21 @@ var filecols = (function () {
|
|||||||
"hz": "sample rate"
|
"hz": "sample rate"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (JSON.stringify(def_hcols) != sread('hfilecols')) {
|
||||||
|
console.log("applying default hidden-cols");
|
||||||
|
jwrite('hfilecols', def_hcols);
|
||||||
|
for (var a = 0; a < def_hcols.length; a++) {
|
||||||
|
var t = def_hcols[a];
|
||||||
|
t = t.slice(0, 1).toUpperCase() + t.slice(1);
|
||||||
|
if (t.startsWith("."))
|
||||||
|
t = t.slice(1);
|
||||||
|
|
||||||
|
if (hidden.indexOf(t) == -1)
|
||||||
|
hidden.push(t);
|
||||||
|
}
|
||||||
|
jwrite("filecols", hidden);
|
||||||
|
}
|
||||||
|
|
||||||
var add_btns = function () {
|
var add_btns = function () {
|
||||||
var ths = QSA('#files th>span');
|
var ths = QSA('#files th>span');
|
||||||
for (var a = 0, aa = ths.length; a < aa; a++) {
|
for (var a = 0, aa = ths.length; a < aa; a++) {
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
padding-bottom: 5em;
|
|
||||||
}
|
|
||||||
#box {
|
#box {
|
||||||
padding: .5em 1em;
|
padding: .5em 1em;
|
||||||
background: #2c2c2c;
|
background: #2c2c2c;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
set -e
|
set -e
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
help() { exec cat <<'EOF'
|
||||||
|
|
||||||
# optional args:
|
# optional args:
|
||||||
#
|
#
|
||||||
@@ -26,6 +27,8 @@ echo
|
|||||||
#
|
#
|
||||||
# `no-dd` saves ~2k by removing the mouse cursor
|
# `no-dd` saves ~2k by removing the mouse cursor
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
# port install gnutar findutils gsed coreutils
|
# port install gnutar findutils gsed coreutils
|
||||||
gtar=$(command -v gtar || command -v gnutar) || true
|
gtar=$(command -v gtar || command -v gnutar) || true
|
||||||
@@ -73,6 +76,7 @@ while [ ! -z "$1" ]; do
|
|||||||
no-cm) no_cm=1 ; ;;
|
no-cm) no_cm=1 ; ;;
|
||||||
no-sh) do_sh= ; ;;
|
no-sh) do_sh= ; ;;
|
||||||
no-py) do_py= ; ;;
|
no-py) do_py= ; ;;
|
||||||
|
*) help ; ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
@@ -204,19 +208,24 @@ done
|
|||||||
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
||||||
echo h > copyparty/web/mde.html
|
echo h > copyparty/web/mde.html
|
||||||
f=copyparty/web/md.html
|
f=copyparty/web/md.html
|
||||||
sed -r '/edit2">edit \(fancy/d' <$f >t && tmv "$f"
|
sed -r '/edit2">edit \(fancy/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $no_fnt ] && {
|
[ $no_fnt ] && {
|
||||||
rm -f copyparty/web/deps/scp.woff2
|
rm -f copyparty/web/deps/scp.woff2
|
||||||
f=copyparty/web/md.css
|
f=copyparty/web/md.css
|
||||||
sed -r '/scp\.woff2/d' <$f >t && tmv "$f"
|
gzip -d "$f"
|
||||||
|
sed -r '/scp\.woff2/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $no_dd ] && {
|
[ $no_dd ] && {
|
||||||
rm -rf copyparty/web/dd
|
rm -rf copyparty/web/dd
|
||||||
f=copyparty/web/browser.css
|
f=copyparty/web/browser.css
|
||||||
sed -r 's/(cursor: )url\([^)]+\), (pointer)/\1\2/; /[0-9]+% \{cursor:/d; /animation: cursor/d' <$f >t && tmv "$f"
|
gzip -d "$f"
|
||||||
|
sed -r 's/(cursor: )url\([^)]+\), (pointer)/\1\2/; /[0-9]+% \{cursor:/d; /animation: cursor/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $repack ] ||
|
[ $repack ] ||
|
||||||
@@ -251,7 +260,7 @@ done
|
|||||||
|
|
||||||
gzres() {
|
gzres() {
|
||||||
command -v pigz &&
|
command -v pigz &&
|
||||||
pk='pigz -11 -J 34 -I 256' ||
|
pk='pigz -11 -I 256' ||
|
||||||
pk='gzip'
|
pk='gzip'
|
||||||
|
|
||||||
echo "$pk"
|
echo "$pk"
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ def tc1():
|
|||||||
|
|
||||||
arg = "{}:{}:{}".format(pd, ud, p, hp)
|
arg = "{}:{}:{}".format(pd, ud, p, hp)
|
||||||
if hp:
|
if hp:
|
||||||
arg += ":chist=" + hp
|
arg += ":c,hist=" + hp
|
||||||
|
|
||||||
args += ["-v", arg]
|
args += ["-v", arg]
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class Cfg(Namespace):
|
|||||||
nih=True,
|
nih=True,
|
||||||
mtp=[],
|
mtp=[],
|
||||||
mte="a",
|
mte="a",
|
||||||
|
mth="",
|
||||||
hist=None,
|
hist=None,
|
||||||
no_hash=False,
|
no_hash=False,
|
||||||
css_browser=None,
|
css_browser=None,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class Cfg(Namespace):
|
|||||||
ex2 = {
|
ex2 = {
|
||||||
"mtp": [],
|
"mtp": [],
|
||||||
"mte": "a",
|
"mte": "a",
|
||||||
|
"mth": "",
|
||||||
"hist": None,
|
"hist": None,
|
||||||
"no_hash": False,
|
"no_hash": False,
|
||||||
"css_browser": None,
|
"css_browser": None,
|
||||||
|
|||||||
Reference in New Issue
Block a user