Compare commits

...
184 Commits
Author SHA1 Message Date
Diogo Franco (Kovensky) 4db470e08a Release 0.8.3 2015-03-17 10:50:21 +09:00
Diogo Franco (Kovensky) cf3819ece9 Update RELEASE_NOTES 2015-03-17 10:49:57 +09:00
wm4 575b631d67 video: don't drop anamorphic scaling if it's too minor
This played e.g. a 1264x722 file as 1264x720. There was some code which
dropped the aspect ratio if the video (in original resolution) wasn't
scaled by more than 4 pixels. Commit 5f3c3f8c introduced this (although
I'm not really sure what the code replaced by it did).

Just remove this "feature".

(cherry picked from commit d96fad04be)
2015-03-17 10:49:56 +09:00
wm4 4c1b45b5ab command: don't drop audio buffers when switching filters
This partially reverts commit ae7228c6. I guess the old behavior was
slightly more popular.

Fixes #1693 (probably).

(cherry picked from commit 699fc0e51e)
2015-03-17 10:49:56 +09:00
wm4 8932d90ec6 demux_lavf: fix rejection of image2 demuxer
This was probably broken for some time in git master, and the later
commit which happened to fix it was not picked.

Fixes #1689.
2015-03-16 11:58:24 +01:00
Diogo Franco (Kovensky) fc4da23c64 Update RELEASE_NOTES 2015-03-16 17:26:05 +09:00
Martin Herkt 017c2808e4 bootstrap: add waf mirror
(cherry picked from commit 958dbca3ef)
2015-03-16 17:08:16 +09:00
wm4 c5a8fc793c audio: fix off by one error in channel map selection code
The consequence was that some AOs (like ao_jack) could not output 8
channels.

Fixes #1688.

(cherry picked from commit c4f4b09014)
2015-03-16 17:08:01 +09:00
Martin Herkt 3dcd3476c6 man: fix PDF build
(cherry picked from commit add4c9ee03)
2015-03-16 17:07:47 +09:00
Stefano Pigozzi ad119460d7 cocoa: signal VO_EVENT_WIN_STATE on fps change
Fixes #1686

(cherry picked from commit efe0fb75bc)
2015-03-15 21:33:38 +09:00
James Ross-Gowan e865543be2 w32_common: don't hide cursor when the menu is open
Previously, mpv would hide the cursor when the autohide timer expired,
even if the window menu was open. This made it difficult to use the menu
with the mouse.

When handling VOCTRL_SET_CURSOR_VISIBILITY, instead of determining
whether to call SetCursor by checking if the cursor is in the client
area, call it based on the parameters to the last WM_SETCURSOR message.
When the window enters "menu mode," it gets a WM_SETCURSOR message with
HIWORD(lParam) set to 0 to indicate that the cursor shouldn't be set.

(cherry picked from commit acbac01a73)
2015-03-15 20:09:00 +09:00
wm4 e84a00651a vo: update FPS only on state changes
I'm not comfortable with VOCTRL_GET_DISPLAY_FPS being called every
frame.

This requires the VO to set VO_EVENT_WIN_STATE if the FPS could have
changed. At least the X11 backend does this.

(cherry picked from commit 209f8225ed)
2015-03-15 20:08:00 +09:00
wm4 b17ccd1c43 demux_mkv: make matroska.pl script output deterministic
Work around the randomized Perl hashtables by sorting the elements.

Fixes #1673.

(cherry picked from commit 7e9c441d41)
2015-03-15 20:07:14 +09:00
wm4 62d922e4f0 command: allow changing some VO options even if VO wasn't created yet
Instead of refusing to set properties like "fullscreen" if no VO was
created, always allow it. So if no VO is created, setting the property
merely changes the options (and will be applied once the VO is created).
This is consistent with similar behavior changes to some other
properties.

Improves the behavior reported in #1676.

Also, we shouldn't check the config_ok variable - the VO should do this.

(cherry picked from commit e9841630ad)
2015-03-15 20:07:06 +09:00
wm4 eaa5393c4a player: fix crash when taking screenshots in idle mode
mpctx->filename is obviously not set if no file is playing.

When this code was written, it probably couldn't happen, because the
normal screenshot path fails much earlier in idle mode. But you can
still take screenshots in "full window" mode, and recently the
screenshot code was changed to use the "full window" mode if the normal
path does not work.

(cherry picked from commit ca474d22c9)
2015-03-15 20:06:50 +09:00
wm4 50b74bf407 x11: ignore mouse enter/leave events due to pointer grab
If you click on a window that doesn't have a focus, a LeaveNotify
followed by a EnterNotify event can be generated. The former will have
mode set to NotifyGrab, the latter to NotifyUngrab. This will make the
player think the mouse left the window, even though this is not the
case. Ignore these and only react to those with mode set to
NotifyNormal.

Probably fixes #1672, and some other strange issues on some WMs.

(cherry picked from commit 30860f7b10)
2015-03-15 20:06:28 +09:00
Stefano Pigozzi 414cdc9da6 build: disable tests by default
Having them autodetect is a bad idea since it would link cmocka in the main
mpv binary (which users don't want).

(cherry picked from commit ca0b33e9b1)
2015-03-11 12:49:40 +09:00
wm4 1c4bdfaf9c vo: hide opengl-cb in --vo=help
Avoids stupid questions.

(cherry picked from commit c7790b4526)
2015-03-11 12:49:40 +09:00
wm4 76809e5ef7 vo_opengl_cb: clear last video frame on uninit()
mpv_opengl_cb_render() is supposed to clear the screen with the
background color if there's no video... I think.

It didn't do this, because although uninit() requested gl_video_config()
to be called, this didn't happen, because this function checks whether
the VO is set - and it's unsert after uninit() releases the lock.

Also call the user wakeup callback in this situation, so the user
actually redraws immediately.

(cherry picked from commit eff265c140)
2015-03-11 12:49:40 +09:00
wm4 efc72daea9 client API: clarify mpv_opengl_cb_render() viewport parameter
Basically, the idea behind the vp parameter is broken - I guess the
intention was to enable rendering to a specific subrectangle of the
target framebuffer, but there's nothing to specify the actual target
rectangle (the VO will still clear e.g. the borders between video and
framebuffer borders).

We're not going to keep the current semantics either with the upcoming
rework of vo_opengl, so declare this for broken. Maybe we can introduce
a function later which does this properly.

(cherry picked from commit 90adf2904e)
2015-03-11 12:49:40 +09:00
wm4 b414d9a9a7 stream: use relaxed atomic loads for checking playback aborts
Seems appropriate, and will probably avoid performance surprises with
scary architectures which don't have trivial implementations for atomic
loads. (Consider that demux_mkv calls this very often now, and
libavformat demuxers and streams did this for a while now.)

(cherry picked from commit b9307b7175)
2015-03-11 12:49:40 +09:00
wm4 2b737f8bfc demux_mkv: never warn when trying to resync on EOF
If this happens, the file is actually broken, but the assumption is
simply that the file was truncated, and printing a warning would be
strange.

(cherry picked from commit 112d9aeda1)
2015-03-11 12:49:40 +09:00
wm4 9e99a0c8b9 demux_mkv: check for playback aborts
Check async abort notification. libavformat already do something
equivalent.

Before this commit, the demuxer could enter resync mode (and print silly
warning messages) when the stream stopped returning data because of an
abort.

(cherry picked from commit 39fa05d374)
2015-03-11 12:49:40 +09:00
James Ross-Gowan e22ecb1585 w32_common: remove redundant is_maximized function
This already exists as IsMaximized in the Windows API.

(cherry picked from commit 5f0eda7b94)
2015-03-11 12:49:40 +09:00
wm4 6358e9be5c x11: fix VOCTRL_GET_WIN_STATE
Do not rely on the pointed-to argument to be initialized; VOCTRLs are
supposed to completely overwrite them on success (or not to touch them
on failure).

The currently only caller of VOCTRL_GET_WIN_STATE initializes the value
before calling this, so this is merely about correctness and didn't lead
to any actual bugs.

(cherry picked from commit b7f242dfcf)
2015-03-11 12:49:40 +09:00
Kevin Mitchell d57a48c64e stream/smb: mark as network stream for --cache=auto
This causes the cache to be enabled with --cache=auto. It was not done
previously because the small cache size 320k actually led to worse
performance. However, with the current default cache size of 25000kb,
caching notably improves performance.

(cherry picked from commit 3efbc1a9a3)
2015-03-11 12:49:40 +09:00
Niklas Haas 22691f2e6a manpage: document swapinterval default
(cherry picked from commit 6983430c35)
2015-03-11 12:49:39 +09:00
Filip Milivojevic 623a259eee vf_vavpp: fix deinterlacing
Add filter parameters to VAAPI deinterlacing filter to actually process
bottom fields instead of deinterlacing top field twice.

Signed-off-by: wm4 <wm4@nowhere>
(cherry picked from commit 5c3a6ca96e)
2015-03-11 12:49:39 +09:00
wm4 fa81e835bc DOCS: minor update to coding style
(cherry picked from commit 6f507d14c1)
2015-03-11 12:49:39 +09:00
Stefano Pigozzi 46cc45223c cocoa: fix autohide in fullscreen
(cherry picked from commit 8ec9bce2d3)
2015-03-11 12:49:39 +09:00
Stefano Pigozzi 0e160123fd cocoa: fix mouse hiding with launchpad and dock folders
Some UI elements in OS X – like Launchpad and Dock folders – are implemented
using borderless windows in background demonized applications.
When we use these elements, mpv doesn't stop to be the active application, but
the mpv window leaves keyWindow state while we use the OS controls.

This commit just keeps track of window state to update the cursor visibility
accordingly.

Fixes #513

(cherry picked from commit ce239f1577)
2015-03-11 12:49:39 +09:00
torque 710979e0e8 cocoa: clamp mouse position to window.
Prevents out-of-window coordinates being reported for mouse coordinates.
Previously they could be out-of-window coordinates on init or on resize.

(cherry picked from commit 39537f6474)
2015-03-11 12:49:39 +09:00
torque 18d3c2ab85 cocoa: update mouse coordinates when window is initialized.
Make MpvEventsView -signalMousePosition a public method so it can be
called without a compiler warning. Previously, the mouse position would
be reported as (0,0) until the cursor was moved.

(cherry picked from commit bce753060e)
2015-03-11 12:49:39 +09:00
Martin Herkt f84ca3a516 vo_opengl/x11: fix automatic ICC profile loading
mpv would attempt to load ICC profiles several times during VO init
even if no window is displayed. This potentially causes it to load
a profile for a different screen than it is going to be displayed
on, thereby invalidating the profile cache and rebuilding the LUT
every single time.
It would not unload a previously loaded profile when the video
window is moved to a display without an installed profile.

Fix these issues and tweak the log messages a little.

(cherry picked from commit 1bab7f69ae)
2015-03-11 12:49:39 +09:00
Stefano Pigozzi 324a40d2e1 cocoa: remove possible use after free
(cherry picked from commit 5cddd4ccca)
2015-03-11 12:49:33 +09:00
Stefano Pigozzi 83e98d3ada cocoa: fix some crashes caused by async uninit
Always keep around our private state and destroy it when we are really done in
the async uninit callback.

Fixes #1657

(cherry picked from commit c66b51dab0)
2015-03-11 12:39:56 +09:00
wm4 a424fe1296 demux_mkv: actually skip elements out of reach
This is missing from the previous commit. Not that harmful, but also
slightly un-nice since even a failed seek will reset the cache.

(cherry picked from commit f0ab1f2048)
2015-03-11 12:34:26 +09:00
wm4 e09803ae6b demux_mkv: fix issues with unseekable streams
A user reported a webm stream that couldn't be played. The issue was
that this stream 1. was on an unseekable HTTP connection, and 2. had a
SeekHead element (wtf?). The code reading the SeekHead marked the
element as unreadable too early: although you can't seek in the stream,
reading the header elements after the SeekHead read them anyway. Marking
them as unreadable only after the normal header reading fixes this.

(The way the failing stream was setup was pretty retarded: inserting
these SeekHead elements makes absolutely no sense for a stream that
cannot be seeked.)

Fixes #1656.

(cherry picked from commit 12dcc5eaac)
2015-03-11 12:34:02 +09:00
torque 80669188d6 Lua: add unpack shim for Lua 5.2/5.3 compatibility.
The global unpack function got moved to table.unpack in Lua 5.2, and
it's only available as the global if 5.2 is built with compatibility
enabled (the default). Lua 5.3 does not build with 5.1 compatibility by
default.

Fixes #1648.

(cherry picked from commit 4ff29f33b0)
2015-03-11 12:33:53 +09:00
wm4 1a3a3aba63 player: allow changing playback speed in early audio init stages
If the audio decoder was created, but no audio filter chain created yet
(still trying to decode a first audio frame), setting the "speed"
property could explode. It tried to recreate the filter chain, even
though no format was set yet.

This is inconvenient and should not happen.

(cherry picked from commit 720d4a5a1a)
2015-03-11 12:33:22 +09:00
wm4 0b141eaa82 DOCS/compile-window.md: add Lua package to MXE instructions
The MSYS2 ones already mention Lua.

(cherry picked from commit 3fe61ada84)
2015-03-11 12:33:00 +09:00
wm4 cac071dc23 build: make vdpau and dxva2 checks nicer
Using check_statement() with an empty statement just to check for the
header is quite a hack. Fix check_headers() (so it takes a "use"
parameter), and use it for the checks instead.

(cherry picked from commit dfa562b5db)
2015-03-11 12:32:51 +09:00
ChrisK2 c77f076ae4 DOCS, options: fix example
(cherry picked from commit e9ff136dd3)
2015-03-11 12:32:03 +09:00
wm4 f4393eaa6c build: check whether hwaccels are enabled in FFmpeg
FFmpeg can be compiled with them disabled, and then it won't provide the
public headers specific to these APIs, causing mpv compilation failure.

(cherry picked from commit baa354c64c)
2015-03-11 12:31:53 +09:00
wm4 bbd6501bd3 vd_lavc: let --hwdec=auto select "vaapi-copy"
Instead of "vaapi", simply by changing the probe order.

"vaapi" uses the GLX GL interop, which has causing us more problems than
it solved.

Unfortunately this leads also to copying if "--hwdec=auto --vo=vaapi" is
used, even though GLX is not involved in this case - but I don't care
enough to make the probe logic cleverer just for this. You can still get
the zero-copy path with --hwdec=vaapi.

(cherry picked from commit 27b5492a82)
2015-03-11 12:31:40 +09:00
wm4 dc29a21b5c vd_lavc: request 1 more threads than CPU cores
This duplicates the logic which FFmpeg's libavcodec uses. The effects
are unknown, though it's somewhat clear that a single thread doesn't
necessarily saturate a single CPU.

(Eventually we should just let FFmpeg auto-init the thread count, but
for now I prefer it this way, so e.g. verbose mode will print the
thread count.)

(cherry picked from commit 9657e5a2ca)
2015-03-11 12:31:16 +09:00
wm4 4d87844508 vo_vaapi: fix video equalizer (second try)
The vaapi equalizer have a custom range, and can have a smaller range
than mpv's normalized video equalizer values. The result is that a vaapi
equalizer value can map to multiple mpv values, so changing a mpv value
by 1 can get "stuck". Fix by remember the mpv value, and returning it if
it still corresponds to the vaapi value.

Really fixes #1647.

(Why am I even bothering with this irredeemable crap?)

(cherry picked from commit fc571e0adb)
2015-03-11 12:30:50 +09:00
wm4 cced4bf4d6 vo_vaapi: round equalizer values
Probably fixes #1647 (if it's correct at all). I couldn't reproduce with
the vdpau libva driver, but a driver can use different ranges.

(cherry picked from commit 4e3f8ccb9d)
2015-03-11 12:30:50 +09:00
wm4 5734cd7063 README: fix typos
(cherry picked from commit 6d4a758295)
2015-03-05 10:48:31 +09:00
wm4 52324a494c ad_spdif: remove per-packet message
It was annoying and didn't ever help with anything.

(cherry picked from commit 55f69605fb)
2015-03-05 10:48:25 +09:00
wm4 7ee8237e66 cache: assume file size from EOF position
If we're caching a stream with unknown size, and we reach EOF, then
consider the EOF position the file size. Typically makes sense when
reading from a pipe or a http connection that did not send a size.

(cherry picked from commit 3cd394995f)
2015-03-05 10:47:36 +09:00
wm4 6fc0b4e24b README: add some better links
Also write something about MPlayer, since this is a frequently asked
question.

(cherry picked from commit 771cb34932)
2015-03-05 10:47:24 +09:00
wm4 3d3b1b8bba win32: fix some more -Wparentheses warnings
Stupid compiler.

For decode_surrogate_pair(), I changed the order of evaluation; it
shouldn't matter, but this order is more readable in my opinion.

(cherry picked from commit 85bf102f54)
2015-03-05 10:47:14 +09:00
Stefano Pigozzi 21a90cc49c cocoa: silence -Wlogical-op-parentheses
(cherry picked from commit 3c3f9a8042)
2015-03-05 10:47:03 +09:00
wm4 83119f05c4 build: fix warnings with --disable-libass
This crap seems to break every other fullmoon.

(cherry picked from commit 0bbf36b796)
2015-03-05 10:46:49 +09:00
wm4 081ddd26b4 stream_cdda: fix parameter passing
All cdda options (except --cdrom-drive) were ignored, because the priv
struct (with the options set) was just replaced. Oops.

(cherry picked from commit 1ba24d2585)
2015-03-04 13:08:25 +09:00
wm4 78349cc188 player: refine rar:// playlist-safety handling
It was possible to make the player play local files by putting rar://
links into remote playlists, and some other potentially unsafe things.

Redo the handling of it. Now the rar-redirector (the thing in
demux_playlist.c) sets disable_safety, which makes the player open any
playlist entries returned. This is fine, because it redirects to the
same file anyway (just with different selection/interpretation of the
contents). On the other hand, rar:// itself is now considered fully
unsafe, which means that it is ignored if found in normal playlists.

(cherry picked from commit a4b6bf8c41)
2015-03-03 16:08:43 +09:00
wm4 931f614257 msg: use relaxed atomics for log level test
This should be sufficient.

If stdatomic.h is not available, we make no difference.

(cherry picked from commit 01b87e509c)
2015-03-03 16:08:24 +09:00
wm4 e682d82527 vf_scale: libswscale is being stupid
This time (there are a lot of times), libswscale randomly ignores
brightness/saturation/contrast settings.

Looking at MPlayer code, it appears the return value of
sws_setColorspaceDetails() signals if changing these settings is
supported at all.

(Nevermind that supporting this feature has almost 0 value, and
obviously eats maintenance time.)

(cherry picked from commit 08199a64d2)
2015-03-03 16:07:04 +09:00
torque 76fd4b4e33 Lua: add mp.get_screen_margins().
When used with mp.get_screen_size(), mp.get_screen_margins() allows a
Lua script to determine what portion of the mpv window actually has the
video in it.

(cherry picked from commit 1bbf1eb3ce)
2015-03-03 16:06:46 +09:00
Oliver Freyermuth 623b0a77e1 stream_dvb: Always define NO_STREAM_ID_FILTER if missing.
It is also used for initialization in channel-list setup.
Should fix compilation on FreeBSD, and is more correct
since it is used unconditionally.
Reverts 6445648 .

(cherry picked from commit 40c9e79068)
2015-03-03 16:05:24 +09:00
Stefano Pigozzi c6c4c3ea9c cocoa: remove the setNextKeyView hack
Just use makeFirstResponder on the mpv events view from client code
if you need the built in keyboard events (this is easier for dealing with view
nesting).

(cherry picked from commit 03a69bac95)
2015-03-03 16:05:24 +09:00
Stefano Pigozzi 8e5a854374 examples/cocoabasic: allow to send stop command
(cherry picked from commit 721e430cd2)
2015-03-03 16:05:24 +09:00
zymos 90a0add131 manpage: fix typo
Signed-off-by: wm4 <wm4@nowhere>
(cherry picked from commit b742384162)
2015-03-03 16:05:24 +09:00
wm4 443fd456a1 vo_opengl: delete 16 bit test texture
Essentially a leak, but not that bad since it's small and allocated only
once.

(cherry picked from commit 73c5c3df53)
2015-03-03 16:02:22 +09:00
wm4 910da3af6a vo_opengl: fix some comments
(cherry picked from commit e7b7a5a476)
2015-03-03 16:02:16 +09:00
wm4 517f15dc49 stream: remove stream filter concept
Unused since the previous commit. (Apparently it was a stupid idea.)

(cherry picked from commit fbf76da913)
2015-03-03 16:02:07 +09:00
wm4 62cf557b5f stream_rar: treat rar files as playlists
Refactors an older hack, which for some reason used a more complicated
way. This generates the playlist representing the contents of the rar
file in demux_playlist.c. The pseudo-demuxer could easily be separate
from the the playlist parsers (and in fact there's almost no shared
code), but I don't think this obscure feature deserves a separate file.

Sample files created with:

    rar a -v20000k -m0 files.rar file1.mkv file1.mkv

(cherry picked from commit 5824eb7107)
2015-03-03 16:02:02 +09:00
wm4 95e1952377 build: improve DVB check
A use of NO_STREAM_ID_FILTER was added to the DVB code recently. While I
have no idea what it's needed for, it makes mpv fail to compile on
FreeBSD 10.1. Add it to the dvb configure check.

(cherry picked from commit 64456488b3)
2015-03-03 16:01:37 +09:00
Niklas Haas 90126b9f88 manpage: update wording on speed
The way I interpreted it, it seemed like this was not default behavior
and could be enabled with --audio-pitch-correction - it should be made
clearer that this is actually *the default behavior*.

(cherry picked from commit 0f99b302f5)
2015-03-03 16:00:38 +09:00
wm4 01223a3e0f player: adjust A/V desync message
Broken drivers are an issue rather often. Maybe this gives the user an
idea that this could be the reason. (We can't dump much more info on a
80x24 terminal.)

(cherry picked from commit a5f3083ae7)
2015-03-03 15:58:57 +09:00
wm4 76b0291bf2 vo_opengl: test FBOs only if they're going to be used
Change test_fbo() so that it checks the FBO lazily, and restructure
check_gl_features() to invoke it only if we know that a FBO will be
needed for a certain enabled feature.

This can avoid strange error messages when using --vo=opengl and the
FBO format does not work. It's also less confusing when reading the
verbose log (users might think that the FBO is actually used, etc.).

(cherry picked from commit 280c826379)
2015-03-03 15:58:43 +09:00
wm4 3fc0c18997 vo_xv: don't attempt to clear window before mapped
This can happen with the "no-colorkey" suboption. Then the code in
xv_draw_colorkey() can be run before vo_x11_config_vo_window(), when
vo_gc is not allocated yet.

Fixes #1629.

(cherry picked from commit a2e0cd7f25)
2015-03-03 15:58:28 +09:00
wm4 7fe9126bba Release 0.8.2 2015-02-26 19:13:34 +01:00
wm4 22863d6a79 Revert "osd: force alignment for OSD bar"
This reverts commit ff10bd65d6.

Fixes #1631.
2015-02-26 19:11:42 +01:00
Diogo Franco (Kovensky) 5ccd91d639 Fix typo... 2015-02-26 17:20:01 +09:00
Diogo Franco (Kovensky) 37bf8de258 Release 0.8.1 2015-02-26 11:08:23 +09:00
wm4 2d3e4fb1b9 cache: use MPCLAMP() macro
(cherry picked from commit e85bcc1223)
2015-02-26 11:03:15 +09:00
wm4 78cd25388c cache: limit to file size
Saves some memory. Should be especially helpful if many small files are
loaded, like when mass-loading subtitle files and the cache is enabled.

(cherry picked from commit 899f0cd51e)
2015-02-26 11:03:08 +09:00
Stefano Pigozzi 32f0adaeae remove dead comment
(cherry picked from commit f3eb6ae783)
2015-02-26 11:02:44 +09:00
Stefano Pigozzi 70a7d9d2c5 build: move QuartzCore linking to the cocoa check
It's needed for the DisplayLink functions so it must be enabled for the basic
cocoa code.

(cherry picked from commit 8e80b3fd2f)
2015-02-26 11:02:38 +09:00
wm4 cf14511b1d demux_mkv_timeline: fix potential issue when enabling cache
If the cache is enabled, the demuxer is closed and opened again (because
currently, the cache can not be enabled atfer data was already read).
The call for opening a new demuxer uses the same params struct, which
references the ctx->uids array. But there is a MP_TARRAY_GROW()
invocation somewhere on the way, which can reallocate the ctx->uids
array, making params.uids a dangling pointer.

This issue probably existed for a longer time, probably since 5cd33853
(slightly more obvious since f50b105d).

(cherry picked from commit 9349cfca91)
2015-02-26 11:01:25 +09:00
wm4 a6d57d654a demux_mkv_timeline: move uids array to context too
Again removes some indirections and extra arguments.

Also replace some memcpy/memmoves with assignments. (Assignments became
possible only later, when reference UIDs were turned into a struct.)

(cherry picked from commit 02bd54c0ac)
2015-02-26 10:45:42 +09:00
wm4 63671a4fbb demux_mkv_timeline: pass sources array as part of context
Removes tripple pointer indirections and such.

(cherry picked from commit 2bf0a5f2bc)
2015-02-26 10:40:35 +09:00
wm4 8ace70cfa6 demux_mkv: minor refactor for deferred cue reading
Should behave about the same, but reduces code some duplication with
seeking and reading a header element pointed to by a SeekHead. It also
makes behavior with incomplete files slightly better.

(cherry picked from commit 2e23be5a7b)
2015-02-26 10:39:02 +09:00
wm4 1e0edb3e6f demux_mkv: don't spam too many warnings with partial files
Limit it to a single message. It often printed more than that, and in
some cases (old files with "cluster" index), spammed a lot.

(cherry picked from commit a761a1531a)
2015-02-26 10:38:53 +09:00
Diogo Franco (Kovensky) 4ed46bfa25 Update RELEASE_NOTES 2015-02-25 15:26:03 +09:00
Diogo Franco (Kovensky) 423d47130a Fix compilation
Broken by unchecked cherry-picking of 53867aa.
2015-02-25 11:16:18 +09:00
Niklas Haas 7a02593dc4 vo_opengl: greatly increase smoothmotion performance
Instead of rendering and upscaling each video frame on every vsync, this
version of the algorithm only draws them once and caches the result,
so the only operation that has to run on every vsync is a cheap linear
interpolation, plus CMS/dithering.

On my machine, this is a huge speedup for 24 Hz content (on a 60 Hz
monitor), up to 120% faster. (The speedup is not quite 250% because of
the overhead that the larger FBOs and CMS provides)

In terms of the implementation, this commit basically swaps
interpolation and upscaling - upscaling is moved to inter_program, and
interpolation is moved to the final_program.

Furthermore, the main bulk of the frame rendering logic (upscaling etc.)
was moved to a separete function, which is called from
gl_video_interpolate_frame only if it's actually necessarily, and
skipped otherwise.

(cherry picked from commit 010cf183fe)
2015-02-25 11:10:27 +09:00
Niklas Haas 220d28b902 manpage: update documentation for smoothmotion
Hopefully, this will really clear up how the thing is supposed to work
(and that it's not SVP, nor MVTools).

I also removed instances of the word "interpolation", since that's a
term that's easily misleading.

Finally, I expanded on smoothmotion-threshold since the purpose/meaning
was a bit confusing.

(cherry picked from commit 9fa73b6f64)
2015-02-25 11:10:01 +09:00
wm4 ff10bd65d6 osd: force alignment for OSD bar
Overwrite the alignment applied by the OSD style. Additionally, remove
the initialization of the Alignment field in create_ass_track(); the
value is always overwritten by mp_ass_set_style() later.

Fixes #1626.

(cherry picked from commit 53641a689d)
2015-02-25 11:09:20 +09:00
wm4 e2895e9684 osd: always reset style params
There was some logic to set certain things on init only. Not sure why
this was done (saving some cheap calculations?) - but since the next
call would override these style settings by applying the usual subtitle
style, I don't think this was intended.

(cherry picked from commit 9fbcb56bee)
2015-02-25 11:09:10 +09:00
wm4 83be06c0ad vo_opengl: another GLES2 issue
GLES2 randomly does not support the transpose parameter in matrix
uniform calls. So we have to do this manually. Sure it was worth to
mutilate the standard just so all these shitty SoC vendors can safe 3
lines of code.

(Obviously trying to handle all of GLES2 to GL 4.x in a single codebase
was a mistake.)

(cherry picked from commit cc011415ff)
2015-02-25 11:08:53 +09:00
wm4 fd7077ade9 vo_opengl: extend ifdef against shader arrays
GLES2 shaders do not have line continuation characters. Abuse the
HAVE_ARRAYS define to exclude code which uses arrays, and which also
happens to cover all code that defines multi-line macros. (So yes, this
is a hack.)

(cherry picked from commit 2c8d16d89f)
2015-02-25 11:08:39 +09:00
wm4 d40ccc3416 old-build: proper atomics are not strictly required
(cherry picked from commit a92cab736f)
2015-02-25 11:08:27 +09:00
wm4 57f5ad445d video: remove redundant codec parameters
Remove coded_width and coded_height. This was originally added in commit
fd7dde40, when BITMAPINFOHEADER was killed. The separate fields became
redundant in commit e68f4be1. Remove them (nothing passed to the
decoders actually changes with _this_ commit).

(cherry picked from commit 5baf74fa24)
2015-02-25 11:08:15 +09:00
wm4 a5458a624d ytdl: add --no-warnings
Silences "[ytdl_hook] WARNING: video doesn't have subtitles", which was
an annoying and pointless message printed with almost all youtube
videos.

(cherry picked from commit 7cff2e4131)
2015-02-25 11:01:05 +09:00
Jaime Marquínez Ferrándiz edf0b93703 ytdl: Adapt to new subtitles structure
The requested formats can be sorted by preference and the result gives now an url or the subtitles file content

(cherry picked from commit 7281a72fab)
2015-02-25 11:00:55 +09:00
Kevin Mitchell 6bf46eae23 ao/wasapi: move resume to audio thread
This echanges the two events hForceFeed/hFeedDone for hResume. This
like the last commit makes things more deterministic.

Importantly, the forcefeed is only done if there is not already a full
buffer yet to be played by the device. This should fix some of the
problems with exclusive mode.

This commit also removes the necessity to have a proxy to the
AudioClient object in the main thread.

fixes #1529

(cherry picked from commit c52833bf16)
2015-02-25 10:59:25 +09:00
Kevin Mitchell 774e818371 ao_wasapi: move reset into audio thread
This makes things a bit more deterministic. It ensures that the audio
thread isn't doing anything between IAudioClient_Stop(),
IAudioClient_Reset() and setting the sample_count to 0.

Buffer overfilling on resume is still a problem in exclusive mode (see
next commit).

(cherry picked from commit 446fd5a43a)
2015-02-25 10:59:19 +09:00
wm4 352c00fd08 encoding: allow encoding to channel configurations other than stereo
A while ago, we made mpv output the native number of channels by
default, instead of stereo. I assumed this was not wanted for encode
mode.

This commit keeps the assumption, but allows setting the number of audio
output channels at all, instead of always forcing stereo. (Pretty much a
hack.)

(cherry picked from commit 636d6d724d)
2015-02-25 10:51:05 +09:00
wm4 589b38c187 vf: fix indentation level of verbose output
Just so that it has the same indentation as the extremely similar audio
filter output.

(cherry picked from commit 24e7280178)
2015-02-25 10:50:50 +09:00
wm4 62bc8442be DOCS/client_api_examples/simple: fix option
"input-x11-keyboard" still works, but is a deprecated alias with the
same functionality.

(cherry picked from commit cf395e1754)
2015-02-25 10:50:39 +09:00
wm4 0edd08a1f6 client API: hint that opengl_cb is an alternative to window embedding
(cherry picked from commit d77fdc8f82)
2015-02-25 10:50:31 +09:00
Niklas Haas 7f5378680b vo_opengl: slightly improve ewa_lanczos windowing
The original filter window was design for a radius based on the true
zero, but we always cut it off at our selection of radius either way (by
necessity, due to the square matrix we sample from).

This window is tweaked from the original (true radius) to our actual
cut-off radius, and hence improves the result in a few edge cases. The
main win is the reduction of code complexity, since we no longer need to
know what the true radius actually is.

(cherry picked from commit 1ecd9727f0)
2015-02-25 10:50:16 +09:00
wm4 4753a92b80 player: fix demuxer lifetime management
Commit f54220d9 attempted to improve this, but it got worse. Now there
was a crash when ytdl_hook.lua added external tracks. This happened
because close_unused_demuxers() assumed that sources[0] was the main
demuxer (so that it didn't close it). This assumption failed, because
the ytdl script can add external tracks before the main file is loaded.
The easy fix would have been to check for master_demuxer, and not i==0.
But instead give up on the old idea, make some stricter assumptions how
demuxers and external tracks map, and simplify the code.

(cherry picked from commit 53867aa9d8)
2015-02-24 12:16:00 +09:00
Stefano Pigozzi 584a27e3b8 build: print pkg-config query on failure
(cherry picked from commit 0466ce2f98)
2015-02-24 12:13:05 +09:00
wm4 b4abc0a037 manpage: ipc: add a socat example
Requested, and should be quite good at giving an overview how it works.

(cherry picked from commit af435e0a02)
2015-02-24 12:12:34 +09:00
wm4 192318a216 video: un-discourage "vaapi-copy" hwdec mode
Maybe I don't know what I'm doing. I'm fairly certain though that Intel
does not know what they're doing.

(cherry picked from commit d71bbcbc98)
2015-02-21 21:00:50 +09:00
wm4 822e6e0dd1 player: move timeline scanning (ordered chapters etc.) to a thread
Do timeline building (scanning & opening reference files for ordered
chapters, and more) in a thread. As a result, this process can actually
be stopped without having to kill the player.

This is pretty simple: just reuse the demuxer opening thread. We have
to give up on the idea that open_demux_reentrant() is reusable, though.

(Althoughthe timeline readers still need some fixes before they react to
the quit request.)

(cherry picked from commit 44411674eb)
2015-02-21 20:49:50 +09:00
wm4 45b63c78cd player: cosmetics: async/non-blocking -> reentrant
These functions do blocking work on a separate thread, but wait until
they return. So they are not async or non-blocking. But they do react to
user-input and client API accesses, which makes them reentrant.

(cherry picked from commit 2c305d5b29)
2015-02-21 20:49:32 +09:00
wm4 de47273716 stream_file: open pipes non-blocking
Now the player can actually be quit if a pipe was opened, but nobody is
writing to it.

(cherry picked from commit 3c9344a1a9)
2015-02-21 20:49:11 +09:00
wm4 7ace4f2115 vo_opengl: minor robustness improvement in function loader
Check the scanf() return value, and don't continue if it doesn't find
both numbers (can happen with GLES 1.0). Also, some implementations can
return NULL from glGetString() if something is "broken".

(cherry picked from commit 9861abf8ff)
2015-02-21 20:48:43 +09:00
Niklas Haas f67dfcbec6 vo_opengl: fix smoothmotion coefficient calculation, for real this time
I've reworked pretty much all the logic to correspond to what the theory
actually describes. With this commit, playback is wonderfully smooth on
my machine.

(cherry picked from commit 4356e893a1)
2015-02-21 20:48:24 +09:00
wm4 6b9f7923af demux_lavf: apply hacks even if format is forced
Some of the hacks were not applied if the file format was forced. Commit
37a0c914 moved them to a table, which is checked with normal probing
only.

Fixes #1612 (DVD forces mpeg, which in turn has to export native stream
IDs specifically).

Do some code restructuring on the way. For example, the probescore can
simply be set to the correct initial value, instead of checking whether
it was set at all.

(cherry picked from commit 611f9ab0a8)
2015-02-21 20:48:15 +09:00
wm4 c36b1d01c1 manpage: fix vf_scale arnd parameter
It probably was always a flag, so the documentation became invalid as
soon as mpv stopped accepting 0/1 for flags.

Fixes #1608.

(cherry picked from commit b230f32624)
2015-02-21 20:47:55 +09:00
wm4 07222d14f9 build: fix Python 3 unicode issue with waf 1.8.6
Starting with waf 1.8.6 (in Python 3), the hcode variable isn't a
string, but a byte string.

This commit adds the solution proposed in the upstream waf bug report:
https://code.google.com/p/waf/issues/detail?id=1535

It seems a bit overly verbose, but on the other hand, this solution has
the chance of being most correct/compatible.

Fixes #1604.

(cherry picked from commit 14b231119d)
2015-02-19 19:33:49 +09:00
Diogo Franco (Kovensky) 28fbee7f03 Update RELEASE_NOTES 2015-02-19 13:44:57 +09:00
wm4 b3d2daaa1d demux_lavf: blacklist bintext files
Whatever the hell that is. FFmpeg tries to open any files with .bin file
extension with this demuxer (unless it finds a better demuxer), and then
reads the whole damn file, along with spamming dumb crap.

(cherry picked from commit 79006ef66d)
2015-02-19 13:39:39 +09:00
wm4 3437ea23f6 cache: silence "EOF reached" message
This message will be printed relatively often once EOF is reached. In
some cases this is rather annoying, for example when playing HLS. (With
HLS, the stream is just a playlist file, while libavformat opens actual
media files without mpv's knowledge, so the cache is completely useless
and hits EOF instantly.)

That it retries reading is apparently a good thing: at least local files
can grow, and even after the player got the EOF, playback _could_ be
resumed by basically polling and detecting that there is more data. So
I'm not changing this behavior yet.

(cherry picked from commit 3f5b5b758d)
2015-02-19 13:39:39 +09:00
wm4 16856994e9 demux_lavf: reorganize hacks
An attempt to make format-specifics more declarative. (In my opinion,
all of this should be either provided by libavformat, or should not be
needed.)

I'm still leaving many checks with matches_avinputformat_name(), because
they're so specific.

Also useful for the following commit.

(cherry picked from commit 37a0c9140a)
2015-02-19 13:39:39 +09:00
wm4 c22c440c9c demux_lavf: set interrupt_callback
Helps with terminating the stream if e.g. HLS streams are stuck. (For
other demuxers, the stream's interrupt callback already takes care of
this.)

(cherry picked from commit ff992e093b)
2015-02-19 13:39:39 +09:00
Jaime Marquínez Ferrándiz be42f4c212 find_subfiles: Add 'vtt' to subtitle extensions
(cherry picked from commit aa98049877)
2015-02-19 13:39:39 +09:00
wm4 1b5f127cb0 demux, matroska: remove demuxer type field
The Matroska timeline code was the only thing which still used the
demuxer.type field. This field explicitly identifies a demuxer
implementation. The purpose of the Matroska timeline code was to reject
files that are not Matroska. But it already forces the Matroska format,
meaning loading will explicitly only use the Matroska demuxer. If the
demuxer can't open the file, no other demuxer will be tried, and thus
checking the field is redundant.

The change in demux_mkv_timeline.c removes the if condition, and
unindents the if body.

(cherry picked from commit fa9b587426)
2015-02-19 13:39:39 +09:00
wm4 7ea82eb7bc demux: remove file_contents field
Only demux_cue and demux_edl used it. It's a weird field and doesn't
help with anything anymore - by now, it only saves a priv context in the
mentioned demuxers. Reducing the number of confusing things the demuxer
struct has is more important than minimizing the code.

(cherry picked from commit 082371a160)
2015-02-19 13:39:39 +09:00
wm4 3be989dcd0 demux_edl: make independent from MPContext
(cherry picked from commit d7a4cb14ff)
2015-02-19 13:39:38 +09:00
wm4 0adc8ee5c1 demux_edl: move implementation
Same deal as with demux_cue, and a separate commit for the same reasons.

(cherry picked from commit 1e44c811f3)
2015-02-19 13:39:38 +09:00
wm4 55fe8aab6a demux_cue: make independent from MPContext
Also see previous commit(s).

(cherry picked from commit 10781c2a9c)
2015-02-19 13:39:38 +09:00
wm4 b2aa388f27 demux_cue: move implementation
Move the implementation, of which most was in tl_cue.c, to demux_cue.c.
Currently, this is illogical, because tl_cue.c still accesses MPContext.
This is going to change, and then it will be better if everything is in
demux_cue.c. This is only a separate commit to distinguish code movement
and actual work; the next commit will do the actual work.

(cherry picked from commit 7f03f46882)
2015-02-19 13:39:38 +09:00
wm4 fea1dc0cd5 demux: copy priv pointer too
Weird, but helps with the case a demuxer gets handed its own instance
from outside.

(cherry picked from commit f9872ab26f)
2015-02-19 13:39:38 +09:00
wm4 8161b47e73 matroska: move timeline code to demux/
Separate from previous commit, because git is bad at tracking file
renames when the file contents are also changed.

(cherry picked from commit edc0007e74)
2015-02-19 13:39:38 +09:00
wm4 f113e03b89 matroska: make timeline code independent of MPContext
(cherry picked from commit 5a186d5942)
2015-02-19 13:39:38 +09:00
wm4 630a7b7d82 player: use a separate context for timeline loader stuff
Instead of accessing MPContext in player/timeline/*, create a separate
context struct, which the timeline loaders fill out. It turns out that
there's not much in the way too big MPContext that these need to access.

One major PITA is managing (and closing) the set of open demuxers. The
problem is that we need a list of all demuxers to make sure no unneeded
streams are enabled.

This adds a callback to the demuxer_desc struct, with the intention of
leaving to to the demuxer to call the right loader, instead of
explicitly checking the demuxer type and dispatching manually in common
code. I also considered making the timeline part of the demuxer state,
but decided against: it's too much of a mess wrt. memory management and
threading, and also doesn't make it clear who owns the child demuxers.
With the struct timeline decoupled from the demuxer state, it's at least
somewhat clear that the child demuxers are independent from the "main"
demuxer.

The actual changes to player/timeline/* are separated in the following
commits, because they're quite verbose. Some artifacts will be removed
later as soon as there's only 1 timeline loading mechanism.

(cherry picked from commit a0a089f6a4)
2015-02-19 13:39:38 +09:00
wm4 a4e766a77f demux: chapters without metadata are allowed
Makes some of the following commits slightly simpler. Also fix a typo.

(cherry picked from commit 3efeee446e)
2015-02-19 13:39:38 +09:00
wm4 57deaa02d5 player: use a macro to remove an element from an array
Should be equivalent.

(cherry picked from commit 969edb9e0b)
2015-02-19 13:39:38 +09:00
wm4 a6accfdad7 player: actually close files when using sub_remove
Also effects some other cases.

The real reason for this is for keeping track of which demuxers can be
closed (see following commit). Since I don't want to use reference
counting for this, some sort of simplistic mark-and-sweep is done to
determine whether a demuxer is still needed.

(cherry picked from commit f54220d951)
2015-02-19 13:39:38 +09:00
wm4 8813fe090d DOCS/client-api-changes: mark 0.8.0 release
(There was a missing version bump for the msg-level change; just move it
under 1.14.)

(cherry picked from commit 0283815ee1)
2015-02-19 13:39:38 +09:00
wm4 72ba8515d5 demux_lavf: hack against hls showing "100%" position
The HLs protocol consists of a "playlist" main file, which mpv downloads
and passes to the HLS demuxer. The HLS demuxer actually requests segment
files containing media data on its own. The packets read from the
demuxer have a source file position set, but it's not from the main
file. This leads to a strange effect: as a last fallback, the player
will calculate the approximate playback position from the file
position/size ratio, and since the main file is tiny, this will always
show 100%. Fix this by resetting the packet file position.

This doesn't affect the case when HLS actually reports a duration.

(cherry picked from commit a64b1deaa6)
2015-02-19 13:39:38 +09:00
wm4 21f5d82dc5 etc/input.conf: remove a leftover
This is already mapped by default.

(cherry picked from commit 8b784fb1ca)
2015-02-19 13:24:59 +09:00
wm4 8c992458c3 vo_opengl: glsl: remove trailing \
This should be no problem... but it _might_ help with #1536, so it's
worth a try.

(cherry picked from commit 0063d94927)
2015-02-19 13:24:31 +09:00
wm4 40149c017c m_config: log options set by the client API too
(cherry picked from commit d34eabe286)
2015-02-19 13:24:09 +09:00
Diogo Franco (Kovensky) 0c9f10e0d1 Release v0.8.0 2015-02-17 14:06:06 +09:00
Diogo Franco (Kovensky) 5b0d06ca74 Fix typo in RELEASE_NOTES 2015-02-17 14:05:50 +09:00
Diogo Franco (Kovensky) a410451463 Fix typo on RELEASE_NOTES 2015-02-16 18:54:13 +09:00
Skyrainfit 3152eed6d8 mpv.desktop: Add zh-TW and zh-CN translations
(cherry picked from commit 31ac0574ad)
2015-02-16 18:45:05 +09:00
Diogo Franco (Kovensky) 02260882ce Bump VERSION to v0.8.0_rc3 2015-02-16 18:37:46 +09:00
Diogo Franco (Kovensky) cd5f0a8612 Update RELEASE_NOTES 2015-02-16 18:37:32 +09:00
wm4 23959eb5b4 osxbundle: config file special case isn't needed anymore
The previous commit effectively fixes the mess caused by 'config' vs.
'mpv.conf', and the hack introduced by commit e01a6dac and extended by
commit db167cd4 isn't needed anymore.

(cherry picked from commit 816d7bba1f)
2015-02-16 18:16:36 +09:00
wm4 21a95f5ed9 player: undeprecate 'config' files
Actually, it's pretty simple to look for multiple filenames at once,
since mp_find_all_config_files() is already a bit "special" anyway.

See #1569. Reverts most of commit db167cd4 (keeps osx-bundle.conf).

(cherry picked from commit a27aa68dd3)
2015-02-16 18:16:28 +09:00
wm4 6dc2a23cda vd_lavc: uninit the hwdec backend after closing the decoder
A recent behavior change in libavcodec's h264 decoder keeps at least 1
surface even after avcodec_flush_buffers() has been called. We used to
flush the decoder in order to make sure all surfaces are free'd, so that
the hw decoder can be safely uninitialized. This doesn't work anymore.

Fix it by closing the AVCodecContext before the hw decoder is
uninitialized. This is actually simpler and more robust. It seems to be
well-supported too.

Fixes invalid read accesses with vaapi-copy and dxva2-copy. These
destroyed the hwdec API fully on uninit, and could not deal with
surfaces surviving the decoder.

Probably fixes #1587.

(cherry picked from commit cf073138b2)
2015-02-16 18:15:20 +09:00
wm4 f94318babc x11: fix uninitialized variable reads
This line of code ended up in the wrong block in commit cd6dfcbe.

(cherry picked from commit f247294d73)
2015-02-16 18:15:05 +09:00
wm4 b476d21d0a client API: clarify filename string encoding
Admittedly, the behavior on Windows is not quite straightforward. The
behavior on the other platforms is equivalent to fopen().

Fixes #1585.

(cherry picked from commit a799a4c57f)
2015-02-16 18:14:10 +09:00
Stefano Pigozzi 16c7de4aab cocoa: remove unused function
(cherry picked from commit bf46f4c997)
2015-02-16 18:13:44 +09:00
Martin Herkt 8fdd688d48 x11: add XK_Cancel to the list of special keys
Some IR receivers emit this key by default for remote control
buttons. Make it mappable.

(cherry picked from commit 9aaec7cffb)
2015-02-16 18:13:36 +09:00
wm4 63304b95a1 Fix build on OSX broken by previous commit
Hopefully.

(cherry picked from commit 5247416735)
2015-02-16 18:13:29 +09:00
Julian f4263a811f vf_vapoursynth: add display refresh rate property
This value is not necessarily trustworthy (it might change) and can be
0.

(cherry picked from commit 349067a6ab)
2015-02-16 18:13:22 +09:00
wm4 a6bf8e7c36 x11: return a framerate even if no window is mapped
Falls back to the first display in the list returned by xrandr. Not
entirely correct, but makes some people happy (see #1575).

(cherry picked from commit cd6dfcbef4)
2015-02-16 18:13:15 +09:00
wm4 6488e77d8a player: deprecate 'config' files (use mpv.conf), warn against clashes
Apparently there's at least one distro which ships a /etc/mpv/mpv.conf
file (mpv doesn't install such a file). This breaks config files named
'config' located in the user's mpv config directory, because mpv first
loads files named 'config' and then 'mpv.conf'. There is no mechanism
for putting files with different names into the same config path order.
(Even worse, that mpv.conf file only set an option to the default value.
Why do distros always do very stupid things?)

Print a warning on collisions.

Although using 'config' was well-supported, supporting both names is
starting to become messy, so deprecate 'config' and print a warning if
one is found.

At least we will be able to remove the whole mess once 'config' files
are ignored...

This also affects the osx-bundle, which intentionally used these not-so-
optimal semantics. Solve it in a different way. (Unfortunately with an
ifdef - it's not required, but having to explain everyone why mpv tries
to load a osx-bundle.mpv file on Linux and Windows would consume
energy.)

Closes #1569.

(cherry picked from commit db167cd438)
2015-02-16 18:13:01 +09:00
wm4 34a856f465 command: mark get_property as deprecated
Using it just makes no sense. But we're really being nice about this and
don't remove it immediately.

(cherry picked from commit fd5403cb40)
2015-02-16 18:12:17 +09:00
wm4 db218a8d25 x11: make all XF86 special keys mappable
Makes all keys documented in XF86keysym.h mappable. This requires the
user to deal with numeric keycodes; no names are queried or exported.

This is an easy way to avoid adding all the hundreds of XF86 keys to
our X11 lookup table and mpv's keycode/name list.

(cherry picked from commit 417869f845)
2015-02-16 18:12:04 +09:00
wm4 e3064c3cb7 ipc: put playback core to sleep while dequeuing commands
Happens to fix #1581 due to an unfortunate interaction with the way the
VO does not react to commands for a while if a video frame is queued.
Slightly improves other situations as well, if the client spams mpv with
commands during playback.

(cherry picked from commit 32b56c56ba)
2015-02-16 18:11:52 +09:00
Stefano Pigozzi df4d15be7b vo_opengl: fix smoothmotion coefficient calculation
Using prev_pts as the start of the scale was plain wrong. Change it to
prev_vsync.

(cherry picked from commit 3931544ef3)
2015-02-16 18:09:45 +09:00
wm4 cb5adf4d72 cocoa: fix exiting the command line player
Commit e920a00eb assumed that terminate_cocoa_application() actually
would exit. But apparently that is not always the case; e.g. mpv --help
will just hang. The old code had a dummy exit(0), which was apparently
actually called. Fix by explicitly exiting if mpv_main() returns and
terminate_cocoa_application() does nothing.

(cherry picked from commit 9e14042e57)
2015-02-16 18:09:35 +09:00
wm4 440ba33639 encoding: fixed-vo option was removed
(cherry picked from commit 96547a810e)
2015-02-16 18:09:22 +09:00
wm4 e4eb9c5469 player: add a --loop=force mode
Requested. See manpage additions.

This also makes the magical loop_times constants slightly saner, but
shouldn't change the semantics of any existing --loop option values.

(cherry picked from commit aee0978d50)
2015-02-16 18:09:12 +09:00
wm4 4387919753 osx: move code to unset input context to the right place
Setting the input context is always called, both in cplayer and libmpv,
and under HAVE_COCOA. Unsetting the input context was done only the
cplayer uninit call. Also it was under HAVE_COCOA_APPLICATION, so it was
not unset in libmpv (dangling pointer).

(cherry picked from commit c59a4f12db)
2015-02-16 18:08:58 +09:00
wm4 94123474a4 osx: move cocoa specific call out of common code
This is almost equivalent, and gets rid of the ifdef.
2015-02-16 18:08:52 +09:00
wm4 50a7aac4d7 player: drop explicit exit() calls
The code in main.c calls exit() explicitly, but the code is actually
easier to follow by simply exiting from main() instead. The exit() call
in av_log.c happens only on severely broken builds, so replace it with
abort().

(Shuts up rpmlint warnings.)

(cherry picked from commit e920a00eba)
2015-02-16 18:08:38 +09:00
wm4 2051243a0f ao: set correct client name when listing devices
This is a small oversight. The client name (as set on command line
options or, more importantly, the client API) was not set when listing
devices e.g. via the "audio-device-list" property.

Might or might not fix #1578.

Also adjust the log level for an unrelated message.

(cherry picked from commit c152c59084)
2015-02-16 18:08:17 +09:00
Diogo Franco (Kovensky) a21d09d9f8 Update RELEASE_NOTES formatting 2015-02-13 13:27:38 +09:00
Diogo Franco (Kovensky) b95a0bfa77 Merge remote-tracking branch 'Cpuroast/release/0.8' into release/0.8
* Cpuroast/release/0.8:
  A few tweaks to the 0.80_rc2 release notes
2015-02-13 13:24:30 +09:00
Cpuroast 43f831b6ba A few tweaks to the 0.80_rc2 release notes
scale-radius=3 is the default, so use 2 as an example
Point out that ewa_lanczos (Jinc) is actually new.
2015-02-12 11:39:57 -05:00
wm4 b3eba216b7 Revert "player: make --force-window create the window immediately on start"
This reverts commit acc5e8f574.

As expected, some didn't like this. Others won't like this revert.
Whatever.

See #1561.

This should go into mpv 0.8.0 before it's released.

(cherry picked from commit 7bbc617019)
2015-02-12 21:39:10 +09:00
Diogo Franco (Kovensky) 0668c82da5 Bump version to v0.8.0_rc2 2015-02-12 21:23:45 +09:00
Diogo Franco (Kovensky) c1e8f3b479 Update RELEASE_NOTES 2015-02-12 13:31:03 +09:00
wm4 64a45f972c video/filters: simplify libavfilter bridge
Remove the confusing crap that allowed a filter using the libavfilter
bridge to be compiled without libavfilter. Instead, compile the wrappers
only if libavfilter is enabled at compile time.

The only filter which still requires it is vf_stereo3d (unfortunately).
Special-case this one. (The whole filter and how it interacts with lavfi
is pure braindeath anyway.)

(cherry picked from commit 2522bff565)
2015-02-12 09:53:43 +09:00
wm4 d4207c64e6 vf_noise: remove internal implementation
It requires libavfilter now, just like many other filters. Not sure if
it even makes sense to keep this wrapper.

(cherry picked from commit 73d23a9405)
2015-02-12 09:53:37 +09:00
wm4 abf1fc9a77 af: account for queued frames in audio position calculation
af_rubberband exposed this issue.

(cherry picked from commit d85aa35ffb)
2015-02-12 09:53:27 +09:00
Diogo Franco (Kovensky) da8e573b45 Bump version to -rc1 2015-02-12 00:06:37 +09:00
wm4 25c0097c67 dvb_tune: fix invalid syntax
Oops.
2015-02-11 23:59:35 +09:00
wm4 20d5efb419 audio: fix pool allocation
It reallocated the pool on every request, making the pool completely
useless. Oops.
2015-02-11 23:59:16 +09:00
Diogo Franco (Kovensky) a18b4b2b19 Add preliminary RELEASE_NOTES 2015-02-11 23:57:57 +09:00
wm4 a468e2869d manpage: fix af_scaletempo suggested commands 2015-02-11 23:55:12 +09:00
Diogo Franco (Kovensky) cb9975b942 Branch release/0.8, add -beta VERSION 2015-02-10 22:59:51 +09:00
132 changed files with 2536 additions and 2144 deletions

No files matched your search

+3 -2
View File
@@ -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 -15
View File
@@ -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};
+1 -1
View File
@@ -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));
+2 -2
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+31 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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";
+1
View File
@@ -0,0 +1 @@
0.8.3
+2 -1
View File
@@ -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
View File
@@ -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);
-1
View File
@@ -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;
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+2 -4
View File
@@ -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;
-4
View File
@@ -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
View File
@@ -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:
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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, &param)) {
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(&param);
break;
case CUE_INDEX: {
int type = read_int_2(&param);
double time = read_time(&param);
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(&param);
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
View File
@@ -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
View File
@@ -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
View File
@@ -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", &params, mpctx->global);
struct demuxer *d = demux_open(s, "mkv", &params, 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, &params) < 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, &params) < 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);
}
+26
View File
@@ -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
View File
@@ -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) {
+3
View File
@@ -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"
-1
View File
@@ -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
+42
View File
@@ -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);
}
+37
View File
@@ -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
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+1
View File
@@ -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
View File
@@ -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)
+31
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 \
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+5
View File
@@ -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) \
+4 -10
View File
@@ -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);
}
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+12
View File
@@ -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),
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+2
View File
@@ -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
View File
@@ -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
-415
View File
@@ -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, &param)) {
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(&param);
break;
case CUE_INDEX: {
int type = read_int_2(&param);
double time = read_time(&param);
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(&param);
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);
}
-302
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
-4
View File
@@ -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[];
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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,
};
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+1 -8
View File
@@ -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;
-5
View File
@@ -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
View File
@@ -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
View File
@@ -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]");
+1
View File
@@ -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;
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1
View File
@@ -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"
-27
View File
@@ -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
View File
@@ -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}
},
+1 -1
View File
@@ -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