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 | ||
|
|
0c9f10e0d1 | ||
|
|
5b0d06ca74 | ||
|
|
a410451463 | ||
|
|
3152eed6d8 | ||
|
|
02260882ce | ||
|
|
cd5f0a8612 | ||
|
|
23959eb5b4 | ||
|
|
21a95f5ed9 | ||
|
|
6dc2a23cda | ||
|
|
f94318babc | ||
|
|
b476d21d0a | ||
|
|
16c7de4aab | ||
|
|
8fdd688d48 | ||
|
|
63304b95a1 | ||
|
|
f4263a811f | ||
|
|
a6bf8e7c36 | ||
|
|
6488e77d8a | ||
|
|
34a856f465 | ||
|
|
db218a8d25 | ||
|
|
e3064c3cb7 | ||
|
|
df4d15be7b | ||
|
|
cb5adf4d72 | ||
|
|
440ba33639 | ||
|
|
e4eb9c5469 | ||
|
|
4387919753 | ||
|
|
94123474a4 | ||
|
|
50a7aac4d7 | ||
|
|
2051243a0f | ||
|
|
a21d09d9f8 | ||
|
|
b95a0bfa77 | ||
|
|
43f831b6ba | ||
|
|
b3eba216b7 | ||
|
|
0668c82da5 | ||
|
|
c1e8f3b479 | ||
|
|
64a45f972c | ||
|
|
d4207c64e6 | ||
|
|
abf1fc9a77 | ||
|
|
da8e573b45 | ||
|
|
25c0097c67 | ||
|
|
20d5efb419 | ||
|
|
a18b4b2b19 | ||
|
|
a468e2869d | ||
|
|
cb9975b942 |
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 ``[ speed_mult 0.9438743126816935`` and ``] speed_mult
|
||||
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::
|
||||
|
||||
|
||||
+2
-3
@@ -618,9 +618,8 @@ Input Commands that are Possibly Subject to Change
|
||||
unseekable streams that are going out of sync.
|
||||
This command might be changed or removed in the future.
|
||||
|
||||
Undocumented commands: ``tv_last_channel`` (TV/DVB only), ``get_property`` (?),
|
||||
``ao_reload`` (experimental/internal).
|
||||
|
||||
Undocumented commands: ``tv_last_channel`` (TV/DVB only),
|
||||
``get_property`` (deprecated), ``ao_reload`` (experimental/internal).
|
||||
|
||||
Hooks
|
||||
~~~~~
|
||||
|
||||
+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
|
||||
|
||||
+11
-4
@@ -104,15 +104,21 @@ 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|no>``
|
||||
``--loop=<N|inf|force|no>``
|
||||
Loops playback ``N`` times. A value of ``1`` plays it one time (default),
|
||||
``2`` two times, etc. ``inf`` means forever. ``no`` is the same as ``1`` and
|
||||
disables looping. If several files are specified on command line, the
|
||||
entire playlist is looped.
|
||||
|
||||
The ``force`` mode is like ``inf``, but does not skip playlist entries
|
||||
which have been marked as failing. This means the player might waste CPU
|
||||
time trying to loop a file that doesn't exist. But it might be useful for
|
||||
playing webradios under very bad network conditions.
|
||||
|
||||
``--pause``
|
||||
Start the player in paused state.
|
||||
|
||||
@@ -546,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::
|
||||
|
||||
|
||||
+5
-6
@@ -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
|
||||
@@ -301,10 +301,6 @@ Available filters are:
|
||||
``uniform``
|
||||
uniform noise (Gaussian otherwise)
|
||||
|
||||
``hq``
|
||||
high quality (slightly better looking, slightly slower) - not available
|
||||
when using libavfilter
|
||||
|
||||
``hqdn3d[=luma_spatial:chroma_spatial:luma_tmp:chroma_tmp]``
|
||||
This filter aims to reduce image noise producing smooth images and making
|
||||
still images really still (This should enhance compressibility.).
|
||||
@@ -653,6 +649,9 @@ Available filters are:
|
||||
|
||||
Useful for some filters which insist on having a FPS.
|
||||
|
||||
``display_fps``
|
||||
Refresh rate of the current display. Note that this value can be 0.
|
||||
|
||||
``vapoursynth-lazy``
|
||||
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
|
||||
custom backend using Lua and the raw VapourSynth API is used. The syntax
|
||||
|
||||
+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
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
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
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
Changes that may break users' config files have been annotated with a `(!)`.
|
||||
|
||||
- `(!)` vo_opengl_old has been removed. OpenGL rendering now requires hardware
|
||||
that can do at least OpenGL 2.1, the oldest version supported by vo_opengl.
|
||||
- `(!)` vf_pp has been removed. A version of it can still be accessed through
|
||||
lavfilter (e.g. --vf=lavfi=[pp...]), but it receives no QP information.
|
||||
Also, vf_dlopen no longer passes QP information to its loaded plugin either
|
||||
(it's always 0).
|
||||
- `(!)` vf_softpulldown, vf_swapuv, vf_phase, vf_divtc and vf_ilpack have all
|
||||
been removed. Ports or alternative versions of these filters are available
|
||||
through libavfilter (vf_lavfi).
|
||||
- vf_pullup and vf_noise now are simply wrappers to their libavfilter
|
||||
equivalents. Both are only currently available on FFmpeg (not Libav).
|
||||
- `(!)` ao_portaudio has been removed. There is good support for the native
|
||||
audio drivers of all major desktop platforms, as well as ao_sdl and
|
||||
ao_openal as fallbacks.
|
||||
- `(!)` vo_opengl's `lscale` suboption has been renamed to `scale`; `lradius`
|
||||
is now `scale-radius`, `lparam1` and `lparam2` are now respectively
|
||||
`scale-param1` and `scale-param2`; `lscale-down` is now `scale-down`.
|
||||
The `cscale` suboption remains as is.
|
||||
- `(!)` Several of the `vo_opengl` radius-preset aliases supported by `scale`
|
||||
have been removed; use `scale-radius` to set if needed. For example, use
|
||||
`--vo=opengl:scale=lanczos:scale-radius=2` instead of `scale=lanczos2`.
|
||||
The default radius is recommended for most filters.
|
||||
- `(!)` vo_opengl no longer supports the `stereo` suboption. The anaglyph
|
||||
effect can be reproduced with the stereo3d filter. The quadbuffer support,
|
||||
which requires expensive specialized hardware to begin with, is removed.
|
||||
- `(!)` The `approx-gamma` suboption to vo_opengl has been removed. The
|
||||
BT.1886 specification says that it's actually how it's supposed to be done
|
||||
so it is now the default when `srgb` or `icc-profile` are enabled. This does
|
||||
not include BT.1886's gamma drop.
|
||||
- `(!)` The `no-scale-sep` and `indirect` suboptions to vo_opengl have been
|
||||
removed. These are now autodetected and enabled whenever they would have
|
||||
benefit.
|
||||
- `(!)` The `--lua` and `--lua-opts` parameters / options are now called
|
||||
`--script` and `--script-opts`, respectively. The `lua` subdir of the mpv
|
||||
config dir is also now expected to be called `scripts` instead.
|
||||
- `(!)` The `--fixed-vo` option has been removed. It has been the default for
|
||||
a long time anyway, and disabling fixed-vo is not useful.
|
||||
- vo_opengl_hq has been updated to take into account new features.
|
||||
`fancy-downscaling` and `sigmoid-upscaling` are enabled,
|
||||
"mitchell" is now the default for `scale-down`.
|
||||
- The `sub-visibility` OSD message is now clearer about whether subtitles
|
||||
are hidden or just not available / selected.
|
||||
- The device IDs given to `--audio-device` for use with ao_coreaudio now use
|
||||
UIDs, so they don't change when devices are added/removed or after reboots.
|
||||
- `--msg-level` now also accepts ',' as separator.
|
||||
- (Client API) The client API now refuses to initialize if the LC_NUMERIC
|
||||
locale is not "C".
|
||||
- (Client API) The native type of the `msg-level` parameter is now a key-value
|
||||
list. Setting or reading it as a string still works.
|
||||
- (Slave API) The `get_property` command is now deprecated.
|
||||
- Documentation updates.
|
||||
- PDF documentation is now disabled by default due to rst2pdf being fickle,
|
||||
causing weird build errors.
|
||||
- Print desync messages with negative A/V sync as well. While rare, it could
|
||||
happen with some uses of `--autosync`.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- (Windows) Avoid resizing the video window when player is minimized, might
|
||||
address bug #1547.
|
||||
- (NetBSD) Fix build with v4l2.
|
||||
- (Linux) Attempt to address conflicts with the pulseaudio mixer. (bug #1578)
|
||||
- Multiple cdda:// fixes. (bugs #1555, #1560)
|
||||
- VP9 timestamps no longer cause "missing PTS" warnings with new enough FFmpeg
|
||||
builds.
|
||||
- Fix a crash when using H.264 hardware decoding on new enough libavcodecs.
|
||||
(bug #1587)
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- vo_opengl now supports frame blending to eliminate stuttering when the video
|
||||
framerate does not nicely match the display framerate through the
|
||||
`smoothmotion` suboption. This is not meant to artificially increase the
|
||||
video's FPS, so there is no "soap opera effect" or difficulties with some
|
||||
video types (e.g. anime).
|
||||
- vo_opengl now supports sigmoidal upscaling (e.g. for fullscreen), which
|
||||
reduces ringing induced by upscaling, enabled through the
|
||||
`sigmoid-upscaling` suboption.
|
||||
- vo_opengl now supports ewa_lanczos (Jinc) scaling, which provides higher
|
||||
quality with less aliasing. It supports an experimental `scale-antiringing`
|
||||
parameter, which tries to further reduce video ringing.
|
||||
- vo_opengl now has a `linear-scaling` suboption, that makes the scalers work
|
||||
in linear light. Implied by the `srgb`, `icc-profile` or the new
|
||||
`sigmoid-upscaling` suboption.
|
||||
- vo_opengl has improved downscaling for higher downscale ratios when
|
||||
`fancy-downscaling` is enabled.
|
||||
- Add `--keep-open=always` to make `--keep-open` apply to all files instead of
|
||||
only the last.
|
||||
- mpv now filters the tags that are printed on the console to try to hide
|
||||
useless metadata. The filter works as a whitelist, and can be configured
|
||||
with the `--display-tags` option.
|
||||
- Add a `--sub-scale-by-window` option that, when set to 'no', prevents
|
||||
subtitles from changing their pixel size when the window changes sizes.
|
||||
- vo_opengl now technically has GLES 2 and 3 support (but not GLES 1). GLES 3
|
||||
support is only tested on Mesa's software emulation. GLES 2 has been tested
|
||||
on nvidia drivers. Both GLES modes are feature deficient and inefficient due
|
||||
to GLES limitations, so they should not be preferred over the standard
|
||||
OpenGL mode.
|
||||
- (X11) vo_opengl now supports the `icc-profile-auto` option on X11 hosts.
|
||||
- The 'A' key now by default cycles through a list of preset aspect ratios.
|
||||
Meant to be used to work around broken files.
|
||||
- There is now a "force" mode for `--loop`. Works like "inf", but tries to
|
||||
open files/streams even if there was an error on the previous attempt.
|
||||
- There is now a `--log-file` option to write mpv's log messages to a specifed
|
||||
file.
|
||||
- There is now a `--audio-file-auto` option to automatically load an external
|
||||
audio file with certain constraints. Will only load external audio if the
|
||||
playing file has a video stream. Set to "exact" by default. (bug #967)
|
||||
- There is now a `--network-timeout` option to specify a timeout in seconds
|
||||
for network access. If 0 (default), uses the libavformat default. If a
|
||||
protocol that doesn't support timeouts is used, this option is ignored.
|
||||
- (X11) The XF86 special keys are now all mappable in `input.conf`. All keys
|
||||
documented in `XF86keysym.h` are available, but only as their numeric codes.
|
||||
- (Linux) The DVB implementation now supports DVB-S2 through S2API.
|
||||
- (Linux) The VDR format for `channels.conf` is now supported and preferred.
|
||||
See http://linuxtv.org/vdrwiki/index.php/Syntax_of_channels.conf for the
|
||||
syntax. Tuning to DVB-S2 channels requires the VDR-format `channels.conf`.
|
||||
- (Linux) There is now a `--dvbin-full-transponder` option for handling
|
||||
special broadcast cases where PIDs switch, or just to work around incomplete
|
||||
PID data.
|
||||
- (Client API) There is now an opengl_cb VO, which receives the OpenGL context
|
||||
from the libmpv client. This allows a client to render libmpv video directly
|
||||
to a provided OpenGL context without having to use --wid embedding.
|
||||
- (Client API) The aforementioned --wid parameter can now be set at any time,
|
||||
but has no effect if a file is already loaded.
|
||||
- (Client API) The list of DVD titles can now be queried.
|
||||
- (Client API) There is now a `filtered-metadata` property, containing only
|
||||
the tags allowed by `--display-tags`.
|
||||
- (Client API) There is now support for XEmbed `--wid` targets, such as a
|
||||
`GtkSocket`'s `gtk_socket_get_id()`.
|
||||
- (Client API) There is now a `file-format` property with a symbolic name
|
||||
for the file format. In some cases this might be a comma separated list
|
||||
of various different extensions due to libavformat idiosyncrasies.
|
||||
- (Client API) There is now a `mouse` command for generating mouse events
|
||||
over the video window.
|
||||
- (Client API) There is now a `partially-seekable` property that indicates
|
||||
whether a file is only considered `seekable` because of the stream cache,
|
||||
but would not be seekable otherwise (e.g. realtime stream, or HTTP with
|
||||
no resume support). Small relative seeks may be fine, but larger seeks
|
||||
will likely fail.
|
||||
- (Client API) There is now an MPV_EVENT_QUEUE_OVERFLOW event that is posted
|
||||
whenever events have to be dropped because the queue is full. No further
|
||||
events are posted until the MPV_EVENT_QUEUE_OVERFLOW is consumed to avoid
|
||||
duplicated posting.
|
||||
- (Client API) There is now a helper `mpv_wait_async_requests()` function
|
||||
that blocks until all known async requests have completed. (bug #1542)
|
||||
- (Client API) There is now a `detected-hwdec` property that returns the
|
||||
detected hardware decoder when one is successfully opened. This probably
|
||||
only returns a valid value after playback has started.
|
||||
- (Client API) There are now `audio_add`, `audio_remove` and `audio_reload`
|
||||
counterparts to the equivalent `sub_` commands for handling external audio
|
||||
files.
|
||||
|
||||
This listing is not complete. A complete changelog can be seen by running
|
||||
`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";
|
||||
|
||||
File renamed without changes.
+2
-1
@@ -370,7 +370,8 @@ struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
|
||||
if (alloc >= INT_MAX)
|
||||
return NULL;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
pool->avpool = av_buffer_pool_init(alloc, NULL);
|
||||
pool->element_size = alloc;
|
||||
pool->avpool = av_buffer_pool_init(pool->element_size, NULL);
|
||||
if (!pool->avpool)
|
||||
return NULL;
|
||||
talloc_set_destructor(pool, mp_audio_pool_destructor);
|
||||
|
||||
+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;
|
||||
|
||||
@@ -743,6 +743,8 @@ double af_calc_delay(struct af_stream *s)
|
||||
double delay = 0.0;
|
||||
while (af) {
|
||||
delay += af->delay;
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
delay += af->out_queued[n]->samples / (double)af->data->rate;
|
||||
af = af->next;
|
||||
}
|
||||
return delay;
|
||||
|
||||
+3
-4
@@ -143,6 +143,7 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
.input_ctx = input_ctx,
|
||||
.log = mp_log_new(ao, log, name),
|
||||
.def_buffer = opts->audio_buffer,
|
||||
.client_name = talloc_strdup(ao, opts->audio_client_name),
|
||||
};
|
||||
struct m_config *config = m_config_from_obj_desc(ao, ao->log, &desc);
|
||||
if (m_config_apply_defaults(config, name, opts->ao_defs) < 0)
|
||||
@@ -162,7 +163,6 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
int samplerate, int format, struct mp_chmap channels,
|
||||
char *dev, char *name, char **args)
|
||||
{
|
||||
struct MPOpts *opts = global->opts;
|
||||
struct ao *ao = ao_alloc(probing, global, input_ctx, name, args);
|
||||
if (!ao)
|
||||
return NULL;
|
||||
@@ -178,7 +178,6 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
af_fmt_to_str(ao->format));
|
||||
|
||||
ao->device = talloc_strdup(ao, dev);
|
||||
ao->client_name = talloc_strdup(ao, opts->audio_client_name);
|
||||
|
||||
ao->api = ao->driver->play ? &ao_api_push : &ao_api_pull;
|
||||
ao->api_priv = talloc_zero_size(ao, ao->api->priv_size);
|
||||
@@ -275,8 +274,8 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
ao_list[n].name, ao_list[n].attribs);
|
||||
if (ao)
|
||||
goto done;
|
||||
mp_warn(log, "Failed to initialize audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
mp_err(log, "Failed to initialize audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
if (forced_dev) {
|
||||
mp_err(log, "This audio driver/device was forced with the "
|
||||
"--audio-device option.\n"
|
||||
|
||||
+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.
|
||||
|
||||
+2
-2
@@ -224,8 +224,8 @@ void print_libav_versions(struct mp_log *log, int v)
|
||||
if (broken) {
|
||||
mp_fatal(log, "mpv was compiled and linked against a mixture of Libav "
|
||||
"and FFmpeg versions. This won't work and will most likely "
|
||||
"crash at some point. Exiting.\n");
|
||||
exit(42);
|
||||
"crash at some point. Aborting.\n");
|
||||
abort();
|
||||
}
|
||||
// We don't "really" support mismatched libraries, but if you like to
|
||||
// suffer, you're free to enjoy the terrible aspects of dynamic linking.
|
||||
|
||||
+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"
|
||||
+6
-1
@@ -5,8 +5,12 @@ Name[ca]=Reproductor multimèdia mpv
|
||||
Name[cs]=mpv přehrávač
|
||||
Name[pl]=mpv odtwarzacz multimedialny
|
||||
Name[ru]=Проигрыватель mpv
|
||||
Name[zh-CN]=mpv 媒体播放器
|
||||
Name[zh-TW]=mpv 媒體播放器
|
||||
GenericName=Multimedia player
|
||||
GenericName[cs]=Multimediální přehrávač
|
||||
GenericName[zh-CN]=多媒体播放器
|
||||
GenericName[zh-TW]=多媒體播放器
|
||||
Comment=Play movies and songs
|
||||
Comment[ca]=Reproduïu vídeos i cançons
|
||||
Comment[cs]=Přehrává filmy a hudbu
|
||||
@@ -16,7 +20,8 @@ Comment[fr]=Lecteur multimédia
|
||||
Comment[it]=Lettore multimediale
|
||||
Comment[pl]=Odtwarzaj filmy i muzykę
|
||||
Comment[ru]=Воспроизвести фильмы и музыку
|
||||
Comment[zh]=多媒体播放器
|
||||
Comment[zh-CN]=播放电影和歌曲
|
||||
Comment[zh-TW]=播放電影和歌曲
|
||||
Icon=mpv
|
||||
TryExec=mpv
|
||||
Exec=mpv --no-terminal --force-window -- %U
|
||||
|
||||
+7
-1
@@ -531,9 +531,15 @@ static void *client_thread(void *p)
|
||||
};
|
||||
|
||||
fcntl(arg->client_fd, F_SETFL, fcntl(arg->client_fd, F_GETFL, 0) | O_NONBLOCK);
|
||||
mpv_suspend(arg->client);
|
||||
|
||||
while (1) {
|
||||
rc = poll(fds, 2, -1);
|
||||
rc = poll(fds, 2, 0);
|
||||
if (rc == 0) {
|
||||
mpv_resume(arg->client);
|
||||
rc = poll(fds, 2, -1);
|
||||
mpv_suspend(arg->client);
|
||||
}
|
||||
if (rc < 0) {
|
||||
MP_ERR(arg, "Poll error\n");
|
||||
continue;
|
||||
|
||||
@@ -194,6 +194,7 @@ static const struct key_name key_names[] = {
|
||||
{ MP_KEY_FAVORITES, "FAVORITES" },
|
||||
{ MP_KEY_SEARCH, "SEARCH" },
|
||||
{ MP_KEY_SLEEP, "SLEEP" },
|
||||
{ MP_KEY_CANCEL, "CANCEL" },
|
||||
|
||||
// These are kept for backward compatibility
|
||||
{ MP_KEY_PAUSE, "XF86_PAUSE" },
|
||||
|
||||
+7
-1
@@ -75,6 +75,7 @@
|
||||
#define MP_KEY_FAVORITES (MP_KEY_MM_BASE+16)
|
||||
#define MP_KEY_SEARCH (MP_KEY_MM_BASE+17)
|
||||
#define MP_KEY_SLEEP (MP_KEY_MM_BASE+18)
|
||||
#define MP_KEY_CANCEL (MP_KEY_MM_BASE+19)
|
||||
|
||||
/* Function keys */
|
||||
#define MP_KEY_F (MP_KEY_BASE+0x40)
|
||||
@@ -210,8 +211,13 @@
|
||||
#define MP_AXIS_LEFT (MP_AXIS_BASE+2)
|
||||
#define MP_AXIS_RIGHT (MP_AXIS_BASE+3)
|
||||
|
||||
// Reserved area. Can be used for keys that have no explicit names assigned,
|
||||
// but should be mappable by the user anyway.
|
||||
#define MP_KEY_UNKNOWN_RESERVED_START (MP_KEY_BASE+0x10000)
|
||||
#define MP_KEY_UNKNOWN_RESERVED_LAST (MP_KEY_BASE+0x20000-1)
|
||||
|
||||
/* Special keys */
|
||||
#define MP_KEY_INTERN (MP_KEY_BASE+0x1000)
|
||||
#define MP_KEY_INTERN (MP_KEY_BASE+0x20000)
|
||||
#define MP_KEY_CLOSE_WIN (MP_KEY_INTERN+0)
|
||||
// Generated by input.c (VOs use mp_input_set_mouse_pos())
|
||||
#define MP_KEY_MOUSE_MOVE ((MP_KEY_INTERN+1)|MP_NO_REPEAT_KEY)
|
||||
|
||||
@@ -121,6 +121,27 @@ extern "C" {
|
||||
* process (such as ffmpeg OpenSSL support, or the mpv IPC code).
|
||||
* - On memory exhaustion, mpv will kill the process.
|
||||
*
|
||||
* Encoding of filenames
|
||||
* ---------------------
|
||||
*
|
||||
* mpv uses UTF-8 everywhere.
|
||||
*
|
||||
* On some platforms (like Linux), filenames actually do not have to be UTF-8;
|
||||
* for this reason libmpv supports non-UTF-8 strings. libmpv uses what the
|
||||
* kernel uses and does not recode filenames. At least on Linux, passing a
|
||||
* string to libmpv is like passing a string to the fopen() function.
|
||||
*
|
||||
* On Windows, filenames are always UTF-8, libmpv converts between UTF-8 and
|
||||
* UTF-16 when using win32 API functions. libmpv never uses or accepts
|
||||
* filenames in the local 8 bit encoding. It does not use fopen() either;
|
||||
* it uses _wfopen().
|
||||
*
|
||||
* On OS X, filenames and other strings taken/returned by libmpv can have
|
||||
* inconsistent unicode normalization. This can sometimes lead to problems.
|
||||
* You have to hope for the best.
|
||||
*
|
||||
* Also see the remarks for MPV_FORMAT_STRING.
|
||||
*
|
||||
* Embedding the video window
|
||||
* --------------------------
|
||||
*
|
||||
@@ -133,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
|
||||
@@ -502,6 +530,9 @@ typedef enum mpv_format {
|
||||
* and even filenames don't necessarily have to be in UTF-8 (at
|
||||
* least on Linux). If you pass the strings to code that requires
|
||||
* valid UTF-8, you have to sanitize it in some way.
|
||||
* On Windows, filenames are always UTF-8, and libmpv converts
|
||||
* between UTF-8 and UTF-16 when using win32 API functions. See
|
||||
* the "Encoding of filenames" section for details.
|
||||
*
|
||||
* Example for reading:
|
||||
*
|
||||
|
||||
+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"
|
||||
|
||||
+10
-11
@@ -99,6 +99,14 @@ SOURCES-$(WAYLAND) += video/out/vo_wayland.c \
|
||||
video/out/wayland/memfile.c
|
||||
|
||||
SOURCES-$(LIBAVFILTER) += video/filter/vf_lavfi.c \
|
||||
video/filter/vf_delogo.c \
|
||||
video/filter/vf_gradfun.c \
|
||||
video/filter/vf_hqdn3d.c \
|
||||
video/filter/vf_noise.c \
|
||||
video/filter/vf_pullup.c \
|
||||
video/filter/vf_rotate.c \
|
||||
video/filter/vf_unsharp.c \
|
||||
video/filter/vf_yadif.c \
|
||||
audio/filter/af_lavfi.c
|
||||
|
||||
SOURCES-$(LUA) += player/lua.c
|
||||
@@ -163,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 \
|
||||
@@ -208,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 \
|
||||
@@ -250,25 +257,17 @@ SOURCES = audio/audio.c \
|
||||
video/filter/vf.c \
|
||||
video/filter/vf_buffer.c \
|
||||
video/filter/vf_crop.c \
|
||||
video/filter/vf_delogo.c \
|
||||
video/filter/vf_dsize.c \
|
||||
video/filter/vf_eq.c \
|
||||
video/filter/vf_expand.c \
|
||||
video/filter/vf_flip.c \
|
||||
video/filter/vf_format.c \
|
||||
video/filter/vf_gradfun.c \
|
||||
video/filter/vf_hqdn3d.c \
|
||||
video/filter/vf_mirror.c \
|
||||
video/filter/vf_noformat.c \
|
||||
video/filter/vf_noise.c \
|
||||
video/filter/vf_pullup.c \
|
||||
video/filter/vf_rotate.c \
|
||||
video/filter/vf_scale.c \
|
||||
video/filter/vf_screenshot.c \
|
||||
video/filter/vf_stereo3d.c \
|
||||
video/filter/vf_sub.c \
|
||||
video/filter/vf_unsharp.c \
|
||||
video/filter/vf_yadif.c \
|
||||
video/out/bitmap_packer.c \
|
||||
video/out/aspect.c \
|
||||
video/out/filter_kernels.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;
|
||||
}
|
||||
|
||||
+5
-4
@@ -500,9 +500,10 @@ const m_option_t mp_opts[] = {
|
||||
|
||||
OPT_FLAG("stop-playback-on-init-failure", stop_playback_on_init_failure, 0),
|
||||
|
||||
OPT_CHOICE_OR_INT("loop", loop_times, M_OPT_GLOBAL, 2, 10000,
|
||||
({"no", -1}, {"1", -1},
|
||||
{"inf", 0})),
|
||||
OPT_CHOICE_OR_INT("loop", loop_times, M_OPT_GLOBAL, 1, 10000,
|
||||
({"no", 1},
|
||||
{"inf", -1},
|
||||
{"force", -2})),
|
||||
OPT_CHOICE_OR_INT("loop-file", loop_file, M_OPT_OPTIONAL_PARAM, 0, 10000,
|
||||
({"yes", -1}, {"", -1}, {"no", 0}, // compat
|
||||
{"inf", -1})),
|
||||
@@ -712,7 +713,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.lua_ytdl_format = NULL,
|
||||
#endif
|
||||
.auto_load_scripts = 1,
|
||||
.loop_times = -1,
|
||||
.loop_times = 1,
|
||||
.ordered_chapters = 1,
|
||||
.chapter_merge_threshold = 100,
|
||||
.chapter_seek_threshold = 5.0,
|
||||
|
||||
+10
-7
@@ -147,12 +147,15 @@ char **mp_find_all_config_files(void *talloc_ctx, struct mpv_global *global,
|
||||
|
||||
char **dirs = mp_config_dirs(NULL, global);
|
||||
for (int i = 0; dirs && dirs[i]; i++) {
|
||||
char *file = talloc_asprintf(ret, "%s/%s", dirs[i], filename);
|
||||
bstr s = bstr0(filename);
|
||||
while (s.len) {
|
||||
bstr fn;
|
||||
bstr_split_tok(s, "|", &fn, &s);
|
||||
|
||||
if (!mp_path_exists(file) || num_ret >= MAX_CONFIG_PATHS)
|
||||
continue;
|
||||
|
||||
ret[num_ret++] = file;
|
||||
char *file = talloc_asprintf(ret, "%s/%.*s", dirs[i], BSTR_P(fn));
|
||||
if (mp_path_exists(file) && num_ret < MAX_CONFIG_PATHS)
|
||||
ret[num_ret++] = file;
|
||||
}
|
||||
}
|
||||
talloc_free(dirs);
|
||||
|
||||
@@ -237,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] == '/')
|
||||
@@ -248,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
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@ char *mp_find_config_file(void *talloc_ctx, struct mpv_global *global,
|
||||
const char *filename);
|
||||
|
||||
// Find all instances of the given config file. Paths are returned in order
|
||||
// from lowest to highest priority.
|
||||
// from lowest to highest priority. filename can contain multiple names
|
||||
// separated with '|', with the first having highest priority.
|
||||
char **mp_find_all_config_files(void *talloc_ctx, struct mpv_global *global,
|
||||
const char *filename);
|
||||
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -261,21 +261,15 @@ static void cocoa_run_runloop(void)
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
static void cocoa_stop_runloop(void)
|
||||
{
|
||||
[NSApp performSelectorOnMainThread:@selector(stop:)
|
||||
withObject:nil
|
||||
waitUntilDone:true];
|
||||
}
|
||||
|
||||
static void *playback_thread(void *ctx_obj)
|
||||
{
|
||||
mpthread_set_name("playback core (OSX)");
|
||||
@autoreleasepool {
|
||||
struct playback_thread_ctx *ctx = (struct playback_thread_ctx*) ctx_obj;
|
||||
ctx->mpv_main(*ctx->argc, *ctx->argv);
|
||||
cocoa_stop_runloop();
|
||||
pthread_exit(NULL);
|
||||
int r = ctx->mpv_main(*ctx->argc, *ctx->argv);
|
||||
terminate_cocoa_application();
|
||||
// normally never reached - unless the cocoa mainloop hasn't started yet
|
||||
exit(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+8
-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;
|
||||
@@ -4329,6 +4328,10 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
|
||||
}
|
||||
MP_INFO(mpctx, "ANS_%s=%s\n", cmd->args[0].v.s, tmp);
|
||||
talloc_free(tmp);
|
||||
MP_WARN(mpctx, "The get_property command is deprecated and "
|
||||
"will be removed in the next release.\n"
|
||||
"Use libmpv or the JSON IPC. "
|
||||
"(Or print_text, if you must.)");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +84,7 @@ void mp_parse_cfgfiles(struct MPContext *mpctx)
|
||||
talloc_free(cf);
|
||||
#endif
|
||||
|
||||
load_all_cfgfiles(mpctx, section, "config");
|
||||
load_all_cfgfiles(mpctx, section, "mpv.conf");
|
||||
load_all_cfgfiles(mpctx, section, "mpv.conf|config");
|
||||
|
||||
if (encoding)
|
||||
m_config_set_profile(conf, m_config_add_profile(conf, SECT_ENCODE), 0);
|
||||
|
||||
+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);
|
||||
|
||||
+104
-64
@@ -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);
|
||||
|
||||
@@ -1317,26 +1359,24 @@ struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
|
||||
while (next && next->playback_short)
|
||||
next = next->prev;
|
||||
// Always allow jumping to first file
|
||||
if (!next && mpctx->opts->loop_times < 0)
|
||||
if (!next && mpctx->opts->loop_times == 1)
|
||||
next = mpctx->playlist->first;
|
||||
}
|
||||
if (!next && mpctx->opts->loop_times >= 0) {
|
||||
if (!next && mpctx->opts->loop_times != 1) {
|
||||
if (direction > 0) {
|
||||
if (mpctx->opts->shuffle)
|
||||
playlist_shuffle(mpctx->playlist);
|
||||
next = mpctx->playlist->first;
|
||||
if (next && mpctx->opts->loop_times > 1) {
|
||||
if (next && mpctx->opts->loop_times > 1)
|
||||
mpctx->opts->loop_times--;
|
||||
if (mpctx->opts->loop_times == 1)
|
||||
mpctx->opts->loop_times = -1;
|
||||
}
|
||||
} else {
|
||||
next = mpctx->playlist->last;
|
||||
// Don't jump to files that would immediately go to next file anyway
|
||||
while (next && next->playback_short)
|
||||
next = next->prev;
|
||||
}
|
||||
if (!force && next && next->init_failed) {
|
||||
bool ignore_failures = mpctx->opts->loop_times == -2;
|
||||
if (!force && next && next->init_failed && !ignore_failures) {
|
||||
// Don't endless loop if no file in playlist is playable
|
||||
bool all_failed = true;
|
||||
struct playlist_entry *cur;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+15
-28
@@ -71,9 +71,6 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if HAVE_COCOA_APPLICATION
|
||||
#include "osdep/macosx_application.h"
|
||||
#endif
|
||||
#if HAVE_COCOA
|
||||
#include "osdep/macosx_events.h"
|
||||
#endif
|
||||
@@ -167,6 +164,10 @@ void mp_destroy(struct MPContext *mpctx)
|
||||
|
||||
osd_free(mpctx->osd);
|
||||
|
||||
#if HAVE_COCOA
|
||||
cocoa_set_input_context(NULL);
|
||||
#endif
|
||||
|
||||
if (cas_terminal_owner(mpctx, mpctx)) {
|
||||
terminal_uninit();
|
||||
cas_terminal_owner(mpctx, NULL);
|
||||
@@ -184,13 +185,8 @@ void mp_destroy(struct MPContext *mpctx)
|
||||
talloc_free(mpctx);
|
||||
}
|
||||
|
||||
static MP_NORETURN void exit_player(struct MPContext *mpctx,
|
||||
enum exit_reason how)
|
||||
static int prepare_exit_cplayer(struct MPContext *mpctx, enum exit_reason how)
|
||||
{
|
||||
#if HAVE_COCOA_APPLICATION
|
||||
cocoa_set_input_context(NULL);
|
||||
#endif
|
||||
|
||||
int rc = 0;
|
||||
const char *reason = NULL;
|
||||
|
||||
@@ -228,15 +224,7 @@ static MP_NORETURN void exit_player(struct MPContext *mpctx,
|
||||
rc = mpctx->quit_custom_rc;
|
||||
|
||||
mp_destroy(mpctx);
|
||||
|
||||
#if HAVE_COCOA_APPLICATION
|
||||
terminate_cocoa_application();
|
||||
// never reach here:
|
||||
// terminate calls exit itself, just silence compiler warning
|
||||
exit(0);
|
||||
#else
|
||||
exit(rc);
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
|
||||
static bool handle_help_options(struct MPContext *mpctx)
|
||||
@@ -413,7 +401,6 @@ int mp_initialize(struct MPContext *mpctx)
|
||||
}
|
||||
m_config_set_option0(mpctx->mconfig, "vo", "lavc");
|
||||
m_config_set_option0(mpctx->mconfig, "ao", "lavc");
|
||||
m_config_set_option0(mpctx->mconfig, "fixed-vo", "yes");
|
||||
m_config_set_option0(mpctx->mconfig, "keep-open", "no");
|
||||
m_config_set_option0(mpctx->mconfig, "force-window", "no");
|
||||
m_config_set_option0(mpctx->mconfig, "gapless-audio", "yes");
|
||||
@@ -421,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
|
||||
@@ -457,7 +446,6 @@ int mp_initialize(struct MPContext *mpctx)
|
||||
"the selected video_out (-vo) device.\n");
|
||||
return -1;
|
||||
}
|
||||
handle_force_window(mpctx, false);
|
||||
mpctx->mouse_cursor_visible = true;
|
||||
}
|
||||
|
||||
@@ -508,21 +496,21 @@ int mpv_main(int argc, char *argv[])
|
||||
mpctx->global, argc, argv);
|
||||
if (r < 0) {
|
||||
if (r <= M_OPT_EXIT) {
|
||||
exit_player(mpctx, EXIT_NONE);
|
||||
return prepare_exit_cplayer(mpctx, EXIT_NONE);
|
||||
} else {
|
||||
exit_player(mpctx, EXIT_ERROR);
|
||||
return prepare_exit_cplayer(mpctx, EXIT_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
mp_msg_update_msglevels(mpctx->global);
|
||||
|
||||
if (handle_help_options(mpctx))
|
||||
exit_player(mpctx, EXIT_NONE);
|
||||
return prepare_exit_cplayer(mpctx, EXIT_NONE);
|
||||
|
||||
if (!mpctx->playlist->first && !opts->player_idle_mode) {
|
||||
mp_print_version(mpctx->log, true);
|
||||
MP_INFO(mpctx, "%s", mp_help_text);
|
||||
exit_player(mpctx, EXIT_NONE);
|
||||
return prepare_exit_cplayer(mpctx, EXIT_NONE);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -531,9 +519,8 @@ int mpv_main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
if (mp_initialize(mpctx) < 0)
|
||||
exit_player(mpctx, EXIT_ERROR);
|
||||
return prepare_exit_cplayer(mpctx, EXIT_ERROR);
|
||||
|
||||
mp_play_files(mpctx);
|
||||
exit_player(mpctx, EXIT_NORMAL);
|
||||
return 1;
|
||||
return prepare_exit_cplayer(mpctx, EXIT_NORMAL);
|
||||
}
|
||||
+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);
|
||||
|
||||
+1
-1
@@ -813,7 +813,7 @@ static void handle_keep_open(struct MPContext *mpctx)
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
if (opts->keep_open && mpctx->stop_play == AT_END_OF_FILE &&
|
||||
(opts->keep_open == 2 || !playlist_get_next(mpctx->playlist, 1)) &&
|
||||
opts->loop_times < 0)
|
||||
opts->loop_times == 1)
|
||||
{
|
||||
mpctx->stop_play = KEEP_PLAYING;
|
||||
if (mpctx->d_video) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+5
-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)
|
||||
@@ -180,6 +180,8 @@ static void recreate_video_filters(struct MPContext *mpctx)
|
||||
d_video->vfilter->wakeup_callback = wakeup_playloop;
|
||||
d_video->vfilter->wakeup_callback_ctx = mpctx;
|
||||
d_video->vfilter->container_fps = d_video->fps;
|
||||
vo_control(mpctx->video_out, VOCTRL_GET_DISPLAY_FPS,
|
||||
&d_video->vfilter->display_fps);
|
||||
|
||||
vf_append_filter_list(d_video->vfilter, opts->vf_settings);
|
||||
|
||||
|
||||
+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
@@ -498,7 +498,7 @@ static int tune_it(dvb_priv_t *priv, int fd_frontend, int fd_sec,
|
||||
MP_VERBOSE(priv, "Tuning via DVB-API version 3.\n");
|
||||
if (is_dvb_s2) {
|
||||
MP_ERR(priv, "ERROR: Can not tune to S2 channel, S2-API not "
|
||||
"available, will tune to DVB-S!\n")
|
||||
"available, will tune to DVB-S!\n");
|
||||
}
|
||||
if (ioctl(fd_frontend, FE_SET_FRONTEND, &feparams) < 0) {
|
||||
MP_ERR(priv, "ERROR tuning channel\n");
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
+4
-12
@@ -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) {
|
||||
@@ -436,12 +431,6 @@ static void uninit_avctx(struct dec_video *vd)
|
||||
vd_ffmpeg_ctx *ctx = vd->priv;
|
||||
AVCodecContext *avctx = ctx->avctx;
|
||||
|
||||
if (avctx && avcodec_is_open(avctx))
|
||||
avcodec_flush_buffers(avctx);
|
||||
|
||||
if (ctx->hwdec && ctx->hwdec->uninit)
|
||||
ctx->hwdec->uninit(ctx);
|
||||
|
||||
if (avctx) {
|
||||
if (avctx->codec && avcodec_close(avctx) < 0)
|
||||
MP_ERR(vd, "Could not close codec.\n");
|
||||
@@ -450,6 +439,9 @@ static void uninit_avctx(struct dec_video *vd)
|
||||
av_freep(&avctx->slice_offset);
|
||||
}
|
||||
|
||||
if (ctx->hwdec && ctx->hwdec->uninit)
|
||||
ctx->hwdec->uninit(ctx);
|
||||
|
||||
av_freep(&ctx->avctx);
|
||||
|
||||
av_frame_free(&ctx->pic);
|
||||
|
||||
+10
-10
@@ -77,25 +77,25 @@ static const vf_info_t *const filter_list[] = {
|
||||
&vf_info_format,
|
||||
&vf_info_noformat,
|
||||
&vf_info_flip,
|
||||
&vf_info_rotate,
|
||||
&vf_info_mirror,
|
||||
|
||||
#if HAVE_LIBAVFILTER
|
||||
&vf_info_lavfi,
|
||||
&vf_info_rotate,
|
||||
&vf_info_noise,
|
||||
&vf_info_gradfun,
|
||||
&vf_info_unsharp,
|
||||
&vf_info_hqdn3d,
|
||||
&vf_info_pullup,
|
||||
&vf_info_delogo,
|
||||
&vf_info_yadif,
|
||||
#endif
|
||||
|
||||
&vf_info_screenshot,
|
||||
|
||||
&vf_info_noise,
|
||||
&vf_info_eq,
|
||||
&vf_info_gradfun,
|
||||
&vf_info_unsharp,
|
||||
&vf_info_hqdn3d,
|
||||
&vf_info_dsize,
|
||||
&vf_info_pullup,
|
||||
&vf_info_delogo,
|
||||
&vf_info_sub,
|
||||
&vf_info_yadif,
|
||||
&vf_info_stereo3d,
|
||||
&vf_info_buffer,
|
||||
#if HAVE_DLOPEN
|
||||
@@ -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]");
|
||||
|
||||
@@ -122,6 +122,7 @@ struct vf_chain {
|
||||
uint8_t allowed_output_formats[IMGFMT_END - IMGFMT_START];
|
||||
|
||||
double container_fps;
|
||||
double display_fps;
|
||||
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
|
||||
@@ -48,7 +48,7 @@ static int vf_open(vf_instance_t *vf){
|
||||
return 1;
|
||||
}
|
||||
|
||||
MP_FATAL(vf, "Requires libavfilter.\n");
|
||||
MP_FATAL(vf, "This version of libavfilter has no 'delogo' filter.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ static int vf_open(vf_instance_t *vf)
|
||||
return 1;
|
||||
}
|
||||
|
||||
MP_FATAL(vf, "Requires libavfilter.\n");
|
||||
MP_FATAL(vf, "This version of libavfilter has no 'gradfun' filter.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ static int vf_open(vf_instance_t *vf)
|
||||
return 1;
|
||||
}
|
||||
|
||||
MP_FATAL(vf, "Requires libavfilter.\n");
|
||||
MP_FATAL(vf, "This version of libavfilter has no 'hqdn3' filter.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
#ifndef MP_VF_LAVFI_H_
|
||||
#define MP_VF_LAVFI_H_
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "vf.h"
|
||||
|
||||
struct vf_lw_opts;
|
||||
|
||||
#if HAVE_LIBAVFILTER
|
||||
|
||||
extern const struct m_sub_options vf_lw_conf;
|
||||
|
||||
int vf_lw_set_graph(struct vf_instance *vf, struct vf_lw_opts *lavfi_opts,
|
||||
@@ -21,28 +17,5 @@ void vf_lw_set_reconfig_cb(struct vf_instance *vf,
|
||||
int (*reconfig)(struct vf_instance *vf,
|
||||
struct mp_image_params *in,
|
||||
struct mp_image_params *out));
|
||||
#else
|
||||
static inline
|
||||
int vf_lw_set_graph(struct vf_instance *vf, struct vf_lw_opts *lavfi_opts,
|
||||
char *filter, char *opts, ...)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
static void *vf_lw_old_priv(struct vf_instance *vf)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static void vf_lw_update_graph(struct vf_instance *vf, char *filter, char *opts, ...)
|
||||
{
|
||||
}
|
||||
static void vf_lw_set_reconfig_cb(struct vf_instance *vf,
|
||||
int (*reconfig)(struct vf_instance *vf,
|
||||
struct mp_image_params *in,
|
||||
struct mp_image_params *out))
|
||||
{
|
||||
}
|
||||
#include "options/m_option.h"
|
||||
static const struct m_sub_options vf_lw_conf = {0};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+8
-239
@@ -20,253 +20,25 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
#include "video/img_format.h"
|
||||
#include "video/mp_image.h"
|
||||
#include "vf.h"
|
||||
#include "video/memcpy_pic.h"
|
||||
|
||||
#include "vf_lavfi.h"
|
||||
|
||||
#define MAX_NOISE 4096
|
||||
#define MAX_SHIFT 1024
|
||||
#define MAX_RES (MAX_NOISE-MAX_SHIFT)
|
||||
|
||||
//===========================================================================//
|
||||
|
||||
static inline void lineNoise(uint8_t *dst, uint8_t *src, int8_t *noise, int len, int shift);
|
||||
static inline void lineNoiseAvg(uint8_t *dst, uint8_t *src, int len, int8_t **shift);
|
||||
|
||||
typedef struct FilterParam{
|
||||
int strength;
|
||||
int uniform;
|
||||
int temporal;
|
||||
int quality;
|
||||
int averaged;
|
||||
int pattern;
|
||||
int shiftptr;
|
||||
int8_t *noise;
|
||||
int8_t *prev_shift[MAX_RES][3];
|
||||
int nonTempRandShift[MAX_RES];
|
||||
}FilterParam;
|
||||
|
||||
struct vf_priv_s {
|
||||
FilterParam lumaParam;
|
||||
FilterParam chromaParam;
|
||||
int strength;
|
||||
int averaged;
|
||||
int pattern;
|
||||
int temporal;
|
||||
int uniform;
|
||||
int hq;
|
||||
struct vf_lw_opts *lw_opts;
|
||||
int strength;
|
||||
int averaged;
|
||||
int pattern;
|
||||
int temporal;
|
||||
int uniform;
|
||||
struct vf_lw_opts *lw_opts;
|
||||
};
|
||||
|
||||
static const int patt[4] = {
|
||||
-1,0,1,0
|
||||
};
|
||||
|
||||
#define RAND_N(range) ((int) ((double)range*rand()/(RAND_MAX+1.0)))
|
||||
static int8_t *initNoise(FilterParam *fp){
|
||||
int strength= fp->strength;
|
||||
int uniform= fp->uniform;
|
||||
int averaged= fp->averaged;
|
||||
int pattern= fp->pattern;
|
||||
int8_t *noise= av_malloc(MAX_NOISE*sizeof(int8_t));
|
||||
int i, j;
|
||||
|
||||
srand(123457);
|
||||
|
||||
for(i=0,j=0; i<MAX_NOISE; i++,j++)
|
||||
{
|
||||
if(uniform) {
|
||||
if (averaged) {
|
||||
if (pattern) {
|
||||
noise[i]= (RAND_N(strength) - strength/2)/6
|
||||
+patt[j%4]*strength*0.25/3;
|
||||
} else {
|
||||
noise[i]= (RAND_N(strength) - strength/2)/3;
|
||||
}
|
||||
} else {
|
||||
if (pattern) {
|
||||
noise[i]= (RAND_N(strength) - strength/2)/2
|
||||
+ patt[j%4]*strength*0.25;
|
||||
} else {
|
||||
noise[i]= RAND_N(strength) - strength/2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
double x1, x2, w, y1;
|
||||
do {
|
||||
x1 = 2.0 * rand()/(float)RAND_MAX - 1.0;
|
||||
x2 = 2.0 * rand()/(float)RAND_MAX - 1.0;
|
||||
w = x1 * x1 + x2 * x2;
|
||||
} while ( w >= 1.0 );
|
||||
|
||||
w = sqrt( (-2.0 * log( w ) ) / w );
|
||||
y1= x1 * w;
|
||||
y1*= strength / sqrt(3.0);
|
||||
if (pattern) {
|
||||
y1 /= 2;
|
||||
y1 += patt[j%4]*strength*0.35;
|
||||
}
|
||||
if (y1<-128) y1=-128;
|
||||
else if(y1> 127) y1= 127;
|
||||
if (averaged) y1 /= 3.0;
|
||||
noise[i]= (int)y1;
|
||||
}
|
||||
if (RAND_N(6) == 0) j--;
|
||||
}
|
||||
|
||||
|
||||
for (i = 0; i < MAX_RES; i++)
|
||||
for (j = 0; j < 3; j++)
|
||||
fp->prev_shift[i][j] = noise + (rand()&(MAX_SHIFT-1));
|
||||
|
||||
for(i=0; i<MAX_RES; i++){
|
||||
fp->nonTempRandShift[i]= rand()&(MAX_SHIFT-1);
|
||||
}
|
||||
|
||||
fp->noise= noise;
|
||||
fp->shiftptr= 0;
|
||||
return noise;
|
||||
}
|
||||
|
||||
/***************************************************************************/
|
||||
|
||||
static inline void lineNoise(uint8_t *dst, uint8_t *src, int8_t *noise, int len, int shift){
|
||||
int i;
|
||||
noise+= shift;
|
||||
for(i=0; i<len; i++)
|
||||
{
|
||||
int v= src[i]+ noise[i];
|
||||
if(v>255) dst[i]=255; //FIXME optimize
|
||||
else if(v<0) dst[i]=0;
|
||||
else dst[i]=v;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************/
|
||||
|
||||
static inline void lineNoiseAvg(uint8_t *dst, uint8_t *src, int len, int8_t **shift){
|
||||
int i;
|
||||
int8_t *src2= (int8_t*)src;
|
||||
|
||||
for(i=0; i<len; i++)
|
||||
{
|
||||
const int n= shift[0][i] + shift[1][i] + shift[2][i];
|
||||
dst[i]= src2[i]+((n*src2[i])>>7);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************/
|
||||
|
||||
static void donoise(uint8_t *dst, uint8_t *src, int dstStride, int srcStride, int width, int height, FilterParam *fp){
|
||||
int8_t *noise= fp->noise;
|
||||
int y;
|
||||
int shift=0;
|
||||
|
||||
if(!noise)
|
||||
{
|
||||
if(src==dst) return;
|
||||
|
||||
if(dstStride==srcStride) memcpy(dst, src, srcStride*height);
|
||||
else
|
||||
{
|
||||
for(y=0; y<height; y++)
|
||||
{
|
||||
memcpy(dst, src, width);
|
||||
dst+= dstStride;
|
||||
src+= srcStride;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for(y=0; y<height; y++)
|
||||
{
|
||||
if(fp->temporal) shift= rand()&(MAX_SHIFT -1);
|
||||
else shift= fp->nonTempRandShift[y];
|
||||
|
||||
if(fp->quality==0) shift&= ~7;
|
||||
if (fp->averaged) {
|
||||
lineNoiseAvg(dst, src, width, fp->prev_shift[y]);
|
||||
fp->prev_shift[y][fp->shiftptr] = noise + shift;
|
||||
} else {
|
||||
lineNoise(dst, src, noise, width, shift);
|
||||
}
|
||||
dst+= dstStride;
|
||||
src+= srcStride;
|
||||
}
|
||||
fp->shiftptr++;
|
||||
if (fp->shiftptr == 3) fp->shiftptr = 0;
|
||||
}
|
||||
|
||||
static struct mp_image *filter(struct vf_instance *vf, struct mp_image *mpi)
|
||||
{
|
||||
struct mp_image *dmpi = mpi;
|
||||
if (!mp_image_is_writeable(mpi)) {
|
||||
dmpi = vf_alloc_out_image(vf);
|
||||
if (!dmpi)
|
||||
return NULL;
|
||||
mp_image_copy_attributes(dmpi, mpi);
|
||||
}
|
||||
|
||||
donoise(dmpi->planes[0], mpi->planes[0], dmpi->stride[0], mpi->stride[0], mpi->w, mpi->h, &vf->priv->lumaParam);
|
||||
donoise(dmpi->planes[1], mpi->planes[1], dmpi->stride[1], mpi->stride[1], mpi->w/2, mpi->h/2, &vf->priv->chromaParam);
|
||||
donoise(dmpi->planes[2], mpi->planes[2], dmpi->stride[2], mpi->stride[2], mpi->w/2, mpi->h/2, &vf->priv->chromaParam);
|
||||
|
||||
if (dmpi != mpi)
|
||||
talloc_free(mpi);
|
||||
return dmpi;
|
||||
}
|
||||
|
||||
static void uninit(struct vf_instance *vf){
|
||||
if(!vf->priv) return;
|
||||
|
||||
av_free(vf->priv->chromaParam.noise);
|
||||
vf->priv->chromaParam.noise= NULL;
|
||||
|
||||
av_free(vf->priv->lumaParam.noise);
|
||||
vf->priv->lumaParam.noise= NULL;
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
|
||||
static int query_format(struct vf_instance *vf, unsigned int fmt){
|
||||
switch(fmt)
|
||||
{
|
||||
case IMGFMT_420P:
|
||||
return vf_next_query_format(vf,IMGFMT_420P);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void parse(FilterParam *fp, struct vf_priv_s *p){
|
||||
fp->strength= p->strength;
|
||||
fp->uniform=p->uniform;
|
||||
fp->temporal=p->temporal;
|
||||
fp->quality=p->hq;
|
||||
fp->pattern=p->pattern;
|
||||
fp->averaged=p->averaged;
|
||||
|
||||
if(fp->strength) initNoise(fp);
|
||||
}
|
||||
|
||||
static int vf_open(vf_instance_t *vf){
|
||||
vf->filter=filter;
|
||||
vf->query_format=query_format;
|
||||
vf->uninit=uninit;
|
||||
|
||||
#define CH(f) ((f) ? '+' : '-')
|
||||
struct vf_priv_s *p = vf->priv;
|
||||
if (vf_lw_set_graph(vf, p->lw_opts, "noise", "-1:%d:%ca%cp%ct%cu",
|
||||
@@ -276,10 +48,8 @@ static int vf_open(vf_instance_t *vf){
|
||||
return 1;
|
||||
}
|
||||
|
||||
parse(&vf->priv->lumaParam, vf->priv);
|
||||
parse(&vf->priv->chromaParam, vf->priv);
|
||||
|
||||
return 1;
|
||||
MP_FATAL(vf, "This version of libavfilter has no 'noise' filter.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct vf_priv_s
|
||||
@@ -294,7 +64,6 @@ const vf_info_t vf_info_noise = {
|
||||
OPT_FLAG("pattern", pattern, 0),
|
||||
OPT_FLAG("temporal", temporal, 0),
|
||||
OPT_FLAG("uniform", uniform, 0),
|
||||
OPT_FLAG("hq", hq, 0),
|
||||
OPT_SUBSTRUCT("", lw_opts, vf_lw_conf, 0),
|
||||
{0}
|
||||
},
|
||||
|
||||
@@ -48,7 +48,7 @@ static int vf_open(vf_instance_t *vf)
|
||||
return 1;
|
||||
}
|
||||
|
||||
MP_FATAL(vf, "Requires libavfilter.\n");
|
||||
MP_FATAL(vf, "This version of libavfilter has no 'pullup' filter.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 132 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user