aboutsummaryrefslogtreecommitdiffhomepage
path: root/mpvcore
diff options
context:
space:
mode:
Diffstat (limited to 'mpvcore')
-rw-r--r--mpvcore/asxparser.c590
-rw-r--r--mpvcore/asxparser.h27
-rw-r--r--mpvcore/av_common.c123
-rw-r--r--mpvcore/av_common.h33
-rw-r--r--mpvcore/av_log.c161
-rw-r--r--mpvcore/av_log.h2
-rw-r--r--mpvcore/av_opts.c55
-rw-r--r--mpvcore/av_opts.h30
-rw-r--r--mpvcore/bstr.c314
-rw-r--r--mpvcore/bstr.h183
-rw-r--r--mpvcore/charset_conv.c266
-rw-r--r--mpvcore/charset_conv.h17
-rw-r--r--mpvcore/codecs.c147
-rw-r--r--mpvcore/codecs.h43
-rw-r--r--mpvcore/command.c2673
-rw-r--r--mpvcore/command.h33
-rw-r--r--mpvcore/cpudetect.c56
-rw-r--r--mpvcore/cpudetect.h40
-rw-r--r--mpvcore/encode.h22
-rw-r--r--mpvcore/encode_lavc.c1115
-rw-r--r--mpvcore/encode_lavc.h101
-rw-r--r--mpvcore/input/input.c2284
-rw-r--r--mpvcore/input/input.h273
-rw-r--r--mpvcore/input/joystick.c162
-rw-r--r--mpvcore/input/joystick.h26
-rw-r--r--mpvcore/input/keycodes.h247
-rw-r--r--mpvcore/input/lirc.c123
-rw-r--r--mpvcore/input/lirc.h30
-rw-r--r--mpvcore/m_config.c777
-rw-r--r--mpvcore/m_config.h217
-rw-r--r--mpvcore/m_option.c2407
-rw-r--r--mpvcore/m_option.h646
-rw-r--r--mpvcore/m_property.c369
-rw-r--r--mpvcore/m_property.h142
-rw-r--r--mpvcore/mp_common.c125
-rw-r--r--mpvcore/mp_common.h66
-rw-r--r--mpvcore/mp_core.h343
-rw-r--r--mpvcore/mp_memory_barrier.h23
-rw-r--r--mpvcore/mp_msg.c471
-rw-r--r--mpvcore/mp_msg.h178
-rw-r--r--mpvcore/mp_osd.h52
-rw-r--r--mpvcore/mp_ring.c155
-rw-r--r--mpvcore/mp_ring.h130
-rw-r--r--mpvcore/mp_talloc.h61
-rw-r--r--mpvcore/mplayer.c4747
-rw-r--r--mpvcore/mpv_global.h12
-rw-r--r--mpvcore/options.c838
-rw-r--r--mpvcore/options.h276
-rw-r--r--mpvcore/parser-cfg.c249
-rw-r--r--mpvcore/parser-cfg.h27
-rw-r--r--mpvcore/parser-mpcmd.c314
-rw-r--r--mpvcore/parser-mpcmd.h33
-rw-r--r--mpvcore/path.c229
-rw-r--r--mpvcore/path.h66
-rw-r--r--mpvcore/playlist.c246
-rw-r--r--mpvcore/playlist.h74
-rw-r--r--mpvcore/playlist_parser.c777
-rw-r--r--mpvcore/playlist_parser.h34
-rw-r--r--mpvcore/resolve.h53
-rw-r--r--mpvcore/resolve_quvi.c93
-rw-r--r--mpvcore/resolve_quvi9.c150
-rw-r--r--mpvcore/screenshot.c390
-rw-r--r--mpvcore/screenshot.h46
-rw-r--r--mpvcore/timeline/tl_cue.c419
-rw-r--r--mpvcore/timeline/tl_edl.c392
-rw-r--r--mpvcore/timeline/tl_matroska.c374
-rw-r--r--mpvcore/version.c26
67 files changed, 25203 insertions, 0 deletions
diff --git a/mpvcore/asxparser.c b/mpvcore/asxparser.c
new file mode 100644
index 0000000000..ec15313547
--- /dev/null
+++ b/mpvcore/asxparser.c
@@ -0,0 +1,590 @@
+/*
+ * 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 "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "playlist.h"
+#include "playlist_parser.h"
+#include "stream/stream.h"
+#include "asxparser.h"
+#include "core/mp_msg.h"
+
+
+typedef struct ASX_Parser_t ASX_Parser_t;
+
+typedef struct {
+ char* buffer;
+ int line;
+} ASX_LineSave_t;
+
+struct ASX_Parser_t {
+ int line; // Curent line
+ ASX_LineSave_t *ret_stack;
+ int ret_stack_size;
+ char* last_body;
+ int deep;
+ struct playlist *pl;
+};
+
+ASX_Parser_t *asx_parser_new(struct playlist *pl);
+
+void
+asx_parser_free(ASX_Parser_t* parser);
+
+/*
+ * Return -1 on error, 0 when nothing is found, 1 on sucess
+ */
+int
+asx_get_element(ASX_Parser_t* parser,char** _buffer,
+ char** _element,char** _body,char*** _attribs);
+
+int
+asx_parse_attribs(ASX_Parser_t* parser,char* buffer,char*** _attribs);
+
+/////// Attribs utils
+
+char*
+asx_get_attrib(const char* attrib,char** attribs);
+
+#define asx_free_attribs(a) asx_list_free(&a,free)
+
+////// List utils
+
+typedef void (*ASX_FreeFunc)(void* arg);
+
+void
+asx_list_free(void* list_ptr,ASX_FreeFunc free_func);
+
+
+////// List utils
+
+void
+asx_list_free(void* list_ptr,ASX_FreeFunc free_func) {
+ void** ptr = *(void***)list_ptr;
+ if(ptr == NULL) return;
+ if(free_func != NULL) {
+ for( ; *ptr != NULL ; ptr++)
+ free_func(*ptr);
+ }
+ free(*(void**)list_ptr);
+ *(void**)list_ptr = NULL;
+}
+
+/////// Attribs utils
+
+char*
+asx_get_attrib(const char* attrib,char** attribs) {
+ char** ptr;
+
+ if(attrib == NULL || attribs == NULL) return NULL;
+ for(ptr = attribs; ptr[0] != NULL; ptr += 2){
+ if(strcasecmp(ptr[0],attrib) == 0)
+ return strdup(ptr[1]);
+ }
+ return NULL;
+}
+
+#define asx_warning_attrib_required(p,e,a) mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : element %s don't have the required attribute %s",p->line,e,a)
+#define asx_warning_body_parse_error(p,e) mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : error while parsing %s body",p->line,e)
+
+ASX_Parser_t *asx_parser_new(struct playlist *pl)
+{
+ ASX_Parser_t* parser = calloc(1,sizeof(ASX_Parser_t));
+ parser->pl = pl;
+ return parser;
+}
+
+void
+asx_parser_free(ASX_Parser_t* parser) {
+ if(!parser) return;
+ free(parser->ret_stack);
+ free(parser);
+
+}
+
+#define LETTER "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define SPACE " \n\t\r"
+
+int
+asx_parse_attribs(ASX_Parser_t* parser,char* buffer,char*** _attribs) {
+ char *ptr1, *ptr2, *ptr3;
+ int n_attrib = 0;
+ char **attribs = NULL;
+ char *attrib, *val;
+
+ ptr1 = buffer;
+ while(1) {
+ for( ; strchr(SPACE,*ptr1) != NULL; ptr1++) { // Skip space
+ if(*ptr1 == '\0') break;
+ }
+ ptr3 = strchr(ptr1,'=');
+ if(ptr3 == NULL) break;
+ for(ptr2 = ptr3-1; strchr(SPACE,*ptr2) != NULL; ptr2--) {
+ if (ptr2 == ptr1) {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : this should never append, back to attribute begin while skipping end space",parser->line);
+ break;
+ }
+ }
+ attrib = malloc(ptr2-ptr1+2);
+ strncpy(attrib,ptr1,ptr2-ptr1+1);
+ attrib[ptr2-ptr1+1] = '\0';
+
+ ptr1 = strchr(ptr3,'"');
+ if(ptr1 == NULL || ptr1[1] == '\0') ptr1 = strchr(ptr3,'\'');
+ if(ptr1 == NULL || ptr1[1] == '\0') {
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : can't find attribute %s value",parser->line,attrib);
+ free(attrib);
+ break;
+ }
+ ptr2 = strchr(ptr1+1,ptr1[0]);
+ if (ptr2 == NULL) {
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : value of attribute %s isn't finished",parser->line,attrib);
+ free(attrib);
+ break;
+ }
+ ptr1++;
+ val = malloc(ptr2-ptr1+1);
+ strncpy(val,ptr1,ptr2-ptr1);
+ val[ptr2-ptr1] = '\0';
+ n_attrib++;
+
+ attribs = realloc(attribs, (2 * n_attrib + 1) * sizeof(char*));
+ attribs[n_attrib*2-2] = attrib;
+ attribs[n_attrib*2-1] = val;
+
+ ptr1 = ptr2+1;
+ }
+
+ if(n_attrib > 0)
+ attribs[n_attrib*2] = NULL;
+
+ *_attribs = attribs;
+
+ return n_attrib;
+}
+
+/*
+ * Return -1 on error, 0 when nothing is found, 1 on sucess
+ */
+int
+asx_get_element(ASX_Parser_t* parser,char** _buffer,
+ char** _element,char** _body,char*** _attribs) {
+ char *ptr1,*ptr2, *ptr3, *ptr4;
+ char *attribs = NULL;
+ char *element = NULL, *body = NULL, *ret = NULL, *buffer;
+ int n_attrib = 0;
+ int body_line = 0,attrib_line,ret_line,in = 0;
+ int quotes = 0;
+
+ if(_buffer == NULL || _element == NULL || _body == NULL || _attribs == NULL) {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : asx_get_element called with invalid value",parser->line);
+ return -1;
+ }
+
+ *_body = *_element = NULL;
+ *_attribs = NULL;
+ buffer = *_buffer;
+
+ if(buffer == NULL) return 0;
+
+ if(parser->ret_stack && /*parser->last_body && */buffer != parser->last_body) {
+ ASX_LineSave_t* ls = parser->ret_stack;
+ int i;
+ for(i = 0 ; i < parser->ret_stack_size ; i++) {
+ if(buffer == ls[i].buffer) {
+ parser->line = ls[i].line;
+ break;
+ }
+
+ }
+ if( i < parser->ret_stack_size) {
+ i++;
+ if( i < parser->ret_stack_size)
+ memmove(parser->ret_stack,parser->ret_stack+i, (parser->ret_stack_size - i)*sizeof(ASX_LineSave_t));
+ parser->ret_stack_size -= i;
+ if(parser->ret_stack_size > 0)
+ parser->ret_stack = realloc(parser->ret_stack,parser->ret_stack_size*sizeof(ASX_LineSave_t));
+ else {
+ free(parser->ret_stack);
+ parser->ret_stack = NULL;
+ }
+ }
+ }
+
+ ptr1 = buffer;
+ while(1) {
+ for( ; ptr1[0] != '<' ; ptr1++) {
+ if(ptr1[0] == '\0') {
+ ptr1 = NULL;
+ break;
+ }
+ if(ptr1[0] == '\n') parser->line++;
+ }
+ //ptr1 = strchr(ptr1,'<');
+ if(!ptr1 || ptr1[1] == '\0') return 0; // Nothing found
+
+ if(strncmp(ptr1,"<!--",4) == 0) { // Comments
+ for( ; strncmp(ptr1,"-->",3) != 0 ; ptr1++) {
+ if(ptr1[0] == '\0') {
+ ptr1 = NULL;
+ break;
+ }
+ if(ptr1[0] == '\n') parser->line++;
+ }
+ //ptr1 = strstr(ptr1,"-->");
+ if(!ptr1) {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : unfinished comment",parser->line);
+ return -1;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Is this space skip very useful ??
+ for(ptr1++; strchr(SPACE,ptr1[0]) != NULL; ptr1++) { // Skip space
+ if(ptr1[0] == '\0') {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing element start",parser->line);
+ return -1;
+ }
+ if(ptr1[0] == '\n') parser->line++;
+ }
+
+ for(ptr2 = ptr1; strchr(LETTER,*ptr2) != NULL;ptr2++) { // Go to end of name
+ if(*ptr2 == '\0'){
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing element start",parser->line);
+ return -1;
+ }
+ if(ptr2[0] == '\n') parser->line++;
+ }
+
+ element = malloc(ptr2-ptr1+1);
+ strncpy(element,ptr1,ptr2-ptr1);
+ element[ptr2-ptr1] = '\0';
+
+ for( ; strchr(SPACE,*ptr2) != NULL; ptr2++) { // Skip space
+ if(ptr2[0] == '\0') {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing element start",parser->line);
+ free(element);
+ return -1;
+ }
+ if(ptr2[0] == '\n') parser->line++;
+ }
+ attrib_line = parser->line;
+
+
+
+ for(ptr3 = ptr2; ptr3[0] != '\0'; ptr3++) { // Go to element end
+ if(ptr3[0] == '"') quotes ^= 1;
+ if(!quotes && (ptr3[0] == '>' || strncmp(ptr3,"/>",2) == 0))
+ break;
+ if(ptr3[0] == '\n') parser->line++;
+ }
+ if(ptr3[0] == '\0' || ptr3[1] == '\0') { // End of file
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing element start",parser->line);
+ free(element);
+ return -1;
+ }
+
+ // Save attribs string
+ if(ptr3-ptr2 > 0) {
+ attribs = malloc(ptr3-ptr2+1);
+ strncpy(attribs,ptr2,ptr3-ptr2);
+ attribs[ptr3-ptr2] = '\0';
+ }
+ //bs_line = parser->line;
+ if(ptr3[0] != '/') { // Not Self closed element
+ ptr3++;
+ for( ; strchr(SPACE,*ptr3) != NULL; ptr3++) { // Skip space on body begin
+ if(*ptr3 == '\0') {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing %s element body",parser->line,element);
+ free(element);
+ free(attribs);
+ return -1;
+ }
+ if(ptr3[0] == '\n') parser->line++;
+ }
+ ptr4 = ptr3;
+ body_line = parser->line;
+ while(1) { // Find closing element
+ for( ; ptr4[0] != '<' ; ptr4++) {
+ if(ptr4[0] == '\0') {
+ ptr4 = NULL;
+ break;
+ }
+ if(ptr4[0] == '\n') parser->line++;
+ }
+ if(ptr4 && strncmp(ptr4,"<!--",4) == 0) { // Comments
+ for( ; strncmp(ptr4,"-->",3) != 0 ; ptr4++) {
+ if(ptr4[0] == '\0') {
+ ptr4 = NULL;
+ break;
+ }
+ if(ptr1[0] == '\n') parser->line++;
+ }
+ continue;
+ }
+ if(ptr4 == NULL || ptr4[1] == '\0') {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing %s element body",parser->line,element);
+ free(element);
+ free(attribs);
+ return -1;
+ }
+ if(ptr4[1] != '/' && strncasecmp(element,ptr4+1,strlen(element)) == 0) {
+ in++;
+ ptr4+=2;
+ continue;
+ } else if(strncasecmp(element,ptr4+2,strlen(element)) == 0) { // Extract body
+ if(in > 0) {
+ in--;
+ ptr4 += 2+strlen(element);
+ continue;
+ }
+ ret = ptr4+strlen(element)+3;
+ if(ptr4 != ptr3) {
+ ptr4--;
+ for( ; ptr4 != ptr3 && strchr(SPACE,*ptr4) != NULL; ptr4--) ;// Skip space on body end
+ // if(ptr4[0] == '\0') parser->line--;
+ //}
+ ptr4++;
+ body = malloc(ptr4-ptr3+1);
+ strncpy(body,ptr3,ptr4-ptr3);
+ body[ptr4-ptr3] = '\0';
+ }
+ break;
+ } else {
+ ptr4 += 2;
+ }
+ }
+ } else {
+ ret = ptr3 + 2; // 2 is for />
+ }
+
+ for( ; ret[0] != '\0' && strchr(SPACE,ret[0]) != NULL; ret++) { // Skip space
+ if(ret[0] == '\n') parser->line++;
+ }
+
+ ret_line = parser->line;
+
+ if(attribs) {
+ parser->line = attrib_line;
+ n_attrib = asx_parse_attribs(parser,attribs,_attribs);
+ free(attribs);
+ if(n_attrib < 0) {
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : error while parsing element %s attributes",parser->line,element);
+ free(element);
+ free(body);
+ return -1;
+ }
+ } else
+ *_attribs = NULL;
+
+ *_element = element;
+ *_body = body;
+
+ parser->last_body = body;
+ parser->ret_stack_size++;
+ parser->ret_stack = realloc(parser->ret_stack,parser->ret_stack_size*sizeof(ASX_LineSave_t));
+ if(parser->ret_stack_size > 1)
+ memmove(parser->ret_stack+1,parser->ret_stack,(parser->ret_stack_size-1)*sizeof(ASX_LineSave_t));
+ parser->ret_stack[0].buffer = ret;
+ parser->ret_stack[0].line = ret_line;
+ parser->line = body ? body_line : ret_line;
+
+ *_buffer = ret;
+ return 1;
+
+}
+
+static void
+asx_parse_ref(ASX_Parser_t* parser, char** attribs) {
+ char *href;
+
+ href = asx_get_attrib("HREF",attribs);
+ if(href == NULL) {
+ asx_warning_attrib_required(parser,"REF" ,"HREF" );
+ return;
+ }
+#if 0
+ // replace http my mmshttp to avoid infinite loops
+ // disabled since some playlists for e.g. WinAMP use asx as well
+ // "-user-agent NSPlayer/4.1.0.3856" is a possible workaround
+ if (strncmp(href, "http://", 7) == 0) {
+ char *newref = malloc(3 + strlen(href) + 1);
+ strcpy(newref, "mms");
+ strcpy(newref + 3, href);
+ free(href);
+ href = newref;
+ }
+#endif
+
+ playlist_add_file(parser->pl, href);
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Adding file %s to element entry\n",href);
+
+ free(href);
+
+}
+
+static void asx_parse_entryref(ASX_Parser_t* parser,char* buffer,char** _attribs) {
+ char *href;
+ stream_t* stream;
+
+ if(parser->deep > 0)
+ return;
+
+ href = asx_get_attrib("HREF",_attribs);
+ if(href == NULL) {
+ asx_warning_attrib_required(parser,"ENTRYREF" ,"HREF" );
+ return;
+ }
+ stream=stream_open(href, NULL);
+ if(!stream) {
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"Can't open playlist %s\n",href);
+ free(href);
+ return;
+ }
+
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Not recursively loading playlist %s\n",href);
+
+ free_stream(stream);
+ free(href);
+ //mp_msg(MSGT_PLAYTREE,MSGL_INFO,"Need to implement entryref\n");
+}
+
+static void asx_parse_entry(ASX_Parser_t* parser,char* buffer,char** _attribs) {
+ char *element,*body,**attribs;
+ int r;
+
+ while(buffer && buffer[0] != '\0') {
+ r = asx_get_element(parser,&buffer,&element,&body,&attribs);
+ if(r < 0) {
+ asx_warning_body_parse_error(parser,"ENTRY");
+ return;
+ } else if (r == 0) { // No more element
+ break;
+ }
+ if(strcasecmp(element,"REF") == 0) {
+ asx_parse_ref(parser,attribs);
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Adding element %s to entry\n",element);
+ } else
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Ignoring element %s\n",element);
+ free(body);
+ asx_free_attribs(attribs);
+ }
+
+}
+
+
+static void asx_parse_repeat(ASX_Parser_t* parser,char* buffer,char** _attribs) {
+ char *element,*body,**attribs;
+ int r;
+
+ asx_get_attrib("COUNT",_attribs);
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Ignoring repeated playlist entries\n");
+
+ while(buffer && buffer[0] != '\0') {
+ r = asx_get_element(parser,&buffer,&element,&body,&attribs);
+ if(r < 0) {
+ asx_warning_body_parse_error(parser,"REPEAT");
+ return;
+ } else if (r == 0) { // No more element
+ break;
+ }
+ if(strcasecmp(element,"ENTRY") == 0) {
+ asx_parse_entry(parser,body,attribs);
+ } else if(strcasecmp(element,"ENTRYREF") == 0) {
+ asx_parse_entryref(parser,body,attribs);
+ } else if(strcasecmp(element,"REPEAT") == 0) {
+ asx_parse_repeat(parser,body,attribs);
+ } else
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Ignoring element %s\n",element);
+ free(body);
+ asx_free_attribs(attribs);
+ }
+
+}
+
+
+bool asx_parse(char* buffer, struct playlist *pl)
+{
+ char *element,*asx_body,**asx_attribs,*body = NULL, **attribs;
+ int r;
+ ASX_Parser_t* parser = asx_parser_new(pl);
+
+ parser->line = 1;
+ parser->deep = 0;
+
+ r = asx_get_element(parser,&buffer,&element,&asx_body,&asx_attribs);
+ if(r < 0) {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : Syntax error ???",parser->line);
+ asx_parser_free(parser);
+ return false;
+ } else if(r == 0) { // No contents
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"empty asx element");
+ asx_parser_free(parser);
+ return false;
+ }
+
+ if(strcasecmp(element,"ASX") != 0) {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"first element isn't ASX, it's %s\n",element);
+ asx_free_attribs(asx_attribs);
+ asx_parser_free(parser);
+ return false;
+ }
+
+ if(!asx_body) {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"ASX element is empty");
+ asx_free_attribs(asx_attribs);
+ asx_parser_free(parser);
+ return false;
+ }
+
+ buffer = asx_body;
+ while(buffer && buffer[0] != '\0') {
+ r = asx_get_element(parser,&buffer,&element,&body,&attribs);
+ if(r < 0) {
+ asx_warning_body_parse_error(parser,"ASX");
+ asx_parser_free(parser);
+ return false;
+ } else if (r == 0) { // No more element
+ break;
+ }
+ if(strcasecmp(element,"ENTRY") == 0) {
+ asx_parse_entry(parser,body,attribs);
+ } else if(strcasecmp(element,"ENTRYREF") == 0) {
+ asx_parse_entryref(parser,body,attribs);
+ } else if(strcasecmp(element,"REPEAT") == 0) {
+ asx_parse_repeat(parser,body,attribs);
+ } else
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Ignoring element %s\n",element);
+ free(body);
+ asx_free_attribs(attribs);
+ }
+
+ free(asx_body);
+ asx_free_attribs(asx_attribs);
+ asx_parser_free(parser);
+ return true;
+}
diff --git a/mpvcore/asxparser.h b/mpvcore/asxparser.h
new file mode 100644
index 0000000000..e49a2cedc0
--- /dev/null
+++ b/mpvcore/asxparser.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_ASXPARSER_H
+#define MPLAYER_ASXPARSER_H
+
+#include <stdbool.h>
+
+struct playlist;
+bool asx_parse(char* buffer, struct playlist *pl);
+
+#endif /* MPLAYER_ASXPARSER_H */
diff --git a/mpvcore/av_common.c b/mpvcore/av_common.c
new file mode 100644
index 0000000000..a4dc525aa9
--- /dev/null
+++ b/mpvcore/av_common.c
@@ -0,0 +1,123 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+
+#include <libavutil/common.h>
+#include <libavcodec/avcodec.h>
+
+#include "core/mp_talloc.h"
+#include "demux/demux_packet.h"
+#include "av_common.h"
+#include "codecs.h"
+
+
+// Copy the codec-related fields from st into avctx. This does not set the
+// codec itself, only codec related header data provided by libavformat.
+// The goal is to initialize a new decoder with the header data provided by
+// libavformat, and unlike avcodec_copy_context(), allow the user to create
+// a clean AVCodecContext for a manually selected AVCodec.
+// This is strictly for decoding only.
+void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
+{
+ if (st->extradata_size) {
+ av_free(avctx->extradata);
+ avctx->extradata_size = 0;
+ avctx->extradata =
+ av_mallocz(st->extradata_size + FF_INPUT_BUFFER_PADDING_SIZE);
+ if (avctx->extradata) {
+ avctx->extradata_size = st->extradata_size;
+ memcpy(avctx->extradata, st->extradata, st->extradata_size);
+ }
+ }
+ avctx->codec_tag = st->codec_tag;
+ avctx->stream_codec_tag = st->stream_codec_tag;
+ avctx->bit_rate = st->bit_rate;
+ avctx->width = st->width;
+ avctx->height = st->height;
+ avctx->pix_fmt = st->pix_fmt;
+ avctx->sample_aspect_ratio = st->sample_aspect_ratio;
+ avctx->chroma_sample_location = st->chroma_sample_location;
+ avctx->sample_rate = st->sample_rate;
+ avctx->channels = st->channels;
+ avctx->block_align = st->block_align;
+ avctx->channel_layout = st->channel_layout;
+ avctx->audio_service_type = st->audio_service_type;
+ avctx->bits_per_coded_sample = st->bits_per_coded_sample;
+}
+
+// Set dst from mpkt. Note that dst is not refcountable.
+// mpkt can be NULL to generate empty packets (used to flush delayed data).
+// Does not set pts or duration fields.
+void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt)
+{
+ av_init_packet(dst);
+ dst->data = mpkt ? mpkt->buffer : NULL;
+ dst->size = mpkt ? mpkt->len : 0;
+ /* Some codecs (ZeroCodec, some cases of PNG) may want keyframe info
+ * from demuxer. */
+ if (mpkt && mpkt->keyframe)
+ dst->flags |= AV_PKT_FLAG_KEY;
+ if (mpkt && mpkt->avpacket) {
+ dst->side_data = mpkt->avpacket->side_data;
+ dst->side_data_elems = mpkt->avpacket->side_data_elems;
+ }
+}
+
+void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
+{
+ AVCodec *cur = NULL;
+ for (;;) {
+ cur = av_codec_next(cur);
+ if (!cur)
+ break;
+ if (av_codec_is_decoder(cur) && cur->type == type) {
+ mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
+ cur->name, cur->long_name);
+ }
+ }
+}
+
+int mp_codec_to_av_codec_id(const char *codec)
+{
+ int id = AV_CODEC_ID_NONE;
+ if (codec) {
+ const AVCodecDescriptor *desc = avcodec_descriptor_get_by_name(codec);
+ if (desc)
+ id = desc->id;
+ if (id == AV_CODEC_ID_NONE) {
+ AVCodec *avcodec = avcodec_find_decoder_by_name(codec);
+ if (avcodec)
+ id = avcodec->id;
+ }
+ }
+ return id;
+}
+
+const char *mp_codec_from_av_codec_id(int codec_id)
+{
+ const char *name = NULL;
+ const AVCodecDescriptor *desc = avcodec_descriptor_get(codec_id);
+ if (desc)
+ name = desc->name;
+ if (!name) {
+ AVCodec *avcodec = avcodec_find_decoder(codec_id);
+ if (avcodec)
+ name = avcodec->name;
+ }
+ return name;
+}
diff --git a/mpvcore/av_common.h b/mpvcore/av_common.h
new file mode 100644
index 0000000000..2fa8f127b0
--- /dev/null
+++ b/mpvcore/av_common.h
@@ -0,0 +1,33 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_AVCOMMON_H
+#define MP_AVCOMMON_H
+
+#include <libavutil/avutil.h>
+#include <libavcodec/avcodec.h>
+
+struct mp_decoder_list;
+struct demux_packet;
+
+void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
+void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt);
+void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type);
+int mp_codec_to_av_codec_id(const char *codec);
+const char *mp_codec_from_av_codec_id(int codec_id);
+
+#endif
diff --git a/mpvcore/av_log.c b/mpvcore/av_log.c
new file mode 100644
index 0000000000..1331a1fb26
--- /dev/null
+++ b/mpvcore/av_log.c
@@ -0,0 +1,161 @@
+/*
+ * av_log to mp_msg converter
+ * Copyright (C) 2006 Michael Niedermayer
+ * Copyright (C) 2009 Uoti Urpala
+ *
+ * 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 "av_log.h"
+#include "config.h"
+#include "core/mp_msg.h"
+#include <libavutil/avutil.h>
+#include <libavutil/log.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+
+#ifdef CONFIG_LIBAVDEVICE
+#include <libavdevice/avdevice.h>
+#endif
+
+#ifdef CONFIG_LIBAVFILTER
+#include <libavfilter/avfilter.h>
+#endif
+
+static int av_log_level_to_mp_level(int av_level)
+{
+ if (av_level > AV_LOG_VERBOSE)
+ return MSGL_DBG2;
+ if (av_level > AV_LOG_INFO)
+ return MSGL_V;
+ if (av_level > AV_LOG_WARNING)
+ return MSGL_INFO;
+ if (av_level > AV_LOG_ERROR)
+ return MSGL_WARN;
+ if (av_level > AV_LOG_FATAL)
+ return MSGL_ERR;
+ return MSGL_FATAL;
+}
+
+static int extract_msg_type_from_ctx(void *ptr)
+{
+ if (!ptr)
+ return MSGT_FIXME;
+
+ AVClass *avc = *(AVClass **)ptr;
+ if (!avc) {
+ mp_msg(MSGT_FIXME, MSGL_WARN,
+ "av_log callback called with bad parameters (NULL AVClass).\n"
+ "This is a bug in one of Libav/FFmpeg libraries used.\n");
+ return MSGT_FIXME;
+ }
+
+ if (!strcmp(avc->class_name, "AVCodecContext")) {
+ AVCodecContext *s = ptr;
+ if (s->codec) {
+ if (s->codec->type == AVMEDIA_TYPE_AUDIO) {
+ if (s->codec->decode)
+ return MSGT_DECAUDIO;
+ } else if (s->codec->type == AVMEDIA_TYPE_VIDEO) {
+ if (s->codec->decode)
+ return MSGT_DECVIDEO;
+ }
+ // FIXME subtitles, encoders
+ // What msgt for them? There is nothing appropriate...
+ }
+ return MSGT_FIXME;
+ }
+
+ if (!strcmp(avc->class_name, "AVFormatContext")) {
+ AVFormatContext *s = ptr;
+ if (s->iformat)
+ return MSGT_DEMUXER;
+ else if (s->oformat)
+ return MSGT_MUXER;
+ return MSGT_FIXME;
+ }
+
+ return MSGT_FIXME;
+}
+
+#if LIBAVCODEC_VERSION_MICRO >= 100
+#define LIB_PREFIX "ffmpeg"
+#else
+#define LIB_PREFIX "libav"
+#endif
+
+static bool print_prefix = true;
+
+static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt,
+ va_list vl)
+{
+ AVClass *avc = ptr ? *(AVClass **)ptr : NULL;
+ int mp_level = av_log_level_to_mp_level(level);
+ int type = extract_msg_type_from_ctx(ptr);
+
+ if (!mp_msg_test(type, mp_level))
+ return;
+
+ if (print_prefix) {
+ mp_msg(type, mp_level, "[%s/%s] ", LIB_PREFIX,
+ avc ? avc->item_name(ptr) : "?");
+ }
+ print_prefix = fmt[strlen(fmt) - 1] == '\n';
+
+ mp_msg_va(type, mp_level, fmt, vl);
+}
+
+void init_libav(void)
+{
+ av_log_set_callback(mp_msg_av_log_callback);
+ avcodec_register_all();
+ av_register_all();
+ avformat_network_init();
+
+#ifdef CONFIG_LIBAVFILTER
+ avfilter_register_all();
+#endif
+#ifdef CONFIG_LIBAVDEVICE
+ avdevice_register_all();
+#endif
+}
+
+#define V(x) (x)>>16, (x)>>8 & 255, (x) & 255
+static void print_version(char *name, unsigned buildv, unsigned runv)
+{
+
+ if (buildv == runv)
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Compiled against %s version %d.%d.%d\n",
+ name, V(buildv));
+ else
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Compiled against %s version %d.%d.%d "
+ "(runtime %d.%d.%d)\n", name, V(buildv), V(runv));
+}
+#undef V
+
+void print_libav_versions(void)
+{
+ print_version("libavutil", LIBAVUTIL_VERSION_INT, avutil_version());
+ print_version("libavcodec", LIBAVCODEC_VERSION_INT, avcodec_version());
+ print_version("libavformat", LIBAVFORMAT_VERSION_INT, avformat_version());
+ print_version("libswscale", LIBSWSCALE_VERSION_INT, swscale_version());
+}
diff --git a/mpvcore/av_log.h b/mpvcore/av_log.h
new file mode 100644
index 0000000000..833a7af03b
--- /dev/null
+++ b/mpvcore/av_log.h
@@ -0,0 +1,2 @@
+void init_libav(void);
+void print_libav_versions(void);
diff --git a/mpvcore/av_opts.c b/mpvcore/av_opts.c
new file mode 100644
index 0000000000..777a1eec5a
--- /dev/null
+++ b/mpvcore/av_opts.c
@@ -0,0 +1,55 @@
+/*
+ * AVOption parsing helper
+ * Copyright (C) 2008 Michael Niedermayer
+ *
+ * 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 <string.h>
+
+#include <libavutil/opt.h>
+
+#include "av_opts.h"
+
+int parse_avopts(void *v, char *str){
+ char *start;
+
+ if (!str)
+ return 0;
+
+ start= str= strdup(str);
+
+ while(str && *str){
+ char *next_opt, *arg;
+
+ next_opt= strchr(str, ',');
+ if(next_opt) *next_opt++= 0;
+
+ arg = strchr(str, '=');
+ if(arg) *arg++= 0;
+
+ if (av_opt_set(v, str, arg, AV_OPT_SEARCH_CHILDREN) < 0) {
+ free(start);
+ return -1;
+ }
+ str= next_opt;
+ }
+
+ free(start);
+ return 0;
+}
diff --git a/mpvcore/av_opts.h b/mpvcore/av_opts.h
new file mode 100644
index 0000000000..640443a352
--- /dev/null
+++ b/mpvcore/av_opts.h
@@ -0,0 +1,30 @@
+/*
+ * AVOption parsing helper
+ * Copyright (C) 2008 Michael Niedermayer
+ *
+ * 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.
+ */
+
+#ifndef MPLAYER_AV_OPTS_H
+#define MPLAYER_AV_OPTS_H
+
+/**
+ * Parses str and sets AVOptions in v accordingly.
+ */
+int parse_avopts(void *v, char *str);
+
+#endif /* MPLAYER_AV_OPTS_H */
diff --git a/mpvcore/bstr.c b/mpvcore/bstr.c
new file mode 100644
index 0000000000..16da0993ea
--- /dev/null
+++ b/mpvcore/bstr.c
@@ -0,0 +1,314 @@
+/*
+ * 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 <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+#include <libavutil/common.h>
+
+#include "talloc.h"
+
+#include "core/bstr.h"
+
+int bstrcmp(struct bstr str1, struct bstr str2)
+{
+ int ret = memcmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
+
+ if (!ret) {
+ if (str1.len == str2.len)
+ return 0;
+ else if (str1.len > str2.len)
+ return 1;
+ else
+ return -1;
+ }
+ return ret;
+}
+
+int bstrcasecmp(struct bstr str1, struct bstr str2)
+{
+ int ret = strncasecmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
+
+ if (!ret) {
+ if (str1.len == str2.len)
+ return 0;
+ else if (str1.len > str2.len)
+ return 1;
+ else
+ return -1;
+ }
+ return ret;
+}
+
+int bstrchr(struct bstr str, int c)
+{
+ for (int i = 0; i < str.len; i++)
+ if (str.start[i] == c)
+ return i;
+ return -1;
+}
+
+int bstrrchr(struct bstr str, int c)
+{
+ for (int i = str.len - 1; i >= 0; i--)
+ if (str.start[i] == c)
+ return i;
+ return -1;
+}
+
+int bstrcspn(struct bstr str, const char *reject)
+{
+ int i;
+ for (i = 0; i < str.len; i++)
+ if (strchr(reject, str.start[i]))
+ break;
+ return i;
+}
+
+int bstrspn(struct bstr str, const char *accept)
+{
+ int i;
+ for (i = 0; i < str.len; i++)
+ if (!strchr(accept, str.start[i]))
+ break;
+ return i;
+}
+
+int bstr_find(struct bstr haystack, struct bstr needle)
+{
+ for (int i = 0; i < haystack.len; i++)
+ if (bstr_startswith(bstr_splice(haystack, i, haystack.len), needle))
+ return i;
+ return -1;
+}
+
+struct bstr bstr_lstrip(struct bstr str)
+{
+ while (str.len && isspace(*str.start)) {
+ str.start++;
+ str.len--;
+ }
+ return str;
+}
+
+struct bstr bstr_strip(struct bstr str)
+{
+ str = bstr_lstrip(str);
+ while (str.len && isspace(str.start[str.len - 1]))
+ str.len--;
+ return str;
+}
+
+struct bstr bstr_split(struct bstr str, const char *sep, struct bstr *rest)
+{
+ int start;
+ for (start = 0; start < str.len; start++)
+ if (!strchr(sep, str.start[start]))
+ break;
+ str = bstr_cut(str, start);
+ int end = bstrcspn(str, sep);
+ if (rest) {
+ *rest = bstr_cut(str, end);
+ }
+ return bstr_splice(str, 0, end);
+}
+
+// Unlike with bstr_split(), tok is a string, and not a set of char.
+// If tok is in str, return true, and: concat(out_left, tok, out_right) == str
+// Otherwise, return false, and set out_left==str, out_right==""
+bool bstr_split_tok(bstr str, const char *tok, bstr *out_left, bstr *out_right)
+{
+ bstr bsep = bstr0(tok);
+ int pos = bstr_find(str, bsep);
+ if (pos < 0)
+ pos = str.len;
+ *out_left = bstr_splice(str, 0, pos);
+ *out_right = bstr_cut(str, pos + bsep.len);
+ return pos != str.len;
+}
+
+struct bstr bstr_splice(struct bstr str, int start, int end)
+{
+ if (start < 0)
+ start += str.len;
+ if (end < 0)
+ end += str.len;
+ end = FFMIN(end, str.len);
+ start = FFMAX(start, 0);
+ end = FFMAX(end, start);
+ str.start += start;
+ str.len = end - start;
+ return str;
+}
+
+long long bstrtoll(struct bstr str, struct bstr *rest, int base)
+{
+ str = bstr_lstrip(str);
+ char buf[51];
+ int len = FFMIN(str.len, 50);
+ memcpy(buf, str.start, len);
+ buf[len] = 0;
+ char *endptr;
+ long long r = strtoll(buf, &endptr, base);
+ if (rest)
+ *rest = bstr_cut(str, endptr - buf);
+ return r;
+}
+
+double bstrtod(struct bstr str, struct bstr *rest)
+{
+ str = bstr_lstrip(str);
+ char buf[101];
+ int len = FFMIN(str.len, 100);
+ memcpy(buf, str.start, len);
+ buf[len] = 0;
+ char *endptr;
+ double r = strtod(buf, &endptr);
+ if (rest)
+ *rest = bstr_cut(str, endptr - buf);
+ return r;
+}
+
+struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str)
+{
+ if (str.len == 0)
+ return NULL;
+ int count = 0;
+ for (int i = 0; i < str.len; i++)
+ if (str.start[i] == '\n')
+ count++;
+ if (str.start[str.len - 1] != '\n')
+ count++;
+ struct bstr *r = talloc_array_ptrtype(talloc_ctx, r, count);
+ unsigned char *p = str.start;
+ for (int i = 0; i < count - 1; i++) {
+ r[i].start = p;
+ while (*p++ != '\n');
+ r[i].len = p - r[i].start;
+ }
+ r[count - 1].start = p;
+ r[count - 1].len = str.start + str.len - p;
+ return r;
+}
+
+struct bstr bstr_getline(struct bstr str, struct bstr *rest)
+{
+ int pos = bstrchr(str, '\n');
+ if (pos < 0)
+ pos = str.len;
+ if (rest)
+ *rest = bstr_cut(str, pos + 1);
+ return bstr_splice(str, 0, pos + 1);
+}
+
+struct bstr bstr_strip_linebreaks(struct bstr str)
+{
+ if (bstr_endswith0(str, "\r\n")) {
+ str = bstr_splice(str, 0, str.len - 2);
+ } else if (bstr_endswith0(str, "\n")) {
+ str = bstr_splice(str, 0, str.len - 1);
+ }
+ return str;
+}
+
+bool bstr_eatstart(struct bstr *s, struct bstr prefix)
+{
+ if (!bstr_startswith(*s, prefix))
+ return false;
+ *s = bstr_cut(*s, prefix.len);
+ return true;
+}
+
+void bstr_lower(struct bstr str)
+{
+ for (int i = 0; i < str.len; i++)
+ str.start[i] = tolower(str.start[i]);
+}
+
+int bstr_sscanf(struct bstr str, const char *format, ...)
+{
+ char *ptr = bstrdup0(NULL, str);
+ va_list va;
+ va_start(va, format);
+ int ret = vsscanf(ptr, format, va);
+ va_end(va);
+ talloc_free(ptr);
+ return ret;
+}
+
+int bstr_parse_utf8_code_length(unsigned char b)
+{
+ if (b < 128)
+ return 1;
+ int bytes = 7 - av_log2(b ^ 255);
+ return (bytes >= 2 && bytes <= 4) ? bytes : -1;
+}
+
+int bstr_decode_utf8(struct bstr s, struct bstr *out_next)
+{
+ if (s.len == 0)
+ return -1;
+ unsigned int codepoint = s.start[0];
+ s.start++; s.len--;
+ if (codepoint >= 128) {
+ int bytes = bstr_parse_utf8_code_length(codepoint);
+ if (bytes < 0 || s.len < bytes - 1)
+ return -1;
+ codepoint &= 127 >> bytes;
+ for (int n = 1; n < bytes; n++) {
+ int tmp = s.start[0];
+ if ((tmp & 0xC0) != 0x80)
+ return -1;
+ codepoint = (codepoint << 6) | (tmp & ~0xC0);
+ s.start++; s.len--;
+ }
+ }
+ if (out_next)
+ *out_next = s;
+ return codepoint;
+}
+
+bool bstr_case_startswith(struct bstr s, struct bstr prefix)
+{
+ struct bstr start = bstr_splice(s, 0, prefix.len);
+ return start.len == prefix.len && bstrcasecmp(start, prefix) == 0;
+}
+
+bool bstr_case_endswith(struct bstr s, struct bstr suffix)
+{
+ struct bstr end = bstr_cut(s, -suffix.len);
+ return end.len == suffix.len && bstrcasecmp(end, suffix) == 0;
+}
+
+struct bstr bstr_strip_ext(struct bstr str)
+{
+ int dotpos = bstrrchr(str, '.');
+ if (dotpos < 0)
+ return str;
+ return (struct bstr){str.start, dotpos};
+}
+
+struct bstr bstr_get_ext(struct bstr s)
+{
+ int dotpos = bstrrchr(s, '.');
+ if (dotpos < 0)
+ return (struct bstr){NULL, 0};
+ return bstr_splice(s, dotpos + 1, s.len);
+}
diff --git a/mpvcore/bstr.h b/mpvcore/bstr.h
new file mode 100644
index 0000000000..ce9e029ea5
--- /dev/null
+++ b/mpvcore/bstr.h
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_BSTR_H
+#define MPLAYER_BSTR_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "talloc.h"
+
+/* NOTE: 'len' is size_t, but most string-handling functions below assume
+ * that input size has been sanity checked and len fits in an int.
+ */
+typedef struct bstr {
+ unsigned char *start;
+ size_t len;
+} bstr;
+
+// If str.start is NULL, return NULL.
+static inline char *bstrdup0(void *talloc_ctx, struct bstr str)
+{
+ return talloc_strndup(talloc_ctx, (char *)str.start, str.len);
+}
+
+// Like bstrdup0(), but always return a valid C-string.
+static inline char *bstrto0(void *talloc_ctx, struct bstr str)
+{
+ return str.start ? bstrdup0(talloc_ctx, str) : talloc_strdup(talloc_ctx, "");
+}
+
+// Return start = NULL iff that is true for the original.
+static inline struct bstr bstrdup(void *talloc_ctx, struct bstr str)
+{
+ struct bstr r = { NULL, str.len };
+ if (str.start)
+ r.start = (unsigned char *)talloc_memdup(talloc_ctx, str.start, str.len);
+ return r;
+}
+
+static inline struct bstr bstr0(const char *s)
+{
+ return (struct bstr){(unsigned char *)s, s ? strlen(s) : 0};
+}
+
+int bstrcmp(struct bstr str1, struct bstr str2);
+int bstrcasecmp(struct bstr str1, struct bstr str2);
+int bstrchr(struct bstr str, int c);
+int bstrrchr(struct bstr str, int c);
+int bstrspn(struct bstr str, const char *accept);
+int bstrcspn(struct bstr str, const char *reject);
+
+int bstr_find(struct bstr haystack, struct bstr needle);
+struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str);
+struct bstr bstr_lstrip(struct bstr str);
+struct bstr bstr_strip(struct bstr str);
+struct bstr bstr_split(struct bstr str, const char *sep, struct bstr *rest);
+bool bstr_split_tok(bstr str, const char *tok, bstr *out_left, bstr *out_right);
+struct bstr bstr_splice(struct bstr str, int start, int end);
+long long bstrtoll(struct bstr str, struct bstr *rest, int base);
+double bstrtod(struct bstr str, struct bstr *rest);
+void bstr_lower(struct bstr str);
+int bstr_sscanf(struct bstr str, const char *format, ...);
+
+// Decode the UTF-8 code point at the start of the string,, and return the
+// character.
+// After calling this function, *out_next will point to the next character.
+// out_next can be NULL.
+// On error, -1 is returned, and *out_next is not modified.
+int bstr_decode_utf8(struct bstr str, struct bstr *out_next);
+
+// Return the length of the UTF-8 sequence that starts with the given byte.
+// Given a string char *s, the next UTF-8 code point is to be expected at
+// s + bstr_parse_utf8_code_length(s[0])
+// On error, -1 is returned. On success, it returns a value in the range [1, 4].
+int bstr_parse_utf8_code_length(unsigned char b);
+
+// Return the text before the next line break, and return it. Change *rest to
+// point to the text following this line break. (rest can be NULL.)
+// Line break characters are not stripped.
+struct bstr bstr_getline(struct bstr str, struct bstr *rest);
+
+// Strip one trailing line break. This is intended for use with bstr_getline,
+// and will remove the trailing \n or \r\n sequence.
+struct bstr bstr_strip_linebreaks(struct bstr str);
+
+// If s starts with prefix, return true and return the rest of the string in s.
+bool bstr_eatstart(struct bstr *s, struct bstr prefix);
+
+bool bstr_case_startswith(struct bstr s, struct bstr prefix);
+bool bstr_case_endswith(struct bstr s, struct bstr suffix);
+struct bstr bstr_strip_ext(struct bstr str);
+struct bstr bstr_get_ext(struct bstr s);
+
+static inline struct bstr bstr_cut(struct bstr str, int n)
+{
+ if (n < 0) {
+ n += str.len;
+ if (n < 0)
+ n = 0;
+ }
+ if (((size_t)n) > str.len)
+ n = str.len;
+ return (struct bstr){str.start + n, str.len - n};
+}
+
+static inline bool bstr_startswith(struct bstr str, struct bstr prefix)
+{
+ if (str.len < prefix.len)
+ return false;
+ return !memcmp(str.start, prefix.start, prefix.len);
+}
+
+static inline bool bstr_startswith0(struct bstr str, const char *prefix)
+{
+ return bstr_startswith(str, bstr0(prefix));
+}
+
+static inline bool bstr_endswith(struct bstr str, struct bstr suffix)
+{
+ if (str.len < suffix.len)
+ return false;
+ return !memcmp(str.start + str.len - suffix.len, suffix.start, suffix.len);
+}
+
+static inline bool bstr_endswith0(struct bstr str, const char *suffix)
+{
+ return bstr_endswith(str, bstr0(suffix));
+}
+
+static inline int bstrcmp0(struct bstr str1, const char *str2)
+{
+ return bstrcmp(str1, bstr0(str2));
+}
+
+static inline bool bstr_equals(struct bstr str1, struct bstr str2)
+{
+ return bstrcmp(str1, str2) == 0;
+}
+
+static inline bool bstr_equals0(struct bstr str1, const char *str2)
+{
+ return bstrcmp(str1, bstr0(str2)) == 0;
+}
+
+static inline int bstrcasecmp0(struct bstr str1, const char *str2)
+{
+ return bstrcasecmp(str1, bstr0(str2));
+}
+
+static inline int bstr_find0(struct bstr haystack, const char *needle)
+{
+ return bstr_find(haystack, bstr0(needle));
+}
+
+static inline int bstr_eatstart0(struct bstr *s, const char *prefix)
+{
+ return bstr_eatstart(s, bstr0(prefix));
+}
+
+// create a pair (not single value!) for "%.*s" printf syntax
+#define BSTR_P(bstr) (int)((bstr).len), (bstr).start
+
+#define WHITESPACE " \f\n\r\t\v"
+
+#endif /* MPLAYER_BSTR_H */
diff --git a/mpvcore/charset_conv.c b/mpvcore/charset_conv.c
new file mode 100644
index 0000000000..680c8f83f9
--- /dev/null
+++ b/mpvcore/charset_conv.c
@@ -0,0 +1,266 @@
+/*
+ * This file is part of mpv.
+ *
+ * Based on code taken from libass (ISC license), which was originally part
+ * of MPlayer (GPL).
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "config.h"
+
+#include "core/mp_msg.h"
+
+#ifdef CONFIG_ENCA
+#include <enca.h>
+#endif
+
+#ifdef CONFIG_LIBGUESS
+#include <libguess.h>
+#endif
+
+#ifdef CONFIG_ICONV
+#include <iconv.h>
+#endif
+
+#include "charset_conv.h"
+
+// Split the string on ':' into components.
+// out_arr is at least max entries long.
+// Return number of out_arr entries filled.
+static int split_colon(const char *user_cp, int max, bstr *out_arr)
+{
+ if (!user_cp || max < 1)
+ return 0;
+
+ int count = 0;
+ while (1) {
+ const char *next = strchr(user_cp, ':');
+ if (next && max - count > 1) {
+ out_arr[count++] = (bstr){(char *)user_cp, next - user_cp};
+ user_cp = next + 1;
+ } else {
+ out_arr[count++] = (bstr){(char *)user_cp, strlen(user_cp)};
+ break;
+ }
+ }
+ return count;
+}
+
+// Returns true if user_cp implies that calling mp_charset_guess() on the
+// input data is required to determine the real codepage. This is the case
+// if user_cp is not a real iconv codepage, but a magic value that requests
+// for example ENCA charset auto-detection.
+bool mp_charset_requires_guess(const char *user_cp)
+{
+ bstr res[2] = {{0}};
+ split_colon(user_cp, 2, res);
+ return bstrcasecmp0(res[0], "enca") == 0 ||
+ bstrcasecmp0(res[0], "guess") == 0;
+}
+
+#ifdef CONFIG_ENCA
+static const char *enca_guess(bstr buf, const char *language)
+{
+ if (!language || !language[0])
+ language = "__"; // neutral language
+
+ const char *detected_cp = NULL;
+
+ EncaAnalyser analyser = enca_analyser_alloc(language);
+ if (analyser) {
+ enca_set_termination_strictness(analyser, 0);
+ EncaEncoding enc = enca_analyse_const(analyser, buf.start, buf.len);
+ const char *tmp = enca_charset_name(enc.charset, ENCA_NAME_STYLE_ICONV);
+ if (tmp && enc.charset != ENCA_CS_UNKNOWN)
+ detected_cp = tmp;
+ enca_analyser_free(analyser);
+ } else {
+ mp_msg(MSGT_SUBREADER, MSGL_ERR, "ENCA doesn't know language '%s'\n",
+ language);
+ size_t langcnt;
+ const char **languages = enca_get_languages(&langcnt);
+ mp_msg(MSGT_SUBREADER, MSGL_ERR, "ENCA supported languages:");
+ for (int i = 0; i < langcnt; i++)
+ mp_msg(MSGT_SUBREADER, MSGL_ERR, " %s", languages[i]);
+ mp_msg(MSGT_SUBREADER, MSGL_ERR, "\n");
+ free(languages);
+ }
+
+ return detected_cp;
+}
+#endif
+
+#ifdef CONFIG_LIBGUESS
+static const char *libguess_guess(bstr buf, const char *language)
+{
+ if (libguess_validate_utf8(buf.start, buf.len))
+ return "UTF-8";
+
+ if (!language || !language[0] || strcmp(language, "help") == 0) {
+ mp_msg(MSGT_SUBREADER, MSGL_ERR, "libguess needs a language: "
+ "japanese taiwanese chinese korean russian arabic turkish "
+ "greek hebrew polish baltic\n");
+ return NULL;
+ }
+
+ return libguess_determine_encoding(buf.start, buf.len, language);
+}
+#endif
+
+// Runs charset auto-detection on the input buffer, and returns the result.
+// If auto-detection fails, NULL is returned.
+// If user_cp doesn't refer to any known auto-detection (for example because
+// it's a real iconv codepage), user_cp is returned without even looking at
+// the buf data.
+const char *mp_charset_guess(bstr buf, const char *user_cp)
+{
+ if (!mp_charset_requires_guess(user_cp))
+ return user_cp;
+
+ bstr params[3] = {{0}};
+ split_colon(user_cp, 3, params);
+
+ bstr type = params[0];
+ char lang[100];
+ snprintf(lang, sizeof(lang), "%.*s", BSTR_P(params[1]));
+ const char *fallback = params[2].start; // last item, already 0-terminated
+
+ const char *res = NULL;
+
+#ifdef CONFIG_ENCA
+ if (bstrcasecmp0(type, "enca") == 0)
+ res = enca_guess(buf, lang);
+#endif
+#ifdef CONFIG_LIBGUESS
+ if (bstrcasecmp0(type, "guess") == 0)
+ res = libguess_guess(buf, lang);
+#endif
+
+ if (res) {
+ mp_msg(MSGT_SUBREADER, MSGL_DBG2, "%.*s detected charset: '%s'\n",
+ BSTR_P(type), res);
+ } else {
+ res = fallback;
+ mp_msg(MSGT_SUBREADER, MSGL_DBG2,
+ "Detection with %.*s failed: fallback to %s\n",
+ BSTR_P(type), res && res[0] ? res : "no conversion");
+ }
+
+ return res;
+}
+
+// Convert the data in buf to UTF-8. The charset argument can be an iconv
+// codepage, a value returned by mp_charset_conv_guess(), or a special value
+// that triggers autodetection of the charset (e.g. using ENCA).
+// The auto-detection is the only difference to mp_iconv_to_utf8().
+// buf: same as mp_iconv_to_utf8()
+// user_cp: iconv codepage, special value, NULL
+// flags: same as mp_iconv_to_utf8()
+// returns: same as mp_iconv_to_utf8()
+bstr mp_charset_guess_and_conv_to_utf8(bstr buf, const char *user_cp, int flags)
+{
+ return mp_iconv_to_utf8(buf, mp_charset_guess(buf, user_cp), flags);
+}
+
+// Use iconv to convert buf to UTF-8.
+// Returns buf.start==NULL on error. Returns buf if cp is NULL, or if there is
+// obviously no conversion required (e.g. if cp is "UTF-8").
+// Returns a newly allocated buffer if conversion is done and succeeds. The
+// buffer will be terminated with 0 for convenience (the terminating 0 is not
+// included in the returned length).
+// Free the returned buffer with talloc_free().
+// buf: input data
+// cp: iconv codepage (or NULL)
+// flags: combination of MP_ICONV_* flags
+// returns: buf (no conversion), .start==NULL (error), or allocated buffer
+bstr mp_iconv_to_utf8(bstr buf, const char *cp, int flags)
+{
+#ifdef CONFIG_ICONV
+ const char *tocp = "UTF-8";
+
+ if (!cp || !cp[0] || strcasecmp(cp, tocp) == 0)
+ return buf;
+
+ if (strcasecmp(cp, "ASCII") == 0)
+ return buf;
+
+ iconv_t icdsc;
+ if ((icdsc = iconv_open(tocp, cp)) == (iconv_t) (-1)) {
+ if (flags & MP_ICONV_VERBOSE)
+ mp_msg(MSGT_SUBREADER, MSGL_ERR,
+ "Error opening iconv with codepage '%s'\n", cp);
+ goto failure;
+ }
+
+ size_t size = buf.len;
+ size_t osize = size;
+ size_t ileft = size;
+ size_t oleft = size - 1;
+
+ char *outbuf = talloc_size(NULL, osize);
+ char *ip = buf.start;
+ char *op = outbuf;
+
+ while (1) {
+ int clear = 0;
+ size_t rc;
+ if (ileft)
+ rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
+ else {
+ clear = 1; // clear the conversion state and leave
+ rc = iconv(icdsc, NULL, NULL, &op, &oleft);
+ }
+ if (rc == (size_t) (-1)) {
+ if (errno == E2BIG) {
+ size_t offset = op - outbuf;
+ outbuf = talloc_realloc_size(NULL, outbuf, osize + size);
+ op = outbuf + offset;
+ osize += size;
+ oleft += size;
+ } else {
+ if (errno == EINVAL && (flags & MP_ICONV_ALLOW_CUTOFF)) {
+ // This is intended for cases where the input buffer is cut
+ // at a random byte position. If this happens in the middle
+ // of the buffer, it should still be an error. We say it's
+ // fine if the error is within 10 bytes of the end.
+ if (ileft <= 10)
+ break;
+ }
+ if (flags & MP_ICONV_VERBOSE) {
+ mp_msg(MSGT_SUBREADER, MSGL_ERR,
+ "Error recoding text with codepage '%s'\n", cp);
+ }
+ talloc_free(outbuf);
+ iconv_close(icdsc);
+ goto failure;
+ }
+ } else if (clear)
+ break;
+ }
+
+ iconv_close(icdsc);
+
+ outbuf[osize - oleft - 1] = 0;
+ return (bstr){outbuf, osize - oleft - 1};
+#endif
+
+failure:
+ return (bstr){0};
+}
diff --git a/mpvcore/charset_conv.h b/mpvcore/charset_conv.h
new file mode 100644
index 0000000000..00a2658da3
--- /dev/null
+++ b/mpvcore/charset_conv.h
@@ -0,0 +1,17 @@
+#ifndef MP_CHARSET_CONV_H
+#define MP_CHARSET_CONV_H
+
+#include <stdbool.h>
+#include "core/bstr.h"
+
+enum {
+ MP_ICONV_VERBOSE = 1, // print errors instead of failing silently
+ MP_ICONV_ALLOW_CUTOFF = 2, // allow partial input data
+};
+
+bool mp_charset_requires_guess(const char *user_cp);
+const char *mp_charset_guess(bstr buf, const char *user_cp);
+bstr mp_charset_guess_and_conv_to_utf8(bstr buf, const char *user_cp, int flags);
+bstr mp_iconv_to_utf8(bstr buf, const char *cp, int flags);
+
+#endif
diff --git a/mpvcore/codecs.c b/mpvcore/codecs.c
new file mode 100644
index 0000000000..943860a70b
--- /dev/null
+++ b/mpvcore/codecs.c
@@ -0,0 +1,147 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include "core/mp_talloc.h"
+#include "core/bstr.h"
+#include "core/mp_msg.h"
+#include "codecs.h"
+
+void mp_add_decoder(struct mp_decoder_list *list, const char *family,
+ const char *codec, const char *decoder, const char *desc)
+{
+ struct mp_decoder_entry entry = {
+ .family = talloc_strdup(list, family),
+ .codec = talloc_strdup(list, codec),
+ .decoder = talloc_strdup(list, decoder),
+ .desc = talloc_strdup(list, desc),
+ };
+ MP_TARRAY_APPEND(list, list->entries, list->num_entries, entry);
+}
+
+static void mp_add_decoder_entry(struct mp_decoder_list *list,
+ struct mp_decoder_entry *entry)
+{
+ mp_add_decoder(list, entry->family, entry->codec, entry->decoder,
+ entry->desc);
+}
+
+static struct mp_decoder_entry *find_decoder(struct mp_decoder_list *list,
+ bstr family, bstr decoder)
+{
+ for (int n = 0; n < list->num_entries; n++) {
+ struct mp_decoder_entry *cur = &list->entries[n];
+ if (bstr_equals0(decoder, cur->decoder) &&
+ bstr_equals0(family, cur->family))
+ return cur;
+ }
+ return NULL;
+}
+
+// Add entry, but only if it's not yet on the list, and if the codec matches.
+// If codec == NULL, don't compare codecs.
+static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
+ const char *codec)
+{
+ if (!entry || (codec && strcmp(entry->codec, codec) != 0))
+ return;
+ if (!find_decoder(to, bstr0(entry->family), bstr0(entry->decoder)))
+ mp_add_decoder_entry(to, entry);
+}
+
+// Select a decoder from the given list for the given codec. The selection
+// can be influenced by the selection string, which can specify a priority
+// list of preferred decoders.
+// This returns a list of decoders to try, with the preferred decoders first.
+// The selection string corresponds to --vd/--ad directly, and has the
+// following syntax:
+// selection = [<entry> ("," <entry>)*]
+// entry = <family> ":" <decoder> // prefer decoder
+// entry = <family> ":*" // prefer all decoders
+// entry = "+" <family> ":" <decoder> // force a decoder
+// entry = "-" <family> ":" <decoder> // exclude a decoder
+// entry = "-" // don't add fallback decoders
+// Forcing a decoder means it's added even if the codec mismatches.
+struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
+ const char *codec,
+ const char *selection)
+{
+ struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
+ struct mp_decoder_list *remove = talloc_zero(NULL, struct mp_decoder_list);
+ if (!codec)
+ codec = "unknown";
+ bool stop = false;
+ bstr sel = bstr0(selection);
+ while (sel.len) {
+ bstr entry;
+ bstr_split_tok(sel, ",", &entry, &sel);
+ if (bstr_equals0(entry, "-")) {
+ stop = true;
+ break;
+ }
+ bool force = bstr_eatstart0(&entry, "+");
+ bool exclude = !force && bstr_eatstart0(&entry, "-");
+ struct mp_decoder_list *dest = exclude ? remove : list;
+ bstr family, decoder;
+ if (!bstr_split_tok(entry, ":", &family, &decoder)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Decoders must be specified as "
+ "'family:decoder' for the --ad/--vd options.\n");
+ break;
+ }
+ if (bstr_equals0(decoder, "*")) {
+ for (int n = 0; n < all->num_entries; n++) {
+ struct mp_decoder_entry *cur = &all->entries[n];
+ if (bstr_equals0(family, cur->family))
+ add_new(dest, cur, codec);
+ }
+ } else {
+ add_new(dest, find_decoder(all, family, decoder),
+ force ? NULL : codec);
+ }
+ }
+ if (!stop) {
+ // Add the remaining codecs which haven't been added yet
+ for (int n = 0; n < all->num_entries; n++)
+ add_new(list, &all->entries[n], codec);
+ }
+ for (int n = 0; n < remove->num_entries; n++) {
+ struct mp_decoder_entry *ex = &remove->entries[n];
+ struct mp_decoder_entry *del =
+ find_decoder(list, bstr0(ex->family), bstr0(ex->decoder));
+ if (del) {
+ int index = del - &list->entries[0];
+ MP_TARRAY_REMOVE_AT(list->entries, list->num_entries, index);
+ }
+ }
+ talloc_free(remove);
+ return list;
+}
+
+void mp_print_decoders(int msgt, int msgl, const char *header,
+ struct mp_decoder_list *list)
+{
+ mp_msg(msgt, msgl, "%s\n", header);
+ for (int n = 0; n < list->num_entries; n++) {
+ struct mp_decoder_entry *entry = &list->entries[n];
+ mp_msg(msgt, msgl, " %s:%s", entry->family, entry->decoder);
+ if (strcmp(entry->decoder, entry->codec) != 0)
+ mp_msg(msgt, msgl, " (%s)", entry->codec);
+ mp_msg(msgt, msgl, " - %s\n", entry->desc);
+ }
+ if (list->num_entries == 0)
+ mp_msg(msgt, msgl, " (no decoders)\n");
+}
diff --git a/mpvcore/codecs.h b/mpvcore/codecs.h
new file mode 100644
index 0000000000..21ff284617
--- /dev/null
+++ b/mpvcore/codecs.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_CODECS_H
+#define MP_CODECS_H
+
+struct mp_decoder_entry {
+ const char *family; // decoder module (e.g. ad_lavc => "lavc")
+ const char *codec; // name of the codec (e.g. "mp3")
+ const char *decoder; // decoder name (e.g. "mp3float")
+ const char *desc; // human readable description
+};
+
+struct mp_decoder_list {
+ struct mp_decoder_entry *entries;
+ int num_entries;
+};
+
+void mp_add_decoder(struct mp_decoder_list *list, const char *family,
+ const char *codec, const char *decoder, const char *desc);
+
+struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
+ const char *codec,
+ const char *selection);
+
+void mp_print_decoders(int msgt, int msgl, const char *header,
+ struct mp_decoder_list *list);
+
+#endif
diff --git a/mpvcore/command.c b/mpvcore/command.c
new file mode 100644
index 0000000000..b7718e41b7
--- /dev/null
+++ b/mpvcore/command.c
@@ -0,0 +1,2673 @@
+/*
+ * 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 <inttypes.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <time.h>
+
+#include <libavutil/avstring.h>
+#include <libavutil/common.h>
+
+#include "config.h"
+#include "talloc.h"
+#include "command.h"
+#include "input/input.h"
+#include "stream/stream.h"
+#include "demux/demux.h"
+#include "demux/stheader.h"
+#include "resolve.h"
+#include "playlist.h"
+#include "playlist_parser.h"
+#include "sub/sub.h"
+#include "sub/dec_sub.h"
+#include "core/m_option.h"
+#include "m_property.h"
+#include "m_config.h"
+#include "video/filter/vf.h"
+#include "video/decode/vd.h"
+#include "mp_osd.h"
+#include "video/out/vo.h"
+#include "video/csputils.h"
+#include "playlist.h"
+#include "audio/mixer.h"
+#include "audio/out/ao.h"
+#include "core/mp_common.h"
+#include "audio/filter/af.h"
+#include "video/decode/dec_video.h"
+#include "audio/decode/dec_audio.h"
+#include "core/path.h"
+#include "stream/tv.h"
+#include "stream/stream_radio.h"
+#include "stream/pvr.h"
+#ifdef CONFIG_DVBIN
+#include "stream/dvbin.h"
+#endif
+#ifdef CONFIG_DVDREAD
+#include "stream/stream_dvd.h"
+#endif
+#include "screenshot.h"
+
+#include "core/mp_core.h"
+
+static void change_video_filters(MPContext *mpctx, const char *cmd,
+ const char *arg);
+static int set_filters(struct MPContext *mpctx, enum stream_type mediatype,
+ struct m_obj_settings *new_chain);
+
+static char *format_bitrate(int rate)
+{
+ return talloc_asprintf(NULL, "%d kbps", rate * 8 / 1000);
+}
+
+static char *format_delay(double time)
+{
+ return talloc_asprintf(NULL, "%d ms", ROUND(time * 1000));
+}
+
+// Get current mouse position in OSD coordinate space.
+void mp_get_osd_mouse_pos(struct MPContext *mpctx, float *x, float *y)
+{
+ int wx, wy;
+ mp_input_get_mouse_pos(mpctx->input, &wx, &wy);
+ float p[2] = {wx, wy};
+ // Raw window coordinates (VO mouse events) to OSD resolution.
+ if (mpctx->video_out)
+ vo_control(mpctx->video_out, VOCTRL_WINDOW_TO_OSD_COORDS, p);
+ *x = p[0];
+ *y = p[1];
+}
+
+// Property-option bridge.
+static int mp_property_generic_option(struct m_option *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ char *optname = prop->priv;
+ struct m_config_option *opt = m_config_get_co(mpctx->mconfig,
+ bstr0(optname));
+ void *valptr = opt->data;
+
+ switch (action) {
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)arg = *(opt->opt);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ m_option_copy(opt->opt, arg, valptr);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ m_option_copy(opt->opt, valptr, arg);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Playback speed (RW)
+static int mp_property_playback_speed(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ double orig_speed = opts->playback_speed;
+ switch (action) {
+ case M_PROPERTY_SET: {
+ opts->playback_speed = *(double *) arg;
+ // Adjust time until next frame flip for nosound mode
+ mpctx->time_frame *= orig_speed / opts->playback_speed;
+ if (mpctx->sh_audio)
+ reinit_audio_chain(mpctx);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_PRINT:
+ *(char **)arg = talloc_asprintf(NULL, "x %6.2f", orig_speed);
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// filename with path (RO)
+static int mp_property_path(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->filename)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_strdup_ro(prop, action, arg, mpctx->filename);
+}
+
+static int mp_property_filename(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->filename)
+ return M_PROPERTY_UNAVAILABLE;
+ char *f = (char *)mp_basename(mpctx->filename);
+ return m_property_strdup_ro(prop, action, arg, (*f) ? f : mpctx->filename);
+}
+
+static int mp_property_media_title(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ char *name = NULL;
+ if (mpctx->resolve_result)
+ name = mpctx->resolve_result->title;
+ if (name && name[0])
+ return m_property_strdup_ro(prop, action, arg, name);
+ if (mpctx->master_demuxer) {
+ name = demux_info_get(mpctx->master_demuxer, "title");
+ if (name && name[0])
+ return m_property_strdup_ro(prop, action, arg, name);
+ }
+ return mp_property_filename(prop, action, arg, mpctx);
+}
+
+static int mp_property_stream_path(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream || !stream->url)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_strdup_ro(prop, action, arg, stream->url);
+}
+
+static int mp_property_stream_capture(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->stream)
+ return M_PROPERTY_UNAVAILABLE;
+
+ if (action == M_PROPERTY_SET) {
+ char *filename = *(char **)arg;
+ stream_set_capture_file(mpctx->stream, filename);
+ // fall through to mp_property_generic_option
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Demuxer name (RO)
+static int mp_property_demuxer(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_strdup_ro(prop, action, arg, demuxer->desc->name);
+}
+
+/// Position in the stream (RW)
+static int mp_property_stream_pos(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int64_t *) arg = stream_tell(stream);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ stream_seek(stream, *(int64_t *) arg);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Stream start offset (RO)
+static int mp_property_stream_start(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int64_ro(prop, action, arg, stream->start_pos);
+}
+
+/// Stream end offset (RO)
+static int mp_property_stream_end(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int64_ro(prop, action, arg, stream->end_pos);
+}
+
+/// Stream length (RO)
+static int mp_property_stream_length(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int64_ro(prop, action, arg,
+ stream->end_pos - stream->start_pos);
+}
+
+// Does some magic to handle "<name>/full" as time formatted with milliseconds.
+// Assumes prop is the type of the actual property.
+static int property_time(m_option_t *prop, int action, void *arg, double time)
+{
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(double *)arg = time;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_KEY_ACTION: {
+ struct m_property_action_arg *ka = arg;
+
+ if (strcmp(ka->key, "full") != 0)
+ return M_PROPERTY_UNKNOWN;
+
+ switch (ka->action) {
+ case M_PROPERTY_GET:
+ *(double *)ka->arg = time;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT:
+ *(char **)ka->arg = mp_format_time(time, true);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)ka->arg = *prop;
+ return M_PROPERTY_OK;
+ }
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Current stream position in seconds (RO)
+static int mp_property_stream_time_pos(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ double pts = demuxer->stream_pts;
+ if (pts == MP_NOPTS_VALUE)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return property_time(prop, action, arg, pts);
+}
+
+
+/// Media length in seconds (RO)
+static int mp_property_length(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ double len;
+
+ if (!(int) (len = get_time_length(mpctx)))
+ return M_PROPERTY_UNAVAILABLE;
+
+ return property_time(prop, action, arg, len);
+}
+
+static int mp_property_avsync(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_audio || !mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_double_ro(prop, action, arg, mpctx->last_av_difference);
+}
+
+/// Current position in percent (RW)
+static int mp_property_percent_pos(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->num_sources)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_SET: ;
+ double pos = *(double *)arg;
+ queue_seek(mpctx, MPSEEK_FACTOR, pos / 100.0, 0);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(double *)arg = get_current_pos_ratio(mpctx, false) * 100.0;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT:
+ *(char **)arg = talloc_asprintf(NULL, "%d", get_percent_pos(mpctx));
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Current position in seconds (RW)
+static int mp_property_time_pos(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->num_sources)
+ return M_PROPERTY_UNAVAILABLE;
+
+ if (action == M_PROPERTY_SET) {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, 0);
+ return M_PROPERTY_OK;
+ }
+ return property_time(prop, action, arg, get_current_time(mpctx));
+}
+
+static int mp_property_remaining(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ double len = get_time_length(mpctx);
+ double pos = get_current_time(mpctx);
+ double start = get_start_time(mpctx);
+
+ if (!(int)len)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return property_time(prop, action, arg, len - (pos - start));
+}
+
+/// Current chapter (RW)
+static int mp_property_chapter(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ int chapter = get_current_chapter(mpctx);
+ if (chapter < -1)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *) arg = chapter;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT: {
+ char *chapter_name = chapter_display_name(mpctx, chapter);
+ if (!chapter_name)
+ return M_PROPERTY_UNAVAILABLE;
+ *(char **) arg = chapter_name;
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET: ;
+ int step_all = *(int *)arg - chapter;
+ chapter += step_all;
+ if (chapter >= get_chapter_count(mpctx) && step_all > 0) {
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ } else {
+ mp_seek_chapter(mpctx, chapter);
+ }
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_list_chapters(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (action == M_PROPERTY_GET) {
+ int count = get_chapter_count(mpctx);
+ int cur = mpctx->num_sources ? get_current_chapter(mpctx) : -1;
+ char *res = NULL;
+ int n;
+
+ if (count < 1) {
+ res = talloc_asprintf_append(res, "No chapters.");
+ }
+
+ for (n = 0; n < count; n++) {
+ char *name = chapter_display_name(mpctx, n);
+ double t = chapter_start_time(mpctx, n);
+ char* time = mp_format_time(t, false);
+ res = talloc_asprintf_append(res, "%s", time);
+ talloc_free(time);
+ char *m1 = "> ", *m2 = " <";
+ if (n != cur)
+ m1 = m2 = "";
+ res = talloc_asprintf_append(res, " %s%s%s\n", m1, name, m2);
+ talloc_free(name);
+ }
+
+ *(char **)arg = res;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_edition(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ if (demuxer->num_editions <= 0)
+ return M_PROPERTY_UNAVAILABLE;
+
+ int edition = demuxer->edition;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *)arg = edition;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET: {
+ edition = *(int *)arg;
+ if (edition != demuxer->edition) {
+ opts->edition_id = edition;
+ mpctx->stop_play = PT_RESTART;
+ }
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_GET_TYPE: {
+ struct m_option opt = {
+ .name = prop->name,
+ .type = CONF_TYPE_INT,
+ .flags = CONF_RANGE,
+ .min = 0,
+ .max = demuxer->num_editions - 1,
+ };
+ *(struct m_option *)arg = opt;
+ return M_PROPERTY_OK;
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static struct mp_resolve_src *find_source(struct mp_resolve_result *res,
+ char *url)
+{
+ if (res->num_srcs == 0)
+ return NULL;
+
+ int src = 0;
+ for (int n = 0; n < res->num_srcs; n++) {
+ if (strcmp(res->srcs[n]->url, res->url) == 0) {
+ src = n;
+ break;
+ }
+ }
+ return res->srcs[src];
+}
+
+static int mp_property_quvi_format(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct mp_resolve_result *res = mpctx->resolve_result;
+ if (!res || !res->num_srcs)
+ return M_PROPERTY_UNAVAILABLE;
+
+ struct mp_resolve_src *cur = find_source(res, res->url);
+ if (!cur)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(char **)arg = talloc_strdup(NULL, cur->encid);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET: {
+ mpctx->stop_play = PT_RESTART;
+ break;
+ }
+ case M_PROPERTY_SWITCH: {
+ struct m_property_switch_arg *sarg = arg;
+ int pos = 0;
+ for (int n = 0; n < res->num_srcs; n++) {
+ if (res->srcs[n] == cur) {
+ pos = n;
+ break;
+ }
+ }
+ pos += sarg->inc;
+ if (pos < 0 || pos >= res->num_srcs) {
+ if (sarg->wrap) {
+ pos = (res->num_srcs + pos) % res->num_srcs;
+ } else {
+ pos = av_clip(pos, 0, res->num_srcs);
+ }
+ }
+ char *fmt = res->srcs[pos]->encid;
+ return mp_property_quvi_format(prop, M_PROPERTY_SET, &fmt, mpctx);
+ }
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Number of titles in file
+static int mp_property_titles(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ int num_titles = 0;
+ stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_TITLES, &num_titles);
+ return m_property_int_ro(prop, action, arg, num_titles);
+}
+
+/// Number of chapters in file
+static int mp_property_chapters(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->num_sources)
+ return M_PROPERTY_UNAVAILABLE;
+ int count = get_chapter_count(mpctx);
+ return m_property_int_ro(prop, action, arg, count);
+}
+
+static int mp_property_editions(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ if (demuxer->num_editions <= 0)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(prop, action, arg, demuxer->num_editions);
+}
+
+/// Current dvd angle (RW)
+static int mp_property_angle(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ int angle = -1;
+ int angles;
+
+ if (demuxer)
+ angle = demuxer_get_current_angle(demuxer);
+ if (angle < 0)
+ return M_PROPERTY_UNAVAILABLE;
+ angles = demuxer_angles_count(demuxer);
+ if (angles <= 1)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *) arg = angle;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT: {
+ *(char **) arg = talloc_asprintf(NULL, "%d/%d", angle, angles);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET:
+ angle = demuxer_set_angle(demuxer, *(int *)arg);
+ if (angle >= 0) {
+ if (mpctx->sh_video)
+ resync_video_stream(mpctx->sh_video);
+
+ if (mpctx->sh_audio)
+ resync_audio_stream(mpctx->sh_audio);
+ }
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE: {
+ struct m_option opt = {
+ .name = prop->name,
+ .type = CONF_TYPE_INT,
+ .flags = CONF_RANGE,
+ .min = 1,
+ .max = angles,
+ };
+ *(struct m_option *)arg = opt;
+ return M_PROPERTY_OK;
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Demuxer meta data
+static int mp_property_metadata(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+
+ static const m_option_t key_type =
+ {
+ "metadata", NULL, CONF_TYPE_STRING, 0, 0, 0, NULL
+ };
+
+ switch (action) {
+ case M_PROPERTY_GET: {
+ char **slist = NULL;
+ m_option_copy(prop, &slist, &demuxer->info);
+ *(char ***)arg = slist;
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_PRINT: {
+ char **list = demuxer->info;
+ char *res = NULL;
+ for (int n = 0; list && list[n]; n += 2) {
+ res = talloc_asprintf_append_buffer(res, "%s: %s\n",
+ list[n], list[n + 1]);
+ }
+ *(char **)arg = res;
+ return res ? M_PROPERTY_OK : M_PROPERTY_UNAVAILABLE;
+ }
+ case M_PROPERTY_KEY_ACTION: {
+ struct m_property_action_arg *ka = arg;
+ char *meta = demux_info_get(demuxer, ka->key);
+ if (!meta)
+ return M_PROPERTY_UNKNOWN;
+ switch (ka->action) {
+ case M_PROPERTY_GET:
+ *(char **)ka->arg = talloc_strdup(NULL, meta);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)ka->arg = key_type;
+ return M_PROPERTY_OK;
+ }
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_pause(m_option_t *prop, int action, void *arg,
+ void *ctx)
+{
+ MPContext *mpctx = ctx;
+
+ if (action == M_PROPERTY_SET) {
+ if (*(int *)arg) {
+ pause_player(mpctx);
+ } else {
+ unpause_player(mpctx);
+ }
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, ctx);
+}
+
+static int mp_property_cache(m_option_t *prop, int action, void *arg,
+ void *ctx)
+{
+ MPContext *mpctx = ctx;
+ int cache = mp_get_cache_percent(mpctx);
+ if (cache < 0)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(prop, action, arg, cache);
+}
+
+static int mp_property_clock(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ char outstr[6];
+ time_t t = time(NULL);
+ struct tm *tmp = localtime(&t);
+
+ if ((tmp != NULL) && (strftime(outstr, sizeof(outstr), "%H:%M", tmp) == 5))
+ return m_property_strdup_ro(prop, action, arg, outstr);
+ return M_PROPERTY_UNAVAILABLE;
+}
+
+/// Volume (RW)
+static int mp_property_volume(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+
+ if (!mpctx->sh_audio)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ mixer_getbothvolume(&mpctx->mixer, arg);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ mixer_setvolume(&mpctx->mixer, *(float *) arg, *(float *) arg);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SWITCH: {
+ struct m_property_switch_arg *sarg = arg;
+ if (sarg->inc <= 0)
+ mixer_decvolume(&mpctx->mixer);
+ else
+ mixer_incvolume(&mpctx->mixer);
+ return M_PROPERTY_OK;
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Mute (RW)
+static int mp_property_mute(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+
+ if (!mpctx->sh_audio)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_SET:
+ mixer_setmute(&mpctx->mixer, *(int *) arg);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(int *)arg = mixer_getmute(&mpctx->mixer);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Audio delay (RW)
+static int mp_property_audio_delay(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!(mpctx->sh_audio && mpctx->sh_video))
+ return M_PROPERTY_UNAVAILABLE;
+ float delay = mpctx->opts->audio_delay;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **)arg = format_delay(delay);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ mpctx->audio_delay = mpctx->opts->audio_delay = *(float *)arg;
+ mpctx->delay -= mpctx->audio_delay - delay;
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Audio codec tag (RO)
+static int mp_property_audio_format(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ const char *c = mpctx->sh_audio ? mpctx->sh_audio->gsh->codec : NULL;
+ return m_property_strdup_ro(prop, action, arg, c);
+}
+
+/// Audio codec name (RO)
+static int mp_property_audio_codec(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ const char *c = mpctx->sh_audio ? mpctx->sh_audio->gsh->decoder_desc : NULL;
+ return m_property_strdup_ro(prop, action, arg, c);
+}
+
+/// Audio bitrate (RO)
+static int mp_property_audio_bitrate(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->sh_audio)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **)arg = format_bitrate(mpctx->sh_audio->i_bps);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(int *)arg = mpctx->sh_audio->i_bps;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Samplerate (RO)
+static int mp_property_samplerate(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_audio)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **)arg = talloc_asprintf(NULL, "%d kHz",
+ mpctx->sh_audio->samplerate / 1000);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(int *)arg = mpctx->sh_audio->samplerate;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Number of channels (RO)
+static int mp_property_channels(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_audio)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **) arg = mp_chmap_to_str(&mpctx->sh_audio->channels);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(int *)arg = mpctx->sh_audio->channels.num;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Balance (RW)
+static int mp_property_balance(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ float bal;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ mixer_getbalance(&mpctx->mixer, arg);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT: {
+ char **str = arg;
+ mixer_getbalance(&mpctx->mixer, &bal);
+ if (bal == 0.f)
+ *str = talloc_strdup(NULL, "center");
+ else if (bal == -1.f)
+ *str = talloc_strdup(NULL, "left only");
+ else if (bal == 1.f)
+ *str = talloc_strdup(NULL, "right only");
+ else {
+ unsigned right = (bal + 1.f) / 2.f * 100.f;
+ *str = talloc_asprintf(NULL, "left %d%%, right %d%%",
+ 100 - right, right);
+ }
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET:
+ mixer_setbalance(&mpctx->mixer, *(float *)arg);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static struct track* track_next(struct MPContext *mpctx, enum stream_type type,
+ int direction, struct track *track)
+{
+ assert(direction == -1 || direction == +1);
+ struct track *prev = NULL, *next = NULL;
+ bool seen = track == NULL;
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *cur = mpctx->tracks[n];
+ if (cur->type == type) {
+ if (cur == track) {
+ seen = true;
+ } else {
+ if (seen && !next) {
+ next = cur;
+ }
+ if (!seen || !track) {
+ prev = cur;
+ }
+ }
+ }
+ }
+ return direction > 0 ? next : prev;
+}
+
+static int property_switch_track(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx, enum stream_type type)
+{
+ if (!mpctx->num_sources)
+ return M_PROPERTY_UNAVAILABLE;
+ struct track *track = mpctx->current_track[type];
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *) arg = track ? track->user_tid : -2;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT:
+ if (!track)
+ *(char **) arg = talloc_strdup(NULL, "no");
+ else {
+ char *lang = track->lang;
+ if (!lang)
+ lang = mp_gtext("unknown");
+
+ if (track->title)
+ *(char **)arg = talloc_asprintf(NULL, "(%d) %s (\"%s\")",
+ track->user_tid, lang, track->title);
+ else
+ *(char **)arg = talloc_asprintf(NULL, "(%d) %s",
+ track->user_tid, lang);
+ }
+ return M_PROPERTY_OK;
+
+ case M_PROPERTY_SWITCH: {
+ struct m_property_switch_arg *sarg = arg;
+ mp_switch_track(mpctx, type,
+ track_next(mpctx, type, sarg->inc >= 0 ? +1 : -1, track));
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET:
+ mp_switch_track(mpctx, type, mp_track_by_tid(mpctx, type, *(int *)arg));
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+static const char *track_type_name(enum stream_type t)
+{
+ switch (t) {
+ case STREAM_VIDEO: return "Video";
+ case STREAM_AUDIO: return "Audio";
+ case STREAM_SUB: return "Sub";
+ }
+ return NULL;
+}
+
+static int property_list_tracks(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (action == M_PROPERTY_GET) {
+ char *res = NULL;
+
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->type != type)
+ continue;
+
+ bool selected = mpctx->current_track[track->type] == track;
+ res = talloc_asprintf_append(res, "%s: ",
+ track_type_name(track->type));
+ if (selected)
+ res = talloc_asprintf_append(res, "> ");
+ res = talloc_asprintf_append(res, "(%d) ", track->user_tid);
+ if (track->title)
+ res = talloc_asprintf_append(res, "'%s' ", track->title);
+ if (track->lang)
+ res = talloc_asprintf_append(res, "(%s) ", track->lang);
+ if (track->is_external)
+ res = talloc_asprintf_append(res, "(external) ");
+ if (selected)
+ res = talloc_asprintf_append(res, "<");
+ res = talloc_asprintf_append(res, "\n");
+ }
+
+ res = talloc_asprintf_append(res, "\n");
+ }
+
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (demuxer && demuxer->num_editions > 1)
+ res = talloc_asprintf_append(res, "\nEdition: %d of %d\n",
+ demuxer->edition + 1,
+ demuxer->num_editions);
+
+ *(char **)arg = res;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Selected audio id (RW)
+static int mp_property_audio(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return property_switch_track(prop, action, arg, mpctx, STREAM_AUDIO);
+}
+
+/// Selected video id (RW)
+static int mp_property_video(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return property_switch_track(prop, action, arg, mpctx, STREAM_VIDEO);
+}
+
+static struct track *find_track_by_demuxer_id(MPContext *mpctx,
+ enum stream_type type,
+ int demuxer_id)
+{
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->type == type && track->demuxer_id == demuxer_id)
+ return track;
+ }
+ return NULL;
+}
+
+static int mp_property_program(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ demux_program_t prog;
+
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_SWITCH:
+ case M_PROPERTY_SET:
+ if (action == M_PROPERTY_SET && arg)
+ prog.progid = *((int *) arg);
+ else
+ prog.progid = -1;
+ if (demux_control(demuxer, DEMUXER_CTRL_IDENTIFY_PROGRAM, &prog) ==
+ DEMUXER_CTRL_NOTIMPL)
+ return M_PROPERTY_ERROR;
+
+ if (prog.aid < 0 && prog.vid < 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR,
+ "Selected program contains no audio or video streams!\n");
+ return M_PROPERTY_ERROR;
+ }
+ mp_switch_track(mpctx, STREAM_VIDEO,
+ find_track_by_demuxer_id(mpctx, STREAM_VIDEO, prog.vid));
+ mp_switch_track(mpctx, STREAM_AUDIO,
+ find_track_by_demuxer_id(mpctx, STREAM_AUDIO, prog.aid));
+ mp_switch_track(mpctx, STREAM_SUB,
+ find_track_by_demuxer_id(mpctx, STREAM_VIDEO, prog.sid));
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+
+/// Fullscreen state (RW)
+static int mp_property_fullscreen(m_option_t *prop,
+ int action,
+ void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->video_out)
+ return M_PROPERTY_UNAVAILABLE;
+ struct mp_vo_opts *opts = mpctx->video_out->opts;
+
+ if (action == M_PROPERTY_SET) {
+ int val = *(int *)arg;
+ opts->fullscreen = val;
+ if (mpctx->video_out->config_ok)
+ vo_control(mpctx->video_out, VOCTRL_FULLSCREEN, 0);
+ return opts->fullscreen == val ? M_PROPERTY_OK : M_PROPERTY_ERROR;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+#define VF_DEINTERLACE_LABEL "deinterlace"
+
+#ifdef CONFIG_VF_LAVFI
+#define VF_DEINTERLACE "@" VF_DEINTERLACE_LABEL ":lavfi=yadif"
+#else
+#define VF_DEINTERLACE "@" VF_DEINTERLACE_LABEL ":yadif"
+#endif
+
+static int get_deinterlacing(struct MPContext *mpctx)
+{
+ vf_instance_t *vf = mpctx->sh_video->vfilter;
+ int enabled = 0;
+ if (vf->control(vf, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
+ enabled = -1;
+ if (enabled < 0) {
+ // vf_lavfi doesn't support VFCTRL_GET_DEINTERLACE
+ if (vf_find_by_label(vf, VF_DEINTERLACE_LABEL))
+ enabled = 1;
+ }
+ return enabled;
+}
+
+static void set_deinterlacing(struct MPContext *mpctx, bool enable)
+{
+ vf_instance_t *vf = mpctx->sh_video->vfilter;
+ if (vf_find_by_label(vf, VF_DEINTERLACE_LABEL)) {
+ if (!enable)
+ change_video_filters(mpctx, "del", VF_DEINTERLACE);
+ } else {
+ int arg = enable;
+ if (vf->control(vf, VFCTRL_SET_DEINTERLACE, &arg) != CONTROL_OK)
+ change_video_filters(mpctx, "add", VF_DEINTERLACE);
+ }
+}
+
+static int mp_property_deinterlace(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->sh_video || !mpctx->sh_video->vfilter)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *)arg = get_deinterlacing(mpctx) > 0;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ set_deinterlacing(mpctx, *(int *)arg);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+// Generic option + requires hard refresh to make changes take effect.
+static int video_refresh_property_helper(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ int r = mp_property_generic_option(prop, action, arg, mpctx);
+ if (action == M_PROPERTY_SET) {
+ if (mpctx->sh_video) {
+ reinit_video_filters(mpctx);
+ mp_force_video_refresh(mpctx);
+ }
+ }
+ return r;
+}
+
+static int mp_property_colormatrix(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (action != M_PROPERTY_PRINT)
+ return video_refresh_property_helper(prop, action, arg, mpctx);
+
+ struct MPOpts *opts = mpctx->opts;
+
+ struct mp_csp_details vo_csp = {0};
+ if (mpctx->sh_video && mpctx->sh_video->vfilter)
+ vf_control(mpctx->sh_video->vfilter, VFCTRL_GET_YUV_COLORSPACE, &vo_csp);
+
+ struct mp_image_params vd_csp = {0};
+ if (mpctx->sh_video)
+ vd_control(mpctx->sh_video, VDCTRL_GET_PARAMS, &vd_csp);
+
+ char *res = talloc_asprintf(NULL, "%s",
+ mp_csp_names[opts->requested_colorspace]);
+ if (!vo_csp.format) {
+ res = talloc_asprintf_append(res, " (VO: unknown)");
+ } else if (vo_csp.format != opts->requested_colorspace) {
+ res = talloc_asprintf_append(res, " (VO: %s)",
+ mp_csp_names[vo_csp.format]);
+ }
+ if (!vd_csp.colorspace) {
+ res = talloc_asprintf_append(res, " (VD: unknown)");
+ } else if (!vo_csp.format || vd_csp.colorspace != vo_csp.format) {
+ res = talloc_asprintf_append(res, " (VD: %s)",
+ mp_csp_names[vd_csp.colorspace]);
+ }
+ *(char **)arg = res;
+ return M_PROPERTY_OK;
+}
+
+static int mp_property_colormatrix_input_range(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (action != M_PROPERTY_PRINT)
+ return video_refresh_property_helper(prop, action, arg, mpctx);
+
+ struct MPOpts *opts = mpctx->opts;
+
+ struct mp_csp_details vo_csp = {0};
+ if (mpctx->sh_video && mpctx->sh_video->vfilter)
+ vf_control(mpctx->sh_video->vfilter, VFCTRL_GET_YUV_COLORSPACE, &vo_csp );
+
+ struct mp_image_params vd_csp = {0};
+ if (mpctx->sh_video)
+ vd_control(mpctx->sh_video, VDCTRL_GET_PARAMS, &vd_csp);
+
+ char *res = talloc_asprintf(NULL, "%s",
+ mp_csp_levels_names[opts->requested_input_range]);
+ if (!vo_csp.levels_in) {
+ res = talloc_asprintf_append(res, " (VO: unknown)");
+ } else if (vo_csp.levels_in != opts->requested_input_range) {
+ res = talloc_asprintf_append(res, " (VO: %s)",
+ mp_csp_levels_names[vo_csp.levels_in]);
+ }
+ if (!vd_csp.colorlevels) {
+ res = talloc_asprintf_append(res, " (VD: unknown)");
+ } else if (!vo_csp.levels_in || vd_csp.colorlevels != vo_csp.levels_in) {
+ res = talloc_asprintf_append(res, " (VD: %s)",
+ mp_csp_levels_names[vd_csp.colorlevels]);
+ }
+ *(char **)arg = res;
+ return M_PROPERTY_OK;
+}
+
+static int mp_property_colormatrix_output_range(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (action != M_PROPERTY_PRINT) {
+ int r = mp_property_generic_option(prop, action, arg, mpctx);
+ if (action == M_PROPERTY_SET) {
+ if (mpctx->sh_video)
+ set_video_output_levels(mpctx->sh_video);
+ }
+ return r;
+ }
+
+ struct MPOpts *opts = mpctx->opts;
+
+ int req = opts->requested_output_range;
+ struct mp_csp_details actual = {0};
+ if (mpctx->sh_video && mpctx->sh_video->vfilter)
+ vf_control(mpctx->sh_video->vfilter, VFCTRL_GET_YUV_COLORSPACE, &actual);
+
+ char *res = talloc_asprintf(NULL, "%s", mp_csp_levels_names[req]);
+ if (!actual.levels_out) {
+ res = talloc_asprintf_append(res, " (Actual: unknown)");
+ } else if (actual.levels_out != req) {
+ res = talloc_asprintf_append(res, " (Actual: %s)",
+ mp_csp_levels_names[actual.levels_out]);
+ }
+ *(char **)arg = res;
+ return M_PROPERTY_OK;
+}
+
+/// Panscan (RW)
+static int mp_property_panscan(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+
+ if (!mpctx->video_out
+ || vo_control(mpctx->video_out, VOCTRL_GET_PANSCAN, NULL) != VO_TRUE)
+ return M_PROPERTY_UNAVAILABLE;
+
+ int r = mp_property_generic_option(prop, action, arg, mpctx);
+ if (action == M_PROPERTY_SET)
+ vo_control(mpctx->video_out, VOCTRL_SET_PANSCAN, NULL);
+ return r;
+}
+
+/// Helper to set vo flags.
+/** \ingroup PropertyImplHelper
+ */
+static int mp_property_vo_flag(m_option_t *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) {
+ if (*vo_var == !!*(int *) arg)
+ return M_PROPERTY_OK;
+ if (mpctx->video_out->config_ok)
+ vo_control(mpctx->video_out, vo_ctrl, 0);
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Window always on top (RW)
+static int mp_property_ontop(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return mp_property_vo_flag(prop, action, arg, VOCTRL_ONTOP,
+ &mpctx->opts->vo.ontop, mpctx);
+}
+
+/// Show window borders (RW)
+static int mp_property_border(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return mp_property_vo_flag(prop, action, arg, VOCTRL_BORDER,
+ &mpctx->opts->vo.border, mpctx);
+}
+
+static int mp_property_framedrop(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Color settings, try to use vf/vo then fall back on TV. (RW)
+static int mp_property_gamma(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ int *gamma = (int *)((char *)mpctx->opts + prop->offset);
+ int r, val;
+
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+
+ if (gamma[0] == 1000) {
+ gamma[0] = 0;
+ get_video_colors(mpctx->sh_video, prop->name, gamma);
+ }
+
+ switch (action) {
+ case M_PROPERTY_SET:
+ *gamma = *(int *) arg;
+ r = set_video_colors(mpctx->sh_video, prop->name, *gamma);
+ if (r <= 0)
+ break;
+ return r;
+ case M_PROPERTY_GET:
+ if (get_video_colors(mpctx->sh_video, prop->name, &val) > 0) {
+ *(int *)arg = val;
+ return M_PROPERTY_OK;
+ }
+ break;
+ default:
+ return mp_property_generic_option(prop, action, arg, mpctx);
+ }
+
+#ifdef CONFIG_TV
+ if (mpctx->sh_video->gsh->demuxer->type == DEMUXER_TYPE_TV) {
+ int l = strlen(prop->name);
+ char tv_prop[3 + l + 1];
+ sprintf(tv_prop, "tv-%s", prop->name);
+ return mp_property_do(tv_prop, action, arg, mpctx);
+ }
+#endif
+
+ return M_PROPERTY_UNAVAILABLE;
+}
+
+/// Video codec tag (RO)
+static int mp_property_video_format(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ const char *c = mpctx->sh_video ? mpctx->sh_video->gsh->codec : NULL;
+ return m_property_strdup_ro(prop, action, arg, c);
+}
+
+/// Video codec name (RO)
+static int mp_property_video_codec(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ const char *c = mpctx->sh_video ? mpctx->sh_video->gsh->decoder_desc : NULL;
+ return m_property_strdup_ro(prop, action, arg, c);
+}
+
+
+/// Video bitrate (RO)
+static int mp_property_video_bitrate(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ if (action == M_PROPERTY_PRINT) {
+ *(char **)arg = format_bitrate(mpctx->sh_video->i_bps);
+ return M_PROPERTY_OK;
+ }
+ return m_property_int_ro(prop, action, arg, mpctx->sh_video->i_bps);
+}
+
+/// Video display width (RO)
+static int mp_property_width(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(prop, action, arg, mpctx->sh_video->disp_w);
+}
+
+/// Video display height (RO)
+static int mp_property_height(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(prop, action, arg, mpctx->sh_video->disp_h);
+}
+
+static int property_vo_wh(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx, bool get_w)
+{
+ struct vo *vo = mpctx->video_out;
+ if (!mpctx->sh_video && !vo || !vo->hasframe)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(prop, action, arg,
+ get_w ? vo->aspdat.prew : vo->aspdat.preh);
+}
+
+static int mp_property_dwidth(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return property_vo_wh(prop, action, arg, mpctx, true);
+}
+
+static int mp_property_dheight(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return property_vo_wh(prop, action, arg, mpctx, false);
+}
+
+/// Video fps (RO)
+static int mp_property_fps(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_float_ro(prop, action, arg, mpctx->sh_video->fps);
+}
+
+/// Video aspect (RO)
+static int mp_property_aspect(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_SET: {
+ float f = *(float *)arg;
+ if (f < 0.1)
+ f = (float)mpctx->sh_video->disp_w / mpctx->sh_video->disp_h;
+ mpctx->opts->movie_aspect = f;
+ video_reinit_vo(mpctx->sh_video);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_GET:
+ *(float *)arg = mpctx->sh_video->aspect;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+// For OSD and subtitle related properties using the generic option bridge.
+// - Fail as unavailable if no video is active
+// - Trigger OSD state update when property is set
+static int property_osd_helper(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ if (action == M_PROPERTY_SET)
+ osd_changed_all(mpctx->osd);
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Selected subtitles (RW)
+static int mp_property_sub(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return property_switch_track(prop, action, arg, mpctx, STREAM_SUB);
+}
+
+/// Subtitle delay (RW)
+static int mp_property_sub_delay(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **)arg = format_delay(opts->sub_delay);
+ return M_PROPERTY_OK;
+ }
+ return property_osd_helper(prop, action, arg, mpctx);
+}
+
+static int mp_property_sub_pos(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (!mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ if (action == M_PROPERTY_PRINT) {
+ *(char **)arg = talloc_asprintf(NULL, "%d/100", opts->sub_pos);
+ return M_PROPERTY_OK;
+ }
+ return property_osd_helper(prop, action, arg, mpctx);
+}
+
+#ifdef CONFIG_TV
+
+static tvi_handle_t *get_tvh(struct MPContext *mpctx)
+{
+ if (!(mpctx->master_demuxer && mpctx->master_demuxer->type == DEMUXER_TYPE_TV))
+ return NULL;
+ return mpctx->master_demuxer->priv;
+}
+
+/// TV color settings (RW)
+static int mp_property_tv_color(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ tvi_handle_t *tvh = get_tvh(mpctx);
+ if (!tvh)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_SET:
+ return tv_set_color_options(tvh, prop->offset, *(int *) arg);
+ case M_PROPERTY_GET:
+ return tv_get_color_options(tvh, prop->offset, arg);
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+#endif
+
+static int mp_property_playlist_pos(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct playlist *pl = mpctx->playlist;
+ if (!pl->first)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET: {
+ int pos = playlist_entry_to_index(pl, pl->current);
+ if (pos < 0)
+ return M_PROPERTY_UNAVAILABLE;
+ *(int *)arg = pos;
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET: {
+ struct playlist_entry *e = playlist_entry_from_index(pl, *(int *)arg);
+ if (!e)
+ return M_PROPERTY_ERROR;
+ mp_set_playlist_entry(mpctx, e);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_GET_TYPE: {
+ struct m_option opt = {
+ .name = prop->name,
+ .type = CONF_TYPE_INT,
+ .flags = CONF_RANGE,
+ .min = 0,
+ .max = playlist_entry_count(pl) - 1,
+ };
+ *(struct m_option *)arg = opt;
+ return M_PROPERTY_OK;
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_playlist_count(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (action == M_PROPERTY_GET) {
+ *(int *)arg = playlist_entry_count(mpctx->playlist);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_playlist(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (action == M_PROPERTY_GET) {
+ char *res = talloc_strdup(NULL, "");
+
+ for (struct playlist_entry *e = mpctx->playlist->first; e; e = e->next)
+ {
+ if (mpctx->playlist->current == e) {
+ res = talloc_asprintf_append(res, "> %s <\n", e->filename);
+ } else {
+ res = talloc_asprintf_append(res, "%s\n", e->filename);
+ }
+ }
+
+ *(char **)arg = res;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static char *print_obj_osd_list(struct m_obj_settings *list)
+{
+ char *res = NULL;
+ for (int n = 0; list && list[n].name; n++) {
+ res = talloc_asprintf_append(res, "%s [", list[n].name);
+ for (int i = 0; list[n].attribs && list[n].attribs[i]; i += 2) {
+ res = talloc_asprintf_append(res, "%s%s=%s", i > 0 ? " " : "",
+ list[n].attribs[i],
+ list[n].attribs[i + 1]);
+ }
+ res = talloc_asprintf_append(res, "]\n");
+ }
+ if (!res)
+ res = talloc_strdup(NULL, "(empty)");
+ return res;
+}
+
+static int property_filter(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx, enum stream_type mt)
+{
+ switch (action) {
+ case M_PROPERTY_PRINT: {
+ struct m_config_option *opt = m_config_get_co(mpctx->mconfig,
+ bstr0(prop->name));
+ *(char **)arg = print_obj_osd_list(*(struct m_obj_settings **)opt->data);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET:
+ return set_filters(mpctx, mt, *(struct m_obj_settings **)arg) >= 0
+ ? M_PROPERTY_OK : M_PROPERTY_ERROR;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+static int mp_property_vf(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return property_filter(prop, action, arg, mpctx, STREAM_VIDEO);
+}
+
+static int mp_property_af(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ return property_filter(prop, action, arg, mpctx, STREAM_AUDIO);
+}
+
+static int mp_property_alias(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ const char *real_property = prop->priv;
+ int r = mp_property_do(real_property, action, arg, mpctx);
+ if (action == M_PROPERTY_GET_TYPE && r >= 0) {
+ // Fix the property name
+ struct m_option *type = arg;
+ type->name = prop->name;
+ }
+ return r;
+}
+
+static int mp_property_options(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (action != M_PROPERTY_KEY_ACTION)
+ return M_PROPERTY_NOT_IMPLEMENTED;
+
+ struct m_property_action_arg *ka = arg;
+
+ struct m_config_option *opt = m_config_get_co(mpctx->mconfig,
+ bstr0(ka->key));
+ if (!opt)
+ return M_PROPERTY_UNKNOWN;
+ if (!opt->data)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (ka->action) {
+ case M_PROPERTY_GET:
+ m_option_copy(opt->opt, ka->arg, opt->data);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)ka->arg = *opt->opt;
+ return M_PROPERTY_OK;
+ }
+
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+// Use option-to-property-bridge. (The property and option have the same names.)
+#define M_OPTION_PROPERTY(name) \
+ {(name), mp_property_generic_option, &m_option_type_dummy, 0, 0, 0, (name)}
+
+// OPTION_PROPERTY(), but with a custom property handler. The custom handler
+// must let unknown operations fall back to mp_property_generic_option().
+#define M_OPTION_PROPERTY_CUSTOM(name, handler) \
+ {(name), (handler), &m_option_type_dummy, 0, 0, 0, (name)}
+#define M_OPTION_PROPERTY_CUSTOM_(name, handler, ...) \
+ {(name), (handler), &m_option_type_dummy, 0, 0, 0, (name), __VA_ARGS__}
+
+// Redirect a property name to another
+#define M_PROPERTY_ALIAS(name, real_property) \
+ {(name), mp_property_alias, &m_option_type_dummy, 0, 0, 0, (real_property)}
+
+/// All properties available in MPlayer.
+/** \ingroup Properties
+ */
+static const m_option_t mp_properties[] = {
+ // General
+ M_OPTION_PROPERTY("osd-level"),
+ M_OPTION_PROPERTY_CUSTOM("osd-scale", property_osd_helper),
+ M_OPTION_PROPERTY("loop"),
+ M_OPTION_PROPERTY_CUSTOM("speed", mp_property_playback_speed),
+ { "filename", mp_property_filename, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ { "path", mp_property_path, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ { "media-title", mp_property_media_title, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ { "stream-path", mp_property_stream_path, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ M_OPTION_PROPERTY_CUSTOM("stream-capture", mp_property_stream_capture),
+ { "demuxer", mp_property_demuxer, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ { "stream-pos", mp_property_stream_pos, CONF_TYPE_INT64,
+ M_OPT_MIN, 0, 0, NULL },
+ { "stream-start", mp_property_stream_start, CONF_TYPE_INT64,
+ M_OPT_MIN, 0, 0, NULL },
+ { "stream-end", mp_property_stream_end, CONF_TYPE_INT64,
+ M_OPT_MIN, 0, 0, NULL },
+ { "stream-length", mp_property_stream_length, CONF_TYPE_INT64,
+ M_OPT_MIN, 0, 0, NULL },
+ { "stream-time-pos", mp_property_stream_time_pos, CONF_TYPE_TIME,
+ M_OPT_MIN, 0, 0, NULL },
+ { "length", mp_property_length, CONF_TYPE_TIME,
+ M_OPT_MIN, 0, 0, NULL },
+ { "avsync", mp_property_avsync, CONF_TYPE_DOUBLE },
+ { "percent-pos", mp_property_percent_pos, CONF_TYPE_DOUBLE,
+ M_OPT_RANGE, 0, 100, NULL },
+ { "time-pos", mp_property_time_pos, CONF_TYPE_TIME,
+ M_OPT_MIN, 0, 0, NULL },
+ { "time-remaining", mp_property_remaining, CONF_TYPE_TIME },
+ { "chapter", mp_property_chapter, CONF_TYPE_INT,
+ M_OPT_MIN, 0, 0, NULL },
+ M_OPTION_PROPERTY_CUSTOM("edition", mp_property_edition),
+ M_OPTION_PROPERTY_CUSTOM("quvi-format", mp_property_quvi_format),
+ { "titles", mp_property_titles, CONF_TYPE_INT,
+ 0, 0, 0, NULL },
+ { "chapters", mp_property_chapters, CONF_TYPE_INT,
+ 0, 0, 0, NULL },
+ { "editions", mp_property_editions, CONF_TYPE_INT },
+ { "angle", mp_property_angle, &m_option_type_dummy },
+ { "metadata", mp_property_metadata, CONF_TYPE_STRING_LIST,
+ 0, 0, 0, NULL },
+ M_OPTION_PROPERTY_CUSTOM("pause", mp_property_pause),
+ { "cache", mp_property_cache, CONF_TYPE_INT },
+ M_OPTION_PROPERTY("pts-association-mode"),
+ M_OPTION_PROPERTY("hr-seek"),
+ { "clock", mp_property_clock, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+
+ { "chapter-list", mp_property_list_chapters, CONF_TYPE_STRING },
+ { "track-list", property_list_tracks, CONF_TYPE_STRING },
+
+ { "playlist", mp_property_playlist, CONF_TYPE_STRING },
+ { "playlist-pos", mp_property_playlist_pos, CONF_TYPE_INT },
+ { "playlist-count", mp_property_playlist_count, CONF_TYPE_INT },
+
+ // Audio
+ { "volume", mp_property_volume, CONF_TYPE_FLOAT,
+ M_OPT_RANGE, 0, 100, NULL },
+ { "mute", mp_property_mute, CONF_TYPE_FLAG,
+ M_OPT_RANGE, 0, 1, NULL },
+ M_OPTION_PROPERTY_CUSTOM("audio-delay", mp_property_audio_delay),
+ { "audio-format", mp_property_audio_format, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ { "audio-codec", mp_property_audio_codec, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ { "audio-bitrate", mp_property_audio_bitrate, CONF_TYPE_INT,
+ 0, 0, 0, NULL },
+ { "samplerate", mp_property_samplerate, CONF_TYPE_INT,
+ 0, 0, 0, NULL },
+ { "channels", mp_property_channels, CONF_TYPE_INT,
+ 0, 0, 0, NULL },
+ M_OPTION_PROPERTY_CUSTOM("aid", mp_property_audio),
+ { "balance", mp_property_balance, CONF_TYPE_FLOAT,
+ M_OPT_RANGE, -1, 1, NULL },
+
+ // Video
+ M_OPTION_PROPERTY_CUSTOM("fullscreen", mp_property_fullscreen),
+ { "deinterlace", mp_property_deinterlace, CONF_TYPE_FLAG,
+ M_OPT_RANGE, 0, 1, NULL },
+ M_OPTION_PROPERTY_CUSTOM("colormatrix", mp_property_colormatrix),
+ M_OPTION_PROPERTY_CUSTOM("colormatrix-input-range",
+ mp_property_colormatrix_input_range),
+ M_OPTION_PROPERTY_CUSTOM("colormatrix-output-range",
+ mp_property_colormatrix_output_range),
+ M_OPTION_PROPERTY_CUSTOM("ontop", mp_property_ontop),
+ M_OPTION_PROPERTY_CUSTOM("border", mp_property_border),
+ M_OPTION_PROPERTY_CUSTOM("framedrop", mp_property_framedrop),
+ M_OPTION_PROPERTY_CUSTOM_("gamma", mp_property_gamma,
+ .offset = offsetof(struct MPOpts, gamma_gamma)),
+ M_OPTION_PROPERTY_CUSTOM_("brightness", mp_property_gamma,
+ .offset = offsetof(struct MPOpts, gamma_brightness)),
+ M_OPTION_PROPERTY_CUSTOM_("contrast", mp_property_gamma,
+ .offset = offsetof(struct MPOpts, gamma_contrast)),
+ M_OPTION_PROPERTY_CUSTOM_("saturation", mp_property_gamma,
+ .offset = offsetof(struct MPOpts, gamma_saturation)),
+ M_OPTION_PROPERTY_CUSTOM_("hue", mp_property_gamma,
+ .offset = offsetof(struct MPOpts, gamma_hue)),
+ M_OPTION_PROPERTY_CUSTOM("panscan", mp_property_panscan),
+ { "video-format", mp_property_video_format, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ { "video-codec", mp_property_video_codec, CONF_TYPE_STRING,
+ 0, 0, 0, NULL },
+ { "video-bitrate", mp_property_video_bitrate, CONF_TYPE_INT,
+ 0, 0, 0, NULL },
+ { "width", mp_property_width, CONF_TYPE_INT,
+ 0, 0, 0, NULL },
+ { "height", mp_property_height, CONF_TYPE_INT,
+ 0, 0, 0, NULL },
+ { "dwidth", mp_property_dwidth, CONF_TYPE_INT },
+ { "dheight", mp_property_dheight, CONF_TYPE_INT },
+ { "fps", mp_property_fps, CONF_TYPE_FLOAT,
+ 0, 0, 0, NULL },
+ { "aspect", mp_property_aspect, CONF_TYPE_FLOAT,
+ CONF_RANGE, 0, 10, NULL },
+ M_OPTION_PROPERTY_CUSTOM("vid", mp_property_video),
+ { "program", mp_property_program, CONF_TYPE_INT,
+ CONF_RANGE, -1, 65535, NULL },
+
+ // Subs
+ M_OPTION_PROPERTY_CUSTOM("sid", mp_property_sub),
+ M_OPTION_PROPERTY_CUSTOM("sub-delay", mp_property_sub_delay),
+ M_OPTION_PROPERTY_CUSTOM("sub-pos", mp_property_sub_pos),
+ M_OPTION_PROPERTY_CUSTOM("sub-visibility", property_osd_helper),
+ M_OPTION_PROPERTY_CUSTOM("sub-forced-only", property_osd_helper),
+ M_OPTION_PROPERTY_CUSTOM("sub-scale", property_osd_helper),
+#ifdef CONFIG_ASS
+ M_OPTION_PROPERTY_CUSTOM("ass-use-margins", property_osd_helper),
+ M_OPTION_PROPERTY_CUSTOM("ass-vsfilter-aspect-compat", property_osd_helper),
+ M_OPTION_PROPERTY_CUSTOM("ass-style-override", property_osd_helper),
+#endif
+
+ M_OPTION_PROPERTY_CUSTOM("vf*", mp_property_vf),
+ M_OPTION_PROPERTY_CUSTOM("af*", mp_property_af),
+
+#ifdef CONFIG_TV
+ { "tv-brightness", mp_property_tv_color, CONF_TYPE_INT,
+ M_OPT_RANGE, -100, 100, .offset = TV_COLOR_BRIGHTNESS },
+ { "tv-contrast", mp_property_tv_color, CONF_TYPE_INT,
+ M_OPT_RANGE, -100, 100, .offset = TV_COLOR_CONTRAST },
+ { "tv-saturation", mp_property_tv_color, CONF_TYPE_INT,
+ M_OPT_RANGE, -100, 100, .offset = TV_COLOR_SATURATION },
+ { "tv-hue", mp_property_tv_color, CONF_TYPE_INT,
+ M_OPT_RANGE, -100, 100, .offset = TV_COLOR_HUE },
+#endif
+
+ M_PROPERTY_ALIAS("video", "vid"),
+ M_PROPERTY_ALIAS("audio", "aid"),
+ M_PROPERTY_ALIAS("sub", "sid"),
+
+ { "options", mp_property_options, &m_option_type_dummy },
+
+ {0},
+};
+
+int mp_property_do(const char *name, int action, void *val,
+ struct MPContext *ctx)
+{
+ return m_property_do(mp_properties, name, action, val, ctx);
+}
+
+char *mp_property_expand_string(struct MPContext *mpctx, char *str)
+{
+ return m_properties_expand_string(mp_properties, str, mpctx);
+}
+
+void property_print_help(void)
+{
+ m_properties_print_help_list(mp_properties);
+}
+
+
+/* List of default ways to show a property on OSD.
+ *
+ * If osd_progbar is set, a bar showing the current position between min/max
+ * values of the property is shown. In this case osd_msg is only used for
+ * terminal output if there is no video; it'll be a label shown together with
+ * percentage.
+ */
+static struct property_osd_display {
+ // property name
+ const char *name;
+ // name used on OSD
+ const char *osd_name;
+ // progressbar type
+ int osd_progbar;
+ // osd msg id if it must be shared
+ int osd_id;
+ // Needs special ways to display the new value (seeks are delayed)
+ int seek_msg, seek_bar;
+ // Separator between option name and value (default: ": ")
+ const char *sep;
+} property_osd_display[] = {
+ // general
+ { "loop", _("Loop") },
+ { "chapter", .seek_msg = OSD_SEEK_INFO_CHAPTER_TEXT,
+ .seek_bar = OSD_SEEK_INFO_BAR },
+ { "edition", .seek_msg = OSD_SEEK_INFO_EDITION },
+ { "pts-association-mode", "PTS association mode" },
+ { "hr-seek", "hr-seek" },
+ { "speed", _("Speed") },
+ { "clock", _("Clock") },
+ // audio
+ { "volume", _("Volume"), .osd_progbar = OSD_VOLUME },
+ { "mute", _("Mute") },
+ { "audio-delay", _("A-V delay") },
+ { "audio", _("Audio") },
+ { "balance", _("Balance"), .osd_progbar = OSD_BALANCE },
+ // video
+ { "panscan", _("Panscan"), .osd_progbar = OSD_PANSCAN },
+ { "ontop", _("Stay on top") },
+ { "border", _("Border") },
+ { "framedrop", _("Framedrop") },
+ { "deinterlace", _("Deinterlace") },
+ { "colormatrix", _("YUV colormatrix") },
+ { "colormatrix-input-range", _("YUV input range") },
+ { "colormatrix-output-range", _("RGB output range") },
+ { "gamma", _("Gamma"), .osd_progbar = OSD_BRIGHTNESS },
+ { "brightness", _("Brightness"), .osd_progbar = OSD_BRIGHTNESS },
+ { "contrast", _("Contrast"), .osd_progbar = OSD_CONTRAST },
+ { "saturation", _("Saturation"), .osd_progbar = OSD_SATURATION },
+ { "hue", _("Hue"), .osd_progbar = OSD_HUE },
+ { "angle", _("Angle") },
+ // subs
+ { "sub", _("Subtitles") },
+ { "sub-pos", _("Sub position") },
+ { "sub-delay", _("Sub delay"), .osd_id = OSD_MSG_SUB_DELAY },
+ { "sub-visibility", _("Subtitles") },
+ { "sub-forced-only", _("Forced sub only") },
+ { "sub-scale", _("Sub Scale")},
+ { "ass-vsfilter-aspect-compat", _("Subtitle VSFilter aspect compat")},
+ { "ass-style-override", _("ASS subtitle style override")},
+ { "vf*", _("Video filters"), .sep = ":\n"},
+ { "af*", _("Audio filters"), .sep = ":\n"},
+#ifdef CONFIG_TV
+ { "tv-brightness", _("Brightness"), .osd_progbar = OSD_BRIGHTNESS },
+ { "tv-hue", _("Hue"), .osd_progbar = OSD_HUE},
+ { "tv-saturation", _("Saturation"), .osd_progbar = OSD_SATURATION },
+ { "tv-contrast", _("Contrast"), .osd_progbar = OSD_CONTRAST },
+#endif
+ {0}
+};
+
+static void show_property_osd(MPContext *mpctx, const char *pname,
+ enum mp_on_osd osd_mode)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct m_option prop = {0};
+ struct property_osd_display *p;
+
+ if (mp_property_do(pname, M_PROPERTY_GET_TYPE, &prop, mpctx) <= 0)
+ return;
+
+ int osd_progbar = 0;
+ const char *osd_name = NULL;
+
+ // look for the command
+ for (p = property_osd_display; p->name; p++) {
+ if (!strcmp(p->name, prop.name)) {
+ osd_progbar = p->seek_bar ? 1 : p->osd_progbar;
+ osd_name = p->seek_msg ? "" : mp_gtext(p->osd_name);
+ break;
+ }
+ }
+ if (!p->name)
+ p = NULL;
+
+ if (osd_mode != MP_ON_OSD_AUTO) {
+ osd_name = osd_name ? osd_name : prop.name;
+ if (!(osd_mode & MP_ON_OSD_MSG))
+ osd_name = NULL;
+ osd_progbar = osd_progbar ? osd_progbar : ' ';
+ if (!(osd_mode & MP_ON_OSD_BAR))
+ osd_progbar = 0;
+ }
+
+ if (p && (p->seek_msg || p->seek_bar)) {
+ mpctx->add_osd_seek_info |=
+ (osd_name ? p->seek_msg : 0) | (osd_progbar ? p->seek_bar : 0);
+ return;
+ }
+
+ if (osd_progbar && (prop.flags & CONF_RANGE) == CONF_RANGE) {
+ bool ok = false;
+ if (prop.type == CONF_TYPE_INT) {
+ int i;
+ ok = mp_property_do(prop.name, M_PROPERTY_GET, &i, mpctx) > 0;
+ if (ok)
+ set_osd_bar(mpctx, osd_progbar, osd_name, prop.min, prop.max, i);
+ } else if (prop.type == CONF_TYPE_FLOAT) {
+ float f;
+ ok = mp_property_do(prop.name, M_PROPERTY_GET, &f, mpctx) > 0;
+ if (ok)
+ set_osd_bar(mpctx, osd_progbar, osd_name, prop.min, prop.max, f);
+ }
+ if (ok && osd_mode == MP_ON_OSD_AUTO && opts->osd_bar_visible)
+ return;
+ }
+
+ if (osd_name) {
+ char *val = NULL;
+ int r = mp_property_do(prop.name, M_PROPERTY_PRINT, &val, mpctx);
+ if (r == M_PROPERTY_UNAVAILABLE) {
+ set_osd_tmsg(mpctx, OSD_MSG_TEXT, 1, opts->osd_duration,
+ "%s: (unavailable)", osd_name);
+ } else if (r >= 0 && val) {
+ int osd_id = 0;
+ const char *sep = NULL;
+ if (p) {
+ int index = p - property_osd_display;
+ osd_id = p->osd_id ? p->osd_id : OSD_MSG_PROPERTY + index;
+ sep = p->sep;
+ }
+ if (!sep)
+ sep = ": ";
+ set_osd_tmsg(mpctx, osd_id, 1, opts->osd_duration,
+ "%s%s%s", osd_name, sep, val);
+ talloc_free(val);
+ }
+ }
+}
+
+static const char *property_error_string(int error_value)
+{
+ switch (error_value) {
+ case M_PROPERTY_ERROR:
+ return "ERROR";
+ case M_PROPERTY_UNAVAILABLE:
+ return "PROPERTY_UNAVAILABLE";
+ case M_PROPERTY_NOT_IMPLEMENTED:
+ return "NOT_IMPLEMENTED";
+ case M_PROPERTY_UNKNOWN:
+ return "PROPERTY_UNKNOWN";
+ }
+ return "UNKNOWN";
+}
+
+static bool reinit_filters(MPContext *mpctx, enum stream_type mediatype)
+{
+ switch (mediatype) {
+ case STREAM_VIDEO:
+ return reinit_video_filters(mpctx) >= 0;
+ case STREAM_AUDIO:
+ return reinit_audio_filters(mpctx) >= 0;
+ }
+ return false;
+}
+
+static const char *filter_opt[STREAM_TYPE_COUNT] = {
+ [STREAM_VIDEO] = "vf",
+ [STREAM_AUDIO] = "af",
+};
+
+static int set_filters(struct MPContext *mpctx, enum stream_type mediatype,
+ struct m_obj_settings *new_chain)
+{
+ bstr option = bstr0(filter_opt[mediatype]);
+ struct m_config_option *co = m_config_get_co(mpctx->mconfig, option);
+ if (!co)
+ return -1;
+
+ struct m_obj_settings **list = co->data;
+ struct m_obj_settings *old_settings = *list;
+ *list = NULL;
+ m_option_copy(co->opt, list, &new_chain);
+
+ bool success = reinit_filters(mpctx, mediatype);
+
+ if (success) {
+ m_option_free(co->opt, &old_settings);
+ } else {
+ m_option_free(co->opt, list);
+ *list = old_settings;
+ reinit_filters(mpctx, mediatype);
+ }
+
+ if (mediatype == STREAM_VIDEO)
+ mp_force_video_refresh(mpctx);
+
+ return success ? 0 : -1;
+}
+
+static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype,
+ const char *cmd, const char *arg)
+{
+ bstr option = bstr0(filter_opt[mediatype]);
+ struct m_config_option *co = m_config_get_co(mpctx->mconfig, option);
+ if (!co)
+ return -1;
+
+ // The option parser is used to modify the filter list itself.
+ char optname[20];
+ snprintf(optname, sizeof(optname), "%.*s-%s", BSTR_P(option), cmd);
+
+ struct m_obj_settings *new_chain = NULL;
+ m_option_copy(co->opt, &new_chain, co->data);
+
+ int r = m_option_parse(co->opt, bstr0(optname), bstr0(arg), &new_chain);
+ if (r >= 0)
+ r = set_filters(mpctx, mediatype, new_chain);
+
+ m_option_free(co->opt, &new_chain);
+
+ return r >= 0 ? 0 : -1;
+}
+
+static int edit_filters_osd(struct MPContext *mpctx, enum stream_type mediatype,
+ const char *cmd, const char *arg, bool on_osd)
+{
+ int r = edit_filters(mpctx, mediatype, cmd, arg);
+ if (on_osd) {
+ if (r >= 0) {
+ const char *prop = filter_opt[mediatype];
+ show_property_osd(mpctx, prop, MP_ON_OSD_MSG);
+ } else {
+ set_osd_tmsg(mpctx, OSD_MSG_TEXT, 1, mpctx->opts->osd_duration,
+ "Changing filters failed!");
+ }
+ }
+ return r;
+}
+
+static void change_video_filters(MPContext *mpctx, const char *cmd,
+ const char *arg)
+{
+ edit_filters(mpctx, STREAM_VIDEO, cmd, arg);
+}
+
+void run_command(MPContext *mpctx, mp_cmd_t *cmd)
+{
+ struct MPOpts *opts = mpctx->opts;
+ sh_video_t *const sh_video = mpctx->sh_video;
+ int osd_duration = opts->osd_duration;
+ bool auto_osd = cmd->on_osd == MP_ON_OSD_AUTO;
+ bool msg_osd = auto_osd || (cmd->on_osd & MP_ON_OSD_MSG);
+ bool bar_osd = auto_osd || (cmd->on_osd & MP_ON_OSD_BAR);
+ int osdl = msg_osd ? 1 : OSD_LEVEL_INVISIBLE;
+
+ if (!cmd->raw_args) {
+ for (int n = 0; n < cmd->nargs; n++) {
+ if (cmd->args[n].type.type == CONF_TYPE_STRING) {
+ cmd->args[n].v.s =
+ mp_property_expand_string(mpctx, cmd->args[n].v.s);
+ if (!cmd->args[n].v.s)
+ return;
+ talloc_steal(cmd, cmd->args[n].v.s);
+ }
+ }
+ }
+
+ switch (cmd->id) {
+ case MP_CMD_SEEK: {
+ double v = cmd->args[0].v.d;
+ int abs = cmd->args[1].v.i;
+ int exact = cmd->args[2].v.i;
+ if (abs == 2) { // Absolute seek to a timestamp in seconds
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, v, exact);
+ set_osd_function(mpctx,
+ v > get_current_time(mpctx) ? OSD_FFW : OSD_REW);
+ } else if (abs) { /* Absolute seek by percentage */
+ queue_seek(mpctx, MPSEEK_FACTOR, v / 100.0, exact);
+ set_osd_function(mpctx, OSD_FFW); // Direction isn't set correctly
+ } else {
+ queue_seek(mpctx, MPSEEK_RELATIVE, v, exact);
+ set_osd_function(mpctx, (v > 0) ? OSD_FFW : OSD_REW);
+ }
+ if (bar_osd)
+ mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR;
+ if (msg_osd && !(auto_osd && opts->osd_bar_visible))
+ mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT;
+ break;
+ }
+
+ case MP_CMD_SET: {
+ int r = mp_property_do(cmd->args[0].v.s, M_PROPERTY_SET_STRING,
+ cmd->args[1].v.s, mpctx);
+ if (r == M_PROPERTY_OK || r == M_PROPERTY_UNAVAILABLE) {
+ show_property_osd(mpctx, cmd->args[0].v.s, cmd->on_osd);
+ } else if (r == M_PROPERTY_UNKNOWN) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN,
+ "Unknown property: '%s'\n", cmd->args[0].v.s);
+ } else if (r <= 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN,
+ "Failed to set property '%s' to '%s'.\n",
+ cmd->args[0].v.s, cmd->args[1].v.s);
+ }
+ break;
+ }
+
+ case MP_CMD_ADD:
+ case MP_CMD_CYCLE:
+ {
+ struct m_property_switch_arg s = {
+ .inc = 1,
+ .wrap = cmd->id == MP_CMD_CYCLE,
+ };
+ if (cmd->args[1].v.d)
+ s.inc = cmd->args[1].v.d;
+ int r = mp_property_do(cmd->args[0].v.s, M_PROPERTY_SWITCH, &s, mpctx);
+ if (r == M_PROPERTY_OK || r == M_PROPERTY_UNAVAILABLE) {
+ show_property_osd(mpctx, cmd->args[0].v.s, cmd->on_osd);
+ } else if (r == M_PROPERTY_UNKNOWN) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN,
+ "Unknown property: '%s'\n", cmd->args[0].v.s);
+ } else if (r <= 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN,
+ "Failed to increment property '%s' by %g.\n",
+ cmd->args[0].v.s, s.inc);
+ }
+ break;
+ }
+
+ case MP_CMD_GET_PROPERTY: {
+ char *tmp;
+ int r = mp_property_do(cmd->args[0].v.s, M_PROPERTY_GET_STRING,
+ &tmp, mpctx);
+ if (r <= 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN,
+ "Failed to get value of property '%s'.\n",
+ cmd->args[0].v.s);
+ mp_msg(MSGT_GLOBAL, MSGL_INFO, "ANS_ERROR=%s\n",
+ property_error_string(r));
+ break;
+ }
+ mp_msg(MSGT_GLOBAL, MSGL_INFO, "ANS_%s=%s\n",
+ cmd->args[0].v.s, tmp);
+ talloc_free(tmp);
+ break;
+ }
+
+ case MP_CMD_SPEED_MULT: {
+ double v = cmd->args[0].v.d;
+ v *= mpctx->opts->playback_speed;
+ mp_property_do("speed", M_PROPERTY_SET, &v, mpctx);
+ show_property_osd(mpctx, "speed", cmd->on_osd);
+ break;
+ }
+
+ case MP_CMD_FRAME_STEP:
+ add_step_frame(mpctx, 1);
+ break;
+
+ case MP_CMD_FRAME_BACK_STEP:
+ add_step_frame(mpctx, -1);
+ break;
+
+ case MP_CMD_QUIT:
+ mpctx->stop_play = PT_QUIT;
+ mpctx->quit_custom_rc = cmd->args[0].v.i;
+ mpctx->has_quit_custom_rc = true;
+ break;
+
+ case MP_CMD_QUIT_WATCH_LATER:
+ mp_write_watch_later_conf(mpctx);
+ mpctx->stop_play = PT_QUIT;
+ mpctx->quit_player_rc = 0;
+ break;
+
+ case MP_CMD_PLAYLIST_NEXT:
+ case MP_CMD_PLAYLIST_PREV:
+ {
+ int dir = cmd->id == MP_CMD_PLAYLIST_PREV ? -1 : +1;
+ int force = cmd->args[0].v.i;
+
+ struct playlist_entry *e = mp_next_file(mpctx, dir);
+ if (!e && !force)
+ break;
+ mpctx->playlist->current = e;
+ mpctx->playlist->current_was_replaced = false;
+ mpctx->stop_play = PT_CURRENT_ENTRY;
+ break;
+ }
+
+ case MP_CMD_SUB_STEP:
+ if (mpctx->osd->dec_sub) {
+ double a[2];
+ a[0] = mpctx->video_pts - mpctx->osd->video_offset + opts->sub_delay;
+ a[1] = cmd->args[0].v.i;
+ if (sub_control(mpctx->osd->dec_sub, SD_CTRL_SUB_STEP, a) > 0) {
+ opts->sub_delay += a[0];
+
+ osd_changed_all(mpctx->osd);
+ set_osd_tmsg(mpctx, OSD_MSG_SUB_DELAY, osdl, osd_duration,
+ "Sub delay: %d ms", ROUND(opts->sub_delay * 1000));
+ }
+ }
+ break;
+
+ case MP_CMD_OSD: {
+ int v = cmd->args[0].v.i;
+ int max = (opts->term_osd
+ && !sh_video) ? MAX_TERM_OSD_LEVEL : MAX_OSD_LEVEL;
+ if (opts->osd_level > max)
+ opts->osd_level = max;
+ if (v < 0)
+ opts->osd_level = (opts->osd_level + 1) % (max + 1);
+ else
+ opts->osd_level = v > max ? max : v;
+ if (msg_osd && opts->osd_level <= 1)
+ set_osd_tmsg(mpctx, OSD_MSG_OSD_STATUS, 0, osd_duration,
+ "OSD: %s", opts->osd_level ? "yes" : "no");
+ else
+ rm_osd_msg(mpctx, OSD_MSG_OSD_STATUS);
+ break;
+ }
+
+ case MP_CMD_PRINT_TEXT: {
+ mp_msg(MSGT_GLOBAL, MSGL_INFO, "%s\n", cmd->args[0].v.s);
+ break;
+ }
+
+ case MP_CMD_SHOW_TEXT: {
+ // if no argument supplied use default osd_duration, else <arg> ms.
+ set_osd_msg(mpctx, OSD_MSG_TEXT, cmd->args[2].v.i,
+ (cmd->args[1].v.i < 0 ? osd_duration : cmd->args[1].v.i),
+ "%s", cmd->args[0].v.s);
+ break;
+ }
+
+ case MP_CMD_LOADFILE: {
+ char *filename = cmd->args[0].v.s;
+ bool append = cmd->args[1].v.i;
+
+ if (!append)
+ playlist_clear(mpctx->playlist);
+
+ playlist_add(mpctx->playlist, playlist_entry_new(filename));
+
+ if (!append)
+ mp_set_playlist_entry(mpctx, mpctx->playlist->first);
+ break;
+ }
+
+ case MP_CMD_LOADLIST: {
+ char *filename = cmd->args[0].v.s;
+ bool append = cmd->args[1].v.i;
+ struct playlist *pl = playlist_parse_file(filename);
+ if (pl) {
+ if (!append)
+ playlist_clear(mpctx->playlist);
+ playlist_transfer_entries(mpctx->playlist, pl);
+ talloc_free(pl);
+
+ if (!append && mpctx->playlist->first)
+ mp_set_playlist_entry(mpctx, mpctx->playlist->first);
+ } else {
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR,
+ "\nUnable to load playlist %s.\n", filename);
+ }
+ break;
+ }
+
+ case MP_CMD_PLAYLIST_CLEAR: {
+ // Supposed to clear the playlist, except the currently played item.
+ if (mpctx->playlist->current_was_replaced)
+ mpctx->playlist->current = NULL;
+ while (mpctx->playlist->first) {
+ struct playlist_entry *e = mpctx->playlist->first;
+ if (e == mpctx->playlist->current) {
+ e = e->next;
+ if (!e)
+ break;
+ }
+ playlist_remove(mpctx->playlist, e);
+ }
+ break;
+ }
+
+ case MP_CMD_PLAYLIST_REMOVE: {
+ struct playlist_entry *e = playlist_entry_from_index(mpctx->playlist,
+ cmd->args[0].v.i);
+ if (e) {
+ // Can't play a removed entry
+ if (mpctx->playlist->current == e)
+ mpctx->stop_play = PT_CURRENT_ENTRY;
+ playlist_remove(mpctx->playlist, e);
+ }
+ break;
+ }
+
+ case MP_CMD_PLAYLIST_MOVE: {
+ struct playlist_entry *e1 = playlist_entry_from_index(mpctx->playlist,
+ cmd->args[0].v.i);
+ struct playlist_entry *e2 = playlist_entry_from_index(mpctx->playlist,
+ cmd->args[1].v.i);
+ if (e1) {
+ playlist_move(mpctx->playlist, e1, e2);
+ }
+ break;
+ }
+
+ case MP_CMD_STOP:
+ // Go back to the starting point.
+ mpctx->stop_play = PT_STOP;
+ break;
+
+ case MP_CMD_SHOW_PROGRESS:
+ mpctx->add_osd_seek_info |=
+ (msg_osd ? OSD_SEEK_INFO_TEXT : 0) |
+ (bar_osd ? OSD_SEEK_INFO_BAR : 0);
+ break;
+
+#ifdef CONFIG_RADIO
+ case MP_CMD_RADIO_STEP_CHANNEL:
+ if (mpctx->stream && mpctx->stream->type == STREAMTYPE_RADIO) {
+ int v = cmd->args[0].v.i;
+ if (v > 0)
+ radio_step_channel(mpctx->stream, RADIO_CHANNEL_HIGHER);
+ else
+ radio_step_channel(mpctx->stream, RADIO_CHANNEL_LOWER);
+ if (radio_get_channel_name(mpctx->stream)) {
+ set_osd_tmsg(mpctx, OSD_MSG_RADIO_CHANNEL, osdl, osd_duration,
+ "Channel: %s",
+ radio_get_channel_name(mpctx->stream));
+ }
+ }
+ break;
+
+ case MP_CMD_RADIO_SET_CHANNEL:
+ if (mpctx->stream && mpctx->stream->type == STREAMTYPE_RADIO) {
+ radio_set_channel(mpctx->stream, cmd->args[0].v.s);
+ if (radio_get_channel_name(mpctx->stream)) {
+ set_osd_tmsg(mpctx, OSD_MSG_RADIO_CHANNEL, osdl, osd_duration,
+ "Channel: %s",
+ radio_get_channel_name(mpctx->stream));
+ }
+ }
+ break;
+
+ case MP_CMD_RADIO_SET_FREQ:
+ if (mpctx->stream && mpctx->stream->type == STREAMTYPE_RADIO)
+ radio_set_freq(mpctx->stream, cmd->args[0].v.f);
+ break;
+
+ case MP_CMD_RADIO_STEP_FREQ:
+ if (mpctx->stream && mpctx->stream->type == STREAMTYPE_RADIO)
+ radio_step_freq(mpctx->stream, cmd->args[0].v.f);
+ break;
+#endif
+
+#ifdef CONFIG_TV
+ case MP_CMD_TV_START_SCAN:
+ if (get_tvh(mpctx))
+ tv_start_scan(get_tvh(mpctx), 1);
+ break;
+ case MP_CMD_TV_SET_FREQ:
+ if (get_tvh(mpctx))
+ tv_set_freq(get_tvh(mpctx), cmd->args[0].v.f * 16.0);
+#ifdef CONFIG_PVR
+ else if (mpctx->stream && mpctx->stream->type == STREAMTYPE_PVR) {
+ pvr_set_freq(mpctx->stream, ROUND(cmd->args[0].v.f));
+ set_osd_msg(mpctx, OSD_MSG_TV_CHANNEL, osdl, osd_duration, "%s: %s",
+ pvr_get_current_channelname(mpctx->stream),
+ pvr_get_current_stationname(mpctx->stream));
+ }
+#endif /* CONFIG_PVR */
+ break;
+
+ case MP_CMD_TV_STEP_FREQ:
+ if (get_tvh(mpctx))
+ tv_step_freq(get_tvh(mpctx), cmd->args[0].v.f * 16.0);
+#ifdef CONFIG_PVR
+ else if (mpctx->stream && mpctx->stream->type == STREAMTYPE_PVR) {
+ pvr_force_freq_step(mpctx->stream, ROUND(cmd->args[0].v.f));
+ set_osd_msg(mpctx, OSD_MSG_TV_CHANNEL, osdl, osd_duration, "%s: f %d",
+ pvr_get_current_channelname(mpctx->stream),
+ pvr_get_current_frequency(mpctx->stream));
+ }
+#endif /* CONFIG_PVR */
+ break;
+
+ case MP_CMD_TV_SET_NORM:
+ if (get_tvh(mpctx))
+ tv_set_norm(get_tvh(mpctx), cmd->args[0].v.s);
+ break;
+
+ case MP_CMD_TV_STEP_CHANNEL:
+ if (get_tvh(mpctx)) {
+ int v = cmd->args[0].v.i;
+ if (v > 0) {
+ tv_step_channel(get_tvh(mpctx), TV_CHANNEL_HIGHER);
+ } else {
+ tv_step_channel(get_tvh(mpctx), TV_CHANNEL_LOWER);
+ }
+ if (tv_channel_list) {
+ set_osd_tmsg(mpctx, OSD_MSG_TV_CHANNEL, osdl, osd_duration,
+ "Channel: %s", tv_channel_current->name);
+ }
+ }
+#ifdef CONFIG_PVR
+ else if (mpctx->stream &&
+ mpctx->stream->type == STREAMTYPE_PVR) {
+ pvr_set_channel_step(mpctx->stream, cmd->args[0].v.i);
+ set_osd_msg(mpctx, OSD_MSG_TV_CHANNEL, osdl, osd_duration, "%s: %s",
+ pvr_get_current_channelname(mpctx->stream),
+ pvr_get_current_stationname(mpctx->stream));
+ }
+#endif /* CONFIG_PVR */
+#ifdef CONFIG_DVBIN
+ if (mpctx->stream->type == STREAMTYPE_DVB) {
+ int dir;
+ int v = cmd->args[0].v.i;
+
+ mpctx->last_dvb_step = v;
+ if (v > 0)
+ dir = DVB_CHANNEL_HIGHER;
+ else
+ dir = DVB_CHANNEL_LOWER;
+
+
+ if (dvb_step_channel(mpctx->stream, dir)) {
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ mpctx->dvbin_reopen = 1;
+ }
+ }
+#endif /* CONFIG_DVBIN */
+ break;
+
+ case MP_CMD_TV_SET_CHANNEL:
+ if (get_tvh(mpctx)) {
+ tv_set_channel(get_tvh(mpctx), cmd->args[0].v.s);
+ if (tv_channel_list) {
+ set_osd_tmsg(mpctx, OSD_MSG_TV_CHANNEL, osdl, osd_duration,
+ "Channel: %s", tv_channel_current->name);
+ }
+ }
+#ifdef CONFIG_PVR
+ else if (mpctx->stream && mpctx->stream->type == STREAMTYPE_PVR) {
+ pvr_set_channel(mpctx->stream, cmd->args[0].v.s);
+ set_osd_msg(mpctx, OSD_MSG_TV_CHANNEL, osdl, osd_duration, "%s: %s",
+ pvr_get_current_channelname(mpctx->stream),
+ pvr_get_current_stationname(mpctx->stream));
+ }
+#endif /* CONFIG_PVR */
+ break;
+
+#ifdef CONFIG_DVBIN
+ case MP_CMD_DVB_SET_CHANNEL:
+ if (mpctx->stream->type == STREAMTYPE_DVB) {
+ mpctx->last_dvb_step = 1;
+
+ if (dvb_set_channel(mpctx->stream, cmd->args[1].v.i,
+ cmd->args[0].v.i)) {
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ mpctx->dvbin_reopen = 1;
+ }
+ }
+ break;
+#endif /* CONFIG_DVBIN */
+
+ case MP_CMD_TV_LAST_CHANNEL:
+ if (get_tvh(mpctx)) {
+ tv_last_channel(get_tvh(mpctx));
+ if (tv_channel_list) {
+ set_osd_tmsg(mpctx, OSD_MSG_TV_CHANNEL, osdl, osd_duration,
+ "Channel: %s", tv_channel_current->name);
+ }
+ }
+#ifdef CONFIG_PVR
+ else if (mpctx->stream && mpctx->stream->type == STREAMTYPE_PVR) {
+ pvr_set_lastchannel(mpctx->stream);
+ set_osd_msg(mpctx, OSD_MSG_TV_CHANNEL, osdl, osd_duration, "%s: %s",
+ pvr_get_current_channelname(mpctx->stream),
+ pvr_get_current_stationname(mpctx->stream));
+ }
+#endif /* CONFIG_PVR */
+ break;
+
+ case MP_CMD_TV_STEP_NORM:
+ if (get_tvh(mpctx))
+ tv_step_norm(get_tvh(mpctx));
+ break;
+
+ case MP_CMD_TV_STEP_CHANNEL_LIST:
+ if (get_tvh(mpctx))
+ tv_step_chanlist(get_tvh(mpctx));
+ break;
+#endif /* CONFIG_TV */
+
+ case MP_CMD_SUB_ADD:
+ if (sh_video) {
+ mp_add_subtitles(mpctx, cmd->args[0].v.s, 0);
+ }
+ break;
+
+ case MP_CMD_SUB_REMOVE: {
+ struct track *sub = mp_track_by_tid(mpctx, STREAM_SUB, cmd->args[0].v.i);
+ if (sub)
+ mp_remove_track(mpctx, sub);
+ break;
+ }
+
+ case MP_CMD_SUB_RELOAD: {
+ struct track *sub = mp_track_by_tid(mpctx, STREAM_SUB, cmd->args[0].v.i);
+ if (sh_video && sub && sub->is_external && sub->external_filename)
+ {
+ struct track *nsub = mp_add_subtitles(mpctx, sub->external_filename, 0);
+ if (nsub) {
+ mp_remove_track(mpctx, sub);
+ mp_switch_track(mpctx, nsub->type, nsub);
+ }
+ }
+ break;
+ }
+
+ case MP_CMD_SCREENSHOT:
+ screenshot_request(mpctx, cmd->args[0].v.i, cmd->args[1].v.i, msg_osd);
+ break;
+
+ case MP_CMD_SCREENSHOT_TO_FILE:
+ screenshot_to_file(mpctx, cmd->args[0].v.s, cmd->args[1].v.i, msg_osd);
+ break;
+
+ case MP_CMD_RUN:
+#ifndef __MINGW32__
+ if (!fork()) {
+ execl("/bin/sh", "sh", "-c", cmd->args[0].v.s, NULL);
+ exit(0);
+ }
+#endif
+ break;
+
+ case MP_CMD_KEYDOWN_EVENTS:
+ mp_input_put_key(mpctx->input, cmd->args[0].v.i);
+ break;
+
+ case MP_CMD_ENABLE_INPUT_SECTION:
+ mp_input_enable_section(mpctx->input, cmd->args[0].v.s,
+ cmd->args[1].v.i == 1 ? MP_INPUT_EXCLUSIVE : 0);
+ break;
+
+ case MP_CMD_DISABLE_INPUT_SECTION:
+ mp_input_disable_section(mpctx->input, cmd->args[0].v.s);
+ break;
+
+ case MP_CMD_VO_CMDLINE:
+ if (mpctx->video_out) {
+ char *s = cmd->args[0].v.s;
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "Setting vo cmd line to '%s'.\n",
+ s);
+ if (vo_control(mpctx->video_out, VOCTRL_SET_COMMAND_LINE, s) > 0) {
+ set_osd_msg(mpctx, OSD_MSG_TEXT, osdl, osd_duration, "vo='%s'", s);
+ } else {
+ set_osd_msg(mpctx, OSD_MSG_TEXT, osdl, osd_duration, "Failed!");
+ }
+ }
+ break;
+
+ case MP_CMD_AF:
+ edit_filters_osd(mpctx, STREAM_AUDIO, cmd->args[0].v.s,
+ cmd->args[1].v.s, msg_osd);
+ break;
+
+ case MP_CMD_VF:
+ edit_filters_osd(mpctx, STREAM_VIDEO, cmd->args[0].v.s,
+ cmd->args[1].v.s, msg_osd);
+ break;
+
+ case MP_CMD_COMMAND_LIST: {
+ for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next)
+ run_command(mpctx, sub);
+ break;
+ }
+
+ default:
+ mp_msg(MSGT_CPLAYER, MSGL_V,
+ "Received unknown cmd %s\n", cmd->name);
+ }
+
+ switch (cmd->pausing) {
+ case 1: // "pausing"
+ pause_player(mpctx);
+ break;
+ case 3: // "pausing_toggle"
+ if (opts->pause)
+ unpause_player(mpctx);
+ else
+ pause_player(mpctx);
+ break;
+ }
+}
diff --git a/mpvcore/command.h b/mpvcore/command.h
new file mode 100644
index 0000000000..dbe1f638e2
--- /dev/null
+++ b/mpvcore/command.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_COMMAND_H
+#define MPLAYER_COMMAND_H
+
+struct MPContext;
+struct mp_cmd;
+
+void mp_get_osd_mouse_pos(struct MPContext *mpctx, float *x, float *y);
+
+void run_command(struct MPContext *mpctx, struct mp_cmd *cmd);
+char *mp_property_expand_string(struct MPContext *mpctx, char *str);
+void property_print_help(void);
+int mp_property_do(const char* name, int action, void* val,
+ struct MPContext *mpctx);
+
+#endif /* MPLAYER_COMMAND_H */
diff --git a/mpvcore/cpudetect.c b/mpvcore/cpudetect.c
new file mode 100644
index 0000000000..62cb03008d
--- /dev/null
+++ b/mpvcore/cpudetect.c
@@ -0,0 +1,56 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libavutil/cpu.h>
+#include "compat/libav.h"
+
+#include "config.h"
+#include "core/cpudetect.h"
+#include "core/mp_msg.h"
+
+CpuCaps gCpuCaps;
+
+static void dump_flag(const char *name, bool val)
+{
+ mp_msg(MSGT_CPUDETECT, MSGL_V, "CPU: %s: %s\n", name,
+ val ? "enabled" : "disabled");
+}
+
+void GetCpuCaps(CpuCaps *c)
+{
+ memset(c, 0, sizeof(*c));
+ int flags = av_get_cpu_flags();
+#if ARCH_X86
+ c->hasMMX = flags & AV_CPU_FLAG_MMX;
+ c->hasMMX2 = flags & AV_CPU_FLAG_MMX2;
+ c->hasSSE = flags & AV_CPU_FLAG_SSE;
+ c->hasSSE2 = (flags & AV_CPU_FLAG_SSE2) && !(flags & AV_CPU_FLAG_SSE2SLOW);
+ c->hasSSE3 = (flags & AV_CPU_FLAG_SSE3) && !(flags & AV_CPU_FLAG_SSE3SLOW);
+ c->hasSSSE3 = flags & AV_CPU_FLAG_SSSE3;
+#endif
+ dump_flag("MMX", c->hasMMX);
+ dump_flag("MMX2", c->hasMMX2);
+ dump_flag("SSE", c->hasSSE);
+ dump_flag("SSE2", c->hasSSE2);
+ dump_flag("SSE3", c->hasSSE3);
+ dump_flag("SSSE3", c->hasSSSE3);
+}
diff --git a/mpvcore/cpudetect.h b/mpvcore/cpudetect.h
new file mode 100644
index 0000000000..d3d9206c65
--- /dev/null
+++ b/mpvcore/cpudetect.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_CPUDETECT_H
+#define MPLAYER_CPUDETECT_H
+
+#include <stdbool.h>
+#include "config.h"
+
+#include "compat/x86_cpu.h"
+
+typedef struct cpucaps_s {
+ bool hasMMX;
+ bool hasMMX2;
+ bool hasSSE;
+ bool hasSSE2;
+ bool hasSSE3;
+ bool hasSSSE3;
+} CpuCaps;
+
+extern CpuCaps gCpuCaps;
+
+void GetCpuCaps(CpuCaps *caps);
+
+#endif /* MPLAYER_CPUDETECT_H */
diff --git a/mpvcore/encode.h b/mpvcore/encode.h
new file mode 100644
index 0000000000..acdb75c5a3
--- /dev/null
+++ b/mpvcore/encode.h
@@ -0,0 +1,22 @@
+#ifndef MPLAYER_ENCODE_H
+#define MPLAYER_ENCODE_H
+
+#include <stdbool.h>
+#include <libavutil/avutil.h>
+
+struct MPOpts;
+struct encode_lavc_context;
+struct encode_output_conf;
+
+// interface for mplayer.c
+struct encode_lavc_context *encode_lavc_init(struct encode_output_conf *options);
+void encode_lavc_finish(struct encode_lavc_context *ctx);
+void encode_lavc_free(struct encode_lavc_context *ctx);
+void encode_lavc_discontinuity(struct encode_lavc_context *ctx);
+bool encode_lavc_showhelp(struct MPOpts *opts);
+int encode_lavc_getstatus(struct encode_lavc_context *ctx, char *buf, int bufsize, float relative_position);
+void encode_lavc_expect_stream(struct encode_lavc_context *ctx, enum AVMediaType mt);
+void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps);
+bool encode_lavc_didfail(struct encode_lavc_context *ctx); // check if encoding failed
+
+#endif
diff --git a/mpvcore/encode_lavc.c b/mpvcore/encode_lavc.c
new file mode 100644
index 0000000000..75e57a2443
--- /dev/null
+++ b/mpvcore/encode_lavc.c
@@ -0,0 +1,1115 @@
+/*
+ * muxing using libavformat
+ * Copyright (C) 2010 Nicolas George <george@nsup.org>
+ * Copyright (C) 2011-2012 Rudolf Polzer <divVerent@xonotic.org>
+ *
+ * This file is part of mpv.
+ *
+ * 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 "encode_lavc.h"
+#include "core/mp_msg.h"
+#include "video/vfcap.h"
+#include "core/options.h"
+#include "osdep/timer.h"
+#include "video/out/vo.h"
+#include "talloc.h"
+#include "stream/stream.h"
+
+static int set_to_avdictionary(AVDictionary **dictp, const char *key,
+ const char *val)
+{
+ char keybuf[1024];
+ char valuebuf[1024];
+
+ if (key == NULL) {
+ // we need to split at equals sign
+ const char *equals = strchr(val, '=');
+ if (!equals || equals - val >= sizeof(keybuf)) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "encode-lavc: option '%s' does not contain an equals sign\n",
+ val);
+ return 0;
+ }
+ memcpy(keybuf, val, equals - val);
+ keybuf[equals - val] = 0;
+ key = keybuf;
+ val = equals + 1;
+ }
+
+ // hack: support "qscale" key as virtual "global_quality" key that multiplies by QP2LAMBDA
+ if (!strcmp(key, "qscale")) {
+ key = "global_quality";
+ snprintf(valuebuf, sizeof(valuebuf),
+ "%.1s(%s)*QP2LAMBDA",
+ (val[0] == '+' || val[0] == '-') ? val : "",
+ (val[0] == '+' || val[0] == '-') ? val + 1 : val);
+ valuebuf[sizeof(valuebuf) - 1] = 0;
+ val = valuebuf;
+ }
+
+ mp_msg(MSGT_ENCODE, MSGL_V,
+ "encode-lavc: setting value '%s' for key '%s'\n",
+ val,
+ key);
+
+ if (av_dict_set(dictp, key, *val ? val : NULL,
+ (val[0] == '+' || val[0] == '-') ? AV_DICT_APPEND : 0) >= 0)
+ return 1;
+
+ return 0;
+}
+
+static bool value_has_flag(const char *value, const char *flag)
+{
+ bool state = true;
+ bool ret = false;
+ while (*value) {
+ size_t l = strcspn(value, "+-");
+ if (l == 0) {
+ state = (*value == '+');
+ ++value;
+ } else {
+ if (l == strlen(flag))
+ if (!memcmp(value, flag, l))
+ ret = state;
+ value += l;
+ }
+ }
+ return ret;
+}
+
+#define CHECK_FAIL(ctx, val) \
+ if (ctx && (ctx->failed || ctx->finished)) { \
+ mp_msg(MSGT_ENCODE, MSGL_ERR, \
+ "Called a function on a %s encoding context. Bailing out.\n", \
+ ctx->failed ? "failed" : "finished"); \
+ return val; \
+ }
+
+int encode_lavc_available(struct encode_lavc_context *ctx)
+{
+ CHECK_FAIL(ctx, 0);
+ return ctx && ctx->avc;
+}
+
+int encode_lavc_oformat_flags(struct encode_lavc_context *ctx)
+{
+ CHECK_FAIL(ctx, 0);
+ return ctx->avc ? ctx->avc->oformat->flags : 0;
+}
+
+struct encode_lavc_context *encode_lavc_init(struct encode_output_conf *options)
+{
+ struct encode_lavc_context *ctx;
+ const char *filename = options->file;
+
+ // STUPID STUPID STUPID STUPID avio
+ // does not support "-" as file name to mean stdin/stdout
+ // ffmpeg.c works around this too, the same way
+ if (!strcmp(filename, "-"))
+ filename = "pipe:1";
+
+ if (filename && (
+ !strcmp(filename, "/dev/stdout") ||
+ !strcmp(filename, "pipe:") ||
+ !strcmp(filename, "pipe:1")))
+ mp_msg_stdout_in_use = 1;
+
+ ctx = talloc_zero(NULL, struct encode_lavc_context);
+ encode_lavc_discontinuity(ctx);
+ ctx->options = options;
+
+ ctx->avc = avformat_alloc_context();
+
+ if (ctx->options->format) {
+ char *tok;
+ const char *in = ctx->options->format;
+ while (*in) {
+ tok = av_get_token(&in, ",");
+ ctx->avc->oformat = av_guess_format(tok, filename, NULL);
+ av_free(tok);
+ if (ctx->avc->oformat)
+ break;
+ if (*in)
+ ++in;
+ }
+ } else
+ ctx->avc->oformat = av_guess_format(NULL, filename, NULL);
+
+ if (!ctx->avc->oformat) {
+ encode_lavc_fail(ctx, "encode-lavc: format not found\n");
+ return NULL;
+ }
+
+ av_strlcpy(ctx->avc->filename, filename,
+ sizeof(ctx->avc->filename));
+
+ ctx->foptions = NULL;
+ if (ctx->options->fopts) {
+ char **p;
+ for (p = ctx->options->fopts; *p; ++p) {
+ if (!set_to_avdictionary(&ctx->foptions, NULL, *p))
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "encode-lavc: could not set option %s\n", *p);
+ }
+ }
+
+ if (ctx->options->vcodec) {
+ char *tok;
+ const char *in = ctx->options->vcodec;
+ while (*in) {
+ tok = av_get_token(&in, ",");
+ ctx->vc = avcodec_find_encoder_by_name(tok);
+ av_free(tok);
+ if (ctx->vc && ctx->vc->type != AVMEDIA_TYPE_VIDEO)
+ ctx->vc = NULL;
+ if (ctx->vc)
+ break;
+ if (*in)
+ ++in;
+ }
+ } else
+ ctx->vc = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL,
+ ctx->avc->filename, NULL,
+ AVMEDIA_TYPE_VIDEO));
+
+ if (ctx->options->acodec) {
+ char *tok;
+ const char *in = ctx->options->acodec;
+ while (*in) {
+ tok = av_get_token(&in, ",");
+ ctx->ac = avcodec_find_encoder_by_name(tok);
+ av_free(tok);
+ if (ctx->ac && ctx->ac->type != AVMEDIA_TYPE_AUDIO)
+ ctx->ac = NULL;
+ if (ctx->ac)
+ break;
+ if (*in)
+ ++in;
+ }
+ } else
+ ctx->ac = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL,
+ ctx->avc->filename, NULL,
+ AVMEDIA_TYPE_AUDIO));
+
+ if (!ctx->vc && !ctx->ac) {
+ encode_lavc_fail(
+ ctx, "encode-lavc: neither audio nor video codec was found\n");
+ return NULL;
+ }
+
+ /* taken from ffmpeg unchanged
+ * TODO turn this into an option if anyone needs this */
+
+ ctx->avc->max_delay = 0.7 * AV_TIME_BASE;
+
+ ctx->abytes = 0;
+ ctx->vbytes = 0;
+ ctx->frames = 0;
+
+ if (options->video_first)
+ ctx->video_first = true;
+ if (options->audio_first)
+ ctx->audio_first = true;
+
+ return ctx;
+}
+
+int encode_lavc_start(struct encode_lavc_context *ctx)
+{
+ AVDictionaryEntry *de;
+ unsigned i;
+
+ if (ctx->header_written < 0)
+ return 0;
+ if (ctx->header_written > 0)
+ return 1;
+
+ CHECK_FAIL(ctx, 0);
+
+ if (ctx->expect_video) {
+ for (i = 0; i < ctx->avc->nb_streams; ++i)
+ if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
+ break;
+ if (i >= ctx->avc->nb_streams) {
+ encode_lavc_fail(ctx,
+ "encode-lavc: video stream missing, invalid codec?\n");
+ return 0;
+ }
+ }
+ if (ctx->expect_audio) {
+ for (i = 0; i < ctx->avc->nb_streams; ++i)
+ if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
+ break;
+ if (i >= ctx->avc->nb_streams) {
+ encode_lavc_fail(ctx,
+ "encode-lavc: audio stream missing, invalid codec?\n");
+ return 0;
+ }
+ }
+
+ ctx->header_written = -1;
+
+ if (!(ctx->avc->oformat->flags & AVFMT_NOFILE)) {
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Opening output file: %s\n",
+ ctx->avc->filename);
+
+ if (avio_open(&ctx->avc->pb, ctx->avc->filename,
+ AVIO_FLAG_WRITE) < 0) {
+ encode_lavc_fail(ctx, "encode-lavc: could not open '%s'\n",
+ ctx->avc->filename);
+ return 0;
+ }
+ }
+
+ ctx->t0 = mp_time_sec();
+
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Opening muxer: %s [%s]\n",
+ ctx->avc->oformat->long_name, ctx->avc->oformat->name);
+
+ if (avformat_write_header(ctx->avc, &ctx->foptions) < 0) {
+ encode_lavc_fail(ctx, "encode-lavc: could not write header\n");
+ return 0;
+ }
+
+ for (de = NULL; (de = av_dict_get(ctx->foptions, "", de,
+ AV_DICT_IGNORE_SUFFIX));)
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "ofopts: key '%s' not found.\n", de->key);
+ av_dict_free(&ctx->foptions);
+
+ ctx->header_written = 1;
+ return 1;
+}
+
+void encode_lavc_free(struct encode_lavc_context *ctx)
+{
+ if (!ctx)
+ return;
+
+ if (!ctx->finished)
+ encode_lavc_fail(ctx,
+ "called encode_lavc_free without encode_lavc_finish\n");
+
+ talloc_free(ctx);
+}
+
+void encode_lavc_finish(struct encode_lavc_context *ctx)
+{
+ unsigned i;
+
+ if (!ctx)
+ return;
+
+ if (ctx->finished)
+ return;
+
+ if (ctx->avc) {
+ if (ctx->header_written > 0)
+ av_write_trailer(ctx->avc); // this is allowed to fail
+
+ for (i = 0; i < ctx->avc->nb_streams; i++) {
+ switch (ctx->avc->streams[i]->codec->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ if (ctx->twopass_bytebuffer_v) {
+ char *stats = ctx->avc->streams[i]->codec->stats_out;
+ if (stats)
+ stream_write_buffer(ctx->twopass_bytebuffer_v,
+ stats, strlen(stats));
+ }
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ if (ctx->twopass_bytebuffer_a) {
+ char *stats = ctx->avc->streams[i]->codec->stats_out;
+ if (stats)
+ stream_write_buffer(ctx->twopass_bytebuffer_a,
+ stats, strlen(stats));
+ }
+ break;
+ default:
+ break;
+ }
+ avcodec_close(ctx->avc->streams[i]->codec);
+ talloc_free(ctx->avc->streams[i]->codec->stats_in);
+ av_free(ctx->avc->streams[i]->codec);
+ av_free(ctx->avc->streams[i]->info);
+ av_free(ctx->avc->streams[i]);
+ }
+
+ if (ctx->twopass_bytebuffer_v) {
+ free_stream(ctx->twopass_bytebuffer_v);
+ ctx->twopass_bytebuffer_v = NULL;
+ }
+
+ if (ctx->twopass_bytebuffer_a) {
+ free_stream(ctx->twopass_bytebuffer_a);
+ ctx->twopass_bytebuffer_a = NULL;
+ }
+
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: encoded %lld bytes\n",
+ ctx->vbytes);
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "ao-lavc: encoded %lld bytes\n",
+ ctx->abytes);
+ if (ctx->avc->pb) {
+ mp_msg(MSGT_ENCODE, MSGL_INFO,
+ "encode-lavc: muxing overhead %lld bytes\n",
+ (long long) (avio_size(ctx->avc->pb) - ctx->vbytes
+ - ctx->abytes));
+ avio_close(ctx->avc->pb);
+ }
+
+ av_free(ctx->avc);
+ }
+
+ ctx->finished = true;
+}
+
+void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps)
+{
+ ctx->vo_fps = fps;
+}
+
+static void encode_2pass_prepare(struct encode_lavc_context *ctx,
+ AVDictionary **dictp,
+ AVStream *stream, struct stream **bytebuf,
+ const char *prefix)
+{
+ if (!*bytebuf) {
+ char buf[sizeof(ctx->avc->filename) + 12];
+ AVDictionaryEntry *de = av_dict_get(ctx->voptions, "flags", NULL, 0);
+
+ snprintf(buf, sizeof(buf), "%s-%s-pass1.log", ctx->avc->filename,
+ prefix);
+ buf[sizeof(buf) - 1] = 0;
+
+ if (value_has_flag(de ? de->value : "", "pass2")) {
+ if (!(*bytebuf = stream_open(buf, NULL))) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "%s: could not open '%s', "
+ "disabling 2-pass encoding at pass 2\n", prefix, buf);
+ stream->codec->flags &= ~CODEC_FLAG_PASS2;
+ set_to_avdictionary(dictp, "flags", "-pass2");
+ } else {
+ struct bstr content = stream_read_complete(*bytebuf, NULL,
+ 1000000000);
+ if (content.start == NULL) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "%s: could not read '%s', "
+ "disabling 2-pass encoding at pass 1\n",
+ prefix, ctx->avc->filename);
+ } else {
+ content.start[content.len] = 0;
+ stream->codec->stats_in = content.start;
+ }
+ free_stream(*bytebuf);
+ *bytebuf = NULL;
+ }
+ }
+
+ if (value_has_flag(de ? de->value : "", "pass1")) {
+ if (!(*bytebuf = open_output_stream(buf, NULL))) {
+ mp_msg(
+ MSGT_ENCODE, MSGL_WARN,
+ "%s: could not open '%s', disabling "
+ "2-pass encoding at pass 1\n",
+ prefix, ctx->avc->filename);
+ set_to_avdictionary(dictp, "flags", "-pass1");
+ }
+ }
+ }
+}
+
+AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
+ enum AVMediaType mt)
+{
+ AVDictionaryEntry *de;
+ AVStream *stream = NULL;
+ char **p;
+ int i;
+
+ CHECK_FAIL(ctx, NULL);
+
+ if (ctx->header_written)
+ return NULL;
+
+ for (i = 0; i < ctx->avc->nb_streams; ++i)
+ if (ctx->avc->streams[i]->codec->codec_type == mt)
+ // already have a stream of that type, this cannot really happen
+ return NULL;
+
+ if (ctx->avc->nb_streams == 0) {
+ // if this stream isn't stream #0, allocate a dummy stream first for
+ // the next loop to use
+ if (mt == AVMEDIA_TYPE_VIDEO && ctx->audio_first) {
+ mp_msg(MSGT_ENCODE, MSGL_INFO,
+ "vo-lavc: preallocated audio stream for later use\n");
+ avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
+ }
+ if (mt == AVMEDIA_TYPE_AUDIO && ctx->video_first) {
+ mp_msg(MSGT_ENCODE, MSGL_INFO,
+ "ao-lavc: preallocated video stream for later use\n");
+ avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
+ }
+ } else {
+ // find possibly preallocated stream
+ for (i = 0; i < ctx->avc->nb_streams; ++i)
+ if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_UNKNOWN) // preallocated stream
+ stream = ctx->avc->streams[i];
+ }
+ if (!stream)
+ stream = avformat_new_stream(ctx->avc, NULL);
+
+ if (ctx->timebase.den == 0) {
+ AVRational r;
+
+ if (ctx->options->fps > 0)
+ r = av_d2q(ctx->options->fps, ctx->options->fps * 1001 + 2);
+ else if (ctx->options->autofps && ctx->vo_fps > 0) {
+ r = av_d2q(ctx->vo_fps, ctx->vo_fps * 1001 + 2);
+ mp_msg(
+ MSGT_ENCODE, MSGL_INFO, "vo-lavc: option --ofps not specified "
+ "but --oautofps is active, using guess of %u/%u\n",
+ (unsigned)r.num, (unsigned)r.den);
+ } else {
+ // we want to handle:
+ // 1/25
+ // 1001/24000
+ // 1001/30000
+ // for this we would need 120000fps...
+ // however, mpeg-4 only allows 16bit values
+ // so let's take 1001/30000 out
+ r.num = 24000;
+ r.den = 1;
+ mp_msg(
+ MSGT_ENCODE, MSGL_INFO, "vo-lavc: option --ofps not specified "
+ "and fps could not be inferred, using guess of %u/%u\n",
+ (unsigned)r.num, (unsigned)r.den);
+ }
+
+ if (ctx->vc && ctx->vc->supported_framerates)
+ r = ctx->vc->supported_framerates[av_find_nearest_q_idx(r,
+ ctx->vc->supported_framerates)];
+
+ ctx->timebase.num = r.den;
+ ctx->timebase.den = r.num;
+ }
+
+ switch (mt) {
+ case AVMEDIA_TYPE_VIDEO:
+ if (!ctx->vc) {
+ encode_lavc_fail(ctx, "vo-lavc: encoder not found\n");
+ return NULL;
+ }
+ avcodec_get_context_defaults3(stream->codec, ctx->vc);
+
+ // stream->time_base = ctx->timebase;
+ // doing this breaks mpeg2ts in ffmpeg
+ // which doesn't properly force the time base to be 90000
+ // furthermore, ffmpeg.c doesn't do this either and works
+
+ stream->codec->time_base = ctx->timebase;
+
+ ctx->voptions = NULL;
+
+ if (ctx->options->vopts)
+ for (p = ctx->options->vopts; *p; ++p)
+ if (!set_to_avdictionary(&ctx->voptions, NULL, *p))
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "vo-lavc: could not set option %s\n", *p);
+
+ de = av_dict_get(ctx->voptions, "global_quality", NULL, 0);
+ if (de)
+ set_to_avdictionary(&ctx->voptions, "flags", "+qscale");
+
+ if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
+ set_to_avdictionary(&ctx->voptions, "flags", "+global_header");
+
+ encode_2pass_prepare(ctx, &ctx->voptions, stream,
+ &ctx->twopass_bytebuffer_v,
+ "vo-lavc");
+ break;
+
+ case AVMEDIA_TYPE_AUDIO:
+ if (!ctx->ac) {
+ encode_lavc_fail(ctx, "ao-lavc: encoder not found\n");
+ return NULL;
+ }
+ avcodec_get_context_defaults3(stream->codec, ctx->ac);
+
+ stream->codec->time_base = ctx->timebase;
+
+ ctx->aoptions = NULL;
+
+ if (ctx->options->aopts)
+ for (p = ctx->options->aopts; *p; ++p)
+ if (!set_to_avdictionary(&ctx->aoptions, NULL, *p))
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "ao-lavc: could not set option %s\n", *p);
+
+ de = av_dict_get(ctx->aoptions, "global_quality", NULL, 0);
+ if (de)
+ set_to_avdictionary(&ctx->aoptions, "flags", "+qscale");
+
+ if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
+ set_to_avdictionary(&ctx->aoptions, "flags", "+global_header");
+
+ encode_2pass_prepare(ctx, &ctx->aoptions, stream,
+ &ctx->twopass_bytebuffer_a,
+ "ao-lavc");
+ break;
+
+ default:
+ encode_lavc_fail(ctx, "encode-lavc: requested invalid stream type\n");
+ return NULL;
+ }
+
+ return stream;
+}
+
+AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx,
+ AVStream *stream)
+{
+ CHECK_FAIL(ctx, NULL);
+
+ switch (stream->codec->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ return ctx->vc;
+ case AVMEDIA_TYPE_AUDIO:
+ return ctx->ac;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
+{
+ AVDictionaryEntry *de;
+ int ret;
+
+ CHECK_FAIL(ctx, -1);
+
+ switch (stream->codec->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Opening video encoder: %s [%s]\n",
+ ctx->vc->long_name, ctx->vc->name);
+
+ if (ctx->vc->capabilities & CODEC_CAP_EXPERIMENTAL) {
+ stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+ mp_msg(MSGT_ENCODE, MSGL_WARN, _(
+ "\n\n"
+ " ********************************************\n"
+ " **** Experimental VIDEO codec selected! ****\n"
+ " ********************************************\n\n"
+ "This means the output file may be broken or bad.\n"
+ "Possible reasons, problems, workarounds:\n"
+ "- Codec implementation in ffmpeg/libav is not finished yet.\n"
+ " Try updating ffmpeg or libav.\n"
+ "- Bad picture quality, blocks, blurriness.\n"
+ " Experiment with codec settings (--ovcopts) to maybe still get the\n"
+ " desired quality output at the expense of bitrate.\n"
+ "- Slow compression.\n"
+ " Bear with it.\n"
+ "- Crashes.\n"
+ " Happens. Try varying options to work around.\n"
+ "If none of this helps you, try another codec in place of %s.\n\n"),
+ ctx->vc->name);
+ }
+
+ ret = avcodec_open2(stream->codec, ctx->vc, &ctx->voptions);
+
+ // complain about all remaining options, then free the dict
+ for (de = NULL; (de = av_dict_get(ctx->voptions, "", de,
+ AV_DICT_IGNORE_SUFFIX));)
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "ovcopts: key '%s' not found.\n",
+ de->key);
+ av_dict_free(&ctx->voptions);
+
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Opening audio encoder: %s [%s]\n",
+ ctx->ac->long_name, ctx->ac->name);
+
+ if (ctx->ac->capabilities & CODEC_CAP_EXPERIMENTAL) {
+ stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+ mp_msg(MSGT_ENCODE, MSGL_WARN, _(
+ "\n\n"
+ " ********************************************\n"
+ " **** Experimental AUDIO codec selected! ****\n"
+ " ********************************************\n\n"
+ "This means the output file may be broken or bad.\n"
+ "Possible reasons, problems, workarounds:\n"
+ "- Codec implementation in ffmpeg/libav is not finished yet.\n"
+ " Try updating ffmpeg or libav.\n"
+ "- Bad sound quality, noise, clicking, whistles, choppiness.\n"
+ " Experiment with codec settings (--oacopts) to maybe still get the\n"
+ " desired quality output at the expense of bitrate.\n"
+ "- Slow compression.\n"
+ " Bear with it.\n"
+ "- Crashes.\n"
+ " Happens. Try varying options to work around.\n"
+ "If none of this helps you, try another codec in place of %s.\n\n"),
+ ctx->ac->name);
+ }
+ ret = avcodec_open2(stream->codec, ctx->ac, &ctx->aoptions);
+
+ // complain about all remaining options, then free the dict
+ for (de = NULL; (de = av_dict_get(ctx->aoptions, "", de,
+ AV_DICT_IGNORE_SUFFIX));)
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "oacopts: key '%s' not found.\n",
+ de->key);
+ av_dict_free(&ctx->aoptions);
+
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ if (ret < 0)
+ encode_lavc_fail(ctx,
+ "unable to open encoder (see above for the cause)");
+
+ return ret;
+}
+
+void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream)
+{
+ CHECK_FAIL(ctx, );
+
+ switch (stream->codec->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ if (ctx->twopass_bytebuffer_v)
+ if (stream->codec->stats_out)
+ stream_write_buffer(ctx->twopass_bytebuffer_v,
+ stream->codec->stats_out,
+ strlen(stream->codec->stats_out));
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ if (ctx->twopass_bytebuffer_a)
+ if (stream->codec->stats_out)
+ stream_write_buffer(ctx->twopass_bytebuffer_a,
+ stream->codec->stats_out,
+ strlen(stream->codec->stats_out));
+ break;
+ default:
+ break;
+ }
+}
+
+int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet)
+{
+ int r;
+
+ CHECK_FAIL(ctx, -1);
+
+ if (ctx->header_written <= 0)
+ return -1;
+
+ mp_msg(
+ MSGT_ENCODE, MSGL_DBG2,
+ "encode-lavc: write frame: stream %d ptsi %d (%f) dtsi %d (%f) size %d\n",
+ (int)packet->stream_index,
+ (int)packet->pts,
+ packet->pts
+ * (double)ctx->avc->streams[packet->stream_index]->time_base.num
+ / (double)ctx->avc->streams[packet->stream_index]->time_base.den,
+ (int)packet->dts,
+ packet->dts
+ * (double)ctx->avc->streams[packet->stream_index]->time_base.num
+ / (double)ctx->avc->streams[packet->stream_index]->time_base.den,
+ (int)packet->size);
+
+ switch (ctx->avc->streams[packet->stream_index]->codec->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ ctx->vbytes += packet->size;
+ ++ctx->frames;
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ ctx->abytes += packet->size;
+ ctx->audioseconds += packet->duration
+ * (double)ctx->avc->streams[packet->stream_index]->time_base.num
+ / (double)ctx->avc->streams[packet->stream_index]->time_base.den;
+ break;
+ default:
+ break;
+ }
+
+ r = av_interleaved_write_frame(ctx->avc, packet);
+
+ return r;
+}
+
+int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx,
+ enum PixelFormat pix_fmt)
+{
+ CHECK_FAIL(ctx, 0);
+
+ if (!ctx->vc)
+ return 0;
+ if (pix_fmt == PIX_FMT_NONE)
+ return 0;
+
+ if (!ctx->vc->pix_fmts)
+ return VFCAP_CSP_SUPPORTED;
+ else {
+ const enum PixelFormat *p;
+ for (p = ctx->vc->pix_fmts; *p >= 0; ++p) {
+ if (pix_fmt == *p)
+ return VFCAP_CSP_SUPPORTED;
+ }
+ }
+ return 0;
+}
+
+void encode_lavc_discontinuity(struct encode_lavc_context *ctx)
+{
+ if (!ctx)
+ return;
+
+ CHECK_FAIL(ctx, );
+
+ ctx->audio_pts_offset = MP_NOPTS_VALUE;
+ ctx->last_video_in_pts = MP_NOPTS_VALUE;
+ ctx->discontinuity_pts_offset = MP_NOPTS_VALUE;
+}
+
+static void encode_lavc_printoptions(void *obj, const char *indent,
+ const char *subindent, const char *unit,
+ int filter_and, int filter_eq)
+{
+ const AVOption *opt = NULL;
+ char optbuf[32];
+ while ((opt = av_opt_next(obj, opt))) {
+ // if flags are 0, it simply hasn't been filled in yet and may be
+ // potentially useful
+ if (opt->flags)
+ if ((opt->flags & filter_and) != filter_eq)
+ continue;
+ /* Don't print CONST's on level one.
+ * Don't print anything but CONST's on level two.
+ * Only print items from the requested unit.
+ */
+ if (!unit && opt->type == AV_OPT_TYPE_CONST)
+ continue;
+ else if (unit && opt->type != AV_OPT_TYPE_CONST)
+ continue;
+ else if (unit && opt->type == AV_OPT_TYPE_CONST
+ && strcmp(unit, opt->unit))
+ continue;
+ else if (unit && opt->type == AV_OPT_TYPE_CONST)
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "%s", subindent);
+ else
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "%s", indent);
+
+ switch (opt->type) {
+ case AV_OPT_TYPE_FLAGS:
+ snprintf(optbuf, sizeof(optbuf), "%s=<flags>", opt->name);
+ break;
+ case AV_OPT_TYPE_INT:
+ snprintf(optbuf, sizeof(optbuf), "%s=<int>", opt->name);
+ break;
+ case AV_OPT_TYPE_INT64:
+ snprintf(optbuf, sizeof(optbuf), "%s=<int64>", opt->name);
+ break;
+ case AV_OPT_TYPE_DOUBLE:
+ snprintf(optbuf, sizeof(optbuf), "%s=<double>", opt->name);
+ break;
+ case AV_OPT_TYPE_FLOAT:
+ snprintf(optbuf, sizeof(optbuf), "%s=<float>", opt->name);
+ break;
+ case AV_OPT_TYPE_STRING:
+ snprintf(optbuf, sizeof(optbuf), "%s=<string>", opt->name);
+ break;
+ case AV_OPT_TYPE_RATIONAL:
+ snprintf(optbuf, sizeof(optbuf), "%s=<rational>", opt->name);
+ break;
+ case AV_OPT_TYPE_BINARY:
+ snprintf(optbuf, sizeof(optbuf), "%s=<binary>", opt->name);
+ break;
+ case AV_OPT_TYPE_CONST:
+ snprintf(optbuf, sizeof(optbuf), " [+-]%s", opt->name);
+ break;
+ default:
+ snprintf(optbuf, sizeof(optbuf), "%s", opt->name);
+ break;
+ }
+ optbuf[sizeof(optbuf) - 1] = 0;
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "%-32s ", optbuf);
+ if (opt->help)
+ mp_msg(MSGT_ENCODE, MSGL_INFO, " %s", opt->help);
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "\n");
+ if (opt->unit && opt->type != AV_OPT_TYPE_CONST)
+ encode_lavc_printoptions(obj, indent, subindent, opt->unit,
+ filter_and, filter_eq);
+ }
+}
+
+bool encode_lavc_showhelp(struct MPOpts *opts)
+{
+ bool help_output = false;
+ if (av_codec_next(NULL) == NULL)
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "NO CODECS\n");
+#define CHECKS(str) ((str) && \
+ strcmp((str), "help") == 0 ? (help_output |= 1) : 0)
+#define CHECKV(strv) ((strv) && (strv)[0] && \
+ strcmp((strv)[0], "help") == 0 ? (help_output |= 1) : 0)
+ if (CHECKS(opts->encode_output.format)) {
+ AVOutputFormat *c = NULL;
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output formats:\n");
+ while ((c = av_oformat_next(c)))
+ mp_msg(MSGT_ENCODE, MSGL_INFO, " --of=%-13s %s\n", c->name,
+ c->long_name ? c->long_name : "");
+ av_free(c);
+ }
+ if (CHECKV(opts->encode_output.fopts)) {
+ AVFormatContext *c = avformat_alloc_context();
+ AVOutputFormat *format = NULL;
+ mp_msg(MSGT_ENCODE, MSGL_INFO,
+ "Available output format ctx->options:\n");
+ encode_lavc_printoptions(c, " --ofopts=", " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM);
+ av_free(c);
+ while ((format = av_oformat_next(format))) {
+ if (format->priv_class) {
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for --of=%s:\n",
+ format->name);
+ encode_lavc_printoptions(&format->priv_class, " --ofopts=",
+ " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM);
+ }
+ }
+ }
+ if (CHECKV(opts->encode_output.vopts)) {
+ AVCodecContext *c = avcodec_alloc_context3(NULL);
+ AVCodec *codec = NULL;
+ mp_msg(MSGT_ENCODE, MSGL_INFO,
+ "Available output video codec ctx->options:\n");
+ encode_lavc_printoptions(
+ c, " --ovcopts=", " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_VIDEO_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_VIDEO_PARAM);
+ av_free(c);
+ while ((codec = av_codec_next(codec))) {
+ if (!av_codec_is_encoder(codec))
+ continue;
+ if (codec->type != AVMEDIA_TYPE_VIDEO)
+ continue;
+ if (opts->encode_output.vcodec && opts->encode_output.vcodec[0] &&
+ strcmp(opts->encode_output.vcodec, codec->name) != 0)
+ continue;
+ if (codec->priv_class) {
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for --ovc=%s:\n",
+ codec->name);
+ encode_lavc_printoptions(
+ &codec->priv_class, " --ovcopts=",
+ " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_VIDEO_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_VIDEO_PARAM);
+ }
+ }
+ }
+ if (CHECKV(opts->encode_output.aopts)) {
+ AVCodecContext *c = avcodec_alloc_context3(NULL);
+ AVCodec *codec = NULL;
+ mp_msg(MSGT_ENCODE, MSGL_INFO,
+ "Available output audio codec ctx->options:\n");
+ encode_lavc_printoptions(
+ c, " --oacopts=", " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_AUDIO_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_AUDIO_PARAM);
+ av_free(c);
+ while ((codec = av_codec_next(codec))) {
+ if (!av_codec_is_encoder(codec))
+ continue;
+ if (codec->type != AVMEDIA_TYPE_AUDIO)
+ continue;
+ if (opts->encode_output.acodec && opts->encode_output.acodec[0] &&
+ strcmp(opts->encode_output.acodec, codec->name) != 0)
+ continue;
+ if (codec->priv_class) {
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for --oac=%s:\n",
+ codec->name);
+ encode_lavc_printoptions(
+ &codec->priv_class, " --oacopts=",
+ " ", NULL,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_AUDIO_PARAM,
+ AV_OPT_FLAG_ENCODING_PARAM |
+ AV_OPT_FLAG_AUDIO_PARAM);
+ }
+ }
+ }
+ if (CHECKS(opts->encode_output.vcodec)) {
+ AVCodec *c = NULL;
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output video codecs:\n");
+ while ((c = av_codec_next(c))) {
+ if (!av_codec_is_encoder(c))
+ continue;
+ if (c->type != AVMEDIA_TYPE_VIDEO)
+ continue;
+ mp_msg(MSGT_ENCODE, MSGL_INFO, " --ovc=%-12s %s\n", c->name,
+ c->long_name ? c->long_name : "");
+ }
+ av_free(c);
+ }
+ if (CHECKS(opts->encode_output.acodec)) {
+ AVCodec *c = NULL;
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output audio codecs:\n");
+ while ((c = av_codec_next(c))) {
+ if (!av_codec_is_encoder(c))
+ continue;
+ if (c->type != AVMEDIA_TYPE_AUDIO)
+ continue;
+ mp_msg(MSGT_ENCODE, MSGL_INFO, " --oac=%-12s %s\n", c->name,
+ c->long_name ? c->long_name : "");
+ }
+ av_free(c);
+ }
+ return help_output;
+}
+
+double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream)
+{
+ CHECK_FAIL(ctx, 0);
+
+ switch (stream->codec->codec_type) {
+ case AVMEDIA_TYPE_VIDEO:
+ return ctx->options->voffset;
+ case AVMEDIA_TYPE_AUDIO:
+ return ctx->options->aoffset;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int encode_lavc_getstatus(struct encode_lavc_context *ctx,
+ char *buf, int bufsize,
+ float relative_position)
+{
+ double now = mp_time_sec();
+ float minutes, megabytes, fps, x;
+ float f = FFMAX(0.0001, relative_position);
+ if (!ctx)
+ return -1;
+
+ CHECK_FAIL(ctx, -1);
+
+ minutes = (now - ctx->t0) / 60.0 * (1 - f) / f;
+ megabytes = ctx->avc->pb ? (avio_size(ctx->avc->pb) / 1048576.0 / f) : 0;
+ fps = ctx->frames / (now - ctx->t0);
+ x = ctx->audioseconds / (now - ctx->t0);
+ if (ctx->frames)
+ snprintf(buf, bufsize, "{%.1fmin %.1ffps %.1fMB}",
+ minutes, fps, megabytes);
+ else if (ctx->audioseconds)
+ snprintf(buf, bufsize, "{%.1fmin %.2fx %.1fMB}",
+ minutes, x, megabytes);
+ else
+ snprintf(buf, bufsize, "{%.1fmin %.1fMB}",
+ minutes, megabytes);
+ buf[bufsize - 1] = 0;
+ return 0;
+}
+
+void encode_lavc_expect_stream(struct encode_lavc_context *ctx,
+ enum AVMediaType mt)
+{
+ CHECK_FAIL(ctx, );
+
+ switch (mt) {
+ case AVMEDIA_TYPE_VIDEO:
+ ctx->expect_video = true;
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ ctx->expect_audio = true;
+ break;
+ }
+}
+
+bool encode_lavc_didfail(struct encode_lavc_context *ctx)
+{
+ return ctx && ctx->failed;
+}
+
+void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ mp_msg_va(MSGT_ENCODE, MSGL_ERR, format, va);
+ if (ctx->failed)
+ return;
+ ctx->failed = true;
+ encode_lavc_finish(ctx);
+}
+
+bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
+ AVStream *stream, enum mp_csp csp)
+{
+ CHECK_FAIL(ctx, NULL);
+
+ if (ctx->header_written) {
+ if (stream->codec->colorspace != mp_csp_to_avcol_spc(csp))
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "encode-lavc: can not change color space during encoding\n");
+ return false;
+ }
+
+ stream->codec->colorspace = mp_csp_to_avcol_spc(csp);
+ return true;
+}
+
+bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
+ AVStream *stream, enum mp_csp_levels lev)
+{
+ CHECK_FAIL(ctx, NULL);
+
+ if (ctx->header_written) {
+ if (stream->codec->color_range != mp_csp_levels_to_avcol_range(lev))
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "encode-lavc: can not change color space during encoding\n");
+ return false;
+ }
+
+ stream->codec->color_range = mp_csp_levels_to_avcol_range(lev);
+ return true;
+}
+
+enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
+ AVStream *stream)
+{
+ CHECK_FAIL(ctx, 0);
+
+ return avcol_spc_to_mp_csp(stream->codec->colorspace);
+}
+
+enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
+ AVStream *stream)
+{
+ CHECK_FAIL(ctx, 0);
+
+ return avcol_range_to_mp_csp_levels(stream->codec->color_range);
+}
+
+// vim: ts=4 sw=4 et
diff --git a/mpvcore/encode_lavc.h b/mpvcore/encode_lavc.h
new file mode 100644
index 0000000000..f47825e1d7
--- /dev/null
+++ b/mpvcore/encode_lavc.h
@@ -0,0 +1,101 @@
+/*
+ * muxing using libavformat
+ * Copyright (C) 2011 Rudolf Polzer <divVerent@xonotic.org>
+ *
+ * This file is part of mpv.
+ *
+ * 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.
+ */
+
+#ifndef MPLAYER_ENCODE_LAVC_H
+#define MPLAYER_ENCODE_LAVC_H
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/avstring.h>
+#include <libavutil/pixfmt.h>
+#include <libavutil/opt.h>
+#include <libavutil/mathematics.h>
+
+#include "encode.h"
+#include "video/csputils.h"
+
+struct encode_lavc_context {
+ struct encode_output_conf *options;
+
+ float vo_fps;
+
+ // these are processed from the options
+ AVFormatContext *avc;
+ AVRational timebase;
+ AVCodec *vc;
+ AVCodec *ac;
+ AVDictionary *foptions;
+ AVDictionary *aoptions;
+ AVDictionary *voptions;
+
+ // values created during encoding
+ int header_written; // -1 means currently writing
+
+ // sync to audio mode
+ double audio_pts_offset;
+ double last_video_in_pts;
+
+ // anti discontinuity mode
+ double next_in_pts;
+ double discontinuity_pts_offset;
+
+ long long abytes;
+ long long vbytes;
+ struct stream *twopass_bytebuffer_a;
+ struct stream *twopass_bytebuffer_v;
+ double t0;
+ unsigned int frames;
+ double audioseconds;
+
+ bool expect_video;
+ bool expect_audio;
+ bool video_first;
+ bool audio_first;
+
+ // has encoding failed?
+ bool failed;
+ bool finished;
+};
+
+// interface for vo/ao drivers
+AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx, enum AVMediaType mt);
+void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream);
+int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet);
+int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, enum PixelFormat format);
+AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx, AVStream *stream);
+int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream);
+int encode_lavc_available(struct encode_lavc_context *ctx);
+int encode_lavc_timesyncfailed(struct encode_lavc_context *ctx);
+int encode_lavc_start(struct encode_lavc_context *ctx); // returns 1 on success
+int encode_lavc_oformat_flags(struct encode_lavc_context *ctx);
+double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream);
+void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...); // report failure of encoding
+
+bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
+ AVStream *stream, enum mp_csp csp);
+bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
+ AVStream *stream, enum mp_csp_levels lev);
+enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
+ AVStream *stream);
+enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
+ AVStream *stream);
+
+#endif
diff --git a/mpvcore/input/input.c b/mpvcore/input/input.c
new file mode 100644
index 0000000000..ae1358a76d
--- /dev/null
+++ b/mpvcore/input/input.c
@@ -0,0 +1,2284 @@
+/*
+ * 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 "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <libavutil/avstring.h>
+#include <libavutil/common.h>
+
+#include "osdep/io.h"
+#include "osdep/getch2.h"
+
+#include "input.h"
+#include "keycodes.h"
+#include "osdep/timer.h"
+#include "core/mp_msg.h"
+#include "core/m_config.h"
+#include "core/m_option.h"
+#include "core/path.h"
+#include "talloc.h"
+#include "core/options.h"
+#include "core/bstr.h"
+#include "stream/stream.h"
+#include "core/mp_common.h"
+
+#include "joystick.h"
+
+#ifdef CONFIG_LIRC
+#include "lirc.h"
+#endif
+
+#ifdef CONFIG_LIRCC
+#include <lirc/lircc.h>
+#endif
+
+#ifdef CONFIG_COCOA
+#include "osdep/macosx_events.h"
+#endif
+
+#define MP_MAX_KEY_DOWN 4
+
+struct cmd_bind {
+ int keys[MP_MAX_KEY_DOWN];
+ int num_keys;
+ char *cmd;
+ char *location; // filename/line number of definition
+ bool is_builtin;
+ struct cmd_bind_section *owner;
+};
+
+struct key_name {
+ int key;
+ char *name;
+};
+
+/* This array defines all known commands.
+ * The first field is an id used to recognize the command.
+ * The second is the command name used in slave mode and input.conf.
+ * Then comes the definition of each argument, first mandatory arguments
+ * (ARG_INT, ARG_FLOAT, ARG_STRING) if any, then optional arguments
+ * (OARG_INT(default), etc) if any. The command will be given the default
+ * argument value if the user didn't give enough arguments to specify it.
+ * A command can take a maximum of MP_CMD_MAX_ARGS arguments (10).
+ */
+
+#define ARG_INT { .type = {"", NULL, &m_option_type_int} }
+#define ARG_FLOAT { .type = {"", NULL, &m_option_type_float} }
+#define ARG_DOUBLE { .type = {"", NULL, &m_option_type_double} }
+#define ARG_STRING { .type = {"", NULL, &m_option_type_string} }
+#define ARG_CHOICE(c) { .type = {"", NULL, &m_option_type_choice, \
+ M_CHOICES(c)} }
+#define ARG_TIME { .type = {"", NULL, &m_option_type_time} }
+
+#define OARG_DOUBLE(def) { .type = {"", NULL, &m_option_type_double}, \
+ .optional = true, .v.d = def }
+#define OARG_INT(def) { .type = {"", NULL, &m_option_type_int}, \
+ .optional = true, .v.i = def }
+#define OARG_CHOICE(def, c) { .type = {"", NULL, &m_option_type_choice, \
+ M_CHOICES(c)}, \
+ .optional = true, .v.i = def }
+
+static int parse_cycle_dir(const struct m_option *opt, struct bstr name,
+ struct bstr param, void *dst);
+static const struct m_option_type m_option_type_cycle_dir = {
+ .name = "up|down",
+ .parse = parse_cycle_dir,
+};
+
+static const mp_cmd_t mp_cmds[] = {
+ { MP_CMD_IGNORE, "ignore", },
+
+ { MP_CMD_RADIO_STEP_CHANNEL, "radio_step_channel", { ARG_INT } },
+ { MP_CMD_RADIO_SET_CHANNEL, "radio_set_channel", { ARG_STRING } },
+ { MP_CMD_RADIO_SET_FREQ, "radio_set_freq", { ARG_FLOAT } },
+ { MP_CMD_RADIO_STEP_FREQ, "radio_step_freq", {ARG_FLOAT } },
+
+ { MP_CMD_SEEK, "seek", {
+ ARG_TIME,
+ OARG_CHOICE(0, ({"relative", 0}, {"0", 0},
+ {"absolute-percent", 1}, {"1", 1},
+ {"absolute", 2}, {"2", 2})),
+ OARG_CHOICE(0, ({"default-precise", 0}, {"0", 0},
+ {"exact", 1}, {"1", 1},
+ {"keyframes", -1}, {"-1", -1})),
+ }},
+ { MP_CMD_SPEED_MULT, "speed_mult", { ARG_DOUBLE } },
+ { MP_CMD_QUIT, "quit", { OARG_INT(0) } },
+ { MP_CMD_QUIT_WATCH_LATER, "quit_watch_later", },
+ { MP_CMD_STOP, "stop", },
+ { MP_CMD_FRAME_STEP, "frame_step", },
+ { MP_CMD_FRAME_BACK_STEP, "frame_back_step", },
+ { MP_CMD_PLAYLIST_NEXT, "playlist_next", {
+ OARG_CHOICE(0, ({"weak", 0}, {"0", 0},
+ {"force", 1}, {"1", 1})),
+ }},
+ { MP_CMD_PLAYLIST_PREV, "playlist_prev", {
+ OARG_CHOICE(0, ({"weak", 0}, {"0", 0},
+ {"force", 1}, {"1", 1})),
+ }},
+ { MP_CMD_SUB_STEP, "sub_step", { ARG_INT } },
+ { MP_CMD_OSD, "osd", { OARG_INT(-1) } },
+ { MP_CMD_PRINT_TEXT, "print_text", { ARG_STRING } },
+ { MP_CMD_SHOW_TEXT, "show_text", { ARG_STRING, OARG_INT(-1), OARG_INT(0) } },
+ { MP_CMD_SHOW_PROGRESS, "show_progress", },
+ { MP_CMD_SUB_ADD, "sub_add", { ARG_STRING } },
+ { MP_CMD_SUB_REMOVE, "sub_remove", { OARG_INT(-1) } },
+ { MP_CMD_SUB_RELOAD, "sub_reload", { OARG_INT(-1) } },
+
+ { MP_CMD_TV_START_SCAN, "tv_start_scan", },
+ { MP_CMD_TV_STEP_CHANNEL, "tv_step_channel", { ARG_INT } },
+ { MP_CMD_TV_STEP_NORM, "tv_step_norm", },
+ { MP_CMD_TV_STEP_CHANNEL_LIST, "tv_step_chanlist", },
+ { MP_CMD_TV_SET_CHANNEL, "tv_set_channel", { ARG_STRING } },
+ { MP_CMD_TV_LAST_CHANNEL, "tv_last_channel", },
+ { MP_CMD_TV_SET_FREQ, "tv_set_freq", { ARG_FLOAT } },
+ { MP_CMD_TV_STEP_FREQ, "tv_step_freq", { ARG_FLOAT } },
+ { MP_CMD_TV_SET_NORM, "tv_set_norm", { ARG_STRING } },
+
+ { MP_CMD_DVB_SET_CHANNEL, "dvb_set_channel", { ARG_INT, ARG_INT } },
+
+ { MP_CMD_SCREENSHOT, "screenshot", {
+ OARG_CHOICE(2, ({"video", 0},
+ {"window", 1},
+ {"subtitles", 2})),
+ OARG_CHOICE(0, ({"single", 0},
+ {"each-frame", 1})),
+ }},
+ { MP_CMD_SCREENSHOT_TO_FILE, "screenshot_to_file", {
+ ARG_STRING,
+ OARG_CHOICE(2, ({"video", 0},
+ {"window", 1},
+ {"subtitles", 2})),
+ }},
+ { MP_CMD_LOADFILE, "loadfile", {
+ ARG_STRING,
+ OARG_CHOICE(0, ({"replace", 0}, {"0", 0},
+ {"append", 1}, {"1", 1})),
+ }},
+ { MP_CMD_LOADLIST, "loadlist", {
+ ARG_STRING,
+ OARG_CHOICE(0, ({"replace", 0}, {"0", 0},
+ {"append", 1}, {"1", 1})),
+ }},
+ { MP_CMD_PLAYLIST_CLEAR, "playlist_clear", },
+ { MP_CMD_PLAYLIST_REMOVE, "playlist_remove", { ARG_INT } },
+ { MP_CMD_PLAYLIST_MOVE, "playlist_move", { ARG_INT, ARG_INT } },
+ { MP_CMD_RUN, "run", { ARG_STRING } },
+
+ { MP_CMD_KEYDOWN_EVENTS, "key_down_event", { ARG_INT } },
+ { MP_CMD_SET, "set", { ARG_STRING, ARG_STRING } },
+ { MP_CMD_GET_PROPERTY, "get_property", { ARG_STRING } },
+ { MP_CMD_ADD, "add", { ARG_STRING, OARG_DOUBLE(0) } },
+ { MP_CMD_CYCLE, "cycle", {
+ ARG_STRING,
+ { .type = {"", NULL, &m_option_type_cycle_dir},
+ .optional = true,
+ .v.d = 1 },
+ }},
+
+ { MP_CMD_ENABLE_INPUT_SECTION, "enable_section", {
+ ARG_STRING,
+ OARG_CHOICE(0, ({"default", 0},
+ {"exclusive", 1})),
+ }},
+ { MP_CMD_DISABLE_INPUT_SECTION, "disable_section", { ARG_STRING } },
+
+ { MP_CMD_AF, "af", { ARG_STRING, ARG_STRING } },
+
+ { MP_CMD_VF, "vf", { ARG_STRING, ARG_STRING } },
+
+ { MP_CMD_VO_CMDLINE, "vo_cmdline", { ARG_STRING } },
+
+ {0}
+};
+
+// Map legacy commands to proper commands
+struct legacy_cmd {
+ const char *old, *new;
+};
+static const struct legacy_cmd legacy_cmds[] = {
+ {"loop", "cycle loop"},
+ {"seek_chapter", "add chapter"},
+ {"switch_angle", "cycle angle"},
+ {"pause", "cycle pause"},
+ {"volume", "add volume"},
+ {"mute", "cycle mute"},
+ {"audio_delay", "add audio-delay"},
+ {"switch_audio", "cycle audio"},
+ {"balance", "add balance"},
+ {"vo_fullscreen", "cycle fullscreen"},
+ {"panscan", "add panscan"},
+ {"vo_ontop", "cycle ontop"},
+ {"vo_border", "cycle border"},
+ {"frame_drop", "cycle framedrop"},
+ {"gamma", "add gamma"},
+ {"brightness", "add brightness"},
+ {"contrast", "add contrast"},
+ {"saturation", "add saturation"},
+ {"hue", "add hue"},
+ {"switch_vsync", "cycle vsync"},
+ {"sub_load", "sub_add"},
+ {"sub_select", "cycle sub"},
+ {"sub_pos", "add sub-pos"},
+ {"sub_delay", "add sub-delay"},
+ {"sub_visibility", "cycle sub-visibility"},
+ {"forced_subs_only", "cycle sub-forced-only"},
+ {"sub_scale", "add sub-scale"},
+ {"ass_use_margins", "cycle ass-use-margins"},
+ {"tv_set_brightness", "add tv-brightness"},
+ {"tv_set_hue", "add tv-hue"},
+ {"tv_set_saturation", "add tv-saturation"},
+ {"tv_set_contrast", "add tv-contrast"},
+ {"step_property_osd", "cycle"},
+ {"step_property", "no-osd cycle"},
+ {"set_property", "no-osd set"},
+ {"set_property_osd", "set"},
+ {"speed_set", "set speed"},
+ {"osd_show_text", "show_text"},
+ {"osd_show_property_text", "show_text"},
+ {"osd_show_progression", "show_progress"},
+ {"show_chapters_osd", "show_text ${chapter-list}"},
+ {"!show_chapters", "show_text ${chapter-list}"},
+ {"show_tracks_osd", "show_text ${track-list}"},
+ {"!show_tracks", "show_text ${track-list}"},
+ {"!show_playlist", "show_text ${playlist}"},
+
+ // Approximate (can fail if user added additional whitespace)
+ {"pt_step 1", "playlist_next"},
+ {"pt_step -1", "playlist_prev"},
+ // Switch_ratio without argument resets aspect ratio
+ {"switch_ratio ", "set aspect "},
+ {"switch_ratio", "set aspect 0"},
+ {0}
+};
+
+
+/// The names of the keys as used in input.conf
+/// If you add some new keys, you also need to add them here
+
+static const struct key_name key_names[] = {
+ { ' ', "SPACE" },
+ { '#', "SHARP" },
+ { MP_KEY_ENTER, "ENTER" },
+ { MP_KEY_TAB, "TAB" },
+ { MP_KEY_BACKSPACE, "BS" },
+ { MP_KEY_DELETE, "DEL" },
+ { MP_KEY_INSERT, "INS" },
+ { MP_KEY_HOME, "HOME" },
+ { MP_KEY_END, "END" },
+ { MP_KEY_PAGE_UP, "PGUP" },
+ { MP_KEY_PAGE_DOWN, "PGDWN" },
+ { MP_KEY_ESC, "ESC" },
+ { MP_KEY_PRINT, "PRINT" },
+ { MP_KEY_RIGHT, "RIGHT" },
+ { MP_KEY_LEFT, "LEFT" },
+ { MP_KEY_DOWN, "DOWN" },
+ { MP_KEY_UP, "UP" },
+ { MP_KEY_F+1, "F1" },
+ { MP_KEY_F+2, "F2" },
+ { MP_KEY_F+3, "F3" },
+ { MP_KEY_F+4, "F4" },
+ { MP_KEY_F+5, "F5" },
+ { MP_KEY_F+6, "F6" },
+ { MP_KEY_F+7, "F7" },
+ { MP_KEY_F+8, "F8" },
+ { MP_KEY_F+9, "F9" },
+ { MP_KEY_F+10, "F10" },
+ { MP_KEY_F+11, "F11" },
+ { MP_KEY_F+12, "F12" },
+ { MP_KEY_KP0, "KP0" },
+ { MP_KEY_KP1, "KP1" },
+ { MP_KEY_KP2, "KP2" },
+ { MP_KEY_KP3, "KP3" },
+ { MP_KEY_KP4, "KP4" },
+ { MP_KEY_KP5, "KP5" },
+ { MP_KEY_KP6, "KP6" },
+ { MP_KEY_KP7, "KP7" },
+ { MP_KEY_KP8, "KP8" },
+ { MP_KEY_KP9, "KP9" },
+ { MP_KEY_KPDEL, "KP_DEL" },
+ { MP_KEY_KPDEC, "KP_DEC" },
+ { MP_KEY_KPINS, "KP_INS" },
+ { MP_KEY_KPENTER, "KP_ENTER" },
+ { MP_MOUSE_BTN0, "MOUSE_BTN0" },
+ { MP_MOUSE_BTN1, "MOUSE_BTN1" },
+ { MP_MOUSE_BTN2, "MOUSE_BTN2" },
+ { MP_MOUSE_BTN3, "MOUSE_BTN3" },
+ { MP_MOUSE_BTN4, "MOUSE_BTN4" },
+ { MP_MOUSE_BTN5, "MOUSE_BTN5" },
+ { MP_MOUSE_BTN6, "MOUSE_BTN6" },
+ { MP_MOUSE_BTN7, "MOUSE_BTN7" },
+ { MP_MOUSE_BTN8, "MOUSE_BTN8" },
+ { MP_MOUSE_BTN9, "MOUSE_BTN9" },
+ { MP_MOUSE_BTN10, "MOUSE_BTN10" },
+ { MP_MOUSE_BTN11, "MOUSE_BTN11" },
+ { MP_MOUSE_BTN12, "MOUSE_BTN12" },
+ { MP_MOUSE_BTN13, "MOUSE_BTN13" },
+ { MP_MOUSE_BTN14, "MOUSE_BTN14" },
+ { MP_MOUSE_BTN15, "MOUSE_BTN15" },
+ { MP_MOUSE_BTN16, "MOUSE_BTN16" },
+ { MP_MOUSE_BTN17, "MOUSE_BTN17" },
+ { MP_MOUSE_BTN18, "MOUSE_BTN18" },
+ { MP_MOUSE_BTN19, "MOUSE_BTN19" },
+ { MP_MOUSE_BTN0_DBL, "MOUSE_BTN0_DBL" },
+ { MP_MOUSE_BTN1_DBL, "MOUSE_BTN1_DBL" },
+ { MP_MOUSE_BTN2_DBL, "MOUSE_BTN2_DBL" },
+ { MP_MOUSE_BTN3_DBL, "MOUSE_BTN3_DBL" },
+ { MP_MOUSE_BTN4_DBL, "MOUSE_BTN4_DBL" },
+ { MP_MOUSE_BTN5_DBL, "MOUSE_BTN5_DBL" },
+ { MP_MOUSE_BTN6_DBL, "MOUSE_BTN6_DBL" },
+ { MP_MOUSE_BTN7_DBL, "MOUSE_BTN7_DBL" },
+ { MP_MOUSE_BTN8_DBL, "MOUSE_BTN8_DBL" },
+ { MP_MOUSE_BTN9_DBL, "MOUSE_BTN9_DBL" },
+ { MP_MOUSE_BTN10_DBL, "MOUSE_BTN10_DBL" },
+ { MP_MOUSE_BTN11_DBL, "MOUSE_BTN11_DBL" },
+ { MP_MOUSE_BTN12_DBL, "MOUSE_BTN12_DBL" },
+ { MP_MOUSE_BTN13_DBL, "MOUSE_BTN13_DBL" },
+ { MP_MOUSE_BTN14_DBL, "MOUSE_BTN14_DBL" },
+ { MP_MOUSE_BTN15_DBL, "MOUSE_BTN15_DBL" },
+ { MP_MOUSE_BTN16_DBL, "MOUSE_BTN16_DBL" },
+ { MP_MOUSE_BTN17_DBL, "MOUSE_BTN17_DBL" },
+ { MP_MOUSE_BTN18_DBL, "MOUSE_BTN18_DBL" },
+ { MP_MOUSE_BTN19_DBL, "MOUSE_BTN19_DBL" },
+ { MP_JOY_AXIS1_MINUS, "JOY_UP" },
+ { MP_JOY_AXIS1_PLUS, "JOY_DOWN" },
+ { MP_JOY_AXIS0_MINUS, "JOY_LEFT" },
+ { MP_JOY_AXIS0_PLUS, "JOY_RIGHT" },
+
+ { MP_JOY_AXIS0_PLUS, "JOY_AXIS0_PLUS" },
+ { MP_JOY_AXIS0_MINUS, "JOY_AXIS0_MINUS" },
+ { MP_JOY_AXIS1_PLUS, "JOY_AXIS1_PLUS" },
+ { MP_JOY_AXIS1_MINUS, "JOY_AXIS1_MINUS" },
+ { MP_JOY_AXIS2_PLUS, "JOY_AXIS2_PLUS" },
+ { MP_JOY_AXIS2_MINUS, "JOY_AXIS2_MINUS" },
+ { MP_JOY_AXIS3_PLUS, "JOY_AXIS3_PLUS" },
+ { MP_JOY_AXIS3_MINUS, "JOY_AXIS3_MINUS" },
+ { MP_JOY_AXIS4_PLUS, "JOY_AXIS4_PLUS" },
+ { MP_JOY_AXIS4_MINUS, "JOY_AXIS4_MINUS" },
+ { MP_JOY_AXIS5_PLUS, "JOY_AXIS5_PLUS" },
+ { MP_JOY_AXIS5_MINUS, "JOY_AXIS5_MINUS" },
+ { MP_JOY_AXIS6_PLUS, "JOY_AXIS6_PLUS" },
+ { MP_JOY_AXIS6_MINUS, "JOY_AXIS6_MINUS" },
+ { MP_JOY_AXIS7_PLUS, "JOY_AXIS7_PLUS" },
+ { MP_JOY_AXIS7_MINUS, "JOY_AXIS7_MINUS" },
+ { MP_JOY_AXIS8_PLUS, "JOY_AXIS8_PLUS" },
+ { MP_JOY_AXIS8_MINUS, "JOY_AXIS8_MINUS" },
+ { MP_JOY_AXIS9_PLUS, "JOY_AXIS9_PLUS" },
+ { MP_JOY_AXIS9_MINUS, "JOY_AXIS9_MINUS" },
+
+ { MP_JOY_BTN0, "JOY_BTN0" },
+ { MP_JOY_BTN1, "JOY_BTN1" },
+ { MP_JOY_BTN2, "JOY_BTN2" },
+ { MP_JOY_BTN3, "JOY_BTN3" },
+ { MP_JOY_BTN4, "JOY_BTN4" },
+ { MP_JOY_BTN5, "JOY_BTN5" },
+ { MP_JOY_BTN6, "JOY_BTN6" },
+ { MP_JOY_BTN7, "JOY_BTN7" },
+ { MP_JOY_BTN8, "JOY_BTN8" },
+ { MP_JOY_BTN9, "JOY_BTN9" },
+
+ { MP_AR_PLAY, "AR_PLAY" },
+ { MP_AR_PLAY_HOLD, "AR_PLAY_HOLD" },
+ { MP_AR_CENTER, "AR_CENTER" },
+ { MP_AR_CENTER_HOLD, "AR_CENTER_HOLD" },
+ { MP_AR_NEXT, "AR_NEXT" },
+ { MP_AR_NEXT_HOLD, "AR_NEXT_HOLD" },
+ { MP_AR_PREV, "AR_PREV" },
+ { MP_AR_PREV_HOLD, "AR_PREV_HOLD" },
+ { MP_AR_MENU, "AR_MENU" },
+ { MP_AR_MENU_HOLD, "AR_MENU_HOLD" },
+ { MP_AR_VUP, "AR_VUP" },
+ { MP_AR_VUP_HOLD, "AR_VUP_HOLD" },
+ { MP_AR_VDOWN, "AR_VDOWN" },
+ { MP_AR_VDOWN_HOLD, "AR_VDOWN_HOLD" },
+
+ { MP_MK_PLAY, "MK_PLAY" },
+ { MP_MK_PREV, "MK_PREV" },
+ { MP_MK_NEXT, "MK_NEXT" },
+
+ { MP_KEY_POWER, "POWER" },
+ { MP_KEY_MENU, "MENU" },
+ { MP_KEY_PLAY, "PLAY" },
+ { MP_KEY_PAUSE, "PAUSE" },
+ { MP_KEY_PLAYPAUSE, "PLAYPAUSE" },
+ { MP_KEY_STOP, "STOP" },
+ { MP_KEY_FORWARD, "FORWARD" },
+ { MP_KEY_REWIND, "REWIND" },
+ { MP_KEY_NEXT, "NEXT" },
+ { MP_KEY_PREV, "PREV" },
+ { MP_KEY_VOLUME_UP, "VOLUME_UP" },
+ { MP_KEY_VOLUME_DOWN, "VOLUME_DOWN" },
+ { MP_KEY_MUTE, "MUTE" },
+
+ // These are kept for backward compatibility
+ { MP_KEY_PAUSE, "XF86_PAUSE" },
+ { MP_KEY_STOP, "XF86_STOP" },
+ { MP_KEY_PREV, "XF86_PREV" },
+ { MP_KEY_NEXT, "XF86_NEXT" },
+
+ { MP_KEY_CLOSE_WIN, "CLOSE_WIN" },
+ { MP_KEY_MOUSE_MOVE, "MOUSE_MOVE" },
+ { MP_KEY_MOUSE_LEAVE, "MOUSE_LEAVE" },
+
+ { 0, NULL }
+};
+
+struct key_name modifier_names[] = {
+ { MP_KEY_MODIFIER_SHIFT, "Shift" },
+ { MP_KEY_MODIFIER_CTRL, "Ctrl" },
+ { MP_KEY_MODIFIER_ALT, "Alt" },
+ { MP_KEY_MODIFIER_META, "Meta" },
+ { 0 }
+};
+
+#define MP_MAX_FDS 10
+
+struct input_fd {
+ int fd;
+ int (*read_key)(void *ctx, int fd);
+ int (*read_cmd)(int fd, char *dest, int size);
+ int (*close_func)(int fd);
+ void *ctx;
+ unsigned eof : 1;
+ unsigned drop : 1;
+ unsigned dead : 1;
+ unsigned got_cmd : 1;
+ unsigned select : 1;
+ // These fields are for the cmd fds.
+ char *buffer;
+ int pos, size;
+};
+
+struct cmd_bind_section {
+ struct cmd_bind *binds;
+ int num_binds;
+ char *section;
+ struct mp_rect mouse_area; // set at runtime, if at all
+ bool mouse_area_set; // mouse_area is valid and should be tested
+ struct cmd_bind_section *next;
+};
+
+#define MAX_ACTIVE_SECTIONS 5
+
+struct active_section {
+ char *name;
+ int flags;
+};
+
+struct cmd_queue {
+ struct mp_cmd *first;
+};
+
+struct input_ctx {
+ bool using_ar;
+ bool using_cocoa_media_keys;
+
+ // Autorepeat stuff
+ short ar_state;
+ int64_t last_ar;
+ // Autorepeat config
+ unsigned int ar_delay;
+ unsigned int ar_rate;
+ // Maximum number of queued commands from keypresses (limit to avoid
+ // repeated slow commands piling up)
+ int key_fifo_size;
+
+ // these are the keys currently down
+ int key_down[MP_MAX_KEY_DOWN];
+ unsigned int num_key_down;
+ int64_t last_key_down;
+ struct mp_cmd *current_down_cmd;
+
+ int doubleclick_time;
+ int last_doubleclick_key_down;
+ double last_doubleclick_time;
+
+ // Mouse position on the consumer side (as command.c sees it)
+ int mouse_x, mouse_y;
+ char *mouse_section; // last section to receive mouse event
+
+ // Mouse position on the producer side (as the VO sees it)
+ // Unlike mouse_x/y, this can be used to resolve mouse click bindings.
+ int mouse_vo_x, mouse_vo_y;
+
+ bool test;
+
+ bool default_bindings;
+ // List of command binding sections
+ struct cmd_bind_section *cmd_bind_sections;
+
+ // List currently active command sections
+ struct active_section active_sections[MAX_ACTIVE_SECTIONS];
+ int num_active_sections;
+
+ // Used to track whether we managed to read something while checking
+ // events sources. If yes, the sources may have more queued.
+ bool got_new_events;
+
+ unsigned int mouse_event_counter;
+
+ struct input_fd fds[MP_MAX_FDS];
+ unsigned int num_fds;
+
+ struct cmd_queue key_cmd_queue;
+ struct cmd_queue control_cmd_queue;
+
+ int wakeup_pipe[2];
+};
+
+
+int async_quit_request;
+
+static int print_key_list(m_option_t *cfg, char *optname, char *optparam);
+static int print_cmd_list(m_option_t *cfg, char *optname, char *optparam);
+
+#define OPT_BASE_STRUCT struct MPOpts
+
+// Our command line options
+static const m_option_t input_config[] = {
+ OPT_STRING("conf", input.config_file, CONF_GLOBAL),
+ OPT_INT("ar-delay", input.ar_delay, CONF_GLOBAL),
+ OPT_INT("ar-rate", input.ar_rate, CONF_GLOBAL),
+ { "keylist", print_key_list, CONF_TYPE_PRINT_FUNC, CONF_GLOBAL | CONF_NOCFG },
+ { "cmdlist", print_cmd_list, CONF_TYPE_PRINT_FUNC, CONF_GLOBAL | CONF_NOCFG },
+ OPT_STRING("js-dev", input.js_dev, CONF_GLOBAL),
+ OPT_STRING("file", input.in_file, CONF_GLOBAL),
+ OPT_FLAG("default-bindings", input.default_bindings, CONF_GLOBAL),
+ OPT_FLAG("test", input.test, CONF_GLOBAL),
+ { NULL, NULL, 0, 0, 0, 0, NULL}
+};
+
+const m_option_t mp_input_opts[] = {
+ { "input", (void *)&input_config, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+ OPT_INTRANGE("doubleclick-time", input.doubleclick_time, 0, 0, 1000),
+ OPT_FLAG("joystick", input.use_joystick, CONF_GLOBAL),
+ OPT_FLAG("lirc", input.use_lirc, CONF_GLOBAL),
+ OPT_FLAG("lircc", input.use_lircc, CONF_GLOBAL),
+#ifdef CONFIG_COCOA
+ OPT_FLAG("ar", input.use_ar, CONF_GLOBAL),
+ OPT_FLAG("media-keys", input.use_media_keys, CONF_GLOBAL),
+#endif
+ { NULL, NULL, 0, 0, 0, 0, NULL}
+};
+
+static int default_cmd_func(int fd, char *buf, int l);
+
+static const char builtin_input_conf[] =
+#include "core/input/input_conf.h"
+;
+
+static bool test_rect(struct mp_rect *rc, int x, int y)
+{
+ return x >= rc->x0 && y >= rc->y0 && x < rc->x1 && y < rc->y1;
+}
+
+static char *get_key_name(int key, char *ret)
+{
+ for (int i = 0; modifier_names[i].name; i++) {
+ if (modifier_names[i].key & key) {
+ ret = talloc_asprintf_append_buffer(ret, "%s+",
+ modifier_names[i].name);
+ key -= modifier_names[i].key;
+ }
+ }
+ for (int i = 0; key_names[i].name != NULL; i++) {
+ if (key_names[i].key == key)
+ return talloc_asprintf_append_buffer(ret, "%s", key_names[i].name);
+ }
+
+ // printable, and valid unicode range
+ if (key >= 32 && key <= 0x10FFFF)
+ return mp_append_utf8_buffer(ret, key);
+
+ // Print the hex key code
+ return talloc_asprintf_append_buffer(ret, "%#-8x", key);
+}
+
+static char *get_key_combo_name(int *keys, int max)
+{
+ char *ret = talloc_strdup(NULL, "");
+ while (max > 0) {
+ ret = get_key_name(*keys, ret);
+ if (--max && *++keys)
+ ret = talloc_asprintf_append_buffer(ret, "-");
+ else
+ break;
+ }
+ return ret;
+}
+
+bool mp_input_is_abort_cmd(int cmd_id)
+{
+ switch (cmd_id) {
+ case MP_CMD_QUIT:
+ case MP_CMD_PLAYLIST_NEXT:
+ case MP_CMD_PLAYLIST_PREV:
+ return true;
+ }
+ return false;
+}
+
+static int queue_count_cmds(struct cmd_queue *queue)
+{
+ int res = 0;
+ for (struct mp_cmd *cmd = queue->first; cmd; cmd = cmd->queue_next)
+ res++;
+ return res;
+}
+
+static bool queue_has_abort_cmds(struct cmd_queue *queue)
+{
+ for (struct mp_cmd *cmd = queue->first; cmd; cmd = cmd->queue_next) {
+ if (mp_input_is_abort_cmd(cmd->id))
+ return true;
+ }
+ return false;
+}
+
+static void queue_remove(struct cmd_queue *queue, struct mp_cmd *cmd)
+{
+ struct mp_cmd **p_prev = &queue->first;
+ while (*p_prev != cmd) {
+ p_prev = &(*p_prev)->queue_next;
+ }
+ // if this fails, cmd was not in the queue
+ assert(*p_prev == cmd);
+ *p_prev = cmd->queue_next;
+}
+
+static void queue_add(struct cmd_queue *queue, struct mp_cmd *cmd,
+ bool at_head)
+{
+ if (at_head) {
+ cmd->queue_next = queue->first;
+ queue->first = cmd;
+ } else {
+ struct mp_cmd **p_prev = &queue->first;
+ while (*p_prev)
+ p_prev = &(*p_prev)->queue_next;
+ *p_prev = cmd;
+ cmd->queue_next = NULL;
+ }
+}
+
+static struct input_fd *mp_input_add_fd(struct input_ctx *ictx)
+{
+ if (ictx->num_fds == MP_MAX_FDS) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Too many file descriptors.\n");
+ return NULL;
+ }
+
+ struct input_fd *fd = &ictx->fds[ictx->num_fds];
+ *fd = (struct input_fd){
+ .fd = -1,
+ };
+ ictx->num_fds++;
+
+ return fd;
+}
+
+int mp_input_add_cmd_fd(struct input_ctx *ictx, int unix_fd, int select,
+ int read_func(int fd, char *dest, int size),
+ int close_func(int fd))
+{
+ if (select && unix_fd < 0) {
+ mp_msg(MSGT_INPUT, MSGL_ERR,
+ "Invalid fd %d in mp_input_add_cmd_fd", unix_fd);
+ return 0;
+ }
+
+ struct input_fd *fd = mp_input_add_fd(ictx);
+ if (!fd)
+ return 0;
+ fd->fd = unix_fd;
+ fd->select = select;
+ fd->read_cmd = read_func ? read_func : default_cmd_func;
+ fd->close_func = close_func;
+ return 1;
+}
+
+int mp_input_add_key_fd(struct input_ctx *ictx, int unix_fd, int select,
+ int read_func(void *ctx, int fd),
+ int close_func(int fd), void *ctx)
+{
+ if (select && unix_fd < 0) {
+ mp_msg(MSGT_INPUT, MSGL_ERR,
+ "Invalid fd %d in mp_input_add_key_fd", unix_fd);
+ return 0;
+ }
+ assert(read_func);
+
+ struct input_fd *fd = mp_input_add_fd(ictx);
+ if (!fd)
+ return 0;
+ fd->fd = unix_fd;
+ fd->select = select;
+ fd->read_key = read_func;
+ fd->close_func = close_func;
+ fd->ctx = ctx;
+ return 1;
+}
+
+
+static void mp_input_rm_fd(struct input_ctx *ictx, int fd)
+{
+ struct input_fd *fds = ictx->fds;
+ unsigned int i;
+
+ for (i = 0; i < ictx->num_fds; i++) {
+ if (fds[i].fd == fd)
+ break;
+ }
+ if (i == ictx->num_fds)
+ return;
+ if (fds[i].close_func)
+ fds[i].close_func(fds[i].fd);
+ talloc_free(fds[i].buffer);
+
+ if (i + 1 < ictx->num_fds)
+ memmove(&fds[i], &fds[i + 1],
+ (ictx->num_fds - i - 1) * sizeof(struct input_fd));
+ ictx->num_fds--;
+}
+
+void mp_input_rm_key_fd(struct input_ctx *ictx, int fd)
+{
+ mp_input_rm_fd(ictx, fd);
+}
+
+static int parse_cycle_dir(const struct m_option *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ double val;
+ if (bstrcmp0(param, "up") == 0) {
+ val = +1;
+ } else if (bstrcmp0(param, "down") == 0) {
+ val = -1;
+ } else {
+ return m_option_type_double.parse(opt, name, param, dst);
+ }
+ *(double *)dst = val;
+ return 1;
+}
+
+static bool read_token(bstr str, bstr *out_rest, bstr *out_token)
+{
+ bstr t = bstr_lstrip(str);
+ int next = bstrcspn(t, WHITESPACE "#");
+ // Handle comments
+ if (t.len && t.start[next] == '#')
+ t = bstr_splice(t, 0, next);
+ if (!t.len)
+ return false;
+ *out_token = bstr_splice(t, 0, next);
+ *out_rest = bstr_cut(t, next);
+ return true;
+}
+
+static bool eat_token(bstr *str, const char *tok)
+{
+ bstr rest, token;
+ if (read_token(*str, &rest, &token) && bstrcmp0(token, tok) == 0) {
+ *str = rest;
+ return true;
+ }
+ return false;
+}
+
+static bool read_escaped_string(void *talloc_ctx, bstr *str, bstr *literal)
+{
+ bstr t = *str;
+ char *new = talloc_strdup(talloc_ctx, "");
+ while (t.len) {
+ if (t.start[0] == '"')
+ break;
+ if (t.start[0] == '\\') {
+ t = bstr_cut(t, 1);
+ if (!mp_parse_escape(&t, &new))
+ goto error;
+ } else {
+ new = talloc_strndup_append_buffer(new, t.start, 1);
+ t = bstr_cut(t, 1);
+ }
+ }
+ int len = str->len - t.len;
+ *literal = new ? bstr0(new) : bstr_splice(*str, 0, len);
+ *str = bstr_cut(*str, len);
+ return true;
+error:
+ talloc_free(new);
+ return false;
+}
+
+// If dest is non-NULL when calling this function, append the command to the
+// list formed by dest->queue_next, otherwise just set *dest = new_cmd;
+static int parse_cmd(struct mp_cmd **dest, bstr str, const char *loc)
+{
+ int pausing = 0;
+ int on_osd = MP_ON_OSD_AUTO;
+ bool raw_args = false;
+ struct mp_cmd *cmd = NULL;
+ bstr start = str;
+ bstr next = {0};
+ void *tmp = talloc_new(NULL);
+
+ str = bstr_lstrip(str);
+ for (const struct legacy_cmd *entry = legacy_cmds; entry->old; entry++) {
+ bstr old = bstr0(entry->old);
+ bool silent = bstr_eatstart0(&old, "!");
+ if (bstrcasecmp(bstr_splice(str, 0, old.len), old) == 0) {
+ if (!silent) {
+ mp_tmsg(MSGT_INPUT, MSGL_WARN, "Warning: command '%.*s' is "
+ "deprecated, replaced with '%s' at %s.\n",
+ BSTR_P(old), entry->new, loc);
+ }
+ bstr s = bstr_cut(str, old.len);
+ str = bstr0(talloc_asprintf(tmp, "%s%.*s", entry->new, BSTR_P(s)));
+ start = str;
+ break;
+ }
+ }
+
+ while (1) {
+ if (eat_token(&str, "pausing")) {
+ pausing = 1;
+ } else if (eat_token(&str, "pausing_keep")) {
+ pausing = 2;
+ } else if (eat_token(&str, "pausing_toggle")) {
+ pausing = 3;
+ } else if (eat_token(&str, "pausing_keep_force")) {
+ pausing = 4;
+ } else if (eat_token(&str, "no-osd")) {
+ on_osd = MP_ON_OSD_NO;
+ } else if (eat_token(&str, "osd-bar")) {
+ on_osd = MP_ON_OSD_BAR;
+ } else if (eat_token(&str, "osd-msg")) {
+ on_osd = MP_ON_OSD_MSG;
+ } else if (eat_token(&str, "osd-msg-bar")) {
+ on_osd = MP_ON_OSD_MSG | MP_ON_OSD_BAR;
+ } else if (eat_token(&str, "osd-auto")) {
+ // default
+ } else if (eat_token(&str, "raw")) {
+ raw_args = true;
+ } else if (eat_token(&str, "expand-properties")) {
+ // default
+ } else {
+ break;
+ }
+ }
+
+ int cmd_idx = 0;
+ while (mp_cmds[cmd_idx].name != NULL) {
+ if (eat_token(&str, mp_cmds[cmd_idx].name))
+ break;
+ cmd_idx++;
+ }
+
+ if (mp_cmds[cmd_idx].name == NULL) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command '%.*s' not found.\n",
+ BSTR_P(str));
+ goto error;
+ }
+
+ cmd = talloc_ptrtype(NULL, cmd);
+ *cmd = mp_cmds[cmd_idx];
+ cmd->pausing = pausing;
+ cmd->on_osd = on_osd;
+ cmd->raw_args = raw_args;
+
+ for (int i = 0; i < MP_CMD_MAX_ARGS; i++) {
+ struct mp_cmd_arg *cmdarg = &cmd->args[i];
+ if (!cmdarg->type.type)
+ break;
+ str = bstr_lstrip(str);
+ if (eat_token(&str, ";")) {
+ next = str;
+ str.len = 0;
+ break;
+ }
+ cmd->nargs++;
+ bstr arg = {0};
+ if (bstr_eatstart0(&str, "\"")) {
+ if (!read_escaped_string(tmp, &str, &arg)) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s: argument %d "
+ "has broken string escapes.\n", cmd->name, i + 1);
+ goto error;
+ }
+ if (!bstr_eatstart0(&str, "\"")) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s: argument %d is "
+ "unterminated.\n", cmd->name, i + 1);
+ goto error;
+ }
+ } else {
+ if (!read_token(str, &str, &arg))
+ break;
+ if (cmdarg->optional && bstrcmp0(arg, "-") == 0)
+ continue;
+ }
+ // Prevent option API from trying to deallocate static strings
+ cmdarg->v = ((struct mp_cmd_arg) {{0}}).v;
+ int r = m_option_parse(&cmdarg->type, bstr0(cmd->name), arg, &cmdarg->v);
+ if (r < 0) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s: argument %d "
+ "can't be parsed: %s.\n", cmd->name, i + 1,
+ m_option_strerror(r));
+ goto error;
+ }
+ if (cmdarg->type.type == &m_option_type_string)
+ cmdarg->v.s = talloc_steal(cmd, cmdarg->v.s);
+ }
+
+ if (eat_token(&str, ";")) {
+ next = str;
+ str.len = 0;
+ }
+
+ bstr dummy;
+ if (read_token(str, &dummy, &dummy)) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s has trailing unused "
+ "arguments: '%.*s'.\n", cmd->name, BSTR_P(str));
+ // Better make it fatal to make it clear something is wrong.
+ goto error;
+ }
+
+ int min_args = 0;
+ while (min_args < MP_CMD_MAX_ARGS && cmd->args[min_args].type.type
+ && !cmd->args[min_args].optional)
+ {
+ min_args++;
+ }
+ if (cmd->nargs < min_args) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command %s requires at least %d "
+ "arguments, we found only %d so far.\n", cmd->name, min_args,
+ cmd->nargs);
+ goto error;
+ }
+
+ bstr orig = (bstr) {start.start, str.start - start.start};
+ cmd->original = bstrdup(cmd, bstr_strip(orig));
+
+ while (*dest)
+ dest = &(*dest)->queue_next;
+ *dest = cmd;
+
+ next = bstr_strip(next);
+ if (next.len) {
+ if (parse_cmd(dest, next, loc) < 0) {
+ *dest = NULL;
+ goto error;
+ }
+ }
+
+ talloc_free(tmp);
+ return 1;
+
+error:
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command was defined at %s.\n", loc);
+ talloc_free(cmd);
+ talloc_free(tmp);
+ return -1;
+}
+
+mp_cmd_t *mp_input_parse_cmd(bstr str, const char *loc)
+{
+ struct mp_cmd *cmd = NULL;
+ if (parse_cmd(&cmd, str, loc) < 0) {
+ assert(!cmd);
+ }
+ // Other input.c code uses queue_next for its own purposes, so explicitly
+ // wrap lists in a pseudo-command.
+ if (cmd && cmd->queue_next) {
+ struct mp_cmd *list = talloc_ptrtype(NULL, list);
+ *list = (struct mp_cmd) {
+ .id = MP_CMD_COMMAND_LIST,
+ .name = "list",
+ .original = bstrdup(list, str),
+ };
+ list->args[0].v.p = cmd;
+ while (cmd) {
+ talloc_steal(list, cmd);
+ cmd = cmd->queue_next;
+ }
+ cmd = list;
+ }
+ return cmd;
+}
+
+#define MP_CMD_MAX_SIZE 4096
+
+static int read_cmd(struct input_fd *mp_fd, char **ret)
+{
+ char *end;
+ *ret = NULL;
+
+ // Allocate the buffer if it doesn't exist
+ if (!mp_fd->buffer) {
+ mp_fd->buffer = talloc_size(NULL, MP_CMD_MAX_SIZE);
+ mp_fd->pos = 0;
+ mp_fd->size = MP_CMD_MAX_SIZE;
+ }
+
+ // Get some data if needed/possible
+ while (!mp_fd->got_cmd && !mp_fd->eof && (mp_fd->size - mp_fd->pos > 1)) {
+ int r = mp_fd->read_cmd(mp_fd->fd, mp_fd->buffer + mp_fd->pos,
+ mp_fd->size - 1 - mp_fd->pos);
+ // Error ?
+ if (r < 0) {
+ switch (r) {
+ case MP_INPUT_ERROR:
+ case MP_INPUT_DEAD:
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Error while reading "
+ "command file descriptor %d: %s\n",
+ mp_fd->fd, strerror(errno));
+ case MP_INPUT_NOTHING:
+ return r;
+ case MP_INPUT_RETRY:
+ continue;
+ }
+ // EOF ?
+ } else if (r == 0) {
+ mp_fd->eof = 1;
+ break;
+ }
+ mp_fd->pos += r;
+ break;
+ }
+
+ mp_fd->got_cmd = 0;
+
+ while (1) {
+ int l = 0;
+ // Find the cmd end
+ mp_fd->buffer[mp_fd->pos] = '\0';
+ end = strchr(mp_fd->buffer, '\r');
+ if (end)
+ *end = '\n';
+ end = strchr(mp_fd->buffer, '\n');
+ // No cmd end ?
+ if (!end) {
+ // If buffer is full we must drop all until the next \n
+ if (mp_fd->size - mp_fd->pos <= 1) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Command buffer of file "
+ "descriptor %d is full: dropping content.\n",
+ mp_fd->fd);
+ mp_fd->pos = 0;
+ mp_fd->drop = 1;
+ }
+ break;
+ }
+ // We already have a cmd : set the got_cmd flag
+ else if ((*ret)) {
+ mp_fd->got_cmd = 1;
+ break;
+ }
+
+ l = end - mp_fd->buffer;
+
+ // Not dropping : put the cmd in ret
+ if (!mp_fd->drop)
+ *ret = talloc_strndup(NULL, mp_fd->buffer, l);
+ else
+ mp_fd->drop = 0;
+ mp_fd->pos -= l + 1;
+ memmove(mp_fd->buffer, end + 1, mp_fd->pos);
+ }
+
+ if (*ret)
+ return 1;
+ else
+ return MP_INPUT_NOTHING;
+}
+
+static int default_cmd_func(int fd, char *buf, int l)
+{
+ while (1) {
+ int r = read(fd, buf, l);
+ // Error ?
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ else if (errno == EAGAIN)
+ return MP_INPUT_NOTHING;
+ return MP_INPUT_ERROR;
+ // EOF ?
+ }
+ return r;
+ }
+}
+
+#ifndef __MINGW32__
+static int read_wakeup(void *ctx, int fd)
+{
+ char buf[100];
+ read(fd, buf, sizeof(buf));
+ return MP_INPUT_NOTHING;
+}
+#endif
+
+static bool bind_matches_key(struct cmd_bind *bind, int n, const int *keys);
+
+static void append_bind_info(char **pmsg, struct cmd_bind *bind)
+{
+ char *msg = *pmsg;
+ struct mp_cmd *cmd = mp_input_parse_cmd(bstr0(bind->cmd), bind->location);
+ bstr stripped = cmd ? cmd->original : bstr0(bind->cmd);
+ msg = talloc_asprintf_append(msg, " '%.*s'", BSTR_P(stripped));
+ if (!cmd)
+ msg = talloc_asprintf_append(msg, " (invalid)");
+ if (strcmp(bind->owner->section, "default") != 0)
+ msg = talloc_asprintf_append(msg, " in section {%s}",
+ bind->owner->section);
+ msg = talloc_asprintf_append(msg, " in %s", bind->location);
+ if (bind->is_builtin)
+ msg = talloc_asprintf_append(msg, " (default)");
+ talloc_free(cmd);
+ *pmsg = msg;
+}
+
+static mp_cmd_t *handle_test(struct input_ctx *ictx, int n, int *keys)
+{
+ char *key_buf = get_key_combo_name(keys, n);
+ // "$>" to disable property substitution when invoking "show_text"
+ char *msg = talloc_asprintf(NULL, "$>Key %s is bound to:\n", key_buf);
+ talloc_free(key_buf);
+
+ int count = 0;
+ for (struct cmd_bind_section *bs = ictx->cmd_bind_sections;
+ bs; bs = bs->next)
+ {
+ for (int i = 0; i < bs->num_binds; i++) {
+ if (bind_matches_key(&bs->binds[i], n, keys)) {
+ count++;
+ msg = talloc_asprintf_append(msg, "%d. ", count);
+ append_bind_info(&msg, &bs->binds[i]);
+ msg = talloc_asprintf_append(msg, "\n");
+ }
+ }
+ }
+
+ if (!count)
+ msg = talloc_asprintf_append(msg, "(nothing)");
+
+ mp_msg(MSGT_INPUT, MSGL_V, "[input] %s\n", msg);
+
+ mp_cmd_t *res = mp_input_parse_cmd(bstr0("show_text \"\""), "");
+ res->args[0].v.s = talloc_steal(res, msg);
+ return res;
+}
+
+static bool bind_matches_key(struct cmd_bind *bind, int num_keys, const int *keys)
+{
+ if (bind->num_keys != num_keys)
+ return false;
+ for (int i = 0; i < num_keys; i++) {
+ if (bind->keys[i] != keys[i])
+ return false;
+ }
+ return true;
+}
+
+static struct cmd_bind_section *get_bind_section(struct input_ctx *ictx,
+ bstr section)
+{
+ struct cmd_bind_section *bind_section = ictx->cmd_bind_sections;
+
+ if (section.len == 0)
+ section = bstr0("default");
+ while (bind_section) {
+ if (bstrcmp0(section, bind_section->section) == 0)
+ return bind_section;
+ if (bind_section->next == NULL)
+ break;
+ bind_section = bind_section->next;
+ }
+ if (bind_section) {
+ bind_section->next = talloc_ptrtype(ictx, bind_section->next);
+ bind_section = bind_section->next;
+ } else {
+ ictx->cmd_bind_sections = talloc_ptrtype(ictx, ictx->cmd_bind_sections);
+ bind_section = ictx->cmd_bind_sections;
+ }
+ *bind_section = (struct cmd_bind_section) {
+ .section = bstrdup0(bind_section, section),
+ };
+ return bind_section;
+}
+
+static struct cmd_bind *find_bind_for_key_section(struct input_ctx *ictx,
+ char *section,
+ int num_keys, int *keys)
+{
+ struct cmd_bind_section *bs = get_bind_section(ictx, bstr0(section));
+
+ if (!num_keys || !bs->num_binds)
+ return NULL;
+
+ // Prefer user-defined keys over builtin bindings
+ for (int builtin = 0; builtin < 2; builtin++) {
+ for (int n = 0; n < bs->num_binds; n++) {
+ if (bs->binds[n].is_builtin == (bool)builtin &&
+ bind_matches_key(&bs->binds[n], num_keys, keys))
+ return &bs->binds[n];
+ }
+ }
+ return NULL;
+}
+
+static struct cmd_bind *find_any_bind_for_key(struct input_ctx *ictx,
+ char *force_section,
+ int n, int *keys)
+{
+ if (force_section)
+ return find_bind_for_key_section(ictx, force_section, n, keys);
+
+ for (int i = ictx->num_active_sections - 1; i >= 0; i--) {
+ struct active_section *s = &ictx->active_sections[i];
+ struct cmd_bind *bind = find_bind_for_key_section(ictx, s->name, n, keys);
+ if (bind) {
+ struct cmd_bind_section *bs = bind->owner;
+ for (int x = 0; x < n; x++) {
+ if (MP_KEY_DEPENDS_ON_MOUSE_POS(keys[x]) && bs->mouse_area_set &&
+ !test_rect(&bs->mouse_area, ictx->mouse_vo_x, ictx->mouse_vo_y))
+ goto skip;
+ }
+ return bind;
+ }
+ skip: ;
+ if (s->flags & MP_INPUT_EXCLUSIVE)
+ break;
+ }
+
+ return NULL;
+}
+
+static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
+ int n, int *keys)
+{
+ if (ictx->test)
+ return handle_test(ictx, n, keys);
+
+ struct cmd_bind *cmd = find_any_bind_for_key(ictx, force_section, n, keys);
+ if (cmd == NULL && n > 1) {
+ // Hitting two keys at once, and if there's no binding for this
+ // combination, the key hit last should be checked.
+ cmd = find_any_bind_for_key(ictx, force_section, 1, (int[]){keys[n - 1]});
+ }
+
+ if (cmd == NULL) {
+ char *key_buf = get_key_combo_name(keys, n);
+ mp_tmsg(MSGT_INPUT, MSGL_WARN,
+ "No bind found for key '%s'.\n", key_buf);
+ talloc_free(key_buf);
+ return NULL;
+ }
+ mp_cmd_t *ret = mp_input_parse_cmd(bstr0(cmd->cmd), cmd->location);
+ if (ret) {
+ ret->input_section = cmd->owner->section;
+ } else {
+ char *key_buf = get_key_combo_name(keys, n);
+ mp_tmsg(MSGT_INPUT, MSGL_ERR,
+ "Invalid command for bound key '%s': '%s'\n", key_buf, cmd->cmd);
+ talloc_free(key_buf);
+ }
+ return ret;
+}
+
+static void release_down_cmd(struct input_ctx *ictx)
+{
+ if (ictx->current_down_cmd && ictx->current_down_cmd->key_up_follows) {
+ ictx->current_down_cmd->key_up_follows = false;
+ queue_add(&ictx->key_cmd_queue, ictx->current_down_cmd, false);
+ } else {
+ talloc_free(ictx->current_down_cmd);
+ }
+ ictx->current_down_cmd = NULL;
+ ictx->last_key_down = 0;
+ ictx->ar_state = -1;
+}
+
+static int find_key_down(struct input_ctx *ictx, int code)
+{
+ code &= ~(MP_KEY_STATE_UP | MP_KEY_STATE_DOWN);
+ for (int j = 0; j < ictx->num_key_down; j++) {
+ if (ictx->key_down[j] == code)
+ return j;
+ }
+ return -1;
+}
+
+static void remove_key_down(struct input_ctx *ictx, int code)
+{
+ int index = find_key_down(ictx, code);
+ if (index >= 0) {
+ memmove(&ictx->key_down[index], &ictx->key_down[index + 1],
+ (ictx->num_key_down - (index + 1)) * sizeof(int));
+ ictx->num_key_down -= 1;
+ }
+}
+
+static mp_cmd_t *interpret_key(struct input_ctx *ictx, int code)
+{
+ /* On normal keyboards shift changes the character code of non-special
+ * keys, so don't count the modifier separately for those. In other words
+ * we want to have "a" and "A" instead of "a" and "Shift+A"; but a separate
+ * shift modifier is still kept for special keys like arrow keys.
+ */
+ int unmod = code & ~MP_KEY_MODIFIER_MASK;
+ if (unmod >= 32 && unmod < MP_KEY_BASE)
+ code &= ~MP_KEY_MODIFIER_SHIFT;
+
+ if (!(code & MP_KEY_STATE_UP) && ictx->num_key_down >= MP_MAX_KEY_DOWN) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Too many key down events "
+ "at the same time\n");
+ return NULL;
+ }
+
+ bool key_was_down = find_key_down(ictx, code) >= 0;
+
+ if (code & MP_KEY_STATE_DOWN) {
+ // Check if we don't already have this key as pushed
+ if (key_was_down)
+ return NULL;
+ // Cancel current down-event (there can be only one)
+ release_down_cmd(ictx);
+ ictx->key_down[ictx->num_key_down] = code & ~MP_KEY_STATE_DOWN;
+ ictx->num_key_down++;
+ ictx->last_key_down = mp_time_us();
+ ictx->ar_state = 0;
+ ictx->current_down_cmd = get_cmd_from_keys(ictx, NULL, ictx->num_key_down,
+ ictx->key_down);
+ if (ictx->current_down_cmd && (code & MP_KEY_EMIT_ON_UP))
+ ictx->current_down_cmd->key_up_follows = true;
+ return mp_cmd_clone(ictx->current_down_cmd);
+ } else if (code & MP_KEY_STATE_UP) {
+ if (key_was_down) {
+ remove_key_down(ictx, code);
+ release_down_cmd(ictx);
+ }
+ return NULL;
+ } else {
+ // Press of key with no separate down/up events
+ if (key_was_down) {
+ // Mixing press events and up/down with the same key is not allowed
+ mp_tmsg(MSGT_INPUT, MSGL_WARN, "Mixing key presses and up/down.\n");
+ }
+ // Add temporarily (include ongoing down/up events)
+ int num_key_down = ictx->num_key_down;
+ int key_down[MP_MAX_KEY_DOWN];
+ memcpy(key_down, ictx->key_down, num_key_down * sizeof(int));
+ // Assume doubleclick events never use down/up, while button events do
+ if (MP_KEY_IS_MOUSE_BTN_DBL(code)) {
+ // Don't emit "MOUSE_BTN0+MOUSE_BTN0_DBL", just "MOUSE_BTN0_DBL"
+ int btn = code - MP_MOUSE_BTN0_DBL + MP_MOUSE_BTN0;
+ if (!num_key_down || key_down[num_key_down - 1] != btn)
+ return NULL;
+ key_down[num_key_down - 1] = code;
+ } else {
+ key_down[num_key_down] = code;
+ num_key_down++;
+ }
+ return get_cmd_from_keys(ictx, NULL, num_key_down, key_down);
+ }
+}
+
+static mp_cmd_t *check_autorepeat(struct input_ctx *ictx)
+{
+ // No input : autorepeat ?
+ if (ictx->ar_rate > 0 && ictx->ar_state >= 0 && ictx->num_key_down > 0
+ && !(ictx->key_down[ictx->num_key_down - 1] & MP_NO_REPEAT_KEY)) {
+ int64_t t = mp_time_us();
+ if (ictx->last_ar + 2000000 < t)
+ ictx->last_ar = t;
+ // First time : wait delay
+ if (ictx->ar_state == 0
+ && (t - ictx->last_key_down) >= ictx->ar_delay * 1000)
+ {
+ if (!ictx->current_down_cmd) {
+ ictx->ar_state = -1;
+ return NULL;
+ }
+ ictx->ar_state = 1;
+ ictx->last_ar = ictx->last_key_down + ictx->ar_delay * 1000;
+ return mp_cmd_clone(ictx->current_down_cmd);
+ // Then send rate / sec event
+ } else if (ictx->ar_state == 1
+ && (t - ictx->last_ar) >= 1000000 / ictx->ar_rate) {
+ ictx->last_ar += 1000000 / ictx->ar_rate;
+ return mp_cmd_clone(ictx->current_down_cmd);
+ }
+ }
+ return NULL;
+}
+
+static void add_key_cmd(struct input_ctx *ictx, struct mp_cmd *cmd)
+{
+ struct cmd_queue *queue = &ictx->key_cmd_queue;
+ if (queue_count_cmds(queue) >= ictx->key_fifo_size &&
+ (!mp_input_is_abort_cmd(cmd->id) || queue_has_abort_cmds(queue)))
+ {
+ talloc_free(cmd);
+ return;
+ }
+ queue_add(queue, cmd, false);
+}
+
+// Whether a command can deal with redundant key up events.
+static bool key_updown_ok(enum mp_command_type cmd)
+{
+ switch (cmd) {
+ default:
+ return false;
+ }
+}
+
+static void mp_input_feed_key(struct input_ctx *ictx, int code)
+{
+ ictx->got_new_events = true;
+ if (code == MP_INPUT_RELEASE_ALL) {
+ mp_msg(MSGT_INPUT, MSGL_V, "input: release all\n");
+ ictx->num_key_down = 0;
+ release_down_cmd(ictx);
+ return;
+ }
+ int unmod = code & ~MP_KEY_MODIFIER_MASK;
+ if (MP_KEY_DEPENDS_ON_MOUSE_POS(unmod))
+ ictx->mouse_event_counter++;
+ mp_msg(MSGT_INPUT, MSGL_V, "input: key code=%#x\n", code);
+ struct mp_cmd *cmd = interpret_key(ictx, code);
+ if (!cmd)
+ return;
+ // Prevent redundant key-down events from being added to the queue. In some
+ // cases (like MP_CMD_SEEK commands), duplicated events might severely
+ // confuse the frontend.
+ if (cmd->key_up_follows && !key_updown_ok(cmd->id)) {
+ talloc_free(cmd);
+ return;
+ }
+ add_key_cmd(ictx, cmd);
+}
+
+void mp_input_put_key(struct input_ctx *ictx, int code)
+{
+ double now = mp_time_sec();
+ int doubleclick_time = ictx->doubleclick_time;
+ // ignore system-doubleclick if we generate these events ourselves
+ int unmod = code & ~MP_KEY_MODIFIER_MASK;
+ if (doubleclick_time && MP_KEY_IS_MOUSE_BTN_DBL(unmod))
+ return;
+ mp_input_feed_key(ictx, code);
+ if (code & MP_KEY_STATE_DOWN) {
+ code &= ~MP_KEY_STATE_DOWN;
+ if (ictx->last_doubleclick_key_down == code
+ && now - ictx->last_doubleclick_time < doubleclick_time / 1000.0)
+ {
+ if (code >= MP_MOUSE_BTN0 && code <= MP_MOUSE_BTN2)
+ mp_input_feed_key(ictx, code - MP_MOUSE_BTN0 + MP_MOUSE_BTN0_DBL);
+ }
+ ictx->last_doubleclick_key_down = code;
+ ictx->last_doubleclick_time = now;
+ }
+}
+
+void mp_input_put_key_utf8(struct input_ctx *ictx, int mods, struct bstr t)
+{
+ while (t.len) {
+ int code = bstr_decode_utf8(t, &t);
+ if (code < 0)
+ break;
+ mp_input_put_key(ictx, code | mods);
+ }
+}
+
+static void trigger_mouse_leave(struct input_ctx *ictx, char *new_section)
+{
+ if (!new_section)
+ new_section = "default";
+
+ char *old = ictx->mouse_section;
+ ictx->mouse_section = new_section;
+
+ if (old && strcmp(old, ictx->mouse_section) != 0) {
+ struct mp_cmd *cmd =
+ get_cmd_from_keys(ictx, old, 1, (int[]){MP_KEY_MOUSE_LEAVE});
+ if (cmd)
+ add_key_cmd(ictx, cmd);
+ }
+}
+
+
+void mp_input_set_mouse_pos(struct input_ctx *ictx, int x, int y)
+{
+ // we're already there
+ if (ictx->mouse_vo_x == x && ictx->mouse_vo_y == y)
+ return;
+
+ ictx->mouse_event_counter++;
+ ictx->mouse_vo_x = x;
+ ictx->mouse_vo_y = y;
+
+ struct mp_cmd *cmd =
+ get_cmd_from_keys(ictx, NULL, 1, (int[]){MP_KEY_MOUSE_MOVE});
+
+ trigger_mouse_leave(ictx, cmd ? cmd->input_section : NULL);
+
+ if (!cmd)
+ return;
+ cmd->mouse_move = true;
+ cmd->mouse_x = x;
+ cmd->mouse_y = y;
+ add_key_cmd(ictx, cmd);
+}
+
+static void read_cmd_fd(struct input_ctx *ictx, struct input_fd *cmd_fd)
+{
+ int r;
+ char *text;
+ while ((r = read_cmd(cmd_fd, &text)) >= 0) {
+ ictx->got_new_events = true;
+ struct mp_cmd *cmd = mp_input_parse_cmd(bstr0(text), "<pipe>");
+ talloc_free(text);
+ if (cmd)
+ queue_add(&ictx->control_cmd_queue, cmd, false);
+ if (!cmd_fd->got_cmd)
+ return;
+ }
+ if (r == MP_INPUT_ERROR)
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Error on command file descriptor %d\n",
+ cmd_fd->fd);
+ else if (r == MP_INPUT_DEAD)
+ cmd_fd->dead = true;
+}
+
+static void read_key_fd(struct input_ctx *ictx, struct input_fd *key_fd)
+{
+ int code = key_fd->read_key(key_fd->ctx, key_fd->fd);
+ if (code >= 0 || code == MP_INPUT_RELEASE_ALL) {
+ mp_input_feed_key(ictx, code);
+ return;
+ }
+
+ if (code == MP_INPUT_ERROR)
+ mp_tmsg(MSGT_INPUT, MSGL_ERR,
+ "Error on key input file descriptor %d\n", key_fd->fd);
+ else if (code == MP_INPUT_DEAD) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR,
+ "Dead key input on file descriptor %d\n", key_fd->fd);
+ key_fd->dead = true;
+ }
+}
+
+static void read_fd(struct input_ctx *ictx, struct input_fd *fd)
+{
+ if (fd->read_cmd) {
+ read_cmd_fd(ictx, fd);
+ } else {
+ read_key_fd(ictx, fd);
+ }
+}
+
+static void remove_dead_fds(struct input_ctx *ictx)
+{
+ for (int i = 0; i < ictx->num_fds; i++) {
+ if (ictx->fds[i].dead) {
+ mp_input_rm_fd(ictx, ictx->fds[i].fd);
+ i--;
+ }
+ }
+}
+
+#ifdef HAVE_POSIX_SELECT
+
+static void input_wait_read(struct input_ctx *ictx, int time)
+{
+ fd_set fds;
+ FD_ZERO(&fds);
+ int max_fd = 0;
+ for (int i = 0; i < ictx->num_fds; i++) {
+ if (!ictx->fds[i].select)
+ continue;
+ if (ictx->fds[i].fd > max_fd)
+ max_fd = ictx->fds[i].fd;
+ FD_SET(ictx->fds[i].fd, &fds);
+ }
+ struct timeval tv, *time_val;
+ tv.tv_sec = time / 1000;
+ tv.tv_usec = (time % 1000) * 1000;
+ time_val = &tv;
+ if (select(max_fd + 1, &fds, NULL, NULL, time_val) < 0) {
+ if (errno != EINTR)
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Select error: %s\n",
+ strerror(errno));
+ FD_ZERO(&fds);
+ }
+ for (int i = 0; i < ictx->num_fds; i++) {
+ if (ictx->fds[i].select && !FD_ISSET(ictx->fds[i].fd, &fds))
+ continue;
+ read_fd(ictx, &ictx->fds[i]);
+ }
+}
+
+#else
+
+static void input_wait_read(struct input_ctx *ictx, int time)
+{
+ if (time > 0)
+ mp_sleep_us(time * 1000);
+
+ for (int i = 0; i < ictx->num_fds; i++)
+ read_fd(ictx, &ictx->fds[i]);
+}
+
+#endif
+
+/**
+ * \param time time to wait at most for an event in milliseconds
+ */
+static void read_events(struct input_ctx *ictx, int time)
+{
+ if (ictx->num_key_down) {
+ time = FFMIN(time, 1000 / ictx->ar_rate);
+ time = FFMIN(time, ictx->ar_delay);
+ }
+ time = FFMAX(time, 0);
+
+ while (1) {
+ if (ictx->got_new_events)
+ time = 0;
+ ictx->got_new_events = false;
+
+ remove_dead_fds(ictx);
+
+ if (time) {
+ for (int i = 0; i < ictx->num_fds; i++) {
+ if (!ictx->fds[i].select)
+ read_fd(ictx, &ictx->fds[i]);
+ }
+ }
+
+ if (ictx->got_new_events)
+ time = 0;
+
+ input_wait_read(ictx, time);
+
+ // Read until all input FDs are empty
+ if (!ictx->got_new_events)
+ break;
+ }
+}
+
+static void read_all_events(struct input_ctx *ictx, int time)
+{
+ getch2_poll();
+#ifdef CONFIG_COCOA
+ cocoa_check_events();
+#endif
+ read_events(ictx, time);
+}
+
+int mp_input_queue_cmd(struct input_ctx *ictx, mp_cmd_t *cmd)
+{
+ ictx->got_new_events = true;
+ if (!cmd)
+ return 0;
+ queue_add(&ictx->control_cmd_queue, cmd, false);
+ return 1;
+}
+
+/**
+ * \param peek_only when set, the returned command stays in the queue.
+ * Do not free the returned cmd whe you set this!
+ */
+mp_cmd_t *mp_input_get_cmd(struct input_ctx *ictx, int time, int peek_only)
+{
+ if (async_quit_request) {
+ struct mp_cmd *cmd = mp_input_parse_cmd(bstr0("quit 1"), "");
+ queue_add(&ictx->control_cmd_queue, cmd, true);
+ }
+
+ if (ictx->control_cmd_queue.first || ictx->key_cmd_queue.first)
+ time = 0;
+ read_all_events(ictx, time);
+ struct cmd_queue *queue = &ictx->control_cmd_queue;
+ if (!queue->first)
+ queue = &ictx->key_cmd_queue;
+ if (!queue->first) {
+ struct mp_cmd *repeated = check_autorepeat(ictx);
+ if (repeated)
+ queue_add(queue, repeated, false);
+ }
+ struct mp_cmd *ret = queue->first;
+ if (!ret)
+ return NULL;
+
+ if (!peek_only) {
+ queue_remove(queue, ret);
+ if (ret->mouse_move) {
+ ictx->mouse_x = ret->mouse_x;
+ ictx->mouse_y = ret->mouse_y;
+ }
+ }
+
+ return ret;
+}
+
+void mp_input_get_mouse_pos(struct input_ctx *ictx, int *x, int *y)
+{
+ *x = ictx->mouse_x;
+ *y = ictx->mouse_y;
+}
+
+void mp_cmd_free(mp_cmd_t *cmd)
+{
+ talloc_free(cmd);
+}
+
+mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd)
+{
+ mp_cmd_t *ret;
+ int i;
+
+ if (!cmd)
+ return NULL;
+
+ ret = talloc_memdup(NULL, cmd, sizeof(mp_cmd_t));
+ ret->name = talloc_strdup(ret, cmd->name);
+ for (i = 0; i < MP_CMD_MAX_ARGS; i++) {
+ if (cmd->args[i].type.type == &m_option_type_string)
+ ret->args[i].v.s = talloc_strdup(ret, cmd->args[i].v.s);
+ }
+
+ if (cmd->id == MP_CMD_COMMAND_LIST) {
+ struct mp_cmd *prev = NULL;
+ for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) {
+ sub = mp_cmd_clone(sub);
+ talloc_steal(ret, sub);
+ if (prev) {
+ prev->queue_next = sub;
+ } else {
+ ret->args[0].v.p = sub;
+ }
+ prev = sub;
+ }
+ }
+
+ return ret;
+}
+
+int mp_input_get_key_from_name(const char *name)
+{
+ int modifiers = 0;
+ const char *p;
+ while ((p = strchr(name, '+'))) {
+ for (struct key_name *m = modifier_names; m->name; m++)
+ if (!bstrcasecmp(bstr0(m->name),
+ (struct bstr){(char *)name, p - name})) {
+ modifiers |= m->key;
+ goto found;
+ }
+ if (!strcmp(name, "+"))
+ return '+' + modifiers;
+ return -1;
+found:
+ name = p + 1;
+ }
+
+ struct bstr bname = bstr0(name);
+
+ struct bstr rest;
+ int code = bstr_decode_utf8(bname, &rest);
+ if (code >= 0 && rest.len == 0)
+ return code + modifiers;
+
+ if (bstr_startswith0(bname, "0x"))
+ return strtol(name, NULL, 16) + modifiers;
+
+ for (int i = 0; key_names[i].name != NULL; i++) {
+ if (strcasecmp(key_names[i].name, name) == 0)
+ return key_names[i].key + modifiers;
+ }
+
+ return -1;
+}
+
+static int get_input_from_name(char *name, int *out_num_keys, int *keys)
+{
+ char *end, *ptr;
+ int n = 0;
+
+ ptr = name;
+ n = 0;
+ for (end = strchr(ptr, '-'); ptr != NULL; end = strchr(ptr, '-')) {
+ if (end && end[1] != '\0') {
+ if (end[1] == '-')
+ end = &end[1];
+ end[0] = '\0';
+ }
+ keys[n] = mp_input_get_key_from_name(ptr);
+ if (keys[n] < 0)
+ return 0;
+ n++;
+ if (end && end[1] != '\0' && n < MP_MAX_KEY_DOWN)
+ ptr = &end[1];
+ else
+ break;
+ }
+ *out_num_keys = n;
+ return 1;
+}
+
+static void bind_dealloc(struct cmd_bind *bind)
+{
+ talloc_free(bind->cmd);
+ talloc_free(bind->location);
+}
+
+static void bind_keys(struct input_ctx *ictx, bool builtin, bstr section,
+ const int *keys, int num_keys, bstr command,
+ const char *loc)
+{
+ struct cmd_bind_section *bs = get_bind_section(ictx, section);
+ struct cmd_bind *bind = NULL;
+
+ assert(num_keys <= MP_MAX_KEY_DOWN);
+
+ for (int n = 0; n < bs->num_binds; n++) {
+ struct cmd_bind *b = &bs->binds[n];
+ if (bind_matches_key(b, num_keys, keys) && b->is_builtin == builtin) {
+ bind = b;
+ break;
+ }
+ }
+
+ if (!bind) {
+ struct cmd_bind empty = {{0}};
+ MP_TARRAY_APPEND(bs, bs->binds, bs->num_binds, empty);
+ bind = &bs->binds[bs->num_binds - 1];
+ }
+
+ bind_dealloc(bind);
+
+ *bind = (struct cmd_bind) {
+ .cmd = bstrdup0(bs->binds, command),
+ .location = talloc_strdup(bs->binds, loc),
+ .owner = bs,
+ .is_builtin = builtin,
+ .num_keys = num_keys,
+ };
+ memcpy(bind->keys, keys, num_keys * sizeof(bind->keys[0]));
+}
+
+// restrict_section: every entry is forced to this section name
+// if NULL, load normally and allow any sections
+static int parse_config(struct input_ctx *ictx, bool builtin, bstr data,
+ const char *location, const char *restrict_section)
+{
+ int n_binds = 0;
+ int line_no = 0;
+ char *cur_loc = NULL;
+
+ while (data.len) {
+ line_no++;
+ if (cur_loc)
+ talloc_free(cur_loc);
+ cur_loc = talloc_asprintf(NULL, "%s:%d", location, line_no);
+
+ bstr line = bstr_strip_linebreaks(bstr_getline(data, &data));
+ line = bstr_lstrip(line);
+ if (line.len == 0 || bstr_startswith0(line, "#"))
+ continue;
+ struct bstr command;
+ // Find the key name starting a line
+ struct bstr keyname = bstr_split(line, WHITESPACE, &command);
+ command = bstr_strip(command);
+ if (command.len == 0) {
+ mp_tmsg(MSGT_INPUT, MSGL_ERR,
+ "Unfinished key binding: %.*s at %s\n", BSTR_P(line),
+ cur_loc);
+ continue;
+ }
+ char *name = bstrdup0(NULL, keyname);
+ int keys[MP_MAX_KEY_DOWN];
+ int num_keys = 0;
+ if (!get_input_from_name(name, &num_keys, keys)) {
+ talloc_free(name);
+ mp_tmsg(MSGT_INPUT, MSGL_ERR,
+ "Unknown key '%.*s' at %s\n", BSTR_P(keyname), cur_loc);
+ continue;
+ }
+ talloc_free(name);
+
+ bstr section = bstr0(restrict_section);
+ if (!section.len) {
+ if (bstr_startswith0(command, "{")) {
+ int p = bstrchr(command, '}');
+ if (p != -1) {
+ section = bstr_strip(bstr_splice(command, 1, p));
+ command = bstr_lstrip(bstr_cut(command, p + 1));
+ }
+ }
+ }
+
+ bind_keys(ictx, builtin, section, keys, num_keys, command, cur_loc);
+ n_binds++;
+
+ // Print warnings if invalid commands are encountered.
+ talloc_free(mp_input_parse_cmd(command, cur_loc));
+ }
+
+ talloc_free(cur_loc);
+
+ return n_binds;
+}
+
+static int parse_config_file(struct input_ctx *ictx, char *file, bool warn)
+{
+ if (!mp_path_exists(file)) {
+ mp_msg(MSGT_INPUT, warn ? MSGL_ERR : MSGL_V,
+ "Input config file %s not found.\n", file);
+ return 0;
+ }
+ stream_t *s = stream_open(file, NULL);
+ if (!s) {
+ mp_msg(MSGT_INPUT, MSGL_ERR, "Can't open input config file %s.\n", file);
+ return 0;
+ }
+ bstr res = stream_read_complete(s, NULL, 1000000);
+ free_stream(s);
+ mp_msg(MSGT_INPUT, MSGL_V, "Parsing input config file %s\n", file);
+ int n_binds = parse_config(ictx, false, res, file, NULL);
+ talloc_free(res.start);
+ mp_msg(MSGT_INPUT, MSGL_V, "Input config file %s parsed: %d binds\n",
+ file, n_binds);
+ return 1;
+}
+
+// If name is NULL, return "default".
+// Return a statically allocated name of the section (i.e. return value never
+// gets deallocated).
+static char *normalize_section(struct input_ctx *ictx, char *name)
+{
+ return get_bind_section(ictx, bstr0(name))->section;
+}
+
+void mp_input_disable_section(struct input_ctx *ictx, char *name)
+{
+ name = normalize_section(ictx, name);
+
+ // Remove old section, or make sure it's on top if re-enabled
+ for (int i = ictx->num_active_sections - 1; i >= 0; i--) {
+ struct active_section *as = &ictx->active_sections[i];
+ if (strcmp(as->name, name) == 0) {
+ for (int x = i; i < ictx->num_active_sections - 1; i++)
+ ictx->active_sections[x] = ictx->active_sections[x + 1];
+ ictx->num_active_sections--;
+ }
+ }
+}
+
+void mp_input_enable_section(struct input_ctx *ictx, char *name, int flags)
+{
+ name = normalize_section(ictx, name);
+
+ mp_input_disable_section(ictx, name);
+
+ if (ictx->num_active_sections < MAX_ACTIVE_SECTIONS) {
+ ictx->active_sections[ictx->num_active_sections++] =
+ (struct active_section) {name, flags};
+ }
+}
+
+void mp_input_disable_all_sections(struct input_ctx *ictx)
+{
+ ictx->num_active_sections = 0;
+}
+
+void mp_input_set_section_mouse_area(struct input_ctx *ictx, char *name,
+ int x0, int y0, int x1, int y1)
+{
+ struct cmd_bind_section *s = get_bind_section(ictx, bstr0(name));
+ s->mouse_area = (struct mp_rect){x0, y0, x1, y1};
+ s->mouse_area_set = x0 != x1 && y0 != y1;
+}
+
+bool mp_input_test_mouse_active(struct input_ctx *ictx, int x, int y)
+{
+ for (int i = 0; i < ictx->num_active_sections; i++) {
+ char *name = ictx->active_sections[i].name;
+ struct cmd_bind_section *s = get_bind_section(ictx, bstr0(name));
+ if (s->mouse_area_set && test_rect(&s->mouse_area, x, y))
+ return true;
+ }
+ return false;
+}
+
+bool mp_input_test_dragging(struct input_ctx *ictx, int x, int y)
+{
+ return mp_input_test_mouse_active(ictx, x, y);
+}
+
+// builtin: if true, remove all builtin binds, else remove all user binds
+static void remove_binds(struct cmd_bind_section *bs, bool builtin)
+{
+ for (int n = bs->num_binds - 1; n >= 0; n--) {
+ if (bs->binds[n].is_builtin == builtin) {
+ bind_dealloc(&bs->binds[n]);
+ assert(bs->num_binds >= 1);
+ bs->binds[n] = bs->binds[bs->num_binds - 1];
+ bs->num_binds--;
+ }
+ }
+}
+
+void mp_input_define_section(struct input_ctx *ictx, char *name, char *location,
+ char *contents, bool builtin)
+{
+ if (!name || !name[0])
+ return; // parse_config() changes semantics with restrict_section==empty
+ if (contents) {
+ parse_config(ictx, builtin, bstr0(contents), location, name);
+ } else {
+ // Disable:
+ mp_input_disable_section(ictx, name);
+ // Delete:
+ struct cmd_bind_section *bs = get_bind_section(ictx, bstr0(name));
+ remove_binds(bs, builtin);
+ }
+}
+
+struct input_ctx *mp_input_init(struct MPOpts *opts)
+{
+ struct input_conf *input_conf = &opts->input;
+
+ struct input_ctx *ictx = talloc_ptrtype(NULL, ictx);
+ *ictx = (struct input_ctx){
+ .key_fifo_size = input_conf->key_fifo_size,
+ .doubleclick_time = input_conf->doubleclick_time,
+ .ar_state = -1,
+ .ar_delay = input_conf->ar_delay,
+ .ar_rate = input_conf->ar_rate,
+ .default_bindings = input_conf->default_bindings,
+ .test = input_conf->test,
+ .wakeup_pipe = {-1, -1},
+ };
+ mp_input_enable_section(ictx, NULL, 0);
+
+ parse_config(ictx, true, bstr0(builtin_input_conf), "<builtin>", NULL);
+
+#ifndef __MINGW32__
+ long ret = pipe(ictx->wakeup_pipe);
+ for (int i = 0; i < 2 && ret >= 0; i++) {
+ ret = fcntl(ictx->wakeup_pipe[i], F_GETFL);
+ if (ret < 0)
+ break;
+ ret = fcntl(ictx->wakeup_pipe[i], F_SETFL, ret | O_NONBLOCK);
+ }
+ if (ret < 0)
+ mp_msg(MSGT_INPUT, MSGL_ERR,
+ "Failed to initialize wakeup pipe: %s\n", strerror(errno));
+ else
+ mp_input_add_key_fd(ictx, ictx->wakeup_pipe[0], true, read_wakeup,
+ NULL, NULL);
+#endif
+
+ bool config_ok = false;
+ if (input_conf->config_file)
+ config_ok = parse_config_file(ictx, input_conf->config_file, true);
+ if (!config_ok && opts->load_config) {
+ // Try global conf dir
+ char *file = mp_find_config_file("input.conf");
+ config_ok = file && parse_config_file(ictx, file, false);
+ talloc_free(file);
+ }
+ if (!config_ok) {
+ mp_msg(MSGT_INPUT, MSGL_V, "Falling back on default (hardcoded) "
+ "input config\n");
+ }
+
+#ifdef CONFIG_JOYSTICK
+ if (input_conf->use_joystick) {
+ int fd = mp_input_joystick_init(input_conf->js_dev);
+ if (fd < 0)
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Can't init input joystick\n");
+ else
+ mp_input_add_key_fd(ictx, fd, 1, mp_input_joystick_read,
+ close, NULL);
+ }
+#endif
+
+#ifdef CONFIG_LIRC
+ if (input_conf->use_lirc) {
+ int fd = mp_input_lirc_init();
+ if (fd > 0)
+ mp_input_add_cmd_fd(ictx, fd, 0, mp_input_lirc_read,
+ mp_input_lirc_close);
+ }
+#endif
+
+#ifdef CONFIG_LIRCC
+ if (input_conf->use_lircc) {
+ int fd = lircc_init("mpv", NULL);
+ if (fd >= 0)
+ mp_input_add_cmd_fd(ictx, fd, 1, NULL, lircc_cleanup);
+ }
+#endif
+
+#ifdef CONFIG_COCOA
+ if (input_conf->use_ar) {
+ cocoa_init_apple_remote();
+ ictx->using_ar = true;
+ }
+
+ if (input_conf->use_media_keys) {
+ cocoa_init_media_keys();
+ ictx->using_cocoa_media_keys = true;
+ }
+#endif
+
+ if (input_conf->in_file) {
+ int mode = O_RDONLY;
+#ifndef __MINGW32__
+ // Use RDWR for FIFOs to ensure they stay open over multiple accesses.
+ // Note that on Windows due to how the API works, using RDONLY should
+ // be ok.
+ struct stat st;
+ if (stat(input_conf->in_file, &st) == 0 && S_ISFIFO(st.st_mode))
+ mode = O_RDWR;
+ mode |= O_NONBLOCK;
+#endif
+ int in_file_fd = open(input_conf->in_file, mode);
+ if (in_file_fd >= 0)
+ mp_input_add_cmd_fd(ictx, in_file_fd, 1, NULL, close);
+ else
+ mp_tmsg(MSGT_INPUT, MSGL_ERR, "Can't open %s: %s\n",
+ input_conf->in_file, strerror(errno));
+ }
+
+ return ictx;
+}
+
+static void clear_queue(struct cmd_queue *queue)
+{
+ while (queue->first) {
+ struct mp_cmd *item = queue->first;
+ queue_remove(queue, item);
+ talloc_free(item);
+ }
+}
+
+void mp_input_uninit(struct input_ctx *ictx)
+{
+ if (!ictx)
+ return;
+
+#ifdef CONFIG_COCOA
+ if (ictx->using_ar) {
+ cocoa_uninit_apple_remote();
+ }
+
+ if (ictx->using_cocoa_media_keys) {
+ cocoa_uninit_media_keys();
+ }
+#endif
+
+ for (int i = 0; i < ictx->num_fds; i++) {
+ if (ictx->fds[i].close_func)
+ ictx->fds[i].close_func(ictx->fds[i].fd);
+ }
+ for (int i = 0; i < 2; i++) {
+ if (ictx->wakeup_pipe[i] != -1)
+ close(ictx->wakeup_pipe[i]);
+ }
+ clear_queue(&ictx->key_cmd_queue);
+ clear_queue(&ictx->control_cmd_queue);
+ talloc_free(ictx->current_down_cmd);
+ talloc_free(ictx);
+}
+
+static int print_key_list(m_option_t *cfg, char *optname, char *optparam)
+{
+ int i;
+ printf("\n");
+ for (i = 0; key_names[i].name != NULL; i++)
+ printf("%s\n", key_names[i].name);
+ return M_OPT_EXIT;
+}
+
+static int print_cmd_list(m_option_t *cfg, char *optname, char *optparam)
+{
+ const mp_cmd_t *cmd;
+ int i, j;
+
+ for (i = 0; (cmd = &mp_cmds[i])->name != NULL; i++) {
+ printf("%-20.20s", cmd->name);
+ for (j = 0; j < MP_CMD_MAX_ARGS && cmd->args[j].type.type; j++) {
+ const char *type = cmd->args[j].type.type->name;
+ if (cmd->args[j].optional)
+ printf(" [%s]", type);
+ else
+ printf(" %s", type);
+ }
+ printf("\n");
+ }
+ return M_OPT_EXIT;
+}
+
+void mp_input_wakeup(struct input_ctx *ictx)
+{
+ if (ictx->wakeup_pipe[1] >= 0)
+ write(ictx->wakeup_pipe[1], &(char){0}, 1);
+}
+
+/**
+ * \param time time to wait for an interruption in milliseconds
+ */
+int mp_input_check_interrupt(struct input_ctx *ictx, int time)
+{
+ for (int i = 0; ; i++) {
+ if (async_quit_request || queue_has_abort_cmds(&ictx->key_cmd_queue) ||
+ queue_has_abort_cmds(&ictx->control_cmd_queue)) {
+ mp_tmsg(MSGT_INPUT, MSGL_WARN, "Received command to move to "
+ "another file. Aborting current processing.\n");
+ return true;
+ }
+ if (i)
+ return false;
+ read_all_events(ictx, time);
+ }
+}
+
+unsigned int mp_input_get_mouse_event_counter(struct input_ctx *ictx)
+{
+ // Make the frontend always display the mouse cursor (as long as it's not
+ // forced invisible) if mouse input is desired.
+ if (mp_input_test_mouse_active(ictx, ictx->mouse_x, ictx->mouse_y))
+ ictx->mouse_event_counter++;
+ return ictx->mouse_event_counter;
+}
diff --git a/mpvcore/input/input.h b/mpvcore/input/input.h
new file mode 100644
index 0000000000..92e2a32c4f
--- /dev/null
+++ b/mpvcore/input/input.h
@@ -0,0 +1,273 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_INPUT_H
+#define MPLAYER_INPUT_H
+
+#include <stdbool.h>
+#include "core/bstr.h"
+#include "core/m_option.h"
+
+// All command IDs
+enum mp_command_type {
+ MP_CMD_IGNORE,
+ MP_CMD_SEEK,
+ MP_CMD_QUIT,
+ MP_CMD_QUIT_WATCH_LATER,
+ MP_CMD_PLAYLIST_NEXT,
+ MP_CMD_PLAYLIST_PREV,
+ MP_CMD_OSD,
+ MP_CMD_TV_STEP_CHANNEL,
+ MP_CMD_TV_STEP_NORM,
+ MP_CMD_TV_STEP_CHANNEL_LIST,
+ MP_CMD_SCREENSHOT,
+ MP_CMD_SCREENSHOT_TO_FILE,
+ MP_CMD_LOADFILE,
+ MP_CMD_LOADLIST,
+ MP_CMD_PLAYLIST_CLEAR,
+ MP_CMD_PLAYLIST_REMOVE,
+ MP_CMD_PLAYLIST_MOVE,
+ MP_CMD_SUB_STEP,
+ MP_CMD_TV_SET_CHANNEL,
+ MP_CMD_TV_LAST_CHANNEL,
+ MP_CMD_TV_SET_FREQ,
+ MP_CMD_TV_SET_NORM,
+ MP_CMD_FRAME_STEP,
+ MP_CMD_FRAME_BACK_STEP,
+ MP_CMD_SPEED_MULT,
+ MP_CMD_RUN,
+ MP_CMD_SUB_ADD,
+ MP_CMD_SUB_REMOVE,
+ MP_CMD_SUB_RELOAD,
+ MP_CMD_KEYDOWN_EVENTS,
+ MP_CMD_SET,
+ MP_CMD_GET_PROPERTY,
+ MP_CMD_PRINT_TEXT,
+ MP_CMD_SHOW_TEXT,
+ MP_CMD_SHOW_PROGRESS,
+ MP_CMD_RADIO_STEP_CHANNEL,
+ MP_CMD_RADIO_SET_CHANNEL,
+ MP_CMD_RADIO_SET_FREQ,
+ MP_CMD_ADD,
+ MP_CMD_CYCLE,
+ MP_CMD_RADIO_STEP_FREQ,
+ MP_CMD_TV_STEP_FREQ,
+ MP_CMD_TV_START_SCAN,
+ MP_CMD_STOP,
+
+ MP_CMD_ENABLE_INPUT_SECTION,
+ MP_CMD_DISABLE_INPUT_SECTION,
+
+ /// DVB commands
+ MP_CMD_DVB_SET_CHANNEL = 5101,
+
+ /// Audio Filter commands
+ MP_CMD_AF,
+
+ /// Video filter commands
+ MP_CMD_VF,
+
+ /// Video output commands
+ MP_CMD_VO_CMDLINE,
+
+ // Internal
+ MP_CMD_COMMAND_LIST, // list of sub-commands in args[0].v.p
+};
+
+#define MP_CMD_MAX_ARGS 10
+
+// Error codes for the drivers
+
+// An error occurred but we can continue
+#define MP_INPUT_ERROR -1
+// A fatal error occurred, this driver should be removed
+#define MP_INPUT_DEAD -2
+// No input was available
+#define MP_INPUT_NOTHING -3
+//! Input will be available if you try again
+#define MP_INPUT_RETRY -4
+// Key FIFO was full - release events may be lost, zero button-down status
+#define MP_INPUT_RELEASE_ALL -5
+
+enum mp_on_osd {
+ MP_ON_OSD_NO = 0, // prefer not using OSD
+ MP_ON_OSD_AUTO = 1, // use default behavior of the specific command
+ MP_ON_OSD_BAR = 2, // force a bar, if applicable
+ MP_ON_OSD_MSG = 4, // force a message, if applicable
+};
+
+enum mp_input_section_flags {
+ // If a key binding is not defined in the current section, do not search the
+ // other sections for it (like the default section). Instead, an unbound
+ // key warning will be printed.
+ MP_INPUT_EXCLUSIVE = 1,
+};
+
+struct input_ctx;
+
+struct mp_cmd_arg {
+ struct m_option type;
+ bool optional;
+ union {
+ int i;
+ float f;
+ double d;
+ char *s;
+ void *p;
+ } v;
+};
+
+typedef struct mp_cmd {
+ int id;
+ char *name;
+ struct mp_cmd_arg args[MP_CMD_MAX_ARGS];
+ int nargs;
+ int pausing;
+ bool raw_args;
+ enum mp_on_osd on_osd;
+ bstr original;
+ char *input_section;
+ bool key_up_follows;
+ bool mouse_move;
+ int mouse_x, mouse_y;
+ struct mp_cmd *queue_next;
+} mp_cmd_t;
+
+
+// Executing this command will abort playback (play something else, or quit).
+bool mp_input_is_abort_cmd(int cmd_id);
+
+/* Add a new command input source.
+ * "fd" is a file descriptor (use a negative value if you don't use any fd)
+ * "select" tells whether to use select() on the fd to determine when to
+ * try reading.
+ * "read_func" is optional. If NULL a default function which reads data
+ * directly from the fd will be used. It must return either text data
+ * or one of the MP_INPUT error codes above.
+ * "close_func" will be called when closing. Can be NULL. Its return value
+ * is ignored (it's only there to allow using standard close() as the func).
+ */
+int mp_input_add_cmd_fd(struct input_ctx *ictx, int fd, int select,
+ int read_func(int fd, char *dest, int size),
+ int close_func(int fd));
+
+/* The args are similar to the cmd version above, except you must give
+ * a read_func, and it should return key codes (ASCII plus keycodes.h).
+ */
+int mp_input_add_key_fd(struct input_ctx *ictx, int fd, int select,
+ int read_func(void *ctx, int fd),
+ int close_func(int fd), void *ctx);
+
+// Process keyboard input. code is a key code from keycodes.h, possibly
+// with modifiers applied. MP_INPUT_RELEASE_ALL is also a valid value.
+void mp_input_put_key(struct input_ctx *ictx, int code);
+
+// Like mp_input_put_key(), but process all UTF-8 characters in the given
+// string as key events.
+void mp_input_put_key_utf8(struct input_ctx *ictx, int mods, struct bstr t);
+
+// Update mouse position (in window coordinates).
+void mp_input_set_mouse_pos(struct input_ctx *ictx, int x, int y);
+
+void mp_input_get_mouse_pos(struct input_ctx *ictx, int *x, int *y);
+
+// As for the cmd one you usually don't need this function.
+void mp_input_rm_key_fd(struct input_ctx *ictx, int fd);
+
+// Get input key from its name.
+int mp_input_get_key_from_name(const char *name);
+
+// Add a command to the command queue.
+int mp_input_queue_cmd(struct input_ctx *ictx, struct mp_cmd *cmd);
+
+/* Return next available command, or sleep up to "time" ms if none is
+ * available. If "peek_only" is true return a reference to the command
+ * but leave it queued.
+ */
+struct mp_cmd *mp_input_get_cmd(struct input_ctx *ictx, int time,
+ int peek_only);
+
+// Parse text and return corresponding struct mp_cmd.
+// The location parameter is for error messages.
+struct mp_cmd *mp_input_parse_cmd(bstr str, const char *location);
+
+// After getting a command from mp_input_get_cmd you need to free it using this
+// function
+void mp_cmd_free(struct mp_cmd *cmd);
+
+// This creates a copy of a command (used by the auto repeat stuff).
+struct mp_cmd *mp_cmd_clone(struct mp_cmd *cmd);
+
+// Set current input section. The section is appended on top of the list of
+// active sections, so its bindings are considered first. If the section was
+// already active, it's moved to the top as well.
+// name==NULL will behave as if name=="default"
+// flags is a bitfield of enum mp_input_section_flags values
+void mp_input_enable_section(struct input_ctx *ictx, char *name, int flags);
+
+// Undo mp_input_enable_section().
+// name==NULL will behave as if name=="default"
+void mp_input_disable_section(struct input_ctx *ictx, char *name);
+
+// Like mp_input_set_section(ictx, ..., 0) for all sections.
+void mp_input_disable_all_sections(struct input_ctx *ictx);
+
+// Set the contents of an input section.
+// name: name of the section, for mp_input_set_section() etc.
+// location: location string (like filename) for error reporting
+// contents: list of keybindings, like input.conf
+// a value of NULL deletes the section
+// builtin: create as builtin section; this means if the user defines bindings
+// using "{name}", they won't be ignored or overwritten - instead,
+// they are preferred to the bindings defined with this call
+// If the section already exists, its bindings are removed and replaced.
+void mp_input_define_section(struct input_ctx *ictx, char *name, char *location,
+ char *contents, bool builtin);
+
+// Define where on the screen the named input section should receive.
+// Setting a rectangle of size 0 unsets the mouse area.
+// A rectangle with negative size disables mouse input for this section.
+void mp_input_set_section_mouse_area(struct input_ctx *ictx, char *name,
+ int x0, int y0, int x1, int y1);
+
+// Used to detect mouse movement.
+unsigned int mp_input_get_mouse_event_counter(struct input_ctx *ictx);
+
+// Test whether there is any input section which wants to receive events.
+// Note that the mouse event is always delivered, even if this returns false.
+bool mp_input_test_mouse_active(struct input_ctx *ictx, int x, int y);
+
+// Whether input.c wants mouse drag events at this mouse position. If this
+// returns false, some VOs will initiate window dragging.
+bool mp_input_test_dragging(struct input_ctx *ictx, int x, int y);
+
+// Initialize the input system
+struct MPOpts;
+struct input_ctx *mp_input_init(struct MPOpts *opts);
+
+void mp_input_uninit(struct input_ctx *ictx);
+
+// Wake up sleeping input loop from another thread.
+void mp_input_wakeup(struct input_ctx *ictx);
+
+// Interruptible usleep: (used by demux)
+int mp_input_check_interrupt(struct input_ctx *ictx, int time);
+
+extern int async_quit_request;
+
+#endif /* MPLAYER_INPUT_H */
diff --git a/mpvcore/input/joystick.c b/mpvcore/input/joystick.c
new file mode 100644
index 0000000000..e8330ffaeb
--- /dev/null
+++ b/mpvcore/input/joystick.c
@@ -0,0 +1,162 @@
+/*
+ * 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 "config.h"
+
+#include "joystick.h"
+#include "input.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "core/mp_msg.h"
+#include "keycodes.h"
+
+#ifndef JOY_AXIS_DELTA
+#define JOY_AXIS_DELTA 500
+#endif
+
+#ifndef JS_DEV
+#define JS_DEV "/dev/input/js0"
+#endif
+
+#include <linux/joystick.h>
+
+int axis[256];
+int btns = 0;
+
+int mp_input_joystick_init(char* dev) {
+ int fd,l=0;
+ int initialized = 0;
+ struct js_event ev;
+
+ mp_tmsg(MSGT_INPUT,MSGL_V,"Opening joystick device %s\n",dev ? dev : JS_DEV);
+
+ fd = open( dev ? dev : JS_DEV , O_RDONLY | O_NONBLOCK );
+ if(fd < 0) {
+ mp_tmsg(MSGT_INPUT,MSGL_ERR,"Can't open joystick device %s: %s\n",dev ? dev : JS_DEV,strerror(errno));
+ return -1;
+ }
+
+ while(! initialized) {
+ l = 0;
+ while((unsigned int)l < sizeof(struct js_event)) {
+ int r = read(fd,((char*)&ev)+l,sizeof(struct js_event)-l);
+ if(r < 0) {
+ if(errno == EINTR)
+ continue;
+ else if(errno == EAGAIN) {
+ initialized = 1;
+ break;
+ }
+ mp_tmsg(MSGT_INPUT,MSGL_ERR,"Error while reading joystick device: %s\n",strerror(errno));
+ close(fd);
+ return -1;
+ }
+ l += r;
+ }
+ if((unsigned int)l < sizeof(struct js_event)) {
+ if(l > 0)
+ mp_tmsg(MSGT_INPUT,MSGL_WARN,"Joystick: We lose %d bytes of data\n",l);
+ break;
+ }
+ if(ev.type == JS_EVENT_BUTTON)
+ btns |= (ev.value << ev.number);
+ if(ev.type == JS_EVENT_AXIS)
+ axis[ev.number] = ev.value;
+ }
+
+ return fd;
+}
+
+int mp_input_joystick_read(void *ctx, int fd) {
+ struct js_event ev;
+ int l=0;
+
+ while((unsigned int)l < sizeof(struct js_event)) {
+ int r = read(fd,&ev+l,sizeof(struct js_event)-l);
+ if(r <= 0) {
+ if(errno == EINTR)
+ continue;
+ else if(errno == EAGAIN)
+ return MP_INPUT_NOTHING;
+ if( r < 0)
+ mp_tmsg(MSGT_INPUT,MSGL_ERR,"Error while reading joystick device: %s\n",strerror(errno));
+ else
+ mp_tmsg(MSGT_INPUT,MSGL_ERR,"Error while reading joystick device: %s\n","EOF");
+ return MP_INPUT_DEAD;
+ }
+ l += r;
+ }
+
+ if((unsigned int)l < sizeof(struct js_event)) {
+ if(l > 0)
+ mp_tmsg(MSGT_INPUT,MSGL_WARN,"Joystick: We lose %d bytes of data\n",l);
+ return MP_INPUT_NOTHING;
+ }
+
+ if(ev.type & JS_EVENT_INIT) {
+ mp_tmsg(MSGT_INPUT,MSGL_WARN,"Joystick: warning init event, we have lost sync with driver.\n");
+ ev.type &= ~JS_EVENT_INIT;
+ if(ev.type == JS_EVENT_BUTTON) {
+ int s = (btns >> ev.number) & 1;
+ if(s == ev.value) // State is the same : ignore
+ return MP_INPUT_NOTHING;
+ }
+ if(ev.type == JS_EVENT_AXIS) {
+ if( ( axis[ev.number] == 1 && ev.value > JOY_AXIS_DELTA) ||
+ (axis[ev.number] == -1 && ev.value < -JOY_AXIS_DELTA) ||
+ (axis[ev.number] == 0 && ev.value >= -JOY_AXIS_DELTA && ev.value <= JOY_AXIS_DELTA)
+ ) // State is the same : ignore
+ return MP_INPUT_NOTHING;
+ }
+ }
+
+ if(ev.type & JS_EVENT_BUTTON) {
+ btns &= ~(1 << ev.number);
+ btns |= (ev.value << ev.number);
+ if(ev.value == 1)
+ return (MP_JOY_BTN0 + ev.number) | MP_KEY_STATE_DOWN;
+ else
+ return (MP_JOY_BTN0 + ev.number) | MP_KEY_STATE_UP;
+ } else if(ev.type & JS_EVENT_AXIS) {
+ if(ev.value < -JOY_AXIS_DELTA && axis[ev.number] != -1) {
+ axis[ev.number] = -1;
+ return (MP_JOY_AXIS0_MINUS+(2*ev.number)) | MP_KEY_STATE_DOWN;
+ } else if(ev.value > JOY_AXIS_DELTA && axis[ev.number] != 1) {
+ axis[ev.number] = 1;
+ return (MP_JOY_AXIS0_PLUS+(2*ev.number)) | MP_KEY_STATE_DOWN;
+ } else if(ev.value <= JOY_AXIS_DELTA && ev.value >= -JOY_AXIS_DELTA && axis[ev.number] != 0) {
+ int r = axis[ev.number] == 1 ? MP_JOY_AXIS0_PLUS+(2*ev.number) : MP_JOY_AXIS0_MINUS+(2*ev.number);
+ axis[ev.number] = 0;
+ return r | MP_KEY_STATE_UP;
+ } else
+ return MP_INPUT_NOTHING;
+ } else {
+ mp_tmsg(MSGT_INPUT,MSGL_WARN,"Joystick warning unknown event type %d\n",ev.type);
+ return MP_INPUT_ERROR;
+ }
+
+ return MP_INPUT_NOTHING;
+}
diff --git a/mpvcore/input/joystick.h b/mpvcore/input/joystick.h
new file mode 100644
index 0000000000..b9480eb686
--- /dev/null
+++ b/mpvcore/input/joystick.h
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_JOYSTICK_H
+#define MPLAYER_JOYSTICK_H
+
+int mp_input_joystick_init(char* dev);
+
+int mp_input_joystick_read(void *ctx, int fd);
+
+#endif /* MPLAYER_JOYSTICK_H */
diff --git a/mpvcore/input/keycodes.h b/mpvcore/input/keycodes.h
new file mode 100644
index 0000000000..d91465f3be
--- /dev/null
+++ b/mpvcore/input/keycodes.h
@@ -0,0 +1,247 @@
+/*
+ * KEY code definitions for keys/events not passed by ASCII value
+ *
+ * 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.
+ */
+
+#ifndef MPLAYER_KEYCODES_H
+#define MPLAYER_KEYCODES_H
+
+#define MP_KEY_BASE (1<<21)
+
+#define MP_KEY_ENTER 13
+#define MP_KEY_TAB 9
+
+/* Control keys */
+#define MP_KEY_BACKSPACE (MP_KEY_BASE+0)
+#define MP_KEY_DELETE (MP_KEY_BASE+1)
+#define MP_KEY_INSERT (MP_KEY_BASE+2)
+#define MP_KEY_HOME (MP_KEY_BASE+3)
+#define MP_KEY_END (MP_KEY_BASE+4)
+#define MP_KEY_PAGE_UP (MP_KEY_BASE+5)
+#define MP_KEY_PAGE_DOWN (MP_KEY_BASE+6)
+#define MP_KEY_ESC (MP_KEY_BASE+7)
+#define MP_KEY_PRINT (MP_KEY_BASE+8)
+
+/* Control keys short name */
+#define MP_KEY_BS MP_KEY_BACKSPACE
+#define MP_KEY_DEL MP_KEY_DELETE
+#define MP_KEY_INS MP_KEY_INSERT
+#define MP_KEY_PGUP MP_KEY_PAGE_UP
+#define MP_KEY_PGDOWN MP_KEY_PAGE_DOWN
+#define MP_KEY_PGDWN MP_KEY_PAGE_DOWN
+
+/* Cursor movement */
+#define MP_KEY_CRSR (MP_KEY_BASE+0x10)
+#define MP_KEY_RIGHT (MP_KEY_CRSR+0)
+#define MP_KEY_LEFT (MP_KEY_CRSR+1)
+#define MP_KEY_DOWN (MP_KEY_CRSR+2)
+#define MP_KEY_UP (MP_KEY_CRSR+3)
+
+/* Multimedia keyboard/remote keys */
+#define MP_KEY_MM_BASE (MP_KEY_BASE+0x20)
+#define MP_KEY_POWER (MP_KEY_MM_BASE+0)
+#define MP_KEY_MENU (MP_KEY_MM_BASE+1)
+#define MP_KEY_PLAY (MP_KEY_MM_BASE+2)
+#define MP_KEY_PAUSE (MP_KEY_MM_BASE+3)
+#define MP_KEY_PLAYPAUSE (MP_KEY_MM_BASE+4)
+#define MP_KEY_STOP (MP_KEY_MM_BASE+5)
+#define MP_KEY_FORWARD (MP_KEY_MM_BASE+6)
+#define MP_KEY_REWIND (MP_KEY_MM_BASE+7)
+#define MP_KEY_NEXT (MP_KEY_MM_BASE+8)
+#define MP_KEY_PREV (MP_KEY_MM_BASE+9)
+#define MP_KEY_VOLUME_UP (MP_KEY_MM_BASE+10)
+#define MP_KEY_VOLUME_DOWN (MP_KEY_MM_BASE+11)
+#define MP_KEY_MUTE (MP_KEY_MM_BASE+12)
+
+/* Function keys */
+#define MP_KEY_F (MP_KEY_BASE+0x40)
+
+/* Keypad keys */
+#define MP_KEY_KEYPAD (MP_KEY_BASE+0x60)
+#define MP_KEY_KP0 (MP_KEY_KEYPAD+0)
+#define MP_KEY_KP1 (MP_KEY_KEYPAD+1)
+#define MP_KEY_KP2 (MP_KEY_KEYPAD+2)
+#define MP_KEY_KP3 (MP_KEY_KEYPAD+3)
+#define MP_KEY_KP4 (MP_KEY_KEYPAD+4)
+#define MP_KEY_KP5 (MP_KEY_KEYPAD+5)
+#define MP_KEY_KP6 (MP_KEY_KEYPAD+6)
+#define MP_KEY_KP7 (MP_KEY_KEYPAD+7)
+#define MP_KEY_KP8 (MP_KEY_KEYPAD+8)
+#define MP_KEY_KP9 (MP_KEY_KEYPAD+9)
+#define MP_KEY_KPDEC (MP_KEY_KEYPAD+10)
+#define MP_KEY_KPINS (MP_KEY_KEYPAD+11)
+#define MP_KEY_KPDEL (MP_KEY_KEYPAD+12)
+#define MP_KEY_KPENTER (MP_KEY_KEYPAD+13)
+
+
+// Joystick input module
+#define MP_JOY_BASE (MP_KEY_BASE+0x70)
+#define MP_JOY_AXIS0_PLUS (MP_JOY_BASE+0)
+#define MP_JOY_AXIS0_MINUS (MP_JOY_BASE+1)
+#define MP_JOY_AXIS1_PLUS (MP_JOY_BASE+2)
+#define MP_JOY_AXIS1_MINUS (MP_JOY_BASE+3)
+#define MP_JOY_AXIS2_PLUS (MP_JOY_BASE+4)
+#define MP_JOY_AXIS2_MINUS (MP_JOY_BASE+5)
+#define MP_JOY_AXIS3_PLUS (MP_JOY_BASE+6)
+#define MP_JOY_AXIS3_MINUS (MP_JOY_BASE+7)
+#define MP_JOY_AXIS4_PLUS (MP_JOY_BASE+8)
+#define MP_JOY_AXIS4_MINUS (MP_JOY_BASE+9)
+#define MP_JOY_AXIS5_PLUS (MP_JOY_BASE+10)
+#define MP_JOY_AXIS5_MINUS (MP_JOY_BASE+11)
+#define MP_JOY_AXIS6_PLUS (MP_JOY_BASE+12)
+#define MP_JOY_AXIS6_MINUS (MP_JOY_BASE+13)
+#define MP_JOY_AXIS7_PLUS (MP_JOY_BASE+14)
+#define MP_JOY_AXIS7_MINUS (MP_JOY_BASE+15)
+#define MP_JOY_AXIS8_PLUS (MP_JOY_BASE+16)
+#define MP_JOY_AXIS8_MINUS (MP_JOY_BASE+17)
+#define MP_JOY_AXIS9_PLUS (MP_JOY_BASE+18)
+#define MP_JOY_AXIS9_MINUS (MP_JOY_BASE+19)
+
+#define MP_JOY_BTN_BASE ((MP_KEY_BASE+0x90)|MP_NO_REPEAT_KEY)
+#define MP_JOY_BTN0 (MP_JOY_BTN_BASE+0)
+#define MP_JOY_BTN1 (MP_JOY_BTN_BASE+1)
+#define MP_JOY_BTN2 (MP_JOY_BTN_BASE+2)
+#define MP_JOY_BTN3 (MP_JOY_BTN_BASE+3)
+#define MP_JOY_BTN4 (MP_JOY_BTN_BASE+4)
+#define MP_JOY_BTN5 (MP_JOY_BTN_BASE+5)
+#define MP_JOY_BTN6 (MP_JOY_BTN_BASE+6)
+#define MP_JOY_BTN7 (MP_JOY_BTN_BASE+7)
+#define MP_JOY_BTN8 (MP_JOY_BTN_BASE+8)
+#define MP_JOY_BTN9 (MP_JOY_BTN_BASE+9)
+
+
+// Mouse events from VOs
+#define MP_MOUSE_BASE ((MP_KEY_BASE+0xA0)|MP_NO_REPEAT_KEY|MP_KEY_EMIT_ON_UP)
+#define MP_MOUSE_BTN0 (MP_MOUSE_BASE+0)
+#define MP_MOUSE_BTN1 (MP_MOUSE_BASE+1)
+#define MP_MOUSE_BTN2 (MP_MOUSE_BASE+2)
+#define MP_MOUSE_BTN3 (MP_MOUSE_BASE+3)
+#define MP_MOUSE_BTN4 (MP_MOUSE_BASE+4)
+#define MP_MOUSE_BTN5 (MP_MOUSE_BASE+5)
+#define MP_MOUSE_BTN6 (MP_MOUSE_BASE+6)
+#define MP_MOUSE_BTN7 (MP_MOUSE_BASE+7)
+#define MP_MOUSE_BTN8 (MP_MOUSE_BASE+8)
+#define MP_MOUSE_BTN9 (MP_MOUSE_BASE+9)
+#define MP_MOUSE_BTN10 (MP_MOUSE_BASE+10)
+#define MP_MOUSE_BTN11 (MP_MOUSE_BASE+11)
+#define MP_MOUSE_BTN12 (MP_MOUSE_BASE+12)
+#define MP_MOUSE_BTN13 (MP_MOUSE_BASE+13)
+#define MP_MOUSE_BTN14 (MP_MOUSE_BASE+14)
+#define MP_MOUSE_BTN15 (MP_MOUSE_BASE+15)
+#define MP_MOUSE_BTN16 (MP_MOUSE_BASE+16)
+#define MP_MOUSE_BTN17 (MP_MOUSE_BASE+17)
+#define MP_MOUSE_BTN18 (MP_MOUSE_BASE+18)
+#define MP_MOUSE_BTN19 (MP_MOUSE_BASE+19)
+#define MP_MOUSE_BTN_END (MP_MOUSE_BASE+20)
+
+#define MP_KEY_IS_MOUSE_BTN_SINGLE(code) \
+ ((code) >= MP_MOUSE_BASE && (code) < MP_MOUSE_BTN_END)
+
+#define MP_MOUSE_BASE_DBL ((MP_KEY_BASE+0xC0)|MP_NO_REPEAT_KEY)
+#define MP_MOUSE_BTN0_DBL (MP_MOUSE_BASE_DBL+0)
+#define MP_MOUSE_BTN1_DBL (MP_MOUSE_BASE_DBL+1)
+#define MP_MOUSE_BTN2_DBL (MP_MOUSE_BASE_DBL+2)
+#define MP_MOUSE_BTN3_DBL (MP_MOUSE_BASE_DBL+3)
+#define MP_MOUSE_BTN4_DBL (MP_MOUSE_BASE_DBL+4)
+#define MP_MOUSE_BTN5_DBL (MP_MOUSE_BASE_DBL+5)
+#define MP_MOUSE_BTN6_DBL (MP_MOUSE_BASE_DBL+6)
+#define MP_MOUSE_BTN7_DBL (MP_MOUSE_BASE_DBL+7)
+#define MP_MOUSE_BTN8_DBL (MP_MOUSE_BASE_DBL+8)
+#define MP_MOUSE_BTN9_DBL (MP_MOUSE_BASE_DBL+9)
+#define MP_MOUSE_BTN10_DBL (MP_MOUSE_BASE_DBL+10)
+#define MP_MOUSE_BTN11_DBL (MP_MOUSE_BASE_DBL+11)
+#define MP_MOUSE_BTN12_DBL (MP_MOUSE_BASE_DBL+12)
+#define MP_MOUSE_BTN13_DBL (MP_MOUSE_BASE_DBL+13)
+#define MP_MOUSE_BTN14_DBL (MP_MOUSE_BASE_DBL+14)
+#define MP_MOUSE_BTN15_DBL (MP_MOUSE_BASE_DBL+15)
+#define MP_MOUSE_BTN16_DBL (MP_MOUSE_BASE_DBL+16)
+#define MP_MOUSE_BTN17_DBL (MP_MOUSE_BASE_DBL+17)
+#define MP_MOUSE_BTN18_DBL (MP_MOUSE_BASE_DBL+18)
+#define MP_MOUSE_BTN19_DBL (MP_MOUSE_BASE_DBL+19)
+#define MP_MOUSE_BTN_DBL_END (MP_MOUSE_BASE_DBL+20)
+
+#define MP_KEY_IS_MOUSE_BTN_DBL(code) \
+ ((code) >= MP_MOUSE_BTN0_DBL && (code) < MP_MOUSE_BTN_DBL_END)
+
+// Apple Remote input module
+#define MP_AR_BASE (MP_KEY_BASE+0xE0)
+#define MP_AR_PLAY (MP_AR_BASE + 0)
+#define MP_AR_PLAY_HOLD (MP_AR_BASE + 1)
+#define MP_AR_CENTER (MP_AR_BASE + 2)
+#define MP_AR_CENTER_HOLD (MP_AR_BASE + 3)
+#define MP_AR_NEXT (MP_AR_BASE + 4)
+#define MP_AR_NEXT_HOLD (MP_AR_BASE + 5)
+#define MP_AR_PREV (MP_AR_BASE + 6)
+#define MP_AR_PREV_HOLD (MP_AR_BASE + 7)
+#define MP_AR_MENU (MP_AR_BASE + 8)
+#define MP_AR_MENU_HOLD (MP_AR_BASE + 9)
+#define MP_AR_VUP (MP_AR_BASE + 10)
+#define MP_AR_VUP_HOLD (MP_AR_BASE + 11)
+#define MP_AR_VDOWN (MP_AR_BASE + 12)
+#define MP_AR_VDOWN_HOLD (MP_AR_BASE + 13)
+
+// Apple Media Keys input module
+#define MP_MK_BASE (MP_KEY_BASE+0xF0)
+#define MP_MK_PLAY (MP_MK_BASE + 0)
+#define MP_MK_PREV (MP_MK_BASE + 1)
+#define MP_MK_NEXT (MP_MK_BASE + 2)
+
+/* Special keys */
+#define MP_KEY_INTERN (MP_KEY_BASE+0x1000)
+#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)
+#define MP_KEY_MOUSE_LEAVE ((MP_KEY_INTERN+2)|MP_NO_REPEAT_KEY)
+
+
+#define MP_KEY_DEPENDS_ON_MOUSE_POS(code) \
+ (MP_KEY_IS_MOUSE_BTN_SINGLE(code) || MP_KEY_IS_MOUSE_BTN_DBL(code) || \
+ (code) == MP_KEY_MOUSE_MOVE)
+
+/* Modifiers added to individual keys */
+#define MP_KEY_MODIFIER_SHIFT (1<<22)
+#define MP_KEY_MODIFIER_CTRL (1<<23)
+#define MP_KEY_MODIFIER_ALT (1<<24)
+#define MP_KEY_MODIFIER_META (1<<25)
+
+#define MP_KEY_MODIFIER_MASK (MP_KEY_MODIFIER_SHIFT | MP_KEY_MODIFIER_CTRL | \
+ MP_KEY_MODIFIER_ALT | MP_KEY_MODIFIER_META | \
+ MP_KEY_STATE_DOWN | MP_KEY_STATE_UP)
+
+// Flag for key events. Multiple down events are idempotent. Release keys by
+// sending the key code with KEY_STATE_UP set, or by sending
+// MP_INPUT_RELEASE_ALL as key code.
+#define MP_KEY_STATE_DOWN (1<<26)
+
+// Flag for key events. Releases a key previously held down with
+// MP_KEY_STATE_DOWN. Do not sending redundant UP events and do not forget to
+// release keys at all with UP. If input is unreliable, use MP_INPUT_RELEASE_ALL
+// or don't use MP_KEY_STATE_DOWN in the first place.
+#define MP_KEY_STATE_UP (1<<27)
+
+// The following flags are not modifiers, but are part of the keycode itself.
+
+// Emit a command even on key-up (normally key-up is ignored). The command
+// handling code has to ignore unwanted commands specifically.
+#define MP_KEY_EMIT_ON_UP (1<<28)
+
+// Use this when the key shouldn't be auto-repeated (like mouse buttons)
+// Also means both key-down key-up events produce emit bound commands.
+#define MP_NO_REPEAT_KEY (1<<29)
+
+#endif /* MPLAYER_KEYCODES_H */
diff --git a/mpvcore/input/lirc.c b/mpvcore/input/lirc.c
new file mode 100644
index 0000000000..699168d239
--- /dev/null
+++ b/mpvcore/input/lirc.c
@@ -0,0 +1,123 @@
+/*
+ * 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 "config.h"
+
+#include <lirc/lirc_client.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "core/mp_msg.h"
+#include "input.h"
+#include "lirc.h"
+
+static struct lirc_config *lirc_config;
+char *lirc_configfile;
+
+static char* cmd_buf = NULL;
+
+int
+mp_input_lirc_init(void) {
+ int lirc_sock;
+ int mode;
+
+ mp_tmsg(MSGT_LIRC,MSGL_V,"Setting up LIRC support...\n");
+ if((lirc_sock=lirc_init("mpv",0))==-1){
+ mp_tmsg(MSGT_LIRC,MSGL_V,"Failed to open LIRC support. You will not be able to use your remote control.\n");
+ return -1;
+ }
+
+ mode = fcntl(lirc_sock, F_GETFL);
+ if (mode < 0 || fcntl(lirc_sock, F_SETFL, mode | O_NONBLOCK) < 0) {
+ mp_msg(MSGT_LIRC, MSGL_ERR, "setting non-blocking mode failed: %s\n",
+ strerror(errno));
+ lirc_deinit();
+ return -1;
+ }
+
+ if(lirc_readconfig( lirc_configfile,&lirc_config,NULL )!=0 ){
+ mp_tmsg(MSGT_LIRC,MSGL_ERR,"Failed to read LIRC config file %s.\n",
+ lirc_configfile == NULL ? "~/.lircrc" : lirc_configfile);
+ lirc_deinit();
+ return -1;
+ }
+
+ return lirc_sock;
+}
+
+int mp_input_lirc_read(int fd,char* dest, int s) {
+ int r,cl = 0;
+ char *code = NULL,*c = NULL;
+
+ // We have something in the buffer return it
+ if(cmd_buf != NULL) {
+ int l = strlen(cmd_buf), w = l > s ? s : l;
+ memcpy(dest,cmd_buf,w);
+ l -= w;
+ if(l > 0)
+ memmove(cmd_buf,&cmd_buf[w],l+1);
+ else {
+ free(cmd_buf);
+ cmd_buf = NULL;
+ }
+ return w;
+ }
+
+ // Nothing in the buffer, poll the lirc fd
+ if(lirc_nextcode(&code) != 0) {
+ mp_msg(MSGT_LIRC,MSGL_ERR,"Lirc error :(\n");
+ return MP_INPUT_DEAD;
+ }
+
+ if(!code) return MP_INPUT_NOTHING;
+
+ // We put all cmds in a single buffer separated by \n
+ while((r = lirc_code2char(lirc_config,code,&c))==0 && c!=NULL) {
+ int l = strlen(c);
+ if(l <= 0)
+ continue;
+ cmd_buf = realloc(cmd_buf,cl+l+2);
+ memcpy(&cmd_buf[cl],c,l);
+ cl += l+1;
+ cmd_buf[cl-1] = '\n';
+ cmd_buf[cl] = '\0';
+ }
+
+ free(code);
+
+ if(r < 0)
+ return MP_INPUT_DEAD;
+ else if(cmd_buf) // return the first command in the buffer
+ return mp_input_lirc_read(fd,dest,s);
+ else
+ return MP_INPUT_RETRY;
+
+}
+
+int mp_input_lirc_close(int fd)
+{
+ free(cmd_buf);
+ cmd_buf = NULL;
+ lirc_freeconfig(lirc_config);
+ lirc_deinit();
+ return 0;
+}
diff --git a/mpvcore/input/lirc.h b/mpvcore/input/lirc.h
new file mode 100644
index 0000000000..ac1937f91d
--- /dev/null
+++ b/mpvcore/input/lirc.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_LIRC_H
+#define MPLAYER_LIRC_H
+
+int
+mp_input_lirc_init(void);
+
+int
+mp_input_lirc_read(int fd,char* dest, int s);
+
+int mp_input_lirc_close(int fd);
+
+#endif /* MPLAYER_LIRC_H */
diff --git a/mpvcore/m_config.c b/mpvcore/m_config.c
new file mode 100644
index 0000000000..f362b43836
--- /dev/null
+++ b/mpvcore/m_config.c
@@ -0,0 +1,777 @@
+/*
+ * 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.
+ */
+
+/// \file
+/// \ingroup Config
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include "talloc.h"
+
+#include "m_config.h"
+#include "core/m_option.h"
+#include "core/mp_msg.h"
+
+// Profiles allow to predefine some sets of options that can then
+// be applied later on with the internal -profile option.
+#define MAX_PROFILE_DEPTH 20
+
+struct m_profile {
+ struct m_profile *next;
+ char *name;
+ char *desc;
+ int num_opts;
+ // Option/value pair array.
+ char **opts;
+};
+
+// In the file local case, this contains the old global value.
+struct m_opt_backup {
+ struct m_opt_backup *next;
+ struct m_config_option *co;
+ void *backup;
+};
+
+static int parse_include(struct m_config *config, struct bstr param, bool set,
+ int flags)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+ if (!set)
+ return 1;
+ char *filename = bstrdup0(NULL, param);
+ config->includefunc(config, filename, flags);
+ talloc_free(filename);
+ return 1;
+}
+
+static int parse_profile(struct m_config *config, const struct m_option *opt,
+ struct bstr name, struct bstr param, bool set, int flags)
+{
+ if (!bstrcmp0(param, "help")) {
+ struct m_profile *p;
+ if (!config->profiles) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_INFO,
+ "No profiles have been defined.\n");
+ return M_OPT_EXIT - 1;
+ }
+ mp_tmsg(MSGT_CFGPARSER, MSGL_INFO, "Available profiles:\n");
+ for (p = config->profiles; p; p = p->next)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\t%s\t%s\n", p->name,
+ p->desc ? p->desc : "");
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ return M_OPT_EXIT - 1;
+ }
+
+ char **list = NULL;
+ int r = m_option_type_string_list.parse(opt, name, param, &list);
+ if (r < 0)
+ return r;
+ if (!list || !list[0])
+ return M_OPT_INVALID;
+ for (int i = 0; list[i]; i++) {
+ struct m_profile *p = m_config_get_profile0(config, list[i]);
+ if (!p) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_WARN, "Unknown profile '%s'.\n",
+ list[i]);
+ r = M_OPT_INVALID;
+ } else if (set)
+ m_config_set_profile(config, p, flags);
+ }
+ m_option_free(opt, &list);
+ return r;
+}
+
+static int show_profile(struct m_config *config, bstr param)
+{
+ struct m_profile *p;
+ int i, j;
+ if (!param.len)
+ return M_OPT_MISSING_PARAM;
+ if (!(p = m_config_get_profile(config, param))) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Unknown profile '%.*s'.\n",
+ BSTR_P(param));
+ return M_OPT_EXIT - 1;
+ }
+ if (!config->profile_depth)
+ mp_tmsg(MSGT_CFGPARSER, MSGL_INFO, "Profile %s: %s\n", p->name,
+ p->desc ? p->desc : "");
+ config->profile_depth++;
+ for (i = 0; i < p->num_opts; i++) {
+ char spc[config->profile_depth + 1];
+ for (j = 0; j < config->profile_depth; j++)
+ spc[j] = ' ';
+ spc[config->profile_depth] = '\0';
+
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "%s%s=%s\n", spc,
+ p->opts[2 * i], p->opts[2 * i + 1]);
+
+ if (config->profile_depth < MAX_PROFILE_DEPTH
+ && !strcmp(p->opts[2*i], "profile")) {
+ char *e, *list = p->opts[2 * i + 1];
+ while ((e = strchr(list, ','))) {
+ int l = e - list;
+ char tmp[l+1];
+ if (!l)
+ continue;
+ memcpy(tmp, list, l);
+ tmp[l] = '\0';
+ show_profile(config, bstr0(tmp));
+ list = e + 1;
+ }
+ if (list[0] != '\0')
+ show_profile(config, bstr0(list));
+ }
+ }
+ config->profile_depth--;
+ if (!config->profile_depth)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ return M_OPT_EXIT - 1;
+}
+
+static int list_options(struct m_config *config)
+{
+ m_config_print_option_list(config);
+ return M_OPT_EXIT;
+}
+
+// The memcpys are supposed to work around the struct aliasing violation,
+// that would result if we just dereferenced a void** (where the void** is
+// actually casted from struct some_type* ).
+static void *substruct_read_ptr(void *ptr)
+{
+ void *res;
+ memcpy(&res, ptr, sizeof(void*));
+ return res;
+}
+static void substruct_write_ptr(void *ptr, void *val)
+{
+ memcpy(ptr, &val, sizeof(void*));
+}
+
+static struct m_config_option *m_config_add_option(struct m_config *config,
+ const char *prefix,
+ struct m_config_option *parent,
+ const struct m_option *arg);
+
+static void add_options(struct m_config *config,
+ struct m_config_option *parent,
+ const struct m_option *defs);
+
+static int config_destroy(void *p)
+{
+ struct m_config *config = p;
+ m_config_restore_backups(config);
+ for (struct m_config_option *copt = config->opts; copt; copt = copt->next)
+ m_option_free(copt->opt, copt->data);
+ return 0;
+}
+
+struct m_config *m_config_new(void *talloc_parent, size_t size,
+ const void *defaults,
+ const struct m_option *options,
+ const char *suboptinit)
+{
+ struct m_config *config = talloc(talloc_parent, struct m_config);
+ talloc_set_destructor(config, config_destroy);
+ *config = (struct m_config) {
+ .optstruct_size = size,
+ .optstruct_defaults = defaults,
+ .options = options,
+ .suboptinit = suboptinit,
+ };
+ if (size) { // size==0 means a dummy object is created
+ config->optstruct = talloc_zero_size(config, size);
+ if (defaults)
+ memcpy(config->optstruct, defaults, size);
+ if (options)
+ add_options(config, NULL, options);
+ }
+ if (suboptinit) {
+ bstr s = bstr0(suboptinit);
+ int r = m_obj_parse_sub_config(bstr0("internal"), bstr0("-"), &s,
+ config, 0, NULL);
+ if (r < 0 || s.len > 0)
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Internal error: preset broken\n");
+ }
+ return config;
+}
+
+struct m_config *m_config_from_obj_desc(void *talloc_parent,
+ struct m_obj_desc *desc)
+{
+ return m_config_new(talloc_parent, desc->priv_size, desc->priv_defaults,
+ desc->options, desc->init_options);
+}
+
+int m_config_set_obj_params(struct m_config *conf, char **args)
+{
+ for (int n = 0; args && args[n * 2 + 0]; n++) {
+ int r = m_config_set_option(conf, bstr0(args[n * 2 + 0]),
+ bstr0(args[n * 2 + 1]));
+ if (r < 0)
+ return r;
+ }
+ return 0;
+}
+
+int m_config_initialize_obj(struct m_config *config, struct m_obj_desc *desc,
+ void **ppriv, char ***pargs)
+{
+ if (desc->priv_size) {
+ int r = m_config_set_obj_params(config, *pargs);
+ if (r < 0)
+ return r;
+ *ppriv = config->optstruct;
+ *pargs = NULL;
+ } else if (*pargs && !strcmp((*pargs)[0], "_oldargs_")) {
+ // Handle things which still use the old subopt parser
+ *pargs = (char **)((*pargs)[1]);
+ } else {
+ *pargs = NULL;
+ }
+ return 0;
+}
+
+static void ensure_backup(struct m_config *config, struct m_config_option *co)
+{
+ if (co->opt->type->flags & M_OPT_TYPE_HAS_CHILD)
+ return;
+ if (co->opt->flags & M_OPT_GLOBAL)
+ return;
+ if (!co->data)
+ return;
+ for (struct m_opt_backup *cur = config->backup_opts; cur; cur = cur->next) {
+ if (cur->co->data == co->data) // comparing data ptr catches aliases
+ return;
+ }
+ struct m_opt_backup *bc = talloc_ptrtype(NULL, bc);
+ *bc = (struct m_opt_backup) {
+ .co = co,
+ .backup = talloc_zero_size(bc, co->opt->type->size),
+ };
+ m_option_copy(co->opt, bc->backup, co->data);
+ bc->next = config->backup_opts;
+ config->backup_opts = bc;
+}
+
+void m_config_restore_backups(struct m_config *config)
+{
+ while (config->backup_opts) {
+ struct m_opt_backup *bc = config->backup_opts;
+ config->backup_opts = bc->next;
+
+ m_option_copy(bc->co->opt, bc->co->data, bc->backup);
+ m_option_free(bc->co->opt, bc->backup);
+ talloc_free(bc);
+ }
+}
+
+void m_config_backup_opt(struct m_config *config, const char *opt)
+{
+ struct m_config_option *co = m_config_get_co(config, bstr0(opt));
+ if (co) {
+ ensure_backup(config, co);
+ } else {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Option %s not found.\n", opt);
+ }
+}
+
+void m_config_backup_all_opts(struct m_config *config)
+{
+ for (struct m_config_option *co = config->opts; co; co = co->next)
+ ensure_backup(config, co);
+}
+
+// Given an option --opt, add --no-opt (if applicable).
+static void add_negation_option(struct m_config *config,
+ struct m_config_option *parent,
+ const struct m_option *opt)
+{
+ int value;
+ if (opt->type == CONF_TYPE_FLAG) {
+ value = opt->min;
+ } else if (opt->type == CONF_TYPE_CHOICE) {
+ // Find out whether there's a "no" choice.
+ // m_option_parse() should be used for this, but it prints
+ // unsilenceable error messages.
+ struct m_opt_choice_alternatives *alt = opt->priv;
+ for ( ; alt->name; alt++) {
+ if (strcmp(alt->name, "no") == 0)
+ break;
+ }
+ if (!alt->name)
+ return;
+ value = alt->value;
+ } else {
+ return;
+ }
+ struct m_option *no_opt = talloc_ptrtype(config, no_opt);
+ *no_opt = (struct m_option) {
+ .name = talloc_asprintf(no_opt, "no-%s", opt->name),
+ .type = CONF_TYPE_STORE,
+ .flags = opt->flags & (M_OPT_NOCFG | M_OPT_GLOBAL | M_OPT_PRE_PARSE),
+ .new = opt->new,
+ .p = opt->p,
+ .offset = opt->offset,
+ .max = value,
+ };
+ struct m_config_option *co = m_config_add_option(config, "", parent, no_opt);
+ co->is_generated = true;
+ // Consider a parent option "--sub" and a subopt "opt". Then the above
+ // call will add "no-opt". Add "--no-sub-opt" too. (This former call will
+ // also generate "--sub-no-opt", which is not really needed or wanted, but
+ // is a consequence of supporting "--sub=...:no-opt".)
+ if (parent && parent->name && strlen(parent->name)) {
+ no_opt = talloc_memdup(config, no_opt, sizeof(*no_opt));
+ no_opt->name = opt->name;
+ co = m_config_add_option(config, "no-", parent, no_opt);
+ co->is_generated = true;
+ }
+}
+
+static void add_options(struct m_config *config,
+ struct m_config_option *parent,
+ const struct m_option *defs)
+{
+ for (int i = 0; defs[i].name; i++)
+ m_config_add_option(config, "", parent, defs + i);
+}
+
+// Sub-config that adds all its children to the parent.
+static bool is_merge_opt(const struct m_option *opt)
+{
+ return (opt->type->flags & M_OPT_TYPE_HAS_CHILD) && strlen(opt->name) == 0;
+}
+
+static struct m_config_option *m_config_add_option(struct m_config *config,
+ const char *prefix,
+ struct m_config_option *parent,
+ const struct m_option *arg)
+{
+ assert(config != NULL);
+ assert(arg != NULL);
+
+ // Allocate a new entry for this option
+ struct m_config_option *co = talloc_zero(config, struct m_config_option);
+ co->opt = arg;
+
+ void *optstruct = config->optstruct;
+ if (parent && (parent->opt->type->flags & M_OPT_TYPE_USE_SUBSTRUCT))
+ optstruct = substruct_read_ptr(parent->data);
+ co->data = arg->new ? (char *)optstruct + arg->offset : arg->p;
+
+ if (parent) {
+ // Merge case: pretend it has no parent (note that we still must follow
+ // the "real" parent for accessing struct fields)
+ co->parent = is_merge_opt(parent->opt) ? parent->parent : parent;
+ }
+
+ // Fill in the full name
+ if (co->parent) {
+ co->name = talloc_asprintf(co, "%s-%s", co->parent->name, arg->name);
+ } else {
+ co->name = (char *)arg->name;
+ }
+ co->name = talloc_asprintf(co, "%s%s", prefix, co->name);
+
+ // Option with children -> add them
+ if (arg->type->flags & M_OPT_TYPE_HAS_CHILD) {
+ if (arg->type->flags & M_OPT_TYPE_USE_SUBSTRUCT) {
+ const struct m_sub_options *subopts = arg->priv;
+ if (!substruct_read_ptr(co->data)) {
+ void *subdata = m_config_alloc_struct(config, subopts);
+ substruct_write_ptr(co->data, subdata);
+ }
+ add_options(config, co, subopts->opts);
+ } else {
+ const struct m_option *sub = arg->p;
+ add_options(config, co, sub);
+ }
+ } else {
+ // Initialize options
+ if (co->data) {
+ if (arg->defval) {
+ // Target data in optstruct is supposed to be cleared (consider
+ // m_option freeing previously set dynamic data).
+ m_option_copy(arg, co->data, arg->defval);
+ } else if (arg->type->flags & M_OPT_TYPE_DYNAMIC) {
+ // Initialize dynamically managed fields from static data (like
+ // string options): copy the option into temporary memory,
+ // clear the original option (to stop m_option from freeing the
+ // static data), copy it back.
+ // This would leak memory when done on aliased options.
+ bool aliased = false;
+ for (struct m_config_option *i = config->opts; i; i = i->next) {
+ if (co->data == i->data) {
+ aliased = true;
+ break;
+ }
+ }
+ if (!aliased) {
+ union m_option_value temp = {0};
+ m_option_copy(arg, &temp, co->data);
+ memset(co->data, 0, arg->type->size);
+ m_option_copy(arg, co->data, &temp);
+ m_option_free(arg, &temp);
+ }
+ }
+ }
+ }
+
+ // pretend that merge options don't exist (only their children matter)
+ if (!is_merge_opt(co->opt)) {
+ struct m_config_option **last = &config->opts;
+ while (*last)
+ last = &(*last)->next;
+ *last = co;
+ }
+
+ add_negation_option(config, parent, arg);
+
+ return co;
+}
+
+struct m_config_option *m_config_get_co(const struct m_config *config,
+ struct bstr name)
+{
+ struct m_config_option *co;
+
+ for (co = config->opts; co; co = co->next) {
+ struct bstr coname = bstr0(co->name);
+ if ((co->opt->type->flags & M_OPT_TYPE_ALLOW_WILDCARD)
+ && bstr_endswith0(coname, "*")) {
+ coname.len--;
+ if (bstrcmp(bstr_splice(name, 0, coname.len), coname) == 0)
+ return co;
+ } else if (bstrcmp(coname, name) == 0)
+ return co;
+ }
+ return NULL;
+}
+
+const char *m_config_get_positional_option(const struct m_config *config, int n)
+{
+ int pos = 0;
+ for (struct m_config_option *co = config->opts; co; co = co->next) {
+ if (!co->is_generated) {
+ if (pos == n)
+ return co->name;
+ pos++;
+ }
+ }
+ return NULL;
+}
+
+static int parse_subopts(struct m_config *config, char *name, char *prefix,
+ struct bstr param, int flags);
+
+static int m_config_parse_option(struct m_config *config, struct bstr name,
+ struct bstr param, int flags)
+{
+ assert(config != NULL);
+ assert(name.len != 0);
+ bool set = !(flags & M_SETOPT_CHECK_ONLY);
+
+ struct m_config_option *co = m_config_get_co(config, name);
+ if (!co)
+ return M_OPT_UNKNOWN;
+
+ // This is the only mandatory function
+ assert(co->opt->type->parse);
+
+ if ((flags & M_SETOPT_PRE_PARSE_ONLY) && !(co->opt->flags & M_OPT_PRE_PARSE))
+ return 0;
+
+ // Check if this option isn't forbidden in the current mode
+ if ((flags & M_SETOPT_FROM_CONFIG_FILE) && (co->opt->flags & M_OPT_NOCFG)) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option can't be used in a config file.\n",
+ BSTR_P(name));
+ return M_OPT_INVALID;
+ }
+ if (flags & M_SETOPT_BACKUP) {
+ if (co->opt->flags & M_OPT_GLOBAL) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option is global and can't be set per-file.\n",
+ BSTR_P(name));
+ return M_OPT_INVALID;
+ }
+ if (set)
+ ensure_backup(config, co);
+ }
+
+ if (config->includefunc && bstr_equals0(name, "include"))
+ return parse_include(config, param, set, flags);
+ if (config->use_profiles && bstr_equals0(name, "profile"))
+ return parse_profile(config, co->opt, name, param, set, flags);
+ if (config->use_profiles && bstr_equals0(name, "show-profile"))
+ return show_profile(config, param);
+ if (bstr_equals0(name, "list-options"))
+ return list_options(config);
+
+ // Option with children are a bit different to parse
+ if (co->opt->type->flags & M_OPT_TYPE_HAS_CHILD) {
+ char prefix[110];
+ assert(strlen(co->name) < 100);
+ sprintf(prefix, "%s-", co->name);
+ return parse_subopts(config, co->name, prefix, param, flags);
+ }
+
+ return m_option_parse(co->opt, name, param, set ? co->data : NULL);
+}
+
+static int parse_subopts(struct m_config *config, char *name, char *prefix,
+ struct bstr param, int flags)
+{
+ char **lst = NULL;
+ // Split the argument into child options
+ int r = m_option_type_subconfig.parse(NULL, bstr0(""), param, &lst);
+ if (r < 0)
+ return r;
+ // Parse the child options
+ for (int i = 0; lst && lst[2 * i]; i++) {
+ // Build the full name
+ char n[110];
+ if (snprintf(n, 110, "%s%s", prefix, lst[2 * i]) > 100)
+ abort();
+ r = m_config_parse_option(config,bstr0(n), bstr0(lst[2 * i + 1]), flags);
+ if (r < 0) {
+ if (r > M_OPT_EXIT) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
+ "Error parsing suboption %s/%s (%s)\n",
+ name, lst[2 * i], m_option_strerror(r));
+ r = M_OPT_INVALID;
+ }
+ break;
+ }
+ }
+ talloc_free(lst);
+ return r;
+}
+
+int m_config_parse_suboptions(struct m_config *config, char *name,
+ char *subopts)
+{
+ if (!subopts || !*subopts)
+ return 0;
+ int r = parse_subopts(config, name, "", bstr0(subopts), 0);
+ if (r < 0 && r > M_OPT_EXIT) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Error parsing suboption %s (%s)\n",
+ name, m_option_strerror(r));
+ r = M_OPT_INVALID;
+ }
+ return r;
+}
+
+int m_config_set_option_ext(struct m_config *config, struct bstr name,
+ struct bstr param, int flags)
+{
+ int r = m_config_parse_option(config, name, param, flags);
+ if (r < 0 && r > M_OPT_EXIT) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR, "Error parsing option %.*s (%s)\n",
+ BSTR_P(name), m_option_strerror(r));
+ r = M_OPT_INVALID;
+ }
+ return r;
+}
+
+int m_config_set_option(struct m_config *config, struct bstr name,
+ struct bstr param)
+{
+ return m_config_set_option_ext(config, name, param, 0);
+}
+
+const struct m_option *m_config_get_option(const struct m_config *config,
+ struct bstr name)
+{
+ struct m_config_option *co;
+
+ assert(config != NULL);
+
+ co = m_config_get_co(config, name);
+ if (co)
+ return co->opt;
+ else
+ return NULL;
+}
+
+int m_config_option_requires_param(struct m_config *config, bstr name)
+{
+ const struct m_option *opt = m_config_get_option(config, name);
+ if (opt) {
+ if (bstr_endswith0(name, "-clr"))
+ return 0;
+ return m_option_required_params(opt);
+ }
+ return M_OPT_UNKNOWN;
+}
+
+static struct m_config *get_defaults(const struct m_config *config)
+{
+ return m_config_new(NULL, config->optstruct_size,
+ config->optstruct_defaults, config->options,
+ config->suboptinit);
+}
+
+static char *get_option_value_string(const struct m_config *config,
+ const char *name)
+{
+ struct m_config_option *co = m_config_get_co(config, bstr0(name));
+ if (!co || !co->data)
+ return NULL;
+ return m_option_print(co->opt, co->data);
+}
+
+void m_config_print_option_list(const struct m_config *config)
+{
+ char min[50], max[50];
+ struct m_config_option *co;
+ int count = 0;
+
+ if (!config->opts)
+ return;
+
+ struct m_config *defaults = get_defaults(config);
+
+ mp_tmsg(MSGT_CFGPARSER, MSGL_INFO, "Options:\n\n");
+ for (co = config->opts; co; co = co->next) {
+ const struct m_option *opt = co->opt;
+ if (opt->type->flags & M_OPT_TYPE_HAS_CHILD)
+ continue;
+ if (co->is_generated)
+ continue;
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %-30.30s", co->name);
+ if (opt->type == &m_option_type_choice) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " Choices:");
+ struct m_opt_choice_alternatives *alt = opt->priv;
+ for (int n = 0; alt[n].name; n++)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s", alt[n].name);
+ if (opt->flags & (M_OPT_MIN | M_OPT_MAX))
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " (or an integer)");
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s", co->opt->type->name);
+ }
+ if (opt->flags & (M_OPT_MIN | M_OPT_MAX)) {
+ snprintf(min, sizeof(min), "any");
+ snprintf(max, sizeof(max), "any");
+ if (opt->flags & M_OPT_MIN)
+ snprintf(min, sizeof(min), "%.14g", opt->min);
+ if (opt->flags & M_OPT_MAX)
+ snprintf(max, sizeof(max), "%.14g", opt->max);
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " (%s to %s)", min, max);
+ }
+ char *def = get_option_value_string(defaults, co->name);
+ if (def) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " (default: %s)", def);
+ talloc_free(def);
+ }
+ if (opt->flags & CONF_GLOBAL)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " [global]");
+ if (opt->flags & CONF_NOCFG)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " [nocfg]");
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ count++;
+ }
+ mp_tmsg(MSGT_CFGPARSER, MSGL_INFO, "\nTotal: %d options\n", count);
+
+ talloc_free(defaults);
+}
+
+struct m_profile *m_config_get_profile(const struct m_config *config, bstr name)
+{
+ for (struct m_profile *p = config->profiles; p; p = p->next) {
+ if (bstr_equals0(name, p->name))
+ return p;
+ }
+ return NULL;
+}
+
+struct m_profile *m_config_get_profile0(const struct m_config *config,
+ char *name)
+{
+ return m_config_get_profile(config, bstr0(name));
+}
+
+struct m_profile *m_config_add_profile(struct m_config *config, char *name)
+{
+ struct m_profile *p = m_config_get_profile0(config, name);
+ if (p)
+ return p;
+ p = talloc_zero(config, struct m_profile);
+ p->name = talloc_strdup(p, name);
+ p->next = config->profiles;
+ config->profiles = p;
+ return p;
+}
+
+void m_profile_set_desc(struct m_profile *p, char *desc)
+{
+ talloc_free(p->desc);
+ p->desc = talloc_strdup(p, desc);
+}
+
+int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
+ bstr name, bstr val)
+{
+ int i = m_config_set_option_ext(config, name, val,
+ M_SETOPT_CHECK_ONLY |
+ M_SETOPT_FROM_CONFIG_FILE);
+ if (i < 0)
+ return i;
+ p->opts = talloc_realloc(p, p->opts, char *, 2 * (p->num_opts + 2));
+ p->opts[p->num_opts * 2] = bstrdup0(p, name);
+ p->opts[p->num_opts * 2 + 1] = bstrdup0(p, val);
+ p->num_opts++;
+ p->opts[p->num_opts * 2] = p->opts[p->num_opts * 2 + 1] = NULL;
+ return 1;
+}
+
+void m_config_set_profile(struct m_config *config, struct m_profile *p,
+ int flags)
+{
+ if (config->profile_depth > MAX_PROFILE_DEPTH) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_WARN,
+ "WARNING: Profile inclusion too deep.\n");
+ return;
+ }
+ config->profile_depth++;
+ for (int i = 0; i < p->num_opts; i++) {
+ m_config_set_option_ext(config,
+ bstr0(p->opts[2 * i]),
+ bstr0(p->opts[2 * i + 1]),
+ flags | M_SETOPT_FROM_CONFIG_FILE);
+ }
+ config->profile_depth--;
+}
+
+void *m_config_alloc_struct(void *talloc_parent,
+ const struct m_sub_options *subopts)
+{
+ void *substruct = talloc_zero_size(talloc_parent, subopts->size);
+ if (subopts->defaults)
+ memcpy(substruct, subopts->defaults, subopts->size);
+ return substruct;
+}
diff --git a/mpvcore/m_config.h b/mpvcore/m_config.h
new file mode 100644
index 0000000000..c2f88dfe65
--- /dev/null
+++ b/mpvcore/m_config.h
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_M_CONFIG_H
+#define MPLAYER_M_CONFIG_H
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "core/bstr.h"
+
+// m_config provides an API to manipulate the config variables in MPlayer.
+// It makes use of the Options API to provide a context stack that
+// allows saving and later restoring the state of all variables.
+
+typedef struct m_profile m_profile_t;
+struct m_option;
+struct m_option_type;
+struct m_sub_options;
+struct m_obj_desc;
+
+// Config option
+struct m_config_option {
+ struct m_config_option *next;
+ bool is_generated : 1;
+ // Full name (ie option-subopt).
+ char *name;
+ // Option description.
+ const struct m_option *opt;
+ // Raw value of the option.
+ void *data;
+ // If this is a suboption, the option that contains this option.
+ struct m_config_option *parent;
+};
+
+// Config object
+/** \ingroup Config */
+typedef struct m_config {
+ // Registered options.
+ struct m_config_option *opts; // all options, even suboptions
+
+ // List of defined profiles.
+ struct m_profile *profiles;
+ // Depth when recursively including profiles.
+ int profile_depth;
+
+ struct m_opt_backup *backup_opts;
+
+ bool use_profiles;
+ int (*includefunc)(struct m_config *conf, char *filename, int flags);
+
+ const void *optstruct_defaults;
+ size_t optstruct_size;
+ const struct m_option *options; // top-level options
+ const char *suboptinit;
+
+ void *optstruct; // struct mpopts or other
+} m_config_t;
+
+// Create a new config object.
+// talloc_parent: talloc parent context for the m_config allocation
+// size: size of the optstruct (where option values are stored)
+// defaults: if not NULL, points to a struct of same type as optstruct, which
+// contains default values for all options
+// options: list of options. Each option defines a member of the optstruct
+// and a corresponding option switch or sub-option field.
+// suboptinit: if not NULL, initialize the suboption string (used for presets)
+// Note that the m_config object will keep pointers to defaults and options.
+struct m_config *m_config_new(void *talloc_parent, size_t size,
+ const void *defaults,
+ const struct m_option *options,
+ const char *suboptinit);
+
+struct m_config *m_config_from_obj_desc(void *talloc_parent,
+ struct m_obj_desc *desc);
+
+int m_config_set_obj_params(struct m_config *conf, char **args);
+
+// Initialize an object (VO/VF/...) in one go, including legacy handling.
+// This is pretty specialized, and is just for convenience.
+int m_config_initialize_obj(struct m_config *config, struct m_obj_desc *desc,
+ void **ppriv, char ***pargs);
+
+// Make sure the option is backed up. If it's already backed up, do nothing.
+// All backed up options can be restored with m_config_restore_backups().
+void m_config_backup_opt(struct m_config *config, const char *opt);
+
+// Call m_config_backup_opt() on all options.
+void m_config_backup_all_opts(struct m_config *config);
+
+// Restore all options backed up with m_config_backup_opt(), and delete the
+// backups afterwards.
+void m_config_restore_backups(struct m_config *config);
+
+enum {
+ M_SETOPT_PRE_PARSE_ONLY = 1, // Silently ignore non-M_OPT_PRE_PARSE opt.
+ M_SETOPT_CHECK_ONLY = 2, // Don't set, just check name/value
+ M_SETOPT_FROM_CONFIG_FILE = 4, // Reject M_OPT_NOCFG opt. (print error)
+ M_SETOPT_BACKUP = 8, // Call m_config_backup_opt() before
+};
+
+// Set the named option to the given string.
+// flags: combination of M_SETOPT_* flags (0 for normal operation)
+// Returns >= 0 on success, otherwise see OptionParserReturn.
+int m_config_set_option_ext(struct m_config *config, struct bstr name,
+ struct bstr param, int flags);
+
+/* Set an option. (Like: m_config_set_option_ext(config, name, param, 0))
+ * \param config The config object.
+ * \param name The option's name.
+ * \param param The value of the option, can be NULL.
+ * \return See \ref OptionParserReturn.
+ */
+int m_config_set_option(struct m_config *config, struct bstr name,
+ struct bstr param);
+
+static inline int m_config_set_option0(struct m_config *config,
+ const char *name, const char *param)
+{
+ return m_config_set_option(config, bstr0(name), bstr0(param));
+}
+
+int m_config_parse_suboptions(struct m_config *config, char *name,
+ char *subopts);
+
+
+/* Get the option matching the given name.
+ * \param config The config object.
+ * \param name The option's name.
+ */
+const struct m_option *m_config_get_option(const struct m_config *config,
+ struct bstr name);
+
+struct m_config_option *m_config_get_co(const struct m_config *config,
+ struct bstr name);
+
+// Return the n-th option by position. n==0 is the first option. If there are
+// less than (n + 1) options, return NULL.
+const char *m_config_get_positional_option(const struct m_config *config, int n);
+
+// Return a hint to the option parser whether a parameter is/may be required.
+// The option may still accept empty/non-empty parameters independent from
+// this, and this function is useful only for handling ambiguous options like
+// flags (e.g. "--a" is ok, "--a=yes" is also ok).
+// Returns: error code (<0), or number of expected params (0, 1)
+int m_config_option_requires_param(struct m_config *config, bstr name);
+
+/* Print a list of all registered options.
+ * \param config The config object.
+ */
+void m_config_print_option_list(const struct m_config *config);
+
+
+/* Find the profile with the given name.
+ * \param config The config object.
+ * \param arg The profile's name.
+ * \return The profile object or NULL.
+ */
+struct m_profile *m_config_get_profile0(const struct m_config *config,
+ char *name);
+struct m_profile *m_config_get_profile(const struct m_config *config, bstr name);
+
+/* Get the profile with the given name, creating it if necessary.
+ * \param config The config object.
+ * \param arg The profile's name.
+ * \return The profile object.
+ */
+struct m_profile *m_config_add_profile(struct m_config *config, char *name);
+
+/* Set the description of a profile.
+ * Used by the config file parser when defining a profile.
+ *
+ * \param p The profile object.
+ * \param arg The profile's name.
+ */
+void m_profile_set_desc(struct m_profile *p, char *desc);
+
+/* Add an option to a profile.
+ * Used by the config file parser when defining a profile.
+ *
+ * \param config The config object.
+ * \param p The profile object.
+ * \param name The option's name.
+ * \param val The option's value.
+ */
+int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
+ bstr name, bstr val);
+
+/* Enables profile usage
+ * Used by the config file parser when loading a profile.
+ *
+ * \param config The config object.
+ * \param p The profile object.
+ * \param flags M_SETOPT_* bits
+ */
+void m_config_set_profile(struct m_config *config, struct m_profile *p,
+ int flags);
+
+void *m_config_alloc_struct(void *talloc_parent,
+ const struct m_sub_options *subopts);
+
+#endif /* MPLAYER_M_CONFIG_H */
diff --git a/mpvcore/m_option.c b/mpvcore/m_option.c
new file mode 100644
index 0000000000..41916befeb
--- /dev/null
+++ b/mpvcore/m_option.c
@@ -0,0 +1,2407 @@
+/*
+ * 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.
+ */
+
+/// \file
+/// \ingroup Options
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <libavutil/common.h>
+#include <libavutil/avstring.h>
+
+#include "talloc.h"
+#include "core/mp_common.h"
+#include "core/m_option.h"
+#include "core/m_config.h"
+#include "core/mp_msg.h"
+
+char *m_option_strerror(int code)
+{
+ switch (code) {
+ case M_OPT_UNKNOWN:
+ return mp_gtext("option not found");
+ case M_OPT_MISSING_PARAM:
+ return mp_gtext("option requires parameter");
+ case M_OPT_INVALID:
+ return mp_gtext("option parameter could not be parsed");
+ case M_OPT_OUT_OF_RANGE:
+ return mp_gtext("parameter is outside values allowed for option");
+ case M_OPT_DISALLOW_PARAM:
+ return mp_gtext("option doesn't take a parameter");
+ case M_OPT_PARSER_ERR:
+ default:
+ return mp_gtext("parser error");
+ }
+}
+
+int m_option_required_params(const m_option_t *opt)
+{
+ if (((opt->flags & M_OPT_OPTIONAL_PARAM) ||
+ (opt->type->flags & M_OPT_TYPE_OPTIONAL_PARAM)))
+ return 0;
+ return 1;
+}
+
+static const struct m_option *m_option_list_findb(const struct m_option *list,
+ struct bstr name)
+{
+ for (int i = 0; list[i].name; i++) {
+ struct bstr lname = bstr0(list[i].name);
+ if ((list[i].type->flags & M_OPT_TYPE_ALLOW_WILDCARD)
+ && bstr_endswith0(lname, "*")) {
+ lname.len--;
+ if (bstrcmp(bstr_splice(name, 0, lname.len), lname) == 0)
+ return &list[i];
+ } else if (bstrcmp(lname, name) == 0)
+ return &list[i];
+ }
+ return NULL;
+}
+
+const m_option_t *m_option_list_find(const m_option_t *list, const char *name)
+{
+ return m_option_list_findb(list, bstr0(name));
+}
+
+// Default function that just does a memcpy
+
+static void copy_opt(const m_option_t *opt, void *dst, const void *src)
+{
+ if (dst && src)
+ memcpy(dst, src, opt->type->size);
+}
+
+// Flag
+
+#define VAL(x) (*(int *)(x))
+
+static int clamp_flag(const m_option_t *opt, void *val)
+{
+ if (VAL(val) == opt->min || VAL(val) == opt->max)
+ return 0;
+ VAL(val) = opt->min;
+ return M_OPT_OUT_OF_RANGE;
+}
+
+static int parse_flag(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len) {
+ if (!bstrcmp0(param, "yes")) {
+ if (dst)
+ VAL(dst) = opt->max;
+ return 1;
+ }
+ if (!bstrcmp0(param, "no")) {
+ if (dst)
+ VAL(dst) = opt->min;
+ return 1;
+ }
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid parameter for %.*s flag: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+ } else {
+ if (dst)
+ VAL(dst) = opt->max;
+ return 0;
+ }
+}
+
+static char *print_flag(const m_option_t *opt, const void *val)
+{
+ if (VAL(val) == opt->min)
+ return talloc_strdup(NULL, "no");
+ else
+ return talloc_strdup(NULL, "yes");
+}
+
+static void add_flag(const m_option_t *opt, void *val, double add, bool wrap)
+{
+ if (fabs(add) < 0.5)
+ return;
+ bool state = VAL(val) != opt->min;
+ state = wrap ? !state : add > 0;
+ VAL(val) = state ? opt->max : opt->min;
+}
+
+const m_option_type_t m_option_type_flag = {
+ // need yes or no in config files
+ .name = "Flag",
+ .size = sizeof(int),
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_flag,
+ .print = print_flag,
+ .copy = copy_opt,
+ .add = add_flag,
+ .clamp = clamp_flag,
+};
+
+// Single-value, write-only flag
+
+static int parse_store(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0 || bstrcmp0(param, "yes") == 0) {
+ if (dst)
+ VAL(dst) = opt->max;
+ return 0;
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid parameter for %.*s flag: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_DISALLOW_PARAM;
+ }
+}
+
+const m_option_type_t m_option_type_store = {
+ // can only be activated
+ .name = "Flag",
+ .size = sizeof(int),
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_store,
+};
+
+// Same for float types
+
+#undef VAL
+#define VAL(x) (*(float *)(x))
+
+static int parse_store_float(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0 || bstrcmp0(param, "yes") == 0) {
+ if (dst)
+ VAL(dst) = opt->max;
+ return 0;
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid parameter for %.*s flag: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_DISALLOW_PARAM;
+ }
+}
+
+const m_option_type_t m_option_type_float_store = {
+ // can only be activated
+ .name = "Flag",
+ .size = sizeof(float),
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_store_float,
+};
+
+// Integer
+
+#undef VAL
+
+static int clamp_longlong(const m_option_t *opt, void *val)
+{
+ long long v = *(long long *)val;
+ int r = 0;
+ if ((opt->flags & M_OPT_MAX) && (v > opt->max)) {
+ v = opt->max;
+ r = M_OPT_OUT_OF_RANGE;
+ }
+ if ((opt->flags & M_OPT_MIN) && (v < opt->min)) {
+ v = opt->min;
+ r = M_OPT_OUT_OF_RANGE;
+ }
+ *(long long *)val = v;
+ return r;
+}
+
+static int parse_longlong(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ struct bstr rest;
+ long long tmp_int = bstrtoll(param, &rest, 10);
+ if (rest.len)
+ tmp_int = bstrtoll(param, &rest, 0);
+ if (rest.len) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option must be an integer: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if ((opt->flags & M_OPT_MIN) && (tmp_int < opt->min)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option must be >= %d: %.*s\n",
+ BSTR_P(name), (int) opt->min, BSTR_P(param));
+ return M_OPT_OUT_OF_RANGE;
+ }
+
+ if ((opt->flags & M_OPT_MAX) && (tmp_int > opt->max)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option must be <= %d: %.*s\n",
+ BSTR_P(name), (int) opt->max, BSTR_P(param));
+ return M_OPT_OUT_OF_RANGE;
+ }
+
+ if (dst)
+ *(long long *)dst = tmp_int;
+
+ return 1;
+}
+
+static int clamp_int(const m_option_t *opt, void *val)
+{
+ long long tmp = *(int *)val;
+ int r = clamp_longlong(opt, &tmp);
+ *(int *)val = tmp;
+ return r;
+}
+
+static int clamp_int64(const m_option_t *opt, void *val)
+{
+ long long tmp = *(int64_t *)val;
+ int r = clamp_longlong(opt, &tmp);
+ *(int64_t *)val = tmp;
+ return r;
+}
+
+static int parse_int(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ long long tmp;
+ int r = parse_longlong(opt, name, param, &tmp);
+ if (r >= 0 && dst)
+ *(int *)dst = tmp;
+ return r;
+}
+
+static int parse_int64(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ long long tmp;
+ int r = parse_longlong(opt, name, param, &tmp);
+ if (r >= 0 && dst)
+ *(int64_t *)dst = tmp;
+ return r;
+}
+
+static char *print_int(const m_option_t *opt, const void *val)
+{
+ if (opt->type->size == sizeof(int64_t))
+ return talloc_asprintf(NULL, "%"PRId64, *(const int64_t *)val);
+ return talloc_asprintf(NULL, "%d", *(const int *)val);
+}
+
+static void add_int64(const m_option_t *opt, void *val, double add, bool wrap)
+{
+ int64_t v = *(int64_t *)val;
+
+ v = v + add;
+
+ bool is64 = opt->type->size == sizeof(int64_t);
+ int64_t nmin = is64 ? INT64_MIN : INT_MIN;
+ int64_t nmax = is64 ? INT64_MAX : INT_MAX;
+
+ int64_t min = (opt->flags & M_OPT_MIN) ? opt->min : nmin;
+ int64_t max = (opt->flags & M_OPT_MAX) ? opt->max : nmax;
+
+ if (v < min)
+ v = wrap ? max : min;
+ if (v > max)
+ v = wrap ? min : max;
+
+ *(int64_t *)val = v;
+}
+
+static void add_int(const m_option_t *opt, void *val, double add, bool wrap)
+{
+ int64_t tmp = *(int *)val;
+ add_int64(opt, &tmp, add, wrap);
+ *(int *)val = tmp;
+}
+
+const m_option_type_t m_option_type_int = {
+ .name = "Integer",
+ .size = sizeof(int),
+ .parse = parse_int,
+ .print = print_int,
+ .copy = copy_opt,
+ .add = add_int,
+ .clamp = clamp_int,
+};
+
+const m_option_type_t m_option_type_int64 = {
+ .name = "Integer64",
+ .size = sizeof(int64_t),
+ .parse = parse_int64,
+ .print = print_int,
+ .copy = copy_opt,
+ .add = add_int64,
+ .clamp = clamp_int64,
+};
+
+static int parse_intpair(const struct m_option *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ struct bstr s = param;
+ int end = -1;
+ int start = bstrtoll(s, &s, 10);
+ if (s.len == param.len)
+ goto bad;
+ if (s.len > 0) {
+ if (!bstr_startswith0(s, "-"))
+ goto bad;
+ s = bstr_cut(s, 1);
+ }
+ if (s.len > 0)
+ end = bstrtoll(s, &s, 10);
+ if (s.len > 0)
+ goto bad;
+
+ if (dst) {
+ int *p = dst;
+ p[0] = start;
+ p[1] = end;
+ }
+
+ return 1;
+
+bad:
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Invalid integer range "
+ "specification for option %.*s: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+}
+
+const struct m_option_type m_option_type_intpair = {
+ .name = "Int[-Int]",
+ .size = sizeof(int[2]),
+ .parse = parse_intpair,
+ .copy = copy_opt,
+};
+
+static int clamp_choice(const m_option_t *opt, void *val)
+{
+ int v = *(int *)val;
+ if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ if (v >= opt->min && v <= opt->max)
+ return 0;
+ }
+ ;
+ for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++) {
+ if (alt->value == v)
+ return 0;
+ }
+ return M_OPT_INVALID;
+}
+
+static int parse_choice(const struct m_option *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ struct m_opt_choice_alternatives *alt = opt->priv;
+ for ( ; alt->name; alt++)
+ if (!bstrcmp0(param, alt->name))
+ break;
+ if (!alt->name) {
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+ if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ long long val;
+ if (parse_longlong(opt, name, param, &val) == 1) {
+ if (dst)
+ *(int *)dst = val;
+ return 1;
+ }
+ }
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid value for option %.*s: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Valid values are:");
+ for (alt = opt->priv; alt->name; alt++)
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, " %s", alt->name);
+ if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX))
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, " %g-%g", opt->min, opt->max);
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "\n");
+ return M_OPT_INVALID;
+ }
+ if (dst)
+ *(int *)dst = alt->value;
+
+ return 1;
+}
+
+static char *print_choice(const m_option_t *opt, const void *val)
+{
+ int v = *(int *)val;
+ struct m_opt_choice_alternatives *alt;
+ for (alt = opt->priv; alt->name; alt++)
+ if (alt->value == v)
+ return talloc_strdup(NULL, alt->name);
+ if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ if (v >= opt->min && v <= opt->max)
+ return talloc_asprintf(NULL, "%d", v);
+ }
+ abort();
+}
+
+static void choice_get_min_max(const struct m_option *opt, int *min, int *max)
+{
+ assert(opt->type == &m_option_type_choice);
+ *min = INT_MAX;
+ *max = INT_MIN;
+ for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++) {
+ *min = FFMIN(*min, alt->value);
+ *max = FFMAX(*max, alt->value);
+ }
+ if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ *min = FFMIN(*min, opt->min);
+ *max = FFMAX(*max, opt->max);
+ }
+}
+
+static void check_choice(int dir, int val, bool *found, int *best, int choice)
+{
+ if ((dir == -1 && (!(*found) || choice > (*best)) && choice < val) ||
+ (dir == +1 && (!(*found) || choice < (*best)) && choice > val))
+ {
+ *found = true;
+ *best = choice;
+ }
+}
+
+static void add_choice(const m_option_t *opt, void *val, double add, bool wrap)
+{
+ assert(opt->type == &m_option_type_choice);
+ int dir = add > 0 ? +1 : -1;
+ bool found = false;
+ int ival = *(int *)val;
+ int best = 0; // init. value unused
+
+ if (fabs(add) < 0.5)
+ return;
+
+ if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
+ int newval = ival + add;
+ if (ival >= opt->min && ival <= opt->max &&
+ newval >= opt->min && newval <= opt->max)
+ {
+ found = true;
+ best = newval;
+ } else {
+ check_choice(dir, ival, &found, &best, opt->min);
+ check_choice(dir, ival, &found, &best, opt->max);
+ }
+ }
+
+ for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++)
+ check_choice(dir, ival, &found, &best, alt->value);
+
+ if (!found) {
+ int min, max;
+ choice_get_min_max(opt, &min, &max);
+ best = (dir == -1) ^ wrap ? min : max;
+ }
+
+ *(int *)val = best;
+}
+
+const struct m_option_type m_option_type_choice = {
+ .name = "String", // same as arbitrary strings in option list for now
+ .size = sizeof(int),
+ .parse = parse_choice,
+ .print = print_choice,
+ .copy = copy_opt,
+ .add = add_choice,
+ .clamp = clamp_choice,
+};
+
+// Float
+
+#undef VAL
+#define VAL(x) (*(double *)(x))
+
+static int clamp_double(const m_option_t *opt, void *val)
+{
+ double v = VAL(val);
+ int r = 0;
+ if ((opt->flags & M_OPT_MAX) && (v > opt->max)) {
+ v = opt->max;
+ r = M_OPT_OUT_OF_RANGE;
+ }
+ if ((opt->flags & M_OPT_MIN) && (v < opt->min)) {
+ v = opt->min;
+ r = M_OPT_OUT_OF_RANGE;
+ }
+ if (!isfinite(v)) {
+ v = opt->min;
+ r = M_OPT_OUT_OF_RANGE;
+ }
+ VAL(val) = v;
+ return r;
+}
+
+static int parse_double(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ struct bstr rest;
+ double tmp_float = bstrtod(param, &rest);
+
+ if (bstr_eatstart0(&rest, ":") || bstr_eatstart0(&rest, "/"))
+ tmp_float /= bstrtod(rest, &rest);
+
+ if (rest.len) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option must be a floating point number or a "
+ "ratio (numerator[:/]denominator): %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if (opt->flags & M_OPT_MIN)
+ if (tmp_float < opt->min) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option must be >= %f: %.*s\n",
+ BSTR_P(name), opt->min, BSTR_P(param));
+ return M_OPT_OUT_OF_RANGE;
+ }
+
+ if (opt->flags & M_OPT_MAX)
+ if (tmp_float > opt->max) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option must be <= %f: %.*s\n",
+ BSTR_P(name), opt->max, BSTR_P(param));
+ return M_OPT_OUT_OF_RANGE;
+ }
+
+ if (!isfinite(tmp_float)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option must be a finite number: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_OUT_OF_RANGE;
+ }
+
+ if (dst)
+ VAL(dst) = tmp_float;
+ return 1;
+}
+
+static char *print_double(const m_option_t *opt, const void *val)
+{
+ return talloc_asprintf(NULL, "%f", VAL(val));
+}
+
+static char *print_double_f3(const m_option_t *opt, const void *val)
+{
+ return talloc_asprintf(NULL, "%.3f", VAL(val));
+}
+
+static void add_double(const m_option_t *opt, void *val, double add, bool wrap)
+{
+ double v = VAL(val);
+
+ v = v + add;
+
+ double min = (opt->flags & M_OPT_MIN) ? opt->min : -INFINITY;
+ double max = (opt->flags & M_OPT_MAX) ? opt->max : +INFINITY;
+
+ if (v < min)
+ v = wrap ? max : min;
+ if (v > max)
+ v = wrap ? min : max;
+
+ VAL(val) = v;
+}
+
+const m_option_type_t m_option_type_double = {
+ // double precision float or ratio (numerator[:/]denominator)
+ .name = "Double",
+ .size = sizeof(double),
+ .parse = parse_double,
+ .print = print_double,
+ .pretty_print = print_double_f3,
+ .copy = copy_opt,
+ .clamp = clamp_double,
+};
+
+#undef VAL
+#define VAL(x) (*(float *)(x))
+
+static int clamp_float(const m_option_t *opt, void *val)
+{
+ double tmp = VAL(val);
+ int r = clamp_double(opt, &tmp);
+ VAL(val) = tmp;
+ return r;
+}
+
+static int parse_float(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ double tmp;
+ int r = parse_double(opt, name, param, &tmp);
+ if (r == 1 && dst)
+ VAL(dst) = tmp;
+ return r;
+}
+
+static char *print_float(const m_option_t *opt, const void *val)
+{
+ return talloc_asprintf(NULL, "%f", VAL(val));
+}
+
+static char *print_float_f3(const m_option_t *opt, const void *val)
+{
+ return talloc_asprintf(NULL, "%.3f", VAL(val));
+}
+
+static void add_float(const m_option_t *opt, void *val, double add, bool wrap)
+{
+ double tmp = VAL(val);
+ add_double(opt, &tmp, add, wrap);
+ VAL(val) = tmp;
+}
+
+const m_option_type_t m_option_type_float = {
+ // floating point number or ratio (numerator[:/]denominator)
+ .name = "Float",
+ .size = sizeof(float),
+ .parse = parse_float,
+ .print = print_float,
+ .pretty_print = print_float_f3,
+ .copy = copy_opt,
+ .add = add_float,
+ .clamp = clamp_float,
+};
+
+///////////// String
+
+#undef VAL
+#define VAL(x) (*(char **)(x))
+
+static char *unescape_string(void *talloc_ctx, bstr str)
+{
+ char *res = talloc_strdup(talloc_ctx, "");
+ while (str.len) {
+ bstr rest;
+ bool esc = bstr_split_tok(str, "\\", &str, &rest);
+ res = talloc_strndup_append_buffer(res, str.start, str.len);
+ if (esc) {
+ if (!mp_parse_escape(&rest, &res)) {
+ talloc_free(res);
+ return NULL;
+ }
+ }
+ str = rest;
+ }
+ return res;
+}
+
+static char *escape_string(char *str0)
+{
+ char *res = talloc_strdup(NULL, "");
+ bstr str = bstr0(str0);
+ while (str.len) {
+ bstr rest;
+ bool esc = bstr_split_tok(str, "\\", &str, &rest);
+ res = talloc_strndup_append_buffer(res, str.start, str.len);
+ if (esc)
+ res = talloc_strdup_append_buffer(res, "\\\\");
+ str = rest;
+ }
+ return res;
+}
+
+static int clamp_str(const m_option_t *opt, void *val)
+{
+ char *v = VAL(val);
+ int len = v ? strlen(v) : 0;
+ if ((opt->flags & M_OPT_MIN) && (len < opt->min))
+ return M_OPT_OUT_OF_RANGE;
+ if ((opt->flags & M_OPT_MAX) && (len > opt->max))
+ return M_OPT_OUT_OF_RANGE;
+ return 0;
+}
+
+static int parse_str(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ int r = 1;
+ void *tmp = talloc_new(NULL);
+
+ if (param.start == NULL) {
+ r = M_OPT_MISSING_PARAM;
+ goto exit;
+ }
+
+ m_opt_string_validate_fn validate = opt->priv;
+ if (validate) {
+ r = validate(opt, name, param);
+ if (r < 0)
+ goto exit;
+ }
+
+ if (opt->flags & M_OPT_PARSE_ESCAPES) {
+ char *res = unescape_string(tmp, param);
+ if (!res) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Parameter has broken escapes: %.*s\n", BSTR_P(param));
+ r = M_OPT_INVALID;
+ goto exit;
+ }
+ param = bstr0(res);
+ }
+
+ if ((opt->flags & M_OPT_MIN) && (param.len < opt->min)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Parameter must be >= %d chars: %.*s\n",
+ (int) opt->min, BSTR_P(param));
+ r = M_OPT_OUT_OF_RANGE;
+ goto exit;
+ }
+
+ if ((opt->flags & M_OPT_MAX) && (param.len > opt->max)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Parameter must be <= %d chars: %.*s\n",
+ (int) opt->max, BSTR_P(param));
+ r = M_OPT_OUT_OF_RANGE;
+ goto exit;
+ }
+
+ if (dst) {
+ talloc_free(VAL(dst));
+ VAL(dst) = bstrdup0(NULL, param);
+ }
+
+exit:
+ talloc_free(tmp);
+ return r;
+}
+
+static char *print_str(const m_option_t *opt, const void *val)
+{
+ bool need_escape = opt->flags & M_OPT_PARSE_ESCAPES;
+ char *s = val ? VAL(val) : NULL;
+ return s ? (need_escape ? escape_string(s) : talloc_strdup(NULL, s)) : NULL;
+}
+
+static void copy_str(const m_option_t *opt, void *dst, const void *src)
+{
+ if (dst && src) {
+ talloc_free(VAL(dst));
+ VAL(dst) = talloc_strdup(NULL, VAL(src));
+ }
+}
+
+static void free_str(void *src)
+{
+ if (src && VAL(src)) {
+ talloc_free(VAL(src));
+ VAL(src) = NULL;
+ }
+}
+
+const m_option_type_t m_option_type_string = {
+ .name = "String",
+ .size = sizeof(char *),
+ .flags = M_OPT_TYPE_DYNAMIC,
+ .parse = parse_str,
+ .print = print_str,
+ .copy = copy_str,
+ .free = free_str,
+ .clamp = clamp_str,
+};
+
+//////////// String list
+
+#undef VAL
+#define VAL(x) (*(char ***)(x))
+
+#define OP_NONE 0
+#define OP_ADD 1
+#define OP_PRE 2
+#define OP_DEL 3
+#define OP_CLR 4
+#define OP_TOGGLE 5
+
+static void free_str_list(void *dst)
+{
+ char **d;
+ int i;
+
+ if (!dst || !VAL(dst))
+ return;
+ d = VAL(dst);
+
+ for (i = 0; d[i] != NULL; i++)
+ talloc_free(d[i]);
+ talloc_free(d);
+ VAL(dst) = NULL;
+}
+
+static int str_list_add(char **add, int n, void *dst, int pre)
+{
+ if (!dst)
+ return M_OPT_PARSER_ERR;
+ char **lst = VAL(dst);
+
+ int ln;
+ for (ln = 0; lst && lst[ln]; ln++)
+ /**/;
+
+ lst = talloc_realloc(NULL, lst, char *, n + ln + 1);
+
+ if (pre) {
+ memmove(&lst[n], lst, ln * sizeof(char *));
+ memcpy(lst, add, n * sizeof(char *));
+ } else
+ memcpy(&lst[ln], add, n * sizeof(char *));
+ // (re-)add NULL-termination
+ lst[ln + n] = NULL;
+
+ talloc_free(add);
+
+ VAL(dst) = lst;
+
+ return 1;
+}
+
+static int str_list_del(char **del, int n, void *dst)
+{
+ char **lst, *ep;
+ int i, ln, s;
+ long idx;
+
+ if (!dst)
+ return M_OPT_PARSER_ERR;
+ lst = VAL(dst);
+
+ for (ln = 0; lst && lst[ln]; ln++)
+ /**/;
+ s = ln;
+
+ for (i = 0; del[i] != NULL; i++) {
+ idx = strtol(del[i], &ep, 0);
+ if (*ep) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Invalid index: %s\n", del[i]);
+ talloc_free(del[i]);
+ continue;
+ }
+ talloc_free(del[i]);
+ if (idx < 0 || idx >= ln) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Index %ld is out of range.\n", idx);
+ continue;
+ } else if (!lst[idx])
+ continue;
+ talloc_free(lst[idx]);
+ lst[idx] = NULL;
+ s--;
+ }
+ talloc_free(del);
+
+ if (s == 0) {
+ talloc_free(lst);
+ VAL(dst) = NULL;
+ return 1;
+ }
+
+ // Don't bother shrinking the list allocation
+ for (i = 0, n = 0; i < ln; i++) {
+ if (!lst[i])
+ continue;
+ lst[n] = lst[i];
+ n++;
+ }
+ lst[s] = NULL;
+
+ return 1;
+}
+
+static struct bstr get_nextsep(struct bstr *ptr, char sep, bool modify)
+{
+ struct bstr str = *ptr;
+ struct bstr orig = str;
+ for (;;) {
+ int idx = bstrchr(str, sep);
+ if (idx > 0 && str.start[idx - 1] == '\\') {
+ if (modify) {
+ memmove(str.start + idx - 1, str.start + idx, str.len - idx);
+ str.len--;
+ str = bstr_cut(str, idx);
+ } else
+ str = bstr_cut(str, idx + 1);
+ } else {
+ str = bstr_cut(str, idx < 0 ? str.len : idx);
+ break;
+ }
+ }
+ *ptr = str;
+ return bstr_splice(orig, 0, str.start - orig.start);
+}
+
+static int parse_str_list(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ char **res;
+ int op = OP_NONE;
+ int len = strlen(opt->name);
+ if (opt->name[len - 1] == '*' && (name.len > len - 1)) {
+ struct bstr suffix = bstr_cut(name, len - 1);
+ if (bstrcmp0(suffix, "-add") == 0)
+ op = OP_ADD;
+ else if (bstrcmp0(suffix, "-pre") == 0)
+ op = OP_PRE;
+ else if (bstrcmp0(suffix, "-del") == 0)
+ op = OP_DEL;
+ else if (bstrcmp0(suffix, "-clr") == 0)
+ op = OP_CLR;
+ else
+ return M_OPT_UNKNOWN;
+ }
+
+ // Clear the list ??
+ if (op == OP_CLR) {
+ if (dst)
+ free_str_list(dst);
+ return 0;
+ }
+
+ // All other ops need a param
+ if (param.len == 0 && op != OP_NONE)
+ return M_OPT_MISSING_PARAM;
+
+ // custom type for "profile" calls this but uses ->priv for something else
+ char separator = opt->type == &m_option_type_string_list && opt->priv ?
+ *(char *)opt->priv : OPTION_LIST_SEPARATOR;
+ int n = 0;
+ struct bstr str = param;
+ while (str.len) {
+ get_nextsep(&str, separator, 0);
+ str = bstr_cut(str, 1);
+ n++;
+ }
+ if (n == 0 && op != OP_NONE)
+ return M_OPT_INVALID;
+ if (((opt->flags & M_OPT_MIN) && (n < opt->min)) ||
+ ((opt->flags & M_OPT_MAX) && (n > opt->max)))
+ return M_OPT_OUT_OF_RANGE;
+
+ if (!dst)
+ return 1;
+
+ res = talloc_array(NULL, char *, n + 2);
+ str = bstrdup(NULL, param);
+ char *ptr = str.start;
+ n = 0;
+
+ while (1) {
+ struct bstr el = get_nextsep(&str, separator, 1);
+ res[n] = bstrdup0(NULL, el);
+ n++;
+ if (!str.len)
+ break;
+ str = bstr_cut(str, 1);
+ }
+ res[n] = NULL;
+ talloc_free(ptr);
+
+ switch (op) {
+ case OP_ADD:
+ return str_list_add(res, n, dst, 0);
+ case OP_PRE:
+ return str_list_add(res, n, dst, 1);
+ case OP_DEL:
+ return str_list_del(res, n, dst);
+ }
+
+ if (VAL(dst))
+ free_str_list(dst);
+ VAL(dst) = res;
+
+ if (!res[0])
+ free_str_list(dst);
+
+ return 1;
+}
+
+static void copy_str_list(const m_option_t *opt, void *dst, const void *src)
+{
+ int n;
+ char **d, **s;
+
+ if (!(dst && src))
+ return;
+ s = VAL(src);
+
+ if (VAL(dst))
+ free_str_list(dst);
+
+ if (!s) {
+ VAL(dst) = NULL;
+ return;
+ }
+
+ for (n = 0; s[n] != NULL; n++)
+ /* NOTHING */;
+ d = talloc_array(NULL, char *, n + 1);
+ for (; n >= 0; n--)
+ d[n] = talloc_strdup(NULL, s[n]);
+
+ VAL(dst) = d;
+}
+
+static char *print_str_list(const m_option_t *opt, const void *src)
+{
+ char **lst = NULL;
+ char *ret = NULL;
+
+ if (!(src && VAL(src)))
+ return NULL;
+ lst = VAL(src);
+
+ for (int i = 0; lst[i]; i++) {
+ if (ret)
+ ret = talloc_strdup_append_buffer(ret, ",");
+ ret = talloc_strdup_append_buffer(ret, lst[i]);
+ }
+ return ret;
+}
+
+const m_option_type_t m_option_type_string_list = {
+ /* A list of strings separated by ','.
+ * Option with a name ending in '*' permits using the following suffixes:
+ * -add: Add the given parameters at the end of the list.
+ * -pre: Add the given parameters at the beginning of the list.
+ * -del: Remove the entry at the given indices.
+ * -clr: Clear the list.
+ * e.g: -vf-add flip,mirror -vf-del 2,5
+ */
+ .name = "String list",
+ .size = sizeof(char **),
+ .flags = M_OPT_TYPE_DYNAMIC | M_OPT_TYPE_ALLOW_WILDCARD,
+ .parse = parse_str_list,
+ .print = print_str_list,
+ .copy = copy_str_list,
+ .free = free_str_list,
+};
+
+
+/////////////////// Print
+
+static int parse_print(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (opt->type == CONF_TYPE_PRINT) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "%s", mp_gtext(opt->p));
+ } else {
+ char *name0 = bstrdup0(NULL, name);
+ char *param0 = bstrdup0(NULL, param);
+ int r = ((m_opt_func_full_t) opt->p)(opt, name0, param0);
+ talloc_free(name0);
+ talloc_free(param0);
+ return r;
+ }
+
+ if (opt->priv == NULL)
+ return M_OPT_EXIT;
+ return 0;
+}
+
+const m_option_type_t m_option_type_print = {
+ .name = "Print",
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_print,
+};
+
+const m_option_type_t m_option_type_print_func_param = {
+ .name = "Print",
+ .flags = M_OPT_TYPE_ALLOW_WILDCARD,
+ .parse = parse_print,
+};
+
+const m_option_type_t m_option_type_print_func = {
+ .name = "Print",
+ .flags = M_OPT_TYPE_ALLOW_WILDCARD | M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_print,
+};
+
+
+/////////////////////// Subconfig
+#undef VAL
+#define VAL(x) (*(char ***)(x))
+
+// Read s sub-option name, or a positional sub-opt value.
+// Return 0 on succes, M_OPT_ error code otherwise.
+// optname is for error reporting.
+static int read_subparam(bstr optname, bstr *str, bstr *out_subparam)
+{
+ bstr p = *str;
+ bstr subparam = {0};
+
+ if (bstr_eatstart0(&p, "\"")) {
+ int optlen = bstrcspn(p, "\"");
+ subparam = bstr_splice(p, 0, optlen);
+ p = bstr_cut(p, optlen);
+ if (!bstr_startswith0(p, "\"")) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Terminating '\"' missing for '%.*s'\n",
+ BSTR_P(optname));
+ return M_OPT_INVALID;
+ }
+ p = bstr_cut(p, 1);
+ } else if (bstr_eatstart0(&p, "[")) {
+ if (!bstr_split_tok(p, "]", &subparam, &p)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Terminating ']' missing for '%.*s'\n",
+ BSTR_P(optname));
+ return M_OPT_INVALID;
+ }
+ } else if (bstr_eatstart0(&p, "%")) {
+ int optlen = bstrtoll(p, &p, 0);
+ if (!bstr_startswith0(p, "%") || (optlen > p.len - 1)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid length %d for '%.*s'\n",
+ optlen, BSTR_P(optname));
+ return M_OPT_INVALID;
+ }
+ subparam = bstr_splice(p, 1, optlen + 1);
+ p = bstr_cut(p, optlen + 1);
+ } else {
+ // Skip until the next character that could possibly be a meta
+ // character in option parsing.
+ int optlen = bstrcspn(p, ":=,\\%\"'[]");
+ subparam = bstr_splice(p, 0, optlen);
+ p = bstr_cut(p, optlen);
+ }
+
+ *str = p;
+ *out_subparam = subparam;
+ return 0;
+}
+
+// Return 0 on success, otherwise error code
+// On success, set *out_name and *out_val, and advance *str
+// out_val.start is NULL if there was no parameter.
+// optname is for error reporting.
+static int split_subconf(bstr optname, bstr *str, bstr *out_name, bstr *out_val)
+{
+ bstr p = *str;
+ bstr subparam = {0};
+ bstr subopt;
+ int r = read_subparam(optname, &p, &subopt);
+ if (r < 0)
+ return r;
+ if (bstr_eatstart0(&p, "=")) {
+ r = read_subparam(subopt, &p, &subparam);
+ if (r < 0)
+ return r;
+ }
+ *str = p;
+ *out_name = subopt;
+ *out_val = subparam;
+ return 0;
+}
+
+static int parse_subconf(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ int nr = 0;
+ char **lst = NULL;
+
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ struct bstr p = param;
+
+ while (p.len) {
+ bstr subopt, subparam;
+ int r = split_subconf(name, &p, &subopt, &subparam);
+ if (r < 0)
+ return r;
+ if (bstr_startswith0(p, ":"))
+ p = bstr_cut(p, 1);
+ else if (p.len > 0) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Incorrect termination for '%.*s'\n", BSTR_P(subopt));
+ return M_OPT_INVALID;
+ }
+
+ if (dst) {
+ lst = talloc_realloc(NULL, lst, char *, 2 * (nr + 2));
+ lst[2 * nr] = bstrto0(lst, subopt);
+ lst[2 * nr + 1] = bstrto0(lst, subparam);
+ memset(&lst[2 * (nr + 1)], 0, 2 * sizeof(char *));
+ nr++;
+ }
+ }
+
+ if (dst)
+ VAL(dst) = lst;
+
+ return 1;
+}
+
+const m_option_type_t m_option_type_subconfig = {
+ // The syntax is -option opt1=foo:flag:opt2=blah
+ .name = "Subconfig",
+ .flags = M_OPT_TYPE_HAS_CHILD,
+ .parse = parse_subconf,
+};
+
+const m_option_type_t m_option_type_subconfig_struct = {
+ .name = "Subconfig",
+ .flags = M_OPT_TYPE_HAS_CHILD | M_OPT_TYPE_USE_SUBSTRUCT,
+ .parse = parse_subconf,
+};
+
+static int parse_color(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ bstr val = param;
+ struct m_color color = {0};
+
+ if (bstr_eatstart0(&val, "#")) {
+ // #[AA]RRGGBB
+ if (val.len != 6 && val.len != 8)
+ goto error;
+ bool has_alpha = val.len == 8;
+ uint32_t c = bstrtoll(val, &val, 16);
+ if (val.len)
+ goto error;
+ color = (struct m_color) {
+ (c >> 16) & 0xFF,
+ (c >> 8) & 0xFF,
+ c & 0xFF,
+ has_alpha ? (c >> 24) & 0xFF : 0xFF,
+ };
+ } else {
+ goto error;
+ }
+
+ if (dst)
+ *((struct m_color *)dst) = color;
+
+ return 1;
+
+error:
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: invalid color: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Valid colors must be in the form #RRRGGBB or #AARRGGBB (in hex)\n");
+ return M_OPT_INVALID;
+}
+
+const m_option_type_t m_option_type_color = {
+ .name = "Color",
+ .size = sizeof(struct m_color),
+ .parse = parse_color,
+};
+
+
+// Parse a >=0 number starting at s. Set s to the string following the number.
+// If the number ends with '%', eat that and set *out_per to true, but only
+// if the number is between 0-100; if not, don't eat anything, even the number.
+static bool eat_num_per(bstr *s, int *out_num, bool *out_per)
+{
+ bstr rest;
+ long long v = bstrtoll(*s, &rest, 10);
+ if (s->len == rest.len || v < INT_MIN || v > INT_MAX)
+ return false;
+ *out_num = v;
+ *out_per = false;
+ *s = rest;
+ if (bstr_eatstart0(&rest, "%") && v >= 0 && v <= 100) {
+ *out_per = true;
+ *s = rest;
+ }
+ return true;
+}
+
+static bool parse_geometry_str(struct m_geometry *gm, bstr s)
+{
+ *gm = (struct m_geometry) { .x = INT_MIN, .y = INT_MIN };
+ if (s.len == 0)
+ return true;
+ // Approximate grammar:
+ // [W[xH]][{+-}X{+-}Y] | [X:Y]
+ // (meaning: [optional] {one character of} one|alternative)
+ // Every number can be followed by '%'
+ int num;
+ bool per;
+
+#define READ_NUM(F, F_PER) do { \
+ if (!eat_num_per(&s, &num, &per)) \
+ goto error; \
+ gm->F = num; \
+ gm->F_PER = per; \
+} while(0)
+
+#define READ_SIGN(F) do { \
+ if (bstr_eatstart0(&s, "+")) { \
+ gm->F = false; \
+ } else if (bstr_eatstart0(&s, "-")) {\
+ gm->F = true; \
+ } else goto error; \
+} while(0)
+
+ if (bstrchr(s, ':') < 0) {
+ gm->wh_valid = true;
+ if (!bstr_startswith0(s, "+") && !bstr_startswith0(s, "-")) {
+ READ_NUM(w, w_per);
+ if (bstr_eatstart0(&s, "x"))
+ READ_NUM(h, h_per);
+ }
+ if (s.len > 0) {
+ gm->xy_valid = true;
+ READ_SIGN(x_sign);
+ READ_NUM(x, x_per);
+ READ_SIGN(y_sign);
+ READ_NUM(y, y_per);
+ }
+ } else {
+ gm->xy_valid = true;
+ READ_NUM(x, x_per);
+ if (!bstr_eatstart0(&s, ":"))
+ goto error;
+ READ_NUM(y, y_per);
+ }
+
+ return s.len == 0;
+
+error:
+ return false;
+}
+
+#undef READ_NUM
+#undef READ_SIGN
+
+// xpos,ypos: position of the left upper corner
+// widw,widh: width and height of the window
+// scrw,scrh: width and height of the current screen
+// The input parameters should be set to a centered window (default fallbacks).
+void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
+ int scrw, int scrh, struct m_geometry *gm)
+{
+ if (gm->wh_valid) {
+ int prew = *widw, preh = *widh;
+ if (gm->w > 0)
+ *widw = gm->w_per ? scrw * (gm->w / 100.0) : gm->w;
+ if (gm->h > 0)
+ *widh = gm->h_per ? scrh * (gm->h / 100.0) : gm->h;
+ // keep aspect if the other value is not set
+ double asp = (double)prew / preh;
+ if (gm->w > 0 && !(gm->h > 0)) {
+ *widh = *widw / asp;
+ } else if (!(gm->w > 0) && gm->h > 0) {
+ *widw = *widh * asp;
+ }
+ }
+
+ if (gm->xy_valid) {
+ if (gm->x != INT_MIN) {
+ *xpos = gm->x;
+ if (gm->x_per)
+ *xpos = (scrw - *widw) * (*xpos / 100.0);
+ if (gm->x_sign)
+ *xpos = scrw - *widw - *xpos;
+ }
+ if (gm->y != INT_MIN) {
+ *ypos = gm->y;
+ if (gm->y_per)
+ *ypos = (scrh - *widh) * (*ypos / 100.0);
+ if (gm->y_sign)
+ *ypos = scrh - *widh - *ypos;
+ }
+ }
+}
+
+static int parse_geometry(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ struct m_geometry gm;
+ if (!parse_geometry_str(&gm, param))
+ goto error;
+
+ if (dst)
+ *((struct m_geometry *)dst) = gm;
+
+ return 1;
+
+error:
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid geometry: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Valid format: [W[%%][xH[%%]]][{+-}X[%%]{+-}Y[%%]] | [X[%%]:Y[%%]]\n");
+ return M_OPT_INVALID;
+}
+
+const m_option_type_t m_option_type_geometry = {
+ .name = "Window geometry",
+ .size = sizeof(struct m_geometry),
+ .parse = parse_geometry,
+};
+
+static int parse_size_box(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ struct m_geometry gm;
+ if (!parse_geometry_str(&gm, param))
+ goto error;
+
+ if (gm.xy_valid)
+ goto error;
+
+ if (dst)
+ *((struct m_geometry *)dst) = gm;
+
+ return 1;
+
+error:
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid size: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Valid format: W[%%][xH[%%]] or empty string\n");
+ return M_OPT_INVALID;
+}
+
+const m_option_type_t m_option_type_size_box = {
+ .name = "Window size",
+ .size = sizeof(struct m_geometry),
+ .parse = parse_size_box,
+};
+
+
+#include "video/img_format.h"
+
+static int parse_imgfmt(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ if (!bstrcmp0(param, "help")) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available formats:");
+ for (int i = 0; mp_imgfmt_list[i].name; i++)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s", mp_imgfmt_list[i].name);
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ return M_OPT_EXIT - 1;
+ }
+
+ unsigned int fmt = mp_imgfmt_from_name(param, false);
+ if (!fmt) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: unknown format name: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if (dst)
+ *((uint32_t *)dst) = fmt;
+
+ return 1;
+}
+
+const m_option_type_t m_option_type_imgfmt = {
+ // Please report any missing colorspaces
+ .name = "Image format",
+ .size = sizeof(uint32_t),
+ .parse = parse_imgfmt,
+ .copy = copy_opt,
+};
+
+static int parse_fourcc(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ unsigned int value;
+
+ if (param.len == 4) {
+ uint8_t *s = param.start;
+ value = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24);
+ } else {
+ bstr rest;
+ value = bstrtoll(param, &rest, 16);
+ if (rest.len != 0) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: invalid FourCC: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+ }
+
+ if (dst)
+ *((unsigned int *)dst) = value;
+
+ return 1;
+}
+
+const m_option_type_t m_option_type_fourcc = {
+ .name = "FourCC",
+ .size = sizeof(unsigned int),
+ .parse = parse_fourcc,
+ .copy = copy_opt,
+};
+
+#include "audio/format.h"
+
+static int parse_afmt(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ if (!bstrcmp0(param, "help")) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available formats:");
+ for (int i = 0; af_fmtstr_table[i].name; i++)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s", af_fmtstr_table[i].name);
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ return M_OPT_EXIT - 1;
+ }
+
+ int fmt = af_str2fmt_short(param);
+ if (fmt == -1) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: unknown format name: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if (dst)
+ *((uint32_t *)dst) = fmt;
+
+ return 1;
+}
+
+const m_option_type_t m_option_type_afmt = {
+ // Please report any missing formats
+ .name = "Audio format",
+ .size = sizeof(uint32_t),
+ .parse = parse_afmt,
+ .copy = copy_opt,
+};
+
+#include "audio/chmap.h"
+
+static int parse_chmap(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ // min>0: at least min channels, min=0: empty ok, min=-1: invalid ok
+ int min_ch = (opt->flags & M_OPT_MIN) ? opt->min : 1;
+
+ if (bstr_equals0(param, "help")) {
+ mp_chmap_print_help(MSGT_CFGPARSER, MSGL_INFO);
+ return M_OPT_EXIT - 1;
+ }
+
+ if (param.len == 0 && min_ch >= 1)
+ return M_OPT_MISSING_PARAM;
+
+ struct mp_chmap res = {0};
+ if (!mp_chmap_from_str(&res, param)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Error parsing channel layout: %.*s\n", BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if ((min_ch > 0 && !mp_chmap_is_valid(&res)) ||
+ (min_ch >= 0 && mp_chmap_is_empty(&res)))
+ {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid channel layout: %.*s\n", BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if (dst)
+ *(struct mp_chmap *)dst = res;
+
+ return 1;
+}
+
+const m_option_type_t m_option_type_chmap = {
+ .name = "Audio channels or channel map",
+ .size = sizeof(struct mp_chmap *),
+ .parse = parse_chmap,
+ .copy = copy_opt,
+};
+
+static int parse_timestring(struct bstr str, double *time, char endchar)
+{
+ int a, b, len;
+ double d;
+ *time = 0; /* ensure initialization for error cases */
+ if (bstr_sscanf(str, "%d:%d:%lf%n", &a, &b, &d, &len) >= 3)
+ *time = 3600 * a + 60 * b + d;
+ else if (bstr_sscanf(str, "%d:%lf%n", &a, &d, &len) >= 2)
+ *time = 60 * a + d;
+ else if (bstr_sscanf(str, "%lf%n", &d, &len) >= 1)
+ *time = d;
+ else
+ return 0; /* unsupported time format */
+ if (len < str.len && str.start[len] != endchar)
+ return 0; /* invalid extra characters at the end */
+ if (!isfinite(*time))
+ return 0;
+ return len;
+}
+
+
+static int parse_time(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ double time;
+
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ if (!parse_timestring(param, &time, 0)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid time: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if (dst)
+ *(double *)dst = time;
+ return 1;
+}
+
+static char *pretty_print_time(const m_option_t *opt, const void *val)
+{
+ return mp_format_time(*(double *)val, false);
+}
+
+const m_option_type_t m_option_type_time = {
+ .name = "Time",
+ .size = sizeof(double),
+ .parse = parse_time,
+ .print = print_double,
+ .pretty_print = pretty_print_time,
+ .copy = copy_opt,
+ .add = add_double,
+ .clamp = clamp_double,
+};
+
+
+// Relative time
+
+static int parse_rel_time(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ struct m_rel_time t = {0};
+
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ // Percent pos
+ if (bstr_endswith0(param, "%")) {
+ double percent = bstrtod(bstr_splice(param, 0, -1), &param);
+ if (param.len == 0 && percent >= 0 && percent <= 100) {
+ t.type = REL_TIME_PERCENT;
+ t.pos = percent;
+ goto out;
+ }
+ }
+
+ // Chapter pos
+ if (bstr_startswith0(param, "#")) {
+ int chapter = bstrtoll(bstr_cut(param, 1), &param, 10);
+ if (param.len == 0 && chapter >= 1) {
+ t.type = REL_TIME_CHAPTER;
+ t.pos = chapter - 1;
+ goto out;
+ }
+ }
+
+ bool sign = bstr_eatstart0(&param, "-");
+ double time;
+ if (parse_timestring(param, &time, 0)) {
+ t.type = sign ? REL_TIME_NEGATIVE : REL_TIME_ABSOLUTE;
+ t.pos = time;
+ goto out;
+ }
+
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: invalid time or position: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+
+out:
+ if (dst)
+ *(struct m_rel_time *)dst = t;
+ return 1;
+}
+
+const m_option_type_t m_option_type_rel_time = {
+ .name = "Relative time or percent position",
+ .size = sizeof(struct m_rel_time),
+ .parse = parse_rel_time,
+ .copy = copy_opt,
+};
+
+
+//// Objects (i.e. filters, etc) settings
+
+#undef VAL
+#define VAL(x) (*(m_obj_settings_t **)(x))
+
+bool m_obj_list_find(struct m_obj_desc *dst, const struct m_obj_list *l,
+ bstr name)
+{
+ for (int i = 0; ; i++) {
+ if (!l->get_desc(dst, i))
+ break;
+ if (bstr_equals0(name, dst->name))
+ return true;
+ }
+ if (l->aliases) {
+ for (int i = 0; l->aliases[i][0]; i++) {
+ const char *aname = l->aliases[i][0];
+ const char *alias = l->aliases[i][1];
+ const char *opts = l->aliases[i][2];
+ if (bstr_equals0(name, aname) &&
+ m_obj_list_find(dst, l, bstr0(alias)))
+ {
+ if (opts) {
+ dst->init_options = opts;
+ } else {
+ // Assume it's deprecated in this case.
+ // Also, it's used by the VO code only, so whatever.
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "VO driver '%s' has been replaced with '%s'!\n",
+ aname, alias);
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static void obj_setting_free(m_obj_settings_t *item)
+{
+ talloc_free(item->name);
+ talloc_free(item->label);
+ free_str_list(&(item->attribs));
+}
+
+// If at least one item has a label, compare labels only - otherwise ignore them.
+static bool obj_setting_equals(m_obj_settings_t *a, m_obj_settings_t *b)
+{
+ bstr la = bstr0(a->label), lb = bstr0(b->label);
+ if (la.len || lb.len)
+ return bstr_equals(la, lb);
+ if (strcmp(a->name, b->name) != 0)
+ return false;
+
+ int a_attr_count = 0;
+ while (a->attribs && a->attribs[a_attr_count])
+ a_attr_count++;
+ int b_attr_count = 0;
+ while (b->attribs && b->attribs[b_attr_count])
+ b_attr_count++;
+ if (a_attr_count != b_attr_count)
+ return false;
+ for (int n = 0; n < a_attr_count; n++) {
+ if (strcmp(a->attribs[n], b->attribs[n]) != 0)
+ return false;
+ }
+ return true;
+}
+
+static int obj_settings_list_num_items(m_obj_settings_t *obj_list)
+{
+ int num = 0;
+ while (obj_list && obj_list[num].name)
+ num++;
+ return num;
+}
+
+static void obj_settings_list_del_at(m_obj_settings_t **p_obj_list, int idx)
+{
+ m_obj_settings_t *obj_list = *p_obj_list;
+ int num = obj_settings_list_num_items(obj_list);
+
+ assert(idx >= 0 && idx < num);
+
+ obj_setting_free(&obj_list[idx]);
+
+ // Note: the NULL-terminating element is moved down as part of this
+ memmove(&obj_list[idx], &obj_list[idx + 1],
+ sizeof(m_obj_settings_t) * (num - idx));
+
+ *p_obj_list = talloc_realloc(NULL, obj_list, struct m_obj_settings, num);
+}
+
+// Insert such that *p_obj_list[idx] is set to item.
+// If idx < 0, set idx = count + idx + 1 (i.e. -1 inserts it as last element).
+// Memory referenced by *item is not copied.
+static void obj_settings_list_insert_at(m_obj_settings_t **p_obj_list, int idx,
+ m_obj_settings_t *item)
+{
+ int num = obj_settings_list_num_items(*p_obj_list);
+ if (idx < 0)
+ idx = num + idx + 1;
+ assert(idx >= 0 && idx <= num);
+ *p_obj_list = talloc_realloc(NULL, *p_obj_list, struct m_obj_settings,
+ num + 2);
+ memmove(*p_obj_list + idx + 1, *p_obj_list + idx,
+ (num - idx) * sizeof(m_obj_settings_t));
+ (*p_obj_list)[idx] = *item;
+ (*p_obj_list)[num + 1] = (m_obj_settings_t){0};
+}
+
+static int obj_settings_list_find_by_label(m_obj_settings_t *obj_list,
+ bstr label)
+{
+ for (int n = 0; obj_list && obj_list[n].name; n++) {
+ if (label.len && bstr_equals0(label, obj_list[n].label))
+ return n;
+ }
+ return -1;
+}
+
+static int obj_settings_list_find_by_label0(m_obj_settings_t *obj_list,
+ const char *label)
+{
+ return obj_settings_list_find_by_label(obj_list, bstr0(label));
+}
+
+static int obj_settings_find_by_content(m_obj_settings_t *obj_list,
+ m_obj_settings_t *item)
+{
+ for (int n = 0; obj_list && obj_list[n].name; n++) {
+ if (obj_setting_equals(&obj_list[n], item))
+ return n;
+ }
+ return -1;
+}
+
+static void free_obj_settings_list(void *dst)
+{
+ int n;
+ m_obj_settings_t *d;
+
+ if (!dst || !VAL(dst))
+ return;
+
+ d = VAL(dst);
+ for (n = 0; d[n].name; n++)
+ obj_setting_free(&d[n]);
+ talloc_free(d);
+ VAL(dst) = NULL;
+}
+
+static void copy_obj_settings_list(const m_option_t *opt, void *dst,
+ const void *src)
+{
+ m_obj_settings_t *d, *s;
+ int n;
+
+ if (!(dst && src))
+ return;
+
+ s = VAL(src);
+
+ if (VAL(dst))
+ free_obj_settings_list(dst);
+ if (!s)
+ return;
+
+ for (n = 0; s[n].name; n++)
+ /* NOP */;
+ d = talloc_array(NULL, struct m_obj_settings, n + 1);
+ for (n = 0; s[n].name; n++) {
+ d[n].name = talloc_strdup(NULL, s[n].name);
+ d[n].label = talloc_strdup(NULL, s[n].label);
+ d[n].attribs = NULL;
+ copy_str_list(NULL, &(d[n].attribs), &(s[n].attribs));
+ }
+ d[n].name = NULL;
+ d[n].label = NULL;
+ d[n].attribs = NULL;
+ VAL(dst) = d;
+}
+
+// Consider -vf a=b=c:d=e. This verifies "b"="c" and "d"="e" and that the
+// option names/values are correct. Try to determine whether an option
+// without '=' sets a flag, or whether it's a positional argument.
+static int get_obj_param(bstr opt_name, bstr obj_name, struct m_config *config,
+ bstr name, bstr val, int flags, int *nold,
+ bstr *out_name, bstr *out_val)
+{
+ int r;
+
+ if (!config)
+ return 0; // skip
+
+ // va.start != NULL => of the form name=val (not positional)
+ // If it's just "name", and the associated option exists and is a flag,
+ // don't accept it as positional argument.
+ if (val.start || m_config_option_requires_param(config, name) == 0) {
+ r = m_config_set_option_ext(config, name, val, flags);
+ if (r < 0) {
+ if (r == M_OPT_UNKNOWN) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: %.*s doesn't have a %.*s parameter.\n",
+ BSTR_P(opt_name), BSTR_P(obj_name), BSTR_P(name));
+ return M_OPT_UNKNOWN;
+ }
+ if (r > M_OPT_EXIT)
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: "
+ "Error while parsing %.*s parameter %.*s (%.*s)\n",
+ BSTR_P(opt_name), BSTR_P(obj_name), BSTR_P(name),
+ BSTR_P(val));
+ return r;
+ }
+ *out_name = name;
+ *out_val = val;
+ return 1;
+ } else {
+ val = name;
+ // positional fields
+ if (val.len == 0) { // Empty field, count it and go on
+ (*nold)++;
+ return 0;
+ }
+ const char *opt = m_config_get_positional_option(config, *nold);
+ if (!opt) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: %.*s has only %d "
+ "params, so you can't give more than %d unnamed params.\n",
+ BSTR_P(opt_name), BSTR_P(obj_name), *nold, *nold);
+ return M_OPT_OUT_OF_RANGE;
+ }
+ r = m_config_set_option_ext(config, bstr0(opt), val, flags);
+ if (r < 0) {
+ if (r > M_OPT_EXIT)
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: "
+ "Error while parsing %.*s parameter %s (%.*s)\n",
+ BSTR_P(opt_name), BSTR_P(obj_name), opt, BSTR_P(val));
+ return r;
+ }
+ *out_name = bstr0(opt);
+ *out_val = val;
+ (*nold)++;
+ return 1;
+ }
+}
+
+// Consider -vf a=b:c:d. This parses "b:c:d" into name/value pairs, stored as
+// linear array in *_ret. In particular, config contains what options a the
+// object takes, and verifies the option values as well.
+// If config is NULL, all parameters are accepted without checking.
+// _ret set to NULL can be used for checking-only.
+// flags can contain any M_SETOPT_* flag.
+int m_obj_parse_sub_config(struct bstr opt_name, struct bstr name,
+ struct bstr *pstr, struct m_config *config,
+ int flags, char ***ret)
+{
+ int nold = 0;
+ char **args = NULL;
+ int num_args = 0;
+ int r = 1;
+
+ if (ret) {
+ args = *ret;
+ while (args && args[num_args])
+ num_args++;
+ }
+
+ while (pstr->len > 0) {
+ bstr fname, fval;
+ r = split_subconf(opt_name, pstr, &fname, &fval);
+ if (r < 0)
+ goto exit;
+ if (bstr_equals0(fname, "help"))
+ goto print_help;
+ r = get_obj_param(opt_name, name, config, fname, fval, flags, &nold,
+ &fname, &fval);
+ if (r < 0)
+ goto exit;
+
+ if (r > 0 && ret) {
+ MP_TARRAY_APPEND(NULL, args, num_args, bstrto0(NULL, fname));
+ MP_TARRAY_APPEND(NULL, args, num_args, bstrto0(NULL, fval));
+ MP_TARRAY_APPEND(NULL, args, num_args, NULL);
+ MP_TARRAY_APPEND(NULL, args, num_args, NULL);
+ num_args -= 2;
+ }
+
+ if (!bstr_eatstart0(pstr, ":"))
+ break;
+ }
+
+ if (ret) {
+ if (num_args > 0) {
+ *ret = args;
+ args = NULL;
+ } else {
+ *ret = NULL;
+ }
+ }
+
+ goto exit;
+
+print_help: ;
+ if (config) {
+ m_config_print_option_list(config);
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Option %.*s doesn't exist.\n",
+ BSTR_P(opt_name));
+ }
+ r = M_OPT_EXIT - 1;
+
+exit:
+ free_str_list(&args);
+ return r;
+}
+
+// Characters which may appear in a filter name
+#define NAMECH "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
+
+// Parse one item, e.g. -vf a=b:c:d,e=f:g => parse a=b:c:d into "a" and "b:c:d"
+static int parse_obj_settings(struct bstr opt, struct bstr *pstr,
+ const struct m_obj_list *list,
+ m_obj_settings_t **_ret)
+{
+ int r;
+ char **plist = NULL;
+ struct m_obj_desc desc;
+ bstr label = {0};
+
+ if (bstr_eatstart0(pstr, "@")) {
+ if (!bstr_split_tok(*pstr, ":", &label, pstr)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: ':' expected after label.\n", BSTR_P(opt));
+ return M_OPT_INVALID;
+ }
+ }
+
+ bool has_param = false;
+ int idx = bstrspn(*pstr, NAMECH);
+ bstr str = bstr_splice(*pstr, 0, idx);
+ *pstr = bstr_cut(*pstr, idx);
+ // video filters use "=", VOs use ":"
+ if (bstr_eatstart0(pstr, "=") || bstr_eatstart0(pstr, ":"))
+ has_param = true;
+
+ bool legacy = false;
+ bool skip = false;
+ if (m_obj_list_find(&desc, list, str)) {
+ legacy = !desc.priv_size && list->legacy_hacks;
+ } else {
+ if (!list->allow_unknown_entries) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: %.*s doesn't exist.\n",
+ BSTR_P(opt), BSTR_P(str));
+ return M_OPT_INVALID;
+ }
+ desc = (struct m_obj_desc){0};
+ skip = true;
+ }
+
+ if (has_param) {
+ if (legacy) {
+ // Should perhaps be parsed as escape-able string. But this is a
+ // compatibility path, so it's not worth the trouble.
+ int next = bstrcspn(*pstr, ",");
+ bstr param = bstr_splice(*pstr, 0, next);
+ *pstr = bstr_cut(*pstr, next);
+ if (!bstrcmp0(param, "help")) {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "Option %.*s: %.*s has no option description.\n",
+ BSTR_P(opt), BSTR_P(str));
+ return M_OPT_EXIT - 1;
+ }
+ if (_ret) {
+ plist = talloc_zero_array(NULL, char *, 4);
+ plist[0] = talloc_strdup(NULL, "_oldargs_");
+ plist[1] = bstrto0(NULL, param);
+ }
+ } else {
+ struct m_config *config = NULL;
+ if (!skip)
+ config = m_config_from_obj_desc(NULL, &desc);
+ r = m_obj_parse_sub_config(opt, str, pstr, config,
+ M_SETOPT_CHECK_ONLY,
+ _ret ? &plist : NULL);
+ talloc_free(config);
+ if (r < 0)
+ return r;
+ }
+ }
+ if (!_ret)
+ return 1;
+
+ m_obj_settings_t item = {
+ .name = bstrto0(NULL, str),
+ .label = bstrdup0(NULL, label),
+ .attribs = plist,
+ };
+ obj_settings_list_insert_at(_ret, -1, &item);
+ return 1;
+}
+
+// Parse a single entry for -vf-del (return 0 if not applicable)
+// mark_del is bounded by the number of items in dst
+static int parse_obj_settings_del(struct bstr opt_name, struct bstr *param,
+ void *dst, bool *mark_del)
+{
+ bstr s = *param;
+ if (bstr_eatstart0(&s, "@")) {
+ // '@name:' -> parse as normal filter entry
+ // '@name,' or '@name<end>' -> parse here
+ int idx = bstrspn(s, NAMECH);
+ bstr label = bstr_splice(s, 0, idx);
+ s = bstr_cut(s, idx);
+ if (bstr_startswith0(s, ":"))
+ return 0;
+ if (dst) {
+ int label_index = obj_settings_list_find_by_label(VAL(dst), label);
+ if (label_index >= 0) {
+ mark_del[label_index] = true;
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "Option %.*s: item label @%.*s not found.\n",
+ BSTR_P(opt_name), BSTR_P(label));
+ }
+ }
+ *param = s;
+ return 1;
+ }
+
+ bstr rest;
+ long long id = bstrtoll(s, &rest, 0);
+ if (rest.len == s.len)
+ return 0;
+
+ if (dst) {
+ int num = obj_settings_list_num_items(VAL(dst));
+ if (id < 0)
+ id = num + id;
+
+ if (id >= 0 && id < num) {
+ mark_del[id] = true;
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "Option %.*s: Index %lld is out of range.\n",
+ BSTR_P(opt_name), id);
+ }
+ }
+
+ *param = rest;
+ return 1;
+}
+
+static int parse_obj_settings_list(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ int len = strlen(opt->name);
+ m_obj_settings_t *res = NULL;
+ int op = OP_NONE;
+ bool *mark_del = NULL;
+ int num_items = obj_settings_list_num_items(dst ? VAL(dst) : 0);
+ struct m_obj_list *ol = opt->priv;
+
+ assert(opt->priv);
+
+ if (opt->name[len - 1] == '*' && (name.len > len - 1)) {
+ struct bstr suffix = bstr_cut(name, len - 1);
+ if (bstrcmp0(suffix, "-add") == 0)
+ op = OP_ADD;
+ else if (bstrcmp0(suffix, "-set") == 0)
+ op = OP_NONE;
+ else if (bstrcmp0(suffix, "-pre") == 0)
+ op = OP_PRE;
+ else if (bstrcmp0(suffix, "-del") == 0)
+ op = OP_DEL;
+ else if (bstrcmp0(suffix, "-clr") == 0)
+ op = OP_CLR;
+ else if (bstrcmp0(suffix, "-toggle") == 0)
+ op = OP_TOGGLE;
+ else {
+ char prefix[len];
+ strncpy(prefix, opt->name, len - 1);
+ prefix[len - 1] = '\0';
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: unknown postfix %.*s\n"
+ "Supported postfixes are:\n"
+ " %s-set\n"
+ " Overwrite the old list with the given list\n\n"
+ " %s-add\n"
+ " Append the given list to the current list\n\n"
+ " %s-pre\n"
+ " Prepend the given list to the current list\n\n"
+ " %s-del x,y,...\n"
+ " Remove the given elements. Take the list element index (starting from 0).\n"
+ " Negative index can be used (i.e. -1 is the last element).\n"
+ " Filter names work as well.\n\n"
+ " %s-clr\n"
+ " Clear the current list.\n",
+ BSTR_P(name), BSTR_P(suffix), prefix, prefix, prefix, prefix, prefix);
+
+ return M_OPT_UNKNOWN;
+ }
+ }
+
+ if (!bstrcmp0(param, "help")) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available %s:\n", ol->description);
+ for (int n = 0; ; n++) {
+ struct m_obj_desc desc;
+ if (!ol->get_desc(&desc, n))
+ break;
+ if (!desc.hidden) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %-15s: %s\n",
+ desc.name, desc.description);
+ }
+ }
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ return M_OPT_EXIT - 1;
+ }
+
+ if (op == OP_CLR) {
+ if (dst)
+ free_obj_settings_list(dst);
+ return 0;
+ } else if (op == OP_DEL) {
+ mark_del = talloc_zero_array(NULL, bool, num_items + 1);
+ }
+
+ if (op != OP_NONE && param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ while (param.len > 0) {
+ int r = 0;
+ if (op == OP_DEL)
+ r = parse_obj_settings_del(name, &param, dst, mark_del);
+ if (r == 0) {
+ r = parse_obj_settings(name, &param, ol, dst ? &res : NULL);
+ }
+ if (r < 0)
+ return r;
+ if (param.len > 0) {
+ const char sep[2] = {OPTION_LIST_SEPARATOR, 0};
+ if (!bstr_eatstart0(&param, sep))
+ return M_OPT_INVALID;
+ if (param.len == 0) {
+ if (!ol->allow_trailer)
+ return M_OPT_INVALID;
+ if (dst) {
+ m_obj_settings_t item = {
+ .name = talloc_strdup(NULL, ""),
+ };
+ obj_settings_list_insert_at(&res, -1, &item);
+ }
+ }
+ }
+ }
+
+ if (dst) {
+ m_obj_settings_t *list = VAL(dst);
+ if (op == OP_PRE) {
+ int prepend_counter = 0;
+ for (int n = 0; res && res[n].name; n++) {
+ int label = obj_settings_list_find_by_label0(list, res[n].label);
+ if (label < 0) {
+ obj_settings_list_insert_at(&list, prepend_counter, &res[n]);
+ prepend_counter++;
+ } else {
+ // Prefer replacement semantics, instead of actually
+ // prepending.
+ obj_setting_free(&list[label]);
+ list[label] = res[n];
+ }
+ }
+ talloc_free(res);
+ } else if (op == OP_ADD) {
+ for (int n = 0; res && res[n].name; n++) {
+ int label = obj_settings_list_find_by_label0(list, res[n].label);
+ if (label < 0) {
+ obj_settings_list_insert_at(&list, -1, &res[n]);
+ } else {
+ // Prefer replacement semantics, instead of actually
+ // appending.
+ obj_setting_free(&list[label]);
+ list[label] = res[n];
+ }
+ }
+ talloc_free(res);
+ } else if (op == OP_TOGGLE) {
+ for (int n = 0; res && res[n].name; n++) {
+ int found = obj_settings_find_by_content(list, &res[n]);
+ if (found < 0) {
+ obj_settings_list_insert_at(&list, -1, &res[n]);
+ } else {
+ obj_settings_list_del_at(&list, found);
+ obj_setting_free(&res[n]);
+ }
+ }
+ talloc_free(res);
+ } else if (op == OP_DEL) {
+ for (int n = num_items - 1; n >= 0; n--) {
+ if (mark_del[n])
+ obj_settings_list_del_at(&list, n);
+ }
+ for (int n = 0; res && res[n].name; n++) {
+ int found = obj_settings_find_by_content(list, &res[n]);
+ if (found < 0) {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "Option %.*s: Item not found\n", BSTR_P(name));
+ } else {
+ obj_settings_list_del_at(&list, found);
+ }
+ }
+ free_obj_settings_list(&res);
+ } else {
+ assert(op == OP_NONE);
+ free_obj_settings_list(&list);
+ list = res;
+ }
+ VAL(dst) = list;
+ }
+
+ talloc_free(mark_del);
+ return 1;
+}
+
+const m_option_type_t m_option_type_obj_settings_list = {
+ .name = "Object settings list",
+ .size = sizeof(m_obj_settings_t *),
+ .flags = M_OPT_TYPE_DYNAMIC | M_OPT_TYPE_ALLOW_WILDCARD,
+ .parse = parse_obj_settings_list,
+ .copy = copy_obj_settings_list,
+ .free = free_obj_settings_list,
+};
diff --git a/mpvcore/m_option.h b/mpvcore/m_option.h
new file mode 100644
index 0000000000..89f3caa652
--- /dev/null
+++ b/mpvcore/m_option.h
@@ -0,0 +1,646 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_M_OPTION_H
+#define MPLAYER_M_OPTION_H
+
+#include <string.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "config.h"
+#include "core/bstr.h"
+#include "audio/chmap.h"
+
+// m_option allows to parse, print and copy data of various types.
+
+typedef struct m_option_type m_option_type_t;
+typedef struct m_option m_option_t;
+struct m_config;
+
+///////////////////////////// Options types declarations ////////////////////
+
+// Simple types
+extern const m_option_type_t m_option_type_flag;
+extern const m_option_type_t m_option_type_store;
+extern const m_option_type_t m_option_type_float_store;
+extern const m_option_type_t m_option_type_int;
+extern const m_option_type_t m_option_type_int64;
+extern const m_option_type_t m_option_type_intpair;
+extern const m_option_type_t m_option_type_float;
+extern const m_option_type_t m_option_type_double;
+extern const m_option_type_t m_option_type_string;
+extern const m_option_type_t m_option_type_string_list;
+extern const m_option_type_t m_option_type_time;
+extern const m_option_type_t m_option_type_rel_time;
+extern const m_option_type_t m_option_type_choice;
+
+extern const m_option_type_t m_option_type_print;
+extern const m_option_type_t m_option_type_print_func;
+extern const m_option_type_t m_option_type_print_func_param;
+extern const m_option_type_t m_option_type_subconfig;
+extern const m_option_type_t m_option_type_subconfig_struct;
+extern const m_option_type_t m_option_type_imgfmt;
+extern const m_option_type_t m_option_type_fourcc;
+extern const m_option_type_t m_option_type_afmt;
+extern const m_option_type_t m_option_type_color;
+extern const m_option_type_t m_option_type_geometry;
+extern const m_option_type_t m_option_type_size_box;
+extern const m_option_type_t m_option_type_chmap;
+
+// Callback used by m_option_type_print_func options.
+typedef int (*m_opt_func_full_t)(const m_option_t *, const char *, const char *);
+
+enum m_rel_time_type {
+ REL_TIME_NONE,
+ REL_TIME_ABSOLUTE,
+ REL_TIME_NEGATIVE,
+ REL_TIME_PERCENT,
+ REL_TIME_CHAPTER,
+};
+
+struct m_rel_time {
+ double pos;
+ enum m_rel_time_type type;
+};
+
+struct m_color {
+ uint8_t r, g, b, a;
+};
+
+struct m_geometry {
+ int x, y, w, h;
+ bool xy_valid : 1, wh_valid : 1;
+ bool w_per : 1, h_per : 1;
+ bool x_sign : 1, y_sign : 1, x_per : 1, y_per : 1;
+};
+
+void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
+ int scrw, int scrh, struct m_geometry *gm);
+
+struct m_obj_desc {
+ // Name which will be used in the option string
+ const char *name;
+ // Will be printed when "help" is passed
+ const char *description;
+ // Size of the private struct
+ int priv_size;
+ // If not NULL, default values for private struct
+ const void *priv_defaults;
+ // Options which refer to members in the private struct
+ const struct m_option *options;
+ // For free use by the implementer of m_obj_list.get_desc
+ const void *p;
+ // If not NULL, options which should be set before applying other options.
+ // This member is usually set my m_obj_list_find() only.
+ // Only works if options is not NULL.
+ const char *init_options;
+ // Don't list entries with "help"
+ bool hidden;
+};
+
+// Extra definition needed for \ref m_option_type_obj_settings_list options.
+struct m_obj_list {
+ bool (*get_desc)(struct m_obj_desc *dst, int index);
+ const char *description;
+ // Can be set to a NULL terminated array of aliases
+ const char *aliases[4][5];
+ // Allow a trailing ",", which adds an entry with name=""
+ bool allow_trailer;
+ // Allow unknown entries, for which a dummy entry is inserted, and whose
+ // options are skipped and ignored.
+ bool allow_unknown_entries;
+ // If object has no options set, assume it parses options on its own.
+ bool legacy_hacks;
+};
+
+// Find entry by name
+bool m_obj_list_find(struct m_obj_desc *dst, const struct m_obj_list *list,
+ bstr name);
+
+// The data type used by \ref m_option_type_obj_settings_list.
+typedef struct m_obj_settings {
+ // Type of the object.
+ char *name;
+ // Optional user-defined name.
+ char *label;
+ // NULL terminated array of parameter/value pairs.
+ char **attribs;
+} m_obj_settings_t;
+
+// A parser to set up a list of objects.
+/** It creates a NULL terminated array \ref m_obj_settings. The option priv
+ * field (\ref m_option::priv) must point to a \ref m_obj_list_t describing
+ * the available object types.
+ */
+extern const m_option_type_t m_option_type_obj_settings_list;
+
+int m_obj_parse_sub_config(struct bstr opt_name, struct bstr name,
+ struct bstr *pstr, struct m_config *config,
+ int flags, char ***ret);
+
+struct m_opt_choice_alternatives {
+ char *name;
+ int value;
+};
+
+// For OPT_STRING_VALIDATE(). Behaves like m_option_type.parse().
+typedef int (*m_opt_string_validate_fn)(const m_option_t *opt, struct bstr name,
+ struct bstr param);
+
+// m_option.priv points to this if M_OPT_TYPE_USE_SUBSTRUCT is used
+struct m_sub_options {
+ const struct m_option *opts;
+ size_t size;
+ const void *defaults;
+};
+
+// FIXME: backward compatibility
+#define CONF_TYPE_FLAG (&m_option_type_flag)
+#define CONF_TYPE_STORE (&m_option_type_store)
+#define CONF_TYPE_INT (&m_option_type_int)
+#define CONF_TYPE_INT64 (&m_option_type_int64)
+#define CONF_TYPE_FLOAT (&m_option_type_float)
+#define CONF_TYPE_DOUBLE (&m_option_type_double)
+#define CONF_TYPE_STRING (&m_option_type_string)
+#define CONF_TYPE_PRINT (&m_option_type_print)
+#define CONF_TYPE_PRINT_FUNC (&m_option_type_print_func)
+#define CONF_TYPE_SUBCONFIG (&m_option_type_subconfig)
+#define CONF_TYPE_STRING_LIST (&m_option_type_string_list)
+#define CONF_TYPE_IMGFMT (&m_option_type_imgfmt)
+#define CONF_TYPE_FOURCC (&m_option_type_fourcc)
+#define CONF_TYPE_AFMT (&m_option_type_afmt)
+#define CONF_TYPE_OBJ_SETTINGS_LIST (&m_option_type_obj_settings_list)
+#define CONF_TYPE_TIME (&m_option_type_time)
+#define CONF_TYPE_CHOICE (&m_option_type_choice)
+#define CONF_TYPE_INT_PAIR (&m_option_type_intpair)
+
+// Possible option values. Code is allowed to access option data without going
+// through this union. It serves for self-documentation and to get minimal
+// size/alignment requirements for option values in general.
+union m_option_value {
+ int flag; // not the C type "bool"!
+ int store;
+ float float_store;
+ int int_;
+ int64_t int64;
+ int intpair[2];
+ float float_;
+ double double_;
+ char *string;
+ char **string_list;
+ int imgfmt;
+ unsigned int fourcc;
+ int afmt;
+ m_obj_settings_t *obj_settings_list;
+ double time;
+ struct m_rel_time rel_time;
+ struct m_color color;
+ struct m_geometry geometry;
+ struct m_geometry size_box;
+ struct mp_chmap chmap;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+// Option type description
+struct m_option_type {
+ const char *name;
+ // Size needed for the data.
+ unsigned int size;
+ // One of M_OPT_TYPE*.
+ unsigned int flags;
+
+ // Parse the data from a string.
+ /** It is the only required function, all others can be NULL.
+ *
+ * \param opt The option that is parsed.
+ * \param name The full option name.
+ * \param param The parameter to parse.
+ * may not be an argument meant for this option
+ * \param dst Pointer to the memory where the data should be written.
+ * If NULL the parameter validity should still be checked.
+ * \return On error a negative value is returned, on success the number
+ * of arguments consumed. For details see \ref OptionParserReturn.
+ */
+ int (*parse)(const m_option_t *opt, struct bstr name, struct bstr param,
+ void *dst);
+
+ // Print back a value in string form.
+ /** \param opt The option to print.
+ * \param val Pointer to the memory holding the data to be printed.
+ * \return An allocated string containing the text value or (void*)-1
+ * on error.
+ */
+ char *(*print)(const m_option_t *opt, const void *val);
+
+ // Print the value in a human readable form. Unlike print(), it doesn't
+ // necessarily return the exact value, and is generally not parseable with
+ // parse().
+ char *(*pretty_print)(const m_option_t *opt, const void *val);
+
+ // Copy data between two locations. Deep copy if the data has pointers.
+ /** \param opt The option to copy.
+ * \param dst Pointer to the destination memory.
+ * \param src Pointer to the source memory.
+ */
+ void (*copy)(const m_option_t *opt, void *dst, const void *src);
+
+ // Free the data allocated for a save slot.
+ /** This is only needed for dynamic types like strings.
+ * \param dst Pointer to the data, usually a pointer that should be freed and
+ * set to NULL.
+ */
+ void (*free)(void *dst);
+
+ // Add the value add to the value in val. For types that are not numeric,
+ // add gives merely the direction. The wrap parameter determines whether
+ // the value is clipped, or wraps around to the opposite max/min.
+ void (*add)(const m_option_t *opt, void *val, double add, bool wrap);
+
+ // Clamp the value in val to the option's valid value range.
+ // Return values:
+ // M_OPT_OUT_OF_RANGE: val was invalid, and modified (clamped) to be valid
+ // M_OPT_INVALID: val was invalid, and can't be made valid
+ // 0: val was already valid and is unchanged
+ int (*clamp)(const m_option_t *opt, void *val);
+};
+
+// Option description
+struct m_option {
+ // Option name.
+ const char *name;
+
+ // Reserved for higher level APIs, it shouldn't be used by parsers.
+ /** The suboption parser and func types do use it. They should instead
+ * use the priv field but this was inherited from older versions of the
+ * config code.
+ */
+ void *p;
+
+ // Option type.
+ const m_option_type_t *type;
+
+ // See \ref OptionFlags.
+ unsigned int flags;
+
+ // \brief Mostly useful for numeric types, the \ref M_OPT_MIN flags must
+ // also be set.
+ double min;
+
+ // \brief Mostly useful for numeric types, the \ref M_OPT_MAX flags must
+ // also be set.
+ double max;
+
+ // Type dependent data (for all kinds of extended settings).
+ /** This used to be a function pointer to hold a 'reverse to defaults' func.
+ * Now it can be used to pass any type of extra args needed by the parser.
+ */
+ void *priv;
+
+ int new;
+
+ int offset;
+
+ // Initialize variable to given default before parsing options
+ void *defval;
+};
+
+
+// The option has a minimum set in \ref m_option::min.
+#define M_OPT_MIN (1 << 0)
+
+// The option has a maximum set in \ref m_option::max.
+#define M_OPT_MAX (1 << 1)
+
+// The option has a minimum and maximum in m_option::min and m_option::max.
+#define M_OPT_RANGE (M_OPT_MIN | M_OPT_MAX)
+
+// The option is forbidden in config files.
+#define M_OPT_NOCFG (1 << 2)
+
+// This option can't be set per-file when used with struct m_config.
+#define M_OPT_GLOBAL (1 << 4)
+
+// The option should be set during command line pre-parsing
+#define M_OPT_PRE_PARSE (1 << 6)
+
+// See M_OPT_TYPE_OPTIONAL_PARAM.
+#define M_OPT_OPTIONAL_PARAM (1 << 10)
+
+// Parse C-style escapes like "\n" (for CONF_TYPE_STRING only)
+#define M_OPT_PARSE_ESCAPES (1 << 11)
+
+// These are kept for compatibility with older code.
+#define CONF_MIN M_OPT_MIN
+#define CONF_MAX M_OPT_MAX
+#define CONF_RANGE M_OPT_RANGE
+#define CONF_NOCFG M_OPT_NOCFG
+#define CONF_GLOBAL M_OPT_GLOBAL
+#define CONF_PRE_PARSE M_OPT_PRE_PARSE
+
+// These flags are used to describe special parser capabilities or behavior.
+
+// Suboption parser flag.
+/** When this flag is set, m_option::p should point to another m_option
+ * array. Only the parse function will be called. If dst is set, it should
+ * create/update an array of char* containg opt/val pairs. The options in
+ * the child array will then be set automatically by the \ref Config.
+ * Also note that suboptions may be directly accessed by using
+ * -option:subopt blah.
+ */
+#define M_OPT_TYPE_HAS_CHILD (1 << 0)
+
+// Wildcard matching flag.
+/** If set the option type has a use for option names ending with a *
+ * (used for -aa*), this only affects the option name matching.
+ */
+#define M_OPT_TYPE_ALLOW_WILDCARD (1 << 1)
+
+// Dynamic data type.
+/** This flag indicates that the data is dynamically allocated (m_option::p
+ * points to a pointer). It enables a little hack in the \ref Config wich
+ * replaces the initial value of such variables with a dynamic copy in case
+ * the initial value is statically allocated (pretty common with strings).
+ */
+#define M_OPT_TYPE_DYNAMIC (1 << 2)
+
+// The parameter is optional and by default no parameter is preferred. If
+// ambiguous syntax is used ("--opt value"), the command line parser will
+// assume that the argument takes no parameter. In config files, these
+// options can be used without "=" and value.
+#define M_OPT_TYPE_OPTIONAL_PARAM (1 << 3)
+
+// modify M_OPT_TYPE_HAS_CHILD so that m_option::p points to
+// struct m_sub_options, instead of a direct m_option array.
+#define M_OPT_TYPE_USE_SUBSTRUCT (1 << 4)
+
+///////////////////////////// Parser flags /////////////////////////////////
+
+// OptionParserReturn
+//
+// On success parsers return a number >= 0.
+//
+// To indicate that MPlayer should exit without playing anything,
+// parsers return M_OPT_EXIT minus the number of parameters they
+// consumed: \ref M_OPT_EXIT or \ref M_OPT_EXIT-1.
+//
+// On error one of the following (negative) error codes is returned:
+
+// For use by higher level APIs when the option name is invalid.
+#define M_OPT_UNKNOWN -1
+
+// Returned when a parameter is needed but wasn't provided.
+#define M_OPT_MISSING_PARAM -2
+
+// Returned when the given parameter couldn't be parsed.
+#define M_OPT_INVALID -3
+
+// Returned if the value is "out of range". The exact meaning may
+// vary from type to type.
+#define M_OPT_OUT_OF_RANGE -4
+
+// The option doesn't take a parameter.
+#define M_OPT_DISALLOW_PARAM -5
+
+// Returned if the parser failed for any other reason than a bad parameter.
+#define M_OPT_PARSER_ERR -6
+
+// Returned when MPlayer should exit. Used by various help stuff.
+/** M_OPT_EXIT must be the lowest number on this list.
+ */
+#define M_OPT_EXIT -7
+
+char *m_option_strerror(int code);
+
+// Find the option matching the given name in the list.
+/** \ingroup Options
+ * This function takes the possible wildcards into account (see
+ * \ref M_OPT_TYPE_ALLOW_WILDCARD).
+ *
+ * \param list Pointer to an array of \ref m_option.
+ * \param name Name of the option.
+ * \return The matching option or NULL.
+ */
+const m_option_t *m_option_list_find(const m_option_t *list, const char *name);
+
+// Helper to parse options, see \ref m_option_type::parse.
+static inline int m_option_parse(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ return opt->type->parse(opt, name, param, dst);
+}
+
+// Helper to print options, see \ref m_option_type::print.
+static inline char *m_option_print(const m_option_t *opt, const void *val_ptr)
+{
+ if (opt->type->print)
+ return opt->type->print(opt, val_ptr);
+ else
+ return NULL;
+}
+
+static inline char *m_option_pretty_print(const m_option_t *opt,
+ const void *val_ptr)
+{
+ if (opt->type->pretty_print)
+ return opt->type->pretty_print(opt, val_ptr);
+ else
+ return m_option_print(opt, val_ptr);
+}
+
+// Helper around \ref m_option_type::copy.
+static inline void m_option_copy(const m_option_t *opt, void *dst,
+ const void *src)
+{
+ if (opt->type->copy)
+ opt->type->copy(opt, dst, src);
+}
+
+// Helper around \ref m_option_type::free.
+static inline void m_option_free(const m_option_t *opt, void *dst)
+{
+ if (opt->type->free)
+ opt->type->free(dst);
+}
+
+int m_option_required_params(const m_option_t *opt);
+
+// Cause a compilation warning if typeof(expr) != type.
+// Should be used with pointer types only.
+#define MP_EXPECT_TYPE(type, expr) (0 ? (type)0 : (expr))
+
+// This behaves like offsetof(type, member), but will cause a compilation
+// warning if typeof(member) != expected_member_type.
+// It uses some trickery to make it compile as expression.
+#define MP_CHECKED_OFFSETOF(type, member, expected_member_type) \
+ (offsetof(type, member) + (0 && MP_EXPECT_TYPE(expected_member_type*, \
+ &((type*)0)->member)))
+
+
+#define OPTION_LIST_SEPARATOR ','
+
+#if HAVE_DOS_PATHS
+#define OPTION_PATH_SEPARATOR ';'
+#else
+#define OPTION_PATH_SEPARATOR ':'
+#endif
+
+#define OPTDEF_STR(s) .defval = (void *)&(char * const){s}
+#define OPTDEF_INT(i) .defval = (void *)&(const int){i}
+
+#define OPT_GENERAL(ctype, optname, varname, flagv, ...) \
+ {.name = optname, .flags = flagv, .new = 1, \
+ .offset = MP_CHECKED_OFFSETOF(OPT_BASE_STRUCT, varname, ctype), \
+ __VA_ARGS__}
+
+#define OPT_GENERAL_NOTYPE(optname, varname, flagv, ...) \
+ {.name = optname, .flags = flagv, .new = 1, \
+ .offset = offsetof(OPT_BASE_STRUCT, varname), \
+ __VA_ARGS__}
+
+#define OPT_HELPER_REMOVEPAREN(...) __VA_ARGS__
+
+/* The OPT_FLAG_CONSTANTS->OPT_FLAG_CONSTANTS_ kind of redirection exists to
+ * make the code fully standard-conforming: the C standard requires that
+ * __VA_ARGS__ has at least one argument (though GCC for example would accept
+ * 0). Thus the first OPT_FLAG_CONSTANTS is a wrapper which just adds one
+ * argument to ensure __VA_ARGS__ is not empty when calling the next macro.
+ */
+
+#define OPT_FLAG(...) \
+ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_flag, .max = 1)
+
+#define OPT_FLAG_CONSTANTS_(optname, varname, flags, offvalue, value, ...) \
+ OPT_GENERAL(int, optname, varname, flags, \
+ .min = offvalue, .max = value, __VA_ARGS__)
+#define OPT_FLAG_CONSTANTS(...) \
+ OPT_FLAG_CONSTANTS_(__VA_ARGS__, .type = &m_option_type_flag)
+
+#define OPT_FLAG_STORE(optname, varname, flags, value) \
+ OPT_GENERAL(int, optname, varname, flags, .max = value, \
+ .type = &m_option_type_store)
+
+#define OPT_FLOAT_STORE(optname, varname, flags, value) \
+ OPT_GENERAL(float, optname, varname, flags, .max = value, \
+ .type = &m_option_type_float_store)
+
+#define OPT_STRINGLIST(...) \
+ OPT_GENERAL(char**, __VA_ARGS__, .type = &m_option_type_string_list)
+
+#define OPT_PATHLIST(...) \
+ OPT_GENERAL(char**, __VA_ARGS__, .type = &m_option_type_string_list, \
+ .priv = (void *)&(const char){OPTION_PATH_SEPARATOR})
+
+#define OPT_INT(...) \
+ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_int)
+
+#define OPT_INT64(...) \
+ OPT_GENERAL(int64_t, __VA_ARGS__, .type = &m_option_type_int64)
+
+#define OPT_RANGE_(ctype, optname, varname, flags, minval, maxval, ...) \
+ OPT_GENERAL(ctype, optname, varname, (flags) | CONF_RANGE, \
+ .min = minval, .max = maxval, __VA_ARGS__)
+
+#define OPT_INTRANGE(...) \
+ OPT_RANGE_(int, __VA_ARGS__, .type = &m_option_type_int)
+
+#define OPT_FLOATRANGE(...) \
+ OPT_RANGE_(float, __VA_ARGS__, .type = &m_option_type_float)
+
+#define OPT_INTPAIR(...) \
+ OPT_GENERAL_NOTYPE(__VA_ARGS__, .type = &m_option_type_intpair)
+
+#define OPT_FLOAT(...) \
+ OPT_GENERAL(float, __VA_ARGS__, .type = &m_option_type_float)
+
+#define OPT_DOUBLE(...) \
+ OPT_GENERAL(double, __VA_ARGS__, .type = &m_option_type_double)
+
+#define OPT_STRING(...) \
+ OPT_GENERAL(char*, __VA_ARGS__, .type = &m_option_type_string)
+
+#define OPT_SETTINGSLIST(optname, varname, flags, objlist) \
+ OPT_GENERAL(m_obj_settings_t*, optname, varname, flags, \
+ .type = &m_option_type_obj_settings_list, \
+ .priv = (void*)MP_EXPECT_TYPE(const struct m_obj_list*, objlist))
+
+#define OPT_IMAGEFORMAT(...) \
+ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_imgfmt)
+
+#define OPT_AUDIOFORMAT(...) \
+ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_afmt)
+
+#define OPT_CHMAP(...) \
+ OPT_GENERAL(struct mp_chmap, __VA_ARGS__, .type = &m_option_type_chmap)
+
+
+#define M_CHOICES(choices) \
+ .priv = (void *)&(const struct m_opt_choice_alternatives[]){ \
+ OPT_HELPER_REMOVEPAREN choices, {NULL}}
+
+#define OPT_CHOICE(...) \
+ OPT_CHOICE_(__VA_ARGS__, .type = &m_option_type_choice)
+#define OPT_CHOICE_(optname, varname, flags, choices, ...) \
+ OPT_GENERAL(int, optname, varname, flags, M_CHOICES(choices), __VA_ARGS__)
+
+// Union of choices and an int range. The choice values can be included in the
+// int range, or be completely separate - both works.
+#define OPT_CHOICE_OR_INT_(optname, varname, flags, minval, maxval, choices, ...) \
+ OPT_GENERAL(int, optname, varname, (flags) | CONF_RANGE, \
+ .min = minval, .max = maxval, \
+ M_CHOICES(choices), __VA_ARGS__)
+#define OPT_CHOICE_OR_INT(...) \
+ OPT_CHOICE_OR_INT_(__VA_ARGS__, .type = &m_option_type_choice)
+
+#define OPT_TIME(...) \
+ OPT_GENERAL(double, __VA_ARGS__, .type = &m_option_type_time)
+
+#define OPT_REL_TIME(...) \
+ OPT_GENERAL(struct m_rel_time, __VA_ARGS__, .type = &m_option_type_rel_time)
+
+#define OPT_COLOR(...) \
+ OPT_GENERAL(struct m_color, __VA_ARGS__, .type = &m_option_type_color)
+
+#define OPT_GEOMETRY(...) \
+ OPT_GENERAL(struct m_geometry, __VA_ARGS__, .type = &m_option_type_geometry)
+
+#define OPT_SIZE_BOX(...) \
+ OPT_GENERAL(struct m_geometry, __VA_ARGS__, .type = &m_option_type_size_box)
+
+#define OPT_TRACKCHOICE(name, var) \
+ OPT_CHOICE_OR_INT(name, var, 0, 0, 8190, ({"no", -2}, {"auto", -1}))
+
+#define OPT_STRING_VALIDATE_(optname, varname, flags, validate_fn, ...) \
+ OPT_GENERAL(char*, optname, varname, flags, __VA_ARGS__, \
+ .priv = MP_EXPECT_TYPE(m_opt_string_validate_fn, validate_fn))
+#define OPT_STRING_VALIDATE(...) \
+ OPT_STRING_VALIDATE_(__VA_ARGS__, .type = &m_option_type_string)
+
+// subconf must have the type struct m_sub_options.
+// All sub-options are prefixed with "name-" and are added to the current
+// (containing) option list.
+// If name is "", add the sub-options directly instead.
+// varname refers to the field, that must be a pointer to a field described by
+// the subconf struct.
+#define OPT_SUBSTRUCT(name, varname, subconf, flagv) \
+ OPT_GENERAL_NOTYPE(name, varname, flagv, \
+ .type = &m_option_type_subconfig_struct, \
+ .priv = (void*)&subconf)
+
+#endif /* MPLAYER_M_OPTION_H */
diff --git a/mpvcore/m_property.c b/mpvcore/m_property.c
new file mode 100644
index 0000000000..f334b6fe1f
--- /dev/null
+++ b/mpvcore/m_property.c
@@ -0,0 +1,369 @@
+/*
+ * 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.
+ */
+
+/// \file
+/// \ingroup Properties
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include <libavutil/common.h>
+
+#include "talloc.h"
+#include "core/m_option.h"
+#include "m_property.h"
+#include "core/mp_msg.h"
+#include "core/mp_common.h"
+
+const struct m_option_type m_option_type_dummy = {
+ .name = "Unknown",
+ .flags = M_OPT_TYPE_ALLOW_WILDCARD, // make "vf*" property work
+};
+
+struct legacy_prop {
+ const char *old, *new;
+};
+static const struct legacy_prop legacy_props[] = {
+ {"switch_video", "video"},
+ {"switch_audio", "audio"},
+ {"switch_program", "program"},
+ {"framedropping", "framedrop"},
+ {"osdlevel", "osd-level"},
+ {0}
+};
+
+static bool translate_legacy_property(const char *name, char *buffer,
+ size_t buffer_size)
+{
+ if (strlen(name) + 1 > buffer_size)
+ return false;
+
+ const char *old_name = name;
+
+ for (int n = 0; legacy_props[n].new; n++) {
+ if (strcmp(name, legacy_props[n].old) == 0) {
+ name = legacy_props[n].new;
+ break;
+ }
+ }
+
+ snprintf(buffer, buffer_size, "%s", name);
+
+ // Old names used "_" instead of "-"
+ for (int n = 0; buffer[n]; n++) {
+ if (buffer[n] == '_')
+ buffer[n] = '-';
+ }
+
+ if (strcmp(old_name, buffer) != 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Warning: property '%s' is deprecated, "
+ "replaced with '%s'. Fix your input.conf!\n", old_name, buffer);
+ }
+
+ return true;
+}
+
+static int do_action(const m_option_t *prop_list, const char *name,
+ int action, void *arg, void *ctx)
+{
+ const char *sep;
+ const m_option_t *prop;
+ struct m_property_action_arg ka;
+ if ((sep = strchr(name, '/')) && sep[1]) {
+ int len = sep - name;
+ char base[len + 1];
+ memcpy(base, name, len);
+ base[len] = 0;
+ prop = m_option_list_find(prop_list, base);
+ ka = (struct m_property_action_arg) {
+ .key = sep + 1,
+ .action = action,
+ .arg = arg,
+ };
+ action = M_PROPERTY_KEY_ACTION;
+ arg = &ka;
+ } else
+ prop = m_option_list_find(prop_list, name);
+ if (!prop)
+ return M_PROPERTY_UNKNOWN;
+ int (*control)(const m_option_t*, int, void*, void*) = prop->p;
+ int r = control(prop, action, arg, ctx);
+ if (action == M_PROPERTY_GET_TYPE && r < 0 &&
+ prop->type != &m_option_type_dummy)
+ {
+ *(struct m_option *)arg = *prop;
+ return M_PROPERTY_OK;
+ }
+ return r;
+}
+
+int m_property_do(const m_option_t *prop_list, const char *in_name,
+ int action, void *arg, void *ctx)
+{
+ union m_option_value val = {0};
+ int r;
+
+ char name[64];
+ if (!translate_legacy_property(in_name, name, sizeof(name)))
+ return M_PROPERTY_UNKNOWN;
+
+ struct m_option opt = {0};
+ r = do_action(prop_list, name, M_PROPERTY_GET_TYPE, &opt, ctx);
+ if (r <= 0)
+ return r;
+ assert(opt.type);
+
+ switch (action) {
+ case M_PROPERTY_PRINT: {
+ if ((r = do_action(prop_list, name, M_PROPERTY_PRINT, arg, ctx)) >= 0)
+ return r;
+ // Fallback to m_option
+ if ((r = do_action(prop_list, name, M_PROPERTY_GET, &val, ctx)) <= 0)
+ return r;
+ char *str = m_option_pretty_print(&opt, &val);
+ m_option_free(&opt, &val);
+ *(char **)arg = str;
+ return str != NULL;
+ }
+ case M_PROPERTY_GET_STRING: {
+ if ((r = do_action(prop_list, name, M_PROPERTY_GET, &val, ctx)) <= 0)
+ return r;
+ char *str = m_option_print(&opt, &val);
+ m_option_free(&opt, &val);
+ *(char **)arg = str;
+ return str != NULL;
+ }
+ case M_PROPERTY_SET_STRING: {
+ // (reject 0 return value: success, but empty string with flag)
+ if (m_option_parse(&opt, bstr0(name), bstr0(arg), &val) <= 0)
+ return M_PROPERTY_ERROR;
+ r = do_action(prop_list, name, M_PROPERTY_SET, &val, ctx);
+ m_option_free(&opt, &val);
+ return r;
+ }
+ case M_PROPERTY_SWITCH: {
+ struct m_property_switch_arg *sarg = arg;
+ if ((r = do_action(prop_list, name, M_PROPERTY_SWITCH, arg, ctx)) !=
+ M_PROPERTY_NOT_IMPLEMENTED)
+ return r;
+ // Fallback to m_option
+ if (!opt.type->add)
+ return M_PROPERTY_NOT_IMPLEMENTED;
+ if ((r = do_action(prop_list, name, M_PROPERTY_GET, &val, ctx)) <= 0)
+ return r;
+ opt.type->add(&opt, &val, sarg->inc, sarg->wrap);
+ r = do_action(prop_list, name, M_PROPERTY_SET, &val, ctx);
+ m_option_free(&opt, &val);
+ return r;
+ }
+ case M_PROPERTY_SET: {
+ if (!opt.type->clamp) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Property '%s' without clamp().\n",
+ name);
+ } else {
+ m_option_copy(&opt, &val, arg);
+ r = opt.type->clamp(&opt, arg);
+ m_option_free(&opt, &val);
+ if (r != 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR,
+ "Property '%s': invalid value.\n", name);
+ return M_PROPERTY_ERROR;
+ }
+ }
+ return do_action(prop_list, name, M_PROPERTY_SET, arg, ctx);
+ }
+ default:
+ return do_action(prop_list, name, action, arg, ctx);
+ }
+}
+
+static int m_property_do_bstr(const m_option_t *prop_list, bstr name,
+ int action, void *arg, void *ctx)
+{
+ char name0[64];
+ if (name.len >= sizeof(name0))
+ return M_PROPERTY_UNKNOWN;
+ snprintf(name0, sizeof(name0), "%.*s", BSTR_P(name));
+ return m_property_do(prop_list, name0, action, arg, ctx);
+}
+
+static void append_str(char **s, int *len, bstr append)
+{
+ MP_TARRAY_GROW(NULL, *s, *len + append.len);
+ memcpy(*s + *len, append.start, append.len);
+ *len = *len + append.len;
+}
+
+char *m_properties_expand_string(const m_option_t *prop_list, char *str0,
+ void *ctx)
+{
+ char *ret = NULL;
+ int ret_len = 0;
+ bool skip = false;
+ int level = 0, skip_level = 0;
+ bstr str = bstr0(str0);
+
+ while (str.len) {
+ if (level > 0 && bstr_eatstart0(&str, "}")) {
+ if (skip && level <= skip_level)
+ skip = false;
+ level--;
+ } else if (bstr_startswith0(str, "${") && bstr_find0(str, "}") >= 0) {
+ str = bstr_cut(str, 2);
+ level++;
+
+ // Assume ":" and "}" can't be part of the property name
+ // => if ":" comes before "}", it must be for the fallback
+ int term_pos = bstrcspn(str, ":}");
+ bstr name = bstr_splice(str, 0, term_pos < 0 ? str.len : term_pos);
+ str = bstr_cut(str, term_pos);
+ bool have_fallback = bstr_eatstart0(&str, ":");
+
+ if (!skip) {
+ bool cond_yes = bstr_eatstart0(&name, "?");
+ bool cond_no = !cond_yes && bstr_eatstart0(&name, "!");
+ bool raw = bstr_eatstart0(&name, "=");
+ int method = (raw || cond_yes || cond_no)
+ ? M_PROPERTY_GET_STRING : M_PROPERTY_PRINT;
+
+ char *s = NULL;
+ int r = m_property_do_bstr(prop_list, name, method, &s, ctx);
+ if (cond_yes || cond_no) {
+ skip = (!!s != cond_yes);
+ } else {
+ skip = !!s;
+ char *append = s;
+ if (!s && !have_fallback && !raw) {
+ append = r == M_PROPERTY_UNAVAILABLE
+ ? "(unavailable)" : "(error)";
+ }
+ append_str(&ret, &ret_len, bstr0(append));
+ }
+ talloc_free(s);
+ if (skip)
+ skip_level = level;
+ }
+ } else if (level == 0 && bstr_eatstart0(&str, "$>")) {
+ append_str(&ret, &ret_len, str);
+ break;
+ } else {
+ char c;
+
+ // Other combinations, e.g. "$x", are added verbatim
+ if (bstr_eatstart0(&str, "$$")) {
+ c = '$';
+ } else if (bstr_eatstart0(&str, "$}")) {
+ c = '}';
+ } else {
+ c = str.start[0];
+ str = bstr_cut(str, 1);
+ }
+
+ if (!skip)
+ MP_TARRAY_APPEND(NULL, ret, ret_len, c);
+ }
+ }
+
+ MP_TARRAY_APPEND(NULL, ret, ret_len, '\0');
+ return ret;
+}
+
+void m_properties_print_help_list(const m_option_t *list)
+{
+ char min[50], max[50];
+ int i, count = 0;
+
+ mp_tmsg(MSGT_CFGPARSER, MSGL_INFO,
+ "\n Name Type Min Max\n\n");
+ for (i = 0; list[i].name; i++) {
+ const m_option_t *opt = &list[i];
+ if (opt->flags & M_OPT_MIN)
+ sprintf(min, "%-8.0f", opt->min);
+ else
+ strcpy(min, "No");
+ if (opt->flags & M_OPT_MAX)
+ sprintf(max, "%-8.0f", opt->max);
+ else
+ strcpy(max, "No");
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO,
+ " %-20.20s %-15.15s %-10.10s %-10.10s\n",
+ opt->name,
+ opt->type->name,
+ min,
+ max);
+ count++;
+ }
+ mp_tmsg(MSGT_CFGPARSER, MSGL_INFO, "\nTotal: %d properties\n", count);
+}
+
+int m_property_int_ro(const m_option_t *prop, int action,
+ void *arg, int var)
+{
+ if (action == M_PROPERTY_GET) {
+ *(int *)arg = var;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+int m_property_int64_ro(const struct m_option* prop, int action, void* arg,
+ int64_t var)
+{
+ if (action == M_PROPERTY_GET) {
+ *(int64_t *)arg = var;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+int m_property_float_ro(const m_option_t *prop, int action,
+ void *arg, float var)
+{
+ if (action == M_PROPERTY_GET) {
+ *(float *)arg = var;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+int m_property_double_ro(const m_option_t *prop, int action,
+ void *arg, double var)
+{
+ if (action == M_PROPERTY_GET) {
+ *(double *)arg = var;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+int m_property_strdup_ro(const struct m_option* prop, int action, void* arg,
+ const char *var)
+{
+ if (action == M_PROPERTY_GET) {
+ if (!var)
+ return M_PROPERTY_UNAVAILABLE;
+ *(char **)arg = talloc_strdup(NULL, var);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
diff --git a/mpvcore/m_property.h b/mpvcore/m_property.h
new file mode 100644
index 0000000000..b471b94ecd
--- /dev/null
+++ b/mpvcore/m_property.h
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_M_PROPERTY_H
+#define MPLAYER_M_PROPERTY_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct m_option;
+
+extern const struct m_option_type m_option_type_dummy;
+
+enum mp_property_action {
+ // Get the property type. This defines the fundamental data type read from
+ // or written to the property.
+ // If unimplemented, the m_option entry that defines the property is used.
+ // arg: m_option*
+ M_PROPERTY_GET_TYPE,
+
+ // Get the current value.
+ // arg: pointer to a variable of the type according to the property type
+ M_PROPERTY_GET,
+
+ // Set a new value. The property wrapper will make sure that only valid
+ // values are set (e.g. according to the property type's min/max range).
+ // If unimplemented, the property is read-only.
+ // arg: pointer to a variable of the type according to the property type
+ M_PROPERTY_SET,
+
+ // Get human readable string representing the current value.
+ // If unimplemented, the property wrapper uses the property type as
+ // fallback.
+ // arg: char**
+ M_PROPERTY_PRINT,
+
+ // Switch the property up/down by a given value.
+ // If unimplemented, the property wrapper uses the property type as
+ // fallback.
+ // arg: struct m_property_switch_arg*
+ M_PROPERTY_SWITCH,
+
+ // Get a string containing a parsable representation.
+ // Can't be overridden by property implementations.
+ // arg: char**
+ M_PROPERTY_GET_STRING,
+
+ // Set a new value from a string. The property wrapper parses this using the
+ // parse function provided by the property type.
+ // Can't be overridden by property implementations.
+ // arg: char*
+ M_PROPERTY_SET_STRING,
+
+ // Pass down an action to a sub-property.
+ // arg: struct m_property_action_arg*
+ M_PROPERTY_KEY_ACTION,
+};
+
+// Argument for M_PROPERTY_SWITCH
+struct m_property_switch_arg {
+ double inc; // value to add to property, or cycle direction
+ bool wrap; // whether value should wrap around on over/underflow
+};
+
+// Argument for M_PROPERTY_KEY_ACTION
+struct m_property_action_arg {
+ const char* key;
+ int action;
+ void* arg;
+};
+
+enum mp_property_return {
+ // Returned on success.
+ M_PROPERTY_OK = 1,
+
+ // Returned on error.
+ M_PROPERTY_ERROR = 0,
+
+ // Returned when the property can't be used, for example video related
+ // properties while playing audio only.
+ M_PROPERTY_UNAVAILABLE = -1,
+
+ // Returned if the requested action is not implemented.
+ M_PROPERTY_NOT_IMPLEMENTED = -2,
+
+ // Returned when asking for a property that doesn't exist.
+ M_PROPERTY_UNKNOWN = -3,
+};
+
+// Access a property.
+// action: one of m_property_action
+// ctx: opaque value passed through to property implementation
+// returns: one of mp_property_return
+int m_property_do(const struct m_option* prop_list, const char* property_name,
+ int action, void* arg, void *ctx);
+
+// Print a list of properties.
+void m_properties_print_help_list(const struct m_option* list);
+
+// Expand a property string.
+// This function allows to print strings containing property values.
+// ${NAME} is expanded to the value of property NAME.
+// If NAME starts with '=', use the raw value of the property.
+// ${NAME:STR} expands to the property, or STR if the property is not
+// available.
+// ${?NAME:STR} expands to STR if the property is available.
+// ${!NAME:STR} expands to STR if the property is not available.
+// General syntax: "${" ["?" | "!"] ["="] NAME ":" STR "}"
+// STR is recursively expanded using the same rules.
+// "$$" can be used to escape "$", and "$}" to escape "}".
+// "$>" disables parsing of "$" for the rest of the string.
+char* m_properties_expand_string(const struct m_option* prop_list, char *str,
+ void *ctx);
+
+// Trivial helpers for implementing properties.
+int m_property_int_ro(const struct m_option* prop, int action, void* arg,
+ int var);
+int m_property_int64_ro(const struct m_option* prop, int action, void* arg,
+ int64_t var);
+int m_property_float_ro(const struct m_option* prop, int action, void* arg,
+ float var);
+int m_property_double_ro(const struct m_option* prop, int action, void* arg,
+ double var);
+int m_property_strdup_ro(const struct m_option* prop, int action, void* arg,
+ const char *var);
+
+#endif /* MPLAYER_M_PROPERTY_H */
diff --git a/mpvcore/mp_common.c b/mpvcore/mp_common.c
new file mode 100644
index 0000000000..03e3e988fd
--- /dev/null
+++ b/mpvcore/mp_common.c
@@ -0,0 +1,125 @@
+/*
+ * 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 <libavutil/common.h>
+
+#include "talloc.h"
+#include "core/bstr.h"
+#include "core/mp_common.h"
+
+char *mp_format_time(double time, bool fractions)
+{
+ if (time == MP_NOPTS_VALUE)
+ return talloc_strdup(NULL, "unknown");
+ char sign[2] = {0};
+ if (time < 0) {
+ time = -time;
+ sign[0] = '-';
+ }
+ long long int itime = time;
+ long long int h, m, s;
+ s = itime;
+ h = s / 3600;
+ s -= h * 3600;
+ m = s / 60;
+ s -= m * 60;
+ char *res = talloc_asprintf(NULL, "%s%02lld:%02lld:%02lld", sign, h, m, s);
+ if (fractions)
+ res = talloc_asprintf_append(res, ".%03d",
+ (int)((time - itime) * 1000));
+ return res;
+}
+
+// Set rc to the union of rc and rc2
+void mp_rect_union(struct mp_rect *rc, const struct mp_rect *rc2)
+{
+ rc->x0 = FFMIN(rc->x0, rc2->x0);
+ rc->y0 = FFMIN(rc->y0, rc2->y0);
+ rc->x1 = FFMAX(rc->x1, rc2->x1);
+ rc->y1 = FFMAX(rc->y1, rc2->y1);
+}
+
+// Set rc to the intersection of rc and src.
+// Return false if the result is empty.
+bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2)
+{
+ rc->x0 = FFMAX(rc->x0, rc2->x0);
+ rc->y0 = FFMAX(rc->y0, rc2->y0);
+ rc->x1 = FFMIN(rc->x1, rc2->x1);
+ rc->y1 = FFMIN(rc->y1, rc2->y1);
+
+ return rc->x1 > rc->x0 && rc->y1 > rc->y0;
+}
+
+// Encode the unicode codepoint as UTF-8, and append to the end of the
+// talloc'ed buffer.
+char *mp_append_utf8_buffer(char *buffer, uint32_t codepoint)
+{
+ char data[8];
+ uint8_t tmp;
+ char *output = data;
+ PUT_UTF8(codepoint, tmp, *output++ = tmp;);
+ return talloc_strndup_append_buffer(buffer, data, output - data);
+}
+
+// Parse a C-style escape beginning at code, and append the result to *str
+// using talloc. The input string (*code) must point to the first character
+// after the initial '\', and after parsing *code is set to the first character
+// after the current escape.
+// On error, false is returned, and all input remains unchanged.
+bool mp_parse_escape(bstr *code, char **str)
+{
+ if (code->len < 1)
+ return false;
+ char replace = 0;
+ switch (code->start[0]) {
+ case '"': replace = '"'; break;
+ case '\\': replace = '\\'; break;
+ case 'b': replace = '\b'; break;
+ case 'f': replace = '\f'; break;
+ case 'n': replace = '\n'; break;
+ case 'r': replace = '\r'; break;
+ case 't': replace = '\t'; break;
+ case 'e': replace = '\x1b'; break;
+ case '\'': replace = '\''; break;
+ }
+ if (replace) {
+ *str = talloc_strndup_append_buffer(*str, &replace, 1);
+ *code = bstr_cut(*code, 1);
+ return true;
+ }
+ if (code->start[0] == 'x' && code->len >= 3) {
+ bstr num = bstr_splice(*code, 1, 3);
+ char c = bstrtoll(num, &num, 16);
+ if (!num.len)
+ return false;
+ *str = talloc_strndup_append_buffer(*str, &c, 1);
+ *code = bstr_cut(*code, 3);
+ return true;
+ }
+ if (code->start[0] == 'u' && code->len >= 5) {
+ bstr num = bstr_splice(*code, 1, 5);
+ int c = bstrtoll(num, &num, 16);
+ if (num.len)
+ return false;
+ *str = mp_append_utf8_buffer(*str, c);
+ *code = bstr_cut(*code, 5);
+ return true;
+ }
+ return false;
+}
diff --git a/mpvcore/mp_common.h b/mpvcore/mp_common.h
new file mode 100644
index 0000000000..71e24604ec
--- /dev/null
+++ b/mpvcore/mp_common.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_MPCOMMON_H
+#define MPLAYER_MPCOMMON_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "compat/compiler.h"
+#include "core/mp_talloc.h"
+
+// both int64_t and double should be able to represent this exactly
+#define MP_NOPTS_VALUE (-1LL<<63)
+
+#define MP_CONCAT_(a, b) a ## b
+#define MP_CONCAT(a, b) MP_CONCAT_(a, b)
+
+#define ROUND(x) ((int)((x) < 0 ? (x) - 0.5 : (x) + 0.5))
+
+#define MPMAX(a, b) ((a) > (b) ? (a) : (b))
+#define MPMIN(a, b) ((a) > (b) ? (b) : (a))
+#define MP_ARRAY_SIZE(s) (sizeof(s) / sizeof((s)[0]))
+
+#define CONTROL_OK 1
+#define CONTROL_TRUE 1
+#define CONTROL_FALSE 0
+#define CONTROL_UNKNOWN -1
+#define CONTROL_ERROR -2
+#define CONTROL_NA -3
+
+extern const char *mplayer_version;
+extern const char *mplayer_builddate;
+
+char *mp_format_time(double time, bool fractions);
+
+struct mp_rect {
+ int x0, y0;
+ int x1, y1;
+};
+
+void mp_rect_union(struct mp_rect *rc, const struct mp_rect *src);
+bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2);
+
+char *mp_append_utf8_buffer(char *buffer, uint32_t codepoint);
+
+struct bstr;
+bool mp_parse_escape(struct bstr *code, char **str);
+
+#endif /* MPLAYER_MPCOMMON_H */
diff --git a/mpvcore/mp_core.h b/mpvcore/mp_core.h
new file mode 100644
index 0000000000..126c646a0b
--- /dev/null
+++ b/mpvcore/mp_core.h
@@ -0,0 +1,343 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_MP_CORE_H
+#define MPLAYER_MP_CORE_H
+
+#include <stdbool.h>
+
+#include "core/options.h"
+#include "audio/mixer.h"
+#include "demux/demux.h"
+
+// definitions used internally by the core player code
+
+#define INITIALIZED_VO 1
+#define INITIALIZED_AO 2
+#define INITIALIZED_VOL 4
+#define INITIALIZED_GETCH2 8
+#define INITIALIZED_STREAM 64
+#define INITIALIZED_DEMUXER 512
+#define INITIALIZED_ACODEC 1024
+#define INITIALIZED_VCODEC 2048
+#define INITIALIZED_SUB 4096
+#define INITIALIZED_ALL 0xFFFF
+
+
+enum stop_play_reason {
+ KEEP_PLAYING = 0, // must be 0, numeric values of others do not matter
+ AT_END_OF_FILE, // file has ended normally, prepare to play next
+ PT_NEXT_ENTRY, // prepare to play next entry in playlist
+ PT_CURRENT_ENTRY, // prepare to play mpctx->playlist->current
+ PT_STOP, // stop playback, clear playlist
+ PT_RESTART, // restart previous file
+ PT_QUIT, // stop playback, quit player
+};
+
+enum exit_reason {
+ EXIT_NONE,
+ EXIT_QUIT,
+ EXIT_PLAYED,
+ EXIT_ERROR,
+ EXIT_NOTPLAYED,
+ EXIT_SOMENOTPLAYED
+};
+
+struct timeline_part {
+ double start;
+ double source_start;
+ struct demuxer *source;
+};
+
+struct chapter {
+ double start;
+ char *name;
+};
+
+enum mp_osd_seek_info {
+ OSD_SEEK_INFO_BAR = 1,
+ OSD_SEEK_INFO_TEXT = 2,
+ OSD_SEEK_INFO_CHAPTER_TEXT = 4,
+ OSD_SEEK_INFO_EDITION = 8,
+};
+
+struct track {
+ enum stream_type type;
+ // The type specific ID, also called aid (audio), sid (subs), vid (video).
+ // For UI purposes only; this ID doesn't have anything to do with any
+ // IDs coming from demuxers or container files.
+ int user_tid;
+
+ // Same as stream->demuxer_id. -1 if not set.
+ int demuxer_id;
+
+ char *title;
+ bool default_track;
+ bool attached_picture;
+ char *lang;
+
+ // If this track is from an external file (e.g. subtitle file).
+ bool is_external;
+ char *external_filename;
+ bool auto_loaded;
+
+ // If the track's stream changes with the timeline (ordered chapters).
+ bool under_timeline;
+
+ // NULL if not backed by a demuxer (e.g. external subtitles).
+ // Value can change if under_timeline==true.
+ struct demuxer *demuxer;
+ // Invariant: (!demuxer && !stream) || stream->demuxer == demuxer
+ struct sh_stream *stream;
+
+ // For external subtitles, which are read fully on init. Do not attempt
+ // to read packets from them.
+ bool preloaded;
+};
+
+enum {
+ MAX_NUM_VO_PTS = 100,
+};
+
+typedef struct MPContext {
+ struct mpv_global *global;
+ struct MPOpts *opts;
+ struct mp_log *log;
+ struct m_config *mconfig;
+ struct input_ctx *input;
+ struct osd_state *osd;
+ struct mp_osd_msg *osd_msg_stack;
+ char *terminal_osd_text;
+
+ int add_osd_seek_info; // bitfield of enum mp_osd_seek_info
+ double osd_visible; // for the osd bar only
+ int osd_function;
+ double osd_function_visible;
+ double osd_last_update;
+
+ struct playlist *playlist;
+ char *filename; // currently playing file
+ struct mp_resolve_result *resolve_result;
+ enum stop_play_reason stop_play;
+ unsigned int initialized_flags; // which subsystems have been initialized
+
+ // Return code to use with PT_QUIT
+ enum exit_reason quit_player_rc;
+ int quit_custom_rc;
+ bool has_quit_custom_rc;
+ bool error_playing;
+
+ struct demuxer **sources;
+ int num_sources;
+
+ struct timeline_part *timeline;
+ int num_timeline_parts;
+ int timeline_part;
+ // NOTE: even if num_chapters==0, chapters being not NULL signifies presence
+ // of chapter metadata
+ struct chapter *chapters;
+ int num_chapters;
+ double video_offset;
+
+ struct stream *stream;
+ struct demuxer *demuxer;
+
+ struct track **tracks;
+ int num_tracks;
+
+ // Selected tracks. NULL if no track selected.
+ struct track *current_track[STREAM_TYPE_COUNT];
+
+ struct sh_stream *sh[STREAM_TYPE_COUNT];
+ struct sh_audio *sh_audio; // same as sh[STREAM_AUDIO]->audio
+ struct sh_video *sh_video; // same as sh[STREAM_VIDEO]->video
+ struct sh_sub *sh_sub; // same as sh[STREAM_SUB]->sub
+
+ // Uses: accessing metadata (consider ordered chapters case, where the main
+ // demuxer defines metadata), or special purpose demuxers like TV.
+ struct demuxer *master_demuxer;
+
+ mixer_t mixer;
+ struct ao *ao;
+ struct vo *video_out;
+
+ /* We're starting playback from scratch or after a seek. Show first
+ * video frame immediately and reinitialize sync. */
+ bool restart_playback;
+ /* Set if audio should be timed to start with video frame after seeking,
+ * not set when e.g. playing cover art */
+ bool sync_audio_to_video;
+ /* After playback restart (above) or audio stream change, adjust audio
+ * stream by cutting samples or adding silence at the beginning to make
+ * audio playback position match video position. */
+ bool syncing_audio;
+ bool hrseek_active;
+ bool hrseek_framedrop;
+ double hrseek_pts;
+ // AV sync: the next frame should be shown when the audio out has this
+ // much (in seconds) buffered data left. Increased when more data is
+ // written to the ao, decreased when moving to the next frame.
+ // In the audio-only case used as a timer since the last seek
+ // by the audio CPU usage meter.
+ double delay;
+ // AV sync: time until next frame should be shown
+ double time_frame;
+ // How long the last vo flip() call took. Used to adjust timing with
+ // the goal of making flip() calls finish (rather than start) at the
+ // specified time.
+ double last_vo_flip_duration;
+ // How much video timing has been changed to make it match the audio
+ // timeline. Used for status line information only.
+ double total_avsync_change;
+ // Total number of dropped frames that were "approved" to be dropped.
+ // Actual dropping depends on --framedrop and decoder internals.
+ int drop_frame_cnt;
+ // Number of frames dropped in a row.
+ int dropped_frames;
+ // A-V sync difference when last frame was displayed. Kept to display
+ // the same value if the status line is updated at a time where no new
+ // video frame is shown.
+ double last_av_difference;
+ /* timestamp of video frame currently visible on screen
+ * (or at least queued to be flipped by VO) */
+ double video_pts;
+ double last_seek_pts;
+ // As video_pts, but is not reset when seeking away. (For the very short
+ // period of time until a new frame is decoded and shown.)
+ double last_vo_pts;
+ // Video PTS, or audio PTS if video has ended.
+ double playback_pts;
+
+ // History of video frames timestamps that were queued in the VO
+ // This includes even skipped frames during hr-seek
+ double vo_pts_history_pts[MAX_NUM_VO_PTS];
+ // Whether the PTS at vo_pts_history[n] is after a seek reset
+ uint64_t vo_pts_history_seek[MAX_NUM_VO_PTS];
+ uint64_t vo_pts_history_seek_ts;
+ uint64_t backstep_start_seek_ts;
+ bool backstep_active;
+
+ double audio_delay;
+
+ double last_heartbeat;
+ double last_metadata_update;
+
+ double mouse_timer;
+ unsigned int mouse_event_ts;
+ int mouse_waiting_hide;
+
+ // used to prevent hanging in some error cases
+ double start_timestamp;
+
+ // Timestamp from the last time some timing functions read the
+ // current time, in (occasionally wrapping) microseconds. Used
+ // to turn a new time value to a delta from last time.
+ int64_t last_time;
+
+ // Used to communicate the parameters of a seek between parts
+ struct seek_params {
+ enum seek_type {
+ MPSEEK_NONE, MPSEEK_RELATIVE, MPSEEK_ABSOLUTE, MPSEEK_FACTOR
+ } type;
+ double amount;
+ int exact; // -1 = disable, 0 = default, 1 = enable
+ // currently not set by commands, only used internally by seek()
+ int direction; // -1 = backward, 0 = default, 1 = forward
+ } seek;
+
+ /* Heuristic for relative chapter seeks: keep track which chapter
+ * the user wanted to go to, even if we aren't exactly within the
+ * boundaries of that chapter due to an inaccurate seek. */
+ int last_chapter_seek;
+ double last_chapter_pts;
+
+ struct ass_library *ass_library;
+
+ int last_dvb_step;
+ int dvbin_reopen;
+
+ bool paused;
+ // step this many frames, then pause
+ int step_frames;
+ // Counted down each frame, stop playback if 0 is reached. (-1 = disable)
+ int max_frames;
+ bool playing_msg_shown;
+
+ bool paused_for_cache;
+
+ // Set after showing warning about decoding being too slow for realtime
+ // playback rate. Used to avoid showing it multiple times.
+ bool drop_message_shown;
+
+ struct screenshot_ctx *screenshot_ctx;
+
+ char *track_layout_hash;
+
+ struct encode_lavc_context *encode_lavc_ctx;
+} MPContext;
+
+
+// should not be global
+extern FILE *edl_fd;
+// These appear in options list
+extern int forced_subs_only;
+
+void uninit_player(struct MPContext *mpctx, unsigned int mask);
+void reinit_audio_chain(struct MPContext *mpctx);
+double playing_audio_pts(struct MPContext *mpctx);
+struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, int noerr);
+int reinit_video_chain(struct MPContext *mpctx);
+int reinit_video_filters(struct MPContext *mpctx);
+int reinit_audio_filters(struct MPContext *mpctx);
+void pause_player(struct MPContext *mpctx);
+void unpause_player(struct MPContext *mpctx);
+void add_step_frame(struct MPContext *mpctx, int dir);
+void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
+ int exact);
+bool mp_seek_chapter(struct MPContext *mpctx, int chapter);
+double get_time_length(struct MPContext *mpctx);
+double get_start_time(struct MPContext *mpctx);
+double get_current_time(struct MPContext *mpctx);
+int get_percent_pos(struct MPContext *mpctx);
+double get_current_pos_ratio(struct MPContext *mpctx, bool use_range);
+int get_current_chapter(struct MPContext *mpctx);
+char *chapter_display_name(struct MPContext *mpctx, int chapter);
+char *chapter_name(struct MPContext *mpctx, int chapter);
+double chapter_start_time(struct MPContext *mpctx, int chapter);
+int get_chapter_count(struct MPContext *mpctx);
+void mp_switch_track(struct MPContext *mpctx, enum stream_type type,
+ struct track *track);
+struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
+ int tid);
+bool mp_remove_track(struct MPContext *mpctx, struct track *track);
+struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction);
+int mp_get_cache_percent(struct MPContext *mpctx);
+void mp_write_watch_later_conf(struct MPContext *mpctx);
+void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e);
+void mp_force_video_refresh(struct MPContext *mpctx);
+
+void mp_print_version(int always);
+
+// timeline/tl_matroska.c
+void build_ordered_chapter_timeline(struct MPContext *mpctx);
+// timeline/tl_edl.c
+void build_edl_timeline(struct MPContext *mpctx);
+// timeline/tl_cue.c
+void build_cue_timeline(struct MPContext *mpctx);
+
+#endif /* MPLAYER_MP_CORE_H */
diff --git a/mpvcore/mp_memory_barrier.h b/mpvcore/mp_memory_barrier.h
new file mode 100644
index 0000000000..e27825de8f
--- /dev/null
+++ b/mpvcore/mp_memory_barrier.h
@@ -0,0 +1,23 @@
+/*
+ * This file is part of mpv.
+ * Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// At this point both gcc and clang had __sync_synchronize support for some
+// time. We only support a full memory barrier.
+
+#define mp_memory_barrier() __sync_synchronize()
+#define mp_atomic_add_and_fetch(a, b) __sync_add_and_fetch(a, b)
diff --git a/mpvcore/mp_msg.c b/mpvcore/mp_msg.c
new file mode 100644
index 0000000000..ff611451c2
--- /dev/null
+++ b/mpvcore/mp_msg.c
@@ -0,0 +1,471 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "talloc.h"
+
+#include "config.h"
+#include "core/mpv_global.h"
+#include "osdep/getch2.h"
+#include "osdep/io.h"
+
+#ifdef CONFIG_GETTEXT
+#include <locale.h>
+#include <libintl.h>
+#endif
+
+#ifndef __MINGW32__
+#include <signal.h>
+#endif
+
+#include "core/mp_msg.h"
+
+bool mp_msg_stdout_in_use = 0;
+
+struct mp_log_root {
+ /* This should, at some point, contain all mp_msg related state, instead
+ * of having global variables (at least as long as we don't want to
+ * control the terminal, which is global anyway). But for now, there is
+ * not much. */
+ struct mpv_global *global;
+};
+
+struct mp_log {
+ struct mp_log_root *root;
+ const char *prefix;
+ const char *verbose_prefix;
+ int legacy_mod;
+};
+
+// should not exist
+static bool initialized;
+static struct mp_log *legacy_logs[MSGT_MAX];
+
+/* maximum message length of mp_msg */
+#define MSGSIZE_MAX 6144
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <io.h>
+#define hSTDOUT GetStdHandle(STD_OUTPUT_HANDLE)
+#define hSTDERR GetStdHandle(STD_ERROR_HANDLE)
+static short stdoutAttrs = 0;
+static const unsigned char ansi2win32[10] = {
+ 0,
+ FOREGROUND_RED,
+ FOREGROUND_GREEN,
+ FOREGROUND_GREEN | FOREGROUND_RED,
+ FOREGROUND_BLUE,
+ FOREGROUND_BLUE | FOREGROUND_RED,
+ FOREGROUND_BLUE | FOREGROUND_GREEN,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED,
+ FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+};
+#endif
+
+int mp_msg_levels[MSGT_MAX]; // verbose level of this module. initialized to -2
+int mp_msg_level_all = MSGL_STATUS;
+int verbose = 0;
+int mp_msg_color = 1;
+int mp_msg_module = 0;
+int mp_msg_cancolor = 0;
+
+static int mp_msg_docolor(void) {
+ return mp_msg_cancolor && mp_msg_color;
+}
+
+static void mp_msg_do_init(void){
+#ifdef _WIN32
+ CONSOLE_SCREEN_BUFFER_INFO cinfo;
+ DWORD cmode = 0;
+ GetConsoleMode(hSTDOUT, &cmode);
+ cmode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
+ SetConsoleMode(hSTDOUT, cmode);
+ SetConsoleMode(hSTDERR, cmode);
+ GetConsoleScreenBufferInfo(hSTDOUT, &cinfo);
+ stdoutAttrs = cinfo.wAttributes;
+#endif
+#ifndef __MINGW32__
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGTTOU, &sa, NULL); // just write to stdout if you have to
+#endif
+ int i;
+ char *env = getenv("MPV_VERBOSE");
+ if (env)
+ verbose = atoi(env);
+ for(i=0;i<MSGT_MAX;i++) mp_msg_levels[i] = -2;
+ mp_msg_cancolor = isatty(fileno(stdout));
+ mp_msg_levels[MSGT_IDENTIFY] = -1; // no -identify output by default
+#ifdef CONFIG_GETTEXT
+ textdomain("mpv");
+ char *localedir = getenv("MPV_LOCALEDIR");
+ if (localedir == NULL && strlen(MPLAYER_LOCALEDIR))
+ localedir = MPLAYER_LOCALEDIR;
+ bindtextdomain("mpv", localedir);
+ bind_textdomain_codeset("mpv", "UTF-8");
+#endif
+}
+
+int mp_msg_test(int mod, int lev)
+{
+#ifndef __MINGW32__
+ if (lev == MSGL_STATUS) {
+ // skip status line output if stderr is a tty but in background
+ if (isatty(2) && tcgetpgrp(2) != getpgrp())
+ return false;
+ }
+#endif
+ return lev <= (mp_msg_levels[mod] == -2 ? mp_msg_level_all + verbose : mp_msg_levels[mod]);
+}
+
+bool mp_msg_test_log(struct mp_log *log, int lev)
+{
+ return mp_msg_test(log->legacy_mod, lev);
+}
+
+static void set_msg_color(FILE* stream, int lev)
+{
+ static const int v_colors[10] = {9, 1, 3, 3, -1, -1, 2, 8, 8, 8};
+ int c = v_colors[lev];
+#ifdef MP_ANNOY_ME
+ /* that's only a silly color test */
+ {
+ int c;
+ static int flag = 1;
+ if (flag)
+ for(c = 0; c < 24; c++)
+ printf("\033[%d;3%dm*** COLOR TEST %d ***\n", c>7, c&7, c);
+ flag = 0;
+ }
+#endif
+ if (mp_msg_docolor())
+ {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ HANDLE *wstream = stream == stderr ? hSTDERR : hSTDOUT;
+ if (c == -1)
+ c = 7;
+ SetConsoleTextAttribute(wstream, ansi2win32[c] | FOREGROUND_INTENSITY);
+#else
+ if (c == -1) {
+ fprintf(stream, "\033[0m");
+ } else {
+ fprintf(stream, "\033[%d;3%dm", c >> 3, c & 7);
+ }
+#endif
+ }
+}
+
+static void print_msg_module(FILE* stream, struct mp_log *log)
+{
+ int mod = log->legacy_mod;
+ int c2 = (mod + 1) % 15 + 1;
+
+#ifdef _WIN32
+ HANDLE *wstream = stream == stderr ? hSTDERR : hSTDOUT;
+ if (mp_msg_docolor())
+ SetConsoleTextAttribute(wstream, ansi2win32[c2&7] | FOREGROUND_INTENSITY);
+ fprintf(stream, "%9s", log->verbose_prefix);
+ if (mp_msg_docolor())
+ SetConsoleTextAttribute(wstream, stdoutAttrs);
+#else
+ if (mp_msg_docolor())
+ fprintf(stream, "\033[%d;3%dm", c2 >> 3, c2 & 7);
+ fprintf(stream, "%9s", log->verbose_prefix);
+ if (mp_msg_docolor())
+ fprintf(stream, "\033[0;37m");
+#endif
+ fprintf(stream, ": ");
+}
+
+static void mp_msg_log_va(struct mp_log *log, int lev, const char *format,
+ va_list va)
+{
+ char tmp[MSGSIZE_MAX];
+ FILE *stream =
+ (mp_msg_stdout_in_use || (lev == MSGL_STATUS)) ? stderr : stdout;
+ static int header = 1;
+ // indicates if last line printed was a status line
+ static int statusline;
+
+ if (!mp_msg_test_log(log, lev)) return; // do not display
+ vsnprintf(tmp, MSGSIZE_MAX, format, va);
+ tmp[MSGSIZE_MAX-2] = '\n';
+ tmp[MSGSIZE_MAX-1] = 0;
+
+ /* A status line is normally intended to be overwritten by the next
+ * status line, and does not end with a '\n'. If we're printing a normal
+ * line instead after the status one print '\n' to change line. */
+ if (statusline && lev != MSGL_STATUS)
+ fprintf(stderr, "\n");
+ statusline = lev == MSGL_STATUS;
+
+ set_msg_color(stream, lev);
+ if (header) {
+ if (mp_msg_module) {
+ print_msg_module(stream, log);
+ set_msg_color(stream, lev);
+ } else if (lev >= MSGL_V || verbose) {
+ fprintf(stream, "[%s] ", log->verbose_prefix);
+ } else if (log->prefix) {
+ fprintf(stream, "[%s] ", log->prefix);
+ }
+ }
+
+ size_t len = strlen(tmp);
+ header = len && (tmp[len-1] == '\n' || tmp[len-1] == '\r');
+
+ fprintf(stream, "%s", tmp);
+
+ if (mp_msg_docolor())
+ {
+#ifdef _WIN32
+ HANDLE *wstream = lev <= MSGL_WARN ? hSTDERR : hSTDOUT;
+ SetConsoleTextAttribute(wstream, stdoutAttrs);
+#else
+ fprintf(stream, "\033[0m");
+#endif
+ }
+ fflush(stream);
+}
+
+void mp_msg_va(int mod, int lev, const char *format, va_list va)
+{
+ assert(initialized);
+ assert(mod >= 0 && mod < MSGT_MAX);
+ mp_msg_log_va(legacy_logs[mod], lev, format, va);
+}
+
+void mp_msg(int mod, int lev, const char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ mp_msg_va(mod, lev, format, va);
+ va_end(va);
+}
+
+char *mp_gtext(const char *string)
+{
+#ifdef CONFIG_GETTEXT
+ /* gettext expects the global locale to be set with
+ * setlocale(LC_ALL, ""). However doing that would suck for a
+ * couple of reasons (locale stuff is badly designed and sucks in
+ * general).
+ *
+ * First, setting the locale, especially LC_CTYPE, changes the
+ * behavior of various C functions and we don't want that - we
+ * want isalpha() for example to always behave like in the C
+ * locale.
+
+ * Second, there is no way to enforce a sane character set. All
+ * strings inside MPlayer must always be in utf-8, not in the
+ * character set specified by the system locale which could be
+ * something different and completely insane. The locale system
+ * lacks any way to say "set LC_CTYPE to utf-8, ignoring the
+ * default system locale if it specifies something different". We
+ * could try to work around that flaw by leaving LC_CTYPE to the C
+ * locale and only setting LC_MESSAGES (which is the variable that
+ * must be set to tell gettext which language to translate
+ * to). However if we leave LC_MESSAGES set then things like
+ * strerror() may produce completely garbled output when they try
+ * to translate their results but then try to convert some
+ * translated non-ASCII text to the character set specified by
+ * LC_CTYPE which would still be in the C locale (this doesn't
+ * affect gettext itself because it supports specifying the
+ * character set directly with bind_textdomain_codeset()).
+ *
+ * So the only solution (at least short of trying to work around
+ * things possibly producing non-utf-8 output) is to leave all the
+ * locale variables unset. Note that this means it's not possible
+ * to get translated output from any libraries we call if they
+ * only rely on the broken locale system to specify the language
+ * to use; this is the case with libc for example.
+ *
+ * The locale changing below is rather ugly, but hard to avoid.
+ * gettext doesn't support specifying the translation target
+ * directly, only through locale.
+ * The main actual problem this could cause is interference with
+ * other threads; that could be avoided with thread-specific
+ * locale changes, but such functionality is less standard and I
+ * think it's not worth adding pre-emptively unless someone sees
+ * an actual problem case.
+ */
+ setlocale(LC_MESSAGES, "");
+ string = gettext(string);
+ setlocale(LC_MESSAGES, "C");
+#endif
+ return (char *)string;
+}
+
+void mp_tmsg(int mod, int lev, const char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ mp_msg_va(mod, lev, mp_gtext(format), va);
+ va_end(va);
+}
+
+// legacy names
+static const char *module_text[MSGT_MAX] = {
+ "global",
+ "cplayer",
+ "gplayer",
+ "vo",
+ "ao",
+ "demuxer",
+ "ds",
+ "demux",
+ "header",
+ "avsync",
+ "autoq",
+ "cfgparser",
+ "decaudio",
+ "decvideo",
+ "seek",
+ "win32",
+ "open",
+ "dvd",
+ "parsees",
+ "lirc",
+ "stream",
+ "cache",
+ "mencoder",
+ "xacodec",
+ "tv",
+ "osdep",
+ "spudec",
+ "playtree",
+ "input",
+ "vf",
+ "osd",
+ "network",
+ "cpudetect",
+ "codeccfg",
+ "sws",
+ "vobsub",
+ "subreader",
+ "af",
+ "netst",
+ "muxer",
+ "osdmenu",
+ "identify",
+ "radio",
+ "ass",
+ "loader",
+ "statusline",
+ "teletext",
+};
+
+// Create a new log context, which uses talloc_ctx as talloc parent, and parent
+// as logical parent.
+// The name is the prefix put before the output. It's usually prefixed by the
+// parent's name. If the name starts with "/", the parent's name is not
+// prefixed (except in verbose mode), and if it starts with "!", the name is
+// not printed at all (except in verbose mode).
+struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent,
+ const char *name)
+{
+ assert(parent);
+ assert(name);
+ struct mp_log *log = talloc_zero(talloc_ctx, struct mp_log);
+ log->root = parent->root;
+ if (name[0] == '!') {
+ name = &name[1];
+ } else if (name[0] == '/') {
+ name = &name[1];
+ log->prefix = talloc_strdup(log, name);
+ } else {
+ log->prefix = parent->prefix
+ ? talloc_asprintf(log, "%s/%s", parent->prefix, name)
+ : talloc_strdup(log, name);
+ }
+ log->verbose_prefix = parent->prefix
+ ? talloc_asprintf(log, "%s/%s", parent->prefix, name)
+ : talloc_strdup(log, name);
+ if (log->prefix && !log->prefix[0])
+ log->prefix = NULL;
+ if (!log->verbose_prefix[0])
+ log->verbose_prefix = "global";
+ log->legacy_mod = parent->legacy_mod;
+ for (int n = 0; n < MSGT_MAX; n++) {
+ if (module_text[n] && strcmp(name, module_text[n]) == 0) {
+ log->legacy_mod = n;
+ break;
+ }
+ }
+ return log;
+}
+
+void mp_msg_init(struct mpv_global *global)
+{
+ assert(!initialized);
+ assert(!global->log);
+
+ struct mp_log_root *root = talloc_zero(NULL, struct mp_log_root);
+ root->global = global;
+
+ struct mp_log dummy = { .root = root };
+ struct mp_log *log = mp_log_new(root, &dummy, "");
+ for (int n = 0; n < MSGT_MAX; n++) {
+ char name[80];
+ snprintf(name, sizeof(name), "!%s", module_text[n]);
+ legacy_logs[n] = mp_log_new(root, log, name);
+ }
+ mp_msg_do_init();
+
+ global->log = log;
+ initialized = true;
+}
+
+struct mpv_global *mp_log_get_global(struct mp_log *log)
+{
+ return log->root->global;
+}
+
+void mp_msg_uninit(struct mpv_global *global)
+{
+ talloc_free(global->log->root);
+ global->log = NULL;
+ initialized = false;
+}
+
+void mp_msg_log(struct mp_log *log, int lev, const char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ mp_msg_log_va(log, lev, format, va);
+ va_end(va);
+}
+
+void mp_tmsg_log(struct mp_log *log, int lev, const char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ mp_msg_log_va(log, lev, mp_gtext(format), va);
+ va_end(va);
+}
diff --git a/mpvcore/mp_msg.h b/mpvcore/mp_msg.h
new file mode 100644
index 0000000000..4685668f01
--- /dev/null
+++ b/mpvcore/mp_msg.h
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_MP_MSG_H
+#define MPLAYER_MP_MSG_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+struct mp_log;
+
+// defined in mplayer.c
+extern int verbose;
+
+/* No-op macro to mark translated strings in the sources */
+#define _(x) x
+
+// verbosity elevel:
+
+/* Only messages level MSGL_FATAL-MSGL_STATUS should be translated,
+ * messages level MSGL_V and above should not be translated. */
+
+#define MSGL_FATAL 0 // will exit/abort
+#define MSGL_ERR 1 // continues
+#define MSGL_WARN 2 // only warning
+#define MSGL_HINT 3 // short help message
+#define MSGL_INFO 4 // -quiet
+#define MSGL_STATUS 5 // v=0
+#define MSGL_V 6 // v=1
+#define MSGL_DBG2 7 // v=2
+#define MSGL_DBG3 8 // v=3
+#define MSGL_DBG4 9 // v=4
+#define MSGL_DBG5 10 // v=5
+
+#define MSGL_FIXME 1 // for conversions from printf where the appropriate MSGL is not known; set equal to ERR for obtrusiveness
+#define MSGT_FIXME 0 // for conversions from printf where the appropriate MSGT is not known; set equal to GLOBAL for obtrusiveness
+
+// code/module:
+
+#define MSGT_GLOBAL 0 // common player stuff errors
+#define MSGT_CPLAYER 1 // console player (mplayer.c)
+
+#define MSGT_VO 3 // libvo
+#define MSGT_AO 4 // libao
+
+#define MSGT_DEMUXER 5 // demuxer.c (general stuff)
+#define MSGT_DS 6 // demux stream (add/read packet etc)
+#define MSGT_DEMUX 7 // fileformat-specific stuff (demux_*.c)
+#define MSGT_HEADER 8 // fileformat-specific header (*header.c)
+
+#define MSGT_AVSYNC 9 // mplayer.c timer stuff
+#define MSGT_AUTOQ 10 // mplayer.c auto-quality stuff
+
+#define MSGT_CFGPARSER 11 // cfgparser.c
+
+#define MSGT_DECAUDIO 12 // av decoder
+#define MSGT_DECVIDEO 13
+
+#define MSGT_SEEK 14 // seeking code
+#define MSGT_WIN32 15 // win32 dll stuff
+#define MSGT_OPEN 16 // open.c (stream opening)
+#define MSGT_DVD 17 // open.c (DVD init/read/seek)
+
+#define MSGT_PARSEES 18 // parse_es.c (mpeg stream parser)
+#define MSGT_LIRC 19 // lirc_mp.c and input lirc driver
+
+#define MSGT_STREAM 20 // stream.c
+#define MSGT_CACHE 21 // cache2.c
+
+#define MSGT_ENCODE 22 // now encode_lavc.c
+
+#define MSGT_XACODEC 23 // XAnim codecs
+
+#define MSGT_TV 24 // TV input subsystem
+
+#define MSGT_OSDEP 25 // OS-dependent parts
+
+#define MSGT_SPUDEC 26 // spudec.c
+
+#define MSGT_PLAYTREE 27 // Playtree handeling (playtree.c, playtreeparser.c)
+
+#define MSGT_INPUT 28
+
+#define MSGT_VFILTER 29
+
+#define MSGT_OSD 30
+
+#define MSGT_NETWORK 31
+
+#define MSGT_CPUDETECT 32
+
+#define MSGT_CODECCFG 33
+
+#define MSGT_SWS 34
+
+#define MSGT_VOBSUB 35
+#define MSGT_SUBREADER 36
+
+#define MSGT_AFILTER 37 // Audio filter messages
+
+#define MSGT_NETST 38 // Netstream
+
+#define MSGT_MUXER 39 // muxer layer
+
+#define MSGT_IDENTIFY 41 // -identify output
+
+#define MSGT_RADIO 42
+
+#define MSGT_ASS 43 // libass messages
+
+#define MSGT_LOADER 44 // dll loader messages
+
+#define MSGT_STATUSLINE 45 // playback/encoding status line
+
+#define MSGT_TELETEXT 46 // Teletext decoder
+
+#define MSGT_MAX 47
+
+int mp_msg_test(int mod, int lev);
+bool mp_msg_test_log(struct mp_log *log, int lev);
+
+#include "config.h"
+#include "core/mp_common.h"
+
+char *mp_gtext(const char *string);
+
+void mp_msg_va(int mod, int lev, const char *format, va_list va);
+
+void mp_msg(int mod, int lev, const char *format, ... ) PRINTF_ATTRIBUTE(3, 4);
+void mp_tmsg(int mod, int lev, const char *format, ... ) PRINTF_ATTRIBUTE(3, 4);
+#define mp_dbg mp_msg
+
+struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent,
+ const char *name);
+
+void mp_msg_log(struct mp_log *log, int lev, const char *format, ...)
+ PRINTF_ATTRIBUTE(3, 4);
+void mp_tmsg_log(struct mp_log *log, int lev, const char *format, ...)
+ PRINTF_ATTRIBUTE(3, 4);
+
+// Convenience macros, typically called with a pointer to a context struct
+// as first argument, which has a "struct mp_log log;" member.
+
+#define MP_MSG(obj, lev, ...) mp_msg_log((obj)->log, lev, __VA_ARGS__)
+#define MP_MSGT(obj, lev, ...) mp_msgt_log((obj)->log, lev, __VA_ARGS__)
+
+#define MP_FATAL(obj, ...) MP_MSG(obj, MSGL_FATAL, __VA_ARGS__)
+#define MP_ERR(obj, ...) MP_MSG(obj, MSGL_ERR, __VA_ARGS__)
+#define MP_WARN(obj, ...) MP_MSG(obj, MSGL_WARN, __VA_ARGS__)
+#define MP_INFO(obj, ...) MP_MSG(obj, MSGL_INFO, __VA_ARGS__)
+#define MP_VERBOSE(obj, ...) MP_MSG(obj, MSGL_V, __VA_ARGS__)
+#define MP_DBG(obj, ...) MP_MSG(obj, MSGL_DGB2, __VA_ARGS__)
+#define MP_TRACE(obj, ...) MP_MSG(obj, MSGL_DGB5, __VA_ARGS__)
+
+struct mpv_global;
+void mp_msg_init(struct mpv_global *global);
+void mp_msg_uninit(struct mpv_global *global);
+
+struct mpv_global *mp_log_get_global(struct mp_log *log);
+
+extern bool mp_msg_stdout_in_use;
+
+#endif /* MPLAYER_MP_MSG_H */
diff --git a/mpvcore/mp_osd.h b/mpvcore/mp_osd.h
new file mode 100644
index 0000000000..0b737f0c22
--- /dev/null
+++ b/mpvcore/mp_osd.h
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_MP_OSD_H
+#define MPLAYER_MP_OSD_H
+
+#include "compat/compiler.h"
+
+#define OSD_MSG_TEXT 1
+#define OSD_MSG_SUB_DELAY 2
+#define OSD_MSG_SPEED 3
+#define OSD_MSG_OSD_STATUS 4
+#define OSD_MSG_BAR 5
+#define OSD_MSG_PAUSE 6
+#define OSD_MSG_RADIO_CHANNEL 7
+#define OSD_MSG_TV_CHANNEL 8
+/// Base id for messages generated from the commmand to property bridge.
+#define OSD_MSG_PROPERTY 0x100
+#define OSD_MSG_SUB_BASE 0x1000
+
+#define MAX_OSD_LEVEL 3
+#define MAX_TERM_OSD_LEVEL 1
+#define OSD_LEVEL_INVISIBLE 4
+
+#define OSD_BAR_SEEK 256
+
+struct MPContext;
+
+void set_osd_bar(struct MPContext *mpctx, int type,const char* name,double min,double max,double val);
+void set_osd_msg(struct MPContext *mpctx, int id, int level, int time, const char* fmt, ...) PRINTF_ATTRIBUTE(5,6);
+void set_osd_tmsg(struct MPContext *mpctx, int id, int level, int time, const char* fmt, ...) PRINTF_ATTRIBUTE(5,6);
+void rm_osd_msg(struct MPContext *mpctx, int id);
+
+// osd_function is the symbol appearing in the video status, such as OSD_PLAY
+void set_osd_function(struct MPContext *mpctx, int osd_function);
+
+#endif /* MPLAYER_MP_OSD_H */
diff --git a/mpvcore/mp_ring.c b/mpvcore/mp_ring.c
new file mode 100644
index 0000000000..bd94870710
--- /dev/null
+++ b/mpvcore/mp_ring.c
@@ -0,0 +1,155 @@
+/*
+ * This file is part of mpv.
+ * Copyright (c) 2012 wm4
+ * Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <inttypes.h>
+#include <libavutil/common.h>
+#include <assert.h>
+#include "talloc.h"
+#include "core/mp_memory_barrier.h"
+#include "core/mp_ring.h"
+
+struct mp_ring {
+ uint8_t *buffer;
+
+ /* Positions of thes first readable/writeable chunks. Do not read this
+ * fields but use the atomic private accessors `mp_ring_get_wpos`
+ * and `mp_ring_get_rpos`. */
+ uint32_t rpos, wpos;
+};
+
+static uint32_t mp_ring_get_wpos(struct mp_ring *buffer)
+{
+ mp_memory_barrier();
+ return buffer->wpos;
+}
+
+static uint32_t mp_ring_get_rpos(struct mp_ring *buffer)
+{
+ mp_memory_barrier();
+ return buffer->rpos;
+}
+
+struct mp_ring *mp_ring_new(void *talloc_ctx, int size)
+{
+ struct mp_ring *ringbuffer =
+ talloc_zero(talloc_ctx, struct mp_ring);
+
+ *ringbuffer = (struct mp_ring) {
+ .buffer = talloc_size(talloc_ctx, size),
+ };
+
+ return ringbuffer;
+}
+
+int mp_ring_drain(struct mp_ring *buffer, int len)
+{
+ int buffered = mp_ring_buffered(buffer);
+ int drain_len = FFMIN(len, buffered);
+ mp_atomic_add_and_fetch(&buffer->rpos, drain_len);
+ mp_memory_barrier();
+ return drain_len;
+}
+
+int mp_ring_read(struct mp_ring *buffer, unsigned char *dest, int len)
+{
+ if (!dest) return mp_ring_drain(buffer, len);
+
+ int size = mp_ring_size(buffer);
+ int buffered = mp_ring_buffered(buffer);
+ int read_len = FFMIN(len, buffered);
+ int read_ptr = mp_ring_get_rpos(buffer) % size;
+
+ int len1 = FFMIN(size - read_ptr, read_len);
+ int len2 = read_len - len1;
+
+ memcpy(dest, buffer->buffer + read_ptr, len1);
+ memcpy(dest + len1, buffer->buffer, len2);
+
+ mp_atomic_add_and_fetch(&buffer->rpos, read_len);
+ mp_memory_barrier();
+
+ return read_len;
+}
+
+int mp_ring_read_cb(struct mp_ring *buffer, void *ctx, int len,
+ void (*func)(void*, void*, int))
+{
+ // The point of this function is defining custom read behaviour, assume
+ // it's a programmers error if func is null.
+ assert(func);
+
+ int size = mp_ring_size(buffer);
+ int buffered = mp_ring_buffered(buffer);
+ int read_len = FFMIN(len, buffered);
+ int read_ptr = mp_ring_get_rpos(buffer) % size;
+
+ func(ctx, buffer->buffer + read_ptr, read_len);
+
+ return mp_ring_drain(buffer, read_len);
+}
+
+int mp_ring_write(struct mp_ring *buffer, unsigned char *src, int len)
+{
+ int size = mp_ring_size(buffer);
+ int free = mp_ring_available(buffer);
+ int write_len = FFMIN(len, free);
+ int write_ptr = mp_ring_get_wpos(buffer) % size;
+
+ int len1 = FFMIN(size - write_ptr, write_len);
+ int len2 = write_len - len1;
+
+ memcpy(buffer->buffer + write_ptr, src, len1);
+ memcpy(buffer->buffer, src + len1, len2);
+
+ mp_atomic_add_and_fetch(&buffer->wpos, write_len);
+ mp_memory_barrier();
+
+ return write_len;
+}
+
+void mp_ring_reset(struct mp_ring *buffer)
+{
+ buffer->wpos = buffer->rpos = 0;
+ mp_memory_barrier();
+}
+
+int mp_ring_available(struct mp_ring *buffer)
+{
+ return mp_ring_size(buffer) - mp_ring_buffered(buffer);
+}
+
+int mp_ring_size(struct mp_ring *buffer)
+{
+ return talloc_get_size(buffer->buffer);
+}
+
+int mp_ring_buffered(struct mp_ring *buffer)
+{
+ return (mp_ring_get_wpos(buffer) - mp_ring_get_rpos(buffer));
+}
+
+char *mp_ring_repr(struct mp_ring *buffer, void *talloc_ctx)
+{
+ return talloc_asprintf(
+ talloc_ctx,
+ "Ringbuffer { .size = %dB, .buffered = %dB, .available = %dB }",
+ mp_ring_size(buffer),
+ mp_ring_buffered(buffer),
+ mp_ring_available(buffer));
+}
diff --git a/mpvcore/mp_ring.h b/mpvcore/mp_ring.h
new file mode 100644
index 0000000000..ba104af625
--- /dev/null
+++ b/mpvcore/mp_ring.h
@@ -0,0 +1,130 @@
+/*
+ * This file is part of mpv.
+ * Copyright (c) 2012 wm4
+ * Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPV_MP_RING_H
+#define MPV_MP_RING_H
+
+/**
+ * A simple non-blocking SPSC (single producer, single consumer) ringbuffer
+ * implementation. Thread safety is accomplished through atomic operations.
+ */
+
+struct mp_ring;
+
+/**
+ * Instantiate a new ringbuffer
+ *
+ * talloc_ctx: talloc context of the newly created object
+ * size: total size in bytes
+ * return: the newly created ringbuffer
+ */
+struct mp_ring *mp_ring_new(void *talloc_ctx, int size);
+
+/**
+ * Read data from the ringbuffer
+ *
+ * buffer: target ringbuffer instance
+ * dest: destination buffer for the read data. If NULL read data is discarded.
+ * len: maximum number of bytes to read
+ * return: number of bytes read
+ */
+int mp_ring_read(struct mp_ring *buffer, unsigned char *dest, int len);
+
+/**
+ * Read data from the ringbuffer
+ *
+ * This function behaves similarly to `av_fifo_generic_read` and was actually
+ * added for compatibility with code that was written for it.
+ * This function will drain the returned amount of bytes from the ringbuffer
+ * so you don't have to handle that in inside `func`.
+ *
+ * buffer: target ringbuffer instance
+ * ctx: context for the callback function
+ * len: maximum number of bytes to read
+ * func: callback function to customize reading behaviour. It will be called
+ * by `mp_ring_read_cb` with the following parameters:
+ * ctx: context data provided to `mp_ring_read_cb`
+ * src: source buffer to read from
+ * len: the *exact* amount of bytes to read. These will be drained
+ * by the ring after this callback is called.
+ * return: number of bytes read
+ */
+int mp_ring_read_cb(struct mp_ring *buffer, void *ctx, int len,
+ void (*func)(void *ctx, void *src, int len));
+
+/**
+ * Write data to the ringbuffer
+ *
+ * buffer: target ringbuffer instance
+ * src: source buffer for the write data
+ * len: maximum number of bytes to write
+ * return: number of bytes written
+ */
+int mp_ring_write(struct mp_ring *buffer, unsigned char *src, int len);
+
+/**
+ * Drain data from the ringbuffer
+ *
+ * buffer: target ringbuffer instance
+ * len: maximum number of bytes to drain
+ * return: number of bytes drained
+ */
+int mp_ring_drain(struct mp_ring *buffer, int len);
+
+/**
+ * Reset the ringbuffer discarding any content
+ *
+ * buffer: target ringbuffer instance
+ */
+void mp_ring_reset(struct mp_ring *buffer);
+
+/**
+ * Get the available size for writing
+ *
+ * buffer: target ringbuffer instance
+ * return: number of bytes that can be written
+ */
+int mp_ring_available(struct mp_ring *buffer);
+
+/**
+ * Get the total size
+ *
+ * buffer: target ringbuffer instance
+ * return: total ringbuffer size in bytes
+ */
+int mp_ring_size(struct mp_ring *buffer);
+
+/**
+ * Get the available size for reading
+ *
+ * buffer: target ringbuffer instance
+ * return: number of bytes ready for reading
+ */
+int mp_ring_buffered(struct mp_ring *buffer);
+
+/**
+ * Get a string representation of the ringbuffer
+ *
+ * buffer: target ringbuffer instance
+ * talloc_ctx: talloc context of the newly created string
+ * return: string representing the ringbuffer
+ */
+char *mp_ring_repr(struct mp_ring *buffer, void *talloc_ctx);
+
+#endif
diff --git a/mpvcore/mp_talloc.h b/mpvcore/mp_talloc.h
new file mode 100644
index 0000000000..1dcb0bce07
--- /dev/null
+++ b/mpvcore/mp_talloc.h
@@ -0,0 +1,61 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPV_TALLOC_H
+#define MPV_TALLOC_H
+
+#include "talloc.h"
+#include "compat/compiler.h"
+
+#define MP_TALLOC_ELEMS(p) (talloc_get_size(p) / sizeof((p)[0]))
+#define MP_GROW_ARRAY(p, nextidx) do { \
+ if ((nextidx) == MP_TALLOC_ELEMS(p)) \
+ (p) = talloc_realloc_size(NULL, p, talloc_get_size(p) * 2); } while (0)
+#define MP_RESIZE_ARRAY(ctx, p, count) do { \
+ (p) = talloc_realloc_size((ctx), p, (count) * sizeof((p)[0])); } while (0)
+
+#define MP_TARRAY_GROW(ctx, p, nextidx) \
+ do { \
+ size_t nextidx_ = (nextidx); \
+ size_t nelems_ = MP_TALLOC_ELEMS(p); \
+ if (nextidx_ >= nelems_) \
+ (p) = talloc_realloc_size(ctx, p, \
+ (nextidx_ + 1) * sizeof((p)[0]) * 2);\
+ } while (0)
+
+#define MP_TARRAY_APPEND(ctx, p, idxvar, ...) \
+ do { \
+ MP_TARRAY_GROW(ctx, p, idxvar); \
+ (p)[(idxvar)] = (MP_EXPAND_ARGS(__VA_ARGS__));\
+ (idxvar)++; \
+ } while (0)
+
+// Doesn't actually free any memory, or do any other talloc calls.
+#define MP_TARRAY_REMOVE_AT(p, idxvar, at) \
+ do { \
+ size_t at_ = (at); \
+ assert(at_ <= (idxvar)); \
+ memmove((p) + at_, (p) + at_ + 1, \
+ ((idxvar) - at_ - 1) * sizeof((p)[0])); \
+ (idxvar)--; \
+ } while (0)
+
+#define talloc_struct(ctx, type, ...) \
+ talloc_memdup(ctx, &(type) MP_EXPAND_ARGS(__VA_ARGS__), sizeof(type))
+
+#endif
diff --git a/mpvcore/mplayer.c b/mpvcore/mplayer.c
new file mode 100644
index 0000000000..d3872442ee
--- /dev/null
+++ b/mpvcore/mplayer.c
@@ -0,0 +1,4747 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+#include <ctype.h>
+
+#ifdef PTW32_STATIC_LIB
+#include <pthread.h>
+#endif
+
+#include <libavutil/intreadwrite.h>
+#include <libavutil/attributes.h>
+#include <libavutil/md5.h>
+#include <libavutil/common.h>
+
+#include <libavcodec/version.h>
+
+#include "config.h"
+#include "talloc.h"
+
+#include "osdep/io.h"
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+#include <windows.h>
+#endif
+#define WAKEUP_PERIOD 0.5
+#include <string.h>
+#include <unistd.h>
+
+// #include <sys/mman.h>
+#include <sys/types.h>
+#ifndef __MINGW32__
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#endif
+
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <time.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <errno.h>
+
+#include "core/mpv_global.h"
+#include "core/mp_msg.h"
+#include "av_log.h"
+
+
+#include "core/m_option.h"
+#include "core/m_config.h"
+#include "core/resolve.h"
+#include "core/m_property.h"
+
+#include "sub/find_subfiles.h"
+#include "sub/dec_sub.h"
+#include "sub/sd.h"
+
+#include "core/mp_osd.h"
+#include "video/out/vo.h"
+#include "core/screenshot.h"
+
+#include "sub/sub.h"
+#include "core/cpudetect.h"
+
+#ifdef CONFIG_X11
+#include "video/out/x11_common.h"
+#endif
+
+#ifdef CONFIG_COCOA
+#include "osdep/macosx_application.h"
+#endif
+
+#include "audio/out/ao.h"
+
+#include "core/codecs.h"
+
+#include "osdep/getch2.h"
+#include "osdep/timer.h"
+
+#include "core/input/input.h"
+#include "core/encode.h"
+
+#include "osdep/priority.h"
+
+#include "stream/tv.h"
+#include "stream/stream_radio.h"
+#ifdef CONFIG_DVBIN
+#include "stream/dvbin.h"
+#endif
+
+//**************************************************************************//
+// Playtree
+//**************************************************************************//
+#include "core/playlist.h"
+#include "core/playlist_parser.h"
+
+//**************************************************************************//
+// Config
+//**************************************************************************//
+#include "core/parser-cfg.h"
+#include "core/parser-mpcmd.h"
+
+//**************************************************************************//
+// Config file
+//**************************************************************************//
+
+#include "core/path.h"
+
+//**************************************************************************//
+//**************************************************************************//
+// Input media streaming & demultiplexer:
+//**************************************************************************//
+
+#include "stream/stream.h"
+#include "demux/demux.h"
+#include "demux/stheader.h"
+
+#include "audio/filter/af.h"
+#include "audio/decode/dec_audio.h"
+#include "video/decode/dec_video.h"
+#include "video/mp_image.h"
+#include "video/filter/vf.h"
+#include "video/decode/vd.h"
+
+#include "audio/mixer.h"
+
+#include "core/mp_core.h"
+#include "core/options.h"
+
+const char mp_help_text[] = _(
+"Usage: mpv [options] [url|path/]filename\n"
+"\n"
+"Basic options: (complete list in the man page)\n"
+" --start=<time> seek to given (percent, seconds, or hh:mm:ss) position\n"
+" --no-audio do not play sound\n"
+" --no-video do not play video\n"
+" --fs fullscreen playback\n"
+" --sub=<file> specify subtitle file to use\n"
+" --playlist=<file> specify playlist file\n"
+"\n");
+
+static const char av_desync_help_text[] = _(
+"\n\n"
+" *************************************************\n"
+" **** Audio/Video desynchronisation detected! ****\n"
+" *************************************************\n\n"
+"This means either the audio or the video is played too slowly.\n"
+"Possible reasons, problems, workarounds:\n"
+"- Your system is simply too slow for this file.\n"
+" Transcode it to a lower bitrate file with tools like HandBrake.\n"
+"- Broken/buggy _audio_ driver.\n"
+" Experiment with different values for --autosync, 30 is a good start.\n"
+" If you have PulseAudio, try --ao=alsa .\n"
+"- Slow video output.\n"
+" Try a different -vo driver (-vo help for a list) or try -framedrop!\n"
+"- Playing a video file with --vo=opengl with higher FPS than the monitor.\n"
+" This is due to vsync limiting the framerate.\n"
+"- Playing from a slow network source.\n"
+" Download the file instead.\n"
+"- Try to find out whether audio or video is causing this by experimenting\n"
+" with --no-video and --no-audio.\n"
+"If none of this helps you, file a bug report.\n\n");
+
+
+//**************************************************************************//
+//**************************************************************************//
+
+#include "sub/ass_mp.h"
+
+
+// ---
+
+#include "core/mp_common.h"
+#include "core/command.h"
+
+static void reset_subtitles(struct MPContext *mpctx);
+static void reinit_subs(struct MPContext *mpctx);
+
+static double get_relative_time(struct MPContext *mpctx)
+{
+ int64_t new_time = mp_time_us();
+ int64_t delta = new_time - mpctx->last_time;
+ mpctx->last_time = new_time;
+ return delta * 0.000001;
+}
+
+static double rel_time_to_abs(struct MPContext *mpctx, struct m_rel_time t,
+ double fallback_time)
+{
+ double length = get_time_length(mpctx);
+ switch (t.type) {
+ case REL_TIME_ABSOLUTE:
+ return t.pos;
+ case REL_TIME_NEGATIVE:
+ if (length != 0)
+ return FFMAX(length - t.pos, 0.0);
+ break;
+ case REL_TIME_PERCENT:
+ if (length != 0)
+ return length * (t.pos / 100.0);
+ break;
+ case REL_TIME_CHAPTER:
+ if (chapter_start_time(mpctx, t.pos) >= 0)
+ return chapter_start_time(mpctx, t.pos);
+ break;
+ }
+ return fallback_time;
+}
+
+static double get_play_end_pts(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (opts->play_end.type) {
+ return rel_time_to_abs(mpctx, opts->play_end, MP_NOPTS_VALUE);
+ } else if (opts->play_length.type) {
+ double start = rel_time_to_abs(mpctx, opts->play_start, 0);
+ double length = rel_time_to_abs(mpctx, opts->play_length, -1);
+ if (start != -1 && length != -1)
+ return start + length;
+ }
+ return MP_NOPTS_VALUE;
+}
+
+static void print_stream(struct MPContext *mpctx, struct track *t)
+{
+ struct sh_stream *s = t->stream;
+ const char *tname = "?";
+ const char *selopt = "?";
+ const char *langopt = "?";
+ const char *iid = NULL;
+ switch (t->type) {
+ case STREAM_VIDEO:
+ tname = "Video"; selopt = "vid"; langopt = NULL; iid = "VID";
+ break;
+ case STREAM_AUDIO:
+ tname = "Audio"; selopt = "aid"; langopt = "alang"; iid = "AID";
+ break;
+ case STREAM_SUB:
+ tname = "Subs"; selopt = "sid"; langopt = "slang"; iid = "SID";
+ break;
+ }
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "[stream] %-5s %3s",
+ tname, mpctx->current_track[t->type] == t ? "(+)" : "");
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%d", selopt, t->user_tid);
+ if (t->lang && langopt)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " --%s=%s", langopt, t->lang);
+ if (t->default_track)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (*)");
+ if (t->attached_picture)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " [P]");
+ if (t->title)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " '%s'", t->title);
+ const char *codec = s ? s->codec : NULL;
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (%s)", codec ? codec : "<unknown>");
+ if (t->is_external)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, " (external)");
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "\n");
+ // legacy compatibility
+ if (!iid)
+ return;
+ int id = t->user_tid;
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_ID=%d\n", iid, id);
+ if (t->title)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_%d_NAME=%s\n", iid, id, t->title);
+ if (t->lang)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_%d_LANG=%s\n", iid, id, t->lang);
+}
+
+static void print_file_properties(struct MPContext *mpctx, const char *filename)
+{
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_FILENAME=%s\n",
+ filename);
+ if (mpctx->sh_video) {
+ /* Assume FOURCC if all bytes >= 0x20 (' ') */
+ if (mpctx->sh_video->format >= 0x20202020)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FORMAT=%.4s\n", (char *)&mpctx->sh_video->format);
+ else
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FORMAT=0x%08X\n", mpctx->sh_video->format);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_BITRATE=%d\n", mpctx->sh_video->i_bps * 8);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_WIDTH=%d\n", mpctx->sh_video->disp_w);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_HEIGHT=%d\n", mpctx->sh_video->disp_h);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_FPS=%5.3f\n", mpctx->sh_video->fps);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_VIDEO_ASPECT=%1.4f\n", mpctx->sh_video->aspect);
+ }
+ if (mpctx->sh_audio) {
+ /* Assume FOURCC if all bytes >= 0x20 (' ') */
+ if (mpctx->sh_audio->format >= 0x20202020)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_FORMAT=%.4s\n", (char *)&mpctx->sh_audio->format);
+ else
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_FORMAT=%d\n", mpctx->sh_audio->format);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_BITRATE=%d\n", mpctx->sh_audio->i_bps * 8);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_RATE=%d\n", mpctx->sh_audio->samplerate);
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_AUDIO_NCH=%d\n", mpctx->sh_audio->channels.num);
+ }
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO,
+ "ID_LENGTH=%.2f\n", get_time_length(mpctx));
+ int chapter_count = get_chapter_count(mpctx);
+ if (chapter_count >= 0) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTERS=%d\n", chapter_count);
+ for (int i = 0; i < chapter_count; i++) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_ID=%d\n", i);
+ // print in milliseconds
+ double time = chapter_start_time(mpctx, i) * 1000.0;
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_%d_START=%"PRId64"\n",
+ i, (int64_t)(time < 0 ? -1 : time));
+ char *name = chapter_name(mpctx, i);
+ if (name) {
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_%d_NAME=%s\n", i,
+ name);
+ talloc_free(name);
+ }
+ }
+ }
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (demuxer->num_editions > 1)
+ mp_msg(MSGT_CPLAYER, MSGL_INFO,
+ "Playing edition %d of %d (--edition=%d).\n",
+ demuxer->edition + 1, demuxer->num_editions, demuxer->edition);
+ for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
+ for (int n = 0; n < mpctx->num_tracks; n++)
+ if (mpctx->tracks[n]->type == t)
+ print_stream(mpctx, mpctx->tracks[n]);
+ }
+}
+
+// Time used to seek external tracks to.
+static double get_main_demux_pts(struct MPContext *mpctx)
+{
+ double main_new_pos = MP_NOPTS_VALUE;
+ if (mpctx->demuxer) {
+ for (int n = 0; n < mpctx->demuxer->num_streams; n++) {
+ if (main_new_pos == MP_NOPTS_VALUE)
+ main_new_pos = demux_get_next_pts(mpctx->demuxer->streams[n]);
+ }
+ }
+ return main_new_pos;
+}
+
+static void set_demux_field(struct MPContext *mpctx, enum stream_type type,
+ struct sh_stream *s)
+{
+ mpctx->sh[type] = s;
+ // redundant fields for convenience access
+ switch(type) {
+ case STREAM_VIDEO: mpctx->sh_video = s ? s->video : NULL; break;
+ case STREAM_AUDIO: mpctx->sh_audio = s ? s->audio : NULL; break;
+ case STREAM_SUB: mpctx->sh_sub = s ? s->sub : NULL; break;
+ }
+}
+
+static void init_demux_stream(struct MPContext *mpctx, enum stream_type type)
+{
+ struct track *track = mpctx->current_track[type];
+ set_demux_field(mpctx, type, track ? track->stream : NULL);
+ struct sh_stream *stream = mpctx->sh[type];
+ if (stream) {
+ demuxer_switch_track(stream->demuxer, type, stream);
+ if (track->is_external) {
+ double pts = get_main_demux_pts(mpctx);
+ demux_seek(stream->demuxer, pts, mpctx->audio_delay, SEEK_ABSOLUTE);
+ }
+ }
+}
+
+static void cleanup_demux_stream(struct MPContext *mpctx, enum stream_type type)
+{
+ struct sh_stream *stream = mpctx->sh[type];
+ if (stream)
+ demuxer_switch_track(stream->demuxer, type, NULL);
+ set_demux_field(mpctx, type, NULL);
+}
+
+// Switch the demuxers to current track selection. This is possibly important
+// for intialization: if something reads packets from the demuxer (like at least
+// reinit_audio_chain does, or when seeking), packets from the other streams
+// should be queued instead of discarded. So all streams should be enabled
+// before the first initialization function is called.
+static void preselect_demux_streams(struct MPContext *mpctx)
+{
+ // Disable all streams, just to be sure no unwanted streams are selected.
+ for (int n = 0; n < mpctx->num_sources; n++) {
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ if (!(track && track->demuxer == mpctx->sources[n] &&
+ demuxer_stream_is_selected(track->demuxer, track->stream)))
+ demuxer_switch_track(mpctx->sources[n], type, NULL);
+ }
+ }
+
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ if (track && track->stream)
+ demuxer_switch_track(track->stream->demuxer, type, track->stream);
+ }
+}
+
+static void uninit_subs(struct demuxer *demuxer)
+{
+ for (int i = 0; i < demuxer->num_streams; i++) {
+ struct sh_stream *sh = demuxer->streams[i];
+ if (sh->sub) {
+ sub_destroy(sh->sub->dec_sub);
+ sh->sub->dec_sub = NULL;
+ }
+ }
+}
+
+void uninit_player(struct MPContext *mpctx, unsigned int mask)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ mask &= mpctx->initialized_flags;
+
+ mp_msg(MSGT_CPLAYER, MSGL_DBG2, "\n*** uninit(0x%X)\n", mask);
+
+ if (mask & INITIALIZED_ACODEC) {
+ mpctx->initialized_flags &= ~INITIALIZED_ACODEC;
+ if (mpctx->sh_audio)
+ uninit_audio(mpctx->sh_audio);
+ cleanup_demux_stream(mpctx, STREAM_AUDIO);
+ mpctx->mixer.afilter = NULL;
+ }
+
+ if (mask & INITIALIZED_SUB) {
+ mpctx->initialized_flags &= ~INITIALIZED_SUB;
+ if (mpctx->sh_sub)
+ sub_reset(mpctx->sh_sub->dec_sub);
+ cleanup_demux_stream(mpctx, STREAM_SUB);
+ mpctx->osd->dec_sub = NULL;
+ reset_subtitles(mpctx);
+ }
+
+ if (mask & INITIALIZED_VCODEC) {
+ mpctx->initialized_flags &= ~INITIALIZED_VCODEC;
+ if (mpctx->sh_video)
+ uninit_video(mpctx->sh_video);
+ cleanup_demux_stream(mpctx, STREAM_VIDEO);
+ mpctx->sync_audio_to_video = false;
+ }
+
+ if (mask & INITIALIZED_DEMUXER) {
+ mpctx->initialized_flags &= ~INITIALIZED_DEMUXER;
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ talloc_free(mpctx->tracks[i]);
+ }
+ mpctx->num_tracks = 0;
+ for (int t = 0; t < STREAM_TYPE_COUNT; t++)
+ mpctx->current_track[t] = NULL;
+ assert(!mpctx->sh_video && !mpctx->sh_audio && !mpctx->sh_sub);
+ mpctx->master_demuxer = NULL;
+ for (int i = 0; i < mpctx->num_sources; i++) {
+ uninit_subs(mpctx->sources[i]);
+ struct demuxer *demuxer = mpctx->sources[i];
+ if (demuxer->stream != mpctx->stream)
+ free_stream(demuxer->stream);
+ free_demuxer(demuxer);
+ }
+ talloc_free(mpctx->sources);
+ mpctx->sources = 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;
+ mpctx->video_offset = 0;
+ }
+
+ // kill the cache process:
+ if (mask & INITIALIZED_STREAM) {
+ mpctx->initialized_flags &= ~INITIALIZED_STREAM;
+ if (mpctx->stream)
+ free_stream(mpctx->stream);
+ mpctx->stream = NULL;
+ }
+
+ if (mask & INITIALIZED_VO) {
+ mpctx->initialized_flags &= ~INITIALIZED_VO;
+ vo_destroy(mpctx->video_out);
+ mpctx->video_out = NULL;
+ }
+
+ // Must be after libvo uninit, as few vo drivers (svgalib) have tty code.
+ if (mask & INITIALIZED_GETCH2) {
+ mpctx->initialized_flags &= ~INITIALIZED_GETCH2;
+ mp_msg(MSGT_CPLAYER, MSGL_DBG2, "\n[[[uninit getch2]]]\n");
+ // restore terminal:
+ getch2_disable();
+ }
+
+ if (mask & INITIALIZED_VOL) {
+ mpctx->initialized_flags &= ~INITIALIZED_VOL;
+ if (mpctx->mixer.ao) {
+ // Normally the mixer remembers volume, but do it even if the
+ // volume is set explicitly with --volume=... (so that the same
+ // volume is restored on reinit)
+ if (opts->mixer_init_volume >= 0 && mpctx->mixer.user_set_volume)
+ mixer_getbothvolume(&mpctx->mixer, &opts->mixer_init_volume);
+ if (opts->mixer_init_mute >= 0 && mpctx->mixer.user_set_mute)
+ opts->mixer_init_mute = mixer_getmute(&mpctx->mixer);
+ }
+ }
+
+ if (mask & INITIALIZED_AO) {
+ mpctx->initialized_flags &= ~INITIALIZED_AO;
+ if (mpctx->mixer.ao)
+ mixer_uninit(&mpctx->mixer);
+ mpctx->mixer.ao = NULL;
+ if (mpctx->ao)
+ ao_uninit(mpctx->ao, mpctx->stop_play != AT_END_OF_FILE);
+ mpctx->ao = NULL;
+ }
+}
+
+static MP_NORETURN void exit_player(struct MPContext *mpctx,
+ enum exit_reason how)
+{
+ int rc;
+ uninit_player(mpctx, INITIALIZED_ALL);
+
+#ifdef CONFIG_ENCODING
+ encode_lavc_finish(mpctx->encode_lavc_ctx);
+ encode_lavc_free(mpctx->encode_lavc_ctx);
+#endif
+
+ mpctx->encode_lavc_ctx = NULL;
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ timeEndPeriod(1);
+#endif
+
+ mp_input_uninit(mpctx->input);
+
+ osd_free(mpctx->osd);
+
+#ifdef CONFIG_ASS
+ ass_library_done(mpctx->ass_library);
+ mpctx->ass_library = NULL;
+#endif
+
+ if (how != EXIT_NONE) {
+ const char *reason;
+ switch (how) {
+ case EXIT_SOMENOTPLAYED:
+ case EXIT_PLAYED:
+ reason = "End of file";
+ break;
+ case EXIT_NOTPLAYED:
+ reason = "No files played";
+ break;
+ case EXIT_ERROR:
+ reason = "Fatal error";
+ break;
+ default:
+ reason = "Quit";
+ }
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "\nExiting... (%s)\n", reason);
+ }
+
+ if (mpctx->has_quit_custom_rc) {
+ rc = mpctx->quit_custom_rc;
+ } else {
+ switch (how) {
+ case EXIT_ERROR:
+ rc = 1; break;
+ case EXIT_NOTPLAYED:
+ rc = 2; break;
+ case EXIT_SOMENOTPLAYED:
+ rc = 3; break;
+ default:
+ rc = 0;
+ }
+ }
+
+ // must be last since e.g. mp_msg uses option values
+ // that will be freed by this.
+
+ mp_msg_uninit(mpctx->global);
+ talloc_free(mpctx);
+
+#ifdef CONFIG_COCOA
+ terminate_cocoa_application();
+ // never reach here:
+ // terminate calls exit itself, just silence compiler warning
+ exit(0);
+#else
+ exit(rc);
+#endif
+}
+
+static void mk_config_dir(char *subdir)
+{
+ void *tmp = talloc_new(NULL);
+ char *confdir = talloc_steal(tmp, mp_find_user_config_file(""));
+ if (subdir)
+ confdir = mp_path_join(tmp, bstr0(confdir), bstr0(subdir));
+ mkdir(confdir, 0777);
+ talloc_free(tmp);
+}
+
+static int cfg_include(struct m_config *conf, char *filename, int flags)
+{
+ return m_config_parse_config_file(conf, filename, flags);
+}
+
+#define DEF_CONFIG "# Write your default config options here!\n\n\n"
+
+static bool parse_cfgfiles(struct MPContext *mpctx, m_config_t *conf)
+{
+ struct MPOpts *opts = mpctx->opts;
+ char *conffile;
+ int conffile_fd;
+ if (!opts->load_config)
+ return true;
+ if (!m_config_parse_config_file(conf, MPLAYER_CONFDIR "/mpv.conf", 0) < 0)
+ return false;
+ mk_config_dir(NULL);
+ if ((conffile = mp_find_user_config_file("config")) == NULL)
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR,
+ "mp_find_user_config_file(\"config\") problem\n");
+ else {
+ if ((conffile_fd = open(conffile, O_CREAT | O_EXCL | O_WRONLY,
+ 0666)) != -1) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Creating config file: %s\n", conffile);
+ write(conffile_fd, DEF_CONFIG, sizeof(DEF_CONFIG) - 1);
+ close(conffile_fd);
+ }
+ if (m_config_parse_config_file(conf, conffile, 0) < 0)
+ return false;
+ talloc_free(conffile);
+ }
+ return true;
+}
+
+#define PROFILE_CFG_PROTOCOL "protocol."
+
+static void load_per_protocol_config(m_config_t *conf, const char * const file)
+{
+ char *str;
+ char protocol[strlen(PROFILE_CFG_PROTOCOL) + strlen(file) + 1];
+ m_profile_t *p;
+
+ /* does filename actually uses a protocol ? */
+ str = strstr(file, "://");
+ if (!str)
+ return;
+
+ sprintf(protocol, "%s%s", PROFILE_CFG_PROTOCOL, file);
+ protocol[strlen(PROFILE_CFG_PROTOCOL) + strlen(file) - strlen(str)] = '\0';
+ p = m_config_get_profile0(conf, protocol);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading protocol-related profile '%s'\n", protocol);
+ m_config_set_profile(conf, p, M_SETOPT_BACKUP);
+ }
+}
+
+#define PROFILE_CFG_EXTENSION "extension."
+
+static void load_per_extension_config(m_config_t *conf, const char * const file)
+{
+ char *str;
+ char extension[strlen(PROFILE_CFG_EXTENSION) + 8];
+ m_profile_t *p;
+
+ /* does filename actually have an extension ? */
+ str = strrchr(file, '.');
+ if (!str)
+ return;
+
+ sprintf(extension, PROFILE_CFG_EXTENSION);
+ strncat(extension, ++str, 7);
+ p = m_config_get_profile0(conf, extension);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading extension-related profile '%s'\n", extension);
+ m_config_set_profile(conf, p, M_SETOPT_BACKUP);
+ }
+}
+
+#define PROFILE_CFG_VO "vo."
+#define PROFILE_CFG_AO "ao."
+
+static void load_per_output_config(m_config_t *conf, char *cfg, char *out)
+{
+ char profile[strlen(cfg) + strlen(out) + 1];
+ m_profile_t *p;
+
+ if (!out && !out[0])
+ return;
+
+ sprintf(profile, "%s%s", cfg, out);
+ p = m_config_get_profile0(conf, profile);
+ if (p) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "Loading extension-related profile '%s'\n", profile);
+ m_config_set_profile(conf, p, M_SETOPT_BACKUP);
+ }
+}
+
+/**
+ * Tries to load a config file (in file local mode)
+ * @return 0 if file was not found, 1 otherwise
+ */
+static int try_load_config(m_config_t *conf, const char *file)
+{
+ if (!mp_path_exists(file))
+ return 0;
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Loading config '%s'\n", file);
+ m_config_parse_config_file(conf, file, M_SETOPT_BACKUP);
+ return 1;
+}
+
+static void load_per_file_config(m_config_t *conf, const char * const file,
+ bool search_file_dir)
+{
+ char *confpath;
+ char cfg[MP_PATH_MAX];
+ const char *name;
+
+ if (strlen(file) > MP_PATH_MAX - 14) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Filename is too long, "
+ "can not load file or directory specific config files\n");
+ return;
+ }
+ sprintf(cfg, "%s.conf", file);
+
+ name = mp_basename(cfg);
+ if (search_file_dir) {
+ char dircfg[MP_PATH_MAX];
+ strcpy(dircfg, cfg);
+ strcpy(dircfg + (name - cfg), "mpv.conf");
+ try_load_config(conf, dircfg);
+
+ if (try_load_config(conf, cfg))
+ return;
+ }
+
+ if ((confpath = mp_find_user_config_file(name)) != NULL) {
+ try_load_config(conf, confpath);
+
+ talloc_free(confpath);
+ }
+}
+
+static bool might_be_an_url(bstr f)
+{
+ return bstr_find0(f, "://") >= 0;
+}
+
+#define MP_WATCH_LATER_CONF "watch_later"
+
+static char *get_playback_resume_config_filename(const char *fname)
+{
+ char *res = NULL;
+ void *tmp = talloc_new(NULL);
+ const char *realpath = fname;
+ if (!might_be_an_url(bstr0(fname))) {
+ char *cwd = mp_getcwd(tmp);
+ if (!cwd)
+ goto exit;
+ realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname));
+ }
+ uint8_t md5[16];
+ av_md5_sum(md5, realpath, strlen(realpath));
+ char *conf = talloc_strdup(tmp, "");
+ for (int i = 0; i < 16; i++)
+ conf = talloc_asprintf_append(conf, "%02X", md5[i]);
+
+ conf = talloc_asprintf(tmp, "%s/%s", MP_WATCH_LATER_CONF, conf);
+
+ res = mp_find_user_config_file(conf);
+
+exit:
+ talloc_free(tmp);
+ return res;
+}
+
+static const char *backup_properties[] = {
+ "osd-level",
+ //"loop",
+ "speed",
+ "edition",
+ "pause",
+ //"volume",
+ //"mute",
+ "audio-delay",
+ //"balance",
+ "fullscreen",
+ "colormatrix",
+ "colormatrix-input-range",
+ "colormatrix-output-range",
+ "ontop",
+ "border",
+ "gamma",
+ "brightness",
+ "contrast",
+ "saturation",
+ "hue",
+ "panscan",
+ "aid",
+ "vid",
+ "sid",
+ "sub-delay",
+ "sub-pos",
+ "sub-visibility",
+ "sub-scale",
+ "ass-use-margins",
+ "ass-vsfilter-aspect-compat",
+ "ass-style-override",
+ 0
+};
+
+void mp_write_watch_later_conf(struct MPContext *mpctx)
+{
+ void *tmp = talloc_new(NULL);
+ char *filename = mpctx->filename;
+ if (!filename)
+ goto exit;
+
+ double pos = get_current_time(mpctx);
+ int percent = get_percent_pos(mpctx);
+ if (percent < 1 || percent > 99 || pos == MP_NOPTS_VALUE)
+ goto exit;
+
+ mk_config_dir(MP_WATCH_LATER_CONF);
+
+ char *conffile = get_playback_resume_config_filename(mpctx->filename);
+ talloc_steal(tmp, conffile);
+ if (!conffile)
+ goto exit;
+
+ FILE *file = fopen(conffile, "wb");
+ if (!file)
+ goto exit;
+ fprintf(file, "start=%f\n", pos);
+ for (int i = 0; backup_properties[i]; i++) {
+ const char *pname = backup_properties[i];
+ char *val = NULL;
+ int r = mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
+ if (r == M_PROPERTY_OK)
+ fprintf(file, "%s=%s\n", pname, val);
+ talloc_free(val);
+ }
+ fclose(file);
+
+exit:
+ talloc_free(tmp);
+}
+
+static void load_playback_resume(m_config_t *conf, const char *file)
+{
+ char *fname = get_playback_resume_config_filename(file);
+ if (fname) {
+ try_load_config(conf, fname);
+ unlink(fname);
+ }
+ talloc_free(fname);
+}
+
+static void load_per_file_options(m_config_t *conf,
+ struct playlist_param *params,
+ int params_count)
+{
+ for (int n = 0; n < params_count; n++) {
+ m_config_set_option_ext(conf, params[n].name, params[n].value,
+ M_SETOPT_BACKUP);
+ }
+}
+
+/* When demux performs a blocking operation (network connection or
+ * cache filling) if the operation fails we use this function to check
+ * if it was interrupted by the user.
+ * The function returns whether it was interrupted. */
+static bool demux_was_interrupted(struct MPContext *mpctx)
+{
+ for (;;) {
+ if (mpctx->stop_play != KEEP_PLAYING
+ && mpctx->stop_play != AT_END_OF_FILE)
+ return true;
+ mp_cmd_t *cmd = mp_input_get_cmd(mpctx->input, 0, 0);
+ if (!cmd)
+ break;
+ if (mp_input_is_abort_cmd(cmd->id))
+ run_command(mpctx, cmd);
+ mp_cmd_free(cmd);
+ }
+ return false;
+}
+
+static int find_new_tid(struct MPContext *mpctx, enum stream_type t)
+{
+ int new_id = -1;
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ struct track *track = mpctx->tracks[i];
+ if (track->type == t)
+ new_id = FFMAX(new_id, track->user_tid);
+ }
+ return new_id + 1;
+}
+
+// Map stream number (as used by libdvdread) to MPEG IDs (as used by demuxer).
+static int map_id_from_demuxer(struct demuxer *d, enum stream_type type, int id)
+{
+ if (d->stream->uncached_type == STREAMTYPE_DVD && type == STREAM_SUB)
+ id = id & 0x1F;
+ return id;
+}
+
+static struct track *add_stream_track(struct MPContext *mpctx,
+ struct sh_stream *stream,
+ bool under_timeline)
+{
+ for (int i = 0; i < mpctx->num_tracks; i++) {
+ struct track *track = mpctx->tracks[i];
+ if (track->stream == stream)
+ return track;
+ // DVD subtitle track that was added later
+ if (stream->type == STREAM_SUB && track->type == STREAM_SUB &&
+ map_id_from_demuxer(stream->demuxer, stream->type,
+ stream->demuxer_id) == track->demuxer_id
+ && !track->stream)
+ {
+ track->stream = stream;
+ track->demuxer_id = stream->demuxer_id;
+ // Initialize lazily selected track
+ bool selected = track == mpctx->current_track[STREAM_SUB];
+ demuxer_select_track(track->demuxer, stream, selected);
+ if (selected)
+ reinit_subs(mpctx);
+ return track;
+ }
+ }
+
+ struct track *track = talloc_ptrtype(NULL, track);
+ *track = (struct track) {
+ .type = stream->type,
+ .user_tid = find_new_tid(mpctx, stream->type),
+ .demuxer_id = stream->demuxer_id,
+ .title = stream->title,
+ .default_track = stream->default_track,
+ .attached_picture = stream->attached_picture != NULL,
+ .lang = stream->lang,
+ .under_timeline = under_timeline,
+ .demuxer = stream->demuxer,
+ .stream = stream,
+ };
+ MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
+
+ if (stream->type == STREAM_SUB)
+ track->preloaded = !!stream->sub->track;
+
+ // Needed for DVD and Blu-ray.
+ if (!track->lang) {
+ struct stream_lang_req req = {
+ .type = track->type,
+ .id = map_id_from_demuxer(track->demuxer, track->type,
+ track->demuxer_id)
+ };
+ stream_control(track->demuxer->stream, STREAM_CTRL_GET_LANG, &req);
+ if (req.name[0])
+ track->lang = talloc_strdup(track, req.name);
+ }
+
+ demuxer_select_track(track->demuxer, stream, false);
+
+ return track;
+}
+
+static void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer)
+{
+ for (int n = 0; n < demuxer->num_streams; n++)
+ add_stream_track(mpctx, demuxer->streams[n], !!mpctx->timeline);
+}
+
+static void add_dvd_tracks(struct MPContext *mpctx)
+{
+#ifdef CONFIG_DVDREAD
+ struct demuxer *demuxer = mpctx->demuxer;
+ struct stream *stream = demuxer->stream;
+ struct stream_dvd_info_req info;
+ if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
+ for (int n = 0; n < info.num_subs; n++) {
+ struct track *track = talloc_ptrtype(NULL, track);
+ *track = (struct track) {
+ .type = STREAM_SUB,
+ .user_tid = find_new_tid(mpctx, STREAM_SUB),
+ .demuxer_id = n,
+ .demuxer = mpctx->demuxer,
+ };
+ MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
+
+ struct stream_lang_req req = {.type = STREAM_SUB, .id = n};
+ stream_control(stream, STREAM_CTRL_GET_LANG, &req);
+ track->lang = talloc_strdup(track, req.name);
+ }
+ }
+ demuxer_enable_autoselect(demuxer);
+#endif
+}
+
+int mp_get_cache_percent(struct MPContext *mpctx)
+{
+ if (mpctx->stream) {
+ int64_t size = -1;
+ int64_t fill = -1;
+ stream_control(mpctx->stream, STREAM_CTRL_GET_CACHE_SIZE, &size);
+ stream_control(mpctx->stream, STREAM_CTRL_GET_CACHE_FILL, &fill);
+ if (size > 0 && fill >= 0)
+ return fill / (size / 100);
+ }
+ return -1;
+}
+
+static bool mp_get_cache_idle(struct MPContext *mpctx)
+{
+ int idle = 0;
+ if (mpctx->stream)
+ stream_control(mpctx->stream, STREAM_CTRL_GET_CACHE_IDLE, &idle);
+ return idle;
+}
+
+static void vo_update_window_title(struct MPContext *mpctx)
+{
+ if (!mpctx->video_out)
+ return;
+ char *title = mp_property_expand_string(mpctx, mpctx->opts->wintitle);
+ if (!mpctx->video_out->window_title ||
+ strcmp(title, mpctx->video_out->window_title))
+ {
+ talloc_free(mpctx->video_out->window_title);
+ mpctx->video_out->window_title = talloc_steal(mpctx, title);
+ vo_control(mpctx->video_out, VOCTRL_UPDATE_WINDOW_TITLE, title);
+ } else {
+ talloc_free(title);
+ }
+}
+
+#define saddf(var, ...) (*(var) = talloc_asprintf_append((*var), __VA_ARGS__))
+
+// append time in the hh:mm:ss format (plus fractions if wanted)
+static void sadd_hhmmssff(char **buf, double time, bool fractions)
+{
+ char *s = mp_format_time(time, fractions);
+ *buf = talloc_strdup_append(*buf, s);
+ talloc_free(s);
+}
+
+static void sadd_percentage(char **buf, int percent) {
+ if (percent >= 0)
+ *buf = talloc_asprintf_append(*buf, " (%d%%)", percent);
+}
+
+static int get_term_width(void)
+{
+ get_screen_size();
+ int width = screen_width > 0 ? screen_width : 80;
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ /* Windows command line is broken (MinGW's rxvt works, but we
+ * should not depend on that). */
+ width--;
+#endif
+ return width;
+}
+
+static void write_status_line(struct MPContext *mpctx, const char *line)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (opts->slave_mode) {
+ mp_msg(MSGT_STATUSLINE, MSGL_STATUS, "%s\n", line);
+ } else if (erase_to_end_of_line) {
+ mp_msg(MSGT_STATUSLINE, MSGL_STATUS,
+ "%s%s\r", line, erase_to_end_of_line);
+ } else {
+ int pos = strlen(line);
+ int width = get_term_width() - pos;
+ mp_msg(MSGT_STATUSLINE, MSGL_STATUS, "%s%*s\r", line, width, "");
+ }
+}
+
+static void print_status(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ sh_video_t * const sh_video = mpctx->sh_video;
+
+ vo_update_window_title(mpctx);
+
+ if (opts->quiet)
+ return;
+
+ if (opts->status_msg) {
+ char *r = mp_property_expand_string(mpctx, opts->status_msg);
+ write_status_line(mpctx, r);
+ talloc_free(r);
+ return;
+ }
+
+ char *line = NULL;
+
+ // Playback status
+ if (mpctx->paused_for_cache && !opts->pause) {
+ saddf(&line, "(Buffering) ");
+ } else if (mpctx->paused) {
+ saddf(&line, "(Paused) ");
+ }
+
+ if (mpctx->sh_audio)
+ saddf(&line, "A");
+ if (mpctx->sh_video)
+ saddf(&line, "V");
+ saddf(&line, ": ");
+
+ // Playback position
+ double cur = get_current_time(mpctx);
+ sadd_hhmmssff(&line, cur, mpctx->opts->osd_fractions);
+
+ double len = get_time_length(mpctx);
+ if (len >= 0) {
+ saddf(&line, " / ");
+ sadd_hhmmssff(&line, len, mpctx->opts->osd_fractions);
+ }
+
+ sadd_percentage(&line, get_percent_pos(mpctx));
+
+ // other
+ if (opts->playback_speed != 1)
+ saddf(&line, " x%4.2f", opts->playback_speed);
+
+ // A-V sync
+ if (mpctx->sh_audio && sh_video && mpctx->sync_audio_to_video) {
+ if (mpctx->last_av_difference != MP_NOPTS_VALUE)
+ saddf(&line, " A-V:%7.3f", mpctx->last_av_difference);
+ else
+ saddf(&line, " A-V: ???");
+ if (fabs(mpctx->total_avsync_change) > 0.05)
+ saddf(&line, " ct:%7.3f", mpctx->total_avsync_change);
+ }
+
+#ifdef CONFIG_ENCODING
+ double position = get_current_pos_ratio(mpctx, true);
+ char lavcbuf[80];
+ if (encode_lavc_getstatus(mpctx->encode_lavc_ctx, lavcbuf, sizeof(lavcbuf),
+ position) >= 0)
+ {
+ // encoding stats
+ saddf(&line, " %s", lavcbuf);
+ } else
+#endif
+ {
+ // VO stats
+ if (sh_video && mpctx->drop_frame_cnt)
+ saddf(&line, " D: %d", mpctx->drop_frame_cnt);
+ }
+
+ int cache = mp_get_cache_percent(mpctx);
+ if (cache >= 0)
+ saddf(&line, " Cache: %d%%", cache);
+
+ // end
+ write_status_line(mpctx, line);
+ talloc_free(line);
+}
+
+typedef struct mp_osd_msg mp_osd_msg_t;
+struct mp_osd_msg {
+ /// Previous message on the stack.
+ mp_osd_msg_t *prev;
+ /// Message text.
+ char *msg;
+ int id, level, started;
+ /// Display duration in seconds.
+ double time;
+ // Show full OSD for duration of message instead of msg
+ // (osd_show_progression command)
+ bool show_position;
+};
+
+// time is in ms
+static mp_osd_msg_t *add_osd_msg(struct MPContext *mpctx, int id, int level,
+ int time)
+{
+ rm_osd_msg(mpctx, id);
+ mp_osd_msg_t *msg = talloc_struct(mpctx, mp_osd_msg_t, {
+ .prev = mpctx->osd_msg_stack,
+ .msg = "",
+ .id = id,
+ .level = level,
+ .time = time / 1000.0,
+ });
+ mpctx->osd_msg_stack = msg;
+ return msg;
+}
+
+static void set_osd_msg_va(struct MPContext *mpctx, int id, int level, int time,
+ const char *fmt, va_list ap)
+{
+ if (level == OSD_LEVEL_INVISIBLE)
+ return;
+ mp_osd_msg_t *msg = add_osd_msg(mpctx, id, level, time);
+ msg->msg = talloc_vasprintf(msg, fmt, ap);
+}
+
+void set_osd_msg(struct MPContext *mpctx, int id, int level, int time,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ set_osd_msg_va(mpctx, id, level, time, fmt, ap);
+ va_end(ap);
+}
+
+void set_osd_tmsg(struct MPContext *mpctx, int id, int level, int time,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ set_osd_msg_va(mpctx, id, level, time, mp_gtext(fmt), ap);
+ va_end(ap);
+}
+
+/**
+ * \brief Remove a message from the OSD stack
+ *
+ * This function can be used to get rid of a message right away.
+ *
+ */
+
+void rm_osd_msg(struct MPContext *mpctx, int id)
+{
+ mp_osd_msg_t *msg, *last = NULL;
+
+ // Search for the msg
+ for (msg = mpctx->osd_msg_stack; msg && msg->id != id;
+ last = msg, msg = msg->prev) ;
+ if (!msg)
+ return;
+
+ // Detach it from the stack and free it
+ if (last)
+ last->prev = msg->prev;
+ else
+ mpctx->osd_msg_stack = msg->prev;
+ talloc_free(msg);
+}
+
+/**
+ * \brief Get the current message from the OSD stack.
+ *
+ * This function decrements the message timer and destroys the old ones.
+ * The message that should be displayed is returned (if any).
+ *
+ */
+
+static mp_osd_msg_t *get_osd_msg(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ mp_osd_msg_t *msg, *prev, *last = NULL;
+ double now = mp_time_sec();
+ double diff;
+ char hidden_dec_done = 0;
+
+ if (mpctx->osd_visible && now >= mpctx->osd_visible) {
+ mpctx->osd_visible = 0;
+ mpctx->osd->progbar_type = -1; // disable
+ osd_changed(mpctx->osd, OSDTYPE_PROGBAR);
+ }
+ if (mpctx->osd_function_visible && now >= mpctx->osd_function_visible) {
+ mpctx->osd_function_visible = 0;
+ mpctx->osd_function = 0;
+ }
+
+ if (!mpctx->osd_last_update)
+ mpctx->osd_last_update = now;
+ diff = now >= mpctx->osd_last_update ? now - mpctx->osd_last_update : 0;
+
+ mpctx->osd_last_update = now;
+
+ // Look for the first message in the stack with high enough level.
+ for (msg = mpctx->osd_msg_stack; msg; last = msg, msg = prev) {
+ prev = msg->prev;
+ if (msg->level > opts->osd_level && hidden_dec_done)
+ continue;
+ // The message has a high enough level or it is the first hidden one
+ // in both cases we decrement the timer or kill it.
+ if (!msg->started || msg->time > diff) {
+ if (msg->started)
+ msg->time -= diff;
+ else
+ msg->started = 1;
+ // display it
+ if (msg->level <= opts->osd_level)
+ return msg;
+ hidden_dec_done = 1;
+ continue;
+ }
+ // kill the message
+ talloc_free(msg);
+ if (last) {
+ last->prev = prev;
+ msg = last;
+ } else {
+ mpctx->osd_msg_stack = prev;
+ msg = NULL;
+ }
+ }
+ // Nothing found
+ return NULL;
+}
+
+// type: mp_osd_font_codepoints, ASCII, or OSD_BAR_*
+// name: fallback for terminal OSD
+void set_osd_bar(struct MPContext *mpctx, int type, const char *name,
+ double min, double max, double val)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (opts->osd_level < 1 || !opts->osd_bar_visible)
+ return;
+
+ if (mpctx->sh_video && opts->term_osd != 1) {
+ mpctx->osd_visible = mp_time_sec() + opts->osd_duration / 1000.0;
+ mpctx->osd->progbar_type = type;
+ mpctx->osd->progbar_value = (val - min) / (max - min);
+ mpctx->osd->progbar_num_stops = 0;
+ osd_changed(mpctx->osd, OSDTYPE_PROGBAR);
+ return;
+ }
+
+ set_osd_msg(mpctx, OSD_MSG_BAR, 1, opts->osd_duration, "%s: %d %%",
+ name, ROUND(100 * (val - min) / (max - min)));
+}
+
+// Update a currently displayed bar of the same type, without resetting the
+// timer.
+static void update_osd_bar(struct MPContext *mpctx, int type,
+ double min, double max, double val)
+{
+ if (mpctx->osd->progbar_type == type) {
+ float new_value = (val - min) / (max - min);
+ if (new_value != mpctx->osd->progbar_value) {
+ mpctx->osd->progbar_value = new_value;
+ osd_changed(mpctx->osd, OSDTYPE_PROGBAR);
+ }
+ }
+}
+
+static void set_osd_bar_chapters(struct MPContext *mpctx, int type)
+{
+ struct osd_state *osd = mpctx->osd;
+ osd->progbar_num_stops = 0;
+ if (osd->progbar_type == type) {
+ double len = get_time_length(mpctx);
+ if (len > 0) {
+ int num = get_chapter_count(mpctx);
+ for (int n = 0; n < num; n++) {
+ double time = chapter_start_time(mpctx, n);
+ if (time >= 0) {
+ float pos = time / len;
+ MP_TARRAY_APPEND(osd, osd->progbar_stops,
+ osd->progbar_num_stops, pos);
+ }
+ }
+ }
+ }
+}
+
+void set_osd_function(struct MPContext *mpctx, int osd_function)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ mpctx->osd_function = osd_function;
+ mpctx->osd_function_visible = mp_time_sec() + opts->osd_duration / 1000.0;
+}
+
+/**
+ * \brief Display text subtitles on the OSD
+ */
+static void set_osd_subtitle(struct MPContext *mpctx, const char *text)
+{
+ if (!text)
+ text = "";
+ if (strcmp(mpctx->osd->sub_text, text) != 0) {
+ osd_set_sub(mpctx->osd, text);
+ if (!mpctx->sh_video) {
+ rm_osd_msg(mpctx, OSD_MSG_SUB_BASE);
+ if (text && text[0])
+ set_osd_msg(mpctx, OSD_MSG_SUB_BASE, 1, INT_MAX, "%s", text);
+ }
+ }
+ if (!text[0])
+ rm_osd_msg(mpctx, OSD_MSG_SUB_BASE);
+}
+
+// sym == mpctx->osd_function
+static void saddf_osd_function_sym(char **buffer, int sym)
+{
+ char temp[10];
+ osd_get_function_sym(temp, sizeof(temp), sym);
+ saddf(buffer, "%s ", temp);
+}
+
+static void sadd_osd_status(char **buffer, struct MPContext *mpctx, bool full)
+{
+ bool fractions = mpctx->opts->osd_fractions;
+ int sym = mpctx->osd_function;
+ if (!sym) {
+ if (mpctx->paused_for_cache && !mpctx->opts->pause) {
+ sym = OSD_CLOCK;
+ } else if (mpctx->paused || mpctx->step_frames) {
+ sym = OSD_PAUSE;
+ } else {
+ sym = OSD_PLAY;
+ }
+ }
+ saddf_osd_function_sym(buffer, sym);
+ char *custom_msg = mpctx->opts->osd_status_msg;
+ if (custom_msg && full) {
+ char *text = mp_property_expand_string(mpctx, custom_msg);
+ *buffer = talloc_strdup_append(*buffer, text);
+ talloc_free(text);
+ } else {
+ sadd_hhmmssff(buffer, get_current_time(mpctx), fractions);
+ if (full) {
+ saddf(buffer, " / ");
+ sadd_hhmmssff(buffer, get_time_length(mpctx), fractions);
+ sadd_percentage(buffer, get_percent_pos(mpctx));
+ int cache = mp_get_cache_percent(mpctx);
+ if (cache >= 0)
+ saddf(buffer, " Cache: %d%%", cache);
+ }
+ }
+}
+
+// OSD messages initated by seeking commands are added lazily with this
+// function, because multiple successive seek commands can be coalesced.
+static void add_seek_osd_messages(struct MPContext *mpctx)
+{
+ if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_BAR) {
+ set_osd_bar(mpctx, OSD_BAR_SEEK, "Position", 0, 1,
+ av_clipf(get_current_pos_ratio(mpctx, false), 0, 1));
+ set_osd_bar_chapters(mpctx, OSD_BAR_SEEK);
+ }
+ if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_TEXT) {
+ mp_osd_msg_t *msg = add_osd_msg(mpctx, OSD_MSG_TEXT, 1,
+ mpctx->opts->osd_duration);
+ msg->show_position = true;
+ }
+ if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_CHAPTER_TEXT) {
+ char *chapter = chapter_display_name(mpctx, get_current_chapter(mpctx));
+ set_osd_tmsg(mpctx, OSD_MSG_TEXT, 1, mpctx->opts->osd_duration,
+ "Chapter: %s", chapter);
+ talloc_free(chapter);
+ }
+ if ((mpctx->add_osd_seek_info & OSD_SEEK_INFO_EDITION)
+ && mpctx->master_demuxer)
+ {
+ set_osd_tmsg(mpctx, OSD_MSG_TEXT, 1, mpctx->opts->osd_duration,
+ "Playing edition %d of %d.",
+ mpctx->master_demuxer->edition + 1,
+ mpctx->master_demuxer->num_editions);
+ }
+ mpctx->add_osd_seek_info = 0;
+}
+
+/**
+ * \brief Update the OSD message line.
+ *
+ * This function displays the current message on the vo OSD or on the term.
+ * If the stack is empty and the OSD level is high enough the timer
+ * is displayed (only on the vo OSD).
+ *
+ */
+
+static void update_osd_msg(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct osd_state *osd = mpctx->osd;
+
+ add_seek_osd_messages(mpctx);
+ update_osd_bar(mpctx, OSD_BAR_SEEK, 0, 1,
+ av_clipf(get_current_pos_ratio(mpctx, false), 0, 1));
+
+ // Look if we have a msg
+ mp_osd_msg_t *msg = get_osd_msg(mpctx);
+ if (msg && !msg->show_position) {
+ if (mpctx->sh_video && opts->term_osd != 1) {
+ osd_set_text(osd, msg->msg);
+ } else if (opts->term_osd) {
+ if (strcmp(mpctx->terminal_osd_text, msg->msg)) {
+ talloc_free(mpctx->terminal_osd_text);
+ mpctx->terminal_osd_text = talloc_strdup(mpctx, msg->msg);
+ // Multi-line message => clear what will be the second line
+ write_status_line(mpctx, "");
+ mp_msg(MSGT_CPLAYER, MSGL_STATUS, "%s%s\n", opts->term_osd_esc,
+ mpctx->terminal_osd_text);
+ print_status(mpctx);
+ }
+ }
+ return;
+ }
+
+ int osd_level = opts->osd_level;
+ if (msg && msg->show_position)
+ osd_level = 3;
+
+ if (mpctx->sh_video && opts->term_osd != 1) {
+ // fallback on the timer
+ char *text = NULL;
+
+ if (osd_level >= 2)
+ sadd_osd_status(&text, mpctx, osd_level == 3);
+
+ osd_set_text(osd, text);
+ talloc_free(text);
+ return;
+ }
+
+ // Clear the term osd line
+ if (opts->term_osd && mpctx->terminal_osd_text[0]) {
+ mpctx->terminal_osd_text[0] = '\0';
+ mp_msg(MSGT_CPLAYER, MSGL_STATUS, "%s\n", opts->term_osd_esc);
+ }
+}
+
+static int build_afilter_chain(struct MPContext *mpctx)
+{
+ struct sh_audio *sh_audio = mpctx->sh_audio;
+ struct ao *ao = mpctx->ao;
+ struct MPOpts *opts = mpctx->opts;
+ int new_srate;
+ if (af_control_any_rev(sh_audio->afilter,
+ AF_CONTROL_PLAYBACK_SPEED | AF_CONTROL_SET,
+ &opts->playback_speed))
+ new_srate = sh_audio->samplerate;
+ else {
+ new_srate = sh_audio->samplerate * opts->playback_speed;
+ if (new_srate != ao->samplerate) {
+ // limits are taken from libaf/af_resample.c
+ if (new_srate < 8000)
+ new_srate = 8000;
+ if (new_srate > 192000)
+ new_srate = 192000;
+ opts->playback_speed = (double)new_srate / sh_audio->samplerate;
+ }
+ }
+ return init_audio_filters(sh_audio, new_srate,
+ &ao->samplerate, &ao->channels, &ao->format);
+}
+
+static int recreate_audio_filters(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ assert(mpctx->sh_audio);
+
+ // init audio filters:
+ if (!build_afilter_chain(mpctx)) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR,
+ "Couldn't find matching filter/ao format!\n");
+ return -1;
+ }
+
+ mpctx->mixer.afilter = mpctx->sh_audio->afilter;
+ mpctx->mixer.volstep = opts->volstep;
+ mpctx->mixer.softvol = opts->softvol;
+ mpctx->mixer.softvol_max = opts->softvol_max;
+ mixer_reinit(&mpctx->mixer, mpctx->ao);
+ if (!(mpctx->initialized_flags & INITIALIZED_VOL)) {
+ if (opts->mixer_init_volume >= 0) {
+ mixer_setvolume(&mpctx->mixer, opts->mixer_init_volume,
+ opts->mixer_init_volume);
+ }
+ if (opts->mixer_init_mute >= 0)
+ mixer_setmute(&mpctx->mixer, opts->mixer_init_mute);
+ mpctx->initialized_flags |= INITIALIZED_VOL;
+ }
+
+ return 0;
+}
+
+int reinit_audio_filters(struct MPContext *mpctx)
+{
+ struct sh_audio *sh_audio = mpctx->sh_audio;
+ if (!sh_audio)
+ return -2;
+
+ af_uninit(mpctx->sh_audio->afilter);
+ if (af_init(mpctx->sh_audio->afilter) < 0)
+ return -1;
+ if (recreate_audio_filters(mpctx) < 0)
+ return -1;
+
+ return 0;
+}
+
+void reinit_audio_chain(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ init_demux_stream(mpctx, STREAM_AUDIO);
+ if (!mpctx->sh_audio) {
+ uninit_player(mpctx, INITIALIZED_VOL | INITIALIZED_AO);
+ goto no_audio;
+ }
+
+ if (!(mpctx->initialized_flags & INITIALIZED_ACODEC)) {
+ if (!init_best_audio_codec(mpctx->sh_audio, opts->audio_decoders))
+ goto init_error;
+ mpctx->initialized_flags |= INITIALIZED_ACODEC;
+ }
+
+ int ao_srate = opts->force_srate;
+ int ao_format = opts->audio_output_format;
+ struct mp_chmap ao_channels = {0};
+ if (mpctx->initialized_flags & INITIALIZED_AO) {
+ ao_srate = mpctx->ao->samplerate;
+ ao_format = mpctx->ao->format;
+ ao_channels = mpctx->ao->channels;
+ } else {
+ // Automatic downmix
+ if (mp_chmap_is_stereo(&opts->audio_output_channels) &&
+ !mp_chmap_is_stereo(&mpctx->sh_audio->channels))
+ {
+ mp_chmap_from_channels(&ao_channels, 2);
+ }
+ }
+
+ // Determine what the filter chain outputs. build_afilter_chain() also
+ // needs this for testing whether playback speed is changed by resampling
+ // or using a special filter.
+ if (!init_audio_filters(mpctx->sh_audio, // preliminary init
+ // input:
+ mpctx->sh_audio->samplerate,
+ // output:
+ &ao_srate, &ao_channels, &ao_format)) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR, "Error at audio filter chain "
+ "pre-init!\n");
+ goto init_error;
+ }
+
+ if (!(mpctx->initialized_flags & INITIALIZED_AO)) {
+ mpctx->initialized_flags |= INITIALIZED_AO;
+ mp_chmap_remove_useless_channels(&ao_channels,
+ &opts->audio_output_channels);
+ mpctx->ao = ao_init_best(mpctx->global, mpctx->input,
+ mpctx->encode_lavc_ctx, ao_srate, ao_format,
+ ao_channels);
+ struct ao *ao = mpctx->ao;
+ if (!ao) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR,
+ "Could not open/initialize audio device -> no sound.\n");
+ goto init_error;
+ }
+ ao->buffer.start = talloc_new(ao);
+ char *s = mp_audio_fmt_to_str(ao->samplerate, &ao->channels, ao->format);
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "AO: [%s] %s\n",
+ ao->driver->info->short_name, s);
+ talloc_free(s);
+ mp_msg(MSGT_CPLAYER, MSGL_V, "AO: Description: %s\nAO: Author: %s\n",
+ ao->driver->info->name, ao->driver->info->author);
+ if (strlen(ao->driver->info->comment) > 0)
+ mp_msg(MSGT_CPLAYER, MSGL_V, "AO: Comment: %s\n",
+ ao->driver->info->comment);
+ }
+
+ if (recreate_audio_filters(mpctx) < 0)
+ goto init_error;
+
+ mpctx->syncing_audio = true;
+ return;
+
+init_error:
+ uninit_player(mpctx, INITIALIZED_ACODEC | INITIALIZED_AO | INITIALIZED_VOL);
+ cleanup_demux_stream(mpctx, STREAM_AUDIO);
+no_audio:
+ mpctx->current_track[STREAM_AUDIO] = NULL;
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Audio: no audio\n");
+}
+
+
+// Return pts value corresponding to the end point of audio written to the
+// ao so far.
+static double written_audio_pts(struct MPContext *mpctx)
+{
+ sh_audio_t *sh_audio = mpctx->sh_audio;
+ if (!sh_audio)
+ return MP_NOPTS_VALUE;
+
+ double bps = sh_audio->channels.num * sh_audio->samplerate *
+ sh_audio->samplesize;
+
+ // first calculate the end pts of audio that has been output by decoder
+ double a_pts = sh_audio->pts;
+ if (a_pts == MP_NOPTS_VALUE)
+ return MP_NOPTS_VALUE;
+
+ // sh_audio->pts is the timestamp of the latest input packet with
+ // known pts that the decoder has decoded. sh_audio->pts_bytes is
+ // the amount of bytes the decoder has written after that timestamp.
+ a_pts += sh_audio->pts_bytes / bps;
+
+ // Now a_pts hopefully holds the pts for end of audio from decoder.
+ // Subtract data in buffers between decoder and audio out.
+
+ // Decoded but not filtered
+ a_pts -= sh_audio->a_buffer_len / bps;
+
+ // Data buffered in audio filters, measured in bytes of "missing" output
+ double buffered_output = af_calc_delay(sh_audio->afilter);
+
+ // Data that was ready for ao but was buffered because ao didn't fully
+ // accept everything to internal buffers yet
+ buffered_output += mpctx->ao->buffer.len;
+
+ // Filters divide audio length by playback_speed, so multiply by it
+ // to get the length in original units without speedup or slowdown
+ a_pts -= buffered_output * mpctx->opts->playback_speed / mpctx->ao->bps;
+
+ return a_pts + mpctx->video_offset;
+}
+
+// Return pts value corresponding to currently playing audio.
+double playing_audio_pts(struct MPContext *mpctx)
+{
+ double pts = written_audio_pts(mpctx);
+ if (pts == MP_NOPTS_VALUE)
+ return pts;
+ return pts - mpctx->opts->playback_speed * ao_get_delay(mpctx->ao);
+}
+
+// When reading subtitles from a demuxer, and we read video or audio from the
+// demuxer, we should not explicitly read subtitle packets. (With external
+// subs, we have to.)
+static bool is_interleaved(struct MPContext *mpctx, struct track *track)
+{
+ if (track->is_external || !track->demuxer)
+ return false;
+
+ struct demuxer *demuxer = track->demuxer;
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *other = mpctx->current_track[type];
+ if (other && other != track && other->demuxer && other->demuxer == demuxer)
+ return true;
+ }
+ return false;
+}
+
+static void reset_subtitles(struct MPContext *mpctx)
+{
+ if (mpctx->sh_sub)
+ sub_reset(mpctx->sh_sub->dec_sub);
+ set_osd_subtitle(mpctx, NULL);
+ osd_changed(mpctx->osd, OSDTYPE_SUB);
+}
+
+static void update_subtitles(struct MPContext *mpctx, double refpts_tl)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (!(mpctx->initialized_flags & INITIALIZED_SUB))
+ return;
+
+ struct track *track = mpctx->current_track[STREAM_SUB];
+ struct sh_sub *sh_sub = mpctx->sh_sub;
+ assert(track && sh_sub);
+ struct dec_sub *dec_sub = sh_sub->dec_sub;
+
+ if (mpctx->sh_video) {
+ struct mp_image_params params;
+ if (get_video_params(mpctx->sh_video, &params) > 0)
+ sub_control(dec_sub, SD_CTRL_SET_VIDEO_PARAMS, &params);
+ }
+
+ mpctx->osd->video_offset = track->under_timeline ? mpctx->video_offset : 0;
+
+ double refpts_s = refpts_tl - mpctx->osd->video_offset;
+ double curpts_s = refpts_s + opts->sub_delay;
+
+ if (!track->preloaded) {
+ bool interleaved = is_interleaved(mpctx, track);
+
+ while (1) {
+ if (interleaved && !demux_has_packet(sh_sub->gsh))
+ break;
+ double subpts_s = demux_get_next_pts(sh_sub->gsh);
+ if (!demux_has_packet(sh_sub->gsh))
+ break;
+ if (subpts_s > curpts_s) {
+ mp_dbg(MSGT_CPLAYER, MSGL_DBG2,
+ "Sub early: c_pts=%5.3f s_pts=%5.3f\n",
+ curpts_s, subpts_s);
+ // Libass handled subs can be fed to it in advance
+ if (!sub_accept_packets_in_advance(dec_sub))
+ break;
+ // Try to avoid demuxing whole file at once
+ if (subpts_s > curpts_s + 1 && !interleaved)
+ break;
+ }
+ struct demux_packet *pkt = demux_read_packet(sh_sub->gsh);
+ mp_dbg(MSGT_CPLAYER, MSGL_V, "Sub: c_pts=%5.3f s_pts=%5.3f "
+ "duration=%5.3f len=%d\n", curpts_s, pkt->pts, pkt->duration,
+ pkt->len);
+ sub_decode(dec_sub, pkt);
+ talloc_free(pkt);
+ }
+ }
+
+ if (!mpctx->osd->render_bitmap_subs || !mpctx->sh_video)
+ set_osd_subtitle(mpctx, sub_get_text(dec_sub, curpts_s));
+}
+
+static int check_framedrop(struct MPContext *mpctx, double frame_time)
+{
+ struct MPOpts *opts = mpctx->opts;
+ // check for frame-drop:
+ if (mpctx->sh_audio && !mpctx->ao->untimed &&
+ !demux_stream_eof(mpctx->sh_audio->gsh))
+ {
+ float delay = opts->playback_speed * ao_get_delay(mpctx->ao);
+ float d = delay - mpctx->delay;
+ if (frame_time < 0)
+ frame_time = mpctx->sh_video->fps > 0 ? 1.0 / mpctx->sh_video->fps : 0;
+ // we should avoid dropping too many frames in sequence unless we
+ // are too late. and we allow 100ms A-V delay here:
+ if (d < -mpctx->dropped_frames * frame_time - 0.100 && !mpctx->paused
+ && !mpctx->restart_playback) {
+ mpctx->drop_frame_cnt++;
+ mpctx->dropped_frames++;
+ return mpctx->opts->frame_dropping;
+ } else
+ mpctx->dropped_frames = 0;
+ }
+ return 0;
+}
+
+static double timing_sleep(struct MPContext *mpctx, double time_frame)
+{
+ // assume kernel HZ=100 for softsleep, works with larger HZ but with
+ // unnecessarily high CPU usage
+ struct MPOpts *opts = mpctx->opts;
+ double margin = opts->softsleep ? 0.011 : 0;
+ while (time_frame > margin) {
+ mp_sleep_us(1000000 * (time_frame - margin));
+ time_frame -= get_relative_time(mpctx);
+ }
+ if (opts->softsleep) {
+ if (time_frame < 0)
+ mp_tmsg(MSGT_AVSYNC, MSGL_WARN,
+ "Warning! Softsleep underflow!\n");
+ while (time_frame > 0)
+ time_frame -= get_relative_time(mpctx); // burn the CPU
+ }
+ return time_frame;
+}
+
+static void set_dvdsub_fake_extradata(struct dec_sub *dec_sub, struct stream *st,
+ int width, int height)
+{
+#ifdef CONFIG_DVDREAD
+ if (!st)
+ return;
+
+ struct stream_dvd_info_req info;
+ if (stream_control(st, STREAM_CTRL_GET_DVD_INFO, &info) < 0)
+ return;
+
+ struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
+ csp.int_bits_in = 8;
+ csp.int_bits_out = 8;
+ float cmatrix[3][4];
+ mp_get_yuv2rgb_coeffs(&csp, cmatrix);
+
+ if (width == 0 || height == 0) {
+ width = 720;
+ height = 480;
+ }
+
+ char *s = NULL;
+ s = talloc_asprintf_append(s, "size: %dx%d\n", width, height);
+ s = talloc_asprintf_append(s, "palette: ");
+ for (int i = 0; i < 16; i++) {
+ int color = info.palette[i];
+ int c[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
+ mp_map_int_color(cmatrix, 8, c);
+ color = (c[2] << 16) | (c[1] << 8) | c[0];
+
+ if (i != 0)
+ talloc_asprintf_append(s, ", ");
+ s = talloc_asprintf_append(s, "%06x", color);
+ }
+ s = talloc_asprintf_append(s, "\n");
+
+ sub_set_extradata(dec_sub, s, strlen(s));
+ talloc_free(s);
+#endif
+}
+
+static void reinit_subs(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct track *track = mpctx->current_track[STREAM_SUB];
+
+ assert(!(mpctx->initialized_flags & INITIALIZED_SUB));
+
+ init_demux_stream(mpctx, STREAM_SUB);
+ if (!mpctx->sh_sub)
+ return;
+
+ if (!mpctx->sh_sub->dec_sub)
+ mpctx->sh_sub->dec_sub = sub_create(opts);
+
+ assert(track->demuxer);
+ // Lazily added DVD track - will be created on first sub packet
+ if (!track->stream)
+ return;
+
+ mpctx->initialized_flags |= INITIALIZED_SUB;
+
+ struct sh_sub *sh_sub = mpctx->sh_sub;
+ struct dec_sub *dec_sub = sh_sub->dec_sub;
+ assert(dec_sub);
+
+ if (!sub_is_initialized(dec_sub)) {
+ int w = mpctx->sh_video ? mpctx->sh_video->disp_w : 0;
+ int h = mpctx->sh_video ? mpctx->sh_video->disp_h : 0;
+ float fps = mpctx->sh_video ? mpctx->sh_video->fps : 25;
+
+ set_dvdsub_fake_extradata(dec_sub, track->demuxer->stream, w, h);
+ sub_set_video_res(dec_sub, w, h);
+ sub_set_video_fps(dec_sub, fps);
+ sub_set_ass_renderer(dec_sub, mpctx->osd->ass_library,
+ mpctx->osd->ass_renderer);
+ sub_init_from_sh(dec_sub, sh_sub);
+
+ // Don't do this if the file has video/audio streams. Don't do it even
+ // if it has only sub streams, because reading packets will change the
+ // demuxer position.
+ if (!track->preloaded && track->is_external) {
+ demux_seek(track->demuxer, 0, 0, SEEK_ABSOLUTE);
+ track->preloaded = sub_read_all_packets(dec_sub, sh_sub);
+ }
+ }
+
+ mpctx->osd->dec_sub = dec_sub;
+
+ // Decides whether to use OSD path or normal subtitle rendering path.
+ mpctx->osd->render_bitmap_subs =
+ opts->ass_enabled || !sub_has_get_text(dec_sub);
+
+ reset_subtitles(mpctx);
+}
+
+static char *track_layout_hash(struct MPContext *mpctx)
+{
+ char *h = talloc_strdup(NULL, "");
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->type != type)
+ continue;
+ h = talloc_asprintf_append_buffer(h, "%d-%d-%d-%d-%s\n", type,
+ track->user_tid, track->default_track, track->is_external,
+ track->lang ? track->lang : "");
+ }
+ }
+ return h;
+}
+
+void mp_switch_track(struct MPContext *mpctx, enum stream_type type,
+ struct track *track)
+{
+ assert(!track || track->type == type);
+
+ struct track *current = mpctx->current_track[type];
+ if (track == current)
+ return;
+
+ if (type == STREAM_VIDEO) {
+ uninit_player(mpctx, INITIALIZED_VCODEC |
+ (mpctx->opts->fixed_vo && track ? 0 : INITIALIZED_VO));
+ } else if (type == STREAM_AUDIO) {
+ uninit_player(mpctx, INITIALIZED_AO | INITIALIZED_ACODEC | INITIALIZED_VOL);
+ } else if (type == STREAM_SUB) {
+ uninit_player(mpctx, INITIALIZED_SUB);
+ }
+
+ mpctx->current_track[type] = track;
+
+ int user_tid = track ? track->user_tid : -2;
+ if (type == STREAM_VIDEO) {
+ mpctx->opts->video_id = user_tid;
+ reinit_video_chain(mpctx);
+ } else if (type == STREAM_AUDIO) {
+ mpctx->opts->audio_id = user_tid;
+ reinit_audio_chain(mpctx);
+ } else if (type == STREAM_SUB) {
+ mpctx->opts->sub_id = user_tid;
+ reinit_subs(mpctx);
+ }
+
+ talloc_free(mpctx->track_layout_hash);
+ mpctx->track_layout_hash = talloc_steal(mpctx, track_layout_hash(mpctx));
+}
+
+struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
+ int tid)
+{
+ if (tid == -1)
+ return mpctx->current_track[type];
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->type == type && track->user_tid == tid)
+ return track;
+ }
+ return NULL;
+}
+
+bool mp_remove_track(struct MPContext *mpctx, struct track *track)
+{
+ if (track->under_timeline)
+ return false;
+ if (!track->is_external)
+ return false;
+
+ if (mpctx->current_track[track->type] == track) {
+ mp_switch_track(mpctx, track->type, NULL);
+ if (mpctx->current_track[track->type] == track)
+ return false;
+ }
+
+ 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--;
+ talloc_free(track);
+ return true;
+}
+
+/* Modify video timing to match the audio timeline. There are two main
+ * reasons this is needed. First, video and audio can start from different
+ * positions at beginning of file or after a seek (MPlayer starts both
+ * immediately even if they have different pts). Second, the file can have
+ * audio timestamps that are inconsistent with the duration of the audio
+ * packets, for example two consecutive timestamp values differing by
+ * one second but only a packet with enough samples for half a second
+ * of playback between them.
+ */
+static void adjust_sync(struct MPContext *mpctx, double frame_time)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ if (!mpctx->sh_audio || mpctx->syncing_audio)
+ return;
+
+ double a_pts = written_audio_pts(mpctx) - mpctx->delay;
+ double v_pts = mpctx->sh_video->pts;
+ double av_delay = a_pts - v_pts;
+ // Try to sync vo_flip() so it will *finish* at given time
+ av_delay += mpctx->last_vo_flip_duration;
+ av_delay -= mpctx->audio_delay; // This much pts difference is desired
+
+ double change = av_delay * 0.1;
+ double max_change = opts->default_max_pts_correction >= 0 ?
+ opts->default_max_pts_correction : frame_time * 0.1;
+ if (change < -max_change)
+ change = -max_change;
+ else if (change > max_change)
+ change = max_change;
+ mpctx->delay += change;
+ mpctx->total_avsync_change += change;
+}
+
+static int write_to_ao(struct MPContext *mpctx, void *data, int len, int flags,
+ double pts)
+{
+ if (mpctx->paused)
+ return 0;
+ struct ao *ao = mpctx->ao;
+ double bps = ao->bps / mpctx->opts->playback_speed;
+ ao->pts = pts;
+ int played = ao_play(mpctx->ao, data, len, flags);
+ if (played > 0) {
+ mpctx->delay += played / bps;
+ // Keep correct pts for remaining data - could be used to flush
+ // remaining buffer when closing ao.
+ ao->pts += played / bps;
+ return played;
+ }
+ return 0;
+}
+
+#define ASYNC_PLAY_DONE -3
+static int audio_start_sync(struct MPContext *mpctx, int playsize)
+{
+ struct ao *ao = mpctx->ao;
+ struct MPOpts *opts = mpctx->opts;
+ sh_audio_t * const sh_audio = mpctx->sh_audio;
+ int res;
+
+ // Timing info may not be set without
+ res = decode_audio(sh_audio, &ao->buffer, 1);
+ if (res < 0)
+ return res;
+
+ int bytes;
+ bool did_retry = false;
+ double written_pts;
+ double bps = ao->bps / opts->playback_speed;
+ bool hrseek = mpctx->hrseek_active; // audio only hrseek
+ mpctx->hrseek_active = false;
+ while (1) {
+ written_pts = written_audio_pts(mpctx);
+ double ptsdiff;
+ if (hrseek)
+ ptsdiff = written_pts - mpctx->hrseek_pts;
+ else
+ ptsdiff = written_pts - mpctx->sh_video->pts - mpctx->delay
+ - mpctx->audio_delay;
+ bytes = ptsdiff * bps;
+ bytes -= bytes % (ao->channels.num * af_fmt2bits(ao->format) / 8);
+
+ // ogg demuxers give packets without timing
+ if (written_pts <= 1 && sh_audio->pts == MP_NOPTS_VALUE) {
+ if (!did_retry) {
+ // Try to read more data to see packets that have pts
+ res = decode_audio(sh_audio, &ao->buffer, ao->bps);
+ if (res < 0)
+ return res;
+ did_retry = true;
+ continue;
+ }
+ bytes = 0;
+ }
+
+ if (fabs(ptsdiff) > 300 || isnan(ptsdiff)) // pts reset or just broken?
+ bytes = 0;
+
+ if (bytes > 0)
+ break;
+
+ mpctx->syncing_audio = false;
+ int a = FFMIN(-bytes, FFMAX(playsize, 20000));
+ res = decode_audio(sh_audio, &ao->buffer, a);
+ bytes += ao->buffer.len;
+ if (bytes >= 0) {
+ memmove(ao->buffer.start,
+ ao->buffer.start + ao->buffer.len - bytes, bytes);
+ ao->buffer.len = bytes;
+ if (res < 0)
+ return res;
+ return decode_audio(sh_audio, &ao->buffer, playsize);
+ }
+ ao->buffer.len = 0;
+ if (res < 0)
+ return res;
+ }
+ if (hrseek)
+ // Don't add silence in audio-only case even if position is too late
+ return 0;
+ int fillbyte = 0;
+ if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_US)
+ fillbyte = 0x80;
+ if (bytes >= playsize) {
+ /* This case could fall back to the one below with
+ * bytes = playsize, but then silence would keep accumulating
+ * in a_out_buffer if the AO accepts less data than it asks for
+ * in playsize. */
+ char *p = malloc(playsize);
+ memset(p, fillbyte, playsize);
+ write_to_ao(mpctx, p, playsize, 0, written_pts - bytes / bps);
+ free(p);
+ return ASYNC_PLAY_DONE;
+ }
+ mpctx->syncing_audio = false;
+ decode_audio_prepend_bytes(&ao->buffer, bytes, fillbyte);
+ return decode_audio(sh_audio, &ao->buffer, playsize);
+}
+
+static int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct ao *ao = mpctx->ao;
+ int playsize;
+ int playflags = 0;
+ bool audio_eof = false;
+ bool partial_fill = false;
+ sh_audio_t * const sh_audio = mpctx->sh_audio;
+ bool modifiable_audio_format = !(ao->format & AF_FORMAT_SPECIAL_MASK);
+ int unitsize = ao->channels.num * af_fmt2bits(ao->format) / 8;
+
+ if (mpctx->paused)
+ playsize = 1; // just initialize things (audio pts at least)
+ else
+ playsize = ao_get_space(ao);
+
+ // Coming here with hrseek_active still set means audio-only
+ if (!mpctx->sh_video || !mpctx->sync_audio_to_video)
+ mpctx->syncing_audio = false;
+ if (!opts->initial_audio_sync || !modifiable_audio_format) {
+ mpctx->syncing_audio = false;
+ mpctx->hrseek_active = false;
+ }
+
+ int res;
+ if (mpctx->syncing_audio || mpctx->hrseek_active)
+ res = audio_start_sync(mpctx, playsize);
+ else
+ res = decode_audio(sh_audio, &ao->buffer, playsize);
+
+ if (res < 0) { // EOF, error or format change
+ if (res == -2) {
+ /* The format change isn't handled too gracefully. A more precise
+ * implementation would require draining buffered old-format audio
+ * while displaying video, then doing the output format switch.
+ */
+ if (!mpctx->opts->gapless_audio)
+ uninit_player(mpctx, INITIALIZED_AO | INITIALIZED_VOL);
+ reinit_audio_chain(mpctx);
+ return -1;
+ } else if (res == ASYNC_PLAY_DONE)
+ return 0;
+ else if (demux_stream_eof(mpctx->sh_audio->gsh))
+ audio_eof = true;
+ }
+
+ if (endpts != MP_NOPTS_VALUE && modifiable_audio_format) {
+ double bytes = (endpts - written_audio_pts(mpctx) + mpctx->audio_delay)
+ * ao->bps / opts->playback_speed;
+ if (playsize > bytes) {
+ playsize = FFMAX(bytes, 0);
+ playflags |= AOPLAY_FINAL_CHUNK;
+ audio_eof = true;
+ partial_fill = true;
+ }
+ }
+
+ assert(ao->buffer.len % unitsize == 0);
+ if (playsize > ao->buffer.len) {
+ partial_fill = true;
+ playsize = ao->buffer.len;
+ if (audio_eof)
+ playflags |= AOPLAY_FINAL_CHUNK;
+ }
+ playsize -= playsize % unitsize;
+ if (!playsize)
+ return partial_fill && audio_eof ? -2 : -partial_fill;
+
+ // play audio:
+
+ int played = write_to_ao(mpctx, ao->buffer.start, playsize, playflags,
+ written_audio_pts(mpctx));
+ assert(played % unitsize == 0);
+ ao->buffer_playable_size = playsize - played;
+
+ if (played > 0) {
+ ao->buffer.len -= played;
+ memmove(ao->buffer.start, ao->buffer.start + played, ao->buffer.len);
+ } else if (!mpctx->paused && audio_eof && ao_get_delay(ao) < .04) {
+ // Sanity check to avoid hanging in case current ao doesn't output
+ // partial chunks and doesn't check for AOPLAY_FINAL_CHUNK
+ return -2;
+ }
+
+ return -partial_fill;
+}
+
+static void update_fps(struct MPContext *mpctx)
+{
+#ifdef CONFIG_ENCODING
+ struct sh_video *sh_video = mpctx->sh_video;
+ if (mpctx->encode_lavc_ctx && sh_video)
+ encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, sh_video->fps);
+#endif
+}
+
+static void recreate_video_filters(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct sh_video *sh_video = mpctx->sh_video;
+ assert(sh_video);
+
+ vf_uninit_filter_chain(sh_video->vfilter);
+
+ char *vf_arg[] = {
+ "_oldargs_", (char *)mpctx->video_out, NULL
+ };
+ sh_video->vfilter = vf_open_filter(opts, NULL, "vo", vf_arg);
+
+ sh_video->vfilter = append_filters(sh_video->vfilter, opts->vf_settings);
+
+ struct vf_instance *vf = sh_video->vfilter;
+ mpctx->osd->render_subs_in_filter
+ = vf->control(vf, VFCTRL_INIT_OSD, NULL) == VO_TRUE;
+}
+
+int reinit_video_filters(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+
+ if (!sh_video)
+ return -2;
+
+ recreate_video_filters(mpctx);
+ video_reinit_vo(sh_video);
+
+ return sh_video->vf_initialized > 0 ? 0 : -1;
+}
+
+int reinit_video_chain(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ assert(!(mpctx->initialized_flags & INITIALIZED_VCODEC));
+ init_demux_stream(mpctx, STREAM_VIDEO);
+ sh_video_t *sh_video = mpctx->sh_video;
+ if (!sh_video) {
+ uninit_player(mpctx, INITIALIZED_VO);
+ goto no_video;
+ }
+
+ mp_tmsg(MSGT_CPLAYER, MSGL_V, "[V] fourcc:0x%X "
+ "size:%dx%d fps:%5.3f\n",
+ mpctx->sh_video->format,
+ mpctx->sh_video->disp_w, mpctx->sh_video->disp_h,
+ mpctx->sh_video->fps);
+ if (opts->force_fps)
+ mpctx->sh_video->fps = opts->force_fps;
+ update_fps(mpctx);
+
+ if (!mpctx->sh_video->fps && !opts->force_fps && !opts->correct_pts) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR, "FPS not specified in the "
+ "header or invalid, use the -fps option.\n");
+ }
+
+ double ar = -1.0;
+ //================== Init VIDEO (codec & libvo) ==========================
+ if (!opts->fixed_vo || !(mpctx->initialized_flags & INITIALIZED_VO)) {
+ mpctx->video_out = init_best_video_out(mpctx->global, mpctx->input,
+ mpctx->encode_lavc_ctx);
+ if (!mpctx->video_out) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_FATAL, "Error opening/initializing "
+ "the selected video_out (-vo) device.\n");
+ goto err_out;
+ }
+ if (opts->vo.cursor_autohide_delay != -1) {
+ vo_control(mpctx->video_out, VOCTRL_SET_CURSOR_VISIBILITY,
+ &(bool){false});
+ }
+ mpctx->initialized_flags |= INITIALIZED_VO;
+
+ // dynamic allocation only to make stheader.h lighter
+ talloc_free(sh_video->hwdec_info);
+ sh_video->hwdec_info = talloc_zero(sh_video, struct mp_hwdec_info);
+ vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, sh_video->hwdec_info);
+ }
+
+ vo_update_window_title(mpctx);
+
+ if (stream_control(mpctx->sh_video->gsh->demuxer->stream,
+ STREAM_CTRL_GET_ASPECT_RATIO, &ar) != STREAM_UNSUPPORTED)
+ mpctx->sh_video->stream_aspect = ar;
+
+ recreate_video_filters(mpctx);
+
+ init_best_video_codec(sh_video, opts->video_decoders);
+
+ if (!sh_video->initialized)
+ goto err_out;
+
+ mpctx->initialized_flags |= INITIALIZED_VCODEC;
+
+ bool saver_state = opts->pause || !opts->stop_screensaver;
+ vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER
+ : VOCTRL_KILL_SCREENSAVER, NULL);
+
+ vo_control(mpctx->video_out, mpctx->paused ? VOCTRL_PAUSE
+ : VOCTRL_RESUME, NULL);
+
+ sh_video->last_pts = MP_NOPTS_VALUE;
+ sh_video->num_buffered_pts = 0;
+ sh_video->next_frame_time = 0;
+ mpctx->restart_playback = true;
+ mpctx->sync_audio_to_video = !sh_video->gsh->attached_picture;
+ mpctx->delay = 0;
+ mpctx->vo_pts_history_seek_ts++;
+
+ reset_subtitles(mpctx);
+
+ return 1;
+
+err_out:
+ uninit_player(mpctx, INITIALIZED_VO);
+ cleanup_demux_stream(mpctx, STREAM_VIDEO);
+no_video:
+ mpctx->current_track[STREAM_VIDEO] = NULL;
+ mpctx->sync_audio_to_video = false;
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Video: no video\n");
+ return 0;
+}
+
+// Try to refresh the video by doing a precise seek to the currently displayed
+// frame. This can go wrong in all sorts of ways, so use sparingly.
+void mp_force_video_refresh(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ // If not paused, the next frame should come soon enough.
+ if (opts->pause && mpctx->last_vo_pts != MP_NOPTS_VALUE)
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts, 1);
+}
+
+static void add_frame_pts(struct MPContext *mpctx, double pts)
+{
+ if (pts == MP_NOPTS_VALUE || mpctx->hrseek_framedrop) {
+ mpctx->vo_pts_history_seek_ts++; // mark discontinuity
+ return;
+ }
+ for (int n = MAX_NUM_VO_PTS - 1; n >= 1; n--) {
+ mpctx->vo_pts_history_seek[n] = mpctx->vo_pts_history_seek[n - 1];
+ mpctx->vo_pts_history_pts[n] = mpctx->vo_pts_history_pts[n - 1];
+ }
+ mpctx->vo_pts_history_seek[0] = mpctx->vo_pts_history_seek_ts;
+ mpctx->vo_pts_history_pts[0] = pts;
+}
+
+static double find_previous_pts(struct MPContext *mpctx, double pts)
+{
+ for (int n = 0; n < MAX_NUM_VO_PTS - 1; n++) {
+ if (pts == mpctx->vo_pts_history_pts[n] &&
+ mpctx->vo_pts_history_seek[n] != 0 &&
+ mpctx->vo_pts_history_seek[n] == mpctx->vo_pts_history_seek[n + 1])
+ {
+ return mpctx->vo_pts_history_pts[n + 1];
+ }
+ }
+ return MP_NOPTS_VALUE;
+}
+
+static double get_last_frame_pts(struct MPContext *mpctx)
+{
+ if (mpctx->vo_pts_history_seek[0] == mpctx->vo_pts_history_seek_ts)
+ return mpctx->vo_pts_history_pts[0];
+ return MP_NOPTS_VALUE;
+}
+
+static bool filter_output_queued_frame(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+ struct vo *video_out = mpctx->video_out;
+
+ struct mp_image *img = vf_chain_output_queued_frame(sh_video->vfilter);
+ if (img)
+ vo_queue_image(video_out, img);
+ talloc_free(img);
+
+ return !!img;
+}
+
+static bool load_next_vo_frame(struct MPContext *mpctx, bool eof)
+{
+ if (vo_get_buffered_frame(mpctx->video_out, eof) >= 0)
+ return true;
+ if (filter_output_queued_frame(mpctx))
+ return true;
+ return false;
+}
+
+static void filter_video(struct MPContext *mpctx, struct mp_image *frame)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+
+ frame->pts = sh_video->pts;
+ vf_filter_frame(sh_video->vfilter, frame);
+ filter_output_queued_frame(mpctx);
+}
+
+
+static struct demux_packet *video_read_frame(struct MPContext *mpctx)
+{
+ sh_video_t *sh_video = mpctx->sh_video;
+ demuxer_t *demuxer = sh_video->gsh->demuxer;
+ float pts1 = sh_video->last_pts;
+
+ struct demux_packet *pkt = demux_read_packet(sh_video->gsh);
+ if (!pkt)
+ return NULL; // EOF
+
+ if (pkt->pts != MP_NOPTS_VALUE)
+ sh_video->last_pts = pkt->pts;
+
+ float frame_time = sh_video->fps > 0 ? 1.0f / sh_video->fps : 0;
+
+ // override frame_time for variable/unknown FPS formats:
+ if (!mpctx->opts->force_fps) {
+ double next_pts = demux_get_next_pts(sh_video->gsh);
+ double d = next_pts == MP_NOPTS_VALUE ? sh_video->last_pts - pts1
+ : next_pts - sh_video->last_pts;
+ if (d >= 0) {
+ if (demuxer->type == DEMUXER_TYPE_TV) {
+ if (d > 0)
+ sh_video->fps = 1.0f / d;
+ frame_time = d;
+ } else {
+ if ((int)sh_video->fps <= 1)
+ frame_time = d;
+ }
+ }
+ }
+
+ sh_video->pts = sh_video->last_pts;
+ sh_video->next_frame_time = frame_time;
+ return pkt;
+}
+
+static double update_video_nocorrect_pts(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+ double frame_time = 0;
+ while (1) {
+ // In nocorrect-pts mode there is no way to properly time these frames
+ if (load_next_vo_frame(mpctx, false))
+ break;
+ frame_time = sh_video->next_frame_time;
+ if (mpctx->restart_playback)
+ frame_time = 0;
+ struct demux_packet *pkt = video_read_frame(mpctx);
+ if (!pkt)
+ return -1;
+ if (mpctx->sh_audio)
+ mpctx->delay -= frame_time;
+ // video_read_frame can change fps (e.g. for ASF video)
+ update_fps(mpctx);
+ int framedrop_type = check_framedrop(mpctx, frame_time);
+
+ void *decoded_frame = decode_video(sh_video, pkt, framedrop_type,
+ sh_video->pts);
+ talloc_free(pkt);
+ if (decoded_frame) {
+ filter_video(mpctx, decoded_frame);
+ }
+ break;
+ }
+ return frame_time;
+}
+
+static double update_video_attached_pic(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+
+ // Try to decode the picture multiple times, until it is displayed.
+ if (mpctx->video_out->hasframe)
+ return -1;
+
+ struct mp_image *decoded_frame =
+ decode_video(sh_video, sh_video->gsh->attached_picture, 0, 0);
+ if (decoded_frame)
+ filter_video(mpctx, decoded_frame);
+ load_next_vo_frame(mpctx, true);
+ mpctx->sh_video->pts = MP_NOPTS_VALUE;
+ return 0;
+}
+
+static void determine_frame_pts(struct MPContext *mpctx)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+ struct MPOpts *opts = mpctx->opts;
+
+ if (opts->user_pts_assoc_mode)
+ sh_video->pts_assoc_mode = opts->user_pts_assoc_mode;
+ else if (sh_video->pts_assoc_mode == 0) {
+ if (mpctx->sh_video->gsh->demuxer->timestamp_type == TIMESTAMP_TYPE_PTS
+ && sh_video->codec_reordered_pts != MP_NOPTS_VALUE)
+ sh_video->pts_assoc_mode = 1;
+ else
+ sh_video->pts_assoc_mode = 2;
+ } else {
+ int probcount1 = sh_video->num_reordered_pts_problems;
+ int probcount2 = sh_video->num_sorted_pts_problems;
+ if (sh_video->pts_assoc_mode == 2) {
+ int tmp = probcount1;
+ probcount1 = probcount2;
+ probcount2 = tmp;
+ }
+ if (probcount1 >= probcount2 * 1.5 + 2) {
+ sh_video->pts_assoc_mode = 3 - sh_video->pts_assoc_mode;
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Switching to pts association mode "
+ "%d.\n", sh_video->pts_assoc_mode);
+ }
+ }
+ sh_video->pts = sh_video->pts_assoc_mode == 1 ?
+ sh_video->codec_reordered_pts : sh_video->sorted_pts;
+}
+
+static double update_video(struct MPContext *mpctx, double endpts)
+{
+ struct sh_video *sh_video = mpctx->sh_video;
+ struct vo *video_out = mpctx->video_out;
+ sh_video->vfilter->control(sh_video->vfilter, VFCTRL_SET_OSD_OBJ,
+ mpctx->osd); // for vf_sub
+ if (!mpctx->opts->correct_pts)
+ return update_video_nocorrect_pts(mpctx);
+
+ if (sh_video->gsh->attached_picture)
+ return update_video_attached_pic(mpctx);
+
+ double pts;
+
+ while (1) {
+ if (load_next_vo_frame(mpctx, false))
+ break;
+ pts = MP_NOPTS_VALUE;
+ struct demux_packet *pkt = NULL;
+ while (1) {
+ pkt = demux_read_packet(mpctx->sh_video->gsh);
+ if (!pkt || pkt->len)
+ break;
+ /* Packets with size 0 are assumed to not correspond to frames,
+ * but to indicate the absence of a frame in formats like AVI
+ * that must have packets at fixed timecode intervals. */
+ talloc_free(pkt);
+ }
+ if (pkt)
+ pts = pkt->pts;
+ if (pts != MP_NOPTS_VALUE)
+ pts += mpctx->video_offset;
+ if (pts >= mpctx->hrseek_pts - .005)
+ mpctx->hrseek_framedrop = false;
+ int framedrop_type = mpctx->hrseek_active && mpctx->hrseek_framedrop ?
+ 1 : check_framedrop(mpctx, -1);
+ struct mp_image *decoded_frame =
+ decode_video(sh_video, pkt, framedrop_type, pts);
+ talloc_free(pkt);
+ if (decoded_frame) {
+ determine_frame_pts(mpctx);
+ filter_video(mpctx, decoded_frame);
+ } else if (!pkt) {
+ if (!load_next_vo_frame(mpctx, true))
+ return -1;
+ }
+ break;
+ }
+
+ if (!video_out->frame_loaded)
+ return 0;
+
+ pts = video_out->next_pts;
+ if (pts == MP_NOPTS_VALUE) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Video pts after filters MISSING\n");
+ // Try to use decoder pts from before filters
+ pts = sh_video->pts;
+ if (pts == MP_NOPTS_VALUE)
+ pts = sh_video->last_pts;
+ }
+ if (endpts == MP_NOPTS_VALUE || pts < endpts)
+ add_frame_pts(mpctx, pts);
+ if (mpctx->hrseek_active && pts < mpctx->hrseek_pts - .005) {
+ vo_skip_frame(video_out);
+ return 0;
+ }
+ mpctx->hrseek_active = false;
+ sh_video->pts = pts;
+ if (sh_video->last_pts == MP_NOPTS_VALUE)
+ sh_video->last_pts = sh_video->pts;
+ else if (sh_video->last_pts > sh_video->pts) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Decreasing video pts: %f < %f\n",
+ sh_video->pts, sh_video->last_pts);
+ /* If the difference in pts is small treat it as jitter around the
+ * right value (possibly caused by incorrect timestamp ordering) and
+ * just show this frame immediately after the last one.
+ * Treat bigger differences as timestamp resets and start counting
+ * timing of later frames from the position of this one. */
+ if (sh_video->last_pts - sh_video->pts > 0.5)
+ sh_video->last_pts = sh_video->pts;
+ else
+ sh_video->pts = sh_video->last_pts;
+ } else if (sh_video->pts >= sh_video->last_pts + 60) {
+ // Assume a PTS difference >= 60 seconds is a discontinuity.
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Jump in video pts: %f -> %f\n",
+ sh_video->last_pts, sh_video->pts);
+ sh_video->last_pts = sh_video->pts;
+ }
+ double frame_time = sh_video->pts - sh_video->last_pts;
+ sh_video->last_pts = sh_video->pts;
+ if (mpctx->sh_audio)
+ mpctx->delay -= frame_time;
+ return frame_time;
+}
+
+void pause_player(struct MPContext *mpctx)
+{
+ mpctx->opts->pause = 1;
+
+ if (mpctx->video_out)
+ vo_control(mpctx->video_out, VOCTRL_RESTORE_SCREENSAVER, NULL);
+
+ if (mpctx->paused)
+ return;
+ mpctx->paused = true;
+ mpctx->step_frames = 0;
+ mpctx->time_frame -= get_relative_time(mpctx);
+ mpctx->osd_function = 0;
+ mpctx->paused_for_cache = false;
+
+ if (mpctx->video_out && mpctx->sh_video && mpctx->video_out->config_ok)
+ vo_control(mpctx->video_out, VOCTRL_PAUSE, NULL);
+
+ if (mpctx->ao && mpctx->sh_audio)
+ ao_pause(mpctx->ao); // pause audio, keep data if possible
+
+ // Only print status if there's actually a file being played.
+ if (mpctx->num_sources)
+ print_status(mpctx);
+
+ if (!mpctx->opts->quiet)
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_PAUSED\n");
+}
+
+void unpause_player(struct MPContext *mpctx)
+{
+ mpctx->opts->pause = 0;
+
+ if (mpctx->video_out && mpctx->opts->stop_screensaver)
+ vo_control(mpctx->video_out, VOCTRL_KILL_SCREENSAVER, NULL);
+
+ if (!mpctx->paused)
+ return;
+ // Don't actually unpause while cache is loading.
+ if (mpctx->paused_for_cache)
+ return;
+ mpctx->paused = false;
+ mpctx->osd_function = 0;
+
+ if (mpctx->ao && mpctx->sh_audio)
+ ao_resume(mpctx->ao);
+ if (mpctx->video_out && mpctx->sh_video && mpctx->video_out->config_ok)
+ vo_control(mpctx->video_out, VOCTRL_RESUME, NULL); // resume video
+ (void)get_relative_time(mpctx); // ignore time that passed during pause
+}
+
+static void draw_osd(struct MPContext *mpctx)
+{
+ struct vo *vo = mpctx->video_out;
+
+ mpctx->osd->vo_pts = mpctx->video_pts;
+ vo_draw_osd(vo, mpctx->osd);
+}
+
+static bool redraw_osd(struct MPContext *mpctx)
+{
+ struct vo *vo = mpctx->video_out;
+ if (vo_redraw_frame(vo) < 0)
+ return false;
+
+ if (mpctx->sh_video)
+ update_subtitles(mpctx, mpctx->sh_video->pts);
+ draw_osd(mpctx);
+
+ vo_flip_page(vo, 0, -1);
+ return true;
+}
+
+void add_step_frame(struct MPContext *mpctx, int dir)
+{
+ if (dir > 0) {
+ mpctx->step_frames += 1;
+ unpause_player(mpctx);
+ } else if (dir < 0) {
+ if (!mpctx->backstep_active && !mpctx->hrseek_active) {
+ mpctx->backstep_active = true;
+ mpctx->backstep_start_seek_ts = mpctx->vo_pts_history_seek_ts;
+ pause_player(mpctx);
+ }
+ }
+}
+
+static void seek_reset(struct MPContext *mpctx, bool reset_ao, bool reset_ac)
+{
+ if (mpctx->sh_video) {
+ resync_video_stream(mpctx->sh_video);
+ vo_seek_reset(mpctx->video_out);
+ if (mpctx->sh_video->vf_initialized == 1)
+ vf_chain_seek_reset(mpctx->sh_video->vfilter);
+ mpctx->sh_video->num_buffered_pts = 0;
+ mpctx->sh_video->last_pts = MP_NOPTS_VALUE;
+ mpctx->sh_video->pts = MP_NOPTS_VALUE;
+ mpctx->video_pts = MP_NOPTS_VALUE;
+ mpctx->delay = 0;
+ mpctx->time_frame = 0;
+ }
+
+ if (mpctx->sh_audio && reset_ac) {
+ resync_audio_stream(mpctx->sh_audio);
+ if (reset_ao)
+ ao_reset(mpctx->ao);
+ mpctx->ao->buffer.len = mpctx->ao->buffer_playable_size;
+ mpctx->sh_audio->a_buffer_len = 0;
+ }
+
+ reset_subtitles(mpctx);
+
+ mpctx->restart_playback = true;
+ mpctx->hrseek_active = false;
+ mpctx->hrseek_framedrop = false;
+ mpctx->total_avsync_change = 0;
+ mpctx->drop_frame_cnt = 0;
+ mpctx->dropped_frames = 0;
+ mpctx->playback_pts = MP_NOPTS_VALUE;
+
+#ifdef CONFIG_ENCODING
+ encode_lavc_discontinuity(mpctx->encode_lavc_ctx);
+#endif
+}
+
+static bool timeline_set_part(struct MPContext *mpctx, int i, bool force)
+{
+ struct timeline_part *p = mpctx->timeline + mpctx->timeline_part;
+ struct timeline_part *n = mpctx->timeline + i;
+ mpctx->timeline_part = i;
+ mpctx->video_offset = n->start - n->source_start;
+ if (n->source == p->source && !force)
+ return false;
+ enum stop_play_reason orig_stop_play = mpctx->stop_play;
+ if (!mpctx->sh_video && mpctx->stop_play == KEEP_PLAYING)
+ mpctx->stop_play = AT_END_OF_FILE; // let audio uninit drain data
+ uninit_player(mpctx, INITIALIZED_VCODEC | (mpctx->opts->fixed_vo ? 0 : INITIALIZED_VO) | (mpctx->opts->gapless_audio ? 0 : INITIALIZED_AO) | INITIALIZED_VOL | INITIALIZED_ACODEC | INITIALIZED_SUB);
+ mpctx->stop_play = orig_stop_play;
+
+ mpctx->demuxer = n->source;
+ mpctx->stream = mpctx->demuxer->stream;
+
+ // While another timeline was active, the selection of active tracks might
+ // have been changed - possibly we need to update this source.
+ for (int x = 0; x < mpctx->num_tracks; x++) {
+ struct track *track = mpctx->tracks[x];
+ if (track->under_timeline) {
+ track->demuxer = mpctx->demuxer;
+ track->stream = demuxer_stream_by_demuxer_id(track->demuxer,
+ track->type,
+ track->demuxer_id);
+ }
+ }
+ preselect_demux_streams(mpctx);
+
+ return true;
+}
+
+// Given pts, switch playback to the corresponding part.
+// Return offset within that part.
+static double timeline_set_from_time(struct MPContext *mpctx, double pts,
+ bool *need_reset)
+{
+ if (pts < 0)
+ pts = 0;
+ for (int i = 0; i < mpctx->num_timeline_parts; i++) {
+ struct timeline_part *p = mpctx->timeline + i;
+ if (pts < (p + 1)->start) {
+ *need_reset = timeline_set_part(mpctx, i, false);
+ return pts - p->start + p->source_start;
+ }
+ }
+ return -1;
+}
+
+
+// return -1 if seek failed (non-seekable stream?), 0 otherwise
+static int seek(MPContext *mpctx, struct seek_params seek,
+ bool timeline_fallthrough)
+{
+ struct MPOpts *opts = mpctx->opts;
+ uint64_t prev_seek_ts = mpctx->vo_pts_history_seek_ts;
+
+ if (!mpctx->demuxer)
+ return -1;
+
+ if (mpctx->stop_play == AT_END_OF_FILE)
+ mpctx->stop_play = KEEP_PLAYING;
+ bool hr_seek = mpctx->demuxer->accurate_seek && opts->correct_pts;
+ hr_seek &= seek.exact >= 0 && seek.type != MPSEEK_FACTOR;
+ hr_seek &= (opts->hr_seek == 0 && seek.type == MPSEEK_ABSOLUTE) ||
+ opts->hr_seek > 0 || seek.exact > 0;
+ if (seek.type == MPSEEK_FACTOR || seek.amount < 0 ||
+ (seek.type == MPSEEK_ABSOLUTE && seek.amount < mpctx->last_chapter_pts))
+ mpctx->last_chapter_seek = -2;
+ if (seek.type == MPSEEK_FACTOR) {
+ double len = get_time_length(mpctx);
+ if (len > 0 && !mpctx->demuxer->ts_resets_possible) {
+ seek.amount = seek.amount * len + get_start_time(mpctx);
+ seek.type = MPSEEK_ABSOLUTE;
+ }
+ }
+ if ((mpctx->demuxer->accurate_seek || mpctx->timeline)
+ && seek.type == MPSEEK_RELATIVE) {
+ seek.type = MPSEEK_ABSOLUTE;
+ seek.direction = seek.amount > 0 ? 1 : -1;
+ seek.amount += get_current_time(mpctx);
+ }
+
+ /* At least the liba52 decoder wants to read from the input stream
+ * during initialization, so reinit must be done after the demux_seek()
+ * call that clears possible stream EOF. */
+ bool need_reset = false;
+ double demuxer_amount = seek.amount;
+ if (mpctx->timeline) {
+ demuxer_amount = timeline_set_from_time(mpctx, seek.amount,
+ &need_reset);
+ if (demuxer_amount == -1) {
+ assert(!need_reset);
+ mpctx->stop_play = AT_END_OF_FILE;
+ // Clear audio from current position
+ if (mpctx->sh_audio && !timeline_fallthrough) {
+ ao_reset(mpctx->ao);
+ mpctx->sh_audio->a_buffer_len = 0;
+ }
+ return -1;
+ }
+ }
+ if (need_reset) {
+ reinit_video_chain(mpctx);
+ reinit_subs(mpctx);
+ }
+
+ int demuxer_style = 0;
+ switch (seek.type) {
+ case MPSEEK_FACTOR:
+ demuxer_style |= SEEK_ABSOLUTE | SEEK_FACTOR;
+ break;
+ case MPSEEK_ABSOLUTE:
+ demuxer_style |= SEEK_ABSOLUTE;
+ break;
+ }
+ if (hr_seek || seek.direction < 0)
+ demuxer_style |= SEEK_BACKWARD;
+ else if (seek.direction > 0)
+ demuxer_style |= SEEK_FORWARD;
+ if (hr_seek || opts->mkv_subtitle_preroll)
+ demuxer_style |= SEEK_SUBPREROLL;
+
+ if (hr_seek)
+ demuxer_amount -= opts->hr_seek_demuxer_offset;
+ int seekresult = demux_seek(mpctx->demuxer, demuxer_amount,
+ mpctx->audio_delay, demuxer_style);
+ if (seekresult == 0) {
+ if (need_reset) {
+ reinit_audio_chain(mpctx);
+ seek_reset(mpctx, !timeline_fallthrough, false);
+ }
+ return -1;
+ }
+
+ // If audio or demuxer subs come from different files, seek them too:
+ bool have_external_tracks = false;
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ have_external_tracks |= track && track->is_external && track->demuxer;
+ }
+ if (have_external_tracks) {
+ double main_new_pos;
+ if (seek.type == MPSEEK_ABSOLUTE) {
+ main_new_pos = seek.amount - mpctx->video_offset;
+ } else {
+ main_new_pos = get_main_demux_pts(mpctx);
+ }
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ struct track *track = mpctx->current_track[type];
+ if (track && track->is_external && track->demuxer)
+ demux_seek(track->demuxer, main_new_pos, mpctx->audio_delay,
+ SEEK_ABSOLUTE);
+ }
+ }
+
+ if (need_reset)
+ reinit_audio_chain(mpctx);
+ /* If we just reinitialized audio it doesn't need to be reset,
+ * and resetting could lose audio some decoders produce during init. */
+ seek_reset(mpctx, !timeline_fallthrough, !need_reset);
+
+ if (timeline_fallthrough) {
+ // Important if video reinit happens.
+ mpctx->vo_pts_history_seek_ts = prev_seek_ts;
+ } else {
+ mpctx->vo_pts_history_seek_ts++;
+ mpctx->backstep_active = false;
+ }
+
+ /* Use the target time as "current position" for further relative
+ * seeks etc until a new video frame has been decoded */
+ if (seek.type == MPSEEK_ABSOLUTE) {
+ mpctx->video_pts = seek.amount;
+ mpctx->last_seek_pts = seek.amount;
+ } else
+ mpctx->last_seek_pts = MP_NOPTS_VALUE;
+
+ // The hr_seek==false case is for skipping frames with PTS before the
+ // current timeline chapter start. It's not really known where the demuxer
+ // level seek will end up, so the hrseek mechanism is abused to skip all
+ // frames before chapter start by setting hrseek_pts to the chapter start.
+ // It does nothing when the seek is inside of the current chapter, and
+ // seeking past the chapter is handled elsewhere.
+ if (hr_seek || mpctx->timeline) {
+ mpctx->hrseek_active = true;
+ mpctx->hrseek_framedrop = true;
+ mpctx->hrseek_pts = hr_seek ? seek.amount
+ : mpctx->timeline[mpctx->timeline_part].start;
+ }
+
+ mpctx->start_timestamp = mp_time_sec();
+
+ return 0;
+}
+
+void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
+ int exact)
+{
+ struct seek_params *seek = &mpctx->seek;
+ switch (type) {
+ case MPSEEK_RELATIVE:
+ if (seek->type == MPSEEK_FACTOR)
+ return; // Well... not common enough to bother doing better
+ seek->amount += amount;
+ seek->exact = FFMAX(seek->exact, exact);
+ if (seek->type == MPSEEK_NONE)
+ seek->exact = exact;
+ if (seek->type == MPSEEK_ABSOLUTE)
+ return;
+ if (seek->amount == 0) {
+ *seek = (struct seek_params){ 0 };
+ return;
+ }
+ seek->type = MPSEEK_RELATIVE;
+ return;
+ case MPSEEK_ABSOLUTE:
+ case MPSEEK_FACTOR:
+ *seek = (struct seek_params) {
+ .type = type,
+ .amount = amount,
+ .exact = exact,
+ };
+ return;
+ case MPSEEK_NONE:
+ *seek = (struct seek_params){ 0 };
+ return;
+ }
+ abort();
+}
+
+static void execute_queued_seek(struct MPContext *mpctx)
+{
+ if (mpctx->seek.type) {
+ seek(mpctx, mpctx->seek, false);
+ mpctx->seek = (struct seek_params){0};
+ }
+}
+
+double get_time_length(struct MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return 0;
+
+ if (mpctx->timeline)
+ return mpctx->timeline[mpctx->num_timeline_parts].start;
+
+ double len = demuxer_get_time_length(demuxer);
+ if (len >= 0)
+ return len;
+
+ // Unknown
+ return 0;
+}
+
+/* If there are timestamps from stream level then use those (for example
+ * DVDs can have consistent times there while the MPEG-level timestamps
+ * reset). */
+double get_current_time(struct MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return 0;
+ if (demuxer->stream_pts != MP_NOPTS_VALUE)
+ return demuxer->stream_pts;
+ if (mpctx->playback_pts != MP_NOPTS_VALUE)
+ return mpctx->playback_pts;
+ if (mpctx->last_seek_pts != MP_NOPTS_VALUE)
+ return mpctx->last_seek_pts;
+ return 0;
+}
+
+double get_start_time(struct MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return 0;
+ return demuxer_get_start_time(demuxer);
+}
+
+// Return playback position in 0.0-1.0 ratio, or -1 if unknown.
+double get_current_pos_ratio(struct MPContext *mpctx, bool use_range)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return -1;
+ double ans = -1;
+ double start = get_start_time(mpctx);
+ double len = get_time_length(mpctx);
+ if (use_range) {
+ double startpos = rel_time_to_abs(mpctx, mpctx->opts->play_start,
+ MP_NOPTS_VALUE);
+ double endpos = get_play_end_pts(mpctx);
+ if (endpos == MP_NOPTS_VALUE || endpos > start + len)
+ endpos = start + len;
+ if (startpos == MP_NOPTS_VALUE || startpos < start)
+ startpos = start;
+ if (endpos < startpos)
+ endpos = startpos;
+ start = startpos;
+ len = endpos - startpos;
+ }
+ double pos = get_current_time(mpctx);
+ if (len > 0 && !demuxer->ts_resets_possible) {
+ ans = av_clipf((pos - start) / len, 0, 1);
+ } else {
+ int64_t size = (demuxer->movi_end - demuxer->movi_start);
+ int64_t fpos = demuxer->filepos > 0 ?
+ demuxer->filepos : stream_tell(demuxer->stream);
+ if (size > 0)
+ ans = av_clipf((double)(fpos - demuxer->movi_start) / size, 0, 1);
+ }
+ if (use_range) {
+ if (mpctx->opts->play_frames > 0)
+ ans = max(ans, 1.0 -
+ mpctx->max_frames / (double) mpctx->opts->play_frames);
+ }
+ return ans;
+}
+
+int get_percent_pos(struct MPContext *mpctx)
+{
+ return av_clip(get_current_pos_ratio(mpctx, false) * 100, 0, 100);
+}
+
+// -2 is no chapters, -1 is before first chapter
+int get_current_chapter(struct MPContext *mpctx)
+{
+ double current_pts = get_current_time(mpctx);
+ if (mpctx->chapters) {
+ int i;
+ for (i = 1; i < mpctx->num_chapters; i++)
+ if (current_pts < mpctx->chapters[i].start)
+ break;
+ return FFMAX(mpctx->last_chapter_seek, i - 1);
+ }
+ if (mpctx->master_demuxer)
+ return FFMAX(mpctx->last_chapter_seek,
+ demuxer_get_current_chapter(mpctx->master_demuxer, current_pts));
+ return -2;
+}
+
+char *chapter_display_name(struct MPContext *mpctx, int chapter)
+{
+ char *name = chapter_name(mpctx, chapter);
+ char *dname = name;
+ if (name) {
+ dname = talloc_asprintf(NULL, "(%d) %s", chapter + 1, name);
+ } else if (chapter < -1) {
+ dname = talloc_strdup(NULL, "(unavailable)");
+ } else {
+ int chapter_count = get_chapter_count(mpctx);
+ if (chapter_count <= 0)
+ dname = talloc_asprintf(NULL, "(%d)", chapter + 1);
+ else
+ dname = talloc_asprintf(NULL, "(%d) of %d", chapter + 1,
+ chapter_count);
+ }
+ if (dname != name)
+ talloc_free(name);
+ return dname;
+}
+
+// returns NULL if chapter name unavailable
+char *chapter_name(struct MPContext *mpctx, int chapter)
+{
+ if (mpctx->chapters) {
+ if (chapter < 0 || chapter >= mpctx->num_chapters)
+ return NULL;
+ return talloc_strdup(NULL, mpctx->chapters[chapter].name);
+ }
+ if (mpctx->master_demuxer)
+ return demuxer_chapter_name(mpctx->master_demuxer, chapter);
+ return NULL;
+}
+
+// returns the start of the chapter in seconds (-1 if unavailable)
+double chapter_start_time(struct MPContext *mpctx, int chapter)
+{
+ if (mpctx->chapters)
+ return mpctx->chapters[chapter].start;
+ if (mpctx->master_demuxer)
+ return demuxer_chapter_time(mpctx->master_demuxer, chapter);
+ return -1;
+}
+
+int get_chapter_count(struct MPContext *mpctx)
+{
+ if (mpctx->chapters)
+ return mpctx->num_chapters;
+ if (mpctx->master_demuxer)
+ return demuxer_chapter_count(mpctx->master_demuxer);
+ return 0;
+}
+
+// Seek to a given chapter. Tries to queue the seek, but might seek immediately
+// in some cases. Returns success, no matter if seek is queued or immediate.
+bool mp_seek_chapter(struct MPContext *mpctx, int chapter)
+{
+ int num = get_chapter_count(mpctx);
+ if (num == 0)
+ return false;
+ if (chapter < 0 || chapter >= num)
+ return false;
+
+ mpctx->last_chapter_seek = -2;
+
+ double pts;
+ if (mpctx->chapters) {
+ pts = mpctx->chapters[chapter].start;
+ goto do_seek;
+ } else if (mpctx->master_demuxer) {
+ int res = demuxer_seek_chapter(mpctx->master_demuxer, chapter, &pts);
+ if (res >= 0) {
+ if (pts == -1) {
+ // for DVD/BD - seek happened via stream layer
+ seek_reset(mpctx, true, true);
+ mpctx->seek = (struct seek_params){0};
+ return true;
+ }
+ chapter = res;
+ goto do_seek;
+ }
+ }
+ return false;
+
+do_seek:
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, 0);
+ mpctx->last_chapter_seek = chapter;
+ mpctx->last_chapter_pts = pts;
+ return true;
+}
+
+static void update_avsync(struct MPContext *mpctx)
+{
+ if (!mpctx->sh_audio || !mpctx->sh_video)
+ return;
+
+ double a_pos = playing_audio_pts(mpctx);
+
+ mpctx->last_av_difference = a_pos - mpctx->video_pts - mpctx->audio_delay;
+ if (mpctx->time_frame > 0)
+ mpctx->last_av_difference +=
+ mpctx->time_frame * mpctx->opts->playback_speed;
+ if (a_pos == MP_NOPTS_VALUE || mpctx->video_pts == MP_NOPTS_VALUE)
+ mpctx->last_av_difference = MP_NOPTS_VALUE;
+ if (mpctx->last_av_difference > 0.5 && mpctx->drop_frame_cnt > 50
+ && !mpctx->drop_message_shown) {
+ mp_tmsg(MSGT_AVSYNC, MSGL_WARN, "%s", mp_gtext(av_desync_help_text));
+ mpctx->drop_message_shown = true;
+ }
+}
+
+static void handle_pause_on_low_cache(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ int cache = mp_get_cache_percent(mpctx);
+ bool idle = mp_get_cache_idle(mpctx);
+ if (mpctx->paused && mpctx->paused_for_cache) {
+ if (cache < 0 || cache >= opts->stream_cache_min_percent || idle) {
+ mpctx->paused_for_cache = false;
+ if (!opts->pause)
+ unpause_player(mpctx);
+ }
+ } else {
+ if (cache >= 0 && cache <= opts->stream_cache_pause && !idle) {
+ bool prev_paused_user = opts->pause;
+ pause_player(mpctx);
+ mpctx->paused_for_cache = true;
+ opts->pause = prev_paused_user;
+ }
+ }
+}
+
+static double get_wakeup_period(struct MPContext *mpctx)
+{
+ /* Even if we can immediately wake up in response to most input events,
+ * there are some timers which are not registered to the event loop
+ * and need to be checked periodically (like automatic mouse cursor hiding).
+ * OSD content updates behave similarly. Also some uncommon input devices
+ * may not have proper FD event support.
+ */
+ double sleeptime = WAKEUP_PERIOD;
+
+#ifndef HAVE_POSIX_SELECT
+ // No proper file descriptor event handling; keep waking up to poll input
+ sleeptime = FFMIN(sleeptime, 0.02);
+#endif
+
+ if (mpctx->video_out)
+ if (mpctx->video_out->wakeup_period > 0)
+ sleeptime = FFMIN(sleeptime, mpctx->video_out->wakeup_period);
+
+ return sleeptime;
+}
+
+static void run_playloop(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ bool full_audio_buffers = false;
+ bool audio_left = false, video_left = false;
+ double endpts = get_play_end_pts(mpctx);
+ bool end_is_chapter = false;
+ double sleeptime = get_wakeup_period(mpctx);
+ bool was_restart = mpctx->restart_playback;
+ bool new_frame_shown = false;
+
+#ifdef CONFIG_ENCODING
+ if (encode_lavc_didfail(mpctx->encode_lavc_ctx)) {
+ mpctx->stop_play = PT_QUIT;
+ return;
+ }
+#endif
+
+ // Add tracks that were added by the demuxer later (e.g. MPEG)
+ if (!mpctx->timeline && mpctx->demuxer)
+ add_demuxer_tracks(mpctx, mpctx->demuxer);
+
+ if (mpctx->timeline) {
+ double end = mpctx->timeline[mpctx->timeline_part + 1].start;
+ if (endpts == MP_NOPTS_VALUE || end < endpts) {
+ endpts = end;
+ end_is_chapter = true;
+ }
+ }
+
+ if (opts->chapterrange[1] > 0) {
+ int cur_chapter = get_current_chapter(mpctx);
+ if (cur_chapter != -1 && cur_chapter + 1 > opts->chapterrange[1])
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ }
+
+ if (mpctx->sh_audio && !mpctx->restart_playback && !mpctx->ao->untimed) {
+ int status = fill_audio_out_buffers(mpctx, endpts);
+ full_audio_buffers = status >= 0;
+ // Not at audio stream EOF yet
+ audio_left = status > -2;
+ }
+
+ double buffered_audio = -1;
+ while (mpctx->sh_video) { // never loops, for "break;" only
+ struct vo *vo = mpctx->video_out;
+ update_fps(mpctx);
+
+ video_left = vo->hasframe || vo->frame_loaded;
+ if (!vo->frame_loaded && (!mpctx->paused || mpctx->restart_playback)) {
+ double frame_time = update_video(mpctx, endpts);
+ mp_dbg(MSGT_AVSYNC, MSGL_DBG2, "*** ftime=%5.3f ***\n", frame_time);
+ if (mpctx->sh_video->vf_initialized < 0) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_FATAL,
+ "\nFATAL: Could not initialize video filters (-vf) "
+ "or video output (-vo).\n");
+ uninit_player(mpctx, INITIALIZED_VCODEC | INITIALIZED_VO);
+ cleanup_demux_stream(mpctx, STREAM_VIDEO);
+ mpctx->current_track[STREAM_VIDEO] = NULL;
+ if (!mpctx->current_track[STREAM_AUDIO])
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ mpctx->error_playing = true;
+ break;
+ }
+ video_left = frame_time >= 0;
+ if (video_left && !mpctx->restart_playback) {
+ mpctx->time_frame += frame_time / opts->playback_speed;
+ adjust_sync(mpctx, frame_time);
+ }
+ if (!video_left) {
+ mpctx->delay = 0;
+ mpctx->last_av_difference = 0;
+ }
+ }
+
+ if (endpts != MP_NOPTS_VALUE)
+ video_left &= mpctx->sh_video->pts < endpts;
+
+ // ================================================================
+ vo_check_events(vo);
+
+ unsigned mouse_event_ts = mp_input_get_mouse_event_counter(mpctx->input);
+ if (mpctx->mouse_event_ts != mouse_event_ts) {
+ mpctx->mouse_event_ts = mouse_event_ts;
+ if (opts->vo.cursor_autohide_delay > -1) {
+ vo_control(vo, VOCTRL_SET_CURSOR_VISIBILITY, &(bool){true});
+ if (opts->vo.cursor_autohide_delay >= 0) {
+ mpctx->mouse_waiting_hide = 1;
+ mpctx->mouse_timer =
+ mp_time_sec() + opts->vo.cursor_autohide_delay / 1000.0;
+ }
+ }
+ }
+
+ if (mpctx->mouse_waiting_hide == 1 &&
+ mp_time_sec() >= mpctx->mouse_timer)
+ {
+ vo_control(vo, VOCTRL_SET_CURSOR_VISIBILITY, &(bool){false});
+ mpctx->mouse_waiting_hide = 2;
+ }
+
+ if (opts->heartbeat_cmd) {
+ double now = mp_time_sec();
+ if (now - mpctx->last_heartbeat > opts->heartbeat_interval) {
+ mpctx->last_heartbeat = now;
+ system(opts->heartbeat_cmd);
+ }
+ }
+
+ if (!video_left || (mpctx->paused && !mpctx->restart_playback))
+ break;
+ if (!vo->frame_loaded) {
+ sleeptime = 0;
+ break;
+ }
+
+ mpctx->time_frame -= get_relative_time(mpctx);
+ if (full_audio_buffers && !mpctx->restart_playback) {
+ buffered_audio = ao_get_delay(mpctx->ao);
+ mp_dbg(MSGT_AVSYNC, MSGL_DBG2, "delay=%f\n", buffered_audio);
+
+ if (opts->autosync) {
+ /* Smooth reported playback position from AO by averaging
+ * it with the value expected based on previus value and
+ * time elapsed since then. May help smooth video timing
+ * with audio output that have inaccurate position reporting.
+ * This is badly implemented; the behavior of the smoothing
+ * now undesirably depends on how often this code runs
+ * (mainly depends on video frame rate). */
+ float predicted = (mpctx->delay / opts->playback_speed +
+ mpctx->time_frame);
+ float difference = buffered_audio - predicted;
+ buffered_audio = predicted + difference / opts->autosync;
+ }
+
+ mpctx->time_frame = (buffered_audio -
+ mpctx->delay / opts->playback_speed);
+ } else {
+ /* If we're more than 200 ms behind the right playback
+ * position, don't try to speed up display of following
+ * frames to catch up; continue with default speed from
+ * the current frame instead.
+ * If untimed is set always output frames immediately
+ * without sleeping.
+ */
+ if (mpctx->time_frame < -0.2 || opts->untimed || vo->untimed)
+ mpctx->time_frame = 0;
+ }
+
+ double vsleep = mpctx->time_frame - vo->flip_queue_offset;
+ if (vsleep > 0.050) {
+ sleeptime = FFMIN(sleeptime, vsleep - 0.040);
+ break;
+ }
+ sleeptime = 0;
+
+ //=================== FLIP PAGE (VIDEO BLT): ======================
+
+ vo_new_frame_imminent(vo);
+ struct sh_video *sh_video = mpctx->sh_video;
+ mpctx->video_pts = sh_video->pts;
+ mpctx->last_vo_pts = mpctx->video_pts;
+ mpctx->playback_pts = mpctx->video_pts;
+ update_subtitles(mpctx, sh_video->pts);
+ update_osd_msg(mpctx);
+ draw_osd(mpctx);
+
+ mpctx->time_frame -= get_relative_time(mpctx);
+ mpctx->time_frame -= vo->flip_queue_offset;
+ if (mpctx->time_frame > 0.001)
+ mpctx->time_frame = timing_sleep(mpctx, mpctx->time_frame);
+ mpctx->time_frame += vo->flip_queue_offset;
+
+ int64_t t2 = mp_time_us();
+ /* Playing with playback speed it's possible to get pathological
+ * cases with mpctx->time_frame negative enough to cause an
+ * overflow in pts_us calculation, thus the FFMAX. */
+ double time_frame = FFMAX(mpctx->time_frame, -1);
+ int64_t pts_us = mpctx->last_time + time_frame * 1e6;
+ int duration = -1;
+ double pts2 = vo->next_pts2;
+ if (pts2 != MP_NOPTS_VALUE && opts->correct_pts &&
+ !mpctx->restart_playback) {
+ // expected A/V sync correction is ignored
+ double diff = (pts2 - mpctx->video_pts);
+ diff /= opts->playback_speed;
+ if (mpctx->time_frame < 0)
+ diff += mpctx->time_frame;
+ if (diff < 0)
+ diff = 0;
+ if (diff > 10)
+ diff = 10;
+ duration = diff * 1e6;
+ }
+ vo_flip_page(vo, pts_us | 1, duration);
+
+ mpctx->last_vo_flip_duration = (mp_time_us() - t2) * 0.000001;
+ if (vo->driver->flip_page_timed) {
+ // No need to adjust sync based on flip speed
+ mpctx->last_vo_flip_duration = 0;
+ // For print_status - VO call finishing early is OK for sync
+ mpctx->time_frame -= get_relative_time(mpctx);
+ }
+ if (mpctx->restart_playback) {
+ if (mpctx->sync_audio_to_video) {
+ mpctx->syncing_audio = true;
+ if (mpctx->sh_audio)
+ fill_audio_out_buffers(mpctx, endpts);
+ mpctx->restart_playback = false;
+ }
+ mpctx->time_frame = 0;
+ get_relative_time(mpctx);
+ }
+ update_avsync(mpctx);
+ print_status(mpctx);
+ screenshot_flip(mpctx);
+ new_frame_shown = true;
+
+ break;
+ } // video
+
+ video_left &= mpctx->sync_audio_to_video; // force no-video semantics
+
+ if (mpctx->sh_audio && (mpctx->restart_playback ? !video_left :
+ mpctx->ao->untimed && (mpctx->delay <= 0 ||
+ !video_left))) {
+ int status = fill_audio_out_buffers(mpctx, endpts);
+ full_audio_buffers = status >= 0 && !mpctx->ao->untimed;
+ // Not at audio stream EOF yet
+ audio_left = status > -2;
+ }
+ if (!video_left)
+ mpctx->restart_playback = false;
+ if (mpctx->sh_audio && buffered_audio == -1)
+ buffered_audio = mpctx->paused ? 0 : ao_get_delay(mpctx->ao);
+
+ update_osd_msg(mpctx);
+
+ // The cache status is part of the status line. Possibly update it.
+ if (mpctx->paused && mp_get_cache_percent(mpctx) >= 0)
+ print_status(mpctx);
+
+ if (!video_left && (!mpctx->paused || was_restart)) {
+ double a_pos = 0;
+ if (mpctx->sh_audio) {
+ a_pos = (written_audio_pts(mpctx) -
+ mpctx->opts->playback_speed * buffered_audio);
+ }
+ mpctx->playback_pts = a_pos;
+ print_status(mpctx);
+
+ if (!mpctx->sh_video)
+ update_subtitles(mpctx, a_pos);
+ }
+
+ /* It's possible for the user to simultaneously switch both audio
+ * and video streams to "disabled" at runtime. Handle this by waiting
+ * rather than immediately stopping playback due to EOF.
+ *
+ * When all audio has been written to output driver, stay in the
+ * main loop handling commands until it has been mostly consumed,
+ * except in the gapless case, where the next file will be started
+ * while audio from the current one still remains to be played.
+ *
+ * We want this check to trigger if we seeked to this position,
+ * but not if we paused at it with audio possibly still buffered in
+ * the AO. There's currently no working way to check buffered audio
+ * inside AO while paused. Thus the "was_restart" check below, which
+ * should trigger after seek only, when we know there's no audio
+ * buffered.
+ */
+ if ((mpctx->sh_audio || mpctx->sh_video) && !audio_left && !video_left
+ && (opts->gapless_audio || buffered_audio < 0.05)
+ && (!mpctx->paused || was_restart)) {
+ if (end_is_chapter) {
+ seek(mpctx, (struct seek_params){
+ .type = MPSEEK_ABSOLUTE,
+ .amount = mpctx->timeline[mpctx->timeline_part+1].start
+ }, true);
+ } else
+ mpctx->stop_play = AT_END_OF_FILE;
+ sleeptime = 0;
+ }
+
+ if (!mpctx->stop_play && !mpctx->restart_playback) {
+
+ // If no more video is available, one frame means one playloop iteration.
+ // Otherwise, one frame means one video frame.
+ if (!video_left)
+ new_frame_shown = true;
+
+ if (opts->playing_msg && !mpctx->playing_msg_shown && new_frame_shown) {
+ mpctx->playing_msg_shown = true;
+ char *msg = mp_property_expand_string(mpctx, opts->playing_msg);
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "%s\n", msg);
+ talloc_free(msg);
+ }
+
+ if (mpctx->max_frames >= 0) {
+ if (new_frame_shown)
+ mpctx->max_frames--;
+ if (mpctx->max_frames <= 0)
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ }
+
+ if (mpctx->step_frames > 0 && !mpctx->paused) {
+ if (new_frame_shown)
+ mpctx->step_frames--;
+ if (mpctx->step_frames == 0)
+ pause_player(mpctx);
+ }
+
+ }
+
+ if (!mpctx->stop_play) {
+ double audio_sleep = 9;
+ if (mpctx->sh_audio && !mpctx->paused) {
+ if (mpctx->ao->untimed) {
+ if (!video_left)
+ audio_sleep = 0;
+ } else if (full_audio_buffers) {
+ audio_sleep = buffered_audio - 0.050;
+ // Keep extra safety margin if the buffers are large
+ if (audio_sleep > 0.100)
+ audio_sleep = FFMAX(audio_sleep - 0.200, 0.100);
+ else
+ audio_sleep = FFMAX(audio_sleep, 0.020);
+ } else
+ audio_sleep = 0.020;
+ }
+ sleeptime = FFMIN(sleeptime, audio_sleep);
+ if (sleeptime > 0 && mpctx->sh_video) {
+ bool want_redraw = vo_get_want_redraw(mpctx->video_out);
+ if (mpctx->video_out->driver->draw_osd)
+ want_redraw |= mpctx->osd->want_redraw;
+ mpctx->osd->want_redraw = false;
+ if (want_redraw) {
+ if (redraw_osd(mpctx)) {
+ sleeptime = 0;
+ } else if (mpctx->paused && video_left) {
+ // force redrawing OSD by framestepping
+ add_step_frame(mpctx, 1);
+ sleeptime = 0;
+ }
+ }
+ }
+ if (sleeptime > 0)
+ mp_input_get_cmd(mpctx->input, sleeptime * 1000, true);
+ }
+
+ if (mp_time_sec() > mpctx->last_metadata_update + 2) {
+ demux_info_update(mpctx->demuxer);
+ mpctx->last_metadata_update = mp_time_sec();
+ }
+
+ //================= Keyboard events, SEEKing ====================
+
+ handle_pause_on_low_cache(mpctx);
+
+ mp_cmd_t *cmd;
+ while ((cmd = mp_input_get_cmd(mpctx->input, 0, 1)) != NULL) {
+ /* Allow running consecutive seek commands to combine them,
+ * but execute the seek before running other commands.
+ * If the user seeks continuously (keeps arrow key down)
+ * try to finish showing a frame from one location before doing
+ * another seek (which could lead to unchanging display). */
+ if ((mpctx->seek.type && cmd->id != MP_CMD_SEEK) ||
+ (mpctx->restart_playback && cmd->id == MP_CMD_SEEK &&
+ mp_time_sec() - mpctx->start_timestamp < 0.3))
+ break;
+ cmd = mp_input_get_cmd(mpctx->input, 0, 0);
+ run_command(mpctx, cmd);
+ mp_cmd_free(cmd);
+ if (mpctx->stop_play)
+ break;
+ }
+
+ if (mpctx->backstep_active) {
+ double current_pts = mpctx->last_vo_pts;
+ mpctx->backstep_active = false;
+ bool demuxer_ok = mpctx->demuxer && mpctx->demuxer->accurate_seek;
+ if (demuxer_ok && mpctx->sh_video && current_pts != MP_NOPTS_VALUE) {
+ double seek_pts = find_previous_pts(mpctx, current_pts);
+ if (seek_pts != MP_NOPTS_VALUE) {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, seek_pts, 1);
+ } else {
+ double last = get_last_frame_pts(mpctx);
+ if (last != MP_NOPTS_VALUE && last >= current_pts &&
+ mpctx->backstep_start_seek_ts != mpctx->vo_pts_history_seek_ts)
+ {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Backstep failed.\n");
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, current_pts, 1);
+ } else if (!mpctx->hrseek_active) {
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Start backstep indexing.\n");
+ // Force it to index the video up until current_pts.
+ // The whole point is getting frames _before_ that PTS,
+ // so apply an arbitrary offset. (In theory the offset
+ // has to be large enough to reach the previous frame.)
+ seek(mpctx, (struct seek_params){
+ .type = MPSEEK_ABSOLUTE,
+ .amount = current_pts - 1.0,
+ }, false);
+ // Don't leave hr-seek mode. If all goes right, hr-seek
+ // mode is cancelled as soon as the frame before
+ // current_pts is found during hr-seeking.
+ // Note that current_pts should be part of the index,
+ // otherwise we can't find the previous frame, so set the
+ // seek target an arbitrary amount of time after it.
+ if (mpctx->hrseek_active) {
+ mpctx->hrseek_pts = current_pts + 10.0;
+ mpctx->hrseek_framedrop = false;
+ mpctx->backstep_active = true;
+ }
+ } else {
+ mpctx->backstep_active = true;
+ }
+ }
+ }
+ }
+
+ // handle -sstep
+ if (opts->step_sec > 0 && !mpctx->stop_play && !mpctx->paused &&
+ !mpctx->restart_playback)
+ {
+ set_osd_function(mpctx, OSD_FFW);
+ queue_seek(mpctx, MPSEEK_RELATIVE, opts->step_sec, 0);
+ }
+
+ if (opts->keep_open && mpctx->stop_play == AT_END_OF_FILE) {
+ mpctx->stop_play = KEEP_PLAYING;
+ pause_player(mpctx);
+ if (mpctx->video_out && !mpctx->video_out->hasframe) {
+ // Force screen refresh to make OSD usable
+ double seek_to = mpctx->last_vo_pts;
+ if (seek_to == MP_NOPTS_VALUE)
+ seek_to = 0; // arbitrary default
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, seek_to, 1);
+ }
+ }
+
+ execute_queued_seek(mpctx);
+}
+
+static bool attachment_is_font(struct demux_attachment *att)
+{
+ if (!att->name || !att->type || !att->data || !att->data_size)
+ return false;
+ // match against MIME types
+ if (strcmp(att->type, "application/x-truetype-font") == 0
+ || strcmp(att->type, "application/x-font") == 0)
+ return true;
+ // fallback: match against file extension
+ if (strlen(att->name) > 4) {
+ char *ext = att->name + strlen(att->name) - 4;
+ if (strcasecmp(ext, ".ttf") == 0 || strcasecmp(ext, ".ttc") == 0
+ || strcasecmp(ext, ".otf") == 0)
+ return true;
+ }
+ return false;
+}
+
+// Result numerically higher => better match. 0 == no match.
+static int match_lang(char **langs, char *lang)
+{
+ for (int idx = 0; langs && langs[idx]; idx++) {
+ if (lang && strcmp(langs[idx], lang) == 0)
+ return INT_MAX - idx;
+ }
+ return 0;
+}
+
+/* Get the track wanted by the user.
+ * tid is the track ID requested by the user (-2: deselect, -1: default)
+ * lang is a string list, NULL is same as empty list
+ * Sort tracks based on the following criteria, and pick the first:
+ * 0) track matches tid (always wins)
+ * 1) track is external
+ * 1b) track was passed explicitly (is not an auto-loaded subtitle)
+ * 2) earlier match in lang list
+ * 3) track is marked default
+ * 4) lower track number
+ * If select_fallback is not set, 4) is only used to determine whether a
+ * matching track is preferred over another track. Otherwise, always pick a
+ * track (if nothing else matches, return the track with lowest ID).
+ */
+// Return whether t1 is preferred over t2
+static bool compare_track(struct track *t1, struct track *t2, char **langs)
+{
+ if (t1->is_external != t2->is_external)
+ return t1->is_external;
+ if (t1->auto_loaded != t2->auto_loaded)
+ return !t1->auto_loaded;
+ int l1 = match_lang(langs, t1->lang), l2 = match_lang(langs, t2->lang);
+ if (l1 != l2)
+ return l1 > l2;
+ if (t1->default_track != t2->default_track)
+ return t1->default_track;
+ if (t1->attached_picture != t2->attached_picture)
+ return !t1->attached_picture;
+ return t1->user_tid <= t2->user_tid;
+}
+static struct track *select_track(struct MPContext *mpctx,
+ enum stream_type type, int tid, char **langs)
+{
+ if (tid == -2)
+ return NULL;
+ bool select_fallback = type == STREAM_VIDEO || type == STREAM_AUDIO;
+ struct track *pick = NULL;
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->type != type)
+ continue;
+ if (track->user_tid == tid)
+ return track;
+ if (!pick || compare_track(track, pick, langs))
+ pick = track;
+ }
+ if (pick && !select_fallback && !pick->is_external
+ && !match_lang(langs, pick->lang) && !pick->default_track)
+ pick = NULL;
+ if (pick && pick->attached_picture && !mpctx->opts->audio_display)
+ pick = NULL;
+ return pick;
+}
+
+// Normally, video/audio/sub track selection is persistent across files. This
+// code resets track selection if the new file has a different track layout.
+static void check_previous_track_selection(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ if (!mpctx->track_layout_hash)
+ return;
+
+ char *h = track_layout_hash(mpctx);
+ if (strcmp(h, mpctx->track_layout_hash) != 0) {
+ // Reset selection, but only if they're not "auto" or "off".
+ if (opts->video_id >= 0)
+ mpctx->opts->video_id = -1;
+ if (opts->audio_id >= 0)
+ mpctx->opts->audio_id = -1;
+ if (opts->sub_id >= 0)
+ mpctx->opts->sub_id = -1;
+ talloc_free(mpctx->track_layout_hash);
+ mpctx->track_layout_hash = NULL;
+ }
+ talloc_free(h);
+}
+
+static int read_keys(void *ctx, int fd)
+{
+ if (getch2(ctx))
+ return MP_INPUT_NOTHING;
+ return MP_INPUT_DEAD;
+}
+
+static void init_input(struct MPContext *mpctx)
+{
+ mpctx->input = mp_input_init(mpctx->opts);
+ if (mpctx->opts->slave_mode)
+ mp_input_add_cmd_fd(mpctx->input, 0, USE_FD0_CMD_SELECT, MP_INPUT_SLAVE_CMD_FUNC, NULL);
+ else if (mpctx->opts->consolecontrols)
+ mp_input_add_key_fd(mpctx->input, 0, 1, read_keys, NULL, mpctx->input);
+ // Set the libstream interrupt callback
+ stream_set_interrupt_callback(mp_input_check_interrupt, mpctx->input);
+
+#ifdef CONFIG_COCOA
+ cocoa_set_input_context(mpctx->input);
+#endif
+}
+
+static void open_subtitles_from_options(struct MPContext *mpctx)
+{
+ // after reading video params we should load subtitles because
+ // we know fps so now we can adjust subtitle time to ~6 seconds AST
+ // check .sub
+ if (mpctx->opts->sub_name) {
+ for (int i = 0; mpctx->opts->sub_name[i] != NULL; ++i)
+ mp_add_subtitles(mpctx, mpctx->opts->sub_name[i], 0);
+ }
+ if (mpctx->opts->sub_auto) { // auto load sub file ...
+ char **tmp = find_text_subtitles(mpctx->opts, mpctx->filename);
+ int nsub = MP_TALLOC_ELEMS(tmp);
+ for (int i = 0; i < nsub; i++) {
+ struct track *track = mp_add_subtitles(mpctx, tmp[i], 1);
+ if (track)
+ track->auto_loaded = true;
+ }
+ talloc_free(tmp);
+ }
+}
+
+static struct track *open_external_file(struct MPContext *mpctx, char *filename,
+ char *demuxer_name, int stream_cache,
+ enum stream_type filter)
+{
+ struct MPOpts *opts = mpctx->opts;
+ if (!filename)
+ return NULL;
+ char *disp_filename = filename;
+ if (strncmp(disp_filename, "memory://", 9) == 0)
+ disp_filename = "memory://"; // avoid noise
+ struct stream *stream = stream_open(filename, mpctx->opts);
+ if (!stream)
+ goto err_out;
+ stream_enable_cache_percent(&stream, stream_cache,
+ opts->stream_cache_def_size,
+ opts->stream_cache_min_percent,
+ opts->stream_cache_seek_min_percent);
+ struct demuxer_params params = {
+ .ass_library = mpctx->ass_library, // demux_libass requires it
+ };
+ struct demuxer *demuxer =
+ demux_open(stream, demuxer_name, &params, mpctx->opts);
+ if (!demuxer) {
+ free_stream(stream);
+ goto err_out;
+ }
+ struct track *first = NULL;
+ for (int n = 0; n < demuxer->num_streams; n++) {
+ struct sh_stream *sh = demuxer->streams[n];
+ if (sh->type == filter) {
+ struct track *t = add_stream_track(mpctx, sh, false);
+ t->is_external = true;
+ t->title = talloc_strdup(t, disp_filename);
+ t->external_filename = talloc_strdup(t, filename);
+ first = t;
+ }
+ }
+ if (!first) {
+ free_demuxer(demuxer);
+ free_stream(stream);
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "No streams added from file %s.\n",
+ disp_filename);
+ goto err_out;
+ }
+ MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, demuxer);
+ return first;
+
+err_out:
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Can not open external file %s.\n",
+ disp_filename);
+ return false;
+}
+
+static void open_audiofiles_from_options(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ open_external_file(mpctx, opts->audio_stream, opts->audio_demuxer_name,
+ opts->audio_stream_cache, STREAM_AUDIO);
+}
+
+struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename, int noerr)
+{
+ struct MPOpts *opts = mpctx->opts;
+ return open_external_file(mpctx, filename, opts->sub_demuxer_name, 0,
+ STREAM_SUB);
+}
+
+static void open_subtitles_from_resolve(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct mp_resolve_result *res = mpctx->resolve_result;
+ if (!res)
+ return;
+ for (int n = 0; n < res->num_subs; n++) {
+ struct mp_resolve_sub *sub = res->subs[n];
+ char *s = talloc_strdup(NULL, sub->url);
+ if (!s)
+ s = talloc_asprintf(NULL, "memory://%s", sub->data);
+ struct track *t =
+ open_external_file(mpctx, s, opts->sub_demuxer_name, 0, STREAM_SUB);
+ talloc_free(s);
+ if (t)
+ t->lang = talloc_strdup(t, sub->lang);
+ }
+}
+
+static void print_timeline(struct MPContext *mpctx)
+{
+ if (mpctx->timeline) {
+ int part_count = mpctx->num_timeline_parts;
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Timeline contains %d parts from %d "
+ "sources. Total length %.3f seconds.\n", part_count,
+ mpctx->num_sources, mpctx->timeline[part_count].start);
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Source files:\n");
+ for (int i = 0; i < mpctx->num_sources; i++)
+ mp_msg(MSGT_CPLAYER, MSGL_V, "%d: %s\n", i,
+ mpctx->sources[i]->filename);
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Timeline parts: (number, start, "
+ "source_start, source):\n");
+ for (int i = 0; i < part_count; i++) {
+ struct timeline_part *p = mpctx->timeline + i;
+ mp_msg(MSGT_CPLAYER, MSGL_V, "%3d %9.3f %9.3f %p/%s\n", i, p->start,
+ p->source_start, p->source, p->source->filename);
+ }
+ mp_msg(MSGT_CPLAYER, MSGL_V, "END %9.3f\n",
+ mpctx->timeline[part_count].start);
+ }
+}
+
+static void add_subtitle_fonts_from_sources(struct MPContext *mpctx)
+{
+#ifdef CONFIG_ASS
+ if (mpctx->opts->ass_enabled) {
+ for (int j = 0; j < mpctx->num_sources; j++) {
+ struct demuxer *d = mpctx->sources[j];
+ for (int i = 0; i < d->num_attachments; i++) {
+ struct demux_attachment *att = d->attachments + i;
+ if (mpctx->opts->use_embedded_fonts && attachment_is_font(att))
+ ass_add_font(mpctx->ass_library, att->name, att->data,
+ att->data_size);
+ }
+ }
+ }
+
+ // libass seems to misbehave if fonts are changed while a renderer
+ // exists, so we (re)create the renderer after fonts are set.
+ assert(!mpctx->osd->ass_renderer);
+ mpctx->osd->ass_renderer = ass_renderer_init(mpctx->osd->ass_library);
+ if (mpctx->osd->ass_renderer)
+ mp_ass_configure_fonts(mpctx->osd->ass_renderer,
+ mpctx->opts->sub_text_style);
+#endif
+}
+
+static struct mp_resolve_result *resolve_url(const char *filename,
+ struct MPOpts *opts)
+{
+#if defined(CONFIG_LIBQUVI) || defined(CONFIG_LIBQUVI9)
+ return mp_resolve_quvi(filename, opts);
+#else
+ return NULL;
+#endif
+}
+
+// Waiting for the slave master to send us a new file to play.
+static void idle_loop(struct MPContext *mpctx)
+{
+ // ================= idle loop (STOP state) =========================
+ while (mpctx->opts->player_idle_mode && !mpctx->playlist->current
+ && mpctx->stop_play != PT_QUIT)
+ {
+ uninit_player(mpctx, INITIALIZED_AO | INITIALIZED_VO);
+ mp_cmd_t *cmd;
+ while (!(cmd = mp_input_get_cmd(mpctx->input,
+ get_wakeup_period(mpctx) * 1000,
+ false)));
+ run_command(mpctx, cmd);
+ mp_cmd_free(cmd);
+ }
+}
+
+static void stream_dump(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ char *filename = opts->stream_dump;
+ stream_t *stream = mpctx->stream;
+ assert(stream && filename);
+
+ stream_set_capture_file(stream, filename);
+
+ while (mpctx->stop_play == KEEP_PLAYING && !stream->eof) {
+ if (!opts->quiet && ((stream->pos / (1024 * 1024)) % 2) == 1) {
+ uint64_t pos = stream->pos - stream->start_pos;
+ uint64_t end = stream->end_pos - stream->start_pos;
+ char *line = talloc_asprintf(NULL, "Dumping %lld/%lld...",
+ (long long int)pos, (long long int)end);
+ write_status_line(mpctx, line);
+ talloc_free(line);
+ }
+ stream_fill_buffer(stream);
+ for (;;) {
+ mp_cmd_t *cmd = mp_input_get_cmd(mpctx->input, 0, false);
+ if (!cmd)
+ break;
+ run_command(mpctx, cmd);
+ talloc_free(cmd);
+ }
+ }
+}
+
+// Start playing the current playlist entry.
+// Handle initialization and deinitialization.
+static void play_current_file(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ mpctx->stop_play = 0;
+ mpctx->filename = NULL;
+
+ if (mpctx->playlist->current)
+ mpctx->filename = mpctx->playlist->current->filename;
+
+ if (!mpctx->filename)
+ goto terminate_playback;
+
+#ifdef CONFIG_ENCODING
+ encode_lavc_discontinuity(mpctx->encode_lavc_ctx);
+#endif
+
+ mpctx->add_osd_seek_info &= OSD_SEEK_INFO_EDITION;
+
+ load_per_protocol_config(mpctx->mconfig, mpctx->filename);
+ load_per_extension_config(mpctx->mconfig, mpctx->filename);
+ load_per_file_config(mpctx->mconfig, mpctx->filename, opts->use_filedir_conf);
+
+ if (opts->vo.video_driver_list)
+ load_per_output_config(mpctx->mconfig, PROFILE_CFG_VO,
+ opts->vo.video_driver_list[0].name);
+ if (opts->audio_driver_list)
+ load_per_output_config(mpctx->mconfig, PROFILE_CFG_AO,
+ opts->audio_driver_list[0].name);
+
+ if (opts->position_resume)
+ load_playback_resume(mpctx->mconfig, mpctx->filename);
+
+ load_per_file_options(mpctx->mconfig, mpctx->playlist->current->params,
+ mpctx->playlist->current->num_params);
+
+ if (opts->reset_options) {
+ for (int n = 0; opts->reset_options[n]; n++) {
+ const char *opt = opts->reset_options[n];
+ if (strcmp(opt, "all") == 0) {
+ m_config_backup_all_opts(mpctx->mconfig);
+ } else {
+ m_config_backup_opt(mpctx->mconfig, opt);
+ }
+ }
+ }
+
+ // We must enable getch2 here to be able to interrupt network connection
+ // or cache filling
+ if (opts->consolecontrols && !opts->slave_mode) {
+ if (mpctx->initialized_flags & INITIALIZED_GETCH2)
+ mp_tmsg(MSGT_CPLAYER, MSGL_WARN,
+ "WARNING: getch2_init called twice!\n");
+ else
+ getch2_enable(); // prepare stdin for hotkeys...
+ mpctx->initialized_flags |= INITIALIZED_GETCH2;
+ mp_msg(MSGT_CPLAYER, MSGL_DBG2, "\n[[[init getch2]]]\n");
+ }
+
+#ifdef CONFIG_ASS
+ if (opts->ass_style_override)
+ ass_set_style_overrides(mpctx->ass_library, opts->ass_force_style_list);
+#endif
+
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Playing %s.\n", mpctx->filename);
+
+ //============ Open & Sync STREAM --- fork cache2 ====================
+
+ assert(mpctx->stream == NULL);
+ assert(mpctx->demuxer == NULL);
+ assert(mpctx->sh_audio == NULL);
+ assert(mpctx->sh_video == NULL);
+ assert(mpctx->sh_sub == NULL);
+
+ char *stream_filename = mpctx->filename;
+ mpctx->resolve_result = resolve_url(stream_filename, opts);
+ if (mpctx->resolve_result) {
+ if (mpctx->resolve_result->playlist) {
+ // Replace entry with playlist contents
+ playlist_transfer_entries(mpctx->playlist,
+ mpctx->resolve_result->playlist);
+ if (mpctx->playlist->current)
+ playlist_remove(mpctx->playlist, mpctx->playlist->current);
+ goto terminate_playback;
+ }
+ stream_filename = mpctx->resolve_result->url;
+ }
+ mpctx->stream = stream_open(stream_filename, opts);
+ if (!mpctx->stream) { // error...
+ demux_was_interrupted(mpctx);
+ goto terminate_playback;
+ }
+ mpctx->initialized_flags |= INITIALIZED_STREAM;
+
+ mpctx->stream->start_pos += opts->seek_to_byte;
+
+ if (opts->stream_dump && opts->stream_dump[0]) {
+ stream_dump(mpctx);
+ goto terminate_playback;
+ }
+
+ // CACHE2: initial prefill: 20% later: 5% (should be set by -cacheopts)
+ int res = stream_enable_cache_percent(&mpctx->stream,
+ opts->stream_cache_size,
+ opts->stream_cache_def_size,
+ opts->stream_cache_min_percent,
+ opts->stream_cache_seek_min_percent);
+ if (res == 0)
+ if (demux_was_interrupted(mpctx))
+ goto terminate_playback;
+
+ stream_set_capture_file(mpctx->stream, opts->stream_capture);
+
+#ifdef CONFIG_DVBIN
+goto_reopen_demuxer: ;
+#endif
+
+ //============ Open DEMUXERS --- DETECT file type =======================
+
+ mpctx->audio_delay = opts->audio_delay;
+
+ mpctx->demuxer = demux_open(mpctx->stream, opts->demuxer_name, NULL, opts);
+ mpctx->master_demuxer = mpctx->demuxer;
+
+ if (!mpctx->demuxer) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR, "Failed to recognize file format.\n");
+ goto terminate_playback;
+ }
+
+ if (mpctx->demuxer->matroska_data.ordered_chapters)
+ build_ordered_chapter_timeline(mpctx);
+
+ if (mpctx->demuxer->type == DEMUXER_TYPE_EDL)
+ build_edl_timeline(mpctx);
+
+ if (mpctx->demuxer->type == DEMUXER_TYPE_CUE)
+ build_cue_timeline(mpctx);
+
+ print_timeline(mpctx);
+
+ if (!mpctx->num_sources) {
+ MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources,
+ mpctx->demuxer);
+ }
+
+ if (mpctx->timeline) {
+ // With Matroska, the "master" file usually dictates track layout etc.
+ // On the contrary, the EDL and CUE demuxers are empty wrappers, as
+ // well as Matroska ordered chapter playlist-like files.
+ for (int n = 0; n < mpctx->num_timeline_parts; n++) {
+ if (mpctx->timeline[n].source == mpctx->demuxer)
+ goto main_is_ok;
+ }
+ mpctx->demuxer = mpctx->timeline[0].source;
+ main_is_ok: ;
+ }
+ add_dvd_tracks(mpctx);
+ add_demuxer_tracks(mpctx, mpctx->demuxer);
+
+ mpctx->timeline_part = 0;
+ if (mpctx->timeline)
+ timeline_set_part(mpctx, mpctx->timeline_part, true);
+
+ mpctx->initialized_flags |= INITIALIZED_DEMUXER;
+
+ add_subtitle_fonts_from_sources(mpctx);
+
+ open_subtitles_from_options(mpctx);
+ open_subtitles_from_resolve(mpctx);
+ open_audiofiles_from_options(mpctx);
+
+ check_previous_track_selection(mpctx);
+
+ mpctx->current_track[STREAM_VIDEO] =
+ select_track(mpctx, STREAM_VIDEO, mpctx->opts->video_id, NULL);
+ mpctx->current_track[STREAM_AUDIO] =
+ select_track(mpctx, STREAM_AUDIO, mpctx->opts->audio_id,
+ mpctx->opts->audio_lang);
+ mpctx->current_track[STREAM_SUB] =
+ select_track(mpctx, STREAM_SUB, mpctx->opts->sub_id,
+ mpctx->opts->sub_lang);
+
+ demux_info_print(mpctx->master_demuxer);
+ print_file_properties(mpctx, mpctx->filename);
+
+ preselect_demux_streams(mpctx);
+
+#ifdef CONFIG_ENCODING
+ if (mpctx->encode_lavc_ctx && mpctx->current_track[STREAM_VIDEO])
+ encode_lavc_expect_stream(mpctx->encode_lavc_ctx, AVMEDIA_TYPE_VIDEO);
+ if (mpctx->encode_lavc_ctx && mpctx->current_track[STREAM_AUDIO])
+ encode_lavc_expect_stream(mpctx->encode_lavc_ctx, AVMEDIA_TYPE_AUDIO);
+#endif
+
+ reinit_video_chain(mpctx);
+ reinit_audio_chain(mpctx);
+ reinit_subs(mpctx);
+
+ //================ SETUP STREAMS ==========================
+
+ if (opts->force_fps && mpctx->sh_video) {
+ mpctx->sh_video->fps = opts->force_fps;
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO,
+ "FPS forced to be %5.3f.\n", mpctx->sh_video->fps);
+ }
+
+ //==================== START PLAYING =======================
+
+ if (!mpctx->sh_video && !mpctx->sh_audio) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_FATAL,
+ "No video or audio streams selected.\n");
+#ifdef CONFIG_DVBIN
+ if (mpctx->stream->type == STREAMTYPE_DVB) {
+ int dir;
+ int v = mpctx->last_dvb_step;
+ if (v > 0)
+ dir = DVB_CHANNEL_HIGHER;
+ else
+ dir = DVB_CHANNEL_LOWER;
+
+ if (dvb_step_channel(mpctx->stream, dir)) {
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ mpctx->dvbin_reopen = 1;
+ }
+ }
+#endif
+ goto terminate_playback;
+ }
+
+ mp_tmsg(MSGT_CPLAYER, MSGL_V, "Starting playback...\n");
+
+ mpctx->drop_frame_cnt = 0;
+ mpctx->dropped_frames = 0;
+ mpctx->max_frames = opts->play_frames;
+
+ if (mpctx->max_frames == 0) {
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ goto terminate_playback;
+ }
+
+ mpctx->time_frame = 0;
+ mpctx->drop_message_shown = 0;
+ mpctx->restart_playback = true;
+ mpctx->video_pts = 0;
+ mpctx->last_vo_pts = MP_NOPTS_VALUE;
+ mpctx->last_seek_pts = 0;
+ mpctx->playback_pts = MP_NOPTS_VALUE;
+ mpctx->hrseek_active = false;
+ mpctx->hrseek_framedrop = false;
+ mpctx->step_frames = 0;
+ mpctx->backstep_active = false;
+ mpctx->total_avsync_change = 0;
+ mpctx->last_chapter_seek = -2;
+ mpctx->playing_msg_shown = false;
+ mpctx->paused = false;
+ mpctx->paused_for_cache = false;
+ mpctx->seek = (struct seek_params){ 0 };
+
+ // If there's a timeline force an absolute seek to initialize state
+ double startpos = rel_time_to_abs(mpctx, opts->play_start, -1);
+ if (startpos != -1 || mpctx->timeline) {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, 0);
+ execute_queued_seek(mpctx);
+ }
+ if (startpos == -1 && mpctx->resolve_result &&
+ mpctx->resolve_result->start_time > 0)
+ {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->resolve_result->start_time, 0);
+ execute_queued_seek(mpctx);
+ }
+ if (opts->chapterrange[0] > 0) {
+ if (mp_seek_chapter(mpctx, opts->chapterrange[0] - 1))
+ execute_queued_seek(mpctx);
+ }
+
+ get_relative_time(mpctx); // reset current delta
+
+ if (mpctx->opts->pause)
+ pause_player(mpctx);
+
+ mpctx->error_playing = false;
+ while (!mpctx->stop_play)
+ run_playloop(mpctx);
+
+ mp_msg(MSGT_GLOBAL, MSGL_V, "EOF code: %d \n", mpctx->stop_play);
+
+#ifdef CONFIG_DVBIN
+ if (mpctx->dvbin_reopen) {
+ mpctx->stop_play = 0;
+ uninit_player(mpctx, INITIALIZED_ALL - (INITIALIZED_STREAM | INITIALIZED_GETCH2 | (opts->fixed_vo ? INITIALIZED_VO : 0)));
+ mpctx->dvbin_reopen = 0;
+ goto goto_reopen_demuxer;
+ }
+#endif
+
+terminate_playback: // don't jump here after ao/vo/getch initialization!
+
+ if (opts->position_save_on_quit && mpctx->stop_play != PT_RESTART)
+ mp_write_watch_later_conf(mpctx);
+
+ if (mpctx->step_frames)
+ opts->pause = 1;
+
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "\n");
+
+ // time to uninit all, except global stuff:
+ int uninitialize_parts = INITIALIZED_ALL;
+ if (opts->fixed_vo)
+ uninitialize_parts -= INITIALIZED_VO;
+ if ((opts->gapless_audio && mpctx->stop_play == AT_END_OF_FILE) ||
+ mpctx->encode_lavc_ctx)
+ uninitialize_parts -= INITIALIZED_AO;
+ uninit_player(mpctx, uninitialize_parts);
+
+ // xxx handle this as INITIALIZED_CONFIG?
+ m_config_restore_backups(mpctx->mconfig);
+
+ mpctx->filename = NULL;
+ talloc_free(mpctx->resolve_result);
+ mpctx->resolve_result = NULL;
+
+#ifdef CONFIG_ASS
+ if (mpctx->osd->ass_renderer)
+ ass_renderer_done(mpctx->osd->ass_renderer);
+ mpctx->osd->ass_renderer = NULL;
+ ass_clear_fonts(mpctx->ass_library);
+#endif
+}
+
+// Determine the next file to play. Note that if this function returns non-NULL,
+// it can have side-effects and mutate mpctx.
+struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction)
+{
+ struct playlist_entry *next = playlist_get_next(mpctx->playlist, direction);
+ if (!next && mpctx->opts->loop_times >= 0) {
+ if (direction > 0) {
+ next = mpctx->playlist->first;
+ if (next && mpctx->opts->loop_times > 0) {
+ mpctx->opts->loop_times--;
+ if (mpctx->opts->loop_times == 0)
+ mpctx->opts->loop_times = -1;
+ }
+ } else {
+ next = mpctx->playlist->last;
+ }
+ }
+ return next;
+}
+
+// Play all entries on the playlist, starting from the current entry.
+// Return if all done.
+static void play_files(struct MPContext *mpctx)
+{
+ mpctx->quit_player_rc = EXIT_NONE;
+ for (;;) {
+ idle_loop(mpctx);
+ if (mpctx->stop_play == PT_QUIT)
+ break;
+
+ mpctx->error_playing = true;
+ play_current_file(mpctx);
+ if (mpctx->error_playing) {
+ if (!mpctx->quit_player_rc) {
+ mpctx->quit_player_rc = EXIT_NOTPLAYED;
+ } else if (mpctx->quit_player_rc == EXIT_PLAYED) {
+ mpctx->quit_player_rc = EXIT_SOMENOTPLAYED;
+ }
+ } else if (mpctx->quit_player_rc == EXIT_NOTPLAYED) {
+ mpctx->quit_player_rc = EXIT_SOMENOTPLAYED;
+ } else {
+ mpctx->quit_player_rc = EXIT_PLAYED;
+ }
+ if (mpctx->stop_play == PT_QUIT)
+ break;
+
+ if (!mpctx->stop_play || mpctx->stop_play == AT_END_OF_FILE)
+ mpctx->stop_play = PT_NEXT_ENTRY;
+
+ struct playlist_entry *new_entry = NULL;
+
+ if (mpctx->stop_play == PT_NEXT_ENTRY) {
+ new_entry = mp_next_file(mpctx, +1);
+ } else if (mpctx->stop_play == PT_CURRENT_ENTRY) {
+ new_entry = mpctx->playlist->current;
+ } else if (mpctx->stop_play == PT_RESTART) {
+ // The same as PT_CURRENT_ENTRY, unless we decide that the current
+ // playlist entry can be removed during playback.
+ new_entry = mpctx->playlist->current;
+ } else { // PT_STOP
+ playlist_clear(mpctx->playlist);
+ }
+
+ mpctx->playlist->current = new_entry;
+ mpctx->playlist->current_was_replaced = false;
+ mpctx->stop_play = 0;
+
+ if (!mpctx->playlist->current && !mpctx->opts->player_idle_mode)
+ break;
+ }
+}
+
+// Abort current playback and set the given entry to play next.
+// e must be on the mpctx->playlist.
+void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e)
+{
+ assert(playlist_entry_to_index(mpctx->playlist, e) >= 0);
+ mpctx->playlist->current = e;
+ mpctx->playlist->current_was_replaced = false;
+ mpctx->stop_play = PT_CURRENT_ENTRY;
+}
+
+void mp_print_version(int always)
+{
+ mp_msg(MSGT_CPLAYER, always ? MSGL_INFO : MSGL_V,
+ "%s (C) 2000-2013 mpv/MPlayer/mplayer2 projects\n built on %s\n", mplayer_version, mplayer_builddate);
+}
+
+static bool handle_help_options(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ int opt_exit = 0;
+ if (opts->audio_decoders && strcmp(opts->audio_decoders, "help") == 0) {
+ struct mp_decoder_list *list = mp_audio_decoder_list();
+ mp_print_decoders(MSGT_CPLAYER, MSGL_INFO, "Audio decoders:", list);
+ talloc_free(list);
+ opt_exit = 1;
+ }
+ if (opts->video_decoders && strcmp(opts->video_decoders, "help") == 0) {
+ struct mp_decoder_list *list = mp_video_decoder_list();
+ mp_print_decoders(MSGT_CPLAYER, MSGL_INFO, "Video decoders:", list);
+ talloc_free(list);
+ opt_exit = 1;
+ }
+#ifdef CONFIG_X11
+ if (opts->vo.fstype_list && strcmp(opts->vo.fstype_list[0], "help") == 0) {
+ fstype_help();
+ mp_msg(MSGT_FIXME, MSGL_FIXME, "\n");
+ opt_exit = 1;
+ }
+#endif
+ if ((opts->demuxer_name && strcmp(opts->demuxer_name, "help") == 0) ||
+ (opts->audio_demuxer_name && strcmp(opts->audio_demuxer_name, "help") == 0) ||
+ (opts->sub_demuxer_name && strcmp(opts->sub_demuxer_name, "help") == 0)) {
+ demuxer_help();
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "\n");
+ opt_exit = 1;
+ }
+ if (opts->list_properties) {
+ property_print_help();
+ opt_exit = 1;
+ }
+#ifdef CONFIG_ENCODING
+ if (encode_lavc_showhelp(mpctx->opts))
+ opt_exit = 1;
+#endif
+ return opt_exit;
+}
+
+#ifdef PTW32_STATIC_LIB
+static void detach_ptw32(void)
+{
+ pthread_win32_thread_detach_np();
+ pthread_win32_process_detach_np();
+}
+#endif
+
+static void osdep_preinit(int *p_argc, char ***p_argv)
+{
+ char *enable_talloc = getenv("MPV_LEAK_REPORT");
+ if (*p_argc > 1 && (strcmp((*p_argv)[1], "-leak-report") == 0 ||
+ strcmp((*p_argv)[1], "--leak-report") == 0))
+ enable_talloc = "1";
+ if (enable_talloc && strcmp(enable_talloc, "1") == 0)
+ talloc_enable_leak_report();
+
+#ifdef __MINGW32__
+ mp_get_converted_argv(p_argc, p_argv);
+#endif
+
+#ifdef PTW32_STATIC_LIB
+ pthread_win32_process_attach_np();
+ pthread_win32_thread_attach_np();
+ atexit(detach_ptw32);
+#endif
+
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ // stop Windows from showing all kinds of annoying error dialogs
+ SetErrorMode(0x8003);
+#endif
+
+ load_termcap(NULL); // load key-codes
+
+ mp_time_init();
+}
+
+/* This preprocessor directive is a hack to generate a mplayer-nomain.o object
+ * file for some tools to link against. */
+#ifndef DISABLE_MAIN
+static int mpv_main(int argc, char *argv[])
+{
+ osdep_preinit(&argc, &argv);
+
+ if (argc >= 1) {
+ argc--;
+ argv++;
+ }
+
+ struct MPContext *mpctx = talloc(NULL, MPContext);
+ *mpctx = (struct MPContext){
+ .last_dvb_step = 1,
+ .terminal_osd_text = talloc_strdup(mpctx, ""),
+ .playlist = talloc_struct(mpctx, struct playlist, {0}),
+ };
+
+ // Create the config context and register the options
+ mpctx->mconfig = m_config_new(mpctx, sizeof(struct MPOpts),
+ &mp_default_opts, mp_opts, NULL);
+ mpctx->opts = mpctx->mconfig->optstruct;
+ mpctx->mconfig->includefunc = cfg_include;
+ mpctx->mconfig->use_profiles = true;
+
+ struct MPOpts *opts = mpctx->opts;
+
+
+ mpctx->global = talloc_zero(mpctx, struct mpv_global);
+ mpctx->global->opts = opts;
+
+ // Nothing must call mp_msg() before this
+ mp_msg_init(mpctx->global);
+ mpctx->log = mp_log_new(mpctx, mpctx->global->log, "!mpv");
+
+ init_libav();
+ GetCpuCaps(&gCpuCaps);
+ screenshot_init(mpctx);
+
+ // Preparse the command line
+ m_config_preparse_command_line(mpctx->mconfig, argc, argv);
+
+ mp_print_version(false);
+ print_libav_versions();
+
+ if (!parse_cfgfiles(mpctx, mpctx->mconfig))
+ exit_player(mpctx, EXIT_ERROR);
+
+ int r = m_config_parse_mp_command_line(mpctx->mconfig, mpctx->playlist,
+ argc, argv);
+ if (r < 0) {
+ if (r <= M_OPT_EXIT) {
+ exit_player(mpctx, EXIT_NONE);
+ } else {
+ exit_player(mpctx, EXIT_ERROR);
+ }
+ }
+
+ if (handle_help_options(mpctx))
+ exit_player(mpctx, EXIT_NONE);
+
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Configuration: " CONFIGURATION "\n");
+ mp_tmsg(MSGT_CPLAYER, MSGL_V, "Command line:");
+ for (int i = 0; i < argc; i++)
+ mp_msg(MSGT_CPLAYER, MSGL_V, " '%s'", argv[i]);
+ mp_msg(MSGT_CPLAYER, MSGL_V, "\n");
+
+ if (!mpctx->playlist->first && !opts->player_idle_mode) {
+ mp_print_version(true);
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "%s", mp_gtext(mp_help_text));
+ exit_player(mpctx, EXIT_NONE);
+ }
+
+#ifdef CONFIG_PRIORITY
+ set_priority();
+#endif
+
+ init_input(mpctx);
+
+#ifdef CONFIG_ENCODING
+ if (opts->encode_output.file && *opts->encode_output.file) {
+ mpctx->encode_lavc_ctx = encode_lavc_init(&opts->encode_output);
+ if(!mpctx->encode_lavc_ctx) {
+ mp_msg(MSGT_VO, MSGL_INFO, "Encoding initialization failed.");
+ exit_player(mpctx, EXIT_ERROR);
+ }
+ 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, "gapless-audio", "yes");
+ mp_input_enable_section(mpctx->input, "encode", MP_INPUT_EXCLUSIVE);
+ }
+#endif
+
+#ifdef CONFIG_ASS
+ mpctx->ass_library = mp_ass_init(opts);
+#endif
+
+ mpctx->osd = osd_create(opts, mpctx->ass_library);
+
+ mpctx->playlist->current = mpctx->playlist->first;
+
+ play_files(mpctx);
+
+ exit_player(mpctx, mpctx->stop_play == PT_QUIT ? EXIT_QUIT : mpctx->quit_player_rc);
+
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+#ifdef CONFIG_COCOA
+ return cocoa_main(mpv_main, argc, argv);
+#else
+ return mpv_main(argc, argv);
+#endif
+}
+
+#endif /* DISABLE_MAIN */
diff --git a/mpvcore/mpv_global.h b/mpvcore/mpv_global.h
new file mode 100644
index 0000000000..546c585294
--- /dev/null
+++ b/mpvcore/mpv_global.h
@@ -0,0 +1,12 @@
+#ifndef MPV_MPV_H
+#define MPV_MPV_H
+
+// This should be accessed by glue code only, never normal code.
+// The only purpose of this is to make mpv library-safe.
+// Think hard before adding new members.
+struct mpv_global {
+ struct MPOpts *opts;
+ struct mp_log *log;
+};
+
+#endif
diff --git a/mpvcore/options.c b/mpvcore/options.c
new file mode 100644
index 0000000000..dcab2dc564
--- /dev/null
+++ b/mpvcore/options.c
@@ -0,0 +1,838 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_CFG_MPLAYER_H
+#define MPLAYER_CFG_MPLAYER_H
+
+/*
+ * config for cfgparser
+ */
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <limits.h>
+
+#include "core/options.h"
+#include "config.h"
+#include "core/m_config.h"
+#include "core/m_option.h"
+#include "stream/tv.h"
+#include "stream/stream_radio.h"
+#include "video/csputils.h"
+#include "sub/sub.h"
+#include "audio/mixer.h"
+#include "audio/filter/af.h"
+#include "audio/decode/dec_audio.h"
+#include "mp_core.h"
+#include "osdep/priority.h"
+
+int network_bandwidth=0;
+int network_cookies_enabled = 0;
+char *network_useragent="MPlayer 1.1-4.7";
+char *network_referrer=NULL;
+char **network_http_header_fields=NULL;
+
+extern char *lirc_configfile;
+
+extern int mp_msg_color;
+extern int mp_msg_module;
+
+extern int dvd_speed; /* stream/stream_dvd.c */
+
+/* defined in demux: */
+extern const m_option_t demux_rawaudio_opts[];
+extern const m_option_t demux_rawvideo_opts[];
+extern const m_option_t cdda_opts[];
+
+extern int sws_flags;
+extern const char pp_help[];
+
+extern const char mp_help_text[];
+
+static int print_version_opt(const m_option_t *opt, const char *name,
+ const char *param)
+{
+ mp_print_version(true);
+ exit(0);
+}
+
+#ifdef CONFIG_RADIO
+static const m_option_t radioopts_conf[]={
+ {"device", &stream_radio_defaults.device, CONF_TYPE_STRING, 0, 0 ,0, NULL},
+ {"driver", &stream_radio_defaults.driver, CONF_TYPE_STRING, 0, 0 ,0, NULL},
+ {"channels", &stream_radio_defaults.channels, CONF_TYPE_STRING_LIST, 0, 0 ,0, NULL},
+ {"volume", &stream_radio_defaults.volume, CONF_TYPE_INT, CONF_RANGE, 0 ,100, NULL},
+ {"adevice", &stream_radio_defaults.adevice, CONF_TYPE_STRING, 0, 0 ,0, NULL},
+ {"arate", &stream_radio_defaults.arate, CONF_TYPE_INT, CONF_MIN, 0 ,0, NULL},
+ {"achannels", &stream_radio_defaults.achannels, CONF_TYPE_INT, CONF_MIN, 0 ,0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+#endif /* CONFIG_RADIO */
+
+#ifdef CONFIG_TV
+static const m_option_t tvopts_conf[]={
+ {"immediatemode", &stream_tv_defaults.immediate, CONF_TYPE_INT, CONF_RANGE, 0, 1, NULL},
+ {"audio", &stream_tv_defaults.noaudio, CONF_TYPE_FLAG, 0, 1, 0, NULL},
+ {"audiorate", &stream_tv_defaults.audiorate, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"driver", &stream_tv_defaults.driver, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"device", &stream_tv_defaults.device, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"freq", &stream_tv_defaults.freq, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"channel", &stream_tv_defaults.channel, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"chanlist", &stream_tv_defaults.chanlist, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"norm", &stream_tv_defaults.norm, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"automute", &stream_tv_defaults.automute, CONF_TYPE_INT, CONF_RANGE, 0, 255, NULL},
+#if defined(CONFIG_TV_V4L2)
+ {"normid", &stream_tv_defaults.normid, CONF_TYPE_INT, 0, 0, 0, NULL},
+#endif
+ {"width", &stream_tv_defaults.width, CONF_TYPE_INT, 0, 0, 4096, NULL},
+ {"height", &stream_tv_defaults.height, CONF_TYPE_INT, 0, 0, 4096, NULL},
+ {"input", &stream_tv_defaults.input, CONF_TYPE_INT, 0, 0, 20, NULL},
+ {"outfmt", &stream_tv_defaults.outfmt, CONF_TYPE_FOURCC, 0, 0, 0, NULL},
+ {"fps", &stream_tv_defaults.fps, CONF_TYPE_FLOAT, 0, 0, 100.0, NULL},
+ {"channels", &stream_tv_defaults.channels, CONF_TYPE_STRING_LIST, 0, 0, 0, NULL},
+ {"brightness", &stream_tv_defaults.brightness, CONF_TYPE_INT, CONF_RANGE, -100, 100, NULL},
+ {"contrast", &stream_tv_defaults.contrast, CONF_TYPE_INT, CONF_RANGE, -100, 100, NULL},
+ {"hue", &stream_tv_defaults.hue, CONF_TYPE_INT, CONF_RANGE, -100, 100, NULL},
+ {"saturation", &stream_tv_defaults.saturation, CONF_TYPE_INT, CONF_RANGE, -100, 100, NULL},
+ {"gain", &stream_tv_defaults.gain, CONF_TYPE_INT, CONF_RANGE, -1, 100, NULL},
+#if defined(CONFIG_TV_V4L2)
+ {"amode", &stream_tv_defaults.amode, CONF_TYPE_INT, CONF_RANGE, 0, 3, NULL},
+ {"volume", &stream_tv_defaults.volume, CONF_TYPE_INT, CONF_RANGE, 0, 65535, NULL},
+ {"bass", &stream_tv_defaults.bass, CONF_TYPE_INT, CONF_RANGE, 0, 65535, NULL},
+ {"treble", &stream_tv_defaults.treble, CONF_TYPE_INT, CONF_RANGE, 0, 65535, NULL},
+ {"balance", &stream_tv_defaults.balance, CONF_TYPE_INT, CONF_RANGE, 0, 65535, NULL},
+ {"forcechan", &stream_tv_defaults.forcechan, CONF_TYPE_INT, CONF_RANGE, 1, 2, NULL},
+ {"forceaudio", &stream_tv_defaults.force_audio, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+ {"buffersize", &stream_tv_defaults.buffer_size, CONF_TYPE_INT, CONF_RANGE, 16, 1024, NULL},
+ {"mjpeg", &stream_tv_defaults.mjpeg, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+ {"decimation", &stream_tv_defaults.decimation, CONF_TYPE_INT, CONF_RANGE, 1, 4, NULL},
+ {"quality", &stream_tv_defaults.quality, CONF_TYPE_INT, CONF_RANGE, 0, 100, NULL},
+#ifdef CONFIG_ALSA
+ {"alsa", &stream_tv_defaults.alsa, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+#endif /* CONFIG_ALSA */
+#endif /* defined(CONFIG_TV_V4L2) */
+ {"adevice", &stream_tv_defaults.adevice, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"audioid", &stream_tv_defaults.audio_id, CONF_TYPE_INT, CONF_RANGE, 0, 9, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+#endif /* CONFIG_TV */
+
+extern int pvr_param_aspect_ratio;
+extern int pvr_param_sample_rate;
+extern int pvr_param_audio_layer;
+extern int pvr_param_audio_bitrate;
+extern char *pvr_param_audio_mode;
+extern int pvr_param_bitrate;
+extern char *pvr_param_bitrate_mode;
+extern int pvr_param_bitrate_peak;
+extern char *pvr_param_stream_type;
+
+#ifdef CONFIG_PVR
+static const m_option_t pvropts_conf[]={
+ {"aspect", &pvr_param_aspect_ratio, CONF_TYPE_INT, 0, 1, 4, NULL},
+ {"arate", &pvr_param_sample_rate, CONF_TYPE_INT, 0, 32000, 48000, NULL},
+ {"alayer", &pvr_param_audio_layer, CONF_TYPE_INT, 0, 1, 2, NULL},
+ {"abitrate", &pvr_param_audio_bitrate, CONF_TYPE_INT, 0, 32, 448, NULL},
+ {"amode", &pvr_param_audio_mode, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"vbitrate", &pvr_param_bitrate, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"vmode", &pvr_param_bitrate_mode, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"vpeak", &pvr_param_bitrate_peak, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"fmt", &pvr_param_stream_type, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+#endif /* CONFIG_PVR */
+
+extern const m_option_t dvbin_opts_conf[];
+extern const m_option_t lavfdopts_conf[];
+
+extern int sws_chr_vshift;
+extern int sws_chr_hshift;
+extern float sws_chr_gblur;
+extern float sws_lum_gblur;
+extern float sws_chr_sharpen;
+extern float sws_lum_sharpen;
+
+static const m_option_t scaler_filter_conf[]={
+ {"lgb", &sws_lum_gblur, CONF_TYPE_FLOAT, 0, 0, 100.0, NULL},
+ {"cgb", &sws_chr_gblur, CONF_TYPE_FLOAT, 0, 0, 100.0, NULL},
+ {"cvs", &sws_chr_vshift, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"chs", &sws_chr_hshift, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"ls", &sws_lum_sharpen, CONF_TYPE_FLOAT, 0, -100.0, 100.0, NULL},
+ {"cs", &sws_chr_sharpen, CONF_TYPE_FLOAT, 0, -100.0, 100.0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+
+extern char *dvd_device, *cdrom_device;
+
+extern double mf_fps;
+extern char * mf_type;
+extern const struct m_obj_list vf_obj_list;
+extern const struct m_obj_list af_obj_list;
+extern const struct m_obj_list vo_obj_list;
+extern const struct m_obj_list ao_obj_list;
+
+static const m_option_t mfopts_conf[]={
+ {"fps", &mf_fps, CONF_TYPE_DOUBLE, 0, 0, 0, NULL},
+ {"type", &mf_type, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+
+extern int mp_msg_levels[MSGT_MAX];
+extern int mp_msg_level_all;
+
+static const m_option_t msgl_config[]={
+ { "all", &mp_msg_level_all, CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL},
+
+ { "global", &mp_msg_levels[MSGT_GLOBAL], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "cplayer", &mp_msg_levels[MSGT_CPLAYER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "vo", &mp_msg_levels[MSGT_VO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "ao", &mp_msg_levels[MSGT_AO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "demuxer", &mp_msg_levels[MSGT_DEMUXER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "ds", &mp_msg_levels[MSGT_DS], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "demux", &mp_msg_levels[MSGT_DEMUX], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "header", &mp_msg_levels[MSGT_HEADER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "avsync", &mp_msg_levels[MSGT_AVSYNC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "autoq", &mp_msg_levels[MSGT_AUTOQ], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "cfgparser", &mp_msg_levels[MSGT_CFGPARSER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "decaudio", &mp_msg_levels[MSGT_DECAUDIO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "decvideo", &mp_msg_levels[MSGT_DECVIDEO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "seek", &mp_msg_levels[MSGT_SEEK], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "win32", &mp_msg_levels[MSGT_WIN32], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "open", &mp_msg_levels[MSGT_OPEN], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "dvd", &mp_msg_levels[MSGT_DVD], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "parsees", &mp_msg_levels[MSGT_PARSEES], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "lirc", &mp_msg_levels[MSGT_LIRC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "stream", &mp_msg_levels[MSGT_STREAM], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "cache", &mp_msg_levels[MSGT_CACHE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "encode", &mp_msg_levels[MSGT_ENCODE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "xacodec", &mp_msg_levels[MSGT_XACODEC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "tv", &mp_msg_levels[MSGT_TV], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "radio", &mp_msg_levels[MSGT_RADIO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "osdep", &mp_msg_levels[MSGT_OSDEP], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "spudec", &mp_msg_levels[MSGT_SPUDEC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "playtree", &mp_msg_levels[MSGT_PLAYTREE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "input", &mp_msg_levels[MSGT_INPUT], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "vfilter", &mp_msg_levels[MSGT_VFILTER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "osd", &mp_msg_levels[MSGT_OSD], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "network", &mp_msg_levels[MSGT_NETWORK], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "cpudetect", &mp_msg_levels[MSGT_CPUDETECT], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "codeccfg", &mp_msg_levels[MSGT_CODECCFG], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "sws", &mp_msg_levels[MSGT_SWS], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "vobsub", &mp_msg_levels[MSGT_VOBSUB], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "subreader", &mp_msg_levels[MSGT_SUBREADER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "afilter", &mp_msg_levels[MSGT_AFILTER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "netst", &mp_msg_levels[MSGT_NETST], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "muxer", &mp_msg_levels[MSGT_MUXER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "identify", &mp_msg_levels[MSGT_IDENTIFY], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "ass", &mp_msg_levels[MSGT_ASS], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "statusline", &mp_msg_levels[MSGT_STATUSLINE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "fixme", &mp_msg_levels[MSGT_FIXME], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ {"help", "Available msg modules:\n"
+ " global - common player errors/information\n"
+ " cplayer - console player (mplayer.c)\n"
+ " vo - libvo\n"
+ " ao - libao\n"
+ " demuxer - demuxer.c (general stuff)\n"
+ " ds - demux stream (add/read packet etc)\n"
+ " demux - fileformat-specific stuff (demux_*.c)\n"
+ " header - fileformat-specific header (*header.c)\n"
+ " avsync - mplayer.c timer stuff\n"
+ " autoq - mplayer.c auto-quality stuff\n"
+ " cfgparser - cfgparser.c\n"
+ " decaudio - av decoder\n"
+ " decvideo\n"
+ " seek - seeking code\n"
+ " win32 - win32 dll stuff\n"
+ " open - open.c (stream opening)\n"
+ " dvd - open.c (DVD init/read/seek)\n"
+ " parsees - parse_es.c (mpeg stream parser)\n"
+ " lirc - lirc_mp.c and input lirc driver\n"
+ " stream - stream.c\n"
+ " cache - cache2.c\n"
+ " encode - encode_lavc.c and associated vo/ao drivers\n"
+ " xacodec - XAnim codecs\n"
+ " tv - TV input subsystem\n"
+ " osdep - OS-dependent parts\n"
+ " spudec - spudec.c\n"
+ " playtree - Playtree handling (playtree.c, playtreeparser.c)\n"
+ " input\n"
+ " vfilter\n"
+ " osd\n"
+ " network\n"
+ " cpudetect\n"
+ " codeccfg\n"
+ " sws\n"
+ " vobsub\n"
+ " subreader\n"
+ " afilter - Audio filter messages\n"
+ " netst - Netstream\n"
+ " muxer - muxer layer\n"
+ " identify - identify output\n"
+ " ass - libass messages\n"
+ " statusline - playback/encoding status line\n"
+ " fixme - messages not yet fixed to map to module\n"
+ "\n", CONF_TYPE_PRINT, CONF_GLOBAL | CONF_NOCFG, 0, 0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+
+};
+
+#ifdef CONFIG_TV
+static const m_option_t tvscan_conf[]={
+ {"autostart", &stream_tv_defaults.scan, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+ {"threshold", &stream_tv_defaults.scan_threshold, CONF_TYPE_INT, CONF_RANGE, 1, 100, NULL},
+ {"period", &stream_tv_defaults.scan_period, CONF_TYPE_FLOAT, CONF_RANGE, 0.1, 2.0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+#endif
+
+#define OPT_BASE_STRUCT struct MPOpts
+
+extern const struct m_sub_options image_writer_conf;
+
+static const m_option_t screenshot_conf[] = {
+ OPT_SUBSTRUCT("", screenshot_image_opts, image_writer_conf, 0),
+ OPT_STRING("template", screenshot_template, 0),
+ {0},
+};
+
+extern const m_option_t lavc_decode_opts_conf[];
+extern const m_option_t ad_lavc_decode_opts_conf[];
+
+extern const m_option_t mp_input_opts[];
+
+const m_option_t mp_opts[] = {
+ // handled in command line pre-parser (parser-mpcmd.c)
+ {"v", NULL, CONF_TYPE_STORE, CONF_GLOBAL | CONF_NOCFG, 0, 0, NULL},
+
+ // handled in command line parser (parser-mpcmd.c)
+ {"playlist", NULL, CONF_TYPE_STRING, CONF_NOCFG | M_OPT_MIN, 1, 0, NULL},
+ {"shuffle", NULL, CONF_TYPE_FLAG, CONF_NOCFG, 0, 0, NULL},
+ {"{", NULL, CONF_TYPE_STORE, CONF_NOCFG, 0, 0, NULL},
+ {"}", NULL, CONF_TYPE_STORE, CONF_NOCFG, 0, 0, NULL},
+
+ // handled in m_config.c
+ { "include", NULL, CONF_TYPE_STRING },
+ { "profile", NULL, CONF_TYPE_STRING_LIST },
+ { "show-profile", NULL, CONF_TYPE_STRING, CONF_NOCFG },
+ { "list-options", NULL, CONF_TYPE_STORE, CONF_NOCFG },
+
+ // handled in mplayer.c (looks at the raw argv[])
+ {"leak-report", "", CONF_TYPE_STORE, CONF_GLOBAL | CONF_NOCFG },
+
+// ------------------------- common options --------------------
+ OPT_FLAG("quiet", quiet, CONF_GLOBAL),
+ {"really-quiet", &verbose, CONF_TYPE_STORE, CONF_GLOBAL|CONF_PRE_PARSE, 0, -10, NULL},
+ {"msglevel", (void *) msgl_config, CONF_TYPE_SUBCONFIG, CONF_GLOBAL, 0, 0, NULL},
+ {"msgcolor", &mp_msg_color, CONF_TYPE_FLAG, CONF_GLOBAL | CONF_PRE_PARSE, 0, 1, NULL},
+ {"msgmodule", &mp_msg_module, CONF_TYPE_FLAG, CONF_GLOBAL, 0, 1, NULL},
+#ifdef CONFIG_PRIORITY
+ {"priority", &proc_priority, CONF_TYPE_STRING, 0, 0, 0, NULL},
+#endif
+ OPT_FLAG("config", load_config, CONF_GLOBAL | CONF_NOCFG | CONF_PRE_PARSE),
+ OPT_STRINGLIST("reset-on-next-file", reset_options, CONF_GLOBAL),
+
+// ------------------------- stream options --------------------
+
+#ifdef CONFIG_STREAM_CACHE
+ OPT_CHOICE_OR_INT("cache", stream_cache_size, 0, 32, 0x7fffffff,
+ ({"no", 0},
+ {"auto", -1}),
+ OPTDEF_INT(-1)),
+ OPT_CHOICE_OR_INT("cache-default", stream_cache_def_size, 0, 32, 0x7fffffff,
+ ({"no", 0}),
+ OPTDEF_INT(320)),
+ OPT_FLOATRANGE("cache-min", stream_cache_min_percent, 0, 0, 99),
+ OPT_FLOATRANGE("cache-seek-min", stream_cache_seek_min_percent, 0, 0, 99),
+ OPT_CHOICE_OR_INT("cache-pause", stream_cache_pause, 0,
+ 0, 40, ({"no", -1})),
+#endif /* CONFIG_STREAM_CACHE */
+ {"cdrom-device", &cdrom_device, CONF_TYPE_STRING, 0, 0, 0, NULL},
+#ifdef CONFIG_DVDREAD
+ {"dvd-device", &dvd_device, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"dvd-speed", &dvd_speed, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"dvdangle", &dvd_angle, CONF_TYPE_INT, CONF_RANGE, 1, 99, NULL},
+#endif /* CONFIG_DVDREAD */
+ OPT_INTPAIR("chapter", chapterrange, 0),
+ OPT_INTRANGE("edition", edition_id, 0, -1, 8190),
+#ifdef CONFIG_LIBBLURAY
+ {"bluray-device", &bluray_device, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"bluray-angle", &bluray_angle, CONF_TYPE_INT, CONF_RANGE, 0, 999, NULL},
+#endif /* CONFIG_LIBBLURAY */
+
+ {"http-header-fields", &network_http_header_fields, CONF_TYPE_STRING_LIST, 0, 0, 0, NULL},
+ {"user-agent", &network_useragent, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"referrer", &network_referrer, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"cookies", &network_cookies_enabled, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+ {"cookies-file", &cookies_file, CONF_TYPE_STRING, 0, 0, 0, NULL},
+
+// ------------------------- demuxer options --------------------
+
+ OPT_CHOICE_OR_INT("frames", play_frames, 0, 0, INT_MAX,
+ ({"all", -1})),
+
+ // seek to byte/seconds position
+ OPT_INT64("sb", seek_to_byte, 0),
+ OPT_REL_TIME("start", play_start, 0),
+ OPT_REL_TIME("end", play_end, 0),
+ OPT_REL_TIME("length", play_length, 0),
+
+ OPT_FLAG("pause", pause, 0),
+ OPT_FLAG("keep-open", keep_open, 0),
+
+ // AVI and Ogg only: (re)build index at startup
+ OPT_FLAG_CONSTANTS("idx", index_mode, 0, -1, 1),
+ OPT_FLAG_STORE("forceidx", index_mode, 0, 2),
+
+ // select audio/video/subtitle stream
+ OPT_TRACKCHOICE("aid", audio_id),
+ OPT_TRACKCHOICE("vid", video_id),
+ OPT_TRACKCHOICE("sid", sub_id),
+ OPT_FLAG_STORE("no-sub", sub_id, 0, -2),
+ OPT_FLAG_STORE("no-video", video_id, 0, -2),
+ OPT_FLAG_STORE("no-audio", audio_id, 0, -2),
+ OPT_STRINGLIST("alang", audio_lang, 0),
+ OPT_STRINGLIST("slang", sub_lang, 0),
+
+ OPT_CHOICE("audio-display", audio_display, 0,
+ ({"no", 0}, {"attachment", 1})),
+
+ OPT_STRING("quvi-format", quvi_format, 0),
+
+#ifdef CONFIG_CDDA
+ { "cdda", (void *)&cdda_opts, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif
+
+ // demuxer.c - select audio/sub file/demuxer
+ OPT_STRING("audiofile", audio_stream, 0),
+ OPT_INTRANGE("audiofile-cache", audio_stream_cache, 0, 50, 65536),
+ OPT_STRING("demuxer", demuxer_name, 0),
+ OPT_STRING("audio-demuxer", audio_demuxer_name, 0),
+ OPT_STRING("sub-demuxer", sub_demuxer_name, 0),
+
+ {"mf", (void *) mfopts_conf, CONF_TYPE_SUBCONFIG, 0,0,0, NULL},
+#ifdef CONFIG_RADIO
+ {"radio", (void *) radioopts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif /* CONFIG_RADIO */
+#ifdef CONFIG_TV
+ {"tv", (void *) tvopts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif /* CONFIG_TV */
+#ifdef CONFIG_PVR
+ {"pvr", (void *) pvropts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif /* CONFIG_PVR */
+#ifdef CONFIG_DVBIN
+ {"dvbin", (void *) dvbin_opts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif
+
+// ------------------------- a-v sync options --------------------
+
+ // set A-V sync correction speed (0=disables it):
+ OPT_FLOATRANGE("mc", default_max_pts_correction, 0, 0, 100),
+
+ // force video/audio rate:
+ OPT_DOUBLE("fps", force_fps, CONF_MIN, 0),
+ OPT_INTRANGE("srate", force_srate, 0, 1000, 8*48000),
+ OPT_CHMAP("channels", audio_output_channels, CONF_MIN, .min = 1),
+ OPT_AUDIOFORMAT("format", audio_output_format, 0),
+ OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE, .min = 0.01, .max = 100.0),
+
+ // set a-v distance
+ OPT_FLOATRANGE("audio-delay", audio_delay, 0, -100.0, 100.0),
+
+// ------------------------- codec/vfilter options --------------------
+
+ OPT_SETTINGSLIST("af*", af_settings, 0, &af_obj_list),
+ OPT_SETTINGSLIST("vf*", vf_settings, 0, &vf_obj_list),
+
+ OPT_STRING("ad", audio_decoders, 0),
+ OPT_STRING("vd", video_decoders, 0),
+
+ OPT_FLAG("ad-spdif-dtshd", dtshd, 0),
+ OPT_FLAG("dtshd", dtshd, 0), // old alias
+
+ OPT_CHOICE("hwdec", hwdec_api, 0,
+ ({"no", 0},
+ {"vdpau", 1},
+ {"vda", 2},
+ {"crystalhd", 3})),
+ OPT_STRING("hwdec-codecs", hwdec_codecs, 0),
+
+ // postprocessing:
+ OPT_INT("pp", divx_quality, 0),
+#ifdef CONFIG_LIBPOSTPROC
+ {"pphelp", (void *) &pp_help, CONF_TYPE_PRINT, CONF_GLOBAL | CONF_NOCFG, 0, 0, NULL},
+#endif
+
+ // scaling:
+ {"sws", &sws_flags, CONF_TYPE_INT, 0, 0, 2, NULL},
+ {"ssf", (void *) scaler_filter_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+ OPT_FLOATRANGE("aspect", movie_aspect, 0, 0.1, 10.0),
+ OPT_FLOAT_STORE("no-aspect", movie_aspect, 0, 0),
+
+ OPT_FLAG_CONSTANTS("flip", flip, 0, 0, 1),
+
+ OPT_CHOICE("field-dominance", field_dominance, 0,
+ ({"auto", -1}, {"top", 0}, {"bottom", 1})),
+
+ {"vd-lavc", (void *) lavc_decode_opts_conf, CONF_TYPE_SUBCONFIG},
+ {"ad-lavc", (void *) ad_lavc_decode_opts_conf, CONF_TYPE_SUBCONFIG},
+
+ {"demuxer-lavf", (void *) lavfdopts_conf, CONF_TYPE_SUBCONFIG},
+ {"demuxer-rawaudio", (void *)&demux_rawaudio_opts, CONF_TYPE_SUBCONFIG},
+ {"demuxer-rawvideo", (void *)&demux_rawvideo_opts, CONF_TYPE_SUBCONFIG},
+
+ OPT_FLAG("demuxer-mkv-subtitle-preroll", mkv_subtitle_preroll, 0),
+ OPT_FLAG("mkv-subtitle-preroll", mkv_subtitle_preroll, 0), // old alias
+
+// ------------------------- subtitles options --------------------
+
+ OPT_STRINGLIST("sub", sub_name, 0),
+ OPT_PATHLIST("sub-paths", sub_paths, 0),
+ OPT_STRING("subcp", sub_cp, 0),
+ OPT_FLOAT("sub-delay", sub_delay, 0),
+ OPT_FLOAT("subfps", sub_fps, 0),
+ OPT_FLOAT("sub-speed", sub_speed, 0),
+ OPT_FLAG("autosub", sub_auto, 0),
+ OPT_FLAG("sub-visibility", sub_visibility, 0),
+ OPT_FLAG("sub-forced-only", forced_subs_only, 0),
+ OPT_FLAG_CONSTANTS("sub-fix-timing", suboverlap_enabled, 0, 1, 0),
+ OPT_CHOICE("autosub-match", sub_match_fuzziness, 0,
+ ({"exact", 0}, {"fuzzy", 1}, {"all", 2})),
+ OPT_INTRANGE("sub-pos", sub_pos, 0, 0, 100),
+ OPT_FLOATRANGE("sub-gauss", sub_gauss, 0, 0.0, 3.0),
+ OPT_FLAG("sub-gray", sub_gray, 0),
+ OPT_FLAG("ass", ass_enabled, 0),
+ OPT_FLOATRANGE("sub-scale", sub_scale, 0, 0, 100),
+ OPT_FLOATRANGE("ass-line-spacing", ass_line_spacing, 0, -1000, 1000),
+ OPT_FLAG("ass-use-margins", ass_use_margins, 0),
+ OPT_FLAG("ass-vsfilter-aspect-compat", ass_vsfilter_aspect_compat, 0),
+ OPT_CHOICE("ass-vsfilter-color-compat", ass_vsfilter_color_compat, 0,
+ ({"no", 0}, {"basic", 1}, {"full", 2}, {"force-601", 3})),
+ OPT_FLAG("ass-vsfilter-blur-compat", ass_vsfilter_blur_compat, 0),
+ OPT_FLAG("embeddedfonts", use_embedded_fonts, 0),
+ OPT_STRINGLIST("ass-force-style", ass_force_style_list, 0),
+ OPT_STRING("ass-styles", ass_styles_file, 0),
+ OPT_INTRANGE("ass-hinting", ass_hinting, 0, 0, 7),
+ OPT_CHOICE("ass-style-override", ass_style_override, 0,
+ ({"no", 0}, {"yes", 1})),
+ OPT_FLAG("osd-bar", osd_bar_visible, 0),
+ OPT_FLOATRANGE("osd-bar-align-x", osd_bar_align_x, 0, -1.0, +1.0),
+ OPT_FLOATRANGE("osd-bar-align-y", osd_bar_align_y, 0, -1.0, +1.0),
+ OPT_FLOATRANGE("osd-bar-w", osd_bar_w, 0, 1, 100),
+ OPT_FLOATRANGE("osd-bar-h", osd_bar_h, 0, 0.1, 50),
+ OPT_SUBSTRUCT("osd", osd_style, osd_style_conf, 0),
+ OPT_SUBSTRUCT("sub-text", sub_text_style, osd_style_conf, 0),
+
+//---------------------- libao/libvo options ------------------------
+ OPT_SETTINGSLIST("vo", vo.video_driver_list, 0, &vo_obj_list),
+ OPT_SETTINGSLIST("ao", audio_driver_list, 0, &ao_obj_list),
+ OPT_FLAG("fixed-vo", fixed_vo, CONF_GLOBAL),
+ OPT_FLAG("ontop", vo.ontop, 0),
+ OPT_FLAG("border", vo.border, 0),
+
+ OPT_CHOICE("softvol", softvol, 0,
+ ({"no", SOFTVOL_NO},
+ {"yes", SOFTVOL_YES},
+ {"auto", SOFTVOL_AUTO})),
+ OPT_FLOATRANGE("softvol-max", softvol_max, 0, 10, 10000),
+ OPT_INTRANGE("volstep", volstep, 0, 0, 100),
+ OPT_FLOATRANGE("volume", mixer_init_volume, 0, -1, 10000),
+ OPT_CHOICE("mute", mixer_init_mute, M_OPT_OPTIONAL_PARAM,
+ ({"auto", -1},
+ {"no", 0},
+ {"yes", 1}, {"", 1})),
+ OPT_FLAG("gapless-audio", gapless_audio, 0),
+
+ // set screen dimensions (when not detectable or virtual!=visible)
+ OPT_INTRANGE("screenw", vo.screenwidth, CONF_GLOBAL, 0, 4096),
+ OPT_INTRANGE("screenh", vo.screenheight, CONF_GLOBAL, 0, 4096),
+ OPT_GEOMETRY("geometry", vo.geometry, 0),
+ OPT_SIZE_BOX("autofit", vo.autofit, 0),
+ OPT_SIZE_BOX("autofit-larger", vo.autofit_larger, 0),
+ OPT_FLAG("force-window-position", vo.force_window_position, 0),
+ // vo name (X classname) and window title strings
+ OPT_STRING("name", vo.winname, 0),
+ OPT_STRING("title", wintitle, 0),
+ // set aspect ratio of monitor - useful for 16:9 TV-out
+ OPT_FLOATRANGE("monitoraspect", vo.force_monitor_aspect, 0, 0.0, 9.0),
+ OPT_FLOATRANGE("monitorpixelaspect", vo.monitor_pixel_aspect, 0, 0.2, 9.0),
+ // start in fullscreen mode:
+ OPT_FLAG("fullscreen", vo.fullscreen, 0),
+ OPT_FLAG("fs", vo.fullscreen, 0),
+ // set fullscreen switch method (workaround for buggy WMs)
+ OPT_INTRANGE("fsmode-dontuse", vo.fsmode, 0, 31, 4096),
+ OPT_FLAG("native-keyrepeat", vo.native_keyrepeat, 0),
+ OPT_FLOATRANGE("panscan", vo.panscan, 0, 0.0, 1.0),
+ OPT_FLOATRANGE("panscanrange", vo.panscanrange, 0, -19.0, 99.0),
+ OPT_FLAG("force-rgba-osd-rendering", force_rgba_osd, 0),
+ OPT_CHOICE("colormatrix", requested_colorspace, 0,
+ ({"auto", MP_CSP_AUTO},
+ {"BT.601", MP_CSP_BT_601},
+ {"BT.709", MP_CSP_BT_709},
+ {"SMPTE-240M", MP_CSP_SMPTE_240M},
+ {"YCgCo", MP_CSP_YCGCO})),
+ OPT_CHOICE("colormatrix-input-range", requested_input_range, 0,
+ ({"auto", MP_CSP_LEVELS_AUTO},
+ {"limited", MP_CSP_LEVELS_TV},
+ {"full", MP_CSP_LEVELS_PC})),
+ OPT_CHOICE("colormatrix-output-range", requested_output_range, 0,
+ ({"auto", MP_CSP_LEVELS_AUTO},
+ {"limited", MP_CSP_LEVELS_TV},
+ {"full", MP_CSP_LEVELS_PC})),
+
+ OPT_CHOICE_OR_INT("cursor-autohide", vo.cursor_autohide_delay, 0,
+ 0, 30000, ({"no", -1}, {"always", -2})),
+ OPT_FLAG("stop-screensaver", stop_screensaver, 0),
+
+ OPT_INT64("wid", vo.WinID, CONF_GLOBAL),
+#ifdef CONFIG_X11
+ OPT_STRINGLIST("fstype", vo.fstype_list, 0),
+#endif
+ OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0),
+ OPT_FLOAT("heartbeat-interval", heartbeat_interval, CONF_MIN, 0),
+ OPT_FLAG_CONSTANTS("mouseinput", vo.nomouse_input, 0, 1, 0),
+
+ OPT_CHOICE_OR_INT("screen", vo.screen_id, 0, 0, 32,
+ ({"default", -1})),
+
+ OPT_CHOICE_OR_INT("fs-screen", vo.fsscreen_id, 0, 0, 32,
+ ({"all", -2}, {"current", -1})),
+
+#ifdef CONFIG_COCOA
+ OPT_FLAG("native-fs", vo.native_fs, 0),
+#endif
+
+ OPT_INTRANGE("brightness", gamma_brightness, 0, -100, 100),
+ OPT_INTRANGE("saturation", gamma_saturation, 0, -100, 100),
+ OPT_INTRANGE("contrast", gamma_contrast, 0, -100, 100),
+ OPT_INTRANGE("hue", gamma_hue, 0, -100, 100),
+ OPT_INTRANGE("gamma", gamma_gamma, 0, -100, 100),
+ OPT_FLAG("keepaspect", vo.keepaspect, 0),
+
+//---------------------- mplayer-only options ------------------------
+
+ OPT_FLAG("use-filedir-conf", use_filedir_conf, CONF_GLOBAL),
+ OPT_CHOICE("osd-level", osd_level, 0,
+ ({"0", 0}, {"1", 1}, {"2", 2}, {"3", 3})),
+ OPT_INTRANGE("osd-duration", osd_duration, 0, 0, 3600000),
+ OPT_FLAG("osd-fractions", osd_fractions, 0),
+ OPT_FLOATRANGE("osd-scale", osd_scale, 0, 0, 100),
+
+ OPT_DOUBLE("sstep", step_sec, CONF_MIN, 0),
+
+ OPT_CHOICE("framedrop", frame_dropping, 0,
+ ({"no", 0},
+ {"yes", 1},
+ {"hard", 2})),
+
+ OPT_FLAG("untimed", untimed, 0),
+
+ OPT_STRING("stream-capture", stream_capture, 0),
+ OPT_STRING("stream-dump", stream_dump, 0),
+
+#ifdef CONFIG_LIRC
+ {"lircconf", &lirc_configfile, CONF_TYPE_STRING, CONF_GLOBAL, 0, 0, NULL},
+#endif
+
+ OPT_CHOICE_OR_INT("loop", loop_times, M_OPT_GLOBAL, 1, 10000,
+ ({"no", -1}, {"0", -1},
+ {"inf", 0})),
+
+ OPT_FLAG("resume-playback", position_resume, 0),
+ OPT_FLAG("save-position-on-quit", position_save_on_quit, 0),
+
+ OPT_FLAG("ordered-chapters", ordered_chapters, 0),
+ OPT_INTRANGE("chapter-merge-threshold", chapter_merge_threshold, 0, 0, 10000),
+
+ // a-v sync stuff:
+ OPT_FLAG("correct-pts", correct_pts, 0),
+ OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0,
+ ({"auto", 0}, {"decoder", 1}, {"sort", 2})),
+ OPT_FLAG("initial-audio-sync", initial_audio_sync, 0),
+ OPT_CHOICE("hr-seek", hr_seek, 0,
+ ({"no", -1}, {"absolute", 0}, {"always", 1}, {"yes", 1})),
+ OPT_FLOATRANGE("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0, -9, 99),
+ OPT_CHOICE_OR_INT("autosync", autosync, 0, 0, 10000,
+ ({"no", -1})),
+
+ OPT_FLAG("softsleep", softsleep, 0),
+
+ OPT_CHOICE("term-osd", term_osd, 0,
+ ({"force", 1},
+ {"auto", 2},
+ {"no", 0})),
+
+ OPT_STRING("term-osd-esc", term_osd_esc, M_OPT_PARSE_ESCAPES,
+ OPTDEF_STR("\x1b[A\r\x1b[K")),
+ OPT_STRING("playing-msg", playing_msg, M_OPT_PARSE_ESCAPES),
+ OPT_STRING("status-msg", status_msg, M_OPT_PARSE_ESCAPES),
+ OPT_STRING("osd-status-msg", osd_status_msg, M_OPT_PARSE_ESCAPES),
+
+ OPT_FLAG("slave-broken", slave_mode, CONF_GLOBAL),
+ OPT_FLAG("idle", player_idle_mode, CONF_GLOBAL),
+ OPT_INTRANGE("key-fifo-size", input.key_fifo_size, CONF_GLOBAL, 2, 65000),
+ OPT_FLAG("consolecontrols", consolecontrols, CONF_GLOBAL),
+ OPT_FLAG("mouse-movements", vo.enable_mouse_movements, CONF_GLOBAL),
+#ifdef CONFIG_TV
+ {"tvscan", (void *) tvscan_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif /* CONFIG_TV */
+
+ {"screenshot", (void *) screenshot_conf, CONF_TYPE_SUBCONFIG},
+
+ {"", (void *) mp_input_opts, CONF_TYPE_SUBCONFIG},
+
+ OPT_FLAG("list-properties", list_properties, CONF_GLOBAL),
+ {"identify", &mp_msg_levels[MSGT_IDENTIFY], CONF_TYPE_FLAG, CONF_GLOBAL, 0, MSGL_V, NULL},
+ {"help", (void *) mp_help_text, CONF_TYPE_PRINT, CONF_NOCFG|CONF_GLOBAL, 0, 0, NULL},
+ {"h", (void *) mp_help_text, CONF_TYPE_PRINT, CONF_NOCFG|CONF_GLOBAL, 0, 0, NULL},
+ {"version", (void *)print_version_opt, CONF_TYPE_PRINT_FUNC, CONF_NOCFG|CONF_GLOBAL|M_OPT_PRE_PARSE},
+ {"V", (void *)print_version_opt, CONF_TYPE_PRINT_FUNC, CONF_NOCFG|CONF_GLOBAL|M_OPT_PRE_PARSE},
+
+#ifdef CONFIG_ENCODING
+ OPT_STRING("o", encode_output.file, CONF_GLOBAL),
+ OPT_STRING("of", encode_output.format, CONF_GLOBAL),
+ OPT_STRINGLIST("ofopts*", encode_output.fopts, CONF_GLOBAL),
+ OPT_FLOATRANGE("ofps", encode_output.fps, CONF_GLOBAL, 0.0, 1000000.0),
+ OPT_FLOATRANGE("omaxfps", encode_output.maxfps, CONF_GLOBAL, 0.0, 1000000.0),
+ OPT_STRING("ovc", encode_output.vcodec, CONF_GLOBAL),
+ OPT_STRINGLIST("ovcopts*", encode_output.vopts, CONF_GLOBAL),
+ OPT_STRING("oac", encode_output.acodec, CONF_GLOBAL),
+ OPT_STRINGLIST("oacopts*", encode_output.aopts, CONF_GLOBAL),
+ OPT_FLAG("oharddup", encode_output.harddup, CONF_GLOBAL),
+ OPT_FLOATRANGE("ovoffset", encode_output.voffset, CONF_GLOBAL, -1000000.0, 1000000.0),
+ OPT_FLOATRANGE("oaoffset", encode_output.aoffset, CONF_GLOBAL, -1000000.0, 1000000.0),
+ OPT_FLAG("ocopyts", encode_output.copyts, CONF_GLOBAL),
+ OPT_FLAG("orawts", encode_output.rawts, CONF_GLOBAL),
+ OPT_FLAG("oautofps", encode_output.autofps, CONF_GLOBAL),
+ OPT_FLAG("oneverdrop", encode_output.neverdrop, CONF_GLOBAL),
+ OPT_FLAG("ovfirst", encode_output.video_first, CONF_GLOBAL),
+ OPT_FLAG("oafirst", encode_output.audio_first, CONF_GLOBAL),
+#endif
+
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+
+const struct MPOpts mp_default_opts = {
+ .reset_options = (char **)(const char *[]){"pause", NULL},
+ .audio_driver_list = NULL,
+ .audio_decoders = "-spdif:*", // never select spdif by default
+ .video_decoders = NULL,
+ .fixed_vo = 1,
+ .softvol = SOFTVOL_AUTO,
+ .softvol_max = 200,
+ .mixer_init_volume = -1,
+ .mixer_init_mute = -1,
+ .volstep = 3,
+ .vo = {
+ .video_driver_list = NULL,
+ .cursor_autohide_delay = 1000,
+ .monitor_pixel_aspect = 1.0,
+ .panscanrange = 1.0,
+ .screen_id = -1,
+ .fsscreen_id = -1,
+ .nomouse_input = 0,
+ .enable_mouse_movements = 1,
+ .fsmode = 0,
+ .panscan = 0.0f,
+ .keepaspect = 1,
+ .border = 1,
+ .WinID = -1,
+ },
+ .wintitle = "mpv - ${media-title}",
+ .heartbeat_interval = 30.0,
+ .stop_screensaver = 1,
+ .gamma_gamma = 1000,
+ .gamma_brightness = 1000,
+ .gamma_contrast = 1000,
+ .gamma_saturation = 1000,
+ .gamma_hue = 1000,
+ .osd_level = 1,
+ .osd_duration = 1000,
+ .osd_bar_align_y = 0.5,
+ .osd_bar_w = 75.0,
+ .osd_bar_h = 3.125,
+ .osd_scale = 1,
+ .loop_times = -1,
+ .ordered_chapters = 1,
+ .chapter_merge_threshold = 100,
+ .load_config = 1,
+ .position_resume = 1,
+ .stream_cache_min_percent = 20.0,
+ .stream_cache_seek_min_percent = 50.0,
+ .stream_cache_pause = 10.0,
+ .chapterrange = {-1, -1},
+ .edition_id = -1,
+ .default_max_pts_correction = -1,
+ .correct_pts = 1,
+ .initial_audio_sync = 1,
+ .term_osd = 2,
+ .consolecontrols = 1,
+ .play_frames = -1,
+ .keep_open = 0,
+ .audio_id = -1,
+ .video_id = -1,
+ .sub_id = -1,
+ .audio_display = 1,
+ .sub_visibility = 1,
+ .sub_pos = 100,
+ .sub_speed = 1.0,
+ .audio_output_channels = MP_CHMAP_INIT_STEREO,
+ .audio_output_format = -1, // AF_FORMAT_UNKNOWN
+ .playback_speed = 1.,
+ .movie_aspect = -1.,
+ .field_dominance = -1,
+ .sub_auto = 1,
+ .osd_bar_visible = 1,
+#ifdef CONFIG_ASS
+ .ass_enabled = 1,
+#endif
+ .sub_scale = 1,
+ .ass_vsfilter_aspect_compat = 1,
+ .ass_vsfilter_color_compat = 1,
+ .ass_vsfilter_blur_compat = 1,
+ .ass_style_override = 1,
+ .use_embedded_fonts = 1,
+ .suboverlap_enabled = 0,
+
+ .hwdec_codecs = "all",
+
+ .index_mode = -1,
+
+ .ad_lavc_param = {
+ .ac3drc = 1.,
+ .downmix = 1,
+ },
+ .lavfdopts = {
+ .allow_mimetype = 1,
+ },
+ .input = {
+ .key_fifo_size = 7,
+ .doubleclick_time = 300,
+ .ar_delay = 200,
+ .ar_rate = 40,
+ .use_joystick = 1,
+ .use_lirc = 1,
+ .use_lircc = 1,
+#ifdef CONFIG_COCOA
+ .use_ar = 1,
+ .use_media_keys = 1,
+#endif
+ .default_bindings = 1,
+ },
+};
+
+#endif /* MPLAYER_CFG_MPLAYER_H */
diff --git a/mpvcore/options.h b/mpvcore/options.h
new file mode 100644
index 0000000000..c83ab7a73a
--- /dev/null
+++ b/mpvcore/options.h
@@ -0,0 +1,276 @@
+#ifndef MPLAYER_OPTIONS_H
+#define MPLAYER_OPTIONS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "core/m_option.h"
+
+typedef struct mp_vo_opts {
+ struct m_obj_settings *video_driver_list;
+
+ int screenwidth;
+ int screenheight;
+ int ontop;
+ int fullscreen;
+ int screen_id;
+ int fsscreen_id;
+ char *winname;
+ char** fstype_list;
+ int native_keyrepeat;
+
+ float panscan;
+ float panscanrange;
+
+ struct m_geometry geometry;
+ struct m_geometry autofit;
+ struct m_geometry autofit_larger;
+
+ int fsmode;
+ int keepaspect;
+ int border;
+
+ int nomouse_input;
+ int enable_mouse_movements;
+ int cursor_autohide_delay;
+
+ int64_t WinID;
+
+ float force_monitor_aspect;
+ float monitor_pixel_aspect;
+ int force_window_position;
+
+ int native_fs;
+} mp_vo_opts;
+
+typedef struct MPOpts {
+ char **reset_options;
+
+ struct m_obj_settings *audio_driver_list;
+ int fixed_vo;
+ int softvol;
+ float mixer_init_volume;
+ int mixer_init_mute;
+ int volstep;
+ float softvol_max;
+ int gapless_audio;
+
+ mp_vo_opts vo;
+
+ char *wintitle;
+ int force_rgba_osd;
+
+ // ranges -100 - 100, 1000 if the vo default should be used
+ int gamma_gamma;
+ int gamma_brightness;
+ int gamma_contrast;
+ int gamma_saturation;
+ int gamma_hue;
+
+ int stop_screensaver;
+ int requested_colorspace;
+ int requested_input_range;
+ int requested_output_range;
+
+ char *audio_decoders;
+ char *video_decoders;
+
+ int osd_level;
+ int osd_duration;
+ int osd_fractions;
+ int untimed;
+ char *stream_capture;
+ char *stream_dump;
+ int loop_times;
+ int ordered_chapters;
+ int chapter_merge_threshold;
+ int quiet;
+ int load_config;
+ int use_filedir_conf;
+ int stream_cache_size;
+ int stream_cache_def_size;
+ float stream_cache_min_percent;
+ float stream_cache_seek_min_percent;
+ int stream_cache_pause;
+ int chapterrange[2];
+ int edition_id;
+ int correct_pts;
+ int user_pts_assoc_mode;
+ int initial_audio_sync;
+ int hr_seek;
+ float hr_seek_demuxer_offset;
+ float audio_delay;
+ float default_max_pts_correction;
+ int autosync;
+ int softsleep;
+ int frame_dropping;
+ int term_osd;
+ char *term_osd_esc;
+ char *playing_msg;
+ char *status_msg;
+ char *osd_status_msg;
+ char *heartbeat_cmd;
+ float heartbeat_interval;
+ int player_idle_mode;
+ int slave_mode;
+ int consolecontrols;
+ int list_properties;
+ struct m_rel_time play_start;
+ struct m_rel_time play_end;
+ struct m_rel_time play_length;
+ int play_frames;
+ double step_sec;
+ int64_t seek_to_byte;
+ int position_resume;
+ int position_save_on_quit;
+ int pause;
+ int keep_open;
+ int audio_id;
+ int video_id;
+ int sub_id;
+ char **audio_lang;
+ char **sub_lang;
+ int audio_display;
+ int sub_visibility;
+ int sub_pos;
+ float sub_delay;
+ float sub_fps;
+ float sub_speed;
+ int forced_subs_only;
+ char *quvi_format;
+
+ // subreader.c
+ int suboverlap_enabled;
+ char *sub_cp;
+
+ char *audio_stream;
+ int audio_stream_cache;
+ char *demuxer_name;
+ char *audio_demuxer_name;
+ char *sub_demuxer_name;
+ int mkv_subtitle_preroll;
+
+ struct image_writer_opts *screenshot_image_opts;
+ char *screenshot_template;
+
+ double force_fps;
+ int index_mode; // -1=untouched 0=don't use index 1=use (generate) index
+
+ struct mp_chmap audio_output_channels;
+ int audio_output_format;
+ int force_srate;
+ int dtshd;
+ double playback_speed;
+ struct m_obj_settings *vf_settings;
+ struct m_obj_settings *af_settings;
+ float movie_aspect;
+ int flip;
+ int field_dominance;
+ int divx_quality;
+ char **sub_name;
+ char **sub_paths;
+ int sub_auto;
+ int sub_match_fuzziness;
+ int osd_bar_visible;
+ float osd_bar_align_x;
+ float osd_bar_align_y;
+ float osd_bar_w;
+ float osd_bar_h;
+ float osd_scale;
+ struct osd_style_opts *osd_style;
+ struct osd_style_opts *sub_text_style;
+ float sub_scale;
+ float sub_gauss;
+ int sub_gray;
+ int ass_enabled;
+ float ass_line_spacing;
+ int ass_use_margins;
+ int ass_vsfilter_aspect_compat;
+ int ass_vsfilter_color_compat;
+ int ass_vsfilter_blur_compat;
+ int use_embedded_fonts;
+ char **ass_force_style_list;
+ char *ass_styles_file;
+ int ass_style_override;
+ int ass_hinting;
+
+ int hwdec_api;
+ char *hwdec_codecs;
+
+ struct lavc_param {
+ int fast;
+ char *skip_loop_filter_str;
+ char *skip_idct_str;
+ char *skip_frame_str;
+ int threads;
+ int bitexact;
+ char *avopt;
+ } lavc_param;
+
+ struct ad_lavc_param {
+ float ac3drc;
+ int downmix;
+ char *avopt;
+ } ad_lavc_param;
+
+ struct lavfdopts {
+ int probesize;
+ int probescore;
+ float analyzeduration;
+ int buffersize;
+ int allow_mimetype;
+ char *format;
+ char *cryptokey;
+ char *avopt;
+ int genptsmode;
+ } lavfdopts;
+
+ struct input_conf {
+ char *config_file;
+ int doubleclick_time;
+ int key_fifo_size;
+ int ar_delay;
+ int ar_rate;
+ char *js_dev;
+ char *in_file;
+ int use_joystick;
+ int use_lirc;
+ int use_lircc;
+ int use_ar;
+ int use_media_keys;
+ int default_bindings;
+ int test;
+ } input;
+
+ struct encode_output_conf {
+ char *file;
+ char *format;
+ char **fopts;
+ float fps;
+ float maxfps;
+ char *vcodec;
+ char **vopts;
+ char *acodec;
+ char **aopts;
+ int harddup;
+ float voffset;
+ float aoffset;
+ int copyts;
+ int rawts;
+ int autofps;
+ int neverdrop;
+ int video_first;
+ int audio_first;
+ } encode_output;
+} MPOpts;
+
+// Should be moved into MPOpts
+extern char **network_http_header_fields;
+extern char *network_useragent;
+extern char *network_referrer;
+extern int network_cookies_enabled;
+extern char *cookies_file;
+
+extern const m_option_t mp_opts[];
+extern const struct MPOpts mp_default_opts;
+
+#endif
diff --git a/mpvcore/parser-cfg.c b/mpvcore/parser-cfg.c
new file mode 100644
index 0000000000..a69f4545d2
--- /dev/null
+++ b/mpvcore/parser-cfg.c
@@ -0,0 +1,249 @@
+/*
+ * 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 "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "osdep/io.h"
+
+#include "parser-cfg.h"
+#include "core/mp_msg.h"
+#include "core/m_option.h"
+#include "m_config.h"
+
+/// Maximal include depth.
+#define MAX_RECURSION_DEPTH 8
+
+/// Current include depth.
+static int recursion_depth = 0;
+
+/// Setup the \ref Config from a config file.
+/** \param config The config object.
+ * \param conffile Path to the config file.
+ * \param flags M_SETOPT_* bits
+ * \return 1 on sucess, -1 on error, 0 if file not accessible.
+ */
+int m_config_parse_config_file(m_config_t *config, const char *conffile,
+ int flags)
+{
+#define PRINT_LINENUM mp_msg(MSGT_CFGPARSER, MSGL_ERR, "%s:%d: ", conffile, line_num)
+#define MAX_LINE_LEN 10000
+#define MAX_OPT_LEN 1000
+#define MAX_PARAM_LEN 1500
+ FILE *fp = NULL;
+ char *line = NULL;
+ char opt[MAX_OPT_LEN + 1];
+ char param[MAX_PARAM_LEN + 1];
+ char c; /* for the "" and '' check */
+ int tmp;
+ int line_num = 0;
+ int line_pos; /* line pos */
+ int opt_pos; /* opt pos */
+ int param_pos; /* param pos */
+ int ret = 1;
+ int errors = 0;
+ m_profile_t *profile = NULL;
+
+ flags = flags | M_SETOPT_FROM_CONFIG_FILE;
+
+ mp_msg(MSGT_CFGPARSER, MSGL_V, "Reading config file %s", conffile);
+
+ if (recursion_depth > MAX_RECURSION_DEPTH) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ ": too deep 'include'. check your configfiles\n");
+ ret = -1;
+ goto out;
+ }
+
+ if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL,
+ "\ncan't get memory for 'line': %s", strerror(errno));
+ ret = -1;
+ goto out;
+ } else
+
+ mp_msg(MSGT_CFGPARSER, MSGL_V, "\n");
+
+ if ((fp = fopen(conffile, "r")) == NULL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_V, ": %s\n", strerror(errno));
+ ret = 0;
+ goto out;
+ }
+
+ while (fgets(line, MAX_LINE_LEN, fp)) {
+ if (errors >= 16) {
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "too many errors\n");
+ goto out;
+ }
+
+ line_num++;
+ line_pos = 0;
+
+ /* skip whitespaces */
+ while (isspace(line[line_pos]))
+ ++line_pos;
+
+ /* EOL / comment */
+ if (line[line_pos] == '\0' || line[line_pos] == '#')
+ continue;
+
+ /* read option. */
+ for (opt_pos = 0; isprint(line[line_pos]) &&
+ line[line_pos] != ' ' &&
+ line[line_pos] != '#' &&
+ line[line_pos] != '='; /* NOTHING */) {
+ opt[opt_pos++] = line[line_pos++];
+ if (opt_pos >= MAX_OPT_LEN) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too long option\n");
+ errors++;
+ ret = -1;
+ goto nextline;
+ }
+ }
+ if (opt_pos == 0) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parse error\n");
+ ret = -1;
+ errors++;
+ continue;
+ }
+ opt[opt_pos] = '\0';
+
+ /* Profile declaration */
+ if (opt_pos > 2 && opt[0] == '[' && opt[opt_pos - 1] == ']') {
+ opt[opt_pos - 1] = '\0';
+ if (strcmp(opt + 1, "default"))
+ profile = m_config_add_profile(config, opt + 1);
+ else
+ profile = NULL;
+ continue;
+ }
+
+ /* skip whitespaces */
+ while (isspace(line[line_pos]))
+ ++line_pos;
+
+ param_pos = 0;
+ bool param_set = false;
+
+ /* check '=' */
+ if (line[line_pos] == '=') {
+ line_pos++;
+ param_set = true;
+
+ /* whitespaces... */
+ while (isspace(line[line_pos]))
+ ++line_pos;
+
+ /* read the parameter */
+ if (line[line_pos] == '"' || line[line_pos] == '\'') {
+ c = line[line_pos];
+ ++line_pos;
+ for (param_pos = 0; line[line_pos] != c; /* NOTHING */) {
+ param[param_pos++] = line[line_pos++];
+ if (param_pos >= MAX_PARAM_LEN) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "option %s has a too long parameter\n", opt);
+ ret = -1;
+ errors++;
+ goto nextline;
+ }
+ }
+ line_pos++; /* skip the closing " or ' */
+ } else {
+ for (param_pos = 0; isprint(line[line_pos])
+ && !isspace(line[line_pos])
+ && line[line_pos] != '#'; /* NOTHING */) {
+ param[param_pos++] = line[line_pos++];
+ if (param_pos >= MAX_PARAM_LEN) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too long parameter\n");
+ ret = -1;
+ errors++;
+ goto nextline;
+ }
+ }
+ }
+
+ while (isspace(line[line_pos]))
+ ++line_pos;
+ }
+ param[param_pos] = '\0';
+
+ /* EOL / comment */
+ if (line[line_pos] != '\0' && line[line_pos] != '#') {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "extra characters: %s\n", line + line_pos);
+ ret = -1;
+ }
+
+ bstr bopt = bstr0(opt);
+ bstr bparam = bstr0(param);
+
+ if (profile && bstr_equals0(bopt, "profile-desc")) {
+ m_profile_set_desc(profile, param);
+ goto nextline;
+ }
+
+ tmp = m_config_option_requires_param(config, bopt);
+ if (tmp > 0 && !param_set)
+ tmp = M_OPT_MISSING_PARAM;
+ if (tmp < 0) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "error parsing option %s=%s: %s\n",
+ opt, param, m_option_strerror(tmp));
+ continue;
+ }
+
+ if (profile) {
+ tmp = m_config_set_profile_option(config, profile, bopt, bparam);
+ } else {
+ tmp = m_config_set_option_ext(config, bopt, bparam, flags);
+ }
+ if (tmp < 0) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "setting option %s='%s' failed.\n", opt, param);
+ continue;
+ /* break */
+ }
+nextline:
+ ;
+ }
+
+out:
+ free(line);
+ if (fp)
+ fclose(fp);
+ --recursion_depth;
+ if (ret < 0) {
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "Error loading config file %s.\n",
+ conffile);
+ }
+ return ret;
+}
diff --git a/mpvcore/parser-cfg.h b/mpvcore/parser-cfg.h
new file mode 100644
index 0000000000..65a4b49496
--- /dev/null
+++ b/mpvcore/parser-cfg.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_PARSER_CFG_H
+#define MPLAYER_PARSER_CFG_H
+
+#include "m_config.h"
+
+int m_config_parse_config_file(m_config_t* config, const char *conffile,
+ int flags);
+
+#endif /* MPLAYER_PARSER_CFG_H */
diff --git a/mpvcore/parser-mpcmd.c b/mpvcore/parser-mpcmd.c
new file mode 100644
index 0000000000..d716fc4d28
--- /dev/null
+++ b/mpvcore/parser-mpcmd.c
@@ -0,0 +1,314 @@
+/*
+ * 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 "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include "core/mp_msg.h"
+#include "core/m_option.h"
+#include "m_config.h"
+#include "playlist.h"
+#include "playlist_parser.h"
+#include "parser-mpcmd.h"
+
+#define GLOBAL 0
+#define LOCAL 1
+
+#define dvd_range(a) (a > 0 && a < 256)
+
+
+struct parse_state {
+ struct m_config *config;
+ int argc;
+ char **argv;
+
+ bool no_more_opts;
+ bool error;
+
+ bool is_opt;
+ struct bstr arg;
+ struct bstr param;
+};
+
+// Returns 0 if a valid option/file is available, <0 on error, 1 on end of args.
+static int split_opt_silent(struct parse_state *p)
+{
+ assert(!p->error);
+
+ if (p->argc < 1)
+ return 1;
+
+ p->is_opt = false;
+ p->arg = bstr0(p->argv[0]);
+ p->param = bstr0(NULL);
+
+ p->argc--;
+ p->argv++;
+
+ if (p->no_more_opts || !bstr_startswith0(p->arg, "-") || p->arg.len == 1)
+ return 0;
+
+ if (bstrcmp0(p->arg, "--") == 0) {
+ p->no_more_opts = true;
+ return split_opt_silent(p);
+ }
+
+ p->is_opt = true;
+
+ if (!bstr_eatstart0(&p->arg, "--"))
+ bstr_eatstart0(&p->arg, "-");
+
+ bool ambiguous = !bstr_split_tok(p->arg, "=", &p->arg, &p->param);
+
+ int r = m_config_option_requires_param(p->config, p->arg);
+ if (r < 0)
+ return r;
+
+ if (ambiguous && r > 0) {
+ if (p->argc < 1)
+ return M_OPT_MISSING_PARAM;
+ p->param = bstr0(p->argv[0]);
+ p->argc--;
+ p->argv++;
+ }
+
+ return 0;
+}
+
+// Returns true if more args, false if all parsed or an error occurred.
+static bool split_opt(struct parse_state *p)
+{
+ int r = split_opt_silent(p);
+ if (r >= 0)
+ return r == 0;
+ p->error = true;
+
+ mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL,
+ "Error parsing commandline option %.*s: %s\n",
+ BSTR_P(p->arg), m_option_strerror(r));
+ return false;
+}
+
+static bool parse_flag(bstr name, bstr f)
+{
+ struct m_option opt = {NULL, NULL, CONF_TYPE_FLAG, 0, 0, 1, NULL};
+ int val = 0;
+ m_option_parse(&opt, name, f, &val);
+ return !!val;
+}
+
+// returns M_OPT_... error code
+int m_config_parse_mp_command_line(m_config_t *config, struct playlist *files,
+ int argc, char **argv)
+{
+ int ret = M_OPT_UNKNOWN;
+ int mode = 0;
+ struct playlist_entry *local_start = NULL;
+ bool shuffle = false;
+
+ int local_params_count = 0;
+ struct playlist_param *local_params = 0;
+
+ assert(config != NULL);
+
+ mode = GLOBAL;
+
+ struct parse_state p = {config, argc, argv};
+ while (split_opt(&p)) {
+ if (p.is_opt) {
+ int flags = mode == LOCAL ? M_SETOPT_BACKUP | M_SETOPT_CHECK_ONLY : 0;
+ int r;
+ r = m_config_set_option_ext(config, p.arg, p.param, flags);
+ if (r <= M_OPT_EXIT) {
+ ret = r;
+ goto err_out;
+ }
+ if (r < 0) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL,
+ "Setting commandline option --%.*s=%.*s failed.\n",
+ BSTR_P(p.arg), BSTR_P(p.param));
+ goto err_out;
+ }
+
+ // Handle some special arguments outside option parser.
+
+ if (!bstrcmp0(p.arg, "{")) {
+ if (mode != GLOBAL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "'--{' can not be nested.\n");
+ goto err_out;
+ }
+ mode = LOCAL;
+ assert(!local_start);
+ local_start = files->last;
+ continue;
+ }
+
+ if (!bstrcmp0(p.arg, "}")) {
+ if (mode != LOCAL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Too many closing '--}'.\n");
+ goto err_out;
+ }
+ if (local_params_count) {
+ // The files added between '{' and '}' are the entries from
+ // the entry _after_ local_start, until the end of the list.
+ // If local_start is NULL, the list was empty on '{', and we
+ // want all files in the list.
+ struct playlist_entry *cur
+ = local_start ? local_start->next : files->first;
+ if (!cur)
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Ignored options!\n");
+ while (cur) {
+ playlist_entry_add_params(cur, local_params,
+ local_params_count);
+ cur = cur->next;
+ }
+ }
+ local_params_count = 0;
+ mode = GLOBAL;
+ m_config_restore_backups(config);
+ local_start = NULL;
+ shuffle = false;
+ continue;
+ }
+
+ if (bstrcmp0(p.arg, "shuffle") == 0) {
+ shuffle = parse_flag(p.arg, p.param);
+ continue;
+ }
+ if (bstrcmp0(p.arg, "no-shuffle") == 0) {
+ shuffle = false;
+ continue;
+ }
+
+ if (bstrcmp0(p.arg, "playlist") == 0) {
+ // append the playlist to the local args
+ char *param0 = bstrdup0(NULL, p.param);
+ struct playlist *pl = playlist_parse_file(param0);
+ talloc_free(param0);
+ if (!pl) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL,
+ "Error reading playlist '%.*s'", BSTR_P(p.param));
+ goto err_out;
+ }
+ playlist_transfer_entries(files, pl);
+ talloc_free(pl);
+ continue;
+ }
+
+ if (mode == LOCAL) {
+ MP_TARRAY_APPEND(NULL, local_params, local_params_count,
+ (struct playlist_param) {p.arg, p.param});
+ }
+ } else {
+ // filename
+ void *tmp = talloc_new(NULL);
+ bstr file = p.arg;
+ char *file0 = bstrdup0(tmp, p.arg);
+ // expand DVD filename entries like dvd://1-3 into component titles
+ if (bstr_startswith0(file, "dvd://")) {
+ int offset = 6;
+ char *splitpos = strstr(file0 + offset, "-");
+ if (splitpos != NULL) {
+ char *endpos;
+ int start_title = strtol(file0 + offset, &endpos, 10);
+ int end_title;
+ //entries like dvd://-2 imply start at title 1
+ if (start_title < 0) {
+ end_title = abs(start_title);
+ start_title = 1;
+ } else
+ end_title = strtol(splitpos + 1, &endpos, 10);
+
+ if (dvd_range(start_title) && dvd_range(end_title)
+ && (start_title < end_title)) {
+ for (int j = start_title; j <= end_title; j++) {
+ char *f = talloc_asprintf(tmp, "dvd://%d%s", j,
+ endpos);
+ playlist_add_file(files, f);
+ }
+ } else
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid play entry %s\n", file0);
+
+ } else // dvd:// or dvd://x entry
+ playlist_add_file(files, file0);
+ } else
+ playlist_add_file(files, file0);
+ talloc_free(tmp);
+
+ // Lock stdin if it will be used as input
+ if (bstrcmp0(file, "-") == 0)
+ m_config_set_option0(config, "consolecontrols", "no");
+ }
+ }
+
+ if (p.error)
+ goto err_out;
+
+ if (mode != GLOBAL) {
+ mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
+ "Missing closing --} on command line.\n");
+ goto err_out;
+ }
+
+ if (shuffle)
+ playlist_shuffle(files);
+
+ ret = 0; // success
+
+err_out:
+ talloc_free(local_params);
+ m_config_restore_backups(config);
+ return ret;
+}
+
+extern int mp_msg_levels[];
+
+/* Parse some command line options early before main parsing.
+ * --no-config prevents reading configuration files (otherwise done before
+ * command line parsing), and --really-quiet suppresses messages printed
+ * during normal options parsing.
+ */
+void m_config_preparse_command_line(m_config_t *config, int argc, char **argv)
+{
+ // Hack to shut up parser error messages
+ int msg_lvl_backup = mp_msg_levels[MSGT_CFGPARSER];
+ mp_msg_levels[MSGT_CFGPARSER] = -11;
+
+ struct parse_state p = {config, argc, argv};
+ while (split_opt_silent(&p) == 0) {
+ if (p.is_opt) {
+ // Ignore non-pre-parse options. They will be set later.
+ // Option parsing errors will be handled later as well.
+ m_config_set_option_ext(config, p.arg, p.param,
+ M_SETOPT_PRE_PARSE_ONLY);
+ if (bstrcmp0(p.arg, "v") == 0)
+ verbose++;
+ }
+ }
+
+ mp_msg_levels[MSGT_CFGPARSER] = msg_lvl_backup;
+}
diff --git a/mpvcore/parser-mpcmd.h b/mpvcore/parser-mpcmd.h
new file mode 100644
index 0000000000..256bc514b2
--- /dev/null
+++ b/mpvcore/parser-mpcmd.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_PARSER_MPCMD_H
+#define MPLAYER_PARSER_MPCMD_H
+
+#include <stdbool.h>
+
+struct playlist;
+struct m_config;
+
+int m_config_parse_mp_command_line(struct m_config *config,
+ struct playlist *files,
+ int argc, char **argv);
+void m_config_preparse_command_line(struct m_config *config,
+ int argc, char **argv);
+
+#endif /* MPLAYER_PARSER_MPCMD_H */
diff --git a/mpvcore/path.c b/mpvcore/path.c
new file mode 100644
index 0000000000..50350be18c
--- /dev/null
+++ b/mpvcore/path.c
@@ -0,0 +1,229 @@
+/*
+ * Get path to config dir/file.
+ *
+ * Return Values:
+ * Returns the pointer to the ALLOCATED buffer containing the
+ * zero terminated path string. This buffer has to be FREED
+ * by the caller.
+ *
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include "config.h"
+#include "core/mp_msg.h"
+#include "core/path.h"
+#include "talloc.h"
+#include "osdep/io.h"
+
+#if defined(__MINGW32__)
+#include <windows.h>
+#elif defined(__CYGWIN__)
+#include <windows.h>
+#include <sys/cygwin.h>
+#endif
+
+#ifdef CONFIG_MACOSX_BUNDLE
+#include "osdep/macosx_bundle.h"
+#endif
+
+
+typedef char *(*lookup_fun)(const char *);
+static const lookup_fun config_lookup_functions[] = {
+ mp_find_user_config_file,
+#ifdef CONFIG_MACOSX_BUNDLE
+ get_bundled_path,
+#endif
+ mp_find_global_config_file,
+ NULL
+};
+
+char *mp_find_config_file(const char *filename)
+{
+ for (int i = 0; config_lookup_functions[i] != NULL; i++) {
+ char *path = config_lookup_functions[i](filename);
+ if (!path) continue;
+
+ if (mp_path_exists(path))
+ return path;
+
+ talloc_free(path);
+ }
+ return NULL;
+}
+
+char *mp_find_user_config_file(const char *filename)
+{
+ char *homedir = NULL, *buff = NULL;
+#ifdef __MINGW32__
+ static char *config_dir = "mpv";
+#else
+ static char *config_dir = ".mpv";
+#endif
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ char *temp = NULL;
+ char exedir[260];
+ /* Hack to get fonts etc. loaded outside of Cygwin environment. */
+ int i, imax = 0;
+ int len = (int)GetModuleFileNameA(NULL, exedir, 260);
+ for (i = 0; i < len; i++)
+ if (exedir[i] == '\\') {
+ exedir[i] = '/';
+ imax = i;
+ }
+ exedir[imax] = '\0';
+
+ if (filename)
+ temp = mp_path_join(NULL, bstr0(exedir), bstr0(filename));
+
+ if (temp && mp_path_exists(temp) && !mp_path_isdir(temp)) {
+ homedir = exedir;
+ config_dir = "";
+ }
+ else
+#endif
+ if ((homedir = getenv("MPV_HOME")) != NULL) {
+ config_dir = "";
+ } else if ((homedir = getenv("HOME")) == NULL) {
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ homedir = exedir;
+#else
+ return NULL;
+#endif
+ }
+#if defined(__MINGW32__) || defined(__CYGWIN__)
+ talloc_free(temp);
+#endif
+
+ if (filename) {
+ char * temp = mp_path_join(NULL, bstr0(homedir), bstr0(config_dir));
+ buff = mp_path_join(NULL, bstr0(temp), bstr0(filename));
+ talloc_free(temp);
+ } else {
+ buff = mp_path_join(NULL, bstr0(homedir), bstr0(config_dir));
+ }
+
+ mp_msg(MSGT_GLOBAL, MSGL_V, "get_path('%s') -> '%s'\n", filename, buff);
+ return buff;
+}
+
+char *mp_find_global_config_file(const char *filename)
+{
+ if (filename) {
+ return mp_path_join(NULL, bstr0(MPLAYER_CONFDIR), bstr0(filename));
+ } else {
+ return talloc_strdup(NULL, MPLAYER_CONFDIR);
+ }
+}
+
+char *mp_basename(const char *path)
+{
+ char *s;
+
+#if HAVE_DOS_PATHS
+ s = strrchr(path, '\\');
+ if (s)
+ path = s + 1;
+ s = strrchr(path, ':');
+ if (s)
+ path = s + 1;
+#endif
+ s = strrchr(path, '/');
+ return s ? s + 1 : (char *)path;
+}
+
+struct bstr mp_dirname(const char *path)
+{
+ struct bstr ret = {
+ (uint8_t *)path, mp_basename(path) - path
+ };
+ if (ret.len == 0)
+ return bstr0(".");
+ return ret;
+}
+
+char *mp_splitext(const char *path, bstr *root)
+{
+ assert(path);
+ const char *split = strrchr(path, '.');
+ if (!split)
+ split = path + strlen(path);
+ if (root)
+ *root = (bstr){.start = (char *)path, .len = path - split};
+ return (char *)split;
+}
+
+char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2)
+{
+ if (p1.len == 0)
+ return bstrdup0(talloc_ctx, p2);
+ if (p2.len == 0)
+ return bstrdup0(talloc_ctx, p1);
+
+#if HAVE_DOS_PATHS
+ if (p2.len >= 2 && p2.start[1] == ':'
+ || p2.start[0] == '\\' || p2.start[0] == '/')
+#else
+ if (p2.start[0] == '/')
+#endif
+ return bstrdup0(talloc_ctx, p2); // absolute path
+
+ bool have_separator;
+ int endchar1 = p1.start[p1.len - 1];
+#if HAVE_DOS_PATHS
+ have_separator = endchar1 == '/' || endchar1 == '\\'
+ || p1.len == 2 && endchar1 == ':'; // "X:" only
+#else
+ have_separator = endchar1 == '/';
+#endif
+
+ return talloc_asprintf(talloc_ctx, "%.*s%s%.*s", BSTR_P(p1),
+ have_separator ? "" : "/", BSTR_P(p2));
+}
+
+char *mp_getcwd(void *talloc_ctx)
+{
+ char *wd = talloc_array(talloc_ctx, char, 20);
+ while (getcwd(wd, talloc_get_size(wd)) == NULL) {
+ if (errno != ERANGE) {
+ talloc_free(wd);
+ return NULL;
+ }
+ wd = talloc_realloc(talloc_ctx, wd, char, talloc_get_size(wd) * 2);
+ }
+ return wd;
+}
+
+bool mp_path_exists(const char *path)
+{
+ struct stat st;
+ return mp_stat(path, &st) == 0;
+}
+
+bool mp_path_isdir(const char *path)
+{
+ struct stat st;
+ return mp_stat(path, &st) == 0 && S_ISDIR(st.st_mode);
+}
diff --git a/mpvcore/path.h b/mpvcore/path.h
new file mode 100644
index 0000000000..a38ad503ea
--- /dev/null
+++ b/mpvcore/path.h
@@ -0,0 +1,66 @@
+/*
+ * Get path to config dir/file.
+ *
+ * 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.
+ */
+
+#ifndef MPLAYER_PATH_H
+#define MPLAYER_PATH_H
+
+#include <stdbool.h>
+#include "core/bstr.h"
+
+
+// Search for the input filename in several paths. These include user and global
+// config locations by default. Some platforms may implement additional platform
+// related lookups (i.e.: OSX inside an application bundle).
+char *mp_find_config_file(const char *filename);
+
+// Search for the input filename in the global configuration location.
+char *mp_find_global_config_file(const char *filename);
+
+// Search for the input filename in the user configuration location.
+char *mp_find_user_config_file(const char *filename);
+
+// Return pointer to filename part of path
+
+char *mp_basename(const char *path);
+
+/* Return file extension, including the '.'. If root is not NULL, set it to the
+ * part of the path without extension. So: path == root + returnvalue
+ * Don't consider it a file extension if the only '.' is the first character.
+ * Return "" if no extension.
+ */
+char *mp_splitext(const char *path, bstr *root);
+
+/* Return struct bstr referencing directory part of path, or if that
+ * would be empty, ".".
+ */
+struct bstr mp_dirname(const char *path);
+
+/* Join two path components and return a newly allocated string
+ * for the result. '/' is inserted between the components if needed.
+ * If p2 is an absolute path then the value of p1 is ignored.
+ */
+char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2);
+
+char *mp_getcwd(void *talloc_ctx);
+
+bool mp_path_exists(const char *path);
+bool mp_path_isdir(const char *path);
+
+#endif /* MPLAYER_PATH_H */
diff --git a/mpvcore/playlist.c b/mpvcore/playlist.c
new file mode 100644
index 0000000000..b016cebca6
--- /dev/null
+++ b/mpvcore/playlist.c
@@ -0,0 +1,246 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include "config.h"
+#include "playlist.h"
+#include "core/mp_common.h"
+#include "talloc.h"
+#include "core/path.h"
+
+struct playlist_entry *playlist_entry_new(const char *filename)
+{
+ struct playlist_entry *e = talloc_zero(NULL, struct playlist_entry);
+ e->filename = talloc_strdup(e, filename);
+ return e;
+}
+
+void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value)
+{
+ struct playlist_param p = {bstrdup(e, name), bstrdup(e, value)};
+ MP_TARRAY_APPEND(e, e->params, e->num_params, p);
+}
+
+void playlist_entry_add_params(struct playlist_entry *e,
+ struct playlist_param *params,
+ int num_params)
+{
+ for (int n = 0; n < num_params; n++)
+ playlist_entry_add_param(e, params[n].name, params[n].value);
+}
+
+// Add entry "add" after entry "after".
+// If "after" is NULL, add as first entry.
+// Post condition: add->prev == after
+void playlist_insert(struct playlist *pl, struct playlist_entry *after,
+ struct playlist_entry *add)
+{
+ assert(pl && add->pl == NULL && add->next == NULL && add->prev == NULL);
+ if (after) {
+ assert(after->pl == pl);
+ assert(pl->first && pl->last);
+ }
+ add->prev = after;
+ if (after) {
+ add->next = after->next;
+ after->next = add;
+ } else {
+ add->next = pl->first;
+ pl->first = add;
+ }
+ if (add->next) {
+ add->next->prev = add;
+ } else {
+ pl->last = add;
+ }
+ add->pl = pl;
+ talloc_steal(pl, add);
+}
+
+void playlist_add(struct playlist *pl, struct playlist_entry *add)
+{
+ playlist_insert(pl, pl->last, add);
+}
+
+static void playlist_unlink(struct playlist *pl, struct playlist_entry *entry)
+{
+ assert(pl && entry->pl == pl);
+
+ if (pl->current == entry) {
+ pl->current = entry->next;
+ pl->current_was_replaced = true;
+ }
+
+ if (entry->next) {
+ entry->next->prev = entry->prev;
+ } else {
+ pl->last = entry->prev;
+ }
+ if (entry->prev) {
+ entry->prev->next = entry->next;
+ } else {
+ pl->first = entry->next;
+ }
+ entry->next = entry->prev = NULL;
+ // xxx: we'd want to reset the talloc parent of entry
+ entry->pl = NULL;
+}
+
+void playlist_remove(struct playlist *pl, struct playlist_entry *entry)
+{
+ playlist_unlink(pl, entry);
+ talloc_free(entry);
+}
+
+void playlist_clear(struct playlist *pl)
+{
+ while (pl->first)
+ playlist_remove(pl, pl->first);
+ assert(!pl->current);
+ pl->current_was_replaced = false;
+}
+
+// Moves entry such that entry->prev = at (even if at is NULL)
+void playlist_move(struct playlist *pl, struct playlist_entry *entry,
+ struct playlist_entry *at)
+{
+ struct playlist_entry *save_current = pl->current;
+ bool save_replaced = pl->current_was_replaced;
+
+ playlist_unlink(pl, entry);
+ playlist_insert(pl, at ? at->prev : pl->last, entry);
+
+ pl->current = save_current;
+ pl->current_was_replaced = save_replaced;
+}
+
+void playlist_add_file(struct playlist *pl, const char *filename)
+{
+ playlist_add(pl, playlist_entry_new(filename));
+}
+
+static int playlist_count(struct playlist *pl)
+{
+ int c = 0;
+ for (struct playlist_entry *e = pl->first; e; e = e->next)
+ c++;
+ return c;
+}
+
+void playlist_shuffle(struct playlist *pl)
+{
+ struct playlist_entry *save_current = pl->current;
+ bool save_replaced = pl->current_was_replaced;
+ int count = playlist_count(pl);
+ struct playlist_entry **arr = talloc_array(NULL, struct playlist_entry *,
+ count);
+ for (int n = 0; n < count; n++) {
+ arr[n] = pl->first;
+ playlist_unlink(pl, pl->first);
+ }
+ for (int n = 0; n < count; n++) {
+ int other = (int)((double)(count) * rand() / (RAND_MAX + 1.0));
+ struct playlist_entry *tmp = arr[n];
+ arr[n] = arr[other];
+ arr[other] = tmp;
+ }
+ for (int n = 0; n < count; n++)
+ playlist_add(pl, arr[n]);
+ talloc_free(arr);
+ pl->current = save_current;
+ pl->current_was_replaced = save_replaced;
+}
+
+struct playlist_entry *playlist_get_next(struct playlist *pl, int direction)
+{
+ assert(direction == -1 || direction == +1);
+ if (!pl->current)
+ return NULL;
+ assert(pl->current->pl == pl);
+ if (direction < 0)
+ return pl->current->prev;
+ return pl->current_was_replaced ? pl->current : pl->current->next;
+}
+
+static bool might_be_an_url(bstr f)
+{
+ return bstr_find0(f, "://") >= 0;
+}
+
+void playlist_add_base_path(struct playlist *pl, bstr base_path)
+{
+ if (base_path.len == 0 || bstrcmp0(base_path, ".") == 0)
+ return;
+ for (struct playlist_entry *e = pl->first; e; e = e->next) {
+ if (!might_be_an_url(bstr0(e->filename))) {
+ char *new_file = mp_path_join(e, base_path, bstr0(e->filename));
+ talloc_free(e->filename);
+ e->filename = new_file;
+ }
+ }
+}
+
+// Move all entries from source_pl to pl, appending them after the current entry
+// of pl. source_pl will be empty, and all entries have changed ownership to pl.
+void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
+{
+ struct playlist_entry *add_after = pl->current;
+ if (pl->current && pl->current_was_replaced)
+ add_after = pl->current->next;
+ if (!add_after)
+ add_after = pl->last;
+
+ while (source_pl->first) {
+ struct playlist_entry *e = source_pl->first;
+ playlist_unlink(source_pl, e);
+ playlist_insert(pl, add_after, e);
+ add_after = e;
+ }
+}
+
+// Return number of entries between list start and e.
+// Return -1 if e is not on the list, or if e is NULL.
+int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e)
+{
+ struct playlist_entry *cur = pl->first;
+ int pos = 0;
+ if (!e)
+ return -1;
+ while (cur && cur != e) {
+ cur = cur->next;
+ pos++;
+ }
+ return cur == e ? pos : -1;
+}
+
+int playlist_entry_count(struct playlist *pl)
+{
+ return playlist_entry_to_index(pl, pl->last) + 1;
+}
+
+// Return entry for which playlist_entry_to_index() would return index.
+// Return NULL if not found.
+struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index)
+{
+ struct playlist_entry *e = pl->first;
+ for (int n = 0; ; n++) {
+ if (!e || n == index)
+ return e;
+ e = e->next;
+ }
+}
+
diff --git a/mpvcore/playlist.h b/mpvcore/playlist.h
new file mode 100644
index 0000000000..f01d4b8ddd
--- /dev/null
+++ b/mpvcore/playlist.h
@@ -0,0 +1,74 @@
+/*
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_PLAYLIST_H
+#define MPLAYER_PLAYLIST_H
+
+#include <stdbool.h>
+#include "core/bstr.h"
+
+struct playlist_param {
+ bstr name, value;
+};
+
+struct playlist_entry {
+ struct playlist_entry *prev, *next;
+ struct playlist *pl;
+
+ char *filename;
+
+ struct playlist_param *params;
+ int num_params;
+};
+
+struct playlist {
+ struct playlist_entry *first, *last;
+
+ // This provides some sort of stable iterator. If this entry is removed from
+ // the playlist, current is set to the next element (or NULL), and
+ // current_was_replaced is set to true.
+ struct playlist_entry *current;
+ bool current_was_replaced;
+};
+
+void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value);
+void playlist_entry_add_params(struct playlist_entry *e,
+ struct playlist_param *params,
+ int params_count);
+
+struct playlist_entry *playlist_entry_new(const char *filename);
+
+void playlist_insert(struct playlist *pl, struct playlist_entry *after,
+ struct playlist_entry *add);
+void playlist_add(struct playlist *pl, struct playlist_entry *add);
+void playlist_remove(struct playlist *pl, struct playlist_entry *entry);
+void playlist_clear(struct playlist *pl);
+
+void playlist_move(struct playlist *pl, struct playlist_entry *entry,
+ struct playlist_entry *at);
+
+void playlist_add_file(struct playlist *pl, const char *filename);
+void playlist_shuffle(struct playlist *pl);
+struct playlist_entry *playlist_get_next(struct playlist *pl, int direction);
+void playlist_add_base_path(struct playlist *pl, bstr base_path);
+void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl);
+
+int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e);
+int playlist_entry_count(struct playlist *pl);
+struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index);
+
+#endif
diff --git a/mpvcore/playlist_parser.c b/mpvcore/playlist_parser.c
new file mode 100644
index 0000000000..59d5123be6
--- /dev/null
+++ b/mpvcore/playlist_parser.c
@@ -0,0 +1,777 @@
+/*
+ * 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 "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <limits.h>
+
+#include "talloc.h"
+#include "asxparser.h"
+#include "m_config.h"
+#include "playlist.h"
+#include "playlist_parser.h"
+#include "stream/stream.h"
+#include "demux/demux.h"
+#include "core/mp_msg.h"
+#include "core/path.h"
+
+
+#define BUF_STEP 1024
+
+#define WHITES " \n\r\t"
+
+typedef struct play_tree_parser {
+ struct stream *stream;
+ char *buffer,*iter,*line;
+ int buffer_size , buffer_end;
+ int keep;
+ struct playlist *pl;
+} play_tree_parser_t;
+
+static void
+strstrip(char* str) {
+ char* i;
+
+ if (str==NULL)
+ return;
+ for(i = str ; i[0] != '\0' && strchr(WHITES,i[0]) != NULL; i++)
+ /* NOTHING */;
+ if(i[0] != '\0') {
+ memmove(str,i,strlen(i) + 1);
+ for(i = str + strlen(str) - 1 ; strchr(WHITES,i[0]) != NULL; i--)
+ /* NOTHING */;
+ i[1] = '\0';
+ } else
+ str[0] = '\0';
+}
+
+static char*
+play_tree_parser_get_line(play_tree_parser_t* p) {
+ char *end,*line_end;
+ int r,resize = 0;
+
+ if(p->buffer == NULL) {
+ p->buffer = malloc(BUF_STEP);
+ p->buffer_size = BUF_STEP;
+ p->buffer[0] = 0;
+ p->iter = p->buffer;
+ }
+
+ if(p->stream->eof && (p->buffer_end == 0 || p->iter[0] == '\0'))
+ return NULL;
+
+ assert(p->buffer_end < p->buffer_size);
+ assert(!p->buffer[p->buffer_end]);
+ while(1) {
+
+ if(resize) {
+ char *tmp;
+ r = p->iter - p->buffer;
+ end = p->buffer + p->buffer_end;
+ if (p->buffer_size > INT_MAX - BUF_STEP)
+ break;
+ tmp = realloc(p->buffer, p->buffer_size + BUF_STEP);
+ if (!tmp)
+ break;
+ p->buffer = tmp;
+ p->iter = p->buffer + r;
+ p->buffer_size += BUF_STEP;
+ resize = 0;
+ }
+
+ if(p->buffer_size - p->buffer_end > 1 && ! p->stream->eof) {
+ r = stream_read(p->stream,p->buffer + p->buffer_end,p->buffer_size - p->buffer_end - 1);
+ if(r > 0) {
+ p->buffer_end += r;
+ assert(p->buffer_end < p->buffer_size);
+ p->buffer[p->buffer_end] = '\0';
+ while(strlen(p->buffer + p->buffer_end - r) != r)
+ p->buffer[p->buffer_end - r + strlen(p->buffer + p->buffer_end - r)] = '\n';
+ }
+ assert(!p->buffer[p->buffer_end]);
+ }
+
+ end = strchr(p->iter,'\n');
+ if(!end) {
+ if(p->stream->eof) {
+ end = p->buffer + p->buffer_end;
+ break;
+ }
+ resize = 1;
+ continue;
+ }
+ break;
+ }
+
+ line_end = (end > p->iter && *(end-1) == '\r') ? end-1 : end;
+ if(line_end - p->iter >= 0)
+ p->line = realloc(p->line, line_end - p->iter + 1);
+ else
+ return NULL;
+ if(line_end - p->iter > 0)
+ strncpy(p->line,p->iter,line_end - p->iter);
+ p->line[line_end - p->iter] = '\0';
+ if(end[0] != '\0')
+ end++;
+
+ if(!p->keep) {
+ if(end[0] != '\0') {
+ p->buffer_end -= end-p->iter;
+ memmove(p->buffer,end,p->buffer_end);
+ } else
+ p->buffer_end = 0;
+ p->buffer[p->buffer_end] = '\0';
+ p->iter = p->buffer;
+ } else
+ p->iter = end;
+
+ return p->line;
+}
+
+static void
+play_tree_parser_reset(play_tree_parser_t* p) {
+ p->iter = p->buffer;
+}
+
+static void
+play_tree_parser_stop_keeping(play_tree_parser_t* p) {
+ p->keep = 0;
+ if(p->iter && p->iter != p->buffer) {
+ p->buffer_end -= p->iter -p->buffer;
+ if(p->buffer_end)
+ memmove(p->buffer,p->iter,p->buffer_end);
+ p->buffer[p->buffer_end] = 0;
+ p->iter = p->buffer;
+ }
+}
+
+
+static bool parse_asx(play_tree_parser_t* p) {
+ int comments = 0,get_line = 1;
+ char* line = NULL;
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying asx...\n");
+
+ while(1) {
+ if(get_line) {
+ line = play_tree_parser_get_line(p);
+ if(!line)
+ return false;
+ strstrip(line);
+ if(line[0] == '\0')
+ continue;
+ }
+ if(!comments) {
+ if(line[0] != '<') {
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"First char isn't '<' but '%c'\n",line[0]);
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG3,"Buffer = [%s]\n",p->buffer);
+ return false;
+ } else if(strncmp(line,"<!--",4) == 0) { // Comments
+ comments = 1;
+ line += 4;
+ if(line[0] != '\0' && strlen(line) > 0)
+ get_line = 0;
+ } else if(strncasecmp(line,"<ASX",4) == 0) // We got an asx element
+ break;
+ else // We don't get an asx
+ return false;
+ } else { // Comments
+ char* c;
+ c = strchr(line,'-');
+ if(c) {
+ if (strncmp(c,"--!>",4) == 0) { // End of comments
+ comments = 0;
+ line = c+4;
+ if(line[0] != '\0') // There is some more data on this line : keep it
+ get_line = 0;
+
+ } else {
+ line = c+1; // Jump the -
+ if(line[0] != '\0') // Some more data
+ get_line = 0;
+ else // End of line
+ get_line = 1;
+ }
+ } else // No - on this line (or rest of line) : get next one
+ get_line = 1;
+ }
+ }
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected asx format\n");
+
+ // We have an asx : load it in memory and parse
+
+ while((line = play_tree_parser_get_line(p)) != NULL)
+ /* NOTHING */;
+
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG3,"Parsing asx file: [%s]\n",p->buffer);
+ return asx_parse(p->buffer,p->pl);
+}
+
+static char*
+pls_entry_get_value(char* line) {
+ char* i;
+
+ i = strchr(line,'=');
+ if(!i || i[1] == '\0')
+ return NULL;
+ else
+ return i+1;
+}
+
+typedef struct pls_entry {
+ char* file;
+ char* title;
+ char* length;
+} pls_entry_t;
+
+static int
+pls_read_entry(char* line,pls_entry_t** _e,int* _max_entry,char** val) {
+ int num,max_entry = (*_max_entry);
+ pls_entry_t* e = (*_e);
+ int limit = INT_MAX / sizeof(*e);
+ char* v;
+
+ v = pls_entry_get_value(line);
+ if(!v) {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
+ return -1;
+ }
+
+ num = atoi(line);
+ if(num <= 0 || num > limit) {
+ if (max_entry >= limit) {
+ mp_msg(MSGT_PLAYTREE, MSGL_WARN, "Too many index entries\n");
+ return -1;
+ }
+ num = max_entry+1;
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"No or invalid entry index in entry %s\nAssuming %d\n",line,num);
+ }
+ if(num > max_entry) {
+ e = realloc(e, num * sizeof(pls_entry_t));
+ if (!e)
+ return -1;
+ memset(&e[max_entry],0,(num-max_entry)*sizeof(pls_entry_t));
+ max_entry = num;
+ }
+ (*_e) = e;
+ (*_max_entry) = max_entry;
+ (*val) = v;
+
+ return num;
+}
+
+
+static bool parse_pls(play_tree_parser_t* p) {
+ char *line,*v;
+ pls_entry_t* entries = NULL;
+ int n_entries = 0,max_entry=0,num;
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying Winamp playlist...\n");
+ while((line = play_tree_parser_get_line(p))) {
+ strstrip(line);
+ if(strlen(line))
+ break;
+ }
+ if (!line)
+ return false;
+ if(strcasecmp(line,"[playlist]"))
+ return false;
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected Winamp playlist format\n");
+ play_tree_parser_stop_keeping(p);
+ line = play_tree_parser_get_line(p);
+ if(!line)
+ return false;
+ strstrip(line);
+ if(strncasecmp(line,"NumberOfEntries",15) == 0) {
+ v = pls_entry_get_value(line);
+ n_entries = atoi(v);
+ if(n_entries < 0)
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Invalid number of entries: very funny!!!\n");
+ else
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Playlist claims to have %d entries. Let's see.\n",n_entries);
+ line = play_tree_parser_get_line(p);
+ }
+
+ while(line) {
+ strstrip(line);
+ if(line[0] == '\0') {
+ line = play_tree_parser_get_line(p);
+ continue;
+ }
+ if(strncasecmp(line,"File",4) == 0) {
+ num = pls_read_entry(line+4,&entries,&max_entry,&v);
+ if(num < 0)
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
+ else
+ entries[num-1].file = strdup(v);
+ } else if(strncasecmp(line,"Title",5) == 0) {
+ num = pls_read_entry(line+5,&entries,&max_entry,&v);
+ if(num < 0)
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
+ else
+ entries[num-1].title = strdup(v);
+ } else if(strncasecmp(line,"Length",6) == 0) {
+ num = pls_read_entry(line+6,&entries,&max_entry,&v);
+ if(num < 0)
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
+ else {
+ char *end;
+ long val = strtol(v, &end, 10);
+ if (*end || (val <= 0 && val != -1))
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Invalid length value in entry %s\n",line);
+ else if (val > 0)
+ entries[num-1].length = strdup(v);
+ }
+ } else
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"Unknown entry type %s\n",line);
+ line = play_tree_parser_get_line(p);
+ }
+
+ for(num = 0; num < max_entry ; num++) {
+ if(entries[num].file == NULL)
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Entry %d don't have a file !!!!\n",num+1);
+ else {
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Adding entry %s\n",entries[num].file);
+ playlist_add_file(p->pl,entries[num].file);
+ if (entries[num].length)
+ playlist_entry_add_param(p->pl->last, bstr0("end"), bstr0(entries[num].length));
+ free(entries[num].file);
+ }
+ // When we have info in playtree we add these info
+ free(entries[num].title);
+ free(entries[num].length);
+ }
+
+ free(entries);
+ return true;
+}
+
+/*
+ Reference Ini-Format: Each entry is assumed a reference
+ */
+static bool parse_ref_ini(play_tree_parser_t* p) {
+ char *line,*v;
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying reference-ini playlist...\n");
+ if (!(line = play_tree_parser_get_line(p)))
+ return NULL;
+ strstrip(line);
+ if(strcasecmp(line,"[Reference]"))
+ return NULL;
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected reference-ini playlist format\n");
+ play_tree_parser_stop_keeping(p);
+ line = play_tree_parser_get_line(p);
+ if(!line)
+ return NULL;
+ while(line) {
+ strstrip(line);
+ if(strncasecmp(line,"Ref",3) == 0) {
+ v = pls_entry_get_value(line+3);
+ if(!v)
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
+ else
+ {
+ mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Adding entry %s\n",v);
+ playlist_add_file(p->pl, v);
+ }
+ }
+ line = play_tree_parser_get_line(p);
+ }
+
+ return true;
+}
+
+static bool parse_m3u(play_tree_parser_t* p) {
+ char* line;
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying extended m3u playlist...\n");
+ if (!(line = play_tree_parser_get_line(p)))
+ return NULL;
+ strstrip(line);
+ if(strcasecmp(line,"#EXTM3U"))
+ return NULL;
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected extended m3u playlist format\n");
+ play_tree_parser_stop_keeping(p);
+
+ while((line = play_tree_parser_get_line(p)) != NULL) {
+ strstrip(line);
+ if(line[0] == '\0')
+ continue;
+ /* EXTM3U files contain such lines:
+ * #EXTINF:<seconds>, <title>
+ * followed by a line with the filename
+ * for now we have no place to put that
+ * so we just skip that extra-info ::atmos
+ */
+ if(line[0] == '#') {
+#if 0 /* code functional */
+ if(strncasecmp(line,"#EXTINF:",8) == 0) {
+ mp_msg(MSGT_PLAYTREE,MSGL_INFO,"[M3U] Duration: %dsec Title: %s\n",
+ strtol(line+8,&line,10), line+2);
+ }
+#endif
+ continue;
+ }
+ playlist_add_file(p->pl, line);
+ }
+
+ return true;
+}
+
+static bool parse_smil(play_tree_parser_t* p) {
+ int entrymode=0;
+ char* line,source[512],*pos,*s_start,*s_end,*src_line;
+ int is_rmsmil = 0;
+ unsigned int npkt, ttlpkt;
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying smil playlist...\n");
+
+ // Check if smil
+ while((line = play_tree_parser_get_line(p)) != NULL) {
+ strstrip(line);
+ if(line[0] == '\0') // Ignore empties
+ continue;
+ if (strncasecmp(line,"<?xml",5)==0) // smil in xml
+ continue;
+ if (strncasecmp(line,"<!DOCTYPE smil",13)==0) // smil in xml
+ continue;
+ if (strncasecmp(line,"<smil",5)==0 || strncasecmp(line,"<?wpl",5)==0 ||
+ strncasecmp(line,"(smil-document",14)==0)
+ break; // smil header found
+ else
+ return NULL; //line not smil exit
+ }
+
+ if (!line) return NULL;
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected smil playlist format\n");
+ play_tree_parser_stop_keeping(p);
+
+ if (strncasecmp(line,"(smil-document",14)==0) {
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Special smil-over-realrtsp playlist header\n");
+ is_rmsmil = 1;
+ if (sscanf(line, "(smil-document (ver 1.0)(npkt %u)(ttlpkt %u", &npkt, &ttlpkt) != 2) {
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: header parsing failure, assuming single packet.\n");
+ npkt = ttlpkt = 1;
+ }
+ if (ttlpkt == 0 || npkt > ttlpkt) {
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: bad packet counters (npkk = %u, ttlpkt = %u), assuming single packet.\n",
+ npkt, ttlpkt);
+ npkt = ttlpkt = 1;
+ }
+ }
+
+ //Get entries from smil
+ src_line = line;
+ line = NULL;
+ do {
+ strstrip(src_line);
+ free(line);
+ line = NULL;
+ /* If we're parsing smil over realrtsp and this is not the last packet and
+ * this is the last line in the packet (terminating with ") ) we must get
+ * the next line, strip the header, and concatenate it to the current line.
+ */
+ if (is_rmsmil && npkt != ttlpkt && strstr(src_line,"\")")) {
+ char *payload;
+
+ line = strdup(src_line);
+ if(!(src_line = play_tree_parser_get_line(p))) {
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: can't get line from packet %u/%u.\n", npkt, ttlpkt);
+ break;
+ }
+ strstrip(src_line);
+ // Skip header, packet starts after "
+ if(!(payload = strchr(src_line,'\"'))) {
+ mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: can't find start of packet, using complete line.\n");
+ payload = src_line;
+ } else
+ payload++;
+ // Skip ") at the end of the last line from the current packet
+ line[strlen(line)-2] = 0;
+ line = realloc(line, strlen(line)+strlen(payload)+1);
+ strcat (line, payload);
+ npkt++;
+ } else
+ line = strdup(src_line);
+ /* Unescape \" to " for smil-over-rtsp */
+ if (is_rmsmil && line[0] != '\0') {
+ int i, j;
+
+ for (i = 0; i < strlen(line); i++)
+ if (line[i] == '\\' && line[i+1] == '"')
+ for (j = i; line[j]; j++)
+ line[j] = line[j+1];
+ }
+ pos = line;
+ while (pos) {
+ if (!entrymode) { // all entries filled so far
+ while ((pos=strchr(pos, '<'))) {
+ if (strncasecmp(pos,"<video",6)==0 || strncasecmp(pos,"<audio",6)==0 || strncasecmp(pos,"<media",6)==0) {
+ entrymode=1;
+ break; // Got a valid tag, exit '<' search loop
+ }
+ pos++;
+ }
+ }
+ if (entrymode) { //Entry found but not yet filled
+ pos = strstr(pos,"src="); // Is source present on this line
+ if (pos != NULL) {
+ entrymode=0;
+ if (pos[4] != '"' && pos[4] != '\'') {
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Unknown delimiter %c in source line %s\n", pos[4], line);
+ break;
+ }
+ s_start=pos+5;
+ s_end=strchr(s_start,pos[4]);
+ if (s_end == NULL) {
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Error parsing this source line %s\n",line);
+ break;
+ }
+ if (s_end-s_start> 511) {
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Cannot store such a large source %s\n",line);
+ break;
+ }
+ strncpy(source,s_start,s_end-s_start);
+ source[(s_end-s_start)]='\0'; // Null terminate
+ playlist_add_file(p->pl, source);
+ pos = s_end;
+ }
+ }
+ }
+ } while((src_line = play_tree_parser_get_line(p)) != NULL);
+
+ free(line);
+ return true;
+}
+
+static bool parse_textplain(play_tree_parser_t* p) {
+ char* line;
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying plaintext playlist...\n");
+ play_tree_parser_stop_keeping(p);
+
+ while((line = play_tree_parser_get_line(p)) != NULL) {
+ strstrip(line);
+ if(line[0] == '\0' || line[0] == '#' || (line[0] == '/' && line[1] == '/'))
+ continue;
+
+ playlist_add_file(p->pl,line);
+ }
+
+ return true;
+}
+
+/**
+ * \brief decode the base64 used in nsc files
+ * \param in input string, 0-terminated
+ * \param buf output buffer, must point to memory suitable for realloc,
+ * will be NULL on failure.
+ * \return decoded length in bytes
+ */
+static int decode_nsc_base64(char *in, char **buf) {
+ int i, j, n;
+ if (in[0] != '0' || in[1] != '2')
+ goto err_out;
+ in += 2; // skip prefix
+ if (strlen(in) < 16) // error out if nothing to decode
+ goto err_out;
+ in += 12; // skip encoded string length
+ n = strlen(in) / 4;
+ *buf = realloc(*buf, n * 3);
+ for (i = 0; i < n; i++) {
+ uint8_t c[4];
+ for (j = 0; j < 4; j++) {
+ c[j] = in[4 * i + j];
+ if (c[j] >= '0' && c[j] <= '9') c[j] += 0 - '0';
+ else if (c[j] >= 'A' && c[j] <= 'Z') c[j] += 10 - 'A';
+ else if (c[j] >= 'a' && c[j] <= 'z') c[j] += 36 - 'a';
+ else if (c[j] == '{') c[j] = 62;
+ else if (c[j] == '}') c[j] = 63;
+ else {
+ mp_msg(MSGT_PLAYTREE, MSGL_ERR, "Invalid character %c (0x%02"PRIx8")\n", c[j], c[j]);
+ goto err_out;
+ }
+ }
+ (*buf)[3 * i] = (c[0] << 2) | (c[1] >> 4);
+ (*buf)[3 * i + 1] = (c[1] << 4) | (c[2] >> 2);
+ (*buf)[3 * i + 2] = (c[2] << 6) | c[3];
+ }
+ return 3 * n;
+err_out:
+ free(*buf);
+ *buf = NULL;
+ return 0;
+}
+
+/**
+ * \brief "converts" utf16 to ascii by just discarding every second byte
+ * \param buf buffer to convert
+ * \param len lenght of buffer, must be > 0
+ */
+static void utf16_to_ascii(char *buf, int len) {
+ int i;
+ if (len <= 0) return;
+ for (i = 0; i < len / 2; i++)
+ buf[i] = buf[i * 2];
+ buf[i] = 0; // just in case
+}
+
+static bool parse_nsc(play_tree_parser_t* p) {
+ char *line, *addr = NULL, *url, *unicast_url = NULL;
+ int port = 0;
+
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying nsc playlist...\n");
+ while((line = play_tree_parser_get_line(p)) != NULL) {
+ strstrip(line);
+ if(!line[0]) // Ignore empties
+ continue;
+ if (strncasecmp(line,"[Address]", 9) == 0)
+ break; // nsc header found
+ else
+ return false;
+ }
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected nsc playlist format\n");
+ play_tree_parser_stop_keeping(p);
+ while ((line = play_tree_parser_get_line(p)) != NULL) {
+ strstrip(line);
+ if (!line[0])
+ continue;
+ if (strncasecmp(line, "Unicast URL=", 12) == 0) {
+ int len = decode_nsc_base64(&line[12], &unicast_url);
+ if (len <= 0)
+ mp_msg(MSGT_PLAYTREE, MSGL_WARN, "[nsc] Unsupported Unicast URL encoding\n");
+ else
+ utf16_to_ascii(unicast_url, len);
+ } else if (strncasecmp(line, "IP Address=", 11) == 0) {
+ int len = decode_nsc_base64(&line[11], &addr);
+ if (len <= 0)
+ mp_msg(MSGT_PLAYTREE, MSGL_WARN, "[nsc] Unsupported IP Address encoding\n");
+ else
+ utf16_to_ascii(addr, len);
+ } else if (strncasecmp(line, "IP Port=", 8) == 0) {
+ port = strtol(&line[8], NULL, 0);
+ }
+ }
+
+ bool success = false;
+
+ if (unicast_url)
+ url = strdup(unicast_url);
+ else if (addr && port) {
+ url = malloc(strlen(addr) + 7 + 20 + 1);
+ sprintf(url, "http://%s:%i", addr, port);
+ } else
+ goto err_out;
+
+ playlist_add_file(p->pl, url);
+ free(url);
+ success = true;
+err_out:
+ free(addr);
+ free(unicast_url);
+ return success;
+}
+
+struct playlist *playlist_parse_file(const char *file)
+{
+ stream_t *stream = stream_open(file, NULL);
+ if(!stream) {
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,
+ "Error while opening playlist file %s: %s\n",
+ file, strerror(errno));
+ return false;
+ }
+
+ mp_msg(MSGT_PLAYTREE, MSGL_V,
+ "Parsing playlist file %s...\n", file);
+
+ struct playlist *ret = playlist_parse(stream);
+ free_stream(stream);
+
+ playlist_add_base_path(ret, mp_dirname(file));
+
+ return ret;
+
+}
+
+typedef bool (*parser_fn)(play_tree_parser_t *);
+static const parser_fn pl_parsers[] = {
+ parse_asx,
+ parse_pls,
+ parse_m3u,
+ parse_ref_ini,
+ parse_smil,
+ parse_nsc,
+ parse_textplain
+};
+
+
+static struct playlist *do_parse(struct stream* stream, bool forced)
+{
+ play_tree_parser_t p = {
+ .stream = stream,
+ .pl = talloc_zero(NULL, struct playlist),
+ .keep = 1,
+ };
+
+ bool success = false;
+ if (play_tree_parser_get_line(&p) != NULL) {
+ for (int n = 0; n < sizeof(pl_parsers) / sizeof(pl_parsers[0]); n++) {
+ play_tree_parser_reset(&p);
+ if (pl_parsers[n] == parse_textplain && !forced)
+ break;
+ if (pl_parsers[n](&p)) {
+ success = true;
+ break;
+ }
+ }
+ }
+
+ if(success)
+ mp_msg(MSGT_PLAYTREE,MSGL_V,"Playlist successfully parsed\n");
+ else {
+ mp_msg(MSGT_PLAYTREE,((forced==1)?MSGL_ERR:MSGL_V),"Error while parsing playlist\n");
+ talloc_free(p.pl);
+ p.pl = NULL;
+ }
+
+ if (p.pl && !p.pl->first)
+ mp_msg(MSGT_PLAYTREE,((forced==1)?MSGL_WARN:MSGL_V),"Warning: empty playlist\n");
+
+ return p.pl;
+}
+
+struct playlist *playlist_parse(struct stream* stream)
+{
+ return do_parse(stream, true);
+}
+
+struct playlist *playlist_probe_and_parse(struct stream* stream)
+{
+ return do_parse(stream, false);
+}
diff --git a/mpvcore/playlist_parser.h b/mpvcore/playlist_parser.h
new file mode 100644
index 0000000000..3ceb95c460
--- /dev/null
+++ b/mpvcore/playlist_parser.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_PLAYLISTPARSER_H
+#define MPLAYER_PLAYLISTPARSER_H
+
+#include <stdbool.h>
+
+struct stream;
+struct playlist;
+
+// Parse the given stream as playlist. Append entries to pl. Return whether
+// there was an error when parsing.
+// deep = Parser depth. Some formats allow including other files,
+struct playlist *playlist_parse(struct stream* stream);
+struct playlist *playlist_probe_and_parse(struct stream* stream);
+struct playlist *playlist_parse_file(const char *file);
+
+#endif
diff --git a/mpvcore/resolve.h b/mpvcore/resolve.h
new file mode 100644
index 0000000000..91684df250
--- /dev/null
+++ b/mpvcore/resolve.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef MP_RESOLVE_H
+#define MP_RESOLVE_H
+
+struct MPContext;
+struct MPOpts;
+
+struct mp_resolve_result {
+ char *url;
+ char *title;
+
+ struct mp_resolve_src **srcs;
+ int num_srcs;
+
+ double start_time;
+
+ struct mp_resolve_sub **subs;
+ int num_subs;
+
+ struct playlist *playlist;
+};
+
+struct mp_resolve_src {
+ char *url;
+ char *encid; // indicates quality level, contents are libquvi specific
+};
+
+struct mp_resolve_sub {
+ char *url;
+ char *data;
+ char *lang;
+};
+
+struct mp_resolve_result *mp_resolve_quvi(const char *url, struct MPOpts *opts);
+
+#endif
diff --git a/mpvcore/resolve_quvi.c b/mpvcore/resolve_quvi.c
new file mode 100644
index 0000000000..5870335811
--- /dev/null
+++ b/mpvcore/resolve_quvi.c
@@ -0,0 +1,93 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <quvi/quvi.h>
+
+#include "talloc.h"
+#include "core/mp_msg.h"
+#include "core/options.h"
+#include "resolve.h"
+
+struct mp_resolve_result *mp_resolve_quvi(const char *url, struct MPOpts *opts)
+{
+ QUVIcode rc;
+ bool mp_url = false;
+
+ quvi_t q;
+ rc = quvi_init(&q);
+ if (rc != QUVI_OK)
+ return NULL;
+
+ if (!strncmp(url, "mp_", 3)) {
+ url += 3;
+ mp_url = true;
+ }
+
+ // Don't try to use quvi on an URL that's not directly supported, since
+ // quvi will do a network access anyway in order to check for HTTP
+ // redirections etc.
+ // The documentation says this will fail on "shortened" URLs.
+ if (quvi_supported(q, (char *)url) != QUVI_OK) {
+ quvi_close(&q);
+ return NULL;
+ }
+
+ mp_msg(MSGT_OPEN, MSGL_INFO, "[quvi] Checking URL...\n");
+
+ // Can use quvi_query_formats() to get a list of formats like this:
+ // "fmt05_240p|fmt18_360p|fmt34_360p|fmt35_480p|fmt43_360p|fmt44_480p"
+ // (This example is youtube specific.)
+ // That call requires an extra net access. quvi_next_media_url() doesn't
+ // seem to do anything useful. So we can't really do anything useful
+ // except pass through the user's format setting.
+ quvi_setopt(q, QUVIOPT_FORMAT, opts->quvi_format
+ ? opts->quvi_format : "best");
+
+ quvi_media_t m;
+ rc = quvi_parse(q, (char *)url, &m);
+ if (rc != QUVI_OK) {
+ mp_msg(MSGT_OPEN, MSGL_ERR, "[quvi] %s\n", quvi_strerror(q, rc));
+ quvi_close(&q);
+ return NULL;
+ }
+
+ struct mp_resolve_result *result
+ = talloc_zero(NULL, struct mp_resolve_result);
+
+ char *val;
+
+ if (quvi_getprop(m, QUVIPROP_MEDIAURL, &val) == QUVI_OK) {
+ if (mp_url)
+ result->url = talloc_asprintf(result, "mp_%s", val);
+ else
+ result->url = talloc_strdup(result, val);
+ }
+
+ if (quvi_getprop(m, QUVIPROP_PAGETITLE, &val) == QUVI_OK)
+ result->title = talloc_strdup(result, val);
+
+ quvi_parse_close(&m);
+ quvi_close(&q);
+
+ if (!result->url) {
+ talloc_free(result);
+ result = NULL;
+ }
+
+ return result;
+}
diff --git a/mpvcore/resolve_quvi9.c b/mpvcore/resolve_quvi9.c
new file mode 100644
index 0000000000..f6e6e8b94f
--- /dev/null
+++ b/mpvcore/resolve_quvi9.c
@@ -0,0 +1,150 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdbool.h>
+#include <assert.h>
+
+#include <quvi.h>
+
+#include "talloc.h"
+#include "core/mp_msg.h"
+#include "core/options.h"
+#include "core/playlist.h"
+#include "resolve.h"
+
+static bool mp_quvi_ok(quvi_t q)
+{
+ if (!quvi_ok(q)) {
+ mp_msg(MSGT_OPEN, MSGL_ERR, "[quvi] %s\n", quvi_errmsg(q));
+ return false;
+ }
+ return true;
+}
+
+struct mp_resolve_result *mp_resolve_quvi(const char *url, struct MPOpts *opts)
+{
+ int mode = QUVI_SUPPORTS_MODE_OFFLINE;
+
+ quvi_t q = quvi_new();
+ if (!quvi_ok(q)) {
+ mp_msg(MSGT_OPEN, MSGL_ERR, "[quvi] %s\n", quvi_errmsg(q));
+
+ quvi_free(q);
+ return NULL;
+ }
+
+ struct mp_resolve_result *res = talloc_zero(NULL, struct mp_resolve_result);
+
+ if (quvi_supports(q, url, mode, QUVI_SUPPORTS_TYPE_PLAYLIST)) {
+ mp_msg(MSGT_OPEN, MSGL_INFO, "[quvi] Checking playlist...\n");
+ quvi_playlist_t qp = quvi_playlist_new(q, url);
+ if (mp_quvi_ok(q)) {
+ res->playlist = talloc_zero(res, struct playlist);
+ while (quvi_playlist_media_next(qp)) {
+ char *entry = NULL;
+ quvi_playlist_get(qp, QUVI_PLAYLIST_MEDIA_PROPERTY_URL, &entry);
+ if (entry)
+ playlist_add_file(res->playlist, entry);
+ }
+ }
+ quvi_playlist_free(qp);
+ }
+
+ if (quvi_supports(q, url, mode, QUVI_SUPPORTS_TYPE_MEDIA)) {
+ mp_msg(MSGT_OPEN, MSGL_INFO, "[quvi] Checking URL...\n");
+ quvi_media_t media = quvi_media_new(q, url);
+ if (mp_quvi_ok(q)) {
+ char *format = opts->quvi_format ? opts->quvi_format : "best";
+ bool use_default = strcmp(format, "default") == 0;
+ if (!use_default)
+ quvi_media_stream_select(media, format);
+
+ char *val = NULL;
+ quvi_media_get(media, QUVI_MEDIA_STREAM_PROPERTY_URL, &val);
+ res->url = talloc_strdup(res, val);
+
+ val = NULL;
+ quvi_media_get(media, QUVI_MEDIA_PROPERTY_TITLE, &val);
+ res->title = talloc_strdup(res, val);
+
+ double start = 0;
+ quvi_media_get(media, QUVI_MEDIA_PROPERTY_START_TIME_MS, &start);
+ res->start_time = start / 1000.0;
+
+ quvi_media_stream_reset(media);
+ while (quvi_media_stream_next(media)) {
+ char *entry = NULL, *id = NULL;
+ quvi_media_get(media, QUVI_MEDIA_STREAM_PROPERTY_URL, &entry);
+ quvi_media_get(media, QUVI_MEDIA_STREAM_PROPERTY_ID, &id);
+ if (entry) {
+ struct mp_resolve_src *src = talloc_ptrtype(res, src);
+ *src = (struct mp_resolve_src) {
+ .url = talloc_strdup(src, entry),
+ .encid = talloc_strdup(src, id),
+ };
+ MP_TARRAY_APPEND(res, res->srcs, res->num_srcs, src);
+ talloc_steal(res->srcs, src);
+ }
+ }
+
+ }
+ quvi_media_free(media);
+ }
+
+ if (quvi_supports(q, url, mode, QUVI_SUPPORTS_TYPE_SUBTITLE)) {
+ mp_msg(MSGT_OPEN, MSGL_INFO, "[quvi] Getting subtitles...\n");
+ quvi_subtitle_t qsub = quvi_subtitle_new(q, url);
+ if (mp_quvi_ok(q)) {
+ while (1) {
+ quvi_subtitle_type_t qst = quvi_subtitle_type_next(qsub);
+ if (!qst)
+ break;
+ while (1) {
+ quvi_subtitle_lang_t qsl = quvi_subtitle_lang_next(qst);
+ if (!qsl)
+ break;
+ char *lang;
+ quvi_subtitle_lang_get(qsl, QUVI_SUBTITLE_LANG_PROPERTY_ID,
+ &lang);
+ // Let quvi convert the subtitle to SRT.
+ quvi_subtitle_export_t qse =
+ quvi_subtitle_export_new(qsl, "srt");
+ if (mp_quvi_ok(q)) {
+ const char *subdata = quvi_subtitle_export_data(qse);
+ struct mp_resolve_sub *sub = talloc_ptrtype(res, sub);
+ *sub = (struct mp_resolve_sub) {
+ .lang = talloc_strdup(sub, lang),
+ .data = talloc_strdup(sub, subdata),
+ };
+ MP_TARRAY_APPEND(res, res->subs, res->num_subs, sub);
+ talloc_steal(res->subs, sub);
+ }
+ quvi_subtitle_export_free(qse);
+ }
+ }
+ }
+ quvi_subtitle_free(qsub);
+ }
+
+ quvi_free(q);
+
+ if (!res->url && (!res->playlist || !res->playlist->first)) {
+ talloc_free(res);
+ res = NULL;
+ }
+ return res;
+}
diff --git a/mpvcore/screenshot.c b/mpvcore/screenshot.c
new file mode 100644
index 0000000000..ea2fe7a3c9
--- /dev/null
+++ b/mpvcore/screenshot.c
@@ -0,0 +1,390 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "config.h"
+
+#include "osdep/io.h"
+
+#include "talloc.h"
+#include "core/screenshot.h"
+#include "core/mp_core.h"
+#include "core/command.h"
+#include "core/bstr.h"
+#include "core/mp_msg.h"
+#include "core/mp_osd.h"
+#include "core/path.h"
+#include "video/mp_image.h"
+#include "video/decode/dec_video.h"
+#include "video/filter/vf.h"
+#include "video/out/vo.h"
+#include "video/image_writer.h"
+#include "sub/sub.h"
+
+#include "video/csputils.h"
+
+#define MODE_FULL_WINDOW 1
+#define MODE_SUBTITLES 2
+
+typedef struct screenshot_ctx {
+ struct MPContext *mpctx;
+
+ int mode;
+ bool each_frame;
+ bool osd;
+
+ int frameno;
+} screenshot_ctx;
+
+void screenshot_init(struct MPContext *mpctx)
+{
+ mpctx->screenshot_ctx = talloc(mpctx, screenshot_ctx);
+ *mpctx->screenshot_ctx = (screenshot_ctx) {
+ .mpctx = mpctx,
+ .frameno = 1,
+ };
+}
+
+#define SMSG_OK 0
+#define SMSG_ERR 1
+
+static void screenshot_msg(screenshot_ctx *ctx, int status, const char *msg,
+ ...) PRINTF_ATTRIBUTE(3,4);
+
+static void screenshot_msg(screenshot_ctx *ctx, int status, const char *msg,
+ ...)
+{
+ va_list ap;
+ char *s;
+
+ va_start(ap, msg);
+ s = talloc_vasprintf(NULL, msg, ap);
+ va_end(ap);
+
+ mp_msg(MSGT_CPLAYER, status == SMSG_ERR ? MSGL_ERR : MSGL_INFO, "%s\n", s);
+ if (ctx->osd) {
+ set_osd_tmsg(ctx->mpctx, OSD_MSG_TEXT, 1, ctx->mpctx->opts->osd_duration,
+ "%s", s);
+ }
+
+ talloc_free(s);
+}
+
+static char *stripext(void *talloc_ctx, const char *s)
+{
+ const char *end = strrchr(s, '.');
+ if (!end)
+ end = s + strlen(s);
+ return talloc_asprintf(talloc_ctx, "%.*s", (int)(end - s), s);
+}
+
+#ifdef _WIN32
+#define ILLEGAL_FILENAME_CHARS "?\"/\\<>*|:"
+#else
+#define ILLEGAL_FILENAME_CHARS "/"
+#endif
+
+// Replace all characters disallowed in filenames with '_' and return the newly
+// allocated result string.
+static char *sanitize_filename(void *talloc_ctx, const char *s)
+{
+ char *res = talloc_strdup(talloc_ctx, s);
+ char *cur = res;
+ while (*cur) {
+ if (strchr(ILLEGAL_FILENAME_CHARS, *cur) || ((unsigned char)*cur) < 32)
+ *cur = '_';
+ cur++;
+ }
+ return res;
+}
+
+static void append_filename(char **s, const char *f)
+{
+ char *append = sanitize_filename(NULL, f);
+ *s = talloc_strdup_append(*s, append);
+ talloc_free(append);
+}
+
+static char *create_fname(struct MPContext *mpctx, char *template,
+ const char *file_ext, int *sequence, int *frameno)
+{
+ char *res = talloc_strdup(NULL, ""); //empty string, non-NULL context
+
+ time_t raw_time = time(NULL);
+ struct tm *local_time = localtime(&raw_time);
+
+ if (!template || *template == '\0')
+ template = "shot%n";
+
+ for (;;) {
+ char *next = strchr(template, '%');
+ if (!next)
+ break;
+ res = talloc_strndup_append(res, template, next - template);
+ template = next + 1;
+ char fmt = *template++;
+ switch (fmt) {
+ case '#':
+ case '0':
+ case 'n': {
+ int digits = '4';
+ if (fmt == '#') {
+ if (!*sequence) {
+ *frameno = 1;
+ }
+ fmt = *template++;
+ }
+ if (fmt == '0') {
+ digits = *template++;
+ if (digits < '0' || digits > '9')
+ goto error_exit;
+ fmt = *template++;
+ }
+ if (fmt != 'n')
+ goto error_exit;
+ char fmtstr[] = {'%', '0', digits, 'd', '\0'};
+ res = talloc_asprintf_append(res, fmtstr, *frameno);
+ if (*frameno < 100000 - 1) {
+ (*frameno) += 1;
+ (*sequence) += 1;
+ }
+ break;
+ }
+ case 'f':
+ case 'F': {
+ char *video_file = mp_basename(mpctx->filename);
+ if (video_file) {
+ char *name = video_file;
+ if (fmt == 'F')
+ name = stripext(res, video_file);
+ append_filename(&res, name);
+ }
+ break;
+ }
+ case 'p':
+ case 'P': {
+ char *t = mp_format_time(get_current_time(mpctx), fmt == 'P');
+ append_filename(&res, t);
+ talloc_free(t);
+ break;
+ }
+ case 't': {
+ char tfmt = *template;
+ if (!tfmt)
+ goto error_exit;
+ template++;
+ char fmtstr[] = {'%', tfmt, '\0'};
+ char buffer[80];
+ if (strftime(buffer, sizeof(buffer), fmtstr, local_time) == 0)
+ buffer[0] = '\0';
+ append_filename(&res, buffer);
+ break;
+ }
+ case '{': {
+ char *end = strchr(template, '}');
+ if (!end)
+ goto error_exit;
+ struct bstr prop = bstr_splice(bstr0(template), 0, end - template);
+ char *tmp = talloc_asprintf(NULL, "${%.*s}", BSTR_P(prop));
+ char *s = mp_property_expand_string(mpctx, tmp);
+ talloc_free(tmp);
+ if (s)
+ append_filename(&res, s);
+ talloc_free(s);
+ template = end + 1;
+ break;
+ }
+ case '%':
+ res = talloc_strdup_append(res, "%");
+ break;
+ default:
+ goto error_exit;
+ }
+ }
+
+ res = talloc_strdup_append(res, template);
+ return talloc_asprintf_append(res, ".%s", file_ext);
+
+error_exit:
+ talloc_free(res);
+ return NULL;
+}
+
+static char *gen_fname(screenshot_ctx *ctx, const char *file_ext)
+{
+ int sequence = 0;
+ for (;;) {
+ int prev_sequence = sequence;
+ char *fname = create_fname(ctx->mpctx,
+ ctx->mpctx->opts->screenshot_template,
+ file_ext,
+ &sequence,
+ &ctx->frameno);
+
+ if (!fname) {
+ screenshot_msg(ctx, SMSG_ERR, "Invalid screenshot filename "
+ "template! Fix or remove the --screenshot-template "
+ "option.");
+ return NULL;
+ }
+
+ if (!mp_path_exists(fname))
+ return fname;
+
+ if (sequence == prev_sequence) {
+ screenshot_msg(ctx, SMSG_ERR, "Can't save screenshot, file '%s' "
+ "already exists!", fname);
+ talloc_free(fname);
+ return NULL;
+ }
+
+ talloc_free(fname);
+ }
+}
+
+static void add_subs(struct MPContext *mpctx, struct mp_image *image)
+{
+ int d_w = image->display_w ? image->display_w : image->w;
+ int d_h = image->display_h ? image->display_h : image->h;
+
+ double sar = (double)image->w / image->h;
+ double dar = (double)d_w / d_h;
+ struct mp_osd_res res = {
+ .w = image->w,
+ .h = image->h,
+ .display_par = sar / dar,
+ .video_par = dar / sar,
+ };
+
+ osd_draw_on_image(mpctx->osd, res, mpctx->osd->vo_pts,
+ OSD_DRAW_SUB_ONLY, image);
+}
+
+static void screenshot_save(struct MPContext *mpctx, struct mp_image *image)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ struct image_writer_opts *opts = mpctx->opts->screenshot_image_opts;
+
+ char *filename = gen_fname(ctx, image_writer_file_ext(opts));
+ if (filename) {
+ screenshot_msg(ctx, SMSG_OK, "Screenshot: '%s'", filename);
+ if (!write_image(image, opts, filename))
+ screenshot_msg(ctx, SMSG_ERR, "Error writing screenshot!");
+ talloc_free(filename);
+ }
+}
+
+static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
+{
+ struct mp_image *image = NULL;
+ if (mpctx->video_out && mpctx->video_out->config_ok) {
+ if (mode == MODE_SUBTITLES && mpctx->osd->render_subs_in_filter)
+ mode = 0;
+
+ struct voctrl_screenshot_args args =
+ { .full_window = (mode == MODE_FULL_WINDOW) };
+
+ struct vf_instance *vfilter = mpctx->sh_video->vfilter;
+ vfilter->control(vfilter, VFCTRL_SCREENSHOT, &args);
+
+ if (!args.out_image)
+ vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &args);
+
+ image = args.out_image;
+ if (image) {
+ if (mode == MODE_SUBTITLES && !args.has_osd)
+ add_subs(mpctx, image);
+ }
+ }
+ return image;
+}
+
+void screenshot_to_file(struct MPContext *mpctx, const char *filename, int mode,
+ bool osd)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+ struct image_writer_opts opts = *mpctx->opts->screenshot_image_opts;
+ bool old_osd = ctx->osd;
+ ctx->osd = osd;
+
+ if (mp_path_exists(filename)) {
+ screenshot_msg(ctx, SMSG_ERR, "Screenshot: file '%s' already exists.",
+ filename);
+ goto end;
+ }
+ char *ext = mp_splitext(filename, NULL);
+ if (ext)
+ opts.format = ext + 1; // omit '.'
+ struct mp_image *image = screenshot_get(mpctx, mode);
+ if (!image) {
+ screenshot_msg(ctx, SMSG_ERR, "Taking screenshot failed.");
+ goto end;
+ }
+ screenshot_msg(ctx, SMSG_OK, "Screenshot: '%s'", filename);
+ if (!write_image(image, &opts, filename))
+ screenshot_msg(ctx, SMSG_ERR, "Error writing screenshot!");
+ talloc_free(image);
+
+end:
+ ctx->osd = old_osd;
+}
+
+void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
+ bool osd)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ if (mode == MODE_SUBTITLES && mpctx->osd->render_subs_in_filter)
+ mode = 0;
+
+ if (each_frame) {
+ ctx->each_frame = !ctx->each_frame;
+ if (!ctx->each_frame)
+ return;
+ } else {
+ ctx->each_frame = false;
+ }
+
+ ctx->mode = mode;
+ ctx->osd = osd;
+
+ struct mp_image *image = screenshot_get(mpctx, mode);
+
+ if (image) {
+ screenshot_save(mpctx, image);
+ } else {
+ screenshot_msg(ctx, SMSG_ERR, "Taking screenshot failed.");
+ }
+
+ talloc_free(image);
+}
+
+void screenshot_flip(struct MPContext *mpctx)
+{
+ screenshot_ctx *ctx = mpctx->screenshot_ctx;
+
+ if (!ctx->each_frame)
+ return;
+
+ ctx->each_frame = false;
+ screenshot_request(mpctx, ctx->mode, true, ctx->osd);
+}
diff --git a/mpvcore/screenshot.h b/mpvcore/screenshot.h
new file mode 100644
index 0000000000..1b12ac9b73
--- /dev/null
+++ b/mpvcore/screenshot.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_SCREENSHOT_H
+#define MPLAYER_SCREENSHOT_H
+
+#include <stdbool.h>
+
+struct MPContext;
+
+// One time initialization at program start.
+void screenshot_init(struct MPContext *mpctx);
+
+// Request a taking & saving a screenshot of the currently displayed frame.
+// mode: 0: -, 1: save the actual output window contents, 2: with subtitles.
+// each_frame: If set, this toggles per-frame screenshots, exactly like the
+// screenshot slave command (MP_CMD_SCREENSHOT).
+// osd: show status on OSD
+void screenshot_request(struct MPContext *mpctx, int mode, bool each_frame,
+ bool osd);
+
+// filename: where to store the screenshot; doesn't try to find an alternate
+// name if the file already exists
+// mode, osd: same as in screenshot_request()
+void screenshot_to_file(struct MPContext *mpctx, const char *filename, int mode,
+ bool osd);
+
+// Called by the playback core code when a new frame is displayed.
+void screenshot_flip(struct MPContext *mpctx);
+
+#endif /* MPLAYER_SCREENSHOT_H */
diff --git a/mpvcore/timeline/tl_cue.c b/mpvcore/timeline/tl_cue.c
new file mode 100644
index 0000000000..3c4a997982
--- /dev/null
+++ b/mpvcore/timeline/tl_cue.c
@@ -0,0 +1,419 @@
+/*
+ * 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 <ctype.h>
+
+#include "talloc.h"
+
+#include "core/mp_core.h"
+#include "core/mp_msg.h"
+#include "demux/demux.h"
+#include "core/path.h"
+#include "core/bstr.h"
+#include "core/mp_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->opts);
+ if (!s)
+ return false;
+ struct demuxer *d = demux_open(s, NULL, NULL, mpctx->opts);
+ // 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_msg(MSGT_CPLAYER, MSGL_WARN, "CUE: Opening as BIN file!\n");
+ d = demux_open(s, "rawaudio", NULL, mpctx->opts);
+ }
+ if (d) {
+ add_source(mpctx, d);
+ return true;
+ }
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "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_msg(MSGT_CPLAYER, MSGL_WARN,
+ "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_msg(MSGT_CPLAYER, MSGL_WARN, "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_msg(MSGT_CPLAYER, MSGL_ERR, "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_msg(MSGT_CPLAYER, MSGL_ERR, "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_msg(MSGT_CPLAYER, MSGL_ERR, "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;
+ }
+ }
+
+ add_source(mpctx, mpctx->demuxer);
+
+ 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 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_msg(MSGT_CPLAYER, MSGL_WARN,
+ "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 chapter) {
+ .start = 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;
+
+out:
+ talloc_free(ctx);
+}
diff --git a/mpvcore/timeline/tl_edl.c b/mpvcore/timeline/tl_edl.c
new file mode 100644
index 0000000000..4afb346dae
--- /dev/null
+++ b/mpvcore/timeline/tl_edl.c
@@ -0,0 +1,392 @@
+/*
+ * 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 <ctype.h>
+
+#include "talloc.h"
+
+#include "core/mp_core.h"
+#include "core/mp_msg.h"
+#include "demux/demux.h"
+#include "core/path.h"
+#include "core/bstr.h"
+#include "core/mp_common.h"
+#include "stream/stream.h"
+
+
+struct edl_source {
+ struct bstr id;
+ char *filename;
+ int lineno;
+};
+
+struct edl_time {
+ int64_t start;
+ int64_t end;
+ bool implied_start;
+ bool implied_end;
+};
+
+struct edl_part {
+ struct edl_time tl;
+ struct edl_time src;
+ int64_t duration;
+ int id;
+ int lineno;
+};
+
+static int find_edl_source(struct edl_source *sources, int num_sources,
+ struct bstr name)
+{
+ for (int i = 0; i < num_sources; i++)
+ if (!bstrcmp(sources[i].id, name))
+ return i;
+ return -1;
+}
+
+void build_edl_timeline(struct MPContext *mpctx)
+{
+ const struct bstr file_prefix = bstr0("<");
+ void *tmpmem = talloc_new(NULL);
+
+ struct bstr *lines = bstr_splitlines(tmpmem, mpctx->demuxer->file_contents);
+ int linec = MP_TALLOC_ELEMS(lines);
+ struct bstr header = bstr0("mplayer EDL file, version ");
+ if (!linec || !bstr_startswith(lines[0], header)) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Bad EDL header!\n");
+ goto out;
+ }
+ struct bstr version = bstr_strip(bstr_cut(lines[0], header.len));
+ if (bstrcmp(bstr0("2"), version)) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Unsupported EDL file version!\n");
+ goto out;
+ }
+ int num_sources = 0;
+ int num_parts = 0;
+ for (int i = 1; i < linec; i++) {
+ if (bstr_startswith(lines[i], file_prefix)) {
+ num_sources++;
+ } else {
+ int comment = bstrchr(lines[i], '#');
+ if (comment >= 0)
+ lines[i] = bstr_splice(lines[i], 0, comment);
+ if (bstr_strip(lines[i]).len)
+ num_parts++;
+ }
+ }
+ if (!num_parts) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "No parts in timeline!\n");
+ goto out;
+ }
+
+ // Parse source filename definitions
+
+ struct edl_source *edl_ids = talloc_array_ptrtype(tmpmem, edl_ids,
+ num_sources);
+ num_sources = 0;
+ for (int i = 1; i < linec; i++) {
+ struct bstr line = lines[i];
+ if (!bstr_startswith(line, file_prefix))
+ continue;
+ line = bstr_cut(line, file_prefix.len);
+ struct bstr id = bstr_split(line, WHITESPACE, &line);
+ if (find_edl_source(edl_ids, num_sources, id) >= 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Repeated ID on line %d!\n",
+ i+1);
+ goto out;
+ }
+ if (!isalpha(*id.start)) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Invalid ID on line %d!\n",
+ i+1);
+ goto out;
+ }
+ char *filename = mp_basename(bstrdup0(tmpmem, bstr_strip(line)));
+ if (!strlen(filename)) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR,
+ "EDL: Invalid filename on line %d!\n", i+1);
+ goto out;
+ }
+ struct bstr dirname = mp_dirname(mpctx->demuxer->filename);
+ char *fullname = mp_path_join(tmpmem, dirname, bstr0(filename));
+ edl_ids[num_sources++] = (struct edl_source){id, fullname, i+1};
+ }
+
+ // Parse timeline part definitions
+
+ struct edl_part *parts = talloc_array_ptrtype(tmpmem, parts, num_parts);
+ int total_parts = num_parts;
+ num_parts = 0;
+ for (int i = 1; i < linec; i++) {
+ struct bstr line = bstr_strip(lines[i]);
+ if (!line.len || bstr_startswith(line, file_prefix))
+ continue;
+ parts[num_parts] = (struct edl_part){{-1, -1}, {-1, -1}, 0, -1};
+ parts[num_parts].lineno = i + 1;
+ for (int s = 0; s < 2; s++) {
+ struct edl_time *p = !s ? &parts[num_parts].tl :
+ &parts[num_parts].src;
+ while (1) {
+ struct bstr t = bstr_split(line, WHITESPACE, &line);
+ if (!t.len) {
+ if (!s && num_parts < total_parts - 1) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: missing source "
+ "identifier on line %d (not last)!\n", i+1);
+ goto out;
+ }
+ break;
+ }
+ if (isalpha(*t.start)) {
+ if (s)
+ goto bad;
+ parts[num_parts].id = find_edl_source(edl_ids, num_sources,
+ t);
+ if (parts[num_parts].id < 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Undefined source "
+ "identifier on line %d!\n", i+1);
+ goto out;
+ }
+ break;
+ }
+ while (t.len) {
+ struct bstr next;
+ struct bstr arg = bstr_split(t, "+-", &next);
+ if (!arg.len) {
+ next = bstr_split(line, WHITESPACE, &line);
+ arg = bstr_split(next, "+-", &next);
+ }
+ if (!arg.len)
+ goto bad;
+ int64_t val;
+ if (!bstrcmp(arg, bstr0("*")))
+ val = -1;
+ else if (isdigit(*arg.start)) {
+ val = bstrtoll(arg, &arg, 10) * 1000000000;
+ if (arg.len && *arg.start == '.') {
+ int len = arg.len - 1;
+ arg = bstr_splice(arg, 1, 10);
+ int64_t val2 = bstrtoll(arg, &arg, 10);
+ if (arg.len)
+ goto bad;
+ for (; len < 9; len++)
+ val2 *= 10;
+ val += val2;
+ }
+ } else
+ goto bad;
+ int c = *t.start;
+ if (isdigit(c) || c == '*') {
+ if (val < 0)
+ p->implied_start = true;
+ else
+ p->start = val;
+ } else if (c == '-') {
+ if (val < 0)
+ p->implied_end = true;
+ else
+ p->end = val;
+ } else if (c == '+') {
+ if (val < 0)
+ goto bad;
+ if (val == 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: zero duration "
+ "on line %d!\n", i+1);
+ goto out;
+ }
+ parts[num_parts].duration = val;
+ } else
+ goto bad;
+ t = next;
+ }
+ }
+ }
+ num_parts++;
+ continue;
+ bad:
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Malformed line %d!\n", i+1);
+ goto out;
+ }
+
+ // Fill in implied start/stop/duration values
+
+ int64_t *times = talloc_zero_array(tmpmem, int64_t, num_sources);
+ while (1) {
+ int64_t time = 0;
+ for (int i = 0; i < num_parts; i++) {
+ for (int s = 0; s < 2; s++) {
+ struct edl_time *p = s ? &parts[i].tl : &parts[i].src;
+ if (!s && parts[i].id == -1)
+ continue;
+ int64_t *t = s ? &time : times + parts[i].id;
+ p->implied_start |= s && *t >= 0;
+ if (p->implied_start && p->start >= 0 && *t >= 0
+ && p->start != *t) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Inconsistent line "
+ "%d!\n", parts[i].lineno);
+ goto out;
+ }
+ if (p->start >= 0)
+ *t = p->start;
+ if (p->implied_start)
+ p->start = *t;
+ if (*t >= 0 && parts[i].duration)
+ *t += parts[i].duration;
+ else
+ *t = -1;
+ if (p->end >= 0)
+ *t = p->end;
+ }
+ }
+ for (int i = 0; i < num_sources; i++)
+ times[i] = -1;
+ time = -1;
+ for (int i = num_parts - 1; i >= 0; i--) {
+ for (int s = 0; s < 2; s++) {
+ struct edl_time *p = s ? &parts[i].tl : &parts[i].src;
+ if (!s && parts[i].id == -1)
+ continue;
+ int64_t *t = s ? &time : times + parts[i].id;
+ p->implied_end |= s && *t >= 0;
+ if (p->implied_end && p->end >= 0 && *t >=0 && p->end != *t) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Inconsistent line "
+ "%d!\n", parts[i].lineno);
+ goto out;
+ }
+ if (p->end >= 0)
+ *t = p->end;
+ if (p->implied_end)
+ p->end = *t;
+ if (*t >= 0 && parts[i].duration) {
+ *t -= parts[i].duration;
+ if (*t < 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Negative time "
+ "on line %d!\n", parts[i].lineno);
+ goto out;
+ }
+ } else
+ *t = -1;
+ if (p->start >= 0)
+ *t = p->start;
+ }
+ }
+ int missing_duration = -1;
+ int missing_srcstart = -1;
+ bool anything_done = false;
+ for (int i = 0; i < num_parts; i++) {
+ int64_t duration = parts[i].duration;
+ if (parts[i].tl.start >= 0 && parts[i].tl.end >= 0) {
+ int64_t duration2 = parts[i].tl.end - parts[i].tl.start;
+ if (duration && duration != duration2)
+ goto incons;
+ duration = duration2;
+ if (duration <= 0)
+ goto neg;
+ }
+ if (parts[i].src.start >= 0 && parts[i].src.end >= 0) {
+ int64_t duration2 = parts[i].src.end - parts[i].src.start;
+ if (duration && duration != duration2) {
+ incons:
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Inconsistent line "
+ "%d!\n", i+1);
+ goto out;
+ }
+ duration = duration2;
+ if (duration <= 0) {
+ neg:
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: duration <= 0 on "
+ "line %d!\n", parts[i].lineno);
+ goto out;
+ }
+ }
+ if (parts[i].id == -1)
+ continue;
+ if (!duration)
+ missing_duration = i;
+ else if (!parts[i].duration)
+ anything_done = true;
+ parts[i].duration = duration;
+ if (duration && parts[i].src.start < 0) {
+ if (parts[i].src.end < 0)
+ missing_srcstart = i;
+ else
+ parts[i].src.start = parts[i].src.end - duration;
+ }
+ }
+ if (!anything_done) {
+ if (missing_duration >= 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Could not determine "
+ "duration for line %d!\n",
+ parts[missing_duration].lineno);
+ goto out;
+ }
+ if (missing_srcstart >= 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: no source start time for "
+ "line %d!\n", parts[missing_srcstart].lineno);
+ goto out;
+ }
+ break;
+ }
+ }
+
+ // Open source files
+
+ struct demuxer **sources = talloc_array_ptrtype(NULL, sources,
+ num_sources + 1);
+ mpctx->sources = sources;
+ sources[0] = mpctx->demuxer;
+ mpctx->num_sources = 1;
+
+ for (int i = 0; i < num_sources; i++) {
+ struct stream *s = stream_open(edl_ids[i].filename, mpctx->opts);
+ if (!s)
+ goto openfail;
+ struct demuxer *d = demux_open(s, NULL, NULL, mpctx->opts);
+ if (!d) {
+ free_stream(s);
+ openfail:
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "EDL: Could not open source "
+ "file on line %d!\n", edl_ids[i].lineno);
+ goto out;
+ }
+ sources[mpctx->num_sources] = d;
+ mpctx->num_sources++;
+ }
+
+ // Write final timeline structure
+
+ struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline,
+ num_parts + 1);
+ int64_t starttime = 0;
+ for (int i = 0; i < num_parts; i++) {
+ timeline[i].start = starttime / 1e9;
+ starttime += parts[i].duration;
+ timeline[i].source_start = parts[i].src.start / 1e9;
+ timeline[i].source = sources[parts[i].id + 1];
+ }
+ if (parts[num_parts - 1].id != -1) {
+ timeline[num_parts].start = starttime / 1e9;
+ num_parts++;
+ }
+ mpctx->timeline = timeline;
+ mpctx->num_timeline_parts = num_parts - 1;
+
+ out:
+ talloc_free(tmpmem);
+}
diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c
new file mode 100644
index 0000000000..cc6e62d429
--- /dev/null
+++ b/mpvcore/timeline/tl_matroska.c
@@ -0,0 +1,374 @@
+/*
+ * 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 <assert.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <libavutil/common.h>
+
+#include "osdep/io.h"
+
+#include "talloc.h"
+
+#include "core/mp_core.h"
+#include "core/mp_msg.h"
+#include "demux/demux.h"
+#include "core/path.h"
+#include "core/bstr.h"
+#include "core/mp_common.h"
+#include "stream/stream.h"
+
+struct find_entry {
+ char *name;
+ int matchlen;
+ off_t size;
+};
+
+static int cmp_entry(const void *pa, const void *pb)
+{
+ const struct find_entry *a = pa, *b = pb;
+ // check "similar" filenames first
+ int matchdiff = b->matchlen - a->matchlen;
+ if (matchdiff)
+ return FFSIGN(matchdiff);
+ // check small files first
+ off_t sizediff = a->size - b->size;
+ if (sizediff)
+ return FFSIGN(sizediff);
+ return 0;
+}
+
+static char **find_files(const char *original_file, const char *suffix)
+{
+ void *tmpmem = talloc_new(NULL);
+ char *basename = mp_basename(original_file);
+ struct bstr directory = mp_dirname(original_file);
+ char **results = talloc_size(NULL, 0);
+ char *dir_zero = bstrdup0(tmpmem, directory);
+ DIR *dp = opendir(dir_zero);
+ if (!dp) {
+ talloc_free(tmpmem);
+ return results;
+ }
+ struct find_entry *entries = NULL;
+ struct dirent *ep;
+ int num_results = 0;
+ while ((ep = readdir(dp))) {
+ int suffix_offset = strlen(ep->d_name) - strlen(suffix);
+ // name must end with suffix
+ if (suffix_offset < 0 || strcmp(ep->d_name + suffix_offset, suffix))
+ continue;
+ // don't list the original name
+ if (!strcmp(ep->d_name, basename))
+ continue;
+
+ char *name = mp_path_join(results, directory, bstr0(ep->d_name));
+ char *s1 = ep->d_name;
+ char *s2 = basename;
+ int matchlen = 0;
+ while (*s1 && *s1++ == *s2++)
+ matchlen++;
+ // be a bit more fuzzy about matching the filename
+ matchlen = (matchlen + 3) / 5;
+
+ struct stat statbuf;
+ if (stat(name, &statbuf) != 0)
+ continue;
+ off_t size = statbuf.st_size;
+
+ entries = talloc_realloc(tmpmem, entries, struct find_entry,
+ num_results + 1);
+ entries[num_results] = (struct find_entry) { name, matchlen, size };
+ num_results++;
+ }
+ closedir(dp);
+ // NOTE: maybe should make it compare pointers instead
+ if (entries)
+ qsort(entries, num_results, sizeof(struct find_entry), cmp_entry);
+ results = talloc_realloc(NULL, results, char *, num_results);
+ for (int i = 0; i < num_results; i++) {
+ results[i] = entries[i].name;
+ }
+ talloc_free(tmpmem);
+ return results;
+}
+
+static int enable_cache(struct MPContext *mpctx, struct stream **stream,
+ struct demuxer **demuxer, struct demuxer_params *params)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ if (opts->stream_cache_size <= 0)
+ return 0;
+
+ char *filename = talloc_strdup(NULL, (*demuxer)->filename);
+ free_demuxer(*demuxer);
+ free_stream(*stream);
+
+ *stream = stream_open(filename, opts);
+ if (!*stream) {
+ talloc_free(filename);
+ return -1;
+ }
+
+ stream_enable_cache_percent(stream,
+ opts->stream_cache_size,
+ opts->stream_cache_def_size,
+ opts->stream_cache_min_percent,
+ opts->stream_cache_seek_min_percent);
+
+ *demuxer = demux_open(*stream, "mkv", params, opts);
+ if (!*demuxer) {
+ talloc_free(filename);
+ free_stream(*stream);
+ return -1;
+ }
+
+ talloc_free(filename);
+ return 1;
+}
+
+// segment = get Nth segment of a multi-segment file
+static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources,
+ int num_sources, unsigned char uid_map[][16],
+ char *filename, int segment)
+{
+ bool was_valid = false;
+ struct demuxer_params params = {
+ .matroska_wanted_uids = uid_map,
+ .matroska_wanted_segment = segment,
+ .matroska_was_valid = &was_valid,
+ };
+ struct stream *s = stream_open(filename, mpctx->opts);
+ if (!s)
+ return false;
+ struct demuxer *d = demux_open(s, "mkv", &params, mpctx->opts);
+
+ if (!d) {
+ free_stream(s);
+ return was_valid;
+ }
+ if (d->type == DEMUXER_TYPE_MATROSKA) {
+ for (int i = 1; i < num_sources; i++) {
+ if (sources[i])
+ continue;
+ if (!memcmp(uid_map[i], d->matroska_data.segment_uid, 16)) {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "Match for source %d: %s\n",
+ i, d->filename);
+
+ if (enable_cache(mpctx, &s, &d, &params) < 0)
+ continue;
+
+ 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, unsigned char uid_map[][16],
+ char *filename, int first)
+{
+ for (int segment = first; ; segment++) {
+ if (!check_file_seg(mpctx, sources, num_sources, uid_map,
+ filename, segment))
+ break;
+ }
+}
+
+static bool missing(struct demuxer **sources, int num_sources)
+{
+ for (int i = 0; i < num_sources; i++) {
+ if (!sources[i])
+ return true;
+ }
+ return false;
+}
+
+static int find_ordered_chapter_sources(struct MPContext *mpctx,
+ struct demuxer **sources,
+ int num_sources,
+ unsigned char uid_map[][16])
+{
+ int num_filenames = 0;
+ char **filenames = NULL;
+ if (num_sources > 1) {
+ char *main_filename = mpctx->demuxer->filename;
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "This file references data from "
+ "other sources.\n");
+ if (mpctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Playback source is not a "
+ "normal disk file. Will not search for related files.\n");
+ } else {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "Will scan other files in the "
+ "same directory to find referenced sources.\n");
+ filenames = find_files(main_filename, ".mkv");
+ num_filenames = MP_TALLOC_ELEMS(filenames);
+ }
+ // Possibly get further segments appended to the first segment
+ check_file(mpctx, sources, num_sources, uid_map, main_filename, 1);
+ }
+
+ for (int i = 0; i < num_filenames; i++) {
+ if (!missing(sources, num_sources))
+ break;
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "Checking file %s\n", filenames[i]);
+ check_file(mpctx, sources, num_sources, uid_map, filenames[i], 0);
+ }
+
+ talloc_free(filenames);
+ if (missing(sources, num_sources)) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "Failed to find ordered chapter part!\n"
+ "There will be parts MISSING from the video!\n");
+ int j = 1;
+ for (int i = 1; i < num_sources; i++)
+ if (sources[i]) {
+ sources[j] = sources[i];
+ memcpy(uid_map[j], uid_map[i], 16);
+ j++;
+ }
+ num_sources = j;
+ }
+ return num_sources;
+}
+
+void build_ordered_chapter_timeline(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+
+ if (!opts->ordered_chapters) {
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "File uses ordered chapters, but "
+ "you have disabled support for them. Ignoring.\n");
+ return;
+ }
+
+ mp_msg(MSGT_CPLAYER, MSGL_INFO, "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_array_ptrtype(NULL, sources,
+ m->num_ordered_chapters+1);
+ sources[0] = mpctx->demuxer;
+ unsigned char (*uid_map)[16] = talloc_array_ptrtype(NULL, uid_map,
+ m->num_ordered_chapters + 1);
+ int num_sources = 1;
+ memcpy(uid_map[0], m->segment_uid, 16);
+
+ for (int i = 0; i < m->num_ordered_chapters; i++) {
+ struct matroska_chapter *c = m->ordered_chapters + i;
+ if (!c->has_segment_uid)
+ memcpy(c->segment_uid, m->segment_uid, 16);
+
+ for (int j = 0; j < num_sources; j++)
+ if (!memcmp(c->segment_uid, uid_map[j], 16))
+ goto found1;
+ memcpy(uid_map[num_sources], c->segment_uid, 16);
+ sources[num_sources] = NULL;
+ num_sources++;
+ found1:
+ ;
+ }
+
+ num_sources = find_ordered_chapter_sources(mpctx, sources, num_sources,
+ uid_map);
+
+
+ // +1 for terminating chapter with start time marking end of last real one
+ struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline,
+ m->num_ordered_chapters + 1);
+ struct chapter *chapters = talloc_array_ptrtype(NULL, chapters,
+ m->num_ordered_chapters);
+ uint64_t starttime = 0;
+ uint64_t missing_time = 0;
+ int part_count = 0;
+ int num_chapters = 0;
+ uint64_t prev_part_offset = 0;
+ for (int i = 0; i < m->num_ordered_chapters; i++) {
+ struct matroska_chapter *c = m->ordered_chapters + i;
+
+ int j;
+ for (j = 0; j < num_sources; j++) {
+ if (!memcmp(c->segment_uid, uid_map[j], 16))
+ goto found2;
+ }
+ missing_time += c->end - c->start;
+ continue;
+ found2:;
+ /* Only add a separate part if the time or file actually changes.
+ * Matroska files have chapter divisions that are redundant from
+ * timeline point of view because the same chapter structure is used
+ * both to specify the timeline and for normal chapter information.
+ * Removing a missing inserted external chapter can also cause this.
+ * We allow for a configurable fudge factor because of files which
+ * specify chapter end times that are one frame too early;
+ * we don't want to try seeking over a one frame gap. */
+ int64_t join_diff = c->start - starttime - prev_part_offset;
+ if (part_count == 0
+ || FFABS(join_diff) > opts->chapter_merge_threshold * 1000000
+ || sources[j] != timeline[part_count - 1].source) {
+ timeline[part_count].source = sources[j];
+ timeline[part_count].start = starttime / 1e9;
+ timeline[part_count].source_start = c->start / 1e9;
+ prev_part_offset = c->start - starttime;
+ part_count++;
+ } else if (part_count > 0 && join_diff) {
+ /* Chapter was merged at an inexact boundary;
+ * adjust timestamps to match. */
+ mp_msg(MSGT_CPLAYER, MSGL_V, "Merging timeline part %d with "
+ "offset %g ms.\n", i, join_diff / 1e6);
+ starttime += join_diff;
+ }
+ chapters[num_chapters].start = starttime / 1e9;
+ chapters[num_chapters].name = talloc_strdup(chapters, c->name);
+ starttime += c->end - c->start;
+ num_chapters++;
+ }
+ timeline[part_count].start = starttime / 1e9;
+ talloc_free(uid_map);
+
+ if (!part_count) {
+ // None of the parts come from the file itself???
+ talloc_free(sources);
+ talloc_free(timeline);
+ talloc_free(chapters);
+ return;
+ }
+
+ if (missing_time)
+ mp_msg(MSGT_CPLAYER, MSGL_ERR, "There are %.3f seconds missing "
+ "from the timeline!\n", missing_time / 1e9);
+ mpctx->sources = sources;
+ mpctx->num_sources = num_sources;
+ mpctx->timeline = timeline;
+ mpctx->num_timeline_parts = part_count;
+ mpctx->num_chapters = num_chapters;
+ mpctx->chapters = chapters;
+}
diff --git a/mpvcore/version.c b/mpvcore/version.c
new file mode 100644
index 0000000000..23a0c59bc3
--- /dev/null
+++ b/mpvcore/version.c
@@ -0,0 +1,26 @@
+/*
+ * 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 "version.h"
+#ifdef NO_BUILD_TIMESTAMPS
+#undef BUILDDATE
+#define BUILDDATE "UNKNOWN"
+#endif
+
+const char *mplayer_version = "mpv " VERSION;
+const char *mplayer_builddate = BUILDDATE;