6 Commits
Author SHA1 Message Date
AZiZ 82f942e8d6 Update version badge in README.md to 0.3 2026-10-09 23:06:03 +01:00
AZiZ 6613c82e5d Update README with Blu-ray Java menu status
Added note about Blu-ray Java (BD-J) menus not supported on PS5.
2026-10-09 23:05:02 +01:00
AZiZ 2e211f289e Delete README (1).md 2026-10-09 23:03:08 +01:00
AZiZ 016a7421d9 Add files via upload 2026-10-09 23:02:53 +01:00
AZiZ 3ed7996194 update readme #1 2026-10-09 23:01:29 +01:00
AZiZ 19a422b8b8 v0.3
Updates & fixes
2026-10-09 21:34:11 +01:00
16 changed files with 1332 additions and 71 deletions

No files matched your search

+2 -1
View File
@@ -10,7 +10,7 @@
</p>
<p align="center">
<img alt="Version" src="https://img.shields.io/badge/version-0.1-f37a2b">
<img alt="Version" src="https://img.shields.io/badge/version-0.3-f37a2b">
<img alt="Platform" src="https://img.shields.io/badge/platform-PS5-1f1f1f">
<img alt="VLC" src="https://img.shields.io/badge/libvlc-3.0.24-ff8800">
<img alt="Renderer" src="https://img.shields.io/badge/renderer-Vulkan%20(RADV)-c0561b">
@@ -188,6 +188,7 @@ Optional full access. With etaHEN, OnionHEN or the Helper, VLC can lift its sand
|---|---|
| DVD folders and ISOs with menus | ✅ |
| Blu-ray folders and ISOs (unprotected) | ✅ |
| Blu-ray Java (BD-J) menus | ❌ needs Java, which the PS5 hasn't got: those discs play without menus |
| ZIP, RAR and 7z archives open like folders | ✅ |
## Building
+2 -2
View File
@@ -48,7 +48,7 @@ done
compile "$repo/third_party/imgui/backends/imgui_impl_vulkan.cpp" g++ -std=c++17
compile "$repo/third_party/volk/volk.c" gcc -std=c11
compile "$repo/src/platform_host.c" gcc -std=gnu11
for f in gfx image prefs player library network osub lang web ui main; do
for f in gfx image prefs player subconv library network osub lang web ui main; do
compile "$repo/src/$f.cc" g++ -std=c++17 -Wno-missing-field-initializers
done
gcc -O2 -c "$static/static_modules.c" -o "$obj/static_modules.o"
@@ -67,5 +67,5 @@ if [[ ${VLCPS5_OWN_IO:-0} == 1 ]]; then
objs+=("$obj/libc_missing.o")
io_flags=(-Wl,--defsym=readv=xemu_ps5_readv -Wl,--defsym=writev=xemu_ps5_writev)
fi
g++ "${san[@]}" -o "$out/$name" "${objs[@]}" @"$static/link.rsp" -lpthread -ldl "${io_flags[@]}"
g++ "${san[@]}" -o "$out/$name" "${objs[@]}" @"$static/link.rsp" "$prefix/lib/libiconv.a" -lpthread -ldl "${io_flags[@]}"
echo "built $out/$name"
@@ -0,0 +1,17 @@
diff -ru a/modules/access/dvdnav.c b/modules/access/dvdnav.c
--- a/modules/access/dvdnav.c
+++ b/modules/access/dvdnav.c
@@ -89,6 +89,13 @@
static int AccessDemuxOpen ( vlc_object_t * );
static void Close( vlc_object_t * );
+/* VLC-PS5: configure looks for dvdnav_stream_cb with the plain preprocessor,
+ * without the cross prefix's include path, so it never finds it. libdvdnav
+ * 6.1 has it: DVD ISOs on a share or a link need this demux. */
+#ifndef HAVE_STREAM_CB_IN_DVDNAV_H
+# define HAVE_STREAM_CB_IN_DVDNAV_H 1
+#endif
+
#if DVDREAD_VERSION >= 50300 && defined( HAVE_STREAM_CB_IN_DVDNAV_H )
#define HAVE_DVDNAV_DEMUX
static int DemuxOpen ( vlc_object_t * );
+35
View File
@@ -0,0 +1,35 @@
--- a/modules/access/bluray.c 2026-10-09 16:18:09.281680898 +0000
+++ b/modules/access/bluray.c 2026-10-09 16:18:09.281979298 +0000
@@ -807,6 +807,11 @@
var_SetBool( p_demux, "ts-pcr-offsetfix", false );
var_AddCallback( p_demux->p_input, "intf-event", onIntfEvent, p_demux );
+ /* VLC-PS5: whether a menu (HDMV IG or pop-up) is on screen, and whether
+ * the film has a pop-up menu, for the app: the d-pad presses its buttons
+ * only then (a menu can show in any title, not only "Top Menu"). */
+ var_Create( p_demux->p_input, "bluray-menu-open", VLC_VAR_BOOL );
+ var_Create( p_demux->p_input, "bluray-popup", VLC_VAR_BOOL );
/* Open BluRay */
#ifdef BLURAY_DEMUX
@@ -1012,6 +1017,8 @@
demux_sys_t *p_sys = p_demux->p_sys;
var_DelCallback( p_demux->p_input, "intf-event", onIntfEvent, p_demux );
+ var_Destroy( p_demux->p_input, "bluray-menu-open" );
+ var_Destroy( p_demux->p_input, "bluray-popup" );
setTitleInfo(p_sys, NULL);
@@ -2809,9 +2816,11 @@
#endif
case BD_EVENT_MENU:
p_sys->b_menu_open = e->param;
+ var_SetBool( p_demux->p_input, "bluray-menu-open", e->param != 0 );
break;
case BD_EVENT_POPUP:
p_sys->b_popup_available = e->param;
+ var_SetBool( p_demux->p_input, "bluray-popup", e->param != 0 );
/* TODO: show / hide pop-up menu button in gui ? */
break;
@@ -0,0 +1,36 @@
--- a/modules/text_renderer/freetype/freetype.c 2026-10-09 16:41:20.491780240 +0000
+++ b/modules/text_renderer/freetype/freetype.c 2026-10-09 16:41:20.492100340 +0000
@@ -1081,6 +1081,33 @@
return psz_ucs4;
}
+#elif !defined( HAVE_ICONV )
+/* VLC-PS5: no iconv on the console (ToCharset gave nothing back, so plain-text
+ * subtitles drew nothing): the UTF-8 is read here, to native-endian UCS-4 as
+ * FREETYPE_TO_UCS asks; a bad byte becomes U+FFFD. */
+static void *ToUCS4( const char *in, size_t *outsize )
+{
+ size_t i_len = strlen( in );
+ uint32_t *p_out = vlc_alloc( i_len + 1, sizeof( *p_out ) );
+ if( unlikely( !p_out ) )
+ return NULL;
+ size_t i = 0;
+ while( *in )
+ {
+ uint32_t cp;
+ size_t i_bytes = vlc_towc( in, &cp );
+ if( i_bytes == 0 || i_bytes == (size_t)-1 )
+ {
+ cp = 0xFFFD;
+ i_bytes = 1;
+ }
+ p_out[i++] = cp;
+ in += i_bytes;
+ }
+ p_out[i] = 0;
+ *outsize = i * sizeof( *p_out );
+ return p_out;
+}
#else
# define ToUCS4( in, outsize ) ToCharset( FREETYPE_TO_UCS, ( in ), ( outsize ))
#endif
@@ -0,0 +1,94 @@
--- a/src/dvd_input.c
+++ b/src/dvd_input.c
@@ -103,6 +103,10 @@
/* dummy file input */
int fd;
+
+ /* VLC-PS5: a stream (DVDOpenStream) without libdvdcss */
+ dvd_reader_stream_cb *stream_cb;
+ uint64_t pos;
};
static dvd_input_t dvd_input_New(void *priv, dvd_logger_cb *logcb)
@@ -199,12 +203,26 @@
*/
static dvd_input_t file_open(void *priv, dvd_logger_cb *logcb,
const char *target,
- dvd_reader_stream_cb *stream_cb UNUSED)
+ dvd_reader_stream_cb *stream_cb)
{
dvd_input_t dev;
- if(target == NULL)
- return NULL;
+ if(target == NULL) {
+ /* VLC-PS5: no libdvdcss, so the plain input reads the stream itself
+ * (a DVD image on a network share). Unencrypted discs only. */
+ if(priv == NULL || stream_cb == NULL || !stream_cb->pf_seek || !stream_cb->pf_read)
+ return NULL;
+ dev = dvd_input_New(priv, logcb);
+ if(dev == NULL)
+ return NULL;
+ dev->fd = -1;
+ dev->stream_cb = stream_cb;
+ if(stream_cb->pf_seek(priv, 0) != 0) {
+ free(dev);
+ return NULL;
+ }
+ return dev;
+ }
/* Allocate the library structure */
dev = dvd_input_New(priv, logcb);
if(dev == NULL) {
@@ -256,6 +274,14 @@
{
off_t pos;
+ if(dev->stream_cb) {
+ uint64_t to = (uint64_t)blocks * DVD_VIDEO_LB_LEN;
+ if(dev->stream_cb->pf_seek(dev->priv, to) != 0)
+ return -1;
+ dev->pos = to;
+ return blocks;
+ }
+
pos = lseek(dev->fd, (off_t)blocks * (off_t)DVD_VIDEO_LB_LEN, SEEK_SET);
if(pos < 0) {
return pos;
@@ -283,6 +309,26 @@
len = (size_t)blocks * DVD_VIDEO_LB_LEN;
bytes = 0;
+ if(dev->stream_cb) {
+ while(len > 0) {
+ int chunk = len > (1 << 30) ? (1 << 30) : (int)len;
+ int ret = dev->stream_cb->pf_read(dev->priv, ((char*)buffer) + bytes, chunk);
+ if(ret < 0)
+ return ret;
+ if(ret == 0)
+ break;
+ len -= ret;
+ bytes += ret;
+ dev->pos += ret;
+ }
+ if(bytes % DVD_VIDEO_LB_LEN) {
+ /* back to the last whole block, as the file input does */
+ dev->pos -= bytes % DVD_VIDEO_LB_LEN;
+ dev->stream_cb->pf_seek(dev->priv, dev->pos);
+ }
+ return (int) (bytes / DVD_VIDEO_LB_LEN);
+ }
+
while(len > 0) {
ssize_t ret = read(dev->fd, ((char*)buffer) + bytes, len);
@@ -318,7 +364,7 @@
{
int ret;
- ret = close(dev->fd);
+ ret = dev->stream_cb ? 0 : close(dev->fd);
free(dev);
+40 -1
View File
@@ -193,7 +193,7 @@ void scan_dir(const std::string &dir, int depth)
const char *x = *dot ? dot + 1 : dot;
bool video = has_ext(x, video_exts), audio = has_ext(x, audio_exts);
bool image = has_ext(x, image_exts), text = has_ext(x, text_exts);
bool archive = has_ext(x, archive_exts), iso = !strcasecmp(x, "iso");
bool archive = has_ext(x, archive_exts), iso = !strcasecmp(x, "iso") || !strcasecmp(x, "img"); /* a DVD image named .img */
if (image) {
/* an album's cover file isn't a photo of its own */
std::string base = std::string(e->d_name, dot - e->d_name);
@@ -1438,6 +1438,45 @@ std::vector<int> library_playlist_items(const std::string &path)
return out;
}
int library_playlist_count(const std::string &path)
{
FILE *f = fopen(path.c_str(), "r");
if (!f)
return 0;
bool pls = path.size() > 4 && strcasecmp(path.c_str() + path.size() - 4, ".pls") == 0;
int n = 0;
char line[4096];
while (fgets(line, sizeof(line), f)) {
const char *l = line;
while (*l == ' ' || *l == '\t' || (unsigned char)*l == 0xEF || (unsigned char)*l == 0xBB || (unsigned char)*l == 0xBF)
l++; /* indent, UTF-8 mark */
if (pls ? (!strncasecmp(l, "File", 4) && strchr(l, '=')) : (*l && *l != '#' && *l != '\r' && *l != '\n'))
n++;
}
fclose(f);
return n;
}
bool library_playlist_has_links(const std::string &path)
{
FILE *f = fopen(path.c_str(), "r");
if (!f)
return false;
bool links = false;
char line[4096];
while (!links && fgets(line, sizeof(line), f)) {
const char *l = line;
if (*l == '#')
continue;
if (!strncasecmp(l, "File", 4) && strchr(l, '='))
l = strchr(l, '=') + 1; /* .pls */
const char *scheme = strstr(l, "://");
links = scheme && scheme > l && strncasecmp(l, "file://", 7) != 0;
}
fclose(f);
return links;
}
/* ---- playlists made in VLC -------------------------------------------------------- */
namespace {
+4
View File
@@ -69,6 +69,10 @@ MediaItem *library_find(const std::string &path);
std::vector<PlaylistFile> &library_playlists();
/* The library files a playlist names, in its order (others are skipped). */
std::vector<int> library_playlist_items(const std::string &path);
/* Every entry it names, links and files alike. */
int library_playlist_count(const std::string &path);
/* It names links (http://, rtp://...): a channel list, opened in Browse. */
bool library_playlist_has_links(const std::string &path);
/* Playlists made in VLC: <media>/Playlists/<name>.m3u8, one absolute path a
* line (with #EXTINF names), so VLC on a computer reads them too. Only these
* are changed; ones found elsewhere just play. */
+8 -2
View File
@@ -287,8 +287,14 @@ void finish_listing()
libvlc_media_list_unlock(subs);
libvlc_media_list_release(subs);
}
/* Folders A-Z; a playlist (TV channels, radio) keeps its own order. */
bool playlist = list_url.compare(0, 4, "http") == 0;
/* Folders A-Z; a playlist (TV channels, radio, an .m3u on a share)
* keeps its own order. */
std::string bare = list_url.substr(0, list_url.find('?'));
size_t dot = bare.rfind('.');
std::string ext = dot == std::string::npos ? "" : bare.substr(dot + 1);
for (char &c : ext)
c = (char)tolower((unsigned char)c);
bool playlist = list_url.compare(0, 4, "http") == 0 || ext == "m3u" || ext == "m3u8" || ext == "pls";
if (!playlist)
std::stable_sort(entries.begin(), entries.end(), [](const NetEntry &a, const NetEntry &b) {
if (a.dir != b.dir)
+1 -1
View File
@@ -32,7 +32,7 @@
namespace {
const char *API_HOST = "api.opensubtitles.com";
const char *USER_AGENT = "VLC-PS5 v0.1";
const char *USER_AGENT = "VLC-PS5 v" VLC_PS5_VERSION;
/* ---- what the interface sees ---- */
+1 -1
View File
@@ -23,7 +23,7 @@ extern "C" {
#endif
#define VLC_PS5_TITLE_ID "PPSA85300"
#define VLC_PS5_VERSION "0.1"
#define VLC_PS5_VERSION "0.3"
/* DualSense buttons (scePadRead's layout, as QUICK3 and PS5_vkQuake use it),
* plus the triggers past half as buttons. */
+567 -14
View File
@@ -19,7 +19,9 @@
#include <algorithm>
#include <atomic>
#include <chrono>
#include <ctype.h>
#include <dirent.h>
#include <math.h>
#include <condition_variable>
#include <deque>
@@ -37,6 +39,7 @@
#include <vlc_common.h>
#include <vlc_variables.h>
#include <vlc_url.h>
#include <vlc_stream.h>
#include "gen/video_frag.h"
#include "gen/video_vert.h"
@@ -45,6 +48,7 @@
#include "lang.h"
#include "platform.h"
#include "prefs.h"
#include "subconv.h"
namespace {
@@ -87,6 +91,24 @@ bool restore_delays;
double sub_check_at;
size_t sub_check_count;
bool sub_attach_all; /* the reopen: every added file, wherever it is */
/* Subtitle files beside a video on a share or a link, found in the background
* once it plays (src: side_subs_find). */
struct SideSubs {
std::mutex lock;
std::string path; /* the video they're for */
std::vector<std::string> uris; /* best first */
bool ready;
int serial; /* bumped by every open: an old search is dropped */
} side;
std::vector<std::string> source_options; /* the open's login options, for the folder listing */
/* Subtitle files added by hand from a share or a link, read in the background:
* player_tick puts them in (when it's still the same file). */
struct PendingSub {
std::string path, ready; /* ready: what VLC gets, "" = couldn't be read */
int serial; /* side.serial when it was asked for */
};
std::mutex pending_lock;
std::vector<PendingSub> pending_subs;
/* ---- pictures -------------------------------------------------------------- */
@@ -900,6 +922,7 @@ void apply_saved_choices()
std::vector<Chapter> chapters_cache;
double chapters_tried;
int chapters_title = -2; /* a disc's chapters belong to its current title */
/* A slot's picture as RGB, cropped to what's visible: the shader's sums on
* the CPU (BT.709 for HD, 601 below). */
@@ -967,9 +990,18 @@ bool player_init()
/* HLS / DASH segments through the access modules: ps5http (the
* console's HTTP service), not the adaptive module's own sockets. */
"--adaptive-use-access",
/* No iconv on the console: subtitles are read as UTF-8 (VLC's CP1252
* fallback only logged "cannot convert" for every file). */
"--subsdec-encoding=UTF-8",
#endif
"--ignore-config",
"--no-video-title-show",
/* Browse shows every file: VLC's listings (SMB, DLNA, ZIP/RAR) too, not
* only local folders. VLC hid m3u, images, subtitles, txt... there. */
"--ignore-filetypes=",
/* Subtitle files beside a video: our own search, for every source
* (side_subs_find); VLC's looked beside local files only. */
"--no-sub-autodetect-file",
"--stats", /* decoded/shown/lost pictures, for player_tick */
"--aout=adummy", /* replaced by our callbacks (World 1) */
"--avcodec-threads=0", /* one per core */
@@ -1084,13 +1116,327 @@ bool player_open(const std::string &path, int64_t start_ms, const std::vector<st
return false;
for (const std::string &o : options)
libvlc_media_add_option(m, o.c_str());
return player_open_media(m, path, start_ms);
return player_open_media(m, path, start_ms, options);
}
std::string file_uri(const std::string &path);
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms)
/* A subtitle file's address for VLC: a share's or a link's as it is, a
* local path made a file:// URI. */
std::string sub_uri(const std::string &path)
{
return path.find("://") != std::string::npos ? path : file_uri(path);
}
extern "C" input_thread_t *libvlc_get_input_thread(libvlc_media_player_t *);
/* The language a subtitle file is most likely in, for one that isn't UTF-8:
* the subtitle language chosen, else the menus'. */
std::string sub_lang_hint()
{
std::string s = player_sub_language();
return s.empty() || s == "off" || s == "none" ? std::string(lang_code(lang_current())) : s;
}
/* A subtitle file on a share or a link, read through VLC as a child of the
* playing input (so a share's login comes with it); at most 8 MiB. False
* when it can't be opened (a link's guess that isn't there). */
bool read_remote_sub(const std::string &uri, std::string *out)
{
input_thread_t *input = nullptr;
/* the open may still be on its way */
for (int i = 0; i < 50 && !(input = libvlc_get_input_thread(mp)); i++)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (!input)
return false;
stream_t *s = vlc_stream_NewURL((vlc_object_t *)input, uri.c_str());
bool ok = s != nullptr;
out->clear();
if (s) {
char buf[65536];
ssize_t n;
while ((n = vlc_stream_Read(s, buf, sizeof(buf))) > 0 && out->size() < (8u << 20))
out->append(buf, (size_t)n);
vlc_stream_Delete(s);
ok = !out->empty();
}
vlc_object_release((vlc_object_t *)input);
return ok;
}
/* A subtitle file ready for VLC: a local path, or the address of one on a
* share or a link; one that isn't UTF-8 becomes a converted local copy
* (the console's VLC has no iconv). "" when a remote one can't be read. */
std::string sub_ready(const std::string &path, const std::string &hint)
{
if (path.find("://") == std::string::npos)
return subconv_local(path, hint);
std::string bytes;
if (!read_remote_sub(path, &bytes))
return "";
std::string b = path.substr(0, path.find_first_of("?#"));
char *name = vlc_uri_decode_duplicate(b.substr(b.rfind('/') + 1).c_str());
std::string copy = subconv_bytes(bytes, name ? name : "subtitle.srt", hint);
free(name);
return copy.empty() ? path : copy;
}
/* ---- subtitles beside the video ------------------------------------------------
* Ours, the same for every source: VLC's own search only looks beside local
* files, and the ones its share listings attach weren't turned on. A
* subtitle file goes with a video when its name starts with the video's
* (without the extension) followed by nothing or a separator: "Film.srt",
* "Film.en.srt", "Film - English.ass". Local folders and shares (SMB, FTP,
* SFTP, NFS) are listed; a link (http, https) is asked for Film.srt. */
bool is_sub_name(const std::string &lower_name)
{
static const char *const exts[] = { "srt", "ass", "ssa", "vtt", "sub", "idx", "smi", "sami", "ttml",
"usf", "jss", "psb", "rt", "mpl2", "pjs", "stl", "dks" };
size_t dot = lower_name.rfind('.');
if (dot == std::string::npos)
return false;
for (const char *e : exts)
if (lower_name.compare(dot + 1, std::string::npos, e) == 0)
return true;
return false;
}
std::string lowered(std::string s)
{
for (char &c : s)
c = (char)tolower((unsigned char)c);
return s;
}
/* name (a file's) goes with the video named base (no extension), both lowercase */
bool sub_goes_with(const std::string &base, const std::string &name)
{
if (base.empty() || name.size() <= base.size() || name.compare(0, base.size(), base) != 0 ||
!is_sub_name(name))
return false;
char c = name[base.size()];
return c == '.' || c == ' ' || c == '-' || c == '_' || c == '[' || c == '(';
}
/* The matches, best first: Film.srt, then the others by name; a VobSub .sub
* goes only through its .idx. */
std::vector<std::string> sort_side_subs(const std::string &base, std::vector<std::string> names)
{
std::vector<std::string> lower;
for (const std::string &n : names)
lower.push_back(lowered(n));
std::vector<size_t> order;
for (size_t i = 0; i < names.size(); i++) {
const std::string &l = lower[i];
if (l.size() > 4 && !l.compare(l.size() - 4, 4, ".sub") &&
std::find(lower.begin(), lower.end(), l.substr(0, l.size() - 4) + ".idx") != lower.end())
continue;
order.push_back(i);
}
std::string exact = base + ".srt";
std::stable_sort(order.begin(), order.end(), [&](size_t a, size_t b) {
bool ea = lower[a] == exact, eb = lower[b] == exact;
if (ea != eb)
return ea;
return lower[a] < lower[b];
});
std::vector<std::string> out;
for (size_t i : order)
out.push_back(names[i]);
return out;
}
/* A local video: its folder and the usual subtitle folders inside it. */
std::vector<std::string> local_side_subs(const std::string &path)
{
size_t slash = path.rfind('/');
if (slash == std::string::npos)
return {};
std::string dir = path.substr(0, slash), file = path.substr(slash + 1);
std::string base = lowered(file.substr(0, file.rfind('.')));
std::vector<std::string> found;
static const char *const subdirs[] = { "", "/Subs", "/subs", "/Subtitles", "/subtitles", "/Sub", "/sub" };
for (const char *sd : subdirs) {
std::string d = dir + sd;
DIR *h = opendir(d.c_str());
if (!h)
continue;
std::vector<std::string> names;
while (struct dirent *e = readdir(h))
if (e->d_name[0] != '.' && sub_goes_with(base, lowered(e->d_name)))
names.push_back(e->d_name);
closedir(h);
for (const std::string &n : sort_side_subs(base, names)) {
std::string full = d + "/" + n;
if (std::find(found.begin(), found.end(), full) == found.end())
found.push_back(full);
}
}
return found;
}
bool url_scheme_is(const std::string &url, const char *scheme)
{
size_t n = strlen(scheme);
return url.size() > n + 3 && !strncasecmp(url.c_str(), scheme, n) && !url.compare(n, 3, "://");
}
/* A share or a link: in the background, then player_tick adds what it found. */
void side_subs_find(const std::string &url, int serial)
{
std::string bare = url.substr(0, url.find_first_of("?#"));
size_t slash = bare.rfind('/');
if (slash == std::string::npos || slash < bare.find("://") + 3)
return;
std::string parent = bare.substr(0, slash + 1), last = bare.substr(slash + 1);
char *dec = vlc_uri_decode_duplicate(last.c_str());
std::string file = dec ? dec : last;
free(dec);
size_t dot = file.rfind('.');
if (dot == std::string::npos || dot == 0)
return; /* a stream's address, not a file */
std::string ext = lowered(file.substr(dot + 1));
if (ext == "m3u8" || ext == "m3u" || ext == "mpd" || ext == "pls" || ext == "iso" || ext == "img" || is_sub_name(lowered(file)))
return;
std::string base = lowered(file.substr(0, dot));
bool listable = url_scheme_is(url, "smb") || url_scheme_is(url, "ftp") || url_scheme_is(url, "ftps") ||
url_scheme_is(url, "sftp") || url_scheme_is(url, "nfs");
bool link = url_scheme_is(url, "http") || url_scheme_is(url, "https");
if (!listable && !link)
return;
std::vector<std::string> opts = source_options;
/* its own reference: a quit during the listing (up to 8 s) mustn't free
* VLC under it */
libvlc_instance_t *inst = vlc;
libvlc_retain(inst);
std::string hint = sub_lang_hint();
std::thread([=]() {
std::vector<std::string> uris;
if (link) {
/* No folder to list: the one most links have beside them. */
uris.push_back(bare.substr(0, bare.size() - (file.size() - dot)) + ".srt");
(void)base;
} else if (libvlc_media_t *m = libvlc_media_new_location(inst, parent.c_str())) {
for (const std::string &o : opts)
libvlc_media_add_option(m, o.c_str());
libvlc_media_parse_with_options(m, libvlc_media_parse_network, 8000);
for (int i = 0; i < 100 && libvlc_media_get_parsed_status(m) == 0; i++)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::vector<std::string> names, mrls;
if (libvlc_media_list_t *list = libvlc_media_subitems(m)) {
libvlc_media_list_lock(list);
for (int i = 0; i < libvlc_media_list_count(list); i++) {
libvlc_media_t *c = libvlc_media_list_item_at_index(list, i);
if (!c)
continue;
if (char *mrl = libvlc_media_get_mrl(c)) {
std::string u = mrl;
std::string b = u.substr(0, u.find_first_of("?#"));
char *n = vlc_uri_decode_duplicate(b.substr(b.rfind('/') + 1).c_str());
if (n && sub_goes_with(base, lowered(n))) {
names.push_back(n);
mrls.push_back(u);
}
free(n);
free(mrl);
}
libvlc_media_release(c);
}
libvlc_media_list_unlock(list);
libvlc_media_list_release(list);
}
libvlc_media_release(m);
for (const std::string &n : sort_side_subs(base, names))
uris.push_back(mrls[std::find(names.begin(), names.end(), n) - names.begin()]);
}
/* read through VLC: one that isn't UTF-8 becomes a converted copy,
* a link's guess that isn't there is dropped */
std::vector<std::string> ready;
for (const std::string &u : uris) {
std::string r = sub_ready(u, hint);
if (!r.empty())
ready.push_back(r.find("://") == std::string::npos ? file_uri(r) : r);
}
uris.swap(ready);
libvlc_release(inst);
fprintf(stderr, "player: %zu subtitle file(s) beside %s\n", uris.size(), url.c_str());
std::lock_guard<std::mutex> g(side.lock);
if (side.serial != serial)
return;
side.path = url;
side.uris = uris;
side.ready = true;
}).detach();
}
/* Found ones go in once the video plays: the first turned on (unless
* subtitles are set to Off), the others listed in Tracks. */
void side_subs_apply()
{
std::vector<std::string> uris;
{
std::lock_guard<std::mutex> g(side.lock);
if (!side.ready || side.path != current_path)
return;
side.ready = false;
uris = side.uris;
}
/* A media from a listing opened again still has the ones that loaded
* last time (VLC keeps them on the item): not twice. */
std::vector<std::string> have;
if (libvlc_media_t *m = libvlc_media_player_get_media(mp)) {
libvlc_media_slave_t **slaves = nullptr;
unsigned n = libvlc_media_slaves_get(m, &slaves);
for (unsigned i = 0; i < n; i++)
if (slaves[i]->psz_uri)
have.push_back(slaves[i]->psz_uri);
libvlc_media_slaves_release(slaves, n);
libvlc_media_release(m);
}
bool on = player_sub_language() != "off";
for (size_t i = 0; i < uris.size(); i++) {
if (std::find(have.begin(), have.end(), uris[i]) != have.end()) {
fprintf(stderr, "player: subtitle beside the video %s: already there\n", uris[i].c_str());
continue;
}
int r = libvlc_media_player_add_slave(mp, libvlc_media_slave_type_subtitle, uris[i].c_str(), on && i == 0);
fprintf(stderr, "player: subtitle beside the video %s: %s\n", uris[i].c_str(), r == 0 ? "added" : "failed");
}
}
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms,
const std::vector<std::string> &options)
{
/* A reopen of the same file (Auto's, Watch again) keeps its login. */
if (!options.empty() || path != current_path)
source_options = options;
{
std::lock_guard<std::mutex> g(side.lock);
side.serial++;
side.ready = false;
}
bool url = path.find("://") != std::string::npos;
std::string local = path;
if (url_scheme_is(path, "file")) {
char *p = vlc_uri2path(path.c_str());
local = p ? p : "";
free(p);
url = false;
}
if (url) {
side_subs_find(path, side.serial);
} else if (!local.empty()) {
std::vector<std::string> subs = local_side_subs(local);
bool on = player_sub_language() != "off";
std::string hint = sub_lang_hint();
for (size_t i = 0; i < subs.size(); i++) {
/* 4 (the user's): VLC turns the first one on; 3: only listed */
libvlc_media_slaves_add(m, libvlc_media_slave_type_subtitle, on && i == 0 ? 4 : 3,
file_uri(sub_ready(subs[i], hint)).c_str());
fprintf(stderr, "player: subtitle beside the video: %s\n", subs[i].c_str());
}
}
if (path != kept.path) {
/* another file: upright, no delays, its own tracks */
picture.rotate = 0;
@@ -1098,11 +1444,11 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
kept = { path, 0, 0, -2, -2, {} };
} else {
/* the same one again: what was set comes back once it plays */
/* (VLC finds the ones beside the video, named like it, by itself) */
/* (the ones beside the video, named like it, are found again) */
std::string base = path.substr(0, path.rfind('.'));
for (const std::string &sub : kept.subs)
if (sub_attach_all || sub.compare(0, base.size(), base) != 0)
libvlc_media_slaves_add(m, libvlc_media_slave_type_subtitle, 4, file_uri(sub).c_str());
libvlc_media_slaves_add(m, libvlc_media_slave_type_subtitle, 4, sub_uri(sub).c_str());
if (kept.audio != -2)
restore_audio = kept.audio;
if (kept.spu != -2)
@@ -1121,6 +1467,8 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
* (PS5 recordings, streaming encodes) at 60 fps, that hidden work froze the
* picture after every jump on the console. */
libvlc_media_add_option(m, ":input-fast-seek");
/* A Blu-ray checks the player's region (VLC's default is B, Europe): ours. */
libvlc_media_add_option(m, (":bluray-region=" + player_bluray_region()).c_str());
std::string alang = player_audio_language(), slang = player_sub_language();
if (!alang.empty())
libvlc_media_add_option(m, (":audio-language=" + alang).c_str());
@@ -1128,6 +1476,7 @@ bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start
libvlc_media_add_option(m, (":sub-language=" + (slang == "off" ? std::string("none") : slang)).c_str());
chapters_cache.clear();
chapters_tried = 0;
chapters_title = -2;
fast_now = decode_mode == DECODE_FAST || (decode_mode == DECODE_AUTO && path == fast_path);
if (fast_now) {
libvlc_media_add_option(m, ":avcodec-skiploopfilter=4");
@@ -1169,12 +1518,36 @@ bool player_fast_decoding()
return fast_now;
}
bool add_ready_subtitle(const std::string &path, const std::string &ready);
/* The file playing is a disc: an image (.iso, .img), dvd:// or bluray://, or
* anything VLC sees several titles in (a VIDEO_TS / BDMV folder). */
bool playing_disc_now()
{
std::string bare = lowered(current_path.substr(0, current_path.find('?')));
if (bare.size() > 4 && (!bare.compare(bare.size() - 4, 4, ".iso") || !bare.compare(bare.size() - 4, 4, ".img")))
return true;
if (url_scheme_is(bare, "dvd") || url_scheme_is(bare, "bluray"))
return true;
return mp && libvlc_media_player_get_title_count(mp) > 1;
}
PlayerEvent player_tick()
{
double t = plat_time();
std::vector<PendingSub> done;
{
std::lock_guard<std::mutex> g(pending_lock);
done.swap(pending_subs);
}
for (const PendingSub &p : done)
if (mp && p.serial == side.serial)
add_ready_subtitle(p.path, p.ready);
if (!mp || t < health.next)
return PLAYER_EVENT_NONE;
health.next = t + 1;
if (state() == libvlc_Playing)
side_subs_apply();
if (sub_check_at > 0 && t >= sub_check_at && state() == libvlc_Playing) {
sub_check_at = 0;
size_t now_count = player_subtitle_tracks().size();
@@ -1231,6 +1604,11 @@ PlayerEvent player_tick()
/* Struggling: most pictures of a second lost, or far fewer shown than the
* file has. Not while paused, fast-forwarding or with no video at all. */
bool playing = state() == libvlc_Playing && libvlc_media_player_get_rate(mp) <= 1.01f;
/* Not on a disc: its menus and still screens show few pictures by design
* (a Blu-ray menu looked like "can't keep up" every second), and Auto's
* reopen would throw the menus away. */
if (playing_disc_now())
playing = false;
bool bad = playing && decoded > 0 &&
(lost > shown || shown < fps * 0.5f);
health.bad_seconds = bad ? health.bad_seconds + 1 : 0;
@@ -1310,6 +1688,86 @@ void player_navigate(int action)
libvlc_media_player_navigate(mp, modes[action]);
}
/* libvlc's own (lib/media_player_internal.h): the input, held. */
extern "C" input_thread_t *libvlc_get_input_thread(libvlc_media_player_t *);
bool player_in_menu()
{
/* Read four times a second: the title list is copied out on every call. */
static double next;
static bool menu;
double now = plat_time();
if (now < next)
return menu;
next = now + 0.25;
menu = false;
if (!mp)
return menu;
/* A DVD says when menu buttons are on screen ("highlight", set by VLC's
* dvdnav). A DVD's intro or warning screen is in its menu part too but has
* no buttons: there ✕ must pause, not press a button nobody sees. */
if (input_thread_t *input = libvlc_get_input_thread(mp)) {
vlc_object_t *in = (vlc_object_t *)input; /* input_thread_t is opaque here */
/* A Blu-ray says when its menu is on screen, in any title
* (patches/0010); the "Top Menu" title alone missed most discs. Asked
* first: VLC's subtitle unit puts a "highlight" on every input with
* a video output, so that one alone doesn't mean a DVD. */
bool bd = var_Type(in, "bluray-menu-open") != 0;
bool dvd = !bd && var_Type(in, "highlight") != 0;
if (dvd)
menu = var_GetBool(in, "highlight");
else if (bd)
menu = var_GetBool(in, "bluray-menu-open");
vlc_object_release(in);
static int logged = -1;
if ((dvd || bd) && logged != (int)menu) {
logged = menu;
fprintf(stderr, "player: %s menu %s\n", dvd ? "DVD" : "Blu-ray", menu ? "on screen" : "gone");
}
if (dvd || bd)
return menu;
}
/* A Blu-ray played without menus: its menu titles. */
int cur = libvlc_media_player_get_title(mp);
libvlc_title_description_t **titles = nullptr;
int n = libvlc_media_player_get_full_title_descriptions(mp, &titles);
if (n > 0 && cur >= 0 && cur < n && titles[cur])
/* menu only: a Blu-ray marks its movie "interactive" too (pop-up
* menus), and ✕ must still pause that */
menu = (titles[cur]->i_flags & libvlc_title_menu) != 0;
if (n > 0)
libvlc_title_descriptions_release(titles, (unsigned)n);
return menu;
}
/* The disc's main menu (a DVD's root menu, a Blu-ray's Top Menu) or a
* Blu-ray's pop-up menu, over the film. */
void player_disc_menu(bool popup)
{
if (!mp)
return;
if (input_thread_t *input = libvlc_get_input_thread(mp)) {
vlc_object_t *in = (vlc_object_t *)input;
const char *var = popup ? "menu-popup" : "menu-title";
if (var_Type(in, var) != 0)
var_TriggerCallback(in, var);
fprintf(stderr, "player: disc %s\n", popup ? "pop-up menu" : "menu");
vlc_object_release(in);
}
}
bool player_disc_has_popup()
{
bool popup = false;
if (mp)
if (input_thread_t *input = libvlc_get_input_thread(mp)) {
vlc_object_t *in = (vlc_object_t *)input;
popup = var_Type(in, "bluray-popup") != 0 && var_GetBool(in, "bluray-popup");
vlc_object_release(in);
}
return popup;
}
bool player_paused()
{
if (live_paused)
@@ -1350,7 +1808,14 @@ void player_toggle_pause()
return;
}
bool want = !player_paused();
fprintf(stderr, "player: %s\n", want ? "pause" : "play");
libvlc_media_player_set_pause(mp, want ? 1 : 0);
if (!want) {
/* the second that plays on from a pause starts slowly: not a
* "can't keep up" (the count starts again after it) */
health.primed = false;
health.next = plat_time() + 2;
}
pause_wanted = want ? 1 : 0;
pause_wanted_at = plat_time();
std::lock_guard<std::mutex> g(audio_lock);
@@ -1480,16 +1945,57 @@ std::string file_uri(const std::string &path)
return uri;
}
bool player_is_subtitle_name(const std::string &name)
{
return is_sub_name(lowered(name));
}
bool player_add_subtitle(const std::string &path)
{
if (!mp)
return false;
/* A full path or an address only: VLC makes a relative one full with
* getcwd(), which crashed on the console. */
if (path.empty() || (path[0] != '/' && path.find("://") == std::string::npos)) {
fprintf(stderr, "player: not a subtitle file address: \"%s\"\n", path.c_str());
return false;
}
if (path.find("://") != std::string::npos) {
/* On a share or a link: read (and made UTF-8) in the background, put
* in by player_tick; read here, the picture froze ~0.6 s. */
int serial = side.serial;
std::string hint = sub_lang_hint();
libvlc_instance_t *inst = vlc;
libvlc_retain(inst);
std::thread([=]() {
std::string ready = sub_ready(path, hint);
libvlc_release(inst);
std::lock_guard<std::mutex> g(pending_lock);
pending_subs.push_back({ path, ready, serial });
}).detach();
return true;
}
/* not UTF-8: a converted copy */
return add_ready_subtitle(path, sub_ready(path, sub_lang_hint()));
}
/* A subtitle file read and made ready: into the file playing. */
bool add_ready_subtitle(const std::string &path, const std::string &ready)
{
if (ready.empty()) {
fprintf(stderr, "player: subtitle file %s: can't be read\n", path.c_str());
return false;
}
sub_check_count = player_subtitle_tracks().size();
sub_check_at = plat_time() + 4;
int r = libvlc_media_player_add_slave(mp, libvlc_media_slave_type_subtitle, file_uri(path).c_str(), true);
int r = libvlc_media_player_add_slave(mp, libvlc_media_slave_type_subtitle, sub_uri(ready).c_str(), true);
fprintf(stderr, "player: subtitle file %s: %s\n", path.c_str(), r == 0 ? "added" : "failed");
if (r == 0 && std::find(kept.subs.begin(), kept.subs.end(), path) == kept.subs.end())
kept.subs.push_back(path);
/* the file VLC got is the one a reopen attaches again */
if (r == 0 && std::find(kept.subs.begin(), kept.subs.end(), ready) == kept.subs.end())
kept.subs.push_back(ready);
/* VLC jumps to put the new track in step: not a "can't keep up" second */
health.primed = false;
health.next = plat_time() + 3;
return r == 0;
}
@@ -1616,10 +2122,11 @@ void video_draw(VkCommandBuffer cmd, float x, float y, float w, float h)
p.view[2] = tanf(view_fov * 3.14159265f / 360);
p.view[3] = h > 0 ? w / h : 16.0f / 9;
}
uint32_t vis_w = v.width && v.width <= planes.buf_w ? v.width : planes.buf_w;
vis_h = v.height && v.height <= planes.buf_h ? v.height : planes.buf_h;
p.uv_scale[0] = (float)vis_w / planes.buf_w;
p.uv_scale[1] = (float)vis_h / planes.buf_h;
/* The whole buffer is picture: vmem's pictures fill the size it set up
* (1024x560 arrives stretched to 1024x578), so nothing is cut; the
* rectangle has the picture's real shape. */
p.uv_scale[0] = 1;
p.uv_scale[1] = 1;
VkViewport vp = { x, y, w, h, 0, 1 };
/* The scissor stays on screen ("Fill" makes the rectangle larger than it). */
float sx0 = x < 0 ? 0 : x, sy0 = y < 0 ? 0 : y;
@@ -1667,12 +2174,36 @@ void player_set_subtitle_delay(int64_t ms)
kept.spu_delay = ms;
}
/* The title is a disc menu (a DVD's "DVD Menu" lists Resume, Root, Title...
* as chapters: not chapters of anything). */
static bool title_is_menu(int title)
{
libvlc_title_description_t **titles = nullptr;
int n = libvlc_media_player_get_full_title_descriptions(mp, &titles);
bool menu = n > 0 && title >= 0 && title < n && titles[title] && (titles[title]->i_flags & libvlc_title_menu);
if (n > 0)
libvlc_title_descriptions_release(titles, (unsigned)n);
return menu;
}
std::vector<Chapter> player_chapters()
{
if (!mp)
return chapters_cache;
/* A disc moves between titles (menu, intro, the film): each has its own
* chapters, so the list is read again when the title changes. */
int title = libvlc_media_player_get_title(mp);
if (title != chapters_title) {
chapters_cache.clear();
chapters_tried = 0;
chapters_title = title;
}
/* Known once the file is open; asked again at most once a second until then. */
if (!mp || !chapters_cache.empty() || plat_time() < chapters_tried + 1)
if (!chapters_cache.empty() || plat_time() < chapters_tried + 1)
return chapters_cache;
chapters_tried = plat_time();
if (title_is_menu(title))
return chapters_cache;
libvlc_chapter_description_t **d = nullptr;
int n = libvlc_media_player_get_full_chapter_descriptions(mp, -1, &d);
for (int i = 0; i < n; i++) {
@@ -1716,8 +2247,9 @@ bool player_screenshot(const std::string &path)
shot.f = format;
shot.data.assign(slots[last_slot].mapped, slots[last_slot].mapped + format.size);
}
shot.w = info.width && info.width <= shot.f.width ? info.width : shot.f.width;
shot.h = info.height && info.height <= shot.f.height ? info.height : shot.f.height;
/* the whole buffer: the picture fills it (see video_draw) */
shot.w = shot.f.width;
shot.h = shot.f.height;
shot.path = path;
std::thread(shot_main, std::move(shot)).detach();
return true;
@@ -1782,6 +2314,27 @@ void player_set_sub_style(const SubStyle &s)
apply_sub_style(s);
}
/* A, B or C: the saved choice, else the region of the interface's language
* (A: the Americas, Japan, Korea, South-East Asia, Taiwan; B: Europe, the
* Middle East, Africa, Oceania; C: China, Russia, India). */
std::string player_bluray_region()
{
std::string r = pref_str("bd_region", "");
if (r == "A" || r == "B" || r == "C")
return r;
std::string code = lang_code(lang_current());
if (code == "zh-CN" || code == "ru")
return "C";
if (code == "en" || code == "es" || code == "pt-BR" || code == "ja" || code == "ko" || code == "zh-TW" || code == "id")
return "A";
return "B";
}
void player_set_bluray_region(const std::string &region)
{
pref_set("bd_region", region);
}
std::string player_audio_language()
{
return pref_str("lang_audio", "");
+13 -2
View File
@@ -40,12 +40,20 @@ libvlc_instance_t *player_vlc();
bool player_open(const std::string &path, int64_t start_ms, const std::vector<std::string> &options = {});
/* The same with a media VLC made (an entry of a listed playlist, with the options
* its list gave it); takes the reference. path: what it's called in the log. */
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms);
bool player_open_media(libvlc_media_t *m, const std::string &path, int64_t start_ms,
const std::vector<std::string> &options = {});
void player_stop();
bool player_active(); /* a file is open (playing, paused, buffering) */
bool player_ended(); /* the file played to its end (or failed) */
bool player_failed();
bool player_buffering(); /* opening or buffering: no picture moving yet */
/* Disc menus: the main menu (DVD root, Blu-ray Top Menu) or a Blu-ray's
* pop-up menu; whether the Blu-ray playing has one. */
/* The Blu-ray region VLC plays as (A, B, C), from the next file. */
std::string player_bluray_region();
void player_set_bluray_region(const std::string &region);
void player_disc_menu(bool popup);
bool player_disc_has_popup();
bool player_paused();
void player_toggle_pause();
int64_t player_time(); /* ms */
@@ -62,7 +70,8 @@ std::vector<Track> player_subtitle_tracks();
int player_subtitle_track();
void player_set_subtitle_track(int id);
/* Adds a subtitle file (.srt, .ass, ...) to what plays and selects it. */
bool player_add_subtitle(const std::string &path);
bool player_add_subtitle(const std::string &path); /* a local path, or a share's / a link's address */
bool player_is_subtitle_name(const std::string &name); /* .srt, .ass, .vtt... */
const VideoInfo &player_video_info();
/* A 360° video: where the view looks (degrees) and how wide it sees. */
@@ -70,6 +79,8 @@ void player_view(float yaw, float pitch, float fov);
/* A DVD / Blu-ray menu: 0 activate, 1 up, 2 down, 3 left, 4 right. */
void player_navigate(int action);
/* A disc's menu is on screen (its current title is a menu). */
bool player_in_menu();
/* The sound being heard, as n bands from bass to treble (0..1 each). */
void player_spectrum(float *bands, int n);
+179
View File
@@ -0,0 +1,179 @@
/*
* VLC-PS5: subtitle files that aren't UTF-8, made UTF-8 for VLC (see
* subconv.h). Our own code; the conversion tables are GNU libiconv's.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "subconv.h"
#include <iconv.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "platform.h"
namespace {
/* Valid UTF-8 (no overlongs, no surrogates, nothing past U+10FFFF). */
bool is_utf8(const std::string &s)
{
const unsigned char *p = (const unsigned char *)s.data(), *end = p + s.size();
while (p < end) {
unsigned c = *p;
if (c < 0x80) {
p++;
continue;
}
int n = c >= 0xF0 && c <= 0xF4 ? 3 : c >= 0xE0 ? 2 : c >= 0xC2 && c <= 0xDF ? 1 : -1;
if (n < 0 || c > 0xF4 || end - p <= n)
return false;
uint32_t cp = c & (0x3F >> n);
for (int i = 1; i <= n; i++) {
if ((p[i] & 0xC0) != 0x80)
return false;
cp = cp << 6 | (p[i] & 0x3F);
}
if ((n == 2 && cp < 0x800) || (n == 3 && (cp < 0x10000 || cp > 0x10FFFF)) || (cp >= 0xD800 && cp <= 0xDFFF))
return false;
p += n + 1;
}
return true;
}
/* The code page subtitles in that language are usually saved in when they
* aren't Unicode (what Windows' "ANSI" was for it). */
const char *legacy_encoding(const std::string &lang)
{
std::string l = lang.substr(0, lang.find('-'));
if (lang == "zh-TW" || lang == "zh-HK" || lang == "zht")
return "BIG5-HKSCS";
if (l == "zh" || l == "chi" || l == "zho")
return "GB18030";
if (l == "ja" || l == "jpn")
return "CP932";
if (l == "ko" || l == "kor")
return "CP949";
if (l == "ru" || l == "rus" || l == "uk" || l == "ukr" || l == "bg" || l == "bul" || l == "sr" || l == "mk" || l == "be")
return "CP1251";
if (l == "ar" || l == "ara" || l == "fa" || l == "per" || l == "fas" || l == "ur")
return "CP1256";
if (l == "he" || l == "heb")
return "CP1255";
if (l == "tr" || l == "tur")
return "CP1254";
if (l == "el" || l == "gre" || l == "ell")
return "CP1253";
if (l == "pl" || l == "pol" || l == "cs" || l == "cze" || l == "ces" || l == "sk" || l == "hu" || l == "hun" ||
l == "ro" || l == "rum" || l == "hr" || l == "sl")
return "CP1250";
if (l == "th" || l == "tha")
return "CP874";
if (l == "vi" || l == "vie")
return "CP1258";
return "CP1252";
}
bool convert(const std::string &in, const char *from, std::string *out)
{
iconv_t cd = iconv_open("UTF-8", from);
if (cd == (iconv_t)-1)
return false;
out->clear();
out->resize(in.size() * 4 + 16);
char *src = (char *)in.data(), *dst = &(*out)[0];
size_t left = in.size(), room = out->size();
bool ok = true;
while (left > 0) {
if (iconv(cd, &src, &left, &dst, &room) == (size_t)-1) {
/* a byte the code page hasn't got: U+FFFD, and on */
if (room < 3) {
ok = false;
break;
}
memcpy(dst, "\xEF\xBF\xBD", 3);
dst += 3;
room -= 3;
src++;
left--;
}
}
iconv_close(cd);
out->resize(out->size() - room);
return ok;
}
std::string copy_dir()
{
std::string d = std::string(plat_data_dir()) + "/cache";
mkdir(d.c_str(), 0777);
d += "/subtitles";
mkdir(d.c_str(), 0777);
d += "/converted";
mkdir(d.c_str(), 0777);
return d;
}
} // namespace
std::string subconv_to_utf8(const std::string &bytes, const std::string &lang, std::string *from)
{
if (from)
from->clear();
const unsigned char *b = (const unsigned char *)bytes.data();
std::string out;
if (bytes.size() >= 2 && ((b[0] == 0xFF && b[1] == 0xFE) || (b[0] == 0xFE && b[1] == 0xFF))) {
const char *enc = b[0] == 0xFF ? "UTF-16LE" : "UTF-16BE";
if (convert(bytes.substr(2), enc, &out)) {
if (from)
*from = enc;
return out;
}
return bytes;
}
if (is_utf8(bytes))
return bytes;
const char *enc = legacy_encoding(lang);
if (!convert(bytes, enc, &out))
return bytes;
if (from)
*from = enc;
return out;
}
std::string subconv_bytes(const std::string &bytes, const std::string &name, const std::string &lang)
{
std::string from;
std::string utf8 = subconv_to_utf8(bytes, lang, &from);
if (from.empty())
return "";
/* The name kept (VLC shows it, and finds the format by its extension). */
std::string base = name.substr(name.rfind('/') + 1);
std::string path = copy_dir() + "/" + base;
FILE *f = fopen(path.c_str(), "wb");
if (!f)
return "";
fwrite(utf8.data(), 1, utf8.size(), f);
fclose(f);
fprintf(stderr, "subconv: %s read as %s, UTF-8 copy %s\n", base.c_str(), from.c_str(), path.c_str());
return path;
}
std::string subconv_local(const std::string &path, const std::string &lang)
{
FILE *f = fopen(path.c_str(), "rb");
if (!f)
return path;
std::string bytes;
char buf[65536];
size_t n;
/* subtitle files are small: a big one is something else, left alone */
while ((n = fread(buf, 1, sizeof(buf), f)) > 0 && bytes.size() < (16u << 20))
bytes.append(buf, n);
fclose(f);
if (bytes.size() >= (16u << 20))
return path;
std::string copy = subconv_bytes(bytes, path, lang);
return copy.empty() ? path : copy;
}
+27
View File
@@ -0,0 +1,27 @@
/*
* VLC-PS5: subtitle files that aren't UTF-8 (Windows-1252, GBK, Big5, Shift
* JIS, Windows-1256...). The console's VLC has no iconv, so it read them as
* UTF-8 and showed mangled letters; such a file is converted here (GNU
* libiconv) to a UTF-8 copy in cache/subtitles/converted, which VLC gets
* instead.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <string>
/* The bytes of a subtitle file made UTF-8: as they are when they already are
* (or the encoding can't be told), else converted from UTF-16 (by its BOM)
* or from the legacy code page of lang ("zh-CN", "ar", "ru"...; "" = Western).
* *from says which encoding it was read as ("" = left alone). */
std::string subconv_to_utf8(const std::string &bytes, const std::string &lang, std::string *from = nullptr);
/* A local subtitle file ready for VLC: the path itself when it's UTF-8 (or
* can't be read), else the path of a converted UTF-8 copy. */
std::string subconv_local(const std::string &path, const std::string &lang);
/* The same for bytes already read (a file on a share or a link): the copy's
* path, or "" when they're UTF-8 already and VLC can read the original. name:
* the file's name, for the copy's. */
std::string subconv_bytes(const std::string &bytes, const std::string &name, const std::string &lang);
+306 -47
View File
@@ -171,6 +171,9 @@ std::string vlc_message(const std::string &m)
}
if (m.compare(0, 14, "Please provide") == 0)
return "A Windows PC takes its own account; a NAS, the user you made on it.";
/* libbluray without a JVM (none runs on the console): not "install Java" */
if (m.compare(0, 30, "This Blu-ray disc requires Java") == 0)
return "This Blu-ray's menus are made in Java, which the PS5 can't run: it plays without them";
if (m.compare(0, 6, "Track ") == 0 && m.size() > 6 && isdigit((unsigned char)m[6]))
return trf("Track %d", atoi(m.c_str() + 6));
return m;
@@ -1042,14 +1045,73 @@ bool audio_name(const std::string &name)
return false;
}
/* A file on a share by its name, as ci_file() kinds: 0 video, 1 audio,
* 3 text, 4 image, 5 another file (subtitles, databases...). */
int name_kind(const std::string &name)
{
if (audio_name(name))
return 1;
std::string ext = name.substr(name.rfind('.') + 1);
for (char &c : ext)
c = (char)tolower((unsigned char)c);
for (const char *x : { "jpg", "jpeg", "png", "gif", "bmp", "webp", "tif", "tiff" })
if (ext == x)
return 4;
for (const char *x : { "txt", "nfo", "diz", "log", "md" })
if (ext == x)
return 3;
for (const char *x : { "srt", "ass", "ssa", "sub", "idx", "vtt", "sup", "db", "ini", "sfv", "cue", "exe", "pdf" })
if (ext == x)
return 5;
return 0;
}
/* A list of links (IPTV channels, radio), not a stream: .m3u and .pls, and
* .m3u8 unless it's online (an online .m3u8 is usually a live HLS stream). */
bool is_playlist_url(const std::string &url)
{
std::string u = url.substr(0, url.find('?'));
size_t dot = u.rfind('.');
if (dot == std::string::npos || u.find('/', dot) != std::string::npos)
return false;
const char *x = u.c_str() + dot + 1;
if (!strcasecmp(x, "m3u8"))
return u.compare(0, 4, "http") != 0;
return !strcasecmp(x, "m3u") || !strcasecmp(x, "pls");
}
/* file:///abs/path with everything but plain characters escaped. */
std::string file_uri(std::string path)
{
std::string uri = "file://";
if (path[0] != '/') { /* the test build's data folder is relative */
char cwd[1024];
if (getcwd(cwd, sizeof(cwd)))
path = std::string(cwd) + "/" + path;
}
for (unsigned char c : path) {
if (isalnum(c) || strchr("/-_.~", c)) {
uri += (char)c;
} else {
char hex[4];
snprintf(hex, sizeof(hex), "%%%02X", c);
uri += hex;
}
}
return uri;
}
void rebuild_browse()
{
browse_entries.clear();
auto &items = L();
if (!net_path.empty()) {
if (net_list_state() == NET_READY)
for (const NetEntry &e : net_list_entries())
browse_entries.push_back({ e.name, e.url, e.dir, -1, false, e.dir ? NET_DIR : NET_FILE });
for (const NetEntry &e : net_list_entries()) {
/* a channel list on a share opens like a folder */
bool list = !e.dir && is_playlist_url(e.url);
browse_entries.push_back({ e.name, e.url, e.dir, -1, list, e.dir || list ? NET_DIR : NET_FILE });
}
return;
}
if (browse_dir.empty()) {
@@ -1252,9 +1314,26 @@ int current_item()
return m ? (int)(m - &L()[0]) : -1;
}
void net_enter(const std::string &url, const std::string &name);
/* A playlist with links (IPTV channels, radio): its entries in Browse, where
* each one plays as a stream. */
void open_channel_list(const std::string &path)
{
std::string name = path.substr(path.rfind('/') + 1);
name = name.substr(0, name.rfind('.'));
screen = LIBRARY;
tab = BROWSE;
net_enter(file_uri(path), name);
}
/* A playlist file: its files, from the first (or a random one with shuffle). */
void play_playlist(const std::string &path)
{
if (library_playlist_has_links(path)) {
open_channel_list(path);
return;
}
std::vector<int> list = library_playlist_items(path);
if (list.empty()) {
toast("No files of this playlist are in the library", 3);
@@ -2032,6 +2111,23 @@ const std::vector<int> &playlist_items_cached(const std::string &path)
return c.items;
}
/* How many entries a playlist names: its library files, or every line of a
* channel list (links aren't library files). */
int playlist_count_cached(const std::string &path)
{
struct Cached {
double at;
int n;
};
static std::unordered_map<std::string, Cached> cache;
Cached &c = cache[path];
if (c.at == 0 || now - c.at > 3) {
c.n = library_playlist_has_links(path) ? library_playlist_count(path) : (int)playlist_items_cached(path).size();
c.at = now;
}
return c.n;
}
/* A picture covering (x, y, w, h): the thumbnail cropped to that shape. */
void thumb_cover(const MediaItem &m, float x, float y, float w, float h, float round, ImDrawFlags corners)
{
@@ -2118,8 +2214,7 @@ void playlist_card(float x, float y, float w, const PlaylistFile *pl, float f)
if (pl) {
playlist_cover(pl->path, cx, cy, cw, ch, 16);
/* how many, bottom right, like a video's length */
const std::vector<int> &items = playlist_items_cached(pl->path);
std::string count = std::to_string(items.size());
std::string count = std::to_string(playlist_count_cached(pl->path));
float tw = text_size(f_semi, 18, count.c_str()).x;
rect(cx + cw - tw - 54, cy + ch - 40, tw + 44, 28, IM_COL32(0, 0, 0, 170), 8);
icon_list(cx + cw - tw - 38, cy + ch - 26, 16, C_TEXT);
@@ -2164,6 +2259,8 @@ void tab_playlists()
if (pl_focus == 0) {
pl_add_file.clear();
open_playlist_name("");
} else if (library_playlist_has_links(pls[pl_focus - 1].path)) {
open_channel_list(pls[pl_focus - 1].path);
} else {
pl_open = pls[pl_focus - 1].path;
pl_item_focus = 0;
@@ -2394,26 +2491,11 @@ void net_enter(const std::string &url, const std::string &name);
void open_archive(const MediaItem &m)
{
std::string uri = "file://", path = m.path;
if (path[0] != '/') { /* the test build's data folder is relative */
char cwd[1024];
if (getcwd(cwd, sizeof(cwd)))
path = std::string(cwd) + "/" + path;
}
for (unsigned char c : path) {
if (isalnum(c) || strchr("/-_.~", c)) {
uri += (char)c;
} else {
char hex[4];
snprintf(hex, sizeof(hex), "%%%02X", c);
uri += hex;
}
}
tab = BROWSE;
std::string ext = m.ext;
for (char &c : ext)
c = (char)tolower((unsigned char)c);
net_enter(uri, m.name + "." + ext);
net_enter(file_uri(m.path), m.name + "." + ext);
}
/* Opens a network place in Browse (a server, or a folder on it). */
@@ -2426,6 +2508,80 @@ void net_enter(const std::string &url, const std::string &name)
rebuild_browse();
}
/* ---- picking a subtitle file for the video playing (Tracks › Add a
* subtitle file): Browse, from anywhere it reaches, then back to the video */
std::string link_name(const std::string &url);
bool sub_pick;
bool sub_pick_resume; /* it was playing: it plays on after */
std::string sub_pick_dir;
std::vector<std::pair<std::string, std::string>> sub_pick_net;
void start_sub_pick()
{
pressed = 0; /* the ✕ that asked is used up: the player mustn't pause on it too */
sub_pick = true;
sub_pick_resume = !player_paused();
if (sub_pick_resume)
player_toggle_pause();
fprintf(stderr, "ui: picking a subtitle file (%s)\n", sub_pick_resume ? "paused the video" : "the video was paused");
/* what Browse showed before comes back after */
sub_pick_dir = browse_dir;
sub_pick_net = net_path;
modal = NONE;
screen = LIBRARY;
screen_fade = 0;
tab = BROWSE;
library_set_busy(false);
/* It starts at the top (Sources): the media folder, drives and shares
* alike, whatever the video came from; a subtitle can be anywhere. */
net_path.clear();
browse_dir.clear();
net_list_stop();
rebuild_browse();
browse_focus = 0;
browse_scroll = 0;
}
void end_sub_pick(std::string picked) /* a copy: it may be a Browse entry, and Browse is rebuilt below */
{
pressed = 0;
sub_pick = false;
net_path = sub_pick_net;
browse_dir = sub_pick_dir;
if (!net_path.empty())
net_list(net_path.back().first);
else
net_list_stop();
rebuild_browse();
browse_focus = 0;
screen = PLAYER;
screen_fade = 0;
library_set_busy(true);
osd_until = now + 3.5;
/* playing again first: a subtitle added to a paused file left VLC paused
* after the resume */
if (sub_pick_resume && player_paused())
player_toggle_pause();
if (!picked.empty()) {
std::string name = picked.find("://") != std::string::npos ? link_name(picked)
: picked.substr(picked.rfind('/') + 1);
if (player_add_subtitle(picked))
toast(trf("Subtitles: %s", name.c_str()), 2.5);
else
toast("Couldn't load that subtitle file", 2.5);
}
fprintf(stderr, "ui: subtitle pick done (%s)\n", picked.empty() ? "none" : "picked");
}
/* ✕ on a file while picking: a subtitle file goes in, anything else says so. */
void sub_pick_file(std::string path, std::string name) /* copies, as above */
{
if (player_is_subtitle_name(name))
end_sub_pick(path);
else
toast("That isn't a subtitle file (.srt, .ass, .vtt...)", 2.5);
}
/* "1.4 GB" */
std::string size_text(int64_t bytes)
{
@@ -2454,7 +2610,9 @@ void classic_label(const BrowseEntry &e, std::string *name, std::string *type, i
*type = e.path == "tv:" ? "Channel lists (iptv-org)" : "Stations (radio-browser.info)";
*group = 2;
} else if (e.net == NET_DIR) {
*type = "File folder";
*type = e.playlist ? "Playlist" : "File folder";
if (e.playlist && e.name.rfind('.') != std::string::npos && e.name.rfind('.') > 0)
*name = e.name.substr(0, e.name.rfind('.')); /* like local playlists */
} else if (e.net == NET_FILE) {
size_t dot = e.name.rfind('.');
bool has_ext = dot != std::string::npos && dot > 0 && e.name.size() - dot <= 6;
@@ -2464,7 +2622,10 @@ void classic_label(const BrowseEntry &e, std::string *name, std::string *type, i
std::string ext = e.name.substr(dot + 1);
for (char &c : ext)
c = (char)toupper((unsigned char)c);
*type = ext + (audio_name(e.name) ? " audio" : " video");
int k = name_kind(e.name);
*type = k == 3 ? std::string(tr("Text document"))
: k == 5 ? trf("%s file", ext.c_str())
: trf(k == 4 ? "%s image" : k == 1 ? "%s audio" : "%s video", ext.c_str());
} else {
*type = "Live stream";
}
@@ -2490,7 +2651,7 @@ void classic_label(const BrowseEntry &e, std::string *name, std::string *type, i
*type = "File folder";
} else if (e.item >= 0 && L()[e.item].disc) {
const MediaItem &m = L()[e.item];
*type = m.ext == "ISO" ? "Disc image (ISO)" : m.ext == "DVD" ? "DVD (folder)" : "Blu-ray (folder)";
*type = m.ext == "ISO" || m.ext == "IMG" ? "Disc image (ISO)" : m.ext == "DVD" ? "DVD (folder)" : "Blu-ray (folder)";
} else if (e.item >= 0 && L()[e.item].archive) {
*type = trf("Compressed (%s)", L()[e.item].ext.c_str());
} else if (e.item >= 0 && L()[e.item].other) {
@@ -2511,7 +2672,7 @@ void classic_icon(const BrowseEntry &e, float cx, float cy, float s)
else if (e.net == NET_ONLINE)
e.path == "tv:" ? ci_tv(cx, cy, s) : ci_radio(cx, cy, s);
else if (e.net == NET_FILE)
ci_file(cx, cy, s, audio_name(e.name) ? 1 : 0);
ci_file(cx, cy, s, name_kind(e.name));
else if (e.dir && browse_dir.empty() && net_path.empty() && e.net == NET_NONE)
e.path == library_media_dir() ? ci_folder(cx, cy, s) : ci_drive(cx, cy, s, e.path.find("/usb") != std::string::npos);
else if (e.dir)
@@ -2548,7 +2709,7 @@ void browse_draw_classic()
tri(106, ay + ah / 2, 118, ay + ah / 2 - 10, 118, ay + ah / 2 + 10, arrow);
rect(117, ay + ah / 2 - 3.5f, 13, 7, arrow);
/* the path, in segments */
std::vector<std::string> segs = { "Sources" };
std::vector<std::string> segs = { sub_pick ? "Pick a subtitle file" : "Sources" };
/* an archive opened from a folder: the folder's path, then inside it */
bool in_archive = !net_path.empty() && net_path[0].first.compare(0, 7, "file://") == 0;
if (!net_path.empty() && !in_archive)
@@ -2803,6 +2964,14 @@ void tab_browse()
toast(std::string(1, letter_of(names[to])), 0.8);
}
}
if (sub_pick && hit(PAD_CIRCLE) && net_path.empty() && browse_dir.empty()) {
end_sub_pick(""); /* ○ at the top: back to the video, nothing picked */
return;
}
if (sub_pick && hit(PAD_CROSS) && n && browse_entries[browse_focus].net == NET_FILE) {
sub_pick_file(browse_entries[browse_focus].path, browse_entries[browse_focus].name);
return;
}
if (hit(PAD_CROSS) && n && browse_entries[browse_focus].net != NET_NONE) {
BrowseEntry e = browse_entries[browse_focus];
if (e.net == NET_ADD) {
@@ -2840,8 +3009,14 @@ void tab_browse()
rebuild_browse();
return;
}
if (sub_pick) {
std::string path = e.item >= 0 ? L()[e.item].path : e.path;
sub_pick_file(path, path.substr(path.rfind('/') + 1));
return;
}
if (e.playlist) {
play_playlist(e.path);
/* its entries, like a folder: pick a channel or a song */
net_enter(file_uri(e.path), e.name);
return;
}
std::vector<int> list;
@@ -2870,7 +3045,7 @@ void tab_browse()
return;
}
/* Breadcrumb: "Media › Movies", not the raw path. */
std::string crumb = tr("Sources");
std::string crumb = tr(sub_pick ? "Pick a subtitle file" : "Sources");
bool in_archive = !net_path.empty() && net_path[0].first.compare(0, 7, "file://") == 0;
if (!net_path.empty() && !in_archive)
crumb = tr("Network");
@@ -2909,8 +3084,17 @@ void tab_browse()
icon_radio(146, y + 33, 34, mix(C_DIM, C_ORANGE, f));
else if (e.net == NET_ADD)
icon_plus_circle(146, y + 33, 32, mix(C_DIM, C_ORANGE, f));
else if (e.net == NET_FILE)
audio_name(e.name) ? icon_note(146, y + 33, 28, C_DIM) : icon_film(146, y + 33, 34, C_DIM);
else if (e.net == NET_FILE) {
int k = name_kind(e.name);
if (k == 1)
icon_note(146, y + 33, 28, C_DIM);
else if (k == 4)
icon_picture(146, y + 33, 34, C_DIM);
else if (k == 3 || k == 5)
icon_list(146, y + 33, 30, k == 5 ? C_FAINT : C_DIM);
else
icon_film(146, y + 33, 34, C_DIM);
}
else if (e.dir)
icon_folder(146, y + 33, 34, mix(C_DIM, C_ORANGE, f));
else if (e.playlist)
@@ -3389,8 +3573,25 @@ std::string stream_name;
void start_stream(const std::string &url, const std::vector<std::string> &options, bool remember,
const std::string &name, libvlc_media_t *media)
{
if (is_playlist_url(url)) {
/* A channel list (typed, or a file on a share): its entries in Browse. */
if (remember)
remember_link(url);
screen = LIBRARY;
tab = BROWSE;
net_enter(url, name.empty() ? link_name(url) : name);
return;
}
music_bg = false;
stream_options = options;
std::vector<std::string> opts = options;
std::string bare = url.substr(0, url.find('?'));
if (bare.size() > 4 && (!strcasecmp(bare.c_str() + bare.size() - 4, ".iso") || !strcasecmp(bare.c_str() + bare.size() - 4, ".img")) && url.compare(0, 7, "file://") != 0 &&
std::find(opts.begin(), opts.end(), ":demux=dvd,any") == opts.end())
/* A disc image on a share or a link: VLC's DVD reader only tries it by
* itself on fast-seeking sources (not SMB), so ask for it first; a
* Blu-ray image falls through to the Blu-ray reader ("any"). */
opts.push_back(":demux=dvd,any");
stream_options = opts;
stream_remember = remember;
stream_name = name;
playing_path = url;
@@ -3402,11 +3603,11 @@ void start_stream(const std::string &url, const std::vector<std::string> &option
library_set_busy(true);
bool opened;
if (media) {
for (const std::string &o : options)
for (const std::string &o : opts)
libvlc_media_add_option(media, o.c_str());
opened = player_open_media(media, url, 0);
opened = player_open_media(media, url, 0, opts);
} else {
opened = player_open(url, 0, options);
opened = player_open(url, 0, opts);
}
if (!opened) {
toast("Couldn't open that link", 3);
@@ -4558,13 +4759,13 @@ void setup_begin(bool with_logo);
enum {
ST_DECODING, ST_ALANG, ST_SLANG, ST_SOUND, ST_NIGHT, ST_SPEAKERS, ST_THEME, ST_HAPTICS,
ST_SWIPES, ST_REPEAT, ST_SHUFFLE, ST_DEBUG, ST_STORAGE, ST_VERSION, ST_RELOAD, ST_ABOUT,
ST_WEB, ST_LOOK, ST_OSUB, ST_UILANG, ST_SETUP, ST_COUNT
ST_WEB, ST_LOOK, ST_OSUB, ST_UILANG, ST_SETUP, ST_BDREGION, ST_COUNT
};
const char *const st_labels[ST_COUNT] = {
"Decoding", "Audio language", "Subtitle language", "Sound output", "Night mode",
"Speaker test", "Theme", "Controller vibration", "Touchpad swipes", "Repeat the list", "Shuffle",
"Detailed log", "Storage", "Version", "Reload drives", "About VLC", "Phone access", "Look",
"Subtitle downloads", "Language", "Run setup again",
"Subtitle downloads", "Language", "Run setup again", "Blu-ray region",
};
/* One line under the focused setting: what it does. */
const char *const st_help[ST_COUNT] = {
@@ -4589,6 +4790,7 @@ const char *const st_help[ST_COUNT] = {
"Your own free OpenSubtitles API key (and account), here or from the phone page",
"The language of VLC's menus",
"Language, look and playback, chosen again",
"A: the Americas and East Asia. B: Europe, Africa and Oceania. C: China, Russia and India",
};
/* Categories: an icon, a name, and their settings. */
@@ -4599,7 +4801,7 @@ struct SettingsCat {
};
enum { SI_PLAY, SI_SOUND, SI_THEME, SI_LIST, SI_GEAR };
const SettingsCat settings_cats[] = {
{ "Playback", SI_PLAY, { ST_DECODING, ST_ALANG, ST_SLANG, ST_OSUB } },
{ "Playback", SI_PLAY, { ST_DECODING, ST_ALANG, ST_SLANG, ST_OSUB, ST_BDREGION } },
{ "Sound", SI_SOUND, { ST_SOUND, ST_NIGHT, ST_SPEAKERS } },
{ "Interface", SI_THEME, { ST_UILANG, ST_LOOK, ST_THEME, ST_SWIPES } },
{ "Playlists", SI_LIST, { ST_REPEAT, ST_SHUFFLE } },
@@ -4724,6 +4926,7 @@ void setting_icon(int id, float cx, float cy, float s, ImU32 col)
case ST_DECODING: si_chip(cx, cy, s, col); break;
case ST_ALANG: case ST_UILANG: si_globe(cx, cy, s, col); break;
case ST_SLANG: case ST_OSUB: icon_cc(cx, cy, s * 0.9f, col); break;
case ST_BDREGION: si_globe(cx, cy, s, col); break;
case ST_SOUND: icon_speaker(cx, cy, s * 0.85f, col); break;
case ST_NIGHT: si_moon(cx, cy, s, col); break;
case ST_SPEAKERS: si_surround(cx, cy, s, col); break;
@@ -4794,6 +4997,7 @@ std::string st_value(int id)
}
case ST_WEB: return web_running() ? "On" : "Off";
case ST_ABOUT: return "v" VLC_PS5_VERSION;
case ST_BDREGION: return trf("Region %s", player_bluray_region().c_str());
default: return "";
}
}
@@ -4807,6 +5011,14 @@ void st_change(int id, int step)
break;
case ST_ALANG: cycle_language(false, dir); break;
case ST_SLANG: cycle_language(true, dir); break;
case ST_BDREGION: {
static const char *const regions[3] = { "A", "B", "C" };
std::string r = player_bluray_region();
int i = r == "B" ? 1 : r == "C" ? 2 : 0;
player_set_bluray_region(regions[(i + 3 + dir) % 3]);
toast("Blu-ray region: from the next disc you open", 2.5);
break;
}
case ST_SOUND:
player_set_audio_output(!player_audio_output());
toast(player_audio_output() ? "Surround: 5.1 / 7.1 files from the next one you open" : "Stereo from the next file", 3);
@@ -5414,12 +5626,24 @@ struct PanelEntry {
std::string shown; /* kind 7: the value, right-aligned */
};
/* A disc plays: from the library, dvd:// / bluray://, or an .iso / .img
* from a share or a link. */
bool playing_disc()
{
int cur_item = current_item();
std::string bare = playing_path.substr(0, playing_path.find('?'));
return (cur_item >= 0 && L()[cur_item].disc) || playing_path.compare(0, 6, "dvd://") == 0 ||
playing_path.compare(0, 9, "bluray://") == 0 ||
(bare.size() > 4 && (!strcasecmp(bare.c_str() + bare.size() - 4, ".iso") || !strcasecmp(bare.c_str() + bare.size() - 4, ".img")));
}
/* The settings rows (PanelEntry kind 7). */
enum {
SET_AUDIO_DELAY, SET_EQ, SET_SUB_DELAY, SET_SUB_SIZE, SET_SUB_COLOUR, SET_SUB_BOX, SET_SLEEP,
SET_LOOP, SET_DEINT, SET_BRIGHT, SET_CONTRAST, SET_SAT, SET_GAMMA, SET_HUE, SET_ROTATE, SET_FLIP,
SET_SHARPEN, SET_PIC_RESET,
SET_SOUND_OUT, SET_NIGHT, SET_REPEAT, SET_SHUFFLE, SET_BOOKMARK, SET_BM_CLEAR, SET_OSUB,
SET_DISC_MENU, SET_POPUP, SET_SUB_PICK,
};
std::string ms_text(int64_t ms)
@@ -5503,6 +5727,18 @@ void setting_change(int id, int step)
if (step == 0)
open_osub_results();
return;
case SET_SUB_PICK:
if (step == 0)
start_sub_pick();
return;
case SET_DISC_MENU:
case SET_POPUP:
/* the menu shows over the film: the panel gets out of its way */
if (step == 0) {
player_disc_menu(id == SET_POPUP);
modal = NONE;
}
return;
case SET_AUDIO_DELAY:
player_set_audio_delay(step ? player_audio_delay() + step * 50 : 0);
break;
@@ -5664,12 +5900,18 @@ void modal_tracks()
}
for (const std::string &f : files)
e.push_back({ 6, 0, trf("Load %s", f.substr(f.rfind('/') + 1).c_str()), false, f });
e.push_back({ 7, SET_SUB_PICK, "Add a subtitle file", false, "" });
e.push_back({ 7, SET_OSUB, "Download subtitles", false, "" });
}
if (all || panel_section == SEC_SPEED) {
e.push_back({ 0, 0, "Playback", false, "" });
e.push_back({ 7, SET_REPEAT, "Loop this video", false, "" });
e.push_back({ 7, SET_SHUFFLE, "Shuffle", false, "" });
if (playing_disc()) {
e.push_back({ 7, SET_DISC_MENU, "Disc menu", false, "" });
if (player_disc_has_popup())
e.push_back({ 7, SET_POPUP, "Pop-up menu", false, "" });
}
e.push_back({ 7, SET_SLEEP, "Sleep timer", false, "" });
e.push_back({ 7, SET_LOOP, "A-B repeat", false, "" });
e.push_back({ 7, SET_BOOKMARK, "Add a bookmark here", false, "" });
@@ -5852,7 +6094,7 @@ void modal_tracks()
const char *act = id == SET_LOOP ? (loop_a < 0 ? "Mark A" : loop_b < 0 ? "Mark B" : "Clear")
: id == SET_SUB_BOX || id == SET_NIGHT || id == SET_SOUND_OUT || id == SET_SHUFFLE ? "Switch"
: id == SET_REPEAT ? "Switch" : id == SET_BOOKMARK ? "Add"
: id == SET_BM_CLEAR ? "Clear" : "Reset";
: id == SET_BM_CLEAR ? "Clear" : id == SET_DISC_MENU || id == SET_POPUP || id == SET_SUB_PICK ? "Open" : "Reset";
hints(1920 - 48, 1040, { { PAD_CROSS, act }, { PAD_CIRCLE, "Close" } }, a);
}
else
@@ -6614,16 +6856,23 @@ void player_screen()
}
/* A disc: the d-pad and ✕ drive its menu while the controls are hidden
* (the touchpad brings them up). */
int cur_item = current_item();
bool disc = (cur_item >= 0 && L()[cur_item].disc) || playing_path.compare(0, 6, "dvd://") == 0 ||
playing_path.compare(0, 9, "bluray://") == 0;
bool disc = playing_disc();
const uint32_t menu_keys = PAD_UP | PAD_DOWN | PAD_LEFT | PAD_RIGHT | PAD_CROSS;
bool menu_press = disc && modal == NONE && !osd_was_up && !paused && (pressed & menu_keys);
/* A disc menu with buttons on screen (in the film ✕ pauses as usual): the
* D-pad and ✕ press its buttons straight away, and the controls stay out
* of the way unless the touchpad asked for them. */
static bool osd_touch; /* the controls were brought up with the touchpad */
if (now >= osd_until)
osd_touch = false;
bool in_menu = disc && player_in_menu() && modal == NONE && !paused;
if (in_menu && !osd_touch)
osd_until = std::min(osd_until, now);
bool menu_press = in_menu && !(osd_was_up && osd_touch) && (pressed & menu_keys);
if (menu_press) {
player_navigate(hit(PAD_CROSS) ? 0 : hit(PAD_UP) ? 1 : hit(PAD_DOWN) ? 2 : hit(PAD_LEFT) ? 3 : 4);
pressed &= ~menu_keys;
}
if (pressed && modal == NONE)
if (pressed && modal == NONE && (!in_menu || osd_touch))
osd_until = now + 4.0;
bool osd = now < osd_until || paused || scrubbing;
@@ -6652,6 +6901,7 @@ void player_screen()
return;
} else if (hit(PAD_TOUCHPAD)) {
osd_until = osd ? now : now + 4.0;
osd_touch = !osd;
} else if (hit(PAD_L1) || hit(PAD_R1)) {
std::vector<Chapter> ch = player_chapters();
if (ch.empty()) {
@@ -7592,7 +7842,9 @@ void ui_frame(const PadState &pad, float frame_dt)
} else {
background();
tab_bar();
if (modal == NONE) {
if (sub_pick)
tab = BROWSE; /* picking: Browse only */
if (modal == NONE && !sub_pick) {
if (hit(PAD_L1))
tab = (Tab)((tab + TABS - 1) % TABS);
if (hit(PAD_R1))
@@ -7601,12 +7853,12 @@ void ui_frame(const PadState &pad, float frame_dt)
uint32_t keep = pressed;
if (modal != NONE)
pressed = 0;
if (modal == NONE && hit(PAD_R3)) {
if (modal == NONE && !sub_pick && hit(PAD_R3)) {
open_search();
modal_anim = 1;
pressed = 0;
}
if (modal == NONE)
if (modal == NONE && !sub_pick)
library_actions();
switch (tab) {
case HOME: tab_home(); break;
@@ -7640,8 +7892,15 @@ void ui_frame(const PadState &pad, float frame_dt)
h.push_back({ PAD_CIRCLE, "Back" });
if (tab == PLAYLISTS)
h = playlist_hints();
h.push_back({ PAD_R3, "Search" });
h.push_back({ PAD_OPTIONS, "Options" });
if (sub_pick) {
bool file = browse_focus < (int)browse_entries.size() && !browse_entries[browse_focus].dir &&
(browse_entries[browse_focus].net == NET_FILE || browse_entries[browse_focus].net == NET_NONE);
h = { { PAD_CROSS, file ? "Add" : "Open" },
{ PAD_CIRCLE, browse_dir.empty() && net_path.empty() ? "Cancel" : "Back" } };
} else {
h.push_back({ PAD_R3, "Search" });
h.push_back({ PAD_OPTIONS, "Options" });
}
if (classic_look) {
if (tab == PLAYLISTS) {
if (pl_open.empty())