mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 17:12:13 -06:00
tar/zip-download: add opus transcoding filter
This commit is contained in:
parent
48a3898aa6
commit
1b7634932d
|
@ -519,6 +519,10 @@ you can also zip a selection of files or folders by clicking them in the browser
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
cool trick: download a folder by appending url-params `?tar&opus` to transcode all audio files (except aac|m4a|mp3|ogg|opus|wma) to opus before they're added to the archive
|
||||||
|
* super useful if you're 5 minutes away from takeoff and realize you don't have any music on your phone but your server only has flac files and downloading those will burn through all your data + there wouldn't be enough time anyways
|
||||||
|
* and url-params `&j` / `&w` produce jpeg/webm thumbnails/spectrograms instead of the original audio/video/images
|
||||||
|
|
||||||
|
|
||||||
## uploading
|
## uploading
|
||||||
|
|
||||||
|
@ -701,7 +705,7 @@ open the `[🎺]` media-player-settings tab to configure it,
|
||||||
* `[loop]` keeps looping the folder
|
* `[loop]` keeps looping the folder
|
||||||
* `[next]` plays into the next folder
|
* `[next]` plays into the next folder
|
||||||
* transcode:
|
* transcode:
|
||||||
* `[flac]` convers `flac` and `wav` files into opus
|
* `[flac]` converts `flac` and `wav` files into opus
|
||||||
* `[aac]` converts `aac` and `m4a` files into opus
|
* `[aac]` converts `aac` and `m4a` files into opus
|
||||||
* `[oth]` converts all other known formats into opus
|
* `[oth]` converts all other known formats into opus
|
||||||
* `aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|m4a|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|tak|tta|ulaw|wav|wma|wv|xm|xpk`
|
* `aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|m4a|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|tak|tta|ulaw|wav|wma|wv|xm|xpk`
|
||||||
|
|
|
@ -1041,6 +1041,7 @@ def add_thumbnail(ap):
|
||||||
def add_transcoding(ap):
|
def add_transcoding(ap):
|
||||||
ap2 = ap.add_argument_group('transcoding options')
|
ap2 = ap.add_argument_group('transcoding options')
|
||||||
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
ap2.add_argument("--no-acode", action="store_true", help="disable audio transcoding")
|
||||||
|
ap2.add_argument("--no-bacode", action="store_true", help="disable batch audio transcoding by folder download (zip/tar)")
|
||||||
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after SEC seconds")
|
ap2.add_argument("--ac-maxage", metavar="SEC", type=int, default=86400, help="delete cached transcode output after SEC seconds")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ from .__version__ import S_VERSION
|
||||||
from .authsrv import VFS # typechk
|
from .authsrv import VFS # typechk
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
from .star import StreamTar
|
from .star import StreamTar
|
||||||
from .sutil import StreamArc # typechk
|
from .sutil import StreamArc, gfilter
|
||||||
from .szip import StreamZip
|
from .szip import StreamZip
|
||||||
from .util import (
|
from .util import (
|
||||||
HTTPCODE,
|
HTTPCODE,
|
||||||
|
@ -2896,6 +2896,16 @@ class HttpCli(object):
|
||||||
vpath, rem, set(items), self.uname, dots, False, not self.args.no_scandir
|
vpath, rem, set(items), self.uname, dots, False, not self.args.no_scandir
|
||||||
)
|
)
|
||||||
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
||||||
|
cfmt = ""
|
||||||
|
if self.thumbcli and not self.args.no_bacode:
|
||||||
|
for zs in ("opus", "w", "j"):
|
||||||
|
if zs in self.ouparam or uarg == zs:
|
||||||
|
cfmt = zs
|
||||||
|
|
||||||
|
if cfmt:
|
||||||
|
self.log("transcoding to [{}]".format(cfmt))
|
||||||
|
fgen = gfilter(fgen, self.thumbcli, self.uname, vpath, cfmt)
|
||||||
|
|
||||||
bgen = packer(self.log, fgen, utf8="utf" in uarg, pre_crc="crc" in uarg)
|
bgen = packer(self.log, fgen, utf8="utf" in uarg, pre_crc="crc" in uarg)
|
||||||
bsent = 0
|
bsent = 0
|
||||||
for buf in bgen.gen():
|
for buf in bgen.gen():
|
||||||
|
@ -2907,6 +2917,7 @@ class HttpCli(object):
|
||||||
bsent += len(buf)
|
bsent += len(buf)
|
||||||
except:
|
except:
|
||||||
logmsg += " \033[31m" + unicode(bsent) + "\033[0m"
|
logmsg += " \033[31m" + unicode(bsent) + "\033[0m"
|
||||||
|
bgen.stop()
|
||||||
break
|
break
|
||||||
|
|
||||||
spd = self._spd(bsent)
|
spd = self._spd(bsent)
|
||||||
|
|
|
@ -61,6 +61,7 @@ class StreamTar(StreamArc):
|
||||||
Daemon(self._gen, "star-gen")
|
Daemon(self._gen, "star-gen")
|
||||||
|
|
||||||
def gen(self) -> Generator[Optional[bytes], None, None]:
|
def gen(self) -> Generator[Optional[bytes], None, None]:
|
||||||
|
buf = b""
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
buf = self.qfile.q.get()
|
buf = self.qfile.q.get()
|
||||||
|
@ -72,6 +73,12 @@ class StreamTar(StreamArc):
|
||||||
|
|
||||||
yield None
|
yield None
|
||||||
finally:
|
finally:
|
||||||
|
while buf:
|
||||||
|
try:
|
||||||
|
buf = self.qfile.q.get()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
if self.errf:
|
if self.errf:
|
||||||
bos.unlink(self.errf["ap"])
|
bos.unlink(self.errf["ap"])
|
||||||
|
|
||||||
|
@ -101,6 +108,9 @@ class StreamTar(StreamArc):
|
||||||
errors.append((f["vp"], f["err"]))
|
errors.append((f["vp"], f["err"]))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if self.stopped:
|
||||||
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.ser(f)
|
self.ser(f)
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from .__init__ import CORES
|
||||||
from .bos import bos
|
from .bos import bos
|
||||||
|
from .th_cli import ThumbCli
|
||||||
|
from .util import vjoin
|
||||||
|
|
||||||
if True: # pylint: disable=using-constant-test
|
if True: # pylint: disable=using-constant-test
|
||||||
from typing import Any, Generator, Optional
|
from typing import Any, Generator, Optional
|
||||||
|
@ -21,10 +25,78 @@ class StreamArc(object):
|
||||||
):
|
):
|
||||||
self.log = log
|
self.log = log
|
||||||
self.fgen = fgen
|
self.fgen = fgen
|
||||||
|
self.stopped = False
|
||||||
|
|
||||||
def gen(self) -> Generator[Optional[bytes], None, None]:
|
def gen(self) -> Generator[Optional[bytes], None, None]:
|
||||||
raise Exception("override me")
|
raise Exception("override me")
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
self.stopped = True
|
||||||
|
|
||||||
|
|
||||||
|
def gfilter(
|
||||||
|
fgen: Generator[dict[str, Any], None, None],
|
||||||
|
thumbcli: ThumbCli,
|
||||||
|
uname: str,
|
||||||
|
vtop: str,
|
||||||
|
fmt: str,
|
||||||
|
) -> Generator[dict[str, Any], None, None]:
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
pend = []
|
||||||
|
with ThreadPoolExecutor(max_workers=CORES) as tp:
|
||||||
|
try:
|
||||||
|
for f in fgen:
|
||||||
|
task = tp.submit(enthumb, thumbcli, uname, vtop, f, fmt)
|
||||||
|
pend.append((task, f))
|
||||||
|
if pend[0][0].done() or len(pend) > CORES * 4:
|
||||||
|
task, f = pend.pop(0)
|
||||||
|
try:
|
||||||
|
f = task.result(600)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
yield f
|
||||||
|
|
||||||
|
for task, f in pend:
|
||||||
|
try:
|
||||||
|
f = task.result(600)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
yield f
|
||||||
|
except Exception as ex:
|
||||||
|
thumbcli.log("gfilter flushing ({})".format(ex))
|
||||||
|
for task, f in pend:
|
||||||
|
try:
|
||||||
|
task.result(600)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
thumbcli.log("gfilter flushed")
|
||||||
|
|
||||||
|
|
||||||
|
def enthumb(
|
||||||
|
thumbcli: ThumbCli, uname: str, vtop: str, f: dict[str, Any], fmt: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
rem = f["vp"]
|
||||||
|
ext = rem.rsplit(".", 1)[-1].lower()
|
||||||
|
if fmt == "opus" and ext in "aac|m4a|mp3|ogg|opus|wma".split("|"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
vp = vjoin(vtop, rem.split("/", 1)[1])
|
||||||
|
vn, rem = thumbcli.asrv.vfs.get(vp, uname, True, False)
|
||||||
|
dbv, vrem = vn.get_dbv(rem)
|
||||||
|
thp = thumbcli.get(dbv, vrem, f["st"].st_mtime, fmt)
|
||||||
|
if not thp:
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
ext = "jpg" if fmt == "j" else "webp" if fmt == "w" else fmt
|
||||||
|
sz = bos.path.getsize(thp)
|
||||||
|
st: os.stat_result = f["st"]
|
||||||
|
ts = st.st_mtime
|
||||||
|
f["ap"] = thp
|
||||||
|
f["vp"] = f["vp"].rsplit(".", 1)[0] + "." + ext
|
||||||
|
f["st"] = os.stat_result((st.st_mode, -1, -1, 1, 1000, 1000, sz, ts, ts, ts))
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
def errdesc(errors: list[tuple[str, str]]) -> tuple[dict[str, Any], list[str]]:
|
def errdesc(errors: list[tuple[str, str]]) -> tuple[dict[str, Any], list[str]]:
|
||||||
report = ["copyparty failed to add the following files to the archive:", ""]
|
report = ["copyparty failed to add the following files to the archive:", ""]
|
||||||
|
|
|
@ -108,6 +108,7 @@ class ThumbCli(object):
|
||||||
if st.st_size:
|
if st.st_size:
|
||||||
ret = tpath = tp
|
ret = tpath = tp
|
||||||
fmt = ret.rsplit(".")[1]
|
fmt = ret.rsplit(".")[1]
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
abort = True
|
abort = True
|
||||||
except:
|
except:
|
||||||
|
|
Loading…
Reference in a new issue