Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4db470e08a | ||
|
|
cf3819ece9 | ||
|
|
575b631d67 | ||
|
|
4c1b45b5ab | ||
|
|
8932d90ec6 | ||
|
|
fc4da23c64 | ||
|
|
017c2808e4 | ||
|
|
c5a8fc793c | ||
|
|
3dcd3476c6 | ||
|
|
ad119460d7 | ||
|
|
e865543be2 | ||
|
|
e84a00651a | ||
|
|
b17ccd1c43 | ||
|
|
62d922e4f0 | ||
|
|
eaa5393c4a | ||
|
|
50b74bf407 | ||
|
|
414cdc9da6 | ||
|
|
1c4bdfaf9c | ||
|
|
76809e5ef7 | ||
|
|
efc72daea9 | ||
|
|
b414d9a9a7 | ||
|
|
2b737f8bfc | ||
|
|
9e99a0c8b9 | ||
|
|
e22ecb1585 | ||
|
|
6358e9be5c | ||
|
|
d57a48c64e | ||
|
|
22691f2e6a | ||
|
|
623a259eee | ||
|
|
fa81e835bc | ||
|
|
46cc45223c | ||
|
|
0e160123fd | ||
|
|
710979e0e8 | ||
|
|
18d3c2ab85 | ||
|
|
f84ca3a516 | ||
|
|
324a40d2e1 | ||
|
|
83e98d3ada | ||
|
|
a424fe1296 | ||
|
|
e09803ae6b | ||
|
|
80669188d6 | ||
|
|
1a3a3aba63 | ||
|
|
0b141eaa82 | ||
|
|
cac071dc23 | ||
|
|
c77f076ae4 | ||
|
|
f4393eaa6c | ||
|
|
bbd6501bd3 | ||
|
|
dc29a21b5c | ||
|
|
4d87844508 | ||
|
|
cced4bf4d6 | ||
|
|
5734cd7063 | ||
|
|
52324a494c | ||
|
|
7ee8237e66 | ||
|
|
6fc0b4e24b | ||
|
|
3d3b1b8bba | ||
|
|
21a90cc49c | ||
|
|
83119f05c4 | ||
|
|
081ddd26b4 | ||
|
|
78349cc188 | ||
|
|
931f614257 | ||
|
|
e682d82527 | ||
|
|
76fd4b4e33 | ||
|
|
623b0a77e1 | ||
|
|
c6c4c3ea9c | ||
|
|
8e5a854374 | ||
|
|
90a0add131 | ||
|
|
443fd456a1 | ||
|
|
910da3af6a | ||
|
|
517f15dc49 | ||
|
|
62cf557b5f | ||
|
|
95e1952377 | ||
|
|
90126b9f88 | ||
|
|
01223a3e0f | ||
|
|
76b0291bf2 | ||
|
|
3fc0c18997 | ||
|
|
7fe9126bba | ||
|
|
22863d6a79 | ||
|
|
5ccd91d639 | ||
|
|
37bf8de258 | ||
|
|
2d3e4fb1b9 | ||
|
|
78cd25388c | ||
|
|
32f0adaeae | ||
|
|
70a7d9d2c5 | ||
|
|
cf14511b1d | ||
|
|
a6d57d654a | ||
|
|
63671a4fbb | ||
|
|
8ace70cfa6 | ||
|
|
1e0edb3e6f | ||
|
|
4ed46bfa25 | ||
|
|
423d47130a | ||
|
|
7a02593dc4 | ||
|
|
220d28b902 | ||
|
|
ff10bd65d6 | ||
|
|
e2895e9684 | ||
|
|
83be06c0ad | ||
|
|
fd7077ade9 | ||
|
|
d40ccc3416 | ||
|
|
57f5ad445d | ||
|
|
a5458a624d | ||
|
|
edf0b93703 | ||
|
|
6bf46eae23 | ||
|
|
774e818371 | ||
|
|
352c00fd08 | ||
|
|
589b38c187 | ||
|
|
62bc8442be | ||
|
|
0edd08a1f6 | ||
|
|
7f5378680b | ||
|
|
4753a92b80 | ||
|
|
584a27e3b8 | ||
|
|
b4abc0a037 | ||
|
|
192318a216 | ||
|
|
822e6e0dd1 | ||
|
|
45b63c78cd | ||
|
|
de47273716 | ||
|
|
7ace4f2115 | ||
|
|
f67dfcbec6 | ||
|
|
6b9f7923af | ||
|
|
c36b1d01c1 | ||
|
|
07222d14f9 | ||
|
|
28fbee7f03 | ||
|
|
b3d2daaa1d | ||
|
|
3437ea23f6 | ||
|
|
16856994e9 | ||
|
|
c22c440c9c | ||
|
|
be42f4c212 | ||
|
|
1b5f127cb0 | ||
|
|
7ea82eb7bc | ||
|
|
3be989dcd0 | ||
|
|
0adc8ee5c1 | ||
|
|
55fe8aab6a | ||
|
|
b2aa388f27 | ||
|
|
fea1dc0cd5 | ||
|
|
8161b47e73 | ||
|
|
f113e03b89 | ||
|
|
630a7b7d82 | ||
|
|
a4e766a77f | ||
|
|
57deaa02d5 | ||
|
|
a6accfdad7 | ||
|
|
8813fe090d | ||
|
|
72ba8515d5 | ||
|
|
21f5d82dc5 | ||
|
|
8c992458c3 | ||
|
|
40149c017c |
No files matched your search
@@ -25,9 +25,10 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
git - the --msg-level option changes its native type from a flat string to
|
||||
a key-value list (setting/reading the option as string still works)
|
||||
--- mpv 0.8.0 is released ---
|
||||
1.14 - add mpv_wait_async_requests()
|
||||
- the --msg-level option changes its native type from a flat string to
|
||||
a key-value list (setting/reading the option as string still works)
|
||||
1.13 - add MPV_EVENT_QUEUE_OVERFLOW
|
||||
1.12 - add class Handle to qthelper.hpp
|
||||
- improve opengl_cb.h API uninitialization behavior, and fix the qml
|
||||
|
||||
@@ -33,6 +33,7 @@ static inline void check_error(int status)
|
||||
mpv_handle *mpv;
|
||||
dispatch_queue_t queue;
|
||||
NSWindow *w;
|
||||
NSView *wrapper;
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -55,11 +56,19 @@ static void wakeup(void *);
|
||||
[self->w makeMainWindow];
|
||||
[self->w makeKeyAndOrderFront:nil];
|
||||
|
||||
NSRect frame = [[self->w contentView] bounds];
|
||||
self->wrapper = [[NSView alloc] initWithFrame:frame];
|
||||
[self->wrapper setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
|
||||
[[self->w contentView] addSubview:self->wrapper];
|
||||
[self->wrapper release];
|
||||
|
||||
NSMenu *m = [[NSMenu alloc] initWithTitle:@"AMainMenu"];
|
||||
NSMenuItem *item = [m addItemWithTitle:@"Apple" action:nil keyEquivalent:@""];
|
||||
NSMenu *sm = [[NSMenu alloc] initWithTitle:@"Apple"];
|
||||
[m setSubmenu:sm forItem:item];
|
||||
[sm addItemWithTitle: @"Shutdown mpv" action:@selector(shutdown) keyEquivalent:@"s"];
|
||||
[sm addItemWithTitle: @"mpv_command('stop')" action:@selector(mpv_stop) keyEquivalent:@""];
|
||||
[sm addItemWithTitle: @"mpv_command('quit')" action:@selector(mpv_quit) keyEquivalent:@""];
|
||||
[sm addItemWithTitle: @"quit" action:@selector(terminate:) keyEquivalent:@"q"];
|
||||
[NSApp setMenu:m];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
@@ -93,7 +102,7 @@ static void wakeup(void *);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int64_t wid = (intptr_t) [self->w contentView];
|
||||
int64_t wid = (intptr_t) self->wrapper;
|
||||
check_error(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid));
|
||||
|
||||
// Maybe set some options here, like default key bindings.
|
||||
@@ -122,24 +131,28 @@ static void wakeup(void *);
|
||||
- (void) handleEvent:(mpv_event *)event
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_SHUTDOWN:
|
||||
// Clean up and shut down.
|
||||
mpv_terminate_destroy(mpv);
|
||||
case MPV_EVENT_SHUTDOWN: {
|
||||
mpv_detach_destroy(mpv);
|
||||
mpv = NULL;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSApplication sharedApplication] terminate:nil];
|
||||
});
|
||||
printf("event: shutdown\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case MPV_EVENT_LOG_MESSAGE: {
|
||||
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
|
||||
printf("[%s] %s: %s", msg->prefix, msg->level, msg->text);
|
||||
}
|
||||
|
||||
case MPV_EVENT_VIDEO_RECONFIG:
|
||||
case MPV_EVENT_VIDEO_RECONFIG: {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self->w selectNextKeyView:nil];
|
||||
NSArray *subviews = [self->wrapper subviews];
|
||||
if ([subviews count] > 0) {
|
||||
// mpv's events view
|
||||
NSView *eview = [self->wrapper subviews][0];
|
||||
[self->w makeFirstResponder:eview];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
printf("event: %s\n", mpv_event_name(event->event_id));
|
||||
@@ -166,13 +179,18 @@ static void wakeup(void *context) {
|
||||
// Ostensibly, mpv's window would be hooked up to this.
|
||||
- (BOOL) windowShouldClose:(id)sender
|
||||
{
|
||||
[self shutdown];
|
||||
if (self->w)
|
||||
[self->w release];
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) shutdown
|
||||
- (void) mpv_stop
|
||||
{
|
||||
if (mpv) {
|
||||
const char *args[] = {"stop", NULL};
|
||||
mpv_command(mpv, args);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) mpv_quit
|
||||
{
|
||||
if (mpv) {
|
||||
const char *args[] = {"quit", NULL};
|
||||
|
||||
@@ -30,7 +30,7 @@ int main(int argc, char *argv[])
|
||||
// Enable default key bindings, so the user can actually interact with
|
||||
// the player (and e.g. close the window).
|
||||
check_error(mpv_set_option_string(ctx, "input-default-bindings", "yes"));
|
||||
mpv_set_option_string(ctx, "input-x11-keyboard", "yes");
|
||||
mpv_set_option_string(ctx, "input-vo-keyboard", "yes");
|
||||
int val = 1;
|
||||
check_error(mpv_set_option(ctx, "osc", MPV_FORMAT_FLAG, &val));
|
||||
|
||||
|
||||
@@ -66,9 +66,9 @@ echo "MXE_TARGETS := i686-w64-mingw32.static" >> settings.mk
|
||||
# echo "MXE_TARGETS := x86_64-w64-mingw32.static" >> settings.mk
|
||||
|
||||
# Build required packages. The following provide a minimum required to build
|
||||
# mpv.
|
||||
# a reasonable mpv binary (though not an absolute minimum).
|
||||
|
||||
make gcc ffmpeg libass jpeg pthreads
|
||||
make gcc ffmpeg libass jpeg pthreads lua
|
||||
|
||||
# Add MXE binaries to $PATH
|
||||
export PATH=/opt/mxe/usr/bin/:$PATH
|
||||
|
||||
@@ -118,6 +118,8 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
very_long_condition_b)
|
||||
{
|
||||
code();
|
||||
} else {
|
||||
...
|
||||
}
|
||||
```
|
||||
- Remove any trailing whitespace.
|
||||
|
||||
+4
-3
@@ -565,9 +565,10 @@ Available filters are:
|
||||
Scale tempo in sync with speed (default).
|
||||
pitch
|
||||
Reverses effect of filter. Scales pitch without altering tempo.
|
||||
Add ``[ multiply speed 0.9438743126816935`` and
|
||||
``] multiply speed 1.059463094352953`` to your ``input.conf``
|
||||
to step by musical semi-tones.
|
||||
Add this to your ``input.conf`` to step by musical semi-tones::
|
||||
|
||||
[ multiply speed 0.9438743126816935
|
||||
] multiply speed 1.059463094352953
|
||||
|
||||
.. warning::
|
||||
|
||||
|
||||
+33
-3
@@ -4,9 +4,7 @@ JSON IPC
|
||||
mpv can be controlled by external programs using the JSON-based IPC protocol. It
|
||||
can be enabled by specifying the path to a unix socket using the option
|
||||
``--input-unix-socket``. Clients can connect to this socket and send commands to
|
||||
the player or receive events from it. You can use the ``socat`` tool to send
|
||||
commands (and receive reply) from the shell. See the ``--idle`` option how to
|
||||
make mpv start without exiting immediately or playing a file.
|
||||
the player or receive events from it.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -16,6 +14,38 @@ make mpv start without exiting immediately or playing a file.
|
||||
which can run arbitrary system commands. The use-case is controlling the
|
||||
player locally. This is not different from the MPlayer slave protocol.
|
||||
|
||||
Socat example
|
||||
-------------
|
||||
|
||||
You can use the ``socat`` tool to send commands (and receive reply) from the
|
||||
shell. Assuming mpv was started with:
|
||||
|
||||
::
|
||||
|
||||
mpv file.mkv --input-unix-socket=/tmp/mpvsocket
|
||||
|
||||
Then you can control it using socat:
|
||||
|
||||
::
|
||||
|
||||
> echo '{ "command": ["get_property", "playback-time"] }' | socat - /tmp/mpvsocket
|
||||
{"data":190.482000,"error":"success"}
|
||||
|
||||
In this case, socat copies data between stdin/stdout and the mpv socket
|
||||
connection.
|
||||
|
||||
See the ``--idle`` option how to make mpv start without exiting immediately or
|
||||
playing a file.
|
||||
|
||||
It's also possible to send input.conf style text-only commands:
|
||||
|
||||
::
|
||||
|
||||
> echo 'show_text ${playback-time}' | socat - /tmp/mpvsocket
|
||||
|
||||
But you won't get a reply over the socket. (This particular command shows the
|
||||
playback time on the player's OSD.)
|
||||
|
||||
Protocol
|
||||
--------
|
||||
|
||||
|
||||
+1
-1
@@ -495,7 +495,7 @@ Example implementation::
|
||||
optionB = -0.5,
|
||||
optionC = true,
|
||||
}
|
||||
options.read_options(options, "myscript")
|
||||
read_options(options, "myscript")
|
||||
print(option.optionA)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -517,7 +517,7 @@ listed.
|
||||
- Cache state, e.g. ``Cache: 2s+134KB``. Visible if the stream cache is enabled.
|
||||
The first value shows the amount of video buffered in the demuxer in seconds,
|
||||
the second value shows *additional* data buffered in the stream cache in
|
||||
kilobytes. (``demuxer-cache-duration`` and ``cached-used`` properties.)
|
||||
kilobytes. (``demuxer-cache-duration`` and ``cache-used`` properties.)
|
||||
|
||||
|
||||
PROTOCOLS
|
||||
|
||||
@@ -104,8 +104,9 @@ Playback Control
|
||||
``--speed=<0.01-100>``
|
||||
Slow down or speed up playback by the factor given as parameter.
|
||||
|
||||
If ``--audio-pitch-correction`` is used, playing with a speed higher than
|
||||
normal automatically inserts the ``scaletempo`` audio filter.
|
||||
If ``--audio-pitch-correction`` (on by default) is used, playing with a
|
||||
speed higher than normal automatically inserts the ``scaletempo`` audio
|
||||
filter.
|
||||
|
||||
``--loop=<N|inf|force|no>``
|
||||
Loops playback ``N`` times. A value of ``1`` plays it one time (default),
|
||||
@@ -551,7 +552,8 @@ Video
|
||||
|
||||
The ``vaapi-copy`` mode allows you to use vaapi with any VO. Because
|
||||
this copies the decoded video back to system RAM, it's likely less efficient
|
||||
than the ``vaapi`` mode.
|
||||
than the ``vaapi`` mode. But there are reports that this is actually faster
|
||||
as well, and avoids many issues with ``vaapi``.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
+2
-2
@@ -143,8 +143,8 @@ Available filters are:
|
||||
Accurate rounding for the vertical scaler, which may be faster or
|
||||
slower than the default rounding.
|
||||
|
||||
:0: Disable accurate rounding (default).
|
||||
:1: Enable accurate rounding.
|
||||
:no: Disable accurate rounding (default).
|
||||
:yes: Enable accurate rounding.
|
||||
|
||||
``dsize[=w:h:aspect-method:r:aspect]``
|
||||
Changes the intended display size/aspect at an arbitrary point in the
|
||||
|
||||
+29
-9
@@ -419,7 +419,8 @@ Available video output drivers are:
|
||||
|
||||
``swapinterval=<n>``
|
||||
Interval in displayed frames between two buffer swaps.
|
||||
1 is equivalent to enable VSYNC, 0 to disable VSYNC.
|
||||
1 is equivalent to enable VSYNC, 0 to disable VSYNC. Defaults to 1 if
|
||||
not specified.
|
||||
|
||||
``cscale=<filter>``
|
||||
As ``scale``, but for interpolating chroma information. If the image
|
||||
@@ -590,16 +591,27 @@ Available video output drivers are:
|
||||
See ``--osd-color`` option how colors are defined.
|
||||
|
||||
``smoothmotion``
|
||||
Use frame interpolation to reduce stuttering caused by mismatches in
|
||||
video fps and display refresh rate.
|
||||
Reduce stuttering caused by mismatches in video fps and display
|
||||
refresh rate (also known as judder).
|
||||
|
||||
Instead of drawing each frame exactly once, smoothmotion redraws the
|
||||
the OpenGL scene at the display refresh rate. If a vsync is detected
|
||||
to be the one when a frame changes to the next, a linear interpolation
|
||||
of the previous frame with next is shown instead.
|
||||
Instead of drawing source frames for variable durations, smoothmotion
|
||||
will blend frames that overlap the transition between two frames in
|
||||
the source material.
|
||||
|
||||
This means that displaying a 1fps video on a 60hz monitor will blend
|
||||
at most during 1 vsync for each second of playback.
|
||||
For example, a 24 Hz clip played back on a 60 Hz display would normally
|
||||
result in a pattern like this::
|
||||
|
||||
A A A B B C C C D D E E E F F
|
||||
|
||||
which has different lengths, alternating between 3 and 2. This
|
||||
difference in frame duration is what causes judder.
|
||||
|
||||
With smoothmotion enabled, the pattern changes to::
|
||||
|
||||
A A A+B B B C C C+D D D E E E+F F F
|
||||
|
||||
where A+B is a blend of A and B. In this pattern, each frame gets a
|
||||
(consistent) duration of roughly 2.5 - resulting in smooth motion.
|
||||
|
||||
GPU drivers or compositing window managers overriding vsync behavior
|
||||
can lead to bad results. If the framerate is close to or over the
|
||||
@@ -612,6 +624,14 @@ Available video output drivers are:
|
||||
Mix threshold at which interpolation is skipped (default: 0.0 – never
|
||||
skip).
|
||||
|
||||
For example, with a ``smoothmotion-threshold`` of 0.1, if the
|
||||
smoothmotion algorithm would try to blend two frames by a ratio of
|
||||
95% A + 5% B, it would simply display A instead. (Since the
|
||||
distance, 0.05, is lower than the threshold)
|
||||
|
||||
Setting this to 0.5 would be similar to disabling smoothmotion
|
||||
completely, since it would always just display the nearest frame.
|
||||
|
||||
``opengl-hq``
|
||||
Same as ``opengl``, but with default settings for high quality rendering.
|
||||
|
||||
|
||||
@@ -7,8 +7,13 @@ Overview
|
||||
**mpv** is a media player based on MPlayer and mplayer2. It supports a wide
|
||||
variety of video file formats, audio and video codecs, and subtitle types.
|
||||
|
||||
If you are wondering what's different from mplayer2 and MPlayer you can read
|
||||
more about the [changes][changes].
|
||||
Releases can be found on the [release list][releases].
|
||||
|
||||
Downloads
|
||||
---------
|
||||
|
||||
For semi-official builds and third-party packages please see
|
||||
[mpv.io](http://mpv.io/installation/).
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
@@ -124,6 +129,28 @@ For small changes you can just send us pull requests through GitHub. For bigger
|
||||
changes come and talk to us on IRC before you start working on them. It will
|
||||
make code review easier for both parties later on.
|
||||
|
||||
Relation to MPlayer and mplayer2
|
||||
--------------------------------
|
||||
|
||||
mpv is based on mplayer2, which in turn is based on the original MPlayer
|
||||
(also called mplayer, mplayer-svn, mplayer1). Many changes have been made, a
|
||||
large part of which is incompatible or completely changes how the player
|
||||
behaves. Although there are still many similarities to its ancestors, mpv
|
||||
should generally be treated as a completely different program.
|
||||
|
||||
mpv was forked because we wanted to modernize MPlayer. This includes
|
||||
removing cruft (including features which stopped making sense 10 years ago),
|
||||
and of course adding modern features. Such huge and intrusive changes made it
|
||||
infeasible to work directly with MPlayer, which is mostly focused on
|
||||
preservation, so a fork had to be made. (Actually, mpv is based on mplayer2,
|
||||
which already started this process of removing cruft.)
|
||||
|
||||
In general, mpv should be considered a completely new program, rather than a
|
||||
MPlayer drop-in replacement.
|
||||
|
||||
If you are wondering what's different from mplayer2 and MPlayer, an incomplete
|
||||
list of changes is located [here][mplayer-changes].
|
||||
|
||||
Contact
|
||||
-------
|
||||
|
||||
@@ -139,7 +166,7 @@ mailing lists are mostly unused.
|
||||
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
|
||||
only if discretion is required.
|
||||
|
||||
[changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/man/changes.rst
|
||||
[releases]: https://github.com/mpv-player/mpv/releases
|
||||
[mpv-build]: https://github.com/mpv-player/mpv-build
|
||||
[homebrew-mpv]: https://github.com/mpv-player/homebrew-mpv
|
||||
[issue-tracker]: https://github.com/mpv-player/mpv/issues
|
||||
@@ -148,3 +175,4 @@ only if discretion is required.
|
||||
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
[mplayer-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/mplayer-changes.rst
|
||||
+70
-1
@@ -1,3 +1,72 @@
|
||||
Release 0.8.3
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Documentation fixes and updates.
|
||||
- (Client API) Clarify `mpv_opengl_cb_render`'s viewport parameter behavior.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- (X11) Fix crash on `vo_xv:no-colorkey` with the Overlay adapter. (bug #1629)
|
||||
- Make the video equalizer work correctly on some VAAPI drivers. (bug #1647)
|
||||
- Prevent OSD from disappearing when clicking on mozplugger. (bug #1672)
|
||||
- The new DVB-S2 support code, using S2API, now builds on FreeBSD.
|
||||
- Fix decoding of seekable matroska from unseekable network stream. (bug #1656)
|
||||
- (OSX) Fix crashing when closing a VO's window (usually at exit). (bug #1657)
|
||||
- (OSX) Unhide the mouse cursor when over the Dock or Launchpad. (bug #513)
|
||||
- (OSX) Fix mouse cursor autohiding when the player is fullscreen.
|
||||
- `vf_vavpp` (VAAPI postprocessing) now deinterlaces video correctly.
|
||||
- `smb://` streams now use the stream cache (used to be inefficient).
|
||||
- (Windows) Do not hide the mouse cursor when it is hovering the window menu.
|
||||
- Fix 8-channel output on `ao_jack`. (bug #1688)
|
||||
- Fix `--mf-fps` parameter on JPEG files. (bug #1689)
|
||||
- Fix anamorphic scaling being ignored if it was very minor.
|
||||
|
||||
This listing is not complete. A complete changelog can be seen by running
|
||||
`git log v0.8.2..v0.8.3` in the git repository.
|
||||
|
||||
Release 0.8.2
|
||||
=============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fix OSD placement accidentally messed up in 0.8.1.
|
||||
|
||||
Release 0.8.1
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
- DOCS/client-api-changes has been correctly updated for the v0.8.0 release
|
||||
series.
|
||||
- Quality and major perfomance improvements to smoothmotion.
|
||||
- Silence the "[ytdl_hook] WARNING: video doesn't have subtitles" warning.
|
||||
- Other documentation updates.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Attempt to fix OpenGL shader compilation on Intel windows drivers. (bug
|
||||
#1536)
|
||||
- Attempt to improve the OpenGL shader's compatibility with GLES2.
|
||||
- (Windows) Fix noise when seeking while using wasapi:exclusive. (bug #1529)
|
||||
- Fix the waf-based build system when used with newer versions of waf with
|
||||
python3.
|
||||
- Documentation fixes. (includes bug #1608)
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- mpv now tries to load '.vtt' subtitles.
|
||||
|
||||
This listing is not complete. A complete changelog can be seen by running
|
||||
`git log v0.8.0..v0.8.1` in the git repository.
|
||||
|
||||
Release 0.8.0
|
||||
=============
|
||||
|
||||
@@ -162,4 +231,4 @@ New features
|
||||
files.
|
||||
|
||||
This listing is not complete. A complete changelog can be seen by running
|
||||
`git log v0.7.0..` in the git repository.
|
||||
`git log v0.7.0..v0.8.0` in the git repository.
|
||||
+1
-1
@@ -64,7 +64,7 @@ sub generate_c_header {
|
||||
|
||||
# Output a counter variable for each element
|
||||
# (presence/absence for scalars, item count for arrays)
|
||||
for my $subel (values %{$el->{subelements}}) {
|
||||
for my $subel (sort values %{$el->{subelements}}) {
|
||||
print " int n_$subel->{fieldname};\n"
|
||||
}
|
||||
print "};\n";
|
||||
|
||||
+2
-2
@@ -74,7 +74,7 @@ void mp_chmap_sel_add_waveext(struct mp_chmap_sel *s)
|
||||
// Classic ALSA-based MPlayer layouts.
|
||||
void mp_chmap_sel_add_alsa_def(struct mp_chmap_sel *s)
|
||||
{
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
for (int n = 1; n <= MP_NUM_CHANNELS; n++) {
|
||||
struct mp_chmap t;
|
||||
mp_chmap_from_channels_alsa(&t, n);
|
||||
if (t.num)
|
||||
@@ -102,7 +102,7 @@ void mp_chmap_sel_add_map(struct mp_chmap_sel *s, const struct mp_chmap *map)
|
||||
// Allow all waveext formats in default order.
|
||||
void mp_chmap_sel_add_waveext_def(struct mp_chmap_sel *s)
|
||||
{
|
||||
for (int n = 1; n < MP_NUM_CHANNELS; n++) {
|
||||
for (int n = 1; n <= MP_NUM_CHANNELS; n++) {
|
||||
struct mp_chmap map;
|
||||
mp_chmap_from_channels(&map, n);
|
||||
mp_chmap_sel_add_map(s, &map);
|
||||
|
||||
@@ -200,7 +200,6 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
MP_VERBOSE(da, "spdif packet, size=%d\n", pkt.size);
|
||||
if (mpkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = mpkt->pts;
|
||||
da->pts_offset = 0;
|
||||
|
||||
+68
-20
@@ -113,6 +113,58 @@ exit_label:
|
||||
return;
|
||||
}
|
||||
|
||||
static void thread_resume(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
HRESULT hr;
|
||||
|
||||
MP_DBG(state, "Thread Resume\n");
|
||||
UINT32 padding = 0;
|
||||
hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding);
|
||||
if (hr != S_OK) {
|
||||
MP_ERR(state, "IAudioClient_GetCurrentPadding returned %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
}
|
||||
|
||||
/* Fill the buffer before starting, but only if there is no audio queued to play. */
|
||||
/* This prevents overfilling the buffer, which leads to problems in exclusive mode */
|
||||
if (padding < (UINT32)state->bufferFrameCount)
|
||||
thread_feed(ao);
|
||||
|
||||
hr = IAudioClient_Start(state->pAudioClient);
|
||||
if (hr != S_OK) {
|
||||
MP_ERR(state, "IAudioClient_Start returned %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void thread_reset(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
HRESULT hr;
|
||||
MP_DBG(state, "Thread Reset\n");
|
||||
hr = IAudioClient_Stop(state->pAudioClient);
|
||||
/* we may get S_FALSE if the stream is already stopped */
|
||||
if (hr != S_OK &&
|
||||
hr != S_FALSE) {
|
||||
MP_ERR(state, "IAudioClient_Stop returned: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
}
|
||||
|
||||
/* we may get S_FALSE if the stream is already reset */
|
||||
hr = IAudioClient_Reset(state->pAudioClient);
|
||||
if (hr != S_OK &&
|
||||
hr != S_FALSE) {
|
||||
MP_ERR(state, "IAudioClient_Reset returned: %s (0x%"PRIx32")\n",
|
||||
wasapi_explain_err(hr), (uint32_t) hr);
|
||||
}
|
||||
|
||||
atomic_store(&state->sample_count, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
{
|
||||
struct ao *ao = lpParameter;
|
||||
@@ -126,10 +178,10 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
|
||||
DWORD waitstatus;
|
||||
HANDLE playcontrol[] =
|
||||
{state->hUninit, state->hFeed, state->hForceFeed, NULL};
|
||||
{state->hUninit, state->hFeed, state->hReset, state->hResume, NULL};
|
||||
MP_DBG(ao, "Entering dispatch loop\n");
|
||||
while (true) { /* watch events */
|
||||
waitstatus = MsgWaitForMultipleObjects(3, playcontrol, FALSE, INFINITE,
|
||||
waitstatus = MsgWaitForMultipleObjects(4, playcontrol, FALSE, INFINITE,
|
||||
QS_POSTMESSAGE | QS_SENDMESSAGE);
|
||||
switch (waitstatus) {
|
||||
case WAIT_OBJECT_0: /*shutdown*/
|
||||
@@ -138,16 +190,18 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
case (WAIT_OBJECT_0 + 1): /* feed */
|
||||
thread_feed(ao);
|
||||
break;
|
||||
case (WAIT_OBJECT_0 + 2): /* force feed */
|
||||
thread_feed(ao);
|
||||
SetEvent(state->hFeedDone);
|
||||
case (WAIT_OBJECT_0 + 2): /* reset */
|
||||
thread_reset(ao);
|
||||
break;
|
||||
case (WAIT_OBJECT_0 + 3): /* messages to dispatch (COM marshalling) */
|
||||
case (WAIT_OBJECT_0 + 3): /* resume */
|
||||
thread_resume(ao);
|
||||
break;
|
||||
case (WAIT_OBJECT_0 + 4): /* messages to dispatch (COM marshalling) */
|
||||
MP_DBG(ao, "Dispatch\n");
|
||||
wasapi_dispatch();
|
||||
break;
|
||||
default:
|
||||
MP_ERR(ao, "Unhandled case in thread loop");
|
||||
MP_ERR(ao, "Unhandled case in thread loop\n");
|
||||
goto exit_label;
|
||||
}
|
||||
}
|
||||
@@ -165,8 +219,8 @@ static void closehandles(struct ao *ao)
|
||||
if (state->init_done) CloseHandle(state->init_done);
|
||||
if (state->hUninit) CloseHandle(state->hUninit);
|
||||
if (state->hFeed) CloseHandle(state->hFeed);
|
||||
if (state->hForceFeed) CloseHandle(state->hForceFeed);
|
||||
if (state->hFeedDone) CloseHandle(state->hFeedDone);
|
||||
if (state->hResume) CloseHandle(state->hResume);
|
||||
if (state->hReset) CloseHandle(state->hReset);
|
||||
if (state->threadLoop) CloseHandle(state->threadLoop);
|
||||
}
|
||||
|
||||
@@ -220,10 +274,10 @@ static int init(struct ao *ao)
|
||||
state->init_done = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hUninit = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hFeed = CreateEventW(NULL, FALSE, FALSE, NULL); /* for wasapi event mode */
|
||||
state->hForceFeed = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hFeedDone = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hResume = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hReset = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
if (!state->init_done || !state->hFeed || !state->hUninit ||
|
||||
!state->hForceFeed || !state->hFeedDone)
|
||||
!state->hResume || !state->hReset)
|
||||
{
|
||||
MP_ERR(ao, "Error initing events\n");
|
||||
uninit(ao);
|
||||
@@ -335,19 +389,13 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
static void audio_reset(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
|
||||
IAudioClient_Stop(state->pAudioClientProxy);
|
||||
IAudioClient_Reset(state->pAudioClientProxy);
|
||||
atomic_store(&state->sample_count, 0);
|
||||
SetEvent(state->hReset);
|
||||
}
|
||||
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
|
||||
SetEvent(state->hForceFeed);
|
||||
WaitForSingleObject(state->hFeedDone, INFINITE);
|
||||
IAudioClient_Start(state->pAudioClientProxy);
|
||||
SetEvent(state->hResume);
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
|
||||
@@ -72,15 +72,14 @@ typedef struct wasapi_state {
|
||||
IMMDeviceEnumerator *pEnumerator;
|
||||
|
||||
HANDLE hFeed; /* wasapi event */
|
||||
HANDLE hForceFeed; /* forces writing a buffer (e.g. before audio_resume) */
|
||||
HANDLE hFeedDone; /* set only after a hForceFeed */
|
||||
HANDLE hResume; /* signal audio thread to resume the stream */
|
||||
HANDLE hReset; /* signal audio thread to reset the stream */
|
||||
HANDLE hTask; /* AV thread */
|
||||
DWORD taskIndex; /* AV task ID */
|
||||
WAVEFORMATEXTENSIBLE format;
|
||||
|
||||
/* WASAPI proxy handles, for Single-Threaded Apartment communication.
|
||||
One is needed for each object that's accessed by a different thread. */
|
||||
IAudioClient *pAudioClientProxy;
|
||||
ISimpleAudioVolume *pAudioVolumeProxy;
|
||||
IAudioEndpointVolume *pEndpointVolumeProxy;
|
||||
IAudioSessionControl *pSessionControlProxy;
|
||||
@@ -88,7 +87,6 @@ typedef struct wasapi_state {
|
||||
/* Streams used to marshal the proxy objects. The thread owning the actual objects
|
||||
needs to marshal proxy objects into these streams, and the thread that wants the
|
||||
proxies unmarshals them from here. */
|
||||
IStream *sAudioClient;
|
||||
IStream *sAudioVolume;
|
||||
IStream *sEndpointVolume;
|
||||
IStream *sSessionControl;
|
||||
|
||||
@@ -958,7 +958,6 @@ HRESULT wasapi_setup_proxies(struct wasapi_state *state) {
|
||||
EXIT_ON_ERROR(hr); \
|
||||
} while (0)
|
||||
|
||||
UNMARSHAL(IID_IAudioClient, state->pAudioClientProxy, state->sAudioClient);
|
||||
UNMARSHAL(IID_ISimpleAudioVolume, state->pAudioVolumeProxy, state->sAudioVolume);
|
||||
UNMARSHAL(IID_IAudioEndpointVolume, state->pEndpointVolumeProxy, state->sEndpointVolume);
|
||||
UNMARSHAL(IID_IAudioSessionControl, state->pSessionControlProxy, state->sSessionControl);
|
||||
@@ -973,7 +972,6 @@ exit_label:
|
||||
}
|
||||
|
||||
void wasapi_release_proxies(wasapi_state *state) {
|
||||
SAFE_RELEASE(state->pAudioClientProxy, IUnknown_Release(state->pAudioClientProxy));
|
||||
SAFE_RELEASE(state->pAudioVolumeProxy, IUnknown_Release(state->pAudioVolumeProxy));
|
||||
SAFE_RELEASE(state->pEndpointVolumeProxy, IUnknown_Release(state->pEndpointVolumeProxy));
|
||||
SAFE_RELEASE(state->pSessionControlProxy, IUnknown_Release(state->pSessionControlProxy));
|
||||
@@ -991,7 +989,6 @@ static HRESULT create_proxies(struct wasapi_state *state) {
|
||||
EXIT_ON_ERROR(hr); \
|
||||
} while (0)
|
||||
|
||||
MARSHAL(IID_IAudioClient, state->sAudioClient, state->pAudioClient);
|
||||
MARSHAL(IID_ISimpleAudioVolume, state->sAudioVolume, state->pAudioVolume);
|
||||
MARSHAL(IID_IAudioEndpointVolume, state->sEndpointVolume, state->pEndpointVolume);
|
||||
MARSHAL(IID_IAudioSessionControl, state->sSessionControl, state->pSessionControl);
|
||||
@@ -1004,7 +1001,6 @@ exit_label:
|
||||
}
|
||||
|
||||
static void destroy_proxies(struct wasapi_state *state) {
|
||||
SAFE_RELEASE(state->sAudioClient, IUnknown_Release(state->sAudioClient));
|
||||
SAFE_RELEASE(state->sAudioVolume, IUnknown_Release(state->sAudioVolume));
|
||||
SAFE_RELEASE(state->sEndpointVolume, IUnknown_Release(state->sEndpointVolume));
|
||||
SAFE_RELEASE(state->sSessionControl, IUnknown_Release(state->sSessionControl));
|
||||
|
||||
+18
-5
@@ -6,7 +6,8 @@ from __future__ import print_function
|
||||
import os, sys, stat, hashlib, subprocess
|
||||
|
||||
WAFRELEASE = "waf-1.8.4"
|
||||
WAFURL = "http://ftp.waf.io/pub/release/" + WAFRELEASE
|
||||
WAFURLS = ["http://ftp.waf.io/pub/release/" + WAFRELEASE,
|
||||
"http://www.freehackers.org/~tnagy/release/" + WAFRELEASE]
|
||||
SHA256HASH = "f02035fa5d8814f33f19b2b20d43822ddef6bb39b955ca196c2a247a1f9ffaa8"
|
||||
|
||||
if os.path.exists("waf"):
|
||||
@@ -16,12 +17,24 @@ if os.path.exists("waf"):
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
from urllib.request import urlopen, URLError
|
||||
except:
|
||||
from urllib2 import urlopen
|
||||
from urllib2 import urlopen, URLError
|
||||
|
||||
print("Downloading %s..." % WAFURL)
|
||||
waf = urlopen(WAFURL).read()
|
||||
waf = None
|
||||
|
||||
for WAFURL in WAFURLS:
|
||||
try:
|
||||
print("Downloading {}...".format(WAFURL))
|
||||
waf = urlopen(WAFURL).read()
|
||||
break
|
||||
except URLError:
|
||||
print("Download failed.")
|
||||
|
||||
if not waf:
|
||||
print("Could not download {}.".format(WAFRELEASE))
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
if SHA256HASH == hashlib.sha256(waf).hexdigest():
|
||||
with open("waf", "wb") as wf:
|
||||
|
||||
@@ -135,6 +135,8 @@ void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads
|
||||
threads = 1;
|
||||
} else {
|
||||
mp_verbose(l, "Detected %d logical cores.\n", threads);
|
||||
if (threads > 1)
|
||||
threads += 1; // extra thread for better load balancing
|
||||
}
|
||||
// Apparently some libavcodec versions have or had trouble with more
|
||||
// than 16 threads, and/or print a warning when using > 16.
|
||||
|
||||
+4
-1
@@ -136,8 +136,11 @@ bool mp_msg_test(struct mp_log *log, int lev)
|
||||
struct mp_log_root *root = log->root;
|
||||
if (!root || root->mute)
|
||||
return false;
|
||||
if (atomic_load(&log->reload_counter) != atomic_load(&root->reload_counter))
|
||||
if (atomic_load_explicit(&log->reload_counter, memory_order_relaxed) !=
|
||||
atomic_load_explicit(&root->reload_counter, memory_order_relaxed))
|
||||
{
|
||||
update_loglevel(log);
|
||||
}
|
||||
return lev <= log->level;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ struct playlist {
|
||||
// current_was_replaced is set to true.
|
||||
struct playlist_entry *current;
|
||||
bool current_was_replaced;
|
||||
|
||||
bool disable_safety;
|
||||
};
|
||||
|
||||
void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value);
|
||||
|
||||
+10
-5
@@ -744,7 +744,7 @@ static void demux_export_replaygain(demuxer_t *demuxer)
|
||||
static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
{
|
||||
if (src->events & DEMUX_EVENT_INIT) {
|
||||
// Note that we do as shallow copies as possible. We expect the date
|
||||
// Note that we do as shallow copies as possible. We expect the data
|
||||
// that is not-copied (only referenced) to be immutable.
|
||||
// This implies e.g. that no chapters are added after initialization.
|
||||
dst->chapters = src->chapters;
|
||||
@@ -755,7 +755,6 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->attachments = src->attachments;
|
||||
dst->num_attachments = src->num_attachments;
|
||||
dst->matroska_data = src->matroska_data;
|
||||
dst->file_contents = src->file_contents;
|
||||
dst->playlist = src->playlist;
|
||||
dst->seekable = src->seekable;
|
||||
dst->partially_seekable = src->partially_seekable;
|
||||
@@ -763,6 +762,7 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->ts_resets_possible = src->ts_resets_possible;
|
||||
dst->rel_seeks = src->rel_seeks;
|
||||
dst->start_time = src->start_time;
|
||||
dst->priv = src->priv;
|
||||
}
|
||||
if (src->events & DEMUX_EVENT_STREAMS) {
|
||||
// The stream structs themselves are immutable.
|
||||
@@ -841,7 +841,6 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
struct demuxer *demuxer = talloc_ptrtype(NULL, demuxer);
|
||||
*demuxer = (struct demuxer) {
|
||||
.desc = desc,
|
||||
.type = desc->type,
|
||||
.stream = stream,
|
||||
.seekable = stream->seekable,
|
||||
.filepos = -1,
|
||||
@@ -889,7 +888,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
stream_seek(stream, 0);
|
||||
|
||||
// Peek this much data to avoid that stream_read() run by some demuxers
|
||||
// or stream filters will flush previous peeked data.
|
||||
// will flush previous peeked data.
|
||||
stream_peek(stream, STREAM_BUFFER_SIZE);
|
||||
|
||||
int ret = demuxer->desc->open(in->d_thread, check);
|
||||
@@ -1379,13 +1378,19 @@ void demux_unpause(demuxer_t *demuxer)
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
bool demux_cancel_test(struct demuxer *demuxer)
|
||||
{
|
||||
return mp_cancel_test(demuxer->stream->cancel);
|
||||
}
|
||||
|
||||
struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num)
|
||||
{
|
||||
struct demux_chapter *new = talloc_array(NULL, struct demux_chapter, num);
|
||||
for (int n = 0; n < num; n++) {
|
||||
new[n] = c[n];
|
||||
new[n].name = talloc_strdup(new, new[n].name);
|
||||
new[n].metadata = mp_tags_dup(new, new[n].metadata);
|
||||
if (new[n].metadata)
|
||||
new[n].metadata = mp_tags_dup(new, new[n].metadata);
|
||||
}
|
||||
return new;
|
||||
}
|
||||
+5
-12
@@ -36,13 +36,6 @@
|
||||
#define MAX_PACKS 16000
|
||||
#define MAX_PACK_BYTES (400 * 1024 * 1024)
|
||||
|
||||
enum demuxer_type {
|
||||
DEMUXER_TYPE_GENERIC = 0,
|
||||
DEMUXER_TYPE_MATROSKA,
|
||||
DEMUXER_TYPE_EDL,
|
||||
DEMUXER_TYPE_CUE,
|
||||
};
|
||||
|
||||
// DEMUXER control commands/answers
|
||||
#define DEMUXER_CTRL_NOTIMPL -1
|
||||
#define DEMUXER_CTRL_DONTKNOW 0
|
||||
@@ -104,6 +97,7 @@ enum demux_event {
|
||||
#define MAX_SH_STREAMS 256
|
||||
|
||||
struct demuxer;
|
||||
struct timeline;
|
||||
|
||||
/**
|
||||
* Demuxer description structure
|
||||
@@ -112,8 +106,6 @@ typedef struct demuxer_desc {
|
||||
const char *name; // Demuxer name, used with -demuxer switch
|
||||
const char *desc; // Displayed to user
|
||||
|
||||
enum demuxer_type type; // optional
|
||||
|
||||
// Return 0 on success, otherwise -1
|
||||
int (*open)(struct demuxer *demuxer, enum demux_check check);
|
||||
// The following functions are all optional
|
||||
@@ -121,6 +113,8 @@ typedef struct demuxer_desc {
|
||||
void (*close)(struct demuxer *demuxer);
|
||||
void (*seek)(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
||||
int (*control)(struct demuxer *demuxer, int cmd, void *arg);
|
||||
// See timeline.c
|
||||
void (*load_timeline)(struct timeline *tl);
|
||||
} demuxer_desc_t;
|
||||
|
||||
typedef struct demux_chapter
|
||||
@@ -184,7 +178,6 @@ typedef struct demuxer {
|
||||
const char *filetype; // format name when not identified by demuxer (libavformat)
|
||||
int64_t filepos; // input stream current pos.
|
||||
char *filename; // same as stream->url
|
||||
enum demuxer_type type;
|
||||
bool seekable;
|
||||
bool partially_seekable; // implies seekable=true
|
||||
double start_time;
|
||||
@@ -211,8 +204,6 @@ typedef struct demuxer {
|
||||
int num_attachments;
|
||||
|
||||
struct matroska_data matroska_data;
|
||||
// for trivial demuxers which just read the whole file for codec to use
|
||||
struct bstr file_contents;
|
||||
|
||||
// If the file is a playlist file
|
||||
struct playlist *playlist;
|
||||
@@ -260,6 +251,8 @@ void demux_start_thread(struct demuxer *demuxer);
|
||||
void demux_stop_thread(struct demuxer *demuxer);
|
||||
void demux_set_wakeup_cb(struct demuxer *demuxer, void (*cb)(void *ctx), void *ctx);
|
||||
|
||||
bool demux_cancel_test(struct demuxer *demuxer);
|
||||
|
||||
void demux_flush(struct demuxer *demuxer);
|
||||
int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
||||
|
||||
|
||||
+401
-7
@@ -21,15 +21,407 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "talloc.h"
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "demux.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/demux.h"
|
||||
#include "options/path.h"
|
||||
#include "common/common.h"
|
||||
#include "stream/stream.h"
|
||||
|
||||
// timeline/tl_cue.c
|
||||
bool mp_probe_cue(struct bstr s);
|
||||
#include "timeline.h"
|
||||
|
||||
#define PROBE_SIZE 512
|
||||
#define SECS_PER_CUE_FRAME (1.0/75.0)
|
||||
|
||||
enum cue_command {
|
||||
CUE_ERROR = -1, // not a valid CUE command, or an unknown extension
|
||||
CUE_EMPTY, // line with whitespace only
|
||||
CUE_UNUSED, // valid CUE command, but ignored by this code
|
||||
CUE_FILE,
|
||||
CUE_TRACK,
|
||||
CUE_INDEX,
|
||||
CUE_TITLE,
|
||||
};
|
||||
|
||||
static const struct {
|
||||
enum cue_command command;
|
||||
const char *text;
|
||||
} cue_command_strings[] = {
|
||||
{ CUE_FILE, "FILE" },
|
||||
{ CUE_TRACK, "TRACK" },
|
||||
{ CUE_INDEX, "INDEX" },
|
||||
{ CUE_TITLE, "TITLE" },
|
||||
{ CUE_UNUSED, "CATALOG" },
|
||||
{ CUE_UNUSED, "CDTEXTFILE" },
|
||||
{ CUE_UNUSED, "FLAGS" },
|
||||
{ CUE_UNUSED, "ISRC" },
|
||||
{ CUE_UNUSED, "PERFORMER" },
|
||||
{ CUE_UNUSED, "POSTGAP" },
|
||||
{ CUE_UNUSED, "PREGAP" },
|
||||
{ CUE_UNUSED, "REM" },
|
||||
{ CUE_UNUSED, "SONGWRITER" },
|
||||
{ CUE_UNUSED, "MESSAGE" },
|
||||
{ -1 },
|
||||
};
|
||||
|
||||
struct cue_track {
|
||||
double pregap_start; // corresponds to INDEX 00
|
||||
double start; // corresponds to INDEX 01
|
||||
struct bstr filename;
|
||||
int source;
|
||||
struct bstr title;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
bstr data;
|
||||
};
|
||||
|
||||
static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
|
||||
{
|
||||
struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data));
|
||||
line = bstr_lstrip(line);
|
||||
if (line.len == 0)
|
||||
return CUE_EMPTY;
|
||||
for (int n = 0; cue_command_strings[n].command != -1; n++) {
|
||||
struct bstr name = bstr0(cue_command_strings[n].text);
|
||||
if (bstr_startswith(line, name)) {
|
||||
struct bstr rest = bstr_cut(line, name.len);
|
||||
if (rest.len && !strchr(WHITESPACE, rest.start[0]))
|
||||
continue;
|
||||
if (out_params)
|
||||
*out_params = rest;
|
||||
return cue_command_strings[n].command;
|
||||
}
|
||||
}
|
||||
return CUE_ERROR;
|
||||
}
|
||||
|
||||
static bool eat_char(struct bstr *data, char ch)
|
||||
{
|
||||
if (data->len && data->start[0] == ch) {
|
||||
*data = bstr_cut(*data, 1);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct bstr read_quoted(struct bstr *data)
|
||||
{
|
||||
*data = bstr_lstrip(*data);
|
||||
if (!eat_char(data, '"'))
|
||||
return (struct bstr) {0};
|
||||
int end = bstrchr(*data, '"');
|
||||
if (end < 0)
|
||||
return (struct bstr) {0};
|
||||
struct bstr res = bstr_splice(*data, 0, end);
|
||||
*data = bstr_cut(*data, end + 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Read a 2 digit unsigned decimal integer.
|
||||
// Return -1 on failure.
|
||||
static int read_int_2(struct bstr *data)
|
||||
{
|
||||
*data = bstr_lstrip(*data);
|
||||
if (data->len && data->start[0] == '-')
|
||||
return -1;
|
||||
struct bstr s = *data;
|
||||
int res = (int)bstrtoll(s, &s, 10);
|
||||
if (data->len == s.len || data->len - s.len > 2)
|
||||
return -1;
|
||||
*data = s;
|
||||
return res;
|
||||
}
|
||||
|
||||
static double read_time(struct bstr *data)
|
||||
{
|
||||
struct bstr s = *data;
|
||||
bool ok = true;
|
||||
double t1 = read_int_2(&s);
|
||||
ok = eat_char(&s, ':') && ok;
|
||||
double t2 = read_int_2(&s);
|
||||
ok = eat_char(&s, ':') && ok;
|
||||
double t3 = read_int_2(&s);
|
||||
ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0;
|
||||
return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0;
|
||||
}
|
||||
|
||||
static struct bstr skip_utf8_bom(struct bstr data)
|
||||
{
|
||||
return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data;
|
||||
}
|
||||
|
||||
// Check if the text in data is most likely CUE data. This is used by the
|
||||
// demuxer code to check the file type.
|
||||
// data is the start of the probed file, possibly cut off at a random point.
|
||||
static bool mp_probe_cue(struct bstr data)
|
||||
{
|
||||
bool valid = false;
|
||||
data = skip_utf8_bom(data);
|
||||
for (;;) {
|
||||
enum cue_command cmd = read_cmd(&data, NULL);
|
||||
// End reached. Since the line was most likely cut off, don't use the
|
||||
// result of the last parsing call.
|
||||
if (data.len == 0)
|
||||
break;
|
||||
if (cmd == CUE_ERROR)
|
||||
return false;
|
||||
if (cmd != CUE_EMPTY)
|
||||
valid = true;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
static void add_source(struct timeline *tl, struct demuxer *d)
|
||||
{
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
|
||||
}
|
||||
|
||||
static bool try_open(struct timeline *tl, char *filename)
|
||||
{
|
||||
struct bstr bfilename = bstr0(filename);
|
||||
// Avoid trying to open itself or another .cue file. Best would be
|
||||
// to check the result of demuxer auto-detection, but the demuxer
|
||||
// API doesn't allow this without opening a full demuxer.
|
||||
if (bstr_case_endswith(bfilename, bstr0(".cue"))
|
||||
|| bstrcasecmp(bstr0(tl->demuxer->filename), bfilename) == 0)
|
||||
return false;
|
||||
|
||||
struct stream *s = stream_open(filename, tl->global);
|
||||
if (!s)
|
||||
return false;
|
||||
struct demuxer *d = demux_open(s, NULL, NULL, tl->global);
|
||||
// Since .bin files are raw PCM data with no headers, we have to explicitly
|
||||
// open them. Also, try to avoid to open files that are most likely not .bin
|
||||
// files, as that would only play noise. Checking the file extension is
|
||||
// fragile, but it's about the only way we have.
|
||||
// TODO: maybe also could check if the .bin file is a multiple of the Audio
|
||||
// CD sector size (2352 bytes)
|
||||
if (!d && bstr_case_endswith(bfilename, bstr0(".bin"))) {
|
||||
MP_WARN(tl, "CUE: Opening as BIN file!\n");
|
||||
d = demux_open(s, "rawaudio", NULL, tl->global);
|
||||
}
|
||||
if (d) {
|
||||
add_source(tl, d);
|
||||
return true;
|
||||
}
|
||||
MP_ERR(tl, "Could not open source '%s'!\n", filename);
|
||||
free_stream(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool open_source(struct timeline *tl, struct bstr filename)
|
||||
{
|
||||
void *ctx = talloc_new(NULL);
|
||||
bool res = false;
|
||||
|
||||
struct bstr dirname = mp_dirname(tl->demuxer->filename);
|
||||
|
||||
struct bstr base_filename = bstr0(mp_basename(bstrdup0(ctx, filename)));
|
||||
if (!base_filename.len) {
|
||||
MP_WARN(tl, "CUE: Invalid audio filename in .cue file!\n");
|
||||
} else {
|
||||
char *fullname = mp_path_join(ctx, dirname, base_filename);
|
||||
if (try_open(tl, fullname)) {
|
||||
res = true;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
// Try an audio file with the same name as the .cue file (but different
|
||||
// extension).
|
||||
// Rationale: this situation happens easily if the audio file or both files
|
||||
// are renamed.
|
||||
|
||||
struct bstr cuefile =
|
||||
bstr_strip_ext(bstr0(mp_basename(tl->demuxer->filename)));
|
||||
|
||||
DIR *d = opendir(bstrdup0(ctx, dirname));
|
||||
if (!d)
|
||||
goto out;
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d))) {
|
||||
char *dename0 = de->d_name;
|
||||
struct bstr dename = bstr0(dename0);
|
||||
if (bstr_case_startswith(dename, cuefile)) {
|
||||
MP_WARN(tl, "CUE: No useful audio filename "
|
||||
"in .cue file found, trying with '%s' instead!\n",
|
||||
dename0);
|
||||
if (try_open(tl, mp_path_join(ctx, dirname, dename))) {
|
||||
res = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
out:
|
||||
talloc_free(ctx);
|
||||
if (!res)
|
||||
MP_ERR(tl, "CUE: Could not open audio file!\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
// return length of the source in seconds, or -1 if unknown
|
||||
static double source_get_length(struct demuxer *demuxer)
|
||||
{
|
||||
double get_time_ans;
|
||||
// <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW
|
||||
if (demuxer && demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH,
|
||||
(void *) &get_time_ans) > 0)
|
||||
{
|
||||
return get_time_ans;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void build_timeline(struct timeline *tl)
|
||||
{
|
||||
struct priv *p = tl->demuxer->priv;
|
||||
|
||||
void *ctx = talloc_new(NULL);
|
||||
|
||||
add_source(tl, tl->demuxer);
|
||||
|
||||
struct bstr data = p->data;
|
||||
data = skip_utf8_bom(data);
|
||||
|
||||
struct cue_track *tracks = NULL;
|
||||
size_t track_count = 0;
|
||||
|
||||
struct bstr filename = {0};
|
||||
// Global metadata, and copied into new tracks.
|
||||
struct cue_track proto_track = {0};
|
||||
struct cue_track *cur_track = &proto_track;
|
||||
|
||||
while (data.len) {
|
||||
struct bstr param;
|
||||
switch (read_cmd(&data, ¶m)) {
|
||||
case CUE_ERROR:
|
||||
MP_ERR(tl, "CUE: error parsing input file!\n");
|
||||
goto out;
|
||||
case CUE_TRACK: {
|
||||
track_count++;
|
||||
tracks = talloc_realloc(ctx, tracks, struct cue_track, track_count);
|
||||
cur_track = &tracks[track_count - 1];
|
||||
*cur_track = proto_track;
|
||||
break;
|
||||
}
|
||||
case CUE_TITLE:
|
||||
cur_track->title = read_quoted(¶m);
|
||||
break;
|
||||
case CUE_INDEX: {
|
||||
int type = read_int_2(¶m);
|
||||
double time = read_time(¶m);
|
||||
if (type == 1) {
|
||||
cur_track->start = time;
|
||||
cur_track->filename = filename;
|
||||
} else if (type == 0) {
|
||||
cur_track->pregap_start = time;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CUE_FILE:
|
||||
// NOTE: FILE comes before TRACK, so don't use cur_track->filename
|
||||
filename = read_quoted(¶m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (track_count == 0) {
|
||||
MP_ERR(tl, "CUE: no tracks found!\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
// Remove duplicate file entries. This might be too sophisticated, since
|
||||
// CUE files usually use either separate files for every single track, or
|
||||
// only one file for all tracks.
|
||||
|
||||
struct bstr *files = 0;
|
||||
size_t file_count = 0;
|
||||
|
||||
for (size_t n = 0; n < track_count; n++) {
|
||||
struct cue_track *track = &tracks[n];
|
||||
track->source = -1;
|
||||
for (size_t file = 0; file < file_count; file++) {
|
||||
if (bstrcmp(files[file], track->filename) == 0) {
|
||||
track->source = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (track->source == -1) {
|
||||
file_count++;
|
||||
files = talloc_realloc(ctx, files, struct bstr, file_count);
|
||||
files[file_count - 1] = track->filename;
|
||||
track->source = file_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < file_count; i++) {
|
||||
if (!open_source(tl, files[i]))
|
||||
goto out;
|
||||
}
|
||||
|
||||
struct timeline_part *timeline = talloc_array_ptrtype(tl, timeline,
|
||||
track_count + 1);
|
||||
struct demux_chapter *chapters = talloc_array_ptrtype(tl, chapters,
|
||||
track_count);
|
||||
double starttime = 0;
|
||||
for (int i = 0; i < track_count; i++) {
|
||||
struct demuxer *source = tl->sources[1 + tracks[i].source];
|
||||
double duration;
|
||||
if (i + 1 < track_count && tracks[i].source == tracks[i + 1].source) {
|
||||
duration = tracks[i + 1].start - tracks[i].start;
|
||||
} else {
|
||||
duration = source_get_length(source);
|
||||
// Two cases: 1) last track of a single-file cue, or 2) any track of
|
||||
// a multi-file cue. We need to do this for 1) only because the
|
||||
// timeline needs to be terminated with the length of the last
|
||||
// track.
|
||||
duration -= tracks[i].start;
|
||||
}
|
||||
if (duration < 0) {
|
||||
MP_WARN(tl, "CUE: Can't get duration of source file!\n");
|
||||
// xxx: do something more reasonable
|
||||
duration = 0.0;
|
||||
}
|
||||
timeline[i] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
.source_start = tracks[i].start,
|
||||
.source = source,
|
||||
};
|
||||
chapters[i] = (struct demux_chapter) {
|
||||
.pts = timeline[i].start,
|
||||
// might want to include other metadata here
|
||||
.name = bstrdup0(chapters, tracks[i].title),
|
||||
};
|
||||
starttime += duration;
|
||||
}
|
||||
|
||||
// apparently we need this to give the last part a non-zero length
|
||||
timeline[track_count] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
// perhaps unused by the timeline code
|
||||
.source_start = 0,
|
||||
.source = timeline[0].source,
|
||||
};
|
||||
|
||||
tl->parts = timeline;
|
||||
// the last part is not included it in the count
|
||||
tl->num_parts = track_count + 1 - 1;
|
||||
tl->chapters = chapters;
|
||||
tl->num_chapters = track_count;
|
||||
tl->track_layout = tl->parts[0].source;
|
||||
|
||||
out:
|
||||
talloc_free(ctx);
|
||||
}
|
||||
|
||||
static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
@@ -39,8 +431,10 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
if (d.len < 1 || !mp_probe_cue(d))
|
||||
return -1;
|
||||
}
|
||||
demuxer->file_contents = stream_read_complete(s, demuxer, 1000000);
|
||||
if (demuxer->file_contents.start == NULL)
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
demuxer->priv = p;
|
||||
p->data = stream_read_complete(s, demuxer, 1000000);
|
||||
if (p->data.start == NULL)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
@@ -48,6 +442,6 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
const struct demuxer_desc demuxer_desc_cue = {
|
||||
.name = "cue",
|
||||
.desc = "CUE sheet",
|
||||
.type = DEMUXER_TYPE_CUE,
|
||||
.open = try_open_file,
|
||||
.load_timeline = build_timeline,
|
||||
};
|
||||
+289
-7
@@ -21,34 +21,316 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "demux.h"
|
||||
#include "talloc.h"
|
||||
|
||||
#include "player/core.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
#include "demux/demux.h"
|
||||
#include "options/path.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "common/common.h"
|
||||
#include "stream/stream.h"
|
||||
|
||||
#define HEADER "# mpv EDL v0\n"
|
||||
|
||||
// Note: the real work is handled in tl_mpv_edl.c.
|
||||
struct tl_part {
|
||||
char *filename; // what is stream_open()ed
|
||||
double offset; // offset into the source file
|
||||
bool offset_set;
|
||||
bool chapter_ts;
|
||||
double length; // length of the part (-1 if rest of the file)
|
||||
};
|
||||
|
||||
struct tl_parts {
|
||||
struct tl_part *parts;
|
||||
int num_parts;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
bstr data;
|
||||
};
|
||||
|
||||
// Parse a time (absolute file time or duration). Currently equivalent to a
|
||||
// number. Return false on failure.
|
||||
static bool parse_time(bstr str, double *out_time)
|
||||
{
|
||||
bstr rest;
|
||||
double time = bstrtod(str, &rest);
|
||||
if (!str.len || rest.len || !isfinite(time))
|
||||
return false;
|
||||
*out_time = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returns a list of parts, or NULL on parse error.
|
||||
* Syntax (without file header or URI prefix):
|
||||
* url ::= <entry> ( (';' | '\n') <entry> )*
|
||||
* entry ::= <param> ( <param> ',' )*
|
||||
* param ::= [<string> '='] (<string> | '%' <number> '%' <bytes>)
|
||||
*/
|
||||
static struct tl_parts *parse_edl(bstr str)
|
||||
{
|
||||
struct tl_parts *tl = talloc_zero(NULL, struct tl_parts);
|
||||
while (str.len) {
|
||||
if (bstr_eatstart0(&str, "#"))
|
||||
bstr_split_tok(str, "\n", &(bstr){0}, &str);
|
||||
if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
|
||||
continue;
|
||||
struct tl_part p = { .length = -1 };
|
||||
int nparam = 0;
|
||||
while (1) {
|
||||
bstr name, val;
|
||||
// Check if it's of the form "name=..."
|
||||
int next = bstrcspn(str, "=%,;\n");
|
||||
if (next > 0 && next < str.len && str.start[next] == '=') {
|
||||
name = bstr_splice(str, 0, next);
|
||||
str = bstr_cut(str, next + 1);
|
||||
} else {
|
||||
const char *names[] = {"file", "start", "length"}; // implied name
|
||||
name = bstr0(nparam < 3 ? names[nparam] : "-");
|
||||
}
|
||||
if (bstr_eatstart0(&str, "%")) {
|
||||
int len = bstrtoll(str, &str, 0);
|
||||
if (!bstr_startswith0(str, "%") || (len > str.len - 1))
|
||||
goto error;
|
||||
val = bstr_splice(str, 1, len + 1);
|
||||
str = bstr_cut(str, len + 1);
|
||||
} else {
|
||||
next = bstrcspn(str, ",;\n");
|
||||
val = bstr_splice(str, 0, next);
|
||||
str = bstr_cut(str, next);
|
||||
}
|
||||
// Interpret parameters. Explicitly ignore unknown ones.
|
||||
if (bstr_equals0(name, "file")) {
|
||||
p.filename = bstrto0(tl, val);
|
||||
} else if (bstr_equals0(name, "start")) {
|
||||
if (!parse_time(val, &p.offset))
|
||||
goto error;
|
||||
p.offset_set = true;
|
||||
} else if (bstr_equals0(name, "length")) {
|
||||
if (!parse_time(val, &p.length))
|
||||
goto error;
|
||||
} else if (bstr_equals0(name, "timestamps")) {
|
||||
if (bstr_equals0(val, "chapters"))
|
||||
p.chapter_ts = true;
|
||||
}
|
||||
nparam++;
|
||||
if (!bstr_eatstart0(&str, ","))
|
||||
break;
|
||||
}
|
||||
if (!p.filename)
|
||||
goto error;
|
||||
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
|
||||
}
|
||||
if (!tl->num_parts)
|
||||
goto error;
|
||||
return tl;
|
||||
error:
|
||||
talloc_free(tl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct demuxer *open_file(char *filename, struct timeline *tl)
|
||||
{
|
||||
struct MPOpts *opts = tl->global->opts;
|
||||
struct demuxer *d = NULL;
|
||||
struct stream *s = stream_open(filename, tl->global);
|
||||
if (s) {
|
||||
stream_enable_cache(&s, &opts->stream_cache);
|
||||
d = demux_open(s, NULL, NULL, tl->global);
|
||||
}
|
||||
if (!d) {
|
||||
MP_ERR(tl, "EDL: Could not open source file '%s'.\n", filename);
|
||||
free_stream(s);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static struct demuxer *open_source(struct timeline *tl, char *filename)
|
||||
{
|
||||
for (int n = 0; n < tl->num_sources; n++) {
|
||||
struct demuxer *d = tl->sources[n];
|
||||
if (strcmp(d->stream->url, filename) == 0)
|
||||
return d;
|
||||
}
|
||||
struct demuxer *d = open_file(filename, tl);
|
||||
if (d)
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d);
|
||||
return d;
|
||||
}
|
||||
|
||||
static double demuxer_chapter_time(struct demuxer *demuxer, int n)
|
||||
{
|
||||
if (n < 0 || n >= demuxer->num_chapters)
|
||||
return -1;
|
||||
return demuxer->chapters[n].pts;
|
||||
}
|
||||
|
||||
// Append all chapters from src to the chapters array.
|
||||
// Ignore chapters outside of the given time range.
|
||||
static void copy_chapters(struct demux_chapter **chapters, int *num_chapters,
|
||||
struct demuxer *src, double start, double len,
|
||||
double dest_offset)
|
||||
{
|
||||
for (int n = 0; n < src->num_chapters; n++) {
|
||||
double time = demuxer_chapter_time(src, n);
|
||||
if (time >= start && time <= start + len) {
|
||||
struct demux_chapter ch = {
|
||||
.pts = dest_offset + time - start,
|
||||
.name = talloc_strdup(*chapters, src->chapters[n].name),
|
||||
};
|
||||
MP_TARRAY_APPEND(NULL, *chapters, *num_chapters, ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return length of the source in seconds, or -1 if unknown
|
||||
static double source_get_length(struct demuxer *demuxer)
|
||||
{
|
||||
double time;
|
||||
// <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW
|
||||
if (demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH, &time) <= 0)
|
||||
time = -1;
|
||||
return time;
|
||||
}
|
||||
|
||||
static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
|
||||
{
|
||||
if (part->chapter_ts) {
|
||||
double start = demuxer_chapter_time(demuxer, part->offset);
|
||||
double length = part->length;
|
||||
double end = length;
|
||||
if (end >= 0)
|
||||
end = demuxer_chapter_time(demuxer, part->offset + part->length);
|
||||
if (end >= 0 && start >= 0)
|
||||
length = end - start;
|
||||
part->offset = start;
|
||||
part->length = length;
|
||||
}
|
||||
if (!part->offset_set)
|
||||
part->offset = demuxer->start_time;
|
||||
}
|
||||
|
||||
static void build_timeline(struct timeline *tl, struct tl_parts *parts)
|
||||
{
|
||||
tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1);
|
||||
double starttime = 0;
|
||||
for (int n = 0; n < parts->num_parts; n++) {
|
||||
struct tl_part *part = &parts->parts[n];
|
||||
struct demuxer *source = open_source(tl, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
|
||||
resolve_timestamps(part, source);
|
||||
|
||||
double len = source_get_length(source);
|
||||
if (len > 0) {
|
||||
len += source->start_time;
|
||||
} else {
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
}
|
||||
|
||||
// Unknown length => use rest of the file. If duration is unknown, make
|
||||
// something up.
|
||||
if (part->length < 0)
|
||||
part->length = (len < 0 ? 1 : len) - part->offset;
|
||||
|
||||
if (len > 0) {
|
||||
double partlen = part->offset + part->length;
|
||||
if (partlen > len) {
|
||||
MP_WARN(tl, "EDL: entry %d uses %f "
|
||||
"seconds, but file has only %f seconds.\n",
|
||||
n, partlen, len);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a chapter between each file.
|
||||
struct demux_chapter ch = {
|
||||
.pts = starttime,
|
||||
.name = talloc_strdup(tl, part->filename),
|
||||
};
|
||||
MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch);
|
||||
|
||||
// Also copy the source file's chapters for the relevant parts
|
||||
copy_chapters(&tl->chapters, &tl->num_chapters, source, part->offset,
|
||||
part->length, starttime);
|
||||
|
||||
tl->parts[n] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
.source_start = part->offset,
|
||||
.source = source,
|
||||
};
|
||||
|
||||
starttime += part->length;
|
||||
}
|
||||
tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime};
|
||||
tl->num_parts = parts->num_parts;
|
||||
tl->track_layout = tl->parts[0].source;
|
||||
return;
|
||||
|
||||
error:
|
||||
tl->num_parts = 0;
|
||||
tl->num_chapters = 0;
|
||||
}
|
||||
|
||||
// For security, don't allow relative or absolute paths, only plain filenames.
|
||||
// Also, make these filenames relative to the edl source file.
|
||||
static void fix_filenames(struct tl_parts *parts, char *source_path)
|
||||
{
|
||||
struct bstr dirname = mp_dirname(source_path);
|
||||
for (int n = 0; n < parts->num_parts; n++) {
|
||||
struct tl_part *part = &parts->parts[n];
|
||||
char *filename = mp_basename(part->filename); // plain filename only
|
||||
part->filename = mp_path_join(parts, dirname, bstr0(filename));
|
||||
}
|
||||
}
|
||||
|
||||
static void build_mpv_edl_timeline(struct timeline *tl)
|
||||
{
|
||||
struct priv *p = tl->demuxer->priv;
|
||||
|
||||
struct tl_parts *parts = parse_edl(p->data);
|
||||
if (!parts) {
|
||||
MP_ERR(tl, "Error in EDL.\n");
|
||||
return;
|
||||
}
|
||||
MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer);
|
||||
// Source is .edl and not edl:// => don't allow arbitrary paths
|
||||
if (tl->demuxer->stream->uncached_type != STREAMTYPE_EDL)
|
||||
fix_filenames(parts, tl->demuxer->filename);
|
||||
build_timeline(tl, parts);
|
||||
talloc_free(parts);
|
||||
}
|
||||
|
||||
static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
demuxer->priv = p;
|
||||
|
||||
struct stream *s = demuxer->stream;
|
||||
if (s->uncached_type == STREAMTYPE_EDL) {
|
||||
demuxer->file_contents = bstr0(s->path);
|
||||
p->data = bstr0(s->path);
|
||||
return 0;
|
||||
}
|
||||
if (check >= DEMUX_CHECK_UNSAFE) {
|
||||
if (!bstr_equals0(stream_peek(s, strlen(HEADER)), HEADER))
|
||||
return -1;
|
||||
}
|
||||
demuxer->file_contents = stream_read_complete(s, demuxer, 1000000);
|
||||
if (demuxer->file_contents.start == NULL)
|
||||
p->data = stream_read_complete(s, demuxer, 1000000);
|
||||
if (p->data.start == NULL)
|
||||
return -1;
|
||||
bstr_eatstart0(&demuxer->file_contents, HEADER);
|
||||
bstr_eatstart0(&p->data, HEADER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct demuxer_desc demuxer_desc_edl = {
|
||||
.name = "edl",
|
||||
.desc = "Edit decision list",
|
||||
.type = DEMUXER_TYPE_EDL,
|
||||
.open = try_open_file,
|
||||
.load_timeline = build_mpv_edl_timeline,
|
||||
};
|
||||
+117
-92
@@ -81,7 +81,7 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
OPT_INTRANGE("buffersize", buffersize, 0, 1, 10 * 1024 * 1024,
|
||||
OPTDEF_INT(BIO_BUFFER_SIZE)),
|
||||
OPT_FLAG("allow-mimetype", allow_mimetype, 0),
|
||||
OPT_INTRANGE("probescore", probescore, 0, 0, 100),
|
||||
OPT_INTRANGE("probescore", probescore, 0, 1, AVPROBE_SCORE_MAX),
|
||||
OPT_STRING("cryptokey", cryptokey, 0),
|
||||
OPT_CHOICE("genpts-mode", genptsmode, 0,
|
||||
({"lavf", 1}, {"no", 0})),
|
||||
@@ -91,12 +91,60 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
.size = sizeof(struct demux_lavf_opts),
|
||||
.defaults = &(const struct demux_lavf_opts){
|
||||
.allow_mimetype = 1,
|
||||
// AVPROBE_SCORE_MAX/4 + 1 is the "recommended" limit. Below that, the
|
||||
// user is supposed to retry with larger probe sizes until a higher
|
||||
// value is reached.
|
||||
.probescore = AVPROBE_SCORE_MAX/4 + 1,
|
||||
},
|
||||
};
|
||||
|
||||
struct format_hack {
|
||||
const char *ff_name;
|
||||
const char *mime_type;
|
||||
int probescore;
|
||||
float analyzeduration;
|
||||
bool max_probe : 1; // use probescore only if max. probe size reached
|
||||
bool ignore : 1; // blacklisted
|
||||
bool no_stream : 1; // do not wrap struct stream as AVIOContext
|
||||
bool use_stream_ids : 1; // export the native stream IDs
|
||||
// Do not confuse player's position estimation (position is into external
|
||||
// segment, with e.g. HLS, player knows about the playlist main file only).
|
||||
bool clear_filepos : 1;
|
||||
};
|
||||
|
||||
#define BLACKLIST(fmt) {fmt, .ignore = true}
|
||||
|
||||
static const struct format_hack format_hacks[] = {
|
||||
// for webradios
|
||||
{"aac", "audio/aacp", 25, 0.5},
|
||||
{"aac", "audio/aac", 25, 0.5},
|
||||
|
||||
// some mp3 files don't detect correctly (usually id3v2 too large)
|
||||
{"mp3", "audio/mpeg", 24, 0.5},
|
||||
{"mp3", NULL, 24, .max_probe = true},
|
||||
|
||||
{"hls", .no_stream = true, .clear_filepos = true},
|
||||
{"mpeg", .use_stream_ids = true},
|
||||
{"mpegts", .use_stream_ids = true},
|
||||
|
||||
// Useless non-sense, sometimes breaks MLP2 subreader.c fallback
|
||||
BLACKLIST("tty"),
|
||||
// Let's open files with extremely generic extensions (.bin) with a
|
||||
// demuxer that doesn't have a probe function! NO.
|
||||
BLACKLIST("bin"),
|
||||
// Image demuxers, disabled in favor of demux_mf (for now):
|
||||
BLACKLIST("image"),
|
||||
BLACKLIST("image2"),
|
||||
BLACKLIST("image2pipe"),
|
||||
BLACKLIST("bmp_pipe"), BLACKLIST("dpx_pipe"), BLACKLIST("exr_pipe"),
|
||||
BLACKLIST("j2k_pipe"), BLACKLIST("png_pipe"), BLACKLIST("tiff_pipe"),
|
||||
BLACKLIST("jpeg_pipe"),
|
||||
{0}
|
||||
};
|
||||
|
||||
typedef struct lavf_priv {
|
||||
char *filename;
|
||||
const struct format_hack *format_hack;
|
||||
struct format_hack format_hack;
|
||||
AVInputFormat *avif;
|
||||
AVFormatContext *avfc;
|
||||
AVIOContext *pb;
|
||||
@@ -109,32 +157,22 @@ typedef struct lavf_priv {
|
||||
bool merge_track_metadata;
|
||||
} lavf_priv_t;
|
||||
|
||||
struct format_hack {
|
||||
const char *ff_name;
|
||||
const char *mime_type;
|
||||
int probescore;
|
||||
float analyzeduration;
|
||||
bool max_probe; // use probescore only if max. probe size reached
|
||||
};
|
||||
|
||||
static const struct format_hack format_hacks[] = {
|
||||
// for webradios
|
||||
{"aac", "audio/aacp", 25, 0.5},
|
||||
{"aac", "audio/aac", 25, 0.5},
|
||||
// some mp3 files don't detect correctly
|
||||
{"mp3", "audio/mpeg", 24, 0.5},
|
||||
{"mp3", NULL, 24, .max_probe = true},
|
||||
{0}
|
||||
};
|
||||
|
||||
static const char *const format_blacklist[] = {
|
||||
"tty", // Useless non-sense, sometimes breaks MLP2 subreader.c fallback
|
||||
// Image demuxers, disabled in favor of demux_mf:
|
||||
"image2", "image2pipe",
|
||||
"bmp_pipe", "dpx_pipe", "exr_pipe", "j2k_pipe", "png_pipe", "tiff_pipe",
|
||||
"jpeg_pipe",
|
||||
0
|
||||
};
|
||||
// At least mp4 has name="mov,mp4,m4a,3gp,3g2,mj2", so we split the name
|
||||
// on "," in general.
|
||||
static bool matches_avinputformat_name(struct lavf_priv *priv,
|
||||
const char *name)
|
||||
{
|
||||
const char *avifname = priv->avif->name;
|
||||
while (1) {
|
||||
const char *next = strchr(avifname, ',');
|
||||
if (!next)
|
||||
return !strcmp(avifname, name);
|
||||
int len = next - avifname;
|
||||
if (len == strlen(name) && !memcmp(avifname, name, len))
|
||||
return true;
|
||||
avifname = next + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
{
|
||||
@@ -254,6 +292,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
if (!lavfdopts->allow_mimetype || !mime_type)
|
||||
mime_type = "";
|
||||
|
||||
AVInputFormat *forced_format = NULL;
|
||||
const char *format = lavfdopts->format;
|
||||
if (!format)
|
||||
format = s->lavf_type;
|
||||
@@ -264,22 +303,13 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
list_formats(demuxer);
|
||||
return -1;
|
||||
}
|
||||
priv->avif = av_find_input_format(format);
|
||||
if (!priv->avif) {
|
||||
forced_format = av_find_input_format(format);
|
||||
if (!forced_format) {
|
||||
MP_FATAL(demuxer, "Unknown lavf format %s\n", format);
|
||||
return -1;
|
||||
}
|
||||
MP_VERBOSE(demuxer, "Forced lavf %s demuxer\n", priv->avif->long_name);
|
||||
goto success;
|
||||
}
|
||||
|
||||
// AVPROBE_SCORE_MAX/4 + 1 is the "recommended" limit. Below that, the user
|
||||
// is supposed to retry with larger probe sizes until a higher value is
|
||||
// reached.
|
||||
int min_probe = AVPROBE_SCORE_MAX/4 + 1;
|
||||
if (lavfdopts->probescore)
|
||||
min_probe = lavfdopts->probescore;
|
||||
|
||||
AVProbeData avpd = {
|
||||
// Disable file-extension matching with normal checks
|
||||
.filename = check <= DEMUX_CHECK_REQUEST ? priv->filename : "",
|
||||
@@ -291,56 +321,56 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
bool final_probe = false;
|
||||
do {
|
||||
int nsize = av_clip(avpd.buf_size * 2, INITIAL_PROBE_SIZE,
|
||||
PROBE_BUF_SIZE);
|
||||
bstr buf = stream_peek(s, nsize);
|
||||
if (buf.len <= avpd.buf_size)
|
||||
final_probe = true;
|
||||
memcpy(avpd.buf, buf.start, buf.len);
|
||||
avpd.buf_size = buf.len;
|
||||
|
||||
int score = 0;
|
||||
priv->avif = av_probe_input_format2(&avpd, avpd.buf_size > 0, &score);
|
||||
|
||||
if (forced_format) {
|
||||
priv->avif = forced_format;
|
||||
score = AVPROBE_SCORE_MAX;
|
||||
} else {
|
||||
int nsize = av_clip(avpd.buf_size * 2, INITIAL_PROBE_SIZE,
|
||||
PROBE_BUF_SIZE);
|
||||
bstr buf = stream_peek(s, nsize);
|
||||
if (buf.len <= avpd.buf_size)
|
||||
final_probe = true;
|
||||
memcpy(avpd.buf, buf.start, buf.len);
|
||||
avpd.buf_size = buf.len;
|
||||
|
||||
priv->avif = av_probe_input_format2(&avpd, avpd.buf_size > 0, &score);
|
||||
}
|
||||
|
||||
if (priv->avif) {
|
||||
MP_VERBOSE(demuxer, "Found '%s' at score=%d size=%d.\n",
|
||||
priv->avif->name, score, avpd.buf_size);
|
||||
MP_VERBOSE(demuxer, "Found '%s' at score=%d size=%d%s.\n",
|
||||
priv->avif->name, score, avpd.buf_size,
|
||||
forced_format ? " (forced)" : "");
|
||||
|
||||
priv->format_hack = NULL;
|
||||
for (int n = 0; format_hacks[n].ff_name; n++) {
|
||||
const struct format_hack *entry = &format_hacks[n];
|
||||
if (strcmp(entry->ff_name, priv->avif->name) != 0)
|
||||
if (!matches_avinputformat_name(priv, entry->ff_name))
|
||||
continue;
|
||||
if (entry->mime_type && strcasecmp(entry->mime_type, mime_type) != 0)
|
||||
continue;
|
||||
priv->format_hack = entry;
|
||||
priv->format_hack = *entry;
|
||||
break;
|
||||
}
|
||||
|
||||
if (score >= min_probe)
|
||||
if (score >= lavfdopts->probescore)
|
||||
break;
|
||||
|
||||
if (priv->format_hack) {
|
||||
if (score >= priv->format_hack->probescore &&
|
||||
(!priv->format_hack->max_probe || final_probe))
|
||||
break;
|
||||
}
|
||||
if (priv->format_hack.probescore &&
|
||||
score >= priv->format_hack.probescore &&
|
||||
(!priv->format_hack.max_probe || final_probe))
|
||||
break;
|
||||
}
|
||||
|
||||
priv->avif = NULL;
|
||||
priv->format_hack = NULL;
|
||||
priv->format_hack = (struct format_hack){0};
|
||||
} while (!final_probe);
|
||||
|
||||
av_free(avpd.buf);
|
||||
|
||||
if (priv->avif && !format) {
|
||||
for (int n = 0; format_blacklist[n]; n++) {
|
||||
if (strcmp(format_blacklist[n], priv->avif->name) == 0) {
|
||||
MP_VERBOSE(demuxer, "Format blacklisted.\n");
|
||||
priv->avif = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (priv->avif && !forced_format && priv->format_hack.ignore) {
|
||||
MP_VERBOSE(demuxer, "Format blacklisted.\n");
|
||||
priv->avif = NULL;
|
||||
}
|
||||
|
||||
if (!priv->avif) {
|
||||
@@ -348,30 +378,11 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
return -1;
|
||||
}
|
||||
|
||||
success:
|
||||
|
||||
demuxer->filetype = priv->avif->name;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool matches_avinputformat_name(struct lavf_priv *priv,
|
||||
const char *name)
|
||||
{
|
||||
// At least mp4 has name="mov,mp4,m4a,3gp,3g2,mj2", so we split the name
|
||||
// on "," in general.
|
||||
const char *avifname = priv->avif->name;
|
||||
while (1) {
|
||||
const char *next = strchr(avifname, ',');
|
||||
if (!next)
|
||||
return !strcmp(avifname, name);
|
||||
int len = next - avifname;
|
||||
if (len == strlen(name) && !memcmp(avifname, name, len))
|
||||
return true;
|
||||
avifname = next + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t char2int(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
@@ -597,8 +608,7 @@ static void handle_stream(demuxer_t *demuxer, int i)
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||
sh->default_track = 1;
|
||||
if (matches_avinputformat_name(priv, "mpeg") ||
|
||||
matches_avinputformat_name(priv, "mpegts"))
|
||||
if (priv->format_hack.use_stream_ids)
|
||||
sh->demuxer_id = st->id;
|
||||
AVDictionaryEntry *title = av_dict_get(st->metadata, "title", NULL, 0);
|
||||
if (title && title->value)
|
||||
@@ -662,6 +672,12 @@ static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
|
||||
#endif
|
||||
}
|
||||
|
||||
static int interrupt_cb(void *ctx)
|
||||
{
|
||||
struct demuxer *demuxer = ctx;
|
||||
return mp_cancel_test(demuxer->stream->cancel);
|
||||
}
|
||||
|
||||
static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
@@ -702,8 +718,8 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
lavfdopts->probesize);
|
||||
}
|
||||
|
||||
if (priv->format_hack && priv->format_hack->analyzeduration)
|
||||
analyze_duration = priv->format_hack->analyzeduration;
|
||||
if (priv->format_hack.analyzeduration)
|
||||
analyze_duration = priv->format_hack.analyzeduration;
|
||||
if (lavfdopts->analyzeduration)
|
||||
analyze_duration = lavfdopts->analyzeduration;
|
||||
if (analyze_duration > 0) {
|
||||
@@ -717,7 +733,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
if ((priv->avif->flags & AVFMT_NOFILE) ||
|
||||
demuxer->stream->type == STREAMTYPE_AVDEVICE ||
|
||||
matches_avinputformat_name(priv, "hls"))
|
||||
priv->format_hack.no_stream)
|
||||
{
|
||||
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log, opts);
|
||||
// This might be incorrect.
|
||||
@@ -750,6 +766,11 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
av_dict_set(&dopts, "rtsp_transport", transport, 0);
|
||||
}
|
||||
|
||||
avfc->interrupt_callback = (AVIOInterruptCB){
|
||||
.callback = interrupt_cb,
|
||||
.opaque = demuxer,
|
||||
};
|
||||
|
||||
mp_set_avdict(&dopts, lavfdopts->avopts);
|
||||
|
||||
if (avformat_open_input(&avfc, priv->filename, priv->avif, &dopts) < 0) {
|
||||
@@ -856,6 +877,10 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
priv->last_pts = dp->dts * AV_TIME_BASE;
|
||||
}
|
||||
av_free_packet(pkt);
|
||||
|
||||
if (priv->format_hack.clear_filepos)
|
||||
dp->pos = -1;
|
||||
|
||||
demux_add_packet(stream, dp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
+59
-59
@@ -168,7 +168,6 @@ typedef struct mkv_demuxer {
|
||||
mkv_index_t *indexes;
|
||||
size_t num_indexes;
|
||||
bool index_complete;
|
||||
uint64_t deferred_cues;
|
||||
|
||||
struct header_elem {
|
||||
int32_t id;
|
||||
@@ -183,6 +182,8 @@ typedef struct mkv_demuxer {
|
||||
int subtitle_preroll;
|
||||
|
||||
bool index_has_durations;
|
||||
|
||||
bool eof_warning;
|
||||
} mkv_demuxer_t;
|
||||
|
||||
#define REALHEADER_SIZE 16
|
||||
@@ -704,8 +705,6 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
mkv_d->deferred_cues = 0;
|
||||
|
||||
if (opts->index_mode != 1) {
|
||||
ebml_read_skip(demuxer->log, -1, s);
|
||||
return 0;
|
||||
@@ -750,26 +749,6 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void read_deferred_cues(demuxer_t *demuxer)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
if (mkv_d->deferred_cues) {
|
||||
int64_t pos = mkv_d->deferred_cues;
|
||||
mkv_d->deferred_cues = 0;
|
||||
if (!stream_seek(s, pos)) {
|
||||
MP_WARN(demuxer, "Failed to seek to cues\n");
|
||||
return;
|
||||
}
|
||||
if (ebml_read_id(s) != MATROSKA_ID_CUES) {
|
||||
MP_WARN(demuxer, "Expected element not found\n");
|
||||
return;
|
||||
}
|
||||
demux_mkv_read_cues(demuxer);
|
||||
}
|
||||
}
|
||||
|
||||
static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
@@ -1009,9 +988,6 @@ static int demux_mkv_read_attachments(demuxer_t *demuxer)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_header_element(struct demuxer *demuxer, uint32_t id,
|
||||
int64_t at_filepos);
|
||||
|
||||
static struct header_elem *get_header_element(struct demuxer *demuxer,
|
||||
uint32_t id,
|
||||
int64_t element_filepos)
|
||||
@@ -1075,12 +1051,6 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer)
|
||||
MP_DBG(demuxer, "Element 0x%x at %"PRIu64".\n",
|
||||
(unsigned)seek->seek_id, pos);
|
||||
get_header_element(demuxer, seek->seek_id, pos);
|
||||
// This is nice to warn against incomplete files.
|
||||
int64_t end = 0;
|
||||
stream_control(s, STREAM_CTRL_GET_SIZE, &end);
|
||||
if (pos >= end)
|
||||
MP_WARN(demuxer, "SeekHead position beyond "
|
||||
"end of file - incomplete file?\n");
|
||||
}
|
||||
out:
|
||||
MP_VERBOSE(demuxer, "\\---- [ parsing seek head ] ---------\n");
|
||||
@@ -1118,6 +1088,45 @@ skip:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_deferred_element(struct demuxer *demuxer,
|
||||
struct header_elem *elem)
|
||||
{
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
if (elem->parsed)
|
||||
return 0;
|
||||
elem->parsed = true;
|
||||
MP_VERBOSE(demuxer, "Seeking to %"PRIu64" to read header element 0x%x.\n",
|
||||
elem->pos, (unsigned)elem->id);
|
||||
if (!stream_seek(s, elem->pos)) {
|
||||
MP_WARN(demuxer, "Failed to seek when reading header element.\n");
|
||||
return 0;
|
||||
}
|
||||
if (ebml_read_id(s) != elem->id) {
|
||||
MP_ERR(demuxer, "Expected element 0x%x not found\n",
|
||||
(unsigned int)elem->id);
|
||||
return 0;
|
||||
}
|
||||
elem->parsed = false; // don't make read_header_element skip it
|
||||
return read_header_element(demuxer, elem->id, elem->pos);
|
||||
}
|
||||
|
||||
static void read_deferred_cues(demuxer_t *demuxer)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
|
||||
if (mkv_d->index_complete || opts->index_mode != 1)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < mkv_d->num_headers; n++) {
|
||||
struct header_elem *elem = &mkv_d->headers[n];
|
||||
|
||||
if (elem->id == MATROSKA_ID_CUES)
|
||||
read_deferred_element(demuxer, elem);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_coverart(struct demuxer *demuxer)
|
||||
{
|
||||
for (int n = 0; n < demuxer->num_attachments; n++) {
|
||||
@@ -1283,8 +1292,6 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
return 1;
|
||||
}
|
||||
|
||||
sh_v->coded_width = track->v_width;
|
||||
sh_v->coded_height = track->v_height;
|
||||
sh_v->extradata = talloc_memdup(sh_v, extradata, extradata_size);
|
||||
sh_v->extradata_len = extradata_size;
|
||||
if (!sh->codec) {
|
||||
@@ -1791,6 +1798,9 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t end = 0;
|
||||
stream_control(s, STREAM_CTRL_GET_SIZE, &end);
|
||||
|
||||
// Read headers that come after the first cluster (i.e. require seeking).
|
||||
// Note: reading might increase ->num_headers.
|
||||
// Likewise, ->headers might be reallocated.
|
||||
@@ -1798,34 +1808,22 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
struct header_elem *elem = &mkv_d->headers[n];
|
||||
if (elem->parsed)
|
||||
continue;
|
||||
elem->parsed = true;
|
||||
// Warn against incomplete files and skip headers outside of range.
|
||||
if (elem->pos >= end) {
|
||||
elem->parsed = true; // don't bother if file is incomplete
|
||||
if (!mkv_d->eof_warning) {
|
||||
MP_WARN(demuxer, "SeekHead position beyond "
|
||||
"end of file - incomplete file?\n");
|
||||
mkv_d->eof_warning = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (elem->id == MATROSKA_ID_CUES) {
|
||||
// Read cues when they are needed, to avoid seeking on opening.
|
||||
MP_VERBOSE(demuxer, "Deferring reading cues.\n");
|
||||
mkv_d->deferred_cues = elem->pos;
|
||||
continue;
|
||||
}
|
||||
MP_VERBOSE(demuxer, "Seeking to %"PRIu64" to read header element 0x%x.\n",
|
||||
elem->pos, (unsigned)elem->id);
|
||||
int64_t end = 0;
|
||||
stream_control(s, STREAM_CTRL_GET_SIZE, &end);
|
||||
if (elem->pos >= end) {
|
||||
MP_WARN(demuxer, "SeekHead position beyond "
|
||||
"end of file - incomplete file?\n");
|
||||
continue;
|
||||
}
|
||||
if (!stream_seek(s, elem->pos)) {
|
||||
MP_WARN(demuxer, "Failed to seek when reading header element.\n");
|
||||
continue;
|
||||
}
|
||||
if (ebml_read_id(s) != elem->id) {
|
||||
MP_ERR(demuxer, "Expected element 0x%x not found\n",
|
||||
(unsigned int)elem->id);
|
||||
continue;
|
||||
}
|
||||
elem->parsed = false; // don't make read_header_element skip it
|
||||
int res = read_header_element(demuxer, elem->id, elem->pos);
|
||||
if (res < 0)
|
||||
if (read_deferred_element(demuxer, elem) < 0)
|
||||
return -1;
|
||||
}
|
||||
if (!stream_seek(s, start_pos)) {
|
||||
@@ -2524,6 +2522,8 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
break;
|
||||
if (s->eof)
|
||||
return -1;
|
||||
if (demux_cancel_test(demuxer))
|
||||
return -1;
|
||||
if (id == EBML_ID_EBML && stream_tell(s) >= mkv_d->segment_end) {
|
||||
// Appended segment - don't use its clusters, consider this EOF.
|
||||
stream_seek(s, stream_tell(s) - 4);
|
||||
@@ -2882,12 +2882,12 @@ static void mkv_free(struct demuxer *demuxer)
|
||||
const demuxer_desc_t demuxer_desc_matroska = {
|
||||
.name = "mkv",
|
||||
.desc = "Matroska",
|
||||
.type = DEMUXER_TYPE_MATROSKA,
|
||||
.open = demux_mkv_open,
|
||||
.fill_buffer = demux_mkv_fill_buffer,
|
||||
.close = mkv_free,
|
||||
.seek = demux_mkv_seek,
|
||||
.control = demux_mkv_control
|
||||
.control = demux_mkv_control,
|
||||
.load_timeline = build_ordered_chapter_timeline,
|
||||
};
|
||||
|
||||
bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
|
||||
|
||||
@@ -32,15 +32,37 @@
|
||||
|
||||
#include "talloc.h"
|
||||
|
||||
#include "player/core.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
#include "demux/demux.h"
|
||||
#include "demux/timeline.h"
|
||||
#include "demux/matroska.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "common/common.h"
|
||||
#include "common/playlist.h"
|
||||
#include "stream/stream.h"
|
||||
|
||||
struct tl_ctx {
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global;
|
||||
|
||||
struct demuxer *demuxer;
|
||||
|
||||
struct demuxer **sources;
|
||||
int num_sources;
|
||||
|
||||
struct timeline_part *timeline;
|
||||
int num_parts;
|
||||
|
||||
struct matroska_segment_uid *uids;
|
||||
uint64_t start_time; // When the next part should start on the complete timeline.
|
||||
uint64_t missing_time; // Total missing time so far.
|
||||
uint64_t last_end_time; // When the last part ended on the complete timeline.
|
||||
int num_chapters; // Total number of expected chapters.
|
||||
};
|
||||
|
||||
struct find_entry {
|
||||
char *name;
|
||||
int matchlen;
|
||||
@@ -127,10 +149,10 @@ static char **find_files(const char *original_file)
|
||||
return results;
|
||||
}
|
||||
|
||||
static int enable_cache(struct MPContext *mpctx, struct stream **stream,
|
||||
static int enable_cache(struct mpv_global *global, struct stream **stream,
|
||||
struct demuxer **demuxer, struct demuxer_params *params)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct MPOpts *opts = global->opts;
|
||||
|
||||
if (!stream_wants_cache(*stream, &opts->stream_cache))
|
||||
return 0;
|
||||
@@ -139,7 +161,7 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream,
|
||||
free_demuxer(*demuxer);
|
||||
free_stream(*stream);
|
||||
|
||||
*stream = stream_open(filename, mpctx->global);
|
||||
*stream = stream_open(filename, global);
|
||||
if (!*stream) {
|
||||
talloc_free(filename);
|
||||
return -1;
|
||||
@@ -147,7 +169,7 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream,
|
||||
|
||||
stream_enable_cache(stream, &opts->stream_cache);
|
||||
|
||||
*demuxer = demux_open(*stream, "mkv", params, mpctx->global);
|
||||
*demuxer = demux_open(*stream, "mkv", params, global);
|
||||
if (!*demuxer) {
|
||||
talloc_free(filename);
|
||||
free_stream(*stream);
|
||||
@@ -158,185 +180,161 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool has_source_request(struct matroska_segment_uid *uids,
|
||||
int num_sources,
|
||||
static bool has_source_request(struct tl_ctx *ctx,
|
||||
struct matroska_segment_uid *new_uid)
|
||||
{
|
||||
for (int i = 0; i < num_sources; ++i) {
|
||||
if (demux_matroska_uid_cmp(uids + i, new_uid))
|
||||
for (int i = 0; i < ctx->num_sources; ++i) {
|
||||
if (demux_matroska_uid_cmp(&ctx->uids[i], new_uid))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// segment = get Nth segment of a multi-segment file
|
||||
static bool check_file_seg(struct MPContext *mpctx, struct demuxer ***sources,
|
||||
int *num_sources, struct matroska_segment_uid **uids,
|
||||
char *filename, int segment)
|
||||
static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
|
||||
{
|
||||
bool was_valid = false;
|
||||
struct demuxer_params params = {
|
||||
.matroska_num_wanted_uids = *num_sources,
|
||||
.matroska_wanted_uids = *uids,
|
||||
.matroska_num_wanted_uids = ctx->num_sources,
|
||||
.matroska_wanted_uids = ctx->uids,
|
||||
.matroska_wanted_segment = segment,
|
||||
.matroska_was_valid = &was_valid,
|
||||
};
|
||||
struct stream *s = stream_open(filename, mpctx->global);
|
||||
struct stream *s = stream_open(filename, ctx->global);
|
||||
if (!s)
|
||||
return false;
|
||||
struct demuxer *d = demux_open(s, "mkv", ¶ms, mpctx->global);
|
||||
struct demuxer *d = demux_open(s, "mkv", ¶ms, ctx->global);
|
||||
|
||||
if (!d) {
|
||||
free_stream(s);
|
||||
return was_valid;
|
||||
}
|
||||
if (d->type == DEMUXER_TYPE_MATROSKA) {
|
||||
struct matroska_data *m = &d->matroska_data;
|
||||
|
||||
for (int i = 1; i < *num_sources; i++) {
|
||||
struct matroska_segment_uid *uid = *uids + i;
|
||||
if ((*sources)[i])
|
||||
continue;
|
||||
/* Accept the source if the segment uid matches and the edition
|
||||
* either matches or isn't specified. */
|
||||
if (!memcmp(uid->segment, m->uid.segment, 16) &&
|
||||
(!uid->edition || uid->edition == m->uid.edition))
|
||||
{
|
||||
MP_INFO(mpctx, "Match for source %d: %s\n", i, d->filename);
|
||||
struct matroska_data *m = &d->matroska_data;
|
||||
|
||||
for (int j = 0; j < m->num_ordered_chapters; j++) {
|
||||
struct matroska_chapter *c = m->ordered_chapters + j;
|
||||
for (int i = 1; i < ctx->num_sources; i++) {
|
||||
struct matroska_segment_uid *uid = &ctx->uids[i];
|
||||
if (ctx->sources[i])
|
||||
continue;
|
||||
/* Accept the source if the segment uid matches and the edition
|
||||
* either matches or isn't specified. */
|
||||
if (!memcmp(uid->segment, m->uid.segment, 16) &&
|
||||
(!uid->edition || uid->edition == m->uid.edition))
|
||||
{
|
||||
MP_INFO(ctx, "Match for source %d: %s\n", i, d->filename);
|
||||
|
||||
if (!c->has_segment_uid)
|
||||
continue;
|
||||
for (int j = 0; j < m->num_ordered_chapters; j++) {
|
||||
struct matroska_chapter *c = m->ordered_chapters + j;
|
||||
|
||||
if (has_source_request(*uids, *num_sources, &c->uid))
|
||||
continue;
|
||||
|
||||
/* Set the requested segment. */
|
||||
MP_TARRAY_GROW(NULL, *uids, *num_sources);
|
||||
(*uids)[*num_sources] = c->uid;
|
||||
|
||||
/* Add a new source slot. */
|
||||
MP_TARRAY_APPEND(NULL, *sources, *num_sources, NULL);
|
||||
}
|
||||
|
||||
if (enable_cache(mpctx, &s, &d, ¶ms) < 0)
|
||||
if (!c->has_segment_uid)
|
||||
continue;
|
||||
|
||||
(*sources)[i] = d;
|
||||
return true;
|
||||
if (has_source_request(ctx, &c->uid))
|
||||
continue;
|
||||
|
||||
/* Set the requested segment. */
|
||||
MP_TARRAY_GROW(NULL, ctx->uids, ctx->num_sources);
|
||||
ctx->uids[ctx->num_sources] = c->uid;
|
||||
|
||||
/* Add a new source slot. */
|
||||
MP_TARRAY_APPEND(NULL, ctx->sources, ctx->num_sources, NULL);
|
||||
}
|
||||
|
||||
params.matroska_wanted_uids = ctx->uids; // potentially reallocated, same data
|
||||
if (enable_cache(ctx->global, &s, &d, ¶ms) < 0)
|
||||
continue;
|
||||
|
||||
ctx->sources[i] = d;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
free_demuxer(d);
|
||||
free_stream(s);
|
||||
return was_valid;
|
||||
}
|
||||
|
||||
static void check_file(struct MPContext *mpctx, struct demuxer ***sources,
|
||||
int *num_sources, struct matroska_segment_uid **uids,
|
||||
char *filename, int first)
|
||||
static void check_file(struct tl_ctx *ctx, char *filename, int first)
|
||||
{
|
||||
for (int segment = first; ; segment++) {
|
||||
if (!check_file_seg(mpctx, sources, num_sources, uids, filename, segment))
|
||||
if (!check_file_seg(ctx, filename, segment))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool missing(struct demuxer **sources, int num_sources)
|
||||
static bool missing(struct tl_ctx *ctx)
|
||||
{
|
||||
for (int i = 0; i < num_sources; i++) {
|
||||
if (!sources[i])
|
||||
for (int i = 0; i < ctx->num_sources; i++) {
|
||||
if (!ctx->sources[i])
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int find_ordered_chapter_sources(struct MPContext *mpctx,
|
||||
struct demuxer ***sources,
|
||||
int *num_sources,
|
||||
struct matroska_segment_uid **uids)
|
||||
static void find_ordered_chapter_sources(struct tl_ctx *ctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct MPOpts *opts = ctx->global->opts;
|
||||
void *tmp = talloc_new(NULL);
|
||||
int num_filenames = 0;
|
||||
char **filenames = NULL;
|
||||
if (*num_sources > 1) {
|
||||
char *main_filename = mpctx->demuxer->filename;
|
||||
MP_INFO(mpctx, "This file references data from other sources.\n");
|
||||
if (ctx->num_sources > 1) {
|
||||
char *main_filename = ctx->demuxer->filename;
|
||||
MP_INFO(ctx, "This file references data from other sources.\n");
|
||||
if (opts->ordered_chapters_files && opts->ordered_chapters_files[0]) {
|
||||
MP_INFO(mpctx, "Loading references from '%s'.\n",
|
||||
MP_INFO(ctx, "Loading references from '%s'.\n",
|
||||
opts->ordered_chapters_files);
|
||||
struct playlist *pl =
|
||||
playlist_parse_file(opts->ordered_chapters_files, mpctx->global);
|
||||
playlist_parse_file(opts->ordered_chapters_files, ctx->global);
|
||||
talloc_steal(tmp, pl);
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next)
|
||||
MP_TARRAY_APPEND(tmp, filenames, num_filenames, e->filename);
|
||||
} else if (mpctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
|
||||
MP_WARN(mpctx, "Playback source is not a "
|
||||
} else if (ctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
|
||||
MP_WARN(ctx, "Playback source is not a "
|
||||
"normal disk file. Will not search for related files.\n");
|
||||
} else {
|
||||
MP_INFO(mpctx, "Will scan other files in the "
|
||||
MP_INFO(ctx, "Will scan other files in the "
|
||||
"same directory to find referenced sources.\n");
|
||||
filenames = find_files(main_filename);
|
||||
num_filenames = MP_TALLOC_AVAIL(filenames);
|
||||
talloc_steal(tmp, filenames);
|
||||
}
|
||||
// Possibly get further segments appended to the first segment
|
||||
check_file(mpctx, sources, num_sources, uids, main_filename, 1);
|
||||
check_file(ctx, main_filename, 1);
|
||||
}
|
||||
|
||||
int old_source_count;
|
||||
do {
|
||||
old_source_count = *num_sources;
|
||||
old_source_count = ctx->num_sources;
|
||||
for (int i = 0; i < num_filenames; i++) {
|
||||
if (!missing(*sources, *num_sources))
|
||||
if (!missing(ctx))
|
||||
break;
|
||||
MP_VERBOSE(mpctx, "Checking file %s\n", filenames[i]);
|
||||
check_file(mpctx, sources, num_sources, uids, filenames[i], 0);
|
||||
MP_VERBOSE(ctx, "Checking file %s\n", filenames[i]);
|
||||
check_file(ctx, filenames[i], 0);
|
||||
}
|
||||
} while (old_source_count != *num_sources);
|
||||
} while (old_source_count != ctx->num_sources);
|
||||
|
||||
if (missing(*sources, *num_sources)) {
|
||||
MP_ERR(mpctx, "Failed to find ordered chapter part!\n");
|
||||
if (missing(ctx)) {
|
||||
MP_ERR(ctx, "Failed to find ordered chapter part!\n");
|
||||
int j = 1;
|
||||
for (int i = 1; i < *num_sources; i++) {
|
||||
if ((*sources)[i]) {
|
||||
struct matroska_segment_uid *source_uid = *uids + i;
|
||||
struct matroska_segment_uid *target_uid = *uids + j;
|
||||
(*sources)[j] = (*sources)[i];
|
||||
memmove(target_uid, source_uid, sizeof(*source_uid));
|
||||
for (int i = 1; i < ctx->num_sources; i++) {
|
||||
if (ctx->sources[i]) {
|
||||
ctx->sources[j] = ctx->sources[i];
|
||||
ctx->uids[j] = ctx->uids[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
*num_sources = j;
|
||||
ctx->num_sources = j;
|
||||
}
|
||||
|
||||
talloc_free(tmp);
|
||||
return *num_sources;
|
||||
}
|
||||
|
||||
struct timeline_info {
|
||||
struct demuxer **sources;
|
||||
int num_sources;
|
||||
struct timeline_part **timeline;
|
||||
int num_parts;
|
||||
|
||||
uint64_t start_time; // When the next part should start on the complete timeline.
|
||||
uint64_t missing_time; // Total missing time so far.
|
||||
uint64_t last_end_time; // When the last part ended on the complete timeline.
|
||||
int num_chapters; // Total number of expected chapters.
|
||||
};
|
||||
|
||||
struct inner_timeline_info {
|
||||
uint64_t skip; // Amount of time to skip.
|
||||
uint64_t limit; // How much time is expected for the parent chapter.
|
||||
};
|
||||
|
||||
static int64_t add_timeline_part(struct MPContext *mpctx,
|
||||
struct timeline_info *ctx,
|
||||
static int64_t add_timeline_part(struct tl_ctx *ctx,
|
||||
struct demuxer *source,
|
||||
uint64_t start)
|
||||
{
|
||||
@@ -345,18 +343,18 @@ static int64_t add_timeline_part(struct MPContext *mpctx,
|
||||
* early; we don't want to try seeking over a one frame gap. */
|
||||
int64_t join_diff = start - ctx->last_end_time;
|
||||
if (ctx->num_parts == 0
|
||||
|| FFABS(join_diff) > mpctx->opts->chapter_merge_threshold * 1e6
|
||||
|| source != (*ctx->timeline)[ctx->num_parts - 1].source)
|
||||
|| FFABS(join_diff) > ctx->global->opts->chapter_merge_threshold * 1e6
|
||||
|| source != ctx->timeline[ctx->num_parts - 1].source)
|
||||
{
|
||||
struct timeline_part new = {
|
||||
.start = ctx->start_time / 1e9,
|
||||
.source_start = start / 1e9,
|
||||
.source = source,
|
||||
};
|
||||
MP_TARRAY_APPEND(NULL, *ctx->timeline, ctx->num_parts, new);
|
||||
MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new);
|
||||
} else if (ctx->num_parts > 0 && join_diff) {
|
||||
// Chapter was merged at an inexact boundary; adjust timestamps to match.
|
||||
MP_VERBOSE(mpctx, "Merging timeline part %d with offset %g ms.\n",
|
||||
MP_VERBOSE(ctx, "Merging timeline part %d with offset %g ms.\n",
|
||||
ctx->num_parts, join_diff / 1e6);
|
||||
ctx->start_time += join_diff;
|
||||
return join_diff;
|
||||
@@ -365,9 +363,8 @@ static int64_t add_timeline_part(struct MPContext *mpctx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void build_timeline_loop(struct MPContext *mpctx,
|
||||
static void build_timeline_loop(struct tl_ctx *ctx,
|
||||
struct demux_chapter *chapters,
|
||||
struct timeline_info *ctx,
|
||||
struct inner_timeline_info *info,
|
||||
int current_source)
|
||||
{
|
||||
@@ -431,8 +428,7 @@ static void build_timeline_loop(struct MPContext *mpctx,
|
||||
chapter_length = source_length;
|
||||
}
|
||||
|
||||
join_diff = add_timeline_part(mpctx, ctx,
|
||||
linked_source, c->start);
|
||||
join_diff = add_timeline_part(ctx, linked_source, c->start);
|
||||
|
||||
/* If we merged two chapters into a single part due to them
|
||||
* being off by a few frames, we need to change the limit to
|
||||
@@ -453,7 +449,7 @@ static void build_timeline_loop(struct MPContext *mpctx,
|
||||
.skip = c->start,
|
||||
.limit = c->end
|
||||
};
|
||||
build_timeline_loop(mpctx, chapters, ctx, &new_info, j);
|
||||
build_timeline_loop(ctx, chapters, &new_info, j);
|
||||
// Already handled by the loop call.
|
||||
chapter_length = 0;
|
||||
}
|
||||
@@ -478,12 +474,12 @@ static void build_timeline_loop(struct MPContext *mpctx,
|
||||
ctx->missing_time += info->limit - local_starttime;
|
||||
}
|
||||
|
||||
static void check_track_compatibility(struct MPContext *mpctx)
|
||||
static void check_track_compatibility(struct timeline *tl)
|
||||
{
|
||||
struct demuxer *mainsrc = mpctx->track_layout;
|
||||
struct demuxer *mainsrc = tl->track_layout;
|
||||
|
||||
for (int n = 0; n < mpctx->num_timeline_parts; n++) {
|
||||
struct timeline_part *p = &mpctx->timeline[n];
|
||||
for (int n = 0; n < tl->num_parts; n++) {
|
||||
struct timeline_part *p = &tl->parts[n];
|
||||
if (p->source == mainsrc)
|
||||
continue;
|
||||
|
||||
@@ -493,12 +489,12 @@ static void check_track_compatibility(struct MPContext *mpctx)
|
||||
continue;
|
||||
|
||||
if (!demuxer_stream_by_demuxer_id(mainsrc, s->type, s->demuxer_id)) {
|
||||
MP_WARN(mpctx, "Source %s has %s stream with TID=%d, which "
|
||||
"is not present in the ordered chapters main "
|
||||
"file. This is a broken file. "
|
||||
"The additional stream is ignored.\n",
|
||||
p->source->filename, stream_type_name(s->type),
|
||||
s->demuxer_id);
|
||||
MP_WARN(tl, "Source %s has %s stream with TID=%d, which "
|
||||
"is not present in the ordered chapters main "
|
||||
"file. This is a broken file. "
|
||||
"The additional stream is ignored.\n",
|
||||
p->source->filename, stream_type_name(s->type),
|
||||
s->demuxer_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,45 +509,53 @@ static void check_track_compatibility(struct MPContext *mpctx)
|
||||
// There are actually many more things that in theory have to
|
||||
// match (though mpv's implementation doesn't care).
|
||||
if (s->codec && m->codec && strcmp(s->codec, m->codec) != 0)
|
||||
MP_WARN(mpctx, "Timeline segments have mismatching codec.\n");
|
||||
MP_WARN(tl, "Timeline segments have mismatching codec.\n");
|
||||
} else {
|
||||
MP_WARN(mpctx, "Source %s lacks %s stream with TID=%d, which "
|
||||
"is present in the ordered chapters main "
|
||||
"file. This is a broken file.\n",
|
||||
p->source->filename, stream_type_name(m->type),
|
||||
m->demuxer_id);
|
||||
MP_WARN(tl, "Source %s lacks %s stream with TID=%d, which "
|
||||
"is present in the ordered chapters main "
|
||||
"file. This is a broken file.\n",
|
||||
p->source->filename, stream_type_name(m->type),
|
||||
m->demuxer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void build_ordered_chapter_timeline(struct MPContext *mpctx)
|
||||
void build_ordered_chapter_timeline(struct timeline *tl)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct demuxer *demuxer = tl->demuxer;
|
||||
|
||||
if (!opts->ordered_chapters) {
|
||||
MP_INFO(mpctx, "File uses ordered chapters, but "
|
||||
"you have disabled support for them. Ignoring.\n");
|
||||
if (!demuxer->matroska_data.ordered_chapters)
|
||||
return;
|
||||
|
||||
if (!demuxer->global->opts->ordered_chapters) {
|
||||
MP_INFO(demuxer, "File uses ordered chapters, but "
|
||||
"you have disabled support for them. Ignoring.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
MP_INFO(mpctx, "File uses ordered chapters, will build "
|
||||
"edit timeline.\n");
|
||||
struct tl_ctx *ctx = talloc_ptrtype(tl, ctx);
|
||||
*ctx = (struct tl_ctx){
|
||||
.log = tl->log,
|
||||
.global = tl->global,
|
||||
.demuxer = demuxer,
|
||||
};
|
||||
|
||||
MP_INFO(ctx, "File uses ordered chapters, will build edit timeline.\n");
|
||||
|
||||
struct demuxer *demuxer = mpctx->demuxer;
|
||||
struct matroska_data *m = &demuxer->matroska_data;
|
||||
|
||||
// +1 because sources/uid_map[0] is original file even if all chapters
|
||||
// actually use other sources and need separate entries
|
||||
struct demuxer **sources = talloc_zero_array(NULL, struct demuxer *,
|
||||
m->num_ordered_chapters+1);
|
||||
sources[0] = mpctx->demuxer;
|
||||
struct matroska_segment_uid *uids =
|
||||
talloc_zero_array(NULL, struct matroska_segment_uid,
|
||||
m->num_ordered_chapters + 1);
|
||||
int num_sources = 1;
|
||||
memcpy(uids[0].segment, m->uid.segment, 16);
|
||||
uids[0].edition = 0;
|
||||
ctx->sources = talloc_zero_array(tl, struct demuxer *,
|
||||
m->num_ordered_chapters + 1);
|
||||
ctx->sources[0] = demuxer;
|
||||
ctx->num_sources = 1;
|
||||
|
||||
ctx->uids = talloc_zero_array(NULL, struct matroska_segment_uid,
|
||||
m->num_ordered_chapters + 1);
|
||||
ctx->uids[0] = m->uid;
|
||||
ctx->uids[0].edition = 0;
|
||||
|
||||
for (int i = 0; i < m->num_ordered_chapters; i++) {
|
||||
struct matroska_chapter *c = m->ordered_chapters + i;
|
||||
@@ -562,39 +566,33 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx)
|
||||
if (!c->has_segment_uid || demux_matroska_uid_cmp(&c->uid, &m->uid))
|
||||
continue;
|
||||
|
||||
if (has_source_request(uids, num_sources, &c->uid))
|
||||
if (has_source_request(ctx, &c->uid))
|
||||
continue;
|
||||
|
||||
memcpy(uids + num_sources, &c->uid, sizeof(c->uid));
|
||||
sources[num_sources] = NULL;
|
||||
num_sources++;
|
||||
ctx->uids[ctx->num_sources] = c->uid;
|
||||
ctx->sources[ctx->num_sources] = NULL;
|
||||
ctx->num_sources++;
|
||||
}
|
||||
|
||||
num_sources = find_ordered_chapter_sources(mpctx, &sources, &num_sources,
|
||||
&uids);
|
||||
talloc_free(uids);
|
||||
find_ordered_chapter_sources(ctx);
|
||||
|
||||
talloc_free(ctx->uids);
|
||||
ctx->uids = NULL;
|
||||
|
||||
struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, 0);
|
||||
struct demux_chapter *chapters =
|
||||
talloc_zero_array(NULL, struct demux_chapter, m->num_ordered_chapters);
|
||||
talloc_zero_array(tl, struct demux_chapter, m->num_ordered_chapters);
|
||||
// Stupid hack, because fuck everything.
|
||||
for (int n = 0; n < m->num_ordered_chapters; n++)
|
||||
chapters[n].pts = -1;
|
||||
struct timeline_info ctx = {
|
||||
.sources = sources,
|
||||
.num_sources = num_sources,
|
||||
.timeline = &timeline,
|
||||
.num_parts = 0,
|
||||
.start_time = 0,
|
||||
.missing_time = 0,
|
||||
.last_end_time = 0,
|
||||
.num_chapters = m->num_ordered_chapters
|
||||
};
|
||||
|
||||
ctx->timeline = talloc_array_ptrtype(tl, ctx->timeline, 0);
|
||||
ctx->num_chapters = m->num_ordered_chapters;
|
||||
|
||||
struct inner_timeline_info info = {
|
||||
.skip = 0,
|
||||
.limit = 0
|
||||
};
|
||||
build_timeline_loop(mpctx, chapters, &ctx, &info, 0);
|
||||
build_timeline_loop(ctx, chapters, &info, 0);
|
||||
|
||||
// Fuck everything (2): filter out all "unset" chapters.
|
||||
for (int n = m->num_ordered_chapters - 1; n >= 0; n--) {
|
||||
@@ -602,45 +600,45 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx)
|
||||
MP_TARRAY_REMOVE_AT(chapters, m->num_ordered_chapters, n);
|
||||
}
|
||||
|
||||
if (!ctx.num_parts) {
|
||||
if (!ctx->num_parts) {
|
||||
// None of the parts come from the file itself???
|
||||
// Broken file, but we need at least 1 valid timeline part - add a dummy.
|
||||
MP_WARN(mpctx, "Ordered chapters file with no parts?\n");
|
||||
MP_WARN(ctx, "Ordered chapters file with no parts?\n");
|
||||
struct timeline_part new = {
|
||||
.source = demuxer,
|
||||
};
|
||||
MP_TARRAY_APPEND(NULL, timeline, ctx.num_parts, new);
|
||||
MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new);
|
||||
}
|
||||
|
||||
struct timeline_part new = {
|
||||
.start = ctx.start_time / 1e9,
|
||||
.start = ctx->start_time / 1e9,
|
||||
};
|
||||
MP_TARRAY_APPEND(NULL, timeline, ctx.num_parts, new);
|
||||
MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new);
|
||||
|
||||
/* Ignore anything less than a millisecond when reporting missing time. If
|
||||
* users really notice less than a millisecond missing, maybe this can be
|
||||
* revisited. */
|
||||
if (ctx.missing_time >= 1e6) {
|
||||
MP_ERR(mpctx, "There are %.3f seconds missing from the timeline!\n",
|
||||
ctx.missing_time / 1e9);
|
||||
if (ctx->missing_time >= 1e6) {
|
||||
MP_ERR(ctx, "There are %.3f seconds missing from the timeline!\n",
|
||||
ctx->missing_time / 1e9);
|
||||
}
|
||||
talloc_free(mpctx->sources);
|
||||
mpctx->sources = sources;
|
||||
mpctx->num_sources = num_sources;
|
||||
mpctx->timeline = timeline;
|
||||
mpctx->num_timeline_parts = ctx.num_parts - 1;
|
||||
mpctx->num_chapters = m->num_ordered_chapters;
|
||||
mpctx->chapters = chapters;
|
||||
|
||||
tl->sources = ctx->sources;
|
||||
tl->num_sources = ctx->num_sources;
|
||||
tl->parts = ctx->timeline;
|
||||
tl->num_parts = ctx->num_parts - 1; // minus termination
|
||||
tl->chapters = chapters;
|
||||
tl->num_chapters = m->num_ordered_chapters;
|
||||
|
||||
// With Matroska, the "master" file usually dictates track layout etc.,
|
||||
// except maybe with playlist-like files.
|
||||
mpctx->track_layout = mpctx->timeline[0].source;
|
||||
for (int n = 0; n < mpctx->num_timeline_parts; n++) {
|
||||
if (mpctx->timeline[n].source == mpctx->demuxer) {
|
||||
mpctx->track_layout = mpctx->demuxer;
|
||||
tl->track_layout = tl->parts[0].source;
|
||||
for (int n = 0; n < tl->num_parts; n++) {
|
||||
if (tl->parts[n].source == ctx->demuxer) {
|
||||
tl->track_layout = ctx->demuxer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
check_track_compatibility(mpctx);
|
||||
check_track_compatibility(tl);
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "common/playlist.h"
|
||||
#include "options/path.h"
|
||||
#include "stream/stream.h"
|
||||
#include "stream/rar.h"
|
||||
#include "demux.h"
|
||||
|
||||
#define PROBE_SIZE (8 * 1024)
|
||||
@@ -188,6 +189,30 @@ static int parse_pls(struct pl_parser *p)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_rar(struct pl_parser *p)
|
||||
{
|
||||
if (RarProbe(p->s))
|
||||
return -1;
|
||||
if (p->probing)
|
||||
return 0;
|
||||
|
||||
int count;
|
||||
rar_file_t **files;
|
||||
if (RarParse(p->s, &count, &files))
|
||||
return -1;
|
||||
|
||||
p->pl->disable_safety = true; // make it load rar://
|
||||
char *prefix = mp_url_escape(p, p->real_stream->url, "~|");
|
||||
for (int n = 0; n < count; n++) {
|
||||
// stream_rar.c does the real work
|
||||
playlist_add_file(p->pl,
|
||||
talloc_asprintf(p, "rar://%s|%s", prefix, files[n]->name));
|
||||
RarFileDelete(files[n]);
|
||||
}
|
||||
talloc_free(files);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_txt(struct pl_parser *p)
|
||||
{
|
||||
if (!p->force)
|
||||
@@ -220,6 +245,7 @@ static const struct pl_format formats[] = {
|
||||
{"mov", parse_mov_rtsptext},
|
||||
{"pls", parse_pls,
|
||||
MIME_TYPES("audio/x-scpls")},
|
||||
{"rar", parse_rar},
|
||||
{"txt", parse_txt},
|
||||
};
|
||||
|
||||
|
||||
+4
-2
@@ -228,8 +228,10 @@ int ebml_resync_cluster(struct mp_log *log, stream_t *s)
|
||||
{
|
||||
int64_t pos = stream_tell(s);
|
||||
uint32_t last_4_bytes = 0;
|
||||
mp_err(log, "Corrupt file detected. "
|
||||
"Trying to resync starting from position %"PRId64"...\n", pos);
|
||||
if (!s->eof) {
|
||||
mp_err(log, "Corrupt file detected. "
|
||||
"Trying to resync starting from position %"PRId64"...\n", pos);
|
||||
}
|
||||
while (!s->eof) {
|
||||
// Assumes MATROSKA_ID_CLUSTER is 4 bytes, with no 0 bytes.
|
||||
if (last_4_bytes == MATROSKA_ID_CLUSTER) {
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#ifndef MPLAYER_MATROSKA_H
|
||||
#define MPLAYER_MATROSKA_H
|
||||
|
||||
struct timeline;
|
||||
void build_ordered_chapter_timeline(struct timeline *tl);
|
||||
|
||||
#define MKV_A_AAC_2MAIN "A_AAC/MPEG2/MAIN"
|
||||
#define MKV_A_AAC_2LC "A_AAC/MPEG2/LC"
|
||||
#define MKV_A_AAC_2SBR "A_AAC/MPEG2/LC/SBR"
|
||||
|
||||
@@ -83,7 +83,6 @@ typedef struct sh_video {
|
||||
float aspect; // aspect ratio stored in the file (for prescaling)
|
||||
int bitrate; // compressed bits/sec
|
||||
int bits_per_coded_sample;
|
||||
int coded_width, coded_height;
|
||||
unsigned char *extradata;
|
||||
int extradata_len;
|
||||
int disp_w, disp_h; // display size
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "common/common.h"
|
||||
#include "stream/stream.h"
|
||||
#include "demux.h"
|
||||
|
||||
#include "timeline.h"
|
||||
|
||||
struct timeline *timeline_load(struct mpv_global *global, struct mp_log *log,
|
||||
struct demuxer *demuxer)
|
||||
{
|
||||
if (!demuxer->desc->load_timeline)
|
||||
return NULL;
|
||||
|
||||
struct timeline *tl = talloc_ptrtype(NULL, tl);
|
||||
*tl = (struct timeline){
|
||||
.global = global,
|
||||
.log = log,
|
||||
.demuxer = demuxer,
|
||||
.track_layout = demuxer,
|
||||
};
|
||||
|
||||
demuxer->desc->load_timeline(tl);
|
||||
|
||||
if (tl->num_parts)
|
||||
return tl;
|
||||
timeline_destroy(tl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void timeline_destroy(struct timeline *tl)
|
||||
{
|
||||
if (!tl)
|
||||
return;
|
||||
for (int n = 0; n < tl->num_sources; n++) {
|
||||
struct demuxer *d = tl->sources[n];
|
||||
if (d != tl->demuxer) {
|
||||
struct stream *s = d->stream;
|
||||
free_demuxer(d);
|
||||
free_stream(s);
|
||||
}
|
||||
}
|
||||
talloc_free(tl);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef MP_TIMELINE_H_
|
||||
#define MP_TIMELINE_H_
|
||||
|
||||
struct timeline_part {
|
||||
double start;
|
||||
double source_start;
|
||||
struct demuxer *source;
|
||||
};
|
||||
|
||||
struct timeline {
|
||||
struct mpv_global *global;
|
||||
struct mp_log *log;
|
||||
|
||||
// main source
|
||||
struct demuxer *demuxer;
|
||||
|
||||
// All referenced files. The source file must be at sources[0].
|
||||
struct demuxer **sources;
|
||||
int num_sources;
|
||||
|
||||
// Segments to play, ordered by time. parts[num_parts] must be valid; its
|
||||
// start field sets the duration, and source must be NULL.
|
||||
struct timeline_part *parts;
|
||||
int num_parts;
|
||||
|
||||
struct demux_chapter *chapters;
|
||||
int num_chapters;
|
||||
|
||||
// Which source defines the overall track list (over the full timeline).
|
||||
struct demuxer *track_layout;
|
||||
};
|
||||
|
||||
struct timeline *timeline_load(struct mpv_global *global, struct mp_log *log,
|
||||
struct demuxer *demuxer);
|
||||
void timeline_destroy(struct timeline *tl);
|
||||
|
||||
#endif
|
||||
@@ -213,5 +213,3 @@
|
||||
# ? cycle sub-forced-only # toggle DVD forced subs
|
||||
# ? cycle program # cycle transport stream programs
|
||||
# ? stop # stop playback (quit or enter idle mode)
|
||||
# Cycle aspect ratio:
|
||||
# ? cycle_values video-aspect "16:9" "16:10" "4:3" "2.35:1" "-1"
|
||||
@@ -154,6 +154,13 @@ extern "C" {
|
||||
* "wid" option fully and letterbox the video (i.e. add black bars if the
|
||||
* aspect ratio of the window and the video mismatch).
|
||||
*
|
||||
* Setting the "input-vo-keyboard" may be required to get keyboard input
|
||||
* through the embedded window, if this is desired.
|
||||
*
|
||||
* For OpenGL integration (e.g. rendering video to a texture), a separate API
|
||||
* is available. Look at opengl_cb.h. This API does not include keyboard or
|
||||
* mouse input directly.
|
||||
*
|
||||
* Also see client API examples and the mpv manpage.
|
||||
*
|
||||
* Compatibility
|
||||
|
||||
+4
-1
@@ -186,11 +186,14 @@ int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts,
|
||||
* postprocessing, it will always bind and unbind FBOs itself. If
|
||||
* you want mpv to render on the main framebuffer, pass 0.
|
||||
* @param vp Viewport to render on. The renderer will essentially call:
|
||||
* glViewport(vp[0], vp[1], vp[2], vp[3]);
|
||||
* glViewport(0, 0, vp[2], vp[3]);
|
||||
* before rendering. The height (vp[3]) can be negative to flip the
|
||||
* image - the renderer will flip it before setting the viewport
|
||||
* (typically you want to flip the image if you are rendering
|
||||
* directly to the main framebuffer).
|
||||
* vp[0] and vp[1] should be set to 0.
|
||||
* The viewport width and height imply the target FBO or framebuffer's
|
||||
* size. It will always render to the full size of it.
|
||||
* @return the number of left frames in the internal queue to be rendered
|
||||
*/
|
||||
int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]);
|
||||
|
||||
+1
-1
@@ -509,7 +509,7 @@ check_statement_libs "compiler support for __sync built-ins" $_sync SYNC_BUILTIN
|
||||
_sync=$(defretval)
|
||||
|
||||
if test "$_atomic" = no && test "$_sync" = no && test "$_stdatomic" = no ; then
|
||||
die "your compiler must support either stdatomic.h, or __atomic, or __sync built-ins."
|
||||
echo "your compiler must support either stdatomic.h, or __atomic, or __sync built-ins."
|
||||
fi
|
||||
|
||||
check_compile "iconv" $_iconv ICONV waftools/fragments/iconv.c " " "-liconv" "-liconv $_ld_dl"
|
||||
|
||||
+2
-3
@@ -171,11 +171,13 @@ SOURCES = audio/audio.c \
|
||||
demux/demux_lavf.c \
|
||||
demux/demux_mf.c \
|
||||
demux/demux_mkv.c \
|
||||
demux/demux_mkv_timeline.c \
|
||||
demux/demux_playlist.c \
|
||||
demux/demux_raw.c \
|
||||
demux/demux_subreader.c \
|
||||
demux/ebml.c \
|
||||
demux/packet.c \
|
||||
demux/timeline.c \
|
||||
input/cmd_list.c \
|
||||
input/cmd_parse.c \
|
||||
input/event.c \
|
||||
@@ -216,9 +218,6 @@ SOURCES = audio/audio.c \
|
||||
player/scripting.c \
|
||||
player/sub.c \
|
||||
player/video.c \
|
||||
player/timeline/tl_matroska.c \
|
||||
player/timeline/tl_mpv_edl.c \
|
||||
player/timeline/tl_cue.c \
|
||||
stream/cache.c \
|
||||
stream/cache_file.c \
|
||||
stream/cookies.c \
|
||||
|
||||
@@ -717,6 +717,13 @@ int m_config_set_option_node(struct m_config *config, bstr name,
|
||||
if (r >= 0)
|
||||
r = m_config_set_option_raw(config, co, &val, flags);
|
||||
|
||||
if (mp_msg_test(config->log, MSGL_V)) {
|
||||
char *s = m_option_type_node.print(NULL, data);
|
||||
MP_VERBOSE(config, "Setting option '%.*s' = %s (flags = %d) -> %d\n",
|
||||
BSTR_P(name), s ? s : "?", flags, r);
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
m_option_free(co->opt, &val);
|
||||
return r;
|
||||
}
|
||||
|
||||
+2
-2
@@ -240,7 +240,7 @@ char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2)
|
||||
return bstrdup0(talloc_ctx, p1);
|
||||
|
||||
#if HAVE_DOS_PATHS
|
||||
if (p2.len >= 2 && p2.start[1] == ':'
|
||||
if ((p2.len >= 2 && p2.start[1] == ':')
|
||||
|| p2.start[0] == '\\' || p2.start[0] == '/')
|
||||
#else
|
||||
if (p2.start[0] == '/')
|
||||
@@ -251,7 +251,7 @@ char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2)
|
||||
int endchar1 = p1.start[p1.len - 1];
|
||||
#if HAVE_DOS_PATHS
|
||||
have_separator = endchar1 == '/' || endchar1 == '\\'
|
||||
|| p1.len == 2 && endchar1 == ':'; // "X:" only
|
||||
|| (p1.len == 2 && endchar1 == ':'); // "X:" only
|
||||
#else
|
||||
have_separator = endchar1 == '/';
|
||||
#endif
|
||||
|
||||
@@ -41,6 +41,11 @@ typedef struct { volatile unsigned long long v; } atomic_ullong;
|
||||
#define ATOMIC_VAR_INIT(x) \
|
||||
{.v = (x)}
|
||||
|
||||
#define memory_order_relaxed 1
|
||||
#define memory_order_seq_cst 2
|
||||
|
||||
#define atomic_load_explicit(p, e) atomic_load(p)
|
||||
|
||||
#if HAVE_ATOMIC_BUILTINS
|
||||
|
||||
#define atomic_load(p) \
|
||||
|
||||
@@ -376,7 +376,7 @@ void cocoa_set_input_context(struct input_ctx *input_context)
|
||||
if (cocoaModifiers & NSControlKeyMask)
|
||||
mask |= MP_KEY_MODIFIER_CTRL;
|
||||
if (LeftAltPressed(cocoaModifiers) ||
|
||||
RightAltPressed(cocoaModifiers) && ![self useAltGr])
|
||||
(RightAltPressed(cocoaModifiers) && ![self useAltGr]))
|
||||
mask |= MP_KEY_MODIFIER_ALT;
|
||||
if (cocoaModifiers & NSCommandKeyMask)
|
||||
mask |= MP_KEY_MODIFIER_META;
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ void set_playback_speed(struct MPContext *mpctx, double new_speed)
|
||||
|
||||
opts->playback_speed = new_speed;
|
||||
|
||||
if (!mpctx->d_audio)
|
||||
if (!mpctx->d_audio || mpctx->d_audio->afilter->initialized < 1)
|
||||
return;
|
||||
|
||||
recreate_audio_filters(mpctx);
|
||||
|
||||
+4
-5
@@ -2297,15 +2297,15 @@ static int panscan_property_helper(void *ctx, struct m_property *prop,
|
||||
static int mp_property_vo_flag(struct m_property *prop, int action, void *arg,
|
||||
int vo_ctrl, int *vo_var, MPContext *mpctx)
|
||||
{
|
||||
if (!mpctx->video_out)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
if (action == M_PROPERTY_SET) {
|
||||
int desired = !!*(int *) arg;
|
||||
if (*vo_var == desired)
|
||||
return M_PROPERTY_OK;
|
||||
if (mpctx->video_out->config_ok)
|
||||
if (mpctx->video_out) {
|
||||
vo_control(mpctx->video_out, vo_ctrl, 0);
|
||||
} else {
|
||||
*vo_var = desired;
|
||||
}
|
||||
return *vo_var == desired ? M_PROPERTY_OK : M_PROPERTY_ERROR;
|
||||
}
|
||||
return mp_property_generic_option(mpctx, prop, action, arg);
|
||||
@@ -3808,7 +3808,6 @@ static bool reinit_filters(MPContext *mpctx, enum stream_type mediatype)
|
||||
case STREAM_VIDEO:
|
||||
return reinit_video_filters(mpctx) >= 0;
|
||||
case STREAM_AUDIO:
|
||||
clear_audio_output_buffers(mpctx);
|
||||
return reinit_audio_filters(mpctx) >= 0;
|
||||
}
|
||||
return false;
|
||||
|
||||
+6
-17
@@ -26,6 +26,7 @@
|
||||
#include "common/common.h"
|
||||
#include "options/options.h"
|
||||
#include "sub/osd.h"
|
||||
#include "demux/timeline.h"
|
||||
|
||||
// definitions used internally by the core player code
|
||||
|
||||
@@ -41,12 +42,6 @@ enum stop_play_reason {
|
||||
PT_ERROR, // play next playlist entry (due to an error)
|
||||
};
|
||||
|
||||
struct timeline_part {
|
||||
double start;
|
||||
double source_start;
|
||||
struct demuxer *source;
|
||||
};
|
||||
|
||||
enum mp_osd_seek_info {
|
||||
OSD_SEEK_INFO_BAR = 1,
|
||||
OSD_SEEK_INFO_TEXT = 2,
|
||||
@@ -185,9 +180,10 @@ typedef struct MPContext {
|
||||
int64_t shown_vframes, shown_aframes;
|
||||
|
||||
struct stream *stream; // stream that was initially opened
|
||||
struct demuxer **sources;
|
||||
struct demuxer **sources; // all open demuxers
|
||||
int num_sources;
|
||||
|
||||
struct timeline *tl;
|
||||
struct timeline_part *timeline;
|
||||
int num_timeline_parts;
|
||||
int timeline_part;
|
||||
@@ -195,7 +191,7 @@ typedef struct MPContext {
|
||||
int num_chapters;
|
||||
double video_offset;
|
||||
|
||||
struct demuxer *demuxer;
|
||||
struct demuxer *demuxer; // can change with timeline
|
||||
struct mp_tags *filtered_tags;
|
||||
|
||||
struct track **tracks;
|
||||
@@ -416,8 +412,8 @@ bool mp_get_cache_idle(struct MPContext *mpctx);
|
||||
void update_window_title(struct MPContext *mpctx, bool force);
|
||||
void error_on_track(struct MPContext *mpctx, struct track *track);
|
||||
void stream_dump(struct MPContext *mpctx);
|
||||
int mpctx_run_non_blocking(struct MPContext *mpctx, void (*thread_fn)(void *arg),
|
||||
void *thread_arg);
|
||||
int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg),
|
||||
void *thread_arg);
|
||||
struct mpv_global *create_sub_global(struct MPContext *mpctx);
|
||||
|
||||
// osd.c
|
||||
@@ -478,13 +474,6 @@ void uninit_sub_renderer(struct MPContext *mpctx);
|
||||
void update_osd_sub_state(struct MPContext *mpctx, int order,
|
||||
struct osd_sub_state *out_state);
|
||||
|
||||
// timeline/tl_matroska.c
|
||||
void build_ordered_chapter_timeline(struct MPContext *mpctx);
|
||||
// timeline/tl_mpv_edl.c
|
||||
void build_mpv_edl_timeline(struct MPContext *mpctx);
|
||||
// timeline/tl_cue.c
|
||||
void build_cue_timeline(struct MPContext *mpctx);
|
||||
|
||||
// video.c
|
||||
void reset_video_state(struct MPContext *mpctx);
|
||||
int reinit_video_chain(struct MPContext *mpctx);
|
||||
|
||||
+99
-57
@@ -63,33 +63,41 @@ static void uninit_demuxer(struct MPContext *mpctx)
|
||||
{
|
||||
assert(!mpctx->d_video && !mpctx->d_audio &&
|
||||
!mpctx->d_sub[0] && !mpctx->d_sub[1]);
|
||||
for (int i = 0; i < mpctx->num_tracks; i++) {
|
||||
talloc_free(mpctx->tracks[i]);
|
||||
}
|
||||
mpctx->num_tracks = 0;
|
||||
for (int r = 0; r < NUM_PTRACKS; r++) {
|
||||
for (int t = 0; t < STREAM_TYPE_COUNT; t++)
|
||||
mpctx->current_track[r][t] = NULL;
|
||||
}
|
||||
mpctx->master_demuxer = NULL;
|
||||
for (int i = 0; i < mpctx->num_sources; i++) {
|
||||
uninit_stream_sub_decoders(mpctx->sources[i]);
|
||||
struct demuxer *demuxer = mpctx->sources[i];
|
||||
struct stream *stream = demuxer->stream;
|
||||
free_demuxer(demuxer);
|
||||
if (stream != mpctx->stream)
|
||||
free_stream(stream);
|
||||
}
|
||||
talloc_free(mpctx->sources);
|
||||
mpctx->sources = NULL;
|
||||
mpctx->track_layout = NULL;
|
||||
mpctx->demuxer = NULL;
|
||||
mpctx->num_sources = 0;
|
||||
talloc_free(mpctx->timeline);
|
||||
mpctx->timeline = NULL;
|
||||
mpctx->num_timeline_parts = 0;
|
||||
talloc_free(mpctx->chapters);
|
||||
mpctx->chapters = NULL;
|
||||
mpctx->num_chapters = 0;
|
||||
|
||||
// per-stream cached subtitle state
|
||||
for (int i = 0; i < mpctx->num_sources; i++)
|
||||
uninit_stream_sub_decoders(mpctx->sources[i]);
|
||||
|
||||
// close demuxers for external tracks
|
||||
for (int n = mpctx->num_tracks - 1; n >= 0; n--) {
|
||||
mpctx->tracks[n]->selected = false;
|
||||
mp_remove_track(mpctx, mpctx->tracks[n]);
|
||||
}
|
||||
for (int i = 0; i < mpctx->num_tracks; i++)
|
||||
talloc_free(mpctx->tracks[i]);
|
||||
mpctx->num_tracks = 0;
|
||||
|
||||
mpctx->timeline = NULL;
|
||||
mpctx->num_timeline_parts = 0;
|
||||
timeline_destroy(mpctx->tl);
|
||||
mpctx->tl = NULL;
|
||||
|
||||
free_demuxer(mpctx->master_demuxer);
|
||||
mpctx->master_demuxer = NULL;
|
||||
|
||||
talloc_free(mpctx->sources);
|
||||
mpctx->sources = NULL;
|
||||
mpctx->num_sources = 0;
|
||||
|
||||
mpctx->video_offset = 0;
|
||||
}
|
||||
|
||||
@@ -619,26 +627,35 @@ struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
|
||||
|
||||
bool mp_remove_track(struct MPContext *mpctx, struct track *track)
|
||||
{
|
||||
if (track->under_timeline)
|
||||
return false;
|
||||
if (!track->is_external)
|
||||
return false;
|
||||
assert(!track->under_timeline);
|
||||
|
||||
mp_deselect_track(mpctx, track);
|
||||
if (track->selected)
|
||||
return false;
|
||||
|
||||
struct demuxer *d = track->demuxer;
|
||||
|
||||
int index = 0;
|
||||
while (index < mpctx->num_tracks && mpctx->tracks[index] != track)
|
||||
index++;
|
||||
assert(index < mpctx->num_tracks);
|
||||
while (index + 1 < mpctx->num_tracks) {
|
||||
mpctx->tracks[index] = mpctx->tracks[index + 1];
|
||||
index++;
|
||||
}
|
||||
mpctx->num_tracks--;
|
||||
MP_TARRAY_REMOVE_AT(mpctx->tracks, mpctx->num_tracks, index);
|
||||
talloc_free(track);
|
||||
|
||||
// Close the demuxer, unless there is still a track using it. These are
|
||||
// all external tracks, so there are no complications due to the timeline
|
||||
// mechanism switching the track's demuxer dynamically.
|
||||
bool in_use = false;
|
||||
for (int n = mpctx->num_tracks - 1; n >= 0 && !in_use; n--)
|
||||
in_use |= mpctx->tracks[n]->demuxer == d;
|
||||
|
||||
if (!in_use) {
|
||||
struct stream *s = d->stream;
|
||||
free_demuxer(d);
|
||||
free_stream(s);
|
||||
}
|
||||
|
||||
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
|
||||
|
||||
return true;
|
||||
@@ -906,8 +923,8 @@ static void open_stream_thread(void *pctx)
|
||||
args->cancel, args->global);
|
||||
}
|
||||
|
||||
static struct stream *open_stream_async(struct MPContext *mpctx,
|
||||
char *filename, int stream_flags)
|
||||
static struct stream *open_stream_reentrant(struct MPContext *mpctx,
|
||||
char *filename, int stream_flags)
|
||||
{
|
||||
struct stream_open_args args = {
|
||||
.cancel = mpctx->playback_abort,
|
||||
@@ -915,7 +932,7 @@ static struct stream *open_stream_async(struct MPContext *mpctx,
|
||||
.filename = filename,
|
||||
.stream_flags = stream_flags,
|
||||
};
|
||||
mpctx_run_non_blocking(mpctx, open_stream_thread, &args);
|
||||
mpctx_run_reentrant(mpctx, open_stream_thread, &args);
|
||||
if (args.stream) {
|
||||
talloc_steal(args.stream, args.global);
|
||||
} else {
|
||||
@@ -927,7 +944,10 @@ static struct stream *open_stream_async(struct MPContext *mpctx,
|
||||
struct demux_open_args {
|
||||
struct stream *stream;
|
||||
struct mpv_global *global;
|
||||
struct demuxer *demux; // result
|
||||
struct mp_log *log;
|
||||
// results
|
||||
struct demuxer *demux;
|
||||
struct timeline *tl;
|
||||
};
|
||||
|
||||
static void open_demux_thread(void *pctx)
|
||||
@@ -936,19 +956,50 @@ static void open_demux_thread(void *pctx)
|
||||
struct stream *s = args->stream;
|
||||
struct mpv_global *global = args->global;
|
||||
args->demux = demux_open(s, global->opts->demuxer_name, NULL, global);
|
||||
if (args->demux)
|
||||
args->tl = timeline_load(global, args->log, args->demux);
|
||||
}
|
||||
|
||||
static struct demuxer *open_demux_async(struct MPContext *mpctx,
|
||||
struct stream *stream)
|
||||
static void open_demux_reentrant(struct MPContext *mpctx)
|
||||
{
|
||||
struct demux_open_args args = {stream, create_sub_global(mpctx)};
|
||||
mpctx_run_non_blocking(mpctx, open_demux_thread, &args);
|
||||
struct demux_open_args args = {
|
||||
.global = create_sub_global(mpctx),
|
||||
.stream = mpctx->stream,
|
||||
.log = mpctx->log,
|
||||
};
|
||||
mpctx_run_reentrant(mpctx, open_demux_thread, &args);
|
||||
if (args.demux) {
|
||||
talloc_steal(args.demux, args.global);
|
||||
mpctx->master_demuxer = args.demux;
|
||||
mpctx->tl = args.tl;
|
||||
} else {
|
||||
talloc_free(args.global);
|
||||
}
|
||||
return args.demux;
|
||||
}
|
||||
|
||||
static void load_timeline(struct MPContext *mpctx)
|
||||
{
|
||||
mpctx->track_layout = mpctx->master_demuxer;
|
||||
|
||||
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources,
|
||||
mpctx->master_demuxer);
|
||||
|
||||
if (mpctx->tl) {
|
||||
mpctx->timeline = mpctx->tl->parts;
|
||||
mpctx->num_timeline_parts = mpctx->tl->num_parts;
|
||||
mpctx->num_chapters = mpctx->tl->num_chapters;
|
||||
mpctx->chapters = demux_copy_chapter_data(mpctx->tl->chapters,
|
||||
mpctx->tl->num_chapters);
|
||||
mpctx->track_layout = mpctx->tl->track_layout;
|
||||
for (int n = 0; n < mpctx->tl->num_sources; n++) {
|
||||
if (mpctx->tl->sources[n] != mpctx->master_demuxer) {
|
||||
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources,
|
||||
mpctx->tl->sources[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_timeline(mpctx);
|
||||
}
|
||||
|
||||
// Start playing the current playlist entry.
|
||||
@@ -1029,8 +1080,8 @@ static void play_current_file(struct MPContext *mpctx)
|
||||
int stream_flags = STREAM_READ;
|
||||
if (!opts->load_unsafe_playlists)
|
||||
stream_flags |= mpctx->playing->stream_flags;
|
||||
mpctx->stream = open_stream_async(mpctx, mpctx->stream_open_filename,
|
||||
stream_flags);
|
||||
mpctx->stream = open_stream_reentrant(mpctx, mpctx->stream_open_filename,
|
||||
stream_flags);
|
||||
if (!mpctx->stream)
|
||||
goto terminate_playback;
|
||||
|
||||
@@ -1056,21 +1107,24 @@ goto_reopen_demuxer: ;
|
||||
|
||||
mp_nav_reset(mpctx);
|
||||
|
||||
mpctx->demuxer = open_demux_async(mpctx, mpctx->stream);
|
||||
if (!mpctx->demuxer) {
|
||||
open_demux_reentrant(mpctx);
|
||||
if (!mpctx->master_demuxer) {
|
||||
MP_ERR(mpctx, "Failed to recognize file format.\n");
|
||||
mpctx->error_playing = MPV_ERROR_UNKNOWN_FORMAT;
|
||||
goto terminate_playback;
|
||||
}
|
||||
mpctx->master_demuxer = mpctx->demuxer;
|
||||
mpctx->demuxer = mpctx->master_demuxer;
|
||||
|
||||
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, mpctx->demuxer);
|
||||
load_timeline(mpctx);
|
||||
|
||||
if (mpctx->demuxer->playlist) {
|
||||
int entry_stream_flags =
|
||||
(mpctx->demuxer->stream->safe_origin ? 0 : STREAM_SAFE_ONLY) |
|
||||
(mpctx->demuxer->stream->is_network ? STREAM_NETWORK_ONLY : 0);
|
||||
struct playlist *pl = mpctx->demuxer->playlist;
|
||||
int entry_stream_flags = 0;
|
||||
if (!pl->disable_safety) {
|
||||
entry_stream_flags = STREAM_SAFE_ONLY;
|
||||
if (mpctx->demuxer->stream->is_network)
|
||||
entry_stream_flags |= STREAM_NETWORK_ONLY;
|
||||
}
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next)
|
||||
e->stream_flags |= entry_stream_flags;
|
||||
transfer_playlist(mpctx, pl);
|
||||
@@ -1079,18 +1133,6 @@ goto_reopen_demuxer: ;
|
||||
goto terminate_playback;
|
||||
}
|
||||
|
||||
mpctx->track_layout = mpctx->demuxer;
|
||||
|
||||
if (mpctx->demuxer->matroska_data.ordered_chapters)
|
||||
build_ordered_chapter_timeline(mpctx);
|
||||
|
||||
if (mpctx->demuxer->type == DEMUXER_TYPE_EDL)
|
||||
build_mpv_edl_timeline(mpctx);
|
||||
|
||||
if (mpctx->demuxer->type == DEMUXER_TYPE_CUE)
|
||||
build_cue_timeline(mpctx);
|
||||
|
||||
print_timeline(mpctx);
|
||||
load_chapters(mpctx);
|
||||
add_demuxer_tracks(mpctx, mpctx->track_layout);
|
||||
|
||||
|
||||
@@ -971,6 +971,17 @@ static int script_get_screen_size(lua_State *L)
|
||||
return 3;
|
||||
}
|
||||
|
||||
static int script_get_screen_margins(lua_State *L)
|
||||
{
|
||||
struct MPContext *mpctx = get_mpctx(L);
|
||||
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd, OSDTYPE_EXTERNAL);
|
||||
lua_pushnumber(L, vo_res.ml);
|
||||
lua_pushnumber(L, vo_res.mt);
|
||||
lua_pushnumber(L, vo_res.mr);
|
||||
lua_pushnumber(L, vo_res.mb);
|
||||
return 4;
|
||||
}
|
||||
|
||||
static int script_get_mouse_pos(lua_State *L)
|
||||
{
|
||||
struct MPContext *mpctx = get_mpctx(L);
|
||||
@@ -1280,6 +1291,7 @@ static const struct fn_entry main_fns[] = {
|
||||
FN_ENTRY(set_osd_ass),
|
||||
FN_ENTRY(get_osd_resolution),
|
||||
FN_ENTRY(get_screen_size),
|
||||
FN_ENTRY(get_screen_margins),
|
||||
FN_ENTRY(get_mouse_pos),
|
||||
FN_ENTRY(get_time),
|
||||
FN_ENTRY(input_define_section),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
-- Compatibility shim for lua 5.2/5.3
|
||||
unpack = unpack or table.unpack
|
||||
|
||||
-- these are used internally by lua.c
|
||||
mp.UNKNOWN_TYPE.info = "this value is inserted if the C type is not supported"
|
||||
|
||||
+12
-10
@@ -3,7 +3,7 @@ local msg = require 'mp.msg'
|
||||
|
||||
local ytdl = {
|
||||
path = "youtube-dl",
|
||||
minver = "2014.11.26",
|
||||
minver = "2015.02.23.1",
|
||||
vercheck = nil,
|
||||
}
|
||||
|
||||
@@ -82,13 +82,10 @@ mp.add_hook("on_load", 10, function ()
|
||||
local format = mp.get_property("options/ytdl-format")
|
||||
|
||||
-- subformat workaround
|
||||
local subformat = "srt"
|
||||
if url:find("crunchyroll.com") then
|
||||
subformat = "ass"
|
||||
end
|
||||
local subformat = "ass/srt/best"
|
||||
|
||||
local command = {
|
||||
ytdl.path, "-J", "--flat-playlist", "--all-subs",
|
||||
ytdl.path, "--no-warnings", "-J", "--flat-playlist", "--all-subs",
|
||||
"--sub-format", subformat, "--no-playlist"
|
||||
}
|
||||
if (format ~= "") then
|
||||
@@ -209,8 +206,8 @@ mp.add_hook("on_load", 10, function ()
|
||||
mp.set_property("file-local-options/media-title", json.title)
|
||||
|
||||
-- add subtitles
|
||||
if not (json.subtitles == nil) then
|
||||
for lang, script in pairs(json.subtitles) do
|
||||
if not (json.requested_subtitles == nil) then
|
||||
for lang, sub_info in pairs(json.requested_subtitles) do
|
||||
msg.verbose("adding subtitle ["..lang.."]")
|
||||
|
||||
local slang = lang
|
||||
@@ -218,8 +215,13 @@ mp.add_hook("on_load", 10, function ()
|
||||
slang = lang:sub(1,2)
|
||||
end
|
||||
|
||||
mp.commandv("sub_add", "memory://"..script,
|
||||
"auto", lang.." "..subformat, slang)
|
||||
if not (sub_info.data == nil) then
|
||||
sub = "memory://"..sub_info.data
|
||||
else
|
||||
sub = sub_info.url
|
||||
end
|
||||
mp.commandv("sub_add", sub,
|
||||
"auto", lang.." "..sub_info.ext, slang)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+3
-1
@@ -408,7 +408,9 @@ int mp_initialize(struct MPContext *mpctx)
|
||||
m_config_set_option0(mpctx->mconfig, "load-scripts", "no");
|
||||
m_config_set_option0(mpctx->mconfig, "osc", "no");
|
||||
m_config_set_option0(mpctx->mconfig, "framedrop", "no");
|
||||
m_config_set_option0(mpctx->mconfig, "audio-channels", "stereo");
|
||||
// never use auto
|
||||
if (!opts->audio_output_channels.num)
|
||||
m_config_set_option0(mpctx->mconfig, "audio-channels", "stereo");
|
||||
mp_input_enable_section(mpctx->input, "encode", MP_INPUT_EXCLUSIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
+2
-2
@@ -283,8 +283,8 @@ static void *thread_wrapper(void *pctx)
|
||||
|
||||
// Run the thread_fn in a new thread. Wait until the thread returns, but while
|
||||
// waiting, process input and input commands.
|
||||
int mpctx_run_non_blocking(struct MPContext *mpctx, void (*thread_fn)(void *arg),
|
||||
void *thread_arg)
|
||||
int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg),
|
||||
void *thread_arg)
|
||||
{
|
||||
struct wrapper_args args = {mpctx, thread_fn, thread_arg};
|
||||
pthread_mutex_init(&args.mutex, NULL);
|
||||
|
||||
@@ -167,6 +167,8 @@ static char *create_fname(struct MPContext *mpctx, char *template,
|
||||
}
|
||||
case 'f':
|
||||
case 'F': {
|
||||
if (!mpctx->filename)
|
||||
goto error_exit;
|
||||
char *video_file = mp_basename(mpctx->filename);
|
||||
if (video_file) {
|
||||
char *name = video_file;
|
||||
|
||||
+2
-2
@@ -127,8 +127,8 @@ void uninit_sub_renderer(struct MPContext *mpctx)
|
||||
static void init_sub_renderer(struct MPContext *mpctx) {}
|
||||
void uninit_sub_renderer(struct MPContext *mpctx) {}
|
||||
|
||||
void mp_ass_configure_fonts(struct ass_renderer *a, struct MPOpts *b,
|
||||
struct mpv_global *c, struct mp_log *d) {}
|
||||
static void mp_ass_configure_fonts(struct ass_renderer *a, struct osd_style_opts *b,
|
||||
struct mpv_global *c, struct mp_log *d) {}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
* This file is part of mplayer2.
|
||||
*
|
||||
* mplayer2 is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mplayer2 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mplayer2; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "talloc.h"
|
||||
|
||||
#include "player/core.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/demux.h"
|
||||
#include "options/path.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "common/common.h"
|
||||
#include "stream/stream.h"
|
||||
|
||||
// used by demuxer_cue.c
|
||||
bool mp_probe_cue(struct bstr data);
|
||||
|
||||
#define SECS_PER_CUE_FRAME (1.0/75.0)
|
||||
|
||||
enum cue_command {
|
||||
CUE_ERROR = -1, // not a valid CUE command, or an unknown extension
|
||||
CUE_EMPTY, // line with whitespace only
|
||||
CUE_UNUSED, // valid CUE command, but ignored by this code
|
||||
CUE_FILE,
|
||||
CUE_TRACK,
|
||||
CUE_INDEX,
|
||||
CUE_TITLE,
|
||||
};
|
||||
|
||||
static const struct {
|
||||
enum cue_command command;
|
||||
const char *text;
|
||||
} cue_command_strings[] = {
|
||||
{ CUE_FILE, "FILE" },
|
||||
{ CUE_TRACK, "TRACK" },
|
||||
{ CUE_INDEX, "INDEX" },
|
||||
{ CUE_TITLE, "TITLE" },
|
||||
{ CUE_UNUSED, "CATALOG" },
|
||||
{ CUE_UNUSED, "CDTEXTFILE" },
|
||||
{ CUE_UNUSED, "FLAGS" },
|
||||
{ CUE_UNUSED, "ISRC" },
|
||||
{ CUE_UNUSED, "PERFORMER" },
|
||||
{ CUE_UNUSED, "POSTGAP" },
|
||||
{ CUE_UNUSED, "PREGAP" },
|
||||
{ CUE_UNUSED, "REM" },
|
||||
{ CUE_UNUSED, "SONGWRITER" },
|
||||
{ CUE_UNUSED, "MESSAGE" },
|
||||
{ -1 },
|
||||
};
|
||||
|
||||
struct cue_track {
|
||||
double pregap_start; // corresponds to INDEX 00
|
||||
double start; // corresponds to INDEX 01
|
||||
struct bstr filename;
|
||||
int source;
|
||||
struct bstr title;
|
||||
};
|
||||
|
||||
static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
|
||||
{
|
||||
struct bstr line = bstr_strip_linebreaks(bstr_getline(*data, data));
|
||||
line = bstr_lstrip(line);
|
||||
if (line.len == 0)
|
||||
return CUE_EMPTY;
|
||||
for (int n = 0; cue_command_strings[n].command != -1; n++) {
|
||||
struct bstr name = bstr0(cue_command_strings[n].text);
|
||||
if (bstr_startswith(line, name)) {
|
||||
struct bstr rest = bstr_cut(line, name.len);
|
||||
if (rest.len && !strchr(WHITESPACE, rest.start[0]))
|
||||
continue;
|
||||
if (out_params)
|
||||
*out_params = rest;
|
||||
return cue_command_strings[n].command;
|
||||
}
|
||||
}
|
||||
return CUE_ERROR;
|
||||
}
|
||||
|
||||
static bool eat_char(struct bstr *data, char ch)
|
||||
{
|
||||
if (data->len && data->start[0] == ch) {
|
||||
*data = bstr_cut(*data, 1);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct bstr read_quoted(struct bstr *data)
|
||||
{
|
||||
*data = bstr_lstrip(*data);
|
||||
if (!eat_char(data, '"'))
|
||||
return (struct bstr) {0};
|
||||
int end = bstrchr(*data, '"');
|
||||
if (end < 0)
|
||||
return (struct bstr) {0};
|
||||
struct bstr res = bstr_splice(*data, 0, end);
|
||||
*data = bstr_cut(*data, end + 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Read a 2 digit unsigned decimal integer.
|
||||
// Return -1 on failure.
|
||||
static int read_int_2(struct bstr *data)
|
||||
{
|
||||
*data = bstr_lstrip(*data);
|
||||
if (data->len && data->start[0] == '-')
|
||||
return -1;
|
||||
struct bstr s = *data;
|
||||
int res = (int)bstrtoll(s, &s, 10);
|
||||
if (data->len == s.len || data->len - s.len > 2)
|
||||
return -1;
|
||||
*data = s;
|
||||
return res;
|
||||
}
|
||||
|
||||
static double read_time(struct bstr *data)
|
||||
{
|
||||
struct bstr s = *data;
|
||||
bool ok = true;
|
||||
double t1 = read_int_2(&s);
|
||||
ok = eat_char(&s, ':') && ok;
|
||||
double t2 = read_int_2(&s);
|
||||
ok = eat_char(&s, ':') && ok;
|
||||
double t3 = read_int_2(&s);
|
||||
ok = ok && t1 >= 0 && t2 >= 0 && t3 >= 0;
|
||||
return ok ? t1 * 60.0 + t2 + t3 * SECS_PER_CUE_FRAME : 0;
|
||||
}
|
||||
|
||||
static struct bstr skip_utf8_bom(struct bstr data)
|
||||
{
|
||||
return bstr_startswith0(data, "\xEF\xBB\xBF") ? bstr_cut(data, 3) : data;
|
||||
}
|
||||
|
||||
// Check if the text in data is most likely CUE data. This is used by the
|
||||
// demuxer code to check the file type.
|
||||
// data is the start of the probed file, possibly cut off at a random point.
|
||||
bool mp_probe_cue(struct bstr data)
|
||||
{
|
||||
bool valid = false;
|
||||
data = skip_utf8_bom(data);
|
||||
for (;;) {
|
||||
enum cue_command cmd = read_cmd(&data, NULL);
|
||||
// End reached. Since the line was most likely cut off, don't use the
|
||||
// result of the last parsing call.
|
||||
if (data.len == 0)
|
||||
break;
|
||||
if (cmd == CUE_ERROR)
|
||||
return false;
|
||||
if (cmd != CUE_EMPTY)
|
||||
valid = true;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
static void add_source(struct MPContext *mpctx, struct demuxer *d)
|
||||
{
|
||||
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, d);
|
||||
}
|
||||
|
||||
static bool try_open(struct MPContext *mpctx, char *filename)
|
||||
{
|
||||
struct bstr bfilename = bstr0(filename);
|
||||
// Avoid trying to open itself or another .cue file. Best would be
|
||||
// to check the result of demuxer auto-detection, but the demuxer
|
||||
// API doesn't allow this without opening a full demuxer.
|
||||
if (bstr_case_endswith(bfilename, bstr0(".cue"))
|
||||
|| bstrcasecmp(bstr0(mpctx->demuxer->filename), bfilename) == 0)
|
||||
return false;
|
||||
|
||||
struct stream *s = stream_open(filename, mpctx->global);
|
||||
if (!s)
|
||||
return false;
|
||||
struct demuxer *d = demux_open(s, NULL, NULL, mpctx->global);
|
||||
// Since .bin files are raw PCM data with no headers, we have to explicitly
|
||||
// open them. Also, try to avoid to open files that are most likely not .bin
|
||||
// files, as that would only play noise. Checking the file extension is
|
||||
// fragile, but it's about the only way we have.
|
||||
// TODO: maybe also could check if the .bin file is a multiple of the Audio
|
||||
// CD sector size (2352 bytes)
|
||||
if (!d && bstr_case_endswith(bfilename, bstr0(".bin"))) {
|
||||
MP_WARN(mpctx, "CUE: Opening as BIN file!\n");
|
||||
d = demux_open(s, "rawaudio", NULL, mpctx->global);
|
||||
}
|
||||
if (d) {
|
||||
add_source(mpctx, d);
|
||||
return true;
|
||||
}
|
||||
MP_ERR(mpctx, "Could not open source '%s'!\n", filename);
|
||||
free_stream(s);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool open_source(struct MPContext *mpctx, struct bstr filename)
|
||||
{
|
||||
void *ctx = talloc_new(NULL);
|
||||
bool res = false;
|
||||
|
||||
struct bstr dirname = mp_dirname(mpctx->demuxer->filename);
|
||||
|
||||
struct bstr base_filename = bstr0(mp_basename(bstrdup0(ctx, filename)));
|
||||
if (!base_filename.len) {
|
||||
MP_WARN(mpctx, "CUE: Invalid audio filename in .cue file!\n");
|
||||
} else {
|
||||
char *fullname = mp_path_join(ctx, dirname, base_filename);
|
||||
if (try_open(mpctx, fullname)) {
|
||||
res = true;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
// Try an audio file with the same name as the .cue file (but different
|
||||
// extension).
|
||||
// Rationale: this situation happens easily if the audio file or both files
|
||||
// are renamed.
|
||||
|
||||
struct bstr cuefile =
|
||||
bstr_strip_ext(bstr0(mp_basename(mpctx->demuxer->filename)));
|
||||
|
||||
DIR *d = opendir(bstrdup0(ctx, dirname));
|
||||
if (!d)
|
||||
goto out;
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d))) {
|
||||
char *dename0 = de->d_name;
|
||||
struct bstr dename = bstr0(dename0);
|
||||
if (bstr_case_startswith(dename, cuefile)) {
|
||||
MP_WARN(mpctx, "CUE: No useful audio filename "
|
||||
"in .cue file found, trying with '%s' instead!\n",
|
||||
dename0);
|
||||
if (try_open(mpctx, mp_path_join(ctx, dirname, dename))) {
|
||||
res = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
out:
|
||||
talloc_free(ctx);
|
||||
if (!res)
|
||||
MP_ERR(mpctx, "CUE: Could not open audio file!\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
// return length of the source in seconds, or -1 if unknown
|
||||
static double source_get_length(struct demuxer *demuxer)
|
||||
{
|
||||
double get_time_ans;
|
||||
// <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW
|
||||
if (demuxer && demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH,
|
||||
(void *) &get_time_ans) > 0)
|
||||
{
|
||||
return get_time_ans;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void build_cue_timeline(struct MPContext *mpctx)
|
||||
{
|
||||
void *ctx = talloc_new(NULL);
|
||||
|
||||
struct bstr data = mpctx->demuxer->file_contents;
|
||||
data = skip_utf8_bom(data);
|
||||
|
||||
struct cue_track *tracks = NULL;
|
||||
size_t track_count = 0;
|
||||
|
||||
struct bstr filename = {0};
|
||||
// Global metadata, and copied into new tracks.
|
||||
struct cue_track proto_track = {0};
|
||||
struct cue_track *cur_track = &proto_track;
|
||||
|
||||
while (data.len) {
|
||||
struct bstr param;
|
||||
switch (read_cmd(&data, ¶m)) {
|
||||
case CUE_ERROR:
|
||||
MP_ERR(mpctx, "CUE: error parsing input file!\n");
|
||||
goto out;
|
||||
case CUE_TRACK: {
|
||||
track_count++;
|
||||
tracks = talloc_realloc(ctx, tracks, struct cue_track, track_count);
|
||||
cur_track = &tracks[track_count - 1];
|
||||
*cur_track = proto_track;
|
||||
break;
|
||||
}
|
||||
case CUE_TITLE:
|
||||
cur_track->title = read_quoted(¶m);
|
||||
break;
|
||||
case CUE_INDEX: {
|
||||
int type = read_int_2(¶m);
|
||||
double time = read_time(¶m);
|
||||
if (type == 1) {
|
||||
cur_track->start = time;
|
||||
cur_track->filename = filename;
|
||||
} else if (type == 0) {
|
||||
cur_track->pregap_start = time;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CUE_FILE:
|
||||
// NOTE: FILE comes before TRACK, so don't use cur_track->filename
|
||||
filename = read_quoted(¶m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (track_count == 0) {
|
||||
MP_ERR(mpctx, "CUE: no tracks found!\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
// Remove duplicate file entries. This might be too sophisticated, since
|
||||
// CUE files usually use either separate files for every single track, or
|
||||
// only one file for all tracks.
|
||||
|
||||
struct bstr *files = 0;
|
||||
size_t file_count = 0;
|
||||
|
||||
for (size_t n = 0; n < track_count; n++) {
|
||||
struct cue_track *track = &tracks[n];
|
||||
track->source = -1;
|
||||
for (size_t file = 0; file < file_count; file++) {
|
||||
if (bstrcmp(files[file], track->filename) == 0) {
|
||||
track->source = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (track->source == -1) {
|
||||
file_count++;
|
||||
files = talloc_realloc(ctx, files, struct bstr, file_count);
|
||||
files[file_count - 1] = track->filename;
|
||||
track->source = file_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < file_count; i++) {
|
||||
if (!open_source(mpctx, files[i]))
|
||||
goto out;
|
||||
}
|
||||
|
||||
struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline,
|
||||
track_count + 1);
|
||||
struct demux_chapter *chapters = talloc_array_ptrtype(NULL, chapters,
|
||||
track_count);
|
||||
double starttime = 0;
|
||||
for (int i = 0; i < track_count; i++) {
|
||||
struct demuxer *source = mpctx->sources[1 + tracks[i].source];
|
||||
double duration;
|
||||
if (i + 1 < track_count && tracks[i].source == tracks[i + 1].source) {
|
||||
duration = tracks[i + 1].start - tracks[i].start;
|
||||
} else {
|
||||
duration = source_get_length(source);
|
||||
// Two cases: 1) last track of a single-file cue, or 2) any track of
|
||||
// a multi-file cue. We need to do this for 1) only because the
|
||||
// timeline needs to be terminated with the length of the last
|
||||
// track.
|
||||
duration -= tracks[i].start;
|
||||
}
|
||||
if (duration < 0) {
|
||||
MP_WARN(mpctx, "CUE: Can't get duration of source file!\n");
|
||||
// xxx: do something more reasonable
|
||||
duration = 0.0;
|
||||
}
|
||||
timeline[i] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
.source_start = tracks[i].start,
|
||||
.source = source,
|
||||
};
|
||||
chapters[i] = (struct demux_chapter) {
|
||||
.pts = timeline[i].start,
|
||||
// might want to include other metadata here
|
||||
.name = bstrdup0(chapters, tracks[i].title),
|
||||
};
|
||||
starttime += duration;
|
||||
}
|
||||
|
||||
// apparently we need this to give the last part a non-zero length
|
||||
timeline[track_count] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
// perhaps unused by the timeline code
|
||||
.source_start = 0,
|
||||
.source = timeline[0].source,
|
||||
};
|
||||
|
||||
mpctx->timeline = timeline;
|
||||
// the last part is not included it in the count
|
||||
mpctx->num_timeline_parts = track_count + 1 - 1;
|
||||
mpctx->chapters = chapters;
|
||||
mpctx->num_chapters = track_count;
|
||||
mpctx->track_layout = mpctx->timeline[0].source;
|
||||
|
||||
out:
|
||||
talloc_free(ctx);
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "talloc.h"
|
||||
|
||||
#include "player/core.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/demux.h"
|
||||
#include "options/path.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "common/common.h"
|
||||
#include "stream/stream.h"
|
||||
|
||||
struct tl_part {
|
||||
char *filename; // what is stream_open()ed
|
||||
double offset; // offset into the source file
|
||||
bool offset_set;
|
||||
bool chapter_ts;
|
||||
double length; // length of the part (-1 if rest of the file)
|
||||
};
|
||||
|
||||
struct tl_parts {
|
||||
struct tl_part *parts;
|
||||
int num_parts;
|
||||
};
|
||||
|
||||
// Parse a time (absolute file time or duration). Currently equivalent to a
|
||||
// number. Return false on failure.
|
||||
static bool parse_time(bstr str, double *out_time)
|
||||
{
|
||||
bstr rest;
|
||||
double time = bstrtod(str, &rest);
|
||||
if (!str.len || rest.len || !isfinite(time))
|
||||
return false;
|
||||
*out_time = time;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returns a list of parts, or NULL on parse error.
|
||||
* Syntax (without file header or URI prefix):
|
||||
* url ::= <entry> ( (';' | '\n') <entry> )*
|
||||
* entry ::= <param> ( <param> ',' )*
|
||||
* param ::= [<string> '='] (<string> | '%' <number> '%' <bytes>)
|
||||
*/
|
||||
static struct tl_parts *parse_edl(bstr str)
|
||||
{
|
||||
struct tl_parts *tl = talloc_zero(NULL, struct tl_parts);
|
||||
while (str.len) {
|
||||
if (bstr_eatstart0(&str, "#"))
|
||||
bstr_split_tok(str, "\n", &(bstr){0}, &str);
|
||||
if (bstr_eatstart0(&str, "\n") || bstr_eatstart0(&str, ";"))
|
||||
continue;
|
||||
struct tl_part p = { .length = -1 };
|
||||
int nparam = 0;
|
||||
while (1) {
|
||||
bstr name, val;
|
||||
// Check if it's of the form "name=..."
|
||||
int next = bstrcspn(str, "=%,;\n");
|
||||
if (next > 0 && next < str.len && str.start[next] == '=') {
|
||||
name = bstr_splice(str, 0, next);
|
||||
str = bstr_cut(str, next + 1);
|
||||
} else {
|
||||
const char *names[] = {"file", "start", "length"}; // implied name
|
||||
name = bstr0(nparam < 3 ? names[nparam] : "-");
|
||||
}
|
||||
if (bstr_eatstart0(&str, "%")) {
|
||||
int len = bstrtoll(str, &str, 0);
|
||||
if (!bstr_startswith0(str, "%") || (len > str.len - 1))
|
||||
goto error;
|
||||
val = bstr_splice(str, 1, len + 1);
|
||||
str = bstr_cut(str, len + 1);
|
||||
} else {
|
||||
next = bstrcspn(str, ",;\n");
|
||||
val = bstr_splice(str, 0, next);
|
||||
str = bstr_cut(str, next);
|
||||
}
|
||||
// Interpret parameters. Explicitly ignore unknown ones.
|
||||
if (bstr_equals0(name, "file")) {
|
||||
p.filename = bstrto0(tl, val);
|
||||
} else if (bstr_equals0(name, "start")) {
|
||||
if (!parse_time(val, &p.offset))
|
||||
goto error;
|
||||
p.offset_set = true;
|
||||
} else if (bstr_equals0(name, "length")) {
|
||||
if (!parse_time(val, &p.length))
|
||||
goto error;
|
||||
} else if (bstr_equals0(name, "timestamps")) {
|
||||
if (bstr_equals0(val, "chapters"))
|
||||
p.chapter_ts = true;
|
||||
}
|
||||
nparam++;
|
||||
if (!bstr_eatstart0(&str, ","))
|
||||
break;
|
||||
}
|
||||
if (!p.filename)
|
||||
goto error;
|
||||
MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p);
|
||||
}
|
||||
if (!tl->num_parts)
|
||||
goto error;
|
||||
return tl;
|
||||
error:
|
||||
talloc_free(tl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct demuxer *open_file(char *filename, struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
struct demuxer *d = NULL;
|
||||
struct stream *s = stream_open(filename, mpctx->global);
|
||||
if (s) {
|
||||
stream_enable_cache(&s, &opts->stream_cache);
|
||||
d = demux_open(s, NULL, NULL, mpctx->global);
|
||||
}
|
||||
if (!d) {
|
||||
MP_ERR(mpctx, "EDL: Could not open source file '%s'.\n",
|
||||
filename);
|
||||
free_stream(s);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static struct demuxer *open_source(struct MPContext *mpctx, char *filename)
|
||||
{
|
||||
for (int n = 0; n < mpctx->num_sources; n++) {
|
||||
struct demuxer *d = mpctx->sources[n];
|
||||
if (strcmp(d->stream->url, filename) == 0)
|
||||
return d;
|
||||
}
|
||||
struct demuxer *d = open_file(filename, mpctx);
|
||||
if (d)
|
||||
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, d);
|
||||
return d;
|
||||
}
|
||||
|
||||
static double demuxer_chapter_time(struct demuxer *demuxer, int n)
|
||||
{
|
||||
if (n < 0 || n >= demuxer->num_chapters)
|
||||
return -1;
|
||||
return demuxer->chapters[n].pts;
|
||||
}
|
||||
|
||||
// Append all chapters from src to the chapters array.
|
||||
// Ignore chapters outside of the given time range.
|
||||
static void copy_chapters(struct demux_chapter **chapters, int *num_chapters,
|
||||
struct demuxer *src, double start, double len,
|
||||
double dest_offset)
|
||||
{
|
||||
for (int n = 0; n < src->num_chapters; n++) {
|
||||
double time = demuxer_chapter_time(src, n);
|
||||
if (time >= start && time <= start + len) {
|
||||
struct demux_chapter ch = {
|
||||
.pts = dest_offset + time - start,
|
||||
.name = talloc_strdup(*chapters, src->chapters[n].name),
|
||||
};
|
||||
MP_TARRAY_APPEND(NULL, *chapters, *num_chapters, ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return length of the source in seconds, or -1 if unknown
|
||||
static double source_get_length(struct demuxer *demuxer)
|
||||
{
|
||||
double time;
|
||||
// <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW
|
||||
if (demux_control(demuxer, DEMUXER_CTRL_GET_TIME_LENGTH, &time) <= 0)
|
||||
time = -1;
|
||||
return time;
|
||||
}
|
||||
|
||||
static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
|
||||
{
|
||||
if (part->chapter_ts) {
|
||||
double start = demuxer_chapter_time(demuxer, part->offset);
|
||||
double length = part->length;
|
||||
double end = length;
|
||||
if (end >= 0)
|
||||
end = demuxer_chapter_time(demuxer, part->offset + part->length);
|
||||
if (end >= 0 && start >= 0)
|
||||
length = end - start;
|
||||
part->offset = start;
|
||||
part->length = length;
|
||||
}
|
||||
if (!part->offset_set)
|
||||
part->offset = demuxer->start_time;
|
||||
}
|
||||
|
||||
static void build_timeline(struct MPContext *mpctx, struct tl_parts *parts)
|
||||
{
|
||||
struct demux_chapter *chapters = talloc_new(NULL);
|
||||
int num_chapters = 0;
|
||||
struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline,
|
||||
parts->num_parts + 1);
|
||||
double starttime = 0;
|
||||
for (int n = 0; n < parts->num_parts; n++) {
|
||||
struct tl_part *part = &parts->parts[n];
|
||||
struct demuxer *source = open_source(mpctx, part->filename);
|
||||
if (!source)
|
||||
goto error;
|
||||
|
||||
resolve_timestamps(part, source);
|
||||
|
||||
double len = source_get_length(source);
|
||||
if (len > 0) {
|
||||
len += source->start_time;
|
||||
} else {
|
||||
MP_WARN(mpctx, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
}
|
||||
|
||||
// Unknown length => use rest of the file. If duration is unknown, make
|
||||
// something up.
|
||||
if (part->length < 0)
|
||||
part->length = (len < 0 ? 1 : len) - part->offset;
|
||||
|
||||
if (len > 0) {
|
||||
double partlen = part->offset + part->length;
|
||||
if (partlen > len) {
|
||||
MP_WARN(mpctx, "EDL: entry %d uses %f "
|
||||
"seconds, but file has only %f seconds.\n",
|
||||
n, partlen, len);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a chapter between each file.
|
||||
struct demux_chapter ch = {
|
||||
.pts = starttime,
|
||||
.name = talloc_strdup(chapters, part->filename),
|
||||
};
|
||||
MP_TARRAY_APPEND(NULL, chapters, num_chapters, ch);
|
||||
|
||||
// Also copy the source file's chapters for the relevant parts
|
||||
copy_chapters(&chapters, &num_chapters, source, part->offset,
|
||||
part->length, starttime);
|
||||
|
||||
timeline[n] = (struct timeline_part) {
|
||||
.start = starttime,
|
||||
.source_start = part->offset,
|
||||
.source = source,
|
||||
};
|
||||
|
||||
starttime += part->length;
|
||||
}
|
||||
timeline[parts->num_parts] = (struct timeline_part) {.start = starttime};
|
||||
mpctx->timeline = timeline;
|
||||
mpctx->num_timeline_parts = parts->num_parts;
|
||||
mpctx->chapters = chapters;
|
||||
mpctx->num_chapters = num_chapters;
|
||||
mpctx->track_layout = mpctx->timeline[0].source;
|
||||
return;
|
||||
|
||||
error:
|
||||
talloc_free(timeline);
|
||||
talloc_free(chapters);
|
||||
}
|
||||
|
||||
// For security, don't allow relative or absolute paths, only plain filenames.
|
||||
// Also, make these filenames relative to the edl source file.
|
||||
static void fix_filenames(struct tl_parts *parts, char *source_path)
|
||||
{
|
||||
struct bstr dirname = mp_dirname(source_path);
|
||||
for (int n = 0; n < parts->num_parts; n++) {
|
||||
struct tl_part *part = &parts->parts[n];
|
||||
char *filename = mp_basename(part->filename); // plain filename only
|
||||
part->filename = mp_path_join(parts, dirname, bstr0(filename));
|
||||
}
|
||||
}
|
||||
|
||||
void build_mpv_edl_timeline(struct MPContext *mpctx)
|
||||
{
|
||||
struct tl_parts *parts = parse_edl(mpctx->demuxer->file_contents);
|
||||
if (!parts) {
|
||||
MP_ERR(mpctx, "Error in EDL.\n");
|
||||
return;
|
||||
}
|
||||
// Source is .edl and not edl:// => don't allow arbitrary paths
|
||||
if (mpctx->demuxer->stream->uncached_type != STREAMTYPE_EDL)
|
||||
fix_filenames(parts, mpctx->demuxer->filename);
|
||||
build_timeline(mpctx, parts);
|
||||
talloc_free(parts);
|
||||
}
|
||||
+3
-3
@@ -70,11 +70,11 @@ static const char av_desync_help_text[] =
|
||||
"- Slow video output.\n"
|
||||
" Try a different --vo driver (--vo=help for a list). Make sure framedrop\n"
|
||||
" is not disabled, or experiment with different values for --framedrop.\n"
|
||||
"- Playing from a slow network source.\n"
|
||||
" Download the file instead.\n"
|
||||
" Make sure you have proper drivers for your GPU installed. If mpv\n"
|
||||
" autoselects 'VO: [x11]', it's a sure sign your drivers are messed up.\n"
|
||||
"- Playing from a slow network source. Download the file instead.\n"
|
||||
"- Try to find out whether audio/video/subs are causing this by experimenting\n"
|
||||
" with --no-video, --no-audio, or --no-sub.\n"
|
||||
"- If you switched audio or video tracks, try seeking to force synchronization.\n"
|
||||
"If none of this helps you, file a bug report.\n\n";
|
||||
|
||||
void update_fps(struct MPContext *mpctx)
|
||||
|
||||
+19
-13
@@ -102,6 +102,9 @@ struct priv {
|
||||
int64_t reads; // number of actual read attempts performed
|
||||
|
||||
int64_t read_filepos; // client read position (mirrors cache->pos)
|
||||
|
||||
int64_t eof_pos;
|
||||
|
||||
int control; // requested STREAM_CTRL_... or CACHE_CTRL_...
|
||||
void *control_arg; // temporary for executing STREAM_CTRLs
|
||||
int control_res;
|
||||
@@ -126,13 +129,6 @@ enum {
|
||||
FILL_LIMIT = 16 * 1024,
|
||||
};
|
||||
|
||||
static int64_t mp_clipi64(int64_t val, int64_t min, int64_t max)
|
||||
{
|
||||
val = FFMIN(val, max);
|
||||
val = FFMAX(val, min);
|
||||
return val;
|
||||
}
|
||||
|
||||
// Used by the main thread to wakeup the cache thread, and to wait for the
|
||||
// cache thread. The cache mutex has to be locked when calling this function.
|
||||
// *retry_time should be set to 0 on the first call.
|
||||
@@ -232,7 +228,7 @@ static bool cache_fill(struct priv *s)
|
||||
}
|
||||
|
||||
// number of buffer bytes which should be preserved in backwards direction
|
||||
int64_t back = mp_clipi64(read - s->min_filepos, 0, s->back_size);
|
||||
int64_t back = MPCLAMP(read - s->min_filepos, 0, s->back_size);
|
||||
|
||||
// number of buffer bytes that are valid and can be read
|
||||
int64_t newb = FFMAX(s->max_filepos - read, 0);
|
||||
@@ -284,8 +280,10 @@ done:
|
||||
s->eof = len <= 0;
|
||||
s->idle = s->eof;
|
||||
s->reads++;
|
||||
if (s->eof)
|
||||
MP_VERBOSE(s, "EOF reached.\n");
|
||||
if (s->eof) {
|
||||
s->eof_pos = stream_tell(s->stream);
|
||||
MP_TRACE(s, "EOF reached.\n");
|
||||
}
|
||||
|
||||
pthread_cond_signal(&s->wakeup);
|
||||
|
||||
@@ -297,7 +295,7 @@ static int resize_cache(struct priv *s, int64_t size)
|
||||
{
|
||||
int64_t min_size = FILL_LIMIT * 4;
|
||||
int64_t max_size = ((size_t)-1) / 4;
|
||||
int64_t buffer_size = MPMIN(MPMAX(size, min_size), max_size);
|
||||
int64_t buffer_size = MPCLAMP(size, min_size, max_size);
|
||||
|
||||
unsigned char *buffer = malloc(buffer_size);
|
||||
if (!buffer) {
|
||||
@@ -359,7 +357,7 @@ static void update_cached_controls(struct priv *s)
|
||||
talloc_free(s->stream_metadata);
|
||||
s->stream_metadata = talloc_steal(s, tags);
|
||||
}
|
||||
s->stream_size = -1;
|
||||
s->stream_size = s->eof_pos;
|
||||
if (stream_control(s->stream, STREAM_CTRL_GET_SIZE, &i64) == STREAM_OK)
|
||||
s->stream_size = i64;
|
||||
s->has_avseek = stream_control(s->stream, STREAM_CTRL_HAS_AVSEEK, NULL) > 0;
|
||||
@@ -617,12 +615,20 @@ int stream_cache_init(stream_t *cache, stream_t *stream,
|
||||
|
||||
struct priv *s = talloc_zero(NULL, struct priv);
|
||||
s->log = cache->log;
|
||||
s->eof_pos = -1;
|
||||
|
||||
cache_drop_contents(s);
|
||||
|
||||
s->seek_limit = opts->seek_min * 1024ULL;
|
||||
|
||||
if (resize_cache(s, opts->size * 1024ULL) != STREAM_OK) {
|
||||
int64_t cache_size = opts->size * 1024ULL;
|
||||
|
||||
int64_t file_size = -1;
|
||||
stream_control(stream, STREAM_CTRL_GET_SIZE, &file_size);
|
||||
if (file_size >= 0)
|
||||
cache_size = MPMIN(cache_size, file_size);
|
||||
|
||||
if (resize_cache(s, cache_size) != STREAM_OK) {
|
||||
MP_ERR(s, "Failed to allocate cache buffer.\n");
|
||||
talloc_free(s);
|
||||
return -1;
|
||||
|
||||
+1
-1
@@ -37,12 +37,12 @@
|
||||
#ifndef DTV_STREAM_ID
|
||||
#define DTV_STREAM_ID DTV_ISDBS_TS_ID
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// This is only defined, for convenience, since API 5.8.
|
||||
#ifndef NO_STREAM_ID_FILTER
|
||||
#define NO_STREAM_ID_FILTER (~0U)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (DVB_API_VERSION == 3 && DVB_API_VERSION_MINOR >= 1) || DVB_API_VERSION == 5
|
||||
#define DVB_ATSC 1
|
||||
|
||||
+2
-4
@@ -385,8 +385,7 @@ int RarParse(struct stream *s, int *count, rar_file_t ***file)
|
||||
if (!volume_mrl)
|
||||
goto done;
|
||||
|
||||
vol = stream_create(volume_mrl, STREAM_READ | STREAM_NO_FILTERS,
|
||||
s->cancel, s->global);
|
||||
vol = stream_create(volume_mrl, STREAM_READ, s->cancel, s->global);
|
||||
|
||||
if (!vol)
|
||||
goto done;
|
||||
@@ -421,8 +420,7 @@ int RarSeek(rar_file_t *file, uint64_t position)
|
||||
if (strcmp(old_chunk->mrl, file->current_chunk->mrl)) {
|
||||
if (file->s)
|
||||
free_stream(file->s);
|
||||
file->s = stream_create(file->current_chunk->mrl,
|
||||
STREAM_READ | STREAM_NO_FILTERS,
|
||||
file->s = stream_create(file->current_chunk->mrl, STREAM_READ,
|
||||
file->cancel, file->global);
|
||||
}
|
||||
return file->s ? stream_seek(file->s, offset) : 0;
|
||||
|
||||
+14
-39
@@ -74,8 +74,7 @@ extern const stream_info_t stream_info_dvdnav;
|
||||
extern const stream_info_t stream_info_bdmv_dir;
|
||||
extern const stream_info_t stream_info_bluray;
|
||||
extern const stream_info_t stream_info_bdnav;
|
||||
extern const stream_info_t stream_info_rar_filter;
|
||||
extern const stream_info_t stream_info_rar_entry;
|
||||
extern const stream_info_t stream_info_rar;
|
||||
extern const stream_info_t stream_info_edl;
|
||||
|
||||
static const stream_info_t *const stream_list[] = {
|
||||
@@ -115,8 +114,7 @@ static const stream_info_t *const stream_list[] = {
|
||||
&stream_info_null,
|
||||
&stream_info_mf,
|
||||
&stream_info_edl,
|
||||
&stream_info_rar_filter,
|
||||
&stream_info_rar_entry,
|
||||
&stream_info_rar,
|
||||
&stream_info_file,
|
||||
NULL
|
||||
};
|
||||
@@ -256,32 +254,25 @@ static const char *match_proto(const char *url, const char *proto)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int open_internal(const stream_info_t *sinfo, struct stream *underlying,
|
||||
const char *url, int flags, struct mp_cancel *c,
|
||||
struct mpv_global *global, struct stream **ret)
|
||||
static int open_internal(const stream_info_t *sinfo, const char *url, int flags,
|
||||
struct mp_cancel *c, struct mpv_global *global,
|
||||
struct stream **ret)
|
||||
{
|
||||
if (sinfo->stream_filter != !!underlying)
|
||||
return STREAM_NO_MATCH;
|
||||
if (sinfo->stream_filter && (flags & STREAM_NO_FILTERS))
|
||||
return STREAM_NO_MATCH;
|
||||
if (!sinfo->is_safe && (flags & STREAM_SAFE_ONLY))
|
||||
return STREAM_UNSAFE;
|
||||
if (!sinfo->is_network && (flags & STREAM_NETWORK_ONLY))
|
||||
return STREAM_UNSAFE;
|
||||
|
||||
const char *path = NULL;
|
||||
// Stream filters use the original URL, with no protocol matching at all.
|
||||
if (!sinfo->stream_filter) {
|
||||
for (int n = 0; sinfo->protocols && sinfo->protocols[n]; n++) {
|
||||
path = match_proto(url, sinfo->protocols[n]);
|
||||
if (path)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!path)
|
||||
return STREAM_NO_MATCH;
|
||||
for (int n = 0; sinfo->protocols && sinfo->protocols[n]; n++) {
|
||||
path = match_proto(url, sinfo->protocols[n]);
|
||||
if (path)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!path)
|
||||
return STREAM_NO_MATCH;
|
||||
|
||||
stream_t *s = new_stream();
|
||||
s->log = mp_log_new(s, global->log, sinfo->name);
|
||||
s->info = sinfo;
|
||||
@@ -290,7 +281,6 @@ static int open_internal(const stream_info_t *sinfo, struct stream *underlying,
|
||||
s->global = global;
|
||||
s->url = talloc_strdup(s, url);
|
||||
s->path = talloc_strdup(s, path);
|
||||
s->source = underlying;
|
||||
s->allow_caching = true;
|
||||
s->is_network = sinfo->is_network;
|
||||
s->mode = flags & (STREAM_READ | STREAM_WRITE);
|
||||
@@ -357,7 +347,7 @@ struct stream *stream_create(const char *url, int flags,
|
||||
// Open stream proper
|
||||
bool unsafe = false;
|
||||
for (int i = 0; stream_list[i]; i++) {
|
||||
int r = open_internal(stream_list[i], NULL, url, flags, c, global, &s);
|
||||
int r = open_internal(stream_list[i], url, flags, c, global, &s);
|
||||
if (r == STREAM_OK)
|
||||
break;
|
||||
if (r == STREAM_NO_MATCH || r == STREAM_UNSUPPORTED)
|
||||
@@ -386,19 +376,6 @@ struct stream *stream_create(const char *url, int flags,
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Open stream filters
|
||||
for (;;) {
|
||||
struct stream *new = NULL;
|
||||
for (int i = 0; stream_list[i]; i++) {
|
||||
int r = open_internal(stream_list[i], s, s->url, flags, c, global, &new);
|
||||
if (r == STREAM_OK)
|
||||
break;
|
||||
}
|
||||
if (!new)
|
||||
break;
|
||||
s = new;
|
||||
}
|
||||
|
||||
done:
|
||||
talloc_free(log);
|
||||
return s;
|
||||
@@ -730,7 +707,6 @@ void free_stream(stream_t *s)
|
||||
if (s->close)
|
||||
s->close(s);
|
||||
free_stream(s->uncached_stream);
|
||||
free_stream(s->source);
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
@@ -759,7 +735,6 @@ static stream_t *open_cache(stream_t *orig, const char *name)
|
||||
cache->mime_type = talloc_strdup(cache, orig->mime_type);
|
||||
cache->demuxer = talloc_strdup(cache, orig->demuxer);
|
||||
cache->lavf_type = talloc_strdup(cache, orig->lavf_type);
|
||||
cache->safe_origin = orig->safe_origin;
|
||||
cache->streaming = orig->streaming,
|
||||
cache->is_network = orig->is_network;
|
||||
cache->opts = orig->opts;
|
||||
@@ -1012,7 +987,7 @@ void mp_cancel_reset(struct mp_cancel *c)
|
||||
// For convenience, c==NULL is allowed.
|
||||
bool mp_cancel_test(struct mp_cancel *c)
|
||||
{
|
||||
return c ? atomic_load(&c->triggered) : false;
|
||||
return c ? atomic_load_explicit(&c->triggered, memory_order_relaxed) : false;
|
||||
}
|
||||
|
||||
// Wait until the even is signaled. If the timeout (in seconds) expires, return
|
||||
|
||||
@@ -54,7 +54,6 @@ enum streamtype {
|
||||
#define STREAM_WRITE 1
|
||||
|
||||
// flags for stream_open_ext (this includes STREAM_READ and STREAM_WRITE)
|
||||
#define STREAM_NO_FILTERS 2
|
||||
#define STREAM_SAFE_ONLY 4
|
||||
#define STREAM_NETWORK_ONLY 8
|
||||
|
||||
@@ -157,7 +156,6 @@ typedef struct stream_info_st {
|
||||
void *(*get_defaults)(struct stream *st);
|
||||
const struct m_option *options;
|
||||
const char *const *url_options;
|
||||
bool stream_filter;
|
||||
bool can_write; // correctly checks for READ/WRITE modes
|
||||
bool is_safe; // opening is no security issue, even with remote provided URLs
|
||||
bool is_network; // used to restrict remote playlist entries to remote URLs
|
||||
@@ -196,7 +194,6 @@ typedef struct stream {
|
||||
bool streaming : 1; // known to be a network stream if true
|
||||
bool seekable : 1; // presence of general byte seeking support
|
||||
bool fast_skip : 1; // consider stream fast enough to fw-seek by skipping
|
||||
bool safe_origin : 1; // used for playlists that can be opened safely
|
||||
bool is_network : 1; // original stream_info_t.is_network flag
|
||||
bool allow_caching : 1; // stream cache makes sense
|
||||
struct mp_log *log;
|
||||
@@ -209,7 +206,6 @@ typedef struct stream {
|
||||
char *capture_filename;
|
||||
|
||||
struct stream *uncached_stream; // underlying stream for cache wrapper
|
||||
struct stream *source;
|
||||
|
||||
// Includes additional padding in case sizes get rounded up by sector size.
|
||||
unsigned char buffer[];
|
||||
|
||||
@@ -227,7 +227,6 @@ static void close_cdda(stream_t *s)
|
||||
cdda_priv *p = (cdda_priv *)s->priv;
|
||||
paranoia_free(p->cdp);
|
||||
cdda_close(p->cd);
|
||||
free(p);
|
||||
}
|
||||
|
||||
static int get_track_by_sector(cdda_priv *p, unsigned int sector)
|
||||
@@ -313,7 +312,6 @@ static int open_cdda(stream_t *st)
|
||||
return STREAM_ERROR;
|
||||
}
|
||||
|
||||
priv = calloc(1,sizeof(cdda_priv));
|
||||
priv->cd = cdd;
|
||||
|
||||
if (p->toc_bias)
|
||||
|
||||
+33
-4
@@ -27,6 +27,10 @@
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef __MINGW32__
|
||||
#include <poll.h>
|
||||
#endif
|
||||
|
||||
#include "osdep/io.h"
|
||||
|
||||
#include "common/common.h"
|
||||
@@ -57,11 +61,24 @@
|
||||
struct priv {
|
||||
int fd;
|
||||
bool close;
|
||||
bool regular;
|
||||
};
|
||||
|
||||
static int fill_buffer(stream_t *s, char *buffer, int max_len)
|
||||
{
|
||||
struct priv *p = s->priv;
|
||||
#ifndef __MINGW32__
|
||||
if (!p->regular) {
|
||||
int c = s->cancel ? mp_cancel_get_fd(s->cancel) : -1;
|
||||
struct pollfd fds[2] = {
|
||||
{.fd = p->fd, .events = POLLIN},
|
||||
{.fd = c, .events = POLLIN},
|
||||
};
|
||||
poll(fds, c >= 0 ? 2 : 1, -1);
|
||||
if (fds[1].revents & POLLIN)
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
int r = read(p->fd, buffer, max_len);
|
||||
return (r <= 0) ? -1 : r;
|
||||
}
|
||||
@@ -250,6 +267,8 @@ static int open_f(stream_t *stream)
|
||||
mode_t openmode = S_IRUSR | S_IWUSR;
|
||||
#ifndef __MINGW32__
|
||||
openmode |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
if (!write)
|
||||
m |= O_NONBLOCK;
|
||||
#endif
|
||||
fd = open(filename, m | O_BINARY, openmode);
|
||||
if (fd < 0) {
|
||||
@@ -258,10 +277,20 @@ static int open_f(stream_t *stream)
|
||||
return STREAM_ERROR;
|
||||
}
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
MP_ERR(stream, "File is a directory: '%s'\n", filename);
|
||||
close(fd);
|
||||
return STREAM_ERROR;
|
||||
if (fstat(fd, &st) == 0) {
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
MP_ERR(stream, "File is a directory: '%s'\n", filename);
|
||||
close(fd);
|
||||
return STREAM_ERROR;
|
||||
}
|
||||
#ifndef __MINGW32__
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
priv->regular = true;
|
||||
// O_NONBLOCK has weird semantics on file locks; remove it.
|
||||
int val = fcntl(fd, F_GETFL) & ~(unsigned)O_NONBLOCK;
|
||||
fcntl(fd, F_SETFL, val);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
priv->fd = fd;
|
||||
priv->close = true;
|
||||
|
||||
+5
-74
@@ -43,11 +43,10 @@ This works as follows:
|
||||
|
||||
- stream_open() with file01.rar
|
||||
- is opened as normal file (stream_file.c or others) first
|
||||
- stream_info_rar_filter stream filter applies
|
||||
- leads to rar_filter_open()
|
||||
- the rar code in demux_playlist.c detects it
|
||||
- if multi-part, opens file02.rar, file03.rar, etc. as actual streams
|
||||
(recursive opening is prevented with the STREAM_NO_FILTERS flag)
|
||||
- read accesses return a m3u playlist with entries like:
|
||||
- it returns a playlist like this to the player:
|
||||
rar://bla01.rar|subfile.mkv
|
||||
(one such entry for each file contained in the rar)
|
||||
- stream_open() with the playlist entry, e.g. rar://bla01.rar|subfile.mkv
|
||||
@@ -99,7 +98,7 @@ static int rar_entry_open(stream_t *stream)
|
||||
*name++ = '\0';
|
||||
mp_url_unescape_inplace(base);
|
||||
|
||||
struct stream *rar = stream_create(base, STREAM_READ | STREAM_NO_FILTERS,
|
||||
struct stream *rar = stream_create(base, STREAM_READ | STREAM_SAFE_ONLY,
|
||||
stream->cancel, stream->global);
|
||||
if (!rar)
|
||||
return STREAM_ERROR;
|
||||
@@ -143,76 +142,8 @@ static int rar_entry_open(stream_t *stream)
|
||||
return STREAM_OK;
|
||||
}
|
||||
|
||||
static int rar_filter_fill_buffer(stream_t *s, char *buffer, int max_len)
|
||||
{
|
||||
struct stream *m = s->priv;
|
||||
return stream_read_partial(m, buffer, max_len);
|
||||
}
|
||||
|
||||
static int rar_filter_seek(stream_t *s, int64_t newpos)
|
||||
{
|
||||
struct stream *m = s->priv;
|
||||
return stream_seek(m, newpos);
|
||||
}
|
||||
|
||||
static void rar_filter_close(stream_t *s)
|
||||
{
|
||||
struct stream *m = s->priv;
|
||||
free_stream(m);
|
||||
}
|
||||
|
||||
static int rar_filter_control(stream_t *s, int cmd, void *arg)
|
||||
{
|
||||
struct stream *m = s->priv;
|
||||
return stream_control(m, cmd, arg);
|
||||
}
|
||||
|
||||
static int rar_filter_open(stream_t *stream)
|
||||
{
|
||||
struct stream *rar = stream->source;
|
||||
if (!rar)
|
||||
return STREAM_UNSUPPORTED;
|
||||
|
||||
int count;
|
||||
rar_file_t **files;
|
||||
if (!rar || RarProbe(rar) || RarParse(rar, &count, &files))
|
||||
return STREAM_UNSUPPORTED;
|
||||
|
||||
void *tmp = talloc_new(NULL);
|
||||
|
||||
// Create a playlist containing all entries of the .rar file. The URLs
|
||||
// link to rar_entry_open().
|
||||
char *prefix = mp_url_escape(tmp, stream->url, "~|");
|
||||
char *pl = talloc_strdup(tmp, "#EXTM3U\n");
|
||||
for (int n = 0; n < count; n++) {
|
||||
pl = talloc_asprintf_append_buffer(pl, "rar://%s|%s\n",
|
||||
prefix, files[n]->name);
|
||||
RarFileDelete(files[n]);
|
||||
}
|
||||
talloc_free(files);
|
||||
|
||||
struct stream *m = open_memory_stream(pl, strlen(pl));
|
||||
|
||||
stream->priv = m;
|
||||
stream->fill_buffer = rar_filter_fill_buffer;
|
||||
stream->seek = rar_filter_seek;
|
||||
stream->seekable = true;
|
||||
stream->close = rar_filter_close;
|
||||
stream->control = rar_filter_control;
|
||||
stream->safe_origin = true;
|
||||
|
||||
talloc_free(tmp);
|
||||
return STREAM_OK;
|
||||
}
|
||||
|
||||
const stream_info_t stream_info_rar_entry = {
|
||||
.name = "rar_entry",
|
||||
const stream_info_t stream_info_rar = {
|
||||
.name = "rar",
|
||||
.open = rar_entry_open,
|
||||
.protocols = (const char*const[]){ "rar", NULL },
|
||||
};
|
||||
|
||||
const stream_info_t stream_info_rar_filter = {
|
||||
.name = "rar_filter",
|
||||
.open = rar_filter_open,
|
||||
.stream_filter = true,
|
||||
};
|
||||
@@ -133,6 +133,7 @@ static int open_f (stream_t *stream)
|
||||
stream->close = close_f;
|
||||
stream->control = control;
|
||||
stream->read_chunk = 128 * 1024;
|
||||
stream->streaming = true;
|
||||
|
||||
return STREAM_OK;
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
|
||||
static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub", "srt",
|
||||
"smi", "rt", "txt", "ssa", "aqt", "jss",
|
||||
"js", "ass", "mks", NULL};
|
||||
"js", "ass", "mks", "vtt", NULL};
|
||||
|
||||
static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac",
|
||||
"ogg", "m4a", NULL};
|
||||
|
||||
+8
-11
@@ -301,7 +301,6 @@ static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj,
|
||||
{
|
||||
struct MPOpts *opts = osd->opts;
|
||||
|
||||
bool new_track = !obj->osd_track;
|
||||
create_ass_track(osd, obj, 0, 0);
|
||||
ASS_Track *track = obj->osd_track;
|
||||
ASS_Style *style = track->styles + track->default_style;
|
||||
@@ -309,16 +308,14 @@ static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj,
|
||||
*o_w = track->PlayResX * (opts->osd_bar_w / 100.0);
|
||||
*o_h = track->PlayResY * (opts->osd_bar_h / 100.0);
|
||||
|
||||
if (new_track) {
|
||||
float base_size = 0.03125;
|
||||
style->Outline *= *o_h / track->PlayResY / base_size;
|
||||
// So that the chapter marks have space between them
|
||||
style->Outline = FFMIN(style->Outline, *o_h / 5.2);
|
||||
// So that the border is not 0
|
||||
style->Outline = FFMAX(style->Outline, *o_h / 32.0);
|
||||
// Rendering with shadow is broken (because there's more than one shape)
|
||||
style->Shadow = 0;
|
||||
}
|
||||
float base_size = 0.03125;
|
||||
style->Outline *= *o_h / track->PlayResY / base_size;
|
||||
// So that the chapter marks have space between them
|
||||
style->Outline = FFMIN(style->Outline, *o_h / 5.2);
|
||||
// So that the border is not 0
|
||||
style->Outline = FFMAX(style->Outline, *o_h / 32.0);
|
||||
// Rendering with shadow is broken (because there's more than one shape)
|
||||
style->Shadow = 0;
|
||||
|
||||
*o_border = style->Outline;
|
||||
|
||||
|
||||
@@ -402,6 +402,7 @@ int video_reconfig_filters(struct dec_video *d_video,
|
||||
if (sh->aspect > 0)
|
||||
vf_set_dar(&p.d_w, &p.d_h, p.w, p.h, sh->aspect);
|
||||
} else {
|
||||
MP_VERBOSE(d_video, "Using bitstream aspect ratio.\n");
|
||||
// Even if the aspect switches back, don't use container aspect again.
|
||||
d_video->initial_decoder_aspect = -1;
|
||||
}
|
||||
@@ -410,14 +411,6 @@ int video_reconfig_filters(struct dec_video *d_video,
|
||||
if (force_aspect >= 0.0)
|
||||
vf_set_dar(&p.d_w, &p.d_h, p.w, p.h, force_aspect);
|
||||
|
||||
if (abs(p.d_w - p.w) >= 4 || abs(p.d_h - p.h) >= 4) {
|
||||
MP_VERBOSE(d_video, "Aspect ratio is %.2f:1 - "
|
||||
"scaling to correct movie aspect.\n", sh->aspect);
|
||||
} else {
|
||||
p.d_w = p.w;
|
||||
p.d_h = p.h;
|
||||
}
|
||||
|
||||
// Apply user overrides
|
||||
if (opts->requested_colorspace != MP_CSP_AUTO)
|
||||
p.colorspace = opts->requested_colorspace;
|
||||
|
||||
@@ -69,7 +69,6 @@ struct priv {
|
||||
int rt_format;
|
||||
|
||||
struct mp_image_pool *sw_pool;
|
||||
bool printed_readback_warning;
|
||||
};
|
||||
|
||||
#define PE(av_codec_id, ff_profile, vdp_profile) \
|
||||
@@ -450,10 +449,6 @@ static struct mp_image *copy_image(struct lavc_ctx *ctx, struct mp_image *img)
|
||||
|
||||
struct mp_image *simg = va_surface_download(img, p->sw_pool);
|
||||
if (simg) {
|
||||
if (!p->printed_readback_warning) {
|
||||
MP_WARN(p, "Using GPU readback. This is usually inefficient.\n");
|
||||
p->printed_readback_warning = true;
|
||||
}
|
||||
talloc_free(img);
|
||||
return simg;
|
||||
}
|
||||
|
||||
@@ -130,8 +130,8 @@ static const struct vd_lavc_hwdec *const hwdec_list[] = {
|
||||
&mp_vd_lavc_vda,
|
||||
#endif
|
||||
#if HAVE_VAAPI_HWACCEL
|
||||
&mp_vd_lavc_vaapi,
|
||||
&mp_vd_lavc_vaapi_copy,
|
||||
&mp_vd_lavc_vaapi,
|
||||
#endif
|
||||
#if HAVE_DXVA2_HWACCEL
|
||||
&mp_vd_lavc_dxva2_copy,
|
||||
@@ -402,11 +402,6 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
|
||||
avctx->coded_height = sh->video->disp_h;
|
||||
avctx->bits_per_coded_sample = sh->video->bits_per_coded_sample;
|
||||
|
||||
if (sh->video->coded_width && sh->video->coded_height) {
|
||||
avctx->coded_width = sh->video->coded_width;
|
||||
avctx->coded_height = sh->video->coded_height;
|
||||
}
|
||||
|
||||
mp_lavc_set_extradata(avctx, sh->video->extradata, sh->video->extradata_len);
|
||||
|
||||
if (mp_rawvideo) {
|
||||
|
||||
+2
-2
@@ -226,11 +226,11 @@ void vf_print_filter_chain(struct vf_chain *c, int msglevel,
|
||||
char b[128] = {0};
|
||||
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_image_params_to_str(&c->input_params));
|
||||
mp_msg(c->log, msglevel, " [vd] %s\n", b);
|
||||
mp_msg(c->log, msglevel, " [vd] %s\n", b);
|
||||
|
||||
for (vf_instance_t *f = c->first; f; f = f->next) {
|
||||
b[0] = '\0';
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", f->info->name);
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", f->info->name);
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_image_params_to_str(&f->fmt_out));
|
||||
if (f->autoinserted)
|
||||
mp_snprintf_cat(b, sizeof(b), " [a]");
|
||||
|
||||
@@ -163,6 +163,14 @@ static struct mp_image *render(struct vf_instance *vf, struct mp_image *in,
|
||||
status = vaMapBuffer(p->display, buffer, (void**)¶m);
|
||||
if (!check_error(vf, status, "vaMapBuffer()"))
|
||||
break;
|
||||
|
||||
VAProcFilterParameterBufferDeinterlacing *filter_params;
|
||||
status = vaMapBuffer(p->display, *(p->pipe.filters), (void**)&filter_params);
|
||||
if (!check_error(vf, status, "vaMapBuffer()"))
|
||||
break;
|
||||
filter_params->flags = flags & VA_TOP_FIELD ? 0 : VA_DEINTERLACING_BOTTOM_FIELD;
|
||||
vaUnmapBuffer(p->display, *(p->pipe.filters));
|
||||
|
||||
param->surface = in_id;
|
||||
param->surface_region = NULL;
|
||||
param->output_region = NULL;
|
||||
|
||||
@@ -23,4 +23,5 @@
|
||||
- (void)setFullScreen:(BOOL)willBeFullscreen;
|
||||
- (void)clear;
|
||||
- (BOOL)canHideCursor;
|
||||
- (void)signalMousePosition;
|
||||
@end
|
||||
@@ -28,7 +28,6 @@
|
||||
@property(nonatomic, assign) BOOL clearing;
|
||||
@property(nonatomic, assign) BOOL hasMouseDown;
|
||||
@property(nonatomic, retain) NSTrackingArea *tracker;
|
||||
- (void)signalMousePosition;
|
||||
- (BOOL)hasDock:(NSScreen*)screen;
|
||||
- (BOOL)hasMenubar:(NSScreen*)screen;
|
||||
- (int)mpvButtonNumber:(NSEvent*)event;
|
||||
@@ -167,7 +166,8 @@
|
||||
|
||||
- (BOOL)canHideCursor
|
||||
{
|
||||
return !self.hasMouseDown && [self containsMouseLocation];
|
||||
return !self.hasMouseDown && [self containsMouseLocation]
|
||||
&& [[self window] isKeyWindow];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)event
|
||||
@@ -207,6 +207,8 @@
|
||||
- (void)signalMousePosition
|
||||
{
|
||||
NSPoint p = [self convertPointToPixels:[self mouseLocation]];
|
||||
p.x = MIN(MAX(p.x, 0), self.bounds.size.width-1);
|
||||
p.y = MIN(MAX(p.y, 0), self.bounds.size.height-1);
|
||||
[self.adapter signalMouseMovement:p];
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include "video/out/vo.h"
|
||||
|
||||
@interface MpvCocoaAdapter : NSObject
|
||||
@interface MpvCocoaAdapter : NSObject<NSWindowDelegate>
|
||||
- (void)setNeedsResize;
|
||||
- (void)signalMouseMovement:(NSPoint)point;
|
||||
- (void)putKeyEvent:(NSEvent*)event;
|
||||
@@ -28,6 +28,7 @@
|
||||
- (void)handleFilesArray:(NSArray *)files;
|
||||
- (void)didChangeWindowedScreenProfile:(NSScreen *)screen;
|
||||
- (void)performAsyncResize:(NSSize)size;
|
||||
- (void)didChangeMousePosition;
|
||||
|
||||
- (BOOL)isInFullScreenMode;
|
||||
- (BOOL)keyboardEnabled;
|
||||
|
||||
@@ -61,6 +61,16 @@
|
||||
[self.adapter didChangeWindowedScreenProfile:[self screen]];
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification
|
||||
{
|
||||
[self.adapter windowDidResignKey:notification];
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification
|
||||
{
|
||||
[self.adapter windowDidBecomeKey:notification];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeMainWindow { return YES; }
|
||||
- (BOOL)canBecomeKeyWindow { return YES; }
|
||||
- (BOOL)windowShouldClose:(id)sender
|
||||
|
||||
+48
-37
@@ -52,12 +52,13 @@
|
||||
#define cocoa_unlock(s) pthread_mutex_unlock(&s->mutex)
|
||||
|
||||
static void vo_cocoa_fullscreen(struct vo *vo);
|
||||
static void cocoa_rm_fs_screen_profile_observer(struct vo *vo);
|
||||
static void cocoa_rm_fs_screen_profile_observer(struct vo_cocoa_state *s);
|
||||
|
||||
struct vo_cocoa_state {
|
||||
NSWindow *window;
|
||||
NSView *view;
|
||||
MpvVideoView *video;
|
||||
MpvCocoaAdapter *adapter;
|
||||
NSOpenGLContext *gl_ctx;
|
||||
|
||||
NSScreen *current_screen;
|
||||
@@ -87,9 +88,8 @@ struct vo_cocoa_state {
|
||||
void (*resize_redraw)(struct vo *vo, int w, int h);
|
||||
};
|
||||
|
||||
static void with_cocoa_lock(struct vo *vo, void(^block)(void))
|
||||
static void with_cocoa_lock(struct vo_cocoa_state *s, void(^block)(void))
|
||||
{
|
||||
struct vo_cocoa_state *s = vo->cocoa;
|
||||
cocoa_lock(s);
|
||||
block();
|
||||
cocoa_unlock(s);
|
||||
@@ -97,8 +97,9 @@ static void with_cocoa_lock(struct vo *vo, void(^block)(void))
|
||||
|
||||
static void with_cocoa_lock_on_main_thread(struct vo *vo, void(^block)(void))
|
||||
{
|
||||
struct vo_cocoa_state *s = vo->cocoa;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
with_cocoa_lock(vo, block);
|
||||
with_cocoa_lock(s, block);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -111,17 +112,15 @@ static void queue_new_video_size(struct vo *vo, int w, int h)
|
||||
}
|
||||
}
|
||||
|
||||
static void enable_power_management(struct vo *vo)
|
||||
static void enable_power_management(struct vo_cocoa_state *s)
|
||||
{
|
||||
struct vo_cocoa_state *s = vo->cocoa;
|
||||
if (!s->power_mgmt_assertion) return;
|
||||
IOPMAssertionRelease(s->power_mgmt_assertion);
|
||||
s->power_mgmt_assertion = kIOPMNullAssertionID;
|
||||
}
|
||||
|
||||
static void disable_power_management(struct vo *vo)
|
||||
static void disable_power_management(struct vo_cocoa_state *s)
|
||||
{
|
||||
struct vo_cocoa_state *s = vo->cocoa;
|
||||
if (s->power_mgmt_assertion) return;
|
||||
IOPMAssertionCreateWithName(
|
||||
kIOPMAssertionTypePreventUserIdleDisplaySleep,
|
||||
@@ -148,7 +147,7 @@ static void set_application_icon(NSApplication *app)
|
||||
|
||||
int vo_cocoa_init(struct vo *vo)
|
||||
{
|
||||
struct vo_cocoa_state *s = talloc_zero(vo, struct vo_cocoa_state);
|
||||
struct vo_cocoa_state *s = talloc_zero(NULL, struct vo_cocoa_state);
|
||||
*s = (struct vo_cocoa_state){
|
||||
.waiting_frame = false,
|
||||
.power_mgmt_assertion = kIOPMNullAssertionID,
|
||||
@@ -190,16 +189,10 @@ void vo_cocoa_register_resize_callback(struct vo *vo,
|
||||
void vo_cocoa_uninit(struct vo *vo)
|
||||
{
|
||||
struct vo_cocoa_state *s = vo->cocoa;
|
||||
NSView *ev = s->view;
|
||||
|
||||
// keep the event view around for later in order to call -clear
|
||||
if (!s->embedded) {
|
||||
[ev retain];
|
||||
}
|
||||
|
||||
with_cocoa_lock_on_main_thread(vo, ^{
|
||||
enable_power_management(vo);
|
||||
cocoa_rm_fs_screen_profile_observer(vo);
|
||||
enable_power_management(s);
|
||||
cocoa_rm_fs_screen_profile_observer(s);
|
||||
|
||||
[s->gl_ctx release];
|
||||
|
||||
@@ -208,21 +201,15 @@ void vo_cocoa_uninit(struct vo *vo)
|
||||
[s->video removeFromSuperview];
|
||||
|
||||
[s->view removeFromSuperview];
|
||||
[(MpvEventsView *)s->view clear];
|
||||
[s->view release];
|
||||
|
||||
// if using --wid + libmpv there's no window to release
|
||||
if (s->window)
|
||||
[s->window release];
|
||||
});
|
||||
|
||||
// don't use the mutex, because at that point it could have been destroyed
|
||||
// and no one is accessing the events view anyway
|
||||
if (!s->embedded) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[(MpvEventsView *)ev clear];
|
||||
[ev release];
|
||||
});
|
||||
}
|
||||
talloc_free(s);
|
||||
});
|
||||
}
|
||||
|
||||
static int get_screen_handle(struct vo *vo, int identifier, NSWindow *window,
|
||||
@@ -279,6 +266,8 @@ static void vo_cocoa_update_screen_fps(struct vo *vo)
|
||||
s->screen_fps = (t.timeScale / (double) t.timeValue);
|
||||
CVDisplayLinkRelease(link);
|
||||
}
|
||||
|
||||
s->pending_events |= VO_EVENT_WIN_STATE;
|
||||
}
|
||||
|
||||
static void vo_cocoa_update_screen_info(struct vo *vo, struct mp_rect *out_rc)
|
||||
@@ -379,10 +368,9 @@ static void create_ui(struct vo *vo, struct mp_rect *win, int geo_flags)
|
||||
view.adapter = adapter;
|
||||
s->view = view;
|
||||
[parent addSubview:s->view];
|
||||
|
||||
// insert ourselves as the next key view so that clients can give key
|
||||
// focus to the mpv view by calling -[NSWindow selectNextKeyView:]
|
||||
[parent setNextKeyView:s->view];
|
||||
// update the cursor position now that the view has been added.
|
||||
[view signalMousePosition];
|
||||
s->adapter = adapter;
|
||||
|
||||
#if HAVE_COCOA_APPLICATION
|
||||
cocoa_register_menu_item_action(MPM_H_SIZE, @selector(halfSize));
|
||||
@@ -427,9 +415,8 @@ static int cocoa_set_window_title(struct vo *vo, const char *title)
|
||||
return VO_TRUE;
|
||||
}
|
||||
|
||||
static void cocoa_rm_fs_screen_profile_observer(struct vo *vo)
|
||||
static void cocoa_rm_fs_screen_profile_observer(struct vo_cocoa_state *s)
|
||||
{
|
||||
struct vo_cocoa_state *s = vo->cocoa;
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
removeObserver:s->fs_icc_changed_ns_observer];
|
||||
}
|
||||
@@ -439,7 +426,7 @@ static void cocoa_add_fs_screen_profile_observer(struct vo *vo)
|
||||
struct vo_cocoa_state *s = vo->cocoa;
|
||||
|
||||
if (s->fs_icc_changed_ns_observer)
|
||||
cocoa_rm_fs_screen_profile_observer(vo);
|
||||
cocoa_rm_fs_screen_profile_observer(s);
|
||||
|
||||
if (vo->opts->fsscreen_id < 0)
|
||||
return;
|
||||
@@ -501,7 +488,7 @@ int vo_cocoa_config_window(struct vo *vo, uint32_t flags, void *gl_ctx)
|
||||
}
|
||||
|
||||
// trigger a resize -> don't set vo->dwidth and vo->dheight directly
|
||||
// since this block is executed asynchrolously to the video
|
||||
// since this block is executed asynchronously to the video
|
||||
// reconfiguration code.
|
||||
s->pending_events |= VO_EVENT_RESIZE;
|
||||
});
|
||||
@@ -612,6 +599,13 @@ static void vo_cocoa_fullscreen(struct vo *vo)
|
||||
draw_changes_after_next_frame(vo);
|
||||
[(MpvEventsView *)s->view setFullScreen:opts->fullscreen];
|
||||
|
||||
if ([s->view window] != s->window) {
|
||||
// cocoa implements fullscreen views by moving the view to a fullscreen
|
||||
// window. Set that window delegate to the cocoa adapter to trigger
|
||||
// calls to -windowDidResignKey: and -windowDidBecomeKey:
|
||||
[[s->view window] setDelegate:s->adapter];
|
||||
}
|
||||
|
||||
s->pending_events |= VO_EVENT_ICC_PROFILE_CHANGED;
|
||||
s->pending_events |= VO_EVENT_RESIZE;
|
||||
}
|
||||
@@ -645,7 +639,7 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
|
||||
return vo_cocoa_ontop(vo);
|
||||
case VOCTRL_GET_UNFS_WINDOW_SIZE: {
|
||||
int *s = arg;
|
||||
with_cocoa_lock(vo, ^{
|
||||
with_cocoa_lock(vo->cocoa, ^{
|
||||
NSSize size = [vo->cocoa->view frame].size;
|
||||
s[0] = size.width;
|
||||
s[1] = size.height;
|
||||
@@ -667,10 +661,10 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
|
||||
case VOCTRL_UPDATE_WINDOW_TITLE:
|
||||
return cocoa_set_window_title(vo, (const char *) arg);
|
||||
case VOCTRL_RESTORE_SCREENSAVER:
|
||||
enable_power_management(vo);
|
||||
enable_power_management(vo->cocoa);
|
||||
return VO_TRUE;
|
||||
case VOCTRL_KILL_SCREENSAVER:
|
||||
disable_power_management(vo);
|
||||
disable_power_management(vo->cocoa);
|
||||
return VO_TRUE;
|
||||
case VOCTRL_GET_ICC_PROFILE:
|
||||
vo_cocoa_control_get_icc_profile(vo, arg);
|
||||
@@ -769,4 +763,21 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
|
||||
struct vo_cocoa_state *s = self.vout->cocoa;
|
||||
s->pending_events |= VO_EVENT_ICC_PROFILE_CHANGED;
|
||||
}
|
||||
|
||||
- (void)didChangeMousePosition
|
||||
{
|
||||
struct vo_cocoa_state *s = self.vout->cocoa;
|
||||
[(MpvEventsView *)s->view signalMousePosition];
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification
|
||||
{
|
||||
[self didChangeMousePosition];
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification
|
||||
{
|
||||
[self didChangeMousePosition];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -297,43 +297,14 @@ static double lanczos(kernel *k, double x)
|
||||
static double ewa_lanczos(kernel *k, double x)
|
||||
{
|
||||
double radius = k->radius;
|
||||
assert(radius >= 1.0);
|
||||
|
||||
// This is already three orders of magnitude slower than anything you could
|
||||
// possibly hope to play back in realtime and results in tons of ringing
|
||||
// artifacts, so I doubt anybody will complain.
|
||||
if (radius > 16)
|
||||
radius = 16;
|
||||
|
||||
if (fabs(x) < 1e-8)
|
||||
return 1.0;
|
||||
if (fabs(x) >= radius)
|
||||
return 0.0;
|
||||
|
||||
// Precomputed zeros of the jinc() function, needed to adjust the
|
||||
// window size. Computing this at runtime is nontrivial.
|
||||
// Copied from: https://github.com/AviSynth/jinc-resize/blob/master/JincResize/JincFilter.cpp#L171
|
||||
static double jinc_zeros[16] = {
|
||||
1.2196698912665045,
|
||||
2.2331305943815286,
|
||||
3.2383154841662362,
|
||||
4.2410628637960699,
|
||||
5.2427643768701817,
|
||||
6.2439216898644877,
|
||||
7.2447598687199570,
|
||||
8.2453949139520427,
|
||||
9.2458926849494673,
|
||||
10.246293348754916,
|
||||
11.246622794877883,
|
||||
12.246898461138105,
|
||||
13.247132522181061,
|
||||
14.247333735806849,
|
||||
15.247508563037300,
|
||||
16.247661874700962
|
||||
};
|
||||
|
||||
double window = jinc_zeros[0] / jinc_zeros[(int)radius - 1];
|
||||
return jinc(k, x) * jinc(k, x*window);
|
||||
// First zero of the jinc function. We simply scale it to fit into the
|
||||
// given radius.
|
||||
double jinc_zero = 1.2196698912665045;
|
||||
return jinc(k, x) * jinc(k, x * jinc_zero / radius);
|
||||
}
|
||||
|
||||
static double blackman(kernel *k, double x)
|
||||
|
||||
@@ -342,11 +342,15 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
|
||||
|
||||
int major = 0, minor = 0;
|
||||
const char *version_string = gl->GetString(GL_VERSION);
|
||||
if (!version_string)
|
||||
goto error;
|
||||
mp_verbose(log, "GL_VERSION='%s'\n", version_string);
|
||||
if (strncmp(version_string, "OpenGL ES ", 10) == 0) {
|
||||
version_string += 10;
|
||||
gl->es = 100;
|
||||
}
|
||||
sscanf(version_string, "%d.%d", &major, &minor);
|
||||
if (sscanf(version_string, "%d.%d", &major, &minor) < 2)
|
||||
goto error;
|
||||
gl->version = MPGL_VER(major, minor);
|
||||
mp_verbose(log, "Detected %s %d.%d.\n", gl->es ? "GLES" : "desktop OpenGL",
|
||||
major, minor);
|
||||
@@ -362,7 +366,6 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
|
||||
|
||||
mp_verbose(log, "GL_VENDOR='%s'\n", gl->GetString(GL_VENDOR));
|
||||
mp_verbose(log, "GL_RENDERER='%s'\n", gl->GetString(GL_RENDERER));
|
||||
mp_verbose(log, "GL_VERSION='%s'\n", gl->GetString(GL_VERSION));
|
||||
const char *shader = gl->GetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
if (shader)
|
||||
mp_verbose(log, "GL_SHADING_LANGUAGE_VERSION='%s'\n", shader);
|
||||
|
||||
+184
-136
@@ -116,6 +116,7 @@ struct scaler {
|
||||
struct fbosurface {
|
||||
struct fbotex fbotex;
|
||||
int64_t pts;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
#define FBOSURFACES_MAX 2
|
||||
@@ -169,7 +170,6 @@ struct gl_video {
|
||||
|
||||
struct fbotex indirect_fbo; // RGB target
|
||||
struct fbotex scale_sep_fbo; // first pass when doing 2 pass scaling
|
||||
struct fbotex inter_fbo; // interpolation target
|
||||
struct fbosurface surfaces[FBOSURFACES_MAX];
|
||||
size_t surface_idx;
|
||||
|
||||
@@ -467,9 +467,9 @@ void gl_video_set_debug(struct gl_video *p, bool enable)
|
||||
}
|
||||
|
||||
// Draw a textured quad.
|
||||
// x0, y0, x1, y1 = destination coordinates of the quad
|
||||
// tx0, ty0, tx1, ty1 = source texture coordinates (usually in pixels)
|
||||
// texture_w, texture_h = size of the texture, or an inverse factor
|
||||
// x0, y0, x1, y1 = destination coordinates of the quad in pixels
|
||||
// tx0, ty0, tx1, ty1 = source texture coordinates in pixels
|
||||
// tex_w, tex_h = size of the texture in pixels
|
||||
// flags = bits 0-1: rotate, bits 2: flip vertically
|
||||
static void draw_quad(struct gl_video *p,
|
||||
float x0, float y0, float x1, float y1,
|
||||
@@ -506,6 +506,13 @@ static void draw_quad(struct gl_video *p,
|
||||
debug_check_gl(p, "after rendering");
|
||||
}
|
||||
|
||||
static void transpose3x3(float r[3][3])
|
||||
{
|
||||
MPSWAP(float, r[0][1], r[1][0]);
|
||||
MPSWAP(float, r[0][2], r[2][0]);
|
||||
MPSWAP(float, r[1][2], r[2][1]);
|
||||
}
|
||||
|
||||
static void update_uniforms(struct gl_video *p, GLuint program)
|
||||
{
|
||||
GL *gl = p->gl;
|
||||
@@ -551,7 +558,8 @@ static void update_uniforms(struct gl_video *p, GLuint program)
|
||||
} else {
|
||||
mp_get_yuv2rgb_coeffs(&cparams, &m);
|
||||
}
|
||||
gl->UniformMatrix3fv(loc, 1, GL_TRUE, &m.m[0][0]);
|
||||
transpose3x3(m.m); // GLES2 can not transpose in glUniformMatrix3fv
|
||||
gl->UniformMatrix3fv(loc, 1, GL_FALSE, &m.m[0][0]);
|
||||
loc = gl->GetUniformLocation(program, "colormatrix_c");
|
||||
gl->Uniform3f(loc, m.c[0], m.c[1], m.c[2]);
|
||||
}
|
||||
@@ -1060,16 +1068,16 @@ static void compile_shaders(struct gl_video *p)
|
||||
header_sep = talloc_strdup(tmp, "");
|
||||
shader_def_opt(&header_sep, "FIXED_SCALE", true);
|
||||
shader_setup_scaler(&header_sep, &p->scalers[0], 0);
|
||||
shader_setup_scaler(&header_final, &p->scalers[0], 1);
|
||||
shader_setup_scaler(&header_inter, &p->scalers[0], 1);
|
||||
} else {
|
||||
shader_setup_scaler(&header_final, &p->scalers[0], -1);
|
||||
shader_setup_scaler(&header_inter, &p->scalers[0], -1);
|
||||
}
|
||||
|
||||
bool use_interpolation = p->opts.smoothmotion;
|
||||
|
||||
if (use_interpolation) {
|
||||
shader_def_opt(&header_inter, "FIXED_SCALE", true);
|
||||
shader_def_opt(&header_inter, "USE_LINEAR_INTERPOLATION", 1);
|
||||
shader_def_opt(&header_final, "USE_LINEAR_INTERPOLATION", 1);
|
||||
}
|
||||
|
||||
// The indirect pass is used to preprocess the image before scaling.
|
||||
@@ -1077,8 +1085,7 @@ static void compile_shaders(struct gl_video *p)
|
||||
|
||||
// Don't sample from input video textures before converting the input to
|
||||
// its proper gamma.
|
||||
if (use_input_gamma || use_conv_gamma || use_linear_light ||
|
||||
use_const_luma || use_interpolation)
|
||||
if (use_input_gamma || use_conv_gamma || use_linear_light || use_const_luma)
|
||||
use_indirect = true;
|
||||
|
||||
// Trivial scalers are implemented directly and efficiently by the GPU.
|
||||
@@ -1114,7 +1121,7 @@ static void compile_shaders(struct gl_video *p)
|
||||
} else if (header_sep) {
|
||||
header_sep = t_concat(tmp, header_sep, header_conv);
|
||||
} else {
|
||||
header_final = t_concat(tmp, header_final, header_conv);
|
||||
header_inter = t_concat(tmp, header_inter, header_conv);
|
||||
}
|
||||
|
||||
if (header_sep) {
|
||||
@@ -1127,6 +1134,8 @@ static void compile_shaders(struct gl_video *p)
|
||||
header_inter = t_concat(tmp, header, header_inter);
|
||||
p->inter_program =
|
||||
create_program(p, "inter", header_inter, vertex_shader, s_video, v);
|
||||
} else {
|
||||
header_final = t_concat(tmp, header_final, header_inter);
|
||||
}
|
||||
|
||||
header_final = t_concat(tmp, header, header_final);
|
||||
@@ -1418,18 +1427,6 @@ static void reinit_rendering(struct gl_video *p)
|
||||
p->opts.fbo_format);
|
||||
}
|
||||
|
||||
if (p->inter_program) {
|
||||
fbotex_init(&p->inter_fbo, gl, p->log, w, h, p->gl_target, filter,
|
||||
p->opts.fbo_format);
|
||||
}
|
||||
|
||||
if (p->inter_program) {
|
||||
for (int i = 0; i < FBOSURFACES_MAX; i++) {
|
||||
fbotex_init(&p->surfaces[i].fbotex, gl, p->log, w, h, p->gl_target,
|
||||
filter, p->opts.fbo_format);
|
||||
}
|
||||
}
|
||||
|
||||
recreate_osd(p);
|
||||
|
||||
p->need_reinit_rendering = false;
|
||||
@@ -1452,10 +1449,11 @@ static void uninit_rendering(struct gl_video *p)
|
||||
p->dither_texture = 0;
|
||||
|
||||
fbotex_uninit(&p->indirect_fbo);
|
||||
fbotex_uninit(&p->inter_fbo);
|
||||
|
||||
for (int i = 0; i < FBOSURFACES_MAX; i++)
|
||||
for (int i = 0; i < FBOSURFACES_MAX; i++) {
|
||||
fbotex_uninit(&p->surfaces[i].fbotex);
|
||||
p->surfaces[i].valid = false;
|
||||
}
|
||||
|
||||
fbotex_uninit(&p->scale_sep_fbo);
|
||||
}
|
||||
@@ -1732,54 +1730,12 @@ static size_t fbosurface_next(struct gl_video *p)
|
||||
return (p->surface_idx + 1) % FBOSURFACES_MAX;
|
||||
}
|
||||
|
||||
static void gl_video_interpolate_frame(struct gl_video *p,
|
||||
struct pass *chain,
|
||||
struct frame_timing *t)
|
||||
// Handle all of the frame passes upto and including upscaling, assuming
|
||||
// upscaling is not part of the final pass
|
||||
static void gl_video_upscale_frame(struct gl_video *p, struct pass *chain, struct fbotex *inter_fbo)
|
||||
{
|
||||
GL *gl = p->gl;
|
||||
double inter_coeff = 0.0;
|
||||
int64_t prev_pts = p->surfaces[fbosurface_next(p)].pts;
|
||||
p->is_interpolated = prev_pts < t->pts;
|
||||
|
||||
if (p->is_interpolated) {
|
||||
MP_STATS(p, "new-pts");
|
||||
// fbosurface 0 is already bound from the caller
|
||||
p->surfaces[p->surface_idx].pts = t->pts;
|
||||
p->surface_idx = fbosurface_next(p);
|
||||
gl->ActiveTexture(GL_TEXTURE0 + 1);
|
||||
gl->BindTexture(p->gl_target, p->surfaces[p->surface_idx].fbotex.texture);
|
||||
gl->ActiveTexture(GL_TEXTURE0);
|
||||
if (prev_pts < t->next_vsync && t->pts > t->next_vsync) {
|
||||
double N = t->next_vsync - t->prev_vsync;
|
||||
double P = t->pts - t->prev_vsync;
|
||||
float ts = p->opts.smoothmotion_threshold;
|
||||
inter_coeff = 1 - (N / P);
|
||||
inter_coeff = inter_coeff < 0.0 + ts ? 0.0 : inter_coeff;
|
||||
inter_coeff = inter_coeff > 1.0 - ts ? 1.0 : inter_coeff;
|
||||
MP_DBG(p, "inter frame ppts: %lld, pts: %lld, "
|
||||
"vsync: %lld, mix: %f\n",
|
||||
(long long)prev_pts, (long long)t->pts,
|
||||
(long long)t->next_vsync, inter_coeff);
|
||||
MP_STATS(p, "frame-mix");
|
||||
|
||||
// the value is scaled to fit in the graph with the completely
|
||||
// unrelated "phase" value (which is stupid)
|
||||
MP_STATS(p, "value-timed %lld %f mix-value",
|
||||
(long long)t->pts, inter_coeff * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
gl->UseProgram(p->inter_program);
|
||||
GLint loc = gl->GetUniformLocation(p->inter_program, "inter_coeff");
|
||||
gl->Uniform1f(loc, inter_coeff);
|
||||
handle_pass(p, chain, &p->inter_fbo, p->inter_program);
|
||||
}
|
||||
|
||||
// (fbo==0 makes BindFramebuffer select the screen backbuffer)
|
||||
void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
|
||||
{
|
||||
GL *gl = p->gl;
|
||||
struct video_image *vimg = &p->image;
|
||||
// Order of processing: [indirect -> [scale_sep ->]] inter
|
||||
handle_pass(p, chain, &p->indirect_fbo, p->indirect_program);
|
||||
|
||||
// compensated for optional rotation
|
||||
struct mp_rect src_rect_rot = p->src_rect;
|
||||
@@ -1788,6 +1744,99 @@ void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
|
||||
MPSWAP(int, src_rect_rot.x1, src_rect_rot.y1);
|
||||
}
|
||||
|
||||
// Clip to visible height so that separate scaling scales the visible part
|
||||
// only (and the target FBO texture can have a bounded size).
|
||||
// Don't clamp width; too hard to get correct final scaling on l/r borders.
|
||||
chain->f.vp_y = src_rect_rot.y0;
|
||||
chain->f.vp_h = src_rect_rot.y1 - src_rect_rot.y0;
|
||||
|
||||
handle_pass(p, chain, &p->scale_sep_fbo, p->scale_sep_program);
|
||||
|
||||
// For Y direction, use the whole source viewport; it has been fit to the
|
||||
// correct origin/height before.
|
||||
// For X direction, assume the texture wasn't scaled yet, so we can
|
||||
// select the correct portion, which will be scaled to screen.
|
||||
chain->f.vp_x = src_rect_rot.x0;
|
||||
chain->f.vp_w = src_rect_rot.x1 - src_rect_rot.x0;
|
||||
|
||||
if (inter_fbo)
|
||||
handle_pass(p, chain, inter_fbo, p->inter_program);
|
||||
}
|
||||
|
||||
static double gl_video_interpolate_frame(struct gl_video *p,
|
||||
struct pass *chain,
|
||||
struct frame_timing *t)
|
||||
{
|
||||
GL *gl = p->gl;
|
||||
double inter_coeff = 0.0;
|
||||
int64_t prev_pts = p->surfaces[fbosurface_next(p)].pts;
|
||||
|
||||
// Make sure all surfaces are actually valid, and redraw them manually
|
||||
// if this is not the case
|
||||
for (int i = 0; i < FBOSURFACES_MAX; i++) {
|
||||
if (!p->surfaces[i].valid) {
|
||||
struct pass frame = { .f = chain->f };
|
||||
gl_video_upscale_frame(p, &frame, &p->surfaces[i].fbotex);
|
||||
p->surfaces[i].valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (t && prev_pts < t->pts) {
|
||||
MP_STATS(p, "new-pts");
|
||||
gl_video_upscale_frame(p, chain, &p->surfaces[p->surface_idx].fbotex);
|
||||
p->surfaces[p->surface_idx].valid = true;
|
||||
p->surfaces[p->surface_idx].pts = t->pts;
|
||||
p->surface_idx = fbosurface_next(p);
|
||||
} else {
|
||||
// re-use the previously rendered surface as source
|
||||
chain->f = p->surfaces[fbosurface_next(p)].fbotex;
|
||||
}
|
||||
|
||||
// fbosurface 0 is bound by handle_pass
|
||||
gl->ActiveTexture(GL_TEXTURE0 + 1);
|
||||
gl->BindTexture(p->gl_target, p->surfaces[p->surface_idx].fbotex.texture);
|
||||
gl->ActiveTexture(GL_TEXTURE0);
|
||||
|
||||
if (!t) {
|
||||
p->is_interpolated = false;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
int64_t vsync_interval = t->next_vsync - t->prev_vsync;
|
||||
|
||||
if (t->pts > t->next_vsync && t->pts < t->next_vsync + vsync_interval) {
|
||||
// current frame overlaps PTS boundary, blend
|
||||
double R = t->pts - t->next_vsync;
|
||||
float ts = p->opts.smoothmotion_threshold;
|
||||
inter_coeff = R / vsync_interval;
|
||||
inter_coeff = inter_coeff < 0.0 + ts ? 0.0 : inter_coeff;
|
||||
inter_coeff = inter_coeff > 1.0 - ts ? 1.0 : inter_coeff;
|
||||
MP_DBG(p, "inter frame ppts: %lld, pts: %lld, "
|
||||
"vsync: %lld, mix: %f\n",
|
||||
(long long)prev_pts, (long long)t->pts,
|
||||
(long long)t->next_vsync, inter_coeff);
|
||||
MP_STATS(p, "frame-mix");
|
||||
|
||||
// the value is scaled to fit in the graph with the completely
|
||||
// unrelated "phase" value (which is stupid)
|
||||
MP_STATS(p, "value-timed %lld %f mix-value",
|
||||
(long long)t->pts, inter_coeff * 10000);
|
||||
} else if (t->pts > t->next_vsync) {
|
||||
// there's a new frame, but we haven't displayed or blended it yet,
|
||||
// so we still draw the old frame
|
||||
inter_coeff = 1.0;
|
||||
}
|
||||
|
||||
p->is_interpolated = inter_coeff > 0.0;
|
||||
return inter_coeff;
|
||||
}
|
||||
|
||||
// (fbo==0 makes BindFramebuffer select the screen backbuffer)
|
||||
void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
|
||||
{
|
||||
GL *gl = p->gl;
|
||||
struct video_image *vimg = &p->image;
|
||||
|
||||
p->is_interpolated = false;
|
||||
|
||||
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
@@ -1808,9 +1857,6 @@ void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
|
||||
goto draw_osd;
|
||||
}
|
||||
|
||||
// Order of processing:
|
||||
// [indirect -> [interpolate -> [scale_sep ->]]] final
|
||||
|
||||
GLuint imgtex[4] = {0};
|
||||
set_image_textures(p, vimg, imgtex);
|
||||
|
||||
@@ -1824,27 +1870,13 @@ void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
|
||||
},
|
||||
};
|
||||
|
||||
int64_t prev_pts = p->surfaces[fbosurface_next(p)].pts;
|
||||
struct fbotex *indirect_target;
|
||||
if (p->inter_program && t && prev_pts < t->pts) {
|
||||
indirect_target = &p->surfaces[p->surface_idx].fbotex;
|
||||
double inter_coeff = 0.0;
|
||||
if (p->opts.smoothmotion) {
|
||||
inter_coeff = gl_video_interpolate_frame(p, &chain, t);
|
||||
} else {
|
||||
indirect_target = &p->indirect_fbo;
|
||||
gl_video_upscale_frame(p, &chain, NULL);
|
||||
}
|
||||
|
||||
handle_pass(p, &chain, indirect_target, p->indirect_program);
|
||||
|
||||
if (t && p->inter_program)
|
||||
gl_video_interpolate_frame(p, &chain, t);
|
||||
|
||||
// Clip to visible height so that separate scaling scales the visible part
|
||||
// only (and the target FBO texture can have a bounded size).
|
||||
// Don't clamp width; too hard to get correct final scaling on l/r borders.
|
||||
chain.f.vp_y = src_rect_rot.y0;
|
||||
chain.f.vp_h = src_rect_rot.y1 - src_rect_rot.y0;
|
||||
|
||||
handle_pass(p, &chain, &p->scale_sep_fbo, p->scale_sep_program);
|
||||
|
||||
struct fbotex screen = {
|
||||
.vp_x = p->vp_x,
|
||||
.vp_y = p->vp_y,
|
||||
@@ -1853,18 +1885,14 @@ void gl_video_render_frame(struct gl_video *p, int fbo, struct frame_timing *t)
|
||||
.fbo = fbo,
|
||||
};
|
||||
|
||||
// For Y direction, use the whole source viewport; it has been fit to the
|
||||
// correct origin/height before.
|
||||
// For X direction, assume the texture wasn't scaled yet, so we can
|
||||
// select the correct portion, which will be scaled to screen.
|
||||
chain.f.vp_x = src_rect_rot.x0;
|
||||
chain.f.vp_w = src_rect_rot.x1 - src_rect_rot.x0;
|
||||
|
||||
chain.use_dst = true;
|
||||
chain.dst = p->dst_rect;
|
||||
chain.flags = (p->image_params.rotate % 90 ? 0 : p->image_params.rotate / 90)
|
||||
| (vimg->image_flipped ? 4 : 0);
|
||||
|
||||
gl->UseProgram(p->final_program);
|
||||
GLint loc = gl->GetUniformLocation(p->final_program, "inter_coeff");
|
||||
gl->Uniform1f(loc, inter_coeff);
|
||||
handle_pass(p, &chain, &screen, p->final_program);
|
||||
|
||||
gl->UseProgram(0);
|
||||
@@ -1883,22 +1911,39 @@ draw_osd:
|
||||
|
||||
static void update_window_sized_objects(struct gl_video *p)
|
||||
{
|
||||
int w = p->dst_rect.x1 - p->dst_rect.x0;
|
||||
int h = p->dst_rect.y1 - p->dst_rect.y0;
|
||||
if ((p->image_params.rotate % 180) == 90)
|
||||
MPSWAP(int, w, h);
|
||||
|
||||
// Round up to an arbitrary alignment to make window resizing or
|
||||
// panscan controls smoother (less texture reallocations).
|
||||
int width = FFALIGN(w, 256);
|
||||
int height = FFALIGN(h, 256);
|
||||
|
||||
if (p->scale_sep_program) {
|
||||
int w = p->dst_rect.x1 - p->dst_rect.x0;
|
||||
int h = p->dst_rect.y1 - p->dst_rect.y0;
|
||||
if ((p->image_params.rotate % 180) == 90)
|
||||
MPSWAP(int, w, h);
|
||||
if (h > p->scale_sep_fbo.tex_h) {
|
||||
fbotex_uninit(&p->scale_sep_fbo);
|
||||
// Round up to an arbitrary alignment to make window resizing or
|
||||
// panscan controls smoother (less texture reallocations).
|
||||
int height = FFALIGN(h, 256);
|
||||
fbotex_init(&p->scale_sep_fbo, p->gl, p->log, p->image_w, height,
|
||||
p->gl_target, GL_NEAREST, p->opts.fbo_format);
|
||||
}
|
||||
p->scale_sep_fbo.vp_w = p->image_w;
|
||||
p->scale_sep_fbo.vp_h = h;
|
||||
}
|
||||
|
||||
if (p->opts.smoothmotion) {
|
||||
for (int i = 0; i < FBOSURFACES_MAX; i++) {
|
||||
struct fbotex *fbo = &p->surfaces[i].fbotex;
|
||||
if (w > fbo->tex_w || h > fbo->tex_h) {
|
||||
fbotex_uninit(fbo);
|
||||
fbotex_init(fbo, p->gl, p->log, width, height,
|
||||
p->gl_target, GL_NEAREST, p->opts.fbo_format);
|
||||
}
|
||||
fbo->vp_w = w;
|
||||
fbo->vp_h = h;
|
||||
p->surfaces[i].valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_resize(struct gl_video *p)
|
||||
@@ -2041,20 +2086,26 @@ void gl_video_upload_image(struct gl_video *p, struct mp_image *mpi)
|
||||
gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
static bool test_fbo(struct gl_video *p, GLenum format)
|
||||
static bool test_fbo(struct gl_video *p, bool *success)
|
||||
{
|
||||
if (!*success)
|
||||
return false;
|
||||
|
||||
GL *gl = p->gl;
|
||||
bool success = false;
|
||||
*success = false;
|
||||
MP_VERBOSE(p, "Testing user-set FBO format (0x%x)\n",
|
||||
(unsigned)p->opts.fbo_format);
|
||||
struct fbotex fbo = {0};
|
||||
if (fbotex_init(&fbo, p->gl, p->log, 16, 16, p->gl_target, GL_LINEAR, format))
|
||||
if (fbotex_init(&fbo, p->gl, p->log, 16, 16, p->gl_target, GL_LINEAR,
|
||||
p->opts.fbo_format))
|
||||
{
|
||||
gl->BindFramebuffer(GL_FRAMEBUFFER, fbo.fbo);
|
||||
gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
success = true;
|
||||
*success = true;
|
||||
}
|
||||
fbotex_uninit(&fbo);
|
||||
glCheckError(gl, p->log, "FBO test");
|
||||
return success;
|
||||
return *success;
|
||||
}
|
||||
|
||||
// Disable features that are not supported with the current OpenGL version.
|
||||
@@ -2071,33 +2122,25 @@ static void check_gl_features(struct gl_video *p)
|
||||
char *disabled[10];
|
||||
int n_disabled = 0;
|
||||
|
||||
if (have_fbo) {
|
||||
MP_VERBOSE(p, "Testing user-set FBO format (0x%x)\n",
|
||||
(unsigned)p->opts.fbo_format);
|
||||
have_fbo = test_fbo(p, p->opts.fbo_format);
|
||||
}
|
||||
|
||||
// Normally, we want to disable them by default if FBOs are unavailable,
|
||||
// because they will be slow (not critically slow, but still slower).
|
||||
// Without FP textures, we must always disable them.
|
||||
// I don't know if luminance alpha float textures exist, so disregard them.
|
||||
if (!have_float_tex || !have_arrays || !have_fbo || !have_1d_tex) {
|
||||
for (int n = 0; n < 2; n++) {
|
||||
const struct filter_kernel *kernel = mp_find_filter_kernel(p->opts.scalers[n]);
|
||||
if (kernel) {
|
||||
char *reason = "";
|
||||
if (!have_fbo)
|
||||
reason = "scaler (FBO)";
|
||||
if (!have_float_tex)
|
||||
reason = "scaler (float tex.)";
|
||||
if (!have_arrays)
|
||||
reason = "scaler (no GLSL support)";
|
||||
if (!have_1d_tex && kernel->polar)
|
||||
reason = "scaler (1D tex.)";
|
||||
if (*reason) {
|
||||
p->opts.scalers[n] = "bilinear";
|
||||
disabled[n_disabled++] = reason;
|
||||
}
|
||||
for (int n = 0; n < 2; n++) {
|
||||
const struct filter_kernel *kernel = mp_find_filter_kernel(p->opts.scalers[n]);
|
||||
if (kernel) {
|
||||
char *reason = NULL;
|
||||
if (!test_fbo(p, &have_fbo))
|
||||
reason = "scaler (FBO)";
|
||||
if (!have_float_tex)
|
||||
reason = "scaler (float tex.)";
|
||||
if (!have_arrays)
|
||||
reason = "scaler (no GLSL support)";
|
||||
if (!have_1d_tex && kernel->polar)
|
||||
reason = "scaler (1D tex.)";
|
||||
if (reason) {
|
||||
p->opts.scalers[n] = "bilinear";
|
||||
disabled[n_disabled++] = reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2122,11 +2165,15 @@ static void check_gl_features(struct gl_video *p)
|
||||
p->opts.srgb = false;
|
||||
disabled[n_disabled++] = "sRGB output (GLSL version)";
|
||||
}
|
||||
if (!have_fbo && use_cms) {
|
||||
if (use_cms && !test_fbo(p, &have_fbo)) {
|
||||
p->opts.srgb = false;
|
||||
p->use_lut_3d = false;
|
||||
disabled[n_disabled++] = "color management (FBO)";
|
||||
}
|
||||
if (p->opts.smoothmotion && !test_fbo(p, &have_fbo)) {
|
||||
p->opts.smoothmotion = false;
|
||||
disabled[n_disabled++] = "smoothmotion (FBO)";
|
||||
}
|
||||
// because of bt709_expand()
|
||||
if (!have_mix && p->use_lut_3d) {
|
||||
p->use_lut_3d = false;
|
||||
@@ -2183,6 +2230,7 @@ static int init_gl(struct gl_video *p)
|
||||
MP_VERBOSE(p, "16 bit texture depth: %d.\n", (int)param);
|
||||
p->texture_16bit_depth = param;
|
||||
}
|
||||
gl->DeleteTextures(1, &tex);
|
||||
}
|
||||
|
||||
debug_check_gl(p, "after init_gl");
|
||||
|
||||
@@ -252,7 +252,6 @@ float[6] weights6(sampler2D lookup, float f) {
|
||||
vec4 c2 = texture(lookup, vec2(0.75, f));
|
||||
return float[6](c1.r, c1.g, c1.b, c2.r, c2.g, c2.b);
|
||||
}
|
||||
#endif
|
||||
|
||||
// For N=n*4 with n>1.
|
||||
#define WEIGHTS_N(NAME, N) \
|
||||
@@ -305,12 +304,12 @@ float[6] weights6(sampler2D lookup, float f) {
|
||||
w = texture1D(LUT, length(vec2(X, Y) - fcoord)/R).r; \
|
||||
c = texture(tex, base + pt * vec2(X, Y)); \
|
||||
wsum += w; \
|
||||
res += vec4(w) * c; \
|
||||
res += vec4(w) * c;
|
||||
|
||||
#define SAMPLE_POLAR_PRIMARY(LUT, R, X, Y) \
|
||||
SAMPLE_POLAR_HELPER(LUT, R, X, Y) \
|
||||
lo = min(lo, c); \
|
||||
hi = max(hi, c); \
|
||||
hi = max(hi, c);
|
||||
|
||||
#define SAMPLE_CONVOLUTION_POLAR_R(NAME, R, LUT, WEIGHTS_FN, ANTIRING) \
|
||||
vec4 NAME(VIDEO_SAMPLER tex, vec2 texsize, vec2 texcoord) { \
|
||||
@@ -328,6 +327,8 @@ float[6] weights6(sampler2D lookup, float f) {
|
||||
return mix(res, clamp(res, lo, hi), ANTIRING); \
|
||||
}
|
||||
|
||||
#endif /* HAVE_ARRAYS */
|
||||
|
||||
#ifdef DEF_SCALER0
|
||||
DEF_SCALER0
|
||||
#endif
|
||||
@@ -387,9 +388,6 @@ void main() {
|
||||
texture(texture0, texcoord),
|
||||
texture(texture1, texcoord),
|
||||
inter_coeff);
|
||||
// debug code to visually check the interpolation amount
|
||||
// vec4 acolor = texture(texture0, texcoord) -
|
||||
// inter_coeff * texture(texture1, texcoord);
|
||||
#elif USE_CONV == CONV_PLANAR
|
||||
vec4 acolor = vec4(SAMPLE(texture0, textures_size[0], texcoord).r,
|
||||
SAMPLE_C(texture1, textures_size[1], chr_texcoord).r,
|
||||
|
||||
+33
-13
@@ -129,7 +129,8 @@ struct vo_internal {
|
||||
bool paused;
|
||||
bool vsync_timed; // the VO redraws itself as fast as possible
|
||||
// at every vsync
|
||||
int queued_events;
|
||||
int queued_events; // event mask for the user
|
||||
int internal_events; // event mask for us
|
||||
|
||||
int64_t flip_queue_offset; // queue flip events at most this much in advance
|
||||
|
||||
@@ -145,8 +146,9 @@ struct vo_internal {
|
||||
int64_t frame_pts; // realtime of intended display
|
||||
int64_t frame_duration; // realtime frame duration (for framedrop)
|
||||
|
||||
// --- The following fields can be accessed from the VO thread only
|
||||
int64_t vsync_interval;
|
||||
|
||||
// --- The following fields can be accessed from the VO thread only
|
||||
int64_t vsync_interval_approx;
|
||||
int64_t last_flip;
|
||||
char *window_title;
|
||||
@@ -166,7 +168,7 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
.priv_size = vo->priv_size,
|
||||
.priv_defaults = vo->priv_defaults,
|
||||
.options = vo->options,
|
||||
.hidden = vo->encode,
|
||||
.hidden = vo->encode || !strcmp(vo->name, "opengl-cb"),
|
||||
.p = vo,
|
||||
};
|
||||
return true;
|
||||
@@ -230,6 +232,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global,
|
||||
talloc_steal(vo, log);
|
||||
*vo->in = (struct vo_internal) {
|
||||
.dispatch = mp_dispatch_create(vo),
|
||||
.internal_events = VO_EVENT_WIN_STATE,
|
||||
};
|
||||
mp_make_wakeup_pipe(vo->in->wakeup_pipe);
|
||||
mp_dispatch_set_wakeup_fn(vo->in->dispatch, dispatch_wakeup_cb, vo);
|
||||
@@ -302,16 +305,27 @@ void vo_destroy(struct vo *vo)
|
||||
// to be called from VO thread only
|
||||
static void update_display_fps(struct vo *vo)
|
||||
{
|
||||
double display_fps = 1000.0; // assume infinite if unset
|
||||
if (vo->global->opts->frame_drop_fps > 0) {
|
||||
display_fps = vo->global->opts->frame_drop_fps;
|
||||
} else {
|
||||
vo->driver->control(vo, VOCTRL_GET_DISPLAY_FPS, &display_fps);
|
||||
struct vo_internal *in = vo->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
if (in->internal_events & VO_EVENT_WIN_STATE) {
|
||||
in->internal_events &= ~(unsigned)VO_EVENT_WIN_STATE;
|
||||
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
|
||||
double display_fps = 1000.0; // assume infinite if unset
|
||||
if (vo->global->opts->frame_drop_fps > 0) {
|
||||
display_fps = vo->global->opts->frame_drop_fps;
|
||||
} else {
|
||||
vo->driver->control(vo, VOCTRL_GET_DISPLAY_FPS, &display_fps);
|
||||
}
|
||||
int64_t n_interval = MPMAX((int64_t)(1e6 / display_fps), 1);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
if (vo->in->vsync_interval != n_interval)
|
||||
MP_VERBOSE(vo, "Assuming %f FPS for framedrop.\n", display_fps);
|
||||
vo->in->vsync_interval = n_interval;
|
||||
}
|
||||
int64_t n_interval = MPMAX((int64_t)(1e6 / display_fps), 1);
|
||||
if (vo->in->vsync_interval != n_interval)
|
||||
MP_VERBOSE(vo, "Assuming %f FPS for framedrop.\n", display_fps);
|
||||
vo->in->vsync_interval = n_interval;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
static void check_vo_caps(struct vo *vo)
|
||||
@@ -533,6 +547,7 @@ void vo_wait_frame(struct vo *vo)
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
// needs lock
|
||||
static int64_t prev_sync(struct vo *vo, int64_t ts)
|
||||
{
|
||||
struct vo_internal *in = vo->in;
|
||||
@@ -895,7 +910,11 @@ void vo_set_flip_queue_params(struct vo *vo, int64_t offset_us, bool vsync_timed
|
||||
|
||||
int64_t vo_get_vsync_interval(struct vo *vo)
|
||||
{
|
||||
return vo->in->vsync_interval;
|
||||
struct vo_internal *in = vo->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
int64_t res = vo->in->vsync_interval;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Set specific event flags, and wakeup the playback core if needed.
|
||||
@@ -907,6 +926,7 @@ void vo_event(struct vo *vo, int event)
|
||||
if ((in->queued_events & event & VO_EVENTS_USER) != (event & VO_EVENTS_USER))
|
||||
mp_input_wakeup(vo->input_ctx);
|
||||
in->queued_events |= event;
|
||||
in->internal_events |= event;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@
|
||||
#define VO_EVENT_RESIZE 2
|
||||
// The ICC profile needs to be reloaded
|
||||
#define VO_EVENT_ICC_PROFILE_CHANGED 4
|
||||
// Some other window state changed
|
||||
// Some other window state changed (position, window state, fps)
|
||||
#define VO_EVENT_WIN_STATE 8
|
||||
|
||||
// Set of events the player core may be interested in.
|
||||
|
||||
@@ -940,7 +940,7 @@ render_osd:
|
||||
// Return the high byte of the value that represents white in chroma (U/V)
|
||||
static int get_chroma_clear_val(int bit_depth)
|
||||
{
|
||||
return 1 << (bit_depth - 1 & 7);
|
||||
return 1 << ((bit_depth - 1) & 7);
|
||||
}
|
||||
|
||||
// this macro is supposed to work on all formats supported by 3D rendering, and
|
||||
|
||||
@@ -254,15 +254,17 @@ static bool get_and_update_icc_profile(struct gl_priv *p, int *events)
|
||||
{
|
||||
if (p->icc_opts->profile_auto) {
|
||||
MP_VERBOSE(p, "Querying ICC profile...\n");
|
||||
bstr icc;
|
||||
bstr icc = bstr0(NULL);
|
||||
int r = p->glctx->vo_control(p->vo, events, VOCTRL_GET_ICC_PROFILE, &icc);
|
||||
|
||||
if (r == VO_TRUE) {
|
||||
if (r != VO_NOTAVAIL) {
|
||||
if (r == VO_FALSE) {
|
||||
MP_WARN(p, "Could not retrieve an ICC profile.\n");
|
||||
} else if (r == VO_NOTIMPL) {
|
||||
MP_ERR(p, "icc-profile-auto not implemented on this platform.\n");
|
||||
}
|
||||
|
||||
gl_lcms_set_memory_profile(p->cms, &icc);
|
||||
} else if (r == VO_NOTIMPL) {
|
||||
MP_ERR(p, "icc-profile-auto not implemented on this platform.\n");
|
||||
} else {
|
||||
MP_ERR(p, "Could not retrieve an ICC profile.\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -306,20 +306,20 @@ int mpv_opengl_cb_render(struct mpv_opengl_cb_context *ctx, int fbo, int vp[4])
|
||||
gl_video_resize(ctx->renderer, &wnd, &src, &dst, &osd, !ctx->flip);
|
||||
}
|
||||
|
||||
if (vo) {
|
||||
struct vo_priv *p = vo->priv;
|
||||
if (ctx->reconfigured)
|
||||
gl_video_config(ctx->renderer, &ctx->img_params);
|
||||
if (ctx->reconfigured || ctx->update_new_opts) {
|
||||
struct vo_priv *opts = p->ctx->new_opts ? p->ctx->new_opts : p;
|
||||
if (ctx->reconfigured)
|
||||
gl_video_config(ctx->renderer, &ctx->img_params);
|
||||
if (ctx->update_new_opts) {
|
||||
struct vo_priv *p = vo ? vo->priv : NULL;
|
||||
struct vo_priv *opts = ctx->new_opts ? ctx->new_opts : p;
|
||||
if (opts) {
|
||||
gl_video_set_options(ctx->renderer, opts->renderer_opts);
|
||||
ctx->gl->debug_context = opts->use_gl_debug;
|
||||
gl_video_set_debug(ctx->renderer, opts->use_gl_debug);
|
||||
frame_queue_shrink(ctx, opts->frame_queue_size);
|
||||
}
|
||||
ctx->reconfigured = false;
|
||||
ctx->update_new_opts = false;
|
||||
}
|
||||
ctx->reconfigured = false;
|
||||
ctx->update_new_opts = false;
|
||||
|
||||
struct mp_csp_equalizer *eq = gl_video_eq_ptr(ctx->renderer);
|
||||
if (ctx->eq_changed) {
|
||||
@@ -507,6 +507,7 @@ static void uninit(struct vo *vo)
|
||||
p->ctx->img_params = (struct mp_image_params){0};
|
||||
p->ctx->reconfigured = true;
|
||||
p->ctx->active = NULL;
|
||||
update(p);
|
||||
pthread_mutex_unlock(&p->ctx->lock);
|
||||
}
|
||||
|
||||
|
||||
+41
-14
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
@@ -100,6 +101,7 @@ struct priv {
|
||||
unsigned int *va_subpic_flags;
|
||||
int va_num_subpic_formats;
|
||||
VADisplayAttribute *va_display_attrs;
|
||||
int *mp_display_attr;
|
||||
int va_num_display_attrs;
|
||||
};
|
||||
|
||||
@@ -446,46 +448,70 @@ static int get_displayattribtype(const char *name)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static VADisplayAttribute *get_display_attribute(struct priv *p,
|
||||
const char *name)
|
||||
static int get_display_attribute(struct priv *p, const char *name)
|
||||
{
|
||||
int type = get_displayattribtype(name);
|
||||
for (int n = 0; n < p->va_num_display_attrs; n++) {
|
||||
VADisplayAttribute *attr = &p->va_display_attrs[n];
|
||||
if (attr->type == type)
|
||||
return attr;
|
||||
return n;
|
||||
}
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int mp_eq_to_va(VADisplayAttribute * const attr, int mpvalue)
|
||||
{
|
||||
/* normalize to attribute value range */
|
||||
int r = attr->max_value - attr->min_value;
|
||||
if (r == 0)
|
||||
return INT_MIN; // assume INT_MIN is outside allowed min/max range
|
||||
return ((mpvalue + 100) * r + 100) / 200 + attr->min_value;
|
||||
}
|
||||
|
||||
static int get_equalizer(struct priv *p, const char *name, int *value)
|
||||
{
|
||||
VADisplayAttribute * const attr = get_display_attribute(p, name);
|
||||
int index = get_display_attribute(p, name);
|
||||
if (index < 0)
|
||||
return VO_NOTIMPL;
|
||||
|
||||
if (!attr || !(attr->flags & VA_DISPLAY_ATTRIB_GETTABLE))
|
||||
VADisplayAttribute *attr = &p->va_display_attrs[index];
|
||||
|
||||
if (!(attr->flags & VA_DISPLAY_ATTRIB_GETTABLE))
|
||||
return VO_NOTIMPL;
|
||||
|
||||
/* normalize to -100 .. 100 range */
|
||||
int r = attr->max_value - attr->min_value;
|
||||
if (r == 0)
|
||||
return VO_NOTIMPL;
|
||||
*value = ((attr->value - attr->min_value) * 200) / r - 100;
|
||||
|
||||
*value = ((attr->value - attr->min_value) * 200 + r / 2) / r - 100;
|
||||
if (mp_eq_to_va(attr, p->mp_display_attr[index]) == attr->value)
|
||||
*value = p->mp_display_attr[index];
|
||||
|
||||
return VO_TRUE;
|
||||
}
|
||||
|
||||
static int set_equalizer(struct priv *p, const char *name, int value)
|
||||
{
|
||||
VADisplayAttribute * const attr = get_display_attribute(p, name);
|
||||
VAStatus status;
|
||||
|
||||
if (!attr || !(attr->flags & VA_DISPLAY_ATTRIB_SETTABLE))
|
||||
int index = get_display_attribute(p, name);
|
||||
if (index < 0)
|
||||
return VO_NOTIMPL;
|
||||
|
||||
/* normalize to attribute value range */
|
||||
int r = attr->max_value - attr->min_value;
|
||||
if (r == 0)
|
||||
VADisplayAttribute *attr = &p->va_display_attrs[index];
|
||||
|
||||
if (!(attr->flags & VA_DISPLAY_ATTRIB_SETTABLE))
|
||||
return VO_NOTIMPL;
|
||||
attr->value = ((value + 100) * r) / 200 + attr->min_value;
|
||||
|
||||
int r = mp_eq_to_va(attr, value);
|
||||
if (r == INT_MIN)
|
||||
return VO_NOTIMPL;
|
||||
|
||||
attr->value = r;
|
||||
p->mp_display_attr[index] = value;
|
||||
|
||||
MP_VERBOSE(p, "Changing '%s' (range [%d, %d]) to %d\n", name,
|
||||
attr->max_value, attr->min_value, attr->value);
|
||||
|
||||
va_lock(p->mpvaapi);
|
||||
status = vaSetDisplayAttributes(p->display, attr, 1);
|
||||
@@ -642,6 +668,7 @@ static int preinit(struct vo *vo)
|
||||
&p->va_num_display_attrs);
|
||||
if (!CHECK_VA_STATUS(p, "vaQueryDisplayAttributes()"))
|
||||
p->va_num_display_attrs = 0;
|
||||
p->mp_display_attr = talloc_zero_array(vo, int, p->va_num_display_attrs);
|
||||
}
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -382,6 +382,8 @@ static void xv_draw_colorkey(struct vo *vo, const struct mp_rect *rc)
|
||||
if (ctx->xv_ck_info.method == CK_METHOD_MANUALFILL ||
|
||||
ctx->xv_ck_info.method == CK_METHOD_BACKGROUND)
|
||||
{
|
||||
if (!x11->vo_gc)
|
||||
return;
|
||||
//less tearing than XClearWindow()
|
||||
XSetForeground(x11->display, x11->vo_gc, ctx->xv_colorkey);
|
||||
XFillRectangle(x11->display, x11->window, x11->vo_gc, rc->x0, rc->y0,
|
||||
|
||||
+10
-17
@@ -98,6 +98,9 @@ struct vo_w32_state {
|
||||
int mouse_x;
|
||||
int mouse_y;
|
||||
|
||||
// Should SetCursor be called when handling VOCTRL_SET_CURSOR_VISIBILITY?
|
||||
bool can_set_cursor;
|
||||
|
||||
// UTF-16 decoding state for WM_CHAR and VK_PACKET
|
||||
int high_surrogate;
|
||||
|
||||
@@ -312,16 +315,9 @@ static void subtract_window_borders(HWND hwnd, RECT *rc)
|
||||
rc->bottom -= b.bottom;
|
||||
}
|
||||
|
||||
static bool is_maximized(struct vo_w32_state *w32)
|
||||
{
|
||||
WINDOWPLACEMENT wp = { .length = sizeof(WINDOWPLACEMENT) };
|
||||
GetWindowPlacement(w32->window, &wp);
|
||||
return wp.showCmd == SW_SHOWMAXIMIZED;
|
||||
}
|
||||
|
||||
static LRESULT borderless_nchittest(struct vo_w32_state *w32, int x, int y)
|
||||
{
|
||||
if (is_maximized(w32))
|
||||
if (IsMaximized(w32->window))
|
||||
return HTCLIENT;
|
||||
|
||||
POINT mouse = { x, y };
|
||||
@@ -403,7 +399,7 @@ static int mod_state(struct vo_w32_state *w32)
|
||||
|
||||
static int decode_surrogate_pair(wchar_t lead, wchar_t trail)
|
||||
{
|
||||
return 0x10000 + ((lead & 0x3ff) << 10) | (trail & 0x3ff);
|
||||
return 0x10000 + (((lead & 0x3ff) << 10) | (trail & 0x3ff));
|
||||
}
|
||||
|
||||
static int decode_utf16(struct vo_w32_state *w32, wchar_t c)
|
||||
@@ -686,7 +682,10 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
mp_input_put_key(w32->input_ctx, MP_INPUT_RELEASE_ALL);
|
||||
break;
|
||||
case WM_SETCURSOR:
|
||||
if (LOWORD(lParam) == HTCLIENT && !w32->cursor_visible) {
|
||||
// The cursor should only be hidden if the mouse is in the client area
|
||||
// and if the window isn't in menu mode (HIWORD(lParam) is non-zero)
|
||||
w32->can_set_cursor = LOWORD(lParam) == HTCLIENT && HIWORD(lParam);
|
||||
if (w32->can_set_cursor && !w32->cursor_visible) {
|
||||
SetCursor(NULL);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -1206,12 +1205,6 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool vo_w32_is_cursor_in_client(struct vo_w32_state *w32)
|
||||
{
|
||||
DWORD pos = GetMessagePos();
|
||||
return SendMessage(w32->window, WM_NCHITTEST, 0, pos) == HTCLIENT;
|
||||
}
|
||||
|
||||
static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
|
||||
{
|
||||
switch (request) {
|
||||
@@ -1257,7 +1250,7 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
|
||||
case VOCTRL_SET_CURSOR_VISIBILITY:
|
||||
w32->cursor_visible = *(bool *)arg;
|
||||
|
||||
if (vo_w32_is_cursor_in_client(w32)) {
|
||||
if (w32->can_set_cursor && w32->tracking) {
|
||||
if (w32->cursor_visible)
|
||||
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
||||
else
|
||||
|
||||
@@ -931,6 +931,8 @@ int vo_x11_check_events(struct vo *vo)
|
||||
x11->win_drag_button1_down = false;
|
||||
break;
|
||||
case LeaveNotify:
|
||||
if (Event.xcrossing.mode != NotifyNormal)
|
||||
break;
|
||||
x11->win_drag_button1_down = false;
|
||||
mp_input_put_key(vo->input_ctx, MP_KEY_MOUSE_LEAVE);
|
||||
break;
|
||||
@@ -1672,6 +1674,7 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg)
|
||||
case VOCTRL_GET_WIN_STATE: {
|
||||
if (!x11->pseudo_mapped)
|
||||
return VO_FALSE;
|
||||
*(int *)arg = 0;
|
||||
int num_elems;
|
||||
long *elems = x11_get_property(x11, x11->window, XA(x11, _NET_WM_STATE),
|
||||
XA_ATOM, 32, &num_elems);
|
||||
@@ -1700,6 +1703,8 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg)
|
||||
return VO_TRUE;
|
||||
}
|
||||
case VOCTRL_GET_ICC_PROFILE: {
|
||||
if (!x11->pseudo_mapped)
|
||||
return VO_NOTAVAIL;
|
||||
int screen = 0; // xinerama screen number
|
||||
for (int n = 0; n < x11->num_displays; n++) {
|
||||
struct xrandr_display *disp = &x11->displays[n];
|
||||
@@ -1714,6 +1719,7 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg)
|
||||
mp_snprintf_cat(prop, sizeof(prop), "_%d", screen);
|
||||
x11->icc_profile_property = XAs(x11, prop);
|
||||
int len;
|
||||
MP_VERBOSE(x11, "Retrieving ICC profile for display: %d\n", screen);
|
||||
void *icc = x11_get_property(x11, x11->rootwin, x11->icc_profile_property,
|
||||
XA_CARDINAL, 8, &len);
|
||||
if (!icc)
|
||||
|
||||
+9
-3
@@ -238,9 +238,11 @@ int mp_sws_reinit(struct mp_sws_context *ctx)
|
||||
|
||||
// This can fail even with normal operation, e.g. if a conversion path
|
||||
// simply does not support these settings.
|
||||
sws_setColorspaceDetails(ctx->sws, sws_getCoefficients(s_csp), s_range,
|
||||
sws_getCoefficients(d_csp), d_range,
|
||||
ctx->brightness, ctx->contrast, ctx->saturation);
|
||||
int r =
|
||||
sws_setColorspaceDetails(ctx->sws, sws_getCoefficients(s_csp), s_range,
|
||||
sws_getCoefficients(d_csp), d_range,
|
||||
ctx->brightness, ctx->contrast, ctx->saturation);
|
||||
ctx->supports_csp = r >= 0;
|
||||
|
||||
if (sws_init_context(ctx->sws, ctx->src_filter, ctx->dst_filter) < 0)
|
||||
return -1;
|
||||
@@ -294,6 +296,8 @@ int mp_image_sw_blur_scale(struct mp_image *dst, struct mp_image *src,
|
||||
|
||||
int mp_sws_get_vf_equalizer(struct mp_sws_context *sws, struct vf_seteq *eq)
|
||||
{
|
||||
if (!sws->supports_csp)
|
||||
return 0;
|
||||
if (!strcmp(eq->item, "brightness"))
|
||||
eq->value = ((sws->brightness * 100) + (1 << 15)) >> 16;
|
||||
else if (!strcmp(eq->item, "contrast"))
|
||||
@@ -307,6 +311,8 @@ int mp_sws_get_vf_equalizer(struct mp_sws_context *sws, struct vf_seteq *eq)
|
||||
|
||||
int mp_sws_set_vf_equalizer(struct mp_sws_context *sws, struct vf_seteq *eq)
|
||||
{
|
||||
if (!sws->supports_csp)
|
||||
return 0;
|
||||
if (!strcmp(eq->item, "brightness"))
|
||||
sws->brightness = ((eq->value << 16) + 50) / 100;
|
||||
else if (!strcmp(eq->item, "contrast"))
|
||||
|
||||
@@ -44,6 +44,7 @@ struct mp_sws_context {
|
||||
|
||||
// Cached context (if any)
|
||||
struct SwsContext *sws;
|
||||
bool supports_csp;
|
||||
|
||||
// Contains parameters for which sws is valid
|
||||
struct mp_sws_context *cached;
|
||||
|
||||
@@ -106,7 +106,7 @@ def check_cocoa(ctx, dependency_identifier):
|
||||
fn = check_cc(
|
||||
fragment = load_fragment('cocoa.m'),
|
||||
compile_filename = 'test.m',
|
||||
framework_name = ['Cocoa', 'IOKit', 'OpenGL'],
|
||||
framework_name = ['Cocoa', 'IOKit', 'OpenGL', 'QuartzCore'],
|
||||
includes = ctx.srcnode.abspath(),
|
||||
linkflags = '-fobjc-arc')
|
||||
|
||||
|
||||
@@ -103,11 +103,13 @@ def check_pkg_config(*args, **kw_ext):
|
||||
if result:
|
||||
ctx.define(defkey, 1)
|
||||
else:
|
||||
ctx.add_optional_message(dependency_identifier,
|
||||
"'{0}' not found".format(" ".join(sargs)))
|
||||
ctx.undefine(defkey)
|
||||
return result
|
||||
return fn
|
||||
|
||||
def check_headers(*headers):
|
||||
def check_headers(*headers, **kw_ext):
|
||||
def undef_others(ctx, headers, found):
|
||||
not_found_hs = set(headers) - set([found])
|
||||
for not_found_h in not_found_hs:
|
||||
@@ -117,7 +119,7 @@ def check_headers(*headers):
|
||||
def fn(ctx, dependency_identifier):
|
||||
for header in headers:
|
||||
defaults = {'header_name': header, 'features': 'c cprogram'}
|
||||
options = __merge_options__(dependency_identifier, defaults)
|
||||
options = __merge_options__(dependency_identifier, defaults, kw_ext)
|
||||
if ctx.check(**options):
|
||||
undef_others(ctx, headers, header)
|
||||
defkey = DependencyInflector(dependency_identifier).define_key()
|
||||
|
||||
@@ -9,5 +9,7 @@ int main(int argc, char **argv) {
|
||||
NSArray *ary = @[@1, @2, @3];
|
||||
NSLog(@"test subscripting: %@", ary[0]);
|
||||
NSApplicationLoad();
|
||||
CVDisplayLinkRef link;
|
||||
CVDisplayLinkCreateWithCGDisplay(0, &link);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user