aboutsummaryrefslogtreecommitdiffhomepage
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/asxparser.c581
-rw-r--r--common/asxparser.h27
-rw-r--r--common/av_common.c178
-rw-r--r--common/av_common.h39
-rw-r--r--common/av_log.c176
-rw-r--r--common/av_log.h2
-rw-r--r--common/av_opts.c55
-rw-r--r--common/av_opts.h30
-rw-r--r--common/codecs.c147
-rw-r--r--common/codecs.h43
-rw-r--r--common/common.c163
-rw-r--r--common/common.h81
-rw-r--r--common/cpudetect.c56
-rw-r--r--common/cpudetect.h40
-rw-r--r--common/encode.h21
-rw-r--r--common/encode_lavc.c1115
-rw-r--r--common/encode_lavc.h101
-rw-r--r--common/global.h12
-rw-r--r--common/msg.c389
-rw-r--r--common/msg.h180
-rw-r--r--common/playlist.c241
-rw-r--r--common/playlist.h82
-rw-r--r--common/playlist_parser.c566
-rw-r--r--common/playlist_parser.h29
-rw-r--r--common/version.c26
25 files changed, 4380 insertions, 0 deletions
diff --git a/common/asxparser.c b/common/asxparser.c
new file mode 100644
index 0000000000..5b1d5652a3
--- /dev/null
+++ b/common/asxparser.c
@@ -0,0 +1,581 @@
+/*
+ * 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 "common/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;
+
+ if(parser->deep > 0)
+ return;
+
+ href = asx_get_attrib("HREF",_attribs);
+ if(href == NULL) {
+ asx_warning_attrib_required(parser,"ENTRYREF" ,"HREF" );
+ return;
+ }
+ mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Recursive playlist %s\n", href);
+ playlist_add_file(parser->pl, href);
+ 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/common/asxparser.h b/common/asxparser.h
new file mode 100644
index 0000000000..e49a2cedc0
--- /dev/null
+++ b/common/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/common/av_common.c b/common/av_common.c
new file mode 100644
index 0000000000..cccb0f755e
--- /dev/null
+++ b/common/av_common.c
@@ -0,0 +1,178 @@
+/*
+ * 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 "common/common.h"
+#include "common/msg.h"
+#include "demux/packet.h"
+#include "av_common.h"
+#include "codecs.h"
+
+#include "osdep/numcores.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;
+}
+
+// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
+union pts { int64_t i; double d; };
+
+// Convert the mpv style timestamp (seconds as double) to a libavcodec style
+// timestamp (integer units in a given timebase).
+//
+// If the given timebase is NULL or invalid, pass through the mpv timestamp by
+// reinterpret casting them to int64_t. In this case, the timestamps will be
+// non-sense for libavcodec, but we expect that it doesn't interpret them,
+// and treats them as opaque.
+int64_t mp_pts_to_av(double mp_pts, AVRational *tb)
+{
+ assert(sizeof(int64_t) >= sizeof(double));
+ if (tb && tb->num > 0 && tb->den > 0)
+ return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : mp_pts / av_q2d(*tb);
+ // The + 0.0 is to squash possible negative zero mp_pts, which would
+ // happen to end up as AV_NOPTS_VALUE.
+ return (union pts){.d = mp_pts + 0.0}.i;
+}
+
+// Inverse of mp_pts_to_av(). (The timebases must be exactly the same.)
+double mp_pts_from_av(int64_t av_pts, AVRational *tb)
+{
+ assert(sizeof(int64_t) >= sizeof(double));
+ if (tb && tb->num > 0 && tb->den > 0)
+ return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(*tb);
+ // Should libavcodec set the PTS to AV_NOPTS_VALUE, it would end up as
+ // non-sense (usually negative zero) when unwrapped to double.
+ return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : (union pts){.i = av_pts}.d;
+}
+
+// Set dst from mpkt. Note that dst is not refcountable.
+// mpkt can be NULL to generate empty packets (used to flush delayed data).
+// Sets pts/dts using mp_pts_to_av(ts, tb). (Be aware of the implications.)
+// Set duration field only if tb is set.
+void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb)
+{
+ 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;
+ }
+ if (mpkt && tb && tb->num > 0 && tb->den > 0)
+ dst->duration = mpkt->duration / av_q2d(*tb);
+ dst->pts = mp_pts_to_av(mpkt ? mpkt->pts : MP_NOPTS_VALUE, tb);
+ dst->dts = mp_pts_to_av(mpkt ? mpkt->dts : MP_NOPTS_VALUE, tb);
+}
+
+void mp_set_avcodec_threads(AVCodecContext *avctx, int threads)
+{
+ if (threads == 0) {
+ threads = default_thread_count();
+ if (threads < 1) {
+ mp_msg(MSGT_GLOBAL, MSGL_WARN, "Could not determine "
+ "thread count to use, defaulting to 1.\n");
+ threads = 1;
+ }
+ // Apparently some libavcodec versions have or had trouble with more
+ // than 16 threads, and/or print a warning when using > 16.
+ threads = MPMIN(threads, 16);
+ }
+ avctx->thread_count = threads;
+}
+
+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/common/av_common.h b/common/av_common.h
new file mode 100644
index 0000000000..7bf2d64d9e
--- /dev/null
+++ b/common/av_common.h
@@ -0,0 +1,39 @@
+/*
+ * 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 <inttypes.h>
+
+#include <libavutil/avutil.h>
+#include <libavutil/rational.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, AVRational *tb);
+int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
+double mp_pts_from_av(int64_t av_pts, AVRational *tb);
+void mp_set_avcodec_threads(AVCodecContext *avctx, int threads);
+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/common/av_log.c b/common/av_log.c
new file mode 100644
index 0000000000..ba47500572
--- /dev/null
+++ b/common/av_log.c
@@ -0,0 +1,176 @@
+/*
+ * 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 "common/msg.h"
+#include <libavutil/avutil.h>
+#include <libavutil/log.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswscale/swscale.h>
+
+#if HAVE_LIBAVDEVICE
+#include <libavdevice/avdevice.h>
+#endif
+
+#if HAVE_LIBAVFILTER
+#include <libavfilter/avfilter.h>
+#endif
+
+#if HAVE_LIBAVRESAMPLE
+#include <libavresample/avresample.h>
+#endif
+#if HAVE_LIBSWRESAMPLE
+#include <libswresample/swresample.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_V;
+ 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();
+
+#if HAVE_LIBAVFILTER
+ avfilter_register_all();
+#endif
+#if HAVE_LIBAVDEVICE
+ avdevice_register_all();
+#endif
+}
+
+#define V(x) (x)>>16, (x)>>8 & 255, (x) & 255
+static void print_version(int v, char *name, unsigned buildv, unsigned runv)
+{
+ mp_msg(MSGT_CPLAYER, v, " %-15s %d.%d.%d", name, V(buildv));
+ if (buildv != runv)
+ mp_msg(MSGT_CPLAYER, v, " (runtime %d.%d.%d)", V(runv));
+ mp_msg(MSGT_CPLAYER, v, "\n");
+}
+#undef V
+
+void print_libav_versions(int v)
+{
+ mp_msg(MSGT_CPLAYER, v, "%s library versions:\n", LIB_PREFIX);
+
+ print_version(v, "libavutil", LIBAVUTIL_VERSION_INT, avutil_version());
+ print_version(v, "libavcodec", LIBAVCODEC_VERSION_INT, avcodec_version());
+ print_version(v, "libavformat", LIBAVFORMAT_VERSION_INT, avformat_version());
+ print_version(v, "libswscale", LIBSWSCALE_VERSION_INT, swscale_version());
+#if HAVE_LIBAVFILTER
+ print_version(v, "libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version());
+#endif
+#if HAVE_LIBAVRESAMPLE
+ print_version(v, "libavresample", LIBAVRESAMPLE_VERSION_INT, avresample_version());
+#endif
+#if HAVE_LIBSWRESAMPLE
+ print_version(v, "libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version());
+#endif
+}
diff --git a/common/av_log.h b/common/av_log.h
new file mode 100644
index 0000000000..d5c57b0aeb
--- /dev/null
+++ b/common/av_log.h
@@ -0,0 +1,2 @@
+void init_libav(void);
+void print_libav_versions(int v);
diff --git a/common/av_opts.c b/common/av_opts.c
new file mode 100644
index 0000000000..777a1eec5a
--- /dev/null
+++ b/common/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/common/av_opts.h b/common/av_opts.h
new file mode 100644
index 0000000000..640443a352
--- /dev/null
+++ b/common/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/common/codecs.c b/common/codecs.c
new file mode 100644
index 0000000000..b7639a4576
--- /dev/null
+++ b/common/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 "talloc.h"
+#include "bstr/bstr.h"
+#include "common/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/common/codecs.h b/common/codecs.h
new file mode 100644
index 0000000000..21ff284617
--- /dev/null
+++ b/common/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/common/common.c b/common/common.c
new file mode 100644
index 0000000000..365a369425
--- /dev/null
+++ b/common/common.c
@@ -0,0 +1,163 @@
+/*
+ * 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 "bstr/bstr.h"
+#include "common/common.h"
+
+#define appendf(ptr, ...) \
+ do {(*(ptr)) = talloc_asprintf_append_buffer(*(ptr), __VA_ARGS__);} while(0)
+
+// Return a talloc'ed string formatted according to the format string in fmt.
+// On error, return NULL.
+// Valid formats:
+// %H, %h: hour (%H is padded with 0 to two digits)
+// %M: minutes from 00-59 (hours are subtracted)
+// %m: total minutes (includes hours, unlike %M)
+// %S: seconds from 00-59 (minutes and hours are subtracted)
+// %s: total seconds (includes hours and minutes)
+// %f: like %s, but as float
+// %T: milliseconds (000-999)
+char *mp_format_time_fmt(const char *fmt, double time)
+{
+ if (time == MP_NOPTS_VALUE)
+ return talloc_strdup(NULL, "unknown");
+ char *sign = time < 0 ? "-" : "";
+ time = time < 0 ? -time : time;
+ long long int itime = time;
+ long long int h, m, tm, s;
+ int ms;
+ s = itime;
+ tm = s / 60;
+ h = s / 3600;
+ s -= h * 3600;
+ m = s / 60;
+ s -= m * 60;
+ ms = (time - itime) * 1000;
+ char *res = talloc_strdup(NULL, "");
+ while (*fmt) {
+ if (fmt[0] == '%') {
+ fmt++;
+ switch (fmt[0]) {
+ case 'h': appendf(&res, "%s%lld", sign, h); break;
+ case 'H': appendf(&res, "%s%02lld", sign, h); break;
+ case 'm': appendf(&res, "%s%lld", sign, tm); break;
+ case 'M': appendf(&res, "%02lld", m); break;
+ case 's': appendf(&res, "%s%lld", sign, itime); break;
+ case 'S': appendf(&res, "%02lld", s); break;
+ case 'T': appendf(&res, "%03d", ms); break;
+ case '%': appendf(&res, "%s", "%"); break;
+ default: goto error;
+ }
+ fmt++;
+ } else {
+ appendf(&res, "%c", *fmt);
+ fmt++;
+ }
+ }
+ return res;
+error:
+ talloc_free(res);
+ return NULL;
+}
+
+char *mp_format_time(double time, bool fractions)
+{
+ return mp_format_time_fmt(fractions ? "%H:%M:%S.%T" : "%H:%M:%S", time);
+}
+
+// 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/common/common.h b/common/common.h
new file mode 100644
index 0000000000..9e751ee851
--- /dev/null
+++ b/common/common.h
@@ -0,0 +1,81 @@
+/*
+ * 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 "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 MPCLAMP(a, min, max) (((a) < (min)) ? (min) : (((a) > (max)) ? (max) : (a)))
+#define MPSWAP(type, a, b) \
+ do { type SWAP_tmp = b; b = a; a = SWAP_tmp; } while (0)
+#define MP_ARRAY_SIZE(s) (sizeof(s) / sizeof((s)[0]))
+
+// align must be a power of two (align >= 1), x >= 0
+#define MP_ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1))
+#define MP_ALIGN_DOWN(x, align) ((x) & ~((align) - 1))
+
+#define CONTROL_OK 1
+#define CONTROL_TRUE 1
+#define CONTROL_FALSE 0
+#define CONTROL_UNKNOWN -1
+#define CONTROL_ERROR -2
+#define CONTROL_NA -3
+
+enum stream_type {
+ STREAM_VIDEO,
+ STREAM_AUDIO,
+ STREAM_SUB,
+ STREAM_TYPE_COUNT,
+};
+
+extern const char *mplayer_version;
+extern const char *mplayer_builddate;
+
+char *mp_format_time(double time, bool fractions);
+char *mp_format_time_fmt(const char *fmt, double time);
+
+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/common/cpudetect.c b/common/cpudetect.c
new file mode 100644
index 0000000000..ea28cf4dd4
--- /dev/null
+++ b/common/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 "common/cpudetect.h"
+#include "common/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/common/cpudetect.h b/common/cpudetect.h
new file mode 100644
index 0000000000..d3d9206c65
--- /dev/null
+++ b/common/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/common/encode.h b/common/encode.h
new file mode 100644
index 0000000000..fec14045ed
--- /dev/null
+++ b/common/encode.h
@@ -0,0 +1,21 @@
+#ifndef MPLAYER_ENCODE_H
+#define MPLAYER_ENCODE_H
+
+#include <stdbool.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, int 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/common/encode_lavc.c b/common/encode_lavc.c
new file mode 100644
index 0000000000..97a45ccbbe
--- /dev/null
+++ b/common/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 <libavutil/avutil.h>
+
+#include "encode_lavc.h"
+#include "common/msg.h"
+#include "video/vfcap.h"
+#include "options/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)\n");
+
+ 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 AVPixelFormat pix_fmt)
+{
+ CHECK_FAIL(ctx, 0);
+
+ if (!ctx->vc)
+ return 0;
+ if (pix_fmt == AV_PIX_FMT_NONE)
+ return 0;
+
+ if (!ctx->vc->pix_fmts)
+ return VFCAP_CSP_SUPPORTED;
+ else {
+ const enum AVPixelFormat *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, int 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/common/encode_lavc.h b/common/encode_lavc.h
new file mode 100644
index 0000000000..15e0a5c7f2
--- /dev/null
+++ b/common/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 AVPixelFormat 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/common/global.h b/common/global.h
new file mode 100644
index 0000000000..546c585294
--- /dev/null
+++ b/common/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/common/msg.c b/common/msg.c
new file mode 100644
index 0000000000..dd03be0f73
--- /dev/null
+++ b/common/msg.c
@@ -0,0 +1,389 @@
+/*
+ * 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 "common/global.h"
+#include "osdep/getch2.h"
+#include "osdep/io.h"
+
+#ifndef __MINGW32__
+#include <signal.h>
+#endif
+
+#include "common/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
+}
+
+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);
+}
+
+// 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);
+}
diff --git a/common/msg.h b/common/msg.h
new file mode 100644
index 0000000000..889d99ae62
--- /dev/null
+++ b/common/msg.h
@@ -0,0 +1,180 @@
+/*
+ * 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 "common/common.h"
+
+// Note: using mp_msg_log or the MP_ERR/... macros is preferred.
+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);
+
+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);
+
+// 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_DBG2, __VA_ARGS__)
+#define MP_TRACE(obj, ...) MP_MSG(obj, MSGL_DBG5, __VA_ARGS__)
+
+#define mp_fatal(log, ...) mp_msg_log(log, MSGL_FATAL, __VA_ARGS__)
+#define mp_err(log, ...) mp_msg_log(log, MSGL_ERR, __VA_ARGS__)
+#define mp_warn(log, ...) mp_msg_log(log, MSGL_WARN, __VA_ARGS__)
+#define mp_info(log, ...) mp_msg_log(log, MSGL_INFO, __VA_ARGS__)
+#define mp_verbose(log, ...) mp_msg_log(log, MSGL_V, __VA_ARGS__)
+#define mp_dbg(log, ...) mp_msg_log(log, MSGL_DBG2, __VA_ARGS__)
+#define mp_trace(log, ...) mp_msg_log(log, MSGL_DBG5, __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/common/playlist.c b/common/playlist.c
new file mode 100644
index 0000000000..297cb4d379
--- /dev/null
+++ b/common/playlist.c
@@ -0,0 +1,241 @@
+/*
+ * 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 "common/common.h"
+#include "talloc.h"
+#include "options/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;
+}
+
+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 (!mp_is_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/common/playlist.h b/common/playlist.h
new file mode 100644
index 0000000000..f383a85fea
--- /dev/null
+++ b/common/playlist.h
@@ -0,0 +1,82 @@
+/*
+ * 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 "bstr/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;
+
+ // Set to true if playback didn't seem to work, or if the file could be
+ // played only for a very short time. This is used to make playlist
+ // navigation just work in case the user has unplayable files in the
+ // playlist.
+ bool playback_short : 1;
+ // Set to true if not at least 1 frame (audio or video) could be played.
+ bool init_failed : 1;
+};
+
+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/common/playlist_parser.c b/common/playlist_parser.c
new file mode 100644
index 0000000000..af249f4aa5
--- /dev/null
+++ b/common/playlist_parser.c
@@ -0,0 +1,566 @@
+/*
+ * 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.
+ */
+
+/*
+ * Warning: this is outdated, crappy code. It is used only for --playlist.
+ * New or cleaned up code should be added to demux_playlist.c instead.
+ */
+
+#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 "playlist.h"
+#include "playlist_parser.h"
+#include "stream/stream.h"
+#include "demux/demux.h"
+#include "common/msg.h"
+#include "options/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 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;
+}
+
+static struct playlist *do_parse(struct stream* stream, bool forced);
+
+struct playlist *playlist_parse_file(const char *file, struct MPOpts *opts)
+{
+ stream_t *stream = stream_open(file, opts);
+ 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 = do_parse(stream, true);
+ free_stream(stream);
+
+ if (ret)
+ 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_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;
+ struct demuxer *pl_demux = demux_open(stream, "playlist", NULL, stream->opts);
+ if (pl_demux && pl_demux->playlist) {
+ playlist_transfer_entries(p.pl, pl_demux->playlist);
+ success = true;
+ }
+ free_demuxer(pl_demux);
+ if (!success && 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;
+}
diff --git a/common/playlist_parser.h b/common/playlist_parser.h
new file mode 100644
index 0000000000..a541aa2cb4
--- /dev/null
+++ b/common/playlist_parser.h
@@ -0,0 +1,29 @@
+/*
+ * 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 MPOpts;
+struct playlist;
+
+struct playlist *playlist_parse_file(const char *file, struct MPOpts *opts);
+
+#endif
diff --git a/common/version.c b/common/version.c
new file mode 100644
index 0000000000..23a0c59bc3
--- /dev/null
+++ b/common/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;