ui option to play video as audio

audio extraction happens serverside to opus or mp3
depending on browser support

remuxing (extracting audio without transcoding)
is currently not supported, and is not planned
This commit is contained in:
ed 2024-07-22 22:30:21 +00:00
parent eeef80919f
commit 53f1e3c91d
4 changed files with 26 additions and 13 deletions

View file

@ -225,6 +225,7 @@ also see [comparison to similar software](./docs/versus.md)
* ☑ [navpane](#navpane) (directory tree sidebar) * ☑ [navpane](#navpane) (directory tree sidebar)
* ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename)) * ☑ file manager (cut/paste, delete, [batch-rename](#batch-rename))
* ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding) * ☑ audio player (with [OS media controls](https://user-images.githubusercontent.com/241032/215347492-b4250797-6c90-4e09-9a4c-721edf2fb15c.png) and opus/mp3 transcoding)
* ☑ play video files as audio (converted on server)
* ☑ image gallery with webm player * ☑ image gallery with webm player
* ☑ textfile browser with syntax hilighting * ☑ textfile browser with syntax hilighting
* ☑ [thumbnails](#thumbnails) * ☑ [thumbnails](#thumbnails)
@ -801,6 +802,7 @@ some hilights:
* OS integration; control playback from your phone's lockscreen ([windows](https://user-images.githubusercontent.com/241032/233213022-298a98ba-721a-4cf1-a3d4-f62634bc53d5.png) // [iOS](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) // [android](https://user-images.githubusercontent.com/241032/233212311-a7368590-08c7-4f9f-a1af-48ccf3f36fad.png)) * OS integration; control playback from your phone's lockscreen ([windows](https://user-images.githubusercontent.com/241032/233213022-298a98ba-721a-4cf1-a3d4-f62634bc53d5.png) // [iOS](https://user-images.githubusercontent.com/241032/142711926-0700be6c-3e31-47b3-9928-53722221f722.png) // [android](https://user-images.githubusercontent.com/241032/233212311-a7368590-08c7-4f9f-a1af-48ccf3f36fad.png))
* shows the audio waveform in the seekbar * shows the audio waveform in the seekbar
* not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended * not perfectly gapless but can get really close (see settings + eq below); good enough to enjoy gapless albums as intended
* videos can be played as audio, without wasting bandwidth on the video
click the `play` link next to an audio file, or copy the link target to [share it](https://a.ocv.me/pub/demo/music/Ubiktune%20-%20SOUNDSHOCK%202%20-%20FM%20FUNK%20TERRROR!!/#af-1fbfba61&t=18) (optionally with a timestamp to start playing from, like that example does) click the `play` link next to an audio file, or copy the link target to [share it](https://a.ocv.me/pub/demo/music/Ubiktune%20-%20SOUNDSHOCK%202%20-%20FM%20FUNK%20TERRROR!!/#af-1fbfba61&t=18) (optionally with a timestamp to start playing from, like that example does)

View file

@ -59,7 +59,8 @@ class ThumbCli(object):
want_opus = fmt in ("opus", "caf", "mp3") want_opus = fmt in ("opus", "caf", "mp3")
is_au = ext in self.fmt_ffa is_au = ext in self.fmt_ffa
if is_au: is_vau = want_opus and ext in self.fmt_ffv
if is_au or is_vau:
if want_opus: if want_opus:
if self.args.no_acode: if self.args.no_acode:
return None return None
@ -107,7 +108,7 @@ class ThumbCli(object):
fmt = sfmt fmt = sfmt
elif fmt[:1] == "p" and not is_au: elif fmt[:1] == "p" and not is_au and not is_vid:
t = "cannot thumbnail [%s]: png only allowed for waveforms" t = "cannot thumbnail [%s]: png only allowed for waveforms"
self.log(t % (rem), 6) self.log(t % (rem), 6)
return None return None

View file

@ -304,23 +304,29 @@ class ThumbSrv(object):
ap_unpk = abspath ap_unpk = abspath
if not bos.path.exists(tpath): if not bos.path.exists(tpath):
want_mp3 = tpath.endswith(".mp3")
want_opus = tpath.endswith(".opus") or tpath.endswith(".caf")
want_png = tpath.endswith(".png")
want_au = want_mp3 or want_opus
for lib in self.args.th_dec: for lib in self.args.th_dec:
can_au = lib == "ff" and (ext in self.fmt_ffa or ext in self.fmt_ffv)
if lib == "pil" and ext in self.fmt_pil: if lib == "pil" and ext in self.fmt_pil:
funs.append(self.conv_pil) funs.append(self.conv_pil)
elif lib == "vips" and ext in self.fmt_vips: elif lib == "vips" and ext in self.fmt_vips:
funs.append(self.conv_vips) funs.append(self.conv_vips)
elif lib == "ff" and ext in self.fmt_ffi or ext in self.fmt_ffv: elif can_au and (want_png or want_au):
funs.append(self.conv_ffmpeg) if want_opus:
elif lib == "ff" and ext in self.fmt_ffa:
if tpath.endswith(".opus") or tpath.endswith(".caf"):
funs.append(self.conv_opus) funs.append(self.conv_opus)
elif tpath.endswith(".mp3"): elif want_mp3:
funs.append(self.conv_mp3) funs.append(self.conv_mp3)
elif tpath.endswith(".png"): elif want_png:
funs.append(self.conv_waves) funs.append(self.conv_waves)
png_ok = True png_ok = True
else: elif lib == "ff" and (ext in self.fmt_ffi or ext in self.fmt_ffv):
funs.append(self.conv_spec) funs.append(self.conv_ffmpeg)
elif lib == "ff" and ext in self.fmt_ffa and not want_au:
funs.append(self.conv_spec)
tdir, tfn = os.path.split(tpath) tdir, tfn = os.path.split(tpath)
ttpath = os.path.join(tdir, "w", tfn) ttpath = os.path.join(tdir, "w", tfn)

View file

@ -360,6 +360,7 @@ var Ls = {
"tvt_sel": "select file   ( for cut / delete / ... )$NHotkey: S\">sel", "tvt_sel": "select file   ( for cut / delete / ... )$NHotkey: S\">sel",
"tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit", "tvt_edit": "open file in text editor$NHotkey: E\">✏️ edit",
"gt_vau": "don't show videos, just play the audio\">🎧",
"gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect", "gt_msel": "enable file selection; ctrl-click a file to override$N$N<em>when active: doubleclick a file / folder to open it</em>$N$NHotkey: S\">multiselect",
"gt_crop": "center-crop thumbnails\">crop", "gt_crop": "center-crop thumbnails\">crop",
"gt_3x": "hi-res thumbnails\">3x", "gt_3x": "hi-res thumbnails\">3x",
@ -874,6 +875,7 @@ var Ls = {
"tvt_sel": "markér filen   ( for utklipp / sletting / ... )$NSnarvei: S\">merk", "tvt_sel": "markér filen   ( for utklipp / sletting / ... )$NSnarvei: S\">merk",
"tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre", "tvt_edit": "redigér filen$NSnarvei: E\">✏️ endre",
"gt_vau": "ikke vis videofiler, bare spill lyden\">🎧",
"gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N<em>når aktiv: dobbelklikk en fil / mappe for å åpne</em>$N$NSnarvei: S\">markering", "gt_msel": "markér filer istedenfor å åpne dem; ctrl-klikk filer for å overstyre$N$N<em>når aktiv: dobbelklikk en fil / mappe for å åpne</em>$N$NSnarvei: S\">markering",
"gt_crop": "beskjær ikonene så de passer bedre\">✂", "gt_crop": "beskjær ikonene så de passer bedre\">✂",
"gt_3x": "høyere oppløsning på ikoner\">3x", "gt_3x": "høyere oppløsning på ikoner\">3x",
@ -1709,7 +1711,7 @@ catch (ex) { }
var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4a|mp3|ogg|opus|wav)$/i : /\.(aac|flac|m4a|mp3|wav)$/i, var re_au_native = (can_ogg || have_acode) ? /\.(aac|flac|m4a|mp3|ogg|opus|wav)$/i : /\.(aac|flac|m4a|mp3|wav)$/i,
re_au_all = /\.(aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|itgz|itxz|itz|m4a|mdgz|mdxz|mdz|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|s3gz|s3xz|s3z|tak|tta|ulaw|wav|wma|wv|xm|xmgz|xmxz|xmz|xpk)$/i; re_au_all = /\.(aac|ac3|aif|aiff|alac|alaw|amr|ape|au|dfpwm|dts|flac|gsm|it|itgz|itxz|itz|m4a|mdgz|mdxz|mdz|mo3|mod|mp2|mp3|mpc|mptm|mt2|mulaw|ogg|okt|opus|ra|s3m|s3gz|s3xz|s3z|tak|tta|ulaw|wav|wma|wv|xm|xmgz|xmxz|xmz|xpk|3gp|asf|avi|flv|m4v|mkv|mov|mp4|mpeg|mpeg2|mpegts|mpg|mpg2|nut|ogm|ogv|rm|ts|vob|webm|wmv)$/i;
// extract songs + add play column // extract songs + add play column
@ -4410,7 +4412,7 @@ var showfile = (function () {
var td = ebi(link.id).closest('tr').getElementsByTagName('td')[0]; var td = ebi(link.id).closest('tr').getElementsByTagName('td')[0];
if (lang == 'md' && td.textContent != '-') if (lang == 'ts' || (lang == 'md' && td.textContent != '-'))
continue; continue;
td.innerHTML = '<a href="#" id="t' + td.innerHTML = '<a href="#" id="t' +
@ -4677,6 +4679,7 @@ var thegrid = (function () {
gfiles.style.display = 'none'; gfiles.style.display = 'none';
gfiles.innerHTML = ( gfiles.innerHTML = (
'<div id="ghead" class="ghead">' + '<div id="ghead" class="ghead">' +
'<a href="#" class="tgl btn" id="gridvau" tt="' + L.gt_vau + '</a> ' +
'<a href="#" class="tgl btn" id="gridsel" tt="' + L.gt_msel + '</a> ' + '<a href="#" class="tgl btn" id="gridsel" tt="' + L.gt_msel + '</a> ' +
'<a href="#" class="tgl btn" id="gridcrop" tt="' + L.gt_crop + '</a> ' + '<a href="#" class="tgl btn" id="gridcrop" tt="' + L.gt_crop + '</a> ' +
'<a href="#" class="tgl btn" id="grid3x" tt="' + L.gt_3x + '</a> ' + '<a href="#" class="tgl btn" id="grid3x" tt="' + L.gt_3x + '</a> ' +
@ -4838,7 +4841,7 @@ var thegrid = (function () {
else if (oth.hasAttribute('download')) else if (oth.hasAttribute('download'))
oth.click(); oth.click();
else if (widget.is_open && aplay) else if (aplay && (r.vau || !is_img))
aplay.click(); aplay.click();
else if (is_dir && !have_sel) else if (is_dir && !have_sel)
@ -5129,6 +5132,7 @@ var thegrid = (function () {
bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty); bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty);
bcfg_bind(r, 'ihop', 'ihop', true); bcfg_bind(r, 'ihop', 'ihop', true);
bcfg_bind(r, 'vau', 'gridvau', false);
bcfg_bind(r, 'crop', 'gridcrop', !dcrop.endsWith('n'), r.set_crop); bcfg_bind(r, 'crop', 'gridcrop', !dcrop.endsWith('n'), r.set_crop);
bcfg_bind(r, 'x3', 'grid3x', dth3x.endsWith('y'), r.set_x3); bcfg_bind(r, 'x3', 'grid3x', dth3x.endsWith('y'), r.set_x3);
bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel); bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel);